2010-10-03

QtCreator-XmlTree: Добавляем редактор исходников

Редактор xml'я в виде дерева это безусловно удобно, но иногда возникает необходимость подправить его в текстовом виде. Самый логичный вариант добавить в интерфейс две вкладки: дерево и текстовый редактор.


Вводная
В Qt Creator сейчас существует мощный текстовый редактор с поддержкой Generic Highlighting на основе katepart. Было бы странным писать свой текстовый редактор, когда рядом есть такой инструмент. Вопрос только как его использовать. Решением этого вопроса и займемся

Плагин TextEditor
В стандартную поставку Creator входит плагин TextEditor, который собственно и является редактором для любых текстовых файлов, он поддерживает подсветку синтаксиса на основе katepart, цветовую гамму, задаваемую в настройках и много других полезных фич.
Из него нас интересуют классы TextEditor::PlainTextEditor и TextEditor::PlainTextEditorEditable. От этих классов мы и будем наследовать наши. Но для начала подготовим проект к переходу на таббированый интерфейс и интеграцию TextEditor'а.

Подготовка
При использовании TextEditor отпадает необходимость в использовании реализаций интерфейсов IFile и IEditor, так как их предоставляет TextEditor.
Следующим шагом нам надо вынести основной виджет редактора отдельно, а дерево отдельно. Переименуем класс дерева в XmlEditorTreeView и создадим новый класс XmlEditorWidget, который будет отнаследован от QTabWidget. Вновь созданный класс и будет основным виджетом нашего обновленного редактора.
Также нам необходимо добавить TextEditor в зависимости в XmlTreePlugin.pluginspec.

Интеграция TextEditor
Для базовой интеграции нам необходимо отнаследовать класс TextEditor::PlainTextEditor, который будет заниматься визуальной частью редактирования (он в свою очередь в глубине иерархии отнаследован от обычного QPlainTextEdit). Также нам понадобится наследник TextEditor::PlainTextEditorEditable, который сам по себе является наследником IEditor (то есть именно этот класс у нас и будет возвращаться из XmlEditorFactory).

Наследник TextEditor::PlainTextEditor
Класс XmlSourceEditor практически не отличается от своего предка, за исключением одной особенности. Дело в том что по умолчанию Editable возвращает указатель на свой Editor при вызове метода widget() (который используется для получения виджета, который необходимо отобразить в качестве редактора). Нам нужно отобразить не Editor, а наш виджет с табами. Для этого нам надо переопределить виртуальный метод createEditableInterface() и вернуть из него экземпляр нашего Editable (о котором речь пойдет дальше), который будет знать о вышележащем виджете с табами.

Наследник TextEditor::PlainTextEditorEditable
Класс XmlSourceEditorEditable знает о том, что на самом деле редактором является не его Editor, а наш виджет с табами и все операции (открытие, создание нового документа и тд) проводит именно через наш виджет.

Доработка напильником
Для минимально рабочей версии нам осталось всего ничего: поменять метод createEditor() в нашей фабрике и научить виджет с табами правильно работать с двумя редакторами (текстовый и дерево).
В случае фабрики все просто и состоит из трех строк:
Core::IEditor *XmlEditorFactory::createEditor(QWidget *parent)
{
    XmlEditorWidget *editorWidget = new XmlEditorWidget(parent);
    TextEditor::TextEditorSettings::instance()->initializeEditor(
                                    editorWidget->sourceEditor());
    return editorWidget->sourceEditor()->editableInterface();
}
Теперь займемся таббированым виджетом.
Нам необходимо:
  1. Реализовать методы открытия файла и создания нового
  2. Обновлять контент вкладок, только когда это необходимо (когда он поменялся на другой вкладке)
  3. Правильно реагировать на переключение вкладок
  4. Правильно удалить наш виджет
А теперь по-порядку.
Методы открытия и создания:
bool XmlEditorWidget::createNew(const QString &contents)
{
    bool result = d->sourceEditor->createNew(contents);
    if (result)
        d->treeView->setContent(
                    d->sourceEditor->document()->toPlainText());
    d->contentModifiedFromLastTabSwitch = false;
    d->sourceEditor->baseTextDocument()->
            document()->setModified(false);
    return result;
}

bool XmlEditorWidget::open(const QString &fileName)
{
    bool result = d->sourceEditor->open(fileName);
    if (result)
        d->treeView->setContent(
                    d->sourceEditor->document()->toPlainText());
    d->contentModifiedFromLastTabSwitch = false;
    d->sourceEditor->baseTextDocument()->
            document()->setModified(false);
    return result;
}
Реакция на изменение контента вкладки (слоты подключены соответственно к сигналам изменения дерева и Editor'а):
void XmlEditorWidget::slotTreeContentModified()
{
    d->contentModifiedFromLastTabSwitch = true;
    d->sourceEditor->baseTextDocument()->
                        document()->setModified(true);
}

void XmlEditorWidget::slotSourceContentModified()
{
    d->contentModifiedFromLastTabSwitch = true;
}

Переключение вкладок (слот подключен к сигналу currentChanged()):
void XmlEditorWidget::slotCurrentChanged(int index)
{
    if (d->contentModifiedFromLastTabSwitch)
    {
        switch (index)
        {
        case SourceEditor:
            d->sourceEditor->setPlainText(d->treeView->content());
            d->sourceEditor->baseTextDocument()->
                                           document()->setModified(true);
            break;
        case TreeViewEditor:
            d->treeView->setContent(
                         d->sourceEditor->document()->toPlainText());
        default:
            break;
        }
    }
    d->contentModifiedFromLastTabSwitch = false;
}
В деструкторе нам необходимо (помимо обычного удаления всего, что мы создали) НЕ удалять Editor, так как его в последствии еще будет удалять Editable, для этого просто вынесем его из иерархии QObject'ов:
XmlEditorWidget::~XmlEditorWidget()
{
    disconnect(SIGNAL(currentChanged(int)));
    this->clear();
    d->sourceEditor->setParent(0);
    delete d;
}

Заключение
По сути на этом все, теперь у нас есть две вкладки (дерево и исходники), которые связаны между собой и предоставляют минимальный функционал по редактированию.

Комментариев нет:

Отправить комментарий