最近准备为taskBus软件无线电平台添加集成文档功能。毕竟模块变多了之后,简单一页纸的小文档对正确想起来如何使用模块功能意义很大。对于一款极简的轻量级SDR产品,我们希望taskBus的文档继续保持极简的风格,支持txt纯文本、简易Markdown和本地html。虽然以前在别的项目里,用QtWebKit显示HTML,后来用过QtWebEngine,但有问题一直没有解决。首先这两个基于浏览器内核的库都太大了,另外Qt WebEngine不支持mingw编译器。
早就听说Qt的textBrowser是一种轻量级的超文本浏览器,类似的控件是QTextEdit,还具备编辑功能。项目用QTextBrowser显示简单的html文件、图文提示、帮助,还支持markdown,又不会带来额外的依赖。本文介绍一下我尝试用QTextBrowser显示Markdown+Html文件的情况。
要在Qt程序里打开Markdown/Html文件,至少有三种方法。
这三种方法里,QTextBrowser是费效比最好的,但是对Markdown的支持比较弱,对转义符的处理也与JS前端常见行为有所区别。
一般的带模块扩展功能的软件,模块的文档有两种管理方式。其一是集约的,其二是分散的。集约的方式,所有模块的可执行统一位于可执行文件夹,文档位于文档文件夹。分散的,则是一个模块的可执行、文档等东西放在一起。
相比这两种方式,对于小作坊的绿色软件而言,分散管理便于给模块开发者更大的简便性。开发者只要把模块的东西丢在一起,就够了。要卸载模块,直接把文件夹删除,就卸载了。
为了最简化我们的设计思路,固定文档规则如下:
有了这样的规则,在框架程序内可方便的查找文档。假设模块的名字是 mod.exe,则文档的合法位置、查找顺序如下:
|
+-mod_root
|-0 mod.exe(模块可执行)
|-1 mod.exe.html
|-2 mod.html
|-4 mod.exe.md
|-5 mod.md
|-7 mod.exe.txt
|-8 mod.txt
+-mod.handbook
|-3 mod.html
|-6 mod.md
|-9 mod.txt
按照上述顺序,先找到哪个文件,就打开谁。
新建一个视图,用于盛放帮助文档。
这个视图存在一个setUrl方法,用于打开相应的磁盘文件。
HandbookView::HandbookView(QWidget *parent) :
QWidget(parent),
ui(new Ui::HandbookView)
{
ui->setupUi(this);
connect (ui->textBrowser_help, &QTextBrowser::backwardAvailable,ui->pushButton_backward,&QPushButton::setEnabled);
connect (ui->textBrowser_help, &QTextBrowser::forwardAvailable,ui->pushButton_forward,&QPushButton::setEnabled);
connect (ui->pushButton_forward,&QPushButton::pressed,ui->textBrowser_help, &QTextBrowser::forward);
connect (ui->pushButton_backward,&QPushButton::pressed,ui->textBrowser_help, &QTextBrowser::backward);
connect (ui->textBrowser_help, &QTextBrowser::sourceChanged,[&](QUrl u){
ui->lineEdit_url->setText(u.toDisplayString());
});
}
void HandbookView::setUrl(QString urlstr,QString FailedString)
{
if (urlstr.length())
{
QUrl u = QUrl::fromLocalFile(urlstr);
ui->textBrowser_help->setSource(u) ;
}
else
{
ui->textBrowser_help->setMarkdown(
tr("# Documents Does NOT Exist\r\n\r\n"
"You can write documents with markdown in any place below:\r\n\r\n")
+FailedString
);
}
}
void HandbookView::on_lineEdit_url_returnPressed()
{
QUrl u = QUrl(ui->lineEdit_url->text());
ui->textBrowser_help->setSource(u);
}
下图是文档不存在时的提示:
按照上述设计思路,直接根据exe文件名,对文档进行查找。
//通可执行文件名直接查找文档。
void taskBusPlatformFrm::load_doucment(QString func,QString exe)
{
//Open handbook
QFileInfo info(exe);
QString baseName = info.path()+"/"+
info.completeBaseName();
QString urlstr;
//用于加载失败时的提示。
QString FailedString;
FailedString += exe+".md/.html/.txt" + "\n\n";
FailedString += baseName+".md/.html/.txt" + "\n\n";
FailedString += baseName+".handbook/" + info.completeBaseName() + ".md/.html/.txt\n\n";
//逐一查找文件
if (QFileInfo::exists(exe+".html"))
urlstr = exe+".html";
else if (QFileInfo::exists(baseName+".html"))
urlstr = baseName+".html";
else if (QFileInfo::exists(baseName+".handbook/" + info.completeBaseName() + ".html"))
urlstr = baseName+".handbook/" + info.completeBaseName() + ".html";
else if (QFileInfo::exists(exe+".md"))
urlstr = exe+".md";
else if (QFileInfo::exists(baseName+".md"))
urlstr = baseName+".md";
else if (QFileInfo::exists(baseName+".handbook/" + info.completeBaseName() + ".md"))
urlstr = baseName+".handbook/" + info.completeBaseName() + ".md";
else if (QFileInfo::exists(exe+".txt"))
urlstr = exe+".txt";
else if (QFileInfo::exists(baseName+".txt"))
urlstr = baseName+".txt";
else if (QFileInfo::exists(baseName+".handbook/" + info.completeBaseName() + ".txt"))
urlstr = baseName+".handbook/" + info.completeBaseName() + ".txt";
//...
HandbookView * view = new HandbookView(this);
if (view)
{
view->setWindowTitle(title);
wnd = ui->mdiArea->addSubWindow(view);
view->show();
view->setUrl(urlstr,FailedString);
wnd->setWindowTitle(title);
}
//...
}
从代码内做好准备后,还要把源码文件夹里的文档及时拷贝到exe所在文件夹。这里我们使用POST_LINK步骤直接拷贝,而非install步骤。因为大部分受众是绿色软件安装者方式,不希望污染到其他路径。POST_LINK只是告诉用户,发布时应该携带这个文件/文件夹而已。用户开心时,自己手工拷贝也不是不可以。
#QMAKE:
DESTDIR = $$OUT_PWD/../../../bin/modules
#Documents Copy
QMAKE_POST_LINK += $${QMAKE_COPY_DIR} $$PWD/network_p2p.handbook $$DESTDIR/network_p2p.handbook
add_custom_command(TARGET network_p2p
POST_BUILD
COMMAND echo Copy Handbook to EXE path
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/network_p2p.handbook ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/network_p2p.handbook
)
在模块代码文件夹,文档存放在 target.handbook文件夹里。这个文件夹会在编译链接后,被拷贝到exe所在文件夹。
EXE文件夹看起来类似这样:
只要保证文档中的图片等依赖是本地的,且在相对路径下存在,则可以直接跳转到链接。同时,QTextBrowser会记住访问历史,并根据前进后退命令进行切换。
同时,QTextBrower支持在url里使用锚点,跳转到文档的某个位置:
QTextBrowser 对Markdown的支持不如html好。我的方法是:
相关代码见Git仓库。