File indexing completed on 2024-04-28 17:06:20

0001 /*
0002     SPDX-FileCopyrightText: 2010 Jan Lepper <krusader@users.sourceforge.net>
0003     SPDX-FileCopyrightText: 2010-2022 Krusader Krew <https://krusader.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "krlayoutfactory.h"
0009 
0010 #include "../compat.h"
0011 #include "../krglobal.h"
0012 #include "listpanel.h"
0013 #include "listpanelframe.h"
0014 
0015 // QtCore
0016 #include <QDebug>
0017 #include <QFile>
0018 #include <QMetaEnum>
0019 #include <QResource>
0020 #include <QStandardPaths>
0021 // QtWidgets
0022 #include <QHBoxLayout>
0023 #include <QVBoxLayout>
0024 // QtXml
0025 #include <QDomDocument>
0026 
0027 #include <KConfigCore/KSharedConfig>
0028 #include <KI18n/KLocalizedString>
0029 
0030 #define XMLFILE_VERSION "1.0"
0031 #define MAIN_FILE "layout.xml"
0032 #define MAIN_FILE_RC_PATH ":/" MAIN_FILE
0033 #define EXTRA_FILE_MASK "layouts/*.xml"
0034 #define DEFAULT_LAYOUT "krusader:default"
0035 
0036 bool KrLayoutFactory::_parsed = false;
0037 QDomDocument KrLayoutFactory::_mainDoc;
0038 QList<QDomDocument> KrLayoutFactory::_extraDocs;
0039 
0040 QString KrLayoutFactory::layoutDescription(const QString &layoutName)
0041 {
0042     if (layoutName == DEFAULT_LAYOUT)
0043         return i18nc("Default layout", "Default");
0044     else if (layoutName == "krusader:compact")
0045         return i18n("Compact");
0046     else if (layoutName == "krusader:classic")
0047         return i18n("Classic");
0048     else
0049         return i18n("Custom layout: \"%1\"", layoutName);
0050 }
0051 
0052 bool KrLayoutFactory::parseFiles()
0053 {
0054     if (_parsed)
0055         return true;
0056 
0057     _parsed = parseResource(MAIN_FILE_RC_PATH, _mainDoc);
0058     if (!_parsed) {
0059         return false;
0060     }
0061 
0062     QStringList extraFilePaths = QStandardPaths::locateAll(QStandardPaths::DataLocation, EXTRA_FILE_MASK);
0063 
0064     foreach (const QString &path, extraFilePaths) {
0065         qWarning() << "extra file: " << path;
0066         QDomDocument doc;
0067         if (parseFile(path, doc))
0068             _extraDocs << doc;
0069     }
0070 
0071     return true;
0072 }
0073 
0074 bool KrLayoutFactory::parseFile(const QString &path, QDomDocument &doc)
0075 {
0076     bool success = false;
0077 
0078     QFile file(path);
0079 
0080     if (file.open(QIODevice::ReadOnly))
0081         return parseContent(file.readAll(), path, doc);
0082     else
0083         qWarning() << "can't open" << path;
0084 
0085     return success;
0086 }
0087 
0088 bool KrLayoutFactory::parseResource(const QString &path, QDomDocument &doc)
0089 {
0090     QResource res(path);
0091     if (res.isValid()) {
0092         QByteArray data;
0093         if (QRESOURCE_ISCOMPRESSED(res))
0094             data = qUncompress(res.data(), static_cast<int>(res.size()));
0095         else
0096             data = QByteArray(reinterpret_cast<const char *>(res.data()), static_cast<int>(res.size()));
0097         return parseContent(data, path, doc);
0098     } else {
0099         qWarning() << "resource does not exist:" << path;
0100         return false;
0101     }
0102 }
0103 
0104 bool KrLayoutFactory::parseContent(const QByteArray &content, const QString &fileName, QDomDocument &doc)
0105 {
0106     bool success = false;
0107 
0108     QString errorMsg;
0109     if (doc.setContent(content, &errorMsg)) {
0110         QDomElement root = doc.documentElement();
0111         if (root.tagName() == "KrusaderLayout") {
0112             QString version = root.attribute("version");
0113             if (version == XMLFILE_VERSION)
0114                 success = true;
0115             else
0116                 qWarning() << fileName << "has wrong version" << version << "- required is" << XMLFILE_VERSION;
0117         } else
0118             qWarning() << "root.tagName() != \"KrusaderLayout\"";
0119     } else
0120         qWarning() << "error parsing" << fileName << ":" << errorMsg;
0121 
0122     return success;
0123 }
0124 
0125 void KrLayoutFactory::getLayoutNames(const QDomDocument &doc, QStringList &names)
0126 {
0127     QDomElement root = doc.documentElement();
0128 
0129     for (QDomElement e = root.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
0130         if (e.tagName() == "layout") {
0131             QString name(e.attribute("name"));
0132             if (!name.isEmpty() && (name != DEFAULT_LAYOUT))
0133                 names << name;
0134         }
0135     }
0136 }
0137 
0138 QStringList KrLayoutFactory::layoutNames()
0139 {
0140     QStringList names;
0141     names << DEFAULT_LAYOUT;
0142 
0143     if (parseFiles()) {
0144         getLayoutNames(_mainDoc, names);
0145 
0146         foreach (const QDomDocument &doc, _extraDocs)
0147             getLayoutNames(doc, names);
0148     }
0149 
0150     return names;
0151 }
0152 
0153 QDomElement KrLayoutFactory::findLayout(const QDomDocument &doc, const QString &layoutName)
0154 {
0155     QDomElement root = doc.documentElement();
0156 
0157     for (QDomElement e = root.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
0158         if (e.tagName() == "layout" && e.attribute("name") == layoutName)
0159             return e;
0160     }
0161 
0162     return QDomElement();
0163 }
0164 
0165 QLayout *KrLayoutFactory::createLayout(QString layoutName)
0166 {
0167     if (layoutName.isEmpty()) {
0168         KConfigGroup cg(krConfig, "PanelLayout");
0169         layoutName = cg.readEntry("Layout", DEFAULT_LAYOUT);
0170     }
0171     QLayout *layout = nullptr;
0172 
0173     if (parseFiles()) {
0174         QDomElement layoutRoot;
0175 
0176         layoutRoot = findLayout(_mainDoc, layoutName);
0177         if (layoutRoot.isNull()) {
0178             foreach (const QDomDocument &doc, _extraDocs) {
0179                 layoutRoot = findLayout(doc, layoutName);
0180                 if (!layoutRoot.isNull())
0181                     break;
0182             }
0183         }
0184         if (layoutRoot.isNull()) {
0185             qWarning() << "no layout with name" << layoutName << "found";
0186             if (layoutName != DEFAULT_LAYOUT)
0187                 return createLayout(DEFAULT_LAYOUT);
0188         } else {
0189             layout = createLayout(layoutRoot, panel);
0190         }
0191     }
0192 
0193     if (layout) {
0194         for (auto it = widgets.constBegin(), end = widgets.constEnd(); it != end; ++it) {
0195             qWarning() << "widget" << it.key() << "was not added to the layout";
0196             it.value()->hide();
0197         }
0198     } else
0199         qWarning() << "couldn't load layout" << layoutName;
0200 
0201     return layout;
0202 }
0203 
0204 QBoxLayout *KrLayoutFactory::createLayout(const QDomElement &e, QWidget *parent)
0205 {
0206     QBoxLayout *l = nullptr;
0207     bool horizontal = false;
0208 
0209     if (e.attribute("type") == "horizontal") {
0210         horizontal = true;
0211         l = new QHBoxLayout();
0212     } else if (e.attribute("type") == "vertical")
0213         l = new QVBoxLayout();
0214     else {
0215         qWarning() << "unknown layout type:" << e.attribute("type");
0216         return nullptr;
0217     }
0218 
0219     l->setSpacing(0);
0220     l->setContentsMargins(0, 0, 0, 0);
0221 
0222     for (QDomElement child = e.firstChildElement(); !child.isNull(); child = child.nextSiblingElement()) {
0223         if (child.tagName() == "layout") {
0224             if (QLayout *childLayout = createLayout(child, parent))
0225                 l->addLayout(childLayout);
0226         } else if (child.tagName() == "frame") {
0227             QWidget *frame = createFrame(child, parent);
0228             l->addWidget(frame);
0229         } else if (child.tagName() == "widget") {
0230             if (QWidget *w = widgets.take(child.attribute("name")))
0231                 l->addWidget(w);
0232             else
0233                 qWarning() << "layout: no such widget:" << child.attribute("name");
0234         } else if (child.tagName() == "hide_widget") {
0235             if (QWidget *w = widgets.take(child.attribute("name")))
0236                 w->hide();
0237             else
0238                 qWarning() << "layout: no such widget:" << child.attribute("name");
0239         } else if (child.tagName() == "spacer") {
0240             if (horizontal)
0241                 l->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Fixed));
0242             else
0243                 l->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding));
0244         }
0245     }
0246 
0247     return l;
0248 }
0249 
0250 QWidget *KrLayoutFactory::createFrame(const QDomElement &e, QWidget *parent)
0251 {
0252     KConfigGroup cg(krConfig, "PanelLayout");
0253 
0254     QString color = cg.readEntry("FrameColor", "default");
0255     if (color == "default")
0256         color = e.attribute("color");
0257     else if (color == "none")
0258         color.clear();
0259 
0260     int shadow = -1, shape = -1;
0261 
0262     QMetaEnum shadowEnum = QFrame::staticMetaObject.enumerator(QFrame::staticMetaObject.indexOfEnumerator("Shadow"));
0263     QString cfgShadow = cg.readEntry("FrameShadow", "default");
0264     if (cfgShadow != "default")
0265         shadow = shadowEnum.keyToValue(cfgShadow.toLatin1().data());
0266     if (shadow < 0)
0267         shadow = shadowEnum.keyToValue(e.attribute("shadow").toLatin1().data());
0268 
0269     QMetaEnum shapeEnum = QFrame::staticMetaObject.enumerator(QFrame::staticMetaObject.indexOfEnumerator("Shape"));
0270     QString cfgShape = cg.readEntry("FrameShape", "default");
0271     if (cfgShape != "default")
0272         shape = shapeEnum.keyToValue(cfgShape.toLatin1().data());
0273     if (shape < 0)
0274         shape = shapeEnum.keyToValue(e.attribute("shape").toLatin1().data());
0275 
0276     ListPanelFrame *frame = new ListPanelFrame(parent, color);
0277     frame->setFrameStyle(shape | shadow);
0278     frame->setAcceptDrops(true);
0279 
0280     if (QLayout *l = createLayout(e, frame)) {
0281         l->setContentsMargins(frame->frameWidth(), frame->frameWidth(), frame->frameWidth(), frame->frameWidth());
0282         frame->setLayout(l);
0283     }
0284 
0285     QObject::connect(frame, &ListPanelFrame::dropped, panel, [=](QDropEvent *event) {
0286         qobject_cast<ListPanel *>(panel)->handleDrop(event);
0287     });
0288     if (!color.isEmpty())
0289         QObject::connect(panel, &ListPanel::signalRefreshColors, frame, &ListPanelFrame::refreshColors);
0290 
0291     return frame;
0292 }