/**************************************************************************** ** ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** Contact: Qt Software Information (qt-info@nokia.com) ** ** Copyright (C) 2009 netlabs.org. OS/2 parts. ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial Usage ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Commercial License Agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Nokia. ** ** 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.0, included in the file LGPL_EXCEPTION.txt in this ** package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** If you are unsure which license is appropriate for your use, please ** contact the sales department at qt-sales@nokia.com. ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qmime.h" #include "qimagereader.h" #include "qimagewriter.h" #include "qdatastream.h" #include "qbuffer.h" #include "qt_os2.h" #include "qapplication_p.h" #include "qtextcodec.h" #include "qregexp.h" #include "qalgorithms.h" #include "qmap.h" #include "qdnd_p.h" #include "qurl.h" #include "qvariant.h" #include "qtextdocument.h" #include "qdir.h" #define QMIME_DEBUG QT_BEGIN_NAMESPACE class QPMMimeList { public: QPMMimeList(); ~QPMMimeList(); void addMime(QPMMime *mime); void removeMime(QPMMime *mime); QList mimes(); private: void init(); bool initialized; QList list; }; Q_GLOBAL_STATIC(QPMMimeList, theMimeList); /*! \class QPMMime \brief The QMPMime class maps open-standard MIME to OS/2 PM Clipboard formats. \ingroup io \ingroup draganddrop \ingroup misc Qt's drag-and-drop and clipboard facilities use the MIME standard. On X11, this maps trivially to the Xdnd protocol, but on OS/2 although some applications use MIME types to describe clipboard formats, others use arbitrary non-standardized naming conventions, or unnamed built-in formats of the Presentation Manager. By instantiating subclasses of QPMMime that provide conversions between OS/2 PM Clipboard and MIME formats, you can convert proprietary clipboard formats to MIME formats. Qt has predefined support for the following PM Clipboard formats (custom formats registered in the system atom table by name are given in double quotes): \table \header \o PM Format \o Equivalent MIME type \row \o \c CF_TEXT \o \c text/plain (system codepage, zero-terminated string) \row \o \c "text/unicode" \o \c text/plain (16-bit Unicode, zero-terminated string, Mozilla-compatible) \row \o \c "text/html" \o \c text/html (16-bit Unicode, zero-terminated string, Mozilla-compatible) \row \o \c CF_BITMAP \o \c{image/xyz}, where \c xyz is a \l{QImageWriter::supportedImageFormats()}{Qt image format} \row \o \c "x-mime:" \o data in the format corresponding to the given MIME type \c \endtable Note that all "x-mime:" formats use the CFI_POINTER storage type. That is, the clipboard contains a pointer to the memory block containing the MIME data in the corresponding format. The first 4 bytes of this memory block always contain the length of the subsequent MIME data array, in bytes. An example use of this class by the user application would be to map the PM Metafile clipboard format (\c CF_METAFILE) to and from the MIME type \c{image/x-metafile}. This conversion might simply be adding or removing a header, or even just passing on the data. See \l{Drag and Drop} for more information on choosing and definition MIME types. */ /*! Constructs a new conversion object, adding it to the globally accessed list of available converters. */ QPMMime::QPMMime() { theMimeList()->addMime(this); } /*! Destroys a conversion object, removing it from the global list of available converters. */ QPMMime::~QPMMime() { theMimeList()->removeMime(this); } /*! Registers the MIME type \a mime, and returns an ID number identifying the format on OS/2. Intended to be used by QPMMime implementations for registering custom clipboard formats they use. */ // static ULONG QPMMime::registerMimeType(const QString &mime) { ULONG cf = WinAddAtom(WinQuerySystemAtomTable(), mime.toLocal8Bit()); if (!cf) { #ifndef QT_NO_DEBUG qWarning("QPMMime: WinAddAtom failed with %lX", WinGetLastError(NULLHANDLE)); #endif return 0; } return cf; } /*! Allocates a block of shared memory of the given size and returns the address of this block. This memory block may be then filled with data and returned by convertFromMimeData() as the value of the CFI_POINTER type. */ // static ULONG QPMMime::allocateMemory(size_t size) { if (size == 0) return 0; ULONG data = 0; // allocate giveable memory for the array APIRET arc = DosAllocSharedMem((PVOID *)&data, NULL, size, PAG_WRITE | PAG_COMMIT | OBJ_GIVEABLE); if (arc != NO_ERROR) { #ifndef QT_NO_DEBUG qWarning("QPMMime::allocateMemory: DosAllocSharedMem failed with %lu", arc); #endif return 0; } return data; } /*! Frees memory allocated by allocateMemory(). Normally, not used because the CFI_POINTER memory blocks are owned by the system after convertFromMimeData() returns. */ // static void QPMMime::freeMemory(ULONG addr) { DosFreeMem((PVOID)addr); } /*! \fn QList QPMMime::formatsForMimeData(const QMimeData *mimeData) const Returns a list of ULONG values representing the different OS/2 PM clipboard formats that can be provided for the \a mimeData, in order of precedence (the most suitable format goes first), or an empty list if neither of the mime types provided by \a mimeData is supported by this converter. Note that each item in the returned list is actually a pair consisting of the mime type name and the corresponding format identifier. All subclasses must reimplement this pure virtual function. */ /*! \fn bool QPMMime::convertFromMimeData(const QMimeData *mimeData, ULONG format, ULONG &flags, ULONG *data) const Converts the \a mimeData to the specified \a format. If \a data is not NULL, a handle to the converted data should be then placed in a variable pointed to by \a data and with the necessary flags describing the handle returned in the \a flags variable. The following flags describing the data storage type are recognized: \table \row \o \c CFI_POINTER \o \a data is a pointer to a block of memory allocated with QPMMime::allocateMemory() \row \o \c CFI_HANDLE \o \a data is a handle to the appropriate PM resource \endtable If \a data is NULL then a delayed conversion is requested by the caller. The implementation should return the appropriate flags in the \a flags variable and may perform the real data conversion later when this method is called again with \a data being non-NULL. Return true if the conversion was successful. All subclasses must reimplement this pure virtual function. */ /*! \fn QList QPMMime::mimesForFormats(const QList &formats) const Returns a list of mime types that will be created form the specified \a list of \a formats, in order of precedence (the most suitable mime type comes first), or an empty list if neither of the \a formats is supported by this converter. Note that each item in the returned list is actually a pair consisting of the mime type name and the corresponding format identifier. All subclasses must reimplement this pure virtual function. */ /*! \fn QVariant QPMMime::convertFromFormat(ULONG format, ULONG flags, ULONG data, const QString &mimeType, QVariant::Type preferredType) const Returns a QVariant containing the converted from the \a data in the specified \a format with the given \a flags to the requested \a mimeType. If possible the QVariant should be of the \a preferredType to avoid needless conversions. All subclasses must reimplement this pure virtual function. */ // static QList QPMMime::allConvertersFromFormats(const QList &formats) { QList matches; QList mimes = theMimeList()->mimes(); foreach(QPMMime *mime, mimes) { QList fmts = mime->mimesForFormats(formats); int priority = 0; foreach (MimeCFPair fmt, fmts) { ++priority; QList::iterator it = matches.begin(); for (; it != matches.end(); ++it) { Match &match = *it; if (match.mime == fmt.mime) { // replace if priority is higher, ignore otherwise if (priority < match.priority) { match.converter = mime; match.format = fmt.format; match.priority = priority; } break; } } if (it == matches.end()) { matches += Match(mime, fmt.mime, fmt.format, priority); } } } return matches; } // static QList QPMMime::allConvertersFromMimeData(const QMimeData *mimeData) { QList matches; QList mimes = theMimeList()->mimes(); foreach(QPMMime *mime, mimes) { QList fmts = mime->formatsForMimeData(mimeData); int priority = 0; foreach (MimeCFPair fmt, fmts) { ++priority; QList::iterator it = matches.begin(); for (; it != matches.end(); ++it) { Match &match = *it; if (mime == mimes.last()) { // QPMMimeAnyMime? if (match.mime == fmt.mime){ // we assume that specialized converters (that come // first) provide a more precise conversion than // QPMMimeAnyMime and don't let it get into the list in // order to avoid unnecessary duplicate representations break; } } if (match.format == fmt.format) { // replace if priority is higher, ignore otherwise if (priority < match.priority) { match.converter = mime; match.mime = fmt.mime; match.priority = priority; } break; } } if (it == matches.end()) { matches += Match(mime, fmt.mime, fmt.format, priority); } } } return matches; } QString QPMMime::formatName(ULONG format) { QString name; HATOMTBL tbl = WinQuerySystemAtomTable(); if (tbl != NULLHANDLE) { ULONG len = WinQueryAtomLength(tbl, format); QByteArray atom(len, '\0'); WinQueryAtomName(tbl, format, atom.data(), atom.size() + 1); name = QString::fromLocal8Bit(atom); } return name; } //////////////////////////////////////////////////////////////////////////////// class QPMMimeText : public QPMMime { public: QPMMimeText(); // for converting from Qt QList formatsForMimeData(const QMimeData *mimeData) const; bool convertFromMimeData(const QMimeData *mimeData, ULONG format, ULONG &flags, ULONG *data) const; // for converting to Qt QList mimesForFormats(const QList &formats) const; QVariant convertFromFormat(ULONG format, ULONG flags, ULONG data, const QString &mimeType, QVariant::Type preferredType) const; const ULONG CF_TextUnicode; const ULONG CF_TextHtml; }; QPMMimeText::QPMMimeText() // "text/unicode" is what Mozilla uses to for unicode : CF_TextUnicode (registerMimeType(QLatin1String("text/unicode"))) // "text/html" is what Mozilla uses to for HTML , CF_TextHtml (registerMimeType(QLatin1String("text/html"))) { } QList QPMMimeText::formatsForMimeData(const QMimeData *mimeData) const { QList fmts; // prefer HTML as it's reacher if (mimeData->hasHtml()) fmts << MimeCFPair(QLatin1String("text/html"), CF_TextHtml); // prefer unicode over local8Bit if (mimeData->hasText()) fmts << MimeCFPair(QLatin1String("text/plain"), CF_TextUnicode) << MimeCFPair(QLatin1String("text/plain"), CF_TEXT); return fmts; } // text/plain is defined as using CRLF, but so many programs don't, // and programmers just look for '\n' in strings. // OS/2 really needs CRLF, so we ensure it here. bool QPMMimeText::convertFromMimeData(const QMimeData *mimeData, ULONG format, ULONG &flags, ULONG *data) const { if (!mimeData->hasText()) return false; flags = CFI_POINTER; if (data == NULL) return true; // delayed rendering, nothing to do QByteArray r; if (format == CF_TEXT) { QByteArray str = mimeData->text().toLocal8Bit(); // Anticipate required space for CRLFs at 1/40 int maxsize = str.size()+str.size()/40+1; r.fill('\0', maxsize); char *o = r.data(); const char *d = str.data(); const int s = str.size(); bool cr = false; int j = 0; for (int i = 0; i < s; i++) { char c = d[i]; if (c == '\r') cr = true; else { if (c == '\n') { if (!cr) o[j++] = '\r'; } cr = false; } o[j++] = c; if (j+1 >= maxsize) { maxsize += maxsize/4; r.resize(maxsize); o = r.data(); } } if (j < r.size()) o[j] = '\0'; } else if (format == CF_TextUnicode || CF_TextHtml) { QString str = mimeData->text(); const QChar *u = str.unicode(); QString res; const int s = str.length(); int maxsize = s + s/40 + 3; res.resize(maxsize); int ri = 0; bool cr = false; for (int i = 0; i < s; ++i) { if (*u == QLatin1Char('\r')) cr = true; else { if (*u == QLatin1Char('\n') && !cr) res[ri++] = QLatin1Char('\r'); cr = false; } res[ri++] = *u; if (ri+3 >= maxsize) { maxsize += maxsize/4; res.resize(maxsize); } ++u; } res.truncate(ri); const int byteLength = res.length()*2; r.fill('\0', byteLength + 2); memcpy(r.data(), res.unicode(), byteLength); r[byteLength] = 0; r[byteLength+1] = 0; } else{ return false; } *data = QPMMime::allocateMemory(r.size()); if (!*data) return false; memcpy((void *)*data, r.data(), r.size()); return true; } QList QPMMimeText::mimesForFormats(const QList &formats) const { QList mimes; // prefer HTML as it's reacher if (formats.contains(CF_TextHtml)) mimes << MimeCFPair(QLatin1String("text/html"), CF_TextHtml); // prefer unicode over local8Bit if (formats.contains(CF_TextUnicode)) mimes << MimeCFPair(QLatin1String("text/plain"), CF_TextUnicode); if (formats.contains(CF_TEXT)) mimes << MimeCFPair(QLatin1String("text/plain"), CF_TEXT); return mimes; } QVariant QPMMimeText::convertFromFormat(ULONG format, ULONG flags, ULONG data, const QString &mimeType, QVariant::Type preferredType) const { QVariant ret; if (!mimeType.startsWith("text/plain") && !mimeType.startsWith("text/html")) return ret; if (!(flags & CFI_POINTER) || !data) return ret; QString str; if (format == CF_TEXT) { const char *d = (const char *)data; QByteArray r(""); if (*d) { const int s = qstrlen(d); r.fill('\0', s); char *o = r.data(); int j = 0; for (int i = 0; i < s; i++) { char c = d[i]; if (c != '\r') o[j++] = c; } } str = QString::fromLocal8Bit(r); } else if (format == CF_TextUnicode || CF_TextHtml) { str = QString::fromUtf16((const unsigned short *)data); str.replace(QLatin1String("\r\n"), QLatin1String("\n")); } if (preferredType == QVariant::String) ret = str; else ret = str.toUtf8(); return ret; } //////////////////////////////////////////////////////////////////////////////// class QPMMimeAnyMime : public QPMMime { public: QPMMimeAnyMime(); // for converting from Qt QList formatsForMimeData(const QMimeData *mimeData) const; bool convertFromMimeData(const QMimeData *mimeData, ULONG format, ULONG &flags, ULONG *data) const; // for converting to Qt QList mimesForFormats(const QList &formats) const; QVariant convertFromFormat(ULONG format, ULONG flags, ULONG data, const QString &mimeType, QVariant::Type preferredType) const; private: ULONG registerMimeType(const QString &mime) const; QString registerFormat(ULONG format) const; mutable QMap cfMap; mutable QMap mimeMap; static QStringList ianaTypes; static QString mimePrefix; static QString customPrefix; }; // static QStringList QPMMimeAnyMime::ianaTypes; QString QPMMimeAnyMime::mimePrefix; QString QPMMimeAnyMime::customPrefix; QPMMimeAnyMime::QPMMimeAnyMime() { //MIME Media-Types if (!ianaTypes.size()) { ianaTypes.append(QLatin1String("application/")); ianaTypes.append(QLatin1String("audio/")); ianaTypes.append(QLatin1String("example/")); ianaTypes.append(QLatin1String("image/")); ianaTypes.append(QLatin1String("message/")); ianaTypes.append(QLatin1String("model/")); ianaTypes.append(QLatin1String("multipart/")); ianaTypes.append(QLatin1String("text/")); ianaTypes.append(QLatin1String("video/")); mimePrefix = QLatin1String("x-mime:"); customPrefix = QLatin1String("application/x-qt-pm-mime;value=\""); } } QList QPMMimeAnyMime::formatsForMimeData(const QMimeData *mimeData) const { QList fmts; QStringList mimes = QInternalMimeData::formatsHelper(mimeData); foreach (QString mime, mimes) { ULONG cf = cfMap.value(mime); if (!cf) cf = registerMimeType(mime); if (cf) fmts << MimeCFPair(mime, cf); } return fmts; } bool QPMMimeAnyMime::convertFromMimeData(const QMimeData *mimeData, ULONG format, ULONG &flags, ULONG *data) const { QString mime = mimeMap.value(format); if (mime.isNull()) return false; flags = CFI_POINTER; if (data == NULL) return true; // delayed rendering, nothing to do QByteArray r = QInternalMimeData::renderDataHelper(mime, mimeData); if (r.isNull()) return false; *data = QPMMime::allocateMemory(r.size() + sizeof(ULONG)); if (!*data) return false; *((ULONG *)(*data)) = r.size(); memcpy((void *)(*data + sizeof(ULONG)), r.data(), r.size()); return true; } QList QPMMimeAnyMime::mimesForFormats(const QList &formats) const { QList mimes; foreach (ULONG format, formats) { QString mime = mimeMap.value(format); if (mime.isEmpty()) mime = registerFormat(format); if (!mime.isEmpty()) mimes << MimeCFPair(mime, format); } return mimes; } QVariant QPMMimeAnyMime::convertFromFormat(ULONG format, ULONG flags, ULONG data, const QString &mimeType, QVariant::Type preferredType) const { Q_UNUSED(preferredType); QVariant ret; if (cfMap.value(mimeType) != format) return ret; if (!(flags & CFI_POINTER) || !data) return ret; // get the real block size (always rounded to the page boundary (4K)) ULONG sz = ~0, fl = 0, arc; arc = DosQueryMem((PVOID)data, &sz, &fl); if (arc != NO_ERROR) { #ifndef QT_NO_DEBUG qWarning("QPMMimeText::convertFromFormat: DosQueryMem failed with %lu", arc); #endif return ret; } ULONG size = *((ULONG *)data); if (!size || size + sizeof(ULONG) > sz) return ret; // it should be enough to return the data and let QMimeData do the rest. ret = QByteArray((const char *)(data + sizeof(ULONG)), size); return ret; } ULONG QPMMimeAnyMime::registerMimeType(const QString &mime) const { if (mime.isEmpty()) return 0; QString mimeToReg = mime; bool ianaType = false; foreach(QString prefix, ianaTypes) { if (mime.startsWith(prefix)) { ianaType = true; break; } } if (!ianaType) { // prepend the non-standard type with the prefix that makes it comply // with the standard mimeToReg = customPrefix + mime + QChar('\"'); } mimeToReg = mimePrefix + mimeToReg; ULONG cf = QPMMime::registerMimeType(mimeToReg); if (cf) { cfMap[mime] = cf; mimeMap[cf] = mime; } return cf; } QString QPMMimeAnyMime::registerFormat(ULONG format) const { QString mime; if (!format) return mime; QString atomStr = formatName(format); if (atomStr.startsWith(mimePrefix)) { // the format represents the mime type we can recognize mime = atomStr.mid(mimePrefix.size()); if (!mime.isEmpty()) { cfMap[mime] = format; mimeMap[format] = mime; } } return mime; } //////////////////////////////////////////////////////////////////////////////// QPMMimeList::QPMMimeList() : initialized(false) { } QPMMimeList::~QPMMimeList() { while (list.size()) delete list.first(); } void QPMMimeList::init() { if (!initialized) { initialized = true; new QPMMimeAnyMime; // must be the first (used as a fallback) new QPMMimeText; } } void QPMMimeList::addMime(QPMMime *mime) { init(); list.prepend(mime); } void QPMMimeList::removeMime(QPMMime *mime) { init(); list.removeAll(mime); } QList QPMMimeList::mimes() { init(); return list; } QT_END_NAMESPACE