Page MenuHomePhorge

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/experimental/libkdeclarative/bindings/url.cpp b/experimental/libkdeclarative/bindings/url.cpp
index 0d8cc2171a..b003cbebb3 100644
--- a/experimental/libkdeclarative/bindings/url.cpp
+++ b/experimental/libkdeclarative/bindings/url.cpp
@@ -1,117 +1,117 @@
/*
* Copyright 2007 Richard J. Moore <rich@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 version 2 as
* published by the Free Software Foundation
*
* 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 <QtScript/QScriptValue>
#include <QtScript/QScriptEngine>
#include <QtScript/QScriptContext>
#include <kurl.h>
#include "backportglobal.h"
Q_DECLARE_METATYPE(KUrl*)
//Q_DECLARE_METATYPE(KUrl) unneeded; found in kurl.h
static QScriptValue urlCtor(QScriptContext *ctx, QScriptEngine *eng)
{
if (ctx->argumentCount() == 1)
{
QString url = ctx->argument(0).toString();
return qScriptValueFromValue(eng, KUrl(url));
}
return qScriptValueFromValue(eng, KUrl());
}
static QScriptValue toString(QScriptContext *ctx, QScriptEngine *eng)
{
DECLARE_SELF(KUrl, toString);
return QScriptValue(eng, self->prettyUrl());
}
static QScriptValue protocol(QScriptContext *ctx, QScriptEngine *eng)
{
DECLARE_SELF(KUrl, protocol);
if (ctx->argumentCount()) {
QString v = ctx->argument(0).toString();
self->setProtocol(v);
}
- return QScriptValue(eng, self->protocol());
+ return QScriptValue(eng, self->scheme());
}
static QScriptValue host(QScriptContext *ctx, QScriptEngine *eng)
{
DECLARE_SELF(KUrl, protocol);
if (ctx->argumentCount()) {
QString v = ctx->argument(0).toString();
self->setHost(v);
}
return QScriptValue(eng, self->host());
}
static QScriptValue path(QScriptContext *ctx, QScriptEngine *eng)
{
DECLARE_SELF(KUrl, path);
if (ctx->argumentCount()) {
QString v = ctx->argument(0).toString();
self->setPath(v);
}
return QScriptValue(eng, self->path());
}
static QScriptValue user(QScriptContext *ctx, QScriptEngine *eng)
{
DECLARE_SELF(KUrl, user);
if (ctx->argumentCount()) {
QString v = ctx->argument(0).toString();
self->setUser(v);
}
return QScriptValue(eng, self->user());
}
static QScriptValue password(QScriptContext *ctx, QScriptEngine *eng)
{
DECLARE_SELF(KUrl, password);
if (ctx->argumentCount()) {
QString v = ctx->argument(0).toString();
self->setPassword(v);
}
return QScriptValue(eng, self->password());
}
QScriptValue constructKUrlClass(QScriptEngine *eng)
{
QScriptValue proto = qScriptValueFromValue(eng, KUrl());
QScriptValue::PropertyFlags getter = QScriptValue::PropertyGetter;
QScriptValue::PropertyFlags setter = QScriptValue::PropertySetter;
proto.setProperty("toString", eng->newFunction(toString), getter);
proto.setProperty("protocol", eng->newFunction(protocol), getter | setter);
proto.setProperty("host", eng->newFunction(host), getter | setter);
proto.setProperty("path", eng->newFunction(path), getter | setter);
proto.setProperty("user", eng->newFunction(user), getter | setter);
proto.setProperty("password", eng->newFunction(password), getter | setter);
eng->setDefaultPrototype(qMetaTypeId<KUrl*>(), proto);
eng->setDefaultPrototype(qMetaTypeId<KUrl>(), proto);
return eng->newFunction(urlCtor, proto);
}
diff --git a/kde3support/kdecore/k3urldrag.cpp b/kde3support/kdecore/k3urldrag.cpp
index 700f0ebbe4..72cd06bfd1 100644
--- a/kde3support/kdecore/k3urldrag.cpp
+++ b/kde3support/kdecore/k3urldrag.cpp
@@ -1,284 +1,284 @@
/* This file is part of the KDE project
Copyright (C) 2000 David Faure <faure@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 of the License.
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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this program; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "k3urldrag.h"
#include <Qt3Support/Q3CString>
#include <Qt3Support/Q3StrIList>
#include <Qt3Support/Q3ColorDrag>
#include <QFont>
#include <unistd.h>
#include <kglobal.h>
#include <klocale.h>
#include <kdebug.h>
class K3URLDragPrivate
{
public:
bool m_exportAsText;
};
K3URLDrag::K3URLDrag( const KUrl::List &urls, QWidget* dragSource )
: Q3UriDrag(dragSource), m_metaData(), d( 0 )
{
init(urls);
}
K3URLDrag::K3URLDrag( const KUrl::List &urls,
const QMap<QString,QString>& metaData,
QWidget* dragSource )
: Q3UriDrag(dragSource), m_metaData(metaData), d( 0 )
{
init(urls);
}
K3URLDrag::~K3URLDrag()
{
delete d;
}
void K3URLDrag::init(const KUrl::List &urls)
{
KUrl::List::ConstIterator uit = urls.begin();
KUrl::List::ConstIterator uEnd = urls.end();
// Get each URL encoded in utf8 - and since we get it in escaped
// form on top of that, .toLatin1().constData() is fine.
for ( ; uit != uEnd ; ++uit )
{
m_urls.append( urlToString(*uit).toLatin1().constData() );
}
setUris(m_urls);
}
void K3URLDrag::setExportAsText( bool exp )
{
// For now d is only used here, so create it on demand
if ( !d )
d = new K3URLDragPrivate;
d->m_exportAsText = exp;
}
K3URLDrag * K3URLDrag::newDrag( const KUrl::List &urls, QWidget* dragSource )
{
return new K3URLDrag( urls, QMap<QString, QString>(), dragSource );
}
K3URLDrag * K3URLDrag::newDrag( const KUrl::List &urls, const QMap<QString, QString>& metaData,
QWidget* dragSource )
{
return new K3URLDrag( urls, metaData, dragSource );
}
QMap<QString, QString> &K3URLDrag::metaData()
{
return m_metaData;
}
bool K3URLDrag::decode( const QMimeSource *e, KUrl::List &uris )
{
// x-kde4-urilist is the same format as text/uri-list, but contains
// KDE-aware urls, like media:/ and system:/, whereas text/uri-list is resolved to
// local files.
if ( e->provides( "application/x-kde4-urilist" ) ) {
QByteArray payload = e->encodedData( "application/x-kde4-urilist" );
if ( payload.size() ) {
int c=0;
const char* d = payload.data();
while (c < payload.size() && d[c]) {
int f = c;
// Find line end
while (c < payload.size() && d[c] && d[c]!='\r'
&& d[c] != '\n')
c++;
Q3CString s(d+f,c-f+1);
if ( s[0] != '#' ) // non-comment?
uris.append(stringToUrl(s));
// Skip junk
while (c < payload.size() && d[c] &&
(d[c]=='\n' || d[c]=='\r'))
c++;
}
return !uris.isEmpty();
}
}
Q3StrList lst;
Q3UriDrag::decode( e, lst );
for (Q3StrListIterator it(lst); *it; ++it)
{
KUrl url = stringToUrl( *it );
if ( !url.isValid() )
{
uris.clear();
break;
}
uris.append( url );
}
return !uris.isEmpty();
}
bool K3URLDrag::decode( const QMimeSource *e, KUrl::List &uris, QMap<QString,QString>& metaData )
{
if ( decode( e, uris ) ) // first decode the URLs (see above)
{
QByteArray ba = e->encodedData( "application/x-kio-metadata" );
if ( ba.size() )
{
QString s = ba.data();
const QStringList l = s.split( "$@@$", QString::SkipEmptyParts );
QStringList::ConstIterator it = l.begin();
bool readingKey = true; // true, then false, then true, etc.
QString key;
for ( ; it != l.end(); ++it ) {
if ( readingKey )
key = *it;
else
metaData.replace( key, *it );
readingKey = !readingKey;
}
Q_ASSERT( readingKey ); // an odd number of items would be, well, odd ;-)
}
return true; // Success, even if no metadata was found
}
return false; // Couldn't decode the URLs
}
////
const char * K3URLDrag::format( int i ) const
{
if ( i == 0 )
return "text/uri-list";
else if ( i == 1 )
return "application/x-kio-metadata";
if ( d && d->m_exportAsText == false )
return 0;
if ( i == 2 )
return "text/plain";
else if ( i == 3 ) //Support this for apps that use plain XA_STRING clipboard
return "text/plain;charset=ISO-8859-1";
else if ( i == 4 ) //Support this for apps that use the UTF_STRING clipboard
return "text/plain;charset=UTF-8";
else return 0;
}
QByteArray K3URLDrag::encodedData( const char* mime ) const
{
QByteArray a;
QByteArray mimetype( mime );
if ( mimetype == "text/uri-list" )
return Q3UriDrag::encodedData( mime );
else if ( mimetype == "text/plain" )
{
QStringList uris;
for (Q3StrListIterator it(m_urls); *it; ++it)
uris.append(stringToUrl(*it).prettyUrl());
QByteArray s = uris.join( "\n" ).toLocal8Bit();
if( uris.count() > 1 ) // terminate last line, unless it's the only line
s.append( "\n" );
a.resize( s.length());
memcpy( a.data(), s.data(), s.length()); // no trailing zero in clipboard text
}
else if ( mimetype.toLower() == "text/plain;charset=iso-8859-1")
{
QStringList uris;
for (Q3StrListIterator it(m_urls); *it; ++it)
uris.append(stringToUrl(*it).url()); // was using ",4" - the mib for latin1
QByteArray s = uris.join( "\n" ).toLatin1();
if( uris.count() > 1 )
s.append( "\n" );
a.resize( s.length());
memcpy( a.data(), s.data(), s.length());
}
else if ( mimetype.toLower() == "text/plain;charset=utf-8")
{
QStringList uris;
for (Q3StrListIterator it(m_urls); *it; ++it)
uris.append(stringToUrl(*it).prettyUrl());
QByteArray s = uris.join( "\n" ).toUtf8();
if( uris.count() > 1 )
s.append( "\n" );
a.resize( s.length());
memcpy( a.data(), s.data(), s.length());
}
else if ( mimetype == "application/x-kio-metadata" )
{
if ( !m_metaData.isEmpty() )
{
QString s;
QMap<QString,QString>::ConstIterator it;
for( it = m_metaData.begin(); it != m_metaData.end(); ++it )
{
s += it.key();
s += "$@@$";
s += it.data();
s += "$@@$";
}
a.resize( s.length() + 1 );
memcpy( a.data(), s.toLatin1().constData(), a.size() );
}
}
return a;
}
KUrl K3URLDrag::stringToUrl(const QByteArray &s)
{
if (strncmp(s.data(), "file:", 5) == 0)
return KUrl(s /*, KGlobal::locale()->fileEncodingMib()*/);
return KUrl(s /*, 106*/); // 106 is mib enum for utf8 codec;
}
QString K3URLDrag::urlToString(const KUrl &url)
{
if (url.isLocalFile())
{
#if 1
return url.url(/*0 , KGlobal::locale()->fileEncodingMib()*/);
#else
// According to the XDND spec, file:/ URLs for DND must have
// the hostname part. But in really it just breaks many apps,
// so it's disabled for now.
QString s = url.url(0, KGlobal::locale()->fileEncodingMib());
if( !s.startsWith( "file://" ))
{
char hostname[257];
if ( gethostname( hostname, 255 ) == 0 )
{
hostname[256] = '\0';
return QString( "file://" ) + hostname + s.mid( 5 );
}
}
#endif
}
- if ( url.protocol() == "mailto" ) {
+ if ( url.scheme() == "mailto" ) {
return url.path();
}
return url.url(/*0 , 106*/); // 106 is mib enum for utf8 codec
}
// deprecated ctor
K3URLDrag::K3URLDrag( const Q3StrList & urls, const QMap<QString,QString>& metaData,
QWidget * dragSource ) :
Q3UriDrag( urls, dragSource ), m_urls( urls ), m_metaData( metaData ), d( 0 ) {}
diff --git a/kdecore/kernel/kauthorized.cpp b/kdecore/kernel/kauthorized.cpp
index cd491c1c08..f8fa2b9745 100644
--- a/kdecore/kernel/kauthorized.cpp
+++ b/kdecore/kernel/kauthorized.cpp
@@ -1,379 +1,379 @@
/* This file is part of the KDE libraries
Copyright (C) 1997 Matthias Kalle Dalheimer (kalle@kde.org)
Copyright (C) 1998, 1999, 2000 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.
*/
#include "kauthorized.h"
#include <QtCore/QDir>
#include <QtCore/QRegExp>
#include <QList>
#include <QCoreApplication>
#include <kglobal.h>
#include <ksharedconfig.h>
#include <kprotocolinfo.h>
#include <kstandarddirs.h>
#include <stdlib.h> // srand(), rand()
#include <unistd.h>
#include <netdb.h>
#include <kurl.h>
#include <kconfiggroup.h>
#include <QMutex>
#include <QMutexLocker>
extern bool kde_kiosk_exception;
class URLActionRule
{
public:
#define checkExactMatch(s, b) \
if (s.isEmpty()) b = true; \
else if (s[s.length()-1] == QLatin1Char('!')) \
{ b = false; s.truncate(s.length()-1); } \
else b = true;
#define checkStartWildCard(s, b) \
if (s.isEmpty()) b = true; \
else if (s[0] == QLatin1Char('*')) \
{ b = true; s = s.mid(1); } \
else b = false;
#define checkEqual(s, b) \
b = (s == QString::fromLatin1("="));
URLActionRule(const QByteArray &act,
const QString &bProt, const QString &bHost, const QString &bPath,
const QString &dProt, const QString &dHost, const QString &dPath,
bool perm)
: action(act),
baseProt(bProt), baseHost(bHost), basePath(bPath),
destProt(dProt), destHost(dHost), destPath(dPath),
permission(perm)
{
checkExactMatch(baseProt, baseProtWildCard);
checkStartWildCard(baseHost, baseHostWildCard);
checkExactMatch(basePath, basePathWildCard);
checkExactMatch(destProt, destProtWildCard);
checkStartWildCard(destHost, destHostWildCard);
checkExactMatch(destPath, destPathWildCard);
checkEqual(destProt, destProtEqual);
checkEqual(destHost, destHostEqual);
}
bool baseMatch(const KUrl &url, const QString &protClass) const
{
if (baseProtWildCard)
{
- if ( !baseProt.isEmpty() && !url.protocol().startsWith(baseProt) &&
+ if ( !baseProt.isEmpty() && !url.scheme().startsWith(baseProt) &&
(protClass.isEmpty() || (protClass != baseProt)) )
return false;
}
else
{
- if ( (url.protocol() != baseProt) &&
+ if ( (url.scheme() != baseProt) &&
(protClass.isEmpty() || (protClass != baseProt)) )
return false;
}
if (baseHostWildCard)
{
if (!baseHost.isEmpty() && !url.host().endsWith(baseHost))
return false;
}
else
{
if (url.host() != baseHost)
return false;
}
if (basePathWildCard)
{
if (!basePath.isEmpty() && !url.path().startsWith(basePath))
return false;
}
else
{
if (url.path() != basePath)
return false;
}
return true;
}
bool destMatch(const KUrl &url, const QString &protClass, const KUrl &base, const QString &baseClass) const
{
if (destProtEqual)
{
- if ( (url.protocol() != base.protocol()) &&
+ if ( (url.scheme() != base.scheme()) &&
(protClass.isEmpty() || baseClass.isEmpty() || protClass != baseClass) )
return false;
}
else if (destProtWildCard)
{
- if ( !destProt.isEmpty() && !url.protocol().startsWith(destProt) &&
+ if ( !destProt.isEmpty() && !url.scheme().startsWith(destProt) &&
(protClass.isEmpty() || (protClass != destProt)) )
return false;
}
else
{
- if ( (url.protocol() != destProt) &&
+ if ( (url.scheme() != destProt) &&
(protClass.isEmpty() || (protClass != destProt)) )
return false;
}
if (destHostWildCard)
{
if (!destHost.isEmpty() && !url.host().endsWith(destHost))
return false;
}
else if (destHostEqual)
{
if (url.host() != base.host())
return false;
}
else
{
if (url.host() != destHost)
return false;
}
if (destPathWildCard)
{
if (!destPath.isEmpty() && !url.path().startsWith(destPath))
return false;
}
else
{
if (url.path() != destPath)
return false;
}
return true;
}
QByteArray action;
QString baseProt;
QString baseHost;
QString basePath;
QString destProt;
QString destHost;
QString destPath;
bool baseProtWildCard : 1;
bool baseHostWildCard : 1;
bool basePathWildCard : 1;
bool destProtWildCard : 1;
bool destHostWildCard : 1;
bool destPathWildCard : 1;
bool destProtEqual : 1;
bool destHostEqual : 1;
bool permission;
};
class KAuthorizedPrivate {
public:
KAuthorizedPrivate()
: actionRestrictions( false ), blockEverything(false),mutex(QMutex::Recursive)
{
Q_ASSERT_X(QCoreApplication::instance(),"KAuthorizedPrivate()","There has to be an existing QCoreApplication::instance() pointer");
KSharedConfig::Ptr config = KGlobal::config();
Q_ASSERT_X(config,"KAuthorizedPrivate()","There has to be an existing KGlobal::config() pointer");
if (!config) {
blockEverything=true;
return;
}
actionRestrictions = config->hasGroup("KDE Action Restrictions" ) && !kde_kiosk_exception;
}
~KAuthorizedPrivate()
{
}
bool actionRestrictions : 1;
bool blockEverything : 1;
QList<URLActionRule> urlActionRestrictions;
QMutex mutex;
};
Q_GLOBAL_STATIC(KAuthorizedPrivate,authPrivate)
#define MY_D KAuthorizedPrivate *d=authPrivate();
bool KAuthorized::authorize(const QString &genericAction)
{
MY_D
if (d->blockEverything) return false;
if (!d->actionRestrictions)
return true;
KConfigGroup cg(KGlobal::config(), "KDE Action Restrictions");
return cg.readEntry(genericAction, true);
}
bool KAuthorized::authorizeKAction(const QString& action)
{
MY_D
if (d->blockEverything) return false;
if (!d->actionRestrictions || action.isEmpty())
return true;
return authorize(QLatin1String("action/") + action);
}
bool KAuthorized::authorizeControlModule(const QString &menuId)
{
if (menuId.isEmpty() || kde_kiosk_exception)
return true;
KConfigGroup cg(KGlobal::config(), "KDE Control Module Restrictions");
return cg.readEntry(menuId, true);
}
QStringList KAuthorized::authorizeControlModules(const QStringList &menuIds)
{
KConfigGroup cg(KGlobal::config(), "KDE Control Module Restrictions");
QStringList result;
for(QStringList::ConstIterator it = menuIds.begin();
it != menuIds.end(); ++it)
{
if (cg.readEntry(*it, true))
result.append(*it);
}
return result;
}
static void initUrlActionRestrictions()
{
MY_D
const QString Any;
d->urlActionRestrictions.clear();
d->urlActionRestrictions.append(
URLActionRule("open", Any, Any, Any, Any, Any, Any, true));
d->urlActionRestrictions.append(
URLActionRule("list", Any, Any, Any, Any, Any, Any, true));
// TEST:
// d->urlActionRestrictions.append(
// URLActionRule("list", Any, Any, Any, Any, Any, Any, false));
// d->urlActionRestrictions.append(
// URLActionRule("list", Any, Any, Any, "file", Any, QDir::homePath(), true));
d->urlActionRestrictions.append(
URLActionRule("link", Any, Any, Any, QLatin1String(":internet"), Any, Any, true));
d->urlActionRestrictions.append(
URLActionRule("redirect", Any, Any, Any, QLatin1String(":internet"), Any, Any, true));
// We allow redirections to file: but not from internet protocols, redirecting to file:
// is very popular among io-slaves and we don't want to break them
d->urlActionRestrictions.append(
URLActionRule("redirect", Any, Any, Any, QLatin1String("file"), Any, Any, true));
d->urlActionRestrictions.append(
URLActionRule("redirect", QLatin1String(":internet"), Any, Any, QLatin1String("file"), Any, Any, false));
// local protocols may redirect everywhere
d->urlActionRestrictions.append(
URLActionRule("redirect", QLatin1String(":local"), Any, Any, Any, Any, Any, true));
// Anyone may redirect to about:
d->urlActionRestrictions.append(
URLActionRule("redirect", Any, Any, Any, QLatin1String("about"), Any, Any, true));
// Anyone may redirect to itself, cq. within it's own group
d->urlActionRestrictions.append(
URLActionRule("redirect", Any, Any, Any, QLatin1String("="), Any, Any, true));
d->urlActionRestrictions.append(
URLActionRule("redirect", QLatin1String("about"), Any, Any, Any, Any, Any, true));
KConfigGroup cg(KGlobal::config(), "KDE URL Restrictions");
int count = cg.readEntry("rule_count", 0);
QString keyFormat = QString::fromLatin1("rule_%1");
for(int i = 1; i <= count; i++)
{
QString key = keyFormat.arg(i);
const QStringList rule = cg.readEntry(key, QStringList());
if (rule.count() != 8)
continue;
const QByteArray action = rule[0].toLatin1();
QString refProt = rule[1];
QString refHost = rule[2];
QString refPath = rule[3];
QString urlProt = rule[4];
QString urlHost = rule[5];
QString urlPath = rule[6];
bool bEnabled = (rule[7].toLower() == QLatin1String("true"));
if (refPath.startsWith(QLatin1String("$HOME")))
refPath.replace(0, 5, QDir::homePath());
else if (refPath.startsWith(QLatin1Char('~')))
refPath.replace(0, 1, QDir::homePath());
if (urlPath.startsWith(QLatin1String("$HOME")))
urlPath.replace(0, 5, QDir::homePath());
else if (urlPath.startsWith(QLatin1Char('~')))
urlPath.replace(0, 1, QDir::homePath());
if (refPath.startsWith(QLatin1String("$TMP")))
refPath.replace(0, 4, KGlobal::dirs()->saveLocation("tmp"));
if (urlPath.startsWith(QLatin1String("$TMP")))
urlPath.replace(0, 4, KGlobal::dirs()->saveLocation("tmp"));
d->urlActionRestrictions.append(
URLActionRule( action, refProt, refHost, refPath, urlProt, urlHost, urlPath, bEnabled));
}
}
void KAuthorized::allowUrlAction(const QString &action, const KUrl &_baseURL, const KUrl &_destURL)
{
MY_D
QMutexLocker locker((&d->mutex));
if (authorizeUrlAction(action, _baseURL, _destURL))
return;
d->urlActionRestrictions.append( URLActionRule
- ( action.toLatin1(), _baseURL.protocol(), _baseURL.host(), _baseURL.path(KUrl::RemoveTrailingSlash),
- _destURL.protocol(), _destURL.host(), _destURL.path(KUrl::RemoveTrailingSlash), true));
+ ( action.toLatin1(), _baseURL.scheme(), _baseURL.host(), _baseURL.path(KUrl::RemoveTrailingSlash),
+ _destURL.scheme(), _destURL.host(), _destURL.path(KUrl::RemoveTrailingSlash), true));
}
bool KAuthorized::authorizeUrlAction(const QString &action, const KUrl &_baseURL, const KUrl &_destURL)
{
MY_D
QMutexLocker locker(&(d->mutex));
if (d->blockEverything) return false;
if (_destURL.isEmpty())
return true;
bool result = false;
if (d->urlActionRestrictions.isEmpty())
initUrlActionRestrictions();
KUrl baseURL(_baseURL);
baseURL.setPath(QDir::cleanPath(baseURL.path()));
- QString baseClass = KProtocolInfo::protocolClass(baseURL.protocol());
+ QString baseClass = KProtocolInfo::protocolClass(baseURL.scheme());
KUrl destURL(_destURL);
destURL.setPath(QDir::cleanPath(destURL.path()));
- QString destClass = KProtocolInfo::protocolClass(destURL.protocol());
+ QString destClass = KProtocolInfo::protocolClass(destURL.scheme());
foreach(const URLActionRule &rule, d->urlActionRestrictions) {
if ((result != rule.permission) && // No need to check if it doesn't make a difference
(action == QLatin1String(rule.action)) &&
rule.baseMatch(baseURL, baseClass) &&
rule.destMatch(destURL, destClass, baseURL, baseClass))
{
result = rule.permission;
}
}
return result;
}
diff --git a/kdecore/kernel/ktoolinvocation.cpp b/kdecore/kernel/ktoolinvocation.cpp
index 328a065716..dc547062ee 100644
--- a/kdecore/kernel/ktoolinvocation.cpp
+++ b/kdecore/kernel/ktoolinvocation.cpp
@@ -1,396 +1,396 @@
/* This file is part of the KDE libraries
Copyright (C) 2005 Brad Hards <bradh@frogmouth.net>
Copyright (C) 2006 Thiago Macieira <thiago@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 "ktoolinvocation.h"
#include "klauncher_iface.h"
#include "kdebug.h"
//#include "kglobal.h"
//#include "kcomponentdata.h"
#include "kurl.h"
#include "kmessage.h"
#include "kservice.h"
#include <klockfile.h>
#include <klocale.h>
#include <QMutex>
#include <QMutexLocker>
#include <QCoreApplication>
#include <QThread>
#include <qstandardpaths.h>
#include <errno.h>
KToolInvocation *KToolInvocation::self()
{
K_GLOBAL_STATIC(KToolInvocation, s_self)
return s_self;
}
KToolInvocation::KToolInvocation() : QObject(0), d(0)
{
}
KToolInvocation::~KToolInvocation()
{
}
Q_GLOBAL_STATIC_WITH_ARGS(org::kde::KLauncher, klauncherIface,
(QString::fromLatin1("org.kde.klauncher"), QString::fromLatin1("/KLauncher"), QDBusConnection::sessionBus()))
org::kde::KLauncher *KToolInvocation::klauncher()
{
if (!QDBusConnection::sessionBus().interface()->isServiceRegistered(QString::fromLatin1("org.kde.klauncher"))) {
kDebug(180) << "klauncher not running... launching kdeinit";
KToolInvocation::startKdeinit();
}
return ::klauncherIface();
}
static void printError(const QString& text, QString* error)
{
if (error)
*error = text;
else
kError() << text << endl;
}
bool KToolInvocation::isMainThreadActive(QString* error)
{
if (QCoreApplication::instance() && QCoreApplication::instance()->thread() != QThread::currentThread())
{
printError(i18n("Function must be called from the main thread."), error);
return false;
}
return true;
}
int KToolInvocation::startServiceInternal(const char *_function,
const QString& _name, const QStringList &URLs,
QString *error, QString *serviceName, int *pid,
const QByteArray& startup_id, bool noWait,
const QString& workdir)
{
QString function = QLatin1String(_function);
org::kde::KLauncher *launcher = KToolInvocation::klauncher();
QDBusMessage msg = QDBusMessage::createMethodCall(launcher->service(),
launcher->path(),
launcher->interface(),
function);
msg << _name << URLs;
if (function == QLatin1String("kdeinit_exec_with_workdir"))
msg << workdir;
#ifdef Q_WS_X11
// make sure there is id, so that user timestamp exists
QStringList envs;
QByteArray s = startup_id;
emit kapplication_hook(envs, s);
msg << envs;
msg << QString::fromLatin1(s);
#else
msg << QStringList();
msg << QString();
#endif
if( !function.startsWith( QLatin1String("kdeinit_exec") ) )
msg << noWait;
QDBusMessage reply = QDBusConnection::sessionBus().call(msg, QDBus::Block, INT_MAX);
if ( reply.type() != QDBusMessage::ReplyMessage )
{
QDBusReply<QString> replyObj(reply);
if (replyObj.error().type() == QDBusError::NoReply) {
printError(i18n("Error launching %1. Either KLauncher is not running anymore, or it failed to start the application.", _name), error);
} else {
const QString rpl = reply.arguments().count() > 0 ? reply.arguments().at(0).toString() : reply.errorMessage();
printError(i18n("KLauncher could not be reached via D-Bus. Error when calling %1:\n%2\n",function, rpl), error);
}
//qDebug() << reply;
return EINVAL;
}
if (noWait)
return 0;
Q_ASSERT(reply.arguments().count() == 4);
if (serviceName)
*serviceName = reply.arguments().at(1).toString();
if (error)
*error = reply.arguments().at(2).toString();
if (pid)
*pid = reply.arguments().at(3).toInt();
return reply.arguments().at(0).toInt();
}
#ifndef KDE_NO_DEPRECATED
int
KToolInvocation::startServiceByName( const QString& _name, const QString &URL,
QString *error, QString *serviceName, int *pid,
const QByteArray& startup_id, bool noWait )
{
if (!isMainThreadActive(error))
return EINVAL;
QStringList URLs;
if (!URL.isEmpty())
URLs.append(URL);
return self()->startServiceInternal("start_service_by_name",
_name, URLs, error, serviceName, pid, startup_id, noWait);
}
#endif
#ifndef KDE_NO_DEPRECATED
int
KToolInvocation::startServiceByName( const QString& _name, const QStringList &URLs,
QString *error, QString *serviceName, int *pid,
const QByteArray& startup_id, bool noWait )
{
if (!isMainThreadActive(error))
return EINVAL;
return self()->startServiceInternal("start_service_by_name",
_name, URLs, error, serviceName, pid, startup_id, noWait);
}
#endif
int
KToolInvocation::startServiceByDesktopPath( const QString& _name, const QString &URL,
QString *error, QString *serviceName,
int *pid, const QByteArray& startup_id, bool noWait )
{
if (!isMainThreadActive(error))
return EINVAL;
QStringList URLs;
if (!URL.isEmpty())
URLs.append(URL);
return self()->startServiceInternal("start_service_by_desktop_path",
_name, URLs, error, serviceName, pid, startup_id, noWait);
}
int
KToolInvocation::startServiceByDesktopPath( const QString& _name, const QStringList &URLs,
QString *error, QString *serviceName, int *pid,
const QByteArray& startup_id, bool noWait )
{
if (!isMainThreadActive(error))
return EINVAL;
return self()->startServiceInternal("start_service_by_desktop_path",
_name, URLs, error, serviceName, pid, startup_id, noWait);
}
int
KToolInvocation::startServiceByDesktopName( const QString& _name, const QString &URL,
QString *error, QString *serviceName, int *pid,
const QByteArray& startup_id, bool noWait )
{
if (!isMainThreadActive(error))
return EINVAL;
QStringList URLs;
if (!URL.isEmpty())
URLs.append(URL);
return self()->startServiceInternal("start_service_by_desktop_name",
_name, URLs, error, serviceName, pid, startup_id, noWait);
}
int
KToolInvocation::startServiceByDesktopName( const QString& _name, const QStringList &URLs,
QString *error, QString *serviceName, int *pid,
const QByteArray& startup_id, bool noWait )
{
if (!isMainThreadActive(error))
return EINVAL;
return self()->startServiceInternal("start_service_by_desktop_name",
_name, URLs, error, serviceName, pid, startup_id, noWait);
}
int
KToolInvocation::kdeinitExec( const QString& name, const QStringList &args,
QString *error, int *pid, const QByteArray& startup_id )
{
if (!isMainThreadActive(error))
return EINVAL;
return self()->startServiceInternal("kdeinit_exec",
name, args, error, 0, pid, startup_id, false);
}
int
KToolInvocation::kdeinitExecWait( const QString& name, const QStringList &args,
QString *error, int *pid, const QByteArray& startup_id )
{
if (!isMainThreadActive(error))
return EINVAL;
return self()->startServiceInternal("kdeinit_exec_wait",
name, args, error, 0, pid, startup_id, false);
}
void KToolInvocation::invokeHelp( const QString& anchor,
const QString& _appname,
const QByteArray& startup_id )
{
if (!isMainThreadActive())
return;
KUrl url;
QString appname;
QString docPath;
if (_appname.isEmpty()) {
appname = QCoreApplication::instance()->applicationName();
} else
appname = _appname;
KService::Ptr service(KService::serviceByDesktopName(appname));
if (service) {
docPath = service->docPath();
}
if (!docPath.isEmpty()) {
url = KUrl(KUrl("help:/"), docPath);
} else {
url = QString::fromLatin1("help:/%1/index.html").arg(appname);
}
if (!anchor.isEmpty()) {
url.addQueryItem(QString::fromLatin1("anchor"), anchor);
}
// launch a browser for URIs not handled by khelpcenter
// (following KCMultiDialog::slotHelpClicked())
- if (!(url.protocol() == QLatin1String("help") || url.protocol() == QLatin1String("man") || url.protocol() == QLatin1String("info"))) {
+ if (!(url.scheme() == QLatin1String("help") || url.scheme() == QLatin1String("man") || url.scheme() == QLatin1String("info"))) {
invokeBrowser(url.url());
return;
}
QDBusInterface *iface = new QDBusInterface(QLatin1String("org.kde.khelpcenter"),
QLatin1String("/KHelpCenter"),
QLatin1String("org.kde.khelpcenter.khelpcenter"),
QDBusConnection::sessionBus());
if ( !iface->isValid() )
{
QString error;
#ifdef Q_WS_WIN
// startServiceByDesktopName() does not work yet; KRun:processDesktopExec returned 'KRun: syntax error in command "khelpcenter %u" , service "KHelpCenter" '
if (kdeinitExec(QLatin1String("khelpcenter"), QStringList() << url.url(), &error, 0, startup_id))
#else
if (startServiceByDesktopName(QLatin1String("khelpcenter"), url.url(), &error, 0, 0, startup_id, false))
#endif
{
KMessage::message(KMessage::Error,
i18n("Could not launch the KDE Help Center:\n\n%1", error),
i18n("Could not Launch Help Center"));
delete iface;
return;
}
delete iface;
iface = new QDBusInterface(QLatin1String("org.kde.khelpcenter"),
QLatin1String("/KHelpCenter"),
QLatin1String("org.kde.khelpcenter.khelpcenter"),
QDBusConnection::sessionBus());
}
iface->call(QString::fromLatin1("openUrl"), url.url(), startup_id );
delete iface;
}
void KToolInvocation::invokeMailer(const QString &address, const QString &subject, const QByteArray& startup_id)
{
if (!isMainThreadActive())
return;
invokeMailer(address, QString(), QString(), subject, QString(), QString(),
QStringList(), startup_id );
}
void KToolInvocation::invokeMailer(const KUrl &mailtoURL, const QByteArray& startup_id, bool allowAttachments )
{
if (!isMainThreadActive())
return;
QString address = mailtoURL.path();
QString subject;
QString cc;
QString bcc;
QString body;
const QStringList queries = mailtoURL.query().mid(1).split(QLatin1Char('&'));
const QChar comma = QChar::fromLatin1(',');
QStringList attachURLs;
for (QStringList::ConstIterator it = queries.begin(); it != queries.end(); ++it)
{
QString q = (*it).toLower();
if (q.startsWith(QLatin1String("subject=")))
subject = KUrl::fromPercentEncoding((*it).mid(8).toLatin1());
else
if (q.startsWith(QLatin1String("cc=")))
cc = cc.isEmpty()? KUrl::fromPercentEncoding((*it).mid(3).toLatin1()): cc + comma + KUrl::fromPercentEncoding((*it).mid(3).toLatin1());
else
if (q.startsWith(QLatin1String("bcc=")))
bcc = bcc.isEmpty()? KUrl::fromPercentEncoding((*it).mid(4).toLatin1()): bcc + comma + KUrl::fromPercentEncoding((*it).mid(4).toLatin1());
else
if (q.startsWith(QLatin1String("body=")))
body = KUrl::fromPercentEncoding((*it).mid(5).toLatin1());
else
if (allowAttachments && q.startsWith(QLatin1String("attach=")))
attachURLs.push_back(KUrl::fromPercentEncoding((*it).mid(7).toLatin1()));
else
if (allowAttachments && q.startsWith(QLatin1String("attachment=")))
attachURLs.push_back(KUrl::fromPercentEncoding((*it).mid(11).toLatin1()));
else
if (q.startsWith(QLatin1String("to=")))
address = address.isEmpty()? KUrl::fromPercentEncoding((*it).mid(3).toLatin1()): address + comma + KUrl::fromPercentEncoding((*it).mid(3).toLatin1());
}
invokeMailer( address, cc, bcc, subject, body, QString(), attachURLs, startup_id );
}
void KToolInvocation::startKdeinit()
{
//KComponentData inst( "startkdeinitlock" );
KLockFile lock(QDir::tempPath() + QLatin1Char('/') + QLatin1String("startkdeinitlock"));
if( lock.lock( KLockFile::NoBlockFlag ) != KLockFile::LockOK ) {
lock.lock();
if( QDBusConnection::sessionBus().interface()->isServiceRegistered(QString::fromLatin1("org.kde.klauncher")))
return; // whoever held the lock has already started it
}
// Try to launch kdeinit.
QString srv = QStandardPaths::findExecutable(QLatin1String("kdeinit4"));
if (srv.isEmpty())
return;
// this is disabled because we are in kdecore
// const bool gui = qApp && qApp->type() != QApplication::Tty;
// if ( gui )
// qApp->setOverrideCursor( Qt::WaitCursor );
QStringList args;
#ifndef Q_WS_WIN
args += QString::fromLatin1("--suicide");
#endif
QProcess::execute(srv, args);
// if ( gui )
// qApp->restoreOverrideCursor();
}
#include "ktoolinvocation.moc"
diff --git a/kdecore/services/kmimetype.cpp b/kdecore/services/kmimetype.cpp
index a719b63727..f422e7147b 100644
--- a/kdecore/services/kmimetype.cpp
+++ b/kdecore/services/kmimetype.cpp
@@ -1,419 +1,419 @@
/* This file is part of the KDE libraries
* Copyright (C) 1999 Waldo Bastian <bastian@kde.org>
* 2000-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 "kmimetype.h"
#include "kmimetype_p.h"
#include "kmimetypefactory.h"
#include "kmimetyperepository_p.h"
#include "qmimedatabase.h"
#include <kdebug.h>
#include <kde_file.h> // KDE::stat
#include <kdeversion.h> // KDE_MAKE_VERSION
#include <klocale.h>
#include <kprotocolinfo.h>
#include <kprotocolinfofactory.h>
#include <qstandardpaths.h>
#include <kurl.h>
#include <QtCore/QFile>
#include <QtDBus/QtDBus>
#include <QBuffer>
extern int servicesDebugArea();
template class KSharedPtr<KMimeType>;
KMimeType::Ptr KMimeType::defaultMimeTypePtr()
{
return KMimeTypeRepository::self()->defaultMimeTypePtr();
}
bool KMimeType::isDefault() const
{
return name() == defaultMimeType();
}
KMimeType::Ptr KMimeType::mimeType(const QString& name, FindByNameOption options)
{
Q_UNUSED(options);
QMimeDatabase db;
QMimeType mime = db.mimeTypeForName(name);
if (mime.isValid()) {
return KMimeType::Ptr(new KMimeType(mime));
} else {
return KMimeType::Ptr();
}
}
KMimeType::List KMimeType::allMimeTypes()
{
// This could be done faster...
KMimeType::List lst;
QMimeDatabase db;
Q_FOREACH(const QMimeType& mimeType, db.allMimeTypes()) {
Q_ASSERT(!mimeType.name().startsWith(QLatin1String("x-scheme-handler")));
lst.append(KMimeType::Ptr(new KMimeType(mimeType)));
}
return lst;
}
// TODO used outside of kmimetype?
bool KMimeType::isBufferBinaryData(const QByteArray& data)
{
// Check the first 32 bytes (see shared-mime spec)
const char* p = data.data();
const int end = qMin(32, data.size());
for (int i = 0; i < end; ++i) {
if ((unsigned char)(p[i]) < 32 && p[i] != 9 && p[i] != 10 && p[i] != 13) // ASCII control character
return true;
}
return false;
}
// the windows-specific code, and the locked directory, are missing from QMimeDatabase.
static KMimeType::Ptr findFromMode( const QString& path /*only used if is_local_file*/,
mode_t mode /*0 if unknown*/,
bool is_local_file )
{
if ( is_local_file && (mode == 0 || mode == (mode_t)-1) ) {
KDE_struct_stat buff;
if ( KDE::stat( path, &buff ) != -1 )
mode = buff.st_mode;
}
if ( S_ISDIR( mode ) ) {
// KDE4 TODO: use an overlay instead
#if 0
// Special hack for local files. We want to see whether we
// are allowed to enter the directory
if ( is_local_file )
{
if ( KDE::access( path, R_OK ) == -1 )
return KMimeType::mimeType( "inode/directory-locked" );
}
#endif
return KMimeType::mimeType( QLatin1String("inode/directory") );
}
if ( S_ISCHR( mode ) )
return KMimeType::mimeType( QLatin1String("inode/chardevice") );
if ( S_ISBLK( mode ) )
return KMimeType::mimeType( QLatin1String("inode/blockdevice") );
if ( S_ISFIFO( mode ) )
return KMimeType::mimeType( QLatin1String("inode/fifo") );
if ( S_ISSOCK( mode ) )
return KMimeType::mimeType( QLatin1String("inode/socket") );
#ifdef Q_OS_WIN
// FIXME: distinguish between mounted & unmounted
int size = path.size();
if ( size == 2 || size == 3 ) {
//GetDriveTypeW is not defined in wince
#ifndef _WIN32_WCE
unsigned int type = GetDriveTypeW( (LPCWSTR) path.utf16() );
switch( type ) {
case DRIVE_REMOVABLE:
return KMimeType::mimeType( QLatin1String("media/floppy_mounted") );
case DRIVE_FIXED:
return KMimeType::mimeType( QLatin1String("media/hdd_mounted") );
case DRIVE_REMOTE:
return KMimeType::mimeType( QLatin1String("media/smb_mounted") );
case DRIVE_CDROM:
return KMimeType::mimeType( QLatin1String("media/cdrom_mounted") );
case DRIVE_RAMDISK:
return KMimeType::mimeType( QLatin1String("media/hdd_mounted") );
default:
break;
};
#else
return KMimeType::mimeType( QLatin1String("media/hdd_mounted") );
#endif
}
#endif
// remote executable file? stop here (otherwise findFromContent can do that better for local files)
if ( !is_local_file && S_ISREG( mode ) && ( mode & ( S_IXUSR | S_IXGRP | S_IXOTH ) ) )
return KMimeType::mimeType( QLatin1String("application/x-executable") );
return KMimeType::Ptr();
}
/*
Note: in KDE we want the file views to sniff in a delayed manner.
So there's also a fast mode which is:
if no glob matches, or if more than one glob matches, use default mimetype and mark as "can be refined".
===> KDE5 TODO, implement (in qmime, I guess)
*/
KMimeType::Ptr KMimeType::findByUrl( const QUrl& url, mode_t mode,
bool is_local_file, bool fast_mode,
int *accuracy )
{
Q_UNUSED(mode); // special devices only matter locally; caller can use S_ISDIR by itself.
Q_UNUSED(is_local_file); // QUrl can tell us...
QMimeDatabase db;
if (accuracy)
*accuracy = 80; // not supported anymore; was it really used for anything?
if (fast_mode) {
return KMimeType::Ptr(new KMimeType(db.findByName(url.path())));
}
return KMimeType::Ptr(new KMimeType(db.findByUrl(url)));
}
KMimeType::Ptr KMimeType::findByPath( const QString& path, mode_t mode,
bool fast_mode, int* accuracy )
{
Q_UNUSED(mode); // special devices only matter locally; caller can use S_ISDIR by itself.
QMimeDatabase db;
if (accuracy)
*accuracy = 80; // not supported anymore; was it really used for anything?
if (fast_mode) {
return KMimeType::Ptr(new KMimeType(db.findByName(path)));
}
return KMimeType::Ptr(new KMimeType(db.findByFile(path)));
}
KMimeType::Ptr KMimeType::findByNameAndContent( const QString& name, const QByteArray& data,
mode_t mode, int* accuracy )
{
Q_UNUSED(mode); // If we have data, then this is a regular file anyway...
if (accuracy)
*accuracy = 80; // not supported anymore; was it really used for anything?
QMimeDatabase db;
return KMimeType::Ptr(new KMimeType(db.findByNameAndData(name, data)));
}
KMimeType::Ptr KMimeType::findByNameAndContent( const QString& name, QIODevice* device,
mode_t mode, int* accuracy )
{
Q_UNUSED(mode); // If we have data, then this is a regular file anyway...
if (accuracy)
*accuracy = 80; // not supported anymore; was it really used for anything?
QMimeDatabase db;
return KMimeType::Ptr(new KMimeType(db.findByNameAndData(name, device)));
}
QString KMimeType::extractKnownExtension(const QString &fileName)
{
QMimeDatabase db;
return db.suffixForFileName(fileName);
}
KMimeType::Ptr KMimeType::findByContent( const QByteArray &data, int *accuracy )
{
QMimeDatabase db;
if (accuracy)
*accuracy = 80; // not supported anymore; was it really used for anything?
return KMimeType::Ptr(new KMimeType(db.findByData(data)));
}
KMimeType::Ptr KMimeType::findByContent( QIODevice* device, int* accuracy )
{
QMimeDatabase db;
if (accuracy)
*accuracy = 80; // not supported anymore; was it really used for anything?
return KMimeType::Ptr(new KMimeType(db.findByData(device)));
}
KMimeType::Ptr KMimeType::findByFileContent( const QString &fileName, int *accuracy )
{
QFile device(fileName);
#if 1
// Look at mode first
KMimeType::Ptr mimeFromMode = findFromMode( fileName, 0, true );
if (mimeFromMode) {
if (accuracy)
*accuracy = 100;
return mimeFromMode;
}
#endif
QMimeDatabase db;
KMimeType::Ptr mime(new KMimeType(db.findByData(&device)));
if (accuracy)
*accuracy = mime->isDefault() ? 0 : 80; // not supported anymore; was it really used for anything?
return mime;
}
bool KMimeType::isBinaryData( const QString &fileName )
{
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly))
return false; // err, whatever
const QByteArray data = file.read(32);
return isBufferBinaryData(data);
}
KMimeType::KMimeType(const QMimeType& mime)
: d_ptr(new KMimeTypePrivate(mime))
{
}
KMimeType::~KMimeType()
{
delete d_ptr;
}
QString KMimeType::iconNameForUrl( const QUrl & url, mode_t mode )
{
KUrl _url(url);
const KMimeType::Ptr mt = findByUrl( _url, mode, _url.isLocalFile(),
false /*HACK*/);
if (!mt) {
return QString();
}
static const QString& unknown = KGlobal::staticQString("unknown");
const QString mimeTypeIcon = mt->iconName();
QString i = mimeTypeIcon;
// if we don't find an icon, maybe we can use the one for the protocol
if ( i == unknown || i.isEmpty() || mt->name() == defaultMimeType()
// and for the root of the protocol (e.g. trash:/) the protocol icon has priority over the mimetype icon
|| _url.path().length() <= 1 )
{
i = favIconForUrl( _url ); // maybe there is a favicon?
if ( i.isEmpty() )
- i = KProtocolInfo::icon( _url.protocol() );
+ i = KProtocolInfo::icon( _url.scheme() );
// root of protocol: if we found nothing, revert to mimeTypeIcon (which is usually "folder")
if ( _url.path().length() <= 1 && ( i == unknown || i.isEmpty() ) )
i = mimeTypeIcon;
}
return !i.isEmpty() ? i : unknown;
}
QString KMimeType::favIconForUrl( const QUrl& _url )
{
KUrl url(_url);
if (url.isLocalFile()
|| !url.scheme().startsWith(QLatin1String("http"))
|| !KMimeTypeRepository::self()->useFavIcons())
return QString();
QDBusInterface kded( QString::fromLatin1("org.kde.kded"),
QString::fromLatin1("/modules/favicons"),
QString::fromLatin1("org.kde.FavIcon") );
QDBusReply<QString> result = kded.call( QString::fromLatin1("iconForUrl"), url.url() );
return result; // default is QString()
}
QString KMimeType::comment() const
{
return d_ptr->m_qmime.comment();
}
#ifndef KDE_NO_DEPRECATED
QString KMimeType::parentMimeType() const
{
const QStringList parents = d_ptr->m_qmime.parentMimeTypes();
if (!parents.isEmpty())
return parents.first();
return QString();
}
#endif
bool KMimeType::is(const QString& mimeTypeName) const
{
return d_ptr->m_qmime.inherits(mimeTypeName);
}
QStringList KMimeType::parentMimeTypes() const
{
return d_ptr->m_qmime.parentMimeTypes();
}
QStringList KMimeType::allParentMimeTypes() const
{
return d_ptr->m_qmime.allParentMimeTypes();
}
QString KMimeType::defaultMimeType()
{
static const QString & s_strDefaultMimeType =
KGlobal::staticQString( "application/octet-stream" );
return s_strDefaultMimeType;
}
QString KMimeType::iconName() const
{
return d_ptr->m_qmime.iconName();
}
QStringList KMimeType::patterns() const
{
return d_ptr->m_qmime.globPatterns();
}
// TODO MOVE TO keditfiletype/mimetypedata.cpp
QString KMimeType::userSpecifiedIconName() const
{
//d->ensureXmlDataLoaded();
//return d->m_iconName;
return QString();
}
int KMimeType::sharedMimeInfoVersion()
{
return KMimeTypeRepository::self()->sharedMimeInfoVersion();
}
QString KMimeType::mainExtension() const
{
#if 1 // HACK START - can be removed once shared-mime-info >= 0.70 is used/required.
// The idea was: first usable pattern from m_lstPatterns.
// But update-mime-database makes a mess of the order of the patterns,
// because it uses a hash internally.
static const struct { const char* mime; const char* extension; } s_hardcodedMimes[] = {
{ "text/plain", ".txt" } };
if (patterns().count() > 1) {
const QByteArray me = name().toLatin1();
for (uint i = 0; i < sizeof(s_hardcodedMimes)/sizeof(*s_hardcodedMimes); ++i) {
if (me == s_hardcodedMimes[i].mime)
return QString::fromLatin1(s_hardcodedMimes[i].extension);
}
}
#endif // HACK END
Q_FOREACH(const QString& pattern, patterns()) {
// Skip if if looks like: README or *. or *.*
// or *.JP*G or *.JP?
if (pattern.startsWith(QLatin1String("*.")) &&
pattern.length() > 2 &&
pattern.indexOf(QLatin1Char('*'), 2) < 0 && pattern.indexOf(QLatin1Char('?'), 2) < 0) {
return pattern.mid(1);
}
}
// TODO we should also look into the parent mimetype's patterns, no?
return QString();
}
bool KMimeType::matchFileName( const QString &filename, const QString &pattern )
{
return KMimeTypeRepository::matchFileName( filename, pattern );
}
/*
int KMimeTypePrivate::serviceOffersOffset() const
{
return KMimeTypeFactory::self()->serviceOffersOffset(name());
}
*/
QString KMimeType::name() const
{
return d_ptr->m_qmime.name();
}
diff --git a/kdecore/sycoca/kprotocolinfo.cpp b/kdecore/sycoca/kprotocolinfo.cpp
index 7b987262d9..accef6083b 100644
--- a/kdecore/sycoca/kprotocolinfo.cpp
+++ b/kdecore/sycoca/kprotocolinfo.cpp
@@ -1,462 +1,462 @@
/* This file is part of the KDE libraries
Copyright (C) 1999 Torben Weis <weis@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 "kprotocolinfo.h"
#include "kprotocolinfo_p.h"
#include "kprotocolinfofactory.h"
#include <kmimetypetrader.h>
#include <kstandarddirs.h>
#include <kconfig.h>
#include <kconfiggroup.h>
//
// Internal functions:
//
KProtocolInfo::KProtocolInfo(const QString &path)
: KSycocaEntry(*new KProtocolInfoPrivate(path, this))
{
Q_D(KProtocolInfo);
QString fullPath = KStandardDirs::locate("services", path);
KConfig sconfig( fullPath );
KConfigGroup config(&sconfig, "Protocol" );
m_name = config.readEntry( "protocol" );
m_exec = config.readPathEntry( "exec", QString() );
m_isSourceProtocol = config.readEntry( "source", true );
m_isHelperProtocol = config.readEntry( "helper", false );
m_supportsReading = config.readEntry( "reading", false );
m_supportsWriting = config.readEntry( "writing", false );
m_supportsMakeDir = config.readEntry( "makedir", false );
m_supportsDeleting = config.readEntry( "deleting", false );
m_supportsLinking = config.readEntry( "linking", false );
m_supportsMoving = config.readEntry( "moving", false );
m_supportsOpening = config.readEntry( "opening", false );
m_canCopyFromFile = config.readEntry( "copyFromFile", false );
m_canCopyToFile = config.readEntry( "copyToFile", false );
d->canRenameFromFile = config.readEntry( "renameFromFile", false );
d->canRenameToFile = config.readEntry( "renameToFile", false );
d->canDeleteRecursive = config.readEntry( "deleteRecursive", false );
const QString fnu = config.readEntry( "fileNameUsedForCopying", "FromURL" );
d->fileNameUsedForCopying = FromUrl;
if (fnu == QLatin1String("Name"))
d->fileNameUsedForCopying = Name;
else if (fnu == QLatin1String("DisplayName"))
d->fileNameUsedForCopying = DisplayName;
m_listing = config.readEntry( "listing", QStringList() );
// Many .protocol files say "Listing=false" when they really mean "Listing=" (i.e. unsupported)
if ( m_listing.count() == 1 && m_listing.first() == QLatin1String("false") )
m_listing.clear();
m_supportsListing = ( m_listing.count() > 0 );
m_defaultMimetype = config.readEntry( "defaultMimetype" );
m_determineMimetypeFromExtension = config.readEntry( "determineMimetypeFromExtension", true );
d->archiveMimetype = config.readEntry("archiveMimetype", QStringList());
m_icon = config.readEntry( "Icon" );
m_config = config.readEntry( "config", m_name );
m_maxSlaves = config.readEntry( "maxInstances", 1);
d->maxSlavesPerHost = config.readEntry( "maxInstancesPerHost", 0);
QString tmp = config.readEntry( "input" );
if ( tmp == QLatin1String("filesystem") )
m_inputType = KProtocolInfo::T_FILESYSTEM;
else if ( tmp == QLatin1String("stream") )
m_inputType = KProtocolInfo::T_STREAM;
else
m_inputType = KProtocolInfo::T_NONE;
tmp = config.readEntry( "output" );
if ( tmp == QLatin1String("filesystem") )
m_outputType = KProtocolInfo::T_FILESYSTEM;
else if ( tmp == QLatin1String("stream") )
m_outputType = KProtocolInfo::T_STREAM;
else
m_outputType = KProtocolInfo::T_NONE;
d->docPath = config.readPathEntry( "X-DocPath", QString() );
if (d->docPath.isEmpty())
d->docPath = config.readPathEntry( "DocPath", QString() );
d->protClass = config.readEntry( "Class" ).toLower();
if (d->protClass[0] != QLatin1Char(':'))
d->protClass.prepend(QLatin1Char(':'));
const QStringList extraNames = config.readEntry( "ExtraNames", QStringList() );
const QStringList extraTypes = config.readEntry( "ExtraTypes", QStringList() );
QStringList::const_iterator it = extraNames.begin();
QStringList::const_iterator typeit = extraTypes.begin();
for( ; it != extraNames.end() && typeit != extraTypes.end(); ++it, ++typeit ) {
QVariant::Type type = QVariant::nameToType( (*typeit).toLatin1() );
// currently QVariant::Type and ExtraField::Type use the same subset of values, so we can just cast.
d->extraFields.append( ExtraField( *it, static_cast<ExtraField::Type>(type) ) );
}
d->showPreviews = config.readEntry( "ShowPreviews", d->protClass == QLatin1String(":local") );
d->capabilities = config.readEntry( "Capabilities", QStringList() );
d->proxyProtocol = config.readEntry( "ProxiedBy" );
}
KProtocolInfo::KProtocolInfo( QDataStream& _str, int offset) :
KSycocaEntry(*new KProtocolInfoPrivate( _str, offset, this) )
{
load( _str );
}
KProtocolInfo::~KProtocolInfo()
{
}
void
KProtocolInfo::load( QDataStream& _str)
{
Q_D(KProtocolInfo);
// You may add new fields at the end. Make sure to update the version
// number in ksycoca.h
qint32 i_inputType, i_outputType;
qint8 i_isSourceProtocol, i_isHelperProtocol,
i_supportsListing, i_supportsReading,
i_supportsWriting, i_supportsMakeDir,
i_supportsDeleting, i_supportsLinking,
i_supportsMoving, i_supportsOpening,
i_determineMimetypeFromExtension,
i_canCopyFromFile, i_canCopyToFile, i_showPreviews,
i_uriMode, i_canRenameFromFile, i_canRenameToFile,
i_canDeleteRecursive, i_fileNameUsedForCopying;
_str >> m_name >> m_exec >> m_listing >> m_defaultMimetype
>> i_determineMimetypeFromExtension
>> m_icon
>> i_inputType >> i_outputType
>> i_isSourceProtocol >> i_isHelperProtocol
>> i_supportsListing >> i_supportsReading
>> i_supportsWriting >> i_supportsMakeDir
>> i_supportsDeleting >> i_supportsLinking
>> i_supportsMoving >> i_supportsOpening
>> i_canCopyFromFile >> i_canCopyToFile
>> m_config >> m_maxSlaves >> d->docPath >> d->protClass
>> d->extraFields >> i_showPreviews >> i_uriMode
>> d->capabilities >> d->proxyProtocol
>> i_canRenameFromFile >> i_canRenameToFile
>> i_canDeleteRecursive >> i_fileNameUsedForCopying
>> d->archiveMimetype >> d->maxSlavesPerHost;
m_inputType = (Type) i_inputType;
m_outputType = (Type) i_outputType;
m_isSourceProtocol = (i_isSourceProtocol != 0);
m_isHelperProtocol = (i_isHelperProtocol != 0);
m_supportsListing = (i_supportsListing != 0);
m_supportsReading = (i_supportsReading != 0);
m_supportsWriting = (i_supportsWriting != 0);
m_supportsMakeDir = (i_supportsMakeDir != 0);
m_supportsDeleting = (i_supportsDeleting != 0);
m_supportsLinking = (i_supportsLinking != 0);
m_supportsMoving = (i_supportsMoving != 0);
m_supportsOpening = (i_supportsOpening != 0);
m_canCopyFromFile = (i_canCopyFromFile != 0);
m_canCopyToFile = (i_canCopyToFile != 0);
d->canRenameFromFile = (i_canRenameFromFile != 0);
d->canRenameToFile = (i_canRenameToFile != 0);
d->canDeleteRecursive = (i_canDeleteRecursive != 0);
d->fileNameUsedForCopying = FileNameUsedForCopying(i_fileNameUsedForCopying);
m_determineMimetypeFromExtension = (i_determineMimetypeFromExtension != 0);
d->showPreviews = (i_showPreviews != 0);
}
void
KProtocolInfoPrivate::save( QDataStream& _str)
{
KSycocaEntryPrivate::save( _str );
// You may add new fields at the end. Make sure to update the version
// number in ksycoca.h
qint32 i_inputType, i_outputType;
qint8 i_isSourceProtocol, i_isHelperProtocol,
i_supportsListing, i_supportsReading,
i_supportsWriting, i_supportsMakeDir,
i_supportsDeleting, i_supportsLinking,
i_supportsMoving, i_supportsOpening,
i_determineMimetypeFromExtension,
i_canCopyFromFile, i_canCopyToFile, i_showPreviews,
i_uriMode, i_canRenameFromFile, i_canRenameToFile,
i_canDeleteRecursive, i_fileNameUsedForCopying;
i_inputType = (qint32) q->m_inputType;
i_outputType = (qint32) q->m_outputType;
i_isSourceProtocol = q->m_isSourceProtocol ? 1 : 0;
i_isHelperProtocol = q->m_isHelperProtocol ? 1 : 0;
i_supportsListing = q->m_supportsListing ? 1 : 0;
i_supportsReading = q->m_supportsReading ? 1 : 0;
i_supportsWriting = q->m_supportsWriting ? 1 : 0;
i_supportsMakeDir = q->m_supportsMakeDir ? 1 : 0;
i_supportsDeleting = q->m_supportsDeleting ? 1 : 0;
i_supportsLinking = q->m_supportsLinking ? 1 : 0;
i_supportsMoving = q->m_supportsMoving ? 1 : 0;
i_supportsOpening = q->m_supportsOpening ? 1 : 0;
i_canCopyFromFile = q->m_canCopyFromFile ? 1 : 0;
i_canCopyToFile = q->m_canCopyToFile ? 1 : 0;
i_canRenameFromFile = canRenameFromFile ? 1 : 0;
i_canRenameToFile = canRenameToFile ? 1 : 0;
i_canDeleteRecursive = canDeleteRecursive ? 1 : 0;
i_fileNameUsedForCopying = int(fileNameUsedForCopying);
i_determineMimetypeFromExtension = q->m_determineMimetypeFromExtension ? 1 : 0;
i_showPreviews = showPreviews ? 1 : 0;
i_uriMode = 0;
_str << q->m_name << q->m_exec << q->m_listing << q->m_defaultMimetype
<< i_determineMimetypeFromExtension
<< q->m_icon
<< i_inputType << i_outputType
<< i_isSourceProtocol << i_isHelperProtocol
<< i_supportsListing << i_supportsReading
<< i_supportsWriting << i_supportsMakeDir
<< i_supportsDeleting << i_supportsLinking
<< i_supportsMoving << i_supportsOpening
<< i_canCopyFromFile << i_canCopyToFile
<< q->m_config << q->m_maxSlaves << docPath << protClass
<< extraFields << i_showPreviews << i_uriMode
<< capabilities << proxyProtocol
<< i_canRenameFromFile << i_canRenameToFile
<< i_canDeleteRecursive << i_fileNameUsedForCopying
<< archiveMimetype << maxSlavesPerHost;
}
//
// Static functions:
//
QStringList KProtocolInfo::protocols()
{
return KProtocolInfoFactory::self()->protocols();
}
bool KProtocolInfo::isFilterProtocol( const QString& _protocol )
{
// We call the findProtocol directly (not via KProtocolManager) to bypass any proxy settings.
KProtocolInfo::Ptr prot = KProtocolInfoFactory::self()->findProtocol(_protocol);
if ( !prot )
return false;
return !prot->m_isSourceProtocol;
}
QString KProtocolInfo::icon( const QString& _protocol )
{
// We call the findProtocol directly (not via KProtocolManager) to bypass any proxy settings.
KProtocolInfo::Ptr prot = KProtocolInfoFactory::self()->findProtocol(_protocol);
if ( !prot )
return QString();
return prot->m_icon;
}
QString KProtocolInfo::config( const QString& _protocol )
{
// We call the findProtocol directly (not via KProtocolManager) to bypass any proxy settings.
KProtocolInfo::Ptr prot = KProtocolInfoFactory::self()->findProtocol(_protocol);
if ( !prot )
return QString();
return QString::fromLatin1("kio_%1rc").arg(prot->m_config);
}
int KProtocolInfo::maxSlaves( const QString& _protocol )
{
KProtocolInfo::Ptr prot = KProtocolInfoFactory::self()->findProtocol(_protocol);
if ( !prot )
return 1;
return prot->m_maxSlaves;
}
int KProtocolInfo::maxSlavesPerHost( const QString& _protocol )
{
KProtocolInfo::Ptr prot = KProtocolInfoFactory::self()->findProtocol(_protocol);
if ( !prot )
return 0;
return prot->d_func()->maxSlavesPerHost;
}
bool KProtocolInfo::determineMimetypeFromExtension( const QString &_protocol )
{
KProtocolInfo::Ptr prot = KProtocolInfoFactory::self()->findProtocol( _protocol );
if ( !prot )
return true;
return prot->m_determineMimetypeFromExtension;
}
QString KProtocolInfo::exec(const QString& protocol)
{
KProtocolInfo::Ptr prot = KProtocolInfoFactory::self()->findProtocol(protocol);
if ( prot ) {
return prot->m_exec;
}
// Maybe it's "helper protocol", i.e. launches an app?
const KService::Ptr service = KMimeTypeTrader::self()->preferredService(QString::fromLatin1("x-scheme-handler/") + protocol);
if (service)
return service->exec();
return QString();
}
KProtocolInfo::ExtraFieldList KProtocolInfo::extraFields( const KUrl &url )
{
- KProtocolInfo::Ptr prot = KProtocolInfoFactory::self()->findProtocol(url.protocol());
+ KProtocolInfo::Ptr prot = KProtocolInfoFactory::self()->findProtocol(url.scheme());
if ( !prot )
return ExtraFieldList();
return prot->d_func()->extraFields;
}
QString KProtocolInfo::docPath( const QString& _protocol )
{
KProtocolInfo::Ptr prot = KProtocolInfoFactory::self()->findProtocol(_protocol);
if ( !prot )
return QString();
return prot->d_func()->docPath;
}
QString KProtocolInfo::protocolClass( const QString& _protocol )
{
KProtocolInfo::Ptr prot = KProtocolInfoFactory::self()->findProtocol(_protocol);
if ( !prot )
return QString();
return prot->d_func()->protClass;
}
bool KProtocolInfo::showFilePreview( const QString& _protocol )
{
KProtocolInfo::Ptr prot = KProtocolInfoFactory::self()->findProtocol(_protocol);
if ( !prot )
return false;
return prot->d_func()->showPreviews;
}
QStringList KProtocolInfo::capabilities( const QString& _protocol )
{
KProtocolInfo::Ptr prot = KProtocolInfoFactory::self()->findProtocol(_protocol);
if ( !prot )
return QStringList();
return prot->d_func()->capabilities;
}
QString KProtocolInfo::proxiedBy( const QString& _protocol )
{
KProtocolInfo::Ptr prot = KProtocolInfoFactory::self()->findProtocol(_protocol);
if ( !prot )
return QString();
return prot->d_func()->proxyProtocol;
}
QString KProtocolInfo::defaultMimeType() const
{
return m_defaultMimetype;
}
QStringList KProtocolInfo::archiveMimeTypes() const
{
Q_D(const KProtocolInfo);
return d->archiveMimetype;
}
bool KProtocolInfo::supportsListing() const
{
return m_supportsListing;
}
bool KProtocolInfo::canRenameFromFile() const
{
Q_D(const KProtocolInfo);
return d->canRenameFromFile;
}
bool KProtocolInfo::canRenameToFile() const
{
Q_D(const KProtocolInfo);
return d->canRenameToFile;
}
bool KProtocolInfo::canDeleteRecursive() const
{
Q_D(const KProtocolInfo);
return d->canDeleteRecursive;
}
KProtocolInfo::FileNameUsedForCopying KProtocolInfo::fileNameUsedForCopying() const
{
Q_D(const KProtocolInfo);
return d->fileNameUsedForCopying;
}
bool KProtocolInfo::isFilterProtocol( const KUrl &url )
{
- return isFilterProtocol (url.protocol());
+ return isFilterProtocol (url.scheme());
}
bool KProtocolInfo::isHelperProtocol( const KUrl &url )
{
- return isHelperProtocol (url.protocol());
+ return isHelperProtocol (url.scheme());
}
bool KProtocolInfo::isHelperProtocol( const QString &protocol )
{
// We call the findProtocol directly (not via KProtocolManager) to bypass any proxy settings.
KProtocolInfo::Ptr prot = KProtocolInfoFactory::self()->findProtocol(protocol);
if ( prot )
return prot->m_isHelperProtocol;
const KService::Ptr service = KMimeTypeTrader::self()->preferredService(QString::fromLatin1("x-scheme-handler/") + protocol);
return !service.isNull();
}
bool KProtocolInfo::isKnownProtocol( const KUrl &url )
{
- return isKnownProtocol (url.protocol());
+ return isKnownProtocol (url.scheme());
}
bool KProtocolInfo::isKnownProtocol( const QString &protocol )
{
// We call the findProtocol (const QString&) to bypass any proxy settings.
KProtocolInfo::Ptr prot = KProtocolInfoFactory::self()->findProtocol(protocol);
return prot || isHelperProtocol(protocol);
}
QDataStream& operator>>( QDataStream& s, KProtocolInfo::ExtraField& field ) {
s >> field.name;
int type;
s >> type;
field.type = static_cast<KProtocolInfo::ExtraField::Type>( type );
return s;
}
QDataStream& operator<<( QDataStream& s, const KProtocolInfo::ExtraField& field ) {
s << field.name;
s << static_cast<int>( field.type );
return s;
}
diff --git a/kdeui/kernel/kglobalsettings.cpp b/kdeui/kernel/kglobalsettings.cpp
index 0d81c11406..a884371b31 100644
--- a/kdeui/kernel/kglobalsettings.cpp
+++ b/kdeui/kernel/kglobalsettings.cpp
@@ -1,1157 +1,1157 @@
/* This file is part of the KDE libraries
Copyright (C) 2000, 2006 David Faure <faure@kde.org>
Copyright 2008 Friedrich W. H. Kossebau <kossebau@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 "kglobalsettings.h"
#include <config.h>
#include <kconfig.h>
#include <kdebug.h>
#include <kglobal.h>
#include <klocale.h>
#include <kstandarddirs.h>
#include <kprotocolinfo.h>
#include <kcolorscheme.h>
#include <kstyle.h>
#include <QColor>
#include <QCursor>
#include <QDesktopWidget>
#include <QtCore/QDir>
#include <QFont>
#include <QFontDatabase>
#include <QFontInfo>
#include <QKeySequence>
#include <QPixmap>
#include <QPixmapCache>
#include <QApplication>
#include <QtDBus/QtDBus>
#include <QStyleFactory>
#include <qstandardpaths.h>
// next two needed so we can set their palettes
#include <QToolTip>
#include <QWhatsThis>
#ifdef Q_WS_WIN
#include <windows.h>
#include <kkernel_win.h>
static QRgb qt_colorref2qrgb(COLORREF col)
{
return qRgb(GetRValue(col),GetGValue(col),GetBValue(col));
}
#endif
#ifdef Q_WS_X11
#include <X11/Xlib.h>
#ifdef HAVE_XCURSOR
#include <X11/Xcursor/Xcursor.h>
#endif
#include "fixx11h.h"
#include <QX11Info>
#endif
#include <stdlib.h>
#include <kconfiggroup.h>
//static QColor *_buttonBackground = 0;
static KGlobalSettings::GraphicEffects _graphicEffects = KGlobalSettings::NoEffects;
// TODO: merge this with KGlobalSettings::Private
//
// F. Kossebau: KDE5: think to make all methods static and not expose an object,
// making KGlobalSettings rather a namespace
// D. Faure: how would people connect to signals, then?
class KGlobalSettingsData
{
public:
// if adding a new type here also add an entry to DefaultFontData
enum FontTypes
{
GeneralFont = 0,
FixedFont,
ToolbarFont,
MenuFont,
WindowTitleFont,
TaskbarFont ,
SmallestReadableFont,
FontTypesCount
};
public:
KGlobalSettingsData();
~KGlobalSettingsData();
public:
static KGlobalSettingsData* self();
public: // access, is not const due to caching
QFont font( FontTypes fontType );
QFont largeFont( const QString& text );
KGlobalSettings::KMouseSettings& mouseSettings();
public:
void dropFontSettingsCache();
void dropMouseSettingsCache();
protected:
QFont* mFonts[FontTypesCount];
QFont* mLargeFont;
KGlobalSettings::KMouseSettings* mMouseSettings;
};
KGlobalSettingsData::KGlobalSettingsData()
: mLargeFont( 0 ),
mMouseSettings( 0 )
{
for( int i=0; i<FontTypesCount; ++i )
mFonts[i] = 0;
}
KGlobalSettingsData::~KGlobalSettingsData()
{
for( int i=0; i<FontTypesCount; ++i )
delete mFonts[i];
delete mLargeFont;
delete mMouseSettings;
}
K_GLOBAL_STATIC( KGlobalSettingsData, globalSettingsDataSingleton )
inline KGlobalSettingsData* KGlobalSettingsData::self()
{
return globalSettingsDataSingleton;
}
class KGlobalSettings::Private
{
public:
Private(KGlobalSettings *q)
: q(q), activated(false), paletteCreated(false)
{
kdeFullSession = !qgetenv("KDE_FULL_SESSION").isEmpty();
}
QPalette createApplicationPalette(const KSharedConfigPtr &config);
QPalette createNewApplicationPalette(const KSharedConfigPtr &config);
void _k_slotNotifyChange(int, int);
void propagateQtSettings();
void kdisplaySetPalette();
void kdisplaySetStyle();
void kdisplaySetFont();
void applyGUIStyle();
/**
* @internal
*
* Ensures that cursors are loaded from the theme KDE is configured
* to use. Note that calling this function doesn't cause existing
* cursors to be reloaded. Reloading already created cursors is
* handled by the KCM when a cursor theme is applied.
*
* It is not necessary to call this function when KGlobalSettings
* is initialized.
*/
void applyCursorTheme();
/**
* drop cached values for settings that aren't in any of the previous groups
*/
static void rereadOtherSettings();
KGlobalSettings *q;
bool activated;
bool paletteCreated;
bool kdeFullSession;
QPalette applicationPalette;
};
KGlobalSettings* KGlobalSettings::self()
{
K_GLOBAL_STATIC(KGlobalSettings, s_self)
return s_self;
}
KGlobalSettings::KGlobalSettings()
: QObject(0), d(new Private(this))
{
}
KGlobalSettings::~KGlobalSettings()
{
delete d;
}
void KGlobalSettings::activate()
{
activate(ApplySettings | ListenForChanges);
}
void KGlobalSettings::activate(ActivateOptions options)
{
if (!d->activated) {
d->activated = true;
if (options & ListenForChanges) {
QDBusConnection::sessionBus().connect( QString(), "/KGlobalSettings", "org.kde.KGlobalSettings",
"notifyChange", this, SLOT(_k_slotNotifyChange(int,int)) );
}
if (options & ApplySettings) {
d->kdisplaySetStyle(); // implies palette setup
d->kdisplaySetFont();
d->propagateQtSettings();
}
}
}
int KGlobalSettings::dndEventDelay()
{
KConfigGroup g( KGlobal::config(), "General" );
return g.readEntry("StartDragDist", QApplication::startDragDistance());
}
bool KGlobalSettings::singleClick()
{
KConfigGroup g( KGlobal::config(), "KDE" );
return g.readEntry("SingleClick", KDE_DEFAULT_SINGLECLICK );
}
bool KGlobalSettings::smoothScroll()
{
KConfigGroup g( KGlobal::config(), "KDE" );
return g.readEntry("SmoothScroll", KDE_DEFAULT_SMOOTHSCROLL );
}
KGlobalSettings::TearOffHandle KGlobalSettings::insertTearOffHandle()
{
int tearoff;
bool effectsenabled;
KConfigGroup g( KGlobal::config(), "KDE" );
effectsenabled = g.readEntry( "EffectsEnabled", false);
tearoff = g.readEntry("InsertTearOffHandle", KDE_DEFAULT_INSERTTEAROFFHANDLES);
return effectsenabled ? (TearOffHandle) tearoff : Disable;
}
bool KGlobalSettings::changeCursorOverIcon()
{
KConfigGroup g( KGlobal::config(), "KDE" );
return g.readEntry("ChangeCursor", KDE_DEFAULT_CHANGECURSOR);
}
int KGlobalSettings::autoSelectDelay()
{
KConfigGroup g( KGlobal::config(), "KDE" );
return g.readEntry("AutoSelectDelay", KDE_DEFAULT_AUTOSELECTDELAY);
}
KGlobalSettings::Completion KGlobalSettings::completionMode()
{
int completion;
KConfigGroup g( KGlobal::config(), "General" );
completion = g.readEntry("completionMode", -1);
if ((completion < (int) CompletionNone) ||
(completion > (int) CompletionPopupAuto))
{
completion = (int) CompletionPopup; // Default
}
return (Completion) completion;
}
bool KGlobalSettings::showContextMenusOnPress ()
{
KConfigGroup g(KGlobal::config(), "ContextMenus");
return g.readEntry("ShowOnPress", true);
}
#ifndef KDE_NO_DEPRECATED
int KGlobalSettings::contextMenuKey ()
{
KConfigGroup g(KGlobal::config(), "Shortcuts");
QString s = g.readEntry ("PopupMenuContext", "Menu");
// this is a bit of a code duplication with KShortcut,
// but seeing as that is all in kdeui these days there's little choice.
// this is faster for what we're really after here anyways
// (less allocations, only processing the first item always, etc)
if (s == QLatin1String("none")) {
return QKeySequence()[0];
}
const QStringList shortCuts = s.split(';');
if (shortCuts.count() < 1) {
return QKeySequence()[0];
}
s = shortCuts.at(0);
if ( s.startsWith( QLatin1String("default(") ) ) {
s = s.mid( 8, s.length() - 9 );
}
return QKeySequence::fromString(s)[0];
}
#endif
// NOTE: keep this in sync with kdebase/workspace/kcontrol/colors/colorscm.cpp
QColor KGlobalSettings::inactiveTitleColor()
{
#ifdef Q_WS_WIN
return qt_colorref2qrgb(GetSysColor(COLOR_INACTIVECAPTION));
#else
KConfigGroup g( KGlobal::config(), "WM" );
return g.readEntry( "inactiveBackground", QColor(224,223,222) );
#endif
}
// NOTE: keep this in sync with kdebase/workspace/kcontrol/colors/colorscm.cpp
QColor KGlobalSettings::inactiveTextColor()
{
#ifdef Q_WS_WIN
return qt_colorref2qrgb(GetSysColor(COLOR_INACTIVECAPTIONTEXT));
#else
KConfigGroup g( KGlobal::config(), "WM" );
return g.readEntry( "inactiveForeground", QColor(75,71,67) );
#endif
}
// NOTE: keep this in sync with kdebase/workspace/kcontrol/colors/colorscm.cpp
QColor KGlobalSettings::activeTitleColor()
{
#ifdef Q_WS_WIN
return qt_colorref2qrgb(GetSysColor(COLOR_ACTIVECAPTION));
#else
KConfigGroup g( KGlobal::config(), "WM" );
return g.readEntry( "activeBackground", QColor(48,174,232));
#endif
}
// NOTE: keep this in sync with kdebase/workspace/kcontrol/colors/colorscm.cpp
QColor KGlobalSettings::activeTextColor()
{
#ifdef Q_WS_WIN
return qt_colorref2qrgb(GetSysColor(COLOR_CAPTIONTEXT));
#else
KConfigGroup g( KGlobal::config(), "WM" );
return g.readEntry( "activeForeground", QColor(255,255,255) );
#endif
}
int KGlobalSettings::contrast()
{
KConfigGroup g( KGlobal::config(), "KDE" );
return g.readEntry( "contrast", 7 );
}
qreal KGlobalSettings::contrastF(const KSharedConfigPtr &config)
{
if (config) {
KConfigGroup g( config, "KDE" );
return 0.1 * g.readEntry( "contrast", 7 );
}
return 0.1 * (qreal)contrast();
}
bool KGlobalSettings::shadeSortColumn()
{
KConfigGroup g( KGlobal::config(), "General" );
return g.readEntry( "shadeSortColumn", KDE_DEFAULT_SHADE_SORT_COLUMN );
}
bool KGlobalSettings::allowDefaultBackgroundImages()
{
KConfigGroup g( KGlobal::config(), "General" );
return g.readEntry( "allowDefaultBackgroundImages", KDE_DEFAULT_ALLOW_DEFAULT_BACKGROUND_IMAGES );
}
struct KFontData
{
const char* ConfigGroupKey;
const char* ConfigKey;
const char* FontName;
int Size;
int Weight;
QFont::StyleHint StyleHint;
};
// NOTE: keep in sync with kdebase/workspace/kcontrol/fonts/fonts.cpp
static const char GeneralId[] = "General";
static const char DefaultFont[] = "Sans Serif";
#ifdef Q_WS_MAC
static const char DefaultMacFont[] = "Lucida Grande";
#endif
static const KFontData DefaultFontData[KGlobalSettingsData::FontTypesCount] =
{
#ifdef Q_WS_MAC
{ GeneralId, "font", DefaultMacFont, 13, -1, QFont::SansSerif },
{ GeneralId, "fixed", "Monaco", 10, -1, QFont::TypeWriter },
{ GeneralId, "toolBarFont", DefaultMacFont, 11, -1, QFont::SansSerif },
{ GeneralId, "menuFont", DefaultMacFont, 13, -1, QFont::SansSerif },
#elif defined(Q_WS_MAEMO_5) || defined(MEEGO_EDITION_HARMATTAN)
{ GeneralId, "font", DefaultFont, 16, -1, QFont::SansSerif },
{ GeneralId, "fixed", "Monospace", 16, -1, QFont::TypeWriter },
{ GeneralId, "toolBarFont", DefaultFont, 16, -1, QFont::SansSerif },
{ GeneralId, "menuFont", DefaultFont, 16, -1, QFont::SansSerif },
#else
{ GeneralId, "font", DefaultFont, 9, -1, QFont::SansSerif },
{ GeneralId, "fixed", "Monospace", 9, -1, QFont::TypeWriter },
{ GeneralId, "toolBarFont", DefaultFont, 8, -1, QFont::SansSerif },
{ GeneralId, "menuFont", DefaultFont, 9, -1, QFont::SansSerif },
#endif
{ "WM", "activeFont", DefaultFont, 8, -1, QFont::SansSerif },
{ GeneralId, "taskbarFont", DefaultFont, 9, -1, QFont::SansSerif },
{ GeneralId, "smallestReadableFont", DefaultFont, 8, -1, QFont::SansSerif }
};
QFont KGlobalSettingsData::font( FontTypes fontType )
{
QFont* cachedFont = mFonts[fontType];
if (!cachedFont)
{
const KFontData& fontData = DefaultFontData[fontType];
cachedFont = new QFont( fontData.FontName, fontData.Size, fontData.Weight );
cachedFont->setStyleHint( fontData.StyleHint );
const KConfigGroup configGroup( KGlobal::config(), fontData.ConfigGroupKey );
*cachedFont = configGroup.readEntry( fontData.ConfigKey, *cachedFont );
mFonts[fontType] = cachedFont;
}
return *cachedFont;
}
QFont KGlobalSettings::generalFont()
{
return KGlobalSettingsData::self()->font( KGlobalSettingsData::GeneralFont );
}
QFont KGlobalSettings::fixedFont()
{
return KGlobalSettingsData::self()->font( KGlobalSettingsData::FixedFont );
}
QFont KGlobalSettings::toolBarFont()
{
return KGlobalSettingsData::self()->font( KGlobalSettingsData::ToolbarFont );
}
QFont KGlobalSettings::menuFont()
{
return KGlobalSettingsData::self()->font( KGlobalSettingsData::MenuFont );
}
QFont KGlobalSettings::windowTitleFont()
{
return KGlobalSettingsData::self()->font( KGlobalSettingsData::WindowTitleFont );
}
QFont KGlobalSettings::taskbarFont()
{
return KGlobalSettingsData::self()->font( KGlobalSettingsData::TaskbarFont );
}
QFont KGlobalSettings::smallestReadableFont()
{
return KGlobalSettingsData::self()->font( KGlobalSettingsData::SmallestReadableFont );
}
QFont KGlobalSettingsData::largeFont( const QString& text )
{
QFontDatabase db;
QStringList fam = db.families();
// Move a bunch of preferred fonts to the front.
// most preferred last
static const char* const PreferredFontNames[] =
{
"Arial",
"Sans Serif",
"Verdana",
"Tahoma",
"Lucida Sans",
"Lucidux Sans",
"Nimbus Sans",
"Gothic I"
};
static const unsigned int PreferredFontNamesCount = sizeof(PreferredFontNames)/sizeof(const char*);
for( unsigned int i=0; i<PreferredFontNamesCount; ++i )
{
const QString fontName (PreferredFontNames[i]);
if (fam.removeAll(fontName)>0)
fam.prepend(fontName);
}
if (mLargeFont) {
fam.prepend(mLargeFont->family());
delete mLargeFont;
}
for(QStringList::ConstIterator it = fam.constBegin();
it != fam.constEnd(); ++it)
{
if (db.isSmoothlyScalable(*it) && !db.isFixedPitch(*it))
{
QFont font(*it);
font.setPixelSize(75);
QFontMetrics metrics(font);
int h = metrics.height();
if ((h < 60) || ( h > 90))
continue;
bool ok = true;
for(int i = 0; i < text.length(); i++)
{
if (!metrics.inFont(text[i]))
{
ok = false;
break;
}
}
if (!ok)
continue;
font.setPointSize(48);
mLargeFont = new QFont(font);
return *mLargeFont;
}
}
mLargeFont = new QFont( font(GeneralFont) );
mLargeFont->setPointSize(48);
return *mLargeFont;
}
QFont KGlobalSettings::largeFont( const QString& text )
{
return KGlobalSettingsData::self()->largeFont( text );
}
void KGlobalSettingsData::dropFontSettingsCache()
{
for( int i=0; i<FontTypesCount; ++i )
{
delete mFonts[i];
mFonts[i] = 0;
}
delete mLargeFont;
mLargeFont = 0;
}
KGlobalSettings::KMouseSettings& KGlobalSettingsData::mouseSettings()
{
if (!mMouseSettings)
{
mMouseSettings = new KGlobalSettings::KMouseSettings;
KGlobalSettings::KMouseSettings& s = *mMouseSettings; // for convenience
#ifndef Q_WS_WIN
KConfigGroup g( KGlobal::config(), "Mouse" );
QString setting = g.readEntry("MouseButtonMapping");
if (setting == "RightHanded")
s.handed = KGlobalSettings::KMouseSettings::RightHanded;
else if (setting == "LeftHanded")
s.handed = KGlobalSettings::KMouseSettings::LeftHanded;
else
{
#ifdef Q_WS_X11
// get settings from X server
// This is a simplified version of the code in input/mouse.cpp
// Keep in sync !
s.handed = KGlobalSettings::KMouseSettings::RightHanded;
unsigned char map[20];
int num_buttons = XGetPointerMapping(QX11Info::display(), map, 20);
if( num_buttons == 2 )
{
if ( (int)map[0] == 1 && (int)map[1] == 2 )
s.handed = KGlobalSettings::KMouseSettings::RightHanded;
else if ( (int)map[0] == 2 && (int)map[1] == 1 )
s.handed = KGlobalSettings::KMouseSettings::LeftHanded;
}
else if( num_buttons >= 3 )
{
if ( (int)map[0] == 1 && (int)map[2] == 3 )
s.handed = KGlobalSettings::KMouseSettings::RightHanded;
else if ( (int)map[0] == 3 && (int)map[2] == 1 )
s.handed = KGlobalSettings::KMouseSettings::LeftHanded;
}
#else
// FIXME: Implement on other platforms
#endif
}
#endif //Q_WS_WIN
}
#ifdef Q_WS_WIN
//not cached
#ifndef _WIN32_WCE
mMouseSettings->handed = (GetSystemMetrics(SM_SWAPBUTTON) ?
KGlobalSettings::KMouseSettings::LeftHanded :
KGlobalSettings::KMouseSettings::RightHanded);
#else
// There is no mice under wince
mMouseSettings->handed =KGlobalSettings::KMouseSettings::RightHanded;
#endif
#endif
return *mMouseSettings;
}
// KDE5: make this a const return?
KGlobalSettings::KMouseSettings & KGlobalSettings::mouseSettings()
{
return KGlobalSettingsData::self()->mouseSettings();
}
void KGlobalSettingsData::dropMouseSettingsCache()
{
#ifndef Q_WS_WIN
delete mMouseSettings;
mMouseSettings = 0;
#endif
}
QString KGlobalSettings::desktopPath()
{
QString path = QStandardPaths::writableLocation( QStandardPaths::DesktopLocation );
return path.isEmpty() ? QDir::homePath() : path;
}
// Autostart is not a XDG path, so we have our own code for it.
QString KGlobalSettings::autostartPath()
{
QString s_autostartPath;
KConfigGroup g( KGlobal::config(), "Paths" );
s_autostartPath = KGlobal::dirs()->localkdedir() + "Autostart/";
s_autostartPath = g.readPathEntry( "Autostart" , s_autostartPath );
s_autostartPath = QDir::cleanPath( s_autostartPath );
if ( !s_autostartPath.endsWith( '/' ) ) {
s_autostartPath.append( QLatin1Char( '/' ) );
}
return s_autostartPath;
}
QString KGlobalSettings::documentPath()
{
QString path = QStandardPaths::writableLocation( QStandardPaths::DocumentsLocation );
return path.isEmpty() ? QDir::homePath() : path;
}
QString KGlobalSettings::downloadPath()
{
// Qt 4.4.1 does not have DOWNLOAD, so we based on old code for now
QString downloadPath = QDir::homePath();
#ifndef Q_WS_WIN
const QString xdgUserDirs = KGlobal::dirs()->localxdgconfdir() + QLatin1String( "user-dirs.dirs" );
if( QFile::exists( xdgUserDirs ) ) {
KConfig xdgUserConf( xdgUserDirs, KConfig::SimpleConfig );
KConfigGroup g( &xdgUserConf, "" );
downloadPath = g.readPathEntry( "XDG_DOWNLOAD_DIR", downloadPath ).remove( '"' );
if ( downloadPath.isEmpty() ) {
downloadPath = QDir::homePath();
}
}
#endif
downloadPath = QDir::cleanPath( downloadPath );
if ( !downloadPath.endsWith( '/' ) ) {
downloadPath.append( QLatin1Char( '/' ) );
}
return downloadPath;
}
QString KGlobalSettings::videosPath()
{
QString path = QStandardPaths::writableLocation( QStandardPaths::MoviesLocation );
return path.isEmpty() ? QDir::homePath() : path;
}
QString KGlobalSettings::picturesPath()
{
QString path = QStandardPaths::writableLocation( QStandardPaths::PicturesLocation );
return path.isEmpty() ? QDir::homePath() :path;
}
QString KGlobalSettings::musicPath()
{
QString path = QStandardPaths::writableLocation( QStandardPaths::MusicLocation );
return path.isEmpty() ? QDir::homePath() : path;
}
bool KGlobalSettings::isMultiHead()
{
#ifdef Q_WS_WIN
return GetSystemMetrics(SM_CMONITORS) > 1;
#else
QByteArray multiHead = qgetenv("KDE_MULTIHEAD");
if (!multiHead.isEmpty()) {
return (multiHead.toLower() == "true");
}
return false;
#endif
}
bool KGlobalSettings::wheelMouseZooms()
{
KConfigGroup g( KGlobal::config(), "KDE" );
return g.readEntry( "WheelMouseZooms", KDE_DEFAULT_WHEEL_ZOOM );
}
QRect KGlobalSettings::splashScreenDesktopGeometry()
{
QDesktopWidget *dw = QApplication::desktop();
if (dw->isVirtualDesktop()) {
KConfigGroup group(KGlobal::config(), "Windows");
int scr = group.readEntry("Unmanaged", -3);
if (group.readEntry("XineramaEnabled", true) && scr != -2) {
if (scr == -3)
scr = dw->screenNumber(QCursor::pos());
return dw->screenGeometry(scr);
} else {
return dw->geometry();
}
} else {
return dw->geometry();
}
}
QRect KGlobalSettings::desktopGeometry(const QPoint& point)
{
QDesktopWidget *dw = QApplication::desktop();
if (dw->isVirtualDesktop()) {
KConfigGroup group(KGlobal::config(), "Windows");
if (group.readEntry("XineramaEnabled", true) &&
group.readEntry("XineramaPlacementEnabled", true)) {
return dw->screenGeometry(dw->screenNumber(point));
} else {
return dw->geometry();
}
} else {
return dw->geometry();
}
}
QRect KGlobalSettings::desktopGeometry(const QWidget* w)
{
QDesktopWidget *dw = QApplication::desktop();
if (dw->isVirtualDesktop()) {
KConfigGroup group(KGlobal::config(), "Windows");
if (group.readEntry("XineramaEnabled", true) &&
group.readEntry("XineramaPlacementEnabled", true)) {
if (w)
return dw->screenGeometry(dw->screenNumber(w));
else return dw->screenGeometry(-1);
} else {
return dw->geometry();
}
} else {
return dw->geometry();
}
}
bool KGlobalSettings::showIconsOnPushButtons()
{
KConfigGroup g( KGlobal::config(), "KDE" );
return g.readEntry("ShowIconsOnPushButtons",
KDE_DEFAULT_ICON_ON_PUSHBUTTON);
}
bool KGlobalSettings::naturalSorting()
{
KConfigGroup g( KGlobal::config(), "KDE" );
return g.readEntry("NaturalSorting",
KDE_DEFAULT_NATURAL_SORTING);
}
KGlobalSettings::GraphicEffects KGlobalSettings::graphicEffectsLevel()
{
// This variable stores whether _graphicEffects has the default value because it has not been
// loaded yet, or if it has been loaded from the user settings or defaults and contains a valid
// value.
static bool _graphicEffectsInitialized = false;
if (!_graphicEffectsInitialized) {
_graphicEffectsInitialized = true;
Private::rereadOtherSettings();
}
return _graphicEffects;
}
KGlobalSettings::GraphicEffects KGlobalSettings::graphicEffectsLevelDefault()
{
// For now, let always enable animations by default. The plan is to make
// this code a bit smarter. (ereslibre)
return ComplexAnimationEffects;
}
bool KGlobalSettings::showFilePreview(const KUrl &url)
{
KConfigGroup g(KGlobal::config(), "PreviewSettings");
- QString protocol = url.protocol();
+ QString protocol = url.scheme();
bool defaultSetting = KProtocolInfo::showFilePreview( protocol );
return g.readEntry(protocol, defaultSetting );
}
bool KGlobalSettings::opaqueResize()
{
KConfigGroup g( KGlobal::config(), "KDE" );
return g.readEntry("OpaqueResize", KDE_DEFAULT_OPAQUE_RESIZE);
}
int KGlobalSettings::buttonLayout()
{
KConfigGroup g( KGlobal::config(), "KDE" );
return g.readEntry("ButtonLayout", KDE_DEFAULT_BUTTON_LAYOUT);
}
void KGlobalSettings::emitChange(ChangeType changeType, int arg)
{
QDBusMessage message = QDBusMessage::createSignal("/KGlobalSettings", "org.kde.KGlobalSettings", "notifyChange" );
QList<QVariant> args;
args.append(static_cast<int>(changeType));
args.append(arg);
message.setArguments(args);
QDBusConnection::sessionBus().send(message);
#ifdef Q_WS_X11
if (qApp && qApp->type() != QApplication::Tty) {
//notify non-kde qt applications of the change
extern void qt_x11_apply_settings_in_all_apps();
qt_x11_apply_settings_in_all_apps();
}
#endif
}
void KGlobalSettings::Private::_k_slotNotifyChange(int changeType, int arg)
{
switch(changeType) {
case StyleChanged:
if (activated) {
KGlobal::config()->reparseConfiguration();
kdisplaySetStyle();
}
break;
case ToolbarStyleChanged:
KGlobal::config()->reparseConfiguration();
emit q->toolbarAppearanceChanged(arg);
break;
case PaletteChanged:
if (activated) {
KGlobal::config()->reparseConfiguration();
paletteCreated = false;
kdisplaySetPalette();
}
break;
case FontChanged:
KGlobal::config()->reparseConfiguration();
KGlobalSettingsData::self()->dropFontSettingsCache();
if (activated) {
kdisplaySetFont();
}
break;
case SettingsChanged: {
KGlobal::config()->reparseConfiguration();
rereadOtherSettings();
SettingsCategory category = static_cast<SettingsCategory>(arg);
if (category == SETTINGS_MOUSE) {
KGlobalSettingsData::self()->dropMouseSettingsCache();
}
if (category == SETTINGS_QT) {
if (activated) {
propagateQtSettings();
}
} else {
if (category == SETTINGS_LOCALE) {
KGlobal::locale()->reparseConfiguration();
}
emit q->settingsChanged(category);
}
break;
}
case IconChanged:
QPixmapCache::clear();
KGlobal::config()->reparseConfiguration();
emit q->iconChanged(arg);
break;
case CursorChanged:
applyCursorTheme();
break;
case BlockShortcuts:
// FIXME KAccel port
//KGlobalAccel::blockShortcuts(arg);
emit q->blockShortcuts(arg); // see kwin
break;
case NaturalSortingChanged:
emit q->naturalSortingChanged();
break;
default:
kWarning(240) << "Unknown type of change in KGlobalSettings::slotNotifyChange: " << changeType;
}
}
// Set by KApplication
QString kde_overrideStyle;
void KGlobalSettings::Private::applyGUIStyle()
{
//Platform plugin only loaded on X11 systems
#ifdef Q_WS_X11
if (!kde_overrideStyle.isEmpty()) {
const QLatin1String currentStyleName(qApp->style()->metaObject()->className());
if (0 != kde_overrideStyle.compare(currentStyleName, Qt::CaseInsensitive) &&
0 != (QString(kde_overrideStyle + QLatin1String("Style"))).compare(currentStyleName, Qt::CaseInsensitive)) {
qApp->setStyle(kde_overrideStyle);
}
} else {
emit q->kdisplayStyleChanged();
}
#else
const QLatin1String currentStyleName(qApp->style()->metaObject()->className());
if (kde_overrideStyle.isEmpty()) {
const QString &defaultStyle = KStyle::defaultStyle();
const KConfigGroup pConfig(KGlobal::config(), "General");
const QString &styleStr = pConfig.readEntry("widgetStyle", defaultStyle);
if (styleStr.isEmpty() ||
// check whether we already use the correct style to return then
// (workaround for Qt misbehavior to avoid double style initialization)
0 == (QString(styleStr + QLatin1String("Style"))).compare(currentStyleName, Qt::CaseInsensitive) ||
0 == styleStr.compare(currentStyleName, Qt::CaseInsensitive)) {
return;
}
QStyle* sp = QStyleFactory::create( styleStr );
if (sp && currentStyleName == sp->metaObject()->className()) {
delete sp;
return;
}
// If there is no default style available, try falling back any available style
if ( !sp && styleStr != defaultStyle)
sp = QStyleFactory::create( defaultStyle );
if ( !sp )
sp = QStyleFactory::create( QStyleFactory::keys().first() );
qApp->setStyle(sp);
} else if (0 != kde_overrideStyle.compare(currentStyleName, Qt::CaseInsensitive) &&
0 != (QString(kde_overrideStyle + QLatin1String("Style"))).compare(currentStyleName, Qt::CaseInsensitive)) {
qApp->setStyle(kde_overrideStyle);
}
emit q->kdisplayStyleChanged();
#endif //Q_WS_X11
}
QPalette KGlobalSettings::createApplicationPalette(const KSharedConfigPtr &config)
{
return self()->d->createApplicationPalette(config);
}
QPalette KGlobalSettings::createNewApplicationPalette(const KSharedConfigPtr &config)
{
return self()->d->createNewApplicationPalette(config);
}
QPalette KGlobalSettings::Private::createApplicationPalette(const KSharedConfigPtr &config)
{
// This method is typically called once by KQGuiPlatformPlugin::palette and once again
// by kdisplaySetPalette(), so we cache the palette to save time.
if (config == KGlobal::config() && paletteCreated) {
return applicationPalette;
}
return createNewApplicationPalette(config);
}
QPalette KGlobalSettings::Private::createNewApplicationPalette(const KSharedConfigPtr &config)
{
QPalette palette;
QPalette::ColorGroup states[3] = { QPalette::Active, QPalette::Inactive,
QPalette::Disabled };
// TT thinks tooltips shouldn't use active, so we use our active colors for all states
KColorScheme schemeTooltip(QPalette::Active, KColorScheme::Tooltip, config);
for ( int i = 0; i < 3 ; i++ ) {
QPalette::ColorGroup state = states[i];
KColorScheme schemeView(state, KColorScheme::View, config);
KColorScheme schemeWindow(state, KColorScheme::Window, config);
KColorScheme schemeButton(state, KColorScheme::Button, config);
KColorScheme schemeSelection(state, KColorScheme::Selection, config);
palette.setBrush( state, QPalette::WindowText, schemeWindow.foreground() );
palette.setBrush( state, QPalette::Window, schemeWindow.background() );
palette.setBrush( state, QPalette::Base, schemeView.background() );
palette.setBrush( state, QPalette::Text, schemeView.foreground() );
palette.setBrush( state, QPalette::Button, schemeButton.background() );
palette.setBrush( state, QPalette::ButtonText, schemeButton.foreground() );
palette.setBrush( state, QPalette::Highlight, schemeSelection.background() );
palette.setBrush( state, QPalette::HighlightedText, schemeSelection.foreground() );
palette.setBrush( state, QPalette::ToolTipBase, schemeTooltip.background() );
palette.setBrush( state, QPalette::ToolTipText, schemeTooltip.foreground() );
palette.setColor( state, QPalette::Light, schemeWindow.shade( KColorScheme::LightShade ) );
palette.setColor( state, QPalette::Midlight, schemeWindow.shade( KColorScheme::MidlightShade ) );
palette.setColor( state, QPalette::Mid, schemeWindow.shade( KColorScheme::MidShade ) );
palette.setColor( state, QPalette::Dark, schemeWindow.shade( KColorScheme::DarkShade ) );
palette.setColor( state, QPalette::Shadow, schemeWindow.shade( KColorScheme::ShadowShade ) );
palette.setBrush( state, QPalette::AlternateBase, schemeView.background( KColorScheme::AlternateBackground) );
palette.setBrush( state, QPalette::Link, schemeView.foreground( KColorScheme::LinkText ) );
palette.setBrush( state, QPalette::LinkVisited, schemeView.foreground( KColorScheme::VisitedText ) );
}
if (config == KGlobal::config()) {
paletteCreated = true;
applicationPalette = palette;
}
return palette;
}
void KGlobalSettings::Private::kdisplaySetPalette()
{
#if !defined(Q_WS_MAEMO_5) && !defined(Q_OS_WINCE) && !defined(MEEGO_EDITION_HARMATTAN)
if (!kdeFullSession) {
return;
}
if (qApp->type() == QApplication::GuiClient) {
QApplication::setPalette( q->createApplicationPalette() );
}
emit q->kdisplayPaletteChanged();
emit q->appearanceChanged();
#endif
}
void KGlobalSettings::Private::kdisplaySetFont()
{
#if !defined(Q_WS_MAEMO_5) && !defined(Q_OS_WINCE) && !defined(MEEGO_EDITION_HARMATTAN)
if (!kdeFullSession) {
return;
}
if (qApp->type() == QApplication::GuiClient) {
KGlobalSettingsData* data = KGlobalSettingsData::self();
QApplication::setFont( data->font(KGlobalSettingsData::GeneralFont) );
const QFont menuFont = data->font( KGlobalSettingsData::MenuFont );
QApplication::setFont( menuFont, "QMenuBar" );
QApplication::setFont( menuFont, "QMenu" );
QApplication::setFont( menuFont, "KPopupTitle" );
QApplication::setFont( data->font(KGlobalSettingsData::ToolbarFont), "QToolBar" );
}
emit q->kdisplayFontChanged();
emit q->appearanceChanged();
#endif
}
void KGlobalSettings::Private::kdisplaySetStyle()
{
if (qApp->type() == QApplication::GuiClient) {
applyGUIStyle();
// Reread palette from config file.
kdisplaySetPalette();
}
}
void KGlobalSettings::Private::rereadOtherSettings()
{
KConfigGroup g( KGlobal::config(), "KDE-Global GUI Settings" );
// Asking for hasKey we do not ask for graphicEffectsLevelDefault() that can
// contain some very slow code. If we can save that time, do it. (ereslibre)
if (g.hasKey("GraphicEffectsLevel")) {
_graphicEffects = ((GraphicEffects) g.readEntry("GraphicEffectsLevel", QVariant((int) NoEffects)).toInt());
return;
}
_graphicEffects = KGlobalSettings::graphicEffectsLevelDefault();
}
void KGlobalSettings::Private::applyCursorTheme()
{
#if defined(Q_WS_X11) && defined(HAVE_XCURSOR)
KConfig config("kcminputrc");
KConfigGroup g(&config, "Mouse");
QString theme = g.readEntry("cursorTheme", QString());
int size = g.readEntry("cursorSize", -1);
// Default cursor size is 16 points
if (size == -1)
{
QApplication *app = static_cast<QApplication*>(QApplication::instance());
size = app->desktop()->screen(0)->logicalDpiY() * 16 / 72;
}
// Note that in X11R7.1 and earlier, calling XcursorSetTheme()
// with a NULL theme would cause Xcursor to use "default", but
// in 7.2 and later it will cause it to revert to the theme that
// was configured when the application was started.
XcursorSetTheme(QX11Info::display(), theme.isNull() ?
"default" : QFile::encodeName(theme));
XcursorSetDefaultSize(QX11Info::display(), size);
emit q->cursorChanged();
#endif
}
void KGlobalSettings::Private::propagateQtSettings()
{
KConfigGroup cg( KGlobal::config(), "KDE" );
int num = cg.readEntry("CursorBlinkRate", QApplication::cursorFlashTime());
if ((num != 0) && (num < 200))
num = 200;
if (num > 2000)
num = 2000;
QApplication::setCursorFlashTime(num);
num = cg.readEntry("DoubleClickInterval", QApplication::doubleClickInterval());
QApplication::setDoubleClickInterval(num);
num = cg.readEntry("StartDragTime", QApplication::startDragTime());
QApplication::setStartDragTime(num);
num = cg.readEntry("StartDragDist", QApplication::startDragDistance());
QApplication::setStartDragDistance(num);
num = cg.readEntry("WheelScrollLines", QApplication::wheelScrollLines());
QApplication::setWheelScrollLines(num);
bool showIcons = cg.readEntry("ShowIconsInMenuItems", !QApplication::testAttribute(Qt::AA_DontShowIconsInMenus));
QApplication::setAttribute(Qt::AA_DontShowIconsInMenus, !showIcons);
// KDE5: this seems fairly pointless
emit q->settingsChanged(SETTINGS_QT);
}
#include "moc_kglobalsettings.cpp"
diff --git a/kfile/kdiroperator.cpp b/kfile/kdiroperator.cpp
index 338753eacb..a378612d5c 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;
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.protocol().isEmpty())
+ 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->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;
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;
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->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();
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/kdirselectdialog.cpp b/kfile/kdirselectdialog.cpp
index 020a9d7075..990be42e6a 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)) {
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)) {
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.protocol() != d->m_rootUrl.protocol()) {
+ 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/kfilewidget.cpp b/kfile/kfilewidget.cpp
index 76ce27b98d..f2b6e44558 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 appendExtension(KUrl &url);
void updateLocationEditExtension(const QString &);
void updateFilter();
KUrl::List& 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;
/**
* 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
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));
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.
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();
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();
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;
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 )
{
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));
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
{
// kDebug(kfile_area);
KUrl::List list;
if ( d->inAccept ) {
if (d->ops->mode() & KFile::Files)
list = d->parseSelectedUrls();
else
list.append( d->url );
}
return list;
}
KUrl::List& 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
{
// kDebug(kfile_area);
KUrl::List 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();
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();
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.protocol() == "kfiledialog" )
+ 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/knewfilemenu.cpp b/kfile/knewfilemenu.cpp
index 31486b40df..a6d9c766a7 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;
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();
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();
for (; it != m_popupFiles.constEnd(); ++it)
{
KUrl dest(*it);
dest.addPath(KIO::encodeFileName(chosenFileName));
KUrl::List 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.protocol()));
+ 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
{
return d->m_popupFiles;
}
void KNewFileMenu::setModal(bool modal)
{
d->m_modal = modal;
}
void KNewFileMenu::setPopupFiles(const KUrl::List& 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/kurlnavigator.cpp b/kfile/kurlnavigator.cpp
index 885f4290d4..779562fb4f 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());
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().protocol();
+ 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.protocol() != "nepomuksearch");
+ 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.protocol() + QLatin1Char(':');
+ 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.protocol() == QLatin1String("tar")) || (url.protocol() == QLatin1String("zip"))) {
+ 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 c6fd4ff407..3b0f480774 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.protocol());
+ 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.protocol();
+ 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());
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/tests/kurlnavigatortest.cpp b/kfile/tests/kurlnavigatortest.cpp
index 57115aaf9b..eeff6f1a51 100644
--- a/kfile/tests/kurlnavigatortest.cpp
+++ b/kfile/tests/kurlnavigatortest.cpp
@@ -1,191 +1,191 @@
/***************************************************************************
* Copyright (C) 2008 by Peter Penz <peter.penz@gmx.at> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
***************************************************************************/
#include "kurlnavigatortest.h"
#include <qtest_kde.h>
#include <kurlnavigator.h>
QTEST_KDEMAIN(KUrlNavigatorTest, GUI)
void KUrlNavigatorTest::initTestCase()
{
m_navigator = new KUrlNavigator(0, KUrl("A"), 0);
}
void KUrlNavigatorTest::cleanupTestCase()
{
delete m_navigator;
m_navigator = 0;
}
void KUrlNavigatorTest::testHistorySizeAndIndex()
{
QCOMPARE(m_navigator->historyIndex(), 0);
QCOMPARE(m_navigator->historySize(), 1);
m_navigator->setLocationUrl(KUrl("A"));
QCOMPARE(m_navigator->historyIndex(), 0);
QCOMPARE(m_navigator->historySize(), 1);
m_navigator->setLocationUrl(KUrl("B"));
QCOMPARE(m_navigator->historyIndex(), 0);
QCOMPARE(m_navigator->historySize(), 2);
m_navigator->setLocationUrl(KUrl("C"));
QCOMPARE(m_navigator->historyIndex(), 0);
QCOMPARE(m_navigator->historySize(), 3);
}
void KUrlNavigatorTest::testGoBack()
{
QCOMPARE(m_navigator->historyIndex(), 0);
QCOMPARE(m_navigator->historySize(), 3);
bool ok = m_navigator->goBack();
QVERIFY(ok);
QCOMPARE(m_navigator->historyIndex(), 1);
QCOMPARE(m_navigator->historySize(), 3);
ok = m_navigator->goBack();
QVERIFY(ok);
QCOMPARE(m_navigator->historyIndex(), 2);
QCOMPARE(m_navigator->historySize(), 3);
ok = m_navigator->goBack();
QVERIFY(!ok);
QCOMPARE(m_navigator->historyIndex(), 2);
QCOMPARE(m_navigator->historySize(), 3);
}
void KUrlNavigatorTest::testGoForward()
{
QCOMPARE(m_navigator->historyIndex(), 2);
QCOMPARE(m_navigator->historySize(), 3);
bool ok = m_navigator->goForward();
QVERIFY(ok);
QCOMPARE(m_navigator->historyIndex(), 1);
QCOMPARE(m_navigator->historySize(), 3);
ok = m_navigator->goForward();
QVERIFY(ok);
QCOMPARE(m_navigator->historyIndex(), 0);
QCOMPARE(m_navigator->historySize(), 3);
ok = m_navigator->goForward();
QVERIFY(!ok);
QCOMPARE(m_navigator->historyIndex(), 0);
QCOMPARE(m_navigator->historySize(), 3);
}
void KUrlNavigatorTest::testHistoryInsert()
{
QCOMPARE(m_navigator->historyIndex(), 0);
QCOMPARE(m_navigator->historySize(), 3);
m_navigator->setLocationUrl(KUrl("D"));
QCOMPARE(m_navigator->historyIndex(), 0);
QCOMPARE(m_navigator->historySize(), 4);
bool ok = m_navigator->goBack();
QVERIFY(ok);
QCOMPARE(m_navigator->historyIndex(), 1);
QCOMPARE(m_navigator->historySize(), 4);
m_navigator->setLocationUrl(KUrl("E"));
QCOMPARE(m_navigator->historyIndex(), 0);
QCOMPARE(m_navigator->historySize(), 4);
m_navigator->setLocationUrl(KUrl("F"));
QCOMPARE(m_navigator->historyIndex(), 0);
QCOMPARE(m_navigator->historySize(), 5);
ok = m_navigator->goBack();
QVERIFY(ok);
ok = m_navigator->goBack();
QVERIFY(ok);
QCOMPARE(m_navigator->historyIndex(), 2);
QCOMPARE(m_navigator->historySize(), 5);
m_navigator->setLocationUrl(KUrl("G"));
QCOMPARE(m_navigator->historyIndex(), 0);
QCOMPARE(m_navigator->historySize(), 4);
// insert same URL as the current history index
m_navigator->setLocationUrl(KUrl("G"));
QCOMPARE(m_navigator->historyIndex(), 0);
QCOMPARE(m_navigator->historySize(), 4);
// insert same URL with a trailing slash as the current history index
m_navigator->setLocationUrl(KUrl("G/"));
QCOMPARE(m_navigator->historyIndex(), 0);
QCOMPARE(m_navigator->historySize(), 4);
// jump to "C" and insert same URL as the current history index
ok = m_navigator->goBack();
QVERIFY(ok);
QCOMPARE(m_navigator->historyIndex(), 1);
QCOMPARE(m_navigator->historySize(), 4);
m_navigator->setLocationUrl(KUrl("C"));
QCOMPARE(m_navigator->historyIndex(), 1);
QCOMPARE(m_navigator->historySize(), 4);
}
/**
* When the current URL is inside an archive and the user goes "up", it is expected
* that the new URL is that of the folder containing the archive (unless the URL was
* in a subfolder inside the archive). Furthermore, the protocol should be "file".
* An empty protocol would lead to problems in Dolphin, see
*
* https://bugs.kde.org/show_bug.cgi?id=251553
*/
void KUrlNavigatorTest::bug251553_goUpFromArchive()
{
m_navigator->setLocationUrl(KUrl("zip:/test/archive.zip"));
QCOMPARE(m_navigator->locationUrl().path(), QLatin1String("/test/archive.zip"));
- QCOMPARE(m_navigator->locationUrl().protocol(), QLatin1String("zip"));
+ QCOMPARE(m_navigator->locationUrl().scheme(), QLatin1String("zip"));
bool ok = m_navigator->goUp();
QVERIFY(ok);
QCOMPARE(m_navigator->locationUrl().path(KUrl::AddTrailingSlash), QLatin1String("/test/"));
- QCOMPARE(m_navigator->locationUrl().protocol(), QLatin1String("file"));
+ QCOMPARE(m_navigator->locationUrl().scheme(), QLatin1String("file"));
m_navigator->setLocationUrl(KUrl("tar:/test/archive.tar.gz"));
QCOMPARE(m_navigator->locationUrl().path(), QLatin1String("/test/archive.tar.gz"));
- QCOMPARE(m_navigator->locationUrl().protocol(), QLatin1String("tar"));
+ QCOMPARE(m_navigator->locationUrl().scheme(), QLatin1String("tar"));
ok = m_navigator->goUp();
QVERIFY(ok);
QCOMPARE(m_navigator->locationUrl().path(KUrl::AddTrailingSlash), QLatin1String("/test/"));
- QCOMPARE(m_navigator->locationUrl().protocol(), QLatin1String("file"));
+ QCOMPARE(m_navigator->locationUrl().scheme(), QLatin1String("file"));
}
diff --git a/khtml/ecma/kjs_html.cpp b/khtml/ecma/kjs_html.cpp
index e3da95c788..f8c10d5e44 100644
--- a/khtml/ecma/kjs_html.cpp
+++ b/khtml/ecma/kjs_html.cpp
@@ -1,3510 +1,3510 @@
// -*- c-basic-offset: 2 -*-
/*
* This file is part of the KDE libraries
* Copyright (C) 1999-2002 Harri Porten (porten@kde.org)
* Copyright (C) 2001-2003 David Faure (faure@kde.org)
* Copyright (C) 2004 Apple Computer, Inc.
* Copyright (C) 2005 Maksim Orlovich (maksim@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; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "kjs_html.h"
#include "kjs_html.lut.h"
#include <misc/loader.h>
#include <html/html_blockimpl.h>
#include <html/html_headimpl.h>
#include <html/html_imageimpl.h>
#include <html/html_inlineimpl.h>
#include <html/html_listimpl.h>
#include <html/html_tableimpl.h>
#include <html/html_objectimpl.h>
#include <html/html_canvasimpl.h>
#include <dom/dom_exception.h>
#include <css/csshelper.h> // for parseUrl
#include <html/html_baseimpl.h>
#include <html/html_documentimpl.h>
#include <html/html_formimpl.h>
#include <html/html_miscimpl.h>
#include <xml/dom2_eventsimpl.h>
#include <kparts/browserextension.h>
#include <khtml_part.h>
#include <khtmlview.h>
#include "kjs_css.h"
#include "kjs_events.h"
#include "kjs_window.h"
#include "kjs_context2d.h"
#include <kjs/PropertyNameArray.h>
#include <rendering/render_object.h>
#include <rendering/render_canvas.h>
#include <rendering/render_frames.h>
#include <rendering/render_layer.h>
#include <kmessagebox.h>
#include <kstringhandler.h>
#include <klocale.h>
#include <kdebug.h>
#include <QtCore/QList>
#include <QtCore/QHash>
using namespace DOM;
namespace KJS {
KJS_DEFINE_PROTOTYPE(HTMLDocumentProto)
KJS_IMPLEMENT_PROTOFUNC(HTMLDocFunction)
KJS_IMPLEMENT_PROTOTYPE("HTMLDocument", HTMLDocumentProto, HTMLDocFunction, DOMDocumentProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLDocumentPseudoCtor, "HTMLDocument", HTMLDocumentProto)
/* Source for HTMLDocumentProtoTable.
@begin HTMLDocumentProtoTable 11
clear HTMLDocument::Clear DontDelete|Function 0
open HTMLDocument::Open DontDelete|Function 0
close HTMLDocument::Close DontDelete|Function 0
write HTMLDocument::Write DontDelete|Function 1
writeln HTMLDocument::WriteLn DontDelete|Function 1
getElementsByName HTMLDocument::GetElementsByName DontDelete|Function 1
getSelection HTMLDocument::GetSelection DontDelete|Function 1
captureEvents HTMLDocument::CaptureEvents DontDelete|Function 0
releaseEvents HTMLDocument::ReleaseEvents DontDelete|Function 0
@end
*/
JSValue* KJS::HTMLDocFunction::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args)
{
KJS_CHECK_THIS( HTMLDocument, thisObj );
DOM::HTMLDocumentImpl& doc = *static_cast<KJS::HTMLDocument *>(thisObj)->impl();
switch (id) {
case HTMLDocument::Clear: // even IE doesn't support that one...
//doc.clear(); // TODO
return jsUndefined();
case HTMLDocument::Open:
if (args.size() >= 3) // IE extension for document.open: it means window.open if it has 3 args or more
{
KHTMLPart* part = doc.part();
if ( part ) {
Window* win = Window::retrieveWindow(part);
if( win ) {
win->openWindow(exec, args);
}
}
}
doc.open();
return jsUndefined();
case HTMLDocument::Close:
// see khtmltests/ecma/tokenizer-script-recursion.html
doc.close();
return jsUndefined();
case HTMLDocument::Write:
case HTMLDocument::WriteLn: {
// DOM only specifies single string argument, but NS & IE allow multiple
// or no arguments
UString str = "";
for (int i = 0; i < args.size(); i++)
str += args[i]->toString(exec);
if (id == HTMLDocument::WriteLn)
str += "\n";
#ifdef KJS_VERBOSE
kDebug(6070) << "document.write: " << str.qstring();
#endif
doc.write(str.qstring());
return jsUndefined();
}
case HTMLDocument::GetElementsByName:
return getDOMNodeList(exec,doc.getElementsByName(args[0]->toString(exec).domString()));
case HTMLDocument::GetSelection: {
// NS4 and Mozilla specific. IE uses document.selection.createRange()
// http://docs.sun.com/source/816-6408-10/document.htm#1195981
KHTMLPart *part = doc.part();
if ( part )
return jsString(part->selectedText());
else
return jsUndefined();
}
case HTMLDocument::CaptureEvents:
case HTMLDocument::ReleaseEvents:
// Do nothing for now. These are NS-specific legacy calls.
break;
}
return jsUndefined();
}
const ClassInfo KJS::HTMLDocument::info =
{ "HTMLDocument", &DOMDocument::info, &HTMLDocumentTable, 0 };
/* Source for HTMLDocumentTable.
@begin HTMLDocumentTable 31
referrer HTMLDocument::Referrer DontDelete|ReadOnly
domain HTMLDocument::Domain DontDelete
URL HTMLDocument::URL DontDelete|ReadOnly
body HTMLDocument::Body DontDelete
location HTMLDocument::Location DontDelete
cookie HTMLDocument::Cookie DontDelete
images HTMLDocument::Images DontDelete|ReadOnly
applets HTMLDocument::Applets DontDelete|ReadOnly
links HTMLDocument::Links DontDelete|ReadOnly
forms HTMLDocument::Forms DontDelete|ReadOnly
anchors HTMLDocument::Anchors DontDelete|ReadOnly
scripts HTMLDocument::Scripts DontDelete|ReadOnly
all HTMLDocument::All DontDelete|ReadOnly
bgColor HTMLDocument::BgColor DontDelete
fgColor HTMLDocument::FgColor DontDelete
alinkColor HTMLDocument::AlinkColor DontDelete
linkColor HTMLDocument::LinkColor DontDelete
vlinkColor HTMLDocument::VlinkColor DontDelete
lastModified HTMLDocument::LastModified DontDelete|ReadOnly
height HTMLDocument::Height DontDelete|ReadOnly
width HTMLDocument::Width DontDelete|ReadOnly
dir HTMLDocument::Dir DontDelete
compatMode HTMLDocument::CompatMode DontDelete|ReadOnly
designMode HTMLDocument::DesignMode DontDelete
#IE extension
frames HTMLDocument::Frames DontDelete|ReadOnly
#NS4 extension
layers HTMLDocument::Layers DontDelete|ReadOnly
# HTML5
activeElement HTMLDocument::ActiveElement DontDelete|ReadOnly
#potentially obsolete array properties
# plugins
# tags
#potentially obsolete properties
# embeds
# ids
@end
*/
KJS::HTMLDocument::HTMLDocument(ExecState *exec, DOM::HTMLDocumentImpl* d)
: DOMDocument(HTMLDocumentProto::self(exec), d) { }
/* Should this property be checked after overrides? */
static bool isLateProperty(unsigned token)
{
switch (token) {
case HTMLDocument::BgColor:
case HTMLDocument::FgColor:
case HTMLDocument::AlinkColor:
case HTMLDocument::LinkColor:
case HTMLDocument::VlinkColor:
case HTMLDocument::LastModified:
case HTMLDocument::Height: // NS-only, not available in IE
case HTMLDocument::Width: // NS-only, not available in IE
case HTMLDocument::Dir:
case HTMLDocument::Frames:
return true;
default:
return false;
}
}
bool KJS::HTMLDocument::getOwnPropertySlot(ExecState *exec, const Identifier &propertyName, PropertySlot& slot)
{
#ifdef KJS_VERBOSE
kDebug(6070) << "KJS::HTMLDocument::getOwnPropertySlot " << propertyName.qstring();
#endif
DOM::DocumentImpl* docImpl = impl();
KHTMLPart *part = docImpl->part();
Window* win = part ? Window::retrieveWindow(part) : 0L;
if ( !win || !win->isSafeScript(exec) ) {
slot.setUndefined(this);
return true;
}
DOM::DOMString propertyDOMString = propertyName.domString();
//See whether to return named items under document.
ElementMappingCache::ItemInfo* info = docImpl->underDocNamedCache().get(propertyDOMString);
if (info) {
//May be a false positive, but we can try to avoid doing it the hard way in
//simpler cases. The trickiness here is that the cache is kept under both
//name and id, but we sometimes ignore id for IE compat
bool matched = false;
if (info->nd && DOM::HTMLMappedNameCollectionImpl::matchesName(info->nd,
HTMLCollectionImpl::DOCUMENT_NAMED_ITEMS, propertyDOMString)) {
matched = true;
} else {
//Can't tell it just like that, so better go through collection and count stuff. This is the slow path...
DOM::HTMLMappedNameCollectionImpl coll(impl(), HTMLCollectionImpl::DOCUMENT_NAMED_ITEMS, propertyDOMString);
matched = coll.firstItem() != 0;
}
if (matched) {
slot.setCustom(this, nameGetter);
return true;
}
}
// Check for frames/iframes with name==propertyName
if ( part ) {
if (part->findFrame( propertyName.qstring() )) {
slot.setCustom(this, frameNameGetter);
return true;
}
}
// Static properties
const HashEntry* entry = Lookup::findEntry(&HTMLDocumentTable, propertyName);
if (entry && !isLateProperty(entry->value)) {
getSlotFromEntry<HTMLDocFunction, HTMLDocument>(entry, this, slot);
return true;
}
// Look for overrides
JSValue **val = getDirectLocation(propertyName);
if (val) {
fillDirectLocationSlot(slot, val);
return true;
}
// The rest of static properties -- the late ones.
if (entry) {
getSlotFromEntry<HTMLDocFunction, HTMLDocument>(entry, this, slot);
return true;
}
return DOMDocument::getOwnPropertySlot(exec, propertyName, slot);
}
JSValue *HTMLDocument::nameGetter(ExecState *exec, JSObject*, const Identifier& propertyName, const PropertySlot& slot)
{
HTMLDocument *thisObj = static_cast<HTMLDocument*>(slot.slotBase());
DOM::DocumentImpl* docImpl = thisObj->impl();
//Return named items under document (e.g. images, applets, etc.)
DOMString name = propertyName.domString();
ElementMappingCache::ItemInfo* info = docImpl->underDocNamedCache().get(name);
if (info && info->nd)
return getDOMNode(exec, info->nd);
else {
//No cached mapping, do it the hard way..
DOM::HTMLMappedNameCollectionImpl* coll = new DOM::HTMLMappedNameCollectionImpl(docImpl,
HTMLCollectionImpl::DOCUMENT_NAMED_ITEMS, name);
if (info && coll->length() == 1) {
info->nd = static_cast<DOM::ElementImpl*>(coll->firstItem());
delete coll;
return getDOMNode(exec, info->nd);
}
return getHTMLCollection(exec, coll);
}
assert(0);
return jsUndefined();
}
JSValue *HTMLDocument::frameNameGetter(ExecState*, JSObject*, const Identifier& name, const PropertySlot& slot)
{
HTMLDocument *thisObj = static_cast<HTMLDocument*>(slot.slotBase());
// Check for frames/iframes with name==propertyName
return Window::retrieve(thisObj->impl()->part()->findFrame( name.qstring() ));
}
JSValue *HTMLDocument::objectNameGetter(ExecState *exec, JSObject*, const Identifier& name, const PropertySlot& slot)
{
HTMLDocument *thisObj = static_cast<HTMLDocument*>(slot.slotBase());
DOM::HTMLCollectionImpl objectLike(thisObj->impl(), DOM::HTMLCollectionImpl::DOC_APPLETS);
return getDOMNode(exec, objectLike.namedItem(name.domString()));
}
JSValue *HTMLDocument::layerNameGetter(ExecState *exec, JSObject*, const Identifier& name, const PropertySlot& slot)
{
HTMLDocument *thisObj = static_cast<HTMLDocument*>(slot.slotBase());
DOM::HTMLCollectionImpl layerLike(thisObj->impl(), DOM::HTMLCollectionImpl::DOC_LAYERS);
return getDOMNode(exec, layerLike.namedItem(name.domString()));
}
JSValue* HTMLDocument::getValueProperty(ExecState *exec, int token)
{
DOM::HTMLDocumentImpl& doc = *impl();
KHTMLView* view = doc.view();
KHTMLPart* part = doc.part();
Window* win = part ? Window::retrieveWindow(part) : 0L;
DOM::HTMLElementImpl* body = doc.body();
switch (token) {
case Referrer:
return jsString(doc.referrer());
case Domain:
return jsString(doc.domain());
case URL:
return jsString(doc.URL().url());
case Body:
return getDOMNode(exec,doc.body());
case Location:
if (win)
return win->location();
else
return jsUndefined();
case Cookie:
return jsString(doc.cookie());
case Images:
return getHTMLCollection(exec,doc.images());
case Applets:
return getHTMLCollection(exec,doc.applets());
case Links:
return getHTMLCollection(exec,doc.links());
case Forms:
return getHTMLCollection(exec,doc.forms());
case Layers:
// ### Should not be hidden when we emulate Netscape4
return getHTMLCollection(exec,doc.layers(), true);
case Anchors:
return getHTMLCollection(exec,doc.anchors());
case Scripts:
return getHTMLCollection(exec,doc.scripts());
case All:
// Disable document.all when we try to be Netscape-compatible
if ( exec->dynamicInterpreter()->compatMode() == Interpreter::NetscapeCompat )
return jsUndefined();
else
if ( exec->dynamicInterpreter()->compatMode() == Interpreter::IECompat )
return getHTMLCollection(exec,doc.all());
else // enabled but hidden
return getHTMLCollection(exec,doc.all(), true);
case CompatMode:
return jsString(doc.parseMode()
== DocumentImpl::Compat ? "BackCompat" : "CSS1Compat");
case DesignMode:
return jsString((doc.designMode() ? "on":"off"));
case ActiveElement:
return getDOMNode(exec, doc.activeElement());
case BgColor:
return jsString(body ? body->getAttribute(ATTR_BGCOLOR) : DOMString());
case FgColor:
return jsString(body ? body->getAttribute(ATTR_TEXT) : DOMString());
case AlinkColor:
return jsString(body ? body->getAttribute(ATTR_ALINK) : DOMString());
case LinkColor:
return jsString(body ? body->getAttribute(ATTR_LINK) : DOMString());
case VlinkColor:
return jsString(body ? body->getAttribute(ATTR_VLINK) : DOMString());
case LastModified:
return jsString(doc.lastModified());
case Height: // NS-only, not available in IE
return jsNumber(view ? view->contentsHeight() : 0);
case Width: // NS-only, not available in IE
return jsNumber(view ? view->contentsWidth() : 0);
case Dir:
return body ? jsString(body->getAttribute(ATTR_DIR)) : jsUndefined();
case Frames:
if ( win )
return win;
else
return jsUndefined();
}
assert(0);
return 0;
}
void KJS::HTMLDocument::put(ExecState *exec, const Identifier &propertyName, JSValue *value, int attr)
{
#ifdef KJS_VERBOSE
kDebug(6070) << "KJS::HTMLDocument::out " << propertyName.qstring();
#endif
KHTMLPart* part = impl()->part();
Window* win = part ? Window::retrieveWindow(part) : 0L;
if ( !win || !win->isSafeScript(exec) )
return;
lookupPut<HTMLDocument, DOMDocument>( exec, propertyName, value, attr, &HTMLDocumentTable, this );
}
void KJS::HTMLDocument::putValueProperty(ExecState *exec, int token, JSValue *value, int /*attr*/)
{
DOM::HTMLDocumentImpl& doc = *impl();
DOM::DOMString val = value->toString(exec).domString();
DOMExceptionTranslator exception(exec);
switch (token) {
case Body: {
DOM::NodeImpl* body = toNode(value);
if (body && body->isHTMLElement())
doc.setBody(static_cast<DOM::HTMLElementImpl*>(body), exception);
return;
}
case Domain: { // not part of the DOM
doc.setDomain(val);
return;
}
case Cookie:
doc.setCookie(val);
return;
case Location:
{
KHTMLPart *part = doc.part();
if ( part )
Window::retrieveWindow(part)->goURL(exec, value->toString(exec).qstring(), false /*don't lock history*/);
return;
}
case DesignMode:
doc.setDesignMode((value->toString(exec).qstring().toLower()=="on"));
return;
}
/* The rest of the properties require a body. Note that Doc::body may be the
frameset(!!) so we have to be a bit careful here. I am not sure this is
100% right, but it should match previous behavior - M.O. */
DOM::HTMLElementImpl* bodyCand = doc.body();
if (!bodyCand || bodyCand->id() != ID_BODY)
return; //Just ignore.
DOM::HTMLBodyElementImpl& body = *static_cast<DOM::HTMLBodyElementImpl*>(bodyCand);
switch (token) {
case BgColor:
if (body.bgColor() != val) body.setBgColor(val);
break;
case FgColor:
if (body.text() != val) body.setText(val);
break;
case AlinkColor:
if (body.aLink() != val) body.setALink(val);
break;
case LinkColor:
if (body.link() != val) body.setLink(val);
break;
case VlinkColor:
if (body.vLink() != val) body.setVLink(val);
break;
case Dir:
body.setAttribute(ID_DIR, value->toString(exec).domString());
break;
default:
kDebug(6070) << "WARNING: HTMLDocument::putValueProperty unhandled token " << token;
}
}
// -------------------------------------------------------------------------
const ClassInfo KJS::HTMLElement::info = { "HTMLElement", &DOMElement::info, &HTMLElementTable, 0 };
const ClassInfo KJS::HTMLElement::html_info = { "HTMLHtmlElement", &KJS::HTMLElement::info, &HTMLHtmlElementTable, 0 };
const ClassInfo KJS::HTMLElement::head_info = { "HTMLHeadElement", &KJS::HTMLElement::info, &HTMLHeadElementTable, 0 };
const ClassInfo KJS::HTMLElement::link_info = { "HTMLLinkElement", &KJS::HTMLElement::info, &HTMLLinkElementTable, 0 };
const ClassInfo KJS::HTMLElement::title_info = { "HTMLTitleElement", &KJS::HTMLElement::info, &HTMLTitleElementTable, 0 };
const ClassInfo KJS::HTMLElement::meta_info = { "HTMLMetaElement", &KJS::HTMLElement::info, &HTMLMetaElementTable, 0 };
const ClassInfo KJS::HTMLElement::base_info = { "HTMLBaseElement", &KJS::HTMLElement::info, &HTMLBaseElementTable, 0 };
const ClassInfo KJS::HTMLElement::isIndex_info = { "HTMLIsIndexElement", &KJS::HTMLElement::info, &HTMLIsIndexElementTable, 0 };
const ClassInfo KJS::HTMLElement::style_info = { "HTMLStyleElement", &KJS::HTMLElement::info, &HTMLStyleElementTable, 0 };
const ClassInfo KJS::HTMLElement::body_info = { "HTMLBodyElement", &KJS::HTMLElement::info, &HTMLBodyElementTable, 0 };
const ClassInfo KJS::HTMLElement::form_info = { "HTMLFormElement", &KJS::HTMLElement::info, &HTMLFormElementTable, 0 };
const ClassInfo KJS::HTMLElement::select_info = { "HTMLSelectElement", &KJS::HTMLElement::info, &HTMLSelectElementTable, 0 };
const ClassInfo KJS::HTMLElement::optGroup_info = { "HTMLOptGroupElement", &KJS::HTMLElement::info, &HTMLOptGroupElementTable, 0 };
const ClassInfo KJS::HTMLElement::option_info = { "HTMLOptionElement", &KJS::HTMLElement::info, &HTMLOptionElementTable, 0 };
const ClassInfo KJS::HTMLElement::input_info = { "HTMLInputElement", &KJS::HTMLElement::info, &HTMLInputElementTable, 0 };
const ClassInfo KJS::HTMLElement::textArea_info = { "HTMLTextAreaElement", &KJS::HTMLElement::info, &HTMLTextAreaElementTable, 0 };
const ClassInfo KJS::HTMLElement::button_info = { "HTMLButtonElement", &KJS::HTMLElement::info, &HTMLButtonElementTable, 0 };
const ClassInfo KJS::HTMLElement::label_info = { "HTMLLabelElement", &KJS::HTMLElement::info, &HTMLLabelElementTable, 0 };
const ClassInfo KJS::HTMLElement::fieldSet_info = { "HTMLFieldSetElement", &KJS::HTMLElement::info, &HTMLFieldSetElementTable, 0 };
const ClassInfo KJS::HTMLElement::legend_info = { "HTMLLegendElement", &KJS::HTMLElement::info, &HTMLLegendElementTable, 0 };
const ClassInfo KJS::HTMLElement::ul_info = { "HTMLUListElement", &KJS::HTMLElement::info, &HTMLUListElementTable, 0 };
const ClassInfo KJS::HTMLElement::ol_info = { "HTMLOListElement", &KJS::HTMLElement::info, &HTMLOListElementTable, 0 };
const ClassInfo KJS::HTMLElement::dl_info = { "HTMLDListElement", &KJS::HTMLElement::info, &HTMLDListElementTable, 0 };
const ClassInfo KJS::HTMLElement::dir_info = { "HTMLDirectoryElement", &KJS::HTMLElement::info, &HTMLDirectoryElementTable, 0 };
const ClassInfo KJS::HTMLElement::menu_info = { "HTMLMenuElement", &KJS::HTMLElement::info, &HTMLMenuElementTable, 0 };
const ClassInfo KJS::HTMLElement::li_info = { "HTMLLIElement", &KJS::HTMLElement::info, &HTMLLIElementTable, 0 };
const ClassInfo KJS::HTMLElement::div_info = { "HTMLDivElement", &KJS::HTMLElement::info, &HTMLDivElementTable, 0 };
const ClassInfo KJS::HTMLElement::p_info = { "HTMLParagraphElement", &KJS::HTMLElement::info, &HTMLParagraphElementTable, 0 };
const ClassInfo KJS::HTMLElement::heading_info = { "HTMLHeadingElement", &KJS::HTMLElement::info, &HTMLHeadingElementTable, 0 };
const ClassInfo KJS::HTMLElement::blockQuote_info = { "HTMLBlockQuoteElement", &KJS::HTMLElement::info, &HTMLBlockQuoteElementTable, 0 };
const ClassInfo KJS::HTMLElement::q_info = { "HTMLQuoteElement", &KJS::HTMLElement::info, &HTMLQuoteElementTable, 0 };
const ClassInfo KJS::HTMLElement::pre_info = { "HTMLPreElement", &KJS::HTMLElement::info, &HTMLPreElementTable, 0 };
const ClassInfo KJS::HTMLElement::br_info = { "HTMLBRElement", &KJS::HTMLElement::info, &HTMLBRElementTable, 0 };
const ClassInfo KJS::HTMLElement::baseFont_info = { "HTMLBaseFontElement", &KJS::HTMLElement::info, &HTMLBaseFontElementTable, 0 };
const ClassInfo KJS::HTMLElement::font_info = { "HTMLFontElement", &KJS::HTMLElement::info, &HTMLFontElementTable, 0 };
const ClassInfo KJS::HTMLElement::hr_info = { "HTMLHRElement", &KJS::HTMLElement::info, &HTMLHRElementTable, 0 };
const ClassInfo KJS::HTMLElement::mod_info = { "HTMLModElement", &KJS::HTMLElement::info, &HTMLModElementTable, 0 };
const ClassInfo KJS::HTMLElement::a_info = { "HTMLAnchorElement", &KJS::HTMLElement::info, &HTMLAnchorElementTable, 0 };
const ClassInfo KJS::HTMLElement::canvas_info = { "HTMLCanvasElement", &KJS::HTMLElement::info, &HTMLCanvasElementTable, 0 };
const ClassInfo KJS::HTMLElement::img_info = { "HTMLImageElement", &KJS::HTMLElement::info, &HTMLImageElementTable, 0 };
const ClassInfo KJS::HTMLElement::object_info = { "HTMLObjectElement", &KJS::HTMLElement::info, &HTMLObjectElementTable, 0 };
const ClassInfo KJS::HTMLElement::param_info = { "HTMLParamElement", &KJS::HTMLElement::info, &HTMLParamElementTable, 0 };
const ClassInfo KJS::HTMLElement::applet_info = { "HTMLAppletElement", &KJS::HTMLElement::info, &HTMLAppletElementTable, 0 };
const ClassInfo KJS::HTMLElement::map_info = { "HTMLMapElement", &KJS::HTMLElement::info, &HTMLMapElementTable, 0 };
const ClassInfo KJS::HTMLElement::area_info = { "HTMLAreaElement", &KJS::HTMLElement::info, &HTMLAreaElementTable, 0 };
const ClassInfo KJS::HTMLElement::script_info = { "HTMLScriptElement", &KJS::HTMLElement::info, &HTMLScriptElementTable, 0 };
const ClassInfo KJS::HTMLElement::table_info = { "HTMLTableElement", &KJS::HTMLElement::info, &HTMLTableElementTable, 0 };
const ClassInfo KJS::HTMLElement::caption_info = { "HTMLTableCaptionElement", &KJS::HTMLElement::info, &HTMLTableCaptionElementTable, 0 };
const ClassInfo KJS::HTMLElement::col_info = { "HTMLTableColElement", &KJS::HTMLElement::info, &HTMLTableColElementTable, 0 };
const ClassInfo KJS::HTMLElement::tablesection_info = { "HTMLTableSectionElement", &KJS::HTMLElement::info, &HTMLTableSectionElementTable, 0 };
const ClassInfo KJS::HTMLElement::tr_info = { "HTMLTableRowElement", &KJS::HTMLElement::info, &HTMLTableRowElementTable, 0 };
const ClassInfo KJS::HTMLElement::tablecell_info = { "HTMLTableCellElement", &KJS::HTMLElement::info, &HTMLTableCellElementTable, 0 };
const ClassInfo KJS::HTMLElement::frameSet_info = { "HTMLFrameSetElement", &KJS::HTMLElement::info, &HTMLFrameSetElementTable, 0 };
const ClassInfo KJS::HTMLElement::frame_info = { "HTMLFrameElement", &KJS::HTMLElement::info, &HTMLFrameElementTable, 0 };
const ClassInfo KJS::HTMLElement::iFrame_info = { "HTMLIFrameElement", &KJS::HTMLElement::info, &HTMLIFrameElementTable, 0 };
const ClassInfo KJS::HTMLElement::marquee_info = { "HTMLMarqueeElement", &KJS::HTMLElement::info, 0, 0 };
const ClassInfo KJS::HTMLElement::layer_info = { "HTMLLayerElement", &KJS::HTMLElement::info, &HTMLLayerElementTable, 0 };
static JSObject* prototypeForID(ExecState* exec, DOM::NodeImpl::Id id);
KJS::HTMLElement::HTMLElement(ExecState *exec, DOM::HTMLElementImpl* e) :
DOMElement(prototypeForID(exec, e->id()), e) { }
const ClassInfo* KJS::HTMLElement::classInfo() const
{
DOM::HTMLElementImpl& element = *impl();
switch (element.id()) {
case ID_HTML:
return &html_info;
case ID_HEAD:
return &head_info;
case ID_LINK:
return &link_info;
case ID_TITLE:
return &title_info;
case ID_META:
return &meta_info;
case ID_BASE:
return &base_info;
case ID_ISINDEX:
return &isIndex_info;
case ID_STYLE:
return &style_info;
case ID_BODY:
return &body_info;
case ID_FORM:
return &form_info;
case ID_SELECT:
return &select_info;
case ID_OPTGROUP:
return &optGroup_info;
case ID_OPTION:
return &option_info;
case ID_INPUT:
return &input_info;
case ID_TEXTAREA:
return &textArea_info;
case ID_BUTTON:
return &button_info;
case ID_LABEL:
return &label_info;
case ID_FIELDSET:
return &fieldSet_info;
case ID_LEGEND:
return &legend_info;
case ID_UL:
return &ul_info;
case ID_OL:
return &ol_info;
case ID_DL:
return &dl_info;
case ID_DIR:
return &dir_info;
case ID_MENU:
return &menu_info;
case ID_LI:
return &li_info;
case ID_DIV:
return &div_info;
case ID_P:
return &p_info;
case ID_H1:
case ID_H2:
case ID_H3:
case ID_H4:
case ID_H5:
case ID_H6:
return &heading_info;
case ID_BLOCKQUOTE:
return &blockQuote_info;
case ID_Q:
return &q_info;
case ID_PRE:
return &pre_info;
case ID_BR:
return &br_info;
case ID_BASEFONT:
return &baseFont_info;
case ID_FONT:
return &font_info;
case ID_HR:
return &hr_info;
case ID_INS:
case ID_DEL:
return &mod_info;
case ID_A:
return &a_info;
case ID_IMG:
return &img_info;
case ID_CANVAS:
return &canvas_info;
case ID_OBJECT:
return &object_info;
case ID_PARAM:
return &param_info;
case ID_APPLET:
return &applet_info;
case ID_MAP:
return &map_info;
case ID_AREA:
return &area_info;
case ID_SCRIPT:
return &script_info;
case ID_TABLE:
return &table_info;
case ID_CAPTION:
return &caption_info;
case ID_COL:
case ID_COLGROUP:
return &col_info;
case ID_THEAD:
case ID_TBODY:
case ID_TFOOT:
return &tablesection_info;
case ID_TR:
return &tr_info;
case ID_TH:
case ID_TD:
return &tablecell_info;
case ID_FRAMESET:
return &frameSet_info;
case ID_FRAME:
return &frame_info;
case ID_IFRAME:
return &iFrame_info;
case ID_MARQUEE:
return &marquee_info;
case ID_LAYER:
return &layer_info;
default:
return &info;
}
}
/*
@begin HTMLElementTable 11
id KJS::HTMLElement::ElementId DontDelete
title KJS::HTMLElement::ElementTitle DontDelete
lang KJS::HTMLElement::ElementLang DontDelete
dir KJS::HTMLElement::ElementDir DontDelete
className KJS::HTMLElement::ElementClassName DontDelete
innerHTML KJS::HTMLElement::ElementInnerHTML DontDelete
innerText KJS::HTMLElement::ElementInnerText DontDelete
document KJS::HTMLElement::ElementDocument DontDelete|ReadOnly
tabIndex KJS::HTMLElement::ElementTabIndex DontDelete
# IE extension
children KJS::HTMLElement::ElementChildren DontDelete|ReadOnly
all KJS::HTMLElement::ElementAll DontDelete|ReadOnly
contentEditable KJS::HTMLElement::ElementContentEditable DontDelete
isContentEditable KJS::HTMLElement::ElementIsContentEditable DontDelete|ReadOnly
@end
@begin HTMLElementProtoTable 1
scrollIntoView KJS::HTMLElement::ElementScrollIntoView DontDelete|Function 0
@end
@begin HTMLHtmlElementTable 1
version KJS::HTMLElement::HtmlVersion DontDelete
@end
@begin HTMLHeadElementTable 1
profile KJS::HTMLElement::HeadProfile DontDelete
@end
@begin HTMLLinkElementTable 11
disabled KJS::HTMLElement::LinkDisabled DontDelete
charset KJS::HTMLElement::LinkCharset DontDelete
href KJS::HTMLElement::LinkHref DontDelete
hreflang KJS::HTMLElement::LinkHrefLang DontDelete
media KJS::HTMLElement::LinkMedia DontDelete
rel KJS::HTMLElement::LinkRel DontDelete
rev KJS::HTMLElement::LinkRev DontDelete
target KJS::HTMLElement::LinkTarget DontDelete
type KJS::HTMLElement::LinkType DontDelete
sheet KJS::HTMLElement::LinkSheet DontDelete|ReadOnly
@end
@begin HTMLTitleElementTable 1
text KJS::HTMLElement::TitleText DontDelete
@end
@begin HTMLMetaElementTable 4
content KJS::HTMLElement::MetaContent DontDelete
httpEquiv KJS::HTMLElement::MetaHttpEquiv DontDelete
name KJS::HTMLElement::MetaName DontDelete
scheme KJS::HTMLElement::MetaScheme DontDelete
@end
@begin HTMLBaseElementTable 2
href KJS::HTMLElement::BaseHref DontDelete
target KJS::HTMLElement::BaseTarget DontDelete
@end
@begin HTMLIsIndexElementTable 2
form KJS::HTMLElement::IsIndexForm DontDelete|ReadOnly
prompt KJS::HTMLElement::IsIndexPrompt DontDelete
@end
@begin HTMLStyleElementTable 4
disabled KJS::HTMLElement::StyleDisabled DontDelete
media KJS::HTMLElement::StyleMedia DontDelete
type KJS::HTMLElement::StyleType DontDelete
sheet KJS::HTMLElement::StyleSheet DontDelete|ReadOnly
@end
@begin HTMLBodyElementTable 8
aLink KJS::HTMLElement::BodyALink DontDelete
background KJS::HTMLElement::BodyBackground DontDelete
bgColor KJS::HTMLElement::BodyBgColor DontDelete
link KJS::HTMLElement::BodyLink DontDelete
text KJS::HTMLElement::BodyText DontDelete
vLink KJS::HTMLElement::BodyVLink DontDelete
# HTML5 specifies these as shadowing normal ones and forwarding to window
onload KJS::HTMLElement::BodyOnLoad DontDelete
onerror KJS::HTMLElement::BodyOnError DontDelete
onfocus KJS::HTMLElement::BodyOnFocus DontDelete
onblur KJS::HTMLElement::BodyOnBlur DontDelete
onmessage KJS::HTMLElement::BodyOnMessage DontDelete
@end
@begin HTMLBodyElementProtoTable 2
# Even though we do blur/focus everywhere, we still handle body.focus()
# specially for now
focus KJS::HTMLElement::BodyFocus DontDelete|Function 0
@end
@begin HTMLFormElementTable 11
# Also supported, by name/index
elements KJS::HTMLElement::FormElements DontDelete|ReadOnly
length KJS::HTMLElement::FormLength DontDelete|ReadOnly
name KJS::HTMLElement::FormName DontDelete
acceptCharset KJS::HTMLElement::FormAcceptCharset DontDelete
action KJS::HTMLElement::FormAction DontDelete
encoding KJS::HTMLElement::FormEncType DontDelete
enctype KJS::HTMLElement::FormEncType DontDelete
method KJS::HTMLElement::FormMethod DontDelete
target KJS::HTMLElement::FormTarget DontDelete
@end
@begin HTMLFormElementProtoTable 2
submit KJS::HTMLElement::FormSubmit DontDelete|Function 0
reset KJS::HTMLElement::FormReset DontDelete|Function 0
@end
@begin HTMLSelectElementTable 11
# Also supported, by index
type KJS::HTMLElement::SelectType DontDelete|ReadOnly
selectedIndex KJS::HTMLElement::SelectSelectedIndex DontDelete
value KJS::HTMLElement::SelectValue DontDelete
length KJS::HTMLElement::SelectLength DontDelete
form KJS::HTMLElement::SelectForm DontDelete|ReadOnly
options KJS::HTMLElement::SelectOptions DontDelete|ReadOnly
disabled KJS::HTMLElement::SelectDisabled DontDelete
multiple KJS::HTMLElement::SelectMultiple DontDelete
name KJS::HTMLElement::SelectName DontDelete
size KJS::HTMLElement::SelectSize DontDelete
@end
@begin HTMLSelectElementProtoTable 4
add KJS::HTMLElement::SelectAdd DontDelete|Function 2
item KJS::HTMLElement::SelectItem DontDelete|Function 1
remove KJS::HTMLElement::SelectRemove DontDelete|Function 1
@end
@begin HTMLOptGroupElementTable 2
disabled KJS::HTMLElement::OptGroupDisabled DontDelete
label KJS::HTMLElement::OptGroupLabel DontDelete
@end
@begin HTMLOptionElementTable 8
form KJS::HTMLElement::OptionForm DontDelete|ReadOnly
defaultSelected KJS::HTMLElement::OptionDefaultSelected DontDelete
text KJS::HTMLElement::OptionText DontDelete
index KJS::HTMLElement::OptionIndex DontDelete|ReadOnly
disabled KJS::HTMLElement::OptionDisabled DontDelete
label KJS::HTMLElement::OptionLabel DontDelete
selected KJS::HTMLElement::OptionSelected DontDelete
value KJS::HTMLElement::OptionValue DontDelete
@end
@begin HTMLInputElementTable 25
defaultValue KJS::HTMLElement::InputDefaultValue DontDelete
defaultChecked KJS::HTMLElement::InputDefaultChecked DontDelete
form KJS::HTMLElement::InputForm DontDelete|ReadOnly
accept KJS::HTMLElement::InputAccept DontDelete
accessKey KJS::HTMLElement::InputAccessKey DontDelete
align KJS::HTMLElement::InputAlign DontDelete
alt KJS::HTMLElement::InputAlt DontDelete
checked KJS::HTMLElement::InputChecked DontDelete
indeterminate KJS::HTMLElement::InputIndeterminate DontDelete
status KJS::HTMLElement::InputChecked DontDelete
disabled KJS::HTMLElement::InputDisabled DontDelete
maxLength KJS::HTMLElement::InputMaxLength DontDelete
name KJS::HTMLElement::InputName DontDelete
readOnly KJS::HTMLElement::InputReadOnly DontDelete
size KJS::HTMLElement::InputSize DontDelete
src KJS::HTMLElement::InputSrc DontDelete
type KJS::HTMLElement::InputType DontDelete
useMap KJS::HTMLElement::InputUseMap DontDelete
value KJS::HTMLElement::InputValue DontDelete
selectionStart KJS::HTMLElement::InputSelectionStart DontDelete
selectionEnd KJS::HTMLElement::InputSelectionEnd DontDelete
placeholder KJS::HTMLElement::InputPlaceholder DontDelete
@end
@begin HTMLInputElementProtoTable 5
select KJS::HTMLElement::InputSelect DontDelete|Function 0
click KJS::HTMLElement::InputClick DontDelete|Function 0
setSelectionRange KJS::HTMLElement::InputSetSelectionRange DontDelete|Function 2
@end
@begin HTMLTextAreaElementTable 13
defaultValue KJS::HTMLElement::TextAreaDefaultValue DontDelete
form KJS::HTMLElement::TextAreaForm DontDelete|ReadOnly
accessKey KJS::HTMLElement::TextAreaAccessKey DontDelete
cols KJS::HTMLElement::TextAreaCols DontDelete
disabled KJS::HTMLElement::TextAreaDisabled DontDelete
name KJS::HTMLElement::TextAreaName DontDelete
readOnly KJS::HTMLElement::TextAreaReadOnly DontDelete
rows KJS::HTMLElement::TextAreaRows DontDelete
type KJS::HTMLElement::TextAreaType DontDelete|ReadOnly
value KJS::HTMLElement::TextAreaValue DontDelete
selectionStart KJS::HTMLElement::TextAreaSelectionStart DontDelete
selectionEnd KJS::HTMLElement::TextAreaSelectionEnd DontDelete
textLength KJS::HTMLElement::TextAreaTextLength DontDelete|ReadOnly
placeholder KJS::HTMLElement::TextAreaPlaceholder DontDelete
@end
@begin HTMLTextAreaElementProtoTable 4
select KJS::HTMLElement::TextAreaSelect DontDelete|Function 0
setSelectionRange KJS::HTMLElement::TextAreaSetSelectionRange DontDelete|Function 2
@end
@begin HTMLButtonElementTable 9
form KJS::HTMLElement::ButtonForm DontDelete|ReadOnly
accessKey KJS::HTMLElement::ButtonAccessKey DontDelete
disabled KJS::HTMLElement::ButtonDisabled DontDelete
name KJS::HTMLElement::ButtonName DontDelete
type KJS::HTMLElement::ButtonType DontDelete|ReadOnly
value KJS::HTMLElement::ButtonValue DontDelete
@end
@begin HTMLButtonElementProtoTable 3
click KJS::HTMLElement::ButtonClick DontDelete|Function 0
@end
@begin HTMLLabelElementTable 3
form KJS::HTMLElement::LabelForm DontDelete|ReadOnly
accessKey KJS::HTMLElement::LabelAccessKey DontDelete
htmlFor KJS::HTMLElement::LabelHtmlFor DontDelete
@end
@begin HTMLFieldSetElementTable 1
form KJS::HTMLElement::FieldSetForm DontDelete|ReadOnly
@end
@begin HTMLLegendElementTable 3
form KJS::HTMLElement::LegendForm DontDelete|ReadOnly
accessKey KJS::HTMLElement::LegendAccessKey DontDelete
align KJS::HTMLElement::LegendAlign DontDelete
@end
@begin HTMLUListElementTable 2
compact KJS::HTMLElement::UListCompact DontDelete
type KJS::HTMLElement::UListType DontDelete
@end
@begin HTMLOListElementTable 3
compact KJS::HTMLElement::OListCompact DontDelete
start KJS::HTMLElement::OListStart DontDelete
type KJS::HTMLElement::OListType DontDelete
@end
@begin HTMLDListElementTable 1
compact KJS::HTMLElement::DListCompact DontDelete
@end
@begin HTMLDirectoryElementTable 1
compact KJS::HTMLElement::DirectoryCompact DontDelete
@end
@begin HTMLMenuElementTable 1
compact KJS::HTMLElement::MenuCompact DontDelete
@end
@begin HTMLLIElementTable 2
type KJS::HTMLElement::LIType DontDelete
value KJS::HTMLElement::LIValue DontDelete
@end
@begin HTMLDivElementTable 1
align KJS::HTMLElement::DivAlign DontDelete
@end
@begin HTMLParagraphElementTable 1
align KJS::HTMLElement::ParagraphAlign DontDelete
@end
@begin HTMLHeadingElementTable 1
align KJS::HTMLElement::HeadingAlign DontDelete
@end
@begin HTMLBlockQuoteElementTable 1
cite KJS::HTMLElement::BlockQuoteCite DontDelete
@end
@begin HTMLQuoteElementTable 1
cite KJS::HTMLElement::QuoteCite DontDelete
@end
@begin HTMLPreElementTable 1
width KJS::HTMLElement::PreWidth DontDelete
@end
@begin HTMLBRElementTable 1
clear KJS::HTMLElement::BRClear DontDelete
@end
@begin HTMLBaseFontElementTable 3
color KJS::HTMLElement::BaseFontColor DontDelete
face KJS::HTMLElement::BaseFontFace DontDelete
size KJS::HTMLElement::BaseFontSize DontDelete
@end
@begin HTMLFontElementTable 3
color KJS::HTMLElement::FontColor DontDelete
face KJS::HTMLElement::FontFace DontDelete
size KJS::HTMLElement::FontSize DontDelete
@end
@begin HTMLHRElementTable 4
align KJS::HTMLElement::HRAlign DontDelete
noShade KJS::HTMLElement::HRNoShade DontDelete
size KJS::HTMLElement::HRSize DontDelete
width KJS::HTMLElement::HRWidth DontDelete
@end
@begin HTMLModElementTable 2
cite KJS::HTMLElement::ModCite DontDelete
dateTime KJS::HTMLElement::ModDateTime DontDelete
@end
@begin HTMLAnchorElementTable 23
accessKey KJS::HTMLElement::AnchorAccessKey DontDelete
charset KJS::HTMLElement::AnchorCharset DontDelete
coords KJS::HTMLElement::AnchorCoords DontDelete
href KJS::HTMLElement::AnchorHref DontDelete
hreflang KJS::HTMLElement::AnchorHrefLang DontDelete
hash KJS::HTMLElement::AnchorHash DontDelete|ReadOnly
host KJS::HTMLElement::AnchorHost DontDelete|ReadOnly
hostname KJS::HTMLElement::AnchorHostname DontDelete|ReadOnly
name KJS::HTMLElement::AnchorName DontDelete
pathname KJS::HTMLElement::AnchorPathName DontDelete|ReadOnly
port KJS::HTMLElement::AnchorPort DontDelete|ReadOnly
protocol KJS::HTMLElement::AnchorProtocol DontDelete|ReadOnly
rel KJS::HTMLElement::AnchorRel DontDelete
rev KJS::HTMLElement::AnchorRev DontDelete
search KJS::HTMLElement::AnchorSearch DontDelete
shape KJS::HTMLElement::AnchorShape DontDelete
target KJS::HTMLElement::AnchorTarget DontDelete
text KJS::HTMLElement::AnchorText DontDelete|ReadOnly
type KJS::HTMLElement::AnchorType DontDelete
@end
@begin HTMLAnchorElementProtoTable 3
click KJS::HTMLElement::AnchorClick DontDelete|Function 0
toString KJS::HTMLElement::AnchorToString DontDelete|Function 0
@end
@begin HTMLImageElementTable 15
name KJS::HTMLElement::ImageName DontDelete
align KJS::HTMLElement::ImageAlign DontDelete
alt KJS::HTMLElement::ImageAlt DontDelete
border KJS::HTMLElement::ImageBorder DontDelete
complete KJS::HTMLElement::ImageComplete DontDelete|ReadOnly
height KJS::HTMLElement::ImageHeight DontDelete
hspace KJS::HTMLElement::ImageHspace DontDelete
isMap KJS::HTMLElement::ImageIsMap DontDelete
longDesc KJS::HTMLElement::ImageLongDesc DontDelete
src KJS::HTMLElement::ImageSrc DontDelete
useMap KJS::HTMLElement::ImageUseMap DontDelete
vspace KJS::HTMLElement::ImageVspace DontDelete
width KJS::HTMLElement::ImageWidth DontDelete
x KJS::HTMLElement::ImageX DontDelete|ReadOnly
y KJS::HTMLElement::ImageY DontDelete|ReadOnly
@end
@begin HTMLObjectElementTable 23
form KJS::HTMLElement::ObjectForm DontDelete|ReadOnly
code KJS::HTMLElement::ObjectCode DontDelete
align KJS::HTMLElement::ObjectAlign DontDelete
archive KJS::HTMLElement::ObjectArchive DontDelete
border KJS::HTMLElement::ObjectBorder DontDelete
codeBase KJS::HTMLElement::ObjectCodeBase DontDelete
codeType KJS::HTMLElement::ObjectCodeType DontDelete
contentDocument KJS::HTMLElement::ObjectContentDocument DontDelete|ReadOnly
data KJS::HTMLElement::ObjectData DontDelete
declare KJS::HTMLElement::ObjectDeclare DontDelete
height KJS::HTMLElement::ObjectHeight DontDelete
hspace KJS::HTMLElement::ObjectHspace DontDelete
name KJS::HTMLElement::ObjectName DontDelete
standby KJS::HTMLElement::ObjectStandby DontDelete
type KJS::HTMLElement::ObjectType DontDelete
useMap KJS::HTMLElement::ObjectUseMap DontDelete
vspace KJS::HTMLElement::ObjectVspace DontDelete
width KJS::HTMLElement::ObjectWidth DontDelete
@end
@begin HTMLObjectElementProtoTable 1
# half deprecated - cf. http://lists.w3.org/Archives/Public/www-svg/2008Feb/0031.html
# only implemented in ecma, because of acid3 dependency
getSVGDocument KJS::HTMLElement::ObjectGetSVGDocument DontDelete|Function 0
@end
@begin HTMLParamElementTable 4
name KJS::HTMLElement::ParamName DontDelete
type KJS::HTMLElement::ParamType DontDelete
value KJS::HTMLElement::ParamValue DontDelete
valueType KJS::HTMLElement::ParamValueType DontDelete
@end
@begin HTMLAppletElementTable 11
align KJS::HTMLElement::AppletAlign DontDelete
alt KJS::HTMLElement::AppletAlt DontDelete
archive KJS::HTMLElement::AppletArchive DontDelete
code KJS::HTMLElement::AppletCode DontDelete
codeBase KJS::HTMLElement::AppletCodeBase DontDelete
height KJS::HTMLElement::AppletHeight DontDelete
hspace KJS::HTMLElement::AppletHspace DontDelete
name KJS::HTMLElement::AppletName DontDelete
object KJS::HTMLElement::AppletObject DontDelete
vspace KJS::HTMLElement::AppletVspace DontDelete
width KJS::HTMLElement::AppletWidth DontDelete
@end
@begin HTMLMapElementTable 2
areas KJS::HTMLElement::MapAreas DontDelete|ReadOnly
name KJS::HTMLElement::MapName DontDelete
@end
@begin HTMLAreaElementTable 15
accessKey KJS::HTMLElement::AreaAccessKey DontDelete
alt KJS::HTMLElement::AreaAlt DontDelete
coords KJS::HTMLElement::AreaCoords DontDelete
href KJS::HTMLElement::AreaHref DontDelete
hash KJS::HTMLElement::AreaHash DontDelete|ReadOnly
host KJS::HTMLElement::AreaHost DontDelete|ReadOnly
hostname KJS::HTMLElement::AreaHostName DontDelete|ReadOnly
pathname KJS::HTMLElement::AreaPathName DontDelete|ReadOnly
port KJS::HTMLElement::AreaPort DontDelete|ReadOnly
protocol KJS::HTMLElement::AreaProtocol DontDelete|ReadOnly
search KJS::HTMLElement::AreaSearch DontDelete|ReadOnly
noHref KJS::HTMLElement::AreaNoHref DontDelete
shape KJS::HTMLElement::AreaShape DontDelete
target KJS::HTMLElement::AreaTarget DontDelete
@end
@begin HTMLScriptElementTable 7
text KJS::HTMLElement::ScriptText DontDelete
htmlFor KJS::HTMLElement::ScriptHtmlFor DontDelete
event KJS::HTMLElement::ScriptEvent DontDelete
charset KJS::HTMLElement::ScriptCharset DontDelete
defer KJS::HTMLElement::ScriptDefer DontDelete
src KJS::HTMLElement::ScriptSrc DontDelete
type KJS::HTMLElement::ScriptType DontDelete
@end
@begin HTMLTableElementTable 23
caption KJS::HTMLElement::TableCaption DontDelete
tHead KJS::HTMLElement::TableTHead DontDelete
tFoot KJS::HTMLElement::TableTFoot DontDelete
rows KJS::HTMLElement::TableRows DontDelete|ReadOnly
tBodies KJS::HTMLElement::TableTBodies DontDelete|ReadOnly
align KJS::HTMLElement::TableAlign DontDelete
bgColor KJS::HTMLElement::TableBgColor DontDelete
border KJS::HTMLElement::TableBorder DontDelete
cellPadding KJS::HTMLElement::TableCellPadding DontDelete
cellSpacing KJS::HTMLElement::TableCellSpacing DontDelete
frame KJS::HTMLElement::TableFrame DontDelete
rules KJS::HTMLElement::TableRules DontDelete
summary KJS::HTMLElement::TableSummary DontDelete
width KJS::HTMLElement::TableWidth DontDelete
@end
@begin HTMLTableElementProtoTable 8
createTHead KJS::HTMLElement::TableCreateTHead DontDelete|Function 0
deleteTHead KJS::HTMLElement::TableDeleteTHead DontDelete|Function 0
createTFoot KJS::HTMLElement::TableCreateTFoot DontDelete|Function 0
deleteTFoot KJS::HTMLElement::TableDeleteTFoot DontDelete|Function 0
createCaption KJS::HTMLElement::TableCreateCaption DontDelete|Function 0
deleteCaption KJS::HTMLElement::TableDeleteCaption DontDelete|Function 0
insertRow KJS::HTMLElement::TableInsertRow DontDelete|Function 1
deleteRow KJS::HTMLElement::TableDeleteRow DontDelete|Function 1
@end
@begin HTMLTableCaptionElementTable 1
align KJS::HTMLElement::TableCaptionAlign DontDelete
@end
@begin HTMLTableColElementTable 7
align KJS::HTMLElement::TableColAlign DontDelete
ch KJS::HTMLElement::TableColCh DontDelete
chOff KJS::HTMLElement::TableColChOff DontDelete
span KJS::HTMLElement::TableColSpan DontDelete
vAlign KJS::HTMLElement::TableColVAlign DontDelete
width KJS::HTMLElement::TableColWidth DontDelete
@end
@begin HTMLTableSectionElementTable 7
align KJS::HTMLElement::TableSectionAlign DontDelete
ch KJS::HTMLElement::TableSectionCh DontDelete
chOff KJS::HTMLElement::TableSectionChOff DontDelete
vAlign KJS::HTMLElement::TableSectionVAlign DontDelete
rows KJS::HTMLElement::TableSectionRows DontDelete|ReadOnly
@end
@begin HTMLTableSectionElementProtoTable 2
insertRow KJS::HTMLElement::TableSectionInsertRow DontDelete|Function 1
deleteRow KJS::HTMLElement::TableSectionDeleteRow DontDelete|Function 1
@end
@begin HTMLTableRowElementTable 11
rowIndex KJS::HTMLElement::TableRowRowIndex DontDelete|ReadOnly
sectionRowIndex KJS::HTMLElement::TableRowSectionRowIndex DontDelete|ReadOnly
cells KJS::HTMLElement::TableRowCells DontDelete|ReadOnly
align KJS::HTMLElement::TableRowAlign DontDelete
bgColor KJS::HTMLElement::TableRowBgColor DontDelete
ch KJS::HTMLElement::TableRowCh DontDelete
chOff KJS::HTMLElement::TableRowChOff DontDelete
vAlign KJS::HTMLElement::TableRowVAlign DontDelete
@end
@begin HTMLTableRowElementProtoTable 2
insertCell KJS::HTMLElement::TableRowInsertCell DontDelete|Function 1
deleteCell KJS::HTMLElement::TableRowDeleteCell DontDelete|Function 1
@end
@begin HTMLTableCellElementTable 15
cellIndex KJS::HTMLElement::TableCellCellIndex DontDelete|ReadOnly
abbr KJS::HTMLElement::TableCellAbbr DontDelete
align KJS::HTMLElement::TableCellAlign DontDelete
axis KJS::HTMLElement::TableCellAxis DontDelete
bgColor KJS::HTMLElement::TableCellBgColor DontDelete
ch KJS::HTMLElement::TableCellCh DontDelete
chOff KJS::HTMLElement::TableCellChOff DontDelete
colSpan KJS::HTMLElement::TableCellColSpan DontDelete
headers KJS::HTMLElement::TableCellHeaders DontDelete
height KJS::HTMLElement::TableCellHeight DontDelete
noWrap KJS::HTMLElement::TableCellNoWrap DontDelete
rowSpan KJS::HTMLElement::TableCellRowSpan DontDelete
scope KJS::HTMLElement::TableCellScope DontDelete
vAlign KJS::HTMLElement::TableCellVAlign DontDelete
width KJS::HTMLElement::TableCellWidth DontDelete
@end
@begin HTMLFrameSetElementTable 2
cols KJS::HTMLElement::FrameSetCols DontDelete
rows KJS::HTMLElement::FrameSetRows DontDelete
onmessage KJS::HTMLElement::FrameSetOnMessage DontDelete
@end
@begin HTMLLayerElementTable 6
top KJS::HTMLElement::LayerTop DontDelete
left KJS::HTMLElement::LayerLeft DontDelete
visibility KJS::HTMLElement::LayerVisibility DontDelete
bgColor KJS::HTMLElement::LayerBgColor DontDelete
document KJS::HTMLElement::LayerDocument DontDelete|ReadOnly
clip KJS::HTMLElement::LayerClip DontDelete|ReadOnly
layers KJS::HTMLElement::LayerLayers DontDelete|ReadOnly
@end
@begin HTMLFrameElementTable 13
contentDocument KJS::HTMLElement::FrameContentDocument DontDelete|ReadOnly
contentWindow KJS::HTMLElement::FrameContentWindow DontDelete|ReadOnly
frameBorder KJS::HTMLElement::FrameFrameBorder DontDelete
longDesc KJS::HTMLElement::FrameLongDesc DontDelete
marginHeight KJS::HTMLElement::FrameMarginHeight DontDelete
marginWidth KJS::HTMLElement::FrameMarginWidth DontDelete
name KJS::HTMLElement::FrameName DontDelete
noResize KJS::HTMLElement::FrameNoResize DontDelete
scrolling KJS::HTMLElement::FrameScrolling DontDelete
src KJS::HTMLElement::FrameSrc DontDelete
location KJS::HTMLElement::FrameLocation DontDelete
# IE extension
width KJS::HTMLElement::FrameWidth DontDelete|ReadOnly
height KJS::HTMLElement::FrameHeight DontDelete|ReadOnly
@end
@begin HTMLIFrameElementTable 13
align KJS::HTMLElement::IFrameAlign DontDelete
contentDocument KJS::HTMLElement::IFrameContentDocument DontDelete|ReadOnly
contentWindow KJS::HTMLElement::IFrameContentWindow DontDelete|ReadOnly
frameBorder KJS::HTMLElement::IFrameFrameBorder DontDelete
height KJS::HTMLElement::IFrameHeight DontDelete
longDesc KJS::HTMLElement::IFrameLongDesc DontDelete
marginHeight KJS::HTMLElement::IFrameMarginHeight DontDelete
marginWidth KJS::HTMLElement::IFrameMarginWidth DontDelete
name KJS::HTMLElement::IFrameName DontDelete
scrolling KJS::HTMLElement::IFrameScrolling DontDelete
src KJS::HTMLElement::IFrameSrc DontDelete
width KJS::HTMLElement::IFrameWidth DontDelete
@end
@begin HTMLIFrameElementProtoTable 1
# half deprecated - cf. http://lists.w3.org/Archives/Public/www-svg/2008Feb/0031.html
# only implemented in ecma, because of acid3 dependency
getSVGDocument KJS::HTMLElement::IFrameGetSVGDocument DontDelete|Function 0
@end
@begin HTMLMarqueeElementProtoTable 2
start KJS::HTMLElement::MarqueeStart DontDelete|Function 0
stop KJS::HTMLElement::MarqueeStop DontDelete|Function 0
@end
@begin HTMLCanvasElementTable 2
width KJS::HTMLElement::CanvasWidth DontDelete
height KJS::HTMLElement::CanvasHeight DontDelete
@end
@begin HTMLCanvasElementProtoTable 1
getContext KJS::HTMLElement::CanvasGetContext DontDelete|Function 1
toDataURL KJS::HTMLElement::CanvasToDataURL DontDelete|Function 0
@end
*/
KJS_IMPLEMENT_PROTOFUNC(HTMLElementFunction)
KParts::ScriptableExtension *HTMLElement::getScriptableExtension(const DOM::HTMLElementImpl &element)
{
DOM::DocumentImpl* doc = element.document();
if (doc->part())
return doc->part()->scriptableExtension(&element);
return 0L;
}
JSValue *HTMLElement::formNameGetter(ExecState *exec, JSObject*, const Identifier& propertyName, const PropertySlot& slot)
{
HTMLElement* thisObj = static_cast<HTMLElement*>(slot.slotBase());
HTMLFormElementImpl* form = static_cast<HTMLFormElementImpl*>(thisObj->impl());
KJS::HTMLCollection coll(exec, form->elements());
JSValue* result = coll.getNamedItems(exec, propertyName);
// In case of simple result, remember in past names map
// ### our HTMLFormElementsCollection is a bit too IE-compatey rather than HTML5ey
if (DOM::NodeImpl* node = toNode(result)) {
if (node->isGenericFormElement())
form->bindPastName(static_cast<HTMLGenericFormElementImpl*>(node));
}
return result;
}
//JSValue* KJS::HTMLElement::tryGet(ExecState *exec, const Identifier &propertyName) const
bool KJS::HTMLElement::getOwnPropertySlot(ExecState *exec, const Identifier &propertyName, PropertySlot& slot)
{
DOM::HTMLElementImpl& element = *impl();
#ifdef KJS_VERBOSE
kDebug(6070) << "KJS::HTMLElement::getOwnPropertySlot " << propertyName.qstring() << " thisTag=" << element.tagName().string();
#endif
// First look at dynamic properties
switch (element.id()) {
case ID_FORM: {
DOM::HTMLFormElementImpl& form = static_cast<DOM::HTMLFormElementImpl&>(element);
// Check if we're retrieving an element (by index or by name)
if (getIndexSlot(this, propertyName, slot))
return true;
KJS::HTMLCollection coll(exec, form.elements());
JSValue *namedItems = coll.getNamedItems(exec, propertyName);
if (namedItems->type() != UndefinedType) {
slot.setCustom(this, formNameGetter);
return true;
}
// Try with past names map
if (HTMLGenericFormElementImpl* r = form.lookupByPastName(propertyName.domString()))
return getImmediateValueSlot(this, getDOMNode(exec, r), slot);
break;
}
case ID_SELECT:
if (getIndexSlot(this, propertyName, slot))
return true;
break;
case ID_APPLET:
case ID_OBJECT:
case ID_EMBED: {
KParts::ScriptableExtension* se = getScriptableExtension(*impl());
if (pluginRootGet(exec, se, propertyName, slot))
return true;
break;
}
}
const HashTable* table = classInfo()->propHashTable; // get the right hashtable
if (table && getStaticOwnPropertySlot<HTMLElementFunction, HTMLElement>(table, this, propertyName, slot))
return true;
// Base HTMLElement stuff or parent class forward, as usual
return getStaticPropertySlot<KJS::HTMLElementFunction, KJS::HTMLElement, DOMElement>(
exec, &KJS::HTMLElementTable, this, propertyName, slot);
}
JSValue* HTMLElement::indexGetter(ExecState *exec, unsigned index)
{
switch (impl()->id())
{
case ID_FORM: {
DOM::HTMLFormElementImpl* form = static_cast<DOM::HTMLFormElementImpl*>(impl());
SharedPtr<DOM::HTMLCollectionImpl> elems = form->elements();
return getDOMNode(exec, elems->item(index));
}
case ID_SELECT: {
DOM::HTMLSelectElementImpl* select = static_cast<DOM::HTMLSelectElementImpl*>(impl());
SharedPtr<DOM::HTMLCollectionImpl> opts = select->options();
return getDOMNode(exec, opts->item(index)); // not specified by DOM(?) but supported in netscape/IE
}
default:
assert(0);
return jsUndefined();
}
}
#if 0
// First look at dynamic properties
switch (element.id()) {
case ID_FORM: {
DOM::HTMLFormElementImpl& form = static_cast<DOM::HTMLFormElementImpl&>(element);
// Check if we're retrieving an element (by index or by name)
KJS::HTMLCollection coll(exec, form.elements());
JSValue *namedItems = coll.getNamedItems(exec, propertyName);
if (namedItems->type() != UndefinedType)
return namedItems;
}
}
#endif
/**
Table of how to connect JS tokens to attributes
*/
const KJS::HTMLElement::BoundPropInfo KJS::HTMLElement::bpTable[] = {
{ID_HTML, HtmlVersion, T_String, ATTR_VERSION},
{ID_HEAD, HeadProfile, T_String, ATTR_PROFILE},
{ID_LINK, LinkDisabled, T_Bool, ATTR_DISABLED},
{ID_LINK, LinkCharset, T_String, ATTR_CHARSET},
{ID_LINK, LinkHref, T_URL, ATTR_HREF},
{ID_LINK, LinkHrefLang, T_String, ATTR_HREFLANG},
{ID_LINK, LinkMedia, T_String, ATTR_MEDIA},
{ID_LINK, LinkRel, T_String, ATTR_REL},
{ID_LINK, LinkRev, T_String, ATTR_REV},
{ID_LINK, LinkTarget, T_String, ATTR_TARGET},
{ID_LINK, LinkType, T_String, ATTR_TYPE},
{ID_BASE, BaseHref, T_URL, ATTR_HREF},
{ID_BASE, BaseTarget, T_String, ATTR_TARGET},
{ID_META, MetaContent, T_String, ATTR_CONTENT},
{ID_META, MetaHttpEquiv,T_String, ATTR_HTTP_EQUIV},
{ID_META, MetaName, T_String, ATTR_NAME},
{ID_META, MetaScheme, T_String, ATTR_SCHEME},
{ID_STYLE, StyleDisabled, T_Bool, ATTR_DISABLED},
{ID_STYLE, StyleMedia, T_String, ATTR_MEDIA},
{ID_STYLE, StyleType, T_String, ATTR_TYPE},
{ID_BODY, BodyALink, T_String, ATTR_ALINK},
{ID_BODY, BodyBackground, T_String, ATTR_BACKGROUND},
{ID_BODY, BodyBgColor, T_String, ATTR_BGCOLOR},
{ID_BODY, BodyLink, T_String, ATTR_LINK},
{ID_BODY, BodyText, T_String, ATTR_TEXT},//### odd?
{ID_BODY, BodyVLink, T_String, ATTR_VLINK},
{ID_FORM, FormName, T_String, ATTR_NAME}, // NOT getString (IE gives empty string)
{ID_FORM, FormAcceptCharset, T_String, ATTR_ACCEPT_CHARSET},
{ID_FORM, FormAction, T_String, ATTR_ACTION},
{ID_FORM, FormEncType, T_String, ATTR_ENCTYPE},
{ID_FORM, FormMethod, T_String, ATTR_METHOD},
{ID_FORM, FormTarget, T_String, ATTR_TARGET},
{ID_SELECT, SelectDisabled, T_Bool, ATTR_DISABLED},
{ID_SELECT, SelectMultiple, T_Bool, ATTR_MULTIPLE},
{ID_SELECT, SelectSize, T_Int, ATTR_SIZE}, //toInt on attr, then number
{ID_OPTGROUP, OptGroupDisabled, T_Bool, ATTR_DISABLED},
{ID_OPTGROUP, OptGroupLabel, T_String, ATTR_LABEL},
{ID_OPTION, OptionDefaultSelected, T_Bool, ATTR_SELECTED},
{ID_OPTION, OptionDisabled, T_Bool, ATTR_DISABLED},
{ID_OPTION, OptionLabel, T_String, ATTR_LABEL},
{ID_INPUT, InputDefaultValue, T_String, ATTR_VALUE},
{ID_INPUT, InputDefaultChecked, T_Bool, ATTR_CHECKED},
{ID_INPUT, InputAccept, T_String, ATTR_ACCEPT},
{ID_INPUT, InputAccessKey, T_String, ATTR_ACCESSKEY},
{ID_INPUT, InputAlign, T_String, ATTR_ALIGN},
{ID_INPUT, InputAlt, T_String, ATTR_ALT},
{ID_INPUT, InputDisabled, T_Bool, ATTR_DISABLED},
{ID_INPUT, InputMaxLength, T_Int, ATTR_MAXLENGTH},
{ID_INPUT, InputReadOnly, T_Bool, ATTR_READONLY},
{ID_INPUT, InputSize, T_Int, ATTR_SIZE},
{ID_INPUT, InputSrc, T_URL, ATTR_SRC},
{ID_INPUT, InputUseMap, T_String, ATTR_USEMAP},
{ID_TEXTAREA, TextAreaAccessKey, T_String, ATTR_ACCESSKEY},
{ID_TEXTAREA, TextAreaCols, T_Int, ATTR_COLS},
{ID_TEXTAREA, TextAreaDisabled, T_Bool, ATTR_DISABLED},
{ID_TEXTAREA, TextAreaReadOnly, T_Bool, ATTR_READONLY},
{ID_TEXTAREA, TextAreaRows, T_Int, ATTR_ROWS},
{ID_BUTTON, ButtonAccessKey, T_String, ATTR_ACCESSKEY},
{ID_BUTTON, ButtonDisabled, T_Bool , ATTR_DISABLED},
{ID_BUTTON, ButtonName, T_String, ATTR_NAME},
{ID_BUTTON, ButtonValue, T_String, ATTR_VALUE},
{ID_LABEL, LabelAccessKey, T_String, ATTR_ACCESSKEY},
{ID_LABEL, LabelHtmlFor, T_String, ATTR_FOR},
{ID_LEGEND, LegendAccessKey, T_String, ATTR_ACCESSKEY},
{ID_LEGEND, LegendAlign, T_String, ATTR_ALIGN},
{ID_UL, UListCompact, T_Bool, ATTR_COMPACT},
{ID_UL, UListType, T_String, ATTR_TYPE},
{ID_OL, OListCompact, T_Bool, ATTR_COMPACT},
{ID_OL, OListStart, T_Int, ATTR_START},
{ID_OL, OListType, T_String, ATTR_TYPE},
{ID_DL, DListCompact, T_Bool, ATTR_COMPACT},
{ID_DIR, DirectoryCompact, T_Bool, ATTR_COMPACT},
{ID_MENU, MenuCompact, T_Bool, ATTR_COMPACT},
{ID_LI, LIType, T_String, ATTR_TYPE},
{ID_LI, LIValue, T_Int, ATTR_VALUE},
{ID_DIV, DivAlign, T_String, ATTR_ALIGN},
{ID_P, ParagraphAlign, T_String, ATTR_ALIGN},
{NotApplicable,HeadingAlign, T_String, ATTR_ALIGN},
{ID_BLOCKQUOTE, BlockQuoteCite, T_String, ATTR_CITE},
{ID_Q, QuoteCite, T_String, ATTR_CITE},
{ID_PRE, PreWidth, T_Int, ATTR_WIDTH},
{ID_BR, BRClear, T_String, ATTR_CLEAR},
{ID_BASEFONT, BaseFontColor, T_String, ATTR_COLOR},
{ID_BASEFONT, BaseFontFace, T_String, ATTR_FACE},
{ID_BASEFONT, BaseFontSize, T_Int, ATTR_SIZE},
{ID_FONT, FontColor, T_String, ATTR_COLOR},
{ID_FONT, FontFace, T_String, ATTR_FACE},
{ID_FONT, FontSize, T_String, ATTR_SIZE},
{ID_HR, HRAlign, T_String, ATTR_ALIGN},
{ID_HR, HRNoShade, T_Bool, ATTR_NOSHADE},
{ID_HR, HRSize, T_String, ATTR_SIZE},
{ID_HR, HRWidth, T_String, ATTR_WIDTH},
{NotApplicable, ModCite, T_String, ATTR_CITE},
{NotApplicable, ModDateTime, T_String, ATTR_DATETIME},
{ID_A, AnchorAccessKey, T_String, ATTR_ACCESSKEY},
{ID_A, AnchorCharset, T_String, ATTR_CHARSET},
{ID_A, AnchorCoords, T_String, ATTR_COORDS},
{ID_A, AnchorHref, T_URL, ATTR_HREF},
{ID_A, AnchorHrefLang, T_String, ATTR_HREFLANG},
{ID_A, AnchorName, T_String, ATTR_NAME},
{ID_A, AnchorRel, T_String, ATTR_REL},
{ID_A, AnchorRev, T_String, ATTR_REV},
{ID_A, AnchorShape, T_String, ATTR_SHAPE},
{ID_A, AnchorTarget, T_String, ATTR_TARGET},
{ID_A, AnchorType, T_String, ATTR_TYPE},
{ID_IMG, ImageName, T_String, ATTR_NAME},
{ID_IMG, ImageAlign, T_String, ATTR_ALIGN},
{ID_IMG, ImageAlt, T_String, ATTR_ALT},
{ID_IMG, ImageBorder, T_String, ATTR_BORDER},
{ID_IMG, ImageHspace, T_Int, ATTR_HSPACE}, // ### return actual value
{ID_IMG, ImageIsMap, T_Bool, ATTR_ISMAP},
{ID_IMG, ImageLongDesc, T_String, ATTR_LONGDESC},
{ID_IMG, ImageSrc, T_URL, ATTR_SRC},
{ID_IMG, ImageUseMap, T_String, ATTR_USEMAP},
{ID_IMG, ImageVspace, T_Int, ATTR_VSPACE}, // ### return actual value
{ID_OBJECT, ObjectCode, T_String, ATTR_CODE},
{ID_OBJECT, ObjectAlign, T_String, ATTR_ALIGN},
{ID_OBJECT, ObjectArchive, T_String, ATTR_ARCHIVE},
{ID_OBJECT, ObjectBorder, T_String, ATTR_BORDER},
{ID_OBJECT, ObjectCodeBase, T_String, ATTR_CODEBASE},
{ID_OBJECT, ObjectCodeType, T_String, ATTR_CODETYPE},
{ID_OBJECT, ObjectData, T_URL, ATTR_DATA},
{ID_OBJECT, ObjectDeclare, T_Bool, ATTR_DECLARE},
{ID_OBJECT, ObjectHeight, T_String, ATTR_HEIGHT},
{ID_OBJECT, ObjectHspace, T_Int, ATTR_HSPACE},
{ID_OBJECT, ObjectName, T_String, ATTR_NAME},
{ID_OBJECT, ObjectStandby, T_String, ATTR_STANDBY},
{ID_OBJECT, ObjectType, T_String, ATTR_TYPE},
{ID_OBJECT, ObjectUseMap, T_String, ATTR_USEMAP},
{ID_OBJECT, ObjectVspace, T_Int, ATTR_VSPACE},
{ID_OBJECT, ObjectWidth, T_String, ATTR_WIDTH},
{ID_PARAM, ParamName, T_String, ATTR_NAME},
{ID_PARAM, ParamType, T_String, ATTR_TYPE},
{ID_PARAM, ParamValue, T_String, ATTR_VALUE},
{ID_PARAM, ParamValueType, T_String, ATTR_VALUETYPE},
{ID_APPLET, AppletAlign, T_String, ATTR_ALIGN},
{ID_APPLET, AppletAlt, T_String, ATTR_ALT},
{ID_APPLET, AppletArchive, T_String, ATTR_ARCHIVE},
{ID_APPLET, AppletCode, T_String, ATTR_CODE},
{ID_APPLET, AppletCodeBase, T_String, ATTR_CODEBASE},
{ID_APPLET, AppletHeight, T_String, ATTR_HEIGHT},
{ID_APPLET, AppletHspace, T_Int, ATTR_HSPACE},
{ID_APPLET, AppletName, T_String, ATTR_NAME},
{ID_APPLET, AppletObject, T_String, ATTR_OBJECT},
{ID_APPLET, AppletVspace, T_Int, ATTR_VSPACE},
{ID_APPLET, AppletWidth, T_String, ATTR_WIDTH},
{ID_MAP, MapName, T_String, ATTR_NAME},
{ID_MAP, MapAreas, T_Coll, HTMLCollectionImpl::MAP_AREAS},
{ID_AREA, AreaAccessKey, T_String, ATTR_ACCESSKEY},
{ID_AREA, AreaAlt, T_String, ATTR_ALT},
{ID_AREA, AreaCoords, T_String, ATTR_COORDS},
{ID_AREA, AreaHref, T_URL, ATTR_HREF},
{ID_AREA, AreaNoHref, T_Bool, ATTR_NOHREF},
{ID_AREA, AreaShape, T_String, ATTR_SHAPE},
{ID_AREA, AreaTarget, T_String, ATTR_TARGET},
{ID_SCRIPT, ScriptHtmlFor, T_Res, NotApplicable},
{ID_SCRIPT, ScriptEvent, T_Res, NotApplicable},
{ID_SCRIPT, ScriptCharset, T_String, ATTR_CHARSET},
{ID_SCRIPT, ScriptDefer, T_Bool, ATTR_DEFER},
{ID_SCRIPT, ScriptSrc, T_URL, ATTR_SRC},
{ID_SCRIPT, ScriptType, T_String, ATTR_TYPE},
{ID_TABLE, TableAlign, T_String, ATTR_ALIGN},
{ID_TABLE, TableBgColor, T_String, ATTR_BGCOLOR},
{ID_TABLE, TableBorder, T_String, ATTR_BORDER},
{ID_TABLE, TableCellPadding, T_String, ATTR_CELLPADDING},
{ID_TABLE, TableCellSpacing, T_String, ATTR_CELLSPACING},
{ID_TABLE, TableFrame, T_String, ATTR_FRAME},
{ID_TABLE, TableRules, T_String, ATTR_RULES},
{ID_TABLE, TableSummary, T_String, ATTR_SUMMARY},
{ID_TABLE, TableWidth, T_String, ATTR_WIDTH},
{ID_TABLE, TableRows, T_Coll, HTMLCollectionImpl::TABLE_ROWS},
{ID_TABLE, TableTBodies, T_Coll, HTMLCollectionImpl::TABLE_TBODIES},
{ID_CAPTION, TableCaptionAlign,T_String, ATTR_ALIGN},
{NotApplicable,TableColAlign, T_String, ATTR_ALIGN}, //Col/ColGroup
{NotApplicable,TableColCh, T_String, ATTR_CHAR},
{NotApplicable,TableColChOff, T_String, ATTR_CHAROFF},
{NotApplicable,TableColSpan, T_Int, ATTR_SPAN},
{NotApplicable,TableColVAlign, T_String, ATTR_VALIGN},
{NotApplicable,TableColWidth, T_String, ATTR_WIDTH},
{NotApplicable,TableSectionAlign,T_String, ATTR_ALIGN}, //THead/TBody/TFoot
{NotApplicable,TableSectionCh, T_String, ATTR_CHAR},
{NotApplicable,TableSectionChOff,T_String, ATTR_CHAROFF},
{NotApplicable,TableSectionVAlign,T_String, ATTR_VALIGN},
{NotApplicable,TableSectionRows, T_Coll, HTMLCollectionImpl::TSECTION_ROWS},
{ID_TR, TableRowAlign, T_String, ATTR_ALIGN}, //TR
{ID_TR, TableRowBgColor, T_String, ATTR_BGCOLOR},
{ID_TR, TableRowCh, T_String, ATTR_CHAR},
{ID_TR, TableRowChOff, T_String, ATTR_CHAROFF},
{ID_TR, TableRowVAlign, T_String, ATTR_VALIGN},
{ID_TR, TableRowCells, T_Coll, HTMLCollectionImpl::TR_CELLS},
{NotApplicable,TableCellAbbr, T_String, ATTR_ABBR}, //TD/TH
{NotApplicable,TableCellAlign, T_String, ATTR_ALIGN},
{NotApplicable,TableCellAxis, T_String, ATTR_AXIS},
{NotApplicable,TableCellBgColor, T_String, ATTR_BGCOLOR},
{NotApplicable,TableCellCh, T_String, ATTR_CHAR},
{NotApplicable,TableCellChOff, T_String, ATTR_CHAROFF},
{NotApplicable,TableCellColSpan, T_Int, ATTR_COLSPAN},
{NotApplicable,TableCellHeaders, T_String, ATTR_HEADERS},
{NotApplicable,TableCellHeight, T_String, ATTR_HEIGHT},
{NotApplicable,TableCellNoWrap, T_Bool, ATTR_NOWRAP},
{NotApplicable,TableCellRowSpan, T_Int, ATTR_ROWSPAN},
{NotApplicable,TableCellScope, T_String, ATTR_SCOPE},
{NotApplicable,TableCellVAlign, T_String, ATTR_VALIGN},
{NotApplicable,TableCellWidth, T_String, ATTR_WIDTH},
{ID_FRAMESET, FrameSetCols, T_String, ATTR_COLS},
{ID_FRAMESET, FrameSetRows, T_String, ATTR_ROWS},
{ID_LAYER, LayerTop, T_Int, ATTR_TOP},
{ID_LAYER, LayerLeft, T_Int, ATTR_LEFT},
{ID_LAYER, LayerVisibility, T_StrOrNl,ATTR_VISIBILITY},
{ID_LAYER, LayerBgColor, T_StrOrNl,ATTR_BGCOLOR},
{ID_LAYER, LayerLayers, T_Coll, HTMLCollectionImpl::DOC_LAYERS},
{ID_FRAME, FrameFrameBorder, T_String, ATTR_FRAMEBORDER},
{ID_FRAME, FrameLongDesc, T_String, ATTR_LONGDESC},
{ID_FRAME, FrameMarginHeight, T_String, ATTR_MARGINHEIGHT},
{ID_FRAME, FrameMarginWidth, T_String, ATTR_MARGINWIDTH},
{ID_FRAME, FrameName, T_String, ATTR_NAME},
{ID_FRAME, FrameNoResize, T_Bool, ATTR_NORESIZE},
{ID_FRAME, FrameScrolling, T_String, ATTR_SCROLLING},
{ID_FRAME, FrameSrc, T_String, ATTR_SRC}, //### not URL?
{ID_FRAME, FrameLocation, BoundPropType(T_String | T_ReadOnly), ATTR_SRC},
{ID_IFRAME, IFrameFrameBorder, T_String, ATTR_FRAMEBORDER},
{ID_IFRAME, IFrameLongDesc, T_String, ATTR_LONGDESC},
{ID_IFRAME, IFrameMarginHeight,T_String, ATTR_MARGINHEIGHT},
{ID_IFRAME, IFrameMarginWidth, T_String, ATTR_MARGINWIDTH},
{ID_IFRAME, IFrameName, T_String, ATTR_NAME},
{ID_IFRAME, IFrameScrolling, T_String, ATTR_SCROLLING},
{ID_IFRAME, IFrameSrc, T_URL, ATTR_SRC},
{ID_IFRAME, IFrameAlign, T_String, ATTR_ALIGN},
{ID_IFRAME, IFrameHeight, T_String, ATTR_HEIGHT},
{ID_IFRAME, IFrameWidth, T_String, ATTR_WIDTH},
{NotApplicable,ElementId, T_String, ATTR_ID},
{NotApplicable,ElementTitle, T_String, ATTR_TITLE},
{NotApplicable,ElementLang, T_String, ATTR_LANG},
{NotApplicable,ElementDir, T_String, ATTR_DIR},
{NotApplicable,ElementClassName, T_String, ATTR_CLASS},
{NotApplicable,ElementChildren, T_Coll, HTMLCollectionImpl::NODE_CHILDREN},
{0, 0, T_Res, 0},
};
QHash<int, const HTMLElement::BoundPropInfo*>* HTMLElement::s_boundPropInfo = 0;
QHash<int, const HTMLElement::BoundPropInfo*>* HTMLElement::boundPropInfo()
{
if (!s_boundPropInfo) {
s_boundPropInfo = new QHash<int, const BoundPropInfo*>();
for (int c = 0; bpTable[c].elId; ++c) {
s_boundPropInfo->insert(bpTable[c].token, &bpTable[c]);
}
}
return s_boundPropInfo;
}
QString KJS::HTMLElement::getURLArg(unsigned id) const
{
DOMString rel = khtml::parseURL(impl()->getAttribute(id));
return !rel.isNull() ? impl()->document()->completeURL(rel.string()) : QString();
}
DOM::HTMLElementImpl *toHTMLElement(JSValue *val) {
DOM::ElementImpl* e = toElement(val);
if (e && e->isHTMLElement())
return static_cast<HTMLElementImpl*>(e);
return 0;
}
DOM::HTMLTableCaptionElementImpl *toHTMLTableCaptionElement(JSValue *val)
{
DOM::ElementImpl *e = toElement(val);
if (e && e->id() == ID_CAPTION)
return static_cast<HTMLTableCaptionElementImpl *>(e);
return 0;
}
HTMLTableSectionElementImpl *toHTMLTableSectionElement(JSValue *val)
{
DOM::ElementImpl *e = toElement(val);
if (e && (e->id() == ID_THEAD || e->id() == ID_TBODY || e->id() == ID_TFOOT))
return static_cast<HTMLTableSectionElementImpl *>(e);
return 0;
}
JSValue* KJS::HTMLElement::handleBoundRead(ExecState* exec, int token) const
{
const BoundPropInfo* prop = boundPropInfo()->value(token);
if (!prop) return 0;
assert(prop->elId == NotApplicable || prop->elId == impl()->id());
switch (prop->type & ~T_ReadOnly) {
case T_String:
return jsString(impl()->getAttribute(prop->attrId));
case T_StrOrNl:
return getStringOrNull(impl()->getAttribute(prop->attrId));
case T_Bool:
return jsBoolean(!impl()->getAttribute(prop->attrId).isNull());
case T_Int:
return jsNumber(impl()->getAttribute(prop->attrId).toInt());
case T_URL:
return jsString(getURLArg(prop->attrId));
case T_Res:
return jsString("");
case T_Coll:
return getHTMLCollection(exec, new HTMLCollectionImpl(impl(), prop->attrId));
}
assert(0);
return 0;
}
KJS::Window* KJS::HTMLElement::ourWindow() const
{
KHTMLPart* part = impl()->document()->part();
if (part)
return Window::retrieveWindow(part);
else
return 0;
}
JSValue* KJS::HTMLElement::getWindowListener(ExecState* exec, int ev) const
{
if (KJS::Window* win = ourWindow()) {
return win->getListener(exec, ev);
} else {
return jsNull();
}
}
void KJS::HTMLElement::setWindowListener(ExecState* exec, int ev, JSValue* val) const
{
if (KJS::Window* win = ourWindow()) {
win->setListener(exec, ev, val);
}
}
JSValue* KJS::HTMLElement::getValueProperty(ExecState *exec, int token) const
{
JSValue* cand = handleBoundRead(exec, token);
if (cand) return cand;
DOM::HTMLElementImpl& element = *impl();
switch (element.id()) {
case ID_LINK: {
DOM::HTMLLinkElementImpl& link = static_cast<DOM::HTMLLinkElementImpl&>(element);
switch (token) {
case LinkSheet: return getDOMStyleSheet(exec,link.sheet());
}
}
break;
case ID_TITLE: {
DOM::HTMLTitleElementImpl& title = static_cast<DOM::HTMLTitleElementImpl&>(element);
switch (token) {
case TitleText: return jsString(title.text());
}
}
break;
case ID_ISINDEX: {
DOM::HTMLIsIndexElementImpl& isindex = static_cast<DOM::HTMLIsIndexElementImpl&>(element);
switch (token) {
case IsIndexForm: return getDOMNode(exec,isindex.form()); // type HTMLFormElement
case IsIndexPrompt: return jsString(isindex.prompt());
}
}
break;
case ID_STYLE: {
DOM::HTMLStyleElementImpl& style = static_cast<DOM::HTMLStyleElementImpl&>(element);
switch (token) {
case StyleSheet: return getDOMStyleSheet(exec,style.sheet());
}
}
break;
case ID_BODY: {
switch (token) {
case BodyOnLoad:
return getWindowListener(exec, DOM::EventImpl::LOAD_EVENT);
case BodyOnError:
return getWindowListener(exec, DOM::EventImpl::ERROR_EVENT);
case BodyOnBlur:
return getWindowListener(exec, DOM::EventImpl::BLUR_EVENT);
case BodyOnFocus:
return getWindowListener(exec, DOM::EventImpl::FOCUS_EVENT);
case BodyOnMessage:
return getWindowListener(exec, DOM::EventImpl::MESSAGE_EVENT);
}
}
break;
case ID_FRAMESET: {
switch (token) {
case FrameSetOnMessage:
return getWindowListener(exec, DOM::EventImpl::MESSAGE_EVENT);
}
}
break;
case ID_FORM: {
DOM::HTMLFormElementImpl& form = static_cast<DOM::HTMLFormElementImpl&>(element);
switch (token) {
case FormElements: return getHTMLCollection(exec,form.elements());
case FormLength: return jsNumber(form.length());
}
}
break;
case ID_SELECT: {
DOM::HTMLSelectElementImpl& select = static_cast<DOM::HTMLSelectElementImpl&>(element);
switch (token) {
case SelectType: return jsString(select.type());
case SelectSelectedIndex: return jsNumber(select.selectedIndex());
case SelectValue: return jsString(select.value());
case SelectLength: return jsNumber(select.length());
case SelectForm: return getDOMNode(exec,select.form()); // type HTMLFormElement
case SelectOptions: return getSelectHTMLCollection(exec, select.options(), &select); // type HTMLCollection
case SelectName: return jsString(select.name());
}
}
break;
case ID_OPTION: {
DOM::HTMLOptionElementImpl& option = static_cast<DOM::HTMLOptionElementImpl&>(element);
switch (token) {
case OptionForm: return getDOMNode(exec,option.form()); // type HTMLFormElement
case OptionText: return jsString(option.text());
case OptionIndex: return jsNumber(option.index());
case OptionSelected: return jsBoolean(option.selected());
case OptionValue: return jsString(option.value());
}
}
break;
case ID_INPUT: {
DOM::HTMLInputElementImpl& input = static_cast<DOM::HTMLInputElementImpl&>(element);
switch (token) {
case InputForm: return getDOMNode(exec,input.form()); // type HTMLFormElement
case InputChecked: return jsBoolean(input.checked());
case InputIndeterminate: return jsBoolean(input.indeterminate());
case InputName: return jsString(input.name()); // NOT getString (IE gives empty string)
case InputType: return jsString(input.type());
case InputValue: return jsString(input.value());
case InputSelectionStart: {
long val = input.selectionStart();
if (val != -1)
return jsNumber(val);
else
return jsUndefined();
}
case InputSelectionEnd: {
long val = input.selectionEnd();
if (val != -1)
return jsNumber(val);
else
return jsUndefined();
}
case InputPlaceholder: {
return jsString(input.placeholder());
}
}
}
break;
case ID_TEXTAREA: {
DOM::HTMLTextAreaElementImpl& textarea = static_cast<DOM::HTMLTextAreaElementImpl&>(element);
switch (token) {
case TextAreaDefaultValue: return jsString(textarea.defaultValue());
case TextAreaForm: return getDOMNode(exec,textarea.form()); // type HTMLFormElement
case TextAreaName: return jsString(textarea.name());
case TextAreaType: return jsString(textarea.type());
case TextAreaValue: return jsString(textarea.value());
case TextAreaSelectionStart: return jsNumber(textarea.selectionStart());
case TextAreaSelectionEnd: return jsNumber(textarea.selectionEnd());
case TextAreaTextLength: return jsNumber(textarea.textLength());
case TextAreaPlaceholder: return jsString(textarea.placeholder());
}
}
break;
case ID_BUTTON: {
DOM::HTMLButtonElementImpl& button = static_cast<DOM::HTMLButtonElementImpl&>(element);
switch (token) {
case ButtonForm: return getDOMNode(exec,button.form()); // type HTMLFormElement
case ButtonType: return jsString(button.type());
}
}
break;
case ID_LABEL: {
DOM::HTMLLabelElementImpl& label = static_cast<DOM::HTMLLabelElementImpl&>(element);
switch (token) {
case LabelForm: return getDOMNode(exec,label.form()); // type HTMLFormElement
}
}
break;
case ID_FIELDSET: {
DOM::HTMLFieldSetElementImpl& fieldSet = static_cast<DOM::HTMLFieldSetElementImpl&>(element);
switch (token) {
case FieldSetForm: return getDOMNode(exec,fieldSet.form()); // type HTMLFormElement
}
}
break;
case ID_LEGEND: {
DOM::HTMLLegendElementImpl& legend = static_cast<DOM::HTMLLegendElementImpl&>(element);
switch (token) {
case LegendForm: return getDOMNode(exec,legend.form()); // type HTMLFormElement
}
}
break;
case ID_A: {
DOM::HTMLAnchorElementImpl& anchor = static_cast<DOM::HTMLAnchorElementImpl&>(element);
QString href = getURLArg(ATTR_HREF);
switch (token) {
case AnchorHash: return jsString('#'+KUrl(href).ref());
case AnchorHost: return jsString(KUrl(href).host());
case AnchorHostname: {
KUrl url(href);
kDebug(6070) << "anchor::hostname uses:" <<url.url();
if (url.port()<=0)
return jsString(url.host());
else
return jsString(url.host() + ":" + QString::number(url.port()));
}
case AnchorPathName: return jsString(KUrl(href).path());
case AnchorPort: return jsString(QString::number(KUrl(href).port()));
- case AnchorProtocol: return jsString(KUrl(href).protocol()+":");
+ case AnchorProtocol: return jsString(KUrl(href).scheme()+":");
case AnchorSearch: { KUrl u(href);
QString q = u.query();
if (q.length() == 1)
return jsString("");
return jsString(q); }
// Not specified in http://msdn.microsoft.com/workshop/author/dhtml/reference/objects/a.asp
// Mozilla returns the inner text.
case AnchorText: return jsString(anchor.innerText());
}
}
break;
case ID_IMG: {
DOM::HTMLImageElementImpl& image = static_cast<DOM::HTMLImageElementImpl&>(element);
switch (token) {
case ImageComplete: return jsBoolean(image.complete());
case ImageHeight: return jsNumber(image.height());
case ImageWidth: return jsNumber(image.width());
case ImageX: return jsNumber(image.x());
case ImageY: return jsNumber(image.y());
}
}
break;
case ID_CANVAS: {
DOM::HTMLCanvasElementImpl& canvas = static_cast<DOM::HTMLCanvasElementImpl&>(element);
switch (token) {
case CanvasHeight: return jsNumber(canvas.height());
case CanvasWidth: return jsNumber(canvas.width());
}
}
break;
case ID_OBJECT: {
DOM::HTMLObjectElementImpl& object = static_cast<DOM::HTMLObjectElementImpl&>(element);
switch (token) {
case ObjectForm: return getDOMNode(exec,object.form()); // type HTMLFormElement
case ObjectContentDocument: return checkNodeSecurity(exec,object.contentDocument()) ?
getDOMNode(exec, object.contentDocument()) : jsUndefined();
}
}
break;
case ID_AREA: {
DOM::HTMLAreaElementImpl& area = static_cast<DOM::HTMLAreaElementImpl&>(element);
// Everything here needs href
DOM::Document doc = area.ownerDocument();
DOM::DOMString href = getURLArg(ATTR_HREF);
KUrl url;
if ( !href.isNull() ) {
url = href.string();
if ( href.isEmpty() )
url.setFileName( QString() ); // href="" clears the filename (in IE)
}
switch(token) {
case AreaHref:
return jsString(url.url());
case AreaHash: return jsString(url.isEmpty() ? "" : QString('#'+url.ref()));
case AreaHost: return jsString(url.host());
case AreaHostName: {
if (url.port()<=0)
return jsString(url.host());
else
return jsString(url.host() + ":" + QString::number(url.port()));
}
case AreaPathName: {
return jsString(url.path());
}
case AreaPort: return jsString(QString::number(url.port()));
- case AreaProtocol: return jsString(url.isEmpty() ? "" : QString(url.protocol()+":"));
+ case AreaProtocol: return jsString(url.isEmpty() ? "" : QString(url.scheme()+":"));
case AreaSearch: return jsString(url.query());
}
}
break;
case ID_SCRIPT: {
DOM::HTMLScriptElementImpl& script = static_cast<DOM::HTMLScriptElementImpl&>(element);
switch (token) {
case ScriptText: return jsString(script.text());
}
}
break;
case ID_TABLE: {
DOM::HTMLTableElementImpl& table = static_cast<DOM::HTMLTableElementImpl&>(element);
switch (token) {
case TableCaption: return getDOMNode(exec,table.caption()); // type HTMLTableCaptionElement
case TableTHead: return getDOMNode(exec,table.tHead()); // type HTMLTableSectionElement
case TableTFoot: return getDOMNode(exec,table.tFoot()); // type HTMLTableSectionElement
}
}
break;
case ID_TR: {
DOM::HTMLTableRowElementImpl& tableRow = static_cast<DOM::HTMLTableRowElementImpl&>(element);
switch (token) {
case TableRowRowIndex: return jsNumber(tableRow.rowIndex());
case TableRowSectionRowIndex: return jsNumber(tableRow.sectionRowIndex());
}
}
break;
case ID_TH:
case ID_TD: {
DOM::HTMLTableCellElementImpl& tableCell = static_cast<DOM::HTMLTableCellElementImpl&>(element);
switch (token) {
case TableCellCellIndex: return jsNumber(tableCell.cellIndex());
}
}
break;
case ID_LAYER: {
//DOM::HTMLLayerElementImpl& layerElement = static_cast<DOM::HTMLLayerElementImpl&>(element);
switch (token) {
/*case LayerClip: return getLayerClip(exec, layerElement); */
case LayerDocument: return jsUndefined();
}
}
break;
case ID_FRAME: {
DOM::HTMLFrameElementImpl& frameElement = static_cast<DOM::HTMLFrameElementImpl&>(element);
switch (token) {
case FrameContentDocument: return checkNodeSecurity(exec,frameElement.contentDocument()) ?
getDOMNode(exec, frameElement.contentDocument()) : jsUndefined();
case FrameContentWindow: {
KHTMLPart* part = frameElement.contentPart();
if (part) {
Window *w = Window::retrieveWindow(part);
if (w)
return w;
}
return jsUndefined();
}
// IE only
case FrameWidth:
case FrameHeight:
{
frameElement.document()->updateLayout();
khtml::RenderObject* r = frameElement.renderer();
return jsNumber( r ? (token == FrameWidth ? r->width() : r->height()) : 0 );
}
}
}
break;
case ID_IFRAME: {
DOM::HTMLIFrameElementImpl& iFrame = static_cast<DOM::HTMLIFrameElementImpl&>(element);
switch (token) {
case IFrameContentDocument: return checkNodeSecurity(exec,iFrame.contentDocument()) ?
getDOMNode(exec, iFrame.contentDocument()) : jsUndefined();
case IFrameContentWindow: {
KHTMLPart* part = iFrame.contentPart();
if (part) {
Window *w = Window::retrieveWindow(part);
if (w)
return w;
}
return jsUndefined();
}
}
break;
}
} // xemacs (or arnt) could be a bit smarter when it comes to indenting switch()es ;)
// it is not arnt to blame - it is the original Stroustrup style we like :) (Dirk)
// generic properties
switch (token) {
case ElementInnerHTML:
return jsString(element.innerHTML());
case ElementInnerText:
return jsString(element.innerText());
case ElementDocument:
return getDOMNode(exec,element.ownerDocument());
case ElementAll:
// Disable element.all when we try to be Netscape-compatible
if ( exec->dynamicInterpreter()->compatMode() == Interpreter::NetscapeCompat )
return jsUndefined();
else
if ( exec->dynamicInterpreter()->compatMode() == Interpreter::IECompat )
return getHTMLCollection(exec,new HTMLCollectionImpl(&element, HTMLCollectionImpl::DOC_ALL));
else // Enabled but hidden by default
return getHTMLCollection(exec,new HTMLCollectionImpl(&element, HTMLCollectionImpl::DOC_ALL), true);
case ElementTabIndex:
return jsNumber(element.tabIndex());
// ### what about style? or is this used instead for DOM2 stylesheets?
case ElementContentEditable:
return jsString(element.contentEditable());
case ElementIsContentEditable:
return jsBoolean(element.isContentEditable());
}
kError() << "HTMLElement::getValueProperty unhandled token " << token << endl;
return jsUndefined();
}
UString KJS::HTMLElement::toString(ExecState *exec) const
{
if (impl()->id() == ID_A)
return UString(getURLArg(ATTR_HREF));
#if 0 // ### debug stuff?
else if (impl()->id() == ID_APPLET) {
KParts::LiveConnectExtension *lc = getLiveConnectExtension(*impl());
QStringList qargs;
QString retvalue;
KParts::LiveConnectExtension::Type rettype;
unsigned long retobjid;
if (lc && lc->call(0, "hashCode", qargs, rettype, retobjid, retvalue)) {
QString str("[object APPLET ref=");
return UString(str + retvalue + QString("]"));
}
}
#endif
else if (impl()->id() == ID_IMG) {
DOMString alt = impl()->getAttribute(ATTR_ALT);
if (!alt.isEmpty())
return UString(alt) + " " + DOMElement::toString(exec);
}
return DOMElement::toString(exec);
}
static DOM::HTMLFormElementImpl* getForm(const DOM::HTMLElementImpl* element)
{
switch (element->id()) {
case ID_ISINDEX:
case ID_SELECT:
case ID_OPTION:
case ID_INPUT:
case ID_TEXTAREA:
case ID_LABEL:
case ID_FIELDSET:
case ID_LEGEND: {
const DOM::HTMLGenericFormElementImpl* fEl = static_cast<const HTMLGenericFormElementImpl*>(element);
return fEl->form();
}
case ID_OBJECT: {
const DOM::HTMLObjectElementImpl* oEl = static_cast<const HTMLObjectElementImpl*>(element);
return oEl->form();
}
default:
return 0;
}
}
void KJS::HTMLElement::pushEventHandlerScope(ExecState *exec, ScopeChain &scope) const
{
DOM::HTMLElementImpl& element = *impl();
// The document is put on first, fall back to searching it only after the element and form.
scope.push(static_cast<JSObject *>(getDOMNode(exec, element.document())));
// The form is next, searched before the document, but after the element itself.
DOM::HTMLFormElementImpl* formElt;
// First try to obtain the form from the element itself. We do this to deal with
// the malformed case where <form>s aren't in our parent chain (e.g., when they were inside
// <table> or <tbody>.
formElt = getForm(impl());
if (formElt)
scope.push(static_cast<JSObject *>(getDOMNode(exec, formElt)));
else {
DOM::NodeImpl* form = element.parentNode();
while (form && form->id() != ID_FORM)
form = form->parentNode();
if (form)
scope.push(static_cast<JSObject *>(getDOMNode(exec, form)));
}
// The element is on top, searched first.
scope.push(static_cast<JSObject *>(getDOMNode(exec, &element)));
}
JSValue* KJS::HTMLElementFunction::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args)
{
KJS_CHECK_THIS( HTMLElement, thisObj );
DOMExceptionTranslator exception(exec);
#ifdef KJS_VERBOSE
kDebug(6070) << "KJS::HTMLElementFunction::callAsFunction ";
#endif
DOM::HTMLElementImpl& element = *static_cast<KJS::HTMLElement *>(thisObj)->impl();
switch (element.id()) {
case ID_FORM: {
DOM::HTMLFormElementImpl& form = static_cast<DOM::HTMLFormElementImpl&>(element);
if (id == KJS::HTMLElement::FormSubmit) {
KHTMLPart *part = element.document()->part();
KHTMLSettings::KJSWindowOpenPolicy policy = KHTMLSettings::KJSWindowOpenAllow;
if (part)
policy = part->settings()->windowOpenPolicy(part->url().host());
bool block = false;
if ( policy != KHTMLSettings::KJSWindowOpenAllow ) {
block = true;
// if this is a form without a target, don't block
if ( form.target().isEmpty() )
block = false;
QString caption;
// if there is a frame with the target name, don't block
if ( part ) {
if (!part->url().host().isEmpty())
caption = part->url().host() + " - ";
if ( Window::targetIsExistingWindow(part, form.target().string()) )
block = false;
}
if ( block && policy == KHTMLSettings::KJSWindowOpenAsk && part ) {
if (part )
emit part->browserExtension()->requestFocus(part);
caption += i18n( "Confirmation: JavaScript Popup" );
if ( KMessageBox::questionYesNo(part->view(), form.action().isEmpty() ?
i18n( "This site is submitting a form which will open up a new browser "
"window via JavaScript.\n"
"Do you want to allow the form to be submitted?" ) :
i18n( "<qt>This site is submitting a form which will open <p>%1</p> in a new browser window via JavaScript.<br />"
"Do you want to allow the form to be submitted?</qt>", KStringHandler::csqueeze(form.action().string(), 100)),
caption, KGuiItem(i18n("Allow")), KGuiItem(i18n("Do Not Allow")) ) == KMessageBox::Yes )
block = false;
} else if ( block && policy == KHTMLSettings::KJSWindowOpenSmart ) {
if( static_cast<KJS::ScriptInterpreter *>(exec->dynamicInterpreter())->isWindowOpenAllowed() ) {
// This submission has been triggered by the user
block = false;
}
}
}
if( !block )
form.submit();
return jsUndefined();
}
else if (id == KJS::HTMLElement::FormReset) {
form.reset();
return jsUndefined();
}
}
break;
case ID_BODY: {
if (id == KJS::HTMLElement::BodyFocus) {
// Just blur everything. Not perfect, but good enough for now
element.document()->setFocusNode(0);
}
}
break;
case ID_SELECT: {
DOM::HTMLSelectElementImpl& select = static_cast<DOM::HTMLSelectElementImpl&>(element);
if (id == KJS::HTMLElement::SelectAdd) {
select.add(toHTMLElement(args[0]),toHTMLElement(args[1]),exception);
return jsUndefined();
}
else if (id == KJS::HTMLElement::SelectItem) {
SharedPtr<DOM::HTMLCollectionImpl> opts = select.options();
return getDOMNode(exec, opts->item(static_cast<unsigned long>(args[0]->toNumber(exec))));
}
else if (id == KJS::HTMLElement::SelectRemove) {
// Apparently this takes both elements and indices (ebay.fr)
DOM::NodeImpl* node = toNode(args[0]);
if (node && node->id() == ID_OPTION)
select.removeChild(node, exception);
else
select.remove(int(args[0]->toNumber(exec)));
return jsUndefined();
}
}
break;
case ID_INPUT: {
DOM::HTMLInputElementImpl& input = static_cast<DOM::HTMLInputElementImpl&>(element);
if (id == KJS::HTMLElement::InputSelect) {
input.select();
return jsUndefined();
}
else if (id == KJS::HTMLElement::InputClick) {
input.click();
return jsUndefined();
}
else if (id == KJS::HTMLElement::InputSetSelectionRange) {
input.setSelectionRange(args[0]->toNumber(exec), args[1]->toNumber(exec));
return jsUndefined();
}
}
break;
case ID_BUTTON: {
DOM::HTMLButtonElementImpl& button = static_cast<DOM::HTMLButtonElementImpl&>(element);
if (id == KJS::HTMLElement::ButtonClick) {
button.click();
return jsUndefined();
}
}
break;
case ID_TEXTAREA: {
DOM::HTMLTextAreaElementImpl& textarea = static_cast<DOM::HTMLTextAreaElementImpl&>(element);
if (id == KJS::HTMLElement::TextAreaSelect) {
textarea.select();
return jsUndefined();
}
else if (id == KJS::HTMLElement::TextAreaSetSelectionRange) {
textarea.setSelectionRange(args[0]->toNumber(exec), args[1]->toNumber(exec));
return jsUndefined();
}
}
break;
case ID_A: {
DOM::HTMLAnchorElementImpl& anchor = static_cast<DOM::HTMLAnchorElementImpl&>(element);
if (id == KJS::HTMLElement::AnchorClick) {
anchor.click();
return jsUndefined();
} else if (id == KJS::HTMLElement::AnchorToString) {
return jsString(static_cast<KJS::HTMLElement *>(thisObj)->toString(exec));
}
}
break;
case ID_TABLE: {
DOM::HTMLTableElementImpl& table = static_cast<DOM::HTMLTableElementImpl&>(element);
if (id == KJS::HTMLElement::TableCreateTHead)
return getDOMNode(exec,table.createTHead());
else if (id == KJS::HTMLElement::TableDeleteTHead) {
table.deleteTHead();
return jsUndefined();
}
else if (id == KJS::HTMLElement::TableCreateTFoot)
return getDOMNode(exec,table.createTFoot());
else if (id == KJS::HTMLElement::TableDeleteTFoot) {
table.deleteTFoot();
return jsUndefined();
}
else if (id == KJS::HTMLElement::TableCreateCaption)
return getDOMNode(exec,table.createCaption());
else if (id == KJS::HTMLElement::TableDeleteCaption) {
table.deleteCaption();
return jsUndefined();
}
else if (id == KJS::HTMLElement::TableInsertRow)
return getDOMNode(exec,table.insertRow(args[0]->toInteger(exec),exception));
else if (id == KJS::HTMLElement::TableDeleteRow) {
table.deleteRow(args[0]->toInteger(exec), exception);
return jsUndefined();
}
}
break;
case ID_THEAD:
case ID_TBODY:
case ID_TFOOT: {
DOM::HTMLTableSectionElementImpl& tableSection = static_cast<DOM::HTMLTableSectionElementImpl&>(element);
if (id == KJS::HTMLElement::TableSectionInsertRow)
return getDOMNode(exec,tableSection.insertRow(args[0]->toInteger(exec), exception));
else if (id == KJS::HTMLElement::TableSectionDeleteRow) {
tableSection.deleteRow(args[0]->toInteger(exec),exception);
return jsUndefined();
}
}
break;
case ID_TR: {
DOM::HTMLTableRowElementImpl& tableRow = static_cast<DOM::HTMLTableRowElementImpl&>(element);
if (id == KJS::HTMLElement::TableRowInsertCell)
return getDOMNode(exec,tableRow.insertCell(args[0]->toInteger(exec), exception));
else if (id == KJS::HTMLElement::TableRowDeleteCell) {
tableRow.deleteCell(args[0]->toInteger(exec), exception);
return jsUndefined();
}
break;
}
case ID_MARQUEE: {
if (id == KJS::HTMLElement::MarqueeStart && element.renderer() &&
element.renderer()->layer() &&
element.renderer()->layer()->marquee()) {
element.renderer()->layer()->marquee()->start();
return jsUndefined();
}
else if (id == KJS::HTMLElement::MarqueeStop && element.renderer() &&
element.renderer()->layer() &&
element.renderer()->layer()->marquee()) {
element.renderer()->layer()->marquee()->stop();
return jsUndefined();
}
break;
}
case ID_CANVAS: {
DOM::HTMLCanvasElementImpl& canvasEl = static_cast<DOM::HTMLCanvasElementImpl&>(element);
if (id == KJS::HTMLElement::CanvasGetContext) {
if (args[0]->toString(exec) == "2d")
return getWrapper<Context2D>(exec, canvasEl.getContext2D());
return jsNull();
} else if (id == KJS::HTMLElement::CanvasToDataURL) {
return jsString(canvasEl.toDataURL(exception));
}
break;
}
case ID_IFRAME: {
DOM::HTMLIFrameElementImpl& iFrame = static_cast<DOM::HTMLIFrameElementImpl&>(element);
if (id == KJS::HTMLElement::IFrameGetSVGDocument) {
if (!checkNodeSecurity(exec,iFrame.contentDocument()) || !iFrame.contentDocument() || !iFrame.contentDocument()->isSVGDocument())
return jsUndefined();
return getDOMNode(exec, iFrame.contentDocument());
}
}
case ID_OBJECT: {
DOM::HTMLObjectElementImpl& object = static_cast<DOM::HTMLObjectElementImpl&>(element);
if (id == KJS::HTMLElement::ObjectGetSVGDocument) {
if (!checkNodeSecurity(exec,object.contentDocument()) || !object.contentDocument() || !object.contentDocument()->isSVGDocument())
return jsUndefined();
return getDOMNode(exec, object.contentDocument());
}
}
}
if (id == HTMLElement::ElementScrollIntoView) {
bool alignToTop = true;
if (args.size() > 0)
alignToTop = args[0]->toBoolean(exec);
element.scrollIntoView(alignToTop);
return jsUndefined();
}
return jsUndefined();
}
void KJS::HTMLElement::put(ExecState *exec, const Identifier &propertyName, JSValue *value, int attr)
{
#ifdef KJS_VERBOSE
DOM::DOMString str = value->type() == NullType ? DOM::DOMString() : value->toString(exec).domString();
#endif
DOM::HTMLElementImpl& element = *impl();
#ifdef KJS_VERBOSE
kDebug(6070) << "KJS::HTMLElement::tryPut " << propertyName.qstring()
<< " thisTag=" << element.tagName().string()
<< " str=" << str.string() << endl;
#endif
//
// First look at dynamic properties
switch (element.id()) {
case ID_SELECT: {
DOM::HTMLSelectElementImpl& select = static_cast<DOM::HTMLSelectElementImpl&>(element);
bool ok;
/*uint u =*/ propertyName.qstring().toULong(&ok);
if (ok) {
JSObject *coll = getSelectHTMLCollection(exec, select.options(), &select)->getObject();
if ( coll )
coll->put(exec,propertyName,value);
return;
}
break;
}
case ID_APPLET:
case ID_OBJECT:
case ID_EMBED: {
KParts::ScriptableExtension* se = getScriptableExtension(element);
if (pluginRootPut(exec, se, propertyName, value))
return;
break;
}
default:
break;
}
const HashTable* table = classInfo()->propHashTable; // get the right hashtable
const HashEntry* entry = table ? Lookup::findEntry(table, propertyName) : 0;
if (entry) {
if (entry->attr & Function) { // function: put as override property
JSObject::put(exec, propertyName, value, attr);
return;
}
else if (!(entry->attr & ReadOnly)) { // let lookupPut print the warning if read-only
putValueProperty(exec, entry->value, value, attr);
return;
}
}
lookupPut<KJS::HTMLElement, DOMElement>(exec, propertyName, value, attr, &HTMLElementTable, this);
}
bool KJS::HTMLElement::handleBoundWrite(ExecState* exec, int token, JSValue* value)
{
const BoundPropInfo* prop = boundPropInfo()->value(token);
if (!prop) return false;
if (prop->type & T_ReadOnly) return false;
DOM::DOMString str = value->type() == NullType ? DOM::DOMString() : value->toString(exec).domString();
assert(prop->elId == NotApplicable || prop->elId == impl()->id());
switch (prop->type) {
case T_String:
case T_StrOrNl:
case T_URL:
impl()->setAttribute(prop->attrId, str);
return true;
case T_Int:
impl()->setAttribute(prop->attrId, QString::number(value->toInteger(exec)));
return true;
case T_Bool:
impl()->setAttribute(prop->attrId, value->toBoolean(exec) ? "" : 0);
return true;
case T_Res: //ignored
return true;
}
assert(0);
return false;
}
void KJS::HTMLElement::putValueProperty(ExecState *exec, int token, JSValue *value, int)
{
if (handleBoundWrite(exec, token, value))
return;
DOMExceptionTranslator exception(exec);
DOM::DOMString str = value->type() == NullType ? DOM::DOMString() : value->toString(exec).domString();
DOM::HTMLElementImpl& element = *impl();
#ifdef KJS_VERBOSE
kDebug(6070) << "KJS::HTMLElement::putValueProperty "
<< " thisTag=" << element.tagName().string()
<< " token=" << token << endl;
#endif
switch (element.id()) {
case ID_TITLE: {
DOM::HTMLTitleElementImpl& title = static_cast<DOM::HTMLTitleElementImpl&>(element);
switch (token) {
case TitleText: { title.setText(str); return; }
}
}
break;
case ID_ISINDEX: {
DOM::HTMLIsIndexElementImpl& isindex = static_cast<DOM::HTMLIsIndexElementImpl&>(element);
switch (token) {
// read-only: form
case IsIndexPrompt: { isindex.setPrompt(str); return; }
}
}
break;
case ID_BODY: {
//DOM::HTMLBodyElementImpl& body = static_cast<DOM::HTMLBodyElementImpl&>(element);
switch (token) {
case BodyOnLoad:
setWindowListener(exec, DOM::EventImpl::LOAD_EVENT, value);
break;
case BodyOnError:
setWindowListener(exec, DOM::EventImpl::ERROR_EVENT, value);
break;
case BodyOnBlur:
setWindowListener(exec, DOM::EventImpl::BLUR_EVENT, value);
break;
case BodyOnFocus:
setWindowListener(exec, DOM::EventImpl::FOCUS_EVENT, value);
break;
case BodyOnMessage:
setWindowListener(exec, DOM::EventImpl::MESSAGE_EVENT, value);
break;
}
}
case ID_FRAMESET: {
switch (token) {
case FrameSetOnMessage:
setWindowListener(exec, DOM::EventImpl::MESSAGE_EVENT, value);
break;
}
}
break;
case ID_SELECT: {
DOM::HTMLSelectElementImpl& select = static_cast<DOM::HTMLSelectElementImpl&>(element);
switch (token) {
// read-only: type
case SelectSelectedIndex: { select.setSelectedIndex(value->toInteger(exec)); return; }
case SelectValue: { select.setValue(str.implementation()); return; }
case SelectLength: { // read-only according to the NS spec, but webpages need it writeable
JSObject *coll = getSelectHTMLCollection(exec, select.options(), &select)->getObject();
if ( coll )
coll->put(exec, "length", value);
return;
}
// read-only: form
// read-only: options
case SelectName: { select.setName(str); return; }
}
}
break;
case ID_OPTION: {
DOM::HTMLOptionElementImpl& option = static_cast<DOM::HTMLOptionElementImpl&>(element);
switch (token) {
// read-only: form
// read-only: text <--- According to the DOM, but JavaScript and JScript both allow changes.
// So, we'll do it here and not add it to our DOM headers.
case OptionText: { SharedPtr<DOM::NodeListImpl> nl(option.childNodes());
for (unsigned int i = 0; i < nl->length(); i++) {
if (nl->item(i)->nodeType() == DOM::Node::TEXT_NODE) {
static_cast<DOM::TextImpl*>(nl->item(i))->setData(str, exception);
return;
}
}
// No child text node found, creating one
DOM::TextImpl* t = option.document()->createTextNode(str.implementation());
int dummyexception;
option.appendChild(t, dummyexception); // #### exec->setException ?
return;
}
// read-only: index
case OptionSelected: { option.setSelected(value->toBoolean(exec)); return; }
case OptionValue: { option.setValue(str.implementation()); return; }
}
}
break;
case ID_INPUT: {
DOM::HTMLInputElementImpl& input = static_cast<DOM::HTMLInputElementImpl&>(element);
switch (token) {
case InputChecked: { input.setChecked(value->toBoolean(exec)); return; }
case InputIndeterminate: { input.setIndeterminate(value->toBoolean(exec)); return; }
case InputName: { input.setName(str); return; }
case InputType: { input.setType(str); return; }
case InputValue: { input.setValue(str); return; }
case InputSelectionStart: { input.setSelectionStart(value->toInteger(exec)); return; }
case InputSelectionEnd: { input.setSelectionEnd (value->toInteger(exec)); return; }
case InputPlaceholder: { input.setPlaceholder(str); return; }
}
}
break;
case ID_TEXTAREA: {
DOM::HTMLTextAreaElementImpl& textarea = static_cast<DOM::HTMLTextAreaElementImpl&>(element);
switch (token) {
case TextAreaDefaultValue: { textarea.setDefaultValue(str); return; }
case TextAreaName: { textarea.setName(str); return; }
case TextAreaValue: { textarea.setValue(str); return; }
case TextAreaSelectionStart: { textarea.setSelectionStart(value->toInteger(exec)); return; }
case TextAreaSelectionEnd: { textarea.setSelectionEnd (value->toInteger(exec)); return; }
case TextAreaPlaceholder: { textarea.setPlaceholder(str); return; }
}
}
break;
case ID_A: {
DOM::HTMLAnchorElementImpl& anchor = static_cast<DOM::HTMLAnchorElementImpl&>(element);
switch (token) {
case AnchorSearch: { QString href = getURLArg(ATTR_HREF);
KUrl u(href);
QString q = str.isEmpty() ? QString() : str.string();
u.setQuery(q);
anchor.setAttribute(ATTR_HREF, u.url());
return; }
}
}
break;
case ID_IMG: {
DOM::HTMLImageElementImpl& image = static_cast<DOM::HTMLImageElementImpl&>(element);
switch (token) {
case ImageHeight: { image.setHeight(value->toInteger(exec)); return; }
case ImageWidth: { image.setWidth (value->toInteger(exec)); return; }
}
}
break;
case ID_CANVAS: {
DOM::HTMLCanvasElementImpl& canvas = static_cast<DOM::HTMLCanvasElementImpl&>(element);
switch (token) {
// ### it may make sense to do something different here, to at least
// emulate reflecting properties somewhat.
case CanvasWidth:
canvas.setAttribute(ATTR_WIDTH, value->toString(exec).domString());
return;
case CanvasHeight:
canvas.setAttribute(ATTR_HEIGHT, value->toString(exec).domString());
return;
}
}
break;
// case ID_FIELDSET: {
// DOM::HTMLFieldSetElementImpl& fieldSet = static_cast<DOM::HTMLFieldSetElementImpl&>(element);
// // read-only: form
// }
// break;
case ID_SCRIPT: {
DOM::HTMLScriptElementImpl& script = static_cast<DOM::HTMLScriptElementImpl&>(element);
switch (token) {
case ScriptText: { script.setText(str); return; }
}
}
break;
case ID_TABLE: {
DOM::HTMLTableElementImpl& table = static_cast<DOM::HTMLTableElementImpl&>(element);
switch (token) {
case TableCaption: { table.setCaption(toHTMLTableCaptionElement(value)); return; } // type HTMLTableCaptionElement
case TableTHead: { table.setTHead(toHTMLTableSectionElement(value)); return; } // type HTMLTableSectionElement
case TableTFoot: { table.setTFoot(toHTMLTableSectionElement(value)); return; } // type HTMLTableSectionElement
}
}
break;
case ID_FRAME: {
DOM::HTMLFrameElementImpl& frameElement = static_cast<DOM::HTMLFrameElementImpl&>(element);
switch (token) {
// read-only: FrameContentDocument:
case FrameLocation: {
frameElement.setLocation(str);
return;
}
}
}
break;
}
// generic properties
switch (token) {
case ElementInnerHTML:
element.setInnerHTML(str, exception);
return;
case ElementInnerText:
element.setInnerText(str, exception);
return;
case ElementContentEditable:
element.setContentEditable(str);
return;
case ElementTabIndex:
element.setTabIndex(value->toInteger(exec));
return;
default:
kDebug(6070) << "WARNING: KJS::HTMLElement::putValueProperty unhandled token " << token << " thisTag=" << element.tagName().string() << " str=" << str.string();
}
}
//Prototype mess for this...
KJS_DEFINE_PROTOTYPE(HTMLElementProto)
KJS_IMPLEMENT_PROTOTYPE("HTMLElement", HTMLElementProto, HTMLElementFunction, DOMElementProto )
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLElementPseudoCtor, "HTMLElement", HTMLElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLHtmlElement", HTMLHtmlElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLHtmlElementPseudoCtor, "HTMLHtmlElement", HTMLHtmlElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLHeadElement", HTMLHeadElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLHeadElementPseudoCtor, "HTMLHeadElement", HTMLHeadElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLLinkElement", HTMLLinkElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLLinkElementPseudoCtor, "HTMLLinkElement", HTMLLinkElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLTitleElement", HTMLTitleElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLTitleElementPseudoCtor, "HTMLTitleElement", HTMLTitleElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLMetaElement", HTMLMetaElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLMetaElementPseudoCtor, "HTMLMetaElement", HTMLMetaElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLBaseElement", HTMLBaseElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLBaseElementPseudoCtor, "HTMLBaseElement", HTMLBaseElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLIsIndexElement", HTMLIsIndexElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLIsIndexElementPseudoCtor, "HTMLIsIndexElement", HTMLIsIndexElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLStyleElement", HTMLStyleElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLStyleElementPseudoCtor, "HTMLStyleElement", HTMLStyleElementProto)
KJS_DEFINE_PROTOTYPE(HTMLBodyElementProto)
KJS_IMPLEMENT_PROTOTYPE("HTMLBodyElement", HTMLBodyElementProto, HTMLElementFunction, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLBodyElementPseudoCtor, "HTMLBodyElement", HTMLBodyElementProto)
KJS_DEFINE_PROTOTYPE(HTMLFormElementProto)
KJS_IMPLEMENT_PROTOTYPE("HTMLFormElement", HTMLFormElementProto, HTMLElementFunction, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLFormElementPseudoCtor, "HTMLFormElement", HTMLFormElementProto)
KJS_DEFINE_PROTOTYPE(HTMLSelectElementProto)
KJS_IMPLEMENT_PROTOTYPE("HTMLSelectElement", HTMLSelectElementProto, HTMLElementFunction, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLSelectElementPseudoCtor, "HTMLSelectElement", HTMLSelectElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLOptGroupElement", HTMLOptGroupElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLOptGroupElementPseudoCtor, "HTMLOptGroupElement", HTMLOptGroupElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLOptionElement", HTMLOptionElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLOptionElementPseudoCtor, "HTMLOptionElement", HTMLOptionElementProto)
KJS_DEFINE_PROTOTYPE(HTMLInputElementProto)
KJS_IMPLEMENT_PROTOTYPE("HTMLInputElement", HTMLInputElementProto, HTMLElementFunction, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLInputElementPseudoCtor, "HTMLInputElement", HTMLInputElementProto)
KJS_DEFINE_PROTOTYPE(HTMLTextAreaElementProto)
KJS_IMPLEMENT_PROTOTYPE("HTMLTextAreaElement", HTMLTextAreaElementProto, HTMLElementFunction, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLTextAreaElementPseudoCtor, "HTMLTextAreaElement", HTMLTextAreaElementProto)
KJS_DEFINE_PROTOTYPE(HTMLButtonElementProto)
KJS_IMPLEMENT_PROTOTYPE("HTMLButtonElement", HTMLButtonElementProto, HTMLElementFunction, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLButtonElementPseudoCtor, "HTMLButtonElement", HTMLButtonElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLLabelElement", HTMLLabelElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLLabelElementPseudoCtor, "HTMLLabelElement", HTMLLabelElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLFieldSetElement", HTMLFieldSetElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLFieldSetElementPseudoCtor, "HTMLFieldSetElement", HTMLFieldSetElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLLegendElement", HTMLLegendElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLLegendElementPseudoCtor, "HTMLLegendElement", HTMLLegendElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLUListElement", HTMLUListElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLUListElementPseudoCtor, "HTMLUListElement", HTMLUListElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLOListElement", HTMLOListElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLOListElementPseudoCtor, "HTMLOListElement", HTMLOListElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLDListElement", HTMLDListElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLDListElementPseudoCtor, "HTMLDListElement", HTMLDListElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLDirectoryElement", HTMLDirectoryElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLDirectoryElementPseudoCtor, "HTMLDirectoryElement", HTMLDirectoryElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLMenuElement", HTMLMenuElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLMenuElementPseudoCtor, "HTMLMenuElement", HTMLMenuElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLLIElement", HTMLLIElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLLIElementPseudoCtor, "HTMLLIElement", HTMLLIElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLDivElement", HTMLDivElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLDivElementPseudoCtor, "HTMLDivElement", HTMLDivElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLParagraphElement", HTMLParagraphElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLParagraphElementPseudoCtor, "HTMLParagraphElement", HTMLParagraphElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLHeadingElement", HTMLHeadingElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLHeadingElementPseudoCtor, "HTMLHeadingElement", HTMLHeadingElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLBlockQuoteElement", HTMLBlockQuoteElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLBlockQuoteElementPseudoCtor, "HTMLBlockQuoteElement", HTMLBlockQuoteElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLQuoteElement", HTMLQuoteElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLQuoteElementPseudoCtor, "HTMLQuoteElement", HTMLQuoteElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLPreElement", HTMLPreElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLPreElementPseudoCtor, "HTMLPreElement", HTMLPreElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLBRElement", HTMLBRElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLBRElementPseudoCtor, "HTMLBRElement", HTMLBRElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLBaseFontElement", HTMLBaseFontElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLBaseFontElementPseudoCtor, "HTMLBaseFontElement", HTMLBaseFontElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLFontElement", HTMLFontElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLFontElementPseudoCtor, "HTMLFontElement", HTMLFontElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLHRElement", HTMLHRElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLHRElementPseudoCtor, "HTMLHRElement", HTMLHRElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLModElement", HTMLModElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLModElementPseudoCtor, "HTMLModElement", HTMLModElementProto)
KJS_DEFINE_PROTOTYPE(HTMLAnchorElementProto)
KJS_IMPLEMENT_PROTOTYPE("HTMLAnchorElement", HTMLAnchorElementProto, HTMLElementFunction, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLAnchorElementPseudoCtor, "HTMLAnchorElement", HTMLAnchorElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLImageElement", HTMLImageElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLImageElementPseudoCtor, "HTMLImageElement", HTMLImageElementProto)
KJS_DEFINE_PROTOTYPE(HTMLObjectElementProto)
KJS_IMPLEMENT_PROTOTYPE("HTMLObjectElement", HTMLObjectElementProto, HTMLElementFunction, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLObjectElementPseudoCtor, "HTMLObjectElement", HTMLObjectElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLParamElement", HTMLParamElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLParamElementPseudoCtor, "HTMLParamElement", HTMLParamElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLAppletElement", HTMLAppletElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLAppletElementPseudoCtor, "HTMLAppletElement", HTMLAppletElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLMapElement", HTMLMapElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLMapElementPseudoCtor, "HTMLMapElement", HTMLMapElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLAreaElement", HTMLAreaElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLAreaElementPseudoCtor, "HTMLAreaElement", HTMLAreaElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLScriptElement", HTMLScriptElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLScriptElementPseudoCtor, "HTMLScriptElement", HTMLScriptElementProto)
KJS_DEFINE_PROTOTYPE(HTMLTableElementProto)
KJS_IMPLEMENT_PROTOTYPE("HTMLTableElement", HTMLTableElementProto, HTMLElementFunction, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLTableElementPseudoCtor, "HTMLTableElement", HTMLTableElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLTableCaptionElement", HTMLTableCaptionElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLTableCaptionElementPseudoCtor, "HTMLTableCaptionElement", HTMLTableCaptionElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLTableColElement", HTMLTableColElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLTableColElementPseudoCtor, "HTMLTableColElement", HTMLTableColElementProto)
KJS_DEFINE_PROTOTYPE(HTMLTableSectionElementProto)
KJS_IMPLEMENT_PROTOTYPE("HTMLTableSectionElement", HTMLTableSectionElementProto, HTMLElementFunction, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLTableSectionElementPseudoCtor, "HTMLTableSectionElement", HTMLTableSectionElementProto)
KJS_DEFINE_PROTOTYPE(HTMLTableRowElementProto)
KJS_IMPLEMENT_PROTOTYPE("HTMLTableRowElement", HTMLTableRowElementProto, HTMLElementFunction, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLTableRowElementPseudoCtor, "HTMLTableRowElement", HTMLTableRowElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLTableCellElement", HTMLTableCellElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLTableCellElementPseudoCtor, "HTMLTableCellElement", HTMLTableCellElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLFrameSetElement", HTMLFrameSetElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLFrameSetElementPseudoCtor, "HTMLFrameSetElement", HTMLFrameSetElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLLayerElement", HTMLLayerElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLLayerElementPseudoCtor, "HTMLLayerElement", HTMLLayerElementProto)
KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLFrameElement", HTMLFrameElementProto, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLFrameElementPseudoCtor, "HTMLFrameElement", HTMLFrameElementProto)
KJS_DEFINE_PROTOTYPE(HTMLIFrameElementProto)
KJS_IMPLEMENT_PROTOTYPE("HTMLIFrameElement", HTMLIFrameElementProto, HTMLElementFunction, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLIFrameElementPseudoCtor, "HTMLIFrameElement", HTMLIFrameElementProto)
KJS_DEFINE_PROTOTYPE(HTMLMarqueeElementProto)
KJS_IMPLEMENT_PROTOTYPE("HTMLMarqueeElement", HTMLMarqueeElementProto, HTMLElementFunction, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLMarqueeElementPseudoCtor, "HTMLMarqueeElement", HTMLMarqueeElementProto)
KJS_DEFINE_PROTOTYPE(HTMLCanvasElementProto)
KJS_IMPLEMENT_PROTOTYPE("HTMLCanvasElement", HTMLCanvasElementProto, HTMLElementFunction, HTMLElementProto)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLCanvasElementPseudoCtor, "HTMLCanvasElement", HTMLCanvasElementProto)
static JSObject* prototypeForID(ExecState* exec, DOM::NodeImpl::Id id) {
switch (id) {
case ID_HTML:
return HTMLHtmlElementProto::self(exec);
case ID_HEAD:
return HTMLHeadElementProto::self(exec);
case ID_LINK:
return HTMLLinkElementProto::self(exec);
case ID_TITLE:
return HTMLTitleElementProto::self(exec);
case ID_META:
return HTMLMetaElementProto::self(exec);
case ID_BASE:
return HTMLBaseElementProto::self(exec);
case ID_ISINDEX:
return HTMLIsIndexElementProto::self(exec);
case ID_STYLE:
return HTMLStyleElementProto::self(exec);
case ID_BODY:
return HTMLBodyElementProto::self(exec);
case ID_FORM:
return HTMLFormElementProto::self(exec);
case ID_SELECT:
return HTMLSelectElementProto::self(exec);
case ID_OPTGROUP:
return HTMLOptGroupElementProto::self(exec);
case ID_OPTION:
return HTMLOptionElementProto::self(exec);
case ID_INPUT:
return HTMLInputElementProto::self(exec);
case ID_TEXTAREA:
return HTMLTextAreaElementProto::self(exec);
case ID_BUTTON:
return HTMLButtonElementProto::self(exec);
case ID_LABEL:
return HTMLLabelElementProto::self(exec);
case ID_FIELDSET:
return HTMLFieldSetElementProto::self(exec);
case ID_LEGEND:
return HTMLLegendElementProto::self(exec);
case ID_UL:
return HTMLUListElementProto::self(exec);
case ID_OL:
return HTMLOListElementProto::self(exec);
case ID_DL:
return HTMLDListElementProto::self(exec);
case ID_DIR:
return HTMLDirectoryElementProto::self(exec);
case ID_MENU:
return HTMLMenuElementProto::self(exec);
case ID_LI:
return HTMLLIElementProto::self(exec);
case ID_DIV:
return HTMLDivElementProto::self(exec);
case ID_P:
return HTMLParagraphElementProto::self(exec);
case ID_H1:
case ID_H2:
case ID_H3:
case ID_H4:
case ID_H5:
case ID_H6:
return HTMLHeadingElementProto::self(exec);
case ID_BLOCKQUOTE:
return HTMLBlockQuoteElementProto::self(exec);
case ID_Q:
return HTMLQuoteElementProto::self(exec);
case ID_PRE:
return HTMLPreElementProto::self(exec);
case ID_BR:
return HTMLBRElementProto::self(exec);
case ID_BASEFONT:
return HTMLBaseFontElementProto::self(exec);
case ID_FONT:
return HTMLFontElementProto::self(exec);
case ID_HR:
return HTMLHRElementProto::self(exec);
case ID_INS:
case ID_DEL:
return HTMLModElementProto::self(exec);
case ID_A:
return HTMLAnchorElementProto::self(exec);
case ID_IMG:
return HTMLImageElementProto::self(exec);
case ID_OBJECT:
return HTMLObjectElementProto::self(exec);
case ID_PARAM:
return HTMLParamElementProto::self(exec);
case ID_APPLET:
return HTMLAppletElementProto::self(exec);
case ID_MAP:
return HTMLMapElementProto::self(exec);
case ID_AREA:
return HTMLAreaElementProto::self(exec);
case ID_SCRIPT:
return HTMLScriptElementProto::self(exec);
case ID_TABLE:
return HTMLTableElementProto::self(exec);
case ID_CAPTION:
return HTMLTableCaptionElementProto::self(exec);
case ID_COL:
case ID_COLGROUP:
return HTMLTableColElementProto::self(exec);
case ID_THEAD:
case ID_TBODY:
case ID_TFOOT:
return HTMLTableSectionElementProto::self(exec);
case ID_TR:
return HTMLTableRowElementProto::self(exec);
case ID_TD:
case ID_TH:
return HTMLTableCellElementProto::self(exec);
case ID_FRAMESET:
return HTMLFrameSetElementProto::self(exec);
case ID_LAYER:
return HTMLLayerElementProto::self(exec);
case ID_FRAME:
return HTMLFrameElementProto::self(exec);
case ID_IFRAME:
return HTMLIFrameElementProto::self(exec);
case ID_MARQUEE:
return HTMLMarqueeElementProto::self(exec);
case ID_CANVAS:
return HTMLCanvasElementProto::self(exec);
default:
return HTMLElementProto::self(exec);
}
}
// -------------------------------------------------------------------------
/* Source for HTMLCollectionProtoTable.
@begin HTMLCollectionProtoTable 3
item HTMLCollection::Item DontDelete|Function 1
namedItem HTMLCollection::NamedItem DontDelete|Function 1
tags HTMLCollection::Tags DontDelete|Function 1
@end
*/
KJS_DEFINE_PROTOTYPE(HTMLCollectionProto)
KJS_IMPLEMENT_PROTOFUNC(HTMLCollectionProtoFunc)
KJS_IMPLEMENT_PROTOTYPE("HTMLCollection", HTMLCollectionProto, HTMLCollectionProtoFunc, ObjectPrototype)
IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLCollectionPseudoCtor, "HTMLCollection", HTMLCollectionProto)
const ClassInfo KJS::HTMLCollection::info = { "HTMLCollection", 0, 0, 0 };
KJS::HTMLCollection::HTMLCollection(ExecState *exec, DOM::HTMLCollectionImpl* c)
: DOMObject(HTMLCollectionProto::self(exec)), m_impl(c), hidden(false) {}
KJS::HTMLCollection::HTMLCollection(JSObject* proto, DOM::HTMLCollectionImpl* c)
: DOMObject(proto), m_impl(c), hidden(false) {}
KJS::HTMLCollection::~HTMLCollection()
{
ScriptInterpreter::forgetDOMObject(m_impl.get());
}
bool KJS::HTMLCollection::masqueradeAsUndefined() const {
return hidden;
}
bool KJS::HTMLCollection::toBoolean(ExecState *) const {
return !hidden;
}
JSValue* HTMLCollection::indexGetter(ExecState *exec, unsigned index)
{
return getDOMNode(exec, m_impl->item(index));
}
void KJS::HTMLCollection::getOwnPropertyNames(ExecState* exec, PropertyNameArray& propertyNames)
{
for (unsigned i = 0; i < m_impl->length(); ++i)
propertyNames.add(Identifier::from(i));
propertyNames.add(exec->propertyNames().length);
JSObject::getOwnPropertyNames(exec, propertyNames);
}
JSValue *HTMLCollection::lengthGetter(ExecState *, JSObject*, const Identifier&, const PropertySlot& slot)
{
HTMLCollection *thisObj = static_cast<HTMLCollection *>(slot.slotBase());
return jsNumber(thisObj->m_impl->length());
}
JSValue *HTMLCollection::nameGetter(ExecState *exec, JSObject*, const Identifier& propertyName, const PropertySlot& slot)
{
HTMLCollection *thisObj = static_cast<HTMLCollection *>(slot.slotBase());
return thisObj->getNamedItems(exec, propertyName);
}
bool KJS::HTMLCollection::getOwnPropertySlot(ExecState *exec, const Identifier &propertyName, PropertySlot& slot)
{
#ifdef KJS_VERBOSE
kDebug(6070) << "KJS::HTMLCollection::getOwnPropertySlot " << propertyName.ascii();
#endif
if (propertyName.isEmpty())
return false;
if (propertyName == exec->propertyNames().length)
{
#ifdef KJS_VERBOSE
kDebug(6070) << " collection length is " << m_impl->length();
#endif
slot.setCustom(this, lengthGetter);
return true;
}
// Look in the prototype (for functions) before assuming it's an item's name
JSValue *proto = prototype();
if (proto->isObject() && static_cast<JSObject *>(proto)->hasProperty(exec, propertyName))
return false;
// name or index ?
if (getIndexSlot(this, *m_impl, propertyName, slot))
return true;
if (!getNamedItems(exec, propertyName)->isUndefined()) {
slot.setCustom(this, nameGetter);
return true;
}
return DOMObject::getOwnPropertySlot(exec, propertyName, slot);
}
// HTMLCollections are strange objects, they support both get and call,
// so that document.forms.item(0) and document.forms(0) both work.
JSValue* KJS::HTMLCollection::callAsFunction(ExecState *exec, JSObject *, const List &args)
{
// Do not use thisObj here. It can be the HTMLDocument, in the document.forms(i) case.
/*if( thisObj.imp() != this )
{
kDebug(6070) << "WARNING: thisObj.imp() != this in HTMLCollection::tryCall";
KJS::printInfo(exec,"KJS::HTMLCollection::tryCall thisObj",thisObj,-1);
KJS::printInfo(exec,"KJS::HTMLCollection::tryCall this",Value(this),-1);
}*/
// Also, do we need the TypeError test here ?
HTMLCollectionImpl &collection = *m_impl;
// Also, do we need the TypeError test here ?
if (args.size() == 1) {
// support for document.all(<index>) etc.
bool ok;
UString s = args[0]->toString(exec);
unsigned int u = s.toArrayIndex(&ok);
if (ok)
return getDOMNode(exec, collection.item(u));
// support for document.images('<name>') etc.
return getNamedItems(exec, Identifier(s));
}
else if (args.size() >= 1) // the second arg, if set, is the index of the item we want
{
bool ok;
UString s = args[0]->toString(exec);
unsigned int u = args[1]->toString(exec).toArrayIndex(&ok);
if (ok)
{
DOM::DOMString pstr = s.domString();
DOM::NodeImpl* node = collection.namedItem(pstr);
while (node) {
if (!u)
return getDOMNode(exec,node);
node = collection.nextNamedItem(pstr);
--u;
}
}
}
return jsUndefined();
}
JSValue* KJS::HTMLCollection::getNamedItems(ExecState *exec, const Identifier &propertyName) const
{
#ifdef KJS_VERBOSE
kDebug(6070) << "KJS::HTMLCollection::getNamedItems " << propertyName.ascii();
#endif
DOM::DOMString pstr = propertyName.domString();
const QList<DOM::NodeImpl*> matches = m_impl->namedItems(pstr);
if (!matches.isEmpty()) {
if (matches.size() == 1) {
#ifdef KJS_VERBOSE
kDebug(6070) << "returning single node";
#endif
return getDOMNode(exec,matches[0]);
}
else {
// multiple items, return a collection
QList<SharedPtr<DOM::NodeImpl> > nodes;
foreach (DOM::NodeImpl* node, matches)
nodes.append(node);
#ifdef KJS_VERBOSE
kDebug(6070) << "returning list of " << matches.count() << " nodes";
#endif
return new DOMNamedNodesCollection(exec, nodes);
}
}
#ifdef KJS_VERBOSE
kDebug(6070) << "not found";
#endif
return jsUndefined();
}
JSValue* KJS::HTMLCollectionProtoFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args)
{
KJS_CHECK_THIS( KJS::HTMLCollection, thisObj );
HTMLCollectionImpl &coll = *static_cast<HTMLCollection *>(thisObj)->impl();
switch (id) {
case KJS::HTMLCollection::Item:
{
// support for item(<index>) (DOM)
UString s = args[0]->toString(exec);
bool ok;
unsigned int u = s.toArrayIndex(&ok);
if (ok) {
return getDOMNode(exec,coll.item(u));
}
// support for item('<name>') (IE only)
kWarning() << "non-standard HTMLCollection.item('" << s.ascii() << "') called, use namedItem instead";
return static_cast<HTMLCollection *>(thisObj)->getNamedItems(exec, Identifier(s));
}
case KJS::HTMLCollection::Tags:
{
DOM::DOMString tagName = args[0]->toString(exec).domString();
DOM::NodeListImpl* list;
// getElementsByTagName exists in Document and in Element, pick up the right one
if ( coll.base()->nodeType() == DOM::Node::DOCUMENT_NODE )
{
DOM::DocumentImpl* doc = static_cast<DOM::DocumentImpl*>(coll.base());
list = doc->getElementsByTagName(tagName);
#ifdef KJS_VERBOSE
kDebug(6070) << "KJS::HTMLCollectionProtoFunc::callAsFunction document.tags(" << tagName.string() << ") -> " << list->length() << " items in node list";
#endif
} else
{
DOM::ElementImpl* e = static_cast<DOM::ElementImpl*>(coll.base());
list = e->getElementsByTagName(tagName);
#ifdef KJS_VERBOSE
kDebug(6070) << "KJS::HTMLCollectionProtoFunc::tryCall element.tags(" << tagName.string() << ") -> " << list->length() << " items in node list";
#endif
}
return getDOMNodeList(exec, list);
}
case KJS::HTMLCollection::NamedItem:
{
JSValue *val = static_cast<HTMLCollection *>(thisObj)->getNamedItems(exec, Identifier(args[0]->toString(exec)));
// Must return null when asking for a named item that isn't in the collection
// (DOM2 testsuite, HTMLCollection12 test)
if ( val->type() == KJS::UndefinedType )
return jsNull();
else
return val;
}
default:
return jsUndefined();
}
}
// -------------------------------------------------------------------------
/* Source for HTMLSelectCollectionProtoTable.
@begin HTMLSelectCollectionProtoTable 1
add HTMLSelectCollection::Add DontDelete|Function 2
@end
*/
KJS_DEFINE_PROTOTYPE(HTMLSelectCollectionProto)
KJS_IMPLEMENT_PROTOFUNC(HTMLSelectCollectionProtoFunc)
KJS_IMPLEMENT_PROTOTYPE("HTMLOptionsCollection", HTMLSelectCollectionProto, HTMLSelectCollectionProtoFunc, HTMLCollectionProto)
const ClassInfo KJS::HTMLSelectCollection::info = { "HTMLOptionsCollection", &HTMLCollection::info, 0, 0 };
KJS::HTMLSelectCollection::HTMLSelectCollection(ExecState *exec, DOM::HTMLCollectionImpl* c,
DOM::HTMLSelectElementImpl* e)
: HTMLCollection(HTMLSelectCollectionProto::self(exec), c), element(e) { }
JSValue *HTMLSelectCollection::selectedIndexGetter(ExecState*, JSObject*, const Identifier&, const PropertySlot& slot)
{
HTMLSelectCollection *thisObj = static_cast<HTMLSelectCollection *>(slot.slotBase());
return jsNumber(thisObj->element->selectedIndex());
}
JSValue *HTMLSelectCollection::selectedValueGetter(ExecState*, JSObject*, const Identifier& propertyName, const PropertySlot& slot)
{
Q_UNUSED(propertyName);
HTMLSelectCollection *thisObj = static_cast<HTMLSelectCollection *>(slot.slotBase());
return jsString(thisObj->element->value());
}
bool KJS::HTMLSelectCollection::getOwnPropertySlot(ExecState *exec, const Identifier &p, PropertySlot& slot)
{
if (p == "selectedIndex") {
slot.setCustom(this, selectedIndexGetter);
return true;
} else if (p == "value") {
slot.setCustom(this, selectedValueGetter);
return true;
}
return HTMLCollection::getOwnPropertySlot(exec, p, slot);
}
void KJS::HTMLSelectCollection::put(ExecState *exec, const Identifier &propertyName, JSValue *value, int)
{
DOMExceptionTranslator exception(exec);
#ifdef KJS_VERBOSE
kDebug(6070) << "KJS::HTMLSelectCollection::put " << propertyName.qstring();
#endif
if ( propertyName == "selectedIndex" ) {
element->setSelectedIndex( value->toInteger( exec ) );
return;
}
// resize ?
else if (propertyName == exec->propertyNames().length) {
uint32_t newLen;
bool converted = value->getUInt32(newLen);
if (!converted) {
return;
}
// CVE-2009-2537 (vendors agreed on max 10000 elements)
if (newLen > 10000) {
setDOMException(exec, DOMException::INDEX_SIZE_ERR);
return;
}
long diff = element->length() - newLen;
if (diff < 0) { // add dummy elements
do {
ElementImpl *option = element->document()->createElement("option", exception);
if (exception.triggered()) return;
element->add(static_cast<HTMLElementImpl *>(option), 0, exception);
if (exception.triggered()) return;
} while (++diff);
}
else // remove elements
while (diff-- > 0)
element->remove(newLen + diff);
return;
}
// an index ?
bool ok;
unsigned int u = propertyName.qstring().toULong(&ok);
if (!ok)
return;
if (value->type() == NullType || value->type() == UndefinedType) {
// null and undefined delete. others, too ?
element->remove(u);
return;
}
// is v an option element ?
DOM::NodeImpl* node = KJS::toNode(value);
if (!node || node->id() != ID_OPTION)
return;
DOM::HTMLOptionElementImpl* option = static_cast<DOM::HTMLOptionElementImpl*>(node);
if ( option->document() != element->document() )
option = static_cast<DOM::HTMLOptionElementImpl*>(element->ownerDocument()->importNode(option, true, exception));
if (exception.triggered()) return;
long diff = long(u) - element->length();
DOM::HTMLElementImpl* before = 0;
// out of array bounds ? first insert empty dummies
if (diff > 0) {
while (diff--) {
element->add(
static_cast<DOM::HTMLElementImpl*>(element->document()->createElement("OPTION")),
before, exception);
if (exception.triggered()) return;
}
// replace an existing entry ?
} else if (diff < 0) {
SharedPtr<DOM::HTMLCollectionImpl> options = element->options();
before = static_cast<DOM::HTMLElementImpl*>(options->item(u+1));
element->remove(u);
}
// finally add the new element
element->add(option, before, exception);
}
JSValue* KJS::HTMLSelectCollectionProtoFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args)
{
KJS_CHECK_THIS( KJS::HTMLSelectCollection, thisObj );
DOM::HTMLSelectElementImpl* element = static_cast<KJS::HTMLSelectCollection *>(thisObj)->toElement();
switch (id) {
case KJS::HTMLSelectCollection::Add:
{
//Non-standard select.options.add.
//The first argument is the item, 2nd is offset.
//IE and Mozilla are both quite picky here, too...
DOM::NodeImpl* node = KJS::toNode(args[0]);
if (!node || node->id() != ID_OPTION)
return throwError(exec, GeneralError, "Invalid argument to HTMLOptionsCollection::add");
DOM::HTMLOptionElementImpl* option = static_cast<DOM::HTMLOptionElementImpl*>(node);
int pos = 0;
//By default append, if not specified or null..
if (args[1]->isUndefined())
pos = element->length();
else
pos = (int)args[1]->toNumber(exec);
if (pos < 0)
return throwError(exec, GeneralError, "Invalid index argument to HTMLOptionsCollection::add");
DOMExceptionTranslator exception(exec);
if (pos >= element->length()) {
//Append
element->add(option, 0, exception);
} else {
//Find what to prepend before..
QVector<HTMLGenericFormElementImpl*> items = element->listItems();
int dummy;
element->insertBefore(option, items.at(pos), dummy);
}
return jsUndefined();
break;
}
default:
break;
}
return jsUndefined();
}
////////////////////// Option Object ////////////////////////
OptionConstructorImp::OptionConstructorImp(ExecState *exec, DOM::DocumentImpl* d)
: JSObject(exec->lexicalInterpreter()->builtinObjectPrototype()), doc(d)
{
// ## isn't there some redundancy between JSObject::_proto and the "prototype" property ?
//put(exec,"prototype", ...,DontEnum|DontDelete|ReadOnly);
// no. of arguments for constructor
// ## is 4 correct ? 0 to 4, it seems to be
put(exec, exec->propertyNames().length, jsNumber(4), ReadOnly|DontDelete|DontEnum);
}
bool OptionConstructorImp::implementsConstruct() const
{
return true;
}
JSObject *OptionConstructorImp::construct(ExecState *exec, const List &args)
{
DOMExceptionTranslator exception(exec);
DOM::ElementImpl* el = doc->createElement("OPTION");
DOM::HTMLOptionElementImpl* opt = static_cast<DOM::HTMLOptionElementImpl*>(el);
int sz = args.size();
SharedPtr<DOM::TextImpl> t = doc->createTextNode("");
int dummyexception = 0;// #### exec->setException ?
opt->appendChild(t.get(), dummyexception);
if (sz > 0)
t->setData(args[0]->toString(exec).domString(), exception); // set the text
if (sz > 1)
opt->setValue(args[1]->toString(exec).domString().implementation());
if (sz > 2)
opt->setDefaultSelected(args[2]->toBoolean(exec));
if (sz > 3)
opt->setSelected(args[3]->toBoolean(exec));
return getDOMNode(exec,opt)->getObject();
}
////////////////////// Image Object ////////////////////////
//Like in other browsers, we merely make a new HTMLImageElement
//not in tree for this.
ImageConstructorImp::ImageConstructorImp(ExecState* exec, DOM::DocumentImpl* d)
: JSObject(exec->lexicalInterpreter()->builtinObjectPrototype()), doc(d)
{
}
bool ImageConstructorImp::implementsConstruct() const
{
return true;
}
JSObject *ImageConstructorImp::construct(ExecState *exec, const List &list)
{
bool widthSet = false, heightSet = false;
int width = 0, height = 0;
if (list.size() > 0) {
widthSet = true;
JSValue *w = list.at(0);
width = w->toInt32(exec);
}
if (list.size() > 1) {
heightSet = true;
JSValue *h = list.at(1);
height = h->toInt32(exec);
}
HTMLImageElementImpl* image = static_cast<HTMLImageElementImpl*>(doc->createElement("img"));
if (widthSet)
image->setAttribute(ATTR_WIDTH, QString::number(width));
if (heightSet)
image->setAttribute(ATTR_HEIGHT, QString::number(height));
return getDOMNode(exec,image)->getObject();
}
JSValue* getHTMLCollection(ExecState *exec, DOM::HTMLCollectionImpl* c, bool hide)
{
assert(!c || c->getType() != HTMLCollectionImpl::SELECT_OPTIONS);
JSValue *coll = cacheDOMObject<DOM::HTMLCollectionImpl, KJS::HTMLCollection>(exec, c);
if (hide) {
KJS::HTMLCollection *impl = static_cast<KJS::HTMLCollection*>(coll);
impl->hide();
}
return coll;
}
JSValue* getSelectHTMLCollection(ExecState *exec, DOM::HTMLCollectionImpl* c, DOM::HTMLSelectElementImpl* e)
{
assert(!c || c->getType() == HTMLCollectionImpl::SELECT_OPTIONS);
DOMObject *ret;
if (!c) return jsNull();
ScriptInterpreter* interp = static_cast<ScriptInterpreter *>(exec->dynamicInterpreter());
if ((ret = interp->getDOMObject(c)))
return ret;
else {
ret = new HTMLSelectCollection(exec, c, e);
interp->putDOMObject(c,ret);
return ret;
}
}
} //namespace KJS
diff --git a/khtml/ecma/kjs_window.cpp b/khtml/ecma/kjs_window.cpp
index 3b0fa1fcd3..8f5b9cdb9e 100644
--- a/khtml/ecma/kjs_window.cpp
+++ b/khtml/ecma/kjs_window.cpp
@@ -1,3001 +1,3001 @@
// -*- c-basic-offset: 2 -*-
/*
* This file is part of the KDE libraries
* Copyright (C) 2000-2003 Harri Porten (porten@kde.org)
* Copyright (C) 2001-2003 David Faure (faure@kde.org)
* Copyright (C) 2003 Apple Computer, Inc.
* Copyright (C) 2006, 2008-2010 Maksim Orlovich (maksim@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; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "kjs_window.h"
#include "kjs_data.h"
#include <khtmlview.h>
#include <khtml_part.h>
#include <khtmlpart_p.h>
#include <khtml_settings.h>
#include <xml/dom2_eventsimpl.h>
#include <xml/dom_docimpl.h>
#include <dom/html_document.h>
#include <html/html_documentimpl.h>
#include <rendering/render_frames.h>
#include <config.h>
#include <QtCore/QTimer>
#include <QApplication>
#include <kdebug.h>
#include <kmessagebox.h>
#include <kinputdialog.h>
#include <klocale.h>
#include <kcodecs.h>
#include <kparts/browserinterface.h>
#include <kwindowsystem.h>
#include <QRegExpValidator>
#ifndef KONQ_EMBEDDED
#include <kbookmarkmanager.h>
#include <kbookmarkdialog.h>
#endif
#include <kglobalsettings.h>
#include <assert.h>
#include <QStyle>
#include <QtCore/QObject>
#include <QTextDocument>
#include <kstringhandler.h>
#include "kjs_proxy.h"
#include "kjs_navigator.h"
#include "kjs_mozilla.h"
#include "kjs_html.h"
#include "kjs_range.h"
#include "kjs_traversal.h"
#include "kjs_css.h"
#include "kjs_events.h"
#include "kjs_views.h"
#include "kjs_audio.h"
#include "kjs_context2d.h"
#include "kjs_xpath.h"
#include "kjs_scriptable.h"
#include "xmlhttprequest.h"
#include "xmlserializer.h"
#include "domparser.h"
#include <rendering/render_replaced.h>
using namespace KJS;
using namespace DOM;
namespace KJS {
class History : public JSObject {
friend class HistoryFunc;
public:
History(ExecState *exec, KHTMLPart *p)
: JSObject(exec->lexicalInterpreter()->builtinObjectPrototype()), part(p) { }
using KJS::JSObject::getOwnPropertySlot;
virtual bool getOwnPropertySlot(ExecState *exec, const Identifier& propertyName, PropertySlot& slot);
JSValue *getValueProperty(ExecState *exec, int token) const;
virtual const ClassInfo* classInfo() const { return &info; }
static const ClassInfo info;
enum { Back, Forward, Go, Length };
private:
QPointer<KHTMLPart> part;
};
class External : public JSObject {
friend class ExternalFunc;
public:
External(ExecState *exec, KHTMLPart *p)
: JSObject(exec->lexicalInterpreter()->builtinObjectPrototype()), part(p) { }
using KJS::JSObject::getOwnPropertySlot;
virtual bool getOwnPropertySlot(ExecState *exec, const Identifier& propertyName, PropertySlot& slot);
virtual const ClassInfo* classInfo() const { return &info; }
static const ClassInfo info;
enum { AddFavorite };
private:
QPointer<KHTMLPart> part;
};
} //namespace KJS
#include "kjs_window.lut.h"
namespace KJS {
////////////////////// Screen Object ////////////////////////
// table for screen object
/*
@begin ScreenTable 7
height Screen::Height DontEnum|ReadOnly
width Screen::Width DontEnum|ReadOnly
colorDepth Screen::ColorDepth DontEnum|ReadOnly
pixelDepth Screen::PixelDepth DontEnum|ReadOnly
availLeft Screen::AvailLeft DontEnum|ReadOnly
availTop Screen::AvailTop DontEnum|ReadOnly
availHeight Screen::AvailHeight DontEnum|ReadOnly
availWidth Screen::AvailWidth DontEnum|ReadOnly
@end
*/
const ClassInfo Screen::info = { "Screen", 0, &ScreenTable, 0 };
// We set the object prototype so that toString is implemented
Screen::Screen(ExecState *exec)
: JSObject(exec->lexicalInterpreter()->builtinObjectPrototype()) {}
bool Screen::getOwnPropertySlot(ExecState *exec, const Identifier& propertyName, PropertySlot& slot)
{
#ifdef KJS_VERBOSE
kDebug(6070) << "Screen::getPropertyName " << propertyName.qstring();
#endif
return getStaticValueSlot<Screen, JSObject>(exec, &ScreenTable, this, propertyName, slot);
}
JSValue *Screen::getValueProperty(ExecState *exec, int token) const
{
QWidget *thisWidget = Window::retrieveActive(exec)->part()->widget();
QRect sg = KGlobalSettings::desktopGeometry(thisWidget);
switch( token ) {
case Height:
return jsNumber(sg.height());
case Width:
return jsNumber(sg.width());
case ColorDepth:
case PixelDepth:
return jsNumber(thisWidget->depth());
case AvailLeft: {
#if defined Q_WS_X11 && ! defined K_WS_QTONLY
QRect clipped = KWindowSystem::workArea().intersect(sg);
return jsNumber(clipped.x()-sg.x());
#else
return jsNumber(10);
#endif
}
case AvailTop: {
#if defined Q_WS_X11 && ! defined K_WS_QTONLY
QRect clipped = KWindowSystem::workArea().intersect(sg);
return jsNumber(clipped.y()-sg.y());
#else
return jsNumber(10);
#endif
}
case AvailHeight: {
#if defined Q_WS_X11 && ! defined K_WS_QTONLY
QRect clipped = KWindowSystem::workArea().intersect(sg);
return jsNumber(clipped.height());
#else
return jsNumber(100);
#endif
}
case AvailWidth: {
#if defined Q_WS_X11 && ! defined K_WS_QTONLY
QRect clipped = KWindowSystem::workArea().intersect(sg);
return jsNumber(clipped.width());
#else
return jsNumber(100);
#endif
}
default:
kDebug(6070) << "WARNING: Screen::getValueProperty unhandled token " << token;
return jsUndefined();
}
}
////////////////////// Window Object ////////////////////////
const ClassInfo Window::info = { "Window", &DOMAbstractView::info, &WindowTable, 0 };
/*
@begin WindowTable 233
atob Window::AToB DontDelete|Function 1
btoa Window::BToA DontDelete|Function 1
closed Window::Closed DontDelete|ReadOnly
crypto Window::Crypto DontDelete|ReadOnly
defaultStatus Window::DefaultStatus DontDelete
defaultstatus Window::DefaultStatus DontDelete
status Window::Status DontDelete
document Window::Document DontDelete|ReadOnly
frameElement Window::FrameElement DontDelete|ReadOnly
frames Window::Frames DontDelete
history Window::_History DontDelete|ReadOnly
external Window::_External DontDelete|ReadOnly
event Window::Event DontDelete|ReadOnly
innerHeight Window::InnerHeight DontDelete|ReadOnly
innerWidth Window::InnerWidth DontDelete|ReadOnly
length Window::Length DontDelete
location Window::_Location DontDelete
name Window::Name DontDelete
navigator Window::_Navigator DontDelete|ReadOnly
clientInformation Window::ClientInformation DontDelete|ReadOnly
konqueror Window::_Konqueror DontDelete
offscreenBuffering Window::OffscreenBuffering DontDelete|ReadOnly
opener Window::Opener DontDelete|ReadOnly
outerHeight Window::OuterHeight DontDelete|ReadOnly
outerWidth Window::OuterWidth DontDelete|ReadOnly
pageXOffset Window::PageXOffset DontDelete|ReadOnly
pageYOffset Window::PageYOffset DontDelete|ReadOnly
parent Window::Parent DontDelete
personalbar Window::Personalbar DontDelete
screenX Window::ScreenX DontDelete|ReadOnly
screenY Window::ScreenY DontDelete|ReadOnly
scrollbars Window::Scrollbars DontDelete|ReadOnly
scroll Window::Scroll DontDelete|Function 2
scrollBy Window::ScrollBy DontDelete|Function 2
scrollTo Window::ScrollTo DontDelete|Function 2
scrollX Window::ScrollX DontDelete|ReadOnly
scrollY Window::ScrollY DontDelete|ReadOnly
moveBy Window::MoveBy DontDelete|Function 2
moveTo Window::MoveTo DontDelete|Function 2
postMessage Window::PostMessage DontDelete|Function 2
resizeBy Window::ResizeBy DontDelete|Function 2
resizeTo Window::ResizeTo DontDelete|Function 2
self Window::Self DontDelete|ReadOnly
window Window::_Window DontDelete|ReadOnly
top Window::Top DontDelete
screen Window::_Screen DontDelete|ReadOnly
alert Window::Alert DontDelete|Function 1
confirm Window::Confirm DontDelete|Function 1
prompt Window::Prompt DontDelete|Function 2
open Window::Open DontDelete|Function 3
setTimeout Window::SetTimeout DontDelete|Function 2
clearTimeout Window::ClearTimeout DontDelete|Function 1
focus Window::Focus DontDelete|Function 0
blur Window::Blur DontDelete|Function 0
close Window::Close DontDelete|Function 0
setInterval Window::SetInterval DontDelete|Function 2
clearInterval Window::ClearInterval DontDelete|Function 1
captureEvents Window::CaptureEvents DontDelete|Function 0
releaseEvents Window::ReleaseEvents DontDelete|Function 0
print Window::Print DontDelete|Function 0
addEventListener Window::AddEventListener DontDelete|Function 3
removeEventListener Window::RemoveEventListener DontDelete|Function 3
getSelection Window::GetSelection DontDelete|Function 0
# Normally found in prototype. Add to window object itself to make them
# accessible in closed and cross-site windows
valueOf Window::ValueOf DontEnum|DontDelete|Function 0
toString Window::ToString DontEnum|DontDelete|Function 0
# IE extension
navigate Window::Navigate DontDelete|Function 1
# Mozilla extension
sidebar Window::SideBar DontDelete|DontEnum
getComputedStyle Window::GetComputedStyle DontDelete|Function 2
# Warning, when adding a function to this object you need to add a case in Window::get
# Event handlers
# IE also has: onactivate, onbefore/afterprint, onbeforedeactivate/unload, oncontrolselect,
# ondeactivate, onhelp, onmovestart/end, onresizestart/end.
# It doesn't have onabort, onchange, ondragdrop (but NS has that last one).
onabort Window::Onabort DontDelete
onblur Window::Onblur DontDelete
onchange Window::Onchange DontDelete
onclick Window::Onclick DontDelete
ondblclick Window::Ondblclick DontDelete
ondragdrop Window::Ondragdrop DontDelete
onerror Window::Onerror DontDelete
onfocus Window::Onfocus DontDelete
onkeydown Window::Onkeydown DontDelete
onkeypress Window::Onkeypress DontDelete
onkeyup Window::Onkeyup DontDelete
onload Window::Onload DontDelete
onmessage Window::Onmessage DontDelete
onmousedown Window::Onmousedown DontDelete
onmousemove Window::Onmousemove DontDelete
onmouseout Window::Onmouseout DontDelete
onmouseover Window::Onmouseover DontDelete
onmouseup Window::Onmouseup DontDelete
onmove Window::Onmove DontDelete
onreset Window::Onreset DontDelete
onresize Window::Onresize DontDelete
onscroll Window::Onscroll DontDelete
onselect Window::Onselect DontDelete
onsubmit Window::Onsubmit DontDelete
onunload Window::Onunload DontDelete
# Constructors/constant tables
Node Window::Node DontEnum|DontDelete
Event Window::EventCtor DontEnum|DontDelete
Range Window::Range DontEnum|DontDelete
NodeFilter Window::NodeFilter DontEnum|DontDelete
NodeList Window::NodeList DontEnum|DontDelete
DOMException Window::DOMException DontEnum|DontDelete
RangeException Window::RangeException DontEnum|DontDelete
CSSRule Window::CSSRule DontEnum|DontDelete
MutationEvent Window::MutationEventCtor DontEnum|DontDelete
MessageEvent Window::MessageEventCtor DontEnum|DontDelete
KeyboardEvent Window::KeyboardEventCtor DontEnum|DontDelete
EventException Window::EventExceptionCtor DontEnum|DontDelete
Audio Window::Audio DontEnum|DontDelete
Image Window::Image DontEnum|DontDelete
Option Window::Option DontEnum|DontDelete
XMLHttpRequest Window::XMLHttpRequest DontEnum|DontDelete
XMLSerializer Window::XMLSerializer DontEnum|DontDelete
DOMParser Window::DOMParser DontEnum|DontDelete
# Mozilla dom emulation ones.
Element Window::ElementCtor DontEnum|DontDelete
Document Window::DocumentCtor DontEnum|DontDelete
DocumentFragment Window::DocumentFragmentCtor DontEnum|DontDelete
#this one is an alias since we don't have a separate XMLDocument
XMLDocument Window::DocumentCtor DontEnum|DontDelete
HTMLElement Window::HTMLElementCtor DontEnum|DontDelete
HTMLDocument Window::HTMLDocumentCtor DontEnum|DontDelete
HTMLHtmlElement Window::HTMLHtmlElementCtor DontEnum|DontDelete
HTMLHeadElement Window::HTMLHeadElementCtor DontEnum|DontDelete
HTMLLinkElement Window::HTMLLinkElementCtor DontEnum|DontDelete
HTMLTitleElement Window::HTMLTitleElementCtor DontEnum|DontDelete
HTMLMetaElement Window::HTMLMetaElementCtor DontEnum|DontDelete
HTMLBaseElement Window::HTMLBaseElementCtor DontEnum|DontDelete
HTMLIsIndexElement Window::HTMLIsIndexElementCtor DontEnum|DontDelete
HTMLStyleElement Window::HTMLStyleElementCtor DontEnum|DontDelete
HTMLBodyElement Window::HTMLBodyElementCtor DontEnum|DontDelete
HTMLFormElement Window::HTMLFormElementCtor DontEnum|DontDelete
HTMLSelectElement Window::HTMLSelectElementCtor DontEnum|DontDelete
HTMLOptGroupElement Window::HTMLOptGroupElementCtor DontEnum|DontDelete
HTMLOptionElement Window::HTMLOptionElementCtor DontEnum|DontDelete
HTMLInputElement Window::HTMLInputElementCtor DontEnum|DontDelete
HTMLTextAreaElement Window::HTMLTextAreaElementCtor DontEnum|DontDelete
HTMLButtonElement Window::HTMLButtonElementCtor DontEnum|DontDelete
HTMLLabelElement Window::HTMLLabelElementCtor DontEnum|DontDelete
HTMLFieldSetElement Window::HTMLFieldSetElementCtor DontEnum|DontDelete
HTMLLegendElement Window::HTMLLegendElementCtor DontEnum|DontDelete
HTMLUListElement Window::HTMLUListElementCtor DontEnum|DontDelete
HTMLOListElement Window::HTMLOListElementCtor DontEnum|DontDelete
HTMLDListElement Window::HTMLDListElementCtor DontEnum|DontDelete
HTMLDirectoryElement Window::HTMLDirectoryElementCtor DontEnum|DontDelete
HTMLMenuElement Window::HTMLMenuElementCtor DontEnum|DontDelete
HTMLLIElement Window::HTMLLIElementCtor DontEnum|DontDelete
HTMLDivElement Window::HTMLDivElementCtor DontEnum|DontDelete
HTMLParagraphElement Window::HTMLParagraphElementCtor DontEnum|DontDelete
HTMLHeadingElement Window::HTMLHeadingElementCtor DontEnum|DontDelete
HTMLBlockQuoteElement Window::HTMLBlockQuoteElementCtor DontEnum|DontDelete
HTMLQuoteElement Window::HTMLQuoteElementCtor DontEnum|DontDelete
HTMLPreElement Window::HTMLPreElementCtor DontEnum|DontDelete
HTMLBRElement Window::HTMLBRElementCtor DontEnum|DontDelete
HTMLBaseFontElement Window::HTMLBaseFontElementCtor DontEnum|DontDelete
HTMLFontElement Window::HTMLFontElementCtor DontEnum|DontDelete
HTMLHRElement Window::HTMLHRElementCtor DontEnum|DontDelete
HTMLModElement Window::HTMLModElementCtor DontEnum|DontDelete
HTMLAnchorElement Window::HTMLAnchorElementCtor DontEnum|DontDelete
HTMLImageElement Window::HTMLImageElementCtor DontEnum|DontDelete
HTMLObjectElement Window::HTMLObjectElementCtor DontEnum|DontDelete
HTMLParamElement Window::HTMLParamElementCtor DontEnum|DontDelete
HTMLAppletElement Window::HTMLAppletElementCtor DontEnum|DontDelete
HTMLMapElement Window::HTMLMapElementCtor DontEnum|DontDelete
HTMLAreaElement Window::HTMLAreaElementCtor DontEnum|DontDelete
HTMLScriptElement Window::HTMLScriptElementCtor DontEnum|DontDelete
HTMLTableElement Window::HTMLTableElementCtor DontEnum|DontDelete
HTMLTableCaptionElement Window::HTMLTableCaptionElementCtor DontEnum|DontDelete
HTMLTableColElement Window::HTMLTableColElementCtor DontEnum|DontDelete
HTMLTableSectionElement Window::HTMLTableSectionElementCtor DontEnum|DontDelete
HTMLTableRowElement Window::HTMLTableRowElementCtor DontEnum|DontDelete
HTMLTableCellElement Window::HTMLTableCellElementCtor DontEnum|DontDelete
HTMLFrameSetElement Window::HTMLFrameSetElementCtor DontEnum|DontDelete
HTMLLayerElement Window::HTMLLayerElementCtor DontEnum|DontDelete
HTMLFrameElement Window::HTMLFrameElementCtor DontEnum|DontDelete
HTMLIFrameElement Window::HTMLIFrameElementCtor DontEnum|DontDelete
HTMLCollection Window::HTMLCollectionCtor DontEnum|DontDelete
HTMLCanvasElement Window::HTMLCanvasElementCtor DontEnum|DontDelete
CSSStyleDeclaration Window::CSSStyleDeclarationCtor DontEnum|DontDelete
StyleSheet Window::StyleSheetCtor DontEnum|DontDelete
CanvasRenderingContext2D Window::Context2DCtor DontEnum|DontDelete
SVGAngle Window::SVGAngleCtor DontEnum|DontDelete
XPathResult Window::XPathResultCtor DontEnum|DontDelete
XPathExpression Window::XPathExpressionCtor DontEnum|DontDelete
XPathNSResolver Window::XPathNSResolverCtor DontEnum|DontDelete
@end
*/
KJS_IMPLEMENT_PROTOFUNC(WindowFunc)
Window::Window(khtml::ChildFrame *p)
: JSGlobalObject(/*no proto*/), m_frame(p), screen(0), history(0), external(0), loc(0), m_evt(0)
{
winq = new WindowQObject(this);
//kDebug(6070) << "Window::Window this=" << this << " part=" << m_part << " " << m_part->name();
}
Window::~Window()
{
qDeleteAll(m_delayed);
delete winq;
}
Window *Window::retrieveWindow(KParts::ReadOnlyPart *p)
{
JSObject *obj = retrieve( p )->getObject();
#ifndef NDEBUG
// obj should never be null, except when javascript has been disabled in that part.
KHTMLPart *part = qobject_cast<KHTMLPart*>(p);
if ( part && part->jScriptEnabled() )
{
assert( obj );
#ifndef QWS
assert( dynamic_cast<KJS::Window*>(obj) ); // type checking
#endif
}
#endif
if ( !obj ) // JS disabled
return 0;
return static_cast<KJS::Window*>(obj);
}
Window *Window::retrieveActive(ExecState *exec)
{
JSValue *imp = exec->dynamicInterpreter()->globalObject();
assert( imp );
#ifndef QWS
assert( dynamic_cast<KJS::Window*>(imp) );
#endif
return static_cast<KJS::Window*>(imp);
}
JSValue *Window::retrieve(KParts::ReadOnlyPart *p)
{
assert(p);
KHTMLPart * part = qobject_cast<KHTMLPart*>(p);
KJSProxy *proxy = 0L;
if (!part) {
part = qobject_cast<KHTMLPart*>(p->parent());
if (part)
proxy = part->framejScript(p);
} else
proxy = part->jScript();
if (proxy) {
#ifdef KJS_VERBOSE
kDebug(6070) << "Window::retrieve part=" << part << " '" << part->objectName() << "' interpreter=" << proxy->interpreter() << " window=" << proxy->interpreter()->globalObject();
#endif
return proxy->interpreter()->globalObject(); // the Global object is the "window"
} else {
#ifdef KJS_VERBOSE
kDebug(6070) << "Window::retrieve part=" << p << " '" << p->objectName() << "' no jsproxy.";
#endif
return jsUndefined(); // This can happen with JS disabled on the domain of that window
}
}
Location *Window::location() const
{
if (!loc)
const_cast<Window*>(this)->loc = new Location(m_frame);
return loc;
}
// reference our special objects during garbage collection
void Window::mark()
{
JSObject::mark();
if (screen && !screen->marked())
screen->mark();
if (history && !history->marked())
history->mark();
if (external && !external->marked())
external->mark();
//kDebug(6070) << "Window::mark " << this << " marking loc=" << loc;
if (loc && !loc->marked())
loc->mark();
if (winq)
winq->mark();
foreach (DelayedAction* action, m_delayed)
action->mark();
}
UString Window::toString(ExecState *) const
{
return "[object Window]";
}
bool Window::isCrossFrameAccessible(int token) const
{
switch (token) {
case Closed:
case _Location: // No isSafeScript test here, we must be able to _set_ location.href (#49819)
case _Window:
case Self:
case Frames:
case Opener:
case Parent:
case Top:
case Alert:
case Confirm:
case Prompt:
case Open:
case Close:
case Focus:
case Blur:
case AToB:
case BToA:
case ValueOf:
case ToString:
case PostMessage:
return true;
default:
return false;
}
}
bool Window::getOwnPropertySlot(ExecState *exec, const Identifier& propertyName, PropertySlot& slot)
{
#ifdef KJS_VERBOSE
kDebug(6070) << "Window("<<this<<")::getOwnPropertySlot " << propertyName.qstring();
#endif
// we want only limited operations on a closed window
if (m_frame.isNull() || m_frame->m_part.isNull()) {
const HashEntry* entry = Lookup::findEntry(&WindowTable, propertyName);
if (entry) {
switch (entry->value) {
case Closed:
case _Location:
case ValueOf:
case ToString:
getSlotFromEntry<WindowFunc, Window>(entry, this, slot);
return true;
default:
break;
}
}
slot.setUndefined(this);
return true;
}
bool safe = isSafeScript(exec);
// Look for overrides first.
JSValue **val = getDirectLocation(propertyName);
if (val) {
if (safe) {
fillDirectLocationSlot(slot, val);
} else {
// We may need to permit access to the property map cross-frame in
// order to pick up cross-frame accessible functions that got
// cached as direct properties.
const HashEntry* entry = Lookup::findEntry(&WindowTable, propertyName);
if (entry && isCrossFrameAccessible(entry->value))
fillDirectLocationSlot(slot, val);
else
slot.setUndefined(this);
}
return true;
}
// The only stuff we permit XSS (besides cached things above) are
// a few of hashtable properties.
const HashEntry* entry = Lookup::findEntry(&WindowTable, propertyName);
if (!safe && (!entry || !isCrossFrameAccessible(entry->value))) {
slot.setUndefined(this);
return true;
}
// invariant: accesses below this point are permitted by the XSS policy
KHTMLPart *part = qobject_cast<KHTMLPart*>(m_frame->m_part.data());
if (entry) {
// Things that work on any ReadOnlyPart first
switch(entry->value) {
case Closed:
case _Location:
case _Window:
case Self:
getSlotFromEntry<WindowFunc, Window>(entry, this, slot);
return true;
default:
break;
}
if (!part) {
slot.setUndefined(this);
return true;
}
// KHTMLPart-specific next.
// Disabled in NS-compat mode. Supported by default - can't hurt, unless someone uses
// if (navigate) to test for IE (unlikely).
if (entry->value == Navigate && exec->dynamicInterpreter()->compatMode() == Interpreter::NetscapeCompat ) {
slot.setUndefined(this);
return true;
}
getSlotFromEntry<WindowFunc, Window>(entry, this, slot);
return true;
}
if (!part) {
// not a KHTMLPart, so try to get plugin scripting stuff
if (pluginRootGet(exec, m_frame->m_scriptable.data(), propertyName, slot))
return true;
slot.setUndefined(this);
return true;
}
// Now do frame indexing.
KParts::ReadOnlyPart *rop = part->findFramePart( propertyName.qstring() );
if (rop) {
slot.setCustom(this, framePartGetter);
return true;
}
// allow window[1] or parent[1] etc. (#56983)
bool ok;
unsigned int i = propertyName.toArrayIndex(&ok);
if (ok && frameByIndex(i)) {
slot.setCustomIndex(this, i, indexGetterAdapter<Window>);
return true;
}
// allow shortcuts like 'Image1' instead of document.images.Image1
DOM::DocumentImpl *doc = part->xmlDocImpl();
if (doc && doc->isHTMLDocument()) {
DOM::ElementMappingCache::ItemInfo* info = doc->underDocNamedCache().get(propertyName.domString());
if (info || doc->getElementById(propertyName.domString())) {
slot.setCustom(this, namedItemGetter);
return true;
}
}
// This isn't necessarily a bug. Some code uses if(!window.blah) window.blah=1
// But it can also mean something isn't loaded or implemented, hence the WARNING to help grepping.
#ifdef KJS_VERBOSE
kDebug(6070) << "WARNING: Window::get property not found: " << propertyName.qstring();
#endif
return JSObject::getOwnPropertySlot(exec, propertyName, slot);
}
KParts::ReadOnlyPart* Window::frameByIndex(unsigned i)
{
KHTMLPart *part = qobject_cast<KHTMLPart*>(m_frame->m_part);
QList<KParts::ReadOnlyPart*> frames = part->frames();
unsigned int len = frames.count();
if (i < len) {
KParts::ReadOnlyPart* frame = frames.at(i);
return frame;
}
return 0;
}
JSValue* Window::indexGetter(ExecState *exec, unsigned index)
{
Q_UNUSED(exec);
KParts::ReadOnlyPart* frame = frameByIndex(index);
if (frame)
return Window::retrieve(frame);
return jsUndefined(); //### ?
}
JSValue *Window::framePartGetter(ExecState *exec, JSObject*, const Identifier& propertyName, const PropertySlot& slot)
{
Q_UNUSED(exec);
Window* thisObj = static_cast<Window*>(slot.slotBase());
KHTMLPart *part = qobject_cast<KHTMLPart*>(thisObj->m_frame->m_part);
KParts::ReadOnlyPart *rop = part->findFramePart( propertyName.qstring() );
return thisObj->retrieve(rop);
}
JSValue *Window::namedItemGetter(ExecState *exec, JSObject*, const Identifier& p, const PropertySlot& slot)
{
Window* thisObj = static_cast<Window*>(slot.slotBase());
KHTMLPart *part = qobject_cast<KHTMLPart*>(thisObj->m_frame->m_part);
DOM::DocumentImpl* doc = part->xmlDocImpl();
DOM::ElementMappingCache::ItemInfo* info = doc->underDocNamedCache().get(p.domString());
if (info) {
if (info->nd)
return getDOMNode(exec, info->nd);
else {
//No cached mapping, do it by hand...
DOM::HTMLMappedNameCollectionImpl* coll = new DOM::HTMLMappedNameCollectionImpl(doc,
DOM::HTMLCollectionImpl::DOCUMENT_NAMED_ITEMS, p.domString());
if (coll->length() == 1) {
info->nd = static_cast<DOM::ElementImpl*>(coll->firstItem());
delete coll;
return getDOMNode(exec, info->nd);
}
return getHTMLCollection(exec, coll);
}
}
DOM::ElementImpl* element = doc->getElementById(p.domString());
return getDOMNode(exec, element);
}
JSValue* Window::getValueProperty(ExecState *exec, int token)
{
KHTMLPart *part = m_frame.isNull() ? 0 : qobject_cast<KHTMLPart*>(m_frame->m_part);
if (!part) {
switch (token) {
case Closed:
return jsBoolean(true);
case _Location:
return jsNull();
default:
return jsUndefined();
}
}
switch(token) {
case Closed:
return jsBoolean(!part);
case _Location:
// No isSafeScript test here, we must be able to _set_ location.href (#49819)
return location();
case _Window:
case Self:
return retrieve(part);
case Frames:
return this;
case Opener:
if (!part->opener())
return jsNull(); // ### a null Window might be better, but == null
else // doesn't work yet
return retrieve(part->opener());
case Parent:
return retrieve(part && part->parentPart() ? part->parentPart() : (KHTMLPart*)part);
case Top: {
KHTMLPart *p = part;
while (p->parentPart())
p = p->parentPart();
return retrieve(p);
}
case Crypto:
return jsUndefined(); // ###
case DefaultStatus:
return jsString(UString(part->jsDefaultStatusBarText()));
case Status:
return jsString(UString(part->jsStatusBarText()));
case Document:
return getDOMNode(exec, part->xmlDocImpl());
case FrameElement:
if (m_frame->m_partContainerElement)
return getDOMNode(exec,m_frame->m_partContainerElement.data());
else
return jsUndefined();
case Node:
return NodeConstructor::self(exec);
case Range:
return getRangeConstructor(exec);
case NodeFilter:
return getNodeFilterConstructor(exec);
case NodeList:
return NodeListPseudoCtor::self(exec);
case DOMException:
return getDOMExceptionConstructor(exec);
case RangeException:
return RangeExceptionPseudoCtor::self(exec);
case CSSRule:
return getCSSRuleConstructor(exec);
case ElementCtor:
return ElementPseudoCtor::self(exec);
case DocumentFragmentCtor:
return DocumentFragmentPseudoCtor::self(exec);
case HTMLElementCtor:
return HTMLElementPseudoCtor::self(exec);
case HTMLHtmlElementCtor:
return HTMLHtmlElementPseudoCtor::self(exec);
case HTMLHeadElementCtor:
return HTMLHeadElementPseudoCtor::self(exec);
case HTMLLinkElementCtor:
return HTMLLinkElementPseudoCtor::self(exec);
case HTMLTitleElementCtor:
return HTMLTitleElementPseudoCtor::self(exec);
case HTMLMetaElementCtor:
return HTMLMetaElementPseudoCtor::self(exec);
case HTMLBaseElementCtor:
return HTMLBaseElementPseudoCtor::self(exec);
case HTMLIsIndexElementCtor:
return HTMLIsIndexElementPseudoCtor::self(exec);
case HTMLStyleElementCtor:
return HTMLStyleElementPseudoCtor::self(exec);
case HTMLBodyElementCtor:
return HTMLBodyElementPseudoCtor::self(exec);
case HTMLFormElementCtor:
return HTMLFormElementPseudoCtor::self(exec);
case HTMLSelectElementCtor:
return HTMLSelectElementPseudoCtor::self(exec);
case HTMLOptGroupElementCtor:
return HTMLOptGroupElementPseudoCtor::self(exec);
case HTMLOptionElementCtor:
return HTMLOptionElementPseudoCtor::self(exec);
case HTMLInputElementCtor:
return HTMLInputElementPseudoCtor::self(exec);
case HTMLTextAreaElementCtor:
return HTMLTextAreaElementPseudoCtor::self(exec);
case HTMLButtonElementCtor:
return HTMLButtonElementPseudoCtor::self(exec);
case HTMLLabelElementCtor:
return HTMLLabelElementPseudoCtor::self(exec);
case HTMLFieldSetElementCtor:
return HTMLFieldSetElementPseudoCtor::self(exec);
case HTMLLegendElementCtor:
return HTMLLegendElementPseudoCtor::self(exec);
case HTMLUListElementCtor:
return HTMLUListElementPseudoCtor::self(exec);
case HTMLOListElementCtor:
return HTMLOListElementPseudoCtor::self(exec);
case HTMLDListElementCtor:
return HTMLDListElementPseudoCtor::self(exec);
case HTMLDirectoryElementCtor:
return HTMLDirectoryElementPseudoCtor::self(exec);
case HTMLMenuElementCtor:
return HTMLMenuElementPseudoCtor::self(exec);
case HTMLLIElementCtor:
return HTMLLIElementPseudoCtor::self(exec);
case HTMLDivElementCtor:
return HTMLDivElementPseudoCtor::self(exec);
case HTMLParagraphElementCtor:
return HTMLParagraphElementPseudoCtor::self(exec);
case HTMLHeadingElementCtor:
return HTMLHeadingElementPseudoCtor::self(exec);
case HTMLBlockQuoteElementCtor:
return HTMLBlockQuoteElementPseudoCtor::self(exec);
case HTMLQuoteElementCtor:
return HTMLQuoteElementPseudoCtor::self(exec);
case HTMLPreElementCtor:
return HTMLPreElementPseudoCtor::self(exec);
case HTMLBRElementCtor:
return HTMLBRElementPseudoCtor::self(exec);
case HTMLBaseFontElementCtor:
return HTMLBaseFontElementPseudoCtor::self(exec);
case HTMLFontElementCtor:
return HTMLFontElementPseudoCtor::self(exec);
case HTMLHRElementCtor:
return HTMLHRElementPseudoCtor::self(exec);
case HTMLModElementCtor:
return HTMLModElementPseudoCtor::self(exec);
case HTMLAnchorElementCtor:
return HTMLAnchorElementPseudoCtor::self(exec);
case HTMLImageElementCtor:
return HTMLImageElementPseudoCtor::self(exec);
case HTMLObjectElementCtor:
return HTMLObjectElementPseudoCtor::self(exec);
case HTMLParamElementCtor:
return HTMLParamElementPseudoCtor::self(exec);
case HTMLAppletElementCtor:
return HTMLAppletElementPseudoCtor::self(exec);
case HTMLMapElementCtor:
return HTMLMapElementPseudoCtor::self(exec);
case HTMLAreaElementCtor:
return HTMLAreaElementPseudoCtor::self(exec);
case HTMLScriptElementCtor:
return HTMLScriptElementPseudoCtor::self(exec);
case HTMLTableElementCtor:
return HTMLTableElementPseudoCtor::self(exec);
case HTMLTableCaptionElementCtor:
return HTMLTableCaptionElementPseudoCtor::self(exec);
case HTMLTableColElementCtor:
return HTMLTableColElementPseudoCtor::self(exec);
case HTMLTableSectionElementCtor:
return HTMLTableSectionElementPseudoCtor::self(exec);
case HTMLTableRowElementCtor:
return HTMLTableRowElementPseudoCtor::self(exec);
case HTMLTableCellElementCtor:
return HTMLTableCellElementPseudoCtor::self(exec);
case HTMLFrameSetElementCtor:
return HTMLFrameSetElementPseudoCtor::self(exec);
case HTMLLayerElementCtor:
return HTMLLayerElementPseudoCtor::self(exec);
case HTMLFrameElementCtor:
return HTMLFrameElementPseudoCtor::self(exec);
case HTMLIFrameElementCtor:
return HTMLIFrameElementPseudoCtor::self(exec);
case HTMLCollectionCtor:
return HTMLCollectionPseudoCtor::self(exec);
case HTMLCanvasElementCtor:
return HTMLCanvasElementPseudoCtor::self(exec);
case Context2DCtor:
return Context2DPseudoCtor::self(exec);
case SVGAngleCtor:
return SVGAnglePseudoCtor::self(exec);
case XPathResultCtor:
return XPathResultPseudoCtor::self(exec);
case XPathExpressionCtor:
return XPathExpressionPseudoCtor::self(exec);
case XPathNSResolverCtor:
return XPathNSResolverPseudoCtor::self(exec);
case DocumentCtor:
return DocumentPseudoCtor::self(exec);
case HTMLDocumentCtor:
return HTMLDocumentPseudoCtor::self(exec);
case CSSStyleDeclarationCtor:
return CSSStyleDeclarationPseudoCtor::self(exec);
case StyleSheetCtor:
return DOMStyleSheetPseudoCtor::self(exec);
case EventCtor:
return EventConstructor::self(exec);
case MessageEventCtor:
return MessageEventPseudoCtor::self(exec);
case MutationEventCtor:
return getMutationEventConstructor(exec);
case KeyboardEventCtor:
return getKeyboardEventConstructor(exec);
case EventExceptionCtor:
return getEventExceptionConstructor(exec);
case _History:
return history ? history :
(const_cast<Window*>(this)->history = new History(exec,part));
case _External:
return external ? external :
(const_cast<Window*>(this)->external = new External(exec,part));
case Event:
if (m_evt)
return getDOMEvent(exec,m_evt);
else {
#ifdef KJS_VERBOSE
kDebug(6070) << "WARNING: window(" << this << "," << part->objectName() << ").event, no event!";
#endif
return jsUndefined();
}
case InnerHeight:
{
if (!part->view())
return jsUndefined();
int ret = part->view()->visibleHeight();
// match Gecko which does not subtract the scrollbars
if (part->view()->horizontalScrollBar()->isVisible()) {
ret += part->view()->horizontalScrollBar()->sizeHint().height();
}
return jsNumber(ret);
}
case InnerWidth:
{
if (!part->view())
return jsUndefined();
int ret = part->view()->visibleWidth();
// match Gecko which does not subtract the scrollbars
if (part->view()->verticalScrollBar()->isVisible()) {
ret += part->view()->verticalScrollBar()->sizeHint().width();
}
return jsNumber(ret);
}
case Length:
return jsNumber(part->frames().count());
case Name:
return jsString(part->objectName());
case SideBar:
return new MozillaSidebarExtension(exec, part);
case _Navigator:
case ClientInformation: {
// Store the navigator in the object so we get the same one each time.
JSValue *nav( new Navigator(exec, part) );
const_cast<Window *>(this)->put(exec, "navigator", nav, DontDelete|ReadOnly|Internal);
const_cast<Window *>(this)->put(exec, "clientInformation", nav, DontDelete|ReadOnly|Internal);
return nav;
}
case OffscreenBuffering:
return jsBoolean(true);
case OuterHeight:
case OuterWidth:
{
#if defined Q_WS_X11 && ! defined K_WS_QTONLY
if (!part->widget())
return jsNumber(0);
KWindowInfo inf = KWindowSystem::windowInfo(part->widget()->topLevelWidget()->winId(), NET::WMGeometry);
return jsNumber(token == OuterHeight ?
inf.geometry().height() : inf.geometry().width());
#else
return jsNumber(token == OuterHeight ?
part->view()->height() : part->view()->width());
#endif
}
case PageXOffset:
return jsNumber(part->view()->contentsX());
case PageYOffset:
return jsNumber(part->view()->contentsY());
case Personalbar:
return jsUndefined(); // ###
case ScreenLeft:
case ScreenX: {
if (!part->view())
return jsUndefined();
QRect sg = KGlobalSettings::desktopGeometry(part->view());
return jsNumber(part->view()->mapToGlobal(QPoint(0,0)).x() + sg.x());
}
case ScreenTop:
case ScreenY: {
if (!part->view())
return jsUndefined();
QRect sg = KGlobalSettings::desktopGeometry(part->view());
return jsNumber(part->view()->mapToGlobal(QPoint(0,0)).y() + sg.y());
}
case ScrollX: {
if (!part->view())
return jsUndefined();
return jsNumber(part->view()->contentsX());
}
case ScrollY: {
if (!part->view())
return jsUndefined();
return jsNumber(part->view()->contentsY());
}
case Scrollbars:
return new JSObject(); // ###
case _Screen:
return screen ? screen :
(const_cast<Window*>(this)->screen = new Screen(exec));
case Audio:
return new AudioConstructorImp(exec, part->xmlDocImpl());
case Image:
return new ImageConstructorImp(exec, part->xmlDocImpl());
case Option:
return new OptionConstructorImp(exec, part->xmlDocImpl());
case XMLHttpRequest:
return new XMLHttpRequestConstructorImp(exec, part->xmlDocImpl());
case XMLSerializer:
return new XMLSerializerConstructorImp(exec);
case DOMParser:
return new DOMParserConstructorImp(exec, part->xmlDocImpl());
case Onabort:
return getListener(exec,DOM::EventImpl::ABORT_EVENT);
case Onblur:
return getListener(exec,DOM::EventImpl::BLUR_EVENT);
case Onchange:
return getListener(exec,DOM::EventImpl::CHANGE_EVENT);
case Onclick:
return getListener(exec,DOM::EventImpl::KHTML_ECMA_CLICK_EVENT);
case Ondblclick:
return getListener(exec,DOM::EventImpl::KHTML_ECMA_DBLCLICK_EVENT);
case Ondragdrop:
return getListener(exec,DOM::EventImpl::KHTML_DRAGDROP_EVENT);
case Onerror:
return getListener(exec,DOM::EventImpl::ERROR_EVENT);
case Onfocus:
return getListener(exec,DOM::EventImpl::FOCUS_EVENT);
case Onkeydown:
return getListener(exec,DOM::EventImpl::KEYDOWN_EVENT);
case Onkeypress:
return getListener(exec,DOM::EventImpl::KEYPRESS_EVENT);
case Onkeyup:
return getListener(exec,DOM::EventImpl::KEYUP_EVENT);
case Onload:
return getListener(exec,DOM::EventImpl::LOAD_EVENT);
case Onmessage:
return getListener(exec,DOM::EventImpl::MESSAGE_EVENT);
case Onmousedown:
return getListener(exec,DOM::EventImpl::MOUSEDOWN_EVENT);
case Onmousemove:
return getListener(exec,DOM::EventImpl::MOUSEMOVE_EVENT);
case Onmouseout:
return getListener(exec,DOM::EventImpl::MOUSEOUT_EVENT);
case Onmouseover:
return getListener(exec,DOM::EventImpl::MOUSEOVER_EVENT);
case Onmouseup:
return getListener(exec,DOM::EventImpl::MOUSEUP_EVENT);
case Onmove:
return getListener(exec,DOM::EventImpl::KHTML_MOVE_EVENT);
case Onreset:
return getListener(exec,DOM::EventImpl::RESET_EVENT);
case Onresize:
return getListener(exec,DOM::EventImpl::RESIZE_EVENT);
case Onscroll:
return getListener(exec,DOM::EventImpl::SCROLL_EVENT);
case Onselect:
return getListener(exec,DOM::EventImpl::SELECT_EVENT);
case Onsubmit:
return getListener(exec,DOM::EventImpl::SUBMIT_EVENT);
case Onunload:
return getListener(exec,DOM::EventImpl::UNLOAD_EVENT);
}
return jsUndefined();
}
void Window::put(ExecState* exec, const Identifier &propertyName, JSValue *value, int attr)
{
// we don't want any operations on a closed window
if (m_frame.isNull() || m_frame->m_part.isNull()) {
// ### throw exception? allow setting of some props like location?
return;
}
// Called by an internal KJS call (e.g. InterpreterImp's constructor) ?
// If yes, save time and jump directly to JSObject. We also have
// to do this now since calling isSafeScript() may not work yet.
if (attr != None && attr != DontDelete)
{
JSObject::put( exec, propertyName, value, attr );
return;
}
// If we already have a variable, that's writeable w/o a getter/setter mess, just write to it.
bool safe = isSafeScript(exec);
if (safe) {
if (JSValue** slot = getDirectWriteLocation(propertyName)) {
*slot = value;
return;
}
}
const HashEntry* entry = Lookup::findEntry(&WindowTable, propertyName);
if (entry && !m_frame.isNull() && !m_frame->m_part.isNull())
{
#ifdef KJS_VERBOSE
kDebug(6070) << "Window("<<this<<")::put " << propertyName.qstring();
#endif
switch( entry->value) {
case _Location:
goURL(exec, value->toString(exec).qstring(), false /*don't lock history*/);
return;
default:
break;
}
KHTMLPart *part = qobject_cast<KHTMLPart*>(m_frame->m_part);
if (part) {
switch( entry->value ) {
case Status: {
if (isSafeScript(exec) && part->settings()->windowStatusPolicy(part->url().host())
== KHTMLSettings::KJSWindowStatusAllow) {
UString s = value->toString(exec);
part->setJSStatusBarText(s.qstring());
}
return;
}
case DefaultStatus: {
if (isSafeScript(exec) && part->settings()->windowStatusPolicy(part->url().host())
== KHTMLSettings::KJSWindowStatusAllow) {
UString s = value->toString(exec);
part->setJSDefaultStatusBarText(s.qstring());
}
return;
}
case Onabort:
if (isSafeScript(exec))
setListener(exec, DOM::EventImpl::ABORT_EVENT,value);
return;
case Onblur:
if (isSafeScript(exec))
setListener(exec, DOM::EventImpl::BLUR_EVENT,value);
return;
case Onchange:
if (isSafeScript(exec))
setListener(exec, DOM::EventImpl::CHANGE_EVENT,value);
return;
case Onclick:
if (isSafeScript(exec))
setListener(exec,DOM::EventImpl::KHTML_ECMA_CLICK_EVENT,value);
return;
case Ondblclick:
if (isSafeScript(exec))
setListener(exec,DOM::EventImpl::KHTML_ECMA_DBLCLICK_EVENT,value);
return;
case Ondragdrop:
if (isSafeScript(exec))
setListener(exec,DOM::EventImpl::KHTML_DRAGDROP_EVENT,value);
return;
case Onerror:
if (isSafeScript(exec))
setListener(exec,DOM::EventImpl::ERROR_EVENT,value);
return;
case Onfocus:
if (isSafeScript(exec))
setListener(exec,DOM::EventImpl::FOCUS_EVENT,value);
return;
case Onkeydown:
if (isSafeScript(exec))
setListener(exec,DOM::EventImpl::KEYDOWN_EVENT,value);
return;
case Onkeypress:
if (isSafeScript(exec))
setListener(exec,DOM::EventImpl::KEYPRESS_EVENT,value);
return;
case Onkeyup:
if (isSafeScript(exec))
setListener(exec,DOM::EventImpl::KEYUP_EVENT,value);
return;
case Onload:
if (isSafeScript(exec))
setListener(exec,DOM::EventImpl::LOAD_EVENT,value);
return;
case Onmessage:
if (isSafeScript(exec))
setListener(exec,DOM::EventImpl::MESSAGE_EVENT,value);
return;
case Onmousedown:
if (isSafeScript(exec))
setListener(exec,DOM::EventImpl::MOUSEDOWN_EVENT,value);
return;
case Onmousemove:
if (isSafeScript(exec))
setListener(exec,DOM::EventImpl::MOUSEMOVE_EVENT,value);
return;
case Onmouseout:
if (isSafeScript(exec))
setListener(exec,DOM::EventImpl::MOUSEOUT_EVENT,value);
return;
case Onmouseover:
if (isSafeScript(exec))
setListener(exec,DOM::EventImpl::MOUSEOVER_EVENT,value);
return;
case Onmouseup:
if (isSafeScript(exec))
setListener(exec,DOM::EventImpl::MOUSEUP_EVENT,value);
return;
case Onmove:
if (isSafeScript(exec))
setListener(exec,DOM::EventImpl::KHTML_MOVE_EVENT,value);
return;
case Onreset:
if (isSafeScript(exec))
setListener(exec,DOM::EventImpl::RESET_EVENT,value);
return;
case Onresize:
if (isSafeScript(exec))
setListener(exec,DOM::EventImpl::RESIZE_EVENT,value);
return;
case Onscroll:
if (isSafeScript(exec))
setListener(exec,DOM::EventImpl::SCROLL_EVENT,value);
return;
case Onselect:
if (isSafeScript(exec))
setListener(exec,DOM::EventImpl::SELECT_EVENT,value);
return;
case Onsubmit:
if (isSafeScript(exec))
setListener(exec,DOM::EventImpl::SUBMIT_EVENT,value);
return;
case Onunload:
if (isSafeScript(exec))
setListener(exec,DOM::EventImpl::UNLOAD_EVENT,value);
return;
case Name:
if (isSafeScript(exec))
part->setObjectName( value->toString(exec).qstring().toLocal8Bit().data() );
return;
default:
break;
}
}
}
if (isSafeScript(exec) &&
pluginRootPut(exec, m_frame->m_scriptable.data(), propertyName, value))
return;
if (safe) {
//kDebug(6070) << "Window("<<this<<")::put storing " << propertyName.qstring();
JSObject::put(exec, propertyName, value, attr);
}
}
bool Window::toBoolean(ExecState *) const
{
return !m_frame.isNull() && !m_frame->m_part.isNull();
}
DOM::AbstractViewImpl* Window::toAbstractView() const
{
KHTMLPart *part = ::qobject_cast<KHTMLPart *>(m_frame->m_part);
if (!part || !part->xmlDocImpl())
return 0;
return part->xmlDocImpl()->defaultView();
}
void Window::scheduleClose()
{
kDebug(6070) << "Window::scheduleClose window.close() " << m_frame;
Q_ASSERT(winq);
QTimer::singleShot( 0, winq, SLOT( timeoutClose() ) );
}
void Window::closeNow()
{
if (m_frame.isNull() || m_frame->m_part.isNull()) {
kDebug(6070) << "part is deleted already";
} else {
KHTMLPart *part = qobject_cast<KHTMLPart*>(m_frame->m_part);
if (!part) {
kDebug(6070) << "closeNow on non KHTML part";
} else {
//kDebug(6070) << " -> closing window";
// We want to make sure that window.open won't find this part by name.
part->setObjectName( QString() );
part->deleteLater();
part = 0;
}
}
}
void Window::afterScriptExecution()
{
DOM::DocumentImpl::updateDocumentsRendering();
const QList<DelayedAction*> delayedActions = m_delayed;
m_delayed.clear();
foreach(DelayedAction* act, delayedActions) {
if (!act->execute(this))
break; // done with them
}
qDeleteAll(delayedActions);
}
bool Window::checkIsSafeScript(KParts::ReadOnlyPart *activePart) const
{
if (m_frame.isNull() || m_frame->m_part.isNull()) { // part deleted ? can't grant access
kDebug(6070) << "Window::isSafeScript: accessing deleted part !";
return false;
}
if (!activePart) {
kDebug(6070) << "Window::isSafeScript: current interpreter's part is 0L!";
return false;
}
if ( activePart == m_frame->m_part ) // Not calling from another frame, no problem.
return true;
KHTMLPart *part = qobject_cast<KHTMLPart*>(m_frame->m_part);
if (!part)
return true; // not a KHTMLPart
if ( !part->xmlDocImpl() )
return true; // allow to access a window that was just created (e.g. with window.open("about:blank"))
DOM::DocumentImpl* thisDocument = part->xmlDocImpl();
KHTMLPart *activeKHTMLPart = qobject_cast<KHTMLPart*>(activePart);
if (!activeKHTMLPart)
return true; // not a KHTMLPart
DOM::DocumentImpl* actDocument = activeKHTMLPart->xmlDocImpl();
if ( !actDocument ) {
kDebug(6070) << "Window::isSafeScript: active part has no document!";
return false;
}
khtml::SecurityOrigin* actDomain = actDocument->origin();
khtml::SecurityOrigin* thisDomain = thisDocument->origin();
if ( actDomain->canAccess( thisDomain ) ) {
#ifdef KJS_VERBOSE
//kDebug(6070) << "JavaScript: access granted, domain is '" << actDomain.string() << "'";
#endif
return true;
}
kDebug(6070) << "WARNING: JavaScript: access denied for current frame '" << actDomain->toString() << "' to frame '" << thisDomain->toString() << "'";
// TODO after 3.1: throw security exception (exec->setException())
return false;
}
void Window::setListener(ExecState *exec, int eventId, JSValue *func)
{
KHTMLPart *part = qobject_cast<KHTMLPart*>(m_frame->m_part);
if (!part || !isSafeScript(exec))
return;
DOM::DocumentImpl *doc = static_cast<DOM::DocumentImpl*>(part->htmlDocument().handle());
if (!doc)
return;
doc->setHTMLWindowEventListener(eventId,getJSEventListener(func,true));
}
JSValue *Window::getListener(ExecState *exec, int eventId) const
{
KHTMLPart *part = qobject_cast<KHTMLPart*>(m_frame->m_part);
if (!part || !isSafeScript(exec))
return jsUndefined();
DOM::DocumentImpl *doc = static_cast<DOM::DocumentImpl*>(part->htmlDocument().handle());
if (!doc)
return jsUndefined();
DOM::EventListener *listener = doc->getHTMLWindowEventListener(eventId);
if (listener && static_cast<JSEventListener*>(listener)->listenerObj())
return static_cast<JSEventListener*>(listener)->listenerObj();
else
return jsNull();
}
JSEventListener *Window::getJSEventListener(JSValue *val, bool html)
{
// This function is so hot that it's worth coding it directly with imps.
KHTMLPart *part = qobject_cast<KHTMLPart*>(m_frame->m_part);
if (!part || val->type() != ObjectType)
return 0;
// It's ObjectType, so it must be valid.
JSObject *listenerObject = val->getObject();
JSObject *thisObject = listenerObject;
// 'listener' is not a simple ecma function. (Always use sanity checks: Better safe than sorry!)
if (!listenerObject->implementsCall() && part && part->jScript() && part->jScript()->interpreter())
{
Interpreter *interpreter = part->jScript()->interpreter();
// 'listener' probably is an EventListener object containing a 'handleEvent' function.
JSValue *handleEventValue = listenerObject->get(interpreter->globalExec(), Identifier("handleEvent"));
JSObject *handleEventObject = handleEventValue->getObject();
if(handleEventObject && handleEventObject->implementsCall())
{
thisObject = listenerObject;
listenerObject = handleEventObject;
}
}
JSEventListener *existingListener = jsEventListeners.value(QPair<void*, bool>(thisObject, html));
if (existingListener) {
assert( existingListener->isHTMLEventListener() == html );
return existingListener;
}
// Note that the JSEventListener constructor adds it to our jsEventListeners list
return new JSEventListener(listenerObject, thisObject, this, html);
}
JSLazyEventListener *Window::getJSLazyEventListener(const QString& code, const QString& srcUrl, int line,
const QString& name, DOM::NodeImpl *node, bool svg)
{
return new JSLazyEventListener(code, srcUrl, line, name, this, node, svg);
}
void Window::clear( ExecState *exec )
{
Q_UNUSED(exec);
delete winq;
qDeleteAll(m_delayed);
m_delayed.clear();
winq = 0L;
// Get rid of everything, those user vars could hold references to DOM nodes
clearProperties();
// Ditto for the special subobjects.
screen = 0;
history = 0;
external = 0;
loc = 0;
setPrototype(jsNull());
// Break the dependency between the listeners and their object
QHashIterator<const QPair<void*, bool>, JSEventListener*> it(jsEventListeners);
while ( it.hasNext() ) {
it.next();
it.value()->clear();
}
// Forget about the listeners (the DOM::NodeImpls will delete them)
jsEventListeners.clear();
if (m_frame) {
KJSProxy* proxy = m_frame->m_jscript;
if (proxy) // i.e. JS not disabled
{
winq = new WindowQObject(this);
// Now recreate a working global object for the next URL that will use us
KJS::Interpreter *interpreter = proxy->interpreter();
interpreter->initGlobalObject();
}
}
}
void Window::setCurrentEvent( DOM::EventImpl *evt )
{
m_evt = evt;
//kDebug(6070) << "Window " << this << " (part=" << m_part << ")::setCurrentEvent m_evt=" << evt;
}
void Window::goURL(ExecState* exec, const QString& url, bool lockHistory)
{
Window* active = Window::retrieveActive(exec);
KHTMLPart *part = qobject_cast<KHTMLPart*>(m_frame->m_part);
KHTMLPart *active_part = qobject_cast<KHTMLPart*>(active->part());
// Complete the URL using the "active part" (running interpreter)
if (active_part && part) {
QString dstUrl = active_part->htmlDocument().completeURL(url).string();
kDebug(6070) << "Window::goURL dstUrl=" << dstUrl;
// check if we're allowed to inject javascript
if ( !KHTMLPartPrivate::isJavaScriptURL(dstUrl) || isSafeScript(exec) )
part->scheduleRedirection(-1,
dstUrl,
lockHistory);
} else if (!part && m_frame->m_partContainerElement) {
KParts::BrowserExtension *b = KParts::BrowserExtension::childObject(m_frame->m_part);
if (b)
emit b->openUrlRequest(m_frame->m_partContainerElement.data()->document()->completeURL(url));
kDebug() << "goURL for ROPart";
}
}
class DelayedGoHistory: public Window::DelayedAction {
public:
DelayedGoHistory(int _steps): steps(_steps)
{}
virtual bool execute(Window* win) {
win->goHistory(steps);
return true;
}
private:
int steps;
};
void Window::delayedGoHistory( int steps )
{
m_delayed.append(new DelayedGoHistory(steps));
}
void Window::goHistory( int steps )
{
KHTMLPart *part = qobject_cast<KHTMLPart*>(m_frame->m_part);
if(!part)
// TODO history readonlypart
return;
KParts::BrowserExtension *ext = part->browserExtension();
if(!ext)
return;
KParts::BrowserInterface *iface = ext->browserInterface();
if ( !iface )
return;
iface->callMethod( "goHistory", steps );
//emit ext->goHistory(steps);
}
void KJS::Window::resizeTo(QWidget* tl, int width, int height)
{
KHTMLPart *part = qobject_cast<KHTMLPart*>(m_frame->m_part);
if(!part)
// TODO resizeTo readonlypart
return;
KParts::BrowserExtension *ext = part->browserExtension();
if (!ext) {
kDebug(6070) << "Window::resizeTo found no browserExtension";
return;
}
// Security check: within desktop limits and bigger than 100x100 (per spec)
if ( width < 100 || height < 100 ) {
kDebug(6070) << "Window::resizeTo refused, window would be too small ("<<width<<","<<height<<")";
return;
}
QRect sg = KGlobalSettings::desktopGeometry(tl);
if ( width > sg.width() || height > sg.height() ) {
kDebug(6070) << "Window::resizeTo refused, window would be too big ("<<width<<","<<height<<")";
return;
}
kDebug(6070) << "resizing to " << width << "x" << height;
emit ext->resizeTopLevelWidget( width, height );
// If the window is out of the desktop, move it up/left
// (maybe we should use workarea instead of sg, otherwise the window ends up below kicker)
int right = tl->x() + tl->frameGeometry().width();
int bottom = tl->y() + tl->frameGeometry().height();
int moveByX = 0;
int moveByY = 0;
if ( right > sg.right() )
moveByX = - right + sg.right(); // always <0
if ( bottom > sg.bottom() )
moveByY = - bottom + sg.bottom(); // always <0
if ( moveByX || moveByY )
emit ext->moveTopLevelWidget( tl->x() + moveByX , tl->y() + moveByY );
}
bool Window::targetIsExistingWindow(KHTMLPart* ourPart, const QString& frameName)
{
QString normalized = frameName.toLower();
if (normalized == "_top" || normalized == "_self" || normalized == "_parent")
return true;
// Find the highest parent part we can access.
KHTMLPart* p = ourPart;
while (p->parentPart() && p->parentPart()->checkFrameAccess(ourPart))
p = p->parentPart();
return p->findFrame(frameName);
}
JSValue *Window::openWindow(ExecState *exec, const List& args)
{
KHTMLPart *part = qobject_cast<KHTMLPart*>(m_frame->m_part);
if (!part)
return jsUndefined();
KHTMLView *widget = part->view();
JSValue *v = args[0];
QString str;
if (!v->isUndefinedOrNull())
str = v->toString(exec).qstring();
// prepare arguments
KUrl url;
if (!str.isEmpty())
{
KHTMLPart* p = qobject_cast<KHTMLPart*>(Window::retrieveActive(exec)->m_frame->m_part);
if ( p )
url = p->htmlDocument().completeURL(str).string();
if ( !p ||
!static_cast<DOM::DocumentImpl*>(p->htmlDocument().handle())->isURLAllowed(url.url()) )
return jsUndefined();
}
KHTMLSettings::KJSWindowOpenPolicy policy =
part->settings()->windowOpenPolicy(part->url().host());
QString frameName = args.size() > 1 ? args[1]->toString(exec).qstring() : QString("_blank");
// Always permit opening in an exist frame (including _self, etc.)
if ( targetIsExistingWindow( part, frameName ) )
policy = KHTMLSettings::KJSWindowOpenAllow;
if ( policy == KHTMLSettings::KJSWindowOpenAsk ) {
emit part->browserExtension()->requestFocus(part);
QString caption;
if (!part->url().host().isEmpty())
caption = part->url().host() + " - ";
caption += i18n( "Confirmation: JavaScript Popup" );
if ( KMessageBox::questionYesNo(widget,
str.isEmpty() ?
i18n( "This site is requesting to open up a new browser "
"window via JavaScript.\n"
"Do you want to allow this?" ) :
i18n( "<qt>This site is requesting to open<p>%1</p>in a new browser window via JavaScript.<br />"
"Do you want to allow this?</qt>", KStringHandler::csqueeze(Qt::escape(url.prettyUrl()), 100)),
caption, KGuiItem(i18n("Allow")), KGuiItem(i18n("Do Not Allow")) ) == KMessageBox::Yes )
policy = KHTMLSettings::KJSWindowOpenAllow;
} else if ( policy == KHTMLSettings::KJSWindowOpenSmart )
{
// window.open disabled unless from a key/mouse event
if (static_cast<ScriptInterpreter *>(exec->dynamicInterpreter())->isWindowOpenAllowed())
policy = KHTMLSettings::KJSWindowOpenAllow;
}
v = args[2];
QString features;
if (v && v->type() != UndefinedType && v->toString(exec).size() > 0) {
features = v->toString(exec).qstring();
// Buggy scripts have ' at beginning and end, cut those
if (features.startsWith(QLatin1Char('\'')) &&
features.endsWith(QLatin1Char('\'')))
features = features.mid(1, features.length()-2);
}
if ( policy != KHTMLSettings::KJSWindowOpenAllow ) {
if ( url.isEmpty() )
part->setSuppressedPopupIndicator(true, 0);
else {
part->setSuppressedPopupIndicator(true, part);
m_suppressedWindowInfo.append( SuppressedWindowInfo( url, frameName, features ) );
}
return jsUndefined();
} else {
return executeOpenWindow(exec, url, frameName, features);
}
}
JSValue *Window::executeOpenWindow(ExecState *exec, const KUrl& url, const QString& frameName, const QString& features)
{
KHTMLPart *p = qobject_cast<KHTMLPart *>(m_frame->m_part);
KHTMLView *widget = p->view();
KParts::WindowArgs winargs;
// Split on commas and syntactic whitespace
// Testcase: 'height=600, width=950 left = 30,top = 50,statusbar=0'
static const QRegExp m(",|\\b\\s+(?!=)");
// scan feature argument
if (!features.isEmpty()) {
// specifying window params means false defaults
winargs.setMenuBarVisible(false);
winargs.setToolBarsVisible(false);
winargs.setStatusBarVisible(false);
winargs.setScrollBarsVisible(false);
const QStringList flist = features.trimmed().split(m);
QStringList::ConstIterator it = flist.begin();
while (it != flist.end()) {
QString s = *it++;
QString key, val;
int pos = s.indexOf('=');
if (pos >= 0) {
key = s.left(pos).trimmed().toLower();
val = s.mid(pos + 1).trimmed().toLower();
QRect screen = KGlobalSettings::desktopGeometry(widget->topLevelWidget());
if (key == "left" || key == "screenx") {
winargs.setX((int)val.toFloat() + screen.x());
if (winargs.x() < screen.x() || winargs.x() > screen.right())
winargs.setX(screen.x()); // only safe choice until size is determined
} else if (key == "top" || key == "screeny") {
winargs.setY((int)val.toFloat() + screen.y());
if (winargs.y() < screen.y() || winargs.y() > screen.bottom())
winargs.setY(screen.y()); // only safe choice until size is determined
} else if (key == "height") {
winargs.setHeight((int)val.toFloat() + 2*qApp->style()->pixelMetric( QStyle::PM_DefaultFrameWidth ) + 2);
if (winargs.height() > screen.height()) // should actually check workspace
winargs.setHeight(screen.height());
if (winargs.height() < 100)
winargs.setHeight(100);
} else if (key == "width") {
winargs.setWidth((int)val.toFloat() + 2*qApp->style()->pixelMetric( QStyle::PM_DefaultFrameWidth ) + 2);
if (winargs.width() > screen.width()) // should actually check workspace
winargs.setWidth(screen.width());
if (winargs.width() < 100)
winargs.setWidth(100);
} else {
goto boolargs;
}
continue;
} else {
// leaving away the value gives true
key = s.trimmed().toLower();
val = "1";
}
boolargs:
if (key == "menubar")
winargs.setMenuBarVisible(val == "1" || val == "yes");
else if (key == "toolbar")
winargs.setToolBarsVisible(val == "1" || val == "yes");
else if (key == "location") // ### missing in WindowArgs
winargs.setToolBarsVisible(val == "1" || val == "yes");
else if (key == "status" || key == "statusbar")
winargs.setStatusBarVisible(val == "1" || val == "yes");
else if (key == "scrollbars")
winargs.setScrollBarsVisible(val == "1" || val == "yes");
else if (key == "resizable")
winargs.setResizable(val == "1" || val == "yes");
else if (key == "fullscreen")
winargs.setFullScreen(val == "1" || val == "yes");
}
}
KParts::OpenUrlArguments args;
KParts::BrowserArguments browserArgs;
browserArgs.frameName = frameName;
if ( browserArgs.frameName.toLower() == "_top" )
{
while ( p->parentPart() )
p = p->parentPart();
Window::retrieveWindow(p)->goURL(exec, url.url(), false /*don't lock history*/);
return Window::retrieve(p);
}
if ( browserArgs.frameName.toLower() == "_parent" )
{
if ( p->parentPart() )
p = p->parentPart();
Window::retrieveWindow(p)->goURL(exec, url.url(), false /*don't lock history*/);
return Window::retrieve(p);
}
if ( browserArgs.frameName.toLower() == "_self")
{
Window::retrieveWindow(p)->goURL(exec, url.url(), false /*don't lock history*/);
return Window::retrieve(p);
}
if ( browserArgs.frameName.toLower() == "replace" )
{
Window::retrieveWindow(p)->goURL(exec, url.url(), true /*lock history*/);
return Window::retrieve(p);
}
args.setMimeType("text/html");
args.setActionRequestedByUser(false);
// request window (new or existing if framename is set)
KParts::ReadOnlyPart *newPart = 0;
emit p->browserExtension()->createNewWindow(KUrl(), args, browserArgs, winargs, &newPart);
if (newPart && qobject_cast<KHTMLPart*>(newPart)) {
KHTMLPart *khtmlpart = static_cast<KHTMLPart*>(newPart);
//qDebug("opener set to %p (this Window's part) in new Window %p (this Window=%p)",part,win,window);
khtmlpart->setOpener(p);
khtmlpart->setOpenedByJS(true);
if (khtmlpart->document().isNull()) {
khtmlpart->begin();
khtmlpart->write("<HTML><BODY>");
khtmlpart->end();
if ( p->docImpl() ) {
//kDebug(6070) << "Setting domain to " << p->docImpl()->domain().string();
khtmlpart->docImpl()->setOrigin( p->docImpl()->origin());
khtmlpart->docImpl()->setBaseURL( p->docImpl()->baseURL() );
}
}
args.setMimeType(QString());
if (browserArgs.frameName.toLower() == "_blank")
browserArgs.frameName.clear();
if (!url.isEmpty())
emit khtmlpart->browserExtension()->openUrlRequest(url, args, browserArgs);
return Window::retrieve(khtmlpart); // global object
} else
return jsUndefined();
}
void Window::forgetSuppressedWindows()
{
m_suppressedWindowInfo.clear();
}
void Window::showSuppressedWindows()
{
KHTMLPart *part = qobject_cast<KHTMLPart*>(m_frame->m_part);
KJS::Interpreter *interpreter = part->jScript()->interpreter();
ExecState *exec = interpreter->globalExec();
QList<SuppressedWindowInfo> suppressedWindowInfo = m_suppressedWindowInfo;
m_suppressedWindowInfo.clear();
foreach ( const SuppressedWindowInfo &info, suppressedWindowInfo ) {
executeOpenWindow(exec, info.url, info.frameName, info.features);
}
}
class DelayedClose: public Window::DelayedAction {
public:
virtual bool execute(Window* win) {
win->scheduleClose();
return false;
}
private:
int steps;
};
JSValue *WindowFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args)
{
KJS_CHECK_THIS( Window, thisObj );
// these should work no matter whether the window is already
// closed or not
if (id == Window::ValueOf || id == Window::ToString) {
return jsString("[object Window]");
}
Window *window = static_cast<Window *>(thisObj);
QString str, str2;
KHTMLPart *part = qobject_cast<KHTMLPart*>(window->m_frame->m_part);
if (!part)
return jsUndefined();
KHTMLView *widget = part->view();
JSValue *v = args[0];
UString s;
if (!v->isUndefinedOrNull()) {
s = v->toString(exec);
str = s.qstring();
}
QString caption;
if (part && !part->url().host().isEmpty())
caption = part->url().host() + " - ";
caption += "JavaScript"; // TODO: i18n
// functions that work everywhere
switch(id) {
case Window::Alert: {
TimerPauser pause(exec);
if (!widget->dialogsAllowed())
return jsUndefined();
if ( part && part->xmlDocImpl() )
part->xmlDocImpl()->updateRendering();
if ( part )
emit part->browserExtension()->requestFocus(part);
KMessageBox::error(widget, Qt::convertFromPlainText(str, Qt::WhiteSpaceNormal), caption);
return jsUndefined();
}
case Window::Confirm: {
TimerPauser pause(exec);
if (!widget->dialogsAllowed())
return jsUndefined();
if ( part && part->xmlDocImpl() )
part->xmlDocImpl()->updateRendering();
if ( part )
emit part->browserExtension()->requestFocus(part);
return jsBoolean((KMessageBox::warningYesNo(widget, Qt::convertFromPlainText(str), caption,
KStandardGuiItem::ok(), KStandardGuiItem::cancel()) == KMessageBox::Yes));
}
case Window::Prompt: {
TimerPauser pause(exec);
#ifndef KONQ_EMBEDDED
if (!widget->dialogsAllowed())
return jsUndefined();
if ( part && part->xmlDocImpl() )
part->xmlDocImpl()->updateRendering();
if ( part )
emit part->browserExtension()->requestFocus(part);
bool ok;
QRegExpValidator validator(0);
if (args.size() >= 2)
str2 = KInputDialog::getText(caption,
Qt::convertFromPlainText(str),
args[1]->toString(exec).qstring(), &ok, widget, &validator);
else
str2 = KInputDialog::getText(caption,
Qt::convertFromPlainText(str),
QString(), &ok, widget, &validator);
if ( ok )
return jsString(UString(str2));
else
return jsNull();
#else
return jsUndefined();
#endif
}
case Window::GetComputedStyle: {
if ( !part || !part->xmlDocImpl() )
return jsUndefined();
DOM::NodeImpl* arg0 = toNode(args[0]);
if (!arg0 || arg0->nodeType() != DOM::Node::ELEMENT_NODE)
return jsUndefined(); // throw exception?
else
return getDOMCSSStyleDeclaration(exec, part->xmlDocImpl()->defaultView()->getComputedStyle(
static_cast<DOM::ElementImpl*>(arg0), args[1]->toString(exec).domString().implementation()));
}
case Window::Open:
return window->openWindow(exec, args);
case Window::Close: {
/* From http://developer.netscape.com/docs/manuals/js/client/jsref/window.htm :
The close method closes only windows opened by JavaScript using the open method.
If you attempt to close any other window, a confirm is generated, which
lets the user choose whether the window closes.
This is a security feature to prevent "mail bombs" containing self.close().
However, if the window has only one document (the current one) in its
session history, the close is allowed without any confirm. This is a
special case for one-off windows that need to open other windows and
then dispose of themselves.
*/
bool doClose = false;
if (!part->openedByJS())
{
// To conform to the SPEC, we only ask if the window
// has more than one entry in the history (NS does that too).
History history(exec,part);
if ( history.get( exec, "length" )->toInt32(exec) <= 1 )
{
doClose = true;
}
else
{
// Can we get this dialog with tabs??? Does it close the window or the tab in that case?
emit part->browserExtension()->requestFocus(part);
if ( KMessageBox::questionYesNo( window->part()->widget(),
i18n("Close window?"), i18n("Confirmation Required"),
KStandardGuiItem::close(), KStandardGuiItem::cancel() )
== KMessageBox::Yes )
doClose = true;
}
}
else
doClose = true;
if (doClose)
{
// If this is the current window (the one the interpreter runs in),
// then schedule a delayed close (so that the script terminates first).
// But otherwise, close immediately. This fixes w=window.open("","name");w.close();window.open("name");
if ( Window::retrieveActive(exec) == window ) {
if (widget) {
// quit all dialogs of this view
// this fixes 'setTimeout('self.close()',1000); alert("Hi");' crash
widget->closeChildDialogs();
}
//kDebug() << "scheduling delayed close";
// We'll close the window at the end of the script execution
Window* w = const_cast<Window*>(window);
w->m_delayed.append( new DelayedClose );
} else {
//kDebug() << "closing NOW";
(const_cast<Window*>(window))->closeNow();
}
}
return jsUndefined();
}
case Window::GetSelection:
return new KJS::DOMSelection(exec, part->xmlDocImpl());
case Window::Navigate:
window->goURL(exec, args[0]->toString(exec).qstring(), false /*don't lock history*/);
return jsUndefined();
case Window::Focus: {
KHTMLSettings::KJSWindowFocusPolicy policy =
part->settings()->windowFocusPolicy(part->url().host());
if(policy == KHTMLSettings::KJSWindowFocusAllow && widget) {
widget->topLevelWidget()->raise();
#ifdef Q_WS_X11
KWindowSystem::unminimizeWindow( widget->topLevelWidget()->winId() );
#else
//TODO
#endif
widget->activateWindow();
emit part->browserExtension()->requestFocus(part);
}
return jsUndefined();
}
case Window::Blur:
// TODO
return jsUndefined();
case Window::BToA:
case Window::AToB: {
if (!s.is8Bit())
return jsUndefined();
QByteArray in, out;
char *binData = s.ascii();
in = QByteArray( binData, s.size() );
if (id == Window::AToB)
out = QByteArray::fromBase64(in);
else
out = in.toBase64();
UChar *d = new UChar[out.size()];
for (int i = 0; i < out.size(); i++)
d[i].uc = (uchar) out[i];
UString ret(d, out.size(), false /*no copy*/);
return jsString(ret);
}
case Window::PostMessage: {
// Get our own origin.
if (!part->xmlDocImpl()) {
setDOMException(exec, DOM::DOMException::SECURITY_ERR);
return jsUndefined();
}
QString sourceOrigin = part->xmlDocImpl()->origin()->toString();
QString targetOrigin = args[1]->toString(exec).qstring();
KUrl targetURL(targetOrigin);
kDebug(6070) << "postMessage targetting:" << targetOrigin;
// Make sure we get * or an absolute URL for target origin
if (targetOrigin != QLatin1String("*") &&
! (targetURL.isValid() && !targetURL.isRelative() && !targetURL.isEmpty())) {
setDOMException(exec, DOM::DOMException::SYNTAX_ERR);
return jsUndefined();
}
// Grab a snapshot of the data. Unfortunately it means we copy it
// twice, but it's simpler than having separate code for swizzling
// prototype pointers.
JSValue* payload = cloneData(exec, args[0]);
// Queue the actual action, for after script execution.
window->m_delayed.append(new DelayedPostMessage(part, sourceOrigin, targetOrigin, payload));
}
};
// now unsafe functions..
if (!window->isSafeScript(exec))
return jsUndefined();
switch (id) {
case Window::ScrollBy:
if(args.size() == 2 && widget)
widget->scrollBy(args[0]->toInt32(exec), args[1]->toInt32(exec));
return jsUndefined();
case Window::Scroll:
case Window::ScrollTo:
if(args.size() == 2 && widget)
widget->setContentsPos(args[0]->toInt32(exec), args[1]->toInt32(exec));
return jsUndefined();
case Window::MoveBy: {
KHTMLSettings::KJSWindowMovePolicy policy =
part->settings()->windowMovePolicy(part->url().host());
if(policy == KHTMLSettings::KJSWindowMoveAllow && args.size() == 2 && widget)
{
KParts::BrowserExtension *ext = part->browserExtension();
if (ext) {
QWidget * tl = widget->topLevelWidget();
QRect sg = KGlobalSettings::desktopGeometry(tl);
QPoint dest = tl->pos() + QPoint( args[0]->toInt32(exec), args[1]->toInt32(exec) );
// Security check (the spec talks about UniversalBrowserWrite to disable this check...)
if ( dest.x() >= sg.x() && dest.y() >= sg.x() &&
dest.x()+tl->width() <= sg.width()+sg.x() &&
dest.y()+tl->height() <= sg.height()+sg.y() )
emit ext->moveTopLevelWidget( dest.x(), dest.y() );
}
}
return jsUndefined();
}
case Window::MoveTo: {
KHTMLSettings::KJSWindowMovePolicy policy =
part->settings()->windowMovePolicy(part->url().host());
if(policy == KHTMLSettings::KJSWindowMoveAllow && args.size() == 2 && widget)
{
KParts::BrowserExtension *ext = part->browserExtension();
if (ext) {
QWidget * tl = widget->topLevelWidget();
QRect sg = KGlobalSettings::desktopGeometry(tl);
QPoint dest( args[0]->toInt32(exec)+sg.x(), args[1]->toInt32(exec)+sg.y() );
// Security check (the spec talks about UniversalBrowserWrite to disable this check...)
if ( dest.x() >= sg.x() && dest.y() >= sg.y() &&
dest.x()+tl->width() <= sg.width()+sg.x() &&
dest.y()+tl->height() <= sg.height()+sg.y() )
emit ext->moveTopLevelWidget( dest.x(), dest.y() );
}
}
return jsUndefined();
}
case Window::ResizeBy: {
KHTMLSettings::KJSWindowResizePolicy policy =
part->settings()->windowResizePolicy(part->url().host());
if(policy == KHTMLSettings::KJSWindowResizeAllow
&& args.size() == 2 && widget)
{
QWidget * tl = widget->topLevelWidget();
QRect geom = tl->frameGeometry();
window->resizeTo( tl,
geom.width() + args[0]->toInt32(exec),
geom.height() + args[1]->toInt32(exec) );
}
return jsUndefined();
}
case Window::ResizeTo: {
KHTMLSettings::KJSWindowResizePolicy policy =
part->settings()->windowResizePolicy(part->url().host());
if(policy == KHTMLSettings::KJSWindowResizeAllow
&& args.size() == 2 && widget)
{
QWidget * tl = widget->topLevelWidget();
window->resizeTo( tl, args[0]->toInt32(exec), args[1]->toInt32(exec) );
}
return jsUndefined();
}
case Window::SetTimeout:
case Window::SetInterval: {
bool singleShot;
int i; // timeout interval
if (args.size() == 0)
return jsUndefined();
if (args.size() > 1) {
singleShot = (id == Window::SetTimeout);
i = args[1]->toInt32(exec);
} else {
// second parameter is missing. Emulate Mozilla behavior.
singleShot = true;
i = 4;
}
if (v->type() == StringType) {
int r = (const_cast<Window*>(window))->winq->installTimeout(Identifier(s), i, singleShot );
return jsNumber(r);
}
else if (v->type() == ObjectType && v->getObject()->implementsCall()) {
JSObject *func = v->getObject();
List funcArgs;
ListIterator it = args.begin();
int argno = 0;
while (it != args.end()) {
JSValue *arg = it++;
if (argno++ >= 2)
funcArgs.append(arg);
}
if (args.size() < 2)
funcArgs.append(jsNumber(i));
int r = (const_cast<Window*>(window))->winq->installTimeout(func, funcArgs, i, singleShot );
return jsNumber(r);
}
else
return jsUndefined();
}
case Window::ClearTimeout:
case Window::ClearInterval:
(const_cast<Window*>(window))->winq->clearTimeout(v->toInt32(exec));
return jsUndefined();
case Window::Print:
if ( widget ) {
// ### TODO emit onbeforeprint event
widget->print();
// ### TODO emit onafterprint event
}
case Window::CaptureEvents:
case Window::ReleaseEvents:
// Do nothing for now. These are NS-specific legacy calls.
break;
case Window::AddEventListener: {
JSEventListener *listener = Window::retrieveActive(exec)->getJSEventListener(args[1]);
if (listener) {
DOM::DocumentImpl* docimpl = static_cast<DOM::DocumentImpl *>(part->document().handle());
if (docimpl)
docimpl->addWindowEventListener(EventName::fromString(args[0]->toString(exec).domString()),listener,args[2]->toBoolean(exec));
else
kWarning() << "document missing on Window::AddEventListener. why?";
}
return jsUndefined();
}
case Window::RemoveEventListener: {
JSEventListener *listener = Window::retrieveActive(exec)->getJSEventListener(args[1]);
if (listener) {
DOM::DocumentImpl* docimpl = static_cast<DOM::DocumentImpl *>(part->document().handle());
if (docimpl)
docimpl->removeWindowEventListener(EventName::fromString(args[0]->toString(exec).domString()),listener,args[2]->toBoolean(exec));
else
kWarning() << "document missing on Window::RemoveEventListener. why?";
}
return jsUndefined();
}
}
return jsUndefined();
}
////////////////////// ScheduledAction ////////////////////////
// KDE 4: Make those parameters const ... &
ScheduledAction::ScheduledAction(JSObject* _func, List _args, DateTimeMS _nextTime, int _interval, bool _singleShot,
int _timerId)
{
//kDebug(6070) << "ScheduledAction::ScheduledAction(isFunction) " << this;
func = static_cast<JSObject*>(_func);
args = _args;
isFunction = true;
singleShot = _singleShot;
nextTime = _nextTime;
interval = _interval;
executing = false;
timerId = _timerId;
}
// KDE 4: Make it const QString &
ScheduledAction::ScheduledAction(QString _code, DateTimeMS _nextTime, int _interval, bool _singleShot, int _timerId)
{
//kDebug(6070) << "ScheduledAction::ScheduledAction(!isFunction) " << this;
//func = 0;
//args = 0;
func = 0;
code = _code;
isFunction = false;
singleShot = _singleShot;
nextTime = _nextTime;
interval = _interval;
executing = false;
timerId = _timerId;
}
bool ScheduledAction::execute(Window *window)
{
KHTMLPart *part = qobject_cast<KHTMLPart*>(window->m_frame->m_part);
if (!part || !part->jScriptEnabled())
return false;
ScriptInterpreter *interpreter = static_cast<ScriptInterpreter *>(part->jScript()->interpreter());
interpreter->setProcessingTimerCallback(true);
//kDebug(6070) << "ScheduledAction::execute " << this;
if (isFunction) {
if (func->implementsCall()) {
// #### check this
Q_ASSERT( part );
if ( part )
{
KJS::Interpreter *interpreter = part->jScript()->interpreter();
ExecState *exec = interpreter->globalExec();
Q_ASSERT( window == interpreter->globalObject() );
JSObject *obj( window );
func->call(exec,obj,args); // note that call() creates its own execution state for the func call
if (exec->hadException())
exec->clearException();
// Update our document's rendering following the execution of the timeout callback.
part->document().updateRendering();
}
}
}
else {
part->executeScript(DOM::Node(), code);
}
interpreter->setProcessingTimerCallback(false);
return true;
}
void ScheduledAction::mark()
{
if (func && !func->marked())
func->mark();
}
ScheduledAction::~ScheduledAction()
{
args.reset();
//kDebug(6070) << "ScheduledAction::~ScheduledAction " << this;
}
////////////////////// WindowQObject ////////////////////////
WindowQObject::WindowQObject(Window *w)
: parent(w)
{
//kDebug(6070) << "WindowQObject::WindowQObject " << this;
if ( !parent->m_frame )
kDebug(6070) << "WARNING: null part in " ;
else
connect( parent->m_frame, SIGNAL( destroyed() ),
this, SLOT( parentDestroyed() ) );
pauseLevel = 0;
lastTimerId = 0;
currentlyDispatching = false;
}
WindowQObject::~WindowQObject()
{
//kDebug(6070) << "WindowQObject::~WindowQObject " << this;
parentDestroyed(); // reuse same code
}
void WindowQObject::parentDestroyed()
{
killTimers();
while (!scheduledActions.isEmpty())
delete scheduledActions.takeFirst();
scheduledActions.clear();
}
void WindowQObject::pauseTimers()
{
++pauseLevel;
if (pauseLevel == 1)
pauseStart = DateTimeMS::now();
}
void WindowQObject::resumeTimers()
{
if (pauseLevel == 1) {
// Adjust all timers by the delay length, making sure there is a minimum
// margin from current time, however, so we don't go stampeding off if
// there is some unwanted recursion, etc.
DateTimeMS curTime = DateTimeMS::now();
DateTimeMS earliestDispatch = curTime.addMSecs(5);
int delay = pauseStart.msecsTo(curTime);
foreach (ScheduledAction *action, scheduledActions) {
action->nextTime = action->nextTime.addMSecs(delay);
if (earliestDispatch > action->nextTime)
action->nextTime = earliestDispatch;
}
// Dispatch any timers that may have been ignored if ::timerEvent fell in the middle
// of a pause..
timerEvent(0);
}
--pauseLevel; // We do it afterwards so that timerEvent can know about us.
}
int WindowQObject::installTimeout(const Identifier &handler, int t, bool singleShot)
{
int id = ++lastTimerId;
if (t < 10) t = 10;
DateTimeMS nextTime = DateTimeMS::now().addMSecs(t);
ScheduledAction *action = new ScheduledAction(handler.qstring(),nextTime,t,singleShot,id);
scheduledActions.append(action);
setNextTimer();
return id;
}
int WindowQObject::installTimeout(JSValue *func, List args, int t, bool singleShot)
{
JSObject *objFunc = func->getObject();
if (!objFunc)
return 0;
int id = ++lastTimerId;
if (t < 10) t = 10;
DateTimeMS nextTime = DateTimeMS::now().addMSecs(t);
ScheduledAction *action = new ScheduledAction(objFunc,args,nextTime,t,singleShot,id);
scheduledActions.append(action);
setNextTimer();
return id;
}
void WindowQObject::clearTimeout(int timerId)
{
foreach (ScheduledAction *action, scheduledActions)
{
if (action->timerId == timerId)
{
scheduledActions.removeAll(action);
if (!action->executing)
delete action;
return;
}
}
}
bool WindowQObject::hasTimers() const
{
return scheduledActions.count();
}
void WindowQObject::mark()
{
foreach (ScheduledAction *action, scheduledActions)
action->mark();
}
void WindowQObject::timerEvent(QTimerEvent *)
{
killTimers();
if (scheduledActions.isEmpty())
return;
if (pauseLevel)
return;
currentlyDispatching = true;
DateTimeMS current = DateTimeMS::now();
// Work out which actions are to be executed. We take a separate copy of
// this list since the main one may be modified during action execution
QList<ScheduledAction*> toExecute;
foreach (ScheduledAction *action, scheduledActions)
{
if (current >= action->nextTime)
toExecute.append(action);
}
// ### verify that the window can't be closed (and action deleted) during execution
foreach (ScheduledAction *action, toExecute)
{
if (!scheduledActions.count(action)) // removed by clearTimeout()
continue;
action->executing = true; // prevent deletion in clearTimeout()
if (parent->part()) {
bool ok = action->execute(parent);
if ( !ok ) // e.g. JS disabled
scheduledActions.removeAll( action );
}
if (action->singleShot)
scheduledActions.removeAll(action);
action->executing = false;
if (!scheduledActions.count(action))
delete action;
else
action->nextTime = action->nextTime.addMSecs(action->interval);
}
currentlyDispatching = false;
// Work out when next event is to occur
setNextTimer();
// unless we're inside a nested context, do post-script processing
if (!pauseLevel)
parent->afterScriptExecution();
}
DateTimeMS DateTimeMS::addMSecs(int s) const
{
DateTimeMS c = *this;
c.mTime = mTime.addMSecs(s);
if (s > 0)
{
if (c.mTime < mTime)
c.mDate = mDate.addDays(1);
}
else
{
if (c.mTime > mTime)
c.mDate = mDate.addDays(-1);
}
return c;
}
bool DateTimeMS::operator >(const DateTimeMS &other) const
{
if (mDate > other.mDate)
return true;
if (mDate < other.mDate)
return false;
return mTime > other.mTime;
}
bool DateTimeMS::operator >=(const DateTimeMS &other) const
{
if (mDate > other.mDate)
return true;
if (mDate < other.mDate)
return false;
return mTime >= other.mTime;
}
int DateTimeMS::msecsTo(const DateTimeMS &other) const
{
int d = mDate.daysTo(other.mDate);
int ms = mTime.msecsTo(other.mTime);
return d*24*60*60*1000 + ms;
}
DateTimeMS DateTimeMS::now()
{
DateTimeMS t;
QTime before = QTime::currentTime();
t.mDate = QDate::currentDate();
t.mTime = QTime::currentTime();
if (t.mTime < before)
t.mDate = QDate::currentDate(); // prevent race condition in hacky way :)
return t;
}
void WindowQObject::setNextTimer()
{
if (currentlyDispatching)
return; // Will schedule at the end
if (scheduledActions.isEmpty())
return;
QListIterator<ScheduledAction*> it(scheduledActions);
DateTimeMS nextTime = it.next()->nextTime;
while (it.hasNext())
{
const DateTimeMS& currTime = it.next()->nextTime;
if (nextTime > currTime)
nextTime = currTime;
}
int nextInterval = DateTimeMS::now().msecsTo(nextTime);
if (nextInterval < 0)
nextInterval = 0;
timerIds.append(startTimer(nextInterval));
}
void WindowQObject::killTimers()
{
for (int i = 0; i < timerIds.size(); ++i)
{
killTimer(timerIds.at(i));
}
timerIds.clear();
}
void WindowQObject::timeoutClose()
{
parent->closeNow();
}
////////////////////// Location Object ////////////////////////
const ClassInfo Location::info = { "Location", 0, &LocationTable, 0 };
/*
@begin LocationTable 11
hash Location::Hash DontDelete
host Location::Host DontDelete
hostname Location::Hostname DontDelete
href Location::Href DontDelete
pathname Location::Pathname DontDelete
port Location::Port DontDelete
protocol Location::Protocol DontDelete
search Location::Search DontDelete
[[==]] Location::EqualEqual DontDelete|ReadOnly
assign Location::Assign DontDelete|Function 1
toString Location::ToString DontDelete|Function 0
replace Location::Replace DontDelete|Function 1
reload Location::Reload DontDelete|Function 0
@end
*/
KJS_IMPLEMENT_PROTOFUNC(LocationFunc)
Location::Location(khtml::ChildFrame *f) : m_frame(f)
{
//kDebug(6070) << "Location::Location " << this << " m_part=" << (void*)m_part;
}
Location::~Location()
{
//kDebug(6070) << "Location::~Location " << this << " m_part=" << (void*)m_part;
}
KParts::ReadOnlyPart *Location::part() const {
return m_frame ? static_cast<KParts::ReadOnlyPart *>(m_frame->m_part) : 0L;
}
bool Location::getOwnPropertySlot(ExecState *exec, const Identifier &p, PropertySlot& slot)
{
#ifdef KJS_VERBOSE
kDebug(6070) << "Location::getOwnPropertySlot " << p.qstring() << " m_part=" << (void*)m_frame->m_part;
#endif
if (m_frame.isNull() || m_frame->m_part.isNull())
return jsUndefined();
const HashEntry *entry = Lookup::findEntry(&LocationTable, p);
if ( entry ) {
// properties that work on all Location objects
if (entry->value == Replace) {
getSlotFromEntry<LocationFunc, Location>(entry, this, slot);
return true;
}
// XSS check
const Window* window = Window::retrieveWindow( m_frame->m_part );
if ( !window || !window->isSafeScript(exec) ) {
slot.setUndefined(this);
return true;
}
// XSS check passed - can now dispatch normally.
getSlotFromEntry<LocationFunc, Location>(entry, this, slot);
return true;
}
return JSObject::getOwnPropertySlot(exec, p, slot);
}
JSValue* Location::getValueProperty(ExecState *exec, int token) const
{
KUrl url = m_frame->m_part->url();
switch(token) {
case Hash:
return jsString( UString(url.htmlRef().isNull() ? QString("") : '#' + url.htmlRef()) );
case Host: {
UString str = url.host();
if (url.port() > 0)
str += QString(QLatin1Char(':') + QString::number((int)url.port()));
return jsString(str);
// Note: this is the IE spec. The NS spec swaps the two, it says
// "The hostname property is the concatenation of the host and port properties, separated by a colon."
// Bleh.
}
case Hostname:
return jsString( UString(url.host()) );
case Href:
if (url.isEmpty())
return jsString("about:blank");
else if (!url.hasPath())
return jsString( UString(url.prettyUrl()+'/') );
else
return jsString( UString(url.prettyUrl()) );
case Pathname:
if (url.isEmpty())
return jsString("");
return jsString( UString(url.path().isEmpty() ? QString("/") : url.path()) );
case Port:
return jsString( UString(url.port() > 0 ? QString::number((int)url.port()) : QLatin1String("")) );
case Protocol:
- return jsString( UString(url.protocol()+':') );
+ return jsString( UString(url.scheme()+':') );
case Search:
return jsString( UString(url.query()) );
case EqualEqual: // [[==]]
return jsString(toString(exec));
}
return jsUndefined();
}
void Location::put(ExecState *exec, const Identifier &p, JSValue *v, int attr)
{
#ifdef KJS_VERBOSE
kDebug(6070) << "Location::put " << p.qstring() << " m_part=" << (void*)m_frame->m_part;
#endif
if (m_frame.isNull() || m_frame->m_part.isNull())
return;
const Window* window = Window::retrieveWindow( m_frame->m_part );
if ( !window )
return;
KUrl url = m_frame->m_part->url();
const HashEntry *entry = Lookup::findEntry(&LocationTable, p);
if (entry) {
// XSS check. Only new hrefs can be set from other sites
if (entry->value != Href && !window->isSafeScript(exec))
return;
QString str = v->toString(exec).qstring();
switch (entry->value) {
case Href: {
KHTMLPart* p =qobject_cast<KHTMLPart*>(Window::retrieveActive(exec)->part());
if ( p )
url = p->htmlDocument().completeURL( str ).string();
else
url = str;
break;
}
case Hash:
// Strip any leading # --- setting hash to #foo is the same as setting it to foo.
if (str.startsWith(QLatin1Char('#')))
str = str.mid(1);
// Note that we want to do gotoAnchor even when the hash is already set, so we
// scroll the destination into view.
// Setting this must always provide a ref, even if just ; see
// HTML5 2.6.
if (str.isEmpty()) {
url.setHTMLRef("");
} else {
url.setRef(str);
}
break;
case Host: {
QString host = str.left(str.indexOf(":"));
QString port = str.mid(str.indexOf(":")+1);
url.setHost(host);
url.setPort(port.toUInt());
break;
}
case Hostname:
url.setHost(str);
break;
case Pathname:
url.setPath(str);
break;
case Port:
url.setPort(str.toUInt());
break;
case Protocol:
url.setProtocol(str);
break;
case Search:
url.setQuery(str);
break;
}
} else {
JSObject::put(exec, p, v, attr);
return;
}
Window::retrieveWindow(m_frame->m_part)->goURL(exec, url.url(), false /* don't lock history*/ );
}
JSValue *Location::toPrimitive(ExecState *exec, JSType) const
{
if (m_frame) {
Window* window = Window::retrieveWindow( m_frame->m_part );
if ( window && window->isSafeScript(exec) )
return jsString(toString(exec));
}
return jsUndefined();
}
UString Location::toString(ExecState *exec) const
{
if (m_frame) {
Window* window = Window::retrieveWindow( m_frame->m_part );
if ( window && window->isSafeScript(exec) )
{
KUrl url = m_frame->m_part->url();
if (url.isEmpty())
return "about:blank";
else if (!url.hasPath())
return QString(url.prettyUrl()+'/');
else
return url.prettyUrl();
}
}
return "";
}
JSValue *LocationFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args)
{
KJS_CHECK_THIS( Location, thisObj );
Location *location = static_cast<Location *>(thisObj);
KParts::ReadOnlyPart *part = location->part();
if (!part) return jsUndefined();
Window* window = Window::retrieveWindow(part);
if ( !window->isSafeScript(exec) && id != Location::Replace)
return jsUndefined();
switch (id) {
case Location::Assign:
case Location::Replace:
Window::retrieveWindow(part)->goURL(exec, args[0]->toString(exec).qstring(),
id == Location::Replace);
break;
case Location::Reload: {
KHTMLPart *khtmlpart = qobject_cast<KHTMLPart*>(part);
if (khtmlpart)
khtmlpart->scheduleRedirection(-1, part->url().url(), true/*lock history*/);
else
part->openUrl(part->url());
break;
}
case Location::ToString:
return jsString(location->toString(exec));
}
return jsUndefined();
}
////////////////////// External Object ////////////////////////
const ClassInfo External::info = { "External", 0, 0, 0 };
/*
@begin ExternalTable 4
addFavorite External::AddFavorite DontDelete|Function 1
@end
*/
KJS_IMPLEMENT_PROTOFUNC(ExternalFunc)
bool External::getOwnPropertySlot(ExecState *exec, const Identifier &p, PropertySlot& propertySlot)
{
return getStaticFunctionSlot<ExternalFunc,JSObject>(exec, &ExternalTable, this, p, propertySlot);
}
JSValue *ExternalFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args)
{
KJS_CHECK_THIS( External, thisObj );
External *external = static_cast<External *>(thisObj);
KHTMLPart *part = external->part;
if (!part)
return jsUndefined();
switch (id) {
case External::AddFavorite:
{
#ifndef KONQ_EMBEDDED
KHTMLView *widget = part->view();
if (!widget->dialogsAllowed())
return jsUndefined();
part->xmlDocImpl()->updateRendering();
if (args.size() != 1 && args.size() != 2)
return jsUndefined();
QString url = args[0]->toString(exec).qstring();
QString title;
if (args.size() == 2)
title = args[1]->toString(exec).qstring();
// AK - don't do anything yet, for the moment i
// just wanted the base js handling code in cvs
return jsUndefined();
QString question;
if ( title.isEmpty() )
question = i18n("Do you want a bookmark pointing to the location \"%1\" to be added to your collection?",
url);
else
question = i18n("Do you want a bookmark pointing to the location \"%1\" titled \"%2\" to be added to your collection?",
url, title);
emit part->browserExtension()->requestFocus(part);
QString caption;
if (!part->url().host().isEmpty())
caption = part->url().host() + " - ";
caption += i18n("JavaScript Attempted Bookmark Insert");
if (KMessageBox::warningYesNo(
widget, question, caption,
KGuiItem(i18n("Insert")), KGuiItem(i18n("Disallow"))) == KMessageBox::Yes)
{
KBookmarkManager *mgr = KBookmarkManager::userBookmarksManager();
KBookmarkDialog dlg(mgr, 0);
dlg.addBookmark(title, url);
}
#else
return jsUndefined();
#endif
break;
}
default:
return jsUndefined();
}
return jsUndefined();
}
////////////////////// History Object ////////////////////////
const ClassInfo History::info = { "History", 0, 0, 0 };
/*
@begin HistoryTable 4
length History::Length DontDelete|ReadOnly
back History::Back DontDelete|Function 0
forward History::Forward DontDelete|Function 0
go History::Go DontDelete|Function 1
@end
*/
KJS_IMPLEMENT_PROTOFUNC(HistoryFunc)
bool History::getOwnPropertySlot(ExecState *exec, const Identifier &p, PropertySlot& slot)
{
return getStaticPropertySlot<HistoryFunc,History,JSObject>(exec, &HistoryTable, this, p, slot);
}
JSValue *History::getValueProperty(ExecState *, int token) const
{
// if previous or next is implemented, make sure it is not a major
// privacy leak (see i.e. http://security.greymagic.com/adv/gm005-op/)
switch (token) {
case Length:
{
if ( !part )
return jsNumber( 0 );
KParts::BrowserExtension *ext = part->browserExtension();
if ( !ext )
return jsNumber( 0 );
KParts::BrowserInterface *iface = ext->browserInterface();
if ( !iface )
return jsNumber( 0 );
QVariant length = iface->property( "historyLength" );
if ( length.type() != QVariant::UInt )
return jsNumber( 0 );
return jsNumber( length.toUInt() );
}
default:
kDebug(6070) << "WARNING: Unhandled token in History::getValueProperty : " << token;
return jsUndefined();
}
}
JSValue *HistoryFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args)
{
KJS_CHECK_THIS( History, thisObj );
History *history = static_cast<History *>(thisObj);
JSValue *v = args[0];
double n = 0.0;
if(v)
n = v->toInteger(exec);
int steps;
switch (id) {
case History::Back:
steps = -1;
break;
case History::Forward:
steps = 1;
break;
case History::Go:
steps = (int)n;
break;
default:
return jsUndefined();
}
// Special case for go(0) from a frame -> reload only the frame
// go(i!=0) from a frame navigates into the history of the frame only,
// in both IE and NS (but not in Mozilla).... we can't easily do that
// in Konqueror...
if (!steps) // add && history->part->parentPart() to get only frames, but doesn't matter
{
history->part->openUrl( history->part->url() ); /// ## need args.reload=true?
} else
{
// Delay it.
// Testcase: history.back(); alert("hello");
Window* window = Window::retrieveWindow( history->part );
window->delayedGoHistory( steps );
}
return jsUndefined();
}
} // namespace KJS
// kate: indent-width 4; replace-tabs on; tab-width 4; space-indent on;
diff --git a/khtml/ecma/xmlhttprequest.cpp b/khtml/ecma/xmlhttprequest.cpp
index 7ac80928d0..004547b26b 100644
--- a/khtml/ecma/xmlhttprequest.cpp
+++ b/khtml/ecma/xmlhttprequest.cpp
@@ -1,991 +1,991 @@
// -*- c-basic-offset: 2 -*-
/*
* This file is part of the KDE libraries
* Copyright (C) 2003 Apple Computer, Inc.
*
* 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 "xmlhttprequest.h"
#include "xmlhttprequest.lut.h"
#include "kjs_window.h"
#include "kjs_events.h"
#include "dom/dom_doc.h"
#include "dom/dom_exception.h"
#include "dom/dom_string.h"
#include "misc/loader.h"
#include "misc/translator.h"
#include "html/html_documentimpl.h"
#include "xml/dom2_eventsimpl.h"
#include "khtml_part.h"
#include "khtmlview.h"
#include <kio/scheduler.h>
#include <kio/job.h>
#include <QtCore/QObject>
#include <QtCore/QHash>
#include <kdebug.h>
#include <kio/netaccess.h>
using KIO::NetAccess;
using namespace KJS;
using namespace DOM;
//
////////////////////// XMLHttpRequest Object ////////////////////////
/* Source for XMLHttpRequestProtoTable.
@begin XMLHttpRequestProtoTable 7
abort XMLHttpRequest::Abort DontDelete|Function 0
getAllResponseHeaders XMLHttpRequest::GetAllResponseHeaders DontDelete|Function 0
getResponseHeader XMLHttpRequest::GetResponseHeader DontDelete|Function 1
open XMLHttpRequest::Open DontDelete|Function 5
overrideMimeType XMLHttpRequest::OverrideMIMEType DontDelete|Function 1
send XMLHttpRequest::Send DontDelete|Function 1
setRequestHeader XMLHttpRequest::SetRequestHeader DontDelete|Function 2
@end
*/
namespace KJS {
KJS_DEFINE_PROTOTYPE(XMLHttpRequestProto)
KJS_IMPLEMENT_PROTOFUNC(XMLHttpRequestProtoFunc)
KJS_IMPLEMENT_PROTOTYPE("XMLHttpRequest", XMLHttpRequestProto,XMLHttpRequestProtoFunc, ObjectPrototype)
XMLHttpRequestQObject::XMLHttpRequestQObject(XMLHttpRequest *_jsObject)
{
jsObject = _jsObject;
}
#ifdef APPLE_CHANGES
void XMLHttpRequestQObject::slotData( KIO::Job* job, const char *data, int size )
{
jsObject->slotData(job, data, size);
}
#else
void XMLHttpRequestQObject::slotData( KIO::Job* job, const QByteArray &data )
{
jsObject->slotData(job, data);
}
#endif
void XMLHttpRequestQObject::slotFinished( KJob* job )
{
jsObject->slotFinished(job);
}
void XMLHttpRequestQObject::slotRedirection( KIO::Job* job, const KUrl& url)
{
jsObject->slotRedirection( job, url );
}
XMLHttpRequestConstructorImp::XMLHttpRequestConstructorImp(ExecState *exec, DOM::DocumentImpl* d)
: JSObject(exec->lexicalInterpreter()->builtinObjectPrototype()), doc(d)
{
JSObject* proto = XMLHttpRequestProto::self(exec);
putDirect(exec->propertyNames().prototype, proto, DontDelete|ReadOnly);
}
bool XMLHttpRequestConstructorImp::implementsConstruct() const
{
return true;
}
JSObject *XMLHttpRequestConstructorImp::construct(ExecState *exec, const List &)
{
return new XMLHttpRequest(exec, doc.get());
}
const ClassInfo XMLHttpRequest::info = { "XMLHttpRequest", 0, &XMLHttpRequestTable, 0 };
/* Source for XMLHttpRequestTable.
@begin XMLHttpRequestTable 7
readyState XMLHttpRequest::ReadyState DontDelete|ReadOnly
responseText XMLHttpRequest::ResponseText DontDelete|ReadOnly
responseXML XMLHttpRequest::ResponseXML DontDelete|ReadOnly
status XMLHttpRequest::Status DontDelete|ReadOnly
statusText XMLHttpRequest::StatusText DontDelete|ReadOnly
onreadystatechange XMLHttpRequest::Onreadystatechange DontDelete
onload XMLHttpRequest::Onload DontDelete
@end
*/
bool XMLHttpRequest::getOwnPropertySlot(ExecState *exec, const Identifier& propertyName, PropertySlot& slot)
{
return getStaticValueSlot<XMLHttpRequest, DOMObject>(exec, &XMLHttpRequestTable, this, propertyName, slot);
}
JSValue *XMLHttpRequest::getValueProperty(ExecState *exec, int token) const
{
switch (token) {
case ReadyState:
return jsNumber(m_state);
case ResponseText:
return ::getStringOrNull(DOM::DOMString(response));
case ResponseXML:
if (m_state != XHRS_Loaded) {
return jsNull();
}
if (!createdDocument) {
QString mimeType = "text/xml";
if (!m_mimeTypeOverride.isEmpty()) {
mimeType = m_mimeTypeOverride;
} else {
int dummy;
JSValue *header = getResponseHeader("Content-Type", dummy);
if (header->type() != UndefinedType)
mimeType = header->toString(exec).qstring().split(";")[0].trimmed();
}
if (mimeType == "text/xml" || mimeType == "application/xml" || mimeType == "application/xhtml+xml") {
responseXML = doc->implementation()->createDocument();
responseXML->open();
responseXML->setURL(url.url());
responseXML->write(response);
responseXML->finishParsing();
responseXML->close();
typeIsXML = true;
} else {
typeIsXML = false;
}
createdDocument = true;
}
if (!typeIsXML) {
return jsNull();
}
return getDOMNode(exec,responseXML.get());
case Status:
return getStatus();
case StatusText:
return getStatusText();
case Onreadystatechange:
if (onReadyStateChangeListener && onReadyStateChangeListener->listenerObj()) {
return onReadyStateChangeListener->listenerObj();
} else {
return jsNull();
}
case Onload:
if (onLoadListener && onLoadListener->listenerObj()) {
return onLoadListener->listenerObj();
} else {
return jsNull();
}
default:
kWarning() << "XMLHttpRequest::getValueProperty unhandled token " << token;
return 0;
}
}
void XMLHttpRequest::put(ExecState *exec, const Identifier &propertyName, JSValue *value, int attr)
{
lookupPut<XMLHttpRequest,DOMObject>(exec, propertyName, value, attr, &XMLHttpRequestTable, this );
}
void XMLHttpRequest::putValueProperty(ExecState *exec, int token, JSValue *value, int /*attr*/)
{
switch(token) {
case Onreadystatechange:
if (onReadyStateChangeListener) onReadyStateChangeListener->deref();
onReadyStateChangeListener = Window::retrieveActive(exec)->getJSEventListener(value, true);
if (onReadyStateChangeListener) onReadyStateChangeListener->ref();
break;
case Onload:
if (onLoadListener) onLoadListener->deref();
onLoadListener = Window::retrieveActive(exec)->getJSEventListener(value, true);
if (onLoadListener) onLoadListener->ref();
break;
default:
kWarning() << "XMLHttpRequest::putValue unhandled token " << token;
}
}
// Token according to RFC 2616
static bool isValidFieldName(const QString& name)
{
const QChar* c = name.constData();
int l = name.length();
if (l == 0)
return false;
for (int i = 0; i < l; ++i, ++c) {
int u = c->unicode();
if (u < 32 || u > 126)
return false;
switch (u) {
case '(': case ')': case '<': case '>':
case '@': case ',': case ';': case ':':
case '\\': case '"': case '/':
case '[': case ']': case '?': case '=':
case '{': case '}': case '\t': case ' ':
return false;
default:
break;
}
}
return true;
}
static bool isValidFieldValue(const QString& name)
{
const QChar* c = name.constData();
int l = name.length();
if (l == 0)
return true;
for (int i = 0; i < l; ++i, ++c) {
int u = c->unicode();
if ( u == '\n' || u == '\r' )
return false;
}
// ### what is invalid?
return true;
}
static bool canSetRequestHeader(const QString& name)
{
static QSet<CaseInsensitiveString> forbiddenHeaders;
if (forbiddenHeaders.isEmpty()) {
static const char* const hdrs[] = {
"accept-charset",
"accept-encoding",
"content-length",
"connect",
"copy",
"date",
"delete",
"expect",
"head",
"host",
"keep-alive",
"lock",
"mkcol",
"move",
"options",
"put",
"propfind",
"proppatch",
"proxy-authorization",
"referer",
"te",
"trace",
"trailer",
"transfer-encoding",
"unlock",
"upgrade",
"via"
};
for (size_t i = 0; i < sizeof(hdrs)/sizeof(char*); ++i)
forbiddenHeaders.insert(CaseInsensitiveString(hdrs[i]));
}
return !forbiddenHeaders.contains(name);
}
XMLHttpRequest::XMLHttpRequest(ExecState *exec, DOM::DocumentImpl* d)
: qObject(new XMLHttpRequestQObject(this)),
doc(d),
async(true),
contentType(QString()),
job(0),
m_state(XHRS_Uninitialized),
onReadyStateChangeListener(0),
onLoadListener(0),
decoder(0),
binaryMode(false),
response(QString::fromLatin1("")),
createdDocument(false),
aborted(false)
{
ref(); // we're a GC point, so refcount pin.
setPrototype(XMLHttpRequestProto::self(exec));
}
XMLHttpRequest::~XMLHttpRequest()
{
if (job && m_method != QLatin1String("POST")) {
job->kill();
job = 0;
}
if (onLoadListener)
onLoadListener->deref();
if (onReadyStateChangeListener)
onReadyStateChangeListener->deref();
delete qObject;
qObject = 0;
delete decoder;
decoder = 0;
}
void XMLHttpRequest::changeState(XMLHttpRequestState newState)
{
// Other engines cancel transfer if the controlling document doesn't
// exist anymore. Match that, though being paranoid about post
// (And don't emit any events w/o a doc, if we're kept alive otherwise).
if (!doc) {
if (job && m_method != QLatin1String("POST")) {
job->kill();
job = 0;
}
return;
}
if (m_state != newState) {
m_state = newState;
ProtectedPtr<JSObject> ref(this);
if (onReadyStateChangeListener != 0 && doc->view() && doc->view()->part()) {
DOM::Event ev = doc->view()->part()->document().createEvent("HTMLEvents");
ev.initEvent("readystatechange", true, true);
ev.handle()->setTarget(this);
ev.handle()->setCurrentTarget(this);
onReadyStateChangeListener->handleEvent(ev);
// Make sure the event doesn't point to us, since it can't prevent
// us from being collecte.
ev.handle()->setTarget(0);
ev.handle()->setCurrentTarget(0);
}
if (m_state == XHRS_Loaded && onLoadListener != 0 && doc->view() && doc->view()->part()) {
DOM::Event ev = doc->view()->part()->document().createEvent("HTMLEvents");
ev.initEvent("load", true, true);
ev.handle()->setTarget(this);
ev.handle()->setCurrentTarget(this);
onLoadListener->handleEvent(ev);
ev.handle()->setTarget(0);
ev.handle()->setCurrentTarget(0);
}
}
}
bool XMLHttpRequest::urlMatchesDocumentDomain(const KUrl& _url) const
{
// No need to do work if _url is not valid...
if (!_url.isValid())
return false;
KUrl documentURL(doc->URL());
// a local file can load anything
- if (documentURL.protocol().toLower() == "file") {
+ if (documentURL.scheme().toLower() == "file") {
return true;
}
// but a remote document can only load from the same port on the server
- if (documentURL.protocol().toLower() == _url.protocol().toLower() &&
+ if (documentURL.scheme().toLower() == _url.scheme().toLower() &&
documentURL.host().toLower() == _url.host().toLower() &&
documentURL.port() == _url.port()) {
return true;
}
return false;
}
// Methods we're to recognize per the XHR spec (3.6.1, #3).
// We map it to whether the method should be permitted or not (#4)
static const IDTranslator<QByteArray, bool, const char*>::Info methodsTable[] = {
{"CONNECT", false},
{"DELETE", true},
{"GET", true},
{"HEAD", true},
{"OPTIONS", true},
{"POST", true},
{"PUT", true},
{"TRACE", false},
{"TRACK", false},
{0, false}
};
MAKE_TRANSLATOR(methodsLookup, QByteArray, bool, const char*, methodsTable)
void XMLHttpRequest::open(const QString& _method, const KUrl& _url, bool _async, int& ec)
{
abort();
aborted = false;
// clear stuff from possible previous load
m_requestHeaders.clear();
responseHeaders.clear();
response = QString::fromLatin1("");
createdDocument = false;
responseXML = 0;
if (!urlMatchesDocumentDomain(_url)) {
ec = DOMException::SECURITY_ERR;
return;
}
// ### potentially raise a SYNTAX_ERR
// Lookup if the method is well-known, and if so check if it's OK
QByteArray methodNormalized = _method.toUpper().toUtf8();
if (methodsLookup()->hasLeft(methodNormalized)) {
if (methodsLookup()->toRight(methodNormalized)) {
// OK, replace with the canonical version...
m_method = _method.toUpper();
} else {
// Scary stuff like CONNECT
ec = DOMException::SECURITY_ERR;
return;
}
} else {
// Unknown -> pass through unchanged
m_method = _method;
}
url = _url;
async = _async;
changeState(XHRS_Open);
}
void XMLHttpRequest::send(const QString& _body, int& ec)
{
aborted = false;
if (m_state != XHRS_Open) {
ec = DOMException::INVALID_STATE_ERR;
return;
}
- const QString protocol = url.protocol().toLower();
+ const QString protocol = url.scheme().toLower();
// Abandon the request when the protocol is other than "http",
// instead of blindly doing a KIO::get on other protocols like file:/.
if (!protocol.startsWith(QLatin1String("http")) &&
!protocol.startsWith(QLatin1String("webdav")))
{
ec = DOMException::INVALID_ACCESS_ERR;
abort();
return;
}
// We need to use a POST-like setup even for non-post whenever we
// have a payload.
if (m_method == QLatin1String("POST") || !_body.isEmpty()) {
// FIXME: determine post encoding correctly by looking in headers
// for charset.
QByteArray buf = _body.toUtf8();
job = KIO::http_post( url, buf, KIO::HideProgressInfo );
if(contentType.isNull())
job->addMetaData( "content-type", "Content-type: text/plain" );
else
job->addMetaData( "content-type", contentType );
}
else {
job = KIO::get( url, KIO::NoReload, KIO::HideProgressInfo );
}
// Regardless of job type, make sure the method is set
job->addMetaData("CustomHTTPMethod", m_method);
if (!m_requestHeaders.isEmpty()) {
QString rh;
HTTPHeaderMap::ConstIterator begin = m_requestHeaders.constBegin();
HTTPHeaderMap::ConstIterator end = m_requestHeaders.constEnd();
for (HTTPHeaderMap::ConstIterator i = begin; i != end; ++i) {
QString key = i.key().original();
QString value = i.value();
if (key.toLower() == "accept") {
// The HTTP KIO slave supports an override this way
job->addMetaData("accept", value);
} else {
if (!rh.isEmpty())
rh += "\r\n";
rh += key + ": " + value;
}
}
job->addMetaData("customHTTPHeader", rh);
}
job->addMetaData("PropagateHttpHeader", "true");
// Set the default referrer. NOTE: the user can still disable
// this feature at the protocol level (kio_http).
KUrl documentURL(doc->URL());
documentURL.setPass(QString());
documentURL.setUser(QString());
job->addMetaData("referrer", documentURL.url());
// kDebug() << "Adding referrer: " << documentURL;
if (!async) {
QByteArray data;
KUrl finalURL;
QString headers;
#ifdef APPLE_CHANGES
data = KWQServeSynchronousRequest(khtml::Cache::loader(), doc->docLoader(), job, finalURL, headers);
#else
QMap<QString, QString> metaData;
if ( NetAccess::synchronousRun( job, 0, &data, &finalURL, &metaData ) ) {
headers = metaData[ "HTTP-Headers" ];
}
#endif
job = 0;
processSyncLoadResults(data, finalURL, headers);
return;
}
qObject->connect( job, SIGNAL( result( KJob* ) ),
SLOT( slotFinished( KJob* ) ) );
#ifdef APPLE_CHANGES
qObject->connect( job, SIGNAL( data( KIO::Job*, const char*, int ) ),
SLOT( slotData( KIO::Job*, const char*, int ) ) );
#else
qObject->connect( job, SIGNAL( data( KIO::Job*, const QByteArray& ) ),
SLOT( slotData( KIO::Job*, const QByteArray& ) ) );
#endif
qObject->connect( job, SIGNAL(redirection(KIO::Job*, const KUrl& ) ),
SLOT( slotRedirection(KIO::Job*, const KUrl&) ) );
#ifdef APPLE_CHANGES
KWQServeRequest(khtml::Cache::loader(), doc->docLoader(), job);
#else
KIO::Scheduler::setJobPriority( job, 1 );
#endif
}
void XMLHttpRequest::clearDecoder()
{
delete decoder;
decoder = 0;
binaryMode = false;
}
void XMLHttpRequest::abort()
{
if (job) {
job->kill();
job = 0;
}
aborted = true;
clearDecoder();
changeState(XHRS_Uninitialized);
}
void XMLHttpRequest::overrideMIMEType(const QString& override)
{
m_mimeTypeOverride = override;
}
void XMLHttpRequest::setRequestHeader(const QString& _name, const QString& _value, int& ec)
{
// throw exception if connection is not open or the send flag is set
if (m_state != XHRS_Open || job != 0) {
ec = DOMException::INVALID_STATE_ERR;
return;
}
if (!isValidFieldName(_name) || !isValidFieldValue(_value)) {
ec = DOMException::SYNTAX_ERR;
return;
}
QString name = _name.toLower();
QString value = _value.trimmed();
if (value.isEmpty())
return;
// Content-type needs to be set separately from the other headers
if(name == "content-type") {
contentType = "Content-type: " + value;
return;
}
// Sanitize the referrer header to protect against spoofing...
if(name == "referer") {
KUrl referrerURL(value);
if (urlMatchesDocumentDomain(referrerURL))
m_requestHeaders[name] = referrerURL.url();
return;
}
// Sanitize the request headers below and handle them as if they are
// calls to open. Otherwise, we will end up ignoring them all together!
// TODO: Do something about "put" which kio_http sort of supports and
// the webDAV headers such as PROPFIND etc...
if (name == "get" || name == "post") {
KUrl reqURL (doc->URL(), value.trimmed());
open(name, reqURL, async, ec);
return;
}
// Reject all banned headers.
if (!canSetRequestHeader(_name)) {
kWarning(6070) << "Refusing to set unsafe XMLHttpRequest header "
<< name << endl;
return;
}
m_requestHeaders[_name] = value;
}
JSValue *XMLHttpRequest::getAllResponseHeaders(int& ec) const
{
if (m_state < XHRS_Receiving) {
ec = DOMException::INVALID_STATE_ERR;
return jsString("");
}
// ### test error flag, return jsNull
if (responseHeaders.isEmpty()) {
return jsUndefined();
}
int endOfLine = responseHeaders.indexOf("\n");
if (endOfLine == -1) {
return jsUndefined();
}
return jsString(responseHeaders.mid(endOfLine + 1) + '\n');
}
JSValue *XMLHttpRequest::getResponseHeader(const QString& name, int& ec) const
{
if (m_state < XHRS_Receiving) {
ec = DOMException::INVALID_STATE_ERR;
return jsString("");
}
if (!isValidFieldName(name)) {
return jsString("");
}
// ### test error flag, return jsNull
if (responseHeaders.isEmpty()) {
return jsUndefined();
}
QRegExp headerLinePattern(name + ':', Qt::CaseInsensitive);
int matchLength;
int headerLinePos = headerLinePattern.indexIn(responseHeaders, 0);
matchLength = headerLinePattern.matchedLength();
while (headerLinePos != -1) {
if (headerLinePos == 0 || responseHeaders[headerLinePos-1] == '\n') {
break;
}
headerLinePos = headerLinePattern.indexIn(responseHeaders, headerLinePos + 1);
matchLength = headerLinePattern.matchedLength();
}
if (headerLinePos == -1) {
return jsNull();
}
int endOfLine = responseHeaders.indexOf("\n", headerLinePos + matchLength);
return jsString(responseHeaders.mid(headerLinePos + matchLength, endOfLine - (headerLinePos + matchLength)).trimmed());
}
static JSValue *httpStatus(const QString& response, bool textStatus = false)
{
if (response.isEmpty()) {
return jsUndefined();
}
int endOfLine = response.indexOf("\n");
QString firstLine = (endOfLine == -1) ? response : response.left(endOfLine);
int codeStart = firstLine.indexOf(" ");
int codeEnd = firstLine.indexOf(" ", codeStart + 1);
if (codeStart == -1 || codeEnd == -1) {
return jsUndefined();
}
if (textStatus) {
QString statusText = firstLine.mid(codeEnd + 1, endOfLine - (codeEnd + 1)).trimmed();
return jsString(statusText);
}
QString number = firstLine.mid(codeStart + 1, codeEnd - (codeStart + 1));
bool ok = false;
int code = number.toInt(&ok);
if (!ok) {
return jsUndefined();
}
return jsNumber(code);
}
JSValue *XMLHttpRequest::getStatus() const
{
return httpStatus(responseHeaders);
}
JSValue *XMLHttpRequest::getStatusText() const
{
return httpStatus(responseHeaders, true);
}
void XMLHttpRequest::processSyncLoadResults(const QByteArray &data, const KUrl &finalURL, const QString &headers)
{
if (!urlMatchesDocumentDomain(finalURL)) {
abort();
return;
}
responseHeaders = headers;
changeState(XHRS_Sent);
if (aborted) {
return;
}
#ifdef APPLE_CHANGES
const char *bytes = (const char *)data.data();
int len = (int)data.size();
slotData(0, bytes, len);
#else
slotData(0, data);
#endif
if (aborted) {
return;
}
slotFinished(0);
}
void XMLHttpRequest::slotFinished(KJob *)
{
if (decoder) {
response += decoder->flush();
}
// make sure to forget about the job before emitting completed,
// since changeState triggers JS code, which might e.g. call abort.
job = 0;
changeState(XHRS_Loaded);
clearDecoder();
}
void XMLHttpRequest::slotRedirection(KIO::Job*, const KUrl& url)
{
if (!urlMatchesDocumentDomain(url)) {
abort();
}
}
static QString encodingFromContentType(const QString& type)
{
QString encoding;
int index = type.indexOf(';');
if (index > -1)
encoding = type.mid( index+1 ).remove(QRegExp("charset[ ]*=[ ]*", Qt::CaseInsensitive)).trimmed();
return encoding;
}
#ifdef APPLE_CHANGES
void XMLHttpRequest::slotData( KIO::Job*, const char *data, int len )
#else
void XMLHttpRequest::slotData(KIO::Job*, const QByteArray &_data)
#endif
{
if (m_state < XHRS_Sent ) {
responseHeaders = job->queryMetaData("HTTP-Headers");
// NOTE: Replace a 304 response with a 200! Both IE and Mozilla do this.
// Problem first reported through bug# 110272.
int codeStart = responseHeaders.indexOf("304");
if ( codeStart != -1) {
int codeEnd = responseHeaders.indexOf("\n", codeStart+3);
if (codeEnd != -1)
responseHeaders.replace(codeStart, (codeEnd-codeStart), "200 OK");
}
changeState(XHRS_Sent);
}
#ifndef APPLE_CHANGES
const char *data = (const char *)_data.data();
int len = (int)_data.size();
#endif
if ( !decoder && !binaryMode ) {
if (!m_mimeTypeOverride.isEmpty())
encoding = encodingFromContentType(m_mimeTypeOverride);
if (encoding.isEmpty()) {
int pos = responseHeaders.indexOf(QLatin1String("content-type:"), 0, Qt::CaseInsensitive);
if ( pos > -1 ) {
pos += 13;
int index = responseHeaders.indexOf('\n', pos);
QString type = responseHeaders.mid(pos, (index-pos));
encoding = encodingFromContentType(type);
}
}
if (encoding == QLatin1String("x-user-defined")) {
binaryMode = true;
} else {
decoder = new KEncodingDetector;
if (!encoding.isEmpty())
decoder->setEncoding(encoding.toLatin1().constData(), KEncodingDetector::EncodingFromHTTPHeader);
else
decoder->setEncoding("UTF-8", KEncodingDetector::DefaultEncoding);
}
}
if (len == 0)
return;
if (len == -1)
len = strlen(data);
QString decoded;
if (binaryMode)
decoded = QString::fromLatin1(data, len);
else
decoded = decoder->decodeWithBuffering(data, len);
response += decoded;
if (!aborted) {
changeState(XHRS_Receiving);
}
}
JSValue *XMLHttpRequestProtoFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args)
{
if (!thisObj->inherits(&XMLHttpRequest::info)) {
return throwError(exec, TypeError);
}
XMLHttpRequest *request = static_cast<XMLHttpRequest *>(thisObj);
if (!request->doc) {
setDOMException(exec, DOMException::INVALID_STATE_ERR);
return jsUndefined();
}
int ec = 0;
switch (id) {
case XMLHttpRequest::Abort:
request->abort();
return jsUndefined();
case XMLHttpRequest::GetAllResponseHeaders:
{
JSValue *ret = request->getAllResponseHeaders(ec);
setDOMException(exec, ec);
return ret;
}
case XMLHttpRequest::GetResponseHeader:
{
if (args.size() < 1)
return throwError(exec, SyntaxError, "Not enough arguments");
JSValue *ret = request->getResponseHeader(args[0]->toString(exec).qstring(), ec);
setDOMException(exec, ec);
return ret;
}
case XMLHttpRequest::Open:
{
if (args.size() < 2)
return throwError(exec, SyntaxError, "Not enough arguments");
QString method = args[0]->toString(exec).qstring();
KUrl url = KUrl(request->doc->completeURL(args[1]->toString(exec).qstring()));
bool async = true;
if (args.size() >= 3) {
async = args[2]->toBoolean(exec);
}
if (args.size() >= 4) {
url.setUser(args[3]->toString(exec).qstring());
}
if (args.size() >= 5) {
url.setPass(args[4]->toString(exec).qstring());
}
request->open(method, url, async, ec);
setDOMException(exec, ec);
return jsUndefined();
}
case XMLHttpRequest::Send:
{
QString body;
if (!args[0]->isUndefinedOrNull()
// make sure we don't marshal "undefined" or such;
&& request->m_method != QLatin1String("GET")
&& request->m_method != QLatin1String("HEAD")) {
// ... or methods that don't have payload
DOM::NodeImpl* docNode = toNode(args[0]);
if (docNode && docNode->isDocumentNode()) {
DOM::DocumentImpl *doc = static_cast<DOM::DocumentImpl *>(docNode);
body = doc->toString().string();
// FIXME: also need to set content type, including encoding!
} else {
body = args[0]->toString(exec).qstring();
}
}
request->send(body, ec);
setDOMException(exec, ec);
return jsUndefined();
}
case XMLHttpRequest::SetRequestHeader:
{
if (args.size() < 2)
return throwError(exec, SyntaxError, "Not enough arguments");
JSValue* keyArgument = args[0];
JSValue* valArgument = args[1];
QString key, val;
if (!keyArgument->isUndefined() && !keyArgument->isNull())
key = keyArgument->toString(exec).qstring();
if (!valArgument->isUndefined() && !valArgument->isNull())
val = valArgument->toString(exec).qstring();
request->setRequestHeader(key, val, ec);
setDOMException(exec, ec);
return jsUndefined();
}
case XMLHttpRequest::OverrideMIMEType:
if (args.size() < 1)
return throwError(exec, SyntaxError, "Not enough arguments");
request->overrideMIMEType(args[0]->toString(exec).qstring());
return jsUndefined();
}
return jsUndefined();
}
} // end namespace
// kate: indent-width 2; replace-tabs on; tab-width 4; space-indent on;
diff --git a/khtml/html/html_objectimpl.cpp b/khtml/html/html_objectimpl.cpp
index fc22418333..e11966498d 100644
--- a/khtml/html/html_objectimpl.cpp
+++ b/khtml/html/html_objectimpl.cpp
@@ -1,859 +1,859 @@
/**
* This file is part of the DOM implementation for KDE.
*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* (C) 2000 Stefan Schimanski (1Stein@gmx.de)
* (C) 2007, 2008 Maks Orlovich (maksim@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 "html/html_objectimpl.h"
#include "khtml_part.h"
#include "dom/dom_string.h"
#include "imload/imagemanager.h"
#include "khtmlview.h"
#include <QtCore/QCharRef>
#include <QtCore/QVariant>
#include <QtCore/QMap>
#include <QtCore/QTimer>
#include <QImageReader>
#include <kdebug.h>
#include <kmessagebox.h>
#include <kmimetype.h>
#include "xml/dom_docimpl.h"
#include "css/cssstyleselector.h"
#include "css/csshelper.h"
#include "css/cssproperties.h"
#include "css/cssvalues.h"
#include "rendering/render_frames.h"
#include "rendering/render_image.h"
#include "xml/dom2_eventsimpl.h"
using namespace DOM;
using namespace khtml;
HTMLPartContainerElementImpl::HTMLPartContainerElementImpl(DocumentImpl *doc)
: HTMLElementImpl(doc)
{
m_needToComputeContent = true;
m_childWidget = 0;
}
HTMLPartContainerElementImpl::~HTMLPartContainerElementImpl()
{
// Kill the renderer here, since we are asking for a widget to be deleted
if (m_render)
detach();
if (m_childWidget)
m_childWidget->deleteLater();
}
void HTMLPartContainerElementImpl::recalcStyle(StyleChange ch)
{
computeContentIfNeeded();
HTMLElementImpl::recalcStyle( ch );
}
void HTMLPartContainerElementImpl::close()
{
HTMLElementImpl::close(); // Do it first, to make sure closed() is set.
computeContentIfNeeded();
}
void HTMLPartContainerElementImpl::computeContentIfNeeded()
{
if (!m_needToComputeContent)
return;
m_needToComputeContent = false;
computeContent();
}
void HTMLPartContainerElementImpl::setNeedComputeContent()
{
m_needToComputeContent = true;
if (closed())
setChanged(); //React quickly when not in the middle of parsing..
}
void HTMLPartContainerElementImpl::setWidget(QWidget* widget)
{
if (widget == m_childWidget)
return; // The same part got navigated. Don't do anything
QWidget* oldWidget = m_childWidget;
m_childWidget = widget;
if (m_childWidget)
m_childWidget->hide();
setWidgetNotify(m_childWidget);
if (oldWidget) {
oldWidget->hide();
oldWidget->deleteLater();
}
}
void HTMLPartContainerElementImpl::partLoadingErrorNotify()
{
clearChildWidget();
}
void HTMLPartContainerElementImpl::clearChildWidget()
{
setWidget(0);
}
bool HTMLPartContainerElementImpl::mimetypeHandledInternally(const QString&)
{
return false;
}
void HTMLPartContainerElementImpl::slotEmitLoadEvent()
{
dispatchHTMLEvent(EventImpl::LOAD_EVENT, false, false);
}
void HTMLPartContainerElementImpl::postResizeEvent()
{
QApplication::postEvent( this, new QEvent(static_cast<QEvent::Type>(DOMCFResizeEvent)) );
}
void HTMLPartContainerElementImpl::sendPostedResizeEvents()
{
QApplication::sendPostedEvents(0, DOMCFResizeEvent);
}
bool HTMLPartContainerElementImpl::event(QEvent *e)
{
if (e->type() == static_cast<QEvent::Type>(DOMCFResizeEvent)) {
dispatchWindowEvent(EventImpl::RESIZE_EVENT, false, false);
e->accept();
return true;
}
return QObject::event(e);
}
// -------------------------------------------------------------------------
HTMLObjectBaseElementImpl::HTMLObjectBaseElementImpl(DocumentImpl *doc)
: HTMLPartContainerElementImpl(doc)
{
m_renderAlternative = false;
m_imageLike = false;
m_rerender = false;
}
void HTMLObjectBaseElementImpl::setServiceType(const QString & val) {
serviceType = val.toLower();
int pos = serviceType.indexOf( ";" );
if ( pos!=-1 )
serviceType.truncate( pos );
}
void HTMLObjectBaseElementImpl::parseAttribute(AttributeImpl *attr)
{
switch ( attr->id() )
{
case ATTR_TYPE:
case ATTR_CODETYPE:
if (attr->val()) {
DOM::DOMStringImpl *stringImpl = attr->val();
QString val = QString( stringImpl->s, stringImpl->l );
setServiceType( val );
setNeedComputeContent();
}
break;
case ATTR_WIDTH:
if (!attr->value().isEmpty())
addCSSLength(CSS_PROP_WIDTH, attr->value());
else
removeCSSProperty(CSS_PROP_WIDTH);
break;
case ATTR_HEIGHT:
if (!attr->value().isEmpty())
addCSSLength(CSS_PROP_HEIGHT, attr->value());
else
removeCSSProperty(CSS_PROP_HEIGHT);
break;
case ATTR_NAME:
if (inDocument() && m_name != attr->value()) {
document()->underDocNamedCache().remove(m_name, this);
document()->underDocNamedCache().add (attr->value(), this);
}
m_name = attr->value();
//fallthrough
default:
HTMLElementImpl::parseAttribute( attr );
}
}
void HTMLObjectBaseElementImpl::defaultEventHandler(EventImpl *e)
{
// ### duplicated in HTMLIFrameElementImpl
if ( e->target() == this && m_render && m_render->isWidget()
&& static_cast<RenderWidget*>(m_render)->isRedirectedWidget()
&& qobject_cast<KHTMLView*>(static_cast<RenderWidget*>(m_render)->widget())) {
switch(e->id()) {
case EventImpl::MOUSEDOWN_EVENT:
case EventImpl::MOUSEUP_EVENT:
case EventImpl::MOUSEMOVE_EVENT:
case EventImpl::MOUSEOUT_EVENT:
case EventImpl::MOUSEOVER_EVENT:
case EventImpl::KHTML_MOUSEWHEEL_EVENT:
case EventImpl::KEYDOWN_EVENT:
case EventImpl::KEYUP_EVENT:
case EventImpl::KEYPRESS_EVENT:
case EventImpl::DOMFOCUSIN_EVENT:
case EventImpl::DOMFOCUSOUT_EVENT:
if (static_cast<RenderWidget*>(m_render)->handleEvent(*e))
e->setDefaultHandled();
default:
break;
}
}
HTMLElementImpl::defaultEventHandler(e);
}
void HTMLObjectBaseElementImpl::removedFromDocument()
{
document()->underDocNamedCache().remove(m_name, this);
// When removed from document, we destroy the widget/plugin.
// We have to do it here and not just call setNeedComputeContent(),
// since khtml will not try to restyle changed() things not in document.
clearChildWidget();
HTMLPartContainerElementImpl::removedFromDocument();
}
void HTMLObjectBaseElementImpl::insertedIntoDocument()
{
document()->underDocNamedCache().add(m_name, this);
setNeedComputeContent();
HTMLPartContainerElementImpl::insertedIntoDocument();
}
void HTMLObjectBaseElementImpl::removeId(const DOMString& id)
{
document()->underDocNamedCache().remove(id, this);
HTMLElementImpl::removeId(id);
}
void HTMLObjectBaseElementImpl::addId (const DOMString& id)
{
document()->underDocNamedCache().add(id, this);
HTMLElementImpl::addId(id);
}
void HTMLObjectBaseElementImpl::requestRerender()
{
if (m_rerender) return;
m_rerender = true;
QTimer::singleShot( 0, this, SLOT( slotRerender()) );
}
void HTMLObjectBaseElementImpl::slotRerender()
{
// the singleshot timer might have fired after we're removed
// from the document, but not yet deleted due to references
if ( !inDocument() || !m_rerender ) return;
// ### there can be a m_render if this is called from our attach indirectly
if ( attached() || m_render) {
detach();
attach();
}
m_rerender = false;
}
void HTMLObjectBaseElementImpl::attach() {
assert(!attached());
assert(!m_render);
computeContentIfNeeded();
m_rerender = false;
if (m_renderAlternative && !m_imageLike) {
// render alternative content
ElementImpl::attach();
return;
}
if (!parentNode()->renderer()) {
NodeBaseImpl::attach();
return;
}
RenderStyle* _style = document()->styleSelector()->styleForElement(this);
_style->ref();
if (parentNode()->renderer() && parentNode()->renderer()->childAllowed() &&
_style->display() != NONE)
{
if (m_imageLike) {
m_render = new (document()->renderArena()) RenderImage(this);
} else {
m_render = new (document()->renderArena()) RenderPartObject(this);
// If we already have a widget, set it.
if (childWidget())
static_cast<RenderFrame*>(m_render)->setWidget(childWidget());
}
m_render->setStyle(_style);
parentNode()->renderer()->addChild(m_render, nextRenderer());
if (m_imageLike)
m_render->updateFromElement();
}
_style->deref();
NodeBaseImpl::attach();
}
HTMLEmbedElementImpl* HTMLObjectBaseElementImpl::relevantEmbed()
{
for (NodeImpl *child = firstChild(); child; child = child->nextSibling()) {
if ( child->id() == ID_EMBED ) {
return static_cast<HTMLEmbedElementImpl *>( child );
}
}
return 0;
}
bool HTMLObjectBaseElementImpl::mimetypeHandledInternally(const QString& mime)
{
QStringList supportedImageTypes = khtmlImLoad::ImageManager::loaderDatabase()->supportedMimeTypes();
bool newImageLike = supportedImageTypes.contains(mime);
if (newImageLike != m_imageLike) {
m_imageLike = newImageLike;
requestRerender();
}
return newImageLike; // No need for kpart for that.
}
void HTMLObjectBaseElementImpl::computeContent()
{
QStringList params;
QString effectiveURL = url; // May be overwritten by some of the <params>
// if the URL isn't there
QString effectiveServiceType = serviceType;
// We need to wait until everything has parsed, since we need the <param>s,
// and the embedded <embed>
if (!closed()) {
setNeedComputeContent();
return;
}
// Not in document => no plugin.
if (!inDocument()) {
clearChildWidget();
return;
}
// Collect information from <param> children for ...
// It also sometimes supplements or replaces some of the element's attributes
for (NodeImpl* child = firstChild(); child; child = child->nextSibling()) {
if (child->id() == ID_PARAM) {
HTMLParamElementImpl *p = static_cast<HTMLParamElementImpl *>( child );
QString aStr = p->name();
aStr += QLatin1String("=\"");
aStr += p->value();
aStr += QLatin1String("\"");
QString name_lower = p->name().toLower();
if (name_lower == QLatin1String("type") && id() != ID_APPLET) {
setServiceType(p->value());
effectiveServiceType = serviceType;
} else if (effectiveURL.isEmpty() &&
(name_lower == QLatin1String("src") ||
name_lower == QLatin1String("movie") ||
name_lower == QLatin1String("code"))) {
effectiveURL = p->value();
}
params.append(aStr);
}
}
// For <applet>(?) and <embed> we also make each attribute a part parameter
if (id() != ID_OBJECT) {
NamedAttrMapImpl* a = attributes();
if (a) {
for (unsigned i = 0; i < a->length(); ++i) {
NodeImpl::Id id = a->idAt(i);
DOMString value = a->valueAt(i);
params.append(LocalName::fromId(localNamePart(id)).toString().string() + "=\"" + value.string() + "\"");
}
}
}
params.append( QLatin1String("__KHTML__PLUGINEMBED=\"YES\"") );
params.append( QString::fromLatin1("__KHTML__PLUGINBASEURL=\"%1\"").arg(document()->baseURL().url()));
params.append( QString::fromLatin1("__KHTML__PLUGINPAGEURL=\"%1\"").arg(document()->URL().url()));
// Non-embed elements parse a bunch of attributes and inherit things off <embed>, if any
HTMLEmbedElementImpl* embed = relevantEmbed();
if (id() != ID_EMBED) {
params.append( QString::fromLatin1("__KHTML__CLASSID=\"%1\"").arg( classId ) );
params.append( QString::fromLatin1("__KHTML__CODEBASE=\"%1\"").arg( getAttribute(ATTR_CODEBASE).string() ) );
if (embed && !embed->getAttribute(ATTR_WIDTH).isEmpty())
setAttribute(ATTR_WIDTH, embed->getAttribute(ATTR_WIDTH));
if (embed && !embed->getAttribute(ATTR_HEIGHT).isEmpty())
setAttribute(ATTR_HEIGHT, embed->getAttribute(ATTR_HEIGHT));
if (!getAttribute(ATTR_WIDTH).isEmpty())
params.append( QString::fromLatin1("WIDTH=\"%1\"").arg( getAttribute(ATTR_WIDTH).string() ) );
if (!getAttribute(ATTR_HEIGHT).isEmpty())
params.append( QString::fromLatin1("HEIGHT=\"%1\"").arg( getAttribute(ATTR_HEIGHT).string() ) );
// Fix up the serviceType from embed, or applet info..
if (embed) {
effectiveURL = embed->url;
if (!embed->serviceType.isEmpty())
effectiveServiceType = embed->serviceType;
} else if (effectiveURL.isEmpty() &&
classId.startsWith(QLatin1String("java:"))) {
effectiveServiceType = "application/x-java-applet";
effectiveURL = classId.mid(5);
}
// Translate ActiveX gibberish into mimetypes
if ((effectiveServiceType.isEmpty() || serviceType == "application/x-oleobject") && !classId.isEmpty()) {
if(classId.indexOf(QString::fromLatin1("D27CDB6E-AE6D-11cf-96B8-444553540000")) >= 0)
effectiveServiceType = "application/x-shockwave-flash";
else if(classId.indexOf(QLatin1String("CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA")) >= 0)
effectiveServiceType = "audio/x-pn-realaudio-plugin";
else if(classId.indexOf(QLatin1String("8AD9C840-044E-11D1-B3E9-00805F499D93")) >= 0 ||
classId.indexOf(QLatin1String("CAFEEFAC-0014-0000-0000-ABCDEFFEDCBA")) >= 0)
effectiveServiceType = "application/x-java-applet";
// http://www.apple.com/quicktime/tools_tips/tutorials/activex.html
else if(classId.indexOf(QLatin1String("02BF25D5-8C17-4B23-BC80-D3488ABDDC6B")) >= 0)
effectiveServiceType = "video/quicktime";
// http://msdn.microsoft.com/library/en-us/dnwmt/html/adding_windows_media_to_web_pages__etse.asp?frame=true
else if(classId.indexOf(QString::fromLatin1("6BF52A52-394A-11d3-B153-00C04F79FAA6")) >= 0 ||
classId.indexOf(QString::fromLatin1("22D6f312-B0F6-11D0-94AB-0080C74C7E95")) >= 0)
effectiveServiceType = "video/x-msvideo";
else
kDebug(6031) << "ActiveX classId " << classId;
// TODO: add more plugins here
}
}
if (effectiveServiceType.isEmpty() &&
effectiveURL.startsWith(QLatin1String("data:"))) {
// Extract the MIME type from the data URL.
int index = effectiveURL.indexOf(';');
if (index == -1)
index = effectiveURL.indexOf(',');
if (index != -1) {
int len = index - 5;
if (len > 0)
effectiveServiceType = effectiveURL.mid(5, len);
else
effectiveServiceType = "text/plain"; // Data URLs with no MIME type are considered text/plain.
}
}
// Figure out if may be we're image-like. In this case, we don't need to load anything,
// but may need to do a detach/attach
QStringList supportedImageTypes = khtmlImLoad::ImageManager::loaderDatabase()->supportedMimeTypes();
bool newImageLike = effectiveServiceType.startsWith(QLatin1String("image/")) && supportedImageTypes.contains(effectiveServiceType);
if (newImageLike != m_imageLike) {
m_imageLike = newImageLike;
requestRerender();
}
if (m_imageLike)
return;
// Now see if we have to render alternate content.
bool newRenderAlternative = false;
// If we aren't permitted to load this by security policy, render alternative content instead.
if (!document()->isURLAllowed(effectiveURL))
newRenderAlternative = true;
// If Java is off, render alternative as well...
if (effectiveServiceType == "application/x-java-applet") {
KHTMLPart* p = document()->part();
if (!p || !p->javaEnabled())
newRenderAlternative = true;
} else {
// Similarly for plugins.
KHTMLPart* p = document()->part();
if (!p || !p->pluginsEnabled())
newRenderAlternative = true;
}
// If there is no <embed> (here or as a child), and we don't have a type + url to go on,
// we need to render alternative as well
if (!embed && effectiveURL.isEmpty() && effectiveServiceType.isEmpty())
newRenderAlternative = true;
if (newRenderAlternative != m_renderAlternative) {
m_renderAlternative = newRenderAlternative;
requestRerender();
}
if (m_renderAlternative)
return;
KHTMLPart* part = document()->part();
clearChildWidget();
kDebug(6031) << effectiveURL << effectiveServiceType << params;
if (!part->loadObjectElement( this, effectiveURL, effectiveServiceType, params)) {
// Looks like we are gonna need alternative content after all...
m_renderAlternative = true;
}
// Either way, we need to re-attach, either for alternative content, or because
// we got the part..
requestRerender();
}
void HTMLObjectBaseElementImpl::setWidgetNotify(QWidget *widget)
{
// Ick.
if(m_render && strcmp( m_render->renderName(), "RenderPartObject" ) == 0 )
static_cast<RenderPartObject*>(m_render)->setWidget(widget);
}
void HTMLObjectBaseElementImpl::renderAlternative()
{
if (m_renderAlternative)
return;
m_renderAlternative = true;
requestRerender();
}
void HTMLObjectBaseElementImpl::partLoadingErrorNotify()
{
// Defer ourselves from the current event loop (to prevent crashes due to the message box staying up)
QTimer::singleShot(0, this, SLOT(slotPartLoadingErrorNotify()));
// Either way, we don't have stuff to display, so have to render alternative content.
if (!m_renderAlternative) {
m_renderAlternative = true;
requestRerender();
}
clearChildWidget();
}
void HTMLObjectBaseElementImpl::slotPartLoadingErrorNotify()
{
// If we have an embed, we may be able to tell the user where to
// download the plugin.
HTMLEmbedElementImpl *embed = relevantEmbed();
QString serviceType; // shadows ours, but we don't care.
if (!embed)
return;
serviceType = embed->serviceType;
KHTMLPart* part = document()->part();
KParts::BrowserExtension *ext = part->browserExtension();
if(!embed->pluginPage.isEmpty() && ext) {
// Prepare the mimetype to show in the question (comment if available, name as fallback)
QString mimeName = serviceType;
KMimeType::Ptr mime = KMimeType::mimeType(serviceType, KMimeType::ResolveAliases);
if ( mime && mime->name() != KMimeType::defaultMimeType() )
mimeName = mime->comment();
// Check if we already asked the user, for this page
if (!mimeName.isEmpty() && !part->pluginPageQuestionAsked(serviceType))
{
part->setPluginPageQuestionAsked(serviceType);
// Prepare the URL to show in the question (host only if http, to make it short)
KUrl pluginPageURL(embed->pluginPage);
- QString shortURL = pluginPageURL.protocol() == "http" ? pluginPageURL.host() : pluginPageURL.prettyUrl();
+ QString shortURL = pluginPageURL.scheme() == "http" ? pluginPageURL.host() : pluginPageURL.prettyUrl();
int res = KMessageBox::questionYesNo( part->view(),
i18n("No plugin found for '%1'.\nDo you want to download one from %2?", mimeName, shortURL),
i18n("Missing Plugin"), KGuiItem(i18n("Download")), KGuiItem(i18n("Do Not Download")), QString("plugin-")+serviceType);
if (res == KMessageBox::Yes)
{
// Display vendor download page
ext->createNewWindow(pluginPageURL);
return;
}
}
}
}
// -------------------------------------------------------------------------
HTMLAppletElementImpl::HTMLAppletElementImpl(DocumentImpl *doc)
: HTMLObjectBaseElementImpl(doc)
{
serviceType = "application/x-java-applet";
}
HTMLAppletElementImpl::~HTMLAppletElementImpl()
{
}
NodeImpl::Id HTMLAppletElementImpl::id() const
{
return ID_APPLET;
}
void HTMLAppletElementImpl::parseAttribute(AttributeImpl *attr)
{
switch( attr->id() )
{
case ATTR_CODEBASE:
case ATTR_ARCHIVE:
case ATTR_CODE:
case ATTR_OBJECT:
case ATTR_ALT:
break;
case ATTR_ALIGN:
addHTMLAlignment( attr->value() );
break;
case ATTR_VSPACE:
addCSSLength(CSS_PROP_MARGIN_TOP, attr->value());
addCSSLength(CSS_PROP_MARGIN_BOTTOM, attr->value());
break;
case ATTR_HSPACE:
addCSSLength(CSS_PROP_MARGIN_LEFT, attr->value());
addCSSLength(CSS_PROP_MARGIN_RIGHT, attr->value());
break;
case ATTR_VALIGN:
addCSSProperty(CSS_PROP_VERTICAL_ALIGN, attr->value().lower() );
break;
default:
HTMLObjectBaseElementImpl::parseAttribute(attr);
}
}
void HTMLAppletElementImpl::computeContent()
{
DOMString codeBase = getAttribute( ATTR_CODEBASE );
DOMString code = getAttribute( ATTR_CODE );
if ( !codeBase.isEmpty() )
url = codeBase.string();
if ( !code.isEmpty() )
url = code.string();
HTMLObjectBaseElementImpl::computeContent();
}
// -------------------------------------------------------------------------
HTMLEmbedElementImpl::HTMLEmbedElementImpl(DocumentImpl *doc)
: HTMLObjectBaseElementImpl(doc)
{
}
HTMLEmbedElementImpl::~HTMLEmbedElementImpl()
{
}
NodeImpl::Id HTMLEmbedElementImpl::id() const
{
return ID_EMBED;
}
HTMLEmbedElementImpl* HTMLEmbedElementImpl::relevantEmbed()
{
return this;
}
void HTMLEmbedElementImpl::parseAttribute(AttributeImpl *attr)
{
switch ( attr->id() )
{
case ATTR_CODE:
case ATTR_SRC:
url = khtml::parseURL(attr->val()).string();
setNeedComputeContent();
break;
case ATTR_BORDER:
addCSSLength(CSS_PROP_BORDER_WIDTH, attr->value());
addCSSProperty( CSS_PROP_BORDER_TOP_STYLE, CSS_VAL_SOLID );
addCSSProperty( CSS_PROP_BORDER_RIGHT_STYLE, CSS_VAL_SOLID );
addCSSProperty( CSS_PROP_BORDER_BOTTOM_STYLE, CSS_VAL_SOLID );
addCSSProperty( CSS_PROP_BORDER_LEFT_STYLE, CSS_VAL_SOLID );
break;
case ATTR_VSPACE:
addCSSLength(CSS_PROP_MARGIN_TOP, attr->value());
addCSSLength(CSS_PROP_MARGIN_BOTTOM, attr->value());
break;
case ATTR_HSPACE:
addCSSLength(CSS_PROP_MARGIN_LEFT, attr->value());
addCSSLength(CSS_PROP_MARGIN_RIGHT, attr->value());
break;
case ATTR_ALIGN:
addHTMLAlignment( attr->value() );
break;
case ATTR_VALIGN:
addCSSProperty(CSS_PROP_VERTICAL_ALIGN, attr->value().lower() );
break;
case ATTR_PLUGINPAGE:
case ATTR_PLUGINSPAGE: {
pluginPage = attr->value().string();
break;
}
case ATTR_HIDDEN:
if (strcasecmp( attr->value(), "yes" ) == 0 || strcasecmp( attr->value() , "true") == 0 )
hidden = true;
else
hidden = false;
break;
default:
HTMLObjectBaseElementImpl::parseAttribute( attr );
}
}
void HTMLEmbedElementImpl::attach()
{
if (parentNode()->id() == ID_OBJECT)
NodeBaseImpl::attach();
else
HTMLObjectBaseElementImpl::attach();
}
void HTMLEmbedElementImpl::computeContent()
{
if (parentNode()->id() != ID_OBJECT)
HTMLObjectBaseElementImpl::computeContent();
}
// -------------------------------------------------------------------------
HTMLObjectElementImpl::HTMLObjectElementImpl(DocumentImpl *doc)
: HTMLObjectBaseElementImpl(doc)
{
}
HTMLObjectElementImpl::~HTMLObjectElementImpl()
{
}
NodeImpl::Id HTMLObjectElementImpl::id() const
{
return ID_OBJECT;
}
HTMLFormElementImpl *HTMLObjectElementImpl::form() const
{
return 0;
}
void HTMLObjectElementImpl::parseAttribute(AttributeImpl *attr)
{
switch ( attr->id() )
{
case ATTR_DATA:
url = khtml::parseURL( attr->val() ).string();
setNeedComputeContent();
break;
case ATTR_CLASSID:
classId = attr->value().string();
setNeedComputeContent();
break;
case ATTR_ONLOAD: // ### support load/unload on object elements
setHTMLEventListener(EventImpl::LOAD_EVENT,
document()->createHTMLEventListener(attr->value().string(), "onload", this));
break;
case ATTR_ONUNLOAD:
setHTMLEventListener(EventImpl::UNLOAD_EVENT,
document()->createHTMLEventListener(attr->value().string(), "onunload", this));
break;
case ATTR_VSPACE:
addCSSLength(CSS_PROP_MARGIN_TOP, attr->value());
addCSSLength(CSS_PROP_MARGIN_BOTTOM, attr->value());
break;
case ATTR_HSPACE:
addCSSLength(CSS_PROP_MARGIN_LEFT, attr->value());
addCSSLength(CSS_PROP_MARGIN_RIGHT, attr->value());
break;
case ATTR_ALIGN:
addHTMLAlignment( attr->value() );
break;
case ATTR_VALIGN:
addCSSProperty(CSS_PROP_VERTICAL_ALIGN, attr->value().lower() );
break;
default:
HTMLObjectBaseElementImpl::parseAttribute( attr );
}
}
DocumentImpl* HTMLObjectElementImpl::contentDocument() const
{
QWidget* widget = childWidget();
if( widget && qobject_cast<KHTMLView*>( widget ) )
return static_cast<KHTMLView*>( widget )->part()->xmlDocImpl();
return 0;
}
void HTMLObjectElementImpl::attach()
{
HTMLObjectBaseElementImpl::attach();
}
// -------------------------------------------------------------------------
NodeImpl::Id HTMLParamElementImpl::id() const
{
return ID_PARAM;
}
void HTMLParamElementImpl::parseAttribute(AttributeImpl *attr)
{
switch( attr->id() )
{
case ATTR_VALUE:
m_value = attr->value().string();
break;
case ATTR_ID:
if (document()->htmlMode() != DocumentImpl::XHtml) break;
// fall through
case ATTR_NAME:
m_name = attr->value().string();
// fall through
default:
HTMLElementImpl::parseAttribute(attr);
}
}
// kate: indent-width 4; replace-tabs on; tab-width 4; space-indent on;
diff --git a/khtml/khtml_ext.cpp b/khtml/khtml_ext.cpp
index 5f290602b8..c3a65f9b68 100644
--- a/khtml/khtml_ext.cpp
+++ b/khtml/khtml_ext.cpp
@@ -1,1213 +1,1213 @@
/* This file is part of the KDE project
*
* Copyright (C) 2000-2003 Simon Hausmann <hausmann@kde.org>
* 2001-2003 George Staikos <staikos@kde.org>
* 2001-2003 Laurent Montel <montel@kde.org>
* 2001-2003 Dirk Mueller <mueller@kde.org>
* 2001-2003 Waldo Bastian <bastian@kde.org>
* 2001-2003 David Faure <faure@kde.org>
* 2001-2003 Daniel Naber <dnaber@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 "khtml_ext.h"
#include "khtmlview.h"
#include "khtml_pagecache.h"
#include "rendering/render_form.h"
#include "rendering/render_image.h"
#include "html/html_imageimpl.h"
#include "misc/loader.h"
#include "dom/html_form.h"
#include "dom/html_image.h"
#include "dom/dom_string.h"
#include "dom/html_document.h"
#include "dom/dom_element.h"
#include "xml/dom_elementimpl.h"
#include <QClipboard>
#include <QtCore/QFileInfo>
#include <QMenu>
#include <QtCore/QUrl>
#include <QtCore/QMetaEnum>
#include <assert.h>
#include <kdebug.h>
#include <klocale.h>
#include <kfiledialog.h>
#include <kjobuidelegate.h>
#include <kio/job.h>
#include <kshell.h>
#include <ktoolbar.h>
#include <ksavefile.h>
#include <kstringhandler.h>
#include <ktoolinvocation.h>
#include <kmessagebox.h>
#include <kstandarddirs.h>
#include <krun.h>
#include <kurifilter.h>
#include <kicon.h>
#include <kiconloader.h>
#include <kdesktopfile.h>
#include <kinputdialog.h>
#include <ktemporaryfile.h>
#include "khtml_global.h"
#include <kstandardaction.h>
#include <kactioncollection.h>
#include <kactionmenu.h>
#include "khtmlpart_p.h"
KHTMLPartBrowserExtension::KHTMLPartBrowserExtension( KHTMLPart *parent )
: KParts::BrowserExtension( parent )
{
m_part = parent;
setURLDropHandlingEnabled( true );
enableAction( "cut", false );
enableAction( "copy", false );
enableAction( "paste", false );
m_connectedToClipboard = false;
}
int KHTMLPartBrowserExtension::xOffset()
{
return m_part->view()->contentsX();
}
int KHTMLPartBrowserExtension::yOffset()
{
return m_part->view()->contentsY();
}
void KHTMLPartBrowserExtension::saveState( QDataStream &stream )
{
//kDebug( 6050 ) << "saveState!";
m_part->saveState( stream );
}
void KHTMLPartBrowserExtension::restoreState( QDataStream &stream )
{
//kDebug( 6050 ) << "restoreState!";
m_part->restoreState( stream );
}
void KHTMLPartBrowserExtension::editableWidgetFocused( QWidget *widget )
{
m_editableFormWidget = widget;
updateEditActions();
if ( !m_connectedToClipboard && m_editableFormWidget )
{
connect( QApplication::clipboard(), SIGNAL( dataChanged() ),
this, SLOT( updateEditActions() ) );
if ( m_editableFormWidget->inherits( "QLineEdit" ) || m_editableFormWidget->inherits( "QTextEdit" ) )
connect( m_editableFormWidget, SIGNAL( selectionChanged() ),
this, SLOT( updateEditActions() ) );
m_connectedToClipboard = true;
}
editableWidgetFocused();
}
void KHTMLPartBrowserExtension::editableWidgetBlurred( QWidget * /*widget*/ )
{
QWidget *oldWidget = m_editableFormWidget;
m_editableFormWidget = 0;
enableAction( "cut", false );
enableAction( "paste", false );
m_part->emitSelectionChanged();
if ( m_connectedToClipboard )
{
disconnect( QApplication::clipboard(), SIGNAL( dataChanged() ),
this, SLOT( updateEditActions() ) );
if ( oldWidget )
{
if ( oldWidget->inherits( "QLineEdit" ) || oldWidget->inherits( "QTextEdit" ) )
disconnect( oldWidget, SIGNAL( selectionChanged() ),
this, SLOT( updateEditActions() ) );
}
m_connectedToClipboard = false;
}
editableWidgetBlurred();
}
void KHTMLPartBrowserExtension::setExtensionProxy( KParts::BrowserExtension *proxy )
{
if ( m_extensionProxy )
{
disconnect( m_extensionProxy, SIGNAL( enableAction( const char *, bool ) ),
this, SLOT( extensionProxyActionEnabled( const char *, bool ) ) );
if ( m_extensionProxy->inherits( "KHTMLPartBrowserExtension" ) )
{
disconnect( m_extensionProxy, SIGNAL( editableWidgetFocused() ),
this, SLOT( extensionProxyEditableWidgetFocused() ) );
disconnect( m_extensionProxy, SIGNAL( editableWidgetBlurred() ),
this, SLOT( extensionProxyEditableWidgetBlurred() ) );
}
}
m_extensionProxy = proxy;
if ( m_extensionProxy )
{
connect( m_extensionProxy, SIGNAL( enableAction( const char *, bool ) ),
this, SLOT( extensionProxyActionEnabled( const char *, bool ) ) );
if ( m_extensionProxy->inherits( "KHTMLPartBrowserExtension" ) )
{
connect( m_extensionProxy, SIGNAL( editableWidgetFocused() ),
this, SLOT( extensionProxyEditableWidgetFocused() ) );
connect( m_extensionProxy, SIGNAL( editableWidgetBlurred() ),
this, SLOT( extensionProxyEditableWidgetBlurred() ) );
}
enableAction( "cut", m_extensionProxy->isActionEnabled( "cut" ) );
enableAction( "copy", m_extensionProxy->isActionEnabled( "copy" ) );
enableAction( "paste", m_extensionProxy->isActionEnabled( "paste" ) );
}
else
{
updateEditActions();
enableAction( "copy", false ); // ### re-check this
}
}
void KHTMLPartBrowserExtension::cut()
{
if ( m_extensionProxy )
{
callExtensionProxyMethod( "cut" );
return;
}
if ( !m_editableFormWidget )
return;
QLineEdit* lineEdit = qobject_cast<QLineEdit *>( m_editableFormWidget );
if ( lineEdit && !lineEdit->isReadOnly() )
lineEdit->cut();
QTextEdit* textEdit = qobject_cast<QTextEdit *>( m_editableFormWidget );
if ( textEdit && !textEdit->isReadOnly() )
textEdit->cut();
}
void KHTMLPartBrowserExtension::copy()
{
if ( m_extensionProxy )
{
callExtensionProxyMethod( "copy" );
return;
}
if ( !m_editableFormWidget )
{
// get selected text and paste to the clipboard
QString text = m_part->selectedText();
text.replace( QChar( 0xa0 ), ' ' );
//kDebug(6050) << text;
QClipboard *cb = QApplication::clipboard();
disconnect( cb, SIGNAL( selectionChanged() ), m_part, SLOT( slotClearSelection() ) );
#ifndef QT_NO_MIMECLIPBOARD
QString htmltext;
/*
* When selectionModeEnabled, that means the user has just selected
* the text, not ctrl+c to copy it. The selection clipboard
* doesn't seem to support mime type, so to save time, don't calculate
* the selected text as html.
* optomisation disabled for now until everything else works.
*/
//if(!cb->selectionModeEnabled())
htmltext = m_part->selectedTextAsHTML();
QMimeData *mimeData = new QMimeData;
mimeData->setText(text);
if(!htmltext.isEmpty()) {
htmltext.replace( QChar( 0xa0 ), ' ' );
mimeData->setHtml(htmltext);
}
cb->setMimeData(mimeData);
#else
cb->setText(text);
#endif
connect( cb, SIGNAL( selectionChanged() ), m_part, SLOT( slotClearSelection() ) );
}
else
{
QLineEdit* lineEdit = qobject_cast<QLineEdit *>( m_editableFormWidget );
if ( lineEdit )
lineEdit->copy();
QTextEdit* textEdit = qobject_cast<QTextEdit *>( m_editableFormWidget );
if ( textEdit )
textEdit->copy();
}
}
void KHTMLPartBrowserExtension::searchProvider()
{
KAction *action = qobject_cast<KAction*>(sender());
if (action) {
KUrl url = action->data().toUrl();
if (url.host().isEmpty()) {
KUriFilterData data(action->data().toString());
if (KUriFilter::self()->filterSearchUri(data, KUriFilter::WebShortcutFilter))
url = data.uri();
}
KParts::BrowserArguments browserArgs;
browserArgs.frameName = "_blank";
emit m_part->browserExtension()->openUrlRequest( url, KParts::OpenUrlArguments(), browserArgs );
}
}
void KHTMLPartBrowserExtension::paste()
{
if ( m_extensionProxy )
{
callExtensionProxyMethod( "paste" );
return;
}
if ( !m_editableFormWidget )
return;
QLineEdit* lineEdit = qobject_cast<QLineEdit *>( m_editableFormWidget );
if ( lineEdit && !lineEdit->isReadOnly() )
lineEdit->paste();
QTextEdit* textEdit = qobject_cast<QTextEdit *>( m_editableFormWidget );
if ( textEdit && !textEdit->isReadOnly() )
textEdit->paste();
}
void KHTMLPartBrowserExtension::callExtensionProxyMethod( const char *method )
{
if ( !m_extensionProxy )
return;
QMetaObject::invokeMethod(m_extensionProxy, method, Qt::DirectConnection);
}
void KHTMLPartBrowserExtension::updateEditActions()
{
if ( !m_editableFormWidget )
{
enableAction( "cut", false );
enableAction( "copy", false );
enableAction( "paste", false );
return;
}
// ### duplicated from KonqMainWindow::slotClipboardDataChanged
#ifndef QT_NO_MIMECLIPBOARD // Handle minimalized versions of Qt Embedded
const QMimeData *data = QApplication::clipboard()->mimeData();
enableAction( "paste", data->hasFormat( "text/plain" ) );
#else
QString data=QApplication::clipboard()->text();
enableAction( "paste", data.contains("://"));
#endif
bool hasSelection = false;
if( m_editableFormWidget) {
if ( qobject_cast<QLineEdit*>(m_editableFormWidget))
hasSelection = static_cast<QLineEdit *>( &(*m_editableFormWidget) )->hasSelectedText();
else if(qobject_cast<QTextEdit*>(m_editableFormWidget))
hasSelection = static_cast<QTextEdit *>( &(*m_editableFormWidget) )->textCursor().hasSelection();
}
enableAction( "copy", hasSelection );
enableAction( "cut", hasSelection );
}
void KHTMLPartBrowserExtension::extensionProxyEditableWidgetFocused() {
editableWidgetFocused();
}
void KHTMLPartBrowserExtension::extensionProxyEditableWidgetBlurred() {
editableWidgetBlurred();
}
void KHTMLPartBrowserExtension::extensionProxyActionEnabled( const char *action, bool enable )
{
// only forward enableAction calls for actions we actually do forward
if ( strcmp( action, "cut" ) == 0 ||
strcmp( action, "copy" ) == 0 ||
strcmp( action, "paste" ) == 0 ) {
enableAction( action, enable );
}
}
void KHTMLPartBrowserExtension::reparseConfiguration()
{
m_part->reparseConfiguration();
}
void KHTMLPartBrowserExtension::print()
{
m_part->view()->print();
}
void KHTMLPartBrowserExtension::disableScrolling()
{
QScrollArea *scrollArea = m_part->view();
if (scrollArea) {
scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
}
}
class KHTMLPopupGUIClient::KHTMLPopupGUIClientPrivate
{
public:
KHTMLPart *m_khtml;
KUrl m_url;
KUrl m_imageURL;
QPixmap m_pixmap;
QString m_suggestedFilename;
KActionCollection* m_actionCollection;
KParts::BrowserExtension::ActionGroupMap actionGroups;
};
KHTMLPopupGUIClient::KHTMLPopupGUIClient( KHTMLPart *khtml, const KUrl &url )
: QObject( khtml ), d(new KHTMLPopupGUIClientPrivate)
{
d->m_khtml = khtml;
d->m_url = url;
d->m_actionCollection = new KActionCollection(this);
bool isImage = false;
bool hasSelection = khtml->hasSelection();
DOM::Element e = khtml->nodeUnderMouse();
if ( !e.isNull() && (e.elementId() == ID_IMG ||
(e.elementId() == ID_INPUT && !static_cast<DOM::HTMLInputElement>(e).src().isEmpty())))
{
if (e.elementId() == ID_IMG) {
DOM::HTMLImageElementImpl *ie = static_cast<DOM::HTMLImageElementImpl*>(e.handle());
khtml::RenderImage *ri = dynamic_cast<khtml::RenderImage*>(ie->renderer());
if (ri && ri->contentObject()) {
d->m_suggestedFilename = static_cast<khtml::CachedImage*>(ri->contentObject())->suggestedFilename();
}
}
isImage=true;
}
if (hasSelection) {
QList<QAction *> editActions;
QAction* copyAction = d->m_actionCollection->addAction( KStandardAction::Copy, "copy",
d->m_khtml->browserExtension(), SLOT( copy() ) );
copyAction->setText(i18n("&Copy Text"));
copyAction->setEnabled(d->m_khtml->browserExtension()->isActionEnabled( "copy" ));
editActions.append(copyAction);
editActions.append(khtml->actionCollection()->action("selectAll"));
addSearchActions(editActions);
QString selectedTextURL = selectedTextAsOneLine(d->m_khtml);
if ( selectedTextURL.contains("://") && KUrl(selectedTextURL).isValid() ) {
if (selectedTextURL.length() > 18) {
selectedTextURL.truncate(15);
selectedTextURL += "...";
}
KAction *action = new KAction(i18n("Open '%1'", selectedTextURL), this);
d->m_actionCollection->addAction( "openSelection", action );
action->setIcon( KIcon( "window-new" ) );
connect( action, SIGNAL(triggered(bool)), this, SLOT( openSelection() ) );
editActions.append(action);
}
KAction* separator = new KAction(d->m_actionCollection);
separator->setSeparator(true);
editActions.append(separator);
d->actionGroups.insert("editactions", editActions);
}
if (!url.isEmpty()) {
QList<QAction *> linkActions;
- if (url.protocol() == "mailto") {
+ if (url.scheme() == "mailto") {
KAction *action = new KAction( i18n( "&Copy Email Address" ), this );
d->m_actionCollection->addAction( "copylinklocation", action );
connect( action, SIGNAL(triggered(bool)), this, SLOT(slotCopyLinkLocation()) );
linkActions.append(action);
} else {
KAction *action = new KAction( i18n( "&Save Link As..." ), this );
d->m_actionCollection->addAction( "savelinkas", action );
connect( action, SIGNAL(triggered(bool)), this, SLOT(slotSaveLinkAs()) );
linkActions.append(action);
action = new KAction( i18n( "&Copy Link Address" ), this );
d->m_actionCollection->addAction( "copylinklocation", action );
connect( action, SIGNAL(triggered(bool)), this, SLOT( slotCopyLinkLocation() ) );
linkActions.append(action);
}
d->actionGroups.insert("linkactions", linkActions);
}
QList<QAction *> partActions;
// frameset? -> add "Reload Frame" etc.
if (!hasSelection) {
if ( khtml->parentPart() ) {
KActionMenu* menu = new KActionMenu( i18nc("@title:menu HTML frame/iframe", "Frame"), this);
KAction *action = new KAction( i18n( "Open in New &Window" ), this );
d->m_actionCollection->addAction( "frameinwindow", action );
action->setIcon( KIcon( "window-new" ) );
connect( action, SIGNAL(triggered(bool)), this, SLOT(slotFrameInWindow()) );
menu->addAction(action);
action = new KAction( i18n( "Open in &This Window" ), this );
d->m_actionCollection->addAction( "frameintop", action );
connect( action, SIGNAL(triggered(bool)), this, SLOT( slotFrameInTop() ) );
menu->addAction(action);
action = new KAction( i18n( "Open in &New Tab" ), this );
d->m_actionCollection->addAction( "frameintab", action );
action->setIcon( KIcon( "tab-new" ) );
connect( action, SIGNAL(triggered(bool)), this, SLOT( slotFrameInTab() ) );
menu->addAction(action);
action = new KAction(d->m_actionCollection);
action->setSeparator(true);
menu->addAction(action);
action = new KAction( i18n( "Reload Frame" ), this );
d->m_actionCollection->addAction( "reloadframe", action );
connect( action, SIGNAL(triggered(bool)), this, SLOT( slotReloadFrame() ) );
menu->addAction(action);
action = new KAction( i18n( "Print Frame..." ), this );
d->m_actionCollection->addAction( "printFrame", action );
action->setIcon( KIcon( "document-print-frame" ) );
connect( action, SIGNAL(triggered(bool)), d->m_khtml->browserExtension(), SLOT( print() ) );
menu->addAction(action);
action = new KAction( i18n( "Save &Frame As..." ), this );
d->m_actionCollection->addAction( "saveFrame", action );
connect( action, SIGNAL(triggered(bool)), d->m_khtml, SLOT( slotSaveFrame() ) );
menu->addAction(action);
action = new KAction( i18n( "View Frame Source" ), this );
d->m_actionCollection->addAction( "viewFrameSource", action );
connect( action, SIGNAL(triggered(bool)), d->m_khtml, SLOT( slotViewDocumentSource() ) );
menu->addAction(action);
action = new KAction( i18n( "View Frame Information" ), this );
d->m_actionCollection->addAction( "viewFrameInfo", action );
connect( action, SIGNAL(triggered(bool)), d->m_khtml, SLOT( slotViewPageInfo() ) );
action = new KAction(d->m_actionCollection);
action->setSeparator(true);
menu->addAction(action);
if ( KHTMLGlobal::defaultHTMLSettings()->isAdFilterEnabled() ) {
if ( khtml->d->m_frame->m_type == khtml::ChildFrame::IFrame ) {
action = new KAction( i18n( "Block IFrame..." ), this );
d->m_actionCollection->addAction( "blockiframe", action );
connect( action, SIGNAL(triggered(bool)), this, SLOT( slotBlockIFrame() ) );
menu->addAction(action);
}
}
partActions.append(menu);
}
}
if (isImage) {
if ( e.elementId() == ID_IMG ) {
d->m_imageURL = KUrl( static_cast<DOM::HTMLImageElement>( e ).src().string() );
DOM::HTMLImageElementImpl *imageimpl = static_cast<DOM::HTMLImageElementImpl *>( e.handle() );
Q_ASSERT(imageimpl);
if(imageimpl) // should be true always. right?
{
if(imageimpl->complete()) {
d->m_pixmap = imageimpl->currentPixmap();
}
}
}
else
d->m_imageURL = KUrl( static_cast<DOM::HTMLInputElement>( e ).src().string() );
KAction *action = new KAction( i18n( "Save Image As..." ), this );
d->m_actionCollection->addAction( "saveimageas", action );
connect( action, SIGNAL(triggered(bool)), this, SLOT( slotSaveImageAs() ) );
partActions.append(action);
action = new KAction( i18n( "Send Image..." ), this );
d->m_actionCollection->addAction( "sendimage", action );
connect( action, SIGNAL(triggered(bool)), this, SLOT( slotSendImage() ) );
partActions.append(action);
#ifndef QT_NO_MIMECLIPBOARD
action = new KAction( i18n( "Copy Image" ), this );
d->m_actionCollection->addAction( "copyimage", action );
action->setEnabled(!d->m_pixmap.isNull());
connect( action, SIGNAL(triggered(bool)), this, SLOT( slotCopyImage() ) );
partActions.append(action);
#endif
if(d->m_pixmap.isNull()) { //fallback to image location if still loading the image. this will always be true if ifdef QT_NO_MIMECLIPBOARD
action = new KAction( i18n( "Copy Image Location" ), this );
d->m_actionCollection->addAction( "copyimagelocation", action );
connect( action, SIGNAL(triggered(bool)), this, SLOT( slotCopyImageLocation() ) );
partActions.append(action);
}
QString actionText = d->m_suggestedFilename.isEmpty() ?
KStringHandler::csqueeze(d->m_imageURL.fileName()+d->m_imageURL.query(), 25)
: d->m_suggestedFilename;
action = new KAction( i18n("View Image (%1)", actionText.replace("&", "&&")), this );
d->m_actionCollection->addAction( "viewimage", action );
connect( action, SIGNAL(triggered(bool)), this, SLOT( slotViewImage() ) );
partActions.append(action);
if (KHTMLGlobal::defaultHTMLSettings()->isAdFilterEnabled()) {
action = new KAction( i18n( "Block Image..." ), this );
d->m_actionCollection->addAction( "blockimage", action );
connect( action, SIGNAL(triggered(bool)), this, SLOT( slotBlockImage() ) );
partActions.append(action);
if (!d->m_imageURL.host().isEmpty() &&
- !d->m_imageURL.protocol().isEmpty())
+ !d->m_imageURL.scheme().isEmpty())
{
action = new KAction( i18n( "Block Images From %1" , d->m_imageURL.host()), this );
d->m_actionCollection->addAction( "blockhost", action );
connect( action, SIGNAL(triggered(bool)), this, SLOT( slotBlockHost() ) );
partActions.append(action);
}
}
KAction* separator = new KAction(d->m_actionCollection);
separator->setSeparator(true);
partActions.append(separator);
}
if ( isImage || url.isEmpty() ) {
KAction *action = new KAction( i18n( "Stop Animations" ), this );
d->m_actionCollection->addAction( "stopanimations", action );
connect( action, SIGNAL(triggered(bool)), this, SLOT(slotStopAnimations()) );
partActions.append(action);
KAction* separator = new KAction(d->m_actionCollection);
separator->setSeparator(true);
partActions.append(separator);
}
if (!hasSelection && url.isEmpty()) { // only when right-clicking on the page itself
partActions.append(khtml->actionCollection()->action("viewDocumentSource"));
}
if (!hasSelection && url.isEmpty() && !isImage) {
partActions.append(khtml->actionCollection()->action("setEncoding"));
}
d->actionGroups.insert("partactions", partActions);
}
KHTMLPopupGUIClient::~KHTMLPopupGUIClient()
{
delete d->m_actionCollection;
delete d;
}
void KHTMLPopupGUIClient::addSearchActions(QList<QAction *>& editActions)
{
const QString selectedText = d->m_khtml->simplifiedSelectedText();
if (selectedText.isEmpty())
return;
KUriFilterData data(selectedText);
QStringList alternateProviders;
alternateProviders << "google" << "google_groups" << "google_news" << "webster" << "dmoz" << "wikipedia";
data.setAlternateSearchProviders(alternateProviders);
data.setAlternateDefaultSearchProvider("google");
if (KUriFilter::self()->filterSearchUri(data, KUriFilter::NormalTextFilter)) {
const QString squeezedText = KStringHandler::rsqueeze(selectedText, 21);
KAction *action = new KAction(i18n("Search for '%1' with %2",
squeezedText, data.searchProvider()), this);
action->setData(QUrl(data.uri()));
action->setIcon(KIcon(data.iconName()));
connect(action, SIGNAL(triggered(bool)), d->m_khtml->browserExtension(), SLOT(searchProvider()));
d->m_actionCollection->addAction("defaultSearchProvider", action);
editActions.append(action);
const QStringList preferredSearchProviders = data.preferredSearchProviders();
if (!preferredSearchProviders.isEmpty()) {
KActionMenu* providerList = new KActionMenu(i18n("Search for '%1' with", squeezedText), this);
Q_FOREACH(const QString &searchProvider, preferredSearchProviders) {
if (searchProvider == data.searchProvider())
continue;
KAction *action = new KAction(searchProvider, this);
action->setData(data.queryForPreferredSearchProvider(searchProvider));
d->m_actionCollection->addAction(searchProvider, action);
action->setIcon(KIcon(data.iconNameForPreferredSearchProvider(searchProvider)));
connect(action, SIGNAL(triggered(bool)), d->m_khtml->browserExtension(), SLOT(searchProvider()));
providerList->addAction(action);
}
d->m_actionCollection->addAction("searchProviderList", providerList);
editActions.append(providerList);
}
}
}
QString KHTMLPopupGUIClient::selectedTextAsOneLine(KHTMLPart* part)
{
QString text = part->simplifiedSelectedText();
// in addition to what simplifiedSelectedText does,
// remove linefeeds and any whitespace surrounding it (#113177),
// to get it all in a single line.
text.remove(QRegExp("[\\s]*\\n+[\\s]*"));
return text;
}
void KHTMLPopupGUIClient::openSelection()
{
KParts::BrowserArguments browserArgs;
browserArgs.frameName = "_blank";
emit d->m_khtml->browserExtension()->openUrlRequest(selectedTextAsOneLine(d->m_khtml), KParts::OpenUrlArguments(), browserArgs);
}
KParts::BrowserExtension::ActionGroupMap KHTMLPopupGUIClient::actionGroups() const
{
return d->actionGroups;
}
void KHTMLPopupGUIClient::slotSaveLinkAs()
{
KIO::MetaData metaData;
metaData["referrer"] = d->m_khtml->referrer();
saveURL( d->m_khtml->widget(), i18n( "Save Link As" ), d->m_url, metaData );
}
void KHTMLPopupGUIClient::slotSendImage()
{
QStringList urls;
urls.append( d->m_imageURL.url());
QString subject = d->m_imageURL.url();
KToolInvocation::invokeMailer(QString(), QString(), QString(), subject,
QString(), //body
QString(),
urls); // attachments
}
void KHTMLPopupGUIClient::slotSaveImageAs()
{
KIO::MetaData metaData;
metaData["referrer"] = d->m_khtml->referrer();
saveURL( d->m_khtml->widget(), i18n( "Save Image As" ), d->m_imageURL, metaData, QString(), 0, d->m_suggestedFilename );
}
void KHTMLPopupGUIClient::slotBlockHost()
{
- QString name=d->m_imageURL.protocol()+"://"+d->m_imageURL.host()+"/*";
+ QString name=d->m_imageURL.scheme()+"://"+d->m_imageURL.host()+"/*";
KHTMLGlobal::defaultHTMLSettings()->addAdFilter( name );
d->m_khtml->reparseConfiguration();
}
void KHTMLPopupGUIClient::slotBlockImage()
{
bool ok = false;
QString url = KInputDialog::getText( i18n("Add URL to Filter"),
i18n("Enter the URL:"),
d->m_imageURL.url(),
&ok);
if ( ok ) {
KHTMLGlobal::defaultHTMLSettings()->addAdFilter( url );
d->m_khtml->reparseConfiguration();
}
}
void KHTMLPopupGUIClient::slotBlockIFrame()
{
bool ok = false;
QString url = KInputDialog::getText( i18n( "Add URL to Filter"),
i18n("Enter the URL:"),
d->m_khtml->url().url(),
&ok );
if ( ok ) {
KHTMLGlobal::defaultHTMLSettings()->addAdFilter( url );
d->m_khtml->reparseConfiguration();
}
}
void KHTMLPopupGUIClient::slotCopyLinkLocation()
{
KUrl safeURL(d->m_url);
safeURL.setPass(QString());
#ifndef QT_NO_MIMECLIPBOARD
// Set it in both the mouse selection and in the clipboard
QMimeData* mimeData = new QMimeData;
safeURL.populateMimeData( mimeData );
QApplication::clipboard()->setMimeData( mimeData, QClipboard::Clipboard );
mimeData = new QMimeData;
safeURL.populateMimeData( mimeData );
QApplication::clipboard()->setMimeData( mimeData, QClipboard::Selection );
#else
QApplication::clipboard()->setText( safeURL.url() ); //FIXME(E): Handle multiple entries
#endif
}
void KHTMLPopupGUIClient::slotStopAnimations()
{
d->m_khtml->stopAnimations();
}
void KHTMLPopupGUIClient::slotCopyImage()
{
#ifndef QT_NO_MIMECLIPBOARD
KUrl safeURL(d->m_imageURL);
safeURL.setPass(QString());
// Set it in both the mouse selection and in the clipboard
QMimeData* mimeData = new QMimeData;
mimeData->setImageData( d->m_pixmap );
safeURL.populateMimeData( mimeData );
QApplication::clipboard()->setMimeData( mimeData, QClipboard::Clipboard );
mimeData = new QMimeData;
mimeData->setImageData( d->m_pixmap );
safeURL.populateMimeData( mimeData );
QApplication::clipboard()->setMimeData( mimeData, QClipboard::Selection );
#else
kDebug() << "slotCopyImage called when the clipboard does not support this. This should not be possible.";
#endif
}
void KHTMLPopupGUIClient::slotCopyImageLocation()
{
KUrl safeURL(d->m_imageURL);
safeURL.setPass(QString());
#ifndef QT_NO_MIMECLIPBOARD
// Set it in both the mouse selection and in the clipboard
QMimeData* mimeData = new QMimeData;
safeURL.populateMimeData( mimeData );
QApplication::clipboard()->setMimeData( mimeData, QClipboard::Clipboard );
mimeData = new QMimeData;
safeURL.populateMimeData( mimeData );
QApplication::clipboard()->setMimeData( mimeData, QClipboard::Selection );
#else
QApplication::clipboard()->setText( safeURL.url() ); //FIXME(E): Handle multiple entries
#endif
}
void KHTMLPopupGUIClient::slotViewImage()
{
d->m_khtml->browserExtension()->createNewWindow(d->m_imageURL);
}
void KHTMLPopupGUIClient::slotReloadFrame()
{
KParts::OpenUrlArguments args = d->m_khtml->arguments();
args.setReload( true );
args.metaData()["referrer"] = d->m_khtml->pageReferrer();
// reload document
d->m_khtml->closeUrl();
d->m_khtml->setArguments( args );
d->m_khtml->openUrl( d->m_khtml->url() );
}
void KHTMLPopupGUIClient::slotFrameInWindow()
{
KParts::OpenUrlArguments args = d->m_khtml->arguments();
args.metaData()["referrer"] = d->m_khtml->pageReferrer();
KParts::BrowserArguments browserArgs( d->m_khtml->browserExtension()->browserArguments() );
browserArgs.setForcesNewWindow(true);
emit d->m_khtml->browserExtension()->createNewWindow( d->m_khtml->url(), args, browserArgs );
}
void KHTMLPopupGUIClient::slotFrameInTop()
{
KParts::OpenUrlArguments args = d->m_khtml->arguments();
args.metaData()["referrer"] = d->m_khtml->pageReferrer();
KParts::BrowserArguments browserArgs( d->m_khtml->browserExtension()->browserArguments() );
browserArgs.frameName = "_top";
emit d->m_khtml->browserExtension()->openUrlRequest( d->m_khtml->url(), args, browserArgs );
}
void KHTMLPopupGUIClient::slotFrameInTab()
{
KParts::OpenUrlArguments args = d->m_khtml->arguments();
args.metaData()["referrer"] = d->m_khtml->pageReferrer();
KParts::BrowserArguments browserArgs( d->m_khtml->browserExtension()->browserArguments() );
browserArgs.setNewTab(true);
emit d->m_khtml->browserExtension()->createNewWindow( d->m_khtml->url(), args, browserArgs );
}
void KHTMLPopupGUIClient::saveURL( QWidget *parent, const QString &caption,
const KUrl &url,
const QMap<QString, QString> &metadata,
const QString &filter, long cacheId,
const QString & suggestedFilename )
{
QString name = QLatin1String( "index.html" );
if ( !suggestedFilename.isEmpty() )
name = suggestedFilename;
else if ( !url.fileName(KUrl::ObeyTrailingSlash).isEmpty() )
name = url.fileName(KUrl::ObeyTrailingSlash);
KUrl destURL;
int query;
do {
query = KMessageBox::Yes;
// convert filename to URL using fromPath to avoid trouble with ':' in filenames (#184202)
destURL = KFileDialog::getSaveUrl( KUrl::fromPath(name), filter, parent, caption );
if( destURL.isLocalFile() )
{
QFileInfo info( destURL.toLocalFile() );
if( info.exists() ) {
// TODO: use KIO::RenameDlg (shows more information)
query = KMessageBox::warningContinueCancel( parent, i18n( "A file named \"%1\" already exists. " "Are you sure you want to overwrite it?" , info.fileName() ), i18n( "Overwrite File?" ), KGuiItem(i18n( "Overwrite" )) );
}
}
} while ( query == KMessageBox::Cancel );
if ( destURL.isValid() )
saveURL(parent, url, destURL, metadata, cacheId);
}
void KHTMLPopupGUIClient::saveURL( QWidget* parent, const KUrl &url, const KUrl &destURL,
const QMap<QString, QString> &metadata,
long cacheId )
{
if ( destURL.isValid() )
{
bool saved = false;
if (KHTMLPageCache::self()->isComplete(cacheId))
{
if (destURL.isLocalFile())
{
KSaveFile destFile(destURL.toLocalFile());
if (destFile.open())
{
QDataStream stream ( &destFile );
KHTMLPageCache::self()->saveData(cacheId, &stream);
saved = true;
}
}
else
{
// save to temp file, then move to final destination.
KTemporaryFile destFile;
if (destFile.open())
{
QDataStream stream ( &destFile );
KHTMLPageCache::self()->saveData(cacheId, &stream);
KUrl url2 = KUrl();
url2.setPath(destFile.fileName());
KIO::file_move(url2, destURL, -1, KIO::Overwrite);
saved = true;
}
}
}
if(!saved)
{
// DownloadManager <-> konqueror integration
// find if the integration is enabled
// the empty key means no integration
// only use download manager for non-local urls!
bool downloadViaKIO = true;
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
{
downloadViaKIO = false;
KUrl cleanDest = destURL;
cleanDest.setPass( QString() ); // don't put password into commandline
cmd += ' ' + KShell::quoteArg(url.url()) + ' ' +
KShell::quoteArg(cleanDest.url());
kDebug(1000) << "Calling command "<<cmd;
KRun::runCommand(cmd, parent->topLevelWidget());
}
}
}
if ( downloadViaKIO )
{
KParts::BrowserRun::saveUrlUsingKIO(url, destURL, parent, metadata);
}
} //end if(!saved)
}
}
KHTMLPartBrowserHostExtension::KHTMLPartBrowserHostExtension( KHTMLPart *part )
: KParts::BrowserHostExtension( part )
{
m_part = part;
}
KHTMLPartBrowserHostExtension::~KHTMLPartBrowserHostExtension()
{
}
QStringList KHTMLPartBrowserHostExtension::frameNames() const
{
return m_part->frameNames();
}
const QList<KParts::ReadOnlyPart*> KHTMLPartBrowserHostExtension::frames() const
{
return m_part->frames();
}
bool KHTMLPartBrowserHostExtension::openUrlInFrame(const KUrl &url, const KParts::OpenUrlArguments& arguments, const KParts::BrowserArguments &browserArguments)
{
return m_part->openUrlInFrame( url, arguments, browserArguments );
}
KParts::BrowserHostExtension* KHTMLPartBrowserHostExtension::findFrameParent( KParts::ReadOnlyPart
*callingPart, const QString &frame )
{
KHTMLPart *parentPart = m_part->d->findFrameParent(callingPart, frame, 0, true /* navigation*/);
if (parentPart)
return parentPart->browserHostExtension();
return 0;
}
// defined in khtml_part.cpp
extern const int KDE_NO_EXPORT fastZoomSizes[];
extern const int KDE_NO_EXPORT fastZoomSizeCount;
KHTMLZoomFactorAction::KHTMLZoomFactorAction( KHTMLPart *part, bool direction, const QString &icon, const QString &text, QObject *parent )
: KSelectAction( text, parent )
{
setIcon( KIcon( icon ) );
setToolBarMode(MenuMode);
setToolButtonPopupMode(QToolButton::DelayedPopup);
init(part, direction);
}
void KHTMLZoomFactorAction::init(KHTMLPart *part, bool direction)
{
m_direction = direction;
m_part = part;
// xgettext: no-c-format
addAction( i18n( "Default Font Size (100%)" ) );
int m = m_direction ? 1 : -1;
int ofs = fastZoomSizeCount / 2; // take index of 100%
// this only works if there is an odd number of elements in fastZoomSizes[]
for ( int i = m; i != m*(ofs+1); i += m )
{
int num = i * m;
QString numStr = QString::number( num );
if ( num > 0 ) numStr.prepend( QLatin1Char('+') );
// xgettext: no-c-format
addAction( i18n( "%1%" , fastZoomSizes[ofs + i] ) );
}
connect( selectableActionGroup(), SIGNAL( triggered(QAction*) ), this, SLOT( slotTriggered(QAction*) ) );
}
KHTMLZoomFactorAction::~KHTMLZoomFactorAction()
{
}
void KHTMLZoomFactorAction::slotTriggered(QAction* action)
{
int idx = selectableActionGroup()->actions().indexOf(action);
if (idx == 0)
m_part->setFontScaleFactor(100);
else
m_part->setFontScaleFactor(fastZoomSizes[fastZoomSizeCount/2 + (m_direction ? 1 : -1)*idx]);
setCurrentAction( 0L );
}
KHTMLTextExtension::KHTMLTextExtension(KHTMLPart* part)
: KParts::TextExtension(part)
{
connect(part, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged()));
}
KHTMLPart* KHTMLTextExtension::part() const
{
return static_cast<KHTMLPart*>(parent());
}
bool KHTMLTextExtension::hasSelection() const
{
return part()->hasSelection();
}
QString KHTMLTextExtension::selectedText(Format format) const
{
switch(format) {
case PlainText:
return part()->selectedText();
case HTML:
return part()->selectedTextAsHTML();
}
return QString();
}
QString KHTMLTextExtension::completeText(Format format) const
{
switch(format) {
case PlainText:
return part()->htmlDocument().body().innerText().string();
case HTML:
return part()->htmlDocument().body().innerHTML().string();
}
return QString();
}
////
KHTMLHtmlExtension::KHTMLHtmlExtension(KHTMLPart* part)
: KParts::HtmlExtension(part)
{
}
KUrl KHTMLHtmlExtension::baseUrl() const
{
return part()->baseURL();
}
bool KHTMLHtmlExtension::hasSelection() const
{
return part()->hasSelection();
}
KParts::SelectorInterface::QueryMethods KHTMLHtmlExtension::supportedQueryMethods() const
{
return (KParts::SelectorInterface::SelectedContent | KParts::SelectorInterface::EntireContent);
}
static KParts::SelectorInterface::Element convertDomElement(const DOM::ElementImpl* domElem)
{
KParts::SelectorInterface::Element elem;
elem.setTagName(domElem->tagName().string());
const DOM::NamedAttrMapImpl* attrMap = domElem->attributes(true /*readonly*/);
if (attrMap) {
for (unsigned i = 0; i < attrMap->length(); ++i) {
const DOM::AttributeImpl& attr = attrMap->attributeAt(i);
elem.setAttribute(attr.localName().string(), attr.value().string());
// we could have a setAttributeNS too.
}
}
return elem;
}
KParts::SelectorInterface::Element KHTMLHtmlExtension::querySelector(const QString& query, KParts::SelectorInterface::QueryMethod method) const
{
KParts::SelectorInterface::Element element;
// If the specified method is None, return an empty list; similarly
// if the document is null, which may be possible in case of an error
if (method == KParts::SelectorInterface::None || part()->document().isNull())
return element;
if (!(supportedQueryMethods() & method))
return element;
switch (method) {
case KParts::SelectorInterface::EntireContent: {
int ec = 0; // exceptions are ignored
WTF::RefPtr<DOM::ElementImpl> domElem = part()->document().handle()->querySelector(query, ec);
element = convertDomElement(domElem.get());
break;
}
case KParts::SelectorInterface::SelectedContent:
if (part()->hasSelection()) {
DOM::Element domElem = part()->selection().cloneContents().querySelector(query);
element = convertDomElement(static_cast<DOM::ElementImpl*>(domElem.handle()));
}
break;
default:
break;
}
return element;
}
QList<KParts::SelectorInterface::Element> KHTMLHtmlExtension::querySelectorAll(const QString& query, KParts::SelectorInterface::QueryMethod method) const
{
QList<KParts::SelectorInterface::Element> elements;
// If the specified method is None, return an empty list; similarly
// if the document is null, which may be possible in case of an error
if (method == KParts::SelectorInterface::None || part()->document().isNull())
return elements;
// If the specified method is not supported, return an empty list...
if (!(supportedQueryMethods() & method))
return elements;
switch (method) {
case KParts::SelectorInterface::EntireContent: {
int ec = 0; // exceptions are ignored
WTF::RefPtr<DOM::NodeListImpl> nodes = part()->document().handle()->querySelectorAll(query, ec);
const unsigned long len = nodes->length();
elements.reserve(len);
for (unsigned long i = 0; i < len; ++i) {
DOM::NodeImpl* node = nodes->item(i);
if (node->isElementNode()) { // should be always true
elements.append(convertDomElement(static_cast<DOM::ElementImpl*>(node)));
}
}
break;
}
case KParts::SelectorInterface::SelectedContent:
if (part()->hasSelection()) {
DOM::NodeList nodes = part()->selection().cloneContents().querySelectorAll(query);
const unsigned long len = nodes.length();
for (unsigned long i = 0; i < len; ++i) {
DOM::NodeImpl* node = nodes.item(i).handle();
if (node->isElementNode())
elements.append(convertDomElement(static_cast<DOM::ElementImpl*>(node)));
}
}
break;
default:
break;
}
return elements;
}
KHTMLPart* KHTMLHtmlExtension::part() const
{
return static_cast<KHTMLPart*>(parent());
}
diff --git a/khtml/khtml_part.cpp b/khtml/khtml_part.cpp
index c9a66e7d82..02ee81d637 100644
--- a/khtml/khtml_part.cpp
+++ b/khtml/khtml_part.cpp
@@ -1,7445 +1,7445 @@
/* This file is part of the KDE project
*
* Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
* 1999 Lars Knoll <knoll@kde.org>
* 1999 Antti Koivisto <koivisto@kde.org>
* 2000 Simon Hausmann <hausmann@kde.org>
* 2000 Stefan Schimanski <1Stein@gmx.de>
* 2001-2005 George Staikos <staikos@kde.org>
* 2001-2003 Dirk Mueller <mueller@kde.org>
* 2000-2005 David Faure <faure@kde.org>
* 2002 Apple Computer, Inc.
* 2010 Maksim Orlovich (maksim@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.
*/
//#define SPEED_DEBUG
#include "khtml_part.h"
#include "ui_htmlpageinfo.h"
#include "khtmlviewbar.h"
#include "khtml_pagecache.h"
#include "dom/dom_string.h"
#include "dom/dom_element.h"
#include "dom/dom_exception.h"
#include "dom/html_document.h"
#include "dom/dom2_range.h"
#include "editing/editor.h"
#include "html/html_documentimpl.h"
#include "html/html_baseimpl.h"
#include "html/html_objectimpl.h"
#include "html/html_miscimpl.h"
#include "html/html_imageimpl.h"
#include "imload/imagemanager.h"
#include "rendering/render_text.h"
#include "rendering/render_frames.h"
#include "rendering/render_layer.h"
#include "rendering/render_position.h"
#include "misc/loader.h"
#include "misc/khtml_partaccessor.h"
#include "xml/dom2_eventsimpl.h"
#include "xml/dom2_rangeimpl.h"
#include "xml/xml_tokenizer.h"
#include "css/cssstyleselector.h"
#include "css/csshelper.h"
using namespace DOM;
#include "khtmlview.h"
#include <kparts/partmanager.h>
#include <kparts/browseropenorsavequestion.h>
#include <kacceleratormanager.h>
#include "ecma/kjs_proxy.h"
#include "ecma/kjs_window.h"
#include "khtml_settings.h"
#include "kjserrordlg.h"
#include <kjs/function.h>
#include <kjs/interpreter.h>
#include <sys/types.h>
#include <assert.h>
#include <unistd.h>
#include <config.h>
#include <kstandarddirs.h>
#include <kstringhandler.h>
#include <kio/job.h>
#include <kio/jobuidelegate.h>
#include <kio/global.h>
#include <kio/netaccess.h>
#include <kio/hostinfo_p.h>
#include <kprotocolmanager.h>
#include <kdebug.h>
#include <kicon.h>
#include <kiconloader.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kstandardaction.h>
#include <kstandardguiitem.h>
#include <kactioncollection.h>
#include <kfiledialog.h>
#include <kmimetypetrader.h>
#include <ktemporaryfile.h>
#include <kglobalsettings.h>
#include <ktoolinvocation.h>
#include <kauthorized.h>
#include <kparts/browserinterface.h>
#include <kparts/scriptableextension.h>
#include <kde_file.h>
#include <kactionmenu.h>
#include <ktoggleaction.h>
#include <kcodecaction.h>
#include <kselectaction.h>
#include <ksslinfodialog.h>
#include <ksslsettings.h>
#include <kfileitem.h>
#include <kurifilter.h>
#include <kstatusbar.h>
#include <kurllabel.h>
#include <QClipboard>
#include <QToolTip>
#include <QtCore/QFile>
#include <QtCore/QMetaEnum>
#include <QTextDocument>
#include <QtCore/QDate>
#include <QtNetwork/QSslCertificate>
#include "khtmlpart_p.h"
#include "khtml_iface.h"
#include "kpassivepopup.h"
#include "kmenu.h"
#include "rendering/render_form.h"
#include <kwindowsystem.h>
#include <kconfiggroup.h>
#include "ecma/debugger/debugwindow.h"
// SVG
#include <svg/SVGDocument.h>
bool KHTMLPartPrivate::s_dnsInitialised = false;
// DNS prefetch settings
static const int sMaxDNSPrefetchPerPage = 42;
static const int sDNSPrefetchTimerDelay = 200;
static const int sDNSTTLSeconds = 400;
static const int sDNSCacheSize = 500;
namespace khtml {
class PartStyleSheetLoader : public CachedObjectClient
{
public:
PartStyleSheetLoader(KHTMLPart *part, DOM::DOMString url, DocLoader* dl)
{
m_part = part;
m_cachedSheet = dl->requestStyleSheet(url, QString(), "text/css",
true /* "user sheet" */);
if (m_cachedSheet)
m_cachedSheet->ref( this );
}
virtual ~PartStyleSheetLoader()
{
if ( m_cachedSheet ) m_cachedSheet->deref(this);
}
virtual void setStyleSheet(const DOM::DOMString&, const DOM::DOMString &sheet, const DOM::DOMString &, const DOM::DOMString &/*mimetype*/)
{
if ( m_part )
m_part->setUserStyleSheet( sheet.string() );
delete this;
}
virtual void error( int, const QString& ) {
delete this;
}
QPointer<KHTMLPart> m_part;
khtml::CachedCSSStyleSheet *m_cachedSheet;
};
}
KHTMLPart::KHTMLPart( QWidget *parentWidget, QObject *parent, GUIProfile prof )
: KParts::ReadOnlyPart( parent )
{
d = 0;
KHTMLGlobal::registerPart( this );
setComponentData( KHTMLGlobal::componentData(), false );
init( new KHTMLView( this, parentWidget ), prof );
}
KHTMLPart::KHTMLPart( KHTMLView *view, QObject *parent, GUIProfile prof )
: KParts::ReadOnlyPart( parent )
{
d = 0;
KHTMLGlobal::registerPart( this );
setComponentData( KHTMLGlobal::componentData(), false );
assert( view );
if (!view->part())
view->setPart( this );
init( view, prof );
}
void KHTMLPart::init( KHTMLView *view, GUIProfile prof )
{
if ( prof == DefaultGUI )
setXMLFile( "khtml.rc" );
else if ( prof == BrowserViewGUI )
setXMLFile( "khtml_browser.rc" );
d = new KHTMLPartPrivate(this, parent());
d->m_view = view;
if (!parentPart()) {
QWidget *widget = new QWidget( view->parentWidget() );
widget->setObjectName("khtml_part_widget");
QVBoxLayout *layout = new QVBoxLayout( widget );
layout->setContentsMargins( 0, 0, 0, 0 );
layout->setSpacing( 0 );
widget->setLayout( layout );
d->m_topViewBar = new KHTMLViewBar( KHTMLViewBar::Top, d->m_view, widget );
d->m_bottomViewBar = new KHTMLViewBar( KHTMLViewBar::Bottom, d->m_view, widget );
layout->addWidget( d->m_topViewBar );
layout->addWidget( d->m_view );
layout->addWidget( d->m_bottomViewBar );
setWidget( widget );
widget->setFocusProxy( d->m_view );
} else {
setWidget( view );
}
d->m_guiProfile = prof;
d->m_extension = new KHTMLPartBrowserExtension( this );
d->m_extension->setObjectName( "KHTMLBrowserExtension" );
d->m_hostExtension = new KHTMLPartBrowserHostExtension( this );
d->m_statusBarExtension = new KParts::StatusBarExtension( this );
d->m_scriptableExtension = new KJS::KHTMLPartScriptable( this );
new KHTMLTextExtension( this );
new KHTMLHtmlExtension( this );
d->m_statusBarPopupLabel = 0L;
d->m_openableSuppressedPopups = 0;
d->m_paLoadImages = 0;
d->m_paDebugScript = 0;
d->m_bMousePressed = false;
d->m_bRightMousePressed = false;
d->m_bCleared = false;
if ( prof == BrowserViewGUI ) {
d->m_paViewDocument = new KAction( i18n( "View Do&cument Source" ), this );
actionCollection()->addAction( "viewDocumentSource", d->m_paViewDocument );
connect( d->m_paViewDocument, SIGNAL( triggered( bool ) ), this, SLOT( slotViewDocumentSource() ) );
if (!parentPart()) {
d->m_paViewDocument->setShortcut( QKeySequence(Qt::CTRL + Qt::Key_U) );
}
d->m_paViewFrame = new KAction( i18n( "View Frame Source" ), this );
actionCollection()->addAction( "viewFrameSource", d->m_paViewFrame );
connect( d->m_paViewFrame, SIGNAL( triggered( bool ) ), this, SLOT( slotViewFrameSource() ) );
if (!parentPart()) {
d->m_paViewFrame->setShortcut( QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_U) );
}
d->m_paViewInfo = new KAction( i18n( "View Document Information" ), this );
actionCollection()->addAction( "viewPageInfo", d->m_paViewInfo );
if (!parentPart()) {
d->m_paViewInfo->setShortcut( QKeySequence(Qt::CTRL+Qt::Key_I) );
}
connect( d->m_paViewInfo, SIGNAL( triggered( bool ) ), this, SLOT( slotViewPageInfo() ) );
d->m_paSaveBackground = new KAction( i18n( "Save &Background Image As..." ), this );
actionCollection()->addAction( "saveBackground", d->m_paSaveBackground );
connect( d->m_paSaveBackground, SIGNAL( triggered( bool ) ), this, SLOT( slotSaveBackground() ) );
d->m_paSaveDocument = actionCollection()->addAction( KStandardAction::SaveAs, "saveDocument",
this, SLOT( slotSaveDocument() ) );
if ( parentPart() )
d->m_paSaveDocument->setShortcuts( KShortcut() ); // avoid clashes
d->m_paSaveFrame = new KAction( i18n( "Save &Frame As..." ), this );
actionCollection()->addAction( "saveFrame", d->m_paSaveFrame );
connect( d->m_paSaveFrame, SIGNAL( triggered( bool ) ), this, SLOT( slotSaveFrame() ) );
} else {
d->m_paViewDocument = 0;
d->m_paViewFrame = 0;
d->m_paViewInfo = 0;
d->m_paSaveBackground = 0;
d->m_paSaveDocument = 0;
d->m_paSaveFrame = 0;
}
d->m_paSecurity = new KAction( i18n( "SSL" ), this );
actionCollection()->addAction( "security", d->m_paSecurity );
connect( d->m_paSecurity, SIGNAL( triggered( bool ) ), this, SLOT( slotSecurity() ) );
d->m_paDebugRenderTree = new KAction( i18n( "Print Rendering Tree to STDOUT" ), this );
actionCollection()->addAction( "debugRenderTree", d->m_paDebugRenderTree );
connect( d->m_paDebugRenderTree, SIGNAL( triggered( bool ) ), this, SLOT( slotDebugRenderTree() ) );
d->m_paDebugDOMTree = new KAction( i18n( "Print DOM Tree to STDOUT" ), this );
actionCollection()->addAction( "debugDOMTree", d->m_paDebugDOMTree );
connect( d->m_paDebugDOMTree, SIGNAL( triggered( bool ) ), this, SLOT( slotDebugDOMTree() ) );
KAction* paDebugFrameTree = new KAction( i18n( "Print frame tree to STDOUT" ), this );
actionCollection()->addAction( "debugFrameTree", paDebugFrameTree );
connect( paDebugFrameTree, SIGNAL( triggered( bool ) ), this, SLOT( slotDebugFrameTree() ) );
d->m_paStopAnimations = new KAction( i18n( "Stop Animated Images" ), this );
actionCollection()->addAction( "stopAnimations", d->m_paStopAnimations );
connect( d->m_paStopAnimations, SIGNAL( triggered( bool ) ), this, SLOT( slotStopAnimations() ) );
d->m_paSetEncoding = new KCodecAction( KIcon("character-set"), i18n( "Set &Encoding" ), this, true );
actionCollection()->addAction( "setEncoding", d->m_paSetEncoding );
// d->m_paSetEncoding->setDelayed( false );
connect( d->m_paSetEncoding, SIGNAL(triggered(const QString&)), this, SLOT( slotSetEncoding(const QString &)));
connect( d->m_paSetEncoding, SIGNAL(triggered(KEncodingDetector::AutoDetectScript)), this, SLOT( slotAutomaticDetectionLanguage(KEncodingDetector::AutoDetectScript)));
if ( KGlobal::config()->hasGroup( "HTML Settings" ) ) {
KConfigGroup config( KGlobal::config(), "HTML Settings" );
d->m_autoDetectLanguage = static_cast<KEncodingDetector::AutoDetectScript>(config.readEntry( "AutomaticDetectionLanguage", /*static_cast<int>(language) */0));
if (d->m_autoDetectLanguage==KEncodingDetector::None) {
const QByteArray name = KGlobal::locale()->encoding().toLower();
// kWarning() << "00000000 ";
if (name.endsWith("1251")||name.startsWith("koi")||name=="iso-8859-5")
d->m_autoDetectLanguage=KEncodingDetector::Cyrillic;
else if (name.endsWith("1256")||name=="iso-8859-6")
d->m_autoDetectLanguage=KEncodingDetector::Arabic;
else if (name.endsWith("1257")||name=="iso-8859-13"||name=="iso-8859-4")
d->m_autoDetectLanguage=KEncodingDetector::Baltic;
else if (name.endsWith("1250")|| name=="ibm852" || name=="iso-8859-2" || name=="iso-8859-3" )
d->m_autoDetectLanguage=KEncodingDetector::CentralEuropean;
else if (name.endsWith("1253")|| name=="iso-8859-7" )
d->m_autoDetectLanguage=KEncodingDetector::Greek;
else if (name.endsWith("1255")|| name=="iso-8859-8" || name=="iso-8859-8-i" )
d->m_autoDetectLanguage=KEncodingDetector::Hebrew;
else if (name=="jis7" || name=="eucjp" || name=="sjis" )
d->m_autoDetectLanguage=KEncodingDetector::Japanese;
else if (name.endsWith("1254")|| name=="iso-8859-9" )
d->m_autoDetectLanguage=KEncodingDetector::Turkish;
else if (name.endsWith("1252")|| name=="iso-8859-1" || name=="iso-8859-15" )
d->m_autoDetectLanguage=KEncodingDetector::WesternEuropean;
else
d->m_autoDetectLanguage=KEncodingDetector::SemiautomaticDetection;
// kWarning() << "0000000end " << d->m_autoDetectLanguage << " " << KGlobal::locale()->encodingMib();
}
d->m_paSetEncoding->setCurrentAutoDetectScript(d->m_autoDetectLanguage);
}
d->m_paUseStylesheet = new KSelectAction( i18n( "Use S&tylesheet"), this );
actionCollection()->addAction( "useStylesheet", d->m_paUseStylesheet );
connect( d->m_paUseStylesheet, SIGNAL( triggered( int ) ), this, SLOT( slotUseStylesheet() ) );
if ( prof == BrowserViewGUI ) {
d->m_paIncZoomFactor = new KHTMLZoomFactorAction( this, true, "format-font-size-more", i18n( "Enlarge Font" ), this );
actionCollection()->addAction( "incFontSizes", d->m_paIncZoomFactor );
connect(d->m_paIncZoomFactor, SIGNAL(triggered(bool)), SLOT( slotIncFontSizeFast() ));
d->m_paIncZoomFactor->setWhatsThis( i18n( "<qt>Enlarge Font<br /><br />"
"Make the font in this window bigger. "
"Click and hold down the mouse button for a menu with all available font sizes.</qt>" ) );
d->m_paDecZoomFactor = new KHTMLZoomFactorAction( this, false, "format-font-size-less", i18n( "Shrink Font" ), this );
actionCollection()->addAction( "decFontSizes", d->m_paDecZoomFactor );
connect(d->m_paDecZoomFactor, SIGNAL(triggered(bool)), SLOT( slotDecFontSizeFast() ));
d->m_paDecZoomFactor->setWhatsThis( i18n( "<qt>Shrink Font<br /><br />"
"Make the font in this window smaller. "
"Click and hold down the mouse button for a menu with all available font sizes.</qt>" ) );
if (!parentPart()) {
// For framesets, this action also affects frames, so only
// the frameset needs to define a shortcut for the action.
// TODO: Why also CTRL+=? Because of http://trolltech.com/developer/knowledgebase/524/?
// Nobody else does it...
d->m_paIncZoomFactor->setShortcut( KShortcut("CTRL++; CTRL+=") );
d->m_paDecZoomFactor->setShortcut( QKeySequence(Qt::CTRL + Qt::Key_Minus) );
}
}
d->m_paFind = actionCollection()->addAction( KStandardAction::Find, "find", this, SLOT( slotFind() ) );
d->m_paFind->setWhatsThis( i18n( "<qt>Find text<br /><br />"
"Shows a dialog that allows you to find text on the displayed page.</qt>" ) );
d->m_paFindNext = actionCollection()->addAction( KStandardAction::FindNext, "findNext", this, SLOT( slotFindNext() ) );
d->m_paFindNext->setWhatsThis( i18n( "<qt>Find next<br /><br />"
"Find the next occurrence of the text that you "
"have found using the <b>Find Text</b> function.</qt>" ) );
d->m_paFindPrev = actionCollection()->addAction( KStandardAction::FindPrev, "findPrevious",
this, SLOT( slotFindPrev() ) );
d->m_paFindPrev->setWhatsThis( i18n( "<qt>Find previous<br /><br />"
"Find the previous occurrence of the text that you "
"have found using the <b>Find Text</b> function.</qt>" ) );
// These two actions aren't visible in the menus, but exist for the (configurable) shortcut
d->m_paFindAheadText = new KAction( i18n("Find Text as You Type"), this );
actionCollection()->addAction( "findAheadText", d->m_paFindAheadText );
d->m_paFindAheadText->setShortcuts( KShortcut( '/' ) );
d->m_paFindAheadText->setHelpText(i18n("This shortcut shows the find bar, for finding text in the displayed page. It cancels the effect of \"Find Links as You Type\", which sets the \"Find links only\" option."));
connect( d->m_paFindAheadText, SIGNAL( triggered( bool ) ), this, SLOT( slotFindAheadText()) );
d->m_paFindAheadLinks = new KAction( i18n("Find Links as You Type"), this );
actionCollection()->addAction( "findAheadLink", d->m_paFindAheadLinks );
// The issue is that it sets the (sticky) option FindLinksOnly, so
// if you trigger this shortcut once by mistake, Esc and Ctrl+F will still have the option set.
// Better let advanced users configure a shortcut for this advanced option
//d->m_paFindAheadLinks->setShortcuts( KShortcut( '\'' ) );
d->m_paFindAheadLinks->setHelpText(i18n("This shortcut shows the find bar, and sets the option \"Find links only\"."));
connect( d->m_paFindAheadLinks, SIGNAL( triggered( bool ) ), this, SLOT( slotFindAheadLink() ) );
if ( parentPart() )
{
d->m_paFind->setShortcuts( KShortcut() ); // avoid clashes
d->m_paFindNext->setShortcuts( KShortcut() ); // avoid clashes
d->m_paFindPrev->setShortcuts( KShortcut() ); // avoid clashes
d->m_paFindAheadText->setShortcuts( KShortcut());
d->m_paFindAheadLinks->setShortcuts( KShortcut());
}
d->m_paPrintFrame = new KAction( i18n( "Print Frame..." ), this );
actionCollection()->addAction( "printFrame", d->m_paPrintFrame );
d->m_paPrintFrame->setIcon( KIcon( "document-print-frame" ) );
connect( d->m_paPrintFrame, SIGNAL( triggered( bool ) ), this, SLOT( slotPrintFrame() ) );
d->m_paPrintFrame->setWhatsThis( i18n( "<qt>Print Frame<br /><br />"
"Some pages have several frames. To print only a single frame, click "
"on it and then use this function.</qt>" ) );
// Warning: The name selectAll is used hardcoded by some 3rd parties to remove the
// shortcut for selectAll so they do not get ambigous shortcuts. Renaming it
// will either crash or render useless that workaround. It would be better
// to use the name KStandardAction::name(KStandardAction::SelectAll) but we
// can't for the same reason.
d->m_paSelectAll = actionCollection()->addAction( KStandardAction::SelectAll, "selectAll",
this, SLOT( slotSelectAll() ) );
if ( parentPart() ) // Only the frameset has the shortcut, but the slot uses the current frame.
d->m_paSelectAll->setShortcuts( KShortcut() ); // avoid clashes
d->m_paToggleCaretMode = new KToggleAction(i18n("Toggle Caret Mode"), this );
actionCollection()->addAction( "caretMode", d->m_paToggleCaretMode );
d->m_paToggleCaretMode->setShortcut( QKeySequence(Qt::Key_F7) );
connect( d->m_paToggleCaretMode, SIGNAL( triggered( bool ) ), this, SLOT(slotToggleCaretMode()) );
d->m_paToggleCaretMode->setChecked(isCaretMode());
if (parentPart())
d->m_paToggleCaretMode->setShortcut(KShortcut()); // avoid clashes
// set the default java(script) flags according to the current host.
d->m_bOpenMiddleClick = d->m_settings->isOpenMiddleClickEnabled();
d->m_bJScriptEnabled = d->m_settings->isJavaScriptEnabled();
setDebugScript( d->m_settings->isJavaScriptDebugEnabled() );
d->m_bJavaEnabled = d->m_settings->isJavaEnabled();
d->m_bPluginsEnabled = d->m_settings->isPluginsEnabled();
// Set the meta-refresh flag...
d->m_metaRefreshEnabled = d->m_settings->isAutoDelayedActionsEnabled ();
KHTMLSettings::KSmoothScrollingMode ssm = d->m_settings->smoothScrolling();
if (ssm == KHTMLSettings::KSmoothScrollingDisabled)
d->m_view->setSmoothScrollingModeDefault(KHTMLView::SSMDisabled);
else if (ssm == KHTMLSettings::KSmoothScrollingWhenEfficient)
d->m_view->setSmoothScrollingModeDefault(KHTMLView::SSMWhenEfficient);
else
d->m_view->setSmoothScrollingModeDefault(KHTMLView::SSMEnabled);
if (d->m_bDNSPrefetchIsDefault && !onlyLocalReferences()) {
KHTMLSettings::KDNSPrefetch dpm = d->m_settings->dnsPrefetch();
if (dpm == KHTMLSettings::KDNSPrefetchDisabled)
d->m_bDNSPrefetch = DNSPrefetchDisabled;
else if (dpm == KHTMLSettings::KDNSPrefetchOnlyWWWAndSLD)
d->m_bDNSPrefetch = DNSPrefetchOnlyWWWAndSLD;
else
d->m_bDNSPrefetch = DNSPrefetchEnabled;
}
if (!KHTMLPartPrivate::s_dnsInitialised && d->m_bDNSPrefetch != DNSPrefetchDisabled) {
KIO::HostInfo::setCacheSize( sDNSCacheSize );
KIO::HostInfo::setTTL( sDNSTTLSeconds );
KHTMLPartPrivate::s_dnsInitialised = true;
}
// all shortcuts should only be active, when this part has focus
foreach ( QAction *action, actionCollection ()->actions () ) {
action->setShortcutContext ( Qt::WidgetWithChildrenShortcut );
}
actionCollection()->associateWidget(view);
connect( view, SIGNAL( zoomView( int ) ), SLOT( slotZoomView( int ) ) );
connect( this, SIGNAL( completed() ),
this, SLOT( updateActions() ) );
connect( this, SIGNAL( completed( bool ) ),
this, SLOT( updateActions() ) );
connect( this, SIGNAL( started( KIO::Job * ) ),
this, SLOT( updateActions() ) );
// #### FIXME: the process wide loader is going to signal every part about every loaded object.
// That's quite inefficient. Should be per-document-tree somehow. Even signaling to
// child parts that a request from an ancestor has loaded is inefficent..
connect( khtml::Cache::loader(), SIGNAL( requestStarted( khtml::DocLoader*, khtml::CachedObject* ) ),
this, SLOT( slotLoaderRequestStarted( khtml::DocLoader*, khtml::CachedObject* ) ) );
connect( khtml::Cache::loader(), SIGNAL( requestDone( khtml::DocLoader*, khtml::CachedObject *) ),
this, SLOT( slotLoaderRequestDone( khtml::DocLoader*, khtml::CachedObject *) ) );
connect( khtml::Cache::loader(), SIGNAL( requestFailed( khtml::DocLoader*, khtml::CachedObject *) ),
this, SLOT( slotLoaderRequestDone( khtml::DocLoader*, khtml::CachedObject *) ) );
connect ( &d->m_progressUpdateTimer, SIGNAL( timeout() ), this, SLOT( slotProgressUpdate() ) );
findTextBegin(); //reset find variables
connect( &d->m_redirectionTimer, SIGNAL( timeout() ),
this, SLOT( slotRedirect() ) );
if (QDBusConnection::sessionBus().isConnected()) {
new KHTMLPartIface(this); // our "adaptor"
for (int i = 1; ; ++i)
if (QDBusConnection::sessionBus().registerObject(QString("/KHTML/%1/widget").arg(i), this))
break;
else if (i == 0xffff)
kFatal() << "Something is very wrong in KHTMLPart!";
}
if (prof == BrowserViewGUI && !parentPart())
loadPlugins();
// "khtml" catalog does not exist, our translations are in kdelibs.
// removing this catalog from KGlobal::locale() prevents problems
// with changing the language in applications at runtime -Thomas Reitelbach
// DF: a better fix would be to set the right catalog name in the KComponentData!
KGlobal::locale()->removeCatalog("khtml");
}
KHTMLPart::~KHTMLPart()
{
kDebug(6050) << this;
KConfigGroup config( KGlobal::config(), "HTML Settings" );
config.writeEntry( "AutomaticDetectionLanguage", int(d->m_autoDetectLanguage) );
if (d->m_manager) { // the PartManager for this part's children
d->m_manager->removePart(this);
}
slotWalletClosed();
if (!parentPart()) { // only delete it if the top khtml_part closes
removeJSErrorExtension();
}
stopAutoScroll();
d->m_redirectionTimer.stop();
if (!d->m_bComplete)
closeUrl();
disconnect( khtml::Cache::loader(), SIGNAL( requestStarted( khtml::DocLoader*, khtml::CachedObject* ) ),
this, SLOT( slotLoaderRequestStarted( khtml::DocLoader*, khtml::CachedObject* ) ) );
disconnect( khtml::Cache::loader(), SIGNAL( requestDone( khtml::DocLoader*, khtml::CachedObject *) ),
this, SLOT( slotLoaderRequestDone( khtml::DocLoader*, khtml::CachedObject *) ) );
disconnect( khtml::Cache::loader(), SIGNAL( requestFailed( khtml::DocLoader*, khtml::CachedObject *) ),
this, SLOT( slotLoaderRequestDone( khtml::DocLoader*, khtml::CachedObject *) ) );
clear();
hide();
if ( d->m_view )
{
d->m_view->m_part = 0;
}
// Have to delete this here since we forward declare it in khtmlpart_p and
// at least some compilers won't call the destructor in this case.
delete d->m_jsedlg;
d->m_jsedlg = 0;
if (!parentPart()) // only delete d->m_frame if the top khtml_part closes
delete d->m_frame;
else if (d->m_frame && d->m_frame->m_run) // for kids, they may get detached while
d->m_frame->m_run.data()->abort(); // resolving mimetype; cancel that if needed
delete d; d = 0;
KHTMLGlobal::deregisterPart( this );
}
bool KHTMLPart::restoreURL( const KUrl &url )
{
kDebug( 6050 ) << url;
d->m_redirectionTimer.stop();
/*
* That's not a good idea as it will call closeUrl() on all
* child frames, preventing them from further loading. This
* method gets called from restoreState() in case of a full frameset
* restoral, and restoreState() calls closeUrl() before restoring
* anyway.
kDebug( 6050 ) << "closing old URL";
closeUrl();
*/
d->m_bComplete = false;
d->m_bLoadEventEmitted = false;
d->m_workingURL = url;
// set the java(script) flags according to the current host.
d->m_bJScriptEnabled = KHTMLGlobal::defaultHTMLSettings()->isJavaScriptEnabled(url.host());
setDebugScript( KHTMLGlobal::defaultHTMLSettings()->isJavaScriptDebugEnabled() );
d->m_bJavaEnabled = KHTMLGlobal::defaultHTMLSettings()->isJavaEnabled(url.host());
d->m_bPluginsEnabled = KHTMLGlobal::defaultHTMLSettings()->isPluginsEnabled(url.host());
setUrl(url);
d->m_restoreScrollPosition = true;
disconnect(d->m_view, SIGNAL(finishedLayout()), this, SLOT(restoreScrollPosition()));
connect(d->m_view, SIGNAL(finishedLayout()), this, SLOT(restoreScrollPosition()));
KHTMLPageCache::self()->fetchData( d->m_cacheId, this, SLOT(slotRestoreData(const QByteArray &)));
emit started( 0L );
return true;
}
bool KHTMLPartPrivate::isLocalAnchorJump( const KUrl& url )
{
// kio_help actually uses fragments to identify different pages, so
// always reload with it.
- if (url.protocol() == QLatin1String("help"))
+ if (url.scheme() == QLatin1String("help"))
return false;
return url.hasRef() && url.equals( q->url(),
KUrl::CompareWithoutTrailingSlash | KUrl::CompareWithoutFragment | KUrl::AllowEmptyPath );
}
void KHTMLPartPrivate::executeAnchorJump( const KUrl& url, bool lockHistory )
{
// Note: we want to emit openUrlNotify first thing, to make the history capture the old state.
if (!lockHistory)
emit m_extension->openUrlNotify();
if ( !q->gotoAnchor( url.encodedHtmlRef()) )
q->gotoAnchor( url.htmlRef() );
q->setUrl(url);
emit m_extension->setLocationBarUrl( url.prettyUrl() );
}
bool KHTMLPart::openUrl( const KUrl &url )
{
kDebug( 6050 ) << this << "opening" << url;
// Wallet forms are per page, so clear it when loading a different page if we
// are not an iframe (because we store walletforms only on the topmost part).
if(!parentPart())
d->m_walletForms.clear();
d->m_redirectionTimer.stop();
// check to see if this is an "error://" URL. This is caused when an error
// occurs before this part was loaded (e.g. KonqRun), and is passed to
// khtmlpart so that it can display the error.
- if ( url.protocol() == "error" ) {
+ if ( url.scheme() == "error" ) {
closeUrl();
if( d->m_bJScriptEnabled ) {
d->m_statusBarText[BarOverrideText].clear();
d->m_statusBarText[BarDefaultText].clear();
}
/**
* The format of the error url is that two variables are passed in the query:
* error = int kio error code, errText = QString error text from kio
* and the URL where the error happened is passed as a sub URL.
*/
KUrl::List urls = KUrl::split( url );
//kDebug(6050) << "Handling error URL. URL count:" << urls.count();
if ( !urls.isEmpty() ) {
const KUrl mainURL = urls.first();
int error = mainURL.queryItem( "error" ).toInt();
// error=0 isn't a valid error code, so 0 means it's missing from the URL
if ( error == 0 ) error = KIO::ERR_UNKNOWN;
const QString errorText = mainURL.queryItem( "errText" );
urls.pop_front();
d->m_workingURL = KUrl::join( urls );
//kDebug(6050) << "Emitting fixed URL " << d->m_workingURL.prettyUrl();
emit d->m_extension->setLocationBarUrl( d->m_workingURL.prettyUrl() );
htmlError( error, errorText, d->m_workingURL );
return true;
}
}
if (!parentPart()) { // only do it for toplevel part
QString host = url.isLocalFile() ? "localhost" : url.host();
QString userAgent = KProtocolManager::userAgentForHost(host);
if (userAgent != KProtocolManager::userAgentForHost(QString())) {
if (!d->m_statusBarUALabel) {
d->m_statusBarUALabel = new KUrlLabel(d->m_statusBarExtension->statusBar());
d->m_statusBarUALabel->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum));
d->m_statusBarUALabel->setUseCursor(false);
d->m_statusBarExtension->addStatusBarItem(d->m_statusBarUALabel, 0, false);
d->m_statusBarUALabel->setPixmap(SmallIcon("preferences-web-browser-identification"));
}
d->m_statusBarUALabel->setToolTip(i18n("The fake user-agent '%1' is in use.", userAgent));
} else if (d->m_statusBarUALabel) {
d->m_statusBarExtension->removeStatusBarItem(d->m_statusBarUALabel);
delete d->m_statusBarUALabel;
d->m_statusBarUALabel = 0L;
}
}
KParts::BrowserArguments browserArgs( d->m_extension->browserArguments() );
KParts::OpenUrlArguments args( arguments() );
// in case
// a) we have no frameset (don't test m_frames.count(), iframes get in there)
// b) the url is identical with the currently displayed one (except for the htmlref!)
// c) the url request is not a POST operation and
// d) the caller did not request to reload the page
// e) there was no HTTP redirection meanwhile (testcase: webmin's software/tree.cgi)
// => we don't reload the whole document and
// we just jump to the requested html anchor
bool isFrameSet = false;
if ( d->m_doc && d->m_doc->isHTMLDocument() ) {
HTMLDocumentImpl* htmlDoc = static_cast<HTMLDocumentImpl*>(d->m_doc);
isFrameSet = htmlDoc->body() && (htmlDoc->body()->id() == ID_FRAMESET);
}
if (isFrameSet && d->isLocalAnchorJump(url) && browserArgs.softReload)
{
QList<khtml::ChildFrame*>::Iterator it = d->m_frames.begin();
const QList<khtml::ChildFrame*>::Iterator end = d->m_frames.end();
for (; it != end; ++it) {
KHTMLPart* const part = qobject_cast<KHTMLPart *>( (*it)->m_part.data() );
if (part)
{
// We are reloading frames to make them jump into offsets.
KParts::OpenUrlArguments partargs( part->arguments() );
partargs.setReload( true );
part->setArguments( partargs );
part->openUrl( part->url() );
}
}/*next it*/
return true;
}
if ( url.hasRef() && !isFrameSet )
{
bool noReloadForced = !args.reload() && !browserArgs.redirectedRequest() && !browserArgs.doPost();
if ( noReloadForced && d->isLocalAnchorJump(url) )
{
kDebug( 6050 ) << "jumping to anchor. m_url = " << url;
setUrl(url);
emit started( 0 );
if ( !gotoAnchor( url.encodedHtmlRef()) )
gotoAnchor( url.htmlRef() );
d->m_bComplete = true;
if (d->m_doc)
d->m_doc->setParsing(false);
kDebug( 6050 ) << "completed...";
emit completed();
return true;
}
}
// Save offset of viewport when page is reloaded to be compliant
// to every other capable browser out there.
if (args.reload()) {
args.setXOffset( d->m_view->contentsX() );
args.setYOffset( d->m_view->contentsY() );
setArguments(args);
}
if (!d->m_restored)
closeUrl();
d->m_restoreScrollPosition = d->m_restored;
disconnect(d->m_view, SIGNAL(finishedLayout()), this, SLOT(restoreScrollPosition()));
connect(d->m_view, SIGNAL(finishedLayout()), this, SLOT(restoreScrollPosition()));
// Classify the mimetype. Some, like images and plugins are handled
// by wrapping things up in tags, so we want to plain output the HTML,
// and not start the job and all that (since we would want the
// KPart or whatever to load it).
// This is also the only place we need to do this, as it's for
// internal iframe use, not any other clients.
MimeType type = d->classifyMimeType(args.mimeType());
if (type == MimeImage || type == MimeOther) {
begin(url, args.xOffset(), args.yOffset());
write(QString::fromLatin1("<html><head></head><body>"));
if (type == MimeImage)
write(QString::fromLatin1("<img "));
else
write(QString::fromLatin1("<embed "));
write(QString::fromLatin1("src=\""));
assert(url.url().indexOf('"') == -1);
write(url.url());
write(QString::fromLatin1("\">"));
end();
return true;
}
// initializing m_url to the new url breaks relative links when opening such a link after this call and _before_ begin() is called (when the first
// data arrives) (Simon)
d->m_workingURL = url;
- if(url.protocol().startsWith( "http" ) && !url.host().isEmpty() &&
+ if(url.scheme().startsWith( "http" ) && !url.host().isEmpty() &&
url.path().isEmpty()) {
d->m_workingURL.setPath("/");
emit d->m_extension->setLocationBarUrl( d->m_workingURL.prettyUrl() );
}
setUrl(d->m_workingURL);
QMap<QString,QString>& metaData = args.metaData();
metaData.insert("main_frame_request", parentPart() == 0 ? "TRUE" : "FALSE" );
metaData.insert("ssl_parent_ip", d->m_ssl_parent_ip);
metaData.insert("ssl_parent_cert", d->m_ssl_parent_cert);
metaData.insert("PropagateHttpHeader", "true");
metaData.insert("ssl_was_in_use", d->m_ssl_in_use ? "TRUE" : "FALSE" );
metaData.insert("ssl_activate_warnings", "TRUE" );
metaData.insert("cross-domain", toplevelURL().url());
if (d->m_restored)
{
metaData.insert("referrer", d->m_pageReferrer);
d->m_cachePolicy = KIO::CC_Cache;
}
else if (args.reload() && !browserArgs.softReload)
d->m_cachePolicy = KIO::CC_Reload;
else
d->m_cachePolicy = KProtocolManager::cacheControl();
- if ( browserArgs.doPost() && (url.protocol().startsWith("http")) )
+ if ( browserArgs.doPost() && (url.scheme().startsWith("http")) )
{
d->m_job = KIO::http_post( url, browserArgs.postData, KIO::HideProgressInfo );
d->m_job->addMetaData("content-type", browserArgs.contentType() );
}
else
{
d->m_job = KIO::get( url, KIO::NoReload, KIO::HideProgressInfo );
d->m_job->addMetaData("cache", KIO::getCacheControlString(d->m_cachePolicy));
}
if (widget())
d->m_job->ui()->setWindow(widget()->topLevelWidget());
d->m_job->addMetaData(metaData);
connect( d->m_job, SIGNAL( result( KJob* ) ),
SLOT( slotFinished( KJob* ) ) );
connect( d->m_job, SIGNAL( data( KIO::Job*, const QByteArray& ) ),
SLOT( slotData( KIO::Job*, const QByteArray& ) ) );
connect ( d->m_job, SIGNAL( infoMessage( KJob*, const QString&, const QString& ) ),
SLOT( slotInfoMessage(KJob*, const QString& ) ) );
connect( d->m_job, SIGNAL(redirection(KIO::Job*, const KUrl& ) ),
SLOT( slotRedirection(KIO::Job*, const KUrl&) ) );
d->m_bComplete = false;
d->m_bLoadEventEmitted = false;
// delete old status bar msg's from kjs (if it _was_ activated on last URL)
if( d->m_bJScriptEnabled ) {
d->m_statusBarText[BarOverrideText].clear();
d->m_statusBarText[BarDefaultText].clear();
}
// set the javascript flags according to the current url
d->m_bJScriptEnabled = KHTMLGlobal::defaultHTMLSettings()->isJavaScriptEnabled(url.host());
setDebugScript( KHTMLGlobal::defaultHTMLSettings()->isJavaScriptDebugEnabled() );
d->m_bJavaEnabled = KHTMLGlobal::defaultHTMLSettings()->isJavaEnabled(url.host());
d->m_bPluginsEnabled = KHTMLGlobal::defaultHTMLSettings()->isPluginsEnabled(url.host());
connect( d->m_job, SIGNAL( speed( KJob*, unsigned long ) ),
this, SLOT( slotJobSpeed( KJob*, unsigned long ) ) );
connect( d->m_job, SIGNAL( percent( KJob*, unsigned long ) ),
this, SLOT( slotJobPercent( KJob*, unsigned long ) ) );
connect( d->m_job, SIGNAL( result( KJob* ) ),
this, SLOT( slotJobDone( KJob* ) ) );
d->m_jobspeed = 0;
// If this was an explicit reload and the user style sheet should be used,
// do a stat to see whether the stylesheet was changed in the meanwhile.
if ( args.reload() && !settings()->userStyleSheet().isEmpty() ) {
KUrl url( settings()->userStyleSheet() );
KIO::StatJob *job = KIO::stat( url, KIO::HideProgressInfo );
connect( job, SIGNAL( result( KJob * ) ),
this, SLOT( slotUserSheetStatDone( KJob * ) ) );
}
startingJob( d->m_job );
emit started( 0L );
return true;
}
bool KHTMLPart::closeUrl()
{
if ( d->m_job )
{
KHTMLPageCache::self()->cancelEntry(d->m_cacheId);
d->m_job->kill();
d->m_job = 0;
}
if ( d->m_doc && d->m_doc->isHTMLDocument() ) {
HTMLDocumentImpl* hdoc = static_cast<HTMLDocumentImpl*>( d->m_doc );
if ( hdoc->body() && d->m_bLoadEventEmitted ) {
hdoc->body()->dispatchWindowEvent( EventImpl::UNLOAD_EVENT, false, false );
if ( d->m_doc )
d->m_doc->updateRendering();
d->m_bLoadEventEmitted = false;
}
}
d->m_bComplete = true; // to avoid emitting completed() in slotFinishedParsing() (David)
d->m_bLoadEventEmitted = true; // don't want that one either
d->m_cachePolicy = KProtocolManager::cacheControl(); // reset cache policy
disconnect(d->m_view, SIGNAL(finishedLayout()), this, SLOT(restoreScrollPosition()));
KHTMLPageCache::self()->cancelFetch(this);
if ( d->m_doc && d->m_doc->parsing() )
{
kDebug( 6050 ) << " was still parsing... calling end ";
slotFinishedParsing();
d->m_doc->setParsing(false);
}
if ( !d->m_workingURL.isEmpty() )
{
// Aborted before starting to render
kDebug( 6050 ) << "Aborted before starting to render, reverting location bar to " << url().prettyUrl();
emit d->m_extension->setLocationBarUrl( url().prettyUrl() );
}
d->m_workingURL = KUrl();
if ( d->m_doc && d->m_doc->docLoader() )
khtml::Cache::loader()->cancelRequests( d->m_doc->docLoader() );
// tell all subframes to stop as well
{
ConstFrameIt it = d->m_frames.constBegin();
const ConstFrameIt end = d->m_frames.constEnd();
for (; it != end; ++it )
{
if ( (*it)->m_run )
(*it)->m_run.data()->abort();
if ( !( *it )->m_part.isNull() )
( *it )->m_part.data()->closeUrl();
}
}
// tell all objects to stop as well
{
ConstFrameIt it = d->m_objects.constBegin();
const ConstFrameIt end = d->m_objects.constEnd();
for (; it != end; ++it)
{
if ( !( *it )->m_part.isNull() )
( *it )->m_part.data()->closeUrl();
}
}
// Stop any started redirections as well!! (DA)
if ( d && d->m_redirectionTimer.isActive() )
d->m_redirectionTimer.stop();
// null node activated.
emit nodeActivated(Node());
// make sure before clear() runs, we pop out of a dialog's message loop
if ( d->m_view )
d->m_view->closeChildDialogs();
return true;
}
DOM::HTMLDocument KHTMLPart::htmlDocument() const
{
if (d->m_doc && d->m_doc->isHTMLDocument())
return static_cast<HTMLDocumentImpl*>(d->m_doc);
else
return static_cast<HTMLDocumentImpl*>(0);
}
DOM::Document KHTMLPart::document() const
{
return d->m_doc;
}
QString KHTMLPart::documentSource() const
{
QString sourceStr;
if ( !( url().isLocalFile() ) && KHTMLPageCache::self()->isComplete( d->m_cacheId ) )
{
QByteArray sourceArray;
QDataStream dataStream( &sourceArray, QIODevice::WriteOnly );
KHTMLPageCache::self()->saveData( d->m_cacheId, &dataStream );
QTextStream stream( sourceArray, QIODevice::ReadOnly );
stream.setCodec( QTextCodec::codecForName( encoding().toLatin1().constData() ) );
sourceStr = stream.readAll();
} else
{
QString tmpFile;
if( KIO::NetAccess::download( url(), tmpFile, NULL ) )
{
QFile f( tmpFile );
if ( f.open( QIODevice::ReadOnly ) )
{
QTextStream stream( &f );
stream.setCodec( QTextCodec::codecForName( encoding().toLatin1().constData() ) );
sourceStr = stream.readAll();
f.close();
}
KIO::NetAccess::removeTempFile( tmpFile );
}
}
return sourceStr;
}
KParts::BrowserExtension *KHTMLPart::browserExtension() const
{
return d->m_extension;
}
KParts::BrowserHostExtension *KHTMLPart::browserHostExtension() const
{
return d->m_hostExtension;
}
KHTMLView *KHTMLPart::view() const
{
return d->m_view;
}
KHTMLViewBar *KHTMLPart::pTopViewBar() const
{
if (const_cast<KHTMLPart*>(this)->parentPart())
return const_cast<KHTMLPart*>(this)->parentPart()->pTopViewBar();
return d->m_topViewBar;
}
KHTMLViewBar *KHTMLPart::pBottomViewBar() const
{
if (const_cast<KHTMLPart*>(this)->parentPart())
return const_cast<KHTMLPart*>(this)->parentPart()->pBottomViewBar();
return d->m_bottomViewBar;
}
void KHTMLPart::setStatusMessagesEnabled( bool enable )
{
d->m_statusMessagesEnabled = enable;
}
KJS::Interpreter *KHTMLPart::jScriptInterpreter()
{
KJSProxy *proxy = jScript();
if (!proxy || proxy->paused())
return 0;
return proxy->interpreter();
}
bool KHTMLPart::statusMessagesEnabled() const
{
return d->m_statusMessagesEnabled;
}
void KHTMLPart::setJScriptEnabled( bool enable )
{
if ( !enable && jScriptEnabled() && d->m_frame && d->m_frame->m_jscript ) {
d->m_frame->m_jscript->clear();
}
d->m_bJScriptForce = enable;
d->m_bJScriptOverride = true;
}
bool KHTMLPart::jScriptEnabled() const
{
if(onlyLocalReferences()) return false;
if ( d->m_bJScriptOverride )
return d->m_bJScriptForce;
return d->m_bJScriptEnabled;
}
void KHTMLPart::setDNSPrefetch( DNSPrefetch pmode )
{
d->m_bDNSPrefetch = pmode;
d->m_bDNSPrefetchIsDefault = false;
}
KHTMLPart::DNSPrefetch KHTMLPart::dnsPrefetch() const
{
if (onlyLocalReferences())
return DNSPrefetchDisabled;
return d->m_bDNSPrefetch;
}
void KHTMLPart::setMetaRefreshEnabled( bool enable )
{
d->m_metaRefreshEnabled = enable;
}
bool KHTMLPart::metaRefreshEnabled() const
{
return d->m_metaRefreshEnabled;
}
KJSProxy *KHTMLPart::jScript()
{
if (!jScriptEnabled()) return 0;
if ( !d->m_frame ) {
KHTMLPart * p = parentPart();
if (!p) {
d->m_frame = new khtml::ChildFrame;
d->m_frame->m_part = this;
} else {
ConstFrameIt it = p->d->m_frames.constBegin();
const ConstFrameIt end = p->d->m_frames.constEnd();
for (; it != end; ++it)
if ((*it)->m_part.data() == this) {
d->m_frame = *it;
break;
}
}
if ( !d->m_frame )
return 0;
}
if ( !d->m_frame->m_jscript )
d->m_frame->m_jscript = new KJSProxy(d->m_frame);
d->m_frame->m_jscript->setDebugEnabled(d->m_bJScriptDebugEnabled);
return d->m_frame->m_jscript;
}
QVariant KHTMLPart::crossFrameExecuteScript(const QString& target, const QString& script)
{
KHTMLPart* destpart = this;
QString trg = target.toLower();
if (target == "_top") {
while (destpart->parentPart())
destpart = destpart->parentPart();
}
else if (target == "_parent") {
if (parentPart())
destpart = parentPart();
}
else if (target == "_self" || target == "_blank") {
// we always allow these
}
else {
destpart = findFrame(target);
if (!destpart)
destpart = this;
}
// easy way out?
if (destpart == this)
return executeScript(DOM::Node(), script);
// now compare the domains
if (destpart->checkFrameAccess(this))
return destpart->executeScript(DOM::Node(), script);
// eww, something went wrong. better execute it in our frame
return executeScript(DOM::Node(), script);
}
//Enable this to see all JS scripts being executed
//#define KJS_VERBOSE
KJSErrorDlg *KHTMLPart::jsErrorExtension() {
if (!d->m_settings->jsErrorsEnabled()) {
return 0L;
}
if (parentPart()) {
return parentPart()->jsErrorExtension();
}
if (!d->m_statusBarJSErrorLabel) {
d->m_statusBarJSErrorLabel = new KUrlLabel(d->m_statusBarExtension->statusBar());
d->m_statusBarJSErrorLabel->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum));
d->m_statusBarJSErrorLabel->setUseCursor(false);
d->m_statusBarExtension->addStatusBarItem(d->m_statusBarJSErrorLabel, 0, false);
d->m_statusBarJSErrorLabel->setToolTip(i18n("This web page contains coding errors."));
d->m_statusBarJSErrorLabel->setPixmap(SmallIcon("script-error"));
connect(d->m_statusBarJSErrorLabel, SIGNAL(leftClickedUrl()), SLOT(launchJSErrorDialog()));
connect(d->m_statusBarJSErrorLabel, SIGNAL(rightClickedUrl()), SLOT(jsErrorDialogContextMenu()));
}
if (!d->m_jsedlg) {
d->m_jsedlg = new KJSErrorDlg;
d->m_jsedlg->setURL(url().prettyUrl());
if (KGlobalSettings::showIconsOnPushButtons()) {
d->m_jsedlg->_clear->setIcon(KIcon("edit-clear-locationbar-ltr"));
d->m_jsedlg->_close->setIcon(KIcon("window-close"));
}
}
return d->m_jsedlg;
}
void KHTMLPart::removeJSErrorExtension() {
if (parentPart()) {
parentPart()->removeJSErrorExtension();
return;
}
if (d->m_statusBarJSErrorLabel != 0) {
d->m_statusBarExtension->removeStatusBarItem( d->m_statusBarJSErrorLabel );
delete d->m_statusBarJSErrorLabel;
d->m_statusBarJSErrorLabel = 0;
}
delete d->m_jsedlg;
d->m_jsedlg = 0;
}
void KHTMLPart::disableJSErrorExtension() {
removeJSErrorExtension();
// These two lines are really kind of hacky, and it sucks to do this inside
// KHTML but I don't know of anything that's reasonably easy as an alternative
// right now. It makes me wonder if there should be a more clean way to
// contact all running "KHTML" instance as opposed to Konqueror instances too.
d->m_settings->setJSErrorsEnabled(false);
emit configurationChanged();
}
void KHTMLPart::jsErrorDialogContextMenu() {
KMenu *m = new KMenu(0L);
m->addAction(i18n("&Hide Errors"), this, SLOT(removeJSErrorExtension()));
m->addAction(i18n("&Disable Error Reporting"), this, SLOT(disableJSErrorExtension()));
m->popup(QCursor::pos());
}
void KHTMLPart::launchJSErrorDialog() {
KJSErrorDlg *dlg = jsErrorExtension();
if (dlg) {
dlg->show();
dlg->raise();
}
}
void KHTMLPart::launchJSConfigDialog() {
QStringList args;
args << "khtml_java_js";
KToolInvocation::kdeinitExec( "kcmshell4", args );
}
QVariant KHTMLPart::executeScript(const QString& filename, int baseLine, const DOM::Node& n, const QString& script)
{
#ifdef KJS_VERBOSE
// The script is now printed by KJS's Parser::parse
kDebug(6070) << "executeScript: caller='" << objectName() << "' filename=" << filename << " baseLine=" << baseLine /*<< " script=" << script*/;
#endif
KJSProxy *proxy = jScript();
if (!proxy || proxy->paused())
return QVariant();
//Make sure to initialize the interpreter before creating Completion
(void)proxy->interpreter();
KJS::Completion comp;
QVariant ret = proxy->evaluate(filename, baseLine, script, n, &comp);
/*
* Error handling
*/
if (comp.complType() == KJS::Throw && comp.value()) {
KJSErrorDlg *dlg = jsErrorExtension();
if (dlg) {
QString msg = KJSDebugger::DebugWindow::exceptionToString(
proxy->interpreter()->globalExec(), comp.value());
dlg->addError(i18n("<qt><b>Error</b>: %1: %2</qt>",
Qt::escape(filename), Qt::escape(msg)));
}
}
// Handle immediate redirects now (e.g. location='foo')
if ( !d->m_redirectURL.isEmpty() && d->m_delayRedirect == -1 )
{
kDebug(6070) << "executeScript done, handling immediate redirection NOW";
// Must abort tokenizer, no further script must execute.
khtml::Tokenizer* t = d->m_doc->tokenizer();
if(t)
t->abort();
d->m_redirectionTimer.setSingleShot( true );
d->m_redirectionTimer.start( 0 );
}
return ret;
}
QVariant KHTMLPart::executeScript( const QString &script )
{
return executeScript( DOM::Node(), script );
}
QVariant KHTMLPart::executeScript( const DOM::Node &n, const QString &script )
{
#ifdef KJS_VERBOSE
kDebug(6070) << "caller=" << objectName() << "node=" << n.nodeName().string().toLatin1().constData() << "(" << (n.isNull() ? 0 : n.nodeType()) << ") " /* << script */;
#endif
KJSProxy *proxy = jScript();
if (!proxy || proxy->paused())
return QVariant();
(void)proxy->interpreter();//Make sure stuff is initialized
++(d->m_runningScripts);
KJS::Completion comp;
const QVariant ret = proxy->evaluate( QString(), 1, script, n, &comp );
--(d->m_runningScripts);
/*
* Error handling
*/
if (comp.complType() == KJS::Throw && comp.value()) {
KJSErrorDlg *dlg = jsErrorExtension();
if (dlg) {
QString msg = KJSDebugger::DebugWindow::exceptionToString(
proxy->interpreter()->globalExec(), comp.value());
dlg->addError(i18n("<qt><b>Error</b>: node %1: %2</qt>",
n.nodeName().string(), Qt::escape(msg)));
}
}
if (!d->m_runningScripts && d->m_doc && !d->m_doc->parsing() && d->m_submitForm )
submitFormAgain();
#ifdef KJS_VERBOSE
kDebug(6070) << "done";
#endif
return ret;
}
void KHTMLPart::setJavaEnabled( bool enable )
{
d->m_bJavaForce = enable;
d->m_bJavaOverride = true;
}
bool KHTMLPart::javaEnabled() const
{
if (onlyLocalReferences()) return false;
#ifndef Q_WS_QWS
if( d->m_bJavaOverride )
return d->m_bJavaForce;
return d->m_bJavaEnabled;
#else
return false;
#endif
}
void KHTMLPart::setPluginsEnabled( bool enable )
{
d->m_bPluginsForce = enable;
d->m_bPluginsOverride = true;
}
bool KHTMLPart::pluginsEnabled() const
{
if (onlyLocalReferences()) return false;
if ( d->m_bPluginsOverride )
return d->m_bPluginsForce;
return d->m_bPluginsEnabled;
}
static int s_DOMTreeIndentLevel = 0;
void KHTMLPart::slotDebugDOMTree()
{
if ( d->m_doc )
qDebug("%s", d->m_doc->toString().string().toLatin1().constData());
// Now print the contents of the frames that contain HTML
const int indentLevel = s_DOMTreeIndentLevel++;
ConstFrameIt it = d->m_frames.constBegin();
const ConstFrameIt end = d->m_frames.constEnd();
for (; it != end; ++it )
if ( !( *it )->m_part.isNull() && (*it)->m_part.data()->inherits( "KHTMLPart" ) ) {
KParts::ReadOnlyPart* const p = ( *it )->m_part.data();
kDebug(6050) << QString().leftJustified(s_DOMTreeIndentLevel*4,' ') << "FRAME " << p->objectName() << " ";
static_cast<KHTMLPart*>( p )->slotDebugDOMTree();
}
s_DOMTreeIndentLevel = indentLevel;
}
void KHTMLPart::slotDebugScript()
{
if (jScript())
jScript()->showDebugWindow();
}
void KHTMLPart::slotDebugRenderTree()
{
#ifndef NDEBUG
if ( d->m_doc ) {
d->m_doc->renderer()->printTree();
// dump out the contents of the rendering & DOM trees
// QString dumps;
// QTextStream outputStream(&dumps,QIODevice::WriteOnly);
// d->m_doc->renderer()->layer()->dump( outputStream );
// kDebug() << "dump output:" << "\n" + dumps;
// d->m_doc->renderer()->printLineBoxTree();
}
#endif
}
void KHTMLPart::slotDebugFrameTree()
{
khtml::ChildFrame::dumpFrameTree(this);
}
void KHTMLPart::slotStopAnimations()
{
stopAnimations();
}
void KHTMLPart::setAutoloadImages( bool enable )
{
if ( d->m_doc && d->m_doc->docLoader()->autoloadImages() == enable )
return;
if ( d->m_doc )
d->m_doc->docLoader()->setAutoloadImages( enable );
unplugActionList( "loadImages" );
if ( enable ) {
delete d->m_paLoadImages;
d->m_paLoadImages = 0;
}
else if ( !d->m_paLoadImages ) {
d->m_paLoadImages = new KAction( i18n( "Display Images on Page" ), this );
actionCollection()->addAction( "loadImages", d->m_paLoadImages );
d->m_paLoadImages->setIcon( KIcon( "image-loading" ) );
connect( d->m_paLoadImages, SIGNAL( triggered( bool ) ), this, SLOT( slotLoadImages() ) );
}
if ( d->m_paLoadImages ) {
QList<QAction*> lst;
lst.append( d->m_paLoadImages );
plugActionList( "loadImages", lst );
}
}
bool KHTMLPart::autoloadImages() const
{
if ( d->m_doc )
return d->m_doc->docLoader()->autoloadImages();
return true;
}
void KHTMLPart::clear()
{
if ( d->m_bCleared )
return;
d->m_bCleared = true;
d->m_bClearing = true;
{
ConstFrameIt it = d->m_frames.constBegin();
const ConstFrameIt end = d->m_frames.constEnd();
for(; it != end; ++it )
{
// Stop HTMLRun jobs for frames
if ( (*it)->m_run )
(*it)->m_run.data()->abort();
}
}
{
ConstFrameIt it = d->m_objects.constBegin();
const ConstFrameIt end = d->m_objects.constEnd();
for(; it != end; ++it )
{
// Stop HTMLRun jobs for objects
if ( (*it)->m_run )
(*it)->m_run.data()->abort();
}
}
findTextBegin(); // resets d->m_findNode and d->m_findPos
d->m_mousePressNode = DOM::Node();
if ( d->m_doc )
{
if (d->m_doc->attached()) //the view may have detached it already
d->m_doc->detach();
}
// Moving past doc so that onUnload works.
if ( d->m_frame && d->m_frame->m_jscript )
d->m_frame->m_jscript->clear();
// stopping marquees
if (d->m_doc && d->m_doc->renderer() && d->m_doc->renderer()->layer())
d->m_doc->renderer()->layer()->suspendMarquees();
if ( d->m_view )
d->m_view->clear();
// do not dereference the document before the jscript and view are cleared, as some destructors
// might still try to access the document.
if ( d->m_doc ) {
d->m_doc->deref();
}
d->m_doc = 0;
delete d->m_decoder;
d->m_decoder = 0;
// We don't want to change between parts if we are going to delete all of them anyway
if (partManager()) {
disconnect( partManager(), SIGNAL( activePartChanged( KParts::Part * ) ),
this, SLOT( slotActiveFrameChanged( KParts::Part * ) ) );
}
if (d->m_frames.count())
{
const KHTMLFrameList frames = d->m_frames;
d->m_frames.clear();
ConstFrameIt it = frames.begin();
const ConstFrameIt end = frames.end();
for(; it != end; ++it )
{
if ( (*it)->m_part )
{
partManager()->removePart( (*it)->m_part.data() );
delete (*it)->m_part.data();
}
delete *it;
}
}
d->m_suppressedPopupOriginParts.clear();
if (d->m_objects.count())
{
KHTMLFrameList objects = d->m_objects;
d->m_objects.clear();
ConstFrameIt oi = objects.constBegin();
const ConstFrameIt oiEnd = objects.constEnd();
for (; oi != oiEnd; ++oi )
{
delete (*oi)->m_part.data();
delete *oi;
}
}
// Listen to part changes again
if (partManager()) {
connect( partManager(), SIGNAL( activePartChanged( KParts::Part * ) ),
this, SLOT( slotActiveFrameChanged( KParts::Part * ) ) );
}
d->clearRedirection();
d->m_redirectLockHistory = true;
d->m_bClearing = false;
d->m_frameNameId = 1;
d->m_bFirstData = true;
d->m_bMousePressed = false;
if (d->editor_context.m_caretBlinkTimer >= 0)
killTimer(d->editor_context.m_caretBlinkTimer);
d->editor_context.reset();
#ifndef QT_NO_CLIPBOARD
connect( qApp->clipboard(), SIGNAL( selectionChanged()), SLOT( slotClearSelection()));
#endif
d->m_jobPercent = 0;
if ( !d->m_haveEncoding )
d->m_encoding.clear();
d->m_DNSPrefetchQueue.clear();
if (d->m_DNSPrefetchTimer > 0)
killTimer(d->m_DNSPrefetchTimer);
d->m_DNSPrefetchTimer = -1;
d->m_lookedupHosts.clear();
if (d->m_DNSTTLTimer > 0)
killTimer(d->m_DNSTTLTimer);
d->m_DNSTTLTimer = -1;
d->m_numDNSPrefetchedNames = 0;
#ifdef SPEED_DEBUG
d->m_parsetime.restart();
#endif
}
bool KHTMLPart::openFile()
{
return true;
}
DOM::HTMLDocumentImpl *KHTMLPart::docImpl() const
{
if ( d && d->m_doc && d->m_doc->isHTMLDocument() )
return static_cast<HTMLDocumentImpl*>(d->m_doc);
return 0;
}
DOM::DocumentImpl *KHTMLPart::xmlDocImpl() const
{
if ( d )
return d->m_doc;
return 0;
}
void KHTMLPart::slotInfoMessage(KJob* kio_job, const QString& msg)
{
assert(d->m_job == kio_job);
Q_ASSERT(kio_job);
Q_UNUSED(kio_job);
if (!parentPart())
setStatusBarText(msg, BarDefaultText);
}
void KHTMLPart::setPageSecurity( PageSecurity sec )
{
emit d->m_extension->setPageSecurity( sec );
}
void KHTMLPart::slotData( KIO::Job* kio_job, const QByteArray &data )
{
assert ( d->m_job == kio_job );
Q_ASSERT(kio_job);
Q_UNUSED(kio_job);
//kDebug( 6050 ) << "slotData: " << data.size();
// The first data ?
if ( !d->m_workingURL.isEmpty() )
{
//kDebug( 6050 ) << "begin!";
// We must suspend KIO while we're inside begin() because it can cause
// crashes if a window (such as kjsdebugger) goes back into the event loop,
// more data arrives, and begin() gets called again (re-entered).
d->m_job->suspend();
begin( d->m_workingURL, arguments().xOffset(), arguments().yOffset() );
d->m_job->resume();
// CC_Refresh means : always send the server an If-Modified-Since conditional request.
// This is the default cache setting and correspond to the KCM's "Keep cache in sync".
// CC_Verify means : only send a conditional request if the cache expiry date is passed.
// It doesn't have a KCM setter.
// We override the first to the second, except when doing a soft-reload.
if (d->m_cachePolicy == KIO::CC_Refresh && !d->m_extension->browserArguments().softReload)
d->m_doc->docLoader()->setCachePolicy(KIO::CC_Verify);
else
d->m_doc->docLoader()->setCachePolicy(d->m_cachePolicy);
d->m_workingURL = KUrl();
d->m_cacheId = KHTMLPageCache::self()->createCacheEntry();
// When the first data arrives, the metadata has just been made available
d->m_httpHeaders = d->m_job->queryMetaData("HTTP-Headers");
time_t cacheCreationDate = d->m_job->queryMetaData("cache-creation-date").toLong();
d->m_doc->docLoader()->setCacheCreationDate(cacheCreationDate);
d->m_pageServices = d->m_job->queryMetaData("PageServices");
d->m_pageReferrer = d->m_job->queryMetaData("referrer");
d->m_ssl_in_use = (d->m_job->queryMetaData("ssl_in_use") == "TRUE");
{
KHTMLPart *p = parentPart();
if (p && p->d->m_ssl_in_use != d->m_ssl_in_use) {
while (p->parentPart()) p = p->parentPart();
p->setPageSecurity( NotCrypted );
}
}
setPageSecurity( d->m_ssl_in_use ? Encrypted : NotCrypted );
// Shouldn't all of this be done only if ssl_in_use == true ? (DF)
d->m_ssl_parent_ip = d->m_job->queryMetaData("ssl_parent_ip");
d->m_ssl_parent_cert = d->m_job->queryMetaData("ssl_parent_cert");
d->m_ssl_peer_chain = d->m_job->queryMetaData("ssl_peer_chain");
d->m_ssl_peer_ip = d->m_job->queryMetaData("ssl_peer_ip");
d->m_ssl_cipher = d->m_job->queryMetaData("ssl_cipher");
d->m_ssl_protocol_version = d->m_job->queryMetaData("ssl_protocol_version");
d->m_ssl_cipher_used_bits = d->m_job->queryMetaData("ssl_cipher_used_bits");
d->m_ssl_cipher_bits = d->m_job->queryMetaData("ssl_cipher_bits");
d->m_ssl_cert_errors = d->m_job->queryMetaData("ssl_cert_errors");
// Check for charset meta-data
QString qData = d->m_job->queryMetaData("charset");
if ( !qData.isEmpty() && !d->m_haveEncoding ) // only use information if the user didn't override the settings
d->m_encoding = qData;
// Support for http-refresh
qData = d->m_job->queryMetaData("http-refresh");
if( !qData.isEmpty())
d->m_doc->processHttpEquiv("refresh", qData);
// DISABLED: Support Content-Location per section 14.14 of RFC 2616.
// See BR# 51185,BR# 82747
/*
QString baseURL = d->m_job->queryMetaData ("content-location");
if (!baseURL.isEmpty())
d->m_doc->setBaseURL(KUrl( d->m_doc->completeURL(baseURL) ));
*/
// Support for Content-Language
QString language = d->m_job->queryMetaData("content-language");
if (!language.isEmpty())
d->m_doc->setContentLanguage(language);
if ( !url().isLocalFile() )
{
// Support for http last-modified
d->m_lastModified = d->m_job->queryMetaData("modified");
}
else
d->m_lastModified.clear(); // done on-demand by lastModified()
}
KHTMLPageCache::self()->addData(d->m_cacheId, data);
write( data.data(), data.size() );
}
void KHTMLPart::slotRestoreData(const QByteArray &data )
{
// The first data ?
if ( !d->m_workingURL.isEmpty() )
{
long saveCacheId = d->m_cacheId;
QString savePageReferrer = d->m_pageReferrer;
QString saveEncoding = d->m_encoding;
begin( d->m_workingURL, arguments().xOffset(), arguments().yOffset() );
d->m_encoding = saveEncoding;
d->m_pageReferrer = savePageReferrer;
d->m_cacheId = saveCacheId;
d->m_workingURL = KUrl();
}
//kDebug( 6050 ) << data.size();
write( data.data(), data.size() );
if (data.size() == 0)
{
//kDebug( 6050 ) << "<<end of data>>";
// End of data.
if (d->m_doc && d->m_doc->parsing())
end(); //will emit completed()
}
}
void KHTMLPart::showError( KJob* job )
{
kDebug(6050) << "d->m_bParsing=" << (d->m_doc && d->m_doc->parsing()) << " d->m_bComplete=" << d->m_bComplete
<< " d->m_bCleared=" << d->m_bCleared;
if (job->error() == KIO::ERR_NO_CONTENT)
return;
if ( (d->m_doc && d->m_doc->parsing()) || d->m_workingURL.isEmpty() ) // if we got any data already
job->uiDelegate()->showErrorMessage();
else
{
htmlError( job->error(), job->errorText(), d->m_workingURL );
}
}
// This is a protected method, placed here because of it's relevance to showError
void KHTMLPart::htmlError( int errorCode, const QString& text, const KUrl& reqUrl )
{
kDebug(6050) << "errorCode" << errorCode << "text" << text;
// make sure we're not executing any embedded JS
bool bJSFO = d->m_bJScriptForce;
bool bJSOO = d->m_bJScriptOverride;
d->m_bJScriptForce = false;
d->m_bJScriptOverride = true;
begin();
QString errorName, techName, description;
QStringList causes, solutions;
QByteArray raw = KIO::rawErrorDetail( errorCode, text, &reqUrl );
QDataStream stream(raw);
stream >> errorName >> techName >> description >> causes >> solutions;
QString url, protocol, datetime;
// This is somewhat confusing, but we have to escape the externally-
// controlled URL twice: once for i18n, and once for HTML.
url = Qt::escape( Qt::escape( reqUrl.prettyUrl() ) );
- protocol = reqUrl.protocol();
+ protocol = reqUrl.scheme();
datetime = KGlobal::locale()->formatDateTime( QDateTime::currentDateTime(),
KLocale::LongDate );
QString filename( KStandardDirs::locate( "data", "khtml/error.html" ) );
QFile file( filename );
bool isOpened = file.open( QIODevice::ReadOnly );
if ( !isOpened )
kWarning(6050) << "Could not open error html template:" << filename;
QString html = QString( QLatin1String( file.readAll() ) );
html.replace( QLatin1String( "TITLE" ), i18n( "Error: %1 - %2", errorName, url ) );
html.replace( QLatin1String( "DIRECTION" ), QApplication::isRightToLeft() ? "rtl" : "ltr" );
html.replace( QLatin1String( "ICON_PATH" ), KIconLoader::global()->iconPath( "dialog-warning", -KIconLoader::SizeHuge ) );
QString doc = QLatin1String( "<h1>" );
doc += i18n( "The requested operation could not be completed" );
doc += QLatin1String( "</h1><h2>" );
doc += errorName;
doc += QLatin1String( "</h2>" );
if ( !techName.isNull() ) {
doc += QLatin1String( "<h2>" );
doc += i18n( "Technical Reason: " );
doc += techName;
doc += QLatin1String( "</h2>" );
}
doc += QLatin1String( "<br clear=\"all\">" );
doc += QLatin1String( "<h3>" );
doc += i18n( "Details of the Request:" );
doc += QLatin1String( "</h3><ul><li>" );
doc += i18n( "URL: %1" , url );
doc += QLatin1String( "</li><li>" );
if ( !protocol.isNull() ) {
doc += i18n( "Protocol: %1", protocol );
doc += QLatin1String( "</li><li>" );
}
doc += i18n( "Date and Time: %1" , datetime );
doc += QLatin1String( "</li><li>" );
doc += i18n( "Additional Information: %1" , text );
doc += QLatin1String( "</li></ul><h3>" );
doc += i18n( "Description:" );
doc += QLatin1String( "</h3><p>" );
doc += description;
doc += QLatin1String( "</p>" );
if ( causes.count() ) {
doc += QLatin1String( "<h3>" );
doc += i18n( "Possible Causes:" );
doc += QLatin1String( "</h3><ul><li>" );
doc += causes.join( "</li><li>" );
doc += QLatin1String( "</li></ul>" );
}
if ( solutions.count() ) {
doc += QLatin1String( "<h3>" );
doc += i18n( "Possible Solutions:" );
doc += QLatin1String( "</h3><ul><li>" );
doc += solutions.join( "</li><li>" );
doc += QLatin1String( "</li></ul>" );
}
html.replace( QLatin1String("TEXT"), doc );
write( html );
end();
d->m_bJScriptForce = bJSFO;
d->m_bJScriptOverride = bJSOO;
// make the working url the current url, so that reload works and
// emit the progress signals to advance one step in the history
// (so that 'back' works)
setUrl(reqUrl); // same as d->m_workingURL
d->m_workingURL = KUrl();
emit started( 0 );
emit completed();
}
void KHTMLPart::slotFinished( KJob * job )
{
d->m_job = 0L;
d->m_jobspeed = 0L;
if (job->error())
{
KHTMLPageCache::self()->cancelEntry(d->m_cacheId);
// The following catches errors that occur as a result of HTTP
// to FTP redirections where the FTP URL is a directory. Since
// KIO cannot change a redirection request from GET to LISTDIR,
// we have to take care of it here once we know for sure it is
// a directory...
if (job->error() == KIO::ERR_IS_DIRECTORY)
{
emit canceled( job->errorString() );
emit d->m_extension->openUrlRequest( d->m_workingURL );
}
else
{
emit canceled( job->errorString() );
// TODO: what else ?
checkCompleted();
showError( job );
}
return;
}
KIO::TransferJob *tjob = ::qobject_cast<KIO::TransferJob*>(job);
if (tjob && tjob->isErrorPage()) {
HTMLPartContainerElementImpl *elt = d->m_frame ?
d->m_frame->m_partContainerElement.data() : 0;
if (!elt)
return;
elt->partLoadingErrorNotify();
checkCompleted();
if (d->m_bComplete) return;
}
//kDebug( 6050 ) << "slotFinished";
KHTMLPageCache::self()->endData(d->m_cacheId);
- if ( d->m_doc && d->m_doc->docLoader()->expireDate() && url().protocol().toLower().startsWith("http"))
+ if ( d->m_doc && d->m_doc->docLoader()->expireDate() && url().scheme().toLower().startsWith("http"))
KIO::http_update_cache(url(), false, d->m_doc->docLoader()->expireDate());
d->m_workingURL = KUrl();
if ( d->m_doc && d->m_doc->parsing())
end(); //will emit completed()
}
MimeType KHTMLPartPrivate::classifyMimeType(const QString& mimeStr)
{
// See HTML5's "5.5.1 Navigating across documents" section.
if (mimeStr == "application/xhtml+xml")
return MimeXHTML;
if (mimeStr == "image/svg+xml")
return MimeSVG;
if (mimeStr == "text/html" || mimeStr.isEmpty())
return MimeHTML;
KMimeType::Ptr mime = KMimeType::mimeType(mimeStr, KMimeType::ResolveAliases);
if ((mime && mime->is("text/xml")) || mimeStr.endsWith("+xml"))
return MimeXML;
if (mime && mime->is("text/plain"))
return MimeText;
if (khtmlImLoad::ImageManager::loaderDatabase()->supportedMimeTypes().contains(mimeStr))
return MimeImage;
// Sometimes our subclasses like to handle custom mimetypes. In that case,
// we want to handle them as HTML. We do that in the following cases:
// 1) We're at top-level, so we were forced to open something
// 2) We're an object --- this again means we were forced to open something,
// as an iframe-generating-an-embed case would have us as an iframe
if (!q->parentPart() || (m_frame && m_frame->m_type == khtml::ChildFrame::Object))
return MimeHTML;
return MimeOther;
}
void KHTMLPart::begin( const KUrl &url, int xOffset, int yOffset )
{
if ( d->m_view->underMouse() )
QToolTip::hideText(); // in case a previous tooltip is still shown
// No need to show this for a new page until an error is triggered
if (!parentPart()) {
removeJSErrorExtension();
setSuppressedPopupIndicator( false );
d->m_openableSuppressedPopups = 0;
foreach ( KHTMLPart* part, d->m_suppressedPopupOriginParts ) {
if (part) {
KJS::Window *w = KJS::Window::retrieveWindow( part );
if (w)
w->forgetSuppressedWindows();
}
}
}
d->m_bCleared = false;
d->m_cacheId = 0;
d->m_bComplete = false;
d->m_bLoadEventEmitted = false;
clear();
d->m_bCleared = false;
if(url.isValid()) {
QString urlString = url.url();
KHTMLGlobal::vLinks()->insert( urlString );
QString urlString2 = url.prettyUrl();
if ( urlString != urlString2 ) {
KHTMLGlobal::vLinks()->insert( urlString2 );
}
}
// ###
//stopParser();
KParts::OpenUrlArguments args = arguments();
args.setXOffset(xOffset);
args.setYOffset(yOffset);
setArguments(args);
d->m_pageReferrer.clear();
KUrl ref(url);
- d->m_referrer = ref.protocol().startsWith("http") ? ref.url() : "";
+ d->m_referrer = ref.scheme().startsWith("http") ? ref.url() : "";
setUrl(url);
// Note: by now, any special mimetype besides plaintext would have been
// handled specially inside openURL, so we handle their cases the same
// as HTML.
MimeType type = d->classifyMimeType(args.mimeType());
switch (type) {
case MimeSVG:
d->m_doc = DOMImplementationImpl::createSVGDocument( d->m_view );
break;
case MimeXML: // any XML derivative, except XHTML or SVG
// ### not sure if XHTML documents served as text/xml should use DocumentImpl or HTMLDocumentImpl
d->m_doc = DOMImplementationImpl::createXMLDocument( d->m_view );
break;
case MimeText:
d->m_doc = new HTMLTextDocumentImpl( d->m_view );
break;
case MimeXHTML:
case MimeHTML:
default:
d->m_doc = DOMImplementationImpl::createHTMLDocument( d->m_view );
// HTML or XHTML? (#86446)
static_cast<HTMLDocumentImpl *>(d->m_doc)->setHTMLRequested( type != MimeXHTML );
}
d->m_doc->ref();
d->m_doc->setURL( url.url() );
d->m_doc->open( );
if (!d->m_doc->attached())
d->m_doc->attach( );
d->m_doc->setBaseURL( KUrl() );
d->m_doc->docLoader()->setShowAnimations( KHTMLGlobal::defaultHTMLSettings()->showAnimations() );
emit docCreated();
d->m_paUseStylesheet->setItems(QStringList());
d->m_paUseStylesheet->setEnabled( false );
setAutoloadImages( KHTMLGlobal::defaultHTMLSettings()->autoLoadImages() );
QString userStyleSheet = KHTMLGlobal::defaultHTMLSettings()->userStyleSheet();
if ( !userStyleSheet.isEmpty() )
setUserStyleSheet( KUrl( userStyleSheet ) );
d->m_doc->setRestoreState(d->m_extension->browserArguments().docState);
connect(d->m_doc,SIGNAL(finishedParsing()),this,SLOT(slotFinishedParsing()));
emit d->m_extension->enableAction( "print", true );
d->m_doc->setParsing(true);
}
void KHTMLPart::write( const char *data, int len )
{
if ( !d->m_decoder )
d->m_decoder = createDecoder();
if ( len == -1 )
len = strlen( data );
if ( len == 0 )
return;
QString decoded=d->m_decoder->decodeWithBuffering(data,len);
if(decoded.isEmpty())
return;
if(d->m_bFirstData)
onFirstData();
khtml::Tokenizer* t = d->m_doc->tokenizer();
if(t)
t->write( decoded, true );
}
// ### KDE5: remove
void KHTMLPart::setAlwaysHonourDoctype( bool b )
{
d->m_bStrictModeQuirk = !b;
}
void KHTMLPart::write( const QString &str )
{
if ( str.isNull() )
return;
if(d->m_bFirstData) {
// determine the parse mode
if (d->m_bStrictModeQuirk) {
d->m_doc->setParseMode( DocumentImpl::Strict );
d->m_bFirstData = false;
} else {
onFirstData();
}
}
khtml::Tokenizer* t = d->m_doc->tokenizer();
if(t)
t->write( str, true );
}
void KHTMLPart::end()
{
if (d->m_doc) {
if (d->m_decoder)
{
QString decoded=d->m_decoder->flush();
if (d->m_bFirstData)
onFirstData();
if (!decoded.isEmpty())
write(decoded);
}
d->m_doc->finishParsing();
}
}
void KHTMLPart::onFirstData()
{
assert( d->m_bFirstData );
// determine the parse mode
d->m_doc->determineParseMode();
d->m_bFirstData = false;
// ### this is still quite hacky, but should work a lot better than the old solution
// Note: decoder may be null if only write(QString) is used.
if (d->m_decoder && d->m_decoder->visuallyOrdered())
d->m_doc->setVisuallyOrdered();
// ensure part and view shares zoom-level before styling
updateZoomFactor();
d->m_doc->recalcStyle( NodeImpl::Force );
}
bool KHTMLPart::doOpenStream( const QString& mimeType )
{
KMimeType::Ptr mime = KMimeType::mimeType(mimeType, KMimeType::ResolveAliases);
if ( mime && ( mime->is( "text/html" ) || mime->is( "text/xml" ) ) )
{
begin( url() );
return true;
}
return false;
}
bool KHTMLPart::doWriteStream( const QByteArray& data )
{
write( data.data(), data.size() );
return true;
}
bool KHTMLPart::doCloseStream()
{
end();
return true;
}
void KHTMLPart::paint(QPainter *p, const QRect &rc, int yOff, bool *more)
{
if (!d->m_view) return;
d->m_view->paint(p, rc, yOff, more);
}
void KHTMLPart::stopAnimations()
{
if ( d->m_doc )
d->m_doc->docLoader()->setShowAnimations( KHTMLSettings::KAnimationDisabled );
ConstFrameIt it = d->m_frames.constBegin();
const ConstFrameIt end = d->m_frames.constEnd();
for (; it != end; ++it ) {
if ( KHTMLPart* p = qobject_cast<KHTMLPart*>((*it)->m_part.data()) )
p->stopAnimations();
}
}
void KHTMLPart::resetFromScript()
{
closeUrl();
d->m_bComplete = false;
d->m_bLoadEventEmitted = false;
disconnect(d->m_doc,SIGNAL(finishedParsing()),this,SLOT(slotFinishedParsing()));
connect(d->m_doc,SIGNAL(finishedParsing()),this,SLOT(slotFinishedParsing()));
d->m_doc->setParsing(true);
emit started( 0L );
}
void KHTMLPart::slotFinishedParsing()
{
d->m_doc->setParsing(false);
d->m_doc->dispatchHTMLEvent(EventImpl::KHTML_CONTENTLOADED_EVENT, true, false);
checkEmitLoadEvent();
disconnect(d->m_doc,SIGNAL(finishedParsing()),this,SLOT(slotFinishedParsing()));
if (!d->m_view)
return; // We are probably being destructed.
checkCompleted();
}
void KHTMLPart::slotLoaderRequestStarted( khtml::DocLoader* dl, khtml::CachedObject *obj )
{
if ( obj && obj->type() == khtml::CachedObject::Image && d->m_doc && d->m_doc->docLoader() == dl ) {
KHTMLPart* p = this;
while ( p ) {
KHTMLPart* const op = p;
++(p->d->m_totalObjectCount);
p = p->parentPart();
if ( !p && op->d->m_loadedObjects <= op->d->m_totalObjectCount
&& !op->d->m_progressUpdateTimer.isActive()) {
op->d->m_progressUpdateTimer.setSingleShot( true );
op->d->m_progressUpdateTimer.start( 200 );
}
}
}
}
static bool isAncestorOrSamePart(KHTMLPart* p1, KHTMLPart* p2)
{
KHTMLPart* p = p2;
do {
if (p == p1)
return true;
} while ((p = p->parentPart()));
return false;
}
void KHTMLPart::slotLoaderRequestDone( khtml::DocLoader* dl, khtml::CachedObject *obj )
{
if ( obj && obj->type() == khtml::CachedObject::Image && d->m_doc && d->m_doc->docLoader() == dl ) {
KHTMLPart* p = this;
while ( p ) {
KHTMLPart* const op = p;
++(p->d->m_loadedObjects);
p = p->parentPart();
if ( !p && op->d->m_loadedObjects <= op->d->m_totalObjectCount && op->d->m_jobPercent <= 100
&& !op->d->m_progressUpdateTimer.isActive()) {
op->d->m_progressUpdateTimer.setSingleShot( true );
op->d->m_progressUpdateTimer.start( 200 );
}
}
}
/// if we have no document, or the object is not a request of one of our children,
// then our loading state can't possibly be affected : don't waste time checking for completion.
if (!d->m_doc || !dl->doc()->part() || !isAncestorOrSamePart(this, dl->doc()->part()))
return;
checkCompleted();
}
void KHTMLPart::slotProgressUpdate()
{
int percent;
if ( d->m_loadedObjects < d->m_totalObjectCount )
percent = d->m_jobPercent / 4 + ( d->m_loadedObjects*300 ) / ( 4*d->m_totalObjectCount );
else
percent = d->m_jobPercent;
if( d->m_bComplete )
percent = 100;
if (d->m_statusMessagesEnabled) {
if( d->m_bComplete )
emit d->m_extension->infoMessage( i18n( "Page loaded." ));
else if ( d->m_loadedObjects < d->m_totalObjectCount && percent >= 75 )
emit d->m_extension->infoMessage( i18np( "%1 Image of %2 loaded.", "%1 Images of %2 loaded.", d->m_loadedObjects, d->m_totalObjectCount) );
}
emit d->m_extension->loadingProgress( percent );
}
void KHTMLPart::slotJobSpeed( KJob* /*job*/, unsigned long speed )
{
d->m_jobspeed = speed;
if (!parentPart())
setStatusBarText(jsStatusBarText(), BarOverrideText);
}
void KHTMLPart::slotJobPercent( KJob* /*job*/, unsigned long percent )
{
d->m_jobPercent = percent;
if ( !parentPart() ) {
d->m_progressUpdateTimer.setSingleShot( true );
d->m_progressUpdateTimer.start( 0 );
}
}
void KHTMLPart::slotJobDone( KJob* /*job*/ )
{
d->m_jobPercent = 100;
if ( !parentPart() ) {
d->m_progressUpdateTimer.setSingleShot( true );
d->m_progressUpdateTimer.start( 0 );
}
}
void KHTMLPart::slotUserSheetStatDone( KJob *_job )
{
using namespace KIO;
if ( _job->error() ) {
showError( _job );
return;
}
const UDSEntry entry = dynamic_cast<KIO::StatJob *>( _job )->statResult();
const time_t lastModified = entry.numberValue( KIO::UDSEntry::UDS_MODIFICATION_TIME, -1 );
// If the filesystem supports modification times, only reload the
// user-defined stylesheet if necessary - otherwise always reload.
if ( lastModified != static_cast<time_t>(-1) ) {
if ( d->m_userStyleSheetLastModified >= lastModified ) {
return;
}
d->m_userStyleSheetLastModified = lastModified;
}
setUserStyleSheet( KUrl( settings()->userStyleSheet() ) );
}
bool KHTMLPartPrivate::isFullyLoaded(bool* pendingRedirections) const
{
*pendingRedirections = false;
// Any frame that hasn't completed yet ?
ConstFrameIt it = m_frames.constBegin();
const ConstFrameIt end = m_frames.constEnd();
for (; it != end; ++it ) {
if ( !(*it)->m_bCompleted || (*it)->m_run )
{
//kDebug( 6050 ) << this << " is waiting for " << (*it)->m_part;
return false;
}
// Check for frames with pending redirections
if ( (*it)->m_bPendingRedirection )
*pendingRedirections = true;
}
// Any object that hasn't completed yet ?
{
ConstFrameIt oi = m_objects.constBegin();
const ConstFrameIt oiEnd = m_objects.constEnd();
for (; oi != oiEnd; ++oi )
if ( !(*oi)->m_bCompleted )
return false;
}
// Are we still parsing
if ( m_doc && m_doc->parsing() )
return false;
// Still waiting for images/scripts from the loader ?
int requests = 0;
if ( m_doc && m_doc->docLoader() )
requests = khtml::Cache::loader()->numRequests( m_doc->docLoader() );
if ( requests > 0 )
{
//kDebug(6050) << "still waiting for images/scripts from the loader - requests:" << requests;
return false;
}
return true;
}
void KHTMLPart::checkCompleted()
{
// kDebug( 6050 ) << this;
// kDebug( 6050 ) << " parsing: " << (d->m_doc && d->m_doc->parsing());
// kDebug( 6050 ) << " complete: " << d->m_bComplete;
// restore the cursor position
if (d->m_doc && !d->m_doc->parsing() && !d->m_focusNodeRestored)
{
if (d->m_focusNodeNumber >= 0)
d->m_doc->setFocusNode(d->m_doc->nodeWithAbsIndex(d->m_focusNodeNumber));
d->m_focusNodeRestored = true;
}
bool fullyLoaded, pendingChildRedirections;
fullyLoaded = d->isFullyLoaded(&pendingChildRedirections);
// Are we still loading, or already have done the relevant work?
if (!fullyLoaded || d->m_bComplete)
return;
// OK, completed.
// Now do what should be done when we are really completed.
d->m_bComplete = true;
d->m_cachePolicy = KProtocolManager::cacheControl(); // reset cache policy
d->m_totalObjectCount = 0;
d->m_loadedObjects = 0;
KHTMLPart* p = this;
while ( p ) {
KHTMLPart* op = p;
p = p->parentPart();
if ( !p && !op->d->m_progressUpdateTimer.isActive()) {
op->d->m_progressUpdateTimer.setSingleShot( true );
op->d->m_progressUpdateTimer.start( 0 );
}
}
checkEmitLoadEvent(); // if we didn't do it before
bool pendingAction = false;
if ( !d->m_redirectURL.isEmpty() )
{
// DA: Do not start redirection for frames here! That action is
// deferred until the parent emits a completed signal.
if ( parentPart() == 0 ) {
//kDebug(6050) << this << " starting redirection timer";
d->m_redirectionTimer.setSingleShot( true );
d->m_redirectionTimer.start( qMax(0, 1000 * d->m_delayRedirect) );
} else {
//kDebug(6050) << this << " not toplevel -> not starting redirection timer. Waiting for slotParentCompleted.";
}
pendingAction = true;
}
else if ( pendingChildRedirections )
{
pendingAction = true;
}
// the view will emit completed on our behalf,
// either now or at next repaint if one is pending
//kDebug(6050) << this << " asks the view to emit completed. pendingAction=" << pendingAction;
d->m_view->complete( pendingAction );
// find the alternate stylesheets
QStringList sheets;
if (d->m_doc)
sheets = d->m_doc->availableStyleSheets();
sheets.prepend( i18n( "Automatic Detection" ) );
d->m_paUseStylesheet->setItems( sheets );
d->m_paUseStylesheet->setEnabled( sheets.count() > 2);
if (sheets.count() > 2)
{
d->m_paUseStylesheet->setCurrentItem(qMax(sheets.indexOf(d->m_sheetUsed), 0));
slotUseStylesheet();
}
setJSDefaultStatusBarText(QString());
#ifdef SPEED_DEBUG
if (!parentPart())
kDebug(6080) << "DONE:" <<d->m_parsetime.elapsed();
#endif
}
void KHTMLPart::checkEmitLoadEvent()
{
bool fullyLoaded, pendingChildRedirections;
fullyLoaded = d->isFullyLoaded(&pendingChildRedirections);
// ### might want to wait on pendingChildRedirections here, too
if ( d->m_bLoadEventEmitted || !d->m_doc || !fullyLoaded ) return;
d->m_bLoadEventEmitted = true;
if (d->m_doc)
d->m_doc->close();
}
const KHTMLSettings *KHTMLPart::settings() const
{
return d->m_settings;
}
#ifndef KDE_NO_COMPAT // KDE5: remove this ifndef, keep the method (renamed to baseUrl)
KUrl KHTMLPart::baseURL() const
{
if ( !d->m_doc ) return KUrl();
return d->m_doc->baseURL();
}
#endif
KUrl KHTMLPart::completeURL( const QString &url )
{
if ( !d->m_doc ) return KUrl( url );
#if 0
if (d->m_decoder)
return KUrl(d->m_doc->completeURL(url), d->m_decoder->codec()->mibEnum());
#endif
return KUrl( d->m_doc->completeURL( url ) );
}
QString KHTMLPartPrivate::codeForJavaScriptURL(const QString &u)
{
return KUrl::fromPercentEncoding( u.right( u.length() - 11 ).toUtf8() );
}
void KHTMLPartPrivate::executeJavascriptURL(const QString &u)
{
QString script = codeForJavaScriptURL(u);
kDebug( 6050 ) << "script=" << script;
QVariant res = q->executeScript( DOM::Node(), script );
if ( res.type() == QVariant::String ) {
q->begin( q->url() );
q->setAlwaysHonourDoctype(); // Disable public API compat; it messes with doctype
q->write( res.toString() );
q->end();
}
emit q->completed();
}
bool KHTMLPartPrivate::isJavaScriptURL(const QString& url)
{
return url.indexOf( QLatin1String( "javascript:" ), 0, Qt::CaseInsensitive ) == 0;
}
// Called by ecma/kjs_window in case of redirections from Javascript,
// and by xml/dom_docimpl.cpp in case of http-equiv meta refresh.
void KHTMLPart::scheduleRedirection( int delay, const QString &url, bool doLockHistory )
{
kDebug(6050) << "delay=" << delay << " url=" << url << " from=" << this->url() << "parent=" << parentPart();
kDebug(6050) << "current redirectURL=" << d->m_redirectURL << " with delay " << d->m_delayRedirect;
// In case of JS redirections, some, such as jump to anchors, and javascript:
// evaluation should actually be handled immediately, and not waiting until
// the end of the script. (Besides, we don't want to abort the tokenizer for those)
if ( delay == -1 && d->isInPageURL(url) ) {
d->executeInPageURL(url, doLockHistory);
return;
}
if( delay < 24*60*60 &&
( d->m_redirectURL.isEmpty() || delay <= d->m_delayRedirect) ) {
d->m_delayRedirect = delay;
d->m_redirectURL = url;
d->m_redirectLockHistory = doLockHistory;
kDebug(6050) << " d->m_bComplete=" << d->m_bComplete;
if ( d->m_bComplete ) {
d->m_redirectionTimer.stop();
d->m_redirectionTimer.setSingleShot( true );
d->m_redirectionTimer.start( qMax(0, 1000 * d->m_delayRedirect) );
}
}
}
void KHTMLPartPrivate::clearRedirection()
{
m_delayRedirect = 0;
m_redirectURL.clear();
m_redirectionTimer.stop();
}
void KHTMLPart::slotRedirect()
{
kDebug(6050) << this;
QString u = d->m_redirectURL;
KUrl url( u );
d->clearRedirection();
if ( d->isInPageURL(u) )
{
d->executeInPageURL(u, d->m_redirectLockHistory);
return;
}
KParts::OpenUrlArguments args;
KUrl cUrl( this->url() );
// handle windows opened by JS
if ( openedByJS() && d->m_opener )
cUrl = d->m_opener->url();
if (!KAuthorized::authorizeUrlAction("redirect", cUrl, url))
{
kWarning(6050) << "KHTMLPart::scheduleRedirection: Redirection from " << cUrl << " to " << url << " REJECTED!";
emit completed();
return;
}
if ( url.equals(this->url(),
KUrl::CompareWithoutTrailingSlash | KUrl::CompareWithoutFragment | KUrl::AllowEmptyPath) )
{
args.metaData().insert("referrer", d->m_pageReferrer);
}
// For javascript and META-tag based redirections:
// - We don't take cross-domain-ness in consideration if we are the
// toplevel frame because the new URL may be in a different domain as the current URL
// but that's ok.
// - If we are not the toplevel frame then we check against the toplevelURL()
if (parentPart())
args.metaData().insert("cross-domain", toplevelURL().url());
KParts::BrowserArguments browserArgs;
browserArgs.setLockHistory( d->m_redirectLockHistory );
// _self: make sure we don't use any <base target=>'s
if ( !urlSelected( u, 0, 0, "_self", args, browserArgs ) ) {
// urlSelected didn't open a url, so emit completed ourselves
emit completed();
}
}
void KHTMLPart::slotRedirection(KIO::Job*, const KUrl& url)
{
// the slave told us that we got redirected
//kDebug( 6050 ) << "redirection by KIO to" << url;
emit d->m_extension->setLocationBarUrl( url.prettyUrl() );
d->m_workingURL = url;
}
bool KHTMLPart::setEncoding( const QString &name, bool override )
{
d->m_encoding = name;
d->m_haveEncoding = override;
if( !url().isEmpty() ) {
// reload document
closeUrl();
KUrl oldUrl = url();
setUrl(KUrl());
d->m_restored = true;
openUrl(oldUrl);
d->m_restored = false;
}
return true;
}
QString KHTMLPart::encoding() const
{
if(d->m_haveEncoding && !d->m_encoding.isEmpty())
return d->m_encoding;
if(d->m_decoder && d->m_decoder->encoding())
return QString(d->m_decoder->encoding());
return defaultEncoding();
}
QString KHTMLPart::defaultEncoding() const
{
QString encoding = settings()->encoding();
if ( !encoding.isEmpty() )
return encoding;
// HTTP requires the default encoding to be latin1, when neither
// the user nor the page requested a particular encoding.
- if ( url().protocol().startsWith( "http" ) )
+ if ( url().scheme().startsWith( "http" ) )
return "iso-8859-1";
else
return KGlobal::locale()->encoding();
}
void KHTMLPart::setUserStyleSheet(const KUrl &url)
{
if ( d->m_doc && d->m_doc->docLoader() )
(void) new khtml::PartStyleSheetLoader(this, url.url(), d->m_doc->docLoader());
}
void KHTMLPart::setUserStyleSheet(const QString &styleSheet)
{
if ( d->m_doc )
d->m_doc->setUserStyleSheet( styleSheet );
}
bool KHTMLPart::gotoAnchor( const QString &name )
{
if (!d->m_doc)
return false;
HTMLCollectionImpl *anchors =
new HTMLCollectionImpl( d->m_doc, HTMLCollectionImpl::DOC_ANCHORS);
anchors->ref();
NodeImpl *n = anchors->namedItem(name);
anchors->deref();
if(!n) {
n = d->m_doc->getElementById( name );
}
d->m_doc->setCSSTarget(n); // Setting to null will clear the current target.
// Implement the rule that "" and "top" both mean top of page as in other browsers.
bool quirkyName = !n && !d->m_doc->inStrictMode() && (name.isEmpty() || name.toLower() == "top");
if (quirkyName) {
d->m_view->setContentsPos( d->m_view->contentsX(), 0);
return true;
} else if (!n) {
kDebug(6050) << name << "not found";
return false;
}
int x = 0, y = 0;
int gox, dummy;
HTMLElementImpl *a = static_cast<HTMLElementImpl *>(n);
a->getUpperLeftCorner(x, y);
if (x <= d->m_view->contentsX())
gox = x - 10;
else {
gox = d->m_view->contentsX();
if ( x + 10 > d->m_view->contentsX()+d->m_view->visibleWidth()) {
a->getLowerRightCorner(x, dummy);
gox = x - d->m_view->visibleWidth() + 10;
}
}
d->m_view->setContentsPos(gox, y);
return true;
}
bool KHTMLPart::nextAnchor()
{
if (!d->m_doc)
return false;
d->m_view->focusNextPrevNode ( true );
return true;
}
bool KHTMLPart::prevAnchor()
{
if (!d->m_doc)
return false;
d->m_view->focusNextPrevNode ( false );
return true;
}
void KHTMLPart::setStandardFont( const QString &name )
{
d->m_settings->setStdFontName(name);
}
void KHTMLPart::setFixedFont( const QString &name )
{
d->m_settings->setFixedFontName(name);
}
void KHTMLPart::setURLCursor( const QCursor &c )
{
d->m_linkCursor = c;
}
QCursor KHTMLPart::urlCursor() const
{
return d->m_linkCursor;
}
bool KHTMLPart::onlyLocalReferences() const
{
return d->m_onlyLocalReferences;
}
void KHTMLPart::setOnlyLocalReferences(bool enable)
{
d->m_onlyLocalReferences = enable;
}
bool KHTMLPart::forcePermitLocalImages() const
{
return d->m_forcePermitLocalImages;
}
void KHTMLPart::setForcePermitLocalImages(bool enable)
{
d->m_forcePermitLocalImages = enable;
}
void KHTMLPartPrivate::setFlagRecursively(
bool KHTMLPartPrivate::*flag, bool value)
{
// first set it on the current one
this->*flag = value;
// descend into child frames recursively
{
QList<khtml::ChildFrame*>::Iterator it = m_frames.begin();
const QList<khtml::ChildFrame*>::Iterator itEnd = m_frames.end();
for (; it != itEnd; ++it) {
KHTMLPart* const part = qobject_cast<KHTMLPart *>( (*it)->m_part.data() );
if (part)
part->d->setFlagRecursively(flag, value);
}/*next it*/
}
// do the same again for objects
{
QList<khtml::ChildFrame*>::Iterator it = m_objects.begin();
const QList<khtml::ChildFrame*>::Iterator itEnd = m_objects.end();
for (; it != itEnd; ++it) {
KHTMLPart* const part = qobject_cast<KHTMLPart *>( (*it)->m_part.data() );
if (part)
part->d->setFlagRecursively(flag, value);
}/*next it*/
}
}
void KHTMLPart::initCaret()
{
// initialize caret if not used yet
if (d->editor_context.m_selection.state() == Selection::NONE) {
if (d->m_doc) {
NodeImpl *node;
if (d->m_doc->isHTMLDocument()) {
HTMLDocumentImpl* htmlDoc = static_cast<HTMLDocumentImpl*>(d->m_doc);
node = htmlDoc->body();
} else
node = d->m_doc;
if (!node) return;
d->editor_context.m_selection.moveTo(Position(node, 0));
d->editor_context.m_selection.setNeedsLayout();
d->editor_context.m_selection.needsCaretRepaint();
}
}
}
static void setCaretInvisibleIfNeeded(KHTMLPart *part)
{
// On contenteditable nodes, don't hide the caret
if (!khtml::KHTMLPartAccessor::caret(part).caretPos().node()->isContentEditable())
part->setCaretVisible(false);
}
void KHTMLPart::setCaretMode(bool enable)
{
kDebug(6200) << enable;
if (isCaretMode() == enable) return;
d->setFlagRecursively(&KHTMLPartPrivate::m_caretMode, enable);
// FIXME: this won't work on frames as expected
if (!isEditable()) {
if (enable) {
initCaret();
setCaretVisible(true);
// view()->ensureCaretVisible();
} else {
setCaretInvisibleIfNeeded(this);
}
}
}
bool KHTMLPart::isCaretMode() const
{
return d->m_caretMode;
}
void KHTMLPart::setEditable(bool enable)
{
if (isEditable() == enable) return;
d->setFlagRecursively(&KHTMLPartPrivate::m_designMode, enable);
// FIXME: this won't work on frames as expected
if (!isCaretMode()) {
if (enable) {
initCaret();
setCaretVisible(true);
// view()->ensureCaretVisible();
} else
setCaretInvisibleIfNeeded(this);
}
}
bool KHTMLPart::isEditable() const
{
return d->m_designMode;
}
khtml::EditorContext *KHTMLPart::editorContext() const {
return &d->editor_context;
}
void KHTMLPart::setCaretPosition(DOM::Node node, long offset, bool extendSelection)
{
Q_UNUSED(node);
Q_UNUSED(offset);
Q_UNUSED(extendSelection);
#ifndef KHTML_NO_CARET
#if 0
kDebug(6200) << "node: " << node.handle() << " nodeName: "
<< node.nodeName().string() << " offset: " << offset
<< " extendSelection " << extendSelection;
if (view()->moveCaretTo(node.handle(), offset, !extendSelection))
emitSelectionChanged();
view()->ensureCaretVisible();
#endif
#endif // KHTML_NO_CARET
}
KHTMLPart::CaretDisplayPolicy KHTMLPart::caretDisplayPolicyNonFocused() const
{
#if 0
#ifndef KHTML_NO_CARET
return (CaretDisplayPolicy)view()->caretDisplayPolicyNonFocused();
#else // KHTML_NO_CARET
return CaretInvisible;
#endif // KHTML_NO_CARET
#endif
return CaretInvisible;
}
void KHTMLPart::setCaretDisplayPolicyNonFocused(CaretDisplayPolicy policy)
{
Q_UNUSED(policy);
#if 0
#ifndef KHTML_NO_CARET
view()->setCaretDisplayPolicyNonFocused(policy);
#endif // KHTML_NO_CARET
#endif
}
void KHTMLPart::setCaretVisible(bool show)
{
if (show) {
NodeImpl *caretNode = d->editor_context.m_selection.caretPos().node();
if (isCaretMode() || (caretNode && caretNode->isContentEditable())) {
invalidateSelection();
enableFindAheadActions(false);
}
} else {
if (d->editor_context.m_caretBlinkTimer >= 0)
killTimer(d->editor_context.m_caretBlinkTimer);
clearCaretRectIfNeeded();
}
}
void KHTMLPart::findTextBegin()
{
d->m_find.findTextBegin();
}
bool KHTMLPart::initFindNode( bool selection, bool reverse, bool fromCursor )
{
return d->m_find.initFindNode(selection, reverse, fromCursor);
}
void KHTMLPart::slotFind()
{
KParts::ReadOnlyPart *part = currentFrame();
if (!part)
return;
if (!part->inherits("KHTMLPart") )
{
kError(6000) << "part is a" << part->metaObject()->className() << ", can't do a search into it";
return;
}
static_cast<KHTMLPart *>( part )->findText();
}
void KHTMLPart::slotFindNext()
{
KParts::ReadOnlyPart *part = currentFrame();
if (!part)
return;
if (!part->inherits("KHTMLPart") )
{
kError(6000) << "part is a" << part->metaObject()->className() << ", can't do a search into it";
return;
}
static_cast<KHTMLPart *>( part )->findTextNext();
}
void KHTMLPart::slotFindPrev()
{
KParts::ReadOnlyPart *part = currentFrame();
if (!part)
return;
if (!part->inherits("KHTMLPart") )
{
kError(6000) << "part is a" << part->metaObject()->className() << ", can't do a search into it";
return;
}
static_cast<KHTMLPart *>( part )->findTextNext( true ); // reverse
}
void KHTMLPart::slotFindDone()
{
// ### remove me
}
void KHTMLPart::slotFindAheadText()
{
KHTMLPart *part = qobject_cast<KHTMLPart*>(currentFrame());
if (!part)
return;
part->findText();
KHTMLFindBar* findBar = part->d->m_find.findBar();
findBar->setOptions(findBar->options() & ~FindLinksOnly);
}
void KHTMLPart::slotFindAheadLink()
{
KHTMLPart *part = qobject_cast<KHTMLPart*>(currentFrame());
if (!part)
return;
part->findText();
KHTMLFindBar* findBar = part->d->m_find.findBar();
findBar->setOptions(findBar->options() | FindLinksOnly);
}
void KHTMLPart::enableFindAheadActions( bool )
{
// ### remove me
}
void KHTMLPart::slotFindDialogDestroyed()
{
// ### remove me
}
void KHTMLPart::findText()
{
if (parentPart())
return parentPart()->findText();
d->m_find.activate();
}
void KHTMLPart::findText( const QString &str, long options, QWidget *parent, KFindDialog *findDialog )
{
if (parentPart())
return parentPart()->findText(str, options, parent, findDialog);
d->m_find.createNewKFind(str, options, parent, findDialog );
}
// New method
bool KHTMLPart::findTextNext( bool reverse )
{
if (parentPart())
return parentPart()->findTextNext( reverse );
return d->m_find.findTextNext( reverse );
}
bool KHTMLPart::pFindTextNextInThisFrame( bool reverse )
{
return d->m_find.findTextNext( reverse );
}
QString KHTMLPart::selectedTextAsHTML() const
{
const Selection &sel = d->editor_context.m_selection;
if(!hasSelection()) {
kDebug() << "Selection is not valid. Returning empty selection";
return QString();
}
if(sel.start().offset() < 0 || sel.end().offset() < 0) {
kDebug() << "invalid values for end/startOffset " << sel.start().offset() << " " << sel.end().offset();
return QString();
}
DOM::Range r = selection();
if(r.isNull() || r.isDetached())
return QString();
int exceptioncode = 0; //ignore the result
return r.handle()->toHTML(exceptioncode).string();
}
QString KHTMLPart::selectedText() const
{
bool hasNewLine = true;
bool seenTDTag = false;
QString text;
const Selection &sel = d->editor_context.m_selection;
DOM::Node n = sel.start().node();
while(!n.isNull()) {
if(n.nodeType() == DOM::Node::TEXT_NODE && n.handle()->renderer()) {
DOM::DOMStringImpl *dstr = static_cast<DOM::TextImpl*>(n.handle())->renderString();
QString str(dstr->s, dstr->l);
if(!str.isEmpty()) {
if(seenTDTag) {
text += " ";
seenTDTag = false;
}
hasNewLine = false;
if(n == sel.start().node() && n == sel.end().node()) {
int s = khtml::RenderPosition::fromDOMPosition(sel.start()).renderedOffset();
int e = khtml::RenderPosition::fromDOMPosition(sel.end()).renderedOffset();
text = str.mid(s, e-s);
} else if(n == sel.start().node()) {
text = str.mid(khtml::RenderPosition::fromDOMPosition(sel.start()).renderedOffset());
} else if(n == sel.end().node()) {
text += str.left(khtml::RenderPosition::fromDOMPosition(sel.end()).renderedOffset());
} else
text += str;
}
}
else {
// This is our simple HTML -> ASCII transformation:
unsigned short id = n.elementId();
switch(id) {
case ID_TEXTAREA:
text += static_cast<HTMLTextAreaElementImpl*>(n.handle())->value().string();
break;
case ID_INPUT:
if (static_cast<HTMLInputElementImpl*>(n.handle())->inputType() != HTMLInputElementImpl::PASSWORD)
text += static_cast<HTMLInputElementImpl*>(n.handle())->value().string();
break;
case ID_SELECT:
text += static_cast<HTMLSelectElementImpl*>(n.handle())->value().string();
break;
case ID_BR:
text += "\n";
hasNewLine = true;
break;
case ID_IMG:
text += static_cast<HTMLImageElementImpl*>(n.handle())->altText().string();
break;
case ID_TD:
break;
case ID_TH:
case ID_HR:
case ID_OL:
case ID_UL:
case ID_LI:
case ID_DD:
case ID_DL:
case ID_DT:
case ID_PRE:
case ID_LISTING:
case ID_BLOCKQUOTE:
case ID_DIV:
if (!hasNewLine)
text += "\n";
hasNewLine = true;
break;
case ID_P:
case ID_TR:
case ID_H1:
case ID_H2:
case ID_H3:
case ID_H4:
case ID_H5:
case ID_H6:
if (!hasNewLine)
text += "\n";
hasNewLine = true;
break;
}
}
if(n == sel.end().node()) break;
DOM::Node next = n.firstChild();
if(next.isNull()) next = n.nextSibling();
while( next.isNull() && !n.parentNode().isNull() ) {
n = n.parentNode();
next = n.nextSibling();
unsigned short id = n.elementId();
switch(id) {
case ID_TD:
seenTDTag = true; //Add two spaces after a td if then followed by text.
break;
case ID_TH:
case ID_HR:
case ID_OL:
case ID_UL:
case ID_LI:
case ID_DD:
case ID_DL:
case ID_DT:
case ID_PRE:
case ID_LISTING:
case ID_BLOCKQUOTE:
case ID_DIV:
seenTDTag = false;
if (!hasNewLine)
text += "\n";
hasNewLine = true;
break;
case ID_P:
case ID_TR:
case ID_H1:
case ID_H2:
case ID_H3:
case ID_H4:
case ID_H5:
case ID_H6:
if (!hasNewLine)
text += "\n";
// text += "\n";
hasNewLine = true;
break;
}
}
n = next;
}
if(text.isEmpty())
return QString();
int start = 0;
int end = text.length();
// Strip leading LFs
while ((start < end) && (text[start] == '\n'))
++start;
// Strip excessive trailing LFs
while ((start < (end-1)) && (text[end-1] == '\n') && (text[end-2] == '\n'))
--end;
return text.mid(start, end-start);
}
QString KHTMLPart::simplifiedSelectedText() const
{
QString text = selectedText();
text.replace(QChar(0xa0), ' ');
// remove leading and trailing whitespace
while (!text.isEmpty() && text[0].isSpace())
text = text.mid(1);
while (!text.isEmpty() && text[text.length()-1].isSpace())
text.truncate(text.length()-1);
return text;
}
bool KHTMLPart::hasSelection() const
{
return !d->editor_context.m_selection.isEmpty() && !d->editor_context.m_selection.isCollapsed();
}
DOM::Range KHTMLPart::selection() const
{
return d->editor_context.m_selection.toRange();
}
void KHTMLPart::selection(DOM::Node &s, long &so, DOM::Node &e, long &eo) const
{
DOM::Range r = d->editor_context.m_selection.toRange();
s = r.startContainer();
so = r.startOffset();
e = r.endContainer();
eo = r.endOffset();
}
void KHTMLPart::setSelection( const DOM::Range &r )
{
setCaret(r);
}
const Selection &KHTMLPart::caret() const
{
return d->editor_context.m_selection;
}
const Selection &KHTMLPart::dragCaret() const
{
return d->editor_context.m_dragCaret;
}
void KHTMLPart::setCaret(const Selection &s, bool closeTyping)
{
if (d->editor_context.m_selection != s) {
clearCaretRectIfNeeded();
setFocusNodeIfNeeded(s);
d->editor_context.m_selection = s;
notifySelectionChanged(closeTyping);
}
}
void KHTMLPart::setDragCaret(const DOM::Selection &dragCaret)
{
if (d->editor_context.m_dragCaret != dragCaret) {
d->editor_context.m_dragCaret.needsCaretRepaint();
d->editor_context.m_dragCaret = dragCaret;
d->editor_context.m_dragCaret.needsCaretRepaint();
}
}
void KHTMLPart::clearSelection()
{
clearCaretRectIfNeeded();
setFocusNodeIfNeeded(d->editor_context.m_selection);
#ifdef APPLE_CHANGES
d->editor_context.m_selection.clear();
#else
d->editor_context.m_selection.collapse();
#endif
notifySelectionChanged();
}
void KHTMLPart::invalidateSelection()
{
clearCaretRectIfNeeded();
d->editor_context.m_selection.setNeedsLayout();
selectionLayoutChanged();
}
void KHTMLPart::setSelectionVisible(bool flag)
{
if (d->editor_context.m_caretVisible == flag)
return;
clearCaretRectIfNeeded();
setFocusNodeIfNeeded(d->editor_context.m_selection);
d->editor_context.m_caretVisible = flag;
// notifySelectionChanged();
}
#if 1
void KHTMLPart::slotClearSelection()
{
if (!isCaretMode()
&& d->editor_context.m_selection.state() != Selection::NONE
&& !d->editor_context.m_selection.caretPos().node()->isContentEditable())
clearCaretRectIfNeeded();
bool hadSelection = hasSelection();
#ifdef APPLE_CHANGES
d->editor_context.m_selection.clear();
#else
d->editor_context.m_selection.collapse();
#endif
if (hadSelection)
notifySelectionChanged();
}
#endif
void KHTMLPart::clearCaretRectIfNeeded()
{
if (d->editor_context.m_caretPaint) {
d->editor_context.m_caretPaint = false;
d->editor_context.m_selection.needsCaretRepaint();
}
}
void KHTMLPart::setFocusNodeIfNeeded(const Selection &s)
{
if (!xmlDocImpl() || s.state() == Selection::NONE)
return;
NodeImpl *n = s.start().node();
NodeImpl *target = (n && n->isContentEditable()) ? n : 0;
if (!target) {
while (n && n != s.end().node()) {
if (n->isContentEditable()) {
target = n;
break;
}
n = n->traverseNextNode();
}
}
assert(target == 0 || target->isContentEditable());
if (target) {
for ( ; target && !target->isFocusable(); target = target->parentNode())
{}
if (target && target->isMouseFocusable())
xmlDocImpl()->setFocusNode(target);
else if (!target || !target->focused())
xmlDocImpl()->setFocusNode(0);
}
}
void KHTMLPart::selectionLayoutChanged()
{
// kill any caret blink timer now running
if (d->editor_context.m_caretBlinkTimer >= 0) {
killTimer(d->editor_context.m_caretBlinkTimer);
d->editor_context.m_caretBlinkTimer = -1;
}
// see if a new caret blink timer needs to be started
if (d->editor_context.m_caretVisible
&& d->editor_context.m_selection.state() != Selection::NONE) {
d->editor_context.m_caretPaint = isCaretMode()
|| d->editor_context.m_selection.caretPos().node()->isContentEditable();
if (d->editor_context.m_caretBlinks && d->editor_context.m_caretPaint)
d->editor_context.m_caretBlinkTimer = startTimer(qApp->cursorFlashTime() / 2);
d->editor_context.m_selection.needsCaretRepaint();
// make sure that caret is visible
QRect r(d->editor_context.m_selection.getRepaintRect());
if (d->editor_context.m_caretPaint)
d->m_view->ensureVisible(r.x(), r.y());
}
if (d->m_doc)
d->m_doc->updateSelection();
// Always clear the x position used for vertical arrow navigation.
// It will be restored by the vertical arrow navigation code if necessary.
d->editor_context.m_xPosForVerticalArrowNavigation = d->editor_context.NoXPosForVerticalArrowNavigation;
}
void KHTMLPart::notifySelectionChanged(bool closeTyping)
{
Editor *ed = d->editor_context.m_editor;
selectionLayoutChanged();
if (ed) {
ed->clearTypingStyle();
if (closeTyping)
ed->closeTyping();
}
emitSelectionChanged();
}
void KHTMLPart::timerEvent(QTimerEvent *e)
{
if (e->timerId() == d->editor_context.m_caretBlinkTimer) {
if (d->editor_context.m_caretBlinks &&
d->editor_context.m_selection.state() != Selection::NONE) {
d->editor_context.m_caretPaint = !d->editor_context.m_caretPaint;
d->editor_context.m_selection.needsCaretRepaint();
}
} else if (e->timerId() == d->m_DNSPrefetchTimer) {
// kDebug( 6050 ) << "will lookup " << d->m_DNSPrefetchQueue.head() << d->m_numDNSPrefetchedNames;
KIO::HostInfo::prefetchHost( d->m_DNSPrefetchQueue.dequeue() );
if (d->m_DNSPrefetchQueue.isEmpty()) {
killTimer( d->m_DNSPrefetchTimer );
d->m_DNSPrefetchTimer = -1;
}
} else if (e->timerId() == d->m_DNSTTLTimer) {
foreach (const QString &name, d->m_lookedupHosts)
d->m_DNSPrefetchQueue.enqueue(name);
if (d->m_DNSPrefetchTimer <= 0)
d->m_DNSPrefetchTimer = startTimer( sDNSPrefetchTimerDelay );
}
}
bool KHTMLPart::mayPrefetchHostname( const QString& name )
{
if (d->m_bDNSPrefetch == DNSPrefetchDisabled)
return false;
if (d->m_numDNSPrefetchedNames >= sMaxDNSPrefetchPerPage)
return false;
if (d->m_bDNSPrefetch == DNSPrefetchOnlyWWWAndSLD) {
int dots = name.count('.');
if (dots > 2 || (dots == 2 && !name.startsWith("www.")))
return false;
}
if ( d->m_lookedupHosts.contains( name ) )
return false;
d->m_DNSPrefetchQueue.enqueue( name );
d->m_lookedupHosts.insert( name );
d->m_numDNSPrefetchedNames++;
if (d->m_DNSPrefetchTimer < 1)
d->m_DNSPrefetchTimer = startTimer( sDNSPrefetchTimerDelay );
if (d->m_DNSTTLTimer < 1)
d->m_DNSTTLTimer = startTimer( sDNSTTLSeconds*1000 + 1 );
return true;
}
void KHTMLPart::paintCaret(QPainter *p, const QRect &rect) const
{
if (d->editor_context.m_caretPaint)
d->editor_context.m_selection.paintCaret(p, rect);
}
void KHTMLPart::paintDragCaret(QPainter *p, const QRect &rect) const
{
d->editor_context.m_dragCaret.paintCaret(p, rect);
}
DOM::Editor *KHTMLPart::editor() const {
if (!d->editor_context.m_editor)
const_cast<KHTMLPart *>(this)->d->editor_context.m_editor = new DOM::Editor(const_cast<KHTMLPart *>(this));
return d->editor_context.m_editor;
}
void KHTMLPart::resetHoverText()
{
if( !d->m_overURL.isEmpty() ) // Only if we were showing a link
{
d->m_overURL.clear();
d->m_overURLTarget.clear();
emit onURL( QString() );
// revert to default statusbar text
setStatusBarText(QString(), BarHoverText);
emit d->m_extension->mouseOverInfo(KFileItem());
}
}
void KHTMLPart::overURL( const QString &url, const QString &target, bool /*shiftPressed*/ )
{
KUrl u = completeURL(url);
// special case for <a href="">
if ( url.isEmpty() )
u.setFileName( url );
emit onURL( url );
if ( url.isEmpty() ) {
setStatusBarText(Qt::escape(u.prettyUrl()), BarHoverText);
return;
}
if ( d->isJavaScriptURL(url) ) {
QString jscode = d->codeForJavaScriptURL( url );
jscode = KStringHandler::rsqueeze( jscode, 80 ); // truncate if too long
if (url.startsWith("javascript:window.open"))
jscode += i18n(" (In new window)");
setStatusBarText( Qt::escape( jscode ), BarHoverText );
return;
}
KFileItem item(u, QString(), KFileItem::Unknown);
emit d->m_extension->mouseOverInfo(item);
QString com;
KMimeType::Ptr typ = KMimeType::findByUrl( u );
if ( typ )
com = typ->comment( u );
if ( !u.isValid() ) {
setStatusBarText(Qt::escape(u.prettyUrl()), BarHoverText);
return;
}
if ( u.isLocalFile() )
{
// TODO : use KIO::stat() and create a KFileItem out of its result,
// to use KFileItem::statusBarText()
const QString path = QFile::encodeName( u.toLocalFile() );
KDE_struct_stat buff;
bool ok = !KDE::stat( path, &buff );
KDE_struct_stat lbuff;
if (ok) ok = !KDE::lstat( path, &lbuff );
QString text = Qt::escape(u.prettyUrl());
QString text2 = text;
if (ok && S_ISLNK( lbuff.st_mode ) )
{
QString tmp;
if ( com.isNull() )
tmp = i18n( "Symbolic Link");
else
tmp = i18n("%1 (Link)", com);
char buff_two[1024];
text += " -> ";
int n = readlink ( path.toLocal8Bit().data(), buff_two, 1022);
if (n == -1)
{
text2 += " ";
text2 += tmp;
setStatusBarText(text2, BarHoverText);
return;
}
buff_two[n] = 0;
text += buff_two;
text += " ";
text += tmp;
}
else if ( ok && S_ISREG( buff.st_mode ) )
{
if (buff.st_size < 1024)
text = i18np("%2 (%1 byte)", "%2 (%1 bytes)", (long) buff.st_size, text2); // always put the URL last, in case it contains '%'
else
{
float d = (float) buff.st_size/1024.0;
text = i18n("%2 (%1 K)", KGlobal::locale()->formatNumber(d, 2), text2); // was %.2f
}
text += " ";
text += com;
}
else if ( ok && S_ISDIR( buff.st_mode ) )
{
text += " ";
text += com;
}
else
{
text += " ";
text += com;
}
setStatusBarText(text, BarHoverText);
}
else
{
QString extra;
if (target.toLower() == "_blank")
{
extra = i18n(" (In new window)");
}
else if (!target.isEmpty() &&
(target.toLower() != "_top") &&
(target.toLower() != "_self") &&
(target.toLower() != "_parent"))
{
KHTMLPart *p = this;
while (p->parentPart())
p = p->parentPart();
if (!p->frameExists(target))
extra = i18n(" (In new window)");
else
extra = i18n(" (In other frame)");
}
- if (u.protocol() == QLatin1String("mailto")) {
+ if (u.scheme() == QLatin1String("mailto")) {
QString mailtoMsg /* = QString::fromLatin1("<img src=%1>").arg(locate("icon", QString::fromLatin1("locolor/16x16/actions/mail_send.png")))*/;
mailtoMsg += i18n("Email to: ") + KUrl::fromPercentEncoding(u.path().toLatin1());
const QStringList queries = u.query().mid(1).split('&');
QStringList::ConstIterator it = queries.begin();
const QStringList::ConstIterator itEnd = queries.end();
for (; it != itEnd; ++it)
if ((*it).startsWith(QLatin1String("subject=")))
mailtoMsg += i18n(" - Subject: ") + KUrl::fromPercentEncoding((*it).mid(8).toLatin1());
else if ((*it).startsWith(QLatin1String("cc=")))
mailtoMsg += i18n(" - CC: ") + KUrl::fromPercentEncoding((*it).mid(3).toLatin1());
else if ((*it).startsWith(QLatin1String("bcc=")))
mailtoMsg += i18n(" - BCC: ") + KUrl::fromPercentEncoding((*it).mid(4).toLatin1());
mailtoMsg = Qt::escape(mailtoMsg);
mailtoMsg.replace(QRegExp("([\n\r\t]|[ ]{10})"), QString());
setStatusBarText("<qt>"+mailtoMsg, BarHoverText);
return;
}
// Is this check necessary at all? (Frerich)
#if 0
- else if (u.protocol() == QLatin1String("http")) {
+ else if (u.scheme() == QLatin1String("http")) {
DOM::Node hrefNode = nodeUnderMouse().parentNode();
while (hrefNode.nodeName().string() != QLatin1String("A") && !hrefNode.isNull())
hrefNode = hrefNode.parentNode();
if (!hrefNode.isNull()) {
DOM::Node hreflangNode = hrefNode.attributes().getNamedItem("HREFLANG");
if (!hreflangNode.isNull()) {
QString countryCode = hreflangNode.nodeValue().string().toLower();
// Map the language code to an appropriate country code.
if (countryCode == QLatin1String("en"))
countryCode = QLatin1String("gb");
QString flagImg = QLatin1String("<img src=%1>").arg(
locate("locale", QLatin1String("l10n/")
+ countryCode
+ QLatin1String("/flag.png")));
emit setStatusBarText(flagImg + u.prettyUrl() + extra);
}
}
}
#endif
setStatusBarText(Qt::escape(u.prettyUrl()) + extra, BarHoverText);
}
}
//
// This executes in the active part on a click or other url selection action in
// that active part.
//
bool KHTMLPart::urlSelected( const QString &url, int button, int state, const QString &_target, const KParts::OpenUrlArguments& _args, const KParts::BrowserArguments& _browserArgs )
{
KParts::OpenUrlArguments args = _args;
KParts::BrowserArguments browserArgs = _browserArgs;
bool hasTarget = false;
QString target = _target;
if ( target.isEmpty() && d->m_doc )
target = d->m_doc->baseTarget();
if ( !target.isEmpty() )
hasTarget = true;
if ( d->isJavaScriptURL(url) )
{
crossFrameExecuteScript( target, d->codeForJavaScriptURL(url) );
return false;
}
KUrl cURL = completeURL(url);
// special case for <a href=""> (IE removes filename, mozilla doesn't)
if ( url.isEmpty() )
cURL.setFileName( url ); // removes filename
if ( !cURL.isValid() )
// ### ERROR HANDLING
return false;
kDebug(6050) << this << "complete URL:" << cURL.url() << "target=" << target;
if ( state & Qt::ControlModifier )
{
emit d->m_extension->createNewWindow( cURL, args, browserArgs );
return true;
}
if ( button == Qt::LeftButton && ( state & Qt::ShiftModifier ) )
{
KIO::MetaData metaData;
metaData.insert( "referrer", d->m_referrer );
KHTMLPopupGUIClient::saveURL( d->m_view, i18n( "Save As" ), cURL, metaData );
return false;
}
if (!checkLinkSecurity(cURL,
ki18n( "<qt>This untrusted page links to<br /><b>%1</b>.<br />Do you want to follow the link?</qt>" ),
i18n( "Follow" )))
return false;
browserArgs.frameName = target;
args.metaData().insert("main_frame_request",
parentPart() == 0 ? "TRUE":"FALSE");
args.metaData().insert("ssl_parent_ip", d->m_ssl_parent_ip);
args.metaData().insert("ssl_parent_cert", d->m_ssl_parent_cert);
args.metaData().insert("PropagateHttpHeader", "true");
args.metaData().insert("ssl_was_in_use", d->m_ssl_in_use ? "TRUE":"FALSE");
args.metaData().insert("ssl_activate_warnings", "TRUE");
if ( hasTarget && target != "_self" && target != "_top" && target != "_blank" && target != "_parent" )
{
// unknown frame names should open in a new window.
khtml::ChildFrame *frame = recursiveFrameRequest( this, cURL, args, browserArgs, false );
if ( frame )
{
args.metaData()["referrer"] = d->m_referrer;
requestObject( frame, cURL, args, browserArgs );
return true;
}
}
if (!d->m_referrer.isEmpty() && !args.metaData().contains("referrer"))
args.metaData()["referrer"] = d->m_referrer;
if ( button == Qt::NoButton && (state & Qt::ShiftModifier) && (state & Qt::ControlModifier) )
{
emit d->m_extension->createNewWindow( cURL, args, browserArgs );
return true;
}
if ( state & Qt::ShiftModifier)
{
KParts::WindowArgs winArgs;
winArgs.setLowerWindow(true);
emit d->m_extension->createNewWindow( cURL, args, browserArgs, winArgs );
return true;
}
//If we're asked to open up an anchor in the current URL, in current window,
//merely gotoanchor, and do not reload the new page. Note that this does
//not apply if the URL is the same page, but without a ref
if (cURL.hasRef() && (!hasTarget || target == "_self"))
{
if (d->isLocalAnchorJump(cURL))
{
d->executeAnchorJump(cURL, browserArgs.lockHistory() );
return false; // we jumped, but we didn't open a URL
}
}
if ( !d->m_bComplete && !hasTarget )
closeUrl();
view()->viewport()->unsetCursor();
emit d->m_extension->openUrlRequest( cURL, args, browserArgs );
return true;
}
void KHTMLPart::slotViewDocumentSource()
{
KUrl currentUrl(this->url());
bool isTempFile = false;
if (!(currentUrl.isLocalFile()) && KHTMLPageCache::self()->isComplete(d->m_cacheId))
{
KTemporaryFile sourceFile;
sourceFile.setSuffix(defaultExtension());
sourceFile.setAutoRemove(false);
if (sourceFile.open())
{
QDataStream stream ( &sourceFile );
KHTMLPageCache::self()->saveData(d->m_cacheId, &stream);
currentUrl = KUrl();
currentUrl.setPath(sourceFile.fileName());
isTempFile = true;
}
}
(void) KRun::runUrl( currentUrl, QLatin1String("text/plain"), view(), isTempFile );
}
void KHTMLPart::slotViewPageInfo()
{
Ui_KHTMLInfoDlg ui;
QDialog *dlg = new QDialog(0);
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->setObjectName("KHTML Page Info Dialog");
ui.setupUi(dlg);
ui._close->setGuiItem(KStandardGuiItem::close());
connect(ui._close, SIGNAL(clicked()), dlg, SLOT(accept()));
if (d->m_doc)
ui._title->setText(d->m_doc->title().string());
// If it's a frame, set the caption to "Frame Information"
if ( parentPart() && d->m_doc && d->m_doc->isHTMLDocument() ) {
dlg->setWindowTitle(i18n("Frame Information"));
}
QString editStr;
if (!d->m_pageServices.isEmpty())
editStr = i18n(" <a href=\"%1\">[Properties]</a>", d->m_pageServices);
QString squeezedURL = KStringHandler::csqueeze( url().prettyUrl(), 80 );
ui._url->setText("<a href=\"" + url().url() + "\">" + squeezedURL + "</a>" + editStr);
if (lastModified().isEmpty())
{
ui._lastModified->hide();
ui._lmLabel->hide();
}
else
ui._lastModified->setText(lastModified());
const QString& enc = encoding();
if (enc.isEmpty()) {
ui._eLabel->hide();
ui._encoding->hide();
} else {
ui._encoding->setText(enc);
}
if (!xmlDocImpl() || xmlDocImpl()->parseMode() == DOM::DocumentImpl::Unknown) {
ui._mode->hide();
ui._modeLabel->hide();
} else {
switch (xmlDocImpl()->parseMode()) {
case DOM::DocumentImpl::Compat:
ui._mode->setText(i18nc("HTML rendering mode (see http://en.wikipedia.org/wiki/Quirks_mode)", "Quirks"));
break;
case DOM::DocumentImpl::Transitional:
ui._mode->setText(i18nc("HTML rendering mode (see http://en.wikipedia.org/wiki/Quirks_mode)", "Almost standards"));
break;
case DOM::DocumentImpl::Strict:
default: // others handled above
ui._mode->setText(i18nc("HTML rendering mode (see http://en.wikipedia.org/wiki/Quirks_mode)", "Strict"));
break;
}
}
/* populate the list view now */
const QStringList headers = d->m_httpHeaders.split("\n");
QStringList::ConstIterator it = headers.begin();
const QStringList::ConstIterator itEnd = headers.end();
for (; it != itEnd; ++it) {
const QStringList header = (*it).split(QRegExp(":[ ]+"));
if (header.count() != 2)
continue;
QTreeWidgetItem *item = new QTreeWidgetItem(ui._headers);
item->setText(0, header[0]);
item->setText(1, header[1]);
}
dlg->show();
/* put no code here */
}
void KHTMLPart::slotViewFrameSource()
{
KParts::ReadOnlyPart *frame = currentFrame();
if ( !frame )
return;
KUrl url = frame->url();
bool isTempFile = false;
if (!(url.isLocalFile()) && frame->inherits("KHTMLPart"))
{
long cacheId = static_cast<KHTMLPart *>(frame)->d->m_cacheId;
if (KHTMLPageCache::self()->isComplete(cacheId))
{
KTemporaryFile sourceFile;
sourceFile.setSuffix(defaultExtension());
sourceFile.setAutoRemove(false);
if (sourceFile.open())
{
QDataStream stream ( &sourceFile );
KHTMLPageCache::self()->saveData(cacheId, &stream);
url = KUrl();
url.setPath(sourceFile.fileName());
isTempFile = true;
}
}
}
(void) KRun::runUrl( url, QLatin1String("text/plain"), view(), isTempFile );
}
KUrl KHTMLPart::backgroundURL() const
{
// ### what about XML documents? get from CSS?
if (!d->m_doc || !d->m_doc->isHTMLDocument())
return KUrl();
QString relURL = static_cast<HTMLDocumentImpl*>(d->m_doc)->body()->getAttribute( ATTR_BACKGROUND ).string();
return KUrl( url(), relURL );
}
void KHTMLPart::slotSaveBackground()
{
KIO::MetaData metaData;
metaData["referrer"] = d->m_referrer;
KHTMLPopupGUIClient::saveURL( d->m_view, i18n("Save Background Image As"), backgroundURL(), metaData );
}
void KHTMLPart::slotSaveDocument()
{
KUrl srcURL( url() );
if ( srcURL.fileName(KUrl::ObeyTrailingSlash).isEmpty() )
srcURL.setFileName( "index" + defaultExtension() );
KIO::MetaData metaData;
// Referre unknown?
KHTMLPopupGUIClient::saveURL( d->m_view, i18n( "Save As" ), srcURL, metaData, "text/html", d->m_cacheId );
}
void KHTMLPart::slotSecurity()
{
// kDebug( 6050 ) << "Meta Data:" << endl
// << d->m_ssl_peer_cert_subject
// << endl
// << d->m_ssl_peer_cert_issuer
// << endl
// << d->m_ssl_cipher
// << endl
// << d->m_ssl_cipher_desc
// << endl
// << d->m_ssl_cipher_version
// << endl
// << d->m_ssl_good_from
// << endl
// << d->m_ssl_good_until
// << endl
// << d->m_ssl_cert_state
// << endl;
//### reenable with new signature
#if 0
KSslInfoDialog *kid = new KSslInfoDialog(d->m_ssl_in_use, widget(), "kssl_info_dlg", true );
const QStringList sl = d->m_ssl_peer_chain.split('\n', QString::SkipEmptyParts);
QList<QSslCertificate> certChain;
bool certChainOk = d->m_ssl_in_use;
if (certChainOk) {
foreach (const QString &s, sl) {
certChain.append(QSslCertificate(s.toAscii())); //or is it toLocal8Bit or whatever?
if (certChain.last().isNull()) {
certChainOk = false;
break;
}
}
}
if (certChainOk) {
kid->setup(certChain,
d->m_ssl_peer_ip,
url().url(),
d->m_ssl_cipher,
d->m_ssl_cipher_desc,
d->m_ssl_cipher_version,
d->m_ssl_cipher_used_bits.toInt(),
d->m_ssl_cipher_bits.toInt(),
(KSSLCertificate::KSSLValidation) d->m_ssl_cert_state.toInt());
}
kid->exec();
//the dialog deletes itself on close
#endif
KSslInfoDialog *kid = new KSslInfoDialog(0);
//### This is boilerplate code and it's copied from SlaveInterface.
QStringList sl = d->m_ssl_peer_chain.split('\x01', QString::SkipEmptyParts);
QList<QSslCertificate> certChain;
bool decodedOk = true;
foreach (const QString &s, sl) {
certChain.append(QSslCertificate(s.toAscii())); //or is it toLocal8Bit or whatever?
if (certChain.last().isNull()) {
decodedOk = false;
break;
}
}
if (decodedOk || true /*H4X*/) {
kid->setSslInfo(certChain,
d->m_ssl_peer_ip,
url().host(),
d->m_ssl_protocol_version,
d->m_ssl_cipher,
d->m_ssl_cipher_used_bits.toInt(),
d->m_ssl_cipher_bits.toInt(),
KSslInfoDialog::errorsFromString(d->m_ssl_cert_errors));
kDebug(7024) << "Showing SSL Info dialog";
kid->exec();
kDebug(7024) << "SSL Info dialog closed";
} else {
KMessageBox::information(0, i18n("The peer SSL certificate chain "
"appears to be corrupt."),
i18n("SSL"));
}
}
void KHTMLPart::slotSaveFrame()
{
KParts::ReadOnlyPart *frame = currentFrame();
if ( !frame )
return;
KUrl srcURL( frame->url() );
if ( srcURL.fileName(KUrl::ObeyTrailingSlash).isEmpty() )
srcURL.setFileName( "index" + defaultExtension() );
KIO::MetaData metaData;
// Referrer unknown?
KHTMLPopupGUIClient::saveURL( d->m_view, i18n( "Save Frame As" ), srcURL, metaData, "text/html" );
}
void KHTMLPart::slotSetEncoding(const QString &enc)
{
d->m_autoDetectLanguage=KEncodingDetector::None;
setEncoding( enc, true);
}
void KHTMLPart::slotAutomaticDetectionLanguage(KEncodingDetector::AutoDetectScript scri)
{
d->m_autoDetectLanguage=scri;
setEncoding( QString(), false );
}
void KHTMLPart::slotUseStylesheet()
{
if (d->m_doc)
{
bool autoselect = (d->m_paUseStylesheet->currentItem() == 0);
d->m_sheetUsed = autoselect ? QString() : d->m_paUseStylesheet->currentText();
d->m_doc->updateStyleSelector();
}
}
void KHTMLPart::updateActions()
{
bool frames = false;
QList<khtml::ChildFrame*>::ConstIterator it = d->m_frames.constBegin();
const QList<khtml::ChildFrame*>::ConstIterator end = d->m_frames.constEnd();
for (; it != end; ++it )
if ( (*it)->m_type == khtml::ChildFrame::Frame )
{
frames = true;
break;
}
if (d->m_paViewFrame)
d->m_paViewFrame->setEnabled( frames );
if (d->m_paSaveFrame)
d->m_paSaveFrame->setEnabled( frames );
if ( frames )
d->m_paFind->setText( i18n( "&Find in Frame..." ) );
else
d->m_paFind->setText( i18n( "&Find..." ) );
KParts::Part *frame = 0;
if ( frames )
frame = currentFrame();
bool enableFindAndSelectAll = true;
if ( frame )
enableFindAndSelectAll = frame->inherits( "KHTMLPart" );
d->m_paFind->setEnabled( enableFindAndSelectAll );
d->m_paSelectAll->setEnabled( enableFindAndSelectAll );
bool enablePrintFrame = false;
if ( frame )
{
QObject *ext = KParts::BrowserExtension::childObject( frame );
if ( ext )
enablePrintFrame = ext->metaObject()->indexOfSlot( "print()" ) != -1;
}
d->m_paPrintFrame->setEnabled( enablePrintFrame );
QString bgURL;
// ### frames
if ( d->m_doc && d->m_doc->isHTMLDocument() && static_cast<HTMLDocumentImpl*>(d->m_doc)->body() && !d->m_bClearing )
bgURL = static_cast<HTMLDocumentImpl*>(d->m_doc)->body()->getAttribute( ATTR_BACKGROUND ).string();
if (d->m_paSaveBackground)
d->m_paSaveBackground->setEnabled( !bgURL.isEmpty() );
if ( d->m_paDebugScript )
d->m_paDebugScript->setEnabled( d->m_frame ? d->m_frame->m_jscript : 0L );
}
KParts::ScriptableExtension *KHTMLPart::scriptableExtension( const DOM::NodeImpl *frame) {
const ConstFrameIt end = d->m_objects.constEnd();
for(ConstFrameIt it = d->m_objects.constBegin(); it != end; ++it )
if ((*it)->m_partContainerElement.data() == frame)
return (*it)->m_scriptable.data();
return 0L;
}
void KHTMLPart::loadFrameElement( DOM::HTMLPartContainerElementImpl *frame, const QString &url,
const QString &frameName, const QStringList &params, bool isIFrame )
{
//kDebug( 6050 ) << this << " requestFrame( ..., " << url << ", " << frameName << " )";
khtml::ChildFrame* child;
FrameIt it = d->m_frames.find( frameName );
if ( it == d->m_frames.end() ) {
child = new khtml::ChildFrame;
//kDebug( 6050 ) << "inserting new frame into frame map " << frameName;
child->m_name = frameName;
d->m_frames.insert( d->m_frames.end(), child );
} else {
child = *it;
}
child->m_type = isIFrame ? khtml::ChildFrame::IFrame : khtml::ChildFrame::Frame;
child->m_partContainerElement = frame;
child->m_params = params;
// If we do not have a part, make sure we create one.
if (!child->m_part) {
QStringList dummy; // the list of servicetypes handled by the part is now unused.
QString khtml = QString::fromLatin1("khtml");
KParts::ReadOnlyPart* part = createPart(d->m_view->viewport(), this,
QString::fromLatin1("text/html"),
khtml, dummy, QStringList());
// We navigate it to about:blank to setup an empty one, but we do it
// before hooking up the signals and extensions, so that any sync emit
// of completed by the kid doesn't cause us to be marked as completed.
// (async ones are discovered by the presence of the KHTMLRun)
// ### load event on the kid?
navigateLocalProtocol(child, part, KUrl("about:blank"));
connectToChildPart(child, part, "text/html" /* mimetype of the part, not what's being loaded */);
}
KUrl u = url.isEmpty() ? KUrl() : completeURL( url );
// Since we don't specify args here a KHTMLRun will be used to determine the
// mimetype, which will then be passed down at the bottom of processObjectRequest
// inside URLArgs to the part. In our particular case, this means that we can
// use that inside KHTMLPart::openUrl to route things appropriately.
child->m_bCompleted = false;
if (!requestObject( child, u ) && !child->m_run) {
child->m_bCompleted = true;
}
}
QString KHTMLPart::requestFrameName()
{
return QString::fromLatin1("<!--frame %1-->").arg(d->m_frameNameId++);
}
bool KHTMLPart::loadObjectElement( DOM::HTMLPartContainerElementImpl *frame, const QString &url,
const QString &serviceType, const QStringList &params )
{
//kDebug( 6031 ) << this << "frame=" << frame;
khtml::ChildFrame *child = new khtml::ChildFrame;
FrameIt it = d->m_objects.insert( d->m_objects.end(), child );
(*it)->m_partContainerElement = frame;
(*it)->m_type = khtml::ChildFrame::Object;
(*it)->m_params = params;
KParts::OpenUrlArguments args;
args.setMimeType(serviceType);
if (!requestObject( *it, completeURL( url ), args ) && !(*it)->m_run) {
(*it)->m_bCompleted = true;
return false;
}
return true;
}
bool KHTMLPart::requestObject( khtml::ChildFrame *child, const KUrl &url, const KParts::OpenUrlArguments &_args,
const KParts::BrowserArguments& browserArgs )
{
// we always permit javascript: URLs here since they're basically just
// empty pages (and checkLinkSecurity/KAuthorized doesn't know what to do with them)
if (!d->isJavaScriptURL(url.url()) && !checkLinkSecurity(url))
{
kDebug(6031) << this << "checkLinkSecurity refused";
return false;
}
if (d->m_bClearing)
{
return false;
}
if ( child->m_bPreloaded )
{
if ( child->m_partContainerElement && child->m_part )
child->m_partContainerElement.data()->setWidget( child->m_part.data()->widget() );
child->m_bPreloaded = false;
return true;
}
//kDebug(6031) << "child=" << child << "child->m_part=" << child->m_part;
KParts::OpenUrlArguments args( _args );
if ( child->m_run ) {
kDebug(6031) << "navigating ChildFrame while mimetype resolution was in progress...";
child->m_run.data()->abort();
}
// ### Dubious -- the whole dir/ vs. img thing
if ( child->m_part && !args.reload() && child->m_part.data()->url().equals( url,
KUrl::CompareWithoutTrailingSlash | KUrl::CompareWithoutFragment | KUrl::AllowEmptyPath ) )
args.setMimeType(child->m_serviceType);
child->m_browserArgs = browserArgs;
child->m_args = args;
// reload/soft-reload arguments are always inherited from parent
child->m_args.setReload( arguments().reload() );
child->m_browserArgs.softReload = d->m_extension->browserArguments().softReload;
child->m_serviceName.clear();
if (!d->m_referrer.isEmpty() && !child->m_args.metaData().contains( "referrer" ))
child->m_args.metaData()["referrer"] = d->m_referrer;
child->m_args.metaData().insert("PropagateHttpHeader", "true");
child->m_args.metaData().insert("ssl_parent_ip", d->m_ssl_parent_ip);
child->m_args.metaData().insert("ssl_parent_cert", d->m_ssl_parent_cert);
child->m_args.metaData().insert("main_frame_request",
parentPart() == 0 ? "TRUE":"FALSE");
child->m_args.metaData().insert("ssl_was_in_use",
d->m_ssl_in_use ? "TRUE":"FALSE");
child->m_args.metaData().insert("ssl_activate_warnings", "TRUE");
child->m_args.metaData().insert("cross-domain", toplevelURL().url());
// We know the frame will be text/html if the HTML says <frame src=""> or <frame src="about:blank">,
// no need to KHTMLRun to figure out the mimetype"
// ### What if we're inside an XML document?
- if ((url.isEmpty() || url.url() == "about:blank" || url.protocol() == "javascript") && args.mimeType().isEmpty())
+ if ((url.isEmpty() || url.url() == "about:blank" || url.scheme() == "javascript") && args.mimeType().isEmpty())
args.setMimeType(QLatin1String("text/html"));
if ( args.mimeType().isEmpty() ) {
kDebug(6031) << "Running new KHTMLRun for" << this << "and child=" << child;
child->m_run = new KHTMLRun( this, child, url, child->m_args, child->m_browserArgs, true );
d->m_bComplete = false; // ensures we stop it in checkCompleted...
return false;
} else {
return processObjectRequest( child, url, args.mimeType() );
}
}
void KHTMLPart::childLoadFailure( khtml::ChildFrame *child )
{
child->m_bCompleted = true;
if ( child->m_partContainerElement )
child->m_partContainerElement.data()->partLoadingErrorNotify();
checkCompleted();
}
bool KHTMLPart::processObjectRequest( khtml::ChildFrame *child, const KUrl &_url, const QString &mimetype )
{
kDebug( 6031 ) << "trying to create part for" << mimetype << _url;
// IMPORTANT: create a copy of the url here, because it is just a reference, which was likely to be given
// by an emitting frame part (emit openUrlRequest( blahurl, ... ) . A few lines below we delete the part
// though -> the reference becomes invalid -> crash is likely
KUrl url( _url );
// khtmlrun called us with empty url + mimetype to indicate a loading error,
// we obviosuly failed; but we can return true here since we don't want it
// doing anything more, while childLoadFailure is enough to notify our kid.
if ( d->m_onlyLocalReferences || ( url.isEmpty() && mimetype.isEmpty() ) ) {
childLoadFailure(child);
return true;
}
// we also want to ignore any spurious requests due to closing when parser is being cleared. These should be
// ignored entirely --- the tail end of ::clear will clean things up.
if (d->m_bClearing)
return false;
if (child->m_bNotify) {
child->m_bNotify = false;
if ( !child->m_browserArgs.lockHistory() )
emit d->m_extension->openUrlNotify();
}
// Now, depending on mimetype and current state of the world, we may have
// to create a new part or ask the user to save things, etc.
//
// We need a new part if there isn't one at all (doh) or the one that's there
// is not for the mimetype we're loading.
//
// For these new types, we may have to ask the user to save it or not
// (we don't if it's navigating the same type).
// Further, we will want to ask if content-disposition suggests we ask for
// saving, even if we're re-navigating.
if ( !child->m_part || child->m_serviceType != mimetype ||
(child->m_run && child->m_run.data()->serverSuggestsSave())) {
// We often get here if we didn't know the mimetype in advance, and had to rely
// on KRun to figure it out. In this case, we let the element check if it wants to
// handle this mimetype itself, for e.g. objects containing images.
if ( child->m_partContainerElement &&
child->m_partContainerElement.data()->mimetypeHandledInternally(mimetype) ) {
child->m_bCompleted = true;
checkCompleted();
return true;
}
// Before attempting to load a part, check if the user wants that.
// Many don't like getting ZIP files embedded.
// However we don't want to ask for flash and other plugin things.
//
// Note: this is fine for frames, since we will merely effectively ignore
// the navigation if this happens
if ( child->m_type != khtml::ChildFrame::Object && child->m_type != khtml::ChildFrame::IFrame ) {
QString suggestedFileName;
int disposition = 0;
if ( KHTMLRun* run = child->m_run.data() ) {
suggestedFileName = run->suggestedFileName();
disposition = run->serverSuggestsSave() ?
KParts::BrowserRun::AttachmentDisposition :
KParts::BrowserRun::InlineDisposition;
}
KParts::BrowserOpenOrSaveQuestion dlg( widget(), url, mimetype );
dlg.setSuggestedFileName( suggestedFileName );
const KParts::BrowserOpenOrSaveQuestion::Result res = dlg.askEmbedOrSave( disposition );
switch( res ) {
case KParts::BrowserOpenOrSaveQuestion::Save:
KHTMLPopupGUIClient::saveURL( widget(), i18n( "Save As" ), url, child->m_args.metaData(), QString(), 0, suggestedFileName );
// fall-through
case KParts::BrowserOpenOrSaveQuestion::Cancel:
child->m_bCompleted = true;
checkCompleted();
return true; // done
default: // Embed
break;
}
}
// Now, for frames and iframes, we always create a KHTMLPart anyway,
// doing it in advance when registering the frame. So we want the
// actual creation only for objects here.
if ( child->m_type == khtml::ChildFrame::Object ) {
KMimeType::Ptr mime = KMimeType::mimeType(mimetype);
if (mime) {
// Even for objects, however, we want to force a KHTMLPart for
// html & xml, even if the normally preferred part is another one,
// so that we can script the target natively via contentDocument method.
if (mime->is("text/html")
|| mime->is("application/xml")) { // this includes xhtml and svg
child->m_serviceName = "khtml";
}
}
QStringList dummy; // the list of servicetypes handled by the part is now unused.
KParts::ReadOnlyPart *part = createPart( d->m_view->viewport(), this, mimetype, child->m_serviceName, dummy, child->m_params );
if ( !part ) {
childLoadFailure(child);
return false;
}
connectToChildPart( child, part, mimetype );
}
}
checkEmitLoadEvent();
// Some JS code in the load event may have destroyed the part
// In that case, abort
if ( !child->m_part )
return false;
if ( child->m_bPreloaded ) {
if ( child->m_partContainerElement && child->m_part )
child->m_partContainerElement.data()->setWidget( child->m_part.data()->widget() );
child->m_bPreloaded = false;
return true;
}
// reload/soft-reload arguments are always inherited from parent
child->m_args.setReload( arguments().reload() );
child->m_browserArgs.softReload = d->m_extension->browserArguments().softReload;
// make sure the part has a way to find out about the mimetype.
// we actually set it in child->m_args in requestObject already,
// but it's useless if we had to use a KHTMLRun instance, as the
// point the run object is to find out exactly the mimetype.
child->m_args.setMimeType(mimetype);
child->m_part.data()->setArguments( child->m_args );
// if not a frame set child as completed
// ### dubious.
child->m_bCompleted = child->m_type == khtml::ChildFrame::Object;
if ( child->m_extension )
child->m_extension.data()->setBrowserArguments( child->m_browserArgs );
return navigateChild( child, url );
}
bool KHTMLPart::navigateLocalProtocol( khtml::ChildFrame* /*child*/, KParts::ReadOnlyPart *inPart,
const KUrl& url )
{
if (!qobject_cast<KHTMLPart*>(inPart))
return false;
KHTMLPart* p = static_cast<KHTMLPart*>(static_cast<KParts::ReadOnlyPart *>(inPart));
p->begin();
// We may have to re-propagate the domain here if we go here due to navigation
d->propagateInitialDomainAndBaseTo(p);
// Support for javascript: sources
if (d->isJavaScriptURL(url.url())) {
// See if we want to replace content with javascript: output..
QVariant res = p->executeScript( DOM::Node(),
d->codeForJavaScriptURL(url.url()));
if (res.type() == QVariant::String && p->d->m_redirectURL.isEmpty()) {
p->begin();
p->setAlwaysHonourDoctype(); // Disable public API compat; it messes with doctype
// We recreated the document, so propagate domain again.
d->propagateInitialDomainAndBaseTo(p);
p->write( res.toString() );
p->end();
}
} else {
p->setUrl(url);
// we need a body element. testcase: <iframe id="a"></iframe><script>alert(a.document.body);</script>
p->write("<HTML><TITLE></TITLE><BODY></BODY></HTML>");
}
p->end();
// we don't need to worry about child completion explicitly for KHTMLPart...
// or do we?
return true;
}
bool KHTMLPart::navigateChild( khtml::ChildFrame *child, const KUrl& url )
{
- if (url.protocol() == "javascript" || url.url() == "about:blank") {
+ if (url.scheme() == "javascript" || url.url() == "about:blank") {
return navigateLocalProtocol(child, child->m_part.data(), url);
} else if ( !url.isEmpty() ) {
kDebug( 6031 ) << "opening" << url << "in frame" << child->m_part;
bool b = child->m_part.data()->openUrl( url );
if (child->m_bCompleted)
checkCompleted();
return b;
} else {
// empty URL -> no need to navigate
child->m_bCompleted = true;
checkCompleted();
return true;
}
}
void KHTMLPart::connectToChildPart( khtml::ChildFrame *child, KParts::ReadOnlyPart *part,
const QString& mimetype)
{
kDebug(6031) << "we:" << this << "kid:" << child << part << mimetype;
part->setObjectName( child->m_name );
// Cleanup any previous part for this childframe and its connections
if ( KParts::ReadOnlyPart* p = child->m_part.data() ) {
if (!qobject_cast<KHTMLPart*>(p) && child->m_jscript)
child->m_jscript->clear();
partManager()->removePart( p );
delete p;
child->m_scriptable.clear();
}
child->m_part = part;
child->m_serviceType = mimetype;
if ( child->m_partContainerElement && part->widget() )
child->m_partContainerElement.data()->setWidget( part->widget() );
if ( child->m_type != khtml::ChildFrame::Object )
partManager()->addPart( part, false );
// else
// kDebug(6031) << "AH! NO FRAME!!!!!";
if (qobject_cast<KHTMLPart*>(part)) {
static_cast<KHTMLPart*>(part)->d->m_frame = child;
} else if (child->m_partContainerElement) {
// See if this can be scripted..
KParts::ScriptableExtension* scriptExt = KParts::ScriptableExtension::childObject(part);
if (!scriptExt) {
// Try to fall back to LiveConnectExtension compat
KParts::LiveConnectExtension* lc = KParts::LiveConnectExtension::childObject(part);
if (lc)
scriptExt = KParts::ScriptableExtension::adapterFromLiveConnect(part, lc);
}
if (scriptExt)
scriptExt->setHost(d->m_scriptableExtension);
child->m_scriptable = scriptExt;
}
KParts::StatusBarExtension *sb = KParts::StatusBarExtension::childObject(part);
if (sb)
sb->setStatusBar( d->m_statusBarExtension->statusBar() );
connect( part, SIGNAL( started( KIO::Job *) ),
this, SLOT( slotChildStarted( KIO::Job *) ) );
connect( part, SIGNAL( completed() ),
this, SLOT( slotChildCompleted() ) );
connect( part, SIGNAL( completed(bool) ),
this, SLOT( slotChildCompleted(bool) ) );
connect( part, SIGNAL( setStatusBarText( const QString & ) ),
this, SIGNAL( setStatusBarText( const QString & ) ) );
if ( part->inherits( "KHTMLPart" ) )
{
connect( this, SIGNAL( completed() ),
part, SLOT( slotParentCompleted() ) );
connect( this, SIGNAL( completed(bool) ),
part, SLOT( slotParentCompleted() ) );
// As soon as the child's document is created, we need to set its domain
// (but we do so only once, so it can't be simply done in the child)
connect( part, SIGNAL( docCreated() ),
this, SLOT( slotChildDocCreated() ) );
}
child->m_extension = KParts::BrowserExtension::childObject( part );
if ( KParts::BrowserExtension* kidBrowserExt = child->m_extension.data() )
{
connect( kidBrowserExt, SIGNAL( openUrlNotify() ),
d->m_extension, SIGNAL( openUrlNotify() ) );
connect( kidBrowserExt, SIGNAL( openUrlRequestDelayed( const KUrl &, const KParts::OpenUrlArguments&, const KParts::BrowserArguments & ) ),
this, SLOT( slotChildURLRequest( const KUrl &, const KParts::OpenUrlArguments&, const KParts::BrowserArguments & ) ) );
connect( kidBrowserExt, SIGNAL( createNewWindow( const KUrl &, const KParts::OpenUrlArguments&, const KParts::BrowserArguments &, const KParts::WindowArgs &, KParts::ReadOnlyPart ** ) ),
d->m_extension, SIGNAL( createNewWindow( const KUrl &, const KParts::OpenUrlArguments&, const KParts::BrowserArguments & , const KParts::WindowArgs &, KParts::ReadOnlyPart **) ) );
connect( kidBrowserExt, SIGNAL(popupMenu(QPoint,KFileItemList,KParts::OpenUrlArguments,KParts::BrowserArguments,KParts::BrowserExtension::PopupFlags,KParts::BrowserExtension::ActionGroupMap)),
d->m_extension, SIGNAL(popupMenu(QPoint,KFileItemList,KParts::OpenUrlArguments,KParts::BrowserArguments,KParts::BrowserExtension::PopupFlags,KParts::BrowserExtension::ActionGroupMap)) );
connect( kidBrowserExt, SIGNAL(popupMenu(QPoint,KUrl,mode_t,KParts::OpenUrlArguments,KParts::BrowserArguments,KParts::BrowserExtension::PopupFlags,KParts::BrowserExtension::ActionGroupMap)),
d->m_extension, SIGNAL(popupMenu(QPoint,KUrl,mode_t,KParts::OpenUrlArguments,KParts::BrowserArguments,KParts::BrowserExtension::PopupFlags,KParts::BrowserExtension::ActionGroupMap)) );
connect( kidBrowserExt, SIGNAL( infoMessage( const QString & ) ),
d->m_extension, SIGNAL( infoMessage( const QString & ) ) );
connect( kidBrowserExt, SIGNAL( requestFocus( KParts::ReadOnlyPart * ) ),
this, SLOT( slotRequestFocus( KParts::ReadOnlyPart * ) ) );
kidBrowserExt->setBrowserInterface( d->m_extension->browserInterface() );
}
}
KParts::ReadOnlyPart *KHTMLPart::createPart( QWidget *parentWidget,
QObject *parent, const QString &mimetype,
QString &serviceName, QStringList &serviceTypes,
const QStringList &params )
{
QString constr;
if ( !serviceName.isEmpty() )
constr.append( QString::fromLatin1( "DesktopEntryName == '%1'" ).arg( serviceName ) );
KService::List offers = KMimeTypeTrader::self()->query( mimetype, "KParts/ReadOnlyPart", constr );
if ( offers.isEmpty() ) {
int pos = mimetype.indexOf( "-plugin" );
if (pos < 0)
return 0L;
QString stripped_mime = mimetype.left( pos );
offers = KMimeTypeTrader::self()->query( stripped_mime, "KParts/ReadOnlyPart", constr );
if ( offers.isEmpty() )
return 0L;
}
KService::List::ConstIterator it = offers.constBegin();
const KService::List::ConstIterator itEnd = offers.constEnd();
for ( ; it != itEnd; ++it )
{
KService::Ptr service = (*it);
KPluginLoader loader( *service, KHTMLGlobal::componentData() );
KPluginFactory* const factory = loader.factory();
if ( factory ) {
// Turn params into a QVariantList as expected by KPluginFactory
QVariantList variantlist;
Q_FOREACH(const QString& str, params)
variantlist << QVariant(str);
if ( service->serviceTypes().contains( "Browser/View" ) )
variantlist << QString("Browser/View");
KParts::ReadOnlyPart* part = factory->create<KParts::ReadOnlyPart>(parentWidget, parent, QString(), variantlist);
if ( part ) {
serviceTypes = service->serviceTypes();
serviceName = service->name();
return part;
}
} else {
// TODO KMessageBox::error and i18n, like in KonqFactory::createView?
kWarning() << QString("There was an error loading the module %1.\nThe diagnostics is:\n%2")
.arg(service->name()).arg(loader.errorString());
}
}
return 0;
}
KParts::PartManager *KHTMLPart::partManager()
{
if ( !d->m_manager && d->m_view )
{
d->m_manager = new KParts::PartManager( d->m_view->topLevelWidget(), this );
d->m_manager->setObjectName( "khtml part manager" );
d->m_manager->setAllowNestedParts( true );
connect( d->m_manager, SIGNAL( activePartChanged( KParts::Part * ) ),
this, SLOT( slotActiveFrameChanged( KParts::Part * ) ) );
connect( d->m_manager, SIGNAL( partRemoved( KParts::Part * ) ),
this, SLOT( slotPartRemoved( KParts::Part * ) ) );
}
return d->m_manager;
}
void KHTMLPart::submitFormAgain()
{
disconnect(this, SIGNAL(completed()), this, SLOT(submitFormAgain()));
if( d->m_doc && !d->m_doc->parsing() && d->m_submitForm)
KHTMLPart::submitForm( d->m_submitForm->submitAction, d->m_submitForm->submitUrl, d->m_submitForm->submitFormData, d->m_submitForm->target, d->m_submitForm->submitContentType, d->m_submitForm->submitBoundary );
delete d->m_submitForm;
d->m_submitForm = 0;
}
void KHTMLPart::submitFormProxy( const char *action, const QString &url, const QByteArray &formData, const QString &_target, const QString& contentType, const QString& boundary )
{
submitForm(action, url, formData, _target, contentType, boundary);
}
void KHTMLPart::submitForm( const char *action, const QString &url, const QByteArray &formData, const QString &_target, const QString& contentType, const QString& boundary )
{
kDebug(6000) << this << "target=" << _target << "url=" << url;
if (d->m_formNotification == KHTMLPart::Only) {
emit formSubmitNotification(action, url, formData, _target, contentType, boundary);
return;
} else if (d->m_formNotification == KHTMLPart::Before) {
emit formSubmitNotification(action, url, formData, _target, contentType, boundary);
}
KUrl u = completeURL( url );
if ( !u.isValid() )
{
// ### ERROR HANDLING!
return;
}
// Form security checks
//
/*
* If these form security checks are still in this place in a month or two
* I'm going to simply delete them.
*/
/* This is separate for a reason. It has to be _before_ all script, etc,
* AND I don't want to break anything that uses checkLinkSecurity() in
* other places.
*/
if (!d->m_submitForm) {
- if (u.protocol() != "https" && u.protocol() != "mailto") {
+ if (u.scheme() != "https" && u.scheme() != "mailto") {
if (d->m_ssl_in_use) { // Going from SSL -> nonSSL
int rc = KMessageBox::warningContinueCancel(NULL, i18n("Warning: This is a secure form but it is attempting to send your data back unencrypted."
"\nA third party may be able to intercept and view this information."
"\nAre you sure you wish to continue?"),
i18n("Network Transmission"),KGuiItem(i18n("&Send Unencrypted")));
if (rc == KMessageBox::Cancel)
return;
} else { // Going from nonSSL -> nonSSL
KSSLSettings kss(true);
if (kss.warnOnUnencrypted()) {
int rc = KMessageBox::warningContinueCancel(NULL,
i18n("Warning: Your data is about to be transmitted across the network unencrypted."
"\nAre you sure you wish to continue?"),
i18n("Network Transmission"),
KGuiItem(i18n("&Send Unencrypted")),
KStandardGuiItem::cancel(),
"WarnOnUnencryptedForm");
// Move this setting into KSSL instead
QString grpNotifMsgs = QLatin1String("Notification Messages");
KConfigGroup cg( KGlobal::config(), grpNotifMsgs );
if (!cg.readEntry("WarnOnUnencryptedForm", true)) {
cg.deleteEntry("WarnOnUnencryptedForm");
cg.sync();
kss.setWarnOnUnencrypted(false);
kss.save();
}
if (rc == KMessageBox::Cancel)
return;
}
}
}
- if (u.protocol() == "mailto") {
+ if (u.scheme() == "mailto") {
int rc = KMessageBox::warningContinueCancel(NULL,
i18n("This site is attempting to submit form data via email.\n"
"Do you want to continue?"),
i18n("Network Transmission"),
KGuiItem(i18n("&Send Email")),
KStandardGuiItem::cancel(),
"WarnTriedEmailSubmit");
if (rc == KMessageBox::Cancel) {
return;
}
}
}
// End form security checks
//
QString urlstring = u.url();
if ( d->isJavaScriptURL(urlstring) ) {
crossFrameExecuteScript( _target, d->codeForJavaScriptURL(urlstring) );
return;
}
if (!checkLinkSecurity(u,
ki18n( "<qt>The form will be submitted to <br /><b>%1</b><br />on your local filesystem.<br />Do you want to submit the form?</qt>" ),
i18n( "Submit" )))
return;
// OK. We're actually going to submit stuff. Clear any redirections,
// we should win over them
d->clearRedirection();
KParts::OpenUrlArguments args;
if (!d->m_referrer.isEmpty())
args.metaData()["referrer"] = d->m_referrer;
args.metaData().insert("PropagateHttpHeader", "true");
args.metaData().insert("ssl_parent_ip", d->m_ssl_parent_ip);
args.metaData().insert("ssl_parent_cert", d->m_ssl_parent_cert);
args.metaData().insert("main_frame_request",
parentPart() == 0 ? "TRUE":"FALSE");
args.metaData().insert("ssl_was_in_use", d->m_ssl_in_use ? "TRUE":"FALSE");
args.metaData().insert("ssl_activate_warnings", "TRUE");
//WABA: When we post a form we should treat it as the main url
//the request should never be considered cross-domain
//args.metaData().insert("cross-domain", toplevelURL().url());
KParts::BrowserArguments browserArgs;
browserArgs.frameName = _target.isEmpty() ? d->m_doc->baseTarget() : _target ;
// Handle mailto: forms
- if (u.protocol() == "mailto") {
+ if (u.scheme() == "mailto") {
// 1) Check for attach= and strip it
QString q = u.query().mid(1);
QStringList nvps = q.split("&");
bool triedToAttach = false;
QStringList::Iterator nvp = nvps.begin();
const QStringList::Iterator nvpEnd = nvps.end();
// cannot be a for loop as if something is removed we don't want to do ++nvp, as
// remove returns an iterator pointing to the next item
while (nvp != nvpEnd) {
const QStringList pair = (*nvp).split("=");
if (pair.count() >= 2) {
if (pair.first().toLower() == "attach") {
nvp = nvps.erase(nvp);
triedToAttach = true;
} else {
++nvp;
}
} else {
++nvp;
}
}
if (triedToAttach)
KMessageBox::information(NULL, i18n("This site attempted to attach a file from your computer in the form submission. The attachment was removed for your protection."), i18n("KDE"), "WarnTriedAttach");
// 2) Append body=
QString bodyEnc;
if (contentType.toLower() == "multipart/form-data") {
// FIXME: is this correct? I suspect not
bodyEnc = QLatin1String( KUrl::toPercentEncoding(QString::fromLatin1(formData.data(),
formData.size())));
} else if (contentType.toLower() == "text/plain") {
// Convention seems to be to decode, and s/&/\n/
QString tmpbody = QString::fromLatin1(formData.data(),
formData.size());
tmpbody.replace(QRegExp("[&]"), "\n");
tmpbody.replace(QRegExp("[+]"), " ");
tmpbody = KUrl::fromPercentEncoding(tmpbody.toLatin1()); // Decode the rest of it
bodyEnc = QLatin1String( KUrl::toPercentEncoding(tmpbody) ); // Recode for the URL
} else {
bodyEnc = QLatin1String( KUrl::toPercentEncoding(QString::fromLatin1(formData.data(),
formData.size())) );
}
nvps.append(QString("body=%1").arg(bodyEnc));
q = nvps.join("&");
u.setQuery(q);
}
if ( strcmp( action, "get" ) == 0 ) {
- if (u.protocol() != "mailto")
+ if (u.scheme() != "mailto")
u.setQuery( QString::fromLatin1( formData.data(), formData.size() ) );
browserArgs.setDoPost( false );
}
else {
browserArgs.postData = formData;
browserArgs.setDoPost( true );
// construct some user headers if necessary
if (contentType.isNull() || contentType == "application/x-www-form-urlencoded")
browserArgs.setContentType( "Content-Type: application/x-www-form-urlencoded" );
else // contentType must be "multipart/form-data"
browserArgs.setContentType( "Content-Type: " + contentType + "; boundary=" + boundary );
}
if ( d->m_doc->parsing() || d->m_runningScripts > 0 ) {
if( d->m_submitForm ) {
kDebug(6000) << "ABORTING!";
return;
}
d->m_submitForm = new KHTMLPartPrivate::SubmitForm;
d->m_submitForm->submitAction = action;
d->m_submitForm->submitUrl = url;
d->m_submitForm->submitFormData = formData;
d->m_submitForm->target = _target;
d->m_submitForm->submitContentType = contentType;
d->m_submitForm->submitBoundary = boundary;
connect(this, SIGNAL(completed()), this, SLOT(submitFormAgain()));
}
else
{
emit d->m_extension->openUrlRequest( u, args, browserArgs );
}
}
void KHTMLPart::popupMenu( const QString &linkUrl )
{
KUrl popupURL;
KUrl linkKUrl;
KParts::OpenUrlArguments args;
KParts::BrowserArguments browserArgs;
QString referrer;
KParts::BrowserExtension::PopupFlags itemflags=KParts::BrowserExtension::ShowBookmark | KParts::BrowserExtension::ShowReload;
if ( linkUrl.isEmpty() ) { // click on background
KHTMLPart* khtmlPart = this;
while ( khtmlPart->parentPart() )
{
khtmlPart=khtmlPart->parentPart();
}
popupURL = khtmlPart->url();
referrer = khtmlPart->pageReferrer();
if (hasSelection())
itemflags = KParts::BrowserExtension::ShowTextSelectionItems;
else
itemflags |= KParts::BrowserExtension::ShowNavigationItems;
} else { // click on link
popupURL = completeURL( linkUrl );
linkKUrl = popupURL;
referrer = this->referrer();
itemflags |= KParts::BrowserExtension::IsLink;
if (!(d->m_strSelectedURLTarget).isEmpty() &&
(d->m_strSelectedURLTarget.toLower() != "_top") &&
(d->m_strSelectedURLTarget.toLower() != "_self") &&
(d->m_strSelectedURLTarget.toLower() != "_parent")) {
if (d->m_strSelectedURLTarget.toLower() == "_blank")
browserArgs.setForcesNewWindow(true);
else {
KHTMLPart *p = this;
while (p->parentPart())
p = p->parentPart();
if (!p->frameExists(d->m_strSelectedURLTarget))
browserArgs.setForcesNewWindow(true);
}
}
}
// Danger, Will Robinson. The Popup might stay around for a much
// longer time than KHTMLPart. Deal with it.
KHTMLPopupGUIClient* client = new KHTMLPopupGUIClient( this, linkKUrl );
QPointer<QObject> guard( client );
QString mimetype = QLatin1String( "text/html" );
args.metaData()["referrer"] = referrer;
if (!linkUrl.isEmpty()) // over a link
{
if (popupURL.isLocalFile()) // safe to do this
{
mimetype = KMimeType::findByUrl(popupURL,0,true,false)->name();
}
else // look at "extension" of link
{
const QString fname(popupURL.fileName(KUrl::ObeyTrailingSlash));
if (!fname.isEmpty() && !popupURL.hasRef() && popupURL.query().isEmpty())
{
KMimeType::Ptr pmt = KMimeType::findByPath(fname,0,true);
// Further check for mime types guessed from the extension which,
// on a web page, are more likely to be a script delivering content
// of undecidable type. If the mime type from the extension is one
// of these, don't use it. Retain the original type 'text/html'.
if (pmt->name() != KMimeType::defaultMimeType() &&
!pmt->is("application/x-perl") &&
!pmt->is("application/x-perl-module") &&
!pmt->is("application/x-php") &&
!pmt->is("application/x-python-bytecode") &&
!pmt->is("application/x-python") &&
!pmt->is("application/x-shellscript"))
mimetype = pmt->name();
}
}
}
args.setMimeType(mimetype);
emit d->m_extension->popupMenu( QCursor::pos(), popupURL, S_IFREG /*always a file*/,
args, browserArgs, itemflags,
client->actionGroups() );
if ( !guard.isNull() ) {
delete client;
emit popupMenu(linkUrl, QCursor::pos());
d->m_strSelectedURL.clear();
d->m_strSelectedURLTarget.clear();
}
}
void KHTMLPart::slotParentCompleted()
{
//kDebug(6050) << this;
if ( !d->m_redirectURL.isEmpty() && !d->m_redirectionTimer.isActive() )
{
//kDebug(6050) << this << ": starting timer for child redirection -> " << d->m_redirectURL;
d->m_redirectionTimer.setSingleShot( true );
d->m_redirectionTimer.start( qMax(0, 1000 * d->m_delayRedirect) );
}
}
void KHTMLPart::slotChildStarted( KIO::Job *job )
{
khtml::ChildFrame *child = frame( sender() );
assert( child );
child->m_bCompleted = false;
if ( d->m_bComplete )
{
#if 0
// WABA: Looks like this belongs somewhere else
if ( !parentPart() ) // "toplevel" html document? if yes, then notify the hosting browser about the document (url) changes
{
emit d->m_extension->openURLNotify();
}
#endif
d->m_bComplete = false;
emit started( job );
}
}
void KHTMLPart::slotChildCompleted()
{
slotChildCompleted( false );
}
void KHTMLPart::slotChildCompleted( bool pendingAction )
{
khtml::ChildFrame *child = frame( sender() );
if ( child ) {
kDebug(6031) << this << "child=" << child << "m_partContainerElement=" << child->m_partContainerElement;
child->m_bCompleted = true;
child->m_bPendingRedirection = pendingAction;
child->m_args = KParts::OpenUrlArguments();
child->m_browserArgs = KParts::BrowserArguments();
// dispatch load event. We don't do that for KHTMLPart's since their internal
// load will be forwarded inside NodeImpl::dispatchWindowEvent
if (!qobject_cast<KHTMLPart*>(child->m_part))
QTimer::singleShot(0, child->m_partContainerElement.data(), SLOT(slotEmitLoadEvent()));
}
checkCompleted();
}
void KHTMLPart::slotChildDocCreated()
{
// Set domain to the frameset's domain
// This must only be done when loading the frameset initially (#22039),
// not when following a link in a frame (#44162).
if (KHTMLPart* htmlFrame = qobject_cast<KHTMLPart*>(sender()))
d->propagateInitialDomainAndBaseTo(htmlFrame);
// So it only happens once
disconnect( sender(), SIGNAL( docCreated() ), this, SLOT( slotChildDocCreated() ) );
}
void KHTMLPartPrivate::propagateInitialDomainAndBaseTo(KHTMLPart* kid)
{
// This method is used to propagate our domain and base information for
// child frames, to provide them for about: or JavaScript: URLs
if ( m_doc && kid->d->m_doc ) {
DocumentImpl* kidDoc = kid->d->m_doc;
if ( kidDoc->origin()->isEmpty() ) {
kidDoc->setOrigin ( m_doc->origin() );
kidDoc->setBaseURL( m_doc->baseURL() );
}
}
}
void KHTMLPart::slotChildURLRequest( const KUrl &url, const KParts::OpenUrlArguments& args, const KParts::BrowserArguments &browserArgs )
{
khtml::ChildFrame *child = frame( sender()->parent() );
KHTMLPart *callingHtmlPart = const_cast<KHTMLPart *>(dynamic_cast<const KHTMLPart *>(sender()->parent()));
// TODO: handle child target correctly! currently the script are always executed for the parent
QString urlStr = url.url();
if ( d->isJavaScriptURL(urlStr) ) {
executeScript( DOM::Node(), d->codeForJavaScriptURL(urlStr) );
return;
}
QString frameName = browserArgs.frameName.toLower();
if ( !frameName.isEmpty() ) {
if ( frameName == QLatin1String( "_top" ) )
{
emit d->m_extension->openUrlRequest( url, args, browserArgs );
return;
}
else if ( frameName == QLatin1String( "_blank" ) )
{
emit d->m_extension->createNewWindow( url, args, browserArgs );
return;
}
else if ( frameName == QLatin1String( "_parent" ) )
{
KParts::BrowserArguments newBrowserArgs( browserArgs );
newBrowserArgs.frameName.clear();
emit d->m_extension->openUrlRequest( url, args, newBrowserArgs );
return;
}
else if ( frameName != QLatin1String( "_self" ) )
{
khtml::ChildFrame *_frame = recursiveFrameRequest( callingHtmlPart, url, args, browserArgs );
if ( !_frame )
{
emit d->m_extension->openUrlRequest( url, args, browserArgs );
return;
}
child = _frame;
}
}
if ( child && child->m_type != khtml::ChildFrame::Object ) {
// Inform someone that we are about to show something else.
child->m_bNotify = true;
requestObject( child, url, args, browserArgs );
} else if ( frameName== "_self" ) // this is for embedded objects (via <object>) which want to replace the current document
{
KParts::BrowserArguments newBrowserArgs( browserArgs );
newBrowserArgs.frameName.clear();
emit d->m_extension->openUrlRequest( url, args, newBrowserArgs );
}
}
void KHTMLPart::slotRequestFocus( KParts::ReadOnlyPart * )
{
emit d->m_extension->requestFocus(this);
}
khtml::ChildFrame *KHTMLPart::frame( const QObject *obj )
{
assert( obj->inherits( "KParts::ReadOnlyPart" ) );
const KParts::ReadOnlyPart* const part = static_cast<const KParts::ReadOnlyPart *>( obj );
FrameIt it = d->m_frames.begin();
const FrameIt end = d->m_frames.end();
for (; it != end; ++it ) {
if ((*it)->m_part.data() == part )
return *it;
}
FrameIt oi = d->m_objects.begin();
const FrameIt oiEnd = d->m_objects.end();
for (; oi != oiEnd; ++oi ) {
if ((*oi)->m_part.data() == part)
return *oi;
}
return 0L;
}
//#define DEBUG_FINDFRAME
bool KHTMLPart::checkFrameAccess(KHTMLPart *callingHtmlPart)
{
if (callingHtmlPart == this)
return true; // trivial
if (!xmlDocImpl()) {
#ifdef DEBUG_FINDFRAME
kDebug(6050) << "Empty part" << this << "URL = " << url();
#endif
return false; // we are empty?
}
// now compare the domains
if (callingHtmlPart && callingHtmlPart->xmlDocImpl() && xmlDocImpl()) {
khtml::SecurityOrigin* actDomain = callingHtmlPart->xmlDocImpl()->origin();
khtml::SecurityOrigin* destDomain = xmlDocImpl()->origin();
if (actDomain->canAccess(destDomain))
return true;
}
#ifdef DEBUG_FINDFRAME
else
{
kDebug(6050) << "Unknown part/domain" << callingHtmlPart << "tries to access part" << this;
}
#endif
return false;
}
KHTMLPart *
KHTMLPart::findFrameParent( KParts::ReadOnlyPart *callingPart, const QString &f, khtml::ChildFrame **childFrame )
{
return d->findFrameParent(callingPart, f, childFrame, false);
}
KHTMLPart* KHTMLPartPrivate::findFrameParent(KParts::ReadOnlyPart* callingPart,
const QString& f, khtml::ChildFrame **childFrame, bool checkForNavigation)
{
#ifdef DEBUG_FINDFRAME
kDebug(6050) << q << "URL =" << q->url() << "name =" << q->objectName() << "findFrameParent(" << f << ")";
#endif
// Check access
KHTMLPart* const callingHtmlPart = dynamic_cast<KHTMLPart *>(callingPart);
if (!checkForNavigation && !q->checkFrameAccess(callingHtmlPart))
return 0;
if (!childFrame && !q->parentPart() && (q->objectName() == f)) {
if (!checkForNavigation || callingHtmlPart->d->canNavigate(q))
return q;
}
FrameIt it = m_frames.find( f );
const FrameIt end = m_frames.end();
if ( it != end )
{
#ifdef DEBUG_FINDFRAME
kDebug(6050) << "FOUND!";
#endif
if (!checkForNavigation || callingHtmlPart->d->canNavigate((*it)->m_part.data())) {
if (childFrame)
*childFrame = *it;
return q;
}
}
it = m_frames.begin();
for (; it != end; ++it )
{
if ( KHTMLPart* p = qobject_cast<KHTMLPart*>((*it)->m_part.data()) )
{
KHTMLPart* const frameParent = p->d->findFrameParent(callingPart, f, childFrame, checkForNavigation);
if (frameParent)
return frameParent;
}
}
return 0;
}
KHTMLPart* KHTMLPartPrivate::top()
{
KHTMLPart* t = q;
while (t->parentPart())
t = t->parentPart();
return t;
}
bool KHTMLPartPrivate::canNavigate(KParts::ReadOnlyPart* bCand)
{
KHTMLPart* b = qobject_cast<KHTMLPart*>(bCand);
assert(b);
// HTML5 gives conditions for this (a) being able to navigate b
// 1) Same domain
if (q->checkFrameAccess(b))
return true;
// 2) A is nested, with B its top
if (q->parentPart() && top() == b)
return true;
// 3) B is 'auxilary' -- window.open with opener,
// and A can navigate B's opener
if (b->opener() && canNavigate(b->opener()))
return true;
// 4) B is not top-level, but an ancestor of it has same origin as A
for (KHTMLPart* anc = b->parentPart(); anc; anc = anc->parentPart()) {
if (anc->checkFrameAccess(q))
return true;
}
return false;
}
KHTMLPart *KHTMLPart::findFrame( const QString &f )
{
khtml::ChildFrame *childFrame;
KHTMLPart *parentFrame = findFrameParent(this, f, &childFrame);
if (parentFrame)
return qobject_cast<KHTMLPart*>(childFrame->m_part.data());
return 0;
}
KParts::ReadOnlyPart *KHTMLPart::findFramePart(const QString &f)
{
khtml::ChildFrame *childFrame;
return findFrameParent(this, f, &childFrame) ? childFrame->m_part.data() : 0L;
}
KParts::ReadOnlyPart *KHTMLPart::currentFrame() const
{
KParts::ReadOnlyPart* part = (KParts::ReadOnlyPart*)(this);
// Find active part in our frame manager, in case we are a frameset
// and keep doing that (in case of nested framesets).
// Just realized we could also do this recursively, calling part->currentFrame()...
while ( part && part->inherits("KHTMLPart") &&
static_cast<KHTMLPart *>(part)->d->m_frames.count() > 0 ) {
KHTMLPart* frameset = static_cast<KHTMLPart *>(part);
part = static_cast<KParts::ReadOnlyPart *>(frameset->partManager()->activePart());
if ( !part ) return frameset;
}
return part;
}
bool KHTMLPart::frameExists( const QString &frameName )
{
FrameIt it = d->m_frames.find( frameName );
if ( it == d->m_frames.end() )
return false;
// WABA: We only return true if the child actually has a frame
// set. Otherwise we might find our preloaded-selve.
// This happens when we restore the frameset.
return (!(*it)->m_partContainerElement.isNull());
}
void KHTMLPartPrivate::renameFrameForContainer(DOM::HTMLPartContainerElementImpl* cont,
const QString& newName)
{
for (int i = 0; i < m_frames.size(); ++i) {
khtml::ChildFrame* f = m_frames[i];
if (f->m_partContainerElement.data() == cont)
f->m_name = newName;
}
}
KJSProxy *KHTMLPart::framejScript(KParts::ReadOnlyPart *framePart)
{
KHTMLPart* const kp = qobject_cast<KHTMLPart*>(framePart);
if (kp)
return kp->jScript();
FrameIt it = d->m_frames.begin();
const FrameIt itEnd = d->m_frames.end();
for (; it != itEnd; ++it) {
khtml::ChildFrame* frame = *it;
if (framePart == frame->m_part.data()) {
if (!frame->m_jscript)
frame->m_jscript = new KJSProxy(frame);
return frame->m_jscript;
}
}
return 0L;
}
KHTMLPart *KHTMLPart::parentPart()
{
return qobject_cast<KHTMLPart*>( parent() );
}
khtml::ChildFrame *KHTMLPart::recursiveFrameRequest( KHTMLPart *callingHtmlPart, const KUrl &url,
const KParts::OpenUrlArguments &args,
const KParts::BrowserArguments &browserArgs, bool callParent )
{
#ifdef DEBUG_FINDFRAME
kDebug( 6050 ) << this << "frame = " << args.frameName << "url = " << url;
#endif
khtml::ChildFrame *childFrame;
KHTMLPart *childPart = findFrameParent(callingHtmlPart, browserArgs.frameName, &childFrame);
if (childPart)
{
if (childPart == this)
return childFrame;
childPart->requestObject( childFrame, url, args, browserArgs );
return 0;
}
if ( parentPart() && callParent )
{
khtml::ChildFrame *res = parentPart()->recursiveFrameRequest( callingHtmlPart, url, args, browserArgs, callParent );
if ( res )
parentPart()->requestObject( res, url, args, browserArgs );
}
return 0L;
}
#ifdef DEBUG_SAVESTATE
static int s_saveStateIndentLevel = 0;
#endif
void KHTMLPart::saveState( QDataStream &stream )
{
#ifdef DEBUG_SAVESTATE
QString indent= QString().leftJustified( s_saveStateIndentLevel * 4, ' ' );
const int indentLevel = s_saveStateIndentLevel++;
kDebug( 6050 ) << indent << "saveState this=" << this << " '" << objectName() << "' saving URL " << url().url();
#endif
stream << url() << (qint32)d->m_view->contentsX() << (qint32)d->m_view->contentsY()
<< (qint32) d->m_view->contentsWidth() << (qint32) d->m_view->contentsHeight() << (qint32) d->m_view->marginWidth() << (qint32) d->m_view->marginHeight();
// save link cursor position
int focusNodeNumber;
if (!d->m_focusNodeRestored)
focusNodeNumber = d->m_focusNodeNumber;
else if (d->m_doc && d->m_doc->focusNode())
focusNodeNumber = d->m_doc->nodeAbsIndex(d->m_doc->focusNode());
else
focusNodeNumber = -1;
stream << focusNodeNumber;
// Save the doc's cache id.
stream << d->m_cacheId;
// Save the state of the document (Most notably the state of any forms)
QStringList docState;
if (d->m_doc)
{
docState = d->m_doc->docState();
}
stream << d->m_encoding << d->m_sheetUsed << docState;
stream << d->m_zoomFactor;
stream << d->m_fontScaleFactor;
stream << d->m_httpHeaders;
stream << d->m_pageServices;
stream << d->m_pageReferrer;
// Save ssl data
stream << d->m_ssl_in_use
<< d->m_ssl_peer_chain
<< d->m_ssl_peer_ip
<< d->m_ssl_cipher
<< d->m_ssl_protocol_version
<< d->m_ssl_cipher_used_bits
<< d->m_ssl_cipher_bits
<< d->m_ssl_cert_errors
<< d->m_ssl_parent_ip
<< d->m_ssl_parent_cert;
QStringList frameNameLst, frameServiceTypeLst, frameServiceNameLst;
KUrl::List frameURLLst;
QList<QByteArray> frameStateBufferLst;
QList<int> frameTypeLst;
ConstFrameIt it = d->m_frames.constBegin();
const ConstFrameIt end = d->m_frames.constEnd();
for (; it != end; ++it )
{
if ( !(*it)->m_part )
continue;
frameNameLst << (*it)->m_name;
frameServiceTypeLst << (*it)->m_serviceType;
frameServiceNameLst << (*it)->m_serviceName;
frameURLLst << (*it)->m_part.data()->url();
QByteArray state;
QDataStream frameStream( &state, QIODevice::WriteOnly );
if ( (*it)->m_extension )
(*it)->m_extension.data()->saveState( frameStream );
frameStateBufferLst << state;
frameTypeLst << int( (*it)->m_type );
}
// Save frame data
stream << (quint32) frameNameLst.count();
stream << frameNameLst << frameServiceTypeLst << frameServiceNameLst << frameURLLst << frameStateBufferLst << frameTypeLst;
#ifdef DEBUG_SAVESTATE
s_saveStateIndentLevel = indentLevel;
#endif
}
void KHTMLPart::restoreState( QDataStream &stream )
{
KUrl u;
qint32 xOffset, yOffset, wContents, hContents, mWidth, mHeight;
quint32 frameCount;
QStringList frameNames, frameServiceTypes, docState, frameServiceNames;
QList<int> frameTypes;
KUrl::List frameURLs;
QList<QByteArray> frameStateBuffers;
QList<int> fSizes;
QString encoding, sheetUsed;
long old_cacheId = d->m_cacheId;
stream >> u >> xOffset >> yOffset >> wContents >> hContents >> mWidth >> mHeight;
d->m_view->setMarginWidth( mWidth );
d->m_view->setMarginHeight( mHeight );
// restore link cursor position
// nth node is active. value is set in checkCompleted()
stream >> d->m_focusNodeNumber;
d->m_focusNodeRestored = false;
stream >> d->m_cacheId;
stream >> encoding >> sheetUsed >> docState;
d->m_encoding = encoding;
d->m_sheetUsed = sheetUsed;
int zoomFactor;
stream >> zoomFactor;
setZoomFactor(zoomFactor);
int fontScaleFactor;
stream >> fontScaleFactor;
setFontScaleFactor(fontScaleFactor);
stream >> d->m_httpHeaders;
stream >> d->m_pageServices;
stream >> d->m_pageReferrer;
// Restore ssl data
stream >> d->m_ssl_in_use
>> d->m_ssl_peer_chain
>> d->m_ssl_peer_ip
>> d->m_ssl_cipher
>> d->m_ssl_protocol_version
>> d->m_ssl_cipher_used_bits
>> d->m_ssl_cipher_bits
>> d->m_ssl_cert_errors
>> d->m_ssl_parent_ip
>> d->m_ssl_parent_cert;
setPageSecurity( d->m_ssl_in_use ? Encrypted : NotCrypted );
stream >> frameCount >> frameNames >> frameServiceTypes >> frameServiceNames
>> frameURLs >> frameStateBuffers >> frameTypes;
d->m_bComplete = false;
d->m_bLoadEventEmitted = false;
// kDebug( 6050 ) << "docState.count() = " << docState.count();
// kDebug( 6050 ) << "m_url " << url().url() << " <-> " << u.url();
// kDebug( 6050 ) << "m_frames.count() " << d->m_frames.count() << " <-> " << frameCount;
if (d->m_cacheId == old_cacheId && signed(frameCount) == d->m_frames.count())
{
// Partial restore
d->m_redirectionTimer.stop();
FrameIt fIt = d->m_frames.begin();
const FrameIt fEnd = d->m_frames.end();
for (; fIt != fEnd; ++fIt )
(*fIt)->m_bCompleted = false;
fIt = d->m_frames.begin();
QStringList::ConstIterator fNameIt = frameNames.constBegin();
QStringList::ConstIterator fServiceTypeIt = frameServiceTypes.constBegin();
QStringList::ConstIterator fServiceNameIt = frameServiceNames.constBegin();
KUrl::List::ConstIterator fURLIt = frameURLs.constBegin();
QList<QByteArray>::ConstIterator fBufferIt = frameStateBuffers.constBegin();
QList<int>::ConstIterator fFrameTypeIt = frameTypes.constBegin();
for (; fIt != fEnd; ++fIt, ++fNameIt, ++fServiceTypeIt, ++fServiceNameIt, ++fURLIt, ++fBufferIt, ++fFrameTypeIt )
{
khtml::ChildFrame* const child = *fIt;
// kDebug( 6050 ) << *fNameIt << " ---- " << *fServiceTypeIt;
if ( child->m_name != *fNameIt || child->m_serviceType != *fServiceTypeIt )
{
child->m_bPreloaded = true;
child->m_name = *fNameIt;
child->m_serviceName = *fServiceNameIt;
child->m_type = static_cast<khtml::ChildFrame::Type>(*fFrameTypeIt);
processObjectRequest( child, *fURLIt, *fServiceTypeIt );
}
if ( child->m_part )
{
child->m_bCompleted = false;
if ( child->m_extension && !(*fBufferIt).isEmpty() )
{
QDataStream frameStream( *fBufferIt );
child->m_extension.data()->restoreState( frameStream );
}
else
child->m_part.data()->openUrl( *fURLIt );
}
}
KParts::OpenUrlArguments args( arguments() );
args.setXOffset(xOffset);
args.setYOffset(yOffset);
setArguments(args);
KParts::BrowserArguments browserArgs( d->m_extension->browserArguments() );
browserArgs.docState = docState;
d->m_extension->setBrowserArguments(browserArgs);
d->m_view->resizeContents( wContents, hContents );
d->m_view->setContentsPos( xOffset, yOffset );
setUrl(u);
}
else
{
// Full restore.
closeUrl();
// We must force a clear because we want to be sure to delete all
// frames.
d->m_bCleared = false;
clear();
d->m_encoding = encoding;
d->m_sheetUsed = sheetUsed;
QStringList::ConstIterator fNameIt = frameNames.constBegin();
const QStringList::ConstIterator fNameEnd = frameNames.constEnd();
QStringList::ConstIterator fServiceTypeIt = frameServiceTypes.constBegin();
QStringList::ConstIterator fServiceNameIt = frameServiceNames.constBegin();
KUrl::List::ConstIterator fURLIt = frameURLs.constBegin();
QList<QByteArray>::ConstIterator fBufferIt = frameStateBuffers.constBegin();
QList<int>::ConstIterator fFrameTypeIt = frameTypes.constBegin();
for (; fNameIt != fNameEnd; ++fNameIt, ++fServiceTypeIt, ++fServiceNameIt, ++fURLIt, ++fBufferIt, ++fFrameTypeIt )
{
khtml::ChildFrame* const newChild = new khtml::ChildFrame;
newChild->m_bPreloaded = true;
newChild->m_name = *fNameIt;
newChild->m_serviceName = *fServiceNameIt;
newChild->m_type = static_cast<khtml::ChildFrame::Type>(*fFrameTypeIt);
// kDebug( 6050 ) << *fNameIt << " ---- " << *fServiceTypeIt;
const FrameIt childFrame = d->m_frames.insert( d->m_frames.end(), newChild );
processObjectRequest( *childFrame, *fURLIt, *fServiceTypeIt );
(*childFrame)->m_bPreloaded = true;
if ( (*childFrame)->m_part )
{
if ( (*childFrame)->m_extension && !(*fBufferIt).isEmpty() )
{
QDataStream frameStream( *fBufferIt );
(*childFrame)->m_extension.data()->restoreState( frameStream );
}
else
(*childFrame)->m_part.data()->openUrl( *fURLIt );
}
}
KParts::OpenUrlArguments args( arguments() );
args.setXOffset(xOffset);
args.setYOffset(yOffset);
setArguments(args);
KParts::BrowserArguments browserArgs( d->m_extension->browserArguments() );
browserArgs.docState = docState;
d->m_extension->setBrowserArguments(browserArgs);
if (!KHTMLPageCache::self()->isComplete(d->m_cacheId))
{
d->m_restored = true;
openUrl( u );
d->m_restored = false;
}
else
{
restoreURL( u );
}
}
}
void KHTMLPart::show()
{
if ( widget() )
widget()->show();
}
void KHTMLPart::hide()
{
if ( widget() )
widget()->hide();
}
DOM::Node KHTMLPart::nodeUnderMouse() const
{
return d->m_view->nodeUnderMouse();
}
DOM::Node KHTMLPart::nonSharedNodeUnderMouse() const
{
return d->m_view->nonSharedNodeUnderMouse();
}
void KHTMLPart::emitSelectionChanged()
{
// Don't emit signals about our selection if this is a frameset;
// the active frame has the selection (#187403)
if (!d->m_activeFrame)
{
emit d->m_extension->enableAction( "copy", hasSelection() );
emit d->m_extension->selectionInfo( selectedText() );
emit selectionChanged();
}
}
int KHTMLPart::zoomFactor() const
{
return d->m_zoomFactor;
}
// ### make the list configurable ?
static const int zoomSizes[] = { 20, 40, 60, 80, 90, 95, 100, 105, 110, 120, 140, 160, 180, 200, 250, 300 };
static const int zoomSizeCount = (sizeof(zoomSizes) / sizeof(int));
static const int minZoom = 20;
static const int maxZoom = 300;
// My idea of useful stepping ;-) (LS)
extern const int KDE_NO_EXPORT fastZoomSizes[] = { 20, 50, 75, 90, 100, 120, 150, 200, 300 };
extern const int KDE_NO_EXPORT fastZoomSizeCount = sizeof fastZoomSizes / sizeof fastZoomSizes[0];
void KHTMLPart::slotIncZoom()
{
zoomIn(zoomSizes, zoomSizeCount);
}
void KHTMLPart::slotDecZoom()
{
zoomOut(zoomSizes, zoomSizeCount);
}
void KHTMLPart::slotIncZoomFast()
{
zoomIn(fastZoomSizes, fastZoomSizeCount);
}
void KHTMLPart::slotDecZoomFast()
{
zoomOut(fastZoomSizes, fastZoomSizeCount);
}
void KHTMLPart::zoomIn(const int stepping[], int count)
{
int zoomFactor = d->m_zoomFactor;
if (zoomFactor < maxZoom) {
// find the entry nearest to the given zoomsizes
for (int i = 0; i < count; ++i)
if (stepping[i] > zoomFactor) {
zoomFactor = stepping[i];
break;
}
setZoomFactor(zoomFactor);
}
}
void KHTMLPart::zoomOut(const int stepping[], int count)
{
int zoomFactor = d->m_zoomFactor;
if (zoomFactor > minZoom) {
// find the entry nearest to the given zoomsizes
for (int i = count-1; i >= 0; --i)
if (stepping[i] < zoomFactor) {
zoomFactor = stepping[i];
break;
}
setZoomFactor(zoomFactor);
}
}
void KHTMLPart::setZoomFactor (int percent)
{
// ### zooming under 100% is majorly botched,
// so disable that for now.
if (percent < 100) percent = 100;
// ### if (percent < minZoom) percent = minZoom;
if (percent > maxZoom) percent = maxZoom;
if (d->m_zoomFactor == percent) return;
d->m_zoomFactor = percent;
updateZoomFactor();
}
void KHTMLPart::updateZoomFactor ()
{
if(d->m_view) {
QApplication::setOverrideCursor( Qt::WaitCursor );
d->m_view->setZoomLevel( d->m_zoomFactor );
QApplication::restoreOverrideCursor();
}
ConstFrameIt it = d->m_frames.constBegin();
const ConstFrameIt end = d->m_frames.constEnd();
for (; it != end; ++it ) {
if ( KHTMLPart* p = qobject_cast<KHTMLPart*>((*it)->m_part.data()) )
p->setZoomFactor(d->m_zoomFactor);
}
if ( d->m_guiProfile == BrowserViewGUI ) {
d->m_paDecZoomFactor->setEnabled( d->m_zoomFactor > minZoom );
d->m_paIncZoomFactor->setEnabled( d->m_zoomFactor < maxZoom );
}
}
void KHTMLPart::slotIncFontSize()
{
incFontSize(zoomSizes, zoomSizeCount);
}
void KHTMLPart::slotDecFontSize()
{
decFontSize(zoomSizes, zoomSizeCount);
}
void KHTMLPart::slotIncFontSizeFast()
{
incFontSize(fastZoomSizes, fastZoomSizeCount);
}
void KHTMLPart::slotDecFontSizeFast()
{
decFontSize(fastZoomSizes, fastZoomSizeCount);
}
void KHTMLPart::incFontSize(const int stepping[], int count)
{
int zoomFactor = d->m_fontScaleFactor;
if (zoomFactor < maxZoom) {
// find the entry nearest to the given zoomsizes
for (int i = 0; i < count; ++i)
if (stepping[i] > zoomFactor) {
zoomFactor = stepping[i];
break;
}
setFontScaleFactor(zoomFactor);
}
}
void KHTMLPart::decFontSize(const int stepping[], int count)
{
int zoomFactor = d->m_fontScaleFactor;
if (zoomFactor > minZoom) {
// find the entry nearest to the given zoomsizes
for (int i = count-1; i >= 0; --i)
if (stepping[i] < zoomFactor) {
zoomFactor = stepping[i];
break;
}
setFontScaleFactor(zoomFactor);
}
}
void KHTMLPart::setFontScaleFactor(int percent)
{
if (percent < minZoom) percent = minZoom;
if (percent > maxZoom) percent = maxZoom;
if (d->m_fontScaleFactor == percent) return;
d->m_fontScaleFactor = percent;
if (d->m_view && d->m_doc) {
QApplication::setOverrideCursor( Qt::WaitCursor );
if (d->m_doc->styleSelector())
d->m_doc->styleSelector()->computeFontSizes(d->m_doc->logicalDpiY(), d->m_fontScaleFactor);
d->m_doc->recalcStyle( NodeImpl::Force );
QApplication::restoreOverrideCursor();
}
ConstFrameIt it = d->m_frames.constBegin();
const ConstFrameIt end = d->m_frames.constEnd();
for (; it != end; ++it ) {
if ( KHTMLPart* p = qobject_cast<KHTMLPart*>((*it)->m_part.data()) )
p->setFontScaleFactor(d->m_fontScaleFactor);
}
}
int KHTMLPart::fontScaleFactor() const
{
return d->m_fontScaleFactor;
}
void KHTMLPart::slotZoomView( int delta )
{
if ( delta < 0 )
slotIncZoom();
else
slotDecZoom();
}
void KHTMLPart::setStatusBarText( const QString& text, StatusBarPriority p)
{
if (!d->m_statusMessagesEnabled)
return;
d->m_statusBarText[p] = text;
// shift handling ?
QString tobe = d->m_statusBarText[BarHoverText];
if (tobe.isEmpty())
tobe = d->m_statusBarText[BarOverrideText];
if (tobe.isEmpty()) {
tobe = d->m_statusBarText[BarDefaultText];
if (!tobe.isEmpty() && d->m_jobspeed)
tobe += " ";
if (d->m_jobspeed)
tobe += i18n( "(%1/s)" , KIO::convertSize( d->m_jobspeed ) );
}
tobe = "<qt>"+tobe;
emit ReadOnlyPart::setStatusBarText(tobe);
}
void KHTMLPart::setJSStatusBarText( const QString &text )
{
setStatusBarText(text, BarOverrideText);
}
void KHTMLPart::setJSDefaultStatusBarText( const QString &text )
{
setStatusBarText(text, BarDefaultText);
}
QString KHTMLPart::jsStatusBarText() const
{
return d->m_statusBarText[BarOverrideText];
}
QString KHTMLPart::jsDefaultStatusBarText() const
{
return d->m_statusBarText[BarDefaultText];
}
QString KHTMLPart::referrer() const
{
return d->m_referrer;
}
QString KHTMLPart::pageReferrer() const
{
KUrl referrerURL = KUrl( d->m_pageReferrer );
if (referrerURL.isValid())
{
- QString protocol = referrerURL.protocol();
+ QString protocol = referrerURL.scheme();
if ((protocol == "http") ||
- ((protocol == "https") && (url().protocol() == "https")))
+ ((protocol == "https") && (url().scheme() == "https")))
{
referrerURL.setRef(QString());
referrerURL.setUser(QString());
referrerURL.setPass(QString());
return referrerURL.url();
}
}
return QString();
}
QString KHTMLPart::lastModified() const
{
if ( d->m_lastModified.isEmpty() && url().isLocalFile() ) {
// Local file: set last-modified from the file's mtime.
// Done on demand to save time when this isn't needed - but can lead
// to slightly wrong results if updating the file on disk w/o reloading.
QDateTime lastModif = QFileInfo( url().toLocalFile() ).lastModified();
d->m_lastModified = lastModif.toString( Qt::LocalDate );
}
//kDebug(6050) << d->m_lastModified;
return d->m_lastModified;
}
void KHTMLPart::slotLoadImages()
{
if (d->m_doc )
d->m_doc->docLoader()->setAutoloadImages( !d->m_doc->docLoader()->autoloadImages() );
ConstFrameIt it = d->m_frames.constBegin();
const ConstFrameIt end = d->m_frames.constEnd();
for (; it != end; ++it ) {
if ( KHTMLPart* p = qobject_cast<KHTMLPart*>((*it)->m_part.data()) )
p->slotLoadImages();
}
}
void KHTMLPart::reparseConfiguration()
{
KHTMLSettings *settings = KHTMLGlobal::defaultHTMLSettings();
settings->init();
setAutoloadImages( settings->autoLoadImages() );
if (d->m_doc)
d->m_doc->docLoader()->setShowAnimations( settings->showAnimations() );
d->m_bOpenMiddleClick = settings->isOpenMiddleClickEnabled();
d->m_bJScriptEnabled = settings->isJavaScriptEnabled(url().host());
setDebugScript( settings->isJavaScriptDebugEnabled() );
d->m_bJavaEnabled = settings->isJavaEnabled(url().host());
d->m_bPluginsEnabled = settings->isPluginsEnabled(url().host());
d->m_metaRefreshEnabled = settings->isAutoDelayedActionsEnabled ();
delete d->m_settings;
d->m_settings = new KHTMLSettings(*KHTMLGlobal::defaultHTMLSettings());
QApplication::setOverrideCursor( Qt::WaitCursor );
khtml::CSSStyleSelector::reparseConfiguration();
if(d->m_doc) d->m_doc->updateStyleSelector();
QApplication::restoreOverrideCursor();
if (d->m_view) {
KHTMLSettings::KSmoothScrollingMode ssm = d->m_settings->smoothScrolling();
if (ssm == KHTMLSettings::KSmoothScrollingDisabled)
d->m_view->setSmoothScrollingModeDefault(KHTMLView::SSMDisabled);
else if (ssm == KHTMLSettings::KSmoothScrollingWhenEfficient)
d->m_view->setSmoothScrollingModeDefault(KHTMLView::SSMWhenEfficient);
else
d->m_view->setSmoothScrollingModeDefault(KHTMLView::SSMEnabled);
}
if (KHTMLGlobal::defaultHTMLSettings()->isAdFilterEnabled())
runAdFilter();
}
QStringList KHTMLPart::frameNames() const
{
QStringList res;
ConstFrameIt it = d->m_frames.constBegin();
const ConstFrameIt end = d->m_frames.constEnd();
for (; it != end; ++it )
if (!(*it)->m_bPreloaded && (*it)->m_part)
res += (*it)->m_name;
return res;
}
QList<KParts::ReadOnlyPart*> KHTMLPart::frames() const
{
QList<KParts::ReadOnlyPart*> res;
ConstFrameIt it = d->m_frames.constBegin();
const ConstFrameIt end = d->m_frames.constEnd();
for (; it != end; ++it )
if (!(*it)->m_bPreloaded && (*it)->m_part) // ### TODO: make sure that we always create an empty
// KHTMLPart for frames so this never happens.
res.append( (*it)->m_part.data() );
return res;
}
bool KHTMLPart::openUrlInFrame( const KUrl &url, const KParts::OpenUrlArguments& args, const KParts::BrowserArguments &browserArgs)
{
kDebug( 6031 ) << this << url;
FrameIt it = d->m_frames.find( browserArgs.frameName );
if ( it == d->m_frames.end() )
return false;
// Inform someone that we are about to show something else.
if ( !browserArgs.lockHistory() )
emit d->m_extension->openUrlNotify();
requestObject( *it, url, args, browserArgs );
return true;
}
void KHTMLPart::setDNDEnabled( bool b )
{
d->m_bDnd = b;
}
bool KHTMLPart::dndEnabled() const
{
return d->m_bDnd;
}
void KHTMLPart::customEvent( QEvent *event )
{
if ( khtml::MousePressEvent::test( event ) )
{
khtmlMousePressEvent( static_cast<khtml::MousePressEvent *>( event ) );
return;
}
if ( khtml::MouseDoubleClickEvent::test( event ) )
{
khtmlMouseDoubleClickEvent( static_cast<khtml::MouseDoubleClickEvent *>( event ) );
return;
}
if ( khtml::MouseMoveEvent::test( event ) )
{
khtmlMouseMoveEvent( static_cast<khtml::MouseMoveEvent *>( event ) );
return;
}
if ( khtml::MouseReleaseEvent::test( event ) )
{
khtmlMouseReleaseEvent( static_cast<khtml::MouseReleaseEvent *>( event ) );
return;
}
if ( khtml::DrawContentsEvent::test( event ) )
{
khtmlDrawContentsEvent( static_cast<khtml::DrawContentsEvent *>( event ) );
return;
}
KParts::ReadOnlyPart::customEvent( event );
}
bool KHTMLPart::isPointInsideSelection(int x, int y)
{
// Treat a collapsed selection like no selection.
if (d->editor_context.m_selection.state() == Selection::CARET)
return false;
if (!xmlDocImpl()->renderer())
return false;
khtml::RenderObject::NodeInfo nodeInfo(true, true);
xmlDocImpl()->renderer()->layer()->nodeAtPoint(nodeInfo, x, y);
NodeImpl *innerNode = nodeInfo.innerNode();
if (!innerNode || !innerNode->renderer())
return false;
return innerNode->isPointInsideSelection(x, y, d->editor_context.m_selection);
}
/** returns the position of the first inline text box of the line at
* coordinate y in renderNode
*
* This is a helper function for line-by-line text selection.
*/
static bool firstRunAt(khtml::RenderObject *renderNode, int y, NodeImpl *&startNode, long &startOffset)
{
for (khtml::RenderObject *n = renderNode; n; n = n->nextSibling()) {
if (n->isText()) {
khtml::RenderText* const textRenderer = static_cast<khtml::RenderText *>(n);
for (khtml::InlineTextBox* box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) {
if (box->m_y == y && textRenderer->element()) {
startNode = textRenderer->element();
startOffset = box->m_start;
return true;
}
}
}
if (firstRunAt(n->firstChild(), y, startNode, startOffset)) {
return true;
}
}
return false;
}
/** returns the position of the last inline text box of the line at
* coordinate y in renderNode
*
* This is a helper function for line-by-line text selection.
*/
static bool lastRunAt(khtml::RenderObject *renderNode, int y, NodeImpl *&endNode, long &endOffset)
{
khtml::RenderObject *n = renderNode;
if (!n) {
return false;
}
khtml::RenderObject *next;
while ((next = n->nextSibling())) {
n = next;
}
while (1) {
if (lastRunAt(n->firstChild(), y, endNode, endOffset)) {
return true;
}
if (n->isText()) {
khtml::RenderText* const textRenderer = static_cast<khtml::RenderText *>(n);
for (khtml::InlineTextBox* box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) {
if (box->m_y == y && textRenderer->element()) {
endNode = textRenderer->element();
endOffset = box->m_start + box->m_len;
return true;
}
}
}
if (n == renderNode) {
return false;
}
n = n->previousSibling();
}
}
void KHTMLPart::handleMousePressEventDoubleClick(khtml::MouseDoubleClickEvent *event)
{
QMouseEvent *mouse = event->qmouseEvent();
DOM::Node innerNode = event->innerNode();
Selection selection;
if (mouse->button() == Qt::LeftButton && !innerNode.isNull() && innerNode.handle()->renderer() &&
innerNode.handle()->renderer()->shouldSelect()) {
Position pos(innerNode.handle()->positionForCoordinates(event->x(), event->y()).position());
if (pos.node() && (pos.node()->nodeType() == Node::TEXT_NODE || pos.node()->nodeType() == Node::CDATA_SECTION_NODE)) {
selection.moveTo(pos);
selection.expandUsingGranularity(Selection::WORD);
}
}
if (selection.state() != Selection::CARET) {
d->editor_context.beginSelectingText(Selection::WORD);
}
setCaret(selection);
startAutoScroll();
}
void KHTMLPart::handleMousePressEventTripleClick(khtml::MouseDoubleClickEvent *event)
{
QMouseEvent *mouse = event->qmouseEvent();
DOM::Node innerNode = event->innerNode();
Selection selection;
if (mouse->button() == Qt::LeftButton && !innerNode.isNull() && innerNode.handle()->renderer() &&
innerNode.handle()->renderer()->shouldSelect()) {
Position pos(innerNode.handle()->positionForCoordinates(event->x(), event->y()).position());
if (pos.node() && (pos.node()->nodeType() == Node::TEXT_NODE || pos.node()->nodeType() == Node::CDATA_SECTION_NODE)) {
selection.moveTo(pos);
selection.expandUsingGranularity(Selection::LINE);
}
}
if (selection.state() != Selection::CARET) {
d->editor_context.beginSelectingText(Selection::LINE);
}
setCaret(selection);
startAutoScroll();
}
void KHTMLPart::handleMousePressEventSingleClick(khtml::MousePressEvent *event)
{
QMouseEvent *mouse = event->qmouseEvent();
DOM::Node innerNode = event->innerNode();
if (mouse->button() == Qt::LeftButton) {
Selection sel;
if (!innerNode.isNull() && innerNode.handle()->renderer() &&
innerNode.handle()->renderer()->shouldSelect()) {
bool extendSelection = mouse->modifiers() & Qt::ShiftModifier;
// Don't restart the selection when the mouse is pressed on an
// existing selection so we can allow for text dragging.
if (!extendSelection && isPointInsideSelection(event->x(), event->y())) {
return;
}
Position pos(innerNode.handle()->positionForCoordinates(event->x(), event->y()).position());
if (pos.isEmpty())
pos = Position(innerNode.handle(), innerNode.handle()->caretMinOffset());
kDebug(6050) << event->x() << event->y() << pos << endl;
sel = caret();
if (extendSelection && sel.notEmpty()) {
sel.clearModifyBias();
sel.setExtent(pos);
if (d->editor_context.m_selectionGranularity != Selection::CHARACTER) {
sel.expandUsingGranularity(d->editor_context.m_selectionGranularity);
}
d->editor_context.m_beganSelectingText = true;
} else {
sel = pos;
d->editor_context.m_selectionGranularity = Selection::CHARACTER;
}
}
setCaret(sel);
startAutoScroll();
}
}
void KHTMLPart::khtmlMousePressEvent( khtml::MousePressEvent *event )
{
DOM::DOMString url = event->url();
QMouseEvent *_mouse = event->qmouseEvent();
DOM::Node innerNode = event->innerNode();
d->m_mousePressNode = innerNode;
d->m_dragStartPos = QPoint(event->x(), event->y());
if ( !event->url().isNull() ) {
d->m_strSelectedURL = event->url().string();
d->m_strSelectedURLTarget = event->target().string();
}
else {
d->m_strSelectedURL.clear();
d->m_strSelectedURLTarget.clear();
}
if ( _mouse->button() == Qt::LeftButton ||
_mouse->button() == Qt::MidButton )
{
d->m_bMousePressed = true;
#ifdef KHTML_NO_SELECTION
d->m_dragLastPos = _mouse->globalPos();
#else
if ( _mouse->button() == Qt::LeftButton )
{
if ( (!d->m_strSelectedURL.isNull() && !isEditable())
|| (!d->m_mousePressNode.isNull() && d->m_mousePressNode.elementId() == ID_IMG) )
return;
d->editor_context.m_beganSelectingText = false;
handleMousePressEventSingleClick(event);
}
#endif
}
if ( _mouse->button() == Qt::RightButton )
{
popupMenu( d->m_strSelectedURL );
// might be deleted, don't touch "this"
}
}
void KHTMLPart::khtmlMouseDoubleClickEvent( khtml::MouseDoubleClickEvent *event )
{
QMouseEvent *_mouse = event->qmouseEvent();
if ( _mouse->button() == Qt::LeftButton )
{
d->m_bMousePressed = true;
d->editor_context.m_beganSelectingText = false;
if (event->clickCount() == 2) {
handleMousePressEventDoubleClick(event);
return;
}
if (event->clickCount() >= 3) {
handleMousePressEventTripleClick(event);
return;
}
}
}
#ifndef KHTML_NO_SELECTION
bool KHTMLPart::isExtendingSelection() const
{
// This is it, the whole detection. khtmlMousePressEvent only sets this
// on LMB or MMB, but never on RMB. As text selection doesn't work for MMB,
// it's sufficient to only rely on this flag to detect selection extension.
return d->editor_context.m_beganSelectingText;
}
void KHTMLPart::extendSelectionTo(int x, int y, const DOM::Node &innerNode)
{
// handle making selection
Position pos(innerNode.handle()->positionForCoordinates(x, y).position());
// Don't modify the selection if we're not on a node.
if (pos.isEmpty())
return;
// Restart the selection if this is the first mouse move. This work is usually
// done in khtmlMousePressEvent, but not if the mouse press was on an existing selection.
Selection sel = caret();
sel.clearModifyBias();
if (!d->editor_context.m_beganSelectingText) {
// We are beginning a selection during press-drag, when the original click
// wasn't appropriate for one. Make sure to set the granularity.
d->editor_context.beginSelectingText(Selection::CHARACTER);
sel.moveTo(pos);
}
sel.setExtent(pos);
if (d->editor_context.m_selectionGranularity != Selection::CHARACTER) {
sel.expandUsingGranularity(d->editor_context.m_selectionGranularity);
}
setCaret(sel);
}
#endif // KHTML_NO_SELECTION
bool KHTMLPart::handleMouseMoveEventDrag(khtml::MouseMoveEvent *event)
{
#ifdef QT_NO_DRAGANDDROP
return false;
#else
if (!dndEnabled())
return false;
DOM::Node innerNode = event->innerNode();
if( (d->m_bMousePressed &&
( (!d->m_strSelectedURL.isEmpty() && !isEditable())
|| (!d->m_mousePressNode.isNull() && d->m_mousePressNode.elementId() == ID_IMG) ) )
&& ( d->m_dragStartPos - QPoint(event->x(), event->y()) ).manhattanLength() > KGlobalSettings::dndEventDelay() ) {
DOM::DOMString url = event->url();
QPixmap pix;
HTMLImageElementImpl *img = 0L;
KUrl u;
// qDebug("****************** Event URL: %s", url.string().toLatin1().constData());
// qDebug("****************** Event Target: %s", target.string().toLatin1().constData());
// Normal image...
if ( url.length() == 0 && innerNode.handle() && innerNode.handle()->id() == ID_IMG )
{
img = static_cast<HTMLImageElementImpl *>(innerNode.handle());
u = KUrl( completeURL( khtml::parseURL(img->getAttribute(ATTR_SRC)).string() ) );
pix = KIconLoader::global()->loadIcon("image-x-generic", KIconLoader::Desktop);
}
else
{
// Text or image link...
u = completeURL( d->m_strSelectedURL );
pix = KIO::pixmapForUrl(u, 0, KIconLoader::Desktop, KIconLoader::SizeMedium);
}
u.setPass(QString());
QDrag *drag = new QDrag( d->m_view->viewport() );
QMap<QString, QString> metaDataMap;
if ( !d->m_referrer.isEmpty() )
metaDataMap.insert( "referrer", d->m_referrer );
QMimeData* mimeData = new QMimeData();
u.populateMimeData( mimeData, metaDataMap );
drag->setMimeData( mimeData );
if( img && img->complete() )
drag->mimeData()->setImageData( img->currentImage() );
if ( !pix.isNull() )
drag->setPixmap( pix );
stopAutoScroll();
drag->start();
// when we finish our drag, we need to undo our mouse press
d->m_bMousePressed = false;
d->m_strSelectedURL.clear();
d->m_strSelectedURLTarget.clear();
return true;
}
return false;
#endif // QT_NO_DRAGANDDROP
}
bool KHTMLPart::handleMouseMoveEventOver(khtml::MouseMoveEvent *event)
{
// Mouse clicked -> do nothing
if ( d->m_bMousePressed ) return false;
DOM::DOMString url = event->url();
// The mouse is over something
if ( url.length() )
{
DOM::DOMString target = event->target();
QMouseEvent *_mouse = event->qmouseEvent();
DOM::Node innerNode = event->innerNode();
bool shiftPressed = ( _mouse->modifiers() & Qt::ShiftModifier );
// Image map
if ( !innerNode.isNull() && innerNode.elementId() == ID_IMG )
{
HTMLImageElementImpl *i = static_cast<HTMLImageElementImpl *>(innerNode.handle());
if ( i && i->isServerMap() )
{
khtml::RenderObject *r = i->renderer();
if(r)
{
int absx, absy;
r->absolutePosition(absx, absy);
int x(event->x() - absx), y(event->y() - absy);
d->m_overURL = url.string() + QString("?%1,%2").arg(x).arg(y);
d->m_overURLTarget = target.string();
overURL( d->m_overURL, target.string(), shiftPressed );
return true;
}
}
}
// normal link
if ( d->m_overURL.isEmpty() || d->m_overURL != url || d->m_overURLTarget != target )
{
d->m_overURL = url.string();
d->m_overURLTarget = target.string();
overURL( d->m_overURL, target.string(), shiftPressed );
}
}
else // Not over a link...
{
if( !d->m_overURL.isEmpty() ) // and we were over a link -> reset to "default statusbar text"
{
// reset to "default statusbar text"
resetHoverText();
}
}
return true;
}
void KHTMLPart::handleMouseMoveEventSelection(khtml::MouseMoveEvent *event)
{
// Mouse not pressed. Do nothing.
if (!d->m_bMousePressed)
return;
#ifdef KHTML_NO_SELECTION
if (d->m_doc && d->m_view) {
QPoint diff( mouse->globalPos() - d->m_dragLastPos );
if (abs(diff.x()) > 64 || abs(diff.y()) > 64) {
d->m_view->scrollBy(-diff.x(), -diff.y());
d->m_dragLastPos = mouse->globalPos();
}
}
#else
QMouseEvent *mouse = event->qmouseEvent();
DOM::Node innerNode = event->innerNode();
if ( (mouse->buttons() & Qt::LeftButton) == 0 || !innerNode.handle() || !innerNode.handle()->renderer() ||
!innerNode.handle()->renderer()->shouldSelect())
return;
// handle making selection
extendSelectionTo(event->x(), event->y(), innerNode);
#endif // KHTML_NO_SELECTION
}
void KHTMLPart::khtmlMouseMoveEvent( khtml::MouseMoveEvent *event )
{
if (handleMouseMoveEventDrag(event))
return;
if (handleMouseMoveEventOver(event))
return;
handleMouseMoveEventSelection(event);
}
void KHTMLPart::khtmlMouseReleaseEvent( khtml::MouseReleaseEvent *event )
{
DOM::Node innerNode = event->innerNode();
d->m_mousePressNode = DOM::Node();
if ( d->m_bMousePressed ) {
setStatusBarText(QString(), BarHoverText);
stopAutoScroll();
}
// Used to prevent mouseMoveEvent from initiating a drag before
// the mouse is pressed again.
d->m_bMousePressed = false;
#ifndef QT_NO_CLIPBOARD
QMouseEvent *_mouse = event->qmouseEvent();
if ((d->m_guiProfile == BrowserViewGUI) && (_mouse->button() == Qt::MidButton) && (event->url().isNull())) {
kDebug( 6050 ) << "MMB shouldOpen=" << d->m_bOpenMiddleClick;
if (d->m_bOpenMiddleClick) {
KHTMLPart *p = this;
while (p->parentPart()) p = p->parentPart();
p->d->m_extension->pasteRequest();
}
}
#endif
#ifndef KHTML_NO_SELECTION
{
// Clear the selection if the mouse didn't move after the last mouse press.
// We do this so when clicking on the selection, the selection goes away.
// However, if we are editing, place the caret.
if (!d->editor_context.m_beganSelectingText
&& d->m_dragStartPos.x() == event->x()
&& d->m_dragStartPos.y() == event->y()
&& d->editor_context.m_selection.state() == Selection::RANGE) {
Selection selection;
#ifdef APPLE_CHANGES
if (d->editor_context.m_selection.base().node()->isContentEditable())
#endif
selection.moveTo(d->editor_context.m_selection.base().node()->positionForCoordinates(event->x(), event->y()).position());
setCaret(selection);
}
// get selected text and paste to the clipboard
#ifndef QT_NO_CLIPBOARD
QString text = selectedText();
text.replace(QChar(0xa0), ' ');
if (!text.isEmpty()) {
disconnect( qApp->clipboard(), SIGNAL( selectionChanged()), this, SLOT( slotClearSelection()));
qApp->clipboard()->setText(text,QClipboard::Selection);
connect( qApp->clipboard(), SIGNAL( selectionChanged()), SLOT( slotClearSelection()));
}
#endif
//kDebug( 6000 ) << "selectedText = " << text;
emitSelectionChanged();
//kDebug(6000) << "rel2: startBefEnd " << d->m_startBeforeEnd << " extAtEnd " << d->m_extendAtEnd << " (" << d->m_startOffset << ") - (" << d->m_endOffset << "), caretOfs " << d->caretOffset();
}
#endif
}
void KHTMLPart::khtmlDrawContentsEvent( khtml::DrawContentsEvent * )
{
}
void KHTMLPart::guiActivateEvent( KParts::GUIActivateEvent *event )
{
if ( event->activated() )
{
emitSelectionChanged();
emit d->m_extension->enableAction( "print", d->m_doc != 0 );
if ( !d->m_settings->autoLoadImages() && d->m_paLoadImages )
{
QList<QAction*> lst;
lst.append( d->m_paLoadImages );
plugActionList( "loadImages", lst );
}
}
}
void KHTMLPart::slotPrintFrame()
{
if ( d->m_frames.count() == 0 )
return;
KParts::ReadOnlyPart *frame = currentFrame();
if (!frame)
return;
KParts::BrowserExtension *ext = KParts::BrowserExtension::childObject( frame );
if ( !ext )
return;
const QMetaObject *mo = ext->metaObject();
if (mo->indexOfSlot( "print()") != -1)
QMetaObject::invokeMethod(ext, "print()", Qt::DirectConnection);
}
void KHTMLPart::slotSelectAll()
{
KParts::ReadOnlyPart *part = currentFrame();
if (part && part->inherits("KHTMLPart"))
static_cast<KHTMLPart *>(part)->selectAll();
}
void KHTMLPart::startAutoScroll()
{
connect(&d->m_scrollTimer, SIGNAL( timeout() ), this, SLOT( slotAutoScroll() ));
d->m_scrollTimer.setSingleShot(false);
d->m_scrollTimer.start(100);
}
void KHTMLPart::stopAutoScroll()
{
disconnect(&d->m_scrollTimer, SIGNAL( timeout() ), this, SLOT( slotAutoScroll() ));
if (d->m_scrollTimer.isActive())
d->m_scrollTimer.stop();
}
void KHTMLPart::slotAutoScroll()
{
if (d->m_view)
d->m_view->doAutoScroll();
else
stopAutoScroll(); // Safety
}
void KHTMLPart::runAdFilter()
{
if ( parentPart() )
parentPart()->runAdFilter();
if ( !d->m_doc )
return;
QSetIterator<khtml::CachedObject*> it( d->m_doc->docLoader()->m_docObjects );
while (it.hasNext())
{
khtml::CachedObject* obj = it.next();
if ( obj->type() == khtml::CachedObject::Image ) {
khtml::CachedImage *image = static_cast<khtml::CachedImage *>(obj);
bool wasBlocked = image->m_wasBlocked;
image->m_wasBlocked = KHTMLGlobal::defaultHTMLSettings()->isAdFiltered( d->m_doc->completeURL( image->url().string() ) );
if ( image->m_wasBlocked != wasBlocked )
image->do_notify(QRect(QPoint(0,0), image->pixmap_size()));
}
}
if ( KHTMLGlobal::defaultHTMLSettings()->isHideAdsEnabled() ) {
for ( NodeImpl *nextNode, *node = d->m_doc; node; node = nextNode ) {
// We might be deleting 'node' shortly.
nextNode = node->traverseNextNode();
if ( node->id() == ID_IMG ||
node->id() == ID_IFRAME ||
(node->id() == ID_INPUT && static_cast<HTMLInputElementImpl *>(node)->inputType() == HTMLInputElementImpl::IMAGE ))
{
if ( KHTMLGlobal::defaultHTMLSettings()->isAdFiltered( d->m_doc->completeURL( static_cast<ElementImpl *>(node)->getAttribute(ATTR_SRC).string() ) ) )
{
// Since any kids of node will be deleted, too, fastforward nextNode
// until we get outside of node.
while (nextNode && nextNode->isAncestor(node))
nextNode = nextNode->traverseNextNode();
node->ref();
NodeImpl *parent = node->parent();
if( parent )
{
int exception = 0;
parent->removeChild(node, exception);
}
node->deref();
}
}
}
}
}
void KHTMLPart::selectAll()
{
if (!d->m_doc) return;
NodeImpl *first;
if (d->m_doc->isHTMLDocument())
first = static_cast<HTMLDocumentImpl*>(d->m_doc)->body();
else
first = d->m_doc;
NodeImpl *next;
// Look for first text/cdata node that has a renderer,
// or first childless replaced element
while ( first && !(first->renderer()
&& ((first->nodeType() == Node::TEXT_NODE || first->nodeType() == Node::CDATA_SECTION_NODE)
|| (first->renderer()->isReplaced() && !first->renderer()->firstChild()))))
{
next = first->firstChild();
if ( !next ) next = first->nextSibling();
while( first && !next )
{
first = first->parentNode();
if ( first )
next = first->nextSibling();
}
first = next;
}
NodeImpl *last;
if (d->m_doc->isHTMLDocument())
last = static_cast<HTMLDocumentImpl*>(d->m_doc)->body();
else
last = d->m_doc;
// Look for last text/cdata node that has a renderer,
// or last childless replaced element
// ### Instead of changing this loop, use findLastSelectableNode
// in render_table.cpp (LS)
while ( last && !(last->renderer()
&& ((last->nodeType() == Node::TEXT_NODE || last->nodeType() == Node::CDATA_SECTION_NODE)
|| (last->renderer()->isReplaced() && !last->renderer()->lastChild()))))
{
next = last->lastChild();
if ( !next ) next = last->previousSibling();
while ( last && !next )
{
last = last->parentNode();
if ( last )
next = last->previousSibling();
}
last = next;
}
if ( !first || !last )
return;
Q_ASSERT(first->renderer());
Q_ASSERT(last->renderer());
d->editor_context.m_selection.moveTo(Position(first, 0), Position(last, last->nodeValue().length()));
d->m_doc->updateSelection();
emitSelectionChanged();
}
bool KHTMLPart::checkLinkSecurity(const KUrl &linkURL,const KLocalizedString &message, const QString &button)
{
bool linkAllowed = true;
if ( d->m_doc )
linkAllowed = KAuthorized::authorizeUrlAction("redirect", url(), linkURL);
if ( !linkAllowed ) {
khtml::Tokenizer *tokenizer = d->m_doc->tokenizer();
if (tokenizer)
tokenizer->setOnHold(true);
int response = KMessageBox::Cancel;
if (!message.isEmpty())
{
// Dangerous flag makes the Cancel button the default
response = KMessageBox::warningContinueCancel( 0,
message.subs(Qt::escape(linkURL.prettyUrl())).toString(),
i18n( "Security Warning" ),
KGuiItem(button),
KStandardGuiItem::cancel(),
QString(), // no don't ask again info
KMessageBox::Notify | KMessageBox::Dangerous );
}
else
{
KMessageBox::error( 0,
i18n( "<qt>Access by untrusted page to<br /><b>%1</b><br /> denied.</qt>", Qt::escape(linkURL.prettyUrl())),
i18n( "Security Alert" ));
}
if (tokenizer)
tokenizer->setOnHold(false);
return (response==KMessageBox::Continue);
}
return true;
}
void KHTMLPart::slotPartRemoved( KParts::Part *part )
{
// kDebug(6050) << part;
if ( part == d->m_activeFrame )
{
d->m_activeFrame = 0L;
if ( !part->inherits( "KHTMLPart" ) )
{
if (factory()) {
factory()->removeClient( part );
}
if (childClients().contains(part)) {
removeChildClient( part );
}
}
}
}
void KHTMLPart::slotActiveFrameChanged( KParts::Part *part )
{
// kDebug(6050) << this << "part=" << part;
if ( part == this )
{
kError(6050) << "strange error! we activated ourselves";
assert( false );
return;
}
// kDebug(6050) << "d->m_activeFrame=" << d->m_activeFrame;
if ( d->m_activeFrame && d->m_activeFrame->widget() && d->m_activeFrame->widget()->inherits( "QFrame" ) )
{
QFrame *frame = static_cast<QFrame *>( d->m_activeFrame->widget() );
if (frame->frameStyle() != QFrame::NoFrame)
{
frame->setFrameStyle( QFrame::StyledPanel | QFrame::Sunken);
frame->repaint();
}
}
if( d->m_activeFrame && !d->m_activeFrame->inherits( "KHTMLPart" ) )
{
if (factory()) {
factory()->removeClient( d->m_activeFrame );
}
removeChildClient( d->m_activeFrame );
}
if( part && !part->inherits( "KHTMLPart" ) )
{
if (factory()) {
factory()->addClient( part );
}
insertChildClient( part );
}
d->m_activeFrame = part;
if ( d->m_activeFrame && d->m_activeFrame->widget()->inherits( "QFrame" ) )
{
QFrame *frame = static_cast<QFrame *>( d->m_activeFrame->widget() );
if (frame->frameStyle() != QFrame::NoFrame)
{
frame->setFrameStyle( QFrame::StyledPanel | QFrame::Plain);
frame->repaint();
}
kDebug(6050) << "new active frame " << d->m_activeFrame;
}
updateActions();
// (note: childObject returns 0 if the argument is 0)
d->m_extension->setExtensionProxy( KParts::BrowserExtension::childObject( d->m_activeFrame ) );
}
void KHTMLPart::setActiveNode(const DOM::Node &node)
{
if (!d->m_doc || !d->m_view)
return;
// Set the document's active node
d->m_doc->setFocusNode(node.handle());
// Scroll the view if necessary to ensure that the new focus node is visible
QRect rect = node.handle()->getRect();
d->m_view->ensureVisible(rect.right(), rect.bottom());
d->m_view->ensureVisible(rect.left(), rect.top());
}
DOM::Node KHTMLPart::activeNode() const
{
return DOM::Node(d->m_doc?d->m_doc->focusNode():0);
}
DOM::EventListener *KHTMLPart::createHTMLEventListener( QString code, QString name, NodeImpl* node, bool svg )
{
KJSProxy *proxy = jScript();
if (!proxy)
return 0;
return proxy->createHTMLEventHandler( url().url(), name, code, node, svg );
}
KHTMLPart *KHTMLPart::opener()
{
return d->m_opener;
}
void KHTMLPart::setOpener(KHTMLPart *_opener)
{
d->m_opener = _opener;
}
bool KHTMLPart::openedByJS()
{
return d->m_openedByJS;
}
void KHTMLPart::setOpenedByJS(bool _openedByJS)
{
d->m_openedByJS = _openedByJS;
}
void KHTMLPart::preloadStyleSheet(const QString &url, const QString &stylesheet)
{
khtml::Cache::preloadStyleSheet(url, stylesheet);
}
void KHTMLPart::preloadScript(const QString &url, const QString &script)
{
khtml::Cache::preloadScript(url, script);
}
long KHTMLPart::cacheId() const
{
return d->m_cacheId;
}
bool KHTMLPart::restored() const
{
return d->m_restored;
}
bool KHTMLPart::pluginPageQuestionAsked(const QString& mimetype) const
{
// parentPart() should be const!
KHTMLPart* parent = const_cast<KHTMLPart *>(this)->parentPart();
if ( parent )
return parent->pluginPageQuestionAsked(mimetype);
return d->m_pluginPageQuestionAsked.contains(mimetype);
}
void KHTMLPart::setPluginPageQuestionAsked(const QString& mimetype)
{
if ( parentPart() )
parentPart()->setPluginPageQuestionAsked(mimetype);
d->m_pluginPageQuestionAsked.append(mimetype);
}
KEncodingDetector *KHTMLPart::createDecoder()
{
KEncodingDetector *dec = new KEncodingDetector();
if( !d->m_encoding.isNull() )
dec->setEncoding( d->m_encoding.toLatin1().constData(),
d->m_haveEncoding ? KEncodingDetector::UserChosenEncoding : KEncodingDetector::EncodingFromHTTPHeader);
else {
// Inherit the default encoding from the parent frame if there is one.
QByteArray defaultEncoding = (parentPart() && parentPart()->d->m_decoder)
? QByteArray( parentPart()->d->m_decoder->encoding() ) : settings()->encoding().toLatin1();
dec->setEncoding(defaultEncoding.constData(), KEncodingDetector::DefaultEncoding);
}
if (d->m_doc)
d->m_doc->setDecoder(dec);
dec->setAutoDetectLanguage( d->m_autoDetectLanguage );
return dec;
}
void KHTMLPart::emitCaretPositionChanged(const DOM::Position &pos) {
// pos must not be already converted to range-compliant coordinates
Position rng_pos = pos.equivalentRangeCompliantPosition();
Node node = rng_pos.node();
emit caretPositionChanged(node, rng_pos.offset());
}
void KHTMLPart::restoreScrollPosition()
{
const KParts::OpenUrlArguments args( arguments() );
if ( url().hasRef() && !d->m_restoreScrollPosition && !args.reload()) {
if ( !d->m_doc || !d->m_doc->parsing() )
disconnect(d->m_view, SIGNAL(finishedLayout()), this, SLOT(restoreScrollPosition()));
if ( !gotoAnchor(url().encodedHtmlRef()) )
gotoAnchor(url().htmlRef());
return;
}
// Check whether the viewport has become large enough to encompass the stored
// offsets. If the document has been fully loaded, force the new coordinates,
// even if the canvas is too short (can happen when user resizes the window
// during loading).
if (d->m_view->contentsHeight() - d->m_view->visibleHeight() >= args.yOffset()
|| d->m_bComplete) {
d->m_view->setContentsPos(args.xOffset(), args.yOffset());
disconnect(d->m_view, SIGNAL(finishedLayout()), this, SLOT(restoreScrollPosition()));
}
}
void KHTMLPart::openWallet(DOM::HTMLFormElementImpl *form)
{
#ifndef KHTML_NO_WALLET
KHTMLPart *p;
for (p = parentPart(); p && p->parentPart(); p = p->parentPart()) {
}
if (p) {
p->openWallet(form);
return;
}
if (onlyLocalReferences()) { // avoid triggering on local apps, thumbnails
return;
}
if (d->m_wallet) {
if (d->m_bWalletOpened) {
if (d->m_wallet->isOpen()) {
form->walletOpened(d->m_wallet);
return;
}
d->m_wallet->deleteLater();
d->m_wallet = 0L;
d->m_bWalletOpened = false;
}
}
if (!d->m_wq) {
KWallet::Wallet *wallet = KWallet::Wallet::openWallet(KWallet::Wallet::NetworkWallet(), widget() ? widget()->topLevelWidget()->winId() : 0, KWallet::Wallet::Asynchronous);
d->m_wq = new KHTMLWalletQueue(this);
d->m_wq->wallet = wallet;
connect(wallet, SIGNAL(walletOpened(bool)), d->m_wq, SLOT(walletOpened(bool)));
connect(d->m_wq, SIGNAL(walletOpened(KWallet::Wallet*)), this, SLOT(walletOpened(KWallet::Wallet*)));
}
assert(form);
d->m_wq->callers.append(KHTMLWalletQueue::Caller(form, form->document()));
#endif // KHTML_NO_WALLET
}
void KHTMLPart::saveToWallet(const QString& key, const QMap<QString,QString>& data)
{
#ifndef KHTML_NO_WALLET
KHTMLPart *p;
for (p = parentPart(); p && p->parentPart(); p = p->parentPart()) {
}
if (p) {
p->saveToWallet(key, data);
return;
}
if (d->m_wallet) {
if (d->m_bWalletOpened) {
if (d->m_wallet->isOpen()) {
if (!d->m_wallet->hasFolder(KWallet::Wallet::FormDataFolder())) {
d->m_wallet->createFolder(KWallet::Wallet::FormDataFolder());
}
d->m_wallet->setFolder(KWallet::Wallet::FormDataFolder());
d->m_wallet->writeMap(key, data);
return;
}
d->m_wallet->deleteLater();
d->m_wallet = 0L;
d->m_bWalletOpened = false;
}
}
if (!d->m_wq) {
KWallet::Wallet *wallet = KWallet::Wallet::openWallet(KWallet::Wallet::NetworkWallet(), widget() ? widget()->topLevelWidget()->winId() : 0, KWallet::Wallet::Asynchronous);
d->m_wq = new KHTMLWalletQueue(this);
d->m_wq->wallet = wallet;
connect(wallet, SIGNAL(walletOpened(bool)), d->m_wq, SLOT(walletOpened(bool)));
connect(d->m_wq, SIGNAL(walletOpened(KWallet::Wallet*)), this, SLOT(walletOpened(KWallet::Wallet*)));
}
d->m_wq->savers.append(qMakePair(key, data));
#endif // KHTML_NO_WALLET
}
void KHTMLPart::dequeueWallet(DOM::HTMLFormElementImpl *form) {
#ifndef KHTML_NO_WALLET
KHTMLPart *p;
for (p = parentPart(); p && p->parentPart(); p = p->parentPart()) {
}
if (p) {
p->dequeueWallet(form);
return;
}
if (d->m_wq) {
d->m_wq->callers.removeAll(KHTMLWalletQueue::Caller(form, form->document()));
}
#endif // KHTML_NO_WALLET
}
void KHTMLPart::walletOpened(KWallet::Wallet *wallet) {
#ifndef KHTML_NO_WALLET
assert(!d->m_wallet);
assert(d->m_wq);
d->m_wq->deleteLater(); // safe?
d->m_wq = 0L;
if (!wallet) {
d->m_bWalletOpened = false;
return;
}
d->m_wallet = wallet;
d->m_bWalletOpened = true;
connect(d->m_wallet, SIGNAL(walletClosed()), SLOT(slotWalletClosed()));
d->m_walletForms.clear();
if (!d->m_statusBarWalletLabel) {
d->m_statusBarWalletLabel = new KUrlLabel(d->m_statusBarExtension->statusBar());
d->m_statusBarWalletLabel->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum));
d->m_statusBarWalletLabel->setUseCursor(false);
d->m_statusBarExtension->addStatusBarItem(d->m_statusBarWalletLabel, 0, false);
d->m_statusBarWalletLabel->setPixmap(SmallIcon("wallet-open"));
connect(d->m_statusBarWalletLabel, SIGNAL(leftClickedUrl()), SLOT(launchWalletManager()));
connect(d->m_statusBarWalletLabel, SIGNAL(rightClickedUrl()), SLOT(walletMenu()));
}
d->m_statusBarWalletLabel->setToolTip(i18n("The wallet '%1' is open and being used for form data and passwords.", KWallet::Wallet::NetworkWallet()));
#endif // KHTML_NO_WALLET
}
KWallet::Wallet *KHTMLPart::wallet()
{
#ifndef KHTML_NO_WALLET
KHTMLPart *p;
for (p = parentPart(); p && p->parentPart(); p = p->parentPart())
;
if (p)
return p->wallet();
return d->m_wallet;
#else
return 0;
#endif // !KHTML_NO_WALLET
}
void KHTMLPart::slotWalletClosed()
{
#ifndef KHTML_NO_WALLET
if (d->m_wallet) {
d->m_wallet->deleteLater();
d->m_wallet = 0L;
}
d->m_bWalletOpened = false;
if (d->m_statusBarWalletLabel) {
d->m_statusBarExtension->removeStatusBarItem(d->m_statusBarWalletLabel);
delete d->m_statusBarWalletLabel;
d->m_statusBarWalletLabel = 0L;
}
#endif // KHTML_NO_WALLET
}
void KHTMLPart::launchWalletManager()
{
#ifndef KHTML_NO_WALLET
QDBusInterface r("org.kde.kwalletmanager", "/kwalletmanager/MainWindow_1",
"org.kde.KMainWindow");
if (!r.isValid()) {
KToolInvocation::startServiceByDesktopName("kwalletmanager_show");
} else {
r.call(QDBus::NoBlock, "show");
r.call(QDBus::NoBlock, "raise");
}
#endif // KHTML_NO_WALLET
}
void KHTMLPart::walletMenu()
{
#ifndef KHTML_NO_WALLET
KMenu *menu = new KMenu(0L);
QActionGroup *menuActionGroup = new QActionGroup(menu);
connect( menuActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(removeStoredPasswordForm(QAction*)) );
menu->addAction(i18n("&Close Wallet"), this, SLOT(slotWalletClosed()));
if (d->m_view && d->m_view->nonPasswordStorableSite(toplevelURL().host())) {
menu->addAction(i18n("&Allow storing passwords for this site"), this, SLOT(delNonPasswordStorableSite()));
}
// List currently removable form passwords
for ( QStringList::ConstIterator it = d->m_walletForms.constBegin(); it != d->m_walletForms.constEnd(); ++it ) {
QAction* action = menu->addAction( i18n("Remove password for form %1", *it) );
action->setActionGroup(menuActionGroup);
QVariant var(*it);
action->setData(var);
}
KAcceleratorManager::manage(menu);
menu->popup(QCursor::pos());
#endif // KHTML_NO_WALLET
}
void KHTMLPart::removeStoredPasswordForm(QAction* action)
{
#ifndef KHTML_NO_WALLET
assert(action);
assert(d->m_wallet);
QVariant var(action->data());
if(var.isNull() || !var.isValid() || var.type() != QVariant::String)
return;
QString key = var.toString();
if (KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(),
KWallet::Wallet::FormDataFolder(),
key))
return; // failed
if (!d->m_wallet->hasFolder(KWallet::Wallet::FormDataFolder()))
return; // failed
d->m_wallet->setFolder(KWallet::Wallet::FormDataFolder());
if (d->m_wallet->removeEntry(key))
return; // failed
d->m_walletForms.removeAll(key);
#endif // KHTML_NO_WALLET
}
void KHTMLPart::addWalletFormKey(const QString& walletFormKey)
{
#ifndef KHTML_NO_WALLET
if (parentPart()) {
parentPart()->addWalletFormKey(walletFormKey);
return;
}
if(!d->m_walletForms.contains(walletFormKey))
d->m_walletForms.append(walletFormKey);
#endif // KHTML_NO_WALLET
}
void KHTMLPart::delNonPasswordStorableSite()
{
#ifndef KHTML_NO_WALLET
if (d->m_view)
d->m_view->delNonPasswordStorableSite(toplevelURL().host());
#endif // KHTML_NO_WALLET
}
void KHTMLPart::saveLoginInformation(const QString& host, const QString& key, const QMap<QString, QString>& walletMap)
{
#ifndef KHTML_NO_WALLET
d->m_storePass.saveLoginInformation(host, key, walletMap);
#endif // KHTML_NO_WALLET
}
void KHTMLPart::slotToggleCaretMode()
{
setCaretMode(d->m_paToggleCaretMode->isChecked());
}
void KHTMLPart::setFormNotification(KHTMLPart::FormNotification fn) {
d->m_formNotification = fn;
}
KHTMLPart::FormNotification KHTMLPart::formNotification() const {
return d->m_formNotification;
}
KUrl KHTMLPart::toplevelURL()
{
KHTMLPart* part = this;
while (part->parentPart())
part = part->parentPart();
if (!part)
return KUrl();
return part->url();
}
bool KHTMLPart::isModified() const
{
if ( !d->m_doc )
return false;
return d->m_doc->unsubmittedFormChanges();
}
void KHTMLPart::setDebugScript( bool enable )
{
unplugActionList( "debugScriptList" );
if ( enable ) {
if (!d->m_paDebugScript) {
d->m_paDebugScript = new KAction( i18n( "JavaScript &Debugger" ), this );
actionCollection()->addAction( "debugScript", d->m_paDebugScript );
connect( d->m_paDebugScript, SIGNAL( triggered( bool ) ), this, SLOT( slotDebugScript() ) );
}
d->m_paDebugScript->setEnabled( d->m_frame ? d->m_frame->m_jscript : 0L );
QList<QAction*> lst;
lst.append( d->m_paDebugScript );
plugActionList( "debugScriptList", lst );
}
d->m_bJScriptDebugEnabled = enable;
}
void KHTMLPart::setSuppressedPopupIndicator( bool enable, KHTMLPart *originPart )
{
if ( parentPart() ) {
parentPart()->setSuppressedPopupIndicator( enable, originPart );
return;
}
if ( enable && originPart ) {
d->m_openableSuppressedPopups++;
if ( d->m_suppressedPopupOriginParts.indexOf( originPart ) == -1 )
d->m_suppressedPopupOriginParts.append( originPart );
}
if ( enable && !d->m_statusBarPopupLabel ) {
d->m_statusBarPopupLabel = new KUrlLabel( d->m_statusBarExtension->statusBar() );
d->m_statusBarPopupLabel->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Minimum ));
d->m_statusBarPopupLabel->setUseCursor( false );
d->m_statusBarExtension->addStatusBarItem( d->m_statusBarPopupLabel, 0, false );
d->m_statusBarPopupLabel->setPixmap( SmallIcon( "window-suppressed") );
d->m_statusBarPopupLabel->setToolTip(i18n("This page was prevented from opening a new window via JavaScript." ) );
connect(d->m_statusBarPopupLabel, SIGNAL(leftClickedUrl()), SLOT(suppressedPopupMenu()));
if (d->m_settings->jsPopupBlockerPassivePopup()) {
QPixmap px;
px = MainBarIcon( "window-suppressed" );
KPassivePopup::message(i18n("Popup Window Blocked"),i18n("This page has attempted to open a popup window but was blocked.\nYou can click on this icon in the status bar to control this behavior\nor to open the popup."),px,d->m_statusBarPopupLabel);
}
} else if ( !enable && d->m_statusBarPopupLabel ) {
d->m_statusBarPopupLabel->setToolTip("" );
d->m_statusBarExtension->removeStatusBarItem( d->m_statusBarPopupLabel );
delete d->m_statusBarPopupLabel;
d->m_statusBarPopupLabel = 0L;
}
}
void KHTMLPart::suppressedPopupMenu() {
KMenu *m = new KMenu(0L);
if ( d->m_openableSuppressedPopups )
m->addAction(i18np("&Show Blocked Popup Window","&Show %1 Blocked Popup Windows", d->m_openableSuppressedPopups), this, SLOT(showSuppressedPopups()));
QAction *a = m->addAction(i18n("Show Blocked Window Passive Popup &Notification"), this, SLOT(togglePopupPassivePopup()));
a->setChecked(d->m_settings->jsPopupBlockerPassivePopup());
m->addAction(i18n("&Configure JavaScript New Window Policies..."), this, SLOT(launchJSConfigDialog()));
m->popup(QCursor::pos());
}
void KHTMLPart::togglePopupPassivePopup() {
// Same hack as in disableJSErrorExtension()
d->m_settings->setJSPopupBlockerPassivePopup( !d->m_settings->jsPopupBlockerPassivePopup() );
emit configurationChanged();
}
void KHTMLPart::showSuppressedPopups() {
foreach ( KHTMLPart* part, d->m_suppressedPopupOriginParts ) {
if (part) {
KJS::Window *w = KJS::Window::retrieveWindow( part );
if (w) {
w->showSuppressedWindows();
w->forgetSuppressedWindows();
}
}
}
setSuppressedPopupIndicator( false );
d->m_openableSuppressedPopups = 0;
d->m_suppressedPopupOriginParts.clear();
}
// Extension to use for "view document source", "save as" etc.
// Using the right extension can help the viewer get into the right mode (#40496)
QString KHTMLPart::defaultExtension() const
{
if ( !d->m_doc )
return ".html";
if ( !d->m_doc->isHTMLDocument() )
return ".xml";
return d->m_doc->htmlMode() == DOM::DocumentImpl::XHtml ? ".xhtml" : ".html";
}
bool KHTMLPart::inProgress() const
{
if (!d->m_bComplete || d->m_runningScripts || (d->m_doc && d->m_doc->parsing()))
return true;
// Any frame that hasn't completed yet ?
ConstFrameIt it = d->m_frames.constBegin();
const ConstFrameIt end = d->m_frames.constEnd();
for (; it != end; ++it ) {
if ((*it)->m_run || !(*it)->m_bCompleted)
return true;
}
return d->m_submitForm || !d->m_redirectURL.isEmpty() || d->m_redirectionTimer.isActive() || d->m_job;
}
using namespace KParts;
#include "khtmlpart_p.moc"
#ifndef KHTML_NO_WALLET
#include "khtml_wallet_p.moc"
#endif
// kate: indent-width 4; replace-tabs on; tab-width 4; space-indent on;
diff --git a/khtml/misc/loader.cpp b/khtml/misc/loader.cpp
index 9a0f6325f4..44b66e7877 100644
--- a/khtml/misc/loader.cpp
+++ b/khtml/misc/loader.cpp
@@ -1,1711 +1,1711 @@
/*
This file is part of the KDE libraries
Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de)
(C) 2001-2003 Dirk Mueller (mueller@kde.org)
(C) 2002 Waldo Bastian (bastian@kde.org)
(C) 2003 Apple Computer, Inc.
(C) 2006-2010 Germain Garand (germain@ebooksfrance.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.
This class provides all functionality needed for loading images, style sheets and html
pages from the web. It has a memory cache for these objects.
// regarding the LRU:
// http://www.is.kyusan-u.ac.jp/~chengk/pub/papers/compsac00_A07-07.pdf
*/
#undef CACHE_DEBUG
//#define CACHE_DEBUG
#ifdef CACHE_DEBUG
#define CDEBUG kDebug(6060)
#else
#define CDEBUG kDebugDevNull()
#endif
#undef LOADER_DEBUG
//#define LOADER_DEBUG
//#define PRELOAD_DEBUG
#include "loader.h"
#include "seed.h"
#include "woff.h"
#include <imload/image.h>
#include <imload/imagepainter.h>
#include <kfilterdev.h>
#include <assert.h>
// default cache size
#define DEFCACHESIZE 2096*1024
//#include <qasyncio.h>
//#include <qasyncimageio.h>
#include <QApplication>
#include <QDesktopWidget>
#include <QPainter>
#include <QBitmap>
#include <QMovie>
#include <QWidget>
#include <QtCore/QDebug>
#include <kauthorized.h>
#include <kio/job.h>
#include <kio/jobuidelegate.h>
#include <kio/jobclasses.h>
#include <kglobal.h>
#include <kcharsets.h>
#include <kiconloader.h>
#include <scheduler.h>
#include <kdebug.h>
#include <khtml_global.h>
#include <khtml_part.h>
#ifdef IMAGE_TITLES
#include <qfile.h>
#include <kfilemetainfo.h>
#include <ktemporaryfile.h>
#endif
#include "html/html_documentimpl.h"
#include "css/css_stylesheetimpl.h"
#include "xml/dom_docimpl.h"
#include "blocked_icon.cpp"
#include <QPaintEngine>
using namespace khtml;
using namespace DOM;
using namespace khtmlImLoad;
#define MAX_LRU_LISTS 20
struct LRUList {
CachedObject* m_head;
CachedObject* m_tail;
LRUList() : m_head(0), m_tail(0) {}
};
static LRUList m_LRULists[MAX_LRU_LISTS];
static LRUList* getLRUListFor(CachedObject* o);
CachedObjectClient::~CachedObjectClient()
{
}
CachedObject::~CachedObject()
{
Cache::removeFromLRUList(this);
}
void CachedObject::finish()
{
m_status = Cached;
}
bool CachedObject::isExpired() const
{
if (!m_expireDate) return false;
time_t now = time(0);
return (difftime(now, m_expireDate) >= 0);
}
void CachedObject::setRequest(Request *_request)
{
if ( _request && !m_request )
m_status = Pending;
if ( allowInLRUList() )
Cache::removeFromLRUList( this );
m_request = _request;
if ( allowInLRUList() )
Cache::insertInLRUList( this );
}
void CachedObject::ref(CachedObjectClient *c)
{
if (m_preloadResult == PreloadNotReferenced) {
if (isLoaded())
m_preloadResult = PreloadReferencedWhileComplete;
else if (m_prospectiveRequest)
m_preloadResult = PreloadReferencedWhileLoading;
else
m_preloadResult = PreloadReferenced;
}
// unfortunately we can be ref'ed multiple times from the
// same object, because it uses e.g. the same foreground
// and the same background picture. so deal with it.
// Hence the use of a QHash rather than of a QSet.
m_clients.insertMulti(c,c);
Cache::removeFromLRUList(this);
m_accessCount++;
}
void CachedObject::deref(CachedObjectClient *c)
{
assert( c );
assert( m_clients.count() );
assert( !canDelete() );
assert( m_clients.contains( c ) );
Cache::flush();
m_clients.take(c);
if (allowInLRUList())
Cache::insertInLRUList(this);
}
void CachedObject::setSize(int size)
{
bool sizeChanged;
if ( !m_next && !m_prev && getLRUListFor(this)->m_head != this )
sizeChanged = false;
else
sizeChanged = ( size - m_size ) != 0;
// The object must now be moved to a different queue,
// since its size has been changed.
if ( sizeChanged && allowInLRUList())
Cache::removeFromLRUList(this);
m_size = size;
if ( sizeChanged && allowInLRUList())
Cache::insertInLRUList(this);
}
QTextCodec* CachedObject::codecForBuffer( const QString& charset, const QByteArray& buffer ) const
{
// we don't use heuristicContentMatch here since it is a) far too slow and
// b) having too much functionality for our case.
uchar* d = ( uchar* ) buffer.data();
int s = buffer.size();
// BOM
if ( s >= 3 &&
d[0] == 0xef && d[1] == 0xbb && d[2] == 0xbf)
return QTextCodec::codecForMib( 106 ); // UTF-8
if ( s >= 2 && ((d[0] == 0xff && d[1] == 0xfe) ||
(d[0] == 0xfe && d[1] == 0xff)))
return QTextCodec::codecForMib( 1000 ); // UCS-2
// Link or @charset
if(!charset.isEmpty())
{
QTextCodec* c = KCharsets::charsets()->codecForName(charset);
if(c->mibEnum() == 11) {
// iso8859-8 (visually ordered)
c = QTextCodec::codecForName("iso8859-8-i");
}
return c;
}
// Default
return QTextCodec::codecForMib( 4 ); // latin 1
}
// -------------------------------------------------------------------------------------------
CachedCSSStyleSheet::CachedCSSStyleSheet(DocLoader* dl, const DOMString &url, KIO::CacheControl _cachePolicy,
const char *accept)
: CachedObject(url, CSSStyleSheet, _cachePolicy, 0)
{
// Set the type we want (probably css or xml)
QString ah = QLatin1String( accept );
if ( !ah.isEmpty() )
ah += ',';
ah += "*/*;q=0.1";
setAccept( ah );
m_hadError = false;
m_wasBlocked = false;
m_err = 0;
// load the file.
// Style sheets block rendering, they are therefore the higher priority item.
// Do |not| touch the priority value unless you conducted thorough tests and
// can back your choice with meaningful data, testing page load time and
// time to first paint.
Cache::loader()->load(dl, this, false, -8);
m_loading = true;
}
CachedCSSStyleSheet::CachedCSSStyleSheet(const DOMString &url, const QString &stylesheet_data)
: CachedObject(url, CSSStyleSheet, KIO::CC_Verify, stylesheet_data.length())
{
m_loading = false;
m_status = Persistent;
m_sheet = DOMString(stylesheet_data);
}
bool khtml::isAcceptableCSSMimetype( const DOM::DOMString& mimetype )
{
// matches Mozilla's check (cf. nsCSSLoader.cpp)
return mimetype.isEmpty() || mimetype == "text/css" || mimetype == "application/x-unknown-content-type";
}
void CachedCSSStyleSheet::ref(CachedObjectClient *c)
{
CachedObject::ref(c);
if (!m_loading) {
if (m_hadError)
c->error( m_err, m_errText );
else
c->setStyleSheet( m_url, m_sheet, m_charset, m_mimetype );
}
}
void CachedCSSStyleSheet::data( QBuffer &buffer, bool eof )
{
if(!eof) return;
buffer.close();
setSize(buffer.buffer().size());
m_charset = checkCharset( buffer.buffer() );
QTextCodec* c = 0;
if (!m_charset.isEmpty()) {
c = KCharsets::charsets()->codecForName(m_charset);
if(c->mibEnum() == 11) c = QTextCodec::codecForName("iso8859-8-i");
}
else {
c = codecForBuffer( m_charsetHint, buffer.buffer() );
m_charset = c->name();
}
QString data = c->toUnicode( buffer.buffer().data(), m_size );
// workaround Qt bugs
m_sheet = static_cast<QChar>(data[0]) == QChar::ByteOrderMark ? DOMString(data.mid( 1 ) ) : DOMString(data);
m_loading = false;
checkNotify();
}
void CachedCSSStyleSheet::checkNotify()
{
if(m_loading || m_hadError) return;
CDEBUG << "CachedCSSStyleSheet:: finishedLoading " << m_url.string() << endl;
for (QHashIterator<CachedObjectClient*,CachedObjectClient*> it( m_clients ); it.hasNext();)
it.next().value()->setStyleSheet( m_url, m_sheet, m_charset, m_mimetype );
}
void CachedCSSStyleSheet::error( int err, const char* text )
{
m_hadError = true;
m_err = err;
m_errText = text;
m_loading = false;
for (QHashIterator<CachedObjectClient*,CachedObjectClient*> it( m_clients ); it.hasNext();)
it.next().value()->error( m_err, m_errText );
}
QString CachedCSSStyleSheet::checkCharset(const QByteArray& buffer ) const
{
int s = buffer.size();
if (s <= 12) return m_charset;
// @charset has to be first or directly after BOM.
// CSS 2.1 says @charset should win over BOM, but since more browsers support BOM
// than @charset, we default to that.
const char* d = buffer.data();
if (strncmp(d, "@charset \"",10) == 0)
{
// the string until "; is the charset name
const char *p = strchr(d+10, '"');
if (p == 0) return m_charset;
QString charset = QString::fromAscii(d+10, p-(d+10));
return charset;
}
return m_charset;
}
// -------------------------------------------------------------------------------------------
CachedScript::CachedScript(DocLoader* dl, const DOMString &url, KIO::CacheControl _cachePolicy, const char*)
: CachedObject(url, Script, _cachePolicy, 0)
{
// It's javascript we want.
// But some websites think their scripts are <some wrong mimetype here>
// and refuse to serve them if we only accept application/x-javascript.
setAccept( QLatin1String("*/*") );
// load the file.
// Scripts block document parsing. They are therefore second in our list of most
// desired resources.
Cache::loader()->load(dl, this, false/*incremental*/, -6);
m_loading = true;
m_hadError = false;
}
CachedScript::CachedScript(const DOMString &url, const QString &script_data)
: CachedObject(url, Script, KIO::CC_Verify, script_data.length())
{
m_hadError = false;
m_loading = false;
m_status = Persistent;
m_script = DOMString(script_data);
}
void CachedScript::ref(CachedObjectClient *c)
{
CachedObject::ref(c);
if(!m_loading) c->notifyFinished(this);
}
void CachedScript::data( QBuffer &buffer, bool eof )
{
if(!eof) return;
buffer.close();
setSize(buffer.buffer().size());
QTextCodec* c = codecForBuffer( m_charset, buffer.buffer() );
QString data = c->toUnicode( buffer.buffer().data(), m_size );
m_script = static_cast<QChar>(data[0]) == QChar::ByteOrderMark ? DOMString(data.mid( 1 ) ) : DOMString(data);
m_loading = false;
checkNotify();
}
void CachedScript::checkNotify()
{
if(m_loading) return;
for (QHashIterator<CachedObjectClient*,CachedObjectClient*> it( m_clients ); it.hasNext();)
it.next().value()->notifyFinished(this);
}
void CachedScript::error( int /*err*/, const char* /*text*/ )
{
m_hadError = true;
m_loading = false;
checkNotify();
}
// ------------------------------------------------------------------------------------------
static QString buildAcceptHeader()
{
return "image/png, image/jpeg, video/x-mng, image/jp2, image/gif;q=0.5,*/*;q=0.1";
}
// -------------------------------------------------------------------------------------
CachedImage::CachedImage(DocLoader* dl, const DOMString &url, KIO::CacheControl _cachePolicy, const char*)
: QObject(), CachedObject(url, Image, _cachePolicy, 0)
{
static const QString &acceptHeader = KGlobal::staticQString( buildAcceptHeader() );
i = new khtmlImLoad::Image(this);
//p = 0;
//pixPart = 0;
bg = 0;
scaled = 0;
bgColor = qRgba( 0, 0, 0, 0 );
m_status = Unknown;
setAccept( acceptHeader );
i->setShowAnimations(dl->showAnimations());
m_loading = true;
if ( KHTMLGlobal::defaultHTMLSettings()->isAdFiltered( url.string() ) ) {
m_wasBlocked = true;
CachedObject::finish();
}
}
CachedImage::~CachedImage()
{
clear();
delete i;
}
void CachedImage::ref( CachedObjectClient *c )
{
CachedObject::ref(c);
#ifdef LOADER_DEBUG
kDebug(6060) << " image "<<this<<" ref'd by client " << c << "\n";
#endif
// for mouseovers, dynamic changes
//### having both makes no sense
if ( m_status >= Persistent && !pixmap_size().isNull() ) {
#ifdef LOADER_DEBUG
kDebug(6060) << "Notifying finished size:" <<
i->size().width() << ", " << i->size().height() << endl;
#endif
c->updatePixmap( QRect(QPoint(0, 0), pixmap_size()),
this );
c->notifyFinished( this );
}
}
void CachedImage::deref( CachedObjectClient *c )
{
CachedObject::deref(c);
/* if(m && m_clients.isEmpty() && m->running())
m->pause();*/
}
#define BGMINWIDTH 32
#define BGMINHEIGHT 32
QPixmap CachedImage::tiled_pixmap(const QColor& newc, int xWidth, int xHeight)
{
// no error indication for background images
if(m_hadError||m_wasBlocked) return *Cache::nullPixmap;
// If we don't have size yet, nothing to draw yet
if (i->size().width() == 0 || i->size().height() == 0)
return *Cache::nullPixmap;
#ifdef __GNUC__
#warning "Needs some additional performance work"
#endif
static QRgb bgTransparent = qRgba( 0, 0, 0, 0 );
QSize s(pixmap_size());
int w = xWidth;
int h = xHeight;
if (w == -1) xWidth = w = s.width();
if (h == -1) xHeight = h = s.height();
if ( ( (bgColor != bgTransparent) && (bgColor != newc.rgba()) ) ||
( bgSize != QSize(xWidth, xHeight)) )
{
delete bg; bg = 0;
}
if (bg)
return *bg;
const QPixmap* src; //source for pretiling, if any
const QPixmap &r = pixmap(); //this is expensive
if (r.isNull()) return r;
//See whether we should scale
if (xWidth != s.width() || xHeight != s.height()) {
src = scaled_pixmap(xWidth, xHeight);
} else {
src = &r;
}
bgSize = QSize(xWidth, xHeight);
//See whether we can - and should - pre-blend
// ### this needs serious investigations. Not likely to help with transparent bgColor,
// won't work with CSS3 multiple backgrounds. Does it help at all in Qt4? (ref: #114938)
if (newc.isValid() && (r.hasAlpha() || r.hasAlphaChannel())) {
bg = new QPixmap(xWidth, xHeight);
bg->fill(newc);
QPainter p(bg);
p.drawPixmap(0, 0, *src);
bgColor = newc.rgba();
src = bg;
} else {
bgColor = bgTransparent;
}
//See whether to pre-tile.
if ( w*h < 8192 )
{
if ( r.width() < BGMINWIDTH )
w = ((BGMINWIDTH-1) / xWidth + 1) * xWidth;
if ( r.height() < BGMINHEIGHT )
h = ((BGMINHEIGHT-1) / xHeight + 1) * xHeight;
}
if ( w != xWidth || h != xHeight )
{
// kDebug() << "pre-tiling " << s.width() << "," << s.height() << " to " << w << "," << h;
QPixmap* oldbg = bg;
bg = new QPixmap(w, h);
if (src->hasAlpha() || src->hasAlphaChannel()) {
if (newc.isValid() && (bgColor != bgTransparent))
bg->fill( bgColor );
else
bg->fill( Qt::transparent );
}
QPainter p(bg);
p.drawTiledPixmap(0, 0, w, h, *src);
p.end();
if ( src == oldbg )
delete oldbg;
} else if (src && !bg) {
// we were asked for the entire pixmap. Cache it.
// ### goes against imload stuff, but it's far too expensive
// to recreate the full pixmap each time it's requested as
// we don't know what portion of it will be used eventually
// (by e.g. paintBackgroundExtended). It could be a few pixels of
// a huge image. See #140248/#1 for an obvious example.
// Imload probably needs to handle all painting in paintBackgroundExtended.
bg = new QPixmap(*src);
}
if (bg)
return *bg;
return *src;
}
QPixmap* CachedImage::scaled_pixmap( int xWidth, int xHeight )
{
// no error indication for background images
if(m_hadError||m_wasBlocked) return Cache::nullPixmap;
// If we don't have size yet, nothing to draw yet
if (i->size().width() == 0 || i->size().height() == 0)
return Cache::nullPixmap;
if (scaled) {
if (scaled->width() == xWidth && scaled->height() == xHeight)
return scaled;
delete scaled;
}
//### this is quite awful performance-wise. It should avoid
// alpha if not needed, and go to pixmap, etc.
QImage im(xWidth, xHeight, QImage::Format_ARGB32_Premultiplied);
QPainter paint(&im);
paint.setCompositionMode(QPainter::CompositionMode_Source);
ImagePainter pi(i, QSize(xWidth, xHeight));
pi.paint(0, 0, &paint);
paint.end();
scaled = new QPixmap(QPixmap::fromImage(im));
return scaled;
}
QPixmap CachedImage::pixmap( ) const
{
if (m_hadError)
return *Cache::brokenPixmap;
if(m_wasBlocked)
return *Cache::blockedPixmap;
int w = i->size().width();
int h = i->size().height();
if (i->hasAlpha() && QApplication::desktop()->paintEngine() &&
!QApplication::desktop()->paintEngine()->hasFeature(QPaintEngine::PorterDuff)) {
QImage im(w, h, QImage::Format_ARGB32_Premultiplied);
QPainter paint(&im);
paint.setCompositionMode(QPainter::CompositionMode_Source);
ImagePainter pi(i);
pi.paint(0, 0, &paint);
paint.end();
return QPixmap::fromImage( im, Qt::NoOpaqueDetection );
} else {
QPixmap pm(w, h);
if (i->hasAlpha())
pm.fill(Qt::transparent);
QPainter paint(&pm);
paint.setCompositionMode(QPainter::CompositionMode_Source);
ImagePainter pi(i);
pi.paint(0, 0, &paint);
paint.end();
return pm;
}
}
QSize CachedImage::pixmap_size() const
{
if (m_wasBlocked) return Cache::blockedPixmap->size();
if (m_hadError) return Cache::brokenPixmap->size();
if (i) return i->size();
return QSize();
}
void CachedImage::imageHasGeometry(khtmlImLoad::Image* /*img*/, int width, int height)
{
#ifdef LOADER_DEBUG
kDebug(6060) << this << " got geometry "<< width << "x" << height;
#endif
do_notify(QRect(0, 0, width, height));
}
void CachedImage::imageChange (khtmlImLoad::Image* /*img*/, QRect region)
{
#ifdef LOADER_DEBUG
kDebug(6060) << "Image " << this << " change " <<
region.x() << "," << region.y() << ":" << region.width() << "x" << region.height() << endl;
#endif
//### this is overly conservative -- I guess we need to also specify reason,
//e.g. repaint vs. changed !!!
delete bg;
bg = 0;
do_notify(region);
}
void CachedImage::doNotifyFinished()
{
for (QHashIterator<CachedObjectClient*,CachedObjectClient*> it( m_clients ); it.hasNext();)
{
it.next().value()->notifyFinished(this);
}
}
void CachedImage::imageError(khtmlImLoad::Image* /*img*/)
{
error(0, 0);
}
void CachedImage::imageDone(khtmlImLoad::Image* /*img*/)
{
#ifdef LOADER_DEBUG
kDebug(6060)<<"Image is done:" << this;
#endif
m_status = Persistent;
m_loading = false;
doNotifyFinished();
}
// QRect CachedImage::valid_rect() const
// {
// if (m_wasBlocked) return Cache::blockedPixmap->rect();
// return (m_hadError ? Cache::brokenPixmap->rect() : m ? m->frameRect() : ( p ? p->rect() : QRect()) );
// }
void CachedImage::do_notify(const QRect& r)
{
for (QHashIterator<CachedObjectClient*,CachedObjectClient*> it( m_clients ); it.hasNext();)
{
#ifdef LOADER_DEBUG
kDebug(6060) << " image "<<this<<" notify of geom client " << it.peekNext() << "\n";
#endif
it.next().value()->updatePixmap( r, this);
}
}
void CachedImage::setShowAnimations( KHTMLSettings::KAnimationAdvice showAnimations )
{
if (i)
i->setShowAnimations(showAnimations);
}
void CachedImage::clear()
{
delete i; i = new khtmlImLoad::Image(this);
delete scaled; scaled = 0;
bgColor = qRgba( 0, 0, 0, 0xff );
delete bg; bg = 0;
bgSize = QSize(-1,-1);
setSize(0);
}
void CachedImage::data ( QBuffer &_buffer, bool eof )
{
#ifdef LOADER_DEBUG
kDebug( 6060 ) << this << "in CachedImage::data(buffersize " << _buffer.buffer().size() <<", eof=" << eof << " pos:" << _buffer.pos();
#endif
i->processData((uchar*)_buffer.data().data(), _buffer.pos());
_buffer.close();
if (eof)
i->processEOF();
}
void CachedImage::finish()
{
CachedObject::finish();
m_loading = false;
QSize s = pixmap_size();
setSize( s.width() * s.height() * 2);
}
void CachedImage::error( int /*err*/, const char* /*text*/ )
{
clear();
m_hadError = true;
m_loading = false;
do_notify(QRect(0, 0, 16, 16));
for (QHashIterator<CachedObjectClient*,CachedObjectClient*> it( m_clients ); it.hasNext();)
it.next().value()->notifyFinished(this);
}
// -------------------------------------------------------------------------------------------
CachedSound::CachedSound(DocLoader* dl, const DOMString &url, KIO::CacheControl _cachePolicy, const char*)
: CachedObject(url, Sound, _cachePolicy, 0)
{
setAccept( QLatin1String("*/*") ); // should be whatever phonon would accept...
Cache::loader()->load(dl, this, false/*incremental*/, 2);
m_loading = true;
}
void CachedSound::ref(CachedObjectClient *c)
{
CachedObject::ref(c);
if(!m_loading) c->notifyFinished(this);
}
void CachedSound::data( QBuffer &buffer, bool eof )
{
if(!eof) return;
buffer.close();
setSize(buffer.buffer().size());
m_sound = buffer.buffer();
m_loading = false;
checkNotify();
}
void CachedSound::checkNotify()
{
if(m_loading) return;
for (QHashIterator<CachedObjectClient*,CachedObjectClient*> it( m_clients ); it.hasNext();)
it.next().value()->notifyFinished(this);
}
void CachedSound::error( int /*err*/, const char* /*text*/ )
{
m_loading = false;
checkNotify();
}
// -------------------------------------------------------------------------------------------
CachedFont::CachedFont(DocLoader* dl, const DOMString &url, KIO::CacheControl _cachePolicy, const char*)
: CachedObject(url, Font, _cachePolicy, 0)
{
setAccept( QLatin1String("*/*") );
// Fonts are desired early because their absence will lead to a page being rendered
// with a default replacement, then the text being re-rendered with the new font when it arrives.
// This can be fairly disturbing for the reader - more than missing images for instance.
Cache::loader()->load(dl, this, false /*incremental*/, -4);
m_loading = true;
}
void CachedFont::ref(CachedObjectClient *c)
{
CachedObject::ref(c);
if(!m_loading) c->notifyFinished(this);
}
void CachedFont::data( QBuffer &buffer, bool eof )
{
if(!eof) return;
buffer.close();
m_font = buffer.buffer();
// some fonts are compressed.
QIODevice* dev = KFilterDev::device(&buffer, mimetype(), false /*autoDeleteInDevice*/);
if (dev && dev->open( QIODevice::ReadOnly )) {
m_font = dev->readAll();
delete dev;
}
// handle decoding of WOFF fonts
int woffStatus = eWOFF_ok;
if (int need = WOFF::getDecodedSize( m_font.constData(), m_font.size(), &woffStatus)) {
kDebug(6040) << "***************************** Got WOFF FoNT";
m_hadError = true;
do {
if (WOFF_FAILURE(woffStatus))
break;
QByteArray wbuffer;
wbuffer.resize( need );
int len;
woffStatus = eWOFF_ok;
WOFF::decodeToBuffer(m_font.constData(), m_font.size(), wbuffer.data(), wbuffer.size(), &len, &woffStatus);
if (WOFF_FAILURE(woffStatus))
break;
wbuffer.resize(len);
m_font = wbuffer;
m_hadError = false;
} while (false);
} else if (m_font.isEmpty()) {
m_hadError = true;
}
else kDebug(6040) << "******** #################### ********************* NON WOFF font";
setSize(m_font.size());
m_loading = false;
checkNotify();
}
void CachedFont::checkNotify()
{
if(m_loading) return;
for (QHashIterator<CachedObjectClient*,CachedObjectClient*> it( m_clients ); it.hasNext();)
it.next().value()->notifyFinished(this);
}
void CachedFont::error( int /*err*/, const char* /*text*/ )
{
m_loading = false;
m_hadError = true;
checkNotify();
}
// ------------------------------------------------------------------------------------------
Request::Request(DocLoader* dl, CachedObject *_object, bool _incremental, int _priority)
{
object = _object;
object->setRequest(this);
incremental = _incremental;
priority = _priority;
m_docLoader = dl;
}
Request::~Request()
{
object->setRequest(0);
}
// ------------------------------------------------------------------------------------------
DocLoader::DocLoader(KHTMLPart* part, DocumentImpl* doc)
{
m_cachePolicy = KIO::CC_Verify;
m_expireDate = 0;
m_creationDate = time(0);
m_bautoloadImages = true;
m_showAnimations = KHTMLSettings::KAnimationEnabled;
m_part = part;
m_doc = doc;
Cache::docloader->append( this );
}
DocLoader::~DocLoader()
{
clearPreloads();
Cache::loader()->cancelRequests( this );
Cache::docloader->removeAll( this );
}
void DocLoader::setCacheCreationDate(time_t _creationDate)
{
if (_creationDate)
m_creationDate = _creationDate;
else
m_creationDate = time(0); // Now
}
void DocLoader::setExpireDate(time_t _expireDate, bool relative)
{
if (relative)
m_expireDate = _expireDate + m_creationDate; // Relative date
else
m_expireDate = _expireDate; // Absolute date
#ifdef CACHE_DEBUG
kDebug(6061) << "docLoader: " << m_expireDate - time(0) << " seconds left until reload required.\n";
#endif
}
void DocLoader::insertCachedObject( CachedObject* o ) const
{
m_docObjects.insert( o );
}
bool DocLoader::needReload(CachedObject *existing, const QString& fullURL)
{
bool reload = false;
if (m_cachePolicy == KIO::CC_Verify)
{
if (!m_reloadedURLs.contains(fullURL))
{
if (existing && existing->isExpired() && !existing->isPreloaded())
{
Cache::removeCacheEntry(existing);
m_reloadedURLs.append(fullURL);
reload = true;
}
}
}
else if ((m_cachePolicy == KIO::CC_Reload) || (m_cachePolicy == KIO::CC_Refresh))
{
if (!m_reloadedURLs.contains(fullURL))
{
if (existing && !existing->isPreloaded())
{
Cache::removeCacheEntry(existing);
}
if (!existing || !existing->isPreloaded()) {
m_reloadedURLs.append(fullURL);
reload = true;
}
}
}
return reload;
}
void DocLoader::registerPreload(CachedObject* resource)
{
if (!resource || resource->isLoaded() || m_preloads.contains(resource))
return;
resource->increasePreloadCount();
m_preloads.insert(resource);
resource->setProspectiveRequest();
#ifdef PRELOAD_DEBUG
fprintf(stderr, "PRELOADING %s\n", resource->url().string().toLatin1().data());
#endif
}
void DocLoader::clearPreloads()
{
printPreloadStats();
QSet<CachedObject*>::iterator end = m_preloads.end();
for (QSet<CachedObject*>::iterator it = m_preloads.begin(); it != end; ++it) {
CachedObject* res = *it;
res->decreasePreloadCount();
if (res->preloadResult() == CachedObject::PreloadNotReferenced || res->hadError())
Cache::removeCacheEntry(res);
}
m_preloads.clear();
}
void DocLoader::printPreloadStats()
{
#ifdef PRELOAD_DEBUG
unsigned scripts = 0;
unsigned scriptMisses = 0;
unsigned stylesheets = 0;
unsigned stylesheetMisses = 0;
unsigned images = 0;
unsigned imageMisses = 0;
QSet<CachedObject*>::iterator end = m_preloads.end();
for (QSet<CachedObject*>::iterator it = m_preloads.begin(); it != end; ++it) {
CachedObject* res = *it;
if (res->preloadResult() == CachedObject::PreloadNotReferenced)
fprintf(stderr,"!! UNREFERENCED PRELOAD %s\n", res->url().string().toLatin1().data());
else if (res->preloadResult() == CachedObject::PreloadReferencedWhileComplete)
fprintf(stderr,"HIT COMPLETE PRELOAD %s\n", res->url().string().toLatin1().data());
else if (res->preloadResult() == CachedObject::PreloadReferencedWhileLoading)
fprintf(stderr,"HIT LOADING PRELOAD %s\n", res->url().string().toLatin1().data());
if (res->type() == CachedObject::Script) {
scripts++;
if (res->preloadResult() < CachedObject::PreloadReferencedWhileLoading)
scriptMisses++;
} else if (res->type() == CachedObject::CSSStyleSheet) {
stylesheets++;
if (res->preloadResult() < CachedObject::PreloadReferencedWhileLoading)
stylesheetMisses++;
} else {
images++;
if (res->preloadResult() < CachedObject::PreloadReferencedWhileLoading)
imageMisses++;
}
}
if (scripts)
fprintf(stderr, "SCRIPTS: %d (%d hits, hit rate %d%%)\n", scripts, scripts - scriptMisses, (scripts - scriptMisses) * 100 / scripts);
if (stylesheets)
fprintf(stderr, "STYLESHEETS: %d (%d hits, hit rate %d%%)\n", stylesheets, stylesheets - stylesheetMisses, (stylesheets - stylesheetMisses) * 100 / stylesheets);
if (images)
fprintf(stderr, "IMAGES: %d (%d hits, hit rate %d%%)\n", images, images - imageMisses, (images - imageMisses) * 100 / images);
#endif
}
static inline bool securityCheckUrl(const KUrl& fullURL, KHTMLPart* part, DOM::DocumentImpl* doc,
bool doRedirectCheck, bool isImg)
{
if (!fullURL.isValid())
return false;
- if (part && part->onlyLocalReferences() && fullURL.protocol() != "file" && fullURL.protocol() != "data")
+ if (part && part->onlyLocalReferences() && fullURL.scheme() != "file" && fullURL.scheme() != "data")
return false;
if (doRedirectCheck && doc) {
- if (isImg && part && part->forcePermitLocalImages() && fullURL.protocol() == "file")
+ if (isImg && part && part->forcePermitLocalImages() && fullURL.scheme() == "file")
return true;
else
return KAuthorized::authorizeUrlAction("redirect", doc->URL(), fullURL);
}
return true;
}
#define DOCLOADER_SECCHECK_IMP(doRedirectCheck,isImg) \
KUrl fullURL(m_doc->completeURL(url.string())); \
if (!securityCheckUrl(fullURL, m_part, m_doc, doRedirectCheck, isImg)) \
return 0L;
#define DOCLOADER_SECCHECK(doRedirectCheck) DOCLOADER_SECCHECK_IMP(doRedirectCheck, false)
#define DOCLOADER_SECCHECK_IMG(doRedirectCheck) DOCLOADER_SECCHECK_IMP(doRedirectCheck, true)
bool DocLoader::willLoadMediaElement( const DOM::DOMString &url)
{
DOCLOADER_SECCHECK(true);
return true;
}
CachedImage *DocLoader::requestImage( const DOM::DOMString &url)
{
DOCLOADER_SECCHECK_IMG(true);
CachedImage* i = Cache::requestObject<CachedImage, CachedObject::Image>( this, fullURL, 0);
if (i && i->status() == CachedObject::Unknown && autoloadImages())
Cache::loader()->load(this, i, true /*incremental*/);
return i;
}
CachedCSSStyleSheet *DocLoader::requestStyleSheet( const DOM::DOMString &url, const QString& charset,
const char *accept, bool userSheet )
{
DOCLOADER_SECCHECK(!userSheet);
CachedCSSStyleSheet* s = Cache::requestObject<CachedCSSStyleSheet, CachedObject::CSSStyleSheet>( this, fullURL, accept );
if ( s && !charset.isEmpty() ) {
s->setCharsetHint( charset );
}
return s;
}
CachedScript *DocLoader::requestScript( const DOM::DOMString &url, const QString& charset)
{
DOCLOADER_SECCHECK(true);
if ( ! KHTMLGlobal::defaultHTMLSettings()->isJavaScriptEnabled(fullURL.host()) ||
KHTMLGlobal::defaultHTMLSettings()->isAdFiltered(fullURL.url()))
return 0L;
CachedScript* s = Cache::requestObject<CachedScript, CachedObject::Script>( this, fullURL, 0 );
if ( s && !charset.isEmpty() )
s->setCharset( charset );
return s;
}
CachedSound *DocLoader::requestSound( const DOM::DOMString &url )
{
DOCLOADER_SECCHECK(true);
CachedSound* s = Cache::requestObject<CachedSound, CachedObject::Sound>( this, fullURL, 0 );
return s;
}
CachedFont *DocLoader::requestFont( const DOM::DOMString &url )
{
DOCLOADER_SECCHECK(true);
CachedFont* s = Cache::requestObject<CachedFont, CachedObject::Font>( this, fullURL, 0 );
return s;
}
#undef DOCLOADER_SECCHECK
void DocLoader::setAutoloadImages( bool enable )
{
if ( enable == m_bautoloadImages )
return;
m_bautoloadImages = enable;
if ( !m_bautoloadImages ) return;
for ( QSetIterator<CachedObject*> it( m_docObjects ); it.hasNext(); )
{
CachedObject* cur = it.next();
if ( cur->type() == CachedObject::Image )
{
CachedImage *img = const_cast<CachedImage*>( static_cast<const CachedImage *>(cur) );
CachedObject::Status status = img->status();
if ( status != CachedObject::Unknown )
continue;
Cache::loader()->load(this, img, true /*incremental*/);
}
}
}
void DocLoader::setShowAnimations( KHTMLSettings::KAnimationAdvice showAnimations )
{
if ( showAnimations == m_showAnimations ) return;
m_showAnimations = showAnimations;
for ( QSetIterator<CachedObject*> it( m_docObjects ); it.hasNext(); )
{
CachedObject* cur = it.next();
if ( cur->type() == CachedObject::Image )
{
CachedImage *img = const_cast<CachedImage*>( static_cast<const CachedImage *>( cur ) );
img->setShowAnimations( m_showAnimations );
}
}
}
// ------------------------------------------------------------------------------------------
Loader::Loader() : QObject()
{
}
Loader::~Loader()
{
qDeleteAll(m_requestsLoading);
}
void Loader::load(DocLoader* dl, CachedObject *object, bool incremental, int priority)
{
Request *req = new Request(dl, object, incremental, priority);
scheduleRequest(req);
emit requestStarted( req->m_docLoader, req->object );
}
void Loader::scheduleRequest(Request* req)
{
#ifdef LOADER_DEBUG
kDebug( 6060 ) << "starting Loader url=" << req->object->url().string();
#endif
KUrl u(req->object->url().string());
KIO::TransferJob* job = KIO::get( u, KIO::NoReload, KIO::HideProgressInfo /*no GUI*/);
job->addMetaData("cache", KIO::getCacheControlString(req->object->cachePolicy()));
if (!req->object->accept().isEmpty())
job->addMetaData("accept", req->object->accept());
if ( req->m_docLoader )
{
job->addMetaData( "referrer", req->m_docLoader->doc()->URL().url() );
KHTMLPart *part = req->m_docLoader->part();
if (part )
{
job->addMetaData( "cross-domain", part->toplevelURL().url() );
if (part->widget())
job->ui()->setWindow (part->widget()->topLevelWidget());
}
}
connect( job, SIGNAL( result( KJob * ) ), this, SLOT( slotFinished( KJob * ) ) );
connect( job, SIGNAL( mimetype( KIO::Job *, const QString& ) ), this, SLOT( slotMimetype( KIO::Job *, const QString& ) ) );
connect( job, SIGNAL( data( KIO::Job*, const QByteArray &)),
SLOT( slotData( KIO::Job*, const QByteArray &)));
KIO::Scheduler::setJobPriority( job, req->priority );
m_requestsLoading.insertMulti(job, req);
}
void Loader::slotMimetype( KIO::Job *j, const QString& s )
{
Request *r = m_requestsLoading.value( j );
if (!r)
return;
CachedObject *o = r->object;
// Mozilla plain ignores any mimetype that doesn't have / in it, and handles it as "",
// including when being picky about mimetypes. Match that for better compatibility with broken servers.
if (s.contains('/'))
o->m_mimetype = s;
else
o->m_mimetype = "";
}
void Loader::slotFinished( KJob* job )
{
KIO::TransferJob* j = static_cast<KIO::TransferJob*>(job);
Request *r = m_requestsLoading.take( j );
if ( !r )
return;
if (j->error() || j->isErrorPage())
{
#ifdef LOADER_DEBUG
kDebug(6060) << "Loader::slotFinished, with error. job->error()= " << j->error() << " job->isErrorPage()=" << j->isErrorPage();
#endif
r->object->error( job->error(), job->errorText().toAscii().constData() );
emit requestFailed( r->m_docLoader, r->object );
}
else
{
QString cs = j->queryMetaData("charset");
if (!cs.isEmpty()) r->object->setCharset(cs);
r->object->data(r->m_buffer, true);
emit requestDone( r->m_docLoader, r->object );
time_t expireDate = j->queryMetaData("expire-date").toLong();
#ifdef LOADER_DEBUG
kDebug(6060) << "Loader::slotFinished, url = " << j->url().url();
#endif
r->object->setExpireDate( expireDate );
if ( r->object->type() == CachedObject::Image ) {
QString fn = j->queryMetaData("content-disposition-filename");
static_cast<CachedImage*>( r->object )->setSuggestedFilename(fn);
#ifdef IMAGE_TITLES
static_cast<CachedImage*>( r->object )->setSuggestedTitle(fn);
KTemporaryFile tf;
tf.open();
tf.write((const char*)r->m_buffer.buffer().data(), r->m_buffer.size());
tf.flush();
KFileMetaInfo kfmi(tf.fileName());
if (!kfmi.isEmpty()) {
KFileMetaInfoItem i = kfmi.item("Name");
if (i.isValid()) {
static_cast<CachedImage*>(r->object)->setSuggestedTitle(i.string());
} else {
i = kfmi.item("Title");
if (i.isValid()) {
static_cast<CachedImage*>(r->object)->setSuggestedTitle(i.string());
}
}
}
#endif
}
}
r->object->finish();
#ifdef LOADER_DEBUG
kDebug( 6060 ) << "Loader:: JOB FINISHED " << r->object << ": " << r->object->url().string();
#endif
delete r;
}
void Loader::slotData( KIO::Job*job, const QByteArray &data )
{
Request *r = m_requestsLoading[job];
if(!r) {
kDebug( 6060 ) << "got data for unknown request!";
return;
}
if ( !r->m_buffer.isOpen() )
r->m_buffer.open( QIODevice::WriteOnly );
r->m_buffer.write( data.data(), data.size() );
if(r->incremental)
r->object->data( r->m_buffer, false );
}
int Loader::numRequests( DocLoader* dl ) const
{
int res = 0;
foreach( Request* req, m_requestsLoading)
if ( req->m_docLoader == dl )
res++;
return res;
}
void Loader::cancelRequests( DocLoader* dl )
{
QMutableHashIterator<KIO::Job*,Request*> lIt( m_requestsLoading );
while ( lIt.hasNext() )
{
lIt.next();
if ( lIt.value()->m_docLoader == dl )
{
//kDebug( 6060 ) << "canceling loading request for " << lIt.current()->object->url().string();
KIO::Job *job = static_cast<KIO::Job *>( lIt.key() );
Cache::removeCacheEntry( lIt.value()->object );
delete lIt.value();
lIt.remove();
job->kill();
}
}
}
KIO::Job *Loader::jobForRequest( const DOM::DOMString &url ) const
{
QHashIterator<KIO::Job*,Request*> it( m_requestsLoading );
while (it.hasNext())
{
it.next();
if ( it.value()->object && it.value()->object->url() == url )
return static_cast<KIO::Job *>( it.key() );
}
return 0;
}
// ----------------------------------------------------------------------------
QHash<QString,CachedObject*> *Cache::cache;
QLinkedList<DocLoader*> *Cache::docloader;
QLinkedList<CachedObject*> *Cache::freeList;
Loader *Cache::m_loader;
int Cache::maxSize = DEFCACHESIZE;
int Cache::totalSizeOfLRU;
QPixmap *Cache::nullPixmap;
QPixmap *Cache::brokenPixmap;
QPixmap *Cache::blockedPixmap;
void Cache::init()
{
if ( !cache )
cache = new QHash<QString,CachedObject*>();
if ( !docloader )
docloader = new QLinkedList<DocLoader*>;
if ( !nullPixmap )
nullPixmap = new QPixmap;
if ( !brokenPixmap )
brokenPixmap = new QPixmap(KHTMLGlobal::iconLoader()->loadIcon("image-missing", KIconLoader::Desktop, 16, KIconLoader::DisabledState));
if ( !blockedPixmap ) {
blockedPixmap = new QPixmap();
blockedPixmap->loadFromData(blocked_icon_data, blocked_icon_len);
}
if ( !m_loader )
m_loader = new Loader();
if ( !freeList )
freeList = new QLinkedList<CachedObject*>;
}
void Cache::clear()
{
if ( !cache ) return;
#ifdef CACHE_DEBUG
kDebug( 6060 ) << "Cache: CLEAR!";
statistics();
#endif
#ifndef NDEBUG
bool crash = false;
foreach (CachedObject* co, *cache) {
if (!co->canDelete()) {
kDebug( 6060 ) << " Object in cache still linked to";
kDebug( 6060 ) << " -> URL: " << co->url();
kDebug( 6060 ) << " -> #clients: " << co->count();
crash = true;
// assert(co->canDelete());
}
}
foreach (CachedObject* co, *freeList) {
if (!co->canDelete()) {
kDebug( 6060 ) << " Object in freelist still linked to";
kDebug( 6060 ) << " -> URL: " << co->url();
kDebug( 6060 ) << " -> #clients: " << co->count();
crash = true;
/*
foreach (CachedObjectClient* cur, (*co->m_clients)))
{
if (dynamic_cast<RenderObject*>(cur)) {
kDebug( 6060 ) << " --> RenderObject";
} else
kDebug( 6060 ) << " --> Something else";
}*/
}
// assert(freeList->current()->canDelete());
}
assert(!crash);
#endif
qDeleteAll(*cache);
delete cache; cache = 0;
delete nullPixmap; nullPixmap = 0;
delete brokenPixmap; brokenPixmap = 0;
delete blockedPixmap; blockedPixmap = 0;
delete m_loader; m_loader = 0;
delete docloader; docloader = 0;
qDeleteAll(*freeList);
delete freeList; freeList = 0;
}
template<typename CachedObjectType, enum CachedObject::Type CachedType>
CachedObjectType* Cache::requestObject( DocLoader* dl, const KUrl& kurl, const char* accept )
{
KIO::CacheControl cachePolicy = dl->cachePolicy();
QString url = kurl.url();
CachedObject* o = cache->value(url);
if ( o && o->type() != CachedType ) {
removeCacheEntry( o );
o = 0;
}
if ( o && dl->needReload( o, url ) ) {
o = 0;
assert( !cache->contains( url ) );
}
if(!o)
{
#ifdef CACHE_DEBUG
kDebug( 6060 ) << "Cache: new: " << kurl.url();
#endif
CachedObjectType* cot = new CachedObjectType(dl, url, cachePolicy, accept);
cache->insert( url, cot );
if ( cot->allowInLRUList() )
insertInLRUList( cot );
o = cot;
}
#ifdef CACHE_DEBUG
else {
kDebug( 6060 ) << "Cache: using pending/cached: " << kurl.url();
}
#endif
dl->insertCachedObject( o );
return static_cast<CachedObjectType *>(o);
}
void Cache::preloadStyleSheet( const QString &url, const QString &stylesheet_data)
{
if (cache->contains(url))
removeCacheEntry(cache->value(url));
CachedCSSStyleSheet *stylesheet = new CachedCSSStyleSheet(url, stylesheet_data);
cache->insert( url, stylesheet );
}
void Cache::preloadScript( const QString &url, const QString &script_data)
{
if (cache->contains(url))
removeCacheEntry(cache->value(url));
CachedScript *script = new CachedScript(url, script_data);
cache->insert( url, script );
}
void Cache::flush(bool force)
{
init();
if ( force || totalSizeOfLRU > maxSize + maxSize/4) {
for ( int i = MAX_LRU_LISTS-1; i >= 0 && totalSizeOfLRU > maxSize; --i )
while ( totalSizeOfLRU > maxSize && m_LRULists[i].m_tail )
removeCacheEntry( m_LRULists[i].m_tail );
#ifdef CACHE_DEBUG
statistics();
#endif
}
QMutableLinkedListIterator<CachedObject*> it(*freeList);
while ( it.hasNext() ) {
CachedObject* p = it.next();
if ( p->canDelete() ) {
it.remove();
delete p;
}
}
}
void Cache::setSize( int bytes )
{
maxSize = bytes;
flush(true /* force */);
}
void Cache::statistics()
{
// this function is for debugging purposes only
init();
int size = 0;
int msize = 0;
int movie = 0;
int images = 0;
int scripts = 0;
int stylesheets = 0;
int sound = 0;
int fonts = 0;
foreach (CachedObject* o, *cache)
{
switch(o->type()) {
case CachedObject::Image:
{
//CachedImage *im = static_cast<CachedImage *>(o);
images++;
/*if(im->m != 0)
{
movie++;
msize += im->size();
}*/
break;
}
case CachedObject::CSSStyleSheet:
stylesheets++;
break;
case CachedObject::Script:
scripts++;
break;
case CachedObject::Sound:
sound++;
break;
case CachedObject::Font:
fonts++;
break;
}
size += o->size();
}
size /= 1024;
kDebug( 6060 ) << "------------------------- image cache statistics -------------------";
kDebug( 6060 ) << "Number of items in cache: " << cache->count();
kDebug( 6060 ) << "Number of cached images: " << images;
kDebug( 6060 ) << "Number of cached movies: " << movie;
kDebug( 6060 ) << "Number of cached scripts: " << scripts;
kDebug( 6060 ) << "Number of cached stylesheets: " << stylesheets;
kDebug( 6060 ) << "Number of cached sounds: " << sound;
kDebug( 6060 ) << "Number of cached fonts: " << fonts;
kDebug( 6060 ) << "pixmaps: allocated space approx. " << size << " kB";
kDebug( 6060 ) << "movies : allocated space approx. " << msize/1024 << " kB";
kDebug( 6060 ) << "--------------------------------------------------------------------";
}
void Cache::removeCacheEntry( CachedObject *object )
{
QString key = object->url().string();
cache->remove( key );
removeFromLRUList( object );
foreach( DocLoader* dl, *docloader )
dl->removeCachedObject( object );
if ( !object->free() ) {
Cache::freeList->append( object );
object->m_free = true;
}
}
static inline int FastLog2(unsigned int j)
{
unsigned int log2;
log2 = 0;
if (j & (j-1))
log2 += 1;
if (j >> 16)
log2 += 16, j >>= 16;
if (j >> 8)
log2 += 8, j >>= 8;
if (j >> 4)
log2 += 4, j >>= 4;
if (j >> 2)
log2 += 2, j >>= 2;
if (j >> 1)
log2 += 1;
return log2;
}
static LRUList* getLRUListFor(CachedObject* o)
{
int accessCount = o->accessCount();
int queueIndex;
if (accessCount == 0) {
queueIndex = 0;
} else {
int sizeLog = FastLog2(o->size());
queueIndex = sizeLog/o->accessCount() - 1;
if (queueIndex < 0)
queueIndex = 0;
if (queueIndex >= MAX_LRU_LISTS)
queueIndex = MAX_LRU_LISTS-1;
}
return &m_LRULists[queueIndex];
}
void Cache::removeFromLRUList(CachedObject *object)
{
CachedObject *next = object->m_next;
CachedObject *prev = object->m_prev;
LRUList* list = getLRUListFor(object);
CachedObject *&head = getLRUListFor(object)->m_head;
if (next == 0 && prev == 0 && head != object) {
return;
}
object->m_next = 0;
object->m_prev = 0;
if (next)
next->m_prev = prev;
else if (list->m_tail == object)
list->m_tail = prev;
if (prev)
prev->m_next = next;
else if (head == object)
head = next;
totalSizeOfLRU -= object->size();
}
void Cache::insertInLRUList(CachedObject *object)
{
removeFromLRUList(object);
assert( object );
assert( !object->free() );
assert( object->canDelete() );
assert( object->allowInLRUList() );
LRUList* list = getLRUListFor(object);
CachedObject *&head = list->m_head;
object->m_next = head;
if (head)
head->m_prev = object;
head = object;
if (object->m_next == 0)
list->m_tail = object;
totalSizeOfLRU += object->size();
}
// --------------------------------------
void CachedObjectClient::updatePixmap(const QRect&, CachedImage *) {}
void CachedObjectClient::setStyleSheet(const DOM::DOMString &/*url*/, const DOM::DOMString &/*sheet*/, const DOM::DOMString &/*charset*/, const DOM::DOMString &/*mimetype*/) {}
void CachedObjectClient::notifyFinished(CachedObject * /*finishedObj*/) {}
void CachedObjectClient::error(int /*err*/, const QString &/*text*/) {}
#undef CDEBUG
diff --git a/khtml/rendering/render_form.cpp b/khtml/rendering/render_form.cpp
index 8d859180ea..4e7ac24875 100644
--- a/khtml/rendering/render_form.cpp
+++ b/khtml/rendering/render_form.cpp
@@ -1,2407 +1,2407 @@
/*
* This file is part of the DOM implementation for KDE.
*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* (C) 2000 Dirk Mueller (mueller@kde.org)
* (C) 2006 Maksim Orlovich (maksim@kde.org)
* (C) 2007-2009 Germain Garand (germain@ebooksfrance.org)
* (C) 2007 Mitz Pettel (mitz@webkit.org)
* (C) 2007 Charles Samuels (charles@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 "render_form.h"
#include <kcompletionbox.h>
#include <kcursor.h>
#include <kdebug.h>
#include <kfiledialog.h>
#include <kfind.h>
#include <kfinddialog.h>
#include <kiconloader.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kreplace.h>
#include <kreplacedialog.h>
#include <dialog.h>
#include <backgroundchecker.h>
#include <kurlcompletion.h>
#include <kwindowsystem.h>
#include <kstandardaction.h>
#include <kactioncollection.h>
#include <kdeuiwidgetsproxystyle_p.h>
#include <kurl.h>
#include <kdesktopfile.h>
#include <kconfiggroup.h>
#include <kstandarddirs.h>
#include <kdialog.h>
#include <kbuildsycocaprogressdialog.h>
#include <kservicetypetrader.h>
#include <kservice.h>
#include <QAbstractItemView>
#include <QAbstractTextDocumentLayout>
#include <QStyle>
#include <QStyleOptionButton>
#include <QLabel>
#include <QStyleOptionFrameV3>
#include <QStandardItemModel>
#include <misc/helper.h>
#include <xml/dom2_eventsimpl.h>
#include <html/html_formimpl.h>
#include <html/html_miscimpl.h>
#include <assert.h>
#include <khtmlview.h>
#include <khtml_ext.h>
#include <xml/dom_docimpl.h>
#include <QMenu>
#include <QBitmap>
#include <QHBoxLayout>
#include <QVBoxLayout>
using namespace khtml;
using namespace DOM;
// ----------------- proxy style used to apply some CSS properties to native Qt widgets -----------------
struct KHTMLProxyStyle : public KdeUiProxyStyle
{
KHTMLProxyStyle(QWidget *parent)
: KdeUiProxyStyle(parent)
{
noBorder = false;
left = right = top = bottom = 0;
m_proxy = qobject_cast<KdeUiProxyStyle*>(parent->style());
setParent(parent);
}
QStyle* proxy() const { return m_proxy ? m_proxy : style(); }
QRect subElementRect(
SubElement element, const QStyleOption *option, const QWidget *widget
) const
{
QRect r = proxy()->subElementRect(element, option, widget);
switch (element) {
case QStyle::SE_PushButtonContents:
case QStyle::SE_LineEditContents:
case QStyle::SE_FrameContents:
r.adjust(left, top, -right, -bottom);
default:
break;
}
return r;
}
void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const
{
if ( noBorder && element == QStyle::CE_PushButton ) {
const QStyleOptionButton *o = qstyleoption_cast<const QStyleOptionButton *>(option);
if (o) {
QStyleOptionButton opt = *o;
opt.rect = proxy()->subElementRect(SE_PushButtonFocusRect, &opt, widget);
KdeUiProxyStyle::drawControl(CE_PushButtonLabel, &opt, painter, widget);
}
return;
}
KdeUiProxyStyle::drawControl(element,option,painter,widget);
}
QRect subControlRect(ComplexControl cc, const QStyleOptionComplex* opt,
SubControl sc, const QWidget* widget) const
{
// Make sure we give combo popup's enough room to display contents;
// Qt doesn't do this by default
if (cc == QStyle::CC_ComboBox && sc == SC_ComboBoxListBoxPopup) {
const QComboBox* cb = qobject_cast<const QComboBox*>(widget);
const QStyleOptionComboBox* cbOpt = qstyleoption_cast<const QStyleOptionComboBox*>(opt);
if (cb && cbOpt) {
QFontMetrics fm = cb->fontMetrics();
// Compute content width; Qt uses the usual +4 magic number for icon/text margin
int maxW = 0;
for (int c = 0; c < cb->count(); ++c) {
int iw = fm.width(cb->itemText(c));
if (!cb->itemIcon(c).isNull())
iw += 4 + cb->iconSize().width();
maxW = qMax(maxW, iw);
}
// Now let sizeFromContent add in extra stuff.
maxW = proxy()->sizeFromContents(QStyle::CT_ComboBox, opt, QSize(maxW, 1), widget).width();
// How much more room do we need for the text?
int extraW = maxW > cbOpt->rect.width() ? maxW - cbOpt->rect.width() : 0;
QRect r = proxy()->subControlRect(cc, opt, sc, widget);
r.setWidth(r.width() + extraW);
return r;
}
}
return proxy()->subControlRect(cc, opt, sc, widget);
}
int left, right, top, bottom;
bool noBorder;
KdeUiProxyStyle* m_proxy;
};
// ---------------------------------------------------------------------
RenderFormElement::RenderFormElement(HTMLGenericFormElementImpl *element)
: RenderWidget(element)
// , m_state(0)
, m_proxyStyle(0)
, m_exposeInternalPadding(false)
{
// init RenderObject attributes
setInline(true); // our object is Inline
}
RenderFormElement::~RenderFormElement()
{}
void RenderFormElement::setStyle(RenderStyle *style)
{
RenderWidget::setStyle(style);
setPadding();
}
void RenderFormElement::calcMinMaxWidth()
{
// Some form widgets apply the padding internally (i.e. as if they were
// some kind of inline-block). Thus we only want to expose that padding
// while layouting (so that width/height calculations are correct), and
// then pretend it does not exist, as it is beyond the replaced edge and
// thus should not affect other calculations.
m_exposeInternalPadding = true;
RenderWidget::calcMinMaxWidth();
m_exposeInternalPadding = false;
}
int RenderFormElement::paddingTop() const
{
return (!includesPadding() || m_exposeInternalPadding) ? RenderWidget::paddingTop() : 0;
}
int RenderFormElement::paddingBottom() const
{
return (!includesPadding() || m_exposeInternalPadding) ? RenderWidget::paddingBottom() : 0;
}
int RenderFormElement::paddingLeft() const
{
return (!includesPadding() || m_exposeInternalPadding) ? RenderWidget::paddingLeft() : 0;
}
int RenderFormElement::paddingRight() const
{
return (!includesPadding() || m_exposeInternalPadding) ? RenderWidget::paddingRight() : 0;
}
bool RenderFormElement::includesPadding() const
{
return true;
}
void RenderFormElement::setPadding()
{
if (!includesPadding())
return;
KHTMLProxyStyle *style = static_cast<KHTMLProxyStyle*>(getProxyStyle());
style->left = RenderWidget::paddingLeft();
style->right = RenderWidget::paddingRight();
style->top = RenderWidget::paddingTop();
style->bottom = RenderWidget::paddingBottom();
}
KdeUiProxyStyle* RenderFormElement::getProxyStyle()
{
assert(widget());
if (m_proxyStyle)
return m_proxyStyle;
m_proxyStyle = new KHTMLProxyStyle(widget());
widget()->setStyle( m_proxyStyle );
return m_proxyStyle;
}
short RenderFormElement::baselinePosition( bool f ) const
{
return RenderWidget::baselinePosition( f ) - 2 - style()->fontMetrics().descent();
}
void RenderFormElement::setQWidget( QWidget *w )
{
// Avoid dangling proxy pointer when we switch widgets.
// the widget will cleanup the proxy, as it is its kid.
m_proxyStyle = 0;
// sets the Qt Object Name for the purposes
// of setPadding() -- this is because QStyleSheet
// will propagate children of 'w' even if they are toplevel, like
// the "find" dialog or the popup menu
w->setObjectName("RenderFormElementWidget");
RenderWidget::setQWidget(w);
}
void RenderFormElement::updateFromElement()
{
m_widget->setEnabled(!element()->disabled());
// If we've disabled a focused element, clear its focus,
// so Qt doesn't do funny stuff like let one type into a disabled
// line edit.
if (element()->disabled() && element()->focused())
document()->quietResetFocus();
RenderWidget::updateFromElement();
setPadding();
}
void RenderFormElement::layout()
{
KHTMLAssert( needsLayout() );
KHTMLAssert( minMaxKnown() );
// minimum height
m_height = 0;
m_exposeInternalPadding = true;
calcWidth();
calcHeight();
m_exposeInternalPadding = false;
if ( m_widget )
resizeWidget(m_width-borderLeft()-borderRight()-paddingLeft()-paddingRight(),
m_height-borderTop()-borderBottom()-paddingTop()-paddingBottom());
setNeedsLayout(false);
}
Qt::AlignmentFlag RenderFormElement::textAlignment() const
{
switch (style()->textAlign()) {
case LEFT:
case KHTML_LEFT:
return Qt::AlignLeft;
case RIGHT:
case KHTML_RIGHT:
return Qt::AlignRight;
case CENTER:
case KHTML_CENTER:
return Qt::AlignHCenter;
case JUSTIFY:
// Just fall into the auto code for justify.
case TAAUTO:
return style()->direction() == RTL ? Qt::AlignRight : Qt::AlignLeft;
}
assert(false); // Should never be reached.
return Qt::AlignLeft;
}
// -------------------------------------------------------------------------
RenderButton::RenderButton(HTMLGenericFormElementImpl *element)
: RenderFormElement(element)
{
m_hasTextIndentHack = false;
}
short RenderButton::baselinePosition( bool f ) const
{
int ret = (height()-RenderWidget::paddingTop()-RenderWidget::paddingBottom()+1)/2;
ret += marginTop() + RenderWidget::paddingTop();
ret += ((fontMetrics( f ).ascent())/2)-1;
return ret;
}
void RenderButton::layout()
{
RenderFormElement::layout();
bool needsTextIndentHack = false;
if (!style()->width().isAuto()) {
// check if we need to simulate the effect of a popular
// button text hiding 'trick' that makes use of negative text-indent,
// which we do not support on form widgets.
int ti = style()->textIndent().minWidth( containingBlockWidth() );
if (m_widget->width() <= qAbs(ti)) {
needsTextIndentHack = true;
}
}
if (m_hasTextIndentHack != needsTextIndentHack) {
m_hasTextIndentHack = needsTextIndentHack;
updateFromElement();
}
}
void RenderButton::setStyle(RenderStyle *style)
{
RenderFormElement::setStyle(style);
if (shouldDisableNativeBorders()) {
// we paint the borders ourselves on this button,
// remove the widget's native ones.
KHTMLProxyStyle* style = static_cast<KHTMLProxyStyle*>(getProxyStyle());
style->noBorder = true;
}
}
// -------------------------------------------------------------------------------
RenderCheckBox::RenderCheckBox(HTMLInputElementImpl *element)
: RenderButton(element)
{
CheckBoxWidget* b = new CheckBoxWidget(view()->widget());
//b->setAutoMask(true);
b->setMouseTracking(true);
setQWidget(b);
// prevent firing toggled() signals on initialization
b->setChecked(element->checked());
connect(b,SIGNAL(stateChanged(int)),this,SLOT(slotStateChanged(int)));
m_ignoreStateChanged = false;
}
void RenderCheckBox::calcMinMaxWidth()
{
KHTMLAssert( !minMaxKnown() );
QCheckBox *cb = static_cast<QCheckBox *>( m_widget );
QSize s( qMin(22, qMax(14, cb->style()->pixelMetric( QStyle::PM_IndicatorWidth ))),
qMin(22, qMax(12, cb->style()->pixelMetric( QStyle::PM_IndicatorHeight ))) );
setIntrinsicWidth( s.width() );
setIntrinsicHeight( s.height() );
RenderButton::calcMinMaxWidth();
}
void RenderCheckBox::updateFromElement()
{
if (widget()->isChecked() != element()->checked()) {
m_ignoreStateChanged = true;
widget()->setChecked(element()->checked());
m_ignoreStateChanged = false;
}
RenderButton::updateFromElement();
}
void RenderCheckBox::slotStateChanged(int state)
{
if (m_ignoreStateChanged) return;
element()->setChecked(state == Qt::Checked);
}
bool RenderCheckBox::handleEvent(const DOM::EventImpl& ev)
{
switch(ev.id()) {
case EventImpl::DOMFOCUSIN_EVENT:
case EventImpl::DOMFOCUSOUT_EVENT:
case EventImpl::MOUSEMOVE_EVENT:
case EventImpl::MOUSEOUT_EVENT:
case EventImpl::MOUSEOVER_EVENT:
return RenderButton::handleEvent(ev);
default:
break;
}
return false;
}
// -------------------------------------------------------------------------------
RenderRadioButton::RenderRadioButton(HTMLInputElementImpl *element)
: RenderButton(element)
{
RadioButtonWidget* b = new RadioButtonWidget(view()->widget());
b->setMouseTracking(true);
b->setAutoExclusive(false);
setQWidget(b);
// prevent firing toggled() signals on initialization
b->setChecked(element->checked());
connect(b,SIGNAL(toggled(bool)),this,SLOT(slotToggled(bool)));
m_ignoreToggled = false;
}
void RenderRadioButton::updateFromElement()
{
m_ignoreToggled = true;
widget()->setChecked(element()->checked());
m_ignoreToggled = false;
RenderButton::updateFromElement();
}
void RenderRadioButton::calcMinMaxWidth()
{
KHTMLAssert( !minMaxKnown() );
QRadioButton *rb = static_cast<QRadioButton *>( m_widget );
QSize s( qMin(22, qMax(14, rb->style()->pixelMetric( QStyle::PM_ExclusiveIndicatorWidth ))),
qMin(20, qMax(12, rb->style()->pixelMetric( QStyle::PM_ExclusiveIndicatorHeight ))) );
setIntrinsicWidth( s.width() );
setIntrinsicHeight( s.height() );
RenderButton::calcMinMaxWidth();
}
void RenderRadioButton::slotToggled(bool /*activated*/)
{
if (m_ignoreToggled)
return;
}
bool RenderRadioButton::handleEvent(const DOM::EventImpl& ev)
{
switch(ev.id()) {
case EventImpl::DOMFOCUSIN_EVENT:
case EventImpl::DOMFOCUSOUT_EVENT:
case EventImpl::MOUSEMOVE_EVENT:
case EventImpl::MOUSEOUT_EVENT:
case EventImpl::MOUSEOVER_EVENT:
return RenderButton::handleEvent(ev);
default:
break;
}
return false;
}
// -------------------------------------------------------------------------------
static const QString &sBorderNoneSheet = KGlobal::staticQString("QPushButton{border:none}");
RenderSubmitButton::RenderSubmitButton(HTMLInputElementImpl *element)
: RenderButton(element)
{
PushButtonWidget* p = new PushButtonWidget(view()->widget());
setQWidget(p);
//p->setAutoMask(true);
p->setMouseTracking(true);
}
static inline void setStyleSheet_helper(const QString& s, QWidget* w)
{
// ### buggy Qt stylesheets mess with the widget palette.
// force it again after any stylesheet update.
QPalette pal = w->palette();
w->setStyleSheet(s);
w->setPalette(pal);
}
void RenderSubmitButton::setPadding()
{
// Proxy styling doesn't work well enough for buttons.
// Use stylesheets instead. tests/css/button-padding-top.html
assert(!m_proxyStyle);
if (!includesPadding())
return;
if (!RenderWidget::paddingLeft() && !RenderWidget::paddingRight() &&
!RenderWidget::paddingTop() && !RenderWidget::paddingBottom()) {
setStyleSheet_helper( (shouldDisableNativeBorders() ? sBorderNoneSheet : QString()), widget() );
return;
}
setStyleSheet_helper(
QString("QPushButton{padding-left:%1px; padding-right:%2px; padding-top:%3px; padding-bottom:%4px}")
.arg( RenderWidget::paddingLeft() )
.arg( RenderWidget::paddingRight() )
.arg( RenderWidget::paddingTop() )
.arg( RenderWidget::paddingBottom()) + (shouldDisableNativeBorders() ? sBorderNoneSheet : QString())
, widget());
}
void RenderSubmitButton::setStyle(RenderStyle *style)
{
// Proxy styling doesn't work well enough for buttons.
// Use stylesheets instead. tests/css/button-padding-top.html
assert(!m_proxyStyle);
RenderFormElement::setStyle(style);
QString s = widget()->styleSheet();
if (shouldDisableNativeBorders()) {
// we paint the borders ourselves on this button,
// remove the widget's native ones.
if (!s.contains(sBorderNoneSheet)) {
s.append(sBorderNoneSheet);
setStyleSheet_helper( s, widget() );
}
} else {
setStyleSheet_helper( s.remove(sBorderNoneSheet), widget() );
}
}
QString RenderSubmitButton::rawText()
{
QString value = element()->valueWithDefault().string();
value = value.trimmed();
QString raw;
for(int i = 0; i < value.length(); i++) {
raw += value[i];
if(value[i] == '&')
raw += '&';
}
return raw;
}
bool RenderSubmitButton::canHaveBorder() const
{
// ### TODO would be nice to be able to
// return style()->hasBackgroundImage() here,
// depending on a config option (e.g. 'favour usability/integration over aspect')
// so that only buttons with both a custom border
// and a background image are drawn without native styling.
//
// This would go in the same place, gui wise, as a choice of b/w default color scheme,
// versus native color scheme.
return true;
}
void RenderSubmitButton::calcMinMaxWidth()
{
KHTMLAssert( !minMaxKnown() );
QString raw = rawText();
QPushButton* pb = static_cast<QPushButton*>(m_widget);
pb->setText(raw);
pb->setFont(style()->font());
bool empty = raw.isEmpty();
if ( empty )
raw = QLatin1Char('X');
QFontMetrics fm = pb->fontMetrics();
QSize ts = fm.size( Qt::TextShowMnemonic, raw);
//Oh boy.
QStyleOptionButton butOpt;
butOpt.init(pb);
butOpt.text = raw;
QSize s = pb->style()->sizeFromContents( QStyle::CT_PushButton, &butOpt, ts, pb );
s = s.expandedTo(QApplication::globalStrut());
int margin = pb->style()->pixelMetric( QStyle::PM_ButtonMargin) +
pb->style()->pixelMetric( QStyle::PM_DefaultFrameWidth ) * 2;
int w = ts.width() + margin;
int h = s.height();
if (pb->isDefault() || pb->autoDefault()) {
int dbw = pb->style()->pixelMetric( QStyle::PM_ButtonDefaultIndicator ) * 2;
w += dbw;
}
assert(includesPadding());
int hpadding = RenderWidget::paddingLeft() + RenderWidget::paddingRight();
int vpadding = RenderWidget::paddingTop() + RenderWidget::paddingBottom();
// add 30% margins to the width (heuristics to make it look similar to IE)
// ### FIXME BASELINE: we could drop this emulation and adopt Mozilla style buttons
// (+/- padding: 0px 8px 0px 8px) - IE is most often in a separate css
// code path nowadays, so we have wider buttons than other engines.
int toAdd = (w*13/10)-w-hpadding;
toAdd = qMax(0,toAdd);
w += toAdd;
if (shouldDisableNativeBorders()) {
// we paint the borders ourselves, so let's override our height to something saner
h = ts.height();
} else {
h -= vpadding;
}
s = QSize(w,h).expandedTo(QApplication::globalStrut());
setIntrinsicWidth( s.width() );
setIntrinsicHeight( s.height() );
RenderButton::calcMinMaxWidth();
}
void RenderSubmitButton::updateFromElement()
{
QString oldText = static_cast<QPushButton*>(m_widget)->text();
QString newText = rawText();
static_cast<QPushButton*>(m_widget)->setText(newText);
if ( oldText != newText )
setNeedsLayoutAndMinMaxRecalc();
RenderFormElement::updateFromElement();
}
short RenderSubmitButton::baselinePosition( bool f ) const
{
int ret = (height()-RenderWidget::paddingTop()-RenderWidget::paddingBottom()+1)/2;
ret += marginTop() + RenderWidget::paddingTop();
ret += ((fontMetrics( f ).ascent())/2)-2;
return ret;
}
// -------------------------------------------------------------------------------
RenderResetButton::RenderResetButton(HTMLInputElementImpl *element)
: RenderSubmitButton(element)
{
}
// -------------------------------------------------------------------------------
namespace khtml {
class CompletionWidget: public KCompletionBox
{
public:
CompletionWidget( QWidget *parent = 0 ) : KCompletionBox( parent ) {}
virtual QPoint globalPositionHint() const
{
QWidget* pw = parentWidget();
KHTMLWidget* kwp = dynamic_cast<KHTMLWidget*>(pw);
if (!kwp) {
qDebug() << "CompletionWidget has no KHTMLWidget parent" << endl;
return KCompletionBox::globalPositionHint();
}
QPoint dest;
KHTMLView* v = kwp->m_kwp->rootViewPos(dest);
QPoint ret;
if (v) {
ret = v->mapToGlobal( dest + QPoint(0, pw->height()) );
int zoomLevel = v->zoomLevel();
if (zoomLevel != 100) {
ret.setX( ret.x()*zoomLevel/100 );
ret.setY( ret.y()*zoomLevel/100 );
}
}
return ret;
}
};
}
LineEditWidget::LineEditWidget(DOM::HTMLInputElementImpl* input, KHTMLView* view, QWidget* parent)
: KLineEdit(parent), m_input(input), m_view(view)
{
m_kwp->setIsRedirected( true );
setMouseTracking(true);
KActionCollection *ac = new KActionCollection(this);
m_spellAction = KStandardAction::spelling( this, SLOT( slotCheckSpelling() ), ac );
setCompletionBox( new CompletionWidget( this ) );
completionBox()->setObjectName("completion box");
completionBox()->setFont(font());
}
LineEditWidget::~LineEditWidget()
{
}
void LineEditWidget::slotCheckSpelling()
{
if ( text().isEmpty() ) {
return;
}
Sonnet::Dialog *spellDialog = new Sonnet::Dialog(new Sonnet::BackgroundChecker(this), 0);
connect(spellDialog, SIGNAL(replace( const QString&, int,const QString&)), this, SLOT(spellCheckerCorrected( const QString&, int,const QString&)));
connect(spellDialog, SIGNAL(misspelling( const QString&, int)), this, SLOT(spellCheckerMisspelling(const QString &,int)));
connect(spellDialog, SIGNAL(done(const QString&)), this, SLOT(slotSpellCheckDone(const QString&)));
connect(spellDialog, SIGNAL(cancel()), this, SLOT(spellCheckerFinished()));
connect(spellDialog, SIGNAL(stop()), this, SLOT(spellCheckerFinished()));
spellDialog->setBuffer(text());
spellDialog->show();
}
void LineEditWidget::spellCheckerMisspelling( const QString &_text, int pos)
{
highLightWord( _text.length(),pos );
}
void LineEditWidget::setFocus()
{
KLineEdit::setFocus();
end( false );
}
void LineEditWidget::highLightWord( unsigned int length, unsigned int pos )
{
setSelection ( pos, length );
}
void LineEditWidget::spellCheckerCorrected( const QString &old, int pos, const QString &corr )
{
if( old!= corr )
{
setSelection ( pos, old.length() );
insert( corr );
setSelection ( pos, corr.length() );
}
}
void LineEditWidget::spellCheckerFinished()
{
}
void LineEditWidget::slotSpellCheckDone( const QString &s )
{
if( s != text() )
setText( s );
}
namespace khtml {
/**
* @internal
*/
class WebShortcutCreator
{
public:
/**
* @short Creates a Web Shourtcut without using kdebase SearchProvider class.
* It is used by LineEditWidget.
*/
static bool createWebShortcut(QString query);
private:
static bool askData(QString &name, QString &keys);
static void createFile(QString query, QString name, QString keys);
};
bool WebShortcutCreator::createWebShortcut(QString query)
{
QString name = i18n( "New Web Shortcut" );
QString keys;
if ( askData( name, keys ) ) {
bool isOk;
do { //It's going to be checked if the keys have already been assigned
isOk = true;
QStringList keyList( keys.split( ',' ) );
KService::List providers = KServiceTypeTrader::self()->query( "SearchProvider" );
foreach ( const KService::Ptr &provider, providers ) {
if ( !isOk ) {
break;
}
foreach ( const QString &s, provider->property( "Keys" ).toStringList() ) {
if ( !isOk ) {
break;
}
foreach ( const QString &t, keys ) {
if ( !isOk ) {
break;
}
if ( s == t ) {
KMessageBox::sorry( 0, i18n( "%1 is already assigned to %2", s, provider->name() ), i18n( "Error" ) );
isOk = false;
}
}
}
}
if ( !isOk && !askData( name, keys ) ) {
return false;
}
} while ( !isOk );
createFile( query, name, keys );
return true;
} else return false;
}
void WebShortcutCreator::createFile(QString query, QString name, QString keys)
{
// SearchProvider class is part of kdebase, so the file is written as
// an standard desktop file.
QString fileName( keys );
KStandardDirs dirs;
QString dir = dirs.saveLocation( "services", "searchproviders" );
while ( KStandardDirs::exists( dir + fileName + ".desktop" ) )
fileName += '_';
KDesktopFile f( dir + fileName + ".desktop");
f.desktopGroup().writeEntry( "Keys", keys );
f.desktopGroup().writeEntry( "Type", "Service" );
f.desktopGroup().writeEntry( "ServiceTypes", "SearchProvider" );
f.desktopGroup().writeEntry( "Name", name );
f.desktopGroup().writeEntry( "Query", query );
f.sync();
KBuildSycocaProgressDialog::rebuildKSycoca( 0 );
}
bool WebShortcutCreator::askData(QString &name, QString &keys)
{
KDialog *dialog = new KDialog();
QWidget *widget = new QWidget();
dialog->setButtons( KDialog::Ok | KDialog::Cancel );
dialog->setCaption( name );
QVBoxLayout *mainLayout = new QVBoxLayout();
widget->setLayout( mainLayout );
dialog->setMainWidget( widget );
QHBoxLayout *layout = new QHBoxLayout();
mainLayout->addLayout( layout );
QLabel *label = new QLabel( i18n( "Search &provider name:" ) );
layout->addWidget( label );
QLineEdit *nameEdit = new QLineEdit( i18n( "New search provider" ) );
label->setBuddy( nameEdit );
layout->addWidget( nameEdit );
layout = new QHBoxLayout();
mainLayout->addLayout( layout );
label = new QLabel( i18n( "UR&I shortcuts:" ) );
layout->addWidget( label );
QLineEdit *keysEdit = new QLineEdit();
label->setBuddy( keysEdit );
layout->addWidget( keysEdit );
bool res = dialog->exec();
if (res) {
name = nameEdit->text();
keys = keysEdit->text();
}
delete dialog;
return res;
}
}
void LineEditWidget::slotCreateWebShortcut()
{
QString queryName( m_input->name().string() );
HTMLFormElementImpl *form = m_input->form();
KUrl url( form->action().string() );
KUrl baseUrl( m_view->part()->baseURL().url() + '?' );
if ( !url.hasPath() ) {
url.setPath( baseUrl.path() );
}
if ( !url.hasHost() ) {
- url.setProtocol( baseUrl.protocol() );
+ url.setProtocol( baseUrl.scheme() );
url.setHost( baseUrl.host() );
}
NodeImpl *node;
HTMLInputElementImpl *inputNode;
for( unsigned long i = 0; ( node = form->elements()->item( i ) ); i++ ) {
inputNode = dynamic_cast<HTMLInputElementImpl*>( node );
if ( inputNode ) {
if ( ( !inputNode->name().string().size() ) ||
(inputNode->name().string() == queryName) ) {
continue;
} else {
switch ( inputNode->inputType() ) {
case HTMLInputElementImpl::CHECKBOX:
case HTMLInputElementImpl::RADIO:
if ( !inputNode->checked() ) {
break;
}
case HTMLInputElementImpl::TEXT:
case HTMLInputElementImpl::PASSWORD:
case HTMLInputElementImpl::HIDDEN:
url.addQueryItem( inputNode->name().string(), inputNode->value().string() );
default:
break;
}
}
}
}
QString query( url.url() );
if ( !query.contains( "?" ) ) {
query += '?'; //This input is the only one of the form
}
query += '&' + queryName + "=\\{@}";
WebShortcutCreator::createWebShortcut( query );
}
void LineEditWidget::contextMenuEvent(QContextMenuEvent *e)
{
QMenu* popup = createStandardContextMenu();
if ( !popup )
return;
if (m_input->autoComplete()) {
popup->addSeparator();
QAction* act = popup->addAction( KIcon("edit-clear-history"), i18n("Clear &History"));
act->setEnabled(compObj() && !compObj()->isEmpty());
connect(act, SIGNAL(triggered()),
this, SLOT(clearHistoryActivated()));
}
if (echoMode() == QLineEdit::Normal &&
!isReadOnly()) {
popup->addSeparator();
popup->addAction( m_spellAction );
m_spellAction->setEnabled( !text().isEmpty() );
}
if ( !m_view->part()->onlyLocalReferences() ) {
popup->addSeparator();
QAction *act = popup->addAction( i18n("Create Web Shortcut") );
connect(act, SIGNAL(triggered()),
this, SLOT(slotCreateWebShortcut()));
}
emit aboutToShowContextMenu(popup);
popup->exec(e->globalPos());
delete popup;
}
void LineEditWidget::clearHistoryActivated()
{
m_view->clearCompletionHistory(m_input->name().string());
if (compObj())
compObj()->clear();
}
void LineEditWidget::paintEvent( QPaintEvent *pe )
{
KLineEdit::paintEvent( pe );
}
bool LineEditWidget::event( QEvent *e )
{
if (KLineEdit::event(e))
return true;
#if 0
if ( e->type() == QEvent::AccelAvailable && isReadOnly() ) {
QKeyEvent* ke = (QKeyEvent*) e;
if ( ke->modifiers() & Qt::ControlModifier ) {
switch ( ke->key() ) {
case Qt::Key_Left:
case Qt::Key_Right:
case Qt::Key_Up:
case Qt::Key_Down:
case Qt::Key_Home:
case Qt::Key_End:
ke->accept();
default:
break;
}
}
}
#endif
return false;
}
void LineEditWidget::mouseMoveEvent(QMouseEvent *e)
{
// hack to prevent Qt from calling setCursor on the widget
setDragEnabled(false);
KLineEdit::mouseMoveEvent(e);
setDragEnabled(true);
}
// -----------------------------------------------------------------------------
RenderLineEdit::RenderLineEdit(HTMLInputElementImpl *element)
: RenderFormElement(element), m_blockElementUpdates(false)
{
LineEditWidget *edit = new LineEditWidget(element, view(), view()->widget());
connect(edit,SIGNAL(returnPressed()), this, SLOT(slotReturnPressed()));
connect(edit,SIGNAL(textChanged(QString)),this,SLOT(slotTextChanged(QString)));
if(element->inputType() == HTMLInputElementImpl::PASSWORD)
edit->setEchoMode( QLineEdit::Password );
if ( element->autoComplete() ) {
QStringList completions = view()->formCompletionItems(element->name().string());
if (completions.count()) {
edit->completionObject()->setItems(completions);
edit->setContextMenuPolicy(Qt::NoContextMenu);
edit->completionBox()->setTabHandling( false );
}
}
setQWidget(edit);
}
short RenderLineEdit::baselinePosition( bool f ) const
{
bool hasFrame = static_cast<LineEditWidget*>(widget())->hasFrame();
int bTop = hasFrame ? 0 : borderTop();
int bBottom = hasFrame ? 0 : borderBottom();
int ret = (height()-RenderWidget::paddingTop()-RenderWidget::paddingBottom()-bTop-bBottom+1)/2;
ret += marginTop() + RenderWidget::paddingTop() + bTop;
ret += ((fontMetrics( f ).ascent())/2)-2;
return ret;
}
void RenderLineEdit::setStyle(RenderStyle* _style)
{
RenderFormElement::setStyle( _style );
widget()->setAlignment(textAlignment());
bool showClearButton = (!shouldDisableNativeBorders() && !_style->hasBackgroundImage());
if (!showClearButton && widget()->isClearButtonShown()) {
widget()->setClearButtonShown(false);
}
else if (showClearButton && !widget()->isClearButtonShown()) {
widget()->setClearButtonShown(true);
QObjectList children = widget()->children();
foreach (QObject* object, children) {
QWidget *w = qobject_cast<QWidget*>(object);
if (w && !w->isWindow() && (w->objectName() == "KLineEditButton")) {
// this duplicates KHTMLView's handleWidget but this widget
// is created on demand, so it might not be here at ChildPolished time
w->installEventFilter(view());
}
}
}
}
void RenderLineEdit::highLightWord( unsigned int length, unsigned int pos )
{
LineEditWidget* w = static_cast<LineEditWidget*>(m_widget);
if ( w )
w->highLightWord( length, pos );
}
void RenderLineEdit::slotReturnPressed()
{
// don't submit the form when return was pressed in a completion-popup
KCompletionBox *box = widget()->completionBox(false);
if ( box && box->isVisible() && box->currentRow() != -1 ) {
box->hide();
return;
}
// Emit onChange if necessary
// Works but might not be enough, dirk said he had another solution at
// hand (can't remember which) - David
handleFocusOut();
HTMLFormElementImpl* fe = element()->form();
if ( fe )
fe->submitFromKeyboard();
}
void RenderLineEdit::handleFocusOut()
{
if ( widget() && widget()->isModified() ) {
element()->onChange();
widget()->setModified( false );
}
}
void RenderLineEdit::calcMinMaxWidth()
{
KHTMLAssert( !minMaxKnown() );
const QFontMetrics &fm = style()->fontMetrics();
QSize s;
int size = element()->size();
int h = fm.lineSpacing();
int w = fm.width( 'x' ) * (size > 0 ? size+1 : 17); // "some"
QStyleOptionFrame opt;
opt.initFrom(widget());
if (static_cast<LineEditWidget*>(widget())->hasFrame())
opt.lineWidth = widget()->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, widget());
s = QSize(w, qMax(h, 14));
s = widget()->style()->sizeFromContents(QStyle::CT_LineEdit, &opt, s, widget());
s = s.expandedTo(QApplication::globalStrut());
setIntrinsicWidth( s.width() );
setIntrinsicHeight( s.height() );
RenderFormElement::calcMinMaxWidth();
}
void RenderLineEdit::updateFromElement()
{
int ml = element()->maxLength();
if ( ml < 0 )
ml = 32767;
if ( widget()->maxLength() != ml ) {
widget()->setMaxLength( ml );
}
if (element()->value().string() != widget()->text()) {
m_blockElementUpdates = true; // Do not block signals here (#188374)
int pos = widget()->cursorPosition();
widget()->setText(element()->value().string());
widget()->setCursorPosition(pos);
m_blockElementUpdates = false;
}
widget()->setReadOnly(element()->readOnly());
widget()->setClickMessage(element()->placeholder().string().remove(QLatin1Char('\n')).remove(QLatin1Char('\r')));
RenderFormElement::updateFromElement();
}
void RenderLineEdit::slotTextChanged(const QString &string)
{
if (m_blockElementUpdates) return;
// don't use setValue here!
element()->m_value = string.isNull() ? DOMString("") : string;
element()->m_unsubmittedFormChange = true;
}
void RenderLineEdit::select()
{
static_cast<LineEditWidget*>(m_widget)->selectAll();
}
long RenderLineEdit::selectionStart()
{
LineEditWidget* w = static_cast<LineEditWidget*>(m_widget);
if (w->hasSelectedText())
return w->selectionStart();
else
return w->cursorPosition();
}
long RenderLineEdit::selectionEnd()
{
LineEditWidget* w = static_cast<LineEditWidget*>(m_widget);
if (w->hasSelectedText())
return w->selectionStart() + w->selectedText().length();
else
return w->cursorPosition();
}
void RenderLineEdit::setSelectionStart(long pos)
{
LineEditWidget* w = static_cast<LineEditWidget*>(m_widget);
//See whether we have a non-empty selection now.
long end = selectionEnd();
if (end > pos)
w->setSelection(pos, end - pos);
w->setCursorPosition(pos);
}
void RenderLineEdit::setSelectionEnd(long pos)
{
LineEditWidget* w = static_cast<LineEditWidget*>(m_widget);
//See whether we have a non-empty selection now.
long start = selectionStart();
if (start < pos)
w->setSelection(start, pos - start);
w->setCursorPosition(pos);
}
void RenderLineEdit::setSelectionRange(long start, long end)
{
LineEditWidget* w = static_cast<LineEditWidget*>(m_widget);
w->setCursorPosition(end);
w->setSelection(start, end - start);
}
// ---------------------------------------------------------------------------
RenderFieldset::RenderFieldset(HTMLGenericFormElementImpl *element)
: RenderBlock(element)
{
m_intrinsicWidth = 0;
}
void RenderFieldset::calcMinMaxWidth()
{
RenderBlock::calcMinMaxWidth();
if (style()->htmlHacks()){ if (RenderObject* legend = findLegend()) {
int legendMinWidth = legend->minWidth();
Length legendMarginLeft = legend->style()->marginLeft();
Length legendMarginRight = legend->style()->marginLeft();
if (legendMarginLeft.isFixed())
legendMinWidth += legendMarginLeft.value();
if (legendMarginRight.isFixed())
legendMinWidth += legendMarginRight.value();
m_intrinsicWidth = qMax((int)m_minWidth, legendMinWidth + paddingLeft() + paddingRight() + borderLeft() + borderRight());
}}
}
RenderObject* RenderFieldset::layoutLegend(bool relayoutChildren)
{
RenderObject* legend = findLegend();
if (legend) {
if (relayoutChildren)
legend->setNeedsLayout(true);
legend->layoutIfNeeded();
int xPos = borderLeft() + paddingLeft() + legend->marginLeft();
if (style()->direction() == RTL)
xPos = m_width - paddingRight() - borderRight() - legend->width() - legend->marginRight();
int b = borderTop();
int h = legend->height();
legend->setPos(xPos, qMax((b-h)/2, 0));
m_height = qMax(b,h) + paddingTop();
}
return legend;
}
RenderObject* RenderFieldset::findLegend() const
{
for (RenderObject* legend = firstChild(); legend; legend = legend->nextSibling()) {
if (!legend->isFloatingOrPositioned() && legend->element() &&
legend->element()->id() == ID_LEGEND)
return legend;
}
return 0;
}
void RenderFieldset::paintBoxDecorations(PaintInfo& pI, int _tx, int _ty)
{
//kDebug( 6040 ) << renderName() << "::paintDecorations()";
RenderObject* legend = findLegend();
if (!legend)
return RenderBlock::paintBoxDecorations(pI, _tx, _ty);
int w = width();
int h = height() + borderTopExtra() + borderBottomExtra();
int yOff = (legend->yPos() > 0) ? 0 : (legend->height()-borderTop())/2;
int legendBottom = _ty + legend->yPos() + legend->height();
h -= yOff;
_ty += yOff - borderTopExtra();
QRect cr = QRect(_tx, _ty, w, h).intersected( pI.r );
paintOneBackground(pI.p, style()->backgroundColor(), style()->backgroundLayers(), cr, _tx, _ty, w, h);
if ( style()->hasBorder() )
paintBorderMinusLegend(pI.p, _tx, _ty, w, h, style(), legend->xPos(), legend->width(), legendBottom);
}
void RenderFieldset::paintBorderMinusLegend(QPainter *p, int _tx, int _ty, int w, int h,
const RenderStyle* style, int lx, int lw, int lb)
{
const QColor& tc = style->borderTopColor();
const QColor& bc = style->borderBottomColor();
EBorderStyle ts = style->borderTopStyle();
EBorderStyle bs = style->borderBottomStyle();
EBorderStyle ls = style->borderLeftStyle();
EBorderStyle rs = style->borderRightStyle();
bool render_t = ts > BHIDDEN;
bool render_l = ls > BHIDDEN;
bool render_r = rs > BHIDDEN;
bool render_b = bs > BHIDDEN;
int borderLeftWidth = style->borderLeftWidth();
int borderRightWidth = style->borderRightWidth();
if(render_t) {
if (lx >= borderLeftWidth)
drawBorder(p, _tx, _ty, _tx + lx, _ty + style->borderTopWidth(), BSTop, tc, style->color(), ts,
(render_l && (ls == DOTTED || ls == DASHED || ls == DOUBLE)?style->borderLeftWidth():0), 0);
if (lx + lw <= w - borderRightWidth)
drawBorder(p, _tx+lx+lw, _ty, _tx + w, _ty + style->borderTopWidth(), BSTop, tc, style->color(), ts,
0, (render_r && (rs == DOTTED || rs == DASHED || rs == DOUBLE)?style->borderRightWidth():0));
}
if(render_b)
drawBorder(p, _tx, _ty + h - style->borderBottomWidth(), _tx + w, _ty + h, BSBottom, bc, style->color(), bs,
(render_l && (ls == DOTTED || ls == DASHED || ls == DOUBLE)?style->borderLeftWidth():0),
(render_r && (rs == DOTTED || rs == DASHED || rs == DOUBLE)?style->borderRightWidth():0));
if(render_l)
{
const QColor& lc = style->borderLeftColor();
bool ignore_top =
(tc == lc) &&
(ls >= OUTSET) &&
(ts == DOTTED || ts == DASHED || ts == SOLID || ts == OUTSET);
bool ignore_bottom =
(bc == lc) &&
(ls >= OUTSET) &&
(bs == DOTTED || bs == DASHED || bs == SOLID || bs == INSET);
int startY = _ty;
if (lx < borderLeftWidth && lx + lw > 0) {
// The legend intersects the border.
ignore_top = true;
startY = lb;
}
drawBorder(p, _tx, startY, _tx + borderLeftWidth, _ty + h, BSLeft, lc, style->color(), ls,
ignore_top?0:style->borderTopWidth(),
ignore_bottom?0:style->borderBottomWidth());
}
if(render_r)
{
const QColor& rc = style->borderRightColor();
bool ignore_top =
(tc == rc) &&
(rs >= DOTTED || rs == INSET) &&
(ts == DOTTED || ts == DASHED || ts == SOLID || ts == OUTSET);
bool ignore_bottom =
(bc == rc) &&
(rs >= DOTTED || rs == INSET) &&
(bs == DOTTED || bs == DASHED || bs == SOLID || bs == INSET);
int startY = _ty;
if (lx < w && lx + lw > w - borderRightWidth) {
// The legend intersects the border.
ignore_top = true;
startY = lb;
}
drawBorder(p, _tx + w - borderRightWidth, startY, _tx + w, _ty + h, BSRight, rc, style->color(), rs,
ignore_top?0:style->borderTopWidth(),
ignore_bottom?0:style->borderBottomWidth());
}
}
void RenderFieldset::setStyle(RenderStyle* _style)
{
RenderBlock::setStyle(_style);
// WinIE renders fieldsets with display:inline like they're inline-blocks. For us,
// an inline-block is just a block element with replaced set to true and inline set
// to true. Ensure that if we ended up being inline that we set our replaced flag
// so that we're treated like an inline-block.
if (isInline())
setReplaced(true);
}
// -------------------------------------------------------------------------
RenderFileButton::RenderFileButton(HTMLInputElementImpl *element)
: RenderFormElement(element)
{
FileButtonWidget* w = new FileButtonWidget( view()->widget() );
w->setMode(KFile::File | KFile::ExistingOnly);
w->lineEdit()->setCompletionBox( new CompletionWidget(w) );
w->completionObject()->setDir(KGlobalSettings::documentPath());
connect(w->lineEdit(), SIGNAL(returnPressed()), this, SLOT(slotReturnPressed()));
connect(w->lineEdit(), SIGNAL(textChanged(const QString &)),this,SLOT(slotTextChanged(const QString &)));
connect(w, SIGNAL(urlSelected(const KUrl &)),this,SLOT(slotUrlSelected(const KUrl &)));
setQWidget(w);
m_haveFocus = false;
}
short RenderFileButton::baselinePosition( bool f ) const
{
int bTop = borderTop();
int bBottom = borderBottom();
int ret = (height()-paddingTop()-paddingBottom()-bTop-bBottom+1)/2;
ret += marginTop() + paddingTop() + bTop;
ret += ((fontMetrics( f ).ascent())/2)-2;
return ret;
}
void RenderFileButton::calcMinMaxWidth()
{
KHTMLAssert( !minMaxKnown() );
const QFontMetrics &fm = style()->fontMetrics();
int size = element()->size();
int h = fm.lineSpacing();
int w = fm.width( 'x' ) * (size > 0 ? size+1 : 17); // "some"
KLineEdit* edit = static_cast<KUrlRequester*>( m_widget )->lineEdit();
QStyleOptionFrame opt;
opt.initFrom(edit);
if (edit->hasFrame())
opt.lineWidth = edit->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, edit);
QSize s = edit->style()->sizeFromContents(QStyle::CT_LineEdit,
&opt,
QSize(w, qMax(h, 14)), edit)
.expandedTo(QApplication::globalStrut());
QSize bs = static_cast<KUrlRequester*>( m_widget )->minimumSizeHint() - edit->minimumSizeHint();
setIntrinsicWidth( s.width() + bs.width() );
setIntrinsicHeight( qMax(s.height(), bs.height()) );
RenderFormElement::calcMinMaxWidth();
}
void RenderFileButton::handleFocusOut()
{
if ( widget()->lineEdit() && widget()->lineEdit()->isModified() ) {
element()->onChange();
widget()->lineEdit()->setModified( false );
}
}
void RenderFileButton::updateFromElement()
{
KLineEdit* edit = widget()->lineEdit();
bool blocked = edit->blockSignals(true);
edit->setText(element()->value().string());
edit->blockSignals(false);
edit->setModified(blocked );
RenderFormElement::updateFromElement();
}
void RenderFileButton::slotReturnPressed()
{
// don't submit the form when return was pressed in a completion-popup
KCompletionBox* box = widget()->lineEdit()->completionBox(false);
if (box && box->isVisible() && box->currentRow() != -1) {
box->hide();
return;
}
handleFocusOut();
if (element()->form())
element()->form()->submitFromKeyboard();
}
void RenderFileButton::slotTextChanged(const QString &/*string*/)
{
element()->m_value = KUrl( widget()->url() ).pathOrUrl();
}
void RenderFileButton::slotUrlSelected(const KUrl &)
{
element()->onChange();
}
void RenderFileButton::select()
{
widget()->lineEdit()->selectAll();
}
// -------------------------------------------------------------------------
RenderLabel::RenderLabel(HTMLGenericFormElementImpl *element)
: RenderFormElement(element)
{
}
// -------------------------------------------------------------------------
RenderLegend::RenderLegend(HTMLGenericFormElementImpl *element)
: RenderBlock(element)
{
}
// -------------------------------------------------------------------------------
bool ListBoxWidget::event( QEvent * event )
{
// accept all wheel events so that they are not propagated to the view
// once either end of the list is reached.
bool ret = KListWidget::event(event);
if (event->type() == QEvent::Wheel) {
event->accept();
ret = true;
}
return ret;
}
ComboBoxWidget::ComboBoxWidget(QWidget *parent)
: KComboBox(false, parent)
{
m_kwp->setIsRedirected( true );
//setAutoMask(true);
if (view()) view()->installEventFilter(this);
setMouseTracking(true);
}
void ComboBoxWidget::showPopup()
{
QPoint p = pos();
QPoint dest( p );
QWidget* parent = parentWidget();
KHTMLView* v = m_kwp->rootViewPos(dest);
int zoomLevel = v ? v->zoomLevel() : 100;
if (zoomLevel != 100) {
if (v) {
// we need to place the popup even lower on the screen, take in count the widget is bigger
// now, so we add also the difference between the original height, and the zoomed height
dest.setY(dest.y() + (sizeHint().height() * zoomLevel / 100 - sizeHint().height()));
}
}
bool blocked = blockSignals(true);
if (v != parent) {
setParent(v);
}
move( dest );
blockSignals(blocked);
KComboBox::showPopup();
blocked = blockSignals(true);
if (v != parent) {
setParent(parent);
// undo side effect of setParent()
show();
}
move( p );
blockSignals(blocked);
}
void ComboBoxWidget::hidePopup()
{
KComboBox::hidePopup();
}
bool ComboBoxWidget::event(QEvent *e)
{
if (KComboBox::event(e))
return true;
if (e->type()==QEvent::KeyPress)
{
QKeyEvent *ke = static_cast<QKeyEvent *>(e);
switch(ke->key())
{
case Qt::Key_Return:
case Qt::Key_Enter:
showPopup();
ke->accept();
return true;
default:
return false;
}
}
return false;
}
bool ComboBoxWidget::eventFilter(QObject *dest, QEvent *e)
{
if (dest==view() && e->type()==QEvent::KeyPress)
{
QKeyEvent *ke = static_cast<QKeyEvent *>(e);
bool forward = false;
switch(ke->key())
{
case Qt::Key_Tab:
forward=true;
// fall through
case Qt::Key_Backtab:
// ugly hack. emulate popdownlistbox() (private in QComboBox)
// we re-use ke here to store the reference to the generated event.
ke = new QKeyEvent(QEvent::KeyPress, Qt::Key_Escape, Qt::NoModifier);
QApplication::sendEvent(dest,ke);
focusNextPrevChild(forward);
delete ke;
return true;
default:
return KComboBox::eventFilter(dest, e);
}
}
return KComboBox::eventFilter(dest, e);
}
void ComboBoxWidget::keyPressEvent(QKeyEvent *e)
{
// Normally, widgets are not sent Tab keys this way in the first
// place as they are handled by QWidget::event() for focus handling
// already. But we get our events via EventPropagator::sendEvent()
// directly. Ignore them so that HTMLGenericFormElementImpl::
// defaultEventHandler() can call focusNextPrev().
if (e->key() == Qt::Key_Tab || e->key() == Qt::Key_Backtab) {
e->ignore();
return;
}
KComboBox::keyPressEvent(e);
}
// -------------------------------------------------------------------------
RenderSelect::RenderSelect(HTMLSelectElementImpl *element)
: RenderFormElement(element)
{
m_ignoreSelectEvents = false;
m_multiple = element->multiple();
m_size = element->size();
m_useListBox = (m_multiple || m_size > 1);
m_selectionChanged = true;
m_optionsChanged = true;
if(m_useListBox)
setQWidget(createListBox());
else {
setQWidget(createComboBox());
getProxyStyle(); // We always need it to make sure popups are big enough
}
}
void RenderSelect::clearItemFlags(int index, Qt::ItemFlags flags)
{
if(m_useListBox) {
QListWidgetItem* item = static_cast<KListWidget*>(m_widget)->item(index);
item->setFlags(item->flags() & ~flags);
} else {
KComboBox* combo = static_cast<KComboBox*>(m_widget);
if (QStandardItemModel* model = qobject_cast<QStandardItemModel*>(combo->model())) {
QStandardItem* item = model->item(index);
item->setFlags(item->flags() & ~flags);
}
}
}
void RenderSelect::updateFromElement()
{
m_ignoreSelectEvents = true;
// change widget type
bool oldMultiple = m_multiple;
unsigned oldSize = m_size;
bool oldListbox = m_useListBox;
m_multiple = element()->multiple();
m_size = element()->size();
m_useListBox = (m_multiple || m_size > 1);
if (oldMultiple != m_multiple || oldSize != m_size) {
if (m_useListBox != oldListbox) {
// type of select has changed
if(m_useListBox)
setQWidget(createListBox());
else
setQWidget(createComboBox());
}
if (m_useListBox && oldMultiple != m_multiple) {
static_cast<KListWidget*>(m_widget)->setSelectionMode(m_multiple ?
QListWidget::ExtendedSelection
: QListWidget::SingleSelection);
}
m_selectionChanged = true;
m_optionsChanged = true;
}
// update contents listbox/combobox based on options in m_element
if ( m_optionsChanged ) {
if (element()->m_recalcListItems)
element()->recalcListItems();
const QVector<HTMLGenericFormElementImpl*> listItems = element()->listItems();
int listIndex;
if(m_useListBox)
static_cast<KListWidget*>(m_widget)->clear();
else
static_cast<KComboBox*>(m_widget)->clear();
for (listIndex = 0; listIndex < int(listItems.size()); listIndex++) {
if (listItems[listIndex]->id() == ID_OPTGROUP) {
DOMString text = listItems[listIndex]->getAttribute(ATTR_LABEL);
if (text.isNull())
text = "";
text = text.implementation()->collapseWhiteSpace(false, false);
if(m_useListBox) {
QListWidgetItem *item = new QListWidgetItem(QString(text.implementation()->s, text.implementation()->l));
static_cast<KListWidget*>(m_widget)->insertItem(listIndex,item);
} else {
static_cast<KComboBox*>(m_widget)->insertItem(listIndex, QString(text.implementation()->s, text.implementation()->l));
}
bool disabled = !listItems[listIndex]->getAttribute(ATTR_DISABLED).isNull();
if (disabled)
clearItemFlags(listIndex, Qt::ItemIsSelectable | Qt::ItemIsEnabled);
else
clearItemFlags(listIndex, Qt::ItemIsSelectable);
}
else if (listItems[listIndex]->id() == ID_OPTION) {
HTMLOptionElementImpl* optElem = static_cast<HTMLOptionElementImpl*>(listItems[listIndex]);
DOMString domText = optElem->text();
// Prefer label if set
DOMString label = optElem->getAttribute(ATTR_LABEL);
if (!label.isEmpty())
domText = label.string();
domText = domText.implementation()->collapseWhiteSpace(false, false);
QString text;
ElementImpl* parentOptGroup = optElem->parentNode()->id() == ID_OPTGROUP ?
static_cast<ElementImpl*>(optElem->parentNode()) : 0;
if (parentOptGroup) {
text = QLatin1String(" ") + domText.string();
} else {
text = domText.string();
}
if(m_useListBox)
static_cast<KListWidget*>(m_widget)->insertItem(listIndex,text);
else
static_cast<KComboBox*>(m_widget)->insertItem(listIndex, text);
bool disabled = !optElem->getAttribute(ATTR_DISABLED).isNull();
if (parentOptGroup)
disabled = disabled || !parentOptGroup->getAttribute(ATTR_DISABLED).isNull();
if (disabled)
clearItemFlags(listIndex, Qt::ItemIsSelectable | Qt::ItemIsEnabled);
}
else
KHTMLAssert(false);
m_selectionChanged = true;
}
// QComboBox caches the size hint unless you call setFont (ref: TT docu)
if(!m_useListBox) {
KComboBox *that = static_cast<KComboBox*>(m_widget);
that->setFont( that->font() );
}
setNeedsLayoutAndMinMaxRecalc();
m_optionsChanged = false;
}
// update selection
if (m_selectionChanged) {
updateSelection();
}
m_ignoreSelectEvents = false;
RenderFormElement::updateFromElement();
}
short RenderSelect::baselinePosition( bool f ) const
{
if (m_useListBox)
return RenderFormElement::baselinePosition(f);
bool hasFrame = static_cast<KComboBox*>(widget())->hasFrame();
int bTop = hasFrame ? 0 : borderTop();
int bBottom = hasFrame ? 0 : borderBottom();
int ret = (height()-RenderWidget::paddingTop()-RenderWidget::paddingBottom()-bTop-bBottom+1)/2;
ret += marginTop() + RenderWidget::paddingTop() + bTop;
ret += ((fontMetrics( f ).ascent())/2)-2;
return ret;
}
void RenderSelect::calcMinMaxWidth()
{
KHTMLAssert( !minMaxKnown() );
m_exposeInternalPadding = true;
if (m_optionsChanged)
updateFromElement();
// ### ugly HACK FIXME!!!
setMinMaxKnown();
layoutIfNeeded();
setNeedsLayoutAndMinMaxRecalc();
// ### end FIXME
RenderFormElement::calcMinMaxWidth();
m_exposeInternalPadding = false;
}
void RenderSelect::layout( )
{
KHTMLAssert(needsLayout());
KHTMLAssert(minMaxKnown());
// ### maintain selection properly between type/size changes, and work
// out how to handle multiselect->singleselect (probably just select
// first selected one)
// calculate size
if(m_useListBox) {
KListWidget* w = static_cast<KListWidget*>(m_widget);
int width = 0;
int height = 0;
QAbstractItemModel *m = w->model();
QAbstractItemDelegate *d = w->itemDelegate();
QStyleOptionViewItem so;
so.font = w->font();
for ( int rowIndex = 0 ; rowIndex < w->count() ; rowIndex++ ) {
QModelIndex mi = m->index(rowIndex, 0);
QSize s = d->sizeHint( so, mi);
width = qMax(width, s.width());
height = qMax(height, s.height());
}
if ( !height )
height = w->fontMetrics().height();
if ( !width )
width = w->fontMetrics().width( 'x' );
int size = m_size;
// check if multiple and size was not given or invalid
// Internet Exploder sets size to qMin(number of elements, 4)
// Netscape seems to simply set it to "number of elements"
// the average of that is IMHO qMin(number of elements, 10)
// so I did that ;-)
if(size < 1)
size = qMin(w->count(), 10);
QStyleOptionFrameV3 opt;
opt.initFrom(w);
opt.lineWidth = w->lineWidth();
opt.midLineWidth = w->midLineWidth();
opt.frameShape = w->frameShape();
QRect r = w->style()->subElementRect(QStyle::SE_ShapedFrameContents, &opt, w);
QRect o = opt.rect;
int hfw = (r.left()-o.left()) + (o.right()-r.right());
int vfw = (r.top()-o.top()) + (o.bottom()-r.bottom());
width += hfw + w->verticalScrollBar()->sizeHint().width();
// FIXME BASELINE: the 3 lines below could be removed.
int lhs = m_widget->style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing);
if (lhs>0)
width += lhs;
height = size*height + vfw;
assert( includesPadding() );
width -= RenderWidget::paddingLeft() + RenderWidget::paddingRight();
height -= RenderWidget::paddingTop() + RenderWidget::paddingBottom();
setIntrinsicWidth( width );
setIntrinsicHeight( height );
}
else {
QSize s(m_widget->sizeHint());
setIntrinsicWidth( s.width() );
setIntrinsicHeight( s.height() );
}
/// uuh, ignore the following line..
setNeedsLayout(true);
RenderFormElement::layout();
// and now disable the widget in case there is no <option> given
const QVector<HTMLGenericFormElementImpl*> listItems = element()->listItems();
bool foundOption = false;
for (int i = 0; i < listItems.size() && !foundOption; i++)
foundOption = (listItems[i]->id() == ID_OPTION);
m_widget->setEnabled(foundOption && ! element()->disabled());
}
void RenderSelect::slotSelected(int index) // emitted by the combobox only
{
if ( m_ignoreSelectEvents ) return;
KHTMLAssert( !m_useListBox );
const QVector<HTMLGenericFormElementImpl*> listItems = element()->listItems();
if(index >= 0 && index < int(listItems.size()))
{
bool found = ( listItems[index]->id() == ID_OPTION );
if ( !found ) {
// this one is not selectable, we need to find an option element
while ( index < listItems.size() ) {
if ( listItems[index]->id() == ID_OPTION ) {
found = true;
break;
}
++index;
}
if ( !found ) {
while ( index >= 0 ) {
if ( listItems[index]->id() == ID_OPTION ) {
found = true;
break;
}
--index;
}
}
}
if ( found ) {
bool changed = false;
for ( int i = 0; i < listItems.size(); ++i )
if ( listItems[i]->id() == ID_OPTION && i != index )
{
HTMLOptionElementImpl* opt = static_cast<HTMLOptionElementImpl*>( listItems[i] );
changed |= (opt->m_selected == true);
opt->m_selected = false;
}
HTMLOptionElementImpl* opt = static_cast<HTMLOptionElementImpl*>(listItems[index]);
changed |= (opt->m_selected == false);
opt->m_selected = true;
if ( index != static_cast<ComboBoxWidget*>( m_widget )->currentIndex() )
static_cast<ComboBoxWidget*>( m_widget )->setCurrentIndex( index );
// When selecting an optgroup item, and we move forward to we
// shouldn't emit onChange. Hence this bool, the if above doesn't do it.
if ( changed )
{
ref();
element()->onChange();
deref();
}
}
}
}
void RenderSelect::slotSelectionChanged() // emitted by the listbox only
{
if ( m_ignoreSelectEvents ) return;
// don't use listItems() here as we have to avoid recalculations - changing the
// option list will make use update options not in the way the user expects them
const QVector<HTMLGenericFormElementImpl*> listItems = element()->m_listItems;
for ( int i = 0; i < listItems.count(); i++ )
// don't use setSelected() here because it will cause us to be called
// again with updateSelection.
if ( listItems[i]->id() == ID_OPTION )
static_cast<HTMLOptionElementImpl*>( listItems[i] )
->m_selected = static_cast<KListWidget*>( m_widget )->item(i)->isSelected();
ref();
element()->onChange();
deref();
}
void RenderSelect::setOptionsChanged(bool _optionsChanged)
{
m_optionsChanged = _optionsChanged;
}
void RenderSelect::setPadding()
{
if (m_size > 1 || m_multiple)
RenderFormElement::setPadding();
}
ListBoxWidget* RenderSelect::createListBox()
{
ListBoxWidget *lb = new ListBoxWidget(view()->widget());
lb->setSelectionMode(m_multiple ? QListWidget::ExtendedSelection : QListWidget::SingleSelection);
connect( lb, SIGNAL( itemSelectionChanged() ), this, SLOT( slotSelectionChanged() ) );
m_ignoreSelectEvents = false;
lb->setMouseTracking(true);
return lb;
}
ComboBoxWidget *RenderSelect::createComboBox()
{
ComboBoxWidget *cb = new ComboBoxWidget(view()->widget());
connect(cb, SIGNAL(activated(int)), this, SLOT(slotSelected(int)));
return cb;
}
void RenderSelect::updateSelection()
{
const QVector<HTMLGenericFormElementImpl*> listItems = element()->listItems();
int i;
if (m_useListBox) {
// if multi-select, we select only the new selected index
KListWidget *listBox = static_cast<KListWidget*>(m_widget);
for (i = 0; i < int(listItems.size()); i++)
listBox->item(i)->setSelected(listItems[i]->id() == ID_OPTION &&
static_cast<HTMLOptionElementImpl*>(listItems[i])->selectedBit());
}
else {
bool found = false;
int firstOption = i = listItems.size();
while (i--)
if (listItems[i]->id() == ID_OPTION) {
if (found)
static_cast<HTMLOptionElementImpl*>(listItems[i])->m_selected = false;
else if (static_cast<HTMLOptionElementImpl*>(listItems[i])->selectedBit()) {
static_cast<KComboBox*>( m_widget )->setCurrentIndex(i);
found = true;
}
firstOption = i;
}
if (!found && firstOption != listItems.size()) {
// select first option (IE7/Gecko behaviour)
static_cast<HTMLOptionElementImpl*>(listItems[firstOption])->m_selected = true;
static_cast<KComboBox*>( m_widget )->setCurrentIndex(firstOption);
}
}
m_selectionChanged = false;
}
// -------------------------------------------------------------------------
TextAreaWidget::TextAreaWidget(int wrap, QWidget* parent)
: KTextEdit(parent)
{
m_kwp->setIsRedirected( true );
if(wrap != DOM::HTMLTextAreaElementImpl::ta_NoWrap)
setLineWrapMode(QTextEdit::WidgetWidth);
else
setLineWrapMode(QTextEdit::NoWrap);
setHorizontalScrollBarPolicy( Qt::ScrollBarAsNeeded );
setVerticalScrollBarPolicy( Qt::ScrollBarAsNeeded );
KCursor::setAutoHideCursor(viewport(), true);
setAcceptRichText (false);
setMouseTracking(true);
}
void TextAreaWidget::scrollContentsBy( int dx, int dy )
{
KTextEdit::scrollContentsBy(dx, dy);
update();
}
TextAreaWidget::~TextAreaWidget()
{
}
bool TextAreaWidget::event( QEvent *e )
{
#if 0
if ( e->type() == QEvent::AccelAvailable && isReadOnly() ) {
QKeyEvent* ke = (QKeyEvent*) e;
if ( ke->modifiers() & Qt::ControlModifier ) {
switch ( ke->key() ) {
case Qt::Key_Left:
case Qt::Key_Right:
case Qt::Key_Up:
case Qt::Key_Down:
case Qt::Key_Home:
case Qt::Key_End:
ke->accept();
default:
break;
}
}
}
#endif
// accept all wheel events so that they are not propagated to the view
// once either end of the widget is reached.
bool ret = KTextEdit::event(e);
if (e->type() == QEvent::Wheel) {
e->accept();
ret = true;
}
return ret;
}
void TextAreaWidget::keyPressEvent(QKeyEvent *e)
{
// The ComboBoxWidget::keyPressEvent() comment about having to
// deal with events coming from EventPropagator::sendEvent()
// directly applies here, too.
if ((e->key() == Qt::Key_Tab || e->key() == Qt::Key_Backtab) &&
tabChangesFocus()) {
e->ignore();
return;
}
KTextEdit::keyPressEvent(e);
}
// -------------------------------------------------------------------------
RenderTextArea::RenderTextArea(HTMLTextAreaElementImpl *element)
: RenderFormElement(element)
{
scrollbarsStyled = false;
TextAreaWidget *edit = new TextAreaWidget(element->wrap(), view());
setQWidget(edit);
const KHTMLSettings *settings = view()->part()->settings();
edit->setCheckSpellingEnabled( settings->autoSpellCheck() );
edit->setTabChangesFocus( ! settings->allowTabulation() );
connect(edit,SIGNAL(textChanged()),this,SLOT(slotTextChanged()));
setText(element->value().string());
}
RenderTextArea::~RenderTextArea()
{
element()->m_value = text();
}
void RenderTextArea::handleFocusOut()
{
TextAreaWidget* w = static_cast<TextAreaWidget*>(m_widget);
if ( w && element()->m_changed ) {
element()->m_changed = false;
element()->onChange();
}
}
void RenderTextArea::calcMinMaxWidth()
{
KHTMLAssert( !minMaxKnown() );
TextAreaWidget* w = static_cast<TextAreaWidget*>(m_widget);
const QFontMetrics &m = style()->fontMetrics();
w->setTabStopWidth(8 * m.width(" "));
int lvs = qMax(0, w->style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing));
int lhs = qMax(0, w->style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
int llm = qMax(0, w->style()->pixelMetric(QStyle::PM_LayoutLeftMargin));
int lrm = qMax(0, w->style()->pixelMetric(QStyle::PM_LayoutRightMargin));
int lbm = qMax(0, w->style()->pixelMetric(QStyle::PM_LayoutBottomMargin));
int ltm = qMax(0, w->style()->pixelMetric(QStyle::PM_LayoutTopMargin));
QStyleOptionFrameV3 opt;
opt.initFrom(w);
opt.lineWidth = w->lineWidth();
opt.midLineWidth = w->midLineWidth();
opt.frameShape = w->frameShape();
QRect r = w->style()->subElementRect(QStyle::SE_ShapedFrameContents, &opt, w);
QRect o = opt.rect;
int hfw = (r.left()-o.left()) + (o.right()-r.right());
int vfw = (r.top()-o.top()) + (o.bottom()-r.bottom());
QSize size( qMax(element()->cols(), 1L)*m.width('x') + hfw + llm+lrm +
w->verticalScrollBar()->sizeHint().width()+lhs,
qMax(element()->rows(), 1L)*m.lineSpacing() + vfw + lbm+ltm +
(w->lineWrapMode() == QTextEdit::NoWrap ?
w->horizontalScrollBar()->sizeHint().height()+lvs : 0)
);
assert( includesPadding() );
size.rwidth() -= RenderWidget::paddingLeft() + RenderWidget::paddingRight();
size.rheight() -= RenderWidget::paddingTop() + RenderWidget::paddingBottom();
setIntrinsicWidth( size.width() );
setIntrinsicHeight( size.height() );
RenderFormElement::calcMinMaxWidth();
}
void RenderTextArea::setStyle(RenderStyle* _style)
{
bool unsubmittedFormChange = element()->m_unsubmittedFormChange;
RenderFormElement::setStyle(_style);
bool blocked = widget()->blockSignals(true);
widget()->setAlignment(textAlignment());
widget()->blockSignals(blocked);
scrollbarsStyled = false;
element()->m_unsubmittedFormChange = unsubmittedFormChange;
TextAreaWidget* w = static_cast<TextAreaWidget*>(m_widget);
if (style()->overflowX() == OSCROLL)
w->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOn );
else if (style()->overflowX() == OHIDDEN)
w->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
else
w->setHorizontalScrollBarPolicy( Qt::ScrollBarAsNeeded );
if (style()->overflowY() == OSCROLL)
w->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOn );
else if(style()->overflowY() == OHIDDEN)
w->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
else
w->setVerticalScrollBarPolicy( Qt::ScrollBarAsNeeded );
}
void RenderTextArea::layout()
{
KHTMLAssert( needsLayout() );
RenderFormElement::layout();
TextAreaWidget* w = static_cast<TextAreaWidget*>(m_widget);
if (!scrollbarsStyled) {
w->horizontalScrollBar()->setPalette(style()->palette());
w->verticalScrollBar()->setPalette(style()->palette());
scrollbarsStyled=true;
}
}
short RenderTextArea::scrollWidth() const
{
return RenderObject::scrollWidth();
}
int RenderTextArea::scrollHeight() const
{
TextAreaWidget* w = static_cast<TextAreaWidget*>(m_widget);
int contentHeight = qRound(w->document()->size().height());
return qMax(contentHeight, RenderObject::clientHeight());
}
void RenderTextArea::setText(const QString& newText)
{
TextAreaWidget* w = static_cast<TextAreaWidget*>(m_widget);
// When this is called, m_value in the element must have just
// been set to new value --- see if we have any work to do
if ( newText != text() ) {
bool blocked = w->blockSignals(true);
QTextCursor tc = w->textCursor();
bool atEnd = tc.atEnd();
bool atStart = tc.atStart();
int cx = w->horizontalScrollBar()->value();
int cy = w->verticalScrollBar()->value();
QString oldText = w->toPlainText();
int ex = 0;
int otl = oldText.length();
if (otl && newText.length() > otl) {
while (ex < otl && newText[ex] == oldText[ex])
++ex;
QTextCursor tc(w->document());
tc.setPosition( ex, QTextCursor::MoveAnchor );
tc.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
tc.insertText(newText.right( newText.length()-ex ));
} else {
w->setPlainText( newText );
}
w->setTextCursor(tc);
if (atEnd)
tc.movePosition(QTextCursor::End, QTextCursor::MoveAnchor);
else if (atStart)
tc.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor);
w->horizontalScrollBar()->setValue( cx );
w->verticalScrollBar()->setValue( cy );
w->blockSignals(blocked);
}
}
void RenderTextArea::updateFromElement()
{
TextAreaWidget* w = static_cast<TextAreaWidget*>(m_widget);
w->setReadOnly(element()->readOnly());
w->setClickMessage(element()->placeholder().string());
RenderFormElement::updateFromElement();
}
QString RenderTextArea::text()
{
// ### We may want to cache this when physical, since the DOM no longer caches,
// but seeing how text() has always been called on textChanged(), it's probably not needed
QString txt;
TextAreaWidget* w = static_cast<TextAreaWidget*>(m_widget);
#ifdef __GNUC__
#warning "Physical wrap mode needs testing (also in ::selection*)"
#endif
if (element()->wrap() == DOM::HTMLTextAreaElementImpl::ta_Physical) {
QTextCursor tc(w->document());
while (!tc.atEnd()) {
tc.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
txt += tc.selectedText();
if (tc.movePosition(QTextCursor::Right)) {
txt += QLatin1String("\n");
tc.movePosition(QTextCursor::StartOfLine);
} else {
break;
}
}
}
else
txt = w->toPlainText();
return txt;
}
void RenderTextArea::highLightWord( unsigned int length, unsigned int pos )
{
TextAreaWidget* w = static_cast<TextAreaWidget*>(m_widget);
if ( w )
w->highlightWord( length, pos );
}
void RenderTextArea::slotTextChanged()
{
element()->m_changed = true;
if (element()->m_value != text())
element()->m_unsubmittedFormChange = true;
}
void RenderTextArea::select()
{
static_cast<TextAreaWidget *>(m_widget)->selectAll();
}
long RenderTextArea::selectionStart()
{
TextAreaWidget* w = static_cast<TextAreaWidget*>(m_widget);
return w->textCursor().selectionStart();
}
long RenderTextArea::selectionEnd()
{
TextAreaWidget* w = static_cast<TextAreaWidget*>(m_widget);
return w->textCursor().selectionEnd();
}
static void setPhysWrapPos(QTextCursor& otc, bool selStart, int idx)
{
QTextCursor tc = otc;
tc.setPosition(0);
tc.movePosition(QTextCursor::EndOfLine);
while (!tc.atEnd()) {
if (tc.movePosition(QTextCursor::Down) && tc.position()< idx)
--idx;
if (tc.position() >= idx)
break;
}
otc.setPosition(idx, selStart ? QTextCursor::MoveAnchor : QTextCursor::KeepAnchor );
}
void RenderTextArea::setSelectionStart(long offset) {
TextAreaWidget* w = static_cast<TextAreaWidget*>(m_widget);
QTextCursor tc = w->textCursor();
if (element()->wrap() == DOM::HTMLTextAreaElementImpl::ta_Physical)
setPhysWrapPos(tc, true /*selStart*/, offset);
else
tc.setPosition(offset);
w->setTextCursor(tc);
}
void RenderTextArea::setSelectionEnd(long offset) {
TextAreaWidget* w = static_cast<TextAreaWidget*>(m_widget);
QTextCursor tc = w->textCursor();
if (element()->wrap() == DOM::HTMLTextAreaElementImpl::ta_Physical)
setPhysWrapPos(tc, false /*selStart*/, offset);
else
tc.setPosition(offset, QTextCursor::KeepAnchor);
w->setTextCursor(tc);
}
void RenderTextArea::setSelectionRange(long start, long end) {
setSelectionStart(start);
setSelectionEnd(end);
}
// ---------------------------------------------------------------------------
// kate: indent-width 4; replace-tabs on; tab-width 4; space-indent on;
diff --git a/khtml/xml/dom_docimpl.cpp b/khtml/xml/dom_docimpl.cpp
index 942fe8c08b..9e2c6e3617 100644
--- a/khtml/xml/dom_docimpl.cpp
+++ b/khtml/xml/dom_docimpl.cpp
@@ -1,3254 +1,3254 @@
/**
* This file is part of the DOM implementation for KDE.
*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* (C) 2001 Dirk Mueller (mueller@kde.org)
* (C) 2002-2006 Apple Computer, Inc.
* (C) 2006 Allan Sandfeld Jensen (kde@carewolf.com)
* (C) 2005 Frerich Raabe <raabe@kde.org>
* (C) 2010 Maksim Orlovich <maksim@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 "dom_docimpl.h"
#include <dom/dom_exception.h>
#include "dom_textimpl.h"
#include "dom_xmlimpl.h"
#include "dom2_rangeimpl.h"
#include "dom2_eventsimpl.h"
#include "xml_tokenizer.h"
#include <html/htmltokenizer.h>
#include "dom_restyler.h"
#include <css/csshelper.h>
#include <css/cssstyleselector.h>
#include <css/css_stylesheetimpl.h>
#include <misc/helper.h>
#include <misc/seed.h>
#include <misc/loader.h>
#include <ecma/kjs_proxy.h>
#include <ecma/kjs_binding.h>
#include <QtCore/QStack>
//Added by qt3to4:
#include <QTimerEvent>
#include <QtCore/QList>
#include <kdebug.h>
#include <klocale.h>
#include <rendering/counter_tree.h>
#include <rendering/render_canvas.h>
#include <rendering/render_replaced.h>
#include <rendering/render_arena.h>
#include <rendering/render_layer.h>
#include <rendering/render_frames.h>
#include <rendering/render_image.h>
#include <khtmlview.h>
#include <khtml_part.h>
#include <kauthorized.h>
#include <kglobalsettings.h>
#include <kstringhandler.h>
#include <kdatetime.h>
#include <khtml_settings.h>
#include <khtmlpart_p.h>
#include <xml/dom3_xpathimpl.h>
#include <html/html_baseimpl.h>
#include <html/html_blockimpl.h>
#include <html/html_canvasimpl.h>
#include <html/html_documentimpl.h>
#include <html/html_formimpl.h>
#include <html/html_headimpl.h>
#include <html/html_imageimpl.h>
#include <html/html_listimpl.h>
#include <html/html_miscimpl.h>
#include <html/html_tableimpl.h>
#include <html/html_objectimpl.h>
#include <html/HTMLAudioElement.h>
#include <html/HTMLVideoElement.h>
#include <html/HTMLSourceElement.h>
#include <editing/jsediting.h>
#include <ecma/kjs_window.h>
// SVG (WebCore)
#include <svg/SVGElement.h>
#include <svg/SVGSVGElement.h>
#include <svg/SVGNames.h>
#include <svg/SVGDocumentExtensions.h>
#include <svg/SVGRectElement.h>
#include <svg/SVGDocument.h>
#include <svg/SVGCircleElement.h>
#include <svg/SVGStyleElement.h>
#include <svg/SVGEllipseElement.h>
#include <svg/SVGPolygonElement.h>
#include <svg/SVGPolylineElement.h>
#include <svg/SVGPathElement.h>
#include <svg/SVGDefsElement.h>
#include <svg/SVGLinearGradientElement.h>
#include <svg/SVGRadialGradientElement.h>
#include <svg/SVGStopElement.h>
#include <svg/SVGClipPathElement.h>
#include <svg/SVGGElement.h>
#include <svg/SVGUseElement.h>
#include <svg/SVGLineElement.h>
#include <svg/SVGTextElement.h>
#include <svg/SVGAElement.h>
#include <svg/SVGScriptElement.h>
#include <svg/SVGDescElement.h>
#include <svg/SVGTitleElement.h>
#include <svg/SVGTextPathElement.h>
#include <svg/SVGTSpanElement.h>
#include <svg/SVGHKernElement.h>
#include <svg/SVGAltGlyphElement.h>
#include <svg/SVGFontElement.h>
#include <kio/job.h>
#include <stdlib.h>
#include <limits.h>
template class QStack<DOM::NodeImpl*>;
using namespace DOM;
using namespace khtml;
// ------------------------------------------------------------------------
DOMImplementationImpl::DOMImplementationImpl()
{
}
DOMImplementationImpl::~DOMImplementationImpl()
{
}
bool DOMImplementationImpl::hasFeature ( const DOMString &feature, const DOMString &version )
{
// ### update when we (fully) support the relevant features
QString lower = feature.string().toLower();
if ((lower == "html" || lower == "xml") &&
(version.isEmpty() || version == "1.0" || version == "2.0"))
return true;
// ## Do we support Core Level 3 ?
if ((lower == "core" ) &&
(version.isEmpty() || version == "2.0"))
return true;
if ((lower == "traversal") &&
(version.isEmpty() || version == "2.0"))
return true;
if ((lower == "css") &&
(version.isEmpty() || version == "2.0"))
return true;
if ((lower == "events" || lower == "uievents" ||
lower == "mouseevents" || lower == "mutationevents" ||
lower == "htmlevents" || lower == "textevents" ) &&
(version.isEmpty() || version == "2.0" || version == "3.0"))
return true;
if (lower == "selectors-api" && version == "1.0")
return true;
return false;
}
DocumentTypeImpl *DOMImplementationImpl::createDocumentType( const DOMString &qualifiedName, const DOMString &publicId,
const DOMString &systemId, int &exceptioncode )
{
// Not mentioned in spec: throw NAMESPACE_ERR if no qualifiedName supplied
if (qualifiedName.isNull()) {
exceptioncode = DOMException::NAMESPACE_ERR;
return 0;
}
// INVALID_CHARACTER_ERR: Raised if the specified qualified name contains an illegal character.
if (!Element::khtmlValidQualifiedName(qualifiedName)) {
exceptioncode = DOMException::INVALID_CHARACTER_ERR;
return 0;
}
// NAMESPACE_ERR: Raised if the qualifiedName is malformed.
// Added special case for the empty string, which seems to be a common pre-DOM2 misuse
if (!qualifiedName.isEmpty() && Element::khtmlMalformedQualifiedName(qualifiedName)) {
exceptioncode = DOMException::NAMESPACE_ERR;
return 0;
}
return new DocumentTypeImpl(this, 0, qualifiedName, publicId, systemId);
}
DocumentImpl *DOMImplementationImpl::createDocument( const DOMString &namespaceURI, const DOMString &qualifiedName,
DocumentTypeImpl* dtype,
KHTMLView* v,
int &exceptioncode )
{
exceptioncode = 0;
if (!checkQualifiedName(qualifiedName, namespaceURI, 0, true/*nameCanBeNull*/,
true /*nameCanBeEmpty, see #61650*/, &exceptioncode) )
return 0;
// WRONG_DOCUMENT_ERR: Raised if doctype has already been used with a different document or was
// created from a different implementation.
// We elide the "different implementation" case here, since we're not doing interop
// of different implementations, and different impl objects exist only for
// isolation reasons
if (dtype && dtype->document()) {
exceptioncode = DOMException::WRONG_DOCUMENT_ERR;
return 0;
}
// ### view can be 0 which can cause problems
DocumentImpl* doc;
if (namespaceURI == XHTML_NAMESPACE)
doc = new HTMLDocumentImpl(v);
else
doc = new DocumentImpl(v);
if (dtype) {
dtype->setDocument(doc);
doc->appendChild(dtype,exceptioncode);
}
// the document must be created empty if all parameters are null
// (or empty for qName/nsURI as a tolerance) - see DOM 3 Core.
if (dtype || !qualifiedName.isEmpty() || !namespaceURI.isEmpty()) {
ElementImpl *element = doc->createElementNS(namespaceURI,qualifiedName);
doc->appendChild(element,exceptioncode);
if (exceptioncode) {
delete element;
delete doc;
return 0;
}
}
return doc;
}
CSSStyleSheetImpl *DOMImplementationImpl::createCSSStyleSheet(DOMStringImpl* title, DOMStringImpl *media,
int &/*exceptioncode*/)
{
// ### TODO : media could have wrong syntax, in which case we should
// generate an exception.
CSSStyleSheetImpl *parent = 0L;
CSSStyleSheetImpl *sheet = new CSSStyleSheetImpl(parent, DOMString());
sheet->setMedia(new MediaListImpl(sheet, media, true /*fallbackToDescriptor*/));
sheet->setTitle(DOMString(title));
return sheet;
}
DocumentImpl *DOMImplementationImpl::createDocument( KHTMLView *v )
{
DocumentImpl* doc = new DocumentImpl(v);
return doc;
}
XMLDocumentImpl *DOMImplementationImpl::createXMLDocument( KHTMLView *v )
{
XMLDocumentImpl* doc = new XMLDocumentImpl(v);
return doc;
}
HTMLDocumentImpl *DOMImplementationImpl::createHTMLDocument( KHTMLView *v )
{
HTMLDocumentImpl* doc = new HTMLDocumentImpl(v);
return doc;
}
// create SVG document
WebCore::SVGDocument *DOMImplementationImpl::createSVGDocument( KHTMLView *v )
{
WebCore::SVGDocument* doc = new WebCore::SVGDocument(v);
return doc;
}
HTMLDocumentImpl* DOMImplementationImpl::createHTMLDocument( const DOMString& title )
{
HTMLDocumentImpl* r = createHTMLDocument( 0 /* ### create a view otherwise it doesn't work */);
r->open();
r->write(QLatin1String("<HTML><HEAD><TITLE>") + title.string() +
QLatin1String("</TITLE></HEAD>"));
return r;
}
// ------------------------------------------------------------------------
ElementMappingCache::ElementMappingCache():m_dict()
{
}
ElementMappingCache::~ElementMappingCache()
{
qDeleteAll( m_dict );
}
void ElementMappingCache::add(const DOMString& id, ElementImpl* nd)
{
if (id.isEmpty()) return;
ItemInfo* info = m_dict.value(id);
if (info)
{
info->ref++;
info->nd = 0; //Now ambigous
}
else
{
ItemInfo* info = new ItemInfo();
info->ref = 1;
info->nd = nd;
m_dict.insert(id, info);
}
}
void ElementMappingCache::set(const DOMString& id, ElementImpl* nd)
{
if (id.isEmpty()) return;
assert(m_dict.contains(id));
ItemInfo* info = m_dict.value(id);
info->nd = nd;
}
void ElementMappingCache::remove(const DOMString& id, ElementImpl* nd)
{
if (id.isEmpty()) return;
assert(m_dict.contains(id));
ItemInfo* info = m_dict.value(id);
info->ref--;
if (info->ref == 0)
{
m_dict.take(id);
delete info;
}
else
{
if (info->nd == nd)
info->nd = 0;
}
}
bool ElementMappingCache::contains(const DOMString& id)
{
if (id.isEmpty()) return false;
return m_dict.contains(id);
}
ElementMappingCache::ItemInfo* ElementMappingCache::get(const DOMString& id)
{
if (id.isEmpty()) return 0;
return m_dict.value(id);
}
typedef QList<DocumentImpl*> ChangedDocuments ;
K_GLOBAL_STATIC(ChangedDocuments, s_changedDocuments)
// KHTMLView might be 0
DocumentImpl::DocumentImpl(KHTMLView *v)
: NodeBaseImpl( 0 ), m_svgExtensions(0), m_counterDict(),
m_imageLoadEventTimer(0)
{
m_document.resetSkippingRef(this); //Make document return us..
m_selfOnlyRefCount = 0;
m_paintDevice = 0;
//m_decoderMibEnum = 0;
m_textColor = Qt::black;
m_view = v;
m_renderArena.reset();
KHTMLGlobal::registerDocumentImpl(this);
if ( v ) {
m_docLoader = new DocLoader(v->part(), this );
setPaintDevice( m_view );
}
else
m_docLoader = new DocLoader( 0, this );
visuallyOrdered = false;
m_bParsing = false;
m_docChanged = false;
m_elemSheet = 0;
m_tokenizer = 0;
m_decoder = 0;
m_doctype = 0;
m_implementation = 0;
pMode = Strict;
hMode = XHtml;
m_htmlCompat = false;
m_textColor = "#000000";
m_focusNode = 0;
m_hoverNode = 0;
m_activeNode = 0;
m_defaultView = new AbstractViewImpl(this);
m_defaultView->ref();
m_listenerTypes = 0;
m_styleSheets = new StyleSheetListImpl(this);
m_styleSheets->ref();
m_addedStyleSheets = 0;
m_inDocument = true;
m_styleSelectorDirty = false;
m_styleSelector = 0;
m_styleSheetListDirty = true;
m_inStyleRecalc = false;
m_pendingStylesheets = 0;
m_ignorePendingStylesheets = false;
m_async = true;
m_hadLoadError = false;
m_docLoading = false;
m_bVariableLength = false;
m_inSyncLoad = 0;
m_loadingXMLDoc = 0;
m_documentElement = 0;
m_cssTarget = 0;
m_jsEditor = 0;
m_dynamicDomRestyler = new khtml::DynamicDomRestyler();
m_stateRestorePos = 0;
m_windowEventTarget = new WindowEventTargetImpl(this);
m_windowEventTarget->ref();
for (int c = 0; c < NumTreeVersions; ++c)
m_domTreeVersions[c] = 0;
}
void DocumentImpl::removedLastRef()
{
if (m_selfOnlyRefCount) {
/* In this case, the only references to us are from children,
so we have a cycle. We'll try to break it by disconnecting the
children from us; this sucks/is wrong, but it's pretty much
the best we can do without tracing.
Of course, if dumping the children causes the refcount from them to
drop to 0 we can get killed right here, so better hold
a temporary reference, too
*/
DocPtr<DocumentImpl> guard(this);
// we must make sure not to be retaining any of our children through
// these extra pointers or we will create a reference cycle
if (m_doctype) {
m_doctype->deref();
m_doctype = 0;
}
if (m_cssTarget) {
m_cssTarget->deref();
m_cssTarget = 0;
}
if (m_focusNode) {
m_focusNode->deref();
m_focusNode = 0;
}
if (m_hoverNode) {
m_hoverNode->deref();
m_hoverNode = 0;
}
if (m_activeNode) {
m_activeNode->deref();
m_activeNode = 0;
}
if (m_documentElement) {
m_documentElement->deref();
m_documentElement = 0;
}
removeChildren();
delete m_tokenizer;
m_tokenizer = 0;
} else {
delete this;
}
}
DocumentImpl::~DocumentImpl()
{
//Important: if you need to remove stuff here,
//you may also have to fix removedLastRef() above - M.O.
assert( !m_render );
QHashIterator<long,DynamicNodeListImpl::Cache*> it(m_nodeListCache);
while (it.hasNext())
it.next().value()->deref();
if (m_loadingXMLDoc)
m_loadingXMLDoc->deref(this);
if (s_changedDocuments && m_docChanged)
s_changedDocuments->removeAll(this);
delete m_tokenizer;
m_document.resetSkippingRef(0);
delete m_styleSelector;
delete m_docLoader;
if (m_elemSheet ) m_elemSheet->deref();
if (m_doctype)
m_doctype->deref();
if (m_implementation)
m_implementation->deref();
delete m_dynamicDomRestyler;
delete m_jsEditor;
m_defaultView->deref();
m_styleSheets->deref();
if (m_addedStyleSheets)
m_addedStyleSheets->deref();
if (m_cssTarget)
m_cssTarget->deref();
if (m_focusNode)
m_focusNode->deref();
if ( m_hoverNode )
m_hoverNode->deref();
if (m_activeNode)
m_activeNode->deref();
if (m_documentElement)
m_documentElement->deref();
m_windowEventTarget->deref();
qDeleteAll(m_counterDict);
m_renderArena.reset();
KHTMLGlobal::deregisterDocumentImpl(this);
}
DOMImplementationImpl *DocumentImpl::implementation() const
{
if (!m_implementation) {
m_implementation = new DOMImplementationImpl();
m_implementation->ref();
}
return m_implementation;
}
void DocumentImpl::childrenChanged()
{
// invalidate the document element we have cached in case it was replaced
if (m_documentElement)
m_documentElement->deref();
m_documentElement = 0;
// same for m_docType
if (m_doctype)
m_doctype->deref();
m_doctype = 0;
}
ElementImpl *DocumentImpl::documentElement() const
{
if (!m_documentElement) {
NodeImpl* n = firstChild();
while (n && n->nodeType() != Node::ELEMENT_NODE)
n = n->nextSibling();
m_documentElement = static_cast<ElementImpl*>(n);
if (m_documentElement)
m_documentElement->ref();
}
return m_documentElement;
}
DocumentTypeImpl *DocumentImpl::doctype() const
{
if (!m_doctype) {
NodeImpl* n = firstChild();
while (n && n->nodeType() != Node::DOCUMENT_TYPE_NODE)
n = n->nextSibling();
m_doctype = static_cast<DocumentTypeImpl*>(n);
if (m_doctype)
m_doctype->ref();
}
return m_doctype;
}
ElementImpl *DocumentImpl::createElement( const DOMString &name, int* pExceptioncode )
{
if (pExceptioncode && !Element::khtmlValidQualifiedName(name)) {
*pExceptioncode = DOMException::INVALID_CHARACTER_ERR;
return 0;
}
PrefixName prefix;
LocalName localName;
bool htmlCompat = htmlMode() != XHtml;
splitPrefixLocalName(name, prefix, localName, htmlCompat);
XMLElementImpl* e = new XMLElementImpl(document(), emptyNamespaceName, localName, prefix);
e->setHTMLCompat(htmlCompat); // Not a real HTML element, but inside an html-compat doc all tags are uppercase.
return e;
}
AttrImpl *DocumentImpl::createAttribute( const DOMString &tagName, int* pExceptioncode )
{
if (pExceptioncode && !Element::khtmlValidAttrName(tagName)) {
*pExceptioncode = DOMException::INVALID_CHARACTER_ERR;
return 0;
}
PrefixName prefix;
LocalName localName;
bool htmlCompat = (htmlMode() != XHtml);
splitPrefixLocalName(tagName, prefix, localName, htmlCompat);
AttrImpl* attr = new AttrImpl(0, document(), NamespaceName::fromId(emptyNamespace),
localName, prefix, DOMString("").implementation());
attr->setHTMLCompat(htmlCompat);
return attr;
}
DocumentFragmentImpl *DocumentImpl::createDocumentFragment( )
{
return new DocumentFragmentImpl( docPtr() );
}
CommentImpl *DocumentImpl::createComment ( DOMStringImpl* data )
{
return new CommentImpl( docPtr(), data );
}
CDATASectionImpl *DocumentImpl::createCDATASection ( DOMStringImpl* data )
{
return new CDATASectionImpl( docPtr(), data );
}
ProcessingInstructionImpl *DocumentImpl::createProcessingInstruction ( const DOMString &target, DOMStringImpl* data )
{
return new ProcessingInstructionImpl( docPtr(),target,data);
}
EntityReferenceImpl *DocumentImpl::createEntityReference ( const DOMString &name )
{
return new EntityReferenceImpl(docPtr(), name.implementation());
}
EditingTextImpl *DocumentImpl::createEditingTextNode(const DOMString &text)
{
return new EditingTextImpl(docPtr(), text);
}
NodeImpl *DocumentImpl::importNode(NodeImpl *importedNode, bool deep, int &exceptioncode)
{
NodeImpl *result = 0;
// Not mentioned in spec: throw NOT_FOUND_ERR if evt is null
if (!importedNode) {
exceptioncode = DOMException::NOT_FOUND_ERR;
return 0;
}
if(importedNode->nodeType() == Node::ELEMENT_NODE)
{
// Why not use cloneNode?
ElementImpl *otherElem = static_cast<ElementImpl*>(importedNode);
NamedAttrMapImpl *otherMap = static_cast<ElementImpl *>(importedNode)->attributes(true);
ElementImpl *tempElementImpl;
tempElementImpl = createElementNS(otherElem->namespaceURI(),otherElem->nonCaseFoldedTagName());
tempElementImpl->setHTMLCompat(htmlMode() != XHtml && otherElem->htmlCompat());
result = tempElementImpl;
if(otherMap) {
for(unsigned i = 0; i < otherMap->length(); i++)
{
AttrImpl *otherAttr = otherMap->attributeAt(i).createAttr(otherElem,otherElem->docPtr());
tempElementImpl->setAttributeNS(otherAttr->namespaceURI(),
otherAttr->name(),
otherAttr->nodeValue(),
exceptioncode);
if(exceptioncode != 0)
break; // ### properly cleanup here
}
}
}
else if(importedNode->nodeType() == Node::TEXT_NODE)
{
result = createTextNode(static_cast<TextImpl*>(importedNode)->string());
deep = false;
}
else if(importedNode->nodeType() == Node::CDATA_SECTION_NODE)
{
result = createCDATASection(static_cast<CDATASectionImpl*>(importedNode)->string());
deep = false;
}
else if(importedNode->nodeType() == Node::ENTITY_REFERENCE_NODE)
result = createEntityReference(importedNode->nodeName());
else if(importedNode->nodeType() == Node::PROCESSING_INSTRUCTION_NODE)
{
result = createProcessingInstruction(importedNode->nodeName(), importedNode->nodeValue().implementation());
deep = false;
}
else if(importedNode->nodeType() == Node::COMMENT_NODE)
{
result = createComment(static_cast<CommentImpl*>(importedNode)->string());
deep = false;
}
else if (importedNode->nodeType() == Node::DOCUMENT_FRAGMENT_NODE)
result = createDocumentFragment();
else
exceptioncode = DOMException::NOT_SUPPORTED_ERR;
//### FIXME: This should handle Attributes, and a few other things
if(deep && result)
{
for(Node n = importedNode->firstChild(); !n.isNull(); n = n.nextSibling())
result->appendChild(importNode(n.handle(), true, exceptioncode), exceptioncode);
}
return result;
}
ElementImpl *DocumentImpl::createElementNS( const DOMString &_namespaceURI, const DOMString &_qualifiedName, int* pExceptioncode )
{
ElementImpl *e = 0;
int colonPos = -2;
// check NAMESPACE_ERR/INVALID_CHARACTER_ERR
if (pExceptioncode && !checkQualifiedName(_qualifiedName, _namespaceURI, &colonPos,
false/*nameCanBeNull*/, false/*nameCanBeEmpty*/,
pExceptioncode))
return 0;
DOMString prefix, localName;
splitPrefixLocalName(_qualifiedName.implementation(), prefix, localName, colonPos);
if (_namespaceURI == SVG_NAMESPACE) {
e = createSVGElement(QualifiedName(prefix, localName, _namespaceURI));
if (e) return e;
if (!e) {
kWarning() << "svg element" << localName << "either is not supported by khtml or it's not a proper svg element";
}
}
// Regardless of document type (even for HTML), this method will only create HTML
// elements if given the namespace explicitly. Further, this method is always
// case sensitive, again, even in HTML; however .tagName will case-normalize
// in HTML regardless
if (_namespaceURI == XHTML_NAMESPACE) {
e = createHTMLElement(localName, false /* case sensitive */);
int _exceptioncode = 0;
if (!prefix.isNull())
e->setPrefix(prefix, _exceptioncode);
if ( _exceptioncode ) {
if ( pExceptioncode ) *pExceptioncode = _exceptioncode;
delete e;
return 0;
}
}
if (!e) {
e = new XMLElementImpl(document(), NamespaceName::fromString(_namespaceURI),
LocalName::fromString(localName), PrefixName::fromString(prefix));
}
return e;
}
AttrImpl *DocumentImpl::createAttributeNS( const DOMString &_namespaceURI,
const DOMString &_qualifiedName, int* pExceptioncode)
{
int colonPos = -2;
// check NAMESPACE_ERR/INVALID_CHARACTER_ERR
if (pExceptioncode && !checkQualifiedName(_qualifiedName, _namespaceURI, &colonPos,
false/*nameCanBeNull*/, false/*nameCanBeEmpty*/,
pExceptioncode))
return 0;
PrefixName prefix;
LocalName localName;
bool htmlCompat = _namespaceURI.isNull() && htmlMode() != XHtml;
splitPrefixLocalName(_qualifiedName, prefix, localName, false, colonPos);
AttrImpl* attr = new AttrImpl(0, document(), NamespaceName::fromString(_namespaceURI),
localName, prefix, DOMString("").implementation());
attr->setHTMLCompat( htmlCompat );
return attr;
}
ElementImpl *DocumentImpl::getElementById( const DOMString &elementId ) const
{
ElementMappingCache::ItemInfo* info = m_getElementByIdCache.get(elementId);
if (!info)
return 0;
//See if cache has an unambiguous answer.
if (info->nd)
return info->nd;
//Now we actually have to walk.
QStack<NodeImpl*> nodeStack;
NodeImpl *current = _first;
while(1)
{
if(!current)
{
if(nodeStack.isEmpty()) break;
current = nodeStack.pop();
current = current->nextSibling();
}
else
{
if(current->isElementNode())
{
ElementImpl *e = static_cast<ElementImpl *>(current);
if(e->getAttribute(ATTR_ID) == elementId) {
info->nd = e;
return e;
}
}
NodeImpl *child = current->firstChild();
if(child)
{
nodeStack.push(current);
current = child;
}
else
{
current = current->nextSibling();
}
}
}
assert(0); //If there is no item with such an ID, we should never get here
//kDebug() << "WARNING: *DocumentImpl::getElementById not found " << elementId.string();
return 0;
}
void DocumentImpl::setTitle(const DOMString& _title)
{
if (_title == m_title && !m_title.isNull()) return;
m_title = _title;
QString titleStr = m_title.string();
for (int i = 0; i < titleStr.length(); ++i)
if (titleStr[i] < ' ')
titleStr[i] = ' ';
titleStr = titleStr.simplified();
if ( view() && !view()->part()->parentPart() ) {
if (titleStr.isNull() || titleStr.isEmpty()) {
// empty title... set window caption as the URL
KUrl url = m_url;
url.setRef(QString());
url.setQuery(QString());
titleStr = url.prettyUrl();
}
emit view()->part()->setWindowCaption( titleStr );
}
}
DOMString DocumentImpl::nodeName() const
{
return "#document";
}
unsigned short DocumentImpl::nodeType() const
{
return Node::DOCUMENT_NODE;
}
ElementImpl *DocumentImpl::createHTMLElement( const DOMString &name, bool caseInsensitive )
{
LocalName localname = LocalName::fromString(name,
caseInsensitive ? IDS_NormalizeLower : IDS_CaseSensitive);
uint id = localname.id();
ElementImpl *n = 0;
switch(id)
{
case ID_HTML:
n = new HTMLHtmlElementImpl(docPtr());
break;
case ID_HEAD:
n = new HTMLHeadElementImpl(docPtr());
break;
case ID_BODY:
n = new HTMLBodyElementImpl(docPtr());
break;
// head elements
case ID_BASE:
n = new HTMLBaseElementImpl(docPtr());
break;
case ID_LINK:
n = new HTMLLinkElementImpl(docPtr());
break;
case ID_META:
n = new HTMLMetaElementImpl(docPtr());
break;
case ID_STYLE:
n = new HTMLStyleElementImpl(docPtr());
break;
case ID_TITLE:
n = new HTMLTitleElementImpl(docPtr());
break;
// frames
case ID_FRAME:
n = new HTMLFrameElementImpl(docPtr());
break;
case ID_FRAMESET:
n = new HTMLFrameSetElementImpl(docPtr());
break;
case ID_IFRAME:
n = new HTMLIFrameElementImpl(docPtr());
break;
// form elements
// ### FIXME: we need a way to set form dependency after we have made the form elements
case ID_FORM:
n = new HTMLFormElementImpl(docPtr(), false);
break;
case ID_BUTTON:
n = new HTMLButtonElementImpl(docPtr());
break;
case ID_FIELDSET:
n = new HTMLFieldSetElementImpl(docPtr());
break;
case ID_INPUT:
n = new HTMLInputElementImpl(docPtr());
break;
case ID_ISINDEX:
n = new HTMLIsIndexElementImpl(docPtr());
break;
case ID_LABEL:
n = new HTMLLabelElementImpl(docPtr());
break;
case ID_LEGEND:
n = new HTMLLegendElementImpl(docPtr());
break;
case ID_OPTGROUP:
n = new HTMLOptGroupElementImpl(docPtr());
break;
case ID_OPTION:
n = new HTMLOptionElementImpl(docPtr());
break;
case ID_SELECT:
n = new HTMLSelectElementImpl(docPtr());
break;
case ID_TEXTAREA:
n = new HTMLTextAreaElementImpl(docPtr());
break;
// lists
case ID_DL:
n = new HTMLDListElementImpl(docPtr());
break;
case ID_DD:
n = new HTMLGenericElementImpl(docPtr(), id);
break;
case ID_DT:
n = new HTMLGenericElementImpl(docPtr(), id);
break;
case ID_UL:
n = new HTMLUListElementImpl(docPtr());
break;
case ID_OL:
n = new HTMLOListElementImpl(docPtr());
break;
case ID_DIR:
n = new HTMLDirectoryElementImpl(docPtr());
break;
case ID_MENU:
n = new HTMLMenuElementImpl(docPtr());
break;
case ID_LI:
n = new HTMLLIElementImpl(docPtr());
break;
// formatting elements (block)
case ID_DIV:
case ID_P:
n = new HTMLDivElementImpl( docPtr(), id );
break;
case ID_BLOCKQUOTE:
case ID_H1:
case ID_H2:
case ID_H3:
case ID_H4:
case ID_H5:
case ID_H6:
n = new HTMLGenericElementImpl(docPtr(), id);
break;
case ID_HR:
n = new HTMLHRElementImpl(docPtr());
break;
case ID_PLAINTEXT:
case ID_XMP:
case ID_PRE:
case ID_LISTING:
n = new HTMLPreElementImpl(docPtr(), id);
break;
// font stuff
case ID_BASEFONT:
n = new HTMLBaseFontElementImpl(docPtr());
break;
case ID_FONT:
n = new HTMLFontElementImpl(docPtr());
break;
// ins/del
case ID_DEL:
case ID_INS:
n = new HTMLGenericElementImpl(docPtr(), id);
break;
// anchor
case ID_A:
n = new HTMLAnchorElementImpl(docPtr());
break;
// images
case ID_IMG:
case ID_IMAGE: // legacy name
n = new HTMLImageElementImpl(docPtr());
break;
case ID_CANVAS:
n = new HTMLCanvasElementImpl(docPtr());
break;
case ID_MAP:
n = new HTMLMapElementImpl(docPtr());
/*n = map;*/
break;
case ID_AREA:
n = new HTMLAreaElementImpl(docPtr());
break;
// objects, applets and scripts
case ID_APPLET:
n = new HTMLAppletElementImpl(docPtr());
break;
case ID_OBJECT:
n = new HTMLObjectElementImpl(docPtr());
break;
case ID_EMBED:
n = new HTMLEmbedElementImpl(docPtr());
break;
case ID_PARAM:
n = new HTMLParamElementImpl(docPtr());
break;
case ID_SCRIPT:
n = new HTMLScriptElementImpl(docPtr());
break;
// media
case ID_AUDIO:
n = new HTMLAudioElement(docPtr());
break;
case ID_VIDEO:
n = new HTMLVideoElement(docPtr());
break;
case ID_SOURCE:
n = new HTMLSourceElement(docPtr());
break;
// tables
case ID_TABLE:
n = new HTMLTableElementImpl(docPtr());
break;
case ID_CAPTION:
n = new HTMLTableCaptionElementImpl(docPtr());
break;
case ID_COLGROUP:
case ID_COL:
n = new HTMLTableColElementImpl(docPtr(), id);
break;
case ID_TR:
n = new HTMLTableRowElementImpl(docPtr());
break;
case ID_TD:
case ID_TH:
n = new HTMLTableCellElementImpl(docPtr(), id);
break;
case ID_THEAD:
case ID_TBODY:
case ID_TFOOT:
n = new HTMLTableSectionElementImpl(docPtr(), id, false);
break;
// inline elements
case ID_BR:
n = new HTMLBRElementImpl(docPtr());
break;
case ID_WBR:
n = new HTMLWBRElementImpl(docPtr());
break;
case ID_Q:
n = new HTMLGenericElementImpl(docPtr(), id);
break;
// elements with no special representation in the DOM
// block:
case ID_ADDRESS:
case ID_CENTER:
n = new HTMLGenericElementImpl(docPtr(), id);
break;
// inline
// %fontstyle
case ID_TT:
case ID_U:
case ID_B:
case ID_I:
case ID_S:
case ID_STRIKE:
case ID_BIG:
case ID_SMALL:
// %phrase
case ID_EM:
case ID_STRONG:
case ID_DFN:
case ID_CODE:
case ID_SAMP:
case ID_KBD:
case ID_VAR:
case ID_CITE:
case ID_ABBR:
case ID_ACRONYM:
// %special
case ID_SUB:
case ID_SUP:
case ID_SPAN:
case ID_NOBR:
case ID_BDO:
case ID_NOFRAMES:
case ID_NOSCRIPT:
case ID_NOEMBED:
case ID_NOLAYER:
n = new HTMLGenericElementImpl(docPtr(), id);
break;
case ID_MARQUEE:
n = new HTMLMarqueeElementImpl(docPtr());
break;
// text
case ID_TEXT:
kDebug( 6020 ) << "Use document->createTextNode()";
break;
default:
n = new HTMLGenericElementImpl(docPtr(), localname);
break;
}
assert(n);
return n;
}
// SVG
ElementImpl *DocumentImpl::createSVGElement(const QualifiedName& name)
{
uint id = name.localNameId().id();
kDebug() << getPrintableName(name.id()) << endl;
kDebug() << "svg text: " << getPrintableName(WebCore::SVGNames::textTag.id()) << endl;
ElementImpl *n = 0;
switch (id)
{
case ID_TEXTPATH:
n = new WebCore::SVGTextPathElement(name, docPtr());
break;
case ID_TSPAN:
n = new WebCore::SVGTSpanElement(name, docPtr());
break;
case ID_HKERN:
n = new WebCore::SVGHKernElement(name, docPtr());
break;
case ID_ALTGLYPH:
n = new WebCore::SVGAltGlyphElement(name, docPtr());
break;
case ID_FONT:
n = new WebCore::SVGFontElement(name, docPtr());
break;
}
if (id == WebCore::SVGNames::svgTag.localNameId().id()) {
n = new WebCore::SVGSVGElement(name, docPtr());
}
if (id == WebCore::SVGNames::rectTag.localNameId().id()) {
n = new WebCore::SVGRectElement(name, docPtr());
}
if (id == WebCore::SVGNames::circleTag.localNameId().id()) {
n = new WebCore::SVGCircleElement(name, docPtr());
}
if (id == WebCore::SVGNames::ellipseTag.localNameId().id()) {
n = new WebCore::SVGEllipseElement(name, docPtr());
}
if (id == WebCore::SVGNames::polylineTag.localNameId().id()) {
n = new WebCore::SVGPolylineElement(name, docPtr());
}
if (id == WebCore::SVGNames::polygonTag.localNameId().id()) {
n = new WebCore::SVGPolygonElement(name, docPtr());
}
if (id == WebCore::SVGNames::pathTag.localNameId().id()) {
n = new WebCore::SVGPathElement(name, docPtr());
}
if (id == WebCore::SVGNames::defsTag.localNameId().id()) {
n = new WebCore::SVGDefsElement(name, docPtr());
}
if (id == WebCore::SVGNames::linearGradientTag.localNameId().id()) {
n = new WebCore::SVGLinearGradientElement(name, docPtr());
}
if (id == WebCore::SVGNames::radialGradientTag.localNameId().id()) {
n = new WebCore::SVGRadialGradientElement(name, docPtr());
}
if (id == WebCore::SVGNames::stopTag.localNameId().id()) {
n = new WebCore::SVGStopElement(name, docPtr());
}
if (id == WebCore::SVGNames::clipPathTag.localNameId().id()) {
n = new WebCore::SVGClipPathElement(name, docPtr());
}
if (id == WebCore::SVGNames::gTag.localNameId().id()) {
n = new WebCore::SVGGElement(name, docPtr());
}
if (id == WebCore::SVGNames::useTag.localNameId().id()) {
n = new WebCore::SVGUseElement(name, docPtr());
}
if (id == WebCore::SVGNames::lineTag.localNameId().id()) {
n = new WebCore::SVGLineElement(name, docPtr());
}
if (id == WebCore::SVGNames::textTag.localNameId().id()) {
n = new WebCore::SVGTextElement(name, docPtr());
}
if (id == WebCore::SVGNames::aTag.localNameId().id()) {
n = new WebCore::SVGAElement(name, docPtr());
}
if (id == WebCore::SVGNames::scriptTag.localNameId().id()) {
n = new WebCore::SVGScriptElement(name, docPtr());
}
if (id == WebCore::SVGNames::descTag.localNameId().id()) {
n = new WebCore::SVGDescElement(name, docPtr());
}
if (id == WebCore::SVGNames::titleTag.localNameId().id()) {
n = new WebCore::SVGTitleElement(name, docPtr());
}
if (id == makeId(svgNamespace, ID_STYLE)) {
n = new WebCore::SVGStyleElement(name, docPtr());
}
return n;
}
void DocumentImpl::attemptRestoreState(NodeImpl* n)
{
if (!n->isElementNode())
return;
ElementImpl* el = static_cast<ElementImpl*>(n);
if (m_stateRestorePos >= m_state.size())
return;
// Grab the state and element info..
QString idStr = m_state[m_stateRestorePos];
QString nmStr = m_state[m_stateRestorePos + 1];
QString tpStr = m_state[m_stateRestorePos + 2];
QString stStr = m_state[m_stateRestorePos + 3];
// Make sure it matches!
if (idStr.toUInt() != el->id())
return;
if (nmStr != el->getAttribute(ATTR_NAME).string())
return;
if (tpStr != el->getAttribute(ATTR_TYPE).string())
return;
m_stateRestorePos += 4;
if (!stStr.isNull())
el->restoreState(stStr);
}
QStringList DocumentImpl::docState()
{
QStringList s;
for (QListIterator<NodeImpl*> it(m_maintainsState); it.hasNext();) {
NodeImpl* n = it.next();
if (!n->isElementNode())
continue;
ElementImpl* el = static_cast<ElementImpl*>(n);
// Encode the element ID, as well as the name and type attributes
s.append(QString::number(el->id()));
s.append(el->getAttribute(ATTR_NAME).string());
s.append(el->getAttribute(ATTR_TYPE).string());
s.append(el->state());
}
return s;
}
bool DocumentImpl::unsubmittedFormChanges()
{
for (QListIterator<NodeImpl*> it(m_maintainsState); it.hasNext();) {
NodeImpl* node = it.next();
if (node->isGenericFormElement() && static_cast<HTMLGenericFormElementImpl*>(node)->unsubmittedFormChanges())
return true;
}
return false;
}
RangeImpl *DocumentImpl::createRange()
{
return new RangeImpl( docPtr() );
}
NodeIteratorImpl *DocumentImpl::createNodeIterator(NodeImpl *root, unsigned long whatToShow,
NodeFilterImpl* filter, bool entityReferenceExpansion,
int &exceptioncode)
{
if (!root) {
exceptioncode = DOMException::NOT_SUPPORTED_ERR;
return 0;
}
return new NodeIteratorImpl(root,whatToShow,filter,entityReferenceExpansion);
}
TreeWalkerImpl *DocumentImpl::createTreeWalker(NodeImpl *root, unsigned long whatToShow, NodeFilterImpl *filter,
bool entityReferenceExpansion, int &exceptioncode)
{
if (!root) {
exceptioncode = DOMException::NOT_SUPPORTED_ERR;
return 0;
}
return new TreeWalkerImpl( root, whatToShow, filter, entityReferenceExpansion );
}
void DocumentImpl::setDocumentChanged(bool b)
{
if (b && !m_docChanged)
s_changedDocuments->append(this);
else if (!b && m_docChanged)
s_changedDocuments->removeAll(this);
m_docChanged = b;
}
void DocumentImpl::recalcStyle( StyleChange change )
{
// qDebug("recalcStyle(%p)", this);
// QTime qt;
// qt.start();
if (m_inStyleRecalc)
return; // Guard against re-entrancy. -dwh
m_inStyleRecalc = true;
if( !m_render ) goto bail_out;
if ( change == Force ) {
RenderStyle* oldStyle = m_render->style();
if ( oldStyle ) oldStyle->ref();
RenderStyle* _style = new RenderStyle();
_style->setDisplay(BLOCK);
_style->setVisuallyOrdered( visuallyOrdered );
// ### make the font stuff _really_ work!!!!
khtml::FontDef fontDef;
QFont f = KGlobalSettings::generalFont();
fontDef.family = f.family();
fontDef.italic = f.italic();
fontDef.weight = f.weight();
if (m_view) {
const KHTMLSettings *settings = m_view->part()->settings();
QString stdfont = settings->stdFontName();
if ( !stdfont.isEmpty() )
fontDef.family = stdfont;
fontDef.size = m_styleSelector->fontSizes()[3];
}
//kDebug() << "DocumentImpl::attach: setting to charset " << settings->charset();
_style->setFontDef(fontDef);
_style->htmlFont().update( 0 );
if ( inCompatMode() )
_style->setHtmlHacks(true); // enable html specific rendering tricks
StyleChange ch = diff( _style, oldStyle );
if(m_render && ch != NoChange)
m_render->setStyle(_style);
else
delete _style;
if (oldStyle)
oldStyle->deref();
}
NodeImpl *n;
for (n = _first; n; n = n->nextSibling())
if ( change>= Inherit || n->hasChangedChild() || n->changed() )
n->recalcStyle( change );
//kDebug( 6020 ) << "TIME: recalcStyle() dt=" << qt.elapsed();
if (changed() && m_view)
m_view->layout();
bail_out:
setChanged( false );
setHasChangedChild( false );
setDocumentChanged( false );
m_inStyleRecalc = false;
}
void DocumentImpl::updateRendering()
{
if (!hasChangedChild()) return;
// QTime time;
// time.start();
// kDebug() << "UPDATERENDERING: ";
StyleChange change = NoChange;
#if 0
if ( m_styleSelectorDirty ) {
recalcStyleSelector();
change = Force;
}
#endif
recalcStyle( change );
// kDebug() << "UPDATERENDERING time used="<<time.elapsed();
}
void DocumentImpl::updateDocumentsRendering()
{
if (!s_changedDocuments)
return;
while ( !s_changedDocuments->isEmpty() ) {
DocumentImpl* it = s_changedDocuments->takeFirst();
if (it->isDocumentChanged())
it->updateRendering();
}
}
void DocumentImpl::updateLayout()
{
if (ElementImpl* oe = ownerElement())
oe->document()->updateLayout();
bool oldIgnore = m_ignorePendingStylesheets;
if (!haveStylesheetsLoaded()) {
m_ignorePendingStylesheets = true;
updateStyleSelector();
}
updateRendering();
// Only do a layout if changes have occurred that make it necessary.
if (m_view && renderer() && renderer()->needsLayout())
m_view->layout();
m_ignorePendingStylesheets = oldIgnore;
}
void DocumentImpl::attach()
{
assert(!attached());
if ( m_view )
setPaintDevice( m_view );
if (!m_renderArena)
m_renderArena.reset(new RenderArena());
// Create the rendering tree
assert(!m_styleSelector);
m_styleSelector = new CSSStyleSelector( this, m_usersheet, m_styleSheets, m_url,
!inCompatMode() );
m_render = new (m_renderArena.get()) RenderCanvas(this, m_view);
m_styleSelector->computeFontSizes(m_paintDevice->logicalDpiY(), m_view ? m_view->part()->fontScaleFactor() : 100);
recalcStyle( Force );
RenderObject* render = m_render;
m_render = 0;
NodeBaseImpl::attach();
m_render = render;
}
void DocumentImpl::detach()
{
RenderObject* render = m_render;
// indicate destruction mode, i.e. attached() but m_render == 0
m_render = 0;
delete m_tokenizer;
m_tokenizer = 0;
// Empty out these lists as a performance optimization
m_imageLoadEventDispatchSoonList.clear();
m_imageLoadEventDispatchingList.clear();
NodeBaseImpl::detach();
if ( render )
render->detach();
m_view = 0;
m_renderArena.reset();
}
void DocumentImpl::setVisuallyOrdered()
{
visuallyOrdered = true;
if (m_render)
m_render->style()->setVisuallyOrdered(true);
}
void DocumentImpl::setSelection(NodeImpl* s, int sp, NodeImpl* e, int ep)
{
if ( m_render )
static_cast<RenderCanvas*>(m_render)->setSelection(s->renderer(),sp,e->renderer(),ep);
}
void DocumentImpl::clearSelection()
{
if ( m_render )
static_cast<RenderCanvas*>(m_render)->clearSelection();
}
void DocumentImpl::updateSelection()
{
if (!m_render)
return;
RenderCanvas *canvas = static_cast<RenderCanvas*>(m_render);
Selection s = part()->caret();
if (s.isEmpty() || s.state() == Selection::CARET) {
canvas->clearSelection();
}
else {
RenderObject *startRenderer = s.start().node() ? s.start().node()->renderer() : 0;
RenderObject *endRenderer = s.end().node() ? s.end().node()->renderer() : 0;
RenderPosition renderedStart = RenderPosition::fromDOMPosition(s.start());
RenderPosition renderedEnd = RenderPosition::fromDOMPosition(s.end());
static_cast<RenderCanvas*>(m_render)->setSelection(startRenderer, renderedStart.renderedOffset(), endRenderer, renderedEnd.renderedOffset());
}
}
khtml::Tokenizer *DocumentImpl::createTokenizer()
{
return new khtml::XMLTokenizer(docPtr(),m_view);
}
int DocumentImpl::logicalDpiY()
{
return m_paintDevice->logicalDpiY();
}
void DocumentImpl::open( bool clearEventListeners )
{
if (parsing()) return;
if (m_tokenizer)
close();
delete m_tokenizer;
m_tokenizer = 0;
KHTMLView* view = m_view;
bool was_attached = attached();
if ( was_attached )
detach();
removeChildren();
childrenChanged(); // Reset m_documentElement, m_doctype
delete m_styleSelector;
m_styleSelector = 0;
m_view = view;
if ( was_attached )
attach();
if (clearEventListeners)
windowEventTarget()->listenerList().clear();
m_tokenizer = createTokenizer();
//m_decoderMibEnum = 0;
connect(m_tokenizer,SIGNAL(finishedParsing()),this,SIGNAL(finishedParsing()));
m_tokenizer->begin();
}
HTMLElementImpl* DocumentImpl::body() const
{
NodeImpl *de = documentElement();
if (!de)
return 0;
// try to prefer a FRAMESET element over BODY
NodeImpl* body = 0;
for (NodeImpl* i = de->firstChild(); i; i = i->nextSibling()) {
if (i->id() == ID_FRAMESET)
return static_cast<HTMLElementImpl*>(i);
if (i->id() == ID_BODY)
body = i;
}
return static_cast<HTMLElementImpl *>(body);
}
void DocumentImpl::close( )
{
if (parsing() && hasVariableLength() && m_tokenizer) {
m_tokenizer->finish();
} else if (parsing() || !m_tokenizer)
return;
if ( m_render )
m_render->close();
// on an explicit document.close(), the tokenizer might still be waiting on scripts,
// and in that case we don't want to destroy it because that will prevent the
// scripts from getting processed.
if (m_tokenizer && !m_tokenizer->isWaitingForScripts() && !m_tokenizer->isExecutingScript()) {
delete m_tokenizer;
m_tokenizer = 0;
}
if (m_view)
m_view->part()->checkEmitLoadEvent();
}
void DocumentImpl::write( const DOMString &text )
{
write(text.string());
}
void DocumentImpl::write( const QString &text )
{
if (!m_tokenizer) {
open();
if (m_view)
m_view->part()->resetFromScript();
setHasVariableLength();
}
m_tokenizer->write(text, false);
}
void DocumentImpl::writeln( const DOMString &text )
{
write(text);
write(DOMString("\n"));
}
void DocumentImpl::finishParsing ( )
{
if(m_tokenizer)
m_tokenizer->finish();
}
QString DocumentImpl::completeURL(const QString& url) const
{
return KUrl(baseURL(),url /*,m_decoderMibEnum*/).url();
}
void DocumentImpl::setUserStyleSheet( const QString& sheet )
{
if ( m_usersheet != sheet ) {
m_usersheet = sheet;
updateStyleSelector();
}
}
CSSStyleSheetImpl* DocumentImpl::elementSheet()
{
if (!m_elemSheet) {
m_elemSheet = new CSSStyleSheetImpl(this, baseURL().url() );
m_elemSheet->ref();
}
return m_elemSheet;
}
void DocumentImpl::determineParseMode()
{
// For XML documents, use strict parse mode
pMode = Strict;
hMode = XHtml;
m_htmlCompat = false;
kDebug(6020) << " using strict parseMode";
}
NodeImpl *DocumentImpl::nextFocusNode(NodeImpl *fromNode)
{
short fromTabIndex;
if (!fromNode) {
// No starting node supplied; begin with the top of the document
NodeImpl *n;
int lowestTabIndex = SHRT_MAX + 1;
for (n = this; n != 0; n = n->traverseNextNode()) {
if (n->isTabFocusable()) {
if ((n->tabIndex() > 0) && (n->tabIndex() < lowestTabIndex))
lowestTabIndex = n->tabIndex();
}
}
if (lowestTabIndex == SHRT_MAX + 1)
lowestTabIndex = 0;
// Go to the first node in the document that has the desired tab index
for (n = this; n != 0; n = n->traverseNextNode()) {
if (n->isTabFocusable() && (n->tabIndex() == lowestTabIndex))
return n;
}
return 0;
}
else {
fromTabIndex = fromNode->tabIndex();
}
if (fromTabIndex == 0) {
// Just need to find the next selectable node after fromNode (in document order) that doesn't have a tab index
NodeImpl *n = fromNode->traverseNextNode();
while (n && !(n->isTabFocusable() && n->tabIndex() == 0))
n = n->traverseNextNode();
return n;
}
else {
// Find the lowest tab index out of all the nodes except fromNode, that is greater than or equal to fromNode's
// tab index. For nodes with the same tab index as fromNode, we are only interested in those that come after
// fromNode in document order.
// If we don't find a suitable tab index, the next focus node will be one with a tab index of 0.
int lowestSuitableTabIndex = SHRT_MAX + 1;
NodeImpl *n;
bool reachedFromNode = false;
for (n = this; n != 0; n = n->traverseNextNode()) {
if (n->isTabFocusable() &&
((reachedFromNode && (n->tabIndex() >= fromTabIndex)) ||
(!reachedFromNode && (n->tabIndex() > fromTabIndex))) &&
(n->tabIndex() < lowestSuitableTabIndex) &&
(n != fromNode)) {
// We found a selectable node with a tab index at least as high as fromNode's. Keep searching though,
// as there may be another node which has a lower tab index but is still suitable for use.
lowestSuitableTabIndex = n->tabIndex();
}
if (n == fromNode)
reachedFromNode = true;
}
if (lowestSuitableTabIndex == SHRT_MAX + 1) {
// No next node with a tab index -> just take first node with tab index of 0
NodeImpl *n = this;
while (n && !(n->isTabFocusable() && n->tabIndex() == 0))
n = n->traverseNextNode();
return n;
}
// Search forwards from fromNode
for (n = fromNode->traverseNextNode(); n != 0; n = n->traverseNextNode()) {
if (n->isTabFocusable() && (n->tabIndex() == lowestSuitableTabIndex))
return n;
}
// The next node isn't after fromNode, start from the beginning of the document
for (n = this; n != fromNode; n = n->traverseNextNode()) {
if (n->isTabFocusable() && (n->tabIndex() == lowestSuitableTabIndex))
return n;
}
assert(false); // should never get here
return 0;
}
}
NodeImpl *DocumentImpl::previousFocusNode(NodeImpl *fromNode)
{
NodeImpl *lastNode = this;
while (lastNode->lastChild())
lastNode = lastNode->lastChild();
if (!fromNode) {
// No starting node supplied; begin with the very last node in the document
NodeImpl *n;
int highestTabIndex = 0;
for (n = lastNode; n != 0; n = n->traversePreviousNode()) {
if (n->isTabFocusable()) {
if (n->tabIndex() == 0)
return n;
else if (n->tabIndex() > highestTabIndex)
highestTabIndex = n->tabIndex();
}
}
// No node with a tab index of 0; just go to the last node with the highest tab index
for (n = lastNode; n != 0; n = n->traversePreviousNode()) {
if (n->isTabFocusable() && (n->tabIndex() == highestTabIndex))
return n;
}
return 0;
}
else {
short fromTabIndex = fromNode->tabIndex();
if (fromTabIndex == 0) {
// Find the previous selectable node before fromNode (in document order) that doesn't have a tab index
NodeImpl *n = fromNode->traversePreviousNode();
while (n && !(n->isTabFocusable() && n->tabIndex() == 0))
n = n->traversePreviousNode();
if (n)
return n;
// No previous nodes with a 0 tab index, go to the last node in the document that has the highest tab index
int highestTabIndex = 0;
for (n = this; n != 0; n = n->traverseNextNode()) {
if (n->isTabFocusable() && (n->tabIndex() > highestTabIndex))
highestTabIndex = n->tabIndex();
}
if (highestTabIndex == 0)
return 0;
for (n = lastNode; n != 0; n = n->traversePreviousNode()) {
if (n->isTabFocusable() && (n->tabIndex() == highestTabIndex))
return n;
}
assert(false); // should never get here
return 0;
}
else {
// Find the lowest tab index out of all the nodes except fromNode, that is less than or equal to fromNode's
// tab index. For nodes with the same tab index as fromNode, we are only interested in those before
// fromNode.
// If we don't find a suitable tab index, then there will be no previous focus node.
short highestSuitableTabIndex = 0;
NodeImpl *n;
bool reachedFromNode = false;
for (n = this; n != 0; n = n->traverseNextNode()) {
if (n->isTabFocusable() &&
((!reachedFromNode && (n->tabIndex() <= fromTabIndex)) ||
(reachedFromNode && (n->tabIndex() < fromTabIndex))) &&
(n->tabIndex() > highestSuitableTabIndex) &&
(n != fromNode)) {
// We found a selectable node with a tab index no higher than fromNode's. Keep searching though, as
// there may be another node which has a higher tab index but is still suitable for use.
highestSuitableTabIndex = n->tabIndex();
}
if (n == fromNode)
reachedFromNode = true;
}
if (highestSuitableTabIndex == 0) {
// No previous node with a tab index. Since the order specified by HTML is nodes with tab index > 0
// first, this means that there is no previous node.
return 0;
}
// Search backwards from fromNode
for (n = fromNode->traversePreviousNode(); n != 0; n = n->traversePreviousNode()) {
if (n->isTabFocusable() && (n->tabIndex() == highestSuitableTabIndex))
return n;
}
// The previous node isn't before fromNode, start from the end of the document
for (n = lastNode; n != fromNode; n = n->traversePreviousNode()) {
if (n->isTabFocusable() && (n->tabIndex() == highestSuitableTabIndex))
return n;
}
assert(false); // should never get here
return 0;
}
}
}
ElementImpl* DocumentImpl::findAccessKeyElement(QChar c)
{
c = c.toUpper();
for( NodeImpl* n = this;
n != NULL;
n = n->traverseNextNode()) {
if( n->isElementNode()) {
ElementImpl* en = static_cast< ElementImpl* >( n );
DOMString s = en->getAttribute( ATTR_ACCESSKEY );
if( s.length() == 1
&& s[ 0 ].toUpper() == c )
return en;
}
}
return NULL;
}
int DocumentImpl::nodeAbsIndex(NodeImpl *node)
{
assert(node->document() == this);
int absIndex = 0;
for (NodeImpl *n = node; n && n != this; n = n->traversePreviousNode())
absIndex++;
return absIndex;
}
NodeImpl *DocumentImpl::nodeWithAbsIndex(int absIndex)
{
NodeImpl *n = this;
for (int i = 0; n && (i < absIndex); i++) {
n = n->traverseNextNode();
}
return n;
}
void DocumentImpl::processHttpEquiv(const DOMString &equiv, const DOMString &content)
{
assert(!equiv.isNull() && !content.isNull());
KHTMLView *v = document()->view();
if(strcasecmp(equiv, "refresh") == 0 && v && v->part()->metaRefreshEnabled())
{
// get delay and url
QString str = content.string().trimmed();
int pos = str.indexOf(QRegExp("[;,]"));
if ( pos == -1 )
pos = str.indexOf(QRegExp("[ \t]"));
bool ok = false;
int delay = qMax( 0, content.implementation()->toInt(&ok) );
if ( !ok && str.length() && str[0] == '.' )
ok = true;
if (pos == -1) // There can be no url (David)
{
if(ok)
v->part()->scheduleRedirection(delay, v->part()->url().url() );
} else {
pos++;
while(pos < (int)str.length() && str[pos].isSpace()) pos++;
str = str.mid(pos);
if(str.indexOf("url", 0, Qt::CaseInsensitive ) == 0) str = str.mid(3);
str = str.trimmed();
if ( str.length() && str[0] == '=' ) str = str.mid( 1 ).trimmed();
while(str.length() &&
(str[str.length()-1] == ';' || str[str.length()-1] == ','))
str.resize(str.length()-1);
str = parseURL( DOMString(str) ).string();
QString newURL = document()->completeURL( str );
if ( ok )
v->part()->scheduleRedirection(delay, document()->completeURL( str ), delay < 2 || newURL == URL().url());
}
}
else if(strcasecmp(equiv, "expires") == 0)
{
bool relative = false;
QString str = content.string().trimmed();
time_t expire_date = KDateTime::fromString(str, KDateTime::RFCDate).toTime_t();
if (!expire_date)
{
expire_date = str.toULong();
relative = true;
}
if (!expire_date)
expire_date = 1; // expire now
if (m_docLoader)
m_docLoader->setExpireDate(expire_date, relative);
}
else if(v && (strcasecmp(equiv, "pragma") == 0 || strcasecmp(equiv, "cache-control") == 0))
{
QString str = content.string().toLower().trimmed();
KUrl url = v->part()->url();
- if ((str == "no-cache") && url.protocol().startsWith(QLatin1String("http")))
+ if ((str == "no-cache") && url.scheme().startsWith(QLatin1String("http")))
{
KIO::http_update_cache(url, true, 0);
}
}
else if( (strcasecmp(equiv, "set-cookie") == 0))
{
// ### make setCookie work on XML documents too; e.g. in case of <html:meta .....>
HTMLDocumentImpl *d = static_cast<HTMLDocumentImpl *>(this);
d->setCookie(content);
}
else if (strcasecmp(equiv, "default-style") == 0) {
// HTML 4.0 14.3.2
// http://www.hixie.ch/tests/evil/css/import/main/preferred.html
m_preferredStylesheetSet = content;
updateStyleSelector();
}
else if (strcasecmp(equiv, "content-language") == 0) {
m_contentLanguage = content.string();
}
}
bool DocumentImpl::prepareMouseEvent( bool readonly, int _x, int _y, MouseEvent *ev )
{
if ( m_render ) {
assert(m_render->isCanvas());
RenderObject::NodeInfo renderInfo(readonly, ev->type == MousePress);
bool isInside = m_render->layer()->nodeAtPoint(renderInfo, _x, _y);
ev->innerNode = renderInfo.innerNode();
ev->innerNonSharedNode = renderInfo.innerNonSharedNode();
if (renderInfo.URLElement()) {
assert(renderInfo.URLElement()->isElementNode());
//qDebug("urlnode: %s (%d)", getTagName(renderInfo.URLElement()->id()).string().toLatin1().constData(), renderInfo.URLElement()->id());
ElementImpl* e = static_cast<ElementImpl*>(renderInfo.URLElement());
DOMString href = khtml::parseURL(e->getAttribute(ATTR_HREF));
DOMString target = e->getAttribute(ATTR_TARGET);
if (!target.isNull() && !href.isNull()) {
ev->target = target;
ev->url = href;
}
else
ev->url = href;
}
if (!readonly)
updateRendering();
return isInside;
}
return false;
}
// DOM Section 1.1.1
bool DocumentImpl::childTypeAllowed( unsigned short type )
{
switch (type) {
case Node::ATTRIBUTE_NODE:
case Node::CDATA_SECTION_NODE:
case Node::DOCUMENT_FRAGMENT_NODE:
case Node::DOCUMENT_NODE:
case Node::ENTITY_NODE:
case Node::ENTITY_REFERENCE_NODE:
case Node::NOTATION_NODE:
case Node::TEXT_NODE:
// case Node::XPATH_NAMESPACE_NODE:
return false;
case Node::COMMENT_NODE:
case Node::PROCESSING_INSTRUCTION_NODE:
return true;
case Node::DOCUMENT_TYPE_NODE:
case Node::ELEMENT_NODE:
// Documents may contain no more than one of each of these.
// (One Element and one DocumentType.)
for (NodeImpl* c = firstChild(); c; c = c->nextSibling())
if (c->nodeType() == type)
return false;
return true;
}
return false;
}
WTF::PassRefPtr<NodeImpl> DocumentImpl::cloneNode ( bool deep )
{
#if 0
NodeImpl *dtn = m_doctype->cloneNode(deep);
DocumentTypeImpl *dt = static_cast<DocumentTypeImpl*>(dtn);
#endif
int exceptioncode;
WTF::RefPtr<NodeImpl> clone = DOMImplementationImpl::createDocument("",
"",
0, 0,
exceptioncode);
assert( exceptioncode == 0 );
// ### attributes, styles, ...
if (deep)
cloneChildNodes(clone.get());
return clone;
}
// This method is called whenever a top-level stylesheet has finished loading.
void DocumentImpl::styleSheetLoaded()
{
// Make sure we knew this sheet was pending, and that our count isn't out of sync.
assert(m_pendingStylesheets > 0);
m_pendingStylesheets--;
updateStyleSelector();
if (!m_pendingStylesheets && m_tokenizer)
m_tokenizer->executeScriptsWaitingForStylesheets();
}
void DocumentImpl::addPendingSheet()
{
m_pendingStylesheets++;
}
DOMString DocumentImpl::selectedStylesheetSet() const
{
if (!view()) return DOMString();
return view()->part()->d->m_sheetUsed;
}
void DocumentImpl::setSelectedStylesheetSet(const DOMString& s)
{
// this code is evil
if (view() && view()->part()->d->m_sheetUsed != s.string()) {
view()->part()->d->m_sheetUsed = s.string();
updateStyleSelector();
}
}
void DocumentImpl::addStyleSheet(StyleSheetImpl *sheet, int *exceptioncode)
{
int excode = 0;
if (!m_addedStyleSheets) {
m_addedStyleSheets = new StyleSheetListImpl;
m_addedStyleSheets->ref();
}
m_addedStyleSheets->add(sheet);
if (sheet->isCSSStyleSheet()) updateStyleSelector();
if (exceptioncode) *exceptioncode = excode;
}
void DocumentImpl::removeStyleSheet(StyleSheetImpl *sheet, int *exceptioncode)
{
int excode = 0;
bool removed = false;
bool is_css = sheet->isCSSStyleSheet();
if (m_addedStyleSheets) {
bool in_main_list = !sheet->hasOneRef();
removed = m_addedStyleSheets->styleSheets.removeAll(sheet);
sheet->deref();
if (m_addedStyleSheets->styleSheets.count() == 0) {
bool reset = m_addedStyleSheets->hasOneRef();
m_addedStyleSheets->deref();
if (reset) m_addedStyleSheets = 0;
}
// remove from main list, too
if (in_main_list) m_styleSheets->remove(sheet);
}
if (removed) {
if (is_css) updateStyleSelector();
} else
excode = DOMException::NOT_FOUND_ERR;
if (exceptioncode) *exceptioncode = excode;
}
void DocumentImpl::updateStyleSelector(bool shallow)
{
// kDebug() << "PENDING " << m_pendingStylesheets;
// Don't bother updating, since we haven't loaded all our style info yet.
if (m_pendingStylesheets > 0) {
// ... however, if the list of stylesheets changed, mark it as dirty
// so DOM ops can get an up-to-date version.
if (!shallow)
m_styleSheetListDirty = true;
return;
}
if (!shallow)
rebuildStyleSheetList();
rebuildStyleSelector();
recalcStyle(Force);
#if 0
m_styleSelectorDirty = true;
#endif
if ( renderer() )
renderer()->setNeedsLayoutAndMinMaxRecalc();
}
bool DocumentImpl::readyForLayout() const
{
return renderer() && haveStylesheetsLoaded() && (!isHTMLDocument() || (body() && body()->renderer()));
}
void DocumentImpl::rebuildStyleSheetList(bool force)
{
if ( !m_render || !attached() ) {
// Unless we're forced due to CSS DOM ops, we don't have to compute info
// when there is nothing to display
if (!force) {
m_styleSheetListDirty = true;
return;
}
}
// Mark us as clean, as we can call add on the list below, forcing us to re-enter
m_styleSheetListDirty = false;
QList<StyleSheetImpl*> oldStyleSheets = m_styleSheets->styleSheets;
m_styleSheets->styleSheets.clear();
QString sheetUsed = view() ? view()->part()->d->m_sheetUsed.replace("&&", "&") : QString();
bool autoselect = sheetUsed.isEmpty();
if (autoselect && !m_preferredStylesheetSet.isEmpty())
sheetUsed = m_preferredStylesheetSet.string();
NodeImpl *n;
for (int i=0 ; i<2 ; i++) {
m_availableSheets.clear();
m_availableSheets << i18n("Basic Page Style");
bool canResetSheet = false;
QString title;
for (n = this; n; n = n->traverseNextNode()) {
StyleSheetImpl *sheet = 0;
if (n->nodeType() == Node::PROCESSING_INSTRUCTION_NODE)
{
// Processing instruction (XML documents only)
ProcessingInstructionImpl* pi = static_cast<ProcessingInstructionImpl*>(n);
sheet = pi->sheet();
if (!sheet && !pi->localHref().isEmpty())
{
// Processing instruction with reference to an element in this document - e.g.
// <?xml-stylesheet href="#mystyle">, with the element
// <foo id="mystyle">heading { color: red; }</foo> at some location in
// the document
ElementImpl* elem = getElementById(pi->localHref());
if (elem) {
DOMString sheetText("");
NodeImpl *c;
for (c = elem->firstChild(); c; c = c->nextSibling()) {
if (c->nodeType() == Node::TEXT_NODE || c->nodeType() == Node::CDATA_SECTION_NODE)
sheetText += c->nodeValue();
}
CSSStyleSheetImpl *cssSheet = new CSSStyleSheetImpl(this);
cssSheet->parseString(sheetText);
pi->setStyleSheet(cssSheet);
sheet = cssSheet;
}
}
if (sheet) {
title = sheet->title().string();
if ((autoselect || title != sheetUsed) && sheet->disabled()) {
sheet = 0;
} else if (!title.isEmpty() && !pi->isAlternate() && sheetUsed.isEmpty()) {
sheetUsed = title;
sheet->setDisabled(false);
}
}
}
else if (n->isHTMLElement() && ( n->id() == ID_LINK || n->id() == ID_STYLE) ) {
if ( n->id() == ID_LINK ) {
HTMLLinkElementImpl* l = static_cast<HTMLLinkElementImpl*>(n);
if (l->isCSSStyleSheet()) {
sheet = l->sheet();
if (sheet || l->isLoading() || l->isAlternate() )
title = l->getAttribute(ATTR_TITLE).string();
if ((autoselect || title != sheetUsed) && l->isDisabled()) {
sheet = 0;
} else if (!title.isEmpty() && !l->isAlternate() && sheetUsed.isEmpty()) {
sheetUsed = title;
l->setDisabled(false);
}
}
}
else {
// <STYLE> element
HTMLStyleElementImpl* s = static_cast<HTMLStyleElementImpl*>(n);
if (!s->isLoading()) {
sheet = s->sheet();
if (sheet) title = s->getAttribute(ATTR_TITLE).string();
}
if (!title.isEmpty() && sheetUsed.isEmpty())
sheetUsed = title;
}
}
else if (n->isHTMLElement() && n->id() == ID_BODY) {
// <BODY> element (doesn't contain styles as such but vlink="..." and friends
// are treated as style declarations)
sheet = static_cast<HTMLBodyElementImpl*>(n)->sheet();
}
if ( !title.isEmpty() ) {
if ( title != sheetUsed )
sheet = 0; // don't use it
title = title.replace('&', "&&");
if ( !m_availableSheets.contains( title ) )
m_availableSheets.append( title );
title.clear();
}
if (sheet) {
sheet->ref();
m_styleSheets->styleSheets.append(sheet);
}
// For HTML documents, stylesheets are not allowed within/after the <BODY> tag. So we
// can stop searching here.
if (isHTMLDocument() && n->id() == ID_BODY) {
canResetSheet = !canResetSheet;
break;
}
}
// we're done if we don't select an alternative sheet
// or we found the sheet we selected
if (sheetUsed.isEmpty() ||
(!canResetSheet && tokenizer()) ||
m_availableSheets.contains(sheetUsed)) {
break;
}
// the alternative sheet we used doesn't exist anymore
// so try from scratch again
if (view())
view()->part()->d->m_sheetUsed.clear();
if (!m_preferredStylesheetSet.isEmpty() && !(sheetUsed == m_preferredStylesheetSet))
sheetUsed = m_preferredStylesheetSet.string();
else
sheetUsed.clear();
autoselect = true;
}
// Include programmatically added style sheets
if (m_addedStyleSheets) {
foreach (StyleSheetImpl* sh, m_addedStyleSheets->styleSheets) {
if (sh->isCSSStyleSheet() && !sh->disabled())
m_styleSheets->add(sh);
}
}
// De-reference all the stylesheets in the old list
foreach ( StyleSheetImpl* sh, oldStyleSheets)
sh->deref();
}
void DocumentImpl::rebuildStyleSelector()
{
if ( !m_render || !attached() )
return;
// Create a new style selector
delete m_styleSelector;
QString usersheet = m_usersheet;
if ( m_view && m_view->mediaType() == "print" )
usersheet += m_printSheet;
m_styleSelector = new CSSStyleSelector( this, usersheet, m_styleSheets, m_url,
!inCompatMode() );
m_styleSelectorDirty = false;
}
void DocumentImpl::setBaseURL(const KUrl& _baseURL)
{
m_baseURL = _baseURL;
if (m_elemSheet)
m_elemSheet->setHref( baseURL().url() );
}
void DocumentImpl::setHoverNode(NodeImpl *newHoverNode)
{
NodeImpl* oldHoverNode = m_hoverNode;
if (newHoverNode ) newHoverNode->ref();
m_hoverNode = newHoverNode;
if ( oldHoverNode ) oldHoverNode->deref();
}
void DocumentImpl::setActiveNode(NodeImpl* newActiveNode)
{
NodeImpl* oldActiveNode = m_activeNode;
if (newActiveNode ) newActiveNode->ref();
m_activeNode = newActiveNode;
if ( oldActiveNode ) oldActiveNode->deref();
}
void DocumentImpl::quietResetFocus()
{
assert(m_focusNode != this);
if (m_focusNode) {
if (m_focusNode->active())
setActiveNode(0);
m_focusNode->setFocus(false);
m_focusNode->deref();
}
m_focusNode = 0;
//We're blurring. Better clear the Qt focus/give it to the view...
if (view())
view()->setFocus();
}
void DocumentImpl::setFocusNode(NodeImpl *newFocusNode)
{
// don't process focus changes while detaching
if( !m_render ) return;
// See if the new node is really focusable. It might not be
// if focus() was called explicitly.
if (newFocusNode && !newFocusNode->isFocusable())
return;
// Make sure newFocusNode is actually in this document
if (newFocusNode && (newFocusNode->document() != this))
return;
if (m_focusNode != newFocusNode) {
NodeImpl *oldFocusNode = m_focusNode;
// We are blurring, so m_focusNode ATM is 0; this is observable to the
// event handlers.
m_focusNode = 0;
// Remove focus from the existing focus node (if any)
if (oldFocusNode) {
if (oldFocusNode->active())
oldFocusNode->setActive(false);
oldFocusNode->setFocus(false);
if (oldFocusNode->renderer() && oldFocusNode->renderer()->isWidget()) {
// Editable widgets may need to dispatch CHANGE_EVENT
RenderWidget* rw = static_cast<RenderWidget*>(oldFocusNode->renderer());
if (rw->isRedirectedWidget())
rw->handleFocusOut();
}
oldFocusNode->dispatchHTMLEvent(EventImpl::BLUR_EVENT,false,false);
oldFocusNode->dispatchUIEvent(EventImpl::DOMFOCUSOUT_EVENT);
if ((oldFocusNode == this) && oldFocusNode->hasOneRef()) {
oldFocusNode->deref(); // may delete this, if there are not kids keeping it alive...
// so we better not add any.
return;
}
else {
oldFocusNode->deref();
}
}
// It's possible that one of the blur, etc. handlers has already set focus.
// in that case, we don't want to override it.
if (!m_focusNode && newFocusNode) {
// Set focus on the new node
m_focusNode = newFocusNode;
m_focusNode->ref();
m_focusNode->dispatchHTMLEvent(EventImpl::FOCUS_EVENT,false,false);
if (m_focusNode != newFocusNode) return;
m_focusNode->dispatchUIEvent(EventImpl::DOMFOCUSIN_EVENT);
if (m_focusNode != newFocusNode) return;
m_focusNode->setFocus();
if (m_focusNode != newFocusNode) return;
// eww, I suck. set the qt focus correctly
// ### find a better place in the code for this
if (view()) {
if (!m_focusNode->renderer() || !m_focusNode->renderer()->isWidget())
view()->setFocus();
else if (static_cast<RenderWidget*>(m_focusNode->renderer())->widget())
{
if (view()->isVisible())
static_cast<RenderWidget*>(m_focusNode->renderer())->widget()->setFocus();
}
}
} else {
//We're blurring. Better clear the Qt focus/give it to the view...
if (view())
view()->setFocus();
}
updateRendering();
}
}
void DocumentImpl::setCSSTarget(NodeImpl* n)
{
if (n == m_cssTarget)
return;
if (m_cssTarget) {
m_cssTarget->setChanged();
m_cssTarget->deref();
}
m_cssTarget = n;
if (n) {
n->setChanged();
n->ref();
}
}
void DocumentImpl::attachNodeIterator(NodeIteratorImpl *ni)
{
m_nodeIterators.append(ni);
}
void DocumentImpl::detachNodeIterator(NodeIteratorImpl *ni)
{
int i = m_nodeIterators.indexOf(ni);
if (i != -1)
m_nodeIterators.removeAt(i);
}
void DocumentImpl::notifyBeforeNodeRemoval(NodeImpl *n)
{
QListIterator<NodeIteratorImpl*> it(m_nodeIterators);
while (it.hasNext())
it.next()->notifyBeforeNodeRemoval(n);
}
bool DocumentImpl::isURLAllowed(const QString& url) const
{
KHTMLPart *thisPart = part();
KUrl newURL(completeURL(url));
newURL.setRef(QString());
if (KHTMLGlobal::defaultHTMLSettings()->isAdFiltered( newURL.url() ))
return false;
// Prohibit non-file URLs if we are asked to.
- if (!thisPart || ( thisPart->onlyLocalReferences() && newURL.protocol() != "file" && newURL.protocol() != "data" ))
+ if (!thisPart || ( thisPart->onlyLocalReferences() && newURL.scheme() != "file" && newURL.scheme() != "data" ))
return false;
// do we allow this suburl ?
- if (newURL.protocol() != "javascript" && !KAuthorized::authorizeUrlAction("redirect", thisPart->url(), newURL))
+ if (newURL.scheme() != "javascript" && !KAuthorized::authorizeUrlAction("redirect", thisPart->url(), newURL))
return false;
// We allow one level of self-reference because some sites depend on that.
// But we don't allow more than one.
bool foundSelfReference = false;
for (KHTMLPart *part = thisPart; part; part = part->parentPart()) {
KUrl partURL = part->url();
partURL.setRef(QString());
if (partURL == newURL) {
if (foundSelfReference)
return false;
foundSelfReference = true;
}
}
return true;
}
void DocumentImpl::setDesignMode(bool b)
{
if (part())
part()->setEditable(b);
}
bool DocumentImpl::designMode() const
{
return part() ? part()->isEditable() : false;
}
EventImpl *DocumentImpl::createEvent(const DOMString &eventType, int &exceptioncode)
{
if (eventType == "UIEvents" || eventType == "UIEvent")
return new UIEventImpl();
else if (eventType == "MouseEvents" || eventType == "MouseEvent")
return new MouseEventImpl();
else if (eventType == "TextEvent")
return new TextEventImpl();
else if (eventType == "KeyboardEvent")
return new KeyboardEventImpl();
else if (eventType == "MutationEvents" || eventType == "MutationEvent")
return new MutationEventImpl();
else if (eventType == "HTMLEvents" || eventType == "Events" ||
eventType == "HTMLEvent" || eventType == "Event")
return new EventImpl();
else {
exceptioncode = DOMException::NOT_SUPPORTED_ERR;
return 0;
}
}
CSSStyleDeclarationImpl *DocumentImpl::getOverrideStyle(ElementImpl* /*elt*/, DOMStringImpl* /*pseudoElt*/)
{
return 0; // ###
}
void DocumentImpl::abort()
{
if (m_inSyncLoad) {
assert(m_inSyncLoad->isRunning());
m_inSyncLoad->exit();
}
if (m_loadingXMLDoc)
m_loadingXMLDoc->deref(this);
m_loadingXMLDoc = 0;
}
void DocumentImpl::load(const DOMString &uri)
{
if (m_inSyncLoad) {
assert(m_inSyncLoad->isRunning());
m_inSyncLoad->exit();
}
m_hadLoadError = false;
if (m_loadingXMLDoc)
m_loadingXMLDoc->deref(this);
// Use the document loader to retrieve the XML file. We use CachedCSSStyleSheet because
// this is an easy way to retrieve an arbitrary text file... it is not specific to
// stylesheets.
// ### Note: By loading the XML document this way we do not get the proper decoding
// of the data retrieved from the server based on the character set, as happens with
// HTML files. Need to look into a way of using the decoder in CachedCSSStyleSheet.
m_docLoading = true;
m_loadingXMLDoc = m_docLoader->requestStyleSheet(uri.string(),QString(),"text/xml");
if (!m_loadingXMLDoc) {
m_docLoading = false;
return;
}
m_loadingXMLDoc->ref(this);
if (!m_async && m_docLoading) {
assert(!m_inSyncLoad);
m_inSyncLoad = new QEventLoop();
m_inSyncLoad->exec();
// returning from event loop:
assert(!m_inSyncLoad->isRunning());
delete m_inSyncLoad;
m_inSyncLoad = 0;
}
}
void DocumentImpl::loadXML(const DOMString &source)
{
open(false);
write(source);
finishParsing();
close();
dispatchHTMLEvent(EventImpl::LOAD_EVENT,false,false);
}
void DocumentImpl::setStyleSheet(const DOM::DOMString &url, const DOM::DOMString &sheet, const DOM::DOMString &/*charset*/, const DOM::DOMString &mimetype)
{
if (!m_hadLoadError) {
m_url = url.string();
loadXML(khtml::isAcceptableCSSMimetype(mimetype) ? sheet : "");
}
m_docLoading = false;
if (m_inSyncLoad) {
assert(m_inSyncLoad->isRunning());
m_inSyncLoad->exit();
}
assert(m_loadingXMLDoc != 0);
m_loadingXMLDoc->deref(this);
m_loadingXMLDoc = 0;
}
void DocumentImpl::error(int err, const QString &text)
{
m_docLoading = false;
if (m_inSyncLoad) {
assert(m_inSyncLoad->isRunning());
m_inSyncLoad->exit();
}
m_hadLoadError = true;
int exceptioncode = 0;
EventImpl *evt = new EventImpl(EventImpl::ERROR_EVENT,false,false);
if (err != 0)
evt->setMessage(KIO::buildErrorString(err,text));
else
evt->setMessage(text);
evt->ref();
dispatchEvent(evt,exceptioncode,true);
evt->deref();
assert(m_loadingXMLDoc != 0);
m_loadingXMLDoc->deref(this);
m_loadingXMLDoc = 0;
}
void DocumentImpl::defaultEventHandler(EventImpl *evt)
{
if ( evt->id() == EventImpl::KHTML_CONTENTLOADED_EVENT && !evt->propagationStopped() && !evt->defaultPrevented() )
contentLoaded();
}
void DocumentImpl::setHTMLWindowEventListener(EventName id, EventListener *listener)
{
windowEventTarget()->listenerList().setHTMLEventListener(id, listener);
}
void DocumentImpl::setHTMLWindowEventListener(unsigned id, EventListener *listener)
{
windowEventTarget()->listenerList().setHTMLEventListener(EventName::fromId(id), listener);
}
EventListener *DocumentImpl::getHTMLWindowEventListener(EventName id)
{
return windowEventTarget()->listenerList().getHTMLEventListener(id);
}
EventListener *DocumentImpl::getHTMLWindowEventListener(unsigned id)
{
return windowEventTarget()->listenerList().getHTMLEventListener(EventName::fromId(id));
}
void DocumentImpl::addWindowEventListener(EventName id, EventListener *listener, const bool useCapture)
{
windowEventTarget()->listenerList().addEventListener(id, listener, useCapture);
}
void DocumentImpl::removeWindowEventListener(EventName id, EventListener *listener, bool useCapture)
{
windowEventTarget()->listenerList().removeEventListener(id, listener, useCapture);
}
bool DocumentImpl::hasWindowEventListener(EventName id)
{
return windowEventTarget()->listenerList().hasEventListener(id);
}
EventListener *DocumentImpl::createHTMLEventListener(const QString& code, const QString& name, NodeImpl* node)
{
return part() ? part()->createHTMLEventListener(code, name, node) : 0;
}
void DocumentImpl::dispatchImageLoadEventSoon(HTMLImageElementImpl *image)
{
m_imageLoadEventDispatchSoonList.append(image);
if (!m_imageLoadEventTimer) {
m_imageLoadEventTimer = startTimer(0);
}
}
void DocumentImpl::removeImage(HTMLImageElementImpl *image)
{
// Remove instances of this image from both lists.
m_imageLoadEventDispatchSoonList.removeAll(image);
m_imageLoadEventDispatchingList.removeAll(image);
if (m_imageLoadEventDispatchSoonList.isEmpty() && m_imageLoadEventTimer) {
killTimer(m_imageLoadEventTimer);
m_imageLoadEventTimer = 0;
}
}
void DocumentImpl::dispatchImageLoadEventsNow()
{
// need to avoid re-entering this function; if new dispatches are
// scheduled before the parent finishes processing the list, they
// will set a timer and eventually be processed
if (!m_imageLoadEventDispatchingList.isEmpty()) {
return;
}
if (m_imageLoadEventTimer) {
killTimer(m_imageLoadEventTimer);
m_imageLoadEventTimer = 0;
}
m_imageLoadEventDispatchingList = m_imageLoadEventDispatchSoonList;
m_imageLoadEventDispatchSoonList.clear();
while (!m_imageLoadEventDispatchingList.isEmpty())
m_imageLoadEventDispatchingList.takeFirst()->dispatchLoadEvent();
m_imageLoadEventDispatchingList.clear();
}
void DocumentImpl::timerEvent(QTimerEvent *e)
{
assert(e->timerId() == m_imageLoadEventTimer);
Q_UNUSED(e);
dispatchImageLoadEventsNow();
}
/*void DocumentImpl::setDecoderCodec(const QTextCodec *codec)
{
m_decoderMibEnum = codec->mibEnum();
}*/
HTMLPartContainerElementImpl *DocumentImpl::ownerElement() const
{
KHTMLPart *childPart = part();
if (!childPart)
return 0;
ChildFrame *childFrame = childPart->d->m_frame;
if (!childFrame)
return 0;
return childFrame->m_partContainerElement.data();
}
khtml::SecurityOrigin* DocumentImpl::origin() const
{
if (!m_origin)
m_origin = SecurityOrigin::create(URL());
return m_origin.get();
}
void DocumentImpl::setOrigin(khtml::SecurityOrigin* newOrigin)
{
assert(origin()->isEmpty());
m_origin = newOrigin;
}
DOMString DocumentImpl::domain() const
{
return origin()->domain();
}
void DocumentImpl::setDomain(const DOMString &newDomain)
{
// ### this test really should move to SecurityOrigin..
DOMString oldDomain = origin()->domain();
// Both NS and IE specify that changing the domain is only allowed when
// the new domain is a suffix of the old domain.
int oldLength = oldDomain.length();
int newLength = newDomain.length();
if ( newLength < oldLength ) { // e.g. newDomain=kde.org (7) and m_domain=www.kde.org (11)
DOMString test = oldDomain.copy();
DOMString reference = newDomain.lower();
if ( test[oldLength - newLength - 1] == '.' ) // Check that it's a subdomain, not e.g. "de.org"
{
test.remove( 0, oldLength - newLength ); // now test is "kde.org" from m_domain
if ( test == reference ) // and we check that it's the same thing as newDomain
m_origin->setDomainFromDOM( reference.string() );
}
} else if ( oldLength == newLength ) {
// It's OK and not a no-op to set the domain to the present one:
// we want to set the 'set from DOM' bit in that case
DOMString reference = newDomain.lower();
if ( oldDomain.lower() == reference )
m_origin->setDomainFromDOM( reference.string() );
}
}
DOMString DocumentImpl::toString() const
{
DOMString result;
for (NodeImpl *child = firstChild(); child != NULL; child = child->nextSibling()) {
result += child->toString();
}
return result;
}
void DOM::DocumentImpl::setRestoreState( const QStringList &s)
{
m_state = s;
m_stateRestorePos = 0;
}
KHTMLView* DOM::DocumentImpl::view() const
{
return m_view;
}
KHTMLPart* DOM::DocumentImpl::part() const
{
// ### TODO: make this independent from a KHTMLView one day.
return view() ? view()->part() : 0;
}
DynamicNodeListImpl::Cache* DOM::DocumentImpl::acquireCachedNodeListInfo(
DynamicNodeListImpl::CacheFactory* factory, NodeImpl* base, int type)
{
//### might want to flush the dict when the version number
//changes
DynamicNodeListImpl::CacheKey key(base, type);
//Check to see if we have this sort of item cached.
DynamicNodeListImpl::Cache* cached =
(type == DynamicNodeListImpl::UNCACHEABLE) ? 0 : m_nodeListCache.value(key.hash());
if (cached) {
if (cached->key == key) {
cached->ref(); //Add the nodelist's reference
return cached;
} else {
//Conflict. Drop our reference to the old item.
cached->deref();
}
}
//Nothing to reuse, make a new item.
DynamicNodeListImpl::Cache* newInfo = factory();
newInfo->key = key;
newInfo->clear(this);
newInfo->ref(); //Add the nodelist's reference
if (type != DynamicNodeListImpl::UNCACHEABLE) {
newInfo->ref(); //Add the cache's reference
m_nodeListCache.insert(key.hash(), newInfo);
}
return newInfo;
}
void DOM::DocumentImpl::releaseCachedNodeListInfo(DynamicNodeListImpl::Cache* entry)
{
entry->deref();
}
bool DOM::DocumentImpl::isSVGDocument() const
{
return (documentElement()->id() == WebCore::SVGNames::svgTag.id());
}
const WebCore::SVGDocumentExtensions* DOM::DocumentImpl::svgExtensions()
{
return m_svgExtensions;
}
WebCore::SVGDocumentExtensions* DOM::DocumentImpl::accessSVGExtensions()
{
if (!m_svgExtensions) {
m_svgExtensions = new WebCore::SVGDocumentExtensions(this);
}
return m_svgExtensions;
}
// ----------------------------------------------------------------------------
// Support for Javascript execCommand, and related methods
JSEditor *DocumentImpl::jsEditor()
{
if (!m_jsEditor)
m_jsEditor = new JSEditor(this);
return m_jsEditor;
}
bool DocumentImpl::execCommand(const DOMString &command, bool userInterface, const DOMString &value)
{
kDebug() << "[execute command]" << command << userInterface << value << endl;
return jsEditor()->execCommand(jsEditor()->commandImp(command), userInterface, value);
}
bool DocumentImpl::queryCommandEnabled(const DOMString &command)
{
return jsEditor()->queryCommandEnabled(jsEditor()->commandImp(command));
}
bool DocumentImpl::queryCommandIndeterm(const DOMString &command)
{
return jsEditor()->queryCommandIndeterm(jsEditor()->commandImp(command));
}
bool DocumentImpl::queryCommandState(const DOMString &command)
{
return jsEditor()->queryCommandState(jsEditor()->commandImp(command));
}
bool DocumentImpl::queryCommandSupported(const DOMString &command)
{
kDebug() << "[query command supported]" << command << endl;
return jsEditor()->queryCommandSupported(jsEditor()->commandImp(command));
}
DOMString DocumentImpl::queryCommandValue(const DOMString &command)
{
return jsEditor()->queryCommandValue(jsEditor()->commandImp(command));
}
// ----------------------------------------------------------------------------
// DOM3 XPath, from XPathEvaluator interface
khtml::XPathExpressionImpl* DocumentImpl::createExpression(DOMString &expression,
khtml::XPathNSResolverImpl *resolver,
int &exceptioncode )
{
XPathExpressionImpl* cand = new XPathExpressionImpl( expression, resolver );
if ((exceptioncode = cand->parseExceptionCode())) {
delete cand;
return 0;
}
return cand;
}
khtml::XPathNSResolverImpl* DocumentImpl::createNSResolver( NodeImpl *nodeResolver )
{
return nodeResolver ? new DefaultXPathNSResolverImpl(nodeResolver) : 0;
}
khtml::XPathResultImpl * DocumentImpl::evaluate( DOMString &expression,
NodeImpl *contextNode,
khtml::XPathNSResolverImpl *resolver,
unsigned short type,
khtml::XPathResultImpl * /*result*/,
int &exceptioncode )
{
XPathExpressionImpl *expr = createExpression( expression, resolver, exceptioncode );
if ( exceptioncode )
return 0;
XPathResultImpl *res = expr->evaluate( contextNode, type, 0, exceptioncode );
delete expr; // don't need it anymore.
if ( exceptioncode ) {
delete res;
return 0;
}
return res;
}
// ----------------------------------------------------------------------------
WindowEventTargetImpl::WindowEventTargetImpl(DOM::DocumentImpl* owner):
m_owner(owner)
{}
EventTargetImpl::Type WindowEventTargetImpl::eventTargetType() const
{
return WINDOW;
}
DocumentImpl* WindowEventTargetImpl::eventTargetDocument()
{
return m_owner;
}
KJS::Window* WindowEventTargetImpl::window()
{
if (m_owner->part())
return KJS::Window::retrieveWindow(m_owner->part());
else
return 0;
}
// ----------------------------------------------------------------------------
DocumentFragmentImpl::DocumentFragmentImpl(DocumentImpl *doc) : NodeBaseImpl(doc)
{
}
DocumentFragmentImpl::DocumentFragmentImpl(const DocumentFragmentImpl &other)
: NodeBaseImpl(other)
{
}
DOMString DocumentFragmentImpl::nodeName() const
{
return "#document-fragment";
}
unsigned short DocumentFragmentImpl::nodeType() const
{
return Node::DOCUMENT_FRAGMENT_NODE;
}
// DOM Section 1.1.1
bool DocumentFragmentImpl::childTypeAllowed( unsigned short type )
{
switch (type) {
case Node::ELEMENT_NODE:
case Node::PROCESSING_INSTRUCTION_NODE:
case Node::COMMENT_NODE:
case Node::TEXT_NODE:
case Node::CDATA_SECTION_NODE:
case Node::ENTITY_REFERENCE_NODE:
return true;
break;
default:
return false;
}
}
DOMString DocumentFragmentImpl::toString() const
{
DOMString result;
for (NodeImpl *child = firstChild(); child != NULL; child = child->nextSibling()) {
if (child->nodeType() == Node::COMMENT_NODE || child->nodeType() == Node::PROCESSING_INSTRUCTION_NODE)
continue;
result += child->toString();
}
return result;
}
WTF::PassRefPtr<NodeImpl> DocumentFragmentImpl::cloneNode ( bool deep )
{
WTF::RefPtr<DocumentFragmentImpl> clone = new DocumentFragmentImpl( docPtr() );
if (deep)
cloneChildNodes(clone.get());
return clone;
}
// ----------------------------------------------------------------------------
DocumentTypeImpl::DocumentTypeImpl(DOMImplementationImpl *implementation, DocumentImpl *doc,
const DOMString &qualifiedName, const DOMString &publicId,
const DOMString &systemId)
: NodeImpl(doc), m_implementation(implementation),
m_qualifiedName(qualifiedName), m_publicId(publicId), m_systemId(systemId)
{
m_implementation->ref();
m_entities = 0;
m_notations = 0;
// if doc is 0, it is not attached to a document and / or
// therefore does not provide entities or notations. (DOM Level 3)
}
DocumentTypeImpl::~DocumentTypeImpl()
{
m_implementation->deref();
if (m_entities)
m_entities->deref();
if (m_notations)
m_notations->deref();
}
DOMString DocumentTypeImpl::toString() const
{
DOMString result = "<!DOCTYPE ";
result += m_qualifiedName;
if (!m_publicId.isEmpty()) {
result += " PUBLIC \"";
result += m_publicId;
result += "\" \"";
result += m_systemId;
result += "\"";
} else if (!m_systemId.isEmpty()) {
result += " SYSTEM \"";
result += m_systemId;
result += "\"";
}
if (!m_subset.isEmpty()) {
result += " [";
result += m_subset;
result += "]";
}
result += ">";
return result;
}
DOMString DocumentTypeImpl::nodeName() const
{
return name();
}
unsigned short DocumentTypeImpl::nodeType() const
{
return Node::DOCUMENT_TYPE_NODE;
}
// DOM Section 1.1.1
bool DocumentTypeImpl::childTypeAllowed( unsigned short /*type*/ )
{
return false;
}
WTF::PassRefPtr<NodeImpl> DocumentTypeImpl::cloneNode ( bool /*deep*/ )
{
DocumentTypeImpl *clone = new DocumentTypeImpl(implementation(),
0,
name(), publicId(),
systemId());
// ### copy entities etc.
return clone;
}
NamedNodeMapImpl * DocumentTypeImpl::entities() const
{
if ( !m_entities ) {
m_entities = new GenericRONamedNodeMapImpl( docPtr() );
m_entities->ref();
}
return m_entities;
}
NamedNodeMapImpl * DocumentTypeImpl::notations() const
{
if ( !m_notations ) {
m_notations = new GenericRONamedNodeMapImpl( docPtr() );
m_notations->ref();
}
return m_notations;
}
void XMLDocumentImpl::close()
{
bool doload = !parsing() && m_tokenizer;
DocumentImpl::close();
if (doload) {
document()->dispatchWindowEvent(EventImpl::LOAD_EVENT, false, false);
}
}
// kate: indent-width 4; replace-tabs on; tab-width 8; space-indent on;
diff --git a/khtml/xml/security_origin.cpp b/khtml/xml/security_origin.cpp
index 6f6c112676..6150e99434 100644
--- a/khtml/xml/security_origin.cpp
+++ b/khtml/xml/security_origin.cpp
@@ -1,219 +1,219 @@
/*
* Copyright (C) 2007 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "security_origin.h"
#include <wtf/RefPtr.h>
#include <kprotocolinfo.h>
namespace khtml {
static bool isDefaultPortForProtocol(unsigned short port, const QString& proto)
{
return (port == 80 && proto == QLatin1String("http")) ||
(port == 443 && proto == QLatin1String("https"));
}
SecurityOrigin::SecurityOrigin(const KUrl& url) :
- m_protocol(url.protocol().toLower())
+ m_protocol(url.scheme().toLower())
, m_host(url.host().toLower())
, m_port(url.port())
, m_domainWasSetInDOM(false)
, m_isUnique(false)
{
// These protocols do not create security origins; the owner frame provides the origin
if (m_protocol == "about" || m_protocol == "javascript")
m_protocol = "";
// For edge case URLs that were probably misparsed, make sure that the origin is unique.
if (m_host.isEmpty() && KProtocolInfo::protocolClass(m_protocol) == QLatin1String(":internet"))
m_isUnique = true;
// document.domain starts as m_host, but can be set by the DOM.
m_domain = m_host;
if (url.port() == -1 || isDefaultPortForProtocol(m_port, m_protocol))
m_port = 0;
}
SecurityOrigin::SecurityOrigin(const SecurityOrigin* other) :
m_protocol(other->m_protocol)
, m_host(other->m_host)
, m_domain(other->m_domain)
, m_port(other->m_port)
, m_domainWasSetInDOM(other->m_domainWasSetInDOM)
, m_isUnique(other->m_isUnique)
{
}
bool SecurityOrigin::isEmpty() const
{
return m_protocol.isEmpty();
}
SecurityOrigin* SecurityOrigin::create(const KUrl& url)
{
if (!url.isValid())
return new SecurityOrigin(KUrl());
return new SecurityOrigin(url);
}
SecurityOrigin* SecurityOrigin::createEmpty()
{
return create(KUrl());
}
void SecurityOrigin::setDomainFromDOM(const QString& newDomain)
{
m_domainWasSetInDOM = true;
m_domain = newDomain.toLower();
}
bool SecurityOrigin::canAccess(const SecurityOrigin* other) const
{
if (isUnique() || other->isUnique())
return false;
// Here are two cases where we should permit access:
//
// 1) Neither document has set document.domain. In this case, we insist
// that the scheme, host, and port of the URLs match.
//
// 2) Both documents have set document.domain. In this case, we insist
// that the documents have set document.domain to the same value and
// that the scheme of the URLs match.
//
// This matches the behavior of Firefox 2 and Internet Explorer 6.
//
// Internet Explorer 7 and Opera 9 are more strict in that they require
// the port numbers to match when both pages have document.domain set.
//
// FIXME: Evaluate whether we can tighten this policy to require matched
// port numbers.
//
// Opera 9 allows access when only one page has set document.domain, but
// this is a security vulnerability.
if (m_protocol == other->m_protocol) {
if (!m_domainWasSetInDOM && !other->m_domainWasSetInDOM) {
if (m_host == other->m_host && m_port == other->m_port)
return true;
} else if (m_domainWasSetInDOM && other->m_domainWasSetInDOM) {
if (m_domain == other->m_domain)
return true;
}
}
return false;
}
bool SecurityOrigin::canRequest(const KUrl& url) const
{
if (isUnique())
return false;
WTF::RefPtr<SecurityOrigin> targetOrigin = SecurityOrigin::create(url);
if (targetOrigin->isUnique())
return false;
// We call isSameSchemeHostPort here instead of canAccess because we want
// to ignore document.domain effects.
if (isSameSchemeHostPort(targetOrigin.get()))
return true;
return false;
}
bool SecurityOrigin::taintsCanvas(const KUrl& url) const
{
if (canRequest(url))
return false;
// This function exists because we treat data URLs as having a unique origin,
// contrary to the current (9/19/2009) draft of the HTML5 specification.
// We still want to let folks paint data URLs onto untainted canvases, so
// we special case data URLs below. If we change to match HTML5 w.r.t.
// data URL security, then we can remove this function in favor of
// !canRequest.
- if (url.protocol().toLower() == QLatin1String("data"))
+ if (url.scheme().toLower() == QLatin1String("data"))
return false;
return true;
}
void SecurityOrigin::makeUnique()
{
m_isUnique = true;
}
QString SecurityOrigin::toString() const
{
if (isEmpty())
return "null";
if (isUnique())
return "null";
if (m_protocol == "file")
return QString("file://");
QString result;
result += m_protocol;
result += "://";
result += m_host;
if (m_port) {
result += ":";
result += QString::number(m_port);
}
return result;
}
SecurityOrigin* SecurityOrigin::createFromString(const QString& originString)
{
return SecurityOrigin::create(KUrl(originString));
}
bool SecurityOrigin::isSameSchemeHostPort(const SecurityOrigin* other) const
{
if (m_host != other->m_host)
return false;
if (m_protocol != other->m_protocol)
return false;
if (m_port != other->m_port)
return false;
return true;
}
} // namespace khtml
diff --git a/kio/kfile/kfiledialog.cpp b/kio/kfile/kfiledialog.cpp
index a5e6acc7c9..20cfe7d279 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;
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,
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,
const QString& filter,
QWidget *parent,
const QString& caption)
{
return KFileDialogPrivate::getOpenUrls(startDir, filter, parent, caption, 0);
}
KUrl::List 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
{
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 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.protocol() == "kfiledialog";
+ 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(
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/kpropertiesdialog.cpp b/kio/kfile/kpropertiesdialog.cpp
index 099c5bb49b..003b088923 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().protocol());
+ ).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().protocol();
+ QString protocol = properties->kurl().scheme();
d->bKDesktopMode = protocol == QLatin1String("desktop") ||
- properties->currentDir().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().protocol().toLower() == "trash" );
+ 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.protocol() != protocol )
+ 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() );
}
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().protocol().toLower() == "trash" );
+ 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().protocol() == QLatin1String("desktop") ||
- properties->currentDir().protocol() == QLatin1String("desktop");
+ 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/kio/accessmanagerreply_p.cpp b/kio/kio/accessmanagerreply_p.cpp
index af107c18da..dcc2eb684b 100644
--- a/kio/kio/accessmanagerreply_p.cpp
+++ b/kio/kio/accessmanagerreply_p.cpp
@@ -1,436 +1,436 @@
/*
* This file is part of the KDE project.
*
* Copyright (C) 2008 Alex Merry <alex.merry @ kdemail.net>
* Copyright (C) 2008 - 2009 Urs Wolfer <uwolfer @ kde.org>
* 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.
*
*/
#include "accessmanagerreply_p.h"
#include "accessmanager.h"
#include "job.h"
#include "scheduler.h"
#include <kdebug.h>
#include <kauthorized.h>
#include <kprotocolinfo.h>
#include <kmimetype.h>
#include <QtNetwork/QSslConfiguration>
#define QL1S(x) QLatin1String(x)
#define QL1C(x) QLatin1Char(x)
namespace KDEPrivate {
AccessManagerReply::AccessManagerReply(const QNetworkAccessManager::Operation &op,
const QNetworkRequest &request,
KIO::SimpleJob *kioJob,
bool emitReadyReadOnMetaDataChange,
QObject *parent)
:QNetworkReply(parent),
m_metaDataRead(false),
m_ignoreContentDisposition(false),
m_emitReadyReadOnMetaDataChange(emitReadyReadOnMetaDataChange),
m_kioJob(kioJob)
{
setRequest(request);
setOpenMode(QIODevice::ReadOnly);
setUrl(request.url());
setOperation(op);
setError(NoError, QString());
if (!request.sslConfiguration().isNull())
setSslConfiguration(request.sslConfiguration());
if (kioJob) {
connect(kioJob, SIGNAL(redirection(KIO::Job*, const KUrl&)), SLOT(slotRedirection(KIO::Job*, const KUrl&)));
connect(kioJob, SIGNAL(percent(KJob*, unsigned long)), SLOT(slotPercent(KJob*, unsigned long)));
if (qobject_cast<KIO::StatJob*>(kioJob)) {
connect(kioJob, SIGNAL(result(KJob *)), SLOT(slotStatResult(KJob *)));
} else {
connect(kioJob, SIGNAL(result(KJob *)), SLOT(slotResult(KJob *)));
connect(kioJob, SIGNAL(data(KIO::Job *, const QByteArray &)),
SLOT(slotData(KIO::Job *, const QByteArray &)));
connect(kioJob, SIGNAL(mimetype(KIO::Job *, const QString&)),
SLOT(slotMimeType(KIO::Job *, const QString&)));
}
}
}
AccessManagerReply::~AccessManagerReply()
{
}
void AccessManagerReply::abort()
{
if (m_kioJob) {
m_kioJob.data()->kill();
m_kioJob.clear();
}
m_data.clear();
m_metaDataRead = false;
}
qint64 AccessManagerReply::bytesAvailable() const
{
return (QNetworkReply::bytesAvailable() + m_data.length());
}
qint64 AccessManagerReply::readData(char *data, qint64 maxSize)
{
const qint64 length = qMin(qint64(m_data.length()), maxSize);
if (length) {
qMemCopy(data, m_data.constData(), length);
m_data.remove(0, length);
}
return length;
}
bool AccessManagerReply::ignoreContentDisposition (KIO::Job* job)
{
if (m_ignoreContentDisposition) {
return true;
}
if (job->queryMetaData(QL1S("content-disposition-type")).isEmpty()) {
return true;
}
bool ok = false;
const int statusCode = attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(&ok);
if (!ok || statusCode < 200 || statusCode > 299) {
return true;
}
return false;
}
void AccessManagerReply::setIgnoreContentDisposition(bool on)
{
// kDebug(7044) << on;
m_ignoreContentDisposition = on;
}
void AccessManagerReply::setStatus(const QString& message, QNetworkReply::NetworkError code)
{
setError(code, message);
if (code != QNetworkReply::NoError) {
QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, Q_ARG(QNetworkReply::NetworkError, code));
}
QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
}
void AccessManagerReply::putOnHold()
{
if (!m_kioJob || isFinished())
return;
// kDebug(7044) << m_kioJob << m_data;
m_kioJob.data()->disconnect(this);
m_kioJob.data()->putOnHold();
m_kioJob.clear();
KIO::Scheduler::publishSlaveOnHold();
}
bool AccessManagerReply::isLocalRequest (const KUrl& url)
{
- const QString scheme (url.protocol());
+ const QString scheme (url.scheme());
return (KProtocolInfo::isKnownProtocol(scheme) &&
KProtocolInfo::protocolClass(scheme).compare(QL1S(":local"), Qt::CaseInsensitive) == 0);
}
void AccessManagerReply::readHttpResponseHeaders(KIO::Job *job)
{
if (!job || m_metaDataRead)
return;
KIO::MetaData metaData (job->metaData());
if (metaData.isEmpty()) {
// Allow handling of local resources such as man pages and file url...
if (isLocalRequest(url())) {
setHeader(QNetworkRequest::ContentLengthHeader, job->totalAmount(KJob::Bytes));
setAttribute(QNetworkRequest::HttpStatusCodeAttribute, "200");
emit metaDataChanged();
}
return;
}
// Set the encryption attribute and values...
QSslConfiguration sslConfig;
const bool isEncrypted = KIO::Integration::sslConfigFromMetaData(metaData, sslConfig);
setAttribute(QNetworkRequest::ConnectionEncryptedAttribute, isEncrypted);
if (isEncrypted)
setSslConfiguration(sslConfig);
// Set the raw header information...
const QStringList httpHeaders (metaData.value(QL1S("HTTP-Headers")).split(QL1C('\n'), QString::SkipEmptyParts));
if (httpHeaders.isEmpty()) {
if (metaData.contains(QL1S("charset"))) {
QString mimeType = header(QNetworkRequest::ContentTypeHeader).toString();
mimeType += QL1S(" ; charset=");
mimeType += metaData.value(QL1S("charset"));
kDebug(7044) << "changed content-type to" << mimeType;
setHeader(QNetworkRequest::ContentTypeHeader, mimeType.toUtf8());
}
} else {
Q_FOREACH(const QString& httpHeader, httpHeaders) {
int index = httpHeader.indexOf(QL1C(':'));
// Handle HTTP status line...
if (index == -1) {
// Except for the status line, all HTTP header must be an nvpair of
// type "<name>:<value>"
if (!httpHeader.startsWith(QL1S("HTTP/"), Qt::CaseInsensitive)) {
continue;
}
QStringList statusLineAttrs (httpHeader.split(QL1C(' '), QString::SkipEmptyParts));
if (statusLineAttrs.count() > 1) {
setAttribute(QNetworkRequest::HttpStatusCodeAttribute, statusLineAttrs.at(1));
}
if (statusLineAttrs.count() > 2) {
setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, statusLineAttrs.at(2));
}
continue;
}
const QString headerName = httpHeader.left(index);
QString headerValue = httpHeader.mid(index+1);
// Ignore cookie header since it is handled by the http ioslave.
if (headerName.startsWith(QL1S("set-cookie"), Qt::CaseInsensitive)) {
continue;
}
if (headerName.startsWith(QL1S("content-disposition"), Qt::CaseInsensitive) &&
ignoreContentDisposition(job)) {
continue;
}
// Without overridding the corrected mime-type sent by kio_http, add
// back the "charset=" portion of the content-type header if present.
if (headerName.startsWith(QL1S("content-type"), Qt::CaseInsensitive)) {
QString mimeType (header(QNetworkRequest::ContentTypeHeader).toString());
if (m_ignoreContentDisposition) {
// If the server returned application/octet-stream, try to determine the
// real content type from the disposition filename.
if (mimeType == KMimeType::defaultMimeType()) {
int accuracy = 0;
const QString fileName (metaData.value(QL1S("content-disposition-filename")));
KMimeType::Ptr mime = KMimeType::findByUrl((fileName.isEmpty() ? url().path() : fileName), 0, false, true, &accuracy);
if (!mime->isDefault() && accuracy == 100) {
mimeType = mime->name();
}
}
metaData.remove(QL1S("content-disposition-type"));
metaData.remove(QL1S("content-disposition-filename"));
}
if (!headerValue.contains(mimeType, Qt::CaseInsensitive)) {
index = headerValue.indexOf(QL1C(';'));
if (index == -1) {
headerValue = mimeType;
} else {
headerValue.replace(0, index, mimeType);
}
//kDebug(7044) << "Changed mime-type from" << mimeType << "to" << headerValue;
}
}
setRawHeader(headerName.trimmed().toUtf8(), headerValue.trimmed().toUtf8());
}
}
// Set the returned meta data as attribute...
setAttribute(static_cast<QNetworkRequest::Attribute>(KIO::AccessManager::MetaData), metaData.toVariant());
m_metaDataRead = true;
emit metaDataChanged();
}
int AccessManagerReply::jobError(KJob* kJob)
{
const int errCode = kJob->error();
switch (errCode)
{
case 0:
break; // No error;
case KIO::ERR_SLAVE_DEFINED:
case KIO::ERR_NO_CONTENT: // Sent by a 204 response is not an error condition.
setError(QNetworkReply::NoError, kJob->errorText());
//kDebug(7044) << "0 -> QNetworkReply::NoError";
break;
case KIO::ERR_IS_DIRECTORY:
// This error condition can happen if you click on an ftp link that points
// to a directory instead of a file, e.g. ftp://ftp.kde.org/pub
setHeader(QNetworkRequest::ContentTypeHeader, "inode/directory");
setError(QNetworkReply::NoError, kJob->errorText());
break;
case KIO::ERR_COULD_NOT_CONNECT:
setError(QNetworkReply::ConnectionRefusedError, kJob->errorText());
kDebug(7044) << "KIO::ERR_COULD_NOT_CONNECT -> QNetworkReply::ConnectionRefusedError";
break;
case KIO::ERR_UNKNOWN_HOST:
setError(QNetworkReply::HostNotFoundError, kJob->errorText());
kDebug(7044) << "KIO::ERR_UNKNOWN_HOST -> QNetworkReply::HostNotFoundError";
break;
case KIO::ERR_SERVER_TIMEOUT:
setError(QNetworkReply::TimeoutError, kJob->errorText());
kDebug(7044) << "KIO::ERR_SERVER_TIMEOUT -> QNetworkReply::TimeoutError";
break;
case KIO::ERR_USER_CANCELED:
case KIO::ERR_ABORTED:
setError(QNetworkReply::OperationCanceledError, kJob->errorText());
kDebug(7044) << "KIO::ERR_ABORTED -> QNetworkReply::OperationCanceledError";
break;
case KIO::ERR_UNKNOWN_PROXY_HOST:
setError(QNetworkReply::ProxyNotFoundError, kJob->errorText());
kDebug(7044) << "KIO::UNKNOWN_PROXY_HOST -> QNetworkReply::ProxyNotFoundError";
break;
case KIO::ERR_ACCESS_DENIED:
setError(QNetworkReply::ContentAccessDenied, kJob->errorText());
kDebug(7044) << "KIO::ERR_ACCESS_DENIED -> QNetworkReply::ContentAccessDenied";
break;
case KIO::ERR_WRITE_ACCESS_DENIED:
setError(QNetworkReply::ContentOperationNotPermittedError, kJob->errorText());
kDebug(7044) << "KIO::ERR_WRITE_ACCESS_DENIED -> QNetworkReply::ContentOperationNotPermittedError";
break;
case KIO::ERR_DOES_NOT_EXIST:
setError(QNetworkReply::ContentNotFoundError, kJob->errorText());
kDebug(7044) << "KIO::ERR_DOES_NOT_EXIST -> QNetworkReply::ContentNotFoundError";
break;
case KIO::ERR_COULD_NOT_AUTHENTICATE:
setError(QNetworkReply::AuthenticationRequiredError, kJob->errorText());
kDebug(7044) << "KIO::ERR_COULD_NOT_AUTHENTICATE -> QNetworkReply::AuthenticationRequiredError";
break;
case KIO::ERR_UNSUPPORTED_PROTOCOL:
case KIO::ERR_NO_SOURCE_PROTOCOL:
setError(QNetworkReply::ProtocolUnknownError, kJob->errorText());
kDebug(7044) << "KIO::ERR_UNSUPPORTED_PROTOCOL -> QNetworkReply::ProtocolUnknownError";
break;
case KIO::ERR_CONNECTION_BROKEN:
setError(QNetworkReply::RemoteHostClosedError, kJob->errorText());
kDebug(7044) << "KIO::ERR_CONNECTION_BROKEN -> QNetworkReply::RemoteHostClosedError";
break;
case KIO::ERR_UNSUPPORTED_ACTION:
setError(QNetworkReply::ProtocolInvalidOperationError, kJob->errorText());
kDebug(7044) << "KIO::ERR_UNSUPPORTED_ACTION -> QNetworkReply::ProtocolInvalidOperationError";
break;
default:
setError(QNetworkReply::UnknownNetworkError, kJob->errorText());
kDebug(7044) << KIO::rawErrorDetail(errCode, QString()) << "-> QNetworkReply::UnknownNetworkError";
}
return errCode;
}
void AccessManagerReply::slotData(KIO::Job *kioJob, const QByteArray &data)
{
Q_UNUSED (kioJob);
m_data += data;
if (!data.isEmpty())
emit readyRead();
}
void AccessManagerReply::slotMimeType(KIO::Job *kioJob, const QString &mimeType)
{
//kDebug(7044) << kioJob << mimeType;
setHeader(QNetworkRequest::ContentTypeHeader, mimeType.toUtf8());
readHttpResponseHeaders(kioJob);
if (m_emitReadyReadOnMetaDataChange) {
emit readyRead();
}
}
void AccessManagerReply::slotResult(KJob *kJob)
{
const int errcode = jobError(kJob);
const QUrl redirectUrl = attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
if (!redirectUrl.isValid()) {
setAttribute(static_cast<QNetworkRequest::Attribute>(KIO::AccessManager::KioError), errcode);
if (errcode && errcode != KIO::ERR_NO_CONTENT)
emit error(error());
}
// Make sure HTTP response headers are always set.
if (!m_metaDataRead) {
readHttpResponseHeaders(qobject_cast<KIO::Job*>(kJob));
}
emit finished();
}
void AccessManagerReply::slotStatResult(KJob* kJob)
{
if (jobError(kJob)) {
emit error (error());
emit finished();
return;
}
KIO::StatJob* statJob = qobject_cast<KIO::StatJob*>(kJob);
Q_ASSERT(statJob);
KIO::UDSEntry entry = statJob->statResult();
QString mimeType = entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE);
if (mimeType.isEmpty() && entry.isDir())
mimeType = QL1S("inode/directory");
if (!mimeType.isEmpty())
setHeader(QNetworkRequest::ContentTypeHeader, mimeType.toUtf8());
emit finished();
}
void AccessManagerReply::slotRedirection(KIO::Job* job, const KUrl& u)
{
Q_UNUSED(job);
if (!KAuthorized::authorizeUrlAction(QLatin1String("redirect"), url(), u)) {
kWarning(7007) << "Redirection from" << url() << "to" << u << "REJECTED by policy!";
setError(QNetworkReply::ContentAccessDenied, u.url());
emit error(error());
return;
}
setAttribute(QNetworkRequest::RedirectionTargetAttribute, QUrl(u));
}
void AccessManagerReply::slotPercent(KJob *job, unsigned long percent)
{
qulonglong bytesTotal = job->totalAmount(KJob::Bytes);
qulonglong bytesProcessed = bytesTotal * (percent / 100);
if (operation() == QNetworkAccessManager::PutOperation ||
operation() == QNetworkAccessManager::PostOperation) {
emit uploadProgress(bytesProcessed, bytesTotal);
return;
}
emit downloadProgress(bytesProcessed, bytesTotal);
}
}
diff --git a/kio/kio/authinfo.cpp b/kio/kio/authinfo.cpp
index 90d4fdb73e..c142992a7e 100644
--- a/kio/kio/authinfo.cpp
+++ b/kio/kio/authinfo.cpp
@@ -1,508 +1,508 @@
/*
* This file is part of the KDE libraries
* Copyright (C) 2000-2001 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 "authinfo.h"
#include <config.h>
#include <sys/stat.h> // don't move it down the include order, it breaks compilation on MSVC
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <QtCore/QByteArray>
#include <QtCore/QDir>
#include <QtCore/QFile>
#include <QtDBus/QDBusArgument>
#include <QtDBus/QDBusMetaType>
#include <kde_file.h>
#include <kdebug.h>
#include <kstandarddirs.h>
#define NETRC_READ_BUF_SIZE 4096
using namespace KIO;
//////
class ExtraField
{
public:
ExtraField()
: flags(AuthInfo::ExtraFieldNoFlags)
{
}
ExtraField(const ExtraField& other)
: customTitle(other.customTitle),
flags (other.flags),
value (other.value)
{
}
ExtraField& operator=(const ExtraField& other)
{
customTitle = other.customTitle;
flags = other.flags;
value = other.value;
return *this;
}
QString customTitle; // reserved for future use
AuthInfo::FieldFlags flags;
QVariant value;
};
Q_DECLARE_METATYPE(ExtraField)
QDataStream& operator<< (QDataStream& s, const ExtraField& extraField)
{
s << extraField.customTitle;
s << (int)extraField.flags;
s << extraField.value;
return s;
}
QDataStream& operator>> (QDataStream& s, ExtraField& extraField)
{
s >> extraField.customTitle ;
int i;
s >> i;
extraField.flags = (AuthInfo::FieldFlags)i;
s >> extraField.value ;
return s;
}
QDBusArgument &operator<<(QDBusArgument &argument, const ExtraField &extraField)
{
argument.beginStructure();
argument << extraField.customTitle << (int)extraField.flags
<< QDBusVariant(extraField.value);
argument.endStructure();
return argument;
}
const QDBusArgument &operator>>(const QDBusArgument &argument, ExtraField &extraField)
{
QDBusVariant value;
int flag;
argument.beginStructure();
argument >> extraField.customTitle >> flag >> value;
argument.endStructure();
extraField.value = value.variant();
extraField.flags = (KIO::AuthInfo::FieldFlags)flag;
return argument;
}
class KIO::AuthInfoPrivate
{
public:
QMap<QString, ExtraField> extraFields;
};
//////
AuthInfo::AuthInfo() : d(new AuthInfoPrivate())
{
modified = false;
readOnly = false;
verifyPath = false;
keepPassword = false;
AuthInfo::registerMetaTypes();
}
AuthInfo::AuthInfo( const AuthInfo& info ) : d(new AuthInfoPrivate())
{
(*this) = info;
AuthInfo::registerMetaTypes();
}
AuthInfo::~AuthInfo()
{
delete d;
}
AuthInfo& AuthInfo::operator= ( const AuthInfo& info )
{
url = info.url;
username = info.username;
password = info.password;
prompt = info.prompt;
caption = info.caption;
comment = info.comment;
commentLabel = info.commentLabel;
realmValue = info.realmValue;
digestInfo = info.digestInfo;
verifyPath = info.verifyPath;
readOnly = info.readOnly;
keepPassword = info.keepPassword;
modified = info.modified;
d->extraFields = info.d->extraFields;
return *this;
}
bool AuthInfo::isModified() const
{
return modified;
}
void AuthInfo::setModified( bool flag )
{
modified = flag;
}
/////
void AuthInfo::setExtraField(const QString &fieldName, const QVariant & value)
{
d->extraFields[fieldName].value = value;
}
void AuthInfo::setExtraFieldFlags(const QString &fieldName, const FieldFlags flags)
{
d->extraFields[fieldName].flags = flags;
}
QVariant AuthInfo::getExtraField(const QString &fieldName) const
{
if (!d->extraFields.contains(fieldName)) return QVariant();
return d->extraFields[fieldName].value;
}
AuthInfo::FieldFlags AuthInfo::getExtraFieldFlags(const QString &fieldName) const
{
if (!d->extraFields.contains(fieldName)) return AuthInfo::ExtraFieldNoFlags;
return d->extraFields[fieldName].flags;
}
void AuthInfo::registerMetaTypes()
{
qRegisterMetaType<ExtraField>();
qRegisterMetaType<KIO::AuthInfo>();
qDBusRegisterMetaType<ExtraField>();
qDBusRegisterMetaType<KIO::AuthInfo>();
}
/////
QDataStream& KIO::operator<< (QDataStream& s, const AuthInfo& a)
{
s << (quint8)1
<< a.url << a.username << a.password << a.prompt << a.caption
<< a.comment << a.commentLabel << a.realmValue << a.digestInfo
<< a.verifyPath << a.readOnly << a.keepPassword << a.modified
<< a.d->extraFields;
return s;
}
QDataStream& KIO::operator>> (QDataStream& s, AuthInfo& a)
{
quint8 version;
s >> version
>> a.url >> a.username >> a.password >> a.prompt >> a.caption
>> a.comment >> a.commentLabel >> a.realmValue >> a.digestInfo
>> a.verifyPath >> a.readOnly >> a.keepPassword >> a.modified
>> a.d->extraFields;
return s;
}
QDBusArgument &KIO::operator<<(QDBusArgument &argument, const AuthInfo &a)
{
argument.beginStructure();
argument << (quint8)1
<< a.url.url() << a.username << a.password << a.prompt << a.caption
<< a.comment << a.commentLabel << a.realmValue << a.digestInfo
<< a.verifyPath << a.readOnly << a.keepPassword << a.modified
<< a.d->extraFields;
argument.endStructure();
return argument;
}
const QDBusArgument &KIO::operator>>(const QDBusArgument &argument, AuthInfo &a)
{
QString url;
quint8 version;
argument.beginStructure();
argument >> version
>> url >> a.username >> a.password >> a.prompt >> a.caption
>> a.comment >> a.commentLabel >> a.realmValue >> a.digestInfo
>> a.verifyPath >> a.readOnly >> a.keepPassword >> a.modified
>> a.d->extraFields;
argument.endStructure();
a.url = url;
return argument;
}
typedef QList<NetRC::AutoLogin> LoginList;
typedef QMap<QString, LoginList> LoginMap;
class NetRC::NetRCPrivate
{
public:
NetRCPrivate()
: isDirty(false)
{}
bool isDirty;
LoginMap loginMap;
};
NetRC* NetRC::instance = 0L;
NetRC::NetRC()
: d( new NetRCPrivate )
{
}
NetRC::~NetRC()
{
delete instance;
instance = 0L;
delete d;
}
NetRC* NetRC::self()
{
if ( !instance )
instance = new NetRC;
return instance;
}
bool NetRC::lookup( const KUrl& url, AutoLogin& login, bool userealnetrc,
const QString &_type, LookUpMode mode )
{
// kDebug() << "AutoLogin lookup for: " << url.host();
if ( !url.isValid() )
return false;
QString type = _type;
if ( type.isEmpty() )
- type = url.protocol();
+ type = url.scheme();
if ( d->loginMap.isEmpty() || d->isDirty )
{
d->loginMap.clear();
QString filename = KStandardDirs::locateLocal("config", QLatin1String("kionetrc"));
bool status = parse (openf (filename));
if ( userealnetrc )
{
filename = QDir::homePath() + QLatin1String("/.netrc");
status |= parse (openf(filename));
}
if ( !status )
return false;
}
if ( !d->loginMap.contains( type ) )
return false;
const LoginList& l = d->loginMap[type];
if ( l.isEmpty() )
return false;
for (LoginList::ConstIterator it = l.begin(); it != l.end(); ++it)
{
const AutoLogin &log = *it;
if ( (mode & defaultOnly) == defaultOnly &&
log.machine == QLatin1String("default") &&
(login.login.isEmpty() || login.login == log.login) )
{
login.type = log.type;
login.machine = log.machine;
login.login = log.login;
login.password = log.password;
login.macdef = log.macdef;
}
if ( (mode & presetOnly) == presetOnly &&
log.machine == QLatin1String("preset") &&
(login.login.isEmpty() || login.login == log.login) )
{
login.type = log.type;
login.machine = log.machine;
login.login = log.login;
login.password = log.password;
login.macdef = log.macdef;
}
if ( (mode & exactOnly) == exactOnly &&
log.machine == url.host() &&
(login.login.isEmpty() || login.login == log.login) )
{
login.type = log.type;
login.machine = log.machine;
login.login = log.login;
login.password = log.password;
login.macdef = log.macdef;
break;
}
}
return true;
}
void NetRC::reload()
{
d->isDirty = true;
}
int NetRC::openf( const QString& f )
{
KDE_struct_stat sbuff;
if ( KDE::stat(f, &sbuff) != 0 )
return -1;
// Security check!!
if ( sbuff.st_mode != (S_IFREG|S_IRUSR|S_IWUSR) ||
sbuff.st_uid != geteuid() )
return -1;
return KDE::open( f, O_RDONLY );
}
QString NetRC::extract( const char* buf, const char* key, int& pos )
{
int idx = pos;
int m_len = strlen(key);
int b_len = strlen(buf);
while( idx < b_len )
{
while( buf[idx] == ' ' || buf[idx] == '\t' )
idx++;
if ( strncasecmp( buf+idx, key, m_len ) != 0 )
idx++;
else
{
idx += m_len;
while( buf[idx] == ' ' || buf[idx] == '\t' )
idx++;
int start = idx;
while( buf[idx] != ' ' && buf[idx] != '\t' &&
buf[idx] != '\n' && buf[idx] != '\r' )
idx++;
if ( idx > start )
{
pos = idx;
return QString::fromLatin1( buf+start, idx-start);
}
}
}
return QString();
}
bool NetRC::parse( int fd )
{
if (fd == -1)
return false;
QString type;
QString macro;
uint index = 0;
bool isMacro = false;
char* buf = new char[NETRC_READ_BUF_SIZE];
FILE* fstream = KDE_fdopen( fd,"rb" );
while ( fgets (buf, NETRC_READ_BUF_SIZE, fstream) != 0L )
{
int pos = 0;
while ( buf[pos] == ' ' || buf[pos] == '\t' )
pos++;
if ( buf[pos] == '#' || buf[pos] == '\n' ||
buf[pos] == '\r' || buf[pos] == '\0' )
{
if ( buf[pos] != '#' && isMacro )
isMacro = false;
continue;
}
if ( isMacro )
{
int tail = strlen(buf);
while( buf[tail-1] == '\n' || buf[tail-1] =='\r' )
tail--;
QString mac = QString::fromLatin1(buf, tail).trimmed();
if ( !mac.isEmpty() )
d->loginMap[type][index].macdef[macro].append( mac );
continue;
}
AutoLogin l;
l.machine = extract( buf, "machine", pos );
if ( l.machine.isEmpty() )
{
if (strncasecmp(buf+pos, "default", 7) == 0 )
{
pos += 7;
l.machine = QLatin1String("default");
}
else if (strncasecmp(buf+pos, "preset", 6) == 0 )
{
pos += 6;
l.machine = QLatin1String("preset");
}
}
// kDebug() << "Machine: " << l.machine;
l.login = extract( buf, "login", pos );
// kDebug() << "Login: " << l.login;
l.password = extract( buf, "password", pos );
if ( l.password.isEmpty() )
l.password = extract( buf, "account", pos );
// kDebug() << "Password: " << l.password;
type = l.type = extract( buf, "type", pos );
if ( l.type.isEmpty() && !l.machine.isEmpty() )
type = l.type = QLatin1String("ftp");
// kDebug() << "Type: " << l.type;
macro = extract( buf, "macdef", pos );
isMacro = !macro.isEmpty();
// kDebug() << "Macro: " << macro;
d->loginMap[l.type].append(l);
index = d->loginMap[l.type].count()-1;
}
delete [] buf;
fclose (fstream);
close (fd);
return true;
}
diff --git a/kio/kio/connection.cpp b/kio/kio/connection.cpp
index a5b02e696d..edbb8779eb 100644
--- a/kio/kio/connection.cpp
+++ b/kio/kio/connection.cpp
@@ -1,620 +1,620 @@
/* This file is part of the KDE libraries
Copyright (C) 2000 Stephan Kulow <coolo@kde.org>
David Faure <faure@kde.org>
Copyright (C) 2007 Thiago Macieira <thiago@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 "connection.h"
#include "connection_p.h"
#include <errno.h>
#include <QQueue>
#include <QPointer>
#include <QTime>
#include <qtemporaryfile.h>
#include <qstandardpaths.h>
#include <kdebug.h>
#include <kcomponentdata.h>
#include <kglobal.h>
#include <klocale.h>
#include <kstandarddirs.h>
#include <kurl.h>
using namespace KIO;
class KIO::ConnectionPrivate
{
public:
inline ConnectionPrivate()
: backend(0), suspended(false)
{ }
void dequeue();
void commandReceived(const Task &task);
void disconnected();
void setBackend(AbstractConnectionBackend *b);
QQueue<Task> outgoingTasks;
QQueue<Task> incomingTasks;
AbstractConnectionBackend *backend;
Connection *q;
bool suspended;
};
class KIO::ConnectionServerPrivate
{
public:
inline ConnectionServerPrivate()
: backend(0)
{ }
ConnectionServer *q;
AbstractConnectionBackend *backend;
};
void ConnectionPrivate::dequeue()
{
if (!backend || suspended)
return;
while (!outgoingTasks.isEmpty()) {
const Task task = outgoingTasks.dequeue();
q->sendnow(task.cmd, task.data);
}
if (!incomingTasks.isEmpty())
emit q->readyRead();
}
void ConnectionPrivate::commandReceived(const Task &task)
{
//kDebug() << this << "Command " << task.cmd << " added to the queue";
if (!suspended && incomingTasks.isEmpty())
QMetaObject::invokeMethod(q, "dequeue", Qt::QueuedConnection);
incomingTasks.enqueue(task);
}
void ConnectionPrivate::disconnected()
{
q->close();
QMetaObject::invokeMethod(q, "readyRead", Qt::QueuedConnection);
}
void ConnectionPrivate::setBackend(AbstractConnectionBackend *b)
{
backend = b;
if (backend) {
q->connect(backend, SIGNAL(commandReceived(Task)), SLOT(commandReceived(Task)));
q->connect(backend, SIGNAL(disconnected()), SLOT(disconnected()));
backend->setSuspended(suspended);
}
}
AbstractConnectionBackend::AbstractConnectionBackend(QObject *parent)
: QObject(parent), state(Idle)
{
}
AbstractConnectionBackend::~AbstractConnectionBackend()
{
}
SocketConnectionBackend::SocketConnectionBackend(Mode m, QObject *parent)
: AbstractConnectionBackend(parent), socket(0), len(-1), cmd(0),
signalEmitted(false), mode(m)
{
localServer = 0;
//tcpServer = 0;
}
SocketConnectionBackend::~SocketConnectionBackend()
{
if (mode == LocalSocketMode && localServer &&
localServer->localSocketType() == KLocalSocket::UnixSocket)
QFile::remove(localServer->localPath());
}
void SocketConnectionBackend::setSuspended(bool enable)
{
if (state != Connected)
return;
Q_ASSERT(socket);
Q_ASSERT(!localServer); // !tcpServer as well
if (enable) {
//kDebug() << this << " suspending";
socket->setReadBufferSize(1);
} else {
//kDebug() << this << " resuming";
socket->setReadBufferSize(StandardBufferSize);
if (socket->bytesAvailable() >= HeaderSize) {
// there are bytes available
QMetaObject::invokeMethod(this, "socketReadyRead", Qt::QueuedConnection);
}
// We read all bytes here, but we don't use readAll() because we need
// to read at least one byte (even if there isn't any) so that the
// socket notifier is reenabled
QByteArray data = socket->read(socket->bytesAvailable() + 1);
for (int i = data.size(); --i >= 0; )
socket->ungetChar(data[i]);
}
}
bool SocketConnectionBackend::connectToRemote(const KUrl &url)
{
Q_ASSERT(state == Idle);
Q_ASSERT(!socket);
Q_ASSERT(!localServer); // !tcpServer as well
if (mode == LocalSocketMode) {
KLocalSocket *sock = new KLocalSocket(this);
QString path = url.path();
#if 0
// TODO: Activate once abstract socket support is implemented in Qt.
KLocalSocket::LocalSocketType type = KLocalSocket::UnixSocket;
if (url.queryItem(QLatin1String("abstract")) == QLatin1String("1"))
type = KLocalSocket::AbstractUnixSocket;
#endif
sock->connectToPath(path);
socket = sock;
} else {
socket = new QTcpSocket(this);
socket->connectToHost(url.host(),url.port());
if (!socket->waitForConnected(1000)) {
state = Idle;
kDebug() << "could not connect to " << url;
return false;
}
}
connect(socket, SIGNAL(readyRead()), SLOT(socketReadyRead()));
connect(socket, SIGNAL(disconnected()), SLOT(socketDisconnected()));
state = Connected;
return true;
}
void SocketConnectionBackend::socketDisconnected()
{
state = Idle;
emit disconnected();
}
bool SocketConnectionBackend::listenForRemote()
{
Q_ASSERT(state == Idle);
Q_ASSERT(!socket);
Q_ASSERT(!localServer); // !tcpServer as well
if (mode == LocalSocketMode) {
const QString prefix = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);
QTemporaryFile socketfile(prefix + QLatin1Char('/') + KGlobal::mainComponent().componentName() + QLatin1String("XXXXXX.slave-socket"));
if (!socketfile.open())
{
errorString = i18n("Unable to create io-slave: %1", strerror(errno));
return false;
}
QString sockname = socketfile.fileName();
KUrl addressUrl(sockname);
addressUrl.setProtocol("local");
address = addressUrl.url();
socketfile.remove(); // can't bind if there is such a file
localServer = new KLocalSocketServer(this);
if (!localServer->listen(sockname, KLocalSocket::UnixSocket)) {
errorString = localServer->errorString();
delete localServer;
localServer = 0;
return false;
}
connect(localServer, SIGNAL(newConnection()), SIGNAL(newConnection()));
} else {
tcpServer = new QTcpServer(this);
tcpServer->listen(QHostAddress::LocalHost);
if (!tcpServer->isListening()) {
errorString = tcpServer->errorString();
delete tcpServer;
tcpServer = 0;
return false;
}
address = "tcp://127.0.0.1:" + QString::number(tcpServer->serverPort());
connect(tcpServer, SIGNAL(newConnection()), SIGNAL(newConnection()));
}
state = Listening;
return true;
}
bool SocketConnectionBackend::waitForIncomingTask(int ms)
{
Q_ASSERT(state == Connected);
Q_ASSERT(socket);
if (socket->state() != QAbstractSocket::ConnectedState) {
state = Idle;
return false; // socket has probably closed, what do we do?
}
signalEmitted = false;
if (socket->bytesAvailable())
socketReadyRead();
if (signalEmitted)
return true; // there was enough data in the socket
// not enough data in the socket, so wait for more
QTime timer;
timer.start();
while (socket->state() == QAbstractSocket::ConnectedState && !signalEmitted &&
(ms == -1 || timer.elapsed() < ms))
if (!socket->waitForReadyRead(ms == -1 ? -1 : ms - timer.elapsed()))
break;
if (signalEmitted)
return true;
if (socket->state() != QAbstractSocket::ConnectedState)
state = Idle;
return false;
}
bool SocketConnectionBackend::sendCommand(const Task &task)
{
Q_ASSERT(state == Connected);
Q_ASSERT(socket);
static char buffer[HeaderSize + 2];
sprintf(buffer, "%6x_%2x_", task.data.size(), task.cmd);
socket->write(buffer, HeaderSize);
socket->write(task.data);
//kDebug() << this << " Sending command " << hex << task.cmd << " of "
// << task.data.size() << " bytes (" << socket->bytesToWrite()
// << " bytes left to write";
// blocking mode:
while (socket->bytesToWrite() > 0 && socket->state() == QAbstractSocket::ConnectedState)
socket->waitForBytesWritten(-1);
return socket->state() == QAbstractSocket::ConnectedState;
}
AbstractConnectionBackend *SocketConnectionBackend::nextPendingConnection()
{
Q_ASSERT(state == Listening);
Q_ASSERT(localServer || tcpServer);
Q_ASSERT(!socket);
//kDebug() << "Got a new connection";
QTcpSocket *newSocket;
if (mode == LocalSocketMode)
newSocket = localServer->nextPendingConnection();
else
newSocket = tcpServer->nextPendingConnection();
if (!newSocket)
return 0; // there was no connection...
SocketConnectionBackend *result = new SocketConnectionBackend(Mode(mode));
result->state = Connected;
result->socket = newSocket;
newSocket->setParent(result);
connect(newSocket, SIGNAL(readyRead()), result, SLOT(socketReadyRead()));
connect(newSocket, SIGNAL(disconnected()), result, SLOT(socketDisconnected()));
return result;
}
void SocketConnectionBackend::socketReadyRead()
{
bool shouldReadAnother;
do {
if (!socket)
// might happen if the invokeMethods were delivered after we disconnected
return;
// kDebug() << this << "Got " << socket->bytesAvailable() << " bytes";
if (len == -1) {
// We have to read the header
static char buffer[HeaderSize];
if (socket->bytesAvailable() < HeaderSize) {
return; // wait for more data
}
socket->read(buffer, sizeof buffer);
buffer[6] = 0;
buffer[9] = 0;
char *p = buffer;
while( *p == ' ' ) p++;
len = strtol( p, 0L, 16 );
p = buffer + 7;
while( *p == ' ' ) p++;
cmd = strtol( p, 0L, 16 );
// kDebug() << this << " Beginning of command " << hex << cmd << " of size "
// << len;
}
QPointer<SocketConnectionBackend> that = this;
// kDebug() << this << "Want to read " << len << " bytes";
if (socket->bytesAvailable() >= len) {
Task task;
task.cmd = cmd;
if (len)
task.data = socket->read(len);
len = -1;
signalEmitted = true;
emit commandReceived(task);
} else if (len > StandardBufferSize) {
kDebug(7017) << this << "Jumbo packet of" << len << "bytes";
socket->setReadBufferSize(len + 1);
}
// If we're dead, better don't try anything.
if (that.isNull())
return;
// Do we have enough for an another read?
if (len == -1)
shouldReadAnother = socket->bytesAvailable() >= HeaderSize;
else
shouldReadAnother = socket->bytesAvailable() >= len;
}
while (shouldReadAnother);
}
Connection::Connection(QObject *parent)
: QObject(parent), d(new ConnectionPrivate)
{
d->q = this;
}
Connection::~Connection()
{
close();
delete d;
}
void Connection::suspend()
{
//kDebug() << this << "Suspended";
d->suspended = true;
if (d->backend)
d->backend->setSuspended(true);
}
void Connection::resume()
{
// send any outgoing or incoming commands that may be in queue
QMetaObject::invokeMethod(this, "dequeue", Qt::QueuedConnection);
//kDebug() << this << "Resumed";
d->suspended = false;
if (d->backend)
d->backend->setSuspended(false);
}
void Connection::close()
{
if (d->backend) {
d->backend->disconnect(this);
d->backend->deleteLater();
d->backend = 0;
}
d->outgoingTasks.clear();
d->incomingTasks.clear();
}
bool Connection::isConnected() const
{
return d->backend && d->backend->state == AbstractConnectionBackend::Connected;
}
bool Connection::inited() const
{
return d->backend;
}
bool Connection::suspended() const
{
return d->suspended;
}
void Connection::connectToRemote(const QString &address)
{
//kDebug(7017) << "Connection requested to " << address;
KUrl url = address;
- QString scheme = url.protocol();
+ QString scheme = url.scheme();
if (scheme == QLatin1String("local")) {
d->setBackend(new SocketConnectionBackend(SocketConnectionBackend::LocalSocketMode, this));
} else if (scheme == QLatin1String("tcp")) {
d->setBackend(new SocketConnectionBackend(SocketConnectionBackend::TcpSocketMode, this));
} else {
kWarning(7017) << "Unknown requested KIO::Connection protocol='" << scheme
<< "' (" << address << ")";
Q_ASSERT(0);
return;
}
// connection succeeded
if (!d->backend->connectToRemote(url)) {
//kWarning(7017) << "could not connect to " << url << "using scheme" << scheme ;
delete d->backend;
d->backend = 0;
return;
}
d->dequeue();
}
QString Connection::errorString() const
{
if (d->backend)
return d->backend->errorString;
return QString();
}
bool Connection::send(int cmd, const QByteArray& data)
{
if (!inited() || !d->outgoingTasks.isEmpty()) {
Task task;
task.cmd = cmd;
task.data = data;
d->outgoingTasks.enqueue(task);
return true;
} else {
return sendnow(cmd, data);
}
}
bool Connection::sendnow(int _cmd, const QByteArray &data)
{
if (data.size() > 0xffffff)
return false;
if (!isConnected())
return false;
//kDebug() << this << "Sending command " << _cmd << " of size " << data.size();
Task task;
task.cmd = _cmd;
task.data = data;
return d->backend->sendCommand(task);
}
bool Connection::hasTaskAvailable() const
{
return !d->incomingTasks.isEmpty();
}
bool Connection::waitForIncomingTask(int ms)
{
if (!isConnected())
return false;
if (d->backend)
return d->backend->waitForIncomingTask(ms);
return false;
}
int Connection::read( int* _cmd, QByteArray &data )
{
// if it's still empty, then it's an error
if (d->incomingTasks.isEmpty()) {
//kWarning() << this << "Task list is empty!";
return -1;
}
const Task task = d->incomingTasks.dequeue();
//kDebug() << this << "Command " << task.cmd << " removed from the queue (size "
// << task.data.size() << ")";
*_cmd = task.cmd;
data = task.data;
// if we didn't empty our reading queue, emit again
if (!d->suspended && !d->incomingTasks.isEmpty())
QMetaObject::invokeMethod(this, "dequeue", Qt::QueuedConnection);
return data.size();
}
ConnectionServer::ConnectionServer(QObject *parent)
: QObject(parent), d(new ConnectionServerPrivate)
{
d->q = this;
}
ConnectionServer::~ConnectionServer()
{
delete d;
}
void ConnectionServer::listenForRemote()
{
#ifdef Q_WS_WIN
d->backend = new SocketConnectionBackend(SocketConnectionBackend::TcpSocketMode, this);
#else
d->backend = new SocketConnectionBackend(SocketConnectionBackend::LocalSocketMode, this);
#endif
if (!d->backend->listenForRemote()) {
delete d->backend;
d->backend = 0;
return;
}
connect(d->backend, SIGNAL(newConnection()), SIGNAL(newConnection()));
kDebug(7017) << "Listening on " << d->backend->address;
}
QString ConnectionServer::address() const
{
if (d->backend)
return d->backend->address;
return QString();
}
bool ConnectionServer::isListening() const
{
return d->backend && d->backend->state == AbstractConnectionBackend::Listening;
}
void ConnectionServer::close()
{
delete d->backend;
d->backend = 0;
}
Connection *ConnectionServer::nextPendingConnection()
{
if (!isListening())
return 0;
AbstractConnectionBackend *newBackend = d->backend->nextPendingConnection();
if (!newBackend)
return 0; // no new backend...
Connection *result = new Connection;
result->d->setBackend(newBackend);
newBackend->setParent(result);
return result;
}
void ConnectionServer::setNextPendingConnection(Connection *conn)
{
AbstractConnectionBackend *newBackend = d->backend->nextPendingConnection();
Q_ASSERT(newBackend);
conn->d->backend = newBackend;
conn->d->setBackend(newBackend);
newBackend->setParent(conn);
conn->d->dequeue();
}
#include "moc_connection_p.cpp"
#include "moc_connection.cpp"
diff --git a/kio/kio/copyjob.cpp b/kio/kio/copyjob.cpp
index ccb2d844dd..0a8f49d3e2 100644
--- a/kio/kio/copyjob.cpp
+++ b/kio/kio/copyjob.cpp
@@ -1,2211 +1,2211 @@
/* This file is part of the KDE libraries
Copyright 2000 Stephan Kulow <coolo@kde.org>
Copyright 2000-2006 David Faure <faure@kde.org>
Copyright 2000 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.
*/
#include "copyjob.h"
#include <errno.h>
#include "kdirlister.h"
#include "kfileitem.h"
#include "deletejob.h"
#include <klocale.h>
#include <kdesktopfile.h>
#include <kdebug.h>
#include <kde_file.h>
#include "slave.h"
#include "scheduler.h"
#include "kdirwatch.h"
#include "kprotocolmanager.h"
#include "jobuidelegate.h"
#include <kdirnotify.h>
#ifdef Q_OS_UNIX
#include <utime.h>
#endif
#include <assert.h>
#include <qtemporaryfile.h>
#include <QtCore/QTimer>
#include <QtCore/QFile>
#include <sys/stat.h> // mode_t
#include <QPointer>
#include "job_p.h"
#include <kdiskfreespaceinfo.h>
#include <kfilesystemtype_p.h>
using namespace KIO;
//this will update the report dialog with 5 Hz, I think this is fast enough, aleXXX
#define REPORT_TIMEOUT 200
enum DestinationState {
DEST_NOT_STATED,
DEST_IS_DIR,
DEST_IS_FILE,
DEST_DOESNT_EXIST
};
/**
* States:
* STATE_STATING for the dest
* statCurrentSrc then does, for each src url:
* STATE_RENAMING if direct rename looks possible
* (on already exists, and user chooses rename, TODO: go to STATE_RENAMING again)
* STATE_STATING
* and then, if dir -> STATE_LISTING (filling 'd->dirs' and 'd->files')
* STATE_CREATING_DIRS (createNextDir, iterating over 'd->dirs')
* if conflict: STATE_CONFLICT_CREATING_DIRS
* STATE_COPYING_FILES (copyNextFile, iterating over 'd->files')
* if conflict: STATE_CONFLICT_COPYING_FILES
* STATE_DELETING_DIRS (deleteNextDir) (if moving)
* STATE_SETTING_DIR_ATTRIBUTES (setNextDirAttribute, iterating over d->m_directoriesCopied)
* done.
*/
enum CopyJobState {
STATE_STATING,
STATE_RENAMING,
STATE_LISTING,
STATE_CREATING_DIRS,
STATE_CONFLICT_CREATING_DIRS,
STATE_COPYING_FILES,
STATE_CONFLICT_COPYING_FILES,
STATE_DELETING_DIRS,
STATE_SETTING_DIR_ATTRIBUTES
};
/** @internal */
class KIO::CopyJobPrivate: public KIO::JobPrivate
{
public:
CopyJobPrivate(const KUrl::List& src, const KUrl& dest,
CopyJob::CopyMode mode, bool asMethod)
: m_globalDest(dest)
, m_globalDestinationState(DEST_NOT_STATED)
, m_defaultPermissions(false)
, m_bURLDirty(false)
, m_mode(mode)
, m_asMethod(asMethod)
, destinationState(DEST_NOT_STATED)
, state(STATE_STATING)
, m_freeSpace(-1)
, m_totalSize(0)
, m_processedSize(0)
, m_fileProcessedSize(0)
, m_processedFiles(0)
, m_processedDirs(0)
, m_srcList(src)
, m_currentStatSrc(m_srcList.constBegin())
, m_bCurrentOperationIsLink(false)
, m_bSingleFileCopy(false)
, m_bOnlyRenames(mode==CopyJob::Move)
, m_dest(dest)
, m_bAutoRenameFiles(false)
, m_bAutoRenameDirs(false)
, m_bAutoSkipFiles( false )
, m_bAutoSkipDirs( false )
, m_bOverwriteAllFiles( false )
, m_bOverwriteAllDirs( false )
, m_conflictError(0)
, m_reportTimer(0)
{
}
// This is the dest URL that was initially given to CopyJob
// It is copied into m_dest, which can be changed for a given src URL
// (when using the RENAME dialog in slotResult),
// and which will be reset for the next src URL.
KUrl m_globalDest;
// The state info about that global dest
DestinationState m_globalDestinationState;
// See setDefaultPermissions
bool m_defaultPermissions;
// Whether URLs changed (and need to be emitted by the next slotReport call)
bool m_bURLDirty;
// Used after copying all the files into the dirs, to set mtime (TODO: and permissions?)
// after the copy is done
QLinkedList<CopyInfo> m_directoriesCopied;
QLinkedList<CopyInfo>::const_iterator m_directoriesCopiedIterator;
CopyJob::CopyMode m_mode;
bool m_asMethod;
DestinationState destinationState;
CopyJobState state;
KIO::filesize_t m_freeSpace;
KIO::filesize_t m_totalSize;
KIO::filesize_t m_processedSize;
KIO::filesize_t m_fileProcessedSize;
int m_processedFiles;
int m_processedDirs;
QList<CopyInfo> files;
QList<CopyInfo> dirs;
KUrl::List dirsToRemove;
KUrl::List m_srcList;
KUrl::List m_successSrcList; // Entries in m_srcList that have successfully been moved
KUrl::List::const_iterator m_currentStatSrc;
bool m_bCurrentSrcIsDir;
bool m_bCurrentOperationIsLink;
bool m_bSingleFileCopy;
bool m_bOnlyRenames;
KUrl m_dest;
KUrl m_currentDest; // set during listing, used by slotEntries
//
QStringList m_skipList;
QSet<QString> m_overwriteList;
bool m_bAutoRenameFiles;
bool m_bAutoRenameDirs;
bool m_bAutoSkipFiles;
bool m_bAutoSkipDirs;
bool m_bOverwriteAllFiles;
bool m_bOverwriteAllDirs;
int m_conflictError;
QTimer *m_reportTimer;
// The current src url being stat'ed or copied
// During the stat phase, this is initially equal to *m_currentStatSrc but it can be resolved to a local file equivalent (#188903).
KUrl m_currentSrcURL;
KUrl m_currentDestURL;
QSet<QString> m_parentDirs;
void statCurrentSrc();
void statNextSrc();
// Those aren't slots but submethods for slotResult.
void slotResultStating( KJob * job );
void startListing( const KUrl & src );
void slotResultCreatingDirs( KJob * job );
void slotResultConflictCreatingDirs( KJob * job );
void createNextDir();
void slotResultCopyingFiles( KJob * job );
void slotResultConflictCopyingFiles( KJob * job );
// KIO::Job* linkNextFile( const KUrl& uSource, const KUrl& uDest, bool overwrite );
KIO::Job* linkNextFile( const KUrl& uSource, const KUrl& uDest, JobFlags flags );
void copyNextFile();
void slotResultDeletingDirs( KJob * job );
void deleteNextDir();
void sourceStated(const UDSEntry& entry, const KUrl& sourceUrl);
void skip(const KUrl & sourceURL, bool isDir);
void slotResultRenaming( KJob * job );
void slotResultSettingDirAttributes( KJob * job );
void setNextDirAttribute();
void startRenameJob(const KUrl &slave_url);
bool shouldOverwriteDir( const QString& path ) const;
bool shouldOverwriteFile( const QString& path ) const;
bool shouldSkip( const QString& path ) const;
void skipSrc(bool isDir);
void slotStart();
void slotEntries( KIO::Job*, const KIO::UDSEntryList& list );
void addCopyInfoFromUDSEntry(const UDSEntry& entry, const KUrl& srcUrl, bool srcIsDir, const KUrl& currentDest);
/**
* Forward signal from subjob
*/
void slotProcessedSize( KJob*, qulonglong data_size );
/**
* Forward signal from subjob
* @param size the total size
*/
void slotTotalSize( KJob*, qulonglong size );
void slotReport();
Q_DECLARE_PUBLIC(CopyJob)
static inline CopyJob *newJob(const KUrl::List& src, const KUrl& dest,
CopyJob::CopyMode mode, bool asMethod, JobFlags flags)
{
CopyJob *job = new CopyJob(*new CopyJobPrivate(src,dest,mode,asMethod));
job->setUiDelegate(new JobUiDelegate);
if (!(flags & HideProgressInfo))
KIO::getJobTracker()->registerJob(job);
if (flags & KIO::Overwrite) {
job->d_func()->m_bOverwriteAllDirs = true;
job->d_func()->m_bOverwriteAllFiles = true;
}
return job;
}
};
CopyJob::CopyJob(CopyJobPrivate &dd)
: Job(dd)
{
setProperty("destUrl", d_func()->m_dest.url());
QTimer::singleShot(0, this, SLOT(slotStart()));
}
CopyJob::~CopyJob()
{
}
KUrl::List CopyJob::srcUrls() const
{
return d_func()->m_srcList;
}
KUrl CopyJob::destUrl() const
{
return d_func()->m_dest;
}
void CopyJobPrivate::slotStart()
{
Q_Q(CopyJob);
/**
We call the functions directly instead of using signals.
Calling a function via a signal takes approx. 65 times the time
compared to calling it directly (at least on my machine). aleXXX
*/
m_reportTimer = new QTimer(q);
q->connect(m_reportTimer,SIGNAL(timeout()),q,SLOT(slotReport()));
m_reportTimer->start(REPORT_TIMEOUT);
// Stat the dest
KIO::Job * job = KIO::stat( m_dest, StatJob::DestinationSide, 2, KIO::HideProgressInfo );
//kDebug(7007) << "CopyJob:stating the dest " << m_dest;
q->addSubjob(job);
}
// For unit test purposes
KIO_EXPORT bool kio_resolve_local_urls = true;
void CopyJobPrivate::slotResultStating( KJob *job )
{
Q_Q(CopyJob);
//kDebug(7007);
// Was there an error while stating the src ?
if (job->error() && destinationState != DEST_NOT_STATED )
{
const KUrl srcurl = static_cast<SimpleJob*>(job)->url();
if ( !srcurl.isLocalFile() )
{
// Probably : src doesn't exist. Well, over some protocols (e.g. FTP)
// this info isn't really reliable (thanks to MS FTP servers).
// We'll assume a file, and try to download anyway.
kDebug(7007) << "Error while stating source. Activating hack";
q->removeSubjob( job );
assert ( !q->hasSubjobs() ); // We should have only one job at a time ...
struct CopyInfo info;
info.permissions = (mode_t) -1;
info.mtime = (time_t) -1;
info.ctime = (time_t) -1;
info.size = (KIO::filesize_t)-1;
info.uSource = srcurl;
info.uDest = m_dest;
// Append filename or dirname to destination URL, if allowed
if ( destinationState == DEST_IS_DIR && !m_asMethod )
info.uDest.addPath( srcurl.fileName() );
files.append( info );
statNextSrc();
return;
}
// Local file. If stat fails, the file definitely doesn't exist.
// yes, q->Job::, because we don't want to call our override
q->Job::slotResult( job ); // will set the error and emit result(this)
return;
}
// Keep copy of the stat result
const UDSEntry entry = static_cast<StatJob*>(job)->statResult();
if ( destinationState == DEST_NOT_STATED ) {
if ( m_dest.isLocalFile() ) { //works for dirs as well
QString path = m_dest.toLocalFile();
KFileSystemType::Type fsType = KFileSystemType::fileSystemType( path );
if ( fsType != KFileSystemType::Nfs && fsType != KFileSystemType::Smb ) {
m_freeSpace = KDiskFreeSpaceInfo::freeSpaceInfo( path ).available();
}
//TODO actually preliminary check is even more valuable for slow NFS/SMB mounts,
//but we need to find a way to report connection errors to user
}
const bool isGlobalDest = m_dest == m_globalDest;
const bool isDir = entry.isDir();
// we were stating the dest
if (job->error()) {
destinationState = DEST_DOESNT_EXIST;
//kDebug(7007) << "dest does not exist";
} else {
// Treat symlinks to dirs as dirs here, so no test on isLink
destinationState = isDir ? DEST_IS_DIR : DEST_IS_FILE;
//kDebug(7007) << "dest is dir:" << isDir;
const QString sLocalPath = entry.stringValue( KIO::UDSEntry::UDS_LOCAL_PATH );
if ( !sLocalPath.isEmpty() && kio_resolve_local_urls && destinationState != DEST_DOESNT_EXIST ) {
m_dest = KUrl();
m_dest.setPath(sLocalPath);
if ( isGlobalDest )
m_globalDest = m_dest;
}
}
if ( isGlobalDest )
m_globalDestinationState = destinationState;
q->removeSubjob( job );
assert ( !q->hasSubjobs() );
// After knowing what the dest is, we can start stat'ing the first src.
statCurrentSrc();
} else {
sourceStated(entry, static_cast<SimpleJob*>(job)->url());
q->removeSubjob( job );
}
}
void CopyJobPrivate::sourceStated(const UDSEntry& entry, const KUrl& sourceUrl)
{
const QString sLocalPath = entry.stringValue( KIO::UDSEntry::UDS_LOCAL_PATH );
const bool isDir = entry.isDir();
// We were stating the current source URL
// Is it a file or a dir ?
// There 6 cases, and all end up calling addCopyInfoFromUDSEntry first :
// 1 - src is a dir, destination is a directory,
// slotEntries will append the source-dir-name to the destination
// 2 - src is a dir, destination is a file -- will offer to overwrite, later on.
// 3 - src is a dir, destination doesn't exist, then it's the destination dirname,
// so slotEntries will use it as destination.
// 4 - src is a file, destination is a directory,
// slotEntries will append the filename to the destination.
// 5 - src is a file, destination is a file, m_dest is the exact destination name
// 6 - src is a file, destination doesn't exist, m_dest is the exact destination name
KUrl srcurl;
if (!sLocalPath.isEmpty() && destinationState != DEST_DOESNT_EXIST) {
kDebug() << "Using sLocalPath. destinationState=" << destinationState;
// Prefer the local path -- but only if we were able to stat() the dest.
// Otherwise, renaming a desktop:/ url would copy from src=file to dest=desktop (#218719)
srcurl.setPath(sLocalPath);
} else {
srcurl = sourceUrl;
}
addCopyInfoFromUDSEntry(entry, srcurl, false, m_dest);
m_currentDest = m_dest;
m_bCurrentSrcIsDir = false;
if ( isDir
// treat symlinks as files (no recursion)
&& !entry.isLink()
&& m_mode != CopyJob::Link ) // No recursion in Link mode either.
{
//kDebug(7007) << "Source is a directory";
if (srcurl.isLocalFile()) {
const QString parentDir = srcurl.toLocalFile(KUrl::RemoveTrailingSlash);
m_parentDirs.insert(parentDir);
}
m_bCurrentSrcIsDir = true; // used by slotEntries
if ( destinationState == DEST_IS_DIR ) // (case 1)
{
if ( !m_asMethod )
{
// Use <desturl>/<directory_copied> as destination, from now on
QString directory = srcurl.fileName();
const QString sName = entry.stringValue( KIO::UDSEntry::UDS_NAME );
KProtocolInfo::FileNameUsedForCopying fnu = KProtocolManager::fileNameUsedForCopying(srcurl);
if (fnu == KProtocolInfo::Name) {
if (!sName.isEmpty())
directory = sName;
} else if (fnu == KProtocolInfo::DisplayName) {
const QString dispName = entry.stringValue( KIO::UDSEntry::UDS_DISPLAY_NAME );
if (!dispName.isEmpty())
directory = dispName;
else if (!sName.isEmpty())
directory = sName;
}
m_currentDest.addPath( directory );
}
}
else // (case 3)
{
// otherwise dest is new name for toplevel dir
// so the destination exists, in fact, from now on.
// (This even works with other src urls in the list, since the
// dir has effectively been created)
destinationState = DEST_IS_DIR;
if ( m_dest == m_globalDest )
m_globalDestinationState = destinationState;
}
startListing( srcurl );
}
else
{
//kDebug(7007) << "Source is a file (or a symlink), or we are linking -> no recursive listing";
if (srcurl.isLocalFile()) {
const QString parentDir = srcurl.directory(KUrl::ObeyTrailingSlash);
m_parentDirs.insert(parentDir);
}
statNextSrc();
}
}
bool CopyJob::doSuspend()
{
Q_D(CopyJob);
d->slotReport();
return Job::doSuspend();
}
void CopyJobPrivate::slotReport()
{
Q_Q(CopyJob);
if ( q->isSuspended() )
return;
// If showProgressInfo was set, progressId() is > 0.
switch (state) {
case STATE_RENAMING:
q->setTotalAmount(KJob::Files, m_srcList.count());
// fall-through intended
case STATE_COPYING_FILES:
q->setProcessedAmount( KJob::Files, m_processedFiles );
if (m_bURLDirty)
{
// Only emit urls when they changed. This saves time, and fixes #66281
m_bURLDirty = false;
if (m_mode==CopyJob::Move)
{
emitMoving(q, m_currentSrcURL, m_currentDestURL);
emit q->moving( q, m_currentSrcURL, m_currentDestURL);
}
else if (m_mode==CopyJob::Link)
{
emitCopying( q, m_currentSrcURL, m_currentDestURL ); // we don't have a delegate->linking
emit q->linking( q, m_currentSrcURL.path(), m_currentDestURL );
}
else
{
emitCopying( q, m_currentSrcURL, m_currentDestURL );
emit q->copying( q, m_currentSrcURL, m_currentDestURL );
}
}
break;
case STATE_CREATING_DIRS:
q->setProcessedAmount( KJob::Directories, m_processedDirs );
if (m_bURLDirty)
{
m_bURLDirty = false;
emit q->creatingDir( q, m_currentDestURL );
emitCreatingDir( q, m_currentDestURL );
}
break;
case STATE_STATING:
case STATE_LISTING:
if (m_bURLDirty)
{
m_bURLDirty = false;
if (m_mode==CopyJob::Move)
{
emitMoving( q, m_currentSrcURL, m_currentDestURL );
}
else
{
emitCopying( q, m_currentSrcURL, m_currentDestURL );
}
}
q->setTotalAmount(KJob::Bytes, m_totalSize);
q->setTotalAmount(KJob::Files, files.count());
q->setTotalAmount(KJob::Directories, dirs.count());
break;
default:
break;
}
}
void CopyJobPrivate::slotEntries(KIO::Job* job, const UDSEntryList& list)
{
//Q_Q(CopyJob);
UDSEntryList::ConstIterator it = list.constBegin();
UDSEntryList::ConstIterator end = list.constEnd();
for (; it != end; ++it) {
const UDSEntry& entry = *it;
addCopyInfoFromUDSEntry(entry, static_cast<SimpleJob *>(job)->url(), m_bCurrentSrcIsDir, m_currentDest);
}
}
void CopyJobPrivate::addCopyInfoFromUDSEntry(const UDSEntry& entry, const KUrl& srcUrl, bool srcIsDir, const KUrl& currentDest)
{
struct CopyInfo info;
info.permissions = entry.numberValue(KIO::UDSEntry::UDS_ACCESS, -1);
info.mtime = (time_t) entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1);
info.ctime = (time_t) entry.numberValue(KIO::UDSEntry::UDS_CREATION_TIME, -1);
info.size = (KIO::filesize_t) entry.numberValue(KIO::UDSEntry::UDS_SIZE, -1);
if (info.size != (KIO::filesize_t) -1)
m_totalSize += info.size;
// recursive listing, displayName can be a/b/c/d
const QString fileName = entry.stringValue(KIO::UDSEntry::UDS_NAME);
const QString urlStr = entry.stringValue(KIO::UDSEntry::UDS_URL);
KUrl url;
if (!urlStr.isEmpty())
url = urlStr;
QString localPath = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH);
const bool isDir = entry.isDir();
info.linkDest = entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST);
if (fileName != QLatin1String("..") && fileName != QLatin1String(".")) {
const bool hasCustomURL = !url.isEmpty() || !localPath.isEmpty();
if (!hasCustomURL) {
// Make URL from displayName
url = srcUrl;
if (srcIsDir) { // Only if src is a directory. Otherwise uSource is fine as is
//kDebug(7007) << "adding path" << displayName;
url.addPath(fileName);
}
}
//kDebug(7007) << "displayName=" << displayName << "url=" << url;
if (!localPath.isEmpty() && kio_resolve_local_urls && destinationState != DEST_DOESNT_EXIST) {
url = KUrl(localPath);
}
info.uSource = url;
info.uDest = currentDest;
//kDebug(7007) << "uSource=" << info.uSource << "uDest(1)=" << info.uDest;
// Append filename or dirname to destination URL, if allowed
if (destinationState == DEST_IS_DIR &&
// "copy/move as <foo>" means 'foo' is the dest for the base srcurl
// (passed here during stating) but not its children (during listing)
(! (m_asMethod && state == STATE_STATING)))
{
QString destFileName;
KProtocolInfo::FileNameUsedForCopying fnu = KProtocolManager::fileNameUsedForCopying(url);
if (hasCustomURL &&
fnu == KProtocolInfo::FromUrl) {
//destFileName = url.fileName(); // Doesn't work for recursive listing
// Count the number of prefixes used by the recursive listjob
int numberOfSlashes = fileName.count('/'); // don't make this a find()!
QString path = url.path();
int pos = 0;
for (int n = 0; n < numberOfSlashes + 1; ++n) {
pos = path.lastIndexOf('/', pos - 1);
if (pos == -1) { // error
kWarning(7007) << "kioslave bug: not enough slashes in UDS_URL" << path << "- looking for" << numberOfSlashes << "slashes";
break;
}
}
if (pos >= 0) {
destFileName = path.mid(pos + 1);
}
} else if ( fnu == KProtocolInfo::Name ) { // destination filename taken from UDS_NAME
destFileName = fileName;
} else { // from display name (with fallback to name)
const QString displayName = entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME);
destFileName = displayName.isEmpty() ? fileName : displayName;
}
// Here we _really_ have to add some filename to the dest.
// Otherwise, we end up with e.g. dest=..../Desktop/ itself.
// (This can happen when dropping a link to a webpage with no path)
if (destFileName.isEmpty()) {
destFileName = KIO::encodeFileName(info.uSource.prettyUrl());
}
//kDebug(7007) << " adding destFileName=" << destFileName;
info.uDest.addPath(destFileName);
}
//kDebug(7007) << " uDest(2)=" << info.uDest;
//kDebug(7007) << " " << info.uSource << "->" << info.uDest;
if (info.linkDest.isEmpty() && isDir && m_mode != CopyJob::Link) { // Dir
dirs.append(info); // Directories
if (m_mode == CopyJob::Move) {
dirsToRemove.append(info.uSource);
}
} else {
files.append(info); // Files and any symlinks
}
}
}
void CopyJobPrivate::skipSrc(bool isDir)
{
m_dest = m_globalDest;
destinationState = m_globalDestinationState;
skip(*m_currentStatSrc, isDir);
++m_currentStatSrc;
statCurrentSrc();
}
void CopyJobPrivate::statNextSrc()
{
/* Revert to the global destination, the one that applies to all source urls.
* Imagine you copy the items a b and c into /d, but /d/b exists so the user uses "Rename" to put it in /foo/b instead.
* d->m_dest is /foo/b for b, but we have to revert to /d for item c and following.
*/
m_dest = m_globalDest;
destinationState = m_globalDestinationState;
++m_currentStatSrc;
statCurrentSrc();
}
void CopyJobPrivate::statCurrentSrc()
{
Q_Q(CopyJob);
if (m_currentStatSrc != m_srcList.constEnd()) {
m_currentSrcURL = (*m_currentStatSrc);
m_bURLDirty = true;
if (m_mode == CopyJob::Link) {
// Skip the "stating the source" stage, we don't need it for linking
m_currentDest = m_dest;
struct CopyInfo info;
info.permissions = -1;
info.mtime = (time_t) -1;
info.ctime = (time_t) -1;
info.size = (KIO::filesize_t)-1;
info.uSource = m_currentSrcURL;
info.uDest = m_currentDest;
// Append filename or dirname to destination URL, if allowed
if (destinationState == DEST_IS_DIR && !m_asMethod) {
if (
- (m_currentSrcURL.protocol() == info.uDest.protocol()) &&
+ (m_currentSrcURL.scheme() == info.uDest.scheme()) &&
(m_currentSrcURL.host() == info.uDest.host()) &&
(m_currentSrcURL.port() == info.uDest.port()) &&
(m_currentSrcURL.user() == info.uDest.user()) &&
(m_currentSrcURL.pass() == info.uDest.pass()) ) {
// This is the case of creating a real symlink
info.uDest.addPath( m_currentSrcURL.fileName() );
} else {
// Different protocols, we'll create a .desktop file
// We have to change the extension anyway, so while we're at it,
// name the file like the URL
info.uDest.addPath(KIO::encodeFileName(m_currentSrcURL.prettyUrl()) + ".desktop");
}
}
files.append( info ); // Files and any symlinks
statNextSrc(); // we could use a loop instead of a recursive call :)
return;
}
// Let's see if we can skip stat'ing, for the case where a directory view has the info already
const KFileItem cachedItem = KDirLister::cachedItemForUrl(m_currentSrcURL);
KIO::UDSEntry entry;
if (!cachedItem.isNull()) {
entry = cachedItem.entry();
if (destinationState != DEST_DOESNT_EXIST) { // only resolve src if we could resolve dest (#218719)
bool dummyIsLocal;
m_currentSrcURL = cachedItem.mostLocalUrl(dummyIsLocal); // #183585
}
}
if (m_mode == CopyJob::Move && (
// Don't go renaming right away if we need a stat() to find out the destination filename
KProtocolManager::fileNameUsedForCopying(m_currentSrcURL) == KProtocolInfo::FromUrl ||
destinationState != DEST_IS_DIR || m_asMethod)
) {
// If moving, before going for the full stat+[list+]copy+del thing, try to rename
// The logic is pretty similar to FileCopyJobPrivate::slotStart()
- if ( (m_currentSrcURL.protocol() == m_dest.protocol()) &&
+ if ( (m_currentSrcURL.scheme() == m_dest.scheme()) &&
(m_currentSrcURL.host() == m_dest.host()) &&
(m_currentSrcURL.port() == m_dest.port()) &&
(m_currentSrcURL.user() == m_dest.user()) &&
(m_currentSrcURL.pass() == m_dest.pass()) )
{
startRenameJob( m_currentSrcURL );
return;
}
else if ( m_currentSrcURL.isLocalFile() && KProtocolManager::canRenameFromFile( m_dest ) )
{
startRenameJob( m_dest );
return;
}
else if ( m_dest.isLocalFile() && KProtocolManager::canRenameToFile( m_currentSrcURL ) )
{
startRenameJob( m_currentSrcURL );
return;
}
}
// if the file system doesn't support deleting, we do not even stat
if (m_mode == CopyJob::Move && !KProtocolManager::supportsDeleting(m_currentSrcURL)) {
QPointer<CopyJob> that = q;
emit q->warning( q, buildErrorString(ERR_CANNOT_DELETE, m_currentSrcURL.prettyUrl()) );
if (that)
statNextSrc(); // we could use a loop instead of a recursive call :)
return;
}
m_bOnlyRenames = false;
// Testing for entry.count()>0 here is not good enough; KFileItem inserts
// entries for UDS_USER and UDS_GROUP even on initially empty UDSEntries (#192185)
if (entry.contains(KIO::UDSEntry::UDS_NAME)) {
kDebug(7007) << "fast path! found info about" << m_currentSrcURL << "in KDirLister";
sourceStated(entry, m_currentSrcURL);
return;
}
// Stat the next src url
Job * job = KIO::stat( m_currentSrcURL, StatJob::SourceSide, 2, KIO::HideProgressInfo );
//kDebug(7007) << "KIO::stat on" << m_currentSrcURL;
state = STATE_STATING;
q->addSubjob(job);
m_currentDestURL = m_dest;
m_bURLDirty = true;
}
else
{
// Finished the stat'ing phase
// First make sure that the totals were correctly emitted
state = STATE_STATING;
m_bURLDirty = true;
slotReport();
kDebug(7007)<<"Stating finished. To copy:"<<m_totalSize<<", available:"<<m_freeSpace;
//TODO warn user beforehand if space is not enough
if (!dirs.isEmpty())
emit q->aboutToCreate( q, dirs );
if (!files.isEmpty())
emit q->aboutToCreate( q, files );
// Check if we are copying a single file
m_bSingleFileCopy = ( files.count() == 1 && dirs.isEmpty() );
// Then start copying things
state = STATE_CREATING_DIRS;
createNextDir();
}
}
void CopyJobPrivate::startRenameJob( const KUrl& slave_url )
{
Q_Q(CopyJob);
// Silence KDirWatch notifications, otherwise performance is horrible
if (m_currentSrcURL.isLocalFile()) {
const QString parentDir = m_currentSrcURL.directory(KUrl::ObeyTrailingSlash);
if (!m_parentDirs.contains(parentDir)) {
KDirWatch::self()->stopDirScan(parentDir);
m_parentDirs.insert(parentDir);
}
}
KUrl dest = m_dest;
// Append filename or dirname to destination URL, if allowed
if ( destinationState == DEST_IS_DIR && !m_asMethod )
dest.addPath( m_currentSrcURL.fileName() );
m_currentDestURL = dest;
kDebug(7007) << m_currentSrcURL << "->" << dest << "trying direct rename first";
state = STATE_RENAMING;
struct CopyInfo info;
info.permissions = -1;
info.mtime = (time_t) -1;
info.ctime = (time_t) -1;
info.size = (KIO::filesize_t)-1;
info.uSource = m_currentSrcURL;
info.uDest = dest;
QList<CopyInfo> files;
files.append(info);
emit q->aboutToCreate( q, files );
KIO_ARGS << m_currentSrcURL << dest << (qint8) false /*no overwrite*/;
SimpleJob * newJob = SimpleJobPrivate::newJobNoUi(slave_url, CMD_RENAME, packedArgs);
Scheduler::setJobPriority(newJob, 1);
q->addSubjob( newJob );
if ( m_currentSrcURL.directory() != dest.directory() ) // For the user, moving isn't renaming. Only renaming is.
m_bOnlyRenames = false;
}
void CopyJobPrivate::startListing( const KUrl & src )
{
Q_Q(CopyJob);
state = STATE_LISTING;
m_bURLDirty = true;
ListJob * newjob = listRecursive(src, KIO::HideProgressInfo);
newjob->setUnrestricted(true);
q->connect(newjob, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)),
SLOT(slotEntries(KIO::Job*,KIO::UDSEntryList)));
q->addSubjob( newjob );
}
void CopyJobPrivate::skip(const KUrl & sourceUrl, bool isDir)
{
KUrl dir = sourceUrl;
if (!isDir) {
// Skipping a file: make sure not to delete the parent dir (#208418)
dir.setPath(dir.directory());
}
while (dirsToRemove.removeAll(dir) > 0) {
// Do not rely on rmdir() on the parent directories aborting.
// Exclude the parent dirs explicitly.
dir.setPath(dir.directory());
}
}
bool CopyJobPrivate::shouldOverwriteDir( const QString& path ) const
{
if ( m_bOverwriteAllDirs )
return true;
return m_overwriteList.contains(path);
}
bool CopyJobPrivate::shouldOverwriteFile( const QString& path ) const
{
if ( m_bOverwriteAllFiles )
return true;
return m_overwriteList.contains(path);
}
bool CopyJobPrivate::shouldSkip( const QString& path ) const
{
Q_FOREACH(const QString& skipPath, m_skipList) {
if ( path.startsWith(skipPath) )
return true;
}
return false;
}
void CopyJobPrivate::slotResultCreatingDirs( KJob * job )
{
Q_Q(CopyJob);
// The dir we are trying to create:
QList<CopyInfo>::Iterator it = dirs.begin();
// Was there an error creating a dir ?
if ( job->error() )
{
m_conflictError = job->error();
if ( (m_conflictError == ERR_DIR_ALREADY_EXIST)
|| (m_conflictError == ERR_FILE_ALREADY_EXIST) ) // can't happen?
{
KUrl oldURL = ((SimpleJob*)job)->url();
// Should we skip automatically ?
if ( m_bAutoSkipDirs ) {
// We don't want to copy files in this directory, so we put it on the skip list
m_skipList.append( oldURL.path( KUrl::AddTrailingSlash ) );
skip(oldURL, true);
dirs.erase( it ); // Move on to next dir
} else {
// Did the user choose to overwrite already?
const QString destDir = (*it).uDest.path();
if ( shouldOverwriteDir( destDir ) ) { // overwrite => just skip
emit q->copyingDone( q, (*it).uSource, (*it).uDest, (*it).mtime, true /* directory */, false /* renamed */ );
dirs.erase( it ); // Move on to next dir
} else {
if (m_bAutoRenameDirs) {
QString oldPath = (*it).uDest.path(KUrl::AddTrailingSlash);
KUrl destDirectory((*it).uDest);
destDirectory.setPath(destDirectory.directory());
QString newName = KIO::RenameDialog::suggestName(destDirectory, (*it).uDest.fileName());
KUrl newUrl((*it).uDest);
newUrl.setFileName(newName);
emit q->renamed(q, (*it).uDest, newUrl); // for e.g. kpropsdlg
// Change the current one and strip the trailing '/'
(*it).uDest.setPath(newUrl.path(KUrl::RemoveTrailingSlash));
QString newPath = newUrl.path(KUrl::AddTrailingSlash); // With trailing slash
QList<CopyInfo>::Iterator renamedirit = it;
++renamedirit;
// Change the name of subdirectories inside the directory
for(; renamedirit != dirs.end() ; ++renamedirit) {
QString path = (*renamedirit).uDest.path();
if (path.startsWith(oldPath)) {
QString n = path;
n.replace(0, oldPath.length(), newPath);
kDebug(7007) << "dirs list:" << (*renamedirit).uSource.path()
<< "was going to be" << path
<< ", changed into" << n;
(*renamedirit).uDest.setPath(n);
}
}
// Change filenames inside the directory
QList<CopyInfo>::Iterator renamefileit = files.begin();
for(; renamefileit != files.end() ; ++renamefileit) {
QString path = (*renamefileit).uDest.path();
if (path.startsWith(oldPath)) {
QString n = path;
n.replace(0, oldPath.length(), newPath);
kDebug(7007) << "files list:" << (*renamefileit).uSource.path()
<< "was going to be" << path
<< ", changed into" << n;
(*renamefileit).uDest.setPath(n);
}
}
if (!dirs.isEmpty()) {
emit q->aboutToCreate(q, dirs);
}
if (!files.isEmpty()) {
emit q->aboutToCreate(q, files);
}
}
else {
if (!q->isInteractive()) {
q->Job::slotResult(job); // will set the error and emit result(this)
return;
}
assert(((SimpleJob*)job)->url().url() == (*it).uDest.url());
q->removeSubjob(job);
assert (!q->hasSubjobs()); // We should have only one job at a time ...
// We need to stat the existing dir, to get its last-modification time
KUrl existingDest((*it).uDest);
SimpleJob * newJob = KIO::stat(existingDest, StatJob::DestinationSide, 2, KIO::HideProgressInfo);
Scheduler::setJobPriority(newJob, 1);
kDebug(7007) << "KIO::stat for resolving conflict on " << existingDest;
state = STATE_CONFLICT_CREATING_DIRS;
q->addSubjob(newJob);
return; // Don't move to next dir yet !
}
}
}
}
else
{
// Severe error, abort
q->Job::slotResult( job ); // will set the error and emit result(this)
return;
}
}
else // no error : remove from list, to move on to next dir
{
//this is required for the undo feature
emit q->copyingDone( q, (*it).uSource, (*it).uDest, (*it).mtime, true, false );
m_directoriesCopied.append( *it );
dirs.erase( it );
}
m_processedDirs++;
//emit processedAmount( this, KJob::Directories, m_processedDirs );
q->removeSubjob( job );
assert( !q->hasSubjobs() ); // We should have only one job at a time ...
createNextDir();
}
void CopyJobPrivate::slotResultConflictCreatingDirs( KJob * job )
{
Q_Q(CopyJob);
// We come here after a conflict has been detected and we've stated the existing dir
// The dir we were trying to create:
QList<CopyInfo>::Iterator it = dirs.begin();
const UDSEntry entry = ((KIO::StatJob*)job)->statResult();
// Its modification time:
const time_t destmtime = (time_t) entry.numberValue( KIO::UDSEntry::UDS_MODIFICATION_TIME, -1 );
const time_t destctime = (time_t) entry.numberValue( KIO::UDSEntry::UDS_CREATION_TIME, -1 );
const KIO::filesize_t destsize = entry.numberValue( KIO::UDSEntry::UDS_SIZE );
const QString linkDest = entry.stringValue( KIO::UDSEntry::UDS_LINK_DEST );
q->removeSubjob( job );
assert ( !q->hasSubjobs() ); // We should have only one job at a time ...
// Always multi and skip (since there are files after that)
RenameDialog_Mode mode = (RenameDialog_Mode)( M_MULTI | M_SKIP | M_ISDIR );
// Overwrite only if the existing thing is a dir (no chance with a file)
if ( m_conflictError == ERR_DIR_ALREADY_EXIST )
{
if( (*it).uSource == (*it).uDest ||
- ((*it).uSource.protocol() == (*it).uDest.protocol() &&
+ ((*it).uSource.scheme() == (*it).uDest.scheme() &&
(*it).uSource.path( KUrl::RemoveTrailingSlash ) == linkDest) )
mode = (RenameDialog_Mode)( mode | M_OVERWRITE_ITSELF);
else
mode = (RenameDialog_Mode)( mode | M_OVERWRITE );
}
QString existingDest = (*it).uDest.path();
QString newPath;
if (m_reportTimer)
m_reportTimer->stop();
RenameDialog_Result r = q->ui()->askFileRename( q, i18n("Folder Already Exists"),
(*it).uSource.url(),
(*it).uDest.url(),
mode, newPath,
(*it).size, destsize,
(*it).ctime, destctime,
(*it).mtime, destmtime );
if (m_reportTimer)
m_reportTimer->start(REPORT_TIMEOUT);
switch ( r ) {
case R_CANCEL:
q->setError( ERR_USER_CANCELED );
q->emitResult();
return;
case R_AUTO_RENAME:
m_bAutoRenameDirs = true;
// fall through
case R_RENAME:
{
QString oldPath = (*it).uDest.path( KUrl::AddTrailingSlash );
KUrl newUrl( (*it).uDest );
newUrl.setPath( newPath );
emit q->renamed( q, (*it).uDest, newUrl ); // for e.g. kpropsdlg
// Change the current one and strip the trailing '/'
(*it).uDest.setPath( newUrl.path( KUrl::RemoveTrailingSlash ) );
newPath = newUrl.path( KUrl::AddTrailingSlash ); // With trailing slash
QList<CopyInfo>::Iterator renamedirit = it;
++renamedirit;
// Change the name of subdirectories inside the directory
for( ; renamedirit != dirs.end() ; ++renamedirit )
{
QString path = (*renamedirit).uDest.path();
if ( path.startsWith( oldPath ) ) {
QString n = path;
n.replace( 0, oldPath.length(), newPath );
kDebug(7007) << "dirs list:" << (*renamedirit).uSource.path()
<< "was going to be" << path
<< ", changed into" << n;
(*renamedirit).uDest.setPath( n );
}
}
// Change filenames inside the directory
QList<CopyInfo>::Iterator renamefileit = files.begin();
for( ; renamefileit != files.end() ; ++renamefileit )
{
QString path = (*renamefileit).uDest.path();
if ( path.startsWith( oldPath ) ) {
QString n = path;
n.replace( 0, oldPath.length(), newPath );
kDebug(7007) << "files list:" << (*renamefileit).uSource.path()
<< "was going to be" << path
<< ", changed into" << n;
(*renamefileit).uDest.setPath( n );
}
}
if (!dirs.isEmpty())
emit q->aboutToCreate( q, dirs );
if (!files.isEmpty())
emit q->aboutToCreate( q, files );
}
break;
case R_AUTO_SKIP:
m_bAutoSkipDirs = true;
// fall through
case R_SKIP:
m_skipList.append( existingDest );
skip((*it).uSource, true);
// Move on to next dir
dirs.erase( it );
m_processedDirs++;
break;
case R_OVERWRITE:
m_overwriteList.insert( existingDest );
emit q->copyingDone( q, (*it).uSource, (*it).uDest, (*it).mtime, true /* directory */, false /* renamed */ );
// Move on to next dir
dirs.erase( it );
m_processedDirs++;
break;
case R_OVERWRITE_ALL:
m_bOverwriteAllDirs = true;
emit q->copyingDone( q, (*it).uSource, (*it).uDest, (*it).mtime, true /* directory */, false /* renamed */ );
// Move on to next dir
dirs.erase( it );
m_processedDirs++;
break;
default:
assert( 0 );
}
state = STATE_CREATING_DIRS;
//emit processedAmount( this, KJob::Directories, m_processedDirs );
createNextDir();
}
void CopyJobPrivate::createNextDir()
{
Q_Q(CopyJob);
KUrl udir;
if ( !dirs.isEmpty() )
{
// Take first dir to create out of list
QList<CopyInfo>::Iterator it = dirs.begin();
// Is this URL on the skip list or the overwrite list ?
while( it != dirs.end() && udir.isEmpty() )
{
const QString dir = (*it).uDest.path();
if ( shouldSkip( dir ) ) {
dirs.erase( it );
it = dirs.begin();
} else
udir = (*it).uDest;
}
}
if ( !udir.isEmpty() ) // any dir to create, finally ?
{
// Create the directory - with default permissions so that we can put files into it
// TODO : change permissions once all is finished; but for stuff coming from CDROM it sucks...
KIO::SimpleJob *newjob = KIO::mkdir( udir, -1 );
Scheduler::setJobPriority(newjob, 1);
if (shouldOverwriteFile(udir.path())) { // if we are overwriting an existing file or symlink
newjob->addMetaData("overwrite", "true");
}
m_currentDestURL = udir;
m_bURLDirty = true;
q->addSubjob(newjob);
return;
}
else // we have finished creating dirs
{
q->setProcessedAmount( KJob::Directories, m_processedDirs ); // make sure final number appears
if (m_mode == CopyJob::Move) {
// Now we know which dirs hold the files we're going to delete.
// To speed things up and prevent double-notification, we disable KDirWatch
// on those dirs temporarily (using KDirWatch::self, that's the instanced
// used by e.g. kdirlister).
for ( QSet<QString>::const_iterator it = m_parentDirs.constBegin() ; it != m_parentDirs.constEnd() ; ++it )
KDirWatch::self()->stopDirScan( *it );
}
state = STATE_COPYING_FILES;
m_processedFiles++; // Ralf wants it to start at 1, not 0
copyNextFile();
}
}
void CopyJobPrivate::slotResultCopyingFiles( KJob * job )
{
Q_Q(CopyJob);
// The file we were trying to copy:
QList<CopyInfo>::Iterator it = files.begin();
if ( job->error() )
{
// Should we skip automatically ?
if ( m_bAutoSkipFiles )
{
skip((*it).uSource, false);
m_fileProcessedSize = (*it).size;
files.erase( it ); // Move on to next file
}
else
{
m_conflictError = job->error(); // save for later
// Existing dest ?
if ( ( m_conflictError == ERR_FILE_ALREADY_EXIST )
|| ( m_conflictError == ERR_DIR_ALREADY_EXIST )
|| ( m_conflictError == ERR_IDENTICAL_FILES ) )
{
if (m_bAutoRenameFiles) {
KUrl destDirectory((*it).uDest);
destDirectory.setPath(destDirectory.directory());
const QString newName = KIO::RenameDialog::suggestName(destDirectory, (*it).uDest.fileName());
KUrl newUrl((*it).uDest);
newUrl.setFileName(newName);
emit q->renamed(q, (*it).uDest, newUrl); // for e.g. kpropsdlg
(*it).uDest = newUrl;
QList<CopyInfo> files;
files.append(*it);
emit q->aboutToCreate(q, files);
}
else {
if ( !q->isInteractive() ) {
q->Job::slotResult( job ); // will set the error and emit result(this)
return;
}
q->removeSubjob(job);
assert (!q->hasSubjobs());
// We need to stat the existing file, to get its last-modification time
KUrl existingFile((*it).uDest);
SimpleJob * newJob = KIO::stat(existingFile, StatJob::DestinationSide, 2, KIO::HideProgressInfo);
Scheduler::setJobPriority(newJob, 1);
kDebug(7007) << "KIO::stat for resolving conflict on " << existingFile;
state = STATE_CONFLICT_COPYING_FILES;
q->addSubjob(newJob);
return; // Don't move to next file yet !
}
}
else
{
if ( m_bCurrentOperationIsLink && qobject_cast<KIO::DeleteJob*>( job ) )
{
// Very special case, see a few lines below
// We are deleting the source of a symlink we successfully moved... ignore error
m_fileProcessedSize = (*it).size;
files.erase( it );
} else {
if ( !q->isInteractive() ) {
q->Job::slotResult( job ); // will set the error and emit result(this)
return;
}
// Go directly to the conflict resolution, there is nothing to stat
slotResultConflictCopyingFiles( job );
return;
}
}
}
} else // no error
{
// Special case for moving links. That operation needs two jobs, unlike others.
if ( m_bCurrentOperationIsLink && m_mode == CopyJob::Move
&& !qobject_cast<KIO::DeleteJob *>( job ) // Deleting source not already done
)
{
q->removeSubjob( job );
assert ( !q->hasSubjobs() );
// The only problem with this trick is that the error handling for this del operation
// is not going to be right... see 'Very special case' above.
KIO::Job * newjob = KIO::del( (*it).uSource, HideProgressInfo );
q->addSubjob( newjob );
return; // Don't move to next file yet !
}
if ( m_bCurrentOperationIsLink )
{
QString target = ( m_mode == CopyJob::Link ? (*it).uSource.path() : (*it).linkDest );
//required for the undo feature
emit q->copyingLinkDone( q, (*it).uSource, target, (*it).uDest );
}
else {
//required for the undo feature
emit q->copyingDone( q, (*it).uSource, (*it).uDest, (*it).mtime, false, false );
if (m_mode == CopyJob::Move)
{
org::kde::KDirNotify::emitFileMoved( (*it).uSource.url(), (*it).uDest.url() );
}
m_successSrcList.append((*it).uSource);
if (m_freeSpace != (KIO::filesize_t)-1 && (*it).size != (KIO::filesize_t)-1) {
m_freeSpace -= (*it).size;
}
}
// remove from list, to move on to next file
files.erase( it );
}
m_processedFiles++;
// clear processed size for last file and add it to overall processed size
m_processedSize += m_fileProcessedSize;
m_fileProcessedSize = 0;
//kDebug(7007) << files.count() << "files remaining";
// Merge metadata from subjob
KIO::Job* kiojob = dynamic_cast<KIO::Job*>(job);
Q_ASSERT(kiojob);
m_incomingMetaData += kiojob->metaData();
q->removeSubjob( job );
assert( !q->hasSubjobs() ); // We should have only one job at a time ...
copyNextFile();
}
void CopyJobPrivate::slotResultConflictCopyingFiles( KJob * job )
{
Q_Q(CopyJob);
// We come here after a conflict has been detected and we've stated the existing file
// The file we were trying to create:
QList<CopyInfo>::Iterator it = files.begin();
RenameDialog_Result res;
QString newPath;
if (m_reportTimer)
m_reportTimer->stop();
if ( ( m_conflictError == ERR_FILE_ALREADY_EXIST )
|| ( m_conflictError == ERR_DIR_ALREADY_EXIST )
|| ( m_conflictError == ERR_IDENTICAL_FILES ) )
{
// Its modification time:
const UDSEntry entry = ((KIO::StatJob*)job)->statResult();
const time_t destmtime = (time_t) entry.numberValue( KIO::UDSEntry::UDS_MODIFICATION_TIME, -1 );
const time_t destctime = (time_t) entry.numberValue( KIO::UDSEntry::UDS_CREATION_TIME, -1 );
const KIO::filesize_t destsize = entry.numberValue( KIO::UDSEntry::UDS_SIZE );
const QString linkDest = entry.stringValue( KIO::UDSEntry::UDS_LINK_DEST );
// Offer overwrite only if the existing thing is a file
// If src==dest, use "overwrite-itself"
RenameDialog_Mode mode;
bool isDir = true;
if( m_conflictError == ERR_DIR_ALREADY_EXIST )
mode = M_ISDIR;
else
{
if ( (*it).uSource == (*it).uDest ||
- ((*it).uSource.protocol() == (*it).uDest.protocol() &&
+ ((*it).uSource.scheme() == (*it).uDest.scheme() &&
(*it).uSource.path( KUrl::RemoveTrailingSlash ) == linkDest) )
mode = M_OVERWRITE_ITSELF;
else
mode = M_OVERWRITE;
isDir = false;
}
if ( !m_bSingleFileCopy )
mode = (RenameDialog_Mode) ( mode | M_MULTI | M_SKIP );
res = q->ui()->askFileRename( q, !isDir ?
i18n("File Already Exists") : i18n("Already Exists as Folder"),
(*it).uSource.url(),
(*it).uDest.url(),
mode, newPath,
(*it).size, destsize,
(*it).ctime, destctime,
(*it).mtime, destmtime );
}
else
{
if ( job->error() == ERR_USER_CANCELED )
res = R_CANCEL;
else if ( !q->isInteractive() ) {
q->Job::slotResult( job ); // will set the error and emit result(this)
return;
}
else
{
SkipDialog_Result skipResult = q->ui()->askSkip( q, files.count() > 1,
job->errorString() );
// Convert the return code from SkipDialog into a RenameDialog code
res = ( skipResult == S_SKIP ) ? R_SKIP :
( skipResult == S_AUTO_SKIP ) ? R_AUTO_SKIP :
R_CANCEL;
}
}
if (m_reportTimer)
m_reportTimer->start(REPORT_TIMEOUT);
q->removeSubjob( job );
assert ( !q->hasSubjobs() );
switch ( res ) {
case R_CANCEL:
q->setError( ERR_USER_CANCELED );
q->emitResult();
return;
case R_AUTO_RENAME:
m_bAutoRenameFiles = true;
// fall through
case R_RENAME:
{
KUrl newUrl( (*it).uDest );
newUrl.setPath( newPath );
emit q->renamed( q, (*it).uDest, newUrl ); // for e.g. kpropsdlg
(*it).uDest = newUrl;
QList<CopyInfo> files;
files.append(*it);
emit q->aboutToCreate( q, files );
}
break;
case R_AUTO_SKIP:
m_bAutoSkipFiles = true;
// fall through
case R_SKIP:
// Move on to next file
skip((*it).uSource, false);
m_processedSize += (*it).size;
files.erase( it );
m_processedFiles++;
break;
case R_OVERWRITE_ALL:
m_bOverwriteAllFiles = true;
break;
case R_OVERWRITE:
// Add to overwrite list, so that copyNextFile knows to overwrite
m_overwriteList.insert( (*it).uDest.path() );
break;
default:
assert( 0 );
}
state = STATE_COPYING_FILES;
copyNextFile();
}
KIO::Job* CopyJobPrivate::linkNextFile( const KUrl& uSource, const KUrl& uDest, JobFlags flags )
{
//kDebug(7007) << "Linking";
if (
- (uSource.protocol() == uDest.protocol()) &&
+ (uSource.scheme() == uDest.scheme()) &&
(uSource.host() == uDest.host()) &&
(uSource.port() == uDest.port()) &&
(uSource.user() == uDest.user()) &&
(uSource.pass() == uDest.pass()) )
{
// This is the case of creating a real symlink
KIO::SimpleJob *newJob = KIO::symlink( uSource.path(), uDest, flags|HideProgressInfo /*no GUI*/ );
Scheduler::setJobPriority(newJob, 1);
//kDebug(7007) << "Linking target=" << uSource.path() << "link=" << uDest;
//emit linking( this, uSource.path(), uDest );
m_bCurrentOperationIsLink = true;
m_currentSrcURL=uSource;
m_currentDestURL=uDest;
m_bURLDirty = true;
//Observer::self()->slotCopying( this, uSource, uDest ); // should be slotLinking perhaps
return newJob;
} else {
Q_Q(CopyJob);
//kDebug(7007) << "Linking URL=" << uSource << "link=" << uDest;
if ( uDest.isLocalFile() ) {
// if the source is a devices url, handle it a littlebit special
QString path = uDest.toLocalFile();
//kDebug(7007) << "path=" << path;
QFile f( path );
if ( f.open( QIODevice::ReadWrite ) )
{
f.close();
KDesktopFile desktopFile( path );
KConfigGroup config = desktopFile.desktopGroup();
KUrl url = uSource;
url.setPass( "" );
config.writePathEntry( "URL", url.url() );
config.writeEntry( "Name", url.url() );
config.writeEntry( "Type", QString::fromLatin1("Link") );
- QString protocol = uSource.protocol();
+ QString protocol = uSource.scheme();
if ( protocol == QLatin1String("ftp") )
config.writeEntry( "Icon", QString::fromLatin1("folder-remote") );
else if ( protocol == QLatin1String("http") )
config.writeEntry( "Icon", QString::fromLatin1("text-html") );
else if ( protocol == QLatin1String("info") )
config.writeEntry( "Icon", QString::fromLatin1("text-x-texinfo") );
else if ( protocol == QLatin1String("mailto") ) // sven:
config.writeEntry( "Icon", QString::fromLatin1("internet-mail") ); // added mailto: support
else
config.writeEntry( "Icon", QString::fromLatin1("unknown") );
config.sync();
files.erase( files.begin() ); // done with this one, move on
m_processedFiles++;
//emit processedAmount( this, KJob::Files, m_processedFiles );
copyNextFile();
return 0;
}
else
{
kDebug(7007) << "ERR_CANNOT_OPEN_FOR_WRITING";
q->setError( ERR_CANNOT_OPEN_FOR_WRITING );
q->setErrorText( uDest.toLocalFile() );
q->emitResult();
return 0;
}
} else {
// Todo: not show "link" on remote dirs if the src urls are not from the same protocol+host+...
q->setError( ERR_CANNOT_SYMLINK );
q->setErrorText( uDest.prettyUrl() );
q->emitResult();
return 0;
}
}
}
void CopyJobPrivate::copyNextFile()
{
Q_Q(CopyJob);
bool bCopyFile = false;
//kDebug(7007);
// Take the first file in the list
QList<CopyInfo>::Iterator it = files.begin();
// Is this URL on the skip list ?
while (it != files.end() && !bCopyFile)
{
const QString destFile = (*it).uDest.path();
bCopyFile = !shouldSkip( destFile );
if ( !bCopyFile ) {
files.erase( it );
it = files.begin();
}
}
if (bCopyFile) // any file to create, finally ?
{
//kDebug()<<"preparing to copy"<<(*it).uSource<<(*it).size<<m_freeSpace;
if (m_freeSpace != (KIO::filesize_t)-1 && (*it).size != (KIO::filesize_t)-1) {
if (m_freeSpace < (*it).size) {
q->setError( ERR_DISK_FULL );
q->emitResult();
return;
}
//TODO check if dst mount is msdos and (*it).size exceeds it's limits
}
const KUrl& uSource = (*it).uSource;
const KUrl& uDest = (*it).uDest;
// Do we set overwrite ?
bool bOverwrite;
const QString destFile = uDest.path();
// kDebug(7007) << "copying" << destFile;
if ( uDest == uSource )
bOverwrite = false;
else
bOverwrite = shouldOverwriteFile( destFile );
m_bCurrentOperationIsLink = false;
KIO::Job * newjob = 0;
if ( m_mode == CopyJob::Link ) {
// User requested that a symlink be made
const JobFlags flags = bOverwrite ? Overwrite : DefaultFlags;
newjob = linkNextFile(uSource, uDest, flags);
if (!newjob)
return;
} else if ( !(*it).linkDest.isEmpty() &&
- (uSource.protocol() == uDest.protocol()) &&
+ (uSource.scheme() == uDest.scheme()) &&
(uSource.host() == uDest.host()) &&
(uSource.port() == uDest.port()) &&
(uSource.user() == uDest.user()) &&
(uSource.pass() == uDest.pass()))
// Copying a symlink - only on the same protocol/host/etc. (#5601, downloading an FTP file through its link),
{
const JobFlags flags = bOverwrite ? Overwrite : DefaultFlags;
KIO::SimpleJob *newJob = KIO::symlink( (*it).linkDest, uDest, flags | HideProgressInfo /*no GUI*/ );
Scheduler::setJobPriority(newJob, 1);
newjob = newJob;
//kDebug(7007) << "Linking target=" << (*it).linkDest << "link=" << uDest;
m_currentSrcURL = KUrl( (*it).linkDest );
m_currentDestURL = uDest;
m_bURLDirty = true;
//emit linking( this, (*it).linkDest, uDest );
//Observer::self()->slotCopying( this, m_currentSrcURL, uDest ); // should be slotLinking perhaps
m_bCurrentOperationIsLink = true;
// NOTE: if we are moving stuff, the deletion of the source will be done in slotResultCopyingFiles
} else if (m_mode == CopyJob::Move) // Moving a file
{
JobFlags flags = bOverwrite ? Overwrite : DefaultFlags;
KIO::FileCopyJob * moveJob = KIO::file_move( uSource, uDest, (*it).permissions, flags | HideProgressInfo/*no GUI*/ );
moveJob->setSourceSize( (*it).size );
if ((*it).mtime != -1) {
moveJob->setModificationTime( QDateTime::fromTime_t( (*it).mtime ) ); // #55804
}
newjob = moveJob;
//kDebug(7007) << "Moving" << uSource << "to" << uDest;
//emit moving( this, uSource, uDest );
m_currentSrcURL=uSource;
m_currentDestURL=uDest;
m_bURLDirty = true;
//Observer::self()->slotMoving( this, uSource, uDest );
}
else // Copying a file
{
// If source isn't local and target is local, we ignore the original permissions
// Otherwise, files downloaded from HTTP end up with -r--r--r--
bool remoteSource = !KProtocolManager::supportsListing(uSource);
int permissions = (*it).permissions;
if ( m_defaultPermissions || ( remoteSource && uDest.isLocalFile() ) )
permissions = -1;
JobFlags flags = bOverwrite ? Overwrite : DefaultFlags;
KIO::FileCopyJob * copyJob = KIO::file_copy( uSource, uDest, permissions, flags | HideProgressInfo/*no GUI*/ );
copyJob->setParentJob( q ); // in case of rename dialog
copyJob->setSourceSize( (*it).size );
if ((*it).mtime != -1) {
copyJob->setModificationTime( QDateTime::fromTime_t( (*it).mtime ) );
}
newjob = copyJob;
//kDebug(7007) << "Copying" << uSource << "to" << uDest;
m_currentSrcURL=uSource;
m_currentDestURL=uDest;
m_bURLDirty = true;
}
q->addSubjob(newjob);
q->connect( newjob, SIGNAL( processedSize( KJob*, qulonglong ) ),
SLOT( slotProcessedSize( KJob*, qulonglong ) ) );
q->connect( newjob, SIGNAL( totalSize( KJob*, qulonglong ) ),
SLOT( slotTotalSize( KJob*, qulonglong ) ) );
}
else
{
// We're done
//kDebug(7007) << "copyNextFile finished";
deleteNextDir();
}
}
void CopyJobPrivate::deleteNextDir()
{
Q_Q(CopyJob);
if ( m_mode == CopyJob::Move && !dirsToRemove.isEmpty() ) // some dirs to delete ?
{
state = STATE_DELETING_DIRS;
m_bURLDirty = true;
// Take first dir to delete out of list - last ones first !
KUrl::List::Iterator it = --dirsToRemove.end();
SimpleJob *job = KIO::rmdir( *it );
Scheduler::setJobPriority(job, 1);
dirsToRemove.erase(it);
q->addSubjob( job );
}
else
{
// This step is done, move on
state = STATE_SETTING_DIR_ATTRIBUTES;
m_directoriesCopiedIterator = m_directoriesCopied.constBegin();
setNextDirAttribute();
}
}
void CopyJobPrivate::setNextDirAttribute()
{
Q_Q(CopyJob);
while (m_directoriesCopiedIterator != m_directoriesCopied.constEnd() &&
(*m_directoriesCopiedIterator).mtime == -1) {
++m_directoriesCopiedIterator;
}
if ( m_directoriesCopiedIterator != m_directoriesCopied.constEnd() ) {
const KUrl url = (*m_directoriesCopiedIterator).uDest;
const time_t mtime = (*m_directoriesCopiedIterator).mtime;
const QDateTime dt = QDateTime::fromTime_t(mtime);
++m_directoriesCopiedIterator;
KIO::SimpleJob *job = KIO::setModificationTime( url, dt );
Scheduler::setJobPriority(job, 1);
q->addSubjob( job );
#if 0 // ifdef Q_OS_UNIX
// TODO: can be removed now. Or reintroduced as a fast path for local files
// if launching even more jobs as done above is a performance problem.
//
QLinkedList<CopyInfo>::const_iterator it = m_directoriesCopied.constBegin();
for ( ; it != m_directoriesCopied.constEnd() ; ++it ) {
const KUrl& url = (*it).uDest;
if ( url.isLocalFile() && (*it).mtime != (time_t)-1 ) {
KDE_struct_stat statbuf;
if (KDE::lstat(url.path(), &statbuf) == 0) {
struct utimbuf utbuf;
utbuf.actime = statbuf.st_atime; // access time, unchanged
utbuf.modtime = (*it).mtime; // modification time
utime( path, &utbuf );
}
}
}
m_directoriesCopied.clear();
// but then we need to jump to the else part below. Maybe with a recursive call?
#endif
} else {
if (m_reportTimer)
m_reportTimer->stop();
--m_processedFiles; // undo the "start at 1" hack
slotReport(); // display final numbers, important if progress dialog stays up
q->emitResult();
}
}
void CopyJob::emitResult()
{
Q_D(CopyJob);
// Before we go, tell the world about the changes that were made.
// Even if some error made us abort midway, we might still have done
// part of the job so we better update the views! (#118583)
if (!d->m_bOnlyRenames) {
KUrl url(d->m_globalDest);
if (d->m_globalDestinationState != DEST_IS_DIR || d->m_asMethod)
url.setPath(url.directory());
//kDebug(7007) << "KDirNotify'ing FilesAdded" << url;
org::kde::KDirNotify::emitFilesAdded( url.url() );
if (d->m_mode == CopyJob::Move && !d->m_successSrcList.isEmpty()) {
kDebug(7007) << "KDirNotify'ing FilesRemoved" << d->m_successSrcList.toStringList();
org::kde::KDirNotify::emitFilesRemoved(d->m_successSrcList.toStringList());
}
// Re-enable watching on the dirs that held the deleted files
if (d->m_mode == CopyJob::Move) {
for (QSet<QString>::const_iterator it = d->m_parentDirs.constBegin() ; it != d->m_parentDirs.constEnd() ; ++it)
KDirWatch::self()->restartDirScan( *it );
}
}
Job::emitResult();
}
void CopyJobPrivate::slotProcessedSize( KJob*, qulonglong data_size )
{
Q_Q(CopyJob);
//kDebug(7007) << data_size;
m_fileProcessedSize = data_size;
q->setProcessedAmount(KJob::Bytes, m_processedSize + m_fileProcessedSize);
if ( m_processedSize + m_fileProcessedSize > m_totalSize )
{
// Example: download any attachment from bugs.kde.org
m_totalSize = m_processedSize + m_fileProcessedSize;
//kDebug(7007) << "Adjusting m_totalSize to" << m_totalSize;
q->setTotalAmount(KJob::Bytes, m_totalSize); // safety
}
//kDebug(7007) << "emit processedSize" << (unsigned long) (m_processedSize + m_fileProcessedSize);
q->setProcessedAmount(KJob::Bytes, m_processedSize + m_fileProcessedSize);
}
void CopyJobPrivate::slotTotalSize( KJob*, qulonglong size )
{
Q_Q(CopyJob);
//kDebug(7007) << size;
// Special case for copying a single file
// This is because some protocols don't implement stat properly
// (e.g. HTTP), and don't give us a size in some cases (redirection)
// so we'd rather rely on the size given for the transfer
if ( m_bSingleFileCopy && size != m_totalSize)
{
//kDebug(7007) << "slotTotalSize: updating totalsize to" << size;
m_totalSize = size;
q->setTotalAmount(KJob::Bytes, size);
}
}
void CopyJobPrivate::slotResultDeletingDirs( KJob * job )
{
Q_Q(CopyJob);
if (job->error()) {
// Couldn't remove directory. Well, perhaps it's not empty
// because the user pressed Skip for a given file in it.
// Let's not display "Could not remove dir ..." for each of those dir !
} else {
m_successSrcList.append(static_cast<KIO::SimpleJob*>(job)->url());
}
q->removeSubjob( job );
assert( !q->hasSubjobs() );
deleteNextDir();
}
void CopyJobPrivate::slotResultSettingDirAttributes( KJob * job )
{
Q_Q(CopyJob);
if (job->error())
{
// Couldn't set directory attributes. Ignore the error, it can happen
// with inferior file systems like VFAT.
// Let's not display warnings for each dir like "cp -a" does.
}
q->removeSubjob( job );
assert( !q->hasSubjobs() );
setNextDirAttribute();
}
// We were trying to do a direct renaming, before even stat'ing
void CopyJobPrivate::slotResultRenaming( KJob* job )
{
Q_Q(CopyJob);
int err = job->error();
const QString errText = job->errorText();
// Merge metadata from subjob
KIO::Job* kiojob = dynamic_cast<KIO::Job*>(job);
Q_ASSERT(kiojob);
m_incomingMetaData += kiojob->metaData();
q->removeSubjob( job );
assert ( !q->hasSubjobs() );
// Determine dest again
KUrl dest = m_dest;
if ( destinationState == DEST_IS_DIR && !m_asMethod )
dest.addPath( m_currentSrcURL.fileName() );
if ( err )
{
// Direct renaming didn't work. Try renaming to a temp name,
// this can help e.g. when renaming 'a' to 'A' on a VFAT partition.
// In that case it's the _same_ dir, we don't want to copy+del (data loss!)
if ( m_currentSrcURL.isLocalFile() && m_currentSrcURL.url(KUrl::RemoveTrailingSlash) != dest.url(KUrl::RemoveTrailingSlash) &&
m_currentSrcURL.url(KUrl::RemoveTrailingSlash).toLower() == dest.url(KUrl::RemoveTrailingSlash).toLower() &&
( err == ERR_FILE_ALREADY_EXIST ||
err == ERR_DIR_ALREADY_EXIST ||
err == ERR_IDENTICAL_FILES ) )
{
kDebug(7007) << "Couldn't rename directly, dest already exists. Detected special case of lower/uppercase renaming in same dir, try with 2 rename calls";
const QString _src( m_currentSrcURL.toLocalFile() );
const QString _dest( dest.toLocalFile() );
const QString _tmpPrefix = m_currentSrcURL.directory(KUrl::ObeyTrailingSlash|KUrl::AppendTrailingSlash);
QTemporaryFile tmpFile(_tmpPrefix + "kio_XXXXXX");
const bool openOk = tmpFile.open();
if (!openOk) {
kWarning(7007) << "Couldn't open temp file in" << _tmpPrefix;
} else {
const QString _tmp( tmpFile.fileName() );
tmpFile.close();
tmpFile.remove();
kDebug(7007) << "QTemporaryFile using" << _tmp << "as intermediary";
if (KDE::rename( _src, _tmp ) == 0) {
//kDebug(7007) << "Renaming" << _src << "to" << _tmp << "succeeded";
if (!QFile::exists( _dest ) && KDE::rename(_tmp, _dest) == 0) {
err = 0;
org::kde::KDirNotify::emitFileRenamed(m_currentSrcURL.url(), dest.url());
} else {
kDebug(7007) << "Didn't manage to rename" << _tmp << "to" << _dest << ", reverting";
// Revert back to original name!
if (KDE::rename( _tmp, _src ) != 0) {
kError(7007) << "Couldn't rename" << _tmp << "back to" << _src << '!';
// Severe error, abort
q->Job::slotResult(job); // will set the error and emit result(this)
return;
}
}
} else {
kDebug(7007) << "mv" << _src << _tmp << "failed:" << strerror(errno);
}
}
}
}
if ( err )
{
// This code is similar to CopyJobPrivate::slotResultConflictCopyingFiles
// but here it's about the base src url being moved/renamed
// (m_currentSrcURL) and its dest (m_dest), not about a single file.
// It also means we already stated the dest, here.
// On the other hand we haven't stated the src yet (we skipped doing it
// to save time, since it's not necessary to rename directly!)...
// Existing dest?
if ( err == ERR_DIR_ALREADY_EXIST ||
err == ERR_FILE_ALREADY_EXIST ||
err == ERR_IDENTICAL_FILES )
{
// Should we skip automatically ?
bool isDir = (err == ERR_DIR_ALREADY_EXIST); // ## technically, isDir means "source is dir", not "dest is dir" #######
if ((isDir && m_bAutoSkipDirs) || (!isDir && m_bAutoSkipFiles)) {
// Move on to next source url
skipSrc(isDir);
return;
} else if ((isDir && m_bOverwriteAllDirs) || (!isDir && m_bOverwriteAllFiles)) {
; // nothing to do, stat+copy+del will overwrite
} else if ((isDir && m_bAutoRenameDirs) || (!isDir && m_bAutoRenameFiles)) {
KUrl destDirectory(m_currentDestURL); // dest including filename
destDirectory.setPath(destDirectory.directory());
const QString newName = KIO::RenameDialog::suggestName(destDirectory, m_currentDestURL.fileName());
m_dest.setPath(m_currentDestURL.path());
m_dest.setFileName(newName);
KIO::Job* job = KIO::stat(m_dest, StatJob::DestinationSide, 2, KIO::HideProgressInfo);
state = STATE_STATING;
destinationState = DEST_NOT_STATED;
q->addSubjob(job);
return;
} else if ( q->isInteractive() ) {
QString newPath;
// we lack mtime info for both the src (not stated)
// and the dest (stated but this info wasn't stored)
// Let's do it for local files, at least
KIO::filesize_t sizeSrc = (KIO::filesize_t) -1;
KIO::filesize_t sizeDest = (KIO::filesize_t) -1;
time_t ctimeSrc = (time_t) -1;
time_t ctimeDest = (time_t) -1;
time_t mtimeSrc = (time_t) -1;
time_t mtimeDest = (time_t) -1;
bool destIsDir = err == ERR_DIR_ALREADY_EXIST;
// ## TODO we need to stat the source using KIO::stat
// so that this code is properly network-transparent.
KDE_struct_stat stat_buf;
if ( m_currentSrcURL.isLocalFile() &&
KDE::stat(m_currentSrcURL.toLocalFile(), &stat_buf) == 0 ) {
sizeSrc = stat_buf.st_size;
ctimeSrc = stat_buf.st_ctime;
mtimeSrc = stat_buf.st_mtime;
isDir = S_ISDIR(stat_buf.st_mode);
}
if ( dest.isLocalFile() &&
KDE::stat(dest.toLocalFile(), &stat_buf) == 0 ) {
sizeDest = stat_buf.st_size;
ctimeDest = stat_buf.st_ctime;
mtimeDest = stat_buf.st_mtime;
destIsDir = S_ISDIR(stat_buf.st_mode);
}
// If src==dest, use "overwrite-itself"
RenameDialog_Mode mode = ( m_currentSrcURL == dest ) ? M_OVERWRITE_ITSELF : M_OVERWRITE;
if (!isDir && destIsDir) {
// We can't overwrite a dir with a file.
mode = (RenameDialog_Mode) 0;
}
if ( m_srcList.count() > 1 )
mode = (RenameDialog_Mode) ( mode | M_MULTI | M_SKIP );
if (destIsDir)
mode = (RenameDialog_Mode) ( mode | M_ISDIR );
if (m_reportTimer)
m_reportTimer->stop();
RenameDialog_Result r = q->ui()->askFileRename(
q,
err != ERR_DIR_ALREADY_EXIST ? i18n("File Already Exists") : i18n("Already Exists as Folder"),
m_currentSrcURL.url(),
dest.url(),
mode, newPath,
sizeSrc, sizeDest,
ctimeSrc, ctimeDest,
mtimeSrc, mtimeDest );
if (m_reportTimer)
m_reportTimer->start(REPORT_TIMEOUT);
switch ( r )
{
case R_CANCEL:
{
q->setError( ERR_USER_CANCELED );
q->emitResult();
return;
}
case R_AUTO_RENAME:
if (isDir) {
m_bAutoRenameDirs = true;
}
else {
m_bAutoRenameFiles = true;
}
// fall through
case R_RENAME:
{
// Set m_dest to the chosen destination
// This is only for this src url; the next one will revert to m_globalDest
m_dest.setPath( newPath );
KIO::Job* job = KIO::stat( m_dest, StatJob::DestinationSide, 2, KIO::HideProgressInfo );
state = STATE_STATING;
destinationState = DEST_NOT_STATED;
q->addSubjob(job);
return;
}
case R_AUTO_SKIP:
if (isDir)
m_bAutoSkipDirs = true;
else
m_bAutoSkipFiles = true;
// fall through
case R_SKIP:
// Move on to next url
skipSrc(isDir);
return;
case R_OVERWRITE_ALL:
if (destIsDir)
m_bOverwriteAllDirs = true;
else
m_bOverwriteAllFiles = true;
break;
case R_OVERWRITE:
// Add to overwrite list
// Note that we add dest, not m_dest.
// This ensures that when moving several urls into a dir (m_dest),
// we only overwrite for the current one, not for all.
// When renaming a single file (m_asMethod), it makes no difference.
kDebug(7007) << "adding to overwrite list: " << dest.path();
m_overwriteList.insert( dest.path() );
break;
default:
//assert( 0 );
break;
}
} else if ( err != KIO::ERR_UNSUPPORTED_ACTION ) {
// Dest already exists, and job is not interactive -> abort with error
q->setError( err );
q->setErrorText( errText );
q->emitResult();
return;
}
} else if ( err != KIO::ERR_UNSUPPORTED_ACTION ) {
kDebug(7007) << "Couldn't rename" << m_currentSrcURL << "to" << dest << ", aborting";
q->setError( err );
q->setErrorText( errText );
q->emitResult();
return;
}
kDebug(7007) << "Couldn't rename" << m_currentSrcURL << "to" << dest << ", reverting to normal way, starting with stat";
//kDebug(7007) << "KIO::stat on" << m_currentSrcURL;
KIO::Job* job = KIO::stat( m_currentSrcURL, StatJob::SourceSide, 2, KIO::HideProgressInfo );
state = STATE_STATING;
q->addSubjob(job);
m_bOnlyRenames = false;
}
else
{
kDebug(7007) << "Renaming succeeded, move on";
++m_processedFiles;
emit q->copyingDone( q, *m_currentStatSrc, dest, -1 /*mtime unknown, and not needed*/, true, true );
m_successSrcList.append(*m_currentStatSrc);
statNextSrc();
}
}
void CopyJob::slotResult( KJob *job )
{
Q_D(CopyJob);
//kDebug(7007) << "d->state=" << (int) d->state;
// In each case, what we have to do is :
// 1 - check for errors and treat them
// 2 - removeSubjob(job);
// 3 - decide what to do next
switch ( d->state ) {
case STATE_STATING: // We were trying to stat a src url or the dest
d->slotResultStating( job );
break;
case STATE_RENAMING: // We were trying to do a direct renaming, before even stat'ing
{
d->slotResultRenaming( job );
break;
}
case STATE_LISTING: // recursive listing finished
//kDebug(7007) << "totalSize:" << (unsigned int) d->m_totalSize << "files:" << d->files.count() << "d->dirs:" << d->dirs.count();
// Was there an error ?
if (job->error())
{
Job::slotResult( job ); // will set the error and emit result(this)
return;
}
removeSubjob( job );
assert ( !hasSubjobs() );
d->statNextSrc();
break;
case STATE_CREATING_DIRS:
d->slotResultCreatingDirs( job );
break;
case STATE_CONFLICT_CREATING_DIRS:
d->slotResultConflictCreatingDirs( job );
break;
case STATE_COPYING_FILES:
d->slotResultCopyingFiles( job );
break;
case STATE_CONFLICT_COPYING_FILES:
d->slotResultConflictCopyingFiles( job );
break;
case STATE_DELETING_DIRS:
d->slotResultDeletingDirs( job );
break;
case STATE_SETTING_DIR_ATTRIBUTES:
d->slotResultSettingDirAttributes( job );
break;
default:
assert( 0 );
}
}
void KIO::CopyJob::setDefaultPermissions( bool b )
{
d_func()->m_defaultPermissions = b;
}
KIO::CopyJob::CopyMode KIO::CopyJob::operationMode() const
{
return d_func()->m_mode;
}
void KIO::CopyJob::setAutoSkip(bool autoSkip)
{
d_func()->m_bAutoSkipFiles = autoSkip;
d_func()->m_bAutoSkipDirs = autoSkip;
}
void KIO::CopyJob::setAutoRename(bool autoRename)
{
d_func()->m_bAutoRenameFiles = autoRename;
d_func()->m_bAutoRenameDirs = autoRename;
}
void KIO::CopyJob::setWriteIntoExistingDirectories(bool overwriteAll) // #65926
{
d_func()->m_bOverwriteAllDirs = overwriteAll;
}
CopyJob *KIO::copy(const KUrl& src, const KUrl& dest, JobFlags flags)
{
//kDebug(7007) << "src=" << src << "dest=" << dest;
KUrl::List srcList;
srcList.append( src );
return CopyJobPrivate::newJob(srcList, dest, CopyJob::Copy, false, flags);
}
CopyJob *KIO::copyAs(const KUrl& src, const KUrl& dest, JobFlags flags)
{
//kDebug(7007) << "src=" << src << "dest=" << dest;
KUrl::List srcList;
srcList.append( src );
return CopyJobPrivate::newJob(srcList, dest, CopyJob::Copy, true, flags);
}
CopyJob *KIO::copy( const KUrl::List& src, const KUrl& dest, JobFlags flags )
{
//kDebug(7007) << src << dest;
return CopyJobPrivate::newJob(src, dest, CopyJob::Copy, false, flags);
}
CopyJob *KIO::move(const KUrl& src, const KUrl& dest, JobFlags flags)
{
//kDebug(7007) << src << dest;
KUrl::List srcList;
srcList.append( src );
return CopyJobPrivate::newJob(srcList, dest, CopyJob::Move, false, flags);
}
CopyJob *KIO::moveAs(const KUrl& src, const KUrl& dest, JobFlags flags)
{
//kDebug(7007) << src << dest;
KUrl::List srcList;
srcList.append( src );
return CopyJobPrivate::newJob(srcList, dest, CopyJob::Move, true, flags);
}
CopyJob *KIO::move( const KUrl::List& src, const KUrl& dest, JobFlags flags)
{
//kDebug(7007) << src << dest;
return CopyJobPrivate::newJob(src, dest, CopyJob::Move, false, flags);
}
CopyJob *KIO::link(const KUrl& src, const KUrl& destDir, JobFlags flags)
{
KUrl::List srcList;
srcList.append( src );
return CopyJobPrivate::newJob(srcList, destDir, CopyJob::Link, false, flags);
}
CopyJob *KIO::link(const KUrl::List& srcList, const KUrl& destDir, JobFlags flags)
{
return CopyJobPrivate::newJob(srcList, destDir, CopyJob::Link, false, flags);
}
CopyJob *KIO::linkAs(const KUrl& src, const KUrl& destDir, JobFlags flags )
{
KUrl::List srcList;
srcList.append( src );
return CopyJobPrivate::newJob(srcList, destDir, CopyJob::Link, false, flags);
}
CopyJob *KIO::trash(const KUrl& src, JobFlags flags)
{
KUrl::List srcList;
srcList.append( src );
return CopyJobPrivate::newJob(srcList, KUrl( "trash:/" ), CopyJob::Move, false, flags);
}
CopyJob *KIO::trash(const KUrl::List& srcList, JobFlags flags)
{
return CopyJobPrivate::newJob(srcList, KUrl( "trash:/" ), CopyJob::Move, false, flags);
}
#include "moc_copyjob.cpp"
diff --git a/kio/kio/deletejob.cpp b/kio/kio/deletejob.cpp
index 7b860e8f9e..60bc394570 100644
--- a/kio/kio/deletejob.cpp
+++ b/kio/kio/deletejob.cpp
@@ -1,496 +1,496 @@
/* This file is part of the KDE libraries
Copyright 2000 Stephan Kulow <coolo@kde.org>
Copyright 2000-2009 David Faure <faure@kde.org>
Copyright 2000 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.
*/
#include "deletejob.h"
#include "kdirlister.h"
#include "scheduler.h"
#include "kdirwatch.h"
#include "kprotocolmanager.h"
#include "jobuidelegate.h"
#include <kdirnotify.h>
#include <klocale.h>
#include <kdebug.h>
#include <kde_file.h>
#include <QtCore/QTimer>
#include <QtCore/QFile>
#include <QPointer>
#include "job_p.h"
extern bool kio_resolve_local_urls; // from copyjob.cpp, abused here to save a symbol.
static bool isHttpProtocol(const QString& protocol)
{
return (protocol.startsWith(QLatin1String("webdav"), Qt::CaseInsensitive) ||
protocol.startsWith(QLatin1String("http"), Qt::CaseInsensitive));
}
namespace KIO
{
enum DeleteJobState {
DELETEJOB_STATE_STATING,
DELETEJOB_STATE_DELETING_FILES,
DELETEJOB_STATE_DELETING_DIRS
};
/*
static const char* const s_states[] = {
"DELETEJOB_STATE_STATING",
"DELETEJOB_STATE_DELETING_FILES",
"DELETEJOB_STATE_DELETING_DIRS"
};
*/
class DeleteJobPrivate: public KIO::JobPrivate
{
public:
DeleteJobPrivate(const KUrl::List& src)
: state( DELETEJOB_STATE_STATING )
, m_processedFiles( 0 )
, m_processedDirs( 0 )
, m_totalFilesDirs( 0 )
, m_srcList( src )
, m_currentStat( m_srcList.begin() )
, m_reportTimer( 0 )
{
}
DeleteJobState state;
int m_processedFiles;
int m_processedDirs;
int m_totalFilesDirs;
KUrl m_currentURL;
KUrl::List files;
KUrl::List symlinks;
KUrl::List dirs;
KUrl::List m_srcList;
KUrl::List::iterator m_currentStat;
QSet<QString> m_parentDirs;
QTimer *m_reportTimer;
void statNextSrc();
void currentSourceStated(bool isDir, bool isLink);
void finishedStatPhase();
void deleteNextFile();
void deleteNextDir();
void slotReport();
void slotStart();
void slotEntries( KIO::Job*, const KIO::UDSEntryList& list );
Q_DECLARE_PUBLIC(DeleteJob)
static inline DeleteJob *newJob(const KUrl::List &src, JobFlags flags)
{
DeleteJob *job = new DeleteJob(*new DeleteJobPrivate(src));
job->setUiDelegate(new JobUiDelegate);
if (!(flags & HideProgressInfo))
KIO::getJobTracker()->registerJob(job);
return job;
}
};
} // namespace KIO
using namespace KIO;
DeleteJob::DeleteJob(DeleteJobPrivate &dd)
: Job(dd)
{
d_func()->m_reportTimer = new QTimer(this);
connect(d_func()->m_reportTimer,SIGNAL(timeout()),this,SLOT(slotReport()));
//this will update the report dialog with 5 Hz, I think this is fast enough, aleXXX
d_func()->m_reportTimer->start( 200 );
QTimer::singleShot(0, this, SLOT(slotStart()));
}
DeleteJob::~DeleteJob()
{
}
KUrl::List DeleteJob::urls() const
{
return d_func()->m_srcList;
}
void DeleteJobPrivate::slotStart()
{
statNextSrc();
}
void DeleteJobPrivate::slotReport()
{
Q_Q(DeleteJob);
emit q->deleting( q, m_currentURL );
// TODO: maybe we could skip everything else when (flags & HideProgressInfo) ?
JobPrivate::emitDeleting( q, m_currentURL);
switch( state ) {
case DELETEJOB_STATE_STATING:
q->setTotalAmount(KJob::Files, files.count());
q->setTotalAmount(KJob::Directories, dirs.count());
break;
case DELETEJOB_STATE_DELETING_DIRS:
q->setProcessedAmount(KJob::Directories, m_processedDirs);
q->emitPercent( m_processedFiles + m_processedDirs, m_totalFilesDirs );
break;
case DELETEJOB_STATE_DELETING_FILES:
q->setProcessedAmount(KJob::Files, m_processedFiles);
q->emitPercent( m_processedFiles, m_totalFilesDirs );
break;
}
}
void DeleteJobPrivate::slotEntries(KIO::Job* job, const UDSEntryList& list)
{
UDSEntryList::ConstIterator it = list.begin();
const UDSEntryList::ConstIterator end = list.end();
for (; it != end; ++it)
{
const UDSEntry& entry = *it;
const QString displayName = entry.stringValue( KIO::UDSEntry::UDS_NAME );
Q_ASSERT(!displayName.isEmpty());
if (displayName != ".." && displayName != ".")
{
KUrl url;
const QString urlStr = entry.stringValue( KIO::UDSEntry::UDS_URL );
if ( !urlStr.isEmpty() )
url = urlStr;
else {
url = static_cast<SimpleJob *>(job)->url(); // assumed to be a dir
url.addPath( displayName );
}
//kDebug(7007) << displayName << "(" << url << ")";
if ( entry.isLink() )
symlinks.append( url );
else if ( entry.isDir() )
dirs.append( url );
else
files.append( url );
}
}
}
void DeleteJobPrivate::statNextSrc()
{
Q_Q(DeleteJob);
//kDebug(7007);
if (m_currentStat != m_srcList.end()) {
m_currentURL = (*m_currentStat);
// if the file system doesn't support deleting, we do not even stat
if (!KProtocolManager::supportsDeleting(m_currentURL)) {
QPointer<DeleteJob> that = q;
++m_currentStat;
emit q->warning( q, buildErrorString(ERR_CANNOT_DELETE, m_currentURL.prettyUrl()) );
if (that)
statNextSrc();
return;
}
// Stat it
state = DELETEJOB_STATE_STATING;
// Fast path for KFileItems in directory views
while(m_currentStat != m_srcList.end()) {
m_currentURL = (*m_currentStat);
const KFileItem cachedItem = KDirLister::cachedItemForUrl(m_currentURL);
if (cachedItem.isNull())
break;
//kDebug(7007) << "Found cached info about" << m_currentURL << "isDir=" << cachedItem.isDir() << "isLink=" << cachedItem.isLink();
currentSourceStated(cachedItem.isDir(), cachedItem.isLink());
++m_currentStat;
}
// Hook for unit test to disable the fast path.
if (!kio_resolve_local_urls) {
// Fast path for local files
// (using a loop, instead of a huge recursion)
while(m_currentStat != m_srcList.end() && (*m_currentStat).isLocalFile()) {
m_currentURL = (*m_currentStat);
QFileInfo fileInfo(m_currentURL.toLocalFile());
currentSourceStated(fileInfo.isDir(), fileInfo.isSymLink());
++m_currentStat;
}
}
if (m_currentStat == m_srcList.end()) {
// Done, jump to the last else of this method
statNextSrc();
} else {
KIO::SimpleJob * job = KIO::stat( m_currentURL, StatJob::SourceSide, 0, KIO::HideProgressInfo );
Scheduler::setJobPriority(job, 1);
//kDebug(7007) << "stat'ing" << m_currentURL;
q->addSubjob(job);
}
} else {
if (!q->hasSubjobs()) // don't go there yet if we're still listing some subdirs
finishedStatPhase();
}
}
void DeleteJobPrivate::finishedStatPhase()
{
m_totalFilesDirs = files.count() + symlinks.count() + dirs.count();
slotReport();
// Now we know which dirs hold the files we're going to delete.
// To speed things up and prevent double-notification, we disable KDirWatch
// on those dirs temporarily (using KDirWatch::self, that's the instance
// used by e.g. kdirlister).
const QSet<QString>::const_iterator itEnd = m_parentDirs.constEnd();
for ( QSet<QString>::const_iterator it = m_parentDirs.constBegin() ; it != itEnd ; ++it )
KDirWatch::self()->stopDirScan( *it );
state = DELETEJOB_STATE_DELETING_FILES;
deleteNextFile();
}
void DeleteJobPrivate::deleteNextFile()
{
Q_Q(DeleteJob);
//kDebug(7007);
if ( !files.isEmpty() || !symlinks.isEmpty() )
{
SimpleJob *job;
do {
// Take first file to delete out of list
KUrl::List::iterator it = files.begin();
bool isLink = false;
if ( it == files.end() ) // No more files
{
it = symlinks.begin(); // Pick up a symlink to delete
isLink = true;
}
// Normal deletion
// If local file, try do it directly
#ifdef Q_WS_WIN
if ( (*it).isLocalFile() && DeleteFileW( (LPCWSTR)(*it).toLocalFile().utf16() ) == 0 ) {
#else
if ( (*it).isLocalFile() && unlink( QFile::encodeName((*it).toLocalFile()) ) == 0 ) {
#endif
//kdDebug(7007) << "DeleteJob deleted" << (*it).toLocalFile();
job = 0;
m_processedFiles++;
if ( m_processedFiles % 300 == 1 || m_totalFilesDirs < 300) { // update progress info every 300 files
m_currentURL = *it;
slotReport();
}
} else
{ // if remote - or if unlink() failed (we'll use the job's error handling in that case)
//kDebug(7007) << "calling file_delete on" << *it;
- if (isHttpProtocol(it->protocol()))
+ if (isHttpProtocol(it->scheme()))
job = KIO::http_delete( *it, KIO::HideProgressInfo );
else
job = KIO::file_delete( *it, KIO::HideProgressInfo );
Scheduler::setJobPriority(job, 1);
m_currentURL=(*it);
}
if ( isLink )
symlinks.erase(it);
else
files.erase(it);
if ( job ) {
q->addSubjob(job);
return;
}
// loop only if direct deletion worked (job=0) and there is something else to delete
} while (!job && (!files.isEmpty() || !symlinks.isEmpty()));
}
state = DELETEJOB_STATE_DELETING_DIRS;
deleteNextDir();
}
void DeleteJobPrivate::deleteNextDir()
{
Q_Q(DeleteJob);
if ( !dirs.isEmpty() ) // some dirs to delete ?
{
do {
// Take first dir to delete out of list - last ones first !
KUrl::List::iterator it = --dirs.end();
// If local dir, try to rmdir it directly
#ifdef Q_WS_WIN
if ( (*it).isLocalFile() && RemoveDirectoryW( (LPCWSTR)(*it).toLocalFile().utf16() ) != 0 ) {
#else
if ( (*it).isLocalFile() && ::rmdir( QFile::encodeName((*it).toLocalFile()) ) == 0 ) {
#endif
m_processedDirs++;
if ( m_processedDirs % 100 == 1 ) { // update progress info every 100 dirs
m_currentURL = *it;
slotReport();
}
} else {
// Call rmdir - works for kioslaves with canDeleteRecursive too,
// CMD_DEL will trigger the recursive deletion in the slave.
SimpleJob* job = KIO::rmdir( *it );
job->addMetaData(QString::fromLatin1("recurse"), "true");
Scheduler::setJobPriority(job, 1);
dirs.erase(it);
q->addSubjob( job );
return;
}
dirs.erase(it);
} while ( !dirs.isEmpty() );
}
// Re-enable watching on the dirs that held the deleted files
const QSet<QString>::const_iterator itEnd = m_parentDirs.constEnd();
for (QSet<QString>::const_iterator it = m_parentDirs.constBegin() ; it != itEnd ; ++it) {
KDirWatch::self()->restartDirScan( *it );
}
// Finished - tell the world
if ( !m_srcList.isEmpty() )
{
//kDebug(7007) << "KDirNotify'ing FilesRemoved " << m_srcList.toStringList();
org::kde::KDirNotify::emitFilesRemoved( m_srcList.toStringList() );
}
if (m_reportTimer!=0)
m_reportTimer->stop();
q->emitResult();
}
void DeleteJobPrivate::currentSourceStated(bool isDir, bool isLink)
{
Q_Q(DeleteJob);
const KUrl url = (*m_currentStat);
if (isDir && !isLink) {
// Add toplevel dir in list of dirs
dirs.append( url );
if (url.isLocalFile()) {
// We are about to delete this dir, no need to watch it
// Maybe we should ask kdirwatch to remove all watches recursively?
// But then there would be no feedback (things disappearing progressively) during huge deletions
KDirWatch::self()->stopDirScan(url.toLocalFile(KUrl::RemoveTrailingSlash));
}
if (!KProtocolManager::canDeleteRecursive(url)) {
//kDebug(7007) << url << "is a directory, let's list it";
ListJob *newjob = KIO::listRecursive(url, KIO::HideProgressInfo);
newjob->addMetaData("details", "0");
newjob->setUnrestricted(true); // No KIOSK restrictions
Scheduler::setJobPriority(newjob, 1);
QObject::connect(newjob, SIGNAL(entries(KIO::Job*, const KIO::UDSEntryList&)),
q, SLOT(slotEntries(KIO::Job*,const KIO::UDSEntryList&)));
q->addSubjob(newjob);
// Note that this listing job will happen in parallel with other stat jobs.
}
} else {
if (isLink) {
//kDebug(7007) << "Target is a symlink";
symlinks.append(url);
} else {
//kDebug(7007) << "Target is a file";
files.append(url);
}
}
if (url.isLocalFile()) {
const QString parentDir = url.directory(KUrl::IgnoreTrailingSlash);
m_parentDirs.insert(parentDir);
}
}
void DeleteJob::slotResult( KJob *job )
{
Q_D(DeleteJob);
switch ( d->state )
{
case DELETEJOB_STATE_STATING:
removeSubjob( job );
// Was this a stat job or a list job? We do both in parallel.
if (StatJob* statJob = qobject_cast<StatJob*>(job)) {
// Was there an error while stating ?
if (job->error()) {
// Probably : doesn't exist
Job::slotResult(job); // will set the error and emit result(this)
return;
}
const UDSEntry entry = statJob->statResult();
// Is it a file or a dir ?
const bool isLink = entry.isLink();
const bool isDir = entry.isDir();
d->currentSourceStated(isDir, isLink);
++d->m_currentStat;
d->statNextSrc();
} else {
if (job->error()) {
// Try deleting nonetheless, it may be empty (and non-listable)
}
if (!hasSubjobs())
d->finishedStatPhase();
}
break;
case DELETEJOB_STATE_DELETING_FILES:
// Propagate the subjob's metadata (a SimpleJob) to the real DeleteJob
// FIXME: setMetaData() in the KIO API only allows access to outgoing metadata,
// but we need to alter the incoming one
d->m_incomingMetaData = dynamic_cast<KIO::Job*>(job)->metaData();
if ( job->error() )
{
Job::slotResult( job ); // will set the error and emit result(this)
return;
}
removeSubjob( job );
Q_ASSERT( !hasSubjobs() );
d->m_processedFiles++;
d->deleteNextFile();
break;
case DELETEJOB_STATE_DELETING_DIRS:
if ( job->error() )
{
Job::slotResult( job ); // will set the error and emit result(this)
return;
}
removeSubjob( job );
Q_ASSERT( !hasSubjobs() );
d->m_processedDirs++;
//emit processedAmount( this, KJob::Directories, d->m_processedDirs );
//emitPercent( d->m_processedFiles + d->m_processedDirs, d->m_totalFilesDirs );
d->deleteNextDir();
break;
default:
Q_ASSERT(0);
}
}
DeleteJob *KIO::del( const KUrl& src, JobFlags flags )
{
KUrl::List srcList;
srcList.append( src );
return DeleteJobPrivate::newJob(srcList, flags);
}
DeleteJob *KIO::del( const KUrl::List& src, JobFlags flags )
{
return DeleteJobPrivate::newJob(src, flags);
}
#include "moc_deletejob.cpp"
diff --git a/kio/kio/fileundomanager.cpp b/kio/kio/fileundomanager.cpp
index 86fde2f625..f072ddb318 100644
--- a/kio/kio/fileundomanager.cpp
+++ b/kio/kio/fileundomanager.cpp
@@ -1,814 +1,814 @@
/* This file is part of the KDE project
Copyright (C) 2000 Simon Hausmann <hausmann@kde.org>
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 "fileundomanager.h"
#include "fileundomanager_p.h"
#include "fileundomanager_adaptor.h"
#include <kdatetime.h>
#include <kdebug.h>
#include <kdirnotify.h>
#include <kglobal.h>
#include <kio/copyjob.h>
#include <kio/job.h>
#include <kio/jobuidelegate.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kjobtrackerinterface.h>
#include <QtDBus/QtDBus>
#include <assert.h>
using namespace KIO;
static const char* undoStateToString(UndoState state) {
static const char* const s_undoStateToString[] = { "MAKINGDIRS", "MOVINGFILES", "STATINGFILE", "REMOVINGDIRS", "REMOVINGLINKS" };
return s_undoStateToString[state];
}
static QDataStream &operator<<(QDataStream &stream, const KIO::BasicOperation &op)
{
stream << op.m_valid << (qint8)op.m_type << op.m_renamed
<< op.m_src << op.m_dst << op.m_target << (qint64)op.m_mtime;
return stream;
}
static QDataStream &operator>>(QDataStream &stream, BasicOperation &op)
{
qint8 type;
qint64 mtime;
stream >> op.m_valid >> type >> op.m_renamed
>> op.m_src >> op.m_dst >> op.m_target >> mtime;
op.m_type = static_cast<BasicOperation::Type>(type);
op.m_mtime = mtime;
return stream;
}
static QDataStream &operator<<(QDataStream &stream, const UndoCommand &cmd)
{
stream << cmd.m_valid << (qint8)cmd.m_type << cmd.m_opStack << cmd.m_src << cmd.m_dst;
return stream;
}
static QDataStream &operator>>(QDataStream &stream, UndoCommand &cmd)
{
qint8 type;
stream >> cmd.m_valid >> type >> cmd.m_opStack >> cmd.m_src >> cmd.m_dst;
cmd.m_type = static_cast<FileUndoManager::CommandType>(type);
return stream;
}
/**
* checklist:
* copy dir -> overwrite -> works
* move dir -> overwrite -> works
* copy dir -> rename -> works
* move dir -> rename -> works
*
* copy dir -> works
* move dir -> works
*
* copy files -> works
* move files -> works (TODO: optimize (change FileCopyJob to use the renamed arg for copyingDone)
*
* copy files -> overwrite -> works (sorry for your overwritten file...)
* move files -> overwrite -> works (sorry for your overwritten file...)
*
* copy files -> rename -> works
* move files -> rename -> works
*
* -> see also fileundomanagertest, which tests some of the above (but not renaming).
*
*/
class KIO::UndoJob : public KIO::Job
{
public:
UndoJob(bool showProgressInfo) : KIO::Job() {
if (showProgressInfo)
KIO::getJobTracker()->registerJob(this);
}
virtual ~UndoJob() {}
virtual void kill(bool) {
FileUndoManager::self()->d->stopUndo(true);
KIO::Job::doKill();
}
void emitCreatingDir(const KUrl &dir)
{ emit description(this, i18n("Creating directory"),
qMakePair(i18n("Directory"), dir.prettyUrl())); }
void emitMoving(const KUrl &src, const KUrl &dest)
{ emit description(this, i18n("Moving"),
qMakePair(i18nc("The source of a file operation", "Source"), src.prettyUrl()),
qMakePair(i18nc("The destination of a file operation", "Destination"), dest.prettyUrl())); }
void emitDeleting(const KUrl &url)
{ emit description(this, i18n("Deleting"),
qMakePair(i18n("File"), url.prettyUrl())); }
void emitResult() { KIO::Job::emitResult(); }
};
CommandRecorder::CommandRecorder(FileUndoManager::CommandType op, const KUrl::List &src, const KUrl &dst, KIO::Job *job)
: QObject(job)
{
m_cmd.m_type = op;
m_cmd.m_valid = true;
m_cmd.m_serialNumber = FileUndoManager::self()->newCommandSerialNumber();
m_cmd.m_src = src;
m_cmd.m_dst = dst;
connect(job, SIGNAL(result(KJob*)),
this, SLOT(slotResult(KJob*)));
// TODO whitelist, instead
if (op != FileUndoManager::Mkdir && op != FileUndoManager::Put) {
connect(job, SIGNAL(copyingDone(KIO::Job*,KUrl,KUrl,time_t,bool,bool)),
this, SLOT(slotCopyingDone(KIO::Job*,KUrl,KUrl,time_t,bool,bool)));
connect(job, SIGNAL(copyingLinkDone(KIO::Job *,KUrl,QString,KUrl)),
this, SLOT(slotCopyingLinkDone(KIO::Job *,KUrl,QString,KUrl)));
}
}
CommandRecorder::~CommandRecorder()
{
}
void CommandRecorder::slotResult(KJob *job)
{
if (job->error())
return;
FileUndoManager::self()->d->addCommand(m_cmd);
}
void CommandRecorder::slotCopyingDone(KIO::Job *job, const KUrl &from, const KUrl &to, time_t mtime, bool directory, bool renamed)
{
BasicOperation op;
op.m_valid = true;
op.m_type = directory ? BasicOperation::Directory : BasicOperation::File;
op.m_renamed = renamed;
op.m_src = from;
op.m_dst = to;
op.m_mtime = mtime;
if (m_cmd.m_type == FileUndoManager::Trash)
{
- Q_ASSERT(to.protocol() == "trash");
+ Q_ASSERT(to.scheme() == "trash");
const QMap<QString, QString> metaData = job->metaData();
QMap<QString, QString>::ConstIterator it = metaData.find("trashURL-" + from.path());
if (it != metaData.constEnd()) {
// Update URL
op.m_dst = it.value();
}
}
m_cmd.m_opStack.prepend(op);
}
// TODO merge the signals?
void CommandRecorder::slotCopyingLinkDone(KIO::Job *, const KUrl &from, const QString &target, const KUrl &to)
{
BasicOperation op;
op.m_valid = true;
op.m_type = BasicOperation::Link;
op.m_renamed = false;
op.m_src = from;
op.m_target = target;
op.m_dst = to;
op.m_mtime = -1;
m_cmd.m_opStack.prepend(op);
}
////
class KIO::FileUndoManagerSingleton
{
public:
FileUndoManager self;
};
K_GLOBAL_STATIC(KIO::FileUndoManagerSingleton, globalFileUndoManager)
FileUndoManager *FileUndoManager::self()
{
return &globalFileUndoManager->self;
}
// m_nextCommandIndex is initialized to a high number so that konqueror can
// assign low numbers to closed items loaded "on-demand" from a config file
// in KonqClosedWindowsManager::readConfig and thus maintaining the real
// order of the undo items.
FileUndoManagerPrivate::FileUndoManagerPrivate(FileUndoManager* qq)
: m_uiInterface(new FileUndoManager::UiInterface()),
m_undoJob(0), m_nextCommandIndex(1000), q(qq)
{
m_syncronized = initializeFromKDesky();
(void) new KIOFileUndoManagerAdaptor(this);
const QString dbusPath = "/FileUndoManager";
const QString dbusInterface = "org.kde.kio.FileUndoManager";
QDBusConnection dbus = QDBusConnection::sessionBus();
dbus.registerObject(dbusPath, this);
dbus.connect(QString(), dbusPath, dbusInterface, "lock", this, SLOT(slotLock()));
dbus.connect(QString(), dbusPath, dbusInterface, "pop", this, SLOT(slotPop()));
dbus.connect(QString(), dbusPath, dbusInterface, "push", this, SLOT(slotPush(QByteArray)));
dbus.connect(QString(), dbusPath, dbusInterface, "unlock", this, SLOT(slotUnlock()));
}
FileUndoManager::FileUndoManager()
{
d = new FileUndoManagerPrivate(this);
d->m_lock = false;
d->m_currentJob = 0;
}
FileUndoManager::~FileUndoManager()
{
delete d;
}
void FileUndoManager::recordJob(CommandType op, const KUrl::List &src, const KUrl &dst, KIO::Job *job)
{
// This records what the job does and calls addCommand when done
(void) new CommandRecorder(op, src, dst, job);
emit jobRecordingStarted(op);
}
void FileUndoManager::recordCopyJob(KIO::CopyJob* copyJob)
{
CommandType commandType;
switch (copyJob->operationMode()) {
case CopyJob::Copy:
commandType = Copy;
break;
case CopyJob::Move:
commandType = Move;
break;
case CopyJob::Link:
default: // prevent "wrong" compiler warning because of possibly uninitialized variable
commandType = Link;
break;
}
recordJob(commandType, copyJob->srcUrls(), copyJob->destUrl(), copyJob);
}
void FileUndoManagerPrivate::addCommand(const UndoCommand &cmd)
{
broadcastPush(cmd);
emit q->jobRecordingFinished(cmd.m_type);
}
bool FileUndoManager::undoAvailable() const
{
return (d->m_commands.count() > 0) && !d->m_lock;
}
QString FileUndoManager::undoText() const
{
if (d->m_commands.isEmpty())
return i18n("Und&o");
FileUndoManager::CommandType t = d->m_commands.last().m_type;
switch(t) {
case FileUndoManager::Copy:
return i18n("Und&o: Copy");
case FileUndoManager::Link:
return i18n("Und&o: Link");
case FileUndoManager::Move:
return i18n("Und&o: Move");
case FileUndoManager::Rename:
return i18n("Und&o: Rename");
case FileUndoManager::Trash:
return i18n("Und&o: Trash");
case FileUndoManager::Mkdir:
return i18n("Und&o: Create Folder");
case FileUndoManager::Put:
return i18n("Und&o: Create File");
}
/* NOTREACHED */
return QString();
}
quint64 FileUndoManager::newCommandSerialNumber()
{
return ++(d->m_nextCommandIndex);
}
quint64 FileUndoManager::currentCommandSerialNumber() const
{
if(!d->m_commands.isEmpty())
{
const UndoCommand& cmd = d->m_commands.last();
assert(cmd.m_valid);
return cmd.m_serialNumber;
} else
return 0;
}
void FileUndoManager::undo()
{
// Make a copy of the command to undo before broadcastPop() pops it.
UndoCommand cmd = d->m_commands.last();
assert(cmd.m_valid);
d->m_current = cmd;
BasicOperation::Stack& opStack = d->m_current.m_opStack;
// Note that opStack is empty for simple operations like Mkdir.
// Let's first ask for confirmation if we need to delete any file (#99898)
KUrl::List fileCleanupStack;
BasicOperation::Stack::Iterator it = opStack.begin();
for (; it != opStack.end() ; ++it) {
BasicOperation::Type type = (*it).m_type;
if (type == BasicOperation::File && d->m_current.m_type == FileUndoManager::Copy) {
fileCleanupStack.append((*it).m_dst);
}
}
if (d->m_current.m_type == FileUndoManager::Mkdir || d->m_current.m_type == FileUndoManager::Put) {
fileCleanupStack.append(d->m_current.m_dst);
}
if (!fileCleanupStack.isEmpty()) {
if (!d->m_uiInterface->confirmDeletion(fileCleanupStack)) {
return;
}
}
d->broadcastPop();
d->broadcastLock();
d->m_dirCleanupStack.clear();
d->m_dirStack.clear();
d->m_dirsToUpdate.clear();
d->m_undoState = MOVINGFILES;
// Let's have a look at the basic operations we need to undo.
// While we're at it, collect all links that should be deleted.
it = opStack.begin();
while (it != opStack.end()) // don't cache end() here, erase modifies it
{
bool removeBasicOperation = false;
BasicOperation::Type type = (*it).m_type;
if (type == BasicOperation::Directory && !(*it).m_renamed)
{
// If any directory has to be created/deleted, we'll start with that
d->m_undoState = MAKINGDIRS;
// Collect all the dirs that have to be created in case of a move undo.
if (d->m_current.isMoveCommand())
d->m_dirStack.push((*it).m_src);
// Collect all dirs that have to be deleted
// from the destination in both cases (copy and move).
d->m_dirCleanupStack.prepend((*it).m_dst);
removeBasicOperation = true;
}
else if (type == BasicOperation::Link)
{
d->m_fileCleanupStack.prepend((*it).m_dst);
removeBasicOperation = !d->m_current.isMoveCommand();
}
if (removeBasicOperation)
it = opStack.erase(it);
else
++it;
}
if (d->m_current.m_type == FileUndoManager::Put) {
d->m_fileCleanupStack.append(d->m_current.m_dst);
}
kDebug(1203) << "starting with" << undoStateToString(d->m_undoState);
d->m_undoJob = new UndoJob(d->m_uiInterface->showProgressInfo());
d->undoStep();
}
void FileUndoManagerPrivate::stopUndo(bool step)
{
m_current.m_opStack.clear();
m_dirCleanupStack.clear();
m_fileCleanupStack.clear();
m_undoState = REMOVINGDIRS;
m_undoJob = 0;
if (m_currentJob)
m_currentJob->kill();
m_currentJob = 0;
if (step)
undoStep();
}
void FileUndoManagerPrivate::slotResult(KJob *job)
{
m_currentJob = 0;
if (job->error())
{
m_uiInterface->jobError(static_cast<KIO::Job*>(job));
delete m_undoJob;
stopUndo(false);
}
else if (m_undoState == STATINGFILE)
{
BasicOperation op = m_current.m_opStack.last();
//kDebug(1203) << "stat result for " << op.m_dst;
KIO::StatJob* statJob = static_cast<KIO::StatJob*>(job);
time_t mtime = statJob->statResult().numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1);
if (mtime != op.m_mtime) {
kDebug(1203) << op.m_dst << " was modified after being copied!";
KDateTime srcTime; srcTime.setTime_t(op.m_mtime); srcTime = srcTime.toLocalZone();
KDateTime destTime; destTime.setTime_t(mtime); destTime = destTime.toLocalZone();
if (!m_uiInterface->copiedFileWasModified(op.m_src, op.m_dst, srcTime, destTime)) {
stopUndo(false);
}
}
}
undoStep();
}
void FileUndoManagerPrivate::addDirToUpdate(const KUrl& url)
{
if (!m_dirsToUpdate.contains(url))
m_dirsToUpdate.prepend(url);
}
void FileUndoManagerPrivate::undoStep()
{
m_currentJob = 0;
if (m_undoState == MAKINGDIRS)
stepMakingDirectories();
if (m_undoState == MOVINGFILES || m_undoState == STATINGFILE)
stepMovingFiles();
if (m_undoState == REMOVINGLINKS)
stepRemovingLinks();
if (m_undoState == REMOVINGDIRS)
stepRemovingDirectories();
if (m_currentJob) {
if (m_uiInterface)
m_currentJob->ui()->setWindow(m_uiInterface->parentWidget());
QObject::connect(m_currentJob, SIGNAL(result(KJob*)),
this, SLOT(slotResult(KJob*)));
}
}
void FileUndoManagerPrivate::stepMakingDirectories()
{
if (!m_dirStack.isEmpty()) {
KUrl dir = m_dirStack.pop();
kDebug(1203) << "creatingDir" << dir;
m_currentJob = KIO::mkdir(dir);
m_undoJob->emitCreatingDir(dir);
}
else
m_undoState = MOVINGFILES;
}
// Misnamed method: It moves files back, but it also
// renames directories back, recreates symlinks,
// deletes copied files, and restores trashed files.
void FileUndoManagerPrivate::stepMovingFiles()
{
if (!m_current.m_opStack.isEmpty())
{
BasicOperation op = m_current.m_opStack.last();
BasicOperation::Type type = op.m_type;
assert(op.m_valid);
if (type == BasicOperation::Directory)
{
if (op.m_renamed)
{
kDebug(1203) << "rename" << op.m_dst << op.m_src;
m_currentJob = KIO::rename(op.m_dst, op.m_src, KIO::HideProgressInfo);
m_undoJob->emitMoving(op.m_dst, op.m_src);
}
else
assert(0); // this should not happen!
}
else if (type == BasicOperation::Link)
{
kDebug(1203) << "symlink" << op.m_target << op.m_src;
m_currentJob = KIO::symlink(op.m_target, op.m_src, KIO::Overwrite | KIO::HideProgressInfo);
}
else if (m_current.m_type == FileUndoManager::Copy)
{
if (m_undoState == MOVINGFILES) // dest not stat'ed yet
{
// Before we delete op.m_dst, let's check if it was modified (#20532)
kDebug(1203) << "stat" << op.m_dst;
m_currentJob = KIO::stat(op.m_dst, KIO::HideProgressInfo);
m_undoState = STATINGFILE; // temporarily
return; // no pop() yet, we'll finish the work in slotResult
}
else // dest was stat'ed, and the deletion was approved in slotResult
{
m_currentJob = KIO::file_delete(op.m_dst, KIO::HideProgressInfo);
m_undoJob->emitDeleting(op.m_dst);
m_undoState = MOVINGFILES;
}
}
else if (m_current.isMoveCommand()
|| m_current.m_type == FileUndoManager::Trash)
{
kDebug(1203) << "file_move" << op.m_dst << op.m_src;
m_currentJob = KIO::file_move(op.m_dst, op.m_src, -1, KIO::Overwrite | KIO::HideProgressInfo);
m_undoJob->emitMoving(op.m_dst, op.m_src);
}
m_current.m_opStack.removeLast();
// The above KIO jobs are lowlevel, they don't trigger KDirNotify notification
// So we need to do it ourselves (but schedule it to the end of the undo, to compress them)
KUrl url(op.m_dst);
url.setPath(url.directory());
addDirToUpdate(url);
url = op.m_src;
url.setPath(url.directory());
addDirToUpdate(url);
}
else
m_undoState = REMOVINGLINKS;
}
void FileUndoManagerPrivate::stepRemovingLinks()
{
kDebug(1203) << "REMOVINGLINKS";
if (!m_fileCleanupStack.isEmpty())
{
KUrl file = m_fileCleanupStack.pop();
kDebug(1203) << "file_delete" << file;
m_currentJob = KIO::file_delete(file, KIO::HideProgressInfo);
m_undoJob->emitDeleting(file);
KUrl url(file);
url.setPath(url.directory());
addDirToUpdate(url);
}
else
{
m_undoState = REMOVINGDIRS;
if (m_dirCleanupStack.isEmpty() && m_current.m_type == FileUndoManager::Mkdir)
m_dirCleanupStack << m_current.m_dst;
}
}
void FileUndoManagerPrivate::stepRemovingDirectories()
{
if (!m_dirCleanupStack.isEmpty())
{
KUrl dir = m_dirCleanupStack.pop();
kDebug(1203) << "rmdir" << dir;
m_currentJob = KIO::rmdir(dir);
m_undoJob->emitDeleting(dir);
addDirToUpdate(dir);
}
else
{
m_current.m_valid = false;
m_currentJob = 0;
if (m_undoJob)
{
kDebug(1203) << "deleting undojob";
m_undoJob->emitResult();
m_undoJob = 0;
}
QList<KUrl>::ConstIterator it = m_dirsToUpdate.constBegin();
for(; it != m_dirsToUpdate.constEnd(); ++it) {
kDebug() << "Notifying FilesAdded for " << *it;
org::kde::KDirNotify::emitFilesAdded((*it).url());
}
emit q->undoJobFinished();
broadcastUnlock();
}
}
// const ref doesn't work due to QDataStream
void FileUndoManagerPrivate::slotPush(QByteArray data)
{
QDataStream strm(&data, QIODevice::ReadOnly);
UndoCommand cmd;
strm >> cmd;
pushCommand(cmd);
}
void FileUndoManagerPrivate::pushCommand(const UndoCommand& cmd)
{
m_commands.append(cmd);
emit q->undoAvailable(true);
emit q->undoTextChanged(q->undoText());
}
void FileUndoManagerPrivate::slotPop()
{
m_commands.removeLast();
emit q->undoAvailable(q->undoAvailable());
emit q->undoTextChanged(q->undoText());
}
void FileUndoManagerPrivate::slotLock()
{
// assert(!m_lock);
m_lock = true;
emit q->undoAvailable(q->undoAvailable());
}
void FileUndoManagerPrivate::slotUnlock()
{
// assert(m_lock);
m_lock = false;
emit q->undoAvailable(q->undoAvailable());
}
QByteArray FileUndoManagerPrivate::get() const
{
QByteArray data;
QDataStream stream(&data, QIODevice::WriteOnly);
stream << m_commands;
return data;
}
void FileUndoManagerPrivate::broadcastPush(const UndoCommand &cmd)
{
if (!m_syncronized) {
pushCommand(cmd);
return;
}
QByteArray data;
QDataStream stream(&data, QIODevice::WriteOnly);
stream << cmd;
emit push(data); // DBUS signal
}
void FileUndoManagerPrivate::broadcastPop()
{
if (!m_syncronized) {
slotPop();
return;
}
emit pop(); // DBUS signal
}
void FileUndoManagerPrivate::broadcastLock()
{
// assert(!m_lock);
if (!m_syncronized) {
slotLock();
return;
}
emit lock(); // DBUS signal
}
void FileUndoManagerPrivate::broadcastUnlock()
{
// assert(m_lock);
if (!m_syncronized) {
slotUnlock();
return;
}
emit unlock(); // DBUS signal
}
bool FileUndoManagerPrivate::initializeFromKDesky()
{
// ### workaround for dcop problem and upcoming 2.1 release:
// in case of huge io operations the amount of data sent over
// dcop (containing undo information broadcasted for global undo
// to all konqueror instances) can easily exceed the 64kb limit
// of dcop. In order not to run into trouble we disable global
// undo for now! (Simon)
// ### FIXME: post 2.1
// TODO KDE4: port to DBUS and test
return false;
#if 0
DCOPClient *client = kapp->dcopClient();
if (client->appId() == "kdesktop") // we are master :)
return true;
if (!client->isApplicationRegistered("kdesktop"))
return false;
d->m_commands = DCOPRef("kdesktop", "FileUndoManager").call("get");
return true;
#endif
}
void FileUndoManager::setUiInterface(UiInterface* ui)
{
delete d->m_uiInterface;
d->m_uiInterface = ui;
}
FileUndoManager::UiInterface* FileUndoManager::uiInterface() const
{
return d->m_uiInterface;
}
////
class FileUndoManager::UiInterface::UiInterfacePrivate
{
public:
UiInterfacePrivate()
: m_parentWidget(0), m_showProgressInfo(true)
{}
QWidget* m_parentWidget;
bool m_showProgressInfo;
};
FileUndoManager::UiInterface::UiInterface()
: d(new UiInterfacePrivate)
{
}
FileUndoManager::UiInterface::~UiInterface()
{
delete d;
}
void FileUndoManager::UiInterface::jobError(KIO::Job* job)
{
job->ui()->showErrorMessage();
}
bool FileUndoManager::UiInterface::copiedFileWasModified(const KUrl& src, const KUrl& dest, const KDateTime& srcTime, const KDateTime& destTime)
{
Q_UNUSED(srcTime); // not sure it should appear in the msgbox
// Possible improvement: only show the time if date is today
const QString timeStr = KGlobal::locale()->formatDateTime(destTime, KLocale::ShortDate);
return KMessageBox::warningContinueCancel(
d->m_parentWidget,
i18n("The file %1 was copied from %2, but since then it has apparently been modified at %3.\n"
"Undoing the copy will delete the file, and all modifications will be lost.\n"
"Are you sure you want to delete %4?", dest.pathOrUrl(), src.pathOrUrl(), timeStr, dest.pathOrUrl()),
i18n("Undo File Copy Confirmation"),
KStandardGuiItem::cont(),
KStandardGuiItem::cancel(),
QString(),
KMessageBox::Notify | KMessageBox::Dangerous) == KMessageBox::Continue;
}
bool FileUndoManager::UiInterface::confirmDeletion(const KUrl::List& files)
{
KIO::JobUiDelegate uiDelegate;
uiDelegate.setWindow(d->m_parentWidget);
// Because undo can happen with an accidental Ctrl-Z, we want to always confirm.
return uiDelegate.askDeleteConfirmation(files, KIO::JobUiDelegate::Delete, KIO::JobUiDelegate::ForceConfirmation);
}
QWidget* FileUndoManager::UiInterface::parentWidget() const
{
return d->m_parentWidget;
}
void FileUndoManager::UiInterface::setParentWidget(QWidget* parentWidget)
{
d->m_parentWidget = parentWidget;
}
void FileUndoManager::UiInterface::setShowProgressInfo(bool b)
{
d->m_showProgressInfo = b;
}
bool FileUndoManager::UiInterface::showProgressInfo() const
{
return d->m_showProgressInfo;
}
void FileUndoManager::UiInterface::virtual_hook(int, void*)
{
}
#include "moc_fileundomanager_p.cpp"
#include "moc_fileundomanager.cpp"
diff --git a/kio/kio/forwardingslavebase.cpp b/kio/kio/forwardingslavebase.cpp
index c0d0b3b6a4..9ba006ded8 100644
--- a/kio/kio/forwardingslavebase.cpp
+++ b/kio/kio/forwardingslavebase.cpp
@@ -1,565 +1,565 @@
/* This file is part of the KDE project
Copyright (c) 2004 Kevin Ottens <ervin@ipsquad.net>
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 "forwardingslavebase.h"
#include "deletejob.h"
#include "job.h"
#include <kdebug.h>
#include <kmimetype.h>
#include <QApplication>
#include <QtCore/QEventLoop>
namespace KIO
{
class ForwardingSlaveBasePrivate
{
public:
ForwardingSlaveBasePrivate(QObject * eventLoopParent) :
eventLoop(eventLoopParent)
{}
ForwardingSlaveBase *q;
KUrl m_processedURL;
KUrl m_requestedURL;
QEventLoop eventLoop;
bool internalRewriteUrl(const KUrl &url, KUrl &newURL);
void connectJob(Job *job);
void connectSimpleJob(SimpleJob *job);
void connectListJob(ListJob *job);
void connectTransferJob(TransferJob *job);
void _k_slotResult(KJob *job);
void _k_slotWarning(KJob *job, const QString &msg);
void _k_slotInfoMessage(KJob *job, const QString &msg);
void _k_slotTotalSize(KJob *job, qulonglong size);
void _k_slotProcessedSize(KJob *job, qulonglong size);
void _k_slotSpeed(KJob *job, unsigned long bytesPerSecond);
// KIO::SimpleJob subclasses
void _k_slotRedirection(KIO::Job *job, const KUrl &url);
// KIO::ListJob
void _k_slotEntries(KIO::Job *job, const KIO::UDSEntryList &entries);
// KIO::TransferJob
void _k_slotData(KIO::Job *job, const QByteArray &data);
void _k_slotDataReq(KIO::Job *job, QByteArray &data);
void _k_slotMimetype (KIO::Job *job, const QString &type);
void _k_slotCanResume (KIO::Job *job, KIO::filesize_t offset);
};
ForwardingSlaveBase::ForwardingSlaveBase(const QByteArray &protocol,
const QByteArray &poolSocket,
const QByteArray &appSocket)
: QObject(), SlaveBase(protocol, poolSocket, appSocket),
d( new ForwardingSlaveBasePrivate(this) )
{
d->q = this;
}
ForwardingSlaveBase::~ForwardingSlaveBase()
{
delete d;
}
bool ForwardingSlaveBasePrivate::internalRewriteUrl(const KUrl &url, KUrl &newURL)
{
bool result = true;
- if ( url.protocol() == q->mProtocol )
+ if ( url.scheme() == q->mProtocol )
{
result = q->rewriteUrl(url, newURL);
}
else
{
newURL = url;
}
m_processedURL = newURL;
m_requestedURL = url;
return result;
}
void ForwardingSlaveBase::prepareUDSEntry(KIO::UDSEntry &entry,
bool listing) const
{
//kDebug() << "listing==" << listing;
const QString name = entry.stringValue( KIO::UDSEntry::UDS_NAME );
QString mimetype = entry.stringValue( KIO::UDSEntry::UDS_MIME_TYPE );
KUrl url;
const QString urlStr = entry.stringValue( KIO::UDSEntry::UDS_URL );
const bool url_found = !urlStr.isEmpty();
if ( url_found )
{
url = urlStr;
KUrl new_url = d->m_requestedURL;
if (listing)
new_url.addPath(url.fileName());
// ## Didn't find a way to use an iterator instead of re-doing a key lookup
entry.insert( KIO::UDSEntry::UDS_URL, new_url.url() );
kDebug() << "URL =" << url;
kDebug() << "New URL =" << new_url;
}
if (mimetype.isEmpty())
{
KUrl new_url = d->m_processedURL;
if (url_found && listing)
{
new_url.addPath( url.fileName() );
}
else if (listing)
{
new_url.addPath( name );
}
mimetype = KMimeType::findByUrl(new_url)->name();
entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, mimetype );
kDebug() << "New Mimetype = " << mimetype;
}
if ( d->m_processedURL.isLocalFile() )
{
KUrl new_url = d->m_processedURL;
if (listing)
{
new_url.addPath( name );
}
entry.insert( KIO::UDSEntry::UDS_LOCAL_PATH, new_url.toLocalFile() );
}
}
KUrl ForwardingSlaveBase::processedUrl() const
{
return d->m_processedURL;
}
KUrl ForwardingSlaveBase::requestedUrl() const
{
return d->m_requestedURL;
}
void ForwardingSlaveBase::get(const KUrl &url)
{
kDebug() << url;
KUrl new_url;
if ( d->internalRewriteUrl(url, new_url) )
{
KIO::TransferJob *job = KIO::get(new_url, NoReload, HideProgressInfo);
d->connectTransferJob(job);
d->eventLoop.exec();
}
else
{
error(KIO::ERR_DOES_NOT_EXIST, url.prettyUrl());
}
}
void ForwardingSlaveBase::put(const KUrl &url, int permissions,
JobFlags flags)
{
kDebug() << url;
KUrl new_url;
if ( d->internalRewriteUrl(url, new_url) )
{
KIO::TransferJob *job = KIO::put(new_url, permissions,
flags | HideProgressInfo);
d->connectTransferJob(job);
d->eventLoop.exec();
}
else
{
error( KIO::ERR_MALFORMED_URL, url.prettyUrl() );
}
}
void ForwardingSlaveBase::stat(const KUrl &url)
{
kDebug() << url;
KUrl new_url;
if ( d->internalRewriteUrl(url, new_url) )
{
KIO::SimpleJob *job = KIO::stat(new_url, KIO::HideProgressInfo);
d->connectSimpleJob(job);
d->eventLoop.exec();
}
else
{
error(KIO::ERR_DOES_NOT_EXIST, url.prettyUrl());
}
}
void ForwardingSlaveBase::mimetype(const KUrl &url)
{
kDebug() << url;
KUrl new_url;
if ( d->internalRewriteUrl(url, new_url) )
{
KIO::TransferJob *job = KIO::mimetype(new_url, KIO::HideProgressInfo);
d->connectTransferJob(job);
d->eventLoop.exec();
}
else
{
error(KIO::ERR_DOES_NOT_EXIST, url.prettyUrl());
}
}
void ForwardingSlaveBase::listDir(const KUrl &url)
{
kDebug() << url;
KUrl new_url;
if ( d->internalRewriteUrl(url, new_url) )
{
KIO::ListJob *job = KIO::listDir(new_url, KIO::HideProgressInfo);
d->connectListJob(job);
d->eventLoop.exec();
}
else
{
error(KIO::ERR_DOES_NOT_EXIST, url.prettyUrl());
}
}
void ForwardingSlaveBase::mkdir(const KUrl &url, int permissions)
{
kDebug() << url;
KUrl new_url;
if ( d->internalRewriteUrl(url, new_url) )
{
KIO::SimpleJob *job = KIO::mkdir(new_url, permissions);
d->connectSimpleJob(job);
d->eventLoop.exec();
}
else
{
error( KIO::ERR_MALFORMED_URL, url.prettyUrl() );
}
}
void ForwardingSlaveBase::rename(const KUrl &src, const KUrl &dest,
JobFlags flags)
{
kDebug() << src << "," << dest;
KUrl new_src, new_dest;
if( !d->internalRewriteUrl(src, new_src) )
{
error(KIO::ERR_DOES_NOT_EXIST, src.prettyUrl());
}
else if ( d->internalRewriteUrl(dest, new_dest) )
{
KIO::Job *job = KIO::rename(new_src, new_dest, flags);
d->connectJob(job);
d->eventLoop.exec();
}
else
{
error( KIO::ERR_MALFORMED_URL, dest.prettyUrl() );
}
}
void ForwardingSlaveBase::symlink(const QString &target, const KUrl &dest,
JobFlags flags)
{
kDebug() << target << ", " << dest;
KUrl new_dest;
if ( d->internalRewriteUrl(dest, new_dest) )
{
KIO::SimpleJob *job = KIO::symlink(target, new_dest, flags & HideProgressInfo);
d->connectSimpleJob(job);
d->eventLoop.exec();
}
else
{
error( KIO::ERR_MALFORMED_URL, dest.prettyUrl() );
}
}
void ForwardingSlaveBase::chmod(const KUrl &url, int permissions)
{
kDebug() << url;
KUrl new_url;
if ( d->internalRewriteUrl(url, new_url) )
{
KIO::SimpleJob *job = KIO::chmod(new_url, permissions);
d->connectSimpleJob(job);
d->eventLoop.exec();
}
else
{
error(KIO::ERR_DOES_NOT_EXIST, url.prettyUrl());
}
}
void ForwardingSlaveBase::setModificationTime(const KUrl& url, const QDateTime& mtime)
{
kDebug() << url;
KUrl new_url;
if ( d->internalRewriteUrl(url, new_url) )
{
KIO::SimpleJob *job = KIO::setModificationTime(new_url, mtime);
d->connectSimpleJob(job);
d->eventLoop.exec();
}
else
{
error(KIO::ERR_DOES_NOT_EXIST, url.prettyUrl());
}
}
void ForwardingSlaveBase::copy(const KUrl &src, const KUrl &dest,
int permissions, JobFlags flags)
{
kDebug() << src << "," << dest;
KUrl new_src, new_dest;
if ( !d->internalRewriteUrl(src, new_src) )
{
error(KIO::ERR_DOES_NOT_EXIST, src.prettyUrl());
}
else if( d->internalRewriteUrl(dest, new_dest) )
{
// Are you sure you want to display here a ProgressInfo ???
KIO::Job *job = KIO::file_copy(new_src, new_dest, permissions,
(flags & (~Overwrite) & (~HideProgressInfo)) );
d->connectJob(job);
d->eventLoop.exec();
}
else
{
error( KIO::ERR_MALFORMED_URL, dest.prettyUrl() );
}
}
void ForwardingSlaveBase::del(const KUrl &url, bool isfile)
{
kDebug() << url;
KUrl new_url;
if ( d->internalRewriteUrl(url, new_url) )
{
if (isfile)
{
KIO::DeleteJob *job = KIO::del(new_url, HideProgressInfo);
d->connectJob(job);
}
else
{
KIO::SimpleJob *job = KIO::rmdir(new_url);
d->connectSimpleJob(job);
}
d->eventLoop.exec();
}
else
{
error(KIO::ERR_DOES_NOT_EXIST, url.prettyUrl());
}
}
//////////////////////////////////////////////////////////////////////////////
void ForwardingSlaveBasePrivate::connectJob(KIO::Job *job)
{
// We will forward the warning message, no need to let the job
// display it itself
job->setUiDelegate( 0 );
// Forward metadata (e.g. modification time for put())
job->setMetaData( q->allMetaData() );
#if 0 // debug code
kDebug() << "transferring metadata:";
const MetaData md = allMetaData();
for ( MetaData::const_iterator it = md.begin(); it != md.end(); ++it )
kDebug() << it.key() << " = " << it.data();
#endif
q->connect( job, SIGNAL( result(KJob *) ),
SLOT( _k_slotResult(KJob *) ) );
q->connect( job, SIGNAL( warning(KJob *, const QString &, const QString &) ),
SLOT( _k_slotWarning(KJob *, const QString &) ) );
q->connect( job, SIGNAL( infoMessage(KJob *, const QString &, const QString &) ),
SLOT( _k_slotInfoMessage(KJob *, const QString &) ) );
q->connect( job, SIGNAL( totalSize(KJob *, qulonglong) ),
SLOT( _k_slotTotalSize(KJob *, qulonglong) ) );
q->connect( job, SIGNAL( processedSize(KJob *, qulonglong) ),
SLOT( _k_slotProcessedSize(KJob *, qulonglong) ) );
q->connect( job, SIGNAL( speed(KJob *, unsigned long) ),
SLOT( _k_slotSpeed(KJob *, unsigned long) ) );
}
void ForwardingSlaveBasePrivate::connectSimpleJob(KIO::SimpleJob *job)
{
connectJob(job);
q->connect( job, SIGNAL( redirection(KIO::Job *, const KUrl &) ),
SLOT( _k_slotRedirection(KIO::Job *, const KUrl &) ) );
}
void ForwardingSlaveBasePrivate::connectListJob(KIO::ListJob *job)
{
connectSimpleJob(job);
q->connect( job, SIGNAL( entries(KIO::Job *, const KIO::UDSEntryList &) ),
SLOT( _k_slotEntries(KIO::Job *, const KIO::UDSEntryList &) ) );
}
void ForwardingSlaveBasePrivate::connectTransferJob(KIO::TransferJob *job)
{
connectSimpleJob(job);
q->connect( job, SIGNAL( data(KIO::Job *, const QByteArray &) ),
SLOT( _k_slotData(KIO::Job *, const QByteArray &) ) );
q->connect( job, SIGNAL( dataReq(KIO::Job *, QByteArray &) ),
SLOT( _k_slotDataReq(KIO::Job *, QByteArray &) ) );
q->connect( job, SIGNAL( mimetype(KIO::Job *, const QString &) ),
SLOT( _k_slotMimetype(KIO::Job *, const QString &) ) );
q->connect( job, SIGNAL( canResume(KIO::Job *, KIO::filesize_t) ),
SLOT( _k_slotCanResume(KIO::Job *, KIO::filesize_t) ) );
}
//////////////////////////////////////////////////////////////////////////////
void ForwardingSlaveBasePrivate::_k_slotResult(KJob *job)
{
if ( job->error() != 0)
{
q->error( job->error(), job->errorText() );
}
else
{
KIO::StatJob *stat_job = qobject_cast<KIO::StatJob *>(job);
if ( stat_job!=0L )
{
KIO::UDSEntry entry = stat_job->statResult();
q->prepareUDSEntry(entry);
q->statEntry( entry );
}
q->finished();
}
eventLoop.exit();
}
void ForwardingSlaveBasePrivate::_k_slotWarning(KJob* /*job*/, const QString &msg)
{
q->warning(msg);
}
void ForwardingSlaveBasePrivate::_k_slotInfoMessage(KJob* /*job*/, const QString &msg)
{
q->infoMessage(msg);
}
void ForwardingSlaveBasePrivate::_k_slotTotalSize(KJob* /*job*/, qulonglong size)
{
q->totalSize(size);
}
void ForwardingSlaveBasePrivate::_k_slotProcessedSize(KJob* /*job*/, qulonglong size)
{
q->processedSize(size);
}
void ForwardingSlaveBasePrivate::_k_slotSpeed(KJob* /*job*/, unsigned long bytesPerSecond)
{
q->speed(bytesPerSecond);
}
void ForwardingSlaveBasePrivate::_k_slotRedirection(KIO::Job *job, const KUrl &url)
{
q->redirection(url);
// We've been redirected stop everything.
job->kill( KJob::Quietly );
q->finished();
eventLoop.exit();
}
void ForwardingSlaveBasePrivate::_k_slotEntries(KIO::Job* /*job*/,
const KIO::UDSEntryList &entries)
{
KIO::UDSEntryList final_entries = entries;
KIO::UDSEntryList::iterator it = final_entries.begin();
const KIO::UDSEntryList::iterator end = final_entries.end();
for(; it!=end; ++it)
{
q->prepareUDSEntry(*it, true);
}
q->listEntries( final_entries );
}
void ForwardingSlaveBasePrivate::_k_slotData(KIO::Job* /*job*/, const QByteArray &_data)
{
q->data(_data);
}
void ForwardingSlaveBasePrivate::_k_slotDataReq(KIO::Job* /*job*/, QByteArray &data)
{
q->dataReq();
q->readData(data);
}
void ForwardingSlaveBasePrivate::_k_slotMimetype (KIO::Job* /*job*/, const QString &type)
{
q->mimeType(type);
}
void ForwardingSlaveBasePrivate::_k_slotCanResume (KIO::Job* /*job*/, KIO::filesize_t offset)
{
q->canResume(offset);
}
}
#include "moc_forwardingslavebase.cpp"
diff --git a/kio/kio/global.cpp b/kio/kio/global.cpp
index 008fae5282..9587aa6c82 100644
--- a/kio/kio/global.cpp
+++ b/kio/kio/global.cpp
@@ -1,1291 +1,1291 @@
/* 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 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 "global.h"
#include "job.h"
#include <config.h>
#include <kdebug.h>
#include <klocale.h>
#include <kglobal.h>
#include <kiconloader.h>
#include <kprotocolmanager.h>
#include <kmimetype.h>
#include <kdynamicjobtracker_p.h>
#include <QtCore/QByteArray>
#include <QtCore/QDate>
#include <QTextDocument>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/uio.h>
#include <assert.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
K_GLOBAL_STATIC(KDynamicJobTracker, globalJobTracker)
// If someone wants the SI-standard prefixes kB/MB/GB/TB, I would recommend
// a hidden kconfig option and getting the code from #57240 into the same
// method, so that all KDE apps use the same unit, instead of letting each app decide.
KIO_EXPORT QString KIO::convertSize( KIO::filesize_t size )
{
return KGlobal::locale()->formatByteSize(size);
}
KIO_EXPORT QString KIO::convertSizeFromKiB( KIO::filesize_t kibSize )
{
return KGlobal::locale()->formatByteSize(kibSize * 1024);
}
KIO_EXPORT QString KIO::number( KIO::filesize_t size )
{
char charbuf[256];
sprintf(charbuf, "%lld", size);
return QLatin1String(charbuf);
}
KIO_EXPORT unsigned int KIO::calculateRemainingSeconds( KIO::filesize_t totalSize,
KIO::filesize_t processedSize, KIO::filesize_t speed )
{
if ( (speed != 0) && (totalSize != 0) )
return ( totalSize - processedSize ) / speed;
else
return 0;
}
KIO_EXPORT QString KIO::convertSeconds( unsigned int seconds )
{
unsigned int days = seconds / 86400;
unsigned int hours = (seconds - (days * 86400)) / 3600;
unsigned int mins = (seconds - (days * 86400) - (hours * 3600)) / 60;
seconds = (seconds - (days * 86400) - (hours * 3600) - (mins * 60));
const QTime time(hours, mins, seconds);
const QString timeStr( KGlobal::locale()->formatTime(time, true /*with seconds*/, true /*duration*/) );
if ( days > 0 )
return i18np("1 day %2", "%1 days %2", days, timeStr);
else
return timeStr;
}
#ifndef KDE_NO_DEPRECATED
KIO_EXPORT QTime KIO::calculateRemaining( KIO::filesize_t totalSize, KIO::filesize_t processedSize, KIO::filesize_t speed )
{
QTime remainingTime;
if ( speed != 0 ) {
KIO::filesize_t secs;
if ( totalSize == 0 ) {
secs = 0;
} else {
secs = ( totalSize - processedSize ) / speed;
}
if (secs >= (24*60*60)) // Limit to 23:59:59
secs = (24*60*60)-1;
int hr = secs / ( 60 * 60 );
int mn = ( secs - hr * 60 * 60 ) / 60;
int sc = ( secs - hr * 60 * 60 - mn * 60 );
remainingTime.setHMS( hr, mn, sc );
}
return remainingTime;
}
#endif
KIO_EXPORT QString KIO::itemsSummaryString(uint items, uint files, uint dirs, KIO::filesize_t size, bool showSize)
{
if ( files == 0 && dirs == 0 && items == 0 ) {
return i18np( "%1 Item", "%1 Items", 0 );
}
QString summary;
const QString foldersText = i18np( "1 Folder", "%1 Folders", dirs );
const QString filesText = i18np( "1 File", "%1 Files", files );
if ( files > 0 && dirs > 0 ) {
summary = showSize ?
i18nc( "folders, files (size)", "%1, %2 (%3)", foldersText, filesText, KIO::convertSize( size ) ) :
i18nc( "folders, files", "%1, %2", foldersText, filesText );
} else if ( files > 0 ) {
summary = showSize ? i18nc( "files (size)", "%1 (%2)", filesText, KIO::convertSize( size ) ) : filesText;
} else if ( dirs > 0 ) {
summary = foldersText;
}
if ( items > dirs + files ) {
const QString itemsText = i18np( "%1 Item", "%1 Items", items );
summary = summary.isEmpty() ? itemsText : i18nc( "items: folders, files (size)", "%1: %2", itemsText, summary );
}
return summary;
}
KIO_EXPORT QString KIO::encodeFileName( const QString & _str )
{
QString str( _str );
str.replace('/', QChar(0x2044)); // "Fraction slash"
return str;
}
KIO_EXPORT QString KIO::decodeFileName( const QString & _str )
{
// Nothing to decode. "Fraction slash" is fine in filenames.
return _str;
}
KIO_EXPORT QString KIO::Job::errorString() const
{
return KIO::buildErrorString(error(), errorText());
}
KIO_EXPORT QString KIO::buildErrorString(int errorCode, const QString &errorText)
{
QString result;
switch( errorCode )
{
case KIO::ERR_CANNOT_OPEN_FOR_READING:
result = i18n( "Could not read %1." , errorText );
break;
case KIO::ERR_CANNOT_OPEN_FOR_WRITING:
result = i18n( "Could not write to %1." , errorText );
break;
case KIO::ERR_CANNOT_LAUNCH_PROCESS:
result = i18n( "Could not start process %1." , errorText );
break;
case KIO::ERR_INTERNAL:
result = i18n( "Internal Error\nPlease send a full bug report at http://bugs.kde.org\n%1" , errorText );
break;
case KIO::ERR_MALFORMED_URL:
result = i18n( "Malformed URL %1." , errorText );
break;
case KIO::ERR_UNSUPPORTED_PROTOCOL:
result = i18n( "The protocol %1 is not supported." , errorText );
break;
case KIO::ERR_NO_SOURCE_PROTOCOL:
result = i18n( "The protocol %1 is only a filter protocol.", errorText );
break;
case KIO::ERR_UNSUPPORTED_ACTION:
result = errorText;
// result = i18n( "Unsupported action %1" ).arg( errorText );
break;
case KIO::ERR_IS_DIRECTORY:
result = i18n( "%1 is a folder, but a file was expected." , errorText );
break;
case KIO::ERR_IS_FILE:
result = i18n( "%1 is a file, but a folder was expected." , errorText );
break;
case KIO::ERR_DOES_NOT_EXIST:
result = i18n( "The file or folder %1 does not exist." , errorText );
break;
case KIO::ERR_FILE_ALREADY_EXIST:
result = i18n( "A file named %1 already exists." , errorText );
break;
case KIO::ERR_DIR_ALREADY_EXIST:
result = i18n( "A folder named %1 already exists." , errorText );
break;
case KIO::ERR_UNKNOWN_HOST:
result = errorText.isEmpty() ? i18n( "No hostname specified." ) : i18n( "Unknown host %1" , errorText );
break;
case KIO::ERR_ACCESS_DENIED:
result = i18n( "Access denied to %1." , errorText );
break;
case KIO::ERR_WRITE_ACCESS_DENIED:
result = i18n( "Access denied.\nCould not write to %1." , errorText );
break;
case KIO::ERR_CANNOT_ENTER_DIRECTORY:
result = i18n( "Could not enter folder %1." , errorText );
break;
case KIO::ERR_PROTOCOL_IS_NOT_A_FILESYSTEM:
result = i18n( "The protocol %1 does not implement a folder service." , errorText );
break;
case KIO::ERR_CYCLIC_LINK:
result = i18n( "Found a cyclic link in %1." , errorText );
break;
case KIO::ERR_USER_CANCELED:
// Do nothing in this case. The user doesn't need to be told what he just did.
break;
case KIO::ERR_CYCLIC_COPY:
result = i18n( "Found a cyclic link while copying %1." , errorText );
break;
case KIO::ERR_COULD_NOT_CREATE_SOCKET:
result = i18n( "Could not create socket for accessing %1." , errorText );
break;
case KIO::ERR_COULD_NOT_CONNECT:
result = i18n( "Could not connect to host %1." , errorText.isEmpty() ? QLatin1String("localhost") : errorText );
break;
case KIO::ERR_CONNECTION_BROKEN:
result = i18n( "Connection to host %1 is broken." , errorText );
break;
case KIO::ERR_NOT_FILTER_PROTOCOL:
result = i18n( "The protocol %1 is not a filter protocol." , errorText );
break;
case KIO::ERR_COULD_NOT_MOUNT:
result = i18n( "Could not mount device.\nThe reported error was:\n%1" , errorText );
break;
case KIO::ERR_COULD_NOT_UNMOUNT:
result = i18n( "Could not unmount device.\nThe reported error was:\n%1" , errorText );
break;
case KIO::ERR_COULD_NOT_READ:
result = i18n( "Could not read file %1." , errorText );
break;
case KIO::ERR_COULD_NOT_WRITE:
result = i18n( "Could not write to file %1." , errorText );
break;
case KIO::ERR_COULD_NOT_BIND:
result = i18n( "Could not bind %1." , errorText );
break;
case KIO::ERR_COULD_NOT_LISTEN:
result = i18n( "Could not listen %1." , errorText );
break;
case KIO::ERR_COULD_NOT_ACCEPT:
result = i18n( "Could not accept %1." , errorText );
break;
case KIO::ERR_COULD_NOT_LOGIN:
result = errorText;
break;
case KIO::ERR_COULD_NOT_STAT:
result = i18n( "Could not access %1." , errorText );
break;
case KIO::ERR_COULD_NOT_CLOSEDIR:
result = i18n( "Could not terminate listing %1." , errorText );
break;
case KIO::ERR_COULD_NOT_MKDIR:
result = i18n( "Could not make folder %1." , errorText );
break;
case KIO::ERR_COULD_NOT_RMDIR:
result = i18n( "Could not remove folder %1." , errorText );
break;
case KIO::ERR_CANNOT_RESUME:
result = i18n( "Could not resume file %1." , errorText );
break;
case KIO::ERR_CANNOT_RENAME:
result = i18n( "Could not rename file %1." , errorText );
break;
case KIO::ERR_CANNOT_CHMOD:
result = i18n( "Could not change permissions for %1." , errorText );
break;
case KIO::ERR_CANNOT_CHOWN:
result = i18n( "Could not change ownership for %1." , errorText );
break;
case KIO::ERR_CANNOT_DELETE:
result = i18n( "Could not delete file %1." , errorText );
break;
case KIO::ERR_SLAVE_DIED:
result = i18n( "The process for the %1 protocol died unexpectedly." , errorText );
break;
case KIO::ERR_OUT_OF_MEMORY:
result = i18n( "Error. Out of memory.\n%1" , errorText );
break;
case KIO::ERR_UNKNOWN_PROXY_HOST:
result = i18n( "Unknown proxy host\n%1" , errorText );
break;
case KIO::ERR_COULD_NOT_AUTHENTICATE:
result = i18n( "Authorization failed, %1 authentication not supported" , errorText );
break;
case KIO::ERR_ABORTED:
result = i18n( "User canceled action\n%1" , errorText );
break;
case KIO::ERR_INTERNAL_SERVER:
result = i18n( "Internal error in server\n%1" , errorText );
break;
case KIO::ERR_SERVER_TIMEOUT:
result = i18n( "Timeout on server\n%1" , errorText );
break;
case KIO::ERR_UNKNOWN:
result = i18n( "Unknown error\n%1" , errorText );
break;
case KIO::ERR_UNKNOWN_INTERRUPT:
result = i18n( "Unknown interrupt\n%1" , errorText );
break;
/*
case KIO::ERR_CHECKSUM_MISMATCH:
if (errorText)
result = i18n( "Warning: MD5 Checksum for %1 does not match checksum returned from server" ).arg(errorText);
else
result = i18n( "Warning: MD5 Checksum for %1 does not match checksum returned from server" ).arg("document");
break;
*/
case KIO::ERR_CANNOT_DELETE_ORIGINAL:
result = i18n( "Could not delete original file %1.\nPlease check permissions." , errorText );
break;
case KIO::ERR_CANNOT_DELETE_PARTIAL:
result = i18n( "Could not delete partial file %1.\nPlease check permissions." , errorText );
break;
case KIO::ERR_CANNOT_RENAME_ORIGINAL:
result = i18n( "Could not rename original file %1.\nPlease check permissions." , errorText );
break;
case KIO::ERR_CANNOT_RENAME_PARTIAL:
result = i18n( "Could not rename partial file %1.\nPlease check permissions." , errorText );
break;
case KIO::ERR_CANNOT_SYMLINK:
result = i18n( "Could not create symlink %1.\nPlease check permissions." , errorText );
break;
case KIO::ERR_NO_CONTENT:
result = errorText;
break;
case KIO::ERR_DISK_FULL:
result = i18n( "Could not write file %1.\nDisk full." , errorText );
break;
case KIO::ERR_IDENTICAL_FILES:
result = i18n( "The source and destination are the same file.\n%1" , errorText );
break;
case KIO::ERR_SLAVE_DEFINED:
result = errorText;
break;
case KIO::ERR_UPGRADE_REQUIRED:
result = i18n( "%1 is required by the server, but is not available." , errorText);
break;
case KIO::ERR_POST_DENIED:
result = i18n( "Access to restricted port in POST denied.");
break;
case KIO::ERR_POST_NO_SIZE:
result = i18n( "The required content size information was not provided for a POST operation.");
break;
default:
result = i18n( "Unknown error code %1\n%2\nPlease send a full bug report at http://bugs.kde.org." , errorCode , errorText );
break;
}
return result;
}
KIO_EXPORT QString KIO::unsupportedActionErrorString(const QString &protocol, int cmd) {
switch (cmd) {
case CMD_CONNECT:
return i18n("Opening connections is not supported with the protocol %1." , protocol);
case CMD_DISCONNECT:
return i18n("Closing connections is not supported with the protocol %1." , protocol);
case CMD_STAT:
return i18n("Accessing files is not supported with the protocol %1.", protocol);
case CMD_PUT:
return i18n("Writing to %1 is not supported.", protocol);
case CMD_SPECIAL:
return i18n("There are no special actions available for protocol %1.", protocol);
case CMD_LISTDIR:
return i18n("Listing folders is not supported for protocol %1.", protocol);
case CMD_GET:
return i18n("Retrieving data from %1 is not supported.", protocol);
case CMD_MIMETYPE:
return i18n("Retrieving mime type information from %1 is not supported.", protocol);
case CMD_RENAME:
return i18n("Renaming or moving files within %1 is not supported.", protocol);
case CMD_SYMLINK:
return i18n("Creating symlinks is not supported with protocol %1.", protocol);
case CMD_COPY:
return i18n("Copying files within %1 is not supported.", protocol);
case CMD_DEL:
return i18n("Deleting files from %1 is not supported.", protocol);
case CMD_MKDIR:
return i18n("Creating folders is not supported with protocol %1.", protocol);
case CMD_CHMOD:
return i18n("Changing the attributes of files is not supported with protocol %1.", protocol);
case CMD_CHOWN:
return i18n("Changing the ownership of files is not supported with protocol %1.", protocol);
case CMD_SUBURL:
return i18n("Using sub-URLs with %1 is not supported.", protocol);
case CMD_MULTI_GET:
return i18n("Multiple get is not supported with protocol %1.", protocol);
case CMD_OPEN:
return i18n("Opening files is not supported with protocol %1.", protocol);
default:
return i18n("Protocol %1 does not support action %2.", protocol, cmd);
}/*end switch*/
}
KIO_EXPORT QStringList KIO::Job::detailedErrorStrings( const KUrl *reqUrl /*= 0L*/,
int method /*= -1*/ ) const
{
QString errorName, techName, description, ret2;
QStringList causes, solutions, ret;
QByteArray raw = rawErrorDetail( error(), errorText(), reqUrl, method );
QDataStream stream(raw);
stream >> errorName >> techName >> description >> causes >> solutions;
QString url, protocol, datetime;
if ( reqUrl ) {
url = Qt::escape(reqUrl->prettyUrl());
- protocol = reqUrl->protocol();
+ protocol = reqUrl->scheme();
} else {
url = i18nc("@info url", "(unknown)" );
}
datetime = KGlobal::locale()->formatDateTime( QDateTime::currentDateTime(),
KLocale::LongDate );
ret << errorName;
ret << i18nc( "@info %1 error name, %2 description",
"<qt><p><b>%1</b></p><p>%2</p></qt>", errorName, description);
ret2 = QLatin1String( "<qt>" );
if ( !techName.isEmpty() )
ret2 += QLatin1String( "<p>" ) + i18n( "<b>Technical reason</b>: " ) +
techName + QLatin1String( "</p>" );
ret2 += QLatin1String( "<p>" ) + i18n( "<b>Details of the request</b>:" ) +
QLatin1String( "</p><ul>" ) + i18n( "<li>URL: %1</li>", url );
if ( !protocol.isEmpty() ) {
ret2 += i18n( "<li>Protocol: %1</li>" , protocol );
}
ret2 += i18n( "<li>Date and time: %1</li>", datetime ) +
i18n( "<li>Additional information: %1</li>" , errorText() ) +
QLatin1String( "</ul>" );
if ( !causes.isEmpty() ) {
ret2 += QLatin1String( "<p>" ) + i18n( "<b>Possible causes</b>:" ) +
QLatin1String( "</p><ul><li>" ) + causes.join( "</li><li>" ) +
QLatin1String( "</li></ul>" );
}
if ( !solutions.isEmpty() ) {
ret2 += QLatin1String( "<p>" ) + i18n( "<b>Possible solutions</b>:" ) +
QLatin1String( "</p><ul><li>" ) + solutions.join( "</li><li>" ) +
QLatin1String( "</li></ul>" );
}
ret2 += QLatin1String( "</qt>" );
ret << ret2;
return ret;
}
KIO_EXPORT QByteArray KIO::rawErrorDetail(int errorCode, const QString &errorText,
const KUrl *reqUrl /*= 0L*/, int /*method = -1*/ )
{
QString url, host, protocol, datetime, domain, path, filename;
bool isSlaveNetwork = false;
if ( reqUrl ) {
url = reqUrl->prettyUrl();
host = reqUrl->host();
- protocol = reqUrl->protocol();
+ protocol = reqUrl->scheme();
if ( host.startsWith( QLatin1String( "www." ) ) )
domain = host.mid(4);
else
domain = host;
filename = reqUrl->fileName();
path = reqUrl->path();
// detect if protocol is a network protocol...
isSlaveNetwork = KProtocolInfo::protocolClass(protocol) == ":internet";
} else {
// assume that the errorText has the location we are interested in
url = host = domain = path = filename = errorText;
protocol = i18nc("@info protocol", "(unknown)" );
}
datetime = KGlobal::locale()->formatDateTime( QDateTime::currentDateTime(),
KLocale::LongDate );
QString errorName, techName, description;
QStringList causes, solutions;
// c == cause, s == solution
QString sSysadmin = i18n( "Contact your appropriate computer support system, "
"whether the system administrator, or technical support group for further "
"assistance." );
QString sServeradmin = i18n( "Contact the administrator of the server "
"for further assistance." );
// FIXME active link to permissions dialog
QString sAccess = i18n( "Check your access permissions on this resource." );
QString cAccess = i18n( "Your access permissions may be inadequate to "
"perform the requested operation on this resource." );
QString cLocked = i18n( "The file may be in use (and thus locked) by "
"another user or application." );
QString sQuerylock = i18n( "Check to make sure that no other "
"application or user is using the file or has locked the file." );
QString cHardware = i18n( "Although unlikely, a hardware error may have "
"occurred." );
QString cBug = i18n( "You may have encountered a bug in the program." );
QString cBuglikely = i18n( "This is most likely to be caused by a bug in the "
"program. Please consider submitting a full bug report as detailed below." );
QString sUpdate = i18n( "Update your software to the latest version. "
"Your distribution should provide tools to update your software." );
QString sBugreport = i18n( "When all else fails, please consider helping the "
"KDE team or the third party maintainer of this software by submitting a "
"high quality bug report. If the software is provided by a third party, "
"please contact them directly. Otherwise, first look to see if "
"the same bug has been submitted by someone else by searching at the "
"<a href=\"http://bugs.kde.org/\">KDE bug reporting website</a>. If not, take "
"note of the details given above, and include them in your bug report, along "
"with as many other details as you think might help." );
QString cNetwork = i18n( "There may have been a problem with your network "
"connection." );
// FIXME netconf kcontrol link
QString cNetconf = i18n( "There may have been a problem with your network "
"configuration. If you have been accessing the Internet with no problems "
"recently, this is unlikely." );
QString cNetpath = i18n( "There may have been a problem at some point along "
"the network path between the server and this computer." );
QString sTryagain = i18n( "Try again, either now or at a later time." );
QString cProtocol = i18n( "A protocol error or incompatibility may have occurred." );
QString sExists = i18n( "Ensure that the resource exists, and try again." );
QString cExists = i18n( "The specified resource may not exist." );
QString cTypo = i18n( "You may have incorrectly typed the location." );
QString sTypo = i18n( "Double-check that you have entered the correct location "
"and try again." );
QString sNetwork = i18n( "Check your network connection status." );
switch( errorCode ) {
case KIO::ERR_CANNOT_OPEN_FOR_READING:
errorName = i18n( "Cannot Open Resource For Reading" );
description = i18n( "This means that the contents of the requested file "
"or folder <strong>%1</strong> could not be retrieved, as read "
"access could not be obtained.", path );
causes << i18n( "You may not have permissions to read the file or open "
"the folder.") << cLocked << cHardware;
solutions << sAccess << sQuerylock << sSysadmin;
break;
case KIO::ERR_CANNOT_OPEN_FOR_WRITING:
errorName = i18n( "Cannot Open Resource For Writing" );
description = i18n( "This means that the file, <strong>%1</strong>, could "
"not be written to as requested, because access with permission to "
"write could not be obtained." , filename );
causes << cAccess << cLocked << cHardware;
solutions << sAccess << sQuerylock << sSysadmin;
break;
case KIO::ERR_CANNOT_LAUNCH_PROCESS:
errorName = i18n( "Cannot Initiate the %1 Protocol" , protocol );
techName = i18n( "Unable to Launch Process" );
description = i18n( "The program on your computer which provides access "
"to the <strong>%1</strong> protocol could not be started. This is "
"usually due to technical reasons." , protocol );
causes << i18n( "The program which provides compatibility with this "
"protocol may not have been updated with your last update of KDE. "
"This can cause the program to be incompatible with the current version "
"and thus not start." ) << cBug;
solutions << sUpdate << sSysadmin;
break;
case KIO::ERR_INTERNAL:
errorName = i18n( "Internal Error" );
description = i18n( "The program on your computer which provides access "
"to the <strong>%1</strong> protocol has reported an internal error." ,
protocol );
causes << cBuglikely;
solutions << sUpdate << sBugreport;
break;
case KIO::ERR_MALFORMED_URL:
errorName = i18n( "Improperly Formatted URL" );
description = i18n( "The <strong>U</strong>niform <strong>R</strong>esource "
"<strong>L</strong>ocator (URL) that you entered was not properly "
"formatted. The format of a URL is generally as follows:"
"<blockquote><strong>protocol://user:password@www.example.org:port/folder/"
"filename.extension?query=value</strong></blockquote>" );
solutions << sTypo;
break;
case KIO::ERR_UNSUPPORTED_PROTOCOL:
errorName = i18n( "Unsupported Protocol %1" , protocol );
description = i18n( "The protocol <strong>%1</strong> is not supported "
"by the KDE programs currently installed on this computer." ,
protocol );
causes << i18n( "The requested protocol may not be supported." )
<< i18n( "The versions of the %1 protocol supported by this computer and "
"the server may be incompatible." , protocol );
solutions << i18n( "You may perform a search on the Internet for a KDE "
"program (called a kioslave or ioslave) which supports this protocol. "
"Places to search include <a href=\"http://kde-apps.org/\">"
"http://kde-apps.org/</a> and <a href=\"http://freshmeat.net/\">"
"http://freshmeat.net/</a>." )
<< sUpdate << sSysadmin;
break;
case KIO::ERR_NO_SOURCE_PROTOCOL:
errorName = i18n( "URL Does Not Refer to a Resource." );
techName = i18n( "Protocol is a Filter Protocol" );
description = i18n( "The <strong>U</strong>niform <strong>R</strong>esource "
"<strong>L</strong>ocator (URL) that you entered did not refer to a "
"specific resource." );
causes << i18n( "KDE is able to communicate through a protocol within a "
"protocol; the protocol specified is only for use in such situations, "
"however this is not one of these situations. This is a rare event, and "
"is likely to indicate a programming error." );
solutions << sTypo;
break;
case KIO::ERR_UNSUPPORTED_ACTION:
errorName = i18n( "Unsupported Action: %1" , errorText );
description = i18n( "The requested action is not supported by the KDE "
"program which is implementing the <strong>%1</strong> protocol." ,
protocol );
causes << i18n( "This error is very much dependent on the KDE program. The "
"additional information should give you more information than is available "
"to the KDE input/output architecture." );
solutions << i18n( "Attempt to find another way to accomplish the same "
"outcome." );
break;
case KIO::ERR_IS_DIRECTORY:
errorName = i18n( "File Expected" );
description = i18n( "The request expected a file, however the "
"folder <strong>%1</strong> was found instead." , path );
causes << i18n( "This may be an error on the server side." ) << cBug;
solutions << sUpdate << sSysadmin;
break;
case KIO::ERR_IS_FILE:
errorName = i18n( "Folder Expected" );
description = i18n( "The request expected a folder, however "
"the file <strong>%1</strong> was found instead." , filename );
causes << cBug;
solutions << sUpdate << sSysadmin;
break;
case KIO::ERR_DOES_NOT_EXIST:
errorName = i18n( "File or Folder Does Not Exist" );
description = i18n( "The specified file or folder <strong>%1</strong> "
"does not exist." , path );
causes << cExists;
solutions << sExists;
break;
case KIO::ERR_FILE_ALREADY_EXIST:
errorName = i18n( "File Already Exists" );
description = i18n( "The requested file could not be created because a "
"file with the same name already exists." );
solutions << i18n ( "Try moving the current file out of the way first, "
"and then try again." )
<< i18n ( "Delete the current file and try again." )
<< i18n( "Choose an alternate filename for the new file." );
break;
case KIO::ERR_DIR_ALREADY_EXIST:
errorName = i18n( "Folder Already Exists" );
description = i18n( "The requested folder could not be created because "
"a folder with the same name already exists." );
solutions << i18n( "Try moving the current folder out of the way first, "
"and then try again." )
<< i18n( "Delete the current folder and try again." )
<< i18n( "Choose an alternate name for the new folder." );
break;
case KIO::ERR_UNKNOWN_HOST:
errorName = i18n( "Unknown Host" );
description = i18n( "An unknown host error indicates that the server with "
"the requested name, <strong>%1</strong>, could not be "
"located on the Internet." , host );
causes << i18n( "The name that you typed, %1, may not exist: it may be "
"incorrectly typed." , host )
<< cNetwork << cNetconf;
solutions << sNetwork << sSysadmin;
break;
case KIO::ERR_ACCESS_DENIED:
errorName = i18n( "Access Denied" );
description = i18n( "Access was denied to the specified resource, "
"<strong>%1</strong>." , url );
causes << i18n( "You may have supplied incorrect authentication details or "
"none at all." )
<< i18n( "Your account may not have permission to access the "
"specified resource." );
solutions << i18n( "Retry the request and ensure your authentication details "
"are entered correctly." ) << sSysadmin;
if ( !isSlaveNetwork ) solutions << sServeradmin;
break;
case KIO::ERR_WRITE_ACCESS_DENIED:
errorName = i18n( "Write Access Denied" );
description = i18n( "This means that an attempt to write to the file "
"<strong>%1</strong> was rejected." , filename );
causes << cAccess << cLocked << cHardware;
solutions << sAccess << sQuerylock << sSysadmin;
break;
case KIO::ERR_CANNOT_ENTER_DIRECTORY:
errorName = i18n( "Unable to Enter Folder" );
description = i18n( "This means that an attempt to enter (in other words, "
"to open) the requested folder <strong>%1</strong> was rejected." ,
path );
causes << cAccess << cLocked;
solutions << sAccess << sQuerylock << sSysadmin;
break;
case KIO::ERR_PROTOCOL_IS_NOT_A_FILESYSTEM:
errorName = i18n( "Folder Listing Unavailable" );
techName = i18n( "Protocol %1 is not a Filesystem" , protocol );
description = i18n( "This means that a request was made which requires "
"determining the contents of the folder, and the KDE program supporting "
"this protocol is unable to do so." );
causes << cBug;
solutions << sUpdate << sBugreport;
break;
case KIO::ERR_CYCLIC_LINK:
errorName = i18n( "Cyclic Link Detected" );
description = i18n( "UNIX environments are commonly able to link a file or "
"folder to a separate name and/or location. KDE detected a link or "
"series of links that results in an infinite loop - i.e. the file was "
"(perhaps in a roundabout way) linked to itself." );
solutions << i18n( "Delete one part of the loop in order that it does not "
"cause an infinite loop, and try again." ) << sSysadmin;
break;
case KIO::ERR_USER_CANCELED:
// Do nothing in this case. The user doesn't need to be told what he just did.
// rodda: However, if we have been called, an application is about to display
// this information anyway. If we don't return sensible information, the
// user sees a blank dialog (I have seen this myself)
errorName = i18n( "Request Aborted By User" );
description = i18n( "The request was not completed because it was "
"aborted." );
solutions << i18n( "Retry the request." );
break;
case KIO::ERR_CYCLIC_COPY:
errorName = i18n( "Cyclic Link Detected During Copy" );
description = i18n( "UNIX environments are commonly able to link a file or "
"folder to a separate name and/or location. During the requested copy "
"operation, KDE detected a link or series of links that results in an "
"infinite loop - i.e. the file was (perhaps in a roundabout way) linked "
"to itself." );
solutions << i18n( "Delete one part of the loop in order that it does not "
"cause an infinite loop, and try again." ) << sSysadmin;
break;
case KIO::ERR_COULD_NOT_CREATE_SOCKET:
errorName = i18n( "Could Not Create Network Connection" );
techName = i18n( "Could Not Create Socket" );
description = i18n( "This is a fairly technical error in which a required "
"device for network communications (a socket) could not be created." );
causes << i18n( "The network connection may be incorrectly configured, or "
"the network interface may not be enabled." );
solutions << sNetwork << sSysadmin;
break;
case KIO::ERR_COULD_NOT_CONNECT:
errorName = i18n( "Connection to Server Refused" );
description = i18n( "The server <strong>%1</strong> refused to allow this "
"computer to make a connection." , host );
causes << i18n( "The server, while currently connected to the Internet, "
"may not be configured to allow requests." )
<< i18n( "The server, while currently connected to the Internet, "
"may not be running the requested service (%1)." , protocol )
<< i18n( "A network firewall (a device which restricts Internet "
"requests), either protecting your network or the network of the server, "
"may have intervened, preventing this request." );
solutions << sTryagain << sServeradmin << sSysadmin;
break;
case KIO::ERR_CONNECTION_BROKEN:
errorName = i18n( "Connection to Server Closed Unexpectedly" );
description = i18n( "Although a connection was established to "
"<strong>%1</strong>, the connection was closed at an unexpected point "
"in the communication." , host );
causes << cNetwork << cNetpath << i18n( "A protocol error may have occurred, "
"causing the server to close the connection as a response to the error." );
solutions << sTryagain << sServeradmin << sSysadmin;
break;
case KIO::ERR_NOT_FILTER_PROTOCOL:
errorName = i18n( "URL Resource Invalid" );
techName = i18n( "Protocol %1 is not a Filter Protocol" , protocol );
description = i18n( "The <strong>U</strong>niform <strong>R</strong>esource "
"<strong>L</strong>ocator (URL) that you entered did not refer to "
"a valid mechanism of accessing the specific resource, "
"<strong>%1%2</strong>." ,
!host.isNull() ? host + '/' : QString() , path );
causes << i18n( "KDE is able to communicate through a protocol within a "
"protocol. This request specified a protocol be used as such, however "
"this protocol is not capable of such an action. This is a rare event, "
"and is likely to indicate a programming error." );
solutions << sTypo << sSysadmin;
break;
case KIO::ERR_COULD_NOT_MOUNT:
errorName = i18n( "Unable to Initialize Input/Output Device" );
techName = i18n( "Could Not Mount Device" );
description = i18n( "The requested device could not be initialized "
"(\"mounted\"). The reported error was: <strong>%1</strong>" ,
errorText );
causes << i18n( "The device may not be ready, for example there may be "
"no media in a removable media device (i.e. no CD-ROM in a CD drive), "
"or in the case of a peripheral/portable device, the device may not "
"be correctly connected." )
<< i18n( "You may not have permissions to initialize (\"mount\") the "
"device. On UNIX systems, often system administrator privileges are "
"required to initialize a device." )
<< cHardware;
solutions << i18n( "Check that the device is ready; removable drives "
"must contain media, and portable devices must be connected and powered "
"on.; and try again." ) << sAccess << sSysadmin;
break;
case KIO::ERR_COULD_NOT_UNMOUNT:
errorName = i18n( "Unable to Uninitialize Input/Output Device" );
techName = i18n( "Could Not Unmount Device" );
description = i18n( "The requested device could not be uninitialized "
"(\"unmounted\"). The reported error was: <strong>%1</strong>" ,
errorText );
causes << i18n( "The device may be busy, that is, still in use by "
"another application or user. Even such things as having an open "
"browser window on a location on this device may cause the device to "
"remain in use." )
<< i18n( "You may not have permissions to uninitialize (\"unmount\") "
"the device. On UNIX systems, system administrator privileges are "
"often required to uninitialize a device." )
<< cHardware;
solutions << i18n( "Check that no applications are accessing the device, "
"and try again." ) << sAccess << sSysadmin;
break;
case KIO::ERR_COULD_NOT_READ:
errorName = i18n( "Cannot Read From Resource" );
description = i18n( "This means that although the resource, "
"<strong>%1</strong>, was able to be opened, an error occurred while "
"reading the contents of the resource." , url );
causes << i18n( "You may not have permissions to read from the resource." );
if ( !isSlaveNetwork ) causes << cNetwork;
causes << cHardware;
solutions << sAccess;
if ( !isSlaveNetwork ) solutions << sNetwork;
solutions << sSysadmin;
break;
case KIO::ERR_COULD_NOT_WRITE:
errorName = i18n( "Cannot Write to Resource" );
description = i18n( "This means that although the resource, <strong>%1</strong>"
", was able to be opened, an error occurred while writing to the resource." ,
url );
causes << i18n( "You may not have permissions to write to the resource." );
if ( !isSlaveNetwork ) causes << cNetwork;
causes << cHardware;
solutions << sAccess;
if ( !isSlaveNetwork ) solutions << sNetwork;
solutions << sSysadmin;
break;
case KIO::ERR_COULD_NOT_BIND:
errorName = i18n( "Could Not Listen for Network Connections" );
techName = i18n( "Could Not Bind" );
description = i18n( "This is a fairly technical error in which a required "
"device for network communications (a socket) could not be established "
"to listen for incoming network connections." );
causes << i18n( "The network connection may be incorrectly configured, or "
"the network interface may not be enabled." );
solutions << sNetwork << sSysadmin;
break;
case KIO::ERR_COULD_NOT_LISTEN:
errorName = i18n( "Could Not Listen for Network Connections" );
techName = i18n( "Could Not Listen" );
description = i18n( "This is a fairly technical error in which a required "
"device for network communications (a socket) could not be established "
"to listen for incoming network connections." );
causes << i18n( "The network connection may be incorrectly configured, or "
"the network interface may not be enabled." );
solutions << sNetwork << sSysadmin;
break;
case KIO::ERR_COULD_NOT_ACCEPT:
errorName = i18n( "Could Not Accept Network Connection" );
description = i18n( "This is a fairly technical error in which an error "
"occurred while attempting to accept an incoming network connection." );
causes << i18n( "The network connection may be incorrectly configured, or "
"the network interface may not be enabled." )
<< i18n( "You may not have permissions to accept the connection." );
solutions << sNetwork << sSysadmin;
break;
case KIO::ERR_COULD_NOT_LOGIN:
errorName = i18n( "Could Not Login: %1" , errorText );
description = i18n( "An attempt to login to perform the requested "
"operation was unsuccessful." );
causes << i18n( "You may have supplied incorrect authentication details or "
"none at all." )
<< i18n( "Your account may not have permission to access the "
"specified resource." ) << cProtocol;
solutions << i18n( "Retry the request and ensure your authentication details "
"are entered correctly." ) << sServeradmin << sSysadmin;
break;
case KIO::ERR_COULD_NOT_STAT:
errorName = i18n( "Could Not Determine Resource Status" );
techName = i18n( "Could Not Stat Resource" );
description = i18n( "An attempt to determine information about the status "
"of the resource <strong>%1</strong>, such as the resource name, type, "
"size, etc., was unsuccessful." , url );
causes << i18n( "The specified resource may not have existed or may "
"not be accessible." ) << cProtocol << cHardware;
solutions << i18n( "Retry the request and ensure your authentication details "
"are entered correctly." ) << sSysadmin;
break;
case KIO::ERR_COULD_NOT_CLOSEDIR:
//result = i18n( "Could not terminate listing %1" ).arg( errorText );
errorName = i18n( "Could Not Cancel Listing" );
techName = i18n( "FIXME: Document this" );
break;
case KIO::ERR_COULD_NOT_MKDIR:
errorName = i18n( "Could Not Create Folder" );
description = i18n( "An attempt to create the requested folder failed." );
causes << cAccess << i18n( "The location where the folder was to be created "
"may not exist." );
if ( !isSlaveNetwork ) causes << cProtocol;
solutions << i18n( "Retry the request." ) << sAccess;
break;
case KIO::ERR_COULD_NOT_RMDIR:
errorName = i18n( "Could Not Remove Folder" );
description = i18n( "An attempt to remove the specified folder, "
"<strong>%1</strong>, failed." , path );
causes << i18n( "The specified folder may not exist." )
<< i18n( "The specified folder may not be empty." )
<< cAccess;
if ( !isSlaveNetwork ) causes << cProtocol;
solutions << i18n( "Ensure that the folder exists and is empty, and try "
"again." ) << sAccess;
break;
case KIO::ERR_CANNOT_RESUME:
errorName = i18n( "Could Not Resume File Transfer" );
description = i18n( "The specified request asked that the transfer of "
"file <strong>%1</strong> be resumed at a certain point of the "
"transfer. This was not possible." , filename );
causes << i18n( "The protocol, or the server, may not support file "
"resuming." );
solutions << i18n( "Retry the request without attempting to resume "
"transfer." );
break;
case KIO::ERR_CANNOT_RENAME:
errorName = i18n( "Could Not Rename Resource" );
description = i18n( "An attempt to rename the specified resource "
"<strong>%1</strong> failed." , url );
causes << cAccess << cExists;
if ( !isSlaveNetwork ) causes << cProtocol;
solutions << sAccess << sExists;
break;
case KIO::ERR_CANNOT_CHMOD:
errorName = i18n( "Could Not Alter Permissions of Resource" );
description = i18n( "An attempt to alter the permissions on the specified "
"resource <strong>%1</strong> failed." , url );
causes << cAccess << cExists;
solutions << sAccess << sExists;
break;
case KIO::ERR_CANNOT_CHOWN:
errorName = i18n( "Could Not Change Ownership of Resource" );
description = i18n( "An attempt to change the ownership of the specified "
"resource <strong>%1</strong> failed." , url );
causes << cAccess << cExists;
solutions << sAccess << sExists;
break;
case KIO::ERR_CANNOT_DELETE:
errorName = i18n( "Could Not Delete Resource" );
description = i18n( "An attempt to delete the specified resource "
"<strong>%1</strong> failed." , url );
causes << cAccess << cExists;
solutions << sAccess << sExists;
break;
case KIO::ERR_SLAVE_DIED:
errorName = i18n( "Unexpected Program Termination" );
description = i18n( "The program on your computer which provides access "
"to the <strong>%1</strong> protocol has unexpectedly terminated." ,
url );
causes << cBuglikely;
solutions << sUpdate << sBugreport;
break;
case KIO::ERR_OUT_OF_MEMORY:
errorName = i18n( "Out of Memory" );
description = i18n( "The program on your computer which provides access "
"to the <strong>%1</strong> protocol could not obtain the memory "
"required to continue." , protocol );
causes << cBuglikely;
solutions << sUpdate << sBugreport;
break;
case KIO::ERR_UNKNOWN_PROXY_HOST:
errorName = i18n( "Unknown Proxy Host" );
description = i18n( "While retrieving information about the specified "
"proxy host, <strong>%1</strong>, an Unknown Host error was encountered. "
"An unknown host error indicates that the requested name could not be "
"located on the Internet." , errorText );
causes << i18n( "There may have been a problem with your network "
"configuration, specifically your proxy's hostname. If you have been "
"accessing the Internet with no problems recently, this is unlikely." )
<< cNetwork;
solutions << i18n( "Double-check your proxy settings and try again." )
<< sSysadmin;
break;
case KIO::ERR_COULD_NOT_AUTHENTICATE:
errorName = i18n( "Authentication Failed: Method %1 Not Supported" ,
errorText );
description = i18n( "Although you may have supplied the correct "
"authentication details, the authentication failed because the "
"method that the server is using is not supported by the KDE "
"program implementing the protocol %1." , protocol );
solutions << i18n( "Please file a bug at <a href=\"http://bugs.kde.org/\">"
"http://bugs.kde.org/</a> to inform the KDE team of the unsupported "
"authentication method." ) << sSysadmin;
break;
case KIO::ERR_ABORTED:
errorName = i18n( "Request Aborted" );
description = i18n( "The request was not completed because it was "
"aborted." );
solutions << i18n( "Retry the request." );
break;
case KIO::ERR_INTERNAL_SERVER:
errorName = i18n( "Internal Error in Server" );
description = i18n( "The program on the server which provides access "
"to the <strong>%1</strong> protocol has reported an internal error: "
"%2." , protocol, errorText );
causes << i18n( "This is most likely to be caused by a bug in the "
"server program. Please consider submitting a full bug report as "
"detailed below." );
solutions << i18n( "Contact the administrator of the server "
"to advise them of the problem." )
<< i18n( "If you know who the authors of the server software are, "
"submit the bug report directly to them." );
break;
case KIO::ERR_SERVER_TIMEOUT:
errorName = i18n( "Timeout Error" );
description = i18n( "Although contact was made with the server, a "
"response was not received within the amount of time allocated for "
"the request as follows:<ul>"
"<li>Timeout for establishing a connection: %1 seconds</li>"
"<li>Timeout for receiving a response: %2 seconds</li>"
"<li>Timeout for accessing proxy servers: %3 seconds</li></ul>"
"Please note that you can alter these timeout settings in the KDE "
"System Settings, by selecting Network Settings -> Connection Preferences." ,
KProtocolManager::connectTimeout() ,
KProtocolManager::responseTimeout() ,
KProtocolManager::proxyConnectTimeout() );
causes << cNetpath << i18n( "The server was too busy responding to other "
"requests to respond." );
solutions << sTryagain << sServeradmin;
break;
case KIO::ERR_UNKNOWN:
errorName = i18n( "Unknown Error" );
description = i18n( "The program on your computer which provides access "
"to the <strong>%1</strong> protocol has reported an unknown error: "
"%2." , protocol , errorText );
causes << cBug;
solutions << sUpdate << sBugreport;
break;
case KIO::ERR_UNKNOWN_INTERRUPT:
errorName = i18n( "Unknown Interruption" );
description = i18n( "The program on your computer which provides access "
"to the <strong>%1</strong> protocol has reported an interruption of "
"an unknown type: %2." , protocol , errorText );
causes << cBug;
solutions << sUpdate << sBugreport;
break;
case KIO::ERR_CANNOT_DELETE_ORIGINAL:
errorName = i18n( "Could Not Delete Original File" );
description = i18n( "The requested operation required the deleting of "
"the original file, most likely at the end of a file move operation. "
"The original file <strong>%1</strong> could not be deleted." ,
errorText );
causes << cAccess;
solutions << sAccess;
break;
case KIO::ERR_CANNOT_DELETE_PARTIAL:
errorName = i18n( "Could Not Delete Temporary File" );
description = i18n( "The requested operation required the creation of "
"a temporary file in which to save the new file while being "
"downloaded. This temporary file <strong>%1</strong> could not be "
"deleted." , errorText );
causes << cAccess;
solutions << sAccess;
break;
case KIO::ERR_CANNOT_RENAME_ORIGINAL:
errorName = i18n( "Could Not Rename Original File" );
description = i18n( "The requested operation required the renaming of "
"the original file <strong>%1</strong>, however it could not be "
"renamed." , errorText );
causes << cAccess;
solutions << sAccess;
break;
case KIO::ERR_CANNOT_RENAME_PARTIAL:
errorName = i18n( "Could Not Rename Temporary File" );
description = i18n( "The requested operation required the creation of "
"a temporary file <strong>%1</strong>, however it could not be "
"created." , errorText );
causes << cAccess;
solutions << sAccess;
break;
case KIO::ERR_CANNOT_SYMLINK:
errorName = i18n( "Could Not Create Link" );
techName = i18n( "Could Not Create Symbolic Link" );
description = i18n( "The requested symbolic link %1 could not be created." ,
errorText );
causes << cAccess;
solutions << sAccess;
break;
case KIO::ERR_NO_CONTENT:
errorName = i18n( "No Content" );
description = errorText;
break;
case KIO::ERR_DISK_FULL:
errorName = i18n( "Disk Full" );
description = i18n( "The requested file <strong>%1</strong> could not be "
"written to as there is inadequate disk space." , errorText );
solutions << i18n( "Free up enough disk space by 1) deleting unwanted and "
"temporary files; 2) archiving files to removable media storage such as "
"CD-Recordable discs; or 3) obtain more storage capacity." )
<< sSysadmin;
break;
case KIO::ERR_IDENTICAL_FILES:
errorName = i18n( "Source and Destination Files Identical" );
description = i18n( "The operation could not be completed because the "
"source and destination files are the same file." );
solutions << i18n( "Choose a different filename for the destination file." );
break;
// We assume that the slave has all the details
case KIO::ERR_SLAVE_DEFINED:
errorName.clear();
description = errorText;
break;
default:
// fall back to the plain error...
errorName = i18n( "Undocumented Error" );
description = buildErrorString( errorCode, errorText );
}
QByteArray ret;
QDataStream stream(&ret, QIODevice::WriteOnly);
stream << errorName << techName << description << causes << solutions;
return ret;
}
/***************************************************************
*
* Utility functions
*
***************************************************************/
KIO::CacheControl KIO::parseCacheControl(const QString &cacheControl)
{
QString tmp = cacheControl.toLower();
if (tmp == "cacheonly")
return KIO::CC_CacheOnly;
if (tmp == "cache")
return KIO::CC_Cache;
if (tmp == "verify")
return KIO::CC_Verify;
if (tmp == "refresh")
return KIO::CC_Refresh;
if (tmp == "reload")
return KIO::CC_Reload;
kDebug() << "unrecognized Cache control option:"<<cacheControl;
return KIO::CC_Verify;
}
QString KIO::getCacheControlString(KIO::CacheControl cacheControl)
{
if (cacheControl == KIO::CC_CacheOnly)
return "CacheOnly";
if (cacheControl == KIO::CC_Cache)
return "Cache";
if (cacheControl == KIO::CC_Verify)
return "Verify";
if (cacheControl == KIO::CC_Refresh)
return "Refresh";
if (cacheControl == KIO::CC_Reload)
return "Reload";
kDebug() << "unrecognized Cache control enum value:"<<cacheControl;
return QString();
}
QPixmap KIO::pixmapForUrl( const KUrl & _url, mode_t _mode, KIconLoader::Group _group,
int _force_size, int _state, QString * _path )
{
const QString iconName = KMimeType::iconNameForUrl( _url, _mode );
return KIconLoader::global()->loadMimeTypeIcon( iconName, _group, _force_size, _state, QStringList(), _path );
}
KJobTrackerInterface *KIO::getJobTracker()
{
return globalJobTracker;
}
/***************************************************************
*
* KIO::MetaData
*
***************************************************************/
KIO::MetaData::MetaData(const QMap<QString,QVariant>& map)
{
*this = map;
}
KIO::MetaData & KIO::MetaData::operator += ( const QMap<QString,QVariant> &metaData )
{
QMapIterator<QString,QVariant> it (metaData);
while(it.hasNext()) {
it.next();
insert(it.key(), it.value().toString());
}
return *this;
}
KIO::MetaData & KIO::MetaData::operator = ( const QMap<QString,QVariant> &metaData )
{
clear();
return (*this += metaData);
}
QVariant KIO::MetaData::toVariant() const
{
QMap<QString, QVariant> map;
QMapIterator <QString,QString> it (*this);
while (it.hasNext()) {
it.next();
map.insert(it.key(), it.value());
}
return QVariant(map);
}
diff --git a/kio/kio/job.cpp b/kio/kio/job.cpp
index 99eade829b..ca64202a85 100644
--- a/kio/kio/job.cpp
+++ b/kio/kio/job.cpp
@@ -1,3157 +1,3157 @@
/* This file is part of the KDE libraries
Copyright (C) 2000 Stephan Kulow <coolo@kde.org>
2000-2009 David Faure <faure@kde.org>
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.
*/
#include "job.h"
#include "job_p.h"
#include <config.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>
extern "C" {
#include <pwd.h>
#include <grp.h>
}
#include <QtCore/QTimer>
#include <QtCore/QFile>
#include <kauthorized.h>
#include <klocale.h>
#include <kconfig.h>
#include <kdebug.h>
#include <kde_file.h>
#include <errno.h>
#include "jobuidelegate.h"
#include "kmimetype.h"
#include "slave.h"
#include "scheduler.h"
#include "kdirwatch.h"
#include "kprotocolinfo.h"
#include "kprotocolmanager.h"
#include "filejob.h"
#include <kdirnotify.h>
using namespace KIO;
#define MAX_READ_BUF_SIZE (64 * 1024) // 64 KB at a time seems reasonable...
static inline Slave *jobSlave(SimpleJob *job)
{
return SimpleJobPrivate::get(job)->m_slave;
}
//this will update the report dialog with 5 Hz, I think this is fast enough, aleXXX
#define REPORT_TIMEOUT 200
Job::Job() : KCompositeJob(*new JobPrivate, 0)
{
setCapabilities( KJob::Killable | KJob::Suspendable );
}
Job::Job(JobPrivate &dd) : KCompositeJob(dd, 0)
{
setCapabilities( KJob::Killable | KJob::Suspendable );
}
Job::~Job()
{
}
JobUiDelegate *Job::ui() const
{
return static_cast<JobUiDelegate*>( uiDelegate() );
}
bool Job::addSubjob(KJob *jobBase)
{
//kDebug(7007) << "addSubjob(" << jobBase << ") this=" << this;
bool ok = KCompositeJob::addSubjob( jobBase );
KIO::Job *job = dynamic_cast<KIO::Job*>( jobBase );
if (ok && job) {
// Copy metadata into subjob (e.g. window-id, user-timestamp etc.)
Q_D(Job);
job->mergeMetaData(d->m_outgoingMetaData);
// Forward information from that subjob.
connect(job, SIGNAL(speed(KJob*,ulong)),
SLOT(slotSpeed(KJob*,ulong)));
if (ui() && job->ui()) {
job->ui()->setWindow( ui()->window() );
job->ui()->updateUserTimestamp( ui()->userTimestamp() );
}
}
return ok;
}
bool Job::removeSubjob( KJob *jobBase )
{
//kDebug(7007) << "removeSubjob(" << jobBase << ") this=" << this << "subjobs=" << subjobs().count();
return KCompositeJob::removeSubjob( jobBase );
}
void JobPrivate::emitMoving(KIO::Job * job, const KUrl &src, const KUrl &dest)
{
emit job->description(job, i18nc("@title job","Moving"),
qMakePair(i18nc("The source of a file operation", "Source"), src.pathOrUrl()),
qMakePair(i18nc("The destination of a file operation", "Destination"), dest.pathOrUrl()));
}
void JobPrivate::emitCopying(KIO::Job * job, const KUrl &src, const KUrl &dest)
{
emit job->description(job, i18nc("@title job","Copying"),
qMakePair(i18nc("The source of a file operation", "Source"), src.pathOrUrl()),
qMakePair(i18nc("The destination of a file operation", "Destination"), dest.pathOrUrl()));
}
void JobPrivate::emitCreatingDir(KIO::Job * job, const KUrl &dir)
{
emit job->description(job, i18nc("@title job","Creating directory"),
qMakePair(i18n("Directory"), dir.pathOrUrl()));
}
void JobPrivate::emitDeleting(KIO::Job *job, const KUrl &url)
{
emit job->description(job, i18nc("@title job","Deleting"),
qMakePair(i18n("File"), url.pathOrUrl()));
}
void JobPrivate::emitStating(KIO::Job *job, const KUrl &url)
{
emit job->description(job, i18nc("@title job","Examining"),
qMakePair(i18n("File"), url.pathOrUrl()));
}
void JobPrivate::emitTransferring(KIO::Job *job, const KUrl &url)
{
emit job->description(job, i18nc("@title job","Transferring"),
qMakePair(i18nc("The source of a file operation", "Source"), url.pathOrUrl()));
}
void JobPrivate::emitMounting(KIO::Job * job, const QString &dev, const QString &point)
{
emit job->description(job, i18nc("@title job","Mounting"),
qMakePair(i18n("Device"), dev),
qMakePair(i18n("Mountpoint"), point));
}
void JobPrivate::emitUnmounting(KIO::Job * job, const QString &point)
{
emit job->description(job, i18nc("@title job","Unmounting"),
qMakePair(i18n("Mountpoint"), point));
}
bool Job::doKill()
{
// kill all subjobs, without triggering their result slot
Q_FOREACH( KJob* it, subjobs()) {
it->kill( KJob::Quietly );
}
clearSubjobs();
return true;
}
bool Job::doSuspend()
{
Q_FOREACH(KJob* it, subjobs()) {
if (!it->suspend())
return false;
}
return true;
}
bool Job::doResume()
{
Q_FOREACH ( KJob* it, subjobs() )
{
if (!it->resume())
return false;
}
return true;
}
void JobPrivate::slotSpeed( KJob*, unsigned long speed )
{
//kDebug(7007) << speed;
q_func()->emitSpeed( speed );
}
//Job::errorString is implemented in global.cpp
#ifndef KDE_NO_DEPRECATED
void Job::showErrorDialog( QWidget *parent )
{
if ( ui() )
{
ui()->setWindow( parent );
ui()->showErrorMessage();
}
else
{
kError() << errorString();
}
}
#endif
bool Job::isInteractive() const
{
return uiDelegate() != 0;
}
void Job::setParentJob(Job* job)
{
Q_D(Job);
Q_ASSERT(d->m_parentJob == 0L);
Q_ASSERT(job);
d->m_parentJob = job;
}
Job* Job::parentJob() const
{
return d_func()->m_parentJob;
}
MetaData Job::metaData() const
{
return d_func()->m_incomingMetaData;
}
QString Job::queryMetaData(const QString &key)
{
return d_func()->m_incomingMetaData.value(key, QString());
}
void Job::setMetaData( const KIO::MetaData &_metaData)
{
Q_D(Job);
d->m_outgoingMetaData = _metaData;
}
void Job::addMetaData( const QString &key, const QString &value)
{
d_func()->m_outgoingMetaData.insert(key, value);
}
void Job::addMetaData( const QMap<QString,QString> &values)
{
Q_D(Job);
QMap<QString,QString>::const_iterator it = values.begin();
for(;it != values.end(); ++it)
d->m_outgoingMetaData.insert(it.key(), it.value());
}
void Job::mergeMetaData( const QMap<QString,QString> &values)
{
Q_D(Job);
QMap<QString,QString>::const_iterator it = values.begin();
for(;it != values.end(); ++it)
// there's probably a faster way
if ( !d->m_outgoingMetaData.contains( it.key() ) )
d->m_outgoingMetaData.insert( it.key(), it.value() );
}
MetaData Job::outgoingMetaData() const
{
return d_func()->m_outgoingMetaData;
}
SimpleJob::SimpleJob(SimpleJobPrivate &dd)
: Job(dd)
{
d_func()->simpleJobInit();
}
void SimpleJobPrivate::simpleJobInit()
{
Q_Q(SimpleJob);
if (!m_url.isValid())
{
q->setError( ERR_MALFORMED_URL );
q->setErrorText( m_url.url() );
QTimer::singleShot(0, q, SLOT(slotFinished()) );
return;
}
Scheduler::doJob(q);
}
bool SimpleJob::doKill()
{
Q_D(SimpleJob);
if ((d->m_extraFlags & JobPrivate::EF_KillCalled) == 0) {
d->m_extraFlags |= JobPrivate::EF_KillCalled;
Scheduler::cancelJob(this); // deletes the slave if not 0
} else {
kWarning(7007) << this << "This is overkill.";
}
return Job::doKill();
}
bool SimpleJob::doSuspend()
{
Q_D(SimpleJob);
if ( d->m_slave )
d->m_slave->suspend();
return Job::doSuspend();
}
bool SimpleJob::doResume()
{
Q_D(SimpleJob);
if ( d->m_slave )
d->m_slave->resume();
return Job::doResume();
}
const KUrl& SimpleJob::url() const
{
return d_func()->m_url;
}
void SimpleJob::putOnHold()
{
Q_D(SimpleJob);
Q_ASSERT( d->m_slave );
if ( d->m_slave )
{
Scheduler::putSlaveOnHold(this, d->m_url);
}
// we should now be disassociated from the slave
Q_ASSERT(!d->m_slave);
kill( Quietly );
}
void SimpleJob::removeOnHold()
{
Scheduler::removeSlaveOnHold();
}
bool SimpleJob::isRedirectionHandlingEnabled() const
{
return d_func()->m_redirectionHandlingEnabled;
}
void SimpleJob::setRedirectionHandlingEnabled(bool handle)
{
Q_D(SimpleJob);
d->m_redirectionHandlingEnabled = handle;
}
SimpleJob::~SimpleJob()
{
Q_D(SimpleJob);
// last chance to remove this job from the scheduler!
if (d->m_schedSerial) {
kDebug(7007) << "Killing job" << this << "in destructor!" << kBacktrace();
Scheduler::cancelJob(this);
}
}
void SimpleJobPrivate::start(Slave *slave)
{
Q_Q(SimpleJob);
m_slave = slave;
// Slave::setJob can send us SSL metadata if there is a persistent connection
q->connect( slave, SIGNAL(metaData(KIO::MetaData)),
SLOT(slotMetaData(KIO::MetaData)) );
slave->setJob(q);
q->connect( slave, SIGNAL(error(int,QString)),
SLOT(slotError(int,QString)) );
q->connect( slave, SIGNAL(warning(QString)),
SLOT(slotWarning(QString)) );
q->connect( slave, SIGNAL(infoMessage(QString)),
SLOT(_k_slotSlaveInfoMessage(QString)) );
q->connect( slave, SIGNAL(connected()),
SLOT(slotConnected()));
q->connect( slave, SIGNAL(finished()),
SLOT(slotFinished()) );
if ((m_extraFlags & EF_TransferJobDataSent) == 0) // this is a "get" job
{
q->connect( slave, SIGNAL(totalSize(KIO::filesize_t)),
SLOT(slotTotalSize(KIO::filesize_t)) );
q->connect( slave, SIGNAL(processedSize(KIO::filesize_t)),
SLOT(slotProcessedSize(KIO::filesize_t)) );
q->connect( slave, SIGNAL(speed(ulong)),
SLOT(slotSpeed(ulong)) );
}
if (ui() && ui()->window())
{
m_outgoingMetaData.insert("window-id", QString::number((qptrdiff)ui()->window()->winId()));
}
if (ui() && ui()->userTimestamp())
{
m_outgoingMetaData.insert("user-timestamp", QString::number(ui()->userTimestamp()));
}
if (ui() == 0) // not interactive
{
m_outgoingMetaData.insert("no-auth-prompt", "true");
}
if (!m_outgoingMetaData.isEmpty())
{
KIO_ARGS << m_outgoingMetaData;
slave->send( CMD_META_DATA, packedArgs );
}
if (!m_subUrl.isEmpty())
{
KIO_ARGS << m_subUrl;
slave->send( CMD_SUBURL, packedArgs );
}
slave->send( m_command, m_packedArgs );
}
void SimpleJobPrivate::slaveDone()
{
Q_Q(SimpleJob);
if (m_slave) {
if (m_command == CMD_OPEN) {
m_slave->send(CMD_CLOSE);
}
q->disconnect(m_slave); // Remove all signals between slave and job
}
// only finish a job once; Scheduler::jobFinished() resets schedSerial to zero.
if (m_schedSerial) {
Scheduler::jobFinished(q, m_slave);
}
}
void SimpleJob::slotFinished( )
{
Q_D(SimpleJob);
// Return slave to the scheduler
d->slaveDone();
if (!hasSubjobs())
{
if ( !error() && (d->m_command == CMD_MKDIR || d->m_command == CMD_RENAME ) )
{
if ( d->m_command == CMD_MKDIR )
{
KUrl urlDir( url() );
urlDir.setPath( urlDir.directory() );
org::kde::KDirNotify::emitFilesAdded( urlDir.url() );
}
else /*if ( m_command == CMD_RENAME )*/
{
KUrl src, dst;
QDataStream str( d->m_packedArgs );
str >> src >> dst;
if( src.directory() == dst.directory() ) // For the user, moving isn't renaming. Only renaming is.
org::kde::KDirNotify::emitFileRenamed( src.url(), dst.url() );
org::kde::KDirNotify::emitFileMoved( src.url(), dst.url() );
}
}
emitResult();
}
}
void SimpleJob::slotError( int err, const QString & errorText )
{
Q_D(SimpleJob);
setError( err );
setErrorText( errorText );
if ((error() == ERR_UNKNOWN_HOST) && d->m_url.host().isEmpty())
setErrorText( QString() );
// error terminates the job
slotFinished();
}
void SimpleJob::slotWarning( const QString & errorText )
{
emit warning( this, errorText );
}
void SimpleJobPrivate::_k_slotSlaveInfoMessage( const QString & msg )
{
emit q_func()->infoMessage( q_func(), msg );
}
void SimpleJobPrivate::slotConnected()
{
emit q_func()->connected( q_func() );
}
void SimpleJobPrivate::slotTotalSize( KIO::filesize_t size )
{
Q_Q(SimpleJob);
if (size != q->totalAmount(KJob::Bytes))
{
q->setTotalAmount(KJob::Bytes, size);
}
}
void SimpleJobPrivate::slotProcessedSize( KIO::filesize_t size )
{
Q_Q(SimpleJob);
//kDebug(7007) << KIO::number(size);
q->setProcessedAmount(KJob::Bytes, size);
}
void SimpleJobPrivate::slotSpeed( unsigned long speed )
{
//kDebug(7007) << speed;
q_func()->emitSpeed( speed );
}
void SimpleJobPrivate::restartAfterRedirection(KUrl *redirectionUrl)
{
Q_Q(SimpleJob);
// Return slave to the scheduler while we still have the old URL in place; the scheduler
// requires a job URL to stay invariant while the job is running.
slaveDone();
m_url = *redirectionUrl;
redirectionUrl->clear();
if ((m_extraFlags & EF_KillCalled) == 0) {
Scheduler::doJob(q);
}
}
void SimpleJob::slotMetaData( const KIO::MetaData &_metaData )
{
Q_D(SimpleJob);
QMapIterator<QString,QString> it (_metaData);
while (it.hasNext()) {
it.next();
if (it.key().startsWith(QLatin1String("{internal~"), Qt::CaseInsensitive))
d->m_internalMetaData.insert(it.key(), it.value());
else
d->m_incomingMetaData.insert(it.key(), it.value());
}
// Update the internal meta-data values as soon as possible. Waiting until
// the ioslave is finished has unintended consequences if the client starts
// a new connection without waiting for the ioslave to finish.
if (!d->m_internalMetaData.isEmpty()) {
Scheduler::updateInternalMetaData(this);
}
}
void SimpleJob::storeSSLSessionFromJob(const KUrl &redirectionURL)
{
Q_UNUSED(redirectionURL);
}
//////////
class KIO::MkdirJobPrivate: public SimpleJobPrivate
{
public:
MkdirJobPrivate(const KUrl& url, int command, const QByteArray &packedArgs)
: SimpleJobPrivate(url, command, packedArgs)
{ }
KUrl m_redirectionURL;
void slotRedirection(const KUrl &url);
/**
* @internal
* Called by the scheduler when a @p slave gets to
* work on this job.
* @param slave the slave that starts working on this job
*/
virtual void start( Slave *slave );
Q_DECLARE_PUBLIC(MkdirJob)
static inline MkdirJob *newJob(const KUrl& url, int command, const QByteArray &packedArgs)
{
MkdirJob *job = new MkdirJob(*new MkdirJobPrivate(url, command, packedArgs));
job->setUiDelegate(new JobUiDelegate);
return job;
}
};
MkdirJob::MkdirJob(MkdirJobPrivate &dd)
: SimpleJob(dd)
{
}
MkdirJob::~MkdirJob()
{
}
void MkdirJobPrivate::start(Slave *slave)
{
Q_Q(MkdirJob);
q->connect( slave, SIGNAL( redirection(const KUrl &) ),
SLOT( slotRedirection(const KUrl &) ) );
SimpleJobPrivate::start(slave);
}
// Slave got a redirection request
void MkdirJobPrivate::slotRedirection( const KUrl &url)
{
Q_Q(MkdirJob);
kDebug(7007) << url;
if (!KAuthorized::authorizeUrlAction("redirect", m_url, url))
{
kWarning(7007) << "Redirection from" << m_url << "to" << url << "REJECTED!";
q->setError( ERR_ACCESS_DENIED );
q->setErrorText( url.pathOrUrl() );
return;
}
m_redirectionURL = url; // We'll remember that when the job finishes
// Tell the user that we haven't finished yet
emit q->redirection(q, m_redirectionURL);
}
void MkdirJob::slotFinished()
{
Q_D(MkdirJob);
if ( !d->m_redirectionURL.isEmpty() && d->m_redirectionURL.isValid() )
{
//kDebug(7007) << "MkdirJob: Redirection to " << m_redirectionURL;
if (queryMetaData("permanent-redirect")=="true")
emit permanentRedirection(this, d->m_url, d->m_redirectionURL);
if ( d->m_redirectionHandlingEnabled )
{
KUrl dummyUrl;
int permissions;
QDataStream istream( d->m_packedArgs );
istream >> dummyUrl >> permissions;
d->m_packedArgs.truncate(0);
QDataStream stream( &d->m_packedArgs, QIODevice::WriteOnly );
stream << d->m_redirectionURL << permissions;
d->restartAfterRedirection(&d->m_redirectionURL);
return;
}
}
// Return slave to the scheduler
SimpleJob::slotFinished();
}
SimpleJob *KIO::mkdir( const KUrl& url, int permissions )
{
//kDebug(7007) << "mkdir " << url;
KIO_ARGS << url << permissions;
return MkdirJobPrivate::newJob(url, CMD_MKDIR, packedArgs);
}
SimpleJob *KIO::rmdir( const KUrl& url )
{
//kDebug(7007) << "rmdir " << url;
KIO_ARGS << url << qint8(false); // isFile is false
return SimpleJobPrivate::newJob(url, CMD_DEL, packedArgs);
}
SimpleJob *KIO::chmod( const KUrl& url, int permissions )
{
//kDebug(7007) << "chmod " << url;
KIO_ARGS << url << permissions;
return SimpleJobPrivate::newJob(url, CMD_CHMOD, packedArgs);
}
SimpleJob *KIO::chown( const KUrl& url, const QString& owner, const QString& group )
{
KIO_ARGS << url << owner << group;
return SimpleJobPrivate::newJob(url, CMD_CHOWN, packedArgs);
}
SimpleJob *KIO::setModificationTime( const KUrl& url, const QDateTime& mtime )
{
//kDebug(7007) << "setModificationTime " << url << " " << mtime;
KIO_ARGS << url << mtime;
return SimpleJobPrivate::newJobNoUi(url, CMD_SETMODIFICATIONTIME, packedArgs);
}
SimpleJob *KIO::rename( const KUrl& src, const KUrl & dest, JobFlags flags )
{
//kDebug(7007) << "rename " << src << " " << dest;
KIO_ARGS << src << dest << (qint8) (flags & Overwrite);
return SimpleJobPrivate::newJob(src, CMD_RENAME, packedArgs);
}
SimpleJob *KIO::symlink( const QString& target, const KUrl & dest, JobFlags flags )
{
//kDebug(7007) << "symlink target=" << target << " " << dest;
KIO_ARGS << target << dest << (qint8) (flags & Overwrite);
return SimpleJobPrivate::newJob(dest, CMD_SYMLINK, packedArgs, flags);
}
SimpleJob *KIO::special(const KUrl& url, const QByteArray & data, JobFlags flags)
{
//kDebug(7007) << "special " << url;
return SimpleJobPrivate::newJob(url, CMD_SPECIAL, data, flags);
}
SimpleJob *KIO::mount( bool ro, const QByteArray& fstype, const QString& dev, const QString& point, JobFlags flags )
{
KIO_ARGS << int(1) << qint8( ro ? 1 : 0 )
<< QString::fromLatin1(fstype) << dev << point;
SimpleJob *job = special( KUrl("file:/"), packedArgs, flags );
if (!(flags & HideProgressInfo)) {
KIO::JobPrivate::emitMounting(job, dev, point);
}
return job;
}
SimpleJob *KIO::unmount( const QString& point, JobFlags flags )
{
KIO_ARGS << int(2) << point;
SimpleJob *job = special( KUrl("file:/"), packedArgs, flags );
if (!(flags & HideProgressInfo)) {
KIO::JobPrivate::emitUnmounting(job, point);
}
return job;
}
//////////
class KIO::StatJobPrivate: public SimpleJobPrivate
{
public:
inline StatJobPrivate(const KUrl& url, int command, const QByteArray &packedArgs)
: SimpleJobPrivate(url, command, packedArgs), m_bSource(true), m_details(2)
{}
UDSEntry m_statResult;
KUrl m_redirectionURL;
bool m_bSource;
short int m_details;
void slotStatEntry( const KIO::UDSEntry & entry );
void slotRedirection( const KUrl &url);
/**
* @internal
* Called by the scheduler when a @p slave gets to
* work on this job.
* @param slave the slave that starts working on this job
*/
virtual void start( Slave *slave );
Q_DECLARE_PUBLIC(StatJob)
static inline StatJob *newJob(const KUrl& url, int command, const QByteArray &packedArgs,
JobFlags flags )
{
StatJob *job = new StatJob(*new StatJobPrivate(url, command, packedArgs));
job->setUiDelegate(new JobUiDelegate);
if (!(flags & HideProgressInfo)) {
KIO::getJobTracker()->registerJob(job);
emitStating(job, url);
}
return job;
}
};
StatJob::StatJob(StatJobPrivate &dd)
: SimpleJob(dd)
{
}
StatJob::~StatJob()
{
}
#ifndef KDE_NO_DEPRECATED
void StatJob::setSide( bool source )
{
d_func()->m_bSource = source;
}
#endif
void StatJob::setSide( StatSide side )
{
d_func()->m_bSource = side == SourceSide;
}
void StatJob::setDetails( short int details )
{
d_func()->m_details = details;
}
const UDSEntry & StatJob::statResult() const
{
return d_func()->m_statResult;
}
KUrl StatJob::mostLocalUrl() const
{
if (!url().isLocalFile()) {
const UDSEntry& udsEntry = d_func()->m_statResult;
const QString path = udsEntry.stringValue( KIO::UDSEntry::UDS_LOCAL_PATH );
if (!path.isEmpty())
return KUrl(path);
}
return url();
}
void StatJobPrivate::start(Slave *slave)
{
Q_Q(StatJob);
m_outgoingMetaData.insert( "statSide", m_bSource ? "source" : "dest" );
m_outgoingMetaData.insert( "details", QString::number(m_details) );
q->connect( slave, SIGNAL( statEntry( const KIO::UDSEntry& ) ),
SLOT( slotStatEntry( const KIO::UDSEntry & ) ) );
q->connect( slave, SIGNAL( redirection(const KUrl &) ),
SLOT( slotRedirection(const KUrl &) ) );
SimpleJobPrivate::start(slave);
}
void StatJobPrivate::slotStatEntry( const KIO::UDSEntry & entry )
{
//kDebug(7007);
m_statResult = entry;
}
// Slave got a redirection request
void StatJobPrivate::slotRedirection( const KUrl &url)
{
Q_Q(StatJob);
kDebug(7007) << m_url << "->" << url;
if (!KAuthorized::authorizeUrlAction("redirect", m_url, url))
{
kWarning(7007) << "Redirection from " << m_url << " to " << url << " REJECTED!";
q->setError( ERR_ACCESS_DENIED );
q->setErrorText( url.pathOrUrl() );
return;
}
m_redirectionURL = url; // We'll remember that when the job finishes
// Tell the user that we haven't finished yet
emit q->redirection(q, m_redirectionURL);
}
void StatJob::slotFinished()
{
Q_D(StatJob);
if ( !d->m_redirectionURL.isEmpty() && d->m_redirectionURL.isValid() )
{
//kDebug(7007) << "StatJob: Redirection to " << m_redirectionURL;
if (queryMetaData("permanent-redirect")=="true")
emit permanentRedirection(this, d->m_url, d->m_redirectionURL);
if ( d->m_redirectionHandlingEnabled )
{
d->m_packedArgs.truncate(0);
QDataStream stream( &d->m_packedArgs, QIODevice::WriteOnly );
stream << d->m_redirectionURL;
d->restartAfterRedirection(&d->m_redirectionURL);
return;
}
}
// Return slave to the scheduler
SimpleJob::slotFinished();
}
void StatJob::slotMetaData( const KIO::MetaData &_metaData)
{
Q_D(StatJob);
SimpleJob::slotMetaData(_metaData);
storeSSLSessionFromJob(d->m_redirectionURL);
}
StatJob *KIO::stat(const KUrl& url, JobFlags flags)
{
// Assume sideIsSource. Gets are more common than puts.
return stat( url, StatJob::SourceSide, 2, flags );
}
StatJob *KIO::mostLocalUrl(const KUrl& url, JobFlags flags)
{
StatJob* job = stat( url, StatJob::SourceSide, 2, flags );
if (url.isLocalFile()) {
QTimer::singleShot(0, job, SLOT(slotFinished()));
Scheduler::cancelJob(job); // deletes the slave if not 0
}
return job;
}
#ifndef KDE_NO_DEPRECATED
StatJob *KIO::stat(const KUrl& url, bool sideIsSource, short int details, JobFlags flags )
{
//kDebug(7007) << "stat" << url;
KIO_ARGS << url;
StatJob * job = StatJobPrivate::newJob(url, CMD_STAT, packedArgs, flags);
job->setSide( sideIsSource ? StatJob::SourceSide : StatJob::DestinationSide );
job->setDetails( details );
return job;
}
#endif
StatJob *KIO::stat(const KUrl& url, KIO::StatJob::StatSide side, short int details, JobFlags flags )
{
//kDebug(7007) << "stat" << url;
KIO_ARGS << url;
StatJob * job = StatJobPrivate::newJob(url, CMD_STAT, packedArgs, flags);
job->setSide( side );
job->setDetails( details );
return job;
}
SimpleJob *KIO::http_update_cache( const KUrl& url, bool no_cache, time_t expireDate)
{
- Q_ASSERT(url.protocol() == "http" || url.protocol() == "https");
+ Q_ASSERT(url.scheme() == "http" || url.scheme() == "https");
// Send http update_cache command (2)
KIO_ARGS << (int)2 << url << no_cache << qlonglong(expireDate);
SimpleJob * job = SimpleJobPrivate::newJob(url, CMD_SPECIAL, packedArgs);
Scheduler::setJobPriority(job, 1);
return job;
}
//////////
TransferJob::TransferJob(TransferJobPrivate &dd)
: SimpleJob(dd)
{
Q_D(TransferJob);
if (d->m_command == CMD_PUT) {
d->m_extraFlags |= JobPrivate::EF_TransferJobDataSent;
}
}
TransferJob::~TransferJob()
{
}
// Slave sends data
void TransferJob::slotData( const QByteArray &_data)
{
Q_D(TransferJob);
if (d->m_command == CMD_GET && !d->m_isMimetypeEmitted) {
kWarning(7007) << "mimeType() not emitted when sending first data!; job URL ="
<< d->m_url << "data size =" << _data.size();
}
// shut up the warning, HACK: downside is that it changes the meaning of the variable
d->m_isMimetypeEmitted = true;
if (d->m_redirectionURL.isEmpty() || !d->m_redirectionURL.isValid() || error()) {
emit data(this, _data);
}
}
void KIO::TransferJob::setTotalSize(KIO::filesize_t bytes)
{
setTotalAmount(KJob::Bytes, bytes);
}
// Slave got a redirection request
void TransferJob::slotRedirection( const KUrl &url)
{
Q_D(TransferJob);
kDebug(7007) << url;
if (!KAuthorized::authorizeUrlAction("redirect", d->m_url, url))
{
kWarning(7007) << "Redirection from " << d->m_url << " to " << url << " REJECTED!";
return;
}
// Some websites keep redirecting to themselves where each redirection
// acts as the stage in a state-machine. We define "endless redirections"
// as 5 redirections to the same URL.
if (d->m_redirectionList.count(url) > 5)
{
kDebug(7007) << "CYCLIC REDIRECTION!";
setError( ERR_CYCLIC_LINK );
setErrorText( d->m_url.pathOrUrl() );
}
else
{
d->m_redirectionURL = url; // We'll remember that when the job finishes
d->m_redirectionList.append(url);
d->m_outgoingMetaData["ssl_was_in_use"] = d->m_incomingMetaData["ssl_in_use"];
// Tell the user that we haven't finished yet
emit redirection(this, d->m_redirectionURL);
}
}
void TransferJob::slotFinished()
{
Q_D(TransferJob);
kDebug(7007) << d->m_url;
if (!d->m_redirectionURL.isEmpty() && d->m_redirectionURL.isValid()) {
//kDebug(7007) << "Redirection to" << m_redirectionURL;
if (queryMetaData("permanent-redirect")=="true")
emit permanentRedirection(this, d->m_url, d->m_redirectionURL);
if (d->m_redirectionHandlingEnabled) {
// Honour the redirection
// We take the approach of "redirecting this same job"
// Another solution would be to create a subjob, but the same problem
// happens (unpacking+repacking)
d->staticData.truncate(0);
d->m_incomingMetaData.clear();
if (queryMetaData("cache") != "reload")
addMetaData("cache","refresh");
d->m_internalSuspended = false;
// The very tricky part is the packed arguments business
QString dummyStr;
KUrl dummyUrl;
QDataStream istream( d->m_packedArgs );
switch( d->m_command ) {
case CMD_GET: {
d->m_packedArgs.truncate(0);
QDataStream stream( &d->m_packedArgs, QIODevice::WriteOnly );
stream << d->m_redirectionURL;
break;
}
case CMD_PUT: {
int permissions;
qint8 iOverwrite, iResume;
istream >> dummyUrl >> iOverwrite >> iResume >> permissions;
d->m_packedArgs.truncate(0);
QDataStream stream( &d->m_packedArgs, QIODevice::WriteOnly );
stream << d->m_redirectionURL << iOverwrite << iResume << permissions;
break;
}
case CMD_SPECIAL: {
int specialcmd;
istream >> specialcmd;
if (specialcmd == 1) // HTTP POST
{
d->m_outgoingMetaData.remove(QLatin1String("content-type"));
addMetaData("cache","reload");
d->m_packedArgs.truncate(0);
QDataStream stream( &d->m_packedArgs, QIODevice::WriteOnly );
stream << d->m_redirectionURL;
d->m_command = CMD_GET;
}
break;
}
}
d->restartAfterRedirection(&d->m_redirectionURL);
return;
}
}
SimpleJob::slotFinished();
}
void TransferJob::setAsyncDataEnabled(bool enabled)
{
Q_D(TransferJob);
if (enabled)
d->m_extraFlags |= JobPrivate::EF_TransferJobAsync;
else
d->m_extraFlags &= ~JobPrivate::EF_TransferJobAsync;
}
void TransferJob::sendAsyncData(const QByteArray &dataForSlave)
{
Q_D(TransferJob);
if (d->m_extraFlags & JobPrivate::EF_TransferJobNeedData)
{
d->m_slave->send( MSG_DATA, dataForSlave );
if (d->m_extraFlags & JobPrivate::EF_TransferJobDataSent) // put job -> emit progress
{
KIO::filesize_t size = processedAmount(KJob::Bytes)+dataForSlave.size();
setProcessedAmount(KJob::Bytes, size);
}
}
d->m_extraFlags &= ~JobPrivate::EF_TransferJobNeedData;
}
#ifndef KDE_NO_DEPRECATED
void TransferJob::setReportDataSent(bool enabled)
{
Q_D(TransferJob);
if (enabled)
d->m_extraFlags |= JobPrivate::EF_TransferJobDataSent;
else
d->m_extraFlags &= ~JobPrivate::EF_TransferJobDataSent;
}
#endif
#ifndef KDE_NO_DEPRECATED
bool TransferJob::reportDataSent() const
{
return (d_func()->m_extraFlags & JobPrivate::EF_TransferJobDataSent);
}
#endif
QString TransferJob::mimetype() const
{
return d_func()->m_mimetype;
}
// Slave requests data
void TransferJob::slotDataReq()
{
Q_D(TransferJob);
QByteArray dataForSlave;
d->m_extraFlags |= JobPrivate::EF_TransferJobNeedData;
if (!d->staticData.isEmpty())
{
dataForSlave = d->staticData;
d->staticData.clear();
}
else
{
emit dataReq( this, dataForSlave);
if (d->m_extraFlags & JobPrivate::EF_TransferJobAsync)
return;
}
static const int max_size = 14 * 1024 * 1024;
if (dataForSlave.size() > max_size)
{
kDebug(7007) << "send " << dataForSlave.size() / 1024 / 1024 << "MB of data in TransferJob::dataReq. This needs to be splitted, which requires a copy. Fix the application.\n";
d->staticData = QByteArray(dataForSlave.data() + max_size , dataForSlave.size() - max_size);
dataForSlave.truncate(max_size);
}
sendAsyncData(dataForSlave);
if (d->m_subJob)
{
// Bitburger protocol in action
d->internalSuspend(); // Wait for more data from subJob.
d->m_subJob->d_func()->internalResume(); // Ask for more!
}
}
void TransferJob::slotMimetype( const QString& type )
{
Q_D(TransferJob);
d->m_mimetype = type;
if (d->m_command == CMD_GET && d->m_isMimetypeEmitted) {
kWarning(7007) << "mimetype() emitted again, or after sending first data!; job URL ="
<< d->m_url;
}
d->m_isMimetypeEmitted = true;
emit mimetype( this, type );
}
void TransferJobPrivate::internalSuspend()
{
m_internalSuspended = true;
if (m_slave)
m_slave->suspend();
}
void TransferJobPrivate::internalResume()
{
m_internalSuspended = false;
if ( m_slave && !suspended )
m_slave->resume();
}
bool TransferJob::doResume()
{
Q_D(TransferJob);
if ( !SimpleJob::doResume() )
return false;
if ( d->m_internalSuspended )
d->internalSuspend();
return true;
}
bool TransferJob::isErrorPage() const
{
return d_func()->m_errorPage;
}
void TransferJobPrivate::start(Slave *slave)
{
Q_Q(TransferJob);
Q_ASSERT(slave);
JobPrivate::emitTransferring(q, m_url);
q->connect( slave, SIGNAL( data( const QByteArray & ) ),
SLOT( slotData( const QByteArray & ) ) );
if (m_outgoingDataSource)
q->connect( slave, SIGNAL( dataReq() ),
SLOT( slotDataReqFromDevice() ) );
else
q->connect( slave, SIGNAL( dataReq() ),
SLOT( slotDataReq() ) );
q->connect( slave, SIGNAL( redirection(const KUrl &) ),
SLOT( slotRedirection(const KUrl &) ) );
q->connect( slave, SIGNAL(mimeType( const QString& ) ),
SLOT( slotMimetype( const QString& ) ) );
q->connect( slave, SIGNAL(errorPage() ),
SLOT( slotErrorPage() ) );
q->connect( slave, SIGNAL( needSubUrlData() ),
SLOT( slotNeedSubUrlData() ) );
q->connect( slave, SIGNAL(canResume( KIO::filesize_t ) ),
SLOT( slotCanResume( KIO::filesize_t ) ) );
if (slave->suspended())
{
m_mimetype = "unknown";
// WABA: The slave was put on hold. Resume operation.
slave->resume();
}
SimpleJobPrivate::start(slave);
if (m_internalSuspended)
slave->suspend();
}
void TransferJobPrivate::slotNeedSubUrlData()
{
Q_Q(TransferJob);
// Job needs data from subURL.
m_subJob = KIO::get( m_subUrl, NoReload, HideProgressInfo);
internalSuspend(); // Put job on hold until we have some data.
q->connect(m_subJob, SIGNAL( data(KIO::Job*,const QByteArray &)),
SLOT( slotSubUrlData(KIO::Job*,const QByteArray &)));
q->addSubjob(m_subJob);
}
void TransferJobPrivate::slotSubUrlData(KIO::Job*, const QByteArray &data)
{
// The Alternating Bitburg protocol in action again.
staticData = data;
m_subJob->d_func()->internalSuspend(); // Put job on hold until we have delivered the data.
internalResume(); // Activate ourselves again.
}
void TransferJob::slotMetaData( const KIO::MetaData &_metaData)
{
Q_D(TransferJob);
SimpleJob::slotMetaData(_metaData);
storeSSLSessionFromJob(d->m_redirectionURL);
}
void TransferJobPrivate::slotErrorPage()
{
m_errorPage = true;
}
void TransferJobPrivate::slotCanResume( KIO::filesize_t offset )
{
Q_Q(TransferJob);
emit q->canResume(q, offset);
}
void TransferJobPrivate::slotDataReqFromDevice()
{
Q_Q(TransferJob);
QByteArray dataForSlave;
m_extraFlags |= JobPrivate::EF_TransferJobNeedData;
if (m_outgoingDataSource)
dataForSlave = m_outgoingDataSource.data()->read(MAX_READ_BUF_SIZE);
if (dataForSlave.isEmpty())
{
emit q->dataReq(q, dataForSlave);
if (m_extraFlags & JobPrivate::EF_TransferJobAsync)
return;
}
q->sendAsyncData(dataForSlave);
if (m_subJob)
{
// Bitburger protocol in action
internalSuspend(); // Wait for more data from subJob.
m_subJob->d_func()->internalResume(); // Ask for more!
}
}
void TransferJob::slotResult( KJob *job)
{
Q_D(TransferJob);
// This can only be our suburl.
Q_ASSERT(job == d->m_subJob);
SimpleJob::slotResult( job );
if (!error() && job == d->m_subJob)
{
d->m_subJob = 0; // No action required
d->internalResume(); // Make sure we get the remaining data.
}
}
void TransferJob::setModificationTime( const QDateTime& mtime )
{
addMetaData( "modified", mtime.toString( Qt::ISODate ) );
}
TransferJob *KIO::get( const KUrl& url, LoadType reload, JobFlags flags )
{
// Send decoded path and encoded query
KIO_ARGS << url;
TransferJob * job = TransferJobPrivate::newJob(url, CMD_GET, packedArgs,
QByteArray(), flags);
if (reload == Reload)
job->addMetaData("cache", "reload");
return job;
}
class KIO::StoredTransferJobPrivate: public TransferJobPrivate
{
public:
StoredTransferJobPrivate(const KUrl& url, int command,
const QByteArray &packedArgs,
const QByteArray &_staticData)
: TransferJobPrivate(url, command, packedArgs, _staticData),
m_uploadOffset( 0 )
{}
StoredTransferJobPrivate(const KUrl& url, int command,
const QByteArray &packedArgs,
QIODevice* ioDevice)
: TransferJobPrivate(url, command, packedArgs, ioDevice),
m_uploadOffset( 0 )
{}
QByteArray m_data;
int m_uploadOffset;
void slotStoredData( KIO::Job *job, const QByteArray &data );
void slotStoredDataReq( KIO::Job *job, QByteArray &data );
Q_DECLARE_PUBLIC(StoredTransferJob)
static inline StoredTransferJob *newJob(const KUrl &url, int command,
const QByteArray &packedArgs,
const QByteArray &staticData, JobFlags flags)
{
StoredTransferJob *job = new StoredTransferJob(
*new StoredTransferJobPrivate(url, command, packedArgs, staticData));
job->setUiDelegate(new JobUiDelegate);
if (!(flags & HideProgressInfo))
KIO::getJobTracker()->registerJob(job);
return job;
}
static inline StoredTransferJob *newJob(const KUrl &url, int command,
const QByteArray &packedArgs,
QIODevice* ioDevice, JobFlags flags)
{
StoredTransferJob *job = new StoredTransferJob(
*new StoredTransferJobPrivate(url, command, packedArgs, ioDevice));
job->setUiDelegate(new JobUiDelegate);
if (!(flags & HideProgressInfo))
KIO::getJobTracker()->registerJob(job);
return job;
}
};
namespace KIO {
class PostErrorJob : public StoredTransferJob
{
public:
PostErrorJob(int _error, const QString& url, const QByteArray &packedArgs, const QByteArray &postData)
: StoredTransferJob(*new StoredTransferJobPrivate(KUrl(), CMD_SPECIAL, packedArgs, postData))
{
setError( _error );
setErrorText( url );
}
PostErrorJob(int _error, const QString& url, const QByteArray &packedArgs, QIODevice* ioDevice)
: StoredTransferJob(*new StoredTransferJobPrivate(KUrl(), CMD_SPECIAL, packedArgs, ioDevice))
{
setError( _error );
setErrorText( url );
}
};
}
static int isUrlPortBad(const KUrl& url)
{
int _error = 0;
// filter out some malicious ports
static const int bad_ports[] = {
1, // tcpmux
7, // echo
9, // discard
11, // systat
13, // daytime
15, // netstat
17, // qotd
19, // chargen
20, // ftp-data
21, // ftp-cntl
22, // ssh
23, // telnet
25, // smtp
37, // time
42, // name
43, // nicname
53, // domain
77, // priv-rjs
79, // finger
87, // ttylink
95, // supdup
101, // hostriame
102, // iso-tsap
103, // gppitnp
104, // acr-nema
109, // pop2
110, // pop3
111, // sunrpc
113, // auth
115, // sftp
117, // uucp-path
119, // nntp
123, // NTP
135, // loc-srv / epmap
139, // netbios
143, // imap2
179, // BGP
389, // ldap
512, // print / exec
513, // login
514, // shell
515, // printer
526, // tempo
530, // courier
531, // Chat
532, // netnews
540, // uucp
556, // remotefs
587, // sendmail
601, //
989, // ftps data
990, // ftps
992, // telnets
993, // imap/SSL
995, // pop3/SSL
1080, // SOCKS
2049, // nfs
4045, // lockd
6000, // x11
6667, // irc
0};
if (url.port() != 80)
{
const int port = url.port();
for (int cnt=0; bad_ports[cnt] && bad_ports[cnt] <= port; ++cnt)
if (port == bad_ports[cnt])
{
_error = KIO::ERR_POST_DENIED;
break;
}
}
if ( _error )
{
static bool override_loaded = false;
static QList< int >* overriden_ports = NULL;
if( !override_loaded ) {
KConfig cfg( "kio_httprc" );
overriden_ports = new QList< int >;
*overriden_ports = cfg.group(QString()).readEntry( "OverriddenPorts", QList<int>() );
override_loaded = true;
}
for( QList< int >::ConstIterator it = overriden_ports->constBegin();
it != overriden_ports->constEnd();
++it ) {
if( overriden_ports->contains( url.port())) {
_error = 0;
}
}
}
// filter out non https? protocols
- if ((url.protocol() != "http") && (url.protocol() != "https" ))
+ if ((url.scheme() != "http") && (url.scheme() != "https" ))
_error = KIO::ERR_POST_DENIED;
if (!_error && !KAuthorized::authorizeUrlAction("open", KUrl(), url))
_error = KIO::ERR_ACCESS_DENIED;
return _error;
}
static KIO::PostErrorJob* precheckHttpPost( const KUrl& url, QIODevice* ioDevice, JobFlags flags )
{
// if request is not valid, return an invalid transfer job
const int _error = isUrlPortBad(url);
if (_error)
{
KIO_ARGS << (int)1 << url;
PostErrorJob * job = new PostErrorJob(_error, url.pathOrUrl(), packedArgs, ioDevice);
job->setUiDelegate(new JobUiDelegate());
if (!(flags & HideProgressInfo)) {
KIO::getJobTracker()->registerJob(job);
}
return job;
}
// all is ok, return 0
return 0;
}
static KIO::PostErrorJob* precheckHttpPost( const KUrl& url, const QByteArray& postData, JobFlags flags )
{
// if request is not valid, return an invalid transfer job
const int _error = isUrlPortBad(url);
if (_error)
{
KIO_ARGS << (int)1 << url;
PostErrorJob * job = new PostErrorJob(_error, url.pathOrUrl(), packedArgs, postData);
job->setUiDelegate(new JobUiDelegate());
if (!(flags & HideProgressInfo)) {
KIO::getJobTracker()->registerJob(job);
}
return job;
}
// all is ok, return 0
return 0;
}
TransferJob *KIO::http_post( const KUrl& url, const QByteArray &postData, JobFlags flags )
{
bool redirection = false;
KUrl _url(url);
if (_url.path().isEmpty())
{
redirection = true;
_url.setPath("/");
}
TransferJob* job = precheckHttpPost(_url, postData, flags);
if (job)
return job;
// Send http post command (1), decoded path and encoded query
KIO_ARGS << (int)1 << _url << static_cast<qint64>(postData.size());
job = TransferJobPrivate::newJob(_url, CMD_SPECIAL, packedArgs, postData, flags);
if (redirection)
QTimer::singleShot(0, job, SLOT(slotPostRedirection()) );
return job;
}
TransferJob *KIO::http_post( const KUrl& url, QIODevice* ioDevice, qint64 size, JobFlags flags )
{
bool redirection = false;
KUrl _url(url);
if (_url.path().isEmpty())
{
redirection = true;
_url.setPath("/");
}
TransferJob* job = precheckHttpPost(_url, ioDevice, flags);
if (job)
return job;
// If no size is specified and the QIODevice is not a sequential one,
// attempt to obtain the size information from it.
Q_ASSERT(ioDevice);
if (size < 0)
size = ((ioDevice && !ioDevice->isSequential()) ? ioDevice->size() : -1);
// Send http post command (1), decoded path and encoded query
KIO_ARGS << (int)1 << _url << size;
job = TransferJobPrivate::newJob(_url, CMD_SPECIAL, packedArgs, ioDevice, flags);
if (redirection)
QTimer::singleShot(0, job, SLOT(slotPostRedirection()) );
return job;
}
TransferJob* KIO::http_delete(const KUrl& url, JobFlags flags)
{
// Send decoded path and encoded query
KIO_ARGS << url;
TransferJob * job = TransferJobPrivate::newJob(url, CMD_DEL, packedArgs,
QByteArray(), flags);
return job;
}
StoredTransferJob *KIO::storedHttpPost( const QByteArray& postData, const KUrl& url, JobFlags flags )
{
KUrl _url(url);
if (_url.path().isEmpty())
{
_url.setPath("/");
}
StoredTransferJob* job = precheckHttpPost(_url, postData, flags);
if (job)
return job;
// Send http post command (1), decoded path and encoded query
KIO_ARGS << (int)1 << _url << static_cast<qint64>(postData.size());
job = StoredTransferJobPrivate::newJob(_url, CMD_SPECIAL, packedArgs, postData, flags );
return job;
}
StoredTransferJob *KIO::storedHttpPost( QIODevice* ioDevice, const KUrl& url, qint64 size, JobFlags flags )
{
KUrl _url(url);
if (_url.path().isEmpty())
{
_url.setPath("/");
}
StoredTransferJob* job = precheckHttpPost(_url, ioDevice, flags);
if (job)
return job;
// If no size is specified and the QIODevice is not a sequential one,
// attempt to obtain the size information from it.
Q_ASSERT(ioDevice);
if (size < 0)
size = ((ioDevice && !ioDevice->isSequential()) ? ioDevice->size() : -1);
// Send http post command (1), decoded path and encoded query
KIO_ARGS << (int)1 << _url << size;
job = StoredTransferJobPrivate::newJob(_url, CMD_SPECIAL, packedArgs, ioDevice, flags );
return job;
}
// http post got redirected from http://host to http://host/ by TransferJob
// We must do this redirection ourselves because redirections by the
// slave change post jobs into get jobs.
void TransferJobPrivate::slotPostRedirection()
{
Q_Q(TransferJob);
kDebug(7007) << "TransferJob::slotPostRedirection(" << m_url << ")";
// Tell the user about the new url.
emit q->redirection(q, m_url);
}
TransferJob *KIO::put( const KUrl& url, int permissions, JobFlags flags )
{
KIO_ARGS << url << qint8( (flags & Overwrite) ? 1 : 0 ) << qint8( (flags & Resume) ? 1 : 0 ) << permissions;
return TransferJobPrivate::newJob(url, CMD_PUT, packedArgs, QByteArray(), flags);
}
//////////
StoredTransferJob::StoredTransferJob(StoredTransferJobPrivate &dd)
: TransferJob(dd)
{
connect( this, SIGNAL( data( KIO::Job *, const QByteArray & ) ),
SLOT( slotStoredData( KIO::Job *, const QByteArray & ) ) );
connect( this, SIGNAL( dataReq( KIO::Job *, QByteArray & ) ),
SLOT( slotStoredDataReq( KIO::Job *, QByteArray & ) ) );
}
StoredTransferJob::~StoredTransferJob()
{
}
void StoredTransferJob::setData( const QByteArray& arr )
{
Q_D(StoredTransferJob);
Q_ASSERT( d->m_data.isNull() ); // check that we're only called once
Q_ASSERT( d->m_uploadOffset == 0 ); // no upload started yet
d->m_data = arr;
setTotalSize( d->m_data.size() );
}
QByteArray StoredTransferJob::data() const
{
return d_func()->m_data;
}
void StoredTransferJobPrivate::slotStoredData( KIO::Job *, const QByteArray &data )
{
// check for end-of-data marker:
if ( data.size() == 0 )
return;
unsigned int oldSize = m_data.size();
m_data.resize( oldSize + data.size() );
memcpy( m_data.data() + oldSize, data.data(), data.size() );
}
void StoredTransferJobPrivate::slotStoredDataReq( KIO::Job *, QByteArray &data )
{
// Inspired from kmail's KMKernel::byteArrayToRemoteFile
// send the data in 64 KB chunks
const int MAX_CHUNK_SIZE = 64*1024;
int remainingBytes = m_data.size() - m_uploadOffset;
if( remainingBytes > MAX_CHUNK_SIZE ) {
// send MAX_CHUNK_SIZE bytes to the receiver (deep copy)
data = QByteArray( m_data.data() + m_uploadOffset, MAX_CHUNK_SIZE );
m_uploadOffset += MAX_CHUNK_SIZE;
//kDebug() << "Sending " << MAX_CHUNK_SIZE << " bytes ("
// << remainingBytes - MAX_CHUNK_SIZE << " bytes remain)\n";
} else {
// send the remaining bytes to the receiver (deep copy)
data = QByteArray( m_data.data() + m_uploadOffset, remainingBytes );
m_data = QByteArray();
m_uploadOffset = 0;
//kDebug() << "Sending " << remainingBytes << " bytes\n";
}
}
StoredTransferJob *KIO::storedGet( const KUrl& url, LoadType reload, JobFlags flags )
{
// Send decoded path and encoded query
KIO_ARGS << url;
StoredTransferJob * job = StoredTransferJobPrivate::newJob(url, CMD_GET, packedArgs, QByteArray(), flags);
if (reload == Reload)
job->addMetaData("cache", "reload");
return job;
}
StoredTransferJob *KIO::storedPut( const QByteArray& arr, const KUrl& url, int permissions,
JobFlags flags )
{
KIO_ARGS << url << qint8( (flags & Overwrite) ? 1 : 0 ) << qint8( (flags & Resume) ? 1 : 0 ) << permissions;
StoredTransferJob * job = StoredTransferJobPrivate::newJob(url, CMD_PUT, packedArgs, QByteArray(), flags );
job->setData( arr );
return job;
}
//////////
class KIO::MimetypeJobPrivate: public KIO::TransferJobPrivate
{
public:
MimetypeJobPrivate(const KUrl& url, int command, const QByteArray &packedArgs)
: TransferJobPrivate(url, command, packedArgs, QByteArray())
{}
Q_DECLARE_PUBLIC(MimetypeJob)
static inline MimetypeJob *newJob(const KUrl& url, int command, const QByteArray &packedArgs,
JobFlags flags)
{
MimetypeJob *job = new MimetypeJob(*new MimetypeJobPrivate(url, command, packedArgs));
job->setUiDelegate(new JobUiDelegate);
if (!(flags & HideProgressInfo)) {
KIO::getJobTracker()->registerJob(job);
emitStating(job, url);
}
return job;
}
};
MimetypeJob::MimetypeJob(MimetypeJobPrivate &dd)
: TransferJob(dd)
{
}
MimetypeJob::~MimetypeJob()
{
}
void MimetypeJob::slotFinished( )
{
Q_D(MimetypeJob);
//kDebug(7007);
if ( 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 KRun, we
// assumed it was a file.
kDebug(7007) << "It is in fact a directory!";
d->m_mimetype = QString::fromLatin1("inode/directory");
emit TransferJob::mimetype( this, d->m_mimetype );
setError( 0 );
}
if ( !d->m_redirectionURL.isEmpty() && d->m_redirectionURL.isValid() && !error() )
{
//kDebug(7007) << "Redirection to " << m_redirectionURL;
if (queryMetaData("permanent-redirect")=="true")
emit permanentRedirection(this, d->m_url, d->m_redirectionURL);
if (d->m_redirectionHandlingEnabled)
{
d->staticData.truncate(0);
d->m_internalSuspended = false;
d->m_packedArgs.truncate(0);
QDataStream stream( &d->m_packedArgs, QIODevice::WriteOnly );
stream << d->m_redirectionURL;
d->restartAfterRedirection(&d->m_redirectionURL);
return;
}
}
// Return slave to the scheduler
TransferJob::slotFinished();
}
MimetypeJob *KIO::mimetype(const KUrl& url, JobFlags flags)
{
KIO_ARGS << url;
return MimetypeJobPrivate::newJob(url, CMD_MIMETYPE, packedArgs, flags);
}
//////////////////////////
class KIO::DirectCopyJobPrivate: public KIO::SimpleJobPrivate
{
public:
DirectCopyJobPrivate(const KUrl& url, int command, const QByteArray &packedArgs)
: SimpleJobPrivate(url, command, packedArgs)
{}
/**
* @internal
* Called by the scheduler when a @p slave gets to
* work on this job.
* @param slave the slave that starts working on this job
*/
virtual void start(Slave *slave);
Q_DECLARE_PUBLIC(DirectCopyJob)
};
DirectCopyJob::DirectCopyJob(const KUrl &url, const QByteArray &packedArgs)
: SimpleJob(*new DirectCopyJobPrivate(url, CMD_COPY, packedArgs))
{
setUiDelegate(new JobUiDelegate);
}
DirectCopyJob::~DirectCopyJob()
{
}
void DirectCopyJobPrivate::start( Slave* slave )
{
Q_Q(DirectCopyJob);
q->connect( slave, SIGNAL(canResume( KIO::filesize_t ) ),
SLOT( slotCanResume( KIO::filesize_t ) ) );
SimpleJobPrivate::start(slave);
}
void DirectCopyJob::slotCanResume( KIO::filesize_t offset )
{
emit canResume(this, offset);
}
//////////////////////////
/** @internal */
class KIO::FileCopyJobPrivate: public KIO::JobPrivate
{
public:
FileCopyJobPrivate(const KUrl& src, const KUrl& dest, int permissions,
bool move, JobFlags flags)
: m_sourceSize(filesize_t(-1)), m_src(src), m_dest(dest), m_moveJob(0), m_copyJob(0), m_delJob(0),
m_chmodJob(0), m_getJob(0), m_putJob(0), m_permissions(permissions),
m_move(move), m_mustChmod(0), m_flags(flags)
{
}
KIO::filesize_t m_sourceSize;
QDateTime m_modificationTime;
KUrl m_src;
KUrl m_dest;
QByteArray m_buffer;
SimpleJob *m_moveJob;
SimpleJob *m_copyJob;
SimpleJob *m_delJob;
SimpleJob *m_chmodJob;
TransferJob *m_getJob;
TransferJob *m_putJob;
int m_permissions;
bool m_move:1;
bool m_canResume:1;
bool m_resumeAnswerSent:1;
bool m_mustChmod:1;
JobFlags m_flags;
void startBestCopyMethod();
void startCopyJob();
void startCopyJob(const KUrl &slave_url);
void startRenameJob(const KUrl &slave_url);
void startDataPump();
void connectSubjob( SimpleJob * job );
void slotStart();
void slotData( KIO::Job *, const QByteArray &data);
void slotDataReq( KIO::Job *, QByteArray &data);
void slotMimetype( KIO::Job*, const QString& type );
/**
* Forward signal from subjob
* @param job the job that emitted this signal
* @param size the processed size in bytes
*/
void slotProcessedSize( KJob *job, qulonglong size );
/**
* Forward signal from subjob
* @param job the job that emitted this signal
* @param size the total size
*/
void slotTotalSize( KJob *job, qulonglong size );
/**
* Forward signal from subjob
* @param job the job that emitted this signal
* @param pct the percentage
*/
void slotPercent( KJob *job, unsigned long pct );
/**
* Forward signal from subjob
* @param job the job that emitted this signal
* @param offset the offset to resume from
*/
void slotCanResume( KIO::Job *job, KIO::filesize_t offset );
Q_DECLARE_PUBLIC(FileCopyJob)
static inline FileCopyJob* newJob(const KUrl& src, const KUrl& dest, int permissions, bool move,
JobFlags flags)
{
//kDebug(7007) << src << "->" << dest;
FileCopyJob *job = new FileCopyJob(
*new FileCopyJobPrivate(src, dest, permissions, move, flags));
job->setProperty("destUrl", dest.url());
job->setUiDelegate(new JobUiDelegate);
if (!(flags & HideProgressInfo))
KIO::getJobTracker()->registerJob(job);
return job;
}
};
/*
* The FileCopyJob works according to the famous Bavarian
* 'Alternating Bitburger Protocol': we either drink a beer or we
* we order a beer, but never both at the same time.
* Translated to io-slaves: We alternate between receiving a block of data
* and sending it away.
*/
FileCopyJob::FileCopyJob(FileCopyJobPrivate &dd)
: Job(dd)
{
//kDebug(7007);
QTimer::singleShot(0, this, SLOT(slotStart()));
}
void FileCopyJobPrivate::slotStart()
{
Q_Q(FileCopyJob);
if (!m_move)
JobPrivate::emitCopying( q, m_src, m_dest );
else
JobPrivate::emitMoving( q, m_src, m_dest );
if ( m_move )
{
// The if() below must be the same as the one in startBestCopyMethod
- if ((m_src.protocol() == m_dest.protocol()) &&
+ if ((m_src.scheme() == m_dest.scheme()) &&
(m_src.host() == m_dest.host()) &&
(m_src.port() == m_dest.port()) &&
(m_src.user() == m_dest.user()) &&
(m_src.pass() == m_dest.pass()) &&
!m_src.hasSubUrl() && !m_dest.hasSubUrl())
{
startRenameJob(m_src);
return;
}
else if (m_src.isLocalFile() && KProtocolManager::canRenameFromFile(m_dest))
{
startRenameJob(m_dest);
return;
}
else if (m_dest.isLocalFile() && KProtocolManager::canRenameToFile(m_src))
{
startRenameJob(m_src);
return;
}
// No fast-move available, use copy + del.
}
startBestCopyMethod();
}
void FileCopyJobPrivate::startBestCopyMethod()
{
- if ((m_src.protocol() == m_dest.protocol()) &&
+ if ((m_src.scheme() == m_dest.scheme()) &&
(m_src.host() == m_dest.host()) &&
(m_src.port() == m_dest.port()) &&
(m_src.user() == m_dest.user()) &&
(m_src.pass() == m_dest.pass()) &&
!m_src.hasSubUrl() && !m_dest.hasSubUrl())
{
startCopyJob();
}
else if (m_src.isLocalFile() && KProtocolManager::canCopyFromFile(m_dest))
{
startCopyJob(m_dest);
}
else if (m_dest.isLocalFile() && KProtocolManager::canCopyToFile(m_src) &&
!KIO::Scheduler::isSlaveOnHoldFor(m_src))
{
startCopyJob(m_src);
}
else
{
startDataPump();
}
}
FileCopyJob::~FileCopyJob()
{
}
void FileCopyJob::setSourceSize( KIO::filesize_t size )
{
Q_D(FileCopyJob);
d->m_sourceSize = size;
if (size != (KIO::filesize_t) -1)
setTotalAmount(KJob::Bytes, size);
}
void FileCopyJob::setModificationTime( const QDateTime& mtime )
{
Q_D(FileCopyJob);
d->m_modificationTime = mtime;
}
KUrl FileCopyJob::srcUrl() const
{
return d_func()->m_src;
}
KUrl FileCopyJob::destUrl() const
{
return d_func()->m_dest;
}
void FileCopyJobPrivate::startCopyJob()
{
startCopyJob(m_src);
}
void FileCopyJobPrivate::startCopyJob(const KUrl &slave_url)
{
Q_Q(FileCopyJob);
//kDebug(7007);
KIO_ARGS << m_src << m_dest << m_permissions << (qint8) (m_flags & Overwrite);
m_copyJob = new DirectCopyJob(slave_url, packedArgs);
q->addSubjob( m_copyJob );
connectSubjob( m_copyJob );
q->connect( m_copyJob, SIGNAL(canResume(KIO::Job *, KIO::filesize_t)),
SLOT(slotCanResume(KIO::Job *, KIO::filesize_t)));
}
void FileCopyJobPrivate::startRenameJob(const KUrl &slave_url)
{
Q_Q(FileCopyJob);
m_mustChmod = true; // CMD_RENAME by itself doesn't change permissions
KIO_ARGS << m_src << m_dest << (qint8) (m_flags & Overwrite);
m_moveJob = SimpleJobPrivate::newJobNoUi(slave_url, CMD_RENAME, packedArgs);
q->addSubjob( m_moveJob );
connectSubjob( m_moveJob );
}
void FileCopyJobPrivate::connectSubjob( SimpleJob * job )
{
Q_Q(FileCopyJob);
q->connect( job, SIGNAL(totalSize( KJob*, qulonglong )),
SLOT( slotTotalSize(KJob*, qulonglong)) );
q->connect( job, SIGNAL(processedSize( KJob*, qulonglong )),
SLOT( slotProcessedSize(KJob*, qulonglong)) );
q->connect( job, SIGNAL(percent( KJob*, unsigned long )),
SLOT( slotPercent(KJob*, unsigned long)) );
}
bool FileCopyJob::doSuspend()
{
Q_D(FileCopyJob);
if (d->m_moveJob)
d->m_moveJob->suspend();
if (d->m_copyJob)
d->m_copyJob->suspend();
if (d->m_getJob)
d->m_getJob->suspend();
if (d->m_putJob)
d->m_putJob->suspend();
Job::doSuspend();
return true;
}
bool FileCopyJob::doResume()
{
Q_D(FileCopyJob);
if (d->m_moveJob)
d->m_moveJob->resume();
if (d->m_copyJob)
d->m_copyJob->resume();
if (d->m_getJob)
d->m_getJob->resume();
if (d->m_putJob)
d->m_putJob->resume();
Job::doResume();
return true;
}
void FileCopyJobPrivate::slotProcessedSize( KJob *, qulonglong size )
{
Q_Q(FileCopyJob);
q->setProcessedAmount(KJob::Bytes, size);
}
void FileCopyJobPrivate::slotTotalSize( KJob*, qulonglong size )
{
Q_Q(FileCopyJob);
if (size != q->totalAmount(KJob::Bytes))
{
q->setTotalAmount(KJob::Bytes, size);
}
}
void FileCopyJobPrivate::slotPercent( KJob*, unsigned long pct )
{
Q_Q(FileCopyJob);
if ( pct > q->percent() ) {
q->setPercent( pct );
}
}
void FileCopyJobPrivate::startDataPump()
{
Q_Q(FileCopyJob);
//kDebug(7007);
m_canResume = false;
m_resumeAnswerSent = false;
m_getJob = 0L; // for now
m_putJob = put( m_dest, m_permissions, (m_flags | HideProgressInfo) /* no GUI */);
//kDebug(7007) << "m_putJob=" << m_putJob << "m_dest=" << m_dest;
if ( m_modificationTime.isValid() ) {
m_putJob->setModificationTime( m_modificationTime );
}
// The first thing the put job will tell us is whether we can
// resume or not (this is always emitted)
q->connect( m_putJob, SIGNAL(canResume(KIO::Job *, KIO::filesize_t)),
SLOT( slotCanResume(KIO::Job *, KIO::filesize_t)));
q->connect( m_putJob, SIGNAL(dataReq(KIO::Job *, QByteArray&)),
SLOT( slotDataReq(KIO::Job *, QByteArray&)));
q->addSubjob( m_putJob );
}
void FileCopyJobPrivate::slotCanResume( KIO::Job* job, KIO::filesize_t offset )
{
Q_Q(FileCopyJob);
if ( job == m_putJob || job == m_copyJob )
{
//kDebug(7007) << "'can resume' from PUT job. offset=" << KIO::number(offset);
if (offset)
{
RenameDialog_Result res = R_RESUME;
if (!KProtocolManager::autoResume() && !(m_flags & Overwrite))
{
QString newPath;
KIO::Job* job = ( q->parentJob() ) ? q->parentJob() : q;
// Ask confirmation about resuming previous transfer
res = ui()->askFileRename(
job, i18n("File Already Exists"),
m_src.url(),
m_dest.url(),
(RenameDialog_Mode) (M_OVERWRITE | M_RESUME | M_NORENAME), newPath,
m_sourceSize, offset );
}
if ( res == R_OVERWRITE || (m_flags & Overwrite) )
offset = 0;
else if ( res == R_CANCEL )
{
if ( job == m_putJob ) {
m_putJob->kill( FileCopyJob::Quietly );
q->removeSubjob(m_putJob);
m_putJob = 0;
} else {
m_copyJob->kill( FileCopyJob::Quietly );
q->removeSubjob(m_copyJob);
m_copyJob = 0;
}
q->setError( ERR_USER_CANCELED );
q->emitResult();
return;
}
}
else
m_resumeAnswerSent = true; // No need for an answer
if ( job == m_putJob )
{
m_getJob = KIO::get( m_src, NoReload, HideProgressInfo /* no GUI */ );
//kDebug(7007) << "m_getJob=" << m_getJob << m_src;
m_getJob->addMetaData( "errorPage", "false" );
m_getJob->addMetaData( "AllowCompressedPage", "false" );
// Set size in subjob. This helps if the slave doesn't emit totalSize.
if ( m_sourceSize != (KIO::filesize_t)-1 )
m_getJob->setTotalAmount(KJob::Bytes, m_sourceSize);
if (offset)
{
//kDebug(7007) << "Setting metadata for resume to" << (unsigned long) offset;
// TODO KDE4: rename to seek or offset and document it
// This isn't used only for resuming, but potentially also for extracting (#72302).
m_getJob->addMetaData( "resume", KIO::number(offset) );
// Might or might not get emitted
q->connect( m_getJob, SIGNAL(canResume(KIO::Job *, KIO::filesize_t)),
SLOT( slotCanResume(KIO::Job *, KIO::filesize_t)));
}
jobSlave(m_putJob)->setOffset( offset );
m_putJob->d_func()->internalSuspend();
q->addSubjob( m_getJob );
connectSubjob( m_getJob ); // Progress info depends on get
m_getJob->d_func()->internalResume(); // Order a beer
q->connect( m_getJob, SIGNAL(data(KIO::Job*,const QByteArray&)),
SLOT( slotData(KIO::Job*,const QByteArray&)) );
q->connect( m_getJob, SIGNAL(mimetype(KIO::Job*,const QString&) ),
SLOT(slotMimetype(KIO::Job*,const QString&)) );
}
else // copyjob
{
jobSlave(m_copyJob)->sendResumeAnswer( offset != 0 );
}
}
else if ( job == m_getJob )
{
// Cool, the get job said ok, we can resume
m_canResume = true;
//kDebug(7007) << "'can resume' from the GET job -> we can resume";
jobSlave(m_getJob)->setOffset( jobSlave(m_putJob)->offset() );
}
else
kWarning(7007) << "unknown job=" << job
<< "m_getJob=" << m_getJob << "m_putJob=" << m_putJob;
}
void FileCopyJobPrivate::slotData( KIO::Job * , const QByteArray &data)
{
//kDebug(7007) << "data size:" << data.size();
Q_ASSERT(m_putJob);
if (!m_putJob) return; // Don't crash
m_getJob->d_func()->internalSuspend();
m_putJob->d_func()->internalResume(); // Drink the beer
m_buffer += data;
// On the first set of data incoming, we tell the "put" slave about our
// decision about resuming
if (!m_resumeAnswerSent)
{
m_resumeAnswerSent = true;
//kDebug(7007) << "(first time) -> send resume answer " << m_canResume;
jobSlave(m_putJob)->sendResumeAnswer( m_canResume );
}
}
void FileCopyJobPrivate::slotDataReq( KIO::Job * , QByteArray &data)
{
Q_Q(FileCopyJob);
//kDebug(7007);
if (!m_resumeAnswerSent && !m_getJob) {
// This can't happen
q->setError( ERR_INTERNAL );
q->setErrorText( i18n( "'Put' job did not send canResume or 'Get' job did not send data!" ) );
m_putJob->kill( FileCopyJob::Quietly );
q->removeSubjob(m_putJob);
m_putJob = 0;
q->emitResult();
return;
}
if (m_getJob)
{
m_getJob->d_func()->internalResume(); // Order more beer
m_putJob->d_func()->internalSuspend();
}
data = m_buffer;
m_buffer = QByteArray();
}
void FileCopyJobPrivate::slotMimetype( KIO::Job*, const QString& type )
{
Q_Q(FileCopyJob);
emit q->mimetype( q, type );
}
void FileCopyJob::slotResult( KJob *job)
{
Q_D(FileCopyJob);
//kDebug(7007) << "this=" << this << "job=" << job;
removeSubjob(job);
// Did job have an error ?
if ( job->error() )
{
if ((job == d->m_moveJob) && (job->error() == ERR_UNSUPPORTED_ACTION))
{
d->m_moveJob = 0;
d->startBestCopyMethod();
return;
}
else if ((job == d->m_copyJob) && (job->error() == ERR_UNSUPPORTED_ACTION))
{
d->m_copyJob = 0;
d->startDataPump();
return;
}
else if (job == d->m_getJob)
{
d->m_getJob = 0L;
if (d->m_putJob)
{
d->m_putJob->kill( Quietly );
removeSubjob( d->m_putJob );
}
}
else if (job == d->m_putJob)
{
d->m_putJob = 0L;
if (d->m_getJob)
{
d->m_getJob->kill( Quietly );
removeSubjob( d->m_getJob );
}
}
setError( job->error() );
setErrorText( job->errorText() );
emitResult();
return;
}
if (d->m_mustChmod)
{
// If d->m_permissions == -1, keep the default permissions
if (d->m_permissions != -1)
{
d->m_chmodJob = chmod(d->m_dest, d->m_permissions);
}
d->m_mustChmod = false;
}
if (job == d->m_moveJob)
{
d->m_moveJob = 0; // Finished
}
if (job == d->m_copyJob)
{
d->m_copyJob = 0;
if (d->m_move)
{
d->m_delJob = file_delete( d->m_src, HideProgressInfo/*no GUI*/ ); // Delete source
addSubjob(d->m_delJob);
}
}
if (job == d->m_getJob)
{
//kDebug(7007) << "m_getJob finished";
d->m_getJob = 0; // No action required
if (d->m_putJob)
d->m_putJob->d_func()->internalResume();
}
if (job == d->m_putJob)
{
//kDebug(7007) << "m_putJob finished";
d->m_putJob = 0;
if (d->m_getJob)
{
// The get job is still running, probably after emitting data(QByteArray())
// and before we receive its finished().
d->m_getJob->d_func()->internalResume();
}
if (d->m_move)
{
d->m_delJob = file_delete( d->m_src, HideProgressInfo/*no GUI*/ ); // Delete source
addSubjob(d->m_delJob);
}
}
if (job == d->m_delJob)
{
d->m_delJob = 0; // Finished
}
if (job == d->m_chmodJob)
{
d->m_chmodJob = 0; // Finished
}
if ( !hasSubjobs() )
emitResult();
}
FileCopyJob *KIO::file_copy( const KUrl& src, const KUrl& dest, int permissions,
JobFlags flags )
{
return FileCopyJobPrivate::newJob(src, dest, permissions, false, flags);
}
FileCopyJob *KIO::file_move( const KUrl& src, const KUrl& dest, int permissions,
JobFlags flags )
{
return FileCopyJobPrivate::newJob(src, dest, permissions, true, flags);
}
SimpleJob *KIO::file_delete( const KUrl& src, JobFlags flags )
{
KIO_ARGS << src << qint8(true); // isFile
return SimpleJobPrivate::newJob(src, CMD_DEL, packedArgs, flags);
}
//////////
class KIO::ListJobPrivate: public KIO::SimpleJobPrivate
{
public:
ListJobPrivate(const KUrl& url, bool _recursive, const QString &_prefix, bool _includeHidden)
: SimpleJobPrivate(url, CMD_LISTDIR, QByteArray()),
recursive(_recursive), includeHidden(_includeHidden),
prefix(_prefix), m_processedEntries(0)
{}
bool recursive;
bool includeHidden;
QString prefix;
unsigned long m_processedEntries;
KUrl m_redirectionURL;
/**
* @internal
* Called by the scheduler when a @p slave gets to
* work on this job.
* @param slave the slave that starts working on this job
*/
virtual void start( Slave *slave );
void slotListEntries( const KIO::UDSEntryList& list );
void slotRedirection( const KUrl &url );
void gotEntries( KIO::Job * subjob, const KIO::UDSEntryList& list );
Q_DECLARE_PUBLIC(ListJob)
static inline ListJob *newJob(const KUrl& u, bool _recursive, const QString &_prefix,
bool _includeHidden, JobFlags flags = HideProgressInfo)
{
ListJob *job = new ListJob(*new ListJobPrivate(u, _recursive, _prefix, _includeHidden));
job->setUiDelegate(new JobUiDelegate);
if (!(flags & HideProgressInfo))
KIO::getJobTracker()->registerJob(job);
return job;
}
static inline ListJob *newJobNoUi(const KUrl& u, bool _recursive, const QString &_prefix,
bool _includeHidden)
{
return new ListJob(*new ListJobPrivate(u, _recursive, _prefix, _includeHidden));
}
};
ListJob::ListJob(ListJobPrivate &dd)
: SimpleJob(dd)
{
Q_D(ListJob);
// We couldn't set the args when calling the parent constructor,
// so do it now.
QDataStream stream( &d->m_packedArgs, QIODevice::WriteOnly );
stream << d->m_url;
}
ListJob::~ListJob()
{
}
void ListJobPrivate::slotListEntries( const KIO::UDSEntryList& list )
{
Q_Q(ListJob);
// Emit progress info (takes care of emit processedSize and percent)
m_processedEntries += list.count();
slotProcessedSize( m_processedEntries );
if (recursive) {
UDSEntryList::ConstIterator it = list.begin();
const UDSEntryList::ConstIterator end = list.end();
for (; it != end; ++it) {
const UDSEntry& entry = *it;
KUrl itemURL;
// const UDSEntry::ConstIterator end2 = entry.end();
// UDSEntry::ConstIterator it2 = entry.find( KIO::UDSEntry::UDS_URL );
// if ( it2 != end2 )
if (entry.contains(KIO::UDSEntry::UDS_URL))
// itemURL = it2.value().toString();
itemURL = entry.stringValue(KIO::UDSEntry::UDS_URL);
else { // no URL, use the name
itemURL = q->url();
const QString fileName = entry.stringValue(KIO::UDSEntry::UDS_NAME);
Q_ASSERT(!fileName.isEmpty()); // we'll recurse forever otherwise :)
itemURL.addPath(fileName);
}
if (entry.isDir() && !entry.isLink()) {
const QString filename = itemURL.fileName();
// skip hidden dirs when listing if requested
if (filename != ".." && filename != "." && (includeHidden || filename[0] != '.')) {
ListJob *job = ListJobPrivate::newJobNoUi(itemURL,
true /*recursive*/,
prefix + filename + '/',
includeHidden);
Scheduler::setJobPriority(job, 1);
q->connect(job, SIGNAL(entries( KIO::Job *, const KIO::UDSEntryList& )),
SLOT( gotEntries( KIO::Job*, const KIO::UDSEntryList& )));
q->addSubjob(job);
}
}
}
}
// Not recursive, or top-level of recursive listing : return now (send . and .. as well)
// exclusion of hidden files also requires the full sweep, but the case for full-listing
// a single dir is probably common enough to justify the shortcut
if (prefix.isNull() && includeHidden) {
emit q->entries(q, list);
} else {
// cull the unwanted hidden dirs and/or parent dir references from the listing, then emit that
UDSEntryList newlist;
UDSEntryList::const_iterator it = list.begin();
const UDSEntryList::const_iterator end = list.end();
for (; it != end; ++it) {
// Modify the name in the UDSEntry
UDSEntry newone = *it;
const QString filename = newone.stringValue( KIO::UDSEntry::UDS_NAME );
// Avoid returning entries like subdir/. and subdir/.., but include . and .. for
// the toplevel dir, and skip hidden files/dirs if that was requested
if ( (prefix.isNull() || (filename != ".." && filename != ".") )
&& (includeHidden || (filename[0] != '.') ) )
{
// ## Didn't find a way to use the iterator instead of re-doing a key lookup
newone.insert( KIO::UDSEntry::UDS_NAME, prefix + filename );
newlist.append(newone);
}
}
emit q->entries(q, newlist);
}
}
void ListJobPrivate::gotEntries(KIO::Job *, const KIO::UDSEntryList& list )
{
// Forward entries received by subjob - faking we received them ourselves
Q_Q(ListJob);
emit q->entries(q, list);
}
void ListJob::slotResult( KJob * job )
{
// If we can't list a subdir, the result is still ok
// This is why we override Job::slotResult() - to skip error checking
removeSubjob( job );
if ( !hasSubjobs() )
emitResult();
}
void ListJobPrivate::slotRedirection( const KUrl & url )
{
Q_Q(ListJob);
if (!KAuthorized::authorizeUrlAction("redirect", m_url, url))
{
kWarning(7007) << "ListJob: Redirection from " << m_url << " to " << url << " REJECTED!";
return;
}
m_redirectionURL = url; // We'll remember that when the job finishes
emit q->redirection( q, m_redirectionURL );
}
void ListJob::slotFinished()
{
Q_D(ListJob);
// Support for listing archives as directories
if ( error() == KIO::ERR_IS_FILE && d->m_url.isLocalFile() ) {
#if 0 // TODO port this KDE 3 code to KProtocolManager::protocolForArchiveMimetype.
// Note however that this code never worked in KDE 4.
KMimeType::Ptr ptr = KMimeType::findByUrl( d->m_url, 0, true, true );
if ( ptr ) {
QString proto = ptr->property("X-KDE-LocalProtocol").toString();
if ( !proto.isEmpty() && KProtocolInfo::isKnownProtocol( proto) ) {
d->m_redirectionURL = d->m_url;
d->m_redirectionURL.setProtocol( proto );
setError( 0 );
emit redirection(this,d->m_redirectionURL);
}
}
#endif
}
if ( !d->m_redirectionURL.isEmpty() && d->m_redirectionURL.isValid() && !error() ) {
//kDebug(7007) << "Redirection to " << d->m_redirectionURL;
if (queryMetaData("permanent-redirect")=="true")
emit permanentRedirection(this, d->m_url, d->m_redirectionURL);
if ( d->m_redirectionHandlingEnabled ) {
d->m_packedArgs.truncate(0);
QDataStream stream( &d->m_packedArgs, QIODevice::WriteOnly );
stream << d->m_redirectionURL;
d->restartAfterRedirection(&d->m_redirectionURL);
return;
}
}
// Return slave to the scheduler
SimpleJob::slotFinished();
}
void ListJob::slotMetaData( const KIO::MetaData &_metaData)
{
Q_D(ListJob);
SimpleJob::slotMetaData(_metaData);
storeSSLSessionFromJob(d->m_redirectionURL);
}
ListJob *KIO::listDir( const KUrl& url, JobFlags flags, bool includeHidden )
{
return ListJobPrivate::newJob(url, false, QString(), includeHidden, flags);
}
ListJob *KIO::listRecursive( const KUrl& url, JobFlags flags, bool includeHidden )
{
return ListJobPrivate::newJob(url, true, QString(), includeHidden, flags);
}
void ListJob::setUnrestricted(bool unrestricted)
{
Q_D(ListJob);
if (unrestricted)
d->m_extraFlags |= JobPrivate::EF_ListJobUnrestricted;
else
d->m_extraFlags &= ~JobPrivate::EF_ListJobUnrestricted;
}
void ListJobPrivate::start(Slave *slave)
{
Q_Q(ListJob);
if (!KAuthorized::authorizeUrlAction("list", m_url, m_url) &&
!(m_extraFlags & EF_ListJobUnrestricted))
{
q->setError( ERR_ACCESS_DENIED );
q->setErrorText( m_url.url() );
QTimer::singleShot(0, q, SLOT(slotFinished()) );
return;
}
q->connect( slave, SIGNAL( listEntries( const KIO::UDSEntryList& )),
SLOT( slotListEntries( const KIO::UDSEntryList& )));
q->connect( slave, SIGNAL( totalSize( KIO::filesize_t ) ),
SLOT( slotTotalSize( KIO::filesize_t ) ) );
q->connect( slave, SIGNAL( redirection(const KUrl &) ),
SLOT( slotRedirection(const KUrl &) ) );
SimpleJobPrivate::start(slave);
}
const KUrl& ListJob::redirectionUrl() const
{
return d_func()->m_redirectionURL;
}
////
class KIO::MultiGetJobPrivate: public KIO::TransferJobPrivate
{
public:
MultiGetJobPrivate(const KUrl& url)
: TransferJobPrivate(url, 0, QByteArray(), QByteArray()),
m_currentEntry( 0, KUrl(), MetaData() )
{}
struct GetRequest {
GetRequest(long _id, const KUrl &_url, const MetaData &_metaData)
: id(_id), url(_url), metaData(_metaData) { }
long id;
KUrl url;
MetaData metaData;
inline bool operator==( const GetRequest& req ) const
{ return req.id == id; }
};
typedef QLinkedList<GetRequest> RequestQueue;
RequestQueue m_waitQueue;
RequestQueue m_activeQueue;
GetRequest m_currentEntry;
bool b_multiGetActive;
/**
* @internal
* Called by the scheduler when a @p slave gets to
* work on this job.
* @param slave the slave that starts working on this job
*/
virtual void start(Slave *slave);
bool findCurrentEntry();
void flushQueue(QLinkedList<GetRequest> &queue);
Q_DECLARE_PUBLIC(MultiGetJob)
static inline MultiGetJob *newJob(const KUrl &url)
{
MultiGetJob *job = new MultiGetJob(*new MultiGetJobPrivate(url));
job->setUiDelegate(new JobUiDelegate);
return job;
}
};
MultiGetJob::MultiGetJob(MultiGetJobPrivate &dd)
: TransferJob(dd)
{
}
MultiGetJob::~MultiGetJob()
{
}
void MultiGetJob::get(long id, const KUrl &url, const MetaData &metaData)
{
Q_D(MultiGetJob);
MultiGetJobPrivate::GetRequest entry(id, url, metaData);
entry.metaData["request-id"] = QString::number(id);
d->m_waitQueue.append(entry);
}
void MultiGetJobPrivate::flushQueue(RequestQueue &queue)
{
// Use multi-get
// Scan all jobs in m_waitQueue
RequestQueue::iterator wqit = m_waitQueue.begin();
const RequestQueue::iterator wqend = m_waitQueue.end();
while ( wqit != wqend )
{
const GetRequest& entry = *wqit;
- if ((m_url.protocol() == entry.url.protocol()) &&
+ if ((m_url.scheme() == entry.url.scheme()) &&
(m_url.host() == entry.url.host()) &&
(m_url.port() == entry.url.port()) &&
(m_url.user() == entry.url.user()))
{
queue.append( entry );
wqit = m_waitQueue.erase( wqit );
}
else
{
++wqit;
}
}
// Send number of URLs, (URL, metadata)*
KIO_ARGS << (qint32) queue.count();
RequestQueue::const_iterator qit = queue.begin();
const RequestQueue::const_iterator qend = queue.end();
for( ; qit != qend; ++qit )
{
stream << (*qit).url << (*qit).metaData;
}
m_packedArgs = packedArgs;
m_command = CMD_MULTI_GET;
m_outgoingMetaData.clear();
}
void MultiGetJobPrivate::start(Slave *slave)
{
// Add first job from m_waitQueue and add it to m_activeQueue
GetRequest entry = m_waitQueue.takeFirst();
m_activeQueue.append(entry);
m_url = entry.url;
- if (!entry.url.protocol().startsWith(QLatin1String("http")))
+ if (!entry.url.scheme().startsWith(QLatin1String("http")))
{
// Use normal get
KIO_ARGS << entry.url;
m_packedArgs = packedArgs;
m_outgoingMetaData = entry.metaData;
m_command = CMD_GET;
b_multiGetActive = false;
}
else
{
flushQueue(m_activeQueue);
b_multiGetActive = true;
}
TransferJobPrivate::start(slave); // Anything else to do??
}
bool MultiGetJobPrivate::findCurrentEntry()
{
if (b_multiGetActive)
{
long id = m_incomingMetaData["request-id"].toLong();
RequestQueue::const_iterator qit = m_activeQueue.begin();
const RequestQueue::const_iterator qend = m_activeQueue.end();
for( ; qit != qend; ++qit )
{
if ((*qit).id == id)
{
m_currentEntry = *qit;
return true;
}
}
m_currentEntry.id = 0;
return false;
}
else
{
if ( m_activeQueue.isEmpty() )
return false;
m_currentEntry = m_activeQueue.first();
return true;
}
}
void MultiGetJob::slotRedirection( const KUrl &url)
{
Q_D(MultiGetJob);
if (!d->findCurrentEntry()) return; // Error
if (!KAuthorized::authorizeUrlAction("redirect", d->m_url, url))
{
kWarning(7007) << "MultiGetJob: Redirection from " << d->m_currentEntry.url << " to " << url << " REJECTED!";
return;
}
d->m_redirectionURL = url;
get(d->m_currentEntry.id, d->m_redirectionURL, d->m_currentEntry.metaData); // Try again
}
void MultiGetJob::slotFinished()
{
Q_D(MultiGetJob);
if (!d->findCurrentEntry()) return;
if (d->m_redirectionURL.isEmpty())
{
// No redirection, tell the world that we are finished.
emit result(d->m_currentEntry.id);
}
d->m_redirectionURL = KUrl();
setError( 0 );
d->m_incomingMetaData.clear();
d->m_activeQueue.removeAll(d->m_currentEntry);
if (d->m_activeQueue.count() == 0)
{
if (d->m_waitQueue.count() == 0)
{
// All done
TransferJob::slotFinished();
}
else
{
// return slave to pool
// fetch new slave for first entry in d->m_waitQueue and call start
// again.
d->slaveDone();
d->m_url = d->m_waitQueue.first().url;
if ((d->m_extraFlags & JobPrivate::EF_KillCalled) == 0) {
Scheduler::doJob(this);
}
}
}
}
void MultiGetJob::slotData( const QByteArray &_data)
{
Q_D(MultiGetJob);
if(d->m_redirectionURL.isEmpty() || !d->m_redirectionURL.isValid() || error())
emit data(d->m_currentEntry.id, _data);
}
void MultiGetJob::slotMimetype( const QString &_mimetype )
{
Q_D(MultiGetJob);
if (d->b_multiGetActive)
{
MultiGetJobPrivate::RequestQueue newQueue;
d->flushQueue(newQueue);
if (!newQueue.isEmpty())
{
d->m_activeQueue += newQueue;
d->m_slave->send( d->m_command, d->m_packedArgs );
}
}
if (!d->findCurrentEntry()) return; // Error, unknown request!
emit mimetype(d->m_currentEntry.id, _mimetype);
}
MultiGetJob *KIO::multi_get(long id, const KUrl &url, const MetaData &metaData)
{
MultiGetJob * job = MultiGetJobPrivate::newJob(url);
job->get(id, url, metaData);
return job;
}
class KIO::SpecialJobPrivate: public TransferJobPrivate
{
SpecialJobPrivate(const KUrl& url, int command,
const QByteArray &packedArgs,
const QByteArray &_staticData)
: TransferJobPrivate(url, command, packedArgs, _staticData)
{}
};
SpecialJob::SpecialJob(const KUrl &url, const QByteArray &packedArgs)
: TransferJob(*new TransferJobPrivate(url, CMD_SPECIAL, packedArgs, QByteArray()))
{
}
SpecialJob::~SpecialJob()
{
}
void SpecialJob::setArguments(const QByteArray &data)
{
Q_D(SpecialJob);
d->m_packedArgs = data;
}
QByteArray SpecialJob::arguments() const
{
return d_func()->m_packedArgs;
}
// Never defined, never used - what's this code about?
#ifdef CACHE_INFO
CacheInfo::CacheInfo(const KUrl &url)
{
m_url = url;
}
QString CacheInfo::cachedFileName()
{
const QChar separator = '_';
QString CEF = m_url.path();
int p = CEF.find('/');
while(p != -1)
{
CEF[p] = separator;
p = CEF.find('/', p);
}
QString host = m_url.host().toLower();
CEF = host + CEF + '_';
QString dir = KProtocolManager::cacheDir();
if (dir[dir.length()-1] != '/')
dir += '/';
int l = m_url.host().length();
for(int i = 0; i < l; i++)
{
if (host[i].isLetter() && (host[i] != 'w'))
{
dir += host[i];
break;
}
}
if (dir[dir.length()-1] == '/')
dir += '0';
unsigned long hash = 0x00000000;
QString u = m_url.url().toLatin1();
for(int i = u.length(); i--;)
{
hash = (hash * 12211 + u[i]) % 2147483563;
}
QString hashString;
hashString.sprintf("%08lx", hash);
CEF = CEF + hashString;
CEF = dir + '/' + CEF;
return CEF;
}
QFile *CacheInfo::cachedFile()
{
#ifdef Q_WS_WIN
const char *mode = (readWrite ? "rb+" : "rb");
#else
const char *mode = (readWrite ? "r+" : "r");
#endif
FILE *fs = KDE::fopen(CEF, mode); // Open for reading and writing
if (!fs)
return 0;
char buffer[401];
bool ok = true;
// CacheRevision
if (ok && (!fgets(buffer, 400, fs)))
ok = false;
if (ok && (strcmp(buffer, CACHE_REVISION) != 0))
ok = false;
time_t date;
time_t currentDate = time(0);
// URL
if (ok && (!fgets(buffer, 400, fs)))
ok = false;
if (ok)
{
int l = strlen(buffer);
if (l>0)
buffer[l-1] = 0; // Strip newline
if (m_.url.url() != buffer)
{
ok = false; // Hash collision
}
}
// Creation Date
if (ok && (!fgets(buffer, 400, fs)))
ok = false;
if (ok)
{
date = (time_t) strtoul(buffer, 0, 10);
if (m_maxCacheAge && (difftime(currentDate, date) > m_maxCacheAge))
{
m_bMustRevalidate = true;
m_expireDate = currentDate;
}
}
// Expiration Date
m_cacheExpireDateOffset = KDE_ftell(fs);
if (ok && (!fgets(buffer, 400, fs)))
ok = false;
if (ok)
{
if (m_request.cache == CC_Verify)
{
date = (time_t) strtoul(buffer, 0, 10);
// After the expire date we need to revalidate.
if (!date || difftime(currentDate, date) >= 0)
m_bMustRevalidate = true;
m_expireDate = date;
}
}
// ETag
if (ok && (!fgets(buffer, 400, fs)))
ok = false;
if (ok)
{
m_etag = QString(buffer).trimmed();
}
// Last-Modified
if (ok && (!fgets(buffer, 400, fs)))
ok = false;
if (ok)
{
m_lastModified = QString(buffer).trimmed();
}
fclose(fs);
if (ok)
return fs;
unlink( QFile::encodeName(CEF) );
return 0;
}
void CacheInfo::flush()
{
cachedFile().remove();
}
void CacheInfo::touch()
{
}
void CacheInfo::setExpireDate(int);
void CacheInfo::setExpireTimeout(int);
int CacheInfo::creationDate();
int CacheInfo::expireDate();
int CacheInfo::expireTimeout();
#endif
#include "moc_jobclasses.cpp"
#include "moc_job_p.cpp"
diff --git a/kio/kio/jobuidelegate.cpp b/kio/kio/jobuidelegate.cpp
index acb3f39de5..53faef4ac1 100644
--- a/kio/kio/jobuidelegate.cpp
+++ b/kio/kio/jobuidelegate.cpp
@@ -1,193 +1,193 @@
/* This file is part of the KDE libraries
Copyright (C) 2000 Stephan Kulow <coolo@kde.org>
David Faure <faure@kde.org>
Copyright (C) 2006 Kevin Ottens <ervin@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 "jobuidelegate.h"
#include <kdebug.h>
#include <kjob.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <ksharedconfig.h>
#include <QPointer>
#include <QWidget>
#include "kio/scheduler.h"
#if defined Q_WS_X11
#include <QX11Info>
#include <netwm.h>
#endif
class KIO::JobUiDelegate::Private
{
public:
};
KIO::JobUiDelegate::JobUiDelegate()
: d(new Private())
{
}
KIO::JobUiDelegate::~JobUiDelegate()
{
delete d;
}
void KIO::JobUiDelegate::setWindow(QWidget *window)
{
KDialogJobUiDelegate::setWindow(window);
KIO::Scheduler::registerWindow(window);
}
KIO::RenameDialog_Result KIO::JobUiDelegate::askFileRename(KJob * job,
const QString & caption,
const QString& src,
const QString & dest,
KIO::RenameDialog_Mode mode,
QString& newDest,
KIO::filesize_t sizeSrc,
KIO::filesize_t sizeDest,
time_t ctimeSrc,
time_t ctimeDest,
time_t mtimeSrc,
time_t mtimeDest)
{
Q_UNUSED(job);
//kDebug() << "job=" << job;
// We now do it in process, so that opening the rename dialog
// doesn't start uiserver for nothing if progressId=0 (e.g. F2 in konq)
KIO::RenameDialog dlg( window(), caption, src, dest, mode,
sizeSrc, sizeDest,
ctimeSrc, ctimeDest, mtimeSrc,
mtimeDest);
connect(job, SIGNAL(finished(KJob*)), &dlg, SLOT(reject())); // #192976
KIO::RenameDialog_Result res = static_cast<RenameDialog_Result>(dlg.exec());
if (res == R_AUTO_RENAME) {
newDest = dlg.autoDestUrl().path();
}
else {
newDest = dlg.newDestUrl().path();
}
return res;
}
KIO::SkipDialog_Result KIO::JobUiDelegate::askSkip(KJob *job,
bool multi,
const QString & error_text)
{
// We now do it in process. So this method is a useless wrapper around KIO::open_RenameDialog.
KIO::SkipDialog dlg( window(), multi, error_text );
connect(job, SIGNAL(finished(KJob*)), &dlg, SLOT(reject())); // #192976
return static_cast<KIO::SkipDialog_Result>(dlg.exec());
}
bool KIO::JobUiDelegate::askDeleteConfirmation(const KUrl::List& urls,
DeletionType deletionType,
ConfirmationType confirmationType)
{
QString keyName;
bool ask = ( confirmationType == ForceConfirmation );
if (!ask) {
KSharedConfigPtr kioConfig = KSharedConfig::openConfig("kiorc", KConfig::NoGlobals);
switch (deletionType ) {
case Delete:
keyName = "ConfirmDelete" ;
break;
case Trash:
keyName = "ConfirmTrash" ;
break;
case EmptyTrash:
keyName = "ConfirmEmptyTrash" ;
break;
}
// The default value for confirmations is true (for both delete and trash)
// If you change this, update kdebase/apps/konqueror/settings/konq/behaviour.cpp
const bool defaultValue = true;
ask = kioConfig->group("Confirmations").readEntry(keyName, defaultValue);
}
if (ask) {
QStringList prettyList;
Q_FOREACH(const KUrl& url, urls) {
- if ( url.protocol() == "trash" ) {
+ if ( url.scheme() == "trash" ) {
QString path = url.path();
// HACK (#98983): remove "0-foo". Note that it works better than
// displaying KFileItem::name(), for files under a subdir.
path.remove(QRegExp("^/[0-9]*-"));
prettyList.append(path);
} else {
prettyList.append(url.pathOrUrl());
}
}
QWidget* widget = window();
int result;
switch(deletionType) {
case Delete:
result = KMessageBox::warningContinueCancelList(
widget,
i18np("Do you really want to delete this item?", "Do you really want to delete these %1 items?", prettyList.count()),
prettyList,
i18n("Delete Files"),
KStandardGuiItem::del(),
KStandardGuiItem::cancel(),
keyName, KMessageBox::Notify);
break;
case EmptyTrash:
result = KMessageBox::warningContinueCancel(
widget,
i18nc("@info", "Do you want to permanently delete all items from Trash? This action cannot be undone."),
QString(),
KGuiItem(i18nc("@action:button", "Empty Trash"),
KIcon("user-trash")),
KStandardGuiItem::cancel(),
keyName, KMessageBox::Notify);
break;
case Trash:
default:
result = KMessageBox::warningContinueCancelList(
widget,
i18np("Do you really want to move this item to the trash?", "Do you really want to move these %1 items to the trash?", prettyList.count()),
prettyList,
i18n("Move to Trash"),
KGuiItem(i18nc("Verb", "&Trash"), "user-trash"),
KStandardGuiItem::cancel(),
keyName, KMessageBox::Notify);
}
if (!keyName.isEmpty()) {
// Check kmessagebox setting... erase & copy to konquerorrc.
KSharedConfig::Ptr config = KGlobal::config();
KConfigGroup notificationGroup(config, "Notification Messages");
if (!notificationGroup.readEntry(keyName, true)) {
notificationGroup.writeEntry(keyName, true);
notificationGroup.sync();
KSharedConfigPtr kioConfig = KSharedConfig::openConfig("kiorc", KConfig::NoGlobals);
kioConfig->group("Confirmations").writeEntry(keyName, false);
}
}
return (result == KMessageBox::Continue);
}
return true;
}
diff --git a/kio/kio/kdirlister.cpp b/kio/kio/kdirlister.cpp
index 027e3c8a7a..1762a052bd 100644
--- a/kio/kio/kdirlister.cpp
+++ b/kio/kio/kdirlister.cpp
@@ -1,2759 +1,2759 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
2000 Carsten Pfeiffer <pfeiffer@kde.org>
2003-2005 David Faure <faure@kde.org>
2001-2006 Michael Brade <brade@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 "kdirlister.h"
#include "kdirlister_p.h"
#include <QtCore/QRegExp>
#include <kdebug.h>
#include <kde_file.h>
#include <klocale.h>
#include <kio/job.h>
#include <kio/jobuidelegate.h>
#include <kmessagebox.h>
#include "kprotocolmanager.h"
#include "kmountpoint.h"
#include <QFile>
// Enable this to get printDebug() called often, to see the contents of the cache
//#define DEBUG_CACHE
// Make really sure it doesn't get activated in the final build
#ifdef NDEBUG
#undef DEBUG_CACHE
#endif
K_GLOBAL_STATIC(KDirListerCache, kDirListerCache)
KDirListerCache::KDirListerCache()
: itemsCached( 10 ) // keep the last 10 directories around
{
//kDebug(7004);
connect( &pendingUpdateTimer, SIGNAL(timeout()), this, SLOT(processPendingUpdates()) );
pendingUpdateTimer.setSingleShot( true );
connect( KDirWatch::self(), SIGNAL( dirty( const QString& ) ),
this, SLOT( slotFileDirty( const QString& ) ) );
connect( KDirWatch::self(), SIGNAL( created( const QString& ) ),
this, SLOT( slotFileCreated( const QString& ) ) );
connect( KDirWatch::self(), SIGNAL( deleted( const QString& ) ),
this, SLOT( slotFileDeleted( const QString& ) ) );
kdirnotify = new org::kde::KDirNotify(QString(), QString(), QDBusConnection::sessionBus(), this);
connect(kdirnotify, SIGNAL(FileRenamed(QString,QString)), SLOT(slotFileRenamed(QString,QString)));
connect(kdirnotify, SIGNAL(FilesAdded(QString)), SLOT(slotFilesAdded(QString)));
connect(kdirnotify, SIGNAL(FilesChanged(QStringList)), SLOT(slotFilesChanged(QStringList)));
connect(kdirnotify, SIGNAL(FilesRemoved(QStringList)), SLOT(slotFilesRemoved(QStringList)));
// The use of KUrl::url() in ~DirItem (sendSignal) crashes if the static for QRegExpEngine got deleted already,
// so we need to destroy the KDirListerCache before that.
qAddPostRoutine(kDirListerCache.destroy);
}
KDirListerCache::~KDirListerCache()
{
//kDebug(7004);
qDeleteAll(itemsInUse);
itemsInUse.clear();
itemsCached.clear();
directoryData.clear();
if ( KDirWatch::exists() )
KDirWatch::self()->disconnect( this );
}
// setting _reload to true will emit the old files and
// call updateDirectory
bool KDirListerCache::listDir( KDirLister *lister, const KUrl& _u,
bool _keep, bool _reload )
{
KUrl _url(_u);
_url.cleanPath(); // kill consecutive slashes
- if (!_url.host().isEmpty() && KProtocolInfo::protocolClass(_url.protocol()) == ":local"
- && _url.protocol() != "file") {
+ if (!_url.host().isEmpty() && KProtocolInfo::protocolClass(_url.scheme()) == ":local"
+ && _url.scheme() != "file") {
// ":local" protocols ignore the hostname, so strip it out preventively - #160057
// kio_file is special cased since it does honor the hostname (by redirecting to e.g. smb)
_url.setHost(QString());
if (_keep == false)
emit lister->redirection(_url);
}
// like this we don't have to worry about trailing slashes any further
_url.adjustPath(KUrl::RemoveTrailingSlash);
const QString urlStr = _url.url();
QString resolved;
if (_url.isLocalFile()) {
// Resolve symlinks (#213799)
const QString local = _url.toLocalFile();
resolved = QFileInfo(local).canonicalFilePath();
if (local != resolved)
canonicalUrls[resolved].append(urlStr);
// TODO: remove entry from canonicalUrls again in forgetDirs
// Note: this is why we use a QStringList value in there rather than a QSet:
// we can just remove one entry and not have to worry about other dirlisters
// (the non-unicity of the stringlist gives us the refcounting, basically).
}
if (!validUrl(lister, _url)) {
kDebug(7004) << lister << "url=" << _url << "not a valid url";
return false;
}
//kDebug(7004) << lister << "url=" << _url << "keep=" << _keep << "reload=" << _reload;
#ifdef DEBUG_CACHE
printDebug();
#endif
if (!_keep) {
// stop any running jobs for lister
stop(lister, true /*silent*/);
// clear our internal list for lister
forgetDirs(lister);
lister->d->rootFileItem = KFileItem();
} else if (lister->d->lstDirs.contains(_url)) {
// stop the job listing _url for this lister
stopListingUrl(lister, _url, true /*silent*/);
// remove the _url as well, it will be added in a couple of lines again!
// forgetDirs with three args does not do this
// TODO: think about moving this into forgetDirs
lister->d->lstDirs.removeAll(_url);
// clear _url for lister
forgetDirs(lister, _url, true);
if (lister->d->url == _url)
lister->d->rootFileItem = KFileItem();
}
lister->d->complete = false;
lister->d->lstDirs.append(_url);
if (lister->d->url.isEmpty() || !_keep) // set toplevel URL only if not set yet
lister->d->url = _url;
DirItem *itemU = itemsInUse.value(urlStr);
KDirListerCacheDirectoryData& dirData = directoryData[urlStr]; // find or insert
if (dirData.listersCurrentlyListing.isEmpty()) {
// if there is an update running for _url already we get into
// the following case - it will just be restarted by updateDirectory().
dirData.listersCurrentlyListing.append(lister);
DirItem *itemFromCache;
if (itemU || (!_reload && (itemFromCache = itemsCached.take(urlStr)) ) ) {
if (itemU) {
kDebug(7004) << "Entry already in use:" << _url;
// if _reload is set, then we'll emit cached items and then updateDirectory.
if (lister->d->autoUpdate)
itemU->incAutoUpdate();
} else {
kDebug(7004) << "Entry in cache:" << _url;
// In this code path, the itemsFromCache->decAutoUpdate + itemU->incAutoUpdate is optimized out
itemsInUse.insert(urlStr, itemFromCache);
itemU = itemFromCache;
}
emit lister->started(_url);
// List items from the cache in a delayed manner, just like things would happen
// if we were not using the cache.
new KDirLister::Private::CachedItemsJob(lister, _url, _reload);
} else {
// dir not in cache or _reload is true
if (_reload) {
kDebug(7004) << "Reloading directory:" << _url;
itemsCached.remove(urlStr);
} else {
kDebug(7004) << "Listing directory:" << _url;
}
itemU = new DirItem(_url, resolved);
itemsInUse.insert(urlStr, itemU);
if (lister->d->autoUpdate)
itemU->incAutoUpdate();
// // we have a limit of MAX_JOBS_PER_LISTER concurrently running jobs
// if ( lister->d->numJobs() >= MAX_JOBS_PER_LISTER )
// {
// pendingUpdates.insert( _url );
// }
// else
{
KIO::ListJob* job = KIO::listDir(_url, KIO::HideProgressInfo);
runningListJobs.insert(job, KIO::UDSEntryList());
lister->d->jobStarted(job);
lister->d->connectJob(job);
if (lister->d->window)
job->ui()->setWindow(lister->d->window);
connect(job, SIGNAL(entries(KIO::Job *, KIO::UDSEntryList)),
this, SLOT(slotEntries(KIO::Job *, KIO::UDSEntryList)));
connect(job, SIGNAL(result(KJob *)),
this, SLOT(slotResult(KJob *)));
connect(job, SIGNAL(redirection(KIO::Job *,KUrl)),
this, SLOT(slotRedirection(KIO::Job *,KUrl)));
emit lister->started(_url);
}
//kDebug(7004) << "Entry now being listed by" << dirData.listersCurrentlyListing;
}
} else {
kDebug(7004) << "Entry currently being listed:" << _url << "by" << dirData.listersCurrentlyListing;
#ifdef DEBUG_CACHE
printDebug();
#endif
emit lister->started( _url );
// Maybe listersCurrentlyListing/listersCurrentlyHolding should be QSets?
Q_ASSERT(!dirData.listersCurrentlyListing.contains(lister));
dirData.listersCurrentlyListing.append( lister );
KIO::ListJob *job = jobForUrl( urlStr );
// job will be 0 if we were listing from cache rather than listing from a kio job.
if( job ) {
lister->d->jobStarted( job );
lister->d->connectJob( job );
}
Q_ASSERT( itemU );
// List existing items in a delayed manner, just like things would happen
// if we were not using the cache.
if (!itemU->lstItems.isEmpty()) {
kDebug() << "Listing" << itemU->lstItems.count() << "cached items soon";
new KDirLister::Private::CachedItemsJob(lister, _url, _reload);
} else {
// The other lister hasn't emitted anything yet. Good, we'll just listen to it.
// One problem could be if we have _reload=true and the existing job doesn't, though.
}
#ifdef DEBUG_CACHE
printDebug();
#endif
}
return true;
}
KDirLister::Private::CachedItemsJob* KDirLister::Private::cachedItemsJobForUrl(const KUrl& url) const
{
Q_FOREACH(CachedItemsJob* job, m_cachedItemsJobs) {
if (job->url() == url)
return job;
}
return 0;
}
KDirLister::Private::CachedItemsJob::CachedItemsJob(KDirLister* lister, const KUrl& url, bool reload)
: KJob(lister),
m_lister(lister), m_url(url),
m_reload(reload), m_emitCompleted(true)
{
//kDebug() << "Creating CachedItemsJob" << this << "for lister" << lister << url;
if (lister->d->cachedItemsJobForUrl(url)) {
kWarning(7004) << "Lister" << lister << "has a cached items job already for" << url;
}
lister->d->m_cachedItemsJobs.append(this);
setAutoDelete(true);
start();
}
// Called by start() via QueuedConnection
void KDirLister::Private::CachedItemsJob::done()
{
if (!m_lister) // job was already killed, but waiting deletion due to deleteLater
return;
kDirListerCache->emitItemsFromCache(this, m_lister, m_url, m_reload, m_emitCompleted);
emitResult();
}
bool KDirLister::Private::CachedItemsJob::doKill()
{
//kDebug(7004) << this;
kDirListerCache->forgetCachedItemsJob(this, m_lister, m_url);
if (!property("_kdlc_silent").toBool()) {
emit m_lister->canceled(m_url);
emit m_lister->canceled();
}
m_lister = 0;
return true;
}
void KDirListerCache::emitItemsFromCache(KDirLister::Private::CachedItemsJob* cachedItemsJob, KDirLister* lister, const KUrl& _url, bool _reload, bool _emitCompleted)
{
const QString urlStr = _url.url();
KDirLister::Private* kdl = lister->d;
kdl->complete = false;
DirItem *itemU = kDirListerCache->itemsInUse.value(urlStr);
if (!itemU) {
kWarning(7004) << "Can't find item for directory" << urlStr << "anymore";
} else {
const KFileItemList items = itemU->lstItems;
const KFileItem rootItem = itemU->rootItem;
_reload = _reload || !itemU->complete;
if (kdl->rootFileItem.isNull() && !rootItem.isNull() && kdl->url == _url) {
kdl->rootFileItem = rootItem;
}
if (!items.isEmpty()) {
//kDebug(7004) << "emitting" << items.count() << "for lister" << lister;
kdl->addNewItems(_url, items);
kdl->emitItems();
}
}
forgetCachedItemsJob(cachedItemsJob, lister, _url);
// Emit completed, unless we were told not to,
// or if listDir() was called while another directory listing for this dir was happening,
// so we "joined" it. We detect that using jobForUrl to ensure it's a real ListJob,
// not just a lister-specific CachedItemsJob (which wouldn't emit completed for us).
if (_emitCompleted) {
kdl->complete = true;
emit lister->completed( _url );
emit lister->completed();
if ( _reload ) {
updateDirectory( _url );
}
}
}
void KDirListerCache::forgetCachedItemsJob(KDirLister::Private::CachedItemsJob* cachedItemsJob, KDirLister* lister, const KUrl& _url)
{
// Modifications to data structures only below this point;
// so that addNewItems is called with a consistent state
const QString urlStr = _url.url();
lister->d->m_cachedItemsJobs.removeAll(cachedItemsJob);
KDirListerCacheDirectoryData& dirData = directoryData[urlStr];
Q_ASSERT(dirData.listersCurrentlyListing.contains(lister));
KIO::ListJob *listJob = jobForUrl(urlStr);
if (!listJob) {
Q_ASSERT(!dirData.listersCurrentlyHolding.contains(lister));
//kDebug(7004) << "Moving from listing to holding, because no more job" << lister << urlStr;
dirData.listersCurrentlyHolding.append( lister );
dirData.listersCurrentlyListing.removeAll( lister );
} else {
//kDebug(7004) << "Still having a listjob" << listJob << ", so not moving to currently-holding.";
}
}
bool KDirListerCache::validUrl( const KDirLister *lister, const KUrl& url ) const
{
if ( !url.isValid() )
{
if ( lister->d->autoErrorHandling )
{
QString tmp = i18n("Malformed URL\n%1", url.prettyUrl() );
KMessageBox::error( lister->d->errorParent, tmp );
}
return false;
}
if ( !KProtocolManager::supportsListing( url ) )
{
if ( lister->d->autoErrorHandling )
{
QString tmp = i18n("URL cannot be listed\n%1", url.prettyUrl() );
KMessageBox::error( lister->d->errorParent, tmp );
}
return false;
}
return true;
}
void KDirListerCache::stop( KDirLister *lister, bool silent )
{
#ifdef DEBUG_CACHE
//printDebug();
#endif
//kDebug(7004) << "lister:" << lister << "silent=" << silent;
const KUrl::List urls = lister->d->lstDirs;
Q_FOREACH(const KUrl& url, urls) {
stopListingUrl(lister, url, silent);
}
#if 0 // test code
QHash<QString,KDirListerCacheDirectoryData>::iterator dirit = directoryData.begin();
const QHash<QString,KDirListerCacheDirectoryData>::iterator dirend = directoryData.end();
for( ; dirit != dirend ; ++dirit ) {
KDirListerCacheDirectoryData& dirData = dirit.value();
if (dirData.listersCurrentlyListing.contains(lister)) {
kDebug(7004) << "ERROR: found lister" << lister << "in list - for" << dirit.key();
Q_ASSERT(false);
}
}
#endif
}
void KDirListerCache::stopListingUrl(KDirLister *lister, const KUrl& _u, bool silent)
{
KUrl url(_u);
url.adjustPath( KUrl::RemoveTrailingSlash );
const QString urlStr = url.url();
KDirLister::Private::CachedItemsJob* cachedItemsJob = lister->d->cachedItemsJobForUrl(url);
if (cachedItemsJob) {
if (silent) {
cachedItemsJob->setProperty("_kdlc_silent", true);
}
cachedItemsJob->kill(); // removes job from list, too
}
// TODO: consider to stop all the "child jobs" of url as well
kDebug(7004) << lister << " url=" << url;
QHash<QString,KDirListerCacheDirectoryData>::iterator dirit = directoryData.find(urlStr);
if (dirit == directoryData.end())
return;
KDirListerCacheDirectoryData& dirData = dirit.value();
if (dirData.listersCurrentlyListing.contains(lister)) {
//kDebug(7004) << " found lister" << lister << "in list - for" << urlStr;
if (dirData.listersCurrentlyListing.count() == 1) {
// This was the only dirlister interested in the list job -> kill the job
stopListJob(urlStr, silent);
} else {
// Leave the job running for the other dirlisters, just unsubscribe us.
dirData.listersCurrentlyListing.removeAll(lister);
if (!silent) {
emit lister->canceled();
emit lister->canceled(url);
}
}
}
}
// Helper for stop() and stopListingUrl()
void KDirListerCache::stopListJob(const QString& url, bool silent)
{
// Old idea: if it's an update job, let's just leave the job running.
// After all, update jobs do run for "listersCurrentlyHolding",
// so there's no reason to kill them just because @p lister is now a holder.
// However it could be a long-running non-local job (e.g. filenamesearch), which
// the user wants to abort, and which will never be used for updating...
// And in any case slotEntries/slotResult is not meant to be called by update jobs.
// So, change of plan, let's kill it after all, in a way that triggers slotResult/slotUpdateResult.
KIO::ListJob *job = jobForUrl(url);
if (job) {
//kDebug() << "Killing list job" << job << "for" << url;
if (silent) {
job->setProperty("_kdlc_silent", true);
}
job->kill(KJob::EmitResult);
}
}
void KDirListerCache::setAutoUpdate( KDirLister *lister, bool enable )
{
// IMPORTANT: this method does not check for the current autoUpdate state!
for ( KUrl::List::const_iterator it = lister->d->lstDirs.constBegin();
it != lister->d->lstDirs.constEnd(); ++it ) {
DirItem* dirItem = itemsInUse.value((*it).url());
Q_ASSERT(dirItem);
if ( enable )
dirItem->incAutoUpdate();
else
dirItem->decAutoUpdate();
}
}
void KDirListerCache::forgetDirs( KDirLister *lister )
{
//kDebug(7004) << lister;
emit lister->clear();
// clear lister->d->lstDirs before calling forgetDirs(), so that
// it doesn't contain things that itemsInUse doesn't. When emitting
// the canceled signals, lstDirs must not contain anything that
// itemsInUse does not contain. (otherwise it might crash in findByName()).
const KUrl::List lstDirsCopy = lister->d->lstDirs;
lister->d->lstDirs.clear();
//kDebug() << "Iterating over dirs" << lstDirsCopy;
for ( KUrl::List::const_iterator it = lstDirsCopy.begin();
it != lstDirsCopy.end(); ++it ) {
forgetDirs( lister, *it, false );
}
}
static bool manually_mounted(const QString& path, const KMountPoint::List& possibleMountPoints)
{
KMountPoint::Ptr mp = possibleMountPoints.findByPath(path);
if (!mp) // not listed in fstab -> yes, manually mounted
return true;
const bool supermount = mp->mountType() == "supermount";
if (supermount) {
return true;
}
// noauto -> manually mounted. Otherwise, mounted at boot time, won't be unmounted any time soon hopefully.
return mp->mountOptions().contains("noauto");
}
void KDirListerCache::forgetDirs( KDirLister *lister, const KUrl& _url, bool notify )
{
//kDebug(7004) << lister << " _url: " << _url;
KUrl url( _url );
url.adjustPath( KUrl::RemoveTrailingSlash );
const QString urlStr = url.url();
DirectoryDataHash::iterator dit = directoryData.find(urlStr);
if (dit == directoryData.end())
return;
KDirListerCacheDirectoryData& dirData = *dit;
dirData.listersCurrentlyHolding.removeAll(lister);
// This lister doesn't care for updates running in <url> anymore
KIO::ListJob *job = jobForUrl(urlStr);
if (job)
lister->d->jobDone(job);
DirItem *item = itemsInUse.value(urlStr);
Q_ASSERT(item);
bool insertIntoCache = false;
if ( dirData.listersCurrentlyHolding.isEmpty() && dirData.listersCurrentlyListing.isEmpty() ) {
// item not in use anymore -> move into cache if complete
directoryData.erase(dit);
itemsInUse.remove( urlStr );
// this job is a running update which nobody cares about anymore
if ( job ) {
killJob( job );
kDebug(7004) << "Killing update job for " << urlStr;
// Well, the user of KDirLister doesn't really care that we're stopping
// a background-running job from a previous URL (in listDir) -> commented out.
// stop() already emitted canceled.
//emit lister->canceled( url );
if ( lister->d->numJobs() == 0 ) {
lister->d->complete = true;
//emit lister->canceled();
}
}
if ( notify ) {
lister->d->lstDirs.removeAll( url );
emit lister->clear( url );
}
insertIntoCache = item->complete;
if (insertIntoCache) {
// TODO(afiestas): remove use of KMountPoint+manually_mounted and port to Solid:
// 1) find Volume for the local path "item->url.toLocalFile()" (which could be anywhere
// under the mount point) -- probably needs a new operator in libsolid query parser
// 2) [**] becomes: if (Drive is hotpluggable or Volume is removable) "set to dirty" else "keep watch"
const KMountPoint::List possibleMountPoints = KMountPoint::possibleMountPoints(KMountPoint::NeedMountOptions);
// Should we forget the dir for good, or keep a watch on it?
// Generally keep a watch, except when it would prevent
// unmounting a removable device (#37780)
const bool isLocal = item->url.isLocalFile();
bool isManuallyMounted = false;
bool containsManuallyMounted = false;
if (isLocal) {
isManuallyMounted = manually_mounted( item->url.toLocalFile(), possibleMountPoints );
if ( !isManuallyMounted ) {
// Look for a manually-mounted directory inside
// If there's one, we can't keep a watch either, FAM would prevent unmounting the CDROM
// I hope this isn't too slow
KFileItemList::const_iterator kit = item->lstItems.constBegin();
KFileItemList::const_iterator kend = item->lstItems.constEnd();
for ( ; kit != kend && !containsManuallyMounted; ++kit )
if ( (*kit).isDir() && manually_mounted((*kit).url().toLocalFile(), possibleMountPoints) )
containsManuallyMounted = true;
}
}
if ( isManuallyMounted || containsManuallyMounted ) // [**]
{
kDebug(7004) << "Not adding a watch on " << item->url << " because it " <<
( isManuallyMounted ? "is manually mounted" : "contains a manually mounted subdir" );
item->complete = false; // set to "dirty"
}
else
item->incAutoUpdate(); // keep watch
}
else
{
delete item;
item = 0;
}
}
if ( item && lister->d->autoUpdate )
item->decAutoUpdate();
// Inserting into QCache must be done last, since it might delete the item
if (item && insertIntoCache) {
kDebug(7004) << lister << "item moved into cache:" << url;
itemsCached.insert(urlStr, item);
}
}
void KDirListerCache::updateDirectory( const KUrl& _dir )
{
kDebug(7004) << _dir;
QString urlStr = _dir.url(KUrl::RemoveTrailingSlash);
if ( !checkUpdate( urlStr ) )
return;
// A job can be running to
// - only list a new directory: the listers are in listersCurrentlyListing
// - only update a directory: the listers are in listersCurrentlyHolding
// - update a currently running listing: the listers are in both
KDirListerCacheDirectoryData& dirData = directoryData[urlStr];
QList<KDirLister *> listers = dirData.listersCurrentlyListing;
QList<KDirLister *> holders = dirData.listersCurrentlyHolding;
//kDebug(7004) << urlStr << "listers=" << listers << "holders=" << holders;
// restart the job for _dir if it is running already
bool killed = false;
QWidget *window = 0;
KIO::ListJob *job = jobForUrl( urlStr );
if (job) {
window = job->ui()->window();
killJob( job );
killed = true;
foreach ( KDirLister *kdl, listers )
kdl->d->jobDone( job );
foreach ( KDirLister *kdl, holders )
kdl->d->jobDone( job );
} else {
// Emit any cached items.
// updateDirectory() is about the diff compared to the cached items...
Q_FOREACH(KDirLister *kdl, listers) {
KDirLister::Private::CachedItemsJob* cachedItemsJob = kdl->d->cachedItemsJobForUrl(_dir);
if (cachedItemsJob) {
cachedItemsJob->setEmitCompleted(false);
cachedItemsJob->done(); // removes from cachedItemsJobs list
delete cachedItemsJob;
killed = true;
}
}
}
//kDebug(7004) << "Killed=" << killed;
// we don't need to emit canceled signals since we only replaced the job,
// the listing is continuing.
if (!(listers.isEmpty() || killed)) {
kWarning() << "The unexpected happened.";
kWarning() << "listers for" << _dir << "=" << listers;
kWarning() << "job=" << job;
Q_FOREACH(KDirLister *kdl, listers) {
kDebug() << "lister" << kdl << "m_cachedItemsJobs=" << kdl->d->m_cachedItemsJobs;
}
#ifndef NDEBUG
printDebug();
#endif
}
Q_ASSERT( listers.isEmpty() || killed );
job = KIO::listDir( _dir, KIO::HideProgressInfo );
runningListJobs.insert( job, KIO::UDSEntryList() );
connect( job, SIGNAL(entries( KIO::Job *, const KIO::UDSEntryList & )),
this, SLOT(slotUpdateEntries( KIO::Job *, const KIO::UDSEntryList & )) );
connect( job, SIGNAL(result( KJob * )),
this, SLOT(slotUpdateResult( KJob * )) );
kDebug(7004) << "update started in" << _dir;
foreach ( KDirLister *kdl, listers ) {
kdl->d->jobStarted( job );
}
if ( !holders.isEmpty() ) {
if ( !killed ) {
bool first = true;
foreach ( KDirLister *kdl, holders ) {
kdl->d->jobStarted( job );
if ( first && kdl->d->window ) {
first = false;
job->ui()->setWindow( kdl->d->window );
}
emit kdl->started( _dir );
}
} else {
job->ui()->setWindow( window );
foreach ( KDirLister *kdl, holders ) {
kdl->d->jobStarted( job );
}
}
}
}
bool KDirListerCache::checkUpdate( const QString& _dir )
{
if ( !itemsInUse.contains(_dir) )
{
DirItem *item = itemsCached[_dir];
if ( item && item->complete )
{
item->complete = false;
item->decAutoUpdate();
// Hmm, this debug output might include login/password from the _dir URL.
//kDebug(7004) << "directory " << _dir << " not in use, marked dirty.";
}
//else
//kDebug(7004) << "aborted, directory " << _dir << " not in cache.";
return false;
}
else
return true;
}
KFileItem KDirListerCache::itemForUrl( const KUrl& url ) const
{
KFileItem *item = findByUrl( 0, url );
if (item) {
return *item;
} else {
return KFileItem();
}
}
KDirListerCache::DirItem *KDirListerCache::dirItemForUrl(const KUrl& dir) const
{
const QString urlStr = dir.url(KUrl::RemoveTrailingSlash);
DirItem *item = itemsInUse.value(urlStr);
if ( !item )
item = itemsCached[urlStr];
return item;
}
KFileItemList *KDirListerCache::itemsForDir(const KUrl& dir) const
{
DirItem *item = dirItemForUrl(dir);
return item ? &item->lstItems : 0;
}
KFileItem KDirListerCache::findByName( const KDirLister *lister, const QString& _name ) const
{
Q_ASSERT(lister);
for (KUrl::List::const_iterator it = lister->d->lstDirs.constBegin();
it != lister->d->lstDirs.constEnd(); ++it) {
DirItem* dirItem = itemsInUse.value((*it).url());
Q_ASSERT(dirItem);
const KFileItem item = dirItem->lstItems.findByName(_name);
if (!item.isNull())
return item;
}
return KFileItem();
}
KFileItem *KDirListerCache::findByUrl( const KDirLister *lister, const KUrl& _u ) const
{
KUrl url(_u);
url.adjustPath(KUrl::RemoveTrailingSlash);
KUrl parentDir(url);
parentDir.setPath( parentDir.directory() );
DirItem* dirItem = dirItemForUrl(parentDir);
if (dirItem) {
// If lister is set, check that it contains this dir
if (!lister || lister->d->lstDirs.contains(parentDir)) {
KFileItemList::iterator it = dirItem->lstItems.begin();
const KFileItemList::iterator end = dirItem->lstItems.end();
for (; it != end ; ++it) {
if ((*it).url() == url) {
return &*it;
}
}
}
}
// Maybe _u is a directory itself? (see KDirModelTest::testChmodDirectory)
// We check this last, though, we prefer returning a kfileitem with an actual
// name if possible (and we make it '.' for root items later).
dirItem = dirItemForUrl(url);
if (dirItem && !dirItem->rootItem.isNull() && dirItem->rootItem.url() == url) {
// If lister is set, check that it contains this dir
if (!lister || lister->d->lstDirs.contains(url)) {
return &dirItem->rootItem;
}
}
return 0;
}
void KDirListerCache::slotFilesAdded( const QString &dir /*url*/ ) // from KDirNotify signals
{
KUrl urlDir(dir);
kDebug(7004) << urlDir; // output urls, not qstrings, since they might contain a password
if (urlDir.isLocalFile()) {
Q_FOREACH(const QString& u, directoriesForCanonicalPath(urlDir.toLocalFile())) {
updateDirectory(KUrl(u));
}
} else {
updateDirectory(urlDir);
}
}
void KDirListerCache::slotFilesRemoved( const QStringList &fileList ) // from KDirNotify signals
{
// TODO: handling of symlinks-to-directories isn't done here,
// because I'm not sure how to do it and keep the performance ok...
slotFilesRemoved(KUrl::List(fileList));
}
void KDirListerCache::slotFilesRemoved(const KUrl::List& fileList)
{
//kDebug(7004) << fileList.count();
// Group notifications by parent dirs (usually there would be only one parent dir)
QMap<QString, KFileItemList> removedItemsByDir;
KUrl::List deletedSubdirs;
for (KUrl::List::const_iterator it = fileList.begin(); it != fileList.end() ; ++it) {
const KUrl url(*it);
DirItem* dirItem = dirItemForUrl(url); // is it a listed directory?
if (dirItem) {
deletedSubdirs.append(url);
if (!dirItem->rootItem.isNull()) {
removedItemsByDir[url.url()].append(dirItem->rootItem);
}
}
KUrl parentDir(url);
parentDir.setPath(parentDir.directory());
dirItem = dirItemForUrl(parentDir);
if (!dirItem)
continue;
for (KFileItemList::iterator fit = dirItem->lstItems.begin(), fend = dirItem->lstItems.end(); fit != fend ; ++fit) {
if ((*fit).url() == url) {
const KFileItem fileitem = *fit;
removedItemsByDir[parentDir.url()].append(fileitem);
// If we found a fileitem, we can test if it's a dir. If not, we'll go to deleteDir just in case.
if (fileitem.isNull() || fileitem.isDir()) {
deletedSubdirs.append(url);
}
dirItem->lstItems.erase(fit); // remove fileitem from list
break;
}
}
}
QMap<QString, KFileItemList>::const_iterator rit = removedItemsByDir.constBegin();
for(; rit != removedItemsByDir.constEnd(); ++rit) {
// Tell the views about it before calling deleteDir.
// They might need the subdirs' file items (see the dirtree).
DirectoryDataHash::const_iterator dit = directoryData.constFind(rit.key());
if (dit != directoryData.constEnd()) {
itemsDeleted((*dit).listersCurrentlyHolding, rit.value());
}
}
Q_FOREACH(const KUrl& url, deletedSubdirs) {
// in case of a dir, check if we have any known children, there's much to do in that case
// (stopping jobs, removing dirs from cache etc.)
deleteDir(url);
}
}
void KDirListerCache::slotFilesChanged( const QStringList &fileList ) // from KDirNotify signals
{
//kDebug(7004) << fileList;
KUrl::List dirsToUpdate;
QStringList::const_iterator it = fileList.begin();
for (; it != fileList.end() ; ++it) {
KUrl url( *it );
KFileItem *fileitem = findByUrl(0, url);
if (!fileitem) {
kDebug(7004) << "item not found for" << url;
continue;
}
if (url.isLocalFile()) {
pendingUpdates.insert(*it); // delegate the work to processPendingUpdates
} else {
pendingRemoteUpdates.insert(fileitem);
// For remote files, we won't be able to figure out the new information,
// we have to do a update (directory listing)
KUrl dir(url);
dir.setPath(dir.directory());
if (!dirsToUpdate.contains(dir))
dirsToUpdate.prepend(dir);
}
}
KUrl::List::const_iterator itdir = dirsToUpdate.constBegin();
for (; itdir != dirsToUpdate.constEnd() ; ++itdir)
updateDirectory( *itdir );
// ## TODO problems with current jobs listing/updating that dir
// ( see kde-2.2.2's kdirlister )
processPendingUpdates();
}
void KDirListerCache::slotFileRenamed( const QString &_src, const QString &_dst ) // from KDirNotify signals
{
KUrl src( _src );
KUrl dst( _dst );
kDebug(7004) << src << "->" << dst;
#ifdef DEBUG_CACHE
printDebug();
#endif
KUrl oldurl(src);
oldurl.adjustPath( KUrl::RemoveTrailingSlash );
KFileItem *fileitem = findByUrl(0, oldurl);
if (!fileitem) {
kDebug(7004) << "Item not found:" << oldurl;
return;
}
const KFileItem oldItem = *fileitem;
// Dest already exists? Was overwritten then (testcase: #151851)
// We better emit it as deleted -before- doing the renaming, otherwise
// the "update" mechanism will emit the old one as deleted and
// kdirmodel will delete the new (renamed) one!
KFileItem* existingDestItem = findByUrl(0, dst);
if (existingDestItem) {
//kDebug() << dst << "already existed, let's delete it";
slotFilesRemoved(dst);
}
// If the item had a UDS_URL as well as UDS_NAME set, the user probably wants
// to be updating the name only (since they can't see the URL).
// Check to see if a URL exists, and if so, if only the file part has changed,
// only update the name and not the underlying URL.
bool nameOnly = !fileitem->entry().stringValue( KIO::UDSEntry::UDS_URL ).isEmpty();
nameOnly &= src.directory( KUrl::IgnoreTrailingSlash | KUrl::AppendTrailingSlash ) ==
dst.directory( KUrl::IgnoreTrailingSlash | KUrl::AppendTrailingSlash );
if (!nameOnly && fileitem->isDir()) {
renameDir( src, dst );
// #172945 - if the fileitem was the root item of a DirItem that was just removed from the cache,
// then it's a dangling pointer now...
fileitem = findByUrl(0, oldurl);
if (!fileitem) //deleted from cache altogether, #188807
return;
}
// Now update the KFileItem representing that file or dir (not exclusive with the above!)
if (!oldItem.isLocalFile() && !oldItem.localPath().isEmpty()) { // it uses UDS_LOCAL_PATH? ouch, needs an update then
slotFilesChanged( QStringList() << src.url() );
} else {
if( nameOnly )
fileitem->setName( dst.fileName() );
else
fileitem->setUrl( dst );
fileitem->refreshMimeType();
fileitem->determineMimeType();
QSet<KDirLister*> listers = emitRefreshItem( oldItem, *fileitem );
Q_FOREACH(KDirLister * kdl, listers) {
kdl->d->emitItems();
}
}
#ifdef DEBUG_CACHE
printDebug();
#endif
}
QSet<KDirLister*> KDirListerCache::emitRefreshItem(const KFileItem& oldItem, const KFileItem& fileitem)
{
//kDebug(7004) << "old:" << oldItem.name() << oldItem.url()
// << "new:" << fileitem.name() << fileitem.url();
// Look whether this item was shown in any view, i.e. held by any dirlister
KUrl parentDir( oldItem.url() );
parentDir.setPath( parentDir.directory() );
const QString parentDirURL = parentDir.url();
DirectoryDataHash::iterator dit = directoryData.find(parentDirURL);
QList<KDirLister *> listers;
// Also look in listersCurrentlyListing, in case the user manages to rename during a listing
if (dit != directoryData.end())
listers += (*dit).listersCurrentlyHolding + (*dit).listersCurrentlyListing;
if (oldItem.isDir()) {
// For a directory, look for dirlisters where it's the root item.
dit = directoryData.find(oldItem.url().url());
if (dit != directoryData.end())
listers += (*dit).listersCurrentlyHolding + (*dit).listersCurrentlyListing;
}
QSet<KDirLister*> listersToRefresh;
Q_FOREACH(KDirLister *kdl, listers) {
// For a directory, look for dirlisters where it's the root item.
KUrl directoryUrl(oldItem.url());
if (oldItem.isDir() && kdl->d->rootFileItem == oldItem) {
const KFileItem oldRootItem = kdl->d->rootFileItem;
kdl->d->rootFileItem = fileitem;
kdl->d->addRefreshItem(directoryUrl, oldRootItem, fileitem);
} else {
directoryUrl.setPath(directoryUrl.directory());
kdl->d->addRefreshItem(directoryUrl, oldItem, fileitem);
}
listersToRefresh.insert(kdl);
}
return listersToRefresh;
}
QStringList KDirListerCache::directoriesForCanonicalPath(const QString& dir) const
{
QStringList dirs;
dirs << dir;
dirs << canonicalUrls.value(dir).toSet().toList(); /* make unique; there are faster ways, but this is really small anyway */
if (dirs.count() > 1)
kDebug() << dir << "known as" << dirs;
return dirs;
}
// private slots
// Called by KDirWatch - usually when a dir we're watching has been modified,
// but it can also be called for a file.
void KDirListerCache::slotFileDirty( const QString& path )
{
kDebug(7004) << path;
// File or dir?
KDE_struct_stat buff;
if ( KDE::stat( path, &buff ) != 0 )
return; // error
const bool isDir = S_ISDIR(buff.st_mode);
KUrl url(path);
url.adjustPath(KUrl::RemoveTrailingSlash);
if (isDir) {
Q_FOREACH(const QString& dir, directoriesForCanonicalPath(url.toLocalFile())) {
handleDirDirty(dir);
}
} else {
Q_FOREACH(const QString& dir, directoriesForCanonicalPath(url.directory())) {
KUrl aliasUrl(dir);
aliasUrl.addPath(url.fileName());
handleFileDirty(aliasUrl);
}
}
}
// Called by slotFileDirty
void KDirListerCache::handleDirDirty(const KUrl& url)
{
// A dir: launch an update job if anyone cares about it
// This also means we can forget about pending updates to individual files in that dir
const QString dirPath = url.toLocalFile(KUrl::AddTrailingSlash);
QMutableSetIterator<QString> pendingIt(pendingUpdates);
while (pendingIt.hasNext()) {
const QString updPath = pendingIt.next();
//kDebug(7004) << "had pending update" << updPath;
if (updPath.startsWith(dirPath) &&
updPath.indexOf('/', dirPath.length()) == -1) { // direct child item
kDebug(7004) << "forgetting about individual update to" << updPath;
pendingIt.remove();
}
}
updateDirectory(url);
}
// Called by slotFileDirty
void KDirListerCache::handleFileDirty(const KUrl& url)
{
// A file: do we know about it already?
KFileItem* existingItem = findByUrl(0, url);
if (!existingItem) {
// No - update the parent dir then
KUrl dir(url);
dir.setPath(url.directory());
updateDirectory(dir);
} else {
// A known file: delay updating it, FAM is flooding us with events
const QString filePath = url.toLocalFile();
if (!pendingUpdates.contains(filePath)) {
KUrl dir(url);
dir.setPath(dir.directory());
if (checkUpdate(dir.url())) {
pendingUpdates.insert(filePath);
if (!pendingUpdateTimer.isActive())
pendingUpdateTimer.start(500);
}
}
}
}
void KDirListerCache::slotFileCreated( const QString& path ) // from KDirWatch
{
kDebug(7004) << path;
// XXX: how to avoid a complete rescan here?
// We'd need to stat that one file separately and refresh the item(s) for it.
KUrl fileUrl(path);
slotFilesAdded(fileUrl.directory());
}
void KDirListerCache::slotFileDeleted( const QString& path ) // from KDirWatch
{
kDebug(7004) << path;
KUrl u( path );
QStringList fileUrls;
Q_FOREACH(KUrl url, directoriesForCanonicalPath(u.directory())) {
url.addPath(u.fileName());
fileUrls << url.url();
}
slotFilesRemoved(fileUrls);
}
void KDirListerCache::slotEntries( KIO::Job *job, const KIO::UDSEntryList &entries )
{
KUrl url(joburl( static_cast<KIO::ListJob *>(job) ));
url.adjustPath(KUrl::RemoveTrailingSlash);
QString urlStr = url.url();
//kDebug(7004) << "new entries for " << url;
DirItem *dir = itemsInUse.value(urlStr);
if (!dir) {
kError(7004) << "Internal error: job is listing" << url << "but itemsInUse only knows about" << itemsInUse.keys();
Q_ASSERT( dir );
return;
}
DirectoryDataHash::iterator dit = directoryData.find(urlStr);
if (dit == directoryData.end()) {
kError(7004) << "Internal error: job is listing" << url << "but directoryData doesn't know about that url, only about:" << directoryData.keys();
Q_ASSERT(dit != directoryData.end());
return;
}
KDirListerCacheDirectoryData& dirData = *dit;
if (dirData.listersCurrentlyListing.isEmpty()) {
kError(7004) << "Internal error: job is listing" << url << "but directoryData says no listers are currently listing " << urlStr;
#ifndef NDEBUG
printDebug();
#endif
Q_ASSERT( !dirData.listersCurrentlyListing.isEmpty() );
return;
}
// check if anyone wants the mimetypes immediately
bool delayedMimeTypes = true;
foreach ( KDirLister *kdl, dirData.listersCurrentlyListing )
delayedMimeTypes &= kdl->d->delayedMimeTypes;
KIO::UDSEntryList::const_iterator it = entries.begin();
const KIO::UDSEntryList::const_iterator end = entries.end();
for ( ; it != end; ++it )
{
const QString name = (*it).stringValue( KIO::UDSEntry::UDS_NAME );
Q_ASSERT( !name.isEmpty() );
if ( name.isEmpty() )
continue;
if ( name == "." )
{
Q_ASSERT( dir->rootItem.isNull() );
// Try to reuse an existing KFileItem (if we listed the parent dir)
// rather than creating a new one. There are many reasons:
// 1) renames and permission changes to the item would have to emit the signals
// twice, otherwise, so that both views manage to recognize the item.
// 2) with kio_ftp we can only know that something is a symlink when
// listing the parent, so prefer that item, which has more info.
// Note that it gives a funky name() to the root item, rather than "." ;)
dir->rootItem = itemForUrl(url);
if (dir->rootItem.isNull())
dir->rootItem = KFileItem( *it, url, delayedMimeTypes, true );
foreach ( KDirLister *kdl, dirData.listersCurrentlyListing )
if ( kdl->d->rootFileItem.isNull() && kdl->d->url == url )
kdl->d->rootFileItem = dir->rootItem;
}
else if ( name != ".." )
{
KFileItem item( *it, url, delayedMimeTypes, true );
//kDebug(7004)<< "Adding item: " << item.url();
dir->lstItems.append( item );
foreach ( KDirLister *kdl, dirData.listersCurrentlyListing )
kdl->d->addNewItem(url, item);
}
}
foreach ( KDirLister *kdl, dirData.listersCurrentlyListing )
kdl->d->emitItems();
}
void KDirListerCache::slotResult( KJob *j )
{
#ifdef DEBUG_CACHE
//printDebug();
#endif
Q_ASSERT( j );
KIO::ListJob *job = static_cast<KIO::ListJob *>( j );
runningListJobs.remove( job );
KUrl jobUrl(joburl( job ));
jobUrl.adjustPath(KUrl::RemoveTrailingSlash); // need remove trailing slashes again, in case of redirections
QString jobUrlStr = jobUrl.url();
kDebug(7004) << "finished listing" << jobUrl;
DirectoryDataHash::iterator dit = directoryData.find(jobUrlStr);
if (dit == directoryData.end()) {
kError() << "Nothing found in directoryData for URL" << jobUrlStr;
#ifndef NDEBUG
printDebug();
#endif
Q_ASSERT(dit != directoryData.end());
return;
}
KDirListerCacheDirectoryData& dirData = *dit;
if ( dirData.listersCurrentlyListing.isEmpty() ) {
kError() << "OOOOPS, nothing in directoryData.listersCurrentlyListing for" << jobUrlStr;
// We're about to assert; dump the current state...
#ifndef NDEBUG
printDebug();
#endif
Q_ASSERT( !dirData.listersCurrentlyListing.isEmpty() );
}
QList<KDirLister *> listers = dirData.listersCurrentlyListing;
// move all listers to the holding list, do it before emitting
// the signals to make sure it exists in KDirListerCache in case someone
// calls listDir during the signal emission
Q_ASSERT( dirData.listersCurrentlyHolding.isEmpty() );
dirData.moveListersWithoutCachedItemsJob(jobUrl);
if ( job->error() )
{
foreach ( KDirLister *kdl, listers )
{
kdl->d->jobDone( job );
if (job->error() != KJob::KilledJobError) {
kdl->handleError( job );
}
const bool silent = job->property("_kdlc_silent").toBool();
if (!silent) {
emit kdl->canceled( jobUrl );
}
if (kdl->d->numJobs() == 0) {
kdl->d->complete = true;
if (!silent) {
emit kdl->canceled();
}
}
}
}
else
{
DirItem *dir = itemsInUse.value(jobUrlStr);
Q_ASSERT( dir );
dir->complete = true;
foreach ( KDirLister* kdl, listers )
{
kdl->d->jobDone( job );
emit kdl->completed( jobUrl );
if ( kdl->d->numJobs() == 0 )
{
kdl->d->complete = true;
emit kdl->completed();
}
}
}
// TODO: hmm, if there was an error and job is a parent of one or more
// of the pending urls we should cancel it/them as well
processPendingUpdates();
#ifdef DEBUG_CACHE
printDebug();
#endif
}
void KDirListerCache::slotRedirection( KIO::Job *j, const KUrl& url )
{
Q_ASSERT( j );
KIO::ListJob *job = static_cast<KIO::ListJob *>( j );
KUrl oldUrl(job->url()); // here we really need the old url!
KUrl newUrl(url);
// strip trailing slashes
oldUrl.adjustPath(KUrl::RemoveTrailingSlash);
newUrl.adjustPath(KUrl::RemoveTrailingSlash);
if ( oldUrl == newUrl ) {
kDebug(7004) << "New redirection url same as old, giving up.";
return;
} else if (newUrl.isEmpty()) {
kDebug(7004) << "New redirection url is empty, giving up.";
return;
}
const QString oldUrlStr = oldUrl.url();
const QString newUrlStr = newUrl.url();
kDebug(7004) << oldUrl << "->" << newUrl;
#ifdef DEBUG_CACHE
// Can't do that here. KDirListerCache::joburl() will use the new url already,
// while our data structures haven't been updated yet -> assert fail.
//printDebug();
#endif
// I don't think there can be dirItems that are children of oldUrl.
// Am I wrong here? And even if so, we don't need to delete them, right?
// DF: redirection happens before listDir emits any item. Makes little sense otherwise.
// oldUrl cannot be in itemsCached because only completed items are moved there
DirItem *dir = itemsInUse.take(oldUrlStr);
Q_ASSERT( dir );
DirectoryDataHash::iterator dit = directoryData.find(oldUrlStr);
Q_ASSERT(dit != directoryData.end());
KDirListerCacheDirectoryData oldDirData = *dit;
directoryData.erase(dit);
Q_ASSERT( !oldDirData.listersCurrentlyListing.isEmpty() );
const QList<KDirLister *> listers = oldDirData.listersCurrentlyListing;
Q_ASSERT( !listers.isEmpty() );
foreach ( KDirLister *kdl, listers ) {
kdl->d->redirect(oldUrlStr, newUrl, false /*clear items*/);
}
// when a lister was stopped before the job emits the redirection signal, the old url will
// also be in listersCurrentlyHolding
const QList<KDirLister *> holders = oldDirData.listersCurrentlyHolding;
foreach ( KDirLister *kdl, holders ) {
kdl->d->jobStarted( job );
// do it like when starting a new list-job that will redirect later
// TODO: maybe don't emit started if there's an update running for newUrl already?
emit kdl->started( oldUrl );
kdl->d->redirect(oldUrl, newUrl, false /*clear items*/);
}
DirItem *newDir = itemsInUse.value(newUrlStr);
if ( newDir ) {
kDebug(7004) << newUrl << "already in use";
// only in this case there can newUrl already be in listersCurrentlyListing or listersCurrentlyHolding
delete dir;
// get the job if one's running for newUrl already (can be a list-job or an update-job), but
// do not return this 'job', which would happen because of the use of redirectionURL()
KIO::ListJob *oldJob = jobForUrl( newUrlStr, job );
// listers of newUrl with oldJob: forget about the oldJob and use the already running one
// which will be converted to an updateJob
KDirListerCacheDirectoryData& newDirData = directoryData[newUrlStr];
QList<KDirLister *>& curListers = newDirData.listersCurrentlyListing;
if ( !curListers.isEmpty() ) {
kDebug(7004) << "and it is currently listed";
Q_ASSERT( oldJob ); // ?!
foreach ( KDirLister *kdl, curListers ) { // listers of newUrl
kdl->d->jobDone( oldJob );
kdl->d->jobStarted( job );
kdl->d->connectJob( job );
}
// append listers of oldUrl with newJob to listers of newUrl with oldJob
foreach ( KDirLister *kdl, listers )
curListers.append( kdl );
} else {
curListers = listers;
}
if ( oldJob ) // kill the old job, be it a list-job or an update-job
killJob( oldJob );
// holders of newUrl: use the already running job which will be converted to an updateJob
QList<KDirLister *>& curHolders = newDirData.listersCurrentlyHolding;
if ( !curHolders.isEmpty() ) {
kDebug(7004) << "and it is currently held.";
foreach ( KDirLister *kdl, curHolders ) { // holders of newUrl
kdl->d->jobStarted( job );
emit kdl->started( newUrl );
}
// append holders of oldUrl to holders of newUrl
foreach ( KDirLister *kdl, holders )
curHolders.append( kdl );
} else {
curHolders = holders;
}
// emit old items: listers, holders. NOT: newUrlListers/newUrlHolders, they already have them listed
// TODO: make this a separate method?
foreach ( KDirLister *kdl, listers + holders ) {
if ( kdl->d->rootFileItem.isNull() && kdl->d->url == newUrl )
kdl->d->rootFileItem = newDir->rootItem;
kdl->d->addNewItems(newUrl, newDir->lstItems);
kdl->d->emitItems();
}
} else if ( (newDir = itemsCached.take( newUrlStr )) ) {
kDebug(7004) << newUrl << "is unused, but already in the cache.";
delete dir;
itemsInUse.insert( newUrlStr, newDir );
KDirListerCacheDirectoryData& newDirData = directoryData[newUrlStr];
newDirData.listersCurrentlyListing = listers;
newDirData.listersCurrentlyHolding = holders;
// emit old items: listers, holders
foreach ( KDirLister *kdl, listers + holders ) {
if ( kdl->d->rootFileItem.isNull() && kdl->d->url == newUrl )
kdl->d->rootFileItem = newDir->rootItem;
kdl->d->addNewItems(newUrl, newDir->lstItems);
kdl->d->emitItems();
}
} else {
kDebug(7004) << newUrl << "has not been listed yet.";
dir->rootItem = KFileItem();
dir->lstItems.clear();
dir->redirect( newUrl );
itemsInUse.insert( newUrlStr, dir );
KDirListerCacheDirectoryData& newDirData = directoryData[newUrlStr];
newDirData.listersCurrentlyListing = listers;
newDirData.listersCurrentlyHolding = holders;
if ( holders.isEmpty() ) {
#ifdef DEBUG_CACHE
printDebug();
#endif
return; // only in this case the job doesn't need to be converted,
}
}
// make the job an update job
job->disconnect( this );
connect( job, SIGNAL(entries( KIO::Job *, const KIO::UDSEntryList & )),
this, SLOT(slotUpdateEntries( KIO::Job *, const KIO::UDSEntryList & )) );
connect( job, SIGNAL(result( KJob * )),
this, SLOT(slotUpdateResult( KJob * )) );
// FIXME: autoUpdate-Counts!!
#ifdef DEBUG_CACHE
printDebug();
#endif
}
struct KDirListerCache::ItemInUseChange
{
ItemInUseChange(const QString& old, const QString& newU, DirItem* di)
: oldUrl(old), newUrl(newU), dirItem(di) {}
QString oldUrl;
QString newUrl;
DirItem* dirItem;
};
void KDirListerCache::renameDir( const KUrl &oldUrl, const KUrl &newUrl )
{
kDebug(7004) << oldUrl << "->" << newUrl;
const QString oldUrlStr = oldUrl.url(KUrl::RemoveTrailingSlash);
const QString newUrlStr = newUrl.url(KUrl::RemoveTrailingSlash);
// Not enough. Also need to look at any child dir, even sub-sub-sub-dir.
//DirItem *dir = itemsInUse.take( oldUrlStr );
//emitRedirections( oldUrl, url );
QLinkedList<ItemInUseChange> itemsToChange;
QSet<KDirLister *> listers;
// Look at all dirs being listed/shown
QHash<QString, DirItem *>::iterator itu = itemsInUse.begin();
const QHash<QString, DirItem *>::iterator ituend = itemsInUse.end();
for (; itu != ituend ; ++itu) {
DirItem *dir = itu.value();
KUrl oldDirUrl ( itu.key() );
//kDebug(7004) << "itemInUse:" << oldDirUrl;
// Check if this dir is oldUrl, or a subfolder of it
if ( oldUrl.isParentOf( oldDirUrl ) ) {
// TODO should use KUrl::cleanpath like isParentOf does
QString relPath = oldDirUrl.path().mid( oldUrl.path().length() );
KUrl newDirUrl( newUrl ); // take new base
if ( !relPath.isEmpty() )
newDirUrl.addPath( relPath ); // add unchanged relative path
//kDebug(7004) << "new url=" << newDirUrl;
// Update URL in dir item and in itemsInUse
dir->redirect( newDirUrl );
itemsToChange.append(ItemInUseChange(oldDirUrl.url(KUrl::RemoveTrailingSlash),
newDirUrl.url(KUrl::RemoveTrailingSlash),
dir));
// Rename all items under that dir
for ( KFileItemList::iterator kit = dir->lstItems.begin(), kend = dir->lstItems.end();
kit != kend ; ++kit )
{
const KFileItem oldItem = *kit;
const KUrl oldItemUrl ((*kit).url());
const QString oldItemUrlStr( oldItemUrl.url(KUrl::RemoveTrailingSlash) );
KUrl newItemUrl( oldItemUrl );
newItemUrl.setPath( newDirUrl.path() );
newItemUrl.addPath( oldItemUrl.fileName() );
kDebug(7004) << "renaming" << oldItemUrl << "to" << newItemUrl;
(*kit).setUrl(newItemUrl);
listers |= emitRefreshItem(oldItem, *kit);
}
emitRedirections( oldDirUrl, newDirUrl );
}
}
Q_FOREACH(KDirLister * kdl, listers) {
kdl->d->emitItems();
}
// Do the changes to itemsInUse out of the loop to avoid messing up iterators,
// and so that emitRefreshItem can find the stuff in the hash.
foreach(const ItemInUseChange& i, itemsToChange) {
itemsInUse.remove(i.oldUrl);
itemsInUse.insert(i.newUrl, i.dirItem);
}
// Is oldUrl a directory in the cache?
// Remove any child of oldUrl from the cache - even if the renamed dir itself isn't in it!
removeDirFromCache( oldUrl );
// TODO rename, instead.
}
// helper for renameDir, not used for redirections from KIO::listDir().
void KDirListerCache::emitRedirections( const KUrl &oldUrl, const KUrl &newUrl )
{
kDebug(7004) << oldUrl << "->" << newUrl;
const QString oldUrlStr = oldUrl.url(KUrl::RemoveTrailingSlash);
const QString newUrlStr = newUrl.url(KUrl::RemoveTrailingSlash);
KIO::ListJob *job = jobForUrl( oldUrlStr );
if ( job )
killJob( job );
// Check if we were listing this dir. Need to abort and restart with new name in that case.
DirectoryDataHash::iterator dit = directoryData.find(oldUrlStr);
if ( dit == directoryData.end() )
return;
const QList<KDirLister *> listers = (*dit).listersCurrentlyListing;
const QList<KDirLister *> holders = (*dit).listersCurrentlyHolding;
KDirListerCacheDirectoryData& newDirData = directoryData[newUrlStr];
// Tell the world that the job listing the old url is dead.
foreach ( KDirLister *kdl, listers ) {
if ( job )
kdl->d->jobDone( job );
emit kdl->canceled( oldUrl );
}
newDirData.listersCurrentlyListing += listers;
// Check if we are currently displaying this directory (odds opposite wrt above)
foreach ( KDirLister *kdl, holders ) {
if ( job )
kdl->d->jobDone( job );
}
newDirData.listersCurrentlyHolding += holders;
directoryData.erase(dit);
if ( !listers.isEmpty() ) {
updateDirectory( newUrl );
// Tell the world about the new url
foreach ( KDirLister *kdl, listers )
emit kdl->started( newUrl );
}
// And notify the dirlisters of the redirection
foreach ( KDirLister *kdl, holders ) {
kdl->d->redirect(oldUrl, newUrl, true /*keep items*/);
}
}
void KDirListerCache::removeDirFromCache( const KUrl& dir )
{
kDebug(7004) << dir;
const QList<QString> cachedDirs = itemsCached.keys(); // seems slow, but there's no qcache iterator...
foreach(const QString& cachedDir, cachedDirs) {
if ( dir.isParentOf( KUrl( cachedDir ) ) )
itemsCached.remove( cachedDir );
}
}
void KDirListerCache::slotUpdateEntries( KIO::Job* job, const KIO::UDSEntryList& list )
{
runningListJobs[static_cast<KIO::ListJob*>(job)] += list;
}
void KDirListerCache::slotUpdateResult( KJob * j )
{
Q_ASSERT( j );
KIO::ListJob *job = static_cast<KIO::ListJob *>( j );
KUrl jobUrl (joburl( job ));
jobUrl.adjustPath(KUrl::RemoveTrailingSlash); // need remove trailing slashes again, in case of redirections
QString jobUrlStr (jobUrl.url());
kDebug(7004) << "finished update" << jobUrl;
KDirListerCacheDirectoryData& dirData = directoryData[jobUrlStr];
// Collect the dirlisters which were listing the URL using that ListJob
// plus those that were already holding that URL - they all get updated.
dirData.moveListersWithoutCachedItemsJob(jobUrl);
QList<KDirLister *> listers = dirData.listersCurrentlyHolding;
listers += dirData.listersCurrentlyListing;
// once we are updating dirs that are only in the cache this will fail!
Q_ASSERT( !listers.isEmpty() );
if ( job->error() ) {
foreach ( KDirLister* kdl, listers ) {
kdl->d->jobDone( job );
//don't bother the user
//kdl->handleError( job );
const bool silent = job->property("_kdlc_silent").toBool();
if (!silent) {
emit kdl->canceled( jobUrl );
}
if ( kdl->d->numJobs() == 0 ) {
kdl->d->complete = true;
if (!silent) {
emit kdl->canceled();
}
}
}
runningListJobs.remove( job );
// TODO: if job is a parent of one or more
// of the pending urls we should cancel them
processPendingUpdates();
return;
}
DirItem *dir = itemsInUse.value(jobUrlStr, 0);
if (!dir) {
kError(7004) << "Internal error: itemsInUse did not contain" << jobUrlStr;
#ifndef NDEBUG
printDebug();
#endif
Q_ASSERT(dir);
} else {
dir->complete = true;
}
// check if anyone wants the mimetypes immediately
bool delayedMimeTypes = true;
foreach ( KDirLister *kdl, listers )
delayedMimeTypes &= kdl->d->delayedMimeTypes;
QHash<QString, KFileItem*> fileItems; // fileName -> KFileItem*
// Unmark all items in url
for ( KFileItemList::iterator kit = dir->lstItems.begin(), kend = dir->lstItems.end() ; kit != kend ; ++kit )
{
(*kit).unmark();
fileItems.insert( (*kit).name(), &*kit );
}
const KIO::UDSEntryList& buf = runningListJobs.value( job );
KIO::UDSEntryList::const_iterator it = buf.constBegin();
const KIO::UDSEntryList::const_iterator end = buf.constEnd();
for ( ; it != end; ++it )
{
// Form the complete url
KFileItem item( *it, jobUrl, delayedMimeTypes, true );
const QString name = item.name();
Q_ASSERT( !name.isEmpty() );
// we duplicate the check for dotdot here, to avoid iterating over
// all items again and checking in matchesFilter() that way.
if ( name.isEmpty() || name == ".." )
continue;
if ( name == "." )
{
// if the update was started before finishing the original listing
// there is no root item yet
if ( dir->rootItem.isNull() )
{
dir->rootItem = item;
foreach ( KDirLister *kdl, listers )
if ( kdl->d->rootFileItem.isNull() && kdl->d->url == jobUrl )
kdl->d->rootFileItem = dir->rootItem;
}
continue;
}
// Find this item
if (KFileItem* tmp = fileItems.value(item.name()))
{
QSet<KFileItem*>::iterator pru_it = pendingRemoteUpdates.find(tmp);
const bool inPendingRemoteUpdates = (pru_it != pendingRemoteUpdates.end());
// check if something changed for this file, using KFileItem::cmp()
if (!tmp->cmp( item ) || inPendingRemoteUpdates) {
if (inPendingRemoteUpdates) {
pendingRemoteUpdates.erase(pru_it);
}
//kDebug(7004) << "file changed:" << tmp->name();
const KFileItem oldItem = *tmp;
*tmp = item;
foreach ( KDirLister *kdl, listers )
kdl->d->addRefreshItem(jobUrl, oldItem, *tmp);
}
//kDebug(7004) << "marking" << tmp;
tmp->mark();
}
else // this is a new file
{
//kDebug(7004) << "new file:" << name;
KFileItem pitem(item);
pitem.mark();
dir->lstItems.append( pitem );
foreach ( KDirLister *kdl, listers )
kdl->d->addNewItem(jobUrl, pitem);
}
}
runningListJobs.remove( job );
deleteUnmarkedItems( listers, dir->lstItems );
foreach ( KDirLister *kdl, listers ) {
kdl->d->emitItems();
kdl->d->jobDone( job );
emit kdl->completed( jobUrl );
if ( kdl->d->numJobs() == 0 )
{
kdl->d->complete = true;
emit kdl->completed();
}
}
// TODO: hmm, if there was an error and job is a parent of one or more
// of the pending urls we should cancel it/them as well
processPendingUpdates();
}
// private
KIO::ListJob *KDirListerCache::jobForUrl( const QString& url, KIO::ListJob *not_job )
{
QMap< KIO::ListJob *, KIO::UDSEntryList >::const_iterator it = runningListJobs.constBegin();
while ( it != runningListJobs.constEnd() )
{
KIO::ListJob *job = it.key();
if ( joburl( job ).url(KUrl::RemoveTrailingSlash) == url && job != not_job )
return job;
++it;
}
return 0;
}
const KUrl& KDirListerCache::joburl( KIO::ListJob *job )
{
if ( job->redirectionUrl().isValid() )
return job->redirectionUrl();
else
return job->url();
}
void KDirListerCache::killJob( KIO::ListJob *job )
{
runningListJobs.remove( job );
job->disconnect( this );
job->kill();
}
void KDirListerCache::deleteUnmarkedItems( const QList<KDirLister *>& listers, KFileItemList &lstItems )
{
KFileItemList deletedItems;
// Find all unmarked items and delete them
QMutableListIterator<KFileItem> kit(lstItems);
while (kit.hasNext()) {
const KFileItem& item = kit.next();
if (!item.isMarked()) {
//kDebug(7004) << "deleted:" << item.name() << &item;
deletedItems.append(item);
kit.remove();
}
}
if (!deletedItems.isEmpty())
itemsDeleted(listers, deletedItems);
}
void KDirListerCache::itemsDeleted(const QList<KDirLister *>& listers, const KFileItemList& deletedItems)
{
Q_FOREACH(KDirLister *kdl, listers) {
kdl->d->emitItemsDeleted(deletedItems);
}
Q_FOREACH(const KFileItem& item, deletedItems) {
if (item.isDir())
deleteDir(item.url());
}
}
void KDirListerCache::deleteDir( const KUrl& dirUrl )
{
//kDebug() << dirUrl;
// unregister and remove the children of the deleted item.
// Idea: tell all the KDirListers that they should forget the dir
// and then remove it from the cache.
// Separate itemsInUse iteration and calls to forgetDirs (which modify itemsInUse)
KUrl::List affectedItems;
QHash<QString, DirItem *>::iterator itu = itemsInUse.begin();
const QHash<QString, DirItem *>::iterator ituend = itemsInUse.end();
for ( ; itu != ituend; ++itu ) {
const KUrl deletedUrl( itu.key() );
if ( dirUrl.isParentOf( deletedUrl ) ) {
affectedItems.append(deletedUrl);
}
}
foreach(const KUrl& deletedUrl, affectedItems) {
const QString deletedUrlStr = deletedUrl.url();
// stop all jobs for deletedUrlStr
DirectoryDataHash::iterator dit = directoryData.find(deletedUrlStr);
if (dit != directoryData.end()) {
// we need a copy because stop modifies the list
QList<KDirLister *> listers = (*dit).listersCurrentlyListing;
foreach ( KDirLister *kdl, listers )
stopListingUrl( kdl, deletedUrl );
// tell listers holding deletedUrl to forget about it
// this will stop running updates for deletedUrl as well
// we need a copy because forgetDirs modifies the list
QList<KDirLister *> holders = (*dit).listersCurrentlyHolding;
foreach ( KDirLister *kdl, holders ) {
// lister's root is the deleted item
if ( kdl->d->url == deletedUrl )
{
// tell the view first. It might need the subdirs' items (which forgetDirs will delete)
if ( !kdl->d->rootFileItem.isNull() ) {
emit kdl->deleteItem( kdl->d->rootFileItem );
emit kdl->itemsDeleted(KFileItemList() << kdl->d->rootFileItem);
}
forgetDirs( kdl );
kdl->d->rootFileItem = KFileItem();
}
else
{
const bool treeview = kdl->d->lstDirs.count() > 1;
if ( !treeview )
{
emit kdl->clear();
kdl->d->lstDirs.clear();
}
else
kdl->d->lstDirs.removeAll( deletedUrl );
forgetDirs( kdl, deletedUrl, treeview );
}
}
}
// delete the entry for deletedUrl - should not be needed, it's in
// items cached now
int count = itemsInUse.remove( deletedUrlStr );
Q_ASSERT( count == 0 );
Q_UNUSED( count ); //keep gcc "unused variable" complaining quiet when in release mode
}
// remove the children from the cache
removeDirFromCache( dirUrl );
}
// delayed updating of files, FAM is flooding us with events
void KDirListerCache::processPendingUpdates()
{
QSet<KDirLister *> listers;
foreach(const QString& file, pendingUpdates) { // always a local path
kDebug(7004) << file;
KUrl u(file);
KFileItem *item = findByUrl( 0, u ); // search all items
if ( item ) {
// we need to refresh the item, because e.g. the permissions can have changed.
KFileItem oldItem = *item;
item->refresh();
listers |= emitRefreshItem( oldItem, *item );
}
}
pendingUpdates.clear();
Q_FOREACH(KDirLister * kdl, listers) {
kdl->d->emitItems();
}
}
#ifndef NDEBUG
void KDirListerCache::printDebug()
{
kDebug(7004) << "Items in use:";
QHash<QString, DirItem *>::const_iterator itu = itemsInUse.constBegin();
const QHash<QString, DirItem *>::const_iterator ituend = itemsInUse.constEnd();
for ( ; itu != ituend ; ++itu ) {
kDebug(7004) << " " << itu.key() << "URL:" << itu.value()->url
<< "rootItem:" << ( !itu.value()->rootItem.isNull() ? itu.value()->rootItem.url() : KUrl() )
<< "autoUpdates refcount:" << itu.value()->autoUpdates
<< "complete:" << itu.value()->complete
<< QString("with %1 items.").arg(itu.value()->lstItems.count());
}
QList<KDirLister*> listersWithoutJob;
kDebug(7004) << "Directory data:";
DirectoryDataHash::const_iterator dit = directoryData.constBegin();
for ( ; dit != directoryData.constEnd(); ++dit )
{
QString list;
foreach ( KDirLister* listit, (*dit).listersCurrentlyListing )
list += " 0x" + QString::number( (qlonglong)listit, 16 );
kDebug(7004) << " " << dit.key() << (*dit).listersCurrentlyListing.count() << "listers:" << list;
foreach ( KDirLister* listit, (*dit).listersCurrentlyListing ) {
if (!listit->d->m_cachedItemsJobs.isEmpty()) {
kDebug(7004) << " Lister" << listit << "has CachedItemsJobs" << listit->d->m_cachedItemsJobs;
} else if (KIO::ListJob* listJob = jobForUrl(dit.key())) {
kDebug(7004) << " Lister" << listit << "has ListJob" << listJob;
} else {
listersWithoutJob.append(listit);
}
}
list.clear();
foreach ( KDirLister* listit, (*dit).listersCurrentlyHolding )
list += " 0x" + QString::number( (qlonglong)listit, 16 );
kDebug(7004) << " " << dit.key() << (*dit).listersCurrentlyHolding.count() << "holders:" << list;
}
QMap< KIO::ListJob *, KIO::UDSEntryList >::Iterator jit = runningListJobs.begin();
kDebug(7004) << "Jobs:";
for ( ; jit != runningListJobs.end() ; ++jit )
kDebug(7004) << " " << jit.key() << "listing" << joburl( jit.key() ) << ":" << (*jit).count() << "entries.";
kDebug(7004) << "Items in cache:";
const QList<QString> cachedDirs = itemsCached.keys();
foreach(const QString& cachedDir, cachedDirs) {
DirItem* dirItem = itemsCached.object(cachedDir);
kDebug(7004) << " " << cachedDir << "rootItem:"
<< (!dirItem->rootItem.isNull() ? dirItem->rootItem.url().prettyUrl() : QString("NULL") )
<< "with" << dirItem->lstItems.count() << "items.";
}
// Abort on listers without jobs -after- showing the full dump. Easier debugging.
Q_FOREACH(KDirLister* listit, listersWithoutJob) {
kFatal() << "HUH? Lister" << listit << "is supposed to be listing, but has no job!";
}
}
#endif
KDirLister::KDirLister( QObject* parent )
: QObject(parent), d(new Private(this))
{
//kDebug(7003) << "+KDirLister";
d->complete = true;
setAutoUpdate( true );
setDirOnlyMode( false );
setShowingDotFiles( false );
setAutoErrorHandlingEnabled( true, 0 );
}
KDirLister::~KDirLister()
{
//kDebug(7003) << "~KDirLister" << this;
// Stop all running jobs, remove lister from lists
if (!kDirListerCache.isDestroyed()) {
stop();
kDirListerCache->forgetDirs( this );
}
delete d;
}
bool KDirLister::openUrl( const KUrl& _url, OpenUrlFlags _flags )
{
// emit the current changes made to avoid an inconsistent treeview
if (d->hasPendingChanges && (_flags & Keep))
emitChanges();
d->hasPendingChanges = false;
return kDirListerCache->listDir( this, _url, _flags & Keep, _flags & Reload );
}
void KDirLister::stop()
{
kDirListerCache->stop( this );
}
void KDirLister::stop( const KUrl& _url )
{
kDirListerCache->stopListingUrl( this, _url );
}
bool KDirLister::autoUpdate() const
{
return d->autoUpdate;
}
void KDirLister::setAutoUpdate( bool _enable )
{
if ( d->autoUpdate == _enable )
return;
d->autoUpdate = _enable;
kDirListerCache->setAutoUpdate( this, _enable );
}
bool KDirLister::showingDotFiles() const
{
return d->settings.isShowingDotFiles;
}
void KDirLister::setShowingDotFiles( bool _showDotFiles )
{
if ( d->settings.isShowingDotFiles == _showDotFiles )
return;
d->prepareForSettingsChange();
d->settings.isShowingDotFiles = _showDotFiles;
}
bool KDirLister::dirOnlyMode() const
{
return d->settings.dirOnlyMode;
}
void KDirLister::setDirOnlyMode( bool _dirsOnly )
{
if ( d->settings.dirOnlyMode == _dirsOnly )
return;
d->prepareForSettingsChange();
d->settings.dirOnlyMode = _dirsOnly;
}
bool KDirLister::autoErrorHandlingEnabled() const
{
return d->autoErrorHandling;
}
void KDirLister::setAutoErrorHandlingEnabled( bool enable, QWidget* parent )
{
d->autoErrorHandling = enable;
d->errorParent = parent;
}
KUrl KDirLister::url() const
{
return d->url;
}
KUrl::List KDirLister::directories() const
{
return d->lstDirs;
}
void KDirLister::emitChanges()
{
d->emitChanges();
}
void KDirLister::Private::emitChanges()
{
if (!hasPendingChanges)
return;
// reset 'hasPendingChanges' now, in case of recursion
// (testcase: enabling recursive scan in ktorrent, #174920)
hasPendingChanges = false;
const Private::FilterSettings newSettings = settings;
settings = oldSettings; // temporarily
// Mark all items that are currently visible
Q_FOREACH(const KUrl& dir, lstDirs) {
KFileItemList* itemList = kDirListerCache->itemsForDir(dir);
if (!itemList) {
continue;
}
KFileItemList::iterator kit = itemList->begin();
const KFileItemList::iterator kend = itemList->end();
for (; kit != kend; ++kit) {
if (isItemVisible(*kit) && m_parent->matchesMimeFilter(*kit))
(*kit).mark();
else
(*kit).unmark();
}
}
settings = newSettings;
Q_FOREACH(const KUrl& dir, lstDirs) {
KFileItemList deletedItems;
KFileItemList* itemList = kDirListerCache->itemsForDir(dir);
if (!itemList) {
continue;
}
KFileItemList::iterator kit = itemList->begin();
const KFileItemList::iterator kend = itemList->end();
for (; kit != kend; ++kit) {
KFileItem& item = *kit;
const QString text = item.text();
if (text == "." || text == "..")
continue;
const bool nowVisible = isItemVisible(item) && m_parent->matchesMimeFilter(item);
if (nowVisible && !item.isMarked())
addNewItem(dir, item); // takes care of emitting newItem or itemsFilteredByMime
else if (!nowVisible && item.isMarked())
deletedItems.append(*kit);
}
if (!deletedItems.isEmpty()) {
emit m_parent->itemsDeleted(deletedItems);
// for compat
Q_FOREACH(const KFileItem& item, deletedItems)
emit m_parent->deleteItem(item);
}
emitItems();
}
oldSettings = settings;
}
void KDirLister::updateDirectory( const KUrl& _u )
{
kDirListerCache->updateDirectory( _u );
}
bool KDirLister::isFinished() const
{
return d->complete;
}
KFileItem KDirLister::rootItem() const
{
return d->rootFileItem;
}
KFileItem KDirLister::findByUrl( const KUrl& _url ) const
{
KFileItem *item = kDirListerCache->findByUrl( this, _url );
if (item) {
return *item;
} else {
return KFileItem();
}
}
KFileItem KDirLister::findByName( const QString& _name ) const
{
return kDirListerCache->findByName( this, _name );
}
// ================ public filter methods ================ //
void KDirLister::setNameFilter( const QString& nameFilter )
{
if (d->nameFilter == nameFilter)
return;
d->prepareForSettingsChange();
d->settings.lstFilters.clear();
d->nameFilter = nameFilter;
// Split on white space
const QStringList list = nameFilter.split( ' ', QString::SkipEmptyParts );
for (QStringList::const_iterator it = list.begin(); it != list.end(); ++it)
d->settings.lstFilters.append(QRegExp(*it, Qt::CaseInsensitive, QRegExp::Wildcard));
}
QString KDirLister::nameFilter() const
{
return d->nameFilter;
}
void KDirLister::setMimeFilter( const QStringList& mimeFilter )
{
if (d->settings.mimeFilter == mimeFilter)
return;
d->prepareForSettingsChange();
if (mimeFilter.contains(QLatin1String("application/octet-stream")) || mimeFilter.contains(QLatin1String("all/allfiles"))) // all files
d->settings.mimeFilter.clear();
else
d->settings.mimeFilter = mimeFilter;
}
void KDirLister::setMimeExcludeFilter( const QStringList& mimeExcludeFilter )
{
if (d->settings.mimeExcludeFilter == mimeExcludeFilter)
return;
d->prepareForSettingsChange();
d->settings.mimeExcludeFilter = mimeExcludeFilter;
}
void KDirLister::clearMimeFilter()
{
d->prepareForSettingsChange();
d->settings.mimeFilter.clear();
d->settings.mimeExcludeFilter.clear();
}
QStringList KDirLister::mimeFilters() const
{
return d->settings.mimeFilter;
}
bool KDirLister::matchesFilter( const QString& name ) const
{
return doNameFilter(name, d->settings.lstFilters);
}
bool KDirLister::matchesMimeFilter( const QString& mime ) const
{
return doMimeFilter(mime, d->settings.mimeFilter) &&
d->doMimeExcludeFilter(mime, d->settings.mimeExcludeFilter);
}
// ================ protected methods ================ //
bool KDirLister::matchesFilter( const KFileItem& item ) const
{
Q_ASSERT( !item.isNull() );
if ( item.text() == ".." )
return false;
if ( !d->settings.isShowingDotFiles && item.isHidden() )
return false;
if ( item.isDir() || d->settings.lstFilters.isEmpty() )
return true;
return matchesFilter( item.text() );
}
bool KDirLister::matchesMimeFilter( const KFileItem& item ) const
{
Q_ASSERT(!item.isNull());
// Don't lose time determining the mimetype if there is no filter
if (d->settings.mimeFilter.isEmpty() && d->settings.mimeExcludeFilter.isEmpty())
return true;
return matchesMimeFilter(item.mimetype());
}
bool KDirLister::doNameFilter( const QString& name, const QList<QRegExp>& filters ) const
{
for ( QList<QRegExp>::const_iterator it = filters.begin(); it != filters.end(); ++it )
if ( (*it).exactMatch( name ) )
return true;
return false;
}
bool KDirLister::doMimeFilter( const QString& mime, const QStringList& filters ) const
{
if ( filters.isEmpty() )
return true;
const KMimeType::Ptr mimeptr = KMimeType::mimeType(mime);
if ( !mimeptr )
return false;
//kDebug(7004) << "doMimeFilter: investigating: "<<mimeptr->name();
QStringList::const_iterator it = filters.begin();
for ( ; it != filters.end(); ++it )
if ( mimeptr->is(*it) )
return true;
//else kDebug(7004) << "doMimeFilter: compared without result to "<<*it;
return false;
}
bool KDirLister::Private::doMimeExcludeFilter( const QString& mime, const QStringList& filters ) const
{
if ( filters.isEmpty() )
return true;
QStringList::const_iterator it = filters.begin();
for ( ; it != filters.end(); ++it )
if ( (*it) == mime )
return false;
return true;
}
void KDirLister::handleError( KIO::Job *job )
{
if ( d->autoErrorHandling )
job->uiDelegate()->showErrorMessage();
}
// ================= private methods ================= //
void KDirLister::Private::addNewItem(const KUrl& directoryUrl, const KFileItem &item)
{
if (!isItemVisible(item))
return; // No reason to continue... bailing out here prevents a mimetype scan.
//kDebug(7004) << "in" << directoryUrl << "item:" << item.url();
if ( m_parent->matchesMimeFilter( item ) )
{
if ( !lstNewItems )
{
lstNewItems = new NewItemsHash;
}
Q_ASSERT( !item.isNull() );
(*lstNewItems)[directoryUrl].append( item ); // items not filtered
}
else
{
if ( !lstMimeFilteredItems ) {
lstMimeFilteredItems = new KFileItemList;
}
Q_ASSERT( !item.isNull() );
lstMimeFilteredItems->append( item ); // only filtered by mime
}
}
void KDirLister::Private::addNewItems(const KUrl& directoryUrl, const KFileItemList& items)
{
// TODO: make this faster - test if we have a filter at all first
// DF: was this profiled? The matchesFoo() functions should be fast, w/o filters...
// Of course if there is no filter and we can do a range-insertion instead of a loop, that might be good.
KFileItemList::const_iterator kit = items.begin();
const KFileItemList::const_iterator kend = items.end();
for ( ; kit != kend; ++kit )
addNewItem(directoryUrl, *kit);
}
void KDirLister::Private::addRefreshItem(const KUrl& directoryUrl, const KFileItem& oldItem, const KFileItem& item)
{
const bool refreshItemWasFiltered = !isItemVisible(oldItem) ||
!m_parent->matchesMimeFilter(oldItem);
if (isItemVisible(item) && m_parent->matchesMimeFilter(item)) {
if ( refreshItemWasFiltered )
{
if ( !lstNewItems ) {
lstNewItems = new NewItemsHash;
}
Q_ASSERT( !item.isNull() );
(*lstNewItems)[directoryUrl].append( item );
}
else
{
if ( !lstRefreshItems ) {
lstRefreshItems = new QList<QPair<KFileItem,KFileItem> >;
}
Q_ASSERT( !item.isNull() );
lstRefreshItems->append( qMakePair(oldItem, item) );
}
}
else if ( !refreshItemWasFiltered )
{
if ( !lstRemoveItems ) {
lstRemoveItems = new KFileItemList;
}
// notify the user that the mimetype of a file changed that doesn't match
// a filter or does match an exclude filter
// This also happens when renaming foo to .foo and dot files are hidden (#174721)
Q_ASSERT(!oldItem.isNull());
lstRemoveItems->append(oldItem);
}
}
void KDirLister::Private::emitItems()
{
NewItemsHash *tmpNew = lstNewItems;
lstNewItems = 0;
KFileItemList *tmpMime = lstMimeFilteredItems;
lstMimeFilteredItems = 0;
QList<QPair<KFileItem, KFileItem> > *tmpRefresh = lstRefreshItems;
lstRefreshItems = 0;
KFileItemList *tmpRemove = lstRemoveItems;
lstRemoveItems = 0;
if (tmpNew) {
QHashIterator<KUrl, KFileItemList> it(*tmpNew);
while (it.hasNext()) {
it.next();
emit m_parent->itemsAdded(it.key(), it.value());
emit m_parent->newItems(it.value()); // compat
}
delete tmpNew;
}
if ( tmpMime )
{
emit m_parent->itemsFilteredByMime( *tmpMime );
delete tmpMime;
}
if ( tmpRefresh )
{
emit m_parent->refreshItems( *tmpRefresh );
delete tmpRefresh;
}
if ( tmpRemove )
{
emit m_parent->itemsDeleted( *tmpRemove );
delete tmpRemove;
}
}
bool KDirLister::Private::isItemVisible(const KFileItem& item) const
{
// Note that this doesn't include mime filters, because
// of the itemsFilteredByMime signal. Filtered-by-mime items are
// considered "visible", they are just visible via a different signal...
return (!settings.dirOnlyMode || item.isDir())
&& m_parent->matchesFilter(item);
}
void KDirLister::Private::emitItemsDeleted(const KFileItemList &_items)
{
KFileItemList items = _items;
QMutableListIterator<KFileItem> it(items);
while (it.hasNext()) {
const KFileItem& item = it.next();
if (isItemVisible(item) && m_parent->matchesMimeFilter(item)) {
// for compat
emit m_parent->deleteItem(item);
} else {
it.remove();
}
}
if (!items.isEmpty())
emit m_parent->itemsDeleted(items);
}
// ================ private slots ================ //
void KDirLister::Private::_k_slotInfoMessage( KJob *, const QString& message )
{
emit m_parent->infoMessage( message );
}
void KDirLister::Private::_k_slotPercent( KJob *job, unsigned long pcnt )
{
jobData[static_cast<KIO::ListJob *>(job)].percent = pcnt;
int result = 0;
KIO::filesize_t size = 0;
QMap< KIO::ListJob *, Private::JobData >::Iterator dataIt = jobData.begin();
while ( dataIt != jobData.end() )
{
result += (*dataIt).percent * (*dataIt).totalSize;
size += (*dataIt).totalSize;
++dataIt;
}
if ( size != 0 )
result /= size;
else
result = 100;
emit m_parent->percent( result );
}
void KDirLister::Private::_k_slotTotalSize( KJob *job, qulonglong size )
{
jobData[static_cast<KIO::ListJob *>(job)].totalSize = size;
KIO::filesize_t result = 0;
QMap< KIO::ListJob *, Private::JobData >::Iterator dataIt = jobData.begin();
while ( dataIt != jobData.end() )
{
result += (*dataIt).totalSize;
++dataIt;
}
emit m_parent->totalSize( result );
}
void KDirLister::Private::_k_slotProcessedSize( KJob *job, qulonglong size )
{
jobData[static_cast<KIO::ListJob *>(job)].processedSize = size;
KIO::filesize_t result = 0;
QMap< KIO::ListJob *, Private::JobData >::Iterator dataIt = jobData.begin();
while ( dataIt != jobData.end() )
{
result += (*dataIt).processedSize;
++dataIt;
}
emit m_parent->processedSize( result );
}
void KDirLister::Private::_k_slotSpeed( KJob *job, unsigned long spd )
{
jobData[static_cast<KIO::ListJob *>(job)].speed = spd;
int result = 0;
QMap< KIO::ListJob *, Private::JobData >::Iterator dataIt = jobData.begin();
while ( dataIt != jobData.end() )
{
result += (*dataIt).speed;
++dataIt;
}
emit m_parent->speed( result );
}
uint KDirLister::Private::numJobs()
{
#ifdef DEBUG_CACHE
// This code helps detecting stale entries in the jobData map.
qDebug() << m_parent << "numJobs:" << jobData.count();
QMapIterator<KIO::ListJob *, JobData> it(jobData);
while (it.hasNext()) {
it.next();
qDebug() << (void*)it.key();
qDebug() << it.key();
}
#endif
return jobData.count();
}
void KDirLister::Private::jobDone( KIO::ListJob *job )
{
jobData.remove( job );
}
void KDirLister::Private::jobStarted( KIO::ListJob *job )
{
Private::JobData data;
data.speed = 0;
data.percent = 0;
data.processedSize = 0;
data.totalSize = 0;
jobData.insert( job, data );
complete = false;
}
void KDirLister::Private::connectJob( KIO::ListJob *job )
{
m_parent->connect( job, SIGNAL(infoMessage( KJob *, const QString&, const QString& )),
m_parent, SLOT(_k_slotInfoMessage( KJob *, const QString& )) );
m_parent->connect( job, SIGNAL(percent( KJob *, unsigned long )),
m_parent, SLOT(_k_slotPercent( KJob *, unsigned long )) );
m_parent->connect( job, SIGNAL(totalSize( KJob *, qulonglong )),
m_parent, SLOT(_k_slotTotalSize( KJob *, qulonglong )) );
m_parent->connect( job, SIGNAL(processedSize( KJob *, qulonglong )),
m_parent, SLOT(_k_slotProcessedSize( KJob *, qulonglong )) );
m_parent->connect( job, SIGNAL(speed( KJob *, unsigned long )),
m_parent, SLOT(_k_slotSpeed( KJob *, unsigned long )) );
}
void KDirLister::setMainWindow( QWidget *window )
{
d->window = window;
}
QWidget *KDirLister::mainWindow()
{
return d->window;
}
KFileItemList KDirLister::items( WhichItems which ) const
{
return itemsForDir( url(), which );
}
KFileItemList KDirLister::itemsForDir( const KUrl& dir, WhichItems which ) const
{
KFileItemList *allItems = kDirListerCache->itemsForDir( dir );
if ( !allItems )
return KFileItemList();
if ( which == AllItems )
return *allItems;
else // only items passing the filters
{
KFileItemList result;
KFileItemList::const_iterator kit = allItems->constBegin();
const KFileItemList::const_iterator kend = allItems->constEnd();
for ( ; kit != kend; ++kit )
{
const KFileItem& item = *kit;
if (d->isItemVisible(item) && matchesMimeFilter(item)) {
result.append(item);
}
}
return result;
}
}
bool KDirLister::delayedMimeTypes() const
{
return d->delayedMimeTypes;
}
void KDirLister::setDelayedMimeTypes( bool delayedMimeTypes )
{
d->delayedMimeTypes = delayedMimeTypes;
}
// called by KDirListerCache::slotRedirection
void KDirLister::Private::redirect(const KUrl& oldUrl, const KUrl& newUrl, bool keepItems)
{
if ( url.equals( oldUrl, KUrl::CompareWithoutTrailingSlash ) ) {
if (!keepItems)
rootFileItem = KFileItem();
url = newUrl;
}
const int idx = lstDirs.indexOf( oldUrl );
if (idx == -1) {
kWarning(7004) << "Unexpected redirection from" << oldUrl << "to" << newUrl
<< "but this dirlister is currently listing/holding" << lstDirs;
} else {
lstDirs[ idx ] = newUrl;
}
if ( lstDirs.count() == 1 ) {
if (!keepItems)
emit m_parent->clear();
emit m_parent->redirection( newUrl );
} else {
if (!keepItems)
emit m_parent->clear( oldUrl );
}
emit m_parent->redirection( oldUrl, newUrl );
}
void KDirListerCacheDirectoryData::moveListersWithoutCachedItemsJob(const KUrl& url)
{
// Move dirlisters from listersCurrentlyListing to listersCurrentlyHolding,
// but not those that are still waiting on a CachedItemsJob...
// Unit-testing note:
// Run kdirmodeltest in valgrind to hit the case where an update
// is triggered while a lister has a CachedItemsJob (different timing...)
QMutableListIterator<KDirLister *> lister_it(listersCurrentlyListing);
while (lister_it.hasNext()) {
KDirLister* kdl = lister_it.next();
if (!kdl->d->cachedItemsJobForUrl(url)) {
// OK, move this lister from "currently listing" to "currently holding".
// Huh? The KDirLister was present twice in listersCurrentlyListing, or was in both lists?
Q_ASSERT(!listersCurrentlyHolding.contains(kdl));
if (!listersCurrentlyHolding.contains(kdl)) {
listersCurrentlyHolding.append(kdl);
}
lister_it.remove();
} else {
//kDebug(7004) << "Not moving" << kdl << "to listersCurrentlyHolding because it still has job" << kdl->d->m_cachedItemsJobs;
}
}
}
KFileItem KDirLister::cachedItemForUrl(const KUrl& url)
{
return kDirListerCache->itemForUrl(url);
}
#include "moc_kdirlister.cpp"
#include "moc_kdirlister_p.cpp"
diff --git a/kio/kio/kdirmodel.cpp b/kio/kio/kdirmodel.cpp
index ab3cc3a596..7fa32d920c 100644
--- a/kio/kio/kdirmodel.cpp
+++ b/kio/kio/kdirmodel.cpp
@@ -1,1170 +1,1170 @@
/* This file is part of the KDE project
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 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 "kdirmodel.h"
#include "kdirlister.h"
#include "kfileitem.h"
#include <kdatetime.h>
#include <kicon.h>
#include <klocale.h>
#include <kglobal.h>
#include <kio/copyjob.h>
#include <kio/fileundomanager.h>
#include <kio/jobuidelegate.h>
#include <kio/joburlcache_p.h>
#include <kurl.h>
#include <kdebug.h>
#include <QMimeData>
#include <QFile>
#include <QFileInfo>
#include <QDir>
#include <sys/types.h>
#include <dirent.h>
#ifdef Q_WS_WIN
#include <windows.h>
#endif
class KDirModelNode;
class KDirModelDirNode;
static KUrl cleanupUrl(const KUrl& url) {
KUrl u = url;
u.cleanPath(); // remove double slashes in the path, simplify "foo/." to "foo/", etc.
u.adjustPath(KUrl::RemoveTrailingSlash); // KDirLister does this too, so we remove the slash before comparing with the root node url.
u.setQuery(QString());
u.setRef(QString());
return u;
}
// We create our own tree behind the scenes to have fast lookup from an item to its parent,
// and also to get the children of an item fast.
class KDirModelNode
{
public:
KDirModelNode( KDirModelDirNode* parent, const KFileItem& item ) :
m_item(item),
m_parent(parent),
m_preview()
{
}
// m_item is KFileItem() for the root item
const KFileItem& item() const { return m_item; }
void setItem(const KFileItem& item) { m_item = item; }
KDirModelDirNode* parent() const { return m_parent; }
// linear search
int rowNumber() const; // O(n)
QIcon preview() const { return m_preview; }
void setPreview( const QPixmap& pix ) { m_preview = QIcon(); m_preview.addPixmap(pix); }
void setPreview( const QIcon& icn ) { m_preview = icn; }
private:
KFileItem m_item;
KDirModelDirNode* const m_parent;
QIcon m_preview;
};
// Specialization for directory nodes
class KDirModelDirNode : public KDirModelNode
{
public:
KDirModelDirNode( KDirModelDirNode* parent, const KFileItem& item)
: KDirModelNode( parent, item),
m_childNodes(),
m_childCount(KDirModel::ChildCountUnknown),
m_populated(false)
{}
~KDirModelDirNode() {
qDeleteAll(m_childNodes);
}
QList<KDirModelNode *> m_childNodes; // owns the nodes
// If we listed the directory, the child count is known. Otherwise it can be set via setChildCount.
int childCount() const { return m_childNodes.isEmpty() ? m_childCount : m_childNodes.count(); }
void setChildCount(int count) { m_childCount = count; }
bool isPopulated() const { return m_populated; }
void setPopulated( bool populated ) { m_populated = populated; }
bool isSlow() const { return item().isSlow(); }
// For removing all child urls from the global hash.
void collectAllChildUrls(KUrl::List &urls) const {
Q_FOREACH(KDirModelNode* node, m_childNodes) {
const KFileItem& item = node->item();
urls.append(cleanupUrl(item.url()));
if (item.isDir())
static_cast<KDirModelDirNode*>(node)->collectAllChildUrls(urls);
}
}
private:
int m_childCount:31;
bool m_populated:1;
};
int KDirModelNode::rowNumber() const
{
if (!m_parent) return 0;
return m_parent->m_childNodes.indexOf(const_cast<KDirModelNode*>(this));
}
////
class KDirModelPrivate
{
public:
KDirModelPrivate( KDirModel* model )
: q(model), m_dirLister(0),
m_rootNode(new KDirModelDirNode(0, KFileItem())),
m_dropsAllowed(KDirModel::NoDrops), m_jobTransfersVisible(false)
{
}
~KDirModelPrivate() {
delete m_rootNode;
}
void _k_slotNewItems(const KUrl& directoryUrl, const KFileItemList&);
void _k_slotDeleteItems(const KFileItemList&);
void _k_slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&);
void _k_slotClear();
void _k_slotRedirection(const KUrl& oldUrl, const KUrl& newUrl);
void _k_slotJobUrlsChanged(const QStringList& urlList);
void clear() {
delete m_rootNode;
m_rootNode = new KDirModelDirNode(0, KFileItem());
}
// Emit expand for each parent and then return the
// last known parent if there is no node for this url
KDirModelNode* expandAllParentsUntil(const KUrl& url) const;
// Return the node for a given url, using the hash.
KDirModelNode* nodeForUrl(const KUrl& url) const;
KDirModelNode* nodeForIndex(const QModelIndex& index) const;
QModelIndex indexForNode(KDirModelNode* node, int rowNumber = -1 /*unknown*/) const;
bool isDir(KDirModelNode* node) const {
return (node == m_rootNode) || node->item().isDir();
}
KUrl urlForNode(KDirModelNode* node) const {
/**
* Queries and fragments are removed from the URL, so that the URL of
* child items really starts with the URL of the parent.
*
* For instance ksvn+http://url?rev=100 is the parent for ksvn+http://url/file?rev=100
* so we have to remove the query in both to be able to compare the URLs
*/
KUrl url(node == m_rootNode ? m_dirLister->url() : node->item().url());
if (url.hasQuery() || url.hasRef()) { // avoid detach if not necessary.
url.setQuery(QString());
url.setRef(QString()); // kill ref (#171117)
}
return url;
}
void removeFromNodeHash(KDirModelNode* node, const KUrl& url);
#ifndef NDEBUG
void dump();
#endif
KDirModel* q;
KDirLister* m_dirLister;
KDirModelDirNode* m_rootNode;
KDirModel::DropsAllowed m_dropsAllowed;
bool m_jobTransfersVisible;
// key = current known parent node (always a KDirModelDirNode but KDirModelNode is more convenient),
// value = final url[s] being fetched
QMap<KDirModelNode*, KUrl::List> m_urlsBeingFetched;
QHash<KUrl, KDirModelNode *> m_nodeHash; // global node hash: url -> node
QStringList m_allCurrentDestUrls; //list of all dest urls that have jobs on them (e.g. copy, download)
};
KDirModelNode* KDirModelPrivate::nodeForUrl(const KUrl& _url) const // O(1), well, O(length of url as a string)
{
KUrl url = cleanupUrl(_url);
if (url == urlForNode(m_rootNode))
return m_rootNode;
return m_nodeHash.value(url);
}
void KDirModelPrivate::removeFromNodeHash(KDirModelNode* node, const KUrl& url)
{
if (node->item().isDir()) {
KUrl::List urls;
static_cast<KDirModelDirNode *>(node)->collectAllChildUrls(urls);
Q_FOREACH(const KUrl& u, urls) {
m_nodeHash.remove(u);
}
}
m_nodeHash.remove(cleanupUrl(url));
}
KDirModelNode* KDirModelPrivate::expandAllParentsUntil(const KUrl& _url) const // O(depth)
{
KUrl url = cleanupUrl(_url);
//kDebug(7008) << url;
KUrl nodeUrl = urlForNode(m_rootNode);
if (url == nodeUrl)
return m_rootNode;
// Protocol mismatch? Don't even start comparing paths then. #171721
- if (url.protocol() != nodeUrl.protocol())
+ if (url.scheme() != nodeUrl.scheme())
return 0;
const QString pathStr = url.path(); // no trailing slash
KDirModelDirNode* dirNode = m_rootNode;
if (!pathStr.startsWith(nodeUrl.path())) {
return 0;
}
for (;;) {
const QString nodePath = nodeUrl.path(KUrl::AddTrailingSlash);
if(!pathStr.startsWith(nodePath)) {
- kError(7008) << "The kioslave for" << url.protocol() << "violates the hierarchy structure:"
+ kError(7008) << "The kioslave for" << url.scheme() << "violates the hierarchy structure:"
<< "I arrived at node" << nodePath << ", but" << pathStr << "does not start with that path.";
return 0;
}
// E.g. pathStr is /a/b/c and nodePath is /a/. We want to find the node with url /a/b
const int nextSlash = pathStr.indexOf('/', nodePath.length());
const QString newPath = pathStr.left(nextSlash); // works even if nextSlash==-1
nodeUrl.setPath(newPath);
nodeUrl.adjustPath(KUrl::RemoveTrailingSlash); // #172508
KDirModelNode* node = nodeForUrl(nodeUrl);
if (!node) {
//kDebug(7008) << "child equal or starting with" << url << "not found";
// return last parent found:
return dirNode;
}
emit q->expand(indexForNode(node));
//kDebug(7008) << " nodeUrl=" << nodeUrl;
if (nodeUrl == url) {
//kDebug(7008) << "Found node" << node << "for" << url;
return node;
}
//kDebug(7008) << "going into" << node->item().url();
Q_ASSERT(isDir(node));
dirNode = static_cast<KDirModelDirNode *>(node);
}
// NOTREACHED
//return 0;
}
#ifndef NDEBUG
void KDirModelPrivate::dump()
{
kDebug() << "Dumping contents of KDirModel" << q << "dirLister url:" << m_dirLister->url();
QHashIterator<KUrl, KDirModelNode *> it(m_nodeHash);
while (it.hasNext()) {
it.next();
kDebug() << it.key() << it.value();
}
}
#endif
// node -> index. If rowNumber is set (or node is root): O(1). Otherwise: O(n).
QModelIndex KDirModelPrivate::indexForNode(KDirModelNode* node, int rowNumber) const
{
if (node == m_rootNode)
return QModelIndex();
Q_ASSERT(node->parent());
return q->createIndex(rowNumber == -1 ? node->rowNumber() : rowNumber, 0, node);
}
// index -> node. O(1)
KDirModelNode* KDirModelPrivate::nodeForIndex(const QModelIndex& index) const
{
return index.isValid()
? static_cast<KDirModelNode*>(index.internalPointer())
: m_rootNode;
}
/*
* This model wraps the data held by KDirLister.
*
* The internal pointer of the QModelIndex for a given file is the node for that file in our own tree.
* E.g. index(2,0) returns a QModelIndex with row=2 internalPointer=<KDirModelNode for the 3rd child of the root>
*
* Invalid parent index means root of the tree, m_rootNode
*/
#ifndef NDEBUG
static QString debugIndex(const QModelIndex& index)
{
QString str;
if (!index.isValid())
str = "[invalid index, i.e. root]";
else {
KDirModelNode* node = static_cast<KDirModelNode*>(index.internalPointer());
str = "[index for " + node->item().url().pathOrUrl();
if (index.column() > 0)
str += ", column " + QString::number(index.column());
str += ']';
}
return str;
}
#endif
KDirModel::KDirModel(QObject* parent)
: QAbstractItemModel(parent),
d(new KDirModelPrivate(this))
{
setDirLister(new KDirLister(this));
}
KDirModel::~KDirModel()
{
delete d;
}
void KDirModel::setDirLister(KDirLister* dirLister)
{
if (d->m_dirLister) {
d->clear();
delete d->m_dirLister;
}
d->m_dirLister = dirLister;
d->m_dirLister->setParent(this);
connect( d->m_dirLister, SIGNAL(itemsAdded(KUrl,KFileItemList)),
this, SLOT(_k_slotNewItems(KUrl,KFileItemList)) );
connect( d->m_dirLister, SIGNAL(itemsDeleted(KFileItemList)),
this, SLOT(_k_slotDeleteItems(KFileItemList)) );
connect( d->m_dirLister, SIGNAL(refreshItems(QList<QPair<KFileItem, KFileItem> >)),
this, SLOT(_k_slotRefreshItems(QList<QPair<KFileItem, KFileItem> >)) );
connect( d->m_dirLister, SIGNAL(clear()),
this, SLOT(_k_slotClear()) );
connect(d->m_dirLister, SIGNAL(redirection(KUrl, KUrl)),
this, SLOT(_k_slotRedirection(KUrl, KUrl)));
}
KDirLister* KDirModel::dirLister() const
{
return d->m_dirLister;
}
void KDirModelPrivate::_k_slotNewItems(const KUrl& directoryUrl, const KFileItemList& items)
{
//kDebug(7008) << "directoryUrl=" << directoryUrl;
KDirModelNode* result = nodeForUrl(directoryUrl); // O(depth)
// If the directory containing the items wasn't found, then we have a big problem.
// Are you calling KDirLister::openUrl(url,true,false)? Please use expandToUrl() instead.
if (!result) {
kError(7008) << "Items emitted in directory" << directoryUrl
<< "but that directory isn't in KDirModel!"
<< "Root directory:" << urlForNode(m_rootNode);
Q_FOREACH(const KFileItem& item, items) {
kDebug() << "Item:" << item.url();
}
#ifndef NDEBUG
dump();
#endif
Q_ASSERT(result);
}
Q_ASSERT(isDir(result));
KDirModelDirNode* dirNode = static_cast<KDirModelDirNode *>(result);
const QModelIndex index = indexForNode(dirNode); // O(n)
const int newItemsCount = items.count();
const int newRowCount = dirNode->m_childNodes.count() + newItemsCount;
#if 0
#ifndef NDEBUG // debugIndex only defined in debug mode
kDebug(7008) << items.count() << "in" << directoryUrl
<< "index=" << debugIndex(index) << "newRowCount=" << newRowCount;
#endif
#endif
q->beginInsertRows( index, newRowCount - newItemsCount, newRowCount - 1 ); // parent, first, last
const KUrl::List urlsBeingFetched = m_urlsBeingFetched.value(dirNode);
//kDebug(7008) << "urlsBeingFetched for dir" << dirNode << directoryUrl << ":" << urlsBeingFetched;
QList<QModelIndex> emitExpandFor;
KFileItemList::const_iterator it = items.begin();
KFileItemList::const_iterator end = items.end();
for ( ; it != end ; ++it ) {
const bool isDir = it->isDir();
KDirModelNode* node = isDir
? new KDirModelDirNode( dirNode, *it )
: new KDirModelNode( dirNode, *it );
#ifndef NDEBUG
// Test code for possible duplication of items in the childnodes list,
// not sure if/how it ever happened.
//if (dirNode->m_childNodes.count() &&
// dirNode->m_childNodes.last()->item().name() == (*it).name())
// kFatal() << "Already having" << (*it).name() << "in" << directoryUrl
// << "url=" << dirNode->m_childNodes.last()->item().url();
#endif
dirNode->m_childNodes.append(node);
const KUrl url = it->url();
m_nodeHash.insert(cleanupUrl(url), node);
//kDebug(7008) << url;
if (!urlsBeingFetched.isEmpty()) {
const KUrl dirUrl = url;
foreach(const KUrl& urlFetched, urlsBeingFetched) {
if (dirUrl.isParentOf(urlFetched)) {
kDebug(7008) << "Listing found" << dirUrl << "which is a parent of fetched url" << urlFetched;
const QModelIndex parentIndex = indexForNode(node, dirNode->m_childNodes.count()-1);
Q_ASSERT(parentIndex.isValid());
emitExpandFor.append(parentIndex);
if (isDir && dirUrl != urlFetched) {
q->fetchMore(parentIndex);
m_urlsBeingFetched[node].append(urlFetched);
}
}
}
}
}
m_urlsBeingFetched.remove(dirNode);
q->endInsertRows();
// Emit expand signal after rowsInserted signal has been emitted,
// so that any proxy model will have updated its mapping already
Q_FOREACH(const QModelIndex& idx, emitExpandFor) {
emit q->expand(idx);
}
}
void KDirModelPrivate::_k_slotDeleteItems(const KFileItemList& items)
{
//kDebug(7008) << items.count();
// I assume all items are from the same directory.
// From KDirLister's code, this should be the case, except maybe emitChanges?
const KFileItem item = items.first();
Q_ASSERT(!item.isNull());
KUrl url = item.url();
KDirModelNode* node = nodeForUrl(url); // O(depth)
if (!node) {
kWarning(7008) << "No node found for item that was just removed:" << url;
return;
}
KDirModelDirNode* dirNode = node->parent();
if (!dirNode)
return;
QModelIndex parentIndex = indexForNode(dirNode); // O(n)
// Short path for deleting a single item
if (items.count() == 1) {
const int r = node->rowNumber();
q->beginRemoveRows(parentIndex, r, r);
removeFromNodeHash(node, url);
delete dirNode->m_childNodes.takeAt(r);
q->endRemoveRows();
return;
}
// We need to make lists of consecutive row numbers, for the beginRemoveRows call.
// Let's use a bit array where each bit represents a given child node.
const int childCount = dirNode->m_childNodes.count();
QBitArray rowNumbers(childCount, false);
Q_FOREACH(const KFileItem& item, items) {
if (!node) { // don't lookup the first item twice
url = item.url();
node = nodeForUrl(url);
if (!node) {
kWarning(7008) << "No node found for item that was just removed:" << url;
continue;
}
if (!node->parent()) {
// The root node has been deleted, but it was not first in the list 'items'.
// see https://bugs.kde.org/show_bug.cgi?id=196695
return;
}
}
rowNumbers.setBit(node->rowNumber(), 1); // O(n)
removeFromNodeHash(node, url);
node = 0;
}
int start = -1;
int end = -1;
bool lastVal = false;
// Start from the end, otherwise all the row numbers are offset while we go
for (int i = childCount - 1; i >= 0; --i) {
const bool val = rowNumbers.testBit(i);
if (!lastVal && val) {
end = i;
//kDebug(7008) << "end=" << end;
}
if ((lastVal && !val) || (i == 0 && val)) {
start = val ? i : i + 1;
//kDebug(7008) << "beginRemoveRows" << start << end;
q->beginRemoveRows(parentIndex, start, end);
for (int r = end; r >= start; --r) { // reverse because takeAt changes indexes ;)
//kDebug(7008) << "Removing from m_childNodes at" << r;
delete dirNode->m_childNodes.takeAt(r);
}
q->endRemoveRows();
}
lastVal = val;
}
}
void KDirModelPrivate::_k_slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >& items)
{
QModelIndex topLeft, bottomRight;
// Solution 1: we could emit dataChanged for one row (if items.size()==1) or all rows
// Solution 2: more fine-grained, actually figure out the beginning and end rows.
for ( QList<QPair<KFileItem, KFileItem> >::const_iterator fit = items.begin(), fend = items.end() ; fit != fend ; ++fit ) {
Q_ASSERT(!fit->first.isNull());
Q_ASSERT(!fit->second.isNull());
const KUrl oldUrl = fit->first.url();
const KUrl newUrl = fit->second.url();
KDirModelNode* node = nodeForUrl(oldUrl); // O(n); maybe we could look up to the parent only once
//kDebug(7008) << "in model for" << m_dirLister->url() << ":" << oldUrl << "->" << newUrl << "node=" << node;
if (!node) // not found [can happen when renaming a dir, redirection was emitted already]
continue;
if (node != m_rootNode) { // we never set an item in the rootnode, we use m_dirLister->rootItem instead.
bool hasNewNode = false;
// A file became directory (well, it was overwritten)
if (fit->first.isDir() != fit->second.isDir()) {
//kDebug(7008) << "DIR/FILE STATUS CHANGE";
const int r = node->rowNumber();
removeFromNodeHash(node, oldUrl);
KDirModelDirNode* dirNode = node->parent();
delete dirNode->m_childNodes.takeAt(r); // i.e. "delete node"
node = fit->second.isDir() ? new KDirModelDirNode(dirNode, fit->second)
: new KDirModelNode(dirNode, fit->second);
dirNode->m_childNodes.insert(r, node); // same position!
hasNewNode = true;
} else {
node->setItem(fit->second);
}
if (oldUrl != newUrl || hasNewNode) {
// What if a renamed dir had children? -> kdirlister takes care of emitting for each item
//kDebug(7008) << "Renaming" << oldUrl << "to" << newUrl << "in node hash";
m_nodeHash.remove(cleanupUrl(oldUrl));
m_nodeHash.insert(cleanupUrl(newUrl), node);
}
// Mimetype changed -> forget cached icon (e.g. from "cut", #164185 comment #13)
if (fit->first.mimeTypePtr()->name() != fit->second.mimeTypePtr()->name()) {
node->setPreview(QIcon());
}
const QModelIndex index = indexForNode(node);
if (!topLeft.isValid() || index.row() < topLeft.row()) {
topLeft = index;
}
if (!bottomRight.isValid() || index.row() > bottomRight.row()) {
bottomRight = index;
}
}
}
#ifndef NDEBUG // debugIndex only defined in debug mode
kDebug(7008) << "dataChanged(" << debugIndex(topLeft) << " - " << debugIndex(bottomRight);
#endif
bottomRight = bottomRight.sibling(bottomRight.row(), q->columnCount(QModelIndex())-1);
emit q->dataChanged(topLeft, bottomRight);
}
// Called when a kioslave redirects (e.g. smb:/Workgroup -> smb://workgroup)
// and when renaming a directory.
void KDirModelPrivate::_k_slotRedirection(const KUrl& oldUrl, const KUrl& newUrl)
{
KDirModelNode* node = nodeForUrl(oldUrl);
if (!node)
return;
m_nodeHash.remove(cleanupUrl(oldUrl));
m_nodeHash.insert(cleanupUrl(newUrl), node);
// Ensure the node's URL is updated. In case of a listjob redirection
// we won't get a refreshItem, and in case of renaming a directory
// we'll get it too late (so the hash won't find the old url anymore).
KFileItem item = node->item();
if (!item.isNull()) { // null if root item, #180156
item.setUrl(newUrl);
node->setItem(item);
}
// The items inside the renamed directory have been handled before,
// KDirLister took care of emitting refreshItem for each of them.
}
void KDirModelPrivate::_k_slotClear()
{
const int numRows = m_rootNode->m_childNodes.count();
if (numRows > 0) {
q->beginRemoveRows( QModelIndex(), 0, numRows - 1 );
q->endRemoveRows();
}
m_nodeHash.clear();
//emit layoutAboutToBeChanged();
clear();
//emit layoutChanged();
}
void KDirModelPrivate::_k_slotJobUrlsChanged(const QStringList& urlList)
{
m_allCurrentDestUrls = urlList;
}
void KDirModel::itemChanged( const QModelIndex& index )
{
// This method is really a itemMimeTypeChanged(), it's mostly called by KMimeTypeResolver.
// When the mimetype is determined, clear the old "preview" (could be
// mimetype dependent like when cutting files, #164185)
KDirModelNode* node = d->nodeForIndex(index);
if (node)
node->setPreview(QIcon());
#ifndef NDEBUG // debugIndex only defined in debug mode
//kDebug(7008) << "dataChanged(" << debugIndex(index);
#endif
emit dataChanged(index, index);
}
int KDirModel::columnCount( const QModelIndex & ) const
{
return ColumnCount;
}
QVariant KDirModel::data( const QModelIndex & index, int role ) const
{
if (index.isValid()) {
KDirModelNode* node = static_cast<KDirModelNode*>(index.internalPointer());
const KFileItem& item( node->item() );
switch (role) {
case Qt::DisplayRole:
switch (index.column()) {
case Name:
return item.text();
case Size:
//
//return KIO::convertSize(item->size());
// Default to "file size in bytes" like in kde3's filedialog
return KGlobal::locale()->formatNumber(item.size(), 0);
case ModifiedTime: {
KDateTime dt = item.time(KFileItem::ModificationTime);
return KGlobal::locale()->formatDateTime(dt);
}
case Permissions:
return item.permissionsString();
case Owner:
return item.user();
case Group:
return item.group();
case Type:
return item.mimeComment();
}
break;
case Qt::EditRole:
switch (index.column()) {
case Name:
return item.text();
}
break;
case Qt::DecorationRole:
if (index.column() == Name) {
if (!node->preview().isNull()) {
//kDebug(7008) << item->url() << " preview found";
return node->preview();
}
Q_ASSERT(!item.isNull());
//kDebug(7008) << item->url() << " overlays=" << item->overlays();
return KIcon(item.iconName(), 0, item.overlays());
}
break;
case Qt::TextAlignmentRole:
if (index.column() == Size) {
// use a right alignment for L2R and R2L languages
const Qt::Alignment alignment = Qt::AlignRight | Qt::AlignVCenter;
return int(alignment);
}
break;
case Qt::ToolTipRole:
return item.text();
case FileItemRole:
return QVariant::fromValue(item);
case ChildCountRole:
if (!item.isDir())
return ChildCountUnknown;
else {
KDirModelDirNode* dirNode = static_cast<KDirModelDirNode *>(node);
int count = dirNode->childCount();
if (count == ChildCountUnknown && item.isReadable() && !dirNode->isSlow()) {
const QString path = item.localPath();
if (!path.isEmpty()) {
// slow
// QDir dir(path);
// count = dir.entryList(QDir::AllEntries|QDir::NoDotAndDotDot|QDir::System).count();
#ifdef Q_WS_WIN
QString s = path + QLatin1String( "\\*.*" );
s.replace('/', '\\');
count = 0;
WIN32_FIND_DATA findData;
HANDLE hFile = FindFirstFile( (LPWSTR)s.utf16(), &findData );
if( hFile != INVALID_HANDLE_VALUE ) {
do {
if (!( findData.cFileName[0] == '.' &&
findData.cFileName[1] == '\0' ) &&
!( findData.cFileName[0] == '.' &&
findData.cFileName[1] == '.' &&
findData.cFileName[2] == '\0' ) )
++count;
} while( FindNextFile( hFile, &findData ) != 0 );
FindClose( hFile );
}
#else
DIR* dir = ::opendir(QFile::encodeName(path));
if (dir) {
count = 0;
struct dirent *dirEntry = 0;
while ((dirEntry = ::readdir(dir))) {
if (dirEntry->d_name[0] == '.') {
if (dirEntry->d_name[1] == '\0') // skip "."
continue;
if (dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0') // skip ".."
continue;
}
++count;
}
::closedir(dir);
}
#endif
//kDebug(7008) << "child count for " << path << ":" << count;
dirNode->setChildCount(count);
}
}
return count;
}
case HasJobRole:
if (d->m_jobTransfersVisible && d->m_allCurrentDestUrls.isEmpty() == false) {
KDirModelNode* node = d->nodeForIndex(index);
const QString url = node->item().url().url();
//return whether or not there are job dest urls visible in the view, so the delegate knows which ones to paint.
return QVariant(d->m_allCurrentDestUrls.contains(url));
}
}
}
return QVariant();
}
void KDirModel::sort( int column, Qt::SortOrder order )
{
// Not implemented - we should probably use QSortFilterProxyModel instead.
return QAbstractItemModel::sort(column, order);
}
bool KDirModel::setData( const QModelIndex & index, const QVariant & value, int role )
{
switch (role) {
case Qt::EditRole:
if (index.column() == Name && value.type() == QVariant::String) {
Q_ASSERT(index.isValid());
KDirModelNode* node = static_cast<KDirModelNode*>(index.internalPointer());
const KFileItem& item = node->item();
const QString newName = value.toString();
if (newName.isEmpty() || newName == item.text() || (newName == QLatin1String(".")) || (newName == QLatin1String("..")))
return true;
KUrl newurl(item.url());
newurl.setPath(newurl.directory(KUrl::AppendTrailingSlash) + KIO::encodeFileName(newName));
KIO::Job * job = KIO::moveAs(item.url(), newurl, newurl.isLocalFile() ? KIO::HideProgressInfo : KIO::DefaultFlags);
job->ui()->setAutoErrorHandlingEnabled(true);
// undo handling
KIO::FileUndoManager::self()->recordJob( KIO::FileUndoManager::Rename, item.url(), newurl, job );
return true;
}
break;
case Qt::DecorationRole:
if (index.column() == Name) {
Q_ASSERT(index.isValid());
// Set new pixmap - e.g. preview
KDirModelNode* node = static_cast<KDirModelNode*>(index.internalPointer());
//kDebug(7008) << "setting icon for " << node->item()->url();
Q_ASSERT(node);
if (value.type() == QVariant::Icon) {
const QIcon icon(qvariant_cast<QIcon>(value));
node->setPreview(icon);
} else if (value.type() == QVariant::Pixmap) {
node->setPreview(qvariant_cast<QPixmap>(value));
}
emit dataChanged(index, index);
return true;
}
break;
default:
break;
}
return false;
}
int KDirModel::rowCount( const QModelIndex & parent ) const
{
KDirModelNode* node = d->nodeForIndex(parent);
if (!node || !d->isDir(node)) // #176555
return 0;
KDirModelDirNode* parentNode = static_cast<KDirModelDirNode *>(node);
Q_ASSERT(parentNode);
const int count = parentNode->m_childNodes.count();
#if 0
QStringList filenames;
for (int i = 0; i < count; ++i) {
filenames << d->urlForNode(parentNode->m_childNodes.at(i)).fileName();
}
kDebug(7008) << "rowCount for " << d->urlForNode(parentNode) << ": " << count << filenames;
#endif
return count;
}
// sibling() calls parent() and isn't virtual! So parent() should be fast...
QModelIndex KDirModel::parent( const QModelIndex & index ) const
{
if (!index.isValid())
return QModelIndex();
KDirModelNode* childNode = static_cast<KDirModelNode*>(index.internalPointer());
Q_ASSERT(childNode);
KDirModelNode* parentNode = childNode->parent();
Q_ASSERT(parentNode);
return d->indexForNode(parentNode); // O(n)
}
static bool lessThan(const KUrl &left, const KUrl &right)
{
return left.url().compare(right.url()) < 0;
}
void KDirModel::requestSequenceIcon(const QModelIndex& index, int sequenceIndex)
{
emit needSequenceIcon(index, sequenceIndex);
}
void KDirModel::setJobTransfersVisible(bool value)
{
if(value) {
d->m_jobTransfersVisible = true;
connect(&JobUrlCache::instance(), SIGNAL(jobUrlsChanged(QStringList)), this, SLOT(_k_slotJobUrlsChanged(QStringList)), Qt::UniqueConnection);
JobUrlCache::instance().requestJobUrlsChanged();
} else {
disconnect(this, SLOT(_k_slotJobUrlsChanged(QStringList)));
}
}
bool KDirModel::jobTransfersVisible() const
{
return d->m_jobTransfersVisible;
}
KUrl::List KDirModel::simplifiedUrlList(const KUrl::List &urls)
{
if (!urls.count()) {
return urls;
}
KUrl::List ret(urls);
qSort(ret.begin(), ret.end(), lessThan);
KUrl::List::iterator it = ret.begin();
KUrl url = *it;
++it;
while (it != ret.end()) {
if (url.isParentOf(*it)) {
it = ret.erase(it);
} else {
url = *it;
++it;
}
}
return ret;
}
QStringList KDirModel::mimeTypes( ) const
{
return KUrl::List::mimeDataTypes();
}
QMimeData * KDirModel::mimeData( const QModelIndexList & indexes ) const
{
KUrl::List urls, mostLocalUrls;
bool canUseMostLocalUrls = true;
foreach (const QModelIndex &index, indexes) {
const KFileItem& item = d->nodeForIndex(index)->item();
urls << item.url();
bool isLocal;
mostLocalUrls << item.mostLocalUrl(isLocal);
if (!isLocal)
canUseMostLocalUrls = false;
}
QMimeData *data = new QMimeData();
const bool different = canUseMostLocalUrls && (mostLocalUrls != urls);
urls = simplifiedUrlList(urls);
if (different) {
mostLocalUrls = simplifiedUrlList(mostLocalUrls);
urls.populateMimeData(mostLocalUrls, data);
} else {
urls.populateMimeData(data);
}
// for compatibility reasons (when dropping or pasting into kde3 applications)
QString application_x_qiconlist;
const int items = urls.count();
for (int i = 0; i < items; i++) {
const int offset = i*16;
QString tmp("%1$@@$%2$@@$32$@@$32$@@$%3$@@$%4$@@$32$@@$16$@@$no data$@@$");
application_x_qiconlist += tmp.arg(offset).arg(offset).arg(offset).arg(offset+40);
}
data->setData("application/x-qiconlist", application_x_qiconlist.toLatin1());
return data;
}
// Public API; not much point in calling it internally
KFileItem KDirModel::itemForIndex( const QModelIndex& index ) const
{
if (!index.isValid()) {
return d->m_dirLister->rootItem();
} else {
return static_cast<KDirModelNode*>(index.internalPointer())->item();
}
}
#ifndef KDE_NO_DEPRECATED
QModelIndex KDirModel::indexForItem( const KFileItem* item ) const
{
// Note that we can only use the URL here, not the pointer.
// KFileItems can be copied.
return indexForUrl(item->url()); // O(n)
}
#endif
QModelIndex KDirModel::indexForItem( const KFileItem& item ) const
{
// Note that we can only use the URL here, not the pointer.
// KFileItems can be copied.
return indexForUrl(item.url()); // O(n)
}
// url -> index. O(n)
QModelIndex KDirModel::indexForUrl(const KUrl& url) const
{
KDirModelNode* node = d->nodeForUrl(url); // O(depth)
if (!node) {
kDebug(7007) << url << "not found";
return QModelIndex();
}
return d->indexForNode(node); // O(n)
}
QModelIndex KDirModel::index( int row, int column, const QModelIndex & parent ) const
{
KDirModelNode* parentNode = d->nodeForIndex(parent); // O(1)
Q_ASSERT(parentNode);
Q_ASSERT(d->isDir(parentNode));
KDirModelNode* childNode = static_cast<KDirModelDirNode *>(parentNode)->m_childNodes.value(row); // O(1)
if (childNode)
return createIndex(row, column, childNode);
else
return QModelIndex();
}
QVariant KDirModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
if (orientation == Qt::Horizontal) {
switch (role) {
case Qt::DisplayRole:
switch (section) {
case Name:
return i18nc("@title:column","Name");
case Size:
return i18nc("@title:column","Size");
case ModifiedTime:
return i18nc("@title:column","Date");
case Permissions:
return i18nc("@title:column","Permissions");
case Owner:
return i18nc("@title:column","Owner");
case Group:
return i18nc("@title:column","Group");
case Type:
return i18nc("@title:column","Type");
}
}
}
return QVariant();
}
bool KDirModel::hasChildren( const QModelIndex & parent ) const
{
if (!parent.isValid())
return true;
const KFileItem& parentItem = static_cast<KDirModelNode*>(parent.internalPointer())->item();
Q_ASSERT(!parentItem.isNull());
return parentItem.isDir();
}
Qt::ItemFlags KDirModel::flags( const QModelIndex & index ) const
{
Qt::ItemFlags f = Qt::ItemIsEnabled;
if (index.column() == Name) {
f |= Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
}
// Allow dropping onto this item?
if (d->m_dropsAllowed != NoDrops) {
if(!index.isValid()) {
if (d->m_dropsAllowed & DropOnDirectory) {
f |= Qt::ItemIsDropEnabled;
}
} else {
KFileItem item = itemForIndex(index);
if (item.isNull()) {
kWarning(7007) << "Invalid item returned for index";
} else if (item.isDir()) {
if (d->m_dropsAllowed & DropOnDirectory) {
f |= Qt::ItemIsDropEnabled;
}
} else { // regular file item
if (d->m_dropsAllowed & DropOnAnyFile)
f |= Qt::ItemIsDropEnabled;
else if (d->m_dropsAllowed & DropOnLocalExecutable) {
if (!item.localPath().isEmpty()) {
// Desktop file?
if (item.mimeTypePtr()->is("application/x-desktop"))
f |= Qt::ItemIsDropEnabled;
// Executable, shell script ... ?
else if ( QFileInfo( item.localPath() ).isExecutable() )
f |= Qt::ItemIsDropEnabled;
}
}
}
}
}
return f;
}
bool KDirModel::canFetchMore( const QModelIndex & parent ) const
{
if (!parent.isValid())
return false;
// We now have a bool KDirModelNode::m_populated,
// to avoid calling fetchMore more than once on empty dirs.
// But this wastes memory, and how often does someone open and re-open an empty dir in a treeview?
// Maybe we can ask KDirLister "have you listed <url> already"? (to discuss with M. Brade)
KDirModelNode* node = static_cast<KDirModelNode*>(parent.internalPointer());
const KFileItem& item = node->item();
return item.isDir() && !static_cast<KDirModelDirNode *>(node)->isPopulated()
&& static_cast<KDirModelDirNode *>(node)->m_childNodes.isEmpty();
}
void KDirModel::fetchMore( const QModelIndex & parent )
{
if (!parent.isValid())
return;
KDirModelNode* parentNode = static_cast<KDirModelNode*>(parent.internalPointer());
KFileItem parentItem = parentNode->item();
Q_ASSERT(!parentItem.isNull());
Q_ASSERT(parentItem.isDir());
KDirModelDirNode* dirNode = static_cast<KDirModelDirNode *>(parentNode);
if( dirNode->isPopulated() )
return;
dirNode->setPopulated( true );
const KUrl parentUrl = parentItem.url();
d->m_dirLister->openUrl(parentUrl, KDirLister::Keep);
}
bool KDirModel::dropMimeData( const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent )
{
// Not sure we want to implement any drop handling at this level,
// but for sure the default QAbstractItemModel implementation makes no sense for a dir model.
Q_UNUSED(data);
Q_UNUSED(action);
Q_UNUSED(row);
Q_UNUSED(column);
Q_UNUSED(parent);
return false;
}
void KDirModel::setDropsAllowed(DropsAllowed dropsAllowed)
{
d->m_dropsAllowed = dropsAllowed;
}
void KDirModel::expandToUrl(const KUrl& url)
{
// emit expand for each parent and return last parent
KDirModelNode* result = d->expandAllParentsUntil(url); // O(depth)
//kDebug(7008) << url << result;
if (!result) // doesn't seem related to our base url?
return;
if (!(result->item().isNull()) && result->item().url() == url) {
// We have it already, nothing to do
kDebug(7008) << "have it already item=" <<url /*result->item()*/;
return;
}
d->m_urlsBeingFetched[result].append(url);
if (result == d->m_rootNode) {
kDebug(7008) << "Remembering to emit expand after listing the root url";
// the root is fetched by default, so it must be currently being fetched
return;
}
kDebug(7008) << "Remembering to emit expand after listing" << result->item().url();
// start a new fetch to look for the next level down the URL
const QModelIndex parentIndex = d->indexForNode(result); // O(n)
Q_ASSERT(parentIndex.isValid());
fetchMore(parentIndex);
}
bool KDirModel::insertRows(int , int, const QModelIndex&)
{
return false;
}
bool KDirModel::insertColumns(int, int, const QModelIndex&)
{
return false;
}
bool KDirModel::removeRows(int, int, const QModelIndex&)
{
return false;
}
bool KDirModel::removeColumns(int, int, const QModelIndex&)
{
return false;
}
#include "moc_kdirmodel.cpp"
diff --git a/kio/kio/kfileitem.cpp b/kio/kio/kfileitem.cpp
index 6e3e431323..d12b584352 100644
--- a/kio/kio/kfileitem.cpp
+++ b/kio/kio/kfileitem.cpp
@@ -1,1642 +1,1642 @@
/* This file is part of the KDE project
Copyright (C) 1999-2011 David Faure <faure@kde.org>
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.
*/
#include "kfileitem.h"
#include <config.h>
#include <config-kio.h>
#include <sys/time.h>
#include <pwd.h>
#include <grp.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <unistd.h>
#include <QtCore/QDate>
#include <QtCore/QDir>
#include <QtCore/QDirIterator>
#include <QtCore/QFile>
#include <QtCore/QMap>
#include <QApplication>
#include <QPalette>
#include <QTextDocument>
#include <QMimeDatabase>
#include <kdebug.h>
#include <kfilemetainfo.h>
#include <kglobal.h>
#include <kiconloader.h>
#include <klocale.h>
#include <kmimetype.h>
#include <krun.h>
#include <kde_file.h>
#include <kdesktopfile.h>
#include <kmountpoint.h>
#include <kconfiggroup.h>
#ifndef Q_OS_WIN
#include <knfsshare.h>
#include <ksambashare.h>
#endif
#include <kfilesystemtype_p.h>
class KFileItemPrivate : public QSharedData
{
public:
KFileItemPrivate(const KIO::UDSEntry& entry,
mode_t mode, mode_t permissions,
const KUrl& itemOrDirUrl,
bool urlIsDirectory,
bool delayedMimeTypes)
: m_entry( entry ),
m_url(itemOrDirUrl),
m_strName(),
m_strText(),
m_iconName(),
m_strLowerCaseName(),
m_pMimeType( 0 ),
m_fileMode( mode ),
m_permissions( permissions ),
m_bMarked( false ),
m_bLink( false ),
m_bIsLocalUrl(itemOrDirUrl.isLocalFile()),
m_bMimeTypeKnown( false ),
m_delayedMimeTypes( delayedMimeTypes ),
m_useIconNameCache(false),
m_hidden(Auto),
m_slow(SlowUnknown)
{
if (entry.count() != 0) {
readUDSEntry( urlIsDirectory );
} else {
Q_ASSERT(!urlIsDirectory);
m_strName = itemOrDirUrl.fileName();
m_strText = KIO::decodeFileName( m_strName );
}
init();
}
~KFileItemPrivate()
{
}
/**
* Computes the text and mode from the UDSEntry
* Called by constructor, but can be called again later
* Nothing does that anymore though (I guess some old KonqFileItem did)
* so it's not a protected method of the public class anymore.
*/
void init();
QString localPath() const;
KIO::filesize_t size() const;
KDateTime time( KFileItem::FileTimes which ) const;
void setTime(KFileItem::FileTimes which, long long time_t_val) const;
bool cmp( const KFileItemPrivate & item ) const;
QString user() const;
QString group() const;
bool isSlow() const;
/**
* Extracts the data from the UDSEntry member and updates the KFileItem
* accordingly.
*/
void readUDSEntry( bool _urlIsDirectory );
/**
* Parses the given permission set and provides it for access()
*/
QString parsePermissions( mode_t perm ) const;
/**
* The UDSEntry that contains the data for this fileitem, if it came from a directory listing.
*/
mutable KIO::UDSEntry m_entry;
/**
* The url of the file
*/
KUrl m_url;
/**
* The text for this item, i.e. the file name without path,
*/
QString m_strName;
/**
* The text for this item, i.e. the file name without path, decoded
* ('%%' becomes '%', '%2F' becomes '/')
*/
QString m_strText;
/**
* The icon name for this item.
*/
mutable QString m_iconName;
/**
* The filename in lower case (to speed up sorting)
*/
mutable QString m_strLowerCaseName;
/**
* The mimetype of the file
*/
mutable KMimeType::Ptr m_pMimeType;
/**
* The file mode
*/
mode_t m_fileMode;
/**
* The permissions
*/
mode_t m_permissions;
/**
* Marked : see mark()
*/
bool m_bMarked:1;
/**
* Whether the file is a link
*/
bool m_bLink:1;
/**
* True if local file
*/
bool m_bIsLocalUrl:1;
mutable bool m_bMimeTypeKnown:1;
bool m_delayedMimeTypes:1;
/** True if m_iconName should be used as cache. */
mutable bool m_useIconNameCache:1;
// Auto: check leading dot.
enum { Auto, Hidden, Shown } m_hidden:3;
// Slow? (nfs/smb/ssh)
mutable enum { SlowUnknown, Fast, Slow } m_slow:3;
// For special case like link to dirs over FTP
QString m_guessedMimeType;
mutable QString m_access;
#ifndef KDE_NO_DEPRECATED
QMap<const void*, void*> m_extra; // DEPRECATED
#endif
mutable KFileMetaInfo m_metaInfo;
enum { NumFlags = KFileItem::CreationTime + 1 };
mutable KDateTime m_time[3];
};
void KFileItemPrivate::init()
{
m_access.clear();
// metaInfo = KFileMetaInfo();
// determine mode and/or permissions if unknown
// TODO: delay this until requested
if ( m_fileMode == KFileItem::Unknown || m_permissions == KFileItem::Unknown )
{
mode_t mode = 0;
if ( m_url.isLocalFile() )
{
/* directories may not have a slash at the end if
* we want to stat() them; it requires that we
* change into it .. which may not be allowed
* stat("/is/unaccessible") -> rwx------
* stat("/is/unaccessible/") -> EPERM H.Z.
* This is the reason for the -1
*/
KDE_struct_stat buf;
const QString path = m_url.toLocalFile( KUrl::RemoveTrailingSlash );
if ( KDE::lstat( path, &buf ) == 0 )
{
mode = buf.st_mode;
if ( S_ISLNK( mode ) )
{
m_bLink = true;
if ( KDE::stat( path, &buf ) == 0 )
mode = buf.st_mode;
else // link pointing to nowhere (see kio/file/file.cc)
mode = (S_IFMT-1) | S_IRWXU | S_IRWXG | S_IRWXO;
}
// While we're at it, store the times
setTime(KFileItem::ModificationTime, buf.st_mtime);
setTime(KFileItem::AccessTime, buf.st_atime);
if ( m_fileMode == KFileItem::Unknown )
m_fileMode = mode & S_IFMT; // extract file type
if ( m_permissions == KFileItem::Unknown )
m_permissions = mode & 07777; // extract permissions
} else {
kDebug() << path << "does not exist anymore";
}
}
}
}
void KFileItemPrivate::readUDSEntry( bool _urlIsDirectory )
{
// extract fields from the KIO::UDS Entry
m_fileMode = m_entry.numberValue( KIO::UDSEntry::UDS_FILE_TYPE );
m_permissions = m_entry.numberValue( KIO::UDSEntry::UDS_ACCESS );
m_strName = m_entry.stringValue( KIO::UDSEntry::UDS_NAME );
const QString displayName = m_entry.stringValue( KIO::UDSEntry::UDS_DISPLAY_NAME );
if (!displayName.isEmpty())
m_strText = displayName;
else
m_strText = KIO::decodeFileName( m_strName );
const QString urlStr = m_entry.stringValue( KIO::UDSEntry::UDS_URL );
const bool UDS_URL_seen = !urlStr.isEmpty();
if ( UDS_URL_seen ) {
m_url = KUrl( urlStr );
if ( m_url.isLocalFile() )
m_bIsLocalUrl = true;
}
const QString mimeTypeStr = m_entry.stringValue( KIO::UDSEntry::UDS_MIME_TYPE );
m_bMimeTypeKnown = !mimeTypeStr.isEmpty();
if ( m_bMimeTypeKnown )
m_pMimeType = KMimeType::mimeType( mimeTypeStr );
m_guessedMimeType = m_entry.stringValue( KIO::UDSEntry::UDS_GUESSED_MIME_TYPE );
m_bLink = !m_entry.stringValue( KIO::UDSEntry::UDS_LINK_DEST ).isEmpty(); // we don't store the link dest
const int hiddenVal = m_entry.numberValue( KIO::UDSEntry::UDS_HIDDEN, -1 );
m_hidden = hiddenVal == 1 ? Hidden : ( hiddenVal == 0 ? Shown : Auto );
// avoid creating these QStrings again and again
static const QString& dot = KGlobal::staticQString(".");
if ( _urlIsDirectory && !UDS_URL_seen && !m_strName.isEmpty() && m_strName != dot )
m_url.addPath( m_strName );
m_iconName.clear();
}
inline //because it is used only in one place
KIO::filesize_t KFileItemPrivate::size() const
{
// Extract it from the KIO::UDSEntry
long long fieldVal = m_entry.numberValue( KIO::UDSEntry::UDS_SIZE, -1 );
if ( fieldVal != -1 ) {
return fieldVal;
}
// If not in the KIO::UDSEntry, or if UDSEntry empty, use stat() [if local URL]
if ( m_bIsLocalUrl ) {
KDE_struct_stat buf;
if ( KDE::stat( m_url.toLocalFile(KUrl::RemoveTrailingSlash), &buf ) == 0 )
return buf.st_size;
}
return 0;
}
void KFileItemPrivate::setTime(KFileItem::FileTimes mappedWhich, long long time_t_val) const
{
m_time[mappedWhich].setTime_t(time_t_val);
m_time[mappedWhich] = m_time[mappedWhich].toLocalZone(); // #160979
}
KDateTime KFileItemPrivate::time( KFileItem::FileTimes mappedWhich ) const
{
if ( !m_time[mappedWhich].isNull() )
return m_time[mappedWhich];
// Extract it from the KIO::UDSEntry
long long fieldVal = -1;
switch ( mappedWhich ) {
case KFileItem::ModificationTime:
fieldVal = m_entry.numberValue( KIO::UDSEntry::UDS_MODIFICATION_TIME, -1 );
break;
case KFileItem::AccessTime:
fieldVal = m_entry.numberValue( KIO::UDSEntry::UDS_ACCESS_TIME, -1 );
break;
case KFileItem::CreationTime:
fieldVal = m_entry.numberValue( KIO::UDSEntry::UDS_CREATION_TIME, -1 );
break;
}
if ( fieldVal != -1 ) {
setTime(mappedWhich, fieldVal);
return m_time[mappedWhich];
}
// If not in the KIO::UDSEntry, or if UDSEntry empty, use stat() [if local URL]
if ( m_bIsLocalUrl )
{
KDE_struct_stat buf;
if ( KDE::stat( m_url.toLocalFile(KUrl::RemoveTrailingSlash), &buf ) == 0 )
{
setTime(KFileItem::ModificationTime, buf.st_mtime);
setTime(KFileItem::AccessTime, buf.st_atime);
m_time[KFileItem::CreationTime] = KDateTime();
return m_time[mappedWhich];
}
}
return KDateTime();
}
inline //because it is used only in one place
bool KFileItemPrivate::cmp( const KFileItemPrivate & item ) const
{
#if 0
kDebug() << "Comparing" << m_url << "and" << item.m_url;
kDebug() << " name" << (m_strName == item.m_strName);
kDebug() << " local" << (m_bIsLocalUrl == item.m_bIsLocalUrl);
kDebug() << " mode" << (m_fileMode == item.m_fileMode);
kDebug() << " perm" << (m_permissions == item.m_permissions);
kDebug() << " UDS_USER" << (user() == item.user());
kDebug() << " UDS_GROUP" << (group() == item.group());
kDebug() << " UDS_EXTENDED_ACL" << (m_entry.stringValue( KIO::UDSEntry::UDS_EXTENDED_ACL ) == item.m_entry.stringValue( KIO::UDSEntry::UDS_EXTENDED_ACL ));
kDebug() << " UDS_ACL_STRING" << (m_entry.stringValue( KIO::UDSEntry::UDS_ACL_STRING ) == item.m_entry.stringValue( KIO::UDSEntry::UDS_ACL_STRING ));
kDebug() << " UDS_DEFAULT_ACL_STRING" << (m_entry.stringValue( KIO::UDSEntry::UDS_DEFAULT_ACL_STRING ) == item.m_entry.stringValue( KIO::UDSEntry::UDS_DEFAULT_ACL_STRING ));
kDebug() << " m_bLink" << (m_bLink == item.m_bLink);
kDebug() << " m_hidden" << (m_hidden == item.m_hidden);
kDebug() << " size" << (size() == item.size());
kDebug() << " ModificationTime" << (time(KFileItem::ModificationTime) == item.time(KFileItem::ModificationTime));
kDebug() << " UDS_ICON_NAME" << (m_entry.stringValue( KIO::UDSEntry::UDS_ICON_NAME ) == item.m_entry.stringValue( KIO::UDSEntry::UDS_ICON_NAME ));
#endif
return ( m_strName == item.m_strName
&& m_bIsLocalUrl == item.m_bIsLocalUrl
&& m_fileMode == item.m_fileMode
&& m_permissions == item.m_permissions
&& user() == item.user()
&& group() == item.group()
&& m_entry.stringValue( KIO::UDSEntry::UDS_EXTENDED_ACL ) == item.m_entry.stringValue( KIO::UDSEntry::UDS_EXTENDED_ACL )
&& m_entry.stringValue( KIO::UDSEntry::UDS_ACL_STRING ) == item.m_entry.stringValue( KIO::UDSEntry::UDS_ACL_STRING )
&& m_entry.stringValue( KIO::UDSEntry::UDS_DEFAULT_ACL_STRING ) == item.m_entry.stringValue( KIO::UDSEntry::UDS_DEFAULT_ACL_STRING )
&& m_bLink == item.m_bLink
&& m_hidden == item.m_hidden
&& size() == item.size()
&& time(KFileItem::ModificationTime) == item.time(KFileItem::ModificationTime) // TODO only if already known!
&& m_entry.stringValue( KIO::UDSEntry::UDS_ICON_NAME ) == item.m_entry.stringValue( KIO::UDSEntry::UDS_ICON_NAME )
);
// Don't compare the mimetypes here. They might not be known, and we don't want to
// do the slow operation of determining them here.
}
inline //because it is used only in one place
QString KFileItemPrivate::parsePermissions(mode_t perm) const
{
static char buffer[ 12 ];
char uxbit,gxbit,oxbit;
if ( (perm & (S_IXUSR|S_ISUID)) == (S_IXUSR|S_ISUID) )
uxbit = 's';
else if ( (perm & (S_IXUSR|S_ISUID)) == S_ISUID )
uxbit = 'S';
else if ( (perm & (S_IXUSR|S_ISUID)) == S_IXUSR )
uxbit = 'x';
else
uxbit = '-';
if ( (perm & (S_IXGRP|S_ISGID)) == (S_IXGRP|S_ISGID) )
gxbit = 's';
else if ( (perm & (S_IXGRP|S_ISGID)) == S_ISGID )
gxbit = 'S';
else if ( (perm & (S_IXGRP|S_ISGID)) == S_IXGRP )
gxbit = 'x';
else
gxbit = '-';
if ( (perm & (S_IXOTH|S_ISVTX)) == (S_IXOTH|S_ISVTX) )
oxbit = 't';
else if ( (perm & (S_IXOTH|S_ISVTX)) == S_ISVTX )
oxbit = 'T';
else if ( (perm & (S_IXOTH|S_ISVTX)) == S_IXOTH )
oxbit = 'x';
else
oxbit = '-';
// Include the type in the first char like kde3 did; people are more used to seeing it,
// even though it's not really part of the permissions per se.
if (m_bLink)
buffer[0] = 'l';
else if (m_fileMode != KFileItem::Unknown) {
if (S_ISDIR(m_fileMode))
buffer[0] = 'd';
else if (S_ISSOCK(m_fileMode))
buffer[0] = 's';
else if (S_ISCHR(m_fileMode))
buffer[0] = 'c';
else if (S_ISBLK(m_fileMode))
buffer[0] = 'b';
else if (S_ISFIFO(m_fileMode))
buffer[0] = 'p';
else
buffer[0] = '-';
} else {
buffer[0] = '-';
}
buffer[1] = ((( perm & S_IRUSR ) == S_IRUSR ) ? 'r' : '-' );
buffer[2] = ((( perm & S_IWUSR ) == S_IWUSR ) ? 'w' : '-' );
buffer[3] = uxbit;
buffer[4] = ((( perm & S_IRGRP ) == S_IRGRP ) ? 'r' : '-' );
buffer[5] = ((( perm & S_IWGRP ) == S_IWGRP ) ? 'w' : '-' );
buffer[6] = gxbit;
buffer[7] = ((( perm & S_IROTH ) == S_IROTH ) ? 'r' : '-' );
buffer[8] = ((( perm & S_IWOTH ) == S_IWOTH ) ? 'w' : '-' );
buffer[9] = oxbit;
// if (hasExtendedACL())
if (m_entry.contains(KIO::UDSEntry::UDS_EXTENDED_ACL)) {
buffer[10] = '+';
buffer[11] = 0;
} else {
buffer[10] = 0;
}
return QString::fromLatin1(buffer);
}
///////
KFileItem::KFileItem()
: d(0)
{
}
KFileItem::KFileItem( const KIO::UDSEntry& entry, const KUrl& itemOrDirUrl,
bool delayedMimeTypes, bool urlIsDirectory )
: d(new KFileItemPrivate(entry, KFileItem::Unknown, KFileItem::Unknown,
itemOrDirUrl, urlIsDirectory, delayedMimeTypes))
{
}
KFileItem::KFileItem( mode_t mode, mode_t permissions, const KUrl& url, bool delayedMimeTypes )
: d(new KFileItemPrivate(KIO::UDSEntry(), mode, permissions,
url, false, delayedMimeTypes))
{
}
KFileItem::KFileItem( const KUrl &url, const QString &mimeType, mode_t mode )
: d(new KFileItemPrivate(KIO::UDSEntry(), mode, KFileItem::Unknown,
url, false, false))
{
d->m_bMimeTypeKnown = !mimeType.isEmpty();
if (d->m_bMimeTypeKnown)
d->m_pMimeType = KMimeType::mimeType( mimeType );
}
KFileItem::KFileItem(const KFileItem& other)
: d(other.d)
{
}
KFileItem::~KFileItem()
{
}
void KFileItem::refresh()
{
d->m_fileMode = KFileItem::Unknown;
d->m_permissions = KFileItem::Unknown;
d->m_metaInfo = KFileMetaInfo();
d->m_hidden = KFileItemPrivate::Auto;
refreshMimeType();
// Basically, we can't trust any information we got while listing.
// Everything could have changed...
// Clearing m_entry makes it possible to detect changes in the size of the file,
// the time information, etc.
d->m_entry.clear();
d->init();
}
void KFileItem::refreshMimeType()
{
d->m_pMimeType = 0;
d->m_bMimeTypeKnown = false;
d->m_iconName.clear();
}
void KFileItem::setUrl( const KUrl &url )
{
d->m_url = url;
setName( url.fileName() );
}
void KFileItem::setName( const QString& name )
{
d->m_strName = name;
d->m_strText = KIO::decodeFileName( d->m_strName );
if (d->m_entry.contains(KIO::UDSEntry::UDS_NAME))
d->m_entry.insert(KIO::UDSEntry::UDS_NAME, d->m_strName); // #195385
}
QString KFileItem::linkDest() const
{
// Extract it from the KIO::UDSEntry
const QString linkStr = d->m_entry.stringValue( KIO::UDSEntry::UDS_LINK_DEST );
if ( !linkStr.isEmpty() )
return linkStr;
// If not in the KIO::UDSEntry, or if UDSEntry empty, use readlink() [if local URL]
if ( d->m_bIsLocalUrl )
{
char buf[1000];
int n = readlink( QFile::encodeName(d->m_url.toLocalFile( KUrl::RemoveTrailingSlash )), buf, sizeof(buf)-1 );
if ( n != -1 )
{
buf[ n ] = 0;
return QFile::decodeName( buf );
}
}
return QString();
}
QString KFileItemPrivate::localPath() const
{
if (m_bIsLocalUrl) {
return m_url.toLocalFile();
}
// Extract the local path from the KIO::UDSEntry
return m_entry.stringValue( KIO::UDSEntry::UDS_LOCAL_PATH );
}
QString KFileItem::localPath() const
{
return d->localPath();
}
KIO::filesize_t KFileItem::size() const
{
return d->size();
}
bool KFileItem::hasExtendedACL() const
{
// Check if the field exists; its value doesn't matter
return d->m_entry.contains(KIO::UDSEntry::UDS_EXTENDED_ACL);
}
KACL KFileItem::ACL() const
{
if ( hasExtendedACL() ) {
// Extract it from the KIO::UDSEntry
const QString fieldVal = d->m_entry.stringValue( KIO::UDSEntry::UDS_ACL_STRING );
if ( !fieldVal.isEmpty() )
return KACL( fieldVal );
}
// create one from the basic permissions
return KACL( d->m_permissions );
}
KACL KFileItem::defaultACL() const
{
// Extract it from the KIO::UDSEntry
const QString fieldVal = d->m_entry.stringValue( KIO::UDSEntry::UDS_DEFAULT_ACL_STRING );
if ( !fieldVal.isEmpty() )
return KACL(fieldVal);
else
return KACL();
}
KDateTime KFileItem::time( FileTimes which ) const
{
return d->time(which);
}
#ifndef KDE_NO_DEPRECATED
time_t KFileItem::time( unsigned int which ) const
{
switch (which) {
case KIO::UDSEntry::UDS_ACCESS_TIME:
return d->time(AccessTime).toTime_t();
case KIO::UDSEntry::UDS_CREATION_TIME:
return d->time(CreationTime).toTime_t();
case KIO::UDSEntry::UDS_MODIFICATION_TIME:
default:
return d->time(ModificationTime).toTime_t();
}
}
#endif
QString KFileItem::user() const
{
return d->user();
}
QString KFileItemPrivate::user() const
{
QString userName = m_entry.stringValue(KIO::UDSEntry::UDS_USER);
if (userName.isEmpty() && m_bIsLocalUrl) {
#ifdef Q_WS_WIN
QFileInfo a(m_url.toLocalFile( KUrl::RemoveTrailingSlash ));
userName = a.owner();
m_entry.insert( KIO::UDSEntry::UDS_USER, userName );
#else
KDE_struct_stat buff;
if ( KDE::lstat( m_url.toLocalFile( KUrl::RemoveTrailingSlash ), &buff ) == 0) // get uid/gid of the link, if it's a link
{
struct passwd *pwuser = getpwuid( buff.st_uid );
if ( pwuser != 0 ) {
userName = QString::fromLocal8Bit(pwuser->pw_name);
m_entry.insert( KIO::UDSEntry::UDS_USER, userName );
}
}
#endif
}
return userName;
}
QString KFileItem::group() const
{
return d->group();
}
QString KFileItemPrivate::group() const
{
QString groupName = m_entry.stringValue( KIO::UDSEntry::UDS_GROUP );
if (groupName.isEmpty() && m_bIsLocalUrl )
{
#ifdef Q_WS_WIN
QFileInfo a(m_url.toLocalFile( KUrl::RemoveTrailingSlash ));
groupName = a.group();
m_entry.insert( KIO::UDSEntry::UDS_GROUP, groupName );
#else
KDE_struct_stat buff;
if ( KDE::lstat( m_url.toLocalFile( KUrl::RemoveTrailingSlash ), &buff ) == 0) // get uid/gid of the link, if it's a link
{
struct group *ge = getgrgid( buff.st_gid );
if ( ge != 0 ) {
groupName = QString::fromLocal8Bit(ge->gr_name);
if (groupName.isEmpty())
groupName.sprintf("%d",ge->gr_gid);
}
else
groupName.sprintf("%d",buff.st_gid);
m_entry.insert( KIO::UDSEntry::UDS_GROUP, groupName );
}
#endif
}
return groupName;
}
bool KFileItemPrivate::isSlow() const
{
if (m_slow == SlowUnknown) {
const QString path = localPath();
if (!path.isEmpty()) {
const KFileSystemType::Type fsType = KFileSystemType::fileSystemType(path);
m_slow = (fsType == KFileSystemType::Nfs || fsType == KFileSystemType::Smb) ? Slow : Fast;
} else {
m_slow = Slow;
}
}
return m_slow == Slow;
}
bool KFileItem::isSlow() const
{
return d->isSlow();
}
QString KFileItem::mimetype() const
{
KFileItem * that = const_cast<KFileItem *>(this);
return that->determineMimeType()->name();
}
KMimeType::Ptr KFileItem::determineMimeType() const
{
if ( !d->m_pMimeType || !d->m_bMimeTypeKnown )
{
bool isLocalUrl;
KUrl url = mostLocalUrl(isLocalUrl);
d->m_pMimeType = KMimeType::findByUrl( url, d->m_fileMode, isLocalUrl );
Q_ASSERT(d->m_pMimeType);
//kDebug() << d << "finding final mimetype for" << url << ":" << d->m_pMimeType->name();
d->m_bMimeTypeKnown = true;
}
return d->m_pMimeType;
}
bool KFileItem::isMimeTypeKnown() const
{
// The mimetype isn't known if determineMimeType was never called (on-demand determination)
// or if this fileitem has a guessed mimetype (e.g. ftp symlink) - in which case
// it always remains "not fully determined"
return d->m_bMimeTypeKnown && d->m_guessedMimeType.isEmpty();
}
static bool isDirectoryMounted(const KUrl& url)
{
// Stating .directory files can cause long freezes when e.g. /home
// uses autofs for every user's home directory, i.e. opening /home
// in a file dialog will mount every single home directory.
// These non-mounted directories can be identified by having 0 size.
// There are also other directories with 0 size, such as /proc, that may
// be mounted, but those are unlikely to contain .directory (and checking
// this would require checking with KMountPoint).
// TODO: maybe this could be checked with KFileSystemType instead?
KDE_struct_stat buff;
if (KDE_stat(QFile::encodeName(url.toLocalFile()), &buff) == 0
&& S_ISDIR(buff.st_mode) && buff.st_size == 0) {
return false;
}
return true;
}
// KDE5 TODO: merge with comment()? Need to see what lxr says about the usage of both.
QString KFileItem::mimeComment() const
{
const QString displayType = d->m_entry.stringValue( KIO::UDSEntry::UDS_DISPLAY_TYPE );
if (!displayType.isEmpty())
return displayType;
bool isLocalUrl;
KUrl url = mostLocalUrl(isLocalUrl);
KMimeType::Ptr mime = mimeTypePtr();
// This cannot move to kio_file (with UDS_DISPLAY_TYPE) because it needs
// the mimetype to be determined, which is done here, and possibly delayed...
if (isLocalUrl && !d->isSlow() && mime->is("application/x-desktop")) {
KDesktopFile cfg( url.toLocalFile() );
QString comment = cfg.desktopGroup().readEntry( "Comment" );
if (!comment.isEmpty())
return comment;
}
// Support for .directory file in directories
if (isLocalUrl && isDir() && isDirectoryMounted(url)) {
KUrl u(url);
u.addPath(QString::fromLatin1(".directory"));
const KDesktopFile cfg(u.toLocalFile());
const QString comment = cfg.readComment();
if (!comment.isEmpty())
return comment;
}
const QString comment = mime->comment();
//kDebug() << "finding comment for " << url.url() << " : " << d->m_pMimeType->name();
if (!comment.isEmpty())
return comment;
else
return mime->name();
}
static QString iconFromDirectoryFile(const QString& path)
{
KUrl u(path);
u.addPath(QString::fromLatin1(".directory"));
const QString filePath = u.toLocalFile();
if (!QFileInfo(filePath).isFile()) // exists -and- is a file
return QString();
KDesktopFile cfg(filePath);
QString icon = cfg.readIcon();
const KConfigGroup group = cfg.desktopGroup();
const QString emptyIcon = group.readEntry( "EmptyIcon" );
if (!emptyIcon.isEmpty()) {
bool isDirEmpty = true;
QDirIterator dirIt(path, QDir::Dirs|QDir::Files|QDir::NoDotAndDotDot);
while (dirIt.hasNext()) {
dirIt.next();
if (dirIt.fileName() != QLatin1String(".directory")) {
isDirEmpty = false;
break;
}
}
if (isDirEmpty) {
icon = emptyIcon;
}
}
if (icon.startsWith(QLatin1String("./"))) {
// path is relative with respect to the location
// of the .directory file (#73463)
KUrl iconUrl(path);
iconUrl.addPath(icon.mid(2));
return iconUrl.toLocalFile();
}
return icon;
}
static QString iconFromDesktopFile(const QString& path)
{
KDesktopFile cfg(path);
const QString icon = cfg.readIcon();
if (cfg.hasLinkType()) {
const KConfigGroup group = cfg.desktopGroup();
const QString emptyIcon = group.readEntry( "EmptyIcon" );
const QString type = cfg.readPath();
if ( !emptyIcon.isEmpty() ) {
const QString u = cfg.readUrl();
const KUrl url( u );
- if ( url.protocol() == "trash" ) {
+ if ( url.scheme() == "trash" ) {
// We need to find if the trash is empty, preferably without using a KIO job.
// So instead kio_trash leaves an entry in its config file for us.
KConfig trashConfig( "trashrc", KConfig::SimpleConfig );
if ( trashConfig.group("Status").readEntry( "Empty", true ) ) {
return emptyIcon;
}
}
}
}
return icon;
}
QString KFileItem::iconName() const
{
if (d->m_useIconNameCache && !d->m_iconName.isEmpty()) {
return d->m_iconName;
}
d->m_iconName = d->m_entry.stringValue( KIO::UDSEntry::UDS_ICON_NAME );
if (!d->m_iconName.isEmpty()) {
d->m_useIconNameCache = d->m_bMimeTypeKnown;
return d->m_iconName;
}
bool isLocalUrl;
KUrl url = mostLocalUrl(isLocalUrl);
KMimeType::Ptr mime;
// Use guessed mimetype for the icon
if (!d->m_guessedMimeType.isEmpty()) {
mime = KMimeType::mimeType(d->m_guessedMimeType);
} else {
mime = mimeTypePtr();
}
if (isLocalUrl && !isSlow() && mime->is("application/x-desktop")) {
d->m_iconName = iconFromDesktopFile(url.toLocalFile());
if (!d->m_iconName.isEmpty()) {
d->m_useIconNameCache = d->m_bMimeTypeKnown;
return d->m_iconName;
}
}
if (isLocalUrl && isDir() && isDirectoryMounted(url)) {
d->m_iconName = iconFromDirectoryFile(url.toLocalFile());
if (!d->m_iconName.isEmpty()) {
d->m_useIconNameCache = d->m_bMimeTypeKnown;
return d->m_iconName;
}
}
d->m_iconName = mime->iconName();
d->m_useIconNameCache = d->m_bMimeTypeKnown;
return d->m_iconName;
}
/**
* Returns true if this is a desktop file.
* Mimetype determination is optional.
*/
static bool checkDesktopFile(const KFileItem& item, bool _determineMimeType)
{
// only local files
bool isLocal;
const KUrl url = item.mostLocalUrl(isLocal);
if (!isLocal)
return false;
// only regular files
if (!item.isRegularFile())
return false;
// only if readable
if (!item.isReadable())
return false;
// return true if desktop file
KMimeType::Ptr mime = _determineMimeType ? item.determineMimeType() : item.mimeTypePtr();
return mime->is("application/x-desktop");
}
QStringList KFileItem::overlays() const
{
QStringList names = d->m_entry.stringValue( KIO::UDSEntry::UDS_ICON_OVERLAY_NAMES ).split(',');
if ( d->m_bLink ) {
names.append("emblem-symbolic-link");
}
if ( !S_ISDIR( d->m_fileMode ) // Locked dirs have a special icon, use the overlay for files only
&& !isReadable()) {
names.append("object-locked");
}
if ( checkDesktopFile(*this, false) ) {
KDesktopFile cfg( localPath() );
const KConfigGroup group = cfg.desktopGroup();
// Add a warning emblem if this is an executable desktop file
// which is untrusted.
if ( group.hasKey( "Exec" ) && !KDesktopFile::isAuthorizedDesktopFile( localPath() ) ) {
names.append( "emblem-important" );
}
if (cfg.hasDeviceType()) {
const QString dev = cfg.readDevice();
if (!dev.isEmpty()) {
KMountPoint::Ptr mountPoint = KMountPoint::currentMountPoints().findByDevice(dev);
if (mountPoint) // mounted?
names.append("emblem-mounted");
}
}
}
if ( isHidden() ) {
names.append("hidden");
}
#ifndef Q_OS_WIN
if( S_ISDIR( d->m_fileMode ) && d->m_bIsLocalUrl)
{
if (KSambaShare::instance()->isDirectoryShared( d->m_url.toLocalFile() ) ||
KNFSShare::instance()->isDirectoryShared( d->m_url.toLocalFile() ))
{
//kDebug() << d->m_url.path();
names.append("network-workgroup");
}
}
#endif // Q_OS_WIN
if ( d->m_pMimeType && d->m_url.fileName().endsWith( QLatin1String( ".gz" ) ) &&
d->m_pMimeType->is("application/x-gzip") ) {
names.append("application-zip");
}
return names;
}
QString KFileItem::comment() const
{
return d->m_entry.stringValue( KIO::UDSEntry::UDS_COMMENT );
}
// ## where is this used?
QPixmap KFileItem::pixmap( int _size, int _state ) const
{
const QString udsIconName = d->m_entry.stringValue( KIO::UDSEntry::UDS_ICON_NAME );
if ( !udsIconName.isEmpty() )
return DesktopIcon(udsIconName, _size, _state);
if (!d->m_useIconNameCache && !d->m_pMimeType) {
// No mimetype determined yet, go for a fast default icon
if (S_ISDIR(d->m_fileMode)) {
static const QString * defaultFolderIcon = 0;
if ( !defaultFolderIcon ) {
const KMimeType::Ptr mimeType = KMimeType::mimeType( "inode/directory" );
if ( mimeType )
defaultFolderIcon = &KGlobal::staticQString( mimeType->iconName() );
else
kWarning(7000) << "No mimetype for inode/directory could be found. Check your installation.";
}
if ( defaultFolderIcon )
return DesktopIcon( *defaultFolderIcon, _size, _state );
}
return DesktopIcon( "unknown", _size, _state );
}
KMimeType::Ptr mime;
// Use guessed mimetype for the icon
if (!d->m_guessedMimeType.isEmpty())
mime = KMimeType::mimeType( d->m_guessedMimeType );
else
mime = d->m_pMimeType;
QString icon = iconName();
// Support for gzipped files: extract mimetype of contained file
// See also the relevant code in overlays, which adds the zip overlay.
if ( mime->name() == "application/x-gzip" && d->m_url.fileName().endsWith( QLatin1String( ".gz" ) ) )
{
KUrl sf;
sf.setPath( d->m_url.path().left( d->m_url.path().length() - 3 ) );
//kDebug() << "subFileName=" << subFileName;
mime = KMimeType::findByUrl(sf, 0, d->m_bIsLocalUrl);
icon = mime->iconName();
}
QPixmap p = KIconLoader::global()->loadMimeTypeIcon(icon, KIconLoader::Desktop, _size, _state);
//kDebug() << "finding pixmap for" << url << "mime=" << mime->name() << "icon=" << icon;
if (p.isNull())
kWarning() << "Pixmap not found for mimetype" << mime->name() << "icon" << icon;
return p;
}
bool KFileItem::isReadable() const
{
/*
struct passwd * user = getpwuid( geteuid() );
bool isMyFile = (QString::fromLocal8Bit(user->pw_name) == d->m_user);
// This gets ugly for the group....
// Maybe we want a static QString for the user and a static QStringList
// for the groups... then we need to handle the deletion properly...
*/
if (d->m_permissions != KFileItem::Unknown) {
// No read permission at all
if ( !(S_IRUSR & d->m_permissions) && !(S_IRGRP & d->m_permissions) && !(S_IROTH & d->m_permissions) )
return false;
// Read permissions for all: save a stat call
if ( (S_IRUSR|S_IRGRP|S_IROTH) & d->m_permissions )
return true;
}
// Or if we can't read it [using ::access()] - not network transparent
if ( d->m_bIsLocalUrl && KDE::access( d->m_url.toLocalFile(), R_OK ) == -1 )
return false;
return true;
}
bool KFileItem::isWritable() const
{
/*
struct passwd * user = getpwuid( geteuid() );
bool isMyFile = (QString::fromLocal8Bit(user->pw_name) == d->m_user);
// This gets ugly for the group....
// Maybe we want a static QString for the user and a static QStringList
// for the groups... then we need to handle the deletion properly...
*/
if (d->m_permissions != KFileItem::Unknown) {
// No write permission at all
if ( !(S_IWUSR & d->m_permissions) && !(S_IWGRP & d->m_permissions) && !(S_IWOTH & d->m_permissions) )
return false;
}
// Or if we can't read it [using ::access()] - not network transparent
if ( d->m_bIsLocalUrl && KDE::access( d->m_url.toLocalFile(), W_OK ) == -1 )
return false;
return true;
}
bool KFileItem::isHidden() const
{
// The kioslave can specify explicitly that a file is hidden or shown
if ( d->m_hidden != KFileItemPrivate::Auto )
return d->m_hidden == KFileItemPrivate::Hidden;
// Prefer the filename that is part of the URL, in case the display name is different.
QString fileName = d->m_url.fileName();
if (fileName.isEmpty()) // e.g. "trash:/"
fileName = d->m_strName;
return fileName.length() > 1 && fileName[0] == '.'; // Just "." is current directory, not hidden.
}
bool KFileItem::isDir() const
{
if (d->m_fileMode == KFileItem::Unknown) {
// Probably the file was deleted already, and KDirLister hasn't told the world yet.
//kDebug() << d << url() << "can't say -> false";
return false; // can't say for sure, so no
}
return (S_ISDIR(d->m_fileMode));
}
bool KFileItem::isFile() const
{
return !isDir();
}
#ifndef KDE_NO_DEPRECATED
bool KFileItem::acceptsDrops() const
{
// A directory ?
if ( S_ISDIR( mode() ) ) {
return isWritable();
}
// But only local .desktop files and executables
if ( !d->m_bIsLocalUrl )
return false;
if ( mimetype() == "application/x-desktop")
return true;
// Executable, shell script ... ?
if ( QFileInfo(d->m_url.toLocalFile()).isExecutable() )
return true;
return false;
}
#endif
QString KFileItem::getStatusBarInfo() const
{
QString text = d->m_strText;
const QString comment = mimeComment();
if ( d->m_bLink )
{
text += ' ';
if ( comment.isEmpty() )
text += i18n ( "(Symbolic Link to %1)", linkDest() );
else
text += i18n("(%1, Link to %2)", comment, linkDest());
}
else if ( targetUrl() != url() )
{
text += i18n ( " (Points to %1)", targetUrl().pathOrUrl());
}
else if ( S_ISREG( d->m_fileMode ) )
{
text += QString(" (%1, %2)").arg( comment, KIO::convertSize( size() ) );
}
else
{
text += QString(" (%1)").arg( comment );
}
return text;
}
#ifndef KDE_NO_DEPRECATED
QString KFileItem::getToolTipText(int maxcount) const
{
// we can return QString() if no tool tip should be shown
QString tip;
KFileMetaInfo info = metaInfo();
// the font tags are a workaround for the fact that the tool tip gets
// screwed if the color scheme uses white as default text color
const QString colorName = QApplication::palette().color(QPalette::ToolTipText).name();
const QString start = "<tr><td align=\"right\"><nobr><font color=\"" + colorName + "\"><b>";
const QString mid = "&nbsp;</b></font></nobr></td><td><nobr><font color=\"" + colorName + "\">";
const char* end = "</font></nobr></td></tr>";
tip = "<table cellspacing=0 cellpadding=0>";
tip += start + i18n("Name:") + mid + text() + end;
tip += start + i18n("Type:") + mid;
QString type = Qt::escape(mimeComment());
if ( d->m_bLink ) {
tip += i18n("Link to %1 (%2)", linkDest(), type) + end;
} else
tip += type + end;
if ( !S_ISDIR ( d->m_fileMode ) )
tip += start + i18n("Size:") + mid +
QString("%1").arg(KIO::convertSize(size())) +
end;
tip += start + i18n("Modified:") + mid +
timeString( KFileItem::ModificationTime ) + end
#ifndef Q_WS_WIN //TODO: show win32-specific permissions
+start + i18n("Owner:") + mid + user() + " - " + group() + end +
start + i18n("Permissions:") + mid +
permissionsString() + end
#endif
;
if (info.isValid())
{
const QStringList keys = info.preferredKeys();
// now the rest
QStringList::ConstIterator it = keys.begin();
for (int count = 0; count<maxcount && it!=keys.end() ; ++it)
{
if ( count == 0 )
{
tip += "<tr><td colspan=2><center><s>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</s></center></td></tr>";
}
KFileMetaInfoItem item = info.item( *it );
if ( item.isValid() )
{
QString s = item.value().toString();
if ( !s.isEmpty() )
{
count++;
tip += start +
Qt::escape( item.name() ) + ':' +
mid +
Qt::escape( s ) +
end;
}
}
}
}
tip += "</table>";
//kDebug() << "making this the tool tip rich text:\n";
//kDebug() << tip;
return tip;
}
#endif
void KFileItem::run( QWidget* parentWidget ) const
{
(void) new KRun( targetUrl(), parentWidget, d->m_fileMode, d->m_bIsLocalUrl );
}
bool KFileItem::cmp( const KFileItem & item ) const
{
return d->cmp(*item.d);
}
bool KFileItem::operator==(const KFileItem& other) const
{
// is this enough?
return d == other.d;
}
bool KFileItem::operator!=(const KFileItem& other) const
{
return d != other.d;
}
#ifndef KDE_NO_DEPRECATED
void KFileItem::setUDSEntry( const KIO::UDSEntry& _entry, const KUrl& _url,
bool _delayedMimeTypes, bool _urlIsDirectory )
{
d->m_entry = _entry;
d->m_url = _url;
d->m_strName.clear();
d->m_strText.clear();
d->m_iconName.clear();
d->m_strLowerCaseName.clear();
d->m_pMimeType = 0;
d->m_fileMode = KFileItem::Unknown;
d->m_permissions = KFileItem::Unknown;
d->m_bMarked = false;
d->m_bLink = false;
d->m_bIsLocalUrl = _url.isLocalFile();
d->m_bMimeTypeKnown = false;
d->m_hidden = KFileItemPrivate::Auto;
d->m_guessedMimeType.clear();
d->m_metaInfo = KFileMetaInfo();
d->m_delayedMimeTypes = _delayedMimeTypes;
d->m_useIconNameCache = false;
d->readUDSEntry( _urlIsDirectory );
d->init();
}
#endif
KFileItem::operator QVariant() const
{
return qVariantFromValue(*this);
}
#ifndef KDE_NO_DEPRECATED
void KFileItem::setExtraData( const void *key, void *value )
{
if ( !key )
return;
d->m_extra.insert( key, value ); // replaces the value of key if already there
}
#endif
#ifndef KDE_NO_DEPRECATED
const void * KFileItem::extraData( const void *key ) const
{
return d->m_extra.value( key, 0 );
}
#endif
#ifndef KDE_NO_DEPRECATED
void KFileItem::removeExtraData( const void *key )
{
d->m_extra.remove( key );
}
#endif
QString KFileItem::permissionsString() const
{
if (d->m_access.isNull() && d->m_permissions != KFileItem::Unknown)
d->m_access = d->parsePermissions( d->m_permissions );
return d->m_access;
}
// check if we need to cache this
QString KFileItem::timeString( FileTimes which ) const
{
return KGlobal::locale()->formatDateTime( d->time(which) );
}
#ifndef KDE_NO_DEPRECATED
QString KFileItem::timeString( unsigned int which ) const
{
switch (which) {
case KIO::UDSEntry::UDS_ACCESS_TIME:
return timeString(AccessTime);
case KIO::UDSEntry::UDS_CREATION_TIME:
return timeString(CreationTime);
case KIO::UDSEntry::UDS_MODIFICATION_TIME:
default:
return timeString(ModificationTime);
}
}
#endif
void KFileItem::setMetaInfo( const KFileMetaInfo & info ) const
{
d->m_metaInfo = info;
}
KFileMetaInfo KFileItem::metaInfo(bool autoget, int what) const
{
if ((isRegularFile() || isDir()) && autoget && !d->m_metaInfo.isValid())
{
bool isLocalUrl;
KUrl url(mostLocalUrl(isLocalUrl));
d->m_metaInfo = KFileMetaInfo(url.toLocalFile(), mimetype(), (KFileMetaInfo::What)what);
}
return d->m_metaInfo;
}
#ifndef KDE_NO_DEPRECATED
void KFileItem::assign( const KFileItem & item )
{
*this = item;
}
#endif
KUrl KFileItem::mostLocalUrl(bool &local) const
{
QString local_path = localPath();
if ( !local_path.isEmpty() )
{
local = true;
KUrl url;
url.setPath(local_path);
return url;
}
else
{
local = d->m_bIsLocalUrl;
return d->m_url;
}
}
KUrl KFileItem::mostLocalUrl() const
{
bool local = false;
return mostLocalUrl(local);
}
QDataStream & operator<< ( QDataStream & s, const KFileItem & a )
{
// We don't need to save/restore anything that refresh() invalidates,
// since that means we can re-determine those by ourselves.
s << a.d->m_url;
s << a.d->m_strName;
s << a.d->m_strText;
return s;
}
QDataStream & operator>> ( QDataStream & s, KFileItem & a )
{
s >> a.d->m_url;
s >> a.d->m_strName;
s >> a.d->m_strText;
a.d->m_bIsLocalUrl = a.d->m_url.isLocalFile();
a.d->m_bMimeTypeKnown = false;
a.refresh();
return s;
}
KUrl KFileItem::url() const
{
return d->m_url;
}
mode_t KFileItem::permissions() const
{
return d->m_permissions;
}
mode_t KFileItem::mode() const
{
return d->m_fileMode;
}
bool KFileItem::isLink() const
{
return d->m_bLink;
}
bool KFileItem::isLocalFile() const
{
return d->m_bIsLocalUrl;
}
QString KFileItem::text() const
{
return d->m_strText;
}
QString KFileItem::name( bool lowerCase ) const
{
if ( !lowerCase )
return d->m_strName;
else
if ( d->m_strLowerCaseName.isNull() )
d->m_strLowerCaseName = d->m_strName.toLower();
return d->m_strLowerCaseName;
}
KUrl KFileItem::targetUrl() const
{
const QString targetUrlStr = d->m_entry.stringValue( KIO::UDSEntry::UDS_TARGET_URL );
if (!targetUrlStr.isEmpty())
return KUrl(targetUrlStr);
else
return url();
}
KUrl KFileItem::nepomukUri() const
{
#ifndef KIO_NO_NEPOMUK
const QString nepomukUriStr = d->m_entry.stringValue( KIO::UDSEntry::UDS_NEPOMUK_URI );
if(!nepomukUriStr.isEmpty()) {
return KUrl(nepomukUriStr);
}
else if(targetUrl().isLocalFile()) {
return targetUrl();
}
else {
return KUrl();
}
#else
return KUrl();
#endif
}
/*
* Mimetype handling.
*
* Initial state: m_pMimeType = 0.
* When mimeTypePtr() is called first: fast mimetype determination,
* might either find an accurate mimetype (-> Final state), otherwise we
* set m_pMimeType but not m_bMimeTypeKnown (-> Intermediate state)
* Intermediate state: determineMimeType() does the real determination -> Final state.
*
* If delayedMimeTypes isn't set, then we always go to the Final state directly.
*/
KMimeType::Ptr KFileItem::mimeTypePtr() const
{
if (!d->m_pMimeType) {
// On-demand fast (but not always accurate) mimetype determination
Q_ASSERT(!d->m_url.isEmpty());
bool isLocalUrl;
KUrl url = mostLocalUrl(isLocalUrl);
if (d->m_delayedMimeTypes) {
QMimeDatabase db;
const QList<QMimeType> mimeTypes = db.findMimeTypesByFileName(url.path());
if (mimeTypes.isEmpty()) {
d->m_pMimeType = KMimeType::defaultMimeTypePtr();
d->m_bMimeTypeKnown = false;
} else {
d->m_pMimeType = KMimeType::Ptr(new KMimeType(mimeTypes.first()));
// If there were conflicting globs. determineMimeType will be able to do better.
d->m_bMimeTypeKnown = (mimeTypes.count() == 1);
}
} else {
d->m_pMimeType = KMimeType::findByUrl(url, d->m_fileMode, isLocalUrl);
d->m_bMimeTypeKnown = true;
}
}
return d->m_pMimeType;
}
KIO::UDSEntry KFileItem::entry() const
{
return d->m_entry;
}
bool KFileItem::isMarked() const
{
return d->m_bMarked;
}
void KFileItem::mark()
{
d->m_bMarked = true;
}
void KFileItem::unmark()
{
d->m_bMarked = false;
}
KFileItem& KFileItem::operator=(const KFileItem& other)
{
d = other.d;
return *this;
}
bool KFileItem::isNull() const
{
return d == 0;
}
KFileItemList::KFileItemList()
{
}
KFileItemList::KFileItemList( const QList<KFileItem> &items )
: QList<KFileItem>( items )
{
}
KFileItem KFileItemList::findByName( const QString& fileName ) const
{
const_iterator it = begin();
const const_iterator itend = end();
for ( ; it != itend ; ++it ) {
if ( (*it).name() == fileName ) {
return *it;
}
}
return KFileItem();
}
KFileItem KFileItemList::findByUrl( const KUrl& url ) const {
const_iterator it = begin();
const const_iterator itend = end();
for ( ; it != itend ; ++it ) {
if ( (*it).url() == url ) {
return *it;
}
}
return KFileItem();
}
KUrl::List KFileItemList::urlList() const {
KUrl::List lst;
const_iterator it = begin();
const const_iterator itend = end();
for ( ; it != itend ; ++it ) {
lst.append( (*it).url() );
}
return lst;
}
KUrl::List KFileItemList::targetUrlList() const {
KUrl::List lst;
const_iterator it = begin();
const const_iterator itend = end();
for ( ; it != itend ; ++it ) {
lst.append( (*it).targetUrl() );
}
return lst;
}
bool KFileItem::isDesktopFile() const
{
return checkDesktopFile(*this, true);
}
bool KFileItem::isRegularFile() const
{
return S_ISREG(d->m_fileMode);
}
QDebug operator<<(QDebug stream, const KFileItem& item)
{
if (item.isNull()) {
stream << "[null KFileItem]";
} else {
stream << "[KFileItem for" << item.url() << "]";
}
return stream;
}
diff --git a/kio/kio/kfileitemactions.cpp b/kio/kio/kfileitemactions.cpp
index 3b44e0be6b..d19160c9db 100644
--- a/kio/kio/kfileitemactions.cpp
+++ b/kio/kio/kfileitemactions.cpp
@@ -1,719 +1,719 @@
/* This file is part of the KDE project
Copyright (C) 1998-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 ) version 3 or, at the discretion of KDE e.V.
( which shall act as a proxy as in section 14 of the GPLv3 ), 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 "kfileitemactions.h"
#include "kfileitemactions_p.h"
#include <kaction.h>
#include <krun.h>
#include <kmimetypetrader.h>
#include <kdebug.h>
#include <kdesktopfileactions.h>
#include <kmenu.h>
#include <klocale.h>
#include <kauthorized.h>
#include <kconfiggroup.h>
#include <kdesktopfile.h>
#include <kglobal.h>
#include <kicon.h>
#include <kstandarddirs.h>
#include <kservicetypetrader.h>
#include <QFile>
#include <QtAlgorithms>
#include <QtDBus/QtDBus>
static bool KIOSKAuthorizedAction(const KConfigGroup& cfg)
{
if (!cfg.hasKey("X-KDE-AuthorizeAction")) {
return true;
}
const QStringList list = cfg.readEntry("X-KDE-AuthorizeAction", QStringList());
for(QStringList::ConstIterator it = list.constBegin();
it != list.constEnd(); ++it) {
if (!KAuthorized::authorize((*it).trimmed())) {
return false;
}
}
return true;
}
// This helper class stores the .desktop-file actions and the servicemenus
// in order to support X-KDE-Priority and X-KDE-Submenu.
namespace KIO {
class PopupServices
{
public:
ServiceList& selectList(const QString& priority, const QString& submenuName);
ServiceList builtin;
ServiceList user, userToplevel, userPriority;
QMap<QString, ServiceList> userSubmenus, userToplevelSubmenus, userPrioritySubmenus;
};
ServiceList& PopupServices::selectList(const QString& priority, const QString& submenuName)
{
// we use the categories .desktop entry to define submenus
// if none is defined, we just pop it in the main menu
if (submenuName.isEmpty()) {
if (priority == "TopLevel") {
return userToplevel;
} else if (priority == "Important") {
return userPriority;
}
} else if (priority == "TopLevel") {
return userToplevelSubmenus[submenuName];
} else if (priority == "Important") {
return userPrioritySubmenus[submenuName];
} else {
return userSubmenus[submenuName];
}
return user;
}
} // namespace
////
KFileItemActionsPrivate::KFileItemActionsPrivate()
: QObject(),
m_executeServiceActionGroup(static_cast<QWidget *>(0)),
m_runApplicationActionGroup(static_cast<QWidget *>(0)),
m_parentWidget(0)
{
QObject::connect(&m_executeServiceActionGroup, SIGNAL(triggered(QAction*)),
this, SLOT(slotExecuteService(QAction*)));
QObject::connect(&m_runApplicationActionGroup, SIGNAL(triggered(QAction*)),
this, SLOT(slotRunApplication(QAction*)));
}
KFileItemActionsPrivate::~KFileItemActionsPrivate()
{
qDeleteAll(m_ownActions);
}
int KFileItemActionsPrivate::insertServicesSubmenus(const QMap<QString, ServiceList>& submenus,
QMenu* menu,
bool isBuiltin)
{
int count = 0;
QMap<QString, ServiceList>::ConstIterator it;
for (it = submenus.begin(); it != submenus.end(); ++it) {
if (it.value().isEmpty()) {
//avoid empty sub-menus
continue;
}
QMenu* actionSubmenu = new KMenu(menu);
actionSubmenu->setTitle(it.key());
actionSubmenu->menuAction()->setObjectName("services_submenu"); // for the unittest
menu->addMenu(actionSubmenu);
count += insertServices(it.value(), actionSubmenu, isBuiltin);
}
return count;
}
int KFileItemActionsPrivate::insertServices(const ServiceList& list,
QMenu* menu,
bool isBuiltin)
{
int count = 0;
ServiceList::const_iterator it = list.begin();
for(; it != list.end(); ++it) {
if ((*it).isSeparator()) {
const QList<QAction*> actions = menu->actions();
if (!actions.isEmpty() && !actions.last()->isSeparator()) {
menu->addSeparator();
}
continue;
}
if (isBuiltin || !(*it).noDisplay()) {
KAction *act = new KAction(m_parentWidget);
m_ownActions.append(act);
act->setObjectName("menuaction"); // for the unittest
QString text = (*it).text();
text.replace('&',"&&");
act->setText(text);
if (!(*it).icon().isEmpty()) {
act->setIcon(KIcon((*it).icon()));
}
act->setData(QVariant::fromValue(*it));
m_executeServiceActionGroup.addAction(act);
menu->addAction(act); // Add to toplevel menu
++count;
}
}
return count;
}
void KFileItemActionsPrivate::slotExecuteService(QAction* act)
{
KServiceAction serviceAction = act->data().value<KServiceAction>();
if (KAuthorized::authorizeKAction(serviceAction.name())) {
KDesktopFileActions::executeService(m_props.urlList(), serviceAction);
}
}
////
KFileItemActions::KFileItemActions(QObject* parent)
: QObject(parent), d(new KFileItemActionsPrivate)
{
}
KFileItemActions::~KFileItemActions()
{
delete d;
}
void KFileItemActions::setItemListProperties(const KFileItemListProperties& itemListProperties)
{
d->m_props = itemListProperties;
d->m_mimeTypeList.clear();
const KFileItemList items = d->m_props.items();
KFileItemList::const_iterator kit = items.constBegin();
const KFileItemList::const_iterator kend = items.constEnd();
for (; kit != kend; ++kit) {
if (!d->m_mimeTypeList.contains((*kit).mimetype()))
d->m_mimeTypeList << (*kit).mimetype();
}
}
int KFileItemActions::addServiceActionsTo(QMenu* mainMenu)
{
const KFileItemList items = d->m_props.items();
const KFileItem firstItem = items.first();
- const QString protocol = firstItem.url().protocol(); // assumed to be the same for all items
+ const QString protocol = firstItem.url().scheme(); // assumed to be the same for all items
const bool isLocal = !firstItem.localPath().isEmpty();
const bool isSingleLocal = items.count() == 1 && isLocal;
const KUrl::List urlList = d->m_props.urlList();
KIO::PopupServices s;
// 1 - Look for builtin and user-defined services
if (isSingleLocal && (d->m_props.mimeType() == "application/x-desktop" || // .desktop file
d->m_props.mimeType() == "inode/blockdevice")) { // dev file
// get builtin services, like mount/unmount
const QString path = firstItem.localPath();
s.builtin = KDesktopFileActions::builtinServices(path);
KDesktopFile desktopFile(path);
KConfigGroup cfg = desktopFile.desktopGroup();
const QString priority = cfg.readEntry("X-KDE-Priority");
const QString submenuName = cfg.readEntry("X-KDE-Submenu");
#if 0
if (cfg.readEntry("Type") == "Link") {
d->m_url = cfg.readEntry("URL");
// TODO: Do we want to make all the actions apply on the target
// of the .desktop file instead of the .desktop file itself?
}
#endif
ServiceList& list = s.selectList(priority, submenuName);
list = KDesktopFileActions::userDefinedServices(path, desktopFile, true /*isLocal*/);
}
// 2 - Look for "servicemenus" bindings (user-defined services)
// first check the .directory if this is a directory
if (d->m_props.isDirectory() && isSingleLocal) {
QString dotDirectoryFile = KUrl::fromPath(firstItem.localPath()).path(KUrl::AddTrailingSlash).append(".directory");
if (QFile::exists(dotDirectoryFile)) {
const KDesktopFile desktopFile(dotDirectoryFile);
const KConfigGroup cfg = desktopFile.desktopGroup();
if (KIOSKAuthorizedAction(cfg)) {
const QString priority = cfg.readEntry("X-KDE-Priority");
const QString submenuName = cfg.readEntry("X-KDE-Submenu");
ServiceList& list = s.selectList(priority, submenuName);
list += KDesktopFileActions::userDefinedServices(dotDirectoryFile, desktopFile, true);
}
}
}
const KConfig config("kservicemenurc", KConfig::NoGlobals);
const KConfigGroup showGroup = config.group("Show");
const QString commonMimeType = d->m_props.mimeType();
const QString commonMimeGroup = d->m_props.mimeGroup();
const KMimeType::Ptr mimeTypePtr = commonMimeType.isEmpty() ? KMimeType::Ptr() : KMimeType::mimeType(commonMimeType);
const KService::List entries = KServiceTypeTrader::self()->query("KonqPopupMenu/Plugin");
KService::List::const_iterator eEnd = entries.end();
for (KService::List::const_iterator it2 = entries.begin(); it2 != eEnd; ++it2) {
QString file = KStandardDirs::locate("services", (*it2)->entryPath());
KDesktopFile desktopFile(file);
const KConfigGroup cfg = desktopFile.desktopGroup();
if (!KIOSKAuthorizedAction(cfg)) {
continue;
}
if (cfg.hasKey("X-KDE-ShowIfRunning")) {
const QString app = cfg.readEntry("X-KDE-ShowIfRunning");
if (QDBusConnection::sessionBus().interface()->isServiceRegistered(app)) {
continue;
}
}
if (cfg.hasKey("X-KDE-ShowIfDBusCall")) {
QString calldata = cfg.readEntry("X-KDE-ShowIfDBusCall");
const QStringList parts = calldata.split(' ');
const QString &app = parts.at(0);
const QString &obj = parts.at(1);
QString interface = parts.at(2);
QString method;
int pos = interface.lastIndexOf(QLatin1Char('.'));
if (pos != -1) {
method = interface.mid(pos + 1);
interface.truncate(pos);
}
//if (!QDBus::sessionBus().busService()->nameHasOwner(app))
// continue; //app does not exist so cannot send call
QDBusMessage reply = QDBusInterface(app, obj, interface).
call(method, urlList.toStringList());
if (reply.arguments().count() < 1 || reply.arguments().at(0).type() != QVariant::Bool || !reply.arguments().at(0).toBool()) {
continue;
}
}
if (cfg.hasKey("X-KDE-Protocol")) {
const QString theProtocol = cfg.readEntry("X-KDE-Protocol");
if (theProtocol.startsWith('!')) {
const QString excludedProtocol = theProtocol.mid(1);
if (excludedProtocol == protocol) {
continue;
}
} else if (protocol != theProtocol) {
continue;
}
}
else if (cfg.hasKey("X-KDE-Protocols")) {
const QStringList protocols = cfg.readEntry("X-KDE-Protocols", QStringList());
if (!protocols.contains(protocol)) {
continue;
}
}
else if (protocol == "trash") {
// Require servicemenus for the trash to ask for protocol=trash explicitly.
// Trashed files aren't supposed to be available for actions.
// One might want a servicemenu for trash.desktop itself though.
continue;
}
if (cfg.hasKey("X-KDE-Require")) {
const QStringList capabilities = cfg.readEntry("X-KDE-Require" , QStringList());
if (capabilities.contains("Write") && !d->m_props.supportsWriting()) {
continue;
}
}
if (cfg.hasKey("Actions") || cfg.hasKey("X-KDE-GetActionMenu")) {
// Like KService, we support ServiceTypes, X-KDE-ServiceTypes, and MimeType.
QStringList types = cfg.readEntry("ServiceTypes", QStringList());
types += cfg.readEntry("X-KDE-ServiceTypes", QStringList());
types += cfg.readXdgListEntry("MimeType");
//kDebug() << file << types;
if (types.isEmpty()) {
continue;
}
const QStringList excludeTypes = cfg.readEntry("ExcludeServiceTypes" , QStringList());
bool ok = false;
// check for exact matches or a typeglob'd mimetype if we have a mimetype
for (QStringList::ConstIterator it = types.constBegin();
it != types.constEnd() && !ok;
++it)
{
// first check if we have an all mimetype
bool checkTheMimetypes = false;
if (*it == "all/all" ||
*it == "allfiles" /*compat with KDE up to 3.0.3*/) {
checkTheMimetypes = true;
}
// next, do we match all files?
if (!ok &&
!d->m_props.isDirectory() &&
*it == "all/allfiles") {
checkTheMimetypes = true;
}
// if we have a mimetype, see if we have an exact or a type globbed match
if (!ok && (
(mimeTypePtr && mimeTypePtr->is(*it)) ||
(!commonMimeGroup.isEmpty() &&
((*it).right(1) == "*" &&
(*it).left((*it).indexOf('/')) == commonMimeGroup)))) {
checkTheMimetypes = true;
}
if (checkTheMimetypes) {
ok = true;
for (QStringList::ConstIterator itex = excludeTypes.constBegin(); itex != excludeTypes.constEnd(); ++itex) {
if(((*itex).endsWith('*') && (*itex).left((*itex).indexOf('/')) == commonMimeGroup) ||
((*itex) == commonMimeType)) {
ok = false;
break;
}
}
}
}
if (ok) {
const QString priority = cfg.readEntry("X-KDE-Priority");
const QString submenuName = cfg.readEntry("X-KDE-Submenu");
ServiceList& list = s.selectList(priority, submenuName);
const ServiceList userServices = KDesktopFileActions::userDefinedServices(*(*it2), isLocal, urlList);
foreach (const KServiceAction& action, userServices) {
if (showGroup.readEntry(action.name(), true)) {
list += action;
}
}
}
}
}
QMenu* actionMenu = mainMenu;
int userItemCount = 0;
if (s.user.count() + s.userSubmenus.count() +
s.userPriority.count() + s.userPrioritySubmenus.count() > 1) {
// we have more than one item, so let's make a submenu
actionMenu = new KMenu(i18nc("@title:menu", "&Actions"), mainMenu);
actionMenu->menuAction()->setObjectName("actions_submenu"); // for the unittest
mainMenu->addMenu(actionMenu);
}
userItemCount += d->insertServicesSubmenus(s.userPrioritySubmenus, actionMenu, false);
userItemCount += d->insertServices(s.userPriority, actionMenu, false);
// see if we need to put a separator between our priority items and our regular items
if (userItemCount > 0 &&
(s.user.count() > 0 ||
s.userSubmenus.count() > 0 ||
s.builtin.count() > 0) &&
!actionMenu->actions().last()->isSeparator()) {
actionMenu->addSeparator();
}
userItemCount += d->insertServicesSubmenus(s.userSubmenus, actionMenu, false);
userItemCount += d->insertServices(s.user, actionMenu, false);
userItemCount += d->insertServices(s.builtin, mainMenu, true);
userItemCount += d->insertServicesSubmenus(s.userToplevelSubmenus, mainMenu, false);
userItemCount += d->insertServices(s.userToplevel, mainMenu, false);
return userItemCount;
}
// static
KService::List KFileItemActions::associatedApplications(const QStringList& mimeTypeList, const QString& traderConstraint)
{
if (!KAuthorized::authorizeKAction("openwith") || mimeTypeList.isEmpty()) {
return KService::List();
}
const KService::List firstOffers = KMimeTypeTrader::self()->query(mimeTypeList.first(), "Application", traderConstraint);
QList<KFileItemActionsPrivate::ServiceRank> rankings;
QStringList serviceList;
// This section does two things. First, it determines which services are common to all the given mimetypes.
// Second, it ranks them based on their preference level in the associated applications list.
// The more often a service appear near the front of the list, the LOWER its score.
for (int i = 0; i < firstOffers.count(); ++i) {
KFileItemActionsPrivate::ServiceRank tempRank;
tempRank.service = firstOffers[i];
tempRank.score = i;
rankings << tempRank;
serviceList << tempRank.service->storageId();
}
for (int j = 1; j < mimeTypeList.count(); ++j) {
QStringList subservice; // list of services that support this mimetype
const KService::List offers = KMimeTypeTrader::self()->query(mimeTypeList[j], "Application", traderConstraint);
for (int i = 0; i != offers.count(); ++i) {
const QString serviceId = offers[i]->storageId();
subservice << serviceId;
const int idPos = serviceList.indexOf(serviceId);
if (idPos != -1) {
rankings[idPos].score += i;
} // else: we ignore the services that didn't support the previous mimetypes
}
// Remove services which supported the previous mimetypes but don't support this one
for (int i = 0; i < serviceList.count(); ++i) {
if (!subservice.contains(serviceList[i])) {
serviceList.removeAt(i);
rankings.removeAt(i);
--i;
}
}
// Nothing left -> there is no common application for these mimetypes
if (rankings.isEmpty()) {
return KService::List();
}
}
qSort(rankings.begin(), rankings.end(), KFileItemActionsPrivate::lessRank);
KService::List result;
Q_FOREACH(const KFileItemActionsPrivate::ServiceRank& tempRank, rankings) {
result << tempRank.service;
}
return result;
}
// KMimeTypeTrader::preferredService doesn't take a constraint
static KService::Ptr preferredService(const QString& mimeType, const QString& constraint)
{
const KService::List services = KMimeTypeTrader::self()->query(mimeType, QString::fromLatin1("Application"), constraint);
return !services.isEmpty() ? services.first() : KService::Ptr();
}
void KFileItemActions::addOpenWithActionsTo(QMenu* topMenu, const QString& traderConstraint)
{
if (!KAuthorized::authorizeKAction("openwith")) {
return;
}
d->m_traderConstraint = traderConstraint;
KService::List offers = associatedApplications(d->m_mimeTypeList, traderConstraint);
//// Ok, we have everything, now insert
const KFileItemList items = d->m_props.items();
const KFileItem firstItem = items.first();
const bool isLocal = firstItem.url().isLocalFile();
// "Open With..." for folders is really not very useful, especially for remote folders.
// (media:/something, or trash:/, or ftp://...)
if (!d->m_props.isDirectory() || isLocal) {
if (!topMenu->actions().isEmpty()) {
topMenu->addSeparator();
}
KAction *runAct = new KAction(d->m_parentWidget);
QString runActionName;
const QStringList serviceIdList = d->listPreferredServiceIds(d->m_mimeTypeList, traderConstraint);
//kDebug(7010) << "serviceIdList=" << serviceIdList;
// When selecting files with multiple mimetypes, offer either "open with <app for all>"
// or a generic <open> (if there are any apps associated).
if (d->m_mimeTypeList.count() > 1
&& !serviceIdList.isEmpty()
&& !(serviceIdList.count()==1 && serviceIdList.first().isEmpty())) { // empty means "no apps associated"
d->m_ownActions.append(runAct);
if (serviceIdList.count() == 1) {
const KService::Ptr app = preferredService(d->m_mimeTypeList.first(), traderConstraint);
runActionName = i18n("&Open with %1", app->name());
runAct->setIcon(KIcon(app->icon()));
// Remove that app from the offers list (#242731)
for (int i = 0; i < offers.count() ; ++i) {
if (offers[i]->storageId() == app->storageId()) {
offers.removeAt(i);
break;
}
}
} else {
runActionName = i18n("&Open");
}
runAct->setText(runActionName);
d->m_traderConstraint = traderConstraint;
d->m_fileOpenList = d->m_props.items();
QObject::connect(runAct, SIGNAL(triggered()), d, SLOT(slotRunPreferredApplications()));
topMenu->addAction(runAct);
}
if (!offers.isEmpty()) {
QMenu* menu = topMenu;
if (offers.count() > 1) { // submenu 'open with'
menu = new QMenu(i18nc("@title:menu", "&Open With"), topMenu);
menu->menuAction()->setObjectName("openWith_submenu"); // for the unittest
topMenu->addMenu(menu);
}
//kDebug() << offers.count() << "offers" << topMenu << menu;
KService::List::ConstIterator it = offers.constBegin();
for(; it != offers.constEnd(); it++) {
KAction* act = d->createAppAction(*it,
// no submenu -> prefix single offer
menu == topMenu);
menu->addAction(act);
}
QString openWithActionName;
if (menu != topMenu) { // submenu
menu->addSeparator();
openWithActionName = i18nc("@action:inmenu Open With", "&Other...");
} else {
openWithActionName = i18nc("@title:menu", "&Open With...");
}
KAction *openWithAct = new KAction(d->m_parentWidget);
d->m_ownActions.append(openWithAct);
openWithAct->setText(openWithActionName);
QObject::connect(openWithAct, SIGNAL(triggered()), d, SLOT(slotOpenWithDialog()));
menu->addAction(openWithAct);
}
else // no app offers -> Open With...
{
KAction *act = new KAction(d->m_parentWidget);
d->m_ownActions.append(act);
act->setText(i18nc("@title:menu", "&Open With..."));
QObject::connect(act, SIGNAL(triggered()), d, SLOT(slotOpenWithDialog()));
topMenu->addAction(act);
}
}
}
void KFileItemActionsPrivate::slotRunPreferredApplications()
{
const KFileItemList fileItems = m_fileOpenList;
const QStringList mimeTypeList = listMimeTypes(fileItems);
const QStringList serviceIdList = listPreferredServiceIds(mimeTypeList, m_traderConstraint);
foreach (const QString serviceId, serviceIdList) {
KFileItemList serviceItems;
foreach (const KFileItem& item, fileItems) {
const KService::Ptr serv = preferredService(item.mimetype(), m_traderConstraint);
const QString preferredServiceId = serv ? serv->storageId() : QString();
if (preferredServiceId == serviceId) {
serviceItems << item;
}
}
if (serviceId.isEmpty()) { // empty means: no associated app for this mimetype
openWithByMime(serviceItems);
continue;
}
const KService::Ptr servicePtr = KService::serviceByStorageId(serviceId);
if (servicePtr.isNull()) {
KRun::displayOpenWithDialog(serviceItems.urlList(), m_parentWidget);
continue;
}
KRun::run(*servicePtr, serviceItems.urlList(), m_parentWidget);
}
}
void KFileItemActions::runPreferredApplications(const KFileItemList& fileOpenList, const QString& traderConstraint)
{
d->m_fileOpenList = fileOpenList;
d->m_traderConstraint = traderConstraint;
d->slotRunPreferredApplications();
}
void KFileItemActionsPrivate::openWithByMime(const KFileItemList& fileItems)
{
const QStringList mimeTypeList = listMimeTypes(fileItems);
foreach (const QString mimeType, mimeTypeList) {
KFileItemList mimeItems;
foreach (const KFileItem& item, fileItems) {
if (item.mimetype() == mimeType) {
mimeItems << item;
}
}
KRun::displayOpenWithDialog(mimeItems.urlList(), m_parentWidget);
}
}
void KFileItemActionsPrivate::slotRunApplication(QAction* act)
{
// Is it an application, from one of the "Open With" actions
KService::Ptr app = act->data().value<KService::Ptr>();
Q_ASSERT(app);
if (app) {
KRun::run(*app, m_props.urlList(), m_parentWidget);
}
}
void KFileItemActionsPrivate::slotOpenWithDialog()
{
// The item 'Other...' or 'Open With...' has been selected
KRun::displayOpenWithDialog(m_props.urlList(), m_parentWidget);
}
QStringList KFileItemActionsPrivate::listMimeTypes(const KFileItemList& items)
{
QStringList mimeTypeList;
foreach (const KFileItem& item, items) {
if (!mimeTypeList.contains(item.mimetype()))
mimeTypeList << item.mimetype();
}
return mimeTypeList;
}
QStringList KFileItemActionsPrivate::listPreferredServiceIds(const QStringList& mimeTypeList, const QString& traderConstraint)
{
QStringList serviceIdList;
Q_FOREACH(const QString& mimeType, mimeTypeList) {
const KService::Ptr serv = preferredService(mimeType, traderConstraint);
const QString newOffer = serv ? serv->storageId() : QString();
serviceIdList << newOffer;
}
serviceIdList.removeDuplicates();
return serviceIdList;
}
KAction* KFileItemActionsPrivate::createAppAction(const KService::Ptr& service, bool singleOffer)
{
QString actionName(service->name().replace('&', "&&"));
if (singleOffer) {
actionName = i18n("Open &with %1", actionName);
} else {
actionName = i18nc("@item:inmenu Open With, %1 is application name", "%1", actionName);
}
KAction *act = new KAction(m_parentWidget);
m_ownActions.append(act);
act->setIcon(KIcon(service->icon()));
act->setText(actionName);
act->setData(QVariant::fromValue(service));
m_runApplicationActionGroup.addAction(act);
return act;
}
KAction* KFileItemActions::preferredOpenWithAction(const QString& traderConstraint)
{
const KService::List offers = associatedApplications(d->m_mimeTypeList, traderConstraint);
if (offers.isEmpty()) {
return 0;
}
return d->createAppAction(offers.first(), true);
}
void KFileItemActions::setParentWidget(QWidget* widget)
{
d->m_parentWidget = widget;
}
diff --git a/kio/kio/kprotocolmanager.cpp b/kio/kio/kprotocolmanager.cpp
index 7c93220ef5..2df7ead122 100644
--- a/kio/kio/kprotocolmanager.cpp
+++ b/kio/kio/kprotocolmanager.cpp
@@ -1,1197 +1,1197 @@
/* This file is part of the KDE libraries
Copyright (C) 1999 Torben Weis <weis@kde.org>
Copyright (C) 2000- Waldo Bastain <bastain@kde.org>
Copyright (C) 2000- Dawit Alemayehu <adawit@kde.org>
Copyright (C) 2008 Jarosław Staniek <staniek@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 "kprotocolmanager.h"
#include "hostinfo_p.h"
#include <string.h>
#include <unistd.h>
#include <sys/utsname.h>
#include <QtCore/QCoreApplication>
#include <QtNetwork/QSslSocket>
#include <QtNetwork/QHostAddress>
#include <QtNetwork/QHostInfo>
#include <QtDBus/QtDBus>
#include <QtCore/QCache>
#if !defined(QT_NO_NETWORKPROXY) && (defined (Q_OS_WIN32) || defined(Q_OS_MAC))
#include <QtNetwork/QNetworkProxyFactory>
#include <QtNetwork/QNetworkProxyQuery>
#endif
#include <kdeversion.h>
#include <kdebug.h>
#include <kglobal.h>
#include <klocale.h>
#include <kconfiggroup.h>
#include <ksharedconfig.h>
#include <kstandarddirs.h>
#include <kurl.h>
#include <kmimetypetrader.h>
#include <kprotocolinfofactory.h>
#include <kio/slaveconfig.h>
#include <kio/ioslave_defaults.h>
#include <kio/http_slave_defaults.h>
#define QL1S(x) QLatin1String(x)
#define QL1C(x) QLatin1Char(x)
typedef QPair<QHostAddress, int> SubnetPair;
/*
Domain suffix match. E.g. return true if host is "cuzco.inka.de" and
nplist is "inka.de,hadiko.de" or if host is "localhost" and nplist is
"localhost".
*/
static bool revmatch(const char *host, const char *nplist)
{
if (host == 0)
return false;
const char *hptr = host + strlen( host ) - 1;
const char *nptr = nplist + strlen( nplist ) - 1;
const char *shptr = hptr;
while ( nptr >= nplist )
{
if ( *hptr != *nptr )
{
hptr = shptr;
// Try to find another domain or host in the list
while(--nptr>=nplist && *nptr!=',' && *nptr!=' ') ;
// Strip out multiple spaces and commas
while(--nptr>=nplist && (*nptr==',' || *nptr==' ')) ;
}
else
{
if ( nptr==nplist || nptr[-1]==',' || nptr[-1]==' ')
return true;
if ( nptr[-1]=='/' && hptr == host ) // "bugs.kde.org" vs "http://bugs.kde.org", the config UI says URLs are ok
return true;
if ( hptr == host ) // e.g. revmatch("bugs.kde.org","mybugs.kde.org")
return false;
hptr--;
nptr--;
}
}
return false;
}
class KProxyData : public QObject
{
public:
KProxyData(const QString& slaveProtocol, const QStringList& proxyAddresses)
:protocol(slaveProtocol)
,proxyList(proxyAddresses) {
}
void removeAddress(const QString& address) {
proxyList.removeAll(address);
}
QString protocol;
QStringList proxyList;
};
class KProtocolManagerPrivate
{
public:
KProtocolManagerPrivate();
~KProtocolManagerPrivate();
bool shouldIgnoreProxyFor(const KUrl& url);
KSharedConfig::Ptr config;
KSharedConfig::Ptr http_config;
QString modifiers;
QString useragent;
QString noProxyFor;
QList<SubnetPair> noProxySubnets;
QCache<QString, KProxyData> cachedProxyData;
QMap<QString /*mimetype*/, QString /*protocol*/> protocolForArchiveMimetypes;
};
K_GLOBAL_STATIC(KProtocolManagerPrivate, kProtocolManagerPrivate)
KProtocolManagerPrivate::KProtocolManagerPrivate()
{
// post routine since KConfig::sync() breaks if called too late
qAddPostRoutine(kProtocolManagerPrivate.destroy);
cachedProxyData.setMaxCost(200); // double the max cost.
}
KProtocolManagerPrivate::~KProtocolManagerPrivate()
{
qRemovePostRoutine(kProtocolManagerPrivate.destroy);
}
/*
* Returns true if url is in the no proxy list.
*/
bool KProtocolManagerPrivate::shouldIgnoreProxyFor(const KUrl& url)
{
bool isMatch = false;
const KProtocolManager::ProxyType type = KProtocolManager::proxyType();
const bool useRevProxy = ((type == KProtocolManager::ManualProxy) && KProtocolManager::useReverseProxy());
const bool useNoProxyList = (type == KProtocolManager::ManualProxy || type == KProtocolManager::EnvVarProxy);
// No proxy only applies to ManualProxy and EnvVarProxy types...
if (useNoProxyList && noProxyFor.isEmpty()) {
QStringList noProxyForList (KProtocolManager::noProxyFor().split(QL1C(',')));
QMutableStringListIterator it (noProxyForList);
while (it.hasNext()) {
SubnetPair subnet = QHostAddress::parseSubnet(it.next());
if (!subnet.first.isNull()) {
noProxySubnets << subnet;
it.remove();
}
}
noProxyFor = noProxyForList.join(QL1S(","));
}
if (!noProxyFor.isEmpty()) {
QString qhost = url.host().toLower();
QByteArray host = qhost.toLatin1();
const QString qno_proxy = noProxyFor.trimmed().toLower();
const QByteArray no_proxy = qno_proxy.toLatin1();
isMatch = revmatch(host, no_proxy);
// If no match is found and the request url has a port
// number, try the combination of "host:port". This allows
// users to enter host:port in the No-proxy-For list.
if (!isMatch && url.port() > 0) {
qhost += QL1C(':');
qhost += QString::number(url.port());
host = qhost.toLatin1();
isMatch = revmatch (host, no_proxy);
}
// If the hostname does not contain a dot, check if
// <local> is part of noProxy.
if (!isMatch && !host.isEmpty() && (strchr(host, '.') == NULL)) {
isMatch = revmatch("<local>", no_proxy);
}
}
const QString host (url.host());
if (!noProxySubnets.isEmpty() && !host.isEmpty()) {
QHostAddress address (host);
// If request url is not IP address, do a DNS lookup of the hostname.
// TODO: Perhaps we should make configurable ?
if (address.isNull()) {
kDebug() << "Performing DNS lookup for" << host;
QHostInfo info = KIO::HostInfo::lookupHost(host, 2000);
const QList<QHostAddress> addresses = info.addresses();
if (!addresses.isEmpty())
address = addresses.first();
}
if (!address.isNull()) {
Q_FOREACH(const SubnetPair& subnet, noProxySubnets) {
if (address.isInSubnet(subnet)) {
isMatch = true;
break;
}
}
}
}
return (useRevProxy != isMatch);
}
#define PRIVATE_DATA \
KProtocolManagerPrivate *d = kProtocolManagerPrivate
void KProtocolManager::reparseConfiguration()
{
PRIVATE_DATA;
if (d->http_config) {
d->http_config->reparseConfiguration();
}
if (d->config) {
d->config->reparseConfiguration();
}
d->cachedProxyData.clear();
d->noProxyFor.clear();
d->modifiers.clear();
d->useragent.clear();
// Force the slave config to re-read its config...
KIO::SlaveConfig::self()->reset();
}
KSharedConfig::Ptr KProtocolManager::config()
{
PRIVATE_DATA;
if (!d->config)
{
d->config = KSharedConfig::openConfig("kioslaverc", KConfig::NoGlobals);
}
return d->config;
}
static KConfigGroup http_config()
{
PRIVATE_DATA;
if (!d->http_config) {
d->http_config = KSharedConfig::openConfig("kio_httprc", KConfig::NoGlobals);
}
return KConfigGroup(d->http_config, QString());
}
/*=============================== TIMEOUT SETTINGS ==========================*/
int KProtocolManager::readTimeout()
{
KConfigGroup cg( config(), QString() );
int val = cg.readEntry( "ReadTimeout", DEFAULT_READ_TIMEOUT );
return qMax(MIN_TIMEOUT_VALUE, val);
}
int KProtocolManager::connectTimeout()
{
KConfigGroup cg( config(), QString() );
int val = cg.readEntry( "ConnectTimeout", DEFAULT_CONNECT_TIMEOUT );
return qMax(MIN_TIMEOUT_VALUE, val);
}
int KProtocolManager::proxyConnectTimeout()
{
KConfigGroup cg( config(), QString() );
int val = cg.readEntry( "ProxyConnectTimeout", DEFAULT_PROXY_CONNECT_TIMEOUT );
return qMax(MIN_TIMEOUT_VALUE, val);
}
int KProtocolManager::responseTimeout()
{
KConfigGroup cg( config(), QString() );
int val = cg.readEntry( "ResponseTimeout", DEFAULT_RESPONSE_TIMEOUT );
return qMax(MIN_TIMEOUT_VALUE, val);
}
/*========================== PROXY SETTINGS =================================*/
bool KProtocolManager::useProxy()
{
return proxyType() != NoProxy;
}
bool KProtocolManager::useReverseProxy()
{
KConfigGroup cg(config(), "Proxy Settings" );
return cg.readEntry("ReversedException", false);
}
KProtocolManager::ProxyType KProtocolManager::proxyType()
{
KConfigGroup cg(config(), "Proxy Settings" );
return static_cast<ProxyType>(cg.readEntry( "ProxyType" , 0));
}
KProtocolManager::ProxyAuthMode KProtocolManager::proxyAuthMode()
{
KConfigGroup cg(config(), "Proxy Settings" );
return static_cast<ProxyAuthMode>(cg.readEntry( "AuthMode" , 0));
}
/*========================== CACHING =====================================*/
bool KProtocolManager::useCache()
{
return http_config().readEntry( "UseCache", true );
}
KIO::CacheControl KProtocolManager::cacheControl()
{
QString tmp = http_config().readEntry("cache");
if (tmp.isEmpty())
return DEFAULT_CACHE_CONTROL;
return KIO::parseCacheControl(tmp);
}
QString KProtocolManager::cacheDir()
{
return http_config().readPathEntry("CacheDir", KGlobal::dirs()->saveLocation("cache","http"));
}
int KProtocolManager::maxCacheAge()
{
return http_config().readEntry( "MaxCacheAge", DEFAULT_MAX_CACHE_AGE ); // 14 days
}
int KProtocolManager::maxCacheSize()
{
return http_config().readEntry( "MaxCacheSize", DEFAULT_MAX_CACHE_SIZE ); // 5 MB
}
QString KProtocolManager::noProxyFor()
{
QString noProxy = config()->group("Proxy Settings").readEntry( "NoProxyFor" );
if (proxyType() == EnvVarProxy)
noProxy = QString::fromLocal8Bit(qgetenv(noProxy.toLocal8Bit()));
return noProxy;
}
static QString adjustProtocol(const QString& scheme)
{
if (scheme.compare(QL1S("webdav"), Qt::CaseInsensitive) == 0)
return QL1S("http");
if (scheme.compare(QL1S("webdavs"), Qt::CaseInsensitive) == 0)
return QL1S("https");
return scheme.toLower();
}
QString KProtocolManager::proxyFor( const QString& protocol )
{
const QString key = adjustProtocol(protocol) + QL1S("Proxy");
QString proxyStr (config()->group("Proxy Settings").readEntry(key));
const int index = proxyStr.lastIndexOf(QL1C(' '));
if (index > -1) {
bool ok = false;
const QString portStr(proxyStr.right(proxyStr.length() - index - 1));
portStr.toInt(&ok);
if (ok) {
proxyStr = proxyStr.left(index) + QL1C(':') + portStr;
} else {
proxyStr.clear();
}
}
return proxyStr;
}
QString KProtocolManager::proxyForUrl( const KUrl &url )
{
const QStringList proxies = proxiesForUrl(url);
if (proxies.isEmpty())
return QString();
return proxies.first();
}
static QStringList getSystemProxyFor( const KUrl& url )
{
QStringList proxies;
#if !defined(QT_NO_NETWORKPROXY) && (defined(Q_OS_WIN32) || defined(Q_OS_MAC))
QNetworkProxyQuery query ( url );
const QList<QNetworkProxy> proxyList = QNetworkProxyFactory::systemProxyForQuery(query);
Q_FOREACH(const QNetworkProxy& proxy, proxyList)
{
KUrl url;
const QNetworkProxy::ProxyType type = proxy.type();
if (type == QNetworkProxy::NoProxy || type == QNetworkProxy::DefaultProxy)
{
proxies << QL1S("DIRECT");
continue;
}
if (type == QNetworkProxy::HttpProxy || type == QNetworkProxy::HttpCachingProxy)
url.setProtocol(QL1S("http"));
else if (type == QNetworkProxy::Socks5Proxy)
url.setProtocol(QL1S("socks"));
else if (type == QNetworkProxy::FtpCachingProxy)
url.setProtocol(QL1S("ftp"));
url.setHost(proxy.hostName());
url.setPort(proxy.port());
url.setUser(proxy.user());
proxies << url.url();
}
#else
// On Unix/Linux use system environment variables if any are set.
- QString proxyVar (KProtocolManager::proxyFor(url.protocol()));
+ QString proxyVar (KProtocolManager::proxyFor(url.scheme()));
// Check for SOCKS proxy, if not proxy is found for given url.
if (!proxyVar.isEmpty()) {
const QString proxy (QString::fromLocal8Bit(qgetenv(proxyVar.toLocal8Bit())).trimmed());
if (!proxy.isEmpty()) {
proxies << proxy;
}
}
// Add the socks proxy as an alternate proxy if it exists,
proxyVar = KProtocolManager::proxyFor(QL1S("socks"));
if (!proxyVar.isEmpty()) {
QString proxy = QString::fromLocal8Bit(qgetenv(proxyVar.toLocal8Bit())).trimmed();
// Make sure the scheme of SOCKS proxy is always set to "socks://".
const int index = proxy.indexOf(QL1S("://"));
proxy = QL1S("socks://") + (index == -1 ? proxy : proxy.mid(index+3));
if (!proxy.isEmpty()) {
proxies << proxy;
}
}
#endif
return proxies;
}
QStringList KProtocolManager::proxiesForUrl( const KUrl &url )
{
QStringList proxyList;
PRIVATE_DATA;
if (!d->shouldIgnoreProxyFor(url)) {
switch (proxyType())
{
case PACProxy:
case WPADProxy:
{
KUrl u (url);
- const QString protocol = adjustProtocol(u.protocol());
+ const QString protocol = adjustProtocol(u.scheme());
u.setProtocol(protocol);
if (KProtocolInfo::protocolClass(protocol) != QL1S(":local")) {
QDBusReply<QStringList> reply = QDBusInterface(QL1S("org.kde.kded"),
QL1S("/modules/proxyscout"),
QL1S("org.kde.KPAC.ProxyScout"))
.call(QL1S("proxiesForUrl"), u.url());
proxyList = reply;
}
break;
}
case EnvVarProxy:
proxyList = getSystemProxyFor( url );
break;
case ManualProxy:
{
- QString proxy (proxyFor(url.protocol()));
+ QString proxy (proxyFor(url.scheme()));
if (!proxy.isEmpty())
proxyList << proxy;
// Add the socks proxy as an alternate proxy if it exists,
proxy = proxyFor(QL1S("socks"));
if (!proxy.isEmpty()) {
// Make sure the scheme of SOCKS proxy is always set to "socks://".
const int index = proxy.indexOf(QL1S("://"));
proxy = QL1S("socks://") + (index == -1 ? proxy : proxy.mid(index+3));
proxyList << proxy;
}
}
break;
case NoProxy:
default:
break;
}
}
if (proxyList.isEmpty()) {
proxyList << QL1S("DIRECT");
}
return proxyList;
}
void KProtocolManager::badProxy( const QString &proxy )
{
QDBusInterface( QL1S("org.kde.kded"), QL1S("/modules/proxyscout"))
.asyncCall(QL1S("blackListProxy"), proxy);
PRIVATE_DATA;
const QStringList keys (d->cachedProxyData.keys());
Q_FOREACH(const QString& key, keys) {
d->cachedProxyData[key]->removeAddress(proxy);
}
}
QString KProtocolManager::slaveProtocol(const KUrl &url, QString &proxy)
{
QStringList proxyList;
const QString protocol = KProtocolManager::slaveProtocol(url, proxyList);
if (!proxyList.isEmpty()) {
proxy = proxyList.first();
}
return protocol;
}
// Generates proxy cache key from request given url.
static void extractProxyCacheKeyFromUrl(const KUrl& u, QString* key)
{
if (!key)
return;
- *key = u.protocol();
+ *key = u.scheme();
*key += u.host();
if (u.port() > 0)
*key += QString::number(u.port());
}
QString KProtocolManager::slaveProtocol(const KUrl &url, QStringList &proxyList)
{
if (url.hasSubUrl()) { // We don't want the suburl's protocol
const KUrl::List list = KUrl::split(url);
return slaveProtocol(list.last(), proxyList);
}
proxyList.clear();
// Do not perform a proxy lookup for any url classified as a ":local" url or
// one that does not have a host component or if proxy is disabled.
- QString protocol (url.protocol());
+ QString protocol (url.scheme());
if (!url.hasHost()
|| KProtocolInfo::protocolClass(protocol) == QL1S(":local")
|| KProtocolManager::proxyType() == KProtocolManager::NoProxy) {
return protocol;
}
QString proxyCacheKey;
extractProxyCacheKeyFromUrl(url, &proxyCacheKey);
PRIVATE_DATA;
// Look for cached proxy information to avoid more work.
if (d->cachedProxyData.contains(proxyCacheKey)) {
KProxyData* data = d->cachedProxyData.object(proxyCacheKey);
proxyList = data->proxyList;
return data->protocol;
}
const QStringList proxies = proxiesForUrl(url);
const int count = proxies.count();
if (count > 0 && !(count == 1 && proxies.first() == QL1S("DIRECT"))) {
Q_FOREACH(const QString& proxy, proxies) {
if (proxy == QL1S("DIRECT")) {
proxyList << proxy;
} else {
KUrl u (proxy);
- if (!u.isEmpty() && u.isValid() && !u.protocol().isEmpty()) {
+ if (!u.isEmpty() && u.isValid() && !u.scheme().isEmpty()) {
proxyList << proxy;
}
}
}
}
// The idea behind slave protocols is not applicable to http
// and webdav protocols as well as protocols unknown to KDE.
if (!proxyList.isEmpty()
&& !protocol.startsWith(QL1S("http"))
&& !protocol.startsWith(QL1S("webdav"))
&& KProtocolInfo::isKnownProtocol(protocol)) {
Q_FOREACH(const QString& proxy, proxyList) {
KUrl u (proxy);
- if (u.isValid() && KProtocolInfo::isKnownProtocol(u.protocol())) {
- protocol = u.protocol();
+ if (u.isValid() && KProtocolInfo::isKnownProtocol(u.scheme())) {
+ protocol = u.scheme();
break;
}
}
}
// cache the proxy information...
d->cachedProxyData.insert(proxyCacheKey, new KProxyData(protocol, proxyList));
return protocol;
}
/*================================= USER-AGENT SETTINGS =====================*/
QString KProtocolManager::userAgentForHost( const QString& hostname )
{
const QString sendUserAgent = KIO::SlaveConfig::self()->configData("http", hostname.toLower(), "SendUserAgent").toLower();
if (sendUserAgent == QL1S("false"))
return QString();
const QString useragent = KIO::SlaveConfig::self()->configData("http", hostname.toLower(), "UserAgent");
// Return the default user-agent if none is specified
// for the requested host.
if (useragent.isEmpty())
return defaultUserAgent();
return useragent;
}
QString KProtocolManager::defaultUserAgent( )
{
const QString modifiers = KIO::SlaveConfig::self()->configData("http", QString(), "UserAgentKeys");
return defaultUserAgent(modifiers);
}
static QString defaultUserAgentFromPreferredService()
{
QString agentStr;
// Check if the default COMPONENT contains a custom default UA string...
KService::Ptr service = KMimeTypeTrader::self()->preferredService(QL1S("text/html"),
QL1S("KParts/ReadOnlyPart"));
if (service && service->showInKDE())
agentStr = service->property(QL1S("X-KDE-Default-UserAgent"),
QVariant::String).toString();
return agentStr;
}
static QString platform()
{
#if defined(Q_WS_X11)
return QL1S("X11");
#elif defined(Q_WS_MAC)
return QL1S("Macintosh");
#elif defined(Q_WS_WIN)
return QL1S("Windows");
#elif defined(Q_WS_S60)
return QL1S("Symbian");
#else
#warning QT5 PORT TO QPA
return QString();
#endif
}
QString KProtocolManager::defaultUserAgent( const QString &_modifiers )
{
PRIVATE_DATA;
QString modifiers = _modifiers.toLower();
if (modifiers.isEmpty())
modifiers = DEFAULT_USER_AGENT_KEYS;
if (d->modifiers == modifiers && !d->useragent.isEmpty())
return d->useragent;
d->modifiers = modifiers;
/*
The following code attempts to determine the default user agent string
from the 'X-KDE-UA-DEFAULT-STRING' property of the desktop file
for the preferred service that was configured to handle the 'text/html'
mime type. If the prefered service's desktop file does not specify this
property, the long standing default user agent string will be used.
The following keyword placeholders are automatically converted when the
user agent string is read from the property:
%SECURITY% Expands to"N" when SSL is not supported, otherwise it is ignored.
%OSNAME% Expands to operating system name, e.g. Linux.
%OSVERSION% Expands to operating system version, e.g. 2.6.32
%SYSTYPE% Expands to machine or system type, e.g. i386
%PLATFORM% Expands to windowing system, e.g. X11 on Unix/Linux.
%LANGUAGE% Expands to default language in use, e.g. en-US.
%APPVERSION% Expands to QCoreApplication applicationName()/applicationVerison(),
e.g. Konqueror/4.5.0. If application name and/or application version
number are not set, then "KDE" and the runtime KDE version numbers
are used respectively.
All of the keywords are handled case-insensitively.
*/
QString systemName, systemVersion, machine, supp;
const bool sysInfoFound = getSystemNameVersionAndMachine( systemName, systemVersion, machine );
QString agentStr = defaultUserAgentFromPreferredService();
if (agentStr.isEmpty())
{
supp += platform();
if (sysInfoFound)
{
if (modifiers.contains('o'))
{
supp += QL1S("; ");
supp += systemName;
if (modifiers.contains('v'))
{
supp += QL1C(' ');
supp += systemVersion;
}
if (modifiers.contains('m'))
{
supp += QL1C(' ');
supp += machine;
}
}
if (modifiers.contains('l'))
{
supp += QL1S("; ");
supp += KGlobal::locale()->language();
}
}
// Full format: Mozilla/5.0 (Linux
d->useragent = QL1S("Mozilla/5.0 (");
d->useragent += supp;
d->useragent += QL1S(") KHTML/");
d->useragent += QString::number(KDE::versionMajor());
d->useragent += QL1C('.');
d->useragent += QString::number(KDE::versionMinor());
d->useragent += QL1C('.');
d->useragent += QString::number(KDE::versionRelease());
d->useragent += QL1S(" (like Gecko) Konqueror/");
d->useragent += QString::number(KDE::versionMajor());
d->useragent += QL1C('.');
d->useragent += QString::number(KDE::versionMinor());
}
else
{
QString appName = QCoreApplication::applicationName();
if (appName.isEmpty() || appName.startsWith(QL1S("kcmshell"), Qt::CaseInsensitive))
appName = QL1S ("KDE");
QString appVersion = QCoreApplication::applicationVersion();
if (appVersion.isEmpty()) {
appVersion += QString::number(KDE::versionMajor());
appVersion += QL1C('.');
appVersion += QString::number(KDE::versionMinor());
appVersion += QL1C('.');
appVersion += QString::number(KDE::versionRelease());
}
appName += QL1C('/');
appName += appVersion;
agentStr.replace(QL1S("%appversion%"), appName, Qt::CaseInsensitive);
if (!QSslSocket::supportsSsl())
agentStr.replace(QL1S("%security%"), QL1S("N"), Qt::CaseInsensitive);
else
agentStr.remove(QL1S("%security%"), Qt::CaseInsensitive);
if (sysInfoFound)
{
// Platform (e.g. X11). It is no longer configurable from UI.
agentStr.replace(QL1S("%platform%"), platform(), Qt::CaseInsensitive);
// Operating system (e.g. Linux)
if (modifiers.contains('o'))
{
agentStr.replace(QL1S("%osname%"), systemName, Qt::CaseInsensitive);
// OS version (e.g. 2.6.36)
if (modifiers.contains('v'))
agentStr.replace(QL1S("%osversion%"), systemVersion, Qt::CaseInsensitive);
else
agentStr.remove(QL1S("%osversion%"), Qt::CaseInsensitive);
// Machine type (i686, x86-64, etc.)
if (modifiers.contains('m'))
agentStr.replace(QL1S("%systype%"), machine, Qt::CaseInsensitive);
else
agentStr.remove(QL1S("%systype%"), Qt::CaseInsensitive);
}
else
{
agentStr.remove(QL1S("%osname%"), Qt::CaseInsensitive);
agentStr.remove(QL1S("%osversion%"), Qt::CaseInsensitive);
agentStr.remove(QL1S("%systype%"), Qt::CaseInsensitive);
}
// Language (e.g. en_US)
if (modifiers.contains('l'))
agentStr.replace(QL1S("%language%"), KGlobal::locale()->language(), Qt::CaseInsensitive);
else
agentStr.remove(QL1S("%language%"), Qt::CaseInsensitive);
// Clean up unnecessary separators that could be left over from the
// possible keyword removal above...
agentStr.replace(QRegExp("[(]\\s*[;]\\s*"), QL1S("("));
agentStr.replace(QRegExp("[;]\\s*[;]\\s*"), QL1S("; "));
agentStr.replace(QRegExp("\\s*[;]\\s*[)]"), QL1S(")"));
}
else
{
agentStr.remove(QL1S("%osname%"));
agentStr.remove(QL1S("%osversion%"));
agentStr.remove(QL1S("%platform%"));
agentStr.remove(QL1S("%systype%"));
agentStr.remove(QL1S("%language%"));
}
d->useragent = agentStr.simplified();
}
//kDebug() << "USERAGENT STRING:" << d->useragent;
return d->useragent;
}
QString KProtocolManager::userAgentForApplication( const QString &appName, const QString& appVersion,
const QStringList& extraInfo )
{
QString systemName, systemVersion, machine, info;
if (getSystemNameVersionAndMachine( systemName, systemVersion, machine ))
{
info += systemName;
info += QL1C('/');
info += systemVersion;
info += QL1S("; ");
}
info += QL1S("KDE/");
info += QString::number(KDE::versionMajor());
info += QL1C('.');
info += QString::number(KDE::versionMinor());
info += QL1C('.');
info += QString::number(KDE::versionRelease());
if (!machine.isEmpty())
{
info += QL1S("; ");
info += machine;
}
info += QL1S("; ");
info += extraInfo.join(QL1S("; "));
return (appName + QL1C('/') + appVersion + QL1S(" (") + info + QL1C(')'));
}
bool KProtocolManager::getSystemNameVersionAndMachine(
QString& systemName, QString& systemVersion, QString& machine )
{
struct utsname unameBuf;
if ( 0 != uname( &unameBuf ) )
return false;
#if defined(Q_WS_WIN) && !defined(_WIN32_WCE)
// we do not use unameBuf.sysname information constructed in kdewin32
// because we want to get separate name and version
systemName = QL1S( "Windows" );
OSVERSIONINFOEX versioninfo;
ZeroMemory(&versioninfo, sizeof(OSVERSIONINFOEX));
// try calling GetVersionEx using the OSVERSIONINFOEX, if that fails, try using the OSVERSIONINFO
versioninfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
bool ok = GetVersionEx( (OSVERSIONINFO *) &versioninfo );
if ( !ok ) {
versioninfo.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
ok = GetVersionEx( (OSVERSIONINFO *) &versioninfo );
}
if ( ok ) {
systemVersion = QString::number(versioninfo.dwMajorVersion);
systemVersion += QL1C('.');
systemVersion += QString::number(versioninfo.dwMinorVersion);
}
#else
systemName = unameBuf.sysname;
systemVersion = unameBuf.release;
#endif
machine = unameBuf.machine;
return true;
}
QString KProtocolManager::acceptLanguagesHeader()
{
static const QString &english = KGlobal::staticQString("en");
// User's desktop language preference.
QStringList languageList = KGlobal::locale()->languageList();
// Replace possible "C" in the language list with "en", unless "en" is
// already pressent. This is to keep user's priorities in order.
// If afterwards "en" is still not present, append it.
int idx = languageList.indexOf(QString::fromLatin1("C"));
if (idx != -1)
{
if (languageList.contains(english))
languageList.removeAt(idx);
else
languageList[idx] = english;
}
if (!languageList.contains(english))
languageList += english;
// Some languages may have web codes different from locale codes,
// read them from the config and insert in proper order.
KConfig acclangConf("accept-languages.codes", KConfig::NoGlobals);
KConfigGroup replacementCodes(&acclangConf, "ReplacementCodes");
QStringList languageListFinal;
Q_FOREACH (const QString &lang, languageList)
{
const QStringList langs = replacementCodes.readEntry(lang, QStringList());
if (langs.isEmpty())
languageListFinal += lang;
else
languageListFinal += langs;
}
// The header is composed of comma separated languages, with an optional
// associated priority estimate (q=1..0) defaulting to 1.
// As our language tags are already sorted by priority, we'll just decrease
// the value evenly
int prio = 10;
QString header;
Q_FOREACH (const QString &lang,languageListFinal) {
header += lang;
if (prio < 10) {
header += QL1S(";q=0.");
header += QString::number(prio);
}
// do not add cosmetic whitespace in here : it is less compatible (#220677)
header += QL1S(",");
if (prio > 1)
--prio;
}
header.chop(1);
// Some of the languages may have country specifier delimited by
// underscore, or modifier delimited by at-sign.
// The header should use dashes instead.
header.replace('_', '-');
header.replace('@', '-');
return header;
}
/*==================================== OTHERS ===============================*/
bool KProtocolManager::markPartial()
{
return config()->group(QByteArray()).readEntry( "MarkPartial", true );
}
int KProtocolManager::minimumKeepSize()
{
return config()->group(QByteArray()).readEntry( "MinimumKeepSize",
DEFAULT_MINIMUM_KEEP_SIZE ); // 5000 byte
}
bool KProtocolManager::autoResume()
{
return config()->group(QByteArray()).readEntry( "AutoResume", false );
}
bool KProtocolManager::persistentConnections()
{
return config()->group(QByteArray()).readEntry( "PersistentConnections", true );
}
bool KProtocolManager::persistentProxyConnection()
{
return config()->group(QByteArray()).readEntry( "PersistentProxyConnection", false );
}
QString KProtocolManager::proxyConfigScript()
{
return config()->group("Proxy Settings").readEntry( "Proxy Config Script" );
}
/* =========================== PROTOCOL CAPABILITIES ============== */
static KProtocolInfo::Ptr findProtocol(const KUrl &url)
{
- QString protocol = url.protocol();
+ QString protocol = url.scheme();
if ( !KProtocolInfo::proxiedBy( protocol ).isEmpty() )
{
QString dummy;
protocol = KProtocolManager::slaveProtocol(url, dummy);
}
return KProtocolInfoFactory::self()->findProtocol(protocol);
}
KProtocolInfo::Type KProtocolManager::inputType( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return KProtocolInfo::T_NONE;
return prot->m_inputType;
}
KProtocolInfo::Type KProtocolManager::outputType( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return KProtocolInfo::T_NONE;
return prot->m_outputType;
}
bool KProtocolManager::isSourceProtocol( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return false;
return prot->m_isSourceProtocol;
}
bool KProtocolManager::supportsListing( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return false;
return prot->m_supportsListing;
}
QStringList KProtocolManager::listing( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return QStringList();
return prot->m_listing;
}
bool KProtocolManager::supportsReading( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return false;
return prot->m_supportsReading;
}
bool KProtocolManager::supportsWriting( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return false;
return prot->m_supportsWriting;
}
bool KProtocolManager::supportsMakeDir( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return false;
return prot->m_supportsMakeDir;
}
bool KProtocolManager::supportsDeleting( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return false;
return prot->m_supportsDeleting;
}
bool KProtocolManager::supportsLinking( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return false;
return prot->m_supportsLinking;
}
bool KProtocolManager::supportsMoving( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return false;
return prot->m_supportsMoving;
}
bool KProtocolManager::supportsOpening( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return false;
return prot->m_supportsOpening;
}
bool KProtocolManager::canCopyFromFile( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return false;
return prot->m_canCopyFromFile;
}
bool KProtocolManager::canCopyToFile( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return false;
return prot->m_canCopyToFile;
}
bool KProtocolManager::canRenameFromFile( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return false;
return prot->canRenameFromFile();
}
bool KProtocolManager::canRenameToFile( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return false;
return prot->canRenameToFile();
}
bool KProtocolManager::canDeleteRecursive( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return false;
return prot->canDeleteRecursive();
}
KProtocolInfo::FileNameUsedForCopying KProtocolManager::fileNameUsedForCopying( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return KProtocolInfo::FromUrl;
return prot->fileNameUsedForCopying();
}
QString KProtocolManager::defaultMimetype( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return QString();
return prot->m_defaultMimetype;
}
QString KProtocolManager::protocolForArchiveMimetype( const QString& mimeType )
{
PRIVATE_DATA;
if (d->protocolForArchiveMimetypes.isEmpty()) {
const KProtocolInfo::List allProtocols = KProtocolInfoFactory::self()->allProtocols();
for (KProtocolInfo::List::const_iterator it = allProtocols.begin();
it != allProtocols.end(); ++it) {
const QStringList archiveMimetypes = (*it)->archiveMimeTypes();
Q_FOREACH(const QString& mime, archiveMimetypes) {
d->protocolForArchiveMimetypes.insert(mime, (*it)->name());
}
}
}
return d->protocolForArchiveMimetypes.value(mimeType);
}
#undef PRIVATE_DATA
diff --git a/kio/kio/krun.cpp b/kio/kio/krun.cpp
index c4acb6c8f0..b54888991f 100644
--- a/kio/kio/krun.cpp
+++ b/kio/kio/krun.cpp
@@ -1,1788 +1,1788 @@
/* This file is part of the KDE libraries
Copyright (C) 2000 Torben Weis <weis@kde.org>
Copyright (C) 2006 David Faure <faure@kde.org>
Copyright (C) 2009 Michael Pyne <michael.pyne@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 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 "krun.h"
#include "krun_p.h"
#include <config.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <typeinfo>
#include <sys/stat.h>
#include <QWidget>
#include <QLabel>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPlainTextEdit>
#include <QApplication>
#include <QDesktopWidget>
#include <kmimetypetrader.h>
#include <kmimetype.h>
#include "kio/jobclasses.h" // for KIO::JobFlags
#include "kio/job.h"
#include "kio/jobuidelegate.h"
#include "kio/global.h"
#include "kio/scheduler.h"
#include "kio/netaccess.h"
#include "kfile/kopenwithdialog.h"
#include "kfile/krecentdocument.h"
#include "kdesktopfileactions.h"
#include <kauthorized.h>
#include <kmessageboxwrapper.h>
#include <kurl.h>
#include <kglobal.h>
#include <kglobalsettings.h>
#include <ktoolinvocation.h>
#include <kdebug.h>
#include <klocale.h>
#include <kprotocolmanager.h>
#include <kstandarddirs.h>
#include <kprocess.h>
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
#include <QtCore/QTextStream>
#include <QtCore/QDate>
#include <QtCore/QRegExp>
#include <QDir>
#include <kdesktopfile.h>
#include <kmacroexpander.h>
#include <kshell.h>
#include <QTextDocument>
#include <kde_file.h>
#include <kconfiggroup.h>
#include <kdialog.h>
#include <kstandardguiitem.h>
#include <kguiitem.h>
#include <ksavefile.h>
#ifdef Q_WS_X11
#include <kwindowsystem.h>
#endif
KRun::KRunPrivate::KRunPrivate(KRun *parent)
: q(parent),
m_showingDialog(false)
{
}
void KRun::KRunPrivate::startTimer()
{
m_timer.start(0);
}
// ---------------------------------------------------------------------------
bool KRun::isExecutableFile(const KUrl& url, const QString &mimetype)
{
if (!url.isLocalFile()) {
return false;
}
QFileInfo file(url.toLocalFile());
if (file.isExecutable()) { // Got a prospective file to run
KMimeType::Ptr mimeType = KMimeType::mimeType(mimetype, KMimeType::ResolveAliases);
if (mimeType && (mimeType->is(QLatin1String("application/x-executable")) ||
#ifdef Q_WS_WIN
mimeType->is(QLatin1String("application/x-ms-dos-executable")) ||
#endif
mimeType->is(QLatin1String("application/x-executable-script")))
)
{
return true;
}
}
return false;
}
// This is called by foundMimeType, since it knows the mimetype of the URL
bool KRun::runUrl(const KUrl& u, const QString& _mimetype, QWidget* window, bool tempFile, bool runExecutables, const QString& suggestedFileName, const QByteArray& asn)
{
bool noRun = false;
bool noAuth = false;
if (_mimetype == QLatin1String("inode/directory-locked")) {
KMessageBoxWrapper::error(window,
i18n("<qt>Unable to enter <b>%1</b>.\nYou do not have access rights to this location.</qt>", Qt::escape(u.prettyUrl())));
return false;
}
else if (_mimetype == QLatin1String("application/x-desktop")) {
if (u.isLocalFile() && runExecutables) {
return KDesktopFileActions::run(u, true);
}
}
else if (isExecutableFile(u, _mimetype)) {
if (u.isLocalFile() && runExecutables) {
if (KAuthorized::authorize("shell_access")) {
return (KRun::runCommand(KShell::quoteArg(u.toLocalFile()), QString(), QString(), window, asn, u.directory())); // just execute the url as a command
// ## TODO implement deleting the file if tempFile==true
}
else {
noAuth = true;
}
}
else if (_mimetype == QLatin1String("application/x-executable")) {
noRun = true;
}
}
else if (isExecutable(_mimetype)) {
if (!runExecutables) {
noRun = true;
}
if (!KAuthorized::authorize("shell_access")) {
noAuth = true;
}
}
if (noRun) {
KMessageBox::sorry(window,
i18n("<qt>The file <b>%1</b> is an executable program. "
"For safety it will not be started.</qt>", Qt::escape(u.prettyUrl())));
return false;
}
if (noAuth) {
KMessageBoxWrapper::error(window,
i18n("<qt>You do not have permission to run <b>%1</b>.</qt>", Qt::escape(u.prettyUrl())));
return false;
}
KUrl::List lst;
lst.append(u);
KService::Ptr offer = KMimeTypeTrader::self()->preferredService(_mimetype);
if (!offer) {
// Open-with dialog
// TODO : pass the mimetype as a parameter, to show it (comment field) in the dialog !
// Hmm, in fact KOpenWithDialog::setServiceType already guesses the mimetype from the first URL of the list...
return displayOpenWithDialog(lst, window, tempFile, suggestedFileName, asn);
}
return KRun::run(*offer, lst, window, tempFile, suggestedFileName, asn);
}
bool KRun::displayOpenWithDialog(const KUrl::List& lst, QWidget* window, bool tempFiles,
const QString& suggestedFileName, const QByteArray& asn)
{
if (!KAuthorized::authorizeKAction("openwith")) {
KMessageBox::sorry(window,
i18n("You are not authorized to select an application to open this file."));
return false;
}
#ifdef Q_WS_WIN
KConfigGroup cfgGroup(KGlobal::config(), "KOpenWithDialog Settings");
if (cfgGroup.readEntry("Native", true)) {
return KRun::KRunPrivate::displayNativeOpenWithDialog(lst, window, tempFiles,
suggestedFileName, asn);
}
#endif
KOpenWithDialog l(lst, i18n("Open with:"), QString(), window);
if (l.exec()) {
KService::Ptr service = l.service();
if (!service) {
kDebug(7010) << "No service set, running " << l.text();
service = KService::Ptr(new KService(QString() /*name*/, l.text(), QString() /*icon*/));
}
return KRun::run(*service, lst, window, tempFiles, suggestedFileName, asn);
}
return false;
}
#ifndef KDE_NO_DEPRECATED
void KRun::shellQuote(QString &_str)
{
// Credits to Walter, says Bernd G. :)
if (_str.isEmpty()) { // Don't create an explicit empty parameter
return;
}
QChar q('\'');
_str.replace(q, "'\\''").prepend(q).append(q);
}
#endif
class KRunMX1 : public KMacroExpanderBase
{
public:
KRunMX1(const KService &_service) :
KMacroExpanderBase('%'), hasUrls(false), hasSpec(false), service(_service) {}
bool hasUrls: 1, hasSpec: 1;
protected:
virtual int expandEscapedMacro(const QString &str, int pos, QStringList &ret);
private:
const KService &service;
};
int
KRunMX1::expandEscapedMacro(const QString &str, int pos, QStringList &ret)
{
uint option = str[pos + 1].unicode();
switch (option) {
case 'c':
ret << service.name().replace('%', "%%");
break;
case 'k':
ret << service.entryPath().replace('%', "%%");
break;
case 'i':
ret << "--icon" << service.icon().replace('%', "%%");
break;
case 'm':
// ret << "-miniicon" << service.icon().replace( '%', "%%" );
kWarning() << "-miniicon isn't supported anymore (service"
<< service.name() << ')';
break;
case 'u':
case 'U':
hasUrls = true;
/* fallthrough */
case 'f':
case 'F':
case 'n':
case 'N':
case 'd':
case 'D':
case 'v':
hasSpec = true;
/* fallthrough */
default:
return -2; // subst with same and skip
}
return 2;
}
class KRunMX2 : public KMacroExpanderBase
{
public:
KRunMX2(const KUrl::List &_urls) :
KMacroExpanderBase('%'), ignFile(false), urls(_urls) {}
bool ignFile: 1;
protected:
virtual int expandEscapedMacro(const QString &str, int pos, QStringList &ret);
private:
void subst(int option, const KUrl &url, QStringList &ret);
const KUrl::List &urls;
};
void
KRunMX2::subst(int option, const KUrl &url, QStringList &ret)
{
switch (option) {
case 'u':
ret << ((url.isLocalFile() && url.fragment().isNull() && url.encodedQuery().isNull()) ?
QDir::toNativeSeparators(url.toLocalFile()) : url.url());
break;
case 'd':
ret << url.directory();
break;
case 'f':
ret << QDir::toNativeSeparators(url.toLocalFile());
break;
case 'n':
ret << url.fileName();
break;
case 'v':
if (url.isLocalFile() && QFile::exists(url.toLocalFile())) {
ret << KDesktopFile(url.path()).desktopGroup().readEntry("Dev");
}
break;
}
return;
}
int
KRunMX2::expandEscapedMacro(const QString &str, int pos, QStringList &ret)
{
uint option = str[pos + 1].unicode();
switch (option) {
case 'f':
case 'u':
case 'n':
case 'd':
case 'v':
if (urls.isEmpty()) {
if (!ignFile) {
kDebug() << "No URLs supplied to single-URL service" << str;
}
}
else if (urls.count() > 1) {
kWarning() << urls.count() << "URLs supplied to single-URL service" << str;
}
else {
subst(option, urls.first(), ret);
}
break;
case 'F':
case 'U':
case 'N':
case 'D':
option += 'a' - 'A';
for (KUrl::List::ConstIterator it = urls.begin(); it != urls.end(); ++it)
subst(option, *it, ret);
break;
case '%':
ret = QStringList(QLatin1String("%"));
break;
default:
return -2; // subst with same and skip
}
return 2;
}
static QStringList supportedProtocols(const KService& _service)
{
// Check which protocols the application supports.
// This can be a list of actual protocol names, or just KIO for KDE apps.
QStringList supportedProtocols = _service.property("X-KDE-Protocols").toStringList();
KRunMX1 mx1(_service);
QString exec = _service.exec();
if (mx1.expandMacrosShellQuote(exec) && !mx1.hasUrls) {
Q_ASSERT(supportedProtocols.isEmpty()); // huh? If you support protocols you need %u or %U...
}
else {
if (supportedProtocols.isEmpty()) {
// compat mode: assume KIO if not set and it's a KDE app (or a KDE service)
const QStringList categories = _service.property("Categories").toStringList();
if (categories.contains("KDE") || !_service.isApplication()) {
supportedProtocols.append("KIO");
}
else { // if no KDE app, be a bit over-generic
supportedProtocols.append("http");
supportedProtocols.append("https"); // #253294
supportedProtocols.append("ftp");
}
}
}
kDebug(7010) << "supportedProtocols:" << supportedProtocols;
return supportedProtocols;
}
static bool isProtocolInSupportedList(const KUrl& url, const QStringList& supportedProtocols)
{
if (supportedProtocols.contains("KIO"))
return true;
- return url.isLocalFile() || supportedProtocols.contains(url.protocol().toLower());
+ return url.isLocalFile() || supportedProtocols.contains(url.scheme().toLower());
}
QStringList KRun::processDesktopExec(const KService &_service, const KUrl::List& _urls, bool tempFiles, const QString& suggestedFileName)
{
QString exec = _service.exec();
if (exec.isEmpty()) {
kWarning() << "KRun: no Exec field in `" << _service.entryPath() << "' !";
return QStringList();
}
QStringList result;
bool appHasTempFileOption;
KRunMX1 mx1(_service);
KRunMX2 mx2(_urls);
if (!mx1.expandMacrosShellQuote(exec)) { // Error in shell syntax
kWarning() << "KRun: syntax error in command" << _service.exec() << ", service" << _service.name();
return QStringList();
}
// FIXME: the current way of invoking kioexec disables term and su use
// Check if we need "tempexec" (kioexec in fact)
appHasTempFileOption = tempFiles && _service.property("X-KDE-HasTempFileOption").toBool();
if (tempFiles && !appHasTempFileOption && _urls.size()) {
const QString kioexec = KStandardDirs::findExe("kioexec");
Q_ASSERT(!kioexec.isEmpty());
result << kioexec << "--tempfiles" << exec;
if (!suggestedFileName.isEmpty()) {
result << "--suggestedfilename";
result << suggestedFileName;
}
result += _urls.toStringList();
return result;
}
// Check if we need kioexec
bool useKioexec = false;
if (!mx1.hasUrls) {
for (KUrl::List::ConstIterator it = _urls.begin(); it != _urls.end(); ++it)
if (!(*it).isLocalFile() && !KProtocolInfo::isHelperProtocol(*it)) {
useKioexec = true;
kDebug(7010) << "non-local files, application does not support urls, using kioexec";
break;
}
} else { // app claims to support %u/%U, check which protocols
QStringList appSupportedProtocols = supportedProtocols(_service);
for (KUrl::List::ConstIterator it = _urls.begin(); it != _urls.end(); ++it)
if (!isProtocolInSupportedList(*it, appSupportedProtocols) && !KProtocolInfo::isHelperProtocol(*it)) {
useKioexec = true;
kDebug(7010) << "application does not support url, using kioexec:" << *it;
break;
}
}
if (useKioexec) {
// We need to run the app through kioexec
const QString kioexec = KStandardDirs::findExe("kioexec");
Q_ASSERT(!kioexec.isEmpty());
result << kioexec;
if (tempFiles) {
result << "--tempfiles";
}
if (!suggestedFileName.isEmpty()) {
result << "--suggestedfilename";
result << suggestedFileName;
}
result << exec;
result += _urls.toStringList();
return result;
}
if (appHasTempFileOption) {
exec += " --tempfile";
}
// Did the user forget to append something like '%f'?
// If so, then assume that '%f' is the right choice => the application
// accepts only local files.
if (!mx1.hasSpec) {
exec += " %f";
mx2.ignFile = true;
}
mx2.expandMacrosShellQuote(exec); // syntax was already checked, so don't check return value
/*
1 = need_shell, 2 = terminal, 4 = su
0 << split(cmd)
1 << "sh" << "-c" << cmd
2 << split(term) << "-e" << split(cmd)
3 << split(term) << "-e" << "sh" << "-c" << cmd
4 << "kdesu" << "-u" << user << "-c" << cmd
5 << "kdesu" << "-u" << user << "-c" << ("sh -c " + quote(cmd))
6 << split(term) << "-e" << "su" << user << "-c" << cmd
7 << split(term) << "-e" << "su" << user << "-c" << ("sh -c " + quote(cmd))
"sh -c" is needed in the "su" case, too, as su uses the user's login shell, not sh.
this could be optimized with the -s switch of some su versions (e.g., debian linux).
*/
if (_service.terminal()) {
KConfigGroup cg(KGlobal::config(), "General");
QString terminal = cg.readPathEntry("TerminalApplication", "konsole");
if (terminal == "konsole") {
if (!_service.path().isEmpty()) {
terminal += " --workdir " + KShell::quoteArg(_service.path());
}
terminal += " -caption=%c %i %m";
}
terminal += ' ';
terminal += _service.terminalOptions();
if (!mx1.expandMacrosShellQuote(terminal)) {
kWarning() << "KRun: syntax error in command" << terminal << ", service" << _service.name();
return QStringList();
}
mx2.expandMacrosShellQuote(terminal);
result = KShell::splitArgs(terminal); // assuming that the term spec never needs a shell!
result << "-e";
}
KShell::Errors err;
QStringList execlist = KShell::splitArgs(exec, KShell::AbortOnMeta | KShell::TildeExpand, &err);
if (err == KShell::NoError && !execlist.isEmpty()) { // mx1 checked for syntax errors already
// Resolve the executable to ensure that helpers in lib/kde4/libexec/ are found.
// Too bad for commands that need a shell - they must reside in $PATH.
const QString exePath = KStandardDirs::findExe(execlist[0]);
if (!exePath.isEmpty()) {
execlist[0] = exePath;
}
}
if (_service.substituteUid()) {
if (_service.terminal()) {
result << "su";
}
else {
result << KStandardDirs::findExe("kdesu") << "-u";
}
result << _service.username() << "-c";
if (err == KShell::FoundMeta) {
exec = "/bin/sh -c " + KShell::quoteArg(exec);
}
else {
exec = KShell::joinArgs(execlist);
}
result << exec;
}
else {
if (err == KShell::FoundMeta) {
result << "/bin/sh" << "-c" << exec;
}
else {
result += execlist;
}
}
return result;
}
//static
QString KRun::binaryName(const QString & execLine, bool removePath)
{
// Remove parameters and/or trailing spaces.
const QStringList args = KShell::splitArgs(execLine);
for (QStringList::ConstIterator it = args.begin(); it != args.end(); ++it)
if (!(*it).contains('=')) {
// Remove path if wanted
return removePath ? (*it).mid((*it).lastIndexOf('/') + 1) : *it;
}
return QString();
}
static bool runCommandInternal(KProcess* proc, const KService* service, const QString& executable,
const QString &userVisibleName, const QString & iconName, QWidget* window,
const QByteArray& asn)
{
if (window != NULL) {
window = window->topLevelWidget();
}
if (service && !service->entryPath().isEmpty()
&& !KDesktopFile::isAuthorizedDesktopFile(service->entryPath()))
{
kWarning() << "No authorization to execute " << service->entryPath();
KMessageBox::sorry(window, i18n("You are not authorized to execute this file."));
delete proc;
return false;
}
QString bin = KRun::binaryName(executable, true);
#ifdef Q_WS_X11 // Startup notification doesn't work with QT/E, service isn't needed without Startup notification
bool silent;
QByteArray wmclass;
KStartupInfoId id;
bool startup_notify = (asn != "0" && KRun::checkStartupNotify(QString() /*unused*/, service, &silent, &wmclass));
if (startup_notify) {
id.initId(asn);
id.setupStartupEnv();
KStartupInfoData data;
data.setHostname();
data.setBin(bin);
if (!userVisibleName.isEmpty()) {
data.setName(userVisibleName);
}
else if (service && !service->name().isEmpty()) {
data.setName(service->name());
}
data.setDescription(i18n("Launching %1" , data.name()));
if (!iconName.isEmpty()) {
data.setIcon(iconName);
}
else if (service && !service->icon().isEmpty()) {
data.setIcon(service->icon());
}
if (!wmclass.isEmpty()) {
data.setWMClass(wmclass);
}
if (silent) {
data.setSilent(KStartupInfoData::Yes);
}
data.setDesktop(KWindowSystem::currentDesktop());
if (window) {
data.setLaunchedBy(window->winId());
}
if(service && !service->entryPath().isEmpty())
data.setApplicationId(service->entryPath());
KStartupInfo::sendStartup(id, data);
}
int pid = KProcessRunner::run(proc, executable, id);
if (startup_notify && pid) {
KStartupInfoData data;
data.addPid(pid);
KStartupInfo::sendChange(id, data);
KStartupInfo::resetStartupEnv();
}
return pid != 0;
#else
Q_UNUSED(userVisibleName);
Q_UNUSED(iconName);
return KProcessRunner::run(proc, bin) != 0;
#endif
}
// This code is also used in klauncher.
bool KRun::checkStartupNotify(const QString& /*binName*/, const KService* service, bool* silent_arg, QByteArray* wmclass_arg)
{
bool silent = false;
QByteArray wmclass;
if (service && service->property("StartupNotify").isValid()) {
silent = !service->property("StartupNotify").toBool();
wmclass = service->property("StartupWMClass").toString().toLatin1();
}
else if (service && service->property("X-KDE-StartupNotify").isValid()) {
silent = !service->property("X-KDE-StartupNotify").toBool();
wmclass = service->property("X-KDE-WMClass").toString().toLatin1();
}
else { // non-compliant app
if (service) {
if (service->isApplication()) { // doesn't have .desktop entries needed, start as non-compliant
wmclass = "0"; // krazy:exclude=doublequote_chars
}
else {
return false; // no startup notification at all
}
}
else {
#if 0
// Create startup notification even for apps for which there shouldn't be any,
// just without any visual feedback. This will ensure they'll be positioned on the proper
// virtual desktop, and will get user timestamp from the ASN ID.
wmclass = '0';
silent = true;
#else // That unfortunately doesn't work, when the launched non-compliant application
// launches another one that is compliant and there is any delay inbetween (bnc:#343359)
return false;
#endif
}
}
if (silent_arg != NULL) {
*silent_arg = silent;
}
if (wmclass_arg != NULL) {
*wmclass_arg = wmclass;
}
return true;
}
static bool runTempService(const KService& _service, const KUrl::List& _urls, QWidget* window,
bool tempFiles, const QString& suggestedFileName, const QByteArray& asn)
{
if (!_urls.isEmpty()) {
kDebug(7010) << "runTempService: first url " << _urls.first().url();
}
QStringList args;
if ((_urls.count() > 1) && !_service.allowMultipleFiles()) {
// We need to launch the application N times. That sucks.
// We ignore the result for application 2 to N.
// For the first file we launch the application in the
// usual way. The reported result is based on this
// application.
KUrl::List::ConstIterator it = _urls.begin();
while (++it != _urls.end()) {
KUrl::List singleUrl;
singleUrl.append(*it);
runTempService(_service, singleUrl, window, tempFiles, suggestedFileName, QByteArray());
}
KUrl::List singleUrl;
singleUrl.append(_urls.first());
args = KRun::processDesktopExec(_service, singleUrl, tempFiles, suggestedFileName);
}
else {
args = KRun::processDesktopExec(_service, _urls, tempFiles, suggestedFileName);
}
if (args.isEmpty()) {
KMessageBox::sorry(window, i18n("Error processing Exec field in %1", _service.entryPath()));
return false;
}
kDebug(7010) << "runTempService: KProcess args=" << args;
KProcess * proc = new KProcess;
*proc << args;
if (!_service.path().isEmpty()) {
proc->setWorkingDirectory(_service.path());
}
return runCommandInternal(proc, &_service, KRun::binaryName(_service.exec(), false),
_service.name(), _service.icon(), window, asn);
}
// WARNING: don't call this from processDesktopExec, since klauncher uses that too...
static KUrl::List resolveURLs(const KUrl::List& _urls, const KService& _service)
{
// Check which protocols the application supports.
// This can be a list of actual protocol names, or just KIO for KDE apps.
QStringList appSupportedProtocols = supportedProtocols(_service);
KUrl::List urls(_urls);
if (!appSupportedProtocols.contains("KIO")) {
for (KUrl::List::Iterator it = urls.begin(); it != urls.end(); ++it) {
const KUrl url = *it;
bool supported = isProtocolInSupportedList(url, appSupportedProtocols);
kDebug(7010) << "Looking at url=" << url << " supported=" << supported;
- if (!supported && KProtocolInfo::protocolClass(url.protocol()) == ":local") {
+ if (!supported && KProtocolInfo::protocolClass(url.scheme()) == ":local") {
// Maybe we can resolve to a local URL?
KUrl localURL = KIO::NetAccess::mostLocalUrl(url, 0);
if (localURL != url) {
*it = localURL;
kDebug(7010) << "Changed to " << localURL;
}
}
}
}
return urls;
}
// Simple KDialog that resizes the given text edit after being shown to more
// or less fit the enclosed text.
class SecureMessageDialog : public KDialog
{
public:
SecureMessageDialog(QWidget *parent) : KDialog(parent), m_textEdit(0)
{
}
void setTextEdit(QPlainTextEdit *textEdit)
{
m_textEdit = textEdit;
}
protected:
virtual void showEvent(QShowEvent* e)
{
// Now that we're shown, use our width to calculate a good
// bounding box for the text, and resize m_textEdit appropriately.
KDialog::showEvent(e);
if(!m_textEdit)
return;
QSize fudge(20, 24); // About what it sounds like :-/
// Form rect with a lot of height for bounding. Use no more than
// 5 lines.
QRect curRect(m_textEdit->rect());
QFontMetrics metrics(fontMetrics());
curRect.setHeight(5 * metrics.lineSpacing());
curRect.setWidth(qMax(curRect.width(), 300)); // At least 300 pixels ok?
QString text(m_textEdit->toPlainText());
curRect = metrics.boundingRect(curRect, Qt::TextWordWrap | Qt::TextSingleLine, text);
// Scroll bars interfere. If we don't think there's enough room, enable
// the vertical scrollbar however.
m_textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
if(curRect.height() < m_textEdit->height()) { // then we've got room
m_textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_textEdit->setMaximumHeight(curRect.height() + fudge.height());
}
m_textEdit->setMinimumSize(curRect.size() + fudge);
m_textEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
updateGeometry();
}
private:
QPlainTextEdit *m_textEdit;
};
// Helper function to make the given .desktop file executable by ensuring
// that a #!/usr/bin/env xdg-open line is added if necessary and the file has
// the +x bit set for the user. Returns false if either fails.
static bool makeFileExecutable(const QString &fileName)
{
// Open the file and read the first two characters, check if it's
// #!. If not, create a new file, prepend appropriate lines, and copy
// over.
QFile desktopFile(fileName);
if (!desktopFile.open(QFile::ReadOnly)) {
kError(7010) << "Error opening service" << fileName << desktopFile.errorString();
return false;
}
QByteArray header = desktopFile.peek(2); // First two chars of file
if (header.size() == 0) {
kError(7010) << "Error inspecting service" << fileName << desktopFile.errorString();
return false; // Some kind of error
}
if (header != "#!") {
// Add header
KSaveFile saveFile;
saveFile.setFileName(fileName);
if (!saveFile.open()) {
kError(7010) << "Unable to open replacement file for" << fileName << saveFile.errorString();
return false;
}
QByteArray shebang("#!/usr/bin/env xdg-open\n");
if (saveFile.write(shebang) != shebang.size()) {
kError(7010) << "Error occurred adding header for" << fileName << saveFile.errorString();
saveFile.abort();
return false;
}
// Now copy the one into the other and then close and reopen desktopFile
QByteArray desktopData(desktopFile.readAll());
if (desktopData.isEmpty()) {
kError(7010) << "Unable to read service" << fileName << desktopFile.errorString();
saveFile.abort();
return false;
}
if (saveFile.write(desktopData) != desktopData.size()) {
kError(7010) << "Error copying service" << fileName << saveFile.errorString();
saveFile.abort();
return false;
}
desktopFile.close();
if (!saveFile.finalize()) { // Figures....
kError(7010) << "Error committing changes to service" << fileName << saveFile.errorString();
return false;
}
if (!desktopFile.open(QFile::ReadOnly)) {
kError(7010) << "Error re-opening service" << fileName << desktopFile.errorString();
return false;
}
} // Add header
// corresponds to owner on unix, which will have to do since if the user
// isn't the owner we can't change perms anyways.
if (!desktopFile.setPermissions(QFile::ExeUser | desktopFile.permissions())) {
kError(7010) << "Unable to change permissions for" << fileName << desktopFile.errorString();
return false;
}
// whew
return true;
}
// Helper function to make a .desktop file executable if prompted by the user.
// returns true if KRun::run() should continue with execution, false if user declined
// to make the file executable or we failed to make it executable.
static bool makeServiceExecutable(const KService& service, QWidget* window)
{
if (!KAuthorized::authorize("run_desktop_files")) {
kWarning() << "No authorization to execute " << service.entryPath();
KMessageBox::sorry(window, i18n("You are not authorized to execute this service."));
return false; // Don't circumvent the Kiosk
}
KGuiItem continueItem = KStandardGuiItem::cont();
SecureMessageDialog *baseDialog = new SecureMessageDialog(window);
baseDialog->setButtons(KDialog::Ok | KDialog::Cancel);
baseDialog->setButtonGuiItem(KDialog::Ok, continueItem);
baseDialog->setDefaultButton(KDialog::Cancel);
baseDialog->setButtonFocus(KDialog::Cancel);
baseDialog->setCaption(i18nc("Warning about executing unknown .desktop file", "Warning"));
// Dialog will have explanatory text with a disabled lineedit with the
// Exec= to make it visually distinct.
QWidget *baseWidget = new QWidget(baseDialog);
QHBoxLayout *mainLayout = new QHBoxLayout(baseWidget);
QLabel *iconLabel = new QLabel(baseWidget);
QPixmap warningIcon(KIconLoader::global()->loadIcon("dialog-warning", KIconLoader::NoGroup, KIconLoader::SizeHuge));
mainLayout->addWidget(iconLabel);
iconLabel->setPixmap(warningIcon);
QVBoxLayout *contentLayout = new QVBoxLayout;
QString warningMessage = i18nc("program name follows in a line edit below",
"This will start the program:");
QLabel *message = new QLabel(warningMessage, baseWidget);
contentLayout->addWidget(message);
// We can use KStandardDirs::findExe to resolve relative pathnames
// but that gets rid of the command line arguments.
QString program = KStandardDirs::realFilePath(service.exec());
QPlainTextEdit *textEdit = new QPlainTextEdit(baseWidget);
textEdit->setPlainText(program);
textEdit->setReadOnly(true);
contentLayout->addWidget(textEdit);
QLabel *footerLabel = new QLabel(i18n("If you do not trust this program, click Cancel"));
contentLayout->addWidget(footerLabel);
contentLayout->addStretch(0); // Don't allow the text edit to expand
mainLayout->addLayout(contentLayout);
baseDialog->setMainWidget(baseWidget);
baseDialog->setTextEdit(textEdit);
// Constrain maximum size. Minimum size set in
// the dialog's show event.
QSize screenSize = QApplication::desktop()->screen()->size();
baseDialog->resize(screenSize.width() / 4, 50);
baseDialog->setMaximumHeight(screenSize.height() / 3);
baseDialog->setMaximumWidth(screenSize.width() / 10 * 8);
int result = baseDialog->exec();
if (result != KDialog::Accepted) {
return false;
}
// Assume that service is an absolute path since we're being called (relative paths
// would have been allowed unless Kiosk said no, therefore we already know where the
// .desktop file is. Now add a header to it if it doesn't already have one
// and add the +x bit.
if (!::makeFileExecutable(service.entryPath())) {
QString serviceName = service.name();
if(serviceName.isEmpty())
serviceName = service.genericName();
KMessageBox::sorry(
window,
i18n("Unable to make the service %1 executable, aborting execution", serviceName)
);
return false;
}
return true;
}
bool KRun::run(const KService& _service, const KUrl::List& _urls, QWidget* window,
bool tempFiles, const QString& suggestedFileName, const QByteArray& asn)
{
if (!_service.entryPath().isEmpty() &&
!KDesktopFile::isAuthorizedDesktopFile(_service.entryPath()) &&
!::makeServiceExecutable(_service, window))
{
return false;
}
if (!tempFiles) {
// Remember we opened those urls, for the "recent documents" menu in kicker
KUrl::List::ConstIterator it = _urls.begin();
for (; it != _urls.end(); ++it) {
//kDebug(7010) << "KRecentDocument::adding " << (*it).url();
KRecentDocument::add(*it, _service.desktopEntryName());
}
}
if (tempFiles || _service.entryPath().isEmpty() || !suggestedFileName.isEmpty()) {
return runTempService(_service, _urls, window, tempFiles, suggestedFileName, asn);
}
kDebug(7010) << "KRun::run " << _service.entryPath();
if (!_urls.isEmpty()) {
kDebug(7010) << "First url " << _urls.first().url();
}
// Resolve urls if needed, depending on what the app supports
const KUrl::List urls = resolveURLs(_urls, _service);
QString error;
int pid = 0;
QByteArray myasn = asn;
// startServiceByDesktopPath() doesn't take QWidget*, add it to the startup info now
if (window != NULL) {
if (myasn.isEmpty()) {
myasn = KStartupInfo::createNewStartupId();
}
if (myasn != "0") {
KStartupInfoId id;
id.initId(myasn);
KStartupInfoData data;
data.setLaunchedBy(window->winId());
KStartupInfo::sendChange(id, data);
}
}
int i = KToolInvocation::startServiceByDesktopPath(
_service.entryPath(), urls.toStringList(), &error, 0L, &pid, myasn
);
if (i != 0) {
kDebug(7010) << error;
KMessageBox::sorry(window, error);
return false;
}
kDebug(7010) << "startServiceByDesktopPath worked fine";
return true;
}
bool KRun::run(const QString& _exec, const KUrl::List& _urls, QWidget* window, const QString& _name,
const QString& _icon, const QByteArray& asn)
{
KService::Ptr service(new KService(_name, _exec, _icon));
return run(*service, _urls, window, false, QString(), asn);
}
bool KRun::runCommand(const QString &cmd, QWidget* window)
{
return runCommand(cmd, window, QString());
}
bool KRun::runCommand(const QString& cmd, QWidget* window, const QString& workingDirectory)
{
if (cmd.isEmpty()) {
kWarning() << "Command was empty, nothing to run";
return false;
}
const QStringList args = KShell::splitArgs(cmd);
if (args.isEmpty()) {
kWarning() << "Command could not be parsed.";
return false;
}
const QString bin = args.first();
return KRun::runCommand(cmd, bin, bin /*iconName*/, window, QByteArray(), workingDirectory);
}
bool KRun::runCommand(const QString& cmd, const QString &execName, const QString & iconName, QWidget* window, const QByteArray& asn)
{
return runCommand(cmd, execName, iconName, window, asn, QString());
}
bool KRun::runCommand(const QString& cmd, const QString &execName, const QString & iconName,
QWidget* window, const QByteArray& asn, const QString& workingDirectory)
{
kDebug(7010) << "runCommand " << cmd << "," << execName;
KProcess * proc = new KProcess;
proc->setShellCommand(cmd);
if (workingDirectory.isEmpty()) {
// see bug 108510, and we need "alt+f2 editor" (which starts a desktop file via klauncher)
// and "alt+f2 editor -someoption" (which calls runCommand) to be consistent.
proc->setWorkingDirectory(KGlobalSettings::documentPath());
} else {
proc->setWorkingDirectory(workingDirectory);
}
QString bin = binaryName(execName, true);
KService::Ptr service = KService::serviceByDesktopName(bin);
return runCommandInternal(proc, service.data(),
execName /*executable to check for in slotProcessExited*/,
execName /*user-visible name*/,
iconName, window, asn);
}
KRun::KRun(const KUrl& url, QWidget* window, mode_t mode, bool isLocalFile,
bool showProgressInfo, const QByteArray& asn)
: d(new KRunPrivate(this))
{
d->m_timer.setObjectName("KRun::timer");
d->m_timer.setSingleShot(true);
d->init(url, window, mode, isLocalFile, showProgressInfo, asn);
}
void KRun::KRunPrivate::init(const KUrl& url, QWidget* window, mode_t mode, bool isLocalFile,
bool showProgressInfo, const QByteArray& asn)
{
m_bFault = false;
m_bAutoDelete = true;
m_bProgressInfo = showProgressInfo;
m_bFinished = false;
m_job = 0L;
m_strURL = url;
m_bScanFile = false;
m_bIsDirectory = false;
m_bIsLocalFile = isLocalFile;
m_mode = mode;
m_runExecutables = true;
m_window = window;
m_asn = asn;
q->setEnableExternalBrowser(true);
// Start the timer. This means we will return to the event
// loop and do initialization afterwards.
// Reason: We must complete the constructor before we do anything else.
m_bInit = true;
q->connect(&m_timer, SIGNAL(timeout()), q, SLOT(slotTimeout()));
startTimer();
//kDebug(7010) << "new KRun" << q << url << "timer=" << &m_timer;
KGlobal::ref();
}
void KRun::init()
{
kDebug(7010) << "INIT called";
if (!d->m_strURL.isValid()) {
// TODO KDE5: call virtual method on error (see BrowserRun::init)
d->m_showingDialog = true;
KMessageBoxWrapper::error(d->m_window, i18n("Malformed URL\n%1", d->m_strURL.url()));
d->m_showingDialog = false;
d->m_bFault = true;
d->m_bFinished = true;
d->startTimer();
return;
}
if (!KAuthorized::authorizeUrlAction("open", KUrl(), d->m_strURL)) {
QString msg = KIO::buildErrorString(KIO::ERR_ACCESS_DENIED, d->m_strURL.prettyUrl());
d->m_showingDialog = true;
KMessageBoxWrapper::error(d->m_window, msg);
d->m_showingDialog = false;
d->m_bFault = true;
d->m_bFinished = true;
d->startTimer();
return;
}
if (!d->m_bIsLocalFile && d->m_strURL.isLocalFile()) {
d->m_bIsLocalFile = true;
}
- if (!d->m_externalBrowser.isEmpty() && d->m_strURL.protocol().startsWith(QLatin1String("http"))) {
+ if (!d->m_externalBrowser.isEmpty() && d->m_strURL.scheme().startsWith(QLatin1String("http"))) {
if (d->runExecutable(d->m_externalBrowser)) {
return;
}
} else if (d->m_bIsLocalFile) {
if (d->m_mode == 0) {
KDE_struct_stat buff;
if (KDE::stat(d->m_strURL.path(), &buff) == -1) {
d->m_showingDialog = true;
KMessageBoxWrapper::error(d->m_window,
i18n("<qt>Unable to run the command specified. "
"The file or folder <b>%1</b> does not exist.</qt>" ,
Qt::escape(d->m_strURL.prettyUrl())));
d->m_showingDialog = false;
d->m_bFault = true;
d->m_bFinished = true;
d->startTimer();
return;
}
d->m_mode = buff.st_mode;
}
KMimeType::Ptr mime = KMimeType::findByUrl(d->m_strURL, d->m_mode, d->m_bIsLocalFile);
assert(mime);
kDebug(7010) << "MIME TYPE is " << mime->name();
if (!d->m_externalBrowser.isEmpty() && (
mime->is(QLatin1String("text/html")) ||
mime->is(QLatin1String("application/xml")))) {
if (d->runExecutable(d->m_externalBrowser)) {
return;
}
} else {
mimeTypeDetermined(mime->name());
return;
}
}
else if (KProtocolInfo::isHelperProtocol(d->m_strURL)) {
kDebug(7010) << "Helper protocol";
- const QString exec = KProtocolInfo::exec(d->m_strURL.protocol());
+ const QString exec = KProtocolInfo::exec(d->m_strURL.scheme());
if (exec.isEmpty()) {
mimeTypeDetermined(KProtocolManager::defaultMimetype(d->m_strURL));
return;
} else {
if (run(exec, KUrl::List() << d->m_strURL, d->m_window, QString(), QString(), d->m_asn)) {
d->m_bFinished = true;
d->startTimer();
return;
}
}
}
// Did we already get the information that it is a directory ?
if (S_ISDIR(d->m_mode)) {
mimeTypeDetermined("inode/directory");
return;
}
// Let's see whether it is a directory
if (!KProtocolManager::supportsListing(d->m_strURL)) {
//kDebug(7010) << "Protocol has no support for listing";
// No support for listing => it can't be a directory (example: http)
scanFile();
return;
}
kDebug(7010) << "Testing directory (stating)";
// It may be a directory or a file, let's stat
KIO::JobFlags flags = d->m_bProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo;
KIO::StatJob *job = KIO::stat(d->m_strURL, KIO::StatJob::SourceSide, 0 /* no details */, flags);
job->ui()->setWindow(d->m_window);
connect(job, SIGNAL(result(KJob *)),
this, SLOT(slotStatResult(KJob *)));
d->m_job = job;
kDebug(7010) << " Job " << job << " is about stating " << d->m_strURL.url();
}
KRun::~KRun()
{
//kDebug(7010) << this;
d->m_timer.stop();
killJob();
KGlobal::deref();
//kDebug(7010) << this << "done";
delete d;
}
bool KRun::KRunPrivate::runExecutable(const QString& _exec)
{
KUrl::List urls;
urls.append(m_strURL);
if (_exec.startsWith('!')) {
QString exec = _exec.mid(1); // Literal command
exec += " %u";
if (q->run(exec, urls, m_window, QString(), QString(), m_asn)) {
m_bFinished = true;
startTimer();
return true;
}
}
else {
KService::Ptr service = KService::serviceByStorageId(_exec);
if (service && q->run(*service, urls, m_window, false, QString(), m_asn)) {
m_bFinished = true;
startTimer();
return true;
}
}
return false;
}
void KRun::scanFile()
{
kDebug(7010) << d->m_strURL;
// First, let's check for well-known extensions
// Not when there is a query in the URL, in any case.
if (d->m_strURL.query().isEmpty()) {
KMimeType::Ptr mime = KMimeType::findByUrl(d->m_strURL);
assert(mime);
if (!mime->isDefault() || d->m_bIsLocalFile) {
kDebug(7010) << "Scanfile: MIME TYPE is " << mime->name();
mimeTypeDetermined(mime->name());
return;
}
}
// No mimetype found, and the URL is not local (or fast mode not allowed).
// We need to apply the 'KIO' method, i.e. either asking the server or
// getting some data out of the file, to know what mimetype it is.
if (!KProtocolManager::supportsReading(d->m_strURL)) {
kError(7010) << "#### NO SUPPORT FOR READING!";
d->m_bFault = true;
d->m_bFinished = true;
d->startTimer();
return;
}
kDebug(7010) << this << " Scanning file " << d->m_strURL.url();
KIO::JobFlags flags = d->m_bProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo;
KIO::TransferJob *job = KIO::get(d->m_strURL, KIO::NoReload /*reload*/, flags);
job->ui()->setWindow(d->m_window);
connect(job, SIGNAL(result(KJob *)),
this, SLOT(slotScanFinished(KJob *)));
connect(job, SIGNAL(mimetype(KIO::Job *, const QString &)),
this, SLOT(slotScanMimeType(KIO::Job *, const QString &)));
d->m_job = job;
kDebug(7010) << " Job " << job << " is about getting from " << d->m_strURL.url();
}
// When arriving in that method there are 5 possible states:
// must_init, must_scan_file, found_dir, done+error or done+success.
void KRun::slotTimeout()
{
kDebug(7010) << this << " slotTimeout called";
if (d->m_bInit) {
d->m_bInit = false;
init();
return;
}
if (d->m_bFault) {
emit error();
}
if (d->m_bFinished) {
emit finished();
}
else {
if (d->m_bScanFile) {
d->m_bScanFile = false;
scanFile();
return;
}
else if (d->m_bIsDirectory) {
d->m_bIsDirectory = false;
mimeTypeDetermined("inode/directory");
return;
}
}
if (d->m_bAutoDelete) {
deleteLater();
return;
}
}
void KRun::slotStatResult(KJob * job)
{
d->m_job = 0L;
const int errCode = job->error();
if (errCode) {
// ERR_NO_CONTENT is not an error, but an indication no further
// actions needs to be taken.
if (errCode != KIO::ERR_NO_CONTENT) {
d->m_showingDialog = true;
kError(7010) << this << "ERROR" << job->error() << job->errorString();
job->uiDelegate()->showErrorMessage();
//kDebug(7010) << this << " KRun returning from showErrorDialog, starting timer to delete us";
d->m_showingDialog = false;
d->m_bFault = true;
}
d->m_bFinished = true;
// will emit the error and autodelete this
d->startTimer();
}
else {
kDebug(7010) << "Finished";
KIO::StatJob* statJob = qobject_cast<KIO::StatJob*>(job);
if (!statJob) {
kFatal() << "job is a " << typeid(*job).name() << " should be a StatJob";
}
// Update our URL in case of a redirection
setUrl(statJob->url());
const KIO::UDSEntry entry = statJob->statResult();
const mode_t mode = entry.numberValue(KIO::UDSEntry::UDS_FILE_TYPE);
if (S_ISDIR(mode)) {
d->m_bIsDirectory = true; // it's a dir
}
else {
d->m_bScanFile = true; // it's a file
}
d->m_localPath = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH);
// mimetype already known? (e.g. print:/manager)
const QString knownMimeType = entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE) ;
if (!knownMimeType.isEmpty()) {
mimeTypeDetermined(knownMimeType);
d->m_bFinished = true;
}
// We should have found something
assert(d->m_bScanFile || d->m_bIsDirectory);
// Start the timer. Once we get the timer event this
// protocol server is back in the pool and we can reuse it.
// This gives better performance than starting a new slave
d->startTimer();
}
}
void KRun::slotScanMimeType(KIO::Job *, const QString &mimetype)
{
if (mimetype.isEmpty()) {
- kWarning(7010) << "get() didn't emit a mimetype! Probably a kioslave bug, please check the implementation of" << url().protocol();
+ kWarning(7010) << "get() didn't emit a mimetype! Probably a kioslave bug, please check the implementation of" << url().scheme();
}
mimeTypeDetermined(mimetype);
d->m_job = 0;
}
void KRun::slotScanFinished(KJob *job)
{
d->m_job = 0;
const int errCode = job->error();
if (errCode) {
// ERR_NO_CONTENT is not an error, but an indication no further
// actions needs to be taken.
if (errCode != KIO::ERR_NO_CONTENT) {
d->m_showingDialog = true;
kError(7010) << this << "ERROR (stat):" << job->error() << ' ' << job->errorString();
job->uiDelegate()->showErrorMessage();
//kDebug(7010) << this << " KRun returning from showErrorDialog, starting timer to delete us";
d->m_showingDialog = false;
d->m_bFault = true;
}
d->m_bFinished = true;
// will emit the error and autodelete this
d->startTimer();
}
}
void KRun::mimeTypeDetermined(const QString& mimeType)
{
// foundMimeType reimplementations might show a dialog box;
// make sure some timer doesn't kill us meanwhile (#137678, #156447)
Q_ASSERT(!d->m_showingDialog);
d->m_showingDialog = true;
foundMimeType(mimeType);
d->m_showingDialog = false;
// We cannot assume that we're finished here. Some reimplementations
// start a KIO job and call setFinished only later.
}
void KRun::foundMimeType(const QString& type)
{
kDebug(7010) << "Resulting mime type is " << type;
KIO::TransferJob *job = qobject_cast<KIO::TransferJob *>(d->m_job);
if (job) {
// Update our URL in case of a redirection
setUrl( job->url() );
job->putOnHold();
KIO::Scheduler::publishSlaveOnHold();
d->m_job = 0;
}
Q_ASSERT(!d->m_bFinished);
// Support for preferred service setting, see setPreferredService
if (!d->m_preferredService.isEmpty()) {
kDebug(7010) << "Attempting to open with preferred service: " << d->m_preferredService;
KService::Ptr serv = KService::serviceByDesktopName(d->m_preferredService);
if (serv && serv->hasMimeType(type)) {
KUrl::List lst;
lst.append(d->m_strURL);
if (KRun::run(*serv, lst, d->m_window, false, QString(), d->m_asn)) {
setFinished(true);
return;
}
/// Note: if that service failed, we'll go to runUrl below to
/// maybe find another service, even though an error dialog box was
/// already displayed. That's good if runUrl tries another service,
/// but it's not good if it tries the same one :}
}
}
// Resolve .desktop files from media:/, remote:/, applications:/ etc.
KMimeType::Ptr mime = KMimeType::mimeType(type, KMimeType::ResolveAliases);
if (!mime) {
kWarning(7010) << "Unknown mimetype " << type;
}
if (mime && mime->is("application/x-desktop") && !d->m_localPath.isEmpty()) {
d->m_strURL = KUrl();
d->m_strURL.setPath(d->m_localPath);
}
if (!KRun::runUrl(d->m_strURL, type, d->m_window, false /*tempfile*/, d->m_runExecutables, d->m_suggestedFileName, d->m_asn)) {
d->m_bFault = true;
}
setFinished(true);
}
void KRun::killJob()
{
if (d->m_job) {
kDebug(7010) << this << "m_job=" << d->m_job;
d->m_job->kill();
d->m_job = 0L;
}
}
void KRun::abort()
{
if (d->m_bFinished) {
return;
}
kDebug(7010) << this << "m_showingDialog=" << d->m_showingDialog;
killJob();
// If we're showing an error message box, the rest will be done
// after closing the msgbox -> don't autodelete nor emit signals now.
if (d->m_showingDialog) {
return;
}
d->m_bFault = true;
d->m_bFinished = true;
d->m_bInit = false;
d->m_bScanFile = false;
// will emit the error and autodelete this
d->startTimer();
}
bool KRun::hasError() const
{
return d->m_bFault;
}
bool KRun::hasFinished() const
{
return d->m_bFinished;
}
bool KRun::autoDelete() const
{
return d->m_bAutoDelete;
}
void KRun::setAutoDelete(bool b)
{
d->m_bAutoDelete = b;
}
void KRun::setEnableExternalBrowser(bool b)
{
if (b) {
d->m_externalBrowser = KConfigGroup(KGlobal::config(), "General").readEntry("BrowserApplication");
}
else {
d->m_externalBrowser.clear();
}
}
void KRun::setPreferredService(const QString& desktopEntryName)
{
d->m_preferredService = desktopEntryName;
}
void KRun::setRunExecutables(bool b)
{
d->m_runExecutables = b;
}
void KRun::setSuggestedFileName(const QString& fileName)
{
d->m_suggestedFileName = fileName;
}
QString KRun::suggestedFileName() const
{
return d->m_suggestedFileName;
}
bool KRun::isExecutable(const QString& serviceType)
{
return (serviceType == "application/x-desktop" ||
serviceType == "application/x-executable" ||
serviceType == "application/x-ms-dos-executable" ||
serviceType == "application/x-shellscript");
}
void KRun::setUrl(const KUrl &url)
{
d->m_strURL = url;
}
KUrl KRun::url() const
{
return d->m_strURL;
}
void KRun::setError(bool error)
{
d->m_bFault = error;
}
void KRun::setProgressInfo(bool progressInfo)
{
d->m_bProgressInfo = progressInfo;
}
bool KRun::progressInfo() const
{
return d->m_bProgressInfo;
}
void KRun::setFinished(bool finished)
{
d->m_bFinished = finished;
if (finished)
d->startTimer();
}
void KRun::setJob(KIO::Job *job)
{
d->m_job = job;
}
KIO::Job* KRun::job()
{
return d->m_job;
}
#ifndef KDE_NO_DEPRECATED
QTimer& KRun::timer()
{
return d->m_timer;
}
#endif
#ifndef KDE_NO_DEPRECATED
void KRun::setDoScanFile(bool scanFile)
{
d->m_bScanFile = scanFile;
}
#endif
#ifndef KDE_NO_DEPRECATED
bool KRun::doScanFile() const
{
return d->m_bScanFile;
}
#endif
#ifndef KDE_NO_DEPRECATED
void KRun::setIsDirecory(bool isDirectory)
{
d->m_bIsDirectory = isDirectory;
}
#endif
bool KRun::isDirectory() const
{
return d->m_bIsDirectory;
}
#ifndef KDE_NO_DEPRECATED
void KRun::setInitializeNextAction(bool initialize)
{
d->m_bInit = initialize;
}
#endif
#ifndef KDE_NO_DEPRECATED
bool KRun::initializeNextAction() const
{
return d->m_bInit;
}
#endif
void KRun::setIsLocalFile(bool isLocalFile)
{
d->m_bIsLocalFile = isLocalFile;
}
bool KRun::isLocalFile() const
{
return d->m_bIsLocalFile;
}
void KRun::setMode(mode_t mode)
{
d->m_mode = mode;
}
mode_t KRun::mode() const
{
return d->m_mode;
}
/****************/
#ifndef Q_WS_X11
int KProcessRunner::run(KProcess * p, const QString & executable)
{
return (new KProcessRunner(p, executable))->pid();
}
#else
int KProcessRunner::run(KProcess * p, const QString & executable, const KStartupInfoId& id)
{
return (new KProcessRunner(p, executable, id))->pid();
}
#endif
#ifndef Q_WS_X11
KProcessRunner::KProcessRunner(KProcess * p, const QString & executable)
#else
KProcessRunner::KProcessRunner(KProcess * p, const QString & executable, const KStartupInfoId& _id) :
id(_id)
#endif
{
m_pid = 0;
process = p;
m_executable = executable;
connect(process, SIGNAL(finished(int, QProcess::ExitStatus)),
this, SLOT(slotProcessExited(int, QProcess::ExitStatus)));
process->start();
if (!process->waitForStarted()) {
//kDebug() << "wait for started failed, exitCode=" << process->exitCode()
// << "exitStatus=" << process->exitStatus();
// Note that exitCode is 255 here (the first time), and 0 later on (bug?).
slotProcessExited(255, process->exitStatus());
}
else {
#ifdef Q_WS_X11
m_pid = process->pid();
#endif
}
}
KProcessRunner::~KProcessRunner()
{
delete process;
}
int KProcessRunner::pid() const
{
return m_pid;
}
void KProcessRunner::terminateStartupNotification()
{
#ifdef Q_WS_X11
if (!id.none()) {
KStartupInfoData data;
data.addPid(m_pid); // announce this pid for the startup notification has finished
data.setHostname();
KStartupInfo::sendFinish(id, data);
}
#endif
}
void
KProcessRunner::slotProcessExited(int exitCode, QProcess::ExitStatus exitStatus)
{
kDebug(7010) << m_executable << "exitCode=" << exitCode << "exitStatus=" << exitStatus;
Q_UNUSED(exitStatus);
terminateStartupNotification(); // do this before the messagebox
if (exitCode != 0 && !m_executable.isEmpty()) {
// Let's see if the error is because the exe doesn't exist.
// When this happens, waitForStarted returns false, but not if kioexec
// was involved, then we come here, that's why the code is here.
//
// We'll try to find the executable relatively to current directory,
// (or with a full path, if m_executable is absolute), and then in the PATH.
if (!QFile(m_executable).exists() && KStandardDirs::findExe(m_executable).isEmpty()) {
KGlobal::ref();
KMessageBox::sorry(0L, i18n("Could not find the program '%1'", m_executable));
KGlobal::deref();
}
else {
kDebug() << process->readAllStandardError();
}
}
deleteLater();
}
#include "moc_krun.cpp"
#include "moc_krun_p.cpp"
diff --git a/kio/kio/kurifilter.cpp b/kio/kio/kurifilter.cpp
index 26c19b582d..ac91df56a3 100644
--- a/kio/kio/kurifilter.cpp
+++ b/kio/kio/kurifilter.cpp
@@ -1,713 +1,713 @@
/* This file is part of the KDE libraries
* Copyright (C) 2000 Yves Arrouye <yves@realnames.com>
* Copyright (C) 2000,2010 Dawit Alemayehu <adawit at 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 "kurifilter.h"
#include "hostinfo_p.h"
#include <kdebug.h>
#include <kiconloader.h>
#include <kservicetypetrader.h>
#include <kmimetype.h>
#include <kstandarddirs.h>
#include <QtCore/QHashIterator>
#include <QtCore/QStringBuilder>
#include <QtNetwork/QHostInfo>
#include <QtNetwork/QHostAddress>
typedef QList<KUriFilterPlugin *> KUriFilterPluginList;
typedef QMap<QString, KUriFilterSearchProvider*> SearchProviderMap;
static QString lookupIconNameFor(const KUrl &url, KUriFilterData::UriTypes type)
{
QString iconName;
switch ( type )
{
case KUriFilterData::NetProtocol:
iconName = KMimeType::favIconForUrl(url);
if (iconName.isEmpty())
iconName = KMimeType::iconNameForUrl( url );
else
iconName = KStandardDirs::locate("cache", iconName + QLatin1String(".png"));
break;
case KUriFilterData::LocalFile:
case KUriFilterData::LocalDir:
{
iconName = KMimeType::iconNameForUrl( url );
break;
}
case KUriFilterData::Executable:
{
QString exeName = url.path();
exeName = exeName.mid( exeName.lastIndexOf( '/' ) + 1 ); // strip path if given
KService::Ptr service = KService::serviceByDesktopName( exeName );
if (service && service->icon() != QLatin1String( "unknown" ))
iconName = service->icon();
// Try to find an icon with the same name as the binary (useful for non-kde apps)
// Use iconPath rather than loadIcon() as the latter uses QPixmap (not threadsafe)
else if ( !KIconLoader::global()->iconPath( exeName, KIconLoader::NoGroup, true ).isNull() )
iconName = exeName;
else
// not found, use default
iconName = QLatin1String("system-run");
break;
}
case KUriFilterData::Help:
{
iconName = QLatin1String("khelpcenter");
break;
}
case KUriFilterData::Shell:
{
iconName = QLatin1String("konsole");
break;
}
case KUriFilterData::Error:
case KUriFilterData::Blocked:
{
iconName = QLatin1String("error");
break;
}
default:
break;
}
return iconName;
}
class KUriFilterSearchProvider::KUriFilterSearchProviderPrivate
{
public:
KUriFilterSearchProviderPrivate() {}
KUriFilterSearchProviderPrivate(const KUriFilterSearchProviderPrivate& other)
: desktopEntryName(other.desktopEntryName),
iconName(other.iconName),
name(other.name),
keys(other.keys) {}
QString desktopEntryName;
QString iconName;
QString name;
QStringList keys;
};
KUriFilterSearchProvider::KUriFilterSearchProvider()
:d(new KUriFilterSearchProvider::KUriFilterSearchProviderPrivate)
{
}
KUriFilterSearchProvider::KUriFilterSearchProvider(const KUriFilterSearchProvider& other)
:d(new KUriFilterSearchProvider::KUriFilterSearchProviderPrivate(*(other.d)))
{
}
KUriFilterSearchProvider::~KUriFilterSearchProvider()
{
delete d;
}
QString KUriFilterSearchProvider::desktopEntryName() const
{
return d->desktopEntryName;
}
QString KUriFilterSearchProvider::iconName() const
{
return d->iconName;
}
QString KUriFilterSearchProvider::name() const
{
return d->name;
}
QStringList KUriFilterSearchProvider::keys() const
{
return d->keys;
}
QString KUriFilterSearchProvider::defaultKey() const
{
if (d->keys.isEmpty())
return QString();
return d->keys.first();
}
KUriFilterSearchProvider& KUriFilterSearchProvider::operator=(const KUriFilterSearchProvider& other)
{
d->desktopEntryName = other.d->desktopEntryName;
d->iconName = other.d->iconName;
d->keys = other.d->keys;
d->name = other.d->name;
return *this;
}
void KUriFilterSearchProvider::setDesktopEntryName(const QString& desktopEntryName)
{
d->desktopEntryName = desktopEntryName;
}
void KUriFilterSearchProvider::setIconName(const QString& iconName)
{
d->iconName = iconName;
}
void KUriFilterSearchProvider::setName(const QString& name)
{
d->name = name;
}
void KUriFilterSearchProvider::setKeys(const QStringList& keys)
{
d->keys = keys;
}
class KUriFilterDataPrivate
{
public:
explicit KUriFilterDataPrivate( const KUrl& u, const QString& typedUrl )
: checkForExecs(true),
wasModified(true),
uriType(KUriFilterData::Unknown),
searchFilterOptions(KUriFilterData::SearchFilterOptionNone),
url(u),
typedString(typedUrl)
{
}
~KUriFilterDataPrivate()
{
qDeleteAll(searchProviderMap.begin(), searchProviderMap.end());
}
void setData( const KUrl& u, const QString& typedUrl )
{
checkForExecs = true;
wasModified = true;
uriType = KUriFilterData::Unknown;
searchFilterOptions = KUriFilterData::SearchFilterOptionNone;
url = u;
typedString = typedUrl;
errMsg.clear();
iconName.clear();
absPath.clear();
args.clear();
searchTerm.clear();
searchProvider.clear();
searchTermSeparator = QChar();
alternateDefaultSearchProvider.clear();
alternateSearchProviders.clear();
searchProviderMap.clear();
defaultUrlScheme.clear();
}
KUriFilterDataPrivate( KUriFilterDataPrivate * data )
{
wasModified = data->wasModified;
checkForExecs = data->checkForExecs;
uriType = data->uriType;
searchFilterOptions = data->searchFilterOptions;
url = data->url;
typedString = data->typedString;
errMsg = data->errMsg;
iconName = data->iconName;
absPath = data->absPath;
args = data->args;
searchTerm = data->searchTerm;
searchTermSeparator = data->searchTermSeparator;
searchProvider = data->searchProvider;
alternateDefaultSearchProvider = data->alternateDefaultSearchProvider;
alternateSearchProviders = data->alternateSearchProviders;
searchProviderMap = data->searchProviderMap;
defaultUrlScheme = data->defaultUrlScheme;
}
bool checkForExecs;
bool wasModified;
KUriFilterData::UriTypes uriType;
KUriFilterData::SearchFilterOptions searchFilterOptions;
KUrl url;
QString typedString;
QString errMsg;
QString iconName;
QString absPath;
QString args;
QString searchTerm;
QString searchProvider;
QString alternateDefaultSearchProvider;
QString defaultUrlScheme;
QChar searchTermSeparator;
QStringList alternateSearchProviders;
QStringList searchProviderList;
SearchProviderMap searchProviderMap;
};
KUriFilterData::KUriFilterData()
:d( new KUriFilterDataPrivate( KUrl(), QString() ) )
{
}
KUriFilterData::KUriFilterData( const KUrl& url )
:d( new KUriFilterDataPrivate( url, url.url() ) )
{
}
KUriFilterData::KUriFilterData( const QString& url )
:d( new KUriFilterDataPrivate( KUrl(url), url ) )
{
}
KUriFilterData::KUriFilterData( const KUriFilterData& other )
:d( new KUriFilterDataPrivate( other.d ) )
{
}
KUriFilterData::~KUriFilterData()
{
delete d;
}
KUrl KUriFilterData::uri() const
{
return d->url;
}
QString KUriFilterData::errorMsg() const
{
return d->errMsg;
}
KUriFilterData::UriTypes KUriFilterData::uriType() const
{
return d->uriType;
}
QString KUriFilterData::absolutePath() const
{
return d->absPath;
}
bool KUriFilterData::hasAbsolutePath() const
{
return !d->absPath.isEmpty();
}
QString KUriFilterData::argsAndOptions() const
{
return d->args;
}
bool KUriFilterData::hasArgsAndOptions() const
{
return !d->args.isEmpty();
}
bool KUriFilterData::checkForExecutables() const
{
return d->checkForExecs;
}
QString KUriFilterData::typedString() const
{
return d->typedString;
}
QString KUriFilterData::searchTerm() const
{
return d->searchTerm;
}
QChar KUriFilterData::searchTermSeparator() const
{
return d->searchTermSeparator;
}
QString KUriFilterData::searchProvider() const
{
return d->searchProvider;
}
QStringList KUriFilterData::preferredSearchProviders() const
{
return d->searchProviderList;
}
KUriFilterSearchProvider KUriFilterData::queryForSearchProvider(const QString& provider) const
{
const KUriFilterSearchProvider* searchProvider = d->searchProviderMap.value(provider);
if (searchProvider)
return *(searchProvider);
return KUriFilterSearchProvider();
}
QString KUriFilterData::queryForPreferredSearchProvider(const QString& provider) const
{
const KUriFilterSearchProvider* searchProvider = d->searchProviderMap.value(provider);
if (searchProvider)
return (searchProvider->defaultKey() % searchTermSeparator() % searchTerm());
return QString();
}
QStringList KUriFilterData::allQueriesForSearchProvider(const QString& provider) const
{
const KUriFilterSearchProvider* searchProvider = d->searchProviderMap.value(provider);
if (searchProvider)
return searchProvider->keys();
return QStringList();
}
QString KUriFilterData::iconNameForPreferredSearchProvider(const QString &provider) const
{
const KUriFilterSearchProvider* searchProvider = d->searchProviderMap.value(provider);
if (searchProvider)
return searchProvider->iconName();
return QString();
}
QStringList KUriFilterData::alternateSearchProviders() const
{
return d->alternateSearchProviders;
}
QString KUriFilterData::alternateDefaultSearchProvider() const
{
return d->alternateDefaultSearchProvider;
}
QString KUriFilterData::defaultUrlScheme() const
{
return d->defaultUrlScheme;
}
KUriFilterData::SearchFilterOptions KUriFilterData::searchFilteringOptions() const
{
return d->searchFilterOptions;
}
QString KUriFilterData::iconName()
{
if (d->wasModified) {
d->iconName = lookupIconNameFor(d->url, d->uriType);
d->wasModified = false;
}
return d->iconName;
}
void KUriFilterData::setData( const KUrl& url )
{
d->setData(url, url.url());
}
void KUriFilterData::setData( const QString& url )
{
d->setData(KUrl(url), url);
}
bool KUriFilterData::setAbsolutePath( const QString& absPath )
{
// Since a malformed URL could possibly be a relative
// URL we tag it as a possible local resource...
- if( (d->url.protocol().isEmpty() || d->url.isLocalFile()) )
+ if( (d->url.scheme().isEmpty() || d->url.isLocalFile()) )
{
d->absPath = absPath;
return true;
}
return false;
}
void KUriFilterData::setCheckForExecutables( bool check )
{
d->checkForExecs = check;
}
void KUriFilterData::setAlternateSearchProviders(const QStringList &providers)
{
d->alternateSearchProviders = providers;
}
void KUriFilterData::setAlternateDefaultSearchProvider(const QString &provider)
{
d->alternateDefaultSearchProvider = provider;
}
void KUriFilterData::setDefaultUrlScheme(const QString& scheme)
{
d->defaultUrlScheme = scheme;
}
void KUriFilterData::setSearchFilteringOptions(SearchFilterOptions options)
{
d->searchFilterOptions = options;
}
KUriFilterData& KUriFilterData::operator=( const KUrl& url )
{
d->setData(url, url.url());
return *this;
}
KUriFilterData& KUriFilterData::operator=( const QString& url )
{
d->setData(KUrl(url), url);
return *this;
}
/************************* KUriFilterPlugin ******************************/
KUriFilterPlugin::KUriFilterPlugin( const QString & name, QObject *parent )
:QObject( parent ), d( 0 )
{
setObjectName( name );
}
KCModule *KUriFilterPlugin::configModule( QWidget*, const char* ) const
{
return 0;
}
QString KUriFilterPlugin::configName() const
{
return objectName();
}
void KUriFilterPlugin::setFilteredUri( KUriFilterData& data, const KUrl& uri ) const
{
data.d->url = uri;
data.d->wasModified = true;
kDebug(7022) << "Got filtered to:" << uri;
}
void KUriFilterPlugin::setErrorMsg ( KUriFilterData& data,
const QString& errmsg ) const
{
data.d->errMsg = errmsg;
}
void KUriFilterPlugin::setUriType ( KUriFilterData& data,
KUriFilterData::UriTypes type) const
{
data.d->uriType = type;
data.d->wasModified = true;
}
void KUriFilterPlugin::setArguments( KUriFilterData& data,
const QString& args ) const
{
data.d->args = args;
}
void KUriFilterPlugin::setSearchProvider( KUriFilterData &data, const QString& provider,
const QString &term, const QChar &separator) const
{
data.d->searchProvider = provider;
data.d->searchTerm = term;
data.d->searchTermSeparator = separator;
}
#ifndef KDE_NO_DEPRECATED
void KUriFilterPlugin::setPreferredSearchProviders(KUriFilterData &data, const ProviderInfoList &providers) const
{
QHashIterator<QString, QPair<QString, QString> > it (providers);
while (it.hasNext())
{
it.next();
KUriFilterSearchProvider* searchProvider = data.d->searchProviderMap[it.key()];
searchProvider->setName(it.key());
searchProvider->setIconName(it.value().second);
QStringList keys;
const QStringList queries = it.value().first.split(QLatin1Char(','));
Q_FOREACH(const QString& query, queries)
keys << query.left(query.indexOf(data.d->searchTermSeparator));
searchProvider->setKeys(keys);
}
}
#endif
void KUriFilterPlugin::setSearchProviders(KUriFilterData &data, const QList<KUriFilterSearchProvider*>& providers) const
{
Q_FOREACH(KUriFilterSearchProvider* searchProvider, providers) {
data.d->searchProviderList << searchProvider->name();
data.d->searchProviderMap.insert(searchProvider->name(), searchProvider);
}
}
QString KUriFilterPlugin::iconNameFor(const KUrl& url, KUriFilterData::UriTypes type) const
{
return lookupIconNameFor(url, type);
}
QHostInfo KUriFilterPlugin::resolveName(const QString& hostname, unsigned long timeout) const
{
return KIO::HostInfo::lookupHost(hostname, timeout);
}
/******************************* KUriFilter ******************************/
class KUriFilterPrivate
{
public:
KUriFilterPrivate() {}
~KUriFilterPrivate()
{
qDeleteAll(plugins);
plugins.clear();
}
QHash<QString, KUriFilterPlugin *> plugins;
// NOTE: DO NOT REMOVE this variable! Read the
// comments in KUriFilter::loadPlugins to understand why...
QStringList pluginNames;
};
KUriFilter *KUriFilter::self()
{
K_GLOBAL_STATIC(KUriFilter, m_self)
return m_self;
}
KUriFilter::KUriFilter()
: d(new KUriFilterPrivate())
{
loadPlugins();
}
KUriFilter::~KUriFilter()
{
delete d;
}
bool KUriFilter::filterUri( KUriFilterData& data, const QStringList& filters )
{
bool filtered = false;
// If no specific filters were requested, iterate through all the plugins.
// Otherwise, only use available filters.
if( filters.isEmpty() ) {
QStringListIterator it (d->pluginNames);
while (it.hasNext()) {
KUriFilterPlugin* plugin = d->plugins.value(it.next());
if (plugin && plugin->filterUri( data ))
filtered = true;
}
} else {
QStringListIterator it (filters);
while (it.hasNext()) {
KUriFilterPlugin* plugin = d->plugins.value(it.next());
if (plugin && plugin->filterUri( data ))
filtered = true;
}
}
return filtered;
}
bool KUriFilter::filterUri( KUrl& uri, const QStringList& filters )
{
KUriFilterData data(uri);
bool filtered = filterUri( data, filters );
if( filtered ) uri = data.uri();
return filtered;
}
bool KUriFilter::filterUri( QString& uri, const QStringList& filters )
{
KUriFilterData data(uri);
bool filtered = filterUri( data, filters );
if( filtered ) uri = data.uri().url();
return filtered;
}
KUrl KUriFilter::filteredUri( const KUrl &uri, const QStringList& filters )
{
KUriFilterData data(uri);
filterUri( data, filters );
return data.uri();
}
QString KUriFilter::filteredUri( const QString &uri, const QStringList& filters )
{
KUriFilterData data(uri);
filterUri( data, filters );
return data.uri().url();
}
#ifndef KDE_NO_DEPRECATED
bool KUriFilter::filterSearchUri(KUriFilterData &data)
{
return filterSearchUri(data, (NormalTextFilter | WebShortcutFilter));
}
#endif
bool KUriFilter::filterSearchUri(KUriFilterData &data, SearchFilterTypes types)
{
QStringList filters;
if (types & WebShortcutFilter)
filters << "kurisearchfilter";
if (types & NormalTextFilter)
filters << "kuriikwsfilter";
return filterUri(data, filters);
}
QStringList KUriFilter::pluginNames() const
{
return d->pluginNames;
}
void KUriFilter::loadPlugins()
{
const KService::List offers = KServiceTypeTrader::self()->query( "KUriFilter/Plugin" );
// NOTE: Plugin priority is determined by the InitialPreference entry in
// the .desktop files, so the trader result is already sorted and should
// not be manually sorted.
Q_FOREACH (const KService::Ptr &ptr, offers) {
KUriFilterPlugin *plugin = ptr->createInstance<KUriFilterPlugin>();
if (plugin) {
const QString& pluginName = plugin->objectName();
Q_ASSERT( !pluginName.isEmpty() );
d->plugins.insert(pluginName, plugin );
// Needed to ensure the order of filtering is honored since
// items are ordered arbitarily in a QHash and QMap always
// sorts by keys. Both undesired behavior.
d->pluginNames << pluginName;
}
}
}
diff --git a/kio/kio/kurlcompletion.cpp b/kio/kio/kurlcompletion.cpp
index 17bcbd6aaf..66a010acf1 100644
--- a/kio/kio/kurlcompletion.cpp
+++ b/kio/kio/kurlcompletion.cpp
@@ -1,1500 +1,1500 @@
/* -*- indent-tabs-mode: t; tab-width: 4; c-basic-offset:4 -*-
This file is part of the KDE libraries
Copyright (C) 2000 David Smith <dsmith@algonet.se>
Copyright (C) 2004 Scott Wheeler <wheeler@kde.org>
This class was inspired by a previous KUrlCompletion by
Henner Zeller <zeller@think.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 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 "kurlcompletion.h"
#include <config.h>
#include <stdlib.h>
#include <assert.h>
#include <limits.h>
#include <QtCore/QCoreApplication>
#include <QtCore/QMutableStringListIterator>
#include <QtCore/QRegExp>
#include <QtCore/QTimer>
#include <QtCore/QDir>
#include <QtCore/QDirIterator>
#include <QtCore/QFile>
#include <QtCore/QTextStream>
#include <QtCore/QThread>
#include <QActionEvent>
#include <kauthorized.h>
#include <kdebug.h>
#include <kurl.h>
#include <kio/job.h>
#include <kprotocolmanager.h>
#include <kconfig.h>
#include <kglobal.h>
#include <kglobalsettings.h>
#include <kde_file.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <sys/stat.h>
#include <pwd.h>
#include <time.h>
#include <sys/param.h>
#include <kconfiggroup.h>
#ifdef Q_WS_WIN
#include <kkernel_win.h>
#endif
static bool expandTilde(QString&);
static bool expandEnv(QString&);
static QString unescape(const QString& text);
// Permission mask for files that are executable by
// user, group or other
#define MODE_EXE (S_IXUSR | S_IXGRP | S_IXOTH)
// Constants for types of completion
enum ComplType {CTNone = 0, CTEnv, CTUser, CTMan, CTExe, CTFile, CTUrl, CTInfo};
class CompletionThread;
///////////////////////////////////////////////////////
///////////////////////////////////////////////////////
// KUrlCompletionPrivate
//
class KUrlCompletionPrivate
{
public:
KUrlCompletionPrivate(KUrlCompletion* parent)
: q(parent),
url_auto_completion(true),
userListThread(0),
dirListThread(0) {
}
~KUrlCompletionPrivate();
void _k_slotEntries(KIO::Job*, const KIO::UDSEntryList&);
void _k_slotIOFinished(KJob*);
class MyURL;
bool userCompletion(const MyURL& url, QString* match);
bool envCompletion(const MyURL& url, QString* match);
bool exeCompletion(const MyURL& url, QString* match);
bool fileCompletion(const MyURL& url, QString* match);
bool urlCompletion(const MyURL& url, QString* match);
bool isAutoCompletion();
// List the next dir in m_dirs
QString listDirectories(const QStringList&,
const QString&,
bool only_exe = false,
bool only_dir = false,
bool no_hidden = false,
bool stat_files = true);
void listUrls(const QList<KUrl> &urls,
const QString& filter = QString(),
bool only_exe = false,
bool no_hidden = false);
void addMatches(const QStringList&);
QString finished();
void init();
void setListedUrl(int compl_type /* enum ComplType */,
const QString& dir = QString(),
const QString& filter = QString(),
bool no_hidden = false);
bool isListedUrl(int compl_type /* enum ComplType */,
const QString& dir = QString(),
const QString& filter = QString(),
bool no_hidden = false);
KUrlCompletion* q;
QList<KUrl> list_urls;
bool onlyLocalProto;
// urlCompletion() in Auto/Popup mode?
bool url_auto_completion;
// Append '/' to directories in Popup mode?
// Doing that stat's all files and is slower
bool popup_append_slash;
// Keep track of currently listed files to avoid reading them again
QString last_path_listed;
QString last_file_listed;
QString last_prepend;
int last_compl_type;
int last_no_hidden;
QString cwd; // "current directory" = base dir for completion
KUrlCompletion::Mode mode; // ExeCompletion, FileCompletion, DirCompletion
bool replace_env;
bool replace_home;
bool complete_url; // if true completing a URL (i.e. 'prepend' is a URL), otherwise a path
KIO::ListJob* list_job; // kio job to list directories
QString prepend; // text to prepend to listed items
QString compl_text; // text to pass on to KCompletion
// Filters for files read with kio
bool list_urls_only_exe; // true = only list executables
bool list_urls_no_hidden;
QString list_urls_filter; // filter for listed files
CompletionThread* userListThread;
CompletionThread* dirListThread;
};
/**
* A custom event type that is used to return a list of completion
* matches from an asynchronous lookup.
*/
class CompletionMatchEvent : public QEvent
{
public:
CompletionMatchEvent(CompletionThread* thread) :
QEvent(uniqueType()),
m_completionThread(thread)
{}
CompletionThread* completionThread() const {
return m_completionThread;
}
static Type uniqueType() {
return Type(User + 61080);
}
private:
CompletionThread* m_completionThread;
};
class CompletionThread : public QThread
{
protected:
CompletionThread(KUrlCompletionPrivate* receiver) :
QThread(),
m_prepend(receiver->prepend),
m_complete_url(receiver->complete_url),
m_receiver(receiver),
m_terminationRequested(false)
{}
public:
void requestTermination() {
m_terminationRequested = true;
}
QStringList matches() const {
return m_matches;
}
protected:
void addMatch(const QString& match) {
m_matches.append(match);
}
bool terminationRequested() const {
return m_terminationRequested;
}
void done() {
if (!m_terminationRequested)
qApp->postEvent(m_receiver->q, new CompletionMatchEvent(this));
else
deleteLater();
}
const QString m_prepend;
const bool m_complete_url; // if true completing a URL (i.e. 'm_prepend' is a URL), otherwise a path
private:
KUrlCompletionPrivate* m_receiver;
QStringList m_matches;
bool m_terminationRequested;
};
/**
* A simple thread that fetches a list of tilde-completions and returns this
* to the caller via a CompletionMatchEvent.
*/
class UserListThread : public CompletionThread
{
public:
UserListThread(KUrlCompletionPrivate* receiver) :
CompletionThread(receiver)
{}
protected:
virtual void run() {
static const QChar tilde = '~';
// we don't need to handle prepend here, right? ~user is always at pos 0
assert(m_prepend.isEmpty());
struct passwd* pw;
while ((pw = ::getpwent()) && !terminationRequested())
addMatch(tilde + QString::fromLocal8Bit(pw->pw_name));
::endpwent();
addMatch(QString(tilde));
done();
}
};
class DirectoryListThread : public CompletionThread
{
public:
DirectoryListThread(KUrlCompletionPrivate* receiver,
const QStringList& dirList,
const QString& filter,
bool onlyExe,
bool onlyDir,
bool noHidden,
bool appendSlashToDir) :
CompletionThread(receiver),
m_dirList(dirList),
m_filter(filter),
m_onlyExe(onlyExe),
m_onlyDir(onlyDir),
m_noHidden(noHidden),
m_appendSlashToDir(appendSlashToDir)
{}
virtual void run();
private:
QStringList m_dirList;
QString m_filter;
bool m_onlyExe;
bool m_onlyDir;
bool m_noHidden;
bool m_appendSlashToDir;
};
void DirectoryListThread::run()
{
// Thread safety notes:
//
// There very possibly may be thread safety issues here, but I've done a check
// of all of the things that would seem to be problematic. Here are a few
// things that I have checked to be safe here (some used indirectly):
//
// QDir::currentPath(), QDir::setCurrent(), QFile::decodeName(), QFile::encodeName()
// QString::fromLocal8Bit(), QString::toLocal8Bit(), QTextCodec::codecForLocale()
//
// Also see (for POSIX functions):
// http://www.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_09.html
// kDebug() << "Entered DirectoryListThread::run(), m_filter=" << m_filter << ", m_onlyExe=" << m_onlyExe << ", m_onlyDir=" << m_onlyDir << ", m_appendSlashToDir=" << m_appendSlashToDir << ", m_dirList.size()=" << m_dirList.size();
QStringList::ConstIterator end = m_dirList.constEnd();
for (QStringList::ConstIterator it = m_dirList.constBegin();
it != end && !terminationRequested();
++it) {
// kDebug() << "Scanning directory" << *it;
// A trick from KIO that helps performance by a little bit:
// chdir to the directory so we won't have to deal with full paths
// with stat()
QString path = QDir::currentPath();
QDir::setCurrent(*it);
QDir::Filters iterator_filter = (m_noHidden ? QDir::Filter(0) : QDir::Hidden) | QDir::Readable | QDir::NoDotAndDotDot;
if (m_onlyExe)
iterator_filter |= (QDir::Dirs | QDir::Files | QDir::Executable);
else if (m_onlyDir)
iterator_filter |= QDir::Dirs;
else
iterator_filter |= (QDir::Dirs | QDir::Files);
QDirIterator current_dir_iterator(*it, iterator_filter);
while (current_dir_iterator.hasNext()) {
current_dir_iterator.next();
QFileInfo file_info = current_dir_iterator.fileInfo();
const QString file_name = file_info.fileName();
//kDebug() << "Found" << file_name;
if (m_filter.isEmpty() || file_name.startsWith(m_filter)) {
QString toAppend = m_complete_url ? QUrl::toPercentEncoding(file_name) : file_name;
// Add '/' to directories
if (m_appendSlashToDir && file_info.isDir())
toAppend.append(QLatin1Char('/'));
addMatch(m_prepend + toAppend);
}
}
// chdir to the original directory
QDir::setCurrent(path);
}
done();
}
KUrlCompletionPrivate::~KUrlCompletionPrivate()
{
if (userListThread)
userListThread->requestTermination();
if (dirListThread)
dirListThread->requestTermination();
}
///////////////////////////////////////////////////////
///////////////////////////////////////////////////////
// MyURL - wrapper for KUrl with some different functionality
//
class KUrlCompletionPrivate::MyURL
{
public:
MyURL(const QString& url, const QString& cwd);
MyURL(const MyURL& url);
~MyURL();
KUrl kurl() const {
return m_kurl;
}
- QString protocol() const {
- return m_kurl.protocol();
+ QString scheme() const {
+ return m_kurl.scheme();
}
// The directory with a trailing '/'
QString dir() const {
return m_kurl.directory(KUrl::AppendTrailingSlash | KUrl::ObeyTrailingSlash);
}
QString file() const {
return m_kurl.fileName(KUrl::ObeyTrailingSlash);
}
// The initial, unparsed, url, as a string.
QString url() const {
return m_url;
}
// Is the initial string a URL, or just a path (whether absolute or relative)
bool isURL() const {
return m_isURL;
}
void filter(bool replace_user_dir, bool replace_env);
private:
void init(const QString& url, const QString& cwd);
KUrl m_kurl;
QString m_url;
bool m_isURL;
};
KUrlCompletionPrivate::MyURL::MyURL(const QString& _url, const QString& cwd)
{
init(_url, cwd);
}
KUrlCompletionPrivate::MyURL::MyURL(const MyURL& _url)
: m_kurl(_url.m_kurl)
{
m_url = _url.m_url;
m_isURL = _url.m_isURL;
}
void KUrlCompletionPrivate::MyURL::init(const QString& _url, const QString& cwd)
{
// Save the original text
m_url = _url;
// Non-const copy
QString url_copy = _url;
// Special shortcuts for "man:" and "info:"
if (url_copy.startsWith(QLatin1Char('#'))) {
if (url_copy.length() > 1 && url_copy.at(1) == QLatin1Char('#'))
url_copy.replace(0, 2, QLatin1String("info:"));
else
url_copy.replace(0, 1, QLatin1String("man:"));
}
// Look for a protocol in 'url'
QRegExp protocol_regex = QRegExp("^(?![A-Za-z]:)[^/\\s\\\\]*:");
// Assume "file:" or whatever is given by 'cwd' if there is
// no protocol. (KUrl does this only for absolute paths)
if (protocol_regex.indexIn(url_copy) == 0) {
m_kurl = KUrl(url_copy);
m_isURL = true;
} else { // relative path or ~ or $something
m_isURL = false;
if (!QDir::isRelativePath(url_copy) ||
url_copy.startsWith(QLatin1Char('~')) ||
url_copy.startsWith(QLatin1Char('$'))) {
m_kurl = KUrl();
m_kurl.setPath(url_copy);
} else {
if (cwd.isEmpty()) {
m_kurl = KUrl(url_copy);
} else {
m_kurl = KUrl(cwd);
m_kurl.addPath(url_copy);
}
}
}
}
KUrlCompletionPrivate::MyURL::~MyURL()
{
}
void KUrlCompletionPrivate::MyURL::filter(bool replace_user_dir, bool replace_env)
{
QString d = dir() + file();
if (replace_user_dir) expandTilde(d);
if (replace_env) expandEnv(d);
m_kurl.setPath(d);
}
///////////////////////////////////////////////////////
///////////////////////////////////////////////////////
// KUrlCompletion
//
KUrlCompletion::KUrlCompletion() : KCompletion(), d(new KUrlCompletionPrivate(this))
{
d->init();
}
KUrlCompletion::KUrlCompletion(Mode _mode)
: KCompletion(),
d(new KUrlCompletionPrivate(this))
{
d->init();
setMode(_mode);
}
KUrlCompletion::~KUrlCompletion()
{
stop();
delete d;
}
void KUrlCompletionPrivate::init()
{
cwd = QDir::homePath();
replace_home = true;
replace_env = true;
last_no_hidden = false;
last_compl_type = 0;
list_job = 0L;
mode = KUrlCompletion::FileCompletion;
// Read settings
KConfigGroup cg(KGlobal::config(), "URLCompletion");
url_auto_completion = cg.readEntry("alwaysAutoComplete", true);
popup_append_slash = cg.readEntry("popupAppendSlash", true);
onlyLocalProto = cg.readEntry("LocalProtocolsOnly", false);
q->setIgnoreCase(true);
}
void KUrlCompletion::setDir(const QString& _dir)
{
d->cwd = _dir;
}
QString KUrlCompletion::dir() const
{
return d->cwd;
}
KUrlCompletion::Mode KUrlCompletion::mode() const
{
return d->mode;
}
void KUrlCompletion::setMode(Mode _mode)
{
d->mode = _mode;
}
bool KUrlCompletion::replaceEnv() const
{
return d->replace_env;
}
void KUrlCompletion::setReplaceEnv(bool replace)
{
d->replace_env = replace;
}
bool KUrlCompletion::replaceHome() const
{
return d->replace_home;
}
void KUrlCompletion::setReplaceHome(bool replace)
{
d->replace_home = replace;
}
/*
* makeCompletion()
*
* Entry point for file name completion
*/
QString KUrlCompletion::makeCompletion(const QString& text)
{
//kDebug() << text << "d->cwd=" << d->cwd;
KUrlCompletionPrivate::MyURL url(text, d->cwd);
d->compl_text = text;
// Set d->prepend to the original URL, with the filename [and ref/query] stripped.
// This is what gets prepended to the directory-listing matches.
int toRemove = url.file().length() - url.kurl().query().length();
if (url.kurl().hasRef())
toRemove += url.kurl().ref().length() + 1;
d->prepend = text.left(text.length() - toRemove);
d->complete_url = url.isURL();
QString aMatch;
// Environment variables
//
if (d->replace_env && d->envCompletion(url, &aMatch))
return aMatch;
// User directories
//
if (d->replace_home && d->userCompletion(url, &aMatch))
return aMatch;
// Replace user directories and variables
url.filter(d->replace_home, d->replace_env);
- //kDebug() << "Filtered: proto=" << url.protocol()
+ //kDebug() << "Filtered: proto=" << url.scheme()
// << ", dir=" << url.dir()
// << ", file=" << url.file()
// << ", kurl url=" << *url.kurl();
if (d->mode == ExeCompletion) {
// Executables
//
if (d->exeCompletion(url, &aMatch))
return aMatch;
// KRun can run "man:" and "info:" etc. so why not treat them
// as executables...
if (d->urlCompletion(url, &aMatch))
return aMatch;
} else {
// Local files, directories
//
if (d->fileCompletion(url, &aMatch))
return aMatch;
// All other...
//
if (d->urlCompletion(url, &aMatch))
return aMatch;
}
d->setListedUrl(CTNone);
stop();
return QString();
}
/*
* finished
*
* Go on and call KCompletion.
* Called when all matches have been added
*/
QString KUrlCompletionPrivate::finished()
{
if (last_compl_type == CTInfo)
return q->KCompletion::makeCompletion(compl_text.toLower());
else
return q->KCompletion::makeCompletion(compl_text);
}
/*
* isRunning
*
* Return true if either a KIO job or the DirLister
* is running
*/
bool KUrlCompletion::isRunning() const
{
return d->list_job || (d->dirListThread && !d->dirListThread->isFinished());
}
/*
* stop
*
* Stop and delete a running KIO job or the DirLister
*/
void KUrlCompletion::stop()
{
if (d->list_job) {
d->list_job->kill();
d->list_job = 0L;
}
if (d->dirListThread) {
d->dirListThread->requestTermination();
d->dirListThread = 0;
}
}
/*
* Keep track of the last listed directory
*/
void KUrlCompletionPrivate::setListedUrl(int complType,
const QString& directory,
const QString& filter,
bool no_hidden)
{
last_compl_type = complType;
last_path_listed = directory;
last_file_listed = filter;
last_no_hidden = (int) no_hidden;
last_prepend = prepend;
}
bool KUrlCompletionPrivate::isListedUrl(int complType,
const QString& directory,
const QString& filter,
bool no_hidden)
{
return last_compl_type == complType
&& (last_path_listed == directory
|| (directory.isEmpty() && last_path_listed.isEmpty()))
&& (filter.startsWith (last_file_listed)
|| (filter.isEmpty() && last_file_listed.isEmpty()))
&& last_no_hidden == (int) no_hidden
&& last_prepend == prepend; // e.g. relative path vs absolute
}
/*
* isAutoCompletion
*
* Returns true if completion mode is Auto or Popup
*/
bool KUrlCompletionPrivate::isAutoCompletion()
{
return q->completionMode() == KGlobalSettings::CompletionAuto
|| q->completionMode() == KGlobalSettings::CompletionPopup
|| q->completionMode() == KGlobalSettings::CompletionMan
|| q->completionMode() == KGlobalSettings::CompletionPopupAuto;
}
//////////////////////////////////////////////////
//////////////////////////////////////////////////
// User directories
//
bool KUrlCompletionPrivate::userCompletion(const KUrlCompletionPrivate::MyURL& url, QString* pMatch)
{
- if (url.protocol() != QLatin1String("file")
+ if (url.scheme() != QLatin1String("file")
|| !url.dir().isEmpty()
|| !url.file().startsWith(QLatin1Char('~')))
return false;
if (!isListedUrl(CTUser)) {
q->stop();
q->clear();
if (!userListThread) {
userListThread = new UserListThread(this);
userListThread->start();
// If the thread finishes quickly make sure that the results
// are added to the first matching case.
userListThread->wait(200);
const QStringList l = userListThread->matches();
addMatches(l);
}
}
*pMatch = finished();
return true;
}
/////////////////////////////////////////////////////
/////////////////////////////////////////////////////
// Environment variables
//
#ifndef Q_OS_WIN
extern char** environ; // Array of environment variables
#endif
bool KUrlCompletionPrivate::envCompletion(const KUrlCompletionPrivate::MyURL& url, QString* pMatch)
{
if (url.file().isEmpty() || url.file().at(0) != QLatin1Char('$'))
return false;
if (!isListedUrl(CTEnv)) {
q->stop();
q->clear();
char** env = environ;
QString dollar = QLatin1String("$");
QStringList l;
while (*env) {
QString s = QString::fromLocal8Bit(*env);
int pos = s.indexOf(QLatin1Char('='));
if (pos == -1)
pos = s.length();
if (pos > 0)
l.append(prepend + dollar + s.left(pos));
env++;
}
addMatches(l);
}
setListedUrl(CTEnv);
*pMatch = finished();
return true;
}
//////////////////////////////////////////////////
//////////////////////////////////////////////////
// Executables
//
bool KUrlCompletionPrivate::exeCompletion(const KUrlCompletionPrivate::MyURL& url, QString* pMatch)
{
- if (url.protocol() != QLatin1String("file"))
+ if (url.scheme() != QLatin1String("file"))
return false;
QString directory = unescape(url.dir()); // remove escapes
// Find directories to search for completions, either
//
// 1. complete path given in url
// 2. current directory (d->cwd)
// 3. $PATH
// 4. no directory at all
QStringList dirList;
if (!url.file().isEmpty()) {
// $PATH
dirList = QString::fromLocal8Bit(qgetenv("PATH")).split(
KPATH_SEPARATOR, QString::SkipEmptyParts);
QStringList::Iterator it = dirList.begin();
for (; it != dirList.end(); ++it)
it->append(QLatin1Char('/'));
} else if (!QDir::isRelativePath(directory)) {
// complete path in url
dirList.append(directory);
} else if (!directory.isEmpty() && !cwd.isEmpty()) {
// current directory
dirList.append(cwd + QLatin1Char('/') + directory);
}
// No hidden files unless the user types "."
bool no_hidden_files = url.file().isEmpty() || url.file().at(0) != QLatin1Char('.');
// List files if needed
//
if (!isListedUrl(CTExe, directory, url.file(), no_hidden_files)) {
q->stop();
q->clear();
setListedUrl(CTExe, directory, url.file(), no_hidden_files);
*pMatch = listDirectories(dirList, url.file(), true, false, no_hidden_files);
} else if (!q->isRunning()) {
*pMatch = finished();
} else {
if (dirListThread)
setListedUrl(CTExe, directory, url.file(), no_hidden_files);
pMatch->clear();
}
return true;
}
//////////////////////////////////////////////////
//////////////////////////////////////////////////
// Local files
//
bool KUrlCompletionPrivate::fileCompletion(const KUrlCompletionPrivate::MyURL& url, QString* pMatch)
{
- if (url.protocol() != QLatin1String("file"))
+ if (url.scheme() != QLatin1String("file"))
return false;
QString directory = unescape(url.dir());
if (url.url().length() && url.url().at(0) == QLatin1Char('.')) {
if (url.url().length() == 1) {
*pMatch = (q->completionMode() == KGlobalSettings::CompletionMan) ?
QLatin1String(".") :
QLatin1String("..");
return true;
} else if (url.url().length() == 2 && url.url().at(1) == QLatin1Char('.')) {
*pMatch = QLatin1String("..");
return true;
}
}
//kDebug() << "fileCompletion" << url << "dir=" << dir;
// Find directories to search for completions, either
//
// 1. complete path given in url
// 2. current directory (d->cwd)
// 3. no directory at all
QStringList dirList;
if (!QDir::isRelativePath(directory)) {
// complete path in url
dirList.append(directory);
} else if (!cwd.isEmpty()) {
// current directory
QString dirToAdd = cwd;
if (!directory.isEmpty()) {
if (!cwd.endsWith('/'))
dirToAdd.append(QLatin1Char('/'));
dirToAdd.append(directory);
}
dirList.append(dirToAdd);
}
// No hidden files unless the user types "."
bool no_hidden_files = !url.file().startsWith(QLatin1Char('.'));
// List files if needed
//
if (!isListedUrl(CTFile, directory, QString(), no_hidden_files)) {
q->stop();
q->clear();
setListedUrl(CTFile, directory, QString(), no_hidden_files);
// Append '/' to directories in Popup mode?
bool append_slash = (popup_append_slash
&& (q->completionMode() == KGlobalSettings::CompletionPopup ||
q->completionMode() == KGlobalSettings::CompletionPopupAuto));
bool only_dir = (mode == KUrlCompletion::DirCompletion);
*pMatch = listDirectories(dirList, QString(), false, only_dir, no_hidden_files,
append_slash);
} else if (!q->isRunning()) {
*pMatch = finished();
} else {
pMatch->clear();
}
return true;
}
//////////////////////////////////////////////////
//////////////////////////////////////////////////
// URLs not handled elsewhere...
//
static bool isLocalProtocol(const QString& protocol)
{
return (KProtocolInfo::protocolClass(protocol) == QLatin1String(":local"));
}
bool KUrlCompletionPrivate::urlCompletion(const KUrlCompletionPrivate::MyURL& url, QString* pMatch)
{
//kDebug() << *url.kurl();
- if (onlyLocalProto && isLocalProtocol(url.protocol()))
+ if (onlyLocalProto && isLocalProtocol(url.scheme()))
return false;
// Use d->cwd as base url in case url is not absolute
KUrl url_dir = url.kurl();
if (url_dir.isRelative() && !cwd.isEmpty()) {
const KUrl url_cwd (cwd);
// Create an URL with the directory to be listed
url_dir = KUrl(url_cwd, url_dir.url());
}
// url is malformed
if (!url_dir.isValid())
return false;
// non local urls
- if (!isLocalProtocol(url.protocol())) {
+ if (!isLocalProtocol(url.scheme())) {
// url does not specify host
if (url_dir.host().isEmpty())
return false;
// url does not specify a valid directory
if (url_dir.directory(KUrl::AppendTrailingSlash | KUrl::ObeyTrailingSlash).isEmpty())
return false;
// automatic completion is disabled
if (isAutoCompletion() && !url_auto_completion)
return false;
}
// url handler doesn't support listing
if (!KProtocolManager::supportsListing(url_dir))
return false;
url_dir.setFileName(QString()); // not really nesseccary, but clear the filename anyway...
// Remove escapes
QString directory = unescape(url_dir.directory(KUrl::AppendTrailingSlash | KUrl::ObeyTrailingSlash));
url_dir.setPath(directory);
// List files if needed
//
if (!isListedUrl(CTUrl, url_dir.prettyUrl(), url.file())) {
q->stop();
q->clear();
setListedUrl(CTUrl, url_dir.prettyUrl(), QString());
QList<KUrl> url_list;
url_list.append(url_dir);
listUrls(url_list, QString(), false);
pMatch->clear();
} else if (!q->isRunning()) {
*pMatch = finished();
} else {
pMatch->clear();
}
return true;
}
//////////////////////////////////////////////////
//////////////////////////////////////////////////
// Directory and URL listing
//
/*
* addMatches
*
* Called to add matches to KCompletion
*/
void KUrlCompletionPrivate::addMatches(const QStringList& matchList)
{
q->insertItems(matchList);
}
/*
* listDirectories
*
* List files starting with 'filter' in the given directories,
* either using DirLister or listURLs()
*
* In either case, addMatches() is called with the listed
* files, and eventually finished() when the listing is done
*
* Returns the match if available, or QString() if
* DirLister timed out or using kio
*/
QString KUrlCompletionPrivate::listDirectories(
const QStringList& dirList,
const QString& filter,
bool only_exe,
bool only_dir,
bool no_hidden,
bool append_slash_to_dir)
{
assert(!q->isRunning());
if (qgetenv("KURLCOMPLETION_LOCAL_KIO").isEmpty()) {
//kDebug() << "Listing (listDirectories):" << dirList << "filter=" << filter << "without KIO";
// Don't use KIO
if (dirListThread)
dirListThread->requestTermination();
QStringList dirs;
QStringList::ConstIterator end = dirList.constEnd();
for (QStringList::ConstIterator it = dirList.constBegin();
it != end;
++it) {
KUrl url;
url.setPath(*it);
if (KAuthorized::authorizeUrlAction(QLatin1String("list"), KUrl(), url))
dirs.append(*it);
}
dirListThread = new DirectoryListThread(this, dirs, filter, only_exe, only_dir,
no_hidden, append_slash_to_dir);
dirListThread->start();
dirListThread->wait(200);
addMatches(dirListThread->matches());
return finished();
}
// Use KIO
//kDebug() << "Listing (listDirectories):" << dirList << "with KIO";
QList<KUrl> url_list;
QStringList::ConstIterator it = dirList.constBegin();
QStringList::ConstIterator end = dirList.constEnd();
for (; it != end; ++it) {
url_list.append(KUrl(*it));
}
listUrls(url_list, filter, only_exe, no_hidden);
// Will call addMatches() and finished()
return QString();
}
/*
* listURLs
*
* Use KIO to list the given urls
*
* addMatches() is called with the listed files
* finished() is called when the listing is done
*/
void KUrlCompletionPrivate::listUrls(
const QList<KUrl> &urls,
const QString& filter,
bool only_exe,
bool no_hidden)
{
assert(list_urls.isEmpty());
assert(list_job == 0L);
list_urls = urls;
list_urls_filter = filter;
list_urls_only_exe = only_exe;
list_urls_no_hidden = no_hidden;
//kDebug() << "Listing URLs:" << *urls[0] << ",...";
// Start it off by calling _k_slotIOFinished
//
// This will start a new list job as long as there
// are urls in d->list_urls
//
_k_slotIOFinished(0);
}
/*
* _k_slotEntries
*
* Receive files listed by KIO and call addMatches()
*/
void KUrlCompletionPrivate::_k_slotEntries(KIO::Job*, const KIO::UDSEntryList& entries)
{
QStringList matchList;
KIO::UDSEntryList::ConstIterator it = entries.constBegin();
const KIO::UDSEntryList::ConstIterator end = entries.constEnd();
QString filter = list_urls_filter;
int filter_len = filter.length();
// Iterate over all files
//
for (; it != end; ++it) {
const KIO::UDSEntry& entry = *it;
const QString url = entry.stringValue(KIO::UDSEntry::UDS_URL);
QString entry_name;
if (!url.isEmpty()) {
// kDebug() << "url:" << url;
entry_name = KUrl(url).fileName();
} else {
entry_name = entry.stringValue(KIO::UDSEntry::UDS_NAME);
}
// kDebug() << "name:" << name;
if ((!entry_name.isEmpty() && entry_name.at(0) == QLatin1Char('.')) &&
(list_urls_no_hidden ||
entry_name.length() == 1 ||
(entry_name.length() == 2 && entry_name.at(1) == QLatin1Char('.'))))
continue;
const bool isDir = entry.isDir();
if (mode == KUrlCompletion::DirCompletion && !isDir)
continue;
if (filter_len == 0 || entry_name.left(filter_len) == filter) {
QString toAppend = complete_url ? QUrl::toPercentEncoding(entry_name) : entry_name;
if (isDir)
toAppend.append(QLatin1Char('/'));
if (!list_urls_only_exe ||
(entry.numberValue(KIO::UDSEntry::UDS_ACCESS) & MODE_EXE) // true if executable
) {
matchList.append(prepend + toAppend);
}
}
}
addMatches(matchList);
}
/*
* _k_slotIOFinished
*
* Called when a KIO job is finished.
*
* Start a new list job if there are still urls in
* list_urls, otherwise call finished()
*/
void KUrlCompletionPrivate::_k_slotIOFinished(KJob* job)
{
assert(job == list_job); Q_UNUSED(job)
if (list_urls.isEmpty()) {
list_job = 0L;
finished(); // will call KCompletion::makeCompletion()
} else {
KUrl kurl(list_urls.takeFirst());
// list_urls.removeAll( kurl );
// kDebug() << "Start KIO::listDir" << kurl;
list_job = KIO::listDir(kurl, KIO::HideProgressInfo);
list_job->addMetaData("no-auth-prompt", "true");
assert(list_job);
q->connect(list_job,
SIGNAL(result(KJob*)),
SLOT(_k_slotIOFinished(KJob*)));
q->connect(list_job,
SIGNAL(entries(KIO::Job*, const KIO::UDSEntryList&)),
SLOT(_k_slotEntries(KIO::Job*, const KIO::UDSEntryList&)));
}
}
///////////////////////////////////////////////////
///////////////////////////////////////////////////
/*
* postProcessMatch, postProcessMatches
*
* Called by KCompletion before emitting match() and matches()
*
* Append '/' to directories for file completion. This is
* done here to avoid stat()'ing a lot of files
*/
void KUrlCompletion::postProcessMatch(QString* pMatch) const
{
// kDebug() << *pMatch;
if (!pMatch->isEmpty()) {
// Add '/' to directories in file completion mode
// unless it has already been done
if (d->last_compl_type == CTFile
&& pMatch->at(pMatch->length() - 1) != QLatin1Char('/')) {
QString copy;
if (pMatch->startsWith(QLatin1String("file:")))
copy = KUrl(*pMatch).toLocalFile();
else
copy = *pMatch;
expandTilde(copy);
expandEnv(copy);
#ifdef Q_WS_WIN
DWORD dwAttr = GetFileAttributesW((LPCWSTR) copy.utf16());
if (dwAttr == INVALID_FILE_ATTRIBUTES) {
kDebug() << "Could not get file attribs ( "
<< GetLastError()
<< " ) for "
<< copy;
} else if ((dwAttr & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)
pMatch->append(QLatin1Char('/'));
#else
if (QDir::isRelativePath(copy))
copy.prepend(d->cwd + QLatin1Char('/'));
// kDebug() << "stat'ing" << copy;
KDE_struct_stat sbuff;
QByteArray file = QFile::encodeName(copy);
if (KDE_stat(file.data(), &sbuff) == 0) {
if (S_ISDIR(sbuff.st_mode))
pMatch->append(QLatin1Char('/'));
} else {
kDebug() << "Could not stat file" << copy;
}
#endif
}
}
}
void KUrlCompletion::postProcessMatches(QStringList* /*matches*/) const
{
// Maybe '/' should be added to directories here as in
// postProcessMatch() but it would slow things down
// when there are a lot of matches...
}
void KUrlCompletion::postProcessMatches(KCompletionMatches* /*matches*/) const
{
// Maybe '/' should be added to directories here as in
// postProcessMatch() but it would slow things down
// when there are a lot of matches...
}
void KUrlCompletion::customEvent(QEvent* e)
{
if (e->type() == CompletionMatchEvent::uniqueType()) {
CompletionMatchEvent* matchEvent = static_cast<CompletionMatchEvent*>(e);
matchEvent->completionThread()->wait();
if (!d->isListedUrl(CTUser)) {
stop();
clear();
d->addMatches(matchEvent->completionThread()->matches());
} else {
d->setListedUrl(CTUser);
}
if (d->userListThread == matchEvent->completionThread())
d->userListThread = 0;
if (d->dirListThread == matchEvent->completionThread())
d->dirListThread = 0;
delete matchEvent->completionThread();
}
}
// static
QString KUrlCompletion::replacedPath(const QString& text, bool replaceHome, bool replaceEnv)
{
if (text.isEmpty())
return text;
KUrlCompletionPrivate::MyURL url(text, QString()); // no need to replace something of our current cwd
if (!url.kurl().isLocalFile())
return text;
url.filter(replaceHome, replaceEnv);
return url.dir() + url.file();
}
QString KUrlCompletion::replacedPath(const QString& text) const
{
return replacedPath(text, d->replace_home, d->replace_env);
}
/////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////
// Static functions
/*
* expandEnv
*
* Expand environment variables in text. Escaped '$' are ignored.
* Return true if expansion was made.
*/
static bool expandEnv(QString& text)
{
// Find all environment variables beginning with '$'
//
int pos = 0;
bool expanded = false;
while ((pos = text.indexOf(QLatin1Char('$'), pos)) != -1) {
// Skip escaped '$'
//
if (pos > 0 && text.at(pos - 1) == QLatin1Char('\\')) {
pos++;
}
// Variable found => expand
//
else {
// Find the end of the variable = next '/' or ' '
//
int pos2 = text.indexOf(QLatin1Char(' '), pos + 1);
int pos_tmp = text.indexOf(QLatin1Char('/'), pos + 1);
if (pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2))
pos2 = pos_tmp;
if (pos2 == -1)
pos2 = text.length();
// Replace if the variable is terminated by '/' or ' '
// and defined
//
if (pos2 >= 0) {
int len = pos2 - pos;
QString key = text.mid(pos + 1, len - 1);
QString value =
QString::fromLocal8Bit(qgetenv(key.toLocal8Bit()));
if (!value.isEmpty()) {
expanded = true;
text.replace(pos, len, value);
pos = pos + value.length();
} else {
pos = pos2;
}
}
}
}
return expanded;
}
/*
* expandTilde
*
* Replace "~user" with the users home directory
* Return true if expansion was made.
*/
static bool expandTilde(QString& text)
{
if (text.isEmpty() || (text.at(0) != QLatin1Char('~')))
return false;
bool expanded = false;
// Find the end of the user name = next '/' or ' '
//
int pos2 = text.indexOf(QLatin1Char(' '), 1);
int pos_tmp = text.indexOf(QLatin1Char('/'), 1);
if (pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2))
pos2 = pos_tmp;
if (pos2 == -1)
pos2 = text.length();
// Replace ~user if the user name is terminated by '/' or ' '
//
if (pos2 >= 0) {
QString user = text.mid(1, pos2 - 1);
QString dir;
// A single ~ is replaced with $HOME
//
if (user.isEmpty()) {
dir = QDir::homePath();
}
// ~user is replaced with the dir from passwd
//
else {
struct passwd* pw = ::getpwnam(user.toLocal8Bit());
if (pw)
dir = QFile::decodeName(pw->pw_dir);
::endpwent();
}
if (!dir.isEmpty()) {
expanded = true;
text.replace(0, pos2, dir);
}
}
return expanded;
}
/*
* unescape
*
* Remove escapes and return the result in a new string
*
*/
static QString unescape(const QString& text)
{
QString result;
for (int pos = 0; pos < text.length(); pos++)
if (text.at(pos) != QLatin1Char('\\'))
result.insert(result.length(), text.at(pos));
return result;
}
#include "moc_kurlcompletion.cpp"
diff --git a/kio/kio/netaccess.cpp b/kio/kio/netaccess.cpp
index e78d0e834f..80ab762696 100644
--- a/kio/kio/netaccess.cpp
+++ b/kio/kio/netaccess.cpp
@@ -1,545 +1,545 @@
/*
This file is part of the KDE libraries
Copyright (C) 1997 Torben Weis (weis@kde.org)
Copyright (C) 1998 Matthias Ettrich (ettrich@kde.org)
Copyright (C) 1999 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 "netaccess.h"
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <cstring>
#include <QtCore/QCharRef>
#include <QApplication>
#include <QtCore/QFile>
#include <QtCore/QMetaClassInfo>
#include <QtCore/QTextStream>
#include <qtemporaryfile.h>
#include <klocale.h>
#include <kurl.h>
#include <kstandarddirs.h>
#include "job.h"
#include "copyjob.h"
#include "deletejob.h"
#include "jobuidelegate.h"
#include "scheduler.h"
namespace KIO
{
class NetAccessPrivate
{
public:
NetAccessPrivate()
: m_metaData(0)
, bJobOK(true)
{}
UDSEntry m_entry;
QString m_mimetype;
QByteArray m_data;
KUrl m_url;
QMap<QString, QString> *m_metaData;
/**
* Whether the download succeeded or not
*/
bool bJobOK;
};
} // namespace KIO
using namespace KIO;
/**
* List of temporary files
*/
static QStringList* tmpfiles;
static QString* lastErrorMsg = 0;
static int lastErrorCode = 0;
NetAccess::NetAccess() :
d( new NetAccessPrivate )
{
}
NetAccess::~NetAccess()
{
delete d;
}
bool NetAccess::download(const KUrl& u, QString & target, QWidget* window)
{
if (u.isLocalFile()) {
// file protocol. We do not need the network
target = u.toLocalFile();
bool accessible = KStandardDirs::checkAccess(target, R_OK);
if(!accessible)
{
if(!lastErrorMsg)
lastErrorMsg = new QString;
*lastErrorMsg = i18n("File '%1' is not readable", target);
lastErrorCode = ERR_COULD_NOT_READ;
}
return accessible;
}
if (target.isEmpty())
{
QTemporaryFile tmpFile;
tmpFile.setAutoRemove(false);
tmpFile.open();
target = tmpFile.fileName();
if (!tmpfiles)
tmpfiles = new QStringList;
tmpfiles->append(target);
}
NetAccess kioNet;
KUrl dest;
dest.setPath( target );
return kioNet.filecopyInternal( u, dest, -1, KIO::Overwrite, window, false /*copy*/);
}
bool NetAccess::upload(const QString& src, const KUrl& target, QWidget* window)
{
if (target.isEmpty())
return false;
// If target is local... well, just copy. This can be useful
// when the client code uses a temp file no matter what.
// Let's make sure it's not the exact same file though
if (target.isLocalFile() && target.toLocalFile() == src)
return true;
NetAccess kioNet;
KUrl s;
s.setPath(src);
return kioNet.filecopyInternal( s, target, -1, KIO::Overwrite, window, false /*copy*/ );
}
bool NetAccess::file_copy( const KUrl & src, const KUrl & target, QWidget* window )
{
NetAccess kioNet;
return kioNet.filecopyInternal( src, target, -1, KIO::DefaultFlags,
window, false /*copy*/ );
}
#ifndef KDE_NO_DEPRECATED
bool NetAccess::copy( const KUrl& src, const KUrl& target, QWidget* window )
{
return file_copy( src, target, window );
}
#endif
// bool NetAccess::file_copy( const KUrl& src, const KUrl& target, int permissions,
// bool overwrite, bool resume, QWidget* window )
// {
// NetAccess kioNet;
// return kioNet.filecopyInternal( src, target, permissions, overwrite, resume,
// window, false /*copy*/ );
// }
// bool NetAccess::file_move( const KUrl& src, const KUrl& target, int permissions,
// bool overwrite, bool resume, QWidget* window )
// {
// NetAccess kioNet;
// return kioNet.filecopyInternal( src, target, permissions, overwrite, resume,
// window, true /*move*/ );
// }
bool NetAccess::dircopy( const KUrl & src, const KUrl & target, QWidget* window )
{
KUrl::List srcList;
srcList.append( src );
return NetAccess::dircopy( srcList, target, window );
}
bool NetAccess::dircopy( const KUrl::List & srcList, const KUrl & target, QWidget* window )
{
NetAccess kioNet;
return kioNet.dircopyInternal( srcList, target, window, false /*copy*/ );
}
#ifndef KDE_NO_DEPRECATED
bool NetAccess::move( const KUrl& src, const KUrl& target, QWidget* window )
{
KUrl::List srcList;
srcList.append( src );
NetAccess kioNet;
return kioNet.dircopyInternal( srcList, target, window, true /*move*/ );
}
#endif
#ifndef KDE_NO_DEPRECATED
bool NetAccess::move( const KUrl::List& srcList, const KUrl& target, QWidget* window )
{
NetAccess kioNet;
return kioNet.dircopyInternal( srcList, target, window, true /*move*/ );
}
#endif
#ifndef KDE_NO_DEPRECATED
bool NetAccess::exists( const KUrl & url, bool source, QWidget* window )
{
if ( url.isLocalFile() )
return QFile::exists( url.toLocalFile() );
NetAccess kioNet;
return kioNet.statInternal( url, 0 /*no details*/,
source ? SourceSide : DestinationSide, window );
}
#endif
bool NetAccess::exists( const KUrl & url, StatSide side, QWidget* window )
{
if ( url.isLocalFile() )
return QFile::exists( url.toLocalFile() );
NetAccess kioNet;
return kioNet.statInternal( url, 0 /*no details*/, side, window );
}
bool NetAccess::stat( const KUrl & url, KIO::UDSEntry & entry, QWidget* window )
{
NetAccess kioNet;
bool ret = kioNet.statInternal( url, 2 /*all details*/, SourceSide, window );
if (ret)
entry = kioNet.d->m_entry;
return ret;
}
KUrl NetAccess::mostLocalUrl(const KUrl & url, QWidget* window)
{
if ( url.isLocalFile() )
{
return url;
}
KIO::UDSEntry entry;
if (!stat(url, entry, window))
{
return url;
}
const QString path = entry.stringValue( KIO::UDSEntry::UDS_LOCAL_PATH );
if ( !path.isEmpty() )
{
KUrl new_url;
new_url.setPath(path);
return new_url;
}
return url;
}
bool NetAccess::del( const KUrl & url, QWidget* window )
{
NetAccess kioNet;
return kioNet.delInternal( url, window );
}
bool NetAccess::mkdir( const KUrl & url, QWidget* window, int permissions )
{
NetAccess kioNet;
return kioNet.mkdirInternal( url, permissions, window );
}
QString NetAccess::fish_execute( const KUrl & url, const QString &command, QWidget* window )
{
NetAccess kioNet;
return kioNet.fish_executeInternal( url, command, window );
}
bool NetAccess::synchronousRun( Job* job, QWidget* window, QByteArray* data,
KUrl* finalURL, QMap<QString, QString>* metaData )
{
NetAccess kioNet;
// Disable autodeletion until we are back from this event loop (#170963)
// We just have to hope people don't mess with setAutoDelete in slots connected to the job, though.
const bool wasAutoDelete = job->isAutoDelete();
job->setAutoDelete(false);
const bool ok = kioNet.synchronousRunInternal(job, window, data, finalURL, metaData);
if (wasAutoDelete) {
job->deleteLater();
}
return ok;
}
QString NetAccess::mimetype( const KUrl& url, QWidget* window )
{
NetAccess kioNet;
return kioNet.mimetypeInternal( url, window );
}
QString NetAccess::lastErrorString()
{
return lastErrorMsg ? *lastErrorMsg : QString();
}
int NetAccess::lastError()
{
return lastErrorCode;
}
void NetAccess::removeTempFile(const QString& name)
{
if (!tmpfiles)
return;
if (tmpfiles->contains(name))
{
unlink(QFile::encodeName(name));
tmpfiles->removeAll(name);
}
}
bool NetAccess::filecopyInternal(const KUrl& src, const KUrl& target, int permissions,
KIO::JobFlags flags, QWidget* window, bool move)
{
d->bJobOK = true; // success unless further error occurs
KIO::Scheduler::checkSlaveOnHold(true);
KIO::Job * job = move
? KIO::file_move( src, target, permissions, flags )
: KIO::file_copy( src, target, permissions, flags );
job->ui()->setWindow (window);
connect( job, SIGNAL( result (KJob *) ),
this, SLOT( slotResult (KJob *) ) );
enter_loop();
return d->bJobOK;
}
bool NetAccess::dircopyInternal(const KUrl::List& src, const KUrl& target,
QWidget* window, bool move)
{
d->bJobOK = true; // success unless further error occurs
KIO::Job * job = move
? KIO::move( src, target )
: KIO::copy( src, target );
job->ui()->setWindow (window);
connect( job, SIGNAL( result (KJob *) ),
this, SLOT( slotResult (KJob *) ) );
enter_loop();
return d->bJobOK;
}
bool NetAccess::statInternal( const KUrl & url, int details, StatSide side,
QWidget* window )
{
d->bJobOK = true; // success unless further error occurs
KIO::JobFlags flags = url.isLocalFile() ? KIO::HideProgressInfo : KIO::DefaultFlags;
KIO::StatJob * job = KIO::stat( url, flags );
job->ui()->setWindow (window);
job->setDetails( details );
job->setSide( side == SourceSide ? StatJob::SourceSide : StatJob::DestinationSide );
connect( job, SIGNAL( result (KJob *) ),
this, SLOT( slotResult (KJob *) ) );
enter_loop();
return d->bJobOK;
}
bool NetAccess::delInternal( const KUrl & url, QWidget* window )
{
d->bJobOK = true; // success unless further error occurs
KIO::Job * job = KIO::del( url );
job->ui()->setWindow (window);
connect( job, SIGNAL( result (KJob *) ),
this, SLOT( slotResult (KJob *) ) );
enter_loop();
return d->bJobOK;
}
bool NetAccess::mkdirInternal( const KUrl & url, int permissions,
QWidget* window )
{
d->bJobOK = true; // success unless further error occurs
KIO::Job * job = KIO::mkdir( url, permissions );
job->ui()->setWindow (window);
connect( job, SIGNAL( result (KJob *) ),
this, SLOT( slotResult (KJob *) ) );
enter_loop();
return d->bJobOK;
}
QString NetAccess::mimetypeInternal( const KUrl & url, QWidget* window )
{
d->bJobOK = true; // success unless further error occurs
d->m_mimetype = QLatin1String("unknown");
KIO::Job * job = KIO::mimetype( url );
job->ui()->setWindow (window);
connect( job, SIGNAL( result (KJob *) ),
this, SLOT( slotResult (KJob *) ) );
connect( job, SIGNAL( mimetype (KIO::Job *, const QString &) ),
this, SLOT( slotMimetype (KIO::Job *, const QString &) ) );
enter_loop();
return d->m_mimetype;
}
void NetAccess::slotMimetype( KIO::Job *, const QString & type )
{
d->m_mimetype = type;
}
QString NetAccess::fish_executeInternal(const KUrl & url, const QString &command, QWidget* window)
{
QString target, remoteTempFileName, resultData;
KUrl tempPathUrl;
QTemporaryFile tmpFile;
tmpFile.open();
- if( url.protocol() == "fish" )
+ if( url.scheme() == "fish" )
{
// construct remote temp filename
tempPathUrl = url;
remoteTempFileName = tmpFile.fileName();
// We only need the filename. The directory might not exist on the remote side.
int pos = remoteTempFileName.lastIndexOf('/');
remoteTempFileName = "/tmp/fishexec_" + remoteTempFileName.mid(pos + 1);
tempPathUrl.setPath( remoteTempFileName );
d->bJobOK = true; // success unless further error occurs
QByteArray packedArgs;
QDataStream stream( &packedArgs, QIODevice::WriteOnly );
stream << int('X') << tempPathUrl << command;
KIO::Job * job = KIO::special( tempPathUrl, packedArgs );
job->ui()->setWindow( window );
connect( job, SIGNAL( result (KJob *) ),
this, SLOT( slotResult (KJob *) ) );
enter_loop();
// since the KIO::special does not provide feedback we need to download the result
if( NetAccess::download( tempPathUrl, target, window ) )
{
QFile resultFile( target );
if (resultFile.open( QIODevice::ReadOnly ))
{
QTextStream ts( &resultFile ); // default encoding is Locale
resultData = ts.readAll();
resultFile.close();
NetAccess::del( tempPathUrl, window );
}
}
}
else
{
- resultData = i18n( "ERROR: Unknown protocol '%1'", url.protocol() );
+ resultData = i18n( "ERROR: Unknown protocol '%1'", url.scheme() );
}
return resultData;
}
bool NetAccess::synchronousRunInternal( Job* job, QWidget* window, QByteArray* data,
KUrl* finalURL, QMap<QString,QString>* metaData )
{
if ( job->ui() ) job->ui()->setWindow( window );
d->m_metaData = metaData;
if ( d->m_metaData ) {
for ( QMap<QString, QString>::iterator it = d->m_metaData->begin(); it != d->m_metaData->end(); ++it ) {
job->addMetaData( it.key(), it.value() );
}
}
if ( finalURL ) {
SimpleJob *sj = qobject_cast<SimpleJob*>( job );
if ( sj ) {
d->m_url = sj->url();
}
}
connect( job, SIGNAL( result (KJob *) ),
this, SLOT( slotResult (KJob *) ) );
const QMetaObject* meta = job->metaObject();
static const char dataSignal[] = "data(KIO::Job*,QByteArray)";
if ( meta->indexOfSignal( dataSignal ) != -1 ) {
connect( job, SIGNAL(data(KIO::Job*,const QByteArray&)),
this, SLOT(slotData(KIO::Job*,const QByteArray&)) );
}
static const char redirSignal[] = "redirection(KIO::Job*,KUrl)";
if ( meta->indexOfSignal( redirSignal ) != -1 ) {
connect( job, SIGNAL(redirection(KIO::Job*,const KUrl&)),
this, SLOT(slotRedirection(KIO::Job*, const KUrl&)) );
}
enter_loop();
if ( finalURL )
*finalURL = d->m_url;
if ( data )
*data = d->m_data;
return d->bJobOK;
}
void NetAccess::enter_loop()
{
QEventLoop eventLoop;
connect(this, SIGNAL(leaveModality()),
&eventLoop, SLOT(quit()));
eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
}
void NetAccess::slotResult( KJob * job )
{
lastErrorCode = job->error();
d->bJobOK = !job->error();
if ( !d->bJobOK )
{
if ( !lastErrorMsg )
lastErrorMsg = new QString;
*lastErrorMsg = job->errorString();
}
KIO::StatJob* statJob = qobject_cast<KIO::StatJob *>( job );
if ( statJob )
d->m_entry = statJob->statResult();
KIO::Job* kioJob = qobject_cast<KIO::Job *>( job );
if ( kioJob && d->m_metaData )
*d->m_metaData = kioJob->metaData();
emit leaveModality();
}
void NetAccess::slotData( KIO::Job*, const QByteArray& data )
{
if ( data.isEmpty() )
return;
unsigned offset = d->m_data.size();
d->m_data.resize( offset + data.size() );
std::memcpy( d->m_data.data() + offset, data.data(), data.size() );
}
void NetAccess::slotRedirection( KIO::Job*, const KUrl& url )
{
d->m_url = url;
}
diff --git a/kio/kio/previewjob.cpp b/kio/kio/previewjob.cpp
index c6e98f38f0..9ffd3e2be0 100644
--- a/kio/kio/previewjob.cpp
+++ b/kio/kio/previewjob.cpp
@@ -1,795 +1,795 @@
// -*- c++ -*-
// vim: ts=4 sw=4 et
/* This file is part of the KDE libraries
Copyright (C) 2000 David Faure <faure@kde.org>
2000 Carsten Pfeiffer <pfeiffer@kde.org>
2001 Malte Starostik <malte.starostik@t-online.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 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 "previewjob.h"
#include <kdebug.h>
#include <sys/stat.h>
#include <sys/types.h>
#ifdef Q_OS_UNIX
#include <sys/ipc.h>
#include <sys/shm.h>
#endif
#include <QtCore/QDir>
#include <QtCore/QFile>
#include <QImage>
#include <QtCore/QTimer>
#include <QtCore/QRegExp>
#include <qtemporaryfile.h>
#include <QtCore/QCryptographicHash>
#include <kfileitem.h>
#include <kde_file.h>
#include <kservicetypetrader.h>
#include <kglobal.h>
#include <kstandarddirs.h>
#include <kservice.h>
#include <QtCore/QLinkedList>
#include <kconfiggroup.h>
#include <kprotocolinfo.h>
#include "jobuidelegate.h"
#include "job_p.h"
namespace KIO { struct PreviewItem; }
using namespace KIO;
struct KIO::PreviewItem
{
KFileItem item;
KService::Ptr plugin;
};
class KIO::PreviewJobPrivate: public KIO::JobPrivate
{
public:
enum { STATE_STATORIG, // if the thumbnail exists
STATE_GETORIG, // if we create it
STATE_CREATETHUMB // thumbnail:/ slave
} state;
PreviewJob *q;
KFileItemList initialItems;
QStringList enabledPlugins;
// Some plugins support remote URLs, <protocol, mimetypes>
QHash<QString, QStringList> m_remoteProtocolPlugins;
// Our todo list :)
// We remove the first item at every step, so use QLinkedList
QLinkedList<PreviewItem> items;
// The current item
PreviewItem currentItem;
// The modification time of that URL
time_t tOrig;
// Path to thumbnail cache for the current size
QString thumbPath;
// Original URL of current item in TMS format
// (file:///path/to/file instead of file:/path/to/file)
QString origName;
// Thumbnail file name for current item
QString thumbName;
// Size of thumbnail
int width;
int height;
// Unscaled size of thumbnail (128 or 256 if cache is enabled)
int cacheWidth;
int cacheHeight;
// Whether the thumbnail should be scaled
bool bScale;
// Whether we should save the thumbnail
bool bSave;
bool ignoreMaximumSize;
int sequenceIndex;
bool succeeded;
// If the file to create a thumb for was a temp file, this is its name
QString tempName;
KIO::filesize_t maximumLocalSize;
KIO::filesize_t maximumRemoteSize;
// the size for the icon overlay
int iconSize;
// the transparency of the blended mimetype icon
int iconAlpha;
// Shared memory segment Id. The segment is allocated to a size
// of extent x extent x 4 (32 bit image) on first need.
int shmid;
// And the data area
uchar *shmaddr;
// Root of thumbnail cache
QString thumbRoot;
void getOrCreateThumbnail();
bool statResultThumbnail();
void createThumbnail( const QString& );
void determineNextFile();
void emitPreview(const QImage &thumb);
void startPreview();
void slotThumbData(KIO::Job *, const QByteArray &);
Q_DECLARE_PUBLIC(PreviewJob)
};
#ifndef KDE_NO_DEPRECATED
PreviewJob::PreviewJob( const KFileItemList &items, int width, int height,
int iconSize, int iconAlpha, bool scale, bool save,
const QStringList *enabledPlugins )
: KIO::Job(*new PreviewJobPrivate)
{
Q_D(PreviewJob);
d->tOrig = 0;
d->shmid = -1;
d->shmaddr = 0;
d->initialItems = items;
d->enabledPlugins = enabledPlugins ? *enabledPlugins : availablePlugins();
d->width = width;
d->height = height ? height : width;
d->cacheWidth = d->width;
d->cacheHeight = d->height;
d->iconSize = iconSize;
d->iconAlpha = iconAlpha;
d->bScale = scale;
d->bSave = save && scale;
d->succeeded = false;
d->thumbRoot = QDir::homePath() + QLatin1String("/.thumbnails/");
d->ignoreMaximumSize = false;
d->sequenceIndex = 0;
d->maximumLocalSize = 0;
d->maximumRemoteSize = 0;
// Return to event loop first, determineNextFile() might delete this;
QTimer::singleShot(0, this, SLOT(startPreview()));
}
#endif
PreviewJob::PreviewJob(const KFileItemList &items,
const QSize &size,
const QStringList *enabledPlugins) :
KIO::Job(*new PreviewJobPrivate)
{
Q_D(PreviewJob);
d->tOrig = 0;
d->shmid = -1;
d->shmaddr = 0;
d->initialItems = items;
if (enabledPlugins) {
d->enabledPlugins = *enabledPlugins;
} else {
const KConfigGroup globalConfig(KGlobal::config(), "PreviewSettings");
d->enabledPlugins = globalConfig.readEntry("Plugins", QStringList()
<< "directorythumbnail"
<< "imagethumbnail"
<< "jpegthumbnail");
}
d->width = size.width();
d->height = size.height();
d->cacheWidth = d->width;
d->cacheHeight = d->height;
d->iconSize = 0;
d->iconAlpha = 70;
d->bScale = true;
d->bSave = true;
d->succeeded = false;
d->thumbRoot = QDir::homePath() + QLatin1String("/.thumbnails/");
d->ignoreMaximumSize = false;
d->sequenceIndex = 0;
d->maximumLocalSize = 0;
d->maximumRemoteSize = 0;
// Return to event loop first, determineNextFile() might delete this;
QTimer::singleShot(0, this, SLOT(startPreview()));
}
PreviewJob::~PreviewJob()
{
#ifdef Q_OS_UNIX
Q_D(PreviewJob);
if (d->shmaddr) {
shmdt((char*)d->shmaddr);
shmctl(d->shmid, IPC_RMID, 0);
}
#endif
}
void PreviewJob::setOverlayIconSize(int size)
{
Q_D(PreviewJob);
d->iconSize = size;
}
int PreviewJob::overlayIconSize() const
{
Q_D(const PreviewJob);
return d->iconSize;
}
void PreviewJob::setOverlayIconAlpha(int alpha)
{
Q_D(PreviewJob);
d->iconAlpha = qBound(0, alpha, 255);
}
int PreviewJob::overlayIconAlpha() const
{
Q_D(const PreviewJob);
return d->iconAlpha;
}
void PreviewJob::setScaleType(ScaleType type)
{
Q_D(PreviewJob);
switch (type) {
case Unscaled:
d->bScale = false;
d->bSave = false;
break;
case Scaled:
d->bScale = true;
d->bSave = false;
break;
case ScaledAndCached:
d->bScale = true;
d->bSave = true;
break;
default:
break;
}
}
PreviewJob::ScaleType PreviewJob::scaleType() const
{
Q_D(const PreviewJob);
if (d->bScale) {
return d->bSave ? ScaledAndCached : Scaled;
}
return Unscaled;
}
void PreviewJobPrivate::startPreview()
{
Q_Q(PreviewJob);
// Load the list of plugins to determine which mimetypes are supported
const KService::List plugins = KServiceTypeTrader::self()->query("ThumbCreator");
QMap<QString, KService::Ptr> mimeMap;
for (KService::List::ConstIterator it = plugins.constBegin(); it != plugins.constEnd(); ++it) {
const QStringList protocols = (*it)->property("X-KDE-Protocol").toStringList();
foreach (const QString &protocol, protocols) {
QStringList mtypes = (*it)->serviceTypes();
// Filter out non-mimetype servicetypes
// TODO KDE5: use KService::mimeTypes()
foreach (const QString &_mtype, mtypes) {
if (!((*it)->hasMimeType(_mtype))) {
mtypes.removeAll(_mtype);
}
}
// Add already existing protocols, so more than one plugin can
// support a given scheme + mimetype
QStringList &_ms = m_remoteProtocolPlugins[protocol];
foreach (const QString &_m, mtypes) {
if (!_ms.contains(_m)) {
_ms.append(_m);
}
}
}
if (enabledPlugins.contains((*it)->desktopEntryName())) {
const QStringList mimeTypes = (*it)->serviceTypes();
for (QStringList::ConstIterator mt = mimeTypes.constBegin(); mt != mimeTypes.constEnd(); ++mt)
mimeMap.insert(*mt, *it);
}
}
// Look for images and store the items in our todo list :)
bool bNeedCache = false;
KFileItemList::const_iterator kit = initialItems.constBegin();
const KFileItemList::const_iterator kend = initialItems.constEnd();
for ( ; kit != kend; ++kit )
{
PreviewItem item;
item.item = *kit;
const QString mimeType = item.item.mimetype();
QMap<QString, KService::Ptr>::ConstIterator plugin = mimeMap.constFind(mimeType);
if (plugin == mimeMap.constEnd())
{
QString groupMimeType = mimeType;
groupMimeType.replace(QRegExp("/.*"), "/*");
plugin = mimeMap.constFind(groupMimeType);
if (plugin == mimeMap.constEnd())
{
// check mime type inheritance, resolve aliases
const KMimeType::Ptr mimeInfo = KMimeType::mimeType(mimeType, KMimeType::ResolveAliases);
if (mimeInfo) {
const QStringList parentMimeTypes = mimeInfo->allParentMimeTypes();
Q_FOREACH(const QString& parentMimeType, parentMimeTypes) {
plugin = mimeMap.constFind(parentMimeType);
if (plugin != mimeMap.constEnd()) break;
}
}
}
#if 0 // KDE4: should be covered by inheritance above, all text mimetypes inherit from text/plain
// if that's not enough, we need to invent something else
if (plugin == mimeMap.end())
{
// check X-KDE-Text property
KMimeType::Ptr mimeInfo = KMimeType::mimeType(mimeType);
QVariant textProperty = mimeInfo->property("X-KDE-text");
if (textProperty.isValid() && textProperty.type() == QVariant::Bool)
{
if (textProperty.toBool())
{
plugin = mimeMap.find("text/plain");
if (plugin == mimeMap.end())
{
plugin = mimeMap.find( "text/*" );
}
}
}
}
#endif
}
if (plugin != mimeMap.constEnd())
{
item.plugin = *plugin;
items.append(item);
if (!bNeedCache && bSave &&
- ((*kit).url().protocol() != "file" ||
+ ((*kit).url().scheme() != "file" ||
!(*kit).url().directory( KUrl::AppendTrailingSlash ).startsWith(thumbRoot)) &&
(*plugin)->property("CacheThumbnail").toBool())
bNeedCache = true;
}
else
{
emit q->failed( *kit );
}
}
KConfigGroup cg( KGlobal::config(), "PreviewSettings" );
maximumLocalSize = cg.readEntry( "MaximumSize", 5*1024*1024LL /* 5MB */ );
maximumRemoteSize = cg.readEntry( "MaximumRemoteSize", 0 );
if (bNeedCache)
{
if (width <= 128 && height <= 128) cacheWidth = cacheHeight = 128;
else cacheWidth = cacheHeight = 256;
thumbPath = thumbRoot + (cacheWidth == 128 ? "normal/" : "large/");
KStandardDirs::makeDir(thumbPath, 0700);
}
else
bSave = false;
initialItems.clear();
determineNextFile();
}
void PreviewJob::removeItem( const KUrl& url )
{
Q_D(PreviewJob);
for (QLinkedList<PreviewItem>::Iterator it = d->items.begin(); it != d->items.end(); ++it)
if ((*it).item.url() == url)
{
d->items.erase(it);
break;
}
if (d->currentItem.item.url() == url)
{
KJob* job = subjobs().first();
job->kill();
removeSubjob( job );
d->determineNextFile();
}
}
void KIO::PreviewJob::setSequenceIndex(int index) {
d_func()->sequenceIndex = index;
}
int KIO::PreviewJob::sequenceIndex() const {
return d_func()->sequenceIndex;
}
void PreviewJob::setIgnoreMaximumSize(bool ignoreSize)
{
d_func()->ignoreMaximumSize = ignoreSize;
}
void PreviewJobPrivate::determineNextFile()
{
Q_Q(PreviewJob);
if (!currentItem.item.isNull())
{
if (!succeeded)
emit q->failed( currentItem.item );
}
// No more items ?
if ( items.isEmpty() )
{
q->emitResult();
return;
}
else
{
// First, stat the orig file
state = PreviewJobPrivate::STATE_STATORIG;
currentItem = items.first();
succeeded = false;
items.removeFirst();
KIO::Job *job = KIO::stat( currentItem.item.url(), KIO::HideProgressInfo );
job->addMetaData( "no-auth-prompt", "true" );
q->addSubjob(job);
}
}
void PreviewJob::slotResult( KJob *job )
{
Q_D(PreviewJob);
removeSubjob(job);
Q_ASSERT ( !hasSubjobs() ); // We should have only one job at a time ...
switch ( d->state )
{
case PreviewJobPrivate::STATE_STATORIG:
{
if (job->error()) // that's no good news...
{
// Drop this one and move on to the next one
d->determineNextFile();
return;
}
const KIO::UDSEntry entry = static_cast<KIO::StatJob*>(job)->statResult();
d->tOrig = entry.numberValue( KIO::UDSEntry::UDS_MODIFICATION_TIME, 0 );
bool skipCurrentItem = false;
const KIO::filesize_t size = (KIO::filesize_t)entry.numberValue( KIO::UDSEntry::UDS_SIZE, 0 );
const KUrl itemUrl = d->currentItem.item.mostLocalUrl();
- if (itemUrl.isLocalFile() || KProtocolInfo::protocolClass(itemUrl.protocol()) == QLatin1String(":local"))
+ if (itemUrl.isLocalFile() || KProtocolInfo::protocolClass(itemUrl.scheme()) == QLatin1String(":local"))
{
skipCurrentItem = !d->ignoreMaximumSize && size > d->maximumLocalSize
&& !d->currentItem.plugin->property("IgnoreMaximumSize").toBool();
}
else
{
// For remote items the "IgnoreMaximumSize" plugin property is not respected
skipCurrentItem = !d->ignoreMaximumSize && size > d->maximumRemoteSize;
// Remote directories are not supported, don't try to do a file_copy on them
if (!skipCurrentItem) {
// TODO update item.mimeType from the UDS entry, in case it wasn't set initially
KMimeType::Ptr mime = d->currentItem.item.mimeTypePtr();
if (mime && mime->is("inode/directory")) {
skipCurrentItem = true;
}
}
}
if (skipCurrentItem)
{
d->determineNextFile();
return;
}
bool pluginHandlesSequences = d->currentItem.plugin->property("HandleSequences", QVariant::Bool).toBool();
if ( !d->currentItem.plugin->property( "CacheThumbnail" ).toBool() || (d->sequenceIndex && pluginHandlesSequences) )
{
// This preview will not be cached, no need to look for a saved thumbnail
// Just create it, and be done
d->getOrCreateThumbnail();
return;
}
if ( d->statResultThumbnail() )
return;
d->getOrCreateThumbnail();
return;
}
case PreviewJobPrivate::STATE_GETORIG:
{
if (job->error())
{
d->determineNextFile();
return;
}
d->createThumbnail( static_cast<KIO::FileCopyJob*>(job)->destUrl().toLocalFile() );
return;
}
case PreviewJobPrivate::STATE_CREATETHUMB:
{
if (!d->tempName.isEmpty())
{
QFile::remove(d->tempName);
d->tempName.clear();
}
d->determineNextFile();
return;
}
}
}
bool PreviewJobPrivate::statResultThumbnail()
{
if ( thumbPath.isEmpty() )
return false;
KUrl url = currentItem.item.mostLocalUrl();
// Don't include the password if any
url.setPass(QString());
origName = url.url();
QCryptographicHash md5(QCryptographicHash::Md5);
md5.addData(QFile::encodeName( origName ) );
thumbName = QFile::encodeName( md5.result().toHex() ) + ".png";
QImage thumb;
if ( !thumb.load( thumbPath + thumbName ) ) return false;
if ( thumb.text( "Thumb::URI", 0 ) != origName ||
thumb.text( "Thumb::MTime", 0 ).toInt() != tOrig ) return false;
QString thumbnailerVersion = currentItem.plugin->property("ThumbnailerVersion", QVariant::String).toString();
if (!thumbnailerVersion.isEmpty() && thumb.text("Software", 0).startsWith("KDE Thumbnail Generator")) {
//Check if the version matches
//The software string should read "KDE Thumbnail Generator pluginName (vX)"
QString softwareString = thumb.text("Software", 0).remove("KDE Thumbnail Generator").trimmed();
if (softwareString.isEmpty()) {
// The thumbnail has been created with an older version, recreating
return false;
}
int versionIndex = softwareString.lastIndexOf("(v");
if (versionIndex < 0) {
return false;
}
QString cachedVersion = softwareString.remove(0, versionIndex+2);
cachedVersion.chop(1);
uint thumbnailerMajor = thumbnailerVersion.toInt();
uint cachedMajor = cachedVersion.toInt();
if (thumbnailerMajor > cachedMajor) {
return false;
}
}
// Found it, use it
emitPreview( thumb );
succeeded = true;
determineNextFile();
return true;
}
void PreviewJobPrivate::getOrCreateThumbnail()
{
Q_Q(PreviewJob);
// We still need to load the orig file ! (This is getting tedious) :)
const KFileItem& item = currentItem.item;
const QString localPath = item.localPath();
if (!localPath.isEmpty()) {
createThumbnail( localPath );
} else {
const KUrl fileUrl = item.url();
// heuristics for remote URL support
bool supportsProtocol = false;
if (m_remoteProtocolPlugins.value(fileUrl.scheme()).contains(item.mimetype())) {
// There's a plugin supporting this protocol and mimetype
supportsProtocol = true;
} else if (m_remoteProtocolPlugins.value("KIO").contains(item.mimetype())) {
// Assume KIO understands any URL, ThumbCreator slaves who have
// X-KDE-Protocol=KIO, will get feed the remote URL directly.
supportsProtocol = true;
}
if (supportsProtocol) {
createThumbnail(fileUrl.url());
return;
}
// No plugin support access to this remote content, copy the file
// to the local machine, then create the thumbnail
state = PreviewJobPrivate::STATE_GETORIG;
QTemporaryFile localFile;
localFile.setAutoRemove(false);
localFile.open();
KUrl localURL;
localURL.setPath( tempName = localFile.fileName() );
const KUrl currentURL = item.mostLocalUrl();
KIO::Job * job = KIO::file_copy( currentURL, localURL, -1, KIO::Overwrite | KIO::HideProgressInfo /* No GUI */ );
job->addMetaData("thumbnail","1");
q->addSubjob(job);
}
}
void PreviewJobPrivate::createThumbnail( const QString &pixPath )
{
Q_Q(PreviewJob);
state = PreviewJobPrivate::STATE_CREATETHUMB;
KUrl thumbURL;
thumbURL.setProtocol("thumbnail");
thumbURL.setPath(pixPath);
KIO::TransferJob *job = KIO::get(thumbURL, NoReload, HideProgressInfo);
q->addSubjob(job);
q->connect(job, SIGNAL(data(KIO::Job *, const QByteArray &)), SLOT(slotThumbData(KIO::Job *, const QByteArray &)));
bool save = bSave && currentItem.plugin->property("CacheThumbnail").toBool() && !sequenceIndex;
job->addMetaData("mimeType", currentItem.item.mimetype());
job->addMetaData("width", QString().setNum(save ? cacheWidth : width));
job->addMetaData("height", QString().setNum(save ? cacheHeight : height));
job->addMetaData("iconSize", QString().setNum(save ? 64 : iconSize));
job->addMetaData("iconAlpha", QString().setNum(iconAlpha));
job->addMetaData("plugin", currentItem.plugin->library());
if(sequenceIndex)
job->addMetaData("sequence-index", QString().setNum(sequenceIndex));
#ifdef Q_OS_UNIX
if (shmid == -1)
{
if (shmaddr) {
shmdt((char*)shmaddr);
shmctl(shmid, IPC_RMID, 0);
}
shmid = shmget(IPC_PRIVATE, cacheWidth * cacheHeight * 4, IPC_CREAT|0600);
if (shmid != -1)
{
shmaddr = (uchar *)(shmat(shmid, 0, SHM_RDONLY));
if (shmaddr == (uchar *)-1)
{
shmctl(shmid, IPC_RMID, 0);
shmaddr = 0;
shmid = -1;
}
}
else
shmaddr = 0;
}
if (shmid != -1)
job->addMetaData("shmid", QString().setNum(shmid));
#endif
}
void PreviewJobPrivate::slotThumbData(KIO::Job *, const QByteArray &data)
{
bool save = bSave &&
currentItem.plugin->property("CacheThumbnail").toBool() &&
- (currentItem.item.url().protocol() != "file" ||
+ (currentItem.item.url().scheme() != "file" ||
!currentItem.item.url().directory( KUrl::AppendTrailingSlash ).startsWith(thumbRoot)) && !sequenceIndex;
QImage thumb;
#ifdef Q_OS_UNIX
if (shmaddr)
{
// Keep this in sync with kdebase/kioslave/thumbnail.cpp
QDataStream str(data);
int width, height;
quint8 iFormat;
str >> width >> height >> iFormat;
QImage::Format format = static_cast<QImage::Format>( iFormat );
thumb = QImage(shmaddr, width, height, format ).copy();
}
else
#endif
thumb.loadFromData(data);
if (thumb.isNull()) {
QDataStream s(data);
s >> thumb;
}
QString tempFileName;
bool savedCorrectly = false;
if (save)
{
thumb.setText("Thumb::URI", origName);
thumb.setText("Thumb::MTime", QString::number(tOrig));
thumb.setText("Thumb::Size", number(currentItem.item.size()));
thumb.setText("Thumb::Mimetype", currentItem.item.mimetype());
QString thumbnailerVersion = currentItem.plugin->property("ThumbnailerVersion", QVariant::String).toString();
QString signature = QString("KDE Thumbnail Generator "+currentItem.plugin->name());
if (!thumbnailerVersion.isEmpty()) {
signature.append(" (v"+thumbnailerVersion+')');
}
thumb.setText("Software", signature);
QTemporaryFile temp(thumbPath + "kde-tmp-XXXXXX.png");
temp.setAutoRemove(false);
if (temp.open()) //Only try to write out the thumbnail if we
{ //actually created the temp file.
tempFileName = temp.fileName();
savedCorrectly = thumb.save(tempFileName, "PNG");
}
}
if(savedCorrectly)
{
Q_ASSERT(!tempFileName.isEmpty());
KDE::rename(tempFileName, thumbPath + thumbName);
}
emitPreview( thumb );
succeeded = true;
}
void PreviewJobPrivate::emitPreview(const QImage &thumb)
{
Q_Q(PreviewJob);
QPixmap pix;
if (thumb.width() > width || thumb.height() > height)
pix = QPixmap::fromImage( thumb.scaled(QSize(width, height), Qt::KeepAspectRatio, Qt::SmoothTransformation) );
else
pix = QPixmap::fromImage( thumb );
emit q->gotPreview(currentItem.item, pix);
}
QStringList PreviewJob::availablePlugins()
{
QStringList result;
const KService::List plugins = KServiceTypeTrader::self()->query("ThumbCreator");
for (KService::List::ConstIterator it = plugins.begin(); it != plugins.end(); ++it)
if (!result.contains((*it)->desktopEntryName()))
result.append((*it)->desktopEntryName());
return result;
}
QStringList PreviewJob::supportedMimeTypes()
{
QStringList result;
const KService::List plugins = KServiceTypeTrader::self()->query("ThumbCreator");
for (KService::List::ConstIterator it = plugins.begin(); it != plugins.end(); ++it)
result += (*it)->serviceTypes();
return result;
}
#ifndef KDE_NO_DEPRECATED
PreviewJob *KIO::filePreview( const KFileItemList &items, int width, int height,
int iconSize, int iconAlpha, bool scale, bool save,
const QStringList *enabledPlugins )
{
return new PreviewJob(items, width, height, iconSize, iconAlpha,
scale, save, enabledPlugins);
}
PreviewJob *KIO::filePreview( const KUrl::List &items, int width, int height,
int iconSize, int iconAlpha, bool scale, bool save,
const QStringList *enabledPlugins )
{
KFileItemList fileItems;
for (KUrl::List::ConstIterator it = items.begin(); it != items.end(); ++it) {
Q_ASSERT( (*it).isValid() ); // please call us with valid urls only
fileItems.append(KFileItem(KFileItem::Unknown, KFileItem::Unknown, *it, true));
}
return new PreviewJob(fileItems, width, height, iconSize, iconAlpha,
scale, save, enabledPlugins);
}
#endif
PreviewJob *KIO::filePreview(const KFileItemList &items, const QSize &size, const QStringList *enabledPlugins)
{
return new PreviewJob(items, size, enabledPlugins);
}
#ifndef KDE_NO_DEPRECATED
KIO::filesize_t PreviewJob::maximumFileSize()
{
KConfigGroup cg( KGlobal::config(), "PreviewSettings" );
return cg.readEntry( "MaximumSize", 5*1024*1024LL /* 5MB */ );
}
#endif
#include "moc_previewjob.cpp"
diff --git a/kio/kssl/ksslcsessioncache.cpp b/kio/kssl/ksslcsessioncache.cpp
index fb2e9fb10a..586003e5a7 100644
--- a/kio/kssl/ksslcsessioncache.cpp
+++ b/kio/kssl/ksslcsessioncache.cpp
@@ -1,120 +1,120 @@
/* This file is part of the KDE project
*
* Copyright (C) 2003 Stefan Rompf <sux@loplof.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 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 "ksslcsessioncache.h"
#include <QtCore/QCoreApplication>
#include <QtCore/QPair>
#include <QtCore/QString>
#include <kdebug.h>
#include <kurl.h>
#include <ksslconfig.h>
/*
* Operation:
*
* Sessions will be stored per running application, not KDE
* wide, to avoid security problems with hostile programs
* that negotiate sessions with weak cryptographic keys and store
* them for everybody to use - I really don't want that.
*
* Retrieval is organized similar to George's thoughts in the KSSLD
* certificate cache: The cache is organised as a list, with the
* recently fetched (or stored) session first.
*
* The cache has an artificial limit of 32 sessions (should really
* be enough), and relies on the peer server for timeouts
*
*/
#define MAX_ENTRIES 32
#ifdef KSSL_HAVE_SSL
typedef QPair<QString,QString> KSSLCSession;
typedef QList<KSSLCSession> KSSLCSessions;
static KSSLCSessions *sessions = 0L;
static QString URLtoKey(const KUrl &kurl) {
- return kurl.host() + ':' + kurl.protocol() + ':' + QString::number(kurl.port());
+ return kurl.host() + ':' + kurl.scheme() + ':' + QString::number(kurl.port());
}
static void cleanupKSSLCSessions() {
delete sessions;
sessions = 0;
}
static void setup() {
sessions = new KSSLCSessions;
qAddPostRoutine(cleanupKSSLCSessions);
}
#endif
QString KSSLCSessionCache::getSessionForUrl(const KUrl &kurl) {
#ifdef KSSL_HAVE_SSL
if (!sessions) return QString();
QString key = URLtoKey(kurl);
for (int i = 0; i < sessions->size(); ++i) {
if (sessions->at(i).first == key) {
QString snd = sessions->at(i).second;
sessions->prepend(sessions->takeAt(i));
return snd;
}
}
// Negative caching disabled: cache pollution
#if 0
kDebug(7029) <<"Negative caching " <<key;
if (sessions->count() >= MAX_ENTRIES) sessions->removeLast();
sessions->prepend(new KSSLCSession(key, QString()));
#endif
#endif
return QString();
}
void KSSLCSessionCache::putSessionForUrl(const KUrl &kurl, const QString &session) {
#ifdef KSSL_HAVE_SSL
if (!sessions) setup();
QString key = URLtoKey(kurl);
KSSLCSessions::iterator it = sessions->begin();
while ( it != sessions->end() ) {
if ( it->first == key )
break;
++it;
}
if (it != sessions->end()) {
it->second = session;
} else {
if (sessions->size() >= MAX_ENTRIES)
sessions->removeLast();
sessions->prepend(KSSLCSession(key, session));
}
#endif
}
diff --git a/kio/misc/ktelnetservice.cpp b/kio/misc/ktelnetservice.cpp
index 86333a539d..acb27f551b 100644
--- a/kio/misc/ktelnetservice.cpp
+++ b/kio/misc/ktelnetservice.cpp
@@ -1,108 +1,108 @@
//krazy:excludeall=license (it's a program, not a library)
/*
Copyright (c) 2001 Malte Starostik <malte@kde.org>
based on kmailservice.cpp,
Copyright (c) 2000 Simon Hausmann <hausmann@kde.org>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include <kapplication.h>
#include <ktoolinvocation.h>
#include <kauthorized.h>
#include <kmessagebox.h>
#include <kcmdlineargs.h>
#include <kdebug.h>
#include <klocale.h>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <kurl.h>
int main(int argc, char **argv)
{
KCmdLineOptions options;
options.add("+url");
KCmdLineArgs::init(argc, argv, "ktelnetservice", "kdelibs4", qi18n("telnet service"),
"unknown", qi18n("telnet protocol handler"));
KCmdLineArgs::addCmdLineOptions(options);
KApplication app;
KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
if (args->count() != 1)
return 1;
KConfig config("kdeglobals");
KConfigGroup cg(&config, "General");
QString terminal = cg.readPathEntry("TerminalApplication", "konsole");
KUrl url(args->arg(0));
QStringList cmd;
if (terminal == "konsole")
cmd << "--noclose";
cmd << "-e";
- if ( url.protocol() == "telnet" )
+ if ( url.scheme() == "telnet" )
cmd << "telnet";
- else if ( url.protocol() == "ssh" )
+ else if ( url.scheme() == "ssh" )
cmd << "ssh";
- else if ( url.protocol() == "rlogin" )
+ else if ( url.scheme() == "rlogin" )
cmd << "rlogin";
else {
- kError() << "Invalid protocol " << url.protocol() << endl;
+ kError() << "Invalid protocol " << url.scheme() << endl;
return 2;
}
if (!KAuthorized::authorize("shell_access"))
{
KMessageBox::sorry(0,
- i18n("You do not have permission to access the %1 protocol.", url.protocol()));
+ i18n("You do not have permission to access the %1 protocol.", url.scheme()));
return 3;
}
if (!url.user().isEmpty())
{
cmd << "-l";
cmd << url.user();
}
QString host;
if (!url.host().isEmpty())
host = url.host(); // telnet://host
else if (!url.path().isEmpty())
host = url.path(); // telnet:host
if (host.isEmpty() || host.startsWith('-'))
{
kError() << "Invalid hostname " << host << endl;
return 2;
}
cmd << host;
if (url.port() > 0){
- if ( url.protocol() == "ssh" )
+ if ( url.scheme() == "ssh" )
cmd << "-p" << QString::number(url.port());
else
cmd << QString::number(url.port());
}
KToolInvocation::kdeinitExec(terminal, cmd);
return 0;
}
diff --git a/kioslave/http/http.cpp b/kioslave/http/http.cpp
index a1d3c810d2..b3da98a195 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.protocol() == QLatin1String("http");
+ 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.protocol();
+ 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.protocol() == QLatin1String("webdavs"))
+ if (newDest.scheme() == QLatin1String("webdavs"))
newDest.setProtocol(QLatin1String("https"));
- else if (newDest.protocol() == QLatin1String("webdav"))
+ 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.protocol() == QLatin1String("webdavs"))
+ if (newDest.scheme() == QLatin1String("webdavs"))
newDest.setProtocol(QLatin1String("https"));
- else if (newDest.protocol() == QLatin1String("webdav"))
+ 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;
Q_FOREACH(const QString& proxyUrl, m_request.proxyUrls) {
const KUrl url (proxyUrl);
- const QString scheme (url.protocol());
+ 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.protocol() == QLatin1String("socks")) {
+ 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.protocol();
+ 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.protocol() == u.protocol()))
+ (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.protocol() == QLatin1String("http")){
+ if(u.scheme() == QLatin1String("http")){
u.setProtocol(QLatin1String("webdav"));
- }else if(u.protocol() == QLatin1String("https")){
+ }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 10d5397ab7..32f18390b3 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;
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.protocol().toLower() == u.protocol().toLower());
+ 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/kioslave/http/kcookiejar/kcookiejar.cpp b/kioslave/http/kcookiejar/kcookiejar.cpp
index 5a67ee3228..217c01820d 100644
--- a/kioslave/http/kcookiejar/kcookiejar.cpp
+++ b/kioslave/http/kcookiejar/kcookiejar.cpp
@@ -1,1534 +1,1534 @@
/* This file is part of the KDE File Manager
Copyright (C) 1998-2000 Waldo Bastian (bastian@kde.org)
Copyright (C) 2000,2001 Dawit Alemayehu (adawit@kde.org)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, and/or sell copies of the
Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
//----------------------------------------------------------------------------
//
// KDE File Manager -- HTTP Cookies
//
// The cookie protocol is a mess. RFC2109 is a joke since nobody seems to
// use it. Apart from that it is badly written.
// We try to implement Netscape Cookies and try to behave us according to
// RFC2109 as much as we can.
//
// We assume cookies do not contain any spaces (Netscape spec.)
// According to RFC2109 this is allowed though.
//
#include "kcookiejar.h"
#include <kurl.h>
#include <kdatetime.h>
#include <ksystemtimezone.h>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <ksavefile.h>
#include <kdebug.h>
#include <QtCore/QString>
#include <QtCore/QFile>
#include <QtCore/QDir>
#include <QtCore/QRegExp>
#include <QtCore/QTextStream>
// BR87227
// Waba: Should the number of cookies be limited?
// I am not convinced of the need of such limit
// Mozilla seems to limit to 20 cookies / domain
// but it is unclear which policy it uses to expire
// cookies when it exceeds that amount
#undef MAX_COOKIE_LIMIT
#define MAX_COOKIES_PER_HOST 25
#define READ_BUFFER_SIZE 8192
#define IP_ADDRESS_EXPRESSION "(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"
// Note with respect to QLatin1String( )....
// Cookies are stored as 8 bit data and passed to kio_http as Latin1
// regardless of their actual encoding.
#define QL1S(x) QLatin1String(x)
#define QL1C(x) QLatin1Char(x)
static KDateTime parseDate(const QString& value)
{
KTimeZones *zones = KSystemTimeZones::timeZones();
// Check for the most common cookie expire date format: Thu, 01-Jan-1970 00:00:00 GMT
KDateTime dt = KDateTime::fromString(value, QL1S("%:A,%t%d-%:B-%Y%t%H:%M:%S%t%Z"), zones);
if (dt.isValid())
return dt;
// Check for incorrect formats (amazon.com): Thu Jan 01 1970 00:00:00 GMT
dt = KDateTime::fromString(value, QL1S("%:A%t%:B%t%d%t%Y%t%H:%M:%S%t%Z"), zones);
if (dt.isValid())
return dt;
// Check for a variation of the above format: Thu Jan 01 00:00:00 1970 GMT (BR# 145244)
dt = KDateTime::fromString(value, QL1S("%:A%t%:B%t%d%t%H:%M:%S%t%Y%t%Z"), zones);
if (dt.isValid())
return dt;
// Finally we try the RFC date formats as last resort
return KDateTime::fromString(value, KDateTime::RFCDate);
}
static qint64 epoch()
{
KDateTime epoch;
epoch.setTime_t(0);
return epoch.secsTo_long(KDateTime::currentUtcDateTime());
}
QString KCookieJar::adviceToStr(KCookieAdvice _advice)
{
switch( _advice )
{
case KCookieAccept: return QL1S("Accept");
case KCookieReject: return QL1S("Reject");
case KCookieAsk: return QL1S("Ask");
default: return QL1S("Dunno");
}
}
KCookieAdvice KCookieJar::strToAdvice(const QString &_str)
{
if (_str.isEmpty())
return KCookieDunno;
QString advice = _str.toLower();
if (advice == QL1S("accept"))
return KCookieAccept;
else if (advice == QL1S("reject"))
return KCookieReject;
else if (advice == QL1S("ask"))
return KCookieAsk;
return KCookieDunno;
}
// KHttpCookie
///////////////////////////////////////////////////////////////////////////
//
// Cookie constructor
//
KHttpCookie::KHttpCookie(const QString &_host,
const QString &_domain,
const QString &_path,
const QString &_name,
const QString &_value,
qint64 _expireDate,
int _protocolVersion,
bool _secure,
bool _httpOnly,
bool _explicitPath) :
mHost(_host),
mDomain(_domain),
mPath(_path.isEmpty() ? QString() : _path),
mName(_name),
mValue(_value),
mExpireDate(_expireDate),
mProtocolVersion(_protocolVersion),
mSecure(_secure),
mHttpOnly(_httpOnly),
mExplicitPath(_explicitPath)
{
}
//
// Checks if a cookie has been expired
//
bool KHttpCookie::isExpired(qint64 currentDate) const
{
if (currentDate == -1)
currentDate = epoch();
return (mExpireDate != 0) && (mExpireDate < currentDate);
}
//
// Returns a string for a HTTP-header
//
QString KHttpCookie::cookieStr(bool useDOMFormat) const
{
QString result;
if (useDOMFormat || (mProtocolVersion == 0)) {
if ( mName.isEmpty() )
result = mValue;
else
result = mName + QL1C('=') + mValue;
} else {
result = mName + QL1C('=') + mValue;
if (mExplicitPath)
result += QL1S("; $Path=\"") + mPath + QL1C('"');
if (!mDomain.isEmpty())
result += QL1S("; $Domain=\"") + mDomain + QL1C('"');
if (!mPorts.isEmpty()) {
if (mPorts.length() == 2 && mPorts.at(0) == -1)
result += QL1S("; $Port");
else {
QString portNums;
Q_FOREACH(int port, mPorts)
portNums += QString::number(port) + QL1C(' ');
result += QL1S("; $Port=\"") + portNums.trimmed() + QL1C('"');
}
}
}
return result;
}
//
// Returns whether this cookie should be send to this location.
bool KHttpCookie::match(const QString &fqdn, const QStringList &domains,
const QString &path, int port) const
{
// Cookie domain match check
if (mDomain.isEmpty())
{
if (fqdn != mHost)
return false;
}
else if (!domains.contains(mDomain))
{
if (mDomain[0] == '.')
return false;
// Maybe the domain needs an extra dot.
const QString domain = QL1C('.') + mDomain;
if ( !domains.contains( domain ) )
if ( fqdn != mDomain )
return false;
}
else if (mProtocolVersion != 0 && port != -1 &&
!mPorts.isEmpty() && !mPorts.contains(port))
{
return false;
}
// Cookie path match check
if (mPath.isEmpty())
return true;
// According to the netscape spec http://www.acme.com/foobar,
// http://www.acme.com/foo.bar and http://www.acme.com/foo/bar
// should all match http://www.acme.com/foo...
// We only match http://www.acme.com/foo/bar
if( path.startsWith(mPath) &&
(
(path.length() == mPath.length() ) || // Paths are exact match
mPath.endsWith(QL1C('/')) || // mPath ended with a slash
(path[mPath.length()] == QL1C('/')) // A slash follows
))
return true; // Path of URL starts with cookie-path
return false;
}
// KCookieJar
///////////////////////////////////////////////////////////////////////////
//
// Constructs a new cookie jar
//
// One jar should be enough for all cookies.
//
KCookieJar::KCookieJar()
{
m_globalAdvice = KCookieDunno;
m_configChanged = false;
m_cookiesChanged = false;
KConfig cfg( "khtml/domain_info", KConfig::NoGlobals, "data" );
KConfigGroup group( &cfg, QString() );
m_gTLDs = QSet<QString>::fromList(group.readEntry("gTLDs", QStringList()));
m_twoLevelTLD = QSet<QString>::fromList(group.readEntry("twoLevelTLD", QStringList()));
}
//
// Destructs the cookie jar
//
// Poor little cookies, they will all be eaten by the cookie monster!
//
KCookieJar::~KCookieJar()
{
qDeleteAll(m_cookieDomains);
// Not much to do here
}
// cookiePtr is modified: the window ids of the existing cookie in the list are added to it
static void removeDuplicateFromList(KHttpCookieList *list, KHttpCookie& cookiePtr, bool nameMatchOnly=false, bool updateWindowId=false)
{
QString domain1 = cookiePtr.domain();
if (domain1.isEmpty())
domain1 = cookiePtr.host();
QMutableListIterator<KHttpCookie> cookieIterator(*list);
while (cookieIterator.hasNext()) {
const KHttpCookie& cookie = cookieIterator.next();
QString domain2 = cookie.domain();
if (domain2.isEmpty())
domain2 = cookie.host();
if (cookiePtr.name() == cookie.name() &&
(nameMatchOnly || (domain1 == domain2 && cookiePtr.path() == cookie.path())))
{
if (updateWindowId) {
Q_FOREACH(long windowId, cookie.windowIds()) {
if (windowId && (!cookiePtr.windowIds().contains(windowId))) {
cookiePtr.windowIds().append(windowId);
}
}
}
cookieIterator.remove();
break;
}
}
}
//
// Looks for cookies in the cookie jar which are appropriate for _url.
// Returned is a string containing all appropriate cookies in a format
// which can be added to a HTTP-header without any additional processing.
//
QString KCookieJar::findCookies(const QString &_url, bool useDOMFormat, long windowId, KHttpCookieList *pendingCookies)
{
QString cookieStr, fqdn, path;
QStringList domains;
int port = -1;
KCookieAdvice advice = m_globalAdvice;
if (!parseUrl(_url, fqdn, path, &port))
return cookieStr;
const bool secureRequest = (_url.startsWith(QL1S("https://"), Qt::CaseInsensitive) ||
_url.startsWith(QL1S("webdavs://"), Qt::CaseInsensitive));
if (port == -1)
port = (secureRequest ? 443 : 80);
extractDomains(fqdn, domains);
KHttpCookieList allCookies;
for (QStringList::ConstIterator it = domains.constBegin(), itEnd = domains.constEnd();;++it)
{
KHttpCookieList *cookieList = 0;
if (it == itEnd)
{
cookieList = pendingCookies; // Add pending cookies
pendingCookies = 0;
if (!cookieList)
break;
}
else
{
if ((*it).isNull())
cookieList = m_cookieDomains.value(QL1S(""));
else
cookieList = m_cookieDomains.value(*it);
if (!cookieList)
continue; // No cookies for this domain
}
if (cookieList->getAdvice() != KCookieDunno)
advice = cookieList->getAdvice();
QMutableListIterator<KHttpCookie> cookieIt (*cookieList);
while (cookieIt.hasNext())
{
KHttpCookie& cookie = cookieIt.next();
// If the we are setup to automatically accept all session cookies and to
// treat all cookies as session cookies or the current cookie is a session
// cookie, then send the cookie back regardless of domain policy.
if (advice == KCookieReject &&
!(m_autoAcceptSessionCookies &&
(m_ignoreCookieExpirationDate || cookie.expireDate() == 0)))
continue;
if (!cookie.match(fqdn, domains, path, port))
continue;
if( cookie.isSecure() && !secureRequest )
continue;
if( cookie.isHttpOnly() && useDOMFormat )
continue;
// Do not send expired cookies.
if ( cookie.isExpired())
{
// NOTE: there is no need to delete the cookie here because the
// cookieserver will invoke its saveCookieJar function as a result
// of the state change below. This will then result in the cookie
// being deleting at that point.
m_cookiesChanged = true;
continue;
}
if (windowId && (cookie.windowIds().indexOf(windowId) == -1))
cookie.windowIds().append(windowId);
if (it == itEnd) // Only needed when processing pending cookies
removeDuplicateFromList(&allCookies, cookie);
allCookies.append(cookie);
}
if (it == itEnd)
break; // Finished.
}
int protVersion = 0;
Q_FOREACH(const KHttpCookie& cookie, allCookies) {
if (cookie.protocolVersion() > protVersion)
protVersion = cookie.protocolVersion();
}
if (!allCookies.isEmpty())
{
if (!useDOMFormat)
cookieStr = QL1S("Cookie: ");
if (protVersion > 0)
cookieStr = cookieStr + QL1S("$Version=") + QString::number(protVersion) + QL1S("; ");
Q_FOREACH(const KHttpCookie& cookie, allCookies)
cookieStr = cookieStr + cookie.cookieStr(useDOMFormat) + QL1S("; ");
cookieStr.truncate(cookieStr.length() - 2); // Remove the trailing ';'
}
return cookieStr;
}
//
// This function parses a string like 'my_name="my_value";' and returns
// 'my_name' in Name and 'my_value' in Value.
//
// A pointer to the end of the parsed part is returned.
// This pointer points either to:
// '\0' - The end of the string has reached.
// ';' - Another my_name="my_value" pair follows
// ',' - Another cookie follows
// '\n' - Another header follows
static const char * parseNameValue(const char *header,
QString &Name,
QString &Value,
bool keepQuotes=false,
bool rfcQuotes=false)
{
const char *s = header;
// Parse 'my_name' part
for(; (*s != '='); s++)
{
if ((*s=='\0') || (*s==';') || (*s=='\n'))
{
// No '=' sign -> use string as the value, name is empty
// (behavior found in Mozilla and IE)
Name = QL1S("");
Value = QL1S(header);
Value.truncate( s - header );
Value = Value.trimmed();
return s;
}
}
Name = QL1S(header);
Name.truncate( s - header );
Name = Name.trimmed();
// *s == '='
s++;
// Skip any whitespace
for(; (*s == ' ') || (*s == '\t'); s++)
{
if ((*s=='\0') || (*s==';') || (*s=='\n'))
{
// End of Name
Value = "";
return s;
}
}
if ((rfcQuotes || !keepQuotes) && (*s == '\"'))
{
// Parse '"my_value"' part (quoted value)
if (keepQuotes)
header = s++;
else
header = ++s; // skip "
for(;(*s != '\"');s++)
{
if ((*s=='\0') || (*s=='\n'))
{
// End of Name
Value = QL1S(header);
Value.truncate(s - header);
return s;
}
}
Value = QL1S(header);
// *s == '\"';
if (keepQuotes)
Value.truncate( ++s - header );
else
Value.truncate( s++ - header );
// Skip any remaining garbage
for(;; s++)
{
if ((*s=='\0') || (*s==';') || (*s=='\n'))
break;
}
}
else
{
// Parse 'my_value' part (unquoted value)
header = s;
while ((*s != '\0') && (*s != ';') && (*s != '\n'))
s++;
// End of Name
Value = QL1S(header);
Value.truncate( s - header );
Value = Value.trimmed();
}
return s;
}
void KCookieJar::stripDomain(const QString &_fqdn, QString &_domain)
{
QStringList domains;
extractDomains(_fqdn, domains);
if (domains.count() > 3)
_domain = domains[3];
else if ( domains.count() > 0 )
_domain = domains[0];
else
_domain = QL1S("");
}
QString KCookieJar::stripDomain(const KHttpCookie& cookie)
{
QString domain; // We file the cookie under this domain.
if (cookie.domain().isEmpty())
stripDomain( cookie.host(), domain);
else
stripDomain( cookie.domain(), domain);
return domain;
}
bool KCookieJar::parseUrl(const QString &_url,
QString &_fqdn,
QString &_path,
int *port)
{
KUrl kurl(_url);
- if (!kurl.isValid() || kurl.protocol().isEmpty())
+ if (!kurl.isValid() || kurl.scheme().isEmpty())
return false;
_fqdn = kurl.host().toLower();
// Cookie spoofing protection. Since there is no way a path separator,
// a space or the escape encoding character is allowed in the hostname
// according to RFC 2396, reject attempts to include such things there!
if (_fqdn.contains(QL1C('/')) || _fqdn.contains(QL1C('%')))
return false; // deny everything!!
// Set the port number from the protocol when one is found...
if (port)
*port = kurl.port();
_path = kurl.path();
if (_path.isEmpty())
_path = QL1S("/");
return true;
}
// not static because it uses m_twoLevelTLD
void KCookieJar::extractDomains(const QString &_fqdn,
QStringList &_domains) const
{
if (_fqdn.isEmpty()) {
_domains.append( QL1S("localhost") );
return;
}
// Return numeric IPv6 addresses as is...
if (_fqdn[0] == '[')
{
_domains.append( _fqdn );
return;
}
// Return numeric IPv4 addresses as is...
if (_fqdn[0] >= '0' && _fqdn[0] <= '9' && _fqdn.indexOf(QRegExp(IP_ADDRESS_EXPRESSION)) > -1)
{
_domains.append( _fqdn );
return;
}
// Always add the FQDN at the start of the list for
// hostname == cookie-domainname checks!
_domains.append(_fqdn);
_domains.append(QL1C('.') + _fqdn);
QStringList partList = _fqdn.split(QL1C('.'), QString::SkipEmptyParts);
if (partList.count())
partList.erase(partList.begin()); // Remove hostname
while(partList.count())
{
if (partList.count() == 1)
break; // We only have a TLD left.
if ((partList.count() == 2) && m_twoLevelTLD.contains(partList[1].toLower()))
{
// This domain uses two-level TLDs in the form xxxx.yy
break;
}
if ((partList.count() == 2) && (partList[1].length() == 2))
{
// If this is a TLD, we should stop. (e.g. co.uk)
// We assume this is a TLD if it ends with .xx.yy or .x.yy
if (partList[0].length() <= 2)
break; // This is a TLD.
// Catch some TLDs that we miss with the previous check
// e.g. com.au, org.uk, mil.co
if (m_gTLDs.contains(partList[0].toLower()))
break;
}
QString domain = partList.join(QL1S("."));
_domains.append(domain);
_domains.append(QL1C('.') + domain);
partList.erase(partList.begin()); // Remove part
}
}
//
// This function parses cookie_headers and returns a linked list of
// KHttpCookie objects for all cookies found in cookie_headers.
// If no cookies could be found 0 is returned.
//
// cookie_headers should be a concatenation of all lines of a HTTP-header
// which start with "Set-Cookie". The lines should be separated by '\n's.
//
KHttpCookieList KCookieJar::makeCookies(const QString &_url,
const QByteArray &cookie_headers,
long windowId)
{
QString fqdn, path;
if (!parseUrl(_url, fqdn, path))
return KHttpCookieList(); // Error parsing _url
QString Name, Value;
KHttpCookieList cookieList, cookieList2;
bool isRFC2965 = false;
bool crossDomain = false;
const char *cookieStr = cookie_headers.constData();
QString defaultPath;
const int index = path.lastIndexOf(QL1C('/'));
if (index > 0)
defaultPath = path.left(index);
KDateTime epoch;
epoch.setTime_t(0);
// Check for cross-domain flag from kio_http
if (qstrncmp(cookieStr, "Cross-Domain\n", 13) == 0)
{
cookieStr += 13;
crossDomain = true;
}
// The hard stuff :)
for(;;)
{
// check for "Set-Cookie"
if (qstrnicmp(cookieStr, "Set-Cookie:", 11) == 0)
{
cookieStr = parseNameValue(cookieStr+11, Name, Value, true);
// Host = FQDN
// Default domain = ""
// Default path according to rfc2109
KHttpCookie cookie(fqdn, QL1S(""), defaultPath, Name, Value);
if (windowId)
cookie.mWindowIds.append(windowId);
cookie.mCrossDomain = crossDomain;
// Insert cookie in chain
cookieList.append(cookie);
}
else if (qstrnicmp(cookieStr, "Set-Cookie2:", 12) == 0)
{
// Attempt to follow rfc2965
isRFC2965 = true;
cookieStr = parseNameValue(cookieStr+12, Name, Value, true, true);
// Host = FQDN
// Default domain = ""
// Default path according to rfc2965
KHttpCookie cookie(fqdn, QL1S(""), defaultPath, Name, Value);
if (windowId)
cookie.mWindowIds.append(windowId);
cookie.mCrossDomain = crossDomain;
// Insert cookie in chain
cookieList2.append(cookie);
}
else
{
// This is not the start of a cookie header, skip till next line.
while (*cookieStr && *cookieStr != '\n')
cookieStr++;
if (*cookieStr == '\n')
cookieStr++;
if (!*cookieStr)
break; // End of cookie_headers
else
continue; // end of this header, continue with next.
}
while ((*cookieStr == ';') || (*cookieStr == ' '))
{
cookieStr++;
// Name-Value pair follows
cookieStr = parseNameValue(cookieStr, Name, Value);
KHttpCookie& lastCookie = (isRFC2965 ? cookieList2.last() : cookieList.last());
if (Name.compare(QL1S("domain"), Qt::CaseInsensitive) == 0)
{
QString dom = Value.toLower();
// RFC2965 3.2.2: If an explicitly specified value does not
// start with a dot, the user agent supplies a leading dot
if(dom.length() && dom[0] != '.')
dom.prepend(".");
// remove a trailing dot
if(dom.length() > 2 && dom[dom.length()-1] == '.')
dom = dom.left(dom.length()-1);
if(dom.count(QL1C('.')) > 1 || dom == ".local")
lastCookie.mDomain = dom;
}
else if (Name.compare(QL1S("max-age"), Qt::CaseInsensitive) == 0)
{
int max_age = Value.toInt();
if (max_age == 0)
lastCookie.mExpireDate = 1;
else
lastCookie.mExpireDate = epoch.secsTo_long(KDateTime::currentUtcDateTime().addSecs(max_age));
}
else if (Name.compare(QL1S("expires"), Qt::CaseInsensitive) == 0)
{
const KDateTime dt = parseDate(Value);
if (dt.isValid()) {
lastCookie.mExpireDate = epoch.secsTo_long(dt);
if (lastCookie.mExpireDate == 0)
lastCookie.mExpireDate = 1;
}
}
else if (Name.compare(QL1S("path"), Qt::CaseInsensitive) == 0)
{
if (Value.isEmpty())
lastCookie.mPath.clear(); // Catch "" <> QString()
else
lastCookie.mPath = QUrl::fromPercentEncoding(Value.toLatin1());
lastCookie.mExplicitPath = true;
}
else if (Name.compare(QL1S("version"), Qt::CaseInsensitive) == 0)
{
lastCookie.mProtocolVersion = Value.toInt();
}
else if (Name.compare(QL1S("secure"), Qt::CaseInsensitive) == 0 ||
(Name.isEmpty() && Value.compare(QL1S("secure"), Qt::CaseInsensitive) == 0))
{
lastCookie.mSecure = true;
}
else if (Name.compare(QL1S("httponly"), Qt::CaseInsensitive) == 0 ||
(Name.isEmpty() && Value.compare(QL1S("httponly"), Qt::CaseInsensitive) == 0))
{
lastCookie.mHttpOnly = true;
}
else if (isRFC2965 && (Name.compare(QL1S("port"), Qt::CaseInsensitive) == 0 ||
(Name.isEmpty() && Value.compare(QL1S("port"), Qt::CaseInsensitive) == 0)))
{
// Based on the port selection rule of RFC 2965 section 3.3.4...
if (Name.isEmpty())
{
// We intentionally append a -1 first in orer to distinguish
// between only a 'Port' vs a 'Port="80 443"' in the sent cookie.
lastCookie.mPorts.append(-1);
const bool secureRequest = (_url.startsWith(QL1S("https://"), Qt::CaseInsensitive) ||
_url.startsWith(QL1S("webdavs://"), Qt::CaseInsensitive));
if (secureRequest)
lastCookie.mPorts.append(443);
else
lastCookie.mPorts.append(80);
}
else
{
bool ok;
const QStringList portNums = Value.split(QL1C(' '), QString::SkipEmptyParts);
Q_FOREACH(const QString& portNum, portNums)
{
const int port = portNum.toInt(&ok);
if (ok)
lastCookie.mPorts.append(port);
}
}
}
}
if (*cookieStr == '\0')
break; // End of header
// Skip ';' or '\n'
cookieStr++;
}
// RFC2965 cookies come last so that they override netscape cookies.
while(!cookieList2.isEmpty()) {
KHttpCookie& lastCookie = cookieList2.first();
removeDuplicateFromList(&cookieList, lastCookie, true);
cookieList.append(lastCookie);
cookieList2.removeFirst();
}
return cookieList;
}
/**
* Parses cookie_domstr and returns a linked list of KHttpCookie objects.
* cookie_domstr should be a semicolon-delimited list of "name=value"
* pairs. Any whitespace before "name" or around '=' is discarded.
* If no cookies are found, 0 is returned.
*/
KHttpCookieList KCookieJar::makeDOMCookies(const QString &_url,
const QByteArray &cookie_domstring,
long windowId)
{
// A lot copied from above
KHttpCookieList cookieList;
const char *cookieStr = cookie_domstring.data();
QString fqdn;
QString path;
if (!parseUrl(_url, fqdn, path))
{
// Error parsing _url
return KHttpCookieList();
}
QString Name;
QString Value;
// This time it's easy
while(*cookieStr)
{
cookieStr = parseNameValue(cookieStr, Name, Value);
// Host = FQDN
// Default domain = ""
// Default path = ""
KHttpCookie cookie(fqdn, QString(), QString(),
Name, Value );
if (windowId)
cookie.mWindowIds.append(windowId);
cookieList.append(cookie);
if (*cookieStr != '\0')
cookieStr++; // Skip ';' or '\n'
}
return cookieList;
}
// KHttpCookieList sorting
///////////////////////////////////////////////////////////////////////////
// We want the longest path first
static bool compareCookies(const KHttpCookie& item1, const KHttpCookie& item2)
{
return item1.path().length() > item2.path().length();
}
#ifdef MAX_COOKIE_LIMIT
static void makeRoom(KHttpCookieList *cookieList, KHttpCookiePtr &cookiePtr)
{
// Too many cookies: throw one away, try to be somewhat clever
KHttpCookiePtr lastCookie = 0;
for(KHttpCookiePtr cookie = cookieList->first(); cookie; cookie = cookieList->next())
{
if (compareCookies(cookie, cookiePtr))
break;
lastCookie = cookie;
}
if (!lastCookie)
lastCookie = cookieList->first();
cookieList->removeRef(lastCookie);
}
#endif
//
// This function hands a KHttpCookie object over to the cookie jar.
//
void KCookieJar::addCookie(KHttpCookie &cookie)
{
QStringList domains;
// We always need to do this to make sure that the
// that cookies of type hostname == cookie-domainname
// are properly removed and/or updated as necessary!
extractDomains( cookie.host(), domains );
QStringListIterator it (domains);
while (it.hasNext())
{
const QString& key = it.next();
KHttpCookieList* list;
if (key.isNull())
list = m_cookieDomains.value(QL1S(""));
else
list = m_cookieDomains.value(key);
if (list)
removeDuplicateFromList(list, cookie, false, true);
}
const QString domain = stripDomain( cookie );
KHttpCookieList* cookieList;
if (domain.isNull())
cookieList = m_cookieDomains.value(QL1S(""));
else
cookieList = m_cookieDomains.value(domain);
if (!cookieList)
{
// Make a new cookie list
cookieList = new KHttpCookieList();
// All cookies whose domain is not already
// known to us should be added with KCookieDunno.
// KCookieDunno means that we use the global policy.
cookieList->setAdvice( KCookieDunno );
m_cookieDomains.insert( domain, cookieList);
// Update the list of domains
m_domainList.append(domain);
}
// Add the cookie to the cookie list
// The cookie list is sorted 'longest path first'
if (!cookie.isExpired())
{
#ifdef MAX_COOKIE_LIMIT
if (cookieList->count() >= MAX_COOKIES_PER_HOST)
makeRoom(cookieList, cookie); // Delete a cookie
#endif
cookieList->push_back(cookie);
// Use a stable sort so that unit tests are reliable.
// In practice it doesn't matter though.
qStableSort(cookieList->begin(), cookieList->end(), compareCookies);
m_cookiesChanged = true;
}
}
//
// This function advices whether a single KHttpCookie object should
// be added to the cookie jar.
//
KCookieAdvice KCookieJar::cookieAdvice(KHttpCookie& cookie)
{
if (m_rejectCrossDomainCookies && cookie.isCrossDomain())
return KCookieReject;
QStringList domains;
extractDomains(cookie.host(), domains);
// If the cookie specifies a domain, check whether it is valid. Otherwise,
// accept the cookie anyways but removes the domain="" value to prevent
// cross-site cookie injection.
if (!cookie.domain().isEmpty()) {
if (!domains.contains(cookie.domain()) &&
!cookie.domain().endsWith(QL1C('.') + cookie.host()))
cookie.fixDomain(QString());
}
if (m_autoAcceptSessionCookies && (cookie.expireDate() == 0 ||
m_ignoreCookieExpirationDate))
return KCookieAccept;
KCookieAdvice advice = KCookieDunno;
QStringListIterator it (domains);
while(advice == KCookieDunno && it.hasNext()) {
const QString& domain = it.next();
if (domain.startsWith(QL1C('.')) || cookie.host() == domain) {
KHttpCookieList *cookieList = m_cookieDomains.value(domain);
if (cookieList)
advice = cookieList->getAdvice();
}
}
if (advice == KCookieDunno)
advice = m_globalAdvice;
return advice;
}
//
// This function gets the advice for all cookies originating from
// _domain.
//
KCookieAdvice KCookieJar::getDomainAdvice(const QString &_domain)
{
KHttpCookieList *cookieList = m_cookieDomains.value(_domain);
KCookieAdvice advice;
if (cookieList)
advice = cookieList->getAdvice();
else
advice = KCookieDunno;
return advice;
}
//
// This function sets the advice for all cookies originating from
// _domain.
//
void KCookieJar::setDomainAdvice(const QString &_domain, KCookieAdvice _advice)
{
QString domain(_domain);
KHttpCookieList *cookieList = m_cookieDomains.value(domain);
if (cookieList) {
if (cookieList->getAdvice() != _advice) {
m_configChanged = true;
// domain is already known
cookieList->setAdvice( _advice);
}
if ((cookieList->isEmpty()) && (_advice == KCookieDunno)) {
// This deletes cookieList!
delete m_cookieDomains.take(domain);
m_domainList.removeAll(domain);
}
} else {
// domain is not yet known
if (_advice != KCookieDunno) {
// We should create a domain entry
m_configChanged = true;
// Make a new cookie list
cookieList = new KHttpCookieList();
cookieList->setAdvice(_advice);
m_cookieDomains.insert(domain, cookieList);
// Update the list of domains
m_domainList.append( domain);
}
}
}
//
// This function sets the advice for all cookies originating from
// the same domain as _cookie
//
void KCookieJar::setDomainAdvice(const KHttpCookie& cookie, KCookieAdvice _advice)
{
QString domain;
stripDomain(cookie.host(), domain); // We file the cookie under this domain.
setDomainAdvice(domain, _advice);
}
//
// This function sets the global advice for cookies
//
void KCookieJar::setGlobalAdvice(KCookieAdvice _advice)
{
if (m_globalAdvice != _advice)
m_configChanged = true;
m_globalAdvice = _advice;
}
//
// Get a list of all domains known to the cookie jar.
//
const QStringList& KCookieJar::getDomainList()
{
return m_domainList;
}
//
// Get a list of all cookies in the cookie jar originating from _domain.
//
KHttpCookieList *KCookieJar::getCookieList(const QString & _domain,
const QString & _fqdn )
{
QString domain;
if (_domain.isEmpty())
stripDomain(_fqdn, domain);
else
domain = _domain;
return m_cookieDomains.value(domain);
}
//
// Eat a cookie out of the jar.
// cookieIterator should be one of the cookies returned by getCookieList()
//
void KCookieJar::eatCookie(KHttpCookieList::iterator cookieIterator)
{
const KHttpCookie& cookie = *cookieIterator;
const QString domain = stripDomain(cookie); // We file the cookie under this domain.
KHttpCookieList *cookieList = m_cookieDomains.value(domain);
if (cookieList) {
// This deletes cookie!
cookieList->erase(cookieIterator);
if ((cookieList->isEmpty()) &&
(cookieList->getAdvice() == KCookieDunno))
{
// This deletes cookieList!
delete m_cookieDomains.take(domain);
m_domainList.removeAll(domain);
}
}
}
void KCookieJar::eatCookiesForDomain(const QString &domain)
{
KHttpCookieList *cookieList = m_cookieDomains.value(domain);
if (!cookieList || cookieList->isEmpty()) return;
cookieList->clear();
if (cookieList->getAdvice() == KCookieDunno)
{
// This deletes cookieList!
delete m_cookieDomains.take(domain);
m_domainList.removeAll(domain);
}
m_cookiesChanged = true;
}
void KCookieJar::eatSessionCookies( long windowId )
{
if (!windowId)
return;
Q_FOREACH(const QString& domain, m_domainList)
eatSessionCookies( domain, windowId, false );
}
void KCookieJar::eatAllCookies()
{
Q_FOREACH(const QString& domain, m_domainList)
eatCookiesForDomain(domain); // This might remove domain from m_domainList!
}
void KCookieJar::eatSessionCookies( const QString& fqdn, long windowId,
bool isFQDN )
{
KHttpCookieList* cookieList;
if ( !isFQDN )
cookieList = m_cookieDomains.value(fqdn);
else {
QString domain;
stripDomain( fqdn, domain );
cookieList = m_cookieDomains.value(domain);
}
if (cookieList) {
QMutableListIterator<KHttpCookie> cookieIterator(*cookieList);
while (cookieIterator.hasNext()) {
KHttpCookie& cookie = cookieIterator.next();
if ((cookie.expireDate() != 0) && !m_ignoreCookieExpirationDate) {
continue;
}
QList<long> &ids = cookie.windowIds();
#ifndef NDEBUG
if (ids.contains(windowId)) {
if (ids.count() > 1)
kDebug(7104) << "removing window id" << windowId << "from session cookie";
else
kDebug(7104) << "deleting session cookie";
}
#endif
if (!ids.removeAll(windowId) || !ids.isEmpty()) {
continue;
}
cookieIterator.remove();
}
}
}
static QString hostWithPort(const KHttpCookie* cookie)
{
const QList<int>& ports = cookie->ports();
if (ports.isEmpty())
return cookie->host();
QStringList portList;
Q_FOREACH(int port, ports)
portList << QString::number(port);
return (cookie->host() + QL1C(':') + portList.join(QL1S(",")));
}
//
// Saves all cookies to the file '_filename'.
// On succes 'true' is returned.
// On failure 'false' is returned.
bool KCookieJar::saveCookies(const QString &_filename)
{
KSaveFile cookieFile(_filename);
if (!cookieFile.open())
return false;
cookieFile.setPermissions(QFile::ReadUser|QFile::WriteUser);
QTextStream ts(&cookieFile);
ts << "# KDE Cookie File v2\n#\n";
QString s;
s.sprintf("%-20s %-20s %-12s %-10s %-4s %-20s %-4s %s\n",
"# Host", "Domain", "Path", "Exp.date", "Prot",
"Name", "Sec", "Value");
ts << s.toLatin1().constData();
QStringListIterator it(m_domainList);
while (it.hasNext())
{
const QString& domain = it.next();
bool domainPrinted = false;
KHttpCookieList *cookieList = m_cookieDomains.value(domain);
if (!cookieList)
continue;
QMutableListIterator<KHttpCookie> cookieIterator(*cookieList);
while (cookieIterator.hasNext()) {
const KHttpCookie& cookie = cookieIterator.next();
if (cookie.isExpired()) {
// Delete expired cookies
cookieIterator.remove();
continue;
}
if (cookie.expireDate() != 0 && !m_ignoreCookieExpirationDate) {
// Only save cookies that are not "session-only cookies"
if (!domainPrinted) {
domainPrinted = true;
ts << '[' << domain.toLocal8Bit().data() << "]\n";
}
// Store persistent cookies
const QString path = QL1S("\"") + cookie.path() + QL1C('"');
const QString domain = QL1S("\"") + cookie.domain() + QL1C('"');
const QString host = hostWithPort(&cookie);
// TODO: replace with direct QTextStream output ?
s.sprintf("%-20s %-20s %-12s %10lld %3d %-20s %-4i %s\n",
host.toLatin1().constData(), domain.toLatin1().constData(),
path.toLatin1().constData(), cookie.expireDate(),
cookie.protocolVersion(),
cookie.name().isEmpty() ? cookie.value().toLatin1().constData() : cookie.name().toLatin1().constData(),
(cookie.isSecure() ? 1 : 0) + (cookie.isHttpOnly() ? 2 : 0) +
(cookie.hasExplicitPath() ? 4 : 0) + (cookie.name().isEmpty() ? 8 : 0),
cookie.value().toLatin1().constData());
ts << s.toLatin1().constData();
}
}
}
return cookieFile.finalize();
}
static const char *parseField(char* &buffer, bool keepQuotes=false)
{
char *result;
if (!keepQuotes && (*buffer == '\"'))
{
// Find terminating "
buffer++;
result = buffer;
while((*buffer != '\"') && (*buffer))
buffer++;
}
else
{
// Find first white space
result = buffer;
while((*buffer != ' ') && (*buffer != '\t') && (*buffer != '\n') && (*buffer))
buffer++;
}
if (!*buffer)
return result; //
*buffer++ = '\0';
// Skip white-space
while((*buffer == ' ') || (*buffer == '\t') || (*buffer == '\n'))
buffer++;
return result;
}
static QString extractHostAndPorts(const QString& str, QList<int>* ports = 0)
{
if (str.isEmpty())
return str;
const int index = str.indexOf(QL1C(':'));
if (index == -1)
return str;
const QString host = str.left(index);
if (ports) {
bool ok;
QStringList portList = str.mid(index+1).split(QL1C(','));
Q_FOREACH(const QString& portStr, portList) {
const int portNum = portStr.toInt(&ok);
if (ok)
ports->append(portNum);
}
}
return host;
}
//
// Reloads all cookies from the file '_filename'.
// On succes 'true' is returned.
// On failure 'false' is returned.
bool KCookieJar::loadCookies(const QString &_filename)
{
QFile cookieFile (_filename);
if (!cookieFile.open(QIODevice::ReadOnly))
return false;
int version = 1;
bool success = false;
char *buffer = new char[READ_BUFFER_SIZE];
qint64 len = cookieFile.readLine(buffer, READ_BUFFER_SIZE-1);
if (len != -1)
{
if (qstrcmp(buffer, "# KDE Cookie File\n") == 0)
{
success = true;
}
else if(qstrcmp(buffer, "# KDE Cookie File v") > 0)
{
bool ok = false;
const int verNum = QByteArray(buffer+19, len-19).trimmed().toInt(&ok);
if (ok)
{
version = verNum;
success = true;
}
}
}
if (success)
{
const qint64 currentTime = epoch();
QList<int> ports;
while(cookieFile.readLine(buffer, READ_BUFFER_SIZE-1) != -1)
{
char *line = buffer;
// Skip lines which begin with '#' or '['
if ((line[0] == '#') || (line[0] == '['))
continue;
const QString host = extractHostAndPorts(QL1S(parseField(line)), &ports);
const QString domain = QL1S( parseField(line) );
if (host.isEmpty() && domain.isEmpty())
continue;
const QString path = QL1S( parseField(line) );
const QString expStr = QL1S( parseField(line) );
if (expStr.isEmpty()) continue;
const qint64 expDate = expStr.toLongLong();
const QString verStr = QL1S( parseField(line) );
if (verStr.isEmpty()) continue;
int protVer = verStr.toInt();
QString name = QL1S( parseField(line) );
bool keepQuotes = false;
bool secure = false;
bool httpOnly = false;
bool explicitPath = false;
const char *value = 0;
if ((version == 2) || (protVer >= 200))
{
if (protVer >= 200)
protVer -= 200;
int i = atoi( parseField(line) );
secure = i & 1;
httpOnly = i & 2;
explicitPath = i & 4;
if (i & 8)
name = "";
line[strlen(line)-1] = '\0'; // Strip LF.
value = line;
}
else
{
if (protVer >= 100)
{
protVer -= 100;
keepQuotes = true;
}
value = parseField(line, keepQuotes);
secure = QByteArray(parseField(line)).toShort();
}
// Expired or parse error
if (!value || expDate == 0 || expDate < currentTime)
continue;
KHttpCookie cookie(host, domain, path, name, value, expDate,
protVer, secure, httpOnly, explicitPath);
if (ports.count())
cookie.mPorts = ports;
addCookie(cookie);
}
}
delete [] buffer;
m_cookiesChanged = false;
return success;
}
//
// Save the cookie configuration
//
void KCookieJar::saveConfig(KConfig *_config)
{
if (!m_configChanged)
return;
KConfigGroup dlgGroup(_config, "Cookie Dialog");
dlgGroup.writeEntry("PreferredPolicy", static_cast<int>(m_preferredPolicy));
dlgGroup.writeEntry("ShowCookieDetails", m_showCookieDetails );
KConfigGroup policyGroup(_config,"Cookie Policy");
policyGroup.writeEntry("CookieGlobalAdvice", adviceToStr( m_globalAdvice));
QStringList domainSettings;
QStringListIterator it (m_domainList);
while (it.hasNext())
{
const QString& domain = it.next();
KCookieAdvice advice = getDomainAdvice( domain);
if (advice != KCookieDunno)
{
const QString value = domain + QL1C(':') + adviceToStr(advice);
domainSettings.append(value);
}
}
policyGroup.writeEntry("CookieDomainAdvice", domainSettings);
_config->sync();
m_configChanged = false;
}
//
// Load the cookie configuration
//
void KCookieJar::loadConfig(KConfig *_config, bool reparse )
{
if ( reparse )
_config->reparseConfiguration();
KConfigGroup dlgGroup(_config, "Cookie Dialog");
m_showCookieDetails = dlgGroup.readEntry( "ShowCookieDetails" , false );
m_preferredPolicy = static_cast<KCookieDefaultPolicy>(dlgGroup.readEntry("PreferredPolicy", 0));
KConfigGroup policyGroup(_config,"Cookie Policy");
const QStringList domainSettings = policyGroup.readEntry("CookieDomainAdvice", QStringList());
// Warning: those default values are duplicated in the kcm (kio/kcookiespolicies.cpp)
m_rejectCrossDomainCookies = policyGroup.readEntry("RejectCrossDomainCookies", true);
m_autoAcceptSessionCookies = policyGroup.readEntry("AcceptSessionCookies", true);
m_ignoreCookieExpirationDate = policyGroup.readEntry("IgnoreExpirationDate", false);
m_globalAdvice = strToAdvice(policyGroup.readEntry("CookieGlobalAdvice", QString(QL1S("Accept"))));
// Reset current domain settings first.
Q_FOREACH( const QString &domain, m_domainList )
setDomainAdvice(domain, KCookieDunno);
// Now apply the domain settings read from config file...
for (QStringList::ConstIterator it = domainSettings.constBegin(), itEnd = domainSettings.constEnd();
it != itEnd; ++it)
{
const QString& value = *it;
const int sepPos = value.lastIndexOf(QL1C(':'));
if (sepPos <= 0)
continue;
const QString domain(value.left(sepPos));
KCookieAdvice advice = strToAdvice( value.mid(sepPos + 1) );
setDomainAdvice(domain, advice);
}
}
QDebug operator<<(QDebug dbg, const KHttpCookie& cookie)
{
dbg.nospace() << cookie.cookieStr(false);
return dbg.space();
}
QDebug operator<<(QDebug dbg, const KHttpCookieList& list)
{
Q_FOREACH(const KHttpCookie& cookie, list)
dbg << cookie;
return dbg;
}
diff --git a/knewstuff/knewstuff2/ui/itemsview.cpp b/knewstuff/knewstuff2/ui/itemsview.cpp
index 9624306b18..441c245cdb 100644
--- a/knewstuff/knewstuff2/ui/itemsview.cpp
+++ b/knewstuff/knewstuff2/ui/itemsview.cpp
@@ -1,394 +1,394 @@
/*
This file is part of KNewStuff2.
Copyright (C) 2005 by Enrico Ros <eros.kde@email.it>
Copyright (C) 2005 - 2007 Josef Spillner <spillner@kde.org>
Copyright (C) 2007 Dirk Mueller <mueller@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.1 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, see <http://www.gnu.org/licenses/>.
*/
// own include
#include "itemsview.h"
// qt/kde includes
#include <QtCore/QFile>
#include <QWidget>
#include <QtCore/QTimer>
#include <QLayout>
#include <QImage>
#include <QFont>
#include <QComboBox>
#include <QPushButton>
#include <QtCore/QMutableVectorIterator>
#include <QtCore/QRect>
#include <QPainter>
#include <QScrollArea>
#include <QApplication>
#include <QTextDocument>
#include <kaboutdata.h>
#include <kapplication.h>
#include <kcomponentdata.h>
#include <kglobalsettings.h>
#include <klocale.h>
#include <klineedit.h>
#include <kconfig.h>
#include <kstandarddirs.h>
#include <kmessagebox.h>
#include <kdebug.h>
#include <kiconloader.h>
#include <kio/job.h>
#include <kio/netaccess.h>
#include <ktitlewidget.h>
#include <ktoolinvocation.h>
#include "knewstuff2/core/provider.h"
#include "knewstuff2/core/providerhandler.h"
#include "knewstuff2/core/entry.h"
#include "knewstuff2/core/entryhandler.h"
#include "knewstuff2/core/category.h"
#include "knewstuff2/dxs/dxs.h"
#include "knewstuff2/ui/qprogressindicator.h"
// local includes
#include "ui_DownloadDialog.h"
#include "kdxsbutton.h"
#include "qasyncimage_p.h"
using namespace KNS;
static bool NameSorter(const Entry* e1, const Entry* e2)
{
return e1->name().representation() < e2->name().representation();
}
static bool RatingSorter(const Entry* e1, const Entry* e2)
{
return e1->rating() < e2->rating();
}
static bool RecentSorter(const Entry* e1, const Entry* e2)
{
// return > instead of < to sort in reverse order
return e1->releaseDate() > e2->releaseDate();
}
static bool DownloadsSorter(const Entry* e1, const Entry* e2)
{
// return > instead of < to sort most downloads at the top
return e1->downloads() > e2->downloads();
}
ItemsView::ItemsView(QWidget* _parent)
: QListView(_parent),
m_currentProvider(0), m_currentFeed(0), m_root(0), m_sorting(0), m_engine(0)
{
m_root = new QWidget(this);
setFrameStyle(QFrame::Plain | QFrame::StyledPanel);
setVerticalScrollMode(ScrollPerPixel);
//setWidgetResizable(true);
}
ItemsView::~ItemsView()
{
}
void ItemsView::setEngine(DxsEngine *engine)
{
m_engine = engine;
}
void ItemsView::setProvider(const Provider * provider, const Feed * feed)
{
m_currentProvider = provider;
m_currentFeed = feed;
buildContents();
}
void ItemsView::setSorting(int sortType)
{
m_sorting = sortType;
buildContents();
}
void ItemsView::setFeed(const Feed * feed)
{
m_currentFeed = feed;
buildContents();
}
void ItemsView::setSearchText(const QString & text)
{
m_searchText = text;
buildContents();
}
void ItemsView::updateItem(Entry *entry)
{
// FIXME: change this to call updateEntry once it is complete
// if (m_views.contains(entry)) {
// m_views[entry]->setEntry(entry);
// }
}
void ItemsView::buildContents()
{
m_views.clear();
m_root->setBackgroundRole(QPalette::Base);
QVBoxLayout* _layout = new QVBoxLayout(m_root);
if (m_currentFeed != NULL) {
Entry::List entries = m_currentFeed->entries();
//switch (m_sorting)
//{
// case 0:
// qSort(entries.begin(), entries.end(), NameSorter);
// break;
// case 1:
// qSort(entries.begin(), entries.end(), RatingSorter);
// break;
// case 2:
// qSort(entries.begin(), entries.end(), RecentSorter);
// break;
// case 3:
// qSort(entries.begin(), entries.end(), DownloadsSorter);
// break;
//}
Entry::List::iterator it = entries.begin(), iEnd = entries.end();
for (unsigned row = 0; it != iEnd; ++it) {
Entry* entry = (*it);
if (entry->name().representation().toLower().contains(m_searchText.toLower())) {
QHBoxLayout * itemLayout = new QHBoxLayout;
_layout->addLayout(itemLayout);
EntryView *part = new EntryView(m_root);
part->setBackgroundRole(row & 1 ? QPalette::AlternateBase : QPalette::Base);
itemLayout->addWidget(part);
QVBoxLayout * previewLayout = new QVBoxLayout;
itemLayout->insertLayout(0, previewLayout);
KDXSButton *dxsbutton = new KDXSButton(m_root);
dxsbutton->setEntry(entry);
dxsbutton->setProvider(m_currentProvider);
dxsbutton->setEngine(m_engine);
QString imageurl = entry->preview().representation();
if (!imageurl.isEmpty()) {
QLabel *f = new QLabel(m_root);
f->setFrameStyle(QFrame::Panel | QFrame::Sunken);
QAsyncImage *pix = new QAsyncImage(imageurl, m_root);
f->setFixedSize(64, 64);
//connect(pix, SIGNAL(signalLoaded(const QImage&)),
// f, SLOT(setImage(const QImage&)));
previewLayout->addWidget(f);
}
//previewLayout->addWidget(dxsbutton);
part->setEntry(entry);
m_views.insert(entry, part);
++row;
}
}
}
//setWidget(m_root);
}
EntryView::EntryView(QWidget * _parent)
: QLabel(_parent)
{
connect(this, SIGNAL(linkActivated(const QString&)), SLOT(urlSelected(const QString&)));
}
void EntryView::setEntry(Entry *entry)
{
m_entry = entry;
buildContents();
}
void EntryView::updateEntry(Entry *entry)
{
// get item id string and iformations
QString idString = QString::number((unsigned long)entry);
// AvailableItem::State state = item->state();
// bool showProgress = state != AvailableItem::Normal;
// int pixelProgress = showProgress ? (int)(item->progress() * 80.0) : 0;
// perform internal scripting operations over the element
// executeScript( "document.getElementById('" + idString + "').style.color='red'" );
// executeScript( "document.getElementById('bar" + idString + "').style.width='" +
// QString::number( pixelProgress ) + "px'" );
// executeScript( "document.getElementById('bc" + idString + "').style.backgroundColor='" +
// (showProgress ? "gray" : "transparent") + "'" );
// executeScript( "document.getElementById('btn" + idString + "').value='" +
// (item->installed() ? i18n( "Uninstall" ) : i18n( "Install" )) + "'" );
}
void EntryView::buildContents()
{
// write the html header and contents manipulation scripts
QString t;
t += "<html><body>";
//t += setTheAaronnesqueStyle();
// precalc the status icon
Entry::Status status = m_entry->status();
QString statusIcon;
KIconLoader *loader = KIconLoader::global();
switch (status) {
case Entry::Invalid:
statusIcon = "<img src='" + loader->iconPath("dialog-error", -KIconLoader::SizeSmall) + "' />";
break;
case Entry::Downloadable:
// find a good icon to represent downloadable data
//statusIcon = "<img src='" + loader->iconPath("network-server", -KIconLoader::SizeSmall) + "' />";
break;
case Entry::Installed:
statusIcon = "<img src='" + loader->iconPath("dialog-ok", -KIconLoader::SizeSmall) + "' />";
break;
case Entry::Updateable:
statusIcon = "<img src='" + loader->iconPath("software-update-available", -KIconLoader::SizeSmall) + "' />";
break;
case Entry::Deleted:
statusIcon = "<img src='" + loader->iconPath("user-trash", -KIconLoader::SizeSmall) + "' />";
break;
}
// precalc the title string
QString titleString = m_entry->name().representation();
if (!m_entry->version().isEmpty()) titleString += " v." + Qt::escape(m_entry->version());
// precalc the string for displaying stars (normal+grayed)
QString starIconPath = KStandardDirs::locate("data", "knewstuff/pics/ghns_star.png");
QString starBgIconPath = KStandardDirs::locate("data", "knewstuff/pics/ghns_star_gray.png");
int starPixels = 11 + 11 * (m_entry->rating() / 10);
QString starsString = "<div style='width: " + QString::number(starPixels) + "px; background-image: url(" + starIconPath + "); background-repeat: repeat-x;'>&nbsp;</div>";
int grayPixels = 22 + 22 * (m_entry->rating() / 20);
starsString = "<div style='width: " + QString::number(grayPixels) + "px;background-image: url(" + starBgIconPath + "); background-repeat: repeat-x;'>" + starsString + "&nbsp;</div>";
// precalc the string for displaying author (parsing email)
KNS::Author author = m_entry->author();
QString authorString = author.name();
QString emailString = author.email();
if (!emailString.isEmpty()) {
authorString = "<a href='mailto:" + Qt::escape(emailString) + "'>"
+ Qt::escape(authorString) + "</a>";
}
// write the HTML code for the current item
t += //QLatin1String("<table class='contentsHeader' cellspacing='2' cellpadding='0'>")
statusIcon + Qt::escape(titleString) + "<br />"
//+ "<span align='right'>" + starsString + "</span><br />"
+ Qt::escape(m_entry->summary().representation())
+ "<br />";
if (m_entry->rating() > 0) {
t += i18n("Rating: ") + QString::number(m_entry->rating())
+ "<br />";
}
if (m_entry->downloads() > 0) {
t += i18n("Downloads: ") + QString::number(m_entry->downloads())
+ "<br />";
}
if (!authorString.isEmpty()) {
t += "<em>" + authorString + "</em>, ";
}
t += KGlobal::locale()->formatDate(m_entry->releaseDate(), KLocale::ShortDate)
+ "<br />" + "</body></html>";
setText(t);
}
void EntryView::setTheAaronnesqueStyle()
{
QString hoverColor = "#000000"; //QApplication::palette().active().highlightedText().name();
QString hoverBackground = "#f8f8f8"; //QApplication::palette().active().highlight().name();
QString starIconPath = KStandardDirs::locate("data", "knewstuff/pics/ghns_star.png");
QString starBgIconPath = KStandardDirs::locate("data", "knewstuff/pics/ghns_star_gray.png");
// default elements style
QString s;
s += "body { background-color: white; color: black; padding: 0; margin: 0; }";
s += "table, td, th { padding: 0; margin: 0; text-align: left; }";
s += "input { color: #000080; font-size:120%; }";
// the main item container (custom element)
s += ".itemBox { background-color: white; color: black; width: 100%; border-bottom: 1px solid gray; margin: 0px 0px; }";
s += ".itemBox:hover { background-color: " + hoverBackground + "; color: " + hoverColor + "; }";
// s of the item elements (4 cells with multiple containers)
s += ".leftColumn { width: 100px; height:100%; text-align: center; }";
s += ".leftImage {}";
s += ".leftButton {}";
s += ".leftProgressContainer { width: 82px; height: 10px; background-color: transparent; }";
s += ".leftProgressBar { left: 1px; width: 0px; top: 1px; height: 8px; background-color: red; }";
s += ".contentsColumn { vertical-align: top; }";
s += ".contentsHeader { width: 100%; font-size: 120%; font-weight: bold; border-bottom: 1px solid #c8c8c8; }";
s += ".contentsBody {}";
s += ".contentsFooter {}";
s += ".star { width: 0px; height: 24px; background-image: url(" + starIconPath + "); background-repeat: repeat-x; }";
s += ".starbg { width: 110px; height: 24px; background-image: url(" + starBgIconPath + "); background-repeat: repeat-x; }";
setStyleSheet(s);
}
void EntryView::urlSelected(const QString &link)
{
//kDebug() << "Clicked on URL " << link;
KUrl url(link);
- QString urlProtocol = url.protocol();
+ QString urlProtocol = url.scheme();
QString urlPath = url.path();
if (urlProtocol == "mailto") {
// clicked over a mail address
// FIXME: if clicked with MRB, show context menu with IM etc.
// FIXME: but RMB never reaches this method?!
KToolInvocation::invokeMailer(url);
} else if (urlProtocol == "item") {
// clicked over an item
bool ok;
unsigned long itemPointer = urlPath.toULong(&ok);
if (!ok) {
kWarning() << "ItemsView: error converting item pointer.";
return;
}
// I love to cast pointers
Entry *entry = (Entry*)itemPointer;
if (entry != m_entry) {
kWarning() << "ItemsView: error retrieving item pointer.";
return;
}
// XXX ???
// install/uninstall the item
// if ( item->installed() )
// m_newStuffDialog->removeItem( item ); // synchronous
// else
// m_newStuffDialog->installItem( item ); // asynchronous
}
}
diff --git a/knewstuff/knewstuff2/ui/kdxscomments.cpp b/knewstuff/knewstuff2/ui/kdxscomments.cpp
index e98eaa5e82..a0fad5a74a 100644
--- a/knewstuff/knewstuff2/ui/kdxscomments.cpp
+++ b/knewstuff/knewstuff2/ui/kdxscomments.cpp
@@ -1,90 +1,90 @@
/*
This file is part of KNewStuff2.
Copyright (c) 2006, 2007 Josef Spillner <spillner@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.1 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, see <http://www.gnu.org/licenses/>.
*/
#include "kdxscomments.h"
#include <klocale.h>
#include <ktextbrowser.h>
#include <QLayout>
#include <QApplication>
#include <QCursor>
KDXSComments::KDXSComments(QWidget *parent)
: KDialog(parent)
{
setCaption(i18n("User comments"));
setButtons(KDialog::Close);
m_log = new KTextBrowser(this);
setMainWidget(m_log);
connect(m_log, SIGNAL(anchorClicked(const QUrl&)),
SLOT(slotUrl(const QUrl&)));
}
void KDXSComments::slotUrl(const QUrl& url)
{
if (!url.isEmpty()) {
qDebug("SHOW %s!", qPrintable(url.toString()));
}
}
void KDXSComments::addComment(const QString& username, const QString& comment)
{
// FIXME: get email address??
QString t;
t += m_log->toHtml();
QString email = "spillner@kde.org";
t += "<a href='" + email + "'>" + Qt::escape(username) + "</a>"
+ "<table class='itemBox'>"
+ "<tr>"
+ "<td class='contentsColumn'>"
+ "<table class='contentsHeader' cellspacing='2' cellpadding='0'><tr>"
+ "<td>Comment!</td>"
+ "</tr></table>"
+ "<div class='contentsBody'>"
+ Qt::escape(comment)
+ "</div>"
+ "<div class='contentsFooter'>"
+ "<em>" + Qt::escape(username) + "</em>"
+ "</div>"
+ "</td>"
+ "</tr>"
+ "</table>";
m_log->setHtml(t);
}
/*
void urlSelected(const QString & link, int, int, const QString &, KParts::URLArgs)
{
KURL url(link);
-QString urlProtocol = url.protocol();
+QString urlProtocol = url.scheme();
QString urlPath = url.path();
if(urlProtocol == "mailto")
{
kapp->invokeMailer( url );
}
}
*/
diff --git a/kparts/browserrun.cpp b/kparts/browserrun.cpp
index f0de678cb0..3a2b309107 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().protocol();
+ 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().protocol().toLower();
+ 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().protocol().startsWith(QLatin1String("http"))) {
+ 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;
//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"
diff --git a/kparts/part.cpp b/kparts/part.cpp
index 807d1e9fb9..052d55bc8b 100644
--- a/kparts/part.cpp
+++ b/kparts/part.cpp
@@ -1,1147 +1,1147 @@
/* This file is part of the KDE project
Copyright (C) 1999 Simon Hausmann <hausmann@kde.org>
(C) 1999-2005 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 "part.h"
#include <kprotocolinfo.h>
#include "event.h"
#include "plugin.h"
#include "mainwindow.h"
#include "partmanager.h"
#include "browserextension.h"
#include <QApplication>
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
#include <qtemporaryfile.h>
#include <QtCore/QPoint>
#include <kdirnotify.h>
#include <kfiledialog.h>
#include <kcomponentdata.h>
#include <kio/job.h>
#include <kio/jobuidelegate.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kstandarddirs.h>
#include <kxmlguifactory.h>
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <kdebug.h>
#include <kiconloader.h>
using namespace KParts;
namespace KParts
{
class PartBasePrivate
{
public:
Q_DECLARE_PUBLIC(PartBase)
PartBasePrivate(PartBase *q): q_ptr(q)
{
m_pluginLoadingMode = PartBase::LoadPlugins;
m_pluginInterfaceVersion = 0;
m_obj = 0;
}
virtual ~PartBasePrivate()
{
}
PartBase *q_ptr;
PartBase::PluginLoadingMode m_pluginLoadingMode;
int m_pluginInterfaceVersion;
QObject *m_obj;
};
class PartPrivate: public PartBasePrivate
{
public:
Q_DECLARE_PUBLIC(Part)
PartPrivate(Part *q)
: PartBasePrivate(q),
m_iconLoader(0),
m_bSelectable(true),
m_autoDeleteWidget(true),
m_autoDeletePart(true),
m_manager(0)
{
}
~PartPrivate()
{
}
KIconLoader* m_iconLoader;
bool m_bSelectable;
bool m_autoDeleteWidget;
bool m_autoDeletePart;
PartManager * m_manager;
QPointer<QWidget> m_widget;
};
}
PartBase::PartBase()
: d_ptr(new PartBasePrivate(this))
{
}
PartBase::PartBase(PartBasePrivate &dd)
: d_ptr(&dd)
{
}
PartBase::~PartBase()
{
delete d_ptr;
}
void PartBase::setPartObject( QObject *obj )
{
Q_D(PartBase);
d->m_obj = obj;
}
QObject *PartBase::partObject() const
{
Q_D(const PartBase);
return d->m_obj;
}
void PartBase::setComponentData(const KComponentData &componentData)
{
setComponentData(componentData, true);
}
void PartBase::setComponentData(const KComponentData &componentData, bool bLoadPlugins)
{
Q_D(PartBase);
KXMLGUIClient::setComponentData(componentData);
KGlobal::locale()->insertCatalog(componentData.catalogName());
// install 'instancename'data resource type
KGlobal::dirs()->addResourceType(QString(componentData.componentName() + "data").toUtf8(),
"data", componentData.componentName());
if (bLoadPlugins) {
loadPlugins(d->m_obj, this, componentData);
}
}
void PartBase::loadPlugins(QObject *parent, KXMLGUIClient *parentGUIClient, const KComponentData &instance)
{
Q_D(PartBase);
if( d->m_pluginLoadingMode != DoNotLoadPlugins )
Plugin::loadPlugins( parent, parentGUIClient, instance, d->m_pluginLoadingMode == LoadPlugins, d->m_pluginInterfaceVersion );
}
void PartBase::setPluginLoadingMode( PluginLoadingMode loadingMode )
{
Q_D(PartBase);
d->m_pluginLoadingMode = loadingMode;
}
void KParts::PartBase::setPluginInterfaceVersion( int version )
{
Q_D(PartBase);
d->m_pluginInterfaceVersion = version;
}
Part::Part( QObject *parent )
: QObject( parent ), PartBase( *new PartPrivate(this) )
{
PartBase::setPartObject( this );
}
Part::Part(PartPrivate &dd, QObject *parent)
: QObject( parent ), PartBase( dd )
{
PartBase::setPartObject( this );
}
Part::~Part()
{
Q_D(Part);
//kDebug(1000) << this;
if ( d->m_widget )
{
// We need to disconnect first, to avoid calling it !
disconnect( d->m_widget, SIGNAL( destroyed() ),
this, SLOT( slotWidgetDestroyed() ) );
}
if ( d->m_manager )
d->m_manager->removePart(this);
if ( d->m_widget && d->m_autoDeleteWidget )
{
kDebug(1000) << "deleting widget" << d->m_widget << d->m_widget->objectName();
delete static_cast<QWidget*>(d->m_widget);
}
delete d->m_iconLoader;
}
void Part::embed( QWidget * parentWidget )
{
if ( widget() )
{
widget()->setParent( parentWidget, 0 );
widget()->setGeometry( 0, 0, widget()->width(), widget()->height() );
widget()->show();
}
}
QWidget *Part::widget()
{
Q_D(Part);
return d->m_widget;
}
void Part::setAutoDeleteWidget(bool autoDeleteWidget)
{
Q_D(Part);
d->m_autoDeleteWidget = autoDeleteWidget;
}
void Part::setAutoDeletePart(bool autoDeletePart)
{
Q_D(Part);
d->m_autoDeletePart = autoDeletePart;
}
KIconLoader* Part::iconLoader()
{
Q_D(Part);
if (!d->m_iconLoader) {
Q_ASSERT(componentData().isValid());
d->m_iconLoader = new KIconLoader( componentData() );
}
return d->m_iconLoader;
}
void Part::setManager( PartManager *manager )
{
Q_D(Part);
d->m_manager = manager;
}
PartManager *Part::manager() const
{
Q_D(const Part);
return d->m_manager;
}
Part *Part::hitTest( QWidget *widget, const QPoint & )
{
Q_D(Part);
if ( (QWidget *)d->m_widget != widget )
return 0;
return this;
}
void Part::setWidget( QWidget *widget )
{
Q_D(Part);
d->m_widget = widget;
connect( d->m_widget, SIGNAL( destroyed() ),
this, SLOT( slotWidgetDestroyed() ), Qt::UniqueConnection );
}
void Part::setSelectable( bool selectable )
{
Q_D(Part);
d->m_bSelectable = selectable;
}
bool Part::isSelectable() const
{
Q_D(const Part);
return d->m_bSelectable;
}
void Part::customEvent( QEvent *ev )
{
if ( PartActivateEvent::test( ev ) )
{
partActivateEvent( static_cast<PartActivateEvent *>(ev) );
return;
}
if ( PartSelectEvent::test( ev ) )
{
partSelectEvent( static_cast<PartSelectEvent *>(ev) );
return;
}
if ( GUIActivateEvent::test( ev ) )
{
guiActivateEvent( static_cast<GUIActivateEvent *>(ev) );
return;
}
QObject::customEvent( ev );
}
void Part::partActivateEvent( PartActivateEvent * )
{
}
void Part::partSelectEvent( PartSelectEvent * )
{
}
void Part::guiActivateEvent( GUIActivateEvent * )
{
}
QWidget *Part::hostContainer( const QString &containerName )
{
if ( !factory() )
return 0;
return factory()->container( containerName, this );
}
void Part::slotWidgetDestroyed()
{
Q_D(Part);
d->m_widget = 0;
if (d->m_autoDeletePart) {
kDebug(1000) << "deleting part" << objectName();
delete this; // ouch, this should probably be deleteLater()
}
}
void Part::loadPlugins()
{
PartBase::loadPlugins(this, this, componentData());
}
//////////////////////////////////////////////////
namespace KParts
{
class ReadOnlyPartPrivate: public PartPrivate
{
public:
Q_DECLARE_PUBLIC(ReadOnlyPart)
ReadOnlyPartPrivate(ReadOnlyPart *q): PartPrivate(q)
{
m_job = 0;
m_statJob = 0;
m_uploadJob = 0;
m_showProgressInfo = true;
m_saveOk = false;
m_waitForSave = false;
m_duringSaveAs = false;
m_bTemp = false;
m_bAutoDetectedMime = false;
}
~ReadOnlyPartPrivate()
{
}
void _k_slotJobFinished( KJob * job );
void _k_slotStatJobFinished(KJob * job);
void _k_slotGotMimeType(KIO::Job *job, const QString &mime);
bool openLocalFile();
void openRemoteFile();
KIO::FileCopyJob * m_job;
KIO::StatJob * m_statJob;
KIO::FileCopyJob * m_uploadJob;
KUrl m_originalURL; // for saveAs
QString m_originalFilePath; // for saveAs
bool m_showProgressInfo : 1;
bool m_saveOk : 1;
bool m_waitForSave : 1;
bool m_duringSaveAs : 1;
/**
* If @p true, @p m_file is a temporary file that needs to be deleted later.
*/
bool m_bTemp: 1;
// whether the mimetype in the arguments was detected by the part itself
bool m_bAutoDetectedMime : 1;
/**
* Remote (or local) url - the one displayed to the user.
*/
KUrl m_url;
/**
* Local file - the only one the part implementation should deal with.
*/
QString m_file;
OpenUrlArguments m_arguments;
};
class ReadWritePartPrivate: public ReadOnlyPartPrivate
{
public:
Q_DECLARE_PUBLIC(ReadWritePart)
ReadWritePartPrivate(ReadWritePart *q): ReadOnlyPartPrivate(q)
{
m_bModified = false;
m_bReadWrite = true;
m_bClosing = false;
}
void _k_slotUploadFinished( KJob * job );
void prepareSaving();
bool m_bModified;
bool m_bReadWrite;
bool m_bClosing;
QEventLoop m_eventLoop;
};
}
ReadOnlyPart::ReadOnlyPart( QObject *parent )
: Part( *new ReadOnlyPartPrivate(this), parent )
{
}
ReadOnlyPart::ReadOnlyPart( ReadOnlyPartPrivate &dd, QObject *parent )
: Part( dd, parent )
{
}
ReadOnlyPart::~ReadOnlyPart()
{
ReadOnlyPart::closeUrl();
}
KUrl ReadOnlyPart::url() const
{
Q_D(const ReadOnlyPart);
return d->m_url;
}
void ReadOnlyPart::setUrl(const KUrl &url)
{
Q_D(ReadOnlyPart);
d->m_url = url;
}
QString ReadOnlyPart::localFilePath() const
{
Q_D(const ReadOnlyPart);
return d->m_file;
}
void ReadOnlyPart::setLocalFilePath( const QString &localFilePath )
{
Q_D(ReadOnlyPart);
d->m_file = localFilePath;
}
#ifndef KDE_NO_DEPRECATED
bool ReadOnlyPart::isLocalFileTemporary() const
{
Q_D(const ReadOnlyPart);
return d->m_bTemp;
}
#endif
#ifndef KDE_NO_DEPRECATED
void ReadOnlyPart::setLocalFileTemporary( bool temp )
{
Q_D(ReadOnlyPart);
d->m_bTemp = temp;
}
#endif
void ReadOnlyPart::setProgressInfoEnabled( bool show )
{
Q_D(ReadOnlyPart);
d->m_showProgressInfo = show;
}
bool ReadOnlyPart::isProgressInfoEnabled() const
{
Q_D(const ReadOnlyPart);
return d->m_showProgressInfo;
}
#ifndef KDE_NO_COMPAT
void ReadOnlyPart::showProgressInfo( bool show )
{
Q_D(ReadOnlyPart);
d->m_showProgressInfo = show;
}
#endif
bool ReadOnlyPart::openUrl( const KUrl &url )
{
Q_D(ReadOnlyPart);
if ( !url.isValid() )
return false;
if (d->m_bAutoDetectedMime) {
d->m_arguments.setMimeType(QString());
d->m_bAutoDetectedMime = false;
}
OpenUrlArguments args = d->m_arguments;
if ( !closeUrl() )
return false;
d->m_arguments = args;
d->m_url = url;
d->m_file.clear();
if (d->m_url.isLocalFile()) {
d->m_file = d->m_url.toLocalFile();
return d->openLocalFile();
- } else if (KProtocolInfo::protocolClass(url.protocol()) == ":local") {
+ } else if (KProtocolInfo::protocolClass(url.scheme()) == ":local") {
// Maybe we can use a "local path", to avoid a temp copy?
KIO::JobFlags flags = d->m_showProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo;
d->m_statJob = KIO::mostLocalUrl(d->m_url, flags);
d->m_statJob->ui()->setWindow( widget() ? widget()->topLevelWidget() : 0 );
connect(d->m_statJob, SIGNAL(result(KJob*)), this, SLOT(_k_slotStatJobFinished(KJob*)));
return true;
} else {
d->openRemoteFile();
return true;
}
}
bool ReadOnlyPart::openFile()
{
kWarning(1000) << "Default implementation of ReadOnlyPart::openFile called!"
<< metaObject()->className() << "should reimplement either openUrl or openFile.";
return false;
}
bool ReadOnlyPartPrivate::openLocalFile()
{
Q_Q(ReadOnlyPart);
emit q->started( 0 );
m_bTemp = false;
// set the mimetype only if it was not already set (for example, by the host application)
if (m_arguments.mimeType().isEmpty()) {
// get the mimetype of the file
// using findByUrl() to avoid another string -> url conversion
KMimeType::Ptr mime = KMimeType::findByUrl(m_url, 0, true /* local file*/);
if (mime) {
m_arguments.setMimeType(mime->name());
m_bAutoDetectedMime = true;
}
}
const bool ret = q->openFile();
if (ret) {
emit q->setWindowCaption(m_url.prettyUrl());
emit q->completed();
} else {
emit q->canceled(QString());
}
return ret;
}
void ReadOnlyPartPrivate::openRemoteFile()
{
Q_Q(ReadOnlyPart);
m_bTemp = true;
// Use same extension as remote file. This is important for mimetype-determination (e.g. koffice)
QString fileName = m_url.fileName();
QFileInfo fileInfo(fileName);
QString ext = fileInfo.completeSuffix();
QString extension;
if (!ext.isEmpty() && m_url.query().isNull()) // not if the URL has a query, e.g. cgi.pl?something
extension = '.'+ext; // keep the '.'
QTemporaryFile tempFile(QDir::tempPath() + QLatin1Char('/') + q->componentData().componentName() + QLatin1String("XXXXXX") + extension);
tempFile.setAutoRemove(false);
tempFile.open();
m_file = tempFile.fileName();
KUrl destURL;
destURL.setPath( m_file );
KIO::JobFlags flags = m_showProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo;
flags |= KIO::Overwrite;
m_job = KIO::file_copy(m_url, destURL, 0600, flags);
m_job->ui()->setWindow(q->widget() ? q->widget()->topLevelWidget() : 0);
emit q->started(m_job);
QObject::connect(m_job, SIGNAL(result(KJob*)), q, SLOT(_k_slotJobFinished(KJob*)));
QObject::connect(m_job, SIGNAL(mimetype(KIO::Job*, QString)),
q, SLOT(_k_slotGotMimeType(KIO::Job*,QString)));
}
void ReadOnlyPart::abortLoad()
{
Q_D(ReadOnlyPart);
if ( d->m_statJob ) {
//kDebug(1000) << "Aborting job" << d->m_statJob;
d->m_statJob->kill();
d->m_statJob = 0;
}
if ( d->m_job ) {
//kDebug(1000) << "Aborting job" << d->m_job;
d->m_job->kill();
d->m_job = 0;
}
}
bool ReadOnlyPart::closeUrl()
{
Q_D(ReadOnlyPart);
abortLoad(); //just in case
d->m_arguments = KParts::OpenUrlArguments();
if ( d->m_bTemp )
{
QFile::remove( d->m_file );
d->m_bTemp = false;
}
// It always succeeds for a read-only part,
// but the return value exists for reimplementations
// (e.g. pressing cancel for a modified read-write part)
return true;
}
void ReadOnlyPartPrivate::_k_slotStatJobFinished(KJob * job)
{
Q_ASSERT(job == m_statJob);
m_statJob = 0;
// We could emit canceled on error, but we haven't even emitted started yet,
// this could maybe confuse some apps? So for now we'll just fallback to KIO::get
// and error again. Well, maybe this even helps with wrong stat results.
if (!job->error()) {
const KUrl localUrl = static_cast<KIO::StatJob*>(job)->mostLocalUrl();
if (localUrl.isLocalFile()) {
m_file = localUrl.toLocalFile();
(void)openLocalFile();
return;
}
}
openRemoteFile();
}
void ReadOnlyPartPrivate::_k_slotJobFinished( KJob * job )
{
Q_Q(ReadOnlyPart);
assert( job == m_job );
m_job = 0;
if (job->error())
emit q->canceled( job->errorString() );
else
{
if ( q->openFile() ) {
emit q->setWindowCaption( m_url.prettyUrl() );
emit q->completed();
} else emit q->canceled(QString());
}
}
void ReadOnlyPartPrivate::_k_slotGotMimeType(KIO::Job *job, const QString &mime)
{
kDebug(1000) << mime;
Q_ASSERT(job == m_job); Q_UNUSED(job)
// set the mimetype only if it was not already set (for example, by the host application)
if (m_arguments.mimeType().isEmpty()) {
m_arguments.setMimeType(mime);
m_bAutoDetectedMime = true;
}
}
void ReadOnlyPart::guiActivateEvent( GUIActivateEvent * event )
{
Q_D(ReadOnlyPart);
if (event->activated())
{
if (!d->m_url.isEmpty())
{
kDebug(1000) << d->m_url;
emit setWindowCaption( d->m_url.prettyUrl() );
} else emit setWindowCaption( "" );
}
}
bool ReadOnlyPart::openStream( const QString& mimeType, const KUrl& url )
{
Q_D(ReadOnlyPart);
OpenUrlArguments args = d->m_arguments;
if ( !closeUrl() )
return false;
d->m_arguments = args;
d->m_url = url;
return doOpenStream( mimeType );
}
bool ReadOnlyPart::writeStream( const QByteArray& data )
{
return doWriteStream( data );
}
bool ReadOnlyPart::closeStream()
{
return doCloseStream();
}
BrowserExtension* ReadOnlyPart::browserExtension() const
{
return findChild<KParts::BrowserExtension *>();
}
void KParts::ReadOnlyPart::setArguments(const OpenUrlArguments& arguments)
{
Q_D(ReadOnlyPart);
d->m_arguments = arguments;
d->m_bAutoDetectedMime = arguments.mimeType().isEmpty();
}
OpenUrlArguments KParts::ReadOnlyPart::arguments() const
{
Q_D(const ReadOnlyPart);
return d->m_arguments;
}
//////////////////////////////////////////////////
ReadWritePart::ReadWritePart( QObject *parent )
: ReadOnlyPart( *new ReadWritePartPrivate(this), parent )
{
}
ReadWritePart::~ReadWritePart()
{
// parent destructor will delete temp file
// we can't call our own closeUrl() here, because
// "cancel" wouldn't cancel anything. We have to assume
// the app called closeUrl() before destroying us.
}
void ReadWritePart::setReadWrite( bool readwrite )
{
Q_D(ReadWritePart);
// Perhaps we should check isModified here and issue a warning if true
d->m_bReadWrite = readwrite;
}
void ReadWritePart::setModified( bool modified )
{
Q_D(ReadWritePart);
kDebug(1000) << "setModified(" << (modified ? "true" : "false") << ")";
if ( !d->m_bReadWrite && modified )
{
kError(1000) << "Can't set a read-only document to 'modified' !" << endl;
return;
}
d->m_bModified = modified;
}
void ReadWritePart::setModified()
{
setModified( true );
}
bool ReadWritePart::queryClose()
{
Q_D(ReadWritePart);
if ( !isReadWrite() || !isModified() )
return true;
QString docName = url().fileName();
if (docName.isEmpty()) docName = i18n( "Untitled" );
QWidget *parentWidget=widget();
if(!parentWidget) parentWidget=QApplication::activeWindow();
int res = KMessageBox::warningYesNoCancel( parentWidget,
i18n( "The document \"%1\" has been modified.\n"
"Do you want to save your changes or discard them?" , docName ),
i18n( "Close Document" ), KStandardGuiItem::save(), KStandardGuiItem::discard() );
bool abortClose=false;
bool handled=false;
switch(res) {
case KMessageBox::Yes :
sigQueryClose(&handled,&abortClose);
if (!handled)
{
if (d->m_url.isEmpty())
{
KUrl url = KFileDialog::getSaveUrl(KUrl(), QString(), parentWidget);
if (url.isEmpty())
return false;
saveAs( url );
}
else
{
save();
}
} else if (abortClose) return false;
return waitSaveComplete();
case KMessageBox::No :
return true;
default : // case KMessageBox::Cancel :
return false;
}
}
bool ReadWritePart::closeUrl()
{
abortLoad(); //just in case
if ( isReadWrite() && isModified() )
{
if (!queryClose())
return false;
}
// Not modified => ok and delete temp file.
return ReadOnlyPart::closeUrl();
}
bool ReadWritePart::closeUrl( bool promptToSave )
{
return promptToSave ? closeUrl() : ReadOnlyPart::closeUrl();
}
bool ReadWritePart::save()
{
Q_D(ReadWritePart);
d->m_saveOk = false;
if ( d->m_file.isEmpty() ) // document was created empty
d->prepareSaving();
if( saveFile() )
return saveToUrl();
else
emit canceled(QString());
return false;
}
bool ReadWritePart::saveAs( const KUrl & kurl )
{
Q_D(ReadWritePart);
if (!kurl.isValid())
{
kError(1000) << "saveAs: Malformed URL " << kurl.url() << endl;
return false;
}
d->m_duringSaveAs = true;
d->m_originalURL = d->m_url;
d->m_originalFilePath = d->m_file;
d->m_url = kurl; // Store where to upload in saveToURL
d->prepareSaving();
bool result = save(); // Save local file and upload local file
if (result) {
emit setWindowCaption( d->m_url.prettyUrl() );
} else {
d->m_url = d->m_originalURL;
d->m_file = d->m_originalFilePath;
d->m_duringSaveAs = false;
d->m_originalURL = KUrl();
d->m_originalFilePath.clear();
}
return result;
}
// Set m_file correctly for m_url
void ReadWritePartPrivate::prepareSaving()
{
// Local file
if ( m_url.isLocalFile() )
{
if ( m_bTemp ) // get rid of a possible temp file first
{ // (happens if previous url was remote)
QFile::remove( m_file );
m_bTemp = false;
}
m_file = m_url.toLocalFile();
}
else
{ // Remote file
// We haven't saved yet, or we did but locally - provide a temp file
if ( m_file.isEmpty() || !m_bTemp )
{
QTemporaryFile tempFile;
tempFile.setAutoRemove(false);
tempFile.open();
m_file = tempFile.fileName();
m_bTemp = true;
}
// otherwise, we already had a temp file
}
}
bool ReadWritePart::saveToUrl()
{
Q_D(ReadWritePart);
if ( d->m_url.isLocalFile() )
{
setModified( false );
emit completed();
// if m_url is a local file there won't be a temp file -> nothing to remove
assert( !d->m_bTemp );
d->m_saveOk = true;
d->m_duringSaveAs = false;
d->m_originalURL = KUrl();
d->m_originalFilePath.clear();
return true; // Nothing to do
}
else
{
if (d->m_uploadJob)
{
QFile::remove(d->m_uploadJob->srcUrl().toLocalFile());
d->m_uploadJob->kill();
d->m_uploadJob = 0;
}
QTemporaryFile *tempFile = new QTemporaryFile();
tempFile->open();
QString uploadFile = tempFile->fileName();
delete tempFile;
KUrl uploadUrl;
uploadUrl.setPath( uploadFile );
// Create hardlink
if (::link(QFile::encodeName(d->m_file), QFile::encodeName(uploadFile)) != 0)
{
// Uh oh, some error happened.
return false;
}
d->m_uploadJob = KIO::file_move( uploadUrl, d->m_url, -1, KIO::Overwrite );
d->m_uploadJob->ui()->setWindow( widget() ? widget()->topLevelWidget() : 0 );
connect( d->m_uploadJob, SIGNAL( result( KJob * ) ), this, SLOT( _k_slotUploadFinished (KJob *) ) );
return true;
}
}
void ReadWritePartPrivate::_k_slotUploadFinished( KJob * )
{
Q_Q(ReadWritePart);
if (m_uploadJob->error())
{
QFile::remove(m_uploadJob->srcUrl().toLocalFile());
QString error = m_uploadJob->errorString();
m_uploadJob = 0;
if (m_duringSaveAs) {
m_url = m_originalURL;
m_file = m_originalFilePath;
}
emit q->canceled( error );
}
else
{
KUrl dirUrl( m_url );
dirUrl.setPath( dirUrl.directory() );
::org::kde::KDirNotify::emitFilesAdded( dirUrl.url() );
m_uploadJob = 0;
q->setModified( false );
emit q->completed();
m_saveOk = true;
}
m_duringSaveAs = false;
m_originalURL = KUrl();
m_originalFilePath.clear();
if (m_waitForSave) {
m_eventLoop.quit();
}
}
bool ReadWritePart::isReadWrite() const
{
Q_D(const ReadWritePart);
return d->m_bReadWrite;
}
bool ReadWritePart::isModified() const
{
Q_D(const ReadWritePart);
return d->m_bModified;
}
bool ReadWritePart::waitSaveComplete()
{
Q_D(ReadWritePart);
if (!d->m_uploadJob)
return d->m_saveOk;
d->m_waitForSave = true;
d->m_eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
d->m_waitForSave = false;
return d->m_saveOk;
}
////
class KParts::OpenUrlArgumentsPrivate : public QSharedData
{
public:
OpenUrlArgumentsPrivate()
: reload(false),
actionRequestedByUser(true),
xOffset(0),
yOffset(0),
mimeType(),
metaData()
{}
bool reload;
bool actionRequestedByUser;
int xOffset;
int yOffset;
QString mimeType;
QMap<QString, QString> metaData;
};
KParts::OpenUrlArguments::OpenUrlArguments()
: d(new OpenUrlArgumentsPrivate)
{
}
KParts::OpenUrlArguments::OpenUrlArguments(const OpenUrlArguments &other)
: d(other.d)
{
}
KParts::OpenUrlArguments & KParts::OpenUrlArguments::operator=( const OpenUrlArguments &other)
{
d = other.d;
return *this;
}
KParts::OpenUrlArguments::~OpenUrlArguments()
{
}
bool KParts::OpenUrlArguments::reload() const
{
return d->reload;
}
void KParts::OpenUrlArguments::setReload(bool b)
{
d->reload = b;
}
int KParts::OpenUrlArguments::xOffset() const
{
return d->xOffset;
}
void KParts::OpenUrlArguments::setXOffset(int x)
{
d->xOffset = x;
}
int KParts::OpenUrlArguments::yOffset() const
{
return d->yOffset;
}
void KParts::OpenUrlArguments::setYOffset(int y)
{
d->yOffset = y;
}
QString KParts::OpenUrlArguments::mimeType() const
{
return d->mimeType;
}
void KParts::OpenUrlArguments::setMimeType(const QString& mime)
{
d->mimeType = mime;
}
QMap<QString, QString> & KParts::OpenUrlArguments::metaData()
{
return d->metaData;
}
const QMap<QString, QString> & KParts::OpenUrlArguments::metaData() const
{
return d->metaData;
}
bool KParts::OpenUrlArguments::actionRequestedByUser() const
{
return d->actionRequestedByUser;
}
void KParts::OpenUrlArguments::setActionRequestedByUser(bool userRequested)
{
d->actionRequestedByUser = userRequested;
}
#include "moc_part.cpp"
diff --git a/kutils/kcmultidialog.cpp b/kutils/kcmultidialog.cpp
index fdcc69619c..28e8fed3a3 100644
--- a/kutils/kcmultidialog.cpp
+++ b/kutils/kcmultidialog.cpp
@@ -1,506 +1,506 @@
/*
Copyright (c) 2000 Matthias Elter <elter@kde.org>
Copyright (c) 2003 Daniel Molkentin <molkentin@kde.org>
Copyright (c) 2003,2006 Matthias Kretz <kretz@kde.org>
Copyright (c) 2004 Frans Englich <frans.englich@telia.com>
Copyright (c) 2006 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 "kcmultidialog.h"
#include "kcmultidialog_p.h"
#include <QtCore/QStringList>
#include <QtCore/QProcess>
#include <kauthorized.h>
#include <kguiitem.h>
#include <khbox.h>
#include <kicon.h>
#include <klocale.h>
#include <kpagewidgetmodel.h>
#include <kpushbutton.h>
#include <ktoolinvocation.h>
#include <kdebug.h>
#include <kmessagebox.h>
#include "kauthaction.h"
#include "kcolorscheme.h"
#include "kcmoduleloader.h"
#include "kcmoduleproxy.h"
bool KCMultiDialogPrivate::resolveChanges(KCModuleProxy *currentProxy)
{
Q_Q(KCMultiDialog);
if( !currentProxy || !currentProxy->changed() ) {
return true;
}
// Let the user decide
const int queryUser = KMessageBox::warningYesNoCancel(
q,
i18n("The settings of the current module have changed.\n"
"Do you want to apply the changes or discard them?"),
i18n("Apply Settings"),
KStandardGuiItem::apply(),
KStandardGuiItem::discard(),
KStandardGuiItem::cancel());
switch (queryUser) {
case KMessageBox::Yes:
return moduleSave(currentProxy);
case KMessageBox::No:
currentProxy->load();
return true;
case KMessageBox::Cancel:
return false;
default:
Q_ASSERT(false);
return false;
}
}
void KCMultiDialogPrivate::_k_slotCurrentPageChanged( KPageWidgetItem *current, KPageWidgetItem *previous )
{
Q_Q(KCMultiDialog);
kDebug(710);
q->blockSignals(true);
q->setCurrentPage(previous);
KCModuleProxy *previousModule = 0;
for ( int i = 0; i < modules.count(); ++i ) {
if ( modules[ i ].item == previous ) {
previousModule = modules[ i ].kcm;
break;
}
}
if( resolveChanges(previousModule) ) {
q->setCurrentPage(current);
}
q->blockSignals(false);
// We need to get the state of the now active module
_k_clientChanged();
}
void KCMultiDialogPrivate::_k_clientChanged()
{
Q_Q(KCMultiDialog);
kDebug(710);
// Get the current module
KCModuleProxy *activeModule = 0;
for ( int i = 0; i < modules.count(); ++i ) {
if ( modules[ i ].item == q->currentPage() ) {
activeModule = modules[ i ].kcm;
break;
}
}
bool change = false;
if (activeModule) {
change = activeModule->changed();
if (q->button(KDialog::Apply)) {
q->disconnect(q, SIGNAL(applyClicked()), q, SLOT(slotApplyClicked()));
q->disconnect(q->button(KDialog::Apply), SIGNAL(authorized(KAuth::Action*)), q, SLOT(slotApplyClicked()));
q->button(KDialog::Apply)->setEnabled(change);
}
if (q->button(KDialog::Ok)) {
q->disconnect(q, SIGNAL(okClicked()), q, SLOT(slotOkClicked()));
q->disconnect(q->button(KDialog::Ok), SIGNAL(authorized(KAuth::Action*)), q, SLOT(slotOkClicked()));
}
if (activeModule->realModule()->needsAuthorization()) {
if (q->button(KDialog::Apply)) {
q->button(KDialog::Apply)->setAuthAction(activeModule->realModule()->authAction());
activeModule->realModule()->authAction()->setParentWidget(activeModule->realModule());
q->connect(q->button(KDialog::Apply), SIGNAL(authorized(KAuth::Action*)), SLOT(slotApplyClicked()));
}
if (q->button(KDialog::Ok)) {
q->button(KDialog::Ok)->setAuthAction(activeModule->realModule()->authAction());
activeModule->realModule()->authAction()->setParentWidget(activeModule->realModule());
q->connect(q->button(KDialog::Ok), SIGNAL(authorized(KAuth::Action*)), SLOT(slotOkClicked()));
}
} else {
if (q->button(KDialog::Apply)) {
q->connect(q, SIGNAL(applyClicked()), SLOT(slotApplyClicked()));
q->button(KDialog::Apply)->setAuthAction(0);
}
if (q->button(KDialog::Ok)) {
q->connect(q, SIGNAL(okClicked()), SLOT(slotOkClicked()));
q->button(KDialog::Ok)->setAuthAction(0);
}
}
}
if (q->button(KDialog::Reset)) {
q->button(KDialog::Reset)->setEnabled(change);
}
if (q->button(KDialog::Apply)) {
q->button(KDialog::Apply)->setEnabled(change);
}
if (activeModule) {
q->enableButton(KDialog::Help, activeModule->buttons() & KCModule::Help);
q->enableButton(KDialog::Default, activeModule->buttons() & KCModule::Default);
}
}
void KCMultiDialogPrivate::_k_updateHeader(bool use, const QString &message)
{
Q_Q(KCMultiDialog);
KPageWidgetItem *item = q->currentPage();
KCModuleProxy *kcm = qobject_cast<KCModuleProxy*>(item->widget());
if (use) {
item->setHeader( "<b>"+kcm->moduleInfo().comment() + "</b><br><i>" +
message + "</i>" );
item->setIcon( KIcon( kcm->moduleInfo().icon(), 0, QStringList() << "dialog-warning" ) );
} else {
item->setHeader( kcm->moduleInfo().comment() );
item->setIcon( KIcon( kcm->moduleInfo().icon() ) );
}
}
void KCMultiDialogPrivate::_k_dialogClosed()
{
kDebug(710) ;
/**
* If we don't delete them, the DBUS registration stays, and trying to load the KCMs
* in other situations will lead to "module already loaded in Foo," while to the user
* doesn't appear so(the dialog is hidden)
*/
for ( int i = 0; i < modules.count(); ++i )
modules[ i ].kcm->deleteClient();
}
void KCMultiDialogPrivate::init()
{
Q_Q(KCMultiDialog);
q->setFaceType(KPageDialog::Auto);
q->setCaption(i18n("Configure"));
q->setButtons(KDialog::Help | KDialog::Default |KDialog::Cancel | KDialog::Apply | KDialog::Ok | KDialog::Reset);
q->setModal(false);
q->connect(q, SIGNAL(finished()), SLOT(_k_dialogClosed()));
q->connect(q, SIGNAL(applyClicked()), SLOT(slotApplyClicked()));
q->connect(q, SIGNAL(okClicked()), SLOT(slotOkClicked()));
q->connect(q, SIGNAL(defaultClicked()), SLOT(slotDefaultClicked()));
q->connect(q, SIGNAL(helpClicked()), SLOT(slotHelpClicked()));
q->connect(q, SIGNAL(user1Clicked()), SLOT(slotUser1Clicked()));
q->connect(q, SIGNAL(resetClicked()), SLOT(slotUser1Clicked()));
q->connect(q, SIGNAL(currentPageChanged(KPageWidgetItem*, KPageWidgetItem*)),
SLOT(_k_slotCurrentPageChanged(KPageWidgetItem*, KPageWidgetItem*)));
q->setInitialSize(QSize(800, 550));
}
KCMultiDialog::KCMultiDialog( QWidget *parent )
: KPageDialog(*new KCMultiDialogPrivate, NULL, parent)
{
d_func()->init();
}
KCMultiDialog::KCMultiDialog(KPageWidget *pageWidget, QWidget *parent, Qt::WFlags flags)
: KPageDialog(*new KCMultiDialogPrivate, pageWidget, parent, flags)
{
d_func()->init();
}
KCMultiDialog::KCMultiDialog(KCMultiDialogPrivate &dd, KPageWidget *pageWidget, QWidget *parent, Qt::WFlags flags)
: KPageDialog(dd, pageWidget, parent, flags)
{
d_func()->init();
}
KCMultiDialog::~KCMultiDialog()
{
}
void KCMultiDialog::slotDefaultClicked()
{
Q_D(KCMultiDialog);
const KPageWidgetItem *item = currentPage();
if ( !item )
return;
for ( int i = 0; i < d->modules.count(); ++i ) {
if ( d->modules[ i ].item == item ) {
d->modules[ i ].kcm->defaults();
d->_k_clientChanged();
return;
}
}
}
void KCMultiDialog::slotUser1Clicked()
{
const KPageWidgetItem *item = currentPage();
if ( !item )
return;
Q_D(KCMultiDialog);
for ( int i = 0; i < d->modules.count(); ++i ) {
if ( d->modules[ i ].item == item ) {
d->modules[ i ].kcm->load();
d->_k_clientChanged();
return;
}
}
}
bool KCMultiDialogPrivate::moduleSave(KCModuleProxy *module)
{
if( !module ) {
return false;
}
module->save();
return true;
}
void KCMultiDialogPrivate::apply()
{
Q_Q(KCMultiDialog);
QStringList updatedComponents;
foreach (const CreatedModule &module, modules) {
KCModuleProxy *proxy = module.kcm;
if (proxy->changed()) {
proxy->save();
/**
* Add name of the components the kcm belongs to the list
* of updated components.
*/
const QStringList componentNames = module.componentNames;
foreach (const QString &componentName, module.componentNames) {
if (!updatedComponents.contains(componentName)) {
updatedComponents.append(componentName);
}
}
}
}
// Send the configCommitted signal for every updated component.
foreach (const QString &name, updatedComponents) {
emit q->configCommitted(name.toLatin1());
}
emit q->configCommitted();
}
void KCMultiDialog::slotApplyClicked()
{
setButtonFocus( Apply );
d_func()->apply();
}
void KCMultiDialog::slotOkClicked()
{
setButtonFocus( Ok );
d_func()->apply();
accept();
}
void KCMultiDialog::slotHelpClicked()
{
const KPageWidgetItem *item = currentPage();
if ( !item )
return;
Q_D(KCMultiDialog);
QString docPath;
for ( int i = 0; i < d->modules.count(); ++i ) {
if ( d->modules[ i ].item == item ) {
docPath = d->modules[ i ].kcm->moduleInfo().docPath();
break;
}
}
KUrl docUrl( KUrl( "help:/" ), docPath );
- if ( docUrl.protocol() == "help" || docUrl.protocol() == "man" || docUrl.protocol() == "info" ) {
+ if ( docUrl.scheme() == "help" || docUrl.scheme() == "man" || docUrl.scheme() == "info" ) {
QProcess::startDetached("khelpcenter", QStringList() << docUrl.url());
} else {
KToolInvocation::invokeBrowser( docUrl.url() );
}
}
KPageWidgetItem* KCMultiDialog::addModule( const QString& path, const QStringList& args )
{
QString complete = path;
if ( !path.endsWith( QLatin1String(".desktop") ) )
complete += ".desktop";
KService::Ptr service = KService::serviceByStorageId( complete );
return addModule( KCModuleInfo( service ), 0, args );
}
KPageWidgetItem* KCMultiDialog::addModule( const KCModuleInfo& moduleInfo,
KPageWidgetItem *parentItem, const QStringList& args )
{
if ( !moduleInfo.service() )
return 0;
//KAuthorized::authorizeControlModule( moduleInfo.service()->menuId() ) is
//checked in noDisplay already
if ( moduleInfo.service()->noDisplay() )
return 0;
KCModuleProxy *kcm = new KCModuleProxy(moduleInfo, 0, args);
kDebug(710) << moduleInfo.moduleName();
KPageWidgetItem *item = new KPageWidgetItem(kcm, moduleInfo.moduleName());
if (kcm->useRootOnlyMessage()) {
item->setHeader( "<b>"+moduleInfo.comment() + "</b><br><i>" + kcm->rootOnlyMessage() + "</i>" );
item->setIcon( KIcon( moduleInfo.icon(), 0, QStringList() << "dialog-warning" ) );
} else {
item->setHeader( moduleInfo.comment() );
item->setIcon( KIcon( moduleInfo.icon() ) );
}
item->setProperty("_k_weight", moduleInfo.weight());
bool updateCurrentPage = false;
const KPageWidgetModel *model = qobject_cast<const KPageWidgetModel *>(pageWidget()->model());
Q_ASSERT(model);
if (parentItem) {
const QModelIndex parentIndex = model->index(parentItem);
const int siblingCount = model->rowCount(parentIndex);
int row = 0;
for (; row < siblingCount; ++row) {
KPageWidgetItem *siblingItem = model->item(parentIndex.child(row, 0));
if (siblingItem->property("_k_weight").toInt() > moduleInfo.weight()) {
// the item we found is heavier than the new module
kDebug(710) << "adding KCM " << item->name() << " before " << siblingItem->name();
insertPage(siblingItem, item);
break;
}
}
if (row >= siblingCount) {
// the new module is either the first or the heaviest item
kDebug(710) << "adding KCM " << item->name() << " with parent " << parentItem->name();
addSubPage(parentItem, item);
}
} else {
const int siblingCount = model->rowCount();
int row = 0;
for (; row < siblingCount; ++row) {
KPageWidgetItem *siblingItem = model->item(model->index(row, 0));
if (siblingItem->property("_k_weight").toInt() > moduleInfo.weight()) {
// the item we found is heavier than the new module
kDebug(710) << "adding KCM " << item->name() << " before " << siblingItem->name();
insertPage(siblingItem, item);
if ( siblingItem == currentPage() )
updateCurrentPage = true;
break;
}
}
if (row == siblingCount) {
// the new module is either the first or the heaviest item
kDebug(710) << "adding KCM " << item->name() << " at the top level";
addPage(item);
}
}
connect(kcm, SIGNAL(changed(bool)), this, SLOT(_k_clientChanged()));
connect(kcm->realModule(), SIGNAL(rootOnlyMessageChanged(bool,QString)), this, SLOT(_k_updateHeader(bool,QString)));
Q_D(KCMultiDialog);
KCMultiDialogPrivate::CreatedModule cm;
cm.kcm = kcm;
cm.item = item;
cm.componentNames = moduleInfo.service()->property( "X-KDE-ParentComponents" ).toStringList();
d->modules.append( cm );
if ( d->modules.count() == 1 || updateCurrentPage )
{
setCurrentPage( item );
d->_k_clientChanged();
}
return item;
}
void KCMultiDialog::clear()
{
Q_D(KCMultiDialog);
kDebug( 710 ) ;
for ( int i = 0; i < d->modules.count(); ++i ) {
removePage( d->modules[ i ].item );
delete d->modules[ i ].kcm;
}
d->modules.clear();
d->_k_clientChanged();
}
void KCMultiDialog::setButtons(ButtonCodes buttonMask)
{
KPageDialog::setButtons(buttonMask);
// Set Auto-Default mode ( KDE Bug #211187 )
if (buttonMask & KDialog::Ok) {
button(KDialog::Ok)->setAutoDefault(true);
}
if (buttonMask & KDialog::Apply) {
button(KDialog::Apply)->setAutoDefault(true);
}
if (buttonMask & KDialog::Default) {
button(KDialog::Default)->setAutoDefault(true);
}
if (buttonMask & KDialog::Reset) {
button(KDialog::Reset)->setAutoDefault(true);
}
if (buttonMask & KDialog::Cancel) {
button(KDialog::Cancel)->setAutoDefault(true);
}
if (buttonMask & KDialog::Help) {
button(KDialog::Help)->setAutoDefault(true);
}
// Old Reset Button
enableButton(KDialog::User1, false);
enableButton(KDialog::Reset, false);
enableButton(KDialog::Apply, false);
}
#include "moc_kcmultidialog.cpp"
diff --git a/plasma/containment.cpp b/plasma/containment.cpp
index daf86139c5..02565dd424 100644
--- a/plasma/containment.cpp
+++ b/plasma/containment.cpp
@@ -1,2430 +1,2430 @@
/*
* Copyright 2007 by Aaron Seigo <aseigo@kde.org>
* Copyright 2008 by Ménard Alexis <darktears31@gmail.com>
* Copyright 2009 Chani Armitage <chani@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 "containment.h"
#include "private/containment_p.h"
#include "config-plasma.h"
#include <QApplication>
#include <QClipboard>
#include <QFile>
#include <QGraphicsSceneContextMenuEvent>
#include <QGraphicsView>
#include <QMimeData>
#include <QPainter>
#include <QStyleOptionGraphicsItem>
#include <QGraphicsLayout>
#include <QGraphicsLinearLayout>
#include <qtemporaryfile.h>
#include <kaction.h>
#include <kauthorized.h>
#include <kicon.h>
#include <kmenu.h>
#include <kmessagebox.h>
#include <kmimetype.h>
#include <kservicetypetrader.h>
#include <kstandarddirs.h>
#include <kwindowsystem.h>
#ifndef PLASMA_NO_KIO
#include "kio/jobclasses.h" // for KIO::JobFlags
#include "kio/job.h"
#include "kio/scheduler.h"
#endif
#include "abstracttoolbox.h"
#include "animator.h"
#include "containmentactions.h"
#include "containmentactionspluginsconfig.h"
#include "corona.h"
#include "pluginloader.h"
#include "svg.h"
#include "wallpaper.h"
#include "remote/accessappletjob.h"
#include "remote/accessmanager.h"
#include "private/applet_p.h"
#include "private/containmentactionspluginsconfig_p.h"
#include "private/wallpaper_p.h"
#include "plasma/plasma.h"
namespace Plasma
{
bool ContainmentPrivate::s_positioningPanels = false;
QHash<QString, ContainmentActions*> ContainmentPrivate::globalActionPlugins;
static const char defaultWallpaper[] = "image";
static const char defaultWallpaperMode[] = "SingleImage";
Containment::StyleOption::StyleOption()
: QStyleOptionGraphicsItem(),
view(0)
{
version = Version;
type = Type;
}
Containment::StyleOption::StyleOption(const Containment::StyleOption & other)
: QStyleOptionGraphicsItem(other),
view(other.view)
{
version = Version;
type = Type;
}
Containment::StyleOption::StyleOption(const QStyleOptionGraphicsItem &other)
: QStyleOptionGraphicsItem(other),
view(0)
{
version = Version;
type = Type;
}
Containment::Containment(QGraphicsItem *parent,
const QString &serviceId,
uint containmentId)
: Applet(parent, serviceId, containmentId),
d(new ContainmentPrivate(this))
{
// WARNING: do not access config() OR globalConfig() in this method!
// that requires a scene, which is not available at this point
setPos(0, 0);
setBackgroundHints(NoBackground);
setContainmentType(CustomContainment);
setHasConfigurationInterface(false);
}
Containment::Containment(QObject *parent, const QVariantList &args)
: Applet(parent, args),
d(new ContainmentPrivate(this))
{
// WARNING: do not access config() OR globalConfig() in this method!
// that requires a scene, which is not available at this point
setPos(0, 0);
setBackgroundHints(NoBackground);
setHasConfigurationInterface(false);
}
Containment::Containment(const QString &packagePath, uint appletId, const QVariantList &args)
: Applet(packagePath, appletId, args),
d(new ContainmentPrivate(this))
{
// WARNING: do not access config() OR globalConfig() in this method!
// that requires a scene, which is not available at this point
setPos(0, 0);
setBackgroundHints(NoBackground);
setHasConfigurationInterface(false);
}
Containment::~Containment()
{
delete d;
// Applet touches our dptr if we are a containment and is the superclass (think of dtors)
// so we reset this as we exit the building
Applet::d->isContainment = false;
}
void Containment::init()
{
Applet::init();
if (!isContainment()) {
return;
}
setCacheMode(NoCache);
setFlag(QGraphicsItem::ItemIsMovable, false);
setFlag(QGraphicsItem::ItemClipsChildrenToShape, true);
setAcceptDrops(true);
setAcceptsHoverEvents(true);
if (d->type == NoContainmentType) {
setContainmentType(DesktopContainment);
}
//connect actions
ContainmentPrivate::addDefaultActions(d->actions(), this);
bool unlocked = immutability() == Mutable;
//fix the text of the actions that need name()
//btw, do we really want to use name() when it's a desktopcontainment?
QAction *closeApplet = action("remove");
if (closeApplet) {
closeApplet->setText(i18nc("%1 is the name of the applet", "Remove this %1", name()));
}
QAction *configAction = action("configure");
if (configAction) {
configAction->setText(i18nc("%1 is the name of the applet", "%1 Settings", name()));
}
QAction *appletBrowserAction = action("add widgets");
if (appletBrowserAction) {
appletBrowserAction->setVisible(unlocked);
appletBrowserAction->setEnabled(unlocked);
connect(appletBrowserAction, SIGNAL(triggered()), this, SLOT(triggerShowAddWidgets()));
}
QAction *act = action("next applet");
if (act) {
connect(act, SIGNAL(triggered()), this, SLOT(focusNextApplet()));
}
act = action("previous applet");
if (act) {
connect(act, SIGNAL(triggered()), this, SLOT(focusPreviousApplet()));
}
if (immutability() != SystemImmutable && corona()) {
QAction *lockDesktopAction = corona()->action("lock widgets");
//keep a pointer so nobody notices it moved to corona
if (lockDesktopAction) {
d->actions()->addAction("lock widgets", lockDesktopAction);
}
}
if (d->type != PanelContainment && d->type != CustomPanelContainment) {
if (corona()) {
//FIXME this is just here because of the darn keyboard shortcut :/
act = corona()->action("manage activities");
if (act) {
d->actions()->addAction("manage activities", act);
}
//a stupid hack to make this one's keyboard shortcut work
act = corona()->action("configure shortcuts");
if (act) {
d->actions()->addAction("configure shortcuts", act);
}
}
if (d->type == DesktopContainment) {
addToolBoxAction(action("add widgets"));
//TODO: do we need some way to allow this be overridden?
// it's always available because shells rely on this
// to offer their own custom configuration as well
QAction *configureContainment = action("configure");
if (configureContainment) {
addToolBoxAction(configureContainment);
}
}
}
}
void ContainmentPrivate::addDefaultActions(KActionCollection *actions, Containment *c)
{
actions->setConfigGroup("Shortcuts-Containment");
//adjust applet actions
KAction *appAction = qobject_cast<KAction*>(actions->action("remove"));
appAction->setShortcut(KShortcut("alt+d, alt+r"));
if (c && c->d->isPanelContainment()) {
appAction->setText(i18n("Remove this Panel"));
} else {
appAction->setText(i18n("Remove this Activity"));
}
appAction = qobject_cast<KAction*>(actions->action("configure"));
if (appAction) {
appAction->setShortcut(KShortcut("alt+d, alt+s"));
appAction->setText(i18n("Activity Settings"));
}
//add our own actions
KAction *appletBrowserAction = actions->addAction("add widgets");
appletBrowserAction->setAutoRepeat(false);
appletBrowserAction->setText(i18n("Add Widgets..."));
appletBrowserAction->setIcon(KIcon("list-add"));
appletBrowserAction->setShortcut(KShortcut("alt+d, a"));
appletBrowserAction->setData(AbstractToolBox::AddTool);
KAction *action = actions->addAction("next applet");
action->setText(i18n("Next Widget"));
//no icon
action->setShortcut(KShortcut("alt+d, n"));
action->setData(AbstractToolBox::ControlTool);
action = actions->addAction("previous applet");
action->setText(i18n("Previous Widget"));
//no icon
action->setShortcut(KShortcut("alt+d, p"));
action->setData(AbstractToolBox::ControlTool);
}
// helper function for sorting the list of applets
bool appletConfigLessThan(const KConfigGroup &c1, const KConfigGroup &c2)
{
QPointF p1 = c1.readEntry("geometry", QRectF()).topLeft();
QPointF p2 = c2.readEntry("geometry", QRectF()).topLeft();
if (!qFuzzyCompare(p1.x(), p2.x())) {
if (QApplication::layoutDirection() == Qt::RightToLeft) {
return p1.x() > p2.x();
}
return p1.x() < p2.x();
}
return qFuzzyCompare(p1.y(), p2.y()) || p1.y() < p2.y();
}
void Containment::restore(KConfigGroup &group)
{
/*
#ifndef NDEBUG
kDebug() << "!!!!!!!!!!!!initConstraints" << group.name() << d->type;
kDebug() << " location:" << group.readEntry("location", (int)d->location);
kDebug() << " geom:" << group.readEntry("geometry", geometry());
kDebug() << " formfactor:" << group.readEntry("formfactor", (int)d->formFactor);
kDebug() << " screen:" << group.readEntry("screen", d->screen);
#endif
*/
if (!isContainment()) {
Applet::restore(group);
return;
}
QRectF geo = group.readEntry("geometry", geometry());
//override max/min
//this ensures panels are set to their saved size even when they have max & min set to prevent
//resizing
if (geo.size() != geo.size().boundedTo(maximumSize())) {
setMaximumSize(maximumSize().expandedTo(geo.size()));
}
if (geo.size() != geo.size().expandedTo(minimumSize())) {
setMinimumSize(minimumSize().boundedTo(geo.size()));
}
resize(geo.size());
//are we an offscreen containment?
if (containmentType() != PanelContainment && containmentType() != CustomPanelContainment && geo.right() < 0) {
corona()->addOffscreenWidget(this);
}
setLocation((Plasma::Location)group.readEntry("location", (int)d->location));
setFormFactor((Plasma::FormFactor)group.readEntry("formfactor", (int)d->formFactor));
//kDebug() << "setScreen from restore";
d->lastScreen = group.readEntry("lastScreen", d->lastScreen);
d->lastDesktop = group.readEntry("lastDesktop", d->lastDesktop);
d->setScreen(group.readEntry("screen", d->screen), group.readEntry("desktop", d->desktop), false);
d->activityId = group.readEntry("activityId", QString());
flushPendingConstraintsEvents();
restoreContents(group);
setImmutability((ImmutabilityType)group.readEntry("immutability", (int)Mutable));
setWallpaper(group.readEntry("wallpaperplugin", defaultWallpaper),
group.readEntry("wallpaperpluginmode", defaultWallpaperMode));
if (d->toolBox) {
d->toolBox.data()->restore(group);
}
KConfigGroup cfg;
if (containmentType() == PanelContainment || containmentType() == CustomPanelContainment) {
//don't let global desktop actions conflict with panels
//this also prevents panels from sharing config with each other
//but the panels aren't configurable anyways, and I doubt that'll change.
d->containmentActionsSource = ContainmentPrivate::Local;
cfg = KConfigGroup(&group, "ActionPlugins");
} else {
const QString source = group.readEntry("ActionPluginsSource", QString());
if (source == "Global") {
cfg = KConfigGroup(corona()->config(), "ActionPlugins");
d->containmentActionsSource = ContainmentPrivate::Global;
} else if (source == "Activity") {
cfg = KConfigGroup(corona()->config(), "Activities");
cfg = KConfigGroup(&cfg, d->activityId);
cfg = KConfigGroup(&cfg, "ActionPlugins");
d->containmentActionsSource = ContainmentPrivate::Activity;
} else if (source == "Local") {
cfg = group;
d->containmentActionsSource = ContainmentPrivate::Local;
} else {
//default to global
//but, if there is no global config, try copying it from local.
cfg = KConfigGroup(corona()->config(), "ActionPlugins");
if (!cfg.exists()) {
cfg = KConfigGroup(&group, "ActionPlugins");
}
d->containmentActionsSource = ContainmentPrivate::Global;
group.writeEntry("ActionPluginsSource", "Global");
}
}
//kDebug() << cfg.keyList();
if (cfg.exists()) {
foreach (const QString &key, cfg.keyList()) {
//kDebug() << "loading" << key;
setContainmentActions(key, cfg.readEntry(key, QString()));
}
} else { //shell defaults
ContainmentActionsPluginsConfig conf = corona()->containmentActionsDefaults(d->type);
//steal the data directly, for efficiency
QHash<QString,QString> defaults = conf.d->plugins;
for (QHash<QString,QString>::const_iterator it = defaults.constBegin(),
end = defaults.constEnd(); it != end; ++it) {
setContainmentActions(it.key(), it.value());
}
}
/*
#ifndef NDEBUG
kDebug() << "Containment" << id() <<
#endif
"screen" << screen() <<
"geometry is" << geometry() <<
"wallpaper" << ((d->wallpaper) ? d->wallpaper->pluginName() : QString()) <<
"wallpaper mode" << wallpaperMode() <<
"config entries" << group.entryMap();
*/
}
void Containment::save(KConfigGroup &g) const
{
if (Applet::d->transient) {
return;
}
KConfigGroup group = g;
if (!group.isValid()) {
group = config();
}
// locking is saved in Applet::save
Applet::save(group);
if (!isContainment()) {
return;
}
group.writeEntry("screen", d->screen);
group.writeEntry("lastScreen", d->lastScreen);
group.writeEntry("desktop", d->desktop);
group.writeEntry("lastDesktop", d->lastDesktop);
group.writeEntry("formfactor", (int)d->formFactor);
group.writeEntry("location", (int)d->location);
group.writeEntry("activityId", d->activityId);
if (d->toolBox) {
d->toolBox.data()->save(group);
}
if (d->wallpaper) {
group.writeEntry("wallpaperplugin", d->wallpaper->pluginName());
group.writeEntry("wallpaperpluginmode", d->wallpaper->renderingMode().name());
if (d->wallpaper->isInitialized()) {
KConfigGroup wallpaperConfig(&group, "Wallpaper");
wallpaperConfig = KConfigGroup(&wallpaperConfig, d->wallpaper->pluginName());
d->wallpaper->save(wallpaperConfig);
}
}
saveContents(group);
}
void Containment::saveContents(KConfigGroup &group) const
{
KConfigGroup applets(&group, "Applets");
foreach (const Applet *applet, d->applets) {
KConfigGroup appletConfig(&applets, QString::number(applet->id()));
applet->save(appletConfig);
}
}
void ContainmentPrivate::initApplets()
{
foreach (Applet *applet, applets) {
applet->restore(*applet->d->mainConfigGroup());
applet->init();
#ifndef NDEBUG
kDebug() << "!!{} STARTUP TIME" << QTime().msecsTo(QTime::currentTime()) << "Applet" << applet->name();
#endif
}
q->flushPendingConstraintsEvents();
foreach (Applet *applet, applets) {
applet->flushPendingConstraintsEvents();
}
#ifndef NDEBUG
kDebug() << "!!{} STARTUP TIME" << QTime().msecsTo(QTime::currentTime()) << "Containment's applets initialized" << q->name();
#endif
}
void Containment::restoreContents(KConfigGroup &group)
{
KConfigGroup applets(&group, "Applets");
// Sort the applet configs in order of geometry to ensure that applets
// are added from left to right or top to bottom for a panel containment
QList<KConfigGroup> appletConfigs;
foreach (const QString &appletGroup, applets.groupList()) {
//kDebug() << "reading from applet group" << appletGroup;
KConfigGroup appletConfig(&applets, appletGroup);
appletConfigs.append(appletConfig);
}
qStableSort(appletConfigs.begin(), appletConfigs.end(), appletConfigLessThan);
QMutableListIterator<KConfigGroup> it(appletConfigs);
while (it.hasNext()) {
KConfigGroup &appletConfig = it.next();
int appId = appletConfig.name().toUInt();
QString plugin = appletConfig.readEntry("plugin", QString());
if (plugin.isEmpty()) {
continue;
}
d->addApplet(plugin, QVariantList(), appletConfig.readEntry("geometry", QRectF()), appId, true);
}
}
Containment::Type Containment::containmentType() const
{
return d->type;
}
void Containment::setContainmentType(Containment::Type type)
{
if (d->type == type) {
return;
}
delete d->toolBox.data();
d->type = type;
d->checkContainmentFurniture();
}
void ContainmentPrivate::checkContainmentFurniture()
{
if (q->isContainment() &&
(type == Containment::DesktopContainment || type == Containment::PanelContainment)) {
createToolBox();
}
}
Corona *Containment::corona() const
{
return qobject_cast<Corona*>(scene());
}
void Containment::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
event->ignore();
if (d->wallpaper && d->wallpaper->isInitialized()) {
QGraphicsItem *item = scene()->itemAt(event->scenePos());
if (item == this) {
d->wallpaper->mouseMoveEvent(event);
}
}
if (!event->isAccepted()) {
event->accept();
Applet::mouseMoveEvent(event);
}
}
void Containment::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
event->ignore();
if (d->appletAt(event->scenePos())) {
return; //no unexpected click-throughs
}
if (d->wallpaper && d->wallpaper->isInitialized() && !event->isAccepted()) {
d->wallpaper->mousePressEvent(event);
}
if (event->isAccepted()) {
setFocus(Qt::MouseFocusReason);
} else if (event->button() == Qt::RightButton && event->modifiers() == Qt::NoModifier) {
// we'll catch this in the context menu even
Applet::mousePressEvent(event);
} else {
QString trigger = ContainmentActions::eventToString(event);
if (d->prepareContainmentActions(trigger, event->screenPos())) {
d->actionPlugins()->value(trigger)->contextEvent(event);
}
if (!event->isAccepted()) {
Applet::mousePressEvent(event);
}
}
}
void Containment::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
event->ignore();
if (d->appletAt(event->scenePos())) {
return; //no unexpected click-throughs
}
QString trigger = ContainmentActions::eventToString(event);
if (d->wallpaper && d->wallpaper->isInitialized()) {
d->wallpaper->mouseReleaseEvent(event);
}
if (!event->isAccepted() && isContainment()) {
if (d->prepareContainmentActions(trigger, event->screenPos())) {
d->actionPlugins()->value(trigger)->contextEvent(event);
}
event->accept();
Applet::mouseReleaseEvent(event);
}
}
void Containment::showDropZone(const QPoint pos)
{
Q_UNUSED(pos)
//Base implementation does nothing, don't put code here
}
void Containment::showContextMenu(const QPointF &containmentPos, const QPoint &screenPos)
{
//kDebug() << containmentPos << screenPos;
QGraphicsSceneContextMenuEvent gvevent;
gvevent.setScreenPos(screenPos);
gvevent.setScenePos(mapToScene(containmentPos));
gvevent.setPos(containmentPos);
gvevent.setReason(QGraphicsSceneContextMenuEvent::Mouse);
gvevent.setWidget(view());
contextMenuEvent(&gvevent);
}
void Containment::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
{
if (!isContainment() || !KAuthorized::authorizeKAction("plasma/containment_context_menu")) {
Applet::contextMenuEvent(event);
return;
}
KMenu desktopMenu;
Applet *applet = d->appletAt(event->scenePos());
//kDebug() << "context menu event " << (QObject*)applet;
if (applet) {
d->addAppletActions(desktopMenu, applet, event);
} else {
d->addContainmentActions(desktopMenu, event);
}
//kDebug() << "executing at" << screenPos;
QMenu *menu = &desktopMenu;
//kDebug() << "showing menu, actions" << desktopMenu.actions().size() << desktopMenu.actions().first()->menu();
if (desktopMenu.actions().size() == 1 && desktopMenu.actions().first()->menu()) {
// we have a menu with a single top level menu; just show that top level menu instad.
menu = desktopMenu.actions().first()->menu();
}
if (!menu->isEmpty()) {
QPoint pos = event->screenPos();
if (applet && d->isPanelContainment()) {
menu->adjustSize();
pos = applet->popupPosition(menu->size());
if (event->reason() == QGraphicsSceneContextMenuEvent::Mouse) {
// if the menu pops up way away from the mouse press, then move it
// to the mouse press
if (d->formFactor == Vertical) {
if (pos.y() + menu->height() < event->screenPos().y()) {
pos.setY(event->screenPos().y());
}
} else if (d->formFactor == Horizontal) {
if (pos.x() + menu->width() < event->screenPos().x()) {
pos.setX(event->screenPos().x());
}
}
}
}
menu->exec(pos);
event->accept();
} else {
Applet::contextMenuEvent(event);
}
}
void ContainmentPrivate::addContainmentActions(KMenu &desktopMenu, QEvent *event)
{
if (static_cast<Corona*>(q->scene())->immutability() != Mutable &&
!KAuthorized::authorizeKAction("plasma/containment_actions")) {
//kDebug() << "immutability";
return;
}
const QString trigger = ContainmentActions::eventToString(event);
prepareContainmentActions(trigger, QPoint(), &desktopMenu);
}
void ContainmentPrivate::addAppletActions(KMenu &desktopMenu, Applet *applet, QEvent *event)
{
foreach (QAction *action, applet->contextualActions()) {
if (action) {
desktopMenu.addAction(action);
}
}
if (!applet->d->failed) {
QAction *configureApplet = applet->d->actions->action("configure");
if (configureApplet && configureApplet->isEnabled()) {
desktopMenu.addAction(configureApplet);
}
QAction *runAssociatedApplication = applet->d->actions->action("run associated application");
if (runAssociatedApplication && runAssociatedApplication->isEnabled()) {
desktopMenu.addAction(runAssociatedApplication);
}
}
KMenu *containmentMenu = new KMenu(i18nc("%1 is the name of the containment", "%1 Options", q->name()), &desktopMenu);
addContainmentActions(*containmentMenu, event);
if (!containmentMenu->isEmpty()) {
int enabled = 0;
//count number of real actions
QListIterator<QAction *> actionsIt(containmentMenu->actions());
while (enabled < 3 && actionsIt.hasNext()) {
QAction *action = actionsIt.next();
if (action->isVisible() && !action->isSeparator()) {
++enabled;
}
}
if (enabled) {
//if there is only one, don't create a submenu
if (enabled < 2) {
foreach (QAction *action, containmentMenu->actions()) {
if (action->isVisible() && !action->isSeparator()) {
desktopMenu.addAction(action);
}
}
} else {
desktopMenu.addMenu(containmentMenu);
}
}
}
if (q->immutability() == Mutable) {
QAction *closeApplet = applet->d->actions->action("remove");
//kDebug() << "checking for removal" << closeApplet;
if (closeApplet) {
if (!desktopMenu.isEmpty()) {
desktopMenu.addSeparator();
}
//kDebug() << "adding close action" << closeApplet->isEnabled() << closeApplet->isVisible();
desktopMenu.addAction(closeApplet);
}
}
}
Applet* ContainmentPrivate::appletAt(const QPointF &point)
{
Applet *applet = 0;
QGraphicsItem *item = q->scene()->itemAt(point);
if (item == q) {
item = 0;
}
while (item) {
if (item->isWidget()) {
applet = qobject_cast<Applet*>(static_cast<QGraphicsWidget*>(item));
if (applet) {
if (applet->isContainment()) {
applet = 0;
}
break;
}
}
AppletHandle *handle = dynamic_cast<AppletHandle*>(item);
if (handle) {
//pretend it was on the applet
applet = handle->applet();
break;
}
item = item->parentItem();
}
return applet;
}
void Containment::setFormFactor(FormFactor formFactor)
{
if (d->formFactor == formFactor) {
return;
}
//kDebug() << "switching FF to " << formFactor;
d->formFactor = formFactor;
if (isContainment() &&
(d->type == PanelContainment || d->type == CustomPanelContainment)) {
// we are a panel and we have chaged our orientation
d->positionPanel(true);
}
if (d->toolBox) {
d->toolBox.data()->reposition();
}
updateConstraints(Plasma::FormFactorConstraint);
KConfigGroup c = config();
c.writeEntry("formfactor", (int)formFactor);
emit configNeedsSaving();
}
void Containment::setLocation(Location location)
{
if (d->location == location) {
return;
}
bool emitGeomChange = false;
if ((location == TopEdge || location == BottomEdge) &&
(d->location == TopEdge || d->location == BottomEdge)) {
emitGeomChange = true;
}
if ((location == RightEdge || location == LeftEdge) &&
(d->location == RightEdge || d->location == LeftEdge)) {
emitGeomChange = true;
}
d->location = location;
foreach (Applet *applet, d->applets) {
applet->updateConstraints(Plasma::LocationConstraint);
}
if (emitGeomChange) {
// our geometry on the scene will not actually change,
// but for the purposes of views it has
emit geometryChanged();
}
updateConstraints(Plasma::LocationConstraint);
KConfigGroup c = config();
c.writeEntry("location", (int)location);
emit configNeedsSaving();
}
void Containment::addSiblingContainment()
{
emit addSiblingContainment(this);
}
void Containment::clearApplets()
{
foreach (Applet *applet, d->applets) {
applet->d->cleanUpAndDelete();
}
d->applets.clear();
}
Applet *Containment::addApplet(const QString &name, const QVariantList &args,
const QRectF &appletGeometry)
{
return d->addApplet(name, args, appletGeometry);
}
void Containment::addApplet(Applet *applet, const QPointF &pos, bool delayInit)
{
if (!isContainment() || (!delayInit && immutability() != Mutable)) {
return;
}
if (!applet) {
#ifndef NDEBUG
kDebug() << "adding null applet!?!";
#endif
return;
}
if (d->applets.contains(applet)) {
#ifndef NDEBUG
kDebug() << "already have this applet!";
#endif
}
Containment *currentContainment = applet->containment();
if (d->type == PanelContainment) {
//panels don't want backgrounds, which is important when setting geometry
setBackgroundHints(NoBackground);
}
if (currentContainment && currentContainment != this) {
emit currentContainment->appletRemoved(applet);
if (currentContainment->d->focusedApplet == applet) {
currentContainment->d->focusedApplet = 0;
}
disconnect(applet, 0, currentContainment, 0);
KConfigGroup oldConfig = applet->config();
currentContainment->d->applets.removeAll(applet);
applet->setParentItem(this);
applet->setParent(this);
// now move the old config to the new location
//FIXME: this doesn't seem to get the actual main config group containing plugin=, etc
KConfigGroup c = config().group("Applets").group(QString::number(applet->id()));
oldConfig.reparent(&c);
applet->d->resetConfigurationObject();
disconnect(applet, SIGNAL(activate()), currentContainment, SIGNAL(activate()));
} else {
applet->setParentItem(this);
applet->setParent(this);
}
d->applets << applet;
connect(applet, SIGNAL(configNeedsSaving()), this, SIGNAL(configNeedsSaving()));
connect(applet, SIGNAL(releaseVisualFocus()), this, SIGNAL(releaseVisualFocus()));
connect(applet, SIGNAL(appletDestroyed(Plasma::Applet*)), this, SLOT(appletDestroyed(Plasma::Applet*)));
connect(applet, SIGNAL(newStatus(Plasma::ItemStatus)), this, SLOT(checkStatus(Plasma::ItemStatus)));
connect(applet, SIGNAL(activate()), this, SIGNAL(activate()));
if (pos != QPointF(-1, -1)) {
applet->setPos(pos);
}
if (!delayInit && !currentContainment) {
applet->restore(*applet->d->mainConfigGroup());
applet->init();
//FIXME: an on-appear animation would be nice to have again
d->appletAppeared(applet);
}
applet->setFlag(QGraphicsItem::ItemIsMovable, true);
applet->updateConstraints(Plasma::AllConstraints);
if (!delayInit) {
applet->flushPendingConstraintsEvents();
}
emit appletAdded(applet, pos);
if (!currentContainment) {
applet->updateConstraints(Plasma::StartupCompletedConstraint);
if (!delayInit) {
applet->flushPendingConstraintsEvents();
}
}
if (!delayInit) {
applet->d->scheduleModificationNotification();
}
}
Applet::List Containment::applets() const
{
return d->applets;
}
void Containment::setScreen(int newScreen, int newDesktop)
{
d->setScreen(newScreen, newDesktop);
}
void ContainmentPrivate::setScreen(int newScreen, int newDesktop, bool preventInvalidDesktops)
{
// What we want to do in here is:
// * claim the screen as our own
// * signal whatever may be watching this containment about the switch
// * if we are a full screen containment, then:
// * resize to match the screen if we're that kind of containment
// * kick other full-screen containments off this screen
// * if we had a screen, then give our screen to the containment
// we kick out
//
// a screen of -1 means no associated screen.
Corona *corona = q->corona();
Q_ASSERT(corona);
//if it's an offscreen widget, don't allow to claim a screen, after all it's *off*screen
if (corona->offscreenWidgets().contains(q)) {
return;
}
int numScreens = corona->numScreens();
if (newScreen < -1) {
newScreen = -1;
}
// -1 == All desktops
if (newDesktop < -1 || (preventInvalidDesktops && newDesktop > KWindowSystem::numberOfDesktops() - 1)) {
newDesktop = -1;
}
//kDebug() << activity() << "setting screen to " << newScreen << newDesktop << "and type is" << type;
Containment *swapScreensWith(0);
const bool isDesktopContainment = type == Containment::DesktopContainment ||
type == Containment::CustomContainment;
if (isDesktopContainment) {
// we want to listen to changes in work area if our screen changes
if (toolBox) {
if (screen < 0 && newScreen > -1) {
QObject::connect(KWindowSystem::self(), SIGNAL(workAreaChanged()), toolBox.data(), SLOT(reposition()), Qt::UniqueConnection);
} else if (newScreen < 0) {
QObject::disconnect(KWindowSystem::self(), SIGNAL(workAreaChanged()), toolBox.data(), SLOT(reposition()));
}
}
if (newScreen > -1) {
// sanity check to make sure someone else doesn't have this screen already!
Containment *currently = corona->containmentForScreen(newScreen, newDesktop);
if (currently && currently != q) {
#ifndef NDEBUG
kDebug() << "currently is on screen" << currently->screen()
<< "desktop" << currently->desktop()
<< "and is" << currently->activity()
<< (QObject*)currently << "i'm" << (QObject*)q;
#endif
currently->setScreen(-1, currently->desktop());
swapScreensWith = currently;
}
}
}
if (newScreen < numScreens && newScreen > -1 && isDesktopContainment) {
q->resize(corona->screenGeometry(newScreen).size());
}
int oldDesktop = desktop;
desktop = newDesktop;
int oldScreen = screen;
screen = newScreen;
q->updateConstraints(Plasma::ScreenConstraint);
if (oldScreen != newScreen || oldDesktop != newDesktop) {
/*
#ifndef NDEBUG
kDebug() << "going to signal change for" << q
#endif
<< ", old screen & desktop:" << oldScreen << oldDesktop
<< ", new:" << screen << desktop;
*/
KConfigGroup c = q->config();
c.writeEntry("screen", screen);
c.writeEntry("desktop", desktop);
if (newScreen != -1) {
lastScreen = newScreen;
lastDesktop = newDesktop;
c.writeEntry("lastScreen", lastScreen);
c.writeEntry("lastDesktop", lastDesktop);
}
emit q->configNeedsSaving();
emit q->screenChanged(oldScreen, newScreen, q);
}
if (swapScreensWith) {
//kDebug() << "setScreen due to swap, part 2";
swapScreensWith->setScreen(oldScreen, oldDesktop);
}
checkRemoveAction();
if (newScreen >= 0) {
emit q->activate();
}
}
int Containment::screen() const
{
return d->screen;
}
int Containment::lastScreen() const
{
return d->lastScreen;
}
int Containment::desktop() const
{
return d->desktop;
}
int Containment::lastDesktop() const
{
return d->lastDesktop;
}
KPluginInfo::List Containment::listContainments(const QString &category,
const QString &parentApp)
{
return listContainmentsOfType(QString(), category, parentApp);
}
KPluginInfo::List Containment::listContainmentsOfType(const QString &type,
const QString &category,
const QString &parentApp)
{
QString constraint;
if (parentApp.isEmpty()) {
constraint.append("(not exist [X-KDE-ParentApp] or [X-KDE-ParentApp] == '')");
} else {
constraint.append("[X-KDE-ParentApp] == '").append(parentApp).append("'");
}
if (!type.isEmpty()) {
if (!constraint.isEmpty()) {
constraint.append(" and ");
}
constraint.append("'").append(type).append("' ~in [X-Plasma-ContainmentCategories]");
}
if (!category.isEmpty()) {
if (!constraint.isEmpty()) {
constraint.append(" and ");
}
constraint.append("[X-KDE-PluginInfo-Category] == '").append(category).append("'");
if (category == "Miscellaneous") {
constraint.append(" or (not exist [X-KDE-PluginInfo-Category] or [X-KDE-PluginInfo-Category] == '')");
}
}
KService::List offers = KServiceTypeTrader::self()->query("Plasma/Containment", constraint);
//kDebug() << "constraint was" << constraint << "which got us" << offers.count() << "matches";
return KPluginInfo::fromServices(offers);
}
KPluginInfo::List Containment::listContainmentsForMimeType(const QString &mimeType)
{
const QString constraint = QString("'%1' in [X-Plasma-DropMimeTypes]").arg(mimeType);
//kDebug() << mimeType << constraint;
const KService::List offers = KServiceTypeTrader::self()->query("Plasma/Containment", constraint);
return KPluginInfo::fromServices(offers);
}
QStringList Containment::listContainmentTypes()
{
KPluginInfo::List containmentInfos = listContainments();
QSet<QString> types;
foreach (const KPluginInfo &containmentInfo, containmentInfos) {
QStringList theseTypes = containmentInfo.service()->property("X-Plasma-ContainmentCategories").toStringList();
foreach (const QString &type, theseTypes) {
types.insert(type);
}
}
return types.toList();
}
void Containment::dragEnterEvent(QGraphicsSceneDragDropEvent *event)
{
//kDebug() << immutability() << Mutable << (immutability() == Mutable);
event->setAccepted(immutability() == Mutable &&
(event->mimeData()->hasFormat(static_cast<Corona*>(scene())->appletMimeType()) ||
KUrl::List::canDecode(event->mimeData())));
if (!event->isAccepted()) {
// check to see if we have an applet that accepts the format.
QStringList formats = event->mimeData()->formats();
foreach (const QString &format, formats) {
KPluginInfo::List appletList = Applet::listAppletInfoForMimeType(format);
if (!appletList.isEmpty()) {
event->setAccepted(true);
break;
}
}
if (!event->isAccepted()) {
foreach (const QString &format, formats) {
KPluginInfo::List wallpaperList = Wallpaper::listWallpaperInfoForMimetype(format);
if (!wallpaperList.isEmpty()) {
event->setAccepted(true);
break;
}
}
}
}
if (event->isAccepted()) {
if (d->dropZoneStarted) {
showDropZone(event->pos().toPoint());
} else {
if (!d->showDropZoneDelayTimer) {
d->showDropZoneDelayTimer = new QTimer(this);
d->showDropZoneDelayTimer->setInterval(300);
d->showDropZoneDelayTimer->setSingleShot(true);
connect(d->showDropZoneDelayTimer, SIGNAL(timeout()), this, SLOT(showDropZoneDelayed()));
}
d->dropPoints.insert(0, event->pos());
d->showDropZoneDelayTimer->start();
}
}
}
void Containment::dragLeaveEvent(QGraphicsSceneDragDropEvent *event)
{
//kDebug() << event->pos() << size().height() << size().width();
if (d->showDropZoneDelayTimer) {
d->showDropZoneDelayTimer->stop();
}
if (event->pos().y() < 1 || event->pos().y() > size().height() ||
event->pos().x() < 1 || event->pos().x() > size().width()) {
showDropZone(QPoint());
d->dropZoneStarted = false;
}
}
void ContainmentPrivate::showDropZoneDelayed()
{
dropZoneStarted = true;
q->showDropZone(dropPoints.value(0).toPoint());
dropPoints.remove(0);
}
void Containment::dragMoveEvent(QGraphicsSceneDragDropEvent *event)
{
QGraphicsItem *item = scene()->itemAt(event->scenePos());
event->setAccepted(item == this || item == d->toolBox.data() || !item);
//kDebug() << event->isAccepted() << d->showDropZoneDelayTimer->isActive();
if (!event->isAccepted()) {
if (d->showDropZoneDelayTimer) {
d->showDropZoneDelayTimer->stop();
}
} else if (!d->showDropZoneDelayTimer->isActive() && immutability() == Plasma::Mutable) {
showDropZone(event->pos().toPoint());
}
}
void Containment::dropEvent(QGraphicsSceneDragDropEvent *event)
{
if (isContainment()) {
d->dropData(event->scenePos(), event->screenPos(), event);
} else {
Applet::dropEvent(event);
}
}
void ContainmentPrivate::dropData(QPointF scenePos, QPoint screenPos, QGraphicsSceneDragDropEvent *dropEvent)
{
if (q->immutability() != Mutable) {
return;
}
QPointF pos = q->mapFromScene(scenePos);
const QMimeData *mimeData = 0;
if (dropEvent) {
mimeData = dropEvent->mimeData();
} else {
QClipboard *clipboard = QApplication::clipboard();
mimeData = clipboard->mimeData(QClipboard::Selection);
//TODO if that's not supported (ie non-linux) should we try clipboard instead of selection?
}
if (!mimeData) {
//Selection is either empty or not supported on this OS
#ifndef NDEBUG
kDebug() << "no mime data";
#endif
return;
}
//kDebug() << event->mimeData()->text();
QString appletMimetype(q->corona() ? q->corona()->appletMimeType() : QString());
if (!appletMimetype.isEmpty() && mimeData->hasFormat(appletMimetype)) {
QString data = mimeData->data(appletMimetype);
const QStringList appletNames = data.split('\n', QString::SkipEmptyParts);
foreach (const QString &appletName, appletNames) {
//kDebug() << "doing" << appletName;
QRectF geom(pos, QSize(0, 0));
q->addApplet(appletName, QVariantList(), geom);
}
if (dropEvent) {
dropEvent->acceptProposedAction();
}
} else if (KUrl::List::canDecode(mimeData)) {
//TODO: collect the mimeTypes of available script engines and offer
// to create widgets out of the matching URLs, if any
const KUrl::List urls = KUrl::List::fromMimeData(mimeData);
foreach (const KUrl &url, urls) {
- if (AccessManager::supportedProtocols().contains(url.protocol())) {
+ if (AccessManager::supportedProtocols().contains(url.scheme())) {
AccessAppletJob *job = AccessManager::self()->accessRemoteApplet(url);
if (dropEvent) {
dropPoints[job] = dropEvent->pos();
} else {
dropPoints[job] = scenePos;
}
QObject::connect(AccessManager::self(), SIGNAL(finished(Plasma::AccessAppletJob*)),
q, SLOT(remoteAppletReady(Plasma::AccessAppletJob*)));
}
#ifndef PLASMA_NO_KIO
else {
KMimeType::Ptr mime = KMimeType::findByUrl(url);
QString mimeName = mime->name();
QRectF geom(pos, QSize());
QVariantList args;
args << url.url();
#ifndef NDEBUG
kDebug() << "can decode" << mimeName << args;
#endif
// It may be a directory or a file, let's stat
KIO::JobFlags flags = KIO::HideProgressInfo;
KIO::MimetypeJob *job = KIO::mimetype(url, flags);
if (dropEvent) {
dropPoints[job] = dropEvent->pos();
} else {
dropPoints[job] = scenePos;
}
QObject::connect(job, SIGNAL(result(KJob*)), q, SLOT(dropJobResult(KJob*)));
QObject::connect(job, SIGNAL(mimeType(KIO::Job *, const QString&)),
q, SLOT(mimeTypeRetrieved(KIO::Job *, const QString&)));
KMenu *choices = new KMenu("Content dropped");
choices->addAction(KIcon("process-working"), i18n("Fetching file type..."));
if (dropEvent) {
choices->popup(dropEvent->screenPos());
} else {
choices->popup(screenPos);
}
dropMenus[job] = choices;
}
#endif
}
if (dropEvent) {
dropEvent->acceptProposedAction();
}
} else {
QStringList formats = mimeData->formats();
QHash<QString, KPluginInfo> seenPlugins;
QHash<QString, QString> pluginFormats;
foreach (const QString &format, formats) {
KPluginInfo::List plugins = Applet::listAppletInfoForMimeType(format);
foreach (const KPluginInfo &plugin, plugins) {
if (seenPlugins.contains(plugin.pluginName())) {
continue;
}
seenPlugins.insert(plugin.pluginName(), plugin);
pluginFormats.insert(plugin.pluginName(), format);
}
}
//kDebug() << "Mimetype ..." << formats << seenPlugins.keys() << pluginFormats.values();
QString selectedPlugin;
if (seenPlugins.isEmpty()) {
// do nothing
} else if (seenPlugins.count() == 1) {
selectedPlugin = seenPlugins.constBegin().key();
} else {
KMenu choices;
QHash<QAction *, QString> actionsToPlugins;
foreach (const KPluginInfo &info, seenPlugins) {
QAction *action;
if (!info.icon().isEmpty()) {
action = choices.addAction(KIcon(info.icon()), info.name());
} else {
action = choices.addAction(info.name());
}
actionsToPlugins.insert(action, info.pluginName());
}
QAction *choice = choices.exec(screenPos);
if (choice) {
selectedPlugin = actionsToPlugins[choice];
}
}
if (!selectedPlugin.isEmpty()) {
if (!dropEvent) {
// since we may have entered an event loop up above with the menu,
// the clipboard item may no longer be valid, as QClipboard resets
// the object behind the back of the application with a zero timer
// so we fetch it again here
QClipboard *clipboard = QApplication::clipboard();
mimeData = clipboard->mimeData(QClipboard::Selection);
}
QTemporaryFile tempFile;
if (mimeData && tempFile.open()) {
//TODO: what should we do with files after the applet is done with them??
tempFile.setAutoRemove(false);
{
QDataStream stream(&tempFile);
QByteArray data = mimeData->data(pluginFormats[selectedPlugin]);
stream.writeRawData(data, data.size());
}
QRectF geom(pos, QSize());
QVariantList args;
args << tempFile.fileName();
#ifndef NDEBUG
kDebug() << args;
#endif
tempFile.close();
q->addApplet(selectedPlugin, args, geom);
}
}
}
}
void ContainmentPrivate::clearDataForMimeJob(KIO::Job *job)
{
#ifndef PLASMA_NO_KIO
QObject::disconnect(job, 0, q, 0);
dropPoints.remove(job);
KMenu *choices = dropMenus.take(job);
delete choices;
job->kill();
#endif // PLASMA_NO_KIO
}
void ContainmentPrivate::remoteAppletReady(Plasma::AccessAppletJob *job)
{
QPointF pos = dropPoints.take(job);
if (job->error()) {
//TODO: nice user visible error handling (knotification probably?)
#ifndef NDEBUG
kDebug() << "remote applet access failed: " << job->errorText();
#endif
return;
}
if (!job->applet()) {
#ifndef NDEBUG
kDebug() << "how did we end up here? if applet is null, the job->error should be nonzero";
#endif
return;
}
q->addApplet(job->applet(), pos);
}
void ContainmentPrivate::dropJobResult(KJob *job)
{
#ifndef PLASMA_NO_KIO
KIO::TransferJob* tjob = dynamic_cast<KIO::TransferJob*>(job);
if (!tjob) {
#ifndef NDEBUG
kDebug() << "job is not a KIO::TransferJob, won't handle the drop...";
#endif
clearDataForMimeJob(tjob);
return;
}
if (job->error()) {
#ifndef NDEBUG
kDebug() << "ERROR" << tjob->error() << ' ' << tjob->errorString();
#endif
}
// We call mimeTypeRetrieved since there might be other mechanisms
// for finding suitable applets. Cleanup happens there as well.
mimeTypeRetrieved(qobject_cast<KIO::Job *>(job), QString());
#endif // PLASMA_NO_KIO
}
void ContainmentPrivate::mimeTypeRetrieved(KIO::Job *job, const QString &mimeType)
{
#ifndef PLASMA_NO_KIO
#ifndef NDEBUG
kDebug() << "Mimetype Job returns." << mimeType;
#endif
KIO::TransferJob* tjob = dynamic_cast<KIO::TransferJob*>(job);
if (!tjob) {
#ifndef NDEBUG
kDebug() << "job should be a TransferJob, but isn't";
#endif
clearDataForMimeJob(job);
return;
}
KPluginInfo::List appletList = Applet::listAppletInfoForUrl(tjob->url());
if (mimeType.isEmpty() && !appletList.count()) {
clearDataForMimeJob(job);
#ifndef NDEBUG
kDebug() << "No applets found matching the url (" << tjob->url() << ") or the mimeType (" << mimeType << ")";
#endif
return;
} else {
QPointF posi; // will be overwritten with the event's position
if (dropPoints.keys().contains(tjob)) {
posi = dropPoints[tjob];
#ifndef NDEBUG
kDebug() << "Received a suitable dropEvent at" << posi;
#endif
} else {
#ifndef NDEBUG
kDebug() << "Bailing out. Cannot find associated dropEvent related to the TransferJob";
#endif
clearDataForMimeJob(job);
return;
}
KMenu *choices = dropMenus.value(tjob);
if (!choices) {
#ifndef NDEBUG
kDebug() << "Bailing out. No QMenu found for this job.";
#endif
clearDataForMimeJob(job);
return;
}
QVariantList args;
args << tjob->url().url() << mimeType;
#ifndef NDEBUG
kDebug() << "Creating menu for:" << mimeType << posi << args;
#endif
appletList << Applet::listAppletInfoForMimeType(mimeType);
KPluginInfo::List wallpaperList;
if (drawWallpaper) {
if (wallpaper && wallpaper->supportsMimetype(mimeType)) {
wallpaperList << wallpaper->d->wallpaperDescription;
} else {
wallpaperList = Wallpaper::listWallpaperInfoForMimetype(mimeType);
}
}
if (!appletList.isEmpty() || !wallpaperList.isEmpty()) {
choices->clear();
QHash<QAction *, QString> actionsToApplets;
choices->addTitle(i18n("Widgets"));
foreach (const KPluginInfo &info, appletList) {
#ifndef NDEBUG
kDebug() << info.name();
#endif
QAction *action;
if (!info.icon().isEmpty()) {
action = choices->addAction(KIcon(info.icon()), info.name());
} else {
action = choices->addAction(info.name());
}
actionsToApplets.insert(action, info.pluginName());
#ifndef NDEBUG
kDebug() << info.pluginName();
#endif
}
actionsToApplets.insert(choices->addAction(i18n("Icon")), "icon");
QHash<QAction *, QString> actionsToWallpapers;
if (!wallpaperList.isEmpty()) {
choices->addTitle(i18n("Wallpaper"));
QMap<QString, KPluginInfo> sorted;
foreach (const KPluginInfo &info, appletList) {
sorted.insert(info.name(), info);
}
foreach (const KPluginInfo &info, wallpaperList) {
QAction *action;
if (!info.icon().isEmpty()) {
action = choices->addAction(KIcon(info.icon()), info.name());
} else {
action = choices->addAction(info.name());
}
actionsToWallpapers.insert(action, info.pluginName());
}
}
QAction *choice = choices->exec();
if (choice) {
// Put the job on hold so it can be recycled to fetch the actual content,
// which is to be expected when something's dropped onto the desktop and
// an applet is to be created with this URL
if (!mimeType.isEmpty() && !tjob->error()) {
tjob->putOnHold();
KIO::Scheduler::publishSlaveOnHold();
}
QString plugin = actionsToApplets.value(choice);
if (plugin.isEmpty()) {
//set wallpapery stuff
plugin = actionsToWallpapers.value(choice);
if (!wallpaper || plugin != wallpaper->pluginName()) {
//kDebug() << "Wallpaper dropped:" << tjob->url();
q->setWallpaper(plugin);
}
if (wallpaper) {
//kDebug() << "Wallpaper dropped:" << tjob->url();
wallpaper->addUrls(KUrl::List() << tjob->url());
}
} else {
addApplet(actionsToApplets[choice], args, QRectF(posi, QSize()));
}
clearDataForMimeJob(job);
return;
}
} else {
// we can at least create an icon as a link to the URL
addApplet("icon", args, QRectF(posi, QSize()));
}
}
clearDataForMimeJob(job);
#endif // PLASMA_NO_KIO
}
void Containment::setToolBox(AbstractToolBox *toolBox)
{
if (d->toolBox.data()) {
d->toolBox.data()->deleteLater();
}
d->toolBox = toolBox;
}
AbstractToolBox *Containment::toolBox() const
{
return d->toolBox.data();
}
void Containment::resizeEvent(QGraphicsSceneResizeEvent *event)
{
Applet::resizeEvent(event);
if (isContainment()) {
if (d->isPanelContainment()) {
d->positionPanel();
} else if (corona()) {
corona()->layoutContainments();
}
if (d->wallpaper) {
d->wallpaper->setBoundingRect(QRectF(QPointF(0, 0), size()));
}
}
}
void Containment::keyPressEvent(QKeyEvent *event)
{
//kDebug() << "keyPressEvent with" << event->key()
// << "and hoping and wishing for a" << Qt::Key_Tab;
if (event->key() == Qt::Key_Tab) { // && event->modifiers() == 0) {
if (!d->applets.isEmpty()) {
#ifndef NDEBUG
kDebug() << "let's give focus to...." << (QObject*)d->applets.first();
#endif
d->applets.first()->setFocus(Qt::TabFocusReason);
}
}
}
void Containment::wheelEvent(QGraphicsSceneWheelEvent *event)
{
event->ignore();
if (d->appletAt(event->scenePos())) {
return; //no unexpected click-throughs
}
if (d->wallpaper && d->wallpaper->isInitialized()) {
QGraphicsItem *item = scene()->itemAt(event->scenePos());
if (item == this) {
event->ignore();
d->wallpaper->wheelEvent(event);
if (event->isAccepted()) {
return;
}
}
}
QString trigger = ContainmentActions::eventToString(event);
if (d->prepareContainmentActions(trigger, event->screenPos())) {
d->actionPlugins()->value(trigger)->contextEvent(event);
event->accept();
} else {
event->ignore();
Applet::wheelEvent(event);
}
}
QVariant Containment::itemChange(GraphicsItemChange change, const QVariant &value)
{
//FIXME if the applet is moved to another containment we need to unfocus it
if (isContainment() &&
(change == QGraphicsItem::ItemSceneHasChanged ||
change == QGraphicsItem::ItemPositionHasChanged)) {
switch (d->type) {
case PanelContainment:
case CustomPanelContainment:
d->positionPanel();
break;
default:
if (corona()) {
corona()->layoutContainments();
}
break;
}
}
return Applet::itemChange(change, value);
}
void Containment::enableAction(const QString &name, bool enable)
{
QAction *action = this->action(name);
if (action) {
action->setEnabled(enable);
action->setVisible(enable);
}
}
void Containment::addToolBoxAction(QAction *action)
{
d->createToolBox();
if (d->toolBox) {
d->toolBox.data()->addTool(action);
}
}
void Containment::removeToolBoxAction(QAction *action)
{
if (d->toolBox) {
d->toolBox.data()->removeTool(action);
}
}
void Containment::setToolBoxOpen(bool open)
{
if (open) {
openToolBox();
} else {
closeToolBox();
}
}
bool Containment::isToolBoxOpen() const
{
return (d->toolBox && d->toolBox.data()->isShowing());
}
void Containment::openToolBox()
{
if (d->toolBox && !d->toolBox.data()->isShowing()) {
d->toolBox.data()->setShowing(true);
emit toolBoxVisibilityChanged(true);
}
}
void Containment::closeToolBox()
{
if (d->toolBox && d->toolBox.data()->isShowing()) {
d->toolBox.data()->setShowing(false);
emit toolBoxVisibilityChanged(false);
}
}
void Containment::addAssociatedWidget(QWidget *widget)
{
Applet::addAssociatedWidget(widget);
if (d->focusedApplet) {
d->focusedApplet->addAssociatedWidget(widget);
}
foreach (const Applet *applet, d->applets) {
if (applet->d->activationAction) {
widget->addAction(applet->d->activationAction);
}
}
}
void Containment::removeAssociatedWidget(QWidget *widget)
{
Applet::removeAssociatedWidget(widget);
if (d->focusedApplet) {
d->focusedApplet->removeAssociatedWidget(widget);
}
foreach (const Applet *applet, d->applets) {
if (applet->d->activationAction) {
widget->removeAction(applet->d->activationAction);
}
}
}
void Containment::setDrawWallpaper(bool drawWallpaper)
{
d->drawWallpaper = drawWallpaper;
if (drawWallpaper) {
KConfigGroup cfg = config();
const QString wallpaper = cfg.readEntry("wallpaperplugin", defaultWallpaper);
const QString mode = cfg.readEntry("wallpaperpluginmode", defaultWallpaperMode);
setWallpaper(wallpaper, mode);
} else {
delete d->wallpaper;
d->wallpaper = 0;
}
}
bool Containment::drawWallpaper()
{
return d->drawWallpaper;
}
void Containment::setWallpaper(const QString &pluginName, const QString &mode)
{
KConfigGroup cfg = config();
bool newPlugin = true;
bool newMode = true;
if (d->drawWallpaper) {
if (d->wallpaper) {
// we have a wallpaper, so let's decide whether we need to swap it out
if (d->wallpaper->pluginName() != pluginName) {
delete d->wallpaper;
d->wallpaper = 0;
} else {
// it's the same plugin, so let's save its state now so when
// we call restore later on we're safe
newMode = d->wallpaper->renderingMode().name() != mode;
newPlugin = false;
}
}
if (!pluginName.isEmpty() && !d->wallpaper) {
d->wallpaper = Plasma::Wallpaper::load(pluginName);
}
if (d->wallpaper) {
d->wallpaper->setParent(this);
d->wallpaper->setBoundingRect(QRectF(QPointF(0, 0), size()));
d->wallpaper->setRenderingMode(mode);
if (newPlugin) {
cfg.writeEntry("wallpaperplugin", pluginName);
}
if (d->wallpaper->isInitialized()) {
KConfigGroup wallpaperConfig = KConfigGroup(&cfg, "Wallpaper");
wallpaperConfig = KConfigGroup(&wallpaperConfig, pluginName);
d->wallpaper->restore(wallpaperConfig);
}
if (newMode) {
cfg.writeEntry("wallpaperpluginmode", mode);
}
}
update();
}
if (!d->wallpaper) {
cfg.deleteEntry("wallpaperplugin");
cfg.deleteEntry("wallpaperpluginmode");
}
if (newPlugin || newMode) {
if (newPlugin && d->wallpaper) {
connect(d->wallpaper, SIGNAL(configureRequested()), this, SLOT(requestConfiguration()));
connect(d->wallpaper, SIGNAL(configNeedsSaving()), this, SIGNAL(configNeedsSaving()));
}
emit configNeedsSaving();
}
}
Plasma::Wallpaper *Containment::wallpaper() const
{
return d->wallpaper;
}
void Containment::setContainmentActions(const QString &trigger, const QString &pluginName)
{
KConfigGroup cfg = containmentActionsConfig();
ContainmentActions *plugin = 0;
if (d->actionPlugins()->contains(trigger)) {
plugin = d->actionPlugins()->value(trigger);
if (plugin->pluginName() != pluginName) {
d->actionPlugins()->remove(trigger);
delete plugin;
plugin=0;
}
}
if (pluginName.isEmpty()) {
cfg.deleteEntry(trigger);
} else if (plugin) {
//it already existed, just reload config
if (plugin->isInitialized()) {
plugin->setContainment(this); //to be safe
//FIXME make a truly unique config group
KConfigGroup pluginConfig = KConfigGroup(&cfg, trigger);
plugin->restore(pluginConfig);
}
} else {
switch (d->containmentActionsSource) {
case ContainmentPrivate::Activity:
//FIXME
case ContainmentPrivate::Local:
plugin = PluginLoader::self()->loadContainmentActions(this, pluginName);
break;
default:
plugin = PluginLoader::self()->loadContainmentActions(0, pluginName);
}
if (plugin) {
cfg.writeEntry(trigger, pluginName);
d->actionPlugins()->insert(trigger, plugin);
} else {
//bad plugin... gets removed. is this a feature or a bug?
cfg.deleteEntry(trigger);
}
}
emit configNeedsSaving();
}
QStringList Containment::containmentActionsTriggers()
{
return d->actionPlugins()->keys();
}
QString Containment::containmentActions(const QString &trigger)
{
ContainmentActions *c = d->actionPlugins()->value(trigger);
return c ? c->pluginName() : QString();
}
void Containment::setActivity(const QString &activityId)
{
if (activityId.isEmpty()) {
return;
}
d->activityId = activityId;
KConfigGroup c = config();
c.writeEntry("activityId", activityId);
if (d->toolBox) {
d->toolBox.data()->update();
}
emit configNeedsSaving();
}
QString Containment::activity() const
{
return d->activityId;
}
KActionCollection* ContainmentPrivate::actions()
{
return static_cast<Applet*>(q)->d->actions;
}
void ContainmentPrivate::focusApplet(Plasma::Applet *applet)
{
if (focusedApplet == applet) {
return;
}
QList<QWidget *> widgets = actions()->associatedWidgets();
if (focusedApplet) {
foreach (QWidget *w, widgets) {
focusedApplet->removeAssociatedWidget(w);
}
}
if (applet && applets.contains(applet)) {
//kDebug() << "switching to" << applet->name();
focusedApplet = applet;
foreach (QWidget *w, widgets) {
focusedApplet->addAssociatedWidget(w);
}
if (!focusedApplet->hasFocus()) {
focusedApplet->setFocus(Qt::ShortcutFocusReason);
}
} else {
focusedApplet = 0;
}
}
void Containment::focusNextApplet()
{
if (d->applets.isEmpty()) {
return;
}
int index = d->focusedApplet ? d->applets.indexOf(d->focusedApplet) + 1 : 0;
if (index >= d->applets.size()) {
index = 0;
}
#ifndef NDEBUG
kDebug() << "index" << index;
#endif
d->focusApplet(d->applets.at(index));
}
void Containment::focusPreviousApplet()
{
if (d->applets.isEmpty()) {
return;
}
int index = d->focusedApplet ? d->applets.indexOf(d->focusedApplet) - 1 : -1;
if (index < 0) {
index = d->applets.size() - 1;
}
#ifndef NDEBUG
kDebug() << "index" << index;
#endif
d->focusApplet(d->applets.at(index));
}
void Containment::destroy()
{
destroy(true);
}
void Containment::showConfigurationInterface()
{
Applet::showConfigurationInterface();
}
void ContainmentPrivate::configChanged()
{
if (drawWallpaper) {
KConfigGroup group = q->config();
q->setWallpaper(group.readEntry("wallpaperplugin", defaultWallpaper),
group.readEntry("wallpaperpluginmode", defaultWallpaperMode));
}
}
void ContainmentPrivate::requestConfiguration()
{
emit q->configureRequested(q);
}
void ContainmentPrivate::checkStatus(Plasma::ItemStatus appletStatus)
{
//kDebug() << "================== "<< appletStatus << q->status();
if (appletStatus == q->status()) {
emit q->newStatus(appletStatus);
return;
}
if (appletStatus < q->status()) {
// check to see if any other applet has a higher status, and stick with that
// if we do
foreach (Applet *applet, applets) {
if (applet->status() > appletStatus) {
appletStatus = applet->status();
}
}
}
q->setStatus(appletStatus);
}
void Containment::destroy(bool confirm)
{
if (immutability() != Mutable || Applet::d->transient) {
return;
}
if (isContainment() && confirm) {
//FIXME: should not be blocking
const QString title = i18nc("@title:window %1 is the name of the containment", "Remove %1", name());
KGuiItem remove = KStandardGuiItem::remove();
remove.setText(title);
if (KMessageBox::warningContinueCancel(view(),
i18nc("%1 is the name of the containment", "Do you really want to remove this %1?", name()),
title, remove) != KMessageBox::Continue) {
return;
}
}
Applet::destroy();
}
void ContainmentPrivate::createToolBox()
{
if (!toolBox && KAuthorized::authorizeKAction("plasma/containment_context_menu")) {
toolBox = Plasma::AbstractToolBox::load(q->corona()->preferredToolBoxPlugin(type), QVariantList(), q);
if (toolBox) {
QObject::connect(toolBox.data(), SIGNAL(toggled()), q, SIGNAL(toolBoxToggled()));
QObject::connect(toolBox.data(), SIGNAL(toggled()), q, SLOT(updateToolBoxVisibility()));
positionToolBox();
}
}
}
void ContainmentPrivate::positionToolBox()
{
if (toolBox) {
toolBox.data()->reposition();
}
}
void ContainmentPrivate::updateToolBoxVisibility()
{
emit q->toolBoxVisibilityChanged(toolBox.data()->isShowing());
}
void ContainmentPrivate::triggerShowAddWidgets()
{
emit q->showAddWidgetsInterface(QPointF());
}
void ContainmentPrivate::containmentConstraintsEvent(Plasma::Constraints constraints)
{
if (!q->isContainment()) {
return;
}
//kDebug() << "got containmentConstraintsEvent" << constraints << (QObject*)toolBox;
if (constraints & Plasma::ImmutableConstraint) {
//update actions
checkRemoveAction();
const bool unlocked = q->immutability() == Mutable;
q->setAcceptDrops(unlocked);
q->enableAction("add widgets", unlocked);
// tell the applets too
foreach (Applet *a, applets) {
a->setImmutability(q->immutability());
a->updateConstraints(ImmutableConstraint);
}
}
// pass on the constraints that are relevant here
Constraints appletConstraints = NoConstraint;
if (constraints & FormFactorConstraint) {
appletConstraints |= FormFactorConstraint;
}
if (constraints & ScreenConstraint) {
appletConstraints |= ScreenConstraint;
}
if (appletConstraints != NoConstraint) {
foreach (Applet *applet, applets) {
applet->updateConstraints(appletConstraints);
}
}
if (toolBox && (constraints & Plasma::SizeConstraint ||
constraints & Plasma::FormFactorConstraint ||
constraints & Plasma::ScreenConstraint ||
constraints & Plasma::StartupCompletedConstraint)) {
//kDebug() << "Positioning toolbox";
positionToolBox();
}
if (constraints & Plasma::StartupCompletedConstraint && type < Containment::CustomContainment) {
q->addToolBoxAction(q->action("remove"));
checkRemoveAction();
}
}
Applet *ContainmentPrivate::addApplet(const QString &name, const QVariantList &args,
const QRectF &appletGeometry, uint id, bool delayInit)
{
if (!q->isContainment()) {
return 0;
}
if (!delayInit && q->immutability() != Mutable) {
#ifndef NDEBUG
kDebug() << "addApplet for" << name << "requested, but we're currently immutable!";
#endif
return 0;
}
QGraphicsView *v = q->view();
if (v) {
v->setCursor(Qt::BusyCursor);
}
Applet *applet = PluginLoader::self()->loadApplet(name, id, args);
if (v) {
v->unsetCursor();
}
if (!applet) {
#ifndef NDEBUG
kDebug() << "Applet" << name << "could not be loaded.";
#endif
applet = new Applet(0, QString(), id);
applet->setFailedToLaunch(true, i18n("Could not find requested component: %1", name));
}
//kDebug() << applet->name() << "sizehint:" << applet->sizeHint() << "geometry:" << applet->geometry();
q->addApplet(applet, appletGeometry.topLeft(), delayInit);
return applet;
}
bool ContainmentPrivate::regionIsEmpty(const QRectF &region, Applet *ignoredApplet) const
{
foreach (Applet *applet, applets) {
if (applet != ignoredApplet && applet->geometry().intersects(region)) {
return false;
}
}
return true;
}
void ContainmentPrivate::appletDestroyed(Plasma::Applet *applet)
{
applets.removeAll(applet);
if (focusedApplet == applet) {
focusedApplet = 0;
}
emit q->appletRemoved(applet);
emit q->configNeedsSaving();
}
void ContainmentPrivate::appletAppeared(Applet *applet)
{
//kDebug() << type << Containment::DesktopContainment;
KConfigGroup *cg = applet->d->mainConfigGroup();
applet->save(*cg);
emit q->configNeedsSaving();
}
void ContainmentPrivate::positionPanel(bool force)
{
if (!q->scene()) {
#ifndef NDEBUG
kDebug() << "no scene yet";
#endif
return;
}
// already positioning the panel - avoid infinite loops
if (ContainmentPrivate::s_positioningPanels) {
return;
}
// we position panels in negative coordinates, and stack all horizontal
// and all vertical panels with each other.
const QPointF p = q->pos();
if (!force &&
p.y() + q->size().height() < -INTER_CONTAINMENT_MARGIN &&
q->scene()->collidingItems(q).isEmpty()) {
// already positioned and not running into any other panels
return;
}
QPointF newPos = preferredPanelPos(q->corona());
if (p != newPos) {
ContainmentPrivate::s_positioningPanels = true;
q->setPos(newPos);
ContainmentPrivate::s_positioningPanels = false;
}
}
bool ContainmentPrivate::isPanelContainment() const
{
return type == Containment::PanelContainment || type == Containment::CustomPanelContainment;
}
QPointF ContainmentPrivate::preferredPos(Corona *corona) const
{
Q_ASSERT(corona);
if (isPanelContainment()) {
//kDebug() << "is a panel, so put it at" << preferredPanelPos(corona);
return preferredPanelPos(corona);
}
QPointF pos(0, 0);
QTransform t;
while (QGraphicsItem *i = corona->itemAt(pos, t)) {
pos.setX(i->scenePos().x() + i->boundingRect().width() + 10);
}
//kDebug() << "not a panel, put it at" << pos;
return pos;
}
QPointF ContainmentPrivate::preferredPanelPos(Corona *corona) const
{
Q_ASSERT(corona);
//TODO: research how non-Horizontal, non-Vertical (e.g. Planar) panels behave here
bool horiz = formFactor == Plasma::Horizontal;
qreal bottom = horiz ? 0 : VERTICAL_STACKING_OFFSET;
qreal lastHeight = 0;
// this should be ok for small numbers of panels, but if we ever end
// up managing hundreds of them, this simplistic alogrithm will
// likely be too slow.
foreach (const Containment *other, corona->containments()) {
if (other == q ||
!other->d->isPanelContainment() ||
horiz != (other->formFactor() == Plasma::Horizontal)) {
// only line up with panels of the same orientation
continue;
}
if (horiz) {
qreal y = other->pos().y();
if (y < bottom) {
lastHeight = other->size().height();
bottom = y;
}
} else {
qreal width = other->size().width();
qreal x = other->pos().x() + width;
if (x > bottom) {
lastHeight = width;
bottom = x + lastHeight;
}
}
}
// give a space equal to the height again of the last item so there is
// room to grow.
QPointF newPos;
if (horiz) {
bottom -= lastHeight + INTER_CONTAINMENT_MARGIN;
//TODO: fix x position for non-flush-left panels
#ifndef NDEBUG
kDebug() << "moved to" << QPointF(0, bottom - q->size().height());
#endif
newPos = QPointF(0, bottom - q->size().height());
} else {
bottom += lastHeight + INTER_CONTAINMENT_MARGIN;
//TODO: fix y position for non-flush-top panels
#ifndef NDEBUG
kDebug() << "moved to" << QPointF(bottom + q->size().width(), -INTER_CONTAINMENT_MARGIN - q->size().height());
#endif
newPos = QPointF(bottom + q->size().width(), -INTER_CONTAINMENT_MARGIN - q->size().height());
}
return newPos;
}
bool ContainmentPrivate::prepareContainmentActions(const QString &trigger, const QPoint &screenPos, KMenu *menu)
{
ContainmentActions *plugin = actionPlugins()->value(trigger);
if (!plugin) {
return false;
}
plugin->setContainment(q);
if (!plugin->isInitialized()) {
KConfigGroup cfg = q->containmentActionsConfig();
KConfigGroup pluginConfig = KConfigGroup(&cfg, trigger);
plugin->restore(pluginConfig);
}
if (plugin->configurationRequired()) {
KMenu *localMenu = menu ? menu : new KMenu();
localMenu->addTitle(i18n("This plugin needs to be configured"));
localMenu->addAction(q->action("configure"));
if (!menu) {
localMenu->exec(screenPos);
delete localMenu;
}
return false;
} else if (menu) {
QList<QAction*> actions = plugin->contextualActions();
if (actions.isEmpty()) {
//it probably didn't bother implementing the function. give the user a chance to set
//a better plugin. note that if the user sets no-plugin this won't happen...
if (!isPanelContainment() && q->action("configure")) {
menu->addAction(q->action("configure"));
}
} else {
menu->addActions(actions);
}
}
return true;
}
KConfigGroup Containment::containmentActionsConfig()
{
KConfigGroup cfg;
switch (d->containmentActionsSource) {
case ContainmentPrivate::Local:
cfg = config();
cfg = KConfigGroup(&cfg, "ActionPlugins");
break;
case ContainmentPrivate::Activity:
cfg = KConfigGroup(corona()->config(), "Activities");
cfg = KConfigGroup(&cfg, d->activityId);
cfg = KConfigGroup(&cfg, "ActionPlugins");
break;
default:
cfg = KConfigGroup(corona()->config(), "ActionPlugins");
}
return cfg;
}
QHash<QString, ContainmentActions*> * ContainmentPrivate::actionPlugins()
{
switch (containmentActionsSource) {
case Activity:
//FIXME
case Local:
return &localActionPlugins;
default:
return &globalActionPlugins;
}
}
} // Plasma namespace
diff --git a/plasma/remote/accessmanager.cpp b/plasma/remote/accessmanager.cpp
index 420de6c0ae..2920a59b18 100644
--- a/plasma/remote/accessmanager.cpp
+++ b/plasma/remote/accessmanager.cpp
@@ -1,273 +1,273 @@
/*
* Copyright 2009 by Rob Scheepmaker <r.scheepmaker@student.utwente.nl>
*
* 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.1 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 St, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
#include "accessmanager.h"
#include "private/accessmanager_p.h"
#include "authorizationmanager.h"
#include "authorizationmanager_p.h"
#include "service.h"
#include "serviceaccessjob.h"
#include "config-plasma.h"
#include <QtCore/QMap>
#include <QtCore/QTimer>
#include <dnssd/remoteservice.h>
#include <dnssd/servicebrowser.h>
#include <kdebug.h>
#include <kglobal.h>
#include <kstandarddirs.h>
#include <kurl.h>
#include <QtJolie/Message>
namespace Plasma
{
class RemoteObjectDescription::Private
{
public:
QString name;
QString description;
QString icon;
KUrl url;
};
RemoteObjectDescription::RemoteObjectDescription()
: d(new Private)
{
}
RemoteObjectDescription::RemoteObjectDescription(const RemoteObjectDescription &other)
: d(new Private(*other.d))
{
}
RemoteObjectDescription &RemoteObjectDescription::operator=(const RemoteObjectDescription &other)
{
*d = *other.d;
return *this;
}
void RemoteObjectDescription::setName(const QString &name)
{
d->name = name;
}
QString RemoteObjectDescription::name() const
{
return d->name;
}
void RemoteObjectDescription::setUrl(const KUrl &url)
{
d->url = url;
}
KUrl RemoteObjectDescription::url() const
{
return d->url;
}
void RemoteObjectDescription::setDescription(const QString &description)
{
d->description = description;
}
QString RemoteObjectDescription::description() const
{
return d->description;
}
void RemoteObjectDescription::setIcon(const QString &icon)
{
d->icon = icon;
}
QString RemoteObjectDescription::icon() const
{
return d->icon;
}
class AccessManagerSingleton
{
public:
AccessManager self;
};
K_GLOBAL_STATIC(AccessManagerSingleton, privateAccessManagerSelf)
AccessManager *AccessManager::self()
{
return &privateAccessManagerSelf->self;
}
AccessManager::AccessManager()
: QObject(),
d(new AccessManagerPrivate(this))
{
KGlobal::dirs()->addResourceType("trustedkeys", "config", "trustedkeys/");
}
AccessManager::~AccessManager()
{
delete d;
}
AccessAppletJob *AccessManager::accessRemoteApplet(const KUrl &location) const
{
AuthorizationManager::self()->d->prepareForServiceAccess();
KUrl resolvedLocation;
- if (location.protocol() == "plasma+zeroconf") {
+ if (location.scheme() == "plasma+zeroconf") {
if (d->zeroconfServices.contains(location.host())) {
resolvedLocation = d->services[location.host()].url();
} else {
#ifndef NDEBUG
kDebug() << "There's no zeroconf service with this name.";
#endif
}
} else {
resolvedLocation = location;
}
AccessAppletJob *job = new AccessAppletJob(resolvedLocation);
connect(job, SIGNAL(finished(KJob*)), this, SLOT(slotJobFinished(KJob*)));
QTimer::singleShot(0, job, SLOT(slotStart()));
return job;
}
QList<RemoteObjectDescription> AccessManager::remoteApplets() const
{
return d->services.values();
}
QStringList AccessManager::supportedProtocols()
{
QStringList list;
list << "plasma" << "plasma+zeroconf";
return list;
}
AccessManagerPrivate::AccessManagerPrivate(AccessManager *manager)
: q(manager),
browser(new DNSSD::ServiceBrowser("_plasma._tcp"))
{
#ifdef ENABLE_REMOTE_WIDGETS
q->connect(browser, SIGNAL(serviceAdded(DNSSD::RemoteService::Ptr)),
q, SLOT(slotAddService(DNSSD::RemoteService::Ptr)));
q->connect(browser, SIGNAL(serviceRemoved(DNSSD::RemoteService::Ptr)),
q, SLOT(slotRemoveService(DNSSD::RemoteService::Ptr)));
browser->startBrowse();
#else
kWarning() << "libplasma is compiled without support for remote widgets. not monitoring remote widgets on the network";
#endif
}
AccessManagerPrivate::~AccessManagerPrivate()
{
delete browser;
}
void AccessManagerPrivate::slotJobFinished(KJob *job)
{
emit q->finished(static_cast<AccessAppletJob*>(job));
}
void AccessManagerPrivate::slotAddService(DNSSD::RemoteService::Ptr service)
{
#ifndef NDEBUG
kDebug();
#endif
if (!service->resolve()) {
#ifndef NDEBUG
kDebug() << "Zeroconf service can't be resolved";
#endif
return;
}
if (!services.contains(service->serviceName())) {
RemoteObjectDescription metadata;
#ifndef NDEBUG
kDebug() << "textdata = " << service->textData();
#endif
#ifndef NDEBUG
kDebug() << "hostname: " << service->hostName();
#endif
QHostAddress address = DNSSD::ServiceBrowser::resolveHostName(service->hostName());
QString ip = address.toString();
#ifndef NDEBUG
kDebug() << "result for resolve = " << ip;
#endif
KUrl url(QString("plasma://%1:%2/%3").arg(ip)
.arg(service->port())
.arg(service->serviceName()));
if (service->textData().isEmpty()) {
#ifndef NDEBUG
kDebug() << "no textdata?";
#endif
metadata.setName(service->serviceName());
metadata.setUrl(url);
} else {
#ifndef NDEBUG
kDebug() << "service has got textdata";
#endif
QMap<QString, QByteArray> textData = service->textData();
metadata.setName(textData["name"]);
metadata.setDescription(textData["description"]);
metadata.setIcon(textData["icon"]);
metadata.setUrl(url);
}
#ifndef NDEBUG
kDebug() << "location = " << metadata.url();
#endif
#ifndef NDEBUG
kDebug() << "name = " << metadata.name();
#endif
#ifndef NDEBUG
kDebug() << "description = " << metadata.name();
#endif
services[service->serviceName()] = metadata;
zeroconfServices[service->serviceName()] = service;
emit q->remoteAppletAnnounced(metadata);
}
}
void AccessManagerPrivate::slotRemoveService(DNSSD::RemoteService::Ptr service)
{
#ifndef NDEBUG
kDebug();
#endif
emit q->remoteAppletUnannounced(services[service->serviceName()]);
services.remove(service->serviceName());
zeroconfServices.remove(service->serviceName());
}
} // Plasma namespace
diff --git a/plasma/runnercontext.cpp b/plasma/runnercontext.cpp
index 160dd7ad4a..5c4147e667 100644
--- a/plasma/runnercontext.cpp
+++ b/plasma/runnercontext.cpp
@@ -1,549 +1,549 @@
/*
* Copyright 2006-2007 Aaron Seigo <aseigo@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 "runnercontext.h"
#include <cmath>
#include <QReadWriteLock>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QSharedData>
#include <kcompletion.h>
#include <kconfiggroup.h>
#include <kdebug.h>
#include <kmimetype.h>
#include <kshell.h>
#include <kstandarddirs.h>
#include <kurl.h>
#include <kprotocolinfo.h>
#include "abstractrunner.h"
#include "querymatch.h"
//#define LOCK_FOR_READ(d) if (d->policy == Shared) { d->lock.lockForRead(); }
//#define LOCK_FOR_WRITE(d) if (d->policy == Shared) { d->lock.lockForWrite(); }
//#define UNLOCK(d) if (d->policy == Shared) { d->lock.unlock(); }
#define LOCK_FOR_READ(d) d->lock.lockForRead();
#define LOCK_FOR_WRITE(d) d->lock.lockForWrite();
#define UNLOCK(d) d->lock.unlock();
namespace Plasma
{
/*
Corrects the case of the last component in a path (e.g. /usr/liB -> /usr/lib)
path: The path to be processed.
correctCasePath: The corrected-case path
mustBeDir: Tells whether the last component is a folder or doesn't matter
Returns true on success and false on error, in case of error, correctCasePath is not modified
*/
bool correctLastComponentCase(const QString &path, QString &correctCasePath, const bool mustBeDir)
{
//kDebug() << "Correcting " << path;
// If the file already exists then no need to search for it.
if (QFile::exists(path)) {
correctCasePath = path;
//kDebug() << "Correct path is" << correctCasePath;
return true;
}
const QFileInfo pathInfo(path);
const QDir fileDir = pathInfo.dir();
//kDebug() << "Directory is" << fileDir;
const QString filename = pathInfo.fileName();
//kDebug() << "Filename is" << filename;
//kDebug() << "searching for a" << (mustBeDir ? "directory" : "directory/file");
const QStringList matchingFilenames = fileDir.entryList(QStringList(filename),
mustBeDir ? QDir::Dirs : QDir::NoFilter);
if (matchingFilenames.empty()) {
//kDebug() << "No matches found!!\n";
return false;
} else {
/*if (matchingFilenames.size() > 1) {
#ifndef NDEBUG
kDebug() << "Found multiple matches!!\n";
#endif
}*/
if (fileDir.path().endsWith(QDir::separator())) {
correctCasePath = fileDir.path() + matchingFilenames[0];
} else {
correctCasePath = fileDir.path() + QDir::separator() + matchingFilenames[0];
}
//kDebug() << "Correct path is" << correctCasePath;
return true;
}
}
/*
Corrects the case of a path (e.g. /uSr/loCAL/bIN -> /usr/local/bin)
path: The path to be processed.
corrected: The corrected-case path
Returns true on success and false on error, in case of error, corrected is not modified
*/
bool correctPathCase(const QString& path, QString &corrected)
{
// early exit check
if (QFile::exists(path)) {
corrected = path;
return true;
}
// path components
QStringList components = QString(path).split(QDir::separator());
if (components.size() < 1) {
return false;
}
const bool mustBeDir = components.back().isEmpty();
//kDebug() << "Components are" << components;
if (mustBeDir) {
components.pop_back();
}
if (components.isEmpty()) {
return true;
}
QString correctPath;
const unsigned initialComponents = components.size();
for (unsigned i = 0; i < initialComponents - 1; i ++) {
const QString tmp = components[0] + QDir::separator() + components[1];
if (!correctLastComponentCase(tmp, correctPath, components.size() > 2 || mustBeDir)) {
//kDebug() << "search was not successful";
return false;
}
components.removeFirst();
components[0] = correctPath;
}
corrected = correctPath;
return true;
}
class RunnerContextPrivate : public QSharedData
{
public:
RunnerContextPrivate(RunnerContext *context)
: QSharedData(),
type(RunnerContext::UnknownType),
q(context),
singleRunnerQueryMode(false)
{
}
RunnerContextPrivate(const RunnerContextPrivate &p)
: QSharedData(),
launchCounts(p.launchCounts),
type(RunnerContext::None),
q(p.q),
singleRunnerQueryMode(false)
{
//kDebug() << "¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿boo yeah" << type;
}
~RunnerContextPrivate()
{
}
/**
* Determines type of query
&&
*/
void determineType()
{
// NOTE! this method must NEVER be called from
// code that may be running in multiple threads
// with the same data.
type = RunnerContext::UnknownType;
QString path = QDir::cleanPath(KShell::tildeExpand(term));
int space = path.indexOf(' ');
if (!KStandardDirs::findExe(path.left(space)).isEmpty()) {
// it's a shell command if there's a space because that implies
// that it has arguments!
type = (space > 0) ? RunnerContext::ShellCommand :
RunnerContext::Executable;
} else {
KUrl url(term);
// check for a normal URL first
- //kDebug() << url << KProtocolInfo::protocolClass(url.protocol()) << url.hasHost() <<
+ //kDebug() << url << KProtocolInfo::protocolClass(url.scheme()) << url.hasHost() <<
// url.host() << url.isLocalFile() << path << path.indexOf('/');
- const bool hasProtocol = !url.protocol().isEmpty();
- const bool isLocalProtocol = KProtocolInfo::protocolClass(url.protocol()) == ":local";
+ const bool hasProtocol = !url.scheme().isEmpty();
+ const bool isLocalProtocol = KProtocolInfo::protocolClass(url.scheme()) == ":local";
if (hasProtocol &&
((!isLocalProtocol && url.hasHost()) ||
- (isLocalProtocol && url.protocol() != "file"))) {
+ (isLocalProtocol && url.scheme() != "file"))) {
// we either have a network protocol with a host, so we can show matches for it
// or we have a non-file url that may be local so a host isn't required
type = RunnerContext::NetworkLocation;
} else if (isLocalProtocol) {
// at this point in the game, we assume we have a path,
// but if a path doesn't have any slashes
// it's too ambiguous to be sure we're in a filesystem context
path = QDir::cleanPath(url.toLocalFile());
//kDebug( )<< "slash check" << path;
if (hasProtocol || ((path.indexOf('/') != -1 || path.indexOf('\\') != -1))) {
QString correctCasePath;
if (correctPathCase(path, correctCasePath)) {
path = correctCasePath;
QFileInfo info(path);
//kDebug( )<< "correct cas epath is" << correctCasePath << info.isSymLink() <<
// info.isDir() << info.isFile();
if (info.isSymLink()) {
path = info.canonicalFilePath();
info = QFileInfo(path);
}
if (info.isDir()) {
type = RunnerContext::Directory;
mimeType = "inode/folder";
} else if (info.isFile()) {
type = RunnerContext::File;
KMimeType::Ptr mimeTypePtr = KMimeType::findByPath(path);
if (mimeTypePtr) {
mimeType = mimeTypePtr->name();
}
}
}
}
}
}
//kDebug() << "term2type" << term << type;
}
void invalidate()
{
q = &s_dummyContext;
}
QReadWriteLock lock;
QList<QueryMatch> matches;
QMap<QString, const QueryMatch*> matchesById;
QHash<QString, int> launchCounts;
QString term;
QString mimeType;
RunnerContext::Type type;
RunnerContext * q;
static RunnerContext s_dummyContext;
bool singleRunnerQueryMode;
};
RunnerContext RunnerContextPrivate::s_dummyContext;
RunnerContext::RunnerContext(QObject *parent)
: QObject(parent),
d(new RunnerContextPrivate(this))
{
}
//copy ctor
RunnerContext::RunnerContext(RunnerContext &other, QObject *parent)
: QObject(parent)
{
LOCK_FOR_READ(other.d)
d = other.d;
UNLOCK(other.d)
}
RunnerContext::~RunnerContext()
{
}
RunnerContext &RunnerContext::operator=(const RunnerContext &other)
{
if (this->d == other.d) {
return *this;
}
QExplicitlySharedDataPointer<Plasma::RunnerContextPrivate> oldD = d;
LOCK_FOR_WRITE(d)
LOCK_FOR_READ(other.d)
d = other.d;
UNLOCK(other.d)
UNLOCK(oldD)
return *this;
}
void RunnerContext::reset()
{
// We will detach if we are a copy of someone. But we will reset
// if we are the 'main' context others copied from. Resetting
// one RunnerContext makes all the copies obsolete.
// We need to mark the q pointer of the detached RunnerContextPrivate
// as dirty on detach to avoid receiving results for old queries
d->invalidate();
d.detach();
// Now that we detached the d pointer we need to reset its q pointer
d->q = this;
// we still have to remove all the matches, since if the
// ref count was 1 (e.g. only the RunnerContext is using
// the dptr) then we won't get a copy made
if (!d->matches.isEmpty()) {
d->matchesById.clear();
d->matches.clear();
emit matchesChanged();
}
d->term.clear();
d->mimeType.clear();
d->type = UnknownType;
d->singleRunnerQueryMode = false;
//kDebug() << "match count" << d->matches.count();
}
void RunnerContext::setQuery(const QString &term)
{
reset();
if (term.isEmpty()) {
return;
}
d->term = term;
d->determineType();
}
QString RunnerContext::query() const
{
// the query term should never be set after
// a search starts. in fact, reset() ensures this
// and setQuery(QString) calls reset()
return d->term;
}
RunnerContext::Type RunnerContext::type() const
{
return d->type;
}
QString RunnerContext::mimeType() const
{
return d->mimeType;
}
bool RunnerContext::isValid() const
{
// if our qptr is dirty, we aren't useful anymore
return (d->q != &(d->s_dummyContext));
}
bool RunnerContext::addMatches(const QList<QueryMatch> &matches)
{
if (matches.isEmpty() || !isValid()) {
//Bail out if the query is empty or the qptr is dirty
return false;
}
LOCK_FOR_WRITE(d)
foreach (QueryMatch match, matches) {
// Give previously launched matches a slight boost in relevance
// The boost smoothly saturates to 0.5;
if (int count = d->launchCounts.value(match.id())) {
match.setRelevance(match.relevance() + 0.5 * (1-exp(-count*0.3)));
}
d->matches.append(match);
#ifndef NDEBUG
if (d->matchesById.contains(match.id())) {
kDebug() << "Duplicate match id " << match.id() << "from" << match.runner()->name();
}
#endif
d->matchesById.insert(match.id(), &d->matches.at(d->matches.size() - 1));
}
UNLOCK(d);
//kDebug()<< "add matches";
// A copied searchContext may share the d pointer,
// we always want to sent the signal of the object that created
// the d pointer
emit d->q->matchesChanged();
return true;
}
bool RunnerContext::addMatch(const QueryMatch &match)
{
if (!isValid()) {
// Bail out if the qptr is dirty
return false;
}
QueryMatch m(match); // match must be non-const to modify relevance
LOCK_FOR_WRITE(d)
if (int count = d->launchCounts.value(m.id())) {
m.setRelevance(m.relevance() + 0.05 * count);
}
d->matches.append(m);
d->matchesById.insert(m.id(), &d->matches.at(d->matches.size() - 1));
UNLOCK(d);
//kDebug()<< "added match" << match->text();
emit d->q->matchesChanged();
return true;
}
bool RunnerContext::removeMatches(const QStringList matchIdList)
{
if (!isValid()) {
return false;
}
QStringList presentMatchIdList;
QList<const QueryMatch*> presentMatchList;
LOCK_FOR_READ(d)
foreach(const QString &matchId, matchIdList) {
const QueryMatch* match = d->matchesById.value(matchId, 0);
if (match) {
presentMatchList << match;
presentMatchIdList << matchId;
}
}
UNLOCK(d)
if (presentMatchIdList.isEmpty()) {
return false;
}
LOCK_FOR_WRITE(d)
foreach(const QueryMatch *match, presentMatchList) {
d->matches.removeAll(*match);
}
foreach(const QString &matchId, presentMatchIdList) {
d->matchesById.remove(matchId);
}
UNLOCK(d)
emit d->q->matchesChanged();
return true;
}
bool RunnerContext::removeMatch(const QString matchId)
{
if (!isValid()) {
return false;
}
LOCK_FOR_READ(d)
const QueryMatch* match = d->matchesById.value(matchId, 0);
UNLOCK(d)
if (!match) {
return false;
}
LOCK_FOR_WRITE(d)
d->matches.removeAll(*match);
d->matchesById.remove(matchId);
UNLOCK(d)
emit d->q->matchesChanged();
return true;
}
QList<QueryMatch> RunnerContext::matches() const
{
LOCK_FOR_READ(d)
QList<QueryMatch> matches = d->matches;
UNLOCK(d);
return matches;
}
QueryMatch RunnerContext::match(const QString &id) const
{
LOCK_FOR_READ(d)
const QueryMatch *match = d->matchesById.value(id, 0);
UNLOCK(d)
if (match) {
return *match;
}
return QueryMatch(0);
}
void RunnerContext::setSingleRunnerQueryMode(bool enabled)
{
d->singleRunnerQueryMode = enabled;
}
bool RunnerContext::singleRunnerQueryMode() const
{
return d->singleRunnerQueryMode;
}
void RunnerContext::restore(const KConfigGroup &config)
{
const QStringList cfgList = config.readEntry("LaunchCounts", QStringList());
const QRegExp r("(\\d*) (.*)");
foreach (const QString& entry, cfgList) {
r.indexIn(entry);
int count = r.cap(1).toInt();
QString id = r.cap(2);
d->launchCounts[id] = count;
}
}
void RunnerContext::save(KConfigGroup &config)
{
QStringList countList;
typedef QHash<QString, int>::const_iterator Iterator;
Iterator end = d->launchCounts.constEnd();
for (Iterator i = d->launchCounts.constBegin(); i != end; ++i) {
countList << QString("%2 %1").arg(i.key()).arg(i.value());
}
config.writeEntry("LaunchCounts", countList);
config.sync();
}
void RunnerContext::run(const QueryMatch &match)
{
++d->launchCounts[match.id()];
match.run(*this);
}
} // Plasma namespace

File Metadata

Mime Type
text/x-diff
Expires
Fri, Nov 1, 9:02 AM (1 d, 12 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
9e/f3/a9ef4490a8e23342aae58a4587f9
Default Alt Text
(2 MB)

Event Timeline