1// -*- c++ -*-
2/* This file is part of the KDE libraries
3 Copyright (C) 1997, 1998 Richard Moore <rich@kde.org>
4 1998 Stephan Kulow <coolo@kde.org>
5 1998 Daniel Grana <grana@ie.iwi.unibe.ch>
6 1999,2000,2001,2002,2003 Carsten Pfeiffer <pfeiffer@kde.org>
7 2003 Clarence Dang <dang@kde.org>
8 2007 David Faure <faure@kde.org>
9 2008 Rafael Fernández López <ereslibre@kde.org>
10
11 This library is free software; you can redistribute it and/or
12 modify it under the terms of the GNU Library General Public
13 License as published by the Free Software Foundation; either
14 version 2 of the License, or (at your option) any later version.
15
16 This library is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 Library General Public License for more details.
20
21 You should have received a copy of the GNU Library General Public License
22 along with this library; see the file COPYING.LIB. If not, write to
23 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24 Boston, MA 02110-1301, USA.
25*/
26
27#include "kfilewidget.h"
28
29#include "kfileplacesview.h"
30#include "kfileplacesmodel.h"
31#include "kfilebookmarkhandler_p.h"
32#include "kurlcombobox.h"
33#include "kurlnavigator.h"
34#include "kfilepreviewgenerator.h"
35#include <config-kfile.h>
36
37#include <kactioncollection.h>
38#include <kdiroperator.h>
39#include <kdirselectdialog.h>
40#include <kfilefiltercombo.h>
41#include <kimagefilepreview.h>
42#include <kmenu.h>
43#include <kmimetype.h>
44#include <kpushbutton.h>
45#include <krecentdocument.h>
46#include <ktoolbar.h>
47#include <kurlcompletion.h>
48#include <kuser.h>
49#include <kprotocolmanager.h>
50#include <kio/job.h>
51#include <kio/jobuidelegate.h>
52#include <kio/netaccess.h>
53#include <kio/scheduler.h>
54#include <krecentdirs.h>
55#include <kdebug.h>
56#include <kio/kfileitemdelegate.h>
57#include <kde_file.h>
58
59#include <QtGui/QCheckBox>
60#include <QtGui/QDockWidget>
61#include <QtGui/QLayout>
62#include <QtGui/QLabel>
63#include <QtGui/QLineEdit>
64#include <QtGui/QSplitter>
65#include <QtGui/QAbstractProxyModel>
66#include <QtGui/QHelpEvent>
67#include <QtGui/QApplication>
68#include <QtCore/QFSFileEngine>
69#include <kshell.h>
70#include <kmessagebox.h>
71#include <kauthorized.h>
72
73class KFileWidgetPrivate
74{
75public:
76 KFileWidgetPrivate(KFileWidget *widget)
77 : q(widget),
78 boxLayout(0),
79 placesDock(0),
80 placesView(0),
81 placesViewSplitter(0),
82 placesViewWidth(-1),
83 labeledCustomWidget(0),
84 bottomCustomWidget(0),
85 autoSelectExtCheckBox(0),
86 operationMode(KFileWidget::Opening),
87 bookmarkHandler(0),
88 toolbar(0),
89 locationEdit(0),
90 ops(0),
91 filterWidget(0),
92 autoSelectExtChecked(false),
93 keepLocation(false),
94 hasView(false),
95 hasDefaultFilter(false),
96 inAccept(false),
97 dummyAdded(false),
98 confirmOverwrite(false),
99 differentHierarchyLevelItemsEntered(false),
100 previewGenerator(0),
101 iconSizeSlider(0)
102 {
103 }
104
105 ~KFileWidgetPrivate()
106 {
107 delete bookmarkHandler; // Should be deleted before ops!
108 delete ops;
109 }
110
111 void updateLocationWhatsThis();
112 void updateAutoSelectExtension();
113 void initSpeedbar();
114 void initGUI();
115 void readViewConfig();
116 void writeViewConfig();
117 void setNonExtSelection();
118 void setLocationText(const KUrl&);
119 void setLocationText(const KUrl::List&);
120 void appendExtension(KUrl &url);
121 void updateLocationEditExtension(const QString &);
122 void updateFilter();
123 KUrl::List& parseSelectedUrls();
124 /**
125 * Parses the string "line" for files. If line doesn't contain any ", the
126 * whole line will be interpreted as one file. If the number of " is odd,
127 * an empty list will be returned. Otherwise, all items enclosed in " "
128 * will be returned as correct urls.
129 */
130 KUrl::List tokenize(const QString& line) const;
131 /**
132 * Reads the recent used files and inserts them into the location combobox
133 */
134 void readRecentFiles();
135 /**
136 * Saves the entries from the location combobox.
137 */
138 void saveRecentFiles();
139 /**
140 * called when an item is highlighted/selected in multiselection mode.
141 * handles setting the locationEdit.
142 */
143 void multiSelectionChanged();
144
145 /**
146 * Returns the absolute version of the URL specified in locationEdit.
147 */
148 KUrl getCompleteUrl(const QString&) const;
149
150 /**
151 * Sets the dummy entry on the history combo box. If the dummy entry
152 * already exists, it is overwritten with this information.
153 */
154 void setDummyHistoryEntry(const QString& text, const QPixmap& icon = QPixmap(),
155 bool usePreviousPixmapIfNull = true);
156
157 /**
158 * Removes the dummy entry of the history combo box.
159 */
160 void removeDummyHistoryEntry();
161
162 /**
163 * Asks for overwrite confirmation using a KMessageBox and returns
164 * true if the user accepts.
165 *
166 * @since 4.2
167 */
168 bool toOverwrite(const KUrl&);
169
170 // private slots
171 void _k_slotLocationChanged( const QString& );
172 void _k_urlEntered( const KUrl& );
173 void _k_enterUrl( const KUrl& );
174 void _k_enterUrl( const QString& );
175 void _k_locationAccepted( const QString& );
176 void _k_slotFilterChanged();
177 void _k_fileHighlighted( const KFileItem& );
178 void _k_fileSelected( const KFileItem& );
179 void _k_slotLoadingFinished();
180 void _k_fileCompletion( const QString& );
181 void _k_toggleSpeedbar( bool );
182 void _k_toggleBookmarks( bool );
183 void _k_slotAutoSelectExtClicked();
184 void _k_placesViewSplitterMoved(int, int);
185 void _k_activateUrlNavigator();
186 void _k_zoomOutIconsSize();
187 void _k_zoomInIconsSize();
188 void _k_slotIconSizeSliderMoved(int);
189 void _k_slotIconSizeChanged(int);
190
191 void addToRecentDocuments();
192
193 QString locationEditCurrentText() const;
194
195 /**
196 * KIO::NetAccess::mostLocalUrl local replacement.
197 * This method won't show any progress dialogs for stating, since
198 * they are very annoying when stating.
199 */
200 KUrl mostLocalUrl(const KUrl &url);
201
202 void setInlinePreviewShown(bool show);
203
204 KFileWidget* q;
205
206 // the last selected url
207 KUrl url;
208
209 // the selected filenames in multiselection mode -- FIXME
210 QString filenames;
211
212 // now following all kind of widgets, that I need to rebuild
213 // the geometry management
214 QBoxLayout *boxLayout;
215 QGridLayout *lafBox;
216 QVBoxLayout *vbox;
217
218 QLabel *locationLabel;
219 QWidget *opsWidget;
220 QWidget *pathSpacer;
221
222 QLabel *filterLabel;
223 KUrlNavigator *urlNavigator;
224 KPushButton *okButton, *cancelButton;
225 QDockWidget *placesDock;
226 KFilePlacesView *placesView;
227 QSplitter *placesViewSplitter;
228 // caches the places view width. This value will be updated when the splitter
229 // is moved. This allows us to properly set a value when the dialog itself
230 // is resized
231 int placesViewWidth;
232
233 QWidget *labeledCustomWidget;
234 QWidget *bottomCustomWidget;
235
236 // Automatically Select Extension stuff
237 QCheckBox *autoSelectExtCheckBox;
238 QString extension; // current extension for this filter
239
240 QList<KIO::StatJob*> statJobs;
241
242 KUrl::List urlList; //the list of selected urls
243
244 KFileWidget::OperationMode operationMode;
245
246 // The file class used for KRecentDirs
247 QString fileClass;
248
249 KFileBookmarkHandler *bookmarkHandler;
250
251 KActionMenu* bookmarkButton;
252
253 KToolBar *toolbar;
254 KUrlComboBox *locationEdit;
255 KDirOperator *ops;
256 KFileFilterCombo *filterWidget;
257 QTimer filterDelayTimer;
258
259 KFilePlacesModel *model;
260
261 // whether or not the _user_ has checked the above box
262 bool autoSelectExtChecked : 1;
263
264 // indicates if the location edit should be kept or cleared when changing
265 // directories
266 bool keepLocation : 1;
267
268 // the KDirOperators view is set in KFileWidget::show(), so to avoid
269 // setting it again and again, we have this nice little boolean :)
270 bool hasView : 1;
271
272 bool hasDefaultFilter : 1; // necessary for the operationMode
273 bool autoDirectoryFollowing : 1;
274 bool inAccept : 1; // true between beginning and end of accept()
275 bool dummyAdded : 1; // if the dummy item has been added. This prevents the combo from having a
276 // blank item added when loaded
277 bool confirmOverwrite : 1;
278 bool differentHierarchyLevelItemsEntered;
279
280 KFilePreviewGenerator *previewGenerator;
281 QSlider *iconSizeSlider;
282
283 // The group which stores app-specific settings. These settings are recent
284 // files and urls. Visual settings (view mode, sorting criteria...) are not
285 // app-specific and are stored in kdeglobals
286 KConfigGroup configGroup; };
287
288K_GLOBAL_STATIC(KUrl, lastDirectory) // to set the start path
289
290static const char autocompletionWhatsThisText[] = I18N_NOOP("<qt>While typing in the text area, you may be presented "
291 "with possible matches. "
292 "This feature can be controlled by clicking with the right mouse button "
293 "and selecting a preferred mode from the <b>Text Completion</b> menu.</qt>");
294
295// returns true if the string contains "<a>:/" sequence, where <a> is at least 2 alpha chars
296static bool containsProtocolSection( const QString& string )
297{
298 int len = string.length();
299 static const char prot[] = ":/";
300 for (int i=0; i < len;) {
301 i = string.indexOf( QLatin1String(prot), i );
302 if (i == -1)
303 return false;
304 int j=i-1;
305 for (; j >= 0; j--) {
306 const QChar& ch( string[j] );
307 if (ch.toLatin1() == 0 || !ch.isLetter())
308 break;
309 if (ch.isSpace() && (i-j-1) >= 2)
310 return true;
311 }
312 if (j < 0 && i >= 2)
313 return true; // at least two letters before ":/"
314 i += 3; // skip : and / and one char
315 }
316 return false;
317}
318
319KFileWidget::KFileWidget( const KUrl& _startDir, QWidget *parent )
320 : QWidget(parent), KAbstractFileWidget(), d(new KFileWidgetPrivate(this))
321{
322 KUrl startDir(_startDir);
323 kDebug(kfile_area) << "startDir" << startDir;
324 QString filename;
325
326 d->okButton = new KPushButton(KStandardGuiItem::ok(), this);
327 d->okButton->setDefault(true);
328 d->cancelButton = new KPushButton(KStandardGuiItem::cancel(), this);
329 // The dialog shows them
330 d->okButton->hide();
331 d->cancelButton->hide();
332
333 d->opsWidget = new QWidget(this);
334 QVBoxLayout *opsWidgetLayout = new QVBoxLayout(d->opsWidget);
335 opsWidgetLayout->setMargin(0);
336 opsWidgetLayout->setSpacing(0);
337 //d->toolbar = new KToolBar(this, true);
338 d->toolbar = new KToolBar(d->opsWidget, true);
339 d->toolbar->setObjectName("KFileWidget::toolbar");
340 d->toolbar->setMovable(false);
341 opsWidgetLayout->addWidget(d->toolbar);
342
343 d->model = new KFilePlacesModel(this);
344
345 // Resolve this now so that a 'kfiledialog:' URL, if specified,
346 // does not get inserted into the urlNavigator history.
347 d->url = getStartUrl( startDir, d->fileClass, filename );
348 startDir = d->url;
349
350 // Don't pass startDir to the KUrlNavigator at this stage: as well as
351 // the above, it may also contain a file name which should not get
352 // inserted in that form into the old-style navigation bar history.
353 // Wait until the KIO::stat has been done later.
354 //
355 // The stat cannot be done before this point, bug 172678.
356 d->urlNavigator = new KUrlNavigator(d->model, KUrl(), d->opsWidget); //d->toolbar);
357 d->urlNavigator->setPlacesSelectorVisible(false);
358 opsWidgetLayout->addWidget(d->urlNavigator);
359
360 KUrl u;
361 KUrlComboBox *pathCombo = d->urlNavigator->editor();
362#ifdef Q_WS_WIN
363 foreach( const QFileInfo &drive,QFSFileEngine::drives() )
364 {
365 u.setPath( drive.filePath() );
366 pathCombo->addDefaultUrl(u,
367 KIO::pixmapForUrl( u, 0, KIconLoader::Small ),
368 i18n("Drive: %1", u.toLocalFile()));
369 }
370#else
371 u.setPath(QDir::rootPath());
372 pathCombo->addDefaultUrl(u,
373 KIO::pixmapForUrl(u, 0, KIconLoader::Small),
374 u.toLocalFile());
375#endif
376
377 u.setPath(QDir::homePath());
378 pathCombo->addDefaultUrl(u, KIO::pixmapForUrl(u, 0, KIconLoader::Small),
379 u.path(KUrl::AddTrailingSlash));
380
381 KUrl docPath;
382 docPath.setPath( KGlobalSettings::documentPath() );
383 if ( (u.path(KUrl::AddTrailingSlash) != docPath.path(KUrl::AddTrailingSlash)) &&
384 QDir(docPath.path(KUrl::AddTrailingSlash)).exists() )
385 {
386 pathCombo->addDefaultUrl( docPath,
387 KIO::pixmapForUrl( docPath, 0, KIconLoader::Small ),
388 docPath.path(KUrl::AddTrailingSlash));
389 }
390
391 u.setPath( KGlobalSettings::desktopPath() );
392 pathCombo->addDefaultUrl(u,
393 KIO::pixmapForUrl(u, 0, KIconLoader::Small),
394 u.path(KUrl::AddTrailingSlash));
395
396 d->ops = new KDirOperator(KUrl(), d->opsWidget);
397 d->ops->setObjectName( "KFileWidget::ops" );
398 d->ops->setIsSaving(d->operationMode == Saving);
399 opsWidgetLayout->addWidget(d->ops);
400 connect(d->ops, SIGNAL(urlEntered(KUrl)),
401 SLOT(_k_urlEntered(KUrl)));
402 connect(d->ops, SIGNAL(fileHighlighted(KFileItem)),
403 SLOT(_k_fileHighlighted(KFileItem)));
404 connect(d->ops, SIGNAL(fileSelected(KFileItem)),
405 SLOT(_k_fileSelected(KFileItem)));
406 connect(d->ops, SIGNAL(finishedLoading()),
407 SLOT(_k_slotLoadingFinished()));
408
409 d->ops->setupMenu(KDirOperator::SortActions |
410 KDirOperator::FileActions |
411 KDirOperator::ViewActions);
412 KActionCollection *coll = d->ops->actionCollection();
413 coll->addAssociatedWidget(this);
414
415 // add nav items to the toolbar
416 //
417 // NOTE: The order of the button icons here differs from that
418 // found in the file manager and web browser, but has been discussed
419 // and agreed upon on the kde-core-devel mailing list:
420 //
421 // http://lists.kde.org/?l=kde-core-devel&m=116888382514090&w=2
422
423 coll->action( "up" )->setWhatsThis(i18n("<qt>Click this button to enter the parent folder.<br /><br />"
424 "For instance, if the current location is file:/home/%1 clicking this "
425 "button will take you to file:/home.</qt>", KUser().loginName() ));
426
427 coll->action( "back" )->setWhatsThis(i18n("Click this button to move backwards one step in the browsing history."));
428 coll->action( "forward" )->setWhatsThis(i18n("Click this button to move forward one step in the browsing history."));
429
430 coll->action( "reload" )->setWhatsThis(i18n("Click this button to reload the contents of the current location."));
431 coll->action( "mkdir" )->setShortcut( QKeySequence(Qt::Key_F10) );
432 coll->action( "mkdir" )->setWhatsThis(i18n("Click this button to create a new folder."));
433
434 KAction *goToNavigatorAction = coll->addAction( "gotonavigator", this, SLOT(_k_activateUrlNavigator()) );
435 goToNavigatorAction->setShortcut( QKeySequence(Qt::CTRL + Qt::Key_L) );
436
437 KToggleAction *showSidebarAction =
438 new KToggleAction(i18n("Show Places Navigation Panel"), this);
439 coll->addAction("toggleSpeedbar", showSidebarAction);
440 showSidebarAction->setShortcut( QKeySequence(Qt::Key_F9) );
441 connect( showSidebarAction, SIGNAL(toggled(bool)),
442 SLOT(_k_toggleSpeedbar(bool)) );
443
444 KToggleAction *showBookmarksAction =
445 new KToggleAction(i18n("Show Bookmarks"), this);
446 coll->addAction("toggleBookmarks", showBookmarksAction);
447 connect( showBookmarksAction, SIGNAL(toggled(bool)),
448 SLOT(_k_toggleBookmarks(bool)) );
449
450 KActionMenu *menu = new KActionMenu( KIcon("configure"), i18n("Options"), this);
451 coll->addAction("extra menu", menu);
452 menu->setWhatsThis(i18n("<qt>This is the preferences menu for the file dialog. "
453 "Various options can be accessed from this menu including: <ul>"
454 "<li>how files are sorted in the list</li>"
455 "<li>types of view, including icon and list</li>"
456 "<li>showing of hidden files</li>"
457 "<li>the Places navigation panel</li>"
458 "<li>file previews</li>"
459 "<li>separating folders from files</li></ul></qt>"));
460 menu->addAction(coll->action("sorting menu"));
461 menu->addAction(coll->action("view menu"));
462 menu->addSeparator();
463 menu->addAction(coll->action("decoration menu"));
464 menu->addSeparator();
465 KAction * showHidden = qobject_cast<KAction*>(coll->action( "show hidden" ));
466 if (showHidden) {
467 showHidden->setShortcut(
468 KShortcut( QKeySequence(Qt::ALT + Qt::Key_Period), QKeySequence(Qt::Key_F8) ) );
469 }
470 menu->addAction( showHidden );
471 menu->addAction( showSidebarAction );
472 menu->addAction( showBookmarksAction );
473 coll->action( "inline preview" )->setShortcut( QKeySequence(Qt::Key_F11) );
474 menu->addAction( coll->action( "preview" ));
475
476 menu->setDelayed( false );
477 connect( menu->menu(), SIGNAL(aboutToShow()),
478 d->ops, SLOT(updateSelectionDependentActions()));
479
480 d->iconSizeSlider = new QSlider(this);
481 d->iconSizeSlider->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed);
482 d->iconSizeSlider->setOrientation(Qt::Horizontal);
483 d->iconSizeSlider->setMinimum(0);
484 d->iconSizeSlider->setMaximum(100);
485 d->iconSizeSlider->installEventFilter(this);
486 connect(d->iconSizeSlider, SIGNAL(valueChanged(int)),
487 d->ops, SLOT(setIconsZoom(int)));
488 connect(d->iconSizeSlider, SIGNAL(valueChanged(int)),
489 this, SLOT(_k_slotIconSizeChanged(int)));
490 connect(d->iconSizeSlider, SIGNAL(sliderMoved(int)),
491 this, SLOT(_k_slotIconSizeSliderMoved(int)));
492 connect(d->ops, SIGNAL(currentIconSizeChanged(int)),
493 d->iconSizeSlider, SLOT(setValue(int)));
494
495 KAction *furtherAction = new KAction(KIcon("file-zoom-out"), i18n("Zoom out"), this);
496 connect(furtherAction, SIGNAL(triggered()), SLOT(_k_zoomOutIconsSize()));
497 KAction *closerAction = new KAction(KIcon("file-zoom-in"), i18n("Zoom in"), this);
498 connect(closerAction, SIGNAL(triggered()), SLOT(_k_zoomInIconsSize()));
499
500 QWidget *midSpacer = new QWidget(this);
501 midSpacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
502
503 QAction *separator = new QAction(this);
504 separator->setSeparator(true);
505
506 QAction *separator2 = new QAction(this);
507 separator2->setSeparator(true);
508
509 d->toolbar->addAction(coll->action("back" ));
510 d->toolbar->addAction(coll->action("forward"));
511 d->toolbar->addAction(coll->action("up"));
512 d->toolbar->addAction(coll->action("reload"));
513 d->toolbar->addAction(separator);
514 d->toolbar->addAction(coll->action("inline preview"));
515 d->toolbar->addWidget(midSpacer);
516 d->toolbar->addAction(furtherAction);
517 d->toolbar->addWidget(d->iconSizeSlider);
518 d->toolbar->addAction(closerAction);
519 d->toolbar->addAction(separator2);
520 d->toolbar->addAction(coll->action("mkdir"));
521 d->toolbar->addAction(menu);
522
523 d->toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly);
524 d->toolbar->setMovable(false);
525
526 KUrlCompletion *pathCompletionObj = new KUrlCompletion( KUrlCompletion::DirCompletion );
527 pathCombo->setCompletionObject( pathCompletionObj );
528 pathCombo->setAutoDeleteCompletionObject( true );
529
530 connect( d->urlNavigator, SIGNAL(urlChanged(KUrl)),
531 this, SLOT(_k_enterUrl(KUrl)));
532 connect( d->urlNavigator, SIGNAL(returnPressed()),
533 d->ops, SLOT(setFocus()));
534
535 QString whatsThisText;
536
537 // the Location label/edit
538 d->locationLabel = new QLabel(i18n("&Name:"), this);
539 d->locationEdit = new KUrlComboBox(KUrlComboBox::Files, true, this);
540 d->locationEdit->installEventFilter(this);
541 // Properly let the dialog be resized (to smaller). Otherwise we could have
542 // huge dialogs that can't be resized to smaller (it would be as big as the longest
543 // item in this combo box). (ereslibre)
544 d->locationEdit->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength);
545 connect( d->locationEdit, SIGNAL(editTextChanged(QString)),
546 SLOT(_k_slotLocationChanged(QString)) );
547
548 d->updateLocationWhatsThis();
549 d->locationLabel->setBuddy(d->locationEdit);
550
551 KUrlCompletion *fileCompletionObj = new KUrlCompletion( KUrlCompletion::FileCompletion );
552 d->locationEdit->setCompletionObject( fileCompletionObj );
553 d->locationEdit->setAutoDeleteCompletionObject( true );
554 connect( fileCompletionObj, SIGNAL(match(QString)),
555 SLOT(_k_fileCompletion(QString)) );
556
557 connect(d->locationEdit, SIGNAL(returnPressed(QString)),
558 this, SLOT(_k_locationAccepted(QString)));
559
560 // the Filter label/edit
561 whatsThisText = i18n("<qt>This is the filter to apply to the file list. "
562 "File names that do not match the filter will not be shown.<p>"
563 "You may select from one of the preset filters in the "
564 "drop down menu, or you may enter a custom filter "
565 "directly into the text area.</p><p>"
566 "Wildcards such as * and ? are allowed.</p></qt>");
567 d->filterLabel = new QLabel(i18n("&Filter:"), this);
568 d->filterLabel->setWhatsThis(whatsThisText);
569 d->filterWidget = new KFileFilterCombo(this);
570 // Properly let the dialog be resized (to smaller). Otherwise we could have
571 // huge dialogs that can't be resized to smaller (it would be as big as the longest
572 // item in this combo box). (ereslibre)
573 d->filterWidget->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength);
574 d->filterWidget->setWhatsThis(whatsThisText);
575 d->filterLabel->setBuddy(d->filterWidget);
576 connect(d->filterWidget, SIGNAL(filterChanged()), SLOT(_k_slotFilterChanged()));
577
578 d->filterDelayTimer.setSingleShot(true);
579 d->filterDelayTimer.setInterval(300);
580 connect(d->filterWidget, SIGNAL(editTextChanged(QString)), &d->filterDelayTimer, SLOT(start()));
581 connect(&d->filterDelayTimer, SIGNAL(timeout()), SLOT(_k_slotFilterChanged()));
582
583 // the Automatically Select Extension checkbox
584 // (the text, visibility etc. is set in updateAutoSelectExtension(), which is called by readConfig())
585 d->autoSelectExtCheckBox = new QCheckBox (this);
586 d->autoSelectExtCheckBox->setStyleSheet(QString("QCheckBox { padding-top: %1px; }").arg(KDialog::spacingHint()));
587 connect(d->autoSelectExtCheckBox, SIGNAL(clicked()), SLOT(_k_slotAutoSelectExtClicked()));
588
589 d->initGUI(); // activate GM
590
591 // read our configuration
592 KSharedConfig::Ptr config = KGlobal::config();
593 KConfigGroup group(config, ConfigGroup);
594 readConfig(group);
595
596 coll->action("inline preview")->setChecked(d->ops->isInlinePreviewShown());
597 d->iconSizeSlider->setValue(d->ops->iconsZoom());
598
599 KFilePreviewGenerator *pg = d->ops->previewGenerator();
600 if (pg) {
601 coll->action("inline preview")->setChecked(pg->isPreviewShown());
602 }
603
604 // getStartUrl() above will have resolved the startDir parameter into
605 // a directory and file name in the two cases: (a) where it is a
606 // special "kfiledialog:" URL, or (b) where it is a plain file name
607 // only without directory or protocol. For any other startDir
608 // specified, it is not possible to resolve whether there is a file name
609 // present just by looking at the URL; the only way to be sure is
610 // to stat it.
611 bool statRes = false;
612 if ( filename.isEmpty() )
613 {
614 KIO::StatJob *statJob = KIO::stat(startDir, KIO::HideProgressInfo);
615 statRes = KIO::NetAccess::synchronousRun(statJob, this);
616 kDebug(kfile_area) << "stat of" << startDir << "-> statRes" << statRes << "isDir" << statJob->statResult().isDir();
617 if (!statRes || !statJob->statResult().isDir()) {
618 filename = startDir.fileName();
619 startDir.setPath(startDir.directory());
620 kDebug(kfile_area) << "statJob -> startDir" << startDir << "filename" << filename;
621 }
622 }
623
624 d->ops->setUrl(startDir, true);
625 d->urlNavigator->setLocationUrl(startDir);
626 if (d->placesView) {
627 d->placesView->setUrl(startDir);
628 }
629
630 // We have a file name either explicitly specified, or have checked that
631 // we could stat it and it is not a directory. Set it.
632 if (!filename.isEmpty()) {
633 QLineEdit* lineEdit = d->locationEdit->lineEdit();
634 kDebug(kfile_area) << "selecting filename" << filename;
635 if (statRes) {
636 d->setLocationText(filename);
637 } else {
638 lineEdit->setText(filename);
639 // Preserve this filename when clicking on the view (cf _k_fileHighlighted)
640 lineEdit->setModified(true);
641 }
642 lineEdit->selectAll();
643 }
644
645 d->locationEdit->setFocus();
646}
647
648KFileWidget::~KFileWidget()
649{
650 KSharedConfig::Ptr config = KGlobal::config();
651 config->sync();
652
653 delete d;
654}
655
656void KFileWidget::setLocationLabel(const QString& text)
657{
658 d->locationLabel->setText(text);
659}
660
661void KFileWidget::setFilter(const QString& filter)
662{
663 int pos = filter.indexOf('/');
664
665 // Check for an un-escaped '/', if found
666 // interpret as a MIME filter.
667
668 if (pos > 0 && filter[pos - 1] != '\\') {
669 QStringList filters = filter.split(' ', QString::SkipEmptyParts);
670 setMimeFilter( filters );
671 return;
672 }
673
674 // Strip the escape characters from
675 // escaped '/' characters.
676
677 QString copy (filter);
678 for (pos = 0; (pos = copy.indexOf("\\/", pos)) != -1; ++pos)
679 copy.remove(pos, 1);
680
681 d->ops->clearFilter();
682 d->filterWidget->setFilter(copy);
683 d->ops->setNameFilter(d->filterWidget->currentFilter());
684 d->ops->updateDir();
685 d->hasDefaultFilter = false;
686 d->filterWidget->setEditable( true );
687
688 d->updateAutoSelectExtension ();
689}
690
691QString KFileWidget::currentFilter() const
692{
693 return d->filterWidget->currentFilter();
694}
695
696void KFileWidget::setMimeFilter( const QStringList& mimeTypes,
697 const QString& defaultType )
698{
699 d->filterWidget->setMimeFilter( mimeTypes, defaultType );
700
701 QStringList types = d->filterWidget->currentFilter().split(' ', QString::SkipEmptyParts); //QStringList::split(" ", d->filterWidget->currentFilter());
702 types.append( QLatin1String( "inode/directory" ));
703 d->ops->clearFilter();
704 d->ops->setMimeFilter( types );
705 d->hasDefaultFilter = !defaultType.isEmpty();
706 d->filterWidget->setEditable( !d->hasDefaultFilter ||
707 d->operationMode != Saving );
708
709 d->updateAutoSelectExtension ();
710}
711
712void KFileWidget::clearFilter()
713{
714 d->filterWidget->setFilter( QString() );
715 d->ops->clearFilter();
716 d->hasDefaultFilter = false;
717 d->filterWidget->setEditable( true );
718
719 d->updateAutoSelectExtension ();
720}
721
722QString KFileWidget::currentMimeFilter() const
723{
724 int i = d->filterWidget->currentIndex();
725 if (d->filterWidget->showsAllTypes() && i == 0)
726 return QString(); // The "all types" item has no mimetype
727
728 return d->filterWidget->filters()[i];
729}
730
731KMimeType::Ptr KFileWidget::currentFilterMimeType()
732{
733 return KMimeType::mimeType( currentMimeFilter() );
734}
735
736void KFileWidget::setPreviewWidget(KPreviewWidgetBase *w) {
737 d->ops->setPreviewWidget(w);
738 d->ops->clearHistory();
739 d->hasView = true;
740}
741
742KUrl KFileWidgetPrivate::getCompleteUrl(const QString &_url) const
743{
744// kDebug(kfile_area) << "got url " << _url;
745
746 const QString url = KShell::tildeExpand(_url);
747 KUrl u;
748
749 if (QDir::isAbsolutePath(url)) {
750 u = url;
751 } else {
752 KUrl relativeUrlTest(ops->url());
753 relativeUrlTest.addPath(url);
754 if (!ops->dirLister()->findByUrl(relativeUrlTest).isNull() ||
755 !KProtocolInfo::isKnownProtocol(relativeUrlTest)) {
756 u = relativeUrlTest;
757 } else {
758 u = url;
759 }
760 }
761
762 return u;
763}
764
765// Called by KFileDialog
766void KFileWidget::slotOk()
767{
768// kDebug(kfile_area) << "slotOk\n";
769
770 const KFileItemList items = d->ops->selectedItems();
771 const QString locationEditCurrentText(KShell::tildeExpand(d->locationEditCurrentText()));
772
773 KUrl::List locationEditCurrentTextList(d->tokenize(locationEditCurrentText));
774 KFile::Modes mode = d->ops->mode();
775
776 // if there is nothing to do, just return from here
777 if (!locationEditCurrentTextList.count()) {
778 return;
779 }
780
781 // Make sure that one of the modes was provided
782 if (!((mode & KFile::File) || (mode & KFile::Directory) || (mode & KFile::Files))) {
783 mode |= KFile::File;
784 kDebug(kfile_area) << "No mode() provided";
785 }
786
787 // if we are on file mode, and the list of provided files/folder is greater than one, inform
788 // the user about it
789 if (locationEditCurrentTextList.count() > 1) {
790 if (mode & KFile::File) {
791 KMessageBox::sorry(this,
792 i18n("You can only select one file"),
793 i18n("More than one file provided"));
794 return;
795 }
796
797 /**
798 * Logic of the next part of code (ends at "end multi relative urls").
799 *
800 * We allow for instance to be at "/" and insert '"home/foo/bar.txt" "boot/grub/menu.lst"'.
801 * Why we need to support this ? Because we provide tree views, which aren't plain.
802 *
803 * Now, how does this logic work. It will get the first element on the list (with no filename),
804 * following the previous example say "/home/foo" and set it as the top most url.
805 *
806 * After this, it will iterate over the rest of items and check if this URL (topmost url)
807 * contains the url being iterated.
808 *
809 * As you might have guessed it will do "/home/foo" against "/boot/grub" (again stripping
810 * filename), and a false will be returned. Then we upUrl the top most url, resulting in
811 * "/home" against "/boot/grub", what will again return false, so we upUrl again. Now we
812 * have "/" against "/boot/grub", what returns true for us, so we can say that the closest
813 * common ancestor of both is "/".
814 *
815 * This example has been written for 2 urls, but this works for any number of urls.
816 */
817 if (!d->differentHierarchyLevelItemsEntered) { // avoid infinite recursion. running this
818 KUrl::List urlList; // one time is always enough.
819 int start = 0;
820 KUrl topMostUrl;
821 KIO::StatJob *statJob = 0;
822 bool res = false;
823
824 // we need to check for a valid first url, so in theory we only iterate one time over
825 // this loop. However it can happen that the user did
826 // "home/foo/nonexistantfile" "boot/grub/menu.lst", so we look for a good first
827 // candidate.
828 while (!res && start < locationEditCurrentTextList.count()) {
829 topMostUrl = locationEditCurrentTextList.at(start);
830 statJob = KIO::stat(topMostUrl, KIO::HideProgressInfo);
831 res = KIO::NetAccess::synchronousRun(statJob, this);
832 start++;
833 }
834
835 Q_ASSERT(statJob);
836
837 // if this is not a dir, strip the filename. after this we have an existent and valid
838 // dir (if we stated correctly the file, setting a null filename won't make any bad).
839 if (!statJob->statResult().isDir()) {
840 topMostUrl.setFileName(QString());
841 }
842
843 // now the funny part. for the rest of filenames, go and look for the closest ancestor
844 // of all them.
845 for (int i = start; i < locationEditCurrentTextList.count(); ++i) {
846 KUrl currUrl = locationEditCurrentTextList.at(i);
847 KIO::StatJob *statJob = KIO::stat(currUrl, KIO::HideProgressInfo);
848 bool res = KIO::NetAccess::synchronousRun(statJob, this);
849 if (res) {
850 // again, we don't care about filenames
851 if (!statJob->statResult().isDir()) {
852 currUrl.setFileName(QString());
853 }
854
855 // iterate while this item is contained on the top most url
856 while (!topMostUrl.isParentOf(currUrl)) {
857 topMostUrl = topMostUrl.upUrl();
858 }
859 }
860 }
861
862 // now recalculate all paths for them being relative in base of the top most url
863 for (int i = 0; i < locationEditCurrentTextList.count(); ++i) {
864 locationEditCurrentTextList[i] = KUrl::relativeUrl(topMostUrl, locationEditCurrentTextList[i]);
865 }
866
867 d->ops->setUrl(topMostUrl, true);
868 const bool signalsBlocked = d->locationEdit->lineEdit()->blockSignals(true);
869 QStringList stringList;
870 foreach (const KUrl &url, locationEditCurrentTextList) {
871 stringList << url.prettyUrl();
872 }
873 d->locationEdit->lineEdit()->setText(QString("\"%1\"").arg(stringList.join("\" \"")));
874 d->locationEdit->lineEdit()->blockSignals(signalsBlocked);
875
876 d->differentHierarchyLevelItemsEntered = true;
877 slotOk();
878 return;
879 }
880 /**
881 * end multi relative urls
882 */
883 } else if (locationEditCurrentTextList.count()) {
884 // if we are on file or files mode, and we have an absolute url written by
885 // the user, convert it to relative
886 if (!locationEditCurrentText.isEmpty() && !(mode & KFile::Directory) &&
887 (QDir::isAbsolutePath(locationEditCurrentText) ||
888 containsProtocolSection(locationEditCurrentText))) {
889
890 QString fileName;
891 KUrl url(locationEditCurrentText);
892 if (d->operationMode == Opening) {
893 KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo);
894 bool res = KIO::NetAccess::synchronousRun(statJob, this);
895 if (res) {
896 if (!statJob->statResult().isDir()) {
897 url.adjustPath(KUrl::RemoveTrailingSlash);
898 fileName = url.fileName();
899 url.setFileName(QString());
900 } else {
901 url.adjustPath(KUrl::AddTrailingSlash);
902 }
903 }
904 } else {
905 KUrl directory = url;
906 directory.setFileName(QString());
907 //Check if the folder exists
908 KIO::StatJob * statJob = KIO::stat(directory, KIO::HideProgressInfo);
909 bool res = KIO::NetAccess::synchronousRun(statJob, this);
910 if (res) {
911 if (statJob->statResult().isDir()) {
912 url.adjustPath(KUrl::RemoveTrailingSlash);
913 fileName = url.fileName();
914 url.setFileName(QString());
915 }
916 }
917 }
918 d->ops->setUrl(url, true);
919 const bool signalsBlocked = d->locationEdit->lineEdit()->blockSignals(true);
920 d->locationEdit->lineEdit()->setText(fileName);
921 d->locationEdit->lineEdit()->blockSignals(signalsBlocked);
922 slotOk();
923 return;
924 }
925 }
926
927 // restore it
928 d->differentHierarchyLevelItemsEntered = false;
929
930 // locationEditCurrentTextList contains absolute paths
931 // this is the general loop for the File and Files mode. Obviously we know
932 // that the File mode will iterate only one time here
933 bool directoryMode = (mode & KFile::Directory);
934 bool onlyDirectoryMode = directoryMode && !(mode & KFile::File) && !(mode & KFile::Files);
935 KUrl::List::ConstIterator it = locationEditCurrentTextList.constBegin();
936 bool filesInList = false;
937 while (it != locationEditCurrentTextList.constEnd()) {
938 KUrl url(*it);
939
940 if (d->operationMode == Saving && !directoryMode) {
941 d->appendExtension(url);
942 }
943
944 d->url = url;
945 KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo);
946 bool res = KIO::NetAccess::synchronousRun(statJob, this);
947
948 if (!KAuthorized::authorizeUrlAction("open", KUrl(), url)) {
949 QString msg = KIO::buildErrorString(KIO::ERR_ACCESS_DENIED, d->url.prettyUrl());
950 KMessageBox::error(this, msg);
951 return;
952 }
953
954 // if we are on local mode, make sure we haven't got a remote base url
955 if ((mode & KFile::LocalOnly) && !d->mostLocalUrl(d->url).isLocalFile()) {
956 KMessageBox::sorry(this,
957 i18n("You can only select local files"),
958 i18n("Remote files not accepted"));
959 return;
960 }
961
962 if ((d->operationMode == Saving) && d->confirmOverwrite && !d->toOverwrite(url)) {
963 return;
964 }
965
966 // if we are given a folder when not on directory mode, let's get into it
967 if (res && !directoryMode && statJob->statResult().isDir()) {
968 // check if we were given more than one folder, in that case we don't know to which one
969 // cd
970 ++it;
971 while (it != locationEditCurrentTextList.constEnd()) {
972 KUrl checkUrl(*it);
973 KIO::StatJob *checkStatJob = KIO::stat(checkUrl, KIO::HideProgressInfo);
974 bool res = KIO::NetAccess::synchronousRun(checkStatJob, this);
975 if (res && checkStatJob->statResult().isDir()) {
976 KMessageBox::sorry(this, i18n("More than one folder has been selected and this dialog does not accept folders, so it is not possible to decide which one to enter. Please select only one folder to list it."), i18n("More than one folder provided"));
977 return;
978 } else if (res) {
979 filesInList = true;
980 }
981 ++it;
982 }
983 if (filesInList) {
984 KMessageBox::information(this, i18n("At least one folder and one file has been selected. Selected files will be ignored and the selected folder will be listed"), i18n("Files and folders selected"));
985 }
986 d->ops->setUrl(url, true);
987 const bool signalsBlocked = d->locationEdit->lineEdit()->blockSignals(true);
988 d->locationEdit->lineEdit()->setText(QString());
989 d->locationEdit->lineEdit()->blockSignals(signalsBlocked);
990 return;
991 } else if (!(mode & KFile::ExistingOnly) || res) {
992 // if we don't care about ExistingOnly flag, add the file even if
993 // it doesn't exist. If we care about it, don't add it to the list
994 if (!onlyDirectoryMode || (res && statJob->statResult().isDir())) {
995 d->urlList << url;
996 }
997 filesInList = true;
998 } else {
999 KMessageBox::sorry(this, i18n("The file \"%1\" could not be found", url.pathOrUrl()), i18n("Cannot open file"));
1000 return; // do not emit accepted() if we had ExistingOnly flag and stat failed
1001 }
1002 ++it;
1003 }
1004
1005 // if we have reached this point and we didn't return before, that is because
1006 // we want this dialog to be accepted
1007 emit accepted();
1008}
1009
1010void KFileWidget::accept()
1011{
1012 d->inAccept = true; // parseSelectedUrls() checks that
1013
1014 *lastDirectory = d->ops->url();
1015 if (!d->fileClass.isEmpty())
1016 KRecentDirs::add(d->fileClass, d->ops->url().url());
1017
1018 // clear the topmost item, we insert it as full path later on as item 1
1019 d->locationEdit->setItemText( 0, QString() );
1020
1021 const KUrl::List list = selectedUrls();
1022 QList<KUrl>::const_iterator it = list.begin();
1023 int atmost = d->locationEdit->maxItems(); //don't add more items than necessary
1024 for ( ; it != list.end() && atmost > 0; ++it ) {
1025 const KUrl& url = *it;
1026 // we strip the last slash (-1) because KUrlComboBox does that as well
1027 // when operating in file-mode. If we wouldn't , dupe-finding wouldn't
1028 // work.
1029 QString file = url.isLocalFile() ? url.toLocalFile(KUrl::RemoveTrailingSlash) : url.prettyUrl(KUrl::RemoveTrailingSlash);
1030
1031 // remove dupes
1032 for ( int i = 1; i < d->locationEdit->count(); i++ ) {
1033 if ( d->locationEdit->itemText( i ) == file ) {
1034 d->locationEdit->removeItem( i-- );
1035 break;
1036 }
1037 }
1038 //FIXME I don't think this works correctly when the KUrlComboBox has some default urls.
1039 //KUrlComboBox should provide a function to add an url and rotate the existing ones, keeping
1040 //track of maxItems, and we shouldn't be able to insert items as we please.
1041 d->locationEdit->insertItem( 1,file);
1042 atmost--;
1043 }
1044
1045 d->writeViewConfig();
1046 d->saveRecentFiles();
1047
1048 d->addToRecentDocuments();
1049
1050 if (!(mode() & KFile::Files)) { // single selection
1051 emit fileSelected(d->url.url()); // old
1052 emit fileSelected(d->url);
1053 }
1054
1055 d->ops->close();
1056}
1057
1058
1059void KFileWidgetPrivate::_k_fileHighlighted(const KFileItem &i)
1060{
1061 if ((!i.isNull() && i.isDir() ) ||
1062 (locationEdit->hasFocus() && !locationEdit->currentText().isEmpty())) // don't disturb
1063 return;
1064
1065 const bool modified = locationEdit->lineEdit()->isModified();
1066
1067 if (!(ops->mode() & KFile::Files)) {
1068 if (i.isNull()) {
1069 if (!modified) {
1070 setLocationText(KUrl());
1071 }
1072 return;
1073 }
1074
1075 url = i.url();
1076
1077 if (!locationEdit->hasFocus()) { // don't disturb while editing
1078 setLocationText( url );
1079 }
1080
1081 emit q->fileHighlighted(url.url()); // old
1082 emit q->fileHighlighted(url);
1083 } else {
1084 multiSelectionChanged();
1085 emit q->selectionChanged();
1086 }
1087
1088 locationEdit->lineEdit()->setModified( false );
1089 locationEdit->lineEdit()->selectAll();
1090}
1091
1092void KFileWidgetPrivate::_k_fileSelected(const KFileItem &i)
1093{
1094 if (!i.isNull() && i.isDir()) {
1095 return;
1096 }
1097
1098 if (!(ops->mode() & KFile::Files)) {
1099 if (i.isNull()) {
1100 setLocationText(KUrl());
1101 return;
1102 }
1103 setLocationText(i.url());
1104 } else {
1105 multiSelectionChanged();
1106 emit q->selectionChanged();
1107 }
1108
1109 // if we are saving, let another chance to the user before accepting the dialog (or trying to
1110 // accept). This way the user can choose a file and add a "_2" for instance to the filename
1111 if (operationMode == KFileWidget::Saving) {
1112 locationEdit->setFocus();
1113 } else {
1114 q->slotOk();
1115 }
1116}
1117
1118
1119// I know it's slow to always iterate thru the whole filelist
1120// (d->ops->selectedItems()), but what can we do?
1121void KFileWidgetPrivate::multiSelectionChanged()
1122{
1123 if (locationEdit->hasFocus() && !locationEdit->currentText().isEmpty()) { // don't disturb
1124 return;
1125 }
1126
1127 const KFileItemList list = ops->selectedItems();
1128
1129 if (list.isEmpty()) {
1130 setLocationText(KUrl());
1131 return;
1132 }
1133
1134 setLocationText(list.urlList());
1135}
1136
1137void KFileWidgetPrivate::setDummyHistoryEntry( const QString& text, const QPixmap& icon,
1138 bool usePreviousPixmapIfNull )
1139{
1140 // setCurrentItem() will cause textChanged() being emitted,
1141 // so slotLocationChanged() will be called. Make sure we don't clear
1142 // the KDirOperator's view-selection in there
1143 QObject::disconnect( locationEdit, SIGNAL(editTextChanged(QString)),
1144 q, SLOT(_k_slotLocationChanged(QString)) );
1145
1146 bool dummyExists = dummyAdded;
1147
1148 int cursorPosition = locationEdit->lineEdit()->cursorPosition();
1149
1150 if ( dummyAdded ) {
1151 if ( !icon.isNull() ) {
1152 locationEdit->setItemIcon( 0, icon );
1153 locationEdit->setItemText( 0, text );
1154 } else {
1155 if ( !usePreviousPixmapIfNull ) {
1156 locationEdit->setItemIcon( 0, QPixmap() );
1157 }
1158 locationEdit->setItemText( 0, text );
1159 }
1160 } else {
1161 if ( !text.isEmpty() ) {
1162 if ( !icon.isNull() ) {
1163 locationEdit->insertItem( 0, icon, text );
1164 } else {
1165 if ( !usePreviousPixmapIfNull ) {
1166 locationEdit->insertItem( 0, QPixmap(), text );
1167 } else {
1168 locationEdit->insertItem( 0, text );
1169 }
1170 }
1171 dummyAdded = true;
1172 dummyExists = true;
1173 }
1174 }
1175
1176 if ( dummyExists && !text.isEmpty() ) {
1177 locationEdit->setCurrentIndex( 0 );
1178 }
1179
1180 locationEdit->lineEdit()->setCursorPosition( cursorPosition );
1181
1182 QObject::connect( locationEdit, SIGNAL(editTextChanged(QString)),
1183 q, SLOT(_k_slotLocationChanged(QString)) );
1184}
1185
1186void KFileWidgetPrivate::removeDummyHistoryEntry()
1187{
1188 if ( !dummyAdded ) {
1189 return;
1190 }
1191
1192 // setCurrentItem() will cause textChanged() being emitted,
1193 // so slotLocationChanged() will be called. Make sure we don't clear
1194 // the KDirOperator's view-selection in there
1195 QObject::disconnect( locationEdit, SIGNAL(editTextChanged(QString)),
1196 q, SLOT(_k_slotLocationChanged(QString)) );
1197
1198 if (locationEdit->count()) {
1199 locationEdit->removeItem( 0 );
1200 }
1201 locationEdit->setCurrentIndex( -1 );
1202 dummyAdded = false;
1203
1204 QObject::connect( locationEdit, SIGNAL(editTextChanged(QString)),
1205 q, SLOT(_k_slotLocationChanged(QString)) );
1206}
1207
1208void KFileWidgetPrivate::setLocationText(const KUrl& url)
1209{
1210 if (!url.isEmpty()) {
1211 QPixmap mimeTypeIcon = KIconLoader::global()->loadMimeTypeIcon( KMimeType::iconNameForUrl( url ), KIconLoader::Small );
1212 if (url.hasPath()) {
1213 if (!url.directory().isEmpty())
1214 {
1215 KUrl u(url);
1216 u.setPath(u.directory());
1217 q->setUrl(u, false);
1218 }
1219 else {
1220 q->setUrl(url.path(), false);
1221 }
1222 }
1223 setDummyHistoryEntry(url.fileName() , mimeTypeIcon);
1224 } else {
1225 removeDummyHistoryEntry();
1226 }
1227
1228 // don't change selection when user has clicked on an item
1229 if (operationMode == KFileWidget::Saving && !locationEdit->isVisible()) {
1230 setNonExtSelection();
1231 }
1232}
1233
1234static bool isRelativeUrl(const KUrl &baseUrl, const KUrl& url)
1235{
1236 return KUrl::relativeUrl(baseUrl, url) != url.url();
1237}
1238
1239static QString relativePathOrUrl(const KUrl &baseUrl, const KUrl& url)
1240{
1241 if (isRelativeUrl(baseUrl, url)) {
1242 QString relPath = KUrl::relativePath(baseUrl.path(), url.path());
1243 if (relPath.startsWith("./")) {
1244 relPath = relPath.mid(2);
1245 }
1246 return relPath;
1247 } else {
1248 return url.prettyUrl();
1249 }
1250}
1251
1252void KFileWidgetPrivate::setLocationText( const KUrl::List& urlList )
1253{
1254 const KUrl currUrl = ops->url();
1255
1256 if ( urlList.count() > 1 ) {
1257 QString urls;
1258 foreach (const KUrl &url, urlList) {
1259 urls += QString("\"%1\"").arg(relativePathOrUrl(currUrl, url)) + ' ';
1260 }
1261 urls = urls.left( urls.size() - 1 );
1262
1263 setDummyHistoryEntry( urls, QPixmap(), false );
1264 } else if ( urlList.count() == 1 ) {
1265 const QPixmap mimeTypeIcon = KIconLoader::global()->loadMimeTypeIcon( KMimeType::iconNameForUrl( urlList[0] ), KIconLoader::Small );
1266 setDummyHistoryEntry( relativePathOrUrl(currUrl, urlList[0]), mimeTypeIcon );
1267 } else {
1268 removeDummyHistoryEntry();
1269 }
1270
1271 // don't change selection when user has clicked on an item
1272 if ( operationMode == KFileWidget::Saving && !locationEdit->isVisible())
1273 setNonExtSelection();
1274}
1275
1276void KFileWidgetPrivate::updateLocationWhatsThis()
1277{
1278 QString whatsThisText;
1279 if (operationMode == KFileWidget::Saving)
1280 {
1281 whatsThisText = "<qt>" + i18n("This is the name to save the file as.") +
1282 i18n (autocompletionWhatsThisText);
1283 }
1284 else if (ops->mode() & KFile::Files)
1285 {
1286 whatsThisText = "<qt>" + i18n("This is the list of files to open. More than "
1287 "one file can be specified by listing several "
1288 "files, separated by spaces.") +
1289 i18n (autocompletionWhatsThisText);
1290 }
1291 else
1292 {
1293 whatsThisText = "<qt>" + i18n("This is the name of the file to open.") +
1294 i18n (autocompletionWhatsThisText);
1295 }
1296
1297 locationLabel->setWhatsThis(whatsThisText);
1298 locationEdit->setWhatsThis(whatsThisText);
1299}
1300
1301void KFileWidgetPrivate::initSpeedbar()
1302{
1303 if (placesDock) {
1304 return;
1305 }
1306
1307 placesDock = new QDockWidget(i18nc("@title:window", "Places"), q);
1308 placesDock->setFeatures(QDockWidget::DockWidgetClosable);
1309
1310 placesView = new KFilePlacesView(placesDock);
1311 placesView->setModel(model);
1312 placesView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1313
1314 placesView->setObjectName(QLatin1String("url bar"));
1315 QObject::connect(placesView, SIGNAL(urlChanged(KUrl)),
1316 q, SLOT(_k_enterUrl(KUrl)));
1317
1318 // need to set the current url of the urlbar manually (not via urlEntered()
1319 // here, because the initial url of KDirOperator might be the same as the
1320 // one that will be set later (and then urlEntered() won't be emitted).
1321 // TODO: KDE5 ### REMOVE THIS when KDirOperator's initial URL (in the c'tor) is gone.
1322 placesView->setUrl(url);
1323
1324 placesDock->setWidget(placesView);
1325 placesViewSplitter->insertWidget(0, placesDock);
1326
1327 // initialize the size of the splitter
1328 placesViewWidth = configGroup.readEntry(SpeedbarWidth, placesView->sizeHint().width());
1329
1330 QList<int> sizes = placesViewSplitter->sizes();
1331 if (placesViewWidth > 0) {
1332 sizes[0] = placesViewWidth + 1;
1333 sizes[1] = q->width() - placesViewWidth -1;
1334 placesViewSplitter->setSizes(sizes);
1335 }
1336
1337 QObject::connect(placesDock, SIGNAL(visibilityChanged(bool)),
1338 q, SLOT(_k_toggleSpeedbar(bool)));
1339}
1340
1341void KFileWidgetPrivate::initGUI()
1342{
1343 delete boxLayout; // deletes all sub layouts
1344
1345 boxLayout = new QVBoxLayout( q);
1346 boxLayout->setMargin(0); // no additional margin to the already existing
1347
1348 placesViewSplitter = new QSplitter(q);
1349 placesViewSplitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
1350 placesViewSplitter->setChildrenCollapsible(false);
1351 boxLayout->addWidget(placesViewSplitter);
1352
1353 QObject::connect(placesViewSplitter, SIGNAL(splitterMoved(int,int)),
1354 q, SLOT(_k_placesViewSplitterMoved(int,int)));
1355 placesViewSplitter->insertWidget(0, opsWidget);
1356
1357 vbox = new QVBoxLayout();
1358 vbox->setMargin(0);
1359 boxLayout->addLayout(vbox);
1360
1361 lafBox = new QGridLayout();
1362
1363 lafBox->addWidget(locationLabel, 0, 0, Qt::AlignVCenter | Qt::AlignRight);
1364 lafBox->addWidget(locationEdit, 0, 1, Qt::AlignVCenter);
1365 lafBox->addWidget(okButton, 0, 2, Qt::AlignVCenter);
1366
1367 lafBox->addWidget(filterLabel, 1, 0, Qt::AlignVCenter | Qt::AlignRight);
1368 lafBox->addWidget(filterWidget, 1, 1, Qt::AlignVCenter);
1369 lafBox->addWidget(cancelButton, 1, 2, Qt::AlignVCenter);
1370
1371 lafBox->setColumnStretch(1, 4);
1372
1373 vbox->addLayout(lafBox);
1374
1375 // add the Automatically Select Extension checkbox
1376 vbox->addWidget(autoSelectExtCheckBox);
1377
1378 q->setTabOrder(ops, autoSelectExtCheckBox);
1379 q->setTabOrder(autoSelectExtCheckBox, locationEdit);
1380 q->setTabOrder(locationEdit, filterWidget);
1381 q->setTabOrder(filterWidget, okButton);
1382 q->setTabOrder(okButton, cancelButton);
1383 q->setTabOrder(cancelButton, urlNavigator);
1384 q->setTabOrder(urlNavigator, ops);
1385 q->setTabOrder(cancelButton, urlNavigator);
1386 q->setTabOrder(urlNavigator, ops);
1387
1388}
1389
1390void KFileWidgetPrivate::_k_slotFilterChanged()
1391{
1392// kDebug(kfile_area);
1393
1394 filterDelayTimer.stop();
1395
1396 QString filter = filterWidget->currentFilter();
1397 ops->clearFilter();
1398
1399 if ( filter.contains('/') ) {
1400 QStringList types = filter.split(' ', QString::SkipEmptyParts);
1401 types.prepend("inode/directory");
1402 ops->setMimeFilter( types );
1403 }
1404 else if ( filter.contains('*') || filter.contains('?') || filter.contains('[') ) {
1405 ops->setNameFilter( filter );
1406 }
1407 else {
1408 ops->setNameFilter('*' + filter.replace(' ', '*') + '*');
1409 }
1410
1411 ops->updateDir();
1412
1413 updateAutoSelectExtension();
1414
1415 emit q->filterChanged(filter);
1416}
1417
1418
1419void KFileWidget::setUrl(const KUrl& url, bool clearforward)
1420{
1421// kDebug(kfile_area);
1422
1423 d->ops->setUrl(url, clearforward);
1424}
1425
1426// Protected
1427void KFileWidgetPrivate::_k_urlEntered(const KUrl& url)
1428{
1429// kDebug(kfile_area);
1430
1431 QString filename = locationEditCurrentText();
1432
1433 KUrlComboBox* pathCombo = urlNavigator->editor();
1434 if (pathCombo->count() != 0) { // little hack
1435 pathCombo->setUrl(url);
1436 }
1437
1438 bool blocked = locationEdit->blockSignals(true);
1439 if (keepLocation) {
1440 locationEdit->changeUrl(0, KIcon(KMimeType::iconNameForUrl(filename)), filename);
1441 locationEdit->lineEdit()->setModified(true);
1442 }
1443
1444 locationEdit->blockSignals( blocked );
1445
1446 urlNavigator->setLocationUrl(url);
1447
1448 // is trigged in ctor before completion object is set
1449 KUrlCompletion *completion = dynamic_cast<KUrlCompletion*>(locationEdit->completionObject());
1450 if (completion) {
1451 completion->setDir( url.path() );
1452 }
1453
1454 if (placesView) {
1455 placesView->setUrl( url );
1456 }
1457}
1458
1459void KFileWidgetPrivate::_k_locationAccepted(const QString &url)
1460{
1461 Q_UNUSED(url);
1462// kDebug(kfile_area);
1463 q->slotOk();
1464}
1465
1466void KFileWidgetPrivate::_k_enterUrl( const KUrl& url )
1467{
1468// kDebug(kfile_area);
1469
1470 KUrl fixedUrl( url );
1471 // append '/' if needed: url combo does not add it
1472 // tokenize() expects it because uses KUrl::setFileName()
1473 fixedUrl.adjustPath( KUrl::AddTrailingSlash );
1474 q->setUrl( fixedUrl );
1475 if (!locationEdit->hasFocus())
1476 ops->setFocus();
1477}
1478
1479void KFileWidgetPrivate::_k_enterUrl( const QString& url )
1480{
1481// kDebug(kfile_area);
1482
1483 _k_enterUrl( KUrl( KUrlCompletion::replacedPath( url, true, true )) );
1484}
1485
1486bool KFileWidgetPrivate::toOverwrite(const KUrl &url)
1487{
1488// kDebug(kfile_area);
1489
1490 KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo);
1491 bool res = KIO::NetAccess::synchronousRun(statJob, q);
1492
1493 if (res) {
1494 int ret = KMessageBox::warningContinueCancel( q,
1495 i18n( "The file \"%1\" already exists. Do you wish to overwrite it?" ,
1496 url.fileName() ), i18n( "Overwrite File?" ), KStandardGuiItem::overwrite(),
1497 KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous);
1498
1499 if (ret != KMessageBox::Continue) {
1500 return false;
1501 }
1502 return true;
1503 }
1504
1505 return true;
1506}
1507
1508void KFileWidget::setSelection(const QString& url)
1509{
1510// kDebug(kfile_area) << "setSelection " << url;
1511
1512 if (url.isEmpty()) {
1513 return;
1514 }
1515
1516 KUrl u = d->getCompleteUrl(url);
1517 if (!u.isValid()) { // if it still is
1518 kWarning() << url << " is not a correct argument for setSelection!";
1519 return;
1520 }
1521
1522 // Honor protocols that do not support directory listing
1523 if (!u.isRelative() && !KProtocolManager::supportsListing(u))
1524 return;
1525
1526 d->setLocationText(url);
1527}
1528
1529void KFileWidgetPrivate::_k_slotLoadingFinished()
1530{
1531 if (locationEdit->currentText().isEmpty()) {
1532 return;
1533 }
1534
1535 ops->blockSignals(true);
1536 KUrl url = ops->url();
1537 url.adjustPath(KUrl::AddTrailingSlash);
1538 url.setFileName(locationEdit->currentText());
1539 ops->setCurrentItem(url.url());
1540 ops->blockSignals(false);
1541}
1542
1543void KFileWidgetPrivate::_k_fileCompletion( const QString& match )
1544{
1545// kDebug(kfile_area);
1546
1547 if (match.isEmpty() || locationEdit->currentText().contains('"')) {
1548 return;
1549 }
1550
1551 setDummyHistoryEntry(locationEdit->currentText(), KIconLoader::global()->loadMimeTypeIcon( KMimeType::iconNameForUrl( match ), KIconLoader::Small), !locationEdit->currentText().isEmpty());
1552}
1553
1554void KFileWidgetPrivate::_k_slotLocationChanged( const QString& text )
1555{
1556// kDebug(kfile_area);
1557
1558 locationEdit->lineEdit()->setModified(true);
1559
1560 if (text.isEmpty() && ops->view()) {
1561 ops->view()->clearSelection();
1562 }
1563
1564 if (text.isEmpty()) {
1565 removeDummyHistoryEntry();
1566 } else {
1567 setDummyHistoryEntry( text );
1568 }
1569
1570 if (!locationEdit->lineEdit()->text().isEmpty()) {
1571 const KUrl::List urlList(tokenize(text));
1572 QStringList stringList;
1573 foreach (const KUrl &url, urlList) {
1574 stringList << url.url();
1575 }
1576 ops->setCurrentItems(stringList);
1577 }
1578
1579 updateFilter();
1580}
1581
1582KUrl KFileWidget::selectedUrl() const
1583{
1584// kDebug(kfile_area);
1585
1586 if ( d->inAccept )
1587 return d->url;
1588 else
1589 return KUrl();
1590}
1591
1592KUrl::List KFileWidget::selectedUrls() const
1593{
1594// kDebug(kfile_area);
1595
1596 KUrl::List list;
1597 if ( d->inAccept ) {
1598 if (d->ops->mode() & KFile::Files)
1599 list = d->parseSelectedUrls();
1600 else
1601 list.append( d->url );
1602 }
1603 return list;
1604}
1605
1606
1607KUrl::List& KFileWidgetPrivate::parseSelectedUrls()
1608{
1609// kDebug(kfile_area);
1610
1611 if ( filenames.isEmpty() ) {
1612 return urlList;
1613 }
1614
1615 urlList.clear();
1616 if ( filenames.contains( '/' )) { // assume _one_ absolute filename
1617 KUrl u;
1618 if ( containsProtocolSection( filenames ) )
1619 u = filenames;
1620 else
1621 u.setPath( filenames );
1622
1623 if ( u.isValid() )
1624 urlList.append( u );
1625 else
1626 KMessageBox::error( q,
1627 i18n("The chosen filenames do not\n"
1628 "appear to be valid."),
1629 i18n("Invalid Filenames") );
1630 }
1631
1632 else
1633 urlList = tokenize( filenames );
1634
1635 filenames.clear(); // indicate that we parsed that one
1636
1637 return urlList;
1638}
1639
1640
1641// FIXME: current implementation drawback: a filename can't contain quotes
1642KUrl::List KFileWidgetPrivate::tokenize( const QString& line ) const
1643{
1644// kDebug(kfile_area);
1645
1646 KUrl::List urls;
1647 KUrl u( ops->url() );
1648 u.adjustPath(KUrl::AddTrailingSlash);
1649 QString name;
1650
1651 const int count = line.count( QLatin1Char( '"' ) );
1652 if ( count == 0 ) { // no " " -> assume one single file
1653 if (!QDir::isAbsolutePath(line)) {
1654 u.setFileName( line );
1655 if ( u.isValid() )
1656 urls.append( u );
1657 } else {
1658 urls << KUrl(line);
1659 }
1660
1661 return urls;
1662 }
1663
1664 int start = 0;
1665 int index1 = -1, index2 = -1;
1666 while ( true ) {
1667 index1 = line.indexOf( '"', start );
1668 index2 = line.indexOf( '"', index1 + 1 );
1669
1670 if ( index1 < 0 || index2 < 0 )
1671 break;
1672
1673 // get everything between the " "
1674 name = line.mid( index1 + 1, index2 - index1 - 1 );
1675
1676 // since we use setFileName we need to do this under a temporary url
1677 KUrl _u( u );
1678 KUrl currUrl( name );
1679
1680 if ( !QDir::isAbsolutePath(currUrl.url()) ) {
1681 _u.setFileName( name );
1682 } else {
1683 // we allow to insert various absolute paths like:
1684 // "/home/foo/bar.txt" "/boot/grub/menu.lst"
1685 _u = currUrl;
1686 }
1687
1688 if ( _u.isValid() ) {
1689 urls.append( _u );
1690 }
1691
1692 start = index2 + 1;
1693 }
1694
1695 return urls;
1696}
1697
1698
1699QString KFileWidget::selectedFile() const
1700{
1701// kDebug(kfile_area);
1702
1703 if ( d->inAccept ) {
1704 const KUrl url = d->mostLocalUrl(d->url);
1705 if (url.isLocalFile())
1706 return url.toLocalFile();
1707 else {
1708 KMessageBox::sorry( const_cast<KFileWidget*>(this),
1709 i18n("You can only select local files."),
1710 i18n("Remote Files Not Accepted") );
1711 }
1712 }
1713 return QString();
1714}
1715
1716QStringList KFileWidget::selectedFiles() const
1717{
1718// kDebug(kfile_area);
1719
1720 QStringList list;
1721
1722 if (d->inAccept) {
1723 if (d->ops->mode() & KFile::Files) {
1724 const KUrl::List urls = d->parseSelectedUrls();
1725 QList<KUrl>::const_iterator it = urls.begin();
1726 while (it != urls.end()) {
1727 KUrl url = d->mostLocalUrl(*it);
1728 if (url.isLocalFile())
1729 list.append(url.toLocalFile());
1730 ++it;
1731 }
1732 }
1733
1734 else { // single-selection mode
1735 if ( d->url.isLocalFile() )
1736 list.append( d->url.toLocalFile() );
1737 }
1738 }
1739
1740 return list;
1741}
1742
1743KUrl KFileWidget::baseUrl() const
1744{
1745 return d->ops->url();
1746}
1747
1748void KFileWidget::resizeEvent(QResizeEvent* event)
1749{
1750 QWidget::resizeEvent(event);
1751
1752 if (d->placesDock) {
1753 // we don't want our places dock actually changing size when we resize
1754 // and qt doesn't make it easy to enforce such a thing with QSplitter
1755 QList<int> sizes = d->placesViewSplitter->sizes();
1756 sizes[0] = d->placesViewWidth + 1; // without this pixel, our places view is reduced 1 pixel each time is shown.
1757 sizes[1] = width() - d->placesViewWidth - 1;
1758 d->placesViewSplitter->setSizes( sizes );
1759 }
1760}
1761
1762void KFileWidget::showEvent(QShowEvent* event)
1763{
1764 if ( !d->hasView ) { // delayed view-creation
1765 Q_ASSERT( d );
1766 Q_ASSERT( d->ops );
1767 d->ops->setView( KFile::Default );
1768 d->ops->view()->setSizePolicy( QSizePolicy( QSizePolicy::Maximum, QSizePolicy::Maximum ) );
1769 d->hasView = true;
1770 }
1771 d->ops->clearHistory();
1772
1773 QWidget::showEvent(event);
1774}
1775
1776bool KFileWidget::eventFilter(QObject* watched, QEvent* event)
1777{
1778 const bool res = QWidget::eventFilter(watched, event);
1779
1780 QKeyEvent *keyEvent = dynamic_cast<QKeyEvent*>(event);
1781 if (watched == d->iconSizeSlider && keyEvent) {
1782 if (keyEvent->key() == Qt::Key_Left || keyEvent->key() == Qt::Key_Up ||
1783 keyEvent->key() == Qt::Key_Right || keyEvent->key() == Qt::Key_Down) {
1784 d->_k_slotIconSizeSliderMoved(d->iconSizeSlider->value());
1785 }
1786 } else if (watched == d->locationEdit && event->type() == QEvent::KeyPress) {
1787 if (keyEvent->modifiers() & Qt::AltModifier) {
1788 switch (keyEvent->key()) {
1789 case Qt::Key_Up:
1790 d->ops->actionCollection()->action("up")->trigger();
1791 break;
1792 case Qt::Key_Left:
1793 d->ops->actionCollection()->action("back")->trigger();
1794 break;
1795 case Qt::Key_Right:
1796 d->ops->actionCollection()->action("forward")->trigger();
1797 break;
1798 default:
1799 break;
1800 }
1801 }
1802 }
1803
1804 return res;
1805}
1806
1807void KFileWidget::setMode( KFile::Modes m )
1808{
1809// kDebug(kfile_area);
1810
1811 d->ops->setMode(m);
1812 if ( d->ops->dirOnlyMode() ) {
1813 d->filterWidget->setDefaultFilter( i18n("*|All Folders") );
1814 }
1815 else {
1816 d->filterWidget->setDefaultFilter( i18n("*|All Files") );
1817 }
1818
1819 d->updateAutoSelectExtension();
1820}
1821
1822KFile::Modes KFileWidget::mode() const
1823{
1824 return d->ops->mode();
1825}
1826
1827
1828void KFileWidgetPrivate::readViewConfig()
1829{
1830 ops->setViewConfig(configGroup);
1831 ops->readConfig(configGroup);
1832 KUrlComboBox *combo = urlNavigator->editor();
1833
1834 autoDirectoryFollowing = configGroup.readEntry(AutoDirectoryFollowing,
1835 DefaultDirectoryFollowing);
1836
1837 KGlobalSettings::Completion cm = (KGlobalSettings::Completion)
1838 configGroup.readEntry( PathComboCompletionMode,
1839 static_cast<int>( KGlobalSettings::completionMode() ) );
1840 if ( cm != KGlobalSettings::completionMode() )
1841 combo->setCompletionMode( cm );
1842
1843 cm = (KGlobalSettings::Completion)
1844 configGroup.readEntry( LocationComboCompletionMode,
1845 static_cast<int>( KGlobalSettings::completionMode() ) );
1846 if ( cm != KGlobalSettings::completionMode() )
1847 locationEdit->setCompletionMode( cm );
1848
1849 // show or don't show the speedbar
1850 _k_toggleSpeedbar( configGroup.readEntry( ShowSpeedbar, true ) );
1851
1852 // show or don't show the bookmarks
1853 _k_toggleBookmarks( configGroup.readEntry(ShowBookmarks, false) );
1854
1855 // does the user want Automatically Select Extension?
1856 autoSelectExtChecked = configGroup.readEntry (AutoSelectExtChecked, DefaultAutoSelectExtChecked);
1857 updateAutoSelectExtension();
1858
1859 // should the URL navigator use the breadcrumb navigation?
1860 urlNavigator->setUrlEditable( !configGroup.readEntry(BreadcrumbNavigation, true) );
1861
1862 // should the URL navigator show the full path?
1863 urlNavigator->setShowFullPath( configGroup.readEntry(ShowFullPath, false) );
1864
1865 int w1 = q->minimumSize().width();
1866 int w2 = toolbar->sizeHint().width();
1867 if (w1 < w2)
1868 q->setMinimumWidth(w2);
1869}
1870
1871void KFileWidgetPrivate::writeViewConfig()
1872{
1873 // these settings are global settings; ALL instances of the file dialog
1874 // should reflect them.
1875 // There is no way to tell KFileOperator::writeConfig() to write to
1876 // kdeglobals so we write settings to a temporary config group then copy
1877 // them all to kdeglobals
1878 KConfig tmp( QString(), KConfig::SimpleConfig );
1879 KConfigGroup tmpGroup( &tmp, ConfigGroup );
1880
1881 KUrlComboBox *pathCombo = urlNavigator->editor();
1882 //saveDialogSize( tmpGroup, KConfigGroup::Persistent | KConfigGroup::Global );
1883 tmpGroup.writeEntry( PathComboCompletionMode, static_cast<int>(pathCombo->completionMode()) );
1884 tmpGroup.writeEntry( LocationComboCompletionMode, static_cast<int>(locationEdit->completionMode()) );
1885
1886 const bool showSpeedbar = placesDock && !placesDock->isHidden();
1887 tmpGroup.writeEntry( ShowSpeedbar, showSpeedbar );
1888 if (showSpeedbar) {
1889 const QList<int> sizes = placesViewSplitter->sizes();
1890 Q_ASSERT( sizes.count() > 0 );
1891 tmpGroup.writeEntry( SpeedbarWidth, sizes[0] );
1892 }
1893
1894 tmpGroup.writeEntry( ShowBookmarks, bookmarkHandler != 0 );
1895 tmpGroup.writeEntry( AutoSelectExtChecked, autoSelectExtChecked );
1896 tmpGroup.writeEntry( BreadcrumbNavigation, !urlNavigator->isUrlEditable() );
1897 tmpGroup.writeEntry( ShowFullPath, urlNavigator->showFullPath() );
1898
1899 ops->writeConfig( tmpGroup );
1900
1901 // Copy saved settings to kdeglobals
1902 tmpGroup.copyTo( &configGroup, KConfigGroup::Persistent | KConfigGroup::Global );
1903}
1904
1905
1906void KFileWidgetPrivate::readRecentFiles()
1907{
1908// kDebug(kfile_area);
1909
1910 QObject::disconnect(locationEdit, SIGNAL(editTextChanged(QString)),
1911 q, SLOT(_k_slotLocationChanged(QString)));
1912
1913 locationEdit->setMaxItems(configGroup.readEntry(RecentFilesNumber, DefaultRecentURLsNumber));
1914 locationEdit->setUrls(configGroup.readPathEntry(RecentFiles, QStringList()),
1915 KUrlComboBox::RemoveBottom);
1916 locationEdit->setCurrentIndex(-1);
1917
1918 QObject::connect(locationEdit, SIGNAL(editTextChanged(QString)),
1919 q, SLOT(_k_slotLocationChanged(QString)));
1920
1921 KUrlComboBox *combo = urlNavigator->editor();
1922 combo->setUrls(configGroup.readPathEntry(RecentURLs, QStringList()), KUrlComboBox::RemoveTop);
1923 combo->setMaxItems(configGroup.readEntry(RecentURLsNumber, DefaultRecentURLsNumber));
1924 combo->setUrl(ops->url());
1925 // since we delayed this moment, initialize the directory of the completion object to
1926 // our current directory (that was very probably set on the constructor)
1927 KUrlCompletion *completion = dynamic_cast<KUrlCompletion*>(locationEdit->completionObject());
1928 if (completion) {
1929 completion->setDir(ops->url().url());
1930 }
1931
1932}
1933
1934void KFileWidgetPrivate::saveRecentFiles()
1935{
1936// kDebug(kfile_area);
1937 configGroup.writePathEntry(RecentFiles, locationEdit->urls());
1938
1939 KUrlComboBox *pathCombo = urlNavigator->editor();
1940 configGroup.writePathEntry(RecentURLs, pathCombo->urls());
1941}
1942
1943KPushButton * KFileWidget::okButton() const
1944{
1945 return d->okButton;
1946}
1947
1948KPushButton * KFileWidget::cancelButton() const
1949{
1950 return d->cancelButton;
1951}
1952
1953// Called by KFileDialog
1954void KFileWidget::slotCancel()
1955{
1956// kDebug(kfile_area);
1957
1958 d->ops->close();
1959
1960 d->writeViewConfig();
1961}
1962
1963void KFileWidget::setKeepLocation( bool keep )
1964{
1965 d->keepLocation = keep;
1966}
1967
1968bool KFileWidget::keepsLocation() const
1969{
1970 return d->keepLocation;
1971}
1972
1973void KFileWidget::setOperationMode( OperationMode mode )
1974{
1975// kDebug(kfile_area);
1976
1977 d->operationMode = mode;
1978 d->keepLocation = (mode == Saving);
1979 d->filterWidget->setEditable( !d->hasDefaultFilter || mode != Saving );
1980 if ( mode == Opening ) {
1981 // don't use KStandardGuiItem::open() here which has trailing ellipsis!
1982 d->okButton->setGuiItem( KGuiItem( i18n( "&Open" ), "document-open") );
1983 // hide the new folder actions...usability team says they shouldn't be in open file dialog
1984 actionCollection()->removeAction( actionCollection()->action("mkdir" ) );
1985 } else if ( mode == Saving ) {
1986 d->okButton->setGuiItem( KStandardGuiItem::save() );
1987 d->setNonExtSelection();
1988 } else {
1989 d->okButton->setGuiItem( KStandardGuiItem::ok() );
1990 }
1991 d->updateLocationWhatsThis();
1992 d->updateAutoSelectExtension();
1993
1994 if (d->ops) {
1995 d->ops->setIsSaving(mode == Saving);
1996 }
1997}
1998
1999KFileWidget::OperationMode KFileWidget::operationMode() const
2000{
2001 return d->operationMode;
2002}
2003
2004void KFileWidgetPrivate::_k_slotAutoSelectExtClicked()
2005{
2006// kDebug (kfile_area) << "slotAutoSelectExtClicked(): "
2007// << autoSelectExtCheckBox->isChecked() << endl;
2008
2009 // whether the _user_ wants it on/off
2010 autoSelectExtChecked = autoSelectExtCheckBox->isChecked();
2011
2012 // update the current filename's extension
2013 updateLocationEditExtension (extension /* extension hasn't changed */);
2014}
2015
2016void KFileWidgetPrivate::_k_placesViewSplitterMoved(int pos, int index)
2017{
2018// kDebug(kfile_area);
2019
2020 // we need to record the size of the splitter when the splitter changes size
2021 // so we can keep the places box the right size!
2022 if (placesDock && index == 1) {
2023 placesViewWidth = pos;
2024// kDebug() << "setting lafBox minwidth to" << placesViewWidth;
2025 lafBox->setColumnMinimumWidth(0, placesViewWidth);
2026 }
2027}
2028
2029void KFileWidgetPrivate::_k_activateUrlNavigator()
2030{
2031// kDebug(kfile_area);
2032
2033 urlNavigator->setUrlEditable(!urlNavigator->isUrlEditable());
2034 if(urlNavigator->isUrlEditable()) {
2035 urlNavigator->setFocus();
2036 urlNavigator->editor()->lineEdit()->selectAll();
2037 }
2038}
2039
2040void KFileWidgetPrivate::_k_zoomOutIconsSize()
2041{
2042 const int currValue = ops->iconsZoom();
2043 const int futValue = qMax(0, currValue - 10);
2044 iconSizeSlider->setValue(futValue);
2045 _k_slotIconSizeSliderMoved(futValue);
2046}
2047
2048void KFileWidgetPrivate::_k_zoomInIconsSize()
2049{
2050 const int currValue = ops->iconsZoom();
2051 const int futValue = qMin(100, currValue + 10);
2052 iconSizeSlider->setValue(futValue);
2053 _k_slotIconSizeSliderMoved(futValue);
2054}
2055
2056void KFileWidgetPrivate::_k_slotIconSizeChanged(int _value)
2057{
2058 int maxSize = KIconLoader::SizeEnormous - KIconLoader::SizeSmall;
2059 int value = (maxSize * _value / 100) + KIconLoader::SizeSmall;
2060 switch (value) {
2061 case KIconLoader::SizeSmall:
2062 case KIconLoader::SizeSmallMedium:
2063 case KIconLoader::SizeMedium:
2064 case KIconLoader::SizeLarge:
2065 case KIconLoader::SizeHuge:
2066 case KIconLoader::SizeEnormous:
2067 iconSizeSlider->setToolTip(i18n("Icon size: %1 pixels (standard size)", value));
2068 break;
2069 default:
2070 iconSizeSlider->setToolTip(i18n("Icon size: %1 pixels", value));
2071 break;
2072 }
2073}
2074
2075void KFileWidgetPrivate::_k_slotIconSizeSliderMoved(int _value)
2076{
2077 // Force this to be called in case this slot is called first on the
2078 // slider move.
2079 _k_slotIconSizeChanged(_value);
2080
2081 QPoint global(iconSizeSlider->rect().topLeft());
2082 global.ry() += iconSizeSlider->height() / 2;
2083 QHelpEvent toolTipEvent(QEvent::ToolTip, QPoint(0, 0), iconSizeSlider->mapToGlobal(global));
2084 QApplication::sendEvent(iconSizeSlider, &toolTipEvent);
2085}
2086
2087static QString getExtensionFromPatternList(const QStringList &patternList)
2088{
2089// kDebug(kfile_area);
2090
2091 QString ret;
2092// kDebug (kfile_area) << "\tgetExtension " << patternList;
2093
2094 QStringList::ConstIterator patternListEnd = patternList.end();
2095 for (QStringList::ConstIterator it = patternList.begin();
2096 it != patternListEnd;
2097 ++it)
2098 {
2099// kDebug (kfile_area) << "\t\ttry: \'" << (*it) << "\'";
2100
2101 // is this pattern like "*.BMP" rather than useless things like:
2102 //
2103 // README
2104 // *.
2105 // *.*
2106 // *.JP*G
2107 // *.JP?
2108 if ((*it).startsWith (QLatin1String("*.")) &&
2109 (*it).length() > 2 &&
2110 (*it).indexOf('*', 2) < 0 && (*it).indexOf ('?', 2) < 0)
2111 {
2112 ret = (*it).mid (1);
2113 break;
2114 }
2115 }
2116
2117 return ret;
2118}
2119
2120static QString stripUndisplayable (const QString &string)
2121{
2122 QString ret = string;
2123
2124 ret.remove (':');
2125 ret = KGlobal::locale()->removeAcceleratorMarker (ret);
2126
2127 return ret;
2128}
2129
2130
2131//QString KFileWidget::currentFilterExtension()
2132//{
2133// return d->extension;
2134//}
2135
2136void KFileWidgetPrivate::updateAutoSelectExtension()
2137{
2138 if (!autoSelectExtCheckBox) return;
2139
2140 //
2141 // Figure out an extension for the Automatically Select Extension thing
2142 // (some Windows users apparently don't know what to do when confronted
2143 // with a text file called "COPYING" but do know what to do with
2144 // COPYING.txt ...)
2145 //
2146
2147// kDebug (kfile_area) << "Figure out an extension: ";
2148 QString lastExtension = extension;
2149 extension.clear();
2150
2151 // Automatically Select Extension is only valid if the user is _saving_ a _file_
2152 if ((operationMode == KFileWidget::Saving) && (ops->mode() & KFile::File))
2153 {
2154 //
2155 // Get an extension from the filter
2156 //
2157
2158 QString filter = filterWidget->currentFilter();
2159 if (!filter.isEmpty())
2160 {
2161 // if the currently selected filename already has an extension which
2162 // is also included in the currently allowed extensions, keep it
2163 // otherwise use the default extension
2164 QString currentExtension = KMimeType::extractKnownExtension(locationEditCurrentText());
2165 if ( currentExtension.isEmpty() )
2166 currentExtension = locationEditCurrentText().section(QLatin1Char('.'), -1, -1);
2167 kDebug (kfile_area) << "filter:" << filter << "locationEdit:" << locationEditCurrentText()
2168 << "currentExtension:" << currentExtension;
2169
2170 QString defaultExtension;
2171 QStringList extensionList;
2172
2173 // e.g. "*.cpp"
2174 if (filter.indexOf ('/') < 0)
2175 {
2176 extensionList = filter.split(' ', QString::SkipEmptyParts);
2177 defaultExtension = getExtensionFromPatternList(extensionList);
2178 }
2179 // e.g. "text/html"
2180 else
2181 {
2182 KMimeType::Ptr mime = KMimeType::mimeType (filter);
2183 if (mime)
2184 {
2185 extensionList = mime->patterns();
2186 defaultExtension = mime->mainExtension();
2187 }
2188 }
2189
2190 if ( !currentExtension.isEmpty() && extensionList.contains(QLatin1String("*.") + currentExtension) )
2191 extension = QLatin1Char('.') + currentExtension;
2192 else
2193 extension = defaultExtension;
2194
2195 kDebug (kfile_area) << "List:" << extensionList << "auto-selected extension:" << extension;
2196 }
2197
2198
2199 //
2200 // GUI: checkbox
2201 //
2202
2203 QString whatsThisExtension;
2204 if (!extension.isEmpty())
2205 {
2206 // remember: sync any changes to the string with below
2207 autoSelectExtCheckBox->setText (i18n ("Automatically select filename e&xtension (%1)", extension));
2208 whatsThisExtension = i18n ("the extension <b>%1</b>", extension);
2209
2210 autoSelectExtCheckBox->setEnabled (true);
2211 autoSelectExtCheckBox->setChecked (autoSelectExtChecked);
2212 }
2213 else
2214 {
2215 // remember: sync any changes to the string with above
2216 autoSelectExtCheckBox->setText (i18n ("Automatically select filename e&xtension"));
2217 whatsThisExtension = i18n ("a suitable extension");
2218
2219 autoSelectExtCheckBox->setChecked (false);
2220 autoSelectExtCheckBox->setEnabled (false);
2221 }
2222
2223 const QString locationLabelText = stripUndisplayable (locationLabel->text());
2224 const QString filterLabelText = stripUndisplayable (filterLabel->text());
2225 autoSelectExtCheckBox->setWhatsThis( "<qt>" +
2226 i18n (
2227 "This option enables some convenient features for "
2228 "saving files with extensions:<br />"
2229 "<ol>"
2230 "<li>Any extension specified in the <b>%1</b> text "
2231 "area will be updated if you change the file type "
2232 "to save in.<br />"
2233 "<br /></li>"
2234 "<li>If no extension is specified in the <b>%2</b> "
2235 "text area when you click "
2236 "<b>Save</b>, %3 will be added to the end of the "
2237 "filename (if the filename does not already exist). "
2238 "This extension is based on the file type that you "
2239 "have chosen to save in.<br />"
2240 "<br />"
2241 "If you do not want KDE to supply an extension for the "
2242 "filename, you can either turn this option off or you "
2243 "can suppress it by adding a period (.) to the end of "
2244 "the filename (the period will be automatically "
2245 "removed)."
2246 "</li>"
2247 "</ol>"
2248 "If unsure, keep this option enabled as it makes your "
2249 "files more manageable."
2250 ,
2251 locationLabelText,
2252 locationLabelText,
2253 whatsThisExtension)
2254 + "</qt>"
2255 );
2256
2257 autoSelectExtCheckBox->show();
2258
2259
2260 // update the current filename's extension
2261 updateLocationEditExtension (lastExtension);
2262 }
2263 // Automatically Select Extension not valid
2264 else
2265 {
2266 autoSelectExtCheckBox->setChecked (false);
2267 autoSelectExtCheckBox->hide();
2268 }
2269}
2270
2271// Updates the extension of the filename specified in d->locationEdit if the
2272// Automatically Select Extension feature is enabled.
2273// (this prevents you from accidently saving "file.kwd" as RTF, for example)
2274void KFileWidgetPrivate::updateLocationEditExtension (const QString &lastExtension)
2275{
2276 if (!autoSelectExtCheckBox->isChecked() || extension.isEmpty())
2277 return;
2278
2279 QString urlStr = locationEditCurrentText();
2280 if (urlStr.isEmpty())
2281 return;
2282
2283 KUrl url = getCompleteUrl(urlStr);
2284// kDebug (kfile_area) << "updateLocationEditExtension (" << url << ")";
2285
2286 const int fileNameOffset = urlStr.lastIndexOf ('/') + 1;
2287 QString fileName = urlStr.mid (fileNameOffset);
2288
2289 const int dot = fileName.lastIndexOf ('.');
2290 const int len = fileName.length();
2291 if (dot > 0 && // has an extension already and it's not a hidden file
2292 // like ".hidden" (but we do accept ".hidden.ext")
2293 dot != len - 1 // and not deliberately suppressing extension
2294 )
2295 {
2296 // exists?
2297 KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo);
2298 bool result = KIO::NetAccess::synchronousRun(statJob, q);
2299 if (result)
2300 {
2301// kDebug (kfile_area) << "\tfile exists";
2302
2303 if (statJob->statResult().isDir())
2304 {
2305// kDebug (kfile_area) << "\tisDir - won't alter extension";
2306 return;
2307 }
2308
2309 // --- fall through ---
2310 }
2311
2312
2313 //
2314 // try to get rid of the current extension
2315 //
2316
2317 // catch "double extensions" like ".tar.gz"
2318 if (lastExtension.length() && fileName.endsWith (lastExtension))
2319 fileName.truncate (len - lastExtension.length());
2320 else if (extension.length() && fileName.endsWith (extension))
2321 fileName.truncate (len - extension.length());
2322 // can only handle "single extensions"
2323 else
2324 fileName.truncate (dot);
2325
2326 // add extension
2327 const QString newText = urlStr.left (fileNameOffset) + fileName + extension;
2328 if ( newText != locationEditCurrentText() )
2329 {
2330 locationEdit->setItemText(locationEdit->currentIndex(),urlStr.left (fileNameOffset) + fileName + extension);
2331 locationEdit->lineEdit()->setModified (true);
2332 }
2333 }
2334}
2335
2336// Updates the filter if the extension of the filename specified in d->locationEdit is changed
2337// (this prevents you from accidently saving "file.kwd" as RTF, for example)
2338void KFileWidgetPrivate::updateFilter()
2339{
2340// kDebug(kfile_area);
2341
2342 if ((operationMode == KFileWidget::Saving) && (ops->mode() & KFile::File) ) {
2343 QString urlStr = locationEditCurrentText();
2344 if (urlStr.isEmpty())
2345 return;
2346
2347 if( filterWidget->isMimeFilter()) {
2348 KMimeType::Ptr mime = KMimeType::findByPath(urlStr, 0, true);
2349 if (mime && mime->name() != KMimeType::defaultMimeType()) {
2350 if (filterWidget->currentFilter() != mime->name() &&
2351 filterWidget->filters().indexOf(mime->name()) != -1)
2352 filterWidget->setCurrentFilter(mime->name());
2353 }
2354 } else {
2355 QString filename = urlStr.mid( urlStr.lastIndexOf( KDIR_SEPARATOR ) + 1 ); // only filename
2356 foreach( const QString& filter, filterWidget->filters()) {
2357 QStringList patterns = filter.left( filter.indexOf( '|' )).split ( ' ', QString::SkipEmptyParts ); // '*.foo *.bar|Foo type' -> '*.foo', '*.bar'
2358 foreach ( const QString& p, patterns ) {
2359 if( KMimeType::matchFileName( filename, p )) {
2360 if ( p != "*" ) { // never match the catch-all filter
2361 filterWidget->setCurrentFilter( filter );
2362 }
2363 return; // do not repeat, could match a later filter
2364 }
2365 }
2366 }
2367 }
2368 }
2369}
2370
2371// applies only to a file that doesn't already exist
2372void KFileWidgetPrivate::appendExtension (KUrl &url)
2373{
2374// kDebug(kfile_area);
2375
2376 if (!autoSelectExtCheckBox->isChecked() || extension.isEmpty())
2377 return;
2378
2379 QString fileName = url.fileName();
2380 if (fileName.isEmpty())
2381 return;
2382
2383// kDebug (kfile_area) << "appendExtension(" << url << ")";
2384
2385 const int len = fileName.length();
2386 const int dot = fileName.lastIndexOf ('.');
2387
2388 const bool suppressExtension = (dot == len - 1);
2389 const bool unspecifiedExtension = (dot <= 0);
2390
2391 // don't KIO::Stat if unnecessary
2392 if (!(suppressExtension || unspecifiedExtension))
2393 return;
2394
2395 // exists?
2396 KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo);
2397 bool res = KIO::NetAccess::synchronousRun(statJob, q);
2398 if (res)
2399 {
2400// kDebug (kfile_area) << "\tfile exists - won't append extension";
2401 return;
2402 }
2403
2404 // suppress automatically append extension?
2405 if (suppressExtension)
2406 {
2407 //
2408 // Strip trailing dot
2409 // This allows lazy people to have autoSelectExtCheckBox->isChecked
2410 // but don't want a file extension to be appended
2411 // e.g. "README." will make a file called "README"
2412 //
2413 // If you really want a name like "README.", then type "README.."
2414 // and the trailing dot will be removed (or just stop being lazy and
2415 // turn off this feature so that you can type "README.")
2416 //
2417// kDebug (kfile_area) << "\tstrip trailing dot";
2418 url.setFileName (fileName.left (len - 1));
2419 }
2420 // evilmatically append extension :) if the user hasn't specified one
2421 else if (unspecifiedExtension)
2422 {
2423// kDebug (kfile_area) << "\tappending extension \'" << extension << "\'...";
2424 url.setFileName (fileName + extension);
2425// kDebug (kfile_area) << "\tsaving as \'" << url << "\'";
2426 }
2427}
2428
2429
2430// adds the selected files/urls to 'recent documents'
2431void KFileWidgetPrivate::addToRecentDocuments()
2432{
2433 int m = ops->mode();
2434 int atmost = KRecentDocument::maximumItems();
2435 //don't add more than we need. KRecentDocument::add() is pretty slow
2436
2437 if (m & KFile::LocalOnly) {
2438 const QStringList files = q->selectedFiles();
2439 QStringList::ConstIterator it = files.begin();
2440 for ( ; it != files.end() && atmost > 0; ++it ) {
2441 KRecentDocument::add( *it );
2442 atmost--;
2443 }
2444 }
2445
2446 else { // urls
2447 const KUrl::List urls = q->selectedUrls();
2448 KUrl::List::ConstIterator it = urls.begin();
2449 for ( ; it != urls.end() && atmost > 0; ++it ) {
2450 if ( (*it).isValid() ) {
2451 KRecentDocument::add( *it );
2452 atmost--;
2453 }
2454 }
2455 }
2456}
2457
2458KUrlComboBox* KFileWidget::locationEdit() const
2459{
2460 return d->locationEdit;
2461}
2462
2463KFileFilterCombo* KFileWidget::filterWidget() const
2464{
2465 return d->filterWidget;
2466}
2467
2468KActionCollection * KFileWidget::actionCollection() const
2469{
2470 return d->ops->actionCollection();
2471}
2472
2473void KFileWidgetPrivate::_k_toggleSpeedbar(bool show)
2474{
2475 if (show) {
2476 initSpeedbar();
2477 placesDock->show();
2478 lafBox->setColumnMinimumWidth(0, placesViewWidth);
2479
2480 // check to see if they have a home item defined, if not show the home button
2481 KUrl homeURL;
2482 homeURL.setPath( QDir::homePath() );
2483 KFilePlacesModel *model = static_cast<KFilePlacesModel*>(placesView->model());
2484 for (int rowIndex = 0 ; rowIndex < model->rowCount() ; rowIndex++) {
2485 QModelIndex index = model->index(rowIndex, 0);
2486 KUrl url = model->url(index);
2487
2488 if ( homeURL.equals( url, KUrl::CompareWithoutTrailingSlash ) ) {
2489 toolbar->removeAction( ops->actionCollection()->action( "home" ) );
2490 break;
2491 }
2492 }
2493 } else {
2494 if (q->sender() == placesDock && placesDock && placesDock->isVisibleTo(q)) {
2495 // we didn't *really* go away! the dialog was simply hidden or
2496 // we changed virtual desktops or ...
2497 return;
2498 }
2499
2500 if (placesDock) {
2501 placesDock->hide();
2502 }
2503
2504 QAction* homeAction = ops->actionCollection()->action("home");
2505 QAction* reloadAction = ops->actionCollection()->action("reload");
2506 if (!toolbar->actions().contains(homeAction)) {
2507 toolbar->insertAction(reloadAction, homeAction);
2508 }
2509
2510 // reset the lafbox to not follow the width of the splitter
2511 lafBox->setColumnMinimumWidth(0, 0);
2512 }
2513
2514 static_cast<KToggleAction *>(q->actionCollection()->action("toggleSpeedbar"))->setChecked(show);
2515
2516 // if we don't show the places panel, at least show the places menu
2517 urlNavigator->setPlacesSelectorVisible(!show);
2518}
2519
2520void KFileWidgetPrivate::_k_toggleBookmarks(bool show)
2521{
2522 if (show)
2523 {
2524 if (bookmarkHandler)
2525 {
2526 return;
2527 }
2528
2529 bookmarkHandler = new KFileBookmarkHandler( q );
2530 q->connect( bookmarkHandler, SIGNAL(openUrl(QString)),
2531 SLOT(_k_enterUrl(QString)));
2532
2533 bookmarkButton = new KActionMenu(KIcon("bookmarks"),i18n("Bookmarks"), q);
2534 bookmarkButton->setDelayed(false);
2535 q->actionCollection()->addAction("bookmark", bookmarkButton);
2536 bookmarkButton->setMenu(bookmarkHandler->menu());
2537 bookmarkButton->setWhatsThis(i18n("<qt>This button allows you to bookmark specific locations. "
2538 "Click on this button to open the bookmark menu where you may add, "
2539 "edit or select a bookmark.<br /><br />"
2540 "These bookmarks are specific to the file dialog, but otherwise operate "
2541 "like bookmarks elsewhere in KDE.</qt>"));
2542 toolbar->addAction(bookmarkButton);
2543 }
2544 else if (bookmarkHandler)
2545 {
2546 delete bookmarkHandler;
2547 bookmarkHandler = 0;
2548 delete bookmarkButton;
2549 bookmarkButton = 0;
2550 }
2551
2552 static_cast<KToggleAction *>(q->actionCollection()->action("toggleBookmarks"))->setChecked( show );
2553}
2554
2555
2556// static, overloaded
2557KUrl KFileWidget::getStartUrl( const KUrl& startDir,
2558 QString& recentDirClass )
2559{
2560 QString fileName; // result discarded
2561 return getStartUrl( startDir, recentDirClass, fileName );
2562}
2563
2564
2565// static, overloaded
2566KUrl KFileWidget::getStartUrl( const KUrl& startDir,
2567 QString& recentDirClass,
2568 QString& fileName )
2569{
2570 recentDirClass.clear();
2571 fileName.clear();
2572 KUrl ret;
2573
2574 bool useDefaultStartDir = startDir.isEmpty();
2575 if ( !useDefaultStartDir )
2576 {
2577 if ( startDir.protocol() == "kfiledialog" )
2578 {
2579
2580// The startDir URL with this protocol may be in the format:
2581// directory() fileName()
2582// 1. kfiledialog:///keyword "/" keyword
2583// 2. kfiledialog:///keyword?global "/" keyword
2584// 3. kfiledialog:///keyword/ "/" keyword
2585// 4. kfiledialog:///keyword/?global "/" keyword
2586// 5. kfiledialog:///keyword/filename /keyword filename
2587// 6. kfiledialog:///keyword/filename?global /keyword filename
2588
2589 QString keyword;
2590 QString urlDir = startDir.directory();
2591 QString urlFile = startDir.fileName();
2592 if ( urlDir == "/" ) // '1'..'4' above
2593 {
2594 keyword = urlFile;
2595 fileName.clear();
2596 }
2597 else // '5' or '6' above
2598 {
2599 keyword = urlDir.mid( 1 );
2600 fileName = urlFile;
2601 }
2602
2603 if ( startDir.query() == "?global" )
2604 recentDirClass = QString( "::%1" ).arg( keyword );
2605 else
2606 recentDirClass = QString( ":%1" ).arg( keyword );
2607
2608 ret = KUrl( KRecentDirs::dir(recentDirClass) );
2609 }
2610 else // not special "kfiledialog" URL
2611 {
2612 // "foo.png" only gives us a file name, the default start dir will be used.
2613 // "file:foo.png" (from KHTML/webkit, due to fromPath()) means the same
2614 // (and is the reason why we don't just use QUrl::isRelative()).
2615
2616 // In all other cases (startDir contains a directory path, or has no
2617 // fileName for us anyway, such as smb://), startDir is indeed a dir url.
2618
2619 if (!startDir.directory().isEmpty() ||
2620 startDir.fileName().isEmpty()) {
2621 // can use start directory
2622 ret = startDir; // will be checked by stat later
2623 // If we won't be able to list it (e.g. http), then use default
2624 if ( !KProtocolManager::supportsListing( ret ) ) {
2625 useDefaultStartDir = true;
2626 fileName = startDir.fileName();
2627 }
2628 }
2629 else // file name only
2630 {
2631 fileName = startDir.fileName();
2632 useDefaultStartDir = true;
2633 }
2634 }
2635 }
2636
2637 if ( useDefaultStartDir )
2638 {
2639 if (lastDirectory->isEmpty()) {
2640 lastDirectory->setPath(KGlobalSettings::documentPath());
2641 KUrl home;
2642 home.setPath( QDir::homePath() );
2643 // if there is no docpath set (== home dir), we prefer the current
2644 // directory over it. We also prefer the homedir when our CWD is
2645 // different from our homedirectory or when the document dir
2646 // does not exist
2647 if ( lastDirectory->path(KUrl::AddTrailingSlash) == home.path(KUrl::AddTrailingSlash) ||
2648 QDir::currentPath() != QDir::homePath() ||
2649 !QDir(lastDirectory->path(KUrl::AddTrailingSlash)).exists() )
2650 lastDirectory->setPath(QDir::currentPath());
2651 }
2652 ret = *lastDirectory;
2653 }
2654
2655 kDebug(kfile_area) << "for" << startDir << "->" << ret << "recentDirClass" << recentDirClass << "fileName" << fileName;
2656 return ret;
2657}
2658
2659void KFileWidget::setStartDir( const KUrl& directory )
2660{
2661 if ( directory.isValid() )
2662 *lastDirectory = directory;
2663}
2664
2665void KFileWidgetPrivate::setNonExtSelection()
2666{
2667 // Enhanced rename: Don't highlight the file extension.
2668 QString filename = locationEditCurrentText();
2669 QString extension = KMimeType::extractKnownExtension( filename );
2670
2671 if ( !extension.isEmpty() )
2672 locationEdit->lineEdit()->setSelection( 0, filename.length() - extension.length() - 1 );
2673 else
2674 {
2675 int lastDot = filename.lastIndexOf( '.' );
2676 if ( lastDot > 0 )
2677 locationEdit->lineEdit()->setSelection( 0, lastDot );
2678 }
2679}
2680
2681KToolBar * KFileWidget::toolBar() const
2682{
2683 return d->toolbar;
2684}
2685
2686void KFileWidget::setCustomWidget(QWidget* widget)
2687{
2688 delete d->bottomCustomWidget;
2689 d->bottomCustomWidget = widget;
2690
2691 // add it to the dialog, below the filter list box.
2692
2693 // Change the parent so that this widget is a child of the main widget
2694 d->bottomCustomWidget->setParent( this );
2695
2696 d->vbox->addWidget( d->bottomCustomWidget );
2697 //d->vbox->addSpacing(3); // can't do this every time...
2698
2699 // FIXME: This should adjust the tab orders so that the custom widget
2700 // comes after the Cancel button. The code appears to do this, but the result
2701 // somehow screws up the tab order of the file path combo box. Not a major
2702 // problem, but ideally the tab order with a custom widget should be
2703 // the same as the order without one.
2704 setTabOrder(d->cancelButton, d->bottomCustomWidget);
2705 setTabOrder(d->bottomCustomWidget, d->urlNavigator);
2706}
2707
2708void KFileWidget::setCustomWidget(const QString& text, QWidget* widget)
2709{
2710 delete d->labeledCustomWidget;
2711 d->labeledCustomWidget = widget;
2712
2713 QLabel* label = new QLabel(text, this);
2714 label->setAlignment(Qt::AlignRight);
2715 d->lafBox->addWidget(label, 2, 0, Qt::AlignVCenter);
2716 d->lafBox->addWidget(widget, 2, 1, Qt::AlignVCenter);
2717}
2718
2719void KFileWidget::virtual_hook( int id, void* data )
2720{
2721 // this is a workaround to avoid binary compatibility breakage
2722 // since setConfirmOverwrite in kabstractfilewidget.h is a new function
2723 // introduced for 4.2. As stated in kabstractfilewidget.h this workaround
2724 // is going to become a virtual function for KDE5
2725
2726 switch (id) {
2727 case 0: { // setConfirmOverwrite(bool)
2728 bool *enable = static_cast<bool*>(data);
2729 d->confirmOverwrite = *enable;
2730 }
2731 break;
2732 case 1: { // setInlinePreviewShown(bool)
2733 bool *show = static_cast<bool*>(data);
2734 d->setInlinePreviewShown(*show);
2735 }
2736 break;
2737 default:
2738 break;
2739 }
2740}
2741
2742KDirOperator* KFileWidget::dirOperator()
2743{
2744 return d->ops;
2745}
2746
2747void KFileWidget::readConfig( KConfigGroup& group )
2748{
2749 d->configGroup = group;
2750 d->readViewConfig();
2751 d->readRecentFiles();
2752}
2753
2754QString KFileWidgetPrivate::locationEditCurrentText() const
2755{
2756 return QDir::fromNativeSeparators(locationEdit->currentText());
2757}
2758
2759KUrl KFileWidgetPrivate::mostLocalUrl(const KUrl &url)
2760{
2761 if (url.isLocalFile()) {
2762 return url;
2763 }
2764
2765 KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo);
2766 bool res = KIO::NetAccess::synchronousRun(statJob, q);
2767
2768 if (!res) {
2769 return url;
2770 }
2771
2772 const QString path = statJob->statResult().stringValue(KIO::UDSEntry::UDS_LOCAL_PATH);
2773 if (!path.isEmpty()) {
2774 KUrl newUrl;
2775 newUrl.setPath(path);
2776 return newUrl;
2777 }
2778
2779 return url;
2780}
2781
2782void KFileWidgetPrivate::setInlinePreviewShown(bool show)
2783{
2784 ops->setInlinePreviewShown(show);
2785}
2786
2787
2788#include "kfilewidget.moc"
2789