KIO
kopenwithdialog.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE libraries 00002 00003 Copyright (C) 1997 Torben Weis <weis@stud.uni-frankfurt.de> 00004 Copyright (C) 1999 Dirk Mueller <mueller@kde.org> 00005 Portions copyright (C) 1999 Preston Brown <pbrown@kde.org> 00006 Copyright (C) 2007 Pino Toscano <pino@kde.org> 00007 00008 This library is free software; you can redistribute it and/or 00009 modify it under the terms of the GNU Library General Public 00010 License as published by the Free Software Foundation; either 00011 version 2 of the License, or (at your option) any later version. 00012 00013 This library is distributed in the hope that it will be useful, 00014 but WITHOUT ANY WARRANTY; without even the implied warranty of 00015 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00016 Library General Public License for more details. 00017 00018 You should have received a copy of the GNU Library General Public License 00019 along with this library; see the file COPYING.LIB. If not, write to 00020 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00021 Boston, MA 02110-1301, USA. 00022 */ 00023 00024 #include "kopenwithdialog.h" 00025 #include "kopenwithdialog_p.h" 00026 00027 #include <QtCore/QtAlgorithms> 00028 #include <QtCore/QList> 00029 #include <QtGui/QLabel> 00030 #include <QtGui/QLayout> 00031 #include <QtGui/QCheckBox> 00032 #include <QtGui/QStyle> 00033 #include <QtGui/QStyleOptionButton> 00034 00035 #include <kauthorized.h> 00036 #include <khistorycombobox.h> 00037 #include <kdesktopfile.h> 00038 #include <klineedit.h> 00039 #include <klocale.h> 00040 #include <kiconloader.h> 00041 #include <kmessagebox.h> 00042 #include <krun.h> 00043 #include <kstandarddirs.h> 00044 #include <kstringhandler.h> 00045 #include <kurlcompletion.h> 00046 #include <kurlrequester.h> 00047 #include <kmimetype.h> 00048 #include <kservicegroup.h> 00049 #include <kserviceoffer.h> 00050 #include <kdebug.h> 00051 00052 #include <assert.h> 00053 #include <stdlib.h> 00054 #include <kbuildsycocaprogressdialog.h> 00055 #include <kconfiggroup.h> 00056 00057 inline void writeEntry( KConfigGroup& group, const char* key, 00058 const KGlobalSettings::Completion& aValue, 00059 KConfigBase::WriteConfigFlags flags = KConfigBase::Normal ) 00060 { 00061 group.writeEntry(key, int(aValue), flags); 00062 } 00063 00064 namespace KDEPrivate { 00065 00066 class AppNode 00067 { 00068 public: 00069 AppNode() 00070 : isDir(false), parent(0), fetched(false) 00071 { 00072 } 00073 ~AppNode() 00074 { 00075 qDeleteAll(children); 00076 } 00077 00078 QString icon; 00079 QString text; 00080 QString entryPath; 00081 QString exec; 00082 bool isDir; 00083 00084 AppNode *parent; 00085 bool fetched; 00086 00087 QList<AppNode*> children; 00088 }; 00089 00090 bool AppNodeLessThan(KDEPrivate::AppNode *n1, KDEPrivate::AppNode *n2) 00091 { 00092 if (n1->isDir) { 00093 if (n2->isDir) { 00094 return n1->text.compare(n2->text, Qt::CaseInsensitive) < 0; 00095 } else { 00096 return true; 00097 } 00098 } else { 00099 if (n2->isDir) { 00100 return false; 00101 } else { 00102 return n1->text.compare(n2->text, Qt::CaseInsensitive) < 0; 00103 } 00104 } 00105 return true; 00106 } 00107 00108 } 00109 00110 00111 class KApplicationModelPrivate 00112 { 00113 public: 00114 KApplicationModelPrivate(KApplicationModel *qq) 00115 : q(qq), root(new KDEPrivate::AppNode()) 00116 { 00117 } 00118 ~KApplicationModelPrivate() 00119 { 00120 delete root; 00121 } 00122 00123 void fillNode(const QString &entryPath, KDEPrivate::AppNode *node); 00124 00125 KApplicationModel *q; 00126 00127 KDEPrivate::AppNode *root; 00128 }; 00129 00130 void KApplicationModelPrivate::fillNode(const QString &_entryPath, KDEPrivate::AppNode *node) 00131 { 00132 KServiceGroup::Ptr root = KServiceGroup::group(_entryPath); 00133 if (!root || !root->isValid()) return; 00134 00135 const KServiceGroup::List list = root->entries(); 00136 00137 for( KServiceGroup::List::ConstIterator it = list.begin(); 00138 it != list.end(); ++it) 00139 { 00140 QString icon; 00141 QString text; 00142 QString entryPath; 00143 QString exec; 00144 bool isDir = false; 00145 const KSycocaEntry::Ptr p = (*it); 00146 if (p->isType(KST_KService)) 00147 { 00148 const KService::Ptr service = KService::Ptr::staticCast(p); 00149 00150 if (service->noDisplay()) 00151 continue; 00152 00153 icon = service->icon(); 00154 text = service->name(); 00155 exec = service->exec(); 00156 entryPath = service->entryPath(); 00157 } 00158 else if (p->isType(KST_KServiceGroup)) 00159 { 00160 const KServiceGroup::Ptr serviceGroup = KServiceGroup::Ptr::staticCast(p); 00161 00162 if (serviceGroup->noDisplay() || serviceGroup->childCount() == 0) 00163 continue; 00164 00165 icon = serviceGroup->icon(); 00166 text = serviceGroup->caption(); 00167 entryPath = serviceGroup->entryPath(); 00168 isDir = true; 00169 } 00170 else 00171 { 00172 kWarning(250) << "KServiceGroup: Unexpected object in list!"; 00173 continue; 00174 } 00175 00176 KDEPrivate::AppNode *newnode = new KDEPrivate::AppNode(); 00177 newnode->icon = icon; 00178 newnode->text = text; 00179 newnode->entryPath = entryPath; 00180 newnode->exec = exec; 00181 newnode->isDir = isDir; 00182 newnode->parent = node; 00183 node->children.append(newnode); 00184 } 00185 qStableSort(node->children.begin(), node->children.end(), KDEPrivate::AppNodeLessThan); 00186 } 00187 00188 00189 00190 KApplicationModel::KApplicationModel(QObject *parent) 00191 : QAbstractItemModel(parent), d(new KApplicationModelPrivate(this)) 00192 { 00193 d->fillNode(QString(), d->root); 00194 } 00195 00196 KApplicationModel::~KApplicationModel() 00197 { 00198 delete d; 00199 } 00200 00201 bool KApplicationModel::canFetchMore(const QModelIndex &parent) const 00202 { 00203 if (!parent.isValid()) 00204 return false; 00205 00206 KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(parent.internalPointer()); 00207 return node->isDir && !node->fetched; 00208 } 00209 00210 int KApplicationModel::columnCount(const QModelIndex &parent) const 00211 { 00212 Q_UNUSED(parent) 00213 return 1; 00214 } 00215 00216 QVariant KApplicationModel::data(const QModelIndex &index, int role) const 00217 { 00218 if (!index.isValid()) 00219 return QVariant(); 00220 00221 KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(index.internalPointer()); 00222 00223 switch (role) { 00224 case Qt::DisplayRole: 00225 return node->text; 00226 break; 00227 case Qt::DecorationRole: 00228 if (!node->icon.isEmpty()) { 00229 return KIcon(node->icon); 00230 } 00231 break; 00232 default: 00233 ; 00234 } 00235 return QVariant(); 00236 } 00237 00238 void KApplicationModel::fetchMore(const QModelIndex &parent) 00239 { 00240 if (!parent.isValid()) 00241 return; 00242 00243 KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(parent.internalPointer()); 00244 if (!node->isDir) 00245 return; 00246 00247 emit layoutAboutToBeChanged(); 00248 d->fillNode(node->entryPath, node); 00249 node->fetched = true; 00250 emit layoutChanged(); 00251 } 00252 00253 bool KApplicationModel::hasChildren(const QModelIndex &parent) const 00254 { 00255 if (!parent.isValid()) 00256 return true; 00257 00258 KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(parent.internalPointer()); 00259 return node->isDir; 00260 } 00261 00262 QVariant KApplicationModel::headerData(int section, Qt::Orientation orientation, int role) const 00263 { 00264 if (orientation != Qt::Horizontal || section != 0) 00265 return QVariant(); 00266 00267 switch (role) { 00268 case Qt::DisplayRole: 00269 return i18n("Known Applications"); 00270 break; 00271 default: 00272 return QVariant(); 00273 } 00274 } 00275 00276 QModelIndex KApplicationModel::index(int row, int column, const QModelIndex &parent) const 00277 { 00278 if (row < 0 || column != 0) 00279 return QModelIndex(); 00280 00281 KDEPrivate::AppNode *node = d->root; 00282 if (parent.isValid()) 00283 node = static_cast<KDEPrivate::AppNode*>(parent.internalPointer()); 00284 00285 if (row >= node->children.count()) 00286 return QModelIndex(); 00287 else 00288 return createIndex(row, 0, node->children.at(row)); 00289 } 00290 00291 QModelIndex KApplicationModel::parent(const QModelIndex &index) const 00292 { 00293 if (!index.isValid()) 00294 return QModelIndex(); 00295 00296 KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(index.internalPointer()); 00297 if (node->parent->parent) { 00298 int id = node->parent->parent->children.indexOf(node->parent); 00299 00300 if (id >= 0 && id < node->parent->parent->children.count()) 00301 return createIndex(id, 0, node->parent); 00302 else 00303 return QModelIndex(); 00304 } 00305 else 00306 return QModelIndex(); 00307 } 00308 00309 int KApplicationModel::rowCount(const QModelIndex &parent) const 00310 { 00311 if (!parent.isValid()) 00312 return d->root->children.count(); 00313 00314 KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(parent.internalPointer()); 00315 return node->children.count(); 00316 } 00317 00318 QString KApplicationModel::entryPathFor(const QModelIndex &index) const 00319 { 00320 if (!index.isValid()) 00321 return QString(); 00322 00323 KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(index.internalPointer()); 00324 return node->entryPath; 00325 } 00326 00327 QString KApplicationModel::execFor(const QModelIndex &index) const 00328 { 00329 if (!index.isValid()) 00330 return QString(); 00331 00332 KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(index.internalPointer()); 00333 return node->exec; 00334 } 00335 00336 bool KApplicationModel::isDirectory(const QModelIndex &index) const 00337 { 00338 if (!index.isValid()) 00339 return false; 00340 00341 KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(index.internalPointer()); 00342 return node->isDir; 00343 } 00344 00345 class KApplicationViewPrivate 00346 { 00347 public: 00348 KApplicationViewPrivate() 00349 : appModel(0) 00350 { 00351 } 00352 00353 KApplicationModel *appModel; 00354 }; 00355 00356 KApplicationView::KApplicationView(QWidget *parent) 00357 : QTreeView(parent), d(new KApplicationViewPrivate) 00358 { 00359 } 00360 00361 KApplicationView::~KApplicationView() 00362 { 00363 delete d; 00364 } 00365 00366 void KApplicationView::setModel(QAbstractItemModel *model) 00367 { 00368 if (d->appModel) { 00369 disconnect(selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), 00370 this, SLOT(slotSelectionChanged(QItemSelection, QItemSelection))); 00371 } 00372 00373 QTreeView::setModel(model); 00374 00375 d->appModel = qobject_cast<KApplicationModel*>(model); 00376 if (d->appModel) { 00377 connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), 00378 this, SLOT(slotSelectionChanged(QItemSelection, QItemSelection))); 00379 } 00380 } 00381 00382 bool KApplicationView::isDirSel() const 00383 { 00384 if (d->appModel) { 00385 QModelIndex index = selectionModel()->currentIndex(); 00386 return d->appModel->isDirectory(index); 00387 } 00388 return false; 00389 } 00390 00391 void KApplicationView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) 00392 { 00393 QTreeView::currentChanged(current, previous); 00394 00395 if (d->appModel && !d->appModel->isDirectory(current)) { 00396 QString exec = d->appModel->execFor(current); 00397 if (!exec.isEmpty()) { 00398 emit highlighted(d->appModel->entryPathFor(current), exec); 00399 } 00400 } 00401 } 00402 00403 void KApplicationView::slotSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) 00404 { 00405 Q_UNUSED(deselected) 00406 00407 const QModelIndexList indexes = selected.indexes(); 00408 if (indexes.count() == 1 && !d->appModel->isDirectory(indexes.at(0))) { 00409 QString exec = d->appModel->execFor(indexes.at(0)); 00410 if (!exec.isEmpty()) { 00411 emit this->selected(d->appModel->entryPathFor(indexes.at(0)), exec); 00412 } 00413 } 00414 } 00415 00416 00417 00418 /*************************************************************** 00419 * 00420 * KOpenWithDialog 00421 * 00422 ***************************************************************/ 00423 class KOpenWithDialogPrivate 00424 { 00425 public: 00426 KOpenWithDialogPrivate(KOpenWithDialog *qq) 00427 : q(qq), saveNewApps(false) 00428 { 00429 } 00430 00431 KOpenWithDialog *q; 00432 00436 void setMimeType(const KUrl::List &_urls); 00437 00438 void addToMimeAppsList(const QString& serviceId); 00439 00447 void init(const QString &text, const QString &value); 00448 00452 void saveComboboxHistory(); 00453 00458 bool checkAccept(); 00459 00460 // slots 00461 void _k_slotDbClick(); 00462 00463 bool saveNewApps; 00464 bool m_terminaldirty; 00465 KService::Ptr curService; 00466 KApplicationView *view; 00467 KUrlRequester *edit; 00468 QString m_command; 00469 QLabel *label; 00470 QString qMimeType; 00471 QCheckBox *terminal; 00472 QCheckBox *remember; 00473 QCheckBox *nocloseonexit; 00474 KService::Ptr m_pService; 00475 }; 00476 00477 KOpenWithDialog::KOpenWithDialog( const KUrl::List& _urls, QWidget* parent ) 00478 : KDialog(parent), d(new KOpenWithDialogPrivate(this)) 00479 { 00480 setObjectName( QLatin1String( "openwith" ) ); 00481 setModal( true ); 00482 setCaption( i18n( "Open With" ) ); 00483 00484 QString text; 00485 if( _urls.count() == 1 ) 00486 { 00487 text = i18n("<qt>Select the program that should be used to open <b>%1</b>. " 00488 "If the program is not listed, enter the name or click " 00489 "the browse button.</qt>", _urls.first().fileName() ); 00490 } 00491 else 00492 // Should never happen ?? 00493 text = i18n( "Choose the name of the program with which to open the selected files." ); 00494 d->setMimeType(_urls); 00495 d->init(text, QString()); 00496 } 00497 00498 KOpenWithDialog::KOpenWithDialog( const KUrl::List& _urls, const QString&_text, 00499 const QString& _value, QWidget *parent) 00500 : KDialog(parent), d(new KOpenWithDialogPrivate(this)) 00501 { 00502 setObjectName( QLatin1String( "openwith" ) ); 00503 setModal( true ); 00504 QString caption; 00505 if (_urls.count()>0 && !_urls.first().isEmpty()) 00506 caption = KStringHandler::csqueeze( _urls.first().prettyUrl() ); 00507 if (_urls.count() > 1) 00508 caption += QString::fromLatin1("..."); 00509 setCaption(caption); 00510 d->setMimeType(_urls); 00511 d->init(_text, _value); 00512 } 00513 00514 KOpenWithDialog::KOpenWithDialog( const QString &mimeType, const QString& value, 00515 QWidget *parent) 00516 : KDialog(parent), d(new KOpenWithDialogPrivate(this)) 00517 { 00518 setObjectName( QLatin1String( "openwith" ) ); 00519 setModal( true ); 00520 setCaption(i18n("Choose Application for %1", mimeType)); 00521 QString text = i18n("<qt>Select the program for the file type: <b>%1</b>. " 00522 "If the program is not listed, enter the name or click " 00523 "the browse button.</qt>", mimeType); 00524 d->qMimeType = mimeType; 00525 d->init(text, value); 00526 if (d->remember) { 00527 d->remember->hide(); 00528 } 00529 } 00530 00531 KOpenWithDialog::KOpenWithDialog( QWidget *parent) 00532 : KDialog(parent), d(new KOpenWithDialogPrivate(this)) 00533 { 00534 setObjectName( QLatin1String( "openwith" ) ); 00535 setModal( true ); 00536 setCaption(i18n("Choose Application")); 00537 QString text = i18n("<qt>Select a program. " 00538 "If the program is not listed, enter the name or click " 00539 "the browse button.</qt>"); 00540 d->qMimeType.clear(); 00541 d->init(text, QString()); 00542 } 00543 00544 void KOpenWithDialogPrivate::setMimeType(const KUrl::List &_urls) 00545 { 00546 if ( _urls.count() == 1 ) 00547 { 00548 qMimeType = KMimeType::findByUrl( _urls.first())->name(); 00549 if (qMimeType == QLatin1String("application/octet-stream")) 00550 qMimeType.clear(); 00551 } 00552 else 00553 qMimeType.clear(); 00554 } 00555 00556 void KOpenWithDialogPrivate::init(const QString &_text, const QString &_value) 00557 { 00558 bool bReadOnly = !KAuthorized::authorize("shell_access"); 00559 m_terminaldirty = false; 00560 view = 0; 00561 m_pService = 0; 00562 curService = 0; 00563 00564 q->setButtons(KDialog::Ok | KDialog::Cancel); 00565 00566 QWidget *mainWidget = q->mainWidget(); 00567 00568 QBoxLayout *topLayout = new QVBoxLayout( mainWidget ); 00569 topLayout->setMargin(0); 00570 label = new QLabel(_text, q); 00571 label->setWordWrap(true); 00572 topLayout->addWidget(label); 00573 00574 if (!bReadOnly) 00575 { 00576 // init the history combo and insert it into the URL-Requester 00577 KHistoryComboBox *combo = new KHistoryComboBox(); 00578 KLineEdit *lineEdit = new KLineEdit(q); 00579 lineEdit->setClearButtonShown(true); 00580 combo->setLineEdit(lineEdit); 00581 combo->setDuplicatesEnabled( false ); 00582 KConfigGroup cg( KGlobal::config(), QString::fromLatin1("Open-with settings") ); 00583 int max = cg.readEntry( "Maximum history", 15 ); 00584 combo->setMaxCount( max ); 00585 int mode = cg.readEntry( "CompletionMode", int(KGlobalSettings::completionMode())); 00586 combo->setCompletionMode((KGlobalSettings::Completion)mode); 00587 const QStringList list = cg.readEntry( "History", QStringList() ); 00588 combo->setHistoryItems( list, true ); 00589 edit = new KUrlRequester( combo, mainWidget ); 00590 } 00591 else 00592 { 00593 edit = new KUrlRequester( mainWidget ); 00594 edit->lineEdit()->setReadOnly(true); 00595 edit->button()->hide(); 00596 } 00597 00598 edit->setText( _value ); 00599 edit->setWhatsThis(i18n( 00600 "Following the command, you can have several place holders which will be replaced " 00601 "with the actual values when the actual program is run:\n" 00602 "%f - a single file name\n" 00603 "%F - a list of files; use for applications that can open several local files at once\n" 00604 "%u - a single URL\n" 00605 "%U - a list of URLs\n" 00606 "%d - the directory of the file to open\n" 00607 "%D - a list of directories\n" 00608 "%i - the icon\n" 00609 "%m - the mini-icon\n" 00610 "%c - the comment")); 00611 00612 topLayout->addWidget(edit); 00613 00614 if ( edit->comboBox() ) { 00615 KUrlCompletion *comp = new KUrlCompletion( KUrlCompletion::ExeCompletion ); 00616 edit->comboBox()->setCompletionObject( comp ); 00617 edit->comboBox()->setAutoDeleteCompletionObject( true ); 00618 } 00619 00620 QObject::connect(edit, SIGNAL(textChanged(QString)), q, SLOT(slotTextChanged())); 00621 00622 view = new KApplicationView(mainWidget); 00623 view->setModel(new KApplicationModel(view)); 00624 topLayout->addWidget(view); 00625 topLayout->setStretchFactor(view, 1); 00626 00627 QObject::connect(view, SIGNAL(selected(QString, QString)), 00628 q, SLOT(slotSelected(QString, QString))); 00629 QObject::connect(view, SIGNAL(highlighted(QString, QString)), 00630 q, SLOT(slotHighlighted(QString, QString))); 00631 QObject::connect(view, SIGNAL(doubleClicked(QModelIndex)), 00632 q, SLOT(_k_slotDbClick())); 00633 00634 terminal = new QCheckBox( i18n("Run in &terminal"), mainWidget ); 00635 if (bReadOnly) 00636 terminal->hide(); 00637 QObject::connect(terminal, SIGNAL(toggled(bool)), q, SLOT(slotTerminalToggled(bool))); 00638 00639 topLayout->addWidget(terminal); 00640 00641 QStyleOptionButton checkBoxOption; 00642 checkBoxOption.initFrom(terminal); 00643 int checkBoxIndentation = terminal->style()->pixelMetric( QStyle::PM_IndicatorWidth, &checkBoxOption, terminal ); 00644 checkBoxIndentation += terminal->style()->pixelMetric( QStyle::PM_CheckBoxLabelSpacing, &checkBoxOption, terminal ); 00645 00646 QBoxLayout* nocloseonexitLayout = new QHBoxLayout(); 00647 nocloseonexitLayout->setMargin( 0 ); 00648 QSpacerItem* spacer = new QSpacerItem( checkBoxIndentation, 0, QSizePolicy::Fixed, QSizePolicy::Minimum ); 00649 nocloseonexitLayout->addItem( spacer ); 00650 00651 nocloseonexit = new QCheckBox( i18n("&Do not close when command exits"), mainWidget ); 00652 nocloseonexit->setChecked( false ); 00653 nocloseonexit->setDisabled( true ); 00654 00655 // check to see if we use konsole if not disable the nocloseonexit 00656 // because we don't know how to do this on other terminal applications 00657 KConfigGroup confGroup( KGlobal::config(), QString::fromLatin1("General") ); 00658 QString preferredTerminal = confGroup.readPathEntry("TerminalApplication", QString::fromLatin1("konsole")); 00659 00660 if (bReadOnly || preferredTerminal != "konsole") 00661 nocloseonexit->hide(); 00662 00663 nocloseonexitLayout->addWidget( nocloseonexit ); 00664 topLayout->addLayout( nocloseonexitLayout ); 00665 00666 if (!qMimeType.isNull()) 00667 { 00668 remember = new QCheckBox(i18n("&Remember application association for this type of file"), mainWidget); 00669 // remember->setChecked(true); 00670 topLayout->addWidget(remember); 00671 } 00672 else 00673 remember = 0L; 00674 00675 q->setMinimumSize(q->minimumSizeHint()); 00676 //edit->setText( _value ); 00677 // This is what caused "can't click on items before clicking on Name header". 00678 // Probably due to the resizeEvent handler using width(). 00679 //resize( minimumWidth(), sizeHint().height() ); 00680 edit->setFocus(); 00681 q->slotTextChanged(); 00682 } 00683 00684 00685 // ---------------------------------------------------------------------- 00686 00687 KOpenWithDialog::~KOpenWithDialog() 00688 { 00689 delete d; 00690 } 00691 00692 00693 // ---------------------------------------------------------------------- 00694 00695 void KOpenWithDialog::slotSelected( const QString& /*_name*/, const QString& _exec ) 00696 { 00697 KService::Ptr pService = d->curService; 00698 d->edit->setText(_exec); // calls slotTextChanged :( 00699 d->curService = pService; 00700 } 00701 00702 00703 // ---------------------------------------------------------------------- 00704 00705 void KOpenWithDialog::slotHighlighted(const QString& entryPath, const QString&) 00706 { 00707 d->curService = KService::serviceByDesktopPath(entryPath); 00708 if (!d->m_terminaldirty) 00709 { 00710 // ### indicate that default value was restored 00711 d->terminal->setChecked(d->curService->terminal()); 00712 QString terminalOptions = d->curService->terminalOptions(); 00713 d->nocloseonexit->setChecked((terminalOptions.contains(QLatin1String("--noclose")) > 0)); 00714 d->m_terminaldirty = false; // slotTerminalToggled changed it 00715 } 00716 } 00717 00718 // ---------------------------------------------------------------------- 00719 00720 void KOpenWithDialog::slotTextChanged() 00721 { 00722 // Forget about the service 00723 d->curService = 0L; 00724 enableButton(Ok, !d->edit->text().isEmpty()); 00725 } 00726 00727 // ---------------------------------------------------------------------- 00728 00729 void KOpenWithDialog::slotTerminalToggled(bool) 00730 { 00731 // ### indicate that default value was overridden 00732 d->m_terminaldirty = true; 00733 d->nocloseonexit->setDisabled(!d->terminal->isChecked()); 00734 } 00735 00736 // ---------------------------------------------------------------------- 00737 00738 void KOpenWithDialogPrivate::_k_slotDbClick() 00739 { 00740 // check if a directory is selected 00741 if (view->isDirSel()) { 00742 return; 00743 } 00744 q->accept(); 00745 } 00746 00747 void KOpenWithDialog::setSaveNewApplications(bool b) 00748 { 00749 d->saveNewApps = b; 00750 } 00751 00752 static QString simplifiedExecLineFromService(const QString& fullExec) 00753 { 00754 QString exec = fullExec; 00755 exec.remove("%u", Qt::CaseInsensitive); 00756 exec.remove("%f", Qt::CaseInsensitive); 00757 exec.remove("-caption %c"); 00758 exec.remove("-caption \"%c\""); 00759 exec.remove("%i"); 00760 exec.remove("%m"); 00761 return exec.simplified(); 00762 } 00763 00764 void KOpenWithDialogPrivate::addToMimeAppsList(const QString& serviceId /*menu id or storage id*/) 00765 { 00766 KSharedConfig::Ptr profile = KSharedConfig::openConfig("mimeapps.list", KConfig::NoGlobals, "xdgdata-apps"); 00767 KConfigGroup addedApps(profile, "Added Associations"); 00768 QStringList apps = addedApps.readXdgListEntry(qMimeType); 00769 apps.removeAll(serviceId); 00770 apps.prepend(serviceId); // make it the preferred app 00771 addedApps.writeXdgListEntry(qMimeType, apps); 00772 addedApps.sync(); 00773 00774 // Also make sure the "auto embed" setting for this mimetype is off 00775 KSharedConfig::Ptr fileTypesConfig = KSharedConfig::openConfig("filetypesrc", KConfig::NoGlobals); 00776 fileTypesConfig->group("EmbedSettings").writeEntry(QString("embed-")+qMimeType, false); 00777 fileTypesConfig->sync(); 00778 00779 kDebug(250) << "rebuilding ksycoca..."; 00780 00781 // kbuildsycoca is the one reading mimeapps.list, so we need to run it now 00782 KBuildSycocaProgressDialog::rebuildKSycoca(q); 00783 00784 m_pService = KService::serviceByStorageId(serviceId); 00785 Q_ASSERT( m_pService ); 00786 } 00787 00788 bool KOpenWithDialogPrivate::checkAccept() 00789 { 00790 const QString typedExec(edit->text()); 00791 if (typedExec.isEmpty()) 00792 return false; 00793 QString fullExec(typedExec); 00794 00795 QString serviceName; 00796 QString initialServiceName; 00797 QString preferredTerminal; 00798 QString binaryName; 00799 m_pService = curService; 00800 if (!m_pService) { 00801 // No service selected - check the command line 00802 00803 // Find out the name of the service from the command line, removing args and paths 00804 serviceName = KRun::binaryName( typedExec, true ); 00805 if (serviceName.isEmpty()) { 00806 KMessageBox::error(q, i18n("Could not extract executable name from '%1', please type a valid program name.", serviceName)); 00807 return false; 00808 } 00809 initialServiceName = serviceName; 00810 // Also remember the binaryName with a path, if any, for the 00811 // check that the binary exists. 00812 binaryName = KRun::binaryName(typedExec, false); 00813 kDebug(250) << "initialServiceName=" << initialServiceName << "binaryName=" << binaryName; 00814 int i = 1; // We have app, app-2, app-3... Looks better for the user. 00815 bool ok = false; 00816 // Check if there's already a service by that name, with the same Exec line 00817 do { 00818 kDebug(250) << "looking for service" << serviceName; 00819 KService::Ptr serv = KService::serviceByDesktopName( serviceName ); 00820 ok = !serv; // ok if no such service yet 00821 // also ok if we find the exact same service (well, "kwrite" == "kwrite %U") 00822 if (serv) { 00823 if (serv->isApplication()) { 00824 /*kDebug(250) << "typedExec=" << typedExec 00825 << "serv->exec=" << serv->exec() 00826 << "simplifiedExecLineFromService=" << simplifiedExecLineFromService(fullExec);*/ 00827 if (typedExec == simplifiedExecLineFromService(serv->exec())) { 00828 ok = true; 00829 m_pService = serv; 00830 kDebug(250) << "OK, found identical service: " << serv->entryPath(); 00831 } else { 00832 kDebug(250) << "Exec line differs, service says:" << simplifiedExecLineFromService(fullExec); 00833 } 00834 } else { 00835 kDebug(250) << "Found, but not an application:" << serv->entryPath(); 00836 } 00837 } 00838 if (!ok) { // service was found, but it was different -> keep looking 00839 ++i; 00840 serviceName = initialServiceName + '-' + QString::number(i); 00841 } 00842 } while (!ok); 00843 } 00844 if ( m_pService ) { 00845 // Existing service selected 00846 serviceName = m_pService->name(); 00847 initialServiceName = serviceName; 00848 fullExec = m_pService->exec(); 00849 } else { 00850 // Ensure that the typed binary name actually exists (#81190) 00851 if (KStandardDirs::findExe(binaryName).isEmpty()) { 00852 KMessageBox::error(q, i18n("'%1' not found, please type a valid program name.", binaryName)); 00853 return false; 00854 } 00855 } 00856 00857 if (terminal->isChecked()) { 00858 KConfigGroup confGroup( KGlobal::config(), QString::fromLatin1("General") ); 00859 preferredTerminal = confGroup.readPathEntry("TerminalApplication", QString::fromLatin1("konsole")); 00860 m_command = preferredTerminal; 00861 // only add --noclose when we are sure it is konsole we're using 00862 if (preferredTerminal == "konsole" && nocloseonexit->isChecked()) 00863 m_command += QString::fromLatin1(" --noclose"); 00864 m_command += QString::fromLatin1(" -e "); 00865 m_command += edit->text(); 00866 kDebug(250) << "Setting m_command to" << m_command; 00867 } 00868 if ( m_pService && terminal->isChecked() != m_pService->terminal() ) 00869 m_pService = 0; // It's not exactly this service we're running 00870 00871 00872 const bool bRemember = remember && remember->isChecked(); 00873 kDebug(250) << "bRemember=" << bRemember << "service found=" << m_pService; 00874 if (m_pService) { 00875 if (bRemember) { 00876 // Associate this app with qMimeType in mimeapps.list 00877 Q_ASSERT(!qMimeType.isEmpty()); // we don't show the remember checkbox otherwise 00878 addToMimeAppsList(m_pService->storageId()); 00879 } 00880 } else { 00881 const bool createDesktopFile = bRemember || saveNewApps; 00882 if (!createDesktopFile) { 00883 // Create temp service 00884 m_pService = new KService(initialServiceName, fullExec, QString()); 00885 if (terminal->isChecked()) { 00886 m_pService->setTerminal(true); 00887 // only add --noclose when we are sure it is konsole we're using 00888 if (preferredTerminal == "konsole" && nocloseonexit->isChecked()) 00889 m_pService->setTerminalOptions("--noclose"); 00890 } 00891 } else { 00892 // If we got here, we can't seem to find a service for what they wanted. Create one. 00893 00894 QString menuId; 00895 QString newPath = KService::newServicePath(false /* ignored argument */, serviceName, &menuId); 00896 kDebug(250) << "Creating new service" << serviceName << "(" << newPath << ")" << "menuId=" << menuId; 00897 00898 KDesktopFile desktopFile(newPath); 00899 KConfigGroup cg = desktopFile.desktopGroup(); 00900 cg.writeEntry("Type", "Application"); 00901 cg.writeEntry("Name", initialServiceName); 00902 cg.writeEntry("Exec", fullExec); 00903 cg.writeEntry("NoDisplay", true); // don't make it appear in the K menu 00904 if (terminal->isChecked()) { 00905 cg.writeEntry("Terminal", true); 00906 // only add --noclose when we are sure it is konsole we're using 00907 if (preferredTerminal == "konsole" && nocloseonexit->isChecked()) 00908 cg.writeEntry("TerminalOptions", "--noclose"); 00909 } 00910 cg.writeXdgListEntry("MimeType", QStringList() << qMimeType); 00911 cg.sync(); 00912 00913 addToMimeAppsList(menuId); 00914 } 00915 } 00916 00917 saveComboboxHistory(); 00918 return true; 00919 } 00920 00921 void KOpenWithDialog::accept() 00922 { 00923 if (d->checkAccept()) 00924 KDialog::accept(); 00925 } 00926 00927 QString KOpenWithDialog::text() const 00928 { 00929 if (!d->m_command.isEmpty()) 00930 return d->m_command; 00931 else 00932 return d->edit->text(); 00933 } 00934 00935 void KOpenWithDialog::hideNoCloseOnExit() 00936 { 00937 // uncheck the checkbox because the value could be used when "Run in Terminal" is selected 00938 d->nocloseonexit->setChecked(false); 00939 d->nocloseonexit->hide(); 00940 } 00941 00942 void KOpenWithDialog::hideRunInTerminal() 00943 { 00944 d->terminal->hide(); 00945 hideNoCloseOnExit(); 00946 } 00947 00948 KService::Ptr KOpenWithDialog::service() const 00949 { 00950 return d->m_pService; 00951 } 00952 00953 void KOpenWithDialogPrivate::saveComboboxHistory() 00954 { 00955 KHistoryComboBox *combo = static_cast<KHistoryComboBox*>(edit->comboBox()); 00956 if (combo) { 00957 combo->addToHistory(edit->text()); 00958 00959 KConfigGroup cg( KGlobal::config(), QString::fromLatin1("Open-with settings") ); 00960 cg.writeEntry( "History", combo->historyItems() ); 00961 writeEntry( cg, "CompletionMode", combo->completionMode() ); 00962 // don't store the completion-list, as it contains all of KUrlCompletion's 00963 // executables 00964 cg.sync(); 00965 } 00966 } 00967 00968 #include "kopenwithdialog.moc" 00969 #include "kopenwithdialog_p.moc"
KDE 4.6 API Reference