/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
// This flag changes the implementation to use S60 CDcoumentHandler
// instead of apparch when opening the files
#define USE_DOCUMENTHANDLER
#include <qcoreapplication.h>
#include <qdir.h>
#include <qurl.h>
#include <private/qcore_symbian_p.h>
/* :QTP:QTBUG-5713: illegal app layer dependency
#include <miutset.h> // KUidMsgTypeSMTP
*/
/** UID of an SMTP message type. */
const TUid KUidMsgTypeSMTP = {0x10001028}; // 268439592
#include <txtrich.h> // CRichText
#include <f32file.h> // TDriveUnit etc
#include <eikenv.h> // CEikonEnv
#include <apgcli.h> // RApaLsSession
#include <apgtask.h> // TApaTaskList, TApaTask
#include <rsendas.h> // RSendAs
#include <rsendasmessage.h> // RSendAsMessage
#ifdef Q_WS_S60
# include <pathinfo.h> // PathInfo
# ifdef USE_DOCUMENTHANDLER
# include <documenthandler.h> // CDocumentHandler
# include <aknserverapp.h>
# endif
#else
# warning CDocumentHandler requires support for S60
# undef USE_DOCUMENTHANDLER // Fallback to RApaLsSession based implementation
#endif
QT_BEGIN_NAMESPACE
_LIT(KCacheSubDir, "Cache\\");
_LIT(KSysBin, "\\Sys\\Bin\\");
_LIT(KBrowserPrefix, "4 " );
_LIT(KFontsDir, "z:\\resource\\Fonts\\");
const TUid KUidBrowser = { 0x10008D39 };
template<class R>
class QAutoClose
{
public:
QAutoClose(R& aObj) : mPtr(&aObj) {}
~QAutoClose()
{
if (mPtr)
mPtr->Close();
}
void Forget()
{
mPtr = 0;
}
private:
QAutoClose(const QAutoClose&);
QAutoClose& operator=(const QAutoClose&);
private:
R* mPtr;
};
#ifdef USE_DOCUMENTHANDLER
class QS60DocumentHandler : public MAknServerAppExitObserver
{
public:
QS60DocumentHandler() :docHandler(0) {}
~QS60DocumentHandler() {
delete docHandler;
}
CDocumentHandler& documentHandler() {
// In case user calls openUrl twice subsequently, before the first embedded app is closed
// we use the same CDocumentHandler instance. Using same instance makes sure the first
// launched embedded app is closed and latter one gets embedded to our app.
// Using different instance would help only theoretically since user cannot interact with
// several embedded apps at the same time.
if(!docHandler) {
QT_TRAP_THROWING(docHandler = CDocumentHandler::NewL());
docHandler->SetExitObserver(this);
}
return *docHandler;
}
private: // From MAknServerAppExitObserver
void HandleServerAppExit(TInt /*aReason*/) {
delete docHandler;
docHandler = 0;
}
private:
CDocumentHandler* docHandler;
};
Q_GLOBAL_STATIC(QS60DocumentHandler, qt_s60_documenthandler);
#endif
static void handleMailtoSchemeLX(const QUrl &url)
{
// this function has many intermingled leaves and throws. Qt and Symbian objects do not have
// destructor dependencies, and cleanup object is used to prevent cleanup stack dependency on stack.
QString recipient = url.path();
QString subject = url.queryItemValue(QLatin1String("subject"));
QString body = url.queryItemValue(QLatin1String("body"));
QString to = url.queryItemValue(QLatin1String("to"));
QString cc = url.queryItemValue(QLatin1String("cc"));
QString bcc = url.queryItemValue(QLatin1String("bcc"));
// these fields might have comma separated addresses
QStringList recipients = recipient.split(QLatin1String(","), QString::SkipEmptyParts);
QStringList tos = to.split(QLatin1String(","), QString::SkipEmptyParts);
QStringList ccs = cc.split(QLatin1String(","), QString::SkipEmptyParts);
QStringList bccs = bcc.split(QLatin1String(","), QString::SkipEmptyParts);
RSendAs sendAs;
User::LeaveIfError(sendAs.Connect());
QAutoClose<RSendAs> sendAsCleanup(sendAs);
CSendAsAccounts* accounts = CSendAsAccounts::NewL();
CleanupStack::PushL(accounts);
sendAs.AvailableAccountsL(KUidMsgTypeSMTP, *accounts);
TInt count = accounts->Count();
CleanupStack::PopAndDestroy(accounts);
if(!count) {
// TODO: Task 259192: We should try to create account if count == 0
// CSendUi would provide account creation service for us, but it requires ridicilous
// capabilities: LocalServices NetworkServices ReadDeviceData ReadUserData WriteDeviceData WriteUserData
User::Leave(KErrNotSupported);
} else {
RSendAsMessage sendAsMessage;
sendAsMessage.CreateL(sendAs, KUidMsgTypeSMTP);
QAutoClose<RSendAsMessage> sendAsMessageCleanup(sendAsMessage);
// Subject
sendAsMessage.SetSubjectL(qt_QString2TPtrC(subject));
// Body
sendAsMessage.SetBodyTextL(qt_QString2TPtrC(body));
// To
foreach(QString item, recipients)
sendAsMessage.AddRecipientL(qt_QString2TPtrC(item), RSendAsMessage::ESendAsRecipientTo);
foreach(QString item, tos)
sendAsMessage.AddRecipientL(qt_QString2TPtrC(item), RSendAsMessage::ESendAsRecipientTo);
// Cc
foreach(QString item, ccs)
sendAsMessage.AddRecipientL(qt_QString2TPtrC(item), RSendAsMessage::ESendAsRecipientCc);
// Bcc
foreach(QString item, bccs)
sendAsMessage.AddRecipientL(qt_QString2TPtrC(item), RSendAsMessage::ESendAsRecipientBcc);
// send the message
sendAsMessage.LaunchEditorAndCloseL();
// sendAsMessage is already closed
sendAsMessageCleanup.Forget();
}
}
static bool handleMailtoScheme(const QUrl &url)
{
TRAPD(err, QT_TRYCATCH_LEAVING(handleMailtoSchemeLX(url)));
return err ? false : true;
}
static void handleOtherSchemesL(const TDesC& aUrl)
{
// Other schemes are at the moment passed to WEB browser
HBufC* buf16 = HBufC::NewLC(aUrl.Length() + KBrowserPrefix.iTypeLength);
buf16->Des().Copy(KBrowserPrefix); // Prefix used to launch correct browser view
buf16->Des().Append(aUrl);
TApaTaskList taskList(CEikonEnv::Static()->WsSession());
TApaTask task = taskList.FindApp(KUidBrowser);
if (task.Exists()){
// Switch to existing browser instance
HBufC8* param8 = HBufC8::NewLC(buf16->Length());
param8->Des().Append(buf16->Des());
task.SendMessage(TUid::Uid( 0 ), *param8); // Uid is not used
CleanupStack::PopAndDestroy(param8);
} else {
// Start a new browser instance
RApaLsSession appArcSession;
User::LeaveIfError(appArcSession.Connect());
CleanupClosePushL<RApaLsSession>(appArcSession);
TThreadId id;
appArcSession.StartDocument(*buf16, KUidBrowser, id);
CleanupStack::PopAndDestroy(); // appArcSession
}
CleanupStack::PopAndDestroy(buf16);
}
static bool handleOtherSchemes(const QUrl &url)
{
QString encUrl(QString::fromUtf8(url.toEncoded()));
TPtrC urlPtr(qt_QString2TPtrC(encUrl));
TRAPD( err, handleOtherSchemesL(urlPtr));
return err ? false : true;
}
static TDriveUnit exeDrive()
{
RProcess me;
TFileName processFileName = me.FileName();
TDriveUnit drive(processFileName);
return drive;
}
static TDriveUnit writableExeDrive()
{
TDriveUnit drive = exeDrive();
if(drive.operator TInt() == EDriveZ)
return TDriveUnit(EDriveC);
return drive;
}
static TPtrC writableDataRoot()
{
TDriveUnit drive = exeDrive();
#ifdef Q_WS_S60
switch(drive.operator TInt()){
case EDriveC:
return PathInfo::PhoneMemoryRootPath();
break;
case EDriveE:
return PathInfo::MemoryCardRootPath();
break;
case EDriveZ:
// It is not possible to write on ROM drive ->
// return phone mem root path instead
return PathInfo::PhoneMemoryRootPath();
break;
default:
return PathInfo::PhoneMemoryRootPath();
break;
}
#else
#warning No fallback implementation of writableDataRoot()
return 0;
#endif
}
static void openDocumentL(const TDesC& aUrl)
{
#ifndef USE_DOCUMENTHANDLER
// Start app associated to file MIME type by using RApaLsSession
// Apparc base method cannot be used to open app in embedded mode,
// but seems to be most stable way at the moment
RApaLsSession appArcSession;
User::LeaveIfError(appArcSession.Connect());
CleanupClosePushL<RApaLsSession>(appArcSession);
TThreadId id;
// ESwitchFiles means do not start another instance
// Leaves if file does not exist, leave is trapped in openDocument and false returned to user.
User::LeaveIfError(appArcSession.StartDocument(aUrl, id,
RApaLsSession::ESwitchFiles)); // ELaunchNewApp
CleanupStack::PopAndDestroy(); // appArcSession
#else
// This is an alternative way to launch app associated to MIME type
// CDocumentHandler also supports opening apps in embedded mode.
TDataType temp;
qt_s60_documenthandler()->documentHandler().OpenFileEmbeddedL(aUrl, temp);
#endif
}
#ifdef USE_SCHEMEHANDLER
// The schemehandler component only exist in private SDK. This implementation
// exist here just for convenience in case that we need to use it later on
// The schemehandle based implementation is not yet tested.
// The biggest advantage of schemehandler is that it can handle
// wide range of schemes and is extensible by plugins
static bool handleUrl(const QUrl &url)
{
if (!url.isValid())
return false;
QString urlString(url.toString());
TPtrC urlPtr(qt_QString2TPtrC(urlString));
TRAPD( err, handleUrlL(urlPtr));
return err ? false : true;
}
static void handleUrlL(const TDesC& aUrl)
{
CSchemeHandler* schemeHandler = CSchemeHandler::NewL(aUrl);
CleanupStack::PushL(schemeHandler);
schemeHandler->HandleUrlStandaloneL(); // Process the Url in standalone mode
CleanupStack::PopAndDestroy();
}
static bool launchWebBrowser(const QUrl &url)
{
return handleUrl(url);
}
static bool openDocument(const QUrl &file)
{
return handleUrl(url);
}
#endif
static bool launchWebBrowser(const QUrl &url)
{
if (!url.isValid())
return false;
if (url.scheme() == QLatin1String("mailto")) {
return handleMailtoScheme(url);
}
return handleOtherSchemes( url );
}
static bool openDocument(const QUrl &file)
{
if (!file.isValid())
return false;
QString filePath = file.toLocalFile();
filePath = QDir::toNativeSeparators(filePath);
TPtrC filePathPtr(qt_QString2TPtrC(filePath));
TRAPD(err, openDocumentL(filePathPtr));
return err ? false : true;
}
QString QDesktopServices::storageLocation(StandardLocation type)
{
TFileName path;
switch (type) {
case DesktopLocation:
qWarning("No desktop concept in Symbian OS");
// But lets still use some feasible default
path.Append(writableDataRoot());
break;
case DocumentsLocation:
path.Append(writableDataRoot());
break;
case FontsLocation:
path.Append(KFontsDir);
break;
case ApplicationsLocation:
path.Append(exeDrive().Name());
path.Append(KSysBin);
break;
case MusicLocation:
path.Append(writableDataRoot());
#ifdef Q_WS_S60
path.Append(PathInfo::SoundsPath());
#endif
break;
case MoviesLocation:
path.Append(writableDataRoot());
#ifdef Q_WS_S60
path.Append(PathInfo::VideosPath());
#endif
break;
case PicturesLocation:
path.Append(writableDataRoot());
#ifdef Q_WS_S60
path.Append(PathInfo::ImagesPath());
#endif
break;
case TempLocation:
return QDir::tempPath();
break;
case HomeLocation:
path.Append(writableDataRoot());
//return QDir::homePath(); break;
break;
case DataLocation:
CEikonEnv::Static()->FsSession().PrivatePath(path);
path.Insert(0, writableExeDrive().Name());
break;
case CacheLocation:
CEikonEnv::Static()->FsSession().PrivatePath(path);
path.Insert(0, writableExeDrive().Name());
path.Append(KCacheSubDir);
break;
default:
// Lets use feasible default
path.Append(writableDataRoot());
break;
}
// Convert to cross-platform format and clean the path
QString nativePath = QString::fromUtf16(path.Ptr(), path.Length());
QString qtPath = QDir::fromNativeSeparators(nativePath);
qtPath = QDir::cleanPath(qtPath);
// Note: The storage location returned can be a directory that does not exist;
// i.e., it may need to be created by the system or the user.
return qtPath;
}
typedef QString (*LocalizerFunc)(QString&);
static QString defaultLocalizedDirectoryName(QString&)
{
return QString();
}
QString QDesktopServices::displayName(StandardLocation type)
{
static LocalizerFunc ptrLocalizerFunc = NULL;
if (!ptrLocalizerFunc) {
ptrLocalizerFunc = reinterpret_cast<LocalizerFunc>
(qt_resolveS60PluginFunc(S60Plugin_LocalizedDirectoryName));
if (!ptrLocalizerFunc)
ptrLocalizerFunc = &defaultLocalizedDirectoryName;
}
QString rawPath = storageLocation(type);
return ptrLocalizerFunc(rawPath);
}
QT_END_NAMESPACE