1/*
2 Copyright (C) 2001,2002 Carsten Pfeiffer <pfeiffer@kde.org>
3 Copyright (C) 2001 Michael Jarrett <michaelj@corel.com>
4 Copyright (C) 2009 Shaun Reich <shaun.reich@kdemail.net>
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Library General Public
8 License version 2 as published by the Free Software Foundation.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
19*/
20
21#include "kdirselectdialog.h"
22
23#include <QtCore/QDir>
24#include <QtCore/QStringList>
25#include <QtGui/QLayout>
26
27#include <kactioncollection.h>
28#include <kapplication.h>
29#include <kauthorized.h>
30#include <kconfig.h>
31#include <kconfiggroup.h>
32#include <khistorycombobox.h>
33#include <kfiledialog.h>
34#include <kfiletreeview.h>
35#include <kfileitemdelegate.h>
36#include <kglobalsettings.h>
37#include <kicon.h>
38#include <kinputdialog.h>
39#include <kio/job.h>
40#include <kio/deletejob.h>
41#include <kio/copyjob.h>
42#include <kio/netaccess.h>
43#include <kio/renamedialog.h>
44#include <jobuidelegate.h>
45#include <klocale.h>
46#include <kmessagebox.h>
47#include <krecentdirs.h>
48#include <ktoggleaction.h>
49#include <kurlcompletion.h>
50#include <kurlpixmapprovider.h>
51#include <kdebug.h>
52#include <kpropertiesdialog.h>
53#include <kpushbutton.h>
54#include <kmenu.h>
55
56#include "kfileplacesview.h"
57#include "kfileplacesmodel.h"
58// ### add mutator for treeview!
59
60
61
62class KDirSelectDialog::Private
63{
64public:
65 Private( bool localOnly, KDirSelectDialog *parent )
66 : m_parent( parent ),
67 m_localOnly( localOnly ),
68 m_comboLocked( false ),
69 m_urlCombo(0)
70 {
71 }
72
73 void readConfig(const KSharedConfigPtr &config, const QString& group);
74 void saveConfig(KSharedConfigPtr config, const QString& group);
75 void slotMkdir();
76
77 void slotCurrentChanged();
78 void slotExpand(const QModelIndex&);
79 void slotUrlActivated(const QString&);
80 void slotComboTextChanged(const QString&);
81 void slotContextMenuRequested(const QPoint&);
82 void slotNewFolder();
83 void slotMoveToTrash();
84 void slotDelete();
85 void slotProperties();
86
87 KDirSelectDialog *m_parent;
88 bool m_localOnly : 1;
89 bool m_comboLocked : 1;
90 KUrl m_rootUrl;
91 KUrl m_startDir;
92 KFileTreeView *m_treeView;
93 KMenu *m_contextMenu;
94 KActionCollection *m_actions;
95 KFilePlacesView *m_placesView;
96 KHistoryComboBox *m_urlCombo;
97 QString m_recentDirClass;
98 KUrl m_startURL;
99 KAction* moveToTrash;
100 KAction* deleteAction;
101 KAction* showHiddenFoldersAction;
102};
103
104void KDirSelectDialog::Private::readConfig(const KSharedConfig::Ptr &config, const QString& group)
105{
106 m_urlCombo->clear();
107
108 KConfigGroup conf( config, group );
109 m_urlCombo->setHistoryItems( conf.readPathEntry( "History Items", QStringList() ));
110
111 const QSize size = conf.readEntry("DirSelectDialog Size", QSize());
112 if (size.isValid()) {
113 m_parent->resize(size);
114 }
115}
116
117void KDirSelectDialog::Private::saveConfig(KSharedConfig::Ptr config, const QString& group)
118{
119 KConfigGroup conf( config, group );
120 KConfigGroup::WriteConfigFlags flags(KConfigGroup::Persistent|KConfigGroup::Global);
121 conf.writePathEntry( "History Items", m_urlCombo->historyItems(), flags );
122 conf.writeEntry( "DirSelectDialog Size", m_parent->size(), flags );
123
124 config->sync();
125}
126
127void KDirSelectDialog::Private::slotMkdir()
128{
129 bool ok;
130 QString where = m_parent->url().pathOrUrl();
131 QString name = i18nc("folder name", "New Folder" );
132 if ( m_parent->url().isLocalFile() && QFileInfo( m_parent->url().path(KUrl::AddTrailingSlash) + name ).exists() )
133 name = KIO::RenameDialog::suggestName( m_parent->url(), name );
134
135 QString directory = KIO::encodeFileName( KInputDialog::getText( i18nc("@title:window", "New Folder" ),
136 i18nc("@label:textbox", "Create new folder in:\n%1" , where ),
137 name, &ok, m_parent));
138 if (!ok)
139 return;
140
141 bool selectDirectory = true;
142 bool writeOk = false;
143 bool exists = false;
144 KUrl folderurl( m_parent->url() );
145
146 const QStringList dirs = directory.split( '/', QString::SkipEmptyParts );
147 QStringList::ConstIterator it = dirs.begin();
148
149 for ( ; it != dirs.end(); ++it )
150 {
151 folderurl.addPath( *it );
152 exists = KIO::NetAccess::exists( folderurl, KIO::NetAccess::DestinationSide, m_parent );
153 writeOk = !exists && KIO::NetAccess::mkdir( folderurl, m_parent );
154 }
155
156 if ( exists ) // url was already existent
157 {
158 QString which = folderurl.isLocalFile() ? folderurl.path() : folderurl.prettyUrl();
159 KMessageBox::sorry(m_parent, i18n("A file or folder named %1 already exists.", which));
160 selectDirectory = false;
161 }
162 else if ( !writeOk ) {
163 KMessageBox::sorry(m_parent, i18n("You do not have permission to create that folder." ));
164 }
165 else if ( selectDirectory ) {
166 m_parent->setCurrentUrl( folderurl );
167 }
168}
169
170void KDirSelectDialog::Private::slotCurrentChanged()
171{
172 if ( m_comboLocked )
173 return;
174
175 const KUrl u = m_treeView->currentUrl();
176
177 if ( u.isValid() )
178 {
179 if ( u.isLocalFile() )
180 m_urlCombo->setEditText( u.toLocalFile() );
181
182 else // remote url
183 m_urlCombo->setEditText( u.prettyUrl() );
184 }
185 else
186 m_urlCombo->setEditText( QString() );
187}
188
189void KDirSelectDialog::Private::slotUrlActivated( const QString& text )
190{
191 if ( text.isEmpty() )
192 return;
193
194 KUrl url( text );
195 m_urlCombo->addToHistory( url.prettyUrl() );
196
197 if ( m_parent->localOnly() && !url.isLocalFile() )
198 return; //FIXME: messagebox for the user
199
200 KUrl oldUrl = m_treeView->currentUrl();
201 if ( oldUrl.isEmpty() )
202 oldUrl = m_startDir;
203
204 m_parent->setCurrentUrl( oldUrl );
205}
206
207void KDirSelectDialog::Private::slotComboTextChanged( const QString& text )
208{
209 m_treeView->blockSignals(true);
210 KUrl url( text );
211#ifdef Q_OS_WIN
212 if( url.isLocalFile() && !m_treeView->rootUrl().isParentOf( url ) )
213 {
214 KUrl tmp = url.upUrl();
215 while(tmp != KUrl("file:///")) {
216 url = tmp;
217 tmp = url.upUrl();
218 }
219 m_treeView->setRootUrl( url );
220 }
221#endif
222 m_treeView->setCurrentUrl( url );
223 m_treeView->blockSignals( false );
224}
225
226void KDirSelectDialog::Private::slotContextMenuRequested( const QPoint& pos )
227{
228 m_contextMenu->popup( m_treeView->viewport()->mapToGlobal(pos) );
229}
230
231void KDirSelectDialog::Private::slotExpand(const QModelIndex &index)
232{
233 m_treeView->setExpanded(index, !m_treeView->isExpanded(index));
234}
235
236void KDirSelectDialog::Private::slotNewFolder()
237{
238 slotMkdir();
239}
240
241void KDirSelectDialog::Private::slotMoveToTrash()
242{
243 const KUrl url = m_treeView->selectedUrl();
244 KIO::JobUiDelegate job;
245 if (job.askDeleteConfirmation(KUrl::List() << url, KIO::JobUiDelegate::Trash, KIO::JobUiDelegate::DefaultConfirmation)) {
246 KIO::CopyJob* copyJob = KIO::trash(url);
247 copyJob->ui()->setWindow(m_parent);
248 copyJob->ui()->setAutoErrorHandlingEnabled(true);
249 }
250}
251
252void KDirSelectDialog::Private::slotDelete()
253{
254 const KUrl url = m_treeView->selectedUrl();
255 KIO::JobUiDelegate job;
256 if (job.askDeleteConfirmation(KUrl::List() << url, KIO::JobUiDelegate::Delete, KIO::JobUiDelegate::DefaultConfirmation)) {
257 KIO::DeleteJob* deleteJob = KIO::del(url);
258 deleteJob->ui()->setWindow(m_parent);
259 deleteJob->ui()->setAutoErrorHandlingEnabled(true);
260 }
261}
262
263void KDirSelectDialog::Private::slotProperties()
264{
265 KPropertiesDialog* dialog = 0;
266 dialog = new KPropertiesDialog(m_treeView->selectedUrl(), this->m_parent);
267 dialog->setAttribute(Qt::WA_DeleteOnClose);
268 dialog->show();
269}
270
271
272KDirSelectDialog::KDirSelectDialog(const KUrl &startDir, bool localOnly,
273 QWidget *parent)
274#ifdef Q_WS_WIN
275 : KDialog( parent , Qt::WindowMinMaxButtonsHint),
276#else
277 : KDialog( parent ),
278#endif
279 d( new Private( localOnly, this ) )
280{
281 setCaption( i18nc("@title:window","Select Folder") );
282 setButtons( Ok | Cancel | User1 );
283 setButtonGuiItem( User1, KGuiItem( i18nc("@action:button","New Folder..."), "folder-new" ) );
284 setDefaultButton(Ok);
285 button(Ok)->setFocus();
286
287 QFrame *page = new QFrame(this);
288 setMainWidget(page);
289 QHBoxLayout *hlay = new QHBoxLayout( page);
290 hlay->setMargin(0);
291 QVBoxLayout *mainLayout = new QVBoxLayout();
292 d->m_actions=new KActionCollection(this);
293 d->m_actions->addAssociatedWidget(this);
294 d->m_placesView = new KFilePlacesView( page );
295 d->m_placesView->setModel(new KFilePlacesModel(d->m_placesView));
296 d->m_placesView->setObjectName( QLatin1String( "speedbar" ) );
297 d->m_placesView->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
298 d->m_placesView->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
299 connect( d->m_placesView, SIGNAL(urlChanged(KUrl)),
300 SLOT(setCurrentUrl(KUrl)) );
301 hlay->addWidget( d->m_placesView );
302 hlay->addLayout( mainLayout );
303
304 d->m_treeView = new KFileTreeView(page);
305 d->m_treeView->setDirOnlyMode(true);
306 d->m_treeView->setContextMenuPolicy(Qt::CustomContextMenu);
307
308 for (int i = 1; i < d->m_treeView->model()->columnCount(); ++i)
309 d->m_treeView->hideColumn(i);
310
311 d->m_urlCombo = new KHistoryComboBox( page);
312 d->m_urlCombo->setLayoutDirection( Qt::LeftToRight );
313 d->m_urlCombo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength);
314 d->m_urlCombo->setTrapReturnKey( true );
315 d->m_urlCombo->setPixmapProvider( new KUrlPixmapProvider() );
316 KUrlCompletion *comp = new KUrlCompletion();
317 comp->setMode( KUrlCompletion::DirCompletion );
318 d->m_urlCombo->setCompletionObject( comp, true );
319 d->m_urlCombo->setAutoDeleteCompletionObject( true );
320 d->m_urlCombo->setDuplicatesEnabled( false );
321
322 d->m_contextMenu = new KMenu( this );
323
324 KAction* newFolder = new KAction( i18nc("@action:inmenu","New Folder..."), this);
325 d->m_actions->addAction( newFolder->objectName(), newFolder );
326 newFolder->setIcon( KIcon( "folder-new" ) );
327 newFolder->setShortcut( Qt::Key_F10);
328 connect( newFolder, SIGNAL(triggered(bool)), this, SLOT(slotNewFolder()) );
329 d->m_contextMenu->addAction( newFolder );
330
331 d->moveToTrash = new KAction( i18nc( "@action:inmenu","Move to Trash" ), this );
332 d->m_actions->addAction( d->moveToTrash->objectName(), d->moveToTrash );
333 d->moveToTrash->setIcon( KIcon( "user-trash" ) );
334 d->moveToTrash->setShortcut(KShortcut(Qt::Key_Delete));
335 connect( d->moveToTrash, SIGNAL(triggered(bool)), this, SLOT(slotMoveToTrash()) );
336 d->m_contextMenu->addAction( d->moveToTrash );
337
338 d->deleteAction = new KAction( i18nc("@action:inmenu","Delete"), this );
339 d->m_actions->addAction( d->deleteAction->objectName(), d->deleteAction );
340 d->deleteAction->setIcon( KIcon( "edit-delete" ) );
341 d->deleteAction->setShortcut( KShortcut( Qt::SHIFT + Qt::Key_Delete ) );
342 connect( d->deleteAction, SIGNAL(triggered(bool)), this, SLOT(slotDelete()) );
343 d->m_contextMenu->addAction( d->deleteAction );
344
345 d->m_contextMenu->addSeparator();
346
347 d->showHiddenFoldersAction = new KToggleAction( i18nc("@option:check", "Show Hidden Folders"), this );
348 d->m_actions->addAction( d->showHiddenFoldersAction->objectName(), d->showHiddenFoldersAction );
349 d->showHiddenFoldersAction->setShortcut( Qt::Key_F8 );
350 connect( d->showHiddenFoldersAction, SIGNAL(triggered(bool)), d->m_treeView, SLOT(setShowHiddenFiles(bool)) );
351 d->m_contextMenu->addAction( d->showHiddenFoldersAction );
352 d->m_contextMenu->addSeparator();
353
354 KAction* propertiesAction = new KAction( i18nc("@action:inmenu","Properties"), this);
355 d->m_actions->addAction(propertiesAction->objectName(), propertiesAction);
356 propertiesAction->setIcon(KIcon("document-properties"));
357 propertiesAction->setShortcut(KShortcut(Qt::ALT + Qt::Key_Return));
358 connect( propertiesAction, SIGNAL(triggered(bool)), this, SLOT(slotProperties()) );
359 d->m_contextMenu->addAction( propertiesAction );
360
361 d->m_startURL = KFileDialog::getStartUrl( startDir, d->m_recentDirClass );
362 if ( localOnly && !d->m_startURL.isLocalFile() )
363 {
364 d->m_startURL = KUrl();
365 QString docPath = KGlobalSettings::documentPath();
366 if (QDir(docPath).exists())
367 d->m_startURL.setPath( docPath );
368 else
369 d->m_startURL.setPath( QDir::homePath() );
370 }
371
372 d->m_startDir = d->m_startURL;
373 d->m_rootUrl = d->m_treeView->rootUrl();
374
375 d->readConfig( KGlobal::config(), "DirSelect Dialog" );
376
377 mainLayout->addWidget( d->m_treeView, 1 );
378 mainLayout->addWidget( d->m_urlCombo, 0 );
379
380 connect( d->m_treeView, SIGNAL(currentChanged(KUrl)),
381 SLOT(slotCurrentChanged()));
382 connect( d->m_treeView, SIGNAL(activated(QModelIndex)),
383 SLOT(slotExpand(QModelIndex)));
384 connect( d->m_treeView, SIGNAL(customContextMenuRequested(QPoint)),
385 SLOT(slotContextMenuRequested(QPoint)));
386
387 connect( d->m_urlCombo, SIGNAL(editTextChanged(QString)),
388 SLOT(slotComboTextChanged(QString)));
389 connect( d->m_urlCombo, SIGNAL(activated(QString)),
390 SLOT(slotUrlActivated(QString)));
391 connect( d->m_urlCombo, SIGNAL(returnPressed(QString)),
392 SLOT(slotUrlActivated(QString)));
393
394 connect(this, SIGNAL(user1Clicked()), this, SLOT(slotNewFolder()));
395
396 setCurrentUrl(d->m_startURL);
397}
398
399
400KDirSelectDialog::~KDirSelectDialog()
401{
402 delete d;
403}
404
405KUrl KDirSelectDialog::url() const
406{
407 KUrl comboUrl(d->m_urlCombo->currentText());
408
409 if ( comboUrl.isValid() ) {
410 KIO::StatJob *statJob = KIO::stat(comboUrl, KIO::HideProgressInfo);
411 const bool ok = KIO::NetAccess::synchronousRun(statJob, d->m_parent);
412 if (ok && statJob->statResult().isDir()) {
413 return comboUrl;
414 }
415 }
416
417 kDebug() << comboUrl.path() << " is not an accessible directory";
418 return d->m_treeView->currentUrl();
419}
420
421QAbstractItemView* KDirSelectDialog::view() const
422{
423 return d->m_treeView;
424}
425
426bool KDirSelectDialog::localOnly() const
427{
428 return d->m_localOnly;
429}
430
431KUrl KDirSelectDialog::startDir() const
432{
433 return d->m_startDir;
434}
435
436void KDirSelectDialog::setCurrentUrl( const KUrl& url )
437{
438 if ( !url.isValid() )
439 return;
440
441 if (url.protocol() != d->m_rootUrl.protocol()) {
442 KUrl u( url );
443 u.cd("/");//NOTE portability?
444 d->m_treeView->setRootUrl( u );
445 d->m_rootUrl = u;
446 }
447
448 //Check if url represents a hidden folder and enable showing them
449 QString fileName = url.fileName();
450 //TODO a better hidden file check?
451 bool isHidden = fileName.length() > 1 && fileName[0] == '.' &&
452 (fileName.length() > 2 ? fileName[1] != '.' : true);
453 bool showHiddenFiles = isHidden && !d->m_treeView->showHiddenFiles();
454 if (showHiddenFiles) {
455 d->showHiddenFoldersAction->setChecked(true);
456 d->m_treeView->setShowHiddenFiles(true);
457 }
458
459 d->m_treeView->setCurrentUrl( url );
460}
461
462void KDirSelectDialog::accept()
463{
464 KUrl selectedUrl = url();
465 if (!selectedUrl.isValid()) {
466 return;
467 }
468
469 if (!d->m_recentDirClass.isEmpty()) {
470 KRecentDirs::add(d->m_recentDirClass, selectedUrl.url());
471 }
472
473 d->m_urlCombo->addToHistory( selectedUrl.prettyUrl() );
474 KFileDialog::setStartDir( url() );
475
476 KDialog::accept();
477}
478
479void KDirSelectDialog::hideEvent( QHideEvent *event )
480{
481 d->saveConfig( KGlobal::config(), "DirSelect Dialog" );
482
483 KDialog::hideEvent(event);
484}
485
486// static
487KUrl KDirSelectDialog::selectDirectory( const KUrl& startDir,
488 bool localOnly,
489 QWidget *parent,
490 const QString& caption)
491{
492 KDirSelectDialog myDialog( startDir, localOnly, parent);
493
494 if ( !caption.isNull() )
495 myDialog.setCaption( caption );
496
497 if ( myDialog.exec() == QDialog::Accepted )
498 return KIO::NetAccess::mostLocalUrl(myDialog.url(),parent);
499 else
500 return KUrl();
501}
502
503#include "kdirselectdialog.moc"
504