Qt Creator 插件开发(17):向查找对话框添加过滤器(续)

ProjectExplorer

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 <extensionsystem/pluginmanager.h>
#include <projectexplorer/projectexplorer.h>

// Catch hold of the plugin-manager
ExtensionSystem::PluginManager* pm
    = ExtensionSystem::PluginManager::instance();

// Look for the ProjectExplorerPlugin object
ProjectExplorer::ProjectExplorerPlugin* projectExplorerPlugin
    = pm->getObject();

// Fetch a list of all open projects
QList<ProjectExplorer::Project*> projects
    = 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()));
}

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

// Make a list of files in each project
QStringList files;

Q_FOREACH(ProjectManager::Project* project, projects)
{
    files += project->files(Project::AllFiles);
}

条件设置 HeaderFilter

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

struct HeaderFilterData
{
    ProjectExplorer::ProjectExplorerPlugin* projectExplorer()
    {
        if(m_projectPlugin) {
            return m_projectPlugin;
        }
        ExtensionSystem::PluginManager* pm
            = ExtensionSystem::PluginManager::instance();
        m_projectPlugin = pm->getObject<ProjectExplorer::ProjectExplorerPlugin>();
	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
    QList<ProjectExplorer::Project*> projects
        = 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
    files.removeDuplicates();

    // 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/filesearch.h 中声明:

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 QList<FileSearchResult> FileSearchResultList;

QTCREATOR_UTILS_EXPORT QFuture<FileSearchResultList> findInFiles(
    const QString &searchTerm,
    FileIterator *files,
    QTextDocument::FindFlags flags,
    QMap<QString, QString> fileToContentsMap = QMap<QString, QString>());

QTCREATOR_UTILS_EXPORT QFuture<FileSearchResultList> findInFilesRegExp(
    const QString &searchTerm,
    FileIterator *files,
    QTextDocument::FindFlags flags,
    QMap<QString, QString> fileToContentsMap = QMap<QString, QString>());

// ...

} // namespace Utils

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

struct HeaderFilterData
{
    QFutureWatcher<Utils::FileSearchResultList> watcher;

    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
    files.removeDuplicates();

    // 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 + ">";
    QList<QTextCodec *> codecs;
    codecs << QTextCodec::codecForLocale();
    searchResults = Utils::findInFiles(includeline,
        new Utils::FileIterator(files, codecs),
        Find::textDocumentFlagsForFindFlags(findFlags));

    // Let the watcher monitor the search results
    d->watcher.setFuture(searchResults);
}

void HeaderFilter::displayResult(int index)
{
    // ...
}

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

Leave a Reply