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/lassert.h"
25 #include "support/convert.h"
26 #include "support/debug.h"
27 #include "support/filetools.h"
28 #include "support/gettext.h"
29 #include "support/lstrings.h"
32 #include "support/linkback/LinkBackProxy.h"
35 #include "frontends/alert.h"
37 #include <QApplication>
40 #include <QDataStream>
45 #include <QStringList>
51 using namespace lyx::support;
53 static char const * const lyx_mime_type = "application/x-lyx";
54 static char const * const pdf_mime_type = "application/pdf";
55 static char const * const emf_mime_type = "image/x-emf";
56 static char const * const wmf_mime_type = "image/x-wmf";
63 GuiClipboard::GuiClipboard()
65 connect(qApp->clipboard(), SIGNAL(dataChanged()),
66 this, SLOT(on_dataChanged()));
67 // initialize clipboard status.
72 string const GuiClipboard::getAsLyX() const
74 LYXERR(Debug::ACTION, "GuiClipboard::getAsLyX(): `");
75 // We don't convert encodings here since the encoding of the
76 // clipboard contents is specified in the data itself
77 QMimeData const * source =
78 qApp->clipboard()->mimeData(QClipboard::Clipboard);
80 LYXERR(Debug::ACTION, "' (no QMimeData)");
84 if (source->hasFormat(lyx_mime_type)) {
85 // data from ourself or some other LyX instance
86 QByteArray const ar = source->data(lyx_mime_type);
87 string const s(ar.data(), ar.count());
88 LYXERR(Debug::ACTION, s << "'");
91 LYXERR(Debug::ACTION, "'");
96 FileName GuiClipboard::getPastedGraphicsFileName(Cursor const & cur,
97 Clipboard::GraphicsType & type) const
99 // create file dialog filter according to the existing types in the clipboard
100 vector<Clipboard::GraphicsType> types;
101 if (hasGraphicsContents(Clipboard::EmfGraphicsType))
102 types.push_back(Clipboard::EmfGraphicsType);
103 if (hasGraphicsContents(Clipboard::WmfGraphicsType))
104 types.push_back(Clipboard::WmfGraphicsType);
105 if (hasGraphicsContents(Clipboard::LinkBackGraphicsType))
106 types.push_back(Clipboard::LinkBackGraphicsType);
107 if (hasGraphicsContents(Clipboard::PdfGraphicsType))
108 types.push_back(Clipboard::PdfGraphicsType);
109 if (hasGraphicsContents(Clipboard::PngGraphicsType))
110 types.push_back(Clipboard::PngGraphicsType);
111 if (hasGraphicsContents(Clipboard::JpegGraphicsType))
112 types.push_back(Clipboard::JpegGraphicsType);
114 LASSERT(!types.empty(), /**/);
116 // select prefered type if AnyGraphicsType was passed
117 if (type == Clipboard::AnyGraphicsType)
118 type = types.front();
121 map<Clipboard::GraphicsType, string> extensions;
122 map<Clipboard::GraphicsType, docstring> typeNames;
124 extensions[Clipboard::EmfGraphicsType] = "emf";
125 extensions[Clipboard::WmfGraphicsType] = "wmf";
126 extensions[Clipboard::LinkBackGraphicsType] = "linkback";
127 extensions[Clipboard::PdfGraphicsType] = "pdf";
128 extensions[Clipboard::PngGraphicsType] = "png";
129 extensions[Clipboard::JpegGraphicsType] = "jpeg";
131 typeNames[Clipboard::EmfGraphicsType] = _("Enhanced Metafile");
132 typeNames[Clipboard::WmfGraphicsType] = _("Windows Metafile");
133 typeNames[Clipboard::LinkBackGraphicsType] = _("LinkBack PDF");
134 typeNames[Clipboard::PdfGraphicsType] = _("PDF");
135 typeNames[Clipboard::PngGraphicsType] = _("PNG");
136 typeNames[Clipboard::JpegGraphicsType] = _("JPEG");
138 // find unused filename with primary extension
139 string document_path = cur.buffer().fileName().onlyPath().absFilename();
140 unsigned newfile_number = 0;
144 filename = FileName(addName(document_path,
146 + convert<string>(newfile_number) + "."
147 + extensions[type]));
148 } while (filename.isReadableFile());
151 // create file type filter, putting the prefered on to the front
153 for (size_t i = 0; i != types.size(); ++i) {
154 docstring s = bformat(_("%1$s Files"), typeNames[types[i]])
155 + " (*." + from_ascii(extensions[types[i]]) + ")";
156 if (types[i] == type)
157 filter.prepend(toqstr(s));
159 filter.append(toqstr(s));
161 filter = fileFilters(filter.join(";;"));
163 // show save dialog for the graphic
164 FileDialog dlg(qt_("Choose a filename to save the pasted graphic as"));
165 FileDialog::Result result =
166 dlg.save(toqstr(filename.onlyPath().absFilename()), filter,
167 toqstr(filename.onlyFileName()));
169 if (result.first == FileDialog::Later)
172 string newFilename = fromqstr(result.second);
173 if (newFilename.empty()) {
174 cur.bv().message(_("Canceled."));
177 filename.set(newFilename);
179 // check the extension (the user could have changed it)
180 if (!suffixIs(ascii_lowercase(filename.absFilename()),
181 "." + extensions[type])) {
182 // the user changed the extension. Check if the type is available
184 for (i = 1; i != types.size(); ++i) {
185 if (suffixIs(ascii_lowercase(filename.absFilename()),
186 "." + extensions[types[i]])) {
192 // invalid extension found, or none at all. In the latter
193 // case set the default extensions.
194 if (i == types.size()
195 && filename.onlyFileName().find('.') == string::npos) {
196 filename.changeExtension("." + extensions[type]);
200 // check whether the file exists and warn the user
201 if (!filename.exists())
203 int ret = frontend::Alert::prompt(
204 _("Overwrite external file?"),
205 bformat(_("File %1$s already exists, do you want to overwrite it?"),
206 from_utf8(filename.absFilename())), 1, 1, _("&Overwrite"), _("&Cancel"));
208 // overwrite, hence break the dialog loop
211 // not overwrite, hence show the dialog again (i.e. loop)
218 FileName GuiClipboard::getAsGraphics(Cursor const & cur, GraphicsType type) const
220 // get the filename from the user
221 FileName filename = getPastedGraphicsFileName(cur, type);
222 if (filename.empty())
225 // handle image cases first
226 if (type == PngGraphicsType || type == JpegGraphicsType) {
227 // get image from QImage from clipboard
228 QImage image = qApp->clipboard()->image();
229 if (image.isNull()) {
230 LYXERR(Debug::ACTION, "No image in clipboard");
234 // convert into graphics format
237 buffer.open(QIODevice::WriteOnly);
238 if (type == PngGraphicsType)
239 image.save(toqstr(filename.absFilename()), "PNG");
240 else if (type == JpegGraphicsType)
241 image.save(toqstr(filename.absFilename()), "JPEG");
243 LASSERT(false, /**/);
249 QMimeData const * source =
250 qApp->clipboard()->mimeData(QClipboard::Clipboard);
252 LYXERR(Debug::ACTION, "0 bytes (no QMimeData)");
259 case PdfGraphicsType: mime = pdf_mime_type; break;
260 case LinkBackGraphicsType: mime = pdf_mime_type; break;
261 case EmfGraphicsType: mime = emf_mime_type; break;
262 case WmfGraphicsType: mime = wmf_mime_type; break;
263 default: LASSERT(false, /**/);
267 if (!source->hasFormat(mime))
269 // data from ourself or some other LyX instance
270 QByteArray const ar = source->data(mime);
271 LYXERR(Debug::ACTION, "Getting from clipboard: mime = " << mime.data()
272 << "length = " << ar.count());
274 QFile f(toqstr(filename.absFilename()));
275 if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
276 LYXERR(Debug::ACTION, "Error opening file "
277 << filename.absFilename() << " for writing");
281 // write the (LinkBack) PDF data
283 if (type == LinkBackGraphicsType) {
285 void const * linkBackData;
286 unsigned linkBackLen;
287 getLinkBackData(&linkBackData, &linkBackLen);
288 f.write((char *)linkBackData, linkBackLen);
289 quint32 pdfLen = ar.size();
291 ds << pdfLen; // big endian by default
293 // only non-Mac this should never happen
294 LASSERT(false, /**/);
303 docstring const GuiClipboard::getAsText() const
305 // text data from other applications
306 QString const str = qApp->clipboard()->text(QClipboard::Clipboard)
307 .normalized(QString::NormalizationForm_C);
308 LYXERR(Debug::ACTION, "GuiClipboard::getAsText(): `" << str << "'");
312 return internalLineEnding(qstring_to_ucs4(str));
316 void GuiClipboard::put(string const & lyx, docstring const & text)
318 LYXERR(Debug::ACTION, "GuiClipboard::put(`" << lyx << "' `"
319 << to_utf8(text) << "')");
320 // We don't convert the encoding of lyx since the encoding of the
321 // clipboard contents is specified in the data itself
322 QMimeData * data = new QMimeData;
324 QByteArray const qlyx(lyx.c_str(), lyx.size());
325 data->setData(lyx_mime_type, qlyx);
327 // Don't test for text.empty() since we want to be able to clear the
329 QString const qtext = toqstr(text);
330 data->setText(qtext);
331 qApp->clipboard()->setMimeData(data, QClipboard::Clipboard);
335 bool GuiClipboard::hasLyXContents() const
337 QMimeData const * const source =
338 qApp->clipboard()->mimeData(QClipboard::Clipboard);
339 return source && source->hasFormat(lyx_mime_type);
343 bool GuiClipboard::hasGraphicsContents(Clipboard::GraphicsType type) const
345 if (type == AnyGraphicsType) {
346 return hasGraphicsContents(PdfGraphicsType)
347 || hasGraphicsContents(PngGraphicsType)
348 || hasGraphicsContents(JpegGraphicsType)
349 || hasGraphicsContents(EmfGraphicsType)
350 || hasGraphicsContents(WmfGraphicsType)
351 || hasGraphicsContents(LinkBackGraphicsType);
354 QMimeData const * const source =
355 qApp->clipboard()->mimeData(QClipboard::Clipboard);
357 // handle image cases first
358 if (type == PngGraphicsType || type == JpegGraphicsType)
359 return source->hasImage();
361 // handle LinkBack for Mac
362 if (type == LinkBackGraphicsType)
364 return isLinkBackDataInPasteboard();
370 QStringList const & formats = source->formats();
371 LYXERR(Debug::ACTION, "We found " << formats.size() << " formats");
372 for (int i = 0; i < formats.size(); ++i)
373 LYXERR(Debug::ACTION, "Found format " << formats[i]);
375 // compute mime for type
378 case EmfGraphicsType: mime = emf_mime_type; break;
379 case WmfGraphicsType: mime = wmf_mime_type; break;
380 case PdfGraphicsType: mime = pdf_mime_type; break;
381 default: LASSERT(false, /**/);
384 return source && source->hasFormat(mime);
388 bool GuiClipboard::isInternal() const
390 // ownsClipboard() is also true for stuff coming from dialogs, e.g.
391 // the preamble dialog
392 // FIXME: This does only work on X11, since ownsClipboard() is
393 // hardwired to return false on Windows and OS X.
394 return qApp->clipboard()->ownsClipboard() && hasLyXContents();
398 bool GuiClipboard::hasInternal() const
400 // Windows and Mac OS X does not have the concept of ownership;
401 // the clipboard is a fully global resource so all applications
402 // are notified of changes.
403 #if (defined(Q_WS_X11))
411 void GuiClipboard::on_dataChanged()
413 QMimeData const * const source =
414 qApp->clipboard()->mimeData(QClipboard::Clipboard);
415 QStringList l = source->formats();
416 LYXERR(Debug::ACTION, "Qt Clipboard changed. We found the following mime types:");
417 for (int i = 0; i < l.count(); i++)
418 LYXERR(Debug::ACTION, l.value(i));
420 text_clipboard_empty_ = qApp->clipboard()->
421 text(QClipboard::Clipboard).isEmpty();
423 has_lyx_contents_ = hasLyXContents();
424 has_graphics_contents_ = hasGraphicsContents();
428 bool GuiClipboard::empty() const
430 // We need to check both the plaintext and the LyX version of the
431 // clipboard. The plaintext version is empty if the LyX version
432 // contains only one inset, and the LyX version is empty if the
433 // clipboard does not come from LyX.
434 if (!text_clipboard_empty_)
436 return !has_lyx_contents_ && !has_graphics_contents_;
439 } // namespace frontend
442 #include "GuiClipboard_moc.cpp"