Qt Creator 插件开发(10):添加新的编辑器(续二)

下面,我们来开始进行 Qt Creator 的 HTML 编辑器的开发。首先来看一下我们的开发成果:

用户通过“文件->打开”菜单来选择一个 HTML 文件:

Qt Creator 打开文件标准菜单

注意在打开文件对话框,我们已经可以找到 HTML 文件类型:

Qt Creator HTML 格式

当我们选择一个 HTML 文件时,Qt Creator 会使用我们的编辑器进行显示:

Qt Creator HTML 编辑器预览界面

注意,我们在编辑器底部添加了“Preview”和“Source”标签:

Qt Creator HTML 编辑器标签

点击“Source”标签,则可以显示该页面的 HTML 代码:

Qt Creator HTML 编辑器源代码视图

用户可以在 source 视图编辑 HTML 文件,然后在 preview 视图预览。最后,用户可以通过保存命令将修改存盘。

为了实现上面所述功能,我们需要创建如下类:

接口/父类 描述
HtmlEditorWidget QTabWidget 提供具有两个标签的 tab widget,一个是 preview,一个是 source
HtmlFile Core::IFile 实现IFile接口,定义显示在HtmlEditorWidget的文件
HtmlEditor Core::IEditor 实现IEditor接口,用于管理HtmlEditorWidget,同时将IFileHtmlEditorWidget关联在一起
HtmlEditorFactory Core::IEditorFactory 实现IEditorFactory接口,用于创建支持 text/html mime-type 的IEdtior实例
HtmlEditorPlugin Core::IPlugin 实现IPlugin接口,将上面所有的类同 Qt Creator 关联在一起

每一个 HTML 文件都会有一组HtmlEditorWidgetHtmlFileHtmlEditor的实例,用于实现加载文件、编辑和保存的功能。在我们的实现中,我们提供一种机制,将处理同一文件所需的各个类关联起来。

HtmlEditorWidget

默认情况下,Qt Creator 使用一个纯文本编辑器来显示 HTML 文件的内容。我们则要提供一个带有 tab 的编辑器,其中一个 tab 用于预览 HTML 文件,另一个则显示 HTML 源代码。我们的编辑器的类叫做HtmlEditorWidget,它的声明如下:

#ifndef HTMLEDITORWIDGET_H
#define HTMLEDITORWIDGET_H

#include <QTabWidget>

struct HtmlEditorWidgetData;

class QWebView;
class QPlainTextEdit;

class HtmlEditorWidget : public QTabWidget
{
    Q_OBJECT

public:
    HtmlEditorWidget(QWidget* parent = 0);
    ~HtmlEditorWidget();
    void setContent(const QByteArray& ba, const QString& path=QString());
    QByteArray content() const;
    QString title() const;

protected slots:
    void slotCurrentTabChanged(int tab);
    void slotContentModified();

signals:
    void contentModified();
    void titleChanged(const QString&);

private:
    HtmlEditorWidgetData* d;
};

struct HtmlEditorWidgetData
{
    QWebView *webView;
    QPlainTextEdit *textEdit;
    bool modified;
    QString path;
};

#endif // HTMLEDITORWIDGET_H

构造函数的作用是使用两个类QWebViewQPlainTextEdit来创建不同的显示,然后将其添加到一个 tab widget 中:

HtmlEditorWidget::HtmlEditorWidget(QWidget* parent)
                 :QTabWidget(parent)
{
    d = new HtmlEditorWidgetData;
    d->webView = new QWebView;
    d->textEdit = new QPlainTextEdit;
    addTab(d->webView, tr("Preview"));
    addTab(d->textEdit, tr("Source"));
    setTabPosition(QTabWidget::South);
    setTabShape(QTabWidget::Triangular);
    d->textEdit->setFont(QFont("Courier", 12));
    connect(this, SIGNAL(currentChanged(int)),
                  SLOT(slotCurrentTabChanged(int)));
    connect(d->textEdit, SIGNAL(textChanged()),
                            SLOT(slotContentModified()));
    connect(d->webView, SIGNAL(titleChanged(QString)),
                           SIGNAL(titleChanged(QString)));
    d->modified = false;
}

析构函数则什么都不做,除了将私有数据指针 d 释放掉:

HtmlEditorWidget::~HtmlEditorWidget()
{
    delete d;
}

setContent()函数用于根据 HTML 文件设置 webView 和 textEdit 的内容:

void HtmlEditorWidget::setContent(const QByteArray& ba, const QString& path)
{
    if(path.isEmpty())
        d->webView->setHtml(ba);
    else
        d->webView->setHtml(QString(ba), QUrl("file:///" + path));
    d->textEdit->setPlainText(ba);
    d->modified = false;
    d->path = path;
}

content()函数用于返回textEdit的内容:

QByteArray HtmlEditorWidget::content() const
{
    QString htmlText = d->textEdit->toPlainText();
    return htmlText.toAscii();
}

title()函数返回webView的标题。这个标题将被显示在打开文件下拉框中:

QString HtmlEditorWidget::title() const
{
    return d->webView->title();
}

我们在构造函数中使用connect()函数关联了信号槽。第一个是使用户在点击切换 tab 的时候,预览窗口和源代码窗口能够保持同步:

connect(this, SIGNAL(currentChanged(int)),
              SLOT(slotCurrentTabChanged(int)));

// ....

void HtmlEditorWidget::slotCurrentTabChanged(int tab)
{
    if(tab == 0 && d->modified)
        setContent(content(), d->path);
}

第二个连接则用于保证用户编辑 HTML 源代码的时候,能够发出setContentModified()信号。这个信号会在后面HtmlFile类中使用到。看名字可以知道,这个信号用于通知文件内容的改变。

connect(d->textEdit, SIGNAL(textChanged()),
                     SLOT(slotContentModified()));

// ....

void HtmlEditorWidget::slotContentModified()
{
    d->modified = true;
    emit contentModified();
}

最后一个连接仅仅将 webView 的titleChanged()信号转发出去。我们会在后面看到这个信号的作用。

5 Comments

  1. kangaxx 2013年10月8日
    • 豆子 2013年10月8日
      • kangaxx 2013年10月9日
        • 豆子 2013年10月9日
          • kangaxx 2013年10月10日

Leave a Reply