diff --git a/kcm/kcm.cpp b/kcm/kcm.cpp index b32ee1ae..abdba2e6 100755 --- a/kcm/kcm.cpp +++ b/kcm/kcm.cpp @@ -1,548 +1,548 @@ /* Copyright 2016-2018 Jan Grulich This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "kcm.h" #include "debug.h" #include "settings/connectionsettings.h" // #include "mobileconnectionwizard.h" #include "uiutils.h" // #include "vpnuiplugin.h" // KDE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Qt #include #include #include Q_LOGGING_CATEGORY(KCM_NETWORKMANAGEMENT, "kcm_networkmanagement") K_PLUGIN_FACTORY_WITH_JSON(KCMNetworkmanagementFactory, "kcm_networkmanagement.json", registerPlugin();) KCMNetworkmanagement::KCMNetworkmanagement(QObject *parent, const QVariantList &args) : KQuickAddons::ConfigModule(parent, args) , m_handler(new Handler(this)) { KAboutData* about = new KAboutData(QStringLiteral("kcm_networkmanagement"), i18n("Edit your Network Connections"), QStringLiteral("0.1"), QString(), KAboutLicense::LGPL); about->addAuthor(i18n("Jan Grulich"), QString(), QStringLiteral("jgrulich@redhat.com")); setAboutData(about); setButtons(Apply); // Check if we can use AP mode to identify security type bool useApMode = false; bool foundInactive = false; NetworkManager::WirelessDevice::Ptr wifiDev; for (const NetworkManager::Device::Ptr &device : NetworkManager::networkInterfaces()) { if (device->type() == NetworkManager::Device::Wifi) { wifiDev = device.objectCast(); if (wifiDev) { if (!wifiDev->isActive()) { foundInactive = true; } else { // Prefer previous device if it was inactive if (foundInactive) { break; } } if (wifiDev->wirelessCapabilities().testFlag(NetworkManager::WirelessDevice::ApCap)) { useApMode = true; } // We prefer inactive wireless card with AP capabilities if (foundInactive && useApMode) { break; } } } } // TODO // m_ui->connectionView->rootContext()->setContextProperty("connectionModified", false); // m_ui->connectionView->rootContext()->setContextProperty("useApMode", useApMode); m_connectionSetting = new ConnectionSetting(this); // connect(m_connectionSetting, &ConnectionSetting::settingChanged, // [this] () { // if (m_connectionSetting->isInitialized() && m_connectionSetting->isValid()) { // setNeedsSave(true); // } // }); connect(m_connectionSettings, &ConnectionSettings::validityChanged, [this] (bool valid) { if (m_connectionSettings->isInitialized()) { setNeedsSave(true); } }); NetworkManager::Connection::Ptr selectedConnection; // Look in the arguments for a connection ID to preselect static const QLatin1String uuidArgumentMarker { "Uuid=" }; for (QVariant arg : args) { if (arg.canConvert(QMetaType::QString)) { QString uuid = arg.toString(); if (uuid.startsWith(uuidArgumentMarker)) { uuid = uuid.replace(uuidArgumentMarker, QString()); selectedConnection = NetworkManager::findConnectionByUuid(uuid); qDebug() << "Selecting user connection:" << uuid; break; } } } // Pre-select currently active primary connection and if there is none then just select // the very first connection - NetworkManager::ActiveConnection::Ptr activeConnection = NetworkManager::primaryConnection(); - if (activeConnection && activeConnection->isValid()) { - // Also check if the connection type is supported by KCM - const NetworkManager::ConnectionSettings::ConnectionType type = activeConnection->type(); - if (UiUtils::isConnectionTypeSupported(type)) { - QMetaObject::invokeMethod(mainUi(), "selectConnectionInView", Q_ARG(QVariant, activeConnection->id()), Q_ARG(QVariant, activeConnection->connection()->path())); - } - } - - // Select the very first connection as a fallback - if (!selectedConnection || !selectedConnection->isValid()) { - NetworkManager::Connection::List connectionList = NetworkManager::listConnections(); - std::sort(connectionList.begin(), connectionList.end(), [] (const NetworkManager::Connection::Ptr &left, const NetworkManager::Connection::Ptr &right) - { - const QString leftName = left->settings()->id(); - const UiUtils::SortedConnectionType leftType = UiUtils::connectionTypeToSortedType(left->settings()->connectionType()); - const QDateTime leftDate = left->settings()->timestamp(); - - const QString rightName = right->settings()->id(); - const UiUtils::SortedConnectionType rightType = UiUtils::connectionTypeToSortedType(right->settings()->connectionType()); - const QDateTime rightDate = right->settings()->timestamp(); - - if (leftType < rightType) { - return true; - } else if (leftType > rightType) { - return false; - } - - if (leftDate > rightDate) { - return true; - } else if (leftDate < rightDate) { - return false; - } - - if (QString::localeAwareCompare(leftName, rightName) > 0) { - return true; - } else { - return false; - } - }); - - for (const NetworkManager::Connection::Ptr &connection : connectionList) { - const NetworkManager::ConnectionSettings::ConnectionType type = connection->settings()->connectionType(); - if (UiUtils::isConnectionTypeSupported(type)) { - QMetaObject::invokeMethod(mainUi(), "selectConnectionInView", Q_ARG(QVariant, connection->settings()->id()), Q_ARG(QVariant, connection->path())); - break; - } - } - } +// NetworkManager::ActiveConnection::Ptr activeConnection = NetworkManager::primaryConnection(); +// if (activeConnection && activeConnection->isValid()) { +// // Also check if the connection type is supported by KCM +// const NetworkManager::ConnectionSettings::ConnectionType type = activeConnection->type(); +// if (UiUtils::isConnectionTypeSupported(type)) { +// QMetaObject::invokeMethod(mainUi(), "selectConnectionInView", Q_ARG(QVariant, activeConnection->id()), Q_ARG(QVariant, activeConnection->connection()->path())); +// } +// } +// +// // Select the very first connection as a fallback +// if (!selectedConnection || !selectedConnection->isValid()) { +// NetworkManager::Connection::List connectionList = NetworkManager::listConnections(); +// std::sort(connectionList.begin(), connectionList.end(), [] (const NetworkManager::Connection::Ptr &left, const NetworkManager::Connection::Ptr &right) +// { +// const QString leftName = left->settings()->id(); +// const UiUtils::SortedConnectionType leftType = UiUtils::connectionTypeToSortedType(left->settings()->connectionType()); +// const QDateTime leftDate = left->settings()->timestamp(); +// +// const QString rightName = right->settings()->id(); +// const UiUtils::SortedConnectionType rightType = UiUtils::connectionTypeToSortedType(right->settings()->connectionType()); +// const QDateTime rightDate = right->settings()->timestamp(); +// +// if (leftType < rightType) { +// return true; +// } else if (leftType > rightType) { +// return false; +// } +// +// if (leftDate > rightDate) { +// return true; +// } else if (leftDate < rightDate) { +// return false; +// } +// +// if (QString::localeAwareCompare(leftName, rightName) > 0) { +// return true; +// } else { +// return false; +// } +// }); +// +// for (const NetworkManager::Connection::Ptr &connection : connectionList) { +// const NetworkManager::ConnectionSettings::ConnectionType type = connection->settings()->connectionType(); +// if (UiUtils::isConnectionTypeSupported(type)) { +// QMetaObject::invokeMethod(mainUi(), "selectConnectionInView", Q_ARG(QVariant, connection->settings()->id()), Q_ARG(QVariant, connection->path())); +// break; +// } +// } +// } if (selectedConnection && selectedConnection->isValid()) { const NetworkManager::ConnectionSettings::Ptr settings = selectedConnection->settings(); if (UiUtils::isConnectionTypeSupported(settings->connectionType())) { QMetaObject::invokeMethod(rootItem, "selectConnection", Q_ARG(QVariant, settings->id()), Q_ARG(QVariant, selectedConnection->path())); } } else { qDebug() << "Cannot preselect a connection"; } connect(NetworkManager::settingsNotifier(), &NetworkManager::SettingsNotifier::connectionAdded, this, &KCMNetworkmanagement::onConnectionAdded, Qt::UniqueConnection); // Initialize first scan and then scan every 15 seconds m_handler->requestScan(); m_timer = new QTimer(this); m_timer->setInterval(15000); connect(m_timer, &QTimer::timeout, [this] () { m_handler->requestScan(); }); m_timer->start(); } KCMNetworkmanagement::~KCMNetworkmanagement() { delete m_handler; } void KCMNetworkmanagement::defaults() { KQuickAddons::ConfigModule::defaults(); } void KCMNetworkmanagement::load() { // If there is no loaded connection do nothing if (m_currentConnectionPath.isEmpty()) { return; } NetworkManager::Connection::Ptr connection = NetworkManager::findConnection(m_currentConnectionPath); if (connection) { NetworkManager::ConnectionSettings::Ptr connectionSettings = connection->settings(); // Re-load the connection again to load stored values m_connectionSettings->loadConfig(connectionSettings); } KQuickAddons::ConfigModule::load(); } void KCMNetworkmanagement::save() { // NetworkManager::Connection::Ptr connection = NetworkManager::findConnection(m_currentConnectionPath); // // if (connection) { // m_handler->updateConnection(connection, m_connectionSettings->setting()); // } KQuickAddons::ConfigModule::save(); } void KCMNetworkmanagement::onConnectionAdded(const QString &connection) { if (m_createdConnectionUuid.isEmpty()) { return; } NetworkManager::Connection::Ptr newConnection = NetworkManager::findConnection(connection); if (newConnection) { NetworkManager::ConnectionSettings::Ptr connectionSettings = newConnection->settings(); if (connectionSettings && connectionSettings->uuid() == m_createdConnectionUuid) { loadConnectionSettings(connectionSettings); QMetaObject::invokeMethod(mainUi(), "selectConnectionInView", Q_ARG(QVariant, connectionSettings->id()), Q_ARG(QVariant, newConnection->path())); m_createdConnectionUuid.clear(); } } } void KCMNetworkmanagement::selectConnection(const QString &connectionPath) { if (connectionPath.isEmpty()) { resetSelection(); return; } m_currentConnectionPath = connectionPath; NetworkManager::Connection::Ptr connection = NetworkManager::findConnection(m_currentConnectionPath); if (connection) { NetworkManager::ConnectionSettings::Ptr connectionSettings = connection->settings(); loadConnectionSettings(connectionSettings); } } void KCMNetworkmanagement::requestCreateConnection(int connectionType, const QString &vpnType, const QString &specificType, bool shared) { NetworkManager::ConnectionSettings::ConnectionType type = static_cast(connectionType); if (type == NetworkManager::ConnectionSettings::Vpn && vpnType == "imported") { importVpn(); } else if (type == NetworkManager::ConnectionSettings::Gsm) { // launch the mobile broadband wizard, both gsm/cdma // #if WITH_MODEMMANAGER_SUPPORT // QPointer wizard = new MobileConnectionWizard(NetworkManager::ConnectionSettings::Unknown, this); // connect(wizard.data(), &MobileConnectionWizard::accepted, // [wizard, this] () { // if (wizard->getError() == MobileProviders::Success) { // qCDebug(KCM_NETWORKMANAGEMENT) << "Mobile broadband wizard finished:" << wizard->type() << wizard->args(); // // if (wizard->args().count() == 2) { // QVariantMap tmp = qdbus_cast(wizard->args().value(1)); // // NetworkManager::ConnectionSettings::Ptr connectionSettings; // connectionSettings = NetworkManager::ConnectionSettings::Ptr(new NetworkManager::ConnectionSettings(wizard->type())); // connectionSettings->setId(wizard->args().value(0).toString()); // if (wizard->type() == NetworkManager::ConnectionSettings::Gsm) { // NetworkManager::GsmSetting::Ptr gsmSetting = connectionSettings->setting(NetworkManager::Setting::Gsm).staticCast(); // gsmSetting->fromMap(tmp); // gsmSetting->setPasswordFlags(NetworkManager::Setting::NotRequired); // gsmSetting->setPinFlags(NetworkManager::Setting::NotRequired); // } else if (wizard->type() == NetworkManager::ConnectionSettings::Cdma) { // connectionSettings->setting(NetworkManager::Setting::Cdma)->fromMap(tmp); // } else { // qCWarning(KCM_NETWORKMANAGEMENT) << Q_FUNC_INFO << "Unhandled setting type"; // } // // Generate new UUID // connectionSettings->setUuid(NetworkManager::ConnectionSettings::createNewUuid()); // addConnection(connectionSettings); // } else { // qCWarning(KCM_NETWORKMANAGEMENT) << Q_FUNC_INFO << "Unexpected number of args to parse"; // } // } // }); // connect(wizard.data(), &MobileConnectionWizard::finished, // [wizard] () { // if (wizard) { // wizard->deleteLater(); // } // }); // wizard->setModal(true); // wizard->show(); // #endif } else { NetworkManager::ConnectionSettings::Ptr connectionSettings; connectionSettings = NetworkManager::ConnectionSettings::Ptr(new NetworkManager::ConnectionSettings(type)); if (type == NetworkManager::ConnectionSettings::Vpn) { NetworkManager::VpnSetting::Ptr vpnSetting = connectionSettings->setting(NetworkManager::Setting::Vpn).dynamicCast(); vpnSetting->setServiceType(vpnType); // Set VPN subtype in case of Openconnect to add support for juniper if (vpnType == QLatin1String("org.freedesktop.NetworkManager.openconnect")) { NMStringMap data = vpnSetting->data(); data.insert(QLatin1String("protocol"), specificType); vpnSetting->setData(data); } } if (type == NetworkManager::ConnectionSettings::Wired || type == NetworkManager::ConnectionSettings::Wireless) { // Set auto-negotiate to true, NM sets it to false by default, but we used to have this before and also // I don't think it's wise to request users to specify speed and duplex as most of them don't know what is that // and what to set if (type == NetworkManager::ConnectionSettings::Wired) { NetworkManager::WiredSetting::Ptr wiredSetting = connectionSettings->setting(NetworkManager::Setting::Wired).dynamicCast(); wiredSetting->setAutoNegotiate(true); } if (shared) { if (type == NetworkManager::ConnectionSettings::Wireless) { NetworkManager::WirelessSetting::Ptr wifiSetting = connectionSettings->setting(NetworkManager::Setting::Wireless).dynamicCast(); wifiSetting->setMode(NetworkManager::WirelessSetting::Adhoc); wifiSetting->setSsid(i18n("my_shared_connection").toUtf8()); for (const NetworkManager::Device::Ptr & device : NetworkManager::networkInterfaces()) { if (device->type() == NetworkManager::Device::Wifi) { NetworkManager::WirelessDevice::Ptr wifiDev = device.objectCast(); if (wifiDev) { if (wifiDev->wirelessCapabilities().testFlag(NetworkManager::WirelessDevice::ApCap)) { wifiSetting->setMode(NetworkManager::WirelessSetting::Ap); wifiSetting->setMacAddress(NetworkManager::macAddressFromString(wifiDev->permanentHardwareAddress())); } } } } } NetworkManager::Ipv4Setting::Ptr ipv4Setting = connectionSettings->setting(NetworkManager::Setting::Ipv4).dynamicCast(); ipv4Setting->setMethod(NetworkManager::Ipv4Setting::Shared); connectionSettings->setAutoconnect(false); } } if (type == NetworkManager::ConnectionSettings::WireGuard) { NetworkManager::WireGuardSetting::Ptr wireguardSetting = connectionSettings->setting(NetworkManager::Setting::WireGuard).dynamicCast(); NetworkManager::Ipv4Setting::Ptr ipv4Setting = connectionSettings->setting(NetworkManager::Setting::Ipv4).dynamicCast(); NetworkManager::Ipv6Setting::Ptr ipv6Setting = connectionSettings->setting(NetworkManager::Setting::Ipv6).dynamicCast(); connectionSettings->setAutoconnect(false); ipv4Setting->setMethod(NetworkManager::Ipv4Setting::Disabled); ipv6Setting->setMethod(NetworkManager::Ipv6Setting::Ignored); } // Generate new UUID connectionSettings->setUuid(NetworkManager::ConnectionSettings::createNewUuid()); addConnection(connectionSettings); } } void KCMNetworkmanagement::requestExportConnection(const QString &connectionPath) { NetworkManager::Connection::Ptr connection = NetworkManager::findConnection(connectionPath); if (!connection) { return; } NetworkManager::ConnectionSettings::Ptr connSettings = connection->settings(); if (connSettings->connectionType() != NetworkManager::ConnectionSettings::Vpn) return; NetworkManager::VpnSetting::Ptr vpnSetting = connSettings->setting(NetworkManager::Setting::Vpn).dynamicCast(); qCDebug(KCM_NETWORKMANAGEMENT) << "Exporting VPN connection" << connection->name() << "type:" << vpnSetting->serviceType(); QString error; // VpnUiPlugin * vpnPlugin = KServiceTypeTrader::createInstanceFromQuery(QStringLiteral("PlasmaNetworkManagement/VpnUiPlugin"), // QStringLiteral("[X-NetworkManager-Services]=='%1'").arg(vpnSetting->serviceType()), // this, QVariantList(), &error); // if (vpnPlugin) { // if (vpnPlugin->suggestedFileName(connSettings).isEmpty()) { // this VPN doesn't support export // qCWarning(KCM_NETWORKMANAGEMENT) << "This VPN doesn't support export"; // return; // } // // const QString url = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + QDir::separator() + vpnPlugin->suggestedFileName(connSettings); // const QString filename = QFileDialog::getSaveFileName(this, i18n("Export VPN Connection"), url, vpnPlugin->supportedFileExtensions()); // if (!filename.isEmpty()) { // if (!vpnPlugin->exportConnectionSettings(connSettings, filename)) { // // TODO display failure // qCWarning(KCM_NETWORKMANAGEMENT) << "Failed to export VPN connection"; // } else { // // TODO display success // } // } // delete vpnPlugin; // } else { // qCWarning(KCM_NETWORKMANAGEMENT) << "Error getting VpnUiPlugin for export:" << error; // } } QObject * KCMNetworkmanagement::connectionSettings() const { return m_connectionSettings; } void KCMNetworkmanagement::addConnection(const NetworkManager::ConnectionSettings::Ptr &connectionSettings) { // QPointer editor = new ConnectionEditorDialog(connectionSettings); // connect(editor.data(), &ConnectionEditorDialog::accepted, // [connectionSettings, editor, this] () { // // We got confirmation so watch this connection and select it once it is created // m_createdConnectionUuid = connectionSettings->uuid(); // m_handler->addConnection(editor->setting()); // }); // connect(editor.data(), &ConnectionEditorDialog::finished, // [editor] () { // if (editor) { // editor->deleteLater(); // } // }); // editor->setModal(true); // editor->show(); } void KCMNetworkmanagement::loadConnectionSettings(const NetworkManager::ConnectionSettings::Ptr& connectionSettings) { m_connectionSettings->loadConfig(connectionSettings); QMetaObject::invokeMethod(mainUi(), "loadConnectionSetting"); setNeedsSave(false); } void KCMNetworkmanagement::importVpn() { // get the list of supported extensions // const KService::List services = KServiceTypeTrader::self()->query("PlasmaNetworkManagement/VpnUiPlugin"); // QString extensions; // for (const KService::Ptr &service : services) { // VpnUiPlugin * vpnPlugin = service->createInstance(this); // if (vpnPlugin) { // extensions += vpnPlugin->supportedFileExtensions() % QStringLiteral(" "); // delete vpnPlugin; // } // } // // const QString &filename = QFileDialog::getOpenFileName(this, i18n("Import VPN Connection"), QDir::homePath(), extensions.simplified()); // // if (!filename.isEmpty()) { // const KService::List services = KServiceTypeTrader::self()->query("PlasmaNetworkManagement/VpnUiPlugin"); // // QFileInfo fi(filename); // const QString ext = QStringLiteral("*.") % fi.suffix(); // qCDebug(PLASMA_NM) << "Importing VPN connection " << filename << "extension:" << ext; // // // Handle WireGuard separately because it is different than all the other VPNs // if (WireGuardInterfaceWidget::supportedFileExtensions().contains(ext)) { // NMVariantMapMap connection = WireGuardInterfaceWidget::importConnectionSettings(filename); // NetworkManager::ConnectionSettings connectionSettings; // connectionSettings.fromMap(connection); // connectionSettings.setUuid(NetworkManager::ConnectionSettings::createNewUuid()); // // // qCDebug(PLASMA_NM) << "Converted connection:" << connectionSettings; // // m_handler->addConnection(connectionSettings.toMap()); // // qCDebug(PLASMA_NM) << "Adding imported connection under id:" << conId; // // if (!connection.isEmpty()) { // return; // get out if the import produced at least some output // } // } // for (const KService::Ptr &service : services) { // VpnUiPlugin * vpnPlugin = service->createInstance(this); // if (vpnPlugin && vpnPlugin->supportedFileExtensions().contains(ext)) { // qCDebug(PLASMA_NM) << "Found VPN plugin" << service->name() << ", type:" << service->property("X-NetworkManager-Services", QVariant::String).toString(); // // NMVariantMapMap connection = vpnPlugin->importConnectionSettings(filename); // // // qCDebug(PLASMA_NM) << "Raw connection:" << connection; // // NetworkManager::ConnectionSettings connectionSettings; // connectionSettings.fromMap(connection); // connectionSettings.setUuid(NetworkManager::ConnectionSettings::createNewUuid()); // // // qCDebug(PLASMA_NM) << "Converted connection:" << connectionSettings; // // m_handler->addConnection(connectionSettings.toMap()); // // qCDebug(PLASMA_NM) << "Adding imported connection under id:" << conId; // // if (connection.isEmpty()) { // the "positive" part will arrive with connectionAdded // // TODO display success // } else { // delete vpnPlugin; // break; // stop iterating over the plugins if the import produced at least some output // } // // delete vpnPlugin; // } // } // } } void KCMNetworkmanagement::resetSelection() { // Reset selected connections m_currentConnectionPath.clear(); QMetaObject::invokeMethod(mainUi(), "deselectConnectionsInView"); // if (m_connectionSetting) { // delete m_connectionSetting; // m_connectionSetting = nullptr; // } setNeedsSave(false); } #include "kcm.moc" diff --git a/kcm/package/contents/ui/ConnectionItemDelegate.qml b/kcm/package/contents/ui/ConnectionItemDelegate.qml index 7edb92d8..35e9930b 100644 --- a/kcm/package/contents/ui/ConnectionItemDelegate.qml +++ b/kcm/package/contents/ui/ConnectionItemDelegate.qml @@ -1,135 +1,138 @@ /* Copyright 2016-2018 Jan Grulich This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ import QtQuick 2.6 import QtQuick.Layouts 1.2 import QtQuick.Controls 2.5 as QQC2 import org.kde.kirigami 2.9 as Kirigami import org.kde.plasma.networkmanagement 0.2 as PlasmaNM Kirigami.AbstractListItem { id: connectionItem - checked: ConnectionPath === scrollablePage.currentConnectionPath - highlighted: hovered + Accessible.role: Accessible.ListItem + Accessible.name: model.Name + + checked: ConnectionPath === connectionViewPage.currentConnectionPath + highlighted: focus signal aboutToChangeConnection(bool exportable, string name, string path) signal aboutToExportConnection(string path) signal aboutToRemoveConnection(string name, string path) RowLayout { anchors { left: parent.left right: parent.right verticalCenter: parent.verticalCenter leftMargin: Kirigami.Units.largeSpacing } spacing: Kirigami.Units.largeSpacing Kirigami.Icon { id: connectionIcon Layout.minimumHeight: Kirigami.Units.iconSizes.smallMedium Layout.maximumHeight: Layout.minimumHeight Layout.minimumWidth: height source: KcmConnectionIcon } ColumnLayout { spacing: 0 QQC2.Label { id: nameLabel Layout.fillWidth: true height: paintedHeight elide: Text.ElideRight font.weight: ConnectionState == PlasmaNM.Enums.Activated ? Font.DemiBold : Font.Normal font.italic: ConnectionState == PlasmaNM.Enums.Activating ? true : false text: Name textFormat: Text.PlainText } QQC2.Label { id: statusLabel Layout.fillWidth: true height: paintedHeight elide: Text.ElideRight font.pointSize: theme.smallestFont.pointSize text: itemText() textFormat: Text.PlainText opacity: 0.6 } } } QQC.Menu { id: connectionItemMenu QQC.MenuItem { text: ConnectionState == PlasmaNM.Enums.Deactivated ? i18n("Connect") : i18n("Disconnect") visible: ItemType == 1 onTriggered: { if (ConnectionState == PlasmaNM.Enums.Deactivated) { handler.activateConnection(ConnectionPath, DevicePath, SpecificPath); } else { handler.deactivateConnection(ConnectionPath, DevicePath); } } } QQC.MenuItem { iconName: "list-remove" text: i18n("Delete"); onTriggered: { aboutToRemoveConnection(Name, ConnectionPath) } } QQC.MenuItem { iconName: "document-export" visible: KcmVpnConnectionExportable text: i18n("Export"); onTriggered: aboutToExportConnection(ConnectionPath) } } onClicked: { if (mouse.button === Qt.LeftButton) { aboutToChangeConnection(KcmVpnConnectionExportable, Name, ConnectionPath) } else if (mouse.button == Qt.RightButton) { connectionItemMenu.popup() } } /* This generates the status description under each connection in the list at the left side of the applet. */ function itemText() { if (ConnectionState == PlasmaNM.Enums.Activated) { return i18n("Connected") } else if (ConnectionState == PlasmaNM.Enums.Activating) { return i18n("Connecting") } else { return LastUsed } } } diff --git a/kcm/package/contents/ui/ConnectionView.qml b/kcm/package/contents/ui/ConnectionView.qml index a0e177c7..10eddf14 100644 --- a/kcm/package/contents/ui/ConnectionView.qml +++ b/kcm/package/contents/ui/ConnectionView.qml @@ -1,92 +1,237 @@ /* Copyright 2016-2018 Jan Grulich This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ import QtQuick 2.6 +import QtQuick.Layouts 1.3 import QtQuick.Controls 2.2 as QtControls import org.kde.kcm 1.0 import org.kde.kirigami 2.3 as Kirigami Kirigami.ScrollablePage { - id: scrollablePage + id: connectionViewPage property bool currentConnectionExportable: false property string currentConnectionName property string currentConnectionPath - Kirigami.Theme.colorSet: Kirigami.Theme.View + title: "Connections" + Kirigami.Theme.colorSet: Kirigami.Theme.View background: Rectangle { color: Kirigami.Theme.backgroundColor } + header: Rectangle { + color: Kirigami.Theme.backgroundColor + + width: connectionViewPage.width + height: Math.round(Kirigami.Units.gridUnit * 2.5) + + RowLayout { + id: searchLayout + + spacing: Kirigami.Units.smallSpacing + anchors { + fill: parent + margins: Kirigami.Units.smallSpacing + } + + QtControls.TextField { + id: searchField + + Layout.minimumHeight: Layout.maximumHeight + Layout.maximumHeight: Kirigami.Units.iconSizes.smallMedium + Kirigami.Units.smallSpacing * 2 + Layout.fillWidth: true + + focus: true + placeholderText: i18n("Type here to search connection...") + + onTextChanged: { + editorProxyModel.setFilterRegExp(text) + } + + MouseArea { + anchors { + right: parent.right + verticalCenter: parent.verticalCenter + rightMargin: y + } + + opacity: searchField.text.length > 0 ? 1 : 0 + width: Kirigami.Units.iconSizes.small + height: width + + onClicked: { + searchField.text = "" + } + + Kirigami.Icon { + anchors.fill: parent + source: LayoutMirroring.enabled ? "edit-clear-rtl" : "edit-clear" + } + + Behavior on opacity { + OpacityAnimator { + duration: Kirigami.Units.longDuration + easing.type: Easing.InOutQuad + } + } + } + } + } + + Kirigami.Separator { + visible: !connectionView.atYBeginning + anchors { + left: parent.left + right: parent.right + top: parent.bottom + } + } + } + ListView { - anchors.fill: parent + id: connectionView clip: true model: editorProxyModel currentIndex: -1 boundsBehavior: Flickable.StopAtBounds + activeFocusOnTab: true + keyNavigationWraps: true + Accessible.role: Accessible.List + Keys.onTabPressed: { + if (applicationWindow().wideScreen && root.pageStack.depth > 1) { + connectionEditor.focus = true; + } + } section { property: "KcmConnectionType" delegate: Kirigami.AbstractListItem { supportsMouseEvents: false background: Rectangle { color: palette.window } QtControls.Label { id: headerLabel anchors.centerIn: parent font.weight: Font.DemiBold text: section } } } delegate: ConnectionItemDelegate { onAboutToChangeConnection: { - // Shouldn't be problem to set this in advance - scrollablePage.currentConnectionExportable = exportable - if (kcm.needsSave) { - confirmSaveDialog.connectionName = name - confirmSaveDialog.connectionPath = path - confirmSaveDialog.open() - } else { - scrollablePage.currentConnectionName = name - scrollablePage.currentConnectionPath = path - } +// // Shouldn't be problem to set this in advance +// connectionViewPage.currentConnectionExportable = exportable +// if (kcm.needsSave) { +// confirmSaveDialog.connectionName = name +// confirmSaveDialog.connectionPath = path +// confirmSaveDialog.open() +// } else { + connectionViewPage.currentConnectionName = name + connectionViewPage.currentConnectionPath = path + +// } } onAboutToExportConnection: { requestExportConnection(path) } onAboutToRemoveConnection: { deleteConfirmationDialog.connectionName = name deleteConfirmationDialog.connectionPath = path deleteConfirmationDialog.open() } } } + footer: Row { + layoutDirection: Qt.RightToLeft + spacing: Kirigami.Units.smallSpacing + padding: Kirigami.Units.smallSpacing + + QtControls.Button { + id: exportConnectionButton + + height: Kirigami.Units.iconSizes.medium + width: Kirigami.Units.iconSizes.medium + + enabled: connectionViewPage.currentConnectionExportable + icon.name: "document-export" + + QtControls.ToolTip.text: i18n("Export selected connection") + QtControls.ToolTip.visible: exportConnectionButton.hovered + + onClicked: { + kcm.requestExportConnection(connectionViewPage.currentConnectionPath) + } + } + + QtControls.Button { + id: removeConnectionButton + + height: Kirigami.Units.iconSizes.medium + width: Kirigami.Units.iconSizes.medium + + enabled: connectionViewPage.currentConnectionPath && connectionViewPage.currentConnectionPath.length + icon.name: "list-remove" + + QtControls.ToolTip.text: i18n("Remove selected connection") + QtControls.ToolTip.visible: removeConnectionButton.hovered + + onClicked: { + deleteConfirmationDialog.connectionName = connectionViewPage.currentConnectionName + deleteConfirmationDialog.connectionPath = connectionViewPage.currentConnectionPath + deleteConfirmationDialog.open() + } + } + + QtControls.Button { + id: addConnectionButton + + width: Kirigami.Units.iconSizes.medium + height: Kirigami.Units.iconSizes.medium + + icon.name: "list-add" + + QtControls.ToolTip.text: i18n("Add new connection") + QtControls.ToolTip.visible: addConnectionButton.hovered + + onClicked: { + addNewConnectionDialog.open() + } + } + } + onCurrentConnectionPathChanged: { - kcm.selectConnection(scrollablePage.currentConnectionPath) + if (currentConnectionPath) { + if (root.pageStack.depth < 2) { + root.pageStack.push(connectionEditor) + } else { + root.pageStack.currentIndex = 1 + } + kcm.selectConnection(connectionViewPage.currentConnectionPath) + } } } diff --git a/kcm/package/contents/ui/editor/ConnectionEditor.qml b/kcm/package/contents/ui/editor/ConnectionEditor.qml index db7db22e..b934126f 100644 --- a/kcm/package/contents/ui/editor/ConnectionEditor.qml +++ b/kcm/package/contents/ui/editor/ConnectionEditor.qml @@ -1,90 +1,160 @@ /* Copyright 2018 Jan Grulich This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ import QtQuick 2.6 import QtQuick.Layouts 1.3 import QtQuick.Controls 2.2 as QtControls -import org.kde.kirigami 2.0 // for units +import org.kde.kirigami 2.0 as Kirigami -// FIXME add horizontal scrollbar? -Item { - id: connectionEditorTab +Kirigami.ScrollablePage { + id: connectionEditorPage - QtControls.TextField { - id: connectionNameTextField + title: connectionNameTextField.text - anchors.top: parent.top + header: MouseArea { + width: connectionEditorPage.width + height: Math.round(Kirigami.Units.gridUnit * 2.5) + enabled: !applicationWindow().wideScreen - hoverEnabled: true - } - - QtControls.TabBar { - id: tabBar + Accessible.role: Accessible.Button + Accessible.name: i18n("Back") - width: parent.width - anchors { - top: connectionNameTextField.bottom - topMargin: Math.round(Units.gridUnit / 2) + onClicked: { + root.pageStack.currentIndex = 0 } - QtControls.TabButton { - text: i18n("Connection") + Item { + id: headerControls + anchors.fill: parent + + QtControls.ToolButton { + id: backButton + anchors.fill: parent + anchors.margins: Kirigami.Units.smallSpacing + visible: !applicationWindow().wideScreen + + onClicked: { + root.pageStack.currentIndex = 0 + } + + RowLayout { + anchors.fill: parent + + Kirigami.Icon { + id: toolButtonIcon + + Layout.alignment: Qt.AlignVCenter + Layout.preferredHeight: Kirigami.Units.iconSizes.small + Layout.preferredWidth: Layout.preferredHeight + + source: LayoutMirroring.enabled ? "go-next" : "go-previous" + } + + QtControls.Label { + Layout.fillWidth: true + Layout.fillHeight: true + + height: toolButtonIcon.height + text: connectionEditorPage.title + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + + //FIXME: QtControls bug, why? + Component.onCompleted: { + font.bold = true + } + } + } + } + + QtControls.Label { + anchors.verticalCenter: parent.verticalCenter + x: y + + text: connectionEditorPage.title + elide: Text.ElideRight + visible: !backButton.visible + opacity: 0.3 + //FIXME: QtControls bug, why? + Component.onCompleted: { + font.bold = true + } + } } - // FIXME just placeholders for now - QtControls.TabButton { - text: i18n("Wireless") + Kirigami.Separator { + anchors { + left: parent.left + right: parent.right + top: parent.bottom + } + visible: !connectionEditorPage.atYBeginning } + } - QtControls.TabButton { - text: i18n("Wireless security") - } + ColumnLayout { + width: connectionEditorPage.width - QtControls.TabButton { - text: i18n("IPv4") - } + QtControls.TextField { + id: connectionNameTextField + + Layout.fillWidth: true - QtControls.TabButton { - text: i18n("IPv6") + hoverEnabled: true } - } - StackLayout { - anchors { - horizontalCenter: parent.horizontalCenter - top: tabBar.bottom - topMargin: Math.round(Units.gridUnit / 2) + QtControls.TabBar { + id: tabBar + + Layout.fillWidth: true + + QtControls.TabButton { + text: i18n("Connection") + } + + // FIXME just placeholders for now + QtControls.TabButton { + text: i18n("Wireless") + } + + QtControls.TabButton { + text: i18n("IP") + } } - currentIndex: tabBar.currentIndex + StackLayout { + Layout.fillWidth: true + + currentIndex: tabBar.currentIndex - ConnectionSetting { - id: connectionSetting + ConnectionSetting { + id: connectionSetting + } } } function loadConnectionSettings() { connectionNameTextField.text = connectionSettingsObject.id // Load general connection setting connectionSetting.loadSettings() } } diff --git a/kcm/package/contents/ui/main.qml b/kcm/package/contents/ui/main.qml index fdb9b5ec..cb8a8726 100755 --- a/kcm/package/contents/ui/main.qml +++ b/kcm/package/contents/ui/main.qml @@ -1,258 +1,259 @@ /* Copyright 2016-2018 Jan Grulich This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ import "editor" import QtQuick 2.6 import QtQuick.Dialogs 1.1 import QtQuick.Layouts 1.2 import QtQuick.Controls 2.5 as QQC2 import org.kde.kcm 1.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.extras 2.0 as PlasmaExtras import org.kde.plasma.networkmanagement 0.2 as PlasmaNM import org.kde.kirigami 2.9 as Kirigami Kirigami.ApplicationItem { id: root property QtObject connectionSettingsObject: kcm.connectionSettings - implicitWidth: Kirigami.Units.gridUnit * 20 - implicitHeight: Kirigami.Units.gridUnit * 20 + LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft + LayoutMirroring.childrenInherit: true + + wideScreen: width >= Kirigami.Units.gridUnit * 50 + + pageStack.defaultColumnWidth: Kirigami.Units.gridUnit * 25 + pageStack.initialPage: connectionView SystemPalette { id: palette colorGroup: SystemPalette.Active } PlasmaNM.Handler { id: handler } PlasmaNM.KcmIdentityModel { id: connectionModel } PlasmaNM.EditorProxyModel { id: editorProxyModel sourceModel: connectionModel } PlasmaNM.Configuration { id: configuration } RowLayout { anchors.fill: parent spacing: units.smallSpacing ColumnLayout { spacing: units.smallSpacing ConnectionView { id: connectionView Layout.fillWidth: true Layout.fillHeight: true Layout.minimumWidth: 300 Layout.preferredWidth: 400 } RowLayout { spacing: units.smallSpacing QQC2.TextField { id: searchField Layout.fillWidth: true placeholderText: i18n("Search...") onTextChanged: { editorProxyModel.setFilterRegExp(text) } } QQC2.ToolButton { id: addConnectionButton width: Kirigami.Units.iconSizes.medium * 3 height: Kirigami.Units.iconSizes.medium icon.name: "list-add" QQC2.ToolTip.text: i18n("Add new connection") QQC2.ToolTip.visible: hovered onClicked: { addNewConnectionDialog.open() } } QQC2.ToolButton { id: removeConnectionButton height: Kirigami.Units.iconSizes.medium width: Kirigami.Units.iconSizes.medium enabled: connectionView.currentConnectionPath && connectionView.currentConnectionPath.length icon.name: "list-remove" QQC2.ToolTip.text: i18n("Remove selected connection") QQC2.ToolTip.visible: hovered onClicked: { deleteConfirmationDialog.connectionName = connectionView.currentConnectionName deleteConfirmationDialog.connectionPath = connectionView.currentConnectionPath deleteConfirmationDialog.open() } } QQC2.ToolButton { id: exportConnectionButton height: Kirigami.Units.iconSizes.medium width: Kirigami.Units.iconSizes.medium enabled: connectionView.currentConnectionExportable icon.name: "document-export" QQC2.ToolTip.text: i18n("Export selected connection") QQC2.ToolTip.visible: hovered onClicked: { kcm.requestExportConnection(connectionView.currentConnectionPath) } } } } + } - ConnectionEditor { - id: connectionEditor - - Layout.fillWidth: true - Layout.fillHeight: true - Layout.minimumWidth: childrenRect.width - } + ConnectionEditor { + id: connectionEditor } Row { id: leftButtonRow anchors { bottom: parent.bottom left: parent.left margins: units.smallSpacing } spacing: units.smallSpacing QQC2.ToolButton { id: configureButton icon.name: "configure" QQC2.ToolTip.text: i18n("Configuration") QQC2.ToolTip.visible: hovered onClicked: { configurationDialog.open() } } } MessageDialog { id: deleteConfirmationDialog property string connectionName property string connectionPath icon: StandardIcon.Question standardButtons: StandardButton.Ok | StandardButton.Cancel title: i18nc("@title:window", "Remove Connection") text: i18n("Do you want to remove the connection '%1'?", connectionName) onAccepted: { if (connectionPath == connectionView.currentConnectionPath) { // Deselect now non-existing connection deselectConnectionsInView() } handler.removeConnection(connectionPath) } } MessageDialog { id: deleteConfirmationDialog property string connectionName property string connectionPath /* Like QString::toHtmlEscaped */ function toHtmlEscaped(s) { return s.replace(/[&<>]/g, function (tag) { return { '&': '&', '<': '<', '>': '>' }[tag] || tag }); } icon: StandardIcon.Question standardButtons: StandardButton.Ok | StandardButton.Cancel title: i18nc("@title:window", "Remove Connection") text: i18n("Do you want to remove the connection '%1'?", toHtmlEscaped(connectionName)) onAccepted: { if (connectionPath == connectionView.currentConnectionPath) { // Deselect now non-existing connection deselectConnections() } handler.removeConnection(connectionPath) } } AddConnectionDialog { id: addNewConnectionDialog onRequestCreateConnection: { root.requestCreateConnection(type, vpnType, specificType, shared) } } ConfigurationDialog { id: configurationDialog } function loadConnectionSetting() { connectionEditor.loadConnectionSettings() } function deselectConnectionsInView() { connectionView.currentConnectionPath = "" } function selectConnectionInView(connectionName, connectionPath) { connectionView.currentConnectionName = connectionName connectionView.currentConnectionPath = connectionPath } }