diff --git a/src/CutAndPaste.C b/src/CutAndPaste.C index d111dc8a85..5de1a5c6f0 100644 --- a/src/CutAndPaste.C +++ b/src/CutAndPaste.C @@ -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 diff --git a/src/CutAndPaste.h b/src/CutAndPaste.h index 2baaeb2ae8..a07d04c118 100644 --- a/src/CutAndPaste.h +++ b/src/CutAndPaste.h @@ -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); diff --git a/src/buffer.C b/src/buffer.C index 2cb481821f..6e93b04bbc 100644 --- a/src/buffer.C +++ b/src/buffer.C @@ -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. diff --git a/src/buffer.h b/src/buffer.h index 66f5f7901d..ac13950039 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -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; diff --git a/src/frontends/Clipboard.h b/src/frontends/Clipboard.h index 5ebde162da..deddf5b0c9 100644 --- a/src/frontends/Clipboard.h +++ b/src/frontends/Clipboard.h @@ -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; diff --git a/src/frontends/qt4/GuiClipboard.C b/src/frontends/qt4/GuiClipboard.C index 235a72986c..0f770319e5 100644 --- a/src/frontends/qt4/GuiClipboard.C +++ b/src/frontends/qt4/GuiClipboard.C @@ -19,6 +19,7 @@ #include #include +#include #include #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); } diff --git a/src/frontends/qt4/GuiClipboard.h b/src/frontends/qt4/GuiClipboard.h index efc705a33d..f167cc1f97 100644 --- a/src/frontends/qt4/GuiClipboard.h +++ b/src/frontends/qt4/GuiClipboard.h @@ -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; //@} diff --git a/src/insets/insettabular.C b/src/insets/insettabular.C index 6fa185b280..e0fd1c7fa0 100644 --- a/src/insets/insettabular.C +++ b/src/insets/insettabular.C @@ -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; diff --git a/src/mathed/InsetMathGrid.C b/src/mathed/InsetMathGrid.C index 86ba646d8a..9d0a5ddd3c 100644 --- a/src/mathed/InsetMathGrid.C +++ b/src/mathed/InsetMathGrid.C @@ -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; diff --git a/src/mathed/InsetMathNest.C b/src/mathed/InsetMathNest.C index 8dfcd7161a..00923f982e 100644 --- a/src/mathed/InsetMathNest.C +++ b/src/mathed/InsetMathNest.C @@ -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()); diff --git a/src/text3.C b/src/text3.C index cd5d7d0046..2e4f09c84e 100644 --- a/src/text3.C +++ b/src/text3.C @@ -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(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: