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

Первое, что мне потребовалось, это узнать как же создать собственную вкладку в настройках 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 и может служит стандартным выводом для любых сообщений. Добавить возможность вывода сообщений именно в это окно я решил по настоятельному совету людей из сообщества 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

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 не тестировалось, в связи с отсутсвием онной, буду благодарен за тестирование и багрепорты.