QT CREATOR 插件开发:向查找对话框添加过滤器

Qt Creator 的查找对话框允许用户使用文本或者正则表达式进行搜索。点击“编辑-查找/替换-高级查找”即可打开查找对话框。

查找对话框:

在查找对话框中,“范围”和“文件模式”两项都是可以扩展的。也是就说,我们可以利用插件,向范围下拉框添加更多项目,每次都可以依据选择的下拉项不同,读取不同的配置文件。在 Qt Creator 中,范围下拉框中的每一项都叫做“查找过滤器(find filter)”。

Qt Creator 的 find 插件暴露出的接口是 Find::IFindFilter ,该接口在 src/plugins/find/ 中声明:

#ifndef IFINDFILTER_H#define IFINDFILTER_H#include "find_"#include ""QT_BEGIN_NAMESPACEclass QWidget;class QSettings;class QKeySequence;QT_END_NAMESPACEnamespace Find {class FIND_EXPORT IFindFilter : public QObject{    Q_OBJECTpublic:    virtual ~IFindFilter() {}    virtual QString id() const = 0;    virtual QString displayName() const = 0;    ///    virtual bool isEnabled() const = 0;    virtual bool canCancel() const = 0;    virtual void cancel() = 0;    virtual QKeySequence defaultShortcut() const;    virtual bool isReplaceSupported() const { return false; }    virtual FindFlags supportedFindFlags() const;    virtual void findAll(const QString &txt, Find::FindFlags findFlags) = 0;    virtual void replaceAll(const QString &txt, Find::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) }signals:    void changed();};} // namespace Find#endif // IFINDFILTER_H

通过实现 IFindFilter 接口,然后将其实例添加到对象池,我们就可以注册一个新的文件过滤器,这意味着我们将会在前面提到的查找对话框的“范围”下拉框中找到新的一项。

假设我们需要一个自定义过滤器,用于在已打开工程中查找一个给定的头文件。那么,我们可以根据下面的几个步骤来理解如何编写这个查找过滤器。

首先,我们需要声明一个实现了 Find::IFindFilter 接口的类,也就是 HeaderFilter 。这个类定义如下:

#ifndef HEADERFILTER_H#define HEADERFILTER_H#include class HeaderFilterData;class HeaderFilter : public Find::IFindFilter{    Q_OBJECTpublic:    HeaderFilter();    ~HeaderFilter();    QString id() const;    QString displayName() const;    bool isEnabled() const;    bool canCancel() const;    void cancel();    QKeySequence defaultShortcut() const;    void findAll(const QString &txt, Find::FindFlags findFlags);    QWidget *createConfigWidget();private:    HeaderFilterData *d;};#endif // HEADERFILTER_H

构造函数和析构函数都是空白的。我们会在后面详细了解过 IFindFilter 的工作过程之后再来填入更多的代码。

struct HeaderFilterData{};HeaderFilter::HeaderFilter(){    d = new HeaderFilterData;}HeaderFilter::~HeaderFilter(){    delete d;}

id() 函数用于该 filter 的唯一标示符:

QString HeaderFilter::id() const{    return "HeaderFilter";}

displayName() 函数用于在范围下拉框中显示:

QString HeaderFilter::displayName() const{    return tr("Header Filter");}

isEnabled() 函数返回 bool 值,表示该过滤器是否可用。在这个例子中,我们希望在 Qt Creator 加载了项目之后,该过滤器可用,否则不可用。为了更进一步了解该函数的作用,我们必须了解 ProjectExplorer 命名空间。现在,我们简单的将其返回值设置为 true,等到学习过 ProjectExplorer 命名空间之后再对此函数作进一步完善。

【领 QT开发教程 学习资料, 点击下方链接莬费领取↓↓ ,先码住不迷路~】

点击这里:

bool HeaderFilter::isEnabled() const{    return true;}

canCancel() 和 cancel() 函数用于表示该过滤器能够被取消。这里我们也是用最简单的代码:

bool HeaderFilter::canCancel() const{    return true;}void HeaderFilter::cancel(){    // Do nothing}

defaultShortcut() 函数返回与此过滤器相关的快捷键。用户按下该快捷键之后,查找对话框将自动打开,并且该过滤器被选中。在本例中,我们返回一个非法快捷键:

QKeySequence HeaderFilter::defaultShortcut() const{    return QKeySequence();}

createConfigWidget() 函数返回一个用于配置的组件。这个组件将显示在查找对话框的最下方(注意本文开始的查找对话框图片中,最下方的“文件模式”的位置)。在本例中,我们不需要对 header filter 作任何配置,因此使用一个 label 显示即可:

QWidget *HeaderFilter::createConfigWidget(){    return (new QLabel("This is a header filter"));}

findAll() 函数用于实现真实的“查找”操作。我们需要了解 ProjectExplorer 、 TextEditor 、 Find 和 Core::Utils 这几个命名空间之后才能编写实际代码。所以现在,我们的 findAll() 函数还是空白的:

void HeaderFilter::findAll(const QString &text, Find::FindFlags findFlags){    // Do nothing}

HeaderFilterPlugin 代码同前面一样,我们只需修改 initialize() 函数:

3. 实现HeaderFilterPlugin代码HeaderFilterPlugin代码同前面一样,我们只需修改 initialize()函数:

将我们的代码编译之后,重启 Qt Creator。我们之前仅仅使用了 coreplugin 这个插件,而现在,我们需要使用 find。这需要修改 pro 文件:

QTC_SOURCE      = E:/codelibs/QtCreator/_BUILD       = E:/codelibs/QtCreator/build/TEMPLATE        = libTARGET          = HeaderFilterPluginIDE_SOURCE_TREE = $QTC_SOURCEIDE_BUILD_TREE  = $QTC_BUILDPROVIDER        = GalaxyWorldDESTDIR         = $QTC_BUILD/lib/qtcreator/plugins/GalaxyWorldLIBS           += -L$QTC_BUILD/lib/qtcreator/plugins/Nokiainclude($QTC_SOURCE/src/)include($QTC_SOURCE/src/plugins/coreplugin/)include($QTC_SOURCE/src/plugins/find/)HEADERS         =           =  _FILES     = 

注意其中添加了 一行。

现在,我们可以在查找对话框中看到新增加的 Header Filter 一项。注意在范围下拉框中的显示以及对话框下方的配置组件。

现在,由于我们的 findAll() 函数没有实现,我们仅仅为对话框提供了一个没有“查找”功能的界面。在后面的章节中,我们将继续完成这个插件。

ProjectExplorer 命名空间中的类都是与 Qt Creator 中的工程管理相关的。该命名空间由 projectexplorer 插件提供,而 Qt Crteaor 所支持的工程类型也是由这个插件提供。例如,

cmakeprojectmanager 插件实现了 ProjectExplorer 命名空间中所定义的接口,支持的是 CMake 工程; qt4projectmanager 插件提供对 Qt 4 工程的支持; qmlprojectmanager 插件提供对 QML 工程的支持。

下面列出的是 ProjectManager 命名空间中一些关键的类和接口:

类/接口

描述

ProjectExplorer::IProjectManager

如果需要提供一种新的工程类型,必须实现此接口。该接口的实现帮助 Qt Creator 加载工程。

ProjectExplorer::Project

该接口定义了一个工程的几个术语:

用于描述工程的文件( Core::IFile ); 用于描述工程是应用程序还是库的一个 bool 值; 构建、清理工程所需的步骤( ProjectExplorer:: BuildStep ); 运行工程所需的配置信息; 运行项目所需的环境; 工程浏览器面板的根节点; 构建工程时所需的 include 路径和宏。

ProjectManager::ProjectExplorerPlugin

工程浏览器插件所需实现的 Core::IPlugin 接口。通过该类,我们可以:

访问所有打开的工程; 访问当前工程; 访问在工程浏览器面板中当前选择的节点(文件/文件夹) 访问构建管理器( ProjectManager::BuildManager )

使用 ProjectManager::ProjectExplorerPlugin ,我们可以获取 Qt Creator 的所有已打开的工程。下面的代码显示如何做到这一点:

#include #include // Catch hold of the plugin-managerExtensionSystem::PluginManager* pm    = ExtensionSystem::PluginManager::instance();// Look for the ProjectExplorerPlugin objectProjectExplorer::ProjectExplorerPlugin* projectExplorerPlugin    = pm->getObject();// Fetch a list of all open projectsQListprojects    = d->projectPlugin->session()->projects();

利用我们得到的 projects 列表,我们可以访问该工程文件以及工程中的其他文件(源代码文件、头文件、资源文件等)。于是,我们可以编写如下的代码:

Q_FOREACH(ProjectExplorer::Project* project, projects){    QString name = project->name();    Core::IFile* projectFile = project->file();    // Do something with the above. For example:    qDebug("Project %s has project file as %s",           qPrintable(name),           qPrintable(projectFile->fileName()));}

上面的代码展示了如何获取工程文件(,.pro 文件等),但是并不能获取与工程关联的其他文件。而下面的代码则显示了如何从 projects 列表获取所有文件的文件名列表:

// Make a list of files in each projectQStringList files;Q_FOREACH(ProjectManager::Project* project, projects){    files += project->files(Project::AllFiles);}

我们的 HeaderFilter 应该在至少有一个工程打开的情况下才能够使用。为简单起见,我们前面将 isEnabled() 函数的返回值始终设置为 true。下面,我们来修改这段代码,以便满足我们的需求:

struct HeaderFilterData{    ProjectExplorer::ProjectExplorerPlugin* projectExplorer()    {        if(m_projectPlugin) {            return m_projectPlugin;        }        ExtensionSystem::PluginManager* pm            = ExtensionSystem::PluginManager::instance();        m_projectPlugin = pm->getObject();    return m_projectPlugin;    }private:    ProjectExplorer::ProjectExplorerPlugin* m_projectPlugin;};// ...bool HeaderFilter::isEnabled() const{    return d->projectExplorer()->session()->projects().count() >0;}

从前面的内容中,我们已经了解到如何访问与打开的工程相关联的文件名。现在,我们就可以在这些文件中进行查找了。下面,我们开始一步步实现 HeaderFilter::findAll() 函数:

void HeaderFilter::findAll(const QString &text, Find::FindFlags findFlags){    // Fetch a list of all open projects    QListprojects        = d->projectExplorer()->session()->projects();    // Make a list of files in each project    QStringList files;    Q_FOREACH(ProjectExplorer::Project* project, projects) {        files += project->files(ProjectExplorer::Project::AllFiles);    }    // Remove duplicates    ();    // Search for text in files    // ...}

我们可以获知需要搜索的文件的数目:或许只有 1 个文件,也可能有 1000 或者更多的文件!因此,直接在 findAll() 函数中进行搜索不是一个好主意。如果 findAll() 函数执行时间过长,就会使得整个 Qt Creator 界面停止响应,直到搜索完成(通常,这是 GUI 程序设计中需要注意的一个问题:不要在 GUI 线程执行过于复杂的操作,以免拖慢 GUI 的响应)!

这种问题的解决方案是:

使用 QtConcurrent ,创建多个线程执行实际的搜索; 为 QtConcurrent 返回的 QFuture 初始化一个 QFutureWatcher ,以便在搜索结果生成之后发送 signals; 监听 QFutureWatcher 发出的信息,列出搜索结果。

Qt Creator 已经为我们提供了函数 findInFiles() ,用于在一个文件列表中查找一个字符串,然后返回一个用于监视搜索结果的 QFuture 对象。这个函数在 src/libs/utils/ 中声明:

namespace Utils {// ...class QTCREATOR_UTILS_EXPORT FileSearchResult{public:    FileSearchResult() {}    FileSearchResult(QString fileName, int lineNumber, QString matchingLine,                     int matchStart, int matchLength,                     QStringList regexpCapturedTexts)            : fileName(fileName),            lineNumber(lineNumber),            matchingLine(matchingLine),            matchStart(matchStart),            matchLength(matchLength),            regexpCapturedTexts(regexpCapturedTexts)    {    }    QString fileName;    int lineNumber;    QString matchingLine;    int matchStart;    int matchLength;    QStringList regexpCapturedTexts;};typedef QListFileSearchResultList;QTCREATOR_UTILS_EXPORT QFuturefindInFiles(    const QString &searchTerm,    FileIterator *files,    QTextDocument::FindFlags flags,    QMapfileToContentsMap = QMap());QTCREATOR_UTILS_EXPORT QFuturefindInFilesRegExp(    const QString &searchTerm,    FileIterator *files,    QTextDocument::FindFlags flags,    QMapfileToContentsMap = QMap());// ...} // namespace Utils

好了,现在照着新的思路,继续完成 HeaderFilter::findAll() 函数吧:

struct HeaderFilterData{    QFutureWatcherwatcher;    ProjectExplorer::ProjectExplorerPlugin* projectExplorer() {        // ...    }private:    ProjectExplorer::ProjectExplorerPlugin* m_projectPlugin;};HeaderFilter::HeaderFilter(){    d = new HeaderFilterData;    connect(&d->watcher, SIGNAL(resultReadyAt(int)), SLOT(displayResult(int)));}void HeaderFilter::findAll(const QString &text, Find::FindFlags findFlags){    // ...    // Remove duplicates    ();    // Search for text in files    // Variable to hold search results that will    // come as the search task progresses    QFuture searchResults;    // Begin searching    QString includeline = "#include <" + text + ">";    QListcodecs;    codecs << QTextCodec::codecForLocale();    searchResults = Utils::findInFiles(includeline,        new Utils::FileIterator(files, codecs),        Find::textDocumentFlagsForFindFlags(findFlags));    // Let the watcher monitor the search results    d->(searchResults);}void HeaderFilter::displayResult(int index){    // ...}

在 findAll() 的后续代码中,我们使用了 findInFiles() 函数,在后台开始了多个线程用于查找字符串。一旦搜索结果生成,根据我们的 connect, displayResult(int) 就会被调用。在这个槽函数中,我们需要将搜索结果展示出来。

find 插件提供了一个叫做 Find::SearchResultWindow 的对象,用于显示搜索结果:

我们当然希望搜索结果在这个 SearchResultWindow 中显示。为了达到这一目的,我们需要再次修改 HeaderFilter 的代码:

struct HeaderFilterData{    // ...    // Method to search and return the search window    Find::SearchResultWindow* searchResultWindow() {        if(m_searchResultWindow) {            return m_searchResultWindow;        }        ExtensionSystem::PluginManager* pm            = ExtensionSystem::PluginManager::instance();        m_searchResultWindow = pm->getObject();        return m_searchResultWindow;    }private:    // ...    Find::SearchResultWindow *m_searchResultWindow;};// ...void HeaderFilter::findAll(const QString &text, Find::FindFlags findFlags){    // ...    // Clear the current search results    d->searchResultWindow()->clearContents();    // Begin searching    QString includeline = "#include <" + text + ">";    // ...}void HeaderFilter::displayResult(int index){    Utils::FileSearchResultList results = d->().resultAt(index);    Q_FOREACH(Utils::FileSearchResult result, results) {        d->searchResultWindow()->addResult(,            ,            ,            ,            );    }}

注意,我们使用了 addResult() 这个函数来向 SearchResultWindow 中添加新的结果项。使用这个函数可以让我们的搜索内容高亮显示,正如前面我们的截屏一样。

【领 QT开发教程 学习资料, 点击下方链接莬费领取↓↓ ,先码住不迷路~】

点击这里:

关键词:

推荐DIY文章
主机存在磨损或划痕风险 PICO4便携包宣布召回
穿越湖海!特斯拉Cybertruck电动皮卡可以当“船”用
vivoXFold+折叠旗舰开售 配备蔡司全焦段旗舰四摄
飞凡R7正式上市 全系标配换电架构
中兴Axon30S开售 拥有黑色蓝色两款配色
荣耀MagicBookV14 2022正式开售 搭载TOF传感器
it