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 | |
62 | class KDirSelectDialog::Private |
63 | { |
64 | public: |
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 *; |
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 | |
104 | void 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 | |
117 | void 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 | |
127 | void 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 | |
170 | void 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 | |
189 | void 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 | |
207 | void 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 | |
226 | void KDirSelectDialog::Private::( const QPoint& pos ) |
227 | { |
228 | m_contextMenu->popup( m_treeView->viewport()->mapToGlobal(pos) ); |
229 | } |
230 | |
231 | void KDirSelectDialog::Private::slotExpand(const QModelIndex &index) |
232 | { |
233 | m_treeView->setExpanded(index, !m_treeView->isExpanded(index)); |
234 | } |
235 | |
236 | void KDirSelectDialog::Private::slotNewFolder() |
237 | { |
238 | slotMkdir(); |
239 | } |
240 | |
241 | void 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 | |
252 | void 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 | |
263 | void 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 | |
272 | KDirSelectDialog::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 | |
400 | KDirSelectDialog::~KDirSelectDialog() |
401 | { |
402 | delete d; |
403 | } |
404 | |
405 | KUrl 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 | |
421 | QAbstractItemView* KDirSelectDialog::view() const |
422 | { |
423 | return d->m_treeView; |
424 | } |
425 | |
426 | bool KDirSelectDialog::localOnly() const |
427 | { |
428 | return d->m_localOnly; |
429 | } |
430 | |
431 | KUrl KDirSelectDialog::startDir() const |
432 | { |
433 | return d->m_startDir; |
434 | } |
435 | |
436 | void 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 | |
462 | void 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 | |
479 | void KDirSelectDialog::hideEvent( QHideEvent *event ) |
480 | { |
481 | d->saveConfig( KGlobal::config(), "DirSelect Dialog" ); |
482 | |
483 | KDialog::hideEvent(event); |
484 | } |
485 | |
486 | // static |
487 | KUrl 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 | |