The branch, master, has been updated. - Log -----------------------------------------------------------------
commit c14b9e67bcf6ac16534a6d37df766c4583722045 Author: Georg Baum <[email protected]> Date: Sun Apr 14 19:45:36 2013 +0200 Implement paste from LaTeX and HTML (bug #3096) As discussed on the list. No automatic contents detection is done, the user needs to use the special paste menu instead. I used the new TempFile class for safe temporary file handling. The documentation would go into section 2.2 of UserGuide.lyx, but I am not allowed to edit that document. diff --git a/lib/ui/stdmenus.inc b/lib/ui/stdmenus.inc index f1d1af4..ced9ece 100644 --- a/lib/ui/stdmenus.inc +++ b/lib/ui/stdmenus.inc @@ -156,6 +156,8 @@ Menuset Menu "edit_paste" Item "Plain Text|T" "clipboard-paste" Item "Plain Text, Join Lines|J" "clipboard-paste paragraph" + Item "HTML Text|H" "paste html" + Item "LaTeX Text|L" "paste latex" Separator Item "Selection|S" "primary-selection-paste" Item "Selection, Join Lines|i" "primary-selection-paste paragraph" diff --git a/src/Buffer.cpp b/src/Buffer.cpp index eaf2746..adda71f 100644 --- a/src/Buffer.cpp +++ b/src/Buffer.cpp @@ -102,6 +102,7 @@ #include "support/Package.h" #include "support/PathChanger.h" #include "support/Systemcall.h" +#include "support/TempFile.h" #include "support/textutils.h" #include "support/types.h" @@ -978,6 +979,49 @@ bool Buffer::readDocument(Lexer & lex) } +bool Buffer::importString(string const & format, docstring const & contents, ErrorList & errorList) +{ + Format const * fmt = formats.getFormat(format); + if (!fmt) + return false; + // It is important to use the correct extension here, since some + // converters create a wrong output file otherwise (e.g. html2latex) + TempFile const tempfile("Buffer_importStringXXXXXX." + fmt->extension()); + FileName const name(tempfile.name()); + 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; + + TempFile const tempfile("Buffer_importFileXXXXXX.lyx"); + FileName const lyx(tempfile.name()); + 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; @@ -1125,7 +1169,7 @@ Buffer::ReadStatus Buffer::parseLyXFormat(Lexer & lex, Buffer::ReadStatus Buffer::convertLyXFormat(FileName const & fn, FileName & tmpfile, int from_format) { - tmpfile = FileName::tempName("Buffer_convertLyXFormat"); + tmpfile = FileName::tempName("Buffer_convertLyXFormatXXXXXX.lyx"); if(tmpfile.empty()) { Alert::error(_("Conversion failed"), bformat(_("%1$s is from a different" @@ -2765,11 +2809,12 @@ string Buffer::absFileName() const string Buffer::filePath() const { - int last = d->filename.onlyPath().absFileName().length() - 1; + string const abs = d->filename.onlyPath().absFileName(); + if (abs.empty()) + return abs; + int last = abs.length() - 1; - return d->filename.onlyPath().absFileName()[last] == '/' - ? d->filename.onlyPath().absFileName() - : d->filename.onlyPath().absFileName() + "/"; + return abs[last] == '/' ? abs : abs + '/'; } 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 da5739d..def5683 100644 --- a/src/CutAndPaste.cpp +++ b/src/CutAndPaste.cpp @@ -1010,16 +1010,21 @@ void pasteFromStack(Cursor & cur, ErrorList & errorList, size_t sel_index) } -void pasteClipboardText(Cursor & cur, ErrorList & errorList, bool asParagraphs) +void pasteClipboardText(Cursor & cur, ErrorList & errorList, bool asParagraphs, + Clipboard::TextType type) { // Use internal clipboard if it is the most recent one + // This overrides asParagraphs and type on purpose! if (theClipboard().isInternal()) { pasteFromStack(cur, errorList, 0); return; } // First try LyX format - if (theClipboard().hasLyXContents()) { + if ((type == Clipboard::LyXTextType || + type == Clipboard::LyXOrPlainTextType || + type == Clipboard::AnyTextType) && + theClipboard().hasTextContents(Clipboard::LyXTextType)) { string lyx = theClipboard().getAsLyX(); if (!lyx.empty()) { // For some strange reason gcc 3.2 and 3.3 do not accept @@ -1035,8 +1040,42 @@ 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) { + if (type != types[i] && type != Clipboard::AnyTextType) + continue; + bool available = theClipboard().hasTextContents(types[i]); + + // If a specific type was explicitly requested, try to + // interpret plain text: The user told us that the clipboard + // contents is in the desired format + if (!available && type == types[i]) { + types[i] = Clipboard::PlainTextType; + available = theClipboard().hasTextContents(types[i]); + } + + if (available) { + docstring text = theClipboard().getAsText(types[i]); + 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(); @@ -1065,7 +1104,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/CutAndPaste.h b/src/CutAndPaste.h index 2453601..802319d 100644 --- a/src/CutAndPaste.h +++ b/src/CutAndPaste.h @@ -87,8 +87,9 @@ void pasteSelection(Cursor & cur, ErrorList &); /// Replace the current selection with the clipboard contents as text /// (internal or external: which is newer). /// Does handle undo. Does only work in text, not mathed. -void pasteClipboardText(Cursor & cur, ErrorList & errorList, - bool asParagraphs = true); +/// \p asParagraphs is only considered if plain text is pasted. +void pasteClipboardText(Cursor & cur, ErrorList & errorList, bool asParagraphs, + Clipboard::TextType preferedType = Clipboard::LyXOrPlainTextType); /// Replace the current selection with the clipboard contents as graphic. /// Does handle undo. Does only work in text, not mathed. void pasteClipboardGraphics(Cursor & cur, ErrorList & errorList, diff --git a/src/LyXAction.cpp b/src/LyXAction.cpp index 01c5556..47aa147 100644 --- a/src/LyXAction.cpp +++ b/src/LyXAction.cpp @@ -1215,7 +1215,7 @@ void LyXAction::init() * \var lyx::FuncCode lyx::LFUN_PASTE * \li Action: Pastes material (text or picture) from the active clipboard. * \li Syntax: paste [<TYPE>|<NUM>] - * \li Params: <TYPE>: emf|pdf|png|jpeg|linkback|wmf \n + * \li Params: <TYPE>: emf|pdf|png|jpeg|linkback|wmf|latex|html \n <NUM>: number of the selection in the internal clipboard stack to be pasted. * \endvar */ diff --git a/src/Text3.cpp b/src/Text3.cpp index b87cf29..a94f16a 100644 --- a/src/Text3.cpp +++ b/src/Text3.cpp @@ -1248,11 +1248,15 @@ void Text::dispatch(Cursor & cur, FuncRequest & cmd) && !theClipboard().hasTextContents()) pasteClipboardGraphics(cur, bv->buffer().errorList("Paste")); else - pasteClipboardText(cur, bv->buffer().errorList("Paste")); + pasteClipboardText(cur, bv->buffer().errorList("Paste"), true); } else if (isStrUnsignedInt(arg)) { // we have a numerical argument pasteFromStack(cur, bv->buffer().errorList("Paste"), convert<unsigned int>(arg)); + } else if (arg == "html" || arg == "latex") { + Clipboard::TextType type = (arg == "html") ? + Clipboard::HtmlTextType : Clipboard::LaTeXTextType; + pasteClipboardText(cur, bv->buffer().errorList("Paste"), true, type); } else { Clipboard::GraphicsType type = Clipboard::AnyGraphicsType; if (arg == "pdf") @@ -1267,7 +1271,6 @@ void Text::dispatch(Cursor & cur, FuncRequest & cmd) type = Clipboard::EmfGraphicsType; else if (arg == "wmf") type = Clipboard::WmfGraphicsType; - else LASSERT(false, /**/); @@ -2772,6 +2775,20 @@ bool Text::getStatus(Cursor & cur, FuncRequest const & cmd, break; } + // explicit text type? + if (arg == "html") { + // Do not enable for PlainTextType, since some tidying in the + // frontend is needed for HTML, which is too unsafe for plain text. + enable = theClipboard().hasTextContents(Clipboard::HtmlTextType); + break; + } else if (arg == "latex") { + // LaTeX is usually not available on the clipboard with + // the correct MIME type, but in plain text. + enable = theClipboard().hasTextContents(Clipboard::PlainTextType) || + theClipboard().hasTextContents(Clipboard::LaTeXTextType); + break; + } + // explicit graphics type? Clipboard::GraphicsType type = Clipboard::AnyGraphicsType; if ((arg == "pdf" && (type = Clipboard::PdfGraphicsType)) diff --git a/src/frontends/Clipboard.h b/src/frontends/Clipboard.h index 1e0d486..282ac1d 100644 --- a/src/frontends/Clipboard.h +++ b/src/frontends/Clipboard.h @@ -42,6 +42,15 @@ public: AnyGraphicsType }; + enum TextType { + AnyTextType, + LyXOrPlainTextType, + 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,8 +60,8 @@ 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; @@ -67,10 +76,8 @@ public: */ 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 b2a97d6..b722ea3 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"; } @@ -328,12 +331,80 @@ 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) + if ((type == AnyTextType || type == LyXOrPlainTextType) && hasTextContents(LyXTextType)) + type = LyXTextType; + if (type == AnyTextType && hasTextContents(LaTeXTextType)) + type = LaTeXTextType; + if (type == AnyTextType && hasTextContents(HtmlTextType)) + type = HtmlTextType; + QString str; + switch (type) { + case LyXTextType: + // must not convert to docstring, since file can contain + // mixed encodings (use getAsLyX() instead) + break; + case AnyTextType: + case LyXOrPlainTextType: + 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(); @@ -369,15 +440,27 @@ void GuiClipboard::put(string const & lyx, docstring const & html, docstring con } -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 LyXOrPlainTextType: + return cache_.hasFormat(lyxMimeType()) || cache_.hasText(); + 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; } @@ -425,7 +508,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. @@ -473,10 +556,10 @@ void GuiClipboard::on_dataChanged() 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(); } @@ -487,9 +570,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 5b452be..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; + docstring const getAsText(TextType type) const; void put(std::string const & lyx, docstring const & html, docstring const & text); - bool hasLyXContents() const; 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..2292655 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()); @@ -1461,6 +1461,13 @@ bool InsetMathNest::getStatus(Cursor & cur, FuncRequest const & cmd, flag.setEnabled(!asHullInset()); break; + case LFUN_PASTE: { + docstring const & name = cmd.argument(); + if (name == "html" || name == "latex") + flag.setEnabled(false); + break; + } + default: ret = false; break; ----------------------------------------------------------------------- Summary of changes: lib/ui/stdmenus.inc | 2 + src/Buffer.cpp | 55 ++++++++++++++++-- src/Buffer.h | 4 + src/CutAndPaste.cpp | 47 +++++++++++++- src/CutAndPaste.h | 5 +- src/LyXAction.cpp | 2 +- src/Text3.cpp | 21 ++++++- src/frontends/Clipboard.h | 17 ++++-- src/frontends/qt4/GuiClipboard.cpp | 115 +++++++++++++++++++++++++++++++----- src/frontends/qt4/GuiClipboard.h | 9 +-- src/insets/InsetTabular.cpp | 2 +- src/mathed/InsetMathGrid.cpp | 2 +- src/mathed/InsetMathNest.cpp | 9 +++- 13 files changed, 247 insertions(+), 43 deletions(-) hooks/post-receive -- The LyX Source Repository
