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"
21 #include "BufferView.h"
24 #include "support/assert.h"
25 #include "support/convert.h"
26 #include "support/debug.h"
27 #include "support/filetools.h"
28 #include "support/FileFilterList.h"
29 #include "support/gettext.h"
30 #include "support/lstrings.h"
32 #include "support/linkback/LinkBackProxy.h"
35 #include "frontends/alert.h"
37 #include <QApplication>
40 #include <QDataStream>
43 #include <QMacPasteboardMime>
46 #include <QStringList>
49 #include <QWindowsMime>
50 #if defined(Q_CYGWIN_WIN) || defined(Q_CC_MINGW)
60 using namespace lyx::support;
62 static char const * const lyx_mime_type = "application/x-lyx";
63 static char const * const pdf_mime_type = "application/pdf";
64 static char const * const emf_mime_type = "image/x-emf";
65 static char const * const wmf_mime_type = "image/x-wmf";
73 static FORMATETC cfFromMime(QString const & mimetype)
76 if (mimetype == emf_mime_type) {
77 formatetc.cfFormat = CF_ENHMETAFILE;
78 formatetc.tymed = TYMED_ENHMF;
79 } else if (mimetype == wmf_mime_type) {
80 formatetc.cfFormat = CF_METAFILEPICT;
81 formatetc.tymed = TYMED_MFPICT;
84 formatetc.dwAspect = DVASPECT_CONTENT;
85 formatetc.lindex = -1;
90 class QWindowsMimeMetafile : public QWindowsMime {
92 bool canConvertFromMime(FORMATETC const & formatetc, QMimeData const * mimedata) const;
93 bool canConvertToMime(QString const & mimetype, IDataObject * pDataObj) const;
94 bool convertFromMime(FORMATETC const & formatetc, const QMimeData * mimedata, STGMEDIUM * pmedium) const;
95 QVariant convertToMime(QString const & mimetype, IDataObject * pDataObj, QVariant::Type preferredType) const;
96 QVector<FORMATETC> formatsForMime(QString const & mimeType, QMimeData const * mimeData) const;
97 QString mimeForFormat(FORMATETC const &) const;
101 QString QWindowsMimeMetafile::mimeForFormat(FORMATETC const & formatetc) const
104 if (formatetc.cfFormat == CF_ENHMETAFILE)
106 else if (formatetc.cfFormat == CF_METAFILEPICT)
112 bool QWindowsMimeMetafile::canConvertFromMime(FORMATETC const & formatetc,
113 QMimeData const * mimedata) const
119 bool QWindowsMimeMetafile::canConvertToMime(QString const & mimetype,
120 IDataObject * pDataObj) const
122 if (mimetype != emf_mime_type && mimetype != wmf_mime_type)
124 FORMATETC formatetc = cfFromMime(mimetype);
125 return pDataObj->QueryGetData(&formatetc) == S_OK;
129 bool QWindowsMimeMetafile::convertFromMime(FORMATETC const & formatetc,
130 QMimeData const * mimedata, STGMEDIUM * pmedium) const
136 QVariant QWindowsMimeMetafile::convertToMime(QString const & mimetype,
137 IDataObject * pDataObj, QVariant::Type preferredType) const
140 if (!canConvertToMime(mimetype, pDataObj))
143 FORMATETC formatetc = cfFromMime(mimetype);
145 if (pDataObj->GetData(&formatetc, &s) != S_OK)
149 if (s.tymed == TYMED_ENHMF) {
150 dataSize = GetEnhMetaFileBits(s.hEnhMetaFile, 0, 0);
151 data.resize(dataSize);
152 dataSize = GetEnhMetaFileBits(s.hEnhMetaFile, dataSize, (LPBYTE)data.data());
153 } else if (s.tymed == TYMED_MFPICT) {
154 dataSize = GetMetaFileBitsEx((HMETAFILE)s.hMetaFilePict, 0, 0);
155 data.resize(dataSize);
156 dataSize = GetMetaFileBitsEx((HMETAFILE)s.hMetaFilePict, dataSize, (LPBYTE)data.data());
159 ReleaseStgMedium(&s);
165 QVector<FORMATETC> QWindowsMimeMetafile::formatsForMime(
166 QString const & mimetype, QMimeData const * mimedata) const
168 QVector<FORMATETC> formats;
169 formats += cfFromMime(mimetype);
173 static std::auto_ptr<QWindowsMimeMetafile> metafileWindowsMime;
179 class QMacPasteboardMimeGraphics : public QMacPasteboardMime {
181 QMacPasteboardMimeGraphics()
182 : QMacPasteboardMime(MIME_QT_CONVERTOR|MIME_ALL)
184 QString convertorName();
185 QString flavorFor(QString const & mime);
186 QString mimeFor(QString flav);
187 bool canConvert(QString const & mime, QString flav);
188 QVariant convertToMime(QString const & mime, QList<QByteArray> data, QString flav);
189 QList<QByteArray> convertFromMime(QString const & mime, QVariant data, QString flav);
193 QString QMacPasteboardMimeGraphics::convertorName()
199 QString QMacPasteboardMimeGraphics::flavorFor(QString const & mime)
201 LYXERR(Debug::ACTION, "flavorFor " << fromqstr(mime));
202 if (mime == QLatin1String(pdf_mime_type))
203 return QLatin1String("com.adobe.pdf");
208 QString QMacPasteboardMimeGraphics::mimeFor(QString flav)
210 LYXERR(Debug::ACTION, "mimeFor " << fromqstr(flav));
211 if (flav == QLatin1String("com.adobe.pdf"))
212 return QLatin1String(pdf_mime_type);
217 bool QMacPasteboardMimeGraphics::canConvert(QString const & mime, QString flav)
219 return mimeFor(flav) == mime;
223 QVariant QMacPasteboardMimeGraphics::convertToMime(QString const & mime, QList<QByteArray> data, QString)
226 qWarning("QMacPasteboardMimeGraphics: Cannot handle multiple member data");
231 QList<QByteArray> QMacPasteboardMimeGraphics::convertFromMime(QString const & mime, QVariant data, QString)
233 QList<QByteArray> ret;
234 ret.append(data.toByteArray());
238 static std::auto_ptr<QMacPasteboardMimeGraphics> graphicsPasteboardMime;
243 GuiClipboard::GuiClipboard()
245 connect(qApp->clipboard(), SIGNAL(dataChanged()),
246 this, SLOT(on_dataChanged()));
247 // initialize clipboard status.
251 if (!graphicsPasteboardMime.get())
252 graphicsPasteboardMime.reset(new QMacPasteboardMimeGraphics());
256 if (!metafileWindowsMime.get())
257 metafileWindowsMime.reset(new QWindowsMimeMetafile());
262 GuiClipboard::~GuiClipboard()
265 closeAllLinkBackLinks();
270 string const GuiClipboard::getAsLyX() const
272 LYXERR(Debug::ACTION, "GuiClipboard::getAsLyX(): `");
273 // We don't convert encodings here since the encoding of the
274 // clipboard contents is specified in the data itself
275 QMimeData const * source =
276 qApp->clipboard()->mimeData(QClipboard::Clipboard);
278 LYXERR(Debug::ACTION, "' (no QMimeData)");
282 if (source->hasFormat(lyx_mime_type)) {
283 // data from ourself or some other LyX instance
284 QByteArray const ar = source->data(lyx_mime_type);
285 string const s(ar.data(), ar.count());
286 LYXERR(Debug::ACTION, s << "'");
289 LYXERR(Debug::ACTION, "'");
294 FileName GuiClipboard::getPastedGraphicsFileName(Cursor const & cur,
295 Clipboard::GraphicsType & type) const
297 // create file dialog filter according to the existing types in the clipboard
298 vector<Clipboard::GraphicsType> types;
299 if (hasGraphicsContents(Clipboard::EmfGraphicsType))
300 types.push_back(Clipboard::EmfGraphicsType);
301 if (hasGraphicsContents(Clipboard::WmfGraphicsType))
302 types.push_back(Clipboard::WmfGraphicsType);
303 if (hasGraphicsContents(Clipboard::LinkBackGraphicsType))
304 types.push_back(Clipboard::LinkBackGraphicsType);
305 if (hasGraphicsContents(Clipboard::PdfGraphicsType))
306 types.push_back(Clipboard::PdfGraphicsType);
307 if (hasGraphicsContents(Clipboard::PngGraphicsType))
308 types.push_back(Clipboard::PngGraphicsType);
309 if (hasGraphicsContents(Clipboard::JpegGraphicsType))
310 types.push_back(Clipboard::JpegGraphicsType);
312 LASSERT(!types.empty(), /**/);
314 // select prefered type if AnyGraphicsType was passed
315 if (type == Clipboard::AnyGraphicsType)
316 type = types.front();
319 map<Clipboard::GraphicsType, string> extensions;
320 map<Clipboard::GraphicsType, docstring> typeNames;
322 extensions[Clipboard::EmfGraphicsType] = "emf";
323 extensions[Clipboard::WmfGraphicsType] = "wmf";
324 extensions[Clipboard::LinkBackGraphicsType] = "linkback";
325 extensions[Clipboard::PdfGraphicsType] = "pdf";
326 extensions[Clipboard::PngGraphicsType] = "png";
327 extensions[Clipboard::JpegGraphicsType] = "jpeg";
329 typeNames[Clipboard::EmfGraphicsType] = _("Enhanced Metafile");
330 typeNames[Clipboard::WmfGraphicsType] = _("Windows Metafile");
331 typeNames[Clipboard::LinkBackGraphicsType] = _("LinkBack PDF");
332 typeNames[Clipboard::PdfGraphicsType] = _("PDF");
333 typeNames[Clipboard::PngGraphicsType] = _("PNG");
334 typeNames[Clipboard::JpegGraphicsType] = _("JPEG");
336 // find unused filename with primary extension
337 string document_path = cur.buffer().fileName().onlyPath().absFilename();
338 unsigned newfile_number = 0;
342 filename = FileName(addName(document_path,
344 + convert<string>(newfile_number) + "."
345 + extensions[type]));
346 } while (filename.isReadableFile());
349 // create file type filter, putting the prefered on to the front
350 docstring filterSpec;
351 for (size_t i = 0; i != types.size(); ++i) {
352 docstring s = bformat(_("%1$s Files"), typeNames[types[i]])
353 + " (*." + from_ascii(extensions[types[i]]) + ")";
354 if (types[i] == type)
355 filterSpec = s + filterSpec;
357 filterSpec += ";;" + s;
359 FileFilterList const filter(filterSpec);
361 // show save dialog for the graphic
362 FileDialog dlg(qt_("Choose a filename to save the pasted graphic as"));
363 FileDialog::Result result =
364 dlg.save(toqstr(filename.onlyPath().absFilename()), filter,
365 toqstr(filename.onlyFileName()));
367 if (result.first == FileDialog::Later)
370 string newFilename = fromqstr(result.second);
371 if (newFilename.empty()) {
372 cur.bv().message(_("Canceled."));
375 filename.set(newFilename);
377 // check the extension (the user could have changed it)
378 if (!suffixIs(ascii_lowercase(filename.absFilename()),
379 "." + extensions[type])) {
380 // the user changed the extension. Check if the type is available
382 for (i = 1; i != types.size(); ++i) {
383 if (suffixIs(ascii_lowercase(filename.absFilename()),
384 "." + extensions[types[i]])) {
390 // invalid extension found, or none at all. In the latter
391 // case set the default extensions.
392 if (i == types.size()
393 && filename.onlyFileName().find('.') == string::npos) {
394 filename.changeExtension("." + extensions[type]);
398 // check whether the file exists and warn the user
399 if (!filename.exists())
401 int ret = frontend::Alert::prompt(
402 _("Overwrite external file?"),
403 bformat(_("File %1$s already exists, do you want to overwrite it?"),
404 from_utf8(filename.absFilename())), 1, 1, _("&Overwrite"), _("&Cancel"));
406 // overwrite, hence break the dialog loop
409 // not overwrite, hence show the dialog again (i.e. loop)
416 FileName GuiClipboard::getAsGraphics(Cursor const & cur, GraphicsType type) const
418 // get the filename from the user
419 FileName filename = getPastedGraphicsFileName(cur, type);
420 if (filename.empty())
423 // handle image cases first
424 if (type == PngGraphicsType || type == JpegGraphicsType) {
425 // get image from QImage from clipboard
426 QImage image = qApp->clipboard()->image();
427 if (image.isNull()) {
428 LYXERR(Debug::ACTION, "No image in clipboard");
432 // convert into graphics format
435 buffer.open(QIODevice::WriteOnly);
436 if (type == PngGraphicsType)
437 image.save(toqstr(filename.absFilename()), "PNG");
438 else if (type == JpegGraphicsType)
439 image.save(toqstr(filename.absFilename()), "JPEG");
441 LASSERT(false, /**/);
447 QMimeData const * source =
448 qApp->clipboard()->mimeData(QClipboard::Clipboard);
450 LYXERR(Debug::ACTION, "0 bytes (no QMimeData)");
457 case PdfGraphicsType: mime = pdf_mime_type; break;
458 case LinkBackGraphicsType: mime = pdf_mime_type; break;
459 case EmfGraphicsType: mime = emf_mime_type; break;
460 case WmfGraphicsType: mime = wmf_mime_type; break;
461 default: LASSERT(false, /**/);
465 if (!source->hasFormat(mime))
467 // data from ourself or some other LyX instance
468 QByteArray const ar = source->data(mime);
469 LYXERR(Debug::ACTION, "Getting from clipboard: mime = " << mime.data()
470 << "length = " << ar.count());
472 QFile f(toqstr(filename.absFilename()));
473 if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
474 LYXERR(Debug::ACTION, "Error opening file "
475 << filename.absFilename() << " for writing");
479 // write the (LinkBack) PDF data
481 if (type == LinkBackGraphicsType) {
483 void const * linkBackData;
484 unsigned linkBackLen;
485 getLinkBackData(&linkBackData, &linkBackLen);
486 f.write((char *)linkBackData, linkBackLen);
487 quint32 pdfLen = ar.size();
489 ds << pdfLen; // big endian by default
491 // only non-Mac this should never happen
492 LASSERT(false, /**/);
501 docstring const GuiClipboard::getAsText() const
503 // text data from other applications
504 QString const str = qApp->clipboard()->text(QClipboard::Clipboard)
505 .normalized(QString::NormalizationForm_C);
506 LYXERR(Debug::ACTION, "GuiClipboard::getAsText(): `" << fromqstr(str) << "'");
510 return internalLineEnding(qstring_to_ucs4(str));
514 void GuiClipboard::put(string const & lyx, docstring const & text)
516 LYXERR(Debug::ACTION, "GuiClipboard::put(`" << lyx << "' `"
517 << to_utf8(text) << "')");
518 // We don't convert the encoding of lyx since the encoding of the
519 // clipboard contents is specified in the data itself
520 QMimeData * data = new QMimeData;
522 QByteArray const qlyx(lyx.c_str(), lyx.size());
523 data->setData(lyx_mime_type, qlyx);
525 // Don't test for text.empty() since we want to be able to clear the
527 QString const qtext = toqstr(text);
528 data->setText(qtext);
529 qApp->clipboard()->setMimeData(data, QClipboard::Clipboard);
533 bool GuiClipboard::hasLyXContents() const
535 QMimeData const * const source =
536 qApp->clipboard()->mimeData(QClipboard::Clipboard);
537 return source && source->hasFormat(lyx_mime_type);
541 bool GuiClipboard::hasGraphicsContents(Clipboard::GraphicsType type) const
543 if (type == AnyGraphicsType) {
544 return hasGraphicsContents(PdfGraphicsType)
545 || hasGraphicsContents(PngGraphicsType)
546 || hasGraphicsContents(JpegGraphicsType)
547 || hasGraphicsContents(EmfGraphicsType)
548 || hasGraphicsContents(WmfGraphicsType)
549 || hasGraphicsContents(LinkBackGraphicsType);
552 QMimeData const * const source =
553 qApp->clipboard()->mimeData(QClipboard::Clipboard);
555 // handle image cases first
556 if (type == PngGraphicsType || type == JpegGraphicsType)
557 return source->hasImage();
559 // handle LinkBack for Mac
561 if (type == LinkBackGraphicsType)
562 return isLinkBackDataInPasteboard();
564 if (type == LinkBackGraphicsType)
569 QStringList const & formats = source->formats();
570 LYXERR(Debug::ACTION, "We found " << formats.size() << " formats");
571 for (int i = 0; i < formats.size(); ++i) {
572 LYXERR(Debug::ACTION, "Found format " << fromqstr(formats[i]));
575 // compute mime for type
578 case EmfGraphicsType: mime = emf_mime_type; break;
579 case WmfGraphicsType: mime = wmf_mime_type; break;
580 case PdfGraphicsType: mime = pdf_mime_type; break;
581 default: LASSERT(false, /**/);
584 return source && source->hasFormat(mime);
588 bool GuiClipboard::isInternal() const
590 // ownsClipboard() is also true for stuff coming from dialogs, e.g.
591 // the preamble dialog
592 // FIXME: This does only work on X11, since ownsClipboard() is
593 // hardwired to return false on Windows and OS X.
594 return qApp->clipboard()->ownsClipboard() && hasLyXContents();
598 bool GuiClipboard::hasInternal() const
600 // Windows and Mac OS X does not have the concept of ownership;
601 // the clipboard is a fully global resource so all applications
602 // are notified of changes.
603 #if (defined(Q_WS_X11))
611 void GuiClipboard::on_dataChanged()
613 QMimeData const * const source =
614 qApp->clipboard()->mimeData(QClipboard::Clipboard);
615 QStringList l = source->formats();
616 LYXERR(Debug::ACTION, "Qt Clipboard changed. We found the following mime types:");
617 for (int i = 0; i < l.count(); i++) {
618 LYXERR(Debug::ACTION, fromqstr(l.value(i)));
621 text_clipboard_empty_ = qApp->clipboard()->
622 text(QClipboard::Clipboard).isEmpty();
624 has_lyx_contents_ = hasLyXContents();
625 has_graphics_contents_ = hasGraphicsContents();
629 bool GuiClipboard::empty() const
631 // We need to check both the plaintext and the LyX version of the
632 // clipboard. The plaintext version is empty if the LyX version
633 // contains only one inset, and the LyX version is empty if the
634 // clipboard does not come from LyX.
635 if (!text_clipboard_empty_)
637 return !has_lyx_contents_ && !has_graphics_contents_;
640 } // namespace frontend
643 #include "GuiClipboard_moc.cpp"