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>
52 #include "boost/assert.hpp"
57 #include "support/linkback/LinkBackProxy.h"
61 using namespace lyx::support;
63 static char const * const lyx_mime_type = "application/x-lyx";
64 static char const * const pdf_mime_type = "application/pdf";
65 static char const * const emf_mime_type = "image/x-emf";
66 static char const * const wmf_mime_type = "image/x-wmf";
74 static FORMATETC setCf(int cf)
77 formatetc.cfFormat = cf;
79 formatetc.dwAspect = DVASPECT_CONTENT;
80 formatetc.lindex = -1;
81 if (cf == CF_ENHMETAFILE)
82 formatetc.tymed = TYMED_ENHMF;
83 if (cf == CF_METAFILEPICT)
84 formatetc.tymed = TYMED_MFPICT;
89 static bool canGetData(int cf, IDataObject * pDataObj)
91 FORMATETC formatetc = setCf(cf);
92 return pDataObj->QueryGetData(&formatetc) == S_OK;
96 class QWindowsMimeMetafile : public QWindowsMime {
98 bool canConvertFromMime(FORMATETC const & formatetc, QMimeData const * mimeData) const;
99 bool canConvertToMime(QString const & mimeType, IDataObject * pDataObj) const;
100 bool convertFromMime(FORMATETC const & formatetc, const QMimeData * mimeData, STGMEDIUM * pmedium) const;
101 QVariant convertToMime(QString const & mimeType, IDataObject * pDataObj, QVariant::Type preferredType) const;
102 QVector<FORMATETC> formatsForMime(QString const & mimeType, QMimeData const * mimeData) const;
103 QString mimeForFormat(FORMATETC const &) const;
107 QString QWindowsMimeMetafile::mimeForFormat(const FORMATETC & formatetc) const
110 if (formatetc.cfFormat == CF_ENHMETAFILE)
112 else if (formatetc.cfFormat == CF_METAFILEPICT)
118 bool QWindowsMimeMetafile::canConvertFromMime(
119 const FORMATETC & formatetc, const QMimeData * mimeData) const
125 bool QWindowsMimeMetafile::canConvertToMime(
126 const QString & mimeType, IDataObject * pDataObj) const
128 return (mimeType == "image/x-emf" && canGetData(CF_ENHMETAFILE, pDataObj))
129 || (mimeType == "image/x-wmf" && canGetData(CF_METAFILEPICT, pDataObj));
133 bool QWindowsMimeMetafile::convertFromMime(
134 const FORMATETC & formatetc, const QMimeData * mimeData,
135 STGMEDIUM * pmedium) const
141 QVariant QWindowsMimeMetafile::convertToMime(
142 const QString & mimeType, IDataObject * pDataObj,
143 QVariant::Type preferredType) const
147 if (canConvertToMime(mimeType, pDataObj)) {
149 if (mimeType == "image/x-emf")
150 formatetc = setCf(CF_ENHMETAFILE);
151 if (mimeType == "image/x-wmf")
152 formatetc = setCf(CF_METAFILEPICT);
157 if (pDataObj->GetData(&formatetc, &s) == S_OK) {
158 if (s.tymed == TYMED_ENHMF) {
159 dataSize = GetEnhMetaFileBits(s.hEnhMetaFile, 0, 0);
160 data.resize(dataSize);
161 dataSize = GetEnhMetaFileBits(s.hEnhMetaFile, dataSize, (LPBYTE)data.data());
162 } else if (s.tymed == TYMED_MFPICT) {
163 dataSize = GetMetaFileBitsEx((HMETAFILE)s.hMetaFilePict, 0, 0);
164 data.resize(dataSize);
165 dataSize = GetMetaFileBitsEx((HMETAFILE)s.hMetaFilePict, dataSize, (LPBYTE)data.data());
168 ReleaseStgMedium(&s);
176 QVector<FORMATETC> QWindowsMimeMetafile::formatsForMime(
177 const QString & mimeType, const QMimeData * mimeData) const
179 QVector<FORMATETC> formats;
180 if (mimeType == "image/x-emf")
181 formats += setCf(CF_ENHMETAFILE);
182 if (mimeType == "image/x-wmf")
183 formats += setCf(CF_METAFILEPICT);
187 static QWindowsMimeMetafile * metafileWindowsMime;
193 class QMacPasteboardMimeGraphics : public QMacPasteboardMime {
195 QMacPasteboardMimeGraphics()
196 : QMacPasteboardMime(MIME_QT_CONVERTOR|MIME_ALL)
198 ~QMacPasteboardMimeGraphics() {}
199 QString convertorName();
200 QString flavorFor(const QString & mime);
201 QString mimeFor(QString flav);
202 bool canConvert(const QString & mime, QString flav);
203 QVariant convertToMime(const QString & mime, QList<QByteArray> data, QString flav);
204 QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav);
208 QString QMacPasteboardMimeGraphics::convertorName()
214 QString QMacPasteboardMimeGraphics::flavorFor(const QString & mime)
216 LYXERR(Debug::ACTION, "flavorFor " << fromqstr(mime));
217 if (mime == QLatin1String(pdf_mime_type))
218 return QLatin1String("com.adobe.pdf");
223 QString QMacPasteboardMimeGraphics::mimeFor(QString flav)
225 LYXERR(Debug::ACTION, "mimeFor " << fromqstr(flav));
226 if (flav == QLatin1String("com.adobe.pdf"))
227 return QLatin1String(pdf_mime_type);
232 bool QMacPasteboardMimeGraphics::canConvert(const QString & mime, QString flav)
234 return mimeFor(flav) == mime;
238 QVariant QMacPasteboardMimeGraphics::convertToMime(const QString & mime, QList<QByteArray> data, QString)
241 qWarning("QMacPasteboardMimeGraphics: Cannot handle multiple member data");
246 QList<QByteArray> QMacPasteboardMimeGraphics::convertFromMime(const QString &mime, QVariant data, QString)
248 QList<QByteArray> ret;
249 ret.append(data.toByteArray());
253 static QMacPasteboardMimeGraphics * graphicsPasteboardMime;
258 GuiClipboard::GuiClipboard()
260 connect(qApp->clipboard(), SIGNAL(dataChanged()),
261 this, SLOT(on_dataChanged()));
262 // initialize clipboard status.
266 if (!graphicsPasteboardMime)
267 graphicsPasteboardMime = new QMacPasteboardMimeGraphics();
271 if (!metafileWindowsMime)
272 metafileWindowsMime = new QWindowsMimeMetafile();
277 GuiClipboard::~GuiClipboard()
280 closeAllLinkBackLinks();
285 string const GuiClipboard::getAsLyX() const
287 LYXERR(Debug::ACTION, "GuiClipboard::getAsLyX(): `");
288 // We don't convert encodings here since the encoding of the
289 // clipboard contents is specified in the data itself
290 QMimeData const * source =
291 qApp->clipboard()->mimeData(QClipboard::Clipboard);
293 LYXERR(Debug::ACTION, "' (no QMimeData)");
297 if (source->hasFormat(lyx_mime_type)) {
298 // data from ourself or some other LyX instance
299 QByteArray const ar = source->data(lyx_mime_type);
300 string const s(ar.data(), ar.count());
301 LYXERR(Debug::ACTION, s << "'");
304 LYXERR(Debug::ACTION, "'");
309 FileName GuiClipboard::getPastedGraphicsFileName(Cursor const & cur,
310 Clipboard::GraphicsType & type) const
312 // create file dialog filter according to the existing types in the clipboard
313 vector<Clipboard::GraphicsType> types;
314 if (hasGraphicsContents(Clipboard::EmfGraphicsType))
315 types.push_back(Clipboard::EmfGraphicsType);
316 if (hasGraphicsContents(Clipboard::WmfGraphicsType))
317 types.push_back(Clipboard::WmfGraphicsType);
318 if (hasGraphicsContents(Clipboard::LinkBackGraphicsType))
319 types.push_back(Clipboard::LinkBackGraphicsType);
320 if (hasGraphicsContents(Clipboard::PdfGraphicsType))
321 types.push_back(Clipboard::PdfGraphicsType);
322 if (hasGraphicsContents(Clipboard::PngGraphicsType))
323 types.push_back(Clipboard::PngGraphicsType);
324 if (hasGraphicsContents(Clipboard::JpegGraphicsType))
325 types.push_back(Clipboard::JpegGraphicsType);
327 BOOST_ASSERT(!types.empty());
329 // select prefered type if AnyGraphicsType was passed
330 if (type == Clipboard::AnyGraphicsType)
331 type = types.front();
334 map<Clipboard::GraphicsType, string> extensions;
335 map<Clipboard::GraphicsType, docstring> typeNames;
337 extensions[Clipboard::EmfGraphicsType] = "emf";
338 extensions[Clipboard::WmfGraphicsType] = "wmf";
339 extensions[Clipboard::LinkBackGraphicsType] = "linkback";
340 extensions[Clipboard::PdfGraphicsType] = "pdf";
341 extensions[Clipboard::PngGraphicsType] = "png";
342 extensions[Clipboard::JpegGraphicsType] = "jpeg";
344 typeNames[Clipboard::EmfGraphicsType] = _("Enhanced Metafile");
345 typeNames[Clipboard::WmfGraphicsType] = _("Windows Metafile");
346 typeNames[Clipboard::LinkBackGraphicsType] = _("LinkBack PDF");
347 typeNames[Clipboard::PdfGraphicsType] = _("PDF");
348 typeNames[Clipboard::PngGraphicsType] = _("PNG");
349 typeNames[Clipboard::JpegGraphicsType] = _("JPEG");
351 // find unused filename with primary extension
352 string document_path = cur.buffer().fileName().onlyPath().absFilename();
353 unsigned newfile_number = 0;
357 filename = FileName(addName(document_path,
359 + convert<string>(newfile_number) + "."
360 + extensions[type]));
361 } while (filename.isReadableFile());
364 // create file type filter, putting the prefered on to the front
365 docstring filterSpec;
366 for (size_t i = 0; i != types.size(); ++i) {
367 docstring s = bformat(_("%1$s Files"), typeNames[types[i]])
368 + " (*." + from_ascii(extensions[types[i]]) + ")";
369 if (types[i] == type)
370 filterSpec = s + filterSpec;
372 filterSpec += ";;" + s;
374 FileFilterList const filter(filterSpec);
376 // show save dialog for the graphic
377 FileDialog dlg(qt_("Choose a filename to save the pasted graphic as"));
378 FileDialog::Result result =
379 dlg.save(toqstr(filename.onlyPath().absFilename()), filter,
380 toqstr(filename.onlyFileName()));
382 if (result.first == FileDialog::Later)
385 string newFilename = fromqstr(result.second);
386 if (newFilename.empty()) {
387 cur.bv().message(_("Canceled."));
390 filename.set(newFilename);
392 // check the extension (the user could have changed it)
393 if (!suffixIs(ascii_lowercase(filename.absFilename()),
394 "." + extensions[type])) {
395 // the user changed the extension. Check if the type is available
397 for (i = 1; i != types.size(); ++i) {
398 if (suffixIs(ascii_lowercase(filename.absFilename()),
399 "." + extensions[types[i]])) {
405 // invalid extension found, or none at all. In the latter
406 // case set the default extensions.
407 if (i == types.size()
408 && filename.onlyFileName().find('.') == string::npos) {
409 filename.changeExtension("." + extensions[type]);
413 // check whether the file exists and warn the user
414 if (!filename.exists())
416 int ret = frontend::Alert::prompt(
417 _("Overwrite external file?"),
418 bformat(_("File %1$s already exists, do you want to overwrite it?"),
419 from_utf8(filename.absFilename())), 1, 1, _("&Overwrite"), _("&Cancel"));
421 // overwrite, hence break the dialog loop
424 // not overwrite, hence show the dialog again (i.e. loop)
431 FileName GuiClipboard::getAsGraphics(Cursor const & cur, GraphicsType type) const
433 // get the filename from the user
434 FileName filename = getPastedGraphicsFileName(cur, type);
435 if (filename.empty())
438 // handle image cases first
439 if (type == PngGraphicsType || type == JpegGraphicsType) {
440 // get image from QImage from clipboard
441 QImage image = qApp->clipboard()->image();
442 if (image.isNull()) {
443 LYXERR(Debug::ACTION, "No image in clipboard");
447 // convert into graphics format
450 buffer.open(QIODevice::WriteOnly);
451 if (type == PngGraphicsType)
452 image.save(toqstr(filename.absFilename()), "PNG");
453 else if (type == JpegGraphicsType)
454 image.save(toqstr(filename.absFilename()), "JPEG");
462 QMimeData const * source =
463 qApp->clipboard()->mimeData(QClipboard::Clipboard);
465 LYXERR(Debug::ACTION, "0 bytes (no QMimeData)");
472 case PdfGraphicsType: mime = pdf_mime_type; break;
473 case LinkBackGraphicsType: mime = pdf_mime_type; break;
474 case EmfGraphicsType: mime = emf_mime_type; break;
475 case WmfGraphicsType: mime = wmf_mime_type; break;
476 default: BOOST_ASSERT(false);
480 if (!source->hasFormat(mime))
482 // data from ourself or some other LyX instance
483 QByteArray const ar = source->data(mime);
484 LYXERR(Debug::ACTION, "Getting from clipboard: mime = " << mime.data()
485 << "length = " << ar.count());
487 QFile f(toqstr(filename.absFilename()));
488 if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
489 LYXERR(Debug::ACTION, "Error opening file "
490 << filename.absFilename() << " for writing");
494 // write the (LinkBack) PDF data
496 if (type == LinkBackGraphicsType) {
498 void const * linkBackData;
499 unsigned linkBackLen;
500 getLinkBackData(&linkBackData, &linkBackLen);
501 f.write((char *)linkBackData, linkBackLen);
502 quint32 pdfLen = ar.size();
504 ds << pdfLen; // big endian by default
506 // only non-Mac this should never happen
516 docstring const GuiClipboard::getAsText() const
518 // text data from other applications
519 QString const str = qApp->clipboard()->text(QClipboard::Clipboard)
520 .normalized(QString::NormalizationForm_C);
521 LYXERR(Debug::ACTION, "GuiClipboard::getAsText(): `" << fromqstr(str) << "'");
525 return internalLineEnding(qstring_to_ucs4(str));
529 void GuiClipboard::put(string const & lyx, docstring const & text)
531 LYXERR(Debug::ACTION, "GuiClipboard::put(`" << lyx << "' `"
532 << to_utf8(text) << "')");
533 // We don't convert the encoding of lyx since the encoding of the
534 // clipboard contents is specified in the data itself
535 QMimeData * data = new QMimeData;
537 QByteArray const qlyx(lyx.c_str(), lyx.size());
538 data->setData(lyx_mime_type, qlyx);
540 // Don't test for text.empty() since we want to be able to clear the
542 QString const qtext = toqstr(text);
543 data->setText(qtext);
544 qApp->clipboard()->setMimeData(data, QClipboard::Clipboard);
548 bool GuiClipboard::hasLyXContents() const
550 QMimeData const * const source =
551 qApp->clipboard()->mimeData(QClipboard::Clipboard);
552 return source && source->hasFormat(lyx_mime_type);
556 bool GuiClipboard::hasGraphicsContents(Clipboard::GraphicsType type) const
558 if (type == AnyGraphicsType) {
559 return hasGraphicsContents(PdfGraphicsType)
560 || hasGraphicsContents(PngGraphicsType)
561 || hasGraphicsContents(JpegGraphicsType)
562 || hasGraphicsContents(EmfGraphicsType)
563 || hasGraphicsContents(WmfGraphicsType)
564 || hasGraphicsContents(LinkBackGraphicsType);
567 QMimeData const * const source =
568 qApp->clipboard()->mimeData(QClipboard::Clipboard);
570 // handle image cases first
571 if (type == PngGraphicsType || type == JpegGraphicsType)
572 return source->hasImage();
574 // handle LinkBack for Mac
576 if (type == LinkBackGraphicsType)
577 return isLinkBackDataInPasteboard();
579 if (type == LinkBackGraphicsType)
584 QStringList const & formats = source->formats();
585 LYXERR(Debug::ACTION, "We found " << formats.size() << " formats");
586 for (int i = 0; i < formats.size(); ++i) {
587 LYXERR(Debug::ACTION, "Found format " << fromqstr(formats[i]));
590 // compute mime for type
593 case EmfGraphicsType: mime = emf_mime_type; break;
594 case WmfGraphicsType: mime = wmf_mime_type; break;
595 case PdfGraphicsType: mime = pdf_mime_type; break;
596 default: BOOST_ASSERT(false);
599 return source && source->hasFormat(mime);
603 bool GuiClipboard::isInternal() const
605 // ownsClipboard() is also true for stuff coming from dialogs, e.g.
606 // the preamble dialog
607 // FIXME: This does only work on X11, since ownsClipboard() is
608 // hardwired to return false on Windows and OS X.
609 return qApp->clipboard()->ownsClipboard() && hasLyXContents();
613 bool GuiClipboard::hasInternal() const
615 // Windows and Mac OS X does not have the concept of ownership;
616 // the clipboard is a fully global resource so all applications
617 // are notified of changes.
618 #if (defined(Q_WS_X11))
626 void GuiClipboard::on_dataChanged()
628 QMimeData const * const source =
629 qApp->clipboard()->mimeData(QClipboard::Clipboard);
630 QStringList l = source->formats();
631 LYXERR(Debug::ACTION, "Qt Clipboard changed. We found the following mime types:");
632 for (int i = 0; i < l.count(); i++) {
633 LYXERR(Debug::ACTION, fromqstr(l.value(i)));
636 text_clipboard_empty_ = qApp->clipboard()->
637 text(QClipboard::Clipboard).isEmpty();
639 has_lyx_contents_ = hasLyXContents();
640 has_graphics_contents_ = hasGraphicsContents();
644 bool GuiClipboard::empty() const
646 // We need to check both the plaintext and the LyX version of the
647 // clipboard. The plaintext version is empty if the LyX version
648 // contains only one inset, and the LyX version is empty if the
649 // clipboard does not come from LyX.
650 if (!text_clipboard_empty_)
652 return !has_lyx_contents_ && !has_graphics_contents_;
655 } // namespace frontend
658 #include "GuiClipboard_moc.cpp"