File indexing completed on 2022-09-27 12:24:12

0001 /*
0002     SPDX-FileCopyrightText: 2005, 2006 Carsten Niehaus <cniehaus@kde.org>
0003     SPDX-FileCopyrightText: 2005-2007 Pino Toscano <pino@kde.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "kdeeduglossary.h"
0009 
0010 #include <KActionCollection>
0011 #include <KConfigGroup>
0012 #include <KLocalizedString>
0013 #include <KTreeWidgetSearchLine>
0014 
0015 #include "kalzium_debug.h"
0016 #include <QDialogButtonBox>
0017 #include <QDomDocument>
0018 #include <QEvent>
0019 #include <QFile>
0020 #include <QHeaderView>
0021 #include <QKeyEvent>
0022 #include <QLabel>
0023 #include <QLayout>
0024 #include <QList>
0025 #include <QPushButton>
0026 #include <QRegularExpression>
0027 #include <QSplitter>
0028 #include <QStringList>
0029 #include <QTextBrowser>
0030 #include <QTreeWidget>
0031 #include <QVBoxLayout>
0032 
0033 static const int FirstLetterRole = 0x00b00a00;
0034 
0035 static const int GlossaryTreeItemType = QTreeWidgetItem::UserType + 1;
0036 
0037 class GlossaryTreeItem : public QTreeWidgetItem
0038 {
0039 public:
0040     GlossaryTreeItem(Glossary *g, GlossaryItem *gi)
0041         : QTreeWidgetItem(GlossaryTreeItemType)
0042         , m_g(g)
0043         , m_gi(gi)
0044     {
0045         setText(0, m_gi->name());
0046     }
0047 
0048     Glossary *glossary() const
0049     {
0050         return m_g;
0051     }
0052 
0053     GlossaryItem *glossaryItem() const
0054     {
0055         return m_gi;
0056     }
0057 
0058 private:
0059     Glossary *m_g;
0060     GlossaryItem *m_gi;
0061 };
0062 
0063 struct GlossaryInfo {
0064     GlossaryInfo(Glossary *g)
0065         : glossary(g)
0066         , folded(true)
0067     {
0068     }
0069 
0070     Glossary *glossary;
0071     bool folded;
0072 };
0073 
0074 class GlossaryDialog::Private
0075 {
0076 public:
0077     Private(GlossaryDialog *qq)
0078         : q(qq)
0079     {
0080     }
0081 
0082     ~Private()
0083     {
0084         QList<GlossaryInfo>::ConstIterator it = m_glossaries.constBegin();
0085         QList<GlossaryInfo>::ConstIterator itEnd = m_glossaries.constEnd();
0086         for (; it != itEnd; ++it) {
0087             delete (*it).glossary;
0088         }
0089 
0090         delete m_htmlpart;
0091     }
0092 
0093     void rebuildTree();
0094     QTreeWidgetItem *createItem(const GlossaryInfo &gi) const;
0095     QTreeWidgetItem *findTreeWithLetter(const QChar &l, QTreeWidgetItem *item) const;
0096 
0097     // slots
0098     void itemActivated(QTreeWidgetItem *item, int column);
0099 
0100     GlossaryDialog *q;
0101 
0102     QList<GlossaryInfo> m_glossaries;
0103 
0104     QTextBrowser *m_htmlpart;
0105     QTreeWidget *m_glosstree;
0106     KTreeWidgetSearchLine *m_search;
0107     QString m_htmlbasestring;
0108 
0109     KActionCollection *m_actionCollection;
0110 };
0111 
0112 Glossary::Glossary(const QUrl &url, const QString &path)
0113 {
0114     init(url, path);
0115 }
0116 
0117 Glossary::Glossary()
0118 {
0119     init(QUrl(), QString());
0120 }
0121 
0122 Glossary::~Glossary()
0123 {
0124     qDeleteAll(m_itemlist);
0125 }
0126 
0127 void Glossary::init(const QUrl &url, const QString &path)
0128 {
0129     // setting a generic name for a new glossary
0130     m_name = i18n("Glossary");
0131 
0132     setPicturePath(path);
0133 
0134     if (!url.isEmpty()) {
0135         QDomDocument doc(QStringLiteral("document"));
0136 
0137         if (loadLayout(doc, url)) {
0138             setItemlist(readItems(doc));
0139             if (!m_picturepath.isEmpty()) {
0140                 fixImagePath();
0141             }
0142         }
0143     }
0144 }
0145 
0146 bool Glossary::loadLayout(QDomDocument &Document, const QUrl &url)
0147 {
0148     QFile layoutFile(url.path());
0149 
0150     if (!layoutFile.exists()) {
0151         qCDebug(KALZIUM_LOG) << "no such file: " << layoutFile.fileName();
0152         return false;
0153     }
0154 
0155     if (!layoutFile.open(QIODevice::ReadOnly)) {
0156         return false;
0157     }
0158 
0159     // check if document is well-formed
0160     if (!Document.setContent(&layoutFile)) {
0161         qCDebug(KALZIUM_LOG) << "wrong xml of " << layoutFile.fileName();
0162         layoutFile.close();
0163         return false;
0164     }
0165     layoutFile.close();
0166 
0167     return true;
0168 }
0169 
0170 bool Glossary::isEmpty() const
0171 {
0172     return m_itemlist.count() == 0;
0173 }
0174 
0175 void Glossary::setName(const QString &name)
0176 {
0177     if (name.isEmpty()) {
0178         return;
0179     }
0180     m_name = name;
0181 }
0182 
0183 void Glossary::setPicturePath(const QString &path)
0184 {
0185     if (path.isEmpty()) {
0186         return;
0187     }
0188     m_picturepath = path;
0189 }
0190 
0191 void Glossary::setBackgroundPicture(const QString &filename)
0192 {
0193     if (filename.isEmpty()) {
0194         return;
0195     }
0196     m_backgroundpicture = filename;
0197 }
0198 
0199 void Glossary::fixImagePath()
0200 {
0201     QString imgtag = "<img src=\"file://" + m_picturepath + '/' + "\\1\" />";
0202     QRegularExpression exp(R"(\[img\]([^[]+)\[/img\])");
0203 
0204     foreach (GlossaryItem *item, m_itemlist) {
0205         QString tmp = item->desc();
0206         while (exp.match(tmp).hasMatch()) {
0207             tmp = tmp.replace(exp, imgtag);
0208         }
0209         item->setDesc(tmp);
0210     }
0211 }
0212 
0213 QList<GlossaryItem *> Glossary::readItems(QDomDocument &itemDocument)
0214 {
0215     QList<GlossaryItem *> list;
0216 
0217     QDomNodeList itemList;
0218     QDomNodeList refNodeList;
0219     QDomElement itemElement;
0220     QStringList reflist;
0221 
0222     itemList = itemDocument.elementsByTagName(QStringLiteral("item"));
0223 
0224     const uint num = itemList.count();
0225     for (uint i = 0; i < num; ++i) {
0226         reflist.clear();
0227         auto item = new GlossaryItem();
0228 
0229         itemElement = itemList.item(i).toElement();
0230 
0231         QDomNode nameNode = itemElement.namedItem(QStringLiteral("name"));
0232         QDomNode descNode = itemElement.namedItem(QStringLiteral("desc"));
0233 
0234         QString picName = itemElement.namedItem(QStringLiteral("picture")).toElement().text();
0235         QDomElement refNode = itemElement.namedItem(QStringLiteral("references")).toElement();
0236 
0237         QString desc = i18n(descNode.toElement().text().toUtf8().constData());
0238         if (!picName.isEmpty()) {
0239             desc.prepend("[br][img]" + picName + "[/img][brclear][br]");
0240         }
0241 
0242         item->setName(i18n(nameNode.toElement().text().toUtf8().constData()));
0243 
0244         desc = desc.replace(QLatin1String("[b]"), QLatin1String("<b>"));
0245         desc = desc.replace(QLatin1String("[/b]"), QLatin1String("</b>"));
0246         desc = desc.replace(QLatin1String("[i]"), QLatin1String("<i>"));
0247         desc = desc.replace(QLatin1String("[/i]"), QLatin1String("</i>"));
0248         desc = desc.replace(QLatin1String("[sub]"), QLatin1String("<sub>"));
0249         desc = desc.replace(QLatin1String("[/sub]"), QLatin1String("</sub>"));
0250         desc = desc.replace(QLatin1String("[sup]"), QLatin1String("<sup>"));
0251         desc = desc.replace(QLatin1String("[/sup]"), QLatin1String("</sup>"));
0252         desc = desc.replace(QLatin1String("[br]"), QLatin1String("<br />"));
0253         desc = desc.replace(QLatin1String("[brclear]"), QLatin1String("<br clear=\"left\"/>"));
0254         item->setDesc(desc);
0255 
0256         refNodeList = refNode.elementsByTagName(QStringLiteral("refitem"));
0257         for (int it = 0; it < refNodeList.count(); ++it) {
0258             reflist << i18n(refNodeList.item(it).toElement().text().toUtf8().constData());
0259         }
0260         item->setRef(reflist);
0261 
0262         list.append(item);
0263     }
0264 
0265     return list;
0266 }
0267 
0268 void Glossary::addItem(GlossaryItem *item)
0269 {
0270     m_itemlist.append(item);
0271 }
0272 
0273 QList<GlossaryItem *> Glossary::itemlist() const
0274 {
0275     return m_itemlist;
0276 }
0277 
0278 void Glossary::clear()
0279 {
0280     m_itemlist.clear();
0281 }
0282 
0283 QString Glossary::name() const
0284 {
0285     return m_name;
0286 }
0287 
0288 void Glossary::setItemlist(QList<GlossaryItem *> list)
0289 {
0290     m_itemlist = list;
0291 }
0292 
0293 QString Glossary::picturePath() const
0294 {
0295     return m_picturepath;
0296 }
0297 
0298 QString Glossary::backgroundPicture() const
0299 {
0300     return m_backgroundpicture;
0301 }
0302 
0303 GlossaryDialog::GlossaryDialog(QWidget *parent)
0304     : QDialog(parent)
0305     , d(new Private(this))
0306 {
0307     setWindowTitle(i18nc("@title:window", "Glossary"));
0308 
0309     // this string will be used for all items. If a backgroundpicture should
0310     // be used call Glossary::setBackgroundPicture().
0311     d->m_htmlbasestring =
0312         QStringLiteral("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\"><html><body%1>");
0313 
0314     auto main = new QWidget(this);
0315     auto vbox = new QVBoxLayout(main);
0316     setLayout(vbox);
0317     vbox->setContentsMargins(0, 0, 0, 0);
0318 
0319     auto hbox = new QHBoxLayout();
0320 
0321     d->m_search = new KTreeWidgetSearchLine(main);
0322     d->m_search->setObjectName(QStringLiteral("search-line"));
0323     hbox->addWidget(d->m_search);
0324     vbox->addLayout(hbox);
0325     setFocusProxy(d->m_search);
0326 
0327     auto vs = new QSplitter(main);
0328     vbox->addWidget(vs);
0329 
0330     d->m_glosstree = new QTreeWidget(vs);
0331     d->m_glosstree->setObjectName(QStringLiteral("treeview"));
0332     d->m_glosstree->setHeaderLabel(QStringLiteral("entries"));
0333     d->m_glosstree->header()->hide();
0334     d->m_glosstree->setRootIsDecorated(true);
0335 
0336     d->m_search->addTreeWidget(d->m_glosstree);
0337 
0338     d->m_htmlpart = new QTextBrowser(vs);
0339     d->m_htmlpart->setOpenLinks(false);
0340 
0341     connect(d->m_glosstree, SIGNAL(itemActivated(QTreeWidgetItem *, int)), this, SLOT(itemActivated(QTreeWidgetItem *, int)));
0342     connect(d->m_htmlpart, &QTextBrowser::anchorClicked, this, [=](const QUrl &link) {
0343         // using the "path" part of a qurl as reference
0344         QString myurl = link.path().toLower();
0345         QTreeWidgetItemIterator it(this->d->m_glosstree);
0346         while (*it) {
0347             if ((*it)->type() == GlossaryTreeItemType && (*it)->text(0).toLower() == myurl) {
0348                 // force the item to be selected
0349                 this->d->m_glosstree->setCurrentItem(*it);
0350                 // display its content
0351                 Q_EMIT this->d->itemActivated((*it), 0);
0352                 break;
0353             } else {
0354                 ++it;
0355             }
0356         }
0357     });
0358 
0359     auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
0360     connect(buttonBox, &QDialogButtonBox::rejected, this, &GlossaryDialog::reject);
0361     buttonBox->button(QDialogButtonBox::Close)->setDefault(true);
0362     vbox->addWidget(buttonBox);
0363 
0364     resize(800, 400);
0365 }
0366 
0367 GlossaryDialog::~GlossaryDialog()
0368 {
0369     delete d;
0370 }
0371 
0372 void GlossaryDialog::keyPressEvent(QKeyEvent *e)
0373 {
0374     if (e->key() == Qt::Key_Return) {
0375         e->ignore();
0376     }
0377     QDialog::keyPressEvent(e);
0378 }
0379 
0380 void GlossaryDialog::Private::rebuildTree()
0381 {
0382     m_glosstree->clear();
0383 
0384     QList<GlossaryInfo>::ConstIterator it = m_glossaries.constBegin();
0385     QList<GlossaryInfo>::ConstIterator itEnd = m_glossaries.constEnd();
0386     for (; it != itEnd; ++it) {
0387         m_glosstree->addTopLevelItem(createItem(*it));
0388     }
0389 }
0390 
0391 QTreeWidgetItem *GlossaryDialog::Private::createItem(const GlossaryInfo &gi) const
0392 {
0393     Glossary *glossary = gi.glossary;
0394     bool folded = gi.folded;
0395     auto main = new QTreeWidgetItem();
0396     main->setText(0, glossary->name());
0397     main->setFlags(Qt::ItemIsEnabled);
0398     foreach (GlossaryItem *item, glossary->itemlist()) {
0399         if (folded) {
0400             QChar thisletter = item->name().toUpper().at(0);
0401             QTreeWidgetItem *thisletteritem = findTreeWithLetter(thisletter, main);
0402             if (!thisletteritem) {
0403                 thisletteritem = new QTreeWidgetItem(main);
0404                 thisletteritem->setText(0, QString(thisletter));
0405                 thisletteritem->setFlags(Qt::ItemIsEnabled);
0406                 thisletteritem->setData(0, FirstLetterRole, thisletter);
0407             }
0408             thisletteritem->addChild(new GlossaryTreeItem(glossary, item));
0409         } else {
0410             main->addChild(new GlossaryTreeItem(glossary, item));
0411         }
0412     }
0413     return main;
0414 }
0415 
0416 void GlossaryDialog::addGlossary(Glossary *newgloss, bool folded)
0417 {
0418     if (!newgloss || newgloss->isEmpty()) {
0419         return;
0420     }
0421 
0422     GlossaryInfo gi(newgloss);
0423     gi.folded = folded;
0424     d->m_glossaries.append(gi);
0425 
0426     d->m_glosstree->addTopLevelItem(d->createItem(gi));
0427     d->m_glosstree->sortItems(0, Qt::AscendingOrder);
0428 }
0429 
0430 QTreeWidgetItem *GlossaryDialog::Private::findTreeWithLetter(const QChar &l, QTreeWidgetItem *item) const
0431 {
0432     int count = item->childCount();
0433     for (int i = 0; i < count; ++i) {
0434         QTreeWidgetItem *itemchild = item->child(i);
0435         if (itemchild->data(0, FirstLetterRole).toChar() == l) {
0436             return itemchild;
0437         }
0438     }
0439     return nullptr;
0440 }
0441 
0442 void GlossaryDialog::Private::itemActivated(QTreeWidgetItem *item, int column)
0443 {
0444     Q_UNUSED(column)
0445     if (!item || item->type() != GlossaryTreeItemType) {
0446         return;
0447     }
0448 
0449     auto glosstreeitem = static_cast<GlossaryTreeItem *>(item);
0450     GlossaryItem *glossitem = glosstreeitem->glossaryItem();
0451     QString html;
0452     QString bg_picture = glosstreeitem->glossary()->backgroundPicture();
0453     if (!bg_picture.isEmpty()) {
0454         html = " background=\"file://" + bg_picture + "\"";
0455     }
0456 
0457     html = m_htmlbasestring.arg(html);
0458     html += "<div style=\"margin-left: 10px\">" + glossitem->toHtml() + "</div></body></html>";
0459 
0460     m_htmlpart->setHtml(html);
0461 }
0462 
0463 void GlossaryItem::setRef(const QStringList &s)
0464 {
0465     m_ref = s;
0466     m_ref.sort();
0467 }
0468 
0469 QString GlossaryItem::toHtml() const
0470 {
0471     QString code = "<h1>" + m_name + "</h1>" + "<p>" + m_desc + "</p>" + parseReferences();
0472 
0473     return code;
0474 }
0475 
0476 QString GlossaryItem::parseReferences() const
0477 {
0478     if (m_ref.isEmpty()) {
0479         return {};
0480     }
0481 
0482     QString htmlcode = "<h3>" + i18n("References") + "</h3><ul type=\"disc\">";
0483     static QString basehref = QStringLiteral("<li><a href=\"item:%1\" title=\"%2\">%3</a></li>");
0484 
0485     foreach (const QString &ref, m_ref) {
0486         htmlcode += basehref.arg(QUrl::toPercentEncoding(ref), i18n("Go to '%1'", ref), ref);
0487     }
0488     htmlcode += QLatin1String("</ul>");
0489 
0490     return htmlcode;
0491 }
0492 
0493 void GlossaryItem::setName(const QString &s)
0494 {
0495     m_name = s;
0496 }
0497 
0498 void GlossaryItem::setDesc(const QString &s)
0499 {
0500     m_desc = s;
0501 }
0502 
0503 void GlossaryItem::setPictures(const QString &s)
0504 {
0505     m_pic = QStringList(s);
0506 }
0507 
0508 QString GlossaryItem::name() const
0509 {
0510     return m_name;
0511 }
0512 
0513 QString GlossaryItem::desc() const
0514 {
0515     return m_desc;
0516 }
0517 
0518 QStringList GlossaryItem::ref() const
0519 {
0520     return m_ref;
0521 }
0522 
0523 QStringList GlossaryItem::pictures() const
0524 {
0525     return m_pic;
0526 }
0527 
0528 #include "moc_kdeeduglossary.cpp"