3 * \file qt4/GuiClipboard.cpp
4 * This file is part of LyX, the document processor.
5 * Licence details can be found in the file COPYING.
8 * \author Abdelrazak Younes
10 * Full author contact details are available in file CREDITS.
15 #include "FileDialog.h"
17 #include "GuiClipboard.h"
18 #include "qt_helpers.h"
20 #include "frontends/alert.h"
23 #include "BufferView.h"
26 #include "support/convert.h"
27 #include "support/debug.h"
28 #include "support/filetools.h"
29 #include "support/FileFilterList.h"
30 #include "support/gettext.h"
31 #include "support/lstrings.h"
33 #include <QApplication>
36 #include <QDataStream>
39 #include <QMacPasteboardMime>
42 #include <QStringList>
45 #include <QWindowsMime>
49 #include "boost/assert.hpp"
54 #include "support/linkback/LinkBackProxy.h"
58 using namespace lyx::support;
60 static char const * const lyx_mime_type = "application/x-lyx";
61 static char const * const pdf_mime_type = "application/pdf";
62 static char const * const emf_mime_type = "image/x-emf";
63 static char const * const wmf_mime_type = "image/x-wmf";
71 static FORMATETC setCf(int cf)
74 formatetc.cfFormat = cf;
76 formatetc.dwAspect = DVASPECT_CONTENT;
77 formatetc.lindex = -1;
78 if (cf == CF_ENHMETAFILE)
79 formatetc.tymed = TYMED_ENHMF;
80 if (cf == CF_METAFILEPICT)
81 formatetc.tymed = TYMED_MFPICT;
86 static bool canGetData(int cf, IDataObject * pDataObj)
88 FORMATETC formatetc = setCf(cf);
89 return pDataObj->QueryGetData(&formatetc) == S_OK;
93 class QWindowsMimeMetafile : public QWindowsMime {
95 bool canConvertFromMime(FORMATETC const & formatetc, QMimeData const * mimeData) const;
96 bool canConvertToMime(QString const & mimeType, IDataObject * pDataObj) const;
97 bool convertFromMime(FORMATETC const & formatetc, const QMimeData * mimeData, STGMEDIUM * pmedium) const;
98 QVariant convertToMime(QString const & mimeType, IDataObject * pDataObj, QVariant::Type preferredType) const;
99 QVector<FORMATETC> formatsForMime(QString const & mimeType, QMimeData const * mimeData) const;
100 QString mimeForFormat(FORMATETC const &) const;
104 QString QWindowsMimeMetafile::mimeForFormat(const FORMATETC & formatetc) const
107 if (formatetc.cfFormat == CF_ENHMETAFILE)
109 else if (formatetc.cfFormat == CF_METAFILEPICT)
115 bool QWindowsMimeMetafile::canConvertFromMime(
116 const FORMATETC & formatetc, const QMimeData * mimeData) const
122 bool QWindowsMimeMetafile::canConvertToMime(
123 const QString & mimeType, IDataObject * pDataObj) const
125 return (mimeType == "image/x-emf" && canGetData(CF_ENHMETAFILE, pDataObj))
126 || (mimeType == "image/x-wmf" && canGetData(CF_METAFILEPICT, pDataObj));
130 bool QWindowsMimeMetafile::convertFromMime(
131 const FORMATETC & formatetc, const QMimeData * mimeData,
132 STGMEDIUM * pmedium) const
138 QVariant QWindowsMimeMetafile::convertToMime(
139 const QString & mimeType, IDataObject * pDataObj,
140 QVariant::Type preferredType) const
144 if (canConvertToMime(mimeType, pDataObj)) {
146 if (mimeType == "image/x-emf")
147 formatetc = setCf(CF_ENHMETAFILE);
148 if (mimeType == "image/x-wmf")
149 formatetc = setCf(CF_METAFILEPICT);
154 if (pDataObj->GetData(&formatetc, &s) == S_OK) {
155 if (s.tymed == TYMED_ENHMF) {
156 dataSize = GetEnhMetaFileBits(s.hEnhMetaFile, NULL, NULL);
157 data.resize(dataSize);
158 dataSize = GetEnhMetaFileBits(s.hEnhMetaFile, dataSize, (LPBYTE)data.data());
159 } else if (s.tymed == TYMED_MFPICT) {
160 dataSize = GetMetaFileBitsEx((HMETAFILE)s.hMetaFilePict, NULL, NULL);
161 data.resize(dataSize);
162 dataSize = GetMetaFileBitsEx((HMETAFILE)s.hMetaFilePict, dataSize, (LPBYTE)data.data());
165 ReleaseStgMedium(&s);
173 QVector<FORMATETC> QWindowsMimeMetafile::formatsForMime(
174 const QString & mimeType, const QMimeData * mimeData) const
176 QVector<FORMATETC> formats;
177 if (mimeType == "image/x-emf")
178 formats += setCf(CF_ENHMETAFILE);
179 if (mimeType == "image/x-wmf")
180 formats += setCf(CF_METAFILEPICT);
184 static QWindowsMimeMetafile * metafileWindowsMime;
190 class QMacPasteboardMimeGraphics : public QMacPasteboardMime {
192 QMacPasteboardMimeGraphics()
193 : QMacPasteboardMime(MIME_QT_CONVERTOR|MIME_ALL)
195 ~QMacPasteboardMimeGraphics() {}
196 QString convertorName();
197 QString flavorFor(const QString & mime);
198 QString mimeFor(QString flav);
199 bool canConvert(const QString & mime, QString flav);
200 QVariant convertToMime(const QString & mime, QList<QByteArray> data, QString flav);
201 QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav);
205 QString QMacPasteboardMimeGraphics::convertorName()
211 QString QMacPasteboardMimeGraphics::flavorFor(const QString & mime)
213 LYXERR(Debug::ACTION, "flavorFor " << fromqstr(mime));
214 if (mime == QLatin1String(pdf_mime_type))
215 return QLatin1String("com.adobe.pdf");
220 QString QMacPasteboardMimeGraphics::mimeFor(QString flav)
222 LYXERR(Debug::ACTION, "mimeFor " << fromqstr(flav));
223 if (flav == QLatin1String("com.adobe.pdf"))
224 return QLatin1String(pdf_mime_type);
229 bool QMacPasteboardMimeGraphics::canConvert(const QString & mime, QString flav)
231 return mimeFor(flav) == mime;
235 QVariant QMacPasteboardMimeGraphics::convertToMime(const QString & mime, QList<QByteArray> data, QString)
238 qWarning("QMacPasteboardMimeGraphics: Cannot handle multiple member data");
243 QList<QByteArray> QMacPasteboardMimeGraphics::convertFromMime(const QString &mime, QVariant data, QString)
245 QList<QByteArray> ret;
246 ret.append(data.toByteArray());
250 static QMacPasteboardMimeGraphics * graphicsPasteboardMime;
255 GuiClipboard::GuiClipboard()
257 connect(qApp->clipboard(), SIGNAL(dataChanged()),
258 this, SLOT(on_dataChanged()));
259 // initialize clipboard status.
263 if (!graphicsPasteboardMime)
264 graphicsPasteboardMime = new QMacPasteboardMimeGraphics();
268 if (!metafileWindowsMime)
269 metafileWindowsMime = new QWindowsMimeMetafile();
274 GuiClipboard::~GuiClipboard()
277 closeAllLinkBackLinks();
282 string const GuiClipboard::getAsLyX() const
284 LYXERR(Debug::ACTION, "GuiClipboard::getAsLyX(): `");
285 // We don't convert encodings here since the encoding of the
286 // clipboard contents is specified in the data itself
287 QMimeData const * source =
288 qApp->clipboard()->mimeData(QClipboard::Clipboard);
290 LYXERR(Debug::ACTION, "' (no QMimeData)");
294 if (source->hasFormat(lyx_mime_type)) {
295 // data from ourself or some other LyX instance
296 QByteArray const ar = source->data(lyx_mime_type);
297 string const s(ar.data(), ar.count());
298 LYXERR(Debug::ACTION, s << "'");
301 LYXERR(Debug::ACTION, "'");
306 FileName GuiClipboard::getPastedGraphicsFileName(Cursor const & cur,
307 Clipboard::GraphicsType & type) const
309 // create file dialog filter according to the existing types in the clipboard
310 vector<Clipboard::GraphicsType> types;
311 if (hasGraphicsContents(Clipboard::EmfGraphicsType))
312 types.push_back(Clipboard::EmfGraphicsType);
313 if (hasGraphicsContents(Clipboard::WmfGraphicsType))
314 types.push_back(Clipboard::WmfGraphicsType);
315 if (hasGraphicsContents(Clipboard::LinkBackGraphicsType))
316 types.push_back(Clipboard::LinkBackGraphicsType);
317 if (hasGraphicsContents(Clipboard::PdfGraphicsType))
318 types.push_back(Clipboard::PdfGraphicsType);
319 if (hasGraphicsContents(Clipboard::PngGraphicsType))
320 types.push_back(Clipboard::PngGraphicsType);
321 if (hasGraphicsContents(Clipboard::JpegGraphicsType))
322 types.push_back(Clipboard::JpegGraphicsType);
324 BOOST_ASSERT(!types.empty());
326 // select prefered type if AnyGraphicsType was passed
327 if (type == Clipboard::AnyGraphicsType)
328 type = types.front();
331 map<Clipboard::GraphicsType, string> extensions;
332 map<Clipboard::GraphicsType, docstring> typeNames;
334 extensions[Clipboard::EmfGraphicsType] = "emf";
335 extensions[Clipboard::WmfGraphicsType] = "wmf";
336 extensions[Clipboard::LinkBackGraphicsType] = "linkback";
337 extensions[Clipboard::PdfGraphicsType] = "pdf";
338 extensions[Clipboard::PngGraphicsType] = "png";
339 extensions[Clipboard::JpegGraphicsType] = "jpeg";
341 typeNames[Clipboard::EmfGraphicsType] = _("Enhanced Metafile");
342 typeNames[Clipboard::WmfGraphicsType] = _("Windows Metafile");
343 typeNames[Clipboard::LinkBackGraphicsType] = _("LinkBack PDF");
344 typeNames[Clipboard::PdfGraphicsType] = _("PDF");
345 typeNames[Clipboard::PngGraphicsType] = _("PNG");
346 typeNames[Clipboard::JpegGraphicsType] = _("JPEG");
348 // find unused filename with primary extension
349 string document_path = cur.buffer().fileName().onlyPath().absFilename();
350 unsigned newfile_number = 0;
354 filename = FileName(addName(document_path,
356 + convert<string>(newfile_number) + "."
357 + extensions[type]));
358 } while (filename.isReadableFile());
361 // create file type filter, putting the prefered on to the front
362 docstring filterSpec;
363 for (size_t i = 0; i != types.size(); ++i) {
364 docstring s = bformat(_("%1$s Files"), typeNames[types[i]])
365 + " (*." + from_ascii(extensions[types[i]]) + ")";
366 if (types[i] == type)
367 filterSpec = s + filterSpec;
369 filterSpec += ";;" + s;
371 FileFilterList const filter(filterSpec);
373 // show save dialog for the graphic
374 FileDialog dlg(qt_("Choose a filename to save the pasted graphic as"));
375 FileDialog::Result result =
376 dlg.save(toqstr(filename.onlyPath().absFilename()), filter,
377 toqstr(filename.onlyFileName()));
379 if (result.first == FileDialog::Later)
382 string newFilename = fromqstr(result.second);
383 if (newFilename.empty()) {
384 cur.bv().message(_("Canceled."));
387 filename.set(newFilename);
389 // check the extension (the user could have changed it)
390 if (!suffixIs(ascii_lowercase(filename.absFilename()),
391 "." + extensions[type])) {
392 // the user changed the extension. Check if the type is available
394 for (i = 1; i != types.size(); ++i) {
395 if (suffixIs(ascii_lowercase(filename.absFilename()),
396 "." + extensions[types[i]])) {
402 // invalid extension found, or none at all. In the latter
403 // case set the default extensions.
404 if (i == types.size()
405 && filename.onlyFileName().find('.') == string::npos) {
406 filename.changeExtension("." + extensions[type]);
410 // check whether the file exists and warn the user
411 if (!filename.exists())
413 int ret = frontend::Alert::prompt(
414 _("Overwrite external file?"),
415 bformat(_("File %1$s already exists, do you want to overwrite it?"),
416 from_utf8(filename.absFilename())), 1, 1, _("&Overwrite"), _("&Cancel"));
418 // overwrite, hence break the dialog loop
421 // not overwrite, hence show the dialog again (i.e. loop)
428 FileName GuiClipboard::getAsGraphics(Cursor const & cur, GraphicsType type) const
430 // get the filename from the user
431 FileName filename = getPastedGraphicsFileName(cur, type);
432 if (filename.empty())
435 // handle image cases first
436 if (type == PngGraphicsType || type == JpegGraphicsType) {
437 // get image from QImage from clipboard
438 QImage image = qApp->clipboard()->image();
439 if (image.isNull()) {
440 LYXERR(Debug::ACTION, "No image in clipboard");
444 // convert into graphics format
447 buffer.open(QIODevice::WriteOnly);
448 if (type == PngGraphicsType)
449 image.save(toqstr(filename.absFilename()), "PNG");
450 else if (type == JpegGraphicsType)
451 image.save(toqstr(filename.absFilename()), "JPEG");
459 QMimeData const * source =
460 qApp->clipboard()->mimeData(QClipboard::Clipboard);
462 LYXERR(Debug::ACTION, "0 bytes (no QMimeData)");
469 case PdfGraphicsType: mime = pdf_mime_type; break;
470 case LinkBackGraphicsType: mime = pdf_mime_type; break;
471 case EmfGraphicsType: mime = emf_mime_type; break;
472 case WmfGraphicsType: mime = wmf_mime_type; break;
473 default: BOOST_ASSERT(false);
477 if (!source->hasFormat(mime))
479 // data from ourself or some other LyX instance
480 QByteArray const ar = source->data(mime);
481 LYXERR(Debug::ACTION, "Getting from clipboard: mime = " << mime.data()
482 << "length = " << ar.count());
484 QFile f(toqstr(filename.absFilename()));
485 if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
486 LYXERR(Debug::ACTION, "Error opening file "
487 << filename.absFilename() << " for writing");
491 // write the (LinkBack) PDF data
493 if (type == LinkBackGraphicsType) {
495 void const * linkBackData;
496 unsigned linkBackLen;
497 getLinkBackData(&linkBackData, &linkBackLen);
498 f.write((char *)linkBackData, linkBackLen);
499 quint32 pdfLen = ar.size();
501 ds << pdfLen; // big endian by default
503 // only non-Mac this should never happen
513 docstring const GuiClipboard::getAsText() const
515 // text data from other applications
516 QString const str = qApp->clipboard()->text(QClipboard::Clipboard)
517 .normalized(QString::NormalizationForm_C);
518 LYXERR(Debug::ACTION, "GuiClipboard::getAsText(): `" << fromqstr(str) << "'");
522 return internalLineEnding(qstring_to_ucs4(str));
526 void GuiClipboard::put(string const & lyx, docstring const & text)
528 LYXERR(Debug::ACTION, "GuiClipboard::put(`" << lyx << "' `"
529 << to_utf8(text) << "')");
530 // We don't convert the encoding of lyx since the encoding of the
531 // clipboard contents is specified in the data itself
532 QMimeData * data = new QMimeData;
534 QByteArray const qlyx(lyx.c_str(), lyx.size());
535 data->setData(lyx_mime_type, qlyx);
537 // Don't test for text.empty() since we want to be able to clear the
539 QString const qtext = toqstr(text);
540 data->setText(qtext);
541 qApp->clipboard()->setMimeData(data, QClipboard::Clipboard);
545 bool GuiClipboard::hasLyXContents() const
547 QMimeData const * const source =
548 qApp->clipboard()->mimeData(QClipboard::Clipboard);
549 return source && source->hasFormat(lyx_mime_type);
553 bool GuiClipboard::hasGraphicsContents(Clipboard::GraphicsType type) const
555 if (type == AnyGraphicsType) {
556 return hasGraphicsContents(PdfGraphicsType)
557 || hasGraphicsContents(PngGraphicsType)
558 || hasGraphicsContents(JpegGraphicsType)
559 || hasGraphicsContents(EmfGraphicsType)
560 || hasGraphicsContents(WmfGraphicsType)
561 || hasGraphicsContents(LinkBackGraphicsType);
564 QMimeData const * const source =
565 qApp->clipboard()->mimeData(QClipboard::Clipboard);
567 // handle image cases first
568 if (type == PngGraphicsType || type == JpegGraphicsType)
569 return source->hasImage();
571 // handle LinkBack for Mac
573 if (type == LinkBackGraphicsType)
574 return isLinkBackDataInPasteboard();
576 if (type == LinkBackGraphicsType)
581 QStringList const & formats = source->formats();
582 LYXERR(Debug::ACTION, "We found " << formats.size() << " formats");
583 for (int i = 0; i < formats.size(); ++i) {
584 LYXERR(Debug::ACTION, "Found format " << fromqstr(formats[i]));
587 // compute mime for type
590 case EmfGraphicsType: mime = emf_mime_type; break;
591 case WmfGraphicsType: mime = wmf_mime_type; break;
592 case PdfGraphicsType: mime = pdf_mime_type; break;
593 default: BOOST_ASSERT(false);
596 return source && source->hasFormat(mime);
600 bool GuiClipboard::isInternal() const
602 // ownsClipboard() is also true for stuff coming from dialogs, e.g.
603 // the preamble dialog
604 // FIXME: This does only work on X11, since ownsClipboard() is
605 // hardwired to return false on Windows and OS X.
606 return qApp->clipboard()->ownsClipboard() && hasLyXContents();
610 bool GuiClipboard::hasInternal() const
612 // Windows and Mac OS X does not have the concept of ownership;
613 // the clipboard is a fully global resource so all applications
614 // are notified of changes.
615 #if (defined(Q_WS_X11))
623 void GuiClipboard::on_dataChanged()
625 QMimeData const * const source =
626 qApp->clipboard()->mimeData(QClipboard::Clipboard);
627 QStringList l = source->formats();
628 LYXERR(Debug::ACTION, "Qt Clipboard changed. We found the following mime types:");
629 for (int i = 0; i < l.count(); i++) {
630 LYXERR(Debug::ACTION, fromqstr(l.value(i)));
633 text_clipboard_empty_ = qApp->clipboard()->
634 text(QClipboard::Clipboard).isEmpty();
636 has_lyx_contents_ = hasLyXContents();
637 has_graphics_contents_ = hasGraphicsContents();
641 bool GuiClipboard::empty() const
643 // We need to check both the plaintext and the LyX version of the
644 // clipboard. The plaintext version is empty if the LyX version
645 // contains only one inset, and the LyX version is empty if the
646 // clipboard does not come from LyX.
647 if (!text_clipboard_empty_)
649 return !has_lyx_contents_ && !has_graphics_contents_;
652 } // namespace frontend
655 #include "GuiClipboard_moc.cpp"