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