2010-11-07

QtCreator-XmlTree: Применение цветовых схем к редактору

QtCreator поддерживает кастомизацию цвета и шрифта текста в редакторах (в самом QtCreator это называется Color scheme). Это удобно, клево и все такое. Рассмотрим как добавить эту функциональность к нашему редактору XML.

Получение информации
Вся информация о текущих настройках описана в классе TextEditor::FontSettings, экземпляр которого передается в виртуальный слот setFontSettings() класса TextEditor::BaseTextEditor. То есть, мы можем спокойно его переопределить в нашем наследнике и будет нам счастье.
void XmlSourceEditor::setFontSettings(const TextEditor::FontSettings &fs)
{
    TextEditor::PlainTextEditor::setFontSettings(fs);
    editorWidget->treeView()->setFontSettings(fs);
}

Сначала применяем настройки к текстовому редактору, а потом передаем их дереву.

В дереве нам необходимо взять нужные настройки, передать их делегатам (о них ниже) и применить новую цветовую схему самому виджету дерева.
void XmlEditorTreeView::setFontSettings(const TextEditor::FontSettings &fs)
{
    const QTextCharFormat genericTextFormat =
            fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_TEXT));
    QTextCharFormat selectedFormat =
            fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_SELECTION));
    XmlTreeValueDelegate::valueFormat =
            fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_STRING));
    XmlTreeValueDelegate::commentFormat =
            fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_COMMENT));
    XmlTreeNameDelegate::attributeFormat =
            fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_FIELD));
    XmlTreeNameDelegate::elementFormat =
            fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_TYPE));
    XmlTreeNameDelegate::nodeTypeFormat =
            fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_KEYWORD));

    selectedFormat.clearBackground();
    XmlTreeNameDelegate::genericFormat = genericTextFormat;
    XmlTreeNameDelegate::selectedFormat = selectedFormat;
    XmlTreeValueDelegate::selectedFormat = selectedFormat;

    QPalette p = palette();
    p.setColor(QPalette::Text, genericTextFormat.foreground().color());
    p.setColor(QPalette::WindowText, genericTextFormat.foreground().color());
    p.setColor(QPalette::Base, genericTextFormat.background().color());
    p.setColor(QPalette::HighlightedText, selectedFormat.foreground().color());
    setPalette(p);
}

Самый простой способ получить настройки конкретного элемента это преобразовать их в QTextCharFormat. Их мы позже будем использовать в делегатах и из них же выстроим новую палитру нашего дерева.

Автоматическое применение схем
В случае изменения текущей схемы TextEditor автоматически ее применит. Но только в том случае, если он visible. То есть, если у нас открыто дерево и мы поменяли схему, то она применится только когда мы откроем текстовый редактор. Обойдем это ограничение.
Core::IEditor *XmlEditorFactory::createEditor(QWidget *parent)
{
    XmlEditorWidget *editorWidget = new XmlEditorWidget(parent);
    TextEditor::TextEditorSettings::instance()->
            initializeEditor(editorWidget->sourceEditor());

    disconnect(TextEditor::TextEditorSettings::instance(),
               SIGNAL(fontSettingsChanged(TextEditor::FontSettings)),
               editorWidget->sourceEditor(), 0);
    connect(TextEditor::TextEditorSettings::instance(),
            SIGNAL(fontSettingsChanged(TextEditor::FontSettings)),
            editorWidget->sourceEditor(),
            SLOT(setFontSettings(TextEditor::FontSettings)));

    return editorWidget->sourceEditor()->editableInterface();
}

Подключение слота setFontSettingsIfVisible() (который собственно и проверяет видимый редактор или нет и вызывает setFontSettings()) происходит при инициализации редактора в фабрике. Отключаем нужный нам сигнал и подключаем его к нашему слоту напрямую.

Применение схемы к элементам дерева
Чтобы применить схему к нашему дереву нам нужно реализовать делегаты для колонок с названием и со значением. Рассмотрим делегат для названия (делегат для значения реализуется аналогично):
void XmlTreeNameDelegate::paint(QPainter *painter, 
                                const QStyleOptionViewItem &option, 
                                const QModelIndex &index) const
{
    QString str = index.data(Qt::DisplayRole).toString();
    QDomNode::NodeType nodeType = static_cast<QDomNode::NodeType>(
                index.data(XmlTreeModel::NodeTypeRole).toInt());
    QTextDocument td;

    QStyleOptionViewItemV4 opt = option;

    QRectF optRect = opt.rect;
    QRectF textRect = optRect;
    td.setPageSize(QSize(textRect.width(), textRect.height()));
    td.setHtml(str);
    QTextCursor cursor(&td);
    cursor.movePosition(QTextCursor::Start);
    cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
    if (opt.state & QStyle::State_Selected)
    {
        cursor.mergeCharFormat(selectedFormat);
    }
    else
    {
        switch (nodeType)
        {
        case QDomNode::AttributeNode:
            cursor.mergeCharFormat(attributeFormat);
            break;
        case QDomNode::ElementNode:
            cursor.mergeCharFormat(elementFormat);
            break;
        case QDomNode::CommentNode:
        case QDomNode::TextNode:
        case QDomNode::CDATASectionNode:
            cursor.mergeCharFormat(nodeTypeFormat);
            break;
        default:
            cursor.mergeCharFormat(genericFormat);
            break;
        }
    }

    QRectF lr = QRectF(textRect.x(), textRect.y(), 
                       textRect.width(), textRect.height());
    painter->save();
    opt.widget->style()->drawControl(QStyle::CE_ItemViewItem, 
                                     &opt, painter, opt.widget);
    painter->translate(lr.topLeft());
    painter->setClipRect(lr.translated(-lr.x(), -lr.y()));
    td.drawContents(painter, QRectF());
    painter->restore();
}
Создаем QTextDocument, выделяем QCursor'ом весь текст в нем и применяем к курсору нужный нам QTextCharFormat. Вуаля! Теперь наше дерево выглядит гораздо приятнее.

1 комментарий: