lyx_mirror/src/frontends/qt4/GuiClipboard.cpp
Christian Ridderström e30f3d76d2 Bulk cleanup/fix incorrect annotation at the end of namespaces.
This commit does a bulk fix of incorrect annotations (comments) at the
end of namespaces.

The commit was generated by initially running clang-format, and then
from the diff of the result extracting the hunks corresponding to
fixes of namespace comments. The changes being applied and all the
results have been manually reviewed. The source code successfully
builds on macOS.

Further details on the steps below, in case they're of interest to
someone else in the future.

1. Checkout a fresh and up to date version of src/

    git pull && git checkout -- src && git status src

2. Ensure there's a suitable .clang-format in place, i.e. with options
   to fix the comment at the end of namespaces, including:

    FixNamespaceComments:                           true
    SpacesBeforeTrailingComments:                   1

and that clang-format is >= 5.0.0, by doing e.g.:

    clang-format -dump-config | grep Comments:
    clang-format --version

3. Apply clang-format to the source:

    clang-format -i $(find src -name "*.cpp" -or -name "*.h")

4. Create and filter out hunks related to fixing the namespace

    git diff -U0 src > tmp.patch
    grepdiff '^} // namespace' --output-matching=hunk tmp.patch  > fix_namespace.patch

5. Filter out hunks corresponding to simple fixes into to a separate patch:

    pcregrep -M -e '^diff[^\n]+\nindex[^\n]+\n--- [^\n]+\n\+\+\+ [^\n]+\n'  \
        -e '^@@ -[0-9]+ \+[0-9]+ @@[^\n]*\n-\}[^\n]*\n\+\}[^\n]*\n'         \
        fix_namespace.patch > fix_namespace_simple.patch

6. Manually review the simple patch and then apply it, after first
   restoring the source.

    git checkout -- src
    patch -p1 < fix_namespace_simple.path

7. Manually review the (simple) changes and then stage the changes

    git diff src
    git add src

8. Again apply clang-format and filter out hunks related to any
   remaining fixes to the namespace, this time filter with more
   context. There will be fewer hunks as all the simple cases have
   already been handled:

    clang-format -i $(find src -name "*.cpp" -or -name "*.h")
    git diff src > tmp.patch
    grepdiff '^} // namespace' --output-matching=hunk tmp.patch  > fix_namespace2.patch

9. Manually review/edit the resulting patch file to remove hunks for files
   which need to be dealt with manually, noting the file names and
   line numbers. Then restore files to as before applying clang-format
   and apply the patch:

    git checkout src
    patch -p1 < fix_namespace2.patch

10. Manually fix the files noted in the previous step. Stage files,
    review changes and commit.
2017-07-23 13:11:54 +02:00

601 lines
17 KiB
C++

// -*- C++ -*-
/**
* \file qt4/GuiClipboard.cpp
* This file is part of LyX, the document processor.
* Licence details can be found in the file COPYING.
*
* \author John Levon
* \author Abdelrazak Younes
*
* Full author contact details are available in file CREDITS.
*/
#include <config.h>
#include "FileDialog.h"
#include "support/FileName.h"
#include "GuiClipboard.h"
#include "qt_helpers.h"
#include "Buffer.h"
#include "BufferView.h"
#include "Cursor.h"
#include "support/lassert.h"
#include "support/convert.h"
#include "support/debug.h"
#include "support/filetools.h"
#include "support/gettext.h"
#include "support/lstrings.h"
#include "support/lyxtime.h"
#ifdef Q_OS_MAC
#include "support/linkback/LinkBackProxy.h"
#endif // Q_OS_MAC
#include "frontends/alert.h"
#include <QApplication>
#include <QBuffer>
#include <QClipboard>
#include <QDataStream>
#include <QFile>
#include <QImage>
#include <QMimeData>
#include <QString>
#include <QStringList>
#include <QTextDocument>
#include <QTimer>
#include <boost/crc.hpp>
#include <memory>
#include <map>
#include <iostream>
using namespace std;
using namespace lyx::support;
namespace lyx {
namespace frontend {
static QMimeData const * read_clipboard()
{
LYXERR(Debug::CLIPBOARD, "Getting Clipboard");
QMimeData const * source =
qApp->clipboard()->mimeData(QClipboard::Clipboard);
if (!source) {
LYXERR0("0 bytes (no QMimeData)");
return new QMimeData;
}
// It appears that doing IO between getting a mimeData object
// and using it can cause a crash (maybe Qt used IO
// as an excuse to free() it? Anyway let's not introduce
// any new IO here, so e.g. leave the following line commented.
// lyxerr << "Got Clipboard (" << (long) source << ")\n" ;
return source;
}
void CacheMimeData::update()
{
time_t const start_time = current_time();
LYXERR(Debug::CLIPBOARD, "Creating CacheMimeData object");
cached_formats_ = read_clipboard()->formats();
// Qt times out after 5 seconds if it does not recieve a response.
if (current_time() - start_time > 3) {
LYXERR0("No timely response from clipboard, perhaps process "
<< "holding clipboard is frozen?");
}
}
QByteArray CacheMimeData::data(QString const & mimeType) const
{
return read_clipboard()->data(mimeType);
}
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"; }
GuiClipboard::GuiClipboard()
{
connect(qApp->clipboard(), SIGNAL(dataChanged()),
this, SLOT(on_dataChanged()));
// initialize clipboard status.
update();
}
string const GuiClipboard::getAsLyX() const
{
LYXERR(Debug::CLIPBOARD, "GuiClipboard::getAsLyX(): `");
// We don't convert encodings here since the encoding of the
// clipboard contents is specified in the data itself
if (cache_.hasFormat(lyxMimeType())) {
// data from ourself or some other LyX instance
QByteArray const ar = cache_.data(lyxMimeType());
string const s(ar.data(), ar.count());
LYXERR(Debug::CLIPBOARD, s << "'");
return s;
}
LYXERR(Debug::CLIPBOARD, "'");
return string();
}
FileName GuiClipboard::getPastedGraphicsFileName(Cursor const & cur,
Clipboard::GraphicsType & type) const
{
// create file dialog filter according to the existing types in the clipboard
vector<Clipboard::GraphicsType> types;
if (hasGraphicsContents(Clipboard::EmfGraphicsType))
types.push_back(Clipboard::EmfGraphicsType);
if (hasGraphicsContents(Clipboard::WmfGraphicsType))
types.push_back(Clipboard::WmfGraphicsType);
if (hasGraphicsContents(Clipboard::LinkBackGraphicsType))
types.push_back(Clipboard::LinkBackGraphicsType);
if (hasGraphicsContents(Clipboard::PdfGraphicsType))
types.push_back(Clipboard::PdfGraphicsType);
if (hasGraphicsContents(Clipboard::PngGraphicsType))
types.push_back(Clipboard::PngGraphicsType);
if (hasGraphicsContents(Clipboard::JpegGraphicsType))
types.push_back(Clipboard::JpegGraphicsType);
LASSERT(!types.empty(), return FileName());
// select prefered type if AnyGraphicsType was passed
if (type == Clipboard::AnyGraphicsType)
type = types.front();
// which extension?
map<Clipboard::GraphicsType, string> extensions;
map<Clipboard::GraphicsType, docstring> typeNames;
extensions[Clipboard::EmfGraphicsType] = "emf";
extensions[Clipboard::WmfGraphicsType] = "wmf";
extensions[Clipboard::LinkBackGraphicsType] = "linkback";
extensions[Clipboard::PdfGraphicsType] = "pdf";
extensions[Clipboard::PngGraphicsType] = "png";
extensions[Clipboard::JpegGraphicsType] = "jpeg";
typeNames[Clipboard::EmfGraphicsType] = _("Enhanced Metafile");
typeNames[Clipboard::WmfGraphicsType] = _("Windows Metafile");
typeNames[Clipboard::LinkBackGraphicsType] = _("LinkBack PDF");
typeNames[Clipboard::PdfGraphicsType] = _("PDF");
typeNames[Clipboard::PngGraphicsType] = _("PNG");
typeNames[Clipboard::JpegGraphicsType] = _("JPEG");
// find unused filename with primary extension
string document_path = cur.buffer()->fileName().onlyPath().absFileName();
unsigned newfile_number = 0;
FileName filename;
do {
++newfile_number;
filename = FileName(addName(document_path,
to_utf8(_("pasted"))
+ convert<string>(newfile_number) + "."
+ extensions[type]));
} while (filename.isReadableFile());
while (true) {
// create file type filter, putting the prefered on to the front
QStringList filter;
for (size_t i = 0; i != types.size(); ++i) {
docstring s = bformat(_("%1$s Files"), typeNames[types[i]])
+ " (*." + from_ascii(extensions[types[i]]) + ")";
if (types[i] == type)
filter.prepend(toqstr(s));
else
filter.append(toqstr(s));
}
filter = fileFilters(filter.join(";;"));
// show save dialog for the graphic
FileDialog dlg(qt_("Choose a filename to save the pasted graphic as"));
FileDialog::Result result =
dlg.save(toqstr(filename.onlyPath().absFileName()), filter,
toqstr(filename.onlyFileName()));
if (result.first == FileDialog::Later)
return FileName();
string newFilename = fromqstr(result.second);
if (newFilename.empty()) {
cur.bv().message(_("Canceled."));
return FileName();
}
filename.set(newFilename);
// check the extension (the user could have changed it)
if (!suffixIs(ascii_lowercase(filename.absFileName()),
"." + extensions[type])) {
// the user changed the extension. Check if the type is available
size_t i;
for (i = 1; i != types.size(); ++i) {
if (suffixIs(ascii_lowercase(filename.absFileName()),
"." + extensions[types[i]])) {
type = types[i];
break;
}
}
// invalid extension found, or none at all. In the latter
// case set the default extensions.
if (i == types.size()
&& filename.onlyFileName().find('.') == string::npos) {
filename.changeExtension("." + extensions[type]);
}
}
// check whether the file exists and warn the user
if (!filename.exists())
break;
int ret = frontend::Alert::prompt(
_("Overwrite external file?"),
bformat(_("File %1$s already exists, do you want to overwrite it?"),
from_utf8(filename.absFileName())), 1, 1, _("&Overwrite"), _("&Cancel"));
if (ret == 0)
// overwrite, hence break the dialog loop
break;
// not overwrite, hence show the dialog again (i.e. loop)
}
return filename;
}
FileName GuiClipboard::getAsGraphics(Cursor const & cur, GraphicsType type) const
{
// get the filename from the user
FileName filename = getPastedGraphicsFileName(cur, type);
if (filename.empty())
return FileName();
// handle image cases first
if (type == PngGraphicsType || type == JpegGraphicsType) {
// get image from QImage from clipboard
QImage image = qApp->clipboard()->image();
if (image.isNull()) {
LYXERR(Debug::CLIPBOARD, "No image in clipboard");
return FileName();
}
// convert into graphics format
QByteArray ar;
QBuffer buffer(&ar);
buffer.open(QIODevice::WriteOnly);
if (type == PngGraphicsType)
image.save(toqstr(filename.absFileName()), "PNG");
else if (type == JpegGraphicsType)
image.save(toqstr(filename.absFileName()), "JPEG");
else
LATTEST(false);
return filename;
}
// get mime for type
QString mime;
switch (type) {
case PdfGraphicsType: mime = pdfMimeType(); break;
case LinkBackGraphicsType: mime = pdfMimeType(); break;
case EmfGraphicsType: mime = emfMimeType(); break;
case WmfGraphicsType: mime = wmfMimeType(); break;
default: LASSERT(false, return FileName());
}
// get data
if (!cache_.hasFormat(mime))
return FileName();
// data from ourself or some other LyX instance
QByteArray const ar = cache_.data(mime);
LYXERR(Debug::CLIPBOARD, "Getting from clipboard: mime = " << mime.constData()
<< "length = " << ar.count());
QFile f(toqstr(filename.absFileName()));
if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
LYXERR(Debug::CLIPBOARD, "Error opening file "
<< filename.absFileName() << " for writing");
return FileName();
}
// write the (LinkBack) PDF data
f.write(ar);
if (type == LinkBackGraphicsType) {
#ifdef Q_OS_MAC
void const * linkBackData;
unsigned linkBackLen;
getLinkBackData(&linkBackData, &linkBackLen);
f.write((char *)linkBackData, linkBackLen);
quint32 pdfLen = ar.size();
QDataStream ds(&f);
ds << pdfLen; // big endian by default
#else
// only non-Mac this should never happen
LATTEST(false);
#endif // Q_OS_MAC
}
f.close();
return filename;
}
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");
}
} // namespace
docstring const GuiClipboard::getAsText(TextType type) const
{
// text data from other applications
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);
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::CLIPBOARD, "GuiClipboard::getAsText(" << type << "): `" << str << "'");
if (str.isNull())
return docstring();
return internalLineEnding(str);
}
void GuiClipboard::put(string const & text) const
{
qApp->clipboard()->setText(toqstr(text));
}
void GuiClipboard::put(string const & lyx, docstring const & html, docstring const & text)
{
LYXERR(Debug::CLIPBOARD, "GuiClipboard::put(`" << lyx << "' `"
<< 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;
if (!lyx.empty()) {
QByteArray const qlyx(lyx.c_str(), lyx.size());
data->setData(lyxMimeType(), qlyx);
// If the OS has not the concept of clipboard ownership,
// we recognize internal data through its checksum.
if (!hasInternal()) {
boost::crc_32_type crc32;
crc32.process_bytes(lyx.c_str(), lyx.size());
checksum = crc32.checksum();
}
}
// Don't test for text.empty() since we want to be able to clear the
// 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::hasTextContents(Clipboard::TextType type) const
{
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;
}
bool GuiClipboard::hasGraphicsContents(Clipboard::GraphicsType type) const
{
if (type == AnyGraphicsType) {
return hasGraphicsContents(PdfGraphicsType)
|| hasGraphicsContents(PngGraphicsType)
|| hasGraphicsContents(JpegGraphicsType)
|| hasGraphicsContents(EmfGraphicsType)
|| hasGraphicsContents(WmfGraphicsType)
|| hasGraphicsContents(LinkBackGraphicsType);
}
// handle image cases first
if (type == PngGraphicsType || type == JpegGraphicsType)
return cache_.hasImage();
// handle LinkBack for Mac
if (type == LinkBackGraphicsType)
#ifdef Q_OS_MAC
return isLinkBackDataInPasteboard();
#else
return false;
#endif // Q_OS_MAC
// get mime data
QStringList const & formats = cache_.formats();
LYXERR(Debug::CLIPBOARD, "We found " << formats.size() << " formats");
for (int i = 0; i < formats.size(); ++i)
LYXERR(Debug::CLIPBOARD, "Found format " << formats[i]);
// compute mime for type
QString mime;
switch (type) {
case EmfGraphicsType: mime = emfMimeType(); break;
case WmfGraphicsType: mime = wmfMimeType(); break;
case PdfGraphicsType: mime = pdfMimeType(); break;
default: LASSERT(false, return false);
}
return cache_.hasFormat(mime);
}
bool GuiClipboard::isInternal() const
{
if (!hasTextContents(LyXTextType))
return false;
// ownsClipboard() is also true for stuff coming from dialogs, e.g.
// the preamble dialog. This does only work on X11 and Windows, since
// ownsClipboard() is hardwired to return false on OS X.
if (hasInternal())
return qApp->clipboard()->ownsClipboard();
// We are running on OS X: Check whether clipboard data is from
// ourself by comparing its checksum with the stored one.
QByteArray const ar = cache_.data(lyxMimeType());
string const data(ar.data(), ar.count());
boost::crc_32_type crc32;
crc32.process_bytes(data.c_str(), data.size());
return checksum == crc32.checksum();
}
bool GuiClipboard::hasInternal() const
{
// Windows and Mac OS X does not have the concept of ownership;
// the clipboard is a fully global resource so all applications
// are notified of changes. However, on Windows ownership is
// emulated by Qt through the OleIsCurrentClipboard() API, while
// on Mac OS X we deal with this issue by ourself.
#ifndef Q_OS_MAC
return true;
#else
return false;
#endif
}
void GuiClipboard::on_dataChanged()
{
update();
#if defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
// Retry on Windows (#10109)
if (cache_.formats().count() == 0) {
QTimer::singleShot(100, this, SLOT(update()));
}
#endif
}
void GuiClipboard::update()
{
//Note: we do not really need to run cache_.update() unless the
//data has been changed *and* the GuiClipboard has been queried.
//However if run cache_.update() the moment a process grabs the
//clipboard, the process holding the clipboard presumably won't
//yet be frozen, and so we won't need to wait 5 seconds for Qt
//to time-out waiting for the clipboard.
cache_.update();
QStringList l = cache_.formats();
LYXERR(Debug::CLIPBOARD, "Qt Clipboard changed. We found the following mime types:");
for (int i = 0; i < l.count(); i++)
LYXERR(Debug::CLIPBOARD, l.value(i));
plaintext_clipboard_empty_ = qApp->clipboard()->
text(QClipboard::Clipboard).isEmpty();
has_text_contents_ = hasTextContents();
has_graphics_contents_ = hasGraphicsContents();
}
bool GuiClipboard::empty() const
{
// We need to check both the plaintext and the LyX version of the
// 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 (!plaintext_clipboard_empty_)
return false;
return !has_text_contents_ && !has_graphics_contents_;
}
} // namespace frontend
} // namespace lyx
#include "moc_GuiClipboard.cpp"