diff options
author | con <qtc-commiter@nokia.com> | 2008-12-02 12:01:29 +0100 |
---|---|---|
committer | con <qtc-commiter@nokia.com> | 2008-12-02 12:01:29 +0100 |
commit | 05c35356abc31549c5db6eba31fb608c0365c2a0 (patch) | |
tree | be044530104267afaff13f8943889cb97f8c8bad /src/plugins/texteditor | |
download | qt-creator-05c35356abc31549c5db6eba31fb608c0365c2a0.tar.gz |
Initial import
Diffstat (limited to 'src/plugins/texteditor')
60 files changed, 11484 insertions, 0 deletions
diff --git a/src/plugins/texteditor/TextEditor.mimetypes.xml b/src/plugins/texteditor/TextEditor.mimetypes.xml new file mode 100644 index 0000000000..e78754da68 --- /dev/null +++ b/src/plugins/texteditor/TextEditor.mimetypes.xml @@ -0,0 +1,47 @@ +<?xml version="1.0"?> +<mime-info xmlns='http://www.freedesktop.org/standards/shared-mime-info'> + <mime-type type="text/plain"> + <comment>Plain text document</comment> + <sub-class-of type="application/octet-stream"/> + <comment xml:lang="bg">Документ с неформатиран текст</comment> + <comment xml:lang="ca">document de text pla</comment> + <comment xml:lang="cs">Prostý textový dokument</comment> + <comment xml:lang="da">ren tekst-dokument</comment> + <comment xml:lang="de">einfaches Textdokument</comment> + <comment xml:lang="el">έγγραφο απλού κειμένου</comment> + <comment xml:lang="eo">plata teksta dokumento</comment> + <comment xml:lang="es">documento de texto sencillo</comment> + <comment xml:lang="eu">testu soileko dokumentua</comment> + <comment xml:lang="fi">perustekstiasiakirja</comment> + <comment xml:lang="fr">document plein texte</comment> + <comment xml:lang="hu">egyszerű szöveg</comment> + <comment xml:lang="it">Documento in testo semplice</comment> + <comment xml:lang="ja">平文テキストドキュメント</comment> + <comment xml:lang="ko">보통 text 문서</comment> + <comment xml:lang="lt">paprastas tekstinis dokumentas</comment> + <comment xml:lang="ms">Dokumen teks jernih</comment> + <comment xml:lang="nb">vanlig tekstdokument</comment> + <comment xml:lang="nl">platte tekst document</comment> + <comment xml:lang="nn">vanleg tekstdokument</comment> + <comment xml:lang="pl">zwykły dokument tekstowy</comment> + <comment xml:lang="pt">documento em texto simples</comment> + <comment xml:lang="pt_BR">Documento somente texto</comment> + <comment xml:lang="sq">dokument teksti i thjeshtë</comment> + <comment xml:lang="sr">обичан текстуални документ</comment> + <comment xml:lang="sv">vanligt textdokument</comment> + <comment xml:lang="uk">звичайний текстовий документ</comment> + <comment xml:lang="vi">thư nhập thô (văn bản không có kiểu dáng)</comment> + <comment xml:lang="zh_CN">纯文本文档</comment> + <comment xml:lang="zh_TW">普通文本檔</comment> + <glob pattern="*.txt"/> + </mime-type> + <mime-type type="application/xml"> + <sub-class-of type="text/plain"/> + <comment>XML document</comment> + <glob pattern="*.xml"/> + <glob pattern="*.xsl"/> + <glob pattern="*.xslt"/> + <glob pattern="*.xbl"/> + <alias type="text/xml"/> + </mime-type> +</mime-info> diff --git a/src/plugins/texteditor/TextEditor.pluginspec b/src/plugins/texteditor/TextEditor.pluginspec new file mode 100644 index 0000000000..aaad622ea4 --- /dev/null +++ b/src/plugins/texteditor/TextEditor.pluginspec @@ -0,0 +1,12 @@ +<plugin name="TextEditor" version="0.9.1" compatVersion="0.9.1"> + <vendor>Nokia Corporation</vendor> + <copyright>(C) 2008 Nokia Corporation</copyright> + <license>Nokia Technology Preview License Agreement</license> + <description>Text editor framework and the implementation of the basic text editor.</description> + <url>http://www.trolltech.com/</url> + <dependencyList> + <dependency name="Core" version="0.9.1"/> + <dependency name="Find" version="0.9.1"/> + <dependency name="QuickOpen" version="0.9.1"/> + </dependencyList> +</plugin> diff --git a/src/plugins/texteditor/basefilefind.cpp b/src/plugins/texteditor/basefilefind.cpp new file mode 100644 index 0000000000..4c11a626db --- /dev/null +++ b/src/plugins/texteditor/basefilefind.cpp @@ -0,0 +1,233 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "basefilefind.h" + +#include <coreplugin/stylehelper.h> +#include <coreplugin/progressmanager/progressmanagerinterface.h> +#include <coreplugin/editormanager/editormanager.h> +#include <find/textfindconstants.h> +#include <texteditor/itexteditor.h> +#include <texteditor/basetexteditor.h> + +#include <QtDebug> +#include <QtCore/QDirIterator> +#include <QtGui/QPushButton> +#include <QtGui/QFileDialog> + +using namespace Core::Utils; +using namespace Find; +using namespace TextEditor; + +BaseFileFind::BaseFileFind(Core::ICore *core, SearchResultWindow *resultWindow) + : m_core(core), + m_resultWindow(resultWindow), + m_isSearching(false), + m_resultLabel(0), + m_filterCombo(0), + m_useRegExp(false), + m_useRegExpCheckBox(0) +{ + m_watcher.setPendingResultsLimit(1); + connect(&m_watcher, SIGNAL(resultReadyAt(int)), this, SLOT(displayResult(int))); + connect(&m_watcher, SIGNAL(finished()), this, SLOT(searchFinished())); +} + +bool BaseFileFind::isEnabled() const +{ + return !m_isSearching; +} + +QStringList BaseFileFind::fileNameFilters() const +{ + QStringList filters; + if (m_filterCombo && !m_filterCombo->currentText().isEmpty()) { + QStringList parts = m_filterCombo->currentText().split(","); + foreach (const QString &part, parts) { + QString filter = part.trimmed(); + if (!filter.isEmpty()) { + filters << filter; + } + } + } + return filters; +} + +void BaseFileFind::findAll(const QString &txt, QTextDocument::FindFlags findFlags) +{ + m_isSearching = true; + emit changed(); + updateComboEntries(m_filterCombo, false); + m_watcher.setFuture(QFuture<FileSearchResult>()); + m_resultWindow->clearContents(); + m_resultWindow->popup(true); + if (m_useRegExp) + m_watcher.setFuture(Core::Utils::findInFilesRegExp(txt, files(), findFlags)); + else + m_watcher.setFuture(Core::Utils::findInFiles(txt, files(), findFlags)); + Core::FutureProgress *progress = m_core->progressManager()->addTask(m_watcher.future(), + "Search", + Constants::TASK_SEARCH); + progress->setWidget(createProgressWidget()); + connect(progress, SIGNAL(clicked()), m_resultWindow, SLOT(popup())); +} + +void BaseFileFind::displayResult(int index) { + Core::Utils::FileSearchResult result = m_watcher.future().resultAt(index); + ResultWindowItem *item = m_resultWindow->addResult(result.fileName, + result.lineNumber, + result.matchingLine, + result.matchStart, + result.matchLength); + if (item) + connect(item, SIGNAL(activated(const QString&,int,int)), this, SLOT(openEditor(const QString&,int,int))); + + if (m_resultLabel) + m_resultLabel->setText(tr("%1 found").arg(m_resultWindow->numberOfResults())); +} + +void BaseFileFind::searchFinished() +{ + m_isSearching = false; + m_resultLabel = 0; + emit changed(); +} + +QWidget *BaseFileFind::createProgressWidget() +{ + m_resultLabel = new QLabel; + // ### TODO this setup should be done by style + QFont f = m_resultLabel->font(); + f.setBold(true); + f.setPointSizeF(StyleHelper::sidebarFontSize()); + m_resultLabel->setFont(f); + m_resultLabel->setPalette(StyleHelper::sidebarFontPalette(m_resultLabel->palette())); + m_resultLabel->setText(tr("%1 found").arg(m_resultWindow->numberOfResults())); + return m_resultLabel; +} + +QWidget *BaseFileFind::createPatternWidget() +{ +/* + QWidget *widget = new QWidget; + QHBoxLayout *hlayout = new QHBoxLayout(widget); + hlayout->setMargin(0); + widget->setLayout(hlayout); +*/ + QString filterToolTip = tr("List of comma separated wildcard filters"); +/* + QLabel *label = new QLabel(tr("File pattern:")); + label->setToolTip(filterToolTip); +*/ +/* + hlayout->addWidget(label); +*/ + m_filterCombo = new QComboBox; + m_filterCombo->setEditable(true); + m_filterCombo->setModel(&m_filterStrings); + m_filterCombo->setMaxCount(10); + m_filterCombo->setMinimumContentsLength(10); + m_filterCombo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); + m_filterCombo->setInsertPolicy(QComboBox::InsertAtBottom); + m_filterCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + m_filterCombo->setToolTip(filterToolTip); + syncComboWithSettings(m_filterCombo, m_filterSetting); +/* + hlayout->addWidget(m_filterCombo); +*/ + return m_filterCombo; +} + +QWidget *BaseFileFind::createRegExpWidget() +{ + m_useRegExpCheckBox = new QCheckBox(tr("Use Regular Expressions")); + m_useRegExpCheckBox->setChecked(m_useRegExp); + connect(m_useRegExpCheckBox, SIGNAL(toggled(bool)), this, SLOT(syncRegExpSetting(bool))); + return m_useRegExpCheckBox; +} + +void BaseFileFind::writeCommonSettings(QSettings *settings) +{ + settings->setValue("filters", m_filterStrings.stringList()); + if (m_filterCombo) + settings->setValue("currentFilter", m_filterCombo->currentText()); + settings->setValue("useRegExp", m_useRegExp); +} + +void BaseFileFind::readCommonSettings(QSettings *settings, const QString &defaultFilter) +{ + QStringList filters = settings->value("filters").toStringList(); + m_filterSetting = settings->value("currentFilter").toString(); + m_useRegExp = settings->value("useRegExp", false).toBool(); + if (m_useRegExpCheckBox) + m_useRegExpCheckBox->setChecked(m_useRegExp); + if (filters.isEmpty()) + filters << defaultFilter; + if (m_filterSetting.isEmpty()) + m_filterSetting = filters.first(); + m_filterStrings.setStringList(filters); + syncComboWithSettings(m_filterCombo, m_filterSetting); +} + +void BaseFileFind::syncComboWithSettings(QComboBox *combo, const QString &setting) +{ + if (!combo) + return; + int index = combo->findText(setting); + if (index < 0) + combo->setEditText(setting); + else + combo->setCurrentIndex(index); +} + +void BaseFileFind::updateComboEntries(QComboBox *combo, bool onTop) +{ + int index = combo->findText(combo->currentText()); + if (index < 0) { + if (onTop) { + combo->insertItem(0, combo->currentText()); + } else { + combo->addItem(combo->currentText()); + } + combo->setCurrentIndex(combo->findText(combo->currentText())); + } +} + +void BaseFileFind::syncRegExpSetting(bool useRegExp) +{ + m_useRegExp = useRegExp; +} + +void BaseFileFind::openEditor(const QString &fileName, int line, int column) +{ + TextEditor::BaseTextEditor::openEditorAt(fileName, line, column); +} diff --git a/src/plugins/texteditor/basefilefind.h b/src/plugins/texteditor/basefilefind.h new file mode 100644 index 0000000000..562a4487cc --- /dev/null +++ b/src/plugins/texteditor/basefilefind.h @@ -0,0 +1,95 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef BASEFILEFIND_H +#define BASEFILEFIND_H + +#include "texteditor_global.h" + +#include <coreplugin/icore.h> +#include <find/ifindfilter.h> +#include <find/searchresultwindow.h> +#include <utils/filesearch.h> + +#include <QtCore/QFutureWatcher> +#include <QtCore/QPointer> +#include <QtGui/QLabel> +#include <QtGui/QComboBox> +#include <QtGui/QStringListModel> +#include <QtGui/QCheckBox> + +namespace TextEditor { + +class TEXTEDITOR_EXPORT BaseFileFind : public Find::IFindFilter +{ + Q_OBJECT + +public: + BaseFileFind(Core::ICore *core, Find::SearchResultWindow *resultWindow); + + bool isEnabled() const; + void findAll(const QString &txt, QTextDocument::FindFlags findFlags); + +protected: + virtual QStringList files() = 0; + void writeCommonSettings(QSettings *settings); + void readCommonSettings(QSettings *settings, const QString &defaultFilter); + QWidget *createPatternWidget(); + QWidget *createRegExpWidget(); + void syncComboWithSettings(QComboBox *combo, const QString &setting); + void updateComboEntries(QComboBox *combo, bool onTop); + QStringList fileNameFilters() const; + +private slots: + void displayResult(int index); + void searchFinished(); + void openEditor(const QString &fileName, int line, int column); + void syncRegExpSetting(bool useRegExp); + +private: + QWidget *createProgressWidget(); + + Core::ICore *m_core; + Find::SearchResultWindow *m_resultWindow; + QFutureWatcher<Core::Utils::FileSearchResult> m_watcher; + bool m_isSearching; + QLabel *m_resultLabel; + QStringListModel m_filterStrings; + QString m_filterSetting; + QPointer<QComboBox> m_filterCombo; + bool m_useRegExp; + QCheckBox *m_useRegExpCheckBox; +}; + +} // namespace TextEditor + +#endif // BASEFILEFIND_H diff --git a/src/plugins/texteditor/basetextdocument.cpp b/src/plugins/texteditor/basetextdocument.cpp new file mode 100644 index 0000000000..916bc11f2f --- /dev/null +++ b/src/plugins/texteditor/basetextdocument.cpp @@ -0,0 +1,349 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "basetextdocument.h" +#include "basetexteditor.h" +#include "storagesettings.h" + +#include <QtCore/QFile> +#include <QtCore/QFileInfo> +#include <QtCore/QTextStream> +#include <QtCore/QTextCodec> +#include <QtGui/QMainWindow> +#include <QtGui/QSyntaxHighlighter> +#include <QtGui/QApplication> + +#ifndef TEXTEDITOR_STANDALONE +#include <utils/reloadpromptutils.h> +#include "coreplugin/icore.h" +#endif + +using namespace TextEditor; + +#if defined (Q_OS_WIN) +# define NATIVE_LINE_TERMINATOR CRLFLineTerminator +#else +# define NATIVE_LINE_TERMINATOR LFLineTerminator +#endif + +DocumentMarker::DocumentMarker(QTextDocument *doc) + : ITextMarkable(doc), document(doc) +{ +} + +BaseTextDocument::BaseTextDocument() + : m_document(new QTextDocument(this)), + m_highlighter(0) +{ + m_documentMarker = new DocumentMarker(m_document); + m_lineTerminatorMode = NativeLineTerminator; + m_isBinaryData = false; + m_codec = QTextCodec::codecForLocale(); + m_hasDecodingError = false; +} + +BaseTextDocument::~BaseTextDocument() +{ + QTextBlock block = m_document->begin(); + while (block.isValid()) { + if (TextBlockUserData *data = static_cast<TextBlockUserData *>(block.userData())) + data->documentClosing(); + block = block.next(); + } + delete m_document; + m_document = 0; +} + +QString BaseTextDocument::mimeType() const +{ + return m_mimeType; +} + +void BaseTextDocument::setMimeType(const QString &mt) +{ + m_mimeType = mt; +} + +bool BaseTextDocument::save(const QString &fileName) +{ + QTextCursor cursor(m_document); + + cursor.beginEditBlock(); + if (m_storageSettings.m_cleanWhitespace) + cleanWhitespace(cursor, m_storageSettings.m_inEntireDocument); + if (m_storageSettings.m_addFinalNewLine) + ensureFinalNewLine(cursor); + cursor.endEditBlock(); + + QString fName = m_fileName; + if (!fileName.isEmpty()) + fName = fileName; + + QFile file(fName); + if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) + return false; + + QString plainText = m_document->toPlainText(); + + if (m_lineTerminatorMode == CRLFLineTerminator) + plainText.replace(QLatin1Char('\n'), QLatin1String("\r\n")); + + file.write(m_codec->fromUnicode(plainText)); + if (!file.flush()) + return false; + file.close(); + + const QFileInfo fi(fName); + m_fileName = fi.absoluteFilePath(); + + m_document->setModified(false); + emit titleChanged(fi.fileName()); + emit changed(); + + m_isBinaryData = false; + m_hasDecodingError = false; + m_decodingErrorSample.clear(); + + return true; +} + +bool BaseTextDocument::isReadOnly() const +{ + if (m_isBinaryData || m_hasDecodingError) + return true; + if (m_fileName.isEmpty()) //have no corresponding file, so editing is ok + return false; + const QFileInfo fi(m_fileName); + return !fi.isWritable(); +} + +bool BaseTextDocument::isModified() const +{ + return m_document->isModified(); +} + +bool BaseTextDocument::open(const QString &fileName) +{ + QString title = tr("untitled"); + if (!fileName.isEmpty()) { + const QFileInfo fi(fileName); + m_fileName = fi.absoluteFilePath(); + + QFile file(fileName); + if (!file.exists()) + return false; + + if (!fi.isReadable()) + return false; + + if (!fi.isWritable()) { + if (!file.open(QIODevice::ReadOnly)) + return false; + } else { + if (!file.open(QIODevice::ReadWrite)) + return false; + } + title = fi.fileName(); + + QByteArray buf = file.readAll(); + int bytesRead = buf.size(); + + QTextCodec *codec = m_codec; + + // code taken from qtextstream + if (bytesRead >= 4 && ((uchar(buf[0]) == 0xff && uchar(buf[1]) == 0xfe && uchar(buf[2]) == 0 && uchar(buf[3]) == 0) + || (uchar(buf[0]) == 0 && uchar(buf[1]) == 0 && uchar(buf[2]) == 0xfe && uchar(buf[3]) == 0xff))) { + codec = QTextCodec::codecForName("UTF-32"); + } else if (bytesRead >= 2 && ((uchar(buf[0]) == 0xff && uchar(buf[1]) == 0xfe) + || (uchar(buf[0]) == 0xfe && uchar(buf[1]) == 0xff))) { + codec = QTextCodec::codecForName("UTF-16"); + } else if (!codec) { + codec = QTextCodec::codecForLocale(); + } + // end code taken from qtextstream + + m_codec = codec; + +#if 0 // should work, but does not, Qt bug with "system" codec + QTextDecoder *decoder = m_codec->makeDecoder(); + QString text = decoder->toUnicode(buf); + m_hasDecodingError = (decoder->hasFailure()); + delete decoder; +#else + QString text = m_codec->toUnicode(buf); + QByteArray verifyBuf = m_codec->fromUnicode(text); // slow + // the minSize trick lets us ignore unicode headers + int minSize = qMin(verifyBuf.size(), buf.size()); + m_hasDecodingError = (minSize < buf.size()- 4 + || memcmp(verifyBuf.constData() + verifyBuf.size() - minSize, + buf.constData() + buf.size() - minSize, minSize)); +#endif + + if (m_hasDecodingError) { + int p = buf.indexOf('\n', 16384); + if (p < 0) + m_decodingErrorSample = buf; + else + m_decodingErrorSample = buf.left(p); + } else { + m_decodingErrorSample.clear(); + } + + int lf = text.indexOf('\n'); + if (lf > 0 && text.at(lf-1) == QLatin1Char('\r')) { + m_lineTerminatorMode = CRLFLineTerminator; + } else if (lf >= 0) { + m_lineTerminatorMode = LFLineTerminator; + } else { + m_lineTerminatorMode = NativeLineTerminator; + } + + m_document->setModified(false); + m_document->setUndoRedoEnabled(false); + if (m_isBinaryData) + m_document->setHtml(tr("<em>Binary data</em>")); + else + m_document->setPlainText(text); + m_document->setUndoRedoEnabled(true); + TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(m_document->documentLayout()); + Q_ASSERT(documentLayout); + documentLayout->lastSaveRevision = 0; + m_document->setModified(false); + emit titleChanged(title); + emit changed(); + } + return true; +} + +void BaseTextDocument::reload(QTextCodec *codec) +{ + Q_ASSERT(codec); + m_codec = codec; + reload(); +} + +void BaseTextDocument::reload() +{ + emit aboutToReload(); + if (open(m_fileName)) { + emit reloaded(); + } +} + +void BaseTextDocument::modified(Core::IFile::ReloadBehavior *behavior) +{ + switch (*behavior) { + case Core::IFile::ReloadNone: + return; + case Core::IFile::ReloadAll: + reload(); + return; + case Core::IFile::ReloadPermissions: + emit changed(); + return; + case Core::IFile::AskForReload: + break; + } + +#ifndef TEXTEDITOR_STANDALONE + switch (Core::Utils::reloadPrompt(m_fileName, QApplication::activeWindow())) { + case Core::Utils::ReloadCurrent: + reload(); + break; + case Core::Utils::ReloadAll: + reload(); + *behavior = Core::IFile::ReloadAll; + break; + case Core::Utils::ReloadSkipCurrent: + break; + case Core::Utils::ReloadNone: + *behavior = Core::IFile::ReloadNone; + break; + } +#endif +} + +void BaseTextDocument::setSyntaxHighlighter(QSyntaxHighlighter *highlighter) +{ + if (m_highlighter) + delete m_highlighter; + m_highlighter = highlighter; + m_highlighter->setParent(this); + m_highlighter->setDocument(m_document); +} + +void BaseTextDocument::cleanWhitespace(QTextCursor& cursor, bool inEntireDocument) +{ + + TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(m_document->documentLayout()); + + QTextBlock block = m_document->firstBlock(); + while (block.isValid()) { + + if (inEntireDocument || block.revision() > documentLayout->lastSaveRevision) { + + QString blockText = block.text(); + if (int trailing = m_tabSettings.trailingWhitespaces(blockText)) { + cursor.setPosition(block.position() + block.length() - 1); + cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor, trailing); + cursor.removeSelectedText(); + } + if (!m_tabSettings.isIndentationClean(blockText)) { + cursor.setPosition(block.position()); + int firstNonSpace = m_tabSettings.firstNonSpace(blockText); + if (firstNonSpace == blockText.length()) { + cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + } else { + int column = m_tabSettings.columnAt(blockText, firstNonSpace); + cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, firstNonSpace); + QString indentationString = m_tabSettings.indentationString(0, column); + cursor.insertText(indentationString); + } + } + } + + block = block.next(); + } +} + +void BaseTextDocument::ensureFinalNewLine(QTextCursor& cursor) +{ + cursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor); + bool emptyFile = !cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor); + + if (!emptyFile && cursor.selectedText().at(0) != QChar::ParagraphSeparator) + { + cursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor); + cursor.insertText(QLatin1String("\n")); + } +} diff --git a/src/plugins/texteditor/basetextdocument.h b/src/plugins/texteditor/basetextdocument.h new file mode 100644 index 0000000000..8dbfda2ea8 --- /dev/null +++ b/src/plugins/texteditor/basetextdocument.h @@ -0,0 +1,161 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef BASETEXTDOCUMENT_H +#define BASETEXTDOCUMENT_H + +#include "texteditor_global.h" +#include "storagesettings.h" +#include "itexteditor.h" +#include "tabsettings.h" + +QT_BEGIN_NAMESPACE +class QTextCursor; +class QTextDocument; +class QSyntaxHighlighter; +QT_END_NAMESPACE + + +namespace Core { class ICore; } + +namespace TextEditor { + + +class DocumentMarker : public ITextMarkable +{ + Q_OBJECT +public: + DocumentMarker(QTextDocument *); + + // ITextMarkable + bool addMark(ITextMark *mark, int line); + TextMarks marksAt(int line) const; + void removeMark(ITextMark *mark); + bool hasMark(ITextMark *mark) const; + void updateMark(ITextMark *mark); + +private: + QTextDocument *document; +}; + + + +class TEXTEDITOR_EXPORT BaseTextDocument + : public Core::IFile +{ + Q_OBJECT + +public: + BaseTextDocument(); + ~BaseTextDocument(); + void setStorageSettings(const StorageSettings &storageSettings) { m_storageSettings = storageSettings; } + void setTabSettings(const TabSettings &tabSettings) { m_tabSettings = tabSettings; } + + inline const StorageSettings &storageSettings() const { return m_storageSettings; } + inline const TabSettings &tabSettings() const { return m_tabSettings; } + + DocumentMarker *documentMarker() const {return m_documentMarker; } + + //IFile + virtual bool save(const QString &fileName = QString()); + virtual QString fileName() const { return m_fileName; } + virtual bool isReadOnly() const; + virtual bool isModified() const; + virtual bool isSaveAsAllowed() const { return true; } + virtual void modified(Core::IFile::ReloadBehavior *behavior); + virtual QString mimeType() const; + void setMimeType(const QString &mt); + + virtual QString defaultPath() const { return m_defaultPath; } + virtual QString suggestedFileName() const { return m_suggestedFileName; } + + void setDefaultPath(const QString &defaultPath) { m_defaultPath = defaultPath; } + void setSuggestedFileName(const QString &suggestedFileName) { m_suggestedFileName = suggestedFileName; } + + virtual bool open(const QString &fileName = QString()); + virtual void reload(); + + QTextDocument *document() const { return m_document; } + void setSyntaxHighlighter(QSyntaxHighlighter *highlighter); + QSyntaxHighlighter *syntaxHighlighter() const { return m_highlighter; } + + + inline bool isBinaryData() const { return m_isBinaryData; } + inline bool hasDecodingError() const { return m_hasDecodingError || m_isBinaryData; } + inline QTextCodec *codec() const { return m_codec; } + inline void setCodec(QTextCodec *c) { m_codec = c; } + inline QByteArray decodingErrorSample() const { return m_decodingErrorSample; } + + void reload(QTextCodec *codec); + +signals: + void titleChanged(QString title); + void changed(); + void aboutToReload(); + void reloaded(); + +private: + QString m_fileName; + QString m_defaultPath; + QString m_suggestedFileName; + QString m_mimeType; + StorageSettings m_storageSettings; + TabSettings m_tabSettings; + Core::ICore *m_core; + QTextDocument *m_document; + DocumentMarker *m_documentMarker; + QSyntaxHighlighter *m_highlighter; + + enum LineTerminatorMode { + LFLineTerminator, + CRLFLineTerminator, + NativeLineTerminator = +#if defined (Q_OS_WIN) + CRLFLineTerminator +#else + LFLineTerminator +#endif + }; + LineTerminatorMode m_lineTerminatorMode; + QTextCodec *m_codec; + + bool m_isBinaryData; + bool m_hasDecodingError; + QByteArray m_decodingErrorSample; + + void cleanWhitespace(QTextCursor& cursor, bool onlyInModifiedLines); + void ensureFinalNewLine(QTextCursor& cursor); +}; + +} // namespace TextEditor + +#endif diff --git a/src/plugins/texteditor/basetexteditor.cpp b/src/plugins/texteditor/basetexteditor.cpp new file mode 100644 index 0000000000..a112770f93 --- /dev/null +++ b/src/plugins/texteditor/basetexteditor.cpp @@ -0,0 +1,3453 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "texteditor_global.h" +#include "texteditorconstants.h" +#ifndef TEXTEDITOR_STANDALONE +#include "texteditorplugin.h" +#include "completionsupport.h" +#endif +#include "basetextdocument.h" +#include "basetexteditor_p.h" +#include "codecselector.h" + +#ifndef TEXTEDITOR_STANDALONE +#include <coreplugin/icore.h> +#include <coreplugin/manhattanstyle.h> +#include <coreplugin/coreconstants.h> +#include <coreplugin/editormanager/editormanager.h> +#include <find/basetextfind.h> +#include <texteditor/fontsettings.h> +#include <utils/reloadpromptutils.h> +#include <aggregation/aggregate.h> +#endif +#include <utils/linecolumnlabel.h> + +#include <QtCore/QCoreApplication> +#include <QtCore/QTextCodec> +#include <QtCore/QFile> +#include <QtCore/QDebug> +#include <QtCore/QTimer> +#include <QtGui/QAbstractTextDocumentLayout> +#include <QtGui/QApplication> +#include <QtGui/QKeyEvent> +#include <QtGui/QLabel> +#include <QtGui/QLayout> +#include <QtGui/QPainter> +#include <QtGui/QPrinter> +#include <QtGui/QPrintDialog> +#include <QtGui/QPainter> +#include <QtGui/QScrollBar> +#include <QtGui/QShortcut> +#include <QtGui/QScrollBar> +#include <QtGui/QStyle> +#include <QtGui/QSyntaxHighlighter> +#include <QtGui/QTextCursor> +#include <QtGui/QTextBlock> +#include <QtGui/QTextLayout> +#include <QtGui/QToolBar> +#include <QtGui/QToolTip> +#include <QtGui/QInputDialog> + +using namespace TextEditor; +using namespace TextEditor::Internal; + + +namespace TextEditor { + namespace Internal { + +class TextEditExtraArea : public QWidget { + BaseTextEditor *textEdit; +public: + TextEditExtraArea(BaseTextEditor *edit):QWidget(edit) { + textEdit = edit; + setAutoFillBackground(true); + } +public: + + QSize sizeHint() const { + return QSize(textEdit->extraAreaWidth(), 0); + } +protected: + void paintEvent(QPaintEvent *event){ + textEdit->extraAreaPaintEvent(event); + } + void mousePressEvent(QMouseEvent *event){ + textEdit->extraAreaMouseEvent(event); + } + void mouseMoveEvent(QMouseEvent *event){ + textEdit->extraAreaMouseEvent(event); + } + void mouseReleaseEvent(QMouseEvent *event){ + textEdit->extraAreaMouseEvent(event); + } + void leaveEvent(QEvent *event){ + textEdit->extraAreaLeaveEvent(event); + } + + void wheelEvent(QWheelEvent *event) { + QCoreApplication::sendEvent(textEdit->viewport(), event); + } +}; + + } +} + +ITextEditor *BaseTextEditor::openEditorAt(const QString &fileName, + int line, + int column) +{ + Core::EditorManager *editorManager = + ExtensionSystem::PluginManager::instance()->getObject<Core::ICore>()->editorManager(); + editorManager->addCurrentPositionToNavigationHistory(true); + Core::IEditor *editor = editorManager->openEditor(fileName, QString(), true); + TextEditor::ITextEditor *texteditor = qobject_cast<TextEditor::ITextEditor *>(editor); + if (texteditor) { + texteditor->gotoLine(line, column); + editorManager->addCurrentPositionToNavigationHistory(); + return texteditor; + } + return 0; +} + +BaseTextEditor::BaseTextEditor(QWidget *parent) + : QPlainTextEdit(parent) +{ + d = new BaseTextEditorPrivate(); + d->q = this; + d->m_extraArea = new TextEditExtraArea(this); + d->m_extraArea->setMouseTracking(true); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + + d->setupDocumentSignals(d->m_document); + d->setupDocumentSignals(d->m_document); + + d->m_lastScrollPos = -1; + setCursorWidth(2); + + + // from RESEARCH + + setLayoutDirection(Qt::LeftToRight); + viewport()->setMouseTracking(true); + d->extraAreaSelectionAnchorBlockNumber + = d->extraAreaToggleMarkBlockNumber + = d->extraAreaHighlightCollapseBlockNumber + = d->extraAreaHighlightFadingBlockNumber + = -1; + d->extraAreaCollapseAlpha = 255; + + d->visibleCollapsedBlockNumber = d->suggestedVisibleCollapsedBlockNumber = -1; + + connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(slotUpdateExtraAreaWidth())); + connect(this, SIGNAL(modificationChanged(bool)), this, SLOT(slotModificationChanged(bool))); + connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(slotCursorPositionChanged())); + connect(this, SIGNAL(updateRequest(QRect, int)), this, SLOT(slotUpdateRequest(QRect, int))); + connect(this, SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged())); + +// (void) new QShortcut(tr("CTRL+L"), this, SLOT(centerCursor()), 0, Qt::WidgetShortcut); +// (void) new QShortcut(tr("F9"), this, SLOT(slotToggleMark()), 0, Qt::WidgetShortcut); +// (void) new QShortcut(tr("F11"), this, SLOT(slotToggleBlockVisible())); + + + // parentheses matcher + d->m_parenthesesMatchingEnabled = false; + d->m_formatRange = true; + d->m_matchFormat.setForeground(Qt::red); + d->m_rangeFormat.setBackground(QColor(0xb4, 0xee, 0xb4)); + d->m_mismatchFormat.setBackground(Qt::magenta); + d->m_parenthesesMatchingTimer = new QTimer(this); + d->m_parenthesesMatchingTimer->setSingleShot(true); + connect(d->m_parenthesesMatchingTimer, SIGNAL(timeout()), this, SLOT(_q_matchParentheses())); + + + d->m_searchResultFormat.setBackground(QColor(0xffef0b)); + + slotUpdateExtraAreaWidth(); + slotCursorPositionChanged(); + setFrameStyle(QFrame::NoFrame); + + + d->extraAreaTimeLine = new QTimeLine(150, this); + d->extraAreaTimeLine->setFrameRange(0, 255); + connect(d->extraAreaTimeLine, SIGNAL(frameChanged(int)), this, + SLOT(setCollapseIndicatorAlpha(int))); + + + connect(Core::EditorManager::instance(), SIGNAL(currentEditorChanged(Core::IEditor*)), + this, SLOT(currentEditorChanged(Core::IEditor*))); +} + +BaseTextEditor::~BaseTextEditor() +{ + delete d; + d = 0; +} + +QString BaseTextEditor::mimeType() const +{ + return d->m_document->mimeType(); +} + +void BaseTextEditor::setMimeType(const QString &mt) +{ + d->m_document->setMimeType(mt); +} + +void BaseTextEditor::print(QPrinter *printer) +{ + const bool oldFullPage = printer->fullPage(); + printer->setFullPage(true); + QPrintDialog *dlg = new QPrintDialog(printer, this); + dlg->setWindowTitle(tr("Print Document")); + if (dlg->exec() == QDialog::Accepted) { + d->print(printer); + } + printer->setFullPage(oldFullPage); + delete dlg; +} + +static void printPage(int index, QPainter *painter, const QTextDocument *doc, + const QRectF &body, const QRectF &titleBox, + const QString &title) +{ + painter->save(); + + painter->translate(body.left(), body.top() - (index - 1) * body.height()); + QRectF view(0, (index - 1) * body.height(), body.width(), body.height()); + + QAbstractTextDocumentLayout *layout = doc->documentLayout(); + QAbstractTextDocumentLayout::PaintContext ctx; + + painter->setFont(QFont(doc->defaultFont())); + QRectF box = titleBox.translated(0, view.top()); + int dpix = painter->device()->logicalDpiX(); + int dpiy = painter->device()->logicalDpiY(); + int mx = 5 * dpix / 72.0; + int my = 2 * dpiy / 72.0; + painter->fillRect(box.adjusted(-mx, -my, mx, my), QColor(210, 210, 210)); + if (!title.isEmpty()) + painter->drawText(box, Qt::AlignCenter, title); + const QString pageString = QString::number(index); + painter->drawText(box, Qt::AlignRight, pageString); + + painter->setClipRect(view); + ctx.clip = view; + // don't use the system palette text as default text color, on HP/UX + // for example that's white, and white text on white paper doesn't + // look that nice + ctx.palette.setColor(QPalette::Text, Qt::black); + + layout->draw(painter, ctx); + + painter->restore(); +} + +void BaseTextEditorPrivate::print(QPrinter *printer) +{ + + QTextDocument *doc = q->document(); + + QString title = q->displayName(); + if (title.isEmpty()) + printer->setDocName(title); + + + QPainter p(printer); + + // Check that there is a valid device to print to. + if (!p.isActive()) + return; + + doc = doc->clone(doc); + + QTextOption opt = doc->defaultTextOption(); + opt.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + doc->setDefaultTextOption(opt); + + (void)doc->documentLayout(); // make sure that there is a layout + + + QColor background = q->palette().color(QPalette::Base); + bool backgroundIsDark = background.value() < 128; + + for (QTextBlock srcBlock = q->document()->firstBlock(), dstBlock = doc->firstBlock(); + srcBlock.isValid() && dstBlock.isValid(); + srcBlock = srcBlock.next(), dstBlock = dstBlock.next()) { + + + QList<QTextLayout::FormatRange> formatList = srcBlock.layout()->additionalFormats(); + if (backgroundIsDark) { + // adjust syntax highlighting colors for better contrast + for (int i = formatList.count() - 1; i >=0; --i) { + QTextCharFormat &format = formatList[i].format; + if (format.background().color() == background) { + QBrush brush = format.foreground(); + QColor color = brush.color(); + int h,s,v,a; + color.getHsv(&h, &s, &v, &a); + color.setHsv(h, s, qMin(128, v), a); + brush.setColor(color); + format.setForeground(brush); + } + format.setBackground(Qt::white); + } + } + + dstBlock.layout()->setAdditionalFormats(formatList); + } + + QAbstractTextDocumentLayout *layout = doc->documentLayout(); + layout->setPaintDevice(p.device()); + + int dpiy = p.device()->logicalDpiY(); + int margin = (int) ((2/2.54)*dpiy); // 2 cm margins + + QTextFrameFormat fmt = doc->rootFrame()->frameFormat(); + fmt.setMargin(margin); + doc->rootFrame()->setFrameFormat(fmt); + + QRectF pageRect(printer->pageRect()); + QRectF body = QRectF(0, 0, pageRect.width(), pageRect.height()); + QFontMetrics fontMetrics(doc->defaultFont(), p.device()); + + QRectF titleBox(margin, + body.top() + margin + - fontMetrics.height() + - 6 * dpiy / 72.0, + body.width() - 2*margin, + fontMetrics.height()); + doc->setPageSize(body.size()); + + int docCopies; + int pageCopies; + if (printer->collateCopies() == true){ + docCopies = 1; + pageCopies = printer->numCopies(); + } else { + docCopies = printer->numCopies(); + pageCopies = 1; + } + + int fromPage = printer->fromPage(); + int toPage = printer->toPage(); + bool ascending = true; + + if (fromPage == 0 && toPage == 0) { + fromPage = 1; + toPage = doc->pageCount(); + } + // paranoia check + fromPage = qMax(1, fromPage); + toPage = qMin(doc->pageCount(), toPage); + + if (printer->pageOrder() == QPrinter::LastPageFirst) { + int tmp = fromPage; + fromPage = toPage; + toPage = tmp; + ascending = false; + } + + for (int i = 0; i < docCopies; ++i) { + + int page = fromPage; + while (true) { + for (int j = 0; j < pageCopies; ++j) { + if (printer->printerState() == QPrinter::Aborted + || printer->printerState() == QPrinter::Error) + goto UserCanceled; + printPage(page, &p, doc, body, titleBox, title); + if (j < pageCopies - 1) + printer->newPage(); + } + + if (page == toPage) + break; + + if (ascending) + ++page; + else + --page; + + printer->newPage(); + } + + if ( i < docCopies - 1) + printer->newPage(); + } + +UserCanceled: + delete doc; +} + + +bool DocumentMarker::addMark(TextEditor::ITextMark *mark, int line) +{ + Q_ASSERT(line >= 1); + int blockNumber = line - 1; + TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(document->documentLayout()); + Q_ASSERT(documentLayout); + QTextBlock block = document->findBlockByNumber(blockNumber); + + if (block.isValid()) { + TextBlockUserData *userData = TextEditDocumentLayout::userData(block); + userData->addMark(mark); + mark->updateLineNumber(blockNumber + 1); + mark->updateBlock(block); + documentLayout->hasMarks = true; + documentLayout->requestUpdate(); + return true; + } + return false; +} + + +TextEditor::TextMarks DocumentMarker::marksAt(int line) const +{ + Q_ASSERT(line >= 1); + int blockNumber = line - 1; + QTextBlock block = document->findBlockByNumber(blockNumber); + + if (block.isValid()) { + if (TextBlockUserData *userData = TextEditDocumentLayout::testUserData(block)) + return userData->marks(); + } + return TextMarks(); +} + +void DocumentMarker::removeMark(TextEditor::ITextMark *mark) +{ + bool needUpdate = false; + QTextBlock block = document->begin(); + while (block.isValid()) { + if (TextBlockUserData *data = static_cast<TextBlockUserData *>(block.userData())) { + needUpdate |= data->removeMark(mark); + } + block = block.next(); + } + if (needUpdate) + updateMark(0); +} + + +bool DocumentMarker::hasMark(TextEditor::ITextMark *mark) const +{ + QTextBlock block = document->begin(); + while (block.isValid()) { + if (TextBlockUserData *data = static_cast<TextBlockUserData *>(block.userData())) { + if (data->hasMark(mark)) + return true; + } + block = block.next(); + } + return false; +} + +ITextMarkable *BaseTextEditor::markableInterface() const +{ + return baseTextDocument()->documentMarker(); +} + +ITextEditable *BaseTextEditor::editableInterface() const +{ + if (!d->m_editable) { + d->m_editable = const_cast<BaseTextEditor*>(this)->createEditableInterface(); + connect(this, SIGNAL(textChanged()), + d->m_editable, SIGNAL(contentsChanged())); + connect(this, SIGNAL(changed()), + d->m_editable, SIGNAL(changed())); + connect(this, + SIGNAL(markRequested(TextEditor::ITextEditor *, int)), + d->m_editable, + SIGNAL(markRequested(TextEditor::ITextEditor *, int))); + } + return d->m_editable; +} + + +void BaseTextEditor::currentEditorChanged(Core::IEditor *editor) +{ + if (editor == d->m_editable) { + if (d->m_document->hasDecodingError()) { + Core::EditorManager::instance()->showEditorInfoBar(QLatin1String(Constants::SELECT_ENCODING), + tr("<b>Error:</b> Could not decode \"%1\" with \"%2\"-encoding. Editing not possible.").arg(displayName()).arg(QString::fromLatin1(d->m_document->codec()->name())), + tr("Select Encoding"), + this, SLOT(selectEncoding())); + } + } +} + +void BaseTextEditor::selectEncoding() +{ + BaseTextDocument *doc = d->m_document; + CodecSelector codecSelector(this, doc); + + switch (codecSelector.exec()) { + case CodecSelector::Reload: + doc->reload(codecSelector.selectedCodec()); + setReadOnly(d->m_document->hasDecodingError()); + if (doc->hasDecodingError()) + currentEditorChanged(Core::EditorManager::instance()->currentEditor()); + else + Core::EditorManager::instance()->hideEditorInfoBar(QLatin1String(Constants::SELECT_ENCODING)); + break; + case CodecSelector::Save: + doc->setCodec(codecSelector.selectedCodec()); + Core::EditorManager::instance()->saveEditor(editableInterface()); + break; + case CodecSelector::Cancel: + break; + } +} + + +void DocumentMarker::updateMark(ITextMark *mark) +{ + TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(document->documentLayout()); + Q_ASSERT(documentLayout); + Q_UNUSED(mark); + documentLayout->requestUpdate(); +} + + +void BaseTextEditor::triggerCompletions() +{ + emit requestAutoCompletion(editableInterface(), true); +} + +bool BaseTextEditor::createNew(const QString &contents) +{ + setPlainText(contents); + document()->setModified(false); + return true; +} + +bool BaseTextEditor::open(const QString &fileName) +{ + if (d->m_document->open(fileName)) { + moveCursor(QTextCursor::Start); + setReadOnly(d->m_document->hasDecodingError()); + return true; + } + return false; +} + +Core::IFile * BaseTextEditor::file() +{ + return d->m_document; +} + +void BaseTextEditor::editorContentsChange(int position, int charsRemoved, int charsAdded) +{ + d->m_contentsChanged = true; + + // Keep the line numbers and the block information for the text marks updated + if (charsRemoved != 0) { + d->updateMarksLineNumber(); + d->updateMarksBlock(document()->findBlock(position)); + } else { + const QTextBlock posBlock = document()->findBlock(position); + const QTextBlock nextBlock = document()->findBlock(position + charsAdded); + if (posBlock != nextBlock) { + d->updateMarksLineNumber(); + d->updateMarksBlock(posBlock); + d->updateMarksBlock(nextBlock); + } else { + d->updateMarksBlock(posBlock); + } + } +} + + +void BaseTextEditor::slotSelectionChanged() +{ + bool changed = (d->m_inBlockSelectionMode != d->m_lastEventWasBlockSelectionEvent); + d->m_inBlockSelectionMode = d->m_lastEventWasBlockSelectionEvent; + if (changed || d->m_inBlockSelectionMode) + viewport()->update(); + if (!d->m_inBlockSelectionMode) + d->m_blockSelectionExtraX = 0; +} + +void BaseTextEditor::keyPressEvent(QKeyEvent *e) +{ + + d->clearVisibleCollapsedBlock(); + + QKeyEvent *original_e = e; + d->m_lastEventWasBlockSelectionEvent = false; + + if (e->key() == Qt::Key_Escape) { + e->accept(); + QTextCursor cursor = textCursor(); + cursor.clearSelection(); + setTextCursor(cursor); + return; + } + + d->m_contentsChanged = false; + + bool ro = isReadOnly(); + + if (d->m_inBlockSelectionMode) { + if (e == QKeySequence::Cut) { + if (!ro) { + cut(); + e->accept(); + return; + } + } else if (e == QKeySequence::Delete) { + if (!ro) { + d->removeBlockSelection(); + e->accept(); + return; + } + } + } + + + if (!ro + && (e == QKeySequence::InsertParagraphSeparator + || (!d->m_lineSeparatorsAllowed && e == QKeySequence::InsertLineSeparator)) + ) { + + QTextCursor cursor = textCursor(); + if (d->m_inBlockSelectionMode) + cursor.clearSelection(); + cursor.insertBlock(); + if (d->m_document->tabSettings().m_autoIndent) { + indent(document(), cursor, QChar::Null); + } + e->accept(); + setTextCursor(cursor); + return; + } else switch (e->key()) { + + +#if 0 + case Qt::Key_sterling: { + + static bool toggle = false; + if ((toggle = !toggle)) { + QList<BaseTextEditor::BlockRange> rangeList; + rangeList += BaseTextEditor::BlockRange(4, 12); + rangeList += BaseTextEditor::BlockRange(15, 19); + setIfdefedOutBlocks(rangeList); + } else { + setIfdefedOutBlocks(QList<BaseTextEditor::BlockRange>()); + } + e->accept(); + return; + + } break; +#endif + case Qt::Key_Tab: + case Qt::Key_Backtab: + if (ro) break; + indentOrUnindent(e->key() == Qt::Key_Tab); + e->accept(); + return; + case Qt::Key_Backspace: + if (ro) break; + if (d->m_document->tabSettings().m_smartBackspace + && (e->modifiers() & (Qt::ControlModifier + | Qt::ShiftModifier + | Qt::AltModifier + | Qt::MetaModifier)) == Qt::NoModifier + && !textCursor().hasSelection()) { + handleBackspaceKey(); + e->accept(); + return; + } + break; + case Qt::Key_Home: + if (!(e == QKeySequence::MoveToStartOfDocument) && !(e == QKeySequence::SelectStartOfDocument)) { + handleHomeKey(e->modifiers() & Qt::ShiftModifier); + e->accept(); + return; + } + break; + case Qt::Key_Up: + case Qt::Key_Down: + if (e->modifiers() & Qt::ControlModifier) { + verticalScrollBar()->triggerAction( + e->key() == Qt::Key_Up ? QAbstractSlider::SliderSingleStepSub : + QAbstractSlider::SliderSingleStepAdd); + e->accept(); + return; + } + // fall through + case Qt::Key_Right: + case Qt::Key_Left: +#ifndef Q_OS_MAC + if ((e->modifiers() & (Qt::AltModifier | Qt::ShiftModifier)) == (Qt::AltModifier | Qt::ShiftModifier)) { + + d->m_lastEventWasBlockSelectionEvent = true; + + if (d->m_inBlockSelectionMode) { + if (e->key() == Qt::Key_Right && textCursor().atBlockEnd()) { + d->m_blockSelectionExtraX++; + viewport()->update(); + e->accept(); + return; + } else if (e->key() == Qt::Key_Left && d->m_blockSelectionExtraX > 0) { + d->m_blockSelectionExtraX--; + e->accept(); + viewport()->update(); + return; + } + } + + e = new QKeyEvent( + e->type(), + e->key(), + e->modifiers() & ~Qt::AltModifier, + e->text(), + e->isAutoRepeat(), + e->count() + ); + } +#endif + break; + case Qt::Key_PageUp: + case Qt::Key_PageDown: + if (e->modifiers() == Qt::ControlModifier) { + verticalScrollBar()->triggerAction( + e->key() == Qt::Key_PageUp ? QAbstractSlider::SliderPageStepSub : + QAbstractSlider::SliderPageStepAdd); + e->accept(); + return; + } + break; + + default: + if (! ro && d->m_document->tabSettings().m_autoIndent + && ! e->text().isEmpty() && isElectricCharacter(e->text().at(0))) { + QTextCursor cursor = textCursor(); + const QString text = e->text(); + cursor.insertText(text); + const QString leftText = cursor.block().text().left(cursor.position() - 1 - cursor.block().position()); + if (leftText.simplified().isEmpty()) { + const QChar typedChar = e->text().at(0); + indent(document(), cursor, typedChar); + } +#if 0 + TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(document()->documentLayout()); + Q_ASSERT(documentLayout); + documentLayout->requestUpdate(); // a bit drastic + e->accept(); +#endif + setTextCursor(cursor); + return; + } + break; + } + + if (d->m_inBlockSelectionMode) { + QString text = e->text(); + if (!text.isEmpty() && (text.at(0).isPrint() || text.at(0) == QLatin1Char('\t'))) { + d->removeBlockSelection(text); + goto skip_event; + } + } + + QPlainTextEdit::keyPressEvent(e); + +skip_event: + if (!ro && e->key() == Qt::Key_Delete && d->m_parenthesesMatchingEnabled) + slotCursorPositionChanged(); // parentheses matching + + if (!ro && d->m_contentsChanged && !e->text().isEmpty() && e->text().at(0).isPrint()) + emit requestAutoCompletion(editableInterface(), false); + + if (e != original_e) + delete e; +} + +void BaseTextEditor::gotoLine(int line, int column) +{ + const int blockNumber = line - 1; + const QTextBlock &block = document()->findBlockByNumber(blockNumber); + if (block.isValid()) { + QTextCursor cursor(block); + if (column > 0) { + cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, column); + } else { + int pos = cursor.position(); + while (characterAt(pos).category() == QChar::Separator_Space) { + ++pos; + } + cursor.setPosition(pos); + } + setTextCursor(cursor); + centerCursor(); + } +} + +int BaseTextEditor::position(ITextEditor::PositionOperation posOp, int at) const +{ + QTextCursor tc = textCursor(); + + if (at != -1) + tc.setPosition(at); + + if (posOp == ITextEditor::Current) + return tc.position(); + + switch (posOp) { + case ITextEditor::EndOfLine: + tc.movePosition(QTextCursor::EndOfLine); + return tc.position(); + case ITextEditor::StartOfLine: + tc.movePosition(QTextCursor::StartOfLine); + return tc.position(); + case ITextEditor::Anchor: + if (tc.hasSelection()) + return tc.anchor(); + break; + case ITextEditor::EndOfDoc: + tc.movePosition(QTextCursor::End); + return tc.position(); + default: + break; + } + + return -1; +} + +void BaseTextEditor::convertPosition(int pos, int *line, int *column) const +{ + QTextBlock block = document()->findBlock(pos); + if (!block.isValid()) { + (*line) = -1; + (*column) = -1; + } else { + (*line) = block.blockNumber() + 1; + (*column) = pos - block.position(); + } +} + +QChar BaseTextEditor::characterAt(int pos) const +{ + return document()->characterAt(pos); +} + +bool BaseTextEditor::event(QEvent *e) +{ + switch (e->type()) { + case QEvent::ShortcutOverride: + e->ignore(); // we are a really nice citizen + return true; + default: + break; + } + + return QPlainTextEdit::event(e); +} + +void BaseTextEditor::duplicateFrom(BaseTextEditor *editor) +{ + if (this == editor) + return; + setDisplayName(editor->displayName()); + d->m_revisionsVisible = editor->d->m_revisionsVisible; + if (d->m_document == editor->d->m_document) + return; + d->setupDocumentSignals(editor->d->m_document); + d->m_document = editor->d->m_document; +} + +QString BaseTextEditor::displayName() const +{ + return d->m_displayName; +} + +void BaseTextEditor::setDisplayName(const QString &title) +{ + d->m_displayName = title; +} + +BaseTextDocument *BaseTextEditor::baseTextDocument() const +{ + return d->m_document; +} + +void BaseTextEditor::setBaseTextDocument(BaseTextDocument *doc) +{ + if (doc) { + d->setupDocumentSignals(doc); + d->m_document = doc; + } +} + +void BaseTextEditor::memorizeCursorPosition() +{ + d->m_tempState = saveState(); +} + +void BaseTextEditor::restoreCursorPosition() +{ + restoreState(d->m_tempState); +} + +QByteArray BaseTextEditor::saveState() const +{ + QByteArray state; + QDataStream stream(&state, QIODevice::WriteOnly); + stream << 0; // version number + stream << verticalScrollBar()->value(); + stream << horizontalScrollBar()->value(); + int line, column; + convertPosition(textCursor().position(), &line, &column); + stream << line; + stream << column; + return state; +} + +bool BaseTextEditor::restoreState(const QByteArray &state) +{ + int version; + int vval; + int hval; + int lval; + int cval; + QDataStream stream(state); + stream >> version; + stream >> vval; + stream >> hval; + stream >> lval; + stream >> cval; + gotoLine(lval, cval); + verticalScrollBar()->setValue(vval); + horizontalScrollBar()->setValue(hval); + return true; +} + +void BaseTextEditor::setDefaultPath(const QString &defaultPath) +{ + baseTextDocument()->setDefaultPath(defaultPath); +} + +void BaseTextEditor::setSuggestedFileName(const QString &suggestedFileName) +{ + baseTextDocument()->setSuggestedFileName(suggestedFileName); +} + +void BaseTextEditor::setParenthesesMatchingEnabled(bool b) +{ + d->m_parenthesesMatchingEnabled = b; +} + +bool BaseTextEditor::isParenthesesMatchingEnabled() const +{ + return d->m_parenthesesMatchingEnabled; +} + +void BaseTextEditor::setHighlightCurrentLine(bool b) +{ + d->m_highlightCurrentLine = b; + slotCursorPositionChanged(); +} + +bool BaseTextEditor::highlightCurrentLine() const +{ + return d->m_highlightCurrentLine; +} + +void BaseTextEditor::setLineNumbersVisible(bool b) +{ + d->m_lineNumbersVisible = b; + slotUpdateExtraAreaWidth(); +} + +bool BaseTextEditor::lineNumbersVisible() const +{ + return d->m_lineNumbersVisible; +} + +void BaseTextEditor::setMarksVisible(bool b) +{ + d->m_marksVisible = b; + slotUpdateExtraAreaWidth(); +} + +bool BaseTextEditor::marksVisible() const +{ + return d->m_marksVisible; +} + +void BaseTextEditor::setRequestMarkEnabled(bool b) +{ + d->m_requestMarkEnabled = b; +} + +bool BaseTextEditor::requestMarkEnabled() const +{ + return d->m_requestMarkEnabled; +} + +void BaseTextEditor::setLineSeparatorsAllowed(bool b) +{ + d->m_lineSeparatorsAllowed = b; +} + +bool BaseTextEditor::lineSeparatorsAllowed() const +{ + return d->m_lineSeparatorsAllowed; +} + + +void BaseTextEditor::setCodeFoldingVisible(bool b) +{ + d->m_codeFoldingVisible = b; + slotUpdateExtraAreaWidth(); +} + +bool BaseTextEditor::codeFoldingVisible() const +{ + return d->m_codeFoldingVisible; +} + +void BaseTextEditor::setRevisionsVisible(bool b) +{ + d->m_revisionsVisible = b; + slotUpdateExtraAreaWidth(); +} + +bool BaseTextEditor::revisionsVisible() const +{ + return d->m_revisionsVisible; +} + +void BaseTextEditor::setVisibleWrapColumn(int column) +{ + d->m_visibleWrapColumn = column; + viewport()->update(); +} + +int BaseTextEditor::visibleWrapColumn() const +{ + return d->m_visibleWrapColumn; +} + +void BaseTextEditor::setFontSettings(const TextEditor::FontSettings &fs) +{ + const QTextCharFormat textFormat = fs.toTextCharFormat(QLatin1String(Constants::C_TEXT)); + const QTextCharFormat selectionFormat = fs.toTextCharFormat(QLatin1String(Constants::C_SELECTION)); + const QTextCharFormat lineNumberFormat = fs.toTextCharFormat(QLatin1String(Constants::C_LINE_NUMBER)); + const QTextCharFormat searchResultFormat = fs.toTextCharFormat(QLatin1String(Constants::C_SEARCH_RESULT)); + const QTextCharFormat searchScopeFormat = fs.toTextCharFormat(QLatin1String(Constants::C_SEARCH_SCOPE)); + const QTextCharFormat parenthesesFormat = fs.toTextCharFormat(QLatin1String(Constants::C_PARENTHESES)); + const QTextCharFormat currentLineFormat = fs.toTextCharFormat(QLatin1String(Constants::C_CURRENT_LINE)); + const QTextCharFormat ifdefedOutFormat = fs.toTextCharFormat(QLatin1String(Constants::C_DISABLED_CODE)); + QFont font(textFormat.font()); + + const QColor foreground = textFormat.foreground().color(); + const QColor background = textFormat.background().color(); + QPalette p = palette(); + p.setColor(QPalette::Text, foreground); + p.setColor(QPalette::Foreground, foreground); + p.setColor(QPalette::Base, background); + p.setColor(QPalette::Highlight, (selectionFormat.background().style() != Qt::NoBrush) ? + selectionFormat.background().color() : + QApplication::palette().color(QPalette::Highlight)); + p.setColor(QPalette::HighlightedText, selectionFormat.foreground().color()); + p.setBrush(QPalette::Inactive, QPalette::Highlight, p.highlight()); + p.setBrush(QPalette::Inactive, QPalette::HighlightedText, p.highlightedText()); + setPalette(p); + setFont(font); + setTabSettings(d->m_document->tabSettings()); // update tabs, they depend on the font + + // Line numbers + QPalette ep = d->m_extraArea->palette(); + ep.setColor(QPalette::Dark, lineNumberFormat.foreground().color()); + ep.setColor(QPalette::Background, lineNumberFormat.background().style() != Qt::NoBrush ? + lineNumberFormat.background().color() : background); + d->m_extraArea->setPalette(ep); + + // Search results + d->m_searchResultFormat.setBackground(searchResultFormat.background()); + d->m_searchScopeFormat.setBackground(searchScopeFormat.background()); + d->m_currentLineFormat.setBackground(currentLineFormat.background()); + + // Matching braces + d->m_matchFormat.setForeground(parenthesesFormat.foreground()); + d->m_rangeFormat.setBackground(parenthesesFormat.background()); + + // Disabled code + d->m_ifdefedOutFormat.setForeground(ifdefedOutFormat.foreground()); + + slotUpdateExtraAreaWidth(); +} + +void BaseTextEditor::setStorageSettings(const StorageSettings &storageSettings) +{ + d->m_document->setStorageSettings(storageSettings); +} + +//--------- BaseTextEditorPrivate ----------- + +BaseTextEditorPrivate::BaseTextEditorPrivate() + : + m_contentsChanged(false), + m_document(new BaseTextDocument()), + m_parenthesesMatchingEnabled(false), + m_extraArea(0), + m_marksVisible(false), + m_codeFoldingVisible(false), + m_revisionsVisible(false), + m_lineNumbersVisible(true), + m_highlightCurrentLine(true), + m_requestMarkEnabled(true), + m_lineSeparatorsAllowed(false), + m_visibleWrapColumn(0), + m_editable(0), + m_actionHack(0), + m_inBlockSelectionMode(false), + m_lastEventWasBlockSelectionEvent(false), + m_blockSelectionExtraX(0) +{ +} + +BaseTextEditorPrivate::~BaseTextEditorPrivate() +{ +} + +void BaseTextEditorPrivate::setupDocumentSignals(BaseTextDocument *document) +{ + BaseTextDocument *oldDocument = q->baseTextDocument(); + if (oldDocument) { + q->disconnect(oldDocument->document(), 0, q, 0); + q->disconnect(oldDocument, 0, q, 0); + } + + QTextDocument *doc = document->document(); + TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(doc->documentLayout()); + if (!documentLayout) { + QTextOption opt = doc->defaultTextOption(); + opt.setTextDirection(Qt::LeftToRight); + opt.setFlags(opt.flags() | QTextOption::IncludeTrailingSpaces + | QTextOption::AddSpaceForLineAndParagraphSeparators + ); + doc->setDefaultTextOption(opt); + documentLayout = new TextEditDocumentLayout(doc); + doc->setDocumentLayout(documentLayout); + } + + + q->setDocument(doc); + QObject::connect(documentLayout, SIGNAL(updateBlock(QTextBlock)), q, SLOT(slotUpdateBlockNotify(QTextBlock))); + QObject::connect(q, SIGNAL(requestBlockUpdate(QTextBlock)), documentLayout, SIGNAL(updateBlock(QTextBlock))); + QObject::connect(doc, SIGNAL(modificationChanged(bool)), q, SIGNAL(changed())); + QObject::connect(doc, SIGNAL(contentsChange(int,int,int)), q, + SLOT(editorContentsChange(int,int,int)), Qt::DirectConnection); + QObject::connect(document, SIGNAL(changed()), q, SIGNAL(changed())); + QObject::connect(document, SIGNAL(titleChanged(QString)), q, SLOT(setDisplayName(const QString &))); + QObject::connect(document, SIGNAL(aboutToReload()), q, SLOT(memorizeCursorPosition())); + QObject::connect(document, SIGNAL(reloaded()), q, SLOT(restoreCursorPosition())); + q->slotUpdateExtraAreaWidth(); +} + +#ifndef TEXTEDITOR_STANDALONE +bool BaseTextEditorPrivate::needMakeWritableCheck() const +{ + return !m_document->isModified() + && !m_document->fileName().isEmpty() + && m_document->isReadOnly(); +} +#endif + +bool Parenthesis::hasClosingCollapse(const Parentheses &parentheses) +{ + return closeCollapseAtPos(parentheses) >= 0; +} + + +int Parenthesis::closeCollapseAtPos(const Parentheses &parentheses) +{ + int depth = 0; + for (int i = 0; i < parentheses.size(); ++i) { + const Parenthesis &p = parentheses.at(i); + if (p.chr == QLatin1Char('{') || p.chr == QLatin1Char('+')) { + ++depth; + } else if (p.chr == QLatin1Char('}') || p.chr == QLatin1Char('-')) { + if (--depth < 0) + return p.pos; + } + } + return -1; +} + +int Parenthesis::collapseAtPos(const Parentheses &parentheses, QChar *character) +{ + int result = -1; + QChar c; + + int depth = 0; + for (int i = 0; i < parentheses.size(); ++i) { + const Parenthesis &p = parentheses.at(i); + if (p.chr == QLatin1Char('{') || p.chr == QLatin1Char('+')) { + if (depth == 0) { + result = p.pos; + c = p.chr; + } + ++depth; + } else if (p.chr == QLatin1Char('}') || p.chr == QLatin1Char('-')) { + if (--depth < 0) + depth = 0; + result = -1; + } + } + if (result >= 0 && character) + *character = c; + return result; +} + + +int TextBlockUserData::collapseAtPos() const +{ + return Parenthesis::collapseAtPos(m_parentheses); +} + + +void TextEditDocumentLayout::setParentheses(const QTextBlock &block, const Parentheses &parentheses) +{ + if (parentheses.isEmpty()) { + if (TextBlockUserData *userData = testUserData(block)) + userData->clearParentheses(); + } else { + userData(block)->setParentheses(parentheses); + } +} + +Parentheses TextEditDocumentLayout::parentheses(const QTextBlock &block) +{ + if (TextBlockUserData *userData = testUserData(block)) + return userData->parentheses(); + return Parentheses(); +} + +bool TextEditDocumentLayout::hasParentheses(const QTextBlock &block) +{ + if (TextBlockUserData *userData = testUserData(block)) + return userData->hasParentheses(); + return false; +} + + +bool TextEditDocumentLayout::setIfdefedOut(const QTextBlock &block) +{ + return userData(block)->setIfdefedOut(); +} + +bool TextEditDocumentLayout::clearIfdefedOut(const QTextBlock &block) +{ + if (TextBlockUserData *userData = testUserData(block)) + return userData->clearIfdefedOut(); + return false; +} + +bool TextEditDocumentLayout::ifdefedOut(const QTextBlock &block) +{ + if (TextBlockUserData *userData = testUserData(block)) + return userData->ifdefedOut(); + return false; +} + + +TextEditDocumentLayout::TextEditDocumentLayout(QTextDocument *doc) + :QPlainTextDocumentLayout(doc) { + lastSaveRevision = 0; + hasMarks = 0; +} + +TextEditDocumentLayout::~TextEditDocumentLayout() +{ +} + +QRectF TextEditDocumentLayout::blockBoundingRect(const QTextBlock &block) const +{ + QRectF r = QPlainTextDocumentLayout::blockBoundingRect(block); + return r; +} + + +bool BaseTextEditor::viewportEvent(QEvent *event) +{ + if (event->type() == QEvent::ContextMenu) { + const QContextMenuEvent *ce = static_cast<QContextMenuEvent*>(event); + if (ce->reason() == QContextMenuEvent::Mouse && !textCursor().hasSelection()) + setTextCursor(cursorForPosition(ce->pos())); + } else if (event->type() == QEvent::ToolTip) { + const QHelpEvent *he = static_cast<QHelpEvent*>(event); + const QPoint &pos = he->pos(); + + // Allow plugins to show tooltips + const QTextCursor &c = cursorForPosition(pos); + QPoint cursorPos = mapToGlobal(cursorRect(c).bottomRight() + QPoint(1,1)); + cursorPos.setX(cursorPos.x() + d->m_extraArea->width()); + + editableInterface(); // create if necessary + + emit d->m_editable->tooltipRequested(editableInterface(), cursorPos, c.position()); + return true; + } + return QPlainTextEdit::viewportEvent(event); +} + + +void BaseTextEditor::resizeEvent(QResizeEvent *e) +{ + QPlainTextEdit::resizeEvent(e); + QRect cr = viewport()->rect(); + d->m_extraArea->setGeometry( + QStyle::visualRect(layoutDirection(), cr, + QRect(cr.left(), cr.top(), extraAreaWidth(), cr.height()))); +} + +QRect BaseTextEditor::collapseBox(const QTextBlock &block) +{ + QRectF br = blockBoundingGeometry(block).translated(contentOffset()); + int collapseBoxWidth = fontMetrics().lineSpacing() + 1; + return QRect(d->m_extraArea->width() - collapseBoxWidth + collapseBoxWidth/4, + int(br.top()) + collapseBoxWidth/4, + 2 * (collapseBoxWidth/4) + 1, 2 * (collapseBoxWidth/4) + 1); + +} + +QTextBlock BaseTextEditor::collapsedBlockAt(const QPoint &pos, QRect *box) const { + QPointF offset(contentOffset()); + QTextBlock block = firstVisibleBlock(); + int top = (int)blockBoundingGeometry(block).translated(offset).top(); + int bottom = top + (int)blockBoundingRect(block).height(); + + int viewportHeight = viewport()->height(); + + while (block.isValid() && top <= viewportHeight) { + QTextBlock nextBlock = block.next(); + if (block.isVisible() && bottom >= 0) { + if (nextBlock.isValid() && !nextBlock.isVisible()) { + QTextLayout *layout = block.layout(); + QTextLine line = layout->lineAt(layout->lineCount()-1); + QRectF lineRect = line.naturalTextRect().translated(offset.x(), top); + lineRect.adjust(0, 0, -1, -1); + + QRectF collapseRect(lineRect.right() + 12, + lineRect.top(), + fontMetrics().width(QLatin1String(" {...}; ")), + lineRect.height()); + if (collapseRect.contains(pos)) { + QTextBlock result = block; + if (box) + *box = collapseRect.toAlignedRect(); + return result; + } else { + block = nextBlock; + while (nextBlock.isValid() && !nextBlock.isVisible()) { + block = nextBlock; + nextBlock = block.next(); + } + } + } + } + + block = nextBlock; + top = bottom; + bottom = top + (int)blockBoundingRect(block).height(); + } + return QTextBlock(); +} + +void BaseTextEditorPrivate::highlightSearchResults(const QTextBlock &block, + QVector<QTextLayout::FormatRange> *selections) +{ + if (m_searchExpr.isEmpty()) + return; + + QString text = block.text(); + text.replace(QChar::Nbsp, QLatin1Char(' ')); + int idx = -1; + while (idx < text.length()) { + idx = m_searchExpr.indexIn(text, idx + 1); + if (idx < 0) + break; + int l = m_searchExpr.matchedLength(); + if ((m_findFlags & QTextDocument::FindWholeWords) + && ((idx && text.at(idx-1).isLetterOrNumber()) + || (idx + l < text.length() && text.at(idx + l).isLetterOrNumber()))) + continue; + + if (m_findScope.isNull() + || (block.position() + idx >= m_findScope.selectionStart() + && block.position() + idx + l <= m_findScope.selectionEnd())) { + QTextLayout::FormatRange selection; + selection.start = idx; + selection.length = l; + selection.format = m_searchResultFormat; + selections->append(selection); + } + } +} + + +namespace TextEditor { + namespace Internal { + struct BlockSelectionData { + int selectionIndex; + int selectionStart; + int selectionEnd; + int firstColumn; + int lastColumn; + }; + + } +} + +void BaseTextEditorPrivate::clearBlockSelection() +{ + if (m_inBlockSelectionMode) { + m_inBlockSelectionMode = false; + QTextCursor cursor = q->textCursor(); + cursor.clearSelection(); + q->setTextCursor(cursor); + } +} + +QString BaseTextEditorPrivate::copyBlockSelection() +{ + QString text; + + QTextCursor cursor = q->textCursor(); + if (!cursor.hasSelection()) + return text; + + QTextDocument *doc = q->document(); + int start = cursor.selectionStart(); + int end = cursor.selectionEnd(); + QTextBlock startBlock = doc->findBlock(start); + int columnA = start - startBlock.position(); + QTextBlock endBlock = doc->findBlock(end); + int columnB = end - endBlock.position(); + int firstColumn = qMin(columnA, columnB); + int lastColumn = qMax(columnA, columnB) + m_blockSelectionExtraX; + + QTextBlock block = startBlock; + for (;;) { + + cursor.setPosition(block.position() + qMin(block.length()-1, firstColumn)); + cursor.setPosition(block.position() + qMin(block.length()-1, lastColumn), QTextCursor::KeepAnchor); + text += cursor.selectedText(); + if (block == endBlock) + break; + text += QLatin1Char('\n'); + block = block.next(); + } + + return text; +} + +void BaseTextEditorPrivate::removeBlockSelection(const QString &text) +{ + QTextCursor cursor = q->textCursor(); + if (!cursor.hasSelection()) + return; + + QTextDocument *doc = q->document(); + int start = cursor.selectionStart(); + int end = cursor.selectionEnd(); + QTextBlock startBlock = doc->findBlock(start); + int columnA = start - startBlock.position(); + QTextBlock endBlock = doc->findBlock(end); + int columnB = end - endBlock.position(); + int firstColumn = qMin(columnA, columnB); + int lastColumn = qMax(columnA, columnB) + m_blockSelectionExtraX; + + cursor.clearSelection(); + cursor.beginEditBlock(); + + QTextBlock block = startBlock; + for (;;) { + + cursor.setPosition(block.position() + qMin(block.length()-1, firstColumn)); + cursor.setPosition(block.position() + qMin(block.length()-1, lastColumn), QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + if (block == endBlock) + break; + block = block.next(); + } + + cursor.setPosition(start); + if (!text.isEmpty()) + cursor.insertText(text); + cursor.endEditBlock(); + q->setTextCursor(cursor); +} + +void BaseTextEditor::paintEvent(QPaintEvent *e) +{ + /* + Here comes an almost verbatim copy of + QPlainTextEdit::paintEvent() so we can adjust the extra + selections dynamically to indicate all search results. + */ + //begin QPlainTextEdit::paintEvent() + + QPainter painter(viewport()); + QTextDocument *doc = document(); + TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(doc->documentLayout()); + Q_ASSERT(documentLayout); + + QPointF offset(contentOffset()); + + QRect er = e->rect(); + QRect viewportRect = viewport()->rect(); + + // keep right margin clean from full-width selection + int maxX = offset.x() + qMax((qreal)viewportRect.width(), documentLayout->documentSize().width()) + - doc->documentMargin(); + er.setRight(qMin(er.right(), maxX)); + painter.setClipRect(er); + + bool editable = !isReadOnly(); + + QTextBlock block = firstVisibleBlock(); + + QAbstractTextDocumentLayout::PaintContext context = getPaintContext(); + + + if (!d->m_findScope.isNull()) { + QAbstractTextDocumentLayout::Selection selection; + selection.format.setBackground(d->m_searchScopeFormat.background()); + selection.cursor = d->m_findScope; + context.selections.prepend(selection); + } + + BlockSelectionData *blockSelection = 0; + + if (d->m_inBlockSelectionMode + && context.selections.count() && context.selections.last().cursor == textCursor()) { + blockSelection = new BlockSelectionData; + blockSelection->selectionIndex = context.selections.size()-1; + const QAbstractTextDocumentLayout::Selection &selection = context.selections[blockSelection->selectionIndex]; + int start = blockSelection->selectionStart = selection.cursor.selectionStart(); + int end = blockSelection->selectionEnd = selection.cursor.selectionEnd(); + QTextBlock block = doc->findBlock(start); + int columnA = start - block.position(); + block = doc->findBlock(end); + int columnB = end - block.position(); + blockSelection->firstColumn = qMin(columnA, columnB); + blockSelection->lastColumn = qMax(columnA, columnB) + d->m_blockSelectionExtraX; + } + + const QColor baseColor = palette().base().color(); + const int blendBase = (baseColor.value() > 128) ? 0 : 255; + // Darker backgrounds may need a bit more contrast + // (this calculation is temporary solution until we have a setting for this color) + const int blendFactor = (baseColor.value() > 128) ? 8 : 16; + const QColor blendColor( + (blendBase * blendFactor + baseColor.blue() * (256 - blendFactor)) / 256, + (blendBase * blendFactor + baseColor.green() * (256 - blendFactor)) / 256, + (blendBase * blendFactor + baseColor.blue() * (256 - blendFactor)) / 256); + if (d->m_visibleWrapColumn > 0) { + qreal lineX = fontMetrics().averageCharWidth() * d->m_visibleWrapColumn + offset.x() + 4; + painter.fillRect(QRectF(lineX, 0, viewportRect.width() - lineX, viewportRect.height()), blendColor); + } + + QTextBlock visibleCollapsedBlock; + QPointF visibleCollapsedBlockOffset; + + while (block.isValid()) { + + if (!block.isVisible()) { + block = block.next(); + continue; + } + + QRectF r = blockBoundingRect(block).translated(offset); + QTextLayout *layout = block.layout(); + + QTextOption option = layout->textOption(); + if (TextEditDocumentLayout::ifdefedOut(block)) { + option.setFlags(option.flags() | QTextOption::SuppressColors); + painter.setPen(d->m_ifdefedOutFormat.foreground().color()); + } else { + option.setFlags(option.flags() & ~QTextOption::SuppressColors); + painter.setPen(context.palette.text().color()); + } + layout->setTextOption(option); + + if (r.bottom() >= er.top() && r.top() <= er.bottom()) { + + int blpos = block.position(); + int bllen = block.length(); + + QVector<QTextLayout::FormatRange> selections; + QVector<QTextLayout::FormatRange> selectionsWithText; + + for (int i = 0; i < context.selections.size(); ++i) { + const QAbstractTextDocumentLayout::Selection &range = context.selections.at(i); + const int selStart = range.cursor.selectionStart() - blpos; + const int selEnd = range.cursor.selectionEnd() - blpos; + if (selStart < bllen && selEnd > 0 + && selEnd > selStart) { + QTextLayout::FormatRange o; + o.start = selStart; + o.length = selEnd - selStart; + o.format = range.format; + if (blockSelection && blockSelection->selectionIndex == i) { + o.start = qMin(blockSelection->firstColumn, bllen-1); + o.length = qMin(blockSelection->lastColumn, bllen-1) - o.start; + } + if (o.format.foreground().style() != Qt::NoBrush) + selectionsWithText.append(o); + else + selections.append(o); + } else if (!range.cursor.hasSelection() && range.format.hasProperty(QTextFormat::FullWidthSelection) + && block.contains(range.cursor.position())) { + // for full width selections we don't require an actual selection, just + // a position to specify the line. that's more convenience in usage. + QTextLayout::FormatRange o; + QTextLine l = layout->lineForTextPosition(range.cursor.position() - blpos); + o.start = l.textStart(); + o.length = l.textLength(); + if (o.start + o.length == bllen - 1) + ++o.length; // include newline + o.format = range.format; + if (o.format.foreground().style() != Qt::NoBrush) + selectionsWithText.append(o); + else + selections.append(o); + } + } + d->highlightSearchResults(block, &selections); + selections += selectionsWithText; + + bool drawCursor = ((editable || true) // we want the cursor in read-only mode + && context.cursorPosition >= blpos + && context.cursorPosition < blpos + bllen); + + bool drawCursorAsBlock = drawCursor && overwriteMode() ; + + if (drawCursorAsBlock) { + if (context.cursorPosition == blpos + bllen - 1) { + drawCursorAsBlock = false; + } else { + QTextLayout::FormatRange o; + o.start = context.cursorPosition - blpos; + o.length = 1; + o.format.setForeground(palette().base()); + o.format.setBackground(palette().text()); + selections.append(o); + } + } + + + layout->draw(&painter, offset, selections, er); + + if ((drawCursor && !drawCursorAsBlock) + || (editable && context.cursorPosition < -1 + && !layout->preeditAreaText().isEmpty())) { + int cpos = context.cursorPosition; + if (cpos < -1) + cpos = layout->preeditAreaPosition() - (cpos + 2); + else + cpos -= blpos; + layout->drawCursor(&painter, offset, cpos, cursorWidth()); + } + } + + offset.ry() += r.height(); + + if (offset.y() > viewportRect.height()) + break; + block = block.next(); + if (!block.isVisible()) { + if (block.blockNumber() == d->visibleCollapsedBlockNumber) { + visibleCollapsedBlock = block; + visibleCollapsedBlockOffset = offset; + } + + // invisible blocks do have zero line count + block = doc->findBlockByLineNumber(block.firstLineNumber()); + } + + } + + if (backgroundVisible() && !block.isValid() && offset.y() <= er.bottom() + && (centerOnScroll() || verticalScrollBar()->maximum() == verticalScrollBar()->minimum())) { + painter.fillRect(QRect(QPoint((int)er.left(), (int)offset.y()), er.bottomRight()), palette().background()); + } + + //end QPlainTextEdit::paintEvent() + + delete blockSelection; + + offset = contentOffset(); + block = firstVisibleBlock(); + + int top = (int)blockBoundingGeometry(block).translated(offset).top(); + int bottom = top + (int)blockBoundingRect(block).height(); + + QTextCursor cursor = textCursor(); + bool hasSelection = cursor.hasSelection(); + int selectionStart = cursor.selectionStart(); + int selectionEnd = cursor.selectionEnd(); + + + while (block.isValid() && top <= e->rect().bottom()) { + QTextBlock nextBlock = block.next(); + QTextBlock nextVisibleBlock = nextBlock; + + if (!nextVisibleBlock.isVisible()) + // invisible blocks do have zero line count + nextVisibleBlock = doc->findBlockByLineNumber(nextVisibleBlock.firstLineNumber()); + if (block.isVisible() && bottom >= e->rect().top()) { + if (d->m_displaySettings.m_visualizeWhitespace) { + QTextLayout *layout = block.layout(); + int lineCount = layout->lineCount(); + if (lineCount >= 2 || !nextBlock.isValid()) { + painter.save(); + painter.setPen(Qt::lightGray); + for (int i = 0; i < lineCount-1; ++i) { // paint line wrap indicator + QTextLine line = layout->lineAt(i); + QRectF lineRect = line.naturalTextRect().translated(offset.x(), top); + QChar visualArrow((ushort)0x21b5); + painter.drawText(static_cast<int>(lineRect.right()), + static_cast<int>(lineRect.top() + line.ascent()), visualArrow); + } + if (!nextBlock.isValid()) { // paint EOF symbol + QTextLine line = layout->lineAt(lineCount-1); + QRectF lineRect = line.naturalTextRect().translated(offset.x(), top); + int h = 4; + lineRect.adjust(0, 0, -1, -1); + QPainterPath path; + QPointF pos(lineRect.topRight() + QPointF(h+4, line.ascent())); + path.moveTo(pos); + path.lineTo(pos + QPointF(-h, -h)); + path.lineTo(pos + QPointF(0, -2*h)); + path.lineTo(pos + QPointF(h, -h)); + path.closeSubpath(); + painter.setBrush(painter.pen().color()); + painter.drawPath(path); + } + painter.restore(); + } + } + + if (nextBlock.isValid() && !nextBlock.isVisible()) { + + bool selectThis = (hasSelection + && nextBlock.position() >= selectionStart + && nextBlock.position() < selectionEnd); + if (selectThis) { + painter.save(); + painter.setBrush(palette().highlight()); + } + + QTextLayout *layout = block.layout(); + QTextLine line = layout->lineAt(layout->lineCount()-1); + QRectF lineRect = line.naturalTextRect().translated(offset.x(), top); + lineRect.adjust(0, 0, -1, -1); + + QRectF collapseRect(lineRect.right() + 12, + lineRect.top(), + fontMetrics().width(QLatin1String(" {...}; ")), + lineRect.height()); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.translate(.5, .5); + painter.drawRoundedRect(collapseRect.adjusted(0, 0, 0, -1), 3, 3); + painter.setRenderHint(QPainter::Antialiasing, false); + painter.translate(-.5, -.5); + + QString replacement = QLatin1String("..."); + + QTextBlock info = block; + if (block.userData() + && static_cast<TextBlockUserData*>(block.userData())->collapseMode() == TextBlockUserData::CollapseAfter) + ; + else if (block.next().userData() + && static_cast<TextBlockUserData*>(block.next().userData())->collapseMode() + == TextBlockUserData::CollapseThis) { + replacement.prepend(nextBlock.text().trimmed().left(1)); + info = nextBlock; + } + + + block = nextVisibleBlock.previous(); + if (!block.isValid()) + block = doc->lastBlock(); + + if (info.userData() + && static_cast<TextBlockUserData*>(info.userData())->collapseIncludesClosure()) { + QString right = block.text().trimmed(); + if (right.endsWith(QLatin1Char(';'))) { + right.chop(1); + right = right.trimmed(); + replacement.append(right.right(right.endsWith(QLatin1Char('/')) ? 2 : 1)); + replacement.append(QLatin1Char(';')); + } else { + replacement.append(right.right(right.endsWith(QLatin1Char('/')) ? 2 : 1)); + } + } + if (selectThis) + painter.setPen(palette().highlightedText().color()); + painter.drawText(collapseRect, Qt::AlignCenter, replacement); + if (selectThis) + painter.restore(); + } + } + + block = nextVisibleBlock; + top = bottom; + bottom = top + (int)blockBoundingRect(block).height(); + } + + if (visibleCollapsedBlock.isValid() ) { + int margin = doc->documentMargin(); + qreal maxWidth = 0; + qreal blockHeight = 0; + QTextBlock b = visibleCollapsedBlock; + + while (!b.isVisible() && visibleCollapsedBlockOffset.y() + blockHeight <= e->rect().bottom()) { + b.setVisible(true); // make sure block bounding rect works + QRectF r = blockBoundingRect(b).translated(visibleCollapsedBlockOffset); + + QTextLayout *layout = b.layout(); + for (int i = layout->lineCount()-1; i >= 0; --i) + maxWidth = qMax(maxWidth, layout->lineAt(i).naturalTextWidth() + margin); + + blockHeight += r.height(); + + b.setVisible(false); // restore previous state + b = b.next(); + } + + painter.save(); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.translate(.5, .5); + QColor color = blendColor; +// color.setAlpha(240); // someone thinks alpha blending looks messy + painter.setBrush(color); + painter.drawRoundedRect(QRectF(visibleCollapsedBlockOffset.x(), + visibleCollapsedBlockOffset.y(), + maxWidth, blockHeight).adjusted(0, 0, 1, 1), 3, 3); + painter.restore(); + + QTextBlock end = b; + b = visibleCollapsedBlock; + while (b != end) { + b.setVisible(true); // make sure block bounding rect works + QRectF r = blockBoundingRect(b).translated(visibleCollapsedBlockOffset); + QTextLayout *layout = b.layout(); + QVector<QTextLayout::FormatRange> selections; + d->highlightSearchResults(b, &selections); + layout->draw(&painter, visibleCollapsedBlockOffset, selections, er); + + b.setVisible(false); // restore previous state + visibleCollapsedBlockOffset.ry() += r.height(); + b = b.next(); + } + } + + + if (d->m_visibleWrapColumn > 0) { + qreal lineX = fontMetrics().width('x') * d->m_visibleWrapColumn + offset.x() + 4; + const QColor bg = palette().base().color(); + QColor col = (bg.value() > 128) ? Qt::black : Qt::white; + col.setAlpha(32); + painter.setPen(QPen(col, 0)); + painter.drawLine(QPointF(lineX, 0), QPointF(lineX, viewport()->height())); + } +} + +void BaseTextEditor::slotUpdateExtraAreaWidth() +{ + if (isLeftToRight()) + setViewportMargins(extraAreaWidth(), 0, 0, 0); + else + setViewportMargins(0, 0, extraAreaWidth(), 0); +} + + +QWidget *BaseTextEditor::extraArea() const +{ + return d->m_extraArea; +} + +int BaseTextEditor::extraAreaWidth(int *markWidthPtr) const +{ + TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(document()->documentLayout()); + if (!documentLayout) + return 0; + + if (!d->m_marksVisible && documentLayout->hasMarks) + d->m_marksVisible = true; + + int space = 0; + const QFontMetrics fm(d->m_extraArea->fontMetrics()); + + if (d->m_lineNumbersVisible) { + int digits = 2; + int max = qMax(1, blockCount()); + while (max >= 100) { + max /= 10; + ++digits; + } + space += fm.width(QLatin1Char('9')) * digits; + } + int markWidth = 0; + + if (d->m_marksVisible) { + markWidth += fm.lineSpacing(); +// if (documentLayout->doubleMarkCount) +// markWidth += fm.lineSpacing() / 3; + space += markWidth; + } else { + space += 2; + } + + if (markWidthPtr) + *markWidthPtr = markWidth; + + space += 4; + + if (d->m_codeFoldingVisible) + space += fm.lineSpacing(); + return space; +} + +void BaseTextEditor::slotModificationChanged(bool m) +{ + if (m) + return; + + QTextDocument *doc = document(); + TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(doc->documentLayout()); + Q_ASSERT(documentLayout); + int oldLastSaveRevision = documentLayout->lastSaveRevision; + documentLayout->lastSaveRevision = doc->revision(); + + if (oldLastSaveRevision != documentLayout->lastSaveRevision) { + QTextBlock block = doc->begin(); + while (block.isValid()) { + if (block.revision() < 0 || block.revision() != oldLastSaveRevision) { + block.setRevision(-documentLayout->lastSaveRevision - 1); + } else { + block.setRevision(documentLayout->lastSaveRevision); + } + block = block.next(); + } + } + d->m_extraArea->update(); +} + +void BaseTextEditor::slotUpdateBlockNotify(const QTextBlock &block) +{ + static bool blockRecursion = false; + if (blockRecursion) + return; + if (block.previous().isValid() && block.userState() != block.previous().userState()) { + /* The syntax highlighting state changes. This opens up for + the possibility that the paragraph has braces that support + code folding. In this case, do the save thing and also + update the previous block, which might contain a collapse + box which now is invalid.*/ + blockRecursion = true; + emit requestBlockUpdate(block.previous()); + blockRecursion = false; + } +} + +void BaseTextEditor::slotUpdateRequest(const QRect &r, int dy) +{ + if (dy) + d->m_extraArea->scroll(0, dy); + else if (r.width() > 4) { // wider than cursor width, not just cursor blinking + d->m_extraArea->update(0, r.y(), d->m_extraArea->width(), r.height()); + } + + if (r.contains(viewport()->rect())) + slotUpdateExtraAreaWidth(); +} + + +void BaseTextEditor::setCollapseIndicatorAlpha(int alpha) +{ + d->extraAreaCollapseAlpha = alpha; + d->m_extraArea->update(); +} + +void BaseTextEditor::extraAreaPaintEvent(QPaintEvent *e) +{ + QTextDocument *doc = document(); + TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(doc->documentLayout()); + Q_ASSERT(documentLayout); + + QPalette pal = d->m_extraArea->palette(); + pal.setCurrentColorGroup(QPalette::Active); + QPainter painter(d->m_extraArea); + QFontMetrics fm(painter.fontMetrics()); + int fmLineSpacing = fm.lineSpacing(); + + int markWidth = 0; + if (d->m_marksVisible) + markWidth += fm.lineSpacing(); +// if (documentLayout->doubleMarkCount) +// markWidth += fm.lineSpacing() / 3; + + const int collapseBoxWidth = d->m_codeFoldingVisible ? fmLineSpacing + 1: 0; + const int extraAreaWidth = d->m_extraArea->width() - collapseBoxWidth; + + painter.fillRect(e->rect(), pal.color(QPalette::Base)); + painter.fillRect(e->rect().intersected(QRect(0, 0, extraAreaWidth, INT_MAX)), + pal.color(QPalette::Background)); + + + QTextBlock block = firstVisibleBlock(); + int blockNumber = block.blockNumber(); + int top = (int)blockBoundingGeometry(block).translated(contentOffset()).top(); + int bottom = top; + + int extraAreaHighlightCollapseEndBlockNumber = -1; + + int extraAreaHighlightCollapseBlockNumber = d->extraAreaHighlightCollapseBlockNumber; + if (extraAreaHighlightCollapseBlockNumber < 0) { + extraAreaHighlightCollapseBlockNumber = d->extraAreaHighlightFadingBlockNumber; + } + + if (extraAreaHighlightCollapseBlockNumber >= 0 ) { + QTextBlock highlightBlock = doc->findBlockByNumber(extraAreaHighlightCollapseBlockNumber); + if (highlightBlock.isValid() && highlightBlock.next().isValid() && highlightBlock.next().isVisible()) + extraAreaHighlightCollapseEndBlockNumber = TextBlockUserData::testCollapse(highlightBlock).blockNumber(); + else + extraAreaHighlightCollapseEndBlockNumber = extraAreaHighlightCollapseBlockNumber; + } + + + while (block.isValid() && top <= e->rect().bottom()) { + + bool collapseThis = false; + bool collapseAfter = false; + bool hasClosingCollapse = false; + + + top = bottom; + bottom = top + (int)blockBoundingRect(block).height(); + QTextBlock nextBlock = block.next(); + + QTextBlock nextVisibleBlock = nextBlock; + int nextVisibleBlockNumber = blockNumber + 1; + + if (!nextVisibleBlock.isVisible()) { + // invisible blocks do have zero line count + nextVisibleBlock = doc->findBlockByLineNumber(nextVisibleBlock.firstLineNumber()); + nextVisibleBlockNumber = nextVisibleBlock.blockNumber(); + } + + painter.setPen(pal.color(QPalette::Dark)); + + if (d->m_codeFoldingVisible || d->m_marksVisible) { + painter.save(); + painter.setRenderHint(QPainter::Antialiasing, false); + + int previousBraceDepth = block.previous().userState(); + if (previousBraceDepth >= 0) + previousBraceDepth >>= 8; + else + previousBraceDepth = 0; + + int braceDepth = block.userState(); + if (!nextBlock.isVisible()) { + QTextBlock lastInvisibleBlock = nextVisibleBlock.previous(); + if (!lastInvisibleBlock.isValid()) + lastInvisibleBlock = doc->lastBlock(); + braceDepth = lastInvisibleBlock.userState(); + } + if (braceDepth >= 0) + braceDepth >>= 8; + else + braceDepth = 0; + + if (TextBlockUserData *userData = static_cast<TextBlockUserData*>(block.userData())) { + if (d->m_marksVisible) { + int xoffset = 0; + foreach (ITextMark *mrk, userData->marks()) { + int x = 0; + int radius = fmLineSpacing - 1; + QRect r(x + xoffset, top, radius, radius); + mrk->icon().paint(&painter, r, Qt::AlignCenter); + xoffset += 2; + } + } + + collapseAfter = (userData->collapseMode() == TextBlockUserData::CollapseAfter); + collapseThis = (userData->collapseMode() == TextBlockUserData::CollapseThis); + hasClosingCollapse = userData->hasClosingCollapse() && (previousBraceDepth > 0); + } + + if (d->m_codeFoldingVisible) { + const QRect box(extraAreaWidth + collapseBoxWidth/4, top + collapseBoxWidth/4, + 2 * (collapseBoxWidth/4) + 1, 2 * (collapseBoxWidth/4) + 1); + const QPoint boxCenter = box.center(); + + QColor textColorAlpha = pal.text().color(); + textColorAlpha.setAlpha(d->extraAreaCollapseAlpha); + QColor textColorInactive = pal.text().color(); + textColorInactive.setAlpha(100); + QColor textColor = pal.text().color(); + textColor.setAlpha(qMax(textColorInactive.alpha(), d->extraAreaCollapseAlpha)); + + const QPen pen( (blockNumber >= extraAreaHighlightCollapseBlockNumber + && blockNumber <= extraAreaHighlightCollapseEndBlockNumber) ? + textColorAlpha : pal.base().color()); + const QPen boxPen((blockNumber == extraAreaHighlightCollapseBlockNumber) ? + textColor : textColorInactive); + const QPen endPen((blockNumber == extraAreaHighlightCollapseEndBlockNumber) ? + textColorAlpha : pal.base().color()); + const QPen previousPen((blockNumber-1 >= extraAreaHighlightCollapseBlockNumber + && blockNumber-1 < extraAreaHighlightCollapseEndBlockNumber) ? + textColorAlpha : pal.base().color()); + const QPen nextPen((blockNumber+1 > extraAreaHighlightCollapseBlockNumber + && blockNumber+1 <= extraAreaHighlightCollapseEndBlockNumber) ? + textColorAlpha : pal.base().color()); + + TextBlockUserData *nextBlockUserData = TextEditDocumentLayout::testUserData(nextBlock); + + bool collapseNext = nextBlockUserData + && nextBlockUserData->collapseMode() + == TextBlockUserData::CollapseThis; + + bool nextHasClosingCollapse = nextBlockUserData + && nextBlockUserData->hasClosingCollapseInside(); + + bool drawBox = ((collapseAfter || collapseNext) && !nextHasClosingCollapse); + + if (braceDepth || (collapseNext && nextBlock.isVisible())) { + painter.setPen((hasClosingCollapse || !nextBlock.isVisible())? nextPen : pen); + painter.drawLine(boxCenter.x(), boxCenter.y(), boxCenter.x(), bottom - 1); + } + + if (previousBraceDepth || collapseThis) { + painter.setPen((collapseAfter || collapseNext) ? previousPen : pen); + painter.drawLine(boxCenter.x(), top, boxCenter.x(), boxCenter.y()); + } + + if (drawBox) { + painter.setPen(boxPen); + painter.setBrush(pal.base()); + painter.drawRect(box.adjusted(0, 0, -1, -1)); + if (!nextBlock.isVisible()) + painter.drawLine(boxCenter.x(), box.top() + 2, boxCenter.x(), box.bottom() - 2); + painter.drawLine(box.left() + 2, boxCenter.y(), box.right() - 2, boxCenter.y()); + } else if (hasClosingCollapse || collapseAfter || collapseNext) { + painter.setPen(endPen); + painter.drawLine(boxCenter.x() + 1, boxCenter.y(), box.right() - 1, boxCenter.y()); + } + + } + + painter.restore(); + } + + + if (d->m_revisionsVisible && block.revision() != documentLayout->lastSaveRevision) { + painter.save(); + painter.setRenderHint(QPainter::Antialiasing, false); + if (block.revision() < 0) + painter.setPen(QPen(Qt::darkGreen, 2)); + else + painter.setPen(QPen(Qt::red, 2)); + painter.drawLine(extraAreaWidth-1, top, extraAreaWidth-1, bottom-1); + painter.restore(); + } + + if (d->m_lineNumbersVisible) { + const QString &number = QString::number(blockNumber + 1); + painter.drawText(markWidth, top, extraAreaWidth - markWidth - 4, fm.height(), Qt::AlignRight, number); + } + + block = nextVisibleBlock; + blockNumber = nextVisibleBlockNumber; + } +} + +void BaseTextEditor::timerEvent(QTimerEvent *e) +{ + if (e->timerId() == d->autoScrollTimer.timerId()) { + const QPoint globalPos = QCursor::pos(); + const QPoint pos = d->m_extraArea->mapFromGlobal(globalPos); + QRect visible = d->m_extraArea->rect(); + verticalScrollBar()->triggerAction( pos.y() < visible.center().y() ? + QAbstractSlider::SliderSingleStepSub + : QAbstractSlider::SliderSingleStepAdd); + QMouseEvent ev(QEvent::MouseMove, pos, globalPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + extraAreaMouseEvent(&ev); + int delta = qMax(pos.y() - visible.top(), visible.bottom() - pos.y()) - visible.height(); + if (delta < 7) + delta = 7; + int timeout = 4900 / (delta * delta); + d->autoScrollTimer.start(timeout, this); + + } else if (e->timerId() == d->collapsedBlockTimer.timerId()) { + d->visibleCollapsedBlockNumber = d->suggestedVisibleCollapsedBlockNumber; + d->suggestedVisibleCollapsedBlockNumber = -1; + d->collapsedBlockTimer.stop(); + viewport()->update(); + } + QPlainTextEdit::timerEvent(e); +} + + +void BaseTextEditorPrivate::clearVisibleCollapsedBlock() +{ + if (suggestedVisibleCollapsedBlockNumber) { + suggestedVisibleCollapsedBlockNumber = -1; + collapsedBlockTimer.stop(); + } + if (visibleCollapsedBlockNumber >= 0) { + visibleCollapsedBlockNumber = -1; + q->viewport()->update(); + } +} + + +void BaseTextEditor::mouseMoveEvent(QMouseEvent *e) +{ + d->m_lastEventWasBlockSelectionEvent = (e->modifiers() & Qt::AltModifier); + if (e->buttons() == 0) { + QTextBlock collapsedBlock = collapsedBlockAt(e->pos()); + int blockNumber = collapsedBlock.next().blockNumber(); + if (blockNumber < 0) { + d->clearVisibleCollapsedBlock(); + } else if (blockNumber != d->visibleCollapsedBlockNumber) { + d->suggestedVisibleCollapsedBlockNumber = blockNumber; + d->collapsedBlockTimer.start(40, this); + } + viewport()->setCursor(collapsedBlock.isValid() ? Qt::PointingHandCursor : Qt::IBeamCursor); + } else { + QPlainTextEdit::mouseMoveEvent(e); + } + if (d->m_lastEventWasBlockSelectionEvent && d->m_inBlockSelectionMode) { + if (textCursor().atBlockEnd()) { + d->m_blockSelectionExtraX = qMax(0, e->pos().x() - cursorRect().center().x()) / fontMetrics().width(QLatin1Char('x')); + } else { + d->m_blockSelectionExtraX = 0; + } + } +} + +void BaseTextEditor::mousePressEvent(QMouseEvent *e) +{ + if (e->button() == Qt::LeftButton) { + d->clearBlockSelection(); // just in case, otherwise we might get strange drag and drop + + QTextBlock collapsedBlock = collapsedBlockAt(e->pos()); + if (collapsedBlock.isValid()) { + toggleBlockVisible(collapsedBlock); + viewport()->setCursor(Qt::IBeamCursor); + } + } + QPlainTextEdit::mousePressEvent(e); +} + +void BaseTextEditor::extraAreaLeaveEvent(QEvent *) +{ + if (d->extraAreaHighlightCollapseBlockNumber >= 0) { + d->extraAreaHighlightFadingBlockNumber = d->extraAreaHighlightCollapseBlockNumber; + d->extraAreaHighlightCollapseBlockNumber = -1; // missing mouse move event from Qt + d->extraAreaTimeLine->setDirection(QTimeLine::Backward); + if (d->extraAreaTimeLine->state() != QTimeLine::Running) + d->extraAreaTimeLine->start(); + } +} + +void BaseTextEditor::extraAreaMouseEvent(QMouseEvent *e) +{ + QTextCursor cursor = cursorForPosition(QPoint(0, e->pos().y())); + cursor.setPosition(cursor.block().position()); + + int markWidth; + extraAreaWidth(&markWidth); + + if (e->type() == QEvent::MouseMove && e->buttons() == 0) { // mouse tracking + int highlightBlockNumber = d->extraAreaHighlightCollapseBlockNumber; + d->extraAreaHighlightCollapseBlockNumber = -1; + if (TextBlockUserData::canCollapse(cursor.block()) + && !TextBlockUserData::hasClosingCollapseInside(cursor.block().next()) + && collapseBox(cursor.block()).contains(e->pos())) + d->extraAreaHighlightCollapseBlockNumber = cursor.blockNumber(); + + bool hand = (e->pos().x() <= markWidth || d->extraAreaHighlightCollapseBlockNumber >= 0); + if (hand != (d->m_extraArea->cursor().shape() == Qt::PointingHandCursor)) + d->m_extraArea->setCursor(hand ? Qt::PointingHandCursor : Qt::ArrowCursor); + + if (highlightBlockNumber != d->extraAreaHighlightCollapseBlockNumber) { + d->extraAreaTimeLine->stop(); + d->extraAreaTimeLine->setDirection(d->extraAreaHighlightCollapseBlockNumber >= 0? + QTimeLine::Forward : QTimeLine::Backward); + if (d->extraAreaTimeLine->direction() == QTimeLine::Backward) + d->extraAreaHighlightFadingBlockNumber = highlightBlockNumber; + else + d->extraAreaHighlightFadingBlockNumber = -1; + if (d->extraAreaTimeLine->state() != QTimeLine::Running) + d->extraAreaTimeLine->start(); + } + } + + + if (e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonDblClick) { + if (e->button() == Qt::LeftButton) { + if (TextBlockUserData::canCollapse(cursor.block()) + && !TextBlockUserData::hasClosingCollapseInside(cursor.block().next()) + && collapseBox(cursor.block()).contains(e->pos())) { + setTextCursor(cursor); + toggleBlockVisible(cursor.block()); + } else if (e->pos().x() > markWidth) { + QTextCursor selection = cursor; + selection.setVisualNavigation(true); + d->extraAreaSelectionAnchorBlockNumber = selection.blockNumber(); + selection.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); + selection.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); + setTextCursor(selection); + } else { + d->extraAreaToggleMarkBlockNumber = cursor.blockNumber(); + } + } + } else if (d->extraAreaSelectionAnchorBlockNumber >= 0) { + QTextCursor selection = cursor; + selection.setVisualNavigation(true); + if (e->type() == QEvent::MouseMove) { + QTextBlock anchorBlock = document()->findBlockByNumber(d->extraAreaSelectionAnchorBlockNumber); + selection.setPosition(anchorBlock.position()); + if (cursor.blockNumber() < d->extraAreaSelectionAnchorBlockNumber) { + selection.movePosition(QTextCursor::EndOfBlock); + selection.movePosition(QTextCursor::Right); + } + selection.setPosition(cursor.block().position(), QTextCursor::KeepAnchor); + if (cursor.blockNumber() >= d->extraAreaSelectionAnchorBlockNumber) { + selection.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); + selection.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); + } + + if (e->pos().y() >= 0 && e->pos().y() <= d->m_extraArea->height()) + d->autoScrollTimer.stop(); + else if (!d->autoScrollTimer.isActive()) + d->autoScrollTimer.start(100, this); + + } else { + d->autoScrollTimer.stop(); + d->extraAreaSelectionAnchorBlockNumber = -1; + return; + } + setTextCursor(selection); + } else if (d->extraAreaToggleMarkBlockNumber >= 0 && d->m_marksVisible && d->m_requestMarkEnabled) { + if (e->type() == QEvent::MouseButtonRelease && e->button() == Qt::LeftButton) { + int n = d->extraAreaToggleMarkBlockNumber; + d->extraAreaToggleMarkBlockNumber = -1; + if (cursor.blockNumber() == n) { + int line = n + 1; + emit markRequested(editableInterface(), line); + } + } + } +} + +void BaseTextEditor::slotCursorPositionChanged() +{ + QList<QTextEdit::ExtraSelection> extraSelections; + + if (d->m_highlightCurrentLine) { + QTextEdit::ExtraSelection sel; + sel.format.setBackground(d->m_currentLineFormat.background()); + sel.format.setProperty(QTextFormat::FullWidthSelection, true); + sel.cursor = textCursor(); + sel.cursor.clearSelection(); + extraSelections.append(sel); + } + + if (d->m_parenthesesMatchingEnabled) + d->m_parenthesesMatchingTimer->start(50); + + d->m_extraSelections = extraSelections; + setExtraSelections(d->m_extraSelections + d->m_extraExtraSelections); +} + +QTextBlock TextBlockUserData::testCollapse(const QTextBlock& block) +{ + QTextBlock info = block; + if (block.userData() && static_cast<TextBlockUserData*>(block.userData())->collapseMode() == CollapseAfter) + ; + else if (block.next().userData() + && static_cast<TextBlockUserData*>(block.next().userData())->collapseMode() + == TextBlockUserData::CollapseThis) + info = block.next(); + else + return QTextBlock(); + int pos = static_cast<TextBlockUserData*>(info.userData())->collapseAtPos(); + if (pos < 0) + return QTextBlock(); + QTextCursor cursor(info); + cursor.setPosition(cursor.position() + pos); + matchCursorForward(&cursor); + return cursor.block(); +} + +void TextBlockUserData::doCollapse(const QTextBlock& block, bool visible) +{ + QTextBlock info = block; + if (block.userData() && static_cast<TextBlockUserData*>(block.userData())->collapseMode() == CollapseAfter) + ; + else if (block.next().userData() + && static_cast<TextBlockUserData*>(block.next().userData())->collapseMode() + == TextBlockUserData::CollapseThis) + info = block.next(); + else { + if (visible && !block.next().isVisible()) { + // no match, at least unfold! + QTextBlock b = block.next(); + while (b.isValid() && !b.isVisible()) { + b.setVisible(true); + b.setLineCount(visible ? qMax(1, b.layout()->lineCount()) : 0); + b = b.next(); + } + } + return; + } + int pos = static_cast<TextBlockUserData*>(info.userData())->collapseAtPos(); + if (pos < 0) + return; + QTextCursor cursor(info); + cursor.setPosition(cursor.position() + pos); + if (matchCursorForward(&cursor) != Match) { + if (visible) { + // no match, at least unfold! + QTextBlock b = block.next(); + while (b.isValid() && !b.isVisible()) { + b.setVisible(true); + b.setLineCount(visible ? qMax(1, b.layout()->lineCount()) : 0); + b = b.next(); + } + } + return; + } + + QTextBlock b = block.next(); + while (b < cursor.block()) { + b.setVisible(visible); + b.setLineCount(visible ? qMax(1, b.layout()->lineCount()) : 0); + if (visible) { + TextBlockUserData *data = canCollapse(b); + if (data && data->collapsed()) { + QTextBlock end = testCollapse(b); + if (data->collapseIncludesClosure()) + end = end.next(); + if (end.isValid()) { + b = end; + continue; + } + } + } + b = b.next(); + } + + bool collapseIncludesClosure = hasClosingCollapseAtEnd(b); + if (collapseIncludesClosure) { + b.setVisible(visible); + b.setLineCount(visible ? qMax(1, b.layout()->lineCount()) : 0); + } + static_cast<TextBlockUserData*>(info.userData())->setCollapseIncludesClosure(collapseIncludesClosure); + static_cast<TextBlockUserData*>(info.userData())->setCollapsed(!block.next().isVisible()); + +} + + +void BaseTextEditor::ensureCursorVisible() +{ + QTextBlock block = textCursor().block(); + if (!block.isVisible()) { + while (!block.isVisible() && block.previous().isValid()) + block = block.previous(); + toggleBlockVisible(block); + } + QPlainTextEdit::ensureCursorVisible(); +} + +void BaseTextEditor::toggleBlockVisible(const QTextBlock &block) +{ + TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(document()->documentLayout()); + Q_ASSERT(documentLayout); + + bool visible = block.next().isVisible(); + TextBlockUserData::doCollapse(block, !visible); + documentLayout->requestUpdate(); + documentLayout->emitDocumentSizeChanged(); +} + + +const TabSettings &BaseTextEditor::tabSettings() const +{ + return d->m_document->tabSettings(); +} + +const DisplaySettings &BaseTextEditor::displaySettings() const +{ + return d->m_displaySettings; +} + + + +void BaseTextEditor::indentOrUnindent(bool doIndent) +{ + QTextCursor cursor = textCursor(); + cursor.beginEditBlock(); + + int pos = cursor.position(); + const TextEditor::TabSettings &tabSettings = d->m_document->tabSettings(); + + + QTextDocument *doc = document(); + if (!cursor.hasSelection() + || (doc->findBlock(cursor.selectionStart()) == doc->findBlock(cursor.selectionEnd()) )) { + cursor.removeSelectedText(); + QTextBlock block = cursor.block(); + QString text = block.text(); + int indentPosition = (cursor.position() - block.position());; + int spaces = tabSettings.spacesLeftFromPosition(text, indentPosition); + int startColumn = tabSettings.columnAt(text, indentPosition - spaces); + int targetColumn = tabSettings.indentedColumn(tabSettings.columnAt(text, indentPosition), doIndent); + + cursor.setPosition(block.position() + indentPosition); + cursor.setPosition(block.position() + indentPosition - spaces, QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + cursor.insertText(tabSettings.indentationString(startColumn, targetColumn)); + } else { + int anchor = cursor.anchor(); + int start = qMin(anchor, pos); + int end = qMax(anchor, pos); + + QTextBlock startBlock = doc->findBlock(start); + QTextBlock endBlock = doc->findBlock(end-1).next(); + + for (QTextBlock block = startBlock; block != endBlock; block = block.next()) { + QString text = block.text(); + int indentPosition = tabSettings.lineIndentPosition(text); + if (!doIndent && !indentPosition) + indentPosition = tabSettings.firstNonSpace(text); + int targetColumn = tabSettings.indentedColumn(tabSettings.columnAt(text, indentPosition), doIndent); + cursor.setPosition(block.position() + indentPosition); + cursor.insertText(tabSettings.indentationString(0, targetColumn)); + cursor.setPosition(block.position()); + cursor.setPosition(block.position() + indentPosition, QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + } + } + + cursor.endEditBlock(); +} + +void BaseTextEditor::handleHomeKey(bool anchor) +{ + QTextCursor cursor = textCursor(); + QTextCursor::MoveMode mode = QTextCursor::MoveAnchor; + + if (anchor) + mode = QTextCursor::KeepAnchor; + + const int initpos = cursor.position(); + int pos = cursor.block().position(); + QChar character = characterAt(pos); + const QLatin1Char tab = QLatin1Char('\t'); + + while (character == tab || character.category() == QChar::Separator_Space) { + ++pos; + character = characterAt(pos); + } + + // Go to the start of the block when we're already at the start of the text + if (pos == initpos) + pos = cursor.block().position(); + + cursor.setPosition(pos, mode); + setTextCursor(cursor); +} + +void BaseTextEditor::handleBackspaceKey() +{ + QTextCursor cursor = textCursor(); + Q_ASSERT(!cursor.hasSelection()); + + const TextEditor::TabSettings &tabSettings = d->m_document->tabSettings(); + QTextBlock currentBlock = cursor.block(); + int positionInBlock = cursor.position() - currentBlock.position(); + const QString blockText = currentBlock.text(); + if (cursor.atBlockStart() || tabSettings.firstNonSpace(blockText) < positionInBlock) { + cursor.deletePreviousChar(); + return; + } + + int previousIndent = 0; + const int indent = tabSettings.columnAt(blockText, positionInBlock); + + for (QTextBlock previousNonEmptyBlock = currentBlock.previous(); + previousNonEmptyBlock.isValid(); + previousNonEmptyBlock = previousNonEmptyBlock.previous()) { + QString previousNonEmptyBlockText = previousNonEmptyBlock.text(); + if (previousNonEmptyBlockText.trimmed().isEmpty()) + continue; + previousIndent = tabSettings.columnAt(previousNonEmptyBlockText, + tabSettings.firstNonSpace(previousNonEmptyBlockText)); + if (previousIndent < indent) + break; + } + + if (previousIndent >= indent) + previousIndent = 0; + + cursor.beginEditBlock(); + cursor.setPosition(currentBlock.position(), QTextCursor::KeepAnchor); + cursor.insertText(tabSettings.indentationString(0, previousIndent)); + cursor.endEditBlock(); +} + + +void BaseTextEditor::format() +{ + QTextCursor cursor = textCursor(); + indent(document(), cursor, QChar::Null); +} + +void BaseTextEditor::unCommentSelection() +{ +} + +void BaseTextEditor::setTabSettings(const TabSettings &ts) +{ + d->m_document->setTabSettings(ts); + int charWidth = QFontMetrics(font()).width(QChar(' ')); + setTabStopWidth(charWidth * ts.m_tabSize); +} + +void BaseTextEditor::setDisplaySettings(const DisplaySettings &ds) +{ + setLineWrapMode(ds.m_textWrapping ? QPlainTextEdit::WidgetWidth : QPlainTextEdit::NoWrap); + setLineNumbersVisible(ds.m_displayLineNumbers); + setVisibleWrapColumn(ds.m_showWrapColumn ? ds.m_wrapColumn : 0); + setCodeFoldingVisible(ds.m_displayFoldingMarkers); + setHighlightCurrentLine(ds.m_highlightCurrentLine); + + if (d->m_displaySettings.m_visualizeWhitespace != ds.m_visualizeWhitespace) { + if (QSyntaxHighlighter *highlighter = baseTextDocument()->syntaxHighlighter()) + highlighter->rehighlight(); + QTextOption option = document()->defaultTextOption(); + if (ds.m_visualizeWhitespace) + option.setFlags(option.flags() | QTextOption::ShowTabsAndSpaces); + else + option.setFlags(option.flags() & ~QTextOption::ShowTabsAndSpaces); + option.setFlags(option.flags() | QTextOption::AddSpaceForLineAndParagraphSeparators); + document()->setDefaultTextOption(option); + } + + d->m_displaySettings = ds; +} + + +void BaseTextEditor::wheelEvent(QWheelEvent *e) +{ + d->clearVisibleCollapsedBlock(); + if (e->modifiers() & Qt::ControlModifier) { + const int delta = e->delta(); + if (delta < 0) + zoomOut(); + else if (delta > 0) + zoomIn(); + return; + } + QPlainTextEdit::wheelEvent(e); +} + +void BaseTextEditor::zoomIn(int range) +{ + d->clearVisibleCollapsedBlock(); + QFont f = font(); + const int newSize = f.pointSize() + range; + if (newSize <= 0) + return; + f.setPointSize(newSize); + setFont(f); +} + +void BaseTextEditor::zoomOut(int range) +{ + zoomIn(-range); +} + +bool BaseTextEditor::isElectricCharacter(const QChar &) const +{ + return false; +} + +void BaseTextEditor::indentBlock(QTextDocument *, QTextBlock, QChar) +{ +} + +void BaseTextEditor::indent(QTextDocument *doc, const QTextCursor &cursor, QChar typedChar) +{ + if (cursor.hasSelection()) { + QTextBlock block = doc->findBlock(qMin(cursor.selectionStart(), cursor.selectionEnd())); + const QTextBlock end = doc->findBlock(qMax(cursor.selectionStart(), cursor.selectionEnd())).next(); + do { + indentBlock(doc, block, typedChar); + block = block.next(); + } while (block.isValid() && block != end); + } else { + indentBlock(doc, cursor.block(), typedChar); + } +} + +void BaseTextEditorPrivate::updateMarksBlock(const QTextBlock &block) +{ + if (const TextBlockUserData *userData = TextEditDocumentLayout::testUserData(block)) + foreach (ITextMark *mrk, userData->marks()) { + mrk->updateBlock(block); + } +} + +void BaseTextEditorPrivate::updateMarksLineNumber() +{ + QTextDocument *doc = q->document(); + QTextBlock block = doc->begin(); + int blockNumber = 0; + while (block.isValid()) { + if (const TextBlockUserData *userData = TextEditDocumentLayout::testUserData(block)) + foreach (ITextMark *mrk, userData->marks()) { + mrk->updateLineNumber(blockNumber + 1); + } + block = block.next(); + ++blockNumber; + } +} + +void BaseTextEditor::markBlocksAsChanged(QList<int> blockNumbers) { + QTextBlock block = document()->begin(); + while (block.isValid()) { + if (block.revision() < 0) + block.setRevision(-block.revision() - 1); + block = block.next(); + } + foreach (const int blockNumber, blockNumbers) { + QTextBlock block = document()->findBlockByNumber(blockNumber); + if (block.isValid()) + block.setRevision(-block.revision() - 1); + } +} + + + +TextBlockUserData::MatchType TextBlockUserData::checkOpenParenthesis(QTextCursor *cursor, QChar c) +{ + if (!TextEditDocumentLayout::hasParentheses(cursor->block())) + return NoMatch; + + Parentheses parenList = TextEditDocumentLayout::parentheses(cursor->block()); + Parenthesis openParen, closedParen; + QTextBlock closedParenParag = cursor->block(); + + const int cursorPos = cursor->position() - closedParenParag.position(); + int i = 0; + int ignore = 0; + bool foundOpen = false; + for (;;) { + if (!foundOpen) { + if (i >= parenList.count()) + return NoMatch; + openParen = parenList.at(i); + if (openParen.pos != cursorPos) { + ++i; + continue; + } else { + foundOpen = true; + ++i; + } + } + + if (i >= parenList.count()) { + for (;;) { + closedParenParag = closedParenParag.next(); + if (!closedParenParag.isValid()) + return NoMatch; + if (TextEditDocumentLayout::hasParentheses(closedParenParag)) { + parenList = TextEditDocumentLayout::parentheses(closedParenParag); + break; + } + } + i = 0; + } + + closedParen = parenList.at(i); + if (closedParen.type == Parenthesis::Opened) { + ignore++; + ++i; + continue; + } else { + if (ignore > 0) { + ignore--; + ++i; + continue; + } + + cursor->clearSelection(); + cursor->setPosition(closedParenParag.position() + closedParen.pos + 1, QTextCursor::KeepAnchor); + + if ((c == QLatin1Char('{') && closedParen.chr != QLatin1Char('}')) + || (c == QLatin1Char('(') && closedParen.chr != QLatin1Char(')')) + || (c == QLatin1Char('[') && closedParen.chr != QLatin1Char(']')) + || (c == QLatin1Char('+') && closedParen.chr != QLatin1Char('-')) + ) + return Mismatch; + + return Match; + } + } +} + +TextBlockUserData::MatchType TextBlockUserData::checkClosedParenthesis(QTextCursor *cursor, QChar c) +{ + if (!TextEditDocumentLayout::hasParentheses(cursor->block())) + return NoMatch; + + Parentheses parenList = TextEditDocumentLayout::parentheses(cursor->block()); + Parenthesis openParen, closedParen; + QTextBlock openParenParag = cursor->block(); + + const int cursorPos = cursor->position() - openParenParag.position(); + int i = parenList.count() - 1; + int ignore = 0; + bool foundClosed = false; + for (;;) { + if (!foundClosed) { + if (i < 0) + return NoMatch; + closedParen = parenList.at(i); + if (closedParen.pos != cursorPos - 1) { + --i; + continue; + } else { + foundClosed = true; + --i; + } + } + + if (i < 0) { + for (;;) { + openParenParag = openParenParag.previous(); + if (!openParenParag.isValid()) + return NoMatch; + + if (TextEditDocumentLayout::hasParentheses(openParenParag)) { + parenList = TextEditDocumentLayout::parentheses(openParenParag); + break; + } + } + i = parenList.count() - 1; + } + + openParen = parenList.at(i); + if (openParen.type == Parenthesis::Closed) { + ignore++; + --i; + continue; + } else { + if (ignore > 0) { + ignore--; + --i; + continue; + } + + cursor->clearSelection(); + cursor->setPosition(openParenParag.position() + openParen.pos, QTextCursor::KeepAnchor); + + if ((c == '}' && openParen.chr != '{') || + (c == ')' && openParen.chr != '(') || + (c == ']' && openParen.chr != '[') || + (c == '-' && openParen.chr != '+')) + return Mismatch; + + return Match; + } + } +} + +TextBlockUserData::MatchType TextBlockUserData::matchCursorBackward(QTextCursor *cursor) +{ + cursor->clearSelection(); + const QTextBlock block = cursor->block(); + + if (!TextEditDocumentLayout::hasParentheses(block)) + return NoMatch; + + const int relPos = cursor->position() - block.position(); + + Parentheses parentheses = TextEditDocumentLayout::parentheses(block); + const Parentheses::const_iterator cend = parentheses.constEnd(); + for (Parentheses::const_iterator it = parentheses.constBegin();it != cend; ++it) { + const Parenthesis &paren = *it; + if (paren.pos == relPos - 1 + && paren.type == Parenthesis::Closed) { + return checkClosedParenthesis(cursor, paren.chr); + } + } + return NoMatch; +} + +TextBlockUserData::MatchType TextBlockUserData::matchCursorForward(QTextCursor *cursor) +{ + cursor->clearSelection(); + const QTextBlock block = cursor->block(); + + if (!TextEditDocumentLayout::hasParentheses(block)) + return NoMatch; + + const int relPos = cursor->position() - block.position(); + + Parentheses parentheses = TextEditDocumentLayout::parentheses(block); + const Parentheses::const_iterator cend = parentheses.constEnd(); + for (Parentheses::const_iterator it = parentheses.constBegin();it != cend; ++it) { + const Parenthesis &paren = *it; + if (paren.pos == relPos + && paren.type == Parenthesis::Opened) { + return checkOpenParenthesis(cursor, paren.chr); + } + } + return NoMatch; +} + + +void BaseTextEditor::highlightSearchResults(const QString &txt, QTextDocument::FindFlags findFlags) +{ + if (d->m_searchExpr.pattern() == txt) + return; + d->m_searchExpr.setPattern(txt); + d->m_searchExpr.setPatternSyntax(QRegExp::FixedString); + d->m_searchExpr.setCaseSensitivity((findFlags & QTextDocument::FindCaseSensitively) ? + Qt::CaseSensitive : Qt::CaseInsensitive); + d->m_findFlags = findFlags; + viewport()->update(); +} + + +void BaseTextEditor::setFindScope(const QTextCursor &scope) +{ + if (scope.isNull() != d->m_findScope.isNull()) { + d->m_findScope = scope; + viewport()->update(); + } +} + +void BaseTextEditor::_q_matchParentheses() +{ + if (isReadOnly()) + return; + + QTextCursor backwardMatch = textCursor(); + QTextCursor forwardMatch = textCursor(); + const TextBlockUserData::MatchType backwardMatchType = TextBlockUserData::matchCursorBackward(&backwardMatch); + const TextBlockUserData::MatchType forwardMatchType = TextBlockUserData::matchCursorForward(&forwardMatch); + + if (backwardMatchType == TextBlockUserData::NoMatch && forwardMatchType == TextBlockUserData::NoMatch) + return; + + QList<QTextEdit::ExtraSelection> extraSelections = d->m_extraSelections; + + if (backwardMatch.hasSelection()) { + QTextEdit::ExtraSelection sel; + if (backwardMatchType == TextBlockUserData::Mismatch) { + sel.cursor = backwardMatch; + sel.format = d->m_mismatchFormat; + } else { + + if (d->m_formatRange) { + sel.cursor = backwardMatch; + sel.format = d->m_rangeFormat; + extraSelections.append(sel); + } + + sel.cursor = backwardMatch; + sel.format = d->m_matchFormat; + + sel.cursor.setPosition(backwardMatch.selectionStart()); + sel.cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); + extraSelections.append(sel); + + sel.cursor.setPosition(backwardMatch.selectionEnd()); + sel.cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor); + } + extraSelections.append(sel); + } + + if (forwardMatch.hasSelection()) { + QTextEdit::ExtraSelection sel; + if (forwardMatchType == TextBlockUserData::Mismatch) { + sel.cursor = forwardMatch; + sel.format = d->m_mismatchFormat; + } else { + + if (d->m_formatRange) { + sel.cursor = forwardMatch; + sel.format = d->m_rangeFormat; + extraSelections.append(sel); + } + + sel.cursor = forwardMatch; + sel.format = d->m_matchFormat; + + sel.cursor.setPosition(forwardMatch.selectionStart()); + sel.cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); + extraSelections.append(sel); + + sel.cursor.setPosition(forwardMatch.selectionEnd()); + sel.cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor); + } + extraSelections.append(sel); + } + d->m_extraSelections = extraSelections; + setExtraSelections(d->m_extraSelections + d->m_extraExtraSelections); +} + +void BaseTextEditor::setActionHack(QObject *hack) +{ + d->m_actionHack = hack; +} + +QObject *BaseTextEditor::actionHack() const +{ + return d->m_actionHack; +} + +void BaseTextEditor::changeEvent(QEvent *e) +{ + QPlainTextEdit::changeEvent(e); + if (e->type() == QEvent::ApplicationFontChange + || e->type() == QEvent::FontChange) { + if (d->m_extraArea) { + QFont f = d->m_extraArea->font(); + f.setPointSize(font().pointSize()); + d->m_extraArea->setFont(f); + slotUpdateExtraAreaWidth(); + d->m_extraArea->update(); + } + } +} + +// shift+del +void BaseTextEditor::deleteLine() +{ + QTextCursor cursor = textCursor(); + if (!cursor.hasSelection()) { + const QTextBlock &block = cursor.block(); + if (block.next().isValid()) { + cursor.setPosition(block.position()); + cursor.setPosition(block.next().position(), QTextCursor::KeepAnchor); + } else { + cursor.movePosition(QTextCursor::EndOfBlock); + cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor); + cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor); + } + setTextCursor(cursor); + } + cut(); +} + +void BaseTextEditor::setExtraExtraSelections(const QList<QTextEdit::ExtraSelection> &selections) +{ + d->m_extraExtraSelections = selections; + setExtraSelections(d->m_extraSelections + d->m_extraExtraSelections); +} + +QList<QTextEdit::ExtraSelection> BaseTextEditor::extraExtraSelections() const +{ + return d->m_extraExtraSelections; +} + + +// the blocks list must be sorted +void BaseTextEditor::setIfdefedOutBlocks(const QList<BaseTextEditor::BlockRange> &blocks) +{ + QTextDocument *doc = document(); + TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(doc->documentLayout()); + Q_ASSERT(documentLayout); + + bool needUpdate = false; + + QTextBlock block = doc->firstBlock(); + + int rangeNumber = 0; + while (block.isValid()) { + if (rangeNumber < blocks.size()) { + const BlockRange &range = blocks.at(rangeNumber); + + if (block.position() >= range.first && (block.position() <= range.last || !range.last)) { + needUpdate += TextEditDocumentLayout::setIfdefedOut(block); + } else { + needUpdate += TextEditDocumentLayout::clearIfdefedOut(block); + } + if (block.contains(range.last)) + ++rangeNumber; + } else { + needUpdate |= TextEditDocumentLayout::clearIfdefedOut(block); + } + + block = block.next(); + } + + if (needUpdate) + documentLayout->requestUpdate(); +} + + +void BaseTextEditorPrivate::moveCursorVisible() +{ + QTextCursor cursor = q->textCursor(); + if (!cursor.block().isVisible()) { + cursor.setVisualNavigation(true); + cursor.movePosition(QTextCursor::PreviousBlock); + q->setTextCursor(cursor); + } + q->ensureCursorVisible(); +} + +void BaseTextEditor::collapse() +{ + QTextDocument *doc = document(); + TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(doc->documentLayout()); + Q_ASSERT(documentLayout); + QTextBlock block = textCursor().block(); + while (block.isValid()) { + if (TextBlockUserData::canCollapse(block)) { + if ((block.next().userState()) >> 8 == (textCursor().block().userState() >> 8)) + break; + } + block = block.previous(); + } + if (block.isValid()) { + TextBlockUserData::doCollapse(block, false); + d->moveCursorVisible(); + documentLayout->requestUpdate(); + documentLayout->emitDocumentSizeChanged(); + } +} + +void BaseTextEditor::expand() +{ + QTextDocument *doc = document(); + TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(doc->documentLayout()); + Q_ASSERT(documentLayout); + QTextBlock block = textCursor().block(); + while (block.isValid() && !block.isVisible()) + block = block.previous(); + TextBlockUserData::doCollapse(block, true); + d->moveCursorVisible(); + documentLayout->requestUpdate(); + documentLayout->emitDocumentSizeChanged(); +} + +void BaseTextEditor::unCollapseAll() +{ + QTextDocument *doc = document(); + TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(doc->documentLayout()); + Q_ASSERT(documentLayout); + + QTextBlock block = doc->firstBlock(); + bool makeVisible = true; + while (block.isValid()) { + if (block.isVisible() && TextBlockUserData::canCollapse(block) && block.next().isVisible()) { + makeVisible = false; + break; + } + block = block.next(); + } + + block = doc->firstBlock(); + + while (block.isValid()) { + if (TextBlockUserData::canCollapse(block)) + TextBlockUserData::doCollapse(block, makeVisible); + block = block.next(); + + } + + d->moveCursorVisible(); + documentLayout->requestUpdate(); + documentLayout->emitDocumentSizeChanged(); +} + +void BaseTextEditor::setTextCodec(QTextCodec *codec) +{ + baseTextDocument()->setCodec(codec); +} + +QTextCodec *BaseTextEditor::textCodec() const +{ + return baseTextDocument()->codec(); +} + +void BaseTextEditor::setReadOnly(bool b) +{ + QPlainTextEdit::setReadOnly(b); + if (b) + setTextInteractionFlags(textInteractionFlags() | Qt::TextSelectableByKeyboard); +} + +void BaseTextEditor::cut() +{ + if (d->m_inBlockSelectionMode) { + copy(); + d->removeBlockSelection(); + return; + } + QPlainTextEdit::cut(); +} + +QMimeData *BaseTextEditor::createMimeDataFromSelection() const +{ + if (d->m_inBlockSelectionMode) { + QMimeData *mimeData = new QMimeData; + QString text = d->copyBlockSelection(); + mimeData->setData(QLatin1String("application/vnd.nokia.qtcreator.blocktext"), text.toUtf8()); + mimeData->setText(text); // for exchangeability + return mimeData; + } + return QPlainTextEdit::createMimeDataFromSelection(); +} + +bool BaseTextEditor::canInsertFromMimeData(const QMimeData *source) const +{ + return QPlainTextEdit::canInsertFromMimeData(source); +} + +void BaseTextEditor::insertFromMimeData(const QMimeData *source) +{ + if (!isReadOnly() && source->hasFormat(QLatin1String("application/vnd.nokia.qtcreator.blocktext"))) { + QString text = QString::fromUtf8(source->data(QLatin1String("application/vnd.nokia.qtcreator.blocktext"))); + if (text.isEmpty()) + return; + QStringList lines = text.split(QLatin1Char('\n')); + QTextCursor cursor = textCursor(); + cursor.beginEditBlock(); + int initialCursorPosition = cursor.position(); + int column = cursor.position() - cursor.block().position(); + cursor.insertText(lines.first()); + for (int i = 1; i < lines.count(); ++i) { + QTextBlock next = cursor.block().next(); + if (next.isValid()) { + cursor.setPosition(next.position() + qMin(column, next.length()-1)); + } else { + cursor.movePosition(QTextCursor::EndOfBlock); + cursor.insertBlock(); + } + + int actualColumn = cursor.position() - cursor.block().position(); + if (actualColumn < column) + cursor.insertText(QString(column - actualColumn, QLatin1Char(' '))); + cursor.insertText(lines.at(i)); + } + cursor.setPosition(initialCursorPosition); + cursor.endEditBlock(); + setTextCursor(cursor); + ensureCursorVisible(); + return; + } + QPlainTextEdit::insertFromMimeData(source); +} + +BaseTextEditorEditable::BaseTextEditorEditable(BaseTextEditor *editor) + : e(editor) +{ +#ifndef TEXTEDITOR_STANDALONE + using namespace Find; + Aggregation::Aggregate *aggregate = new Aggregation::Aggregate; + BaseTextFind *baseTextFind = new BaseTextFind(editor); + connect(baseTextFind, SIGNAL(highlightAll(QString, QTextDocument::FindFlags)), + editor, SLOT(highlightSearchResults(QString, QTextDocument::FindFlags))); + connect(baseTextFind, SIGNAL(findScopeChanged(QTextCursor)), editor, SLOT(setFindScope(QTextCursor))); + aggregate->add(baseTextFind); + aggregate->add(editor); +#endif + + m_cursorPositionLabel = new Core::Utils::LineColumnLabel; + + QHBoxLayout *l = new QHBoxLayout; + QWidget *w = new QWidget; + l->setMargin(0); + l->setContentsMargins(0, 0, 5, 0); + l->addStretch(1); + l->addWidget(m_cursorPositionLabel); + w->setLayout(l); + + m_toolBar = new QToolBar; + m_toolBar->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + m_toolBar->addWidget(w); + + connect(editor, SIGNAL(cursorPositionChanged()), this, SLOT(updateCursorPosition())); +} + +BaseTextEditorEditable::~BaseTextEditorEditable() +{ + delete m_toolBar; + delete e; +} + +QToolBar *BaseTextEditorEditable::toolBar() +{ + return m_toolBar; +} + +int BaseTextEditorEditable::find(const QString &) const +{ + return 0; +} + +int BaseTextEditorEditable::currentLine() const +{ + return e->textCursor().blockNumber() + 1; +} + +int BaseTextEditorEditable::currentColumn() const +{ + QTextCursor cursor = e->textCursor(); + return cursor.position() - cursor.block().position() + 1; +} + +QRect BaseTextEditorEditable::cursorRect(int pos) const +{ + QTextCursor tc = e->textCursor(); + if (pos >= 0) + tc.setPosition(pos); + QRect result = e->cursorRect(tc); + result.moveTo(e->viewport()->mapToGlobal(result.topLeft())); + return result; +} + +QString BaseTextEditorEditable::contents() const +{ + return e->toPlainText(); +} + +QString BaseTextEditorEditable::selectedText() const +{ + if (e->textCursor().hasSelection()) + return e->textCursor().selectedText(); + return QString(); +} + +QString BaseTextEditorEditable::textAt(int pos, int length) const +{ + QTextCursor c = e->textCursor(); + + if (pos < 0) + pos = 0; + c.movePosition(QTextCursor::End); + if (pos + length > c.position()) + length = c.position() - pos; + + c.setPosition(pos); + c.setPosition(pos + length, QTextCursor::KeepAnchor); + + return c.selectedText(); +} + +void BaseTextEditorEditable::remove(int length) +{ + QTextCursor tc = e->textCursor(); + tc.setPosition(tc.position() + length, QTextCursor::KeepAnchor); + tc.removeSelectedText(); +} + +void BaseTextEditorEditable::insert(const QString &string) +{ + QTextCursor tc = e->textCursor(); + tc.insertText(string); +} + +void BaseTextEditorEditable::replace(int length, const QString &string) +{ + QTextCursor tc = e->textCursor(); + tc.setPosition(tc.position() + length, QTextCursor::KeepAnchor); + tc.insertText(string); +} + +void BaseTextEditorEditable::setCurPos(int pos) +{ + QTextCursor tc = e->textCursor(); + tc.setPosition(pos); + e->setTextCursor(tc); +} + +void BaseTextEditorEditable::select(int toPos) +{ + QTextCursor tc = e->textCursor(); + tc.setPosition(toPos, QTextCursor::KeepAnchor); + e->setTextCursor(tc); +} + +void BaseTextEditorEditable::updateCursorPosition() +{ + const QTextCursor cursor = e->textCursor(); + const QTextBlock block = cursor.block(); + const int line = block.blockNumber() + 1; + const int column = cursor.position() - block.position() + 1; + m_cursorPositionLabel->setText(QString("Line: %1, Col: %2").arg(line).arg(column), + QString("Line: %1, Col: 999").arg(e->blockCount())); + m_contextHelpId.clear(); + + if (!block.isVisible()) + e->ensureCursorVisible(); + +} + +QString BaseTextEditorEditable::contextHelpId() const +{ + if (m_contextHelpId.isEmpty()) + emit const_cast<BaseTextEditorEditable*>(this)->contextHelpIdRequested(e->editableInterface(), + e->textCursor().position()); + return m_contextHelpId; +} + + +TextBlockUserData::~TextBlockUserData() +{ + TextMarks marks = m_marks; + m_marks.clear(); + foreach (ITextMark *mrk, marks) { + mrk->removedFromEditor(); + } +} + diff --git a/src/plugins/texteditor/basetexteditor.h b/src/plugins/texteditor/basetexteditor.h new file mode 100644 index 0000000000..f41e26b2b0 --- /dev/null +++ b/src/plugins/texteditor/basetexteditor.h @@ -0,0 +1,513 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef BASETEXTEDITOR_H +#define BASETEXTEDITOR_H + +#include "displaysettings.h" +#include "tabsettings.h" +#include "itexteditable.h" + +#include <QtGui/QPlainTextEdit> +#include <QtGui/QLabel> +#include <QtGui/QKeyEvent> + +QT_BEGIN_NAMESPACE +class QLabel; +class QTextCharFormat; +class QToolBar; +QT_END_NAMESPACE + +namespace Core { + namespace Utils { + class LineColumnLabel; + } +} + +namespace TextEditor { + +namespace Internal { + class BaseTextEditorPrivate; +} + +class ITextMark; +class ITextMarkable; + +class TextEditorActionHandler; +class BaseTextDocument; +class FontSettings; +struct StorageSettings; + +struct Parenthesis; +typedef QVector<Parenthesis> Parentheses; +struct TEXTEDITOR_EXPORT Parenthesis +{ + enum Type { Opened, Closed }; + + inline Parenthesis() : type(Opened), pos(-1) {} + inline Parenthesis(Type t, QChar c, int position) + : type(t), chr(c), pos(position) {} + Type type; + QChar chr; + int pos; + static int collapseAtPos(const Parentheses &parentheses, QChar *character = 0); + static int closeCollapseAtPos(const Parentheses &parentheses); + static bool hasClosingCollapse(const Parentheses &parentheses); +}; + + + +class TEXTEDITOR_EXPORT TextBlockUserData : public QTextBlockUserData { +public: + + enum CollapseMode { NoCollapse , CollapseThis, CollapseAfter }; + enum ClosingCollapseMode { NoClosingCollapse, ClosingCollapse, ClosingCollapseAtEnd }; + + inline TextBlockUserData() + : m_collapseIncludesClosure(false), + m_collapseMode(NoCollapse), + m_closingCollapseMode(NoClosingCollapse), + m_collapsed(false), + m_ifdefedOut(false) {} + ~TextBlockUserData(); + + inline TextMarks marks() const { return m_marks; } + inline void addMark(ITextMark *mark) { m_marks += mark; } + inline bool removeMark(ITextMark *mark) { return m_marks.removeAll(mark); } + inline bool hasMark(ITextMark *mark) const { return m_marks.contains(mark); } + inline void clearMarks() { m_marks.clear(); } + inline void documentClosing() { Q_FOREACH(ITextMark *tm, m_marks) { tm->documentClosing(); } m_marks.clear();} + + inline CollapseMode collapseMode() const { return (CollapseMode)m_collapseMode; } + inline void setCollapseMode(CollapseMode c) { m_collapseMode = c; } + + inline void setClosingCollapseMode(ClosingCollapseMode c) { m_closingCollapseMode = c; } + inline ClosingCollapseMode closingCollapseMode() const { return (ClosingCollapseMode) m_closingCollapseMode; } + + inline bool hasClosingCollapse() const { return closingCollapseMode() != NoClosingCollapse; } + inline bool hasClosingCollapseAtEnd() const { return closingCollapseMode() == ClosingCollapseAtEnd; } + inline bool hasClosingCollapseInside() const { return closingCollapseMode() == ClosingCollapse; } + + inline void setCollapsed(bool b) { m_collapsed = b; } + inline bool collapsed() const { return m_collapsed; } + + inline void setCollapseIncludesClosure(bool b) { m_collapseIncludesClosure = b; } + inline bool collapseIncludesClosure() const { return m_collapseIncludesClosure; } + + inline void setParentheses(const Parentheses &parentheses) { m_parentheses = parentheses; } + inline void clearParentheses() { m_parentheses.clear(); } + inline const Parentheses &parentheses() const { return m_parentheses; } + inline bool hasParentheses() const { return !m_parentheses.isEmpty(); } + + inline bool setIfdefedOut() { bool result = m_ifdefedOut; m_ifdefedOut = true; return !result; } + inline bool clearIfdefedOut() { bool result = m_ifdefedOut; m_ifdefedOut = false; return result;} + inline bool ifdefedOut() const { return m_ifdefedOut; } + + inline static TextBlockUserData *canCollapse(const QTextBlock& block) { + TextBlockUserData *data = static_cast<TextBlockUserData*>(block.userData()); + if (!data || data->collapseMode() != CollapseAfter) { + data = static_cast<TextBlockUserData*>(block.next().userData()); + if (!data || data->collapseMode() != TextBlockUserData::CollapseThis) + data = 0; + } + return data; + } + + inline static bool hasClosingCollapse(const QTextBlock &block) { + TextBlockUserData *data = static_cast<TextBlockUserData*>(block.userData()); + return (data && data->hasClosingCollapse()); + } + + inline static bool hasClosingCollapseAtEnd(const QTextBlock &block) { + TextBlockUserData *data = static_cast<TextBlockUserData*>(block.userData()); + return (data && data->hasClosingCollapseAtEnd()); + } + + inline static bool hasClosingCollapseInside(const QTextBlock &block) { + TextBlockUserData *data = static_cast<TextBlockUserData*>(block.userData()); + return (data && data->hasClosingCollapseInside()); + } + + static QTextBlock testCollapse(const QTextBlock& block); + static void doCollapse(const QTextBlock& block, bool visible); + + int collapseAtPos() const; + + enum MatchType { NoMatch, Match, Mismatch }; + static MatchType checkOpenParenthesis(QTextCursor *cursor, QChar c); + static MatchType checkClosedParenthesis(QTextCursor *cursor, QChar c); + static MatchType matchCursorBackward(QTextCursor *cursor); + static MatchType matchCursorForward(QTextCursor *cursor); + + +private: + TextMarks m_marks; + uint m_collapseIncludesClosure : 1; + uint m_collapseMode : 4; + uint m_closingCollapseMode : 4; + uint m_collapsed : 1; + uint m_ifdefedOut : 1; + Parentheses m_parentheses; +}; + + +class TEXTEDITOR_EXPORT TextEditDocumentLayout : public QPlainTextDocumentLayout +{ + Q_OBJECT + +public: + TextEditDocumentLayout(QTextDocument *doc); + ~TextEditDocumentLayout(); + + QRectF blockBoundingRect(const QTextBlock &block) const; + + static void setParentheses(const QTextBlock &block, const Parentheses &parentheses); + static void clearParentheses(const QTextBlock &block) { setParentheses(block, Parentheses());} + static Parentheses parentheses(const QTextBlock &block); + static bool hasParentheses(const QTextBlock &block); + static bool setIfdefedOut(const QTextBlock &block); + static bool clearIfdefedOut(const QTextBlock &block); + static bool ifdefedOut(const QTextBlock &block); + + static TextBlockUserData *testUserData(const QTextBlock &block) { + return static_cast<TextBlockUserData*>(block.userData()); + } + static TextBlockUserData *userData(const QTextBlock &block) { + TextBlockUserData *data = static_cast<TextBlockUserData*>(block.userData()); + if (!data && block.isValid()) + const_cast<QTextBlock &>(block).setUserData((data = new TextBlockUserData)); + return data; + } + + + void emitDocumentSizeChanged() { emit documentSizeChanged(documentSize()); } + int lastSaveRevision; + bool hasMarks; +}; + + +class BaseTextEditorEditable; + +class TEXTEDITOR_EXPORT BaseTextEditor + : public QPlainTextEdit +{ + Q_OBJECT + +public: + BaseTextEditor(QWidget *parent); + ~BaseTextEditor(); + + static ITextEditor *openEditorAt(const QString &fileName, int line, int column = 0); + + // EditorInterface + Core::IFile * file(); + bool createNew(const QString &contents); + bool open(const QString &fileName = QString()); + QByteArray saveState() const; + bool restoreState(const QByteArray &state); + QString displayName() const; + + // ITextEditor + + void gotoLine(int line, int column = 0); + + int position( + ITextEditor::PositionOperation posOp = ITextEditor::Current + , int at = -1) const; + void convertPosition(int pos, int *line, int *column) const; + + ITextEditable *editableInterface() const; + ITextMarkable *markableInterface() const; + + virtual void triggerCompletions(); + + QChar characterAt(int pos) const; + + void print(QPrinter *); + + void setSuggestedFileName(const QString &suggestedFileName); + QString mimeType() const; + void setMimeType(const QString &mt); + + + // Works only in conjunction with a syntax highlighter that puts + // parentheses into text block user data + void setParenthesesMatchingEnabled(bool b); + bool isParenthesesMatchingEnabled() const; + + void setHighlightCurrentLine(bool b); + bool highlightCurrentLine() const; + + void setLineNumbersVisible(bool b); + bool lineNumbersVisible() const; + + void setMarksVisible(bool b); + bool marksVisible() const; + + void setRequestMarkEnabled(bool b); + bool requestMarkEnabled() const; + + void setLineSeparatorsAllowed(bool b); + bool lineSeparatorsAllowed() const; + + void setCodeFoldingVisible(bool b); + bool codeFoldingVisible() const; + + void setRevisionsVisible(bool b); + bool revisionsVisible() const; + + void setVisibleWrapColumn(int column); + int visibleWrapColumn() const; + + void setActionHack(QObject *); + QObject *actionHack() const; + + void setTextCodec(QTextCodec *codec); + QTextCodec *textCodec() const; + + void setReadOnly(bool b); + +public slots: + void setDisplayName(const QString &title); + virtual void setFontSettings(const TextEditor::FontSettings &); + virtual void format(); + virtual void unCommentSelection(); + virtual void setStorageSettings(const TextEditor::StorageSettings &); + + void cut(); + + void zoomIn(int range = 1); + void zoomOut(int range = 1); + + void deleteLine(); + void unCollapseAll(); + void collapse(); + void expand(); + void selectEncoding(); + +signals: + void changed(); + + // ITextEditor + void contentsChanged(); + +protected: + bool event(QEvent *e); + void keyPressEvent(QKeyEvent *e); + void wheelEvent(QWheelEvent *e); + void changeEvent(QEvent *e); + + // reimplemented to support block selection + QMimeData *createMimeDataFromSelection() const; + bool canInsertFromMimeData(const QMimeData *source) const; + void insertFromMimeData(const QMimeData *source); + +public: + void duplicateFrom(BaseTextEditor *editor); +protected: + BaseTextDocument *baseTextDocument() const; + void setBaseTextDocument(BaseTextDocument *doc); + + void setDefaultPath(const QString &defaultPath); + + virtual BaseTextEditorEditable *createEditableInterface() = 0; + +private slots: + void editorContentsChange(int position, int charsRemoved, int charsAdded); + void memorizeCursorPosition(); + void restoreCursorPosition(); + void highlightSearchResults(const QString &txt, QTextDocument::FindFlags findFlags); + void setFindScope(const QTextCursor &); + void setCollapseIndicatorAlpha(int); + void currentEditorChanged(Core::IEditor *editor); + +private: + Internal::BaseTextEditorPrivate *d; + friend class Internal::BaseTextEditorPrivate; + + +public: + QWidget *extraArea() const; + virtual int extraAreaWidth(int *markWidthPtr = 0) const; + virtual void extraAreaPaintEvent(QPaintEvent *); + virtual void extraAreaMouseEvent(QMouseEvent *); + virtual void extraAreaLeaveEvent(QEvent *); + + + const TabSettings &tabSettings() const; + const DisplaySettings &displaySettings() const; + + void markBlocksAsChanged(QList<int> blockNumbers); + + void ensureCursorVisible(); + + void setExtraExtraSelections(const QList<QTextEdit::ExtraSelection> &selections); + QList<QTextEdit::ExtraSelection> extraExtraSelections() const; + + struct BlockRange { + BlockRange():first(0), last(-1){} + BlockRange(int first_position, int last_position):first(first_position), last(last_position){} + int first; + int last; + inline bool isNull() const { return last < first; } + }; + + // the blocks list must be sorted + void setIfdefedOutBlocks(const QList<BaseTextEditor::BlockRange> &blocks); + + +public slots: + virtual void setTabSettings(const TextEditor::TabSettings &); + virtual void setDisplaySettings(const TextEditor::DisplaySettings &); + +protected: + bool viewportEvent(QEvent *event); + + void resizeEvent(QResizeEvent *); + void paintEvent(QPaintEvent *); + void timerEvent(QTimerEvent *); + void mouseMoveEvent(QMouseEvent *); + void mousePressEvent(QMouseEvent *); + + // Rertuns true if key triggers an indent. + virtual bool isElectricCharacter(const QChar &ch) const; + // Indent a text block based on previous line. Default does nothing + virtual void indentBlock(QTextDocument *doc, QTextBlock block, QChar typedChar); + // Indent at cursor. Calls indentBlock for selection or current line. + virtual void indent(QTextDocument *doc, const QTextCursor &cursor, QChar typedChar); + + +protected slots: + virtual void slotUpdateExtraAreaWidth(); + virtual void slotModificationChanged(bool); + virtual void slotUpdateRequest(const QRect &r, int dy); + virtual void slotCursorPositionChanged(); + virtual void slotUpdateBlockNotify(const QTextBlock &); + + + +signals: + void markRequested(TextEditor::ITextEditor *editor, int line); + void requestBlockUpdate(const QTextBlock &); + void requestAutoCompletion(ITextEditable *editor, bool forced); + +private: + void indentOrUnindent(bool doIndent); + void handleHomeKey(bool anchor); + void handleBackspaceKey(); + + void toggleBlockVisible(const QTextBlock &block); + QRect collapseBox(const QTextBlock &block); + + QTextBlock collapsedBlockAt(const QPoint &pos, QRect *box = 0) const; + + // parentheses matcher +private slots: + void _q_matchParentheses(); + void slotSelectionChanged(); +}; + + +class TEXTEDITOR_EXPORT BaseTextEditorEditable + : public ITextEditable +{ + Q_OBJECT + friend class BaseTextEditor; +public: + BaseTextEditorEditable(BaseTextEditor *editor); + ~BaseTextEditorEditable(); + + inline BaseTextEditor *editor() const { return e; } + + // EditorInterface + inline QWidget *widget() { return e; } + inline Core::IFile * file() { return e->file(); } + inline bool createNew(const QString &contents) { return e->createNew(contents); } + inline bool open(const QString &fileName = QString()) + { + return e->open(fileName); + } + inline QString displayName() const { return e->displayName(); } + inline void setDisplayName(const QString &title) { e->setDisplayName(title); } + + inline QByteArray saveState() const { return e->saveState(); } + inline bool restoreState(const QByteArray &state) { return e->restoreState(state); } + QToolBar *toolBar(); + + // ITextEditor + int find(const QString &string) const; + + int currentLine() const; + int currentColumn() const; + inline void gotoLine(int line, int column = 0) { e->gotoLine(line, column); } + + inline int position( + ITextEditor::PositionOperation posOp = ITextEditor::Current + , int at = -1) const { return e->position(posOp, at); } + inline void convertPosition(int pos, int *line, int *column) const { e->convertPosition(pos, line, column); } + QRect cursorRect(int pos = -1) const; + + QString contents() const; + QString selectedText() const; + QString textAt(int pos, int length) const; + inline QChar characterAt(int pos) const { return e->characterAt(pos); } + + inline void triggerCompletions() { e->triggerCompletions(); } // slot? + inline ITextMarkable *markableInterface() { return e->markableInterface(); } + + void setContextHelpId(const QString &id) { m_contextHelpId = id; } + QString contextHelpId() const; // from IContext + + inline void setTextCodec(QTextCodec *codec) { e->setTextCodec(codec); } + inline QTextCodec *textCodec() const { return e->textCodec(); } + + + // ITextEditable + void remove(int length); + void insert(const QString &string); + void replace(int length, const QString &string); + void setCurPos(int pos); + void select(int toPos); + +private slots: + void updateCursorPosition(); + +private: + BaseTextEditor *e; + mutable QString m_contextHelpId; + QToolBar *m_toolBar; + Core::Utils::LineColumnLabel *m_cursorPositionLabel; +}; + +} // namespace TextEditor + +#endif // BASETEXTEDITOR_H diff --git a/src/plugins/texteditor/basetexteditor_p.h b/src/plugins/texteditor/basetexteditor_p.h new file mode 100644 index 0000000000..4bb0de7070 --- /dev/null +++ b/src/plugins/texteditor/basetexteditor_p.h @@ -0,0 +1,225 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef BASETEXTEDITOR_P_H +#define BASETEXTEDITOR_P_H + +#include "basetexteditor.h" + +#include <QtCore/QBasicTimer> +#include <QtCore/QTimeLine> +#include <QtCore/QSharedData> + +#include <QtGui/QTextEdit> +#include <QtGui/QPixmap> + +namespace TextEditor { + +class BaseTextDocument; + +namespace Internal { + +//========== Pointers with reference count ========== + +template <class T> class QRefCountData : public QSharedData +{ +public: + QRefCountData(T *data) { m_data = data; } + + ~QRefCountData() { delete m_data; } + + T *m_data; +}; + +/* MOSTLY COPIED FROM QSHAREDDATA(-POINTER) */ +template <class T> class QRefCountPointer +{ +public: + inline T &operator*() { return d ? *(d->m_data) : 0; } + inline const T &operator*() const { return d ? *(d->m_data) : 0; } + inline T *operator->() { return d ? d->m_data : 0; } + inline const T *operator->() const { return d ? d->m_data : 0; } + inline operator T *() { return d ? d->m_data : 0; } + inline operator const T *() const { return d ? d->m_data : 0; } + + inline bool operator==(const QRefCountPointer<T> &other) const { return d == other.d; } + inline bool operator!=(const QRefCountPointer<T> &other) const { return d != other.d; } + + inline QRefCountPointer() { d = 0; } + inline ~QRefCountPointer() { if (d && !d->ref.deref()) delete d; } + + explicit QRefCountPointer(T *data) { + if (data) { + d = new QRefCountData<T>(data); + d->ref.ref(); + } + else { + d = 0; + } + } + inline QRefCountPointer(const QRefCountPointer<T> &o) : d(o.d) { if (d) d->ref.ref(); } + inline QRefCountPointer<T> & operator=(const QRefCountPointer<T> &o) { + if (o.d != d) { + if (d && !d->ref.deref()) + delete d; + //todo: atomic assign of pointers + d = o.d; + if (d) + d->ref.ref(); + } + return *this; + } + inline QRefCountPointer &operator=(T *o) { + if (d == 0 || d->m_data != o) { + if (d && !d->ref.deref()) + delete d; + d = new QRefCountData<T>(o); + if (d) + d->ref.ref(); + } + return *this; + } + + inline bool operator!() const { return !d; } + +private: + QRefCountData<T> *d; +}; + +//================BaseTextEditorPrivate============== + +class BaseTextEditorPrivate +{ + BaseTextEditorPrivate(const BaseTextEditorPrivate &); + BaseTextEditorPrivate &operator=(const BaseTextEditorPrivate &); + +public: + BaseTextEditorPrivate(); + ~BaseTextEditorPrivate(); + +#ifndef TEXTEDITOR_STANDALONE + void setupBasicEditActions(TextEditorActionHandler *actionHandler); +#endif + void setupDocumentSignals(BaseTextDocument *document); + void updateLineSelectionColor(); +#ifndef TEXTEDITOR_STANDALONE + bool needMakeWritableCheck() const; +#endif + + void print(QPrinter *printer); + + QTextBlock m_firstVisible; + int m_lastScrollPos; + int m_lineNumber; + + BaseTextEditor *q; + bool m_contentsChanged; + + QList<QTextEdit::ExtraSelection> m_syntaxHighlighterSelections; + QTextEdit::ExtraSelection m_lineSelection; + + QRefCountPointer<BaseTextDocument> m_document; + QByteArray m_tempState; + + QString m_displayName; + bool m_parenthesesMatchingEnabled; + QTimer *m_updateTimer; + + // parentheses matcher + bool m_formatRange; + QTextCharFormat m_matchFormat; + QTextCharFormat m_mismatchFormat; + QTextCharFormat m_rangeFormat; + QTimer *m_parenthesesMatchingTimer; + // end parentheses matcher + + QWidget *m_extraArea; + DisplaySettings m_displaySettings; + + int extraAreaSelectionAnchorBlockNumber; + int extraAreaToggleMarkBlockNumber; + int extraAreaHighlightCollapseBlockNumber; + int extraAreaCollapseAlpha; + int extraAreaHighlightFadingBlockNumber; + QTimeLine *extraAreaTimeLine; + + QBasicTimer collapsedBlockTimer; + int visibleCollapsedBlockNumber; + int suggestedVisibleCollapsedBlockNumber; + void clearVisibleCollapsedBlock(); + + QBasicTimer autoScrollTimer; + void updateMarksLineNumber(); + void updateMarksBlock(const QTextBlock &block); + uint m_marksVisible : 1; + uint m_codeFoldingVisible : 1; + uint m_revisionsVisible : 1; + uint m_lineNumbersVisible : 1; + uint m_highlightCurrentLine : 1; + uint m_requestMarkEnabled : 1; + uint m_lineSeparatorsAllowed : 1; + int m_visibleWrapColumn; + + QTextCharFormat m_ifdefedOutFormat; + + QRegExp m_searchExpr; + QTextDocument::FindFlags m_findFlags; + QTextCharFormat m_searchResultFormat; + QTextCharFormat m_searchScopeFormat; + QTextCharFormat m_currentLineFormat; + void highlightSearchResults(const QTextBlock &block, + QVector<QTextLayout::FormatRange> *selections); + + BaseTextEditorEditable *m_editable; + + QObject *m_actionHack; + + QList<QTextEdit::ExtraSelection> m_extraSelections; + QList<QTextEdit::ExtraSelection> m_extraExtraSelections; + + // block selection mode + bool m_inBlockSelectionMode; + bool m_lastEventWasBlockSelectionEvent; + int m_blockSelectionExtraX; + void clearBlockSelection(); + QString copyBlockSelection(); + void removeBlockSelection(const QString &text = QString()); + + QTextCursor m_findScope; + + void moveCursorVisible(); +}; + +} // namespace Internal +} // namespace TextEditor + +#endif // BASETEXTEDITOR_P_H diff --git a/src/plugins/texteditor/basetextmark.cpp b/src/plugins/texteditor/basetextmark.cpp new file mode 100644 index 0000000000..4110480332 --- /dev/null +++ b/src/plugins/texteditor/basetextmark.cpp @@ -0,0 +1,136 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "basetextmark.h" +#include <coreplugin/editormanager/editormanager.h> +#include <extensionsystem/pluginmanager.h> +#include <coreplugin/icore.h> +#include <QtCore/QTimer> + +using namespace TextEditor; +using namespace TextEditor::Internal; + +BaseTextMark::BaseTextMark() + : m_markableInterface(0), m_internalMark(0), m_init(false) +{ + +} + +BaseTextMark::BaseTextMark(const QString &filename, int line) + : m_markableInterface(0), m_internalMark(0), m_fileName(filename), m_line(line), m_init(false) +{ + // Why is this? + QTimer::singleShot(0, this, SLOT(init())); +} + +void BaseTextMark::init() +{ + m_init = true; + Core::EditorManager *em = ExtensionSystem::PluginManager::instance()->getObject<Core::ICore>()->editorManager(); + connect(em, SIGNAL(editorOpened(Core::IEditor *)), this, SLOT(editorOpened(Core::IEditor *))); + + foreach(Core::IEditor *editor, em->openedEditors()) { + editorOpened(editor); + } +} + +void BaseTextMark::editorOpened(Core::IEditor *editor) +{ + if (editor->file()->fileName() != m_fileName) + return; + if (ITextEditor *textEditor = qobject_cast<ITextEditor *>(editor)) { + if (m_markableInterface == 0) { // We aren't added to something + m_markableInterface = textEditor->markableInterface(); + m_internalMark = new InternalMark(this); + m_markableInterface->addMark(m_internalMark, m_line); + } + } +} + +void BaseTextMark::childRemovedFromEditor(InternalMark *mark) +{ + Q_UNUSED(mark) + // m_internalMark was removed from the editor + delete m_internalMark; + m_markableInterface = 0; + m_internalMark = 0; + removedFromEditor(); +} + +void BaseTextMark::documentClosingFor(InternalMark *mark) +{ + Q_UNUSED(mark) + // the document is closing + delete m_internalMark; + m_markableInterface = 0; + m_internalMark = 0; +} + +BaseTextMark::~BaseTextMark() +{ + // oha we are deleted + if (m_markableInterface) + m_markableInterface->removeMark(m_internalMark); + delete m_internalMark; + m_internalMark = 0; + m_markableInterface = 0; +} + +//#include <QDebug> + +void BaseTextMark::updateMarker() +{ + //qDebug()<<"BaseTextMark::updateMarker()"<<m_markableInterface<<m_internalMark; + if (m_markableInterface) + m_markableInterface->updateMark(m_internalMark); +} + +void BaseTextMark::moveMark(const QString & /* filename */, int /* line */) +{ + Core::EditorManager *em = ExtensionSystem::PluginManager::instance()->getObject<Core::ICore>()->editorManager(); + if (!m_init) { + connect(em, SIGNAL(editorOpened(Core::IEditor *)), this, SLOT(editorOpened(Core::IEditor *))); + m_init = true; + } + + + if (m_markableInterface) + m_markableInterface->removeMark(m_internalMark); + m_markableInterface = 0; + // This is only necessary since m_internalMark is created in ediorOpened + delete m_internalMark; + m_internalMark = 0; + + foreach(Core::IEditor *editor, em->openedEditors()) { + editorOpened(editor); + } +} diff --git a/src/plugins/texteditor/basetextmark.h b/src/plugins/texteditor/basetextmark.h new file mode 100644 index 0000000000..763e3eec47 --- /dev/null +++ b/src/plugins/texteditor/basetextmark.h @@ -0,0 +1,132 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef BASETEXTMARK_H +#define BASETEXTMARK_H + +#include "itexteditor.h" + +namespace TextEditor { + +class ITextMarkable; + +namespace Internal { +class InternalMark; +} + +class TEXTEDITOR_EXPORT BaseTextMark : public QObject +{ + friend class Internal::InternalMark; + Q_OBJECT +public: + BaseTextMark(); + BaseTextMark(const QString &filename, int line); + ~BaseTextMark(); + + // return your icon here + virtual QIcon icon() const = 0; + + // called if the linenumber changes + virtual void updateLineNumber(int lineNumber) = 0; + + // called whenever the text of the block for the marker changed + virtual void updateBlock(const QTextBlock &block) = 0; + + // called if the block containing this mark has been removed + // if this also removes your mark call this->deleteLater(); + virtual void removedFromEditor() = 0; + // call this if the icon has changed. + void updateMarker(); + // access to internal data + QString fileName() const { return m_fileName; } + int lineNumber() const { return m_line; } + + void moveMark(const QString &filename, int line); +private slots: + void editorOpened(Core::IEditor *editor); + void init(); +private: + void childRemovedFromEditor(Internal::InternalMark *mark); + void documentClosingFor(Internal::InternalMark *mark); + + ITextMarkable *m_markableInterface; + Internal::InternalMark *m_internalMark; + + QString m_fileName; + int m_line; + bool m_init; +}; + +namespace Internal { + +class InternalMark : public ITextMark +{ +public: + InternalMark(BaseTextMark *parent) + : m_parent(parent) + { + } + + ~InternalMark() + { + } + + virtual QIcon icon() const + { + return m_parent->icon(); + } + + virtual void updateLineNumber(int lineNumber) + { + return m_parent->updateLineNumber(lineNumber); + } + + virtual void updateBlock(const QTextBlock &block) + { + return m_parent->updateBlock(block); + } + + virtual void removedFromEditor() + { + m_parent->childRemovedFromEditor(this); + } + + virtual void documentClosing() + { + m_parent->documentClosingFor(this); + } +private: + BaseTextMark *m_parent; +}; +} +} +#endif // BASETEXTMARK_H diff --git a/src/plugins/texteditor/codecselector.cpp b/src/plugins/texteditor/codecselector.cpp new file mode 100644 index 0000000000..5893fa5b71 --- /dev/null +++ b/src/plugins/texteditor/codecselector.cpp @@ -0,0 +1,179 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "codecselector.h" +#include "basetextdocument.h" + + +#include <QtCore/QDebug> +#include <QtCore/QFileInfo> +#include <QtCore/QTextCodec> +#include <QtGui/QPushButton> +#include <QtGui/QScrollBar> +#include <QtGui/QVBoxLayout> + +using namespace TextEditor; +using namespace TextEditor::Internal; + + +namespace TextEditor { + namespace Internal { + + /* custom class to make sure the width is wide enough for the + * contents. Should be easier with Qt. */ + class CodecListWidget : public QListWidget { + public: + CodecListWidget(QWidget *parent):QListWidget(parent){} + QSize sizeHint() const { + return QListWidget::sizeHint().expandedTo( + QSize(sizeHintForColumn(0) + verticalScrollBar()->sizeHint().width() + 4, 0)); + } + }; + } +} + +CodecSelector::CodecSelector(QWidget *parent, BaseTextDocument *doc) + : QDialog(parent) +{ + m_hasDecodingError = doc->hasDecodingError(); + m_isModified = doc->isModified(); + + QByteArray buf; + if (m_hasDecodingError) + buf = doc->decodingErrorSample(); + + setWindowTitle(tr("Text Encoding")); + m_label = new QLabel(this); + QString decodingErrorHint; + if (m_hasDecodingError) + decodingErrorHint = tr("\nThe following encodings are likely to fit:"); + m_label->setText(tr("Select encoding for \"%1\".%2").arg(QFileInfo(doc->fileName()).fileName()).arg(decodingErrorHint)); + + m_listWidget = new CodecListWidget(this); + + QStringList encodings; + + QList<int> mibs = QTextCodec::availableMibs(); + qSort(mibs); + QList<int> sortedMibs; + foreach(int mib, mibs) + if (mib >= 0) + sortedMibs += mib; + foreach(int mib, mibs) + if (mib < 0) + sortedMibs += mib; + + int currentIndex = -1; + foreach(int mib, sortedMibs) { + QTextCodec *c = QTextCodec::codecForMib(mib); + if (!buf.isEmpty()) { + + // slow, should use a feature from QTextCodec or QTextDecoder (but those are broken currently) + QByteArray verifyBuf = c->fromUnicode(c->toUnicode(buf)); + // the minSize trick lets us ignore unicode headers + int minSize = qMin(verifyBuf.size(), buf.size()); + if (minSize < buf.size() - 4 + || memcmp(verifyBuf.constData() + verifyBuf.size() - minSize, + buf.constData() + buf.size() - minSize, minSize)) + continue; + } + QString names = QString::fromLatin1(c->name()); + foreach(QByteArray alias, c->aliases()) { + names += QLatin1String(" / ") + QString::fromLatin1(alias); + } + if (doc->codec() == c) + currentIndex = encodings.count(); + encodings << names; + } + m_listWidget->addItems(encodings); + if (currentIndex >= 0) + m_listWidget->setCurrentRow(currentIndex); + + connect(m_listWidget, SIGNAL(itemSelectionChanged()), this, SLOT(updateButtons())); + + m_dialogButtonBox = new QDialogButtonBox(this); + m_reloadButton = m_dialogButtonBox->addButton(tr("Reload with Encoding"), QDialogButtonBox::DestructiveRole); + m_saveButton = m_dialogButtonBox->addButton(tr("Save with Encoding"), QDialogButtonBox::DestructiveRole); + m_dialogButtonBox->addButton(QDialogButtonBox::Cancel); + connect(m_dialogButtonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(buttonClicked(QAbstractButton*))); + + QVBoxLayout *vbox = new QVBoxLayout(this); + vbox->addWidget(m_label); + vbox->addWidget(m_listWidget); + vbox->addWidget(m_dialogButtonBox); + + updateButtons(); +} + + + +CodecSelector::~CodecSelector() +{ +} + +void CodecSelector::updateButtons() +{ + bool hasCodec = (selectedCodec() != 0); + m_reloadButton->setEnabled(!m_isModified && hasCodec); + m_saveButton->setEnabled(!m_hasDecodingError && hasCodec); +} + +QTextCodec *CodecSelector::selectedCodec() const +{ + if (QListWidgetItem *item = m_listWidget->currentItem()) { + if (!item->isSelected()) + return 0; + QString codecName = item->text(); + if (codecName.contains(QLatin1String(" / "))) + codecName = codecName.left(codecName.indexOf(QLatin1String(" / "))); + return QTextCodec::codecForName(codecName.toLatin1()); + } + return 0; +} + + +CodecSelector::Result CodecSelector::exec() +{ + return (Result) QDialog::exec(); +} + + +void CodecSelector::buttonClicked(QAbstractButton *button) +{ + Result result = Cancel; + if (button == m_reloadButton) + result = Reload; + if (button == m_saveButton) + result = Save; + done(result); +} + diff --git a/src/plugins/texteditor/codecselector.h b/src/plugins/texteditor/codecselector.h new file mode 100644 index 0000000000..aa873b3a2b --- /dev/null +++ b/src/plugins/texteditor/codecselector.h @@ -0,0 +1,85 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef CODECSELECTOR_H +#define CODECSELECTOR_H + +#include <QtGui/QDialog> +#include <QtGui/QLabel> +#include <QtGui/QDialogButtonBox> +#include <QtGui/QListWidget> + +namespace TextEditor { + +class BaseTextDocument; + +namespace Internal { + +class CodecSelector : public QDialog +{ + Q_OBJECT + +public: + + CodecSelector(QWidget *parent, BaseTextDocument *doc); + ~CodecSelector(); + + QTextCodec *selectedCodec() const; + + enum Result { + Cancel, Reload, Save + }; + + Result exec(); + +private slots: + void updateButtons(); + void buttonClicked(QAbstractButton *button); + +private: + bool m_hasDecodingError; + bool m_isModified; + QLabel *m_label; + QListWidget *m_listWidget; + QDialogButtonBox *m_dialogButtonBox; + QAbstractButton *m_reloadButton; + QAbstractButton *m_saveButton; +}; + + + + + +} +} + +#endif // CODECSELECTOR_H diff --git a/src/plugins/texteditor/completionsupport.cpp b/src/plugins/texteditor/completionsupport.cpp new file mode 100644 index 0000000000..6c9972d7eb --- /dev/null +++ b/src/plugins/texteditor/completionsupport.cpp @@ -0,0 +1,171 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "completionsupport.h" +#include "completionwidget.h" +#include "icompletioncollector.h" + +#include <coreplugin/icore.h> +#include <texteditor/itexteditable.h> + +#include <QString> +#include <QList> + +using namespace TextEditor; +using namespace TextEditor::Internal; + + +CompletionSupport *CompletionSupport::instance(Core::ICore *core) +{ + static CompletionSupport *m_instance = 0; + if (!m_instance) { + m_instance = new CompletionSupport(core); + } + return m_instance; +} + +CompletionSupport::CompletionSupport(Core::ICore *core) + : QObject(core), + m_completionList(0), + m_startPosition(0), + m_checkCompletionTrigger(false), + m_editor(0) +{ + m_completionCollector = core->pluginManager()->getObject<ICompletionCollector>(); +} + +void CompletionSupport::performCompletion(const CompletionItem &item) +{ + item.m_collector->complete(item); + m_checkCompletionTrigger = true; +} + +void CompletionSupport::cleanupCompletions() +{ + if (m_completionList) + disconnect(m_completionList, SIGNAL(destroyed(QObject*)), + this, SLOT(cleanupCompletions())); + + m_completionList = 0; + m_completionCollector->cleanup(); + + if (m_checkCompletionTrigger) { + m_checkCompletionTrigger = false; + + // Only check for completion trigger when some text was entered + if (m_editor->position() > m_startPosition) + autoComplete(m_editor, false); + } +} + +void CompletionSupport::autoComplete(ITextEditable *editor, bool forced) +{ + if (!m_completionCollector) + return; + + m_editor = editor; + QList<CompletionItem> completionItems; + + if (!m_completionList) { + if (!forced && !m_completionCollector->triggersCompletion(editor)) + return; + + m_startPosition = m_completionCollector->startCompletion(editor); + completionItems = getCompletions(); + + Q_ASSERT(m_startPosition != -1 || completionItems.size() == 0); + + if (completionItems.isEmpty()) { + cleanupCompletions(); + return; + } + + m_completionList = new CompletionWidget(this, editor); + + connect(m_completionList, SIGNAL(itemSelected(TextEditor::CompletionItem)), + this, SLOT(performCompletion(TextEditor::CompletionItem))); + connect(m_completionList, SIGNAL(completionListClosed()), + this, SLOT(cleanupCompletions())); + + // Make sure to clean up the completions if the list is destroyed without + // emitting completionListClosed (can happen when no focus out event is received, + // for example when switching applications on the Mac) + connect(m_completionList, SIGNAL(destroyed(QObject*)), + this, SLOT(cleanupCompletions())); + } else { + completionItems = getCompletions(); + + if (completionItems.isEmpty()) { + m_completionList->closeList(); + return; + } + } + + m_completionList->setCompletionItems(completionItems); + + // Partially complete when completion was forced + if (forced && m_completionCollector->partiallyComplete(completionItems)) { + m_checkCompletionTrigger = true; + m_completionList->closeList(); + } else { + m_completionList->showCompletions(m_startPosition); + } +} + +static bool completionItemLessThan(const CompletionItem &i1, const CompletionItem &i2) +{ + // The order is case-insensitive in principle, but case-sensitive when this would otherwise mean equality + const int c = i1.m_text.compare(i2.m_text, Qt::CaseInsensitive); + return c ? c < 0 : i1.m_text < i2.m_text; +} + +QList<CompletionItem> CompletionSupport::getCompletions() const +{ + QList<CompletionItem> completionItems; + + m_completionCollector->completions(&completionItems); + + qStableSort(completionItems.begin(), completionItems.end(), completionItemLessThan); + + // Remove duplicates + QString lastKey; + QList<CompletionItem> uniquelist; + + foreach (const CompletionItem item, completionItems) { + if (item.m_text != lastKey) { + uniquelist.append(item); + lastKey = item.m_text; + } + } + + return uniquelist; +} diff --git a/src/plugins/texteditor/completionsupport.h b/src/plugins/texteditor/completionsupport.h new file mode 100644 index 0000000000..d3083630b6 --- /dev/null +++ b/src/plugins/texteditor/completionsupport.h @@ -0,0 +1,83 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef COMPLETIONSUPPORT_H +#define COMPLETIONSUPPORT_H + +#include <texteditor/texteditor_global.h> +#include <QtCore/QObject> + +namespace Core { class ICore; } + +namespace TextEditor { + +struct CompletionItem; +class ICompletionCollector; +class ITextEditable; + +namespace Internal { + +class CompletionWidget; + +/* Completion support is responsible for querying the list of completion collectors + and popping up the CompletionWidget with the available completions. + */ +class TEXTEDITOR_EXPORT CompletionSupport : public QObject +{ + Q_OBJECT + +public: + CompletionSupport(Core::ICore *core); + + static CompletionSupport *instance(Core::ICore *core); +public slots: + void autoComplete(ITextEditable *editor, bool forced); + +private slots: + void performCompletion(const TextEditor::CompletionItem &item); + void cleanupCompletions(); + +private: + QList<CompletionItem> getCompletions() const; + + CompletionWidget *m_completionList; + int m_startPosition; + bool m_checkCompletionTrigger; // Whether to check for completion trigger after cleanup + ITextEditable *m_editor; + ICompletionCollector *m_completionCollector; +}; + +} // namespace Internal +} // namespace TextEditor + +#endif // COMPLETIONSUPPORT_H + diff --git a/src/plugins/texteditor/completionwidget.cpp b/src/plugins/texteditor/completionwidget.cpp new file mode 100644 index 0000000000..939123a5cf --- /dev/null +++ b/src/plugins/texteditor/completionwidget.cpp @@ -0,0 +1,264 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "completionwidget.h" +#include "completionsupport.h" +#include "icompletioncollector.h" + +#include <texteditor/itexteditable.h> + +#include <QtCore/QEvent> +#include <QtGui/QKeyEvent> +#include <QtGui/QApplication> +#include <QtGui/QVBoxLayout> + +#include <limits.h> + +using namespace TextEditor; +using namespace TextEditor::Internal; + +#define NUMBER_OF_VISIBLE_ITEMS 10 + +class AutoCompletionModel : public QAbstractListModel +{ +public: + AutoCompletionModel(QObject *parent, const QList<CompletionItem> &items); + + inline const CompletionItem &itemAt(const QModelIndex &index) const + { return m_items.at(index.row()); } + + void setItems(const QList<CompletionItem> &items); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + +private: + QList<CompletionItem> m_items; +}; + +AutoCompletionModel::AutoCompletionModel(QObject *parent, const QList<CompletionItem> &items) + : QAbstractListModel(parent) +{ + m_items = items; +} + +void AutoCompletionModel::setItems(const QList<CompletionItem> &items) +{ + m_items = items; + reset(); +} + +int AutoCompletionModel::rowCount(const QModelIndex &) const +{ + return m_items.count(); +} + +QVariant AutoCompletionModel::data(const QModelIndex &index, int role) const +{ + if (index.row() >= m_items.count()) + return QVariant(); + + if (role == Qt::DisplayRole) { + return itemAt(index).m_text; + } else if (role == Qt::DecorationRole) { + return itemAt(index).m_icon; + } else if (role == Qt::ToolTipRole) { + return itemAt(index).m_details; + } + + return QVariant(); +} + +CompletionWidget::CompletionWidget(CompletionSupport *support, ITextEditable *editor) + : QListView(), + m_blockFocusOut(false), + m_editor(editor), + m_editorWidget(editor->widget()), + m_model(0), + m_support(support) +{ + Q_ASSERT(m_editorWidget); + + setUniformItemSizes(true); + setSelectionBehavior(QAbstractItemView::SelectItems); + setSelectionMode(QAbstractItemView::SingleSelection); + + connect(this, SIGNAL(activated(const QModelIndex &)), + this, SLOT(completionActivated(const QModelIndex &))); + + // We disable the frame on this list view and use a QFrame around it instead. + // This fixes the missing frame on Mac and improves the look with QGTKStyle. + m_popupFrame = new QFrame(0, Qt::Popup); + m_popupFrame->setFrameStyle(frameStyle()); + setFrameStyle(QFrame::NoFrame); + setParent(m_popupFrame); + m_popupFrame->setObjectName("m_popupFrame"); + m_popupFrame->setAttribute(Qt::WA_DeleteOnClose); + QVBoxLayout *layout = new QVBoxLayout(m_popupFrame); + layout->setMargin(0); + layout->addWidget(this); + + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); +} + +bool CompletionWidget::event(QEvent *e) +{ + if (m_blockFocusOut) + return QListView::event(e); + + bool forwardKeys = true; + if (e->type() == QEvent::FocusOut) { + closeList(); + return true; + } else if (e->type() == QEvent::KeyPress) { + QKeyEvent *ke = static_cast<QKeyEvent *>(e); + switch (ke->key()) { + case Qt::Key_Escape: + closeList(); + return true; + case Qt::Key_Right: + case Qt::Key_Left: + break; + case Qt::Key_Tab: + case Qt::Key_Return: + //independently from style, accept current entry if return is pressed + closeList(currentIndex()); + return true; + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_Enter: + case Qt::Key_PageDown: + case Qt::Key_PageUp: + forwardKeys = false; + break; + default: + break; + } + + if (forwardKeys) { + m_blockFocusOut = true; + QApplication::sendEvent(m_editorWidget, e); + m_blockFocusOut = false; + + // Have the completion support update the list of items + m_support->autoComplete(m_editor, false); + + return true; + } + } + return QListView::event(e); +} + +void CompletionWidget::keyboardSearch(const QString &search) +{ + Q_UNUSED(search); +} + +void CompletionWidget::closeList(const QModelIndex &index) +{ + m_blockFocusOut = true; + if (index.isValid()) + emit itemSelected(m_model->itemAt(index)); + + close(); + if (m_popupFrame) { + m_popupFrame->close(); + m_popupFrame = 0; + } + + emit completionListClosed(); + + m_blockFocusOut = false; +} + +void CompletionWidget::setCompletionItems(const QList<TextEditor::CompletionItem> &completionItems) +{ + if (!m_model) { + m_model = new AutoCompletionModel(this, completionItems); + setModel(m_model); + } else { + m_model->setItems(completionItems); + } + + // Select the first of the most relevant completion items + int relevance = INT_MIN; + int mostRelevantIndex = 0; + for (int i = 0; i < completionItems.size(); ++i) { + const CompletionItem &item = completionItems.at(i); + if (item.m_relevance > relevance) { + relevance = item.m_relevance; + mostRelevantIndex = i; + } + } + + setCurrentIndex(m_model->index(mostRelevantIndex)); +} + +void CompletionWidget::showCompletions(int startPos) +{ + const QPoint &pos = m_editor->cursorRect(startPos).bottomLeft(); + m_popupFrame->move(pos.x() - 16, pos.y()); + m_popupFrame->setMinimumSize(1, 1); + setMinimumSize(1, 1); + + updateSize(); + + m_popupFrame->show(); + show(); + setFocus(); +} + +void CompletionWidget::updateSize() +{ + int visibleItems = m_model->rowCount(); + if (visibleItems > NUMBER_OF_VISIBLE_ITEMS) + visibleItems = NUMBER_OF_VISIBLE_ITEMS; + + const QStyleOptionViewItem &option = viewOptions(); + + QSize shint; + for (int i = 0; i < visibleItems; ++i) { + QSize tmp = itemDelegate()->sizeHint(option, m_model->index(i)); + if (shint.width() < tmp.width()) + shint = tmp; + } + + const int width = (shint.width() + (m_popupFrame->frameWidth() * 2) + 30); + const int height = (shint.height() * visibleItems) + m_popupFrame->frameWidth() * 2; + + m_popupFrame->resize(width, height); +} + +void CompletionWidget::completionActivated(const QModelIndex &index) +{ + closeList(index); +} diff --git a/src/plugins/texteditor/completionwidget.h b/src/plugins/texteditor/completionwidget.h new file mode 100644 index 0000000000..4a41ffad34 --- /dev/null +++ b/src/plugins/texteditor/completionwidget.h @@ -0,0 +1,90 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef COMPLETIONWIDGET_H +#define COMPLETIONWIDGET_H + +#include <QtGui/QListView> +#include <QPointer> + +class AutoCompletionModel; + +namespace TextEditor { + +struct CompletionItem; +class ITextEditable; + +namespace Internal { + +class CompletionSupport; + +/* The completion widget is responsible for showing a list of possible completions. + It is only used by the CompletionSupport. + */ +class CompletionWidget : public QListView +{ + Q_OBJECT + +public: + CompletionWidget(CompletionSupport *support, ITextEditable *editor); + + void setCompletionItems(const QList<TextEditor::CompletionItem> &completionitems); + void showCompletions(int startPos); + void keyboardSearch(const QString &search); + void closeList(const QModelIndex &index = QModelIndex()); + +protected: + bool event(QEvent *e); + +signals: + void itemSelected(const TextEditor::CompletionItem &item); + void completionListClosed(); + +private slots: + void completionActivated(const QModelIndex &index); + +private: + void updateSize(); + + QPointer<QFrame> m_popupFrame; + bool m_blockFocusOut; + ITextEditable *m_editor; + QWidget *m_editorWidget; + AutoCompletionModel *m_model; + CompletionSupport *m_support; +}; + +} // namespace Internal +} // namespace TextEditor + +#endif // COMPLETIONWIDGET_H + diff --git a/src/plugins/texteditor/displaysettings.cpp b/src/plugins/texteditor/displaysettings.cpp new file mode 100644 index 0000000000..af74e4b2ab --- /dev/null +++ b/src/plugins/texteditor/displaysettings.cpp @@ -0,0 +1,108 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "displaysettings.h" + +#include <QtCore/QSettings> +#include <QtCore/QString> +#include <QtGui/QTextCursor> +#include <QtGui/QTextDocument> + +static const char * const displayLineNumbersKey = "DisplayLineNumbers"; +static const char * const textWrappingKey = "TextWrapping"; +static const char * const showWrapColumnKey = "ShowWrapColumn"; +static const char * const wrapColumnKey = "WrapColumn"; +static const char * const visualizeWhitespaceKey = "VisualizeWhitespace"; +static const char * const displayFoldingMarkersKey = "DisplayFoldingMarkers"; +static const char * const highlightCurrentLineKey = "HighlightCurrentLineKey"; +static const char * const groupPostfix = "DisplaySettings"; + +namespace TextEditor { + +DisplaySettings::DisplaySettings() : + m_displayLineNumbers(true), + m_textWrapping(false), + m_showWrapColumn(false), + m_wrapColumn(80), + m_visualizeWhitespace(false), + m_displayFoldingMarkers(true), + m_highlightCurrentLine(true) +{ +} + +void DisplaySettings::toSettings(const QString &category, QSettings *s) const +{ + QString group = QLatin1String(groupPostfix); + if (!category.isEmpty()) + group.insert(0, category); + s->beginGroup(group); + s->setValue(QLatin1String(displayLineNumbersKey), m_displayLineNumbers); + s->setValue(QLatin1String(textWrappingKey), m_textWrapping); + s->setValue(QLatin1String(showWrapColumnKey), m_showWrapColumn); + s->setValue(QLatin1String(wrapColumnKey), m_wrapColumn); + s->setValue(QLatin1String(visualizeWhitespaceKey), m_visualizeWhitespace); + s->setValue(QLatin1String(displayFoldingMarkersKey), m_displayFoldingMarkers); + s->setValue(QLatin1String(highlightCurrentLineKey), m_highlightCurrentLine); + s->endGroup(); +} + +void DisplaySettings::fromSettings(const QString &category, const QSettings *s) +{ + QString group = QLatin1String(groupPostfix); + if (!category.isEmpty()) + group.insert(0, category); + group += QLatin1Char('/'); + + *this = DisplaySettings(); // Assign defaults + + m_displayLineNumbers = s->value(group + QLatin1String(displayLineNumbersKey), m_displayLineNumbers).toBool(); + m_textWrapping = s->value(group + QLatin1String(textWrappingKey), m_textWrapping).toBool(); + m_showWrapColumn = s->value(group + QLatin1String(showWrapColumnKey), m_showWrapColumn).toBool(); + m_wrapColumn = s->value(group + QLatin1String(wrapColumnKey), m_wrapColumn).toInt(); + m_visualizeWhitespace = s->value(group + QLatin1String(visualizeWhitespaceKey), m_visualizeWhitespace).toBool(); + m_displayFoldingMarkers = s->value(group + QLatin1String(displayFoldingMarkersKey), m_displayFoldingMarkers).toBool(); + m_highlightCurrentLine = s->value(group + QLatin1String(highlightCurrentLineKey), m_highlightCurrentLine).toBool(); +} + +bool DisplaySettings::equals(const DisplaySettings &ds) const +{ + return m_displayLineNumbers == ds.m_displayLineNumbers + && m_textWrapping == ds.m_textWrapping + && m_showWrapColumn == ds.m_showWrapColumn + && m_wrapColumn == ds.m_wrapColumn + && m_visualizeWhitespace == ds.m_visualizeWhitespace + && m_displayFoldingMarkers == ds.m_displayFoldingMarkers + && m_highlightCurrentLine == ds.m_highlightCurrentLine + ; +} + +} // namespace TextEditor diff --git a/src/plugins/texteditor/displaysettings.h b/src/plugins/texteditor/displaysettings.h new file mode 100644 index 0000000000..7c70126e5d --- /dev/null +++ b/src/plugins/texteditor/displaysettings.h @@ -0,0 +1,67 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef DISPLAYSETTINGS_H +#define DISPLAYSETTINGS_H + +#include "texteditor_global.h" + +QT_BEGIN_NAMESPACE +class QSettings; +QT_END_NAMESPACE + +namespace TextEditor { + +struct TEXTEDITOR_EXPORT DisplaySettings +{ + DisplaySettings(); + + void toSettings(const QString &category, QSettings *s) const; + void fromSettings(const QString &category, const QSettings *s); + + bool m_displayLineNumbers; + bool m_textWrapping; + bool m_showWrapColumn; + int m_wrapColumn; + bool m_visualizeWhitespace; + bool m_displayFoldingMarkers; + bool m_highlightCurrentLine; + + bool equals(const DisplaySettings &ds) const; +}; + +inline bool operator==(const DisplaySettings &t1, const DisplaySettings &t2) { return t1.equals(t2); } +inline bool operator!=(const DisplaySettings &t1, const DisplaySettings &t2) { return !t1.equals(t2); } + +} // namespace TextEditor + +#endif // DISPLAYSETTINGS_H diff --git a/src/plugins/texteditor/findinfiles.cpp b/src/plugins/texteditor/findinfiles.cpp new file mode 100644 index 0000000000..6e11e40017 --- /dev/null +++ b/src/plugins/texteditor/findinfiles.cpp @@ -0,0 +1,144 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "findinfiles.h" + +#include <QtDebug> +#include <QtCore/QDirIterator> +#include <QtGui/QPushButton> +#include <QtGui/QFileDialog> +#include <QtGui/QVBoxLayout> + +using namespace Find; +using namespace TextEditor::Internal; + +FindInFiles::FindInFiles(Core::ICore *core, SearchResultWindow *resultWindow) + : BaseFileFind(core, resultWindow), + m_configWidget(0), + m_directory(0) +{ +} + +QString FindInFiles::name() const +{ + return tr("Files on Disk"); +} + +QKeySequence FindInFiles::defaultShortcut() const +{ + return QKeySequence(); +} + +void FindInFiles::findAll(const QString &txt, QTextDocument::FindFlags findFlags) +{ + updateComboEntries(m_directory, true); + BaseFileFind::findAll(txt, findFlags); +} + +QStringList FindInFiles::files() +{ + QStringList fileList; + QDirIterator it(m_directory->currentText(), + fileNameFilters(), + QDir::Files|QDir::Readable, + QDirIterator::Subdirectories); + while (it.hasNext()) { + it.next(); + fileList << it.filePath(); + } + return fileList; +} + +QWidget *FindInFiles::createConfigWidget() +{ + if (!m_configWidget) { + m_configWidget = new QWidget; + QGridLayout * const gridLayout = new QGridLayout(m_configWidget); + gridLayout->setMargin(0); + m_configWidget->setLayout(gridLayout); + gridLayout->addWidget(createRegExpWidget(), 0, 1, 1, 2); + + gridLayout->addWidget(new QLabel(tr("Directory:")), 1, 0, Qt::AlignRight); + m_directory = new QComboBox; + m_directory->setEditable(true); + m_directory->setMaxCount(30); + m_directory->setMinimumContentsLength(10); + m_directory->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); + m_directory->setInsertPolicy(QComboBox::InsertAtTop); + m_directory->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + m_directory->setModel(&m_directoryStrings); + syncComboWithSettings(m_directory, m_directorySetting); + gridLayout->addWidget(m_directory, 1, 1); + QPushButton *browseButton = new QPushButton(tr("Browse")); + gridLayout->addWidget(browseButton, 1, 2); + connect(browseButton, SIGNAL(clicked()), this, SLOT(openFileBrowser())); + + QLabel * const filePatternLabel = new QLabel(tr("File pattern:")); + filePatternLabel->setMinimumWidth(80); + filePatternLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); + filePatternLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); + gridLayout->addWidget(filePatternLabel, 2, 0); + gridLayout->addWidget(createPatternWidget(), 2, 1, 1, 2); + m_configWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + } + return m_configWidget; +} + +void FindInFiles::openFileBrowser() +{ + if (!m_directory) + return; + QString dir = QFileDialog::getExistingDirectory(m_configWidget, + tr("Directory to search")); + if (!dir.isEmpty()) + m_directory->setEditText(dir); +} + +void FindInFiles::writeSettings(QSettings *settings) +{ + settings->beginGroup("FindInFiles"); + writeCommonSettings(settings); + settings->setValue("directories", m_directoryStrings.stringList()); + if (m_directory) + settings->setValue("currentDirectory", m_directory->currentText()); + settings->endGroup(); +} + +void FindInFiles::readSettings(QSettings *settings) +{ + settings->beginGroup("FindInFiles"); + readCommonSettings(settings, "*.cpp,*.h"); + m_directoryStrings.setStringList(settings->value("directories").toStringList()); + m_directorySetting = settings->value("currentDirectory").toString(); + settings->endGroup(); + syncComboWithSettings(m_directory, m_directorySetting); +} diff --git a/src/plugins/texteditor/findinfiles.h b/src/plugins/texteditor/findinfiles.h new file mode 100644 index 0000000000..3d5bf0c940 --- /dev/null +++ b/src/plugins/texteditor/findinfiles.h @@ -0,0 +1,83 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef FINDINFILES_H +#define FINDINFILES_H + +#include "basefilefind.h" + +#include <coreplugin/icore.h> +#include <find/ifindfilter.h> +#include <find/searchresultwindow.h> + +#include <QtCore/QPointer> +#include <QtGui/QLabel> +#include <QtGui/QComboBox> +#include <QtGui/QStringListModel> + + +namespace TextEditor { +namespace Internal { + +class FindInFiles : public BaseFileFind +{ + Q_OBJECT + +public: + FindInFiles(Core::ICore *core, Find::SearchResultWindow *resultWindow); + + QString name() const; + + QKeySequence defaultShortcut() const; + + void findAll(const QString &txt, QTextDocument::FindFlags findFlags); + QWidget *createConfigWidget(); + void writeSettings(QSettings *settings); + void readSettings(QSettings *settings); + +protected: + QStringList files(); + +private slots: + void openFileBrowser(); + +private: + QStringListModel m_directoryStrings; + QString m_directorySetting; + QPointer<QWidget> m_configWidget; + QPointer<QComboBox> m_directory; +}; + +} // namespace Internal +} // namespace TextEditor + +#endif // FINDINFILES_H diff --git a/src/plugins/texteditor/fontsettings.cpp b/src/plugins/texteditor/fontsettings.cpp new file mode 100644 index 0000000000..1ced2cafd6 --- /dev/null +++ b/src/plugins/texteditor/fontsettings.cpp @@ -0,0 +1,277 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "fontsettings.h" +#include "fontsettingspage.h" + +#include <QtCore/QSettings> +#include <QtGui/QTextCharFormat> + +static const char *fontFamilyKey = "FontFamily"; +static const char *fontSizeKey = "FontSize"; +static const char *trueString = "true"; +static const char *falseString = "false"; + +namespace { +#ifdef Q_WS_MAC + enum { DEFAULT_FONT_SIZE = 12 }; + static const char *DEFAULT_FONT_FAMILY = "Monaco"; +#else +#ifdef Q_OS_UNIX + enum { DEFAULT_FONT_SIZE = 9 }; + static const char *DEFAULT_FONT_FAMILY = "Monospace"; +#else + enum { DEFAULT_FONT_SIZE = 10 }; + static const char *DEFAULT_FONT_FAMILY = "Courier"; +#endif +#endif +} // anonymous namespace + +namespace TextEditor { + +// Format -- +Format::Format() : + m_foreground(Qt::black), + m_background(Qt::white), + m_bold(false), + m_italic(false) +{ +} + +void Format::setForeground(const QColor &foreground) +{ + m_foreground = foreground; +} + +void Format::setBackground(const QColor &background) +{ + m_background = background; +} + +void Format::setBold(bool bold) +{ + m_bold = bold; +} + +void Format::setItalic(bool italic) +{ + m_italic = italic; +} + +static QString colorToString(const QColor &color) { + if (color.isValid()) + return color.name(); + return QLatin1String("invalid"); +} + +static QColor stringToColor(const QString &string) { + if (string == QLatin1String("invalid")) + return QColor(); + return QColor(string); +} + +QString Format::toString() const +{ + const QChar delimiter = QLatin1Char(';'); + QString s = colorToString(m_foreground); + s += delimiter; + s += colorToString(m_background); + s += delimiter; + s += m_bold ? QLatin1String(trueString) : QLatin1String(falseString); + s += delimiter; + s += m_italic ? QLatin1String(trueString) : QLatin1String(falseString); + return s; +} + +bool Format::fromString(const QString &str) +{ + *this = Format(); + + const QStringList lst = str.split(QLatin1Char(';')); + if (lst.count() != 4) + return false; + + m_foreground = stringToColor(lst.at(0)); + m_background = stringToColor(lst.at(1)); + m_bold = lst.at(2) == QLatin1String(trueString); + m_italic = lst.at(3) == QLatin1String(trueString); + return true; +} + +bool Format::equals(const Format &f) const +{ + return m_foreground == f.m_foreground && m_background == f.m_background && + m_bold == f.m_bold && m_italic == f.m_italic; +} +// -- FontSettings +FontSettings::FontSettings(const FormatDescriptions &fd) : + m_family(defaultFixedFontFamily()), + m_fontSize(DEFAULT_FONT_SIZE) +{ +} + +void FontSettings::clear() +{ + m_family = defaultFixedFontFamily(); + m_fontSize = DEFAULT_FONT_SIZE; + qFill(m_formats.begin(), m_formats.end(), Format()); +} + +void FontSettings::toSettings(const QString &category, + const FormatDescriptions &descriptions, + QSettings *s) const +{ + const int numFormats = m_formats.size(); + Q_ASSERT(descriptions.size() == numFormats); + s->beginGroup(category); + if (m_family != defaultFixedFontFamily() || s->contains(QLatin1String(fontFamilyKey))) + s->setValue(QLatin1String(fontFamilyKey), m_family); + + if (m_fontSize != DEFAULT_FONT_SIZE || s->contains(QLatin1String(fontSizeKey))) + s->setValue(QLatin1String(fontSizeKey), m_fontSize); + + const Format defaultFormat; + + foreach (const FormatDescription &desc, descriptions) { + QMap<QString, Format>::const_iterator i = m_formats.find(desc.name()); + if (i != m_formats.end() && ((*i) != defaultFormat || s->contains(desc.name()))) { + s->setValue(desc.name(), (*i).toString()); + } + } + s->endGroup(); +} + +bool FontSettings::fromSettings(const QString &category, + const FormatDescriptions &descriptions, + const QSettings *s) +{ + clear(); + + if (!s->childGroups().contains(category)) + return false; + + QString group = category; + group += QLatin1Char('/'); + + m_family = s->value(group + QLatin1String(fontFamilyKey), defaultFixedFontFamily()).toString(); + m_fontSize = s->value(group + QLatin1String(QLatin1String(fontSizeKey)), m_fontSize).toInt(); + + foreach (const FormatDescription &desc, descriptions) { + const QString name = desc.name(); + const QString fmt = s->value(group + name, QString()).toString(); + if (fmt.isEmpty()) { + m_formats[name].setForeground(desc.foreground()); + m_formats[name].setBackground(desc.background()); + } else { + m_formats[name].fromString(fmt); + } + } + return true; +} + +bool FontSettings::equals(const FontSettings &f) const +{ + return m_family == f.m_family + && m_fontSize == f.m_fontSize + && m_formats == f.m_formats; +} + +QTextCharFormat FontSettings::toTextCharFormat(const QString &category) const +{ + const Format f = m_formats.value(category); + QTextCharFormat tf; + if (category == QLatin1String("Text")) { + tf.setFontFamily(m_family); + tf.setFontPointSize(m_fontSize); + } + + if (f.foreground().isValid()) + tf.setForeground(f.foreground()); + if (f.background().isValid() && (category == QLatin1String("Text") || f.background() != m_formats.value(QLatin1String("Text")).background())) + tf.setBackground(f.background()); + tf.setFontWeight(f.bold() ? QFont::Bold : QFont::Normal); + tf.setFontItalic(f.italic()); + return tf; +} + +QVector<QTextCharFormat> FontSettings::toTextCharFormats(const QVector<QString> &categories) const +{ + QVector<QTextCharFormat> rc; + const int size = categories.size(); + rc.reserve(size); + for (int i = 0; i < size; i++) + rc.push_back(toTextCharFormat(categories.at(i))); + return rc; +} + +QString FontSettings::family() const +{ + return m_family; +} + +void FontSettings::setFamily(const QString &family) +{ + m_family = family; +} + +int FontSettings::fontSize() const +{ + return m_fontSize; +} + +void FontSettings::setFontSize(int size) +{ + m_fontSize = size; +} + +Format &FontSettings::formatFor(const QString &category) +{ + return m_formats[category]; +} + +QString FontSettings::defaultFixedFontFamily() +{ + static QString rc; + if (rc.isEmpty()) { + QFont f(DEFAULT_FONT_FAMILY); + f.setStyleHint(QFont::TypeWriter); + rc = f.family(); + } + return rc; +} + +int FontSettings::defaultFontSize() +{ + return DEFAULT_FONT_SIZE; +} + +} // namespace TextEditor diff --git a/src/plugins/texteditor/fontsettings.h b/src/plugins/texteditor/fontsettings.h new file mode 100644 index 0000000000..1b9088b621 --- /dev/null +++ b/src/plugins/texteditor/fontsettings.h @@ -0,0 +1,149 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef FONTSETTINGS_H +#define FONTSETTINGS_H + +#include "texteditor_global.h" + +#include <QtCore/QString> +#include <QtCore/QList> +#include <QtCore/QMap> +#include <QtCore/QVector> +#include <QtGui/QColor> + +QT_BEGIN_NAMESPACE +class QTextCharFormat; +class QSettings; +QT_END_NAMESPACE + +namespace TextEditor { + +class FormatDescription; + +// Format for a particular piece of text (text/comment, etc). +class TEXTEDITOR_EXPORT Format +{ +public: + Format(); + + QColor foreground() const { return m_foreground; } + void setForeground(const QColor &foreground); + + QColor background() const { return m_background; } + void setBackground(const QColor &background); + + bool bold() const { return m_bold; } + void setBold(bool bold); + + bool italic() const { return m_italic; } + void setItalic(bool italic); + + bool equals(const Format &f) const; + + QString toString() const; + bool fromString(const QString &str); + +private: + QColor m_foreground; + QColor m_background; + bool m_bold; + bool m_italic; +}; + +inline bool operator==(const Format &f1, const Format &f2) { return f1.equals(f2); } +inline bool operator!=(const Format &f1, const Format &f2) { return !f1.equals(f2); } + +/** + * Font settings (default font and enumerated list of formats). + */ +class TEXTEDITOR_EXPORT FontSettings +{ +public: + typedef QList<FormatDescription> FormatDescriptions; + + FontSettings(const FormatDescriptions &fd); + void clear(); + + void toSettings(const QString &category, + const FormatDescriptions &descriptions, + QSettings *s) const; + + bool fromSettings(const QString &category, + const FormatDescriptions &descriptions, + const QSettings *s); + + /** + * Returns the list of QTextCharFormats that corresponds to the list of + * requested format categories. + */ + QVector<QTextCharFormat> toTextCharFormats(const QVector<QString> &categories) const; + + /** + * Returns the QTextCharFormat of the given format category. + */ + QTextCharFormat toTextCharFormat(const QString &category) const; + + /** + * Returns the configured font family. + */ + QString family() const; + void setFamily(const QString &family); + + /** + * Returns the configured font size. + */ + int fontSize() const; + void setFontSize(int size); + + /** + * Returns the format for the given font category. + */ + Format &formatFor(const QString &category); + + bool equals(const FontSettings &f) const; + + static QString defaultFixedFontFamily(); + static int defaultFontSize(); + +private: + QString m_family; + int m_fontSize; + QMap<QString, Format> m_formats; +}; + +inline bool operator==(const FontSettings &f1, const FontSettings &f2) { return f1.equals(f2); } +inline bool operator!=(const FontSettings &f1, const FontSettings &f2) { return !f1.equals(f2); } + +} // namespace TextEditor + +#endif // FONTSETTINGS_H diff --git a/src/plugins/texteditor/fontsettingspage.cpp b/src/plugins/texteditor/fontsettingspage.cpp new file mode 100644 index 0000000000..1583328fc2 --- /dev/null +++ b/src/plugins/texteditor/fontsettingspage.cpp @@ -0,0 +1,465 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "fontsettingspage.h" +#include "fontsettings.h" +#include "texteditorconstants.h" +#include "ui_fontsettingspage.h" + +#include <utils/settingsutils.h> + +#include <QtCore/QSettings> +#include <QtCore/QTimer> +#include <QtGui/QListWidget> +#include <QtGui/QToolButton> +#include <QtGui/QPalette> +#include <QtGui/QCheckBox> +#include <QtGui/QColorDialog> +#include <QtGui/QTextEdit> +#include <QtGui/QTextCharFormat> +#include <QtGui/QComboBox> +#include <QtGui/QFontDatabase> +#include <QtGui/QPalette> + +static inline QString colorButtonStyleSheet(const QColor &bgColor) +{ + if (bgColor.isValid()) { + QString rc = QLatin1String("border: 2px solid black; border-radius: 2px; background:"); + rc += bgColor.name(); + return rc; + } + return QLatin1String("border: 2px dotted black; border-radius: 2px;"); +} + +namespace TextEditor { +namespace Internal { + +class FontSettingsPagePrivate +{ +public: + FontSettingsPagePrivate(const TextEditor::FormatDescriptions &fd, + const QString &name, + const QString &category, + const QString &trCategory, + Core::ICore *core); + + Core::ICore *m_core; + const QString m_name; + const QString m_settingsGroup; + const QString m_category; + const QString m_trCategory; + + TextEditor::FormatDescriptions m_descriptions; + FontSettings m_value; + FontSettings m_lastValue; + int m_curItem; + Ui::FontSettingsPage ui; +}; + +FontSettingsPagePrivate::FontSettingsPagePrivate(const TextEditor::FormatDescriptions &fd, + const QString &name, + const QString &category, + const QString &trCategory, + Core::ICore *core) : + m_core(core), + m_name(name), + m_settingsGroup(Core::Utils::settingsKey(category)), + m_category(category), + m_trCategory(trCategory), + m_descriptions(fd), + m_value(fd), + m_lastValue(fd), + m_curItem(-1) +{ + bool settingsFound = false; + if (m_core) + if (const QSettings *settings = m_core->settings()) + settingsFound = m_value.fromSettings(m_settingsGroup, m_descriptions, settings); + if (!settingsFound) { // Apply defaults + foreach (const FormatDescription &f, m_descriptions) { + const QString name = f.name(); + m_lastValue.formatFor(name).setForeground(f.foreground()); + m_lastValue.formatFor(name).setBackground(f.background()); + m_value.formatFor(name).setForeground(f.foreground()); + m_value.formatFor(name).setBackground(f.background()); + } + } + + m_lastValue = m_value; +} + +} // namespace Internal +} // namespace TextEditor + +using namespace TextEditor; +using namespace TextEditor::Internal; + +// ------- FormatDescription +FormatDescription::FormatDescription(const QString &name, const QString &trName, const QColor &color) : + m_name(name), + m_trName(trName) +{ + m_format.setForeground(color); +} + +QString FormatDescription::name() const +{ + return m_name; +} + +QString FormatDescription::trName() const +{ + return m_trName; +} + +QColor FormatDescription::foreground() const +{ + if (m_name == QLatin1String("LineNumber")) + return QApplication::palette().dark().color(); + if (m_name == QLatin1String("Parentheses")) + return QColor(Qt::red); + return m_format.foreground(); +} + +void FormatDescription::setForeground(const QColor &foreground) +{ + m_format.setForeground(foreground); +} + +QColor FormatDescription::background() const +{ + if (m_name == QLatin1String(Constants::C_TEXT)) + return Qt::white; + else if (m_name == QLatin1String(Constants::C_LINE_NUMBER)) + return QApplication::palette().background().color(); + else if (m_name == QLatin1String(Constants::C_SEARCH_RESULT)) + return QColor(0xffef0b); + else if (m_name == QLatin1String(Constants::C_PARENTHESES)) + return QColor(0xb4, 0xee, 0xb4); + else if (m_name == QLatin1String(Constants::C_CURRENT_LINE) + || m_name == QLatin1String(Constants::C_SEARCH_SCOPE)) { + const QPalette palette = QApplication::palette(); + const QColor &fg = palette.color(QPalette::Highlight); + const QColor &bg = palette.color(QPalette::Base); + + qreal smallRatio; + qreal largeRatio; + if (m_name == QLatin1String(Constants::C_CURRENT_LINE)) { + smallRatio = .15; + largeRatio = .3; + } else { + smallRatio = .05; + largeRatio = .4; + } + const qreal ratio = ((palette.color(QPalette::Text).value() < 128) + ^ (palette.color(QPalette::HighlightedText).value() < 128)) ? smallRatio : largeRatio; + + const QColor &col = QColor::fromRgbF(fg.redF() * ratio + bg.redF() * (1 - ratio), + fg.greenF() * ratio + bg.greenF() * (1 - ratio), + fg.blueF() * ratio + bg.blueF() * (1 - ratio)); + return col; + } else if (m_name == QLatin1String(Constants::C_SELECTION)) { + const QPalette palette = QApplication::palette(); + return palette.color(QPalette::Highlight); + } + return QColor(); // invalid color +} + + +// ------------ FontSettingsPage +FontSettingsPage::FontSettingsPage(const FormatDescriptions &fd, + const QString &category, + const QString &trCategory, + Core::ICore *core, + QObject *parent) : + Core::IOptionsPage(parent), + d_ptr(new FontSettingsPagePrivate(fd, tr("Font & Colors"), category, trCategory, core)) +{ +} + +FontSettingsPage::~FontSettingsPage() +{ + delete d_ptr; +} + +QString FontSettingsPage::name() const +{ + return d_ptr->m_name; +} + +QString FontSettingsPage::category() const +{ + return d_ptr->m_category; +} + +QString FontSettingsPage::trCategory() const +{ + return d_ptr->m_trCategory; +} + +QWidget *FontSettingsPage::createPage(QWidget *parent) +{ + QWidget *w = new QWidget(parent); + d_ptr->ui.setupUi(w); + + + d_ptr->ui.itemListWidget->setSelectionMode(QAbstractItemView::ExtendedSelection); + + foreach (const FormatDescription &d, d_ptr->m_descriptions) + d_ptr->ui.itemListWidget->addItem(d.trName()); + + QFontDatabase db; + const QStringList families = db.families(); + d_ptr->ui.familyComboBox->addItems(families); + const int idx = families.indexOf(d_ptr->m_value.family()); + d_ptr->ui.familyComboBox->setCurrentIndex(idx); + + connect(d_ptr->ui.familyComboBox, SIGNAL(activated(int)), this, SLOT(updatePointSizes())); + connect(d_ptr->ui.sizeComboBox, SIGNAL(activated(int)), this, SLOT(updatePreview())); + connect(d_ptr->ui.itemListWidget, SIGNAL(itemSelectionChanged()), + this, SLOT(itemChanged())); + connect(d_ptr->ui.foregroundToolButton, SIGNAL(clicked()), + this, SLOT(changeForeColor())); + connect(d_ptr->ui.backgroundToolButton, SIGNAL(clicked()), + this, SLOT(changeBackColor())); + connect(d_ptr->ui.eraseBackgroundToolButton, SIGNAL(clicked()), + this, SLOT(eraseBackColor())); + connect(d_ptr->ui.boldCheckBox, SIGNAL(toggled(bool)), this, SLOT(checkCheckBoxes())); + connect(d_ptr->ui.italicCheckBox, SIGNAL(toggled(bool)), this, SLOT(checkCheckBoxes())); + + if (!d_ptr->m_descriptions.empty()) + d_ptr->ui.itemListWidget->setCurrentRow(0); + + updatePointSizes(); + d_ptr->m_lastValue = d_ptr->m_value; + return w; +} + +void FontSettingsPage::itemChanged() +{ + QListWidgetItem *item = d_ptr->ui.itemListWidget->currentItem(); + if (!item) + return; + + const int numFormats = d_ptr->m_descriptions.size(); + for (int i = 0; i < numFormats; i++) { + if (d_ptr->m_descriptions[i].trName() == item->text()) { + d_ptr->m_curItem = i; + const Format &format = d_ptr->m_value.formatFor(d_ptr->m_descriptions[i].name()); + d_ptr->ui.foregroundToolButton->setStyleSheet(colorButtonStyleSheet(format.foreground())); + d_ptr->ui.backgroundToolButton->setStyleSheet(colorButtonStyleSheet(format.background())); + + d_ptr->ui.eraseBackgroundToolButton->setEnabled(i > 0 && format.background().isValid()); + + const bool boldBlocked = d_ptr->ui.boldCheckBox->blockSignals(true); + d_ptr->ui.boldCheckBox->setChecked(format.bold()); + d_ptr->ui.boldCheckBox->blockSignals(boldBlocked); + const bool italicBlocked = d_ptr->ui.italicCheckBox->blockSignals(true); + d_ptr->ui.italicCheckBox->setChecked(format.italic()); + d_ptr->ui.italicCheckBox->blockSignals(italicBlocked); + updatePreview(); + break; + } + } +} + +void FontSettingsPage::changeForeColor() +{ + if (d_ptr->m_curItem == -1) + return; + QColor color = d_ptr->m_value.formatFor(d_ptr->m_descriptions[d_ptr->m_curItem].name()).foreground(); + const QColor newColor = QColorDialog::getColor(color, d_ptr->ui.boldCheckBox->window()); + if (!newColor.isValid()) + return; + QPalette p = d_ptr->ui.foregroundToolButton->palette(); + p.setColor(QPalette::Active, QPalette::Button, newColor); + d_ptr->ui.foregroundToolButton->setStyleSheet(colorButtonStyleSheet(newColor)); + + const int numFormats = d_ptr->m_descriptions.size(); + for (int i = 0; i < numFormats; i++) { + QList<QListWidgetItem*> items = d_ptr->ui.itemListWidget->findItems(d_ptr->m_descriptions[i].trName(), Qt::MatchExactly); + if (!items.isEmpty() && items.first()->isSelected()) + d_ptr->m_value.formatFor(d_ptr->m_descriptions[i].name()).setForeground(newColor); + } + + updatePreview(); +} + +void FontSettingsPage::changeBackColor() +{ + if (d_ptr->m_curItem == -1) + return; + QColor color = d_ptr->m_value.formatFor(d_ptr->m_descriptions[d_ptr->m_curItem].name()).background(); + const QColor newColor = QColorDialog::getColor(color, d_ptr->ui.boldCheckBox->window()); + if (!newColor.isValid()) + return; + d_ptr->ui.backgroundToolButton->setStyleSheet(colorButtonStyleSheet(newColor)); + + const int numFormats = d_ptr->m_descriptions.size(); + for (int i = 0; i < numFormats; i++) { + QList<QListWidgetItem*> items = d_ptr->ui.itemListWidget->findItems(d_ptr->m_descriptions[i].trName(), Qt::MatchExactly); + if (!items.isEmpty() && items.first()->isSelected()) + d_ptr->m_value.formatFor(d_ptr->m_descriptions[i].name()).setBackground(newColor); + } + + updatePreview(); +} + +void FontSettingsPage::eraseBackColor() +{ + if (d_ptr->m_curItem == -1) + return; + QColor newColor; + d_ptr->ui.backgroundToolButton->setStyleSheet(colorButtonStyleSheet(newColor)); + + const int numFormats = d_ptr->m_descriptions.size(); + for (int i = 0; i < numFormats; i++) { + QList<QListWidgetItem*> items = d_ptr->ui.itemListWidget->findItems(d_ptr->m_descriptions[i].trName(), Qt::MatchExactly); + if (!items.isEmpty() && items.first()->isSelected()) + d_ptr->m_value.formatFor(d_ptr->m_descriptions[i].name()).setBackground(newColor); + } + + updatePreview(); +} + +void FontSettingsPage::checkCheckBoxes() +{ + if (d_ptr->m_curItem == -1) + return; + const int numFormats = d_ptr->m_descriptions.size(); + for (int i = 0; i < numFormats; i++) { + QList<QListWidgetItem*> items = d_ptr->ui.itemListWidget->findItems(d_ptr->m_descriptions[i].trName(), Qt::MatchExactly); + if (!items.isEmpty() && items.first()->isSelected()) { + d_ptr->m_value.formatFor(d_ptr->m_descriptions[i].name()).setBold(d_ptr->ui.boldCheckBox->isChecked()); + d_ptr->m_value.formatFor(d_ptr->m_descriptions[i].name()).setItalic(d_ptr->ui.italicCheckBox->isChecked()); + } + } + updatePreview(); +} + +void FontSettingsPage::updatePreview() +{ + if (d_ptr->m_curItem == -1) + return; + + const Format ¤tFormat = d_ptr->m_value.formatFor(d_ptr->m_descriptions[d_ptr->m_curItem].name()); + const Format &baseFormat = d_ptr->m_value.formatFor(QLatin1String("Text")); + + QPalette pal = QApplication::palette(); + if (baseFormat.foreground().isValid()) { + pal.setColor(QPalette::Text, baseFormat.foreground()); + pal.setColor(QPalette::Foreground, baseFormat.foreground()); + } + if (baseFormat.background().isValid()) + pal.setColor(QPalette::Base, baseFormat.background()); + + d_ptr->ui.previewTextEdit->setPalette(pal); + + QTextCharFormat format; + if (currentFormat.foreground().isValid()) + format.setForeground(QBrush(currentFormat.foreground())); + if (currentFormat.background().isValid()) + format.setBackground(QBrush(currentFormat.background())); + format.setFontFamily(d_ptr->ui.familyComboBox->currentText()); + bool ok; + int size = d_ptr->ui.sizeComboBox->currentText().toInt(&ok); + if (!ok) { + size = QFont().pointSize(); + } + format.setFontPointSize(size); + format.setFontItalic(currentFormat.italic()); + if (currentFormat.bold()) + format.setFontWeight(QFont::Bold); + d_ptr->ui.previewTextEdit->setCurrentCharFormat(format); + + d_ptr->ui.previewTextEdit->setPlainText(tr("\n\tThis is only an example.")); +} + +void FontSettingsPage::updatePointSizes() +{ + const int oldSize = d_ptr->m_value.fontSize(); + if (d_ptr->ui.sizeComboBox->count()) { + const QString curSize = d_ptr->ui.sizeComboBox->currentText(); + bool ok = true; + int oldSize = curSize.toInt(&ok); + if (!ok) + oldSize = d_ptr->m_value.fontSize(); + d_ptr->ui.sizeComboBox->clear(); + } + QFontDatabase db; + const QList<int> sizeLst = db.pointSizes(d_ptr->ui.familyComboBox->currentText()); + int idx = 0; + int i = 0; + for (; i<sizeLst.count(); ++i) { + if (idx == 0 && sizeLst.at(i) >= oldSize) + idx = i; + d_ptr->ui.sizeComboBox->addItem(QString::number(sizeLst.at(i))); + } + if (d_ptr->ui.sizeComboBox->count()) + d_ptr->ui.sizeComboBox->setCurrentIndex(idx); + updatePreview(); +} + +void FontSettingsPage::delayedChange() +{ + emit changed(d_ptr->m_value); +} + +void FontSettingsPage::finished(bool accepted) +{ + if (!accepted) { + d_ptr->m_value = d_ptr->m_lastValue; + return; + } + + d_ptr->m_value.setFamily(d_ptr->ui.familyComboBox->currentText()); + + bool ok = true; + const int size = d_ptr->ui.sizeComboBox->currentText().toInt(&ok); + if (ok) + d_ptr->m_value.setFontSize(size); + + + if (d_ptr->m_value != d_ptr->m_lastValue) { + d_ptr->m_lastValue = d_ptr->m_value; + if (d_ptr->m_core) + if (QSettings *settings = d_ptr->m_core->settings()) + d_ptr->m_value.toSettings(d_ptr->m_settingsGroup, d_ptr->m_descriptions, settings); + + QTimer::singleShot(0, this, SLOT(delayedChange())); + } +} + +const FontSettings &FontSettingsPage::fontSettings() const +{ + return d_ptr->m_value; +} diff --git a/src/plugins/texteditor/fontsettingspage.h b/src/plugins/texteditor/fontsettingspage.h new file mode 100644 index 0000000000..86599e70d4 --- /dev/null +++ b/src/plugins/texteditor/fontsettingspage.h @@ -0,0 +1,124 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef FONTSETTINGSPAGE_H +#define FONTSETTINGSPAGE_H + +#include "texteditor_global.h" + +#include "fontsettings.h" + +#include <coreplugin/icore.h> +#include <coreplugin/dialogs/ioptionspage.h> + +#include <QtGui/QColor> +#include <QtGui/QTextCharFormat> +#include <QtCore/QString> +#include <QtCore/QVector> + +QT_BEGIN_NAMESPACE +class QWidget; +QT_END_NAMESPACE + +namespace TextEditor { + +namespace Internal { +class FontSettingsPagePrivate; +} // namespace Internal + +// GUI description of a format consisting of name (settings key) +// and trName to be displayed +class TEXTEDITOR_EXPORT FormatDescription +{ +public: + FormatDescription(const QString &name, const QString &trName, + const QColor &foreground = Qt::black); + + QString name() const; + + QString trName() const; + + QColor foreground() const; + void setForeground(const QColor &foreground); + + QColor background() const; + +private: + QString m_name; // Name of the category + QString m_trName; // Displayed name of the category + Format m_format; // Default format +}; + +typedef QList<FormatDescription> FormatDescriptions; + +class TEXTEDITOR_EXPORT FontSettingsPage : public Core::IOptionsPage +{ + Q_OBJECT + +public: + FontSettingsPage(const FormatDescriptions &fd, + const QString &category, + const QString &trCategory, + Core::ICore *core, + QObject *parent = 0); + + ~FontSettingsPage(); + + QString name() const; + QString category() const; + QString trCategory() const; + + QWidget *createPage(QWidget *parent); + void finished(bool accepted); + + const FontSettings &fontSettings() const; + +signals: + void changed(const TextEditor::FontSettings&); + +private slots: + void delayedChange(); + void itemChanged(); + void changeForeColor(); + void changeBackColor(); + void eraseBackColor(); + void checkCheckBoxes(); + void updatePointSizes(); + void updatePreview(); + +private: + Internal::FontSettingsPagePrivate *d_ptr; +}; + +} // namespace TextEditor + +#endif // FONTSETTINGSPAGE_H diff --git a/src/plugins/texteditor/fontsettingspage.ui b/src/plugins/texteditor/fontsettingspage.ui new file mode 100644 index 0000000000..770d5e2935 --- /dev/null +++ b/src/plugins/texteditor/fontsettingspage.ui @@ -0,0 +1,268 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>TextEditor::Internal::FontSettingsPage</class> + <widget class="QWidget" name="TextEditor::Internal::FontSettingsPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>572</width> + <height>471</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="margin"> + <number>9</number> + </property> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>General Font Settings</string> + </property> + <layout class="QHBoxLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="margin"> + <number>9</number> + </property> + <item> + <widget class="QLabel" name="label_5"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Family:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="familyComboBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>2</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Preferred</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="label_6"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Size:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="sizeComboBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_2"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Item Specific Settings</string> + </property> + <layout class="QHBoxLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="margin"> + <number>9</number> + </property> + <item> + <widget class="QListWidget" name="itemListWidget"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <layout class="QGridLayout"> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <item row="2" column="0"> + <widget class="QCheckBox" name="boldCheckBox"> + <property name="text"> + <string>Bold</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QCheckBox" name="italicCheckBox"> + <property name="text"> + <string>Italic</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QToolButton" name="foregroundToolButton"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Background:</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Foreground:</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <spacer> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QToolButton" name="backgroundToolButton"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="eraseBackgroundToolButton"> + <property name="toolTip"> + <string>Erase background</string> + </property> + <property name="text"> + <string>x</string> + </property> + <property name="arrowType"> + <enum>Qt::LeftArrow</enum> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Preview:</string> + </property> + </widget> + </item> + <item> + <widget class="QTextEdit" name="previewTextEdit"> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/plugins/texteditor/generalsettingspage.cpp b/src/plugins/texteditor/generalsettingspage.cpp new file mode 100644 index 0000000000..7f056d8ca4 --- /dev/null +++ b/src/plugins/texteditor/generalsettingspage.cpp @@ -0,0 +1,217 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "displaysettings.h" +#include "generalsettingspage.h" +#include "storagesettings.h" +#include "tabsettings.h" +#include "ui_generalsettingspage.h" + +#include <QtCore/QSettings> +#include <QtCore/QDebug> + +using namespace TextEditor; + +struct GeneralSettingsPage::GeneralSettingsPagePrivate { + GeneralSettingsPagePrivate(Core::ICore *core, + const GeneralSettingsPageParameters &p); + + Core::ICore *m_core; + const GeneralSettingsPageParameters m_parameters; + Ui::generalSettingsPage m_page; + TabSettings m_tabSettings; + StorageSettings m_storageSettings; + DisplaySettings m_displaySettings; +}; + +GeneralSettingsPage::GeneralSettingsPagePrivate::GeneralSettingsPagePrivate(Core::ICore *core, + const GeneralSettingsPageParameters &p) : + m_core(core), + m_parameters(p) +{ + if (m_core) + if (const QSettings *s = m_core->settings()) { + m_tabSettings.fromSettings(m_parameters.settingsPrefix, s); + m_storageSettings.fromSettings(m_parameters.settingsPrefix, s); + m_displaySettings.fromSettings(m_parameters.settingsPrefix, s); + } +} + +GeneralSettingsPage::GeneralSettingsPage(Core::ICore *core, + const GeneralSettingsPageParameters &p, + QObject *parent) : + Core::IOptionsPage(parent), + m_d(new GeneralSettingsPagePrivate(core, p)) +{ +} + +GeneralSettingsPage::~GeneralSettingsPage() +{ + delete m_d; +} + +QString GeneralSettingsPage::name() const +{ + return m_d->m_parameters.name; +} + +QString GeneralSettingsPage::category() const +{ + return m_d->m_parameters.category; +} + +QString GeneralSettingsPage::trCategory() const +{ + return m_d->m_parameters.trCategory; +} + +QWidget *GeneralSettingsPage::createPage(QWidget *parent) +{ + QWidget *w = new QWidget(parent); + m_d->m_page.setupUi(w); + + settingsToUI(); + + return w; +} + +void GeneralSettingsPage::finished(bool accepted) +{ + if (!accepted) + return; + + TabSettings newTabSettings; + StorageSettings newStorageSettings; + DisplaySettings newDisplaySettings; + settingsFromUI(newTabSettings, newStorageSettings, newDisplaySettings); + + if (newTabSettings != m_d->m_tabSettings) { + m_d->m_tabSettings = newTabSettings; + if (m_d->m_core) + if (QSettings *s = m_d->m_core->settings()) + m_d->m_tabSettings.toSettings(m_d->m_parameters.settingsPrefix, s); + + emit tabSettingsChanged(newTabSettings); + } + + if (newStorageSettings != m_d->m_storageSettings) { + m_d->m_storageSettings = newStorageSettings; + if (m_d->m_core) + if (QSettings *s = m_d->m_core->settings()) + m_d->m_storageSettings.toSettings(m_d->m_parameters.settingsPrefix, s); + + emit storageSettingsChanged(newStorageSettings); + } + + if (newDisplaySettings != m_d->m_displaySettings) { + m_d->m_displaySettings = newDisplaySettings; + if (m_d->m_core) + if (QSettings *s = m_d->m_core->settings()) + m_d->m_displaySettings.toSettings(m_d->m_parameters.settingsPrefix, s); + + emit displaySettingsChanged(newDisplaySettings); + } +} + +void GeneralSettingsPage::settingsFromUI(TabSettings &rc, + StorageSettings &storageSettings, + DisplaySettings &displaySettings) const +{ + rc.m_spacesForTabs = m_d->m_page.insertSpaces->isChecked(); + rc.m_autoIndent = m_d->m_page.autoIndent->isChecked(); + rc.m_smartBackspace = m_d->m_page.smartBackspace->isChecked(); + rc.m_tabSize = m_d->m_page.tabSize->value(); + rc.m_indentSize = m_d->m_page.indentSize->value(); + + storageSettings.m_cleanWhitespace = m_d->m_page.cleanWhitespace->isChecked(); + storageSettings.m_inEntireDocument = m_d->m_page.inEntireDocument->isChecked(); + storageSettings.m_addFinalNewLine = m_d->m_page.addFinalNewLine->isChecked(); + + displaySettings.m_displayLineNumbers = m_d->m_page.displayLineNumbers->isChecked(); + displaySettings.m_textWrapping = m_d->m_page.enableTextWrapping->isChecked(); + displaySettings.m_showWrapColumn = m_d->m_page.showWrapColumn->isChecked(); + displaySettings.m_wrapColumn = m_d->m_page.wrapColumn->value(); + displaySettings.m_visualizeWhitespace = m_d->m_page.visualizeWhitespace->isChecked(); + displaySettings.m_displayFoldingMarkers = m_d->m_page.displayFoldingMarkers->isChecked(); + displaySettings.m_highlightCurrentLine = m_d->m_page.highlightCurrentLine->isChecked(); +} + +void GeneralSettingsPage::settingsToUI() +{ + TabSettings rc = m_d->m_tabSettings; + m_d->m_page.insertSpaces->setChecked(rc.m_spacesForTabs); + m_d->m_page.autoIndent->setChecked(rc.m_autoIndent); + m_d->m_page.smartBackspace->setChecked(rc.m_smartBackspace); + m_d->m_page.tabSize->setValue(rc.m_tabSize); + m_d->m_page.indentSize->setValue(rc.m_indentSize); + + StorageSettings storageSettings = m_d->m_storageSettings; + m_d->m_page.cleanWhitespace->setChecked(storageSettings.m_cleanWhitespace); + m_d->m_page.inEntireDocument->setChecked(storageSettings.m_inEntireDocument); + m_d->m_page.addFinalNewLine->setChecked(storageSettings.m_addFinalNewLine); + + DisplaySettings displaySettings = m_d->m_displaySettings; + m_d->m_page.displayLineNumbers->setChecked(displaySettings.m_displayLineNumbers); + m_d->m_page.enableTextWrapping->setChecked(displaySettings.m_textWrapping); + m_d->m_page.showWrapColumn->setChecked(displaySettings.m_showWrapColumn); + m_d->m_page.wrapColumn->setValue(displaySettings.m_wrapColumn); + m_d->m_page.visualizeWhitespace->setChecked(displaySettings.m_visualizeWhitespace); + m_d->m_page.displayFoldingMarkers->setChecked(displaySettings.m_displayFoldingMarkers); + m_d->m_page.highlightCurrentLine->setChecked(displaySettings.m_highlightCurrentLine); +} + +TabSettings GeneralSettingsPage::tabSettings() const +{ + return m_d->m_tabSettings; +} + +StorageSettings GeneralSettingsPage::storageSettings() const +{ + return m_d->m_storageSettings; +} + +DisplaySettings GeneralSettingsPage::displaySettings() const +{ + return m_d->m_displaySettings; +} + +void GeneralSettingsPage::setDisplaySettings(const DisplaySettings &newDisplaySettings) +{ + if (newDisplaySettings != m_d->m_displaySettings) { + m_d->m_displaySettings = newDisplaySettings; + if (m_d->m_core) + if (QSettings *s = m_d->m_core->settings()) + m_d->m_displaySettings.toSettings(m_d->m_parameters.settingsPrefix, s); + + emit displaySettingsChanged(newDisplaySettings); + } +} diff --git a/src/plugins/texteditor/generalsettingspage.h b/src/plugins/texteditor/generalsettingspage.h new file mode 100644 index 0000000000..184e3bace8 --- /dev/null +++ b/src/plugins/texteditor/generalsettingspage.h @@ -0,0 +1,98 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef GENERALSETTINGSPAGE_H +#define GENERALSETTINGSPAGE_H + +#include "texteditor_global.h" + +#include <coreplugin/icore.h> +#include <coreplugin/dialogs/ioptionspage.h> + +#include <QtCore/QObject> + +namespace TextEditor { + +struct TabSettings; +struct StorageSettings; +struct DisplaySettings; + +struct TEXTEDITOR_EXPORT GeneralSettingsPageParameters { + QString name; + QString category; + QString trCategory; + QString settingsPrefix; +}; + +class Ui_generalSettingsPage; + +class TEXTEDITOR_EXPORT GeneralSettingsPage : public Core::IOptionsPage +{ + Q_OBJECT + +public: + GeneralSettingsPage(Core::ICore *core, + const GeneralSettingsPageParameters &p, + QObject *parent); + virtual ~GeneralSettingsPage(); + + //IOptionsPage + QString name() const; + QString category() const; + QString trCategory() const; + + QWidget *createPage(QWidget *parent); + void finished(bool accepted); + + TabSettings tabSettings() const; + StorageSettings storageSettings() const; + DisplaySettings displaySettings() const; + + void setDisplaySettings(const DisplaySettings &); + +signals: + void tabSettingsChanged(const TextEditor::TabSettings &); + void storageSettingsChanged(const TextEditor::StorageSettings &); + void displaySettingsChanged(const TextEditor::DisplaySettings &); + +private: + void settingsFromUI(TabSettings &rc, + StorageSettings &storageSettings, + DisplaySettings &displaySettings) const; + void settingsToUI(); + struct GeneralSettingsPagePrivate; + GeneralSettingsPagePrivate *m_d; +}; + +} // namespace TextEditor + +#endif // GENERALSETTINGSPAGE_H diff --git a/src/plugins/texteditor/generalsettingspage.ui b/src/plugins/texteditor/generalsettingspage.ui new file mode 100644 index 0000000000..11f6b8b997 --- /dev/null +++ b/src/plugins/texteditor/generalsettingspage.ui @@ -0,0 +1,336 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>TextEditor::generalSettingsPage</class> + <widget class="QWidget" name="TextEditor::generalSettingsPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>514</width> + <height>427</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="groupBox_3"> + <property name="title"> + <string>Tab/Indent Settings</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QCheckBox" name="autoIndent"> + <property name="text"> + <string>Enable automatic &indentation</string> + </property> + </widget> + </item> + <item row="0" column="2"> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QLabel" name="labelTabSize"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Ta&b size:</string> + </property> + <property name="buddy"> + <cstring>tabSize</cstring> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>5</width> + <height>5</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QSpinBox" name="tabSize"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximum"> + <number>20</number> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="3"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="0"> + <widget class="QCheckBox" name="insertSpaces"> + <property name="text"> + <string>Insert &spaces instead of tabs</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <widget class="QLabel" name="labelIndentSize"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Indent size:</string> + </property> + <property name="buddy"> + <cstring>indentSize</cstring> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>5</width> + <height>5</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QSpinBox" name="indentSize"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximum"> + <number>20</number> + </property> + </widget> + </item> + </layout> + </item> + <item row="2" column="0"> + <widget class="QCheckBox" name="smartBackspace"> + <property name="text"> + <string>&Backspace follows indentation</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <spacer name="horizontalSpacer_5"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Expanding</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>Storage Settings</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QCheckBox" name="cleanWhitespace"> + <property name="text"> + <string>&Clean whitespace</string> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QCheckBox" name="inEntireDocument"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>In entire &document</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QCheckBox" name="addFinalNewLine"> + <property name="text"> + <string>&Ensure newline at end of file</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Display Settings</string> + </property> + <layout class="QGridLayout"> + <item row="0" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QCheckBox" name="showWrapColumn"> + <property name="text"> + <string>Display right &margin at column</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="wrapColumn"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="maximum"> + <number>999</number> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="0"> + <widget class="QCheckBox" name="enableTextWrapping"> + <property name="text"> + <string>Enable text &wrapping</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QCheckBox" name="displayLineNumbers"> + <property name="text"> + <string>Display line &numbers</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QCheckBox" name="visualizeWhitespace"> + <property name="text"> + <string>&Visualize whitespace</string> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QCheckBox" name="displayFoldingMarkers"> + <property name="text"> + <string>Display &folding markers</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QCheckBox" name="highlightCurrentLine"> + <property name="text"> + <string>Highlight current &line</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>351</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>cleanWhitespace</sender> + <signal>toggled(bool)</signal> + <receiver>inEntireDocument</receiver> + <slot>setEnabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>47</x> + <y>184</y> + </hint> + <hint type="destinationlabel"> + <x>91</x> + <y>212</y> + </hint> + </hints> + </connection> + <connection> + <sender>showWrapColumn</sender> + <signal>toggled(bool)</signal> + <receiver>wrapColumn</receiver> + <slot>setEnabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>399</x> + <y>308</y> + </hint> + <hint type="destinationlabel"> + <x>474</x> + <y>308</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/plugins/texteditor/icompletioncollector.h b/src/plugins/texteditor/icompletioncollector.h new file mode 100644 index 0000000000..46dc3f8b3d --- /dev/null +++ b/src/plugins/texteditor/icompletioncollector.h @@ -0,0 +1,114 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef COMPLETIONCOLLECTORINTERFACE_H +#define COMPLETIONCOLLECTORINTERFACE_H + +#include "texteditor_global.h" + +#include <QtCore/QObject> +#include <QtCore/QVariant> +#include <QtGui/QIcon> +#include <QtGui/QKeyEvent> + +namespace TextEditor { + +class ICompletionCollector; +class ITextEditable; + +struct CompletionItem +{ + CompletionItem(ICompletionCollector *collector = 0) + : m_relevance(0), + m_collector(collector) + { } + + bool isValid() const + { return m_collector != 0; } + + operator bool() const + { return m_collector != 0; } + + QString m_text; + QString m_details; + QIcon m_icon; + QVariant m_data; + int m_relevance; + ICompletionCollector *m_collector; +}; + +/* Defines the interface to completion collectors. A completion collector tells + * the completion support code when a completion is triggered and provides the + * list of possible completions. It keeps an internal state so that it can be + * polled for the list of completions, which is reset with a call to reset. + */ +class TEXTEDITOR_EXPORT ICompletionCollector : public QObject +{ + Q_OBJECT +public: + ICompletionCollector(QObject *parent = 0) : QObject(parent) {} + virtual ~ICompletionCollector() {} + + /* This method should return whether the cursor is at a position which could + * trigger an autocomplete. It will be called each time a character is typed in + * the text editor. + */ + virtual bool triggersCompletion(ITextEditable *editor) = 0; + + // returns starting position + virtual int startCompletion(ITextEditable *editor) = 0; + + /* This method should add all the completions it wants to show into the list, + * based on the given cursor position. + */ + virtual void completions(QList<CompletionItem> *completions) = 0; + + /* This method should complete the given completion item. + */ + virtual void complete(const CompletionItem &item) = 0; + + /* This method gives the completion collector a chance to partially complete + * based on a set of items. The general use case is to complete the common + * prefix shared by all possible completion items. + * + * Returns whether the completion popup should be closed. + */ + virtual bool partiallyComplete(const QList<TextEditor::CompletionItem> &completionItems) = 0; + + /* Called when it's safe to clean up the completion items. + */ + virtual void cleanup() = 0; +}; + +} // namespace TextEditor + +#endif // COMPLETIONCOLLECTORINTERFACE_H diff --git a/src/plugins/texteditor/images/finddirectory.png b/src/plugins/texteditor/images/finddirectory.png Binary files differnew file mode 100644 index 0000000000..f20c7d013e --- /dev/null +++ b/src/plugins/texteditor/images/finddirectory.png diff --git a/src/plugins/texteditor/images/finddocuments.png b/src/plugins/texteditor/images/finddocuments.png Binary files differnew file mode 100644 index 0000000000..a637456b58 --- /dev/null +++ b/src/plugins/texteditor/images/finddocuments.png diff --git a/src/plugins/texteditor/itexteditable.h b/src/plugins/texteditor/itexteditable.h new file mode 100644 index 0000000000..30ec5e1493 --- /dev/null +++ b/src/plugins/texteditor/itexteditable.h @@ -0,0 +1,64 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef ITEXTEDITABLE_H +#define ITEXTEDITABLE_H + +#include "texteditor_global.h" +#include "itexteditor.h" + +namespace TextEditor { + +class TEXTEDITOR_EXPORT ITextEditable : public ITextEditor +{ + Q_OBJECT +public: + ITextEditable() {} + virtual ~ITextEditable() {} + + /* Removes 'length' characteres to the right of the cursor. */ + virtual void remove(int length) = 0; + + /* Inserts the given string to the right of the cursor. */ + virtual void insert(const QString &string) = 0; + + /* Replaces 'length' characters to the right of the cursor with the given string. */ + virtual void replace(int length, const QString &string) = 0; + + virtual void setCurPos(int pos) = 0; + + virtual void select(int toPos) = 0; +}; + +} // namespace TextEditor + +#endif // ITEXTEDITABLE_H diff --git a/src/plugins/texteditor/itexteditor.h b/src/plugins/texteditor/itexteditor.h new file mode 100644 index 0000000000..ca517577a0 --- /dev/null +++ b/src/plugins/texteditor/itexteditor.h @@ -0,0 +1,132 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef ITEXTEDITOR_H +#define ITEXTEDITOR_H + +#include "texteditor_global.h" + +#include <coreplugin/editormanager/ieditor.h> + +#include <QtCore/QObject> +#include <QtCore/QList> +#include <QtGui/QColor> +#include <QtGui/QIcon> + +QT_BEGIN_NAMESPACE +class QTextBlock; +QT_END_NAMESPACE + +namespace TextEditor { + +class ITextEditor; + +class TEXTEDITOR_EXPORT ITextMark : public QObject +{ + Q_OBJECT +public: + ITextMark(QObject *parent = 0) : QObject(parent) {} + virtual ~ITextMark() {} + + virtual QIcon icon() const = 0; + + virtual void updateLineNumber(int lineNumber) = 0; + virtual void updateBlock(const QTextBlock &block) = 0; + virtual void removedFromEditor() = 0; + virtual void documentClosing() = 0; +}; + +typedef QList<ITextMark *> TextMarks; + + +class TEXTEDITOR_EXPORT ITextMarkable : public QObject +{ + Q_OBJECT +public: + ITextMarkable(QObject *parent = 0) : QObject(parent) {} + virtual ~ITextMarkable() {} + virtual bool addMark(ITextMark *mark, int line) = 0; + + virtual TextMarks marksAt(int line) const = 0; + virtual void removeMark(ITextMark *mark) = 0; + virtual bool hasMark(ITextMark *mark) const = 0; + virtual void updateMark(ITextMark *mark) = 0; +}; + +class TEXTEDITOR_EXPORT ITextEditor : public Core::IEditor +{ + Q_OBJECT +public: + enum PositionOperation { + Current = 1, + EndOfLine = 2, + StartOfLine = 3, + Anchor = 4, + EndOfDoc = 5 + }; + + ITextEditor() {} + virtual ~ITextEditor() {} + + virtual int find(const QString &string) const = 0; + + virtual void gotoLine(int line, int column = 0) = 0; + + virtual int position(PositionOperation posOp = Current, int at = -1) const = 0; + virtual void convertPosition(int pos, int *line, int *column) const = 0; + virtual QRect cursorRect(int pos = -1) const = 0; + + virtual QString contents() const = 0; + virtual QString selectedText() const = 0; + virtual QString textAt(int pos, int length) const = 0; + virtual QChar characterAt(int pos) const = 0; + + virtual void triggerCompletions() = 0; + + virtual ITextMarkable *markableInterface() = 0; + + virtual void setContextHelpId(const QString &) = 0; + + virtual void setTextCodec(QTextCodec *) = 0; + virtual QTextCodec *textCodec() const = 0; + + +signals: + void contentsChanged(); + void markRequested(TextEditor::ITextEditor *editor, int line); + void tooltipRequested(TextEditor::ITextEditor *editor, const QPoint &globalPos, int position); + void contextHelpIdRequested(TextEditor::ITextEditor *editor, int position); +}; + +} // namespace TextEditor + +#endif // ITEXTEDITOR_H diff --git a/src/plugins/texteditor/linenumberfilter.cpp b/src/plugins/texteditor/linenumberfilter.cpp new file mode 100644 index 0000000000..ea079196f7 --- /dev/null +++ b/src/plugins/texteditor/linenumberfilter.cpp @@ -0,0 +1,80 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "linenumberfilter.h" +#include "itexteditor.h" + +#include <coreplugin/editormanager/editormanager.h> + +#include <QtCore/QVariant> + +using namespace Core; +using namespace QuickOpen; +using namespace TextEditor; +using namespace TextEditor::Internal; + +LineNumberFilter::LineNumberFilter(EditorManager *editorManager, QObject *parent): + IQuickOpenFilter(parent) +{ + m_editorManager = editorManager; + setShortcutString("l"); + setIncludedByDefault(true); +} + +QList<FilterEntry> LineNumberFilter::matchesFor(const QString &entry) +{ + bool ok; + QList<FilterEntry> value; + int line = entry.toInt(&ok); + if (line > 0 && currentTextEditor()) + value.append(FilterEntry(this, QString("Line %1").arg(line), QVariant(line))); + return value; +} + +void LineNumberFilter::accept(FilterEntry selection) const +{ + ITextEditor *editor = currentTextEditor(); + if (editor) { + m_editorManager->ensureEditorManagerVisible(); + m_editorManager->addCurrentPositionToNavigationHistory(true); + editor->gotoLine(selection.internalData.toInt()); + m_editorManager->addCurrentPositionToNavigationHistory(); + editor->widget()->setFocus(); + } +} + +ITextEditor *LineNumberFilter::currentTextEditor() const +{ + if (!m_editorManager->currentEditor()) + return 0; + return qobject_cast<TextEditor::ITextEditor*>(m_editorManager->currentEditor()); +} diff --git a/src/plugins/texteditor/linenumberfilter.h b/src/plugins/texteditor/linenumberfilter.h new file mode 100644 index 0000000000..f20de8a718 --- /dev/null +++ b/src/plugins/texteditor/linenumberfilter.h @@ -0,0 +1,76 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef LINENUMBERFILTER_H +#define LINENUMBERFILTER_H + +#include <quickopen/iquickopenfilter.h> + +#include <QtCore/QString> +#include <QtCore/QList> +#include <QtCore/QByteArray> +#include <QtCore/QFutureInterface> +#include <QtGui/QWidget> + +namespace Core { +class EditorManager; +} + +namespace TextEditor { + +class ITextEditor; + +namespace Internal { + +class LineNumberFilter : public QuickOpen::IQuickOpenFilter +{ + Q_OBJECT + +public: + LineNumberFilter(Core::EditorManager *editorManager, QObject *parent = 0); + QString trName() const { return tr("Line in current document"); } + QString name() const { return "Line in current document"; } + QuickOpen::IQuickOpenFilter::Priority priority() const { return QuickOpen::IQuickOpenFilter::High; } + QList<QuickOpen::FilterEntry> matchesFor(const QString &entry); + void accept(QuickOpen::FilterEntry selection) const; + void refresh(QFutureInterface<void> &) {} + +private: + ITextEditor *currentTextEditor() const; + + Core::EditorManager *m_editorManager; +}; + +} // namespace Internal +} // namespace TextEditor + +#endif // LINENUMBERFILTER_H diff --git a/src/plugins/texteditor/plaintexteditor.cpp b/src/plugins/texteditor/plaintexteditor.cpp new file mode 100644 index 0000000000..6019d73b25 --- /dev/null +++ b/src/plugins/texteditor/plaintexteditor.cpp @@ -0,0 +1,129 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "plaintexteditor.h" +#include "texteditorconstants.h" +#include "texteditorplugin.h" + +#include <coreplugin/coreconstants.h> +#include <coreplugin/icore.h> +#include <coreplugin/uniqueidmanager.h> + +using namespace TextEditor; +using namespace TextEditor::Internal; + +PlainTextEditorEditable::PlainTextEditorEditable(PlainTextEditor *editor) + :BaseTextEditorEditable(editor) +{ + Core::ICore *core = TextEditorPlugin::core(); + m_context << core->uniqueIDManager()-> + uniqueIdentifier(Core::Constants::K_DEFAULT_TEXT_EDITOR); + m_context << core->uniqueIDManager()-> + uniqueIdentifier(TextEditor::Constants::C_TEXTEDITOR); +} + +PlainTextEditor::PlainTextEditor(QWidget *parent) : + BaseTextEditor(parent) +{ + + setRevisionsVisible(true); + setMarksVisible(true); + setRequestMarkEnabled(false); + setLineSeparatorsAllowed(true); + + setMimeType(QLatin1String(TextEditor::Constants::C_TEXTEDITOR_MIMETYPE_TEXT)); +} + +QList<int> PlainTextEditorEditable::context() const +{ + return m_context; +} + + +Core::IEditor *PlainTextEditorEditable::duplicate(QWidget *parent) +{ + PlainTextEditor *newEditor = new PlainTextEditor(parent); + newEditor->duplicateFrom(editor()); + TextEditorPlugin::instance()->initializeEditor(newEditor); + return newEditor->editableInterface(); +} + +const char *PlainTextEditorEditable::kind() const +{ + return Core::Constants::K_DEFAULT_TEXT_EDITOR; +} + +// Indent a text block based on previous line. +// Simple text paragraph layout: +// aaaa aaaa +// +// bbb bb +// bbb bb +// +// - list +// list line2 +// +// - listn +// +// ccc +// +// @todo{Add formatting to wrap paragraphs. This requires some +// hoops as the current indentation routines are not prepared +// for additional block being inserted. It might be possible +// to do in 2 steps (indenting/wrapping)} +// + +void PlainTextEditor::indentBlock(QTextDocument *doc, QTextBlock block, QChar /* typedChar */) +{ + // At beginning: Leave as is. + if (block == doc->begin()) + return; + + const QTextBlock previous = block.previous(); + const QString previousText = previous.text(); + // Empty line indicates a start of a new paragraph. Leave as is. + if (previousText.isEmpty() || previousText.trimmed().isEmpty()) + return; + + // Just use previous line. + // Skip non-alphanumerical characters when determining the indentation + // to enable writing bulleted lists whose items span several lines. + int i = 0; + while (i < previousText.size()) { + if (previousText.at(i).isLetterOrNumber()) { + const TextEditor::TabSettings &ts = tabSettings(); + ts.indentLine(block, ts.columnAt(previousText, i)); + break; + } + ++i; + } +} diff --git a/src/plugins/texteditor/plaintexteditor.h b/src/plugins/texteditor/plaintexteditor.h new file mode 100644 index 0000000000..7641580789 --- /dev/null +++ b/src/plugins/texteditor/plaintexteditor.h @@ -0,0 +1,72 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef PLAINTEXTEDITOR_H +#define PLAINTEXTEDITOR_H + +#include "basetexteditor.h" + +#include <QtCore/QList> + +namespace TextEditor { + +class PlainTextEditor; + +class TEXTEDITOR_EXPORT PlainTextEditorEditable : public BaseTextEditorEditable +{ +public: + PlainTextEditorEditable(PlainTextEditor *); + QList<int> context() const; + + bool duplicateSupported() const { return true; } + Core::IEditor *duplicate(QWidget *parent); + const char *kind() const; +private: + QList<int> m_context; +}; + +class TEXTEDITOR_EXPORT PlainTextEditor : public BaseTextEditor +{ + Q_OBJECT + +public: + PlainTextEditor(QWidget *parent); + +protected: + BaseTextEditorEditable *createEditableInterface() { return new PlainTextEditorEditable(this); } + // Indent a text block based on previous line. + virtual void indentBlock(QTextDocument *doc, QTextBlock block, QChar typedChar); +}; + +} // namespace TextEditor + +#endif // PLAINTEXTEDITOR_H diff --git a/src/plugins/texteditor/plaintexteditorfactory.cpp b/src/plugins/texteditor/plaintexteditorfactory.cpp new file mode 100644 index 0000000000..24e36bfff6 --- /dev/null +++ b/src/plugins/texteditor/plaintexteditorfactory.cpp @@ -0,0 +1,83 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "plaintexteditor.h" +#include "plaintexteditorfactory.h" +#include "texteditorconstants.h" +#include "texteditorplugin.h" +#include "texteditoractionhandler.h" + +#include <coreplugin/coreconstants.h> +#include <coreplugin/editormanager/editormanager.h> + +using namespace TextEditor; +using namespace TextEditor::Internal; + +PlainTextEditorFactory::PlainTextEditorFactory(QObject *parent) : + Core::IEditorFactory(parent), + m_kind(Core::Constants::K_DEFAULT_TEXT_EDITOR) +{ + m_actionHandler = new TextEditorActionHandler(TextEditorPlugin::core(), + QLatin1String(TextEditor::Constants::C_TEXTEDITOR), + TextEditorActionHandler::Format); + m_mimeTypes << QLatin1String(TextEditor::Constants::C_TEXTEDITOR_MIMETYPE_TEXT) + << QLatin1String(TextEditor::Constants::C_TEXTEDITOR_MIMETYPE_XML); +} + +PlainTextEditorFactory::~PlainTextEditorFactory() +{ + delete m_actionHandler; +} + +QString PlainTextEditorFactory::kind() const +{ + return m_kind; +} + +Core::IFile *PlainTextEditorFactory::open(const QString &fileName) +{ + Core::ICore *core = TextEditorPlugin::core(); + Core::IEditor *iface = core->editorManager()->openEditor(fileName, kind()); + return iface ? iface->file() : 0; +} + +Core::IEditor *PlainTextEditorFactory::createEditor(QWidget *parent) +{ + PlainTextEditor *rc = new PlainTextEditor(parent); + TextEditorPlugin::instance()->initializeEditor(rc); + return rc->editableInterface(); +} + +QStringList PlainTextEditorFactory::mimeTypes() const +{ + return m_mimeTypes; +} diff --git a/src/plugins/texteditor/plaintexteditorfactory.h b/src/plugins/texteditor/plaintexteditorfactory.h new file mode 100644 index 0000000000..0e225c942e --- /dev/null +++ b/src/plugins/texteditor/plaintexteditorfactory.h @@ -0,0 +1,73 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef PLAINTEXTEDITORFACTORY_H +#define PLAINTEXTEDITORFACTORY_H + +#include <coreplugin/editormanager/ieditorfactory.h> +#include <QtCore/QStringList> + +namespace Core { +class IEditor; +class IFile; +} + +namespace TextEditor { +class TextEditorActionHandler; +namespace Internal { + +class PlainTextEditorFactory : public Core::IEditorFactory +{ + Q_OBJECT + +public: + PlainTextEditorFactory(QObject *parent = 0); + virtual ~PlainTextEditorFactory(); + + virtual QStringList mimeTypes() const; + //Core::IEditorFactory + QString kind() const; + Core::IFile *open(const QString &fileName); + Core::IEditor *createEditor(QWidget *parent); + + TextEditor::TextEditorActionHandler *actionHandler() const { return m_actionHandler; } + +private: + const QString m_kind; + QStringList m_mimeTypes; + TextEditor::TextEditorActionHandler *m_actionHandler; +}; + +} // namespace Internal +} // namespace TextEditor + +#endif // PLAINTEXTEDITORFACTORY_H diff --git a/src/plugins/texteditor/storagesettings.cpp b/src/plugins/texteditor/storagesettings.cpp new file mode 100644 index 0000000000..906215bf81 --- /dev/null +++ b/src/plugins/texteditor/storagesettings.cpp @@ -0,0 +1,81 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "storagesettings.h" +#include <QSettings> +#include <QString> + +namespace TextEditor { + +static const char * const cleanWhitespaceKey = "cleanWhitespace"; +static const char * const inEntireDocumentKey = "inEntireDocument"; +static const char * const addFinalNewLineKey = "addFinalNewLine"; +static const char * const groupPostfix = "StorageSettings"; + +StorageSettings::StorageSettings() + : m_cleanWhitespace(true), + m_inEntireDocument(false), + m_addFinalNewLine(true) +{ +} + +void StorageSettings::toSettings(const QString &category, QSettings *s) const +{ + QString group = QLatin1String(groupPostfix); + if (!category.isEmpty()) + group.insert(0, category); + s->beginGroup(group); + s->setValue(QLatin1String(cleanWhitespaceKey), m_cleanWhitespace); + s->setValue(QLatin1String(inEntireDocumentKey), m_inEntireDocument); + s->setValue(QLatin1String(addFinalNewLineKey), m_addFinalNewLine); + s->endGroup(); +} + +void StorageSettings::fromSettings(const QString &category, const QSettings *s) +{ + QString group = QLatin1String(groupPostfix); + if (!category.isEmpty()) + group.insert(0, category); + group += QLatin1Char('/'); + m_cleanWhitespace = s->value(group + QLatin1String(cleanWhitespaceKey), m_cleanWhitespace).toBool(); + m_inEntireDocument = s->value(group + QLatin1String(inEntireDocumentKey), m_inEntireDocument).toBool(); + m_addFinalNewLine = s->value(group + QLatin1String(addFinalNewLineKey), m_addFinalNewLine).toBool(); +} + +bool StorageSettings::equals(const StorageSettings &ts) const +{ + return m_addFinalNewLine == ts.m_addFinalNewLine + && m_cleanWhitespace == ts.m_cleanWhitespace + && m_inEntireDocument == ts.m_inEntireDocument; +} + +} // namespace TextEditor diff --git a/src/plugins/texteditor/storagesettings.h b/src/plugins/texteditor/storagesettings.h new file mode 100644 index 0000000000..dc3310094a --- /dev/null +++ b/src/plugins/texteditor/storagesettings.h @@ -0,0 +1,62 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef STORAGESETTINGS_H +#define STORAGESETTINGS_H + +#include "texteditor_global.h" + +QT_BEGIN_NAMESPACE +class QSettings; +QT_END_NAMESPACE + +namespace TextEditor { + +struct TEXTEDITOR_EXPORT StorageSettings { + StorageSettings(); + + void toSettings(const QString &category, QSettings *s) const; + void fromSettings(const QString &category, const QSettings *s); + + bool equals(const StorageSettings &ts) const; + + bool m_cleanWhitespace; + bool m_inEntireDocument; + bool m_addFinalNewLine; +}; + +inline bool operator==(const StorageSettings &t1, const StorageSettings &t2) { return t1.equals(t2); } +inline bool operator!=(const StorageSettings &t1, const StorageSettings &t2) { return !t1.equals(t2); } + +} // namespace TextEditor + +#endif // STORAGESETTINGS_H diff --git a/src/plugins/texteditor/tabsettings.cpp b/src/plugins/texteditor/tabsettings.cpp new file mode 100644 index 0000000000..daaee1a562 --- /dev/null +++ b/src/plugins/texteditor/tabsettings.cpp @@ -0,0 +1,241 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "tabsettings.h" + +#include <QtCore/QSettings> +#include <QtCore/QString> +#include <QtGui/QTextCursor> +#include <QtGui/QTextDocument> +#include <QDebug> + +static const char* spacesForTabsKey = "SpacesForTabs"; +static const char* smartBackspaceKey = "SmartBackspace"; +static const char* autoIndentKey = "AutoIndent"; +static const char* tabSizeKey = "TabSize"; +static const char* indentSizeKey = "IndentSize"; +static const char* groupPostfix = "TabSettings"; + +namespace TextEditor { + +TabSettings::TabSettings() : + m_spacesForTabs(true), + m_autoIndent(true), + m_smartBackspace(false), + m_tabSize(8), + m_indentSize(4) +{ +} + +void TabSettings::toSettings(const QString &category, QSettings *s) const +{ + QString group = QLatin1String(groupPostfix); + if (!category.isEmpty()) + group.insert(0, category); + s->beginGroup(group); + s->setValue(QLatin1String(spacesForTabsKey), m_spacesForTabs); + s->setValue(QLatin1String(autoIndentKey), m_autoIndent); + s->setValue(QLatin1String(smartBackspaceKey), m_smartBackspace); + s->setValue(QLatin1String(tabSizeKey), m_tabSize); + s->setValue(QLatin1String(indentSizeKey), m_indentSize); + s->endGroup(); +} + +void TabSettings::fromSettings(const QString &category, const QSettings *s) +{ + QString group = QLatin1String(groupPostfix); + if (!category.isEmpty()) + group.insert(0, category); + group += QLatin1Char('/'); + + *this = TabSettings(); // Assign defaults + + m_spacesForTabs = s->value(group + QLatin1String(spacesForTabsKey), m_spacesForTabs).toBool(); + m_autoIndent = s->value(group + QLatin1String(autoIndentKey), m_autoIndent).toBool(); + m_smartBackspace = s->value(group + QLatin1String(smartBackspaceKey), m_smartBackspace).toBool(); + m_tabSize = s->value(group + QLatin1String(tabSizeKey), m_tabSize).toInt(); + m_indentSize = s->value(group + QLatin1String(indentSizeKey), m_indentSize).toInt(); +} + + + +int TabSettings::lineIndentPosition(const QString &text) const +{ + int i = 0; + while (i < text.size()) { + if (!text.at(i).isSpace()) + break; + ++i; + } + int column = columnAt(text, i); + return i - (column % m_indentSize); +} + +int TabSettings::firstNonSpace(const QString &text) const +{ + int i = 0; + while (i < text.size()) { + if (!text.at(i).isSpace()) + return i; + ++i; + } + return i; +} + +int TabSettings::trailingWhitespaces(const QString &text) const +{ + int i = 0; + while (i < text.size()) { + if (!text.at(text.size()-1-i).isSpace()) + return i; + ++i; + } + return i; +} + +bool TabSettings::isIndentationClean(const QString &text) const +{ + int i = 0; + int spaceCount = 0; + while (i < text.size()) { + QChar c = text.at(i); + if (!c.isSpace()) + return true; + + if (c == QLatin1Char(' ')) { + ++spaceCount; + if (spaceCount == m_tabSize) + return false; + } else if (c == QLatin1Char('\t')) { + if (m_spacesForTabs || spaceCount != m_indentSize) + return false; + spaceCount = 0; + } + ++i; + } + return true; +} + + +int TabSettings::columnAt(const QString &text, int position) const +{ + int column = 0; + for (int i = 0; i < position; ++i) { + if (text.at(i) == QLatin1Char('\t')) + column = column - (column % m_tabSize) + m_tabSize; + else + ++column; + } + return column; +} + +int TabSettings::spacesLeftFromPosition(const QString &text, int position) const +{ + int i = position; + while (i > 0) { + if (!text.at(i-1).isSpace()) + break; + --i; + } + return position - i; +} + +int TabSettings::indentedColumn(int column, bool doIndent) const +{ + int aligned = (column / m_indentSize) * m_indentSize; + if (doIndent) + return aligned + m_indentSize; + if (aligned < column) + return aligned; + return qMax(0, aligned - m_indentSize); +} + +QString TabSettings::indentationString(int startColumn, int targetColumn) const +{ + targetColumn = qMax(startColumn, targetColumn); + if (m_spacesForTabs) + return QString(targetColumn - startColumn, QLatin1Char(' ')); + + QString s; + int alignedStart = startColumn - (startColumn % m_tabSize) + m_tabSize; + if (alignedStart > startColumn && alignedStart <= targetColumn) { + s += QLatin1Char('\t'); + startColumn = alignedStart; + } + if (int columns = targetColumn - startColumn) { + int tabs = columns / m_tabSize; + s += QString(tabs, QLatin1Char('\t')); + s += QString(columns - tabs * m_tabSize, QLatin1Char(' ')); + } + return s; +} + +void TabSettings::indentLine(QTextBlock block, int newIndent) const +{ + const QString text = block.text(); + const int oldBlockLength = text.size(); + + // Quickly check whether indenting is required. + if (oldBlockLength == 0 && newIndent == 0) + return; + + const QString indentString = indentationString(0, newIndent); + newIndent = indentString.length(); + + if (oldBlockLength == indentString.length() && text == indentString) + return; + + if (oldBlockLength > indentString.length() && + text.startsWith(indentString) && + !text.at(indentString.length()).isSpace()) { + return; + } + + QTextCursor cursor(block); + cursor.beginEditBlock(); + cursor.movePosition(QTextCursor::StartOfBlock); + cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, firstNonSpace(text)); + cursor.removeSelectedText(); + cursor.insertText(indentString); + cursor.endEditBlock(); +} + +bool TabSettings::equals(const TabSettings &ts) const +{ + return m_spacesForTabs == ts.m_spacesForTabs + && m_autoIndent == ts.m_autoIndent + && m_smartBackspace == ts.m_smartBackspace + && m_tabSize == ts.m_tabSize + && m_indentSize == ts.m_indentSize; +} + +} // namespace TextEditor diff --git a/src/plugins/texteditor/tabsettings.h b/src/plugins/texteditor/tabsettings.h new file mode 100644 index 0000000000..8b326fadc4 --- /dev/null +++ b/src/plugins/texteditor/tabsettings.h @@ -0,0 +1,83 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef TABSETTINGS_H +#define TABSETTINGS_H + +#include "texteditor_global.h" + +#include <QtGui/QTextBlock> + +QT_BEGIN_NAMESPACE +class QSettings; +QT_END_NAMESPACE + +namespace TextEditor { + +// Tab settings: Data type the GeneralSettingsPage acts on +// with some convenience functions for formatting. +struct TEXTEDITOR_EXPORT TabSettings +{ + TabSettings(); + + void toSettings(const QString &category, QSettings *s) const; + void fromSettings(const QString &category, const QSettings *s); + + + int lineIndentPosition(const QString &text) const; + int firstNonSpace(const QString &text) const; + int columnAt(const QString &text, int position) const; + int spacesLeftFromPosition(const QString &text, int position) const; + int indentedColumn(int column, bool doIndent = true) const; + QString indentationString(int startColumn, int targetColumn) const; + + void indentLine(QTextBlock block, int newIndent) const; + + int trailingWhitespaces(const QString &text) const; + bool isIndentationClean(const QString &text) const; + + + bool m_spacesForTabs; + bool m_autoIndent; + bool m_smartBackspace; + int m_tabSize; + int m_indentSize; + + bool equals(const TabSettings &ts) const; +}; + +inline bool operator==(const TabSettings &t1, const TabSettings &t2) { return t1.equals(t2); } +inline bool operator!=(const TabSettings &t1, const TabSettings &t2) { return !t1.equals(t2); } + +} // namespace TextEditor + +#endif // TABSETTINGS_H diff --git a/src/plugins/texteditor/textblockiterator.cpp b/src/plugins/texteditor/textblockiterator.cpp new file mode 100644 index 0000000000..51c894d9fc --- /dev/null +++ b/src/plugins/texteditor/textblockiterator.cpp @@ -0,0 +1,96 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "textblockiterator.h" + +#include <QtGui/QTextDocument> + +using namespace TextEditor; + +TextBlockIterator::TextBlockIterator() + : m_document(0), + m_initialized(false) +{ } + +TextBlockIterator::TextBlockIterator(const QTextBlock &block) + : m_document(block.document()), + m_block(block), + m_initialized(false) +{ } + +bool TextBlockIterator::equals(const TextBlockIterator &other) const +{ + if (m_document != other.m_document) + return false; + return m_block == other.m_block; +} + +QString TextBlockIterator::operator*() const +{ + if (! m_initialized) + read(); + return m_text; +} + +void TextBlockIterator::read() const +{ + m_initialized = true; + m_text = m_block.text(); +} + +TextBlockIterator &TextBlockIterator::operator++() +{ + m_initialized = false; + m_block = m_block.next(); + return *this; +} + +TextBlockIterator &TextBlockIterator::operator--() +{ + m_initialized = false; + m_block = m_block.previous(); + return *this; +} + +TextBlockIterator TextBlockIterator::operator++(int) +{ + TextBlockIterator prev; + ++*this; + return prev; +} + +TextBlockIterator TextBlockIterator::operator--(int) +{ + TextBlockIterator prev; + --*this; + return prev; +} diff --git a/src/plugins/texteditor/textblockiterator.h b/src/plugins/texteditor/textblockiterator.h new file mode 100644 index 0000000000..0f9c585a6f --- /dev/null +++ b/src/plugins/texteditor/textblockiterator.h @@ -0,0 +1,71 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef _TEXTBLOCKITERATOR_H +#define _TEXTBLOCKITERATOR_H + +#include "texteditor_global.h" + +#include <QtGui/QTextBlock> + +namespace TextEditor { + +/* Iterator for the text blocks of a document. */ +class TEXTEDITOR_EXPORT TextBlockIterator { +public: + TextBlockIterator(); + TextBlockIterator(const QTextBlock &block); + + bool equals(const TextBlockIterator &o) const; + + QString operator*() const; + TextBlockIterator &operator++(); + TextBlockIterator &operator--(); + TextBlockIterator operator++(int); + TextBlockIterator operator--(int); + +private: + void read() const; + +private: + const QTextDocument *m_document; + QTextBlock m_block; + mutable QString m_text; + mutable bool m_initialized; +}; + +inline bool operator==(const TextBlockIterator &i1, const TextBlockIterator &i2) { return i1.equals(i2); } +inline bool operator!=(const TextBlockIterator &i1, const TextBlockIterator &i2) { return !i1.equals(i2); } + +} // namespace TextEditor + +#endif // _TEXTBLOCKITERATOR_H diff --git a/src/plugins/texteditor/texteditor.pri b/src/plugins/texteditor/texteditor.pri new file mode 100644 index 0000000000..9d3789b825 --- /dev/null +++ b/src/plugins/texteditor/texteditor.pri @@ -0,0 +1,3 @@ +include(texteditor_dependencies.pri) + +LIBS *= -l$$qtLibraryTarget(TextEditor) diff --git a/src/plugins/texteditor/texteditor.pro b/src/plugins/texteditor/texteditor.pro new file mode 100644 index 0000000000..59c9cc4bb4 --- /dev/null +++ b/src/plugins/texteditor/texteditor.pro @@ -0,0 +1,58 @@ +TEMPLATE = lib +TARGET = TextEditor +DEFINES += TEXTEDITOR_LIBRARY +include(../../qworkbenchplugin.pri) +include(texteditor_dependencies.pri) +SOURCES += texteditorplugin.cpp \ + textfilewizard.cpp \ + plaintexteditor.cpp \ + plaintexteditorfactory.cpp \ + basetextdocument.cpp \ + basetexteditor.cpp \ + texteditoractionhandler.cpp \ + completionsupport.cpp \ + completionwidget.cpp \ + fontsettingspage.cpp \ + tabsettings.cpp \ + storagesettings.cpp \ + displaysettings.cpp \ + fontsettings.cpp \ + textblockiterator.cpp \ + linenumberfilter.cpp \ + generalsettingspage.cpp \ + basetextmark.cpp \ + findinfiles.cpp \ + basefilefind.cpp \ + texteditorsettings.cpp \ + codecselector.cpp +HEADERS += texteditorplugin.h \ + textfilewizard.h \ + plaintexteditor.h \ + plaintexteditorfactory.h \ + basetexteditor_p.h \ + basetextdocument.h \ + completionsupport.h \ + completionwidget.h \ + basetexteditor.h \ + texteditoractionhandler.h \ + fontsettingspage.h \ + icompletioncollector.h \ + texteditorconstants.h \ + tabsettings.h \ + storagesettings.h \ + displaysettings.h \ + fontsettings.h \ + textblockiterator.h \ + itexteditable.h \ + itexteditor.h \ + linenumberfilter.h \ + texteditor_global.h \ + generalsettingspage.h \ + basetextmark.h \ + findinfiles.h \ + basefilefind.h \ + texteditorsettings.h \ + codecselector.h +FORMS += fontsettingspage.ui \ + generalsettingspage.ui +RESOURCES += texteditor.qrc diff --git a/src/plugins/texteditor/texteditor.qrc b/src/plugins/texteditor/texteditor.qrc new file mode 100644 index 0000000000..191343ce0e --- /dev/null +++ b/src/plugins/texteditor/texteditor.qrc @@ -0,0 +1,7 @@ +<RCC> + <qresource prefix="/texteditor" > + <file>images/finddocuments.png</file> + <file>images/finddirectory.png</file> + <file>TextEditor.mimetypes.xml</file> + </qresource> +</RCC> diff --git a/src/plugins/texteditor/texteditor_dependencies.pri b/src/plugins/texteditor/texteditor_dependencies.pri new file mode 100644 index 0000000000..87f23e9eed --- /dev/null +++ b/src/plugins/texteditor/texteditor_dependencies.pri @@ -0,0 +1,4 @@ +include(../../libs/utils/utils.pri) +include(../../plugins/find/find.pri) +include(../../plugins/quickopen/quickopen.pri) +include(../../plugins/coreplugin/coreplugin.pri) diff --git a/src/plugins/texteditor/texteditor_global.h b/src/plugins/texteditor/texteditor_global.h new file mode 100644 index 0000000000..ef7f01c641 --- /dev/null +++ b/src/plugins/texteditor/texteditor_global.h @@ -0,0 +1,57 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +/**************************************************************************** +** +** Copyright (C) 1992-$THISYEAR$ Trolltech AS. All rights reserved. +** +** This file is part of the $MODULE$ of the Qt Toolkit. +** +** $LICENSE$ +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +****************************************************************************/ + +#ifndef TEXTEDITOR_GLOBAL_H +#define TEXTEDITOR_GLOBAL_H + +#include <QtCore/qglobal.h> + +#if defined(TEXTEDITOR_LIBRARY) +# define TEXTEDITOR_EXPORT Q_DECL_EXPORT +#else +# define TEXTEDITOR_EXPORT Q_DECL_IMPORT +#endif + +#endif // TEXTEDITOR_GLOBAL_H diff --git a/src/plugins/texteditor/texteditoractionhandler.cpp b/src/plugins/texteditor/texteditoractionhandler.cpp new file mode 100644 index 0000000000..91246dcc8c --- /dev/null +++ b/src/plugins/texteditor/texteditoractionhandler.cpp @@ -0,0 +1,459 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "texteditoractionhandler.h" +#include "texteditorconstants.h" +#include "basetexteditor.h" +#include "texteditorplugin.h" +#include "linenumberfilter.h" + +#include <quickopen/quickopenmanager.h> +#include <coreplugin/coreconstants.h> +#include <coreplugin/uniqueidmanager.h> +#include <coreplugin/actionmanager/actionmanagerinterface.h> +#include <coreplugin/editormanager/editormanager.h> + +#include <QtCore/QSet> +#include <QtCore/QtDebug> +#include <QtGui/QAction> +#include <QtGui/QTextCursor> + +using namespace TextEditor; +using namespace TextEditor::Internal; + +TextEditorActionHandler::TextEditorActionHandler(Core::ICore *core, + const QString &context, + uint optionalActions) : + QObject(core), + m_optionalActions(optionalActions), + m_currentEditor(0), + m_core(core), + m_initialized(false) +{ + m_undoAction = m_redoAction = m_copyAction = m_cutAction = m_pasteAction + = m_selectAllAction = m_gotoAction = m_printAction = m_formatAction + = m_visualizeWhitespaceAction = m_textWrappingAction + = m_unCommentSelectionAction = m_unCollapseAllAction + = m_collapseAction = m_expandAction + = m_deleteLineAction = m_selectEncodingAction + = m_increaseFontSizeAction = m_decreaseFontSizeAction + = 0; + + m_contextId << m_core->uniqueIDManager()->uniqueIdentifier(context); + + connect(m_core, SIGNAL(contextAboutToChange(Core::IContext *)), + this, SLOT(updateCurrentEditor(Core::IContext *))); +} + +void TextEditorActionHandler::setupActions(BaseTextEditor *editor) +{ + initializeActions(); + editor->setActionHack(this); + QObject::connect(editor, SIGNAL(undoAvailable(bool)), this, SLOT(updateUndoAction())); + QObject::connect(editor, SIGNAL(redoAvailable(bool)), this, SLOT(updateRedoAction())); + QObject::connect(editor, SIGNAL(copyAvailable(bool)), this, SLOT(updateCopyAction())); +} + + +void TextEditorActionHandler::initializeActions() +{ + if (!m_initialized) { + createActions(); + m_initialized = true; + } +} + +void TextEditorActionHandler::createActions() +{ + m_undoAction = registerNewAction(QLatin1String(Core::Constants::UNDO), this, SLOT(undoAction()), + tr("&Undo")); + m_redoAction = registerNewAction(QLatin1String(Core::Constants::REDO), this, SLOT(redoAction()), + tr("&Redo")); + m_copyAction = registerNewAction(QLatin1String(Core::Constants::COPY), this, SLOT(copyAction())); + m_cutAction = registerNewAction(QLatin1String(Core::Constants::CUT), this, SLOT(cutAction())); + m_pasteAction = registerNewAction(QLatin1String(Core::Constants::PASTE), this, SLOT(pasteAction())); + m_selectAllAction = registerNewAction(QLatin1String(Core::Constants::SELECTALL), this, SLOT(selectAllAction())); + m_gotoAction = registerNewAction(QLatin1String(Core::Constants::GOTO), this, SLOT(gotoAction())); + m_printAction = registerNewAction(QLatin1String(Core::Constants::PRINT), this, SLOT(printAction())); + + Core::ActionManagerInterface *am = m_core->actionManager(); + + Core::IActionContainer *medit = am->actionContainer(Core::Constants::M_EDIT); + Core::IActionContainer *advancedMenu = am->actionContainer(Core::Constants::M_EDIT_ADVANCED); + + m_selectEncodingAction = new QAction(tr("Select Encoding..."), this); + Core::ICommand *command = am->registerAction(m_selectEncodingAction, Constants::SELECT_ENCODING, m_contextId); + connect(m_selectEncodingAction, SIGNAL(triggered()), this, SLOT(selectEncoding())); + medit->addAction(command, Core::Constants::G_EDIT_OTHER); + + + m_formatAction = new QAction(tr("Auto-&indent Selection"), this); + command = am->registerAction(m_formatAction, TextEditor::Constants::AUTO_INDENT_SELECTION, m_contextId); + command->setDefaultKeySequence(QKeySequence(tr("Ctrl+I"))); + advancedMenu->addAction(command); + connect(m_formatAction, SIGNAL(triggered(bool)), this, SLOT(formatAction())); + + + m_visualizeWhitespaceAction = new QAction(tr("Visualize &Whitespace"), this); + m_visualizeWhitespaceAction->setCheckable(true); + command = am->registerAction(m_visualizeWhitespaceAction, + TextEditor::Constants::VISUALIZE_WHITESPACE, m_contextId); +#ifndef Q_OS_MAC + command->setDefaultKeySequence(QKeySequence(tr("Ctrl+E, Ctrl+V"))); +#endif + + advancedMenu->addAction(command); + connect(m_visualizeWhitespaceAction, SIGNAL(triggered(bool)), this, SLOT(setVisualizeWhitespace(bool))); + + m_textWrappingAction = new QAction(tr("Enable Text &Wrapping"), this); + m_textWrappingAction->setCheckable(true); + command = am->registerAction(m_textWrappingAction, + TextEditor::Constants::TEXT_WRAPPING, m_contextId); +#ifndef Q_OS_MAC + command->setDefaultKeySequence(QKeySequence(tr("Ctrl+E, Ctrl+W"))); +#endif + advancedMenu->addAction(command); + connect(m_textWrappingAction, SIGNAL(triggered(bool)), this, SLOT(setTextWrapping(bool))); + + + m_unCommentSelectionAction = new QAction(tr("(Un)Comment &Selection"), this); + command = am->registerAction(m_unCommentSelectionAction, Constants::UN_COMMENT_SELECTION, m_contextId); + command->setDefaultKeySequence(QKeySequence(tr("Ctrl+/"))); + connect(m_unCommentSelectionAction, SIGNAL(triggered()), this, SLOT(unCommentSelection())); + advancedMenu->addAction(command); + + m_deleteLineAction = new QAction(tr("Delete &Line"), this); + command = am->registerAction(m_deleteLineAction, Constants::DELETE_LINE, m_contextId); + command->setDefaultKeySequence(QKeySequence(tr("Shift+Del"))); + connect(m_deleteLineAction, SIGNAL(triggered()), this, SLOT(deleteLine())); + + m_collapseAction = new QAction(tr("Collapse"), this); + command = am->registerAction(m_collapseAction, Constants::COLLAPSE, m_contextId); + command->setDefaultKeySequence(QKeySequence(tr("Ctrl+<"))); + connect(m_collapseAction, SIGNAL(triggered()), this, SLOT(collapse())); + advancedMenu->addAction(command); + + m_expandAction = new QAction(tr("Expand"), this); + command = am->registerAction(m_expandAction, Constants::EXPAND, m_contextId); + command->setDefaultKeySequence(QKeySequence(tr("Ctrl+>"))); + connect(m_expandAction, SIGNAL(triggered()), this, SLOT(expand())); + advancedMenu->addAction(command); + + m_unCollapseAllAction = new QAction(tr("(Un)&Collapse All"), this); + command = am->registerAction(m_unCollapseAllAction, Constants::UN_COLLAPSE_ALL, m_contextId); + connect(m_unCollapseAllAction, SIGNAL(triggered()), this, SLOT(unCollapseAll())); + advancedMenu->addAction(command); + + m_increaseFontSizeAction = new QAction(tr("Increase Font Size"), this); + command = am->registerAction(m_increaseFontSizeAction, Constants::INCREASE_FONT_SIZE, m_contextId); + command->setDefaultKeySequence(QKeySequence(tr("Ctrl++"))); + connect(m_increaseFontSizeAction, SIGNAL(triggered()), this, SLOT(increaseFontSize())); + advancedMenu->addAction(command); + + m_decreaseFontSizeAction = new QAction(tr("Decrease Font Size"), this); + command = am->registerAction(m_decreaseFontSizeAction, Constants::DECREASE_FONT_SIZE, m_contextId); + command->setDefaultKeySequence(QKeySequence(tr("Ctrl+-"))); + connect(m_decreaseFontSizeAction, SIGNAL(triggered()), this, SLOT(decreaseFontSize())); + advancedMenu->addAction(command); +} + +bool TextEditorActionHandler::supportsAction(const QString & /*id */) const +{ + return true; +} + +QAction *TextEditorActionHandler::registerNewAction(const QString &id, const QString &title) +{ + if (!supportsAction(id)) + return 0; + + QAction *result = new QAction(title, this); + m_core->actionManager()->registerAction(result, id, m_contextId); + return result; +} + +QAction *TextEditorActionHandler::registerNewAction(const QString &id, + QObject *receiver, + const char *slot, + const QString &title) +{ + QAction *rc = registerNewAction(id, title); + if (!rc) + return 0; + + connect(rc, SIGNAL(triggered()), receiver, slot); + return rc; +} + +TextEditorActionHandler::UpdateMode TextEditorActionHandler::updateMode() const +{ + if (!m_currentEditor) + return NoEditor; + return m_currentEditor->file()->isReadOnly() ? ReadOnlyMode : WriteMode; +} + +void TextEditorActionHandler::updateActions() +{ + updateActions(updateMode()); +} + +void TextEditorActionHandler::updateActions(UpdateMode um) +{ + if (m_pasteAction) + m_pasteAction->setEnabled(um != NoEditor); + if (m_selectAllAction) + m_selectAllAction->setEnabled(um != NoEditor); + if (m_gotoAction) + m_gotoAction->setEnabled(um != NoEditor); + if (m_selectEncodingAction) + m_selectEncodingAction->setEnabled(um != NoEditor); + if (m_printAction) + m_printAction->setEnabled(um != NoEditor); + if (m_formatAction) + m_formatAction->setEnabled((m_optionalActions & Format) && um != NoEditor); + if (m_unCommentSelectionAction) + m_unCommentSelectionAction->setEnabled((m_optionalActions & UnCommentSelection) && um != NoEditor); + if (m_collapseAction) + m_collapseAction->setEnabled(um != NoEditor); + if (m_expandAction) + m_expandAction->setEnabled(um != NoEditor); + if (m_unCollapseAllAction) + m_unCollapseAllAction->setEnabled((m_optionalActions & UnCollapseAll) && um != NoEditor); + if (m_decreaseFontSizeAction) + m_decreaseFontSizeAction->setEnabled(um != NoEditor); + if (m_increaseFontSizeAction) + m_increaseFontSizeAction->setEnabled(um != NoEditor); + if (m_visualizeWhitespaceAction) { + m_visualizeWhitespaceAction->setEnabled(um != NoEditor); + if (m_currentEditor) + m_visualizeWhitespaceAction->setChecked(m_currentEditor->displaySettings().m_visualizeWhitespace); + } + if (m_textWrappingAction) { + m_textWrappingAction->setEnabled(um != NoEditor); + if (m_currentEditor) + m_textWrappingAction->setChecked(m_currentEditor->displaySettings().m_textWrapping); + } + + updateRedoAction(); + updateUndoAction(); + updateCopyAction(); +} + +void TextEditorActionHandler::updateRedoAction() +{ + if (m_redoAction) + m_redoAction->setEnabled(m_currentEditor && m_currentEditor->document()->isRedoAvailable()); +} + +void TextEditorActionHandler::updateUndoAction() +{ + if (m_undoAction) + m_undoAction->setEnabled(m_currentEditor && m_currentEditor->document()->isUndoAvailable()); +} + +void TextEditorActionHandler::updateCopyAction() +{ + const bool hasCopyableText = m_currentEditor && m_currentEditor->textCursor().hasSelection(); + if (m_cutAction) + m_cutAction->setEnabled(hasCopyableText && updateMode() == WriteMode); + if (m_copyAction) + m_copyAction->setEnabled(hasCopyableText); +} + +void TextEditorActionHandler::undoAction() +{ + if (m_currentEditor) + m_currentEditor->undo(); +} + +void TextEditorActionHandler::redoAction() +{ + if (m_currentEditor) + m_currentEditor->redo(); +} + +void TextEditorActionHandler::copyAction() +{ + if (m_currentEditor) + m_currentEditor->copy(); +} + +void TextEditorActionHandler::cutAction() +{ + if (m_currentEditor) + m_currentEditor->cut(); +} + +void TextEditorActionHandler::pasteAction() +{ + if (m_currentEditor) + m_currentEditor->paste(); +} + +void TextEditorActionHandler::selectAllAction() +{ + if (m_currentEditor) + m_currentEditor->selectAll(); +} + +void TextEditorActionHandler::gotoAction() +{ + QuickOpen::QuickOpenManager *quickopen = QuickOpen::QuickOpenManager::instance(); + Q_ASSERT(quickopen); + QString shortcut = TextEditorPlugin::instance()->lineNumberFilter()->shortcutString(); + quickopen->show(shortcut + " <line number>", 2, 13); +} + +void TextEditorActionHandler::printAction() +{ + if (m_currentEditor) + m_currentEditor->print(m_core->printer()); +} + +void TextEditorActionHandler::formatAction() +{ + if (m_currentEditor) + m_currentEditor->format(); +} + + +void TextEditorActionHandler::setVisualizeWhitespace(bool checked) +{ + if (m_currentEditor) { + DisplaySettings ds = m_currentEditor->displaySettings(); + ds.m_visualizeWhitespace = checked; + m_currentEditor->setDisplaySettings(ds); + } +} + +void TextEditorActionHandler::setTextWrapping(bool checked) +{ + if (m_currentEditor) { + DisplaySettings ds = m_currentEditor->displaySettings(); + ds.m_textWrapping = checked; + m_currentEditor->setDisplaySettings(ds); + } +} + +void TextEditorActionHandler::unCommentSelection() +{ + if (m_currentEditor) + m_currentEditor->unCommentSelection(); +} + +void TextEditorActionHandler::deleteLine() +{ + if (m_currentEditor) + m_currentEditor->deleteLine(); +} + +void TextEditorActionHandler::unCollapseAll() +{ + if (m_currentEditor) + m_currentEditor->unCollapseAll(); +} + +void TextEditorActionHandler::collapse() +{ + if (m_currentEditor) + m_currentEditor->collapse(); +} + +void TextEditorActionHandler::expand() +{ + if (m_currentEditor) + m_currentEditor->expand(); +} + +void TextEditorActionHandler::selectEncoding() +{ + if (m_currentEditor) + m_currentEditor->selectEncoding(); +} + +void TextEditorActionHandler::increaseFontSize() +{ + if (m_currentEditor) + m_currentEditor->zoomIn(); +} + +void TextEditorActionHandler::decreaseFontSize() +{ + if (m_currentEditor) + m_currentEditor->zoomOut(); +} + + +void TextEditorActionHandler::updateCurrentEditor(Core::IContext *object) +{ + do { + if (!object) { + if (!m_currentEditor) + return; + + m_currentEditor = 0; + break; + } + BaseTextEditor *editor = qobject_cast<BaseTextEditor *>(object->widget()); + if (!editor) { + if (!m_currentEditor) + return; + + m_currentEditor = 0; + break; + } + + if (editor == m_currentEditor) + return; + + if (editor->actionHack() != this) { + m_currentEditor = 0; + break; + } + + m_currentEditor = editor; + + } while (false); + updateActions(); +} + + +const QPointer<BaseTextEditor> &TextEditorActionHandler::currentEditor() const +{ + return m_currentEditor; +} + +Core::ICore *TextEditorActionHandler::core() const +{ + return m_core; +} + diff --git a/src/plugins/texteditor/texteditoractionhandler.h b/src/plugins/texteditor/texteditoractionhandler.h new file mode 100644 index 0000000000..d0c222f848 --- /dev/null +++ b/src/plugins/texteditor/texteditoractionhandler.h @@ -0,0 +1,143 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef TEXTEDITORACTIONHANDLER_H +#define TEXTEDITORACTIONHANDLER_H + +#include "texteditor_global.h" +#include "basetexteditor.h" + +#include "coreplugin/icontext.h" +#include "coreplugin/icore.h" + +#include <QtCore/QObject> +#include <QtCore/QPointer> +#include <QtCore/QList> + +namespace TextEditor { + +class BaseTextEditor; + +// Redirects slots from global actions to the respective editor. + +class TEXTEDITOR_EXPORT TextEditorActionHandler : public QObject +{ + Q_OBJECT + +public: + enum OptionalActionsMask { + None = 0, + Format = 1, + UnCommentSelection = 2, + UnCollapseAll = 4 + }; + + TextEditorActionHandler(Core::ICore *core, + const QString &context, + uint optionalActions = None); + void setupActions(BaseTextEditor *editor); + + void initializeActions(); + +public slots: + void updateActions(); + void updateRedoAction(); + void updateUndoAction(); + void updateCopyAction(); + +protected: + const QPointer<BaseTextEditor> ¤tEditor() const; + QAction *registerNewAction(const QString &id, const QString &title = QString()); + QAction *registerNewAction(const QString &id, QObject *receiver, const char *slot, + const QString &title = QString()); + Core::ICore *core() const; + + enum UpdateMode { NoEditor , ReadOnlyMode, WriteMode }; + UpdateMode updateMode() const; + + virtual void createActions(); + virtual bool supportsAction(const QString &id) const; + virtual void updateActions(UpdateMode um); + +private slots: + void undoAction(); + void redoAction(); + void copyAction(); + void cutAction(); + void pasteAction(); + void selectAllAction(); + void gotoAction(); + void printAction(); + void formatAction(); + void setVisualizeWhitespace(bool); + void setTextWrapping(bool); + void unCommentSelection(); + void unCollapseAll(); + void collapse(); + void expand(); + void deleteLine(); + void selectEncoding(); + void increaseFontSize(); + void decreaseFontSize(); + void updateCurrentEditor(Core::IContext *object); + +private: + QAction *m_undoAction; + QAction *m_redoAction; + QAction *m_copyAction; + QAction *m_cutAction; + QAction *m_pasteAction; + QAction *m_selectAllAction; + QAction *m_gotoAction; + QAction *m_printAction; + QAction *m_formatAction; + QAction *m_visualizeWhitespaceAction; + QAction *m_textWrappingAction; + QAction *m_unCommentSelectionAction; + QAction *m_unCollapseAllAction; + QAction *m_collapseAction; + QAction *m_expandAction; + QAction *m_deleteLineAction; + QAction *m_selectEncodingAction; + QAction *m_increaseFontSizeAction; + QAction *m_decreaseFontSizeAction; + + uint m_optionalActions; + QPointer<BaseTextEditor> m_currentEditor; + Core::ICore *m_core; + QList<int> m_contextId; + bool m_initialized; +}; + +} // namespace TextEditor + +#endif // TEXTEDITORACTIONHANDLER_H diff --git a/src/plugins/texteditor/texteditorconstants.h b/src/plugins/texteditor/texteditorconstants.h new file mode 100644 index 0000000000..5133a2c148 --- /dev/null +++ b/src/plugins/texteditor/texteditorconstants.h @@ -0,0 +1,88 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef TEXTEDITORCONSTANTS_H +#define TEXTEDITORCONSTANTS_H + +namespace TextEditor { +namespace Constants { + +const char * const C_TEXTEDITOR = "Text Editor"; +const char * const COMPLETE_THIS = "TextEditor.CompleteThis"; +const char * const VISUALIZE_WHITESPACE = "TextEditor.VisualizeWhitespace"; +const char * const TEXT_WRAPPING = "TextEditor.TextWrapping"; +const char * const UN_COMMENT_SELECTION = "TextEditor.UnCommentSelection"; +const char * const COLLAPSE = "TextEditor.Collapse"; +const char * const EXPAND = "TextEditor.Expand"; +const char * const UN_COLLAPSE_ALL = "TextEditor.UnCollapseAll"; +const char * const AUTO_INDENT_SELECTION = "TextEditor.AutoIndentSelection"; +const char * const INCREASE_FONT_SIZE = "TextEditor.IncreaseFontSize"; +const char * const DECREASE_FONT_SIZE = "TextEditor.DecreaseFontSize"; +const char * const DELETE_LINE = "TextEditor.DeleteLine"; +const char * const DELETE_WORD = "TextEditor.DeleteWord"; +const char * const SELECT_ENCODING = "TextEditor.SelectEncoding"; +const char * const C_TEXTEDITOR_MIMETYPE_TEXT = "text/plain"; +const char * const C_TEXTEDITOR_MIMETYPE_XML = "application/xml"; + + +// Text color and style categories +const char * const C_TEXT = "Text"; + +const char * const C_SELECTION = "Selection"; +const char * const C_LINE_NUMBER = "LineNumber"; +const char * const C_SEARCH_RESULT = "SearchResult"; +const char * const C_SEARCH_SCOPE = "SearchScope"; +const char * const C_PARENTHESES = "Parentheses"; +const char * const C_CURRENT_LINE = "CurrentLine"; + +const char * const C_NUMBER = "Number"; +const char * const C_STRING = "String"; +const char * const C_TYPE = "Type"; +const char * const C_KEYWORD = "Keyword"; +const char * const C_OPERATOR = "Operator"; +const char * const C_PREPROCESSOR = "Preprocessor"; +const char * const C_LABEL = "Label"; +const char * const C_COMMENT = "Comment"; +const char * const C_DISABLED_CODE = "DisabledCode"; + +const char * const C_ADDED_LINE = "AddedLine"; +const char * const C_REMOVED_LINE = "RemovedLine"; +const char * const C_DIFF_FILE = "DiffFile"; +const char * const C_DIFF_LOCATION = "DiffLocation"; + +const char * const C_VARIABLE = "Variable"; +const char * const C_FUNCTION = "Function"; + +} // namespace Constants +} // namespace TextEditor + +#endif // TEXTEDITORCONSTANTS_H diff --git a/src/plugins/texteditor/texteditorplugin.cpp b/src/plugins/texteditor/texteditorplugin.cpp new file mode 100644 index 0000000000..5e943bd70c --- /dev/null +++ b/src/plugins/texteditor/texteditorplugin.cpp @@ -0,0 +1,180 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "texteditorplugin.h" + +#include "findinfiles.h" +#include "fontsettings.h" +#include "linenumberfilter.h" +#include "texteditorconstants.h" +#include "texteditorsettings.h" +#include "textfilewizard.h" +#include "plaintexteditorfactory.h" +#include "plaintexteditor.h" +#include "storagesettings.h" + +#include <coreplugin/coreconstants.h> +#include <coreplugin/mimedatabase.h> +#include <coreplugin/uniqueidmanager.h> +#include <coreplugin/actionmanager/actionmanagerinterface.h> +#include <coreplugin/actionmanager/icommand.h> +#include <coreplugin/editormanager/editormanager.h> +#include <texteditor/texteditoractionhandler.h> + +#include <QtCore/qplugin.h> +#include <QtGui/QShortcut> +#include <QtGui/QMainWindow> + +using namespace TextEditor; +using namespace TextEditor::Internal; + +TextEditorPlugin *TextEditorPlugin::m_instance = 0; + +TextEditorPlugin::TextEditorPlugin() : + m_core(0), + m_settings(0), + m_wizard(0), + m_editorFactory(0), + m_lineNumberFilter(0) +{ + Q_ASSERT(!m_instance); + m_instance = this; +} + +TextEditorPlugin::~TextEditorPlugin() +{ + m_instance = 0; +} + +TextEditorPlugin *TextEditorPlugin::instance() +{ + return m_instance; +} + +Core::ICore *TextEditorPlugin::core() +{ + return m_instance->m_core; +} + +//ExtensionSystem::PluginInterface +bool TextEditorPlugin::initialize(const QStringList & /*arguments*/, QString *errorMessage) +{ + m_core = ExtensionSystem::PluginManager::instance()->getObject<Core::ICore>(); + + if (!m_core->mimeDatabase()->addMimeTypes(QLatin1String(":/texteditor/TextEditor.mimetypes.xml"), errorMessage)) + return false; + + Core::BaseFileWizardParameters wizardParameters(Core::IWizard::FileWizard); + wizardParameters.setDescription(tr("This creates a new text file (.txt)")); + wizardParameters.setName(tr("Text File")); + wizardParameters.setCategory(QLatin1String("General")); + wizardParameters.setTrCategory(tr("General")); + m_wizard = new TextFileWizard(QLatin1String(TextEditor::Constants::C_TEXTEDITOR_MIMETYPE_TEXT), + QLatin1String(Core::Constants::K_DEFAULT_TEXT_EDITOR), + QLatin1String("text$"), + wizardParameters, m_core); + // Add text file wizard + addAutoReleasedObject(m_wizard); + + + m_settings = new TextEditorSettings(this, this); + + // Add plain text editor factory + m_editorFactory = new PlainTextEditorFactory; + addAutoReleasedObject(m_editorFactory); + + // Goto line functionality for quick open + m_lineNumberFilter = new LineNumberFilter(m_core->editorManager()); + addAutoReleasedObject(m_lineNumberFilter); + + int contextId = m_core->uniqueIDManager()->uniqueIdentifier(TextEditor::Constants::C_TEXTEDITOR); + QList<int> context = QList<int>() << contextId; + Core::ActionManagerInterface *am = m_core->actionManager(); + + // Add shortcut for invoking automatic completion + QShortcut *completionShortcut = new QShortcut(m_core->mainWindow()); + completionShortcut->setWhatsThis(tr("Triggers a completion in this scope")); + // Make sure the shortcut still works when the completion widget is active + completionShortcut->setContext(Qt::ApplicationShortcut); + Core::ICommand *command = am->registerShortcut(completionShortcut, Constants::COMPLETE_THIS, context); +#ifndef Q_OS_MAC + command->setDefaultKeySequence(QKeySequence(tr("Ctrl+Space"))); +#else + command->setDefaultKeySequence(QKeySequence(tr("Meta+Space"))); +#endif + connect(completionShortcut, SIGNAL(activated()), this, SLOT(invokeCompletion())); + + addAutoReleasedObject(new FindInFiles(m_core, m_core->pluginManager()->getObject<Find::SearchResultWindow>())); + + return true; +} + +void TextEditorPlugin::extensionsInitialized() +{ + m_editorFactory->actionHandler()->initializeActions(); +} + +void TextEditorPlugin::initializeEditor(TextEditor::PlainTextEditor *editor) +{ + // common actions + m_editorFactory->actionHandler()->setupActions(editor); + + // settings + connect(m_settings, SIGNAL(fontSettingsChanged(TextEditor::FontSettings)), + editor, SLOT(setFontSettings(TextEditor::FontSettings))); + connect(m_settings, SIGNAL(tabSettingsChanged(TextEditor::TabSettings)), + editor, SLOT(setTabSettings(TextEditor::TabSettings))); + connect(m_settings, SIGNAL(storageSettingsChanged(TextEditor::StorageSettings)), + editor, SLOT(setStorageSettings(TextEditor::StorageSettings))); + connect(m_settings, SIGNAL(displaySettingsChanged(TextEditor::DisplaySettings)), + editor, SLOT(setDisplaySettings(TextEditor::DisplaySettings))); + + // tab settings rely on font settings + editor->setFontSettings(m_settings->fontSettings()); + editor->setTabSettings(m_settings->tabSettings()); + editor->setStorageSettings(m_settings->storageSettings()); + editor->setDisplaySettings(m_settings->displaySettings()); +} + +void TextEditorPlugin::invokeCompletion() +{ + if (!m_core) + return; + + Core::IEditor *iface = m_core->editorManager()->currentEditor(); + ITextEditor *editor = qobject_cast<ITextEditor *>(iface); + if (editor) + editor->triggerCompletions(); +} + + +Q_EXPORT_PLUGIN(TextEditorPlugin) diff --git a/src/plugins/texteditor/texteditorplugin.h b/src/plugins/texteditor/texteditorplugin.h new file mode 100644 index 0000000000..578095f609 --- /dev/null +++ b/src/plugins/texteditor/texteditorplugin.h @@ -0,0 +1,94 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef TEXTEDITORPLUGIN_H +#define TEXTEDITORPLUGIN_H + +#include <extensionsystem/iplugin.h> + +QT_BEGIN_NAMESPACE +class QAction; +QT_END_NAMESPACE + +namespace Core { +class ICore; +class IEditor; +} + +namespace TextEditor { + +class FontSettings; +class FontSettingsPage; +class TextEditorSettings; +class TextFileWizard; +class PlainTextEditor; + +namespace Internal { + +class LineNumberFilter; +class PlainTextEditorFactory; + +class TextEditorPlugin : public ExtensionSystem::IPlugin +{ + Q_OBJECT + +public: + TextEditorPlugin(); + virtual ~TextEditorPlugin(); + + static TextEditorPlugin *instance(); + static Core::ICore *core(); + + // ExtensionSystem::PluginInterface + bool initialize(const QStringList &arguments, QString *); + void extensionsInitialized(); + + void initializeEditor(PlainTextEditor *editor); + + LineNumberFilter *lineNumberFilter() { return m_lineNumberFilter; } + +private slots: + void invokeCompletion(); + +private: + static TextEditorPlugin *m_instance; + Core::ICore *m_core; + TextEditorSettings *m_settings; + TextFileWizard *m_wizard; + PlainTextEditorFactory *m_editorFactory; + LineNumberFilter *m_lineNumberFilter; +}; + +} // namespace Internal +} // namespace TextEditor + +#endif // TEXTEDITORPLUGIN_H diff --git a/src/plugins/texteditor/texteditorsettings.cpp b/src/plugins/texteditor/texteditorsettings.cpp new file mode 100644 index 0000000000..0292215f99 --- /dev/null +++ b/src/plugins/texteditor/texteditorsettings.cpp @@ -0,0 +1,152 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "texteditorsettings.h" + +#include "displaysettings.h" +#include "generalsettingspage.h" +#include "fontsettingspage.h" +#include "storagesettings.h" +#include "tabsettings.h" +#include "texteditorconstants.h" +#include "texteditorplugin.h" + +#include <QApplication> + +using namespace TextEditor; +using namespace TextEditor::Constants; + +TextEditorSettings *TextEditorSettings::m_instance = 0; + +TextEditorSettings::TextEditorSettings(Internal::TextEditorPlugin *plugin, + QObject *parent) + : QObject(parent) +{ + Q_ASSERT(!m_instance); + m_instance = this; + + ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance(); + + // Note: default background colors are coming from FormatDescription::background() + + // Add font preference page + FormatDescriptions formatDescriptions; + formatDescriptions.push_back(FormatDescription(QLatin1String(C_TEXT), tr("Text"))); + + // Special categories + const QPalette p = QApplication::palette(); + formatDescriptions.push_back(FormatDescription(QLatin1String(C_SELECTION), tr("Selection"), p.color(QPalette::HighlightedText))); + formatDescriptions.push_back(FormatDescription(QLatin1String(C_LINE_NUMBER), tr("Line Number"))); + formatDescriptions.push_back(FormatDescription(QLatin1String(C_SEARCH_RESULT), tr("Search Result"))); + formatDescriptions.push_back(FormatDescription(QLatin1String(C_SEARCH_SCOPE), tr("Search Scope"))); + formatDescriptions.push_back(FormatDescription(QLatin1String(C_PARENTHESES), tr("Parentheses"))); + formatDescriptions.push_back(FormatDescription(QLatin1String(C_CURRENT_LINE), tr("Current Line"))); + + // Standard categories + formatDescriptions.push_back(FormatDescription(QLatin1String(C_NUMBER), tr("Number"), Qt::darkBlue)); + formatDescriptions.push_back(FormatDescription(QLatin1String(C_STRING), tr("String"), Qt::darkGreen)); + formatDescriptions.push_back(FormatDescription(QLatin1String(C_TYPE), tr("Type"), Qt::darkMagenta)); + formatDescriptions.push_back(FormatDescription(QLatin1String(C_KEYWORD), tr("Keyword"), Qt::darkYellow)); + formatDescriptions.push_back(FormatDescription(QLatin1String(C_OPERATOR), tr("Operator"))); + formatDescriptions.push_back(FormatDescription(QLatin1String(C_PREPROCESSOR), tr("Preprocessor"), Qt::darkBlue)); + formatDescriptions.push_back(FormatDescription(QLatin1String(C_LABEL), tr("Label"), Qt::darkRed)); + formatDescriptions.push_back(FormatDescription(QLatin1String(C_COMMENT), tr("Comment"), Qt::darkGreen)); + formatDescriptions.push_back(FormatDescription(QLatin1String(C_DISABLED_CODE), tr("Disabled Code"), Qt::lightGray)); + + // Diff categories + formatDescriptions.push_back(FormatDescription(QLatin1String(C_ADDED_LINE), tr("Added Line"), Qt::blue)); + formatDescriptions.push_back(FormatDescription(QLatin1String(C_REMOVED_LINE), tr("Removed Line"), Qt::red)); + formatDescriptions.push_back(FormatDescription(QLatin1String(C_DIFF_FILE), tr("Diff File"), Qt::black)); + formatDescriptions.push_back(FormatDescription(QLatin1String(C_DIFF_LOCATION), tr("Diff Location"), Qt::green)); + + // Pro file categories + formatDescriptions.push_back(FormatDescription(QLatin1String(C_VARIABLE), tr("Variable"), Qt::blue)); + formatDescriptions.push_back(FormatDescription(QLatin1String(C_FUNCTION), tr("Function"), Qt::green)); + + m_fontSettingsPage = new FontSettingsPage(formatDescriptions, + QLatin1String("TextEditor"), + tr("Text Editor"), + plugin->core()); + pm->addObject(m_fontSettingsPage); + + // Add the GUI used to configure the tab, storage and display settings + TextEditor::GeneralSettingsPageParameters generalSettingsPageParameters; + generalSettingsPageParameters.name = tr("General"); + generalSettingsPageParameters.category = QLatin1String("TextEditor"); + generalSettingsPageParameters.trCategory = tr("Text Editor"); + generalSettingsPageParameters.settingsPrefix = QLatin1String("text"); + m_generalSettingsPage = new GeneralSettingsPage(plugin->core(), generalSettingsPageParameters, this); + pm->addObject(m_generalSettingsPage); + + connect(m_fontSettingsPage, SIGNAL(changed(TextEditor::FontSettings)), + this, SIGNAL(fontSettingsChanged(TextEditor::FontSettings))); + connect(m_generalSettingsPage, SIGNAL(tabSettingsChanged(TextEditor::TabSettings)), + this, SIGNAL(tabSettingsChanged(TextEditor::TabSettings))); + connect(m_generalSettingsPage, SIGNAL(storageSettingsChanged(TextEditor::StorageSettings)), + this, SIGNAL(storageSettingsChanged(TextEditor::StorageSettings))); + connect(m_generalSettingsPage, SIGNAL(displaySettingsChanged(TextEditor::DisplaySettings)), + this, SIGNAL(displaySettingsChanged(TextEditor::DisplaySettings))); +} + +TextEditorSettings::~TextEditorSettings() +{ + ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance(); + pm->removeObject(m_generalSettingsPage); + pm->removeObject(m_fontSettingsPage); + + m_instance = 0; +} + +TextEditorSettings *TextEditorSettings::instance() +{ + return m_instance; +} + +FontSettings TextEditorSettings::fontSettings() const +{ + return m_fontSettingsPage->fontSettings(); +} + +TabSettings TextEditorSettings::tabSettings() const +{ + return m_generalSettingsPage->tabSettings(); +} + +StorageSettings TextEditorSettings::storageSettings() const +{ + return m_generalSettingsPage->storageSettings(); +} + +DisplaySettings TextEditorSettings::displaySettings() const +{ + return m_generalSettingsPage->displaySettings(); +} diff --git a/src/plugins/texteditor/texteditorsettings.h b/src/plugins/texteditor/texteditorsettings.h new file mode 100644 index 0000000000..1b5a08e966 --- /dev/null +++ b/src/plugins/texteditor/texteditorsettings.h @@ -0,0 +1,88 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef TEXTEDITORSETTINGS_H +#define TEXTEDITORSETTINGS_H + +#include "texteditor_global.h" + +#include <QtCore/QObject> + +namespace TextEditor { + +class GeneralSettingsPage; +class FontSettingsPage; +class FontSettings; +struct TabSettings; +struct StorageSettings; +struct DisplaySettings; + +namespace Internal { +class TextEditorPlugin; +} + +/** + * This class provides a central place for basic text editor settings. These + * settings include font settings, tab settings, storage settings and display + * settings. + */ +class TEXTEDITOR_EXPORT TextEditorSettings : public QObject +{ + Q_OBJECT + +public: + TextEditorSettings(Internal::TextEditorPlugin *plugin, QObject *parent); + ~TextEditorSettings(); + + static TextEditorSettings *instance(); + + FontSettings fontSettings() const; + TabSettings tabSettings() const; + StorageSettings storageSettings() const; + DisplaySettings displaySettings() const; + +signals: + void fontSettingsChanged(const TextEditor::FontSettings &); + void tabSettingsChanged(const TextEditor::TabSettings &); + void storageSettingsChanged(const TextEditor::StorageSettings &); + void displaySettingsChanged(const TextEditor::DisplaySettings &); + +private: + TextEditor::FontSettingsPage *m_fontSettingsPage; + TextEditor::GeneralSettingsPage *m_generalSettingsPage; + + static TextEditorSettings *m_instance; +}; + +} // namespace TextEditor + +#endif // TEXTEDITORSETTINGS_H diff --git a/src/plugins/texteditor/textfilewizard.cpp b/src/plugins/texteditor/textfilewizard.cpp new file mode 100644 index 0000000000..b40201044c --- /dev/null +++ b/src/plugins/texteditor/textfilewizard.cpp @@ -0,0 +1,63 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "textfilewizard.h" +#include "basetexteditor.h" +#include "texteditorconstants.h" + +namespace TextEditor { + +TextFileWizard::TextFileWizard(const QString &mimeType, + const QString &editorKind, + const QString &suggestedFileName, + const BaseFileWizardParameters ¶meters, + Core::ICore *core, + QObject *parent) : + Core::StandardFileWizard(parameters, core, parent), + m_mimeType(mimeType), + m_editorKind(editorKind), + m_suggestedFileName(suggestedFileName) +{ +} + +Core::GeneratedFiles + TextFileWizard::generateFilesFromPath(const QString &path, const QString &name, + QString * /*errorMessage*/) const +{ + const QString suffix = preferredSuffix(m_mimeType); + const QString fileName = Core::BaseFileWizard::buildFileName(path, name, suffix); + Core::GeneratedFile file(fileName); + file.setEditorKind(m_editorKind); + return Core::GeneratedFiles() << file; +} + +} diff --git a/src/plugins/texteditor/textfilewizard.h b/src/plugins/texteditor/textfilewizard.h new file mode 100644 index 0000000000..a3cb379733 --- /dev/null +++ b/src/plugins/texteditor/textfilewizard.h @@ -0,0 +1,67 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef TEXTFILEWIZARD_H +#define TEXTFILEWIZARD_H + +#include "texteditor_global.h" + +#include <coreplugin/basefilewizard.h> + +namespace TextEditor { + +class TEXTEDITOR_EXPORT TextFileWizard : public Core::StandardFileWizard +{ + Q_OBJECT + +public: + typedef Core::BaseFileWizardParameters BaseFileWizardParameters; + TextFileWizard(const QString &mimeType, + const QString &editorKind, + const QString &suggestedFileName, + const BaseFileWizardParameters ¶meters, + Core::ICore *core, + QObject *parent = 0); + +protected: + virtual Core::GeneratedFiles + generateFilesFromPath(const QString &path, const QString &name, + QString *errorMessage) const; +private: + const QString m_mimeType; + const QString m_editorKind; + const QString m_suggestedFileName; +}; + +} // namespace TextEditor + +#endif // TEXTFILEWIZARD_H |