Commit e873c3bf authored by Harald Sitter's avatar Harald Sitter 🏳️‍🌈
Browse files

grand qml rewrite

this moves the entire main ui (excluding settings) to qtquick from
qwidgets.

this loses some of the more esoteric kxmlgui features but should be a
good path forward on the balance

the actual map is still being qpainted, so this isn't super ideal but
the plan there is to hopefully use kquickcharts eventually instead of a
custom implementation.

settings dialog still needs porting but is kept qwidget for now to at
least reduce the scope of this port slightly

BUG: 443751
BUG: 445018
BUG: 445026
BUG: 445027
BUG: 449688
parent 37377cb1
Pipeline #168762 passed with stage
in 2 minutes and 53 seconds
......@@ -9,5 +9,6 @@ Dependencies:
'frameworks/kdoctools': '@stable'
'frameworks/kxmlgui': '@stable'
'frameworks/kcrash': '@stable'
'frameworks/kio': '@stable'
'frameworks/kio': '@stable'
'frameworks/kdeclarative': '@stable'
......@@ -36,12 +36,13 @@ include(KDECompilerSettings NO_POLICY_SCOPE)
include(ECMQtDeclareLoggingCategory)
include(ECMDeprecationSettings)
find_package(Qt${QT_MAJOR_VERSION} ${QT_REQUIRED_VERSION} CONFIG REQUIRED Core Widgets Svg)
find_package(Qt${QT_MAJOR_VERSION} ${QT_REQUIRED_VERSION} CONFIG REQUIRED Core Widgets Svg Quick)
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED
XmlGui # For app
KIO # For part
I18n
IconThemes
Declarative
)
find_package(KF5DocTools) # Optional, not needed on Windows for example.
......
......@@ -11,35 +11,30 @@ ecm_setup_version(${PROJECT_VERSION}
add_executable(filelight)
target_sources(filelight PRIVATE
radialMap/widget.cpp
radialMap/item.cpp
radialMap/map.cpp
radialMap/widgetEvents.cpp
radialMap/labels.cpp
overviewWidget.cpp
radialMap/radialMap.cpp
scan.cpp
progressBox.cpp
Config.cpp
settingsDialog.cpp
fileTree.cpp
localLister.cpp
remoteLister.cpp
historyAction.cpp
mainWindow.cpp
mainContext.cpp
main.cpp
settingsDialog.h
fileTree.h
radialMap/map.h
radialMap/radialMap.h
radialMap/sincos.h
radialMap/widget.h
scan.h
define.h
localLister.h
remoteLister.h
historyAction.h
Config.h
mainWindow.h
progressBox.h
qml/qml.qrc
)
ecm_qt_declare_logging_category(filelight HEADER filelight_debug.h IDENTIFIER FILELIGHT_LOG CATEGORY_NAME org.kde.filelight DESCRIPTION "filelight" EXPORT FILELIGHT)
......@@ -59,7 +54,9 @@ target_link_libraries(filelight
KF5::XmlGui
KF5::KIOWidgets # Only used for KDirLister, may be able to move away from that.
KF5::IconThemes
KF5::Declarative
Qt${QT_MAJOR_VERSION}::Svg
Qt${QT_MAJOR_VERSION}::Quick
)
if (WIN32)
find_package(KDEWin REQUIRED)
......
/***********************************************************************
* SPDX-FileCopyrightText: 2003-2004 Max Howell <max.howell@methylblue.com>
* SPDX-FileCopyrightText: 2008-2009 Martin Sandsmark <martin.sandsmark@kde.org>
* SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
***********************************************************************/
......@@ -8,17 +9,17 @@
#ifndef Config_H
#define Config_H
#include <QStringList>
#include <QObject>
#include <QSet>
#include <QStringList>
enum class Dirty
{
enum class Dirty {
Layout = 1,
AntiAliasing = 2,
Colors = 3,
Font
Font,
};
Q_DECLARE_METATYPE(Dirty);
namespace Filelight
{
......
......@@ -4,7 +4,7 @@
# SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#######################################################################
set(filelight_SRCS historyAction.cpp mainWindow.cpp main.cpp)
set(filelight_SRCS historyAction.cpp mainContext.cpp main.cpp)
add_executable(filelight ${filelight_SRCS})
......
/***********************************************************************
* SPDX-FileCopyrightText: 2003-2004 Max Howell <max.howell@methylblue.com>
* SPDX-FileCopyrightText: 2008-2009 Martin Sandsmark <martin.sandsmark@kde.org>
* SPDX-FileCopyrightText: 2017 Harald Sitter <sitter@kde.org>
* SPDX-FileCopyrightText: 2017-2022 Harald Sitter <sitter@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
***********************************************************************/
......@@ -20,10 +20,11 @@ typedef quint64 Dirsize; //**** currently unused
class Folder;
class File
class File : public QObject
{
public:
friend class Folder;
Q_OBJECT
public:
File(const char *name, FileSize size) : m_parent(nullptr), m_name(qstrdup(name)), m_size(size) {}
......@@ -88,6 +89,8 @@ private:
class Folder : public File
{
Q_OBJECT
Q_PROPERTY(uint children MEMBER m_children CONSTANT)
public:
Folder(const char *name) : File(name, 0), m_children(0) {} //DON'T pass the full path!
......
/***********************************************************************
* SPDX-FileCopyrightText: 2003-2004 Max Howell <max.howell@methylblue.com>
* SPDX-FileCopyrightText: 2008-2014 Martin Sandsmark <martin.sandsmark@kde.org>
* SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
***********************************************************************/
#include "define.h"
#include "mainWindow.h"
#include "mainContext.h"
#include <QApplication>
#include <QCommandLineParser>
......@@ -15,9 +16,6 @@
#include <KAboutData>
#include <KLocalizedString>
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#include <Kdelibs4ConfigMigrator>
#endif
#include <KSharedConfig>
#include <KConfigGroup>
......@@ -33,12 +31,6 @@ int main(int argc, char *argv[])
QApplication app(argc, argv);
KLocalizedString::setApplicationDomain("filelight");
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
Kdelibs4ConfigMigrator migrate(QStringLiteral("filelight"));
migrate.setConfigFiles(QStringList() << QStringLiteral("filelightrc"));
migrate.setUiFiles(QStringList() << QStringLiteral("filelightui.rc"));
migrate.migrate();
#endif
auto config = KSharedConfig::openConfig();
auto stateConfig = KSharedConfig::openStateConfig();
if (config->hasGroup("general")) {
......@@ -47,8 +39,6 @@ int main(int argc, char *argv[])
config->deleteGroup("general");
}
using Filelight::MainWindow;
KAboutData about(
QStringLiteral(APP_NAME),
i18n(APP_PRETTYNAME),
......@@ -56,11 +46,13 @@ int main(int argc, char *argv[])
i18n("Graphical disk-usage information"),
KAboutLicense::GPL,
i18n("(C) 2006 Max Howell\n"
"(C) 2008-2014 Martin Sandsmark"),
"(C) 2008-2014 Martin Sandsmark\n"
"(C) 2017-2022 Harald Sitter"),
QString(),
QStringLiteral("https://utils.kde.org/projects/filelight")
);
about.addAuthor(i18n("Martin Sandsmark"), i18n("Maintainer"), QStringLiteral("martin.sandsmark@kde.org"), QStringLiteral("https://iskrembilen.com/"));
about.addAuthor(i18n("Harald Sitter"), i18n("QtQuick Port"), QStringLiteral("sitter@kde.org"));
about.addAuthor(i18n("Max Howell"), i18n("Original author"), QStringLiteral("max.howell@methylblue.com"), QStringLiteral("https://www.methylblue.com/"));
about.addCredit(i18n("Lukas Appelhans"), i18n("Help and support"));
about.addCredit(i18n("Steffen Gerlach"), i18n("Inspiration"), QString(), QStringLiteral("http://www.steffengerlach.de/"));
......@@ -78,17 +70,11 @@ int main(int argc, char *argv[])
options.process(app);
about.processCommandLine(&options);
if (!app.isSessionRestored()) {
MainWindow *mw = new MainWindow();
QStringList args = options.positionalArguments();
if (args.count() > 0) {
mw->scan(QUrl::fromUserInput(args.at(0), QDir::currentPath(), QUrl::AssumeLocalFile));
}
mw->show();
Filelight::MainContext mainContext;
const QStringList args = options.positionalArguments();
if (args.count() > 0) {
mainContext.scan(QUrl::fromUserInput(args.at(0), QDir::currentPath(), QUrl::AssumeLocalFile));
}
else kRestoreMainWindows<MainWindow>();
return app.exec();
}
/***********************************************************************
* SPDX-FileCopyrightText: 2003-2004 Max Howell <max.howell@methylblue.com>
* SPDX-FileCopyrightText: 2008-2009 Martin Sandsmark <martin.sandsmark@kde.org>
* SPDX-FileCopyrightText: 2017-2022 Harald Sitter <sitter@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
***********************************************************************/
#include "mainContext.h"
#include <KAboutData>
#include <KActionCollection>
#include <KConfigGroup>
#include <KDeclarative/KDeclarative>
#include <KIO/Global> // upUrl
#include <KLocalizedString>
#include <KMessageBox> //::start()
#include <QDir>
#include <QFileDialog>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "Config.h"
#include "define.h"
#include "historyAction.h"
#include "radialMap/item.h"
#include "scan.h"
#include "settingsDialog.h"
namespace Filelight
{
class About : public QObject
{
Q_OBJECT
Q_PROPERTY(KAboutData data READ data CONSTANT)
[[nodiscard]] static KAboutData data()
{
return KAboutData::applicationData();
}
public:
using QObject::QObject;
};
MainContext::MainContext(QObject *parent)
: QObject(parent)
, m_histories(nullptr)
, m_manager(new ScanManager(this))
{
Config::read();
auto engine = new QQmlApplicationEngine(this);
setupActions(engine);
connect(m_manager, &ScanManager::aborted, m_histories, &HistoryCollection::stop);
static auto l10nContext = new KLocalizedContext(engine);
l10nContext->setTranslationDomain(QStringLiteral(TRANSLATION_DOMAIN));
engine->rootContext()->setContextObject(l10nContext);
KDeclarative::KDeclarative::setupEngine(engine);
auto about = new About(this);
qRegisterMetaType<size_t>("size_t");
qmlRegisterType<RadialMap::Item>("org.kde.filelight", 1, 0, "RadialMapItem");
qmlRegisterSingletonInstance("org.kde.filelight", 1, 0, "About", about);
qmlRegisterSingletonInstance("org.kde.filelight", 1, 0, "ScanManager", m_manager);
qmlRegisterSingletonInstance("org.kde.filelight", 1, 0, "MainContext", this);
const QUrl mainUrl(QStringLiteral("qrc:/ui/main.qml"));
QObject::connect(
engine,
&QQmlApplicationEngine::objectCreated,
this,
[mainUrl](QObject *obj, const QUrl &objUrl) {
if (!obj && mainUrl == objUrl) {
qWarning() << "Failed to load QML dialog.";
abort();
}
},
Qt::QueuedConnection);
engine->load(mainUrl);
}
void MainContext::scan(const QUrl &u)
{
slotScanUrl(u);
}
void MainContext::setupActions(QQmlApplicationEngine *engine) //singleton function
{
// Only here to satisfy the HistoryCollection. TODO: revise historycollection
auto ac= new KActionCollection(this, QStringLiteral("dummycollection"));
m_histories = new HistoryCollection(ac, this);
for (const auto &name : {QStringLiteral("go_back"), QStringLiteral("go_forward")}) {
// Synthesize actions
auto action = ac->action(name);
Q_ASSERT(action);
QQmlComponent component(engine, QUrl(QStringLiteral("qrc:/ui/Action.qml")));
QObject *object = component.create();
Q_ASSERT(object);
object->setProperty("iconName", action->icon().name());
object->setProperty("text", action->text());
connect(object, SIGNAL(triggered()), action, SIGNAL(triggered()));
connect(action, &QAction::changed, object, [action, object] {
object->setProperty("enabled", action->isEnabled());
});
object->setProperty("enabled", action->isEnabled());
object->setProperty("shortcut", action->shortcut());
addHistoryAction(object);
}
connect(m_histories, &HistoryCollection::activated, this, &MainContext::slotScanUrl);
}
void MainContext::slotScanFolder()
{
slotScanUrl(QFileDialog::getExistingDirectoryUrl(nullptr, i18n("Select Folder to Scan"), url()));
}
void MainContext::slotScanHomeFolder()
{
slotScanPath(QDir::homePath());
}
void MainContext::slotScanRootFolder()
{
slotScanPath(QDir::rootPath());
}
void MainContext::slotUp()
{
slotScanUrl(KIO::upUrl(url()));
}
bool MainContext::slotScanPath(const QString &path)
{
return slotScanUrl(QUrl::fromUserInput(path));
}
bool MainContext::slotScanUrl(const QUrl &url)
{
const QUrl oldUrl = this->url();
if (openUrl(url)) {
m_histories->push(oldUrl);
return true;
}
return false;
}
void MainContext::urlAboutToChange()
{
//called when part's URL is about to change internally
//the part will then create the Map and emit completed()
m_histories->push(url());
}
bool MainContext::openUrl(const QUrl &u)
{
// TODO everyone hates dialogs, instead render the text in big fonts on the Map
// TODO should have an empty QUrl until scan is confirmed successful
// TODO probably should set caption to QString::null while map is unusable
#define KMSG(s) KMessageBox::information(nullptr, s)
QUrl uri = u.adjusted(QUrl::NormalizePathSegments);
const QString localPath = uri.toLocalFile();
const bool isLocal = uri.isLocalFile();
if (uri.isEmpty()) {
// do nothing, chances are the user accidentally pressed ENTER
} else if (!uri.isValid()) {
KMSG(i18n("The entered URL cannot be parsed; it is invalid."));
} else if (isLocal && !QDir::isAbsolutePath(localPath)) {
KMSG(i18n("Filelight only accepts absolute paths, eg. /%1", localPath));
} else if (isLocal && !QDir(localPath).exists()) {
KMSG(i18n("Folder not found: %1", localPath));
} else if (isLocal && !QDir(localPath).isReadable()) {
KMSG(i18n("Unable to enter: %1\nYou do not have access rights to this location.", localPath));
} else {
const bool success = start(uri);
if (success) {
setUrl(uri);
}
return success;
}
qDebug() << "failed to openurl" << u;
return false;
}
QString MainContext::prettyUrl(const QUrl &url) const
{
return url.isLocalFile() ? QDir::toNativeSeparators(url.toLocalFile()) : url.toString();
}
void MainContext::updateURL(const QUrl &u)
{
if (m_manager->running()) {
m_manager->abort();
}
if (u == url()) {
m_manager->emptyCache(); //same as rescan()
}
//do this last, or it breaks Konqi location bar
setUrl(u);
}
void MainContext::rescanSingleDir(const QUrl &dirUrl)
{
if (m_manager->running()) {
m_manager->abort();
}
m_manager->invalidateCacheFor(dirUrl);
start(url());
}
QUrl MainContext::url() const
{
return m_url;
}
void MainContext::setUrl(const QUrl &url)
{
m_url = url;
Q_EMIT urlChanged();
}
void MainContext::configFilelight()
{
auto dialog = new SettingsDialog(nullptr);
connect(dialog, &SettingsDialog::canvasIsDirty, this, &MainContext::canvasIsDirty);
connect(dialog, &SettingsDialog::mapIsInvalid, m_manager, &ScanManager::emptyCache);
dialog->show(); //deletes itself
}
bool MainContext::start(const QUrl &url) const
{
if (m_manager->running()) {
m_manager->abort();
}
return m_manager->start(url);
}
void MainContext::addHistoryAction(QObject *action)
{
m_historyActions.append(action);
Q_EMIT historyActionsChanged();
}
} // namespace Filelight
#include "mainContext.moc"
/***********************************************************************
* SPDX-FileCopyrightText: 2003-2004 Max Howell <max.howell@methylblue.com>
* SPDX-FileCopyrightText: 2008-2009 Martin Sandsmark <martin.sandsmark@kde.org>
* SPDX-FileCopyrightText: 2017 Harald Sitter <sitter@kde.org>
* SPDX-FileCopyrightText: 2017-2022 Harald Sitter <sitter@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
***********************************************************************/
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#pragma once
#include <QUrl>
#include <KXmlGuiWindow>
#include "Config.h"
class QLabel;
class QQmlApplicationEngine;
namespace RadialMap {
class Widget;
}
class Item;
} // namespace RadialMap
class Folder;
class KSqueezedTextLabel;
class KHistoryComboBox;
class KRecentFilesAction;
class ProgressBox;
class HistoryCollection;
namespace Filelight {
class ScanManager;
class OverviewWidget;
class MainWindow : public KXmlGuiWindow // Maybe use qmainwindow
class MainContext : public QObject
{
Q_OBJECT
public:
MainWindow();
Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged)
Q_SIGNAL void urlChanged();
void setUrl(const QUrl &url);
QUrl m_url;
Q_PROPERTY(QList<QObject *> historyActions MEMBER m_historyActions NOTIFY historyActionsChanged)
Q_SIGNAL void historyActionsChanged();
QList<QObject *> m_historyActions;
explicit MainContext(QObject *parent = nullptr);
void scan(const QUrl &u);
void addHistoryAction(QObject *action);
Q_SLOT void scan(const QUrl &u);
Q_SIGNALS:
void started(); // FIXME: Could be replaced by direct func call once merged with mainwindow
void completed();
void canceled(const QString&);
void setWindowCaption(const QString&);
void canceled(const QString &);
void canvasIsDirty(Dirty filth);
private Q_SLOTS:
public Q_SLOTS:
void slotUp();
void slotComboScan();
void slotScanFolder();
void slotScanHomeFolder();
void slotSaveSvg();
void slotScanRootFolder();
bool slotScanUrl(const QUrl&);
bool slotScanPath(const QString&);
void slotAbortScan();
void configToolbars();
void configKeys();
void scanStarted();
void scanFailed();
void scanCompleted();
void urlAboutToChange();
bool openUrl(const QUrl&);
void configFilelight();
void rescan();
void postInit();
void folderScanCompleted(Folder*);
void mapChanged(const Folder*);
void updateURL(const QUrl &);
void rescanSingleDir(const QUrl &);
protected:
void saveProperties(KConfigGroup&) override;
void readProperties(const KConfigGroup&) override;
void closeEvent(QCloseEvent *event) override;
private:
void setupActions();
bool closeUrl();
QString prettyUrl(const QUrl &url) const;
void showOverview();
void hideOverview();
bool start(const QUrl&);
KSqueezedTextLabel *m_status[2];
KHistoryComboBox *m_combo;
HistoryCollection *m_histories;
KRecentFilesAction *m_recentScans;
QLayout *m_layout;
OverviewWidget *m_overviewWidget;
RadialMap::Widget *m_map;
ProgressBox *m_stateWidget;
ScanManager *m_manager;
QLabel *m_numberOfFiles;
bool m_started;
void setupActions(QQmlApplicationEngine *engine);
public:
Q_INVOKABLE QString prettyUrl(const QUrl &url) const;
Q_INVOKABLE bool start(const QUrl&) const;
HistoryCollection *m_histories;
ScanManager *m_manager;