]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiClipboard.cpp
Fix MSVC link error.
[lyx.git] / src / frontends / qt4 / GuiClipboard.cpp
1 // -*- C++ -*-
2 /**
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.
6  *
7  * \author John Levon
8  * \author Abdelrazak Younes
9  *
10  * Full author contact details are available in file CREDITS.
11  */
12
13 #include <config.h>
14
15 #include "FileDialog.h"
16
17 #include "GuiClipboard.h"
18 #include "qt_helpers.h"
19
20 #include "Buffer.h"
21 #include "BufferView.h"
22 #include "Cursor.h"
23
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"
30
31 #ifdef Q_WS_MACX
32 #include "support/linkback/LinkBackProxy.h"
33 #endif // Q_WS_MACX
34
35 #include "frontends/alert.h"
36
37 #include <QApplication>
38 #include <QBuffer>
39 #include <QClipboard>
40 #include <QDataStream>
41 #include <QFile>
42 #include <QImage>
43 #include <QMimeData>
44 #include <QString>
45 #include <QStringList>
46
47 #include <memory>
48 #include <map>
49
50 using namespace std;
51 using namespace lyx::support;
52
53
54 namespace lyx {
55
56 namespace frontend {
57
58 char const * lyx_mime_type = "application/x-lyx";
59 char const * pdf_mime_type = "application/pdf";
60 char const * emf_mime_type = "image/x-emf";
61 char const * wmf_mime_type = "image/x-wmf";
62
63
64 GuiClipboard::GuiClipboard()
65 {
66         connect(qApp->clipboard(), SIGNAL(dataChanged()),
67                 this, SLOT(on_dataChanged()));
68         // initialize clipboard status.
69         on_dataChanged();
70 }
71
72
73 string const GuiClipboard::getAsLyX() const
74 {
75         LYXERR(Debug::ACTION, "GuiClipboard::getAsLyX(): `");
76         // We don't convert encodings here since the encoding of the
77         // clipboard contents is specified in the data itself
78         QMimeData const * source =
79                 qApp->clipboard()->mimeData(QClipboard::Clipboard);
80         if (!source) {
81                 LYXERR(Debug::ACTION, "' (no QMimeData)");
82                 return string();
83         }
84
85         if (source->hasFormat(lyx_mime_type)) {
86                 // data from ourself or some other LyX instance
87                 QByteArray const ar = source->data(lyx_mime_type);
88                 string const s(ar.data(), ar.count());
89                 LYXERR(Debug::ACTION, s << "'");
90                 return s;
91         }
92         LYXERR(Debug::ACTION, "'");
93         return string();
94 }
95
96
97 FileName GuiClipboard::getPastedGraphicsFileName(Cursor const & cur,
98         Clipboard::GraphicsType & type) const
99 {
100         // create file dialog filter according to the existing types in the clipboard
101         vector<Clipboard::GraphicsType> types;
102         if (hasGraphicsContents(Clipboard::EmfGraphicsType))
103                 types.push_back(Clipboard::EmfGraphicsType);
104         if (hasGraphicsContents(Clipboard::WmfGraphicsType))
105                 types.push_back(Clipboard::WmfGraphicsType);
106         if (hasGraphicsContents(Clipboard::LinkBackGraphicsType))
107                 types.push_back(Clipboard::LinkBackGraphicsType);
108         if (hasGraphicsContents(Clipboard::PdfGraphicsType))
109                 types.push_back(Clipboard::PdfGraphicsType);
110         if (hasGraphicsContents(Clipboard::PngGraphicsType))
111                 types.push_back(Clipboard::PngGraphicsType);
112         if (hasGraphicsContents(Clipboard::JpegGraphicsType))
113                 types.push_back(Clipboard::JpegGraphicsType);
114         
115         LASSERT(!types.empty(), /**/);
116         
117         // select prefered type if AnyGraphicsType was passed
118         if (type == Clipboard::AnyGraphicsType)
119                 type = types.front();
120         
121         // which extension?
122         map<Clipboard::GraphicsType, string> extensions;
123         map<Clipboard::GraphicsType, docstring> typeNames;
124         
125         extensions[Clipboard::EmfGraphicsType] = "emf";
126         extensions[Clipboard::WmfGraphicsType] = "wmf";
127         extensions[Clipboard::LinkBackGraphicsType] = "linkback";
128         extensions[Clipboard::PdfGraphicsType] = "pdf";
129         extensions[Clipboard::PngGraphicsType] = "png";
130         extensions[Clipboard::JpegGraphicsType] = "jpeg";
131         
132         typeNames[Clipboard::EmfGraphicsType] = _("Enhanced Metafile");
133         typeNames[Clipboard::WmfGraphicsType] = _("Windows Metafile");
134         typeNames[Clipboard::LinkBackGraphicsType] = _("LinkBack PDF");
135         typeNames[Clipboard::PdfGraphicsType] = _("PDF");
136         typeNames[Clipboard::PngGraphicsType] = _("PNG");
137         typeNames[Clipboard::JpegGraphicsType] = _("JPEG");
138         
139         // find unused filename with primary extension
140         string document_path = cur.buffer().fileName().onlyPath().absFilename();
141         unsigned newfile_number = 0;
142         FileName filename;
143         do {
144                 ++newfile_number;
145                 filename = FileName(addName(document_path,
146                         to_utf8(_("pasted"))
147                         + convert<string>(newfile_number) + "."
148                         + extensions[type]));
149         } while (filename.isReadableFile());
150         
151         while (true) {
152                 // create file type filter, putting the prefered on to the front
153                 QStringList filter;
154                 for (size_t i = 0; i != types.size(); ++i) {
155                         docstring s = bformat(_("%1$s Files"), typeNames[types[i]])
156                                 + " (*." + from_ascii(extensions[types[i]]) + ")";
157                         if (types[i] == type)
158                                 filter.prepend(toqstr(s));
159                         else
160                                 filter.append(toqstr(s));
161                 }
162                 filter = fileFilters(filter.join(";;"));
163                 
164                 // show save dialog for the graphic
165                 FileDialog dlg(qt_("Choose a filename to save the pasted graphic as"));
166                 FileDialog::Result result =
167                 dlg.save(toqstr(filename.onlyPath().absFilename()), filter,
168                          toqstr(filename.onlyFileName()));
169                 
170                 if (result.first == FileDialog::Later)
171                         return FileName();
172                 
173                 string newFilename = fromqstr(result.second);
174                 if (newFilename.empty()) {
175                         cur.bv().message(_("Canceled."));
176                         return FileName();
177                 }
178                 filename.set(newFilename);
179                 
180                 // check the extension (the user could have changed it)
181                 if (!suffixIs(ascii_lowercase(filename.absFilename()),
182                               "." + extensions[type])) {
183                         // the user changed the extension. Check if the type is available
184                         size_t i;
185                         for (i = 1; i != types.size(); ++i) {
186                                 if (suffixIs(ascii_lowercase(filename.absFilename()),
187                                              "." + extensions[types[i]])) {
188                                         type = types[i];
189                                         break;
190                                 }
191                         }
192                         
193                         // invalid extension found, or none at all. In the latter
194                         // case set the default extensions.
195                         if (i == types.size()
196                             && filename.onlyFileName().find('.') == string::npos) {
197                                 filename.changeExtension("." + extensions[type]);
198                         }
199                 }
200                 
201                 // check whether the file exists and warn the user
202                 if (!filename.exists())
203                         break;
204                 int ret = frontend::Alert::prompt(
205                         _("Overwrite external file?"),
206                         bformat(_("File %1$s already exists, do you want to overwrite it?"),
207                         from_utf8(filename.absFilename())), 1, 1, _("&Overwrite"), _("&Cancel"));
208                 if (ret == 0)
209                         // overwrite, hence break the dialog loop
210                         break;
211                 
212                 // not overwrite, hence show the dialog again (i.e. loop)
213         }
214         
215         return filename;
216 }
217
218
219 FileName GuiClipboard::getAsGraphics(Cursor const & cur, GraphicsType type) const
220 {
221         // get the filename from the user
222         FileName filename = getPastedGraphicsFileName(cur, type);
223         if (filename.empty())
224                 return FileName();
225
226         // handle image cases first
227         if (type == PngGraphicsType || type == JpegGraphicsType) {
228                 // get image from QImage from clipboard
229                 QImage image = qApp->clipboard()->image();
230                 if (image.isNull()) {
231                         LYXERR(Debug::ACTION, "No image in clipboard");
232                         return FileName();
233                 }
234
235                 // convert into graphics format
236                 QByteArray ar;
237                 QBuffer buffer(&ar);
238                 buffer.open(QIODevice::WriteOnly);
239                 if (type == PngGraphicsType)
240                         image.save(toqstr(filename.absFilename()), "PNG");
241                 else if (type == JpegGraphicsType)
242                         image.save(toqstr(filename.absFilename()), "JPEG");
243                 else
244                         LASSERT(false, /**/);
245                 
246                 return filename;
247         }
248         
249         // get mime data
250         QMimeData const * source =
251         qApp->clipboard()->mimeData(QClipboard::Clipboard);
252         if (!source) {
253                 LYXERR(Debug::ACTION, "0 bytes (no QMimeData)");
254                 return FileName();
255         }
256         
257         // get mime for type
258         QString mime;
259         switch (type) {
260         case PdfGraphicsType: mime = pdf_mime_type; break;
261         case LinkBackGraphicsType: mime = pdf_mime_type; break;
262         case EmfGraphicsType: mime = emf_mime_type; break;
263         case WmfGraphicsType: mime = wmf_mime_type; break;
264         default: LASSERT(false, /**/);
265         }
266         
267         // get data
268         if (!source->hasFormat(mime))
269                 return FileName();
270         // data from ourself or some other LyX instance
271         QByteArray const ar = source->data(mime);
272         LYXERR(Debug::ACTION, "Getting from clipboard: mime = " << mime.data()
273                << "length = " << ar.count());
274         
275         QFile f(toqstr(filename.absFilename()));
276         if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
277                 LYXERR(Debug::ACTION, "Error opening file "
278                        << filename.absFilename() << " for writing");
279                 return FileName();
280         }
281         
282         // write the (LinkBack) PDF data
283         f.write(ar);
284         if (type == LinkBackGraphicsType) {
285 #ifdef Q_WS_MACX
286                 void const * linkBackData;
287                 unsigned linkBackLen;
288                 getLinkBackData(&linkBackData, &linkBackLen);
289                 f.write((char *)linkBackData, linkBackLen);
290                 quint32 pdfLen = ar.size();
291                 QDataStream ds(&f);
292                 ds << pdfLen; // big endian by default
293 #else
294                 // only non-Mac this should never happen
295                 LASSERT(false, /**/);
296 #endif // Q_WS_MACX
297         }
298
299         f.close();
300         return filename;
301 }
302
303
304 docstring const GuiClipboard::getAsText() const
305 {
306         // text data from other applications
307         QString const str = qApp->clipboard()->text(QClipboard::Clipboard)
308                                 .normalized(QString::NormalizationForm_C);
309         LYXERR(Debug::ACTION, "GuiClipboard::getAsText(): `" << str << "'");
310         if (str.isNull())
311                 return docstring();
312
313         return internalLineEnding(qstring_to_ucs4(str));
314 }
315
316
317 void GuiClipboard::put(string const & lyx, docstring const & text)
318 {
319         LYXERR(Debug::ACTION, "GuiClipboard::put(`" << lyx << "' `"
320                               << to_utf8(text) << "')");
321         // We don't convert the encoding of lyx since the encoding of the
322         // clipboard contents is specified in the data itself
323         QMimeData * data = new QMimeData;
324         if (!lyx.empty()) {
325                 QByteArray const qlyx(lyx.c_str(), lyx.size());
326                 data->setData(lyx_mime_type, qlyx);
327         }
328         // Don't test for text.empty() since we want to be able to clear the
329         // clipboard.
330         QString const qtext = toqstr(text);
331         data->setText(qtext);
332         qApp->clipboard()->setMimeData(data, QClipboard::Clipboard);
333 }
334
335
336 bool GuiClipboard::hasLyXContents() const
337 {
338         QMimeData const * const source =
339                 qApp->clipboard()->mimeData(QClipboard::Clipboard);
340         return source && source->hasFormat(lyx_mime_type);
341 }
342
343
344 bool GuiClipboard::hasGraphicsContents(Clipboard::GraphicsType type) const
345 {
346         if (type == AnyGraphicsType) {
347                 return hasGraphicsContents(PdfGraphicsType)
348                         || hasGraphicsContents(PngGraphicsType)
349                         || hasGraphicsContents(JpegGraphicsType)
350                         || hasGraphicsContents(EmfGraphicsType)
351                         || hasGraphicsContents(WmfGraphicsType)
352                         || hasGraphicsContents(LinkBackGraphicsType);
353         }
354
355         QMimeData const * const source =
356         qApp->clipboard()->mimeData(QClipboard::Clipboard);
357
358         // handle image cases first
359         if (type == PngGraphicsType || type == JpegGraphicsType)
360                 return source->hasImage();
361
362         // handle LinkBack for Mac
363         if (type == LinkBackGraphicsType)
364 #ifdef Q_WS_MACX
365                 return isLinkBackDataInPasteboard();
366 #else
367                 return false;
368 #endif // Q_WS_MACX
369         
370         // get mime data
371         QStringList const & formats = source->formats();
372         LYXERR(Debug::ACTION, "We found " << formats.size() << " formats");
373         for (int i = 0; i < formats.size(); ++i)
374                 LYXERR(Debug::ACTION, "Found format " << formats[i]);
375
376         // compute mime for type
377         QString mime;
378         switch (type) {
379         case EmfGraphicsType: mime = emf_mime_type; break;
380         case WmfGraphicsType: mime = wmf_mime_type; break;
381         case PdfGraphicsType: mime = pdf_mime_type; break;
382         default: LASSERT(false, /**/);
383         }
384         
385         return source && source->hasFormat(mime);
386 }
387
388
389 bool GuiClipboard::isInternal() const
390 {
391         // ownsClipboard() is also true for stuff coming from dialogs, e.g.
392         // the preamble dialog
393         // FIXME: This does only work on X11, since ownsClipboard() is
394         // hardwired to return false on Windows and OS X.
395         return qApp->clipboard()->ownsClipboard() && hasLyXContents();
396 }
397
398
399 bool GuiClipboard::hasInternal() const
400 {
401         // Windows and Mac OS X does not have the concept of ownership;
402         // the clipboard is a fully global resource so all applications 
403         // are notified of changes.
404 #if (defined(Q_WS_X11))
405         return true;
406 #else
407         return false;
408 #endif
409 }
410
411
412 void GuiClipboard::on_dataChanged()
413 {
414         QMimeData const * const source =
415         qApp->clipboard()->mimeData(QClipboard::Clipboard);
416         QStringList l = source->formats();
417         LYXERR(Debug::ACTION, "Qt Clipboard changed. We found the following mime types:");
418         for (int i = 0; i < l.count(); i++)
419                 LYXERR(Debug::ACTION, l.value(i));
420         
421         text_clipboard_empty_ = qApp->clipboard()->
422                 text(QClipboard::Clipboard).isEmpty();
423
424         has_lyx_contents_ = hasLyXContents();
425         has_graphics_contents_ = hasGraphicsContents();
426 }
427
428
429 bool GuiClipboard::empty() const
430 {
431         // We need to check both the plaintext and the LyX version of the
432         // clipboard. The plaintext version is empty if the LyX version
433         // contains only one inset, and the LyX version is empty if the
434         // clipboard does not come from LyX.
435         if (!text_clipboard_empty_)
436                 return false;
437         return !has_lyx_contents_ && !has_graphics_contents_;
438 }
439
440 } // namespace frontend
441 } // namespace lyx
442
443 #include "GuiClipboard_moc.cpp"