]> git.lyx.org Git - lyx.git/blobdiff - src/frontends/qt4/GuiClipboard.cpp
Move the buffer related part from GuiView::renameBuffer to Buffer::saveAs.
[lyx.git] / src / frontends / qt4 / GuiClipboard.cpp
index 188eafbbcefe545b306b4bfa2e7a3c66c1b8a879..297b828948e45612d0a50456fcfe8ddfe70f4526 100644 (file)
 
 #include <config.h>
 
+#include "FileDialog.h"
+
 #include "GuiClipboard.h"
 #include "qt_helpers.h"
 
+#include "Buffer.h"
+#include "BufferView.h"
+#include "Cursor.h"
+
+#include "support/lassert.h"
+#include "support/convert.h"
 #include "support/debug.h"
+#include "support/filetools.h"
+#include "support/gettext.h"
+#include "support/lstrings.h"
+#include "support/lyxtime.h"
+
+#ifdef Q_WS_MACX
+#include "support/linkback/LinkBackProxy.h"
+#endif // Q_WS_MACX
+
+#include "frontends/alert.h"
 
 #include <QApplication>
+#include <QBuffer>
 #include <QClipboard>
+#include <QDataStream>
+#include <QFile>
+#include <QImage>
 #include <QMimeData>
 #include <QString>
+#include <QStringList>
 
-#include "support/lstrings.h"
+#include <memory>
+#include <map>
+#include <iostream>
 
 using namespace std;
 using namespace lyx::support;
 
-static char const * const mime_type = "application/x-lyx";
 
 namespace lyx {
 
 namespace frontend {
 
+static QMimeData const * read_clipboard() 
+{
+       LYXERR(Debug::ACTION, "Getting Clipboard");
+       QMimeData const * source =
+               qApp->clipboard()->mimeData(QClipboard::Clipboard);
+       if (!source) {
+               LYXERR0("0 bytes (no QMimeData)");
+               return new QMimeData();
+       }
+       // It appears that doing IO between getting a mimeData object
+       // and using it can cause a crash (maybe Qt used IO
+       // as an excuse to free() it? Anyway let's not introduce
+       // any new IO here, so e.g. leave the following line commented.
+       // lyxerr << "Got Clipboard (" << (long) source << ")\n" ;
+       return source;
+}
+
+
+void CacheMimeData::update()
+{
+       time_t const start_time = current_time();
+       LYXERR(Debug::ACTION, "Creating CacheMimeData object");
+       cached_formats_ = read_clipboard()->formats();
+
+       // Qt times out after 5 seconds if it does not recieve a response.
+       if (current_time() - start_time > 3) {
+               LYXERR0("No timely response from clipboard, perhaps process "
+                       << "holding clipboard is frozen?");
+       }
+}
+
+
+QByteArray CacheMimeData::data(QString const & mimeType) const 
+{
+       return read_clipboard()->data(mimeType);
+}
+
+
+QString const lyxMimeType(){ return "application/x-lyx"; }
+QString const pdfMimeType(){ return "application/pdf"; }
+QString const emfMimeType(){ return "image/x-emf"; }
+QString const wmfMimeType(){ return "image/x-wmf"; }
+
+
 GuiClipboard::GuiClipboard()
 {
        connect(qApp->clipboard(), SIGNAL(dataChanged()),
@@ -47,15 +115,9 @@ string const GuiClipboard::getAsLyX() const
        LYXERR(Debug::ACTION, "GuiClipboard::getAsLyX(): `");
        // We don't convert encodings here since the encoding of the
        // clipboard contents is specified in the data itself
-       QMimeData const * source =
-               qApp->clipboard()->mimeData(QClipboard::Clipboard);
-       if (!source) {
-               LYXERR(Debug::ACTION, "' (no QMimeData)");
-               return string();
-       }
-       if (source->hasFormat(mime_type)) {
+       if (cache_.hasFormat(lyxMimeType())) {
                // data from ourself or some other LyX instance
-               QByteArray const ar = source->data(mime_type);
+               QByteArray const ar = cache_.data(lyxMimeType());
                string const s(ar.data(), ar.count());
                LYXERR(Debug::ACTION, s << "'");
                return s;
@@ -65,16 +127,215 @@ string const GuiClipboard::getAsLyX() const
 }
 
 
+FileName GuiClipboard::getPastedGraphicsFileName(Cursor const & cur,
+       Clipboard::GraphicsType & type) const
+{
+       // create file dialog filter according to the existing types in the clipboard
+       vector<Clipboard::GraphicsType> types;
+       if (hasGraphicsContents(Clipboard::EmfGraphicsType))
+               types.push_back(Clipboard::EmfGraphicsType);
+       if (hasGraphicsContents(Clipboard::WmfGraphicsType))
+               types.push_back(Clipboard::WmfGraphicsType);
+       if (hasGraphicsContents(Clipboard::LinkBackGraphicsType))
+               types.push_back(Clipboard::LinkBackGraphicsType);
+       if (hasGraphicsContents(Clipboard::PdfGraphicsType))
+               types.push_back(Clipboard::PdfGraphicsType);
+       if (hasGraphicsContents(Clipboard::PngGraphicsType))
+               types.push_back(Clipboard::PngGraphicsType);
+       if (hasGraphicsContents(Clipboard::JpegGraphicsType))
+               types.push_back(Clipboard::JpegGraphicsType);
+       
+       LASSERT(!types.empty(), /**/);
+       
+       // select prefered type if AnyGraphicsType was passed
+       if (type == Clipboard::AnyGraphicsType)
+               type = types.front();
+       
+       // which extension?
+       map<Clipboard::GraphicsType, string> extensions;
+       map<Clipboard::GraphicsType, docstring> typeNames;
+       
+       extensions[Clipboard::EmfGraphicsType] = "emf";
+       extensions[Clipboard::WmfGraphicsType] = "wmf";
+       extensions[Clipboard::LinkBackGraphicsType] = "linkback";
+       extensions[Clipboard::PdfGraphicsType] = "pdf";
+       extensions[Clipboard::PngGraphicsType] = "png";
+       extensions[Clipboard::JpegGraphicsType] = "jpeg";
+       
+       typeNames[Clipboard::EmfGraphicsType] = _("Enhanced Metafile");
+       typeNames[Clipboard::WmfGraphicsType] = _("Windows Metafile");
+       typeNames[Clipboard::LinkBackGraphicsType] = _("LinkBack PDF");
+       typeNames[Clipboard::PdfGraphicsType] = _("PDF");
+       typeNames[Clipboard::PngGraphicsType] = _("PNG");
+       typeNames[Clipboard::JpegGraphicsType] = _("JPEG");
+       
+       // find unused filename with primary extension
+       string document_path = cur.buffer()->fileName().onlyPath().absFileName();
+       unsigned newfile_number = 0;
+       FileName filename;
+       do {
+               ++newfile_number;
+               filename = FileName(addName(document_path,
+                       to_utf8(_("pasted"))
+                       + convert<string>(newfile_number) + "."
+                       + extensions[type]));
+       } while (filename.isReadableFile());
+       
+       while (true) {
+               // create file type filter, putting the prefered on to the front
+               QStringList filter;
+               for (size_t i = 0; i != types.size(); ++i) {
+                       docstring s = bformat(_("%1$s Files"), typeNames[types[i]])
+                               + " (*." + from_ascii(extensions[types[i]]) + ")";
+                       if (types[i] == type)
+                               filter.prepend(toqstr(s));
+                       else
+                               filter.append(toqstr(s));
+               }
+               filter = fileFilters(filter.join(";;"));
+               
+               // show save dialog for the graphic
+               FileDialog dlg(qt_("Choose a filename to save the pasted graphic as"));
+               FileDialog::Result result =
+               dlg.save(toqstr(filename.onlyPath().absFileName()), filter,
+                        toqstr(filename.onlyFileName()));
+               
+               if (result.first == FileDialog::Later)
+                       return FileName();
+               
+               string newFilename = fromqstr(result.second);
+               if (newFilename.empty()) {
+                       cur.bv().message(_("Canceled."));
+                       return FileName();
+               }
+               filename.set(newFilename);
+               
+               // check the extension (the user could have changed it)
+               if (!suffixIs(ascii_lowercase(filename.absFileName()),
+                             "." + extensions[type])) {
+                       // the user changed the extension. Check if the type is available
+                       size_t i;
+                       for (i = 1; i != types.size(); ++i) {
+                               if (suffixIs(ascii_lowercase(filename.absFileName()),
+                                            "." + extensions[types[i]])) {
+                                       type = types[i];
+                                       break;
+                               }
+                       }
+                       
+                       // invalid extension found, or none at all. In the latter
+                       // case set the default extensions.
+                       if (i == types.size()
+                           && filename.onlyFileName().find('.') == string::npos) {
+                               filename.changeExtension("." + extensions[type]);
+                       }
+               }
+               
+               // check whether the file exists and warn the user
+               if (!filename.exists())
+                       break;
+               int ret = frontend::Alert::prompt(
+                       _("Overwrite external file?"),
+                       bformat(_("File %1$s already exists, do you want to overwrite it?"),
+                       from_utf8(filename.absFileName())), 1, 1, _("&Overwrite"), _("&Cancel"));
+               if (ret == 0)
+                       // overwrite, hence break the dialog loop
+                       break;
+               
+               // not overwrite, hence show the dialog again (i.e. loop)
+       }
+       
+       return filename;
+}
+
+
+FileName GuiClipboard::getAsGraphics(Cursor const & cur, GraphicsType type) const
+{
+       // get the filename from the user
+       FileName filename = getPastedGraphicsFileName(cur, type);
+       if (filename.empty())
+               return FileName();
+
+       // handle image cases first
+       if (type == PngGraphicsType || type == JpegGraphicsType) {
+               // get image from QImage from clipboard
+               QImage image = qApp->clipboard()->image();
+               if (image.isNull()) {
+                       LYXERR(Debug::ACTION, "No image in clipboard");
+                       return FileName();
+               }
+
+               // convert into graphics format
+               QByteArray ar;
+               QBuffer buffer(&ar);
+               buffer.open(QIODevice::WriteOnly);
+               if (type == PngGraphicsType)
+                       image.save(toqstr(filename.absFileName()), "PNG");
+               else if (type == JpegGraphicsType)
+                       image.save(toqstr(filename.absFileName()), "JPEG");
+               else
+                       LASSERT(false, /**/);
+               
+               return filename;
+       }
+       
+       // get mime for type
+       QString mime;
+       switch (type) {
+       case PdfGraphicsType: mime = pdfMimeType(); break;
+       case LinkBackGraphicsType: mime = pdfMimeType(); break;
+       case EmfGraphicsType: mime = emfMimeType(); break;
+       case WmfGraphicsType: mime = wmfMimeType(); break;
+       default: LASSERT(false, /**/);
+       }
+       
+       // get data
+       if (!cache_.hasFormat(mime))
+               return FileName();
+       // data from ourself or some other LyX instance
+       QByteArray const ar = cache_.data(mime);
+       LYXERR(Debug::ACTION, "Getting from clipboard: mime = " << mime.data()
+              << "length = " << ar.count());
+       
+       QFile f(toqstr(filename.absFileName()));
+       if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
+               LYXERR(Debug::ACTION, "Error opening file "
+                      << filename.absFileName() << " for writing");
+               return FileName();
+       }
+       
+       // write the (LinkBack) PDF data
+       f.write(ar);
+       if (type == LinkBackGraphicsType) {
+#ifdef Q_WS_MACX
+               void const * linkBackData;
+               unsigned linkBackLen;
+               getLinkBackData(&linkBackData, &linkBackLen);
+               f.write((char *)linkBackData, linkBackLen);
+               quint32 pdfLen = ar.size();
+               QDataStream ds(&f);
+               ds << pdfLen; // big endian by default
+#else
+               // only non-Mac this should never happen
+               LASSERT(false, /**/);
+#endif // Q_WS_MACX
+       }
+
+       f.close();
+       return filename;
+}
+
+
 docstring const GuiClipboard::getAsText() const
 {
        // text data from other applications
        QString const str = qApp->clipboard()->text(QClipboard::Clipboard)
                                .normalized(QString::NormalizationForm_C);
-       LYXERR(Debug::ACTION, "GuiClipboard::getAsText(): `" << fromqstr(str) << "'");
+       LYXERR(Debug::ACTION, "GuiClipboard::getAsText(): `" << str << "'");
        if (str.isNull())
                return docstring();
 
-       return internalLineEnding(qstring_to_ucs4(str));
+       return internalLineEnding(str);
 }
 
 
@@ -87,7 +348,7 @@ void GuiClipboard::put(string const & lyx, docstring const & text)
        QMimeData * data = new QMimeData;
        if (!lyx.empty()) {
                QByteArray const qlyx(lyx.c_str(), lyx.size());
-               data->setData(mime_type, qlyx);
+               data->setData(lyxMimeType(), qlyx);
        }
        // Don't test for text.empty() since we want to be able to clear the
        // clipboard.
@@ -99,9 +360,55 @@ void GuiClipboard::put(string const & lyx, docstring const & text)
 
 bool GuiClipboard::hasLyXContents() const
 {
-       QMimeData const * const source =
-               qApp->clipboard()->mimeData(QClipboard::Clipboard);
-       return source && source->hasFormat(mime_type);
+       return cache_.hasFormat(lyxMimeType());
+}
+
+
+bool GuiClipboard::hasTextContents() const
+{
+       return cache_.hasText();
+}
+
+
+bool GuiClipboard::hasGraphicsContents(Clipboard::GraphicsType type) const
+{
+       if (type == AnyGraphicsType) {
+               return hasGraphicsContents(PdfGraphicsType)
+                       || hasGraphicsContents(PngGraphicsType)
+                       || hasGraphicsContents(JpegGraphicsType)
+                       || hasGraphicsContents(EmfGraphicsType)
+                       || hasGraphicsContents(WmfGraphicsType)
+                       || hasGraphicsContents(LinkBackGraphicsType);
+       }
+
+       // handle image cases first
+       if (type == PngGraphicsType || type == JpegGraphicsType)
+               return cache_.hasImage();
+
+       // handle LinkBack for Mac
+       if (type == LinkBackGraphicsType)
+#ifdef Q_WS_MACX
+               return isLinkBackDataInPasteboard();
+#else
+               return false;
+#endif // Q_WS_MACX
+       
+       // get mime data
+       QStringList const & formats = cache_.formats();
+       LYXERR(Debug::ACTION, "We found " << formats.size() << " formats");
+       for (int i = 0; i < formats.size(); ++i)
+               LYXERR(Debug::ACTION, "Found format " << formats[i]);
+
+       // compute mime for type
+       QString mime;
+       switch (type) {
+       case EmfGraphicsType: mime = emfMimeType(); break;
+       case WmfGraphicsType: mime = wmfMimeType(); break;
+       case PdfGraphicsType: mime = pdfMimeType(); break;
+       default: LASSERT(false, /**/);
+       }
+       
+       return cache_.hasFormat(mime);
 }
 
 
@@ -115,12 +422,38 @@ bool GuiClipboard::isInternal() const
 }
 
 
+bool GuiClipboard::hasInternal() const
+{
+       // Windows and Mac OS X does not have the concept of ownership;
+       // the clipboard is a fully global resource so all applications 
+       // are notified of changes.
+#if (defined(Q_WS_X11))
+       return true;
+#else
+       return false;
+#endif
+}
+
+
 void GuiClipboard::on_dataChanged()
 {
+       //Note: we do not really need to run cache_.update() unless the
+       //data has been changed *and* the GuiClipboard has been queried.
+       //However if run cache_.update() the moment a process grabs the
+       //clipboard, the process holding the clipboard presumably won't
+       //yet be frozen, and so we won't need to wait 5 seconds for Qt
+       //to time-out waiting for the clipboard.
+       cache_.update();
+       QStringList l = cache_.formats();
+       LYXERR(Debug::ACTION, "Qt Clipboard changed. We found the following mime types:");
+       for (int i = 0; i < l.count(); i++)
+               LYXERR(Debug::ACTION, l.value(i));
+       
        text_clipboard_empty_ = qApp->clipboard()->
                text(QClipboard::Clipboard).isEmpty();
 
        has_lyx_contents_ = hasLyXContents();
+       has_graphics_contents_ = hasGraphicsContents();
 }
 
 
@@ -132,10 +465,10 @@ bool GuiClipboard::empty() const
        // clipboard does not come from LyX.
        if (!text_clipboard_empty_)
                return false;
-       return !has_lyx_contents_;
+       return !has_lyx_contents_ && !has_graphics_contents_;
 }
 
 } // namespace frontend
 } // namespace lyx
 
-#include "GuiClipboard_moc.cpp"
+#include "moc_GuiClipboard.cpp"