diff --git a/messagecomposer/CMakeLists.txt b/messagecomposer/CMakeLists.txt index 78e449776..cf48111b8 100644 --- a/messagecomposer/CMakeLists.txt +++ b/messagecomposer/CMakeLists.txt @@ -1,49 +1,52 @@ set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) +add_definitions( -DQT_NO_CAST_FROM_ASCII ) +add_definitions( -DQT_NO_CAST_TO_ASCII ) + add_subdirectory( tests ) set( messagecomposer_src composer.cpp behaviour.cpp attachmentpart.cpp finalmessage.cpp infopart.cpp messagepart.cpp skeletonmessagejob.cpp textpart.cpp job.cpp contentjob.cpp maintextjob.cpp multipartjob.cpp ) include_directories( ${Boost_INCLUDE_DIR} ) kde4_add_library( messagecomposer SHARED ${messagecomposer_src} ) target_link_libraries( messagecomposer ${KDE4_KIO_LIBS} kmime ) set_target_properties( messagecomposer PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION} ) install( TARGETS messagecomposer EXPORT kdepimlibsLibraryTargets ${INSTALL_TARGETS_DEFAULT_ARGS} ) install( FILES messagecomposer_export.h composer.h behaviour.h attachmentpart.h finalmessage.h infopart.h messagepart.h skeletonmessagejob.h textpart.h job.h contentjob.h maintextjob.h multipartjob.h DESTINATION ${INCLUDE_INSTALL_DIR}/messagecomposer COMPONENT Devel ) diff --git a/messagecomposer/maintextjob.cpp b/messagecomposer/maintextjob.cpp index f16a0a3ab..31ddec987 100644 --- a/messagecomposer/maintextjob.cpp +++ b/messagecomposer/maintextjob.cpp @@ -1,251 +1,256 @@ /* Copyright (c) 2009 Constantin Berzan 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 "maintextjob.h" #include "composer.h" #include "contentjob.h" #include "job_p.h" #include "textpart.h" #include #include #include #include #include #include #include #include using namespace MessageComposer; using namespace KMime; class MessageComposer::MainTextJobPrivate : public JobPrivate { public: MainTextJobPrivate( MainTextJob *qq ) : JobPrivate( qq ) , textPart( 0 ) { } bool chooseCharsetAndEncode(); bool chooseCharset(); void encodeTexts(); TextPart *textPart; QList charsets; QByteArray chosenCharset; QString sourcePlainText; QByteArray encodedPlainText; QByteArray encodedHtml; // TODO related images Q_DECLARE_PUBLIC( MainTextJob ) }; bool MainTextJobPrivate::chooseCharsetAndEncode() { Q_Q( MainTextJob ); Q_ASSERT( composer ); const Behaviour &beh = composer->behaviour(); Q_ASSERT( textPart ); charsets = textPart->charsets(); foreach( const QByteArray &name, textPart->charsets() ) { charsets << name.toLower(); } if( beh.isActionEnabled( Behaviour::UseFallbackCharset ) ) { charsets << "utf-8"; // TODO somehow save the chosen charset in a custom header if behaviour allows it... } - Q_ASSERT( !charsets.isEmpty() ); + if( charsets.isEmpty() ) { + q->setError( Job::BugError ); + q->setErrorText( i18n( "No charsets were available for encoding," + " and the fallback charset was disabled." ) ); + return false; + } if( chooseCharset() ) { // Good, one of the charsets can encode the data without loss. encodeTexts(); return true; } else { // No good charset was found. if( beh.isActionEnabled( Behaviour::UseGui ) && beh.isActionEnabled( Behaviour::WarnBadCharset ) ) { // Warn the user and give them a chance to go back. int result = KMessageBox::warningYesNo( composer->parentWidget(), i18n( "Encoding the message with %1 will lose some characters.\n" "Do you want to continue?", QString::fromLatin1( charsets.first() ) ), i18n( "Some Characters Will Be Lost" ), KGuiItem( i18n("Lose Characters") ), KGuiItem( i18n("Change Encoding") ) ); if( result == KMessageBox::No ) { q->setError( Job::UserCancelledError ); q->setErrorText( i18n( "User decided to change the encoding." ) ); return false; } else { chosenCharset = charsets.first(); encodeTexts(); return true; } } else if( beh.isActionEnabled( Behaviour::WarnBadCharset ) ) { // Should warn user but no Gui available. kDebug() << "WarnBadCharset but not UseGui."; q->setError( Job::UserError ); q->setErrorText( i18n( "The selected encoding (%1) cannot fully encode the message.", QString::fromLatin1( charsets.first() ) ) ); return false; } else { // OK to go ahead with a bad charset. chosenCharset = charsets.first(); encodeTexts(); return true; // FIXME: This is based on the assumption that QTextCodec will replace // unknown characters with '?' or some other meaningful thing. The code in // QTextCodec indeed uses '?', but this behaviour is not documented. } } // Should not reach here. Q_ASSERT( false ); return false; } bool MainTextJobPrivate::chooseCharset() { Q_ASSERT( !charsets.isEmpty() ); Q_ASSERT( textPart ); QString toTry = sourcePlainText; if( textPart->isHtmlUsed() ) { toTry = textPart->cleanHtml(); } foreach( const QByteArray &name, charsets ) { // We use KCharsets::codecForName() instead of QTextCodec::codecForName() here, because // the former knows us-ascii is latin1. - QTextCodec *codec = KGlobal::charsets()->codecForName( name ); + QTextCodec *codec = KGlobal::charsets()->codecForName( QString::fromLatin1( name ) ); if( !codec ) { kWarning() << "Could not get text codec for charset" << name; continue; } if( codec->canEncode( toTry ) ) { // Special check for us-ascii (needed because us-ascii is not exactly latin1). if( name == "us-ascii" && !isUsAscii( toTry ) ) { continue; } kDebug() << "Chosen charset" << name; chosenCharset = name; return true; } } kDebug() << "No appropriate charset found."; return false; } void MainTextJobPrivate::encodeTexts() { Q_Q( MainTextJob ); - QTextCodec *codec = KGlobal::charsets()->codecForName( chosenCharset ); + QTextCodec *codec = KGlobal::charsets()->codecForName( QString::fromLatin1( chosenCharset ) ); if( !codec ) { kError() << "Could not get text codec for charset" << chosenCharset; q->setError( Job::BugError ); q->setErrorText( i18n( "Could not get text codec for charset \"%1\".", QString::fromLatin1( chosenCharset ) ) ); return; } encodedPlainText = codec->fromUnicode( sourcePlainText ); encodedHtml = codec->fromUnicode( textPart->cleanHtml() ); kDebug() << "Done."; } MainTextJob::MainTextJob( TextPart *textPart, QObject *parent ) : Job( *new MainTextJobPrivate( this ), parent ) { Q_D( MainTextJob ); d->textPart = textPart; } MainTextJob::~MainTextJob() { } TextPart *MainTextJob::textPart() const { Q_D( const MainTextJob ); return d->textPart; } void MainTextJob::setTextPart( TextPart *part ) { Q_D( MainTextJob ); d->textPart = part; } void MainTextJob::doStart() { Q_D( MainTextJob ); Q_ASSERT( d->textPart ); Q_ASSERT( d->composer ); // Word wrapping. if( d->composer->behaviour().isActionEnabled( Behaviour::UseWrapping ) ) { d->sourcePlainText = d->textPart->wrappedPlainText(); } else { d->sourcePlainText = d->textPart->cleanPlainText(); } // Charset. if( d->chooseCharsetAndEncode() ) { // Encoding was successful. The user and we are happy with the charset, even if it may // lose characters. if( d->encodedHtml.isEmpty() ) { kDebug() << "Making text/plain"; // Content is text/plain. ContentJob *cjob = new ContentJob( this ); cjob->contentType()->setMimeType( "text/plain" ); cjob->contentType()->setCharset( d->chosenCharset ); cjob->setData( d->encodedPlainText ); // TODO temporary until I figure out what the CTE policy is. cjob->contentTransferEncoding()->setEncoding( Headers::CEquPr ); } else { // TODO Handle multipart/alternative and multipart/related. Q_ASSERT( false ); } Job::doStart(); } else { // chooseCharsetAndEncode has set an error. Q_ASSERT( error() ); emitResult(); } } void MainTextJob::process() { Q_D( MainTextJob ); // The content has been created by our subjob. Q_ASSERT( d->subjobContents.count() == 1 ); d->resultContent = d->subjobContents.first(); emitResult(); } #include "maintextjob.moc" diff --git a/messagecomposer/tests/CMakeLists.txt b/messagecomposer/tests/CMakeLists.txt index 8de129602..dd54ebc78 100644 --- a/messagecomposer/tests/CMakeLists.txt +++ b/messagecomposer/tests/CMakeLists.txt @@ -1,26 +1,27 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories ( ${Boost_INCLUDE_DIR} ) # convenience macro to add messagecomposer unit tests macro( add_messagecomposer_test _source ) set( _test ${_source} ) get_filename_component( _name ${_source} NAME_WE ) kde4_add_unit_test( ${_name} TESTNAME messagecomposer-${_name} ${_test} ) target_link_libraries( ${_name} kmime messagecomposer ${QT_QTTEST_LIBRARY} ${QT_QTGUI_LIBRARY} ${QT_QTCORE_LIBRARY} ${KDE4_KDEUI_LIBS} ) endmacro( add_messagecomposer_test ) # Utility stuff. add_messagecomposer_test( behaviourtest.cpp ) # Basic jobs. add_messagecomposer_test( contentjobtest.cpp ) add_messagecomposer_test( multipartjobtest.cpp ) # More complex jobs. add_messagecomposer_test( skeletonmessagejobtest.cpp ) +add_messagecomposer_test( maintextjobtest.cpp ) # Composer. diff --git a/messagecomposer/tests/maintextjobtest.cpp b/messagecomposer/tests/maintextjobtest.cpp index 7fb2a1159..9c7cc27d0 100644 --- a/messagecomposer/tests/maintextjobtest.cpp +++ b/messagecomposer/tests/maintextjobtest.cpp @@ -1,51 +1,128 @@ /* Copyright (c) 2009 Constantin Berzan 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 "maintextjobtest.h" +#include + #include #include #include using namespace KMime; #include -#include +#include +#include using namespace MessageComposer; -QTEST_KDEMAIN( ContentJobTest, GUI ) +QTEST_KDEMAIN( MainTextJobTest, NoGUI ) void MainTextJobTest::testPlainText() { - // LEFT TODO + Composer *composer = new Composer; + composer->behaviour().disableAction( Behaviour::UseGui ); + TextPart *textPart = new TextPart; + QString data = QString::fromLatin1( "they said their nevers they slept their dream" ); + QList charsets; + charsets << "us-ascii" << "utf-8"; + textPart->setWrappedPlainText( data ); + textPart->setCharsets( charsets ); + MainTextJob *mjob = new MainTextJob( textPart, composer ); + QVERIFY( mjob->exec() ); + Content *result = mjob->content(); + result->assemble(); + kDebug() << result->encodedContent(); + QVERIFY( result->contentType( false ) ); + QCOMPARE( result->contentType()->mimeType(), QByteArray( "text/plain" ) ); + QCOMPARE( result->contentType()->charset(), QByteArray( "us-ascii" ) ); + QCOMPARE( QString::fromLatin1( result->body() ), data ); } void MainTextJobTest::testCustomCharset() { + Composer *composer = new Composer; + composer->behaviour().disableAction( Behaviour::UseGui ); + TextPart *textPart = new TextPart; + QString data = QString::fromUtf8( "şi el o să se-nchidă cu o frunză de pelin" ); + QByteArray charset( "iso-8859-2" ); + textPart->setWrappedPlainText( data ); + textPart->setCharsets( QList() << charset ); + MainTextJob *mjob = new MainTextJob( textPart, composer ); + QVERIFY( mjob->exec() ); + Content *result = mjob->content(); + result->assemble(); + kDebug() << result->encodedContent(); + QVERIFY( result->contentType( false ) ); + QCOMPARE( result->contentType()->mimeType(), QByteArray( "text/plain" ) ); + QCOMPARE( result->contentType()->charset(), charset ); + QByteArray outData = result->body(); + QTextCodec *codec = QTextCodec::codecForName( charset ); + QVERIFY( codec ); + QCOMPARE( codec->toUnicode( outData ), data ); +} + +void MainTextJobTest::testNoCharset() +{ + Composer *composer = new Composer; + composer->behaviour().disableAction( Behaviour::UseGui ); + TextPart *textPart = new TextPart; + QString data = QString::fromLatin1( "do you still play the accordion?" ); + textPart->setWrappedPlainText( data ); + MainTextJob *mjob = new MainTextJob( textPart, composer ); + QVERIFY( !mjob->exec() ); // Error. + QCOMPARE( mjob->error(), int( Job::BugError ) ); + kDebug() << mjob->errorString(); } void MainTextJobTest::testBadCharset() { + Composer *composer = new Composer; + composer->behaviour().disableAction( Behaviour::UseGui ); + TextPart *textPart = new TextPart; + QString data = QString::fromUtf8( "el a plâns peste ţară cu lacrima limbii noastre" ); + QByteArray charset( "us-ascii" ); // Cannot handle Romanian chars. + textPart->setWrappedPlainText( data ); + textPart->setCharsets( QList() << charset ); + MainTextJob *mjob = new MainTextJob( textPart, composer ); + QVERIFY( !mjob->exec() ); // Error. + QCOMPARE( mjob->error(), int( Job::UserError ) ); + kDebug() << mjob->errorString(); } void MainTextJobTest::testFallbackCharset() { + Composer *composer = new Composer; + composer->behaviour().disableAction( Behaviour::UseGui ); + composer->behaviour().enableAction( Behaviour::UseFallbackCharset ); + TextPart *textPart = new TextPart; + QString data = QString::fromLatin1( "and when he falleth..." ); + textPart->setWrappedPlainText( data ); + MainTextJob *mjob = new MainTextJob( textPart, composer ); + QVERIFY( mjob->exec() ); + Content *result = mjob->content(); + result->assemble(); + kDebug() << result->encodedContent(); + QVERIFY( result->contentType( false ) ); + QCOMPARE( result->contentType()->mimeType(), QByteArray( "text/plain" ) ); + QCOMPARE( result->contentType()->charset(), QByteArray( "utf-8" ) ); // Fallback is UTF-8. + QCOMPARE( QString::fromLatin1( result->body() ), data ); } #include "maintextjobtest.moc" diff --git a/messagecomposer/tests/maintextjobtest.h b/messagecomposer/tests/maintextjobtest.h index b97486e82..2b1597095 100644 --- a/messagecomposer/tests/maintextjobtest.h +++ b/messagecomposer/tests/maintextjobtest.h @@ -1,35 +1,36 @@ /* Copyright (c) 2009 Constantin Berzan 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 MAINTEXTJOBTEST_H #define MAINTEXTJOBTEST_H #include class MainTextJobTest : public QObject { Q_OBJECT private Q_SLOTS: void testPlainText(); void testCustomCharset(); + void testNoCharset(); void testBadCharset(); void testFallbackCharset(); }; #endif diff --git a/messagecomposer/tests/skeletonmessagejobtest.cpp b/messagecomposer/tests/skeletonmessagejobtest.cpp index 56774a169..7d2acbf34 100644 --- a/messagecomposer/tests/skeletonmessagejobtest.cpp +++ b/messagecomposer/tests/skeletonmessagejobtest.cpp @@ -1,174 +1,186 @@ /* Copyright (c) 2009 Constantin Berzan 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 "skeletonmessagejobtest.h" #include #include #include using namespace KMime; #include #include #include using namespace MessageComposer; QTEST_KDEMAIN( SkeletonMessageJobTest, NoGUI ) void SkeletonMessageJobTest::testSubject_data() { QTest::addColumn( "subject" ); - QTest::newRow( "simple subject" ) << "Antaa virrata sateen..."; - QTest::newRow( "non-ascii subject" ) << "Muzicologă în bej, vând whisky și tequila, preț fix."; - // NOTE: This works fine, but shows ????s in the debug output. Why? + QTest::newRow( "simple subject" ) << QString::fromLatin1( "Antaa virrata sateen..." ); + QTest::newRow( "non-ascii subject" ) << QString::fromUtf8( "Muzicologă în bej, vând whisky și tequila, preț fix." ); + // NOTE: This works fine, but shows ??s in the debug output. Why? } void SkeletonMessageJobTest::testSubject() { // An InfoPart should belong to a Composer, even if we don't use the composer itself. Composer *composer = new Composer; InfoPart *infoPart = composer->infoPart(); Q_ASSERT( infoPart ); QFETCH( QString, subject ); //kDebug() << subject; infoPart->setSubject( subject ); SkeletonMessageJob *sjob = new SkeletonMessageJob( infoPart, composer ); QVERIFY( sjob->exec() ); Message *message = sjob->message(); QVERIFY( message->subject( false ) ); kDebug() << message->subject()->asUnicodeString(); QCOMPARE( subject, message->subject()->asUnicodeString() ); } void SkeletonMessageJobTest::testAddresses_data() { QTest::addColumn( "from" ); QTest::addColumn( "to" ); QTest::addColumn( "cc" ); QTest::addColumn( "bcc" ); { - QString from( "one@example.com" ); - QStringList to( "two@example.com" ); - QStringList cc( "three@example.com" ); - QStringList bcc( "four@example.com" ); + QString from = QString::fromLatin1( "one@example.com" ); + QStringList to; + to << QString::fromLatin1( "two@example.com" ); + QStringList cc; + cc << QString::fromLatin1( "three@example.com" ); + QStringList bcc; + bcc << QString::fromLatin1( "four@example.com" ); QTest::newRow( "simple single address" ) << from << to << cc << bcc; } { - QString from( "one@example.com" ); - QStringList to( "two@example.com" ); - to << "two.two@example.com"; - QStringList cc( "three@example.com" ); - cc << "three.three@example.com"; - QStringList bcc( "four@example.com" ); - bcc << "four.four@example.com"; + QString from = QString::fromLatin1( "one@example.com" ); + QStringList to; + to << QString::fromLatin1( "two@example.com" ); + to << QString::fromLatin1( "two.two@example.com" ); + QStringList cc; + cc << QString::fromLatin1( "three@example.com" ); + cc << QString::fromLatin1( "three.three@example.com" ); + QStringList bcc; + bcc << QString::fromLatin1( "four@example.com" ); + bcc << QString::fromLatin1( "four.four@example.com" ); QTest::newRow( "simple multi address" ) << from << to << cc << bcc; } { - QString from( "Me " ); - QStringList to( "You " ); - to << "two.two@example.com"; - QStringList cc( "And you " ); - cc << "three.three@example.com"; - QStringList bcc( "And you too " ); - bcc << "four.four@example.com"; + QString from = QString::fromLatin1( "Me " ); + QStringList to; + to << QString::fromLatin1( "You " ); + to << QString::fromLatin1( "two.two@example.com" ); + QStringList cc; + cc << QString::fromLatin1( "And you " ); + cc << QString::fromLatin1( "three.three@example.com" ); + QStringList bcc; + bcc << QString::fromLatin1( "And you too " ); + bcc << QString::fromLatin1( "four.four@example.com" ); QTest::newRow( "named multi address" ) << from << to << cc << bcc; } { - QString from( "Şîşkin " ); - QStringList to( "Ivan Turbincă " ); - to << "two.two@example.com"; - QStringList cc( "Luceafărul " ); - cc << "three.three@example.com"; - QStringList bcc( "Zburătorul " ); - bcc << "four.four@example.com"; + QString from = QString::fromUtf8( "Şîşkin " ); + QStringList to; + to << QString::fromUtf8( "Ivan Turbincă " ); + to << QString::fromLatin1( "two.two@example.com" ); + QStringList cc; + cc << QString::fromUtf8( "Luceafărul " ); + cc << QString::fromLatin1( "three.three@example.com" ); + QStringList bcc; + bcc << QString::fromUtf8( "Zburătorul " ); + bcc << QString::fromLatin1( "four.four@example.com" ); QTest::newRow( "non-ascii named multi address" ) << from << to << cc << bcc; } } void SkeletonMessageJobTest::testAddresses() { // An InfoPart should belong to a Composer, even if we don't use the composer itself. Composer *composer = new Composer; InfoPart *infoPart = composer->infoPart(); Q_ASSERT( infoPart ); QFETCH( QString, from ); QFETCH( QStringList, to ); QFETCH( QStringList, cc ); QFETCH( QStringList, bcc ); infoPart->setFrom( from ); infoPart->setTo( to ); infoPart->setCc( cc ); infoPart->setBcc( bcc ); SkeletonMessageJob *sjob = new SkeletonMessageJob( infoPart, composer ); QVERIFY( sjob->exec() ); Message *message = sjob->message(); { QVERIFY( message->from( false ) ); kDebug() << "From:" << message->from()->asUnicodeString(); QCOMPARE( from, message->from()->asUnicodeString() ); } { QVERIFY( message->to( false ) ); kDebug() << "To:" << message->to()->asUnicodeString(); foreach( const QString &addr, message->to()->prettyAddresses() ) { kDebug() << addr; QVERIFY( to.contains( addr ) ); to.removeOne( addr ); } QVERIFY( to.isEmpty() ); } { QVERIFY( message->cc( false ) ); kDebug() << "Cc:" << message->cc()->asUnicodeString(); foreach( const QString &addr, message->cc()->prettyAddresses() ) { kDebug() << addr; QVERIFY( cc.contains( addr ) ); cc.removeOne( addr ); } QVERIFY( cc.isEmpty() ); } { QVERIFY( message->bcc( false ) ); kDebug() << "Bcc:" << message->bcc()->asUnicodeString(); foreach( const QString &addr, message->bcc()->prettyAddresses() ) { kDebug() << addr; QVERIFY( bcc.contains( addr ) ); bcc.removeOne( addr ); } QVERIFY( bcc.isEmpty() ); } } #include "skeletonmessagejobtest.moc"