]> git.lyx.org Git - features.git/commitdiff
Fix bug 2138: copy and paste should preserve formatting between different
authorGeorg Baum <Georg.Baum@post.rwth-aachen.de>
Sat, 13 Jan 2007 18:29:50 +0000 (18:29 +0000)
committerGeorg Baum <Georg.Baum@post.rwth-aachen.de>
Sat, 13 Jan 2007 18:29:50 +0000 (18:29 +0000)
LyX instances. This re-enables copy/paste from the internal clipboard on
OS X (currently broken since Clipboard::isInternal() always returns false for
some reason).

* src/insets/insettabular.C
(InsetTabular::doDispatch): adjust to clipboard interface change
(InsetTabular::copySelection): ditto

* src/mathed/InsetMathGrid.C
(InsetMathGrid::doDispatch): ditto

* src/mathed/InsetMathNest.C
(InsetMathNest::doDispatch): ditto

* src/buffer.[Ch]
(Buffer::readString): New method: Read document from a string
(Buffer::readFile): Change return value from bool to enum (needed
for readString). Return wrongversion if we are reading from a string
and the version does not match.
(Buffer::do_writeFile): make public and rename to write

* src/CutAndPaste.C
(putClipboard): New helper, put stuff to the system clipboard
(void copySelectionHelper): Use putClipboard instead of
theClipboard().put()
(void copySelection): ditto
(void pasteClipboard): new method for pasting in text
(void pasteParagraphList):

* src/frontends/Clipboard.h
(Clipboard::get): Rename to getAsText
(Clipboard::getAsLyX): New method for getting the system clipboard
in LyX format
(Clipboard::hasLyXContents): New method telling whether there is LyX
contents in the clipboard

* src/frontends/qt4/GuiClipboard.[Ch]: Implement the new methods

* src/text3.C
(LyXText::dispatch): Use pasteClipboard for pasting the system
clipboard

git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@16669 a592a061-630c-0410-9148-cb99ea01b6c8

src/CutAndPaste.C
src/CutAndPaste.h
src/buffer.C
src/buffer.h
src/frontends/Clipboard.h
src/frontends/qt4/GuiClipboard.C
src/frontends/qt4/GuiClipboard.h
src/insets/insettabular.C
src/mathed/InsetMathGrid.C
src/mathed/InsetMathNest.C
src/text3.C

index d111dc8a852dbfd7c301fe633ffb6fceac808dbc..5de1a5c6f0ece8a508e7c0dcee654b5558adf0f2 100644 (file)
@@ -26,6 +26,7 @@
 #include "insetiterator.h"
 #include "language.h"
 #include "lfuns.h"
+#include "lyxfunc.h"
 #include "lyxrc.h"
 #include "lyxtext.h"
 #include "lyxtextclasslist.h"
@@ -324,6 +325,21 @@ PitPosPair eraseSelectionHelper(BufferParams const & params,
 }
 
 
+void putClipboard(ParagraphList const & paragraphs, textclass_type textclass,
+                  docstring const & plaintext)
+{
+       Buffer buffer(string(), false);
+       buffer.setUnnamed(true);
+       buffer.paragraphs() = paragraphs;
+       buffer.params().textclass = textclass;
+       std::ostringstream lyx;
+       if (buffer.write(lyx))
+               theClipboard().put(lyx.str(), plaintext);
+       else
+               theClipboard().put(string(), plaintext);
+}
+
+
 void copySelectionHelper(Buffer const & buf, ParagraphList & pars,
        pit_type startpit, pit_type endpit,
        int start, int end, textclass_type tc)
@@ -493,9 +509,6 @@ void cutSelection(LCursor & cur, bool doclear, bool realcut)
        if (cur.inTexted()) {
                LyXText * text = cur.text();
                BOOST_ASSERT(text);
-               // Stuff what we got on the clipboard. Even if there is no selection.
-               if (realcut)
-                       theClipboard().put(cur.selectionAsString(true));
 
                // make sure that the depth behind the selection are restored, too
                recordUndoSelection(cur);
@@ -511,6 +524,10 @@ void cutSelection(LCursor & cur, bool doclear, bool realcut)
                                begpit, endpit,
                                cur.selBegin().pos(), endpos,
                                bp.textclass);
+                       // Stuff what we got on the clipboard.
+                       // Even if there is no selection.
+                       putClipboard(theCuts[0].first, theCuts[0].second,
+                               cur.selectionAsString(true));
                }
 
                boost::tie(endpit, endpos) =
@@ -558,10 +575,16 @@ void cutSelection(LCursor & cur, bool doclear, bool realcut)
 
 void copySelection(LCursor & cur)
 {
-       // stuff the selection onto the X clipboard, from an explicit copy request
-       theClipboard().put(cur.selectionAsString(true));
+       copySelection(cur, cur.selectionAsString(true));
+}
+
 
+void copySelection(LCursor & cur, docstring const & plaintext)
+{
        copySelectionToStack(cur);
+
+       // stuff the selection onto the X clipboard, from an explicit copy request
+       putClipboard(theCuts[0].first, theCuts[0].second, plaintext);
 }
 
 
@@ -636,6 +659,42 @@ void pasteParagraphList(LCursor & cur, ParagraphList const & parlist,
 }
 
 
+void pasteClipboard(LCursor & cur, ErrorList & errorList, bool asParagraphs)
+{
+       // Use internal clipboard if it is the most recent one
+       if (theClipboard().isInternal()) {
+               pasteSelection(cur, errorList, 0);
+               return;
+       }
+
+       // First try LyX format
+       if (theClipboard().hasLyXContents()) {
+               string lyx = theClipboard().getAsLyX();
+               if (!lyx.empty()) {
+                       Buffer buffer(string(), false);
+                       buffer.setUnnamed(true);
+                       if (buffer.readString(lyx)) {
+                               recordUndo(cur);
+                               pasteParagraphList(cur, buffer.paragraphs(),
+                                       buffer.params().textclass, errorList);
+                               cur.setSelection();
+                               return;
+                       }
+               }
+       }
+
+       // Then try plain text
+       docstring const text = theClipboard().getAsText();
+       if (text.empty())
+               return;
+       recordUndo(cur);
+       if (asParagraphs)
+               cur.text()->insertStringAsParagraphs(cur, text);
+       else
+               cur.text()->insertStringAsLines(cur, text);
+}
+
+
 void pasteSelection(LCursor & cur, ErrorList & errorList, size_t sel_index)
 {
        // this does not make sense, if there is nothing to paste
index 2baaeb2ae8c88a6fef2423ccafaab52a1e05d3f1..a07d04c11810967fe104e34c2e258c87076b3c38 100644 (file)
@@ -62,9 +62,19 @@ void replaceSelection(LCursor & cur);
 void cutSelection(LCursor & cur, bool doclear = true, bool realcut = true);
 /// Push the current selection to the cut buffer and the system clipboard.
 void copySelection(LCursor & cur);
+/**
+ * Push the current selection to the cut buffer and the system clipboard.
+ * \param plaintext plain text version of the selection for the system
+ *        clipboard
+ */
+void copySelection(LCursor & cur, docstring const & plaintext);
 /// Push the current selection to the cut buffer.
 void copySelectionToStack(LCursor & cur);
-/// Paste the sel_index-th element of the cut buffer.
+/// Replace the current selection with the clipboard contents (internal or
+/// external: which is newer)
+/// Does handle undo. Does only work in text, not mathed.
+void pasteClipboard(LCursor & cur, ErrorList & errorList, bool asParagraphs = true);
+/// Replace the current selection with cut buffer \c sel_index
 /// Does handle undo. Does only work in text, not mathed.
 void pasteSelection(LCursor & cur, ErrorList &, size_t sel_index = 0);
 
index 2cb481821f2868eef2ea604f7bbd91f4566acc7a..6e93b04bbcd38b3461f5f5d243b61bf2382e5580 100644 (file)
@@ -566,6 +566,39 @@ void Buffer::insertStringAsLines(ParagraphList & pars,
 }
 
 
+bool Buffer::readString(std::string const & s)
+{
+       params().compressed = false;
+
+       // remove dummy empty par
+       paragraphs().clear();
+       LyXLex lex(0, 0);
+       std::istringstream is(s);
+       lex.setStream(is);
+       FileName const name(tempName());
+       switch (readFile(lex, name)) {
+       case failure:
+               return false;
+       case wrongversion: {
+               // We need to call lyx2lyx, so write the input to a file
+               std::ofstream os(name.toFilesystemEncoding().c_str());
+               os << s;
+               os.close();
+               return readFile(name) == success;
+       }
+       case success:
+               break;
+       }
+
+       // After we have read a file, we must ensure that the buffer
+       // language is set and used in the gui.
+       // If you know of a better place to put this, please tell me. (Lgb)
+       updateDocLang(params().language);
+
+       return true;
+}
+
+
 bool Buffer::readFile(FileName const & filename)
 {
        // Check if the file is compressed.
@@ -578,7 +611,7 @@ bool Buffer::readFile(FileName const & filename)
        paragraphs().clear();
        LyXLex lex(0, 0);
        lex.setFile(filename);
-       if (!readFile(lex, filename))
+       if (readFile(lex, filename) != success)
                return false;
 
        // After we have read a file, we must ensure that the buffer
@@ -602,14 +635,15 @@ void Buffer::fully_loaded(bool const value)
 }
 
 
-bool Buffer::readFile(LyXLex & lex, FileName const & filename)
+Buffer::ReadStatus Buffer::readFile(LyXLex & lex, FileName const & filename,
+               bool fromstring)
 {
        BOOST_ASSERT(!filename.empty());
 
        if (!lex.isOK()) {
                Alert::error(_("Document could not be read"),
                             bformat(_("%1$s could not be read."), from_utf8(filename.absFilename())));
-               return false;
+               return failure;
        }
 
        lex.next();
@@ -618,7 +652,7 @@ bool Buffer::readFile(LyXLex & lex, FileName const & filename)
        if (!lex.isOK()) {
                Alert::error(_("Document could not be read"),
                             bformat(_("%1$s could not be read."), from_utf8(filename.absFilename())));
-               return false;
+               return failure;
        }
 
        // the first token _must_ be...
@@ -628,7 +662,7 @@ bool Buffer::readFile(LyXLex & lex, FileName const & filename)
                Alert::error(_("Document format failure"),
                             bformat(_("%1$s is not a LyX document."),
                                       from_utf8(filename.absFilename())));
-               return false;
+               return failure;
        }
 
        lex.next();
@@ -643,6 +677,11 @@ bool Buffer::readFile(LyXLex & lex, FileName const & filename)
        //lyxerr << "format: " << file_format << endl;
 
        if (file_format != LYX_FORMAT) {
+
+               if (fromstring)
+                       // lyx2lyx would fail
+                       return wrongversion;
+
                FileName const tmpfile(tempName());
                if (tmpfile.empty()) {
                        Alert::error(_("Conversion failed"),
@@ -651,7 +690,7 @@ bool Buffer::readFile(LyXLex & lex, FileName const & filename)
                                              " file for converting it could"
                                                            " not be created."),
                                              from_utf8(filename.absFilename())));
-                       return false;
+                       return failure;
                }
                FileName const lyx2lyx = libFileSearch("lyx2lyx", "lyx2lyx");
                if (lyx2lyx.empty()) {
@@ -661,7 +700,7 @@ bool Buffer::readFile(LyXLex & lex, FileName const & filename)
                                               " conversion script lyx2lyx"
                                                            " could not be found."),
                                               from_utf8(filename.absFilename())));
-                       return false;
+                       return failure;
                }
                ostringstream command;
                command << os::python()
@@ -682,11 +721,11 @@ bool Buffer::readFile(LyXLex & lex, FileName const & filename)
                                              " of LyX, but the lyx2lyx script"
                                                            " failed to convert it."),
                                              from_utf8(filename.absFilename())));
-                       return false;
+                       return failure;
                } else {
                        bool const ret = readFile(tmpfile);
                        // Do stuff with tmpfile name and buffer name here.
-                       return ret;
+                       return ret ? success : failure;
                }
 
        }
@@ -703,7 +742,7 @@ bool Buffer::readFile(LyXLex & lex, FileName const & filename)
        //MacroTable::localMacros().clear();
 
        pimpl_->file_fully_loaded = true;
-       return true;
+       return success;
 }
 
 
@@ -763,20 +802,20 @@ bool Buffer::writeFile(FileName const & fname) const
                if (!ofs)
                        return false;
 
-               retval = do_writeFile(ofs);
+               retval = write(ofs);
        } else {
                ofstream ofs(fname.toFilesystemEncoding().c_str(), ios::out|ios::trunc);
                if (!ofs)
                        return false;
 
-               retval = do_writeFile(ofs);
+               retval = write(ofs);
        }
 
        return retval;
 }
 
 
-bool Buffer::do_writeFile(ostream & ofs) const
+bool Buffer::write(ostream & ofs) const
 {
 #ifdef HAVE_LOCALE
        // Use the standard "C" locale for file output.
index 66f5f7901d46b9f4e29846165a076536849d8a3a..ac1395003909f49c7b4390e114c8afc23f4d09e1 100644 (file)
@@ -78,6 +78,13 @@ public:
                buildlog  ///< Literate build log
        };
 
+       /// Result of \c readFile()
+       enum ReadStatus {
+               failure, ///< The file could not be read
+               success, ///< The file could not be read
+               wrongversion ///< The version of the file does not match ours
+       };
+
        /** Constructor
            \param file
            \param b  optional \c false by default
@@ -98,6 +105,8 @@ public:
        /// Load the autosaved file.
        void loadAutoSaveFile();
 
+       /// read a new document from a string
+       bool readString(std::string const &);
        /// load a new file
        bool readFile(support::FileName const & filename);
 
@@ -143,6 +152,8 @@ public:
        */
        bool save() const;
 
+       /// Write document to stream. Returns \c false if unsuccesful.
+       bool write(std::ostream &) const;
        /// Write file. Returns \c false if unsuccesful.
        bool writeFile(support::FileName const &) const;
 
@@ -386,9 +397,8 @@ private:
        /** Inserts a file into a document
            \return \c false if method fails.
        */
-       bool readFile(LyXLex &, support::FileName const & filename);
-
-       bool do_writeFile(std::ostream & ofs) const;
+       ReadStatus readFile(LyXLex &, support::FileName const & filename,
+                           bool fromString = false);
 
        /// Use the Pimpl idiom to hide the internals.
        class Impl;
index 5ebde162daa695483134974e3bde1c4bf84765e5..deddf5b0c98e72e914528b7907a94652af86d332 100644 (file)
@@ -28,18 +28,29 @@ public:
        virtual ~Clipboard() {}
 
        /**
-        * Get the window system clipboard contents.
+        * Get the system clipboard contents. The format is as written in
+        * .lyx files (may even be an older version than ours if it comes
+        * from an older LyX).
+        * Does not convert plain text to LyX if only plain text is available.
         * This should be called when the user requests to paste from the
         * clipboard.
         */
-       virtual docstring const get() const = 0;
+       virtual std::string const getAsLyX() const = 0;
+       /// Get the contents of the window system clipboard in plain text format.
+       virtual docstring const getAsText() const = 0;
        /**
-        * Fill the window system clipboard.
+        * Fill the system clipboard. The format of \p lyx is as written in
+        * .lyx files, the format of \p text is plain text.
+        * We put the clipboard contents in LyX format and plain text into
+        * the system clipboard if supported, so that it is useful for other
+        * applications as well as other instances of LyX.
         * This should be called when the user requests to cut or copy to
         * the clipboard.
         */
-       virtual void put(docstring const &) = 0;
+       virtual void put(std::string const & lyx, docstring const & text) = 0;
 
+       /// Does the clipboard contain LyX contents?
+       virtual bool hasLyXContents() const = 0;
        /// state of clipboard.
        /// \retval true if the system clipboard has been set within LyX.
        virtual bool isInternal() const = 0;
index 235a72986ced3f637abaf171a363ba3387d28dcd..0f770319e5cc04198528b7a1d28b960475e55904 100644 (file)
@@ -19,6 +19,7 @@
 
 #include <QApplication>
 #include <QClipboard>
+#include <QMimeData>
 #include <QString>
 
 #include "support/lstrings.h"
@@ -26,15 +27,50 @@ using lyx::support::internalLineEnding;
 using lyx::support::externalLineEnding;
 
 using std::endl;
+using std::string;
+
+
+namespace {
+
+char const * const mime_type = "application/x-lyx";
+
+}
+
 
 namespace lyx {
 namespace frontend {
 
-docstring const GuiClipboard::get() const
+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)" << endl;
+               return string();
+       }
+       if (source->hasFormat(mime_type)) {
+               // data from ourself or some other LyX instance
+               QByteArray const ar = source->data(mime_type);
+               string const s(ar.data(), ar.count());
+               if (lyxerr.debugging(Debug::ACTION))
+                       lyxerr[Debug::ACTION] << s << "'" << endl;
+               return s;
+       }
+       lyxerr[Debug::ACTION] << "'" << endl;
+       return string();
+}
+
+
+docstring const GuiClipboard::getAsText() const
+{
+       // text data from other applications
        QString const str = qApp->clipboard()->text(QClipboard::Clipboard);
-       lyxerr[Debug::ACTION] << "GuiClipboard::get: " << fromqstr(str)
-                             << endl;
+       if (lyxerr.debugging(Debug::ACTION))
+               lyxerr[Debug::ACTION] << "GuiClipboard::getAsText(): `"
+                                     << fromqstr(str) << "'" << endl;
        if (str.isNull())
                return docstring();
 
@@ -42,12 +78,31 @@ docstring const GuiClipboard::get() const
 }
 
 
-void GuiClipboard::put(docstring const & str)
+void GuiClipboard::put(string const & lyx, docstring const & text)
 {
-       lyxerr[Debug::ACTION] << "GuiClipboard::put: " << lyx::to_utf8(str) << endl;
+       if (lyxerr.debugging(Debug::ACTION))
+               lyxerr[Debug::ACTION] << "GuiClipboard::put(`" << lyx << "' `"
+                                     << to_utf8(text) << "')" << endl;
+       // We don't convert the encoding of lyx since the encoding of the
+       // clipboard contents is specified in the data itself
+       QMimeData * data = new QMimeData;
+       if (!lyx.empty()) {
+               QByteArray const qlyx(lyx.c_str(), lyx.size());
+               data->setData(mime_type, qlyx);
+       }
+       // Don't test for text.empty() since we want to be able to clear the
+       // clipboard.
+       QString const qtext = toqstr(text);
+       data->setText(qtext);
+       qApp->clipboard()->setMimeData(data, QClipboard::Clipboard);
+}
+
 
-       qApp->clipboard()->setText(toqstr(externalLineEnding(str)),
-                                  QClipboard::Clipboard);
+bool GuiClipboard::hasLyXContents() const
+{
+       QMimeData const * const source =
+               qApp->clipboard()->mimeData(QClipboard::Clipboard);
+       return source && source->hasFormat(mime_type);
 }
 
 
index efc705a33d0b69939a0d5d8b2a2ec6680e2eeee5..f167cc1f971e2a1ebd1f895079b7626ec7a02944 100644 (file)
@@ -30,8 +30,10 @@ public:
        /** Clipboard overloaded methods
         */
        //@{
-       docstring const get() const;
-       void put(docstring const & str);
+       std::string const getAsLyX() const;
+       docstring const getAsText() const;
+       void put(std::string const & lyx, docstring const & text);
+       bool hasLyXContents() const;
        bool isInternal() const;
        bool empty() const;
        //@}
index 6fa185b280b4e28c8cf5d4962ef4e0b3b04232ea..e0fd1c7fa02bebb7be2eae93a95cc7a1b818f50e 100644 (file)
@@ -723,7 +723,7 @@ void InsetTabular::doDispatch(LCursor & cur, FuncRequest & cmd)
        case LFUN_CLIPBOARD_PASTE:
        case LFUN_PRIMARY_SELECTION_PASTE: {
                docstring const clip = (cmd.action == LFUN_CLIPBOARD_PASTE) ?
-                       theClipboard().get() :
+                       theClipboard().getAsText() :
                        theSelection().get();
                if (clip.empty())
                        break;
@@ -1814,10 +1814,13 @@ bool InsetTabular::copySelection(LCursor & cur)
        odocstringstream os;
        OutputParams const runparams;
        paste_tabular->plaintext(cur.buffer(), os, runparams, 0, true, '\t');
-       theClipboard().put(os.str());
+       // Needed for the "Edit->Paste recent" menu and the system clipboard.
+       cap::copySelection(cur, os.str());
+
        // mark tabular stack dirty
        // FIXME: this is a workaround for bug 1919. Should be removed for 1.5,
        // when we (hopefully) have a one-for-all paste mechanism.
+       // This must be called after cap::copySelection.
        dirtyTabularStack(true);
 
        return true;
index 86ba646d8aba586a898b53b76d83e0fed1fa742c..9d0a5ddd3c9cf409870b0916413ad06506441ca1 100644 (file)
@@ -1213,7 +1213,7 @@ void InsetMathGrid::doDispatch(LCursor & cur, FuncRequest & cmd)
                cap::replaceSelection(cur);
                docstring topaste;
                if (cmd.argument().empty() && !theClipboard().isInternal())
-                       topaste = theClipboard().get();
+                       topaste = theClipboard().getAsText();
                else {
                        idocstringstream is(cmd.argument());
                        int n = 0;
index 8dfcd7161ad61a9c904303a4791377f69cdb828d..00923f982ed5143760fef2904fc74f6e3e63fa76 100644 (file)
@@ -440,7 +440,7 @@ void InsetMathNest::doDispatch(LCursor & cur, FuncRequest & cmd)
                replaceSelection(cur);
                docstring topaste;
                if (cmd.argument().empty() && !theClipboard().isInternal())
-                       topaste = theClipboard().get();
+                       topaste = theClipboard().getAsText();
                else {
                        size_t n = 0;
                        idocstringstream is(cmd.argument());
index cd5d7d0046e30cfc795950a6c00f1be6b6c7e0fc..2e4f09c84e17c13c698aeddd6b885c3eb8283e55 100644 (file)
@@ -76,6 +76,7 @@ namespace lyx {
 
 using cap::copySelection;
 using cap::cutSelection;
+using cap::pasteClipboard;
 using cap::pasteSelection;
 using cap::replaceSelection;
 
@@ -758,15 +759,15 @@ void LyXText::dispatch(LCursor & cur, FuncRequest & cmd)
                cur.message(_("Paste"));
                cap::replaceSelection(cur);
                if (cmd.argument().empty() && !theClipboard().isInternal())
-                       pasteString(cur, theClipboard().get(), true);
+                       pasteClipboard(cur, bv->buffer()->errorList("Paste"));
                else {
                        string const arg(to_utf8(cmd.argument()));
                        pasteSelection(cur, bv->buffer()->errorList("Paste"),
                                        isStrUnsignedInt(arg) ?
                                                convert<unsigned int>(arg) :
                                                0);
-                       bv->buffer()->errors("Paste");
                }
+               bv->buffer()->errors("Paste");
                cur.clearSelection(); // bug 393
                bv->switchKeyMap();
                finishUndo();
@@ -865,8 +866,10 @@ void LyXText::dispatch(LCursor & cur, FuncRequest & cmd)
        }
 
        case LFUN_CLIPBOARD_PASTE:
-               pasteString(cur, theClipboard().get(),
-                           cmd.argument() == "paragraph");
+               cur.clearSelection();
+               pasteClipboard(cur, bv->buffer()->errorList("Paste"),
+                              cmd.argument() == "paragraph");
+               bv->buffer()->errors("Paste");
                break;
 
        case LFUN_PRIMARY_SELECTION_PASTE: