diff --git a/KDE5PORTING.html b/KDE5PORTING.html index dbb6ab2042..ddc8f8cadb 100644 --- a/KDE5PORTING.html +++ b/KDE5PORTING.html @@ -1,174 +1,177 @@ Guide to Porting Applications to KDE Frameworks 5

Porting Applications to KDE Frameworks 5

Note

This document contains the changes you have to apply to programs written for KDE 4.x when you want to port them to KDE Frameworks 5.

Table of Contents

Global Changes

Return to the Table of Contents

Changes in kdecore

Return to the Table of Contents

Changes in kdeui

Return to the Table of Contents

Changes in kio

Return to the Table of Contents

Changes in kparts

Return to the Table of Contents

Changes in libkarchive

Return to the Table of Contents

diff --git a/kdecore/tests/CMakeLists.txt b/kdecore/tests/CMakeLists.txt index a592bfc526..c3c0d912aa 100644 --- a/kdecore/tests/CMakeLists.txt +++ b/kdecore/tests/CMakeLists.txt @@ -1,148 +1,147 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories( ${KDE4_KDECORE_INCLUDES} ) remove_definitions(-DQT_NO_CAST_FROM_ASCII) MACRO(KDECORE_UNIT_TESTS) FOREACH(_testname ${ARGN}) kde4_add_unit_test(${_testname} TESTNAME "kdecore-${_testname}" NOGUI ${_testname}.cpp) target_link_libraries(${_testname} ${KDE4_KDECORE_LIBS} ${QT_QTCORE_LIBRARY} ${QT_QTTEST_LIBRARY} ${QT_QTNETWORK_LIBRARY} kcoreaddons kde4support) if(WINCE) target_link_libraries(${_testname} ${WCECOMPAT_LIBRARIES}) endif(WINCE) ENDFOREACH(_testname) ENDMACRO(KDECORE_UNIT_TESTS) MACRO(KDECORE_EXECUTABLE_TESTS) FOREACH(_testname ${ARGN}) kde4_add_executable(${_testname} NOGUI TEST ${_testname}.cpp) target_link_libraries(${_testname} ${KDE4_KDECORE_LIBS} ${QT_QTCORE_LIBRARY} ${QT_QTTEST_LIBRARY} kcoreaddons kde4support) if(WINCE) target_link_libraries(${_testname} ${WCECOMPAT_LIBRARIES}) endif(WINCE) ENDFOREACH(_testname) ENDMACRO(KDECORE_EXECUTABLE_TESTS) ########### next target ############### KDECORE_UNIT_TESTS( kdirwatch_unittest klocaletimeformattest klocalizedstringtest kmountpointtest kurltest kstringhandlertest cplusplustest ksortablelisttest kmacroexpandertest kshelltest kasciitest ktimezonestest kentrymaptest - kurlmimetest ksharedptrtest kdatetimetest ksavefiletest kdesktopfiletest kautostarttest ksycocadicttest kglobalstatictest globalcleanuptest kprocesstest kconfigafterkglobaltest1 kconfigafterkglobaltest2 ktcpsockettest ksycocathreadtest kdebug_unittest kencodingdetectortest qcoreapptest kdebug_qcoreapptest kmimetype_nomimetypes ) if(NOT QT5_BUILD) KDECORE_UNIT_TESTS( kcalendartest kstandarddirstest kservicetest kconfigtest kglobaltest ) endif() if(NOT KDE_NO_DEPRECATED) KDECORE_UNIT_TESTS( klibloadertest ) endif(NOT KDE_NO_DEPRECATED) if(UNIX) KDECORE_UNIT_TESTS( klocalsockettest klocalsocketservertest ) endif(UNIX) KDECORE_EXECUTABLE_TESTS( kdirwatchtest krandomsequencetest kdebugtest kcmdlineargstest kmemtest dbuscalltest kmdcodectest startserviceby ) ########### klocaletest ############### # compile into the test since it's not exported set(klocaletest_SRCS klocaletest.cpp ../date/kdayperiod.cpp) kde4_add_unit_test(klocaletest ${klocaletest_SRCS}) target_link_libraries(klocaletest ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY} kcoreaddons) ########### kdatetimeformattertest ############### # compile KDateTimeFormatter and KDayPeriod into the test since it's not exported set(kdatetimeformattertest_SRCS kdatetimeformattertest.cpp ../date/kdatetimeformatter.cpp ../date/kdayperiod.cpp) kde4_add_unit_test(kdatetimeformattertest ${kdatetimeformattertest_SRCS}) target_link_libraries(kdatetimeformattertest ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY} kcoreaddons) ########### kdirwatchtest_gui ############### kde4_add_executable(kdirwatchtest_gui TEST kdirwatchtest_gui.cpp) target_link_libraries(kdirwatchtest_gui ${KDE4_KDECORE_LIBS} ${QT_QTGUI_LIBRARY} ${QT_QTTEST_LIBRARY} kcoreaddons) ########### kmimetypetest ############### if(NOT QT5_BUILD) set(kmimetypetest_SRCS kmimetypetest.cpp) kde4_add_unit_test(kmimetypetest TESTNAME kdecore-kmimetypetest ${kmimetypetest_SRCS}) target_link_libraries(kmimetypetest ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY} kcoreaddons) endif() ########### module for klibloadertest ############### if(NOT KDE_NO_DEPRECATED) set(klibloadertestmodule_PART_SRCS klibloadertest_module.cpp ) kde4_add_plugin(klibloadertestmodule ${klibloadertestmodule_PART_SRCS}) target_link_libraries(klibloadertestmodule ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY}) set_target_properties(klibloadertestmodule PROPERTIES SKIP_BUILD_RPATH FALSE BUILD_WITH_INSTALL_RPATH FALSE) endif(NOT KDE_NO_DEPRECATED) ########### module for klibloadertest4 ############### if (NOT WIN32) # TODO: reenable for win32 set(klibloadertestmodule4_PART_SRCS klibloadertest4_module.cpp ) kde4_add_plugin(klibloadertestmodule4 ${klibloadertestmodule4_PART_SRCS}) target_link_libraries(klibloadertestmodule4 ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY}) set_target_properties(klibloadertestmodule4 PROPERTIES SKIP_BUILD_RPATH FALSE BUILD_WITH_INSTALL_RPATH FALSE) endif (NOT WIN32) diff --git a/kdecore/tests/kurltest.cpp b/kdecore/tests/kurltest.cpp index e4e78aba5a..cd6c5c9526 100644 --- a/kdecore/tests/kurltest.cpp +++ b/kdecore/tests/kurltest.cpp @@ -1,2111 +1,2112 @@ // krazy:excludeall=qclasses /* This file is part of the KDE libraries Copyright (c) 1999-2005 Waldo Bastian Copyright (c) 2000-2005 David Faure 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. */ // -*- mode: c++; c-basic-offset: 2 -*- #include "kurltest.h" #include QTEST_MAIN( KUrlTest ) #include "kurl.h" #include "kuser.h" #include #include #include -#include //QCOMPARE cannot be used to strictly check for empty or null QString as it treats QString("") == QString() #define QSTREMPTY(_str) QVERIFY(!_str.isNull() && _str.isEmpty()) void KUrlTest::testEmptyURL() { KUrl emptyURL; QVERIFY( !emptyURL.isValid() ); QVERIFY( emptyURL.isEmpty() ); QVERIFY( emptyURL.prettyUrl().isEmpty() ); QCOMPARE( emptyURL.url(), QString() ); QVERIFY( emptyURL.url().isEmpty() ); QVERIFY( !emptyURL.url().isNull() ); // well, a null string would be correct too... QVERIFY(!emptyURL.isLocalFile()); KUrl emptyStringURL(""); QVERIFY( !emptyStringURL.isValid() ); QVERIFY( emptyStringURL.isEmpty() ); QVERIFY( emptyStringURL.url().isEmpty() ); QVERIFY( !emptyStringURL.url().isNull() ); QCOMPARE( emptyURL, emptyStringURL ); // Roundtrip via .url() KUrl emptyCopy( emptyURL.url() ); QCOMPARE( emptyURL, emptyCopy ); KUrl emptyStringCopy = KUrl( emptyStringURL.url() ); QCOMPARE( emptyStringURL, emptyCopy ); KUrl fileURL( "file:/" ); QVERIFY( !fileURL.isEmpty() ); fileURL = "file:///"; QVERIFY( !fileURL.isEmpty() ); KUrl udir; QCOMPARE( udir.url(), QString() ); QVERIFY( udir.isEmpty() ); QVERIFY( !udir.isValid() ); udir = udir.upUrl(); QVERIFY( udir.upUrl().isEmpty() ); } void KUrlTest::testIsValid() { KUrl url1( "gg:www.kde.org" ); QVERIFY( url1.isValid() ); url1 = "KDE"; QVERIFY( url1.isValid() ); // KDE3 difference: was FALSE url1 = "$HOME/.kde/share/config"; QVERIFY( url1.isValid() ); // KDE3 difference: was FALSE } void KUrlTest::testSetQuery() { KUrl url1 = KUrl( QByteArray( "http://www.kde.org/foo.cgi?foo=bar" ) ); QCOMPARE( url1.query(), QString("?foo=bar") ); url1.setQuery( "toto=titi&kde=rocks" ); QCOMPARE( url1.query(), QString("?toto=titi&kde=rocks") ); url1.setQuery( "?kde%20rocks&a=b" ); // must be encoded already, as documented QCOMPARE( url1.query(), QString("?kde%20rocks&a=b") ); // encoded, as documented url1.setQuery( "?" ); QCOMPARE( url1.query(), QString("?") ); url1.setQuery( "" ); QCOMPARE( url1.query(), QString("?") ); url1.setQuery( QString() ); QCOMPARE( url1.query(), QString() ); } void KUrlTest::testEmptyNullReference() { KUrl url1 = KUrl("http://www.kde.org"); QVERIFY( !url1.hasRef() ); QVERIFY( !url1.hasHTMLRef() ); QVERIFY( url1.ref().isNull() ); QVERIFY( url1.htmlRef().isNull() ); QVERIFY( url1.encodedHtmlRef().isNull() ); url1 = "http://www.kde.org#"; QVERIFY( url1.hasRef() ); QVERIFY( url1.hasHTMLRef() ); QSTREMPTY( url1.ref() ); QSTREMPTY( url1.htmlRef() ); QSTREMPTY( url1.encodedHtmlRef() ); } void KUrlTest::testSetRef() { KUrl url1 = KUrl( QByteArray( "http://www.kde.org/foo.cgi#foo=bar" ) ); QCOMPARE( url1.ref(), QString("foo%3Dbar" ) ); // KDE3 difference: was foo=bar #if 0// ditto (TODO) url1.setRef( "toto=titi&kde=rocks" ); QCOMPARE( url1.ref(), QString("toto=titi&kde=rocks" ) ); url1.setRef( "kde=rocks&a=b" ); QCOMPARE( url1.ref(), QString("kde=rocks&a=b" ) ); url1.setRef( "#" ); QCOMPARE( url1.ref(), QString("#" ) ); #endif url1.setRef( "" ); QSTREMPTY( url1.ref() ); QCOMPARE( url1.url(), QString("http://www.kde.org/foo.cgi#") ); url1.setRef( QString() ); QVERIFY( url1.ref().isNull() ); QCOMPARE( url1.url(), QString("http://www.kde.org/foo.cgi") ); } void KUrlTest::testSetHTMLRef() { KUrl url1 = KUrl( QByteArray( "http://www.kde.org/foo.cgi#foo=bar" ) ); QCOMPARE( url1.htmlRef(), QString("foo=bar") ); url1.setHTMLRef( "toto=titi&kde=rocks" ); QCOMPARE( url1.htmlRef(), QString("toto=titi&kde=rocks") ); url1.setHTMLRef( "kde=rocks&a=b" ); QCOMPARE( url1.htmlRef(), QString("kde=rocks&a=b") ); url1.setHTMLRef( "#" ); QCOMPARE( url1.htmlRef(), QString("#") ); QCOMPARE( url1.ref(), QString("%23") ); // it's encoded url1.setHTMLRef( "" ); QSTREMPTY( url1.htmlRef() ); QCOMPARE( url1.url(), QString("http://www.kde.org/foo.cgi#") ); url1.setHTMLRef( QString() ); QVERIFY( url1.htmlRef().isNull() ); QCOMPARE( url1.url(), QString("http://www.kde.org/foo.cgi") ); } void KUrlTest::testQUrl() { QUrl url1( "file:///home/dfaure/my#%2f" ); QCOMPARE( url1.toString(), QString( "file:///home/dfaure/my#%2f" ) ); #ifdef Q_WS_WIN QUrl url2( "file:///c:/home/dfaure/my#%2f" ); QCOMPARE( url2.toString(), QString( "file:///c:/home/dfaure/my#%2f" ) ); #endif // Show how toString() is confusing QUrl url3 = QUrl::fromLocalFile( "/home/dfaure/hash#file" ); QCOMPARE( url3.toString(), QString( "file:///home/dfaure/hash#file" ) ); // ouch QString url3Str = url3.toString(); QUrl url4(url3Str); QCOMPARE( url4.toString(), url3Str ); QVERIFY( url3 != url4 ); // unexpected, huh? //QCOMPARE( QString::fromLatin1(url4.toEncoded()), QString::fromLatin1(url3.toEncoded()) ); // fails } void KUrlTest::testIsLocalFile() { KUrl trash("trash:/"); QVERIFY(!trash.isLocalFile()); KUrl local_file_1("file://localhost/my/file"); QVERIFY( local_file_1.isLocalFile() ); KUrl local_file_2("file://www.kde.org/my/file"); QVERIFY( !local_file_2.isLocalFile() ); KUrl local_file_3; QString host = QString::fromLocal8Bit(qgetenv("HOSTNAME")); host.truncate(host.indexOf('.')); local_file_3.setHost(host); local_file_3.setPath("/my/file"); //qDebug("URL=%s\n", qPrintable( local_file_3.url() )); QVERIFY( local_file_3.isLocalFile() ); KUrl local_file_4("file:///my/file"); QVERIFY( local_file_4.isLocalFile() ); #ifdef Q_WS_WIN KUrl local_file_4a("file:///c:/my/file"); QVERIFY( local_file_4a.isLocalFile() ); #endif KUrl local_file_5; local_file_5.setPath("/foo?bar"); QCOMPARE( local_file_5.url(), QString("file:///foo%3Fbar") ); #ifdef Q_WS_WIN KUrl local_file_5a; local_file_5a.setPath("c:/foo?bar"); QCOMPARE( local_file_5a.url(), QString("file:///c:/foo%3Fbar") ); #endif } void KUrlTest::testSimpleMethods() // to test parsing, mostly { KUrl kde( "http://www.kde.org" ); QVERIFY( kde.isValid() ); QCOMPARE( kde.port(), -1 ); // KDE3 DIFFERENCE: was 0. KUrl mlc( "http://mlc:80/" ); QVERIFY( mlc.isValid() ); QCOMPARE( mlc.port(), 80 ); QCOMPARE( mlc.path(), QString("/") ); KUrl ulong("https://swww.gad.de:443/servlet/CookieAccepted?MAIL=s@gad.de&VER=25901"); QCOMPARE(ulong.host(),QString("swww.gad.de") ); QCOMPARE(ulong.path(),QString("/servlet/CookieAccepted") ); KUrl fileURL( "file:///home/dfaure/myfile" ); QCOMPARE( fileURL.url(), QString("file:///home/dfaure/myfile") ); QCOMPARE( fileURL.path(), QString("/home/dfaure/myfile") ); QVERIFY( !fileURL.hasRef() ); QString u1 = "file:/home/dfaure/my#myref"; KUrl url1(u1); // KDE3 difference: QUrl doesn't resolve file:/ into file:/// QCOMPARE( url1.url(), QString("file:///home/dfaure/my#myref") ); QVERIFY( url1.hasRef() ); QVERIFY( url1.hasHTMLRef() ); QVERIFY( !url1.hasSubUrl() ); QCOMPARE( url1.htmlRef(), QString("myref") ); QCOMPARE( url1.upUrl().url(), QString("file:///home/dfaure/") ); #if 0 QUrl qurl = QUrl::fromEncoded( "file:///home/dfaure/my#%23" ); printf( "toString = %s\n", qurl.toString().toLatin1().constData() ); printf( "toEncoded = %s\n", qurl.toEncoded().data() ); qurl = QUrl::fromEncoded( "file:///home/dfaure/my#%2f" ); printf( "toString = %s\n", qurl.toString().toLatin1().constData() ); printf( "toEncoded = %s\n", qurl.toEncoded().data() ); qurl = QUrl::fromEncoded( "file:///home/dfaure/my#/" ); printf( "toString = %s\n", qurl.toString().toLatin1().constData() ); printf( "toEncoded = %s\n", qurl.toEncoded().data() ); #endif u1 = "file:///home/dfaure/my#%2f"; url1 = u1; // KDE3: was %2f, Qt-4.0 to 4.4: #/, bad. 4.5: %2f again, good #if QT_VERSION < 0x040500 QCOMPARE( url1.url(), QString("file:///home/dfaure/my#/") ); #else QCOMPARE( url1.url(), QString("file:///home/dfaure/my#%2f") ); #endif QVERIFY( url1.hasRef() ); QVERIFY( url1.hasHTMLRef() ); QVERIFY( !url1.hasSubUrl() ); QCOMPARE( url1.ref().toLower(), QString("%2f") ); QCOMPARE( url1.encodedHtmlRef().toLower(), QString("%2f") ); QCOMPARE( url1.htmlRef(), QString("/") ); u1 = "file:///home/dfaure/my#%23"; url1 = u1; QCOMPARE( url1.url(), QString("file:///home/dfaure/my#%23") ); QVERIFY( url1.hasRef() ); QVERIFY( url1.hasHTMLRef() ); QVERIFY( !url1.hasSubUrl() ); QCOMPARE( url1.ref(), QString("%23") ); QCOMPARE( url1.encodedHtmlRef(), QString("%23") ); QCOMPARE( url1.htmlRef(), QString("#") ); #if 0 // TODO url1 = KUrl(url1, "#%6a"); QCOMPARE( url1.url(), QString("file:///home/dfaure/my#%6a") ); QVERIFY( url1.hasRef() ); QVERIFY( url1.hasHTMLRef() ); QVERIFY( !url1.hasSubUrl() ); QCOMPARE( url1.ref(), QString("j") ); QCOMPARE( url1.encodedHtmlRef().toLower(), QString("%6a") ); QCOMPARE( url1.htmlRef(), QString("j") ); #endif u1 = "file:///home/dfaure/my#myref"; url1 = u1; QCOMPARE( url1.url(), QString("file:///home/dfaure/my#myref") ); QVERIFY( url1.hasRef() ); QVERIFY( url1.hasHTMLRef() ); QVERIFY( !url1.hasSubUrl() ); QCOMPARE( url1.htmlRef(), QString("myref") ); QCOMPARE( url1.upUrl().url(), QString("file:///home/dfaure/") ); u1 = "file:/opt/kde2/qt2/doc/html/showimg-main-cpp.html#QObject::connect"; url1 = u1; QCOMPARE( url1.url(), QString("file:///opt/kde2/qt2/doc/html/showimg-main-cpp.html#QObject::connect") ); QVERIFY( url1.hasRef() ); QVERIFY( url1.hasHTMLRef() ); QVERIFY( !url1.hasSubUrl() ); QCOMPARE( url1.ref(), QString("QObject%3A%3Aconnect") ); QCOMPARE( url1.htmlRef(), QString("QObject::connect") ); QCOMPARE( url1.upUrl().url(), QString("file:///opt/kde2/qt2/doc/html/") ); u1 = "file:///opt/kde2/qt2/doc/html/showimg-main-cpp.html#QObject::connect"; url1 = u1; QCOMPARE( url1.url(), QString("file:///opt/kde2/qt2/doc/html/showimg-main-cpp.html#QObject::connect") ); QVERIFY( url1.hasRef() ); QVERIFY( url1.hasHTMLRef() ); QVERIFY( !url1.hasSubUrl() ); QCOMPARE( url1.ref(), QString("QObject%3A%3Aconnect") ); QCOMPARE( url1.htmlRef(), QString("QObject::connect") ); QCOMPARE( url1.upUrl().url(), QString("file:///opt/kde2/qt2/doc/html/") ); u1 = "file:/opt/kde2/qt2/doc/html/showimg-main-cpp.html#QObject:connect"; url1 = u1; QCOMPARE( url1.url(), QString("file:///opt/kde2/qt2/doc/html/showimg-main-cpp.html#QObject:connect") ); QVERIFY( url1.hasRef() ); QVERIFY( url1.hasHTMLRef() ); QVERIFY( !url1.hasSubUrl() ); QCOMPARE( url1.ref(), QString("QObject%3Aconnect") ); QCOMPARE( url1.htmlRef(), QString("QObject:connect") ); QCOMPARE( url1.upUrl().url(), QString("file:///opt/kde2/qt2/doc/html/") ); KUrl carsten; carsten.setPath("/home/gis/src/kde/kdelibs/kfile/.#kfiledetailview.cpp.1.18"); QCOMPARE( carsten.path(), QString("/home/gis/src/kde/kdelibs/kfile/.#kfiledetailview.cpp.1.18") ); KUrl longUserName("http://thisisaverylongusername@foobar.com/"); QCOMPARE(longUserName.prettyUrl(), QString("http://thisisaverylongusername@foobar.com/")); QCOMPARE(KUrl(longUserName.prettyUrl()).url(), QString("http://thisisaverylongusername@foobar.com/")); KUrl whitespaceInUser("http://www.google.com%20%20%20%20%20@foobar.com/"); QCOMPARE(whitespaceInUser.prettyUrl(), QString("http://www.google.com%20%20%20%20%20@foobar.com/")); KUrl whitespaceInPath("http://www.google.com/foo%20bar/"); QCOMPARE(whitespaceInPath.prettyUrl(), QString("http://www.google.com/foo bar/")); KUrl whitespaceInPath2("http://www.google.com/foo%20%20%20%20%20%20%20bar/"); QCOMPARE(whitespaceInPath2.prettyUrl(), QString("http://www.google.com/foo%20%20%20%20%20%20 bar/")); KUrl charles; charles.setPath( "/home/charles/foo%20moo" ); QCOMPARE( charles.path(), QString("/home/charles/foo%20moo") ); KUrl charles2("file:/home/charles/foo%20moo"); QCOMPARE( charles2.path(), QString("/home/charles/foo moo") ); //NOTE this test should be ran in UTF8 locale KUrl percentEncodedQuery( "http://mail.yandex.ru/message_part/%D0%9A%D1%80%D0%B8%D1%82%D0%B5%D1%80%D0%B8%D0%B8%20%D0%BE%D1%86%D0%B5%D0%BD%D0%B8%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%20%D0%BE%D1%80%D0%BB%D0%BE%D0%B2%D0%BE%D0%B9.rar?hid=1.1&mid=391.56424458.99241672611486679803334485488&name=%D0%9A%D1%80%D0%B8%D1%82%D0%B5%D1%80%D0%B8%D0%B8%20%D0%BE%D1%86%D0%B5%D0%BD%D0%B8%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%20%D0%BE%D1%80%D0%BB%D0%BE%D0%B2%D0%BE%D0%B9.rar" ); QCOMPARE( percentEncodedQuery.prettyUrl(), QString::fromUtf8("http://mail.yandex.ru/message_part/Критерии оценивания орловой.rar?hid=1.1&mid=391.56424458.99241672611486679803334485488&name=%D0%9A%D1%80%D0%B8%D1%82%D0%B5%D1%80%D0%B8%D0%B8%20%D0%BE%D1%86%D0%B5%D0%BD%D0%B8%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%20%D0%BE%D1%80%D0%BB%D0%BE%D0%B2%D0%BE%D0%B9.rar")); #ifdef Q_WS_WIN #ifdef Q_CC_MSVC #pragma message ("port KUser") #else #warning port KUser #endif #else KUrl tilde; KUser currentUser; const QString userName = currentUser.loginName(); QVERIFY( !userName.isEmpty() ); tilde.setPath( QString::fromUtf8( "~%1/Matériel" ).arg( userName ) ); QString homeDir = currentUser.homeDir(); QCOMPARE( tilde.url(), QString("file://%1/Mat%C3%A9riel").arg(homeDir)); tilde = KUrl("http://foo.bar/index.html"); tilde.setPath( "~slajsjdlsjd/test.html" ); QCOMPARE( tilde.url(), QString("http://foo.bar/~slajsjdlsjd/test.html")); #endif } void KUrlTest::testHostName() { KUrl u1("http://www.Abc.de/FR"); QCOMPARE(u1.host(), QString("www.abc.de")); // lowercase QCOMPARE(u1.path(), QString("/FR")); // lowercase QCOMPARE(u1.url(), QString("http://www.abc.de/FR")); // hostname is lowercase QCOMPARE(u1.prettyUrl(), QString("http://www.abc.de/FR")); // hostname is lowercase KUrl u2; u2.setProtocol("http"); u2.setHost("www.Abc.de"); QCOMPARE(u2.host(), QString("www.abc.de")); // lowercase QCOMPARE(u2.url(), QString("http://www.abc.de")); // lowercase KUrl u3("donkey://Abc/DE"); QCOMPARE(u3.host(), QString("abc")); // lowercase QCOMPARE(u3.url(), QString("donkey://abc/DE")); // lowercase } void KUrlTest::testEmptyQueryOrRef() { QUrl url = QUrl::fromEncoded( "http://www.kde.org" ); QCOMPARE( url.toEncoded(), QByteArray( "http://www.kde.org" ) ); QCOMPARE( url.encodedQuery(), QByteArray() ); url = QUrl::fromEncoded( "http://www.kde.org?" ); QCOMPARE( url.toEncoded(), QByteArray( "http://www.kde.org?" ) ); QCOMPARE( url.encodedQuery(), QByteArray() ); // note that QByteArray() == QByteArray("") url = QUrl::fromEncoded( "http://www.kde.org" ); QVERIFY( url.encodedQuery().isEmpty() ); QVERIFY( !url.hasQuery() ); url = QUrl::fromEncoded( "http://www.kde.org?" ); QVERIFY( !url.encodedQuery().isNull() ); QVERIFY( url.encodedQuery().isEmpty() ); QVERIFY( url.hasQuery() ); KUrl noQuery( "http://www.kde.org"); QCOMPARE( noQuery.query(), QString() ); // query at all QVERIFY( !noQuery.hasQuery() ); QVERIFY( !noQuery.hasRef()); QVERIFY( noQuery.ref().isNull() ); // Empty queries should be preserved! QUrl qurl = QUrl::fromEncoded("http://www.kde.org/cgi/test.cgi?", QUrl::TolerantMode); QCOMPARE( qurl.toEncoded(), QByteArray("http://www.kde.org/cgi/test.cgi?")); KUrl waba1( "http://www.kde.org/cgi/test.cgi?"); QCOMPARE( waba1.url(), QString( "http://www.kde.org/cgi/test.cgi?" ) ); QCOMPARE( waba1.query(), QString( "?" ) ); // empty query QVERIFY( waba1.hasQuery() ); // Empty references should be preserved waba1 = "http://www.kde.org/cgi/test.cgi#"; QCOMPARE( waba1.url(), QString("http://www.kde.org/cgi/test.cgi#") ); QVERIFY( waba1.hasRef() ); QVERIFY( waba1.hasFragment() ); QVERIFY( waba1.hasHTMLRef() ); QSTREMPTY( waba1.encodedHtmlRef() ); //qurl = QUrl::fromEncoded("http://www.kde.org/cgi/test.cgi#", QUrl::TolerantMode); //QCOMPARE( qurl.toEncoded(), QByteArray("http://www.kde.org/cgi/test.cgi#") ); KUrl tobi1( "http://host.net/path/?#http://broken-adsfk-poij31-029mu-2890zupyc-*!*'O-+-0i" ); QCOMPARE(tobi1.query(), QString("?")); // query is empty QVERIFY( tobi1.hasQuery() ); tobi1 = "http://host.net/path/#no-query"; QCOMPARE(tobi1.query(), QString("")); // no query QVERIFY( !tobi1.hasQuery() ); } void KUrlTest::testParsingTolerance() { // URLs who forgot to encode spaces in the query - the QUrl version first QUrl incorrectEncoded = QUrl::fromEncoded( "http://www.kde.org/cgi/qurl.cgi?hello=My Value" ); QVERIFY( incorrectEncoded.isValid() ); QVERIFY( !incorrectEncoded.toEncoded().isEmpty() ); //qDebug( "%s", incorrectEncoded.toEncoded().data() ); QCOMPARE( incorrectEncoded.toEncoded(), QByteArray("http://www.kde.org/cgi/qurl.cgi?hello=My%20Value") ); // URLs who forgot to encode spaces in the query. KUrl waba1( "http://www.kde.org/cgi/test.cgi?hello=My Value" ); //QVERIFY( waba1.isValid() ); QCOMPARE( waba1.url(), QString("http://www.kde.org/cgi/test.cgi?hello=My%20Value") ); // URL with ':' in query (':' should NOT be encoded!) waba1 = "http://www.kde.org/cgi/test.cgi?hello:My Value"; QCOMPARE( waba1.url(), QString("http://www.kde.org/cgi/test.cgi?hello:My%20Value") ); QCOMPARE( waba1.upUrl().url(), QString("http://www.kde.org/cgi/test.cgi") ); // URLs who forgot to encode spaces in the query. waba1 = "http://www.kde.org/cgi/test.cgi?hello=My Value+20"; QCOMPARE( waba1.url(), QString("http://www.kde.org/cgi/test.cgi?hello=My%20Value+20") ); } void KUrlTest::testNewLine() { QUrl qurl_newline_1 = QUrl::fromEncoded( "http://www.foo.bar/foo/bar\ngnork", QUrl::TolerantMode ); QVERIFY( qurl_newline_1.isValid() ); QCOMPARE( qurl_newline_1.toEncoded(), QByteArray("http://www.foo.bar/foo/bar%0Agnork") ); KUrl url_newline_1("http://www.foo.bar/foo/bar\ngnork"); QCOMPARE( url_newline_1.url(), QLatin1String("http://www.foo.bar/foo/bar%0Agnork") ); KUrl url_newline_2("http://www.foo.bar/foo?bar\ngnork"); QCOMPARE( url_newline_2.url(), QLatin1String("http://www.foo.bar/foo?bar%0Agnork") ); } void KUrlTest::testQueryParsing() { KUrl ldap( "ldap://host.com:6666/o=University%20of%20Michigan,c=US??sub?(cn=Babs%20Jensen)" ); QCOMPARE( ldap.host(), QString("host.com") ); QCOMPARE( ldap.port(), 6666 ); QCOMPARE( ldap.path(), QString("/o=University of Michigan,c=US") ); QCOMPARE( ldap.query(), QString("??sub?(cn=Babs%20Jensen)") ); QCOMPARE( ldap.url(), QString("ldap://host.com:6666/o=University%20of%20Michigan,c=US??sub?(cn=Babs%20Jensen)") ); ldap.setQuery("??sub?(cn=Karl%20Marx)"); QCOMPARE( ldap.query(), QString("??sub?(cn=Karl%20Marx)") ); QCOMPARE( ldap.url(), QString("ldap://host.com:6666/o=University%20of%20Michigan,c=US??sub?(cn=Karl%20Marx)") ); } void KUrlTest::testURLsWithoutPath() { // Urls without path (BR21387) KUrl waba1( "http://meine.db24.de?link=home_c_login_login" ); // has query QCOMPARE( waba1.url(), QString("http://meine.db24.de?link=home_c_login_login") ); QCOMPARE( waba1.path(), QString("") ); QCOMPARE( waba1.query(), QString("?link=home_c_login_login") ); waba1 = "http://a:389?b=c"; // has port and query QCOMPARE( waba1.url(), QString( "http://a:389?b=c" ) ); QCOMPARE( waba1.host(), QString( "a" ) ); QCOMPARE( waba1.port(), 389 ); QCOMPARE( waba1.path(), QString( "" ) ); QCOMPARE( waba1.query(), QString( "?b=c" ) ); // Urls without path (BR21387) waba1 = "http://meine.db24.de#link=home_c_login_login"; // has fragment QCOMPARE( waba1.url(), QString("http://meine.db24.de#link=home_c_login_login") ); QCOMPARE( waba1.path(), QString("")); waba1 = "http://a:389#b=c"; // has port and fragment //qDebug( "%s", qPrintable( waba1.url() ) ); QCOMPARE( waba1.scheme(), QString( "http" ) ); QCOMPARE( waba1.url(), QString( "http://a:389#b=c" ) ); QCOMPARE( waba1.host(), QString( "a" ) ); QCOMPARE( waba1.port(), 389 ); QCOMPARE( waba1.path(), QString( "" ) ); QCOMPARE( waba1.ref(), QString( "b%3Dc" ) ); // was b=c with KDE3, but the docu says encoded, so encoding the = is ok QCOMPARE( waba1.htmlRef(), QString( "b=c" ) ); QCOMPARE( waba1.query(), QString() ); QUrl schemeOnly( "ftp:" ); QVERIFY( schemeOnly.isValid() ); QCOMPARE( schemeOnly.scheme(), QString( "ftp" ) ); } void KUrlTest::testPathAndQuery() { #if 0 // this KDE3 test fails, but Tobias Anton didn't say where it came from, and Andreas Hanssen (TT) says: // "I can't see any reason to support this; it looks like a junk artifact from older days. // Everything after # is the fragment. Parsing what comes after # is broken; tolerant or not." KUrl tobi0("http://some.host.net/path/to/file#fragmentPrecedes?theQuery"); QCOMPARE( tobi0.ref(), QString("fragmentPrecedes") ); QCOMPARE( tobi0.query(), QString("?theQuery") ); #else // So we treat it as part of the fragment KUrl tobi0("http://some.host.net/path/to/file#foo?bar"); QCOMPARE( tobi0.ref(), QString("foo%3Fbar") ); QCOMPARE( tobi0.query(), QString() ); QCOMPARE( tobi0.prettyUrl(), QString("http://some.host.net/path/to/file#foo?bar") ); #endif KUrl tobi1( "http://host.net/path?myfirstquery#andsomeReference" ); tobi1.setEncodedPathAndQuery("another/path/?another&query"); QCOMPARE( tobi1.query(), QString("?another&query") ); QCOMPARE( tobi1.path(), QString("another/path/") ); // with trailing slash QCOMPARE( tobi1.encodedPathAndQuery(), QString( "another/path/?another&query" ) ); tobi1.setEncodedPathAndQuery("another/path?another&query"); QCOMPARE( tobi1.query(), QString("?another&query") ); QCOMPARE( tobi1.path(), QString("another/path") ); // without trailing slash QCOMPARE( tobi1.encodedPathAndQuery(), QString( "another/path?another&query" ) ); tobi1.setEncodedPathAndQuery("and%20another%2Bpath%2E?bug=%2B258301&query%2Bword"); QCOMPARE( tobi1.query(), QString("?bug=%2B258301&query%2Bword") ); // encoded QCOMPARE( tobi1.queryItem("bug"), QString("+258301") ); // decoded QCOMPARE( tobi1.path(), QString("and another+path.") ); // decoded QCOMPARE( tobi1.encodedPath().constData(), "and%20another+path." ); // from QUrl. It only encodes the space. QCOMPARE( tobi1.encodedPathAndQuery(), QString( "and%20another+path.?bug=%2B258301&query%2Bword" ) ); tobi1 = "http://host.net/path/#no-query"; QCOMPARE( tobi1.encodedPathAndQuery(), QString( "/path/" ) ); KUrl kde( "http://www.kde.org" ); QCOMPARE( kde.encodedPathAndQuery(), QString( "" ) ); QCOMPARE( kde.encodedPathAndQuery( KUrl::LeaveTrailingSlash, KUrl::AvoidEmptyPath ), QString( "/" ) ); KUrl theKow( "http://www.google.de/search?q=frerich&hlx=xx&hl=de&empty=&lr=lang+de&test=%2B%20%3A%25" ); QCOMPARE( theKow.encodedPathAndQuery(), QString( "/search?q=frerich&hlx=xx&hl=de&empty=&lr=lang+de&test=%2B%20%3A%25" ) ); KUrl uloc( "file:///home/dfaure/konqtests/Mat%C3%A9riel" ); QCOMPARE( uloc.encodedPathAndQuery(), QString( "/home/dfaure/konqtests/Mat%C3%A9riel" ) ); KUrl urlWithAccent( "file:///home/dfaure/konqtests/Matériel" ); QCOMPARE(urlWithAccent.encodedPathAndQuery(), QString("/home/dfaure/konqtests/Mat%C3%A9riel")); KUrl urlWithUnicodeChar(QString::fromUtf8("file:///home/dfaure/konqtests/Matériel")); QCOMPARE(urlWithUnicodeChar.encodedPathAndQuery(), QString("/home/dfaure/konqtests/Mat%C3%A9riel")); KUrl maelcum(QString::fromUtf8("http://a.b.c/äöu")); QCOMPARE(maelcum.encodedPathAndQuery(), QString("/%C3%A4%C3%B6u")); KUrl gof("file:%2Ftmp%2Fkde-ogoffart%2Fkmail"); // weird URL, but well ;) QCOMPARE(gof.path(), QString("/tmp/kde-ogoffart/kmail")); } void KUrlTest::testUpUrl() { KUrl url1( "ftp://user%40host.com@ftp.host.com/var/www/" ); QCOMPARE( url1.user(), QString("user@host.com" ) ); QCOMPARE( url1.host(), QString("ftp.host.com" ) ); KUrl up = url1.upUrl(); QCOMPARE( up.url(), QString("ftp://user%40host.com@ftp.host.com/var/") ); up = up.upUrl(); QCOMPARE( up.url(), QString("ftp://user%40host.com@ftp.host.com/") ); up = up.upUrl(); QCOMPARE( up.url(), QString("ftp://user%40host.com@ftp.host.com/") ); // unchanged // Going up from a relative url is not supported (#170695) KUrl invalid("tmp"); QVERIFY(invalid.isValid()); up = invalid.upUrl(); QVERIFY(!up.isValid()); } void KUrlTest::testSetFileName() // and addPath { KUrl u2( "file:/home/dfaure/my%20tar%20file.tgz#gzip:/#tar:/README" ); //qDebug( "* URL is %s", qPrintable( u2.url() ) ); u2.setFileName( "myfile.txt" ); QCOMPARE( u2.url(), QString("file:///home/dfaure/myfile.txt") ); u2.setFileName( "myotherfile.txt" ); QCOMPARE( u2.url(), QString("file:///home/dfaure/myotherfile.txt") ); // more tricky, renaming a directory (kpropertiesdialog.cc) QString tmpurl = "file:/home/dfaure/myolddir/"; if ( tmpurl.at(tmpurl.length() - 1) == '/') // It's a directory, so strip the trailing slash first tmpurl.truncate( tmpurl.length() - 1); KUrl newUrl(tmpurl); newUrl.setFileName( "mynewdir" ); QCOMPARE( newUrl.url(), QString("file:///home/dfaure/mynewdir") ); // addPath tests newUrl.addPath( "subdir" ); QCOMPARE( newUrl.url(), QString("file:///home/dfaure/mynewdir/subdir") ); newUrl.addPath( "/foo/" ); QCOMPARE( newUrl.url(), QString("file:///home/dfaure/mynewdir/subdir/foo/") ); u2 = "http://www.kde.org"; // no path u2.addPath( "subdir" ); QCOMPARE( u2.url(), QString("http://www.kde.org/subdir") ); u2.addPath( "" ); QCOMPARE( u2.url(), QString("http://www.kde.org/subdir") ); // unchanged QUrl qurl2 = QUrl::fromEncoded( "print:/specials/Print%20To%20File%20(PDF%252FAcrobat)", QUrl::TolerantMode ); QCOMPARE( qurl2.path(), QString::fromLatin1("/specials/Print To File (PDF%2FAcrobat)") ); QCOMPARE( qurl2.toEncoded(), QByteArray("print:/specials/Print%20To%20File%20(PDF%252FAcrobat)") ); // even more tricky u2 = "print:/specials/Print%20To%20File%20(PDF%252FAcrobat)"; QCOMPARE( u2.path(), QString("/specials/Print To File (PDF%2FAcrobat)") ); QCOMPARE( u2.fileName(), QString("Print To File (PDF%2FAcrobat)") ); u2.setFileName( "" ); QCOMPARE( u2.url(), QString("print:/specials/") ); u2 = "file:/specials/Print"; QCOMPARE( u2.path(), QString("/specials/Print") ); QCOMPARE( u2.fileName(), QString("Print") ); u2.setFileName( "" ); QCOMPARE( u2.url(), QString("file:///specials/") ); const char * u3 = "ftp://host/dir1/dir2/myfile.txt"; QVERIFY( !KUrl(u3).hasSubUrl() ); KUrl::List lst = KUrl::split( KUrl(u3) ); QCOMPARE( lst.count(), 1 ); QCOMPARE( lst.first().url(), QString("ftp://host/dir1/dir2/myfile.txt") ); // cdUp code KUrl lastUrl = lst.last(); QString dir = lastUrl.directory(); QCOMPARE( dir, QString("/dir1/dir2") ); // files without directories KUrl singleFile( "foo.txt" ); QCOMPARE( singleFile.path(), QString("foo.txt") ); QCOMPARE( singleFile.pathOrUrl(), QString("foo.txt") ); QString pre; #ifdef Q_OS_WIN // On Windows we explicitly prepend a slash, see KUrl::setPath pre = '/'; #endif singleFile.setFileName( "bar.bin" ); QCOMPARE( singleFile.path(), QString(pre + "bar.bin") ); QCOMPARE( singleFile.pathOrUrl(), QString(pre + "bar.bin") ); } void KUrlTest::testDirectory() { KUrl udir; udir.setPath("/home/dfaure/file.txt"); //qDebug( "URL is %s", qPrintable( udir.url() ) ); QCOMPARE( udir.path(), QString("/home/dfaure/file.txt") ); QCOMPARE( udir.url(), QString("file:///home/dfaure/file.txt") ); QCOMPARE( udir.directory(KUrl::AppendTrailingSlash|KUrl::ObeyTrailingSlash), QString("/home/dfaure/") ); QCOMPARE( udir.directory(KUrl::ObeyTrailingSlash), QString("/home/dfaure") ); KUrl u2( QByteArray("file:///home/dfaure/") ); // not ignoring trailing slash QCOMPARE( u2.directory(KUrl::AppendTrailingSlash|KUrl::ObeyTrailingSlash), QString("/home/dfaure/") ); QCOMPARE( u2.directory(KUrl::ObeyTrailingSlash), QString("/home/dfaure") ); // ignoring trailing slash QCOMPARE( u2.directory(KUrl::AppendTrailingSlash), QString("/home/") ); QCOMPARE( u2.directory(), QString("/home") ); // cleanPath() tests (before cd() since cd uses that) u2.cleanPath(); QCOMPARE( u2.url(), QString("file:///home/dfaure/") ); u2.addPath( "/..//foo" ); QCOMPARE( u2.url(), QString("file:///home/dfaure/..//foo") ); u2.cleanPath(KUrl::KeepDirSeparators); QCOMPARE( u2.url(), QString("file:///home//foo") ); u2.cleanPath(KUrl::SimplifyDirSeparators); QCOMPARE( u2.url(), QString("file:///home/foo") ); // cd() tests u2.cd(".."); QCOMPARE( u2.url(), QString("file:///home") ); u2.cd("thomas"); QCOMPARE( u2.url(), QString("file:///home/thomas") ); u2.cd("../"); QCOMPARE( u2.url(), QString("file:///home/") ); u2.cd("/opt/kde/bin/"); QCOMPARE( u2.url(), QString("file:///opt/kde/bin/") ); u2 = "ftp://ftp.kde.org/"; u2.cd("pub"); QCOMPARE( u2.url(), QString("ftp://ftp.kde.org/pub") ); u2 = u2.upUrl(); QCOMPARE( u2.url(), QString("ftp://ftp.kde.org/") ); } void KUrlTest::testPrettyURL() { KUrl tildeInPath("http://ferret.lmh.ox.ac.uk/%7Ekdecvs/"); QCOMPARE(tildeInPath.prettyUrl(), QString("http://ferret.lmh.ox.ac.uk/~kdecvs/")); // Tilde should not be re-encoded, see end of 2.3 in rfc3986 QCOMPARE(KUrl(tildeInPath.prettyUrl()).url(), QString("http://ferret.lmh.ox.ac.uk/~kdecvs/")); KUrl spaceInPath("file:/home/test/directory%20with%20spaces"); QCOMPARE(spaceInPath.prettyUrl(), QString("file:///home/test/directory with spaces")); QCOMPARE(KUrl(spaceInPath.prettyUrl()).url(), QString("file:///home/test/directory%20with%20spaces")); KUrl plusInPath("http://slashdot.org/~RAMMS%2BEIN/"); // #232008 QCOMPARE(plusInPath.prettyUrl(), QString::fromLatin1("http://slashdot.org/~RAMMS+EIN/")); QCOMPARE(KUrl(plusInPath.prettyUrl()).url(), QString::fromLatin1("http://slashdot.org/~RAMMS+EIN/")); KUrl notPretty3("fish://foo/%23README%23"); QCOMPARE( notPretty3.prettyUrl(), QString("fish://foo/%23README%23") ); KUrl url15581("http://alain.knaff.linux.lu/bug-reports/kde/spaces in url.html"); QCOMPARE( url15581.prettyUrl(), QString("http://alain.knaff.linux.lu/bug-reports/kde/spaces in url.html") ); QCOMPARE( url15581.url(), QString("http://alain.knaff.linux.lu/bug-reports/kde/spaces%20in%20url.html") ); KUrl newLineInQuery("http://localhost/?a=foo%0A%0Abar%20baz&b=foo%0Abar%21%3F"); QCOMPARE( newLineInQuery.prettyUrl(), QString("http://localhost/?a=foo%0A%0Abar%20baz&b=foo%0Abar%21%3F") ); KUrl nonUtf8Query("http://kde.org/?a=test%C2%A0foo%A0%A0%A0%A0bar"); QCOMPARE( nonUtf8Query.prettyUrl(), QString("http://kde.org/?a=test%C2%A0foo%A0%A0%A0%A0bar") ); // KDE3 test was for parsing "percentage%in%url.html", but this is not supported; too broken. KUrl url15581bis("http://alain.knaff.linux.lu/bug-reports/kde/percentage%25in%25url.html"); QCOMPARE( url15581bis.prettyUrl(), QString("http://alain.knaff.linux.lu/bug-reports/kde/percentage%25in%25url.html") ); QCOMPARE( url15581bis.url(), QString("http://alain.knaff.linux.lu/bug-reports/kde/percentage%25in%25url.html") ); KUrl urlWithPass("ftp://user:password@ftp.kde.org/path"); QCOMPARE( urlWithPass.pass(), QString::fromLatin1( "password" ) ); QCOMPARE( urlWithPass.prettyUrl(), QString::fromLatin1( "ftp://user@ftp.kde.org/path" ) ); KUrl urlWithPassAndNoUser("ftp://:password@ftp.kde.org/path"); QCOMPARE( urlWithPassAndNoUser.pass(), QString::fromLatin1( "password" ) ); QCOMPARE( urlWithPassAndNoUser.prettyUrl(), QString::fromLatin1( "ftp://ftp.kde.org/path" ) ); KUrl xmppUri("xmpp:ogoffart@kde.org"); QCOMPARE( xmppUri.prettyUrl(), QString::fromLatin1( "xmpp:ogoffart@kde.org" ) ); QUrl offEagleqUrl; offEagleqUrl.setEncodedUrl("http://www.sejlsport.dk/Pr%F8v%20noget%20nyt%20dokumenter.pdf", QUrl::TolerantMode); const QString offEaglePath = offEagleqUrl.path(); QCOMPARE((int)offEaglePath.at(2).unicode(), (int)'r'); #if 0 // CURRENTLY BROKEN, PENDING PRETTYURL REWRITE AND QT-4.5 (in thiago's hands) QCOMPARE((int)offEaglePath.at(3).unicode(), (int)0xf8); KUrl offEagle("http://www.sejlsport.dk/graphics/ds/DSUngdom/PDF/Pr%F8v%20noget%20nyt%20dokumenter/Invitation_Kerteminde_11.07.08.pdf"); QCOMPARE(offEagle.path(), QString::fromLatin1("/graphics/ds/DSUngdom/PDF/Pr%F8v noget nyt dokumenter/Invitation_Kerteminde_11.07.08.pdf")); QCOMPARE(offEagle.url(), QString::fromLatin1("http://www.sejlsport.dk/graphics/ds/DSUngdom/PDF/Pr%F8v%20noget%20nyt%20dokumenter/Invitation_Kerteminde_11.07.08.pdf")); QCOMPARE(offEagle.prettyUrl(), QString::fromLatin1("http://www.sejlsport.dk/graphics/ds/DSUngdom/PDF/Pr%F8v noget nyt dokumenter/Invitation_Kerteminde_11.07.08.pdf")); #endif KUrl openWithUrl("kate --use %25U"); QCOMPARE(openWithUrl.url(), QString::fromLatin1("kate%20--use%20%25U")); QCOMPARE(openWithUrl.prettyUrl(), QString::fromLatin1("kate --use %25U")); QCOMPARE(openWithUrl.path(), QString::fromLatin1("kate --use %U")); QCOMPARE(openWithUrl.pathOrUrl(), QString::fromLatin1("kate --use %25U")); // caused #153894; better not use KUrl for this. KUrl ipv6Address( "http://[::ffff:129.144.52.38]:81/index.html" ); QCOMPARE( ipv6Address.prettyUrl(), QString::fromLatin1( "http://[::ffff:129.144.52.38]:81/index.html" ) ); } void KUrlTest::testIsRelative() { QVERIFY( !KUrl::isRelativeUrl("man:mmap") ); QVERIFY( !KUrl::isRelativeUrl("javascript:doSomething()") ); QVERIFY( !KUrl::isRelativeUrl("file:///blah") ); // arguable, but necessary for KUrl( baseURL, "//www1.foo.bar" ); QVERIFY( KUrl::isRelativeUrl("/path") ); QVERIFY( KUrl::isRelativeUrl("something") ); KUrl something("something"); QCOMPARE(something.url(), QString("something")); QCOMPARE(something.protocol(), QString()); QVERIFY(!something.isLocalFile()); // Now let's test QUrl::isRelative. QVERIFY(!KUrl("file:///blah").isRelative()); QVERIFY(!KUrl("/blah").isRelative()); QVERIFY(KUrl("blah").isRelative()); QVERIFY(!KUrl("http://www.kde.org").isRelative()); QVERIFY(KUrl("foo/bar").isRelative()); } void KUrlTest::testRelativePath() { QString basePath = "/home/bastian"; QCOMPARE( KUrl::relativePath(basePath, "/home/bastian"), QString("./") ); bool b; QCOMPARE( KUrl::relativePath(basePath, "/home/bastian/src/plugins", &b), QString("./src/plugins")); QVERIFY( b ); QCOMPARE( KUrl::relativePath(basePath, "./src/plugins"), QString("./src/plugins") ); QCOMPARE( KUrl::relativePath(basePath, "/home/waba/src/plugins", &b), QString("../waba/src/plugins") ); QVERIFY( !b ); QCOMPARE( KUrl::relativePath(basePath, "/"), QString("../../")); QCOMPARE( KUrl::relativePath("/", "/"), QString("./") ); QCOMPARE( KUrl::relativePath("/", "/home/bastian"), QString("./home/bastian") ); QCOMPARE( KUrl::relativePath("", "/home/bastian"), QString("/home/bastian") ); } void KUrlTest::testRelativeURL() { KUrl baseURL( "http://www.kde.org/index.html" ); QCOMPARE( KUrl::relativeUrl(baseURL, KUrl("http://www.kde.org/index.html#help") ), QString("#help") ); QCOMPARE( KUrl::relativeUrl(baseURL, KUrl("http://www.kde.org/index.html?help=true") ), QString("index.html?help=true") ); QCOMPARE( KUrl::relativeUrl(baseURL, KUrl("http://www.kde.org/contact.html") ), QString("contact.html") ); QCOMPARE( KUrl::relativeUrl(baseURL, KUrl("ftp://ftp.kde.org/pub/kde") ), QString("ftp://ftp.kde.org/pub/kde") ); QCOMPARE( KUrl::relativeUrl(baseURL, KUrl("http://www.kde.org/index.html") ), QString("./") ); baseURL = "http://www.kde.org/info/index.html"; QCOMPARE( KUrl::relativeUrl(baseURL, KUrl("http://www.kde.org/bugs/contact.html") ), QString( "../bugs/contact.html") ); } void KUrlTest::testAdjustPath() { KUrl url0("file:///"); QCOMPARE( url0.url( KUrl::RemoveTrailingSlash ), QString("file:///") ); url0.adjustPath( KUrl::RemoveTrailingSlash ); QCOMPARE( url0.url(), QString("file:///") ); KUrl url1("file:///home/kde/"); url1.adjustPath( KUrl::LeaveTrailingSlash ); QCOMPARE( url1.path(), QString("/home/kde/" ) ); url1.adjustPath(KUrl::RemoveTrailingSlash); QCOMPARE( url1.path(), QString("/home/kde" ) ); url1.adjustPath(KUrl::RemoveTrailingSlash); QCOMPARE( url1.path(), QString("/home/kde" ) ); url1.adjustPath(KUrl::AddTrailingSlash); QCOMPARE( url1.path(), QString("/home/kde/" ) ); KUrl url2("file:///home/kde//"); url2.adjustPath(KUrl::LeaveTrailingSlash); QCOMPARE( url2.path(), QString("/home/kde//" ) ); url2.adjustPath(KUrl::RemoveTrailingSlash); QCOMPARE( url2.path(), QString("/home/kde" ) ); url2.adjustPath(KUrl::AddTrailingSlash); QCOMPARE( url2.path(), QString("/home/kde/" ) ); KUrl ftpurl1("ftp://ftp.kde.org/"); ftpurl1.adjustPath(KUrl::LeaveTrailingSlash); QCOMPARE( ftpurl1.path(), QString("/" ) ); ftpurl1.adjustPath(KUrl::RemoveTrailingSlash); QCOMPARE( ftpurl1.path(), QString("/" ) ); KUrl ftpurl2("ftp://ftp.kde.org///"); ftpurl2.adjustPath(KUrl::LeaveTrailingSlash); QCOMPARE( ftpurl2.path(), QString("///" ) ); ftpurl2.adjustPath(KUrl::RemoveTrailingSlash); // should remove all but trailing slash QCOMPARE( ftpurl2.path(), QString("/" ) ); ftpurl2.adjustPath(KUrl::AddTrailingSlash); QCOMPARE( ftpurl2.path(), QString("/" ) ); // Equivalent tests written by the KDirLister maintainer :) KUrl u3( QByteArray("ftp://brade@ftp.kde.org///") ); u3.adjustPath(KUrl::RemoveTrailingSlash); QCOMPARE( u3.url(), QString("ftp://brade@ftp.kde.org/") ); KUrl u4( QByteArray("ftp://brade@ftp.kde.org/kde///") ); u4.adjustPath(KUrl::RemoveTrailingSlash); QCOMPARE( u4.url(), QString("ftp://brade@ftp.kde.org/kde") ); // applying adjustPath(KUrl::RemoveTrailingSlash) twice should not yield two different urls // (follows from the above test) KUrl u5 = u4; u5.adjustPath(KUrl::RemoveTrailingSlash); QCOMPARE( u5.url(), u4.url() ); { KUrl remote("remote:/"); QCOMPARE( remote.url(KUrl::RemoveTrailingSlash ), QString("remote:/") ); remote.adjustPath( KUrl::RemoveTrailingSlash ); QCOMPARE( remote.url(), QString("remote:/") ); remote.adjustPath( KUrl::RemoveTrailingSlash ); QCOMPARE( remote.url(), QString("remote:/") ); } { KUrl remote2("remote://"); QCOMPARE( remote2.url(), QString("remote:") ); QCOMPARE( remote2.url(KUrl::RemoveTrailingSlash ), QString("remote:") ); } } void KUrlTest::testIPV6() { // IPV6 KUrl waba1( "http://[::FFFF:129.144.52.38]:81/index.html" ); QCOMPARE( waba1.host(), QString("::ffff:129.144.52.38") ); QCOMPARE( waba1.port(), 81 ); // IPV6 waba1 = "http://waba:pass@[::FFFF:129.144.52.38]:81/index.html"; QCOMPARE( waba1.host(), QString("::ffff:129.144.52.38") ); QCOMPARE( waba1.user(), QString("waba") ); QCOMPARE( waba1.pass(), QString("pass") ); QCOMPARE( waba1.port(), 81 ); // IPV6 waba1 = "http://www.kde.org/cgi/test.cgi"; waba1.setHost("::ffff:129.144.52.38"); QCOMPARE( waba1.url(), QString("http://[::ffff:129.144.52.38]/cgi/test.cgi") ); waba1 = "http://[::ffff:129.144.52.38]/cgi/test.cgi"; QVERIFY( waba1.isValid() ); // IPV6 without path waba1 = "http://[::ffff:129.144.52.38]?query"; QVERIFY( waba1.isValid() ); QCOMPARE( waba1.url(), QString("http://[::ffff:129.144.52.38]?query") ); QCOMPARE( waba1.query(), QString("?query") ); waba1 = "http://[::ffff:129.144.52.38]#ref"; QVERIFY( waba1.isValid() ); QCOMPARE( waba1.url(), QString("http://[::ffff:129.144.52.38]#ref") ); QCOMPARE( waba1.ref(), QString("ref") ); // IPV6 without path but with a port waba1 = "http://[::ffff:129.144.52.38]:81?query"; QVERIFY( waba1.isValid() ); QCOMPARE( waba1.url(), QString("http://[::ffff:129.144.52.38]:81?query") ); QCOMPARE( waba1.port(), 81 ); QCOMPARE( waba1.query(), QString("?query") ); waba1 = "http://[::ffff:129.144.52.38]:81#ref"; QVERIFY( waba1.isValid() ); QCOMPARE( waba1.url(), QString("http://[::ffff:129.144.52.38]:81#ref") ); QCOMPARE( waba1.port(), 81 ); QCOMPARE( waba1.ref(), QString("ref") ); KUrl weird( "http://[::fff:1:23]/" ); QVERIFY( weird.isValid() ); QCOMPARE( weird.host(), QString("::fff:1:23") ); } void KUrlTest::testBaseURL() // those are tests for the KUrl(base,relative) constructor { KUrl com1(KUrl("http://server.com/dir/"), "."); QCOMPARE( com1.url(), QString("http://server.com/dir/") ); KUrl com2(KUrl("http://server.com/dir/blubb/") , "blah/"); QCOMPARE( com2.url(), QString("http://server.com/dir/blubb/blah/") ); KUrl baseURL("http://www.foo.bar:80" ); QVERIFY( baseURL.isValid() ); QCOMPARE( baseURL.protocol(), QString( "http" ) ); // lowercase QCOMPARE( baseURL.port(), 80 ); QString relativeUrl = "//www1.foo.bar"; QVERIFY( KUrl::isRelativeUrl( relativeUrl ) ); // Mimick what KUrl(2 urls) does: QUrl qurl; qurl = "http://www.foo.bar:80"; QCOMPARE( qurl.toEncoded(), QByteArray("http://www.foo.bar:80") ); qurl.setHost( QString() ); qurl.setPath( QString() ); QCOMPARE( qurl.toEncoded(), QByteArray("http://:80") ); qurl.setPort( -1 ); QCOMPARE( qurl.toEncoded(), QByteArray("http:") ); // hmm we have no '//' anymore KUrl url1 ( baseURL, relativeUrl ); QCOMPARE( url1.url(), QString("http://www1.foo.bar")); QCOMPARE( url1.host(), QString("www1.foo.bar")); baseURL = "http://www.foo.bar"; KUrl rel_url( baseURL, "/top//test/../test1/file.html" ); QCOMPARE( rel_url.url(), QString("http://www.foo.bar/top//test1/file.html" )); baseURL = "http://www.foo.bar/top//test2/file2.html"; QCOMPARE( baseURL.url(), QString("http://www.foo.bar/top//test2/file2.html" )); baseURL = "file:/usr/local/src/kde2/////kdelibs/kio"; QCOMPARE( baseURL.url(), QString("file:///usr/local/src/kde2/////kdelibs/kio" )); baseURL = "http://www.foo.bar"; KUrl rel_url2( baseURL, "mailto:bastian@kde.org" ); QCOMPARE( rel_url2.url(), QString("mailto:bastian@kde.org" )); baseURL = "file:/usr/local/src/kde2/kdelibs/kio/"; KUrl url2( baseURL, "../../////kdebase/konqueror" ); QCOMPARE( url2.url(), QString("file:///usr/local/src/kde2/////kdebase/konqueror" )); // WABA: The following tests are to test the handling of relative URLs as // found on web-pages. KUrl waba1( "http://www.website.com/directory/?hello#ref" ); { KUrl waba2( waba1, "relative.html"); QCOMPARE( waba2.url(), QString("http://www.website.com/directory/relative.html") ); } { KUrl waba2( waba1, "../relative.html"); QCOMPARE( waba2.url(), QString("http://www.website.com/relative.html") ); } { KUrl waba2( waba1, "down/relative.html"); QCOMPARE( waba2.url(), QString("http://www.website.com/directory/down/relative.html") ); } { KUrl waba2( waba1, "/down/relative.html"); QCOMPARE( waba2.url(), QString("http://www.website.com/down/relative.html") ); } { KUrl waba2( waba1, "//www.kde.org/relative.html"); QCOMPARE( waba2.url(), QString("http://www.kde.org/relative.html") ); } { KUrl waba2( waba1, "relative.html?query=test&name=harry"); QCOMPARE( waba2.url(), QString("http://www.website.com/directory/relative.html?query=test&name=harry") ); waba2.removeQueryItem("query"); QCOMPARE( waba2.url(), QString("http://www.website.com/directory/relative.html?name=harry") ); waba2.addQueryItem("age", "18"); QCOMPARE( waba2.url(), QString("http://www.website.com/directory/relative.html?name=harry&age=18") ); waba2.addQueryItem("age", "21"); QCOMPARE( waba2.url(), QString("http://www.website.com/directory/relative.html?name=harry&age=18&age=21") ); waba2.addQueryItem("fullname", "Harry Potter"); QCOMPARE( waba2.url(), QString("http://www.website.com/directory/relative.html?name=harry&age=18&age=21&fullname=Harry%20Potter") ); } { KUrl waba2( waba1, "?query=test&name=harry"); QCOMPARE( waba2.url(), QString("http://www.website.com/directory/?query=test&name=harry") ); } { KUrl waba2( waba1, "relative.html#with_reference"); QCOMPARE( waba2.url(), QString("http://www.website.com/directory/relative.html#with_reference") ); } { KUrl waba2( waba1, "#"); QCOMPARE( waba2.url(), QString("http://www.website.com/directory/?hello#") ); } { KUrl waba2( waba1, ""); QCOMPARE( waba2.url(), QString("http://www.website.com/directory/?hello#ref") ); } { KUrl waba2( waba1, "#%72%22method"); // #243217 QCOMPARE( waba2.url(), QString("http://www.website.com/directory/?hello#%72%22method") ); } { KUrl base( "http://faure@www.kde.org" ); // no path KUrl waba2( base, "filename.html"); QCOMPARE( waba2.url(), QString("http://faure@www.kde.org/filename.html") ); } { KUrl base( "http://faure:pass@www.kde.org:81?query" ); KUrl rel1( base, "http://www.kde.org/bleh/"); // same host QCOMPARE( rel1.url(), QString("http://faure:pass@www.kde.org/bleh/") ); KUrl rel2( base, "http://www.yahoo.org"); // different host QCOMPARE( rel2.url(), QString("http://www.yahoo.org") ); } waba1 = "http://www.website.com/directory/filename?bla#blub"; { KUrl waba2( waba1, "relative.html"); QCOMPARE( waba2.url(), QString("http://www.website.com/directory/relative.html") ); } { KUrl waba2( waba1, "./relative.html"); QCOMPARE( waba2.url(), QString("http://www.website.com/directory/relative.html") ); } { KUrl waba2( waba1, "../relative.html"); QCOMPARE( waba2.url(), QString("http://www.website.com/relative.html") ); } { KUrl waba2( waba1, "down/relative.html"); QCOMPARE( waba2.url(), QString("http://www.website.com/directory/down/relative.html") ); } { KUrl waba2( waba1, "down/./relative.html"); QCOMPARE( waba2.url(), QString("http://www.website.com/directory/down/relative.html") ); } { KUrl waba2( waba1, "/down/relative.html"); QCOMPARE( waba2.url(), QString("http://www.website.com/down/relative.html") ); } { KUrl waba2( waba1, "relative.html?query=test&name=harry"); QCOMPARE( waba2.url(), QString("http://www.website.com/directory/relative.html?query=test&name=harry") ); } { KUrl waba2( waba1, "?query=test&name=harry"); QCOMPARE( waba2.url(), QString("http://www.website.com/directory/filename?query=test&name=harry") ); } { KUrl waba2( waba1, "relative.html#with_reference"); QCOMPARE( waba2.url(), QString("http://www.website.com/directory/relative.html#with_reference") ); } { KUrl waba2( waba1, "http:/relative.html"); // "rfc 1606 loophole" QCOMPARE( waba2.url(), QString("http://www.website.com/relative.html") ); } waba1.setUser("waldo"); QCOMPARE( waba1.url(), QString("http://waldo@www.website.com/directory/filename?bla#blub") ); waba1.setUser("waldo/bastian"); QCOMPARE( waba1.url(), QString("http://waldo%2Fbastian@www.website.com/directory/filename?bla#blub") ); waba1.setRef( QString() ); waba1.setPass( "pass" ); waba1.setDirectory( "/foo" ); waba1.setProtocol( "https" ); waba1.setHost( "web.com" ); waba1.setPort( 881 ); QCOMPARE( waba1.url(), QString("https://waldo%2Fbastian:pass@web.com:881/foo/?bla") ); waba1.setDirectory( "/foo/" ); QCOMPARE( waba1.url(), QString("https://waldo%2Fbastian:pass@web.com:881/foo/?bla") ); QUrl sadEagleTest; sadEagleTest.setEncodedUrl( "http://www.calorieking.com/foo.php?P0=[2006-3-8]", QUrl::TolerantMode ); QVERIFY( sadEagleTest.isValid() ); KUrl sadEagleExpectedResult( "http://www.calorieking.com/personal/diary/rpc.php?C=jsrs1&F=getDiaryDay&P0=[2006-3-8]&U=1141858921458" ); QVERIFY( sadEagleExpectedResult.isValid() ); KUrl sadEagleBase( "http://www.calorieking.com/personal/diary/" ); QVERIFY( sadEagleBase.isValid() ); KUrl sadEagleCombined( sadEagleBase, "/personal/diary/rpc.php?C=jsrs1&F=getDiaryDay&P0=[2006-3-8]&U=1141858921458" ); QCOMPARE( sadEagleCombined.url(), sadEagleExpectedResult.url() ); KUrl dxOffEagle( KUrl("http://something/other.html"), "newpage.html?[{\"foo: bar\"}]" ); //QEXPECT_FAIL("","Issue N183630, task ID 183874", Continue); // Fixed by _setEncodedUrl QVERIFY(dxOffEagle.isValid()); //QEXPECT_FAIL("","Issue N183630, task ID 183874", Continue); // Fixed by _setEncodedUrl QCOMPARE(dxOffEagle.url(), QString("http://something/newpage.html?%5B%7B%22foo:%20bar%22%7D%5D") ); QCOMPARE(dxOffEagle.prettyUrl(), QString("http://something/newpage.html?%5B%7B%22foo:%20bar%22%7D%5D") ); // QtSw issue 243557 QByteArray tsdgeos("http://google.com/c?c=Translation+%C2%BB+trunk|"); QUrl tsdgeosQUrl; tsdgeosQUrl.setEncodedUrl(tsdgeos, QUrl::TolerantMode); QVERIFY(tsdgeosQUrl.isValid()); // failed in Qt-4.4, works in Qt-4.5 QByteArray tsdgeosExpected("http://google.com/c?c=Translation+%C2%BB+trunk%7C"); //QCOMPARE(tsdgeosQUrl.toEncoded(), tsdgeosExpected); // unusable output from qtestlib... QCOMPARE(QString(tsdgeosQUrl.toEncoded()), QString(tsdgeosExpected)); KUrl tsdgeosUrl(tsdgeos); QCOMPARE(tsdgeosUrl.url(), QString(tsdgeosExpected)); QByteArray pipesAgain("http://translate.google.com/translate_t#en|uk|demo"); QUrl pipesUrl; pipesUrl.setEncodedUrl(pipesAgain, QUrl::TolerantMode); QVERIFY(pipesUrl.isValid()); QCOMPARE(QString(pipesUrl.toEncoded()), QString("http://translate.google.com/translate_t#en%7Cuk%7Cdemo")); // Shows up in nspluginviewer/flash QString flashRel = "javascript:window.location+\"__flashplugin_unique__\""; KUrl flashUrl(flashRel); QVERIFY(flashUrl.isValid()); KUrl flashBase("http://www.youtube.com/?v=JvOSnRD5aNk"); KUrl flashComposed(flashBase, flashRel); QCOMPARE(flashComposed.url(), QString("javascript:window.location+%22__flashplugin_unique__%22")); } void KUrlTest::testSetEncodedFragment_data() { QTest::addColumn("base"); QTest::addColumn("fragment"); QTest::addColumn("expected"); typedef QByteArray BA; QTest::newRow("basic test") << BA("http://www.kde.org") << BA("abc") << BA("http://www.kde.org#abc"); QTest::newRow("initial url has fragment") << BA("http://www.kde.org#old") << BA("new") << BA("http://www.kde.org#new"); QTest::newRow("encoded fragment") << BA("http://www.kde.org") << BA("a%20c") << BA("http://www.kde.org#a%20c"); QTest::newRow("with #") << BA("http://www.kde.org") << BA("a#b") << BA("http://www.kde.org#a#b"); QTest::newRow("empty") << BA("http://www.kde.org") << BA("") << BA("http://www.kde.org#"); } void KUrlTest::testSetEncodedFragment() { // Bug fixed in 4.5.1 by Thiago #if QT_VERSION < 0x040501 QSKIP("Bug in Qt-4.4/4.5-rc1: setEncodedFragment doesn't work if the initial url has no fragment", SkipAll); #endif QFETCH(QByteArray, base); QFETCH(QByteArray, fragment); QFETCH(QByteArray, expected); QUrl u; u.setEncodedUrl(base, QUrl::TolerantMode); QVERIFY(u.isValid()); u.setEncodedFragment(fragment); QVERIFY(u.isValid()); QCOMPARE(QString::fromLatin1(u.toEncoded()), QString::fromLatin1(expected)); } void KUrlTest::testSubURL() { QString u1 = "file:/home/dfaure/my%20tar%20file.tgz#gzip:/#tar:/#myref"; KUrl url1(u1); // KDE3: was #,#,#, Qt-4.0 to 4.4: #,%23,%23 . 4.5: #,#,#, good #if QT_VERSION < 0x040500 QCOMPARE( url1.url(), QString("file:///home/dfaure/my%20tar%20file.tgz#gzip:/%23tar:/%23myref") ); #else QCOMPARE( url1.url(), QString("file:///home/dfaure/my%20tar%20file.tgz#gzip:/#tar:/#myref") ); #endif QVERIFY( url1.hasRef() ); QVERIFY( !url1.isLocalFile() ); // Not strictly local! QVERIFY( url1.hasSubUrl() ); //QCOMPARE( url1.htmlRef(), QString("myref") ); QCOMPARE( url1.upUrl().url(), QString("file:///home/dfaure/") ); const KUrl::List splitList = KUrl::split( url1 ); QCOMPARE( splitList.count(), 3 ); //kDebug() << splitList.toStringList(); QCOMPARE( splitList[0].url(), QString("file:///home/dfaure/my%20tar%20file.tgz#myref") ); QCOMPARE( splitList[1].url(), QString("gzip:/#myref") ); QCOMPARE( splitList[2].url(), QString("tar:/#myref") ); #if QT_VERSION < 0x040500 QSKIP( "Multiple sub urls not supported with Qt < 4.5", SkipSingle ); #endif KUrl rejoined = KUrl::join(splitList); QCOMPARE(rejoined.url(), url1.url()); u1 = "error:/?error=14&errText=Unknown%20host%20asdfu.adgi.sdfgoi#http://asdfu.adgi.sdfgoi"; url1 = u1; QCOMPARE( url1.url(), QString("error:/?error=14&errText=Unknown%20host%20asdfu.adgi.sdfgoi#http://asdfu.adgi.sdfgoi") ); QVERIFY( url1.hasSubUrl() ); QVERIFY( url1.hasRef() ); QVERIFY( !url1.isLocalFile() ); QVERIFY( !url1.hasHTMLRef() ); u1 = "file:/home/dfaure/my%20tar%20file.tgz#gzip:/#tar:/"; url1 = u1; QCOMPARE( url1.url(), QString("file:///home/dfaure/my%20tar%20file.tgz#gzip:/#tar:/") ); QVERIFY( url1.hasRef() ); QVERIFY( !url1.hasHTMLRef() ); QVERIFY( url1.hasSubUrl() ); QVERIFY( url1.htmlRef().isNull() ); QCOMPARE( url1.upUrl().url(), QString("file:///home/dfaure/") ); u1 = "file:///home/dfaure/my%20tar%20file.tgz#gzip:/#tar:/"; url1 = u1; QCOMPARE( url1.url(), QString("file:///home/dfaure/my%20tar%20file.tgz#gzip:/#tar:/") ); QVERIFY( url1.hasRef() ); QVERIFY( !url1.hasHTMLRef() ); QVERIFY( url1.hasSubUrl() ); QVERIFY( url1.htmlRef().isNull() ); QCOMPARE( url1.upUrl().url(), QString("file:///home/dfaure/") ); #if 0 // This URL is broken, '#' should be escaped. u1 = "file:/home/dfaure/cdrdao-1.1.5/dao/#CdrDriver.cc#"; url1 = u1; QCOMPARE( url1.url(), QString("file:///home/dfaure/cdrdao-1.1.5/dao/#CdrDriver.cc#") ); QVERIFY( !url1.hasRef() ); QVERIFY( !url1.hasHTMLRef() ); QVERIFY( url1.hasSubUrl() ); QVERIFY( url1.htmlRef().isNull() ); QCOMPARE( url1.upUrl().url(), QString("file:///home/dfaure/cdrdao-1.1.5/dao/#CdrDriver.cc#") ); #endif u1 = "file:/home/dfaure/my%20tar%20file.tgz#gzip:/#tar:/README"; url1 = u1; QCOMPARE( url1.url(), QString("file:///home/dfaure/my%20tar%20file.tgz#gzip:/#tar:/README") ); QVERIFY( url1.hasRef() ); QVERIFY( !url1.hasHTMLRef() ); QVERIFY( url1.hasSubUrl() ); QVERIFY( url1.htmlRef().isNull() ); const KUrl::List url1Splitted = KUrl::split( url1 ); QCOMPARE( url1Splitted.count(), 3 ); //kDebug() << url1Splitted.toStringList(); QCOMPARE(url1Splitted[0].url(), QString("file:///home/dfaure/my%20tar%20file.tgz")); QCOMPARE(url1Splitted[1].url(), QString("gzip:/")); QCOMPARE(url1Splitted[2].url(), QString("tar:/README")); const KUrl url1Rejoined = KUrl::join(url1Splitted); // Bug fixed in 4.5.1 by Thiago #if QT_VERSION < 0x040501 QSKIP("Bug in Qt-4.4/4.5-rc1: setEncodedFragment doesn't work if the initial url has no fragment", SkipAll); #endif QCOMPARE(url1Rejoined.url(), url1.url()); QCOMPARE(url1.upUrl().url(), QString("file:///home/dfaure/my%20tar%20file.tgz#gzip:/#tar:/")); } void KUrlTest::testSetUser() { // The KUrl equality test below works because in Qt4 null == empty. QString str1; QString str2 = ""; QVERIFY( str1 == str2 ); KUrl emptyUserTest1( "http://www.foobar.com/"); QVERIFY( emptyUserTest1.user().isEmpty() ); QVERIFY( emptyUserTest1.user().isNull() ); // Expected result. This was fixed in Qt-4.4 KUrl emptyUserTest2( "http://www.foobar.com/"); emptyUserTest2.setUser( "" ); //QVERIFY( emptyUserTest2.user().isNull() ); QCOMPARE( emptyUserTest1==emptyUserTest2?"TRUE":"FALSE","TRUE" ); emptyUserTest2.setPass( "" ); QCOMPARE( emptyUserTest1==emptyUserTest2?"TRUE":"FALSE","TRUE" ); emptyUserTest2.setUser( "foo" ); QCOMPARE( emptyUserTest2.user(), QString::fromLatin1( "foo" ) ); emptyUserTest2.setUser( QString() ); QCOMPARE( emptyUserTest1==emptyUserTest2, true ); KUrl uga("ftp://ftp.kde.org"); uga.setUser("foo@bar"); QCOMPARE(uga.user(), QString::fromLatin1("foo@bar")); QCOMPARE(uga.url(), QString::fromLatin1("ftp://foo%40bar@ftp.kde.org")); } void KUrlTest::testComparisons() { /* QUrl version of urlcmp */ QUrl u1( "ftp://ftp.de.kde.org/dir" ); QUrl u2( "ftp://ftp.de.kde.org/dir/" ); QUrl::FormattingOptions options = QUrl::None; options |= QUrl::StripTrailingSlash; QString str1 = u1.toString(options); QString str2 = u2.toString(options); QCOMPARE( str1, u1.toString() ); QCOMPARE( str2, u1.toString() ); bool same = str1 == str2; QVERIFY( same ); QString ucmp1 = "ftp://ftp.de.kde.org/dir"; QString ucmp2 = "ftp://ftp.de.kde.org/dir/"; #ifndef KDE_NO_DEPRECATED QVERIFY(!urlcmp(ucmp1, ucmp2)); QVERIFY(urlcmp(ucmp1, ucmp2, KUrl::CompareWithoutTrailingSlash)); //only slash difference, ignore_trailing #endif { KUrl u1(ucmp1); KUrl u2(ucmp2); QVERIFY(!u1.equals(u2)); QVERIFY(u1.equals(u2, KUrl::CompareWithoutTrailingSlash)); QVERIFY(u1.equals(u2, KUrl::CompareWithoutTrailingSlash|KUrl::AllowEmptyPath)); } // Special case: no path vs '/'. { QString str1 = QString::fromLatin1( "ftp://ftp.de.kde.org" ); QString str2 = QString::fromLatin1( "ftp://ftp.de.kde.org/" ); #ifndef KDE_NO_DEPRECATED QVERIFY(!urlcmp(str1, str2)); QVERIFY(!urlcmp(str1, str2, KUrl::CompareWithoutTrailingSlash)); // empty path != '/' QVERIFY(urlcmp(str1, str2, KUrl::CompareWithoutTrailingSlash|KUrl::AllowEmptyPath)); #endif KUrl u1(str1); KUrl u2(str2); QVERIFY(!u1.equals(u2)); QVERIFY(!u1.equals(u2, KUrl::CompareWithoutTrailingSlash)); QVERIFY(u1.equals(u2, KUrl::CompareWithoutTrailingSlash|KUrl::AllowEmptyPath)); } QString ucmp3 = "ftp://ftp.de.kde.org/dir/#"; #ifndef KDE_NO_DEPRECATED QVERIFY( !urlcmp(ucmp2,ucmp3) ); // (only hash difference) QVERIFY( urlcmp(ucmp2,ucmp3,KUrl::CompareWithoutFragment) ); // (only hash difference, ignore_ref) QVERIFY( urlcmp(ucmp2,ucmp3,KUrl::CompareWithoutTrailingSlash | KUrl::CompareWithoutFragment) ); // (slash and hash difference, ignore_trailing, ignore_ref) QVERIFY( urlcmp("","",KUrl::CompareWithoutFragment) ); // (empty, empty) QVERIFY( urlcmp("","") ); // (empty, empty) QVERIFY( !urlcmp("",ucmp1) ); // (empty, not empty) QVERIFY( !urlcmp("",ucmp1,KUrl::CompareWithoutFragment) ); // (empty, not empty) QVERIFY( !urlcmp("file",ucmp1) ); // (malformed, not empty) QVERIFY( !urlcmp("file",ucmp1,KUrl::CompareWithoutFragment) ); // (malformed, not empty) #endif KUrl ftpUrl( "ftp://ftp.de.kde.org" ); QCOMPARE( ftpUrl.path(), QString()); ftpUrl = "ftp://ftp.de.kde.org/"; QVERIFY( ftpUrl.isParentOf( KUrl("ftp://ftp.de.kde.org/host/subdir/") ) ); ftpUrl = "ftp://ftp/host/subdir/"; QVERIFY( ftpUrl.isParentOf( KUrl("ftp://ftp/host/subdir/") ) ); QVERIFY( ftpUrl.isParentOf( KUrl("ftp://ftp/host/subdir") ) ); QVERIFY( !ftpUrl.isParentOf( KUrl("ftp://ftp/host/subdi") ) ); QVERIFY( ftpUrl.isParentOf( KUrl("ftp://ftp/host/subdir/blah/") ) ); QVERIFY( !ftpUrl.isParentOf( KUrl("ftp://ftp/blah/subdir") ) ); QVERIFY( !ftpUrl.isParentOf( KUrl("file:////ftp/host/subdir/") ) ); QVERIFY( ftpUrl.isParentOf( KUrl("ftp://ftp/host/subdir/subsub") ) ); } void KUrlTest::testStreaming() { // Streaming operators KUrl origURL( "http://www.website.com/directory/?#ref" ); KUrl urlWithPassAndNoUser("ftp://:password@ftp.kde.org/path"); KUrl accentuated(QString::fromUtf8("trash:/été")); KUrl empty( "" ); KUrl invalid( "ptal://mlc:usb" ); QVERIFY( !invalid.isValid() ); KUrl waba1( "http://[::ffff:129.144.52.38]:81?query" ); QByteArray buffer; { QDataStream stream( &buffer, QIODevice::WriteOnly ); stream << origURL << urlWithPassAndNoUser << accentuated << empty << invalid << waba1; // the IPv6 one } { QDataStream stream( buffer ); KUrl restoredURL; stream >> restoredURL; // streaming valid url QCOMPARE( restoredURL.url(), origURL.url() ); stream >> restoredURL; // streaming url with pass an no user QCOMPARE( restoredURL.url(), urlWithPassAndNoUser.url() ); stream >> restoredURL; // streaming valid url with accents QCOMPARE( restoredURL.url(), accentuated.url() ); stream >> restoredURL; // streaming empty url QVERIFY( !restoredURL.isValid() ); QVERIFY( restoredURL.isEmpty() ); QCOMPARE( restoredURL.url(), QString("") ); stream >> restoredURL; // streaming invalid url QVERIFY( !restoredURL.isValid() ); // note that this doesn't say what url() returns, that's for testBrokenStuff QCOMPARE( restoredURL.url(), invalid.url() ); stream >> restoredURL; // streaming ipv6 url with query QCOMPARE( restoredURL.url(), waba1.url() ); } } void KUrlTest::testBrokenStuff() { // Broken stuff KUrl waba1( "file:a" ); QCOMPARE( waba1.path(), QString("a") ); QCOMPARE( waba1.fileName(KUrl::ObeyTrailingSlash), QString("a") ); QCOMPARE( waba1.fileName(), QString("a") ); QCOMPARE( waba1.directory(KUrl::AppendTrailingSlash|KUrl::ObeyTrailingSlash), QString("") ); QCOMPARE( waba1.directory(KUrl::ObeyTrailingSlash), QString("") ); QCOMPARE( waba1.directory(), QString("") ); waba1 = "file:a/"; QCOMPARE( waba1.path(), QString("a/") ); QCOMPARE( waba1.fileName(KUrl::ObeyTrailingSlash), QString("") ); QCOMPARE( waba1.fileName(), QString("a") ); QCOMPARE( waba1.directory(KUrl::ObeyTrailingSlash | KUrl::AppendTrailingSlash), QString("a/") ); QCOMPARE( waba1.directory(KUrl::ObeyTrailingSlash), QString("a") ); QCOMPARE( waba1.directory(), QString("") ); waba1 = "file:"; QVERIFY( !waba1.isEmpty() ); QVERIFY( waba1.isValid() ); // KDE3: was invalid. Now it's qurl with scheme="file". QCOMPARE( waba1.path(), QString("") ); QCOMPARE( waba1.fileName(KUrl::ObeyTrailingSlash), QString("") ); QCOMPARE( waba1.fileName(), QString("") ); QCOMPARE( waba1.directory(KUrl::ObeyTrailingSlash | KUrl::AppendTrailingSlash), QString("") ); QCOMPARE( waba1.directory(KUrl::AppendTrailingSlash), QString("") ); QCOMPARE( waba1.directory(), QString("") ); KUrl broken; broken.setPath( QString() ); QVERIFY( !broken.isEmpty() ); // It's valid: because isValid refers to parsing, not to what happens afterwards. QVERIFY( broken.isValid() ); QCOMPARE( broken.path(), QString("") ); broken = "file://"; // just because coolo wondered QVERIFY( !broken.isEmpty() ); QVERIFY( broken.isValid() ); // KDE3: was invalid; same as above QCOMPARE( broken.path(), QString("") ); broken = "file"; QVERIFY( broken.isValid() ); // KDE3: was invalid; now it's path="file" #if 0 // KUrl has a Q_ASSERT on this now, so we can't test it. broken = "/"; QVERIFY( broken.isValid() ); QCOMPARE( broken.path(), QString("/") ); QCOMPARE( broken.url(), QString("/") ); // KDE3: was resolved to "file:///". QUrl supports urls without a protocol. QCOMPARE( broken.protocol(), QString("") ); // KDE3: was "file" #endif { QUrl url; url.setEncodedUrl("LABEL=USB_STICK", QUrl::TolerantMode); QVERIFY( url.isValid() ); QCOMPARE( url.path(), QString("LABEL=USB_STICK") ); QVERIFY( !url.isEmpty() ); } { QUrl url; url.setEncodedUrl("LABEL=USB_STICK", QUrl::TolerantMode); QVERIFY( url.isValid() ); QVERIFY( !url.isEmpty() ); // Qt-4.4-snapshot20080213 bug, reported to TT QCOMPARE( url.path(), QString("LABEL=USB_STICK") ); } broken = "LABEL=USB_STICK"; // 71430, can we use KUrl for this? QVERIFY( broken.isValid() ); // KDE3 difference: QUrl likes this one too QVERIFY( !broken.isEmpty() ); QCOMPARE( broken.path(), QString("LABEL=USB_STICK") ); // was "" in KDE3 } void KUrlTest::testMoreBrokenStuff() { #if 0 // BROKEN? // UNC like names KUrl unc1("FILE://localhost/home/root"); QCOMPARE( unc1.path(), QString("/home/root") ); QCOMPARE( unc1.url(), QString("file:///home/root") ); #endif KUrl unc2("file:///home/root"); // with empty host QCOMPARE( unc2.path(), QString("/home/root") ); QCOMPARE( unc2.url(), QString("file:///home/root") ); { KUrl unc3("FILE://remotehost/home/root"); #if 0 // BROKEN? QCOMPARE( unc3.path(), QString("//remotehost/home/root") ); #endif QCOMPARE( unc3.url(), QString("FILE://remotehost/home/root") ); // KDE3: file:// (lowercase) KUrl url2("file://atlas/dfaure"); QCOMPARE( url2.host(), QString("atlas") ); QCOMPARE( url2.path(), QString("/dfaure") ); //QCOMPARE( url3.path(), QString("//atlas/dfaure")); // says Waba //KUrl url3("file:////atlas/dfaure"); //QCOMPARE( url3.path(), QString("//atlas/dfaure")); // says Waba KUrl url4(url2, "//remotehost/home/root"); QCOMPARE( url4.host(), QString("remotehost") ); QCOMPARE( url4.path(), QString("/home/root") ); } KUrl weird; weird = "http://strange/"; QVERIFY( !weird.isValid() ); weird = "http://strange@strange/"; QVERIFY( !weird.isValid() ); { QUrl url; url.setUrl("http://strange@hostname/", QUrl::TolerantMode); QVERIFY(url.isValid()); } weird = "http://strange@hostname/"; QVERIFY( weird.isValid() ); // KDE3: was valid. Fixed by _setEncodedUrl. QCOMPARE( weird.host(), QString("hostname") ); weird = "http://strange;hostname/"; QVERIFY( !weird.isValid() ); weird = "http://strange;username@strange;hostname/"; QVERIFY( !weird.isValid() ); weird = "http://strange;username@hostname/"; QVERIFY( weird.isValid() ); QCOMPARE( weird.host(), QString("hostname") ); weird = "http://strange;username:password@strange;hostname/"; QVERIFY( !weird.isValid() ); weird = "http://strange;username:password@hostname/"; QVERIFY( weird.isValid() ); QCOMPARE( weird.host(), QString("hostname") ); weird = "http://[strange;hostname]/"; QVERIFY( !weird.isValid() ); weird = "ssh://user@machine?cmd='echo $HOSTNAME'"; QVERIFY( weird.isValid() ); QCOMPARE( weird.host(), QString("machine") ); //qDebug("%s",qPrintable( weird.query() ) ); QCOMPARE( weird.queryItem("cmd"), QString("'echo $HOSTNAME'") ); weird = ":pictures"; // for KFileDialog's startDir QVERIFY( weird.isValid() ); QVERIFY( weird.protocol().isEmpty() ); QVERIFY( weird.host().isEmpty() ); QCOMPARE( weird.path(), QString( "pictures" ) ); QCOMPARE( weird.url(), QString( "pictures" ) ); // # BUG: the : is missing weird = "::keyword"; // for KFileDialog's startDir QVERIFY( weird.isValid() ); QVERIFY( weird.protocol().isEmpty() ); QVERIFY( weird.host().isEmpty() ); QCOMPARE( weird.path(), QString( ":keyword" ) ); QCOMPARE( weird.url(), QString( ":keyword" ) ); // # BUG: the : is missing KUrl broken; broken = "ptal://mlc:usb:PC_970"; QVERIFY( !broken.isValid() ); QVERIFY (!broken.errorString().isEmpty()); //QCOMPARE( broken.url(), QString("ptal://mlc:usb:PC_970") ); // QUrl doesn't provide the initial string if it's an invalid url QUrl brokenUrl( "ptal://mlc:usb:PC_970" ); QVERIFY( !brokenUrl.isValid() ); QUrl dxOffEagle( "http://something/newpage.html?[{\"foo: bar\"}]", QUrl::TolerantMode); QVERIFY(dxOffEagle.isValid()); QCOMPARE(QString(dxOffEagle.toEncoded()), QString("http://something/newpage.html?%5B%7B%22foo:%20bar%22%7D%5D")); QUrl dxOffEagle2; dxOffEagle2.setUrl( "http://something/newpage.html?[{\"foo: bar\"}]", QUrl::TolerantMode); QVERIFY(dxOffEagle2.isValid()); QCOMPARE(dxOffEagle.toEncoded(), dxOffEagle2.toEncoded()); QUrl dxOffEagle3; dxOffEagle3.setEncodedUrl( "http://something/newpage.html?[{\"foo: bar\"}]", QUrl::TolerantMode); #if QT_VERSION < 0x040500 QEXPECT_FAIL("","Issue N183630, task ID 183874; works with setUrl so we do that in _setEncodedUrl now", Continue); #endif QVERIFY(dxOffEagle3.isValid()); QCOMPARE(dxOffEagle.toEncoded(), dxOffEagle3.toEncoded()); QUrl javascript; javascript.setUrl("javascript:window.location+\"__flashplugin_unique__\"", QUrl::TolerantMode); QVERIFY(javascript.isValid()); javascript.setEncodedUrl("javascript:window.location+\"__flashplugin_unique__\"", QUrl::TolerantMode); #if QT_VERSION < 0x040500 QEXPECT_FAIL("","Issue N183630, task ID 183874", Continue); #endif QVERIFY(javascript.isValid()); } void KUrlTest::testMailto() { const QString faure = "faure@kde.org"; const QString mailtoFaure = "mailto:" + faure; KUrl umail1 ( mailtoFaure ); QCOMPARE( umail1.protocol(), QString("mailto") ); QCOMPARE( umail1.path(), QString(faure) ); QVERIFY( !KUrl::isRelativeUrl(mailtoFaure) ); +#if 0 // TODO in Qt // Make sure populateMimeData() works correct: // 1. the text/plain part of the mimedata should not contain the mailto: part // 2. the uri-list part of the mimedata should contain the mailto: part QMimeData md; umail1.populateMimeData(&md); QCOMPARE(md.text(), faure); KUrl::List uriList = KUrl::List::fromMimeData(&md); QCOMPARE(uriList.size(), 1); KUrl first = uriList.first(); QCOMPARE(first.protocol(), QString("mailto")); QCOMPARE(first.path(), faure); +#endif KUrl mailtoOnly( "mailto:" ); QVERIFY( mailtoOnly.isValid() ); // KDE3 said invalid, QUrl is more tolerant KUrl url1( "mailto:user@host.com" ); QCOMPARE( url1.url(), QString("mailto:user@host.com") ); QCOMPARE( url1.url(KUrl::LeaveTrailingSlash), QString("mailto:user@host.com") ); KUrl mailtoUrl("mailto:null@kde.org?subject=hello"); QCOMPARE( mailtoUrl.url(), QString("mailto:null@kde.org?subject=hello" )); QUrl qurl("mailto:null@kde.org?subject=hello#world"); // #80165: is #world part of fragment or query? RFC-3986 says: fragment. QCOMPARE(QString::fromLatin1(qurl.encodedQuery()), QString("subject=hello")); { KUrl mailtoUrl; mailtoUrl.setProtocol("mailto"); mailtoUrl.setPath("a%b"); QCOMPARE(mailtoUrl.path(), QString("a%b")); QCOMPARE(mailtoUrl.url(), QString("mailto:a%25b")); } #if 0 // I wrote this test in the very first kurltest, but there's no proof that it's actually valid. // Andreas says this is broken, i.e. against rfc2368. // Let's see if the need ever comes up. KUrl umail2 ( "mailto:Faure David " ); QCOMPARE( umail2.protocol(), QString("mailto") ); QCOMPARE( umail2.path(), QString("Faure David ") ); QVERIFY( !KUrl::isRelativeUrl("mailto:faure@kde.org") ); #endif KUrl url183433("mailto:test[at]gmail[dot]com"); QCOMPARE(url183433.prettyUrl(), QString("mailto:test[at]gmail[dot]com")); QCOMPARE(url183433.url(), QString("mailto:test[at]gmail[dot]com")); } void KUrlTest::testSmb() { KUrl smb("smb://domain;username:password@server/share"); QVERIFY( smb.isValid() ); QCOMPARE( smb.user(), QString("domain;username") ); smb = "smb:/"; QVERIFY( smb.isValid() ); smb = "smb://"; // KDE3: kurl.cpp rev 1.106 made it invalid. Valid again with QUrl. QVERIFY( smb.isValid() ); smb = "smb://host"; QVERIFY( smb.isValid() ); smb = "smb:///"; QVERIFY( smb.isValid() ); KUrl implicitSmb("file://host/path"); QVERIFY(!implicitSmb.isLocalFile()); // -> kio_file will redirect to smb (by default) QCOMPARE(implicitSmb.host(), QString("host")); KUrl noImplicitSmb("//path1/path2"); QVERIFY(noImplicitSmb.isLocalFile()); QCOMPARE(noImplicitSmb.path(), QString("//path1/path2")); } void KUrlTest::testOtherProtocols() { KUrl about("about:"); QCOMPARE(about.path(), QString()); QCOMPARE(about.protocol(), QString("about")); KUrl aboutKonqueror("about:konqueror"); QCOMPARE(aboutKonqueror.path(), QString("konqueror")); KUrl leo( "data:text/html,http://www.invalid/" ); QVERIFY( leo.isValid() ); QCOMPARE( leo.protocol(), QString("data" ) ); QCOMPARE( leo.url(), QString("data:text/html,http://www.invalid/" ) ); QCOMPARE( leo.path(), QString("text/html,http://www.invalid/" ) ); KUrl ptal( "ptal://mlc:usb@PC_970" ); // User=mlc, password=usb, host=PC_970 #if QT_VERSION >= 0x040600 && QT_VERSION <= 0x040602 // Hostnames with underscores were invalid in 4.6.0 to 4.6.2, then allowed again in e301c82693c33c0f96c6a756d15fe35a9d877443 QCOMPARE(ptal.url(), QString("ptal://mlc:usb@")); // The host "PC_970" is invalid according to STD3 validation KUrl ptalSimpler("ptal://mlc:usb@pc123"); QCOMPARE(ptalSimpler.url(), QString("ptal://mlc:usb@pc123")); #else QUrl ptal_qurl; ptal_qurl.setUrl("ptal://mlc:usb@PC_970", QUrl::TolerantMode); QVERIFY(ptal_qurl.isValid()); QCOMPARE(QString::fromLatin1(ptal_qurl.toEncoded()), QString::fromLatin1("ptal://mlc:usb@pc_970")); QCOMPARE( ptal_qurl.host(), QString("pc_970") ); QVERIFY( ptal.isValid() ); QCOMPARE( ptal.host(), QString("pc_970") ); QCOMPARE( ptal.user(), QString("mlc") ); QCOMPARE( ptal.pass(), QString("usb") ); #endif } void KUrlTest::testUtf8() { QTextCodec* codec = QTextCodec::codecForName( "ISO-8859-1" ); QVERIFY( codec != 0 ); QTextCodec::setCodecForLocale( codec ); #if 0 { QUrl utest; utest.setScheme( "file" ); utest.setPath( QString::fromUtf8( "/home/dfaure/Matériel" ) ); printf( "utest.toString()=%s\n", utest.toString().toLatin1().constData() ); printf( "utest.path()=%s\n", utest.path().toLatin1().constData() ); printf( "utest.toEncoded()=%s\n", utest.toEncoded().data() ); } #endif // UTF8 tests KUrl uloc; uloc.setPath( QString::fromUtf8( "/home/dfaure/Matériel" ) ); QCOMPARE( uloc.url(), QString( "file:///home/dfaure/Mat%C3%A9riel") ); // KDE3 would say %E9 here; but from now on URLs are always utf8 encoded. QCOMPARE( uloc.path(), QString::fromUtf8( "/home/dfaure/Matériel") ); QCOMPARE( uloc.prettyUrl(), QString::fromUtf8( "file:///home/dfaure/Matériel") ); QCOMPARE( uloc.pathOrUrl(), QString::fromUtf8( "/home/dfaure/Matériel") ); // ... but that's why pathOrUrl is nicer. QCOMPARE( uloc.url(), QString( "file:///home/dfaure/Mat%C3%A9riel") ); uloc = KUrl("file:///home/dfaure/Mat%C3%A9riel"); QCOMPARE( uloc.path(), QString::fromUtf8("/home/dfaure/Matériel") ); QCOMPARE( uloc.url(), QString("file:///home/dfaure/Mat%C3%A9riel") ); KUrl umlaut1("http://www.clever-tanken.de/liste.asp?ort=N%FCrnberg&typ=Diesel"); QCOMPARE(umlaut1.url(), QString("http://www.clever-tanken.de/liste.asp?ort=N%FCrnberg&typ=Diesel")); KUrl umlaut2("http://www.clever-tanken.de/liste.asp?ort=N%FCrnberg&typ=Diesel"); // was ,106 QCOMPARE(umlaut2.url(), QString("http://www.clever-tanken.de/liste.asp?ort=N%FCrnberg&typ=Diesel")); KUrl urlWithUnicodeChar( QString::fromUtf8("file:///home/dfaure/Matériel") ); QCOMPARE( uloc.url(), QString("file:///home/dfaure/Mat%C3%A9riel") ); KUrl wkai(QString::fromUtf8("/tmp/魔")); QCOMPARE(wkai.url(), QString("file:///tmp/%E9%AD%94")); QCOMPARE(wkai.prettyUrl(), QString::fromUtf8("file:///tmp/魔")); // Show that the character "fraction slash" (U+2044) cannot appear in url(), // so it's ok to use that to encode urls as filenames (e.g. in kio_http cache) KUrl fractionSlash(QString("http://kde.org/a")+QChar(0x2044)+"b"); QCOMPARE(fractionSlash.url(), QString("http://kde.org/a%E2%81%84b")); QCOMPARE(fractionSlash.prettyUrl(), QString(QString("http://kde.org/a")+QChar(0x2044)+"b")); } void KUrlTest::testOtherEncodings() { QTextCodec::setCodecForLocale( QTextCodec::codecForName( "koi8-r" ) ); KUrl baseURL( "file:/home/coolo" ); KUrl russian = KUrl::fromPath( baseURL.directory(KUrl::AppendTrailingSlash) + QString::fromUtf8( "фгн7" ) ); //QCOMPARE( russian.url(), QString("file:///home/%C6%C7%CE7" ) ); // KDE3: was not using utf8 QCOMPARE( russian.url(), QString("file:///home/%D1%84%D0%B3%D0%BD7") ); // QUrl uses utf8 KUrl utf8_1("audiocd:/By%20Name/15%20Geantra%C3%AE.wav"); QCOMPARE( utf8_1.fileName(), QString::fromUtf8("15 Geantraî.wav") ); // KDE3: url had %2F, and fileName had '/'. But this is wrong, %2F means '/', // and filenames have to use %2F, so the url needs to have %252F. // KIO::encodeFileName takes care of that. KUrl utf8_2("audiocd:/By%20Name/15%252FGeantra%C3%AE.wav"); QCOMPARE( utf8_2.path(), QString::fromUtf8( "/By Name/15%2FGeantraî.wav" ) ); QCOMPARE( utf8_2.fileName(), QString::fromUtf8("15%2FGeantraî.wav") ); } void KUrlTest::testPathOrURL() { // passing path or url to the constructor: both work KUrl uloc( "/home/dfaure/konqtests/Mat%C3%A9riel" ); QCOMPARE( uloc.path(), QString("/home/dfaure/konqtests/Mat%C3%A9riel") ); uloc = KUrl( "http://www.kde.org" ); QCOMPARE( uloc.pathOrUrl(), uloc.url() ); QCOMPARE( uloc.pathOrUrl(KUrl::AddTrailingSlash), QString("http://www.kde.org/") ); uloc = KUrl( QString("www.kde.org" ) ); QVERIFY( uloc.isValid() ); // KDE3: was invalid. But it's now a url with path="www.kde.org", ok. uloc = KUrl( "index.html" ); QVERIFY( uloc.isValid() ); // KDE3: was invalid; same as above uloc = KUrl( "" ); QVERIFY( !uloc.isValid() ); #ifdef Q_WS_WIN #ifdef Q_CC_MSVC #pragma message ("port KUser") #else #warning port KUser #endif #else KUser currentUser; const QString userName = currentUser.loginName(); QVERIFY( !userName.isEmpty() ); uloc = KUrl(QString::fromUtf8("~%1/konqtests/Matériel").arg(userName)); QCOMPARE( uloc.path(), QString::fromUtf8("%1/konqtests/Matériel").arg(currentUser.homeDir()) ); #endif // pathOrUrl tests uloc = KUrl( "/home/dfaure/konqtests/Mat%C3%A9riel" ); QCOMPARE( uloc.pathOrUrl(), uloc.path() ); uloc = "http://www.kde.org"; QCOMPARE( uloc.url(), QString("http://www.kde.org") ); uloc = "file:///home/dfaure/konq%20tests/Mat%C3%A9riel#ref"; QCOMPARE( uloc.pathOrUrl(), QString::fromUtf8("file:///home/dfaure/konq tests/Matériel#ref" ) ); uloc = "file:///home/dfaure/konq%20tests/Mat%C3%A9riel?query"; QCOMPARE( uloc.pathOrUrl(), QString::fromUtf8("file:///home/dfaure/konq tests/Matériel?query" ) ); uloc = KUrl( "/home/dfaure/file#with#hash" ); QCOMPARE( uloc.pathOrUrl(), QString("/home/dfaure/file#with#hash" ) ); // test creation of url from pathOrUrl uloc = KUrl( QString::fromUtf8("http://www.kde.org/home/andreas/täst") ); QCOMPARE( KUrl( uloc.pathOrUrl() ), uloc ); uloc = KUrl( "http://www.kde.org/home/andreas/t%C3%A4st"); QCOMPARE( KUrl( uloc.pathOrUrl() ), uloc ); uloc = KUrl( QString::fromUtf8("file:///home/andreas/täst") ); QCOMPARE( KUrl( uloc.pathOrUrl() ), uloc ); uloc = KUrl( "file:///home/andreas/t%C3%A4st"); QCOMPARE( KUrl( uloc.pathOrUrl() ), uloc ); uloc = KUrl( "http://www.kde.org/home/kde?foobar#test" ); QCOMPARE( KUrl( uloc.pathOrUrl() ), uloc ); uloc = KUrl( "http://www.kde.org/home/%andreas"); QCOMPARE( KUrl( uloc.pathOrUrl() ), uloc ); } void KUrlTest::testAssignment() { // passing path or url to the constructor: both work KUrl uloc; uloc = "/home/dfaure/konqtests/Mat%C3%A9riel"; QCOMPARE( uloc.path(), QString("/home/dfaure/konqtests/Mat%C3%A9riel") ); KUrl u2; u2 = uloc; QCOMPARE( u2.path(), QString("/home/dfaure/konqtests/Mat%C3%A9riel") ); uloc = "http://www.kde.org"; QCOMPARE( uloc.pathOrUrl(), uloc.url() ); uloc = QString("www.kde.org" ); QVERIFY( uloc.isValid() ); uloc = KUrl( "index.html" ); QVERIFY( uloc.isValid() ); uloc = KUrl( "" ); QVERIFY( !uloc.isValid() ); #ifdef Q_WS_WIN #ifdef Q_CC_MSVC #pragma message ("port KUser") #else #warning port KUser #endif #else KUser currentUser; const QString userName = currentUser.loginName(); QVERIFY( !userName.isEmpty() ); uloc = QString::fromUtf8("~%1/konqtests/Matériel").arg(userName); QCOMPARE( uloc.path(), QString::fromUtf8("%1/konqtests/Matériel").arg(currentUser.homeDir()) ); uloc = QByteArray('~' + userName.toUtf8() + "/konqtests/Matériel"); QCOMPARE( uloc.path(), QString::fromUtf8("%1/konqtests/Matériel").arg(currentUser.homeDir()) ); // Assigning a KUrl to a QUrl and back QUrl qurl = uloc; QCOMPARE( qurl.toEncoded(), uloc.toEncoded() ); uloc = KUrl(qurl); QCOMPARE( qurl.toEncoded(), uloc.toEncoded() ); QCOMPARE( uloc.path(), QString::fromUtf8("%1/konqtests/Matériel").arg(currentUser.homeDir()) ); #endif } void KUrlTest::testQueryItem() { KUrl theKow( "http://www.google.de/search?q=frerich&hlx=xx&hl=de&empty=&lr=lang+de&test=%2B%20%3A%25" ); QCOMPARE( theKow.queryItem("q"), QString("frerich") ); QCOMPARE( theKow.queryItem("hl"), QString("de") ); QCOMPARE( theKow.queryItem("lr"), QString("lang de") ); // the '+' got decoded QCOMPARE( theKow.queryItem("InterstellarCounselor"), QString() ); QCOMPARE( theKow.queryItem("empty"), QString("") ); QCOMPARE( theKow.queryItem("test"), QString("+ :%") ); theKow.addQueryItem("a", "b+c" ); QCOMPARE( theKow.url(), QString("http://www.google.de/search?q=frerich&hlx=xx&hl=de&empty=&lr=lang+de&test=%2B%20%3A%25&a=b%2Bc") ); // KDE3 would use b%2Bc, but this is more correct QCOMPARE( theKow.queryItem("a"), QString("b+c") ); // note that the '+' remained // checks for queryItems(), which returns a QMap: KUrl queryUrl( "mailto:Marc%20Mutz%20%3cmutz@kde.org%3E?" "Subject=subscribe+me&" "body=subscribe+mutz%40kde.org&" "Cc=majordomo%40lists.kde.org" ); QCOMPARE(QStringList(queryUrl.queryItems(0).keys()).join(", "), QString( "Cc, Subject, body" ) ); QCOMPARE(QStringList(queryUrl.queryItems(KUrl::CaseInsensitiveKeys).keys()).join(", "), QString( "body, cc, subject" ) ); QCOMPARE(QStringList(queryUrl.queryItems(0).values()).join(", "), QString( "majordomo@lists.kde.org, subscribe me, subscribe mutz@kde.org" ) ); QCOMPARE(QStringList(queryUrl.queryItems(KUrl::CaseInsensitiveKeys).values()).join(", "), QString( "subscribe mutz@kde.org, majordomo@lists.kde.org, subscribe me" ) ); // TODO check for QUrl::queryItems } void KUrlTest::testEncodeString() { // Needed for #49616 QCOMPARE( QUrl::toPercentEncoding( "C++" ), QByteArray("C%2B%2B") ); QCOMPARE( QUrl::fromPercentEncoding( "C%2B%2B" ), QString("C++") ); QString output = QUrl::fromPercentEncoding( "C%00%0A" ); QString expected = QString::fromLatin1("C\0\n", 3); // no reason to stop at %00, in fact QCOMPARE( output.size(), expected.size() ); QCOMPARE( output, expected ); QCOMPARE( QUrl::fromPercentEncoding( "C%A" ), QString("C%A") ); // % A is not percent-encoding (pct-encoded = "%" HEXDIG HEXDIG) QCOMPARE( QUrl::toPercentEncoding( "%" ), QByteArray("%25") ); QCOMPARE( QUrl::toPercentEncoding( ":" ), QByteArray("%3A") ); } void KUrlTest::testIdn() { //qDebug( "trying QUrl with fromPercentEncoding" ); QUrl qurltest( QUrl::fromPercentEncoding( "http://\303\244.de" ) ); // a+trema in utf8 QVERIFY( qurltest.isValid() ); //qDebug( "trying QUrl with fromEncoded" ); QUrl qurl = QUrl::fromEncoded( "http://\303\244.de" ); // a+trema in utf8 QVERIFY( qurl.isValid() ); QCOMPARE( qurl.toEncoded(), QByteArray( "http://xn--4ca.de" ) ); //qDebug( "and now trying KUrl" ); KUrl thiago( QString::fromUtf8( "http://\303\244.de" ) ); // a+trema in utf8 QVERIFY( thiago.isValid() ); QCOMPARE( thiago.url(), QString("http://xn--4ca.de") ); // Non-ascii is allowed in IDN domain names. #if 0 // A broken test - not using utf8. and amantia forgot the real-world testcase. KUrl amantia( "http://%E1.foo.de" ); QVERIFY( amantia.isValid() ); QCOMPARE( amantia.url(), QString("http://xn--80a.foo.de") ); // Non-ascii is allowed in IDN domain names. #endif // A more valid test for % in hostnames: KUrl uwp( "http://%C3%A4.de" ); QVERIFY( uwp.isValid() ); QCOMPARE( thiago.url(), QString("http://xn--4ca.de") ); // as above } void KUrlTest::testUriMode() { KUrl url1; #if 0 // ###### TODO KUri url1 = "http://www.foobar.com/"; QCOMPARE(url1.uriMode(), KUrl::URL); url1 = "mailto:user@host.com"; QCOMPARE(url1.uriMode(), KUrl::Mailto); url1 = "data:text/plain,foobar?gazonk=flarp"; QCOMPARE(url1.uriMode(), KUrl::RawURI); QCOMPARE( url1.path(), QString("text/plain,foobar?gazonk=flarp") ); #endif url1 = "mailto:User@Host.COM?subject=Hello"; QCOMPARE( url1.path(), QString("User@Host.COM") ); // KDE3: "User@host.com". Does it matter? } void KUrlTest::testToLocalFile() { const QString localFile( "/tmp/print.pdf" ); const KUrl urlWithHost( "file://localhost/tmp/print.pdf" ); const KUrl urlWithoutHost( "file:///tmp/print.pdf" ); QCOMPARE( urlWithHost.toLocalFile(), localFile ); QCOMPARE( urlWithoutHost.toLocalFile(), localFile ); } void KUrlTest::testUrl_data() { QTest::addColumn( "url" ); QTest::addColumn( "urlLTS" ); QTest::addColumn( "urlRTS" ); QTest::addColumn( "urlATS" ); QTest::newRow("local file 1") << KUrl("file:///") << QString::fromLatin1("file:///") << QString::fromLatin1("file:///") << QString::fromLatin1("file:///"); QTest::newRow("local file 2") << KUrl("file:///home/kde/") << QString::fromLatin1("file:///home/kde/") << QString::fromLatin1("file:///home/kde") << QString::fromLatin1("file:///home/kde/"); QTest::newRow("local file 3") << KUrl("file:///home/kde//") << QString::fromLatin1("file:///home/kde//") << QString::fromLatin1("file:///home/kde") << QString::fromLatin1("file:///home/kde//"); QTest::newRow("ftp url") << KUrl("ftp://ftp.kde.org/") << QString::fromLatin1("ftp://ftp.kde.org/") << QString::fromLatin1("ftp://ftp.kde.org/") << QString::fromLatin1("ftp://ftp.kde.org/"); QTest::newRow("ftp url - 3 trailing slashes") << KUrl("ftp://ftp.kde.org///") << QString::fromLatin1("ftp://ftp.kde.org///") << QString::fromLatin1("ftp://ftp.kde.org/") << QString::fromLatin1("ftp://ftp.kde.org///"); } void KUrlTest::testUrl() { QFETCH( KUrl, url ); QFETCH( QString, urlLTS ); QFETCH( QString, urlRTS ); QFETCH( QString, urlATS ); QCOMPARE( url.url(KUrl::LeaveTrailingSlash), urlLTS ); QCOMPARE( url.url(KUrl::RemoveTrailingSlash), urlRTS ); QCOMPARE( url.url(KUrl::AddTrailingSlash), urlATS ); } void KUrlTest::testToStringList() { KUrl::List urls; urls << KUrl("file:///") << KUrl("file:///home/kde/") << KUrl("file:///home/kde//") << KUrl("ftp://ftp.kde.org/") << KUrl("ftp://ftp.kde.org///"); //kDebug() << urls.toStringList(KUrl::LeaveTrailingSlash); QCOMPARE( urls.toStringList(KUrl::LeaveTrailingSlash), QStringList() << QLatin1String("file:///") << QLatin1String("file:///home/kde/") << QLatin1String("file:///home/kde//") << QLatin1String("ftp://ftp.kde.org/") << QLatin1String("ftp://ftp.kde.org///") ); //kDebug() << urls.toStringList(KUrl::RemoveTrailingSlash); QCOMPARE( urls.toStringList(KUrl::RemoveTrailingSlash), QStringList() << QLatin1String("file:///") << QLatin1String("file:///home/kde") << QLatin1String("file:///home/kde") << QLatin1String("ftp://ftp.kde.org/") << QLatin1String("ftp://ftp.kde.org/") ); //kDebug() << urls.toStringList(KUrl::AddTrailingSlash); QCOMPARE( urls.toStringList(KUrl::AddTrailingSlash), QStringList() << QLatin1String("file:///") << QLatin1String("file:///home/kde/") << QLatin1String("file:///home/kde//") << QLatin1String("ftp://ftp.kde.org/") << QLatin1String("ftp://ftp.kde.org///") ); } diff --git a/kdeui/widgets/klineedit.cpp b/kdeui/widgets/klineedit.cpp index caf90dafe8..c8672fc98c 100644 --- a/kdeui/widgets/klineedit.cpp +++ b/kdeui/widgets/klineedit.cpp @@ -1,1882 +1,1883 @@ /* This file is part of the KDE libraries Copyright (C) 1997 Sven Radej (sven.radej@iname.com) Copyright (c) 1999 Patrick Ward Copyright (c) 1999 Preston Brown Re-designed for KDE 2.x by Copyright (c) 2000, 2001 Dawit Alemayehu Copyright (c) 2000, 2001 Carsten Pfeiffer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser 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 "klineedit.h" #include "klineedit_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include class KLineEditStyle; class KLineEditPrivate { public: KLineEditPrivate(KLineEdit* qq) : q(qq) { completionBox = 0L; handleURLDrops = true; grabReturnKeyEvents = false; userSelection = true; autoSuggest = false; disableRestoreSelection = false; enableSqueezedText = false; drawClickMsg = false; enableClickMsg = false; threeStars = false; completionRunning = false; if (!s_initialized) { KConfigGroup config( KGlobal::config(), "General" ); s_backspacePerformsCompletion = config.readEntry("Backspace performs completion", false); s_initialized = true; } clearButton = 0; clickInClear = false; wideEnoughForClear = true; // i18n: Placeholder text in line edit widgets is the text appearing // before any user input, briefly explaining to the user what to type // (e.g. "Enter search pattern"). // By default the text is set in italic, which may not be appropriate // for some languages and scripts (e.g. for CJK ideographs). QString metaMsg = i18nc("Italic placeholder text in line edits: 0 no, 1 yes", "1"); italicizePlaceholder = (metaMsg.trimmed() != QString('0')); } ~KLineEditPrivate() { // causes a weird crash in KWord at least, so let Qt delete it for us. // delete completionBox; delete style.data(); } void _k_slotSettingsChanged(int category) { Q_UNUSED(category); if (clearButton) { clearButton->setAnimationsEnabled(KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects); } } void _k_textChanged(const QString &txt) { // COMPAT (as documented): emit userTextChanged whenever textChanged is emitted // KDE5: remove userTextChanged signal, textEdited does the same... if (!completionRunning && (txt != userText)) { userText = txt; #ifndef KDE_NO_DEPRECATED emit q->userTextChanged(txt); #endif } } // Call this when a completion operation changes the lineedit text // "as if it had been edited by the user". void _k_updateUserText(const QString &txt) { if (!completionRunning && (txt != userText)) { userText = txt; q->setModified(true); #ifndef KDE_NO_DEPRECATED emit q->userTextChanged(txt); #endif emit q->textEdited(txt); emit q->textChanged(txt); } } // This is called when the lineedit is readonly. // Either from setReadOnly() itself, or when we realize that // we became readonly and setReadOnly() wasn't called (because it's not virtual) // Typical case: comboBox->lineEdit()->setReadOnly(true) void adjustForReadOnly() { if (style && style.data()->m_overlap) { style.data()->m_overlap = 0; } } /** * Checks whether we should/should not consume a key used as a shortcut. * This makes it possible to handle shortcuts in the focused widget before any * window-global QAction is triggered. */ bool overrideShortcut(const QKeyEvent* e); static bool s_initialized; static bool s_backspacePerformsCompletion; // Configuration option QColor previousHighlightColor; QColor previousHighlightedTextColor; bool userSelection: 1; bool autoSuggest : 1; bool disableRestoreSelection: 1; bool handleURLDrops:1; bool grabReturnKeyEvents:1; bool enableSqueezedText:1; bool completionRunning:1; int squeezedEnd; int squeezedStart; QPalette::ColorRole bgRole; QString squeezedText; QString userText; QString clickMessage; bool enableClickMsg:1; bool drawClickMsg:1; bool threeStars:1; bool possibleTripleClick :1; // set in mousePressEvent, deleted in tripleClickTimeout bool clickInClear:1; bool wideEnoughForClear:1; KLineEditButton *clearButton; QWeakPointer style; QString lastStyleClass; KCompletionBox *completionBox; bool italicizePlaceholder:1; QAction *noCompletionAction, *shellCompletionAction, *autoCompletionAction, *popupCompletionAction, *shortAutoCompletionAction, *popupAutoCompletionAction, *defaultAction; QMap disableCompletionMap; KLineEdit* q; }; QStyle *KLineEditStyle::style() const { if (m_subStyle) { return m_subStyle.data(); } return KdeUiProxyStyle::style(); } QRect KLineEditStyle::subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const { if (element == SE_LineEditContents) { KLineEditStyle *unconstThis = const_cast(this); if (m_sentinel) { // we are recursing: we're wrapping a style that wraps us! unconstThis->m_subStyle.clear(); } unconstThis->m_sentinel = true; QStyle *s = m_subStyle ? m_subStyle.data() : style(); QRect rect = s->subElementRect(SE_LineEditContents, option, widget); unconstThis->m_sentinel = false; if (option->direction == Qt::LeftToRight) { return rect.adjusted(0, 0, -m_overlap, 0); } else { return rect.adjusted(m_overlap, 0, 0, 0); } } return KdeUiProxyStyle::subElementRect(element, option, widget); } bool KLineEditPrivate::s_backspacePerformsCompletion = false; bool KLineEditPrivate::s_initialized = false; KLineEdit::KLineEdit( const QString &string, QWidget *parent ) : QLineEdit( string, parent ), d(new KLineEditPrivate(this)) { initWidget(); } KLineEdit::KLineEdit( QWidget *parent ) : QLineEdit( parent ), d(new KLineEditPrivate(this)) { initWidget(); } KLineEdit::~KLineEdit () { delete d; } void KLineEdit::initWidget() { d->possibleTripleClick = false; d->bgRole = backgroundRole(); // Enable the context menu by default. QLineEdit::setContextMenuPolicy( Qt::DefaultContextMenu ); KCursor::setAutoHideCursor( this, true, true ); KGlobalSettings::Completion mode = completionMode(); d->autoSuggest = (mode == KGlobalSettings::CompletionMan || mode == KGlobalSettings::CompletionPopupAuto || mode == KGlobalSettings::CompletionAuto); connect( this, SIGNAL(selectionChanged()), this, SLOT(slotRestoreSelectionColors())); connect(KGlobalSettings::self(), SIGNAL(settingsChanged(int)), this, SLOT(_k_slotSettingsChanged(int))); const QPalette p = palette(); if ( !d->previousHighlightedTextColor.isValid() ) d->previousHighlightedTextColor=p.color(QPalette::Normal,QPalette::HighlightedText); if ( !d->previousHighlightColor.isValid() ) d->previousHighlightColor=p.color(QPalette::Normal,QPalette::Highlight); d->style = new KLineEditStyle(this); setStyle(d->style.data()); connect(this, SIGNAL(textChanged(QString)), this, SLOT(_k_textChanged(QString))); } QString KLineEdit::clickMessage() const { return d->clickMessage; } void KLineEdit::setClearButtonShown(bool show) { if (show) { if (d->clearButton) { return; } d->clearButton = new KLineEditButton(this); d->clearButton->setObjectName("KLineEditButton"); d->clearButton->setCursor( Qt::ArrowCursor ); d->clearButton->setToolTip( i18nc( "@action:button Clear current text in the line edit", "Clear text" ) ); updateClearButtonIcon(text()); updateClearButton(); connect(this, SIGNAL(textChanged(QString)), this, SLOT(updateClearButtonIcon(QString))); } else { disconnect(this, SIGNAL(textChanged(QString)), this, SLOT(updateClearButtonIcon(QString))); delete d->clearButton; d->clearButton = 0; d->clickInClear = false; if (d->style) { d->style.data()->m_overlap = 0; } } } bool KLineEdit::isClearButtonShown() const { return d->clearButton != 0; } QSize KLineEdit::clearButtonUsedSize() const { QSize s; if (d->clearButton) { const int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, this); s = d->clearButton->sizeHint(); s.rwidth() += frameWidth; } return s; } // Decides whether to show or hide the icon; called when the text changes void KLineEdit::updateClearButtonIcon(const QString& text) { if (!d->clearButton) { return; } if (isReadOnly()) { d->adjustForReadOnly(); return; } // set proper icon if necessary if (d->clearButton->pixmap().isNull()) { const int clearButtonState = KIconLoader::DefaultState; if (layoutDirection() == Qt::LeftToRight) { d->clearButton->setPixmap(SmallIcon("edit-clear-locationbar-rtl", 0, clearButtonState)); } else { d->clearButton->setPixmap(SmallIcon("edit-clear-locationbar-ltr", 0, clearButtonState)); } } // trigger animation if (d->wideEnoughForClear && text.length() > 0) { d->clearButton->animateVisible(true); } else { d->clearButton->animateVisible(false); } } // Determine geometry of clear button. Called initially, and on resizeEvent. void KLineEdit::updateClearButton() { if (!d->clearButton) { return; } if (isReadOnly()) { d->adjustForReadOnly(); return; } const QSize geom = size(); const int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth,0,this); const int buttonWidth = d->clearButton->sizeHint().width(); const QSize newButtonSize(buttonWidth, geom.height()); const QFontMetrics fm(font()); const int em = fm.width("m"); // make sure we have enough room for the clear button // no point in showing it if we can't also see a few characters as well const bool wideEnough = geom.width() > 4 * em + buttonWidth + frameWidth; if (newButtonSize != d->clearButton->size()) { d->clearButton->resize(newButtonSize); } if (d->style) { d->style.data()->m_overlap = wideEnough ? buttonWidth + frameWidth : 0; } if (layoutDirection() == Qt::LeftToRight ) { d->clearButton->move(geom.width() - frameWidth - buttonWidth - 1, 0); } else { d->clearButton->move(frameWidth + 1, 0); } if (wideEnough != d->wideEnoughForClear) { // we may (or may not) have been showing the button, but now our // positiong on that matter has shifted, so let's ensure that it // is properly visible (or not) d->wideEnoughForClear = wideEnough; updateClearButtonIcon(text()); } } void KLineEdit::setCompletionMode( KGlobalSettings::Completion mode ) { KGlobalSettings::Completion oldMode = completionMode(); if ( oldMode != mode && (oldMode == KGlobalSettings::CompletionPopup || oldMode == KGlobalSettings::CompletionPopupAuto ) && d->completionBox && d->completionBox->isVisible() ) d->completionBox->hide(); // If the widgets echo mode is not Normal, no completion // feature will be enabled even if one is requested. if ( echoMode() != QLineEdit::Normal ) mode = KGlobalSettings::CompletionNone; // Override the request. if ( kapp && !KAuthorized::authorize("lineedit_text_completion") ) mode = KGlobalSettings::CompletionNone; if ( mode == KGlobalSettings::CompletionPopupAuto || mode == KGlobalSettings::CompletionAuto || mode == KGlobalSettings::CompletionMan ) d->autoSuggest = true; else d->autoSuggest = false; KCompletionBase::setCompletionMode( mode ); } void KLineEdit::setCompletionModeDisabled( KGlobalSettings::Completion mode, bool disable ) { d->disableCompletionMap[ mode ] = disable; } void KLineEdit::setCompletedText( const QString& t, bool marked ) { if ( !d->autoSuggest ) return; const QString txt = text(); if ( t != txt ) { setText(t); if ( marked ) setSelection(t.length(), txt.length()-t.length()); setUserSelection(false); } else setUserSelection(true); } void KLineEdit::setCompletedText( const QString& text ) { KGlobalSettings::Completion mode = completionMode(); const bool marked = ( mode == KGlobalSettings::CompletionAuto || mode == KGlobalSettings::CompletionMan || mode == KGlobalSettings::CompletionPopup || mode == KGlobalSettings::CompletionPopupAuto ); setCompletedText( text, marked ); } void KLineEdit::rotateText( KCompletionBase::KeyBindingType type ) { KCompletion* comp = compObj(); if ( comp && (type == KCompletionBase::PrevCompletionMatch || type == KCompletionBase::NextCompletionMatch ) ) { QString input; if (type == KCompletionBase::PrevCompletionMatch) input = comp->previousMatch(); else input = comp->nextMatch(); // Skip rotation if previous/next match is null or the same text if ( input.isEmpty() || input == displayText() ) return; setCompletedText( input, hasSelectedText() ); } } void KLineEdit::makeCompletion( const QString& text ) { KCompletion *comp = compObj(); KGlobalSettings::Completion mode = completionMode(); if ( !comp || mode == KGlobalSettings::CompletionNone ) return; // No completion object... const QString match = comp->makeCompletion( text ); if ( mode == KGlobalSettings::CompletionPopup || mode == KGlobalSettings::CompletionPopupAuto ) { if ( match.isEmpty() ) { if ( d->completionBox ) { d->completionBox->hide(); d->completionBox->clear(); } } else setCompletedItems( comp->allMatches() ); } else // Auto, ShortAuto (Man) and Shell { // all other completion modes // If no match or the same match, simply return without completing. if ( match.isEmpty() || match == text ) return; if ( mode != KGlobalSettings::CompletionShell ) setUserSelection(false); if ( d->autoSuggest ) setCompletedText( match ); } } void KLineEdit::setReadOnly(bool readOnly) { // Do not do anything if nothing changed... if (readOnly == isReadOnly ()) { return; } QLineEdit::setReadOnly(readOnly); if (readOnly) { d->bgRole = backgroundRole(); setBackgroundRole(QPalette::Window); if (d->enableSqueezedText && d->squeezedText.isEmpty()) { d->squeezedText = text(); setSqueezedText(); } if (d->clearButton) { d->clearButton->animateVisible(false); d->adjustForReadOnly(); } } else { if (!d->squeezedText.isEmpty()) { setText(d->squeezedText); d->squeezedText.clear(); } setBackgroundRole(d->bgRole); updateClearButton(); } } void KLineEdit::setSqueezedText( const QString &text) { setSqueezedTextEnabled(true); setText(text); } void KLineEdit::setSqueezedTextEnabled( bool enable ) { d->enableSqueezedText = enable; } bool KLineEdit::isSqueezedTextEnabled() const { return d->enableSqueezedText; } void KLineEdit::setText( const QString& text ) { if( d->enableClickMsg ) { d->drawClickMsg = text.isEmpty(); update(); } if( d->enableSqueezedText && isReadOnly() ) { d->squeezedText = text; setSqueezedText(); return; } QLineEdit::setText( text ); } void KLineEdit::setSqueezedText() { d->squeezedStart = 0; d->squeezedEnd = 0; const QString fullText = d->squeezedText; const QFontMetrics fm(fontMetrics()); const int labelWidth = size().width() - 2*style()->pixelMetric(QStyle::PM_DefaultFrameWidth) - 2; const int textWidth = fm.width(fullText); if (textWidth > labelWidth) { // start with the dots only QString squeezedText = "..."; int squeezedWidth = fm.width(squeezedText); // estimate how many letters we can add to the dots on both sides int letters = fullText.length() * (labelWidth - squeezedWidth) / textWidth / 2; squeezedText = fullText.left(letters) + "..." + fullText.right(letters); squeezedWidth = fm.width(squeezedText); if (squeezedWidth < labelWidth) { // we estimated too short // add letters while text < label do { letters++; squeezedText = fullText.left(letters) + "..." + fullText.right(letters); squeezedWidth = fm.width(squeezedText); } while (squeezedWidth < labelWidth); letters--; squeezedText = fullText.left(letters) + "..." + fullText.right(letters); } else if (squeezedWidth > labelWidth) { // we estimated too long // remove letters while text > label do { letters--; squeezedText = fullText.left(letters) + "..." + fullText.right(letters); squeezedWidth = fm.width(squeezedText); } while (squeezedWidth > labelWidth); } if (letters < 5) { // too few letters added -> we give up squeezing QLineEdit::setText(fullText); } else { QLineEdit::setText(squeezedText); d->squeezedStart = letters; d->squeezedEnd = fullText.length() - letters; } setToolTip( fullText ); } else { QLineEdit::setText(fullText); this->setToolTip( "" ); QToolTip::showText(pos(), QString()); // hide } setCursorPosition(0); } void KLineEdit::copy() const { if( !copySqueezedText(true)) QLineEdit::copy(); } bool KLineEdit::copySqueezedText(bool clipboard) const { if (!d->squeezedText.isEmpty() && d->squeezedStart) { KLineEdit *that = const_cast(this); if (!that->hasSelectedText()) return false; int start = selectionStart(), end = start + selectedText().length(); if (start >= d->squeezedStart+3) start = start - 3 - d->squeezedStart + d->squeezedEnd; else if (start > d->squeezedStart) start = d->squeezedStart; if (end >= d->squeezedStart+3) end = end - 3 - d->squeezedStart + d->squeezedEnd; else if (end > d->squeezedStart) end = d->squeezedEnd; if (start == end) return false; QString t = d->squeezedText; t = t.mid(start, end - start); disconnect( QApplication::clipboard(), SIGNAL(selectionChanged()), this, 0); QApplication::clipboard()->setText( t, clipboard ? QClipboard::Clipboard : QClipboard::Selection ); connect( QApplication::clipboard(), SIGNAL(selectionChanged()), this, SLOT(_q_clipboardChanged()) ); return true; } return false; } void KLineEdit::resizeEvent( QResizeEvent * ev ) { if (!d->squeezedText.isEmpty()) setSqueezedText(); updateClearButton(); QLineEdit::resizeEvent(ev); } void KLineEdit::keyPressEvent( QKeyEvent *e ) { const int key = e->key() | e->modifiers(); if ( KStandardShortcut::copy().contains( key ) ) { copy(); return; } else if ( KStandardShortcut::paste().contains( key ) ) { // TODO: // we should restore the original text (not autocompleted), otherwise the paste // will get into troubles Bug: 134691 if( !isReadOnly() ) paste(); return; } else if ( KStandardShortcut::pasteSelection().contains( key ) ) { QString text = QApplication::clipboard()->text( QClipboard::Selection); insert( text ); deselect(); return; } else if ( KStandardShortcut::cut().contains( key ) ) { if( !isReadOnly() ) cut(); return; } else if ( KStandardShortcut::undo().contains( key ) ) { if( !isReadOnly() ) undo(); return; } else if ( KStandardShortcut::redo().contains( key ) ) { if( !isReadOnly() ) redo(); return; } else if ( KStandardShortcut::deleteWordBack().contains( key ) ) { cursorWordBackward(true); if ( hasSelectedText() ) del(); e->accept(); return; } else if ( KStandardShortcut::deleteWordForward().contains( key ) ) { // Workaround for QT bug where cursorWordForward(true); if ( hasSelectedText() ) del(); e->accept(); return; } else if ( KStandardShortcut::backwardWord().contains( key ) ) { cursorWordBackward(false); e->accept(); return; } else if ( KStandardShortcut::forwardWord().contains( key ) ) { cursorWordForward(false); e->accept(); return; } else if ( KStandardShortcut::beginningOfLine().contains( key ) ) { home(false); e->accept(); return; } else if ( KStandardShortcut::endOfLine().contains( key ) ) { end(false); e->accept(); return; } // Filter key-events if EchoMode is normal and // completion mode is not set to CompletionNone if ( echoMode() == QLineEdit::Normal && completionMode() != KGlobalSettings::CompletionNone ) { if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) { const bool trap = (d->completionBox && d->completionBox->isVisible()); const bool stopEvent = (trap || (d->grabReturnKeyEvents && (e->modifiers() == Qt::NoButton || e->modifiers() == Qt::KeypadModifier))); if (stopEvent) { emit QLineEdit::returnPressed(); e->accept(); } emit returnPressed( displayText() ); if (trap) { d->completionBox->hide(); deselect(); setCursorPosition(text().length()); } // Eat the event if the user asked for it, or if a completionbox was visible if (stopEvent) { return; } } const KeyBindingMap keys = getKeyBindings(); const KGlobalSettings::Completion mode = completionMode(); const bool noModifier = (e->modifiers() == Qt::NoButton || e->modifiers() == Qt::ShiftModifier || e->modifiers() == Qt::KeypadModifier); if ( (mode == KGlobalSettings::CompletionAuto || mode == KGlobalSettings::CompletionPopupAuto || mode == KGlobalSettings::CompletionMan) && noModifier ) { if ( !d->userSelection && hasSelectedText() && ( e->key() == Qt::Key_Right || e->key() == Qt::Key_Left ) && e->modifiers()==Qt::NoButton ) { const QString old_txt = text(); d->disableRestoreSelection = true; const int start = selectionStart(); deselect(); QLineEdit::keyPressEvent ( e ); const int cPosition=cursorPosition(); setText(old_txt); // keep cursor at cPosition setSelection(old_txt.length(), cPosition - old_txt.length()); if (e->key() == Qt::Key_Right && cPosition > start ) { //the user explicitly accepted the autocompletion d->_k_updateUserText(text()); } d->disableRestoreSelection = false; return; } if ( e->key() == Qt::Key_Escape ) { if (hasSelectedText() && !d->userSelection ) { del(); setUserSelection(true); } // Don't swallow the Escape press event for the case // of dialogs, which have Escape associated to Cancel e->ignore(); return; } } if ( (mode == KGlobalSettings::CompletionAuto || mode == KGlobalSettings::CompletionMan) && noModifier ) { const QString keycode = e->text(); if ( !keycode.isEmpty() && (keycode.unicode()->isPrint() || e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Delete ) ) { const bool hasUserSelection=d->userSelection; const bool hadSelection=hasSelectedText(); bool cursorNotAtEnd=false; const int start = selectionStart(); const int cPos = cursorPosition(); // When moving the cursor, we want to keep the autocompletion as an // autocompletion, so we want to process events at the cursor position // as if there was no selection. After processing the key event, we // can set the new autocompletion again. if ( hadSelection && !hasUserSelection && start>cPos ) { del(); setCursorPosition(cPos); cursorNotAtEnd=true; } d->disableRestoreSelection = true; QLineEdit::keyPressEvent ( e ); d->disableRestoreSelection = false; QString txt = text(); int len = txt.length(); if ( !hasSelectedText() && len /*&& cursorPosition() == len */) { if ( e->key() == Qt::Key_Backspace ) { if ( hadSelection && !hasUserSelection && !cursorNotAtEnd ) { backspace(); txt = text(); len = txt.length(); } if (!d->s_backspacePerformsCompletion || !len) { d->autoSuggest = false; } } if (e->key() == Qt::Key_Delete ) d->autoSuggest=false; doCompletion(txt); if( (e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Delete) ) d->autoSuggest=true; e->accept(); } return; } } else if (( mode == KGlobalSettings::CompletionPopup || mode == KGlobalSettings::CompletionPopupAuto ) && noModifier && !e->text().isEmpty() ) { const QString old_txt = text(); const bool hasUserSelection=d->userSelection; const bool hadSelection=hasSelectedText(); bool cursorNotAtEnd=false; const int start = selectionStart(); const int cPos = cursorPosition(); const QString keycode = e->text(); // When moving the cursor, we want to keep the autocompletion as an // autocompletion, so we want to process events at the cursor position // as if there was no selection. After processing the key event, we // can set the new autocompletion again. if (hadSelection && !hasUserSelection && start>cPos && ( (!keycode.isEmpty() && keycode.unicode()->isPrint()) || e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Delete ) ) { del(); setCursorPosition(cPos); cursorNotAtEnd=true; } const int selectedLength=selectedText().length(); d->disableRestoreSelection = true; QLineEdit::keyPressEvent ( e ); d->disableRestoreSelection = false; if (( selectedLength != selectedText().length() ) && !hasUserSelection ) slotRestoreSelectionColors(); // and set userSelection to true QString txt = text(); int len = txt.length(); if ( ( txt != old_txt || txt != e->text() ) && len/* && ( cursorPosition() == len || force )*/ && ( (!keycode.isEmpty() && keycode.unicode()->isPrint()) || e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Delete) ) { if ( e->key() == Qt::Key_Backspace ) { if ( hadSelection && !hasUserSelection && !cursorNotAtEnd ) { backspace(); txt = text(); len = txt.length(); } if (!d->s_backspacePerformsCompletion) { d->autoSuggest = false; } } if (e->key() == Qt::Key_Delete ) d->autoSuggest=false; if ( d->completionBox ) d->completionBox->setCancelledText( txt ); doCompletion(txt); if ( (e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Delete ) && mode == KGlobalSettings::CompletionPopupAuto ) d->autoSuggest=true; e->accept(); } else if (!len && d->completionBox && d->completionBox->isVisible()) d->completionBox->hide(); return; } else if ( mode == KGlobalSettings::CompletionShell ) { // Handles completion. KShortcut cut; if ( keys[TextCompletion].isEmpty() ) cut = KStandardShortcut::shortcut(KStandardShortcut::TextCompletion); else cut = keys[TextCompletion]; if ( cut.contains( key ) ) { // Emit completion if the completion mode is CompletionShell // and the cursor is at the end of the string. const QString txt = text(); const int len = txt.length(); if ( cursorPosition() == len && len != 0 ) { doCompletion(txt); return; } } else if ( d->completionBox ) d->completionBox->hide(); } // handle rotation if ( mode != KGlobalSettings::CompletionNone ) { // Handles previous match KShortcut cut; if ( keys[PrevCompletionMatch].isEmpty() ) cut = KStandardShortcut::shortcut(KStandardShortcut::PrevCompletion); else cut = keys[PrevCompletionMatch]; if ( cut.contains( key ) ) { if ( emitSignals() ) emit textRotation( KCompletionBase::PrevCompletionMatch ); if ( handleSignals() ) rotateText( KCompletionBase::PrevCompletionMatch ); return; } // Handles next match if ( keys[NextCompletionMatch].isEmpty() ) cut = KStandardShortcut::shortcut(KStandardShortcut::NextCompletion); else cut = keys[NextCompletionMatch]; if ( cut.contains( key ) ) { if ( emitSignals() ) emit textRotation( KCompletionBase::NextCompletionMatch ); if ( handleSignals() ) rotateText( KCompletionBase::NextCompletionMatch ); return; } } // substring completion if ( compObj() ) { KShortcut cut; if ( keys[SubstringCompletion].isEmpty() ) cut = KStandardShortcut::shortcut(KStandardShortcut::SubstringCompletion); else cut = keys[SubstringCompletion]; if ( cut.contains( key ) ) { if ( emitSignals() ) emit substringCompletion( text() ); if ( handleSignals() ) { setCompletedItems( compObj()->substringCompletion(text())); e->accept(); } return; } } } const int selectedLength = selectedText().length(); // Let QLineEdit handle any other keys events. QLineEdit::keyPressEvent ( e ); if ( selectedLength != selectedText().length() ) slotRestoreSelectionColors(); // and set userSelection to true } void KLineEdit::mouseDoubleClickEvent( QMouseEvent* e ) { if ( e->button() == Qt::LeftButton ) { d->possibleTripleClick=true; QTimer::singleShot( QApplication::doubleClickInterval(),this, SLOT(tripleClickTimeout()) ); } QLineEdit::mouseDoubleClickEvent( e ); } void KLineEdit::mousePressEvent( QMouseEvent* e ) { if ( (e->button() == Qt::LeftButton || e->button() == Qt::MidButton ) && d->clearButton ) { d->clickInClear = ( d->clearButton == childAt(e->pos()) || d->clearButton->underMouse() ); if ( d->clickInClear ) { d->possibleTripleClick = false; } } if ( e->button() == Qt::LeftButton && d->possibleTripleClick ) { selectAll(); e->accept(); return; } // if middle clicking and if text is present in the clipboard then clear the selection // to prepare paste operation if ( e->button() == Qt::MidButton ) { if ( hasSelectedText() ) { if ( QApplication::clipboard()->text( QClipboard::Selection ).length() >0 ) { backspace(); } } } QLineEdit::mousePressEvent( e ); } void KLineEdit::mouseReleaseEvent( QMouseEvent* e ) { if ( d->clickInClear ) { if ( d->clearButton == childAt(e->pos()) || d->clearButton->underMouse() ) { QString newText; if ( e->button() == Qt::MidButton ) { newText = QApplication::clipboard()->text( QClipboard::Selection ); setText( newText ); } else { setSelection(0, text().size()); del(); emit clearButtonClicked(); } emit textChanged( newText ); } d->clickInClear = false; e->accept(); return; } QLineEdit::mouseReleaseEvent( e ); if (QApplication::clipboard()->supportsSelection() ) { if ( e->button() == Qt::LeftButton ) { // Fix copying of squeezed text if needed copySqueezedText( false ); } } } void KLineEdit::tripleClickTimeout() { d->possibleTripleClick=false; } QMenu* KLineEdit::createStandardContextMenu() { QMenu *popup = QLineEdit::createStandardContextMenu(); if( !isReadOnly() ) { // FIXME: This code depends on Qt's action ordering. const QList actionList = popup->actions(); enum { UndoAct, RedoAct, Separator1, CutAct, CopyAct, PasteAct, DeleteAct, ClearAct, Separator2, SelectAllAct, NCountActs }; QAction *separatorAction = 0L; // separator we want is right after Delete right now. const int idx = actionList.indexOf( actionList[DeleteAct] ) + 1; if ( idx < actionList.count() ) separatorAction = actionList.at( idx ); if ( separatorAction ) { KAction *clearAllAction = KStandardAction::clear( this, SLOT( clear() ), this) ; if ( text().isEmpty() ) clearAllAction->setEnabled( false ); popup->insertAction( separatorAction, clearAllAction ); } } KIconTheme::assignIconsToContextMenu( KIconTheme::TextEditor, popup->actions () ); // If a completion object is present and the input // widget is not read-only, show the Text Completion // menu item. if ( compObj() && !isReadOnly() && KAuthorized::authorize("lineedit_text_completion") ) { QMenu *subMenu = popup->addMenu( KIcon("text-completion"), i18nc("@title:menu", "Text Completion") ); connect( subMenu, SIGNAL( triggered ( QAction* ) ), this, SLOT( completionMenuActivated( QAction* ) ) ); popup->addSeparator(); QActionGroup* ag = new QActionGroup( this ); d->noCompletionAction = ag->addAction( i18nc("@item:inmenu Text Completion", "None")); d->shellCompletionAction = ag->addAction( i18nc("@item:inmenu Text Completion", "Manual") ); d->autoCompletionAction = ag->addAction( i18nc("@item:inmenu Text Completion", "Automatic") ); d->popupCompletionAction = ag->addAction( i18nc("@item:inmenu Text Completion", "Dropdown List") ); d->shortAutoCompletionAction = ag->addAction( i18nc("@item:inmenu Text Completion", "Short Automatic") ); d->popupAutoCompletionAction = ag->addAction( i18nc("@item:inmenu Text Completion", "Dropdown List && Automatic")); subMenu->addActions( ag->actions() ); //subMenu->setAccel( KStandardShortcut::completion(), ShellCompletion ); d->shellCompletionAction->setCheckable( true ); d->noCompletionAction->setCheckable( true ); d->popupCompletionAction->setCheckable( true ); d->autoCompletionAction->setCheckable( true ); d->shortAutoCompletionAction->setCheckable( true ); d->popupAutoCompletionAction->setCheckable( true ); d->shellCompletionAction->setEnabled( !d->disableCompletionMap[ KGlobalSettings::CompletionShell ] ); d->noCompletionAction->setEnabled( !d->disableCompletionMap[ KGlobalSettings::CompletionNone ] ); d->popupCompletionAction->setEnabled( !d->disableCompletionMap[ KGlobalSettings::CompletionPopup ] ); d->autoCompletionAction->setEnabled( !d->disableCompletionMap[ KGlobalSettings::CompletionAuto ] ); d->shortAutoCompletionAction->setEnabled( !d->disableCompletionMap[ KGlobalSettings::CompletionMan ] ); d->popupAutoCompletionAction->setEnabled( !d->disableCompletionMap[ KGlobalSettings::CompletionPopupAuto ] ); const KGlobalSettings::Completion mode = completionMode(); d->noCompletionAction->setChecked( mode == KGlobalSettings::CompletionNone ); d->shellCompletionAction->setChecked( mode == KGlobalSettings::CompletionShell ); d->popupCompletionAction->setChecked( mode == KGlobalSettings::CompletionPopup ); d->autoCompletionAction->setChecked( mode == KGlobalSettings::CompletionAuto ); d->shortAutoCompletionAction->setChecked( mode == KGlobalSettings::CompletionMan ); d->popupAutoCompletionAction->setChecked( mode == KGlobalSettings::CompletionPopupAuto ); const KGlobalSettings::Completion defaultMode = KGlobalSettings::completionMode(); if ( mode != defaultMode && !d->disableCompletionMap[ defaultMode ] ) { subMenu->addSeparator(); d->defaultAction = subMenu->addAction( i18nc("@item:inmenu Text Completion", "Default") ); } } return popup; } void KLineEdit::contextMenuEvent( QContextMenuEvent *e ) { if ( QLineEdit::contextMenuPolicy() != Qt::DefaultContextMenu ) return; QMenu *popup = createStandardContextMenu(); // ### do we really need this? Yes, Please do not remove! This // allows applications to extend the popup menu without having to // inherit from this class! (DA) emit aboutToShowContextMenu( popup ); popup->exec(e->globalPos()); delete popup; } void KLineEdit::completionMenuActivated( QAction *act) { KGlobalSettings::Completion oldMode = completionMode(); if( act == d->noCompletionAction ) { setCompletionMode( KGlobalSettings::CompletionNone ); } else if( act == d->shellCompletionAction) { setCompletionMode( KGlobalSettings::CompletionShell ); } else if( act == d->autoCompletionAction) { setCompletionMode( KGlobalSettings::CompletionAuto ); } else if( act == d->popupCompletionAction) { setCompletionMode( KGlobalSettings::CompletionPopup ); } else if( act == d->shortAutoCompletionAction) { setCompletionMode( KGlobalSettings::CompletionMan ); } else if( act == d->popupAutoCompletionAction) { setCompletionMode( KGlobalSettings::CompletionPopupAuto ); } else if( act == d->defaultAction ) { setCompletionMode( KGlobalSettings::completionMode() ); } else return; if ( oldMode != completionMode() ) { if ( (oldMode == KGlobalSettings::CompletionPopup || oldMode == KGlobalSettings::CompletionPopupAuto ) && d->completionBox && d->completionBox->isVisible() ) d->completionBox->hide(); emit completionModeChanged( completionMode() ); } } void KLineEdit::dropEvent(QDropEvent *e) { if( d->handleURLDrops ) { - const KUrl::List urlList = KUrl::List::fromMimeData( e->mimeData() ); + const QList urlList = KUrlMimeData::urlsFromMimeData(e->mimeData()); if ( !urlList.isEmpty() ) { // Let's replace the current text with the dropped URL(s), rather than appending. // Makes more sense in general (#188129), e.g. konq location bar and kurlrequester // can only hold one url anyway. OK this code supports multiple urls being dropped, // but that's not the common case [and it breaks if they contain spaces... this is why // kfiledialog uses double quotes around filenames in multiple-selection mode]... // // Anyway, if some apps prefer "append" then we should have a // setUrlDropsSupport( {NoUrlDrops, SingleUrlDrops, MultipleUrlDrops} ) // where Single replaces and Multiple appends. QString dropText; //QString dropText = text(); - KUrl::List::ConstIterator it; + QList::ConstIterator it; for( it = urlList.begin() ; it != urlList.end() ; ++it ) { if(!dropText.isEmpty()) dropText+=' '; - dropText += (*it).prettyUrl(); + dropText += (*it).toString(); // Qt5 TODO toDisplayString() } setText(dropText); setCursorPosition(dropText.length()); e->accept(); return; } } QLineEdit::dropEvent(e); } bool KLineEdit::event( QEvent* ev ) { KCursor::autoHideEventFilter( this, ev ); if ( ev->type() == QEvent::ShortcutOverride ) { QKeyEvent *e = static_cast( ev ); if (d->overrideShortcut(e)) { ev->accept(); } } else if (ev->type() == QEvent::ApplicationPaletteChange || ev->type() == QEvent::PaletteChange) { // Assume the widget uses the application's palette QPalette p = QApplication::palette(); d->previousHighlightedTextColor=p.color(QPalette::Normal,QPalette::HighlightedText); d->previousHighlightColor=p.color(QPalette::Normal,QPalette::Highlight); setUserSelection(d->userSelection); } else if (ev->type() == QEvent::StyleChange) { // since we have our own style and it relies on this style to Get Things Right, // if a style is set specifically on the widget (which would replace our own style!) // hang on to this special style and re-instate our own style. //FIXME: Qt currently has a grave bug where already deleted QStyleSheetStyle objects // will get passed back in if we set a new style on it here. remove the qstrmcp test // when this is fixed in Qt (or a better approach is found) if (!qobject_cast(style()) && qstrcmp(style()->metaObject()->className(), "QStyleSheetStyle") != 0 && QLatin1String(style()->metaObject()->className()) != d->lastStyleClass) { KLineEditStyle *kleStyle = d->style.data(); if (!kleStyle) { d->style = kleStyle = new KLineEditStyle(this); } kleStyle->m_subStyle = style(); // this guards against "wrap around" where another style, e.g. QStyleSheetStyle, // is setting the style on QEvent::StyleChange d->lastStyleClass = QLatin1String(style()->metaObject()->className()); setStyle(kleStyle); d->lastStyleClass.clear(); } } return QLineEdit::event( ev ); } void KLineEdit::setUrlDropsEnabled(bool enable) { d->handleURLDrops=enable; } bool KLineEdit::urlDropsEnabled() const { return d->handleURLDrops; } void KLineEdit::setTrapReturnKey( bool grab ) { d->grabReturnKeyEvents = grab; } bool KLineEdit::trapReturnKey() const { return d->grabReturnKeyEvents; } void KLineEdit::setUrl( const KUrl& url ) { setText( url.prettyUrl() ); } void KLineEdit::setCompletionBox( KCompletionBox *box ) { if ( d->completionBox ) return; d->completionBox = box; if ( handleSignals() ) { connect( d->completionBox, SIGNAL(currentTextChanged( const QString& )), SLOT(_k_slotCompletionBoxTextChanged( const QString& )) ); connect( d->completionBox, SIGNAL(userCancelled( const QString& )), SLOT(userCancelled( const QString& )) ); connect( d->completionBox, SIGNAL(activated(QString)), SIGNAL(completionBoxActivated(QString)) ); connect( d->completionBox, SIGNAL(activated(QString)), SIGNAL(textEdited(QString)) ); } } /* * Set the line edit text without changing the modified flag. By default * calling setText resets the modified flag to false. */ static void setEditText(KLineEdit* edit, const QString& text) { if (!edit) { return; } const bool wasModified = edit->isModified(); edit->setText(text); edit->setModified(wasModified); } void KLineEdit::userCancelled(const QString & cancelText) { if ( completionMode() != KGlobalSettings::CompletionPopupAuto ) { setEditText(this, cancelText); } else if (hasSelectedText() ) { if (d->userSelection) deselect(); else { d->autoSuggest=false; const int start = selectionStart() ; const QString s = text().remove(selectionStart(), selectedText().length()); setEditText(this, s); setCursorPosition(start); d->autoSuggest=true; } } } bool KLineEditPrivate::overrideShortcut(const QKeyEvent* e) { KShortcut scKey; const int key = e->key() | e->modifiers(); const KLineEdit::KeyBindingMap keys = q->getKeyBindings(); if (keys[KLineEdit::TextCompletion].isEmpty()) scKey = KStandardShortcut::shortcut(KStandardShortcut::TextCompletion); else scKey = keys[KLineEdit::TextCompletion]; if (scKey.contains( key )) return true; if (keys[KLineEdit::NextCompletionMatch].isEmpty()) scKey = KStandardShortcut::shortcut(KStandardShortcut::NextCompletion); else scKey = keys[KLineEdit::NextCompletionMatch]; if (scKey.contains( key )) return true; if (keys[KLineEdit::PrevCompletionMatch].isEmpty()) scKey = KStandardShortcut::shortcut(KStandardShortcut::PrevCompletion); else scKey = keys[KLineEdit::PrevCompletionMatch]; if (scKey.contains( key )) return true; // Override all the text manupilation accelerators... if ( KStandardShortcut::copy().contains( key ) ) return true; else if ( KStandardShortcut::paste().contains( key ) ) return true; else if ( KStandardShortcut::cut().contains( key ) ) return true; else if ( KStandardShortcut::undo().contains( key ) ) return true; else if ( KStandardShortcut::redo().contains( key ) ) return true; else if (KStandardShortcut::deleteWordBack().contains( key )) return true; else if (KStandardShortcut::deleteWordForward().contains( key )) return true; else if (KStandardShortcut::forwardWord().contains( key )) return true; else if (KStandardShortcut::backwardWord().contains( key )) return true; else if (KStandardShortcut::beginningOfLine().contains( key )) return true; else if (KStandardShortcut::endOfLine().contains( key )) return true; // Shortcut overrides for shortcuts that QLineEdit handles // but doesn't dare force as "stronger than kaction shortcuts"... else if (e->matches(QKeySequence::SelectAll)) { return true; } #ifdef Q_WS_X11 else if (key == Qt::CTRL + Qt::Key_E || key == Qt::CTRL + Qt::Key_U) return true; #endif if (completionBox && completionBox->isVisible ()) { const int key = e->key(); const Qt::KeyboardModifiers modifiers = e->modifiers(); if ((key == Qt::Key_Backtab || key == Qt::Key_Tab) && (modifiers == Qt::NoModifier || (modifiers & Qt::ShiftModifier))) { return true; } } return false; } void KLineEdit::setCompletedItems( const QStringList& items, bool autoSuggest ) { QString txt; if ( d->completionBox && d->completionBox->isVisible() ) { // The popup is visible already - do the matching on the initial string, // not on the currently selected one. txt = completionBox()->cancelledText(); } else { txt = text(); } if ( !items.isEmpty() && !(items.count() == 1 && txt == items.first()) ) { // create completion box if non-existent completionBox(); if ( d->completionBox->isVisible() ) { QListWidgetItem* currentItem = d->completionBox->currentItem(); QString currentSelection; if ( currentItem != 0 ) { currentSelection = currentItem->text(); } d->completionBox->setItems( items ); const QList matchedItems = d->completionBox->findItems(currentSelection, Qt::MatchExactly); QListWidgetItem* matchedItem = matchedItems.isEmpty() ? 0 : matchedItems.first(); if (matchedItem) { const bool blocked = d->completionBox->blockSignals( true ); d->completionBox->setCurrentItem( matchedItem ); d->completionBox->blockSignals( blocked ); } else { d->completionBox->setCurrentRow(-1); } } else // completion box not visible yet -> show it { if ( !txt.isEmpty() ) d->completionBox->setCancelledText( txt ); d->completionBox->setItems( items ); d->completionBox->popup(); } if ( d->autoSuggest && autoSuggest ) { const int index = items.first().indexOf( txt ); const QString newText = items.first().mid( index ); setUserSelection(false); // can be removed? setCompletedText sets it anyway setCompletedText(newText,true); } } else { if ( d->completionBox && d->completionBox->isVisible() ) d->completionBox->hide(); } } KCompletionBox * KLineEdit::completionBox( bool create ) { if ( create && !d->completionBox ) { setCompletionBox( new KCompletionBox( this ) ); d->completionBox->setObjectName("completion box"); d->completionBox->setFont(font()); } return d->completionBox; } void KLineEdit::setCompletionObject( KCompletion* comp, bool hsig ) { KCompletion *oldComp = compObj(); if ( oldComp && handleSignals() ) disconnect( oldComp, SIGNAL( matches( const QStringList& )), this, SLOT( setCompletedItems( const QStringList& ))); if ( comp && hsig ) connect( comp, SIGNAL( matches( const QStringList& )), this, SLOT( setCompletedItems( const QStringList& ))); KCompletionBase::setCompletionObject( comp, hsig ); } // QWidget::create() turns off mouse-Tracking which would break auto-hiding void KLineEdit::create( WId id, bool initializeWindow, bool destroyOldWindow ) { QLineEdit::create( id, initializeWindow, destroyOldWindow ); KCursor::setAutoHideCursor( this, true, true ); } void KLineEdit::setUserSelection(bool userSelection) { //if !d->userSelection && userSelection we are accepting a completion, //so trigger an update if (!d->userSelection && userSelection) { d->_k_updateUserText(text()); } QPalette p = palette(); if (userSelection) { p.setColor(QPalette::Highlight, d->previousHighlightColor); p.setColor(QPalette::HighlightedText, d->previousHighlightedTextColor); } else { QColor color=p.color(QPalette::Disabled, QPalette::Text); p.setColor(QPalette::HighlightedText, color); color=p.color(QPalette::Active, QPalette::Base); p.setColor(QPalette::Highlight, color); } d->userSelection=userSelection; setPalette(p); } void KLineEdit::slotRestoreSelectionColors() { if (d->disableRestoreSelection) return; setUserSelection(true); } void KLineEdit::clear() { setText( QString() ); } void KLineEdit::_k_slotCompletionBoxTextChanged( const QString& text ) { if (!text.isEmpty()) { setText( text ); setModified(true); end( false ); // force cursor at end } } QString KLineEdit::originalText() const { if ( d->enableSqueezedText && isReadOnly() ) return d->squeezedText; return text(); } QString KLineEdit::userText() const { return d->userText; } bool KLineEdit::autoSuggest() const { return d->autoSuggest; } void KLineEdit::paintEvent( QPaintEvent *ev ) { if (echoMode() == Password && d->threeStars) { // ### hack alert! // QLineEdit has currently no hooks to modify the displayed string. // When we call setText(), an update() is triggered and we get // into an infinite recursion. // Qt offers the setUpdatesEnabled() method, but when we re-enable // them, update() is triggered, and we get into the same recursion. // To work around this problem, we set/clear the internal Qt flag which // marks the updatesDisabled state manually. setAttribute(Qt::WA_UpdatesDisabled, true); blockSignals(true); const QString oldText = text(); const bool isModifiedState = isModified(); // save modified state because setText resets it setText(oldText + oldText + oldText); QLineEdit::paintEvent(ev); setText(oldText); setModified(isModifiedState); blockSignals(false); setAttribute(Qt::WA_UpdatesDisabled, false); } else { QLineEdit::paintEvent( ev ); } if (d->enableClickMsg && d->drawClickMsg && !hasFocus() && text().isEmpty()) { QPainter p(this); QFont f = font(); f.setItalic(d->italicizePlaceholder); p.setFont(f); QColor color(palette().color(foregroundRole())); color.setAlphaF(0.5); p.setPen(color); QStyleOptionFrame opt; initStyleOption(&opt); QRect cr = style()->subElementRect(QStyle::SE_LineEditContents, &opt, this); // this is copied/adapted from QLineEdit::paintEvent const int verticalMargin(1); const int horizontalMargin(2); int left, top, right, bottom; getTextMargins( &left, &top, &right, &bottom ); cr.adjust( left, top, -right, -bottom ); p.setClipRect(cr); QFontMetrics fm = fontMetrics(); Qt::Alignment va = alignment() & Qt::AlignVertical_Mask; int vscroll; switch (va & Qt::AlignVertical_Mask) { case Qt::AlignBottom: vscroll = cr.y() + cr.height() - fm.height() - verticalMargin; break; case Qt::AlignTop: vscroll = cr.y() + verticalMargin; break; default: vscroll = cr.y() + (cr.height() - fm.height() + 1) / 2; break; } QRect lineRect(cr.x() + horizontalMargin, vscroll, cr.width() - 2*horizontalMargin, fm.height()); p.drawText(lineRect, Qt::AlignLeft|Qt::AlignVCenter, d->clickMessage); } } void KLineEdit::focusInEvent( QFocusEvent *ev ) { if ( d->enableClickMsg && d->drawClickMsg ) { d->drawClickMsg = false; update(); } QLineEdit::focusInEvent( ev ); } void KLineEdit::focusOutEvent( QFocusEvent *ev ) { if ( d->enableClickMsg && text().isEmpty() ) { d->drawClickMsg = true; update(); } QLineEdit::focusOutEvent( ev ); } void KLineEdit::setClickMessage( const QString &msg ) { d->enableClickMsg = !msg.isEmpty(); d->clickMessage = msg; d->drawClickMsg = text().isEmpty(); update(); } #ifndef KDE_NO_DEPRECATED void KLineEdit::setContextMenuEnabled( bool showMenu ) { QLineEdit::setContextMenuPolicy( showMenu ? Qt::DefaultContextMenu : Qt::NoContextMenu ); } #endif #ifndef KDE_NO_DEPRECATED bool KLineEdit::isContextMenuEnabled() const { return ( contextMenuPolicy() == Qt::DefaultContextMenu ); } #endif void KLineEdit::setPasswordMode(bool b) { if(b) { KConfigGroup cg(KGlobal::config(), "Passwords"); const QString val = cg.readEntry("EchoMode", "OneStar"); if (val == "NoEcho") setEchoMode(NoEcho); else { d->threeStars = (val == "ThreeStars"); setEchoMode(Password); } } else { setEchoMode( Normal ); } } bool KLineEdit::passwordMode() const { return echoMode() == NoEcho || echoMode() == Password; } void KLineEdit::doCompletion(const QString& txt) { if (emitSignals()) { emit completion(txt); // emit when requested... } d->completionRunning = true; if (handleSignals()) { makeCompletion(txt); // handle when requested... } d->completionRunning = false; } #include "moc_klineedit.cpp" #include "moc_klineedit_p.cpp" diff --git a/kfile/kfileplacesmodel.cpp b/kfile/kfileplacesmodel.cpp index 3ec7e5fd31..fa372c26e6 100644 --- a/kfile/kfileplacesmodel.cpp +++ b/kfile/kfileplacesmodel.cpp @@ -1,878 +1,879 @@ /* This file is part of the KDE project Copyright (C) 2007 Kevin Ottens Copyright (C) 2007 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kfileplacesmodel.h" #include "kfileplacesitem_p.h" #include "kfileplacessharedbookmarks_p.h" #ifdef Q_OS_WIN #include "Windows.h" #include "WinBase.h" #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include class KFilePlacesModel::Private { public: Private(KFilePlacesModel *self) : q(self), bookmarkManager(0), sharedBookmarks(0) {} ~Private() { delete sharedBookmarks; qDeleteAll(items); } KFilePlacesModel *q; QList items; QSet availableDevices; QMap setupInProgress; Solid::Predicate predicate; KBookmarkManager *bookmarkManager; KFilePlacesSharedBookmarks * sharedBookmarks; void reloadAndSignal(); QList loadBookmarkList(); void _k_initDeviceList(); void _k_deviceAdded(const QString &udi); void _k_deviceRemoved(const QString &udi); void _k_itemChanged(const QString &udi); void _k_reloadBookmarks(); void _k_storageSetupDone(Solid::ErrorType error, QVariant errorData); void _k_storageTeardownDone(Solid::ErrorType error, QVariant errorData); }; KFilePlacesModel::KFilePlacesModel(QObject *parent) : QAbstractItemModel(parent), d(new Private(this)) { const QString file = KStandardDirs::locateLocal("data", "kfileplaces/bookmarks.xml"); d->bookmarkManager = KBookmarkManager::managerForFile(file, "kfilePlaces"); // Let's put some places in there if it's empty. We have a corner case here: // Given you have bookmarked some folders (which have been saved on // ~/.local/share/user-places.xbel (according to freedesktop bookmarks spec), and // deleted the home directory ~/.kde, the call managerForFile() will return the // bookmark manager for the fallback "kfilePlaces", making root.first().isNull() being // false (you have your own items bookmarked), resulting on only being added your own // bookmarks, and not the default ones too. So, we also check if kfileplaces/bookmarks.xml // file exists, and if it doesn't, we also add the default places. (ereslibre) KBookmarkGroup root = d->bookmarkManager->root(); if (root.first().isNull() || !QFile::exists(file)) { // NOTE: The context for these I18N_NOOP2 calls has to be "KFile System Bookmarks". // The real i18nc call is made later, with this context, so the two must match. // // createSystemBookmark actually does nothing with its third argument, // but we have to give it something so the I18N_NOOP2 calls stay here for now. // // (coles, 13th May 2009) KFilePlacesItem::createSystemBookmark(d->bookmarkManager, "Home", I18N_NOOP2("KFile System Bookmarks", "Home"), KUrl(KUser().homeDir()), "user-home"); KFilePlacesItem::createSystemBookmark(d->bookmarkManager, "Network", I18N_NOOP2("KFile System Bookmarks", "Network"), KUrl("remote:/"), "network-workgroup"); #ifdef Q_OS_WIN // adding drives foreach ( const QFileInfo& info, QDir::drives() ) { #ifndef _WIN32_WCE uint type = DRIVE_UNKNOWN; #endif QString driveIcon = "drive-harddisk"; #ifndef _WIN32_WCE QT_WA({ type = GetDriveTypeW((wchar_t *)info.absoluteFilePath().utf16()); }, { type = GetDriveTypeA(info.absoluteFilePath().toLocal8Bit()); }); // qDebug() << "drive " << info.absoluteFilePath() << " type: " << type; switch (type) { case DRIVE_REMOVABLE: driveIcon = "drive-removable-media"; break; case DRIVE_FIXED: driveIcon = "drive-harddisk"; break; case DRIVE_REMOTE: driveIcon = "network-server"; break; case DRIVE_CDROM: driveIcon = "drive-optical"; break; case DRIVE_RAMDISK: case DRIVE_UNKNOWN: case DRIVE_NO_ROOT_DIR: default: driveIcon = "drive-harddisk"; } #endif KFilePlacesItem::createSystemBookmark(d->bookmarkManager, info.absoluteFilePath(), info.absoluteFilePath(), KUrl(info.absoluteFilePath()), driveIcon); } #else KFilePlacesItem::createSystemBookmark(d->bookmarkManager, "Root", I18N_NOOP2("KFile System Bookmarks", "Root"), KUrl("/"), "folder-red"); #endif KFilePlacesItem::createSystemBookmark(d->bookmarkManager, "Trash", I18N_NOOP2("KFile System Bookmarks", "Trash"), KUrl("trash:/"), "user-trash"); // Force bookmarks to be saved. If on open/save dialog and the bookmarks are not saved, QFile::exists // will always return false, which opening/closing all the time the open/save dialog would case the // bookmarks to be added once each time, having lots of times each bookmark. This forces the defaults // to be saved on the bookmarks.xml file. Of course, the complete list of bookmarks (those that come from // user-places.xbel will be filled later). (ereslibre) d->bookmarkManager->saveAs(file); } // create after, so if we have own places, they are added afterwards, in case of equal priorities d->sharedBookmarks = new KFilePlacesSharedBookmarks(d->bookmarkManager); d->predicate = Solid::Predicate::fromString( "[[[[ StorageVolume.ignored == false AND [ StorageVolume.usage == 'FileSystem' OR StorageVolume.usage == 'Encrypted' ]]" " OR " "[ IS StorageAccess AND StorageDrive.driveType == 'Floppy' ]]" " OR " "OpticalDisc.availableContent & 'Audio' ]" " OR " "StorageAccess.ignored == false ]"); Q_ASSERT(d->predicate.isValid()); connect(d->bookmarkManager, SIGNAL(changed(const QString&, const QString&)), this, SLOT(_k_reloadBookmarks())); connect(d->bookmarkManager, SIGNAL(bookmarksChanged(const QString&)), this, SLOT(_k_reloadBookmarks())); d->_k_reloadBookmarks(); QTimer::singleShot(0, this, SLOT(_k_initDeviceList())); } KFilePlacesModel::~KFilePlacesModel() { delete d; } KUrl KFilePlacesModel::url(const QModelIndex &index) const { return KUrl(data(index, UrlRole).toUrl()); } bool KFilePlacesModel::setupNeeded(const QModelIndex &index) const { return data(index, SetupNeededRole).toBool(); } KIcon KFilePlacesModel::icon(const QModelIndex &index) const { return KIcon(data(index, Qt::DecorationRole).value()); } QString KFilePlacesModel::text(const QModelIndex &index) const { return data(index, Qt::DisplayRole).toString(); } bool KFilePlacesModel::isHidden(const QModelIndex &index) const { return data(index, HiddenRole).toBool(); } bool KFilePlacesModel::isDevice(const QModelIndex &index) const { if (!index.isValid()) return false; KFilePlacesItem *item = static_cast(index.internalPointer()); return item->isDevice(); } Solid::Device KFilePlacesModel::deviceForIndex(const QModelIndex &index) const { if (!index.isValid()) return Solid::Device(); KFilePlacesItem *item = static_cast(index.internalPointer()); if (item->isDevice()) { return item->device(); } else { return Solid::Device(); } } KBookmark KFilePlacesModel::bookmarkForIndex(const QModelIndex &index) const { if (!index.isValid()) return KBookmark(); KFilePlacesItem *item = static_cast(index.internalPointer()); if (!item->isDevice()) { return item->bookmark(); } else { return KBookmark(); } } QVariant KFilePlacesModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); KFilePlacesItem *item = static_cast(index.internalPointer()); return item->data(role); } QModelIndex KFilePlacesModel::index(int row, int column, const QModelIndex &parent) const { if (row<0 || column!=0 || row>=d->items.size()) return QModelIndex(); if (parent.isValid()) return QModelIndex(); return createIndex(row, column, d->items.at(row)); } QModelIndex KFilePlacesModel::parent(const QModelIndex &child) const { Q_UNUSED(child); return QModelIndex(); } int KFilePlacesModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) return 0; else return d->items.size(); } int KFilePlacesModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) // We only know 1 piece of information for a particular entry return 1; } QModelIndex KFilePlacesModel::closestItem(const KUrl &url) const { int foundRow = -1; int maxLength = 0; // Search the item which is equal to the URL or at least is a parent URL. // If there are more than one possible item URL candidates, choose the item // which covers the bigger range of the URL. for (int row = 0; rowitems.size(); ++row) { KFilePlacesItem *item = d->items[row]; KUrl itemUrl = KUrl(item->data(UrlRole).toUrl()); if (itemUrl.isParentOf(url)) { const int length = itemUrl.prettyUrl().length(); if (length > maxLength) { foundRow = row; maxLength = length; } } } if (foundRow==-1) return QModelIndex(); else return createIndex(foundRow, 0, d->items[foundRow]); } void KFilePlacesModel::Private::_k_initDeviceList() { Solid::DeviceNotifier *notifier = Solid::DeviceNotifier::instance(); connect(notifier, SIGNAL(deviceAdded(const QString&)), q, SLOT(_k_deviceAdded(const QString&))); connect(notifier, SIGNAL(deviceRemoved(const QString&)), q, SLOT(_k_deviceRemoved(const QString&))); const QList &deviceList = Solid::Device::listFromQuery(predicate); foreach(const Solid::Device &device, deviceList) { availableDevices << device.udi(); } _k_reloadBookmarks(); } void KFilePlacesModel::Private::_k_deviceAdded(const QString &udi) { Solid::Device d(udi); if (predicate.matches(d)) { availableDevices << udi; _k_reloadBookmarks(); } } void KFilePlacesModel::Private::_k_deviceRemoved(const QString &udi) { if (availableDevices.contains(udi)) { availableDevices.remove(udi); _k_reloadBookmarks(); } } void KFilePlacesModel::Private::_k_itemChanged(const QString &id) { for (int row = 0; rowid()==id) { QModelIndex index = q->index(row, 0); emit q->dataChanged(index, index); } } } void KFilePlacesModel::Private::_k_reloadBookmarks() { QList currentItems = loadBookmarkList(); QList::Iterator it_i = items.begin(); QList::Iterator it_c = currentItems.begin(); QList::Iterator end_i = items.end(); QList::Iterator end_c = currentItems.end(); while (it_i!=end_i || it_c!=end_c) { if (it_i==end_i && it_c!=end_c) { int row = items.count(); q->beginInsertRows(QModelIndex(), row, row); it_i = items.insert(it_i, *it_c); ++it_i; it_c = currentItems.erase(it_c); end_i = items.end(); end_c = currentItems.end(); q->endInsertRows(); } else if (it_i!=end_i && it_c==end_c) { int row = items.indexOf(*it_i); q->beginRemoveRows(QModelIndex(), row, row); delete *it_i; it_i = items.erase(it_i); end_i = items.end(); end_c = currentItems.end(); q->endRemoveRows(); } else if ((*it_i)->id()==(*it_c)->id()) { bool shouldEmit = !((*it_i)->bookmark()==(*it_c)->bookmark()); (*it_i)->setBookmark((*it_c)->bookmark()); if (shouldEmit) { int row = items.indexOf(*it_i); QModelIndex idx = q->index(row, 0); emit q->dataChanged(idx, idx); } ++it_i; ++it_c; } else if ((*it_i)->id()!=(*it_c)->id()) { int row = items.indexOf(*it_i); if (it_i+1!=end_i && (*(it_i+1))->id()==(*it_c)->id()) { // if the next one matches, it's a remove q->beginRemoveRows(QModelIndex(), row, row); delete *it_i; it_i = items.erase(it_i); end_i = items.end(); end_c = currentItems.end(); q->endRemoveRows(); } else { q->beginInsertRows(QModelIndex(), row, row); it_i = items.insert(it_i, *it_c); ++it_i; it_c = currentItems.erase(it_c); end_i = items.end(); end_c = currentItems.end(); q->endInsertRows(); } } } qDeleteAll(currentItems); currentItems.clear(); } QList KFilePlacesModel::Private::loadBookmarkList() { QList items; KBookmarkGroup root = bookmarkManager->root(); KBookmark bookmark = root.first(); QSet devices = availableDevices; while (!bookmark.isNull()) { QString udi = bookmark.metaDataItem("UDI"); QString appName = bookmark.metaDataItem("OnlyInApp"); bool deviceAvailable = devices.remove(udi); bool allowedHere = appName.isEmpty() || (appName==KGlobal::mainComponent().componentName()); if ((udi.isEmpty() && allowedHere) || deviceAvailable) { KFilePlacesItem *item; if (deviceAvailable) { item = new KFilePlacesItem(bookmarkManager, bookmark.address(), udi); // TODO: Update bookmark internal element } else { item = new KFilePlacesItem(bookmarkManager, bookmark.address()); } connect(item, SIGNAL(itemChanged(const QString&)), q, SLOT(_k_itemChanged(const QString&))); items << item; } bookmark = root.next(bookmark); } // Add bookmarks for the remaining devices, they were previously unknown foreach (const QString &udi, devices) { bookmark = KFilePlacesItem::createDeviceBookmark(bookmarkManager, udi); if (!bookmark.isNull()) { KFilePlacesItem *item = new KFilePlacesItem(bookmarkManager, bookmark.address(), udi); connect(item, SIGNAL(itemChanged(const QString&)), q, SLOT(_k_itemChanged(const QString&))); // TODO: Update bookmark internal element items << item; } } return items; } void KFilePlacesModel::Private::reloadAndSignal() { bookmarkManager->emitChanged(bookmarkManager->root()); // ... we'll get relisted anyway } Qt::DropActions KFilePlacesModel::supportedDropActions() const { return Qt::ActionMask; } Qt::ItemFlags KFilePlacesModel::flags(const QModelIndex &index) const { Qt::ItemFlags res = Qt::ItemIsSelectable|Qt::ItemIsEnabled; if (index.isValid()) res|= Qt::ItemIsDragEnabled; if (!index.isValid()) res|= Qt::ItemIsDropEnabled; return res; } static QString _k_internalMimetype(const KFilePlacesModel * const self) { return QString("application/x-kfileplacesmodel-")+QString::number((qptrdiff)self); } QStringList KFilePlacesModel::mimeTypes() const { QStringList types; types << _k_internalMimetype(this) << "text/uri-list"; return types; } QMimeData *KFilePlacesModel::mimeData(const QModelIndexList &indexes) const { KUrl::List urls; QByteArray itemData; QDataStream stream(&itemData, QIODevice::WriteOnly); foreach (const QModelIndex &index, indexes) { KUrl itemUrl = url(index); if (itemUrl.isValid()) urls << itemUrl; stream << index.row(); } QMimeData *mimeData = new QMimeData(); if (!urls.isEmpty()) - urls.populateMimeData(mimeData); + mimeData->setUrls(urls); mimeData->setData(_k_internalMimetype(this), itemData); return mimeData; } bool KFilePlacesModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { if (action == Qt::IgnoreAction) return true; if (column > 0) return false; if (row==-1 && parent.isValid()) { return false; // Don't allow to move an item onto another one, // too easy for the user to mess something up // If we really really want to allow copying files this way, // let's do it in the views to get the good old drop menu } KBookmark afterBookmark; if (row==-1) { // The dropped item is moved or added to the last position KFilePlacesItem *lastItem = d->items.last(); afterBookmark = lastItem->bookmark(); } else { // The dropped item is moved or added before position 'row', ie after position 'row-1' if (row>0) { KFilePlacesItem *afterItem = d->items[row-1]; afterBookmark = afterItem->bookmark(); } } if (data->hasFormat(_k_internalMimetype(this))) { // The operation is an internal move QByteArray itemData = data->data(_k_internalMimetype(this)); QDataStream stream(&itemData, QIODevice::ReadOnly); int itemRow; stream >> itemRow; KFilePlacesItem *item = d->items[itemRow]; KBookmark bookmark = item->bookmark(); d->bookmarkManager->root().moveBookmark(bookmark, afterBookmark); } else if (data->hasFormat("text/uri-list")) { // The operation is an add - QList urls = KUrl::List::fromMimeData(data); + const QList urls = KUrlMimeData::urlsFromMimeData(data); KBookmarkGroup group = d->bookmarkManager->root(); foreach (const KUrl &url, urls) { // TODO: use KIO::stat in order to get the UDS_DISPLAY_NAME too KMimeType::Ptr mimetype = KMimeType::mimeType(KIO::NetAccess::mimetype(url, 0)); if (!mimetype) { kWarning() << "URL not added to Places as mimetype could not be determined!"; continue; } if (!mimetype->is("inode/directory")) { // Only directories are allowed continue; } KFileItem item(url, mimetype->name(), S_IFDIR); KBookmark bookmark = KFilePlacesItem::createBookmark(d->bookmarkManager, url.fileName(), url, item.iconName()); group.moveBookmark(bookmark, afterBookmark); afterBookmark = bookmark; } } else { // Oops, shouldn't happen thanks to mimeTypes() kWarning() << ": received wrong mimedata, " << data->formats(); return false; } d->reloadAndSignal(); return true; } void KFilePlacesModel::addPlace(const QString &text, const KUrl &url, const QString &iconName, const QString &appName) { addPlace(text, url, iconName, appName, QModelIndex()); } void KFilePlacesModel::addPlace(const QString &text, const KUrl &url, const QString &iconName, const QString &appName, const QModelIndex &after) { KBookmark bookmark = KFilePlacesItem::createBookmark(d->bookmarkManager, text, url, iconName); if (!appName.isEmpty()) { bookmark.setMetaDataItem("OnlyInApp", appName); } if (after.isValid()) { KFilePlacesItem *item = static_cast(after.internalPointer()); d->bookmarkManager->root().moveBookmark(bookmark, item->bookmark()); } d->reloadAndSignal(); } void KFilePlacesModel::editPlace(const QModelIndex &index, const QString &text, const KUrl &url, const QString &iconName, const QString &appName) { if (!index.isValid()) return; KFilePlacesItem *item = static_cast(index.internalPointer()); if (item->isDevice()) return; KBookmark bookmark = item->bookmark(); if (bookmark.isNull()) return; bookmark.setFullText(text); bookmark.setUrl(url); bookmark.setIcon(iconName); bookmark.setMetaDataItem("OnlyInApp", appName); d->reloadAndSignal(); emit dataChanged(index, index); } void KFilePlacesModel::removePlace(const QModelIndex &index) const { if (!index.isValid()) return; KFilePlacesItem *item = static_cast(index.internalPointer()); if (item->isDevice()) return; KBookmark bookmark = item->bookmark(); if (bookmark.isNull()) return; d->bookmarkManager->root().deleteBookmark(bookmark); d->reloadAndSignal(); } void KFilePlacesModel::setPlaceHidden(const QModelIndex &index, bool hidden) { if (!index.isValid()) return; KFilePlacesItem *item = static_cast(index.internalPointer()); KBookmark bookmark = item->bookmark(); if (bookmark.isNull()) return; bookmark.setMetaDataItem("IsHidden", (hidden ? "true" : "false")); d->reloadAndSignal(); emit dataChanged(index, index); } int KFilePlacesModel::hiddenCount() const { int rows = rowCount(); int hidden = 0; for (int i=0; i() && device.as()->isAccessible()) { Solid::StorageDrive *drive = device.as(); if (drive==0) { drive = device.parent().as(); } bool hotpluggable = false; bool removable = false; if (drive!=0) { hotpluggable = drive->isHotpluggable(); removable = drive->isRemovable(); } QString iconName; QString text; QString label = data(index, Qt::DisplayRole).toString().replace('&',"&&"); if (device.is()) { text = i18n("&Release '%1'", label); } else if (removable || hotpluggable) { text = i18n("&Safely Remove '%1'", label); iconName = "media-eject"; } else { text = i18n("&Unmount '%1'", label); iconName = "media-eject"; } if (!iconName.isEmpty()) { return new QAction(KIcon(iconName), text, 0); } else { return new QAction(text, 0); } } return 0; } QAction *KFilePlacesModel::ejectActionForIndex(const QModelIndex &index) const { Solid::Device device = deviceForIndex(index); if (device.is()) { QString label = data(index, Qt::DisplayRole).toString().replace('&',"&&"); QString text = i18n("&Eject '%1'", label); return new QAction(KIcon("media-eject"), text, 0); } return 0; } void KFilePlacesModel::requestTeardown(const QModelIndex &index) { Solid::Device device = deviceForIndex(index); Solid::StorageAccess *access = device.as(); if (access!=0) { connect(access, SIGNAL(teardownDone(Solid::ErrorType, QVariant, const QString &)), this, SLOT(_k_storageTeardownDone(Solid::ErrorType, QVariant))); access->teardown(); } } void KFilePlacesModel::requestEject(const QModelIndex &index) { Solid::Device device = deviceForIndex(index); Solid::OpticalDrive *drive = device.parent().as(); if (drive!=0) { connect(drive, SIGNAL(ejectDone(Solid::ErrorType, QVariant, const QString &)), this, SLOT(_k_storageTeardownDone(Solid::ErrorType, QVariant))); drive->eject(); } else { QString label = data(index, Qt::DisplayRole).toString().replace('&',"&&"); QString message = i18n("The device '%1' is not a disk and cannot be ejected.", label); emit errorMessage(message); } } void KFilePlacesModel::requestSetup(const QModelIndex &index) { Solid::Device device = deviceForIndex(index); if (device.is() && !d->setupInProgress.contains(device.as()) && !device.as()->isAccessible()) { Solid::StorageAccess *access = device.as(); d->setupInProgress[access] = index; connect(access, SIGNAL(setupDone(Solid::ErrorType, QVariant, const QString &)), this, SLOT(_k_storageSetupDone(Solid::ErrorType, QVariant))); access->setup(); } } void KFilePlacesModel::Private::_k_storageSetupDone(Solid::ErrorType error, QVariant errorData) { QPersistentModelIndex index = setupInProgress.take(q->sender()); if (!index.isValid()) { return; } if (!error) { emit q->setupDone(index, true); } else { if (errorData.isValid()) { emit q->errorMessage(i18n("An error occurred while accessing '%1', the system responded: %2", q->text(index), errorData.toString())); } else { emit q->errorMessage(i18n("An error occurred while accessing '%1'", q->text(index))); } emit q->setupDone(index, false); } } void KFilePlacesModel::Private::_k_storageTeardownDone(Solid::ErrorType error, QVariant errorData) { if (error && errorData.isValid()) { emit q->errorMessage(errorData.toString()); } } #include "moc_kfileplacesmodel.cpp" diff --git a/kfile/kfilepreviewgenerator.cpp b/kfile/kfilepreviewgenerator.cpp index e87daa6f3c..04fdc258bc 100644 --- a/kfile/kfilepreviewgenerator.cpp +++ b/kfile/kfilepreviewgenerator.cpp @@ -1,1295 +1,1296 @@ /******************************************************************************* * Copyright (C) 2008-2009 by Peter Penz * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public License * * along with this library; see the file COPYING.LIB. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * *******************************************************************************/ #include "kfilepreviewgenerator.h" #include "../kio/kio/defaultviewadapter_p.h" // KDE5 TODO: move this class here #include "../kio/kio/imagefilter_p.h" #include // for HAVE_XRENDER #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(Q_WS_X11) && defined(HAVE_XRENDER) # include # include # include #endif /** * If the passed item view is an instance of QListView, expensive * layout operations are blocked in the constructor and are unblocked * again in the destructor. * * This helper class is a workaround for the following huge performance * problem when having directories with several 1000 items: * - each change of an icon emits a dataChanged() signal from the model * - QListView iterates through all items on each dataChanged() signal * and invokes QItemDelegate::sizeHint() * - the sizeHint() implementation of KFileItemDelegate is quite complex, * invoking it 1000 times for each icon change might block the UI * * QListView does not invoke QItemDelegate::sizeHint() when the * uniformItemSize property has been set to true, so this property is * set before exchanging a block of icons. It is important to reset * it again before the event loop is entered, otherwise QListView * would not get the correct size hints after dispatching the layoutChanged() * signal. */ class KFilePreviewGenerator::LayoutBlocker { public: LayoutBlocker(QAbstractItemView* view) : m_uniformSizes(false), m_view(qobject_cast(view)) { if (m_view != 0) { m_uniformSizes = m_view->uniformItemSizes(); m_view->setUniformItemSizes(true); } } ~LayoutBlocker() { if (m_view != 0) { m_view->setUniformItemSizes(m_uniformSizes); } } private: bool m_uniformSizes; QListView* m_view; }; /** Helper class for drawing frames for image previews. */ class KFilePreviewGenerator::TileSet { public: enum { LeftMargin = 3, TopMargin = 2, RightMargin = 3, BottomMargin = 4 }; enum Tile { TopLeftCorner = 0, TopSide, TopRightCorner, LeftSide, RightSide, BottomLeftCorner, BottomSide, BottomRightCorner, NumTiles }; TileSet() { QImage image(8 * 3, 8 * 3, QImage::Format_ARGB32_Premultiplied); QPainter p(&image); p.setCompositionMode(QPainter::CompositionMode_Source); p.fillRect(image.rect(), Qt::transparent); p.fillRect(image.rect().adjusted(3, 3, -3, -3), Qt::black); p.end(); KIO::ImageFilter::shadowBlur(image, 3, Qt::black); QPixmap pixmap = QPixmap::fromImage(image); m_tiles[TopLeftCorner] = pixmap.copy(0, 0, 8, 8); m_tiles[TopSide] = pixmap.copy(8, 0, 8, 8); m_tiles[TopRightCorner] = pixmap.copy(16, 0, 8, 8); m_tiles[LeftSide] = pixmap.copy(0, 8, 8, 8); m_tiles[RightSide] = pixmap.copy(16, 8, 8, 8); m_tiles[BottomLeftCorner] = pixmap.copy(0, 16, 8, 8); m_tiles[BottomSide] = pixmap.copy(8, 16, 8, 8); m_tiles[BottomRightCorner] = pixmap.copy(16, 16, 8, 8); } void paint(QPainter* p, const QRect& r) { p->drawPixmap(r.topLeft(), m_tiles[TopLeftCorner]); if (r.width() - 16 > 0) { p->drawTiledPixmap(r.x() + 8, r.y(), r.width() - 16, 8, m_tiles[TopSide]); } p->drawPixmap(r.right() - 8 + 1, r.y(), m_tiles[TopRightCorner]); if (r.height() - 16 > 0) { p->drawTiledPixmap(r.x(), r.y() + 8, 8, r.height() - 16, m_tiles[LeftSide]); p->drawTiledPixmap(r.right() - 8 + 1, r.y() + 8, 8, r.height() - 16, m_tiles[RightSide]); } p->drawPixmap(r.x(), r.bottom() - 8 + 1, m_tiles[BottomLeftCorner]); if (r.width() - 16 > 0) { p->drawTiledPixmap(r.x() + 8, r.bottom() - 8 + 1, r.width() - 16, 8, m_tiles[BottomSide]); } p->drawPixmap(r.right() - 8 + 1, r.bottom() - 8 + 1, m_tiles[BottomRightCorner]); const QRect contentRect = r.adjusted(LeftMargin + 1, TopMargin + 1, -(RightMargin + 1), -(BottomMargin + 1)); p->fillRect(contentRect, Qt::transparent); } private: QPixmap m_tiles[NumTiles]; }; class KFilePreviewGenerator::Private { public: Private(KFilePreviewGenerator* parent, KAbstractViewAdapter* viewAdapter, QAbstractItemModel* model); ~Private(); /** * Requests a new icon for the item \a index. * @param sequenceIndex If this is zero, the standard icon is requested, else another one. */ void requestSequenceIcon(const QModelIndex& index, int sequenceIndex); /** * Generates previews for the items \a items asynchronously. */ void updateIcons(const KFileItemList& items); /** * Generates previews for the indices within \a topLeft * and \a bottomRight asynchronously. */ void updateIcons(const QModelIndex& topLeft, const QModelIndex& bottomRight); /** * Adds the preview \a pixmap for the item \a item to the preview * queue and starts a timer which will dispatch the preview queue * later. */ void addToPreviewQueue(const KFileItem& item, const QPixmap& pixmap); /** * Is invoked when the preview job has been finished and * removes the job from the m_previewJobs list. */ void slotPreviewJobFinished(KJob* job); /** Synchronizes the icon of all items with the clipboard of cut items. */ void updateCutItems(); /** * Reset all icons of the items from m_cutItemsCache and clear * the cache. */ void clearCutItemsCache(); /** * Dispatches the preview queue block by block within * time slices. */ void dispatchIconUpdateQueue(); /** * Pauses all icon updates and invokes KFilePreviewGenerator::resumeIconUpdates() * after a short delay. Is invoked as soon as the user has moved * a scrollbar. */ void pauseIconUpdates(); /** * Resumes the icons updates that have been paused after moving the * scrollbar. The previews for the current visible area are * generated first. */ void resumeIconUpdates(); /** * Starts the resolving of the MIME types from * the m_pendingItems queue. */ void startMimeTypeResolving(); /** * Resolves the MIME type for exactly one item of the * m_pendingItems queue. */ void resolveMimeType(); /** * Returns true, if the item \a item has been cut into * the clipboard. */ bool isCutItem(const KFileItem& item) const; /** * Applies a cut-item effect to all given \a items, if they * are marked as cut in the clipboard. */ void applyCutItemEffect(const KFileItemList& items); /** * Applies a frame around the icon. False is returned if * no frame has been added because the icon is too small. */ bool applyImageFrame(QPixmap& icon); /** * Resizes the icon to \a maxSize if the icon size does not * fit into the maximum size. The aspect ratio of the icon * is kept. */ void limitToSize(QPixmap& icon, const QSize& maxSize); /** * Creates previews by starting new preview jobs for the items * and triggers the preview timer. */ void createPreviews(const KFileItemList& items); /** * Helper method for createPreviews(): Starts a preview job for the given * items. For each returned preview addToPreviewQueue() will get invoked. */ void startPreviewJob(const KFileItemList& items, int width, int height); /** Kills all ongoing preview jobs. */ void killPreviewJobs(); /** * Orders the items \a items in a way that the visible items * are moved to the front of the list. When passing this * list to a preview job, the visible items will get generated * first. */ void orderItems(KFileItemList& items); /** * Returns true, if \a mimeData represents a selection that has * been cut. */ bool decodeIsCutSelection(const QMimeData* mimeData); /** * Helper method for KFilePreviewGenerator::updateIcons(). Adds * recursively all items from the model to the list \a list. */ void addItemsToList(const QModelIndex& index, KFileItemList& list); /** * Updates the icons of files that are constantly changed due to a copy * operation. See m_changedItems and m_changedItemsTimer for details. */ void delayedIconUpdate(); /** * Any items that are removed from the model are also removed from m_changedItems. */ void rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end); /** Remembers the pixmap for an item specified by an URL. */ struct ItemInfo { KUrl url; QPixmap pixmap; }; /** * During the lifetime of a DataChangeObtainer instance changing * the data of the model won't trigger generating a preview. */ class DataChangeObtainer { public: DataChangeObtainer(KFilePreviewGenerator::Private* generator) : m_gen(generator) { ++m_gen->m_internalDataChange; } ~DataChangeObtainer() { --m_gen->m_internalDataChange; } private: KFilePreviewGenerator::Private* m_gen; }; bool m_previewShown; /** * True, if m_pendingItems and m_dispatchedItems should be * cleared when the preview jobs have been finished. */ bool m_clearItemQueues; /** * True if a selection has been done which should cut items. */ bool m_hasCutSelection; /** * True if the updates of icons has been paused by pauseIconUpdates(). * The value is reset by resumeIconUpdates(). */ bool m_iconUpdatesPaused; /** * If the value is 0, the slot * updateIcons(const QModelIndex&, const QModelIndex&) has * been triggered by an external data change. */ int m_internalDataChange; int m_pendingVisibleIconUpdates; KAbstractViewAdapter* m_viewAdapter; QAbstractItemView* m_itemView; QTimer* m_iconUpdateTimer; QTimer* m_scrollAreaTimer; QList m_previewJobs; QWeakPointer m_dirModel; QAbstractProxyModel* m_proxyModel; /** * Set of all items that already have the 'cut' effect applied, together with the pixmap it was applied to * This is used to make sure that the 'cut' effect is applied max. once for each pixmap * * Referencing the pixmaps here imposes no overhead, as they were also given to KDirModel::setData(), * and thus are held anyway. */ QHash m_cutItemsCache; QList m_previews; QMap m_sequenceIndices; /** * When huge items are copied, it must be prevented that a preview gets generated * for each item size change. m_changedItems keeps track of the changed items and it * is assured that a final preview is only done if an item does not change within * at least 5 seconds. */ QHash m_changedItems; QTimer* m_changedItemsTimer; /** * Contains all items where a preview must be generated, but * where the preview job has not dispatched the items yet. */ KFileItemList m_pendingItems; /** * Contains all items, where a preview has already been * generated by the preview jobs. */ KFileItemList m_dispatchedItems; KFileItemList m_resolvedMimeTypes; QStringList m_enabledPlugins; TileSet* m_tileSet; private: KFilePreviewGenerator* const q; }; KFilePreviewGenerator::Private::Private(KFilePreviewGenerator* parent, KAbstractViewAdapter* viewAdapter, QAbstractItemModel* model) : m_previewShown(true), m_clearItemQueues(true), m_hasCutSelection(false), m_iconUpdatesPaused(false), m_internalDataChange(0), m_pendingVisibleIconUpdates(0), m_viewAdapter(viewAdapter), m_itemView(0), m_iconUpdateTimer(0), m_scrollAreaTimer(0), m_previewJobs(), m_proxyModel(0), m_cutItemsCache(), m_previews(), m_sequenceIndices(), m_changedItems(), m_changedItemsTimer(0), m_pendingItems(), m_dispatchedItems(), m_resolvedMimeTypes(), m_enabledPlugins(), m_tileSet(0), q(parent) { if (!m_viewAdapter->iconSize().isValid()) { m_previewShown = false; } m_proxyModel = qobject_cast(model); m_dirModel = (m_proxyModel == 0) ? qobject_cast(model) : qobject_cast(m_proxyModel->sourceModel()); if (!m_dirModel) { // previews can only get generated for directory models m_previewShown = false; } else { KDirModel* dirModel = m_dirModel.data(); connect(dirModel->dirLister(), SIGNAL(newItems(const KFileItemList&)), q, SLOT(updateIcons(const KFileItemList&))); connect(dirModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), q, SLOT(updateIcons(const QModelIndex&, const QModelIndex&))); connect(dirModel, SIGNAL(needSequenceIcon(const QModelIndex&,int)), q, SLOT(requestSequenceIcon(const QModelIndex&, int))); connect(dirModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)), q, SLOT(rowsAboutToBeRemoved(const QModelIndex&, int, int))); } QClipboard* clipboard = QApplication::clipboard(); connect(clipboard, SIGNAL(dataChanged()), q, SLOT(updateCutItems())); m_iconUpdateTimer = new QTimer(q); m_iconUpdateTimer->setSingleShot(true); m_iconUpdateTimer->setInterval(200); connect(m_iconUpdateTimer, SIGNAL(timeout()), q, SLOT(dispatchIconUpdateQueue())); // Whenever the scrollbar values have been changed, the pending previews should // be reordered in a way that the previews for the visible items are generated // first. The reordering is done with a small delay, so that during moving the // scrollbars the CPU load is kept low. m_scrollAreaTimer = new QTimer(q); m_scrollAreaTimer->setSingleShot(true); m_scrollAreaTimer->setInterval(200); connect(m_scrollAreaTimer, SIGNAL(timeout()), q, SLOT(resumeIconUpdates())); m_viewAdapter->connect(KAbstractViewAdapter::ScrollBarValueChanged, q, SLOT(pauseIconUpdates())); m_changedItemsTimer = new QTimer(q); m_changedItemsTimer->setSingleShot(true); m_changedItemsTimer->setInterval(5000); connect(m_changedItemsTimer, SIGNAL(timeout()), q, SLOT(delayedIconUpdate())); KConfigGroup globalConfig(KGlobal::config(), "PreviewSettings"); m_enabledPlugins = globalConfig.readEntry("Plugins", QStringList() << "directorythumbnail" << "imagethumbnail" << "jpegthumbnail"); // If the user is upgrading from KDE <= 4.6, we must check if he had the 'jpegrotatedthumbnail' plugin enabled. // This plugin does not exist any more in KDE >= 4.7, so we have to replace it with the 'jpegthumbnail' plugin. if(m_enabledPlugins.contains(QLatin1String("jpegrotatedthumbnail"))) { m_enabledPlugins.removeAll(QLatin1String("jpegrotatedthumbnail")); m_enabledPlugins.append(QLatin1String("jpegthumbnail")); globalConfig.writeEntry("Plugins", m_enabledPlugins); globalConfig.sync(); } } KFilePreviewGenerator::Private::~Private() { killPreviewJobs(); m_pendingItems.clear(); m_dispatchedItems.clear(); delete m_tileSet; } void KFilePreviewGenerator::Private::requestSequenceIcon(const QModelIndex& index, int sequenceIndex) { if (m_pendingItems.isEmpty() || (sequenceIndex == 0)) { KDirModel* dirModel = m_dirModel.data(); if (!dirModel) { return; } KFileItem item = dirModel->itemForIndex(index); if (sequenceIndex == 0) { m_sequenceIndices.remove(item.url()); } else { m_sequenceIndices.insert(item.url(), sequenceIndex); } ///@todo Update directly, without using m_sequenceIndices updateIcons(KFileItemList() << item); } } void KFilePreviewGenerator::Private::updateIcons(const KFileItemList& items) { if (items.isEmpty()) { return; } applyCutItemEffect(items); KFileItemList orderedItems = items; orderItems(orderedItems); foreach (const KFileItem& item, orderedItems) { m_pendingItems.append(item); } if (m_previewShown) { createPreviews(orderedItems); } else { startMimeTypeResolving(); } } void KFilePreviewGenerator::Private::updateIcons(const QModelIndex& topLeft, const QModelIndex& bottomRight) { if (m_internalDataChange > 0) { // QAbstractItemModel::setData() has been invoked internally by the KFilePreviewGenerator. // The signal dataChanged() is connected with this method, but previews only need // to be generated when an external data change has occurred. return; } // dataChanged emitted for the root dir (e.g. permission changes) if (!topLeft.isValid() || !bottomRight.isValid()) { return; } KDirModel* dirModel = m_dirModel.data(); if (!dirModel) { return; } KFileItemList itemList; for (int row = topLeft.row(); row <= bottomRight.row(); ++row) { const QModelIndex index = dirModel->index(row, 0); if (!index.isValid()) { continue; } const KFileItem item = dirModel->itemForIndex(index); Q_ASSERT(!item.isNull()); if (m_previewShown) { const KUrl url = item.url(); const bool hasChanged = m_changedItems.contains(url); // O(1) m_changedItems.insert(url, hasChanged); if (!hasChanged) { // only update the icon if it has not been already updated within // the last 5 seconds (the other icons will be updated later with // the help of m_changedItemsTimer) itemList.append(item); } } else { itemList.append(item); } } updateIcons(itemList); m_changedItemsTimer->start(); } void KFilePreviewGenerator::Private::addToPreviewQueue(const KFileItem& item, const QPixmap& pixmap) { KIO::PreviewJob* senderJob = qobject_cast(q->sender()); Q_ASSERT(senderJob != 0); if (senderJob != 0) { QMap::iterator it = m_sequenceIndices.find(item.url()); if (senderJob->sequenceIndex() && (it == m_sequenceIndices.end() || *it != senderJob->sequenceIndex())) { return; // the sequence index does not match the one we want } if (!senderJob->sequenceIndex() && it != m_sequenceIndices.end()) { return; // the sequence index does not match the one we want } m_sequenceIndices.erase(it); } if (!m_previewShown) { // the preview has been canceled in the meantime return; } KDirModel* dirModel = m_dirModel.data(); if (!dirModel) { return; } // check whether the item is part of the directory lister (it is possible // that a preview from an old directory lister is received) bool isOldPreview = true; KUrl itemParentDir(item.url()); itemParentDir.setPath(itemParentDir.directory()); foreach (const KUrl& dir, dirModel->dirLister()->directories()) { if (dir == itemParentDir || !dir.hasPath()) { isOldPreview = false; break; } } if (isOldPreview) { return; } QPixmap icon = pixmap; const QString mimeType = item.mimetype(); const int slashIndex = mimeType.indexOf(QLatin1Char('/')); const QString mimeTypeGroup = mimeType.left(slashIndex); if ((mimeTypeGroup != QLatin1String("image")) || !applyImageFrame(icon)) { limitToSize(icon, m_viewAdapter->iconSize()); } if (m_hasCutSelection && isCutItem(item)) { // apply the disabled effect to the icon for marking it as "cut item" // and apply the icon to the item KIconEffect *iconEffect = KIconLoader::global()->iconEffect(); icon = iconEffect->apply(icon, KIconLoader::Desktop, KIconLoader::DisabledState); } KIconLoader::global()->drawOverlays(item.overlays(), icon, KIconLoader::Desktop); // remember the preview and URL, so that it can be applied to the model // in KFilePreviewGenerator::dispatchIconUpdateQueue() ItemInfo preview; preview.url = item.url(); preview.pixmap = icon; m_previews.append(preview); m_dispatchedItems.append(item); } void KFilePreviewGenerator::Private::slotPreviewJobFinished(KJob* job) { const int index = m_previewJobs.indexOf(job); m_previewJobs.removeAt(index); if (m_previewJobs.isEmpty()) { if (m_clearItemQueues) { m_pendingItems.clear(); m_dispatchedItems.clear(); m_pendingVisibleIconUpdates = 0; QMetaObject::invokeMethod(q, "dispatchIconUpdateQueue", Qt::QueuedConnection); } m_sequenceIndices.clear(); // just to be sure that we don't leak anything } } void KFilePreviewGenerator::Private::updateCutItems() { KDirModel* dirModel = m_dirModel.data(); if (!dirModel) { return; } DataChangeObtainer obt(this); clearCutItemsCache(); KFileItemList items; KDirLister* dirLister = dirModel->dirLister(); const QList dirs = dirLister->directories(); foreach (const KUrl& url, dirs) { items << dirLister->itemsForDir(url); } applyCutItemEffect(items); } void KFilePreviewGenerator::Private::clearCutItemsCache() { KDirModel* dirModel = m_dirModel.data(); if (!dirModel) { return; } DataChangeObtainer obt(this); KFileItemList previews; // Reset the icons of all items that are stored in the cache // to use their default MIME type icon. foreach (const KUrl& url, m_cutItemsCache.keys()) { const QModelIndex index = dirModel->indexForUrl(url); if (index.isValid()) { dirModel->setData(index, QIcon(), Qt::DecorationRole); if (m_previewShown) { previews.append(dirModel->itemForIndex(index)); } } } m_cutItemsCache.clear(); if (previews.size() > 0) { // assure that the previews gets restored Q_ASSERT(m_previewShown); orderItems(previews); updateIcons(previews); } } void KFilePreviewGenerator::Private::dispatchIconUpdateQueue() { KDirModel* dirModel = m_dirModel.data(); if (!dirModel) { return; } const int count = m_previewShown ? m_previews.count() : m_resolvedMimeTypes.count(); if (count > 0) { LayoutBlocker blocker(m_itemView); DataChangeObtainer obt(this); if (m_previewShown) { // dispatch preview queue foreach (const ItemInfo& preview, m_previews) { const QModelIndex idx = dirModel->indexForUrl(preview.url); if (idx.isValid() && (idx.column() == 0)) { dirModel->setData(idx, QIcon(preview.pixmap), Qt::DecorationRole); } } m_previews.clear(); } else { // dispatch mime type queue foreach (const KFileItem& item, m_resolvedMimeTypes) { const QModelIndex idx = dirModel->indexForItem(item); dirModel->itemChanged(idx); } m_resolvedMimeTypes.clear(); } m_pendingVisibleIconUpdates -= count; if (m_pendingVisibleIconUpdates < 0) { m_pendingVisibleIconUpdates = 0; } } if (m_pendingVisibleIconUpdates > 0) { // As long as there are pending previews for visible items, poll // the preview queue periodically. If there are no pending previews, // the queue is dispatched in slotPreviewJobFinished(). m_iconUpdateTimer->start(); } } void KFilePreviewGenerator::Private::pauseIconUpdates() { m_iconUpdatesPaused = true; foreach (KJob* job, m_previewJobs) { Q_ASSERT(job != 0); job->suspend(); } m_scrollAreaTimer->start(); } void KFilePreviewGenerator::Private::resumeIconUpdates() { m_iconUpdatesPaused = false; // Before creating new preview jobs the m_pendingItems queue must be // cleaned up by removing the already dispatched items. Implementation // note: The order of the m_dispatchedItems queue and the m_pendingItems // queue is usually equal. So even when having a lot of elements the // nested loop is no performance bottle neck, as the inner loop is only // entered once in most cases. foreach (const KFileItem& item, m_dispatchedItems) { KFileItemList::iterator begin = m_pendingItems.begin(); KFileItemList::iterator end = m_pendingItems.end(); for (KFileItemList::iterator it = begin; it != end; ++it) { if ((*it).url() == item.url()) { m_pendingItems.erase(it); break; } } } m_dispatchedItems.clear(); m_pendingVisibleIconUpdates = 0; dispatchIconUpdateQueue(); if (m_previewShown) { KFileItemList orderedItems = m_pendingItems; orderItems(orderedItems); // Kill all suspended preview jobs. Usually when a preview job // has been finished, slotPreviewJobFinished() clears all item queues. // This is not wanted in this case, as a new job is created afterwards // for m_pendingItems. m_clearItemQueues = false; killPreviewJobs(); m_clearItemQueues = true; createPreviews(orderedItems); } else { orderItems(m_pendingItems); startMimeTypeResolving(); } } void KFilePreviewGenerator::Private::startMimeTypeResolving() { resolveMimeType(); m_iconUpdateTimer->start(); } void KFilePreviewGenerator::Private::resolveMimeType() { if (m_pendingItems.isEmpty()) { return; } // resolve at least one MIME type bool resolved = false; do { KFileItem item = m_pendingItems.takeFirst(); if (item.isMimeTypeKnown()) { if (m_pendingVisibleIconUpdates > 0) { // The item is visible and the MIME type already known. // Decrease the update counter for dispatchIconUpdateQueue(): --m_pendingVisibleIconUpdates; } } else { // The MIME type is unknown and must get resolved. The // directory model is not informed yet, as a single update // would be very expensive. Instead the item is remembered in // m_resolvedMimeTypes and will be dispatched later // by dispatchIconUpdateQueue(). item.determineMimeType(); m_resolvedMimeTypes.append(item); resolved = true; } } while (!resolved && !m_pendingItems.isEmpty()); if (m_pendingItems.isEmpty()) { // All MIME types have been resolved now. Assure // that the directory model gets informed about // this, so that an update of the icons is done. dispatchIconUpdateQueue(); } else if (!m_iconUpdatesPaused) { // assure that the MIME type of the next // item will be resolved asynchronously QMetaObject::invokeMethod(q, "resolveMimeType", Qt::QueuedConnection); } } bool KFilePreviewGenerator::Private::isCutItem(const KFileItem& item) const { const QMimeData* mimeData = QApplication::clipboard()->mimeData(); - const QList cutUrls = KUrl::List::fromMimeData(mimeData); + const QList cutUrls = KUrlMimeData::urlsFromMimeData(mimeData); return cutUrls.contains(item.url()); } void KFilePreviewGenerator::Private::applyCutItemEffect(const KFileItemList& items) { const QMimeData* mimeData = QApplication::clipboard()->mimeData(); m_hasCutSelection = decodeIsCutSelection(mimeData); if (!m_hasCutSelection) { return; } KDirModel* dirModel = m_dirModel.data(); if (!dirModel) { return; } - const QSet cutUrls = KUrl::List::fromMimeData(mimeData).toSet(); + const QSet cutUrls = KUrlMimeData::urlsFromMimeData(mimeData).toSet(); DataChangeObtainer obt(this); KIconEffect *iconEffect = KIconLoader::global()->iconEffect(); foreach (const KFileItem& item, items) { if (cutUrls.contains(item.url())) { const QModelIndex index = dirModel->indexForItem(item); const QVariant value = dirModel->data(index, Qt::DecorationRole); if (value.type() == QVariant::Icon) { const QIcon icon(qvariant_cast(value)); const QSize actualSize = icon.actualSize(m_viewAdapter->iconSize()); QPixmap pixmap = icon.pixmap(actualSize); const QHash::const_iterator cacheIt = m_cutItemsCache.constFind(item.url()); if ((cacheIt == m_cutItemsCache.constEnd()) || (cacheIt->cacheKey() != pixmap.cacheKey())) { pixmap = iconEffect->apply(pixmap, KIconLoader::Desktop, KIconLoader::DisabledState); dirModel->setData(index, QIcon(pixmap), Qt::DecorationRole); m_cutItemsCache.insert(item.url(), pixmap); } } } } } bool KFilePreviewGenerator::Private::applyImageFrame(QPixmap& icon) { const QSize maxSize = m_viewAdapter->iconSize(); // The original size of an image is not exported by the thumbnail mechanism. // Still it would be helpful to not apply an image frame for e. g. icons that // fit into the given boundaries: const bool isIconCandidate = (icon.width() == icon.height()) && ((icon.width() & 0x7) == 0); const bool applyFrame = (maxSize.width() > KIconLoader::SizeSmallMedium) && (maxSize.height() > KIconLoader::SizeSmallMedium) && !isIconCandidate; if (!applyFrame) { // the maximum size or the image itself is too small for a frame return false; } // resize the icon to the maximum size minus the space required for the frame const QSize size(maxSize.width() - TileSet::LeftMargin - TileSet::RightMargin, maxSize.height() - TileSet::TopMargin - TileSet::BottomMargin); limitToSize(icon, size); if (m_tileSet == 0) { m_tileSet = new TileSet(); } QPixmap framedIcon(icon.size().width() + TileSet::LeftMargin + TileSet::RightMargin, icon.size().height() + TileSet::TopMargin + TileSet::BottomMargin); framedIcon.fill(Qt::transparent); QPainter painter; painter.begin(&framedIcon); painter.setCompositionMode(QPainter::CompositionMode_Source); m_tileSet->paint(&painter, framedIcon.rect()); painter.setCompositionMode(QPainter::CompositionMode_SourceOver); painter.drawPixmap(TileSet::LeftMargin, TileSet::TopMargin, icon); painter.end(); icon = framedIcon; return true; } void KFilePreviewGenerator::Private::limitToSize(QPixmap& icon, const QSize& maxSize) { if ((icon.width() > maxSize.width()) || (icon.height() > maxSize.height())) { #if defined(Q_WS_X11) && defined(HAVE_XRENDER) // Assume that the texture size limit is 2048x2048 if ((icon.width() <= 2048) && (icon.height() <= 2048) && icon.x11PictureHandle()) { QSize size = icon.size(); size.scale(maxSize, Qt::KeepAspectRatio); const qreal factor = size.width() / qreal(icon.width()); XTransform xform = {{ { XDoubleToFixed(1 / factor), 0, 0 }, { 0, XDoubleToFixed(1 / factor), 0 }, { 0, 0, XDoubleToFixed(1) } }}; QPixmap pixmap(size); pixmap.fill(Qt::transparent); Display* dpy = QX11Info::display(); XRenderPictureAttributes attr; attr.repeat = RepeatPad; XRenderChangePicture(dpy, icon.x11PictureHandle(), CPRepeat, &attr); XRenderSetPictureFilter(dpy, icon.x11PictureHandle(), FilterBilinear, 0, 0); XRenderSetPictureTransform(dpy, icon.x11PictureHandle(), &xform); XRenderComposite(dpy, PictOpOver, icon.x11PictureHandle(), None, pixmap.x11PictureHandle(), 0, 0, 0, 0, 0, 0, pixmap.width(), pixmap.height()); icon = pixmap; } else { icon = icon.scaled(maxSize, Qt::KeepAspectRatio, Qt::FastTransformation); } #else icon = icon.scaled(maxSize, Qt::KeepAspectRatio, Qt::FastTransformation); #endif } } void KFilePreviewGenerator::Private::createPreviews(const KFileItemList& items) { if (items.count() == 0) { return; } const QMimeData* mimeData = QApplication::clipboard()->mimeData(); m_hasCutSelection = decodeIsCutSelection(mimeData); // PreviewJob internally caches items always with the size of // 128 x 128 pixels or 256 x 256 pixels. A downscaling is done // by PreviewJob if a smaller size is requested. For images KFilePreviewGenerator must // do a downscaling anyhow because of the frame, so in this case only the provided // cache sizes are requested. KFileItemList imageItems; KFileItemList otherItems; QString mimeType; QString mimeTypeGroup; foreach (const KFileItem& item, items) { mimeType = item.mimetype(); const int slashIndex = mimeType.indexOf(QLatin1Char('/')); mimeTypeGroup = mimeType.left(slashIndex); if (mimeTypeGroup == QLatin1String("image")) { imageItems.append(item); } else { otherItems.append(item); } } const QSize size = m_viewAdapter->iconSize(); startPreviewJob(otherItems, size.width(), size.height()); const int cacheSize = (size.width() > 128) || (size.height() > 128) ? 256 : 128; startPreviewJob(imageItems, cacheSize, cacheSize); m_iconUpdateTimer->start(); } void KFilePreviewGenerator::Private::startPreviewJob(const KFileItemList& items, int width, int height) { if (items.count() > 0) { KIO::PreviewJob* job = KIO::filePreview(items, QSize(width, height), &m_enabledPlugins); // Set the sequence index to the target. We only need to check if items.count() == 1, // because requestSequenceIcon(..) creates exactly such a request. if (!m_sequenceIndices.isEmpty() && (items.count() == 1)) { QMap::iterator it = m_sequenceIndices.find(items[0].url()); if (it != m_sequenceIndices.end()) { job->setSequenceIndex(*it); } } connect(job, SIGNAL(gotPreview(const KFileItem&, const QPixmap&)), q, SLOT(addToPreviewQueue(const KFileItem&, const QPixmap&))); connect(job, SIGNAL(finished(KJob*)), q, SLOT(slotPreviewJobFinished(KJob*))); m_previewJobs.append(job); } } void KFilePreviewGenerator::Private::killPreviewJobs() { foreach (KJob* job, m_previewJobs) { Q_ASSERT(job != 0); job->kill(); } m_previewJobs.clear(); m_sequenceIndices.clear(); - + m_iconUpdateTimer->stop(); m_scrollAreaTimer->stop(); m_changedItemsTimer->stop(); } void KFilePreviewGenerator::Private::orderItems(KFileItemList& items) { KDirModel* dirModel = m_dirModel.data(); if (!dirModel) { return; } // Order the items in a way that the preview for the visible items // is generated first, as this improves the feeled performance a lot. const bool hasProxy = (m_proxyModel != 0); const int itemCount = items.count(); const QRect visibleArea = m_viewAdapter->visibleArea(); QModelIndex dirIndex; QRect itemRect; int insertPos = 0; for (int i = 0; i < itemCount; ++i) { dirIndex = dirModel->indexForItem(items.at(i)); // O(n) (n = number of rows) if (hasProxy) { const QModelIndex proxyIndex = m_proxyModel->mapFromSource(dirIndex); itemRect = m_viewAdapter->visualRect(proxyIndex); } else { itemRect = m_viewAdapter->visualRect(dirIndex); } if (itemRect.intersects(visibleArea)) { // The current item is (at least partly) visible. Move it // to the front of the list, so that the preview is // generated earlier. items.insert(insertPos, items.at(i)); items.removeAt(i + 1); ++insertPos; ++m_pendingVisibleIconUpdates; } } } bool KFilePreviewGenerator::Private::decodeIsCutSelection(const QMimeData* mimeData) { const QByteArray data = mimeData->data("application/x-kde-cutselection"); if (data.isEmpty()) { return false; } else { return data.at(0) == QLatin1Char('1'); } } void KFilePreviewGenerator::Private::addItemsToList(const QModelIndex& index, KFileItemList& list) { KDirModel* dirModel = m_dirModel.data(); if (!dirModel) { return; } const int rowCount = dirModel->rowCount(index); for (int row = 0; row < rowCount; ++row) { const QModelIndex subIndex = dirModel->index(row, 0, index); KFileItem item = dirModel->itemForIndex(subIndex); list.append(item); if (dirModel->rowCount(subIndex) > 0) { // the model is hierarchical (treeview) addItemsToList(subIndex, list); } } } void KFilePreviewGenerator::Private::delayedIconUpdate() { KDirModel* dirModel = m_dirModel.data(); if (!dirModel) { return; } // Precondition: No items have been changed within the last // 5 seconds. This means that items that have been changed constantly // due to a copy operation should be updated now. KFileItemList itemList; QHash::const_iterator it = m_changedItems.constBegin(); while (it != m_changedItems.constEnd()) { const bool hasChanged = it.value(); if (hasChanged) { const QModelIndex index = dirModel->indexForUrl(it.key()); const KFileItem item = dirModel->itemForIndex(index); itemList.append(item); } ++it; } m_changedItems.clear(); updateIcons(itemList); } void KFilePreviewGenerator::Private::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) { if (m_changedItems.isEmpty()) { return; } KDirModel* dirModel = m_dirModel.data(); if (!dirModel) { return; } for (int row = start; row <= end; row++) { const QModelIndex index = dirModel->index(row, 0, parent); const KFileItem item = dirModel->itemForIndex(index); if (!item.isNull()) { m_changedItems.remove(item.url()); } if (dirModel->hasChildren(index)) { rowsAboutToBeRemoved(index, 0, dirModel->rowCount(index) - 1); } } } KFilePreviewGenerator::KFilePreviewGenerator(QAbstractItemView* parent) : QObject(parent), d(new Private(this, new KIO::DefaultViewAdapter(parent, this), parent->model())) { d->m_itemView = parent; } KFilePreviewGenerator::KFilePreviewGenerator(KAbstractViewAdapter* parent, QAbstractProxyModel* model) : QObject(parent), d(new Private(this, parent, model)) { } KFilePreviewGenerator::~KFilePreviewGenerator() { delete d; } void KFilePreviewGenerator::setPreviewShown(bool show) { if (d->m_previewShown == show) { return; } KDirModel* dirModel = d->m_dirModel.data(); if (show && (!d->m_viewAdapter->iconSize().isValid() || !dirModel)) { // The view must provide an icon size and a directory model, // otherwise the showing the previews will get ignored return; } d->m_previewShown = show; if (!show) { // Clear the icon for all items so that the MIME type // gets reloaded KFileItemList itemList; d->addItemsToList(QModelIndex(), itemList); const bool blocked = dirModel->signalsBlocked(); dirModel->blockSignals(true); foreach (const KFileItem& item, itemList) { const QModelIndex index = dirModel->indexForItem(item); dirModel->setData(index, QIcon(), Qt::DecorationRole); } dirModel->blockSignals(blocked); } updateIcons(); } bool KFilePreviewGenerator::isPreviewShown() const { return d->m_previewShown; } // deprecated (use updateIcons() instead) void KFilePreviewGenerator::updatePreviews() { updateIcons(); } void KFilePreviewGenerator::updateIcons() { d->killPreviewJobs(); d->clearCutItemsCache(); d->m_pendingItems.clear(); d->m_dispatchedItems.clear(); KFileItemList itemList; d->addItemsToList(QModelIndex(), itemList); d->updateIcons(itemList); } void KFilePreviewGenerator::cancelPreviews() { d->killPreviewJobs(); d->m_pendingItems.clear(); d->m_dispatchedItems.clear(); updateIcons(); } void KFilePreviewGenerator::setEnabledPlugins(const QStringList& plugins) { d->m_enabledPlugins = plugins; } QStringList KFilePreviewGenerator::enabledPlugins() const { return d->m_enabledPlugins; } #include "moc_kfilepreviewgenerator.cpp" diff --git a/kfile/kurlnavigator.cpp b/kfile/kurlnavigator.cpp index d50efad5fb..aba76f6a5f 100644 --- a/kfile/kurlnavigator.cpp +++ b/kfile/kurlnavigator.cpp @@ -1,1257 +1,1256 @@ /***************************************************************************** * Copyright (C) 2006-2010 by Peter Penz * * Copyright (C) 2006 by Aaron J. Seigo * * Copyright (C) 2007 by Kevin Ottens * * Copyright (C) 2007 by Urs Wolfer * * * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 m_history; KUrlNavigatorPlacesSelector* m_placesSelector; KUrlComboBox* m_pathBox; KUrlNavigatorProtocolCombo* m_protocols; KUrlNavigatorDropDownButton* m_dropDownButton; QList 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 // Copyright (C) 2001 Joseph Wenninger // Copyright (C) 2001 Anders Lund 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 QList urls = KUrl::List::fromMimeData(event->mimeData()); - if (!urls.isEmpty()) { + if (event->mimeData()->hasUrls()) { emit q->urlsDropped(destination, event); } } void KUrlNavigator::Private::slotNavigatorButtonClicked(const KUrl& url, Qt::MouseButton button) { if (button & Qt::LeftButton) { q->setLocationUrl(url); } else if (button & Qt::MidButton) { emit q->tabRequested(url); } } void KUrlNavigator::Private::openContextMenu() { q->setActive(true); KMenu popup(q); // provide 'Copy' action, which copies the current URL of // the URL navigator into the clipboard QAction* copyAction = popup.addAction(KIcon("edit-copy"), i18n("Copy")); // provide 'Paste' action, which copies the current clipboard text // into the URL navigator QAction* pasteAction = popup.addAction(KIcon("edit-paste"), i18n("Paste")); QClipboard* clipboard = QApplication::clipboard(); pasteAction->setEnabled(!clipboard->text().isEmpty()); popup.addSeparator(); // provide radiobuttons for toggling between the edit and the navigation mode QAction* editAction = popup.addAction(i18n("Edit")); editAction->setCheckable(true); QAction* navigateAction = popup.addAction(i18n("Navigate")); navigateAction->setCheckable(true); QActionGroup* modeGroup = new QActionGroup(&popup); modeGroup->addAction(editAction); modeGroup->addAction(navigateAction); if (q->isUrlEditable()) { editAction->setChecked(true); } else { navigateAction->setChecked(true); } popup.addSeparator(); // allow showing of the full path QAction* showFullPathAction = popup.addAction(i18n("Show Full Path")); showFullPathAction->setCheckable(true); showFullPathAction->setChecked(q->showFullPath()); QAction* activatedAction = popup.exec(QCursor::pos()); if (activatedAction == copyAction) { QMimeData* mimeData = new QMimeData(); mimeData->setText(q->locationUrl().pathOrUrl()); clipboard->setMimeData(mimeData); } else if (activatedAction == pasteAction) { q->setLocationUrl(KUrl(clipboard->text())); } else if (activatedAction == editAction) { q->setUrlEditable(true); } else if (activatedAction == navigateAction) { q->setUrlEditable(false); } else if (activatedAction == showFullPathAction) { q->setShowFullPath(showFullPathAction->isChecked()); } } void KUrlNavigator::Private::slotPathBoxChanged(const QString& text) { if (text.isEmpty()) { const QString protocol = q->locationUrl().scheme(); m_protocols->setProtocol(protocol); m_protocols->show(); } else { m_protocols->hide(); } } void KUrlNavigator::Private::updateContent() { const KUrl currentUrl = q->locationUrl(); if (m_placesSelector != 0) { m_placesSelector->updateSelection(currentUrl); } if (m_editable) { m_protocols->hide(); m_dropDownButton->hide(); deleteButtons(); m_toggleEditableMode->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); q->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); m_pathBox->show(); m_pathBox->setUrl(currentUrl); } else { m_pathBox->hide(); m_protocols->hide(); m_toggleEditableMode->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); q->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); // Calculate the start index for the directories that should be shown as buttons // and create the buttons KUrl placeUrl; if ((m_placesSelector != 0) && !m_showFullPath) { placeUrl = m_placesSelector->selectedPlaceUrl(); } QString placePath = placeUrl.isValid() ? placeUrl.pathOrUrl() : retrievePlacePath(); removeTrailingSlash(placePath); const int startIndex = placePath.count('/'); updateButtons(startIndex); } } void KUrlNavigator::Private::updateButtons(int startIndex) { KUrl currentUrl = q->locationUrl(); const QString path = currentUrl.pathOrUrl(); bool createButton = false; const int oldButtonCount = m_navButtons.count(); int idx = startIndex; bool hasNext = true; do { createButton = (idx - startIndex >= oldButtonCount); const bool isFirstButton = (idx == startIndex); const QString dirName = path.section(QLatin1Char('/'), idx, idx); hasNext = isFirstButton || !dirName.isEmpty(); if (hasNext) { KUrlNavigatorButton* button = 0; if (createButton) { button = new KUrlNavigatorButton(buttonUrl(idx), q); button->installEventFilter(q); button->setForegroundRole(QPalette::WindowText); connect(button, SIGNAL(urlsDropped(const KUrl&, QDropEvent*)), q, SLOT(dropUrls(const KUrl&, QDropEvent*))); connect(button, SIGNAL(clicked(KUrl, Qt::MouseButton)), q, SLOT(slotNavigatorButtonClicked(KUrl, Qt::MouseButton))); connect(button, SIGNAL(finishedTextResolving()), q, SLOT(updateButtonVisibility())); appendWidget(button); } else { button = m_navButtons[idx - startIndex]; button->setUrl(buttonUrl(idx)); } if (isFirstButton) { button->setText(firstButtonText()); } button->setActive(q->isActive()); if (createButton) { if (!isFirstButton) { setTabOrder(m_navButtons.last(), button); } m_navButtons.append(button); } ++idx; button->setActiveSubDirectory(path.section(QLatin1Char('/'), idx, idx)); } } while (hasNext); // delete buttons which are not used anymore const int newButtonCount = idx - startIndex; if (newButtonCount < oldButtonCount) { const QList::iterator itBegin = m_navButtons.begin() + newButtonCount; const QList::iterator itEnd = m_navButtons.end(); QList::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::const_iterator it = m_navButtons.constEnd(); const QList::const_iterator itBegin = m_navButtons.constBegin(); bool isLastButton = true; bool hasHiddenButtons = false; QLinkedList buttonsToShow; while (it != itBegin) { --it; KUrlNavigatorButton* button = (*it); availableWidth -= button->minimumWidth(); if ((availableWidth <= 0) && !isLastButton) { button->hide(); hasHiddenButtons = true; } else { // Don't show the button immediately, as setActive() // might change the size and a relayout gets triggered // after showing the button. So the showing of all buttons // is postponed until all buttons have the correct // activation state. buttonsToShow.append(button); } isLastButton = false; } // All buttons have the correct activation state and // can be shown now foreach (KUrlNavigatorButton* button, buttonsToShow) { button->show(); } if (hasHiddenButtons) { m_dropDownButton->show(); } else { // Check whether going upwards is possible. If this is the case, show the drop-down button. KUrl url = m_navButtons.front()->url(); url.adjustPath(KUrl::AddTrailingSlash); const bool visible = !url.equals(url.upUrl()) && (url.scheme() != "nepomuksearch"); m_dropDownButton->setVisible(visible); } } QString KUrlNavigator::Private::firstButtonText() const { QString text; // The first URL navigator button should get the name of the // place instead of the directory name if ((m_placesSelector != 0) && !m_showFullPath) { const KUrl placeUrl = m_placesSelector->selectedPlaceUrl(); text = m_placesSelector->selectedPlaceText(); } if (text.isEmpty()) { const KUrl currentUrl = q->locationUrl(); if (currentUrl.isLocalFile()) { #ifdef Q_OS_WIN text = currentUrl.path().length() > 1 ? currentUrl.path().left(2) : QDir::rootPath(); #else text = m_showFullPath ? QLatin1String("/") : i18n("Custom Path"); #endif } else { text = currentUrl.scheme() + QLatin1Char(':'); if (!currentUrl.host().isEmpty()) { text += QLatin1Char(' ') + currentUrl.host(); } } } return text; } KUrl KUrlNavigator::Private::buttonUrl(int index) const { if (index < 0) { index = 0; } // Keep scheme, hostname etc. as this is needed for e. g. browsing // FTP directories const KUrl currentUrl = q->locationUrl(); KUrl newUrl = currentUrl; newUrl.setPath(QString()); QString pathOrUrl = currentUrl.pathOrUrl(); if (!pathOrUrl.isEmpty()) { if (index == 0) { // prevent the last "/" from being stripped // or we end up with an empty path #ifdef Q_OS_WIN pathOrUrl = pathOrUrl.length() > 1 ? pathOrUrl.left(2) : QDir::rootPath(); #else pathOrUrl = QLatin1String("/"); #endif } else { pathOrUrl = pathOrUrl.section('/', 0, index); } } newUrl.setPath(KUrl(pathOrUrl).path()); return newUrl; } void KUrlNavigator::Private::switchToBreadcrumbMode() { q->setUrlEditable(false); } void KUrlNavigator::Private::deleteButtons() { foreach (KUrlNavigatorButton* button, m_navButtons) { button->hide(); button->deleteLater(); } m_navButtons.clear(); } QString KUrlNavigator::Private::retrievePlacePath() const { const KUrl currentUrl = q->locationUrl(); const QString path = currentUrl.pathOrUrl(); int idx = path.indexOf(QLatin1String("///")); if (idx >= 0) { idx += 3; } else { idx = path.indexOf(QLatin1String("//")); idx = path.indexOf(QLatin1Char('/'), (idx < 0) ? 0 : idx + 2); } QString placePath = (idx < 0) ? path : path.left(idx); removeTrailingSlash(placePath); return placePath; } bool KUrlNavigator::Private::isCompressedPath(const KUrl& url) const { const KMimeType::Ptr mime = KMimeType::findByPath(url.path(KUrl::RemoveTrailingSlash)); // Note: this list of MIME types depends on the protocols implemented by kio_archive return mime->is("application/x-compressed-tar") || mime->is("application/x-bzip-compressed-tar") || mime->is("application/x-lzma-compressed-tar") || mime->is("application/x-xz-compressed-tar") || mime->is("application/x-tar") || mime->is("application/x-tarz") || mime->is("application/x-tzo") || // (not sure KTar supports those?) mime->is("application/zip") || mime->is("application/x-archive"); } void KUrlNavigator::Private::removeTrailingSlash(QString& url) const { const int length = url.length(); if ((length > 0) && (url.at(length - 1) == QChar('/'))) { url.remove(length - 1, 1); } } int KUrlNavigator::Private::adjustedHistoryIndex(int historyIndex) const { if (historyIndex < 0) { historyIndex = m_historyIndex; } else if (historyIndex >= m_history.size()) { historyIndex = m_history.size() - 1; Q_ASSERT(historyIndex >= 0); // m_history.size() must always be > 0 } return historyIndex; } // ------------------------------------------------------------------------------------------------ KUrlNavigator::KUrlNavigator(QWidget* parent) : QWidget(parent), d(new Private(this, 0)) { d->initialize(KUrl()); } KUrlNavigator::KUrlNavigator(KFilePlacesModel* placesModel, const KUrl& url, QWidget* parent) : QWidget(parent), d(new Private(this, placesModel)) { d->initialize(url); } KUrlNavigator::~KUrlNavigator() { delete d; } KUrl KUrlNavigator::locationUrl(int historyIndex) const { historyIndex = d->adjustedHistoryIndex(historyIndex); return d->m_history[historyIndex].url; } void KUrlNavigator::saveLocationState(const QByteArray& state) { d->m_history[d->m_historyIndex].state = state; } QByteArray KUrlNavigator::locationState(int historyIndex) const { historyIndex = d->adjustedHistoryIndex(historyIndex); return d->m_history[historyIndex].state; } bool KUrlNavigator::goBack() { const int count = d->m_history.count(); if (d->m_historyIndex < count - 1) { const KUrl newUrl = locationUrl(d->m_historyIndex + 1); emit urlAboutToBeChanged(newUrl); ++d->m_historyIndex; d->updateContent(); emit historyChanged(); emit urlChanged(locationUrl()); return true; } return false; } bool KUrlNavigator::goForward() { if (d->m_historyIndex > 0) { const KUrl newUrl = locationUrl(d->m_historyIndex - 1); emit urlAboutToBeChanged(newUrl); --d->m_historyIndex; d->updateContent(); emit historyChanged(); emit urlChanged(locationUrl()); return true; } return false; } bool KUrlNavigator::goUp() { const KUrl currentUrl = locationUrl(); const KUrl upUrl = currentUrl.upUrl(); if (upUrl != currentUrl) { setLocationUrl(upUrl); return true; } return false; } void KUrlNavigator::goHome() { if (d->m_homeUrl.isEmpty() || !d->m_homeUrl.isValid()) { setLocationUrl(KUrl(QDir::homePath())); } else { setLocationUrl(d->m_homeUrl); } } void KUrlNavigator::setHomeUrl(const KUrl& url) { d->m_homeUrl = url; } KUrl KUrlNavigator::homeUrl() const { return d->m_homeUrl; } void KUrlNavigator::setUrlEditable(bool editable) { if (d->m_editable != editable) { d->switchView(); } } bool KUrlNavigator::isUrlEditable() const { return d->m_editable; } void KUrlNavigator::setShowFullPath(bool show) { if (d->m_showFullPath != show) { d->m_showFullPath = show; d->updateContent(); } } bool KUrlNavigator::showFullPath() const { return d->m_showFullPath; } void KUrlNavigator::setActive(bool active) { if (active != d->m_active) { d->m_active = active; d->m_dropDownButton->setActive(active); foreach(KUrlNavigatorButton* button, d->m_navButtons) { button->setActive(active); } update(); if (active) { emit activated(); } } } bool KUrlNavigator::isActive() const { return d->m_active; } void KUrlNavigator::setPlacesSelectorVisible(bool visible) { if (visible == d->m_showPlacesSelector) { return; } if (visible && (d->m_placesSelector == 0)) { // the places selector cannot get visible as no // places model is available return; } d->m_showPlacesSelector = visible; d->m_placesSelector->setVisible(visible); } bool KUrlNavigator::isPlacesSelectorVisible() const { return d->m_showPlacesSelector; } KUrl KUrlNavigator::uncommittedUrl() const { KUriFilterData filteredData(d->m_pathBox->currentText().trimmed()); filteredData.setCheckForExecutables(false); if (KUriFilter::self()->filterUri(filteredData, QStringList() << "kshorturifilter" << "kurisearchfilter")) { return filteredData.uri(); } else { return KUrl(filteredData.typedString()); } } void KUrlNavigator::setLocationUrl(const KUrl& newUrl) { if (newUrl == locationUrl()) { return; } KUrl url = newUrl; url.cleanPath(); if ((url.scheme() == QLatin1String("tar")) || (url.scheme() == QLatin1String("zip"))) { // The URL represents a tar- or zip-file. Check whether // the URL is really part of the tar- or zip-file, otherwise // replace it by the local path again. bool insideCompressedPath = d->isCompressedPath(url); if (!insideCompressedPath) { KUrl prevUrl = url; KUrl parentUrl = url.upUrl(); while (parentUrl != prevUrl) { if (d->isCompressedPath(parentUrl)) { insideCompressedPath = true; break; } prevUrl = parentUrl; parentUrl = parentUrl.upUrl(); } } if (!insideCompressedPath) { // drop the tar: or zip: protocol since we are not // inside the compressed path url.setProtocol("file"); } } // Check whether current history element has the same URL. // If this is the case, just ignore setting the URL. const LocationData& data = d->m_history[d->m_historyIndex]; const bool isUrlEqual = url.equals(locationUrl(), KUrl::CompareWithoutTrailingSlash) || (!url.isValid() && url.equals(data.url, KUrl::CompareWithoutTrailingSlash)); if (isUrlEqual) { return; } emit urlAboutToBeChanged(url); if (d->m_historyIndex > 0) { // If an URL is set when the history index is not at the end (= 0), // then clear all previous history elements so that a new history // tree is started from the current position. QList::iterator begin = d->m_history.begin(); QList::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::iterator begin = d->m_history.begin() + historyMax; QList::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 0a5ea6a0bc..0ce9e27952 100644 --- a/kfile/kurlnavigatorbutton.cpp +++ b/kfile/kurlnavigatorbutton.cpp @@ -1,683 +1,682 @@ /***************************************************************************** * Copyright (C) 2006 by Peter Penz * * Copyright (C) 2006 by Aaron J. Seigo * * * * 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 #include #include #include #include #include #include #include #include namespace KDEPrivate { QPointer 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 protocols; if (protocols.isEmpty()) { protocols << "fish" << "ftp" << "nfs" << "sftp" << "smb" << "webdav"; } startTextResolving = !protocols.contains(m_url.scheme()); } if (startTextResolving) { m_pendingTextChange = true; KIO::StatJob* job = KIO::stat(m_url, KIO::HideProgressInfo); connect(job, SIGNAL(result(KJob*)), this, SLOT(statFinished(KJob*))); emit startedTextResolving(); } else { setText(m_url.fileName().replace('&', "&&")); } } KUrl KUrlNavigatorButton::url() const { return m_url; } void KUrlNavigatorButton::setText(const QString& text) { QString adjustedText = text; if (adjustedText.isEmpty()) { adjustedText = m_url.scheme(); } // Assure that the button always consists of one line adjustedText.remove(QLatin1Char('\n')); KUrlNavigatorButtonBase::setText(adjustedText); updateMinimumWidth(); // Assure that statFinished() does not overwrite a text that has been // set by a client of the URL navigator button m_pendingTextChange = false; } void KUrlNavigatorButton::setActiveSubDirectory(const QString& subDir) { m_subDir = subDir; // We use a different (bold) font on active, so the size hint changes updateGeometry(); update(); } QString KUrlNavigatorButton::activeSubDirectory() const { return m_subDir; } QSize KUrlNavigatorButton::sizeHint() const { QFont adjustedFont(font()); adjustedFont.setBold(m_subDir.isEmpty()); // the minimum size is textWidth + arrowWidth() + 2 * BorderWidth; for the // preferred size we add the BorderWidth 2 times again for having an uncluttered look const int width = QFontMetrics(adjustedFont).width(plainText()) + arrowWidth() + 4 * BorderWidth; return QSize(width, KUrlNavigatorButtonBase::sizeHint().height()); } void KUrlNavigatorButton::setShowMnemonic(bool show) { if (m_showMnemonic != show) { m_showMnemonic = show; update(); } } bool KUrlNavigatorButton::showMnemonic() const { return m_showMnemonic; } void KUrlNavigatorButton::paintEvent(QPaintEvent* event) { Q_UNUSED(event); QPainter painter(this); QFont adjustedFont(font()); adjustedFont.setBold(m_subDir.isEmpty()); painter.setFont(adjustedFont); int buttonWidth = width(); int preferredWidth = sizeHint().width(); if (preferredWidth < minimumWidth()) { preferredWidth = minimumWidth(); } if (buttonWidth > preferredWidth) { buttonWidth = preferredWidth; } const int buttonHeight = height(); const QColor fgColor = foregroundColor(); drawHoverBackground(&painter); int textLeft = 0; int textWidth = buttonWidth; const bool leftToRight = (layoutDirection() == Qt::LeftToRight); if (!m_subDir.isEmpty()) { // draw arrow const int arrowSize = arrowWidth(); const int arrowX = leftToRight ? (buttonWidth - arrowSize) - BorderWidth : BorderWidth; const int arrowY = (buttonHeight - arrowSize) / 2; QStyleOption option; option.initFrom(this); option.rect = QRect(arrowX, arrowY, arrowSize, arrowSize); option.palette = palette(); option.palette.setColor(QPalette::Text, fgColor); option.palette.setColor(QPalette::WindowText, fgColor); option.palette.setColor(QPalette::ButtonText, fgColor); if (m_hoverArrow) { // highlight the background of the arrow to indicate that the directories // popup can be opened by a mouse click QColor hoverColor = palette().color(QPalette::HighlightedText); hoverColor.setAlpha(96); painter.setPen(Qt::NoPen); painter.setBrush(hoverColor); int hoverX = arrowX; if (!leftToRight) { hoverX -= BorderWidth; } painter.drawRect(QRect(hoverX, 0, arrowSize + BorderWidth, buttonHeight)); } if (leftToRight) { style()->drawPrimitive(QStyle::PE_IndicatorArrowRight, &option, &painter, this); } else { style()->drawPrimitive(QStyle::PE_IndicatorArrowLeft, &option, &painter, this); textLeft += arrowSize + 2 * BorderWidth; } textWidth -= arrowSize + 2 * BorderWidth; } painter.setPen(fgColor); const bool clipped = isTextClipped(); const QRect textRect(textLeft, 0, textWidth, buttonHeight); if (clipped) { QColor bgColor = fgColor; bgColor.setAlpha(0); QLinearGradient gradient(textRect.topLeft(), textRect.topRight()); if (leftToRight) { gradient.setColorAt(0.8, fgColor); gradient.setColorAt(1.0, bgColor); } else { gradient.setColorAt(0.0, bgColor); gradient.setColorAt(0.2, fgColor); } QPen pen; pen.setBrush(QBrush(gradient)); painter.setPen(pen); } int textFlags = clipped ? Qt::AlignVCenter : Qt::AlignCenter; if (m_showMnemonic) { textFlags |= Qt::TextShowMnemonic; painter.drawText(textRect, textFlags, text()); } else { painter.drawText(textRect, textFlags, plainText()); } } void KUrlNavigatorButton::enterEvent(QEvent* event) { KUrlNavigatorButtonBase::enterEvent(event); // if the text is clipped due to a small window width, the text should // be shown as tooltip if (isTextClipped()) { setToolTip(plainText()); } } void KUrlNavigatorButton::leaveEvent(QEvent* event) { KUrlNavigatorButtonBase::leaveEvent(event); setToolTip(QString()); if (m_hoverArrow) { m_hoverArrow = false; update(); } } void KUrlNavigatorButton::keyPressEvent(QKeyEvent* event) { switch (event->key()) { case Qt::Key_Enter: case Qt::Key_Return: emit clicked(m_url, Qt::LeftButton); break; case Qt::Key_Down: case Qt::Key_Space: startSubDirsJob(); break; default: KUrlNavigatorButtonBase::keyPressEvent(event); } } void KUrlNavigatorButton::dropEvent(QDropEvent* event) { - const QList urls = KUrl::List::fromMimeData(event->mimeData()); - if (!urls.isEmpty()) { + if (event->mimeData()->hasUrls()) { 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(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& s1, const QPair& s2) { return KStringHandler::naturalCompare(s1.first, s2.first, Qt::CaseInsensitive) < 0; } void KUrlNavigatorButton::openSubDirsMenu(KJob* job) { Q_ASSERT(job == m_subDirsJob); m_subDirsJob = 0; if (job->error() || m_subDirs.isEmpty()) { // clear listing return; } qSort(m_subDirs.begin(), m_subDirs.end(), naturalLessThan); setDisplayHintEnabled(PopupActiveHint, true); update(); // ensure the button is drawn highlighted if (m_subDirsMenu != 0) { m_subDirsMenu->close(); m_subDirsMenu->deleteLater(); m_subDirsMenu = 0; } m_subDirsMenu = new KUrlNavigatorMenu(this); initMenu(m_subDirsMenu, 0); const bool leftToRight = (layoutDirection() == Qt::LeftToRight); const int popupX = leftToRight ? width() - arrowWidth() - BorderWidth : 0; const QPoint popupPos = parentWidget()->mapToGlobal(geometry().bottomLeft() + QPoint(popupX, 0)); const QAction* action = m_subDirsMenu->exec(popupPos); if (action != 0) { const int result = action->data().toInt(); KUrl url = m_url; url.addPath(m_subDirs[result].first); emit clicked(url, Qt::LeftButton); } m_subDirs.clear(); delete m_subDirsMenu; m_subDirsMenu = 0; setDisplayHintEnabled(PopupActiveHint, false); } void KUrlNavigatorButton::replaceButton(KJob* job) { Q_ASSERT(job == m_subDirsJob); m_subDirsJob = 0; m_replaceButton = false; if (job->error() || m_subDirs.isEmpty()) { return; } qSort(m_subDirs.begin(), m_subDirs.end(), naturalLessThan); // Get index of the directory that is shown currently in the button const QString currentDir = m_url.fileName(); int currentIndex = 0; const int subDirsCount = m_subDirs.count(); while (currentIndex < subDirsCount) { if (m_subDirs[currentIndex].first == currentDir) { break; } ++currentIndex; } // Adjust the index by respecting the wheel steps and // trigger a replacing of the button content int targetIndex = currentIndex - m_wheelSteps; if (targetIndex < 0) { targetIndex = 0; } else if (targetIndex >= subDirsCount) { targetIndex = subDirsCount - 1; } KUrl url = m_url.upUrl(); url.addPath(m_subDirs[targetIndex].first); emit clicked(url, Qt::LeftButton); m_subDirs.clear(); } void KUrlNavigatorButton::cancelSubDirsRequest() { m_openSubDirsTimer->stop(); if (m_subDirsJob != 0) { m_subDirsJob->kill(); m_subDirsJob = 0; } } QString KUrlNavigatorButton::plainText() const { // Replace all "&&" by '&' and remove all single // '&' characters const QString source = text(); const int sourceLength = source.length(); QString dest; dest.reserve(sourceLength); int sourceIndex = 0; int destIndex = 0; while (sourceIndex < sourceLength) { if (source.at(sourceIndex) == QLatin1Char('&')) { ++sourceIndex; if (sourceIndex >= sourceLength) { break; } } dest[destIndex] = source.at(sourceIndex); ++sourceIndex; ++destIndex; } return dest; } int KUrlNavigatorButton::arrowWidth() const { // if there isn't arrow then return 0 int width = 0; if (!m_subDir.isEmpty()) { width = height() / 2; if (width < 4) { width = 4; } } return width; } bool KUrlNavigatorButton::isAboveArrow(int x) const { const bool leftToRight = (layoutDirection() == Qt::LeftToRight); return leftToRight ? (x >= width() - arrowWidth()) : (x < arrowWidth()); } bool KUrlNavigatorButton::isTextClipped() const { int availableWidth = width() - 2 * BorderWidth; if (!m_subDir.isEmpty()) { availableWidth -= arrowWidth() - BorderWidth; } QFont adjustedFont(font()); adjustedFont.setBold(m_subDir.isEmpty()); return QFontMetrics(adjustedFont).width(plainText()) >= availableWidth; } void KUrlNavigatorButton::updateMinimumWidth() { const int oldMinWidth = minimumWidth(); int minWidth = sizeHint().width(); if (minWidth < 40) { minWidth = 40; } else if (minWidth > 150) { // don't let an overlong path name waste all the URL navigator space minWidth = 150; } if (oldMinWidth != minWidth) { setMinimumWidth(minWidth); } } void KUrlNavigatorButton::initMenu(KUrlNavigatorMenu* menu, int startIndex) { connect(menu, SIGNAL(middleMouseButtonClicked(QAction*)), this, SLOT(slotMenuActionClicked(QAction*))); connect(menu, SIGNAL(urlsDropped(QAction*, QDropEvent*)), this, SLOT(urlsDropped(QAction*, QDropEvent*))); menu->setLayoutDirection(Qt::LeftToRight); const int maxIndex = startIndex + 30; // Don't show more than 30 items in a menu const int lastIndex = qMin(m_subDirs.count() - 1, maxIndex); for (int i = startIndex; i <= lastIndex; ++i) { const QString subDirName = m_subDirs[i].first; const QString subDirDisplayName = m_subDirs[i].second; QString text = KStringHandler::csqueeze(subDirDisplayName, 60); text.replace(QLatin1Char('&'), QLatin1String("&&")); QAction* action = new QAction(text, this); if (m_subDir == subDirName) { QFont font(action->font()); font.setBold(true); action->setFont(font); } action->setData(i); menu->addAction(action); } if (m_subDirs.count() > maxIndex) { // If too much items are shown, move them into a sub menu menu->addSeparator(); KUrlNavigatorMenu* subDirsMenu = new KUrlNavigatorMenu(menu); subDirsMenu->setTitle(i18nc("@action:inmenu", "More")); initMenu(subDirsMenu, maxIndex); menu->addMenu(subDirsMenu); } } } // namespace KDEPrivate #include "moc_kurlnavigatorbutton_p.cpp" diff --git a/kfile/kurlnavigatorplacesselector.cpp b/kfile/kurlnavigatorplacesselector.cpp index d6747bd80f..5bd78c4fcc 100644 --- a/kfile/kurlnavigatorplacesselector.cpp +++ b/kfile/kurlnavigatorplacesselector.cpp @@ -1,242 +1,240 @@ /*************************************************************************** * Copyright (C) 2006 by Peter Penz (peter.penz@gmx.at) * * Copyright (C) 2007 by Kevin Ottens (ervin@kde.org) * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with this library; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #include "kurlnavigatorplacesselector_p.h" #include #include #include #include #include #include +#include #include #include #include #include #include #include namespace KDEPrivate { KUrlNavigatorPlacesSelector::KUrlNavigatorPlacesSelector(QWidget* parent, KFilePlacesModel* placesModel) : KUrlNavigatorButtonBase(parent), m_selectedItem(-1), m_placesModel(placesModel) { setFocusPolicy(Qt::NoFocus); m_placesMenu = new KMenu(this); updateMenu(); connect(m_placesModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)), this, SLOT(updateMenu())); connect(m_placesModel, SIGNAL(rowsRemoved(const QModelIndex&, int, int)), this, SLOT(updateMenu())); connect(m_placesModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), this, SLOT(updateMenu())); connect(m_placesMenu, SIGNAL(triggered(QAction*)), this, SLOT(activatePlace(QAction*))); setMenu(m_placesMenu); setAcceptDrops(true); } KUrlNavigatorPlacesSelector::~KUrlNavigatorPlacesSelector() { } void KUrlNavigatorPlacesSelector::updateMenu() { m_placesMenu->clear(); updateSelection(m_selectedUrl); const int rowCount = m_placesModel->rowCount(); for (int i = 0; i < rowCount; ++i) { QModelIndex index = m_placesModel->index(i, 0); QAction* action = new QAction(m_placesModel->icon(index), m_placesModel->text(index), m_placesMenu); m_placesMenu->addAction(action); action->setData(i); if (i == m_selectedItem) { setIcon(m_placesModel->icon(index)); } } updateTeardownAction(); } void KUrlNavigatorPlacesSelector::updateTeardownAction() { const int rowCount = m_placesModel->rowCount(); if (m_placesMenu->actions().size()==rowCount+2) { // remove teardown action QAction *action = m_placesMenu->actions().at(rowCount+1); m_placesMenu->removeAction(action); delete action; // remove separator action = m_placesMenu->actions().at(rowCount); m_placesMenu->removeAction(action); delete action; } const QModelIndex index = m_placesModel->index(m_selectedItem, 0); QAction *teardown = m_placesModel->teardownActionForIndex(index); if (teardown!=0) { teardown->setParent(m_placesMenu); teardown->setData("teardownAction"); m_placesMenu->addSeparator(); m_placesMenu->addAction(teardown); } } void KUrlNavigatorPlacesSelector::updateSelection(const KUrl& url) { const QModelIndex index = m_placesModel->closestItem(url); if (index.isValid()) { m_selectedItem = index.row(); m_selectedUrl = url; setIcon(m_placesModel->icon(index)); } else { m_selectedItem = -1; // No bookmark has been found which matches to the given Url. Show // a generic folder icon as pixmap for indication: setIcon(KIcon("folder")); } updateTeardownAction(); } KUrl KUrlNavigatorPlacesSelector::selectedPlaceUrl() const { const QModelIndex index = m_placesModel->index(m_selectedItem, 0); return index.isValid() ? m_placesModel->url(index) : KUrl(); } QString KUrlNavigatorPlacesSelector::selectedPlaceText() const { const QModelIndex index = m_placesModel->index(m_selectedItem, 0); return index.isValid() ? m_placesModel->text(index) : QString(); } QSize KUrlNavigatorPlacesSelector::sizeHint() const { const int height = KUrlNavigatorButtonBase::sizeHint().height(); return QSize(height, height); } void KUrlNavigatorPlacesSelector::paintEvent(QPaintEvent* event) { Q_UNUSED(event); QPainter painter(this); drawHoverBackground(&painter); // draw icon const QPixmap pixmap = icon().pixmap(QSize(22, 22), QIcon::Normal); const int x = (width() - pixmap.width()) / 2; const int y = (height() - pixmap.height()) / 2; painter.drawPixmap(x, y, pixmap); } void KUrlNavigatorPlacesSelector::dragEnterEvent(QDragEnterEvent* event) { if (event->mimeData()->hasUrls()) { setDisplayHintEnabled(DraggedHint, true); event->acceptProposedAction(); update(); } } void KUrlNavigatorPlacesSelector::dragLeaveEvent(QDragLeaveEvent* event) { KUrlNavigatorButtonBase::dragLeaveEvent(event); setDisplayHintEnabled(DraggedHint, false); update(); } void KUrlNavigatorPlacesSelector::dropEvent(QDropEvent* event) { setDisplayHintEnabled(DraggedHint, false); update(); - const QList urlList = KUrl::List::fromMimeData(event->mimeData()); - if (urlList.isEmpty()) { - return; - } + const QList urlList = KUrlMimeData::urlsFromMimeData(event->mimeData()); foreach(const KUrl &url, urlList) { KMimeType::Ptr mimetype = KMimeType::findByUrl(url); if (mimetype->is("inode/directory")) { m_placesModel->addPlace(url.fileName(), url); } } } void KUrlNavigatorPlacesSelector::activatePlace(QAction* action) { Q_ASSERT(action != 0); if (action->data().toString()=="teardownAction") { QModelIndex index = m_placesModel->index(m_selectedItem, 0); m_placesModel->requestTeardown(index); return; } QModelIndex index = m_placesModel->index(action->data().toInt(), 0); m_lastClickedIndex = QPersistentModelIndex(); if (m_placesModel->setupNeeded(index)) { connect(m_placesModel, SIGNAL(setupDone(const QModelIndex &, bool)), this, SLOT(onStorageSetupDone(const QModelIndex &, bool))); m_lastClickedIndex = index; m_placesModel->requestSetup(index); return; } else if (index.isValid()) { m_selectedItem = index.row(); setIcon(m_placesModel->icon(index)); updateTeardownAction(); emit placeActivated(m_placesModel->url(index)); } } void KUrlNavigatorPlacesSelector::onStorageSetupDone(const QModelIndex &index, bool success) { if (m_lastClickedIndex == index) { if (success) { m_selectedItem = index.row(); setIcon(m_placesModel->icon(index)); updateTeardownAction(); emit placeActivated(m_placesModel->url(index)); } m_lastClickedIndex = QPersistentModelIndex(); } } } // namespace KDEPrivate #include "moc_kurlnavigatorplacesselector_p.cpp" diff --git a/kio/bookmarks/kbookmark.cc b/kio/bookmarks/kbookmark.cc index f384c15c89..c8986765dc 100644 --- a/kio/bookmarks/kbookmark.cc +++ b/kio/bookmarks/kbookmark.cc @@ -1,745 +1,738 @@ // -*- c-basic-offset:4; indent-tabs-mode:nil -*- // vim: set ts=4 sts=4 sw=4 et: /* This file is part of the KDE libraries Copyright (C) 2000 David Faure Copyright (C) 2003 Alexander Kellett Copyright (C) 2008 Norbert Frese This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kbookmark.h" #include #include #include #include #include #include -#include +#include #include #include #include #define METADATA_KDE_OWNER "http://www.kde.org" #define METADATA_FREEDESKTOP_OWNER "http://freedesktop.org" #define METADATA_MIME_OWNER "http://www.freedesktop.org/standards/shared-mime-info" ////// utility functions static QDomNode cd(QDomNode node, const QString &name, bool create) { QDomNode subnode = node.namedItem(name); if (create && subnode.isNull()) { subnode = node.ownerDocument().createElement(name); node.appendChild(subnode); } return subnode; } static QDomNode cd_or_create(QDomNode node, const QString &name) { return cd(node, name, true); } static QDomText get_or_create_text(QDomNode node) { QDomNode subnode = node.firstChild(); if (subnode.isNull()) { subnode = node.ownerDocument().createTextNode(""); node.appendChild(subnode); } return subnode.toText(); } static QDomNode findMetadata(const QString & forOwner, QDomNode& parent, bool create) { bool forOwnerIsKDE = forOwner == METADATA_KDE_OWNER; QDomElement metadataElement; for ( QDomNode _node = parent.firstChild(); !_node.isNull(); _node = _node.nextSibling() ) { QDomElement elem = _node.toElement(); if ( !elem.isNull() && elem.tagName() == "metadata" ) { const QString owner = elem.attribute( "owner" ); if ( owner == forOwner ) return elem; if ( owner.isEmpty() && forOwnerIsKDE ) metadataElement = elem; } } if ( create && metadataElement.isNull() ) { metadataElement = parent.ownerDocument().createElement( "metadata" ); parent.appendChild(metadataElement); metadataElement.setAttribute( "owner", forOwner ); } else if (!metadataElement.isNull() && forOwnerIsKDE) { // i'm not sure if this is good, we shouln't take over foreign metatdata metadataElement.setAttribute( "owner", METADATA_KDE_OWNER ); } return metadataElement; } ////// KBookmarkGroup::KBookmarkGroup() : KBookmark( QDomElement() ) { } KBookmarkGroup::KBookmarkGroup( const QDomElement &elem ) : KBookmark(elem) { } bool KBookmarkGroup::isOpen() const { return element.attribute("folded") == "no"; // default is: folded } KBookmark KBookmarkGroup::first() const { return KBookmark( nextKnownTag( element.firstChildElement(), true ) ); } KBookmark KBookmarkGroup::previous( const KBookmark & current ) const { return KBookmark( nextKnownTag( current.element.previousSiblingElement(), false ) ); } KBookmark KBookmarkGroup::next( const KBookmark & current ) const { return KBookmark( nextKnownTag( current.element.nextSiblingElement(), true ) ); } int KBookmarkGroup::indexOf(const KBookmark& child) const { uint counter = 0; for ( KBookmark bk = first(); !bk.isNull(); bk = next(bk), ++counter ) { if ( bk.element == child.element ) return counter; } return -1; } QDomElement KBookmarkGroup::nextKnownTag( const QDomElement &start, bool goNext ) const { static const QString & bookmark = KGlobal::staticQString("bookmark"); static const QString & folder = KGlobal::staticQString("folder"); static const QString & separator = KGlobal::staticQString("separator"); for( QDomElement elem = start; !elem.isNull(); ) { QString tag = elem.tagName(); if (tag == folder || tag == bookmark || tag == separator) return elem; if (goNext) elem = elem.nextSiblingElement(); else elem = elem.previousSiblingElement(); } return QDomElement(); } KBookmarkGroup KBookmarkGroup::createNewFolder( const QString & text ) { if (isNull()) return KBookmarkGroup(); QDomDocument doc = element.ownerDocument(); QDomElement groupElem = doc.createElement( "folder" ); element.appendChild( groupElem ); QDomElement textElem = doc.createElement( "title" ); groupElem.appendChild( textElem ); textElem.appendChild( doc.createTextNode( text ) ); return KBookmarkGroup(groupElem); } KBookmark KBookmarkGroup::createNewSeparator() { if (isNull()) return KBookmark(); QDomDocument doc = element.ownerDocument(); Q_ASSERT(!doc.isNull()); QDomElement sepElem = doc.createElement( "separator" ); element.appendChild( sepElem ); return KBookmark(sepElem); } #ifndef KDE_NO_DEPRECATED bool KBookmarkGroup::moveItem( const KBookmark & bookmark, const KBookmark & after ) { return moveBookmark(bookmark, after); } #endif bool KBookmarkGroup::moveBookmark( const KBookmark & item, const KBookmark & after ) { QDomNode n; if ( !after.isNull() ) n = element.insertAfter( item.element, after.element ); else // first child { if ( element.firstChild().isNull() ) // Empty element -> set as real first child n = element.insertBefore( item.element, QDomElement() ); // we have to skip everything up to the first valid child QDomElement firstChild = nextKnownTag(element.firstChild().toElement(), true); if ( !firstChild.isNull() ) n = element.insertBefore( item.element, firstChild ); else { // No real first child -> append after the etc. n = element.appendChild( item.element ); } } return (!n.isNull()); } KBookmark KBookmarkGroup::addBookmark( const KBookmark &bm ) { element.appendChild( bm.internalElement() ); return bm; } KBookmark KBookmarkGroup::addBookmark( const QString & text, const KUrl & url, const QString & icon ) { if (isNull()) return KBookmark(); QDomDocument doc = element.ownerDocument(); QDomElement elem = doc.createElement( "bookmark" ); elem.setAttribute( "href", url.url() ); // gives us utf8 QDomElement textElem = doc.createElement( "title" ); elem.appendChild( textElem ); textElem.appendChild( doc.createTextNode( text ) ); KBookmark newBookmark = addBookmark( KBookmark( elem ) ); // as icons are moved to metadata, we have to use the KBookmark API for this newBookmark.setIcon(icon.isEmpty() ? KMimeType::iconNameForUrl( url ) : icon ); return newBookmark; } void KBookmarkGroup::deleteBookmark( const KBookmark &bk ) { element.removeChild( bk.element ); } bool KBookmarkGroup::isToolbarGroup() const { return ( element.attribute("toolbar") == "yes" ); } QDomElement KBookmarkGroup::findToolbar() const { if ( element.attribute("toolbar") == "yes" ) return element; for (QDomNode n = element.firstChild(); !n.isNull() ; n = n.nextSibling() ) { QDomElement e = n.toElement(); // Search among the "folder" children only if ( e.tagName() == "folder" ) { if ( e.attribute("toolbar") == "yes" ) return e; else { QDomElement result = KBookmarkGroup(e).findToolbar(); if (!result.isNull()) return result; } } } return QDomElement(); } QList<KUrl> KBookmarkGroup::groupUrlList() const { QList<KUrl> urlList; for ( KBookmark bm = first(); !bm.isNull(); bm = next(bm) ) { if ( bm.isSeparator() || bm.isGroup() ) continue; urlList << bm.url(); } return urlList; } ////// KBookmark::KBookmark() { } KBookmark::KBookmark( const QDomElement &elem ) : element(elem) { } bool KBookmark::isGroup() const { QString tag = element.tagName(); return ( tag == "folder" || tag == "xbel" ); // don't forget the toplevel group } bool KBookmark::isSeparator() const { return (element.tagName() == "separator"); } bool KBookmark::isNull() const { return element.isNull(); } bool KBookmark::hasParent() const { QDomElement parent = element.parentNode().toElement(); return !parent.isNull(); } QString KBookmark::text() const { return KStringHandler::csqueeze( fullText() ); } QString KBookmark::fullText() const { if (isSeparator()) return i18n("--- separator ---"); QString text = element.namedItem("title").toElement().text(); text.replace('\n', ' '); // #140673 return text; } void KBookmark::setFullText(const QString &fullText) { QDomNode titleNode = element.namedItem("title"); if (titleNode.isNull()) { titleNode = element.ownerDocument().createElement("title"); element.appendChild(titleNode); } if (titleNode.firstChild().isNull()) { QDomText domtext = titleNode.ownerDocument().createTextNode(""); titleNode.appendChild(domtext); } QDomText domtext = titleNode.firstChild().toText(); domtext.setData(fullText); } KUrl KBookmark::url() const { return KUrl(element.attribute("href").toAscii()); // Decodes it from utf8 } void KBookmark::setUrl(const KUrl &url) { element.setAttribute("href", url.url()); } QString KBookmark::icon() const { QDomNode metaDataNode = metaData(METADATA_FREEDESKTOP_OWNER, false); QDomElement iconElement = cd(metaDataNode, "bookmark:icon", false).toElement(); QString icon = iconElement.attribute("name"); // migration code if (icon.isEmpty()) icon = element.attribute("icon"); if (icon == "www") // common icon for kde3 bookmarks return "internet-web-browser"; // end migration code if (icon == "bookmark_folder") { return "folder-bookmarks"; } if (icon.isEmpty()) { // Default icon depends on URL for bookmarks, and is default directory // icon for groups. if (isGroup()) { icon = "folder-bookmarks"; } else { if (isSeparator()) { icon = "edit-clear"; // whatever } else { // get icon from mimeType QString _mimeType = mimeType(); if (!_mimeType.isEmpty()) { KMimeType::Ptr mime = KMimeType::mimeType(_mimeType, KMimeType::ResolveAliases); if (mime) { return mime->iconName(); } } // get icon from URL icon = KMimeType::iconNameForUrl(url()); } } } return icon; } void KBookmark::setIcon(const QString &icon) { QDomNode metaDataNode = metaData(METADATA_FREEDESKTOP_OWNER, true); QDomElement iconElement = cd_or_create(metaDataNode, "bookmark:icon").toElement(); iconElement.setAttribute ( "name", icon ); // migration code if(!element.attribute("icon").isEmpty()) element.removeAttribute("icon"); } QString KBookmark::description() const { if (isSeparator()) return QString(); QString description = element.namedItem("desc").toElement().text(); description.replace('\n', ' '); // #140673 return description; } void KBookmark::setDescription(const QString &description) { QDomNode descNode = element.namedItem("desc"); if (descNode.isNull()) { descNode = element.ownerDocument().createElement("desc"); element.appendChild(descNode); } if (descNode.firstChild().isNull()) { QDomText domtext = descNode.ownerDocument().createTextNode(QString()); descNode.appendChild(domtext); } QDomText domtext = descNode.firstChild().toText(); domtext.setData(description); } QString KBookmark::mimeType() const { QDomNode metaDataNode = metaData(METADATA_MIME_OWNER, false); QDomElement mimeTypeElement = cd(metaDataNode, "mime:mime-type", false).toElement(); return mimeTypeElement.attribute("type"); } void KBookmark::setMimeType(const QString &mimeType) { QDomNode metaDataNode = metaData(METADATA_MIME_OWNER, true); QDomElement iconElement = cd_or_create(metaDataNode, "mime:mime-type").toElement(); iconElement.setAttribute ( "type", mimeType ); } bool KBookmark::showInToolbar() const { if(element.hasAttribute("showintoolbar")) { bool show = element.attribute("showintoolbar") == "yes"; const_cast<QDomElement *>(&element)->removeAttribute("showintoolbar"); const_cast<KBookmark *>(this)->setShowInToolbar(show); } return metaDataItem("showintoolbar") == "yes"; } void KBookmark::setShowInToolbar(bool show) { setMetaDataItem("showintoolbar", show ? "yes" : "no"); } KBookmarkGroup KBookmark::parentGroup() const { return KBookmarkGroup( element.parentNode().toElement() ); } KBookmarkGroup KBookmark::toGroup() const { Q_ASSERT( isGroup() ); return KBookmarkGroup(element); } QString KBookmark::address() const { if ( element.tagName() == "xbel" ) return ""; // not QString() ! else { // Use keditbookmarks's DEBUG_ADDRESSES flag to debug this code :) if (element.parentNode().isNull()) { Q_ASSERT(false); return "ERROR"; // Avoid an infinite loop } KBookmarkGroup group = parentGroup(); QString parentAddress = group.address(); int pos = group.indexOf(*this); Q_ASSERT(pos != -1); return parentAddress + '/' + QString::number(pos); } } int KBookmark::positionInParent() const { return parentGroup().indexOf(*this); } QDomElement KBookmark::internalElement() const { return element; } KBookmark KBookmark::standaloneBookmark( const QString & text, const KUrl & url, const QString & icon ) { QDomDocument doc("xbel"); QDomElement elem = doc.createElement("xbel"); doc.appendChild( elem ); KBookmarkGroup grp( elem ); grp.addBookmark( text, url, icon ); return grp.first(); } QString KBookmark::commonParent(const QString &first, const QString &second) { QString A = first; QString B = second; QString error("ERROR"); if(A == error || B == error) return error; A += '/'; B += '/'; uint lastCommonSlash = 0; uint lastPos = A.length() < B.length() ? A.length() : B.length(); for(uint i=0; i < lastPos; ++i) { if(A[i] != B[i]) return A.left(lastCommonSlash); if(A[i] == '/') lastCommonSlash = i; } return A.left(lastCommonSlash); } void KBookmark::updateAccessMetadata() { kDebug(7043) << "KBookmark::updateAccessMetadata " << address() << " " << url().prettyUrl(); const uint timet = QDateTime::currentDateTime().toTime_t(); setMetaDataItem( "time_added", QString::number( timet ), DontOverwriteMetaData ); setMetaDataItem( "time_visited", QString::number( timet ) ); QString countStr = metaDataItem( "visit_count" ); // TODO use spec'ed name bool ok; int currentCount = countStr.toInt(&ok); if (!ok) currentCount = 0; currentCount++; setMetaDataItem( "visit_count", QString::number( currentCount ) ); // TODO - for 4.0 - time_modified } QString KBookmark::parentAddress( const QString & address ) { return address.left( address.lastIndexOf(QLatin1Char('/')) ); } uint KBookmark::positionInParent( const QString & address ) { return address.mid( address.lastIndexOf(QLatin1Char('/')) + 1 ).toInt(); } QString KBookmark::previousAddress( const QString & address ) { uint pp = positionInParent(address); return pp>0 ? parentAddress(address) + QLatin1Char('/') + QString::number(pp-1) : QString(); } QString KBookmark::nextAddress( const QString & address ) { return parentAddress(address) + QLatin1Char('/') + QString::number(positionInParent(address)+1); } QDomNode KBookmark::metaData(const QString &owner, bool create) const { QDomNode infoNode = cd( internalElement(), "info", create); if (infoNode.isNull()) return QDomNode(); return findMetadata(owner, infoNode , create); } QString KBookmark::metaDataItem( const QString &key ) const { QDomNode metaDataNode = metaData(METADATA_KDE_OWNER, false); for ( QDomElement e = metaDataNode.firstChildElement(); !e.isNull(); e = e.nextSiblingElement() ) { if ( e.tagName() == key ) { return e.text(); } } return QString(); } void KBookmark::setMetaDataItem( const QString &key, const QString &value, MetaDataOverwriteMode mode ) { QDomNode metaDataNode = metaData(METADATA_KDE_OWNER, true); QDomNode item = cd_or_create( metaDataNode, key ); QDomText text = get_or_create_text( item ); if ( mode == DontOverwriteMetaData && !text.data().isEmpty() ) { return; } text.setData( value ); } bool KBookmark::operator==(const KBookmark& rhs) const { return element == rhs.element; } //// KBookmarkGroupTraverser::~KBookmarkGroupTraverser() { } void KBookmarkGroupTraverser::traverse(const KBookmarkGroup &root) { QStack<KBookmarkGroup> stack; stack.push(root); KBookmark bk = root.first(); for(;;) { if(bk.isNull()) { if(stack.count() == 1) // only root is on the stack return; if(stack.count() > 0) { visitLeave(stack.top()); bk = stack.pop(); } bk = stack.top().next(bk); } else if(bk.isGroup()) { KBookmarkGroup gp = bk.toGroup(); visitEnter(gp); bk = gp.first(); stack.push(gp); } else { visit(bk); bk = stack.top().next(bk); } } } void KBookmarkGroupTraverser::visit(const KBookmark &) { } void KBookmarkGroupTraverser::visitEnter(const KBookmarkGroup &) { } void KBookmarkGroupTraverser::visitLeave(const KBookmarkGroup &) { } void KBookmark::populateMimeData( QMimeData* mimeData ) const { KBookmark::List bookmarkList; bookmarkList.append( *this ); bookmarkList.populateMimeData( mimeData ); } KBookmark::List::List() : QList<KBookmark>() { } void KBookmark::List::populateMimeData( QMimeData* mimeData ) const { KUrl::List urls; QDomDocument doc( "xbel" ); QDomElement elem = doc.createElement( "xbel" ); doc.appendChild( elem ); for ( const_iterator it = begin(), end = this->end() ; it != end ; ++it ) { urls.append( (*it).url() ); elem.appendChild( (*it).internalElement().cloneNode( true /* deep */ ) ); } // This sets text/uri-list and text/plain into the mimedata - urls.populateMimeData( mimeData, KUrl::MetaDataMap() ); + mimeData->setUrls(urls); mimeData->setData( "application/x-xbel", doc.toByteArray() ); } bool KBookmark::List::canDecode( const QMimeData *mimeData ) { - return mimeData->hasFormat( "application/x-xbel" ) || KUrl::List::canDecode(mimeData); + return mimeData->hasFormat( "application/x-xbel" ) || mimeData->hasUrls(); } QStringList KBookmark::List::mimeDataTypes() { - return QStringList()<<("application/x-xbel")<<KUrl::List::mimeDataTypes(); + return QStringList() << ("application/x-xbel") << KUrlMimeData::mimeDataTypes(); } #ifndef KDE_NO_DEPRECATED KBookmark::List KBookmark::List::fromMimeData( const QMimeData *mimeData ) { QDomDocument doc; kWarning(7043) << "Deprecated method called, with wrong lifetime of QDomDocument, will probably crash"; return fromMimeData(mimeData, doc); } #endif KBookmark::List KBookmark::List::fromMimeData( const QMimeData *mimeData, QDomDocument& doc ) { KBookmark::List bookmarks; QByteArray payload = mimeData->data( "application/x-xbel" ); if ( !payload.isEmpty() ) { doc.setContent( payload ); QDomElement elem = doc.documentElement(); const QDomNodeList children = elem.childNodes(); for ( int childno = 0; childno < children.count(); childno++) { bookmarks.append( KBookmark( children.item(childno).toElement() )); } return bookmarks; } - const QList<KUrl> urls = KUrl::List::fromMimeData( mimeData ); - if ( !urls.isEmpty() ) - { - QList<KUrl>::ConstIterator uit = urls.begin(); - QList<KUrl>::ConstIterator uEnd = urls.end(); - for ( ; uit != uEnd ; ++uit ) - { - //kDebug(7043) << "url=" << (*uit); - bookmarks.append( KBookmark::standaloneBookmark( - (*uit).prettyUrl(), (*uit) )); - } + const QList<QUrl> urls = KUrlMimeData::urlsFromMimeData(mimeData); + for (int i = 0; i < urls.size(); ++i) { + const QUrl url = urls.at(i); + bookmarks.append(KBookmark::standaloneBookmark(url.toString(), url)); // Qt5 TODO: toDisplayString() } return bookmarks; } diff --git a/kio/kfile/kurlrequester.cpp b/kio/kfile/kurlrequester.cpp index dc1aa6ae6c..7743925f13 100644 --- a/kio/kfile/kurlrequester.cpp +++ b/kio/kfile/kurlrequester.cpp @@ -1,531 +1,531 @@ /* This file is part of the KDE libraries Copyright (C) 1999,2000,2001 Carsten Pfeiffer <pfeiffer@kde.org> 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 "kurlrequester.h" #include <kcombobox.h> #include <kfiledialog.h> #include <klineedit.h> #include <klocale.h> #include <kurlcompletion.h> #include <kprotocolmanager.h> #include <khbox.h> #include <kstandardshortcut.h> #include <kdebug.h> #include <QEvent> #include <QDrag> #include <QMimeData> #include <QAction> #include <QApplication> class KUrlDragPushButton : public KPushButton { public: KUrlDragPushButton( QWidget *parent) : KPushButton( parent) { setDragEnabled( true ); } ~KUrlDragPushButton() {} - void setURL( const KUrl& url ) + void setURL(const QUrl& url) { m_urls.clear(); - m_urls.append( url ); + m_urls.append(url); } protected: virtual QDrag *dragObject() { if (m_urls.isEmpty()) return 0; QDrag *drag = new QDrag(this); QMimeData *mimeData = new QMimeData; - m_urls.populateMimeData(mimeData); + mimeData->setUrls(m_urls); drag->setMimeData(mimeData); return drag; } private: - KUrl::List m_urls; + QList<QUrl> m_urls; }; class KUrlRequester::KUrlRequesterPrivate { public: KUrlRequesterPrivate(KUrlRequester *parent) : m_parent(parent), edit(0), combo(0), fileDialogMode(KFile::File | KFile::ExistingOnly | KFile::LocalOnly) { } ~KUrlRequesterPrivate() { delete myCompletion; delete myFileDialog; } void init(); void setText( const QString& text ) { if ( combo ) { if (combo->isEditable()) { combo->setEditText( text ); } else { int i = combo->findText( text ); if ( i == -1 ) { combo->addItem( text ); combo->setCurrentIndex( combo->count()-1 ); } else { combo->setCurrentIndex( i ); } } } else { edit->setText( text ); } } void connectSignals( QObject *receiver ) { QObject *sender; if ( combo ) sender = combo; else sender = edit; if (combo ) connect( sender, SIGNAL( editTextChanged( const QString& )), receiver, SIGNAL( textChanged( const QString& ))); else connect( sender, SIGNAL( textChanged( const QString& )), receiver, SIGNAL( textChanged( const QString& ))); connect( sender, SIGNAL( returnPressed() ), receiver, SIGNAL( returnPressed() )); connect( sender, SIGNAL( returnPressed( const QString& ) ), receiver, SIGNAL( returnPressed( const QString& ) )); } void setCompletionObject( KCompletion *comp ) { if ( combo ) combo->setCompletionObject( comp ); else edit->setCompletionObject( comp ); } QString text() const { return combo ? combo->currentText() : edit->text(); } /** * replaces ~user or $FOO, if necessary */ KUrl url() const { const QString txt = text(); KUrlCompletion *comp; if ( combo ) comp = qobject_cast<KUrlCompletion*>(combo->completionObject()); else comp = qobject_cast<KUrlCompletion*>(edit->completionObject()); if ( comp ) return KUrl( comp->replacedPath( txt ) ); else return KUrl( txt ); } // slots void _k_slotUpdateUrl(); void _k_slotOpenDialog(); void _k_slotFileDialogFinished(); KUrl m_startDir; KUrlRequester *m_parent; KLineEdit *edit; KComboBox *combo; KFile::Modes fileDialogMode; QString fileDialogFilter; #ifndef KDE_NO_DEPRECATED KEditListBox::CustomEditor editor; #else KEditListWidget::CustomEditor editor; #endif KUrlDragPushButton *myButton; KFileDialog *myFileDialog; KUrlCompletion *myCompletion; Qt::WindowModality fileDialogModality; }; KUrlRequester::KUrlRequester( QWidget *editWidget, QWidget *parent) : KHBox( parent),d(new KUrlRequesterPrivate(this)) { // must have this as parent editWidget->setParent( this ); d->combo = qobject_cast<KComboBox*>( editWidget ); d->edit = qobject_cast<KLineEdit*>( editWidget ); if ( d->edit ) { d->edit->setClearButtonShown( true ); } d->init(); } KUrlRequester::KUrlRequester( QWidget *parent) : KHBox( parent),d(new KUrlRequesterPrivate(this)) { d->init(); } KUrlRequester::KUrlRequester( const KUrl& url, QWidget *parent) : KHBox( parent),d(new KUrlRequesterPrivate(this)) { d->init(); setUrl( url ); } KUrlRequester::~KUrlRequester() { delete d; } void KUrlRequester::KUrlRequesterPrivate::init() { m_parent->setMargin(0); m_parent->setSpacing(-1); // use default spacing myFileDialog = 0L; fileDialogModality = Qt::ApplicationModal; if ( !combo && !edit ) { edit = new KLineEdit( m_parent ); edit->setClearButtonShown( true ); } QWidget *widget = combo ? (QWidget*) combo : (QWidget*) edit; myButton = new KUrlDragPushButton(m_parent); myButton->setIcon(KIcon("document-open")); int buttonSize = widget->sizeHint().height(); myButton->setFixedSize(buttonSize, buttonSize); myButton->setToolTip(i18n("Open file dialog")); m_parent->connect(myButton, SIGNAL(pressed()), SLOT(_k_slotUpdateUrl())); widget->installEventFilter( m_parent ); m_parent->setFocusProxy( widget ); m_parent->setFocusPolicy(Qt::StrongFocus); connectSignals( m_parent ); m_parent->connect(myButton, SIGNAL(clicked()), m_parent, SLOT(_k_slotOpenDialog())); myCompletion = new KUrlCompletion(); setCompletionObject( myCompletion ); QAction* openAction = new QAction(m_parent); openAction->setShortcut(KStandardShortcut::Open); m_parent->connect(openAction, SIGNAL(triggered(bool)), SLOT( _k_slotOpenDialog() )); } void KUrlRequester::setUrl( const KUrl& url ) { d->setText( url.pathOrUrl() ); } #ifndef KDE_NO_DEPRECATED void KUrlRequester::setPath( const QString& path ) { d->setText( path ); } #endif void KUrlRequester::setText(const QString& text) { d->setText(text); } void KUrlRequester::setStartDir(const KUrl& startDir) { d->m_startDir = startDir; if (startDir.isLocalFile()) d->myCompletion->setDir(startDir.toLocalFile()); } void KUrlRequester::changeEvent(QEvent *e) { if (e->type()==QEvent::WindowTitleChange) { if (d->myFileDialog) { d->myFileDialog->setCaption(windowTitle()); } } KHBox::changeEvent(e); } KUrl KUrlRequester::url() const { return d->url(); } KUrl KUrlRequester::startDir() const { return d->m_startDir; } QString KUrlRequester::text() const { return d->text(); } void KUrlRequester::KUrlRequesterPrivate::_k_slotOpenDialog() { if ( myFileDialog ) if ( myFileDialog->isVisible() ) { //The file dialog is already being shown, raise it and exit myFileDialog->raise(); myFileDialog->activateWindow(); return; } if ( ((fileDialogMode & KFile::Directory) && !(fileDialogMode & KFile::File)) || /* catch possible fileDialog()->setMode( KFile::Directory ) changes */ (myFileDialog && (myFileDialog->mode() & KFile::Directory) && (myFileDialog->mode() & (KFile::File | KFile::Files)) == 0) ) { const KUrl openUrl = (!m_parent->url().isEmpty() && !m_parent->url().isRelative() ) ? m_parent->url() : m_startDir; /* FIXME We need a new abstract interface for using KDirSelectDialog in a non-modal way */ KUrl newurl; if (fileDialogMode & KFile::LocalOnly) newurl = KFileDialog::getExistingDirectory( openUrl, m_parent); else newurl = KFileDialog::getExistingDirectoryUrl( openUrl, m_parent); if ( newurl.isValid() ) { m_parent->setUrl( newurl ); emit m_parent->urlSelected( url() ); } } else { emit m_parent->openFileDialog( m_parent ); //Creates the fileDialog if it doesn't exist yet KFileDialog *dlg = m_parent->fileDialog(); if ( !url().isEmpty() && !url().isRelative() ) { KUrl u( url() ); // If we won't be able to list it (e.g. http), then don't try :) if ( KProtocolManager::supportsListing( u ) ) dlg->setSelection( u.url() ); } else { dlg->setUrl(m_startDir); } //Update the file dialog window modality if ( dlg->windowModality() != fileDialogModality ) dlg->setWindowModality(fileDialogModality); if ( fileDialogModality == Qt::NonModal ) { dlg->show(); } else { dlg->exec(); } } } void KUrlRequester::KUrlRequesterPrivate::_k_slotFileDialogFinished() { if ( !myFileDialog ) return; if ( myFileDialog->result() == QDialog::Accepted ) { KUrl newUrl = myFileDialog->selectedUrl(); if ( newUrl.isValid() ) { m_parent->setUrl( newUrl ); emit m_parent->urlSelected( url() ); } } } void KUrlRequester::setMode( KFile::Modes mode) { Q_ASSERT( (mode & KFile::Files) == 0 ); d->fileDialogMode = mode; if ( (mode & KFile::Directory) && !(mode & KFile::File) ) d->myCompletion->setMode( KUrlCompletion::DirCompletion ); if (d->myFileDialog) { d->myFileDialog->setMode(d->fileDialogMode); } } KFile::Modes KUrlRequester::mode() const { return d->fileDialogMode; } void KUrlRequester::setFilter(const QString &filter) { d->fileDialogFilter = filter; if (d->myFileDialog) { d->myFileDialog->setFilter(d->fileDialogFilter); } } QString KUrlRequester::filter( ) const { return d->fileDialogFilter; } KFileDialog * KUrlRequester::fileDialog() const { if (!d->myFileDialog) { QWidget *p = parentWidget(); d->myFileDialog = new KFileDialog(QString(), d->fileDialogFilter, p); d->myFileDialog->setMode(d->fileDialogMode); d->myFileDialog->setCaption(windowTitle()); d->myFileDialog->setWindowModality(d->fileDialogModality); connect(d->myFileDialog, SIGNAL(finished()), SLOT(_k_slotFileDialogFinished())); } return d->myFileDialog; } void KUrlRequester::clear() { d->setText( QString() ); } KLineEdit * KUrlRequester::lineEdit() const { return d->edit; } KComboBox * KUrlRequester::comboBox() const { return d->combo; } void KUrlRequester::KUrlRequesterPrivate::_k_slotUpdateUrl() { - KUrl u( KUrl::fromPath( QDir::currentPath() + '/' ), url().url() ); + KUrl u( KUrl::fromLocalFile( QDir::currentPath() + '/' ), url().url() ); myButton->setURL( u ); } bool KUrlRequester::eventFilter( QObject *obj, QEvent *ev ) { if ( ( d->edit == obj ) || ( d->combo == obj ) ) { if (( ev->type() == QEvent::FocusIn ) || ( ev->type() == QEvent::FocusOut )) // Forward focusin/focusout events to the urlrequester; needed by file form element in khtml QApplication::sendEvent( this, ev ); } return QWidget::eventFilter( obj, ev ); } KPushButton * KUrlRequester::button() const { return d->myButton; } KUrlCompletion *KUrlRequester::completionObject() const { return d->myCompletion; } void KUrlRequester::setClickMessage(const QString& msg) { if(d->edit) d->edit->setClickMessage(msg); } QString KUrlRequester::clickMessage() const { if(d->edit) return d->edit->clickMessage(); else return QString(); } Qt::WindowModality KUrlRequester::fileDialogModality() const { return d->fileDialogModality; } void KUrlRequester::setFileDialogModality(Qt::WindowModality modality) { d->fileDialogModality = modality; } #ifndef KDE_NO_DEPRECATED const KEditListBox::CustomEditor &KUrlRequester::customEditor() #else const KEditListWidget::CustomEditor &KUrlRequester::customEditor() #endif { setSizePolicy(QSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed)); KLineEdit *edit = d->edit; if ( !edit && d->combo ) edit = qobject_cast<KLineEdit*>( d->combo->lineEdit() ); #ifndef NDEBUG if ( !edit ) { kWarning() << "KUrlRequester's lineedit is not a KLineEdit!??\n"; } #endif d->editor.setRepresentationWidget(this); d->editor.setLineEdit(edit); return d->editor; } KUrlComboRequester::KUrlComboRequester( QWidget *parent) : KUrlRequester( new KComboBox(false), parent), d(0) { } #include "moc_kurlrequester.cpp" diff --git a/kio/kio/kdirmodel.cpp b/kio/kio/kdirmodel.cpp index 7fa32d920c..17ab6ae451 100644 --- a/kio/kio/kdirmodel.cpp +++ b/kio/kio/kdirmodel.cpp @@ -1,1170 +1,1171 @@ /* 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 <kurlmimedata.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.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.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(); + return KUrlMimeData::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); + KUrlMimeData::setUrls(urls, mostLocalUrls, data); } else { - urls.populateMimeData(data); + data->setUrls(urls); } // 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/paste.cpp b/kio/kio/paste.cpp index 6d7205522f..82ce5fc485 100644 --- a/kio/kio/paste.cpp +++ b/kio/kio/paste.cpp @@ -1,402 +1,404 @@ /* 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 "paste.h" #include "pastedialog.h" #include "kio/job.h" #include "kio/copyjob.h" #include "kio/deletejob.h" #include "kio/global.h" #include "kio/netaccess.h" #include "kio/renamedialog.h" #include "kio/kprotocolmanager.h" #include "jobuidelegate.h" #include <kurl.h> #include <kdebug.h> #include <klocale.h> #include <kinputdialog.h> #include <kmessagebox.h> #include <kmimetype.h> +#include <kurlmimedata.h> #include <QApplication> #include <QClipboard> #include <QMimeData> #include <qtemporaryfile.h> static bool decodeIsCutSelection(const QMimeData *mimeData) { const QByteArray data = mimeData->data("application/x-kde-cutselection"); return data.isEmpty() ? false : data.at(0) == '1'; } // This could be made a public method, if there's a need for pasting only urls // and not random data. /** * Pastes URLs from the clipboard. This results in a copy or move job, * depending on whether the user has copied or cut the items. * * @param mimeData the mimeData to paste, usually QApplication::clipboard()->mimeData() * @param destDir Destination directory where the items will be copied/moved. * @param flags the flags are passed to KIO::copy or KIO::move. * @return the copy or move job handling the operation, or 0 if there is nothing to do * @since ... */ //KIO_EXPORT Job *pasteClipboardUrls(const KUrl& destDir, JobFlags flags = DefaultFlags); static KIO::Job *pasteClipboardUrls(const QMimeData* mimeData, const KUrl& destDir, KIO::JobFlags flags = KIO::DefaultFlags) { - const KUrl::List urls = KUrl::List::fromMimeData(mimeData, KUrl::List::PreferLocalUrls); + const KUrl::List urls = KUrlMimeData::urlsFromMimeData(mimeData, KUrlMimeData::PreferLocalUrls); if (!urls.isEmpty()) { const bool move = decodeIsCutSelection(mimeData); KIO::Job *job = 0; if (move) job = KIO::move(urls, destDir, flags); else job = KIO::copy(urls, destDir, flags); // If moving, update the clipboard contents with the new locations if (move) { QApplication::clipboard()->clear(); KUrl::List newUrls; Q_FOREACH(const KUrl& url, urls) { KUrl dUrl = destDir; dUrl.addPath(url.fileName()); newUrls.append(dUrl); } - QMimeData* mime = new QMimeData(); - newUrls.populateMimeData(mime); + QMimeData* mime = new QMimeData; + mime->setUrls(newUrls); QApplication::clipboard()->setMimeData(mime); } return job; } return 0; } static KUrl getNewFileName( const KUrl &u, const QString& text, const QString& suggestedFileName, QWidget *widget, bool delIfOverwrite ) { bool ok; QString dialogText( text ); if ( dialogText.isEmpty() ) dialogText = i18n( "Filename for clipboard content:" ); QString file = KInputDialog::getText( QString(), dialogText, suggestedFileName, &ok, widget ); if ( !ok ) return KUrl(); KUrl myurl(u); myurl.addPath( file ); // Check for existing destination file. // When we were using CopyJob, we couldn't let it do that (would expose // an ugly tempfile name as the source URL) // And now we're using a put job anyway, no destination checking included. if (KIO::NetAccess::exists(myurl, KIO::NetAccess::DestinationSide, widget)) { kDebug(7007) << "Paste will overwrite file. Prompting..."; KIO::RenameDialog_Result res = KIO::R_OVERWRITE; KIO::RenameDialog dlg( widget, i18n("File Already Exists"), u.pathOrUrl(), myurl.pathOrUrl(), (KIO::RenameDialog_Mode) (KIO::M_OVERWRITE | KIO::M_SINGLE) ); res = static_cast<KIO::RenameDialog_Result>(dlg.exec()); if ( res == KIO::R_RENAME ) { myurl = dlg.newDestUrl(); } else if ( res == KIO::R_CANCEL ) { return KUrl(); } else if (res == KIO::R_OVERWRITE) { // Old hack. With the put job we just pass Overwrite. if (delIfOverwrite) { // Ideally we would just pass KIO::Overwrite to the job in pasteDataAsyncTo. // But 1) CopyJob doesn't support that (it wouldn't really apply to multiple files) [not true anymore] // 2) we can't use file_move because CopyJob* is everywhere in the API (see TODO) // But well the simpler is really to delete the dest: KIO::Job* delJob = KIO::del(myurl); delJob->exec(); } } } return myurl; } // Old solution // The final step: write _data to tempfile and move it to newUrl static KIO::CopyJob* pasteDataAsyncTo( const KUrl& newUrl, const QByteArray& _data ) { // ### Bug: because we move from a tempfile to the destination, // if the user does "Undo" then we won't ask for confirmation, and we'll // move back to a tempfile, instead of just deleting. // A KIO::storedPut would be better but FileUndoManager would need to support it first. QTemporaryFile tempFile; tempFile.setAutoRemove(false); tempFile.open(); tempFile.write(_data.data(), _data.size()); tempFile.flush(); KUrl origUrl(tempFile.fileName()); return KIO::move(origUrl, newUrl); } // New solution static KIO::Job* putDataAsyncTo(const KUrl& url, const QByteArray& data, QWidget* widget, KIO::JobFlags flags) { KIO::Job* job = KIO::storedPut(data, url, -1, flags); job->ui()->setWindow(widget); return job; } static QByteArray chooseFormatAndUrl(const KUrl& u, const QMimeData* mimeData, const QStringList& formats, const QString& text, const QString& suggestedFileName, QWidget* widget, bool clipboard, KUrl* newUrl) { QStringList formatLabels; for ( int i = 0; i < formats.size(); ++i ) { const QString& fmt = formats[i]; KMimeType::Ptr mime = KMimeType::mimeType(fmt, KMimeType::ResolveAliases); if (mime) formatLabels.append( i18n("%1 (%2)", mime->comment(), fmt) ); else formatLabels.append( fmt ); } QString dialogText( text ); if ( dialogText.isEmpty() ) dialogText = i18n( "Filename for clipboard content:" ); //using QString() instead of QString::null didn't compile (with gcc 3.2.3), because the ctor was mistaken as a function declaration, Alex //krazy:exclude=nullstrassign KIO::PasteDialog dlg( QString::null, dialogText, suggestedFileName, formatLabels, widget, clipboard ); //krazy:exclude=nullstrassign if ( dlg.exec() != KDialog::Accepted ) return QByteArray(); if ( clipboard && dlg.clipboardChanged() ) { KMessageBox::sorry( widget, i18n( "The clipboard has changed since you used 'paste': " "the chosen data format is no longer applicable. " "Please copy again what you wanted to paste." ) ); return QByteArray(); } const QString result = dlg.lineEditText(); const QString chosenFormat = formats[ dlg.comboItem() ]; kDebug() << " result=" << result << " chosenFormat=" << chosenFormat; *newUrl = KUrl( u ); newUrl->addPath( result ); // if "data" came from QClipboard, then it was deleted already - by a nice 0-seconds timer // In that case, get it again. Let's hope the user didn't copy something else meanwhile :/ // #### QT4/KDE4 TODO: check that this is still the case if ( clipboard ) { mimeData = QApplication::clipboard()->mimeData(); } const QByteArray ba = mimeData->data( chosenFormat ); return ba; } static QStringList extractFormats(const QMimeData* mimeData) { QStringList formats; const QStringList allFormats = mimeData->formats(); Q_FOREACH(const QString& format, allFormats) { if (format == QLatin1String("application/x-qiconlist")) // see QIconDrag continue; if (format == QLatin1String("application/x-kde-cutselection")) // see KonqDrag continue; if (format == QLatin1String("application/x-kde-suggestedfilename")) continue; if (format.startsWith(QLatin1String("application/x-qt-"))) // Qt-internal continue; if (format.startsWith(QLatin1String("x-kmail-drag/"))) // app-internal continue; if (!format.contains(QLatin1Char('/'))) // e.g. TARGETS, MULTIPLE, TIMESTAMP continue; formats.append(format); } return formats; } // The [old] main method for dropping KIO::CopyJob* KIO::pasteMimeSource( const QMimeData* mimeData, const KUrl& destUrl, const QString& dialogText, QWidget* widget, bool clipboard ) { QByteArray ba; const QString suggestedFilename = QString::fromUtf8(mimeData->data("application/x-kde-suggestedfilename")); // Now check for plain text // We don't want to display a mimetype choice for a QTextDrag, those mimetypes look ugly. if ( mimeData->hasText() ) { ba = mimeData->text().toLocal8Bit(); // encoding OK? } else { const QStringList formats = extractFormats(mimeData); if ( formats.size() == 0 ) return 0; if ( formats.size() > 1 ) { KUrl newUrl; ba = chooseFormatAndUrl(destUrl, mimeData, formats, dialogText, suggestedFilename, widget, clipboard, &newUrl); KIO::CopyJob* job = pasteDataAsyncTo(newUrl, ba); job->ui()->setWindow(widget); return job; } ba = mimeData->data( formats.first() ); } if ( ba.isEmpty() ) { KMessageBox::sorry( widget, i18n("The clipboard is empty") ); return 0; } const KUrl newUrl = getNewFileName(destUrl, dialogText, suggestedFilename, widget, true); if (newUrl.isEmpty()) return 0; KIO::CopyJob* job = pasteDataAsyncTo(newUrl, ba); job->ui()->setWindow(widget); return job; } KIO_EXPORT bool KIO::canPasteMimeSource(const QMimeData* data) { return data->hasText() || !extractFormats(data).isEmpty(); } KIO::Job* pasteMimeDataImpl(const QMimeData* mimeData, const KUrl& destUrl, const QString& dialogText, QWidget* widget, bool clipboard) { QByteArray ba; const QString suggestedFilename = QString::fromUtf8(mimeData->data("application/x-kde-suggestedfilename")); // Now check for plain text // We don't want to display a mimetype choice for a QTextDrag, those mimetypes look ugly. if (mimeData->hasText()) { ba = mimeData->text().toLocal8Bit(); // encoding OK? } else { const QStringList formats = extractFormats(mimeData); if (formats.isEmpty()) { return 0; } else if (formats.size() > 1) { KUrl newUrl; ba = chooseFormatAndUrl(destUrl, mimeData, formats, dialogText, suggestedFilename, widget, clipboard, &newUrl); if (ba.isEmpty()) { return 0; } return putDataAsyncTo(newUrl, ba, widget, KIO::Overwrite); } ba = mimeData->data(formats.first()); } if (ba.isEmpty()) { return 0; } const KUrl newUrl = getNewFileName(destUrl, dialogText, suggestedFilename, widget, false); if (newUrl.isEmpty()) return 0; return putDataAsyncTo(newUrl, ba, widget, KIO::Overwrite); } // The main method for pasting KIO_EXPORT KIO::Job *KIO::pasteClipboard( const KUrl& destUrl, QWidget* widget, bool move ) { Q_UNUSED(move); if ( !destUrl.isValid() ) { KMessageBox::error( widget, i18n( "Malformed URL\n%1", destUrl.prettyUrl() ) ); return 0; } // TODO: if we passed mimeData as argument, we could write unittests that don't // mess up the clipboard and that don't need QtGui. const QMimeData *mimeData = QApplication::clipboard()->mimeData(); - if (KUrl::List::canDecode(mimeData)) { + if (mimeData->hasUrls()) { // We can ignore the bool move, KIO::paste decodes it KIO::Job* job = pasteClipboardUrls(mimeData, destUrl); if (job) { job->ui()->setWindow(widget); return job; } } return pasteMimeDataImpl(mimeData, destUrl, QString(), widget, true /*clipboard*/); } KIO_EXPORT void KIO::pasteData(const KUrl& u, const QByteArray& data, QWidget* widget) { const KUrl newUrl = getNewFileName(u, QString(), QString(), widget, false); if (newUrl.isEmpty()) return; KIO::Job* job = putDataAsyncTo(newUrl, data, widget, KIO::Overwrite); KIO::NetAccess::synchronousRun(job, widget); } // KDE5: remove KIO_EXPORT KIO::CopyJob* KIO::pasteDataAsync( const KUrl& u, const QByteArray& _data, QWidget *widget, const QString& text ) { KUrl newUrl = getNewFileName(u, text, QString(), widget, true); if (newUrl.isEmpty()) return 0; KIO::CopyJob* job = pasteDataAsyncTo( newUrl, _data ); job->ui()->setWindow(widget); return job; } // NOTE: DolphinView::pasteInfo() has a better version of this // (but which requires KonqFileItemCapabilities) +// (KFileItemCapabilities exists now, but are missing the KFileItem for the dest dir) KIO_EXPORT QString KIO::pasteActionText() { const QMimeData *mimeData = QApplication::clipboard()->mimeData(); - const KUrl::List urls = KUrl::List::fromMimeData( mimeData ); + const QList<QUrl> urls = KUrlMimeData::urlsFromMimeData( mimeData ); if ( !urls.isEmpty() ) { if ( urls.first().isLocalFile() ) return i18np( "&Paste File", "&Paste %1 Files", urls.count() ); else return i18np( "&Paste URL", "&Paste %1 URLs", urls.count() ); } else if ( !mimeData->formats().isEmpty() ) { return i18n( "&Paste Clipboard Contents" ); } else { return QString(); } } // The [new] main method for dropping KIO_EXPORT KIO::Job* KIO::pasteMimeData(const QMimeData* mimeData, const KUrl& destUrl, const QString& dialogText, QWidget* widget) { return pasteMimeDataImpl(mimeData, destUrl, dialogText, widget, false /*not clipboard*/); } diff --git a/kio/tests/jobguitest.cpp b/kio/tests/jobguitest.cpp index a01a97d308..e3205a7ea1 100644 --- a/kio/tests/jobguitest.cpp +++ b/kio/tests/jobguitest.cpp @@ -1,95 +1,95 @@ /* This file is part of the KDE project Copyright (C) 2011 David Faure <faure@kde.org> This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "qtest_kde.h" #include <kio/copyjob.h> #include <kio/netaccess.h> #include <kio/paste.h> #include <kio/deletejob.h> #include "kiotesthelper.h" // createTestFile etc. #include <QClipboard> static QString otherTmpDir() { #ifdef Q_WS_WIN return QDir::tempPath() + "/jobtest/"; #else // This one needs to be on another partition return "/tmp/jobtest/"; #endif } class JobGuiTest : public QObject { Q_OBJECT public Q_SLOTS: void initTestCase() { // Start with a clean base dir cleanupTestCase(); homeTmpDir(); // create it if ( !QFile::exists( otherTmpDir() ) ) { bool ok = QDir().mkdir( otherTmpDir() ); if ( !ok ) kFatal() << "Couldn't create " << otherTmpDir(); } } void cleanupTestCase() { delDir( homeTmpDir() ); delDir( otherTmpDir() ); } void pasteFileToOtherPartition() { const QString filePath = homeTmpDir() + "fileFromHome"; const QString dest = otherTmpDir() + "fileFromHome_copied"; QFile::remove(dest); createTestFile( filePath ); QMimeData* mimeData = new QMimeData; - KUrl fileUrl(filePath); - fileUrl.populateMimeData(mimeData); + QUrl fileUrl = QUrl::fromLocalFile(filePath); + mimeData->setUrls(QList<QUrl>() << fileUrl); QApplication::clipboard()->setMimeData(mimeData); KIO::Job* job = KIO::pasteClipboard(otherTmpDir(), static_cast<QWidget*>(0)); job->setUiDelegate(0); bool ok = KIO::NetAccess::synchronousRun(job, 0); QVERIFY( ok ); QVERIFY( QFile::exists( dest ) ); QVERIFY( QFile::exists( filePath ) ); // still there } private: static void delDir(const QString& pathOrUrl) { KIO::Job* job = KIO::del(KUrl(pathOrUrl), KIO::HideProgressInfo); job->setUiDelegate(0); KIO::NetAccess::synchronousRun(job, 0); } }; QTEST_KDEMAIN( JobGuiTest, GUI ) #include "jobguitest.moc" diff --git a/kio/tests/kbookmarktest.cpp b/kio/tests/kbookmarktest.cpp index 4958e0ede7..33baa01590 100644 --- a/kio/tests/kbookmarktest.cpp +++ b/kio/tests/kbookmarktest.cpp @@ -1,98 +1,98 @@ /* This file is part of the KDE libraries Copyright (c) 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 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 <qtest_kde.h> #include "kbookmarktest.h" QTEST_KDEMAIN( KBookmarkTest, false ) #include <kbookmark.h> #include <kdebug.h> #include <QtCore/QMimeData> static void compareBookmarks( const KBookmark& initialBookmark, const KBookmark& decodedBookmark ) { QCOMPARE( decodedBookmark.url(), initialBookmark.url() ); QCOMPARE( decodedBookmark.icon(), initialBookmark.icon() ); QCOMPARE( decodedBookmark.text(), initialBookmark.text() ); QCOMPARE( decodedBookmark.description(), initialBookmark.description() ); QDomNamedNodeMap decodedAttribs = decodedBookmark.internalElement().attributes(); QDomNamedNodeMap initialAttribs = initialBookmark.internalElement().attributes(); QCOMPARE( decodedAttribs.count(), initialAttribs.count() ); for ( uint i = 0; i < decodedAttribs.length(); ++i ) { QDomAttr decodedAttr = decodedAttribs.item( i ).toAttr(); QDomAttr initialAttr = initialAttribs.item( i ).toAttr(); QCOMPARE( decodedAttr.name(), initialAttr.name() ); QCOMPARE( decodedAttr.value(), initialAttr.value() ); } } void KBookmarkTest::testMimeDataOneBookmark() { QMimeData* mimeData = new QMimeData; KBookmark bookmark = KBookmark::standaloneBookmark( "KDE", KUrl( "http://www.kde.org" ), "icon" ); bookmark.setDescription( "Comment" ); QVERIFY( !bookmark.isNull() ); bookmark.populateMimeData( mimeData ); QDomDocument doc; - QVERIFY( KUrl::List::canDecode( mimeData ) ); + QVERIFY(mimeData->hasUrls()); QVERIFY( KBookmark::List::canDecode( mimeData ) ); const KBookmark::List decodedBookmarks = KBookmark::List::fromMimeData( mimeData, doc ); QVERIFY( !decodedBookmarks.isEmpty() ); QCOMPARE( decodedBookmarks.count(), 1 ); QVERIFY( !decodedBookmarks[0].isNull() ); compareBookmarks( bookmark, decodedBookmarks[0] ); // Do like keditbookmarks's paste code: (CreateCommand::execute) // Crashed before passing "doc" to fromMimeData (#160679) QDomElement clonedElem = decodedBookmarks[0].internalElement().cloneNode().toElement(); delete mimeData; } void KBookmarkTest::testMimeDataBookmarkList() { QMimeData* mimeData = new QMimeData; KBookmark bookmark1 = KBookmark::standaloneBookmark( "KDE", KUrl( "http://www.kde.org" ), "icon" ); bookmark1.setDescription( "KDE comment" ); QVERIFY( !bookmark1.isNull() ); KBookmark bookmark2 = KBookmark::standaloneBookmark( "KOffice", KUrl( "http://www.koffice.org" ), "koicon" ); bookmark2.setDescription( "KOffice comment" ); QVERIFY( !bookmark2.isNull() ); bookmark2.setMetaDataItem( "key", "value" ); KBookmark::List initialBookmarks; initialBookmarks.append( bookmark1 ); initialBookmarks.append( bookmark2 ); initialBookmarks.populateMimeData( mimeData ); QDomDocument doc; - QVERIFY( KUrl::List::canDecode( mimeData ) ); + QVERIFY(mimeData->hasUrls()); QVERIFY( KBookmark::List::canDecode( mimeData ) ); const KBookmark::List decodedBookmarks = KBookmark::List::fromMimeData( mimeData, doc ); QCOMPARE( decodedBookmarks.count(), 2 ); QVERIFY( !decodedBookmarks[0].isNull() ); QVERIFY( !decodedBookmarks[1].isNull() ); compareBookmarks( bookmark1, decodedBookmarks[0] ); compareBookmarks( bookmark2, decodedBookmarks[1] ); delete mimeData; } diff --git a/plasma/containment.cpp b/plasma/containment.cpp index 5e389d10a9..214335e7ae 100644 --- a/plasma/containment.cpp +++ b/plasma/containment.cpp @@ -1,2432 +1,2433 @@ /* * 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 <kurlmimedata.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()))); + event->mimeData()->hasUrls())); 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)) { + } else if (mimeData->hasUrls()) { //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) { + const QList<QUrl> urls = KUrlMimeData::urlsFromMimeData(mimeData); + foreach (const QUrl &url, urls) { 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(); + args << url.toString(); #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 ®ion, 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 #include "moc_containment.cpp" diff --git a/staging/kcoreaddons/autotests/CMakeLists.txt b/staging/kcoreaddons/autotests/CMakeLists.txt index 6a7dc22551..6837d132b1 100644 --- a/staging/kcoreaddons/autotests/CMakeLists.txt +++ b/staging/kcoreaddons/autotests/CMakeLists.txt @@ -1,40 +1,41 @@ set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}) MACRO(KCOREADDONS_EXECUTABLE_TESTS) FOREACH(_testname ${ARGN}) add_executable(${_testname} ${_testname}.cpp) # TODO: split out the QtNetwork-dependent stuff into a separate addon? target_link_libraries(${_testname} ${QT_QTNETWORK_LIBRARY} ${QT_QTCORE_LIBRARY} kcoreaddons) if(WINCE) target_link_libraries(${_testname} ${WCECOMPAT_LIBRARIES}) endif(WINCE) ENDFOREACH(_testname) ENDMACRO(KCOREADDONS_EXECUTABLE_TESTS) MACRO(KCOREADDONS_UNIT_TESTS) FOREACH(_testname ${ARGN}) KCOREADDONS_EXECUTABLE_TESTS(${_testname}) target_link_libraries(${_testname} ${QT_QTTEST_LIBRARY}) add_test(kcoreaddons-${_testname} ${_testname}) ENDFOREACH(_testname) ENDMACRO(KCOREADDONS_UNIT_TESTS) include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/../src/kernel ${CMAKE_CURRENT_SOURCE_DIR}/../src/text ${CMAKE_CURRENT_SOURCE_DIR}/../src/jobs ${CMAKE_CURRENT_SOURCE_DIR}/../src/io ${CMAKE_CURRENT_BINARY_DIR}/../src ) KCOREADDONS_UNIT_TESTS( kaboutdatatest kcharsetstest kjobtest klockfiletest kautosavefiletest + kurlmimetest ) # Helper for klockfiletest KCOREADDONS_EXECUTABLE_TESTS( klockfile_testlock ) diff --git a/kdecore/tests/kurlmimetest.cpp b/staging/kcoreaddons/autotests/kurlmimetest.cpp similarity index 79% rename from kdecore/tests/kurlmimetest.cpp rename to staging/kcoreaddons/autotests/kurlmimetest.cpp index 5602d8126a..54073ab080 100644 --- a/kdecore/tests/kurlmimetest.cpp +++ b/staging/kcoreaddons/autotests/kurlmimetest.cpp @@ -1,137 +1,139 @@ /* This file is part of the KDE libraries Copyright (c) 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 "kurlmimetest.h" -#include <qtest_kde.h> -#include <QtCore/QMimeData> +#include <QtTest> +#include <QMimeData> #include <kurl.h> #include <kdebug.h> -QTEST_KDEMAIN_CORE( KUrlMimeTest ) +QTEST_MAIN( KUrlMimeTest ) void KUrlMimeTest::testURLList() { QMimeData* mimeData = new QMimeData; QVERIFY( !KUrl::List::canDecode( mimeData ) ); QVERIFY(!mimeData->hasUrls()); KUrl::List urls; urls.append( KUrl( "http://www.kde.org" ) ); urls.append( KUrl( "http://wstephenson:secret@example.com/path" ) ); urls.append( KUrl( "file:///home/dfaure/konqtests/Mat%C3%A9riel" ) ); QMap<QString, QString> metaData; - metaData["key"] = "value"; - metaData["key2"] = "value2"; + metaData[QLatin1String("key")] = QLatin1String("value"); + metaData[QLatin1String("key2")] = QLatin1String("value2"); urls.populateMimeData( mimeData, metaData ); QVERIFY(KUrl::List::canDecode( mimeData )); QVERIFY(mimeData->hasUrls()); QVERIFY(mimeData->hasText()); QMap<QString, QString> decodedMetaData; KUrl::List decodedURLs = KUrl::List::fromMimeData( mimeData, KUrl::List::PreferKdeUrls, &decodedMetaData ); QVERIFY( !decodedURLs.isEmpty() ); KUrl::List expectedUrls = urls; - expectedUrls[1] = KUrl("http://wstephenson@example.com/path"); // password removed - QCOMPARE( expectedUrls.toStringList().join(" "), decodedURLs.toStringList().join(" ") ); + expectedUrls[1] = KUrl("http://wstephenson:secret@example.com/path"); // password kept, unlike in KDE4, but that's okay, it's not displayed + const QString space(QLatin1Char(' ')); + QCOMPARE( expectedUrls.toStringList().join(space), decodedURLs.toStringList().join(space) ); const QList<QUrl> qurls = mimeData->urls(); QCOMPARE(qurls.count(), urls.count()); for (int i = 0; i < qurls.count(); ++i ) QCOMPARE(qurls[i], static_cast<QUrl>(decodedURLs[i])); QVERIFY( !decodedMetaData.isEmpty() ); - QCOMPARE( decodedMetaData["key"], QString( "value" ) ); - QCOMPARE( decodedMetaData["key2"], QString( "value2" ) ); + QCOMPARE( decodedMetaData[QLatin1String("key")], QString::fromLatin1( "value" ) ); + QCOMPARE( decodedMetaData[QLatin1String("key2")], QString::fromLatin1( "value2" ) ); delete mimeData; } void KUrlMimeTest::testOneURL() { KUrl oneURL( "file:///tmp" ); QMimeData* mimeData = new QMimeData; oneURL.populateMimeData( mimeData ); QVERIFY( KUrl::List::canDecode( mimeData ) ); QMap<QString, QString> decodedMetaData; KUrl::List decodedURLs = KUrl::List::fromMimeData( mimeData, KUrl::List::PreferKdeUrls, &decodedMetaData ); QVERIFY( !decodedURLs.isEmpty() ); QCOMPARE( decodedURLs.count(), 1 ); QCOMPARE( decodedURLs[0].url(), oneURL.url() ); QVERIFY( decodedMetaData.isEmpty() ); delete mimeData; } void KUrlMimeTest::testFromQUrl() { QList<QUrl> qurls; - qurls.append( QUrl( "http://www.kde.org" ) ); - qurls.append( QUrl( "file:///home/dfaure/konqtests/Mat%C3%A9riel" ) ); + qurls.append( QUrl( QLatin1String("http://www.kde.org") ) ); + qurls.append( QUrl( QLatin1String("file:///home/dfaure/konqtests/Mat%C3%A9riel") ) ); QMimeData* mimeData = new QMimeData; mimeData->setUrls(qurls); QVERIFY(KUrl::List::canDecode(mimeData)); QMap<QString, QString> decodedMetaData; KUrl::List decodedURLs = KUrl::List::fromMimeData( mimeData, KUrl::List::PreferKdeUrls, &decodedMetaData ); QVERIFY( !decodedURLs.isEmpty() ); QCOMPARE( decodedURLs.count(), 2 ); QCOMPARE( static_cast<QUrl>(decodedURLs[0]), qurls[0] ); QCOMPARE( static_cast<QUrl>(decodedURLs[1]), qurls[1] ); QVERIFY( decodedMetaData.isEmpty() ); delete mimeData; } void KUrlMimeTest::testMostLocalUrlList() { QMimeData* mimeData = new QMimeData; KUrl::List urls; urls.append(KUrl("desktop:/foo")); urls.append(KUrl("desktop:/bar")); KUrl::List localUrls; localUrls.append(KUrl("file:/home/dfaure/Desktop/foo")); localUrls.append(KUrl("file:/home/dfaure/Desktop/bar")); urls.populateMimeData(localUrls, mimeData); QVERIFY(KUrl::List::canDecode(mimeData)); QVERIFY(mimeData->hasUrls()); QVERIFY(mimeData->hasText()); - QVERIFY(mimeData->hasFormat("text/plain")); + QVERIFY(mimeData->hasFormat(QLatin1String("text/plain"))); // KUrl decodes the real "kde" urls by default KUrl::List decodedURLs = KUrl::List::fromMimeData(mimeData); QVERIFY(!decodedURLs.isEmpty()); - QCOMPARE(decodedURLs.toStringList().join(" "), urls.toStringList().join(" ") ); + const QString space(QLatin1Char(' ')); + QCOMPARE(decodedURLs.toStringList().join(space), urls.toStringList().join(space) ); // KUrl can also be told to decode the "most local" urls decodedURLs = KUrl::List::fromMimeData(mimeData, KUrl::List::PreferLocalUrls); QVERIFY(!decodedURLs.isEmpty()); - QCOMPARE(decodedURLs.toStringList().join(" "), localUrls.toStringList().join(" ") ); + QCOMPARE(decodedURLs.toStringList().join(space), localUrls.toStringList().join(space) ); // QMimeData decodes the "most local" urls const QList<QUrl> qurls = mimeData->urls(); QCOMPARE(qurls.count(), localUrls.count()); for (int i = 0; i < qurls.count(); ++i ) QCOMPARE(qurls[i], static_cast<QUrl>(localUrls[i])); } diff --git a/kdecore/tests/kurlmimetest.h b/staging/kcoreaddons/autotests/kurlmimetest.h similarity index 100% rename from kdecore/tests/kurlmimetest.h rename to staging/kcoreaddons/autotests/kurlmimetest.h diff --git a/staging/kcoreaddons/src/CMakeLists.txt b/staging/kcoreaddons/src/CMakeLists.txt index 13de211258..39e66f8e44 100644 --- a/staging/kcoreaddons/src/CMakeLists.txt +++ b/staging/kcoreaddons/src/CMakeLists.txt @@ -1,145 +1,147 @@ # Configure checks for the caching subdir include(CheckIncludeFiles) check_include_files("sys/types.h;sys/mman.h" HAVE_SYS_MMAN_H) configure_file(caching/config-caching.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-caching.h) set(kcoreaddons_OPTIONAL_SRCS ) set(kcoreaddons_OPTIONAL_LIBS ) if (FAM_FOUND) set(kcoreaddons_OPTIONAL_LIBS ${kcoreaddons_OPTIONAL_LIBS} ${FAM_LIBRARIES}) endif (FAM_FOUND) if(NOT WIN32) set(kcoreaddons_OPTIONAL_SRCS caching/kshareddatacache.cpp) set(kcoreaddons_OPTIONAL_LIBS ${kcoreaddons_OPTIONAL_LIBS} ${CMAKE_THREAD_LIBS_INIT}) else(NOT WIN32) set(kcoreaddons_OPTIONAL_SRCS caching/kshareddatacache_win.cpp io/kdirwatch_win.cpp ) endif(NOT WIN32) if (WIN32) set(kcoreaddons_OPTIONAL_SRCS ${kcoreaddons_OPTIONAL_SRCS} io/klockfile_win.cpp text/kmacroexpander_win.cpp ) endif (WIN32) if (UNIX) set(kcoreaddons_OPTIONAL_SRCS ${kcoreaddons_OPTIONAL_SRCS} io/klockfile_unix.cpp text/kmacroexpander_unix.cpp ) endif (UNIX) set(libkcoreaddons_SRCS io/kautosavefile.cpp io/kdirwatch.cpp io/kfilesystemtype_p.cpp io/ksavefile.cpp io/kbackup.cpp io/kurl.cpp + io/kurlmimedata.cpp jobs/kcompositejob.cpp jobs/kjob.cpp jobs/kjobtrackerinterface.cpp jobs/kjobuidelegate.cpp kernel/kaboutdata.cpp kernel/kcmdlineargs.cpp randomness/krandom.cpp randomness/krandomsequence.cpp text/guess_ja.cpp text/kcharsets.cpp text/kcodecs.cpp text/kencodingdetector.cpp text/kencodingprober.cpp text/kmacroexpander.cpp text/probers/CharDistribution.cpp text/probers/ChineseGroupProber.cpp text/probers/JapaneseGroupProber.cpp text/probers/JpCntx.cpp text/probers/LangBulgarianModel.cpp text/probers/LangCyrillicModel.cpp text/probers/LangGreekModel.cpp text/probers/LangHebrewModel.cpp text/probers/LangHungarianModel.cpp text/probers/LangThaiModel.cpp text/probers/UnicodeGroupProber.cpp text/probers/nsBig5Prober.cpp text/probers/nsCharSetProber.cpp text/probers/nsEUCJPProber.cpp text/probers/nsEUCKRProber.cpp text/probers/nsEUCTWProber.cpp text/probers/nsEscCharsetProber.cpp text/probers/nsEscSM.cpp text/probers/nsGB2312Prober.cpp text/probers/nsHebrewProber.cpp text/probers/nsLatin1Prober.cpp text/probers/nsMBCSGroupProber.cpp text/probers/nsMBCSSM.cpp text/probers/nsSBCSGroupProber.cpp text/probers/nsSBCharSetProber.cpp text/probers/nsSJISProber.cpp text/probers/nsUniversalDetector.cpp text/kstringhandler.cpp ${kcoreaddons_OPTIONAL_SRCS} ) include_directories( ${CMAKE_BINARY_DIR}/kdecore/ # kde_export.h (required by kglobal.h) ${CMAKE_SOURCE_DIR}/kdecore/kernel/ # K_GLOBAL_STATIC (TODO remove) ${CMAKE_CURRENT_BINARY_DIR}/io/ ${CMAKE_CURRENT_SOURCE_DIR}/io/ ${CMAKE_CURRENT_SOURCE_DIR}/jobs/ ${CMAKE_CURRENT_SOURCE_DIR}/randomness/ ${CMAKE_CURRENT_SOURCE_DIR}/kernel/ ${CMAKE_CURRENT_SOURCE_DIR}/text/ ) include_directories(${QT_MKSPECS_DIR}/default) # for qplatformdefs.h add_library(kcoreaddons ${LIBRARY_TYPE} ${libkcoreaddons_SRCS}) generate_export_header(kcoreaddons) target_link_libraries(kcoreaddons LINK_PUBLIC ${QT_QTCORE_LIBRARY} ${kcoreaddons_OPTIONAL_LIBS} ${INQT5_LIBRARY} LINK_PRIVATE pthread ) set_target_properties(kcoreaddons PROPERTIES VERSION ${ECM_VERSION_STRING} SOVERSION ${ECM_SOVERSION} ) install(TARGETS kcoreaddons ${ECM_TARGET_DEFAULT_ARGS}) install(FILES caching/kshareddatacache.h io/kautosavefile.h io/kdirwatch.h io/klockfile.h io/ksavefile.h io/kbackup.h io/kurl.h + io/kurlmimedata.h jobs/kcompositejob.h jobs/kcompositejob_p.h jobs/kjob.h jobs/kjob_p.h jobs/kjobtrackerinterface.h jobs/kjobuidelegate.h kernel/kaboutdata.h kernel/kcmdlineargs.h randomness/krandom.h randomness/krandomsequence.h text/kcharsets.h text/kcodecs.h text/kencodingdetector.h text/kencodingprober.h text/kmacroexpander.h text/kstringhandler.h ${CMAKE_CURRENT_BINARY_DIR}/kcoreaddons_export.h DESTINATION ${INCLUDE_INSTALL_DIR} COMPONENT Devel ) diff --git a/staging/kcoreaddons/src/io/kurl.cpp b/staging/kcoreaddons/src/io/kurl.cpp index 8de0dd5e6e..23dd46af52 100644 --- a/staging/kcoreaddons/src/io/kurl.cpp +++ b/staging/kcoreaddons/src/io/kurl.cpp @@ -1,1913 +1,1824 @@ /* Copyright (C) 1999 Torben Weis <weis@kde.org> Copyright (C) 2005-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. */ /// KDE4 TODO: maybe we should use QUrl::resolved() /* * The currently active RFC for URL/URIs is RFC3986 * Previous (and now deprecated) RFCs are RFC1738 and RFC2396 */ #include "kurl.h" +#include "kurlmimedata.h" #include <kglobal.h> #include <stdio.h> #include <assert.h> #include <ctype.h> #include <stdlib.h> #include <unistd.h> #include <QtCore/QDebug> #include <QtCore/QDir> -#include <QtCore/QMutableStringListIterator> +#include <QtCore/QStringList> #include <QtCore/QRegExp> #include <QtCore/QMimeData> #include <QtCore/QTextCodec> #ifdef DEBUG_KURL static int kurlDebugArea() { static int s_area = KDebug::registerArea("kdecore (KUrl)"); return s_area; } #endif static QString cleanpath( const QString &_path, bool cleanDirSeparator, bool decodeDots ) { if (_path.isEmpty()) return QString(); if (QFileInfo(_path).isRelative()) return _path; // Don't mangle mailto-style URLs QString path = _path; int len = path.length(); if (decodeDots) { static const QLatin1String encodedDot("%2e"); if (path.indexOf(encodedDot, 0, Qt::CaseInsensitive) != -1) { static const QLatin1String encodedDOT("%2E"); // Uppercase! path.replace(encodedDot, QString(QLatin1Char('.'))); path.replace(encodedDOT, QString(QLatin1Char('.'))); len = path.length(); } } const bool slash = (len && path[len-1] == QLatin1Char('/')) || (len > 1 && path[len-2] == QLatin1Char('/') && path[len-1] == QLatin1Char('.')); // The following code cleans up directory path much like // QDir::cleanPath() except it can be made to ignore multiple // directory separators by setting the flag to false. That fixes // bug# 15044, mail.altavista.com and other similar brain-dead server // implementations that do not follow what has been specified in // RFC 2396!! (dA) QString result; int cdUp, orig_pos, pos; cdUp = 0; pos = orig_pos = len; while ( pos && (pos = path.lastIndexOf(QLatin1Char('/'),--pos)) != -1 ) { len = orig_pos - pos - 1; if ( len == 2 && path[pos+1] == QLatin1Char('.') && path[pos+2] == QLatin1Char('.') ) cdUp++; else { // Ignore any occurrences of '.' // This includes entries that simply do not make sense like /..../ if ( (len || !cleanDirSeparator) && (len != 1 || path[pos+1] != QLatin1Char('.') ) ) { if ( !cdUp ) result.prepend(path.mid(pos, len+1)); else cdUp--; } } orig_pos = pos; } #ifdef Q_WS_WIN // prepend drive letter if exists (js) if (orig_pos >= 2 && path[0].isLetter() && path[1] == QLatin1Char(':') ) { result.prepend(QString(path[0]) + QLatin1Char(':') ); } #endif if ( result.isEmpty() ) result = QLatin1Char('/'); else if ( slash && result[result.length()-1] != QLatin1Char('/') ) result.append(QLatin1Char('/')); return result; } #ifdef Q_WS_WIN // returns true if provided arguments desinate letter+colon or double slash #define IS_DRIVE_OR_DOUBLESLASH(isletter, char1, char2, colon, slash) \ ((isletter && char2 == colon) || (char1 == slash && char2 == slash)) // Removes file:/// or file:// or file:/ or / prefix assuming that str // is (nonempty) Windows absolute path with a drive letter or double slash. // If there was file protocol, the path is decoded from percent encoding static QString removeSlashOrFilePrefix(const QString& str) { // FIXME this should maybe be replaced with some (faster?)/nicer logic const int len = str.length(); if (str[0]==QLatin1Char('f')) { if ( len > 10 && str.startsWith( QLatin1String( "file:///" ) ) && IS_DRIVE_OR_DOUBLESLASH(str[8].isLetter(), str[8], str[9], QLatin1Char(':'), QLatin1Char('/')) ) return QUrl::fromPercentEncoding( str.toLatin1() ).mid(8); else if ( len > 9 && str.startsWith( QLatin1String( "file://" ) ) && IS_DRIVE_OR_DOUBLESLASH(str[7].isLetter(), str[7], str[8], QLatin1Char(':'), QLatin1Char('/')) ) return QUrl::fromPercentEncoding( str.toLatin1() ).mid(7); else if ( len > 8 && str.startsWith( QLatin1String( "file:/" ) ) && IS_DRIVE_OR_DOUBLESLASH(str[6].isLetter(), str[6], str[7], QLatin1Char(':'), QLatin1Char('/')) ) return QUrl::fromPercentEncoding( str.toLatin1() ).mid(6); } /* No 'else' here since there can be "f:/" path. */ /* '/' + drive letter or // */ if ( len > 2 && str[0] == QLatin1Char('/') && IS_DRIVE_OR_DOUBLESLASH(str[1].isLetter(), str[1], str[2], QLatin1Char(':'), QLatin1Char('/')) ) return str.mid(1); /* drive letter or // */ else if ( len >= 2 && IS_DRIVE_OR_DOUBLESLASH(str[0].isLetter(), str[0], str[1], QLatin1Char(':'), QLatin1Char('/')) ) return str; return QString(); } #endif bool KUrl::isRelativeUrl(const QString &_url) { int len = _url.length(); if (!len) return true; // Very short relative URL. const QChar *str = _url.unicode(); // Absolute URL must start with alpha-character if (!isalpha(str[0].toLatin1())) return true; // Relative URL for(int i = 1; i < len; i++) { char c = str[i].toLatin1(); // Note: non-latin1 chars return 0! if (c == ':') return false; // Absolute URL // Protocol part may only contain alpha, digit, + or - if (!isalpha(c) && !isdigit(c) && (c != '+') && (c != '-')) return true; // Relative URL } // URL did not contain ':' return true; // Relative URL } KUrl::List::List(const KUrl &url) { append( url ); } KUrl::List::List(const QList<KUrl> &list) : QList<KUrl>(list) { } KUrl::List::List(const QList<QUrl> &list) { Q_FOREACH(const QUrl& url, list) { append(KUrl(url)); } } KUrl::List::List(const QStringList &list) { for (QStringList::ConstIterator it = list.begin(); it != list.end(); ++it) { append( KUrl(*it) ); } } QStringList KUrl::List::toStringList() const { return toStringList(KUrl::LeaveTrailingSlash); } QStringList KUrl::List::toStringList(KUrl::AdjustPathOption trailing) const { QStringList lst; for(KUrl::List::ConstIterator it = constBegin(); it != constEnd(); ++it) { lst.append(it->url(trailing)); } return lst; } -static QByteArray uriListData(const KUrl::List& urls) +static void populateMimeDataHelper(const KUrl::List& urls, + QMimeData* mimeData, + const KUrl::MetaDataMap& metaData, + KUrl::MimeDataFlags flags) { - QList<QByteArray> urlStringList; - KUrl::List::ConstIterator uit = urls.constBegin(); - const KUrl::List::ConstIterator uEnd = urls.constEnd(); - for (; uit != uEnd ; ++uit) { - // Get each URL encoded in utf8 - and since we get it in escaped - // form on top of that, .toLatin1() is fine. - urlStringList.append((*uit).toMimeDataString().toLatin1()); + const QString oldText = mimeData->text(); + mimeData->setUrls(urls); // set text/uri-list and text/plain + + if ((flags & KUrl::NoTextExport) == 0) { + mimeData->setText(oldText); } - QByteArray uriListData; - for (int i = 0, n = urlStringList.count(); i < n; ++i) { - uriListData += urlStringList.at(i); - if (i < n-1) - uriListData += "\r\n"; + if (!metaData.isEmpty()) { + KUrlMimeData::setMetaData(metaData, mimeData); } - return uriListData; } -static const char s_kdeUriListMime[] = "application/x-kde4-urilist"; - void KUrl::List::populateMimeData( QMimeData* mimeData, const KUrl::MetaDataMap& metaData, MimeDataFlags flags ) const { - mimeData->setData(QString::fromLatin1("text/uri-list"), uriListData(*this)); - - if ( ( flags & KUrl::NoTextExport ) == 0 ) - { - QStringList prettyURLsList; - KUrl::List::ConstIterator uit = constBegin(); - const KUrl::List::ConstIterator uEnd = constEnd(); - for ( ; uit != uEnd ; ++uit ) { - QString prettyURL = (*uit).prettyUrl(); - if ( (*uit).protocol() == QLatin1String("mailto") ) { - prettyURL = (*uit).path(); // remove mailto: when pasting into konsole - } - prettyURLsList.append( prettyURL ); - } - - QByteArray plainTextData = prettyURLsList.join(QString(QLatin1Char('\n'))).toLocal8Bit(); - if( count() > 1 ) // terminate last line, unless it's the only line - plainTextData.append( "\n" ); - mimeData->setData( QString::fromLatin1("text/plain"), plainTextData ); - } - - if ( !metaData.isEmpty() ) - { - QByteArray metaDataData; // :) - for( KUrl::MetaDataMap::const_iterator it = metaData.begin(); it != metaData.end(); ++it ) - { - metaDataData += it.key().toUtf8(); - metaDataData += "$@@$"; - metaDataData += it.value().toUtf8(); - metaDataData += "$@@$"; - } - mimeData->setData( QString::fromLatin1("application/x-kio-metadata"), metaDataData ); - } + populateMimeDataHelper(*this, mimeData, metaData, flags); } - void KUrl::List::populateMimeData(const KUrl::List& mostLocalUrls, QMimeData* mimeData, const KUrl::MetaDataMap& metaData, MimeDataFlags flags) const { - // Export the most local urls as text/uri-list and plain text. - mostLocalUrls.populateMimeData(mimeData, metaData, flags); + const QString oldText = mimeData->text(); + KUrlMimeData::setUrls(*this, mostLocalUrls, mimeData); + + if ((flags & KUrl::NoTextExport) == 0) { + mimeData->setText(oldText); + } - mimeData->setData(QString::fromLatin1(s_kdeUriListMime), uriListData(*this)); + if (!metaData.isEmpty()) { + KUrlMimeData::setMetaData(metaData, mimeData); + } } bool KUrl::List::canDecode( const QMimeData *mimeData ) { - return mimeData->hasFormat(QString::fromLatin1("text/uri-list")) || - mimeData->hasFormat(QString::fromLatin1(s_kdeUriListMime)); + return mimeData->hasUrls(); } QStringList KUrl::List::mimeDataTypes() { - return QStringList() << QString::fromLatin1(s_kdeUriListMime) << QString::fromLatin1("text/uri-list"); + return KUrlMimeData::mimeDataTypes(); } KUrl::List KUrl::List::fromMimeData(const QMimeData *mimeData, DecodeOptions decodeOptions, KUrl::MetaDataMap* metaData) { - - KUrl::List uris; - const char* firstMimeType = s_kdeUriListMime; - const char* secondMimeType = "text/uri-list"; - if (decodeOptions == PreferLocalUrls) { - qSwap(firstMimeType, secondMimeType); - } - QByteArray payload = mimeData->data(QString::fromLatin1(firstMimeType)); - if (payload.isEmpty()) - payload = mimeData->data(QString::fromLatin1(secondMimeType)); - if ( !payload.isEmpty() ) { - int c = 0; - const char* d = payload.constData(); - 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++; - QByteArray s( d+f, c-f ); - if ( s[0] != '#' ) // non-comment? - uris.append( KUrl::fromMimeDataByteArray( s ) ); - // Skip junk - while ( c < payload.size() && d[c] && - ( d[c] == '\n' || d[c] == '\r' ) ) - ++c; - } - } - if ( metaData ) - { - const QByteArray metaDataPayload = mimeData->data(QLatin1String("application/x-kio-metadata")); - if ( !metaDataPayload.isEmpty() ) - { - QString str = QString::fromUtf8( metaDataPayload.data() ); - Q_ASSERT(str.endsWith(QLatin1String("$@@$"))); - str.truncate( str.length() - 4 ); - const QStringList lst = str.split(QLatin1String("$@@$")); - QStringList::ConstIterator it = lst.begin(); - bool readingKey = true; // true, then false, then true, etc. - QString key; - for ( ; it != lst.end(); ++it ) { - if ( readingKey ) - key = *it; - else - metaData->insert( key, *it ); - readingKey = !readingKey; - } - Q_ASSERT( readingKey ); // an odd number of items would be, well, odd ;-) - } - } - - return uris; + KUrlMimeData::DecodeOptions options = KUrlMimeData::PreferKdeUrls; + if (decodeOptions == PreferLocalUrls) + options = KUrlMimeData::PreferLocalUrls; + return KUrlMimeData::urlsFromMimeData(mimeData, options, metaData); } KUrl::List::operator QVariant() const { return qVariantFromValue(*this); } KUrl::List::operator QList<QUrl>() const { QList<QUrl> list; Q_FOREACH(const KUrl& url, *this) { list << url; } return list; } /// KUrl::KUrl() : QUrl(), d(0) { } KUrl::~KUrl() { } KUrl::KUrl( const QString &str ) : QUrl(), d(0) { if ( !str.isEmpty() ) { #ifdef Q_WS_WIN #ifdef DEBUG_KURL qDebug() << "KUrl::KUrl ( const QString &str = " << str.toAscii().data() << " )"; #endif QString pathToSet; // when it starts with file:// it's a url and must be valid. we don't care if the // path exist/ is valid or not if (!str.startsWith(QLatin1String("file://"))) pathToSet = removeSlashOrFilePrefix( QDir::fromNativeSeparators(str) ); if ( !pathToSet.isEmpty() ) { // we have a prefix indicating this is a local URL // remember the possible query using _setEncodedUrl(), then set up the correct path without query protocol part int index = pathToSet.lastIndexOf(QLatin1Char('?')); if (index == -1) setPath( pathToSet ); else { setPath( pathToSet.left( index ) ); _setQuery( pathToSet.mid( index + 1 ) ); } return; } #endif if ( str[0] == QLatin1Char('/') || str[0] == QLatin1Char('~') ) setPath( str ); else { _setEncodedUrl( str.toUtf8() ); } } } KUrl::KUrl( const char * str ) : QUrl(), d(0) { #ifdef Q_WS_WIN // true if @a c is letter #define IS_LETTER(c) \ ((c >= QLatin1Char('A') && c <= QLatin1Char('Z')) || (c >= QLatin1Char('a') && c <= QLatin1Char('z'))) // like IS_DRIVE_OR_DOUBLESLASH, but slash is prepended #define IS_SLASH_AND_DRIVE_OR_DOUBLESLASH_0 \ ( QLatin1Char(str[0]) == QLatin1Char('/') && IS_DRIVE_OR_DOUBLESLASH(IS_LETTER(QLatin1Char(str[1])), QLatin1Char(str[1]), QLatin1Char(str[2]), QLatin1Char(':'), QLatin1Char('/')) ) // like IS_DRIVE_OR_DOUBLESLASH, with characters == str[0] and str[1] #define IS_DRIVE_OR_DOUBLESLASH_0 \ ( IS_DRIVE_OR_DOUBLESLASH(IS_LETTER(QLatin1Char(str[0])), QLatin1Char(str[0]), QLatin1Char(str[1]), QLatin1Char(':'), QLatin1Char('/')) ) #if defined(DEBUG_KURL) qDebug() << "KUrl::KUrl " << " " << str; #endif if ( str && str[0] && str[1] && str[2] ) { if ( IS_SLASH_AND_DRIVE_OR_DOUBLESLASH_0 ) setPath( QString::fromUtf8( str+1 ) ); else if ( IS_DRIVE_OR_DOUBLESLASH_0 ) setPath( QString::fromUtf8( str ) ); } #endif if ( str && str[0] ) { if ( str[0] == '/' || str[0] == '~' ) setPath( QString::fromUtf8( str ) ); else _setEncodedUrl( str ); } } KUrl::KUrl( const QByteArray& str ) : QUrl(), d(0) { if ( !str.isEmpty() ) { #ifdef Q_WS_WIN #ifdef DEBUG_KURL qDebug() << "KUrl::KUrl " << " " << str.data(); #endif if ( IS_SLASH_AND_DRIVE_OR_DOUBLESLASH_0 ) setPath( QString::fromUtf8( str.mid( 1 ) ) ); else if ( IS_DRIVE_OR_DOUBLESLASH_0 ) setPath( QString::fromUtf8( str ) ); #else if ( str[0] == '/' || str[0] == '~' ) setPath( QString::fromUtf8( str.data() ) ); #endif else _setEncodedUrl( str ); } } KUrl::KUrl( const KUrl& _u ) : QUrl( _u ), d(0) { #if defined(Q_WS_WIN) && defined(DEBUG_KURL) qDebug() << "KUrl::KUrl(KUrl) " << " path " << _u.path() << " toLocalFile " << _u.toLocalFile(); #endif } KUrl::KUrl( const QUrl &u ) : QUrl( u ), d(0) { #if defined(Q_WS_WIN) && defined(DEBUG_KURL) qDebug() << "KUrl::KUrl(Qurl) " << " path " << u.path() << " toLocalFile " << u.toLocalFile(); #endif } KUrl::KUrl( const KUrl& _u, const QString& _rel_url ) : QUrl(), d(0) { #if defined(Q_WS_WIN) && defined(DEBUG_KURL) qDebug() << "KUrl::KUrl(KUrl,QString rel_url) " << " path " << _u.path() << " toLocalFile " << _u.toLocalFile(); #endif #if 0 if (_u.hasSubUrl()) // Operate on the last suburl, not the first { KUrl::List lst = split( _u ); KUrl u(lst.last(), _rel_url); lst.erase( --lst.end() ); lst.append( u ); *this = join( lst ); return; } #endif QString rUrl = _rel_url; // WORKAROUND THE RFC 1606 LOOPHOLE THAT ALLOWS // http:/index.html AS A VALID SYNTAX FOR RELATIVE // URLS. ( RFC 2396 section 5.2 item # 3 ) const int len = _u.scheme().length(); if ( !_u.host().isEmpty() && !rUrl.isEmpty() && rUrl.indexOf( _u.scheme(), 0, Qt::CaseInsensitive ) == 0 && rUrl[len] == QLatin1Char(':') && (rUrl[len+1] != QLatin1Char('/') || (rUrl[len+1] == QLatin1Char('/') && rUrl[len+2] != QLatin1Char('/'))) ) { rUrl.remove( 0, rUrl.indexOf( QLatin1Char(':') ) + 1 ); } if ( rUrl.isEmpty() ) { *this = _u; } else if ( rUrl[0] == QLatin1Char('#') ) { *this = _u; QByteArray strRef_encoded = rUrl.mid(1).toLatin1(); if ( strRef_encoded.isNull() ) strRef_encoded = ""; // we know there was an (empty) html ref, we saw the '#' setEncodedFragment( strRef_encoded ); } else if ( isRelativeUrl( rUrl ) ) { *this = _u; setFragment( QString() ); setEncodedQuery( QByteArray() ); QString strPath = path(); if ( rUrl[0] == QLatin1Char('/') ) { if ((rUrl.length() > 1) && (rUrl[1] == QLatin1Char('/'))) { setHost( QString() ); setPort( -1 ); // File protocol returns file:/// without host, strip // from rUrl if ( _u.isLocalFile() ) rUrl.remove(0, 2); } strPath.clear(); } else if ( rUrl[0] != QLatin1Char('?') ) { const int pos = strPath.lastIndexOf( QLatin1Char('/') ); if (pos >= 0) strPath.truncate(pos); strPath += QLatin1Char('/'); } else { if ( strPath.isEmpty() ) strPath = QLatin1Char('/'); } setPath( strPath ); //kDebug(kurlDebugArea()) << "url()=" << url() << " rUrl=" << rUrl; const KUrl tmp( url() + rUrl); //kDebug(kurlDebugArea()) << "assigning tmp=" << tmp.url(); *this = tmp; cleanPath(KeepDirSeparators); } else { const KUrl tmp( rUrl ); //kDebug(kurlDebugArea()) << "not relative; assigning tmp=" << tmp.url(); *this = tmp; // Preserve userinfo if applicable. if (!_u.userInfo().isEmpty() && userInfo().isEmpty() && (_u.host() == host()) && (_u.scheme() == scheme())) { setUserInfo( _u.userInfo() ); } cleanPath(KeepDirSeparators); } } KUrl& KUrl::operator=( const KUrl& _u ) { QUrl::operator=( _u ); return *this; } bool KUrl::operator==( const KUrl& _u ) const { return QUrl::operator==( _u ); } bool KUrl::operator==( const QString& _u ) const { KUrl u( _u ); return ( *this == u ); } KUrl::operator QVariant() const { return qVariantFromValue(*this); } #ifndef KDE_NO_DEPRECATED bool KUrl::cmp( const KUrl &u, bool ignore_trailing ) const { return equals( u, ignore_trailing ? CompareWithoutTrailingSlash : EqualsOptions(0) ); } #endif bool KUrl::equals( const KUrl &_u, const EqualsOptions& options ) const { if ( !isValid() || !_u.isValid() ) return false; if ( options & CompareWithoutTrailingSlash || options & CompareWithoutFragment ) { QString path1 = path((options & CompareWithoutTrailingSlash) ? RemoveTrailingSlash : LeaveTrailingSlash); QString path2 = _u.path((options & CompareWithoutTrailingSlash) ? RemoveTrailingSlash : LeaveTrailingSlash); if (options & AllowEmptyPath) { if (path1 == QLatin1String("/")) path1.clear(); if (path2 == QLatin1String("/")) path2.clear(); } #ifdef Q_WS_WIN const bool bLocal1 = isLocalFile(); const bool bLocal2 = _u.isLocalFile(); if ( !bLocal1 && bLocal2 || bLocal1 && !bLocal2 ) return false; // local files are case insensitive if ( bLocal1 && bLocal2 && 0 != QString::compare( path1, path2, Qt::CaseInsensitive ) ) return false; #endif if ( path1 != path2 ) return false; if ( scheme() == _u.scheme() && authority() == _u.authority() && // user+pass+host+port encodedQuery() == _u.encodedQuery() && (fragment() == _u.fragment() || options & CompareWithoutFragment ) ) return true; return false; } return ( *this == _u ); } QString KUrl::protocol() const { return scheme().toLower(); } void KUrl::setProtocol( const QString& proto ) { setScheme( proto ); } QString KUrl::user() const { return userName(); } void KUrl::setUser( const QString& user ) { setUserName( user ); } bool KUrl::hasUser() const { return !userName().isEmpty(); } QString KUrl::pass() const { return password(); } void KUrl::setPass( const QString& pass ) { setPassword( pass ); } bool KUrl::hasPass() const { return !password().isEmpty(); } bool KUrl::hasHost() const { return !host().isEmpty(); } bool KUrl::hasPath() const { return !path().isEmpty(); } KUrl KUrl::fromPath( const QString& text ) { KUrl u; u.setPath( text ); return u; } void KUrl::setFileName( const QString& _txt ) { setFragment( QString() ); int i = 0; while( i < _txt.length() && _txt[i] == QLatin1Char('/') ) ++i; QString tmp = i ? _txt.mid( i ) : _txt; QString path = this->path(); if ( path.isEmpty() ) #ifdef Q_OS_WIN path = isLocalFile() ? QDir::rootPath() : QLatin1String("/"); #else path = QDir::rootPath(); #endif else { int lastSlash = path.lastIndexOf( QLatin1Char('/') ); if ( lastSlash == -1) path.clear(); // there's only the file name, remove it else if ( !path.endsWith( QLatin1Char('/') ) ) path.truncate( lastSlash+1 ); // keep the "/" } path += tmp; setPath( path ); cleanPath(); } void KUrl::cleanPath( const CleanPathOption& options ) { //if (m_iUriMode != URL) return; const QString newPath = cleanpath(path(), !(options & KeepDirSeparators), false); if ( path() != newPath ) setPath( newPath ); // WABA: Is this safe when "/../" is encoded with %? //m_strPath_encoded = cleanpath(m_strPath_encoded, cleanDirSeparator, true); } static QString trailingSlash( KUrl::AdjustPathOption trailing, const QString &path ) { if ( trailing == KUrl::LeaveTrailingSlash ) { return path; } QString result = path; if ( trailing == KUrl::AddTrailingSlash ) { int len = result.length(); if ( (len == 0) || (result[ len - 1 ] != QLatin1Char('/')) ) result += QLatin1Char('/'); return result; } else if ( trailing == KUrl::RemoveTrailingSlash ) { if ( result == QLatin1String("/") ) return result; int len = result.length(); while (len > 1 && result[ len - 1 ] == QLatin1Char('/')) { len--; } result.truncate( len ); return result; } else { assert( 0 ); return result; } } void KUrl::adjustPath( AdjustPathOption trailing ) { #if 0 if (!m_strPath_encoded.isEmpty()) { m_strPath_encoded = trailingSlash( _trailing, m_strPath_encoded ); } #endif const QString newPath = trailingSlash( trailing, path() ); if ( path() != newPath ) setPath( newPath ); } QString KUrl::encodedPathAndQuery( AdjustPathOption trailing , const EncodedPathAndQueryOptions &options) const { QString encodedPath; #ifdef Q_OS_WIN // see KUrl::path() if (isLocalFile()) { // ### this is probably broken encodedPath = trailingSlash(trailing, QUrl::toLocalFile()); encodedPath = QString::fromLatin1(QUrl::toPercentEncoding(encodedPath, "!$&'()*+,;=:@/")); } else { encodedPath = trailingSlash(trailing, QString::fromLatin1(QUrl::encodedPath())); } #else encodedPath = trailingSlash(trailing, QString::fromLatin1(QUrl::encodedPath().data())); #endif if ((options & AvoidEmptyPath) && encodedPath.isEmpty()) { encodedPath.append(QLatin1Char('/')); } if (hasQuery()) { return encodedPath + QLatin1Char('?') + QString::fromLatin1(encodedQuery().data()); } else { return encodedPath; } } #if 0 void KUrl::setEncodedPath( const QString& _txt, int encoding_hint ) { m_strPath_encoded = _txt; decode( m_strPath_encoded, m_strPath, m_strPath_encoded, encoding_hint ); // Throw away encoding for local files, makes file-operations faster. if (m_strProtocol == "file") m_strPath_encoded.clear(); if ( m_iUriMode == Auto ) m_iUriMode = URL; } #endif void KUrl::setEncodedPathAndQuery( const QString& _txt ) { const int pos = _txt.indexOf(QLatin1Char('?')); if ( pos == -1 ) { setPath( QUrl::fromPercentEncoding( _txt.toLatin1() ) ); setEncodedQuery( QByteArray() ); } else { setPath( QUrl::fromPercentEncoding(_txt.toLatin1().left(pos)) ); _setQuery( _txt.right( _txt.length() - pos - 1 ) ); } } QString KUrl::path( AdjustPathOption trailing ) const { #ifdef Q_WS_WIN #ifdef DEBUG_KURL kWarning() << (isLocalFile() ? "converted to local file - the related call should be converted to toLocalFile()" : "") << QUrl::path(); #endif return trailingSlash( trailing, isLocalFile() ? QUrl::toLocalFile() : QUrl::path() ); #else return trailingSlash( trailing, QUrl::path() ); #endif } QString KUrl::toLocalFile( AdjustPathOption trailing ) const { if (hasHost() && isLocalFile()) { KUrl urlWithoutHost(*this); urlWithoutHost.setHost(QString()); return trailingSlash(trailing, urlWithoutHost.toLocalFile()); } #pragma message("FIXME: Remove #ifdef below once upstream bug, QTBUG-20322, is fixed. Also see BR# 194746.") #ifndef Q_WS_WIN if (isLocalFile()) { return trailingSlash(trailing, QUrl::path()); } #endif return trailingSlash(trailing, QUrl::toLocalFile()); } inline static bool hasSubUrl( const QUrl& url ); static inline bool isLocalFile( const QUrl& url ) { if ( ( url.scheme() != QLatin1String("file") ) || hasSubUrl( url ) ) return false; if (url.host().isEmpty() || (url.host() == QLatin1String("localhost"))) return true; char hostname[ 256 ]; hostname[ 0 ] = '\0'; if (!gethostname( hostname, 255 )) hostname[sizeof(hostname)-1] = '\0'; for(char *p = hostname; *p; p++) *p = tolower(*p); return (url.host() == QString::fromLatin1( hostname )); } bool KUrl::isLocalFile() const { return ::isLocalFile( *this ); } void KUrl::setFileEncoding(const QString &encoding) { if (!isLocalFile()) return; QString q = query(); if (!q.isEmpty() && q[0] == QLatin1Char('?')) q = q.mid(1); QStringList args = q.split(QLatin1Char('&'), QString::SkipEmptyParts); for(QStringList::Iterator it = args.begin(); it != args.end();) { QString s = QUrl::fromPercentEncoding( (*it).toLatin1() ); if (s.startsWith(QLatin1String("charset="))) it = args.erase(it); else ++it; } if (!encoding.isEmpty()) args.append(QLatin1String("charset=") + QString::fromLatin1(QUrl::toPercentEncoding(encoding).data())); if (args.isEmpty()) _setQuery(QString()); else _setQuery(args.join(QString(QLatin1Char('&')))); } QString KUrl::fileEncoding() const { if (!isLocalFile()) return QString(); QString q = query(); if (q.isEmpty()) return QString(); if (q[0] == QLatin1Char('?')) q = q.mid(1); const QStringList args = q.split(QLatin1Char('&'), QString::SkipEmptyParts); for(QStringList::ConstIterator it = args.begin(); it != args.end(); ++it) { QString s = QUrl::fromPercentEncoding((*it).toLatin1()); if (s.startsWith(QLatin1String("charset="))) return s.mid(8); } return QString(); } inline static bool hasSubUrl( const QUrl& url ) { // The isValid call triggers QUrlPrivate::validate which needs the full encoded url, // all this takes too much time for isLocalFile() const QString scheme = url.scheme(); if ( scheme.isEmpty() /*|| !isValid()*/ ) return false; const QString ref( url.fragment() ); if (ref.isEmpty()) return false; switch ( ref.at(0).unicode() ) { case 'g': if ( ref.startsWith(QLatin1String("gzip:")) ) return true; break; case 'b': if ( ref.startsWith(QLatin1String("bzip:")) || ref.startsWith(QLatin1String("bzip2:")) ) return true; break; case 'l': if ( ref.startsWith(QLatin1String("lzma:")) ) return true; break; case 'x': if ( ref.startsWith(QLatin1String("xz:")) ) return true; break; case 't': if ( ref.startsWith(QLatin1String("tar:")) ) return true; break; case 'a': if ( ref.startsWith(QLatin1String("ar:")) ) return true; break; case 'z': if ( ref.startsWith(QLatin1String("zip:")) ) return true; break; default: break; } if ( scheme == QLatin1String("error") ) // anything that starts with error: has suburls return true; return false; } bool KUrl::hasSubUrl() const { return ::hasSubUrl( *this ); } QString KUrl::url( AdjustPathOption trailing ) const { if (QString::compare(scheme(), QLatin1String("mailto"), Qt::CaseInsensitive) == 0) { // mailto urls should be prettified, see the url183433 testcase. return prettyUrl(trailing); } if ( trailing == AddTrailingSlash && !path().endsWith( QLatin1Char('/') ) ) { // -1 and 0 are provided by QUrl, but not +1, so that one is a bit tricky. // To avoid reimplementing toEncoded() all over again, I just use another QUrl // Let's hope this is fast, or not called often... QUrl newUrl( *this ); newUrl.setPath( path() + QLatin1Char('/') ); return QString::fromLatin1(newUrl.toEncoded().data()); } else if ( trailing == RemoveTrailingSlash) { const QString cleanedPath = trailingSlash(trailing, path()); if (cleanedPath == QLatin1String("/")) { if (path() != QLatin1String("/")) { QUrl fixedUrl = *this; fixedUrl.setPath(cleanedPath); return QLatin1String(fixedUrl.toEncoded(None).data()); } return QLatin1String(toEncoded(None).data()); } } return QString::fromLatin1(toEncoded(trailing == RemoveTrailingSlash ? StripTrailingSlash : None).data()); } static QString toPrettyPercentEncoding(const QString &input, bool forFragment) { QString result; result.reserve(input.length()); for (int i = 0; i < input.length(); ++i) { const QChar c = input.at(i); register ushort u = c.unicode(); if (u < 0x20 || (!forFragment && u == '?') // don't escape '?' in fragments, not needed and wrong (#173101) || u == '#' || u == '%' || (u == ' ' && (i+1 == input.length() || input.at(i+1).unicode() == ' '))) { static const char hexdigits[] = "0123456789ABCDEF"; result += QLatin1Char('%'); result += QLatin1Char(hexdigits[(u & 0xf0) >> 4]); result += QLatin1Char(hexdigits[u & 0xf]); } else { result += c; } } return result; } QString KUrl::prettyUrl( AdjustPathOption trailing ) const { // reconstruct the URL in a "pretty" form // a "pretty" URL is NOT suitable for data transfer. It's only for showing data to the user. // however, it must be parseable back to its original state, since // notably Konqueror displays it in the Location address. // A pretty URL is the same as a normal URL, except that: // - the password is removed // - the hostname is shown in Unicode (as opposed to ACE/Punycode) // - the pathname and fragment parts are shown in Unicode (as opposed to %-encoding) QString result = scheme(); if (!result.isEmpty()) { if(!authority().isEmpty() || result == QLatin1String("file")) result += QLatin1String("://"); else result += QLatin1Char(':'); } QString tmp = userName(); if (!tmp.isEmpty()) { result += QString::fromLatin1(QUrl::toPercentEncoding(tmp).data()); result += QLatin1Char('@'); } // Check if host is an ipv6 address tmp = host(); if (tmp.contains(QLatin1Char(':'))) result += QLatin1Char('[') + tmp + QLatin1Char(']'); else result += tmp; if (port() != -1) { result += QLatin1Char(':'); result += QString::number(port()); } tmp = path(); #ifdef Q_WS_WIN if (isLocalFile()) tmp.prepend(QLatin1Char('/')); // KUrl::path() returns toLocalFile() on windows so we need to add the / back to create a proper url #endif result += toPrettyPercentEncoding(tmp, false); // adjust the trailing slash, if necessary if (trailing == AddTrailingSlash && !tmp.endsWith(QLatin1Char('/'))) result += QLatin1Char('/'); else if (trailing == RemoveTrailingSlash && tmp.length() > 1 && tmp.endsWith(QLatin1Char('/'))) result.chop(1); if (hasQuery()) { result += QLatin1Char('?'); result += QString::fromLatin1(encodedQuery().data()); } if (hasFragment()) { result += QLatin1Char('#'); result += toPrettyPercentEncoding(fragment(), true); } return result; } #if 0 QString KUrl::prettyUrl( int _trailing, AdjustementFlags _flags) const { QString u = prettyUrl(_trailing); if (_flags & StripFileProtocol && u.startsWith("file://")) { u.remove(0, 7); #ifdef Q_WS_WIN return QDir::convertSeparators(u); #endif } return u; } #endif QString KUrl::pathOrUrl() const { return pathOrUrl(LeaveTrailingSlash); } QString KUrl::pathOrUrl(AdjustPathOption trailing) const { if ( isLocalFile() && fragment().isNull() && encodedQuery().isNull() ) { return toLocalFile(trailing); } else { return prettyUrl(trailing); } } // Used for text/uri-list in the mime data QString KUrl::toMimeDataString() const // don't fold this into populateMimeData, it's also needed by other code like konqdrag { if ( isLocalFile() ) { #if 1 return url(); #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. - const QString s = url( 0, KGlobal::locale()->fileEncodingMib() ); + const QString s = url(); if( !s.startsWith( QLatin1String ( "file://" ) )) { char hostname[257]; if ( gethostname( hostname, 255 ) == 0 ) { hostname[256] = '\0'; return QString( "file://" ) + hostname + s.mid( 5 ); } } #endif } if (hasPass()) { KUrl safeUrl(*this); safeUrl.setPassword(QString()); return safeUrl.url(); } return url(); } -KUrl KUrl::fromMimeDataByteArray( const QByteArray& str ) -{ - if ( str.startsWith( "file:" ) ) // krazy:exclude=strings - return KUrl( str /*, QTextCodec::codecForLocale()->mibEnum()*/ ); - - return KUrl( str /*, 106*/ ); // 106 is mib enum for utf8 codec; -} - KUrl::List KUrl::split( const KUrl& _url ) { QString ref; bool hasRef; KUrl::List lst; KUrl url = _url; while(true) { KUrl u = url; u.setFragment( QString() ); lst.append(u); if (url.hasSubUrl()) { url = KUrl(url.fragment()); } else { ref = url.fragment(); hasRef = url.hasFragment(); break; } } if ( hasRef ) { // Set HTML ref in all URLs. KUrl::List::Iterator it; for( it = lst.begin() ; it != lst.end(); ++it ) { (*it).setFragment( ref ); } } return lst; } KUrl::List KUrl::split( const QString& _url ) { return split(KUrl(_url)); } KUrl KUrl::join( const KUrl::List & lst ) { if (lst.isEmpty()) return KUrl(); KUrl tmp; bool first = true; QListIterator<KUrl> it(lst); it.toBack(); while (it.hasPrevious()) { KUrl u(it.previous()); if (!first) { u.setEncodedFragment(tmp.url().toLatin1() /* TODO double check encoding */); } tmp = u; first = false; } return tmp; } QString KUrl::fileName( const DirectoryOptions& options ) const { Q_ASSERT( options != 0 ); //Disallow options == false QString fname; if (hasSubUrl()) { // If we have a suburl, then return the filename from there const KUrl::List list = KUrl::split(*this); return list.last().fileName(options); } const QString path = this->path(); int len = path.length(); if ( len == 0 ) return fname; if (!(options & ObeyTrailingSlash) ) { while ( len >= 1 && path[ len - 1 ] == QLatin1Char('/') ) len--; } else if ( path[ len - 1 ] == QLatin1Char('/') ) return fname; // Does the path only consist of '/' characters ? if ( len == 1 && path[ 0 ] == QLatin1Char('/') ) return fname; // Skip last n slashes int n = 1; #if 0 if (!m_strPath_encoded.isEmpty()) { // This is hairy, we need the last unencoded slash. // Count in the encoded string how many encoded slashes follow the last // unencoded one. int i = m_strPath_encoded.lastIndexOf( QLatin1Char('/'), len - 1 ); QString fileName_encoded = m_strPath_encoded.mid(i+1); n += fileName_encoded.count("%2f", Qt::CaseInsensitive); } #endif int i = len; do { i = path.lastIndexOf( QLatin1Char('/'), i - 1 ); } while (--n && (i > 0)); // If ( i == -1 ) => the first character is not a '/' // So it's some URL like file:blah.tgz, return the whole path if ( i == -1 ) { if ( len == (int)path.length() ) fname = path; else // Might get here if _strip_trailing_slash is true fname = path.left( len ); } else { fname = path.mid( i + 1, len - i - 1 ); // TO CHECK } return fname; } void KUrl::addPath( const QString& _txt ) { if (hasSubUrl()) { KUrl::List lst = split( *this ); KUrl &u = lst.last(); u.addPath(_txt); *this = join( lst ); return; } //m_strPath_encoded.clear(); if ( _txt.isEmpty() ) return; QString strPath = path(); int i = 0; int len = strPath.length(); // Add the trailing '/' if it is missing if ( _txt[0] != QLatin1Char('/') && ( len == 0 || strPath[ len - 1 ] != QLatin1Char('/') ) ) strPath += QLatin1Char('/'); // No double '/' characters i = 0; const int _txtlen = _txt.length(); if ( strPath.endsWith( QLatin1Char('/') ) ) { while ( ( i < _txtlen ) && ( _txt[i] == QLatin1Char('/') ) ) ++i; } setPath( strPath + _txt.mid( i ) ); //kDebug(kurlDebugArea())<<"addPath: resultpath="<<path(); } QString KUrl::directory( const DirectoryOptions& options ) const { Q_ASSERT( options != 0 ); //Disallow options == false QString result = path(); //m_strPath_encoded.isEmpty() ? m_strPath : m_strPath_encoded; if ( !(options & ObeyTrailingSlash) ) result = trailingSlash( RemoveTrailingSlash, result ); if ( result.isEmpty() || result == QLatin1String ( "/" ) ) return result; int i = result.lastIndexOf( QLatin1Char('/') ); // If ( i == -1 ) => the first character is not a '/' // So it's some URL like file:blah.tgz, with no path if ( i == -1 ) return QString(); if ( i == 0 ) { return QString(QLatin1Char('/')); } #ifdef Q_WS_WIN if ( i == 2 && result[1] == QLatin1Char(':') ) { return result.left(3); } #endif if ( options & AppendTrailingSlash ) result = result.left( i + 1 ); else result = result.left( i ); //if (!m_strPath_encoded.isEmpty()) // result = decode(result); return result; } bool KUrl::cd( const QString& _dir ) { if ( _dir.isEmpty() || !isValid() ) return false; if (hasSubUrl()) { KUrl::List lst = split( *this ); KUrl &u = lst.last(); u.cd(_dir); *this = join( lst ); return true; } // absolute path ? #ifdef Q_OS_WIN if ( !QFileInfo(_dir).isRelative() ) #else if ( _dir[0] == QLatin1Char('/') ) #endif { //m_strPath_encoded.clear(); setPath( _dir ); setHTMLRef( QString() ); setEncodedQuery( QByteArray() ); return true; } // Users home directory on the local disk ? if (_dir[0] == QLatin1Char('~') && scheme() == QLatin1String ("file")) { //m_strPath_encoded.clear(); QString strPath = QDir::homePath(); strPath += QLatin1Char('/'); strPath += _dir.right( strPath.length() - 1 ); setPath( strPath ); setHTMLRef( QString() ); setEncodedQuery( QByteArray() ); return true; } // relative path // we always work on the past of the first url. // Sub URLs are not touched. // append '/' if necessary QString p = path(AddTrailingSlash); p += _dir; p = cleanpath( p, true, false ); setPath( p ); setHTMLRef( QString() ); setEncodedQuery( QByteArray() ); return true; } KUrl KUrl::upUrl( ) const { if (!isValid() || isRelative()) return KUrl(); if (!encodedQuery().isEmpty()) { KUrl u(*this); u.setEncodedQuery(QByteArray()); return u; } if (!hasSubUrl()) { KUrl u(*this); u.cd(QLatin1String("../")); return u; } // We have a subURL. KUrl::List lst = split( *this ); if (lst.isEmpty()) return KUrl(); // Huh? while (true) { KUrl &u = lst.last(); const QString old = u.path(); u.cd(QLatin1String("../")); if (u.path() != old) break; // Finished. if (lst.count() == 1) break; // Finished. lst.removeLast(); } return join( lst ); } QString KUrl::htmlRef() const { if ( !hasSubUrl() ) { return fragment(); } const List lst = split( *this ); return (*lst.begin()).fragment(); } QString KUrl::encodedHtmlRef() const { if ( !hasSubUrl() ) { return ref(); } const List lst = split( *this ); return (*lst.begin()).ref(); } void KUrl::setHTMLRef( const QString& _ref ) { if ( !hasSubUrl() ) { setFragment( _ref ); return; } List lst = split( *this ); (*lst.begin()).setFragment( _ref ); *this = join( lst ); } bool KUrl::hasHTMLRef() const { if ( !hasSubUrl() ) { return hasRef(); } const List lst = split( *this ); return (*lst.begin()).hasRef(); } void KUrl::setDirectory( const QString &dir) { if ( dir.endsWith(QLatin1Char('/'))) setPath(dir); else setPath(dir + QLatin1Char('/')); } void KUrl::setQuery( const QString &_txt ) { if (!_txt.isEmpty() && _txt[0] == QLatin1Char('?')) _setQuery( _txt.length() > 1 ? _txt.mid(1) : QString::fromLatin1("") /*empty, not null*/ ); else _setQuery( _txt ); } void KUrl::_setQuery( const QString& query ) { if ( query.isNull() ) { setEncodedQuery( QByteArray() ); } else if ( query.isEmpty() ) { setEncodedQuery(""); } else { setEncodedQuery( query.toLatin1() ); // already percent-escaped, so toLatin1 is ok } } QString KUrl::query() const { if (!hasQuery()) { return QString(); } return QString(QLatin1Char('?')) + QString::fromLatin1(encodedQuery().data()); } void KUrl::_setEncodedUrl(const QByteArray& url) { setEncodedUrl(url, QUrl::TolerantMode); if (!isValid()) // see unit tests referring to N183630/task 183874 setUrl(QString::fromUtf8(url.data()), QUrl::TolerantMode); } #ifndef KDE_NO_DEPRECATED bool urlcmp( const QString& _url1, const QString& _url2 ) { return QUrl( _url1, QUrl::TolerantMode ) == QUrl( _url2, QUrl::TolerantMode ); #if 0 // Both empty ? if ( _url1.isEmpty() && _url2.isEmpty() ) return true; // Only one empty ? if ( _url1.isEmpty() || _url2.isEmpty() ) return false; KUrl::List list1 = KUrl::split( _url1 ); KUrl::List list2 = KUrl::split( _url2 ); // Malformed ? if ( list1.isEmpty() || list2.isEmpty() ) return false; return ( list1 == list2 ); #endif } #endif #ifndef KDE_NO_DEPRECATED bool urlcmp( const QString& _url1, const QString& _url2, const KUrl::EqualsOptions& _options ) { // Both empty ? if (_url1.isEmpty() && _url2.isEmpty()) return true; // Only one empty ? if (_url1.isEmpty() || _url2.isEmpty()) return false; KUrl u1(_url1); KUrl u2(_url2); return u1.equals(u2, _options); #if 0 // kde3 code that supported nested urls KUrl::List list1 = KUrl::split( _url1 ); KUrl::List list2 = KUrl::split( _url2 ); // Malformed ? if ( list1.isEmpty() || list2.isEmpty() ) return false; int size = list1.count(); if ( list2.count() != size ) return false; if ( _ignore_ref ) { (*list1.begin()).setRef(QString()); (*list2.begin()).setRef(QString()); } KUrl::List::Iterator it1 = list1.begin(); KUrl::List::Iterator it2 = list2.begin(); for( ; it1 != list1.end() ; ++it1, ++it2 ) if ( !(*it1).equals( *it2, _ignore_trailing ) ) return false; return true; #endif } #endif // static #ifndef KDE_NO_DEPRECATED KUrl KUrl::fromPathOrUrl( const QString& text ) { KUrl url; if ( !text.isEmpty() ) { if (!QDir::isRelativePath(text) || text[0] == QLatin1Char('~')) url.setPath( text ); else url = KUrl( text ); } return url; } #endif static QString _relativePath(const QString &base_dir, const QString &path, bool &isParent) { QString _base_dir(QDir::cleanPath(base_dir)); QString _path(QDir::cleanPath(path.isEmpty() || (path[0] != QLatin1Char('/')) ? _base_dir+QLatin1Char('/')+path : path)); if (_base_dir.isEmpty()) return _path; if (_base_dir[_base_dir.length()-1] != QLatin1Char('/')) _base_dir.append(QLatin1Char('/') ); const QStringList list1 = _base_dir.split(QLatin1Char('/'), QString::SkipEmptyParts); const QStringList list2 = _path.split(QLatin1Char('/'), QString::SkipEmptyParts); // Find where they meet int level = 0; int maxLevel = qMin(list1.count(), list2.count()); while((level < maxLevel) && (list1[level] == list2[level])) level++; QString result; // Need to go down out of the first path to the common branch. for(int i = level; i < list1.count(); i++) result.append(QLatin1String("../")); // Now up up from the common branch to the second path. for(int i = level; i < list2.count(); i++) result.append(list2[i]).append(QLatin1Char('/')); if ((level < list2.count()) && (path[path.length()-1] != QLatin1Char('/'))) result.truncate(result.length()-1); isParent = (level == list1.count()); return result; } QString KUrl::relativePath(const QString &base_dir, const QString &path, bool *isParent) { bool parent = false; QString result = _relativePath(base_dir, path, parent); if (parent) result.prepend(QLatin1String("./")); if (isParent) *isParent = parent; return result; } QString KUrl::relativeUrl(const KUrl &base_url, const KUrl &url) { if ((url.protocol() != base_url.protocol()) || (url.host() != base_url.host()) || (url.port() && url.port() != base_url.port()) || (url.hasUser() && url.user() != base_url.user()) || (url.hasPass() && url.pass() != base_url.pass())) { return url.url(); } QString relURL; if ((base_url.path() != url.path()) || (base_url.query() != url.query())) { bool dummy; QString basePath = base_url.directory(KUrl::ObeyTrailingSlash); relURL = _relativePath(basePath, url.path(), dummy); // was QUrl::toPercentEncoding() but why? relURL += url.query(); } if ( url.hasRef() ) { relURL += QLatin1Char('#'); relURL += url.ref(); } if ( relURL.isEmpty() ) return QLatin1String("./"); return relURL; } void KUrl::setPath( const QString& _path ) { #if defined(Q_WS_WIN) && defined(DEBUG_KURL) qDebug() << "KUrl::setPath " << " " << _path.toAscii().data(); #endif if ( scheme().isEmpty() ) setScheme( QLatin1String( "file" ) ); #pragma message("KDE5 TODO: Remove tildeExpand feature for local paths") #if 0 QString path = KShell::tildeExpand( _path ); if (path.isEmpty()) #endif QString path = _path; #ifdef Q_WS_WIN const int len = path.length(); if( len == 2 && IS_LETTER(path[0]) && path[1] == QLatin1Char(':') ) path += QLatin1Char('/'); //This is necessary because QUrl has the "path" part including the first slash //Without this QUrl doesn't understand that this is a path, and some operations fail //e.g. C:/blah needs to become /C:/blah else if( len > 0 && path[0] != QLatin1Char('/') && scheme() == QLatin1String( "file" ) ) path = QLatin1Char('/') + path; #endif QUrl::setPath( path ); } #if 0 // this would be if we didn't decode '+' into ' ' QMap< QString, QString > KUrl::queryItems( int options ) const { QMap< QString, QString > result; const QList<QPair<QString, QString> > items = QUrl::queryItems(); QPair<QString, QString> item; Q_FOREACH( item, items ) { result.insert( options & CaseInsensitiveKeys ? item.first.toLower() : item.first, item.second ); } return result; } #endif QMap< QString, QString > KUrl::queryItems( const QueryItemsOptions &options ) const { const QString strQueryEncoded = QString::fromLatin1(encodedQuery().data()); if ( strQueryEncoded.isEmpty() ) return QMap<QString,QString>(); QMap< QString, QString > result; const QStringList items = strQueryEncoded.split( QLatin1Char('&'), QString::SkipEmptyParts ); for ( QStringList::const_iterator it = items.begin() ; it != items.end() ; ++it ) { const int equal_pos = (*it).indexOf(QLatin1Char('=')); if ( equal_pos > 0 ) { // = is not the first char... QString name = (*it).left( equal_pos ); if ( options & CaseInsensitiveKeys ) name = name.toLower(); QString value = (*it).mid( equal_pos + 1 ); if ( value.isEmpty() ) result.insert( name, QString::fromLatin1("") ); else { // ### why is decoding name not necessary? value.replace( QLatin1Char('+'), QLatin1Char(' ') ); // + in queries means space result.insert( name, QUrl::fromPercentEncoding( value.toLatin1() ) ); } } else if ( equal_pos < 0 ) { // no = QString name = (*it); if ( options & CaseInsensitiveKeys ) name = name.toLower(); result.insert( name, QString() ); } } return result; } QString KUrl::queryItem( const QString& _item ) const { const QString strQueryEncoded = QString::fromLatin1(encodedQuery().data()); const QString item = _item + QLatin1Char('='); if ( strQueryEncoded.length() <= 1 ) return QString(); const QStringList items = strQueryEncoded.split( QString(QLatin1Char('&')), QString::SkipEmptyParts ); const int _len = item.length(); for ( QStringList::ConstIterator it = items.begin(); it != items.end(); ++it ) { if ( (*it).startsWith( item ) ) { if ( (*it).length() > _len ) { QString str = (*it).mid( _len ); str.replace( QLatin1Char('+'), QLatin1Char(' ') ); // + in queries means space. return QUrl::fromPercentEncoding( str.toLatin1() ); } else // empty value return QString::fromLatin1(""); } } return QString(); } void KUrl::addQueryItem( const QString& _item, const QString& _value ) { QString item = _item + QLatin1Char('='); QString value = QString::fromLatin1(QUrl::toPercentEncoding(_value).data()); QString strQueryEncoded = QString::fromLatin1(encodedQuery().data()); if (!strQueryEncoded.isEmpty()) strQueryEncoded += QLatin1Char('&'); strQueryEncoded += item + value; setEncodedQuery( strQueryEncoded.toLatin1() ); } void KUrl::populateMimeData( QMimeData* mimeData, const MetaDataMap& metaData, MimeDataFlags flags ) const { - KUrl::List lst( *this ); - lst.populateMimeData( mimeData, metaData, flags ); + populateMimeDataHelper(KUrl::List(*this), mimeData, metaData, flags); } bool KUrl::hasRef() const { return hasFragment(); } void KUrl::setRef( const QString& fragment ) { if ( fragment.isEmpty() ) // empty or null setFragment( fragment ); else setFragment( QUrl::fromPercentEncoding( fragment.toLatin1() ) ); } QString KUrl::ref() const { if ( fragment().isNull() ) return QString(); else return QString::fromLatin1( QUrl::toPercentEncoding( fragment() ).data() ); } bool KUrl::isParentOf( const KUrl& u ) const { return QUrl::isParentOf( u ) || equals( u, CompareWithoutTrailingSlash ); } uint qHash(const KUrl& kurl) { // qHash(kurl.url()) was the worse implementation possible, since QUrl::toEncoded() // had to concatenate the bits of the url into the full url every time. return qHash(kurl.protocol()) ^ qHash(kurl.path()) ^ qHash(kurl.fragment()) ^ qHash(kurl.query()); } diff --git a/staging/kcoreaddons/src/io/kurl.h b/staging/kcoreaddons/src/io/kurl.h index 5d4060b6ca..7616a08cfb 100644 --- a/staging/kcoreaddons/src/io/kurl.h +++ b/staging/kcoreaddons/src/io/kurl.h @@ -1,1180 +1,1192 @@ // -*- c-basic-offset: 2 -*- /* This file is part of the KDE libraries * Copyright (C) 1999 Torben Weis <weis@kde.org> * Copyright (C) 2005-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. */ #ifndef kurl_h #define kurl_h #include <kcoreaddons_export.h> #include <QtCore/QVariant> #include <QtCore/QUrl> #include <QtCore/QMap> class QStringList; class QMimeData; class KUrlPrivate; // maybe we should encapsulate QUrl instead of inheriting from it. // this would even allow us to inherit from KUri instead. // and this way hacks like setPath() would be less ugly, and we could avoid // half KDE code using setScheme() and the other half using setProtocol(), etc. // (DF) /** * \class KUrl kurl.h <KUrl> * * Represents and parses a URL. * * A prototypical URL looks like: * \code * protocol://user:password\@hostname:port/path/to/file.ext#reference * \endcode * * KUrl handles escaping of URLs. This means that the specification * of a full URL will differ from the corresponding string that would specify a * local file or directory in file-operations like fopen. This is because an URL * doesn't allow certain characters and escapes them. (e.g. '#'->"%23", space->"%20") * (In a URL the hash-character '#' is used to specify a "reference", i.e. the position * within a document). * * The constructor KUrl(const QString&) expects a string properly escaped, * or at least non-ambiguous. * If you have the absolute path you should use KUrl::fromPath(const QString&). * \code * KUrl kurl = KUrl::fromPath("/bar/#foo#"); * QString url = kurl.url(); // -> "file:///bar/%23foo%23" * \endcode * * If you have the URL of a local file or directory and need the absolute path, * you would use toLocalFile(). * \code * KUrl url( "file:///bar/%23foo%23" ); * ... * if ( url.isLocalFile() ) * QString path = url.toLocalFile(); // -> "/bar/#foo#" * \endcode * * This must also be considered when you have separated directory and file * strings and need to put them together. * While you can simply concatenate normal path strings, you must take care if * the directory-part is already an escaped URL. * (This might be needed if the user specifies a relative path, and your * program supplies the rest from elsewhere.) * * Wrong: * \code * QString dirUrl = "file:///bar/"; * QString fileName = "#foo#"; * QString invalidURL = dirUrl + fileName; // -> "file:///bar/#foo#" won't behave like you would expect. * \endcode * Instead you should use addPath(): * Right: * \code * KUrl url( "file:///bar/" ); * QString fileName = "#foo#"; * url.addPath( fileName ); * QString validURL = url.url(); // -> "file:///bar/%23foo%23" * \endcode * * Also consider that some URLs contain the password, but this shouldn't be * visible. Your program should use prettyUrl() every time it displays a * URL, whether in the GUI or in debug output or... * * \code * KUrl url( "ftp://name:password@ftp.faraway.org/bar/%23foo%23"); * QString visibleURL = url.prettyUrl(); // -> "ftp://name@ftp.faraway.org/bar/%23foo%23" * \endcode * Note that prettyUrl() doesn't change the character escapes (like "%23"). * Otherwise the URL would be invalid and the user wouldn't be able to use it in another * context. * */ class KCOREADDONS_EXPORT KUrl : public QUrl // krazy:exclude=dpointer,qclasses (krazy can't deal with embedded classes) { public: typedef QMap<QString, QString> MetaDataMap; enum MimeDataFlags { DefaultMimeDataFlags = 0, NoTextExport = 1 }; /** * Options to be used in adjustPath */ enum AdjustPathOption { /** * strips a trailing '/', except when the path is already just "/". */ RemoveTrailingSlash, /** * Do not change the path. */ LeaveTrailingSlash, /** * adds a trailing '/' if there is none yet */ AddTrailingSlash }; /** * \class List kurl.h <KUrl> * * KUrl::List is a QList that contains KUrls with a few * convenience methods. * @see KUrl * @see QList */ class KCOREADDONS_EXPORT List : public QList<KUrl> //krazy:exclude=dpointer (just some convenience methods) { public: /** * Creates an empty List. */ List() { } /** * Creates a list that contains the given URL as only * item. * @param url the url to add. */ List(const KUrl &url); /** * Creates a list that contains the URLs from the given * list of strings. * @param list the list containing the URLs as strings */ List(const QStringList &list); /** * Creates a list that contains the URLs from the given QList<KUrl>. * @param list the list containing the URLs */ List(const QList<KUrl> &list); /** * Creates a list that contains the URLs from the given QList<KUrl>. * @param list the list containing the URLs * @since 4.7 */ List(const QList<QUrl> &list); /** * Converts the URLs of this list to a list of strings. * @return the list of strings */ QStringList toStringList() const; /** * Converts the URLs of this list to a list of strings. * * @param trailing use to add or remove a trailing slash to/from the path. * * @return the list of strings * * @since 4.6 */ QStringList toStringList(KUrl::AdjustPathOption trailing) const; /** * Converts this KUrl::List to a QVariant, this allows to use KUrl::List * in QVariant() constructor */ operator QVariant() const; /** * Converts this KUrl::List into a list of QUrl instances. * @since 4.7 */ operator QList<QUrl>() const; /** * Adds URLs data into the given QMimeData. * * By default, populateMimeData also exports the URLs as plain text, for e.g. dropping * onto a text editor. * But in some cases this might not be wanted, e.g. if adding other mime data * which provides better plain text data. * * WARNING: do not call this method multiple times on the same mimedata object, * you can add urls only once. But you can add other things, e.g. images, XML... * * @param mimeData the QMimeData instance used to drag or copy this URL * @param metaData KIO metadata shipped in the mime data, which is used for instance to * set a correct HTTP referrer (some websites require it for downloading e.g. an image) * @param flags set NoTextExport to prevent setting plain/text data into @p mimeData - * In such a case, setExportAsText( false ) should be called. + * + * @deprecated use QMimeData::setUrls, followed by KUrlMimeData::setMetaData if you have metadata. */ - void populateMimeData( QMimeData* mimeData, +#ifndef KDE_NO_DEPRECATED + KCOREADDONS_DEPRECATED void populateMimeData( QMimeData* mimeData, const KUrl::MetaDataMap& metaData = MetaDataMap(), MimeDataFlags flags = DefaultMimeDataFlags ) const; +#endif /** * Adds URLs into the given QMimeData. * * This should add both the KDE-style URLs (eg: desktop:/foo) and * the "most local" version of the URLs (eg: * file:///home/jbloggs/Desktop/foo) to the mimedata. * * This method should be called on the KDE-style URLs. * * @code * QMimeData* mimeData = new QMimeData(); * * KUrl::List kdeUrls; * kdeUrls << "desktop:/foo"; * kdeUrls << "desktop:/bar"; * * KUrl::List normalUrls; * normalUrls << "file:///home/jbloggs/Desktop/foo"; * normalUrls << "file:///home/jbloggs/Desktop/bar"; * * kdeUrls.populateMimeData(normalUrls, mimeData); * @endcode * * @param mostLocalUrls the "most local" urls * @param mimeData the mime data object to populate * @param metaData KIO metadata shipped in the mime data, which is * used for instance to set a correct HTTP referrer * (some websites require it for downloading e.g. an * image) * @param flags set NoTextExport to prevent setting plain/text - * data into @p mimeData. In such a case, - * <code>setExportAsText(false)</code> should be called. + * data into @p mimeData. * @since 4.2 + * @deprecated use KUrlMimeData::setUrls, followed by KUrlMimeData::setMetaData if you have metadata. */ - void populateMimeData(const KUrl::List& mostLocalUrls, +#ifndef KDE_NO_DEPRECATED + KCOREADDONS_DEPRECATED void populateMimeData(const KUrl::List& mostLocalUrls, QMimeData* mimeData, const KUrl::MetaDataMap& metaData = MetaDataMap(), MimeDataFlags flags = DefaultMimeDataFlags) const; +#endif /** * Return true if @p mimeData contains URI data + * @deprecated use QMimeData::hasUrls */ - static bool canDecode( const QMimeData *mimeData ); +#ifndef KDE_NO_DEPRECATED + KCOREADDONS_DEPRECATED static bool canDecode( const QMimeData *mimeData ); +#endif /** - * Return the list of mimeTypes that can be decoded by fromMimeData - */ - static QStringList mimeDataTypes(); + * Return the list of mimeTypes that can be decoded by fromMimeData + * @deprecated use KUrlMimeData::mimeDataTypes + */ +#ifndef KDE_NO_DEPRECATED + KCOREADDONS_DEPRECATED static QStringList mimeDataTypes(); +#endif /** * Flags to be used in fromMimeData. * @since 4.2.3 + * @deprecated use KUrlMimeData */ enum DecodeOptions { /** * When the mimedata contains both KDE-style URLs (eg: desktop:/foo) and * the "most local" version of the URLs (eg: file:///home/dfaure/Desktop/foo), * decode it as local urls. Useful in paste/drop operations that end up calling KIO, * so that urls from other users work as well. */ PreferLocalUrls, /** * When the mimedata contains both KDE-style URLs (eg: desktop:/foo) and * the "most local" version of the URLs (eg: file:///home/dfaure/Desktop/foo), * decode it as the KDE-style URL. Useful in DnD code e.g. when moving icons, * and the kde-style url is used as identifier for the icons. */ PreferKdeUrls }; /** * Extract a list of KUrls from the contents of @p mimeData. * Decoding will fail if @p mimeData does not contain any URLs, or if at * least one extracted URL is not valid. * @param mimeData the mime data to extract from; cannot be 0 * @param decodeOptions options for decoding * @param metaData optional pointer to a map holding the metadata * @return the list of urls * @since 4.2.3 + * @deprecated use KUrlMimeData::urlsFromMimeData */ - static KUrl::List fromMimeData( const QMimeData *mimeData, +#ifndef KDE_NO_DEPRECATED + KCOREADDONS_DEPRECATED static KUrl::List fromMimeData( const QMimeData *mimeData, DecodeOptions decodeOptions = PreferKdeUrls, KUrl::MetaDataMap* metaData = 0 ); - +#endif }; /** * Constructs an empty URL. */ KUrl(); /** * Destructs the KUrl object. */ ~KUrl(); /** * Usual constructor, to construct from a string. * @param urlOrPath An encoded URL or a path. */ KUrl( const QString& urlOrPath ); /** * Constructor taking a char * @p urlOrPath, which is an _encoded_ representation * of the URL, exactly like the usual constructor. This is useful when * the URL, in its encoded form, is strictly ascii. * @param urlOrPath An encoded URL, or a path. */ explicit KUrl( const char * urlOrPath ); /** * Constructor taking a QByteArray @p urlOrPath, which is an _encoded_ representation * of the URL, exactly like the usual constructor. This is useful when * the URL, in its encoded form, is strictly ascii. * @param urlOrPath An encoded URL, or a path. */ explicit KUrl( const QByteArray& urlOrPath ); /** * Copy constructor. * @param u the KUrl to copy */ KUrl( const KUrl& u ); /** * Converts from a QUrl. * @param u the QUrl */ KUrl( const QUrl &u ); //krazy:exclude=qclasses /** * Constructor allowing relative URLs. * * @param _baseurl The base url. * @param _rel_url A relative or absolute URL. * If this is an absolute URL then @p _baseurl will be ignored. * If this is a relative URL it will be combined with @p _baseurl. * Note that _rel_url should be encoded too, in any case. * So do NOT pass a path here (use setPath or addPath instead). */ KUrl( const KUrl& _baseurl, const QString& _rel_url ); /** * Returns the protocol for the URL (i.e., file, http, etc.), lowercased. * @see QUrl::scheme */ QString protocol() const; /** * Sets the protocol for the URL (i.e., file, http, etc.) * @param proto the new protocol of the URL (without colon) */ void setProtocol( const QString& proto ); /** * Returns the decoded user name (login, user id, ...) included in the URL. * @return the user name or QString() if there is no user name */ QString user() const; /** * Sets the user name (login, user id, ...) included in the URL. * * Special characters in the user name will appear encoded in the URL. * @param user the name of the user or QString() to remove the user */ void setUser( const QString& user ); /** * Test to see if this URL has a user name included in it. * @return true if the URL has an non-empty user name */ bool hasUser() const; /** * Returns the decoded password (corresponding to user()) included in the URL. * @return the password or QString() if it does not exist **/ QString pass() const; /** * Sets the password (corresponding to user()) included in the URL. * * Special characters in the password will appear encoded in the URL. * Note that a password can only appear in a URL string if you also set * a user. * @param pass the password to set or QString() to remove the password * @see setUser * @see hasUser **/ void setPass( const QString& pass ); /** * Test to see if this URL has a password included in it. * @return true if there is a non-empty password set **/ bool hasPass() const; /** * Test to see if this URL has a hostname included in it. * @return true if the URL has a host **/ bool hasHost() const; /** * @param trailing use to add or remove a trailing slash to/from the path. see adjustPath * @return The current decoded path. This does not include the query. Can * be QString() if no path is set. */ QString path( AdjustPathOption trailing = LeaveTrailingSlash ) const; /** * @param trailing use to add or remove a trailing slash to/from the local path. see adjustPath * @return The current local path. Can * be QString() if no path is set. */ QString toLocalFile( AdjustPathOption trailing = LeaveTrailingSlash ) const; /// \reimp so that KUrl u; u.setPath(path); implies "file" protocol. void setPath( const QString& path ); /** * Test to see if this URL has a path is included in it. * @return true if there is a path **/ bool hasPath() const; /** * Options to be used in cleanPath */ enum CleanPathOption { /** * if set, occurrences of consecutive directory separators * (e.g. /foo//bar) are cleaned up as well. (set by default) */ SimplifyDirSeparators = 0x00, /** * The opposite of SimplifyDirSeparators. */ KeepDirSeparators = 0x01 }; Q_DECLARE_FLAGS(CleanPathOptions,CleanPathOption) /** * Resolves "." and ".." components in path. * Some servers seem not to like the removal of extra '/' * even though it is against the specification in RFC 2396. * * @param options use KeepDirSeparators if you don't want to remove consecutive * occurrences of directory separator */ void cleanPath(const CleanPathOption& options = SimplifyDirSeparators); /** * Add or remove a trailing slash to/from the path. * * If the URL has no path, then no '/' is added * anyway. And on the other side: If the path is "/", then this * character won't be stripped. Reason: "ftp://weis\@host" means something * completely different than "ftp://weis\@host/". So adding or stripping * the '/' would really alter the URL, while "ftp://host/path" and * "ftp://host/path/" mean the same directory. * * @param trailing RemoveTrailingSlash strips any trailing '/' and * AddTrailingSlash adds a trailing '/' if there is none yet */ void adjustPath(AdjustPathOption trailing); /** * This is useful for HTTP. It looks first for '?' and decodes then. * The encoded path is the concatenation of the current path and the query. * @param _txt the new path and query. */ void setEncodedPathAndQuery( const QString& _txt ); #if 0 /** * Sets the (already encoded) path * @param _txt the new path * @see QTextCodec::mibEnum() */ void setEncodedPath(const QString& _txt ); #endif /** * Option to be used in encodedPathAndQuery **/ enum EncodedPathAndQueryOption { /** * Permit empty path (default) */ PermitEmptyPath=0x00, /** * If set to true then an empty path is substituted by "/" * (this is the opposite of PermitEmptyPath) */ AvoidEmptyPath=0x01 }; Q_DECLARE_FLAGS( EncodedPathAndQueryOptions, EncodedPathAndQueryOption) /** * Returns the encoded path and the query. * * @param trailing add or remove a trailing '/', see adjustPath * @param options a set of flags from EncodedPathAndQueryOption * @return The concatenation of the encoded path , '?' and the encoded query. * */ QString encodedPathAndQuery( AdjustPathOption trailing = LeaveTrailingSlash, const EncodedPathAndQueryOptions &options = PermitEmptyPath ) const; /** * @param query This is considered to be encoded. This has a good reason: * The query may contain the 0 character. * * The query should start with a '?'. If it doesn't '?' is prepended. */ void setQuery( const QString& query ); /** * Returns the query of the URL. * The query may contain the 0 character. * If a query is present it always starts with a '?'. * A single '?' means an empty query. * An empty string means no query. * @return The encoded query, or QString() if there is none. */ QString query() const; /** * Returns the reference (or "fragment") of the URL. * The reference is @em never decoded automatically. * @return the undecoded reference, or QString() if there is none */ QString ref() const; /** * Sets the reference/fragment part (everything after '#'). * If you have an encoded fragment already (as a QByteArray), you can call setFragment directly. * @param fragment the unencoded reference (or QString() to remove it). */ void setRef( const QString& fragment ); /** * Checks whether the URL has a reference/fragment part. * @return true if the URL has a reference part. In a URL like * http://www.kde.org/kdebase.tar#tar:/README it would * return true, too. */ bool hasRef() const; /** * Returns the HTML reference (the part of the URL after "#"). * @return The HTML-style reference. * @see split * @see hasSubUrl * @see encodedHtmlRef */ QString htmlRef() const; /** * Returns the HTML reference (the part of the URL after "#") in * encoded form. * @return The HTML-style reference in its original form. */ QString encodedHtmlRef() const; /** * Sets the HTML-style reference. * * @param _ref The new reference. This is considered to be @em not encoded in * contrast to setRef(). Use QString() to remove it. * @see htmlRef() */ void setHTMLRef( const QString& _ref ); /** * Checks whether there is a HTML reference. * @return true if the URL has an HTML-style reference. * @see htmlRef() */ bool hasHTMLRef() const; /** * Checks whether the file is local. * @return true if the file is a plain local file (i.e. uses the file protocol * and no hostname, or the local hostname). * When isLocalFile returns true, you can use toLocalFile to read the file contents. * Otherwise you need to use KIO (e.g. KIO::get). */ bool isLocalFile() const; /** * Adds encoding information to url by adding a "charset" parameter. If there * is already a charset parameter, it will be replaced. * @param encoding the encoding to add or QString() to remove the * encoding. */ void setFileEncoding(const QString &encoding); /** * Returns encoding information from url, the content of the "charset" * parameter. * @return An encoding suitable for QTextCodec::codecForName() * or QString() if not encoding was specified. */ QString fileEncoding() const; /** * Checks whether the URL has any sub URLs. See split() * for examples for sub URLs. * @return true if the file has at least one sub URL. * @see split */ bool hasSubUrl() const; /** * Adds to the current path. * Assumes that the current path is a directory. @p txt is appended to the * current path. The function adds '/' if needed while concatenating. * This means it does not matter whether the current path has a trailing * '/' or not. If there is none, it becomes appended. If @p txt * has a leading '/' then this one is stripped. * * @param txt The text to add. It is considered to be decoded. */ void addPath( const QString& txt ); /** * Options for queryItems. Currently, only one option is * defined: * * @param CaseInsensitiveKeys normalize query keys to lowercase. **/ enum QueryItemsOption { CaseInsensitiveKeys = 1 }; Q_DECLARE_FLAGS(QueryItemsOptions,QueryItemsOption) /** * Returns the list of query items as a map mapping keys to values. * * This does the same as QUrl::queryItems(), except that it * decodes "+" into " " in the value, supports CaseInsensitiveKeys, * and returns a different data type. * * @param options any of QueryItemsOption <em>or</em>ed together. * * @return the map of query items or the empty map if the url has no * query items. */ QMap< QString, QString > queryItems( const QueryItemsOptions& options = 0 ) const; // #### TODO port the above queryItems to look more like QUrl's //using QUrl::queryItems; // temporary /** * Returns the value of a certain query item. * * This does the same as QUrl::queryItemValue(), except that it * decodes "+" into " " in the value. * * @param item Item whose value we want * * @return the value of the given query item name or QString() if the * specified item does not exist. */ QString queryItem(const QString &item) const; /** * Add an additional query item. * To replace an existing query item, the item should first be * removed with removeQueryItem() * * @param _item Name of item to add * @param _value Value of item to add */ void addQueryItem( const QString& _item, const QString& _value ); /** * Sets the filename of the path. * In comparison to addPath() this function does not assume that the current * path is a directory. This is only assumed if the current path ends with '/'. * * Any reference is reset. * * @param _txt The filename to be set. It is considered to be decoded. If the * current path ends with '/' then @p _txt int just appended, otherwise * all text behind the last '/' in the current path is erased and * @p _txt is appended then. It does not matter whether @p _txt starts * with '/' or not. */ void setFileName( const QString&_txt ); /** * option to be used in fileName and directory */ enum DirectoryOption { /** * This tells whether a trailing '/' should be ignored. * * If the flag is not set, for both <tt>file:///hallo/torben/</tt> and <tt>file:///hallo/torben</tt> * the fileName is "torben" and the path is "hallo" * * If the flag is set, then everything behind the last '/'is considered to be the filename. * So "hallo/torben" will be the path and the filename will be empty. */ ObeyTrailingSlash = 0x02, /** * tells whether the returned result should end with '/' or not. * If the flag is set, '/' is added to the end of the path * * If the path is empty or just "/" then this flag has no effect. * * This option should only be used in directory(), it has no effect in fileName() */ AppendTrailingSlash = 0x04, /** * Opposite of ObeyTrailingSlash (default) * fileName("file:/foo/") and fileName("file:/foo") is "foo" in both cases. */ IgnoreTrailingSlash = 0x01 }; Q_DECLARE_FLAGS(DirectoryOptions,DirectoryOption) /** * Returns the filename of the path. * @param options a set of DirectoryOption flags. (StripTrailingSlashFromResult has no effect) * @return The filename of the current path. The returned string is decoded. Null * if there is no file (and thus no path). */ QString fileName( const DirectoryOptions& options = IgnoreTrailingSlash ) const; /** * Returns the directory of the path. * @param options a set of DirectoryOption flags * @return The directory part of the current path. Everything between the last and the second last '/' * is returned. For example <tt>file:///hallo/torben/</tt> would return "/hallo/torben/" while * <tt>file:///hallo/torben</tt> would return "hallo/". The returned string is decoded. * QString() is returned when there is no path. */ QString directory( const DirectoryOptions& options = IgnoreTrailingSlash ) const; /** * Set the directory to @p dir, leaving the filename empty. */ void setDirectory(const QString &dir); /** * Changes the directory by descending into the given directory. * It is assumed the current URL represents a directory. * If @p dir starts with a "/" the * current URL will be "protocol://host/dir" otherwise @p _dir will * be appended to the path. @p _dir can be ".." * This function won't strip protocols. That means that when you are in * file:///dir/dir2/my.tgz#tar:/ and you do cd("..") you will * still be in file:///dir/dir2/my.tgz#tar:/ * * @param _dir the directory to change to * @return true if successful */ bool cd( const QString& _dir ); /** * Returns the URL as string, with all escape sequences intact, * encoded in a given charset. * This is used in particular for encoding URLs in UTF-8 before using them * in a drag and drop operation. * Please note that the string returned by url() will include * the password of the URL. If you want to show the URL to the * user, use prettyUrl(). * * @param trailing use to add or remove a trailing slash to/from the path. See adjustPath * @return The complete URL, with all escape sequences intact, encoded * in a given charset. * @see prettyUrl() */ QString url( AdjustPathOption trailing = LeaveTrailingSlash ) const; /** * Returns the URL as string in human-friendly format. * Example: * \code * http://localhost:8080/test.cgi?test=hello world&name=fred * \endcode * @param trailing use to add or remove a trailing slash to/from the path. see adjustPath. * * @return A human readable URL, with no non-necessary encodings/escaped * characters. Password will not be shown. * @see url() */ QString prettyUrl( AdjustPathOption trailing = LeaveTrailingSlash ) const; /** * Return the URL as a string, which will be either the URL (as prettyUrl * would return) or, when the URL is a local file without query or ref, * the path. * Use this method, to display URLs to the user. * You can give the result of pathOrUrl back to the KUrl constructor, it accepts * both paths and urls. * * @return the new KUrl */ QString pathOrUrl() const; /** * Overload with @p trailing parameter * @param trailing use to add or remove a trailing slash to/from the path. see adjustPath. * @since 4.2 */ QString pathOrUrl(AdjustPathOption trailing) const; // KDE5: merge with above. Rename to toUrlOrLocalFile? /** * Returns the URL as a string, using the standard conventions for mime data * (drag-n-drop or copy-n-paste). * Internally used by KUrl::List::fromMimeData, which is probably what you want to use instead. + * @deprecated use QMimeData::setUrls directly, or KUrlMimeData::setUrls */ QString toMimeDataString() const; /** * This function is useful to implement the "Up" button in a file manager for example. * cd() never strips a sub-protocol. That means that if you are in * file:///home/x.tgz#gzip:/#tar:/ and hit the up button you expect to see * file:///home. The algorithm tries to go up on the right-most URL. If that is not * possible it strips the right most URL. It continues stripping URLs. * @return a URL that is a level higher */ KUrl upUrl( ) const; KUrl& operator=( const KUrl& _u ); // Define those, since the constructors are explicit KUrl& operator=( const char * _url ) { *this = KUrl(_url); return *this; } KUrl& operator=( const QByteArray& _url ) { *this = KUrl(_url); return *this; } KUrl& operator=( const QString& _url ) { *this = KUrl(_url); return *this; } bool operator==( const KUrl& _u ) const; bool operator==( const QString& _u ) const; bool operator!=( const KUrl& _u ) const { return !( *this == _u ); } bool operator!=( const QString& _u ) const { return !( *this == _u ); } /** * Converts this KUrl to a QVariant, this allows to use KUrl * in QVariant() constructor */ operator QVariant() const; /** * The same as equals(), just with a less obvious name. * Compares this url with @p u. * @param u the URL to compare this one with. * @param ignore_trailing set to true to ignore trailing '/' characters. * @return True if both urls are the same. If at least one of the urls is invalid, * false is returned. * @see operator==. This function should be used if you want to * ignore trailing '/' characters. * @deprecated Use equals() instead. */ #ifndef KDE_NO_DEPRECATED KCOREADDONS_DEPRECATED bool cmp( const KUrl &u, bool ignore_trailing = false ) const; #endif /** * Flags to be used in URL comparison functions like equals, or urlcmp */ enum EqualsOption { /** * ignore trailing '/' characters. The paths "dir" and "dir/" are treated the same. * Note however, that by default, the paths "" and "/" are not the same * (For instance ftp://user@host redirects to ftp://user@host/home/user (on a linux server), * while ftp://user@host/ is the root dir). * This is also why path(RemoveTrailingSlash) for "/" returns "/" and not "". * * When dealing with web pages however, you should also set AllowEmptyPath so that * no path and "/" are considered equal. */ CompareWithoutTrailingSlash = 0x01, /** * disables comparison of HTML-style references. */ CompareWithoutFragment = 0x02, /** * Treat a URL with no path as equal to a URL with a path of "/", * when CompareWithoutTrailingSlash is set. * Example: * KUrl::urlcmp("http://www.kde.org", "http://www.kde.org/", KUrl::CompareWithoutTrailingSlash | KUrl::AllowEmptyPath) * returns true. * This option is ignored if CompareWithoutTrailingSlash isn't set. * @since 4.5 */ AllowEmptyPath = 0x04 }; Q_DECLARE_FLAGS(EqualsOptions,EqualsOption) /** * Compares this url with @p u. * @param u the URL to compare this one with. * @param options a set of EqualsOption flags * @return True if both urls are the same. If at least one of the urls is invalid, * false is returned. * @see operator==. This function should be used if you want to * set additional options, like ignoring trailing '/' characters. */ bool equals( const KUrl &u, const EqualsOptions& options=0 ) const; /** * Checks whether the given URL is parent of this URL. * For instance, ftp://host/dir/ is a parent of ftp://host/dir/subdir/subsubdir/. * @return true if this url is a parent of @p u (or the same URL as @p u) * */ bool isParentOf( const KUrl& u ) const; // (this overload of the QUrl method allows to use the implicit KUrl constructors) // but also the equality test /** * Splits nested URLs like file:///home/weis/kde.tgz#gzip:/#tar:/kdebase * A URL like http://www.kde.org#tar:/kde/README.hml#ref1 will be split in * http://www.kde.org and tar:/kde/README.html#ref1. * That means in turn that "#ref1" is an HTML-style reference and not a new sub URL. * Since HTML-style references mark * a certain position in a document this reference is appended to every URL. * The idea behind this is that browsers, for example, only look at the first URL while * the rest is not of interest to them. * * * @param _url The URL that has to be split. * @return An empty list on error or the list of split URLs. * @see hasSubUrl */ static List split( const QString& _url ); /** * Splits nested URLs like file:///home/weis/kde.tgz#gzip:/#tar:/kdebase * A URL like http://www.kde.org#tar:/kde/README.hml#ref1 will be split in * http://www.kde.org and tar:/kde/README.html#ref1. * That means in turn that "#ref1" is an HTML-style reference and not a new sub URL. * Since HTML-style references mark * a certain position in a document this reference is appended to every URL. * The idea behind this is that browsers, for example, only look at the first URL while * the rest is not of interest to them. * * @return An empty list on error or the list of split URLs. * * @param _url The URL that has to be split. * @see hasSubUrl */ static List split( const KUrl& _url ); /** * Reverses split(). Only the first URL may have a reference. This reference * is considered to be HTML-like and is appended at the end of the resulting * joined URL. * @param _list the list to join * @return the joined URL */ static KUrl join( const List& _list ); /** * Creates a KUrl object from a QString representing an absolute path. * KUrl url( somePath ) is almost the same, but this method is more explicit, * avoids the path-or-url detection in the KUrl constructor, and parses * "abc:def" as a filename, not as URL. * * @param text the path * @return the new KUrl */ static KUrl fromPath( const QString& text ); /** * \deprecated * Since KDE4 you can pass both urls and paths to the KUrl constructors. * Use KUrl(text) instead. */ #ifndef KDE_NO_DEPRECATED static KCOREADDONS_DEPRECATED KUrl fromPathOrUrl( const QString& text ); #endif - /** - * Creates a KUrl from a string, using the standard conventions for mime data - * (drag-n-drop or copy-n-paste). - * Internally used by KUrl::List::fromMimeData, which is probably what you want to use instead. - */ - static KUrl fromMimeDataByteArray( const QByteArray& str ); - /** * Adds URL data into the given QMimeData. * * By default, populateMimeData also exports the URL as plain text, for e.g. dropping * onto a text editor. * But in some cases this might not be wanted, e.g. if adding other mime data * which provides better plain text data. * * WARNING: do not call this method multiple times, use KUrl::List::populateMimeData instead. * * @param mimeData the QMimeData instance used to drag or copy this URL * @param metaData KIO metadata shipped in the mime data, which is used for instance to * set a correct HTTP referrer (some websites require it for downloading e.g. an image) * @param flags set NoTextExport to prevent setting plain/text data into @p mimeData - * In such a case, setExportAsText( false ) should be called. + * + * @deprecated use QMimeData::setUrls(QList<QUrl>() << url), + * followed by KUrlMimeData::setMetaData if you have metadata. */ - void populateMimeData( QMimeData* mimeData, +#ifndef KDE_NO_DEPRECATED + KCOREADDONS_DEPRECATED void populateMimeData( QMimeData* mimeData, const MetaDataMap& metaData = MetaDataMap(), MimeDataFlags flags = DefaultMimeDataFlags ) const; +#endif /** * Convert unicoded string to local encoding and use %-style * encoding for all common delimiters / non-ascii characters. * @param str String to encode (can be QString()). * @return the encoded string * * @deprecated use QUrl::toPercentEncoding instead, but note that it * returns a QByteArray and not a QString. Which makes sense since * everything is 7 bit (ascii) after being percent-encoded. */ #ifndef KDE_NO_DEPRECATED static KCOREADDONS_DEPRECATED QString encode_string(const QString &str) { return QString::fromLatin1( QUrl::toPercentEncoding( str ).constData() ); //krazy:exclude=qclasses } #endif /** * Convert unicoded string to local encoding and use %-style * encoding for all common delimiters / non-ascii characters * as well as the slash '/'. * @param str String to encode * * @deprecated use QUrl::toPercentEncoding(str,"/") instead, but note that it * returns a QByteArray and not a QString. Which makes sense since * everything is 7 bit (ascii) after being percent-encoded. * */ #ifndef KDE_NO_DEPRECATED static KCOREADDONS_DEPRECATED QString encode_string_no_slash(const QString &str) { return QString::fromLatin1( QUrl::toPercentEncoding( str, "/" ).constData() ); //krazy:exclude=qclasses } #endif /** * Decode %-style encoding and convert from local encoding to unicode. * Reverse of encode_string() * @param str String to decode (can be QString()). * * @deprecated use QUrl::fromPercentEncoding(encodedURL) instead, but * note that it takes a QByteArray and not a QString. Which makes sense since * everything is 7 bit (ascii) when being percent-encoded. * */ #ifndef KDE_NO_DEPRECATED static KCOREADDONS_DEPRECATED QString decode_string(const QString &str) { return QUrl::fromPercentEncoding( str.toLatin1() ); //krazy:exclude=qclasses } #endif /** * Convenience function. * * Returns whether '_url' is likely to be a "relative" URL instead of * an "absolute" URL. * * This is mostly meant for KUrl(url, relativeUrl). * * If you are looking for the notion of "relative path" (foo) vs "absolute path" (/foo), * use QUrl::isRelative() instead. * Indeed, isRelativeUrl() returns true for the string "/foo" since it doesn't contain a protocol, * while KUrl("/foo").isRelative() is false since the KUrl constructor turns it into file:///foo. * The two methods basically test the same thing, but this one takes a string (which is faster) * while the class method requires a QUrl/KUrl which gives a more expected result, given * the "magic" in the KUrl constructor. * * @param _url URL to examine * @return true when the URL is likely to be "relative", false otherwise. */ static bool isRelativeUrl(const QString &_url); /** * Convenience function * * Returns a "relative URL" based on @p base_url that points to @p url. * * If no "relative URL" can be created, e.g. because the protocol * and/or hostname differ between @p base_url and @p url an absolute * URL is returned. * Note that if @p base_url represents a directory, it should contain * a trailing slash. * @param base_url the URL to derive from * @param url new URL * @see adjustPath() */ static QString relativeUrl(const KUrl &base_url, const KUrl &url); /** * Convenience function * * Returns a relative path based on @p base_dir that points to @p path. * @param base_dir the base directory to derive from * @param path the new target directory * @param isParent A pointer to a boolean which, if provided, will be set to reflect * whether @p path has @p base_dir is a parent dir. */ static QString relativePath(const QString &base_dir, const QString &path, bool *isParent=0); private: void _setQuery( const QString& query ); void _setEncodedUrl(const QByteArray& url); QString toString() const; // forbidden, use url(), prettyUrl(), or pathOrUrl() instead. operator QString() const; // forbidden, use url(), prettyUrl(), or pathOrUrl() instead. private: KUrlPrivate* const d; // Don't ever use this, it would break clear() (which is in QUrl) }; Q_DECLARE_OPERATORS_FOR_FLAGS(KUrl::EncodedPathAndQueryOptions) Q_DECLARE_OPERATORS_FOR_FLAGS(KUrl::CleanPathOptions) Q_DECLARE_OPERATORS_FOR_FLAGS(KUrl::QueryItemsOptions) Q_DECLARE_OPERATORS_FOR_FLAGS(KUrl::EqualsOptions) Q_DECLARE_OPERATORS_FOR_FLAGS(KUrl::DirectoryOptions) Q_DECLARE_METATYPE(KUrl) Q_DECLARE_METATYPE(KUrl::List) /** * \relates KUrl * Compares URLs. They are parsed, split and compared. * Two malformed URLs with the same string representation * are nevertheless considered to be unequal. * That means no malformed URL equals anything else. * @deprecated use KUrl(_url1).equals(KUrl(_url2)) instead. */ #ifndef KDE_NO_DEPRECATED KCOREADDONS_DEPRECATED_EXPORT bool urlcmp( const QString& _url1, const QString& _url2 ); // KDE5: remove, KUrl::equals is better API #endif /** * \relates KUrl * Compares URLs. They are parsed, split and compared. * Two malformed URLs with the same string representation * are nevertheless considered to be unequal. * That means no malformed URL equals anything else. * * @param _url1 A reference URL * @param _url2 A URL that will be compared with the reference URL * @param options a set of KUrl::EqualsOption flags * @deprecated use KUrl(_url1).equals(KUrl(_url2), options) instead. */ #ifndef KDE_NO_DEPRECATED KCOREADDONS_DEPRECATED_EXPORT bool urlcmp( const QString& _url1, const QString& _url2, const KUrl::EqualsOptions& options ); // KDE5: remove, KUrl::equals is better API #endif KCOREADDONS_EXPORT uint qHash(const KUrl& kurl); #endif diff --git a/staging/kcoreaddons/src/io/kurlmimedata.cpp b/staging/kcoreaddons/src/io/kurlmimedata.cpp new file mode 100644 index 0000000000..1d8bb4b62d --- /dev/null +++ b/staging/kcoreaddons/src/io/kurlmimedata.cpp @@ -0,0 +1,107 @@ +/* This file is part of the KDE libraries + * Copyright (C) 2005-2012 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 "kurlmimedata.h" +#include <QStringList> +#include <QMimeData> + +static const char s_kdeUriListMime[] = "application/x-kde4-urilist"; // keep this name "kde4" for compat. + +static QByteArray uriListData(const QList<QUrl>& urls) +{ + // compatible with qmimedata.cpp encoding of QUrls + QByteArray result; + for (int i = 0; i < urls.size(); ++i) { + result += urls.at(i).toEncoded(); + result += "\r\n"; + } + return result; +} + +void KUrlMimeData::setUrls(const QList<QUrl> &urls, const QList<QUrl> &mostLocalUrls, + QMimeData *mimeData) +{ + // Export the most local urls as text/uri-list and plain text, for non KDE apps. + mimeData->setUrls(mostLocalUrls); // set text/uri-list and text/plain + + // Export the real KIO urls as a kde-specific mimetype + mimeData->setData(QString::fromLatin1(s_kdeUriListMime), uriListData(urls)); +} + +void KUrlMimeData::setMetaData(const MetaDataMap& metaData, QMimeData *mimeData) +{ + QByteArray metaDataData; // :) + for(MetaDataMap::const_iterator it = metaData.begin(); it != metaData.end(); ++it) { + metaDataData += it.key().toUtf8(); + metaDataData += "$@@$"; + metaDataData += it.value().toUtf8(); + metaDataData += "$@@$"; + } + mimeData->setData(QString::fromLatin1("application/x-kio-metadata"), metaDataData); +} + +QStringList KUrlMimeData::mimeDataTypes() +{ + return QStringList() << QString::fromLatin1(s_kdeUriListMime) << QString::fromLatin1("text/uri-list"); +} + +QList<QUrl> KUrlMimeData::urlsFromMimeData(const QMimeData *mimeData, + DecodeOptions decodeOptions, + MetaDataMap* metaData) +{ + QList<QUrl> uris; + const char* firstMimeType = s_kdeUriListMime; + const char* secondMimeType = "text/uri-list"; + if (decodeOptions == PreferLocalUrls) { + qSwap(firstMimeType, secondMimeType); + } + QByteArray ba = mimeData->data(QString::fromLatin1(firstMimeType)); + if (ba.isEmpty()) + ba = mimeData->data(QString::fromLatin1(secondMimeType)); + if (!ba.isEmpty()) { + // Code from qmimedata.cpp + QList<QByteArray> urls = ba.split('\n'); + for (int i = 0; i < urls.size(); ++i) { + QByteArray data = urls.at(i).trimmed(); + if (!data.isEmpty()) + uris.append(QUrl::fromEncoded(data)); + } + } + if (metaData) { + const QByteArray metaDataPayload = mimeData->data(QLatin1String("application/x-kio-metadata")); + if (!metaDataPayload.isEmpty()) { + QString str = QString::fromUtf8(metaDataPayload.constData()); + Q_ASSERT(str.endsWith(QLatin1String("$@@$"))); + str.truncate(str.length() - 4); + const QStringList lst = str.split(QLatin1String("$@@$")); + bool readingKey = true; // true, then false, then true, etc. + QString key; + for ( QStringList::const_iterator it = lst.begin(); it != lst.end(); ++it ) { + if (readingKey) + key = *it; + else + metaData->insert(key, *it); + readingKey = !readingKey; + } + Q_ASSERT(readingKey); // an odd number of items would be, well, odd ;-) + } + } + return uris; +} + diff --git a/staging/kcoreaddons/src/io/kurlmimedata.h b/staging/kcoreaddons/src/io/kurlmimedata.h new file mode 100644 index 0000000000..d93641b405 --- /dev/null +++ b/staging/kcoreaddons/src/io/kurlmimedata.h @@ -0,0 +1,107 @@ +/* This file is part of the KDE libraries + * Copyright (C) 2005-2012 David Faure <faure@kde.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef KURLMIMEDATA_H +#define KURLMIMEDATA_H + +#include <QMap> +#include <QUrl> +#include "kcoreaddons_export.h" +QT_BEGIN_NAMESPACE +class QMimeData; +QT_END_NAMESPACE + +/** + * Utility functions for using URLs in QMimeData. + * In addition to QMimeData::setUrls() and QMimeData::urls(), these functions allow to: + * + * - Store two sets of URLs, the KDE-specific URLs and the equivalent local-file URLs + * for compatibility with non-KDE applications + * - Store KIO metadata, such as the HTTP referrer for a given URL (some websites + * require it for downloading e.g. an image) + * + * @since 5.0 + */ +namespace KUrlMimeData +{ +typedef QMap<QString, QString> MetaDataMap; + +/** + * Adds URLs and KIO metadata into the given QMimeData. + * + * WARNING: do not call this method multiple times on the same mimedata object, + * you can add urls only once. But you can add other things, e.g. images, XML... + * + * @param mimeData the QMimeData instance used to drag or copy this URL + */ +KCOREADDONS_EXPORT void setUrls(const QList<QUrl> &urls, const QList<QUrl> &mostLocalUrls, + QMimeData *mimeData); +/** + * @param metaData KIO metadata shipped in the mime data, which is used for instance to + * set a correct HTTP referrer (some websites require it for downloading e.g. an image) + */ +KCOREADDONS_EXPORT void setMetaData(const MetaDataMap& metaData, QMimeData *mimeData); + +/** + * Return the list of mimeTypes that can be decoded by urlsFromMimeData + */ +KCOREADDONS_EXPORT QStringList mimeDataTypes(); + +/** + * Flags to be used in urlsFromMimeData. + */ +enum DecodeOptions { + /** + * When the mimedata contains both KDE-style URLs (eg: desktop:/foo) and + * the "most local" version of the URLs (eg: file:///home/dfaure/Desktop/foo), + * decode it as local urls. Useful in paste/drop operations that end up calling KIO, + * so that urls from other users work as well. + */ + PreferLocalUrls, + /** + * When the mimedata contains both KDE-style URLs (eg: desktop:/foo) and + * the "most local" version of the URLs (eg: file:///home/dfaure/Desktop/foo), + * decode it as the KDE-style URL. Useful in DnD code e.g. when moving icons, + * and the kde-style url is used as identifier for the icons. + */ + PreferKdeUrls +}; + +/** + * Extract a list of urls from the contents of @p mimeData. + * + * Compared to QMimeData::urls(), this method has support for retrieving KDE-specific URLs + * when urls() would retrieve "most local URLs" instead. + * + * Decoding will fail if @p mimeData does not contain any URLs, or if at + * least one extracted URL is not valid. + * + * @param mimeData the mime data to extract from; cannot be 0 + * @param decodeOptions options for decoding + * @param metaData optional pointer to a map which will hold the metadata after this call + * @return the list of urls + */ +KCOREADDONS_EXPORT QList<QUrl> urlsFromMimeData(const QMimeData *mimeData, + DecodeOptions decodeOptions = PreferKdeUrls, + MetaDataMap* metaData = 0); + +} + +#endif /* KURLMIMEDATA_H */ +