Commit ccc8a09d authored by Nicolas Fella's avatar Nicolas Fella
Browse files

Redesign Autostart KCM

This is a redesign of the Autostart KCM according to today's standards.

I made a few choices that might be controversial:
- It is not possible to disable applications any more without removing them from the list
- It is not possible any more to control whether something is only autostarted in Plasma
- Scripts are now always symlinked, there is no option to copy them any more
- It is not possible any more to add scripts to ~/.config/plasma-workspace/env. Existing scripts in this directory still show up, in the same category as the other autostart scripts
parent a50ad621
Stephen Leaf <smileaf@gmail.com>
Montel Laurent <montel@kde.org>
Nicolas Fella <nicolas.fella@gmx.de>
......@@ -2,19 +2,15 @@
add_definitions(-DTRANSLATION_DOMAIN=\"kcm_autostart\")
set(kcm_autostart_PART_SRCS
autostartitem.cpp
addscriptdialog.cpp
advanceddialog.cpp
autostartmodel.cpp
autostart.cpp )
ki18n_wrap_ui(kcm_autostart_PART_SRCS autostartconfig.ui )
add_library(kcm_autostart MODULE ${kcm_autostart_PART_SRCS})
target_link_libraries(kcm_autostart KF5::KCMUtils KF5::I18n KF5::KIOCore KF5::KIOWidgets)
target_link_libraries(kcm_autostart KF5::I18n KF5::KIOCore KF5::KIOWidgets KF5::QuickAddons)
########### install files ###############
kcoreaddons_desktop_to_json(kcm_autostart "package/metadata.desktop")
install(TARGETS kcm_autostart DESTINATION ${KDE_INSTALL_PLUGINDIR} )
install( FILES autostart.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} )
install(TARGETS kcm_autostart DESTINATION ${KDE_INSTALL_PLUGINDIR}/kcms )
install(FILES package/metadata.desktop RENAME autostart.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR})
kpackage_install_package(package kcm_autostart kcms)
/***************************************************************************
* Copyright (C) 2007 by Stephen Leaf *
* smileaf@gmail.com *
* Copyright (C) 2008 by Montel Laurent <montel@kde.org> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program 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 General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA *
***************************************************************************/
#include "addscriptdialog.h"
#include <QCheckBox>
#include <QVBoxLayout>
#include <QLabel>
#include <QFileInfo>
#include <KLineEdit>
#include <KLocalizedString>
#include <KUrlRequester>
#include <KShell>
#include <KMessageBox>
AddScriptDialog::AddScriptDialog (QWidget* parent)
: QDialog( parent )
{
QVBoxLayout *lay= new QVBoxLayout;
setLayout( lay );
QLabel *lab = new QLabel( i18n( "Shell script path:" ), this );
lay->addWidget( lab );
m_url = new KUrlRequester( this );
lay->addWidget( m_url );
m_symlink = new QCheckBox( i18n( "Create as symlink" ), this ); //TODO fix text
m_symlink->setChecked( true );
lay->addWidget( m_symlink );
connect( m_url->lineEdit(), &QLineEdit::textChanged, this, &AddScriptDialog::textChanged );
m_url->lineEdit()->setFocus();
m_buttons = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel, this);
m_buttons->button(QDialogButtonBox::Ok)->setEnabled(false);
lay->addWidget(m_buttons);
connect(m_buttons, &QDialogButtonBox::accepted, this, &AddScriptDialog::accept);
connect(m_buttons, &QDialogButtonBox::rejected, this, &AddScriptDialog::reject);
}
AddScriptDialog::~AddScriptDialog()
{
}
void AddScriptDialog::textChanged(const QString &text)
{
m_buttons->button(QDialogButtonBox::Ok)->setEnabled(!text.isEmpty());
}
void AddScriptDialog::accept()
{
if ( doBasicSanityCheck() )
QDialog::accept();
}
bool AddScriptDialog::doBasicSanityCheck()
{
const QString& path = KShell::tildeExpand(m_url->text());
QFileInfo file(path);
if ( ! file.isAbsolute() ) {
KMessageBox::sorry( nullptr, i18n("\"%1\" is not an absolute path.", path) );
return false;
} else if ( ! file.exists() ) {
KMessageBox::sorry( nullptr, i18n("\"%1\" does not exist.", path) );
return false;
} else if ( !file.isFile() ) {
KMessageBox::sorry( nullptr, i18n("\"%1\" is not a file.", path) );
return false;
} else if ( ! file.isReadable() ) {
KMessageBox::sorry( nullptr, i18n("\"%1\" is not readable.", path) );
return false;
}
return true;
}
QUrl AddScriptDialog::importUrl() const
{
return m_url->url();
}
bool AddScriptDialog::symLink() const
{
return m_symlink->isChecked();
}
/***************************************************************************
* Copyright (C) 2007 by Stephen Leaf *
* smileaf@gmail.com *
* Copyright (C) 2008 by Montel Laurent <montel@kde.org> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program 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 General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA *
***************************************************************************/
#ifndef ADDSCRIPTDIALOG_H
#define ADDSCRIPTDIALOG_H
#include <QDialog>
#include <QUrl>
class QDialogButtonBox;
class KUrlRequester;
class QCheckBox;
class AddScriptDialog : public QDialog
{
Q_OBJECT
public:
explicit AddScriptDialog(QWidget* parent=nullptr);
~AddScriptDialog() override;
// Returns the Url of the script to be imported
QUrl importUrl() const;
bool symLink() const;
public Q_SLOTS:
// reimplemented
void accept() override;
protected:
virtual bool doBasicSanityCheck();
private Q_SLOTS:
void textChanged(const QString &text);
private:
KUrlRequester *m_url;
QCheckBox* m_symlink;
QDialogButtonBox* m_buttons;
};
#endif
/***************************************************************************
* Copyright (C) 2008 by Montel Laurent <montel@kde.org> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program 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 General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA *
***************************************************************************/
#include "advanceddialog.h"
#include <QCheckBox>
#include <QVBoxLayout>
#include <QDialogButtonBox>
#include <KLocalizedString>
AdvancedDialog::AdvancedDialog( QWidget *parent, bool status )
: QDialog( parent )
{
QVBoxLayout *lay= new QVBoxLayout(this);
m_onlyInKde = new QCheckBox( i18n( "Autostart only in Plasma" ), this );
m_onlyInKde->setChecked( status );
lay->addWidget( m_onlyInKde );
QDialogButtonBox* buttons = new QDialogButtonBox(this);
buttons->setStandardButtons( QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
lay->addWidget(buttons);
setLayout( lay );
connect(buttons, &QDialogButtonBox::accepted, this, &AdvancedDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, this, &AdvancedDialog::reject);
}
AdvancedDialog::~AdvancedDialog()
{
}
bool AdvancedDialog::onlyInKde() const
{
return m_onlyInKde->isChecked();
}
/***************************************************************************
* Copyright (C) 2008 by Montel Laurent <montel@kde.org> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program 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 General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA *
***************************************************************************/
#ifndef ADVANCEDDIALOG_H
#define ADVANCEDDIALOG_H
#include <QDialog>
class QCheckBox;
class AdvancedDialog : public QDialog
{
Q_OBJECT
public:
explicit AdvancedDialog(QWidget* parent, bool status);
~AdvancedDialog() override;
bool onlyInKde() const;
private:
QCheckBox *m_onlyInKde;
};
#endif
......@@ -18,337 +18,41 @@
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA *
***************************************************************************/
#include "autostart.h"
#include "autostartitem.h"
#include "addscriptdialog.h"
#include "advanceddialog.h"
#include "autostartmodel.h"
#include <QDir>
#include <QTreeWidget>
#include <QStringList>
#include <KConfig>
#include <KConfigGroup>
#include <KShell>
#include <KOpenWithDialog>
#include <KPropertiesDialog>
#include <KDesktopFile>
#include <KMessageBox>
#include <KAboutData>
#include <KLocalizedString>
#include <KIO/DeleteJob>
#include <KIO/CopyJob>
#include <KShell>
#include <QDebug>
K_PLUGIN_FACTORY(AutostartFactory, registerPlugin<Autostart>();)
K_PLUGIN_CLASS_WITH_JSON(Autostart, "metadata.json")
Autostart::Autostart( QWidget* parent, const QVariantList& )
: KCModule(parent )
Autostart::Autostart(QObject *parent, const QVariantList &)
: KQuickAddons::ConfigModule(parent)
, m_model(new AutostartModel(this))
{
widget = new Ui_AutostartConfig();
widget->setupUi(this);
QStringList lstHeader;
lstHeader << i18n( "Name" )
<< i18n( "Command" )
<< i18n( "Status" )
<< i18nc("@title:column The name of the column that decides if the program is run on session startup, on session shutdown, etc", "Run On" );
widget->listCMD->setHeaderLabels(lstHeader);
widget->listCMD->setFocus();
setButtons(Help);
connect( widget->btnProperties, SIGNAL(clicked()), SLOT(slotEditCMD()) );
connect( widget->listCMD, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), SLOT(slotEditCMD(QTreeWidgetItem*)) );
connect(widget->btnAddScript, &QPushButton::clicked, this, &Autostart::slotAddScript);
connect(widget->btnAddProgram, &QPushButton::clicked, this, &Autostart::slotAddProgram);
connect(widget->btnRemove, &QPushButton::clicked, this, &Autostart::slotRemoveCMD);
connect(widget->btnAdvanced, &QPushButton::clicked, this, &Autostart::slotAdvanced);
connect(widget->listCMD, &QTreeWidget::itemClicked, this, &Autostart::slotItemClicked);
connect(widget->listCMD, &QTreeWidget::itemSelectionChanged, this, &Autostart::slotSelectionChanged);
qmlRegisterUncreatableType<AutostartModel>("org.kde.plasma.kcm.autostart", 1, 0, "AutostartModel", QStringLiteral("Only for enums"));
connect(m_model, &QAbstractItemModel::rowsInserted, this, &Autostart::slotRowInserted);
connect(m_model, &QAbstractItemModel::dataChanged, this, &Autostart::slotDatachanged);
KAboutData* about = new KAboutData(QStringLiteral("Autostart"),
i18n("Session Autostart Manager"),
QStringLiteral("1.0"),
i18n("Session Autostart Manager Control Panel Module"),
KAboutLicense::GPL,
i18n("Copyright © 2006–2010 Autostart Manager team"));
KAboutData *about =
new KAboutData(QStringLiteral("kcm_autostart"), i18n("Autostart"), QStringLiteral("1.0"), i18n("Session Autostart Manager Control Panel Module"), KAboutLicense::GPL, i18n("Copyright © 2006–2020 Autostart Manager team"));
about->addAuthor(i18n("Stephen Leaf"), QString(), QStringLiteral("smileaf@gmail.com"));
about->addAuthor(i18n("Montel Laurent"), i18n( "Maintainer" ), QStringLiteral("montel@kde.org"));
setAboutData( about );
about->addAuthor(i18n("Montel Laurent"), i18n("Maintainer"), QStringLiteral("montel@kde.org"));
about->addAuthor(i18n("Nicolas Fella"), QString(), QStringLiteral("nicolas.fella@gmx.de"));
setAboutData(about);
}
Autostart::~Autostart()
{
delete widget;
}
void Autostart::slotItemClicked( QTreeWidgetItem *item, int col)
AutostartModel *Autostart::model() const
{
if ( item && col == COL_STATUS ) {
DesktopStartItem *entry = dynamic_cast<DesktopStartItem*>( item );
if (entry) {
const bool enabled = ( item->checkState( col ) == Qt::Checked );
m_model->setData(indexFromWidget(item), enabled, AutostartModel::Roles::Enabled);
if (enabled) {
item->setText( COL_STATUS, i18nc( "The program will be run", "Enabled" ) );
} else {
item->setText( COL_STATUS, i18nc( "The program won't be run", "Disabled" ) );
}
}
}
}
void Autostart::updateDesktopStartItem(DesktopStartItem *item, const QString &name, const QString &command, bool disabled, const QString &fileName)
{
Q_ASSERT( item );
item->setText( COL_NAME, name );
item->setToolTip(COL_NAME, KShell::tildeCollapse(fileName));
item->setText( COL_RUN, AutostartModel::listPathName()[0] /* Startup */ );
item->setText( COL_COMMAND, command );
item->setCheckState( COL_STATUS, disabled ? Qt::Unchecked : Qt::Checked );
item->setText( COL_STATUS, disabled ? i18nc( "The program won't be run", "Disabled" ) : i18nc( "The program will be run", "Enabled" ));
}
void Autostart::updateScriptStartItem(ScriptStartItem *item, const QString &name, const QString &command, AutostartEntrySource type, const QString &fileName)
{
Q_ASSERT( item );
item->setText( COL_NAME, name );
item->setToolTip(COL_NAME, KShell::tildeCollapse(fileName));
item->setText( COL_COMMAND, command );
item->changeStartup( type );
return m_model;
}
void Autostart::load()
{
widget->listCMD->clear();
m_programItem = new QTreeWidgetItem( widget->listCMD );
m_programItem->setText( 0, i18n( "Desktop File" ));
m_programItem->setFlags(m_programItem->flags()^Qt::ItemIsSelectable );
QFont boldFont = m_programItem->font(0);
boldFont.setBold( true );
m_programItem->setData ( 0, Qt::FontRole, boldFont );
m_scriptItem = new QTreeWidgetItem( widget->listCMD );
m_scriptItem->setText( 0, i18n( "Script File" ));
m_scriptItem->setFlags(m_scriptItem->flags()^Qt::ItemIsSelectable);
m_scriptItem->setData ( 0, Qt::FontRole, boldFont);
widget->listCMD->expandItem( m_programItem );
widget->listCMD->expandItem( m_scriptItem );
m_model->load();
for (int i = 0; i < m_model->rowCount(); i++) {
slotRowInserted(QModelIndex(), i, i);
}
//Update button
slotSelectionChanged();
widget->listCMD->resizeColumnToContents(COL_NAME);
widget->listCMD->resizeColumnToContents(COL_STATUS);
widget->listCMD->resizeColumnToContents(COL_RUN);
}
void Autostart::slotAddProgram()
{
KOpenWithDialog *owdlg = new KOpenWithDialog(this);
connect(owdlg, &QDialog::finished, this, [this, owdlg] (int result) {
if (result == QDialog::Accepted) {
KService::Ptr service = owdlg->service();
Q_ASSERT(service);
if (!service) {
return; // Don't crash if KOpenWith wasn't able to create service.
}
m_model->addEntry(service);
}
});
owdlg->open();
}
void Autostart::slotAddScript()
{
AddScriptDialog *addDialog = new AddScriptDialog(this);
connect(addDialog, &QDialog::finished, this, [this, addDialog] (int result) {
if (result == QDialog::Accepted) {
m_model->addEntry(addDialog->importUrl(), addDialog->symLink());
}
});
addDialog->open();
}
void Autostart::slotRemoveCMD()
{
QTreeWidgetItem *widgetItem = widget->listCMD->currentItem();
if (!widgetItem) {
return;
}
if (m_model->removeEntry(indexFromWidget(widgetItem))) {
if (m_scriptItem->indexOfChild(widgetItem) != -1) {
m_scriptItem->removeChild(widgetItem);
} else {
m_programItem->removeChild(widgetItem);
}
delete widgetItem;
}
}
void Autostart::slotRowInserted(const QModelIndex &parent, int first, int last)
{
Q_ASSERT(!parent.isValid());
for (int i = first; i <= last; i++) {
QModelIndex idx = m_model->index(i, 0);
const QString &name = m_model->data(idx, Qt::DisplayRole).toString();
const QString &fileName = m_model->data(idx, AutostartModel::Roles::FileName).toString();
const AutostartEntrySource source = AutostartModel::sourceFromInt(m_model->data(idx, AutostartModel::Roles::Source).toInt());
const QString &command = KShell::tildeCollapse(m_model->data(idx, AutostartModel::Roles::Command).toString());
if (source == AutostartEntrySource::XdgAutoStart) {
const bool enabled = m_model->data(idx, AutostartModel::Roles::Enabled).toBool();
DesktopStartItem *item = new DesktopStartItem(m_programItem);
updateDesktopStartItem(item, name, command, !enabled, fileName);
} else {
ScriptStartItem *item = new ScriptStartItem(m_scriptItem, this);
updateScriptStartItem(item, name, command, source, fileName);
}
}
}
void Autostart::slotDatachanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
{
Q_UNUSED(roles)
for (int row = topLeft.row(); row <= bottomRight.row(); row++) {
QModelIndex idx = m_model->index(row);
const QString &name = m_model->data(idx, Qt::DisplayRole).toString();
const QString &fileName = m_model->data(idx, AutostartModel::Roles::FileName).toString();
const AutostartEntrySource source = AutostartModel::sourceFromInt(m_model->data(idx, AutostartModel::Roles::Source).toInt());
const QString &command = KShell::tildeCollapse(m_model->data(idx, AutostartModel::Roles::Command).toString());
if (row > (m_programItem->childCount() -1)) {
// scriptItem
QTreeWidgetItem *item = m_scriptItem->child(row - m_programItem->childCount());
ScriptStartItem *scriptEntry = dynamic_cast<ScriptStartItem*>(item);
updateScriptStartItem(scriptEntry, name, command, source, fileName);
} else {
// desktopItem
const bool enabled = m_model->data(idx, AutostartModel::Roles::Enabled).toBool();
QTreeWidgetItem *item = m_programItem->child(row);
DesktopStartItem *desktopItem = dynamic_cast<DesktopStartItem*>(item);
updateDesktopStartItem(desktopItem, name, command, !enabled, fileName);
}
}
}
void Autostart::slotEditCMD(QTreeWidgetItem* ent)
{
if (!ent) return;
DesktopStartItem *desktopItem = dynamic_cast<DesktopStartItem*>( ent );
if (desktopItem) {
const QModelIndex index = indexFromWidget(ent);
const QString fileName = m_model->data(index, AutostartModel::Roles::FileName).toString();
KFileItem kfi(QUrl::fromLocalFile(fileName));
kfi.setDelayedMimeTypes(true);
KPropertiesDialog *dlg = new KPropertiesDialog(kfi, this);
connect(dlg, &QDialog::finished, this, [this, index, fileName, desktopItem, dlg] (int result) {
if (result == QDialog::Accepted) {
// Entry may have change of file
m_model->reloadEntry(index, dlg->item().localPath());
const QString name = m_model->data(index, Qt::DisplayRole).toString();
const QString command = m_model->data(index, AutostartModel::Roles::Command).toString();
const bool enabled = m_model->data(index, AutostartModel::Roles::Enabled).toBool();
updateDesktopStartItem( desktopItem, name, command, !enabled, fileName);
}
});
dlg->open();
}
}
void Autostart::slotEditCMD()
{
if (widget->listCMD->currentItem() == nullptr) {
return;
}
slotEditCMD(dynamic_cast<AutoStartItem*>(widget->listCMD->currentItem()));
}
void Autostart::slotAdvanced()
{
if (widget->listCMD->currentItem() == nullptr) {
return;
}
const QModelIndex index = indexFromWidget(widget->listCMD->currentItem());
const bool onlyInPlasma = m_model->data(index, AutostartModel::Roles::OnlyInPlasma).toBool();
AdvancedDialog *dlg = new AdvancedDialog(this, onlyInPlasma);
connect(dlg, &QDialog::finished, this, [this, index, dlg] (int result) {
if (result == QDialog::Accepted) {
const bool dialogOnlyInKde = dlg->onlyInKde();
m_model->setData(index, dialogOnlyInKde, AutostartModel::Roles::OnlyInPlasma);
};
});