File indexing completed on 2025-01-19 12:45:16
0001 /* 0002 Copyright (C) 2001,2002 Carsten Pfeiffer <pfeiffer@kde.org> 0003 Copyright (C) 2001 Michael Jarrett <michaelj@corel.com> 0004 Copyright (C) 2009 Shaun Reich <shaun.reich@kdemail.net> 0005 0006 This library is free software; you can redistribute it and/or 0007 modify it under the terms of the GNU Library General Public 0008 License version 2 as published by the Free Software Foundation. 0009 0010 This library is distributed in the hope that it will be useful, 0011 but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0013 Library General Public License for more details. 0014 0015 You should have received a copy of the GNU Library General Public License 0016 along with this library; see the file COPYING.LIB. If not, write to 0017 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0018 Boston, MA 02110-1301, USA. 0019 */ 0020 0021 #include "kdirselectdialog.h" 0022 0023 #include <QDebug> 0024 #include <QDialogButtonBox> 0025 #include <QDir> 0026 #include <QFileDialog> 0027 #include <QInputDialog> 0028 #include <QLayout> 0029 #include <QMenu> 0030 #include <QPushButton> 0031 #include <QStandardPaths> 0032 #include <QStringList> 0033 #include <QUrl> 0034 0035 #include <kio/jobuidelegate.h> 0036 #include <kactioncollection.h> 0037 #include <kauthorized.h> 0038 #include <kconfig.h> 0039 #include <kconfiggroup.h> 0040 #include <kfiletreeview_p.h> 0041 #include <kfileitemdelegate.h> 0042 #include <khistorycombobox.h> 0043 #include <kio/job.h> 0044 #include <kio/deletejob.h> 0045 #include <kio/copyjob.h> 0046 #include <kio/mkdirjob.h> 0047 #include <kjobwidgets.h> 0048 #include <klocalizedstring.h> 0049 #include <kmessagebox.h> 0050 #include <kpropertiesdialog.h> 0051 #include <krecentdirs.h> 0052 #include <kservice.h> 0053 #include <ksharedconfig.h> 0054 #include <ktoggleaction.h> 0055 #include <kurlcompletion.h> 0056 #include <kurlpixmapprovider.h> 0057 #include <kfilewidget.h> 0058 #include <kfileutils.h> 0059 0060 #include "kfileplacesview.h" 0061 #include "kfileplacesmodel.h" 0062 // ### add mutator for treeview! 0063 #ifdef stat 0064 #undef stat 0065 #endif 0066 0067 class Q_DECL_HIDDEN KDirSelectDialog::Private 0068 { 0069 public: 0070 Private(bool localOnly, KDirSelectDialog *parent) 0071 : m_parent(parent), 0072 m_localOnly(localOnly), 0073 m_comboLocked(false), 0074 m_urlCombo(nullptr) 0075 { 0076 } 0077 0078 void readConfig(const KSharedConfigPtr &config, const QString &group); 0079 void saveConfig(KSharedConfigPtr config, const QString &group); 0080 void slotMkdir(); 0081 0082 void slotCurrentChanged(); 0083 void slotExpand(const QModelIndex &); 0084 void slotUrlActivated(const QString &); 0085 void slotComboTextChanged(const QString &); 0086 void slotContextMenuRequested(const QPoint &); 0087 void slotNewFolder(); 0088 void slotMoveToTrash(); 0089 void slotDelete(); 0090 void slotProperties(); 0091 0092 KDirSelectDialog *m_parent; 0093 bool m_localOnly : 1; 0094 bool m_comboLocked : 1; 0095 QUrl m_rootUrl; 0096 QUrl m_startDir; 0097 KFileTreeView *m_treeView; 0098 QMenu *m_contextMenu; 0099 KActionCollection *m_actions; 0100 KFilePlacesView *m_placesView; 0101 KHistoryComboBox *m_urlCombo; 0102 QString m_recentDirClass; 0103 QUrl m_startURL; 0104 QAction *moveToTrash; 0105 QAction *deleteAction; 0106 QAction *showHiddenFoldersAction; 0107 }; 0108 0109 void KDirSelectDialog::Private::readConfig(const KSharedConfig::Ptr &config, const QString &group) 0110 { 0111 m_urlCombo->clear(); 0112 0113 KConfigGroup conf(config, group); 0114 m_urlCombo->setHistoryItems(conf.readPathEntry("History Items", QStringList())); 0115 0116 const QSize size = conf.readEntry("DirSelectDialog Size", QSize()); 0117 if (size.isValid()) { 0118 m_parent->resize(size); 0119 } 0120 } 0121 0122 void KDirSelectDialog::Private::saveConfig(KSharedConfig::Ptr config, const QString &group) 0123 { 0124 KConfigGroup conf(config, group); 0125 KConfigGroup::WriteConfigFlags flags(KConfigGroup::Persistent | KConfigGroup::Global); 0126 conf.writePathEntry("History Items", m_urlCombo->historyItems(), flags); 0127 conf.writeEntry("DirSelectDialog Size", m_parent->size(), flags); 0128 0129 config->sync(); 0130 } 0131 0132 void KDirSelectDialog::Private::slotMkdir() 0133 { 0134 bool ok; 0135 QString where = m_parent->url().toDisplayString(QUrl::PreferLocalFile); 0136 QString name = i18nc("folder name", "New Folder"); 0137 if (m_parent->url().isLocalFile() && QFileInfo(m_parent->url().toLocalFile() + '/' + name).exists()) { 0138 name = KFileUtils::suggestName(m_parent->url(), name); 0139 } 0140 0141 QString directory = KIO::encodeFileName(QInputDialog::getText(m_parent, i18nc("@title:window", "New Folder"), 0142 i18nc("@label:textbox", "Create new folder in:\n%1", where), 0143 QLineEdit::Normal, name, &ok)); 0144 if (!ok) { 0145 return; 0146 } 0147 0148 bool selectDirectory = true; 0149 bool writeOk = false; 0150 bool exists = false; 0151 QUrl folderurl(m_parent->url()); 0152 0153 const QStringList dirs = directory.split('/', QString::SkipEmptyParts); 0154 QStringList::ConstIterator it = dirs.begin(); 0155 0156 for (; it != dirs.end(); ++it) { 0157 folderurl.setPath(folderurl.path() + '/' + *it); 0158 KIO::StatJob *job = KIO::stat(folderurl); 0159 KJobWidgets::setWindow(job, m_parent); 0160 job->setDetails(0); //We only want to know if it exists, 0 == that. 0161 job->setSide(KIO::StatJob::DestinationSide); 0162 exists = job->exec(); 0163 if (!exists) { 0164 KIO::MkdirJob *job = KIO::mkdir(folderurl); 0165 KJobWidgets::setWindow(job, m_parent); 0166 writeOk = job->exec(); 0167 } 0168 } 0169 0170 if (exists) { // url was already existent 0171 QString which = folderurl.toDisplayString(QUrl::PreferLocalFile); 0172 KMessageBox::error(m_parent, i18n("A file or folder named %1 already exists.", which)); 0173 selectDirectory = false; 0174 } else if (!writeOk) { 0175 KMessageBox::error(m_parent, i18n("You do not have permission to create that folder.")); 0176 } else if (selectDirectory) { 0177 m_parent->setCurrentUrl(folderurl); 0178 } 0179 } 0180 0181 void KDirSelectDialog::Private::slotCurrentChanged() 0182 { 0183 if (m_comboLocked) { 0184 return; 0185 } 0186 0187 const QUrl u = m_treeView->currentUrl(); 0188 0189 if (u.isValid()) { 0190 m_urlCombo->setEditText(u.toDisplayString(QUrl::PreferLocalFile)); 0191 } else { 0192 m_urlCombo->setEditText(QString()); 0193 } 0194 } 0195 0196 void KDirSelectDialog::Private::slotUrlActivated(const QString &text) 0197 { 0198 if (text.isEmpty()) { 0199 return; 0200 } 0201 0202 const QUrl url = QUrl::fromUserInput(text); 0203 m_urlCombo->addToHistory(url.toDisplayString()); 0204 0205 if (m_parent->localOnly() && !url.isLocalFile()) { 0206 return; //FIXME: messagebox for the user 0207 } 0208 0209 QUrl oldUrl = m_treeView->currentUrl(); 0210 if (oldUrl.isEmpty()) { 0211 oldUrl = m_startDir; 0212 } 0213 0214 m_parent->setCurrentUrl(oldUrl); 0215 } 0216 0217 void KDirSelectDialog::Private::slotComboTextChanged(const QString &text) 0218 { 0219 m_treeView->blockSignals(true); 0220 QUrl url = QUrl::fromUserInput(text); 0221 #ifdef Q_OS_WIN 0222 QUrl rootUrl(m_treeView->rootUrl()); 0223 if (url.isLocalFile() && !rootUrl.isParentOf(url) && !rootUrl.matches(url, QUrl::StripTrailingSlash)) { 0224 QUrl tmp = KIO::upUrl(url); 0225 while (tmp.path().length() > 1) { 0226 url = tmp; 0227 tmp = KIO::upUrl(url); 0228 } 0229 m_treeView->setRootUrl(url); 0230 } 0231 #endif 0232 m_treeView->setCurrentUrl(url); 0233 m_treeView->blockSignals(false); 0234 } 0235 0236 void KDirSelectDialog::Private::slotContextMenuRequested(const QPoint &pos) 0237 { 0238 m_contextMenu->popup(m_treeView->viewport()->mapToGlobal(pos)); 0239 } 0240 0241 void KDirSelectDialog::Private::slotExpand(const QModelIndex &index) 0242 { 0243 m_treeView->setExpanded(index, !m_treeView->isExpanded(index)); 0244 } 0245 0246 void KDirSelectDialog::Private::slotNewFolder() 0247 { 0248 slotMkdir(); 0249 } 0250 0251 void KDirSelectDialog::Private::slotMoveToTrash() 0252 { 0253 const QUrl url = m_treeView->selectedUrl(); 0254 KIO::JobUiDelegate job; 0255 if (job.askDeleteConfirmation(QList<QUrl>() << url, KIO::JobUiDelegate::Trash, KIO::JobUiDelegate::DefaultConfirmation)) { 0256 KIO::CopyJob *copyJob = KIO::trash(url); 0257 KJobWidgets::setWindow(copyJob, m_parent); 0258 copyJob->ui()->setAutoErrorHandlingEnabled(true); 0259 } 0260 } 0261 0262 void KDirSelectDialog::Private::slotDelete() 0263 { 0264 const QUrl url = m_treeView->selectedUrl(); 0265 KIO::JobUiDelegate job; 0266 if (job.askDeleteConfirmation(QList<QUrl>() << url, KIO::JobUiDelegate::Delete, KIO::JobUiDelegate::DefaultConfirmation)) { 0267 KIO::DeleteJob *deleteJob = KIO::del(url); 0268 KJobWidgets::setWindow(deleteJob, m_parent); 0269 deleteJob->ui()->setAutoErrorHandlingEnabled(true); 0270 } 0271 } 0272 0273 void KDirSelectDialog::Private::slotProperties() 0274 { 0275 KPropertiesDialog *dialog = nullptr; 0276 dialog = new KPropertiesDialog(m_treeView->selectedUrl(), this->m_parent); 0277 dialog->setAttribute(Qt::WA_DeleteOnClose); 0278 dialog->show(); 0279 } 0280 0281 KDirSelectDialog::KDirSelectDialog(const QUrl &startDir, bool localOnly, 0282 QWidget *parent) 0283 #ifdef Q_OS_WIN 0284 : QDialog(parent, Qt::WindowMinMaxButtonsHint), 0285 #else 0286 : QDialog(parent), 0287 #endif 0288 d(new Private(localOnly, this)) 0289 { 0290 setWindowTitle(i18nc("@title:window", "Select Folder")); 0291 0292 QVBoxLayout *topLayout = new QVBoxLayout; 0293 setLayout(topLayout); 0294 0295 QFrame *page = new QFrame(this); 0296 topLayout->addWidget(page); 0297 0298 QPushButton *folderButton = new QPushButton; 0299 KGuiItem::assign(folderButton, KGuiItem(i18nc("@action:button", "New Folder..."), "folder-new")); 0300 connect(folderButton, SIGNAL(clicked()), this, SLOT(slotNewFolder())); 0301 0302 QDialogButtonBox *buttonBox = new QDialogButtonBox(this); 0303 buttonBox->addButton(folderButton, QDialogButtonBox::ActionRole); 0304 buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); 0305 connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); 0306 connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); 0307 topLayout->addWidget(buttonBox); 0308 0309 QHBoxLayout *hlay = new QHBoxLayout(page); 0310 hlay->setContentsMargins(0, 0, 0, 0); 0311 QVBoxLayout *mainLayout = new QVBoxLayout(); 0312 d->m_actions = new KActionCollection(this); 0313 d->m_actions->addAssociatedWidget(this); 0314 d->m_placesView = new KFilePlacesView(page); 0315 d->m_placesView->setModel(new KFilePlacesModel(d->m_placesView)); 0316 d->m_placesView->setObjectName(QLatin1String("speedbar")); 0317 d->m_placesView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 0318 d->m_placesView->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); 0319 connect(d->m_placesView, SIGNAL(urlChanged(QUrl)), 0320 SLOT(setCurrentUrl(QUrl))); 0321 hlay->addWidget(d->m_placesView); 0322 hlay->addLayout(mainLayout); 0323 0324 d->m_treeView = new KFileTreeView(page); 0325 d->m_treeView->setDirOnlyMode(true); 0326 d->m_treeView->setContextMenuPolicy(Qt::CustomContextMenu); 0327 0328 for (int i = 1; i < d->m_treeView->model()->columnCount(); ++i) { 0329 d->m_treeView->hideColumn(i); 0330 } 0331 0332 d->m_urlCombo = new KHistoryComboBox(page); 0333 d->m_urlCombo->setLayoutDirection(Qt::LeftToRight); 0334 d->m_urlCombo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); 0335 d->m_urlCombo->setTrapReturnKey(true); 0336 d->m_urlCombo->setPixmapProvider(new KUrlPixmapProvider()); 0337 KUrlCompletion *comp = new KUrlCompletion(); 0338 comp->setMode(KUrlCompletion::DirCompletion); 0339 d->m_urlCombo->setCompletionObject(comp, true); 0340 d->m_urlCombo->setAutoDeleteCompletionObject(true); 0341 d->m_urlCombo->setDuplicatesEnabled(false); 0342 0343 d->m_contextMenu = new QMenu(this); 0344 0345 QAction *newFolder = new QAction(i18nc("@action:inmenu", "New Folder..."), this); 0346 d->m_actions->addAction(newFolder->objectName(), newFolder); 0347 newFolder->setIcon(QIcon::fromTheme("folder-new")); 0348 newFolder->setShortcut(Qt::Key_F10); 0349 connect(newFolder, SIGNAL(triggered(bool)), this, SLOT(slotNewFolder())); 0350 d->m_contextMenu->addAction(newFolder); 0351 0352 d->moveToTrash = new QAction(i18nc("@action:inmenu", "Move to Trash"), this); 0353 d->m_actions->addAction(d->moveToTrash->objectName(), d->moveToTrash); 0354 d->moveToTrash->setIcon(QIcon::fromTheme("user-trash")); 0355 d->moveToTrash->setShortcut(Qt::Key_Delete); 0356 connect(d->moveToTrash, SIGNAL(triggered(bool)), this, SLOT(slotMoveToTrash())); 0357 d->m_contextMenu->addAction(d->moveToTrash); 0358 0359 d->deleteAction = new QAction(i18nc("@action:inmenu", "Delete"), this); 0360 d->m_actions->addAction(d->deleteAction->objectName(), d->deleteAction); 0361 d->deleteAction->setIcon(QIcon::fromTheme("edit-delete")); 0362 d->deleteAction->setShortcut(Qt::SHIFT | Qt::Key_Delete); 0363 connect(d->deleteAction, SIGNAL(triggered(bool)), this, SLOT(slotDelete())); 0364 d->m_contextMenu->addAction(d->deleteAction); 0365 0366 d->m_contextMenu->addSeparator(); 0367 0368 d->showHiddenFoldersAction = new KToggleAction(i18nc("@option:check", "Show Hidden Folders"), this); 0369 d->m_actions->addAction(d->showHiddenFoldersAction->objectName(), d->showHiddenFoldersAction); 0370 d->showHiddenFoldersAction->setShortcut(Qt::Key_F8); 0371 connect(d->showHiddenFoldersAction, SIGNAL(triggered(bool)), d->m_treeView, SLOT(setShowHiddenFiles(bool))); 0372 d->m_contextMenu->addAction(d->showHiddenFoldersAction); 0373 d->m_contextMenu->addSeparator(); 0374 0375 QAction *propertiesAction = new QAction(i18nc("@action:inmenu", "Properties"), this); 0376 d->m_actions->addAction(propertiesAction->objectName(), propertiesAction); 0377 propertiesAction->setIcon(QIcon::fromTheme("document-properties")); 0378 propertiesAction->setShortcut(Qt::ALT | Qt::Key_Return); 0379 connect(propertiesAction, SIGNAL(triggered(bool)), this, SLOT(slotProperties())); 0380 d->m_contextMenu->addAction(propertiesAction); 0381 0382 d->m_startURL = KFileWidget::getStartUrl(startDir, d->m_recentDirClass); 0383 if (localOnly && !d->m_startURL.isLocalFile()) { 0384 d->m_startURL = QUrl(); 0385 QString docPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); 0386 if (QDir(docPath).exists()) { 0387 d->m_startURL.setPath(docPath); 0388 } else { 0389 d->m_startURL.setPath(QDir::homePath()); 0390 } 0391 } 0392 0393 d->m_startDir = d->m_startURL; 0394 d->m_rootUrl = d->m_treeView->rootUrl(); 0395 0396 d->readConfig(KSharedConfig::openConfig(), "DirSelect Dialog"); 0397 0398 mainLayout->addWidget(d->m_treeView, 1); 0399 mainLayout->addWidget(d->m_urlCombo, 0); 0400 0401 connect(d->m_treeView, SIGNAL(currentChanged(QUrl)), 0402 SLOT(slotCurrentChanged())); 0403 connect(d->m_treeView, SIGNAL(activated(QModelIndex)), 0404 SLOT(slotExpand(QModelIndex))); 0405 connect(d->m_treeView, SIGNAL(customContextMenuRequested(QPoint)), 0406 SLOT(slotContextMenuRequested(QPoint))); 0407 0408 connect(d->m_urlCombo, SIGNAL(editTextChanged(QString)), 0409 SLOT(slotComboTextChanged(QString))); 0410 connect(d->m_urlCombo, SIGNAL(activated(QString)), 0411 SLOT(slotUrlActivated(QString))); 0412 connect(d->m_urlCombo, SIGNAL(returnPressed(QString)), 0413 SLOT(slotUrlActivated(QString))); 0414 0415 setCurrentUrl(d->m_startURL); 0416 } 0417 0418 KDirSelectDialog::~KDirSelectDialog() 0419 { 0420 delete d; 0421 } 0422 0423 QUrl KDirSelectDialog::url() const 0424 { 0425 QUrl comboUrl = QUrl::fromUserInput(d->m_urlCombo->currentText()); 0426 0427 if (comboUrl.isValid()) { 0428 KIO::StatJob *statJob = KIO::stat(comboUrl, KIO::HideProgressInfo); 0429 KJobWidgets::setWindow(statJob, d->m_parent); 0430 const bool ok = statJob->exec(); 0431 if (ok && statJob->statResult().isDir()) { 0432 return comboUrl; 0433 } 0434 } 0435 0436 // qDebug() << comboUrl.path() << " is not an accessible directory"; 0437 return d->m_treeView->currentUrl(); 0438 } 0439 0440 QAbstractItemView *KDirSelectDialog::view() const 0441 { 0442 return d->m_treeView; 0443 } 0444 0445 bool KDirSelectDialog::localOnly() const 0446 { 0447 return d->m_localOnly; 0448 } 0449 0450 QUrl KDirSelectDialog::startDir() const 0451 { 0452 return d->m_startDir; 0453 } 0454 0455 void KDirSelectDialog::setCurrentUrl(const QUrl &url) 0456 { 0457 if (!url.isValid()) { 0458 return; 0459 } 0460 0461 if (url.scheme() != d->m_rootUrl.scheme()) { 0462 QUrl u(url); 0463 u.setPath("/");//NOTE portability? 0464 d->m_treeView->setRootUrl(u); 0465 d->m_rootUrl = u; 0466 } 0467 0468 //Check if url represents a hidden folder and enable showing them 0469 QString fileName = url.fileName(); 0470 //TODO a better hidden file check? 0471 bool isHidden = fileName.length() > 1 && fileName[0] == '.' && 0472 (fileName.length() > 2 ? fileName[1] != '.' : true); 0473 bool showHiddenFiles = isHidden && !d->m_treeView->showHiddenFiles(); 0474 if (showHiddenFiles) { 0475 d->showHiddenFoldersAction->setChecked(true); 0476 d->m_treeView->setShowHiddenFiles(true); 0477 } 0478 0479 d->m_treeView->setCurrentUrl(url); 0480 } 0481 0482 void KDirSelectDialog::accept() 0483 { 0484 QUrl selectedUrl = url(); 0485 if (!selectedUrl.isValid()) { 0486 return; 0487 } 0488 0489 if (!d->m_recentDirClass.isEmpty()) { 0490 KRecentDirs::add(d->m_recentDirClass, selectedUrl.toString()); 0491 } 0492 0493 d->m_urlCombo->addToHistory(selectedUrl.toDisplayString()); 0494 KFileWidget::setStartDir(url()); 0495 0496 QDialog::accept(); 0497 } 0498 0499 void KDirSelectDialog::hideEvent(QHideEvent *event) 0500 { 0501 d->saveConfig(KSharedConfig::openConfig(), "DirSelect Dialog"); 0502 0503 QDialog::hideEvent(event); 0504 } 0505 0506 // static 0507 QUrl KDirSelectDialog::selectDirectory(const QUrl &startDir, 0508 bool localOnly, 0509 QWidget *parent, 0510 const QString &caption) 0511 { 0512 KDirSelectDialog myDialog(startDir, localOnly, parent); 0513 0514 if (!caption.isNull()) { 0515 myDialog.setWindowTitle(caption); 0516 } 0517 0518 if (myDialog.exec() == QDialog::Accepted) { 0519 QUrl url = myDialog.url(); 0520 0521 //Returning the most local url 0522 if (url.isLocalFile()) { 0523 return url; 0524 } 0525 0526 KIO::StatJob *job = KIO::stat(url); 0527 KJobWidgets::setWindow(job, parent); 0528 0529 if (!job->exec()) { 0530 return url; 0531 } 0532 0533 KIO::UDSEntry entry = job->statResult(); 0534 const QString path = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH); 0535 0536 return path.isEmpty() ? url : QUrl::fromLocalFile(path); 0537 } else { 0538 return QUrl(); 0539 } 0540 } 0541 0542 #include "moc_kdirselectdialog.cpp"