mirror of
https://bitbucket.org/smil3y/kdelibs.git
synced 2025-02-23 18:32:49 +00:00
974 lines
31 KiB
C++
974 lines
31 KiB
C++
/* This file is part of the KDE libraries
|
|
|
|
Copyright (C) 1997 Torben Weis <weis@stud.uni-frankfurt.de>
|
|
Copyright (C) 1999 Dirk Mueller <mueller@kde.org>
|
|
Portions copyright (C) 1999 Preston Brown <pbrown@kde.org>
|
|
Copyright (C) 2007 Pino Toscano <pino@kde.org>
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Library General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2 of the License, or (at your option) any later version.
|
|
|
|
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
|
|
Library General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Library General Public License
|
|
along with this library; see the file COPYING.LIB. If not, write to
|
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "kopenwithdialog.h"
|
|
#include "kopenwithdialog_p.h"
|
|
|
|
#include <QtCore/qalgorithms.h>
|
|
#include <QtCore/QList>
|
|
#include <QtGui/QLabel>
|
|
#include <QtGui/QLayout>
|
|
#include <QtGui/QCheckBox>
|
|
#include <QtGui/QStyle>
|
|
#include <QtGui/qstyleoption.h>
|
|
|
|
#include <khistorycombobox.h>
|
|
#include <kdesktopfile.h>
|
|
#include <klineedit.h>
|
|
#include <klocale.h>
|
|
#include <kmessagebox.h>
|
|
#include <kshell.h>
|
|
#include <krun.h>
|
|
#include <kstandarddirs.h>
|
|
#include <kstringhandler.h>
|
|
#include <kurlcompletion.h>
|
|
#include <kurlrequester.h>
|
|
#include <kmimetype.h>
|
|
#include <kservicegroup.h>
|
|
#include <kserviceoffer.h>
|
|
#include <kbuildsycocaprogressdialog.h>
|
|
#include <kconfiggroup.h>
|
|
#include <kdebug.h>
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
|
|
static QString simplifiedExecLineFromService(const QString& fullExec)
|
|
{
|
|
QStringList args = KShell::splitArgs(fullExec);
|
|
if (args.isEmpty()) {
|
|
return QString();
|
|
}
|
|
return args.first().simplified();
|
|
}
|
|
|
|
namespace KDEPrivate {
|
|
|
|
class AppNode
|
|
{
|
|
public:
|
|
AppNode()
|
|
: isDir(false), parent(nullptr), fetched(false)
|
|
{
|
|
}
|
|
~AppNode()
|
|
{
|
|
qDeleteAll(children);
|
|
}
|
|
|
|
QString icon;
|
|
QString text;
|
|
QString entryPath;
|
|
QString exec;
|
|
bool isDir;
|
|
|
|
AppNode *parent;
|
|
bool fetched;
|
|
|
|
QList<AppNode*> children;
|
|
};
|
|
|
|
bool AppNodeLessThan(KDEPrivate::AppNode *n1, KDEPrivate::AppNode *n2)
|
|
{
|
|
if (n1->isDir) {
|
|
if (n2->isDir) {
|
|
return (n1->text.compare(n2->text, Qt::CaseInsensitive) < 0);
|
|
}
|
|
return true;
|
|
}
|
|
if (n2->isDir) {
|
|
return false;
|
|
}
|
|
return (n1->text.compare(n2->text, Qt::CaseInsensitive) < 0);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
class KApplicationModelPrivate
|
|
{
|
|
public:
|
|
KApplicationModelPrivate(KApplicationModel *qq)
|
|
: q(qq), root(new KDEPrivate::AppNode())
|
|
{
|
|
}
|
|
~KApplicationModelPrivate()
|
|
{
|
|
delete root;
|
|
}
|
|
|
|
void fillNode(const QString &entryPath, KDEPrivate::AppNode *node);
|
|
|
|
KApplicationModel *q;
|
|
|
|
KDEPrivate::AppNode *root;
|
|
};
|
|
|
|
void KApplicationModelPrivate::fillNode(const QString &_entryPath, KDEPrivate::AppNode *node)
|
|
{
|
|
KServiceGroup::Ptr root = KServiceGroup::group(_entryPath);
|
|
if (!root || !root->isValid()) {
|
|
return;
|
|
}
|
|
|
|
const KServiceGroup::List list = root->entries();
|
|
for( KServiceGroup::List::ConstIterator it = list.begin(); it != list.end(); ++it) {
|
|
QString icon;
|
|
QString text;
|
|
QString entryPath;
|
|
QString exec;
|
|
bool isDir = false;
|
|
const KSycocaEntry::Ptr p = (*it);
|
|
if (p->isType(KST_KService)) {
|
|
const KService::Ptr service = KService::Ptr::staticCast(p);
|
|
if (service->noDisplay()) {
|
|
continue;
|
|
}
|
|
|
|
icon = service->icon();
|
|
text = service->name();
|
|
exec = service->exec();
|
|
entryPath = service->entryPath();
|
|
} else if (p->isType(KST_KServiceGroup)) {
|
|
const KServiceGroup::Ptr serviceGroup = KServiceGroup::Ptr::staticCast(p);
|
|
if (serviceGroup->noDisplay() || serviceGroup->childCount() == 0) {
|
|
continue;
|
|
}
|
|
|
|
icon = serviceGroup->icon();
|
|
text = serviceGroup->caption();
|
|
entryPath = serviceGroup->entryPath();
|
|
isDir = true;
|
|
} else {
|
|
kWarning() << "KServiceGroup: Unexpected object in list!";
|
|
continue;
|
|
}
|
|
|
|
KDEPrivate::AppNode *newnode = new KDEPrivate::AppNode();
|
|
newnode->icon = icon;
|
|
newnode->text = text;
|
|
newnode->entryPath = entryPath;
|
|
newnode->exec = exec;
|
|
newnode->isDir = isDir;
|
|
newnode->parent = node;
|
|
node->children.append(newnode);
|
|
}
|
|
qStableSort(node->children.begin(), node->children.end(), KDEPrivate::AppNodeLessThan);
|
|
}
|
|
|
|
|
|
|
|
KApplicationModel::KApplicationModel(QObject *parent)
|
|
: QAbstractItemModel(parent),
|
|
d(new KApplicationModelPrivate(this))
|
|
{
|
|
d->fillNode(QString(), d->root);
|
|
}
|
|
|
|
KApplicationModel::~KApplicationModel()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
bool KApplicationModel::canFetchMore(const QModelIndex &parent) const
|
|
{
|
|
if (!parent.isValid()) {
|
|
return false;
|
|
}
|
|
KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(parent.internalPointer());
|
|
return node->isDir && !node->fetched;
|
|
}
|
|
|
|
int KApplicationModel::columnCount(const QModelIndex &parent) const
|
|
{
|
|
Q_UNUSED(parent)
|
|
return 1;
|
|
}
|
|
|
|
QVariant KApplicationModel::data(const QModelIndex &index, int role) const
|
|
{
|
|
if (!index.isValid()) {
|
|
return QVariant();
|
|
}
|
|
KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(index.internalPointer());
|
|
switch (role) {
|
|
case Qt::DisplayRole: {
|
|
return node->text;
|
|
}
|
|
case Qt::DecorationRole: {
|
|
if (!node->icon.isEmpty()) {
|
|
return KIcon(node->icon);
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
break;
|
|
}
|
|
}
|
|
return QVariant();
|
|
}
|
|
|
|
void KApplicationModel::fetchMore(const QModelIndex &parent)
|
|
{
|
|
if (!parent.isValid()) {
|
|
return;
|
|
}
|
|
KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(parent.internalPointer());
|
|
if (!node->isDir) {
|
|
return;
|
|
}
|
|
emit layoutAboutToBeChanged();
|
|
d->fillNode(node->entryPath, node);
|
|
node->fetched = true;
|
|
emit layoutChanged();
|
|
}
|
|
|
|
bool KApplicationModel::hasChildren(const QModelIndex &parent) const
|
|
{
|
|
if (!parent.isValid()) {
|
|
return true;
|
|
}
|
|
KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(parent.internalPointer());
|
|
return node->isDir;
|
|
}
|
|
|
|
QVariant KApplicationModel::headerData(int section, Qt::Orientation orientation, int role) const
|
|
{
|
|
if (orientation != Qt::Horizontal || section != 0) {
|
|
return QVariant();
|
|
}
|
|
switch (role) {
|
|
case Qt::DisplayRole: {
|
|
return i18n("Known Applications");
|
|
}
|
|
default: {
|
|
return QVariant();
|
|
}
|
|
}
|
|
}
|
|
|
|
QModelIndex KApplicationModel::index(int row, int column, const QModelIndex &parent) const
|
|
{
|
|
if (row < 0 || column != 0) {
|
|
return QModelIndex();
|
|
}
|
|
KDEPrivate::AppNode *node = d->root;
|
|
if (parent.isValid()) {
|
|
node = static_cast<KDEPrivate::AppNode*>(parent.internalPointer());
|
|
}
|
|
if (row >= node->children.count()) {
|
|
return QModelIndex();
|
|
}
|
|
return createIndex(row, 0, node->children.at(row));
|
|
}
|
|
|
|
QModelIndex KApplicationModel::parent(const QModelIndex &index) const
|
|
{
|
|
if (!index.isValid()) {
|
|
return QModelIndex();
|
|
}
|
|
KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(index.internalPointer());
|
|
if (node->parent->parent) {
|
|
int id = node->parent->parent->children.indexOf(node->parent);
|
|
if (id >= 0 && id < node->parent->parent->children.count()) {
|
|
return createIndex(id, 0, node->parent);
|
|
}
|
|
return QModelIndex();
|
|
}
|
|
return QModelIndex();
|
|
}
|
|
|
|
int KApplicationModel::rowCount(const QModelIndex &parent) const
|
|
{
|
|
if (!parent.isValid()) {
|
|
return d->root->children.count();
|
|
}
|
|
KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(parent.internalPointer());
|
|
return node->children.count();
|
|
}
|
|
|
|
QString KApplicationModel::entryPathFor(const QModelIndex &index) const
|
|
{
|
|
if (!index.isValid()) {
|
|
return QString();
|
|
}
|
|
KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(index.internalPointer());
|
|
return node->entryPath;
|
|
}
|
|
|
|
QString KApplicationModel::execFor(const QModelIndex &index) const
|
|
{
|
|
if (!index.isValid()) {
|
|
return QString();
|
|
}
|
|
KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(index.internalPointer());
|
|
return node->exec;
|
|
}
|
|
|
|
bool KApplicationModel::isDirectory(const QModelIndex &index) const
|
|
{
|
|
if (!index.isValid()) {
|
|
return false;
|
|
}
|
|
KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(index.internalPointer());
|
|
return node->isDir;
|
|
}
|
|
|
|
class KApplicationViewPrivate
|
|
{
|
|
public:
|
|
KApplicationViewPrivate()
|
|
: appModel(nullptr)
|
|
{
|
|
}
|
|
|
|
KApplicationModel *appModel;
|
|
};
|
|
|
|
KApplicationView::KApplicationView(QWidget *parent)
|
|
: QTreeView(parent), d(new KApplicationViewPrivate)
|
|
{
|
|
}
|
|
|
|
KApplicationView::~KApplicationView()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
void KApplicationView::setModel(QAbstractItemModel *model)
|
|
{
|
|
if (d->appModel) {
|
|
disconnect(
|
|
selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
|
|
this, SLOT(slotSelectionChanged(QItemSelection,QItemSelection))
|
|
);
|
|
}
|
|
|
|
QTreeView::setModel(model);
|
|
|
|
d->appModel = qobject_cast<KApplicationModel*>(model);
|
|
if (d->appModel) {
|
|
connect(
|
|
selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
|
|
this, SLOT(slotSelectionChanged(QItemSelection,QItemSelection))
|
|
);
|
|
}
|
|
}
|
|
|
|
bool KApplicationView::isDirSel() const
|
|
{
|
|
if (d->appModel) {
|
|
QModelIndex index = selectionModel()->currentIndex();
|
|
return d->appModel->isDirectory(index);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void KApplicationView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous)
|
|
{
|
|
QTreeView::currentChanged(current, previous);
|
|
if (d->appModel && !d->appModel->isDirectory(current)) {
|
|
QString exec = d->appModel->execFor(current);
|
|
if (!exec.isEmpty()) {
|
|
emit highlighted(d->appModel->entryPathFor(current), exec);
|
|
}
|
|
}
|
|
}
|
|
|
|
void KApplicationView::slotSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
|
|
{
|
|
Q_UNUSED(deselected)
|
|
const QModelIndexList indexes = selected.indexes();
|
|
if (indexes.count() == 1 && !d->appModel->isDirectory(indexes.at(0))) {
|
|
QString exec = d->appModel->execFor(indexes.at(0));
|
|
if (!exec.isEmpty()) {
|
|
emit this->selected(d->appModel->entryPathFor(indexes.at(0)), exec);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************
|
|
*
|
|
* KOpenWithDialog
|
|
*
|
|
***************************************************************/
|
|
class KOpenWithDialogPrivate
|
|
{
|
|
public:
|
|
KOpenWithDialogPrivate(KOpenWithDialog *qq)
|
|
: q(qq), saveNewApps(false)
|
|
{
|
|
}
|
|
|
|
KOpenWithDialog *q;
|
|
|
|
/**
|
|
* Determine mime type from URLs
|
|
*/
|
|
void setMimeType(const KUrl::List &_urls);
|
|
|
|
void addToMimeAppsList(const QString& serviceId);
|
|
|
|
/**
|
|
* Create a dialog that asks for a application to open a given
|
|
* URL(s) with.
|
|
*
|
|
* @param text appears as a label on top of the entry box.
|
|
* @param value is the initial value of the line
|
|
*/
|
|
void init(const QString &text, const QString &value);
|
|
|
|
/**
|
|
* Called by checkAccept() in order to save the history of the combobox
|
|
*/
|
|
void saveComboboxHistory();
|
|
|
|
/**
|
|
* Process the choices made by the user, and return true if everything is OK.
|
|
* Called by KOpenWithDialog::accept(), i.e. when clicking on OK or typing Return.
|
|
*/
|
|
bool checkAccept();
|
|
|
|
// slots
|
|
void _k_slotDbClick();
|
|
void _k_slotFileSelected();
|
|
|
|
bool saveNewApps;
|
|
bool m_terminaldirty;
|
|
KService::Ptr curService;
|
|
KApplicationView *view;
|
|
KUrlRequester *edit;
|
|
QString m_command;
|
|
QLabel *label;
|
|
QString qMimeType;
|
|
QCheckBox *terminal;
|
|
QCheckBox *remember;
|
|
QCheckBox *nocloseonexit;
|
|
KService::Ptr m_pService;
|
|
};
|
|
|
|
KOpenWithDialog::KOpenWithDialog( const KUrl::List &urls, QWidget *parent)
|
|
: KDialog(parent),
|
|
d(new KOpenWithDialogPrivate(this))
|
|
{
|
|
setObjectName(QLatin1String("openwith"));
|
|
setModal(true);
|
|
setCaption(i18n("Open With"));
|
|
|
|
QString text;
|
|
if (urls.count() == 1) {
|
|
text = i18n("<qt>Select the program that should be used to open <b>%1</b>. "
|
|
"If the program is not listed, enter the name or click "
|
|
"the browse button.</qt>", urls.first().fileName());
|
|
} else {
|
|
// Should never happen ??
|
|
text = i18n("Choose the name of the program with which to open the selected files.");
|
|
}
|
|
d->setMimeType(urls);
|
|
d->init(text, QString());
|
|
}
|
|
|
|
KOpenWithDialog::KOpenWithDialog(const KUrl::List &urls, const QString &text,
|
|
const QString &value, QWidget *parent)
|
|
: KDialog(parent),
|
|
d(new KOpenWithDialogPrivate(this))
|
|
{
|
|
setObjectName(QLatin1String("openwith"));
|
|
setModal(true);
|
|
QString caption;
|
|
if (urls.count() > 0 && !urls.first().isEmpty()) {
|
|
caption = KStringHandler::csqueeze(urls.first().prettyUrl());
|
|
}
|
|
if (urls.count() > 1) {
|
|
caption += QString::fromLatin1("...");
|
|
}
|
|
setCaption(caption);
|
|
d->setMimeType(urls);
|
|
d->init(text, value);
|
|
}
|
|
|
|
KOpenWithDialog::KOpenWithDialog(const QString &mimeType, const QString &value,
|
|
QWidget *parent)
|
|
: KDialog(parent),
|
|
d(new KOpenWithDialogPrivate(this))
|
|
{
|
|
setObjectName(QLatin1String("openwith"));
|
|
setModal(true);
|
|
setCaption(i18n("Choose Application for %1", mimeType));
|
|
QString text = i18n("<qt>Select the program for the file type: <b>%1</b>. "
|
|
"If the program is not listed, enter the name or click "
|
|
"the browse button.</qt>", mimeType);
|
|
d->qMimeType = mimeType;
|
|
d->init(text, value);
|
|
if (d->remember) {
|
|
d->remember->hide();
|
|
}
|
|
}
|
|
|
|
KOpenWithDialog::KOpenWithDialog(QWidget *parent)
|
|
: KDialog(parent),
|
|
d(new KOpenWithDialogPrivate(this))
|
|
{
|
|
setObjectName(QLatin1String("openwith"));
|
|
setModal(true);
|
|
setCaption(i18n("Choose Application"));
|
|
QString text = i18n("<qt>Select a program. "
|
|
"If the program is not listed, enter the name or click "
|
|
"the browse button.</qt>");
|
|
d->qMimeType.clear();
|
|
d->init(text, QString());
|
|
}
|
|
|
|
void KOpenWithDialogPrivate::setMimeType(const KUrl::List &urls)
|
|
{
|
|
if (urls.count() == 1) {
|
|
qMimeType = KMimeType::findByUrl(urls.first())->name();
|
|
if (qMimeType == QLatin1String("application/octet-stream")) {
|
|
qMimeType.clear();
|
|
}
|
|
} else {
|
|
qMimeType.clear();
|
|
}
|
|
}
|
|
|
|
void KOpenWithDialogPrivate::init(const QString &text, const QString &value)
|
|
{
|
|
m_terminaldirty = false;
|
|
view = 0;
|
|
m_pService = 0;
|
|
curService = 0;
|
|
|
|
q->setButtons(KDialog::Ok | KDialog::Cancel);
|
|
|
|
QWidget *mainWidget = q->mainWidget();
|
|
|
|
QBoxLayout *topLayout = new QVBoxLayout(mainWidget);
|
|
topLayout->setMargin(0);
|
|
label = new QLabel(text, q);
|
|
label->setWordWrap(true);
|
|
topLayout->addWidget(label);
|
|
|
|
// init the history combo and insert it into the URL-Requester
|
|
KHistoryComboBox *combo = new KHistoryComboBox();
|
|
KLineEdit *lineEdit = new KLineEdit(q);
|
|
lineEdit->setClearButtonShown(true);
|
|
combo->setLineEdit(lineEdit);
|
|
combo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon);
|
|
combo->setDuplicatesEnabled(false);
|
|
KConfigGroup cg( KGlobal::config(), QString::fromLatin1("Open-with settings"));
|
|
int max = cg.readEntry("Maximum history", 15);
|
|
combo->setMaxCount(max);
|
|
int mode = cg.readEntry("CompletionMode", int(KGlobalSettings::completionMode()));
|
|
combo->setCompletionMode((KGlobalSettings::Completion)mode);
|
|
const QStringList list = cg.readEntry("History", QStringList());
|
|
combo->setHistoryItems(list, true);
|
|
edit = new KUrlRequester(combo, mainWidget);
|
|
|
|
edit->setText(value);
|
|
edit->setWhatsThis(
|
|
i18n(
|
|
"Following the command, you can have several place holders which will be replaced "
|
|
"with the actual values when the actual program is run:\n"
|
|
"%f - a single file name\n"
|
|
"%F - a list of files; use for applications that can open several local files at once\n"
|
|
"%u - a single URL\n"
|
|
"%U - a list of URLs\n"
|
|
"%i - the icon\n"
|
|
"%c - the comment\n"
|
|
"%k - the location of the desktop file"
|
|
)
|
|
);
|
|
|
|
topLayout->addWidget(edit);
|
|
|
|
if (edit->comboBox()) {
|
|
KUrlCompletion *comp = new KUrlCompletion(KUrlCompletion::ExeCompletion);
|
|
edit->comboBox()->setCompletionObject(comp);
|
|
edit->comboBox()->setAutoDeleteCompletionObject(true);
|
|
}
|
|
|
|
QObject::connect(edit, SIGNAL(textChanged(QString)), q, SLOT(slotTextChanged()));
|
|
QObject::connect(edit, SIGNAL(urlSelected(KUrl)), q, SLOT(_k_slotFileSelected()));
|
|
|
|
view = new KApplicationView(mainWidget);
|
|
view->setModel(new KApplicationModel(view));
|
|
topLayout->addWidget(view);
|
|
topLayout->setStretchFactor(view, 1);
|
|
|
|
QObject::connect(view, SIGNAL(selected(QString,QString)), q, SLOT(slotSelected(QString,QString)));
|
|
QObject::connect(view, SIGNAL(highlighted(QString,QString)), q, SLOT(slotHighlighted(QString,QString)));
|
|
QObject::connect(view, SIGNAL(doubleClicked(QModelIndex)), q, SLOT(_k_slotDbClick()));
|
|
|
|
terminal = new QCheckBox(i18n("Run in &terminal"), mainWidget);
|
|
QObject::connect(terminal, SIGNAL(toggled(bool)), q, SLOT(slotTerminalToggled(bool)));
|
|
|
|
topLayout->addWidget(terminal);
|
|
|
|
QStyleOptionButton checkBoxOption;
|
|
checkBoxOption.initFrom(terminal);
|
|
int checkBoxIndentation = terminal->style()->pixelMetric(QStyle::PM_IndicatorWidth, &checkBoxOption, terminal);
|
|
checkBoxIndentation += terminal->style()->pixelMetric(QStyle::PM_CheckBoxLabelSpacing, &checkBoxOption, terminal);
|
|
|
|
QBoxLayout* nocloseonexitLayout = new QHBoxLayout();
|
|
nocloseonexitLayout->setMargin(0);
|
|
QSpacerItem* spacer = new QSpacerItem(checkBoxIndentation, 0, QSizePolicy::Fixed, QSizePolicy::Minimum);
|
|
nocloseonexitLayout->addItem(spacer);
|
|
|
|
nocloseonexit = new QCheckBox(i18n("&Do not close when command exits"), mainWidget);
|
|
nocloseonexit->setChecked(false);
|
|
nocloseonexit->setDisabled(true);
|
|
|
|
// check to see if we use konsole if not disable the nocloseonexit
|
|
// because we don't know how to do this on other terminal applications
|
|
KConfigGroup confGroup(KGlobal::config(), QString::fromLatin1("General") );
|
|
QString preferredTerminal = confGroup.readPathEntry("TerminalApplication", QString::fromLatin1("konsole"));
|
|
|
|
if (preferredTerminal != "konsole") {
|
|
nocloseonexit->hide();
|
|
}
|
|
|
|
nocloseonexitLayout->addWidget( nocloseonexit );
|
|
topLayout->addLayout( nocloseonexitLayout );
|
|
|
|
if (!qMimeType.isNull()) {
|
|
remember = new QCheckBox(i18n("&Remember application association for this type of file"), mainWidget);
|
|
// remember->setChecked(true);
|
|
topLayout->addWidget(remember);
|
|
} else {
|
|
remember = 0L;
|
|
}
|
|
|
|
q->setMinimumSize(q->minimumSizeHint());
|
|
// edit->setText( _value );
|
|
// This is what caused "can't click on items before clicking on Name header".
|
|
// Probably due to the resizeEvent handler using width().
|
|
// resize( minimumWidth(), sizeHint().height() );
|
|
edit->setFocus();
|
|
q->slotTextChanged();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
KOpenWithDialog::~KOpenWithDialog()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void KOpenWithDialog::slotSelected(const QString &/*name*/, const QString &exec)
|
|
{
|
|
KService::Ptr pService = d->curService;
|
|
d->edit->setText(exec); // calls slotTextChanged :(
|
|
d->curService = pService;
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void KOpenWithDialog::slotHighlighted(const QString &entryPath, const QString&)
|
|
{
|
|
d->curService = KService::serviceByDesktopPath(entryPath);
|
|
if (d->curService && !d->m_terminaldirty) {
|
|
// ### indicate that default value was restored
|
|
d->terminal->setChecked(d->curService->terminal());
|
|
QString terminalOptions = d->curService->terminalOptions();
|
|
d->nocloseonexit->setChecked(terminalOptions.contains(QLatin1String("--noclose")));
|
|
d->m_terminaldirty = false; // slotTerminalToggled changed it
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void KOpenWithDialog::slotTextChanged()
|
|
{
|
|
// Forget about the service
|
|
d->curService = 0L;
|
|
enableButton(KDialog::Ok, !d->edit->text().isEmpty());
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void KOpenWithDialog::slotTerminalToggled(bool)
|
|
{
|
|
// ### indicate that default value was overridden
|
|
d->m_terminaldirty = true;
|
|
d->nocloseonexit->setDisabled(!d->terminal->isChecked());
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void KOpenWithDialogPrivate::_k_slotDbClick()
|
|
{
|
|
// check if a directory is selected
|
|
if (view->isDirSel()) {
|
|
return;
|
|
}
|
|
q->accept();
|
|
}
|
|
|
|
void KOpenWithDialogPrivate::_k_slotFileSelected()
|
|
{
|
|
// quote the path to avoid unescaped whitespace, backslashes, etc.
|
|
edit->setText(KShell::quoteArg(edit->text()));
|
|
}
|
|
|
|
void KOpenWithDialog::setSaveNewApplications(bool b)
|
|
{
|
|
d->saveNewApps = b;
|
|
}
|
|
|
|
void KOpenWithDialogPrivate::addToMimeAppsList(const QString &serviceId /*menu id or storage id*/)
|
|
{
|
|
KSharedConfig::Ptr profile = KSharedConfig::openConfig("mimeapps.list", KConfig::NoGlobals, "xdgdata-apps");
|
|
KConfigGroup addedApps(profile, "Added Associations");
|
|
QStringList apps = addedApps.readXdgListEntry(qMimeType);
|
|
apps.removeAll(serviceId);
|
|
apps.prepend(serviceId); // make it the preferred app
|
|
addedApps.writeXdgListEntry(qMimeType, apps);
|
|
addedApps.sync();
|
|
|
|
// Also make sure the "auto embed" setting for this mimetype is off
|
|
KSharedConfig::Ptr fileTypesConfig = KSharedConfig::openConfig("filetypesrc", KConfig::NoGlobals);
|
|
fileTypesConfig->group("EmbedSettings").writeEntry(QString("embed-")+qMimeType, false);
|
|
fileTypesConfig->sync();
|
|
|
|
kDebug() << "rebuilding ksycoca...";
|
|
|
|
// kbuildsycoca is the one reading mimeapps.list, so we need to run it now
|
|
KBuildSycocaProgressDialog::rebuildKSycoca(q);
|
|
|
|
m_pService = KService::serviceByStorageId(serviceId);
|
|
Q_ASSERT(m_pService);
|
|
}
|
|
|
|
bool KOpenWithDialogPrivate::checkAccept()
|
|
{
|
|
const QString typedExec(edit->text());
|
|
if (typedExec.isEmpty()) {
|
|
return false;
|
|
}
|
|
QString fullExec(typedExec);
|
|
QString serviceName;
|
|
QString initialServiceName;
|
|
QString preferredTerminal;
|
|
QString configPath;
|
|
QString serviceExec;
|
|
m_pService = curService;
|
|
if (!m_pService) {
|
|
// No service selected - check the command line
|
|
|
|
// Find out the name of the service from the command line, removing args and paths
|
|
serviceName = KRun::binaryName(typedExec, true);
|
|
if (serviceName.isEmpty()) {
|
|
KMessageBox::error(q, i18n("Could not extract executable name from '%1', please type a valid program name.", serviceName));
|
|
return false;
|
|
}
|
|
initialServiceName = serviceName;
|
|
// Also remember the binaryName with a path, if any, for the
|
|
// check that the binary exists.
|
|
kDebug() << "initialServiceName=" << initialServiceName;
|
|
int i = 1; // We have app, app-2, app-3... Looks better for the user.
|
|
bool ok = false;
|
|
// Check if there's already a service by that name, with the same Exec line
|
|
do {
|
|
kDebug() << "looking for service" << serviceName;
|
|
KService::Ptr serv = KService::serviceByDesktopName(serviceName);
|
|
ok = !serv; // ok if no such service yet
|
|
// also ok if we find the exact same service (well, "kwrite" == "kwrite %U")
|
|
if (serv && !serv->noDisplay() /* #297720 */) {
|
|
if (serv->isApplication()) {
|
|
/*kDebug() << "typedExec=" << typedExec
|
|
<< "serv->exec=" << serv->exec()
|
|
<< "simplifiedExecLineFromService=" << simplifiedExecLineFromService(fullExec);*/
|
|
serviceExec = simplifiedExecLineFromService(serv->exec());
|
|
if (typedExec == serviceExec){
|
|
ok = true;
|
|
m_pService = serv;
|
|
kDebug() << "OK, found identical service: " << serv->entryPath();
|
|
} else {
|
|
kDebug() << "Exec line differs, service says:" << serviceExec;
|
|
configPath = serv->entryPath();
|
|
serviceExec = serv->exec();
|
|
}
|
|
} else {
|
|
kDebug() << "Found, but not an application:" << serv->entryPath();
|
|
}
|
|
}
|
|
if (!ok) { // service was found, but it was different -> keep looking
|
|
++i;
|
|
serviceName = initialServiceName + '-' + QString::number(i);
|
|
}
|
|
} while (!ok);
|
|
}
|
|
if (m_pService) {
|
|
// Existing service selected
|
|
serviceName = m_pService->name();
|
|
initialServiceName = serviceName;
|
|
fullExec = m_pService->exec();
|
|
} else {
|
|
const QString binaryName = KRun::binaryName(typedExec, false);
|
|
kDebug() << "binaryName=" << binaryName;
|
|
// Ensure that the typed binary name actually exists (#81190)
|
|
if (KStandardDirs::findExe(binaryName).isEmpty()) {
|
|
KMessageBox::error(q, i18n("'%1' not found, please type a valid program name.", binaryName));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (terminal->isChecked()) {
|
|
KConfigGroup confGroup( KGlobal::config(), QString::fromLatin1("General") );
|
|
preferredTerminal = confGroup.readPathEntry("TerminalApplication", QString::fromLatin1("konsole"));
|
|
m_command = preferredTerminal;
|
|
// only add --noclose when we are sure it is konsole we're using
|
|
if (preferredTerminal == "konsole" && nocloseonexit->isChecked()) {
|
|
m_command += QString::fromLatin1(" --noclose");
|
|
}
|
|
m_command += QString::fromLatin1(" -e ");
|
|
m_command += edit->text();
|
|
kDebug() << "Setting m_command to" << m_command;
|
|
}
|
|
if (m_pService && terminal->isChecked() != m_pService->terminal()) {
|
|
m_pService = 0; // It's not exactly this service we're running
|
|
}
|
|
|
|
const bool bRemember = remember && remember->isChecked();
|
|
kDebug() << "bRemember=" << bRemember << "service found=" << m_pService;
|
|
if (m_pService) {
|
|
if (bRemember) {
|
|
// Associate this app with qMimeType in mimeapps.list
|
|
Q_ASSERT(!qMimeType.isEmpty()); // we don't show the remember checkbox otherwise
|
|
addToMimeAppsList(m_pService->storageId());
|
|
}
|
|
} else {
|
|
const bool createDesktopFile = bRemember || saveNewApps;
|
|
if (!createDesktopFile) {
|
|
// Create temp service
|
|
if (configPath.isEmpty()) {
|
|
m_pService = new KService(initialServiceName, fullExec, QString());
|
|
} else {
|
|
if (!typedExec.contains(QLatin1String("%u"), Qt::CaseInsensitive) &&
|
|
!typedExec.contains(QLatin1String("%f"), Qt::CaseInsensitive)) {
|
|
int index = serviceExec.indexOf(QLatin1String("%u"), 0, Qt::CaseInsensitive);
|
|
if (index == -1) {
|
|
index = serviceExec.indexOf(QLatin1String("%f"), 0, Qt::CaseInsensitive);
|
|
}
|
|
if (index > -1) {
|
|
fullExec += QLatin1Char(' ');
|
|
fullExec += serviceExec.mid(index, 2);
|
|
}
|
|
}
|
|
kDebug() << "Creating service with Exec=" << fullExec;
|
|
m_pService = new KService(configPath);
|
|
m_pService->setExec(fullExec);
|
|
}
|
|
if (terminal->isChecked()) {
|
|
m_pService->setTerminal(true);
|
|
// only add --noclose when we are sure it is konsole we're using
|
|
if (preferredTerminal == "konsole" && nocloseonexit->isChecked()) {
|
|
m_pService->setTerminalOptions("--noclose");
|
|
}
|
|
}
|
|
} else {
|
|
// If we got here, we can't seem to find a service for what they wanted. Create one.
|
|
|
|
QString menuId;
|
|
QString newPath = KService::newServicePath(serviceName, &menuId);
|
|
kDebug() << "Creating new service" << serviceName << "(" << newPath << ")" << "menuId=" << menuId;
|
|
|
|
KDesktopFile desktopFile(newPath);
|
|
KConfigGroup cg = desktopFile.desktopGroup();
|
|
cg.writeEntry("Type", "Application");
|
|
cg.writeEntry("Name", initialServiceName);
|
|
cg.writeEntry("Exec", fullExec);
|
|
cg.writeEntry("NoDisplay", true); // don't make it appear in the K menu
|
|
if (terminal->isChecked()) {
|
|
cg.writeEntry("Terminal", true);
|
|
// only add --noclose when we are sure it is konsole we're using
|
|
if (preferredTerminal == "konsole" && nocloseonexit->isChecked()) {
|
|
cg.writeEntry("TerminalOptions", "--noclose");
|
|
}
|
|
}
|
|
cg.writeXdgListEntry("MimeType", QStringList() << qMimeType);
|
|
cg.sync();
|
|
|
|
addToMimeAppsList(menuId);
|
|
}
|
|
}
|
|
|
|
saveComboboxHistory();
|
|
return true;
|
|
}
|
|
|
|
void KOpenWithDialog::accept()
|
|
{
|
|
if (d->checkAccept()) {
|
|
KDialog::accept();
|
|
}
|
|
}
|
|
|
|
QString KOpenWithDialog::text() const
|
|
{
|
|
if (!d->m_command.isEmpty()) {
|
|
return d->m_command;
|
|
}
|
|
return d->edit->text();
|
|
}
|
|
|
|
void KOpenWithDialog::hideNoCloseOnExit()
|
|
{
|
|
// uncheck the checkbox because the value could be used when "Run in Terminal" is selected
|
|
d->nocloseonexit->setChecked(false);
|
|
d->nocloseonexit->hide();
|
|
}
|
|
|
|
void KOpenWithDialog::hideRunInTerminal()
|
|
{
|
|
d->terminal->hide();
|
|
hideNoCloseOnExit();
|
|
}
|
|
|
|
KService::Ptr KOpenWithDialog::service() const
|
|
{
|
|
return d->m_pService;
|
|
}
|
|
|
|
void KOpenWithDialogPrivate::saveComboboxHistory()
|
|
{
|
|
KHistoryComboBox *combo = static_cast<KHistoryComboBox*>(edit->comboBox());
|
|
if (combo) {
|
|
combo->addToHistory(edit->text());
|
|
|
|
KConfigGroup cg(KGlobal::config(), QString::fromLatin1("Open-with settings"));
|
|
cg.writeEntry("History", combo->historyItems());
|
|
cg.writeEntry("CompletionMode", int(combo->completionMode()));
|
|
// don't store the completion-list, as it contains all of KUrlCompletion's
|
|
// executables
|
|
cg.sync();
|
|
}
|
|
}
|
|
|
|
#include "moc_kopenwithdialog.cpp"
|
|
#include "moc_kopenwithdialog_p.cpp"
|