Commit ba43b8e9 authored by Harald Sitter's avatar Harald Sitter 🏳🌈
Browse files

port to qml and refactor to somewhat isolated pages

this is in preparation to add more "wizardy" behavior for scenarios
where samba isn't installed yet, such as adding the user to the samba
database.

the way the new ui works is that it's rendering a page stage, we push
pages on as they become relevant. longer term that'll be at least the
pages install->reboot->setpassword->acls

this also features some related improvements such as
- better const corectness for some member vars
- less ifdef samba_install
- the maximum share name is now capped at 60 characters (which is what
windows10 allows)
- there's a soft warning for the 60 character limit as well in the gui
parent d2673399
......@@ -15,6 +15,8 @@ include(KDEFrameworkCompilerSettings)
find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS
Core
Widgets
Qml
QuickWidgets
)
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS
......@@ -23,6 +25,7 @@ find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS
I18n
KIO
WidgetsAddons
Declarative
)
find_package(PackageKitQt5)
......@@ -68,4 +71,8 @@ add_subdirectory(samba)
install(FILES org.kde.kdenetwork-filesharing.metainfo.xml
DESTINATION ${KDE_INSTALL_METAINFODIR})
if(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/po")
ki18n_install(po)
endif()
feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
#! /bin/sh
$EXTRACTRC `find . -name '*.ui'` >> rc.cpp || exit 11
$XGETTEXT rc.cpp `find . -name '*.cpp' -o -name '*.h'` -o $podir/kfileshare.pot
$XGETTEXT `find . -name '*.cpp' -o -name '*.h' -o -name '*.qml' -o -name '*.js'` -o $podir/kfileshare.pot
########### next target ###############
set(sambausershareplugin_PART_SRCS sambausershareplugin.cpp delegate.cpp model.cpp)
set(sambausershareplugin_PART_SRCS sambausershareplugin.cpp model.cpp)
ki18n_wrap_ui(sambausershareplugin_PART_SRCS sambausershareplugin.ui)
if(SAMBA_INSTALL)
list(APPEND sambausershareplugin_PART_SRCS sambainstaller.cpp)
endif()
qt5_add_resources(sambausershareplugin_PART_SRCS qml/qml.qrc)
add_library(sambausershareplugin MODULE ${sambausershareplugin_PART_SRCS})
target_link_libraries(sambausershareplugin
KF5::CoreAddons
KF5::I18n
KF5::KIOCore
KF5::KIOWidgets
KF5::WidgetsAddons
Qt5::Qml
Qt5::QuickWidgets
KF5::Declarative
)
if(SAMBA_INSTALL)
target_link_libraries(sambausershareplugin PK::packagekitqt5)
endif()
target_include_directories(sambausershareplugin PUBLIC ${Qt5DBus_INCLUDE_DIRS})
install(TARGETS sambausershareplugin DESTINATION ${PLUGIN_INSTALL_DIR})
########### install files ###############
install(FILES sambausershareplugin.desktop DESTINATION ${SERVICES_INSTALL_DIR})
/*
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
SPDX-FileCopyrightText: 2011 Rodrigo Belem <rclbelem@gmail.com>
SPDX-FileCopyrightText: 2019 Nate Graham <nate@kde.org>
*/
#include <QComboBox>
#include <KLocalizedString>
#include "delegate.h"
#include "model.h"
UserPermissionDelegate::UserPermissionDelegate(QObject *parent)
: QItemDelegate(parent)
{
}
QWidget *UserPermissionDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem & /* option */,
const QModelIndex &index) const
{
if (index.column() != UserPermissionModel::ColumnAccess) {
return nullptr;
}
auto comboBox = new QComboBox(parent);
comboBox->addItem(i18n("---"));
comboBox->addItem(i18n("Full Control"), QLatin1String("F"));
comboBox->addItem(i18n("Read Only"), QLatin1String("R"));
comboBox->addItem(i18n("Deny"), QLatin1String("D"));
return comboBox;
}
void UserPermissionDelegate::setEditorData(QWidget *editor,
const QModelIndex &index) const
{
auto comboBox = qobject_cast<QComboBox *>(editor);
if (!comboBox || (index.column() != UserPermissionModel::ColumnAccess)) {
return;
}
int pos = comboBox->findData(index.model()->data(index, Qt::EditRole));
if (pos == -1) {
pos = 0;
}
comboBox->setCurrentIndex(pos);
}
void UserPermissionDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const
{
auto comboBox = qobject_cast<QComboBox *>(editor);
if (!comboBox || (index.column() != UserPermissionModel::ColumnAccess)) {
return;
}
model->setData(index, comboBox->itemData(comboBox->currentIndex()));
}
/*
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
SPDX-FileCopyrightText: 2011 Rodrigo Belem <rclbelem@gmail.com>
SPDX-FileCopyrightText: 2019 Nate Graham <nate@kde.org>
*/
#ifndef delegate_h
#define delegate_h
#include <QItemDelegate>
class UserPermissionDelegate : public QItemDelegate
{
Q_OBJECT
public:
explicit UserPermissionDelegate(QObject *parent = nullptr);
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
void setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const override;
};
#endif
......@@ -13,13 +13,13 @@
#include "model.h"
UserPermissionModel::UserPermissionModel(KSambaShareData &shareData, QObject *parent)
UserPermissionModel::UserPermissionModel(const KSambaShareData &shareData, QObject *parent)
: QAbstractTableModel(parent)
, m_userList(getUsersList())
, m_shareData(shareData)
, m_usersAcl()
{
setupData();
QMetaObject::invokeMethod(this, &UserPermissionModel::setupData);
}
void UserPermissionModel::setupData()
......
......@@ -22,7 +22,7 @@ public:
};
Q_ENUM(Column)
explicit UserPermissionModel(KSambaShareData &shareData, QObject *parent = nullptr);
explicit UserPermissionModel(const KSambaShareData &shareData, QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
......@@ -35,11 +35,11 @@ public:
QString getAcl() const;
private:
QStringList m_userList;
KSambaShareData m_shareData;
const QStringList m_userList;
const KSambaShareData m_shareData;
QVariantMap m_usersAcl;
void setupData();
Q_INVOKABLE void setupData();
static QStringList getUsersList();
};
......
/*
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
*/
import QtQuick 2.12
import QtQuick.Controls 2.5 as QQC2
import QtQuick.Layouts 1.14
import org.kde.kirigami 2.4 as Kirigami
import org.kde.filesharing.samba 1.0 as Samba
// NOTE: Samba.ShareContext is a singleton its properties cannot be bound and need manual syncing back.
ColumnLayout {
id: page
QQC2.CheckBox {
id: shareEnabled
text: i18nc("@option:check", "Share this folder with other computers on the local network")
checked: Samba.ShareContext.enabled
onToggled: {
Samba.ShareContext.enabled = checked
Samba.Plugin.dirty = true
}
}
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
enabled: shareEnabled.checked
RowLayout {
Layout.fillWidth: true
QQC2.Label {
Layout.maximumWidth: Math.round(page.width / 2.0) // Don't let the label use more than half the space, elide the rest.
text: i18nc("@label", "Name:")
elide: Text.ElideRight
}
QQC2.TextField {
id: nameField
Layout.fillWidth: true
text: Samba.ShareContext.name
onTextEdited: {
if (text.length > Samba.ShareContext.maximumNameLength) {
tooLongMessage.visible = true;
// This is a soft limit, do not return.
} else {
tooLongMessage.visible = false
}
if (!Samba.ShareContext.isNameFree(text)) {
alreadyUsedError.visible = true;
return
}
alreadyUsedError.visible = false;
Samba.ShareContext.name = text
Samba.Plugin.dirty = true
}
}
}
Kirigami.InlineMessage {
id: alreadyUsedError
Layout.fillWidth: true
type: Kirigami.MessageType.Error
text: i18nc("@label",
"This name cannot be used. Share names must not be user names and there must not be two shares with the same name on the entire system.")
visible: false
}
Kirigami.InlineMessage {
id: tooLongMessage
Layout.fillWidth: true
type: Kirigami.MessageType.Warning
text: i18nc("@label",
"This name may be too long. It can cause interoperability problems or get rejected by Samba.")
visible: false
}
QQC2.CheckBox {
text: i18nc("@option:check", "Allow guests")
checked: Samba.ShareContext.guestEnabled
onToggled: {
Samba.ShareContext.guestEnabled = checked
Samba.Plugin.dirty = true
}
}
// TODO: this could benefit form some splitting. This is half the file.
QQC2.ScrollView {
id: scroll
Layout.fillHeight: true
Layout.fillWidth: true
activeFocusOnTab: false
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
Component.onCompleted: background.visible = true // crashes when initialized with this. god knows why
contentItem: TableView {
id: view
property bool itemComplete: false
anchors.fill: parent
anchors.margins: Kirigami.Units.smallSpacing
clip: true
interactive: false
model: Samba.UserPermissionModel
columnWidthProvider: function (column) {
// Give 2/3 of the width to the access column for better looks.
var accessWidth = Math.round(width / 1.5)
if (column == Samba.UserPermissionModel.ColumnAccess) {
return accessWidth
}
return width - accessWidth
}
Timer {
// Helper timer to delay force layouting through the event loop.
// We want width changes to recalculate the column widths so we need to force a layout run,
// when doing that directly in onWidthChanged that has a chance to produce errors so instead
// we'll queue a timeout for the next event loop to force the layout run.
// TableView::forceLayout(): Cannot do an immediate re-layout during an ongoing layout!
id: forceLayoutTimer
interval: 0
running: false
repeat: false
onTriggered: {
// forceLayout docs say it must only be called after component completion, so make sure of that.
if (view.itemComplete) {
view.forceLayout()
}
}
}
onWidthChanged: forceLayoutTimer.start() // make sure columns get recalculated
delegate: ColumnLayout {
// This is only a layout to conveniently forward the child size regardless of which child is in
// use.
Layout.fillWidth: true
QQC2.Label {
Layout.fillWidth: true
visible: !combo.visible
text: display === undefined ? "" : display
elide: Text.ElideMiddle
}
QQC2.ComboBox {
Layout.fillWidth: true
id: combo
textRole: "text"
valueRole: "value"
visible: column == Samba.UserPermissionModel.ColumnAccess
model: [
{ value: undefined, text: "---" },
{ value: "F", text: i18nc("@option:radio user can read&write", "Full Control") },
{ value: "R", text: i18nc("@option:radio user can read", "Read Only") },
{ value: "D", text: i18nc("@option:radio user not allowed to access share", "No Access") }
]
onActivated: {
edit = currentValue // setData on model with edit role
Samba.Plugin.dirty = true
}
Component.onCompleted: currentIndex = indexOfValue(edit)
}
}
Component.onCompleted: itemComplete = true
}
}
}
QQC2.Button {
Layout.fillWidth: true
text: i18nc("@button", "Show Samba status monitor")
onClicked: Samba.Plugin.showSambaStatus()
}
}
\ No newline at end of file
/*
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
*/
import QtQuick 2.12
import QtQuick.Controls 2.5 as QQC2
import QtQuick.Layouts 1.14
import org.kde.kirigami 2.12 as Kirigami
import org.kde.filesharing.samba 1.0 as Samba
Kirigami.PlaceholderMessage {
text: i18nc("@label", "Samba must be installed before folders can be shared.")
helpfulAction: Kirigami.Action {
iconName: "install"
text: i18nc("@button", "Install Samba")
onTriggered: Samba.Installer.install()
enabled: !Samba.Installer.installing && !Samba.Installer.installed
}
QQC2.Label {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
text: i18nc("@label", "The Samba package failed to install.")
wrapMode: Text.Wrap
visible: Samba.Installer.failed
}
QQC2.ProgressBar {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
Layout.margins: Kirigami.Units.largeSpacing * 2
indeterminate: true
visible: Samba.Installer.installing
}
Connections {
target: Samba.Installer
onInstalledChanged: {
if (!Samba.Installer.installed) {
return
}
stack.push("RebootPage.qml")
}
}
}
\ No newline at end of file
/*
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
*/
import QtQuick 2.12
import QtQuick.Controls 2.5 as QQC2
import QtQuick.Layouts 1.14
import org.kde.kirigami 2.4 as Kirigami
import org.kde.filesharing.samba 1.0 as Samba
// When built without packagekit we cannot do auto-installation.
ColumnLayout {
QQC2.Label {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
// FIXME could put this in a component shared with crappy page
text: i18nc("@label", "Samba must be installed before folders can be shared.")
wrapMode: Text.Wrap
}
Item {
Layout.alignment = Qt.AlignHCenter
Layout.fillHeight: true // space everything up
}
}
/*
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
*/
import QtQuick 2.12
import QtQuick.Controls 2.5 as QQC2
import QtQuick.Layouts 1.14
import org.kde.kirigami 2.12 as Kirigami
import org.kde.filesharing.samba 1.0 as Samba
Kirigami.PlaceholderMessage {
text: i18nc("@label", "Restart the computer to complete the installation.")
helpfulAction: Kirigami.Action {
iconName: "system-restart"
text: i18nc("@button restart the system", "Restart")
onTriggered: Samba.Installer.reboot()
}
}
/*
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
*/
import QtQuick 2.12
import QtQuick.Controls 2.5 as QQC2
import QtQuick.Layouts 1.14
import org.kde.kirigami 2.4 as Kirigami
import org.kde.filesharing.samba 1.0 as Samba
QQC2.StackView {
id: stack
// The stack of pending pages. Once all backing data is ready we fill the pending stack with all
// pages that ought to get shown eventually. This enables all pages to simply pop the next page and push
// it into the stack once they are done with their thing.
property var pendingStack: []
initialItem: QQC2.BusyIndicator {
running: true
onRunningChanged: {
if (running) {
return
}
pendingStack.push("ACLPage.qml")
if (!Samba.Plugin.isSambaInstalled()) {
// NB: Samba.Installer may be not set when built without installer support
if (Samba.Installer === undefined) {
pendingStack.push("MissingSambaPage.qml")
} else {
pendingStack.push("InstallPage.qml")
}
}
stack.clear()
stack.push(pendingStack.pop())
}
}
// Currently plugin doesn't lazy load anything. This is going to change eventually.
Component.onCompleted: initialItem.running = false
}
<!--
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
-->
<RCC>
<qresource prefix="/org.kde.filesharing.samba/qml/">
<file>main.qml</file>
<file>ACLPage.qml</file>
<file>MissingSambaPage.qml</file>
<file>InstallPage.qml</file>
<file>RebootPage.qml</file>
</qresource>
</RCC>
/*
SPDX-License-Identifier: GPL-2.0-or-later
SPDX-FileCopyrightText: 2004 Jan Schaefer <j_schaef@informatik.uni-kl.de>
SPDX-FileCopyrightText: 2011 Rodrigo Belem <rclbelem@gmail.com>
SPDX-FileCopyrightText: 2019 Nate Graham <nate@kde.org>
SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
*/
#include "sambainstaller.h"
#include <QDBusInterface>
#include <QDebug>
#include <QFile>
#include "sambausershareplugin.h"
void SambaInstaller::install()
{
setInstalling(true);
const QString package = QStringLiteral(SAMBA_PACKAGE_NAME);
QStringList distroSambaPackages = package.split(QLatin1Char(','));
PackageKit::Transaction *resolveTransaction = PackageKit::Daemon::resolve(distroSambaPackages,
PackageKit::Transaction::FilterArch);
QSharedPointer<QStringList> pkgids(new QStringList);
connect(resolveTransaction, &PackageKit::Transaction::package,
this, [pkgids](PackageKit::Transaction::Info /*info*/, const QString &packageId) {
pkgids->append(packageId);
});
connect(resolveTransaction, &PackageKit::Transaction::finished,
this, [this, pkgids](PackageKit::Transaction::Exit exit) {
if (exit != PackageKit::Transaction::ExitSuccess) {
setFailed(true);
return;
}
auto installTransaction = PackageKit::Daemon::installPackages(*pkgids);
connect(installTransaction, &PackageKit::Transaction::finished,
this, &SambaInstaller::packageFinished);
});
}
void SambaInstaller::reboot()
{
QDBusInterface interface(QStringLiteral("org.kde.ksmserver"), QStringLiteral("/KSMServer"),
QStringLiteral("org.kde.KSMServerInterface"), QDBusConnection::sessionBus());
interface.asyncCall(QStringLiteral("logout"), 0, 1, 2); // Options: do not ask again | reboot | force
}
bool SambaInstaller::isInstalling() const
{
return m_installing;
}
bool SambaInstaller::hasFailed() const
{
return m_failed;
}
bool SambaInstaller::isInstalled()