Pull to refresh

QtCreator TODO Plugin. Продолжение

Reading time 5 min
Views 5.4K
TODO Plugin

Некоторое время назад я писал о разработанном мною плагине отображающем список все TODO, FIXME и т. д. комментариев в текущем открытом документе. Вчера я выпустил новую версию этого плагина, в которой получилось довольно много изменений, вот ключевые из них:
  • Появились настройки
  • Теперь можно добавлять свои ключевые слова для поиска и задавать цвет и иконку их вывода.
  • Можно выбирать куда выводить информацию: в стандартное окно «Build issues» или в отдельное «TODO Output».
  • Можно выбирать: попрежнему выводить информацию только о текущем открытом файле или собирать информацию со всего текущего проекта.
  • Комментарии на языках отличных от английского теперь отображаются корректно.

Во время разработки удалось накопать несколько интересных вещей, которые могут пригодится другим разработчикам плагинов, чем и поделюсь под катом.

Настройки


settings

Первое, что мне потребовалось, это узнать как же создать собственную вкладку в настройках QtCreator, это казалось довольно просто, потребовалось лишь добавить новый класс наследующийся от интерфейса Core::IOptionsPage, его объявление выглядит так:
class SettingsPage : public Core::IOptionsPage
{
    Q_OBJECT
public:

    SettingsPage(KeywordsList keywords = KeywordsList(), int projectOptions = 0, int paneOptions = 0, QObject *parent = 0);
    ~SettingsPage();

    QString id() const; // Уникальный идентификатор вкладки
    QString trName() const; // Выводимое имя вкладки (для старых версий креатора)
    QString category() const; // Имя категории в которую следует добавлять вкладку
    QString trCategory() const; // Выводимое имя категории (для старых версий креатора)
    QString displayName() const; // Выводимое имя вкладки
    QIcon categoryIcon() const; // Иконка категории
    QString displayCategory() const;   // Выводимое имя категории
    QWidget *createPage(QWidget *parent); // Метод возвращающий созданный виджет вкладки
    void apply(); // Метод выполняющийся при нажатии на кнопку "Apply" или "OK"
    void finish(); // Метод выполняющийся при завершении работы с настроками - "OK" или "Cancel"

public slots:
    void settingsChanged();

private:
    SettingsDialog *dialog; // Виджет вкладки
    bool settingsStatus; // Менялись настройки?
    int projectOptions; // Настройка сканирования 
    int paneOptions; // Настройка вывода
    KeywordsList keywords; // Список ключевый слов
};

Виджет вкладки я создал в дизайнере в не нет ничего сложного стандартный QlistWidget, несколько QRadioButton и кучка кнопок (см. изображение).
Ключевым здесь является метод apply(), именно в нем происходит сохранение настроек.

Он выглядит так:
void SettingsPage::apply()
{
    if (settingsStatus)
    {
        QSettings *settings = Core::ICore::instance()->settings(); // Получаем указатель на глобальные настройки
        settings->beginGroup("TODOPlugin"); // открываем свою группу
        projectOptions = dialog->currentFileRadioButtonEnabled() ? 0 : 1; // Читаем настройки из диалога
        paneOptions = dialog->todoOutputRadioButtonEnabled() ? 0 : 1;
        keywords = dialog->keywordsList();
        settings->setValue("project_options", projectOptions); // Сохраняем настройки
        settings->setValue("pane_options", paneOptions);
        settings->setValue("keywords", qVariantFromValue(keywords));

        settings->endGroup();
        settings->sync(); // Выполняем синхронизацию (сохраняем настройки в файл)

        QMessageBox::information(dialog, tr("Information"), tr("The TODO plugin settings change will take effect after a restart of Qt Creator.")); // К сожалению пока настройки ожно применить только после перезапуска программы
        settingsStatus = false;
    }
}

В общем ничего сложного, напомню только, что для того, чтобы иметь возможность передавать в QVariant собственные типы данных, для них надо определить операторы: << и >>, а также зарегистрировать их в MOC.

Окно «Build issues»


taskwindow

Строго говоря оно называется TaskWindow и может служит стандартным выводом для любых сообщений. Добавить возможность вывода сообщений именно в это окно я решил по настоятельному совету людей из сообщества Qt Developer Network, о котором я ранее также писал. Получить доступ к этому окну можно следующим образом, необходимо подключить в проект следующий заголовок:
#include <projectexplorer/taskwindow.h>

И получить указатель на окно:
taskWindow = pluginManager->getObject<ProjectExplorer::TaskWindow>();

Все. теперь можно с ним работать, для этого служат методы:
void addCategory(const QString &categoryId, const QString &displayName);
void addTask(const Task &task);
void removeTask(const Task &task);
void clearTasks(const QString &categoryId = QString());


Категории нужны для того, чтобы идентифицировать именно наши сообщения.
Структура Task проста и выглядит так:
struct PROJECTEXPLORER_EXPORT Task {
    enum TaskType {
        Unknown,
        Error,
        Warning
    };

    Task() : type(Unknown), line(-1)
    { }
    Task(TaskType type_, const QString &description_,
         const QString &file_, int line_, const QString &category_) :
        type(type_), description(description_), file(file_), line(line_), category(category_)
    { }
    Task(const Task &source) :
        type(source.type), description(source.description), file(source.file),
        line(source.line), category(source.category), formats(source.formats)
    { }
    ~Task()
    { }

    TaskType type;
    QString description;
    QString file;
    int line;
    QString category;
    QList<QTextLayout::FormatRange> formats;
};

Важно: В спеке нужно указать зависимость плагина от ProjectExplorer

Progressbar


progressbar Для реализации прогрессбара потребовалось немного магии и нервов. Для того чтобы сканирование не тормозило остальную загрузку проекта, я поместил его в отдельный поток:
    QFuture<void> result = QtConcurrent::run(&TodoPlugin::readCurrentProject, this);

Как оказалось, вот этот всеми любимый серо-зелененький прогресс специально заточен под работу с отдельным потоком.

Для того чтобы с ним работать, необходимо добавить следующие заголовки:
#include <coreplugin/progressmanager/progressmanager.h>
#include <qtconcurrent/runextensions.h>

Добавление нового прогрессбара происходит так:
    Core::ICore::instance()->progressManager()->addTask(result, tr("Todoscan"), "Todo.Plugin.Scanning");

Работа с ним производиться уже из потока сканирования, вот этот метод:
void TodoPlugin::readCurrentProject(QFutureInterface<void> &future, TodoPlugin *instance)
{
    QStringList filesList = instance->currentProject->files(ProjectExplorer::Project::ExcludeGeneratedFiles); // Получаем список файлов
    future.setProgressRange(0, filesList.count()-1); // Устанавливаем крайние значения
    for (int i = 0; i < filesList.count(); ++i)
    {
        instance->readFile(filesList.at(i)); // Читаем текущий файл
        future.setProgressValue(i); // Установка нового значения
    }

    // SKIPPED

}

В чем же магия спросите вы? Обратите внимание, На заголовок функции и на ее вызов, в заголовке объявлено два параметра, а в вызове передается только второй, первый же опущен. Первый параметр подставляется туда кодом из второго требуемого хедера (qtconcurrent/runextensions.h). Вот и вся магия.

Заключение


На этом пока все, скачать плагин вы можете по прежнему отсюда, распространяется под BSD лицензией — экпериментируйте наздоровье. Добавлю что, под Windows не тестировалось, в связи с отсутсвием онной, буду благодарен за тестирование и багрепорты.
Tags:
Hubs:
+40
Comments 14
Comments Comments 14

Articles