diff options
author | hjk <hjk121@nokiamail.com> | 2014-01-13 16:17:34 +0100 |
---|---|---|
committer | Eike Ziller <eike.ziller@digia.com> | 2014-01-14 07:43:00 +0100 |
commit | 4d96fa7aba7be35800d61d8bed89d3f6c3ef9329 (patch) | |
tree | c9b102981cf81023e1488224a24758af18aa064e /src/plugins/coreplugin | |
parent | 8b854270a6c214479b2cdf302072a3e74fa854da (diff) | |
download | qt-creator-4d96fa7aba7be35800d61d8bed89d3f6c3ef9329.tar.gz |
Core: Merge Find and Locator into Core plugin
Change-Id: I7053310272235d854c9f409670ff52a10a7add8b
Reviewed-by: Christian Kandeler <christian.kandeler@digia.com>
Reviewed-by: Orgad Shaneh <orgads@gmail.com>
Reviewed-by: Eike Ziller <eike.ziller@digia.com>
Diffstat (limited to 'src/plugins/coreplugin')
89 files changed, 11612 insertions, 2 deletions
diff --git a/src/plugins/coreplugin/coreplugin.cpp b/src/plugins/coreplugin/coreplugin.cpp index c6869dd175..e16a4e7f96 100644 --- a/src/plugins/coreplugin/coreplugin.cpp +++ b/src/plugins/coreplugin/coreplugin.cpp @@ -35,8 +35,11 @@ #include "mimedatabase.h" #include "modemanager.h" #include "infobar.h" + #include <coreplugin/actionmanager/actionmanager.h> #include <coreplugin/editormanager/editormanager.h> +#include <coreplugin/find/findplugin.h> +#include <coreplugin/locator/locatorplugin.h> #include <utils/savefile.h> @@ -51,10 +54,15 @@ CorePlugin::CorePlugin() : m_editMode(0), m_designMode(0) { qRegisterMetaType<Core::Id>(); m_mainWindow = new MainWindow; + m_findPlugin = new FindPlugin; + m_locatorPlugin = new LocatorPlugin; } CorePlugin::~CorePlugin() { + delete m_findPlugin; + delete m_locatorPlugin; + if (m_editMode) { removeObject(m_editMode); delete m_editMode; @@ -98,6 +106,9 @@ bool CorePlugin::initialize(const QStringList &arguments, QString *errorMessage) // Make sure we respect the process's umask when creating new files Utils::SaveFile::initializeUmask(); + m_findPlugin->initialize(arguments, errorMessage); + m_locatorPlugin->initialize(this, arguments, errorMessage); + return success; } @@ -107,11 +118,14 @@ void CorePlugin::extensionsInitialized() if (m_designMode->designModeIsRequired()) addObject(m_designMode); m_mainWindow->extensionsInitialized(); + m_findPlugin->extensionsInitialized(); + m_locatorPlugin->extensionsInitialized(); } bool CorePlugin::delayedInitialize() { HelpManager::setupHelpManager(); + m_locatorPlugin->delayedInitialize(); return true; } diff --git a/src/plugins/coreplugin/coreplugin.h b/src/plugins/coreplugin/coreplugin.h index 9855c39eba..fd6d1ad662 100644 --- a/src/plugins/coreplugin/coreplugin.h +++ b/src/plugins/coreplugin/coreplugin.h @@ -33,11 +33,15 @@ #include <extensionsystem/iplugin.h> namespace Core { + class DesignMode; +class FindPlugin; + namespace Internal { class EditMode; class MainWindow; +class LocatorPlugin; class CorePlugin : public ExtensionSystem::IPlugin { @@ -69,6 +73,8 @@ private: MainWindow *m_mainWindow; EditMode *m_editMode; DesignMode *m_designMode; + FindPlugin *m_findPlugin; + LocatorPlugin *m_locatorPlugin; }; } // namespace Internal diff --git a/src/plugins/coreplugin/coreplugin.pro b/src/plugins/coreplugin/coreplugin.pro index 10b4087a43..3b68f06f26 100644 --- a/src/plugins/coreplugin/coreplugin.pro +++ b/src/plugins/coreplugin/coreplugin.pro @@ -206,11 +206,14 @@ FORMS += dialogs/newdialog.ui \ mimetypesettingspage.ui \ mimetypemagicdialog.ui \ removefiledialog.ui \ - dialogs/addtovcsdialog.ui + dialogs/addtovcsdialog.ui RESOURCES += core.qrc \ fancyactionbar.qrc +include(find/find.pri) +include(locator/locator.pri) + win32 { SOURCES += progressmanager/progressmanager_win.cpp greaterThan(QT_MAJOR_VERSION, 4): QT += gui-private # Uses QPlatformNativeInterface. diff --git a/src/plugins/coreplugin/coreplugin.qbs b/src/plugins/coreplugin/coreplugin.qbs index b7b6c93b38..a18785cdd3 100644 --- a/src/plugins/coreplugin/coreplugin.qbs +++ b/src/plugins/coreplugin/coreplugin.qbs @@ -1,4 +1,5 @@ import qbs.base 1.0 +import qbs.FileInfo import QtcPlugin QtcPlugin { @@ -183,6 +184,92 @@ QtcPlugin { files: [ "testdatadir.cpp", "testdatadir.h", + "locator/locatorfiltertest.cpp", + "locator/locatorfiltertest.h", + "locator/locator_test.cpp" + ] + + cpp.defines: outer.concat(['SRCDIR="' + FileInfo.path(filePath) + '"']) + } + + Group { + name: "Find" + prefix: "find/" + files: [ + "basetextfind.cpp", + "basetextfind.h", + "currentdocumentfind.cpp", + "currentdocumentfind.h", + "find.qrc", + "finddialog.ui", + "findplugin.cpp", + "findplugin.h", + "findtoolbar.cpp", + "findtoolbar.h", + "findtoolwindow.cpp", + "findtoolwindow.h", + "findwidget.ui", + "ifindfilter.cpp", + "ifindfilter.h", + "ifindsupport.cpp", + "ifindsupport.h", + "searchresultcolor.h", + "searchresulttreeitemdelegate.cpp", + "searchresulttreeitemdelegate.h", + "searchresulttreeitemroles.h", + "searchresulttreeitems.cpp", + "searchresulttreeitems.h", + "searchresulttreemodel.cpp", + "searchresulttreemodel.h", + "searchresulttreeview.cpp", + "searchresulttreeview.h", + "searchresultwidget.cpp", + "searchresultwidget.h", + "searchresultwindow.cpp", + "searchresultwindow.h", + "textfindconstants.h", + "treeviewfind.cpp", + "treeviewfind.h", + ] + } + + Group { + name: "Locator" + prefix: "locator/" + files: [ + "basefilefilter.cpp", + "basefilefilter.h", + "commandlocator.cpp", + "commandlocator.h", + "directoryfilter.cpp", + "directoryfilter.h", + "directoryfilter.ui", + "executefilter.cpp", + "executefilter.h", + "filesystemfilter.cpp", + "filesystemfilter.h", + "filesystemfilter.ui", + "ilocatorfilter.cpp", + "ilocatorfilter.h", + "locator.qrc", + "locatorconstants.h", + "locatorfiltersfilter.cpp", + "locatorfiltersfilter.h", + "locatormanager.cpp", + "locatormanager.h", + "locatorplugin.cpp", + "locatorplugin.h", + "locatorsearchutils.cpp", + "locatorsearchutils.h", + "locatorwidget.cpp", + "locatorwidget.h", + "opendocumentsfilter.cpp", + "opendocumentsfilter.h", + "settingspage.cpp", + "settingspage.h", + "settingspage.ui", + "images/locator.png", + "images/reload.png", ] } diff --git a/src/plugins/coreplugin/find/basetextfind.cpp b/src/plugins/coreplugin/find/basetextfind.cpp new file mode 100644 index 0000000000..70fdef51bd --- /dev/null +++ b/src/plugins/coreplugin/find/basetextfind.cpp @@ -0,0 +1,427 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "basetextfind.h" + +#include <utils/qtcassert.h> +#include <utils/filesearch.h> + +#include <QPointer> + +#include <QTextBlock> +#include <QPlainTextEdit> +#include <QTextCursor> + +namespace Core { + +struct BaseTextFindPrivate +{ + explicit BaseTextFindPrivate(QPlainTextEdit *editor); + explicit BaseTextFindPrivate(QTextEdit *editor); + + QPointer<QTextEdit> m_editor; + QPointer<QPlainTextEdit> m_plaineditor; + QPointer<QWidget> m_widget; + QTextCursor m_findScopeStart; + QTextCursor m_findScopeEnd; + int m_findScopeVerticalBlockSelectionFirstColumn; + int m_findScopeVerticalBlockSelectionLastColumn; + int m_incrementalStartPos; + bool m_incrementalWrappedState; +}; + +BaseTextFindPrivate::BaseTextFindPrivate(QTextEdit *editor) + : m_editor(editor) + , m_widget(editor) + , m_findScopeVerticalBlockSelectionFirstColumn(-1) + , m_findScopeVerticalBlockSelectionLastColumn(-1) + , m_incrementalStartPos(-1) + , m_incrementalWrappedState(false) +{ +} + +BaseTextFindPrivate::BaseTextFindPrivate(QPlainTextEdit *editor) + : m_plaineditor(editor) + , m_widget(editor) + , m_findScopeVerticalBlockSelectionFirstColumn(-1) + , m_findScopeVerticalBlockSelectionLastColumn(-1) + , m_incrementalStartPos(-1) + , m_incrementalWrappedState(false) +{ +} + +BaseTextFind::BaseTextFind(QTextEdit *editor) + : d(new BaseTextFindPrivate(editor)) +{ +} + + +BaseTextFind::BaseTextFind(QPlainTextEdit *editor) + : d(new BaseTextFindPrivate(editor)) +{ +} + +BaseTextFind::~BaseTextFind() +{ + delete d; +} + +QTextCursor BaseTextFind::textCursor() const +{ + QTC_ASSERT(d->m_editor || d->m_plaineditor, return QTextCursor()); + return d->m_editor ? d->m_editor->textCursor() : d->m_plaineditor->textCursor(); +} + +void BaseTextFind::setTextCursor(const QTextCursor &cursor) +{ + QTC_ASSERT(d->m_editor || d->m_plaineditor, return); + d->m_editor ? d->m_editor->setTextCursor(cursor) : d->m_plaineditor->setTextCursor(cursor); +} + +QTextDocument *BaseTextFind::document() const +{ + QTC_ASSERT(d->m_editor || d->m_plaineditor, return 0); + return d->m_editor ? d->m_editor->document() : d->m_plaineditor->document(); +} + +bool BaseTextFind::isReadOnly() const +{ + QTC_ASSERT(d->m_editor || d->m_plaineditor, return true); + return d->m_editor ? d->m_editor->isReadOnly() : d->m_plaineditor->isReadOnly(); +} + +bool BaseTextFind::supportsReplace() const +{ + return !isReadOnly(); +} + +FindFlags BaseTextFind::supportedFindFlags() const +{ + return FindBackward | FindCaseSensitively | FindRegularExpression + | FindWholeWords | FindPreserveCase; +} + +void BaseTextFind::resetIncrementalSearch() +{ + d->m_incrementalStartPos = -1; + d->m_incrementalWrappedState = false; +} + +void BaseTextFind::clearResults() +{ + emit highlightAll(QString(), 0); +} + +QString BaseTextFind::currentFindString() const +{ + QTextCursor cursor = textCursor(); + if (cursor.hasSelection() && cursor.block() != cursor.document()->findBlock(cursor.anchor())) + return QString(); // multi block selection + + if (cursor.hasSelection()) + return cursor.selectedText(); + + if (!cursor.atBlockEnd() && !cursor.hasSelection()) { + cursor.movePosition(QTextCursor::StartOfWord); + cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); + QString s = cursor.selectedText(); + foreach (QChar c, s) { + if (!c.isLetterOrNumber() && c != QLatin1Char('_')) { + s.clear(); + break; + } + } + return s; + } + + return QString(); +} + +QString BaseTextFind::completedFindString() const +{ + QTextCursor cursor = textCursor(); + cursor.setPosition(textCursor().selectionStart()); + cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); + return cursor.selectedText(); +} + +IFindSupport::Result BaseTextFind::findIncremental(const QString &txt, FindFlags findFlags) +{ + QTextCursor cursor = textCursor(); + if (d->m_incrementalStartPos < 0) + d->m_incrementalStartPos = cursor.selectionStart(); + cursor.setPosition(d->m_incrementalStartPos); + bool wrapped = false; + bool found = find(txt, findFlags, cursor, &wrapped); + if (wrapped != d->m_incrementalWrappedState && found) { + d->m_incrementalWrappedState = wrapped; + showWrapIndicator(d->m_widget); + } + if (found) + emit highlightAll(txt, findFlags); + else + emit highlightAll(QString(), 0); + return found ? Found : NotFound; +} + +IFindSupport::Result BaseTextFind::findStep(const QString &txt, FindFlags findFlags) +{ + bool wrapped = false; + bool found = find(txt, findFlags, textCursor(), &wrapped); + if (wrapped) + showWrapIndicator(d->m_widget); + if (found) { + d->m_incrementalStartPos = textCursor().selectionStart(); + d->m_incrementalWrappedState = false; + } + return found ? Found : NotFound; +} + +void BaseTextFind::replace(const QString &before, const QString &after, FindFlags findFlags) +{ + QTextCursor cursor = replaceInternal(before, after, findFlags); + setTextCursor(cursor); +} + +QTextCursor BaseTextFind::replaceInternal(const QString &before, const QString &after, + FindFlags findFlags) +{ + QTextCursor cursor = textCursor(); + bool usesRegExp = (findFlags & FindRegularExpression); + bool preserveCase = (findFlags & FindPreserveCase); + QRegExp regexp(before, + (findFlags & FindCaseSensitively) ? Qt::CaseSensitive : Qt::CaseInsensitive, + usesRegExp ? QRegExp::RegExp : QRegExp::FixedString); + + if (regexp.exactMatch(cursor.selectedText())) { + QString realAfter; + if (usesRegExp) + realAfter = Utils::expandRegExpReplacement(after, regexp.capturedTexts()); + else if (preserveCase) + realAfter = Utils::matchCaseReplacement(cursor.selectedText(), after); + else + realAfter = after; + int start = cursor.selectionStart(); + cursor.insertText(realAfter); + if ((findFlags & FindBackward) != 0) + cursor.setPosition(start); + } + return cursor; +} + +bool BaseTextFind::replaceStep(const QString &before, const QString &after, FindFlags findFlags) +{ + QTextCursor cursor = replaceInternal(before, after, findFlags); + bool wrapped = false; + bool found = find(before, findFlags, cursor, &wrapped); + if (wrapped) + showWrapIndicator(d->m_widget); + return found; +} + +int BaseTextFind::replaceAll(const QString &before, const QString &after, FindFlags findFlags) +{ + QTextCursor editCursor = textCursor(); + if (!d->m_findScopeStart.isNull()) + editCursor.setPosition(d->m_findScopeStart.position()); + else + editCursor.movePosition(QTextCursor::Start); + editCursor.beginEditBlock(); + int count = 0; + bool usesRegExp = (findFlags & FindRegularExpression); + bool preserveCase = (findFlags & FindPreserveCase); + QRegExp regexp(before); + regexp.setPatternSyntax(usesRegExp ? QRegExp::RegExp : QRegExp::FixedString); + regexp.setCaseSensitivity((findFlags & FindCaseSensitively) ? Qt::CaseSensitive : Qt::CaseInsensitive); + QTextCursor found = findOne(regexp, editCursor, textDocumentFlagsForFindFlags(findFlags)); + bool first = true; + while (!found.isNull() && inScope(found.selectionStart(), found.selectionEnd())) { + if (found == editCursor && !first) { + if (editCursor.atEnd()) + break; + // If the newly found QTextCursor is the same as recently edit one we have to move on, + // otherwise we would run into an endless loop for some regular expressions + // like ^ or \b. + QTextCursor newPosCursor = editCursor; + newPosCursor.movePosition(findFlags & FindBackward ? + QTextCursor::PreviousCharacter : + QTextCursor::NextCharacter); + found = findOne(regexp, newPosCursor, textDocumentFlagsForFindFlags(findFlags)); + continue; + } + if (first) + first = false; + ++count; + editCursor.setPosition(found.selectionStart()); + editCursor.setPosition(found.selectionEnd(), QTextCursor::KeepAnchor); + regexp.exactMatch(found.selectedText()); + + QString realAfter; + if (usesRegExp) + realAfter = Utils::expandRegExpReplacement(after, regexp.capturedTexts()); + else if (preserveCase) + realAfter = Utils::matchCaseReplacement(found.selectedText(), after); + else + realAfter = after; + editCursor.insertText(realAfter); + found = findOne(regexp, editCursor, textDocumentFlagsForFindFlags(findFlags)); + } + editCursor.endEditBlock(); + return count; +} + +bool BaseTextFind::find(const QString &txt, FindFlags findFlags, + QTextCursor start, bool *wrapped) +{ + if (txt.isEmpty()) { + setTextCursor(start); + return true; + } + QRegExp regexp(txt); + regexp.setPatternSyntax((findFlags & FindRegularExpression) ? QRegExp::RegExp : QRegExp::FixedString); + regexp.setCaseSensitivity((findFlags & FindCaseSensitively) ? Qt::CaseSensitive : Qt::CaseInsensitive); + QTextCursor found = findOne(regexp, start, textDocumentFlagsForFindFlags(findFlags)); + if (wrapped) + *wrapped = false; + + if (!d->m_findScopeStart.isNull()) { + + // scoped + if (found.isNull() || !inScope(found.selectionStart(), found.selectionEnd())) { + if ((findFlags & FindBackward) == 0) + start.setPosition(d->m_findScopeStart.position()); + else + start.setPosition(d->m_findScopeEnd.position()); + found = findOne(regexp, start, textDocumentFlagsForFindFlags(findFlags)); + if (found.isNull() || !inScope(found.selectionStart(), found.selectionEnd())) + return false; + if (wrapped) + *wrapped = true; + } + } else { + + // entire document + if (found.isNull()) { + if ((findFlags & FindBackward) == 0) + start.movePosition(QTextCursor::Start); + else + start.movePosition(QTextCursor::End); + found = findOne(regexp, start, textDocumentFlagsForFindFlags(findFlags)); + if (found.isNull()) + return false; + if (wrapped) + *wrapped = true; + } + } + if (!found.isNull()) + setTextCursor(found); + return true; +} + + +// helper function. Works just like QTextDocument::find() but supports vertical block selection +QTextCursor BaseTextFind::findOne(const QRegExp &expr, const QTextCursor &from, QTextDocument::FindFlags options) const +{ + QTextCursor candidate = document()->find(expr, from, options); + if (candidate.isNull()) + return candidate; + + if (d->m_findScopeVerticalBlockSelectionFirstColumn < 0) + return candidate; + forever { + if (!inScope(candidate.selectionStart(), candidate.selectionEnd())) + return candidate; + bool inVerticalFindScope = false; + QMetaObject::invokeMethod(d->m_plaineditor, "inFindScope", Qt::DirectConnection, + Q_RETURN_ARG(bool, inVerticalFindScope), + Q_ARG(QTextCursor, candidate)); + if (inVerticalFindScope) + return candidate; + + QTextCursor newCandidate = document()->find(expr, candidate, options); + if (newCandidate == candidate) { + // When searching for regular expressions that match "zero length" strings (like ^ or \b) + // we need to move away from the match before searching for the next one. + candidate.movePosition(options & QTextDocument::FindBackward + ? QTextCursor::PreviousCharacter + : QTextCursor::NextCharacter); + candidate = document()->find(expr, candidate, options); + } else { + candidate = newCandidate; + } + } + return candidate; +} + +bool BaseTextFind::inScope(int startPosition, int endPosition) const +{ + if (d->m_findScopeStart.isNull()) + return true; + return (d->m_findScopeStart.position() <= startPosition + && d->m_findScopeEnd.position() >= endPosition); +} + +void BaseTextFind::defineFindScope() +{ + QTextCursor cursor = textCursor(); + if (cursor.hasSelection() && cursor.block() != cursor.document()->findBlock(cursor.anchor())) { + d->m_findScopeStart = QTextCursor(document()->docHandle(), qMax(0, cursor.selectionStart())); + d->m_findScopeEnd = QTextCursor(document()->docHandle(), cursor.selectionEnd()); + d->m_findScopeVerticalBlockSelectionFirstColumn = -1; + d->m_findScopeVerticalBlockSelectionLastColumn = -1; + + if (d->m_plaineditor && d->m_plaineditor->metaObject()->indexOfProperty("verticalBlockSelectionFirstColumn") >= 0) { + d->m_findScopeVerticalBlockSelectionFirstColumn + = d->m_plaineditor->property("verticalBlockSelectionFirstColumn").toInt(); + d->m_findScopeVerticalBlockSelectionLastColumn + = d->m_plaineditor->property("verticalBlockSelectionLastColumn").toInt(); + } + + emit findScopeChanged(d->m_findScopeStart, d->m_findScopeEnd, + d->m_findScopeVerticalBlockSelectionFirstColumn, + d->m_findScopeVerticalBlockSelectionLastColumn); + cursor.setPosition(d->m_findScopeStart.position()); + setTextCursor(cursor); + } else { + clearFindScope(); + } +} + +void BaseTextFind::clearFindScope() +{ + d->m_findScopeStart = QTextCursor(); + d->m_findScopeEnd = QTextCursor(); + d->m_findScopeVerticalBlockSelectionFirstColumn = -1; + d->m_findScopeVerticalBlockSelectionLastColumn = -1; + emit findScopeChanged(d->m_findScopeStart, d->m_findScopeEnd, + d->m_findScopeVerticalBlockSelectionFirstColumn, + d->m_findScopeVerticalBlockSelectionLastColumn); +} + +} // namespace Core diff --git a/src/plugins/coreplugin/find/basetextfind.h b/src/plugins/coreplugin/find/basetextfind.h new file mode 100644 index 0000000000..1d1d54d9ef --- /dev/null +++ b/src/plugins/coreplugin/find/basetextfind.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef BASETEXTFIND_H +#define BASETEXTFIND_H + +#include "ifindsupport.h" + +QT_BEGIN_NAMESPACE +class QPlainTextEdit; +class QTextEdit; +class QTextCursor; +QT_END_NAMESPACE + +namespace Core { +struct BaseTextFindPrivate; + +class CORE_EXPORT BaseTextFind : public IFindSupport +{ + Q_OBJECT + +public: + explicit BaseTextFind(QPlainTextEdit *editor); + explicit BaseTextFind(QTextEdit *editor); + virtual ~BaseTextFind(); + + bool supportsReplace() const; + FindFlags supportedFindFlags() const; + void resetIncrementalSearch(); + void clearResults(); + QString currentFindString() const; + QString completedFindString() const; + + Result findIncremental(const QString &txt, FindFlags findFlags); + Result findStep(const QString &txt, FindFlags findFlags); + void replace(const QString &before, const QString &after, FindFlags findFlags); + bool replaceStep(const QString &before, const QString &after, FindFlags findFlags); + int replaceAll(const QString &before, const QString &after, FindFlags findFlags); + + void defineFindScope(); + void clearFindScope(); + +signals: + void highlightAll(const QString &txt, Core::FindFlags findFlags); + void findScopeChanged(const QTextCursor &start, const QTextCursor &end, + int verticalBlockSelectionFirstColumn, + int verticalBlockSelectionLastColumn); + +private: + bool find(const QString &txt, FindFlags findFlags, QTextCursor start, bool *wrapped); + QTextCursor replaceInternal(const QString &before, const QString &after, FindFlags findFlags); + + QTextCursor textCursor() const; + void setTextCursor(const QTextCursor&); + QTextDocument *document() const; + bool isReadOnly() const; + bool inScope(int startPosition, int endPosition) const; + QTextCursor findOne(const QRegExp &expr, const QTextCursor &from, QTextDocument::FindFlags options) const; + + BaseTextFindPrivate *d; +}; + +} // namespace Core + +#endif // BASETEXTFIND_H diff --git a/src/plugins/coreplugin/find/currentdocumentfind.cpp b/src/plugins/coreplugin/find/currentdocumentfind.cpp new file mode 100644 index 0000000000..854a391003 --- /dev/null +++ b/src/plugins/coreplugin/find/currentdocumentfind.cpp @@ -0,0 +1,259 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "currentdocumentfind.h" + +#include <aggregation/aggregate.h> +#include <coreplugin/coreconstants.h> +#include <utils/qtcassert.h> + +#include <QDebug> +#include <QApplication> +#include <QWidget> + +using namespace Core; +using namespace Core; +using namespace Core::Internal; + +CurrentDocumentFind::CurrentDocumentFind() + : m_currentFind(0) +{ + connect(qApp, SIGNAL(focusChanged(QWidget*,QWidget*)), + this, SLOT(updateCandidateFindFilter(QWidget*,QWidget*))); +} + +void CurrentDocumentFind::removeConnections() +{ + disconnect(qApp, 0, this, 0); + removeFindSupportConnections(); +} + +void CurrentDocumentFind::resetIncrementalSearch() +{ + QTC_ASSERT(m_currentFind, return); + m_currentFind->resetIncrementalSearch(); +} + +void CurrentDocumentFind::clearResults() +{ + QTC_ASSERT(m_currentFind, return); + m_currentFind->clearResults(); +} + +bool CurrentDocumentFind::isEnabled() const +{ + return m_currentFind && (!m_currentWidget || m_currentWidget->isVisible()); +} + +bool CurrentDocumentFind::candidateIsEnabled() const +{ + return (m_candidateFind != 0); +} + +bool CurrentDocumentFind::supportsReplace() const +{ + QTC_ASSERT(m_currentFind, return false); + return m_currentFind->supportsReplace(); +} + +FindFlags CurrentDocumentFind::supportedFindFlags() const +{ + QTC_ASSERT(m_currentFind, return 0); + return m_currentFind->supportedFindFlags(); +} + +QString CurrentDocumentFind::currentFindString() const +{ + QTC_ASSERT(m_currentFind, return QString()); + return m_currentFind->currentFindString(); +} + +QString CurrentDocumentFind::completedFindString() const +{ + QTC_ASSERT(m_currentFind, return QString()); + return m_currentFind->completedFindString(); +} + +void CurrentDocumentFind::highlightAll(const QString &txt, FindFlags findFlags) +{ + QTC_ASSERT(m_currentFind, return); + m_currentFind->highlightAll(txt, findFlags); +} + +IFindSupport::Result CurrentDocumentFind::findIncremental(const QString &txt, FindFlags findFlags) +{ + QTC_ASSERT(m_currentFind, return IFindSupport::NotFound); + return m_currentFind->findIncremental(txt, findFlags); +} + +IFindSupport::Result CurrentDocumentFind::findStep(const QString &txt, FindFlags findFlags) +{ + QTC_ASSERT(m_currentFind, return IFindSupport::NotFound); + return m_currentFind->findStep(txt, findFlags); +} + +void CurrentDocumentFind::replace(const QString &before, const QString &after, FindFlags findFlags) +{ + QTC_ASSERT(m_currentFind, return); + m_currentFind->replace(before, after, findFlags); +} + +bool CurrentDocumentFind::replaceStep(const QString &before, const QString &after, FindFlags findFlags) +{ + QTC_ASSERT(m_currentFind, return false); + return m_currentFind->replaceStep(before, after, findFlags); +} + +int CurrentDocumentFind::replaceAll(const QString &before, const QString &after, FindFlags findFlags) +{ + QTC_ASSERT(m_currentFind, return 0); + return m_currentFind->replaceAll(before, after, findFlags); +} + +void CurrentDocumentFind::defineFindScope() +{ + QTC_ASSERT(m_currentFind, return); + m_currentFind->defineFindScope(); +} + +void CurrentDocumentFind::clearFindScope() +{ + QTC_ASSERT(m_currentFind, return); + m_currentFind->clearFindScope(); +} + +void CurrentDocumentFind::updateCandidateFindFilter(QWidget *old, QWidget *now) +{ + Q_UNUSED(old) + QWidget *candidate = now; + QPointer<IFindSupport> impl = 0; + while (!impl && candidate) { + impl = Aggregation::query<IFindSupport>(candidate); + if (!impl) + candidate = candidate->parentWidget(); + } + if (m_candidateWidget) + disconnect(Aggregation::Aggregate::parentAggregate(m_candidateWidget), SIGNAL(changed()), + this, SLOT(candidateAggregationChanged())); + m_candidateWidget = candidate; + m_candidateFind = impl; + if (m_candidateWidget) + connect(Aggregation::Aggregate::parentAggregate(m_candidateWidget), SIGNAL(changed()), + this, SLOT(candidateAggregationChanged())); + emit candidateChanged(); +} + +void CurrentDocumentFind::acceptCandidate() +{ + if (!m_candidateFind || m_candidateFind == m_currentFind) + return; + removeFindSupportConnections(); + if (m_currentFind) + m_currentFind->clearResults(); + + if (m_currentWidget) + disconnect(Aggregation::Aggregate::parentAggregate(m_currentWidget), SIGNAL(changed()), + this, SLOT(aggregationChanged())); + m_currentWidget = m_candidateWidget; + connect(Aggregation::Aggregate::parentAggregate(m_currentWidget), SIGNAL(changed()), + this, SLOT(aggregationChanged())); + + m_currentFind = m_candidateFind; + if (m_currentFind) { + connect(m_currentFind, SIGNAL(changed()), this, SIGNAL(changed())); + connect(m_currentFind, SIGNAL(destroyed(QObject*)), SLOT(clearFindSupport())); + } + if (m_currentWidget) + m_currentWidget->installEventFilter(this); + emit changed(); +} + +void CurrentDocumentFind::removeFindSupportConnections() +{ + if (m_currentFind) { + disconnect(m_currentFind, SIGNAL(changed()), this, SIGNAL(changed())); + disconnect(m_currentFind, SIGNAL(destroyed(QObject*)), this, SLOT(clearFindSupport())); + } + if (m_currentWidget) + m_currentWidget->removeEventFilter(this); +} + +void CurrentDocumentFind::clearFindSupport() +{ + removeFindSupportConnections(); + m_currentWidget = 0; + m_currentFind = 0; + emit changed(); +} + +bool CurrentDocumentFind::setFocusToCurrentFindSupport() +{ + if (m_currentFind && m_currentWidget) { + QWidget *w = m_currentWidget->focusWidget(); + if (!w) + w = m_currentWidget; + w->setFocus(); + return true; + } + return false; +} + +bool CurrentDocumentFind::eventFilter(QObject *obj, QEvent *event) +{ + if (m_currentWidget && obj == m_currentWidget) { + if (event->type() == QEvent::Hide || event->type() == QEvent::Show) + emit changed(); + } + return QObject::eventFilter(obj, event); +} + +void CurrentDocumentFind::aggregationChanged() +{ + if (m_currentWidget) { + QPointer<IFindSupport> currentFind = Aggregation::query<IFindSupport>(m_currentWidget); + if (currentFind != m_currentFind) { + // There's a change in the find support + if (currentFind) { + m_candidateWidget = m_currentWidget; + m_candidateFind = currentFind; + acceptCandidate(); + } else { + clearFindSupport(); + } + } + } +} + +void CurrentDocumentFind::candidateAggregationChanged() +{ + if (m_candidateWidget && m_candidateWidget != m_currentWidget) { + m_candidateFind = Aggregation::query<IFindSupport>(m_candidateWidget); + emit candidateChanged(); + } +} diff --git a/src/plugins/coreplugin/find/currentdocumentfind.h b/src/plugins/coreplugin/find/currentdocumentfind.h new file mode 100644 index 0000000000..2b428e0583 --- /dev/null +++ b/src/plugins/coreplugin/find/currentdocumentfind.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef CURRENTDOCUMENTFIND_H +#define CURRENTDOCUMENTFIND_H + +#include "ifindsupport.h" + +#include <QPointer> + +namespace Core { +namespace Internal { + +class CurrentDocumentFind : public QObject +{ + Q_OBJECT + +public: + CurrentDocumentFind(); + + void resetIncrementalSearch(); + void clearResults(); + bool supportsReplace() const; + FindFlags supportedFindFlags() const; + QString currentFindString() const; + QString completedFindString() const; + + bool isEnabled() const; + bool candidateIsEnabled() const; + void highlightAll(const QString &txt, FindFlags findFlags); + IFindSupport::Result findIncremental(const QString &txt, FindFlags findFlags); + IFindSupport::Result findStep(const QString &txt, FindFlags findFlags); + void replace(const QString &before, const QString &after, FindFlags findFlags); + bool replaceStep(const QString &before, const QString &after, FindFlags findFlags); + int replaceAll(const QString &before, const QString &after, FindFlags findFlags); + void defineFindScope(); + void clearFindScope(); + void acceptCandidate(); + + void removeConnections(); + bool setFocusToCurrentFindSupport(); + + bool eventFilter(QObject *obj, QEvent *event); + +signals: + void changed(); + void candidateChanged(); + +private slots: + void updateCandidateFindFilter(QWidget *old, QWidget *now); + void clearFindSupport(); + void aggregationChanged(); + void candidateAggregationChanged(); + +private: + void removeFindSupportConnections(); + + QPointer<IFindSupport> m_currentFind; + QPointer<QWidget> m_currentWidget; + QPointer<IFindSupport> m_candidateFind; + QPointer<QWidget> m_candidateWidget; +}; + +} // namespace Internal +} // namespace Core + +#endif // CURRENTDOCUMENTFIND_H diff --git a/src/plugins/coreplugin/find/find.pri b/src/plugins/coreplugin/find/find.pri new file mode 100644 index 0000000000..c7e8c836bc --- /dev/null +++ b/src/plugins/coreplugin/find/find.pri @@ -0,0 +1,42 @@ +HEADERS += \ + $$PWD/findtoolwindow.h \ + $$PWD/textfindconstants.h \ + $$PWD/ifindsupport.h \ + $$PWD/ifindfilter.h \ + $$PWD/currentdocumentfind.h \ + $$PWD/basetextfind.h \ + $$PWD/findtoolbar.h \ + $$PWD/findplugin.h \ + $$PWD/searchresultcolor.h \ + $$PWD/searchresulttreeitemdelegate.h \ + $$PWD/searchresulttreeitemroles.h \ + $$PWD/searchresulttreeitems.h \ + $$PWD/searchresulttreemodel.h \ + $$PWD/searchresulttreeview.h \ + $$PWD/searchresultwindow.h \ + $$PWD/searchresultwidget.h \ + $$PWD/treeviewfind.h + +SOURCES += \ + $$PWD/findtoolwindow.cpp \ + $$PWD/currentdocumentfind.cpp \ + $$PWD/basetextfind.cpp \ + $$PWD/findtoolbar.cpp \ + $$PWD/findplugin.cpp \ + $$PWD/searchresulttreeitemdelegate.cpp \ + $$PWD/searchresulttreeitems.cpp \ + $$PWD/searchresulttreemodel.cpp \ + $$PWD/searchresulttreeview.cpp \ + $$PWD/searchresultwindow.cpp \ + $$PWD/ifindfilter.cpp \ + $$PWD/ifindsupport.cpp \ + $$PWD/searchresultwidget.cpp \ + $$PWD/treeviewfind.cpp + +FORMS += \ + $$PWD/findwidget.ui \ + $$PWD/finddialog.ui + +RESOURCES += \ + $$PWD/find.qrc + diff --git a/src/plugins/coreplugin/find/find.qrc b/src/plugins/coreplugin/find/find.qrc new file mode 100644 index 0000000000..0c4e128101 --- /dev/null +++ b/src/plugins/coreplugin/find/find.qrc @@ -0,0 +1,10 @@ +<RCC> + <qresource prefix="/find"> + <file>images/casesensitively.png</file> + <file>images/wholewords.png</file> + <file>images/regexp.png</file> + <file>images/expand.png</file> + <file>images/wrapindicator.png</file> + <file>images/preservecase.png</file> + </qresource> +</RCC> diff --git a/src/plugins/coreplugin/find/finddialog.ui b/src/plugins/coreplugin/find/finddialog.ui new file mode 100644 index 0000000000..19d41c938c --- /dev/null +++ b/src/plugins/coreplugin/find/finddialog.ui @@ -0,0 +1,209 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Core::Internal::FindDialog</class> + <widget class="QWidget" name="Core::Internal::FindDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>673</width> + <height>240</height> + </rect> + </property> + <property name="maximumSize"> + <size> + <width>680</width> + <height>16777215</height> + </size> + </property> + <layout class="QGridLayout" name="gridLayout"> + <property name="sizeConstraint"> + <enum>QLayout::SetFixedSize</enum> + </property> + <item row="4" column="0" colspan="2"> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="searchButton"> + <property name="text"> + <string>&Search</string> + </property> + <property name="default"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="replaceButton"> + <property name="text"> + <string>Search && &Replace</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Sear&ch for:</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="buddy"> + <cstring>searchTerm</cstring> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QWidget" name="widget_2" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <property name="spacing"> + <number>0</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item> + <widget class="QComboBox" name="filterList"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <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 row="2" column="1"> + <widget class="QWidget" name="widget" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="margin"> + <number>0</number> + </property> + <item> + <widget class="QCheckBox" name="matchCase"> + <property name="text"> + <string>Case sensiti&ve</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="wholeWords"> + <property name="text"> + <string>Whole words o&nly</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="regExp"> + <property name="text"> + <string>Use re&gular expressions</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <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 row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>80</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>Sco&pe:</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="buddy"> + <cstring>filterList</cstring> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="Utils::FilterLineEdit" name="searchTerm"/> + </item> + <item row="3" column="0" colspan="2"> + <widget class="QWidget" name="configWidget" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>10</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>Utils::FancyLineEdit</class> + <extends>QLineEdit</extends> + <header location="global">utils/fancylineedit.h</header> + </customwidget> + <customwidget> + <class>Utils::FilterLineEdit</class> + <extends>Utils::FancyLineEdit</extends> + <header location="global">utils/filterlineedit.h</header> + </customwidget> + </customwidgets> + <tabstops> + <tabstop>filterList</tabstop> + <tabstop>searchTerm</tabstop> + <tabstop>matchCase</tabstop> + <tabstop>wholeWords</tabstop> + <tabstop>regExp</tabstop> + </tabstops> + <resources/> + <connections/> +</ui> diff --git a/src/plugins/coreplugin/find/findplugin.cpp b/src/plugins/coreplugin/find/findplugin.cpp new file mode 100644 index 0000000000..ebb9f4b183 --- /dev/null +++ b/src/plugins/coreplugin/find/findplugin.cpp @@ -0,0 +1,394 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "findplugin.h" + +#include "currentdocumentfind.h" +#include "findtoolbar.h" +#include "findtoolwindow.h" +#include "searchresultwindow.h" +#include "ifindfilter.h" + +#include <coreplugin/actionmanager/actionmanager.h> +#include <coreplugin/actionmanager/actioncontainer.h> +#include <coreplugin/coreconstants.h> +#include <coreplugin/icore.h> +#include <coreplugin/id.h> +#include <coreplugin/coreplugin.h> + +#include <extensionsystem/pluginmanager.h> + +#include <utils/qtcassert.h> + +#include <QMenu> +#include <QStringListModel> +#include <QAction> + +#include <QtPlugin> +#include <QSettings> + +/*! + \namespace Core::Internal + \internal +*/ +/*! + \namespace Core::Internal::ItemDataRoles + \internal +*/ + +Q_DECLARE_METATYPE(Core::IFindFilter*) + +namespace { + const int MAX_COMPLETIONS = 50; +} + +namespace Core { + +class FindPluginPrivate { +public: + explicit FindPluginPrivate(FindPlugin *q); + + //variables + static FindPlugin *m_instance; + + QHash<IFindFilter *, QAction *> m_filterActions; + + Internal::CurrentDocumentFind *m_currentDocumentFind; + Internal::FindToolBar *m_findToolBar; + Internal::FindToolWindow *m_findDialog; + SearchResultWindow *m_searchResultWindow; + FindFlags m_findFlags; + QStringListModel *m_findCompletionModel; + QStringListModel *m_replaceCompletionModel; + QStringList m_findCompletions; + QStringList m_replaceCompletions; + QAction *m_openFindDialog; +}; + +FindPluginPrivate::FindPluginPrivate(FindPlugin *q) : + m_currentDocumentFind(0), m_findToolBar(0), m_findDialog(0), + m_findCompletionModel(new QStringListModel(q)), + m_replaceCompletionModel(new QStringListModel(q)) +{ +} + +FindPlugin *FindPluginPrivate::m_instance = 0; + +FindPlugin::FindPlugin() : d(new FindPluginPrivate(this)) +{ + QTC_ASSERT(!FindPluginPrivate::m_instance, return); + FindPluginPrivate::m_instance = this; +} + +FindPlugin::~FindPlugin() +{ + FindPluginPrivate::m_instance = 0; + delete d->m_currentDocumentFind; + delete d->m_findToolBar; + delete d->m_findDialog; + ExtensionSystem::PluginManager::removeObject(d->m_searchResultWindow); + delete d->m_searchResultWindow; + delete d; +} + +FindPlugin *FindPlugin::instance() +{ + return FindPluginPrivate::m_instance; +} + +void FindPlugin::initialize(const QStringList &, QString *) +{ + setupMenu(); + + d->m_currentDocumentFind = new Internal::CurrentDocumentFind; + + d->m_findToolBar = new Internal::FindToolBar(this, d->m_currentDocumentFind); + d->m_findDialog = new Internal::FindToolWindow(this); + d->m_searchResultWindow = new SearchResultWindow(d->m_findDialog); + ExtensionSystem::PluginManager::addObject(d->m_searchResultWindow); +} + +void FindPlugin::extensionsInitialized() +{ + setupFilterMenuItems(); + readSettings(); +} + +void FindPlugin::aboutToShutdown() +{ + d->m_findToolBar->setVisible(false); + d->m_findToolBar->setParent(0); + d->m_currentDocumentFind->removeConnections(); + writeSettings(); +} + +void FindPlugin::filterChanged() +{ + IFindFilter *changedFilter = qobject_cast<IFindFilter *>(sender()); + QAction *action = d->m_filterActions.value(changedFilter); + QTC_ASSERT(changedFilter, return); + QTC_ASSERT(action, return); + action->setEnabled(changedFilter->isEnabled()); + bool haveEnabledFilters = false; + foreach (const IFindFilter *filter, d->m_filterActions.keys()) { + if (filter->isEnabled()) { + haveEnabledFilters = true; + break; + } + } + d->m_openFindDialog->setEnabled(haveEnabledFilters); +} + +void FindPlugin::openFindFilter() +{ + QAction *action = qobject_cast<QAction*>(sender()); + QTC_ASSERT(action, return); + IFindFilter *filter = action->data().value<IFindFilter *>(); + openFindDialog(filter); +} + +void FindPlugin::openFindDialog(IFindFilter *filter) +{ + if (d->m_currentDocumentFind->candidateIsEnabled()) + d->m_currentDocumentFind->acceptCandidate(); + const QString currentFindString = + d->m_currentDocumentFind->isEnabled() ? + d->m_currentDocumentFind->currentFindString() : QString(); + if (!currentFindString.isEmpty()) + d->m_findDialog->setFindText(currentFindString); + d->m_findDialog->setCurrentFilter(filter); + SearchResultWindow::instance()->openNewSearchPanel(); +} + +void FindPlugin::setupMenu() +{ + Core::ActionContainer *medit = Core::ActionManager::actionContainer(Core::Constants::M_EDIT); + Core::ActionContainer *mfind = Core::ActionManager::createMenu(Constants::M_FIND); + medit->addMenu(mfind, Core::Constants::G_EDIT_FIND); + mfind->menu()->setTitle(tr("&Find/Replace")); + mfind->appendGroup(Constants::G_FIND_CURRENTDOCUMENT); + mfind->appendGroup(Constants::G_FIND_FILTERS); + mfind->appendGroup(Constants::G_FIND_FLAGS); + mfind->appendGroup(Constants::G_FIND_ACTIONS); + Core::Context globalcontext(Core::Constants::C_GLOBAL); + Core::Command *cmd; + mfind->addSeparator(globalcontext, Constants::G_FIND_FLAGS); + mfind->addSeparator(globalcontext, Constants::G_FIND_ACTIONS); + + Core::ActionContainer *mfindadvanced = Core::ActionManager::createMenu(Constants::M_FIND_ADVANCED); + mfindadvanced->menu()->setTitle(tr("Advanced Find")); + mfind->addMenu(mfindadvanced, Constants::G_FIND_FILTERS); + d->m_openFindDialog = new QAction(tr("Open Advanced Find..."), this); + d->m_openFindDialog->setIconText(tr("Advanced...")); + cmd = Core::ActionManager::registerAction(d->m_openFindDialog, Constants::ADVANCED_FIND, globalcontext); + cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+Shift+F"))); + mfindadvanced->addAction(cmd); + connect(d->m_openFindDialog, SIGNAL(triggered()), this, SLOT(openFindFilter())); +} + +void FindPlugin::setupFilterMenuItems() +{ + QList<IFindFilter*> findInterfaces = + ExtensionSystem::PluginManager::getObjects<IFindFilter>(); + Core::Command *cmd; + Core::Context globalcontext(Core::Constants::C_GLOBAL); + + Core::ActionContainer *mfindadvanced = Core::ActionManager::actionContainer(Constants::M_FIND_ADVANCED); + d->m_filterActions.clear(); + bool haveEnabledFilters = false; + const Core::Id base("FindFilter."); + foreach (IFindFilter *filter, findInterfaces) { + QAction *action = new QAction(QLatin1String(" ") + filter->displayName(), this); + bool isEnabled = filter->isEnabled(); + if (isEnabled) + haveEnabledFilters = true; + action->setEnabled(isEnabled); + action->setData(qVariantFromValue(filter)); + cmd = Core::ActionManager::registerAction(action, + base.withSuffix(filter->id()), globalcontext); + cmd->setDefaultKeySequence(filter->defaultShortcut()); + mfindadvanced->addAction(cmd); + d->m_filterActions.insert(filter, action); + connect(action, SIGNAL(triggered(bool)), this, SLOT(openFindFilter())); + connect(filter, SIGNAL(enabledChanged(bool)), this, SLOT(filterChanged())); + } + d->m_findDialog->setFindFilters(findInterfaces); + d->m_openFindDialog->setEnabled(haveEnabledFilters); +} + +FindFlags FindPlugin::findFlags() const +{ + return d->m_findFlags; +} + +void FindPlugin::setCaseSensitive(bool sensitive) +{ + setFindFlag(FindCaseSensitively, sensitive); +} + +void FindPlugin::setWholeWord(bool wholeOnly) +{ + setFindFlag(FindWholeWords, wholeOnly); +} + +void FindPlugin::setBackward(bool backward) +{ + setFindFlag(FindBackward, backward); +} + +void FindPlugin::setRegularExpression(bool regExp) +{ + setFindFlag(FindRegularExpression, regExp); +} + +void FindPlugin::setPreserveCase(bool preserveCase) +{ + setFindFlag(FindPreserveCase, preserveCase); +} + +void FindPlugin::setFindFlag(FindFlag flag, bool enabled) +{ + bool hasFlag = hasFindFlag(flag); + if ((hasFlag && enabled) || (!hasFlag && !enabled)) + return; + if (enabled) + d->m_findFlags |= flag; + else + d->m_findFlags &= ~flag; + if (flag != FindBackward) + emit findFlagsChanged(); +} + +bool FindPlugin::hasFindFlag(FindFlag flag) +{ + return d->m_findFlags & flag; +} + +void FindPlugin::writeSettings() +{ + QSettings *settings = Core::ICore::settings(); + settings->beginGroup(QLatin1String("Find")); + settings->setValue(QLatin1String("Backward"), hasFindFlag(FindBackward)); + settings->setValue(QLatin1String("CaseSensitively"), hasFindFlag(FindCaseSensitively)); + settings->setValue(QLatin1String("WholeWords"), hasFindFlag(FindWholeWords)); + settings->setValue(QLatin1String("RegularExpression"), hasFindFlag(FindRegularExpression)); + settings->setValue(QLatin1String("PreserveCase"), hasFindFlag(FindPreserveCase)); + settings->setValue(QLatin1String("FindStrings"), d->m_findCompletions); + settings->setValue(QLatin1String("ReplaceStrings"), d->m_replaceCompletions); + settings->endGroup(); + d->m_findToolBar->writeSettings(); + d->m_findDialog->writeSettings(); + d->m_searchResultWindow->writeSettings(); +} + +void FindPlugin::readSettings() +{ + QSettings *settings = Core::ICore::settings(); + settings->beginGroup(QLatin1String("Find")); + bool block = blockSignals(true); + setBackward(settings->value(QLatin1String("Backward"), false).toBool()); + setCaseSensitive(settings->value(QLatin1String("CaseSensitively"), false).toBool()); + setWholeWord(settings->value(QLatin1String("WholeWords"), false).toBool()); + setRegularExpression(settings->value(QLatin1String("RegularExpression"), false).toBool()); + setPreserveCase(settings->value(QLatin1String("PreserveCase"), false).toBool()); + blockSignals(block); + d->m_findCompletions = settings->value(QLatin1String("FindStrings")).toStringList(); + d->m_replaceCompletions = settings->value(QLatin1String("ReplaceStrings")).toStringList(); + d->m_findCompletionModel->setStringList(d->m_findCompletions); + d->m_replaceCompletionModel->setStringList(d->m_replaceCompletions); + settings->endGroup(); + d->m_findToolBar->readSettings(); + d->m_findDialog->readSettings(); + emit findFlagsChanged(); // would have been done in the setXXX methods above +} + +void FindPlugin::updateFindCompletion(const QString &text) +{ + updateCompletion(text, d->m_findCompletions, d->m_findCompletionModel); +} + +void FindPlugin::updateReplaceCompletion(const QString &text) +{ + updateCompletion(text, d->m_replaceCompletions, d->m_replaceCompletionModel); +} + +void FindPlugin::updateCompletion(const QString &text, QStringList &completions, QStringListModel *model) +{ + if (text.isEmpty()) + return; + completions.removeAll(text); + completions.prepend(text); + while (completions.size() > MAX_COMPLETIONS) + completions.removeLast(); + model->setStringList(completions); +} + +void FindPlugin::setUseFakeVim(bool on) +{ + if (d->m_findToolBar) + d->m_findToolBar->setUseFakeVim(on); +} + +void FindPlugin::openFindToolBar(FindDirection direction) +{ + if (d->m_findToolBar) { + d->m_findToolBar->setBackward(direction == FindBackwardDirection); + d->m_findToolBar->openFindToolBar(); + } +} + +QStringListModel *FindPlugin::findCompletionModel() const +{ + return d->m_findCompletionModel; +} + +QStringListModel *FindPlugin::replaceCompletionModel() const +{ + return d->m_replaceCompletionModel; +} + +QKeySequence IFindFilter::defaultShortcut() const +{ + return QKeySequence(); +} + +// declared in textfindconstants.h +QTextDocument::FindFlags textDocumentFlagsForFindFlags(FindFlags flags) +{ + QTextDocument::FindFlags textDocFlags; + if (flags & FindBackward) + textDocFlags |= QTextDocument::FindBackward; + if (flags & FindCaseSensitively) + textDocFlags |= QTextDocument::FindCaseSensitively; + if (flags & FindWholeWords) + textDocFlags |= QTextDocument::FindWholeWords; + return textDocFlags; +} + +} // namespace Core diff --git a/src/plugins/coreplugin/find/findplugin.h b/src/plugins/coreplugin/find/findplugin.h new file mode 100644 index 0000000000..0c8298052f --- /dev/null +++ b/src/plugins/coreplugin/find/findplugin.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef FINDPLUGIN_H +#define FINDPLUGIN_H + +#include "textfindconstants.h" + +#include <extensionsystem/iplugin.h> + +QT_BEGIN_NAMESPACE +class QStringListModel; +QT_END_NAMESPACE + +namespace Core { +class IFindFilter; +class FindPluginPrivate; + +namespace Internal { +class CorePlugin; +class FindToolBar; +class CurrentDocumentFind; +} // namespace Internal + +class CORE_EXPORT FindPlugin : public QObject +{ + Q_OBJECT + +public: + FindPlugin(); + virtual ~FindPlugin(); + + static FindPlugin *instance(); + + enum FindDirection { + FindForwardDirection, + FindBackwardDirection + }; + + Core::FindFlags findFlags() const; + bool hasFindFlag(Core::FindFlag flag); + void updateFindCompletion(const QString &text); + void updateReplaceCompletion(const QString &text); + QStringListModel *findCompletionModel() const; + QStringListModel *replaceCompletionModel() const; + void setUseFakeVim(bool on); + void openFindToolBar(FindDirection direction); + void openFindDialog(IFindFilter *filter); + + void initialize(const QStringList &, QString *); + void extensionsInitialized(); + void aboutToShutdown(); + +public slots: + void setCaseSensitive(bool sensitive); + void setWholeWord(bool wholeOnly); + void setBackward(bool backward); + void setRegularExpression(bool regExp); + void setPreserveCase(bool preserveCase); + +signals: + void findFlagsChanged(); + +private slots: + void filterChanged(); + void openFindFilter(); + +private: + void setFindFlag(Core::FindFlag flag, bool enabled); + void updateCompletion(const QString &text, QStringList &completions, QStringListModel *model); + void setupMenu(); + void setupFilterMenuItems(); + void writeSettings(); + void readSettings(); + + //variables + FindPluginPrivate *d; +}; + +} // namespace Core + +#endif // FINDPLUGIN_H diff --git a/src/plugins/coreplugin/find/findtoolbar.cpp b/src/plugins/coreplugin/find/findtoolbar.cpp new file mode 100644 index 0000000000..e686c3fd48 --- /dev/null +++ b/src/plugins/coreplugin/find/findtoolbar.cpp @@ -0,0 +1,779 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "findtoolbar.h" +#include "ifindfilter.h" +#include "findplugin.h" + +#include <coreplugin/coreconstants.h> +#include <coreplugin/coreplugin.h> +#include <coreplugin/icore.h> +#include <coreplugin/actionmanager/actionmanager.h> +#include <coreplugin/actionmanager/actioncontainer.h> +#include <coreplugin/findplaceholder.h> + +#include <extensionsystem/pluginmanager.h> + +#include <utils/hostosinfo.h> +#include <utils/flowlayout.h> + +#include <QDebug> +#include <QSettings> + +#include <QClipboard> +#include <QCompleter> +#include <QKeyEvent> +#include <QMenu> +#include <QPainter> +#include <QStringListModel> + +Q_DECLARE_METATYPE(QStringList) +Q_DECLARE_METATYPE(Core::IFindFilter*) + +using namespace Core; +using namespace Core::Internal; + +FindToolBar::FindToolBar(FindPlugin *plugin, CurrentDocumentFind *currentDocumentFind) + : m_plugin(plugin), + m_currentDocumentFind(currentDocumentFind), + m_findCompleter(new QCompleter(this)), + m_replaceCompleter(new QCompleter(this)), + m_enterFindStringAction(0), + m_findNextAction(0), + m_findPreviousAction(0), + m_replaceAction(0), + m_replaceNextAction(0), + m_replacePreviousAction(0), + m_findIncrementalTimer(this), m_findStepTimer(this), + m_useFakeVim(false), + m_eventFiltersInstalled(false) +{ + //setup ui + m_ui.setupUi(this); + // compensate for a vertically expanding spacer below the label + m_ui.replaceLabel->setMinimumHeight(m_ui.replaceEdit->sizeHint().height()); + delete m_ui.replaceButtonsWidget->layout(); + Utils::FlowLayout *flowlayout = new Utils::FlowLayout(m_ui.replaceButtonsWidget, 0, 3, 3); + flowlayout->addWidget(m_ui.replaceButton); + flowlayout->addWidget(m_ui.replaceNextButton); + flowlayout->addWidget(m_ui.replaceAllButton); + m_ui.replaceButtonsWidget->setLayout(flowlayout); + setFocusProxy(m_ui.findEdit); + setProperty("topBorder", true); + setSingleRow(false); + m_ui.findEdit->setAttribute(Qt::WA_MacShowFocusRect, false); + m_ui.replaceEdit->setAttribute(Qt::WA_MacShowFocusRect, false); + + connect(m_ui.findEdit, SIGNAL(editingFinished()), this, SLOT(invokeResetIncrementalSearch())); + + m_ui.close->setIcon(QIcon(QLatin1String(Core::Constants::ICON_CLOSE_DOCUMENT))); + connect(m_ui.close, SIGNAL(clicked()), this, SLOT(hideAndResetFocus())); + + m_findCompleter->setModel(m_plugin->findCompletionModel()); + m_replaceCompleter->setModel(m_plugin->replaceCompletionModel()); + m_ui.findEdit->setSpecialCompleter(m_findCompleter); + m_ui.replaceEdit->setSpecialCompleter(m_replaceCompleter); + + QMenu *lineEditMenu = new QMenu(m_ui.findEdit); + m_ui.findEdit->setButtonMenu(Utils::FancyLineEdit::Left, lineEditMenu); + m_ui.findEdit->setButtonVisible(Utils::FancyLineEdit::Left, true); + m_ui.findEdit->setPlaceholderText(QString()); + m_ui.replaceEdit->setPlaceholderText(QString()); + + connect(m_ui.findEdit, SIGNAL(textChanged(QString)), this, SLOT(invokeFindIncremental())); + + // invoke{Find,Replace}Helper change the completion model. QueuedConnection is used to perform these + // changes only after the completer's activated() signal is handled (QTCREATORBUG-8408) + connect(m_ui.findEdit, SIGNAL(returnPressed()), this, SLOT(invokeFindEnter()), Qt::QueuedConnection); + connect(m_ui.replaceEdit, SIGNAL(returnPressed()), this, SLOT(invokeReplaceEnter()), Qt::QueuedConnection); + + QAction *shiftEnterAction = new QAction(m_ui.findEdit); + shiftEnterAction->setShortcut(QKeySequence(tr("Shift+Enter"))); + shiftEnterAction->setShortcutContext(Qt::WidgetShortcut); + connect(shiftEnterAction, SIGNAL(triggered()), this, SLOT(invokeFindPrevious())); + m_ui.findEdit->addAction(shiftEnterAction); + QAction *shiftReturnAction = new QAction(m_ui.findEdit); + shiftReturnAction->setShortcut(QKeySequence(tr("Shift+Return"))); + shiftReturnAction->setShortcutContext(Qt::WidgetShortcut); + connect(shiftReturnAction, SIGNAL(triggered()), this, SLOT(invokeFindPrevious())); + m_ui.findEdit->addAction(shiftReturnAction); + + QAction *shiftEnterReplaceAction = new QAction(m_ui.replaceEdit); + shiftEnterReplaceAction->setShortcut(QKeySequence(tr("Shift+Enter"))); + shiftEnterReplaceAction->setShortcutContext(Qt::WidgetShortcut); + connect(shiftEnterReplaceAction, SIGNAL(triggered()), this, SLOT(invokeReplacePrevious())); + m_ui.replaceEdit->addAction(shiftEnterReplaceAction); + QAction *shiftReturnReplaceAction = new QAction(m_ui.replaceEdit); + shiftReturnReplaceAction->setShortcut(QKeySequence(tr("Shift+Return"))); + shiftReturnReplaceAction->setShortcutContext(Qt::WidgetShortcut); + connect(shiftReturnReplaceAction, SIGNAL(triggered()), this, SLOT(invokeReplacePrevious())); + m_ui.replaceEdit->addAction(shiftReturnReplaceAction); + + // need to make sure QStringList is registered as metatype + QMetaTypeId<QStringList>::qt_metatype_id(); + + // register actions + Core::Context globalcontext(Core::Constants::C_GLOBAL); + Core::ActionContainer *mfind = Core::ActionManager::actionContainer(Constants::M_FIND); + Core::Command *cmd; + + m_ui.advancedButton->setDefaultAction(Core::ActionManager::command(Constants::ADVANCED_FIND)->action()); + + QIcon icon = QIcon::fromTheme(QLatin1String("edit-find-replace")); + m_findInDocumentAction = new QAction(icon, tr("Find/Replace"), this); + cmd = Core::ActionManager::registerAction(m_findInDocumentAction, Constants::FIND_IN_DOCUMENT, globalcontext); + cmd->setDefaultKeySequence(QKeySequence::Find); + mfind->addAction(cmd, Constants::G_FIND_CURRENTDOCUMENT); + connect(m_findInDocumentAction, SIGNAL(triggered()), this, SLOT(openFind())); + + if (QApplication::clipboard()->supportsFindBuffer()) { + m_enterFindStringAction = new QAction(tr("Enter Find String"), this); + cmd = Core::ActionManager::registerAction(m_enterFindStringAction, "Find.EnterFindString", globalcontext); + cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+E"))); + mfind->addAction(cmd, Constants::G_FIND_ACTIONS); + connect(m_enterFindStringAction, SIGNAL(triggered()), this, SLOT(putSelectionToFindClipboard())); + connect(QApplication::clipboard(), SIGNAL(findBufferChanged()), this, SLOT(updateFromFindClipboard())); + } + + m_findNextAction = new QAction(tr("Find Next"), this); + cmd = Core::ActionManager::registerAction(m_findNextAction, Constants::FIND_NEXT, globalcontext); + cmd->setDefaultKeySequence(QKeySequence::FindNext); + mfind->addAction(cmd, Constants::G_FIND_ACTIONS); + connect(m_findNextAction, SIGNAL(triggered()), this, SLOT(invokeFindNext())); + m_ui.findNextButton->setDefaultAction(cmd->action()); + + m_findPreviousAction = new QAction(tr("Find Previous"), this); + cmd = Core::ActionManager::registerAction(m_findPreviousAction, Constants::FIND_PREVIOUS, globalcontext); + cmd->setDefaultKeySequence(QKeySequence::FindPrevious); + mfind->addAction(cmd, Constants::G_FIND_ACTIONS); + connect(m_findPreviousAction, SIGNAL(triggered()), this, SLOT(invokeFindPrevious())); + m_ui.findPreviousButton->setDefaultAction(cmd->action()); + + m_findNextSelectedAction = new QAction(tr("Find Next (Selected)"), this); + cmd = Core::ActionManager::registerAction(m_findNextSelectedAction, Constants::FIND_NEXT_SELECTED, globalcontext); + cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+F3"))); + mfind->addAction(cmd, Constants::G_FIND_ACTIONS); + connect(m_findNextSelectedAction, SIGNAL(triggered()), this, SLOT(findNextSelected())); + + m_findPreviousSelectedAction = new QAction(tr("Find Previous (Selected)"), this); + cmd = Core::ActionManager::registerAction(m_findPreviousSelectedAction, Constants::FIND_PREV_SELECTED, globalcontext); + cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+Shift+F3"))); + mfind->addAction(cmd, Constants::G_FIND_ACTIONS); + connect(m_findPreviousSelectedAction, SIGNAL(triggered()), this, SLOT(findPreviousSelected())); + + m_replaceAction = new QAction(tr("Replace"), this); + cmd = Core::ActionManager::registerAction(m_replaceAction, Constants::REPLACE, globalcontext); + cmd->setDefaultKeySequence(QKeySequence()); + mfind->addAction(cmd, Constants::G_FIND_ACTIONS); + connect(m_replaceAction, SIGNAL(triggered()), this, SLOT(invokeReplace())); + m_ui.replaceButton->setDefaultAction(cmd->action()); + + m_replaceNextAction = new QAction(tr("Replace && Find"), this); + m_replaceNextAction->setIconText(tr("Replace && Find")); // work around bug in Qt that kills ampersands in tool button + cmd = Core::ActionManager::registerAction(m_replaceNextAction, Constants::REPLACE_NEXT, globalcontext); + cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+="))); + mfind->addAction(cmd, Constants::G_FIND_ACTIONS); + connect(m_replaceNextAction, SIGNAL(triggered()), this, SLOT(invokeReplaceNext())); + m_ui.replaceNextButton->setDefaultAction(cmd->action()); + + m_replacePreviousAction = new QAction(tr("Replace && Find Previous"), this); + cmd = Core::ActionManager::registerAction(m_replacePreviousAction, Constants::REPLACE_PREVIOUS, globalcontext); + // shortcut removed, clashes with Ctrl++ on many keyboard layouts + //cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+Shift+="))); + mfind->addAction(cmd, Constants::G_FIND_ACTIONS); + connect(m_replacePreviousAction, SIGNAL(triggered()), this, SLOT(invokeReplacePrevious())); + + m_replaceAllAction = new QAction(tr("Replace All"), this); + cmd = Core::ActionManager::registerAction(m_replaceAllAction, Constants::REPLACE_ALL, globalcontext); + mfind->addAction(cmd, Constants::G_FIND_ACTIONS); + connect(m_replaceAllAction, SIGNAL(triggered()), this, SLOT(invokeReplaceAll())); + m_ui.replaceAllButton->setDefaultAction(cmd->action()); + + m_caseSensitiveAction = new QAction(tr("Case Sensitive"), this); + m_caseSensitiveAction->setIcon(QIcon(QLatin1String(":/find/images/casesensitively.png"))); + m_caseSensitiveAction->setCheckable(true); + m_caseSensitiveAction->setChecked(false); + cmd = Core::ActionManager::registerAction(m_caseSensitiveAction, Constants::CASE_SENSITIVE, globalcontext); + mfind->addAction(cmd, Constants::G_FIND_FLAGS); + connect(m_caseSensitiveAction, SIGNAL(triggered(bool)), this, SLOT(setCaseSensitive(bool))); + lineEditMenu->addAction(m_caseSensitiveAction); + + m_wholeWordAction = new QAction(tr("Whole Words Only"), this); + m_wholeWordAction->setIcon(QIcon(QLatin1String(":/find/images/wholewords.png"))); + m_wholeWordAction->setCheckable(true); + m_wholeWordAction->setChecked(false); + cmd = Core::ActionManager::registerAction(m_wholeWordAction, Constants::WHOLE_WORDS, globalcontext); + mfind->addAction(cmd, Constants::G_FIND_FLAGS); + connect(m_wholeWordAction, SIGNAL(triggered(bool)), this, SLOT(setWholeWord(bool))); + lineEditMenu->addAction(m_wholeWordAction); + + m_regularExpressionAction = new QAction(tr("Use Regular Expressions"), this); + m_regularExpressionAction->setIcon(QIcon(QLatin1String(":/find/images/regexp.png"))); + m_regularExpressionAction->setCheckable(true); + m_regularExpressionAction->setChecked(false); + cmd = Core::ActionManager::registerAction(m_regularExpressionAction, Constants::REGULAR_EXPRESSIONS, globalcontext); + mfind->addAction(cmd, Constants::G_FIND_FLAGS); + connect(m_regularExpressionAction, SIGNAL(triggered(bool)), this, SLOT(setRegularExpressions(bool))); + lineEditMenu->addAction(m_regularExpressionAction); + + m_preserveCaseAction = new QAction(tr("Preserve Case when Replacing"), this); + m_preserveCaseAction->setIcon(QPixmap(QLatin1String(":/find/images/preservecase.png"))); + m_preserveCaseAction->setCheckable(true); + m_preserveCaseAction->setChecked(false); + cmd = Core::ActionManager::registerAction(m_preserveCaseAction, Constants::PRESERVE_CASE, globalcontext); + mfind->addAction(cmd, Constants::G_FIND_FLAGS); + connect(m_preserveCaseAction, SIGNAL(triggered(bool)), this, SLOT(setPreserveCase(bool))); + lineEditMenu->addAction(m_preserveCaseAction); + + connect(m_currentDocumentFind, SIGNAL(candidateChanged()), this, SLOT(adaptToCandidate())); + connect(m_currentDocumentFind, SIGNAL(changed()), this, SLOT(updateToolBar())); + updateToolBar(); + + m_findIncrementalTimer.setSingleShot(true); + m_findStepTimer.setSingleShot(true); + connect(&m_findIncrementalTimer, SIGNAL(timeout()), + this, SLOT(invokeFindIncremental())); + connect(&m_findStepTimer, SIGNAL(timeout()), this, SLOT(invokeFindStep())); +} + +FindToolBar::~FindToolBar() +{ +} + +void FindToolBar::installEventFilters() +{ + if (!m_eventFiltersInstalled) { + m_findCompleter->popup()->installEventFilter(this); + m_ui.findEdit->installEventFilter(this); + m_ui.replaceEdit->installEventFilter(this); + this->installEventFilter(this); + m_eventFiltersInstalled = true; + } +} + +bool FindToolBar::shouldSetFocusOnKeyEvent(QKeyEvent *event) +{ + return event->key() == Qt::Key_Escape && !event->modifiers() + && !m_findCompleter->popup()->isVisible() + && !m_replaceCompleter->popup()->isVisible() + && m_currentDocumentFind->isEnabled(); +} + +bool FindToolBar::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::KeyPress) { + QKeyEvent *ke = static_cast<QKeyEvent *>(event); + if (ke->key() == Qt::Key_Down) { + if (obj == m_ui.findEdit) { + if (m_ui.findEdit->text().isEmpty()) + m_findCompleter->setCompletionPrefix(QString()); + m_findCompleter->complete(); + } else if (obj == m_ui.replaceEdit) { + if (m_ui.replaceEdit->text().isEmpty()) + m_replaceCompleter->setCompletionPrefix(QString()); + m_replaceCompleter->complete(); + } + } + } + + if ((obj == m_ui.findEdit || obj == m_findCompleter->popup()) + && event->type() == QEvent::KeyPress) { + QKeyEvent *ke = static_cast<QKeyEvent *>(event); + if (ke->key() == Qt::Key_Space && (ke->modifiers() & Utils::HostOsInfo::controlModifier())) { + QString completedText = m_currentDocumentFind->completedFindString(); + if (!completedText.isEmpty()) { + setFindText(completedText); + ke->accept(); + return true; + } + } + } else if (obj == this && event->type() == QEvent::ShortcutOverride) { + QKeyEvent *ke = static_cast<QKeyEvent *>(event); + if (shouldSetFocusOnKeyEvent(ke)) { + event->accept(); + return true; + } else if (ke->key() == Qt::Key_Space && (ke->modifiers() & Utils::HostOsInfo::controlModifier())) { + event->accept(); + return true; + } + } else if (obj == this && event->type() == QEvent::Hide) { + invokeClearResults(); + if (m_currentDocumentFind->isEnabled()) + m_currentDocumentFind->clearFindScope(); + } + return Utils::StyledBar::eventFilter(obj, event); +} + +void FindToolBar::keyPressEvent(QKeyEvent *event) +{ + if (shouldSetFocusOnKeyEvent(event)) { + if (setFocusToCurrentFindSupport()) + event->accept(); + return; + } + return Utils::StyledBar::keyPressEvent(event); +} + +void FindToolBar::adaptToCandidate() +{ + updateFindAction(); + if (findToolBarPlaceHolder() == Core::FindToolBarPlaceHolder::getCurrent()) + m_currentDocumentFind->acceptCandidate(); +} + +void FindToolBar::updateFindAction() +{ + bool enabled = m_currentDocumentFind->candidateIsEnabled(); + m_findInDocumentAction->setEnabled(enabled); + m_findNextSelectedAction->setEnabled(enabled); + m_findPreviousSelectedAction->setEnabled(enabled); +} + +void FindToolBar::updateToolBar() +{ + bool enabled = m_currentDocumentFind->isEnabled(); + bool replaceEnabled = enabled && m_currentDocumentFind->supportsReplace(); + m_findNextAction->setEnabled(enabled); + m_findPreviousAction->setEnabled(enabled); + + m_replaceAction->setEnabled(replaceEnabled); + m_replaceNextAction->setEnabled(replaceEnabled); + m_replacePreviousAction->setEnabled(replaceEnabled); + m_replaceAllAction->setEnabled(replaceEnabled); + + m_caseSensitiveAction->setEnabled(enabled); + m_wholeWordAction->setEnabled(enabled); + m_regularExpressionAction->setEnabled(enabled); + m_preserveCaseAction->setEnabled(replaceEnabled && !hasFindFlag(FindRegularExpression)); + if (QApplication::clipboard()->supportsFindBuffer()) + m_enterFindStringAction->setEnabled(enabled); + bool replaceFocus = m_ui.replaceEdit->hasFocus(); + m_ui.findEdit->setEnabled(enabled); + m_ui.findLabel->setEnabled(enabled); + + m_ui.replaceEdit->setEnabled(replaceEnabled); + m_ui.replaceLabel->setEnabled(replaceEnabled); + m_ui.replaceEdit->setVisible(replaceEnabled); + m_ui.replaceLabel->setVisible(replaceEnabled); + m_ui.replaceButtonsWidget->setVisible(replaceEnabled); + m_ui.advancedButton->setVisible(replaceEnabled); + layout()->invalidate(); + + if (!replaceEnabled && enabled && replaceFocus) + m_ui.findEdit->setFocus(); + updateIcons(); + updateFlagMenus(); +} + +void FindToolBar::invokeFindEnter() +{ + if (m_currentDocumentFind->isEnabled()) { + if (m_useFakeVim) + setFocusToCurrentFindSupport(); + else + invokeFindNext(); + } +} + +void FindToolBar::invokeReplaceEnter() +{ + if (m_currentDocumentFind->isEnabled() && m_currentDocumentFind->supportsReplace()) + invokeReplaceNext(); +} + +void FindToolBar::invokeClearResults() +{ + if (m_currentDocumentFind->isEnabled()) + m_currentDocumentFind->clearResults(); +} + + +void FindToolBar::invokeFindNext() +{ + setFindFlag(FindBackward, false); + invokeFindStep(); +} + +void FindToolBar::invokeFindPrevious() +{ + setFindFlag(FindBackward, true); + invokeFindStep(); +} + +QString FindToolBar::getFindText() +{ + return m_ui.findEdit->text(); +} + +QString FindToolBar::getReplaceText() +{ + return m_ui.replaceEdit->text(); +} + +void FindToolBar::setFindText(const QString &text) +{ + disconnect(m_ui.findEdit, SIGNAL(textChanged(QString)), this, SLOT(invokeFindIncremental())); + if (hasFindFlag(FindRegularExpression)) + m_ui.findEdit->setText(QRegExp::escape(text)); + else + m_ui.findEdit->setText(text); + connect(m_ui.findEdit, SIGNAL(textChanged(QString)), this, SLOT(invokeFindIncremental())); +} + +void FindToolBar::selectFindText() +{ + m_ui.findEdit->selectAll(); +} + +void FindToolBar::invokeFindStep() +{ + m_findStepTimer.stop(); + m_findIncrementalTimer.stop(); + if (m_currentDocumentFind->isEnabled()) { + m_plugin->updateFindCompletion(getFindText()); + IFindSupport::Result result = + m_currentDocumentFind->findStep(getFindText(), effectiveFindFlags()); + if (result == IFindSupport::NotYetFound) + m_findStepTimer.start(50); + } +} + +void FindToolBar::invokeFindIncremental() +{ + m_findIncrementalTimer.stop(); + m_findStepTimer.stop(); + if (m_currentDocumentFind->isEnabled()) { + QString text = getFindText(); + IFindSupport::Result result = + m_currentDocumentFind->findIncremental(text, effectiveFindFlags()); + if (result == IFindSupport::NotYetFound) + m_findIncrementalTimer.start(50); + if (text.isEmpty()) + m_currentDocumentFind->clearResults(); + } +} + +void FindToolBar::invokeReplace() +{ + setFindFlag(FindBackward, false); + if (m_currentDocumentFind->isEnabled() && m_currentDocumentFind->supportsReplace()) { + m_plugin->updateFindCompletion(getFindText()); + m_plugin->updateReplaceCompletion(getReplaceText()); + m_currentDocumentFind->replace(getFindText(), getReplaceText(), effectiveFindFlags()); + } +} + +void FindToolBar::invokeReplaceNext() +{ + setFindFlag(FindBackward, false); + invokeReplaceStep(); +} + +void FindToolBar::invokeReplacePrevious() +{ + setFindFlag(FindBackward, true); + invokeReplaceStep(); +} + +void FindToolBar::invokeReplaceStep() +{ + if (m_currentDocumentFind->isEnabled() && m_currentDocumentFind->supportsReplace()) { + m_plugin->updateFindCompletion(getFindText()); + m_plugin->updateReplaceCompletion(getReplaceText()); + m_currentDocumentFind->replaceStep(getFindText(), getReplaceText(), effectiveFindFlags()); + } +} + +void FindToolBar::invokeReplaceAll() +{ + m_plugin->updateFindCompletion(getFindText()); + m_plugin->updateReplaceCompletion(getReplaceText()); + if (m_currentDocumentFind->isEnabled() && m_currentDocumentFind->supportsReplace()) + m_currentDocumentFind->replaceAll(getFindText(), getReplaceText(), effectiveFindFlags()); +} + +void FindToolBar::invokeResetIncrementalSearch() +{ + m_findIncrementalTimer.stop(); + m_findStepTimer.stop(); + if (m_currentDocumentFind->isEnabled()) + m_currentDocumentFind->resetIncrementalSearch(); +} + + +void FindToolBar::putSelectionToFindClipboard() +{ + const QString text = m_currentDocumentFind->currentFindString(); + QApplication::clipboard()->setText(text, QClipboard::FindBuffer); + setFindText(text); +} + + +void FindToolBar::updateFromFindClipboard() +{ + if (QApplication::clipboard()->supportsFindBuffer()) { + const bool blocks = m_ui.findEdit->blockSignals(true); + setFindText(QApplication::clipboard()->text(QClipboard::FindBuffer)); + m_ui.findEdit->blockSignals(blocks); + } +} + +void FindToolBar::findFlagsChanged() +{ + updateIcons(); + updateFlagMenus(); + invokeClearResults(); + if (isVisible()) + m_currentDocumentFind->highlightAll(getFindText(), effectiveFindFlags()); +} + +void FindToolBar::updateIcons() +{ + FindFlags effectiveFlags = effectiveFindFlags(); + bool casesensitive = effectiveFlags & FindCaseSensitively; + bool wholewords = effectiveFlags & FindWholeWords; + bool regexp = effectiveFlags & FindRegularExpression; + bool preserveCase = effectiveFlags & FindPreserveCase; + if (!casesensitive && !wholewords && !regexp && !preserveCase) { + QPixmap pixmap(17, 17); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + const QPixmap mag = QPixmap(QLatin1String(Core::Constants::ICON_MAGNIFIER)); + painter.drawPixmap(0, (pixmap.height() - mag.height()) / 2, mag); + m_ui.findEdit->setButtonPixmap(Utils::FancyLineEdit::Left, pixmap); + } else { + m_ui.findEdit->setButtonPixmap(Utils::FancyLineEdit::Left, + IFindFilter::pixmapForFindFlags(effectiveFlags)); + } +} + +FindFlags FindToolBar::effectiveFindFlags() +{ + FindFlags supportedFlags; + bool supportsReplace = true; + if (m_currentDocumentFind->isEnabled()) { + supportedFlags = m_currentDocumentFind->supportedFindFlags(); + supportsReplace = m_currentDocumentFind->supportsReplace(); + } else { + supportedFlags = (FindFlags)0xFFFFFF; + } + if (!supportsReplace || m_findFlags & FindRegularExpression) + supportedFlags &= ~FindPreserveCase; + return supportedFlags & m_findFlags; +} + +void FindToolBar::updateFlagMenus() +{ + bool wholeOnly = ((m_findFlags & FindWholeWords)); + bool sensitive = ((m_findFlags & FindCaseSensitively)); + bool regexp = ((m_findFlags & FindRegularExpression)); + bool preserveCase = ((m_findFlags & FindPreserveCase)); + if (m_wholeWordAction->isChecked() != wholeOnly) + m_wholeWordAction->setChecked(wholeOnly); + if (m_caseSensitiveAction->isChecked() != sensitive) + m_caseSensitiveAction->setChecked(sensitive); + if (m_regularExpressionAction->isChecked() != regexp) + m_regularExpressionAction->setChecked(regexp); + if (m_preserveCaseAction->isChecked() != preserveCase) + m_preserveCaseAction->setChecked(preserveCase); + FindFlags supportedFlags; + if (m_currentDocumentFind->isEnabled()) + supportedFlags = m_currentDocumentFind->supportedFindFlags(); + m_wholeWordAction->setEnabled(supportedFlags & FindWholeWords); + m_caseSensitiveAction->setEnabled(supportedFlags & FindCaseSensitively); + m_regularExpressionAction->setEnabled(supportedFlags & FindRegularExpression); + bool replaceEnabled = m_currentDocumentFind->isEnabled() && m_currentDocumentFind->supportsReplace(); + m_preserveCaseAction->setEnabled((supportedFlags & FindPreserveCase) && !regexp && replaceEnabled); +} + +bool FindToolBar::setFocusToCurrentFindSupport() +{ + return m_currentDocumentFind->setFocusToCurrentFindSupport(); +} + +void FindToolBar::hideAndResetFocus() +{ + m_currentDocumentFind->setFocusToCurrentFindSupport(); + hide(); +} + +Core::FindToolBarPlaceHolder *FindToolBar::findToolBarPlaceHolder() const +{ + QList<Core::FindToolBarPlaceHolder*> placeholders = ExtensionSystem::PluginManager::getObjects<Core::FindToolBarPlaceHolder>(); + QWidget *candidate = QApplication::focusWidget(); + while (candidate) { + foreach (Core::FindToolBarPlaceHolder *ph, placeholders) { + if (ph->owner() == candidate) + return ph; + } + candidate = candidate->parentWidget(); + } + return 0; +} + +void FindToolBar::openFind(bool focus) +{ + setBackward(false); + openFindToolBar(focus); +} + +void FindToolBar::openFindToolBar(bool focus) +{ + installEventFilters(); + if (!m_currentDocumentFind->candidateIsEnabled()) + return; + Core::FindToolBarPlaceHolder *holder = findToolBarPlaceHolder(); + if (!holder) + return; + Core::FindToolBarPlaceHolder *previousHolder = Core::FindToolBarPlaceHolder::getCurrent(); + if (previousHolder) + previousHolder->setWidget(0); + Core::FindToolBarPlaceHolder::setCurrent(holder); + m_currentDocumentFind->acceptCandidate(); + holder->setWidget(this); + holder->setVisible(true); + setVisible(true); + if (focus) + setFocus(); + QString text = m_currentDocumentFind->currentFindString(); + if (!text.isEmpty()) + setFindText(text); + m_currentDocumentFind->defineFindScope(); + m_currentDocumentFind->highlightAll(getFindText(), effectiveFindFlags()); + if (focus) + selectFindText(); +} + +void FindToolBar::findNextSelected() +{ + openFind(false); + invokeFindNext(); +} + +void FindToolBar::findPreviousSelected() +{ + openFind(false); + invokeFindPrevious(); +} + +bool FindToolBar::focusNextPrevChild(bool next) +{ + // close tab order change + if (next && m_ui.replaceAllButton->hasFocus()) + m_ui.findEdit->setFocus(Qt::TabFocusReason); + else if (!next && m_ui.findEdit->hasFocus()) + m_ui.replaceAllButton->setFocus(Qt::TabFocusReason); + else + return Utils::StyledBar::focusNextPrevChild(next); + return true; +} + +void FindToolBar::writeSettings() +{ + QSettings *settings = Core::ICore::settings(); + settings->beginGroup(QLatin1String("Find")); + settings->beginGroup(QLatin1String("FindToolBar")); + settings->setValue(QLatin1String("Backward"), QVariant((m_findFlags & FindBackward) != 0)); + settings->setValue(QLatin1String("CaseSensitively"), QVariant((m_findFlags & FindCaseSensitively) != 0)); + settings->setValue(QLatin1String("WholeWords"), QVariant((m_findFlags & FindWholeWords) != 0)); + settings->setValue(QLatin1String("RegularExpression"), QVariant((m_findFlags & FindRegularExpression) != 0)); + settings->setValue(QLatin1String("PreserveCase"), QVariant((m_findFlags & FindPreserveCase) != 0)); + settings->endGroup(); + settings->endGroup(); +} + +void FindToolBar::readSettings() +{ + QSettings *settings = Core::ICore::settings(); + settings->beginGroup(QLatin1String("Find")); + settings->beginGroup(QLatin1String("FindToolBar")); + FindFlags flags; + if (settings->value(QLatin1String("Backward"), false).toBool()) + flags |= FindBackward; + if (settings->value(QLatin1String("CaseSensitively"), false).toBool()) + flags |= FindCaseSensitively; + if (settings->value(QLatin1String("WholeWords"), false).toBool()) + flags |= FindWholeWords; + if (settings->value(QLatin1String("RegularExpression"), false).toBool()) + flags |= FindRegularExpression; + if (settings->value(QLatin1String("PreserveCase"), false).toBool()) + flags |= FindPreserveCase; + settings->endGroup(); + settings->endGroup(); + m_findFlags = flags; + findFlagsChanged(); +} + +void FindToolBar::setUseFakeVim(bool on) +{ + m_useFakeVim = on; +} + +void FindToolBar::setFindFlag(FindFlag flag, bool enabled) +{ + bool hasFlag = hasFindFlag(flag); + if ((hasFlag && enabled) || (!hasFlag && !enabled)) + return; + if (enabled) + m_findFlags |= flag; + else + m_findFlags &= ~flag; + if (flag != FindBackward) + findFlagsChanged(); +} + +bool FindToolBar::hasFindFlag(FindFlag flag) +{ + return m_findFlags & flag; +} + +void FindToolBar::setCaseSensitive(bool sensitive) +{ + setFindFlag(FindCaseSensitively, sensitive); +} + +void FindToolBar::setWholeWord(bool wholeOnly) +{ + setFindFlag(FindWholeWords, wholeOnly); +} + +void FindToolBar::setRegularExpressions(bool regexp) +{ + setFindFlag(FindRegularExpression, regexp); +} + +void FindToolBar::setPreserveCase(bool preserveCase) +{ + setFindFlag(FindPreserveCase, preserveCase); +} + +void FindToolBar::setBackward(bool backward) +{ + setFindFlag(FindBackward, backward); +} diff --git a/src/plugins/coreplugin/find/findtoolbar.h b/src/plugins/coreplugin/find/findtoolbar.h new file mode 100644 index 0000000000..fb90dfbf1f --- /dev/null +++ b/src/plugins/coreplugin/find/findtoolbar.h @@ -0,0 +1,149 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef FINDTOOLBAR_H +#define FINDTOOLBAR_H + +#include "ui_findwidget.h" +#include "currentdocumentfind.h" + +#include <utils/styledbar.h> + +#include <QTimer> + +namespace Core { + +class FindToolBarPlaceHolder; +class FindPlugin; + +namespace Internal { + +class FindToolBar : public Utils::StyledBar +{ + Q_OBJECT + +public: + explicit FindToolBar(FindPlugin *plugin, CurrentDocumentFind *currentDocumentFind); + ~FindToolBar(); + + void readSettings(); + void writeSettings(); + + void openFindToolBar(bool focus = true); + void setUseFakeVim(bool on); + +public slots: + void setBackward(bool backward); + +private slots: + void invokeFindNext(); + void invokeFindPrevious(); + void invokeFindStep(); + void invokeReplace(); + void invokeReplaceNext(); + void invokeReplacePrevious(); + void invokeReplaceStep(); + void invokeReplaceAll(); + void invokeResetIncrementalSearch(); + + void invokeFindIncremental(); + void invokeFindEnter(); + void invokeReplaceEnter(); + void putSelectionToFindClipboard(); + void updateFromFindClipboard(); + + void hideAndResetFocus(); + void openFind(bool focus = true); + void findNextSelected(); + void findPreviousSelected(); + void updateFindAction(); + void updateToolBar(); + void findFlagsChanged(); + + void setCaseSensitive(bool sensitive); + void setWholeWord(bool wholeOnly); + void setRegularExpressions(bool regexp); + void setPreserveCase(bool preserveCase); + + void adaptToCandidate(); + +protected: + bool focusNextPrevChild(bool next); + void keyPressEvent(QKeyEvent *event); + +private: + void installEventFilters(); + void invokeClearResults(); + bool setFocusToCurrentFindSupport(); + void setFindFlag(FindFlag flag, bool enabled); + bool hasFindFlag(FindFlag flag); + FindFlags effectiveFindFlags(); + Core::FindToolBarPlaceHolder *findToolBarPlaceHolder() const; + + bool eventFilter(QObject *obj, QEvent *event); + void setFindText(const QString &text); + QString getFindText(); + QString getReplaceText(); + void selectFindText(); + void updateIcons(); + void updateFlagMenus(); + + bool shouldSetFocusOnKeyEvent(QKeyEvent *event); + + FindPlugin *m_plugin; + CurrentDocumentFind *m_currentDocumentFind; + Ui::FindWidget m_ui; + QCompleter *m_findCompleter; + QCompleter *m_replaceCompleter; + QAction *m_findInDocumentAction; + QAction *m_findNextSelectedAction; + QAction *m_findPreviousSelectedAction; + QAction *m_enterFindStringAction; + QAction *m_findNextAction; + QAction *m_findPreviousAction; + QAction *m_replaceAction; + QAction *m_replaceNextAction; + QAction *m_replacePreviousAction; + QAction *m_replaceAllAction; + QAction *m_caseSensitiveAction; + QAction *m_wholeWordAction; + QAction *m_regularExpressionAction; + QAction *m_preserveCaseAction; + FindFlags m_findFlags; + + QTimer m_findIncrementalTimer; + QTimer m_findStepTimer; + bool m_useFakeVim; + bool m_eventFiltersInstalled; +}; + +} // namespace Internal +} // namespace Core + +#endif // FINDTOOLBAR_H diff --git a/src/plugins/coreplugin/find/findtoolwindow.cpp b/src/plugins/coreplugin/find/findtoolwindow.cpp new file mode 100644 index 0000000000..66521dc32b --- /dev/null +++ b/src/plugins/coreplugin/find/findtoolwindow.cpp @@ -0,0 +1,264 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "findtoolwindow.h" +#include "ifindfilter.h" +#include "findplugin.h" + +#include <coreplugin/icore.h> + +#include <QSettings> +#include <QStringListModel> +#include <QCompleter> +#include <QKeyEvent> +#include <QScrollArea> + +using namespace Core; +using namespace Core::Internal; + +static FindToolWindow *m_instance = 0; + +FindToolWindow::FindToolWindow(FindPlugin *plugin, QWidget *parent) + : QWidget(parent), + m_plugin(plugin), + m_findCompleter(new QCompleter(this)), + m_currentFilter(0), + m_configWidget(0) +{ + m_instance = this; + m_ui.setupUi(this); + m_ui.searchTerm->setPlaceholderText(QString()); + setFocusProxy(m_ui.searchTerm); + + connect(m_ui.searchButton, SIGNAL(clicked()), this, SLOT(search())); + connect(m_ui.replaceButton, SIGNAL(clicked()), this, SLOT(replace())); + connect(m_ui.matchCase, SIGNAL(toggled(bool)), m_plugin, SLOT(setCaseSensitive(bool))); + connect(m_ui.wholeWords, SIGNAL(toggled(bool)), m_plugin, SLOT(setWholeWord(bool))); + connect(m_ui.regExp, SIGNAL(toggled(bool)), m_plugin, SLOT(setRegularExpression(bool))); + connect(m_ui.filterList, SIGNAL(activated(int)), this, SLOT(setCurrentFilter(int))); + connect(m_ui.searchTerm, SIGNAL(textChanged(QString)), this, SLOT(updateButtonStates())); + + m_findCompleter->setModel(m_plugin->findCompletionModel()); + m_ui.searchTerm->setSpecialCompleter(m_findCompleter); + m_ui.searchTerm->installEventFilter(this); + QVBoxLayout *layout = new QVBoxLayout; + layout->setMargin(0); + layout->setSpacing(0); + m_ui.configWidget->setLayout(layout); + updateButtonStates(); + + connect(m_plugin, SIGNAL(findFlagsChanged()), this, SLOT(updateFindFlags())); +} + +FindToolWindow::~FindToolWindow() +{ + qDeleteAll(m_configWidgets); +} + +FindToolWindow *FindToolWindow::instance() +{ + return m_instance; +} + +bool FindToolWindow::event(QEvent *event) +{ + if (event->type() == QEvent::KeyPress) { + QKeyEvent *ke = static_cast<QKeyEvent *>(event); + if ((ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) + && (ke->modifiers() == Qt::NoModifier || ke->modifiers() == Qt::KeypadModifier)) { + ke->accept(); + search(); + return true; + } + } + return QWidget::event(event); +} + +bool FindToolWindow::eventFilter(QObject *obj, QEvent *event) +{ + if (obj == m_ui.searchTerm && event->type() == QEvent::KeyPress) { + QKeyEvent *ke = static_cast<QKeyEvent *>(event); + if (ke->key() == Qt::Key_Down) { + if (m_ui.searchTerm->text().isEmpty()) + m_findCompleter->setCompletionPrefix(QString()); + m_findCompleter->complete(); + } + } + return QWidget::eventFilter(obj, event); +} + +void FindToolWindow::updateButtonStates() +{ + bool filterEnabled = m_currentFilter && m_currentFilter->isEnabled(); + bool enabled = !m_ui.searchTerm->text().isEmpty() && filterEnabled; + m_ui.searchButton->setEnabled(enabled); + m_ui.replaceButton->setEnabled(m_currentFilter + && m_currentFilter->isReplaceSupported() && enabled); + if (m_configWidget) + m_configWidget->setEnabled(filterEnabled); + + m_ui.matchCase->setEnabled(filterEnabled + && (m_currentFilter->supportedFindFlags() & FindCaseSensitively)); + m_ui.wholeWords->setEnabled(filterEnabled + && (m_currentFilter->supportedFindFlags() & FindWholeWords)); + m_ui.regExp->setEnabled(filterEnabled + && (m_currentFilter->supportedFindFlags() & FindRegularExpression)); + m_ui.searchTerm->setEnabled(filterEnabled); +} + +void FindToolWindow::updateFindFlags() +{ + m_ui.matchCase->setChecked(m_plugin->hasFindFlag(FindCaseSensitively)); + m_ui.wholeWords->setChecked(m_plugin->hasFindFlag(FindWholeWords)); + m_ui.regExp->setChecked(m_plugin->hasFindFlag(FindRegularExpression)); +} + + +void FindToolWindow::setFindFilters(const QList<IFindFilter *> &filters) +{ + qDeleteAll(m_configWidgets); + m_configWidgets.clear(); + m_filters = filters; + m_ui.filterList->clear(); + QStringList names; + foreach (IFindFilter *filter, filters) { + names << filter->displayName(); + m_configWidgets.append(filter->createConfigWidget()); + } + m_ui.filterList->addItems(names); + if (m_filters.size() > 0) + setCurrentFilter(0); +} + +void FindToolWindow::setFindText(const QString &text) +{ + m_ui.searchTerm->setText(text); +} + +void FindToolWindow::setCurrentFilter(IFindFilter *filter) +{ + if (!filter) + filter = m_currentFilter; + int index = m_filters.indexOf(filter); + if (index >= 0) + setCurrentFilter(index); + updateFindFlags(); + m_ui.searchTerm->setFocus(); + m_ui.searchTerm->selectAll(); +} + +void FindToolWindow::setCurrentFilter(int index) +{ + m_ui.filterList->setCurrentIndex(index); + for (int i = 0; i < m_configWidgets.size(); ++i) { + QWidget *configWidget = m_configWidgets.at(i); + if (i == index) { + m_configWidget = configWidget; + if (m_currentFilter) + disconnect(m_currentFilter, SIGNAL(enabledChanged(bool)), this, SLOT(updateButtonStates())); + m_currentFilter = m_filters.at(i); + connect(m_currentFilter, SIGNAL(enabledChanged(bool)), this, SLOT(updateButtonStates())); + updateButtonStates(); + if (m_configWidget) + m_ui.configWidget->layout()->addWidget(m_configWidget); + } else { + if (configWidget) + configWidget->setParent(0); + } + } + QWidget *w = m_ui.configWidget; + while (w) { + QScrollArea *sa = qobject_cast<QScrollArea *>(w); + if (sa) { + sa->updateGeometry(); + break; + } + w = w->parentWidget(); + } + for (w = m_configWidget ? m_configWidget : m_ui.configWidget; w; w = w->parentWidget()) { + if (w->layout()) + w->layout()->activate(); + } +} + +void FindToolWindow::acceptAndGetParameters(QString *term, IFindFilter **filter) +{ + if (filter) + *filter = 0; + m_plugin->updateFindCompletion(m_ui.searchTerm->text()); + int index = m_ui.filterList->currentIndex(); + QString searchTerm = m_ui.searchTerm->text(); + if (term) + *term = searchTerm; + if (searchTerm.isEmpty() || index < 0) + return; + if (filter) + *filter = m_filters.at(index); +} + +void FindToolWindow::search() +{ + QString term; + IFindFilter *filter; + acceptAndGetParameters(&term, &filter); + if (filter) + filter->findAll(term, m_plugin->findFlags()); +} + +void FindToolWindow::replace() +{ + QString term; + IFindFilter *filter; + acceptAndGetParameters(&term, &filter); + filter->replaceAll(term, m_plugin->findFlags()); +} + +void FindToolWindow::writeSettings() +{ + QSettings *settings = Core::ICore::settings(); + settings->beginGroup(QLatin1String("Find")); + settings->setValue(QLatin1String("CurrentFilter"), m_currentFilter ? m_currentFilter->id() : QString()); + foreach (IFindFilter *filter, m_filters) + filter->writeSettings(settings); + settings->endGroup(); +} + +void FindToolWindow::readSettings() +{ + QSettings *settings = Core::ICore::settings(); + settings->beginGroup(QLatin1String("Find")); + const QString currentFilter = settings->value(QLatin1String("CurrentFilter")).toString(); + for (int i = 0; i < m_filters.size(); ++i) { + IFindFilter *filter = m_filters.at(i); + filter->readSettings(settings); + if (filter->id() == currentFilter) + setCurrentFilter(i); + } + settings->endGroup(); +} diff --git a/src/plugins/coreplugin/find/findtoolwindow.h b/src/plugins/coreplugin/find/findtoolwindow.h new file mode 100644 index 0000000000..e78d0075ec --- /dev/null +++ b/src/plugins/coreplugin/find/findtoolwindow.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef FINDTOOLWINDOW_H +#define FINDTOOLWINDOW_H + +#include "ui_finddialog.h" +#include "findplugin.h" + +#include <QList> + +QT_FORWARD_DECLARE_CLASS(QCompleter) + +namespace Core { +class IFindFilter; + +namespace Internal { + +class FindToolWindow : public QWidget +{ + Q_OBJECT + +public: + explicit FindToolWindow(FindPlugin *plugin, QWidget *parent = 0); + ~FindToolWindow(); + static FindToolWindow *instance(); + + void setFindFilters(const QList<IFindFilter *> &filters); + + void setFindText(const QString &text); + void setCurrentFilter(IFindFilter *filter); + void readSettings(); + void writeSettings(); + +protected: + bool event(QEvent *event); + bool eventFilter(QObject *obj, QEvent *event); + +private slots: + void search(); + void replace(); + void setCurrentFilter(int index); + void updateButtonStates(); + void updateFindFlags(); + +private: + void acceptAndGetParameters(QString *term, IFindFilter **filter); + + Ui::FindDialog m_ui; + FindPlugin *m_plugin; + QList<IFindFilter *> m_filters; + QCompleter *m_findCompleter; + QWidgetList m_configWidgets; + IFindFilter *m_currentFilter; + QWidget *m_configWidget; +}; + +} // namespace Internal +} // namespace Core + +#endif // FINDTOOLWINDOW_H diff --git a/src/plugins/coreplugin/find/findwidget.ui b/src/plugins/coreplugin/find/findwidget.ui new file mode 100644 index 0000000000..4472102781 --- /dev/null +++ b/src/plugins/coreplugin/find/findwidget.ui @@ -0,0 +1,292 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Core::Internal::FindWidget</class> + <widget class="QWidget" name="Core::Internal::FindWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>681</width> + <height>88</height> + </rect> + </property> + <property name="windowTitle"> + <string>Find</string> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <property name="leftMargin"> + <number>5</number> + </property> + <property name="topMargin"> + <number>2</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>2</number> + </property> + <property name="horizontalSpacing"> + <number>3</number> + </property> + <property name="verticalSpacing"> + <number>0</number> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="findLabel"> + <property name="text"> + <string>Find:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="Utils::FilterLineEdit" name="findEdit"> + <property name="minimumSize"> + <size> + <width>100</width> + <height>0</height> + </size> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QWidget" name="findButtonsWidget" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <property name="spacing"> + <number>3</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item> + <widget class="QToolButton" name="findPreviousButton"> + <property name="focusPolicy"> + <enum>Qt::NoFocus</enum> + </property> + <property name="arrowType"> + <enum>Qt::LeftArrow</enum> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="findNextButton"> + <property name="font"> + <font/> + </property> + <property name="focusPolicy"> + <enum>Qt::NoFocus</enum> + </property> + <property name="arrowType"> + <enum>Qt::RightArrow</enum> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QToolButton" name="close"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="1" column="0"> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <property name="spacing"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="replaceLabel"> + <property name="text"> + <string>Replace with:</string> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item row="1" column="1"> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="spacing"> + <number>0</number> + </property> + <item> + <widget class="Utils::FilterLineEdit" name="replaceEdit"> + <property name="minimumSize"> + <size> + <width>100</width> + <height>0</height> + </size> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item row="1" column="2"> + <layout class="QGridLayout" name="gridLayout"> + <property name="horizontalSpacing"> + <number>3</number> + </property> + <property name="verticalSpacing"> + <number>0</number> + </property> + <item row="0" column="0"> + <widget class="QWidget" name="replaceButtonsWidget" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <layout class="QHBoxLayout" name="replaceButtonsLayout"> + <property name="spacing"> + <number>3</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item> + <widget class="QToolButton" name="replaceButton"> + <property name="focusPolicy"> + <enum>Qt::NoFocus</enum> + </property> + <property name="text"> + <string>Replace</string> + </property> + <property name="toolButtonStyle"> + <enum>Qt::ToolButtonTextOnly</enum> + </property> + <property name="arrowType"> + <enum>Qt::LeftArrow</enum> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="replaceNextButton"> + <property name="font"> + <font/> + </property> + <property name="focusPolicy"> + <enum>Qt::NoFocus</enum> + </property> + <property name="text"> + <string>Replace && Find</string> + </property> + <property name="toolButtonStyle"> + <enum>Qt::ToolButtonTextOnly</enum> + </property> + <property name="arrowType"> + <enum>Qt::RightArrow</enum> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="replaceAllButton"> + <property name="font"> + <font/> + </property> + <property name="text"> + <string>Replace All</string> + </property> + <property name="toolButtonStyle"> + <enum>Qt::ToolButtonTextOnly</enum> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="0" column="1"> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <property name="spacing"> + <number>0</number> + </property> + <item> + <widget class="QToolButton" name="advancedButton"> + <property name="text"> + <string>Advanced...</string> + </property> + <property name="toolButtonStyle"> + <enum>Qt::ToolButtonTextOnly</enum> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>Utils::FancyLineEdit</class> + <extends>QLineEdit</extends> + <header location="global">utils/fancylineedit.h</header> + </customwidget> + <customwidget> + <class>Utils::FilterLineEdit</class> + <extends>Utils::FancyLineEdit</extends> + <header location="global">utils/filterlineedit.h</header> + </customwidget> + </customwidgets> + <tabstops> + <tabstop>findEdit</tabstop> + <tabstop>replaceEdit</tabstop> + <tabstop>close</tabstop> + <tabstop>replaceAllButton</tabstop> + </tabstops> + <resources/> + <connections/> +</ui> diff --git a/src/plugins/coreplugin/find/ifindfilter.cpp b/src/plugins/coreplugin/find/ifindfilter.cpp new file mode 100644 index 0000000000..c9fda43f2a --- /dev/null +++ b/src/plugins/coreplugin/find/ifindfilter.cpp @@ -0,0 +1,285 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "ifindfilter.h" + +#include <QPainter> +#include <QPixmap> + +/*! + \class Find::IFindFilter + \brief The IFindFilter class is the base class for find implementations + that are invoked by selecting \gui Edit > \gui {Find/Replace} > + \gui {Advanced Find}. + + Implementations of this class add an additional \gui Scope to the \gui {Advanced + Find} dialog. That can be any search that requires the user to provide + a text based search term (potentially with find flags like + searching case sensitively or using regular expressions). Existing + scopes are \gui {All Projects} that searches from all files in all projects + and \gui {Files on File System} where the user provides a directory and file + patterns to search. + + To make your find scope available to the user, you need to implement this + class, and register an instance of your subclass in the plugin manager. + + A common way to present the search results to the user, is to use the + shared \gui{Search Results} panel. + + If you want to implement a find filter that is doing a file based text + search, you should use Find::BaseFileFind, which already implements all + the details for this kind of search, only requiring you to provide an + iterator over the file names of the files that should be searched. + + If you want to implement a more specialized find filter, you need to: + \list + \li Start your search in a separate thread + \li Make this known to the Core::ProgressManager, for a progress bar + and the ability to cancel the search + \li Interface with the shared \gui{Search Results} panel, to show + the search results, handle the event that the user click on one + of the search result items, and possible handle a global replace + of all or some of the search result items. + \endlist + + Luckily QtConcurrent and the search result panel provide the frameworks + that make it relatively easy to implement, + while ensuring a common way for the user. + + The common pattern is roughly this: + + Implement the actual search within a QtConcurrent based function, that is + a function that takes a \c{QFutureInterface<MySearchResult> &future} + as the first parameter and the other information needed for the search + as additional parameters. It should set useful progress information + on the QFutureInterface, regularly check for \c{future.isPaused()} + and \c{future.isCanceled()}, and report the search results + (possibly in chunks) via \c{future.reportResult}. + + In the find filter's find/replaceAll function, get the shared + \gui{Search Results} window, initiate a new search and connect the + signals for handling selection of results and the replace action + (see the Core::SearchResultWindow class for details). + Start your search implementation via the corresponding QtConcurrent + functions. Add the returned QFuture object to the Core::ProgressManager. + Use a QFutureWatcher on the returned QFuture object to receive a signal + when your search implementation reports search results, and add these + to the shared \gui{Search Results} window. +*/ + +/*! + \fn IFindFilter::~IFindFilter() + \internal +*/ + +/*! + \fn QString IFindFilter::id() const + Returns the unique string identifier for this find filter. + + Usually should be something like "MyPlugin.MyFindFilter". +*/ + +/*! + \fn QString IFindFilter::displayName() const + Returns the name of the find filter or scope as presented to the user. + + This is the name that appears in the scope selection combo box, for example. + Always return a translatable string (that is, use tr() for the return value). +*/ + +/*! + \fn bool IFindFilter::isEnabled() const + Returns whether the user should be able to select this find filter + at the moment. + + This is used for the \gui {Current Projects} scope, for example. If the user + has not + opened a project, the scope is disabled. + + \sa changed() +*/ + +/*! + \fn QKeySequence IFindFilter::defaultShortcut() const + Returns the shortcut that can be used to open the advanced find + dialog with this filter or scope preselected. + + Usually return an empty shortcut here, the user can still choose and + assign a specific shortcut to this find scope via the preferences. +*/ + +/*! + \fn bool IFindFilter::isReplaceSupported() const + Returns whether the find filter supports search and replace. + + The default value is false, override this function to return \c true, if + your find filter supports global search and replace. +*/ + +/*! + \fn void IFindFilter::findAll(const QString &txt, Core::FindFlags findFlags) + This function is called when the user selected this find scope and + initiated a search. + + You should start a thread which actually performs the search for \a txt + using the given \a findFlags + (add it to Core::ProgressManager for a progress bar!) and presents the + search results to the user (using the \gui{Search Results} output pane). + For more information, see the descriptions of this class, + Core::ProgressManager, and Core::SearchResultWindow. + + \sa replaceAll() + \sa Core::ProgressManager + \sa Core::SearchResultWindow +*/ + +/*! + \fn void IFindFilter::replaceAll(const QString &txt, Core::FindFlags findFlags) + Override this function if you want to support search and replace. + + This function is called when the user selected this find scope and + initiated a search and replace. + The default implementation does nothing. + + You should start a thread which actually performs the search for \a txt + using the given \a findFlags + (add it to Core::ProgressManager for a progress bar!) and presents the + search results to the user (using the \gui{Search Results} output pane). + For more information see the descriptions of this class, + Core::ProgressManager, and Core::SearchResultWindow. + + \sa findAll() + \sa Core::ProgressManager + \sa Core::SearchResultWindow +*/ + +/*! + \fn QWidget *IFindFilter::createConfigWidget() + Returns a widget that contains additional controls for options + for this find filter. + + The widget will be shown below the common options in the \gui {Advanced Find} + dialog. It will be reparented and deleted by the find plugin. +*/ + +/*! + \fn void IFindFilter::writeSettings(QSettings *settings) + Called at shutdown to write the state of the additional options + for this find filter to the \a settings. +*/ + +/*! + \fn void IFindFilter::readSettings(QSettings *settings) + Called at startup to read the state of the additional options + for this find filter from the \a settings. +*/ + +/*! + \fn void IFindFilter::enabledChanged(bool enabled) + + Signals that the enabled state of this find filter has changed. +*/ + +/*! + \fn Core::FindFlags BaseTextFind::supportedFindFlags() const + Returns the find flags, like whole words or regular expressions, + that this find filter supports. + + Depending on the returned value, the default find option widgets are + enabled or disabled. + The default is Find::FindCaseSensitively, Find::FindRegularExpression + and Find::FindWholeWords +*/ + +namespace Core { + +FindFlags IFindFilter::supportedFindFlags() const +{ + return FindCaseSensitively + | FindRegularExpression | FindWholeWords; +} + +QPixmap IFindFilter::pixmapForFindFlags(FindFlags flags) +{ + static const QPixmap casesensitiveIcon = QPixmap(QLatin1String(":/find/images/casesensitively.png")); + static const QPixmap regexpIcon = QPixmap(QLatin1String(":/find/images/regexp.png")); + static const QPixmap wholewordsIcon = QPixmap(QLatin1String(":/find/images/wholewords.png")); + static const QPixmap preservecaseIcon = QPixmap(QLatin1String(":/find/images/preservecase.png")); + bool casesensitive = flags & FindCaseSensitively; + bool wholewords = flags & FindWholeWords; + bool regexp = flags & FindRegularExpression; + bool preservecase = flags & FindPreserveCase; + int width = 0; + if (casesensitive) width += 6; + if (wholewords) width += 6; + if (regexp) width += 6; + if (preservecase) width += 6; + if (width > 0) --width; + QPixmap pixmap(width, 17); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + int x = 0; + + if (casesensitive) { + painter.drawPixmap(x - 6, 0, casesensitiveIcon); + x += 6; + } + if (wholewords) { + painter.drawPixmap(x - 6, 0, wholewordsIcon); + x += 6; + } + if (regexp) { + painter.drawPixmap(x - 6, 0, regexpIcon); + x += 6; + } + if (preservecase) + painter.drawPixmap(x - 6, 0, preservecaseIcon); + return pixmap; +} + +QString IFindFilter::descriptionForFindFlags(FindFlags flags) +{ + QStringList flagStrings; + if (flags & FindCaseSensitively) + flagStrings.append(tr("Case sensitive")); + if (flags & FindWholeWords) + flagStrings.append(tr("Whole words")); + if (flags & FindRegularExpression) + flagStrings.append(tr("Regular expressions")); + if (flags & FindPreserveCase) + flagStrings.append(tr("Preserve case")); + QString description = tr("Flags: %1"); + if (flagStrings.isEmpty()) + description = description.arg(tr("None")); + else + description = description.arg(flagStrings.join(tr(", "))); + return description; +} + +} // namespace Core diff --git a/src/plugins/coreplugin/find/ifindfilter.h b/src/plugins/coreplugin/find/ifindfilter.h new file mode 100644 index 0000000000..00bb66a04e --- /dev/null +++ b/src/plugins/coreplugin/find/ifindfilter.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef IFINDFILTER_H +#define IFINDFILTER_H + +#include "textfindconstants.h" + +QT_BEGIN_NAMESPACE +class QWidget; +class QSettings; +class QKeySequence; +class Pixmap; +QT_END_NAMESPACE + +namespace Core { + +class CORE_EXPORT IFindFilter : public QObject +{ + Q_OBJECT +public: + + virtual ~IFindFilter() {} + + virtual QString id() const = 0; + virtual QString displayName() const = 0; + /// + virtual bool isEnabled() const = 0; + virtual QKeySequence defaultShortcut() const; + virtual bool isReplaceSupported() const { return false; } + virtual FindFlags supportedFindFlags() const; + + virtual void findAll(const QString &txt, FindFlags findFlags) = 0; + virtual void replaceAll(const QString &txt, FindFlags findFlags) + { Q_UNUSED(txt) Q_UNUSED(findFlags) } + + virtual QWidget *createConfigWidget() { return 0; } + virtual void writeSettings(QSettings *settings) { Q_UNUSED(settings) } + virtual void readSettings(QSettings *settings) { Q_UNUSED(settings) } + + static QPixmap pixmapForFindFlags(FindFlags flags); + static QString descriptionForFindFlags(FindFlags flags); +signals: + void enabledChanged(bool enabled); +}; + +} // namespace Core + +#endif // IFINDFILTER_H diff --git a/src/plugins/coreplugin/find/ifindsupport.cpp b/src/plugins/coreplugin/find/ifindsupport.cpp new file mode 100644 index 0000000000..f057352de6 --- /dev/null +++ b/src/plugins/coreplugin/find/ifindsupport.cpp @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "ifindsupport.h" + +#include <QTimer> +#include <QPropertyAnimation> +#include <QWidget> +#include <QPaintEvent> +#include <QPainter> + +namespace Core { +namespace Internal { + +class WrapIndicator : public QWidget +{ + Q_OBJECT + Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity USER true) + +public: + WrapIndicator(QWidget *parent = 0) + : QWidget(parent), + m_opacity(1.0) + { + if (parent) + setGeometry(QRect(parent->rect().center() - QPoint(25, 25), + parent->rect().center() + QPoint(25, 25))); + } + + qreal opacity() const { return m_opacity; } + void setOpacity(qreal value) { m_opacity = value; update(); } + + void run() + { + show(); + QTimer::singleShot(300, this, SLOT(runInternal())); + } + +protected: + void paintEvent(QPaintEvent *) + { + static QPixmap foreground(QLatin1String(":/find/images/wrapindicator.png")); + QPainter p(this); + p.setOpacity(m_opacity); + p.drawPixmap(rect(), foreground); + } + +private slots: + void runInternal() + { + QPropertyAnimation *anim = new QPropertyAnimation(this, "opacity", this); + anim->setDuration(200); + anim->setEndValue(0.); + connect(anim, SIGNAL(finished()), this, SLOT(deleteLater())); + anim->start(QAbstractAnimation::DeleteWhenStopped); + } + +private: + qreal m_opacity; +}; + +} // Internal +} // Find + +using namespace Core; + +void IFindSupport::replace(const QString &before, const QString &after, FindFlags findFlags) +{ + Q_UNUSED(before) + Q_UNUSED(after) + Q_UNUSED(findFlags) +} + +bool IFindSupport::replaceStep(const QString &before, const QString &after, FindFlags findFlags) +{ + Q_UNUSED(before) + Q_UNUSED(after) + Q_UNUSED(findFlags) + return false; +} + +int IFindSupport::replaceAll(const QString &before, const QString &after, FindFlags findFlags) +{ + Q_UNUSED(before) + Q_UNUSED(after) + Q_UNUSED(findFlags) + return 0; +} + +void IFindSupport::showWrapIndicator(QWidget *parent) +{ + (new Internal::WrapIndicator(parent))->run(); +} + +#include "ifindsupport.moc" diff --git a/src/plugins/coreplugin/find/ifindsupport.h b/src/plugins/coreplugin/find/ifindsupport.h new file mode 100644 index 0000000000..041de57fdd --- /dev/null +++ b/src/plugins/coreplugin/find/ifindsupport.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef IFINDSUPPORT_H +#define IFINDSUPPORT_H + +#include "textfindconstants.h" + +#include <QObject> +#include <QString> + +namespace Core { + +class CORE_EXPORT IFindSupport : public QObject +{ + Q_OBJECT + +public: + enum Result { Found, NotFound, NotYetFound }; + + IFindSupport() : QObject(0) {} + virtual ~IFindSupport() {} + + virtual bool supportsReplace() const = 0; + virtual FindFlags supportedFindFlags() const = 0; + virtual void resetIncrementalSearch() = 0; + virtual void clearResults() = 0; + virtual QString currentFindString() const = 0; + virtual QString completedFindString() const = 0; + + virtual void highlightAll(const QString &txt, FindFlags findFlags); + virtual Result findIncremental(const QString &txt, FindFlags findFlags) = 0; + virtual Result findStep(const QString &txt, FindFlags findFlags) = 0; + virtual void replace(const QString &before, const QString &after, + FindFlags findFlags); + virtual bool replaceStep(const QString &before, const QString &after, + FindFlags findFlags); + virtual int replaceAll(const QString &before, const QString &after, + FindFlags findFlags); + + virtual void defineFindScope(){} + virtual void clearFindScope(){} + + static void showWrapIndicator(QWidget *parent); + +signals: + void changed(); +}; + +inline void IFindSupport::highlightAll(const QString &, FindFlags) {} + +} // namespace Core + +#endif // IFINDSUPPORT_H diff --git a/src/plugins/coreplugin/find/images/all.png b/src/plugins/coreplugin/find/images/all.png Binary files differnew file mode 100644 index 0000000000..f5c1c1f767 --- /dev/null +++ b/src/plugins/coreplugin/find/images/all.png diff --git a/src/plugins/coreplugin/find/images/casesensitively.png b/src/plugins/coreplugin/find/images/casesensitively.png Binary files differnew file mode 100644 index 0000000000..029b41faa4 --- /dev/null +++ b/src/plugins/coreplugin/find/images/casesensitively.png diff --git a/src/plugins/coreplugin/find/images/empty.png b/src/plugins/coreplugin/find/images/empty.png Binary files differnew file mode 100644 index 0000000000..f02154247c --- /dev/null +++ b/src/plugins/coreplugin/find/images/empty.png diff --git a/src/plugins/coreplugin/find/images/expand.png b/src/plugins/coreplugin/find/images/expand.png Binary files differnew file mode 100644 index 0000000000..48fcb9b703 --- /dev/null +++ b/src/plugins/coreplugin/find/images/expand.png diff --git a/src/plugins/coreplugin/find/images/next.png b/src/plugins/coreplugin/find/images/next.png Binary files differnew file mode 100644 index 0000000000..1844929119 --- /dev/null +++ b/src/plugins/coreplugin/find/images/next.png diff --git a/src/plugins/coreplugin/find/images/preservecase.png b/src/plugins/coreplugin/find/images/preservecase.png Binary files differnew file mode 100644 index 0000000000..4869aabd71 --- /dev/null +++ b/src/plugins/coreplugin/find/images/preservecase.png diff --git a/src/plugins/coreplugin/find/images/previous.png b/src/plugins/coreplugin/find/images/previous.png Binary files differnew file mode 100644 index 0000000000..4fe50af9a8 --- /dev/null +++ b/src/plugins/coreplugin/find/images/previous.png diff --git a/src/plugins/coreplugin/find/images/regexp.png b/src/plugins/coreplugin/find/images/regexp.png Binary files differnew file mode 100644 index 0000000000..be8a5cc48c --- /dev/null +++ b/src/plugins/coreplugin/find/images/regexp.png diff --git a/src/plugins/coreplugin/find/images/replace_all.png b/src/plugins/coreplugin/find/images/replace_all.png Binary files differnew file mode 100644 index 0000000000..d2298a8aad --- /dev/null +++ b/src/plugins/coreplugin/find/images/replace_all.png diff --git a/src/plugins/coreplugin/find/images/wholewords.png b/src/plugins/coreplugin/find/images/wholewords.png Binary files differnew file mode 100644 index 0000000000..0ffcecd963 --- /dev/null +++ b/src/plugins/coreplugin/find/images/wholewords.png diff --git a/src/plugins/coreplugin/find/images/wordandcase.png b/src/plugins/coreplugin/find/images/wordandcase.png Binary files differnew file mode 100644 index 0000000000..34c0ac3190 --- /dev/null +++ b/src/plugins/coreplugin/find/images/wordandcase.png diff --git a/src/plugins/coreplugin/find/images/wrapindicator.png b/src/plugins/coreplugin/find/images/wrapindicator.png Binary files differnew file mode 100644 index 0000000000..a4f8ddf417 --- /dev/null +++ b/src/plugins/coreplugin/find/images/wrapindicator.png diff --git a/src/plugins/coreplugin/find/searchresultcolor.h b/src/plugins/coreplugin/find/searchresultcolor.h new file mode 100644 index 0000000000..0547f07749 --- /dev/null +++ b/src/plugins/coreplugin/find/searchresultcolor.h @@ -0,0 +1,20 @@ +#ifndef SEARCHRESULTCOLOR_H +#define SEARCHRESULTCOLOR_H + +#include <QColor> + +namespace Core { +namespace Internal { + +class SearchResultColor{ +public: + QColor textBackground; + QColor textForeground; + QColor highlightBackground; + QColor highlightForeground; +}; + +} // namespace Internal +} // namespace Core + +#endif // SEARCHRESULTCOLOR_H diff --git a/src/plugins/coreplugin/find/searchresulttreeitemdelegate.cpp b/src/plugins/coreplugin/find/searchresulttreeitemdelegate.cpp new file mode 100644 index 0000000000..949a298f62 --- /dev/null +++ b/src/plugins/coreplugin/find/searchresulttreeitemdelegate.cpp @@ -0,0 +1,224 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "searchresulttreeitemdelegate.h" +#include "searchresulttreeitemroles.h" + +#include <QPainter> +#include <QApplication> + +#include <QModelIndex> +#include <QDebug> + +using namespace Core::Internal; + +SearchResultTreeItemDelegate::SearchResultTreeItemDelegate(QObject *parent) + : QItemDelegate(parent) +{ +} + +void SearchResultTreeItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + static const int iconSize = 16; + + painter->save(); + + QStyleOptionViewItemV3 opt = setOptions(index, option); + painter->setFont(opt.font); + + QItemDelegate::drawBackground(painter, opt, index); + + // ---- do the layout + QRect checkRect; + QRect pixmapRect; + QRect textRect; + + // check mark + bool checkable = (index.model()->flags(index) & Qt::ItemIsUserCheckable); + Qt::CheckState checkState = Qt::Unchecked; + if (checkable) { + QVariant checkStateData = index.data(Qt::CheckStateRole); + checkState = static_cast<Qt::CheckState>(checkStateData.toInt()); +#if QT_VERSION >= 0x050000 + checkRect = doCheck(opt, opt.rect, checkStateData); +#else // Qt4 + checkRect = check(opt, opt.rect, checkStateData); +#endif + } + + // icon + QIcon icon = index.model()->data(index, ItemDataRoles::ResultIconRole).value<QIcon>(); + if (!icon.isNull()) + pixmapRect = QRect(0, 0, iconSize, iconSize); + + // text + textRect = opt.rect.adjusted(0, 0, checkRect.width() + pixmapRect.width(), 0); + + // do layout + doLayout(opt, &checkRect, &pixmapRect, &textRect, false); + + // ---- draw the items + // icon + if (!icon.isNull()) + QItemDelegate::drawDecoration(painter, opt, pixmapRect, icon.pixmap(iconSize)); + + // line numbers + int lineNumberAreaWidth = drawLineNumber(painter, opt, textRect, index); + textRect.adjust(lineNumberAreaWidth, 0, 0, 0); + + // text and focus/selection + drawText(painter, opt, textRect, index); + QItemDelegate::drawFocus(painter, opt, opt.rect); + + // check mark + if (checkable) + QItemDelegate::drawCheck(painter, opt, checkRect, checkState); + + painter->restore(); +} + +// returns the width of the line number area +int SearchResultTreeItemDelegate::drawLineNumber(QPainter *painter, const QStyleOptionViewItemV3 &option, + const QRect &rect, + const QModelIndex &index) const +{ + static const int lineNumberAreaHorizontalPadding = 4; + int lineNumber = index.model()->data(index, ItemDataRoles::ResultLineNumberRole).toInt(); + if (lineNumber < 1) + return 0; + const bool isSelected = option.state & QStyle::State_Selected; + QString lineText = QString::number(lineNumber); + int minimumLineNumberDigits = qMax((int)m_minimumLineNumberDigits, lineText.count()); + int fontWidth = painter->fontMetrics().width(QString(minimumLineNumberDigits, QLatin1Char('0'))); + int lineNumberAreaWidth = lineNumberAreaHorizontalPadding + fontWidth + lineNumberAreaHorizontalPadding; + QRect lineNumberAreaRect(rect); + lineNumberAreaRect.setWidth(lineNumberAreaWidth); + + QPalette::ColorGroup cg = QPalette::Normal; + if (!(option.state & QStyle::State_Active)) + cg = QPalette::Inactive; + else if (!(option.state & QStyle::State_Enabled)) + cg = QPalette::Disabled; + + painter->fillRect(lineNumberAreaRect, QBrush(isSelected ? + option.palette.brush(cg, QPalette::Highlight) : + option.palette.color(cg, QPalette::Base).darker(111))); + + QStyleOptionViewItemV3 opt = option; + opt.displayAlignment = Qt::AlignRight | Qt::AlignVCenter; + opt.palette.setColor(cg, QPalette::Text, Qt::darkGray); + + const QStyle *style = QApplication::style(); + const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, 0) + 1; + + const QRect rowRect = lineNumberAreaRect.adjusted(-textMargin, 0, textMargin-lineNumberAreaHorizontalPadding, 0); + QItemDelegate::drawDisplay(painter, opt, rowRect, lineText); + + return lineNumberAreaWidth; +} + +void SearchResultTreeItemDelegate::drawText(QPainter *painter, + const QStyleOptionViewItem &option, + const QRect &rect, + const QModelIndex &index) const +{ + QString text = index.model()->data(index, Qt::DisplayRole).toString(); + // show number of subresults in displayString + if (index.model()->hasChildren(index)) { + text += QLatin1String(" (") + + QString::number(index.model()->rowCount(index)) + + QLatin1Char(')'); + } + + const int searchTermStart = index.model()->data(index, ItemDataRoles::SearchTermStartRole).toInt(); + int searchTermLength = index.model()->data(index, ItemDataRoles::SearchTermLengthRole).toInt(); + if (searchTermStart < 0 || searchTermStart >= text.length() || searchTermLength < 1) { + QItemDelegate::drawDisplay(painter, option, rect, text); + return; + } + // clip searchTermLength to end of line + searchTermLength = qMin(searchTermLength, text.length() - searchTermStart); + const int textMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; + int searchTermStartPixels = painter->fontMetrics().width(text.left(searchTermStart)); + int searchTermLengthPixels = painter->fontMetrics().width(text.mid(searchTermStart, searchTermLength)); + + // rects + QRect beforeHighlightRect(rect); + beforeHighlightRect.setRight(beforeHighlightRect.left() + searchTermStartPixels); + + QRect resultHighlightRect(rect); + resultHighlightRect.setLeft(beforeHighlightRect.right()); + resultHighlightRect.setRight(resultHighlightRect.left() + searchTermLengthPixels); + + QRect afterHighlightRect(rect); + afterHighlightRect.setLeft(resultHighlightRect.right()); + + // paint all highlight backgrounds + // qitemdelegate has problems with painting background when highlighted + // (highlighted background at wrong position because text is offset with textMargin) + // so we duplicate a lot here, see qitemdelegate for reference + bool isSelected = option.state & QStyle::State_Selected; + QPalette::ColorGroup cg = option.state & QStyle::State_Enabled + ? QPalette::Normal : QPalette::Disabled; + if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) + cg = QPalette::Inactive; + QStyleOptionViewItem baseOption = option; + baseOption.state &= ~QStyle::State_Selected; + if (isSelected) { + painter->fillRect(beforeHighlightRect.adjusted(textMargin, 0, textMargin, 0), + option.palette.brush(cg, QPalette::Highlight)); + painter->fillRect(afterHighlightRect.adjusted(textMargin, 0, textMargin, 0), + option.palette.brush(cg, QPalette::Highlight)); + } + const QColor highlightBackground = + index.model()->data(index, ItemDataRoles::ResultHighlightBackgroundColor).value<QColor>(); + painter->fillRect(resultHighlightRect.adjusted(textMargin, 0, textMargin - 1, 0), QBrush(highlightBackground)); + + // Text before the highlighting + QStyleOptionViewItem noHighlightOpt = baseOption; + noHighlightOpt.rect = beforeHighlightRect; + noHighlightOpt.textElideMode = Qt::ElideNone; + if (isSelected) + noHighlightOpt.palette.setColor(QPalette::Text, noHighlightOpt.palette.color(cg, QPalette::HighlightedText)); + QItemDelegate::drawDisplay(painter, noHighlightOpt, + beforeHighlightRect, text.mid(0, searchTermStart)); + + // Highlight text + QStyleOptionViewItem highlightOpt = noHighlightOpt; + const QColor highlightForeground = + index.model()->data(index, ItemDataRoles::ResultHighlightForegroundColor).value<QColor>(); + highlightOpt.palette.setColor(QPalette::Text, highlightForeground); + QItemDelegate::drawDisplay(painter, highlightOpt, resultHighlightRect, + text.mid(searchTermStart, searchTermLength)); + + // Text after the Highlight + noHighlightOpt.rect = afterHighlightRect; + QItemDelegate::drawDisplay(painter, noHighlightOpt, afterHighlightRect, + text.mid(searchTermStart + searchTermLength)); +} diff --git a/src/plugins/coreplugin/find/searchresulttreeitemdelegate.h b/src/plugins/coreplugin/find/searchresulttreeitemdelegate.h new file mode 100644 index 0000000000..af46024ede --- /dev/null +++ b/src/plugins/coreplugin/find/searchresulttreeitemdelegate.h @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef SEARCHRESULTTREEITEMDELEGATE_H +#define SEARCHRESULTTREEITEMDELEGATE_H + +#include <QItemDelegate> + +namespace Core { +namespace Internal { + +class SearchResultTreeItemDelegate: public QItemDelegate +{ +public: + SearchResultTreeItemDelegate(QObject *parent = 0); + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + +private: + int drawLineNumber(QPainter *painter, const QStyleOptionViewItemV3 &option, const QRect &rect, const QModelIndex &index) const; + void drawText(QPainter *painter, const QStyleOptionViewItem &option, + const QRect &rect, const QModelIndex &index) const; + + static const int m_minimumLineNumberDigits = 6; +}; + +} // namespace Internal +} // namespace Core + +#endif // SEARCHRESULTTREEITEMDELEGATE_H diff --git a/src/plugins/coreplugin/find/searchresulttreeitemroles.h b/src/plugins/coreplugin/find/searchresulttreeitemroles.h new file mode 100644 index 0000000000..bd771d7c6a --- /dev/null +++ b/src/plugins/coreplugin/find/searchresulttreeitemroles.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef SEARCHRESULTTREEITEMROLES_H +#define SEARCHRESULTTREEITEMROLES_H + +#include <QAbstractItemView> + +namespace Core { +namespace Internal { +namespace ItemDataRoles { + +enum Roles +{ + ResultItemRole = Qt::UserRole, + ResultLineRole, + ResultLineNumberRole, + ResultIconRole, + ResultHighlightBackgroundColor, + ResultHighlightForegroundColor, + SearchTermStartRole, + SearchTermLengthRole, + IsGeneratedRole +}; + +} // namespace Internal +} // namespace Core +} // namespace ItemDataRoles + +#endif // SEARCHRESULTTREEITEMROLES_H diff --git a/src/plugins/coreplugin/find/searchresulttreeitems.cpp b/src/plugins/coreplugin/find/searchresulttreeitems.cpp new file mode 100644 index 0000000000..d9f941bd02 --- /dev/null +++ b/src/plugins/coreplugin/find/searchresulttreeitems.cpp @@ -0,0 +1,145 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "searchresulttreeitems.h" + +namespace Core { +namespace Internal { + +SearchResultTreeItem::SearchResultTreeItem(const SearchResultItem &item, + SearchResultTreeItem *parent) + : item(item), + m_parent(parent), + m_isUserCheckable(false), + m_isGenerated(false), + m_checkState(Qt::Unchecked) +{ +} + +SearchResultTreeItem::~SearchResultTreeItem() +{ + clearChildren(); +} + +bool SearchResultTreeItem::isLeaf() const +{ + return childrenCount() == 0 && parent() != 0; +} + +bool SearchResultTreeItem::isUserCheckable() const +{ + return m_isUserCheckable; +} + +void SearchResultTreeItem::setIsUserCheckable(bool isUserCheckable) +{ + m_isUserCheckable = isUserCheckable; +} + +Qt::CheckState SearchResultTreeItem::checkState() const +{ + return m_checkState; +} + +void SearchResultTreeItem::setCheckState(Qt::CheckState checkState) +{ + m_checkState = checkState; +} + +void SearchResultTreeItem::clearChildren() +{ + qDeleteAll(m_children); + m_children.clear(); +} + +int SearchResultTreeItem::childrenCount() const +{ + return m_children.count(); +} + +int SearchResultTreeItem::rowOfItem() const +{ + return (m_parent ? m_parent->m_children.indexOf(const_cast<SearchResultTreeItem*>(this)):0); +} + +SearchResultTreeItem* SearchResultTreeItem::childAt(int index) const +{ + return m_children.at(index); +} + +SearchResultTreeItem *SearchResultTreeItem::parent() const +{ + return m_parent; +} + +static bool lessThanByText(SearchResultTreeItem *a, const QString &b) +{ + return a->item.text < b; +} + +int SearchResultTreeItem::insertionIndex(const QString &text, SearchResultTreeItem **existingItem) const +{ + QList<SearchResultTreeItem *>::const_iterator insertionPosition = + qLowerBound(m_children.begin(), m_children.end(), text, lessThanByText); + if (existingItem) { + if (insertionPosition != m_children.end() && (*insertionPosition)->item.text == text) + (*existingItem) = (*insertionPosition); + else + *existingItem = 0; + } + return insertionPosition - m_children.begin(); +} + +int SearchResultTreeItem::insertionIndex(const SearchResultItem &item, SearchResultTreeItem **existingItem) const +{ + return insertionIndex(item.text, existingItem); +} + +void SearchResultTreeItem::insertChild(int index, SearchResultTreeItem *child) +{ + m_children.insert(index, child); +} + +void SearchResultTreeItem::insertChild(int index, const SearchResultItem &item) +{ + SearchResultTreeItem *child = new SearchResultTreeItem(item, this); + if (isUserCheckable()) { + child->setIsUserCheckable(true); + child->setCheckState(Qt::Checked); + } + insertChild(index, child); +} + +void SearchResultTreeItem::appendChild(const SearchResultItem &item) +{ + insertChild(m_children.count(), item); +} + +} // namespace Internal +} // namespace Core diff --git a/src/plugins/coreplugin/find/searchresulttreeitems.h b/src/plugins/coreplugin/find/searchresulttreeitems.h new file mode 100644 index 0000000000..3f41818ee1 --- /dev/null +++ b/src/plugins/coreplugin/find/searchresulttreeitems.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef SEARCHRESULTTREEITEMS_H +#define SEARCHRESULTTREEITEMS_H + +#include "searchresultwindow.h" + +#include <QString> +#include <QList> + +namespace Core { +namespace Internal { + +class SearchResultTreeItem +{ +public: + explicit SearchResultTreeItem(const SearchResultItem &item = SearchResultItem(), + SearchResultTreeItem *parent = NULL); + virtual ~SearchResultTreeItem(); + + bool isLeaf() const; + SearchResultTreeItem *parent() const; + SearchResultTreeItem *childAt(int index) const; + int insertionIndex(const QString &text, SearchResultTreeItem **existingItem) const; + int insertionIndex(const SearchResultItem &item, SearchResultTreeItem **existingItem) const; + void insertChild(int index, SearchResultTreeItem *child); + void insertChild(int index, const SearchResultItem &item); + void appendChild(const SearchResultItem &item); + int childrenCount() const; + int rowOfItem() const; + void clearChildren(); + + bool isUserCheckable() const; + void setIsUserCheckable(bool isUserCheckable); + + Qt::CheckState checkState() const; + void setCheckState(Qt::CheckState checkState); + + bool isGenerated() const { return m_isGenerated; } + void setGenerated(bool value) { m_isGenerated = value; } + + SearchResultItem item; + +private: + SearchResultTreeItem *m_parent; + QList<SearchResultTreeItem *> m_children; + bool m_isUserCheckable; + bool m_isGenerated; + Qt::CheckState m_checkState; +}; + +} // namespace Internal +} // namespace Core + +#endif // SEARCHRESULTTREEITEMS_H diff --git a/src/plugins/coreplugin/find/searchresulttreemodel.cpp b/src/plugins/coreplugin/find/searchresulttreemodel.cpp new file mode 100644 index 0000000000..7aafe033bb --- /dev/null +++ b/src/plugins/coreplugin/find/searchresulttreemodel.cpp @@ -0,0 +1,504 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "searchresulttreemodel.h" +#include "searchresulttreeitems.h" +#include "searchresulttreeitemroles.h" + +#include <QApplication> +#include <QFont> +#include <QFontMetrics> +#include <QDebug> + +using namespace Core; +using namespace Core::Internal; + +SearchResultTreeModel::SearchResultTreeModel(QObject *parent) + : QAbstractItemModel(parent) + , m_currentParent(0) + , m_showReplaceUI(false) + , m_editorFontIsUsed(false) +{ + m_rootItem = new SearchResultTreeItem; + m_textEditorFont = QFont(QLatin1String("Courier")); +} + +SearchResultTreeModel::~SearchResultTreeModel() +{ + delete m_rootItem; +} + +void SearchResultTreeModel::setShowReplaceUI(bool show) +{ + m_showReplaceUI = show; +} + +void SearchResultTreeModel::setTextEditorFont(const QFont &font, const SearchResultColor color) +{ + layoutAboutToBeChanged(); + m_textEditorFont = font; + m_color = color; + layoutChanged(); +} + +Qt::ItemFlags SearchResultTreeModel::flags(const QModelIndex &idx) const +{ + Qt::ItemFlags flags = QAbstractItemModel::flags(idx); + + if (idx.isValid()) { + if (const SearchResultTreeItem *item = treeItemAtIndex(idx)) { + if (item->isUserCheckable()) + flags |= Qt::ItemIsUserCheckable; + } + } + + return flags; +} + +QModelIndex SearchResultTreeModel::index(int row, int column, + const QModelIndex &parent) const +{ + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + const SearchResultTreeItem *parentItem; + + if (!parent.isValid()) + parentItem = m_rootItem; + else + parentItem = treeItemAtIndex(parent); + + const SearchResultTreeItem *childItem = parentItem->childAt(row); + if (childItem) + return createIndex(row, column, (void *)childItem); + else + return QModelIndex(); +} + +QModelIndex SearchResultTreeModel::index(SearchResultTreeItem *item) const +{ + return createIndex(item->rowOfItem(), 0, (void *)item); +} + +QModelIndex SearchResultTreeModel::parent(const QModelIndex &idx) const +{ + if (!idx.isValid()) + return QModelIndex(); + + const SearchResultTreeItem *childItem = treeItemAtIndex(idx); + const SearchResultTreeItem *parentItem = childItem->parent(); + + if (parentItem == m_rootItem) + return QModelIndex(); + + return createIndex(parentItem->rowOfItem(), 0, (void *)parentItem); +} + +int SearchResultTreeModel::rowCount(const QModelIndex &parent) const +{ + if (parent.column() > 0) + return 0; + + const SearchResultTreeItem *parentItem; + + if (!parent.isValid()) + parentItem = m_rootItem; + else + parentItem = treeItemAtIndex(parent); + + return parentItem->childrenCount(); +} + +int SearchResultTreeModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return 1; +} + +SearchResultTreeItem *SearchResultTreeModel::treeItemAtIndex(const QModelIndex &idx) const +{ + return static_cast<SearchResultTreeItem*>(idx.internalPointer()); +} + +QVariant SearchResultTreeModel::data(const QModelIndex &idx, int role) const +{ + if (!idx.isValid()) + return QVariant(); + + QVariant result; + + if (role == Qt::SizeHintRole) { + int height = QApplication::fontMetrics().height(); + if (m_editorFontIsUsed) { + const int editorFontHeight = QFontMetrics(m_textEditorFont).height(); + height = qMax(height, editorFontHeight); + } + result = QSize(0, height); + } else { + result = data(treeItemAtIndex(idx), role); + } + + return result; +} + +bool SearchResultTreeModel::setData(const QModelIndex &idx, const QVariant &value, int role) +{ + if (role == Qt::CheckStateRole) { + Qt::CheckState checkState = static_cast<Qt::CheckState>(value.toInt()); + return setCheckState(idx, checkState); + } + return QAbstractItemModel::setData(idx, value, role); +} + +bool SearchResultTreeModel::setCheckState(const QModelIndex &idx, Qt::CheckState checkState, bool firstCall) +{ + SearchResultTreeItem *item = treeItemAtIndex(idx); + if (item->checkState() == checkState) + return false; + item->setCheckState(checkState); + if (firstCall) { + emit dataChanged(idx, idx); + // check parents + SearchResultTreeItem *currentItem = item; + QModelIndex currentIndex = idx; + while (SearchResultTreeItem *parent = currentItem->parent()) { + if (parent->isUserCheckable()) { + bool hasChecked = false; + bool hasUnchecked = false; + for (int i = 0; i < parent->childrenCount(); ++i) { + SearchResultTreeItem *child = parent->childAt(i); + if (!child->isUserCheckable()) + continue; + if (child->checkState() == Qt::Checked) + hasChecked = true; + else if (child->checkState() == Qt::Unchecked) + hasUnchecked = true; + else if (child->checkState() == Qt::PartiallyChecked) + hasChecked = hasUnchecked = true; + } + if (hasChecked && hasUnchecked) + parent->setCheckState(Qt::PartiallyChecked); + else if (hasChecked) + parent->setCheckState(Qt::Checked); + else + parent->setCheckState(Qt::Unchecked); + emit dataChanged(idx.parent(), idx.parent()); + } + currentItem = parent; + currentIndex = idx.parent(); + } + } + // check children + if (int children = item->childrenCount()) { + for (int i = 0; i < children; ++i) { + setCheckState(idx.child(i, 0), checkState, false); + } + emit dataChanged(idx.child(0, 0), idx.child(children-1, 0)); + } + return true; +} + +void setDataInternal(const QModelIndex &index, const QVariant &value, int role); + +QVariant SearchResultTreeModel::data(const SearchResultTreeItem *row, int role) const +{ + QVariant result; + + switch (role) + { + case Qt::CheckStateRole: + if (row->isUserCheckable()) + result = row->checkState(); + break; + case Qt::ToolTipRole: + result = row->item.text.trimmed(); + break; + case Qt::FontRole: + if (row->item.useTextEditorFont) + result = m_textEditorFont; + else + result = QVariant(); + break; + case Qt::TextColorRole: + result = m_color.textForeground; + break; + case Qt::BackgroundRole: + result = m_color.textBackground; + break; + case ItemDataRoles::ResultLineRole: + case Qt::DisplayRole: + result = row->item.text; + break; + case ItemDataRoles::ResultItemRole: + result = qVariantFromValue(row->item); + break; + case ItemDataRoles::ResultLineNumberRole: + result = row->item.lineNumber; + break; + case ItemDataRoles::ResultIconRole: + result = row->item.icon; + break; + case ItemDataRoles::ResultHighlightBackgroundColor: + result = m_color.highlightBackground; + break; + case ItemDataRoles::ResultHighlightForegroundColor: + result = m_color.highlightForeground; + break; + case ItemDataRoles::SearchTermStartRole: + result = row->item.textMarkPos; + break; + case ItemDataRoles::SearchTermLengthRole: + result = row->item.textMarkLength; + break; + case ItemDataRoles::IsGeneratedRole: + result = row->isGenerated(); + break; + default: + result = QVariant(); + break; + } + + return result; +} + +QVariant SearchResultTreeModel::headerData(int section, Qt::Orientation orientation, + int role) const +{ + Q_UNUSED(section) + Q_UNUSED(orientation) + Q_UNUSED(role) + return QVariant(); +} + +/** + * Makes sure that the nodes for a specific path exist and sets + * m_currentParent to the last final + */ +QSet<SearchResultTreeItem *> SearchResultTreeModel::addPath(const QStringList &path) +{ + QSet<SearchResultTreeItem *> pathNodes; + SearchResultTreeItem *currentItem = m_rootItem; + QModelIndex currentItemIndex = QModelIndex(); + SearchResultTreeItem *partItem = 0; + QStringList currentPath; + foreach (const QString &part, path) { + const int insertionIndex = currentItem->insertionIndex(part, &partItem); + if (!partItem) { + SearchResultItem item; + item.path = currentPath; + item.text = part; + partItem = new SearchResultTreeItem(item, currentItem); + if (m_showReplaceUI) { + partItem->setIsUserCheckable(true); + partItem->setCheckState(Qt::Checked); + } + partItem->setGenerated(true); + beginInsertRows(currentItemIndex, insertionIndex, insertionIndex); + currentItem->insertChild(insertionIndex, partItem); + endInsertRows(); + } + pathNodes << partItem; + currentItemIndex = index(insertionIndex, 0, currentItemIndex); + currentItem = partItem; + currentPath << part; + } + + m_currentParent = currentItem; + m_currentPath = currentPath; + m_currentIndex = currentItemIndex; + return pathNodes; +} + +void SearchResultTreeModel::addResultsToCurrentParent(const QList<SearchResultItem> &items, SearchResult::AddMode mode) +{ + if (!m_currentParent) + return; + + if (mode == SearchResult::AddOrdered) { + // this is the mode for e.g. text search + beginInsertRows(m_currentIndex, m_currentParent->childrenCount(), m_currentParent->childrenCount() + items.count()); + foreach (const SearchResultItem &item, items) { + m_currentParent->appendChild(item); + } + endInsertRows(); + } else if (mode == SearchResult::AddSorted) { + foreach (const SearchResultItem &item, items) { + SearchResultTreeItem *existingItem; + const int insertionIndex = m_currentParent->insertionIndex(item, &existingItem); + if (existingItem) { + existingItem->setGenerated(false); + existingItem->item = item; + QModelIndex itemIndex = m_currentIndex.child(insertionIndex, 0); + dataChanged(itemIndex, itemIndex); + } else { + beginInsertRows(m_currentIndex, insertionIndex, insertionIndex); + m_currentParent->insertChild(insertionIndex, item); + endInsertRows(); + } + } + } + dataChanged(m_currentIndex, m_currentIndex); // Make sure that the number after the file name gets updated +} + +static bool lessThanByPath(const SearchResultItem &a, const SearchResultItem &b) +{ + if (a.path.size() < b.path.size()) + return true; + if (a.path.size() > b.path.size()) + return false; + for (int i = 0; i < a.path.size(); ++i) { + if (a.path.at(i) < b.path.at(i)) + return true; + if (a.path.at(i) > b.path.at(i)) + return false; + } + return false; +} + +/** + * Adds the search result to the list of results, creating nodes for the path when + * necessary. + */ +QList<QModelIndex> SearchResultTreeModel::addResults(const QList<SearchResultItem> &items, SearchResult::AddMode mode) +{ + QSet<SearchResultTreeItem *> pathNodes; + QList<SearchResultItem> sortedItems = items; + qStableSort(sortedItems.begin(), sortedItems.end(), lessThanByPath); + QList<SearchResultItem> itemSet; + foreach (const SearchResultItem &item, sortedItems) { + m_editorFontIsUsed |= item.useTextEditorFont; + if (!m_currentParent || (m_currentPath != item.path)) { + // first add all the items from before + if (!itemSet.isEmpty()) { + addResultsToCurrentParent(itemSet, mode); + itemSet.clear(); + } + // switch parent + pathNodes += addPath(item.path); + } + itemSet << item; + } + if (!itemSet.isEmpty()) { + addResultsToCurrentParent(itemSet, mode); + itemSet.clear(); + } + QList<QModelIndex> pathIndices; + foreach (SearchResultTreeItem *item, pathNodes) + pathIndices << index(item); + return pathIndices; +} + +void SearchResultTreeModel::clear() +{ + beginResetModel(); + m_currentParent = NULL; + m_rootItem->clearChildren(); + m_editorFontIsUsed = false; + endResetModel(); +} + +QModelIndex SearchResultTreeModel::nextIndex(const QModelIndex &idx, bool *wrapped) const +{ + if (wrapped) + *wrapped = false; + // pathological + if (!idx.isValid()) + return index(0, 0); + + if (rowCount(idx) > 0) { + // node with children + return idx.child(0, 0); + } + // leaf node + QModelIndex nextIndex; + QModelIndex current = idx; + while (!nextIndex.isValid()) { + int row = current.row(); + current = current.parent(); + if (row + 1 < rowCount(current)) { + // Same parent has another child + nextIndex = index(row + 1, 0, current); + } else { + // go up one parent + if (!current.isValid()) { + // we start from the beginning + if (wrapped) + *wrapped = true; + nextIndex = index(0, 0); + } + } + } + return nextIndex; +} + +QModelIndex SearchResultTreeModel::next(const QModelIndex &idx, bool includeGenerated, bool *wrapped) const +{ + QModelIndex value = idx; + do { + value = nextIndex(value, wrapped); + } while (value != idx && !includeGenerated && treeItemAtIndex(value)->isGenerated()); + return value; +} + +QModelIndex SearchResultTreeModel::prevIndex(const QModelIndex &idx, bool *wrapped) const +{ + if (wrapped) + *wrapped = false; + QModelIndex current = idx; + bool checkForChildren = true; + if (current.isValid()) { + int row = current.row(); + if (row > 0) { + current = index(row - 1, 0, current.parent()); + } else { + current = current.parent(); + checkForChildren = !current.isValid(); + if (checkForChildren && wrapped) { + // we start from the end + *wrapped = true; + } + } + } + if (checkForChildren) { + // traverse down the hierarchy + while (int rc = rowCount(current)) { + current = index(rc - 1, 0, current); + } + } + return current; +} + +QModelIndex SearchResultTreeModel::prev(const QModelIndex &idx, bool includeGenerated, bool *wrapped) const +{ + QModelIndex value = idx; + do { + value = prevIndex(value, wrapped); + } while (value != idx && !includeGenerated && treeItemAtIndex(value)->isGenerated()); + return value; +} diff --git a/src/plugins/coreplugin/find/searchresulttreemodel.h b/src/plugins/coreplugin/find/searchresulttreemodel.h new file mode 100644 index 0000000000..36ca6d94ac --- /dev/null +++ b/src/plugins/coreplugin/find/searchresulttreemodel.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef SEARCHRESULTTREEMODEL_H +#define SEARCHRESULTTREEMODEL_H + +#include "searchresultwindow.h" +#include "searchresultcolor.h" + +#include <QAbstractItemModel> +#include <QFont> + +namespace Core { +namespace Internal { + +class SearchResultTreeItem; + +class SearchResultTreeModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + SearchResultTreeModel(QObject *parent = 0); + ~SearchResultTreeModel(); + + void setShowReplaceUI(bool show); + void setTextEditorFont(const QFont &font, const SearchResultColor color); + + Qt::ItemFlags flags(const QModelIndex &index) const; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &child) const; + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + + QModelIndex next(const QModelIndex &idx, bool includeGenerated = false, bool *wrapped = 0) const; + QModelIndex prev(const QModelIndex &idx, bool includeGenerated = false, bool *wrapped = 0) const; + + QList<QModelIndex> addResults(const QList<SearchResultItem> &items, SearchResult::AddMode mode); + +signals: + void jumpToSearchResult(const QString &fileName, int lineNumber, + int searchTermStart, int searchTermLength); + +public slots: + void clear(); + +private: + QModelIndex index(SearchResultTreeItem *item) const; + void addResultsToCurrentParent(const QList<SearchResultItem> &items, SearchResult::AddMode mode); + QSet<SearchResultTreeItem *> addPath(const QStringList &path); + QVariant data(const SearchResultTreeItem *row, int role) const; + bool setCheckState(const QModelIndex &idx, Qt::CheckState checkState, bool firstCall = true); + QModelIndex nextIndex(const QModelIndex &idx, bool *wrapped = 0) const; + QModelIndex prevIndex(const QModelIndex &idx, bool *wrapped = 0) const; + SearchResultTreeItem *treeItemAtIndex(const QModelIndex &idx) const; + + SearchResultTreeItem *m_rootItem; + SearchResultTreeItem *m_currentParent; + SearchResultColor m_color; + QModelIndex m_currentIndex; + QStringList m_currentPath; // the path that belongs to the current parent + QFont m_textEditorFont; + bool m_showReplaceUI; + bool m_editorFontIsUsed; +}; + +} // namespace Internal +} // namespace Core + +#endif // SEARCHRESULTTREEMODEL_H diff --git a/src/plugins/coreplugin/find/searchresulttreeview.cpp b/src/plugins/coreplugin/find/searchresulttreeview.cpp new file mode 100644 index 0000000000..d48efabd97 --- /dev/null +++ b/src/plugins/coreplugin/find/searchresulttreeview.cpp @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "searchresulttreeview.h" +#include "searchresulttreeitemroles.h" +#include "searchresulttreemodel.h" +#include "searchresulttreeitemdelegate.h" + +#include <QHeaderView> +#include <QKeyEvent> + +namespace Core { +namespace Internal { + +SearchResultTreeView::SearchResultTreeView(QWidget *parent) + : QTreeView(parent) + , m_model(new SearchResultTreeModel(this)) + , m_autoExpandResults(false) +{ + setModel(m_model); + setItemDelegate(new SearchResultTreeItemDelegate(this)); + setIndentation(14); + setUniformRowHeights(true); + setExpandsOnDoubleClick(true); + header()->hide(); + + connect(this, SIGNAL(activated(QModelIndex)), this, SLOT(emitJumpToSearchResult(QModelIndex))); +} + +void SearchResultTreeView::setAutoExpandResults(bool expand) +{ + m_autoExpandResults = expand; +} + +void SearchResultTreeView::setTextEditorFont(const QFont &font, const SearchResultColor color) +{ + m_model->setTextEditorFont(font, color); + + QPalette p = palette(); + p.setColor(QPalette::Base, color.textBackground); + setPalette(p); +} + +void SearchResultTreeView::clear() +{ + m_model->clear(); +} + +void SearchResultTreeView::addResults(const QList<SearchResultItem> &items, SearchResult::AddMode mode) +{ + QList<QModelIndex> addedParents = m_model->addResults(items, mode); + if (m_autoExpandResults && !addedParents.isEmpty()) { + foreach (const QModelIndex &index, addedParents) + setExpanded(index, true); + } +} + +void SearchResultTreeView::emitJumpToSearchResult(const QModelIndex &index) +{ + if (model()->data(index, ItemDataRoles::IsGeneratedRole).toBool()) + return; + SearchResultItem item = model()->data(index, ItemDataRoles::ResultItemRole).value<SearchResultItem>(); + + emit jumpToSearchResult(item); +} + +void SearchResultTreeView::keyPressEvent(QKeyEvent *e) +{ + if (!e->modifiers() && e->key() == Qt::Key_Return) { + emit activated(currentIndex()); + e->accept(); + return; + } + QTreeView::keyPressEvent(e); +} + +SearchResultTreeModel *SearchResultTreeView::model() const +{ + return m_model; +} + +} // namespace Internal +} // namespace Core diff --git a/src/plugins/coreplugin/find/searchresulttreeview.h b/src/plugins/coreplugin/find/searchresulttreeview.h new file mode 100644 index 0000000000..b5dcd792d3 --- /dev/null +++ b/src/plugins/coreplugin/find/searchresulttreeview.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef SEARCHRESULTTREEVIEW_H +#define SEARCHRESULTTREEVIEW_H + +#include "searchresultwindow.h" + +#include <QTreeView> + +namespace Core { +namespace Internal { + +class SearchResultTreeModel; +class SearchResultColor; + +class SearchResultTreeView : public QTreeView +{ + Q_OBJECT + +public: + explicit SearchResultTreeView(QWidget *parent = 0); + + void setAutoExpandResults(bool expand); + void setTextEditorFont(const QFont &font, const SearchResultColor color); + + SearchResultTreeModel *model() const; + void addResults(const QList<SearchResultItem> &items, SearchResult::AddMode mode); + +signals: + void jumpToSearchResult(const SearchResultItem &item); + +public slots: + void clear(); + void emitJumpToSearchResult(const QModelIndex &index); + +protected: + void keyPressEvent(QKeyEvent *e); + + SearchResultTreeModel *m_model; + bool m_autoExpandResults; +}; + +} // namespace Internal +} // namespace Core + +#endif // SEARCHRESULTTREEVIEW_H diff --git a/src/plugins/coreplugin/find/searchresultwidget.cpp b/src/plugins/coreplugin/find/searchresultwidget.cpp new file mode 100644 index 0000000000..6539cebdad --- /dev/null +++ b/src/plugins/coreplugin/find/searchresultwidget.cpp @@ -0,0 +1,495 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "searchresultwidget.h" +#include "searchresulttreeview.h" +#include "searchresulttreemodel.h" +#include "searchresulttreeitems.h" +#include "searchresulttreeitemroles.h" + +#include "findplugin.h" +#include "treeviewfind.h" + +#include <aggregation/aggregate.h> +#include <coreplugin/coreplugin.h> + +#include <QDir> +#include <QFrame> +#include <QLabel> +#include <QLineEdit> +#include <QToolButton> +#include <QCheckBox> +#include <QVBoxLayout> +#include <QHBoxLayout> + +static const int SEARCHRESULT_WARNING_LIMIT = 200000; +static const char SIZE_WARNING_ID[] = "sizeWarningLabel"; + +namespace Core { +namespace Internal { + +class WideEnoughLineEdit : public QLineEdit { + Q_OBJECT +public: + WideEnoughLineEdit(QWidget *parent):QLineEdit(parent){ + connect(this, SIGNAL(textChanged(QString)), + this, SLOT(updateGeometry())); + } + ~WideEnoughLineEdit(){} + QSize sizeHint() const { + QSize sh = QLineEdit::minimumSizeHint(); + sh.rwidth() += qMax(25 * fontMetrics().width(QLatin1Char('x')), + fontMetrics().width(text())); + return sh; + } +public slots: + void updateGeometry() { QLineEdit::updateGeometry(); } +}; + +} // namespace Internal +} // namespace Core + +using namespace Core; +using namespace Core::Internal; + +SearchResultWidget::SearchResultWidget(QWidget *parent) : + QWidget(parent), + m_count(0), + m_isShowingReplaceUI(false), + m_searchAgainSupported(false) +{ + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setMargin(0); + layout->setSpacing(0); + setLayout(layout); + + QFrame *topWidget = new QFrame; + QPalette pal = topWidget->palette(); + pal.setColor(QPalette::Window, QColor(255, 255, 225)); + pal.setColor(QPalette::WindowText, Qt::black); + topWidget->setPalette(pal); + topWidget->setFrameStyle(QFrame::Panel | QFrame::Raised); + topWidget->setLineWidth(1); + topWidget->setAutoFillBackground(true); + QHBoxLayout *topLayout = new QHBoxLayout(topWidget); + topLayout->setMargin(2); + topWidget->setLayout(topLayout); + layout->addWidget(topWidget); + + m_messageWidget = new QFrame; + pal.setColor(QPalette::Window, QColor(255, 255, 225)); + pal.setColor(QPalette::WindowText, Qt::red); + m_messageWidget->setPalette(pal); + m_messageWidget->setFrameStyle(QFrame::Panel | QFrame::Raised); + m_messageWidget->setLineWidth(1); + m_messageWidget->setAutoFillBackground(true); + QHBoxLayout *messageLayout = new QHBoxLayout(m_messageWidget); + messageLayout->setMargin(2); + m_messageWidget->setLayout(messageLayout); + QLabel *messageLabel = new QLabel(tr("Search was canceled.")); + messageLabel->setPalette(pal); + messageLayout->addWidget(messageLabel); + layout->addWidget(m_messageWidget); + m_messageWidget->setVisible(false); + + m_searchResultTreeView = new Internal::SearchResultTreeView(this); + m_searchResultTreeView->setFrameStyle(QFrame::NoFrame); + m_searchResultTreeView->setAttribute(Qt::WA_MacShowFocusRect, false); + Aggregation::Aggregate * agg = new Aggregation::Aggregate; + agg->add(m_searchResultTreeView); + agg->add(new TreeViewFind(m_searchResultTreeView, + ItemDataRoles::ResultLineRole)); + layout->addWidget(m_searchResultTreeView); + + m_infoBarDisplay.setTarget(layout, 2); + m_infoBarDisplay.setInfoBar(&m_infoBar); + + m_descriptionContainer = new QWidget(topWidget); + QHBoxLayout *descriptionLayout = new QHBoxLayout(m_descriptionContainer); + m_descriptionContainer->setLayout(descriptionLayout); + descriptionLayout->setMargin(0); + m_descriptionContainer->setMinimumWidth(200); + m_descriptionContainer->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + m_label = new QLabel(m_descriptionContainer); + m_label->setVisible(false); + m_searchTerm = new QLabel(m_descriptionContainer); + m_searchTerm->setVisible(false); + descriptionLayout->addWidget(m_label); + descriptionLayout->addWidget(m_searchTerm); + m_cancelButton = new QToolButton(topWidget); + m_cancelButton->setText(tr("Cancel")); + m_cancelButton->setToolButtonStyle(Qt::ToolButtonTextOnly); + connect(m_cancelButton, SIGNAL(clicked()), this, SLOT(cancel())); + m_searchAgainButton = new QToolButton(topWidget); + m_searchAgainButton->setToolTip(tr("Repeat the search with same parameters")); + m_searchAgainButton->setText(tr("Search again")); + m_searchAgainButton->setToolButtonStyle(Qt::ToolButtonTextOnly); + m_searchAgainButton->setVisible(false); + connect(m_searchAgainButton, SIGNAL(clicked()), this, SLOT(searchAgain())); + + m_replaceLabel = new QLabel(tr("Replace with:"), topWidget); + m_replaceTextEdit = new WideEnoughLineEdit(topWidget); + m_replaceTextEdit->setMinimumWidth(120); + m_replaceTextEdit->setEnabled(false); + m_replaceTextEdit->setTabOrder(m_replaceTextEdit, m_searchResultTreeView); + m_replaceButton = new QToolButton(topWidget); + m_replaceButton->setToolTip(tr("Replace all occurrences")); + m_replaceButton->setText(tr("Replace")); + m_replaceButton->setToolButtonStyle(Qt::ToolButtonTextOnly); + m_replaceButton->setEnabled(false); + m_preserveCaseCheck = new QCheckBox(topWidget); + m_preserveCaseCheck->setText(tr("Preserve case")); + m_preserveCaseCheck->setEnabled(false); + + if (FindPlugin * plugin = FindPlugin::instance()) { + m_preserveCaseCheck->setChecked(plugin->hasFindFlag(FindPreserveCase)); + connect(m_preserveCaseCheck, SIGNAL(clicked(bool)), plugin, SLOT(setPreserveCase(bool))); + } + + m_matchesFoundLabel = new QLabel(topWidget); + updateMatchesFoundLabel(); + + topLayout->addWidget(m_descriptionContainer); + topLayout->addWidget(m_cancelButton); + topLayout->addWidget(m_searchAgainButton); + topLayout->addWidget(m_replaceLabel); + topLayout->addWidget(m_replaceTextEdit); + topLayout->addWidget(m_replaceButton); + topLayout->addWidget(m_preserveCaseCheck); + topLayout->addStretch(2); + topLayout->addWidget(m_matchesFoundLabel); + topWidget->setMinimumHeight(m_cancelButton->sizeHint().height() + + topLayout->contentsMargins().top() + topLayout->contentsMargins().bottom() + + topWidget->lineWidth()); + setShowReplaceUI(false); + + connect(m_searchResultTreeView, SIGNAL(jumpToSearchResult(SearchResultItem)), + this, SLOT(handleJumpToSearchResult(SearchResultItem))); + connect(m_replaceTextEdit, SIGNAL(returnPressed()), this, SLOT(handleReplaceButton())); + connect(m_replaceButton, SIGNAL(clicked()), this, SLOT(handleReplaceButton())); +} + +SearchResultWidget::~SearchResultWidget() +{ + if (m_infoBar.containsInfo(Core::Id(SIZE_WARNING_ID))) + cancelAfterSizeWarning(); +} + +void SearchResultWidget::setInfo(const QString &label, const QString &toolTip, const QString &term) +{ + m_label->setText(label); + m_label->setVisible(!label.isEmpty()); + m_descriptionContainer->setToolTip(toolTip); + m_searchTerm->setText(term); + m_searchTerm->setVisible(!term.isEmpty()); +} + +void SearchResultWidget::addResult(const QString &fileName, int lineNumber, const QString &rowText, + int searchTermStart, int searchTermLength, const QVariant &userData) +{ + SearchResultItem item; + item.path = QStringList() << QDir::toNativeSeparators(fileName); + item.lineNumber = lineNumber; + item.text = rowText; + item.textMarkPos = searchTermStart; + item.textMarkLength = searchTermLength; + item.useTextEditorFont = true; + item.userData = userData; + addResults(QList<SearchResultItem>() << item, SearchResult::AddOrdered); +} + +void SearchResultWidget::addResults(const QList<SearchResultItem> &items, SearchResult::AddMode mode) +{ + bool firstItems = (m_count == 0); + m_count += items.size(); + m_searchResultTreeView->addResults(items, mode); + updateMatchesFoundLabel(); + if (firstItems) { + if (!m_dontAskAgainGroup.isEmpty()) { + Core::Id undoWarningId = Core::Id("warninglabel/").withSuffix(m_dontAskAgainGroup); + if (m_infoBar.canInfoBeAdded(undoWarningId)) { + Core::InfoBarEntry info(undoWarningId, tr("This change cannot be undone."), + Core::InfoBarEntry::GlobalSuppressionEnabled); + m_infoBar.addInfo(info); + } + } + + m_replaceTextEdit->setEnabled(true); + // We didn't have an item before, set the focus to the search widget or replace text edit + if (m_isShowingReplaceUI) { + m_replaceTextEdit->setFocus(); + m_replaceTextEdit->selectAll(); + } else { + m_searchResultTreeView->setFocus(); + } + m_searchResultTreeView->selectionModel()->select(m_searchResultTreeView->model()->index(0, 0, QModelIndex()), QItemSelectionModel::Select); + emit navigateStateChanged(); + } else if (m_count <= SEARCHRESULT_WARNING_LIMIT) { + return; + } else { + Core::Id sizeWarningId(SIZE_WARNING_ID); + if (!m_infoBar.canInfoBeAdded(sizeWarningId)) + return; + emit paused(true); + Core::InfoBarEntry info(sizeWarningId, + tr("The search resulted in more than %n items, do you still want to continue?", + 0, SEARCHRESULT_WARNING_LIMIT)); + info.setCancelButtonInfo(tr("Cancel"), this, SLOT(cancelAfterSizeWarning())); + info.setCustomButtonInfo(tr("Continue"), this, SLOT(continueAfterSizeWarning())); + m_infoBar.addInfo(info); + emit requestPopup(false/*no focus*/); + } +} + + + +int SearchResultWidget::count() const +{ + return m_count; +} + +QString SearchResultWidget::dontAskAgainGroup() const +{ + return m_dontAskAgainGroup; +} + +void SearchResultWidget::setDontAskAgainGroup(const QString &group) +{ + m_dontAskAgainGroup = group; +} + + +void SearchResultWidget::setTextToReplace(const QString &textToReplace) +{ + m_replaceTextEdit->setText(textToReplace); +} + +QString SearchResultWidget::textToReplace() const +{ + return m_replaceTextEdit->text(); +} + +void SearchResultWidget::setShowReplaceUI(bool visible) +{ + m_searchResultTreeView->model()->setShowReplaceUI(visible); + m_replaceLabel->setVisible(visible); + m_replaceTextEdit->setVisible(visible); + m_replaceButton->setVisible(visible); + m_preserveCaseCheck->setVisible(visible); + m_isShowingReplaceUI = visible; +} + +bool SearchResultWidget::hasFocusInternally() const +{ + return m_searchResultTreeView->hasFocus() || (m_isShowingReplaceUI && m_replaceTextEdit->hasFocus()); +} + +void SearchResultWidget::setFocusInternally() +{ + if (m_count > 0) { + if (m_isShowingReplaceUI) { + if (!focusWidget() || focusWidget() == m_replaceTextEdit) { + m_replaceTextEdit->setFocus(); + m_replaceTextEdit->selectAll(); + } else { + m_searchResultTreeView->setFocus(); + } + } else { + m_searchResultTreeView->setFocus(); + } + } +} + +bool SearchResultWidget::canFocusInternally() const +{ + return m_count > 0; +} + +void SearchResultWidget::notifyVisibilityChanged(bool visible) +{ + emit visibilityChanged(visible); +} + +void SearchResultWidget::setTextEditorFont(const QFont &font, const SearchResultColor color) +{ + m_searchResultTreeView->setTextEditorFont(font, color); +} + +void SearchResultWidget::setAutoExpandResults(bool expand) +{ + m_searchResultTreeView->setAutoExpandResults(expand); +} + +void SearchResultWidget::expandAll() +{ + m_searchResultTreeView->expandAll(); +} + +void SearchResultWidget::collapseAll() +{ + m_searchResultTreeView->collapseAll(); +} + +void SearchResultWidget::goToNext() +{ + if (m_count == 0) + return; + QModelIndex idx = m_searchResultTreeView->model()->next(m_searchResultTreeView->currentIndex()); + if (idx.isValid()) { + m_searchResultTreeView->setCurrentIndex(idx); + m_searchResultTreeView->emitJumpToSearchResult(idx); + } +} + +void SearchResultWidget::goToPrevious() +{ + if (!m_searchResultTreeView->model()->rowCount()) + return; + QModelIndex idx = m_searchResultTreeView->model()->prev(m_searchResultTreeView->currentIndex()); + if (idx.isValid()) { + m_searchResultTreeView->setCurrentIndex(idx); + m_searchResultTreeView->emitJumpToSearchResult(idx); + } +} + +void SearchResultWidget::restart() +{ + m_replaceTextEdit->setEnabled(false); + m_replaceButton->setEnabled(false); + m_searchResultTreeView->clear(); + m_count = 0; + Core::Id sizeWarningId(SIZE_WARNING_ID); + m_infoBar.removeInfo(sizeWarningId); + m_infoBar.enableInfo(sizeWarningId); + m_cancelButton->setVisible(true); + m_searchAgainButton->setVisible(false); + m_messageWidget->setVisible(false); + updateMatchesFoundLabel(); + emit restarted(); +} + +void SearchResultWidget::setSearchAgainSupported(bool supported) +{ + m_searchAgainSupported = supported; + m_searchAgainButton->setVisible(supported && !m_cancelButton->isVisible()); +} + +void SearchResultWidget::setSearchAgainEnabled(bool enabled) +{ + m_searchAgainButton->setEnabled(enabled); +} + +void SearchResultWidget::finishSearch(bool canceled) +{ + Core::Id sizeWarningId(SIZE_WARNING_ID); + m_infoBar.removeInfo(sizeWarningId); + m_infoBar.enableInfo(sizeWarningId); + m_replaceTextEdit->setEnabled(m_count > 0); + m_replaceButton->setEnabled(m_count > 0); + m_preserveCaseCheck->setEnabled(m_count > 0); + m_cancelButton->setVisible(false); + m_messageWidget->setVisible(canceled); + m_searchAgainButton->setVisible(m_searchAgainSupported); +} + +void SearchResultWidget::sendRequestPopup() +{ + emit requestPopup(true/*focus*/); +} + +void SearchResultWidget::continueAfterSizeWarning() +{ + m_infoBar.suppressInfo(Core::Id(SIZE_WARNING_ID)); + emit paused(false); +} + +void SearchResultWidget::cancelAfterSizeWarning() +{ + m_infoBar.suppressInfo(Core::Id(SIZE_WARNING_ID)); + emit cancelled(); + emit paused(false); +} + +void SearchResultWidget::handleJumpToSearchResult(const SearchResultItem &item) +{ + emit activated(item); +} + +void SearchResultWidget::handleReplaceButton() +{ + // check if button is actually enabled, because this is also triggered + // by pressing return in replace line edit + if (m_replaceButton->isEnabled()) { + m_infoBar.clear(); + emit replaceButtonClicked(m_replaceTextEdit->text(), checkedItems(), m_preserveCaseCheck->isChecked()); + } +} + +void SearchResultWidget::cancel() +{ + m_cancelButton->setVisible(false); + if (m_infoBar.containsInfo(Core::Id(SIZE_WARNING_ID))) + cancelAfterSizeWarning(); + else + emit cancelled(); +} + +void SearchResultWidget::searchAgain() +{ + emit searchAgainRequested(); +} + +QList<SearchResultItem> SearchResultWidget::checkedItems() const +{ + QList<SearchResultItem> result; + Internal::SearchResultTreeModel *model = m_searchResultTreeView->model(); + const int fileCount = model->rowCount(QModelIndex()); + for (int i = 0; i < fileCount; ++i) { + QModelIndex fileIndex = model->index(i, 0, QModelIndex()); + Internal::SearchResultTreeItem *fileItem = static_cast<Internal::SearchResultTreeItem *>(fileIndex.internalPointer()); + Q_ASSERT(fileItem != 0); + for (int rowIndex = 0; rowIndex < fileItem->childrenCount(); ++rowIndex) { + QModelIndex textIndex = model->index(rowIndex, 0, fileIndex); + Internal::SearchResultTreeItem *rowItem = static_cast<Internal::SearchResultTreeItem *>(textIndex.internalPointer()); + if (rowItem->checkState()) + result << rowItem->item; + } + } + return result; +} + +void SearchResultWidget::updateMatchesFoundLabel() +{ + if (m_count == 0) + m_matchesFoundLabel->setText(tr("No matches found.")); + else + m_matchesFoundLabel->setText(tr("%n matches found.", 0, m_count)); +} + +#include "searchresultwidget.moc" diff --git a/src/plugins/coreplugin/find/searchresultwidget.h b/src/plugins/coreplugin/find/searchresultwidget.h new file mode 100644 index 0000000000..e9f64b4164 --- /dev/null +++ b/src/plugins/coreplugin/find/searchresultwidget.h @@ -0,0 +1,146 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef SEARCHRESULTWIDGET_H +#define SEARCHRESULTWIDGET_H + +#include "searchresultwindow.h" + +#include <coreplugin/infobar.h> + +#include <QWidget> + +QT_BEGIN_NAMESPACE +class QFrame; +class QLabel; +class QLineEdit; +class QToolButton; +class QCheckBox; +QT_END_NAMESPACE + +namespace Core { +namespace Internal { + +class SearchResultTreeView; +class SearchResultColor; + +class SearchResultWidget : public QWidget +{ + Q_OBJECT +public: + explicit SearchResultWidget(QWidget *parent = 0); + ~SearchResultWidget(); + + void setInfo(const QString &label, const QString &toolTip, const QString &term); + + void addResult(const QString &fileName, int lineNumber, const QString &lineText, + int searchTermStart, int searchTermLength, const QVariant &userData = QVariant()); + void addResults(const QList<SearchResultItem> &items, SearchResult::AddMode mode); + + int count() const; + + QString dontAskAgainGroup() const; + void setDontAskAgainGroup(const QString &group); + + void setTextToReplace(const QString &textToReplace); + QString textToReplace() const; + void setShowReplaceUI(bool visible); + + bool hasFocusInternally() const; + void setFocusInternally(); + bool canFocusInternally() const; + + void notifyVisibilityChanged(bool visible); + + void setTextEditorFont(const QFont &font, const SearchResultColor color); + + void setAutoExpandResults(bool expand); + void expandAll(); + void collapseAll(); + + void goToNext(); + void goToPrevious(); + + void restart(); + + void setSearchAgainSupported(bool supported); + void setSearchAgainEnabled(bool enabled); + +public slots: + void finishSearch(bool canceled); + void sendRequestPopup(); + +signals: + void activated(const Core::SearchResultItem &item); + void replaceButtonClicked(const QString &replaceText, const QList<Core::SearchResultItem> &checkedItems, bool preserveCase); + void searchAgainRequested(); + void cancelled(); + void paused(bool paused); + void restarted(); + void visibilityChanged(bool visible); + void requestPopup(bool focus); + + void navigateStateChanged(); + +private slots: + void continueAfterSizeWarning(); + void cancelAfterSizeWarning(); + void handleJumpToSearchResult(const SearchResultItem &item); + void handleReplaceButton(); + void cancel(); + void searchAgain(); + +private: + QList<SearchResultItem> checkedItems() const; + void updateMatchesFoundLabel(); + + SearchResultTreeView *m_searchResultTreeView; + int m_count; + QString m_dontAskAgainGroup; + QFrame *m_messageWidget; + Core::InfoBar m_infoBar; + Core::InfoBarDisplay m_infoBarDisplay; + bool m_isShowingReplaceUI; + QLabel *m_replaceLabel; + QLineEdit *m_replaceTextEdit; + QToolButton *m_replaceButton; + QToolButton *m_searchAgainButton; + QCheckBox *m_preserveCaseCheck; + bool m_searchAgainSupported; + QWidget *m_descriptionContainer; + QLabel *m_label; + QLabel *m_searchTerm; + QToolButton *m_cancelButton; + QLabel *m_matchesFoundLabel; +}; + +} // Internal +} // Find + +#endif // SEARCHRESULTWIDGET_H diff --git a/src/plugins/coreplugin/find/searchresultwindow.cpp b/src/plugins/coreplugin/find/searchresultwindow.cpp new file mode 100644 index 0000000000..dd4f87475f --- /dev/null +++ b/src/plugins/coreplugin/find/searchresultwindow.cpp @@ -0,0 +1,715 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "searchresultwindow.h" +#include "searchresultwidget.h" +#include "searchresultcolor.h" + +#include <coreplugin/icore.h> +#include <coreplugin/actionmanager/actionmanager.h> +#include <coreplugin/coreconstants.h> +#include <coreplugin/icontext.h> +#include <utils/qtcassert.h> + +#include <QSettings> +#include <QDebug> +#include <QFont> +#include <QAction> +#include <QToolButton> +#include <QComboBox> +#include <QScrollArea> +#include <QStackedWidget> + +static const char SETTINGSKEYSECTIONNAME[] = "SearchResults"; +static const char SETTINGSKEYEXPANDRESULTS[] = "ExpandResults"; +static const int MAX_SEARCH_HISTORY = 12; + +namespace Core { + +namespace Internal { + + class InternalScrollArea : public QScrollArea + { + Q_OBJECT + public: + explicit InternalScrollArea(QWidget *parent) + : QScrollArea(parent) + { + setFrameStyle(QFrame::NoFrame); + setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + } + + QSize sizeHint() const + { + if (widget()) + return widget()->size(); + return QScrollArea::sizeHint(); + } + }; + + class SearchResultWindowPrivate : public QObject + { + Q_OBJECT + public: + SearchResultWindowPrivate(SearchResultWindow *window); + bool isSearchVisible() const; + int visibleSearchIndex() const; + void setCurrentIndex(int index, bool focus); + + FindPlugin *m_plugin; + SearchResultWindow *q; + QList<Internal::SearchResultWidget *> m_searchResultWidgets; + QToolButton *m_expandCollapseButton; + QAction *m_expandCollapseAction; + static const bool m_initiallyExpand = false; + QWidget *m_spacer; + QComboBox *m_recentSearchesBox; + QStackedWidget *m_widget; + QList<SearchResult *> m_searchResults; + int m_currentIndex; + QFont m_font; + SearchResultColor m_color; + + public slots: + void setCurrentIndex(int index); + void moveWidgetToTop(); + void popupRequested(bool focus); + }; + + SearchResultWindowPrivate::SearchResultWindowPrivate(SearchResultWindow *window) + : q(window) + { + } + + bool SearchResultWindowPrivate::isSearchVisible() const + { + return m_currentIndex > 0; + } + + int SearchResultWindowPrivate::visibleSearchIndex() const + { + return m_currentIndex - 1; + } + + void SearchResultWindowPrivate::setCurrentIndex(int index, bool focus) + { + if (isSearchVisible()) + m_searchResultWidgets.at(visibleSearchIndex())->notifyVisibilityChanged(false); + m_currentIndex = index; + m_widget->setCurrentIndex(index); + m_recentSearchesBox->setCurrentIndex(index); + if (!isSearchVisible()) { + if (focus) + m_widget->currentWidget()->setFocus(); + m_expandCollapseButton->setEnabled(false); + } else { + if (focus) + m_searchResultWidgets.at(visibleSearchIndex())->setFocusInternally(); + m_searchResultWidgets.at(visibleSearchIndex())->notifyVisibilityChanged(true); + m_expandCollapseButton->setEnabled(true); + } + q->navigateStateChanged(); + } + + void SearchResultWindowPrivate::setCurrentIndex(int index) + { + setCurrentIndex(index, true/*focus*/); + } + + void SearchResultWindowPrivate::moveWidgetToTop() + { + SearchResultWidget *widget = qobject_cast<SearchResultWidget *>(sender()); + QTC_ASSERT(widget, return); + int index = m_searchResultWidgets.indexOf(widget); + if (index == 0) + return; // nothing to do + int internalIndex = index + 1/*account for "new search" entry*/; + QString searchEntry = m_recentSearchesBox->itemText(internalIndex); + + m_searchResultWidgets.removeAt(index); + m_widget->removeWidget(widget); + m_recentSearchesBox->removeItem(internalIndex); + SearchResult *result = m_searchResults.takeAt(index); + + m_searchResultWidgets.prepend(widget); + m_widget->insertWidget(1, widget); + m_recentSearchesBox->insertItem(1, searchEntry); + m_searchResults.prepend(result); + + // adapt the current index + if (index == visibleSearchIndex()) { + // was visible, so we switch + // this is the default case + m_currentIndex = 1; + m_widget->setCurrentIndex(1); + m_recentSearchesBox->setCurrentIndex(1); + } else if (visibleSearchIndex() < index) { + // academical case where the widget moved before the current widget + // only our internal book keeping needed + ++m_currentIndex; + } + } + + void SearchResultWindowPrivate::popupRequested(bool focus) + { + SearchResultWidget *widget = qobject_cast<SearchResultWidget *>(sender()); + QTC_ASSERT(widget, return); + int internalIndex = m_searchResultWidgets.indexOf(widget) + 1/*account for "new search" entry*/; + setCurrentIndex(internalIndex, focus); + q->popup(focus ? Core::IOutputPane::ModeSwitch | Core::IOutputPane::WithFocus + : Core::IOutputPane::NoModeSwitch); + } +} + +using namespace Core::Internal; + +/*! + \enum Core::SearchResultWindow::SearchMode + This enum type specifies whether a search should show the replace UI or not: + + \value SearchOnly + The search does not support replace. + \value SearchAndReplace + The search supports replace, so show the UI for it. +*/ + +/*! + \class Core::SearchResult + \brief The SearchResult class reports user interaction, such as the + activation of a search result item. + + Whenever a new search is initiated via startNewSearch, an instance of this + class is returned to provide the initiator with the hooks for handling user + interaction. +*/ + +/*! + \fn void SearchResult::activated(const Core::SearchResultItem &item) + Indicates that the user activated the search result \a item by + double-clicking it, for example. +*/ + +/*! + \fn void SearchResult::replaceButtonClicked(const QString &replaceText, const QList<Core::SearchResultItem> &checkedItems, bool preserveCase) + Indicates that the user initiated a text replace by selecting + \gui {Replace All}, for example. + + The signal reports the text to use for replacement in \a replaceText, + and the list of search result items that were selected by the user + in \a checkedItems. + The handler of this signal should apply the replace only on the selected + items. +*/ + +/*! + \class Core::SearchResultWindow + \brief The SearchResultWindow class is the implementation of a commonly + shared \gui{Search Results} output pane. Use it to show search results + to a user. + + Whenever you want to show the user a list of search results, or want + to present UI for a global search and replace, use the single instance + of this class. + + Except for being an implementation of a output pane, the + SearchResultWindow has a few functions and one enum that allows other + plugins to show their search results and hook into the user actions for + selecting an entry and performing a global replace. + + Whenever you start a search, call startNewSearch(SearchMode) to initialize + the \gui {Search Results} output pane. The parameter determines if the GUI for + replacing should be shown. + The function returns a SearchResult object that is your + hook into the signals from user interaction for this search. + When you produce search results, call addResults or addResult to add them + to the \gui {Search Results} output pane. + After the search has finished call finishSearch to inform the + \gui {Search Results} output pane about it. + + You will get activated signals via your SearchResult instance when + the user selects a search result item, and, if you started the search + with the SearchAndReplace option, the replaceButtonClicked signal + when the user requests a replace. +*/ + +/*! + \fn QString SearchResultWindow::displayName() const + \internal +*/ + +SearchResultWindow *SearchResultWindow::m_instance = 0; + +/*! + \internal +*/ +SearchResultWindow::SearchResultWindow(QWidget *newSearchPanel) + : d(new SearchResultWindowPrivate(this)) +{ + m_instance = this; + + d->m_spacer = new QWidget; + d->m_spacer->setMinimumWidth(30); + d->m_recentSearchesBox = new QComboBox; + d->m_recentSearchesBox->setSizeAdjustPolicy(QComboBox::AdjustToContents); + d->m_recentSearchesBox->addItem(tr("New Search")); + connect(d->m_recentSearchesBox, SIGNAL(activated(int)), d, SLOT(setCurrentIndex(int))); + + d->m_widget = new QStackedWidget; + d->m_widget->setWindowTitle(displayName()); + + InternalScrollArea *newSearchArea = new InternalScrollArea(d->m_widget); + newSearchArea->setWidget(newSearchPanel); + newSearchArea->setFocusProxy(newSearchPanel); + d->m_widget->addWidget(newSearchArea); + d->m_currentIndex = 0; + + d->m_expandCollapseButton = new QToolButton(d->m_widget); + d->m_expandCollapseButton->setAutoRaise(true); + + d->m_expandCollapseAction = new QAction(tr("Expand All"), this); + d->m_expandCollapseAction->setCheckable(true); + d->m_expandCollapseAction->setIcon(QIcon(QLatin1String(":/find/images/expand.png"))); + Core::Command *cmd = Core::ActionManager::registerAction( + d->m_expandCollapseAction, "Find.ExpandAll", + Core::Context(Core::Constants::C_GLOBAL)); + cmd->setAttribute(Core::Command::CA_UpdateText); + d->m_expandCollapseButton->setDefaultAction(cmd->action()); + + connect(d->m_expandCollapseAction, SIGNAL(toggled(bool)), this, SLOT(handleExpandCollapseToolButton(bool))); + readSettings(); +} + +/*! + \internal +*/ +SearchResultWindow::~SearchResultWindow() +{ + qDeleteAll(d->m_searchResults); + delete d->m_widget; + d->m_widget = 0; + delete d; +} + +/*! + Returns the single shared instance of the \gui {Search Results} output pane. +*/ +SearchResultWindow *SearchResultWindow::instance() +{ + return m_instance; +} + +/*! + \internal +*/ +void SearchResultWindow::visibilityChanged(bool visible) +{ + if (d->isSearchVisible()) + d->m_searchResultWidgets.at(d->visibleSearchIndex())->notifyVisibilityChanged(visible); +} + +/*! + \internal +*/ +QWidget *SearchResultWindow::outputWidget(QWidget *) +{ + return d->m_widget; +} + +/*! + \internal +*/ +QList<QWidget*> SearchResultWindow::toolBarWidgets() const +{ + return QList<QWidget*>() << d->m_expandCollapseButton << d->m_spacer << d->m_recentSearchesBox; +} + +/*! + Tells the \gui {Search Results} output pane to start a new search. + + The \a label should be a string that shortly describes the type of the + search, that is, the search filter and possibly the most relevant search + option, followed by a colon ':'. For example: \c {Project 'myproject':} + The \a searchTerm is shown after the colon. + The \a toolTip should elaborate on the search parameters, like file patterns that are searched and + find flags. + If \a cfgGroup is not empty, it will be used for storing the "do not ask again" + setting of a "this change cannot be undone" warning (which is implicitly requested + by passing a non-empty group). + Returns a SearchResult object that is used for signaling user interaction + with the results of this search. + The search result window owns the returned SearchResult + and might delete it any time, even while the search is running + (for example, when the user clears the \gui {Search Results} pane, or when + the user opens so many other searches + that this search falls out of the history). + +*/ +SearchResult *SearchResultWindow::startNewSearch(const QString &label, + const QString &toolTip, + const QString &searchTerm, + SearchMode searchOrSearchAndReplace, + const QString &cfgGroup) +{ + if (d->m_searchResults.size() >= MAX_SEARCH_HISTORY) { + d->m_searchResultWidgets.last()->notifyVisibilityChanged(false); + // widget first, because that might send interesting signals to SearchResult + delete d->m_searchResultWidgets.takeLast(); + delete d->m_searchResults.takeLast(); + d->m_recentSearchesBox->removeItem(d->m_recentSearchesBox->count()-1); + if (d->m_currentIndex >= d->m_recentSearchesBox->count()) { + // temporarily set the index to the last existing + d->m_currentIndex = d->m_recentSearchesBox->count() - 1; + } + } + Internal::SearchResultWidget *widget = new Internal::SearchResultWidget; + d->m_searchResultWidgets.prepend(widget); + d->m_widget->insertWidget(1, widget); + connect(widget, SIGNAL(navigateStateChanged()), this, SLOT(navigateStateChanged())); + connect(widget, SIGNAL(restarted()), d, SLOT(moveWidgetToTop())); + connect(widget, SIGNAL(requestPopup(bool)), d, SLOT(popupRequested(bool))); + widget->setTextEditorFont(d->m_font, d->m_color); + widget->setShowReplaceUI(searchOrSearchAndReplace != SearchOnly); + widget->setAutoExpandResults(d->m_expandCollapseAction->isChecked()); + widget->setInfo(label, toolTip, searchTerm); + if (searchOrSearchAndReplace == SearchAndReplace) + widget->setDontAskAgainGroup(cfgGroup); + SearchResult *result = new SearchResult(widget); + d->m_searchResults.prepend(result); + d->m_recentSearchesBox->insertItem(1, tr("%1 %2").arg(label, searchTerm)); + if (d->m_currentIndex > 0) + ++d->m_currentIndex; // so setCurrentIndex still knows about the right "currentIndex" and its widget + d->setCurrentIndex(1); + return result; +} + +/*! + Clears the current contents of the \gui {Search Results} output pane. +*/ +void SearchResultWindow::clearContents() +{ + for (int i = d->m_recentSearchesBox->count() - 1; i > 0 /* don't want i==0 */; --i) + d->m_recentSearchesBox->removeItem(i); + foreach (Internal::SearchResultWidget *widget, d->m_searchResultWidgets) + widget->notifyVisibilityChanged(false); + qDeleteAll(d->m_searchResultWidgets); + d->m_searchResultWidgets.clear(); + qDeleteAll(d->m_searchResults); + d->m_searchResults.clear(); + + d->m_currentIndex = 0; + d->m_widget->currentWidget()->setFocus(); + d->m_expandCollapseButton->setEnabled(false); + navigateStateChanged(); +} + +/*! + \internal +*/ +bool SearchResultWindow::hasFocus() const +{ + return d->m_widget->focusWidget() && d->m_widget->focusWidget()->hasFocus(); +} + +/*! + \internal +*/ +bool SearchResultWindow::canFocus() const +{ + if (d->isSearchVisible()) + return d->m_searchResultWidgets.at(d->visibleSearchIndex())->canFocusInternally(); + return true; +} + +/*! + \internal +*/ +void SearchResultWindow::setFocus() +{ + if (!d->isSearchVisible()) + d->m_widget->currentWidget()->setFocus(); + else + d->m_searchResultWidgets.at(d->visibleSearchIndex())->setFocusInternally(); +} + +/*! + \internal +*/ +void SearchResultWindow::setTextEditorFont(const QFont &font, + const QColor &textForegroundColor, + const QColor &textBackgroundColor, + const QColor &highlightForegroundColor, + const QColor &highlightBackgroundColor) +{ + d->m_font = font; + Internal::SearchResultColor color; + color.textBackground = textBackgroundColor; + color.textForeground = textForegroundColor; + color.highlightBackground = highlightBackgroundColor.isValid() + ? highlightBackgroundColor + : textBackgroundColor; + color.highlightForeground = highlightForegroundColor.isValid() + ? highlightForegroundColor + : textForegroundColor; + d->m_color = color; + foreach (Internal::SearchResultWidget *widget, d->m_searchResultWidgets) + widget->setTextEditorFont(font, color); +} + +void SearchResultWindow::openNewSearchPanel() +{ + d->setCurrentIndex(0); + popup(IOutputPane::ModeSwitch | IOutputPane::WithFocus | IOutputPane::EnsureSizeHint); +} + +/*! + \internal +*/ +void SearchResultWindow::handleExpandCollapseToolButton(bool checked) +{ + if (!d->isSearchVisible()) + return; + d->m_searchResultWidgets.at(d->visibleSearchIndex())->setAutoExpandResults(checked); + if (checked) { + d->m_expandCollapseAction->setText(tr("Collapse All")); + d->m_searchResultWidgets.at(d->visibleSearchIndex())->expandAll(); + } else { + d->m_expandCollapseAction->setText(tr("Expand All")); + d->m_searchResultWidgets.at(d->visibleSearchIndex())->collapseAll(); + } +} + +/*! + \internal +*/ +void SearchResultWindow::readSettings() +{ + QSettings *s = Core::ICore::settings(); + s->beginGroup(QLatin1String(SETTINGSKEYSECTIONNAME)); + d->m_expandCollapseAction->setChecked(s->value(QLatin1String(SETTINGSKEYEXPANDRESULTS), d->m_initiallyExpand).toBool()); + s->endGroup(); +} + +/*! + \internal +*/ +void SearchResultWindow::writeSettings() +{ + QSettings *s = Core::ICore::settings(); + s->beginGroup(QLatin1String(SETTINGSKEYSECTIONNAME)); + s->setValue(QLatin1String(SETTINGSKEYEXPANDRESULTS), d->m_expandCollapseAction->isChecked()); + s->endGroup(); +} + +/*! + \internal +*/ +int SearchResultWindow::priorityInStatusBar() const +{ + return 80; +} + +/*! + \internal +*/ +bool SearchResultWindow::canNext() const +{ + if (d->isSearchVisible()) + return d->m_searchResultWidgets.at(d->visibleSearchIndex())->count() > 0; + return false; +} + +/*! + \internal +*/ +bool SearchResultWindow::canPrevious() const +{ + return canNext(); +} + +/*! + \internal +*/ +void SearchResultWindow::goToNext() +{ + int index = d->m_widget->currentIndex(); + if (index != 0) + d->m_searchResultWidgets.at(index-1)->goToNext(); +} + +/*! + \internal +*/ +void SearchResultWindow::goToPrev() +{ + int index = d->m_widget->currentIndex(); + if (index != 0) + d->m_searchResultWidgets.at(index-1)->goToPrevious(); +} + +/*! + \internal +*/ +bool SearchResultWindow::canNavigate() const +{ + return true; +} + +/*! + \internal +*/ +SearchResult::SearchResult(SearchResultWidget *widget) + : m_widget(widget) +{ + connect(widget, SIGNAL(activated(Core::SearchResultItem)), + this, SIGNAL(activated(Core::SearchResultItem))); + connect(widget, SIGNAL(replaceButtonClicked(QString,QList<Core::SearchResultItem>,bool)), + this, SIGNAL(replaceButtonClicked(QString,QList<Core::SearchResultItem>,bool))); + connect(widget, SIGNAL(cancelled()), + this, SIGNAL(cancelled())); + connect(widget, SIGNAL(paused(bool)), + this, SIGNAL(paused(bool))); + connect(widget, SIGNAL(visibilityChanged(bool)), + this, SIGNAL(visibilityChanged(bool))); + connect(widget, SIGNAL(searchAgainRequested()), + this, SIGNAL(searchAgainRequested())); +} + +/*! + Attaches some random \a data to this search, that you can use later. + + \sa userData() +*/ +void SearchResult::setUserData(const QVariant &data) +{ + m_userData = data; +} + +/*! + Returns the data that was attached to this search by calling + setUserData(). + + \sa setUserData() +*/ +QVariant SearchResult::userData() const +{ + return m_userData; +} + +/*! + Returns the text that should replace the text in search results. +*/ +QString SearchResult::textToReplace() const +{ + return m_widget->textToReplace(); +} + +int SearchResult::count() const +{ + return m_widget->count(); +} + +void SearchResult::setSearchAgainSupported(bool supported) +{ + m_widget->setSearchAgainSupported(supported); +} + +/*! + Adds a single result line to the \gui {Search Results} output pane. + + \a fileName, \a lineNumber, and \a lineText are shown on the result line. + \a searchTermStart and \a searchTermLength specify the region that + should be visually marked (string position and length in \a lineText). + You can attach arbitrary \a userData to the search result, which can + be used, for example, when reacting to the signals of the search results + for your search. + + \sa addResults() +*/ +void SearchResult::addResult(const QString &fileName, int lineNumber, const QString &lineText, + int searchTermStart, int searchTermLength, const QVariant &userData) +{ + m_widget->addResult(fileName, lineNumber, lineText, + searchTermStart, searchTermLength, userData); + emit countChanged(m_widget->count()); +} + +/*! + Adds the search result \a items to the \gui {Search Results} output pane. + + \sa addResult() +*/ +void SearchResult::addResults(const QList<SearchResultItem> &items, AddMode mode) +{ + m_widget->addResults(items, mode); + emit countChanged(m_widget->count()); +} + +/*! + Notifies the \gui {Search Results} output pane that the current search + has finished, and the UI should reflect that. +*/ +void SearchResult::finishSearch(bool canceled) +{ + m_widget->finishSearch(canceled); +} + +/*! + Sets the value in the UI element that allows the user to type + the text that should replace text in search results to \a textToReplace. +*/ +void SearchResult::setTextToReplace(const QString &textToReplace) +{ + m_widget->setTextToReplace(textToReplace); +} + +/*! + * Removes all search results. + */ +void SearchResult::restart() +{ + m_widget->restart(); +} + +void SearchResult::setSearchAgainEnabled(bool enabled) +{ + m_widget->setSearchAgainEnabled(enabled); +} + +/*! + * Opens the \gui {Search Results} output pane with this search. + */ +void SearchResult::popup() +{ + m_widget->sendRequestPopup(); +} + +} // namespace Core + +#include "searchresultwindow.moc" diff --git a/src/plugins/coreplugin/find/searchresultwindow.h b/src/plugins/coreplugin/find/searchresultwindow.h new file mode 100644 index 0000000000..c13b9ce32f --- /dev/null +++ b/src/plugins/coreplugin/find/searchresultwindow.h @@ -0,0 +1,198 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef SEARCHRESULTWINDOW_H +#define SEARCHRESULTWINDOW_H + +#include <coreplugin/ioutputpane.h> + +#include <QVariant> +#include <QStringList> +#include <QIcon> + +QT_BEGIN_NAMESPACE +class QFont; +QT_END_NAMESPACE + +namespace Core { +namespace Internal { + class SearchResultTreeView; + class SearchResultWindowPrivate; + class SearchResultWidget; +} +class FindPlugin; +class SearchResultWindow; + +class CORE_EXPORT SearchResultItem +{ +public: + SearchResultItem() + : textMarkPos(-1), + textMarkLength(0), + lineNumber(-1), + useTextEditorFont(false) + { + } + + SearchResultItem(const SearchResultItem &other) + : path(other.path), + text(other.text), + textMarkPos(other.textMarkPos), + textMarkLength(other.textMarkLength), + icon(other.icon), + lineNumber(other.lineNumber), + useTextEditorFont(other.useTextEditorFont), + userData(other.userData) + { + } + + QStringList path; // hierarchy to the parent item of this item + QString text; // text to show for the item itself + int textMarkPos; // 0-based starting position for a mark (-1 for no mark) + int textMarkLength; // length of the mark (0 for no mark) + QIcon icon; // icon to show in front of the item (by be null icon to hide) + int lineNumber; // (0 or -1 for no line number) + bool useTextEditorFont; + QVariant userData; // user data for identification of the item +}; + +class CORE_EXPORT SearchResult : public QObject +{ + Q_OBJECT + +public: + enum AddMode { + AddSorted, + AddOrdered + }; + + void setUserData(const QVariant &data); + QVariant userData() const; + QString textToReplace() const; + int count() const; + void setSearchAgainSupported(bool supported); + +public slots: + void addResult(const QString &fileName, int lineNumber, const QString &lineText, + int searchTermStart, int searchTermLength, const QVariant &userData = QVariant()); + void addResults(const QList<SearchResultItem> &items, AddMode mode); + void finishSearch(bool canceled); + void setTextToReplace(const QString &textToReplace); + void restart(); + void setSearchAgainEnabled(bool enabled); + void popup(); + +signals: + void activated(const Core::SearchResultItem &item); + void replaceButtonClicked(const QString &replaceText, const QList<Core::SearchResultItem> &checkedItems, bool preserveCase); + void cancelled(); + void paused(bool paused); + void visibilityChanged(bool visible); + void countChanged(int count); + void searchAgainRequested(); + void requestEnabledCheck(); + +private: + SearchResult(Internal::SearchResultWidget *widget); + friend class SearchResultWindow; // for the constructor + +private: + Internal::SearchResultWidget *m_widget; + QVariant m_userData; +}; + +class CORE_EXPORT SearchResultWindow : public Core::IOutputPane +{ + Q_OBJECT + +public: + enum SearchMode { + SearchOnly, + SearchAndReplace + }; + + + SearchResultWindow(QWidget *newSearchPanel); + virtual ~SearchResultWindow(); + static SearchResultWindow *instance(); + + QWidget *outputWidget(QWidget *); + QList<QWidget*> toolBarWidgets() const; + + QString displayName() const { return tr("Search Results"); } + int priorityInStatusBar() const; + void visibilityChanged(bool visible); + bool hasFocus() const; + bool canFocus() const; + void setFocus(); + + bool canNext() const; + bool canPrevious() const; + void goToNext(); + void goToPrev(); + bool canNavigate() const; + + void setTextEditorFont(const QFont &font, + const QColor &textForegroundColor, + const QColor &textBackgroundColor, + const QColor &highlightForegroundColor, + const QColor &highlightBackgroundColor); + void openNewSearchPanel(); + + // The search result window owns the returned SearchResult + // and might delete it any time, even while the search is running + // (e.g. when the user clears the search result pane, or if the user opens so many other searches + // that this search falls out of the history). + SearchResult *startNewSearch(const QString &label, + const QString &toolTip, + const QString &searchTerm, + SearchMode searchOrSearchAndReplace = SearchOnly, + const QString &cfgGroup = QString()); + +public slots: + void clearContents(); + +private slots: + void handleExpandCollapseToolButton(bool checked); + +public: // Used by plugin, do not use + void writeSettings(); + +private: + void readSettings(); + + Internal::SearchResultWindowPrivate *d; + static SearchResultWindow *m_instance; +}; + +} // namespace Core + +Q_DECLARE_METATYPE(Core::SearchResultItem) + +#endif // SEARCHRESULTWINDOW_H diff --git a/src/plugins/coreplugin/find/textfindconstants.h b/src/plugins/coreplugin/find/textfindconstants.h new file mode 100644 index 0000000000..53292327b3 --- /dev/null +++ b/src/plugins/coreplugin/find/textfindconstants.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef TEXTFINDCONSTANTS_H +#define TEXTFINDCONSTANTS_H + +#include <coreplugin/core_global.h> + +#include <QMetaType> +#include <QFlags> +#include <QTextDocument> + +namespace Core { +namespace Constants { + +const char M_FIND[] = "Find.FindMenu"; +const char M_FIND_ADVANCED[] = "Find.FindAdvancedMenu"; +const char G_FIND_CURRENTDOCUMENT[] = "Find.FindMenu.CurrentDocument"; +const char G_FIND_FILTERS[] = "Find.FindMenu.Filters"; +const char G_FIND_FLAGS[] = "Find.FindMenu.Flags"; +const char G_FIND_ACTIONS[] = "Find.FindMenu.Actions"; + +const char ADVANCED_FIND[] = "Find.Dialog"; +const char FIND_IN_DOCUMENT[] = "Find.FindInCurrentDocument"; +const char FIND_NEXT_SELECTED[]= "Find.FindNextSelected"; +const char FIND_PREV_SELECTED[]= "Find.FindPreviousSelected"; +const char FIND_NEXT[] = "Find.FindNext"; +const char FIND_PREVIOUS[] = "Find.FindPrevious"; +const char REPLACE[] = "Find.Replace"; +const char REPLACE_NEXT[] = "Find.ReplaceNext"; +const char REPLACE_PREVIOUS[] = "Find.ReplacePrevious"; +const char REPLACE_ALL[] = "Find.ReplaceAll"; +const char CASE_SENSITIVE[] = "Find.CaseSensitive"; +const char WHOLE_WORDS[] = "Find.WholeWords"; +const char REGULAR_EXPRESSIONS[] = "Find.RegularExpressions"; +const char PRESERVE_CASE[] = "Find.PreserveCase"; +const char TASK_SEARCH[] = "Find.Task.Search"; + +} // namespace Constants + +enum FindFlag { + FindBackward = 0x01, + FindCaseSensitively = 0x02, + FindWholeWords = 0x04, + FindRegularExpression = 0x08, + FindPreserveCase = 0x10 +}; +Q_DECLARE_FLAGS(FindFlags, FindFlag) + +// defined in findplugin.cpp +QTextDocument::FindFlags CORE_EXPORT textDocumentFlagsForFindFlags(FindFlags flags); + +} // namespace Core + +Q_DECLARE_OPERATORS_FOR_FLAGS(Core::FindFlags) +Q_DECLARE_METATYPE(Core::FindFlags) + +#endif // TEXTFINDCONSTANTS_H diff --git a/src/plugins/coreplugin/find/treeviewfind.cpp b/src/plugins/coreplugin/find/treeviewfind.cpp new file mode 100644 index 0000000000..6a1c937557 --- /dev/null +++ b/src/plugins/coreplugin/find/treeviewfind.cpp @@ -0,0 +1,277 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "treeviewfind.h" + +#include <QTreeView> +#include <QTextCursor> +#include <QModelIndex> + +namespace Core { + +class ItemModelFindPrivate +{ +public: + explicit ItemModelFindPrivate(QTreeView *view, int role) + : m_view(view) + , m_incrementalWrappedState(false), + m_role(role) + { + } + + QTreeView *m_view; + QModelIndex m_incrementalFindStart; + bool m_incrementalWrappedState; + int m_role; +}; + +TreeViewFind::TreeViewFind(QTreeView *view, int role) + : d(new ItemModelFindPrivate(view, role)) +{ +} + +TreeViewFind::~TreeViewFind() +{ + delete d; +} + +bool TreeViewFind::supportsReplace() const +{ + return false; +} + +FindFlags TreeViewFind::supportedFindFlags() const +{ + return FindBackward | FindCaseSensitively | FindRegularExpression | FindWholeWords; +} + +void TreeViewFind::resetIncrementalSearch() +{ + d->m_incrementalFindStart = QModelIndex(); + d->m_incrementalWrappedState = false; +} + +void TreeViewFind::clearResults() +{ +} + +QString TreeViewFind::currentFindString() const +{ + return QString(); +} + +QString TreeViewFind::completedFindString() const +{ + return QString(); +} + +void TreeViewFind::highlightAll(const QString &/*txt*/, FindFlags /*findFlags*/) +{ +} + +IFindSupport::Result TreeViewFind::findIncremental(const QString &txt, FindFlags findFlags) +{ + if (!d->m_incrementalFindStart.isValid()) { + d->m_incrementalFindStart = d->m_view->currentIndex(); + d->m_incrementalWrappedState = false; + } + d->m_view->setCurrentIndex(d->m_incrementalFindStart); + bool wrapped = false; + IFindSupport::Result result = find(txt, findFlags, true/*startFromCurrent*/, + &wrapped); + if (wrapped != d->m_incrementalWrappedState) { + d->m_incrementalWrappedState = wrapped; + showWrapIndicator(d->m_view); + } + return result; +} + +IFindSupport::Result TreeViewFind::findStep(const QString &txt, FindFlags findFlags) +{ + bool wrapped = false; + IFindSupport::Result result = find(txt, findFlags, false/*startFromNext*/, + &wrapped); + if (wrapped) + showWrapIndicator(d->m_view); + if (result == IFindSupport::Found) { + d->m_incrementalFindStart = d->m_view->currentIndex(); + d->m_incrementalWrappedState = false; + } + return result; +} + +IFindSupport::Result TreeViewFind::find(const QString &searchTxt, + FindFlags findFlags, + bool startFromCurrentIndex, + bool *wrapped) +{ + if (wrapped) + *wrapped = false; + if (searchTxt.isEmpty()) + return IFindSupport::NotFound; + + QTextDocument::FindFlags flags = textDocumentFlagsForFindFlags(findFlags); + QModelIndex resultIndex; + QModelIndex currentIndex = d->m_view->currentIndex(); + QModelIndex index = currentIndex; + int currentRow = currentIndex.row(); + + bool backward = (flags & QTextDocument::FindBackward); + if (wrapped) + *wrapped = false; + bool anyWrapped = false; + bool stepWrapped = false; + if (!startFromCurrentIndex) + index = followingIndex(index, backward, &stepWrapped); + else + currentRow = -1; + do { + anyWrapped |= stepWrapped; // update wrapped state if we actually stepped to next/prev item + if (index.isValid()) { + const QString &text = d->m_view->model()->data( + index, d->m_role).toString(); + if (findFlags & FindRegularExpression) { + bool sensitive = (findFlags & FindCaseSensitively); + QRegExp searchExpr = QRegExp(searchTxt, + (sensitive ? Qt::CaseSensitive : + Qt::CaseInsensitive)); + if (searchExpr.indexIn(text) != -1 + && d->m_view->model()->flags(index) & Qt::ItemIsSelectable + && (index.row() != currentRow || index.parent() != currentIndex.parent())) + resultIndex = index; + } else { + QTextDocument doc(text); + if (!doc.find(searchTxt, 0, + flags & (FindCaseSensitively | FindWholeWords)).isNull() + && d->m_view->model()->flags(index) & Qt::ItemIsSelectable + && (index.row() != currentRow || index.parent() != currentIndex.parent())) + resultIndex = index; + } + } + index = followingIndex(index, backward, &stepWrapped); + } while (!resultIndex.isValid() && index.isValid() && index != currentIndex); + + if (resultIndex.isValid()) { + d->m_view->setCurrentIndex(resultIndex); + d->m_view->scrollTo(resultIndex); + if (resultIndex.parent().isValid()) + d->m_view->expand(resultIndex.parent()); + if (wrapped) + *wrapped = anyWrapped; + return IFindSupport::Found; + } + return IFindSupport::NotFound; +} + +QModelIndex TreeViewFind::nextIndex(const QModelIndex &idx, bool *wrapped) const +{ + if (wrapped) + *wrapped = false; + QAbstractItemModel *model = d->m_view->model(); + // pathological + if (!idx.isValid()) + return model->index(0, 0); + + // same parent has more columns, go to next column + if (idx.column() + 1 < model->columnCount(idx.parent())) + return model->index(idx.row(), idx.column() + 1, idx.parent()); + + // tree views have their children attached to first column + // make sure we are at first column + QModelIndex current = model->index(idx.row(), 0, idx.parent()); + + // check for children + if (model->rowCount(current) > 0) + return current.child(0, 0); + + // no more children, go up and look for parent with more children + QModelIndex nextIndex; + while (!nextIndex.isValid()) { + int row = current.row(); + current = current.parent(); + + if (row + 1 < model->rowCount(current)) { + // Same parent has another child + nextIndex = model->index(row + 1, 0, current); + } else { + // go up one parent + if (!current.isValid()) { + // we start from the beginning + if (wrapped) + *wrapped = true; + nextIndex = model->index(0, 0); + } + } + } + return nextIndex; +} + +QModelIndex TreeViewFind::prevIndex(const QModelIndex &idx, bool *wrapped) const +{ + if (wrapped) + *wrapped = false; + QAbstractItemModel *model = d->m_view->model(); + // if same parent has earlier columns, just move there + if (idx.column() > 0) + return model->index(idx.row(), idx.column() - 1, idx.parent()); + + QModelIndex current = idx; + bool checkForChildren = true; + if (current.isValid()) { + int row = current.row(); + if (row > 0) { + current = model->index(row - 1, 0, current.parent()); + } else { + current = current.parent(); + checkForChildren = !current.isValid(); + if (checkForChildren && wrapped) { + // we start from the end + *wrapped = true; + } + } + } + if (checkForChildren) { + // traverse down the hierarchy + while (int rc = model->rowCount(current)) { + current = model->index(rc - 1, 0, current); + } + } + // set to last column + current = model->index(current.row(), model->columnCount(current.parent()) - 1, current.parent()); + return current; +} + +QModelIndex TreeViewFind::followingIndex(const QModelIndex &idx, bool backward, bool *wrapped) +{ + if (backward) + return prevIndex(idx, wrapped); + return nextIndex(idx, wrapped); +} + +} // namespace Core diff --git a/src/plugins/coreplugin/find/treeviewfind.h b/src/plugins/coreplugin/find/treeviewfind.h new file mode 100644 index 0000000000..06842d2b79 --- /dev/null +++ b/src/plugins/coreplugin/find/treeviewfind.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef TREEVIEWFIND_H +#define TREEVIEWFIND_H + +#include "ifindsupport.h" + +QT_BEGIN_NAMESPACE +class QTreeView; +class QModelIndex; +QT_END_NAMESPACE + +namespace Core { +class ItemModelFindPrivate; + +class CORE_EXPORT TreeViewFind : public IFindSupport +{ + Q_OBJECT +public: + explicit TreeViewFind(QTreeView *view, int role = Qt::DisplayRole); + virtual ~TreeViewFind(); + + bool supportsReplace() const; + FindFlags supportedFindFlags() const; + void resetIncrementalSearch(); + void clearResults(); + QString currentFindString() const; + QString completedFindString() const; + + virtual void highlightAll(const QString &txt, FindFlags findFlags); + Result findIncremental(const QString &txt, FindFlags findFlags); + Result findStep(const QString &txt, FindFlags findFlags); + +private: + Result find(const QString &txt, FindFlags findFlags, + bool startFromCurrentIndex, bool *wrapped); + QModelIndex nextIndex(const QModelIndex &idx, bool *wrapped) const; + QModelIndex prevIndex(const QModelIndex &idx, bool *wrapped) const; + QModelIndex followingIndex(const QModelIndex &idx, bool backward, + bool *wrapped); + +private: + ItemModelFindPrivate *d; +}; + +} // namespace Core + +#endif // TREEVIEWFIND_H diff --git a/src/plugins/coreplugin/locator/basefilefilter.cpp b/src/plugins/coreplugin/locator/basefilefilter.cpp new file mode 100644 index 0000000000..920f5b570d --- /dev/null +++ b/src/plugins/coreplugin/locator/basefilefilter.cpp @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "basefilefilter.h" + +#include <coreplugin/editormanager/editormanager.h> +#include <utils/fileutils.h> + +#include <QDir> +#include <QStringMatcher> + +using namespace Core; +using namespace Core; +using namespace Utils; + +BaseFileFilter::BaseFileFilter() + : m_forceNewSearchList(false) +{ +} + +QList<LocatorFilterEntry> BaseFileFilter::matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future, const QString &origEntry) +{ + updateFiles(); + QList<LocatorFilterEntry> betterEntries; + QList<LocatorFilterEntry> goodEntries; + QString needle = trimWildcards(origEntry); + const QString lineNoSuffix = EditorManager::splitLineNumber(&needle); + QStringMatcher matcher(needle, Qt::CaseInsensitive); + const QChar asterisk = QLatin1Char('*'); + QRegExp regexp(asterisk + needle+ asterisk, Qt::CaseInsensitive, QRegExp::Wildcard); + if (!regexp.isValid()) + return betterEntries; + const QChar pathSeparator = QDir::separator(); + const bool hasPathSeparator = needle.contains(pathSeparator); + const bool hasWildcard = needle.contains(asterisk) || needle.contains(QLatin1Char('?')); + QStringList searchListPaths; + QStringList searchListNames; + const bool containsPreviousEntry = !m_previousEntry.isEmpty() + && needle.contains(m_previousEntry); + const bool pathSeparatorAdded = !m_previousEntry.contains(pathSeparator) + && needle.contains(pathSeparator); + if (!m_forceNewSearchList && containsPreviousEntry && !pathSeparatorAdded) { + searchListPaths = m_previousResultPaths; + searchListNames = m_previousResultNames; + } else { + searchListPaths = m_files; + searchListNames = m_fileNames; + } + m_previousResultPaths.clear(); + m_previousResultNames.clear(); + m_forceNewSearchList = false; + m_previousEntry = needle; + const Qt::CaseSensitivity caseSensitivityForPrefix = caseSensitivity(needle); + QStringListIterator paths(searchListPaths); + QStringListIterator names(searchListNames); + while (paths.hasNext() && names.hasNext()) { + if (future.isCanceled()) + break; + + QString path = paths.next(); + QString name = names.next(); + QString matchText = hasPathSeparator ? path : name; + if ((hasWildcard && regexp.exactMatch(matchText)) + || (!hasWildcard && matcher.indexIn(matchText) != -1)) { + QFileInfo fi(path); + LocatorFilterEntry entry(this, fi.fileName(), QString(path + lineNoSuffix)); + entry.extraInfo = FileUtils::shortNativePath(FileName(fi)); + entry.fileName = path; + if (matchText.startsWith(needle, caseSensitivityForPrefix)) + betterEntries.append(entry); + else + goodEntries.append(entry); + m_previousResultPaths.append(path); + m_previousResultNames.append(name); + } + } + + betterEntries.append(goodEntries); + return betterEntries; +} + +void BaseFileFilter::accept(Core::LocatorFilterEntry selection) const +{ + EditorManager::openEditor(selection.internalData.toString(), Id(), + EditorManager::CanContainLineNumber); +} + +void BaseFileFilter::generateFileNames() +{ + m_fileNames.clear(); + foreach (const QString &fileName, m_files) { + QFileInfo fi(fileName); + m_fileNames.append(fi.fileName()); + } + m_forceNewSearchList = true; +} + +void BaseFileFilter::updateFiles() +{ +} diff --git a/src/plugins/coreplugin/locator/basefilefilter.h b/src/plugins/coreplugin/locator/basefilefilter.h new file mode 100644 index 0000000000..44102b30e4 --- /dev/null +++ b/src/plugins/coreplugin/locator/basefilefilter.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef BASEFILEFILTER_H +#define BASEFILEFILTER_H + +#include "ilocatorfilter.h" + +#include <QStringList> + +namespace Core { + +class CORE_EXPORT BaseFileFilter : public Core::ILocatorFilter +{ + Q_OBJECT + +public: + BaseFileFilter(); + QList<Core::LocatorFilterEntry> matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future, const QString &entry); + void accept(Core::LocatorFilterEntry selection) const; + +protected: + virtual void updateFiles(); + void generateFileNames(); + + inline QStringList &files() { return m_files; } + inline const QStringList &files() const { return m_files; } + +private: + QStringList m_files; + QStringList m_fileNames; + QStringList m_previousResultPaths; + QStringList m_previousResultNames; + bool m_forceNewSearchList; + QString m_previousEntry; +}; + +} // namespace Core + +#endif // BASEFILEFILTER_H diff --git a/src/plugins/coreplugin/locator/commandlocator.cpp b/src/plugins/coreplugin/locator/commandlocator.cpp new file mode 100644 index 0000000000..13b4ec1485 --- /dev/null +++ b/src/plugins/coreplugin/locator/commandlocator.cpp @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "commandlocator.h" + +#include <coreplugin/actionmanager/command.h> + +#include <utils/qtcassert.h> + +#include <QAction> + +namespace Core { + +struct CommandLocatorPrivate +{ + QList<Core::Command *> commands; +}; + +CommandLocator::CommandLocator(Core::Id id, + const QString &displayName, + const QString &shortCutString, + QObject *parent) : + Core::ILocatorFilter(parent), + d(new CommandLocatorPrivate) +{ + setId(id); + setDisplayName(displayName); + setShortcutString(shortCutString); +} + +CommandLocator::~CommandLocator() +{ + delete d; +} + +void CommandLocator::appendCommand(Core::Command *cmd) +{ + d->commands.push_back(cmd); +} + +QList<Core::LocatorFilterEntry> CommandLocator::matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future, const QString &entry) +{ + QList<LocatorFilterEntry> goodEntries; + QList<LocatorFilterEntry> betterEntries; + // Get active, enabled actions matching text, store in list. + // Reference via index in extraInfo. + const QChar ampersand = QLatin1Char('&'); + const Qt::CaseSensitivity caseSensitivity_ = caseSensitivity(entry); + const int count = d->commands.size(); + for (int i = 0; i < count; i++) { + if (future.isCanceled()) + break; + if (d->commands.at(i)->isActive()) { + if (QAction *action = d->commands.at(i)->action()) + if (action->isEnabled()) { + QString text = action->text(); + text.remove(ampersand); + if (text.startsWith(entry, caseSensitivity_)) + betterEntries.append(LocatorFilterEntry(this, text, QVariant(i))); + else if (text.contains(entry, caseSensitivity_)) + goodEntries.append(LocatorFilterEntry(this, text, QVariant(i))); + } + } + } + betterEntries.append(goodEntries); + return betterEntries; +} + +void CommandLocator::accept(Core::LocatorFilterEntry entry) const +{ + // Retrieve action via index. + const int index = entry.internalData.toInt(); + QTC_ASSERT(index >= 0 && index < d->commands.size(), return); + QAction *action = d->commands.at(index)->action(); + QTC_ASSERT(action->isEnabled(), return); + action->trigger(); +} + +void CommandLocator::refresh(QFutureInterface<void> &) +{ +} + +} // namespace Core diff --git a/src/plugins/coreplugin/locator/commandlocator.h b/src/plugins/coreplugin/locator/commandlocator.h new file mode 100644 index 0000000000..d62f5bdfd4 --- /dev/null +++ b/src/plugins/coreplugin/locator/commandlocator.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef COMMANDLOCATOR_H +#define COMMANDLOCATOR_H + +#include "ilocatorfilter.h" + +namespace Core { + +/* Command locators: Provides completion for a set of + * Core::Command's by sub-string of their action's text. */ +class Command; +struct CommandLocatorPrivate; + +class CORE_EXPORT CommandLocator : public Core::ILocatorFilter +{ + Q_OBJECT + +public: + CommandLocator(Core::Id id, const QString &displayName, + const QString &shortCutString, QObject *parent = 0); + ~CommandLocator(); + + void appendCommand(Core::Command *cmd); + + QList<LocatorFilterEntry> matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future, const QString &entry); + void accept(LocatorFilterEntry selection) const; + void refresh(QFutureInterface<void> &future); + +private: + CommandLocatorPrivate *d; +}; + +} // namespace Core + +#endif // COMMANDLOCATOR_H diff --git a/src/plugins/coreplugin/locator/directoryfilter.cpp b/src/plugins/coreplugin/locator/directoryfilter.cpp new file mode 100644 index 0000000000..dd7639cfe4 --- /dev/null +++ b/src/plugins/coreplugin/locator/directoryfilter.cpp @@ -0,0 +1,204 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "directoryfilter.h" + +#include <QFileDialog> +#include <utils/filesearch.h> + +using namespace Core; +using namespace Core::Internal; + +DirectoryFilter::DirectoryFilter() + : m_name(tr("Generic Directory Filter")), + m_dialog(0) +{ + setId(Core::Id::fromString(m_name)); + setIncludedByDefault(true); + setDisplayName(m_name); + + m_filters.append(QLatin1String("*.h")); + m_filters.append(QLatin1String("*.cpp")); + m_filters.append(QLatin1String("*.ui")); + m_filters.append(QLatin1String("*.qrc")); +} + +QByteArray DirectoryFilter::saveState() const +{ + QMutexLocker locker(&m_lock); + QByteArray value; + QDataStream out(&value, QIODevice::WriteOnly); + out << m_name; + out << m_directories; + out << m_filters; + out << shortcutString(); + out << isIncludedByDefault(); + out << files(); + return value; +} + +bool DirectoryFilter::restoreState(const QByteArray &state) +{ + QMutexLocker locker(&m_lock); + + QString shortcut; + bool defaultFilter; + + QDataStream in(state); + in >> m_name; + in >> m_directories; + in >> m_filters; + in >> shortcut; + in >> defaultFilter; + in >> files(); + + setShortcutString(shortcut); + setIncludedByDefault(defaultFilter); + + generateFileNames(); + return true; +} + +bool DirectoryFilter::openConfigDialog(QWidget *parent, bool &needsRefresh) +{ + bool success = false; + QDialog dialog(parent); + m_dialog = &dialog; + m_ui.setupUi(&dialog); + dialog.setWindowTitle(tr("Filter Configuration")); + connect(m_ui.addButton, SIGNAL(clicked()), + this, SLOT(addDirectory()), Qt::DirectConnection); + connect(m_ui.editButton, SIGNAL(clicked()), + this, SLOT(editDirectory()), Qt::DirectConnection); + connect(m_ui.removeButton, SIGNAL(clicked()), + this, SLOT(removeDirectory()), Qt::DirectConnection); + connect(m_ui.directoryList, SIGNAL(itemSelectionChanged()), + this, SLOT(updateOptionButtons()), Qt::DirectConnection); + m_ui.nameEdit->setText(m_name); + m_ui.nameEdit->selectAll(); + m_ui.directoryList->clear(); + m_ui.directoryList->addItems(m_directories); + m_ui.fileTypeEdit->setText(m_filters.join(QString(QLatin1Char(',')))); + m_ui.shortcutEdit->setText(shortcutString()); + m_ui.defaultFlag->setChecked(!isIncludedByDefault()); + updateOptionButtons(); + if (dialog.exec() == QDialog::Accepted) { + QMutexLocker locker(&m_lock); + bool directoriesChanged = false; + QStringList oldDirectories = m_directories; + QStringList oldFilters = m_filters; + m_name = m_ui.nameEdit->text().trimmed(); + m_directories.clear(); + int oldCount = oldDirectories.count(); + int newCount = m_ui.directoryList->count(); + if (oldCount != newCount) + directoriesChanged = true; + for (int i = 0; i < newCount; ++i) { + m_directories.append(m_ui.directoryList->item(i)->text()); + if (!directoriesChanged && m_directories.at(i) != oldDirectories.at(i)) + directoriesChanged = true; + } + m_filters = m_ui.fileTypeEdit->text().trimmed().split(QLatin1Char(',')); + setShortcutString(m_ui.shortcutEdit->text().trimmed()); + setIncludedByDefault(!m_ui.defaultFlag->isChecked()); + if (directoriesChanged || oldFilters != m_filters) + needsRefresh = true; + success = true; + } + return success; +} + +void DirectoryFilter::addDirectory() +{ + QString dir = QFileDialog::getExistingDirectory(m_dialog, tr("Select Directory")); + if (!dir.isEmpty()) + m_ui.directoryList->addItem(dir); +} + +void DirectoryFilter::editDirectory() +{ + if (m_ui.directoryList->selectedItems().count() < 1) + return; + QListWidgetItem *currentItem = m_ui.directoryList->selectedItems().at(0); + QString dir = QFileDialog::getExistingDirectory(m_dialog, tr("Select Directory"), + currentItem->text()); + if (!dir.isEmpty()) + currentItem->setText(dir); +} + +void DirectoryFilter::removeDirectory() +{ + if (m_ui.directoryList->selectedItems().count() < 1) + return; + QListWidgetItem *currentItem = m_ui.directoryList->selectedItems().at(0); + delete m_ui.directoryList->takeItem(m_ui.directoryList->row(currentItem)); +} + +void DirectoryFilter::updateOptionButtons() +{ + bool haveSelectedItem = (m_ui.directoryList->selectedItems().count() > 0); + m_ui.editButton->setEnabled(haveSelectedItem); + m_ui.removeButton->setEnabled(haveSelectedItem); +} + +void DirectoryFilter::refresh(QFutureInterface<void> &future) +{ + QStringList directories; + { + QMutexLocker locker(&m_lock); + if (m_directories.count() < 1) { + files().clear(); + generateFileNames(); + future.setProgressRange(0, 1); + future.setProgressValueAndText(1, tr("%1 filter update: 0 files").arg(m_name)); + return; + } + directories = m_directories; + } + Utils::SubDirFileIterator it(directories, m_filters); + future.setProgressRange(0, it.maxProgress()); + QStringList filesFound; + while (!future.isCanceled() && it.hasNext()) { + filesFound << it.next(); + if (future.isProgressUpdateNeeded() + || future.progressValue() == 0 /*workaround for regression in Qt*/) { + future.setProgressValueAndText(it.currentProgress(), + tr("%1 filter update: %n files", 0, filesFound.size()).arg(m_name)); + } + } + + if (!future.isCanceled()) { + QMutexLocker locker(&m_lock); + files() = filesFound; + generateFileNames(); + future.setProgressValue(it.maxProgress()); + } else { + future.setProgressValueAndText(it.currentProgress(), tr("%1 filter update: canceled").arg(m_name)); + } +} diff --git a/src/plugins/coreplugin/locator/directoryfilter.h b/src/plugins/coreplugin/locator/directoryfilter.h new file mode 100644 index 0000000000..67e97a6e30 --- /dev/null +++ b/src/plugins/coreplugin/locator/directoryfilter.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef DIRECTORYFILTER_H +#define DIRECTORYFILTER_H + +#include "ui_directoryfilter.h" +#include "basefilefilter.h" + +#include <QString> +#include <QByteArray> +#include <QFutureInterface> +#include <QMutex> + +namespace Core { +namespace Internal { + +class DirectoryFilter : public BaseFileFilter +{ + Q_OBJECT + +public: + DirectoryFilter(); + QByteArray saveState() const; + bool restoreState(const QByteArray &state); + bool openConfigDialog(QWidget *parent, bool &needsRefresh); + void refresh(QFutureInterface<void> &future); + +private slots: + void addDirectory(); + void editDirectory(); + void removeDirectory(); + void updateOptionButtons(); + +private: + QString m_name; + QStringList m_directories; + QStringList m_filters; + // Our config dialog, uses in addDirectory and editDirectory + // to give their dialogs the right parent + QDialog *m_dialog; + Ui::DirectoryFilterOptions m_ui; + mutable QMutex m_lock; +}; + +} // namespace Internal +} // namespace Core + +#endif // DIRECTORYFILTER_H diff --git a/src/plugins/coreplugin/locator/directoryfilter.ui b/src/plugins/coreplugin/locator/directoryfilter.ui new file mode 100644 index 0000000000..051f7c5104 --- /dev/null +++ b/src/plugins/coreplugin/locator/directoryfilter.ui @@ -0,0 +1,195 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Core::Internal::DirectoryFilterOptions</class> + <widget class="QDialog" name="Core::Internal::DirectoryFilterOptions"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>393</width> + <height>275</height> + </rect> + </property> + <layout class="QGridLayout"> + <item row="0" column="0"> + <layout class="QGridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Name:</string> + </property> + </widget> + </item> + <item row="0" column="1" colspan="3"> + <widget class="QLineEdit" name="nameEdit"/> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>File types:</string> + </property> + </widget> + </item> + <item row="2" column="1" colspan="3"> + <widget class="QLineEdit" name="fileTypeEdit"> + <property name="toolTip"> + <string>Specify file name filters, separated by comma. Filters may contain wildcards.</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Prefix:</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLineEdit" name="shortcutEdit"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>16777215</height> + </size> + </property> + <property name="toolTip"> + <string>Specify a short word/abbreviation that can be used to restrict completions to files from this directory tree. +To do this, you type this shortcut and a space in the Locator entry field, and then the word to search for.</string> + </property> + </widget> + </item> + <item row="3" column="2" colspan="2"> + <widget class="QCheckBox" name="defaultFlag"> + <property name="text"> + <string>Limit to prefix</string> + </property> + <property name="checked"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="1" column="3"> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QPushButton" name="addButton"> + <property name="text"> + <string>Add</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="editButton"> + <property name="text"> + <string>Edit</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="removeButton"> + <property name="text"> + <string>Remove</string> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item row="1" column="0"> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Directories:</string> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item row="1" column="1" colspan="2"> + <widget class="QListWidget" name="directoryList"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="0"> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>Core::Internal::DirectoryFilterOptions</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>353</x> + <y>174</y> + </hint> + <hint type="destinationlabel"> + <x>390</x> + <y>152</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>Core::Internal::DirectoryFilterOptions</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>280</x> + <y>176</y> + </hint> + <hint type="destinationlabel"> + <x>391</x> + <y>141</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/plugins/coreplugin/locator/executefilter.cpp b/src/plugins/coreplugin/locator/executefilter.cpp new file mode 100644 index 0000000000..6c2e5f83bd --- /dev/null +++ b/src/plugins/coreplugin/locator/executefilter.cpp @@ -0,0 +1,188 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "executefilter.h" + +#include <coreplugin/icore.h> +#include <coreplugin/messagemanager.h> +#include <coreplugin/variablemanager.h> + +#include <QMessageBox> + +using namespace Core; +using namespace Core; +using namespace Core::Internal; + +ExecuteFilter::ExecuteFilter() +{ + setId("Execute custom commands"); + setDisplayName(tr("Execute Custom Commands")); + setShortcutString(QString(QLatin1Char('!'))); + setIncludedByDefault(false); + + m_process = new Utils::QtcProcess(this); + m_process->setEnvironment(Utils::Environment::systemEnvironment()); + connect(m_process, SIGNAL(finished(int,QProcess::ExitStatus)), this, + SLOT(finished(int,QProcess::ExitStatus))); + connect(m_process, SIGNAL(readyReadStandardOutput()), this, SLOT(readStandardOutput())); + connect(m_process, SIGNAL(readyReadStandardError()), this, SLOT(readStandardError())); + + m_runTimer.setSingleShot(true); + connect(&m_runTimer, SIGNAL(timeout()), this, SLOT(runHeadCommand())); +} + +QList<LocatorFilterEntry> ExecuteFilter::matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future, + const QString &entry) +{ + QList<LocatorFilterEntry> value; + if (!entry.isEmpty()) // avoid empty entry + value.append(LocatorFilterEntry(this, entry, QVariant())); + QList<LocatorFilterEntry> others; + const Qt::CaseSensitivity caseSensitivityForPrefix = caseSensitivity(entry); + foreach (const QString &i, m_commandHistory) { + if (future.isCanceled()) + break; + if (i == entry) // avoid repeated entry + continue; + if (i.startsWith(entry, caseSensitivityForPrefix)) + value.append(LocatorFilterEntry(this, i, QVariant())); + else + others.append(LocatorFilterEntry(this, i, QVariant())); + } + value.append(others); + return value; +} + +void ExecuteFilter::accept(LocatorFilterEntry selection) const +{ + ExecuteFilter *p = const_cast<ExecuteFilter *>(this); + + const QString value = selection.displayName.trimmed(); + const int index = m_commandHistory.indexOf(value); + if (index != -1 && index != 0) + p->m_commandHistory.removeAt(index); + if (index != 0) + p->m_commandHistory.prepend(value); + + bool found; + QString workingDirectory = Core::VariableManager::value("CurrentDocument:Path", &found); + if (!found || workingDirectory.isEmpty()) + workingDirectory = Core::VariableManager::value("CurrentProject:Path", &found); + + ExecuteData d; + d.workingDirectory = workingDirectory; + const int pos = value.indexOf(QLatin1Char(' ')); + if (pos == -1) { + d.executable = value; + } else { + d.executable = value.left(pos); + d.arguments = value.right(value.length() - pos - 1); + } + + if (m_process->state() != QProcess::NotRunning) { + const QString info(tr("Previous command is still running ('%1').\nDo you want to kill it?") + .arg(p->headCommand())); + int r = QMessageBox::question(0, tr("Kill Previous Process?"), info, + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, + QMessageBox::Yes); + if (r == QMessageBox::Yes) + m_process->kill(); + if (r != QMessageBox::Cancel) + p->m_taskQueue.enqueue(d); + return; + } + + p->m_taskQueue.enqueue(d); + p->runHeadCommand(); +} + +void ExecuteFilter::finished(int exitCode, QProcess::ExitStatus status) +{ + const QString commandName = headCommand(); + QString message; + if (status == QProcess::NormalExit && exitCode == 0) + message = tr("Command '%1' finished.").arg(commandName); + else + message = tr("Command '%1' failed.").arg(commandName); + MessageManager::write(message); + + m_taskQueue.dequeue(); + if (!m_taskQueue.isEmpty()) + m_runTimer.start(500); +} + +void ExecuteFilter::readStandardOutput() +{ + QByteArray data = m_process->readAllStandardOutput(); + MessageManager::write(QTextCodec::codecForLocale()->toUnicode(data.constData(), data.size(), + &m_stdoutState)); +} + +void ExecuteFilter::readStandardError() +{ + static QTextCodec::ConverterState state; + QByteArray data = m_process->readAllStandardError(); + MessageManager::write(QTextCodec::codecForLocale()->toUnicode(data.constData(), data.size(), + &m_stderrState)); +} + +void ExecuteFilter::runHeadCommand() +{ + if (!m_taskQueue.isEmpty()) { + const ExecuteData &d = m_taskQueue.head(); + const QString fullPath = Utils::Environment::systemEnvironment().searchInPath(d.executable); + if (fullPath.isEmpty()) { + MessageManager::write(tr("Could not find executable for '%1'").arg(d.executable)); + m_taskQueue.dequeue(); + runHeadCommand(); + return; + } + MessageManager::write(tr("Starting command '%1'").arg(headCommand())); + m_process->setWorkingDirectory(d.workingDirectory); + m_process->setCommand(fullPath, d.arguments); + m_process->start(); + m_process->closeWriteChannel(); + if (!m_process->waitForStarted(1000)) { + MessageManager::write(tr("Could not start process: %1").arg(m_process->errorString())); + m_taskQueue.dequeue(); + runHeadCommand(); + } + } +} + +QString ExecuteFilter::headCommand() const +{ + if (m_taskQueue.isEmpty()) + return QString(); + const ExecuteData &data = m_taskQueue.head(); + if (data.arguments.isEmpty()) + return data.executable; + else + return data.executable + QLatin1Char(' ') + data.arguments; +} diff --git a/src/plugins/coreplugin/locator/executefilter.h b/src/plugins/coreplugin/locator/executefilter.h new file mode 100644 index 0000000000..719d8514c8 --- /dev/null +++ b/src/plugins/coreplugin/locator/executefilter.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef EXECUTEFILTER_H +#define EXECUTEFILTER_H + +#include "ilocatorfilter.h" + +#include <utils/qtcprocess.h> + +#include <QQueue> +#include <QStringList> +#include <QTimer> +#include <QTextCodec> + +namespace Core { +namespace Internal { + +class ExecuteFilter : public Core::ILocatorFilter +{ + Q_OBJECT + struct ExecuteData + { + QString executable; + QString arguments; + QString workingDirectory; + }; + +public: + ExecuteFilter(); + QList<Core::LocatorFilterEntry> matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future, + const QString &entry); + void accept(Core::LocatorFilterEntry selection) const; + void refresh(QFutureInterface<void> &) {} + +private slots: + void finished(int exitCode, QProcess::ExitStatus status); + void readStandardOutput(); + void readStandardError(); + void runHeadCommand(); + +private: + QString headCommand() const; + +private: + QQueue<ExecuteData> m_taskQueue; + QStringList m_commandHistory; + Utils::QtcProcess *m_process; + QTimer m_runTimer; + QTextCodec::ConverterState m_stdoutState; + QTextCodec::ConverterState m_stderrState; +}; + +} // namespace Internal +} // namespace Core + +#endif // EXECUTEFILTER_H diff --git a/src/plugins/coreplugin/locator/filesystemfilter.cpp b/src/plugins/coreplugin/locator/filesystemfilter.cpp new file mode 100644 index 0000000000..84b05cfd5e --- /dev/null +++ b/src/plugins/coreplugin/locator/filesystemfilter.cpp @@ -0,0 +1,187 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "filesystemfilter.h" +#include "locatorwidget.h" +#include <coreplugin/editormanager/ieditor.h> +#include <coreplugin/editormanager/editormanager.h> + +#include <QDir> + +using namespace Core; +using namespace Core; +using namespace Core::Internal; + +namespace { + +QList<LocatorFilterEntry> *categorize(const QString &entry, const QString &candidate, + Qt::CaseSensitivity caseSensitivity, + QList<LocatorFilterEntry> *betterEntries, QList<LocatorFilterEntry> *goodEntries) +{ + if (entry.isEmpty() || candidate.startsWith(entry, caseSensitivity)) + return betterEntries; + else if (candidate.contains(entry, caseSensitivity)) + return goodEntries; + return 0; +} + +} // anynoumous namespace + +FileSystemFilter::FileSystemFilter(LocatorWidget *locatorWidget) + : m_locatorWidget(locatorWidget), m_includeHidden(true) +{ + setId("Files in file system"); + setDisplayName(tr("Files in File System")); + setShortcutString(QString(QLatin1Char('f'))); + setIncludedByDefault(false); +} + +QList<LocatorFilterEntry> FileSystemFilter::matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future, const QString &entry) +{ + QList<LocatorFilterEntry> goodEntries; + QList<LocatorFilterEntry> betterEntries; + QFileInfo entryInfo(entry); + QString name = entryInfo.fileName(); + QString directory = entryInfo.path(); + QString filePath = entryInfo.filePath(); + if (entryInfo.isRelative()) { + if (filePath.startsWith(QLatin1String("~/"))) { + directory.replace(0, 1, QDir::homePath()); + } else { + IDocument *document= EditorManager::currentDocument(); + if (document && !document->filePath().isEmpty()) { + QFileInfo info(document->filePath()); + directory.prepend(info.absolutePath() + QLatin1Char('/')); + } + } + } + QDir dirInfo(directory); + QDir::Filters dirFilter = QDir::Dirs|QDir::Drives|QDir::NoDot; + QDir::Filters fileFilter = QDir::Files; + if (m_includeHidden) { + dirFilter |= QDir::Hidden; + fileFilter |= QDir::Hidden; + } + const Qt::CaseSensitivity caseSensitivity_ = caseSensitivity(entry); + QStringList dirs = dirInfo.entryList(dirFilter, + QDir::Name|QDir::IgnoreCase|QDir::LocaleAware); + QStringList files = dirInfo.entryList(fileFilter, + QDir::Name|QDir::IgnoreCase|QDir::LocaleAware); + foreach (const QString &dir, dirs) { + if (future.isCanceled()) + break; + if (QList<LocatorFilterEntry> *category = categorize(name, dir, caseSensitivity_, &betterEntries, + &goodEntries)) { + const QString fullPath = dirInfo.filePath(dir); + LocatorFilterEntry filterEntry(this, dir, QVariant()); + filterEntry.fileName = fullPath; + category->append(filterEntry); + } + } + // file names can match with +linenumber or :linenumber + name = entry; + const QString lineNoSuffix = EditorManager::splitLineNumber(&name); + name = QFileInfo(name).fileName(); + foreach (const QString &file, files) { + if (future.isCanceled()) + break; + if (QList<LocatorFilterEntry> *category = categorize(name, file, caseSensitivity_, &betterEntries, + &goodEntries)) { + const QString fullPath = dirInfo.filePath(file); + LocatorFilterEntry filterEntry(this, file, QString(fullPath + lineNoSuffix)); + filterEntry.fileName = fullPath; + category->append(filterEntry); + } + } + betterEntries.append(goodEntries); + return betterEntries; +} + +void FileSystemFilter::accept(LocatorFilterEntry selection) const +{ + QString fileName = selection.fileName; + QFileInfo info(fileName); + if (info.isDir()) { + QString value = shortcutString(); + value += QLatin1Char(' '); + value += QDir::toNativeSeparators(info.absoluteFilePath() + QLatin1Char('/')); + m_locatorWidget->show(value, value.length()); + return; + } + EditorManager::openEditor(selection.internalData.toString(), Id(), + EditorManager::CanContainLineNumber); +} + +bool FileSystemFilter::openConfigDialog(QWidget *parent, bool &needsRefresh) +{ + Q_UNUSED(needsRefresh) + Ui::FileSystemFilterOptions ui; + QDialog dialog(parent); + ui.setupUi(&dialog); + + ui.hiddenFilesFlag->setChecked(m_includeHidden); + ui.limitCheck->setChecked(!isIncludedByDefault()); + ui.shortcutEdit->setText(shortcutString()); + + if (dialog.exec() == QDialog::Accepted) { + m_includeHidden = ui.hiddenFilesFlag->isChecked(); + setShortcutString(ui.shortcutEdit->text().trimmed()); + setIncludedByDefault(!ui.limitCheck->isChecked()); + return true; + } + return false; +} + +QByteArray FileSystemFilter::saveState() const +{ + QByteArray value; + QDataStream out(&value, QIODevice::WriteOnly); + out << m_includeHidden; + out << shortcutString(); + out << isIncludedByDefault(); + return value; +} + +bool FileSystemFilter::restoreState(const QByteArray &state) +{ + QDataStream in(state); + in >> m_includeHidden; + + // An attempt to prevent setting this on old configuration + if (!in.atEnd()) { + QString shortcut; + bool defaultFilter; + in >> shortcut; + in >> defaultFilter; + setShortcutString(shortcut); + setIncludedByDefault(defaultFilter); + } + + return true; +} diff --git a/src/plugins/coreplugin/locator/filesystemfilter.h b/src/plugins/coreplugin/locator/filesystemfilter.h new file mode 100644 index 0000000000..a6f0283065 --- /dev/null +++ b/src/plugins/coreplugin/locator/filesystemfilter.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef FILESYSTEMFILTER_H +#define FILESYSTEMFILTER_H + +#include "ilocatorfilter.h" +#include "ui_filesystemfilter.h" + +#include <QString> +#include <QList> +#include <QByteArray> +#include <QFutureInterface> + +namespace Core { +namespace Internal { + +class LocatorWidget; + +class FileSystemFilter : public Core::ILocatorFilter +{ + Q_OBJECT + +public: + explicit FileSystemFilter(LocatorWidget *locatorWidget); + QList<Core::LocatorFilterEntry> matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future, const QString &entry); + void accept(Core::LocatorFilterEntry selection) const; + QByteArray saveState() const; + bool restoreState(const QByteArray &state); + bool openConfigDialog(QWidget *parent, bool &needsRefresh); + void refresh(QFutureInterface<void> &) {} + +private: + LocatorWidget *m_locatorWidget; + bool m_includeHidden; +}; + +} // namespace Internal +} // namespace Core + +#endif // FILESYSTEMFILTER_H diff --git a/src/plugins/coreplugin/locator/filesystemfilter.ui b/src/plugins/coreplugin/locator/filesystemfilter.ui new file mode 100644 index 0000000000..b6a8944c70 --- /dev/null +++ b/src/plugins/coreplugin/locator/filesystemfilter.ui @@ -0,0 +1,111 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Core::Internal::FileSystemFilterOptions</class> + <widget class="QDialog" name="Core::Internal::FileSystemFilterOptions"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>360</width> + <height>131</height> + </rect> + </property> + <property name="windowTitle"> + <string>Add Filter Configuration</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Prefix:</string> + </property> + <property name="buddy"> + <cstring>shortcutEdit</cstring> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="shortcutEdit"/> + </item> + <item row="1" column="2"> + <widget class="QCheckBox" name="limitCheck"> + <property name="text"> + <string>Limit to prefix</string> + </property> + </widget> + </item> + <item row="4" column="1" colspan="2"> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + <item row="3" column="1"> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="2" column="1"> + <widget class="QCheckBox" name="hiddenFilesFlag"> + <property name="text"> + <string>Include hidden files</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Filter:</string> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>Core::Internal::FileSystemFilterOptions</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>Core::Internal::FileSystemFilterOptions</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/plugins/coreplugin/locator/ilocatorfilter.cpp b/src/plugins/coreplugin/locator/ilocatorfilter.cpp new file mode 100644 index 0000000000..ea91197cc6 --- /dev/null +++ b/src/plugins/coreplugin/locator/ilocatorfilter.cpp @@ -0,0 +1,208 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "ilocatorfilter.h" + +#include <QBoxLayout> +#include <QCheckBox> +#include <QDialog> +#include <QDialogButtonBox> +#include <QLabel> +#include <QLineEdit> + +using namespace Core; + +ILocatorFilter::ILocatorFilter(QObject *parent): + QObject(parent), + m_priority(Medium), + m_includedByDefault(false), + m_hidden(false), + m_enabled(true), + m_isConfigurable(true) +{ +} + +QString ILocatorFilter::shortcutString() const +{ + return m_shortcut; +} + +void ILocatorFilter::setShortcutString(const QString &shortcut) +{ + m_shortcut = shortcut; +} + +QByteArray ILocatorFilter::saveState() const +{ + QByteArray value; + QDataStream out(&value, QIODevice::WriteOnly); + out << shortcutString(); + out << isIncludedByDefault(); + return value; +} + +bool ILocatorFilter::restoreState(const QByteArray &state) +{ + QString shortcut; + bool defaultFilter; + + QDataStream in(state); + in >> shortcut; + in >> defaultFilter; + + setShortcutString(shortcut); + setIncludedByDefault(defaultFilter); + return true; +} + +bool ILocatorFilter::openConfigDialog(QWidget *parent, bool &needsRefresh) +{ + Q_UNUSED(needsRefresh) + + QDialog dialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint); + dialog.setWindowTitle(tr("Filter Configuration")); + + QVBoxLayout *vlayout = new QVBoxLayout(&dialog); + QHBoxLayout *hlayout = new QHBoxLayout; + QLineEdit *shortcutEdit = new QLineEdit(shortcutString()); + QCheckBox *limitCheck = new QCheckBox(tr("Limit to prefix")); + limitCheck->setChecked(!isIncludedByDefault()); + + hlayout->addWidget(new QLabel(tr("Prefix:"))); + hlayout->addWidget(shortcutEdit); + hlayout->addWidget(limitCheck); + + QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | + QDialogButtonBox::Cancel); + connect(buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept())); + connect(buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject())); + + vlayout->addLayout(hlayout); + vlayout->addStretch(); + vlayout->addWidget(buttonBox); + + if (dialog.exec() == QDialog::Accepted) { + setShortcutString(shortcutEdit->text().trimmed()); + setIncludedByDefault(!limitCheck->isChecked()); + return true; + } + + return false; +} + +QString ILocatorFilter::trimWildcards(const QString &str) +{ + if (str.isEmpty()) + return str; + int first = 0, last = str.size() - 1; + const QChar asterisk = QLatin1Char('*'); + const QChar question = QLatin1Char('?'); + while (first < str.size() && (str.at(first) == asterisk || str.at(first) == question)) + ++first; + while (last >= 0 && (str.at(last) == asterisk || str.at(last) == question)) + --last; + if (first > last) + return QString(); + return str.mid(first, last - first + 1); +} + +Qt::CaseSensitivity ILocatorFilter::caseSensitivity(const QString &str) +{ + return str == str.toLower() ? Qt::CaseInsensitive : Qt::CaseSensitive; +} + +bool ILocatorFilter::isConfigurable() const +{ + return m_isConfigurable; +} + +bool ILocatorFilter::isIncludedByDefault() const +{ + return m_includedByDefault; +} + +void ILocatorFilter::setIncludedByDefault(bool includedByDefault) +{ + m_includedByDefault = includedByDefault; +} + +bool ILocatorFilter::isHidden() const +{ + return m_hidden; +} + +void ILocatorFilter::setHidden(bool hidden) +{ + m_hidden = hidden; +} + +bool ILocatorFilter::isEnabled() const +{ + return m_enabled; +} + +Core::Id ILocatorFilter::id() const +{ + return m_id; +} + +QString ILocatorFilter::displayName() const +{ + return m_displayName; +} + +ILocatorFilter::Priority ILocatorFilter::priority() const +{ + return m_priority; +} + +void ILocatorFilter::setEnabled(bool enabled) +{ + m_enabled = enabled; +} + +void ILocatorFilter::setId(Core::Id id) +{ + m_id = id; +} + +void ILocatorFilter::setPriority(Priority priority) +{ + m_priority = priority; +} + +void ILocatorFilter::setDisplayName(const QString &displayString) +{ + m_displayName = displayString; +} + +void ILocatorFilter::setConfigurable(bool configurable) +{ + m_isConfigurable = configurable; +} diff --git a/src/plugins/coreplugin/locator/ilocatorfilter.h b/src/plugins/coreplugin/locator/ilocatorfilter.h new file mode 100644 index 0000000000..3c6e213fa0 --- /dev/null +++ b/src/plugins/coreplugin/locator/ilocatorfilter.h @@ -0,0 +1,167 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef ILOCATORFILTER_H +#define ILOCATORFILTER_H + +#include <coreplugin/id.h> + +#include <QVariant> +#include <QFutureInterface> +#include <QIcon> + +namespace Core { + +class ILocatorFilter; + +struct LocatorFilterEntry +{ + LocatorFilterEntry() + : filter(0) + , fileIconResolved(false) + {} + + LocatorFilterEntry(ILocatorFilter *fromFilter, const QString &name, const QVariant &data, + const QIcon &icon = QIcon()) + : filter(fromFilter) + , displayName(name) + , internalData(data) + , displayIcon(icon) + , fileIconResolved(false) + {} + + bool operator==(const LocatorFilterEntry &other) const { + if (internalData.canConvert(QVariant::String)) + return (internalData.toString() == other.internalData.toString()); + return internalData.constData() == other.internalData.constData(); + } + + /* backpointer to creating filter */ + ILocatorFilter *filter; + /* displayed string */ + QString displayName; + /* extra information displayed in light-gray in a second column (optional) */ + QString extraInfo; + /* can be used by the filter to save more information about the entry */ + QVariant internalData; + /* icon to display along with the entry */ + QIcon displayIcon; + /* file name, if the entry is related to a file, is used e.g. for resolving a file icon */ + QString fileName; + /* internal */ + bool fileIconResolved; +}; + +class CORE_EXPORT ILocatorFilter : public QObject +{ + Q_OBJECT + +public: + enum Priority {High = 0, Medium = 1, Low = 2}; + + ILocatorFilter(QObject *parent = 0); + virtual ~ILocatorFilter() {} + + /* Internal Id. */ + Core::Id id() const; + + /* Visible name. */ + QString displayName() const; + + /* Selection list order in case of multiple active filters (high goes on top). */ + Priority priority() const; + + /* String to type to use this filter exclusively. */ + QString shortcutString() const; + + /* List of matches for the given user entry. */ + virtual QList<LocatorFilterEntry> matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future, const QString &entry) = 0; + + /* User has selected the given entry that belongs to this filter. */ + virtual void accept(LocatorFilterEntry selection) const = 0; + + /* Implement to update caches on user request, if that's a long operation. */ + virtual void refresh(QFutureInterface<void> &future) = 0; + + /* Saved state is used to restore the filter at start up. */ + virtual QByteArray saveState() const; + + /* Used to restore the filter at start up. */ + virtual bool restoreState(const QByteArray &state); + + /* User wants to configure this filter (if supported). Use it to pop up a dialog. + * needsRefresh is used as a hint to indicate that refresh should be called. + * The default implementation allows changing the shortcut and whether the filter + * is enabled by default. + */ + virtual bool openConfigDialog(QWidget *parent, bool &needsRefresh); + + /* If there is a configure dialog available for this filter. The default + * implementation returns true. */ + bool isConfigurable() const; + + /* Is this filter used also when the shortcutString is not used? */ + bool isIncludedByDefault() const; + + /* Returns whether the filter should be hidden from configuration and menus. */ + bool isHidden() const; + + /* Returns whether the filter should be enabled and used in menus. */ + bool isEnabled() const; + + static QString trimWildcards(const QString &str); + static Qt::CaseSensitivity caseSensitivity(const QString &str); + +public slots: + /* Enable or disable the filter. */ + void setEnabled(bool enabled); + +protected: + void setShortcutString(const QString &shortcut); + void setIncludedByDefault(bool includedByDefault); + void setHidden(bool hidden); + void setId(Core::Id id); + void setPriority(Priority priority); + void setDisplayName(const QString &displayString); + void setConfigurable(bool configurable); + +private: + Core::Id m_id; + QString m_shortcut; + Priority m_priority; + QString m_displayName; + bool m_includedByDefault; + bool m_hidden; + bool m_enabled; + bool m_isConfigurable; +}; + +} // namespace Core + +#endif // ILOCATORFILTER_H diff --git a/src/plugins/coreplugin/locator/images/locator.png b/src/plugins/coreplugin/locator/images/locator.png Binary files differnew file mode 100644 index 0000000000..000ee1c018 --- /dev/null +++ b/src/plugins/coreplugin/locator/images/locator.png diff --git a/src/plugins/coreplugin/locator/images/reload.png b/src/plugins/coreplugin/locator/images/reload.png Binary files differnew file mode 100644 index 0000000000..b5afefb32b --- /dev/null +++ b/src/plugins/coreplugin/locator/images/reload.png diff --git a/src/plugins/coreplugin/locator/locator.pri b/src/plugins/coreplugin/locator/locator.pri new file mode 100644 index 0000000000..174e1684fe --- /dev/null +++ b/src/plugins/coreplugin/locator/locator.pri @@ -0,0 +1,46 @@ +HEADERS += \ + $$PWD/locatorplugin.h \ + $$PWD/commandlocator.h \ + $$PWD/locatorwidget.h \ + $$PWD/locatorfiltersfilter.h \ + $$PWD/settingspage.h \ + $$PWD/ilocatorfilter.h \ + $$PWD/opendocumentsfilter.h \ + $$PWD/filesystemfilter.h \ + $$PWD/locatorconstants.h \ + $$PWD/directoryfilter.h \ + $$PWD/locatormanager.h \ + $$PWD/basefilefilter.h \ + $$PWD/executefilter.h \ + $$PWD/locatorsearchutils.h + +SOURCES += \ + $$PWD/locatorplugin.cpp \ + $$PWD/commandlocator.cpp \ + $$PWD/locatorwidget.cpp \ + $$PWD/locatorfiltersfilter.cpp \ + $$PWD/opendocumentsfilter.cpp \ + $$PWD/filesystemfilter.cpp \ + $$PWD/settingspage.cpp \ + $$PWD/directoryfilter.cpp \ + $$PWD/locatormanager.cpp \ + $$PWD/basefilefilter.cpp \ + $$PWD/ilocatorfilter.cpp \ + $$PWD/executefilter.cpp \ + $$PWD/locatorsearchutils.cpp + +FORMS += \ + $$PWD/settingspage.ui \ + $$PWD/filesystemfilter.ui \ + $$PWD/directoryfilter.ui + +RESOURCES += \ + $$PWD/locator.qrc + +equals(TEST, 1) { + HEADERS += $$PWD/locatorfiltertest.h + SOURCES += \ + $$PWD/locatorfiltertest.cpp \ + $$PWD/locator_test.cpp + DEFINES += SRCDIR=\\\"$$PWD\\\" +} diff --git a/src/plugins/coreplugin/locator/locator.qrc b/src/plugins/coreplugin/locator/locator.qrc new file mode 100644 index 0000000000..4cd5df4f13 --- /dev/null +++ b/src/plugins/coreplugin/locator/locator.qrc @@ -0,0 +1,6 @@ +<RCC> + <qresource prefix="/locator"> + <file>images/reload.png</file> + <file>images/locator.png</file> + </qresource> +</RCC> diff --git a/src/plugins/coreplugin/locator/locator_test.cpp b/src/plugins/coreplugin/locator/locator_test.cpp new file mode 100644 index 0000000000..35640285ec --- /dev/null +++ b/src/plugins/coreplugin/locator/locator_test.cpp @@ -0,0 +1,173 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + + +#include "locatorplugin.h" + +#include "basefilefilter.h" +#include "locatorfiltertest.h" + +#include <coreplugin/testdatadir.h> +#include <utils/fileutils.h> + +#include <QDir> +#include <QTextStream> +#include <QtTest> + +using namespace Core::Tests; + +namespace { + +QTC_DECLARE_MYTESTDATADIR("../../../tests/locators/") + +class MyBaseFileFilter : public Core::BaseFileFilter +{ +public: + MyBaseFileFilter(const QStringList &theFiles) + { + files().clear(); + files().append(theFiles); + BaseFileFilter::generateFileNames(); + } + + void refresh(QFutureInterface<void> &) {} + +protected: + void updateFiles() {} +}; + +inline QString _(const QByteArray &ba) { return QString::fromLatin1(ba, ba.size()); } + +class ReferenceData +{ +public: + ReferenceData() {} + ReferenceData(const QString &searchText, const ResultDataList &results) + : searchText(searchText), results(results) {} + + QString searchText; + ResultDataList results; +}; + +} // anonymous namespace + +Q_DECLARE_METATYPE(ReferenceData) +Q_DECLARE_METATYPE(QList<ReferenceData>) + +void Core::Internal::LocatorPlugin::test_basefilefilter() +{ + QFETCH(QStringList, testFiles); + QFETCH(QList<ReferenceData>, referenceDataList); + + MyBaseFileFilter filter(testFiles); + BasicLocatorFilterTest test(&filter); + + foreach (const ReferenceData &reference, referenceDataList) { + const QList<LocatorFilterEntry> filterEntries = test.matchesFor(reference.searchText); + const ResultDataList results = ResultData::fromFilterEntryList(filterEntries); +// QTextStream(stdout) << "----" << endl; +// ResultData::printFilterEntries(results); + QCOMPARE(results, reference.results); + } +} + +void Core::Internal::LocatorPlugin::test_basefilefilter_data() +{ + QTest::addColumn<QStringList>("testFiles"); + QTest::addColumn<QList<ReferenceData> >("referenceDataList"); + + const QChar pathSeparator = QDir::separator(); + const MyTestDataDir testDir(QLatin1String("testdata_basic")); + const QStringList testFiles = QStringList() + << QDir::toNativeSeparators(testDir.file(QLatin1String("file.cpp"))) + << QDir::toNativeSeparators(testDir.file(QLatin1String("main.cpp"))) + << QDir::toNativeSeparators(testDir.file(QLatin1String("subdir/main.cpp"))); + QStringList testFilesShort; + foreach (const QString &file, testFiles) + testFilesShort << Utils::FileUtils::shortNativePath(Utils::FileName::fromString(file)); + + QTest::newRow("BaseFileFilter-EmptyInput") + << testFiles + << (QList<ReferenceData>() + << ReferenceData( + QString(), + (QList<ResultData>() + << ResultData(_("file.cpp"), testFilesShort.at(0)) + << ResultData(_("main.cpp"), testFilesShort.at(1)) + << ResultData(_("main.cpp"), testFilesShort.at(2)))) + ); + + QTest::newRow("BaseFileFilter-InputIsFileName") + << testFiles + << (QList<ReferenceData>() + << ReferenceData( + _("main.cpp"), + (QList<ResultData>() + << ResultData(_("main.cpp"), testFilesShort.at(1)) + << ResultData(_("main.cpp"), testFilesShort.at(2)))) + ); + + QTest::newRow("BaseFileFilter-InputIsFilePath") + << testFiles + << (QList<ReferenceData>() + << ReferenceData( + QString(_("subdir") + pathSeparator + _("main.cpp")), + (QList<ResultData>() + << ResultData(_("main.cpp"), testFilesShort.at(2)))) + ); + + QTest::newRow("BaseFileFilter-InputIsDirIsPath") + << testFiles + << (QList<ReferenceData>() + << ReferenceData( _("subdir"), QList<ResultData>()) + << ReferenceData( + QString(_("subdir") + pathSeparator + _("main.cpp")), + (QList<ResultData>() + << ResultData(_("main.cpp"), testFilesShort.at(2)))) + ); + + QTest::newRow("BaseFileFilter-InputIsFileNameFilePathFileName") + << testFiles + << (QList<ReferenceData>() + << ReferenceData( + _("main.cpp"), + (QList<ResultData>() + << ResultData(_("main.cpp"), testFilesShort.at(1)) + << ResultData(_("main.cpp"), testFilesShort.at(2)))) + << ReferenceData( + QString(_("subdir") + pathSeparator + _("main.cpp")), + (QList<ResultData>() + << ResultData(_("main.cpp"), testFilesShort.at(2)))) + << ReferenceData( + _("main.cpp"), + (QList<ResultData>() + << ResultData(_("main.cpp"), testFilesShort.at(1)) + << ResultData(_("main.cpp"), testFilesShort.at(2)))) + ); +} diff --git a/src/plugins/coreplugin/locator/locatorconstants.h b/src/plugins/coreplugin/locator/locatorconstants.h new file mode 100644 index 0000000000..bd39b411d3 --- /dev/null +++ b/src/plugins/coreplugin/locator/locatorconstants.h @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef LOCATORCONSTANTS_H +#define LOCATORCONSTANTS_H + +#include <QtGlobal> + +namespace Core { +namespace Constants { + +const char FILTER_OPTIONS_PAGE[] = QT_TRANSLATE_NOOP("Locator", "Locator"); +const char TASK_INDEX[] = "Locator.Task.Index"; + +} // namespace Constants +} // namespace Core + +#endif // LOCATORCONSTANTS_H diff --git a/src/plugins/coreplugin/locator/locatorfiltersfilter.cpp b/src/plugins/coreplugin/locator/locatorfiltersfilter.cpp new file mode 100644 index 0000000000..3c52ad72d9 --- /dev/null +++ b/src/plugins/coreplugin/locator/locatorfiltersfilter.cpp @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "locatorfiltersfilter.h" +#include "locatorplugin.h" +#include "locatorwidget.h" + +#include <coreplugin/coreconstants.h> + +using namespace Core; +using namespace Core::Internal; + +Q_DECLARE_METATYPE(ILocatorFilter*) + +LocatorFiltersFilter::LocatorFiltersFilter(LocatorPlugin *plugin, + LocatorWidget *locatorWidget): + m_plugin(plugin), + m_locatorWidget(locatorWidget), + m_icon(QIcon(QLatin1String(Core::Constants::ICON_NEXT))) +{ + setId("FiltersFilter"); + setDisplayName(tr("Available filters")); + setIncludedByDefault(true); + setHidden(true); + setPriority(High); + setConfigurable(false); +} + +QList<LocatorFilterEntry> LocatorFiltersFilter::matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future, const QString &entry) +{ + QList<LocatorFilterEntry> entries; + if (!entry.isEmpty()) + return entries; + + QMap<QString, ILocatorFilter *> uniqueFilters; + foreach (ILocatorFilter *filter, m_plugin->filters()) { + const QString filterId = filter->shortcutString() + QLatin1Char(',') + filter->displayName(); + uniqueFilters.insert(filterId, filter); + } + + foreach (ILocatorFilter *filter, uniqueFilters) { + if (future.isCanceled()) + break; + if (!filter->shortcutString().isEmpty() && !filter->isHidden() && filter->isEnabled()) { + LocatorFilterEntry filterEntry(this, + filter->shortcutString(), + QVariant::fromValue(filter), + m_icon); + filterEntry.extraInfo = filter->displayName(); + entries.append(filterEntry); + } + } + + return entries; +} + +void LocatorFiltersFilter::accept(LocatorFilterEntry selection) const +{ + ILocatorFilter *filter = selection.internalData.value<ILocatorFilter *>(); + if (filter) + m_locatorWidget->show(filter->shortcutString() + QLatin1Char(' '), + filter->shortcutString().length() + 1); +} + +void LocatorFiltersFilter::refresh(QFutureInterface<void> &future) +{ + Q_UNUSED(future) + // Nothing to refresh +} diff --git a/src/plugins/coreplugin/locator/locatorfiltersfilter.h b/src/plugins/coreplugin/locator/locatorfiltersfilter.h new file mode 100644 index 0000000000..c775c3a824 --- /dev/null +++ b/src/plugins/coreplugin/locator/locatorfiltersfilter.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef LOCATORFILTERSFILTER_H +#define LOCATORFILTERSFILTER_H + +#include "ilocatorfilter.h" + +#include <QIcon> + +namespace Core { +namespace Internal { + +class LocatorPlugin; +class LocatorWidget; + +/*! + This filter provides the user with the list of available Locator filters. + The list is only shown when nothing has been typed yet. + */ +class LocatorFiltersFilter : public ILocatorFilter +{ + Q_OBJECT + +public: + LocatorFiltersFilter(LocatorPlugin *plugin, + LocatorWidget *locatorWidget); + + // ILocatorFilter + QList<LocatorFilterEntry> matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future, const QString &entry); + void accept(LocatorFilterEntry selection) const; + void refresh(QFutureInterface<void> &future); + +private: + LocatorPlugin *m_plugin; + LocatorWidget *m_locatorWidget; + QIcon m_icon; +}; + +} // namespace Internal +} // namespace Core + +#endif // LOCATORFILTERSFILTER_H diff --git a/src/plugins/coreplugin/locator/locatorfiltertest.cpp b/src/plugins/coreplugin/locator/locatorfiltertest.cpp new file mode 100644 index 0000000000..1bdf4998e0 --- /dev/null +++ b/src/plugins/coreplugin/locator/locatorfiltertest.cpp @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + + +#include "locatorfiltertest.h" +#include "locatorsearchutils.h" + +#include <utils/runextensions.h> + +#include <QFuture> +#include <QList> +#include <QString> +#include <QTextStream> + +using namespace Core; +using namespace Core::Tests; + +BasicLocatorFilterTest::BasicLocatorFilterTest(ILocatorFilter *filter) : m_filter(filter) +{ +} + +QList<LocatorFilterEntry> BasicLocatorFilterTest::matchesFor(const QString &searchText) +{ + doBeforeLocatorRun(); + const QList<ILocatorFilter *> filters = QList<ILocatorFilter *>() << m_filter; + QFuture<LocatorFilterEntry> locatorSearch = QtConcurrent::run(Core::Internal::runSearch, + filters, searchText); + locatorSearch.waitForFinished(); + doAfterLocatorRun(); + return locatorSearch.results(); +} + +ResultData::ResultData() +{ +} + +ResultData::ResultData(const QString &textColumn1, const QString &textColumn2) + : textColumn1(textColumn1), textColumn2(textColumn2) +{ +} + +bool ResultData::operator==(const ResultData &other) const +{ + return textColumn1 == other.textColumn1 && textColumn2 == other.textColumn2; +} + +ResultData::ResultDataList ResultData::fromFilterEntryList(const QList<LocatorFilterEntry> &entries) +{ + ResultDataList result; + foreach (const LocatorFilterEntry &entry, entries) + result << ResultData(entry.displayName, entry.extraInfo); + return result; +} + +void ResultData::printFilterEntries(const ResultData::ResultDataList &entries) +{ + QTextStream out(stdout); + foreach (const ResultData entry, entries) { + out << "<< ResultData(_(\"" << entry.textColumn1 << "\"), _(\"" << entry.textColumn2 + << "\"))" << endl; + } +} diff --git a/src/plugins/coreplugin/locator/locatorfiltertest.h b/src/plugins/coreplugin/locator/locatorfiltertest.h new file mode 100644 index 0000000000..6cbf1e115c --- /dev/null +++ b/src/plugins/coreplugin/locator/locatorfiltertest.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + + +#ifndef LOCATORFILTERTEST_H +#define LOCATORFILTERTEST_H + +#include "ilocatorfilter.h" + +#include <QTest> + +namespace Core { +namespace Tests { + +/// Runs a locator filter for a search text and returns the results. +class CORE_EXPORT BasicLocatorFilterTest +{ +public: + BasicLocatorFilterTest(Core::ILocatorFilter *filter); + + QList<Core::LocatorFilterEntry> matchesFor(const QString &searchText = QString()); + +private: + virtual void doBeforeLocatorRun() {} + virtual void doAfterLocatorRun() {} + + Core::ILocatorFilter *m_filter; +}; + +class CORE_EXPORT ResultData +{ +public: + typedef QList<ResultData> ResultDataList; + + ResultData(); + ResultData(const QString &textColumn1, const QString &textColumn2); + + bool operator==(const ResultData &other) const; + + static ResultDataList fromFilterEntryList(const QList<LocatorFilterEntry> &entries); + + /// For debugging and creating reference data + static void printFilterEntries(const ResultDataList &entries); + + QString textColumn1; + QString textColumn2; +}; + +typedef ResultData::ResultDataList ResultDataList; + +} // namespace Tests +} // namespace Core + +Q_DECLARE_METATYPE(Core::Tests::ResultData) +Q_DECLARE_METATYPE(Core::Tests::ResultDataList) + +QT_BEGIN_NAMESPACE +namespace QTest { + +template<> inline char *toString(const Core::Tests::ResultData &data) +{ + QByteArray ba = "\"" + data.textColumn1.toUtf8() + "\", \"" + data.textColumn2.toUtf8() + "\""; + return qstrdup(ba.data()); +} + +} // namespace QTest +QT_END_NAMESPACE + +#endif // LOCATORFILTERTEST_H diff --git a/src/plugins/coreplugin/locator/locatormanager.cpp b/src/plugins/coreplugin/locator/locatormanager.cpp new file mode 100644 index 0000000000..9cc893f5f6 --- /dev/null +++ b/src/plugins/coreplugin/locator/locatormanager.cpp @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "locatormanager.h" +#include "locatorwidget.h" + +#include <extensionsystem/pluginmanager.h> +#include <utils/qtcassert.h> + +namespace Core { + +static Internal::LocatorWidget *m_locatorWidget = 0; + +LocatorManager::LocatorManager(Internal::LocatorWidget *locatorWidget) + : QObject(locatorWidget) +{ + m_locatorWidget = locatorWidget; +} + +LocatorManager::~LocatorManager() +{ + ExtensionSystem::PluginManager::removeObject(this); +} + +void LocatorManager::show(const QString &text, + int selectionStart, int selectionLength) +{ + QTC_ASSERT(m_locatorWidget, return); + m_locatorWidget->show(text, selectionStart, selectionLength); +} + +} // namespace Internal diff --git a/src/plugins/coreplugin/locator/locatormanager.h b/src/plugins/coreplugin/locator/locatormanager.h new file mode 100644 index 0000000000..67d2106c04 --- /dev/null +++ b/src/plugins/coreplugin/locator/locatormanager.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef LOCATORMANAGER_H +#define LOCATORMANAGER_H + +#include <coreplugin/core_global.h> + +#include <QObject> + +namespace Core { + +namespace Internal { class LocatorWidget; } + +class CORE_EXPORT LocatorManager : public QObject +{ + Q_OBJECT + +public: + LocatorManager(Internal::LocatorWidget *locatorWidget); + ~LocatorManager(); + + static void show(const QString &text, int selectionStart = -1, int selectionLength = 0); +}; + +} // namespace Core + +#endif // LOCATORMANAGER_H diff --git a/src/plugins/coreplugin/locator/locatorplugin.cpp b/src/plugins/coreplugin/locator/locatorplugin.cpp new file mode 100644 index 0000000000..085c0071d2 --- /dev/null +++ b/src/plugins/coreplugin/locator/locatorplugin.cpp @@ -0,0 +1,261 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "locatorplugin.h" +#include "locatorconstants.h" +#include "locatorfiltersfilter.h" +#include "locatormanager.h" +#include "locatorwidget.h" +#include "opendocumentsfilter.h" +#include "filesystemfilter.h" +#include "settingspage.h" + +#include <coreplugin/coreplugin.h> +#include <coreplugin/statusbarwidget.h> +#include <coreplugin/coreconstants.h> +#include <coreplugin/settingsdatabase.h> +#include <coreplugin/icore.h> +#include <coreplugin/actionmanager/actionmanager.h> +#include <coreplugin/actionmanager/actioncontainer.h> +#include <coreplugin/progressmanager/progressmanager.h> +#include <coreplugin/progressmanager/futureprogress.h> +#include <extensionsystem/pluginmanager.h> +#include <utils/QtConcurrentTools> +#include <utils/qtcassert.h> + +#include <QSettings> +#include <QtPlugin> +#include <QFuture> +#include <QAction> + +namespace Core { +namespace Internal { + +namespace { + static bool filterLessThan(const ILocatorFilter *first, const ILocatorFilter *second) + { + if (first->priority() < second->priority()) + return true; + if (first->priority() > second->priority()) + return false; + return first->id().alphabeticallyBefore(second->id()); + } +} + +LocatorPlugin::LocatorPlugin() + : m_settingsInitialized(false) +{ + m_corePlugin = 0; + m_refreshTimer.setSingleShot(false); + connect(&m_refreshTimer, SIGNAL(timeout()), this, SLOT(refresh())); +} + +LocatorPlugin::~LocatorPlugin() +{ + m_corePlugin->removeObject(m_openDocumentsFilter); + m_corePlugin->removeObject(m_fileSystemFilter); + m_corePlugin->removeObject(m_executeFilter); + m_corePlugin->removeObject(m_settingsPage); + delete m_openDocumentsFilter; + delete m_fileSystemFilter; + delete m_executeFilter; + delete m_settingsPage; + qDeleteAll(m_customFilters); +} + +void LocatorPlugin::initialize(CorePlugin *corePlugin, const QStringList &, QString *) +{ + m_corePlugin = corePlugin; + + m_settingsPage = new SettingsPage(this); + m_corePlugin->addObject(m_settingsPage); + + m_locatorWidget = new LocatorWidget(this); + m_locatorWidget->setEnabled(false); + StatusBarWidget *view = new StatusBarWidget; + view->setWidget(m_locatorWidget); + view->setContext(Context("LocatorWidget")); + view->setPosition(StatusBarWidget::First); + m_corePlugin->addAutoReleasedObject(view); + + QAction *action = new QAction(m_locatorWidget->windowIcon(), m_locatorWidget->windowTitle(), this); + Command *cmd = ActionManager::registerAction(action, "QtCreator.Locate", + Context(Core::Constants::C_GLOBAL)); + cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+K"))); + connect(action, SIGNAL(triggered()), this, SLOT(openLocator())); + connect(cmd, SIGNAL(keySequenceChanged()), this, SLOT(updatePlaceholderText())); + updatePlaceholderText(cmd); + + ActionContainer *mtools = ActionManager::actionContainer(Core::Constants::M_TOOLS); + mtools->addAction(cmd); + + m_corePlugin->addObject(new LocatorManager(m_locatorWidget)); + + m_openDocumentsFilter = new OpenDocumentsFilter; + m_corePlugin->addObject(m_openDocumentsFilter); + + m_fileSystemFilter = new FileSystemFilter(m_locatorWidget); + m_corePlugin->addObject(m_fileSystemFilter); + + m_executeFilter = new ExecuteFilter(); + m_corePlugin->addObject(m_executeFilter); + + m_corePlugin->addAutoReleasedObject(new LocatorFiltersFilter(this, m_locatorWidget)); +} + +void LocatorPlugin::updatePlaceholderText(Command *command) +{ + if (!command) + command = qobject_cast<Command *>(sender()); + QTC_ASSERT(command, return); + if (command->keySequence().isEmpty()) + m_locatorWidget->setPlaceholderText(tr("Type to locate")); + else + m_locatorWidget->setPlaceholderText(tr("Type to locate (%1)").arg( + command->keySequence().toString(QKeySequence::NativeText))); +} + +void LocatorPlugin::openLocator() +{ + m_locatorWidget->show(QString()); +} + +void LocatorPlugin::extensionsInitialized() +{ + m_filters = ExtensionSystem::PluginManager::getObjects<ILocatorFilter>(); + qSort(m_filters.begin(), m_filters.end(), filterLessThan); + setFilters(m_filters); +} + +bool LocatorPlugin::delayedInitialize() +{ + loadSettings(); + return true; +} + +void LocatorPlugin::loadSettings() +{ + QSettings *qs = ICore::settings(); + + // Backwards compatibility to old settings location + if (qs->contains(QLatin1String("QuickOpen/FiltersFilter"))) { + loadSettingsHelper(qs); + } else { + SettingsDatabase *settings = ICore::settingsDatabase(); + loadSettingsHelper(settings); + } + + qs->remove(QLatin1String("QuickOpen")); + + m_locatorWidget->updateFilterList(); + m_locatorWidget->setEnabled(true); + if (m_refreshTimer.interval() > 0) + m_refreshTimer.start(); + m_settingsInitialized = true; +} + +void LocatorPlugin::saveSettings() +{ + if (m_settingsInitialized) { + SettingsDatabase *s = ICore::settingsDatabase(); + s->beginGroup(QLatin1String("QuickOpen")); + s->remove(QString()); + s->setValue(QLatin1String("RefreshInterval"), refreshInterval()); + foreach (ILocatorFilter *filter, m_filters) { + if (!m_customFilters.contains(filter)) + s->setValue(filter->id().toString(), filter->saveState()); + } + s->beginGroup(QLatin1String("CustomFilters")); + int i = 0; + foreach (ILocatorFilter *filter, m_customFilters) { + s->setValue(QLatin1String("directory") + QString::number(i), + filter->saveState()); + ++i; + } + s->endGroup(); + s->endGroup(); + } +} + +/*! + Return all filters, including the ones created by the user. +*/ +QList<ILocatorFilter *> LocatorPlugin::filters() +{ + return m_filters; +} + +/*! + This returns a subset of all the filters, that contains only the filters that + have been created by the user at some point (maybe in a previous session). + */ +QList<ILocatorFilter *> LocatorPlugin::customFilters() +{ + return m_customFilters; +} + +void LocatorPlugin::setFilters(QList<ILocatorFilter *> f) +{ + m_filters = f; + m_locatorWidget->updateFilterList(); +} + +void LocatorPlugin::setCustomFilters(QList<ILocatorFilter *> filters) +{ + m_customFilters = filters; +} + +int LocatorPlugin::refreshInterval() +{ + return m_refreshTimer.interval() / 60000; +} + +void LocatorPlugin::setRefreshInterval(int interval) +{ + if (interval < 1) { + m_refreshTimer.stop(); + m_refreshTimer.setInterval(0); + return; + } + m_refreshTimer.setInterval(interval * 60000); + m_refreshTimer.start(); +} + +void LocatorPlugin::refresh(QList<ILocatorFilter *> filters) +{ + if (filters.isEmpty()) + filters = m_filters; + QFuture<void> task = QtConcurrent::run(&ILocatorFilter::refresh, filters); + FutureProgress *progress = + ProgressManager::addTask(task, tr("Indexing"), Core::Constants::TASK_INDEX); + connect(progress, SIGNAL(finished()), this, SLOT(saveSettings())); +} + +} // namespace Internal +} // namespace Core diff --git a/src/plugins/coreplugin/locator/locatorplugin.h b/src/plugins/coreplugin/locator/locatorplugin.h new file mode 100644 index 0000000000..f0a8a719b2 --- /dev/null +++ b/src/plugins/coreplugin/locator/locatorplugin.h @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef LOCATORPLUGIN_H +#define LOCATORPLUGIN_H + +#include "ilocatorfilter.h" +#include "directoryfilter.h" +#include "executefilter.h" + +#include <extensionsystem/iplugin.h> +#include <coreplugin/actionmanager/command.h> + +#include <QTimer> +#include <QFutureWatcher> + +namespace Core { +namespace Internal { + +class CorePlugin; +class LocatorWidget; +class OpenDocumentsFilter; +class FileSystemFilter; +class SettingsPage; + +class LocatorPlugin : public QObject +{ + Q_OBJECT + +public: + LocatorPlugin(); + ~LocatorPlugin(); + + void initialize(CorePlugin *corePlugin, const QStringList &arguments, QString *errorMessage); + void extensionsInitialized(); + bool delayedInitialize(); + + QList<ILocatorFilter *> filters(); + QList<ILocatorFilter *> customFilters(); + void setFilters(QList<ILocatorFilter *> f); + void setCustomFilters(QList<ILocatorFilter *> f); + int refreshInterval(); + void setRefreshInterval(int interval); + +public slots: + void refresh(QList<ILocatorFilter *> filters = QList<ILocatorFilter *>()); + void saveSettings(); + void openLocator(); + +private slots: + void updatePlaceholderText(Core::Command *command = 0); + +#ifdef WITH_TESTS + void test_basefilefilter(); + void test_basefilefilter_data(); +#endif + +private: + void loadSettings(); + + template <typename S> + void loadSettingsHelper(S *settings); + + LocatorWidget *m_locatorWidget; + SettingsPage *m_settingsPage; + + bool m_settingsInitialized; + QList<ILocatorFilter *> m_filters; + QList<ILocatorFilter *> m_customFilters; + int m_refreshInterval; + QTimer m_refreshTimer; + OpenDocumentsFilter *m_openDocumentsFilter; + FileSystemFilter *m_fileSystemFilter; + ExecuteFilter *m_executeFilter; + CorePlugin *m_corePlugin; +}; + +template <typename S> +void LocatorPlugin::loadSettingsHelper(S *settings) +{ + settings->beginGroup(QLatin1String("QuickOpen")); + m_refreshTimer.setInterval(settings->value(QLatin1String("RefreshInterval"), 60).toInt() * 60000); + + foreach (ILocatorFilter *filter, m_filters) { + if (settings->contains(filter->id().toString())) { + const QByteArray state = settings->value(filter->id().toString()).toByteArray(); + if (!state.isEmpty()) + filter->restoreState(state); + } + } + settings->beginGroup(QLatin1String("CustomFilters")); + QList<ILocatorFilter *> customFilters; + const QStringList keys = settings->childKeys(); + foreach (const QString &key, keys) { + ILocatorFilter *filter = new DirectoryFilter; + filter->restoreState(settings->value(key).toByteArray()); + m_filters.append(filter); + customFilters.append(filter); + } + setCustomFilters(customFilters); + settings->endGroup(); + settings->endGroup(); +} + +} // namespace Internal +} // namespace Core + +#endif // LOCATORPLUGIN_H diff --git a/src/plugins/coreplugin/locator/locatorsearchutils.cpp b/src/plugins/coreplugin/locator/locatorsearchutils.cpp new file mode 100644 index 0000000000..189449fc66 --- /dev/null +++ b/src/plugins/coreplugin/locator/locatorsearchutils.cpp @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + + +#include "locatorsearchutils.h" + +#include <QSet> +#include <QString> +#include <QVariant> + +namespace Core { + +uint qHash(const Core::LocatorFilterEntry &entry) +{ + if (entry.internalData.canConvert(QVariant::String)) + return QT_PREPEND_NAMESPACE(qHash)(entry.internalData.toString()); + return QT_PREPEND_NAMESPACE(qHash)(entry.internalData.constData()); +} + +} // namespace Core + +void Core::Internal::runSearch(QFutureInterface<Core::LocatorFilterEntry> &entries, + QList<ILocatorFilter *> filters, QString searchText) +{ + QSet<LocatorFilterEntry> alreadyAdded; + const bool checkDuplicates = (filters.size() > 1); + foreach (ILocatorFilter *filter, filters) { + if (entries.isCanceled()) + break; + + foreach (const LocatorFilterEntry &entry, filter->matchesFor(entries, searchText)) { + if (checkDuplicates && alreadyAdded.contains(entry)) + continue; + entries.reportResult(entry); + if (checkDuplicates) + alreadyAdded.insert(entry); + } + } +} diff --git a/src/plugins/coreplugin/locator/locatorsearchutils.h b/src/plugins/coreplugin/locator/locatorsearchutils.h new file mode 100644 index 0000000000..1abf03a8fd --- /dev/null +++ b/src/plugins/coreplugin/locator/locatorsearchutils.h @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef LOCATORSEARCHUTILS_H +#define LOCATORSEARCHUTILS_H + +#include "ilocatorfilter.h" + +namespace Core { +namespace Internal { + +void CORE_EXPORT runSearch(QFutureInterface<LocatorFilterEntry> &entries, + QList<ILocatorFilter *> filters, + QString searchText); + +} // namespace Internal +} // namespace Core + +#endif // LOCATORSEARCHUTILS_H + diff --git a/src/plugins/coreplugin/locator/locatorwidget.cpp b/src/plugins/coreplugin/locator/locatorwidget.cpp new file mode 100644 index 0000000000..483ebe1a3f --- /dev/null +++ b/src/plugins/coreplugin/locator/locatorwidget.cpp @@ -0,0 +1,590 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "locatorwidget.h" +#include "locatorplugin.h" +#include "locatorconstants.h" +#include "locatorsearchutils.h" +#include "ilocatorfilter.h" + +#include <coreplugin/coreconstants.h> +#include <coreplugin/icore.h> +#include <coreplugin/modemanager.h> +#include <coreplugin/actionmanager/actionmanager.h> +#include <coreplugin/fileiconprovider.h> +#include <coreplugin/icontext.h> +#include <utils/appmainwindow.h> +#include <utils/filterlineedit.h> +#include <utils/hostosinfo.h> +#include <utils/qtcassert.h> +#include <utils/runextensions.h> + +#include <QColor> +#include <QFileInfo> +#include <QTimer> +#include <QEvent> +#include <QAction> +#include <QApplication> +#include <QHBoxLayout> +#include <QHeaderView> +#include <QKeyEvent> +#include <QMenu> +#include <QScrollBar> +#include <QTreeView> +#include <QToolTip> + +Q_DECLARE_METATYPE(Core::ILocatorFilter*) +Q_DECLARE_METATYPE(Core::LocatorFilterEntry) + +namespace Core { +namespace Internal { + +/* A model to represent the Locator results. */ +class LocatorModel : public QAbstractListModel +{ +public: + LocatorModel(QObject *parent = 0) + : QAbstractListModel(parent) +// , mDisplayCount(64) + {} + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + + void setEntries(const QList<LocatorFilterEntry> &entries); + //void setDisplayCount(int count); + +private: + mutable QList<LocatorFilterEntry> mEntries; + //int mDisplayCount; +}; + +class CompletionList : public QTreeView +{ +public: + CompletionList(QWidget *parent = 0); + + void updatePreferredSize(); + QSize preferredSize() const { return m_preferredSize; } + + void focusOutEvent (QFocusEvent *event) { + if (event->reason() == Qt::ActiveWindowFocusReason) + hide(); + QTreeView::focusOutEvent(event); + } + + void next() { + int index = currentIndex().row(); + ++index; + if (index >= model()->rowCount(QModelIndex())) { + // wrap + index = 0; + } + setCurrentIndex(model()->index(index, 0)); + } + + void previous() { + int index = currentIndex().row(); + --index; + if (index < 0) { + // wrap + index = model()->rowCount(QModelIndex()) - 1; + } + setCurrentIndex(model()->index(index, 0)); + } + +private: + QSize m_preferredSize; +}; + +} // namespace Internal + + +using namespace Core::Internal; + +// =========== LocatorModel =========== + +int LocatorModel::rowCount(const QModelIndex & /* parent */) const +{ + return mEntries.size(); +} + +int LocatorModel::columnCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : 2; +} + +/*! + * When asked for the icon via Qt::DecorationRole, the LocatorModel lazily + * resolves and caches the Greehouse-specific file icon when + * FilterEntry::resolveFileIcon is true. FilterEntry::internalData is assumed + * to be the filename. + */ +QVariant LocatorModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= mEntries.size()) + return QVariant(); + + if (role == Qt::DisplayRole) { + if (index.column() == 0) + return mEntries.at(index.row()).displayName; + else if (index.column() == 1) + return mEntries.at(index.row()).extraInfo; + } else if (role == Qt::ToolTipRole) { + if (mEntries.at(index.row()).extraInfo.isEmpty()) + return QVariant(mEntries.at(index.row()).displayName); + else + return QVariant(mEntries.at(index.row()).displayName + + QLatin1String("\n\n") + mEntries.at(index.row()).extraInfo); + } else if (role == Qt::DecorationRole && index.column() == 0) { + LocatorFilterEntry &entry = mEntries[index.row()]; + if (!entry.fileIconResolved && !entry.fileName.isEmpty() && entry.displayIcon.isNull()) { + entry.fileIconResolved = true; + entry.displayIcon = FileIconProvider::icon(entry.fileName); + } + return entry.displayIcon; + } else if (role == Qt::ForegroundRole && index.column() == 1) { + return QColor(Qt::darkGray); + } else if (role == Qt::UserRole) { + return qVariantFromValue(mEntries.at(index.row())); + } + + return QVariant(); +} + +void LocatorModel::setEntries(const QList<LocatorFilterEntry> &entries) +{ + beginResetModel(); + mEntries = entries; + endResetModel(); +} + +// =========== CompletionList =========== + +CompletionList::CompletionList(QWidget *parent) + : QTreeView(parent) +{ + setRootIsDecorated(false); + setUniformRowHeights(true); + setMaximumWidth(900); + header()->hide(); + header()->setStretchLastSection(true); + // This is too slow when done on all results + //header()->setResizeMode(QHeaderView::ResizeToContents); + setWindowFlags(Qt::ToolTip); + if (Utils::HostOsInfo::isMacHost()) { + if (horizontalScrollBar()) + horizontalScrollBar()->setAttribute(Qt::WA_MacMiniSize); + if (verticalScrollBar()) + verticalScrollBar()->setAttribute(Qt::WA_MacMiniSize); + } +} + +void CompletionList::updatePreferredSize() +{ + const QStyleOptionViewItem &option = viewOptions(); + const QSize shint = itemDelegate()->sizeHint(option, model()->index(0, 0)); + + m_preferredSize = QSize(730, shint.height() * 17 + frameWidth() * 2); +} + +// =========== LocatorWidget =========== + +LocatorWidget::LocatorWidget(LocatorPlugin *qop) : + m_locatorPlugin(qop), + m_locatorModel(new LocatorModel(this)), + m_completionList(new CompletionList(this)), + m_filterMenu(new QMenu(this)), + m_refreshAction(new QAction(tr("Refresh"), this)), + m_configureAction(new QAction(tr("Configure..."), this)), + m_fileLineEdit(new Utils::FilterLineEdit), + m_updateRequested(false), + m_acceptRequested(false), + m_possibleToolTipRequest(false) +{ + // Explicitly hide the completion list popup. + m_completionList->hide(); + + setFocusProxy(m_fileLineEdit); + setWindowTitle(tr("Locate...")); + resize(200, 90); + QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); + sizePolicy.setHorizontalStretch(0); + sizePolicy.setVerticalStretch(0); + setSizePolicy(sizePolicy); + setMinimumSize(QSize(200, 0)); + + QHBoxLayout *layout = new QHBoxLayout(this); + setLayout(layout); + layout->setMargin(0); + layout->addWidget(m_fileLineEdit); + + setWindowIcon(QIcon(QLatin1String(":/locator/images/locator.png"))); + const QPixmap image = QPixmap(QLatin1String(Core::Constants::ICON_MAGNIFIER)); + m_fileLineEdit->setButtonPixmap(Utils::FancyLineEdit::Left, image); + m_fileLineEdit->setButtonToolTip(Utils::FancyLineEdit::Left, tr("Options")); + m_fileLineEdit->setFocusPolicy(Qt::ClickFocus); + m_fileLineEdit->setButtonVisible(Utils::FancyLineEdit::Left, true); + // We set click focus since otherwise you will always get two popups + m_fileLineEdit->setButtonFocusPolicy(Utils::FancyLineEdit::Left, Qt::ClickFocus); + m_fileLineEdit->setAttribute(Qt::WA_MacShowFocusRect, false); + + m_fileLineEdit->installEventFilter(this); + this->installEventFilter(this); + + m_completionList->setModel(m_locatorModel); + m_completionList->header()->resizeSection(0, 300); + m_completionList->updatePreferredSize(); + m_completionList->resize(m_completionList->preferredSize()); + + m_filterMenu->addAction(m_refreshAction); + m_filterMenu->addAction(m_configureAction); + + m_fileLineEdit->setButtonMenu(Utils::FancyLineEdit::Left, m_filterMenu); + + connect(m_refreshAction, SIGNAL(triggered()), m_locatorPlugin, SLOT(refresh())); + connect(m_configureAction, SIGNAL(triggered()), this, SLOT(showConfigureDialog())); + connect(m_fileLineEdit, SIGNAL(textChanged(QString)), + this, SLOT(showPopup())); + connect(m_completionList, SIGNAL(activated(QModelIndex)), + this, SLOT(scheduleAcceptCurrentEntry())); + + m_entriesWatcher = new QFutureWatcher<LocatorFilterEntry>(this); + connect(m_entriesWatcher, SIGNAL(finished()), SLOT(updateEntries())); + + m_showPopupTimer = new QTimer(this); + m_showPopupTimer->setInterval(100); + m_showPopupTimer->setSingleShot(true); + connect(m_showPopupTimer, SIGNAL(timeout()), SLOT(showPopupNow())); +} + +void LocatorWidget::setPlaceholderText(const QString &text) +{ + m_fileLineEdit->setPlaceholderText(text); +} + +void LocatorWidget::updateFilterList() +{ + typedef QMap<Id, QAction *> IdActionMap; + + m_filterMenu->clear(); + + // update actions and menu + IdActionMap actionCopy = m_filterActionMap; + m_filterActionMap.clear(); + // register new actions, update existent + foreach (ILocatorFilter *filter, m_locatorPlugin->filters()) { + if (filter->shortcutString().isEmpty() || filter->isHidden()) + continue; + Id filterId = filter->id(); + Id locatorId = filterId.withPrefix("Locator."); + QAction *action = 0; + Command *cmd = 0; + if (!actionCopy.contains(filterId)) { + // register new action + action = new QAction(filter->displayName(), this); + cmd = ActionManager::registerAction(action, locatorId, + Context(Core::Constants::C_GLOBAL)); + cmd->setAttribute(Command::CA_UpdateText); + connect(action, SIGNAL(triggered()), this, SLOT(filterSelected())); + action->setData(qVariantFromValue(filter)); + } else { + action = actionCopy.take(filterId); + action->setText(filter->displayName()); + cmd = ActionManager::command(locatorId); + } + m_filterActionMap.insert(filterId, action); + m_filterMenu->addAction(cmd->action()); + } + + // unregister actions that are deleted now + const IdActionMap::Iterator end = actionCopy.end(); + for (IdActionMap::Iterator it = actionCopy.begin(); it != end; ++it) { + ActionManager::unregisterAction(it.value(), it.key().withPrefix("Locator.")); + delete it.value(); + } + + m_filterMenu->addSeparator(); + m_filterMenu->addAction(m_refreshAction); + m_filterMenu->addAction(m_configureAction); +} + +bool LocatorWidget::eventFilter(QObject *obj, QEvent *event) +{ + if (obj == m_fileLineEdit && event->type() == QEvent::KeyPress) { + if (m_possibleToolTipRequest) + m_possibleToolTipRequest = false; + if (QToolTip::isVisible()) + QToolTip::hideText(); + + QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event); + switch (keyEvent->key()) { + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_PageUp: + case Qt::Key_PageDown: + showCompletionList(); + QApplication::sendEvent(m_completionList, event); + return true; + case Qt::Key_Enter: + case Qt::Key_Return: + scheduleAcceptCurrentEntry(); + return true; + case Qt::Key_Escape: + m_completionList->hide(); + return true; + case Qt::Key_Tab: + m_completionList->next(); + return true; + case Qt::Key_Backtab: + m_completionList->previous(); + return true; + case Qt::Key_Alt: + if (keyEvent->modifiers() == Qt::AltModifier) { + m_possibleToolTipRequest = true; + return true; + } + break; + default: + break; + } + } else if (obj == m_fileLineEdit && event->type() == QEvent::KeyRelease) { + QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event); + if (m_possibleToolTipRequest) { + m_possibleToolTipRequest = false; + if (m_completionList->isVisible() + && (keyEvent->key() == Qt::Key_Alt) + && (keyEvent->modifiers() == Qt::NoModifier)) { + const QModelIndex index = m_completionList->currentIndex(); + if (index.isValid()) { + QToolTip::showText(m_completionList->pos() + m_completionList->visualRect(index).topRight(), + m_locatorModel->data(index, Qt::ToolTipRole).toString()); + return true; + } + } + } + } else if (obj == m_fileLineEdit && event->type() == QEvent::FocusOut) { + QFocusEvent *fev = static_cast<QFocusEvent *>(event); + if (fev->reason() != Qt::ActiveWindowFocusReason || !m_completionList->isActiveWindow()) { + m_completionList->hide(); + m_fileLineEdit->clearFocus(); + } + } else if (obj == m_fileLineEdit && event->type() == QEvent::FocusIn) { + showPopupNow(); + } else if (obj == this && event->type() == QEvent::ShortcutOverride) { + QKeyEvent *ke = static_cast<QKeyEvent *>(event); + switch (ke->key()) { + case Qt::Key_Escape: + if (!ke->modifiers()) { + event->accept(); + QTimer::singleShot(0, this, SLOT(setFocusToCurrentMode())); + return true; + } + case Qt::Key_Alt: + if (ke->modifiers() == Qt::AltModifier) { + event->accept(); + return true; + } + break; + default: + break; + } + } + return QWidget::eventFilter(obj, event); +} + +void LocatorWidget::setFocusToCurrentMode() +{ + Core::ModeManager::setFocusToCurrentMode(); +} + +void LocatorWidget::showCompletionList() +{ + const int border = m_completionList->frameWidth(); + const QSize size = m_completionList->preferredSize(); + const QRect rect(mapToGlobal(QPoint(-border, -size.height() - border)), size); + m_completionList->setGeometry(rect); + m_completionList->show(); +} + +void LocatorWidget::showPopup() +{ + m_updateRequested = true; + m_showPopupTimer->start(); +} + +void LocatorWidget::showPopupNow() +{ + m_showPopupTimer->stop(); + updateCompletionList(m_fileLineEdit->text()); + showCompletionList(); +} + +QList<ILocatorFilter *> LocatorWidget::filtersFor(const QString &text, QString &searchText) +{ + QList<ILocatorFilter *> filters = m_locatorPlugin->filters(); + const int whiteSpace = text.indexOf(QLatin1Char(' ')); + QString prefix; + if (whiteSpace >= 0) + prefix = text.left(whiteSpace); + if (!prefix.isEmpty()) { + prefix = prefix.toLower(); + QList<ILocatorFilter *> prefixFilters; + foreach (ILocatorFilter *filter, filters) { + if (prefix == filter->shortcutString()) { + searchText = text.mid(whiteSpace+1); + prefixFilters << filter; + } + } + if (!prefixFilters.isEmpty()) + return prefixFilters; + } + searchText = text; + QList<ILocatorFilter *> activeFilters; + foreach (ILocatorFilter *filter, filters) + if (filter->isIncludedByDefault()) + activeFilters << filter; + return activeFilters; +} + +void LocatorWidget::updateCompletionList(const QString &text) +{ + m_updateRequested = true; + QString searchText; + const QList<ILocatorFilter *> filters = filtersFor(text, searchText); + + // cancel the old future + m_entriesWatcher->future().cancel(); + m_entriesWatcher->future().waitForFinished(); + + QFuture<LocatorFilterEntry> future = QtConcurrent::run(runSearch, filters, searchText); + m_entriesWatcher->setFuture(future); +} + +void LocatorWidget::updateEntries() +{ + m_updateRequested = false; + if (m_entriesWatcher->future().isCanceled()) { + // reset to usable state + m_acceptRequested = false; + return; + } + + const QList<LocatorFilterEntry> entries = m_entriesWatcher->future().results(); + m_locatorModel->setEntries(entries); + if (m_locatorModel->rowCount() > 0) + m_completionList->setCurrentIndex(m_locatorModel->index(0, 0)); +#if 0 + m_completionList->updatePreferredSize(); +#endif + if (m_acceptRequested) + acceptCurrentEntry(); +} + +void LocatorWidget::scheduleAcceptCurrentEntry() +{ + if (m_updateRequested) { + // don't just accept the selected entry, since the list is not up to date + // accept will be called after the update finished + m_acceptRequested = true; + } else { + acceptCurrentEntry(); + } +} + +void LocatorWidget::acceptCurrentEntry() +{ + m_acceptRequested = false; + if (!m_completionList->isVisible()) + return; + const QModelIndex index = m_completionList->currentIndex(); + if (!index.isValid()) + return; + const LocatorFilterEntry entry = m_locatorModel->data(index, Qt::UserRole).value<LocatorFilterEntry>(); + m_completionList->hide(); + m_fileLineEdit->clearFocus(); + entry.filter->accept(entry); +} + +void LocatorWidget::show(const QString &text, int selectionStart, int selectionLength) +{ + if (!text.isEmpty()) + m_fileLineEdit->setText(text); + if (!m_fileLineEdit->hasFocus()) + m_fileLineEdit->setFocus(); + else + showPopupNow(); + ICore::raiseWindow(ICore::mainWindow()); + + if (selectionStart >= 0) { + m_fileLineEdit->setSelection(selectionStart, selectionLength); + if (selectionLength == 0) // make sure the cursor is at the right position (Mac-vs.-rest difference) + m_fileLineEdit->setCursorPosition(selectionStart); + } else { + m_fileLineEdit->selectAll(); + } +} + +void LocatorWidget::filterSelected() +{ + QString searchText = tr("<type here>"); + QAction *action = qobject_cast<QAction *>(sender()); + QTC_ASSERT(action, return); + ILocatorFilter *filter = action->data().value<ILocatorFilter *>(); + QTC_ASSERT(filter, return); + QString currentText = m_fileLineEdit->text().trimmed(); + // add shortcut string at front or replace existing shortcut string + if (!currentText.isEmpty()) { + searchText = currentText; + foreach (ILocatorFilter *otherfilter, m_locatorPlugin->filters()) { + if (currentText.startsWith(otherfilter->shortcutString() + QLatin1Char(' '))) { + searchText = currentText.mid(otherfilter->shortcutString().length() + 1); + break; + } + } + } + show(filter->shortcutString() + QLatin1Char(' ') + searchText, + filter->shortcutString().length() + 1, + searchText.length()); + updateCompletionList(m_fileLineEdit->text()); + m_fileLineEdit->setFocus(); +} + +void LocatorWidget::showEvent(QShowEvent *event) +{ + QWidget::showEvent(event); +} + +void LocatorWidget::showConfigureDialog() +{ + ICore::showOptionsDialog(Core::Constants::SETTINGS_CATEGORY_CORE, Constants::FILTER_OPTIONS_PAGE); +} + +} // namespace Core diff --git a/src/plugins/coreplugin/locator/locatorwidget.h b/src/plugins/coreplugin/locator/locatorwidget.h new file mode 100644 index 0000000000..5bc150b362 --- /dev/null +++ b/src/plugins/coreplugin/locator/locatorwidget.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef LOCATORWIDGET_H +#define LOCATORWIDGET_H + +#include "locatorplugin.h" + +#include <QWidget> + +QT_BEGIN_NAMESPACE +class QAction; +class QLabel; +class QLineEdit; +class QMenu; +class QTreeView; +QT_END_NAMESPACE + +namespace Utils { + class FilterLineEdit; +} + +namespace Core { +namespace Internal { + +class LocatorModel; +class CompletionList; + +class LocatorWidget + : public QWidget +{ + Q_OBJECT + +public: + explicit LocatorWidget(LocatorPlugin *qop); + + void updateFilterList(); + + void show(const QString &text, int selectionStart = -1, int selectionLength = 0); + + void setPlaceholderText(const QString &text); + +private slots: + void showPopup(); + void showPopupNow(); + void acceptCurrentEntry(); + void filterSelected(); + void showConfigureDialog(); + void updateEntries(); + void scheduleAcceptCurrentEntry(); + void setFocusToCurrentMode(); + +private: + bool eventFilter(QObject *obj, QEvent *event); + + void showEvent(QShowEvent *e); + + void showCompletionList(); + void updateCompletionList(const QString &text); + QList<ILocatorFilter*> filtersFor(const QString &text, QString &searchText); + + LocatorPlugin *m_locatorPlugin; + LocatorModel *m_locatorModel; + + CompletionList *m_completionList; + QMenu *m_filterMenu; + QAction *m_refreshAction; + QAction *m_configureAction; + Utils::FilterLineEdit *m_fileLineEdit; + QTimer *m_showPopupTimer; + QFutureWatcher<LocatorFilterEntry> *m_entriesWatcher; + QMap<Core::Id, QAction *> m_filterActionMap; + bool m_updateRequested; + bool m_acceptRequested; + bool m_possibleToolTipRequest; +}; + +} // namespace Internal +} // namespace Core + +#endif // LOCATORWIDGET_H diff --git a/src/plugins/coreplugin/locator/opendocumentsfilter.cpp b/src/plugins/coreplugin/locator/opendocumentsfilter.cpp new file mode 100644 index 0000000000..61fb946c0a --- /dev/null +++ b/src/plugins/coreplugin/locator/opendocumentsfilter.cpp @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "opendocumentsfilter.h" + +#include <coreplugin/editormanager/editormanager.h> +#include <coreplugin/editormanager/ieditor.h> +#include <utils/fileutils.h> + +#include <QFileInfo> + +using namespace Core; +using namespace Core; +using namespace Core::Internal; +using namespace Utils; + +OpenDocumentsFilter::OpenDocumentsFilter() +{ + setId("Open documents"); + setDisplayName(tr("Open Documents")); + setShortcutString(QString(QLatin1Char('o'))); + setIncludedByDefault(true); + + connect(EditorManager::instance(), SIGNAL(editorOpened(Core::IEditor*)), + this, SLOT(refreshInternally())); + connect(EditorManager::instance(), SIGNAL(editorsClosed(QList<Core::IEditor*>)), + this, SLOT(refreshInternally())); +} + +QList<LocatorFilterEntry> OpenDocumentsFilter::matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future, const QString &entry_) +{ + QList<LocatorFilterEntry> goodEntries; + QList<LocatorFilterEntry> betterEntries; + QString entry = entry_; + const QString lineNoSuffix = EditorManager::splitLineNumber(&entry); + const QChar asterisk = QLatin1Char('*'); + QString pattern = QString(asterisk); + pattern += entry; + pattern += asterisk; + QRegExp regexp(pattern, Qt::CaseInsensitive, QRegExp::Wildcard); + if (!regexp.isValid()) + return goodEntries; + const Qt::CaseSensitivity caseSensitivityForPrefix = caseSensitivity(entry); + foreach (const DocumentModel::Entry &editorEntry, m_editors) { + if (future.isCanceled()) + break; + QString fileName = editorEntry.fileName(); + if (fileName.isEmpty()) + continue; + QString displayName = editorEntry.displayName(); + if (regexp.exactMatch(displayName)) { + QFileInfo fi(fileName); + LocatorFilterEntry fiEntry(this, fi.fileName(), QString(fileName + lineNoSuffix)); + fiEntry.extraInfo = FileUtils::shortNativePath(FileName(fi)); + fiEntry.fileName = fileName; + QList<LocatorFilterEntry> &category = displayName.startsWith(entry, caseSensitivityForPrefix) + ? betterEntries : goodEntries; + category.append(fiEntry); + } + } + betterEntries.append(goodEntries); + return betterEntries; +} + +void OpenDocumentsFilter::refreshInternally() +{ + m_editors.clear(); + foreach (DocumentModel::Entry *e, EditorManager::documentModel()->documents()) { + DocumentModel::Entry entry; + // create copy with only the information relevant to use + // to avoid model deleting entries behind our back + entry.m_displayName = e->displayName(); + entry.m_fileName = e->fileName(); + m_editors.append(entry); + } +} + +void OpenDocumentsFilter::refresh(QFutureInterface<void> &future) +{ + Q_UNUSED(future) + QMetaObject::invokeMethod(this, "refreshInternally", Qt::BlockingQueuedConnection); +} + +void OpenDocumentsFilter::accept(LocatorFilterEntry selection) const +{ + EditorManager::openEditor(selection.internalData.toString(), Id(), + EditorManager::CanContainLineNumber); +} diff --git a/src/plugins/coreplugin/locator/opendocumentsfilter.h b/src/plugins/coreplugin/locator/opendocumentsfilter.h new file mode 100644 index 0000000000..b16ca7144b --- /dev/null +++ b/src/plugins/coreplugin/locator/opendocumentsfilter.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef OPENDOCUMENTSFILTER_H +#define OPENDOCUMENTSFILTER_H + +#include "ilocatorfilter.h" + +#include <coreplugin/editormanager/documentmodel.h> + +#include <QString> +#include <QList> +#include <QFutureInterface> + +namespace Core { +namespace Internal { + +class OpenDocumentsFilter : public Core::ILocatorFilter +{ + Q_OBJECT + +public: + OpenDocumentsFilter(); + QList<Core::LocatorFilterEntry> matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future, const QString &entry); + void accept(Core::LocatorFilterEntry selection) const; + void refresh(QFutureInterface<void> &future); + +public slots: + void refreshInternally(); + +private: + QList<Core::DocumentModel::Entry> m_editors; +}; + +} // namespace Internal +} // namespace Core + +#endif // OPENDOCUMENTSFILTER_H diff --git a/src/plugins/coreplugin/locator/settingspage.cpp b/src/plugins/coreplugin/locator/settingspage.cpp new file mode 100644 index 0000000000..abdec8be5d --- /dev/null +++ b/src/plugins/coreplugin/locator/settingspage.cpp @@ -0,0 +1,210 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "settingspage.h" +#include "locatorconstants.h" + +#include "locatorplugin.h" +#include "ilocatorfilter.h" +#include "directoryfilter.h" + +#include <coreplugin/coreconstants.h> +#include <utils/qtcassert.h> + +#include <QCoreApplication> + +Q_DECLARE_METATYPE(Core::ILocatorFilter*) + +using namespace Core; +using namespace Core::Internal; + +SettingsPage::SettingsPage(LocatorPlugin *plugin) + : m_plugin(plugin), m_widget(0) +{ + setId(Constants::FILTER_OPTIONS_PAGE); + setDisplayName(QCoreApplication::translate("Locator", Core::Constants::FILTER_OPTIONS_PAGE)); + setCategory(Core::Constants::SETTINGS_CATEGORY_CORE); + setDisplayCategory(QCoreApplication::translate("Core", Core::Constants::SETTINGS_TR_CATEGORY_CORE)); + setCategoryIcon(QLatin1String(Core::Constants::SETTINGS_CATEGORY_CORE_ICON)); +} + +QWidget *SettingsPage::widget() +{ + if (!m_widget) { + m_widget = new QWidget; + m_ui.setupUi(m_widget); + m_ui.refreshInterval->setToolTip(m_ui.refreshIntervalLabel->toolTip()); + connect(m_ui.filterList, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), + this, SLOT(updateButtonStates())); + connect(m_ui.filterList, SIGNAL(itemActivated(QListWidgetItem*)), + this, SLOT(configureFilter(QListWidgetItem*))); + connect(m_ui.editButton, SIGNAL(clicked()), + this, SLOT(configureFilter())); + connect(m_ui.addButton, SIGNAL(clicked()), + this, SLOT(addCustomFilter())); + connect(m_ui.removeButton, SIGNAL(clicked()), + this, SLOT(removeCustomFilter())); + + m_ui.refreshInterval->setValue(m_plugin->refreshInterval()); + m_filters = m_plugin->filters(); + m_customFilters = m_plugin->customFilters(); + saveFilterStates(); + updateFilterList(); + } + return m_widget; +} + +void SettingsPage::apply() +{ + // Delete removed filters and clear added filters + qDeleteAll(m_removedFilters); + m_removedFilters.clear(); + m_addedFilters.clear(); + + // Pass the new configuration on to the plugin + m_plugin->setFilters(m_filters); + m_plugin->setCustomFilters(m_customFilters); + m_plugin->setRefreshInterval(m_ui.refreshInterval->value()); + requestRefresh(); + m_plugin->saveSettings(); + saveFilterStates(); +} + +void SettingsPage::finish() +{ + // If settings were applied, this shouldn't change anything. Otherwise it + // makes sure the filter states aren't changed permanently. + restoreFilterStates(); + + // Delete added filters and clear removed filters + qDeleteAll(m_addedFilters); + m_addedFilters.clear(); + m_removedFilters.clear(); + + // Further cleanup + m_filters.clear(); + m_customFilters.clear(); + m_refreshFilters.clear(); + delete m_widget; +} + +void SettingsPage::requestRefresh() +{ + if (!m_refreshFilters.isEmpty()) + m_plugin->refresh(m_refreshFilters); +} + +void SettingsPage::saveFilterStates() +{ + m_filterStates.clear(); + foreach (ILocatorFilter *filter, m_filters) + m_filterStates.insert(filter, filter->saveState()); +} + +void SettingsPage::restoreFilterStates() +{ + foreach (ILocatorFilter *filter, m_filterStates.keys()) + filter->restoreState(m_filterStates.value(filter)); +} + +void SettingsPage::updateFilterList() +{ + m_ui.filterList->clear(); + foreach (ILocatorFilter *filter, m_filters) { + if (filter->isHidden()) + continue; + + QString title; + if (filter->isIncludedByDefault()) + title = filter->displayName(); + else + title = tr("%1 (prefix: %2)").arg(filter->displayName()).arg(filter->shortcutString()); + QListWidgetItem *item = new QListWidgetItem(title); + item->setData(Qt::UserRole, qVariantFromValue(filter)); + m_ui.filterList->addItem(item); + } + if (m_ui.filterList->count() > 0) + m_ui.filterList->setCurrentRow(0); +} + +void SettingsPage::updateButtonStates() +{ + QListWidgetItem *item = m_ui.filterList->currentItem(); + ILocatorFilter *filter = (item ? item->data(Qt::UserRole).value<ILocatorFilter *>() : 0); + m_ui.editButton->setEnabled(filter && filter->isConfigurable()); + m_ui.removeButton->setEnabled(filter && m_customFilters.contains(filter)); +} + +void SettingsPage::configureFilter(QListWidgetItem *item) +{ + if (!item) + item = m_ui.filterList->currentItem(); + QTC_ASSERT(item, return); + ILocatorFilter *filter = item->data(Qt::UserRole).value<ILocatorFilter *>(); + QTC_ASSERT(filter, return); + + if (!filter->isConfigurable()) + return; + bool needsRefresh = false; + filter->openConfigDialog(m_widget, needsRefresh); + if (needsRefresh && !m_refreshFilters.contains(filter)) + m_refreshFilters.append(filter); + updateFilterList(); +} + +void SettingsPage::addCustomFilter() +{ + ILocatorFilter *filter = new DirectoryFilter; + bool needsRefresh = false; + if (filter->openConfigDialog(m_widget, needsRefresh)) { + m_filters.append(filter); + m_addedFilters.append(filter); + m_customFilters.append(filter); + m_refreshFilters.append(filter); + updateFilterList(); + } +} + +void SettingsPage::removeCustomFilter() +{ + QListWidgetItem *item = m_ui.filterList->currentItem(); + QTC_ASSERT(item, return); + ILocatorFilter *filter = item->data(Qt::UserRole).value<ILocatorFilter *>(); + QTC_ASSERT(m_customFilters.contains(filter), return); + m_filters.removeAll(filter); + m_customFilters.removeAll(filter); + m_refreshFilters.removeAll(filter); + if (m_addedFilters.contains(filter)) { + m_addedFilters.removeAll(filter); + delete filter; + } else { + m_removedFilters.append(filter); + } + updateFilterList(); +} diff --git a/src/plugins/coreplugin/locator/settingspage.h b/src/plugins/coreplugin/locator/settingspage.h new file mode 100644 index 0000000000..86ddb9d1a5 --- /dev/null +++ b/src/plugins/coreplugin/locator/settingspage.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef SETTINGSPAGE_H +#define SETTINGSPAGE_H + +#include "ui_settingspage.h" + +#include <coreplugin/dialogs/ioptionspage.h> + +#include <QHash> +#include <QPointer> + +QT_BEGIN_NAMESPACE +class QListWidgetItem; +QT_END_NAMESPACE + +namespace Core { + +class ILocatorFilter; + +namespace Internal { + +class LocatorPlugin; + +class SettingsPage : public Core::IOptionsPage +{ + Q_OBJECT + +public: + explicit SettingsPage(LocatorPlugin *plugin); + + QWidget *widget(); + void apply(); + void finish(); + +private slots: + void updateButtonStates(); + void configureFilter(QListWidgetItem *item = 0); + void addCustomFilter(); + void removeCustomFilter(); + +private: + void updateFilterList(); + void saveFilterStates(); + void restoreFilterStates(); + void requestRefresh(); + + Ui::LocatorSettingsWidget m_ui; + LocatorPlugin *m_plugin; + QPointer<QWidget> m_widget; + QList<ILocatorFilter *> m_filters; + QList<ILocatorFilter *> m_addedFilters; + QList<ILocatorFilter *> m_removedFilters; + QList<ILocatorFilter *> m_customFilters; + QList<ILocatorFilter *> m_refreshFilters; + QHash<ILocatorFilter *, QByteArray> m_filterStates; +}; + +} // namespace Internal +} // namespace Core + +#endif // SETTINGSPAGE_H diff --git a/src/plugins/coreplugin/locator/settingspage.ui b/src/plugins/coreplugin/locator/settingspage.ui new file mode 100644 index 0000000000..797b650269 --- /dev/null +++ b/src/plugins/coreplugin/locator/settingspage.ui @@ -0,0 +1,121 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Core::Internal::LocatorSettingsWidget</class> + <widget class="QWidget" name="Core::Internal::LocatorSettingsWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>367</width> + <height>242</height> + </rect> + </property> + <property name="windowTitle"> + <string>Configure Filters</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QListWidget" name="filterList"> + <property name="font"> + <font/> + </property> + </widget> + </item> + <item row="0" column="1"> + <layout class="QVBoxLayout"> + <item> + <widget class="QPushButton" name="addButton"> + <property name="text"> + <string>Add</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="removeButton"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Remove</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="editButton"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Edit</string> + </property> + </widget> + </item> + <item> + <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> + </layout> + </item> + <item row="1" column="0" colspan="2"> + <layout class="QHBoxLayout"> + <item> + <widget class="QLabel" name="refreshIntervalLabel"> + <property name="toolTip"> + <string>Locator filters that do not update their cached data immediately, such as the custom directory filters, update it after this time interval.</string> + </property> + <property name="text"> + <string>Refresh interval:</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="refreshInterval"> + <property name="frame"> + <bool>true</bool> + </property> + <property name="buttonSymbols"> + <enum>QAbstractSpinBox::PlusMinus</enum> + </property> + <property name="suffix"> + <string> min</string> + </property> + <property name="maximum"> + <number>320</number> + </property> + <property name="singleStep"> + <number>5</number> + </property> + <property name="value"> + <number>60</number> + </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> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/plugins/coreplugin/progressmanager/progressmanager.cpp b/src/plugins/coreplugin/progressmanager/progressmanager.cpp index 89818a6d31..5180a37411 100644 --- a/src/plugins/coreplugin/progressmanager/progressmanager.cpp +++ b/src/plugins/coreplugin/progressmanager/progressmanager.cpp @@ -129,7 +129,7 @@ using namespace Core::Internal; \c QFuture object. This is what you want to give the ProgressManager in the addTask() function. - Have a look at e.g Locator::ILocatorFilter. Locator filters implement + Have a look at e.g Core::ILocatorFilter. Locator filters implement a function \c refresh which takes a \c QFutureInterface object as a parameter. These functions look something like: \code |