File indexing completed on 2024-04-14 03:40:22

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