From b8d9b28f1e5ae3e74cbb3699ff93440a8ec40d24 Mon Sep 17 00:00:00 2001 From: Leandro Melo Date: Tue, 13 Dec 2011 12:15:30 +0100 Subject: [PATCH] Editor: Introduce circular clipboard This is a clipboard within Creator only. It allows the user to paste/navigate through the recently copied content by repeatedly triggering a shortcut (which is by default set to Ctrl+Shift+V). Task-number: QTCREATORBUG-146 Change-Id: Ie449ab4b304548d5037a0c877bbbc0344d654325 Reviewed-by: Eike Ziller --- src/plugins/texteditor/basetexteditor.cpp | 93 +++++++++++++++++-- src/plugins/texteditor/basetexteditor.h | 5 +- src/plugins/texteditor/basetexteditor_p.h | 1 + src/plugins/texteditor/circularclipboard.cpp | 79 ++++++++++++++++ src/plugins/texteditor/circularclipboard.h | 63 +++++++++++++ src/plugins/texteditor/texteditor.pro | 6 +- .../texteditor/texteditoractionhandler.cpp | 9 ++ .../texteditor/texteditoractionhandler.h | 2 + src/plugins/texteditor/texteditorconstants.h | 1 + 9 files changed, 250 insertions(+), 9 deletions(-) create mode 100644 src/plugins/texteditor/circularclipboard.cpp create mode 100644 src/plugins/texteditor/circularclipboard.h diff --git a/src/plugins/texteditor/basetexteditor.cpp b/src/plugins/texteditor/basetexteditor.cpp index 5b41cfea5cb..9dd10d0da44 100644 --- a/src/plugins/texteditor/basetexteditor.cpp +++ b/src/plugins/texteditor/basetexteditor.cpp @@ -54,6 +54,7 @@ #include "convenience.h" #include "texteditorsettings.h" #include "texteditoroverlay.h" +#include "circularclipboard.h" #include #include @@ -99,6 +100,7 @@ #include #include #include +#include //#define DO_FOO @@ -201,6 +203,18 @@ static void convertToPlainText(QString &txt) } } +static bool isModifierKey(int key) +{ + return key == Qt::Key_Shift + || key == Qt::Key_Control + || key == Qt::Key_Alt + || key == Qt::Key_Meta; +} + +static const char kTextBlockMimeType[] = "application/vnd.nokia.qtcreator.blocktext"; +static const char kVerticalTextBlockMimeType[] = "application/vnd.nokia.qtcreator.vblocktext"; + + BaseTextEditorWidget::BaseTextEditorWidget(QWidget *parent) : QPlainTextEdit(parent) { @@ -1524,6 +1538,11 @@ void BaseTextEditorWidget::keyPressEvent(QKeyEvent *e) d->m_moveLineUndoHack = false; d->clearVisibleFoldedBlock(); + if (d->m_isCirculatingClipboard + && !isModifierKey(e->key())) { + d->m_isCirculatingClipboard = false; + } + if (e->key() == Qt::Key_Alt && d->m_behaviorSettings.m_keyboardTooltips) { d->m_maybeFakeTooltipEvent = true; @@ -2461,6 +2480,7 @@ BaseTextEditorPrivate::BaseTextEditorPrivate() m_requestMarkEnabled(true), m_lineSeparatorsAllowed(false), m_maybeFakeTooltipEvent(false), + m_isCirculatingClipboard(false), m_visibleWrapColumn(0), m_linkPressed(false), m_delayedUpdateTimer(0), @@ -5742,6 +5762,22 @@ void BaseTextEditorWidget::cut() QPlainTextEdit::cut(); } +void BaseTextEditorWidget::copy() +{ + if (!textCursor().hasSelection()) + return; + + QPlainTextEdit::copy(); + + const QMimeData *mimeData = QApplication::clipboard()->mimeData(); + if (mimeData) { + CircularClipboard *circularClipBoard = CircularClipboard::instance(); + circularClipBoard->collect(duplicateMimeData(mimeData)); + // We want the latest copied content to be the first one to appear on circular paste. + circularClipBoard->toLastCollect(); + } +} + void BaseTextEditorWidget::paste() { if (d->m_inBlockSelectionMode) { @@ -5750,12 +5786,36 @@ void BaseTextEditorWidget::paste() QPlainTextEdit::paste(); } +void BaseTextEditorWidget::circularPaste() +{ + const QMimeData *mimeData = CircularClipboard::instance()->next(); + if (!mimeData) + return; + + QTextCursor cursor = textCursor(); + if (!d->m_isCirculatingClipboard) { + cursor.beginEditBlock(); + d->m_isCirculatingClipboard = true; + } else { + cursor.joinPreviousEditBlock(); + } + const int selectionStart = qMin(cursor.position(), cursor.anchor()); + insertFromMimeData(mimeData); + cursor.setPosition(selectionStart, QTextCursor::KeepAnchor); + cursor.endEditBlock(); + + setTextCursor(flippedCursor(cursor)); + + // We want to latest pasted content to replace the system's current clipboard. + QPlainTextEdit::copy(); +} + QMimeData *BaseTextEditorWidget::createMimeDataFromSelection() const { if (d->m_inBlockSelectionMode) { QMimeData *mimeData = new QMimeData; QString text = d->copyBlockSelection(); - mimeData->setData(QLatin1String("application/vnd.nokia.qtcreator.vblocktext"), text.toUtf8()); + mimeData->setData(QLatin1String(kVerticalTextBlockMimeType), text.toUtf8()); mimeData->setText(text); // for exchangeability return mimeData; } else if (textCursor().hasSelection()) { @@ -5829,7 +5889,7 @@ QMimeData *BaseTextEditorWidget::createMimeDataFromSelection() const cursor.setPosition(selstart.position()); cursor.setPosition(selend.position(), QTextCursor::KeepAnchor); text = cursor.selectedText(); - mimeData->setData(QLatin1String("application/vnd.nokia.qtcreator.blocktext"), text.toUtf8()); + mimeData->setData(QLatin1String(kTextBlockMimeType), text.toUtf8()); } return mimeData; } @@ -5846,8 +5906,8 @@ void BaseTextEditorWidget::insertFromMimeData(const QMimeData *source) if (isReadOnly()) return; - if (source->hasFormat(QLatin1String("application/vnd.nokia.qtcreator.vblocktext"))) { - QString text = QString::fromUtf8(source->data(QLatin1String("application/vnd.nokia.qtcreator.vblocktext"))); + if (source->hasFormat(QLatin1String(kVerticalTextBlockMimeType))) { + QString text = QString::fromUtf8(source->data(QLatin1String(kVerticalTextBlockMimeType))); if (text.isEmpty()) return; @@ -5923,8 +5983,8 @@ void BaseTextEditorWidget::insertFromMimeData(const QMimeData *source) bool insertAtBeginningOfLine = ts.cursorIsAtBeginningOfLine(cursor); if (insertAtBeginningOfLine - && source->hasFormat(QLatin1String("application/vnd.nokia.qtcreator.blocktext"))) { - text = QString::fromUtf8(source->data(QLatin1String("application/vnd.nokia.qtcreator.blocktext"))); + && source->hasFormat(QLatin1String(kTextBlockMimeType))) { + text = QString::fromUtf8(source->data(QLatin1String(kTextBlockMimeType))); if (text.isEmpty()) return; } @@ -5964,6 +6024,24 @@ void BaseTextEditorWidget::insertFromMimeData(const QMimeData *source) setTextCursor(cursor); } +QMimeData *BaseTextEditorWidget::duplicateMimeData(const QMimeData *source) const +{ + Q_ASSERT(source); + + QMimeData *mimeData = new QMimeData; + mimeData->setText(source->text()); + mimeData->setHtml(source->html()); + if (source->hasFormat(QLatin1String(kVerticalTextBlockMimeType))) { + mimeData->setData(QLatin1String(kVerticalTextBlockMimeType), + source->data(QLatin1String(kVerticalTextBlockMimeType))); + } else if (source->hasFormat(QLatin1String(kTextBlockMimeType))) { + mimeData->setData(QLatin1String(kTextBlockMimeType), + source->data(QLatin1String(kTextBlockMimeType))); + } + + return mimeData; +} + void BaseTextEditorWidget::appendStandardContextMenuActions(QMenu *menu) { menu->addSeparator(); @@ -5978,6 +6056,9 @@ void BaseTextEditorWidget::appendStandardContextMenuActions(QMenu *menu) a = am->command(Core::Constants::PASTE)->action(); if (a && a->isEnabled()) menu->addAction(a); + a = am->command(Constants::CIRCULAR_PASTE)->action(); + if (a && a->isEnabled()) + menu->addAction(a); } diff --git a/src/plugins/texteditor/basetexteditor.h b/src/plugins/texteditor/basetexteditor.h index 3772da8a8a7..4e02b961d28 100644 --- a/src/plugins/texteditor/basetexteditor.h +++ b/src/plugins/texteditor/basetexteditor.h @@ -254,9 +254,12 @@ public: public slots: void setDisplayName(const QString &title); + virtual void copy(); virtual void paste(); virtual void cut(); + void circularPaste(); + void zoomIn(int range = 1); void zoomOut(int range = 1); void zoomReset(); @@ -334,10 +337,10 @@ protected: void showEvent(QShowEvent *); - // reimplemented to support block selection QMimeData *createMimeDataFromSelection() const; bool canInsertFromMimeData(const QMimeData *source) const; void insertFromMimeData(const QMimeData *source); + QMimeData *duplicateMimeData(const QMimeData *source) const; static QString msgTextTooLarge(quint64 size); diff --git a/src/plugins/texteditor/basetexteditor_p.h b/src/plugins/texteditor/basetexteditor_p.h index 1984f63dc04..c4312e559d6 100644 --- a/src/plugins/texteditor/basetexteditor_p.h +++ b/src/plugins/texteditor/basetexteditor_p.h @@ -246,6 +246,7 @@ public: uint autoParenthesisOverwriteBackup : 1; uint surroundWithEnabledOverwriteBackup : 1; uint m_maybeFakeTooltipEvent : 1; + uint m_isCirculatingClipboard: 1; int m_visibleWrapColumn; QTextCharFormat m_linkFormat; diff --git a/src/plugins/texteditor/circularclipboard.cpp b/src/plugins/texteditor/circularclipboard.cpp new file mode 100644 index 00000000000..1ebdcfce7aa --- /dev/null +++ b/src/plugins/texteditor/circularclipboard.cpp @@ -0,0 +1,79 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "circularclipboard.h" + +using namespace TextEditor::Internal; + +static const int kMaxSize = 10; + +CircularClipboard::CircularClipboard() + : m_current(-1) +{} + +CircularClipboard::~CircularClipboard() +{ + qDeleteAll(m_items); +} + +CircularClipboard *CircularClipboard::instance() +{ + static CircularClipboard clipboard; + return &clipboard; +} + +void CircularClipboard::collect(const QMimeData *mimeData) +{ + if (m_items.size() > kMaxSize) { + delete m_items.last(); + m_items.removeLast(); + } + m_items.prepend(mimeData); +} + +const QMimeData *CircularClipboard::next() const +{ + if (m_items.isEmpty()) + return 0; + + if (m_current == m_items.length() - 1) + m_current = 0; + else + ++m_current; + + return m_items.at(m_current); +} + +void CircularClipboard::toLastCollect() +{ + m_current = -1; +} diff --git a/src/plugins/texteditor/circularclipboard.h b/src/plugins/texteditor/circularclipboard.h new file mode 100644 index 00000000000..c1827470be9 --- /dev/null +++ b/src/plugins/texteditor/circularclipboard.h @@ -0,0 +1,63 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef CIRCULARCLIPBOARD_H +#define CIRCULARCLIPBOARD_H + +#include +#include + +namespace TextEditor { +namespace Internal { + +class CircularClipboard +{ +public: + static CircularClipboard *instance(); + + void collect(const QMimeData *mimeData); + const QMimeData *next() const; + void toLastCollect(); + +private: + CircularClipboard(); + ~CircularClipboard(); + CircularClipboard &operator=(const CircularClipboard &); + + mutable int m_current; + QList m_items; +}; + +} // namespace Internal +} // namespace TextEditor + +#endif // CIRCULARCLIPBOARD_H diff --git a/src/plugins/texteditor/texteditor.pro b/src/plugins/texteditor/texteditor.pro index e1719f899d3..00fd2bd8e6d 100644 --- a/src/plugins/texteditor/texteditor.pro +++ b/src/plugins/texteditor/texteditor.pro @@ -110,7 +110,8 @@ SOURCES += texteditorplugin.cpp \ typingsettings.cpp \ icodestylepreferences.cpp \ codestylepool.cpp \ - codestyleeditor.cpp + codestyleeditor.cpp \ + circularclipboard.cpp HEADERS += texteditorplugin.h \ textfilewizard.h \ @@ -225,7 +226,8 @@ HEADERS += texteditorplugin.h \ icodestylepreferences.h \ codestylepool.h \ codestyleeditor.h \ - basefilefind_p.h + basefilefind_p.h \ + circularclipboard.h FORMS += \ displaysettingspage.ui \ diff --git a/src/plugins/texteditor/texteditoractionhandler.cpp b/src/plugins/texteditor/texteditoractionhandler.cpp index 457e155ccb3..1b387b92bd7 100644 --- a/src/plugins/texteditor/texteditoractionhandler.cpp +++ b/src/plugins/texteditor/texteditoractionhandler.cpp @@ -64,6 +64,7 @@ TextEditorActionHandler::TextEditorActionHandler(const char *context, m_copyAction(0), m_cutAction(0), m_pasteAction(0), + m_circularPasteAction(0), m_selectAllAction(0), m_gotoAction(0), m_printAction(0), @@ -369,6 +370,13 @@ void TextEditorActionHandler::createActions() command->setDefaultKeySequence(QKeySequence(tr("Alt+U"))); connect(m_lowerCaseSelectionAction, SIGNAL(triggered()), this, SLOT(lowercaseSelection())); + m_circularPasteAction = new QAction(tr("Paste From Circular Clipboard"), this); + m_modifyingActions << m_circularPasteAction; + command = am->registerAction(m_circularPasteAction, Constants::CIRCULAR_PASTE, m_contextId, true); + command->setDefaultKeySequence(QKeySequence(tr("Ctrl+Shift+V"))); + connect(m_circularPasteAction, SIGNAL(triggered()), this, SLOT(circularPasteAction())); + medit->addAction(command, Core::Constants::G_EDIT_COPYPASTE); + QAction *a = 0; a = new QAction(tr("Goto Line Start"), this); command = am->registerAction(a, Constants::GOTO_LINE_START, m_contextId, true); @@ -568,6 +576,7 @@ FUNCTION2(redoAction, redo) FUNCTION2(copyAction, copy) FUNCTION2(cutAction, cut) FUNCTION2(pasteAction, paste) +FUNCTION2(circularPasteAction, circularPaste) FUNCTION2(formatAction, format) FUNCTION2(rewrapParagraphAction, rewrapParagraph) FUNCTION2(selectAllAction, selectAll) diff --git a/src/plugins/texteditor/texteditoractionhandler.h b/src/plugins/texteditor/texteditoractionhandler.h index 7015a792fda..cfe24f0e1d4 100644 --- a/src/plugins/texteditor/texteditoractionhandler.h +++ b/src/plugins/texteditor/texteditoractionhandler.h @@ -98,6 +98,7 @@ private slots: void copyAction(); void cutAction(); void pasteAction(); + void circularPasteAction(); void selectAllAction(); void gotoAction(); void printAction(); @@ -166,6 +167,7 @@ private: QAction *m_copyAction; QAction *m_cutAction; QAction *m_pasteAction; + QAction *m_circularPasteAction; QAction *m_selectAllAction; QAction *m_gotoAction; QAction *m_printAction; diff --git a/src/plugins/texteditor/texteditorconstants.h b/src/plugins/texteditor/texteditorconstants.h index d1dfbcf063f..bc8806aa889 100644 --- a/src/plugins/texteditor/texteditorconstants.h +++ b/src/plugins/texteditor/texteditorconstants.h @@ -101,6 +101,7 @@ const char INFO_SYNTAX_DEFINITION[] = "TextEditor.InfoSyntaxDefinition"; const char TASK_DOWNLOAD_DEFINITIONS[] = "TextEditor.Task.Download"; const char TASK_REGISTER_DEFINITIONS[] = "TextEditor.Task.Register"; const char TASK_OPEN_FILE[] = "TextEditor.Task.OpenFile"; +const char CIRCULAR_PASTE[] = "TextEditor.CircularPaste"; // Text color and style categories const char C_TEXT[] = "Text";