/****************************************************************************
**
** 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$
**
****************************************************************************/
#include "qmime.h"
#include "qimagereader.h"
#include "qimagewriter.h"
#include "qdatastream.h"
#include "qbuffer.h"
#include "qt_windows.h"
#include "qapplication_p.h"
#include "qtextcodec.h"
#include "qregexp.h"
#include "qalgorithms.h"
#include "qmap.h"
#include "qdnd_p.h"
#include <shlobj.h>
#include "qurl.h"
#include "qvariant.h"
#include "qtextdocument.h"
#include "qdir.h"
#if defined(Q_OS_WINCE)
#include "qguifunctions_wince.h"
#endif
QT_BEGIN_NAMESPACE
#ifndef QT_NO_IMAGEFORMAT_BMP
#ifndef CF_DIBV5
#define CF_DIBV5 17
#endif
/* The MSVC compilers allows multi-byte characters, that has the behavior of
* that each character gets shifted into position. 0x73524742 below is for MSVC
* equivalent to doing 'sRGB', but this does of course not work
* on conformant C++ compilers. */
#define BMP_LCS_sRGB 0x73524742
#define BMP_LCS_GM_IMAGES 0x00000004L
struct _CIEXYZ {
long ciexyzX, ciexyzY, ciexyzZ;
};
struct _CIEXYZTRIPLE {
_CIEXYZ ciexyzRed, ciexyzGreen, ciexyzBlue;
};
struct BMP_BITMAPV5HEADER {
DWORD bV5Size;
LONG bV5Width;
LONG bV5Height;
WORD bV5Planes;
WORD bV5BitCount;
DWORD bV5Compression;
DWORD bV5SizeImage;
LONG bV5XPelsPerMeter;
LONG bV5YPelsPerMeter;
DWORD bV5ClrUsed;
DWORD bV5ClrImportant;
DWORD bV5RedMask;
DWORD bV5GreenMask;
DWORD bV5BlueMask;
DWORD bV5AlphaMask;
DWORD bV5CSType;
_CIEXYZTRIPLE bV5Endpoints;
DWORD bV5GammaRed;
DWORD bV5GammaGreen;
DWORD bV5GammaBlue;
DWORD bV5Intent;
DWORD bV5ProfileData;
DWORD bV5ProfileSize;
DWORD bV5Reserved;
};
static const int BMP_BITFIELDS = 3;
extern bool qt_read_dib(QDataStream&, QImage&); // qimage.cpp
extern bool qt_write_dib(QDataStream&, QImage); // qimage.cpp
static bool qt_write_dibv5(QDataStream &s, QImage image);
static bool qt_read_dibv5(QDataStream &s, QImage &image);
#endif
//#define QMIME_DEBUG
// helpers for using global memory
static int getCf(const FORMATETC &formatetc)
{
return formatetc.cfFormat;
}
static FORMATETC setCf(int cf)
{
FORMATETC formatetc;
formatetc.cfFormat = cf;
formatetc.dwAspect = DVASPECT_CONTENT;
formatetc.lindex = -1;
formatetc.ptd = NULL;
formatetc.tymed = TYMED_HGLOBAL;
return formatetc;
}
static bool setData(const QByteArray &data, STGMEDIUM *pmedium)
{
HGLOBAL hData = GlobalAlloc(0, data.size());
if (!hData)
return false;
void *out = GlobalLock(hData);
memcpy(out, data.data(), data.size());
GlobalUnlock(hData);
pmedium->tymed = TYMED_HGLOBAL;
pmedium->hGlobal = hData;
pmedium->pUnkForRelease = 0;
return true;
}
static QByteArray getData(int cf, IDataObject *pDataObj)
{
QByteArray data;
FORMATETC formatetc = setCf(cf);
STGMEDIUM s;
if (pDataObj->GetData(&formatetc, &s) == S_OK) {
DWORD * val = (DWORD*)GlobalLock(s.hGlobal);
data = QByteArray::fromRawData((char*)val, GlobalSize(s.hGlobal));
data.detach();
GlobalUnlock(s.hGlobal);
ReleaseStgMedium(&s);
} else {
//Try reading IStream data
formatetc.tymed = TYMED_ISTREAM;
if (pDataObj->GetData(&formatetc, &s) == S_OK) {
char szBuffer[4096];
ULONG actualRead = 0;
LARGE_INTEGER pos = {{0, 0}};
//Move to front (can fail depending on the data model implemented)
HRESULT hr = s.pstm->Seek(pos, STREAM_SEEK_SET, NULL);
while(SUCCEEDED(hr)){
hr = s.pstm->Read(szBuffer, sizeof(szBuffer), &actualRead);
if (SUCCEEDED(hr) && actualRead > 0) {
data += QByteArray::fromRawData(szBuffer, actualRead);
}
if (actualRead != sizeof(szBuffer))
break;
}
data.detach();
ReleaseStgMedium(&s);
}
}
return data;
}
static bool canGetData(int cf, IDataObject * pDataObj)
{
FORMATETC formatetc = setCf(cf);
if (pDataObj->QueryGetData(&formatetc) != S_OK){
formatetc.tymed = TYMED_ISTREAM;
return pDataObj->QueryGetData(&formatetc) == S_OK;
}
return true;
}
class QWindowsMimeList
{
public:
QWindowsMimeList();
~QWindowsMimeList();
void addWindowsMime(QWindowsMime * mime);
void removeWindowsMime(QWindowsMime * mime);
QList<QWindowsMime*> windowsMimes();
private:
void init();
bool initialized;
QList<QWindowsMime*> mimes;
};
Q_GLOBAL_STATIC(QWindowsMimeList, theMimeList);
/*!
\class QWindowsMime
\brief The QWindowsMime class maps open-standard MIME to Window Clipboard formats.
\ingroup draganddrop
Qt's drag-and-drop and clipboard facilities use the MIME standard.
On X11, this maps trivially to the Xdnd protocol, but on Windows
although some applications use MIME types to describe clipboard
formats, others use arbitrary non-standardized naming conventions,
or unnamed built-in formats of Windows.
By instantiating subclasses of QWindowsMime that provide conversions
between Windows Clipboard and MIME formats, you can convert
proprietary clipboard formats to MIME formats.
Qt has predefined support for the following Windows Clipboard formats:
\table
\header \o Windows Format \o Equivalent MIME type
\row \o \c CF_UNICODETEXT \o \c text/plain
\row \o \c CF_TEXT \o \c text/plain
\row \o \c CF_DIB \o \c{image/xyz}, where \c xyz is
a \l{QImageWriter::supportedImageFormats()}{Qt image format}
\row \o \c CF_HDROP \o \c text/uri-list
\row \o \c CF_INETURL \o \c text/uri-list
\row \o \c CF_HTML \o \c text/html
\endtable
An example use of this class would be to map the Windows Metafile
clipboard format (\c CF_METAFILEPICT) to and from the MIME type
\c{image/x-wmf}. 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.
You can check if a MIME type is convertible using canConvertFromMime() and
can perform conversions with convertToMime() and convertFromMime().
*/
/*!
Constructs a new conversion object, adding it to the globally accessed
list of available converters.
*/
QWindowsMime::QWindowsMime()
{
theMimeList()->addWindowsMime(this);
}
/*!
Destroys a conversion object, removing it from the global
list of available converters.
*/
QWindowsMime::~QWindowsMime()
{
theMimeList()->removeWindowsMime(this);
}
/*!
Registers the MIME type \a mime, and returns an ID number
identifying the format on Windows.
*/
int QWindowsMime::registerMimeType(const QString &mime)
{
int f = RegisterClipboardFormat(reinterpret_cast<const wchar_t *> (mime.utf16()));
if (!f)
qErrnoWarning("QWindowsMime::registerMimeType: Failed to register clipboard format");
return f;
}
/*!
\fn bool QWindowsMime::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
Returns true if the converter can convert from the \a mimeData to
the format specified in \a formatetc.
All subclasses must reimplement this pure virtual function.
*/
/*!
\fn bool QWindowsMime::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
Returns true if the converter can convert to the \a mimeType from
the available formats in \a pDataObj.
All subclasses must reimplement this pure virtual function.
*/
/*!
\fn QString QWindowsMime::mimeForFormat(const FORMATETC &formatetc) const
Returns the mime type that will be created form the format specified
in \a formatetc, or an empty string if this converter does not support
\a formatetc.
All subclasses must reimplement this pure virtual function.
*/
/*!
\fn QVector<FORMATETC> QWindowsMime::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
Returns a QVector of FORMATETC structures representing the different windows clipboard
formats that can be provided for the \a mimeType from the \a mimeData.
All subclasses must reimplement this pure virtual function.
*/
/*!
\fn QVariant QWindowsMime::convertToMime(const QString &mimeType, IDataObject *pDataObj,
QVariant::Type preferredType) const
Returns a QVariant containing the converted data for \a mimeType from \a pDataObj.
If possible the QVariant should be of the \a preferredType to avoid needless conversions.
All subclasses must reimplement this pure virtual function.
*/
/*!
\fn bool QWindowsMime::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
Convert the \a mimeData to the format specified in \a formatetc.
The converted data should then be placed in \a pmedium structure.
Return true if the conversion was successful.
All subclasses must reimplement this pure virtual function.
*/
QWindowsMime *QWindowsMime::converterFromMime(const FORMATETC &formatetc, const QMimeData *mimeData)
{
QList<QWindowsMime*> mimes = theMimeList()->windowsMimes();
for (int i=mimes.size()-1; i>=0; --i) {
if (mimes.at(i)->canConvertFromMime(formatetc, mimeData))
return mimes.at(i);
}
return 0;
}
QWindowsMime *QWindowsMime::converterToMime(const QString &mimeType, IDataObject *pDataObj)
{
QList<QWindowsMime*> mimes = theMimeList()->windowsMimes();
for (int i=mimes.size()-1; i>=0; --i) {
if (mimes.at(i)->canConvertToMime(mimeType, pDataObj))
return mimes.at(i);
}
return 0;
}
QVector<FORMATETC> QWindowsMime::allFormatsForMime(const QMimeData *mimeData)
{
QList<QWindowsMime*> mimes = theMimeList()->windowsMimes();
QVector<FORMATETC> formatics;
formatics.reserve(20);
#ifndef QT_NO_DRAGANDDROP
QStringList formats = QInternalMimeData::formatsHelper(mimeData);
for (int f=0; f<formats.size(); ++f) {
for (int i=mimes.size()-1; i>=0; --i)
formatics += mimes.at(i)->formatsForMime(formats.at(f), mimeData);
}
#else
Q_UNUSED(mimeData);
#endif //QT_NO_DRAGANDDROP
return formatics;
}
QStringList QWindowsMime::allMimesForFormats(IDataObject *pDataObj)
{
QList<QWindowsMime*> mimes = theMimeList()->windowsMimes();
QStringList formats;
LPENUMFORMATETC FAR fmtenum;
HRESULT hr = pDataObj->EnumFormatEtc(DATADIR_GET, &fmtenum);
if (hr == NOERROR) {
FORMATETC fmtetc;
while (S_OK == fmtenum->Next(1, &fmtetc, 0)) {
#if defined(QMIME_DEBUG) && !defined(Q_OS_WINCE)
qDebug("QWindowsMime::allMimesForFormats()");
wchar_t buf[256] = {0};
GetClipboardFormatName(fmtetc.cfFormat, buf, 255);
qDebug("CF = %d : %s", fmtetc.cfFormat, QString::fromWCharArray(buf));
#endif
for (int i=mimes.size()-1; i>=0; --i) {
QString format = mimes.at(i)->mimeForFormat(fmtetc);
if (!format.isEmpty() && !formats.contains(format)) {
formats += format;
}
}
// as documented in MSDN to avoid possible memleak
if (fmtetc.ptd)
CoTaskMemFree(fmtetc.ptd);
}
fmtenum->Release();
}
return formats;
}
class QWindowsMimeText : public QWindowsMime
{
public:
bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const;
QVariant convertToMime(const QString &mime, LPDATAOBJECT pDataObj, QVariant::Type preferredType) const;
QString mimeForFormat(const FORMATETC &formatetc) const;
bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const;
bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const;
QVector<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const;
};
bool QWindowsMimeText::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
{
int cf = getCf(formatetc);
return (cf == CF_UNICODETEXT || cf == CF_TEXT) && mimeData->hasText();
}
/*
text/plain is defined as using CRLF, but so many programs don't,
and programmers just look for '\n' in strings.
Windows really needs CRLF, so we ensure it here.
*/
bool QWindowsMimeText::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const
{
if (canConvertFromMime(formatetc, mimeData)) {
QByteArray data;
int cf = getCf(formatetc);
if (cf == CF_TEXT) {
data = mimeData->text().toLocal8Bit();
// Anticipate required space for CRLFs at 1/40
int maxsize=data.size()+data.size()/40+3;
QByteArray r(maxsize, '\0');
char* o = r.data();
const char* d = data.data();
const int s = data.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+3 >= maxsize) {
maxsize += maxsize/4;
r.resize(maxsize);
o = r.data();
}
}
o[j]=0;
return setData(r, pmedium);
} else if (cf == CF_UNICODETEXT) {
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() * sizeof(ushort);
QByteArray r(byteLength + 2, '\0');
memcpy(r.data(), res.unicode(), byteLength);
r[byteLength] = 0;
r[byteLength+1] = 0;
return setData(r, pmedium);
}
}
return false;
}
bool QWindowsMimeText::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
{
return mimeType.startsWith(QLatin1String("text/plain"))
&& (canGetData(CF_UNICODETEXT, pDataObj)
|| canGetData(CF_TEXT, pDataObj));
}
QString QWindowsMimeText::mimeForFormat(const FORMATETC &formatetc) const
{
int cf = getCf(formatetc);
if (cf == CF_UNICODETEXT || cf == CF_TEXT)
return QLatin1String("text/plain");
return QString();
}
QVector<FORMATETC> QWindowsMimeText::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
{
QVector<FORMATETC> formatics;
if (mimeType.startsWith(QLatin1String("text/plain")) && mimeData->hasText()) {
formatics += setCf(CF_UNICODETEXT);
formatics += setCf(CF_TEXT);
}
return formatics;
}
QVariant QWindowsMimeText::convertToMime(const QString &mime, LPDATAOBJECT pDataObj, QVariant::Type preferredType) const
{
QVariant ret;
if (canConvertToMime(mime, pDataObj)) {
QString str;
QByteArray data = getData(CF_UNICODETEXT, pDataObj);
if (!data.isEmpty()) {
str = QString::fromWCharArray((const wchar_t *)data.data());
str.replace(QLatin1String("\r\n"), QLatin1String("\n"));
} else {
data = getData(CF_TEXT, pDataObj);
if (!data.isEmpty()) {
const char* d = data.data();
const int s = qstrlen(d);
QByteArray r(data.size()+1, '\0');
char* o = r.data();
int j=0;
for (int i=0; i<s; i++) {
char c = d[i];
if (c!='\r')
o[j++]=c;
}
o[j]=0;
str = QString::fromLocal8Bit(r);
}
}
if (preferredType == QVariant::String)
ret = str;
else
ret = str.toUtf8();
}
return ret;
}
class QWindowsMimeURI : public QWindowsMime
{
public:
QWindowsMimeURI();
bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const;
QVariant convertToMime(const QString &mime, LPDATAOBJECT pDataObj, QVariant::Type preferredType) const;
QString mimeForFormat(const FORMATETC &formatetc) const;
bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const;
bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const;
QVector<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const;
private:
int CF_INETURL_W; // wide char version
int CF_INETURL;
};
QWindowsMimeURI::QWindowsMimeURI()
{
CF_INETURL_W = QWindowsMime::registerMimeType(QLatin1String("UniformResourceLocatorW"));
CF_INETURL = QWindowsMime::registerMimeType(QLatin1String("UniformResourceLocator"));
}
bool QWindowsMimeURI::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
{
if (getCf(formatetc) == CF_HDROP) {
QList<QUrl> urls = mimeData->urls();
for (int i=0; i<urls.size(); i++) {
if (!urls.at(i).toLocalFile().isEmpty())
return true;
}
}
return (getCf(formatetc) == CF_INETURL_W || getCf(formatetc) == CF_INETURL) && mimeData->hasFormat(QLatin1String("text/uri-list"));
}
bool QWindowsMimeURI::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const
{
if (canConvertFromMime(formatetc, mimeData)) {
if (getCf(formatetc) == CF_HDROP) {
QList<QUrl> urls = mimeData->urls();
QStringList fileNames;
int size = sizeof(DROPFILES)+2;
for (int i=0; i<urls.size(); i++) {
QString fn = QDir::toNativeSeparators(urls.at(i).toLocalFile());
if (!fn.isEmpty()) {
size += sizeof(ushort) * (fn.length() + 1);
fileNames.append(fn);
}
}
QByteArray result(size, '\0');
DROPFILES* d = (DROPFILES*)result.data();
d->pFiles = sizeof(DROPFILES);
GetCursorPos(&d->pt); // try
d->fNC = true;
char* files = ((char*)d) + d->pFiles;
d->fWide = true;
wchar_t* f = (wchar_t*)files;
for (int i=0; i<fileNames.size(); i++) {
int l = fileNames.at(i).length();
memcpy(f, fileNames.at(i).utf16(), l * sizeof(ushort));
f += l;
*f++ = 0;
}
*f = 0;
return setData(result, pmedium);
} else if (getCf(formatetc) == CF_INETURL_W) {
QList<QUrl> urls = mimeData->urls();
QByteArray result;
QString url = urls.at(0).toString();
result = QByteArray((const char *)url.utf16(), url.length() * sizeof(ushort));
result.append('\0');
result.append('\0');
return setData(result, pmedium);
} else if (getCf(formatetc) == CF_INETURL) {
QList<QUrl> urls = mimeData->urls();
QByteArray result = urls.at(0).toString().toLocal8Bit();
return setData(result, pmedium);
}
}
return false;
}
bool QWindowsMimeURI::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
{
return mimeType == QLatin1String("text/uri-list")
&& (canGetData(CF_HDROP, pDataObj) || canGetData(CF_INETURL_W, pDataObj) || canGetData(CF_INETURL, pDataObj));
}
QString QWindowsMimeURI::mimeForFormat(const FORMATETC &formatetc) const
{
QString format;
if (getCf(formatetc) == CF_HDROP || getCf(formatetc) == CF_INETURL_W || getCf(formatetc) == CF_INETURL)
format = QLatin1String("text/uri-list");
return format;
}
QVector<FORMATETC> QWindowsMimeURI::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
{
QVector<FORMATETC> formatics;
if (mimeType == QLatin1String("text/uri-list")) {
if (canConvertFromMime(setCf(CF_HDROP), mimeData))
formatics += setCf(CF_HDROP);
if (canConvertFromMime(setCf(CF_INETURL_W), mimeData))
formatics += setCf(CF_INETURL_W);
if (canConvertFromMime(setCf(CF_INETURL), mimeData))
formatics += setCf(CF_INETURL);
}
return formatics;
}
QVariant QWindowsMimeURI::convertToMime(const QString &mimeType, LPDATAOBJECT pDataObj, QVariant::Type preferredType) const
{
if (mimeType == QLatin1String("text/uri-list")) {
if (canGetData(CF_HDROP, pDataObj)) {
QByteArray texturi;
QList<QVariant> urls;
QByteArray data = getData(CF_HDROP, pDataObj);
if (data.isEmpty())
return QVariant();
LPDROPFILES hdrop = (LPDROPFILES)data.data();
if (hdrop->fWide) {
const wchar_t* filesw = (const wchar_t *)(data.data() + hdrop->pFiles);
int i = 0;
while (filesw[i]) {
QString fileurl = QString::fromWCharArray(filesw + i);
urls += QUrl::fromLocalFile(fileurl);
i += fileurl.length()+1;
}
} else {
const char* files = (const char *)data.data() + hdrop->pFiles;
int i=0;
while (files[i]) {
urls += QUrl::fromLocalFile(QString::fromLocal8Bit(files+i));
i += int(strlen(files+i))+1;
}
}
if (preferredType == QVariant::Url && urls.size() == 1)
return urls.at(0);
else if (!urls.isEmpty())
return urls;
} else if (canGetData(CF_INETURL_W, pDataObj)) {
QByteArray data = getData(CF_INETURL_W, pDataObj);
if (data.isEmpty())
return QVariant();
return QUrl(QString::fromWCharArray((const wchar_t *)data.constData()));
} else if (canGetData(CF_INETURL, pDataObj)) {
QByteArray data = getData(CF_INETURL, pDataObj);
if (data.isEmpty())
return QVariant();
return QUrl(QString::fromLocal8Bit(data.constData()));
}
}
return QVariant();
}
class QWindowsMimeHtml : public QWindowsMime
{
public:
QWindowsMimeHtml();
// for converting from Qt
bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const;
bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const;
QVector<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const;
// for converting to Qt
bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const;
QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QVariant::Type preferredType) const;
QString mimeForFormat(const FORMATETC &formatetc) const;
private:
int CF_HTML;
};
QWindowsMimeHtml::QWindowsMimeHtml()
{
CF_HTML = QWindowsMime::registerMimeType(QLatin1String("HTML Format"));
}
QVector<FORMATETC> QWindowsMimeHtml::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
{
QVector<FORMATETC> formatetcs;
if (mimeType == QLatin1String("text/html") && (!mimeData->html().isEmpty()))
formatetcs += setCf(CF_HTML);
return formatetcs;
}
QString QWindowsMimeHtml::mimeForFormat(const FORMATETC &formatetc) const
{
if (getCf(formatetc) == CF_HTML)
return QLatin1String("text/html");
return QString();
}
bool QWindowsMimeHtml::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
{
return mimeType == QLatin1String("text/html") && canGetData(CF_HTML, pDataObj);
}
bool QWindowsMimeHtml::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
{
return getCf(formatetc) == CF_HTML && (!mimeData->html().isEmpty());
}
/*
The windows HTML clipboard format is as follows (xxxxxxxxxx is a 10 integer number giving the positions
in bytes). Charset used is mostly utf8, but can be different, ie. we have to look for the <meta> charset tag
Version: 1.0
StartHTML:xxxxxxxxxx
EndHTML:xxxxxxxxxx
StartFragment:xxxxxxxxxx
EndFragment:xxxxxxxxxx
...html...
*/
QVariant QWindowsMimeHtml::convertToMime(const QString &mime, IDataObject *pDataObj, QVariant::Type preferredType) const
{
Q_UNUSED(preferredType);
QVariant result;
if (canConvertToMime(mime, pDataObj)) {
QByteArray html = getData(CF_HTML, pDataObj);
#ifdef QMIME_DEBUG
qDebug("QWindowsMimeHtml::convertToMime");
qDebug("raw :");
qDebug(html);
#endif
int start = html.indexOf("StartFragment:");
int end = html.indexOf("EndFragment:");
if (start != -1) {
int startOffset = start + 14;
int i = startOffset;
while (html.at(i) != '\r' && html.at(i) != '\n')
++i;
QByteArray bytecount = html.mid(startOffset, i - startOffset);
start = bytecount.toInt();
}
if (end != -1) {
int endOffset = end + 12;
int i = endOffset ;
while (html.at(i) != '\r' && html.at(i) != '\n')
++i;
QByteArray bytecount = html.mid(endOffset , i - endOffset);
end = bytecount.toInt();
}
if (end > start && start > 0) {
html = "<!--StartFragment-->" + html.mid(start, end - start);
html += "<!--EndFragment-->";
html.replace('\r', "");
result = QString::fromUtf8(html);
}
}
return result;
}
bool QWindowsMimeHtml::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
{
if (canConvertFromMime(formatetc, mimeData)) {
QByteArray data = mimeData->html().toUtf8();
QByteArray result =
"Version:1.0\r\n" // 0-12
"StartHTML:0000000105\r\n" // 13-35
"EndHTML:0000000000\r\n" // 36-55
"StartFragment:0000000000\r\n" // 58-86
"EndFragment:0000000000\r\n\r\n"; // 87-105
if (data.indexOf("<!--StartFragment-->") == -1)
result += "<!--StartFragment-->";
result += data;
if (data.indexOf("<!--EndFragment-->") == -1)
result += "<!--EndFragment-->";
// set the correct number for EndHTML
QByteArray pos = QString::number(result.size()).toLatin1();
memcpy((char *)(result.data() + 53 - pos.length()), pos.constData(), pos.length());
// set correct numbers for StartFragment and EndFragment
pos = QString::number(result.indexOf("<!--StartFragment-->") + 20).toLatin1();
memcpy((char *)(result.data() + 79 - pos.length()), pos.constData(), pos.length());
pos = QString::number(result.indexOf("<!--EndFragment-->")).toLatin1();
memcpy((char *)(result.data() + 103 - pos.length()), pos.constData(), pos.length());
return setData(result, pmedium);
}
return false;
}
#ifndef QT_NO_IMAGEFORMAT_BMP
class QWindowsMimeImage : public QWindowsMime
{
public:
QWindowsMimeImage();
// for converting from Qt
bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const;
bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const;
QVector<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const;
// for converting to Qt
bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const;
QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QVariant::Type preferredType) const;
QString mimeForFormat(const FORMATETC &formatetc) const;
private:
bool hasOriginalDIBV5(IDataObject *pDataObj) const;
UINT CF_PNG;
};
QWindowsMimeImage::QWindowsMimeImage()
{
CF_PNG = RegisterClipboardFormat(L"PNG");
}
QVector<FORMATETC> QWindowsMimeImage::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
{
QVector<FORMATETC> formatetcs;
if (mimeData->hasImage() && mimeType == QLatin1String("application/x-qt-image")) {
//add DIBV5 if image has alpha channel
QImage image = qvariant_cast<QImage>(mimeData->imageData());
if (!image.isNull() && image.hasAlphaChannel())
formatetcs += setCf(CF_DIBV5);
formatetcs += setCf(CF_DIB);
}
return formatetcs;
}
QString QWindowsMimeImage::mimeForFormat(const FORMATETC &formatetc) const
{
int cf = getCf(formatetc);
if (cf == CF_DIB || cf == CF_DIBV5 || cf == int(CF_PNG))
return QLatin1String("application/x-qt-image");
return QString();
}
bool QWindowsMimeImage::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
{
if ((mimeType == QLatin1String("application/x-qt-image")) &&
(canGetData(CF_DIB, pDataObj) || canGetData(CF_PNG, pDataObj)))
return true;
return false;
}
bool QWindowsMimeImage::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
{
int cf = getCf(formatetc);
if (mimeData->hasImage()) {
if (cf == CF_DIB)
return true;
else if (cf == CF_DIBV5) {
//support DIBV5 conversion only if the image has alpha channel
QImage image = qvariant_cast<QImage>(mimeData->imageData());
if (!image.isNull() && image.hasAlphaChannel())
return true;
}
}
return false;
}
bool QWindowsMimeImage::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
{
int cf = getCf(formatetc);
if ((cf == CF_DIB || cf == CF_DIBV5) && mimeData->hasImage()) {
QImage img = qvariant_cast<QImage>(mimeData->imageData());
if (img.isNull())
return false;
QByteArray ba;
QDataStream s(&ba, QIODevice::WriteOnly);
s.setByteOrder(QDataStream::LittleEndian);// Intel byte order ####
if (cf == CF_DIB) {
if (qt_write_dib(s, img))
return setData(ba, pmedium);
} else {
if (qt_write_dibv5(s, img))
return setData(ba, pmedium);
}
}
return false;
}
bool QWindowsMimeImage::hasOriginalDIBV5(IDataObject *pDataObj) const
{
bool isSynthesized = true;
IEnumFORMATETC *pEnum =NULL;
HRESULT res = pDataObj->EnumFormatEtc(1, &pEnum);
if (res == S_OK && pEnum) {
FORMATETC fc;
while ((res = pEnum->Next(1, &fc, 0)) == S_OK) {
if (fc.ptd)
CoTaskMemFree(fc.ptd);
if (fc.cfFormat == CF_DIB)
break;
else if (fc.cfFormat == CF_DIBV5) {
isSynthesized = false;
break;
}
}
pEnum->Release();
}
return !isSynthesized;
}
QVariant QWindowsMimeImage::convertToMime(const QString &mimeType, IDataObject *pDataObj, QVariant::Type preferredType) const
{
Q_UNUSED(preferredType);
QVariant result;
if (mimeType != QLatin1String("application/x-qt-image"))
return result;
//Try to convert from a format which has more data
//DIBV5, use only if its is not synthesized
if (canGetData(CF_DIBV5, pDataObj) && hasOriginalDIBV5(pDataObj)) {
QImage img;
QByteArray data = getData(CF_DIBV5, pDataObj);
QDataStream s(&data, QIODevice::ReadOnly);
s.setByteOrder(QDataStream::LittleEndian);
if (qt_read_dibv5(s, img)) { // #### supports only 32bit DIBV5
return img;
}
}
//PNG, MS Office place this (undocumented)
if (canGetData(CF_PNG, pDataObj)) {
QImage img;
QByteArray data = getData(CF_PNG, pDataObj);
if (img.loadFromData(data, "PNG")) {
return img;
}
}
//Fallback to DIB
if (canGetData(CF_DIB, pDataObj)) {
QImage img;
QByteArray data = getData(CF_DIB, pDataObj);
QDataStream s(&data, QIODevice::ReadOnly);
s.setByteOrder(QDataStream::LittleEndian);// Intel byte order ####
if (qt_read_dib(s, img)) { // ##### encaps "-14"
return img;
}
}
// Failed
return result;
}
#endif
class QBuiltInMimes : public QWindowsMime
{
public:
QBuiltInMimes();
// for converting from Qt
bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const;
bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const;
QVector<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const;
// for converting to Qt
bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const;
QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QVariant::Type preferredType) const;
QString mimeForFormat(const FORMATETC &formatetc) const;
private:
QMap<int, QString> outFormats;
QMap<int, QString> inFormats;
};
QBuiltInMimes::QBuiltInMimes()
: QWindowsMime()
{
outFormats.insert(QWindowsMime::registerMimeType(QLatin1String("application/x-color")), QLatin1String("application/x-color"));
inFormats.insert(QWindowsMime::registerMimeType(QLatin1String("application/x-color")), QLatin1String("application/x-color"));
}
bool QBuiltInMimes::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
{
// really check
return formatetc.tymed & TYMED_HGLOBAL
&& outFormats.contains(formatetc.cfFormat)
&& mimeData->formats().contains(outFormats.value(formatetc.cfFormat));
}
bool QBuiltInMimes::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
{
if (canConvertFromMime(formatetc, mimeData)) {
QByteArray data;
if (outFormats.value(getCf(formatetc)) == QLatin1String("text/html")) {
// text/html is in wide chars on windows (compatible with mozillia)
QString html = mimeData->html();
// same code as in the text converter up above
const QChar *u = html.unicode();
QString res;
const int s = html.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() * sizeof(ushort);
QByteArray r(byteLength + 2, '\0');
memcpy(r.data(), res.unicode(), byteLength);
r[byteLength] = 0;
r[byteLength+1] = 0;
data = r;
} else {
#ifndef QT_NO_DRAGANDDROP
data = QInternalMimeData::renderDataHelper(outFormats.value(getCf(formatetc)), mimeData);
#endif //QT_NO_DRAGANDDROP
}
return setData(data, pmedium);
}
return false;
}
QVector<FORMATETC> QBuiltInMimes::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
{
QVector<FORMATETC> formatetcs;
if (!outFormats.keys(mimeType).isEmpty() && mimeData->formats().contains(mimeType))
formatetcs += setCf(outFormats.key(mimeType));
return formatetcs;
}
bool QBuiltInMimes::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
{
return (!inFormats.keys(mimeType).isEmpty())
&& canGetData(inFormats.key(mimeType), pDataObj);
}
QVariant QBuiltInMimes::convertToMime(const QString &mimeType, IDataObject *pDataObj, QVariant::Type preferredType) const
{
QVariant val;
if (canConvertToMime(mimeType, pDataObj)) {
QByteArray data = getData(inFormats.key(mimeType), pDataObj);
if (!data.isEmpty()) {
#ifdef QMIME_DEBUG
qDebug("QBuiltInMimes::convertToMime()");
#endif
if (mimeType == QLatin1String("text/html") && preferredType == QVariant::String) {
// text/html is in wide chars on windows (compatible with Mozilla)
val = QString::fromWCharArray((const wchar_t *)data.data());
} else {
val = data; // it should be enough to return the data and let QMimeData do the rest.
}
}
}
return val;
}
QString QBuiltInMimes::mimeForFormat(const FORMATETC &formatetc) const
{
return inFormats.value(getCf(formatetc));
}
class QLastResortMimes : public QWindowsMime
{
public:
QLastResortMimes();
// for converting from Qt
bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const;
bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const;
QVector<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const;
// for converting to Qt
bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const;
QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QVariant::Type preferredType) const;
QString mimeForFormat(const FORMATETC &formatetc) const;
private:
QMap<int, QString> formats;
static QStringList ianaTypes;
static QStringList excludeList;
};
QStringList QLastResortMimes::ianaTypes;
QStringList QLastResortMimes::excludeList;
QLastResortMimes::QLastResortMimes()
{
//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/"));
}
//Types handled by other classes
if (!excludeList.size()) {
excludeList.append(QLatin1String("HTML Format"));
excludeList.append(QLatin1String("UniformResourceLocator"));
excludeList.append(QLatin1String("text/html"));
excludeList.append(QLatin1String("text/plain"));
excludeList.append(QLatin1String("text/uri-list"));
excludeList.append(QLatin1String("application/x-qt-image"));
excludeList.append(QLatin1String("application/x-color"));
}
}
bool QLastResortMimes::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
{
// really check
#ifndef QT_NO_DRAGANDDROP
return formatetc.tymed & TYMED_HGLOBAL
&& (formats.contains(formatetc.cfFormat)
&& QInternalMimeData::hasFormatHelper(formats.value(formatetc.cfFormat), mimeData));
#else
Q_UNUSED(mimeData);
Q_UNUSED(formatetc);
return formatetc.tymed & TYMED_HGLOBAL
&& formats.contains(formatetc.cfFormat);
#endif //QT_NO_DRAGANDDROP
}
bool QLastResortMimes::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
{
#ifndef QT_NO_DRAGANDDROP
return canConvertFromMime(formatetc, mimeData)
&& setData(QInternalMimeData::renderDataHelper(formats.value(getCf(formatetc)), mimeData), pmedium);
#else
Q_UNUSED(mimeData);
Q_UNUSED(formatetc);
Q_UNUSED(pmedium);
return false;
#endif //QT_NO_DRAGANDDROP
}
QVector<FORMATETC> QLastResortMimes::formatsForMime(const QString &mimeType, const QMimeData * /*mimeData*/) const
{
QVector<FORMATETC> formatetcs;
if (!formats.keys(mimeType).isEmpty()) {
formatetcs += setCf(formats.key(mimeType));
} else if (!excludeList.contains(mimeType, Qt::CaseInsensitive)){
// register any other available formats
int cf = QWindowsMime::registerMimeType(mimeType);
QLastResortMimes *that = const_cast<QLastResortMimes *>(this);
that->formats.insert(cf, mimeType);
formatetcs += setCf(cf);
}
return formatetcs;
}
static const char x_qt_windows_mime[] = "application/x-qt-windows-mime;value=\"";
static bool isCustomMimeType(const QString &mimeType)
{
return mimeType.startsWith(QLatin1String(x_qt_windows_mime), Qt::CaseInsensitive);
}
static QString customMimeType(const QString &mimeType)
{
int len = sizeof(x_qt_windows_mime) - 1;
int n = mimeType.lastIndexOf(QLatin1Char('\"'))-len;
return mimeType.mid(len, n);
}
bool QLastResortMimes::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
{
if (isCustomMimeType(mimeType)) {
QString clipFormat = customMimeType(mimeType);
int cf = RegisterClipboardFormat(reinterpret_cast<const wchar_t *> (clipFormat.utf16()));
return canGetData(cf, pDataObj);
} else if (formats.keys(mimeType).isEmpty()) {
// if it is not in there then register it an see if we can get it
int cf = QWindowsMime::registerMimeType(mimeType);
return canGetData(cf, pDataObj);
} else {
return canGetData(formats.key(mimeType), pDataObj);
}
return false;
}
QVariant QLastResortMimes::convertToMime(const QString &mimeType, IDataObject *pDataObj, QVariant::Type preferredType) const
{
Q_UNUSED(preferredType);
QVariant val;
if (canConvertToMime(mimeType, pDataObj)) {
QByteArray data;
if (isCustomMimeType(mimeType)) {
QString clipFormat = customMimeType(mimeType);
int cf = RegisterClipboardFormat(reinterpret_cast<const wchar_t *> (clipFormat.utf16()));
data = getData(cf, pDataObj);
} else if (formats.keys(mimeType).isEmpty()) {
int cf = QWindowsMime::registerMimeType(mimeType);
data = getData(cf, pDataObj);
} else {
data = getData(formats.key(mimeType), pDataObj);
}
if (!data.isEmpty())
val = data; // it should be enough to return the data and let QMimeData do the rest.
}
return val;
}
QString QLastResortMimes::mimeForFormat(const FORMATETC &formatetc) const
{
QString format = formats.value(getCf(formatetc));
if (!format.isEmpty())
return format;
wchar_t buffer[256];
int len = GetClipboardFormatName(getCf(formatetc), buffer, 256);
if (len) {
QString clipFormat = QString::fromWCharArray(buffer, len);
#ifndef QT_NO_DRAGANDDROP
if (QInternalMimeData::canReadData(clipFormat))
format = clipFormat;
else if((formatetc.cfFormat >= 0xC000)){
//create the mime as custom. not registered.
if (!excludeList.contains(clipFormat, Qt::CaseInsensitive)) {
//check if this is a mime type
bool ianaType = false;
int sz = ianaTypes.size();
for (int i = 0; i < sz; i++) {
if (clipFormat.startsWith(ianaTypes[i], Qt::CaseInsensitive)) {
ianaType = true;
break;
}
}
if (!ianaType)
format = QLatin1String(x_qt_windows_mime) + clipFormat + QLatin1Char('\"');
else
format = clipFormat;
}
}
#endif //QT_NO_DRAGANDDROP
}
return format;
}
QWindowsMimeList::QWindowsMimeList()
: initialized(false)
{
}
QWindowsMimeList::~QWindowsMimeList()
{
while (mimes.size())
delete mimes.first();
}
void QWindowsMimeList::init()
{
if (!initialized) {
initialized = true;
#ifndef QT_NO_IMAGEFORMAT_BMP
new QWindowsMimeImage;
#endif
new QLastResortMimes;
new QWindowsMimeText;
new QWindowsMimeURI;
new QWindowsMimeHtml;
new QBuiltInMimes;
}
}
void QWindowsMimeList::addWindowsMime(QWindowsMime * mime)
{
init();
mimes.append(mime);
}
void QWindowsMimeList::removeWindowsMime(QWindowsMime * mime)
{
init();
mimes.removeAll(mime);
}
QList<QWindowsMime*> QWindowsMimeList::windowsMimes()
{
init();
return mimes;
}
#ifndef QT_NO_IMAGEFORMAT_BMP
static bool qt_write_dibv5(QDataStream &s, QImage image)
{
QIODevice* d = s.device();
if (!d->isWritable())
return false;
//depth will be always 32
int bpl_bmp = image.width()*4;
BMP_BITMAPV5HEADER bi ={0};
bi.bV5Size = sizeof(BMP_BITMAPV5HEADER);
bi.bV5Width = image.width();
bi.bV5Height = image.height();
bi.bV5Planes = 1;
bi.bV5BitCount = 32;
bi.bV5Compression = BI_BITFIELDS;
bi.bV5SizeImage = bpl_bmp*image.height();
bi.bV5XPelsPerMeter = 0;
bi.bV5YPelsPerMeter = 0;
bi.bV5ClrUsed = 0;
bi.bV5ClrImportant = 0;
bi.bV5BlueMask = 0x000000ff;
bi.bV5GreenMask = 0x0000ff00;
bi.bV5RedMask = 0x00ff0000;
bi.bV5AlphaMask = 0xff000000;
bi.bV5CSType = BMP_LCS_sRGB; //LCS_sRGB
bi.bV5Intent = BMP_LCS_GM_IMAGES; //LCS_GM_IMAGES
d->write(reinterpret_cast<const char*>(&bi), bi.bV5Size);
if (s.status() != QDataStream::Ok)
return false;
DWORD colorSpace[3] = {0x00ff0000,0x0000ff00,0x000000ff};
d->write(reinterpret_cast<const char*>(colorSpace), sizeof(colorSpace));
if (s.status() != QDataStream::Ok)
return false;
if (image.format() != QImage::Format_ARGB32)
image = image.convertToFormat(QImage::Format_ARGB32);
uchar *buf = new uchar[bpl_bmp];
uchar *b;
memset(buf, 0, bpl_bmp);
for (int y=image.height()-1; y>=0; y--) {
// write the image bits
QRgb *p = (QRgb *)image.scanLine(y);
QRgb *end = p + image.width();
b = buf;
while (p < end) {
int alpha = qAlpha(*p);
if (alpha) {
*b++ = qBlue(*p);
*b++ = qGreen(*p);
*b++ = qRed(*p);
} else {
//white for fully transparent pixels.
*b++ = 0xff;
*b++ = 0xff;
*b++ = 0xff;
}
*b++ = alpha;
p++;
}
d->write((char*)buf, bpl_bmp);
if (s.status() != QDataStream::Ok) {
delete[] buf;
return false;
}
}
delete[] buf;
return true;
}
static int calc_shift(int mask)
{
int result = 0;
while (!(mask & 1)) {
result++;
mask >>= 1;
}
return result;
}
//Supports only 32 bit DIBV5
static bool qt_read_dibv5(QDataStream &s, QImage &image)
{
BMP_BITMAPV5HEADER bi;
QIODevice* d = s.device();
if (d->atEnd())
return false;
d->read((char *)&bi, sizeof(bi)); // read BITMAPV5HEADER header
if (s.status() != QDataStream::Ok)
return false;
int nbits = bi.bV5BitCount;
int comp = bi.bV5Compression;
if (nbits != 32 || bi.bV5Planes != 1 || comp != BMP_BITFIELDS)
return false; //Unsupported DIBV5 format
int w = bi.bV5Width, h = bi.bV5Height;
int red_mask = bi.bV5RedMask;
int green_mask = bi.bV5GreenMask;
int blue_mask = bi.bV5BlueMask;
int alpha_mask = bi.bV5AlphaMask;
int red_shift = 0;
int green_shift = 0;
int blue_shift = 0;
int alpha_shift = 0;
QImage::Format format = QImage::Format_ARGB32;
if (bi.bV5Height < 0)
h = -h; // support images with negative height
if (image.size() != QSize(w, h) || image.format() != format) {
image = QImage(w, h, format);
if (image.isNull()) // could not create image
return false;
}
image.setDotsPerMeterX(bi.bV5XPelsPerMeter);
image.setDotsPerMeterY(bi.bV5YPelsPerMeter);
// read color table
DWORD colorSpace[3];
if (d->read((char *)colorSpace, sizeof(colorSpace)) != sizeof(colorSpace))
return false;
red_shift = calc_shift(red_mask);
green_shift = calc_shift(green_mask);
blue_shift = calc_shift(blue_mask);
if (alpha_mask) {
alpha_shift = calc_shift(alpha_mask);
}
int bpl = image.bytesPerLine();
uchar *data = image.bits();
register QRgb *p;
QRgb *end;
uchar *buf24 = new uchar[bpl];
int bpl24 = ((w*nbits+31)/32)*4;
uchar *b;
unsigned int c;
while (--h >= 0) {
p = (QRgb *)(data + h*bpl);
end = p + w;
if (d->read((char *)buf24,bpl24) != bpl24)
break;
b = buf24;
while (p < end) {
c = *b | (*(b+1))<<8 | (*(b+2))<<16 | (*(b+3))<<24;
*p++ = qRgba(((c & red_mask) >> red_shift) ,
((c & green_mask) >> green_shift),
((c & blue_mask) >> blue_shift),
((c & alpha_mask) >> alpha_shift));
b += 4;
}
}
delete[] buf24;
if (bi.bV5Height < 0) {
// Flip the image
uchar *buf = new uchar[bpl];
h = -bi.bV5Height;
for (int y = 0; y < h/2; ++y) {
memcpy(buf, data + y*bpl, bpl);
memcpy(data + y*bpl, data + (h-y-1)*bpl, bpl);
memcpy(data + (h-y-1)*bpl, buf, bpl);
}
delete [] buf;
}
return true;
}
#endif
QT_END_NAMESPACE