Solved no pictures in helpfile
-
Hi,
I'm currently trying to familiarize myself with the Qt help system. As I understand it, the help system is built on top of the qdoc system.
So I have created some files for qdoc. The generation of the html files works and the files also look as expected.
However, when I generate the help file and view it with QtAssistant, there is not a single image. I searched for additional information, but my uncle Ducky hardly finds any entries about qt-helpfiles.
I vaguely remember reading somewhere that the images should be in the same directory as the html files, so I tried copying the images. Didn't make any difference, unfortunately.
directory layout from QtCreator:
My qdoc config file:
project = FalconView description = controlcenter for linuxcnc outputdir = html headerdirs = src sourcedirs = src imagedirs = src/images exampledirs = . version = 0.1 headers.fileextensions = "*.h *.hpp" sources.fileextensions = "*.cpp *.qdoc" qhp.FalconView.namespace = de.schwarzrot.falconview.0.1 qhp.FalconView.virtualFolder = FalconView qhp.FalconView.indexTitle = User Guide
the help project file:
<?xml version="1.0" encoding="utf-8" ?> <QtHelpProject version="1.0"> <namespace>de.schwarzrot.falconview.0.1</namespace> <virtualFolder>FalconView</virtualFolder> <filterSection> <filterAttribute>FalconView</filterAttribute> <filterAttribute>0.1</filterAttribute> <toc> <section title="User Guide" ref="index.html"> <section title="start the engine" ref="startup.html"/> <section title="reference" ref="reference.html"/> <section title="use case" ref="usage.html"/> </section> </toc> <keywords> <keyword name="fileManager" id="fileManager" ref="fileManager.html"/> </keywords> <files> <file>*.html</file> </files> </filterSection> </QtHelpProject>
... and here's how I build the help-file:
#!/bin/bash project=FalconView qdoc ${project}.qdocconf cp ${project}.qhp html cd html cp -a images/* . qhelpgenerator ${project}.qhp mv ${project}.qch ..
As mentioned - I tried with and without image copying.
May be the biggest question for my understanding:
Are the images put into the compressed helpfile, or do I have to provied them to the helpviewer, like any HTML-browser does? -
qdoc is an internal tool of Qt, do not use it for other projects. Use Doxygen instead.
-
@sierdzio said in no pictures in helpfile:
qdoc is an internal tool of Qt
That's new to me, it is not mentioned in the documentation or did I miss it through all the years?
-
Hi,
thank you for your attention!
@sierdzio said in no pictures in helpfile:
qdoc is an internal tool of Qt, do not use it for other projects.
Hm, my expectations are not very high, so qdoc works fine for me and I have no issues.
Opposed to the helpengine.
I tried to follow the Qt example "contextsensitivehelp", but it does not work at all for me. Documentation says, that I should test helpfile-creation by loading it in QtAssistant.Well, QtAssistant shows the pages, but without pictures. But when I use helpengine like Qt examples, I have no documents, no keywords, nothing.
Even if I use the same url, that QtAssistant claims to use, I get no document in my app. But setup returns ok.So I don't know what's wrong.
I'll gonna try to build my own help engine. -
@django-Reinhard said in no pictures in helpfile:
I'll gonna try to build my own help engine.
Done :)
My uncle duck guided me to stackoverflow (last post), and with that info, it was straight forward. Qt already has all needed stuff - only had to assemble it new.
According to my tests, I can answer my initial question:
@django-Reinhard said in no pictures in helpfile:
Are the images put into the compressed helpfile, or do I have to provied them to the helpviewer, like any HTML-browser does?
Aperently not.
Ok, what did I do?
I have the qdoc-files in directory docs, which has a subdirectory images.
qdoc then creates a directory 'html' with a subdirectory 'images' and copies all images to html/images.So I created a zip-file from the qdoc output - means all generated html-files and the image subdirectory.
Base for help output is
QTextBrowser
. It already has complete functionality. Subclassing is anyway required to be able to overrideQTextBrowser::loadResource
.So the minimal project with a working helpEngine looks like:
MainWindow declaration:
class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr); virtual ~MainWindow(); void createConnections(); void loadPage(const QString& pgName); void loadPage(const QUrl& link); private: Ui::MainWindow* ui; HelpEngine* he; QTextBrowser* tb; };
... and MainWindow definition:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) , he(new HelpEngine(QApplication::applicationDirPath() + "/../share/falconview/help/FalconView.qzh" , this)) , tb(new HTMLBrowser(*he, this)) { ui->setupUi(this); tb->setMinimumWidth(830); setCentralWidget(tb); createConnections(); } MainWindow::~MainWindow() { } void MainWindow::createConnections() { connect(ui->actionExit, &QAction::triggered, this, &QWidget::close); connect(ui->actionStartup, &QAction::triggered, this, [=](){ loadPage("startup"); }); connect(ui->actionReference, &QAction::triggered, this, [=](){ loadPage("reference"); }); connect(ui->actionUsage, &QAction::triggered, this, [=](){ loadPage("usage"); }); } void MainWindow::loadPage(const QString &pgName) { qDebug() << "MW::loadPage(" << pgName << ")"; tb->setSource(pgName + ".html"); } void MainWindow::loadPage(const QUrl& link) { qDebug() << "MW::loadPage(" << link << ")"; tb->setSource(link); }
I renamed the zip-archive to *.qzh - shortcut for "Qt zipped helpfile" ;)
For testing purpuse I added a menubar to mainwindow, and I used the menuentries to select pages from help-archive.
HTMLBrowser is subclass of
QTextBrowser
:class HTMLBrowser : public QTextBrowser { Q_OBJECT public: explicit HTMLBrowser(HelpEngine& engine, QWidget* parent = nullptr); virtual ~HTMLBrowser(); QVariant loadResource(int type, const QUrl &name); private: HelpEngine& engine; };
Nothing special in implementation:
HTMLBrowser::HTMLBrowser(HelpEngine& engine, QWidget* parent) : QTextBrowser(parent) , engine(engine) { } HTMLBrowser::~HTMLBrowser() { } QVariant HTMLBrowser::loadResource(int type, const QUrl& link) { qDebug() << "loadResource(" << type << "url:" << link; return engine.readFile(link.path()); }
... and finally the "new" HelpEngine.
First declaration:class HelpEngine : public QObject { Q_OBJECT public: explicit HelpEngine(const QString& helpFile, QObject *parent = nullptr); QVariant readFile(const QString& file); void tellContent(); protected: void buildDir(const QVector<QZipReader::FileInfo> entries); private: QZipReader* reader; QMap<QString, int> helpDir; };
... and here the implementation:
HelpEngine::HelpEngine(const QString& helpFile, QObject *parent) : QObject(parent) , reader(new QZipReader(helpFile)) { buildDir(reader->fileInfoList()); } void HelpEngine::buildDir(const QVector<QZipReader::FileInfo> entries) { int mx = entries.count(); for (int i=0; i < mx; ++i) { if (!entries[i].isFile) continue; helpDir.insert(entries[i].filePath, i); } } QVariant HelpEngine::readFile(const QString& file) { if (!helpDir.contains(file)) return QVariant(); QByteArray ba = reader->fileData(file); return QVariant(ba); } void HelpEngine::tellContent() { for (const QZipReader::FileInfo& e : reader->fileInfoList()) { qDebug() << "Helpfile-entry:" << e.filePath << (e.isDir ? "Dir" : "") << (e.isFile ? "File" : "") << (e.isSymLink ? "SymLink" : ""); } }
works like charming :)
... and in my humble opinion its fast enuf to use it without sqlite.
Well, I have to find a solution for keywords and index, but I'm confident, that is feasible.Cheers
-
@artwaw said in no pictures in helpfile:
@sierdzio said in no pictures in helpfile:
qdoc is an internal tool of Qt
That's new to me, it is not mentioned in the documentation or did I miss it through all the years?
It's not mentioned in the docs, but it has been said repeatedly on the mailing list. I have no idea why the docs have never been updated with this info.
-
@sierdzio Thank you!
-
Hi,
help from compressed archive works no matter, whether the *.html-files where built by qdoc, doxygen or are handwritten.
If firefox can display the page as local file, then it should be ready for help-browser as well.New changes for index and keyword support: I added *.qhp to archive. That file already contains all necessary informations. So I did it like QHelpEngine: create Standard-widgets, which can be included in some page layout.
I created a help-"dialog" a dockable, which can be used floating or integrated into mainwindow.
HelpDialog::HelpDialog(QWidget* parent) : QDockWidget(tr("Help"), parent) , he(new HelpEngine(Core().helpFilename(), this)) , tb(new HTMLBrowser(*he)) , cw(static_cast<HelpContentWidget*>(he->contentWidget())) , kw(static_cast<HelpKeywordWidget*>(he->keywordWidget())) { setObjectName("HelpDialog"); tb->setMinimumWidth(830); setMinimumWidth(1100); QSplitter* sh = new QSplitter(Qt::Horizontal, this); QTabWidget* tw = new QTabWidget(sh); sh->addWidget(tw); sh->addWidget(tb); tw->addTab(cw, tr("Content")); tw->addTab(kw, tr("Keywords")); setWidget(sh); connect(cw, &QTreeWidget::currentItemChanged, this, &HelpDialog::contentItemChanged); connect(kw, &QListWidget::currentItemChanged, this, &HelpDialog::keywordItemChanged); } void HelpDialog::contentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *) { const QString& page = current->data(1, Qt::DisplayRole).toString(); tb->setSource(page); } void HelpDialog::keywordItemChanged(QListWidgetItem *current, QListWidgetItem *) { const QString& page = current->toolTip(); tb->setSource(page); }
Here the sources. First the content-widget, a subclass of QTreeWidget:
class HelpContentWidget : public QTreeWidget { Q_OBJECT public: explicit HelpContentWidget(QWidget* parent = nullptr); virtual ~HelpContentWidget(); void setFolderIcon(const QIcon& ico); void setHelpIcon(const QIcon& ico); void parse(const QByteArray& ba); protected: QTreeWidgetItem* createItem(const QDomElement& e, QTreeWidgetItem* parent); QTreeWidgetItem* processElement(const QDomElement& e, QTreeWidgetItem* parent); void processChildren(const QDomElement& e, QTreeWidgetItem* parentItem = nullptr); void processAttributes(const QDomElement& e, QTreeWidgetItem* item); private: int level; QIcon folderIcon; QIcon helpIcon; };
... and its implementation:
HelpContentWidget::HelpContentWidget(QWidget* parent) : QTreeWidget(parent) { header()->setStretchLastSection(true); setHeaderLabel(tr("Title")); } HelpContentWidget::~HelpContentWidget() { } void HelpContentWidget::parse(const QByteArray& ba) { QDomDocument doc; doc.setContent(ba); QDomNodeList links = doc.elementsByTagName("toc"); level = 0; clear(); for (int i=0; i < links.count(); ++i) { QDomNode link = links.item(i); qDebug() << "\n"; qDebug() << "check entry #" << i; if (link.isElement()) { QDomElement e = link.toElement(); processChildren(e); } } } void HelpContentWidget::processAttributes(const QDomElement& e, QTreeWidgetItem* item) { int mx = e.attributes().count(); qDebug() << "processAttributes ..."; if (mx > 0) { qDebug() << "element has" << mx << "attributes"; for (int i=0; i < mx; ++i) { const QDomNode& n = e.attributes().item(i); qDebug() << "\tattribute:" << n.nodeName() << " => " << n.nodeValue(); if (n.nodeName() == "title") item->setText(0, n.nodeValue()); else if (n.nodeName() == "ref") item->setText(1, n.nodeValue()); } } else qDebug() << "element has NO attributes"; } void HelpContentWidget::processChildren(const QDomElement& e, QTreeWidgetItem* parent) { qDebug() << "processChildren ... (level:" << level++ << ")"; for (QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { if (n.isElement()) { qDebug() << "child is Element"; processElement(n.toElement(), parent); } } --level; } QTreeWidgetItem* HelpContentWidget::createItem(const QDomElement &e, QTreeWidgetItem *parent) { QTreeWidgetItem* item; if (parent) item = new QTreeWidgetItem(parent); else item = new QTreeWidgetItem(this); item->setIcon(0, folderIcon); processAttributes(e, item); return item; } QTreeWidgetItem* HelpContentWidget::processElement(const QDomElement& e, QTreeWidgetItem* parent) { qDebug() << "processElement - tag:" << e.tagName() << "text:" << e.text(); QTreeWidgetItem* item = nullptr; if (e.tagName() == "section") { item = createItem(e, parent); processChildren(e, item); } return item; } void HelpContentWidget::setFolderIcon(const QIcon &ico) { folderIcon = ico; } void HelpContentWidget::setHelpIcon(const QIcon &ico) { helpIcon = ico; }
... followed by the keywordWidget:
class HelpKeywordWidget : public QListWidget { Q_OBJECT public: explicit HelpKeywordWidget(QWidget* parent = nullptr); virtual ~HelpKeywordWidget(); void parse(const QByteArray& ba); void setIcon(const QIcon& icon); protected: void processChildren(const QDomElement& e); private: QIcon icon; };
with this implementation:
HelpKeywordWidget::HelpKeywordWidget(QWidget* parent) : QListWidget(parent) { } HelpKeywordWidget::~HelpKeywordWidget() { } void HelpKeywordWidget::parse(const QByteArray &ba) { QDomDocument doc; doc.setContent(ba); QDomNodeList links = doc.elementsByTagName("keywords"); clear(); for (int i=0; i < links.count(); ++i) { QDomNode link = links.item(i); qDebug() << "\n"; qDebug() << "check entry #" << i; if (link.isElement()) { QDomElement e = link.toElement(); processChildren(e); } } } void HelpKeywordWidget::processChildren(const QDomElement& e) { qDebug() << "processChildren ... "; for (QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { if (n.isElement()) { qDebug() << "child is Element"; QDomElement elem = n.toElement(); QListWidgetItem* item = new QListWidgetItem(); int mx = elem.attributes().count(); item->setIcon(icon); for (int i=0; i < mx; ++i) { const QDomNode& n = elem.attributes().item(i); if (n.nodeName() == "name") item->setText(n.nodeValue()); else if (n.nodeName() == "ref") item->setToolTip(n.nodeValue()); } addItem(item); } else if (n.isEntity()) qDebug() << "child is Entity"; else if (n.isAttr()) qDebug() << "child is Attribute"; else if (n.isText()) qDebug() << "child is Text"; } } void HelpKeywordWidget::setIcon(const QIcon &icon) { this->icon = icon; }
Creation of helpfile is done with a little bash-script:
#!/bin/bash project=FalconView qdoc ${project}.qdocconf cp ${project}.qhp html cd html zip -u9 ../FalconView.qzh *.html images/* FalconView.qhp
I think that this solution can be used to create a helper solution for small applications with the least possible effort.