File indexing completed on 2024-04-21 05:48:31

0001 /***********************************************************************
0002  * SPDX-FileCopyrightText: 2003-2004 Max Howell <max.howell@methylblue.com>
0003  * SPDX-FileCopyrightText: 2008-2009 Martin Sandsmark <martin.sandsmark@kde.org>
0004  * SPDX-FileCopyrightText: 2017-2022 Harald Sitter <sitter@kde.org>
0005  *
0006  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0007  ***********************************************************************/
0008 
0009 #include "mainContext.h"
0010 
0011 #include <KAboutData>
0012 #include <KActionCollection>
0013 #include <KIO/Global> // upUrl
0014 #include <KLocalizedString>
0015 #include <KMessageBox> //::start()
0016 
0017 #include <QDir>
0018 #include <QFileDialog>
0019 #include <QQmlApplicationEngine>
0020 #include <QQmlContext>
0021 #include <QStandardPaths>
0022 
0023 #include "contextMenuContext.h"
0024 #include "define.h"
0025 #include "dropperItem.h"
0026 #include "fileModel.h"
0027 #include "historyAction.h"
0028 #include "radialMap/map.h"
0029 #include "radialMap/radialMap.h"
0030 #include "scan.h"
0031 
0032 namespace Filelight
0033 {
0034 
0035 class About : public QObject
0036 {
0037     Q_OBJECT
0038     Q_PROPERTY(KAboutData data READ data CONSTANT)
0039     [[nodiscard]] static KAboutData data()
0040     {
0041         return KAboutData::applicationData();
0042     }
0043 
0044 public:
0045     using QObject::QObject;
0046 };
0047 
0048 MainContext::MainContext(QObject *parent)
0049     : QObject(parent)
0050     , m_histories(nullptr)
0051     , m_manager(new ScanManager(this))
0052 {
0053     Config::instance()->read();
0054 
0055     auto engine = new QQmlApplicationEngine(this);
0056 
0057     setupActions(engine);
0058     connect(m_manager, &ScanManager::aborted, m_histories, &HistoryCollection::stop);
0059 
0060     static auto l10nContext = new KLocalizedContext(engine);
0061     l10nContext->setTranslationDomain(QStringLiteral(TRANSLATION_DOMAIN));
0062     engine->rootContext()->setContextObject(l10nContext);
0063 
0064     qmlRegisterUncreatableMetaObject(Filelight::staticMetaObject, "org.kde.filelight", 1, 0, "Filelight", QStringLiteral("Access to enums & flags only"));
0065 
0066     auto about = new About(this);
0067     qRegisterMetaType<size_t>("size_t");
0068     qmlRegisterType<DropperItem>("org.kde.filelight", 1, 0, "DropperItem");
0069     qmlRegisterSingletonInstance("org.kde.filelight", 1, 0, "About", about);
0070     qmlRegisterSingletonInstance("org.kde.filelight", 1, 0, "ScanManager", m_manager);
0071     qmlRegisterSingletonInstance("org.kde.filelight", 1, 0, "MainContext", this);
0072     auto fileModel = new FileModel(this);
0073     qmlRegisterSingletonInstance("org.kde.filelight", 1, 0, "FileModel", fileModel);
0074 
0075     auto contextMenuContext = new ContextMenuContext(this);
0076     qmlRegisterSingletonInstance("org.kde.filelight", 1, 0, "ContextMenuContext", contextMenuContext);
0077     qmlRegisterUncreatableType<RadialMap::Segment>("org.kde.filelight", 1, 0, "Segment", QStringLiteral("only consumed, never created"));
0078 
0079     // Do not initialize the map too early. It causes crashes on exit. Unclear why, probably a lifetime problem deep inside the retrofitted map. May be fixable
0080     // with enough brain juice.
0081     qmlRegisterSingletonType<RadialMap::Map>("org.kde.filelight", 1, 0, "RadialMap", [](QQmlEngine *engine, QJSEngine *scriptEngine) -> QObject * {
0082         Q_UNUSED(engine)
0083         Q_UNUSED(scriptEngine)
0084         QQmlEngine::setObjectOwnership(RadialMap::Map::instance(), QQmlEngine::CppOwnership);
0085         return RadialMap::Map::instance();
0086     });
0087     qmlRegisterSingletonType<Config>("org.kde.filelight", 1, 0, "Config", [](QQmlEngine *engine, QJSEngine *scriptEngine) -> QObject * {
0088         Q_UNUSED(engine)
0089         Q_UNUSED(scriptEngine)
0090         QQmlEngine::setObjectOwnership(Config::instance(), QQmlEngine::CppOwnership);
0091         return Config::instance();
0092     });
0093 
0094     connect(m_manager, &ScanManager::completed, RadialMap::Map::instance(), [](const auto &tree) {
0095         if (tree) {
0096             RadialMap::Map::instance()->make(tree);
0097         }
0098     });
0099 
0100     connect(RadialMap::Map::instance(), &RadialMap::Map::signatureChanged, fileModel, [fileModel]() {
0101         const auto tree = RadialMap::Map::instance()->root();
0102         if (tree) {
0103             fileModel->setTree(tree);
0104         }
0105     });
0106 
0107     engine->setInitialProperties({
0108         {QStringLiteral("inSandbox"),
0109          !QStandardPaths::locate(QStandardPaths::RuntimeLocation, QStringLiteral("flatpak-info")).isEmpty() || qEnvironmentVariableIsSet("SNAP")},
0110     });
0111 
0112     const QUrl mainUrl(QStringLiteral("qrc:/ui/main.qml"));
0113     QObject::connect(
0114         engine,
0115         &QQmlApplicationEngine::objectCreated,
0116         this,
0117         [mainUrl](QObject *obj, const QUrl &objUrl) {
0118             if (!obj && mainUrl == objUrl) {
0119                 qWarning() << "Failed to load QML dialog.";
0120                 abort();
0121             }
0122         },
0123         Qt::QueuedConnection);
0124     engine->load(mainUrl);
0125 }
0126 
0127 void MainContext::scan(const QUrl &u)
0128 {
0129     slotScanUrl(u);
0130 }
0131 
0132 void MainContext::setupActions(QQmlApplicationEngine *engine) // singleton function
0133 {
0134     // Only here to satisfy the HistoryCollection. TODO: revise historycollection
0135     auto ac = new KActionCollection(this, QStringLiteral("dummycollection"));
0136 
0137     m_histories = new HistoryCollection(ac, this);
0138 
0139     for (const auto &name : {QStringLiteral("go_back"), QStringLiteral("go_forward")}) {
0140         // Synthesize actions
0141         auto action = ac->action(name);
0142         Q_ASSERT(action);
0143         QQmlComponent component(engine, QUrl(QStringLiteral("qrc:/ui/Action.qml")));
0144         QObject *object = component.create();
0145         if (!object) {
0146             qWarning() << "Failed to load component:" << component.errorString();
0147         }
0148         Q_ASSERT(object);
0149         object->setProperty("iconName", action->icon().name());
0150         object->setProperty("text", action->text());
0151         connect(object, SIGNAL(triggered()), action, SIGNAL(triggered()));
0152         connect(action, &QAction::changed, object, [action, object] {
0153             object->setProperty("enabled", action->isEnabled());
0154         });
0155         object->setProperty("enabled", action->isEnabled());
0156         object->setProperty("shortcut", action->shortcut());
0157 
0158         addHistoryAction(object);
0159     }
0160 
0161     connect(m_histories, &HistoryCollection::activated, this, &MainContext::slotScanUrl);
0162 }
0163 
0164 void MainContext::slotScanFolder()
0165 {
0166     slotScanUrl(QFileDialog::getExistingDirectoryUrl(nullptr, i18n("Select Folder to Scan"), url()));
0167 }
0168 
0169 void MainContext::slotScanHomeFolder()
0170 {
0171     slotScanPath(QDir::homePath());
0172 }
0173 
0174 void MainContext::slotScanRootFolder()
0175 {
0176     slotScanPath(QDir::rootPath());
0177 }
0178 
0179 void MainContext::slotUp()
0180 {
0181     const auto downUrl = url();
0182     auto upUrl = KIO::upUrl(downUrl);
0183 #ifdef Q_OS_WINDOWS
0184     if (upUrl.path() == QLatin1Char('/')) { // root means nothing on windows
0185         upUrl = downUrl;
0186     }
0187 #endif
0188     slotScanUrl(upUrl);
0189 }
0190 
0191 bool MainContext::slotScanPath(const QString &path)
0192 {
0193     return slotScanUrl(QUrl::fromUserInput(path));
0194 }
0195 
0196 bool MainContext::slotScanUrl(const QUrl &url)
0197 {
0198     const QUrl oldUrl = this->url();
0199 
0200     if (openUrl(url)) {
0201         m_histories->push(oldUrl);
0202         return true;
0203     }
0204     return false;
0205 }
0206 
0207 bool MainContext::openUrl(const QUrl &u)
0208 {
0209     // TODO everyone hates dialogs, instead render the text in big fonts on the Map
0210     // TODO should have an empty QUrl until scan is confirmed successful
0211     // TODO probably should set caption to QString::null while map is unusable
0212 
0213 #define KMSG(s) KMessageBox::information(nullptr, s)
0214 
0215     QUrl uri = u.adjusted(QUrl::NormalizePathSegments);
0216     const QString localPath = uri.toLocalFile();
0217     const bool isLocal = uri.isLocalFile();
0218 
0219     if (uri.isEmpty()) {
0220         // do nothing, chances are the user accidentally pressed ENTER
0221     } else if (!uri.isValid()) {
0222         KMSG(i18n("The entered URL cannot be parsed; it is invalid."));
0223     } else if (isLocal && !QDir::isAbsolutePath(localPath)) {
0224         KMSG(i18n("Filelight only accepts absolute paths, eg. /%1", localPath));
0225     } else if (isLocal && !QDir(localPath).exists()) {
0226         KMSG(i18n("Folder not found: %1", localPath));
0227     } else if (isLocal && !QDir(localPath).isReadable()) {
0228         KMSG(i18n("Unable to enter: %1\nYou do not have access rights to this location.", localPath));
0229     } else {
0230         const bool success = start(uri);
0231         if (success) {
0232             setUrl(uri);
0233         }
0234         return success;
0235     }
0236 
0237     qDebug() << "failed to openurl" << u;
0238     return false;
0239 }
0240 
0241 QString MainContext::prettyUrl(const QUrl &url) const
0242 {
0243     return url.isLocalFile() ? QDir::toNativeSeparators(url.toLocalFile()) : url.toString();
0244 }
0245 
0246 void MainContext::updateURL(const QUrl &u)
0247 {
0248     if (m_manager->running()) {
0249         m_manager->abort();
0250     }
0251 
0252     if (u == url()) {
0253         m_manager->emptyCache(); // same as rescan()
0254     }
0255 
0256     // do this last, or it breaks Konqi location bar
0257     setUrl(u);
0258 }
0259 
0260 void MainContext::rescanSingleDir(const QUrl &dirUrl) const
0261 {
0262     if (m_manager->running()) {
0263         m_manager->abort();
0264     }
0265 
0266     m_manager->invalidateCacheFor(dirUrl);
0267     start(url());
0268 }
0269 
0270 QUrl MainContext::url() const
0271 {
0272     return m_url;
0273 }
0274 
0275 void MainContext::setUrl(const QUrl &url)
0276 {
0277     m_url = url;
0278     Q_EMIT urlChanged();
0279 }
0280 
0281 bool MainContext::start(const QUrl &url) const
0282 {
0283     if (m_manager->running()) {
0284         m_manager->abort();
0285     }
0286     return m_manager->start(url);
0287 }
0288 
0289 void MainContext::addHistoryAction(QObject *action)
0290 {
0291     m_historyActions.append(action);
0292     Q_EMIT historyActionsChanged();
0293 }
0294 
0295 } // namespace Filelight
0296 
0297 #include "mainContext.moc"