This is a patch that was rotting in trac for ages, and I don't want it to
miss another major release. It improves both copying to and pasting from the
system clipboard.
paste: In addition to plain text and LyX format, both HTML and LaTeX are
recognized on the clipboard. Since I did not find any application that puts
LaTeX on the clipboard using the correct MIME type, I had to try to
interpret plain text as LaTeX in order to make the patch useful.
copy: Now the contents is not only copied as plain text and in LyX format,
but also as HTML. This is useful for applications that understand HTML in
the clipboard, e.g. all major office packages. I could also add LaTeX to
make it fully symmetrical to pasting, but I did not do that since I did not
find any application that deals with the MIME types text/x-tex or
application/x-latex on the clipboard correctly.
There are three (minor IMHO) drawbacks:
1) The LaTeX detection for pasting is a heuristic and may fail in some
cases. If that happens, tex2lyx is run on text that is not really LaTeX, and
this may give unexpected results. If that happens, the user needs to paste
as unformatted text. However, I think that this imperfect behaviour is still
preferable over pasting LaTeX verbatim. I can also make the heuristic more
safe if that is desired. In that case it would miss some more valid LaTeX
snippets.
2) Bugs of HTML->LaTeX converters are made more visible, since they are used
if you paste from office programs or web browsers. For example,
gnuhtml2latex messes up the encoding, so that you get nice structure, but
wrong umlauts. I could work around this particular bug by a wrapper script
for gnuhtml2latex, or if you think that there are more bugs I could also
remove this feature from the patch.
3) You notice (a very small) delay if tex2lyx is run, but I believe that
this is mainly lyx2lyx (for some reason it does not run the correct tex2lyx
from the build directory, but the installed 2.0.x one).
Can this patch go in? What should be changed?
Georg
diff --git a/src/Buffer.cpp b/src/Buffer.cpp
index 5a834cf..8a2c513 100644
--- a/src/Buffer.cpp
+++ b/src/Buffer.cpp
@@ -978,6 +978,42 @@ bool Buffer::readDocument(Lexer & lex)
}
+bool Buffer::importString(string const & format, docstring const & contents, ErrorList & errorList)
+{
+ FileName const name(FileName::tempName("Buffer_importString"));
+ ofdocstream os(name.toFilesystemEncoding().c_str());
+ bool const success = (os << contents);
+ os.close();
+
+ bool converted = false;
+ if (success) {
+ params().compressed = false;
+
+ // remove dummy empty par
+ paragraphs().clear();
+
+ converted = importFile(format, name, errorList);
+ }
+
+ if (name.exists())
+ name.removeFile();
+ return converted;
+}
+
+
+bool Buffer::importFile(string const & format, FileName const & name, ErrorList & errorList)
+{
+ if (!theConverters().isReachable(format, "lyx"))
+ return false;
+
+ FileName const lyx(FileName::tempName("Buffer_importFile"));
+ if (theConverters().convert(0, name, lyx, name, format, "lyx", errorList))
+ return readFile(lyx) == ReadSuccess;
+
+ return false;
+}
+
+
bool Buffer::readString(string const & s)
{
params().compressed = false;
diff --git a/src/Buffer.h b/src/Buffer.h
index e30a09a..9535c12 100644
--- a/src/Buffer.h
+++ b/src/Buffer.h
@@ -222,6 +222,10 @@ public:
/// emergency or autosave files, one should use \c loadLyXFile.
/// /sa loadLyXFile
ReadStatus loadThisLyXFile(support::FileName const & fn);
+ /// import a new document from a string
+ bool importString(std::string const &, docstring const &, ErrorList &);
+ /// import a new file
+ bool importFile(std::string const &, support::FileName const &, ErrorList &);
/// read a new document from a string
bool readString(std::string const &);
/// Reloads the LyX file
diff --git a/src/CutAndPaste.cpp b/src/CutAndPaste.cpp
index 6534475..b6db758 100644
--- a/src/CutAndPaste.cpp
+++ b/src/CutAndPaste.cpp
@@ -23,6 +23,7 @@
#include "BufferView.h"
#include "Changes.h"
#include "Cursor.h"
+#include "Encoding.h"
#include "ErrorList.h"
#include "FuncCode.h"
#include "FuncRequest.h"
@@ -474,11 +475,14 @@ void putClipboard(ParagraphList const & paragraphs,
buffer->paragraphs() = paragraphs;
buffer->inset().setBuffer(*buffer);
buffer->params().setDocumentClass(docclass);
- ostringstream lyx;
- if (buffer->write(lyx))
- theClipboard().put(lyx.str(), plaintext);
- else
- theClipboard().put(string(), plaintext);
+ string lyx;
+ ostringstream oslyx;
+ if (buffer->write(oslyx))
+ lyx = oslyx.str();
+ odocstringstream oshtml;
+ OutputParams runparams(encodings.fromLyXName("utf8"));
+ buffer->writeLyXHTMLSource(oshtml, runparams, Buffer::FullSource);
+ theClipboard().put(lyx, oshtml.str(), plaintext);
// Save that memory
buffer->paragraphs().clear();
}
@@ -1015,7 +1019,7 @@ void pasteClipboardText(Cursor & cur, ErrorList & errorList, bool asParagraphs)
}
// First try LyX format
- if (theClipboard().hasLyXContents()) {
+ if (theClipboard().hasTextContents(Clipboard::LyXTextType)) {
string lyx = theClipboard().getAsLyX();
if (!lyx.empty()) {
// For some strange reason gcc 3.2 and 3.3 do not accept
@@ -1031,8 +1035,48 @@ void pasteClipboardText(Cursor & cur, ErrorList & errorList, bool asParagraphs)
}
}
+ // Then try TeX and HTML
+ Clipboard::TextType types[2] = {Clipboard::HtmlTextType, Clipboard::LaTeXTextType};
+ string names[2] = {"html", "latex"};
+ for (int i = 0; i < 2; ++i) {
+ Clipboard::TextType type = types[i];
+ bool available = theClipboard().hasTextContents(type);
+
+ // LaTeX is usually not marked with the proper MIME type,
+ // so try to interpret the plain text as LaTeX
+ if (!available && type == Clipboard::LaTeXTextType) {
+ type = Clipboard::PlainTextType;
+ available = theClipboard().hasTextContents(type);
+ }
+
+ if (available) {
+ docstring text = theClipboard().getAsText(type);
+ if (type == Clipboard::PlainTextType) {
+ // We don't really know if this is LaTeX stuff
+ // after all. Only run tex2lyx if we are pretty
+ // sure that it is, otherwise the user might
+ // get unexpected results.
+ if (formats.getFormatFromString(text) != names[i])
+ available = false;
+ } else
+ available = !text.empty();
+ if (available) {
+ // For some strange reason gcc 3.2 and 3.3 do not accept
+ // Buffer buffer(string(), false);
+ Buffer buffer("", false);
+ buffer.setUnnamed(true);
+ if (buffer.importString(names[i], text, errorList)) {
+ cur.recordUndo();
+ pasteParagraphList(cur, buffer.paragraphs(),
+ buffer.params().documentClassPtr(), errorList);
+ return;
+ }
+ }
+ }
+ }
+
// Then try plain text
- docstring const text = theClipboard().getAsText();
+ docstring const text = theClipboard().getAsText(Clipboard::PlainTextType);
if (text.empty())
return;
cur.recordUndo();
@@ -1061,7 +1105,7 @@ void pasteSimpleText(Cursor & cur, bool asParagraphs)
asParagraphs = false;
} else {
// Then try plain text
- text = theClipboard().getAsText();
+ text = theClipboard().getAsText(Clipboard::PlainTextType);
}
if (text.empty())
diff --git a/src/Format.cpp b/src/Format.cpp
index 8e31a1e..1918301 100644
--- a/src/Format.cpp
+++ b/src/Format.cpp
@@ -253,12 +253,11 @@ string guessFormatFromContents(FileName const & fn)
string str;
string format;
bool firstLine = true;
+ bool backslash = false;
+ int dollars = 0;
while ((count++ < max_count) && format.empty()) {
- if (ifs.eof()) {
- LYXERR(Debug::GRAPHICS, "filetools(getFormatFromContents)\n"
- << "\tFile type not recognised before EOF!");
+ if (ifs.eof())
break;
- }
getline(ifs, str);
string const stamp = str.substr(0, 2);
@@ -363,9 +362,32 @@ string guessFormatFromContents(FileName const & fn)
else if (contains(str, "BITPIX"))
format = "fits";
+
+ else if (contains(str, "\\documentclass") ||
+ contains(str, "\\chapter") ||
+ contains(str, "\\section") ||
+ contains(str, "\\begin") ||
+ contains(str, "\\end") ||
+ contains(str, "$$") ||
+ contains(str, "\\[") ||
+ contains(str, "\\]"))
+ format = "latex";
+ else {
+ if (contains(str, '\\'))
+ backslash = true;
+ dollars += count_char(str, '$');
+ }
}
- if (!format.empty()) {
+ if (format.empty() && backslash && dollars > 1)
+ // inline equation
+ format = "latex";
+
+ if (format.empty()) {
+ if (ifs.eof())
+ LYXERR(Debug::GRAPHICS, "filetools(getFormatFromContents)\n"
+ "\tFile type not recognised before EOF!");
+ } else {
LYXERR(Debug::GRAPHICS, "Recognised Fileformat: " << format);
return format;
}
@@ -433,6 +455,21 @@ string Formats::getFormatFromFile(FileName const & filename) const
}
+string Formats::getFormatFromString(docstring const & s) const
+{
+ FileName const name(FileName::tempName("getFormatFromString"));
+ ofdocstream os(name.toFilesystemEncoding().c_str());
+ bool const success = (os << s);
+ os.close();
+ string ret;
+ if (success)
+ ret = getFormatFromFile(name);
+ if (name.exists())
+ name.removeFile();
+ return ret;
+}
+
+
string Formats::getFormatFromExtension(string const & ext) const
{
if (!ext.empty()) {
diff --git a/src/Format.h b/src/Format.h
index c4c2a5e..4bd392c 100644
--- a/src/Format.h
+++ b/src/Format.h
@@ -154,6 +154,8 @@ public:
* string.
*/
std::string getFormatFromFile(support::FileName const & filename) const;
+ /// Guess the format of \p s
+ std::string getFormatFromString(docstring const & s) const;
/// Finds a format from a file extension. Returns string() if not found.
std::string getFormatFromExtension(std::string const & ext) const;
/// Finds a format by pretty name. Returns string() if not found.
diff --git a/src/frontends/Clipboard.h b/src/frontends/Clipboard.h
index 92e7167..33d80d8 100644
--- a/src/frontends/Clipboard.h
+++ b/src/frontends/Clipboard.h
@@ -42,6 +42,14 @@ public:
AnyGraphicsType
};
+ enum TextType {
+ AnyTextType,
+ PlainTextType,
+ HtmlTextType,
+ LaTeXTextType,
+ LyXTextType,
+ };
+
/**
* Get the system clipboard contents. The format is as written in
* .lyx files (may even be an older version than ours if it comes
@@ -51,11 +59,11 @@ public:
* clipboard.
*/
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;
+ /// Get the contents of the window system clipboard in any text format except LyxTextType.
+ virtual docstring const getAsText(TextType type) const = 0;
/// Get the contents of the window system clipboard as graphics file.
virtual FileName getAsGraphics(Cursor const & cur, GraphicsType type) const = 0;
-
+
/**
* Fill the system clipboard. The format of \p lyx is as written in
* .lyx files, the format of \p text is plain text.
@@ -65,12 +73,10 @@ public:
* This should be called when the user requests to cut or copy to
* the clipboard.
*/
- virtual void put(std::string const & lyx, docstring const & text) = 0;
+ virtual void put(std::string const & lyx, docstring const & html, docstring const & text) = 0;
- /// Does the clipboard contain LyX contents?
- virtual bool hasLyXContents() const = 0;
/// Does the clipboard contain text contents?
- virtual bool hasTextContents() const = 0;
+ virtual bool hasTextContents(TextType type = AnyTextType) const = 0;
/// Does the clipboard contain graphics contents of a certain type?
virtual bool hasGraphicsContents(GraphicsType type = AnyGraphicsType) const = 0;
/// state of clipboard.
diff --git a/src/frontends/qt4/GuiClipboard.cpp b/src/frontends/qt4/GuiClipboard.cpp
index 438feac..fe99706 100644
--- a/src/frontends/qt4/GuiClipboard.cpp
+++ b/src/frontends/qt4/GuiClipboard.cpp
@@ -44,6 +44,7 @@
#include <QMimeData>
#include <QString>
#include <QStringList>
+#include <QTextDocument>
#include <boost/crc.hpp>
@@ -98,6 +99,8 @@ QByteArray CacheMimeData::data(QString const & mimeType) const
QString const lyxMimeType(){ return "application/x-lyx"; }
+QString const texMimeType(){ return "text/x-tex"; }
+QString const latexMimeType(){ return "application/x-latex"; }
QString const pdfMimeType(){ return "application/pdf"; }
QString const emfMimeType(){ return "image/x-emf"; }
QString const wmfMimeType(){ return "image/x-wmf"; }
@@ -296,9 +299,9 @@ FileName GuiClipboard::getAsGraphics(Cursor const & cur, GraphicsType type) cons
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()
+ LYXERR(Debug::ACTION, "Getting from clipboard: mime = " << mime.constData()
<< "length = " << ar.count());
-
+
QFile f(toqstr(filename.absFileName()));
if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
LYXERR(Debug::ACTION, "Error opening file "
@@ -328,12 +331,73 @@ FileName GuiClipboard::getAsGraphics(Cursor const & cur, GraphicsType type) cons
}
-docstring const GuiClipboard::getAsText() const
+namespace {
+/**
+ * Tidy up a HTML chunk coming from the clipboard.
+ * This is needed since different applications put different kinds of HTML
+ * on the clipboard:
+ * - With or without the <?xml> tag
+ * - With or without the <!DOCTYPE> tag
+ * - With or without the <html> tag
+ * - With or without the <body> tag
+ * - With or without the <p> tag
+ * Since we are going to write a HTML file for external converters we need
+ * to ensure that it is a well formed HTML file, including all the mentioned tags.
+ */
+QString tidyHtml(QString input)
+{
+ // Misuse QTextDocument to cleanup the HTML.
+ // As a side effect, all visual markup like <tt> is converted to CSS,
+ // which is ignored by gnuhtml2latex.
+ // While this may be seen as a bug by some people it is actually a
+ // good thing, since we do import structure, but ignore all visual
+ // clutter.
+ QTextDocument converter;
+ converter.setHtml(input);
+ return converter.toHtml("utf-8");
+}
+}
+
+
+docstring const GuiClipboard::getAsText(TextType type) const
{
// text data from other applications
- QString const str = qApp->clipboard()->text(QClipboard::Clipboard)
+ QString str;
+ switch (type) {
+ case AnyTextType:
+ case LyXTextType:
+ // must not convert to docstring, since file can contain
+ // mixed encodings (use getAsLyX() instead)
+ break;
+ case PlainTextType:
+ str = qApp->clipboard()->text(QClipboard::Clipboard)
.normalized(QString::NormalizationForm_C);
- LYXERR(Debug::ACTION, "GuiClipboard::getAsText(): `" << str << "'");
+ break;
+ case LaTeXTextType: {
+ QMimeData const * source =
+ qApp->clipboard()->mimeData(QClipboard::Clipboard);
+ if (source) {
+ // First try LaTeX, then TeX (we do not distinguish
+ // for clipboard purposes)
+ if (source->hasFormat(latexMimeType())) {
+ str = source->data(latexMimeType());
+ str = str.normalized(QString::NormalizationForm_C);
+ } else if (source->hasFormat(texMimeType())) {
+ str = source->data(texMimeType());
+ str = str.normalized(QString::NormalizationForm_C);
+ }
+ }
+ break;
+ }
+ case HtmlTextType: {
+ QString subtype = "html";
+ str = qApp->clipboard()->text(subtype, QClipboard::Clipboard)
+ .normalized(QString::NormalizationForm_C);
+ str = tidyHtml(str);
+ break;
+ }
+ }
+ LYXERR(Debug::ACTION, "GuiClipboard::getAsText(" << type << "): `" << str << "'");
if (str.isNull())
return docstring();
@@ -341,10 +405,10 @@ docstring const GuiClipboard::getAsText() const
}
-void GuiClipboard::put(string const & lyx, docstring const & text)
+void GuiClipboard::put(string const & lyx, docstring const & html, docstring const & text)
{
LYXERR(Debug::ACTION, "GuiClipboard::put(`" << lyx << "' `"
- << to_utf8(text) << "')");
+ << to_utf8(html) << "' `" << to_utf8(text) << "')");
// 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;
@@ -363,19 +427,31 @@ void GuiClipboard::put(string const & lyx, docstring const & text)
// clipboard.
QString const qtext = toqstr(text);
data->setText(qtext);
+ QString const qhtml = toqstr(html);
+ data->setHtml(qhtml);
qApp->clipboard()->setMimeData(data, QClipboard::Clipboard);
}
-bool GuiClipboard::hasLyXContents() const
-{
- return cache_.hasFormat(lyxMimeType());
-}
-
-
-bool GuiClipboard::hasTextContents() const
+bool GuiClipboard::hasTextContents(Clipboard::TextType type) const
{
- return cache_.hasText();
+ switch (type) {
+ case AnyTextType:
+ return cache_.hasFormat(lyxMimeType()) || cache_.hasText() ||
+ cache_.hasHtml() || cache_.hasFormat(latexMimeType()) ||
+ cache_.hasFormat(texMimeType());
+ case LyXTextType:
+ return cache_.hasFormat(lyxMimeType());
+ case PlainTextType:
+ return cache_.hasText();
+ case HtmlTextType:
+ return cache_.hasHtml();
+ case LaTeXTextType:
+ return cache_.hasFormat(latexMimeType()) ||
+ cache_.hasFormat(texMimeType());
+ }
+ // shut up compiler
+ return false;
}
@@ -423,7 +499,7 @@ bool GuiClipboard::hasGraphicsContents(Clipboard::GraphicsType type) const
bool GuiClipboard::isInternal() const
{
- if (!hasLyXContents())
+ if (!hasTextContents(LyXTextType))
return false;
// ownsClipboard() is also true for stuff coming from dialogs, e.g.
@@ -470,11 +546,11 @@ void GuiClipboard::on_dataChanged()
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()->
+
+ plaintext_clipboard_empty_ = qApp->clipboard()->
text(QClipboard::Clipboard).isEmpty();
- has_lyx_contents_ = hasLyXContents();
+ has_text_contents_ = hasTextContents();
has_graphics_contents_ = hasGraphicsContents();
}
@@ -485,9 +561,9 @@ bool GuiClipboard::empty() const
// clipboard. The plaintext version is empty if the LyX version
// contains only one inset, and the LyX version is empty if the
// clipboard does not come from LyX.
- if (!text_clipboard_empty_)
+ if (!plaintext_clipboard_empty_)
return false;
- return !has_lyx_contents_ && !has_graphics_contents_;
+ return !has_text_contents_ && !has_graphics_contents_;
}
} // namespace frontend
diff --git a/src/frontends/qt4/GuiClipboard.h b/src/frontends/qt4/GuiClipboard.h
index a6540f1..3ffdafc 100644
--- a/src/frontends/qt4/GuiClipboard.h
+++ b/src/frontends/qt4/GuiClipboard.h
@@ -69,11 +69,10 @@ public:
//@{
std::string const getAsLyX() const;
FileName getAsGraphics(Cursor const & cur, GraphicsType type) const;
- docstring const getAsText() const;
- void put(std::string const & lyx, docstring const & text);
- bool hasLyXContents() const;
+ docstring const getAsText(TextType type) const;
+ void put(std::string const & lyx, docstring const & html, docstring const & text);
bool hasGraphicsContents(GraphicsType type = AnyGraphicsType) const;
- bool hasTextContents() const;
+ bool hasTextContents(TextType typetype = AnyTextType) const;
bool isInternal() const;
bool hasInternal() const;
bool empty() const;
@@ -86,8 +85,8 @@ private Q_SLOTS:
void on_dataChanged();
private:
- bool text_clipboard_empty_;
- bool has_lyx_contents_;
+ bool plaintext_clipboard_empty_;
+ bool has_text_contents_;
bool has_graphics_contents_;
/// the cached mime data used to describe the information
/// that can be stored in the clipboard
diff --git a/src/insets/InsetTabular.cpp b/src/insets/InsetTabular.cpp
index 2c99670..58fcd92 100644
--- a/src/insets/InsetTabular.cpp
+++ b/src/insets/InsetTabular.cpp
@@ -4329,7 +4329,7 @@ void InsetTabular::doDispatch(Cursor & cur, FuncRequest & cmd)
case LFUN_CLIPBOARD_PASTE:
case LFUN_PRIMARY_SELECTION_PASTE: {
docstring const clip = (act == LFUN_CLIPBOARD_PASTE) ?
- theClipboard().getAsText() :
+ theClipboard().getAsText(Clipboard::PlainTextType) :
theSelection().get();
if (clip.empty())
break;
diff --git a/src/mathed/InsetMathGrid.cpp b/src/mathed/InsetMathGrid.cpp
index b4619de..98a370f 100644
--- a/src/mathed/InsetMathGrid.cpp
+++ b/src/mathed/InsetMathGrid.cpp
@@ -1334,7 +1334,7 @@ void InsetMathGrid::doDispatch(Cursor & cur, FuncRequest & cmd)
cap::replaceSelection(cur);
docstring topaste;
if (cmd.argument().empty() && !theClipboard().isInternal())
- topaste = theClipboard().getAsText();
+ topaste = theClipboard().getAsText(Clipboard::PlainTextType);
else {
idocstringstream is(cmd.argument());
int n = 0;
diff --git a/src/mathed/InsetMathNest.cpp b/src/mathed/InsetMathNest.cpp
index 829bbc0..56e4d0a 100644
--- a/src/mathed/InsetMathNest.cpp
+++ b/src/mathed/InsetMathNest.cpp
@@ -576,7 +576,7 @@ void InsetMathNest::doDispatch(Cursor & cur, FuncRequest & cmd)
replaceSelection(cur);
docstring topaste;
if (cmd.argument().empty() && !theClipboard().isInternal())
- topaste = theClipboard().getAsText();
+ topaste = theClipboard().getAsText(Clipboard::PlainTextType);
else {
size_t n = 0;
idocstringstream is(cmd.argument());
diff --git a/src/support/lstrings.cpp b/src/support/lstrings.cpp
index 651dbc1..3393189 100644
--- a/src/support/lstrings.cpp
+++ b/src/support/lstrings.cpp
@@ -882,6 +882,18 @@ docstring const subst(docstring const & a,
}
+int count_char(string const & str, char chr)
+{
+ int count = 0;
+ string::const_iterator lit = str.begin();
+ string::const_iterator end = str.end();
+ for (; lit != end; ++lit)
+ if ((*lit) == chr)
+ count++;
+ return count;
+}
+
+
/// Count all occurences of char \a chr inside \a str
int count_char(docstring const & str, docstring::value_type chr)
{
diff --git a/src/support/lstrings.h b/src/support/lstrings.h
index 4845bc6..2606546 100644
--- a/src/support/lstrings.h
+++ b/src/support/lstrings.h
@@ -189,6 +189,9 @@ docstring const subst(docstring const & a,
docstring const & oldstr, docstring const & newstr);
/// Count all occurences of char \a chr inside \a str
+int count_char(std::string const & str, char chr);
+
+/// Count all occurences of char \a chr inside \a str
int count_char(docstring const & str, docstring::value_type chr);
/** Trims characters off the end and beginning of a string.