Fix bug 2138: copy and paste should preserve formatting between different

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
This commit is contained in:
Georg Baum 2007-01-13 18:29:50 +00:00
parent e17b39d453
commit 3500af60ba
11 changed files with 235 additions and 43 deletions

View 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

View 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);

View 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.

View 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;

View 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;

View 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);
}

View 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;
//@}

View 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;

View 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;

View 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());

View 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: