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"