File indexing completed on 2024-05-05 16:08:24

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"