1/* This file is part of the KDE project
2 Copyright (C) 2003 Waldo Bastian <bastian@kde.org>
3 Copyright (C) 2003 David Faure <faure@kde.org>
4 Copyright (C) 2002 Daniel Molkentin <molkentin@kde.org>
5
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public
8 License version 2 as published by the Free Software Foundation.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; see the file COPYING. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
19*/
20
21// Own
22#include "kservicelistwidget.h"
23
24// std
25#include <unistd.h>
26
27// Qt
28#include <QLayout>
29#include <QHBoxLayout>
30
31// KDE
32#include <kapplication.h>
33#include <kdebug.h>
34#include <klocale.h>
35#include <kmessagebox.h>
36#include <knotification.h>
37#include <kopenwithdialog.h>
38#include <kpropertiesdialog.h>
39#include <kpushbutton.h>
40#include <kicon.h>
41#include <kstandarddirs.h>
42
43// Local
44#include "kserviceselectdlg.h"
45#include "mimetypedata.h"
46
47KServiceListItem::KServiceListItem( const KService::Ptr& pService, int kind )
48 : QListWidgetItem(), storageId(pService->storageId()), desktopPath(pService->entryPath())
49{
50 if ( kind == KServiceListWidget::SERVICELIST_APPLICATIONS )
51 setText( pService->name() );
52 else
53 setText( i18n( "%1 (%2)", pService->name(), pService->desktopEntryName() ) );
54
55 if (!pService->isApplication())
56 localPath = KStandardDirs::locateLocal("services", desktopPath);
57 else
58 localPath = pService->locateLocal();
59}
60
61bool KServiceListItem::isImmutable() const
62{
63 return !KStandardDirs::checkAccess(localPath, W_OK);
64}
65
66
67
68
69
70KServiceListWidget::KServiceListWidget(int kind, QWidget *parent)
71 : QGroupBox( kind == SERVICELIST_APPLICATIONS ? i18n("Application Preference Order")
72 : i18n("Services Preference Order"), parent ),
73 m_kind( kind ), m_mimeTypeData( 0L )
74{
75 QHBoxLayout *lay= new QHBoxLayout(this);
76
77 servicesLB = new QListWidget(this);
78 connect(servicesLB, SIGNAL(itemSelectionChanged()), SLOT(enableMoveButtons()));
79 lay->addWidget(servicesLB);
80 connect( servicesLB, SIGNAL( itemDoubleClicked(QListWidgetItem*)), this, SLOT( editService()));
81
82 QString wtstr =
83 (kind == SERVICELIST_APPLICATIONS ?
84 i18n("This is a list of applications associated with files of the selected"
85 " file type. This list is shown in Konqueror's context menus when you select"
86 " \"Open With...\". If more than one application is associated with this file type,"
87 " then the list is ordered by priority with the uppermost item taking precedence"
88 " over the others.") :
89 i18n("This is a list of services associated with files of the selected"
90 " file type. This list is shown in Konqueror's context menus when you select"
91 " a \"Preview with...\" option. If more than one service is associated with this file type,"
92 " then the list is ordered by priority with the uppermost item taking precedence"
93 " over the others."));
94
95 setWhatsThis( wtstr );
96 servicesLB->setWhatsThis( wtstr );
97
98 QVBoxLayout *btnsLay= new QVBoxLayout();
99 lay->addLayout(btnsLay);
100
101 servUpButton = new KPushButton(i18n("Move &Up"), this);
102 servUpButton->setIcon(KIcon("arrow-up"));
103 servUpButton->setEnabled(false);
104 connect(servUpButton, SIGNAL(clicked()), SLOT(promoteService()));
105 btnsLay->addWidget(servUpButton);
106
107 servUpButton->setWhatsThis( kind == SERVICELIST_APPLICATIONS ?
108 i18n("Assigns a higher priority to the selected\n"
109 "application, moving it up in the list. Note: This\n"
110 "only affects the selected application if the file type is\n"
111 "associated with more than one application.") :
112 i18n("Assigns a higher priority to the selected\n"
113 "service, moving it up in the list."));
114
115 servDownButton = new KPushButton(i18n("Move &Down"), this);
116 servDownButton->setIcon(KIcon("arrow-down"));
117 servDownButton->setEnabled(false);
118 connect(servDownButton, SIGNAL(clicked()), SLOT(demoteService()));
119 btnsLay->addWidget(servDownButton);
120 servDownButton->setWhatsThis( kind == SERVICELIST_APPLICATIONS ?
121 i18n("Assigns a lower priority to the selected\n"
122 "application, moving it down in the list. Note: This \n"
123 "only affects the selected application if the file type is\n"
124 "associated with more than one application."):
125 i18n("Assigns a lower priority to the selected\n"
126 "service, moving it down in the list."));
127
128 servNewButton = new KPushButton(i18n("Add..."), this);
129 servNewButton->setIcon(KIcon("list-add"));
130 servNewButton->setEnabled(false);
131 connect(servNewButton, SIGNAL(clicked()), SLOT(addService()));
132 btnsLay->addWidget(servNewButton);
133 servNewButton->setWhatsThis( i18n( "Add a new application for this file type." ) );
134
135
136 servEditButton = new KPushButton(i18n("Edit..."), this);
137 servEditButton->setIcon(KIcon("edit-rename"));
138 servEditButton->setEnabled(false);
139 connect(servEditButton, SIGNAL(clicked()), SLOT(editService()));
140 btnsLay->addWidget(servEditButton);
141 servEditButton->setWhatsThis( i18n( "Edit command line of the selected application." ) );
142
143
144 servRemoveButton = new KPushButton(i18n("Remove"), this);
145 servRemoveButton->setIcon(KIcon("list-remove"));
146 servRemoveButton->setEnabled(false);
147 connect(servRemoveButton, SIGNAL(clicked()), SLOT(removeService()));
148 btnsLay->addWidget(servRemoveButton);
149 servRemoveButton->setWhatsThis( i18n( "Remove the selected application from the list." ) );
150
151 btnsLay->addStretch(1);
152}
153
154void KServiceListWidget::setMimeTypeData( MimeTypeData * mimeTypeData )
155{
156 m_mimeTypeData = mimeTypeData;
157 if ( servNewButton )
158 servNewButton->setEnabled(true);
159 // will need a selection
160 servUpButton->setEnabled(false);
161 servDownButton->setEnabled(false);
162
163 servicesLB->clear();
164 servicesLB->setEnabled(false);
165
166 if (m_mimeTypeData) {
167 const QStringList services = ( m_kind == SERVICELIST_APPLICATIONS )
168 ? m_mimeTypeData->appServices()
169 : m_mimeTypeData->embedServices();
170
171 if (services.isEmpty()) {
172 if (m_kind == SERVICELIST_APPLICATIONS)
173 servicesLB->addItem(i18nc("No applications associated with this file type", "None"));
174 else
175 servicesLB->addItem(i18nc("No components associated with this file type", "None"));
176 } else {
177 Q_FOREACH(const QString& service, services) {
178 KService::Ptr pService = KService::serviceByStorageId(service);
179 if (pService)
180 servicesLB->addItem( new KServiceListItem(pService, m_kind) );
181 }
182 servicesLB->setEnabled(true);
183 }
184 }
185
186 if (servRemoveButton)
187 servRemoveButton->setEnabled(servicesLB->currentRow() > -1);
188 if (servEditButton)
189 servEditButton->setEnabled(servicesLB->currentRow() > -1);
190}
191
192void KServiceListWidget::promoteService()
193{
194 if (!servicesLB->isEnabled()) {
195 KNotification::beep();
196 return;
197 }
198
199 int selIndex = servicesLB->currentRow();
200 if (selIndex == 0) {
201 KNotification::beep();
202 return;
203 }
204
205 QListWidgetItem *selItem = servicesLB->item(selIndex);
206 servicesLB->takeItem(selIndex);
207 servicesLB->insertItem(selIndex-1,selItem);
208 servicesLB->setCurrentRow(selIndex - 1);
209
210 updatePreferredServices();
211
212 emit changed(true);
213}
214
215void KServiceListWidget::demoteService()
216{
217 if (!servicesLB->isEnabled()) {
218 KNotification::beep();
219 return;
220 }
221
222 int selIndex = servicesLB->currentRow();
223 if (selIndex == servicesLB->count() - 1) {
224 KNotification::beep();
225 return;
226 }
227
228 QListWidgetItem *selItem = servicesLB->item(selIndex);
229 servicesLB->takeItem(selIndex);
230 servicesLB->insertItem(selIndex + 1, selItem);
231 servicesLB->setCurrentRow(selIndex + 1);
232
233 updatePreferredServices();
234
235 emit changed(true);
236}
237
238void KServiceListWidget::addService()
239{
240 if (!m_mimeTypeData)
241 return;
242
243 KService::Ptr service;
244 if ( m_kind == SERVICELIST_APPLICATIONS )
245 {
246 KOpenWithDialog dlg(m_mimeTypeData->name(), QString(), this);
247 dlg.setSaveNewApplications(true);
248 if (dlg.exec() != QDialog::Accepted)
249 return;
250
251 service = dlg.service();
252
253 Q_ASSERT(service);
254 if (!service)
255 return; // Don't crash if KOpenWith wasn't able to create service.
256 }
257 else
258 {
259 KServiceSelectDlg dlg(m_mimeTypeData->name(), QString(), this);
260 if (dlg.exec() != QDialog::Accepted)
261 return;
262 service = dlg.service();
263 Q_ASSERT(service);
264 if (!service)
265 return;
266 }
267
268 // Did the list simply show "None"?
269 const bool hadDummyEntry = ( m_kind == SERVICELIST_APPLICATIONS )
270 ? m_mimeTypeData->appServices().isEmpty()
271 : m_mimeTypeData->embedServices().isEmpty();
272
273 if (hadDummyEntry) {
274 delete servicesLB->takeItem(0); // Remove the "None" item.
275 servicesLB->setEnabled(true);
276 } else {
277 // check if it is a duplicate entry
278 for (int index = 0; index < servicesLB->count(); index++) {
279 if (static_cast<KServiceListItem*>( servicesLB->item(index) )->desktopPath
280 == service->entryPath()) {
281 // ##### shouldn't we make the existing entry the default one?
282 return;
283 }
284 }
285 }
286
287 servicesLB->insertItem(0, new KServiceListItem(service, m_kind));
288 servicesLB->setCurrentItem(0);
289
290 updatePreferredServices();
291
292 emit changed(true);
293}
294
295void KServiceListWidget::editService()
296{
297 if (!m_mimeTypeData)
298 return;
299 const int selected = servicesLB->currentRow();
300 if (selected < 0)
301 return;
302
303 // Only edit applications, not services as
304 // they don't have any parameters
305 if (m_kind != SERVICELIST_APPLICATIONS)
306 return;
307
308 // Just like popping up an add dialog except that we
309 // pass the current command line as a default
310 KServiceListItem *selItem = (KServiceListItem*)servicesLB->item(selected);
311 const QString desktopPath = selItem->desktopPath;
312
313 KService::Ptr service = KService::serviceByDesktopPath(desktopPath);
314 if (!service)
315 return;
316
317 QString path = service->entryPath();
318
319 // If the path to the desktop file is relative, try to get the full
320 // path from KStandardDirs.
321 path = KStandardDirs::locate("apps", path); // TODO use xdgdata-apps instead?
322
323 KFileItem item(KUrl(path), "application/x-desktop", KFileItem::Unknown);
324 KPropertiesDialog dlg(item, this);
325 if (dlg.exec() != QDialog::Accepted)
326 return;
327
328 // Note that at this point, ksycoca has been updated,
329 // and setMimeTypeData has been called again, so all the items have been recreated.
330
331 // Reload service
332 service = KService::serviceByDesktopPath(desktopPath);
333 if (!service)
334 return;
335
336 // Remove the old one...
337 delete servicesLB->takeItem(selected);
338
339 // ...check that it's not a duplicate entry...
340 bool addIt = true;
341 for (int index = 0; index < servicesLB->count(); index++) {
342 if (static_cast<KServiceListItem*>(servicesLB->item(index))->desktopPath
343 == service->entryPath()) {
344 addIt = false;
345 break;
346 }
347 }
348
349 // ...and add it in the same place as the old one:
350 if (addIt) {
351 servicesLB->insertItem(selected, new KServiceListItem(service, m_kind));
352 servicesLB->setCurrentRow(selected);
353 }
354
355 updatePreferredServices();
356
357 emit changed(true);
358}
359
360void KServiceListWidget::removeService()
361{
362 if (!m_mimeTypeData) return;
363
364 int selected = servicesLB->currentRow();
365
366 if ( selected >= 0 ) {
367 // Check if service is associated with this mimetype or with one of its parents
368 KServiceListItem *serviceItem = static_cast<KServiceListItem *>(servicesLB->item(selected));
369 if (serviceItem->isImmutable())
370 {
371 KMessageBox::sorry(this, i18n("You are not authorized to remove this service."));
372 }
373 else
374 {
375 delete servicesLB->takeItem( selected );
376 updatePreferredServices();
377
378 emit changed(true);
379 }
380 }
381
382 // Update buttons and service list again (e.g. to re-add "None")
383 setMimeTypeData(m_mimeTypeData);
384}
385
386void KServiceListWidget::updatePreferredServices()
387{
388 if (!m_mimeTypeData)
389 return;
390 QStringList sl;
391 unsigned int count = servicesLB->count();
392
393 for (unsigned int i = 0; i < count; i++) {
394 KServiceListItem *sli = (KServiceListItem *) servicesLB->item(i);
395 sl.append( sli->storageId );
396 }
397 sl.removeDuplicates();
398 if ( m_kind == SERVICELIST_APPLICATIONS )
399 m_mimeTypeData->setAppServices(sl);
400 else
401 m_mimeTypeData->setEmbedServices(sl);
402}
403
404void KServiceListWidget::enableMoveButtons()
405{
406 int idx = servicesLB->currentRow();
407 if (servicesLB->model()->rowCount() <= 1)
408 {
409 servUpButton->setEnabled(false);
410 servDownButton->setEnabled(false);
411 }
412 else if ( idx == (servicesLB->model()->rowCount() - 1) )
413 {
414 servUpButton->setEnabled(true);
415 servDownButton->setEnabled(false);
416 }
417 else if (idx == 0)
418 {
419 servUpButton->setEnabled(false);
420 servDownButton->setEnabled(true);
421 }
422 else
423 {
424 servUpButton->setEnabled(true);
425 servDownButton->setEnabled(true);
426 }
427
428 if ( servRemoveButton )
429 servRemoveButton->setEnabled(true);
430
431 if ( servEditButton )
432 servEditButton->setEnabled( m_kind == SERVICELIST_APPLICATIONS );
433}
434
435#include "kservicelistwidget.moc"
436