File indexing completed on 2024-05-12 05:46:43

0001 /* This file is part of the KDE project
0002    Copyright (C) 1998-2009 David Faure <faure@kde.org>
0003                  2003      Sven Leiber <s.leiber@web.de>
0004 
0005    This library is free software; you can redistribute it and/or
0006    modify it under the terms of the GNU Library General Public
0007    License as published by the Free Software Foundation; either
0008    version 2 or at your option version 3.
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 "knewfilemenu.h"
0022 #include "../pathhelpers_p.h"
0023 #include "knameandurlinputdialog.h"
0024 
0025 #include <QDialog>
0026 #include <QDialogButtonBox>
0027 #include <QDir>
0028 #include <QVBoxLayout>
0029 #include <QList>
0030 #include <QLabel>
0031 #include <QLineEdit>
0032 #include <QMenu>
0033 #include <QPushButton>
0034 #include <QStandardPaths>
0035 
0036 #include <qtemporaryfile.h>
0037 #include <kactioncollection.h>
0038 #include <kconfiggroup.h>
0039 #include <QDebug>
0040 #include <kdesktopfile.h>
0041 #include <kdirwatch.h>
0042 #include <kdirnotify.h>
0043 #include <kjobwidgets.h>
0044 #include <klocalizedstring.h>
0045 #include <kmessagebox.h>
0046 #include <kmessagewidget.h>
0047 #include <kprotocolinfo.h>
0048 #include <kprotocolmanager.h>
0049 #include <krun.h>
0050 #include <kshell.h>
0051 #include <kio/job.h>
0052 #include <kio/copyjob.h>
0053 #include <kio/jobuidelegate.h>
0054 #include <kio/fileundomanager.h>
0055 #include <kio/mkpathjob.h>
0056 #include <kurifilter.h>
0057 #include <kfileutils.h>
0058 
0059 #include <kpropertiesdialog.h>
0060 #include <qmimedatabase.h>
0061 #ifdef Q_OS_WIN
0062 #include <sys/utime.h>
0063 #else
0064 #include <utime.h>
0065 #endif
0066 
0067 static QString expandTilde(const QString &name, bool isfile = false)
0068 {
0069     if (!name.isEmpty() && (!isfile || name[0] == QLatin1Char('\\'))) {
0070         const QString expandedName = KShell::tildeExpand(name);
0071         // When a tilde mark cannot be properly expanded, the above call
0072         // returns an empty string...
0073         if (!expandedName.isEmpty()) {
0074             return expandedName;
0075         }
0076     }
0077 
0078     return name;
0079 }
0080 
0081 // Singleton, with data shared by all KNewFileMenu instances
0082 class KNewFileMenuSingleton
0083 {
0084 public:
0085     KNewFileMenuSingleton()
0086         : dirWatch(nullptr),
0087           filesParsed(false),
0088           templatesList(nullptr),
0089           templatesVersion(0)
0090     {
0091     }
0092 
0093     ~KNewFileMenuSingleton()
0094     {
0095         delete dirWatch;
0096         delete templatesList;
0097     }
0098 
0099     /**
0100      * Opens the desktop files and completes the Entry list
0101      * Input: the entry list. Output: the entry list ;-)
0102      */
0103     void parseFiles();
0104 
0105     /**
0106      * For entryType
0107      * LINKTOTEMPLATE: a desktop file that points to a file or dir to copy
0108      * TEMPLATE: a real file to copy as is (the KDE-1.x solution)
0109      * SEPARATOR: to put a separator in the menu
0110      * 0 means: not parsed, i.e. we don't know
0111      */
0112     enum EntryType { Unknown, LinkToTemplate = 1, Template, Separator };
0113 
0114     KDirWatch *dirWatch;
0115 
0116     struct Entry {
0117         QString text;
0118         QString filePath; // empty for Separator
0119         QString templatePath; // same as filePath for Template
0120         QString icon;
0121         EntryType entryType;
0122         QString comment;
0123         QString mimeType;
0124     };
0125     // NOTE: only filePath is known before we call parseFiles
0126 
0127     /**
0128      * List of all template files. It is important that they are in
0129      * the same order as the 'New' menu.
0130      */
0131     typedef QList<Entry> EntryList;
0132 
0133     /**
0134      * Set back to false each time new templates are found,
0135      * and to true on the first call to parseFiles
0136      */
0137     bool filesParsed;
0138     EntryList *templatesList;
0139 
0140     /**
0141      * Is increased when templatesList has been updated and
0142      * menu needs to be re-filled. Menus have their own version and compare it
0143      * to templatesVersion before showing up
0144      */
0145     int templatesVersion;
0146 };
0147 
0148 void KNewFileMenuSingleton::parseFiles()
0149 {
0150     //qDebug();
0151     filesParsed = true;
0152     QMutableListIterator<KNewFileMenuSingleton::Entry> templIter(*templatesList);
0153     while (templIter.hasNext()) {
0154         KNewFileMenuSingleton::Entry &templ = templIter.next();
0155         const QString filePath = templ.filePath;
0156         if (!filePath.isEmpty()) {
0157             QString text;
0158             QString templatePath;
0159             // If a desktop file, then read the name from it.
0160             // Otherwise (or if no name in it?) use file name
0161             if (KDesktopFile::isDesktopFile(filePath)) {
0162                 KDesktopFile desktopFile(filePath);
0163                 if (desktopFile.noDisplay()) {
0164                     templIter.remove();
0165                     continue;
0166                 }
0167                 text = desktopFile.readName();
0168                 templ.icon = desktopFile.readIcon();
0169                 templ.comment = desktopFile.readComment();
0170                 QString type = desktopFile.readType();
0171                 if (type == QLatin1String("Link")) {
0172                     templatePath = desktopFile.desktopGroup().readPathEntry("URL", QString());
0173                     if (templatePath[0] != QLatin1Char('/') && !templatePath.startsWith(QLatin1String("__"))) {
0174                         if (templatePath.startsWith(QLatin1String("file:/"))) {
0175                             templatePath = QUrl(templatePath).toLocalFile();
0176                         } else {
0177                             // A relative path, then (that's the default in the files we ship)
0178                             const QStringRef linkDir = filePath.leftRef(filePath.lastIndexOf(QLatin1Char('/')) + 1 /*keep / */);
0179                             //qDebug() << "linkDir=" << linkDir;
0180                             templatePath = linkDir + templatePath;
0181                         }
0182                     }
0183                 }
0184                 if (templatePath.isEmpty()) {
0185                     // No URL key, this is an old-style template
0186                     templ.entryType = KNewFileMenuSingleton::Template;
0187                     templ.templatePath = templ.filePath; // we'll copy the file
0188                 } else {
0189                     templ.entryType = KNewFileMenuSingleton::LinkToTemplate;
0190                     templ.templatePath = templatePath;
0191                 }
0192 
0193             }
0194             if (text.isEmpty()) {
0195                 text = QUrl(filePath).fileName();
0196                 if (text.endsWith(QLatin1String(".desktop"))) {
0197                     text.chop(8);
0198                 }
0199             }
0200             templ.text = text;
0201             /*// qDebug() << "Updating entry with text=" << text
0202                           << "entryType=" << templ.entryType
0203                           << "templatePath=" << templ.templatePath;*/
0204         } else {
0205             templ.entryType = KNewFileMenuSingleton::Separator;
0206         }
0207     }
0208 }
0209 
0210 Q_GLOBAL_STATIC(KNewFileMenuSingleton, kNewMenuGlobals)
0211 
0212 class KNewFileMenuCopyData
0213 {
0214 public:
0215     KNewFileMenuCopyData()
0216     {
0217         m_isSymlink = false;
0218     }
0219     QString chosenFileName() const
0220     {
0221         return m_chosenFileName;
0222     }
0223 
0224     // If empty, no copy is performed.
0225     QString sourceFileToCopy() const
0226     {
0227         return m_src;
0228     }
0229     QString tempFileToDelete() const
0230     {
0231         return m_tempFileToDelete;
0232     }
0233     bool m_isSymlink;
0234 
0235     QString m_chosenFileName;
0236     QString m_src;
0237     QString m_tempFileToDelete;
0238     QString m_templatePath;
0239 };
0240 
0241 class KNewFileMenuPrivate
0242 {
0243 public:
0244     explicit KNewFileMenuPrivate(KActionCollection *collection, KNewFileMenu *qq)
0245         : m_actionCollection(collection),
0246           q(qq)
0247     {}
0248 
0249     bool checkSourceExists(const QString &src);
0250 
0251     /**
0252       * The strategy used for other desktop files than Type=Link. Example: Application, Device.
0253       */
0254     void executeOtherDesktopFile(const KNewFileMenuSingleton::Entry &entry);
0255 
0256     /**
0257       * The strategy used for "real files or directories" (the common case)
0258       */
0259     void executeRealFileOrDir(const KNewFileMenuSingleton::Entry &entry);
0260 
0261     /**
0262       * Actually performs file handling. Reads in m_copyData for needed data, that has been collected by execute*() before
0263       */
0264     void executeStrategy();
0265 
0266     /**
0267       * The strategy used when creating a symlink
0268       */
0269     void executeSymLink(const KNewFileMenuSingleton::Entry &entry);
0270 
0271     /**
0272       * The strategy used for "url" desktop files
0273       */
0274     void executeUrlDesktopFile(const KNewFileMenuSingleton::Entry &entry);
0275 
0276     /**
0277      * Fills the menu from the templates list.
0278      */
0279     void fillMenu();
0280 
0281     /**
0282      * Tries to map a local URL for the given URL.
0283      */
0284     QUrl mostLocalUrl(const QUrl &url);
0285 
0286     /**
0287       * Just clears the string buffer d->m_text, but I need a slot for this to occur
0288       */
0289     void _k_slotAbortDialog();
0290 
0291     /**
0292      * Called when New->* is clicked
0293      */
0294     void _k_slotActionTriggered(QAction *action);
0295 
0296     /**
0297      * Callback function that reads in directory name from dialog and processes it
0298      */
0299     void _k_slotCreateDirectory(bool writeHiddenDir = false);
0300 
0301     /**
0302      * Callback function that reads in directory name from dialog and processes it. This will wirte
0303      * a hidden directory without further questions
0304      */
0305     void _k_slotCreateHiddenDirectory();
0306 
0307     /**
0308      * Fills the templates list.
0309      */
0310     void _k_slotFillTemplates();
0311 
0312     /**
0313       * Called when accepting the KPropertiesDialog (for "other desktop files")
0314       */
0315     void _k_slotOtherDesktopFile();
0316 
0317     /**
0318      * Called when closing the KPropertiesDialog is closed (whichever way, accepted and rejected)
0319      */
0320     void _k_slotOtherDesktopFileClosed();
0321 
0322     /**
0323       * Callback in KNewFileMenu for the RealFile Dialog. Handles dialog input and gives over
0324       * to executeStrategy()
0325       */
0326     void _k_slotRealFileOrDir();
0327 
0328     /**
0329       * Dialogs use this slot to write the changed string into KNewFile menu when the user
0330       * changes touches them
0331       */
0332     void _k_slotTextChanged(const QString &text);
0333 
0334     /**
0335       * Callback in KNewFileMenu for the Symlink Dialog. Handles dialog input and gives over
0336       * to executeStrategy()
0337       */
0338     void _k_slotSymLink();
0339 
0340     /**
0341       * Callback in KNewFileMenu for the Url/Desktop Dialog. Handles dialog input and gives over
0342       * to executeStrategy()
0343       */
0344     void _k_slotUrlDesktopFile();
0345 
0346     KActionCollection *m_actionCollection;
0347     QDialog *m_fileDialog;
0348 
0349     KActionMenu *m_menuDev;
0350     int m_menuItemsVersion = 0;
0351     QAction *m_newDirAction;
0352     KMessageWidget* m_messageWidget = nullptr;
0353     QDialogButtonBox* m_buttonBox = nullptr;
0354 
0355     // This is used to allow _k_slotTextChanged to know whether it's being used to
0356     // create a file or a directory without duplicating code across two functions
0357     bool m_creatingDirectory = false;
0358     bool m_viewShowsHiddenFiles = false;
0359     bool m_modal = true;
0360 
0361     /**
0362      * The action group that our actions belong to
0363      */
0364     QActionGroup *m_newMenuGroup;
0365     QWidget *m_parentWidget;
0366 
0367     /**
0368      * When the user pressed the right mouse button over an URL a popup menu
0369      * is displayed. The URL belonging to this popup menu is stored here.
0370      */
0371     QList<QUrl> m_popupFiles;
0372 
0373     QStringList m_supportedMimeTypes;
0374     QString m_tempFileToDelete; // set when a tempfile was created for a Type=URL desktop file
0375     QString m_text;
0376 
0377     KNewFileMenuSingleton::Entry *m_firstFileEntry = nullptr;
0378 
0379     KNewFileMenu * const q;
0380 
0381     KNewFileMenuCopyData m_copyData;
0382 };
0383 
0384 bool KNewFileMenuPrivate::checkSourceExists(const QString &src)
0385 {
0386     if (!QFile::exists(src)) {
0387         qWarning() << src << "doesn't exist";
0388 
0389         QDialog *dialog = new QDialog(m_parentWidget);
0390         dialog->setWindowTitle(i18n("Sorry"));
0391         dialog->setObjectName(QStringLiteral("sorry"));
0392         dialog->setModal(q->isModal());
0393         dialog->setAttribute(Qt::WA_DeleteOnClose);
0394 
0395         m_buttonBox = new QDialogButtonBox(dialog);
0396         m_buttonBox->setStandardButtons(QDialogButtonBox::Ok);
0397 
0398         KMessageBox::createKMessageBox(dialog, m_buttonBox, QMessageBox::Warning,
0399                                        i18n("<qt>The template file <b>%1</b> does not exist.</qt>", src),
0400                                        QStringList(), QString(), nullptr, KMessageBox::NoExec);
0401 
0402         dialog->show();
0403 
0404         return false;
0405     }
0406     return true;
0407 }
0408 
0409 void KNewFileMenuPrivate::executeOtherDesktopFile(const KNewFileMenuSingleton::Entry &entry)
0410 {
0411     if (!checkSourceExists(entry.templatePath)) {
0412         return;
0413     }
0414 
0415     QList<QUrl>::const_iterator it = m_popupFiles.constBegin();
0416     for (; it != m_popupFiles.constEnd(); ++it) {
0417         QString text = entry.text;
0418         text.remove(QStringLiteral("...")); // the ... is fine for the menu item but not for the default filename
0419         text = text.trimmed(); // In some languages, there is a space in front of "...", see bug 268895
0420         // KDE5 TODO: remove the "..." from link*.desktop files and use i18n("%1...") when making
0421         // the action.
0422         QString name = text;
0423         text.append(QStringLiteral(".desktop"));
0424 
0425         const QUrl directory = mostLocalUrl(*it);
0426         const QUrl defaultFile = QUrl::fromLocalFile(directory.toLocalFile() + QLatin1Char('/') + KIO::encodeFileName(text));
0427         if (defaultFile.isLocalFile() && QFile::exists(defaultFile.toLocalFile())) {
0428             text = KFileUtils::suggestName(directory, text);
0429         }
0430 
0431         QUrl templateUrl;
0432         bool usingTemplate = false;
0433         if (entry.templatePath.startsWith(QLatin1String(":/"))) {
0434             QTemporaryFile *tmpFile = QTemporaryFile::createNativeFile(entry.templatePath);
0435             tmpFile->setAutoRemove(false);
0436             QString tempFileName = tmpFile->fileName();
0437             tmpFile->close();
0438 
0439             KDesktopFile df(tempFileName);
0440             KConfigGroup group = df.desktopGroup();
0441             group.writeEntry("Name", name);
0442             templateUrl = QUrl::fromLocalFile(tempFileName);
0443             m_tempFileToDelete = tempFileName;
0444             usingTemplate = true;
0445         } else {
0446             templateUrl = QUrl::fromLocalFile(entry.templatePath);
0447         }
0448         QDialog *dlg = new KPropertiesDialog(templateUrl, directory, text, m_parentWidget);
0449         dlg->setModal(q->isModal());
0450         dlg->setAttribute(Qt::WA_DeleteOnClose);
0451         QObject::connect(dlg, SIGNAL(applied()), q, SLOT(_k_slotOtherDesktopFile()));
0452         if (usingTemplate) {
0453             QObject::connect(dlg, SIGNAL(propertiesClosed()), q, SLOT(_k_slotOtherDesktopFileClosed()));
0454         }
0455         dlg->show();
0456     }
0457     // We don't set m_src here -> there will be no copy, we are done.
0458 }
0459 
0460 void KNewFileMenuPrivate::executeRealFileOrDir(const KNewFileMenuSingleton::Entry &entry)
0461 {
0462     // The template is not a desktop file
0463     // Show the small dialog for getting the destination filename
0464     QString text = entry.text;
0465     text.remove(QStringLiteral("...")); // the ... is fine for the menu item but not for the default filename
0466     text = text.trimmed(); // In some languages, there is a space in front of "...", see bug 268895
0467     m_copyData.m_src = entry.templatePath;
0468 
0469     const QUrl directory = mostLocalUrl(m_popupFiles.first());
0470     const QUrl defaultFile = QUrl::fromLocalFile(directory.toLocalFile() + QLatin1Char('/') + KIO::encodeFileName(text));
0471     if (defaultFile.isLocalFile() && QFile::exists(defaultFile.toLocalFile())) {
0472         text = KFileUtils::suggestName(directory, text);
0473     }
0474 
0475     QDialog *fileDialog = new QDialog(m_parentWidget);
0476     fileDialog->setAttribute(Qt::WA_DeleteOnClose);
0477     fileDialog->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
0478     fileDialog->setModal(q->isModal());
0479     m_fileDialog = fileDialog;
0480 
0481     QVBoxLayout *layout = new QVBoxLayout;
0482     layout->setSizeConstraint(QLayout::SetFixedSize);
0483 
0484     m_messageWidget = new KMessageWidget(fileDialog);
0485     m_messageWidget->setCloseButtonVisible(false);
0486     m_messageWidget->setWordWrap(true);
0487     m_messageWidget->hide();
0488     QLabel *label = new QLabel(entry.comment, fileDialog);
0489 
0490     QLineEdit *lineEdit = new QLineEdit(fileDialog);
0491     lineEdit->setClearButtonEnabled(true);
0492     lineEdit->setText(text);
0493     lineEdit->setMinimumWidth(400);
0494 
0495     m_buttonBox = new QDialogButtonBox(fileDialog);
0496     m_buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
0497     QObject::connect(m_buttonBox, &QDialogButtonBox::accepted, fileDialog, &QDialog::accept);
0498     QObject::connect(m_buttonBox, &QDialogButtonBox::rejected, fileDialog, &QDialog::reject);
0499 
0500     m_creatingDirectory = false;
0501     _k_slotTextChanged(text);
0502     QObject::connect(lineEdit, SIGNAL(textChanged(QString)), q, SLOT(_k_slotTextChanged(QString)));
0503 
0504     layout->addWidget(label);
0505     layout->addWidget(lineEdit);
0506     layout->addWidget(m_buttonBox);
0507     layout->addWidget(m_messageWidget);
0508     layout->addStretch();
0509 
0510     fileDialog->setLayout(layout);
0511     QObject::connect(fileDialog, SIGNAL(accepted()), q, SLOT(_k_slotRealFileOrDir()));
0512     QObject::connect(fileDialog, SIGNAL(rejected()), q, SLOT(_k_slotAbortDialog()));
0513 
0514     fileDialog->show();
0515     lineEdit->selectAll();
0516     lineEdit->setFocus();
0517 }
0518 
0519 void KNewFileMenuPrivate::executeSymLink(const KNewFileMenuSingleton::Entry &entry)
0520 {
0521     KNameAndUrlInputDialog *dlg = new KNameAndUrlInputDialog(i18n("File name:"), entry.comment, m_popupFiles.first(), m_parentWidget);
0522     dlg->setModal(q->isModal());
0523     dlg->setAttribute(Qt::WA_DeleteOnClose);
0524     dlg->setWindowTitle(i18n("Create Symlink"));
0525     m_fileDialog = dlg;
0526     QObject::connect(dlg, SIGNAL(accepted()), q, SLOT(_k_slotSymLink()));
0527     dlg->show();
0528 }
0529 
0530 void KNewFileMenuPrivate::executeStrategy()
0531 {
0532     m_tempFileToDelete = m_copyData.tempFileToDelete();
0533     const QString src = m_copyData.sourceFileToCopy();
0534     QString chosenFileName = expandTilde(m_copyData.chosenFileName(), true);
0535 
0536     if (src.isEmpty()) {
0537         return;
0538     }
0539     QUrl uSrc(QUrl::fromLocalFile(src));
0540 
0541     // In case the templates/.source directory contains symlinks, resolve
0542     // them to the target files. Fixes bug #149628.
0543     KFileItem item(uSrc, QString(), KFileItem::Unknown);
0544     if (item.isLink()) {
0545         uSrc.setPath(item.linkDest());
0546     }
0547 
0548     if (!m_copyData.m_isSymlink) {
0549         // If the file is not going to be detected as a desktop file, due to a
0550         // known extension (e.g. ".pl"), append ".desktop". #224142.
0551         QFile srcFile(uSrc.toLocalFile());
0552         if (srcFile.open(QIODevice::ReadOnly)) {
0553             QMimeDatabase db;
0554             QMimeType wantedMime = db.mimeTypeForUrl(uSrc);
0555             QMimeType mime = db.mimeTypeForFileNameAndData(m_copyData.m_chosenFileName, srcFile.read(1024));
0556             //qDebug() << "mime=" << mime->name() << "wantedMime=" << wantedMime->name();
0557             if (!mime.inherits(wantedMime.name()))
0558                 if (!wantedMime.preferredSuffix().isEmpty()) {
0559                     chosenFileName += QLatin1Char('.') + wantedMime.preferredSuffix();
0560                 }
0561         }
0562     }
0563 
0564     // The template is not a desktop file [or it's a URL one]
0565     // Copy it.
0566     QList<QUrl>::const_iterator it = m_popupFiles.constBegin();
0567     for (; it != m_popupFiles.constEnd(); ++it) {
0568         QUrl dest = *it;
0569         dest.setPath(concatPaths(dest.path(), KIO::encodeFileName(chosenFileName)));
0570 
0571         QList<QUrl> lstSrc;
0572         lstSrc.append(uSrc);
0573         KIO::Job *kjob;
0574         if (m_copyData.m_isSymlink) {
0575             KIO::CopyJob *linkJob = KIO::linkAs(uSrc, dest);
0576             kjob = linkJob;
0577             KIO::FileUndoManager::self()->recordCopyJob(linkJob);
0578         } else if (src.startsWith(QLatin1String(":/"))) {
0579             QFile srcFile(src);
0580             if (!srcFile.open(QIODevice::ReadOnly)) {
0581                 return;
0582             }
0583             // The QFile won't live long enough for the job, so let's buffer the contents
0584             const QByteArray srcBuf(srcFile.readAll());
0585             KIO::StoredTransferJob* putJob = KIO::storedPut(srcBuf, dest, -1);
0586             kjob = putJob;
0587             KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Put, QList<QUrl>(), dest, putJob);
0588         } else {
0589             //qDebug() << "KIO::copyAs(" << uSrc.url() << "," << dest.url() << ")";
0590             KIO::CopyJob *job = KIO::copyAs(uSrc, dest);
0591             job->setDefaultPermissions(true);
0592             kjob = job;
0593             KIO::FileUndoManager::self()->recordCopyJob(job);
0594         }
0595         KJobWidgets::setWindow(kjob, m_parentWidget);
0596         QObject::connect(kjob, &KJob::result, q, &KNewFileMenu::slotResult);
0597     }
0598 }
0599 
0600 void KNewFileMenuPrivate::executeUrlDesktopFile(const KNewFileMenuSingleton::Entry &entry)
0601 {
0602     KNameAndUrlInputDialog *dlg = new KNameAndUrlInputDialog(i18n("File name:"), entry.comment, m_popupFiles.first(), m_parentWidget);
0603     m_copyData.m_templatePath = entry.templatePath;
0604     dlg->setModal(q->isModal());
0605     dlg->setAttribute(Qt::WA_DeleteOnClose);
0606     dlg->setWindowTitle(i18n("Create link to URL"));
0607     m_fileDialog = dlg;
0608     QObject::connect(dlg, SIGNAL(accepted()), q, SLOT(_k_slotUrlDesktopFile()));
0609     dlg->show();
0610 }
0611 
0612 void KNewFileMenuPrivate::fillMenu()
0613 {
0614     QMenu *menu = q->menu();
0615     menu->clear();
0616     m_menuDev->menu()->clear();
0617     m_newDirAction = nullptr;
0618 
0619     QSet<QString> seenTexts;
0620     QString lastTemplatePath;
0621     // these shall be put at special positions
0622     QAction *linkURL = nullptr;
0623     QAction *linkApp = nullptr;
0624     QAction *linkPath = nullptr;
0625 
0626     KNewFileMenuSingleton *s = kNewMenuGlobals();
0627     int i = 1;
0628     KNewFileMenuSingleton::EntryList::iterator templ = s->templatesList->begin();
0629     const KNewFileMenuSingleton::EntryList::iterator templ_end = s->templatesList->end();
0630     for (; templ != templ_end; ++templ, ++i) {
0631         KNewFileMenuSingleton::Entry &entry = *templ;
0632         if (entry.entryType != KNewFileMenuSingleton::Separator) {
0633             // There might be a .desktop for that one already, if it's a kdelnk
0634             // This assumes we read .desktop files before .kdelnk files ...
0635 
0636             // In fact, we skip any second item that has the same text as another one.
0637             // Duplicates in a menu look bad in any case.
0638 
0639             const bool bSkip = seenTexts.contains(entry.text);
0640             if (bSkip) {
0641                 // qDebug() << "skipping" << entry.filePath;
0642             } else {
0643                 seenTexts.insert(entry.text);
0644                 //const KNewFileMenuSingleton::Entry entry = templatesList->at(i-1);
0645 
0646                 const QString templatePath = entry.templatePath;
0647                 // The best way to identify the "Create Directory", "Link to Location", "Link to Application" was the template
0648                 if (templatePath.endsWith(QLatin1String("emptydir"))) {
0649                     QAction *act = new QAction(q);
0650                     m_newDirAction = act;
0651                     act->setIcon(QIcon::fromTheme(entry.icon));
0652                     act->setText(i18nc("@item:inmenu Create New", "%1", entry.text));
0653                     act->setActionGroup(m_newMenuGroup);
0654 
0655                     // If there is a shortcut available in the action collection, use it.
0656                     QAction *act2 = m_actionCollection->action(QStringLiteral("create_dir"));
0657                     if (act2) {
0658                         act->setShortcuts(act2->shortcuts());
0659                         // Both actions have now the same shortcut, so this will prevent the "Ambiguous shortcut detected" dialog.
0660                         act->setShortcutContext(Qt::WidgetShortcut);
0661                         // We also need to react to shortcut changes.
0662                         QObject::connect(act2, &QAction::changed, act, [=]() {
0663                             act->setShortcuts(act2->shortcuts());
0664                         });
0665                     }
0666 
0667                     menu->addAction(act);
0668                     menu->addSeparator();
0669                 } else {
0670                     if (lastTemplatePath.startsWith(QDir::homePath()) && !templatePath.startsWith(QDir::homePath())) {
0671                         menu->addSeparator();
0672                     }
0673                     if (!m_supportedMimeTypes.isEmpty()) {
0674                         bool keep = false;
0675 
0676                         // We need to do mimetype filtering, for real files.
0677                         const bool createSymlink = entry.templatePath == QLatin1String("__CREATE_SYMLINK__");
0678                         if (createSymlink) {
0679                             keep = true;
0680                         } else if (!KDesktopFile::isDesktopFile(entry.templatePath)) {
0681 
0682                             // Determine mimetype on demand
0683                             QMimeDatabase db;
0684                             QMimeType mime;
0685                             if (entry.mimeType.isEmpty()) {
0686                                 mime = db.mimeTypeForFile(entry.templatePath);
0687                                 //qDebug() << entry.templatePath << "is" << mime.name();
0688                                 entry.mimeType = mime.name();
0689                             } else {
0690                                 mime = db.mimeTypeForName(entry.mimeType);
0691                             }
0692                             for (const QString &supportedMime : qAsConst(m_supportedMimeTypes)) {
0693                                 if (mime.inherits(supportedMime)) {
0694                                     keep = true;
0695                                     break;
0696                                 }
0697                             }
0698                         }
0699 
0700                         if (!keep) {
0701                             //qDebug() << "Not keeping" << entry.templatePath;
0702                             continue;
0703                         }
0704                     }
0705 
0706                     QAction *act = new QAction(q);
0707                     act->setData(i);
0708                     act->setIcon(QIcon::fromTheme(entry.icon));
0709                     act->setText(i18nc("@item:inmenu Create New", "%1", entry.text));
0710                     act->setActionGroup(m_newMenuGroup);
0711 
0712                     //qDebug() << templatePath << entry.filePath;
0713 
0714                     if (templatePath.endsWith(QLatin1String("/URL.desktop"))) {
0715                         linkURL = act;
0716                     } else if (templatePath.endsWith(QLatin1String("/Program.desktop"))) {
0717                         linkApp = act;
0718                     } else if (entry.filePath.endsWith(QLatin1String("/linkPath.desktop"))) {
0719                         linkPath = act;
0720                     } else if (KDesktopFile::isDesktopFile(templatePath)) {
0721                         KDesktopFile df(templatePath);
0722                         if (df.readType() == QLatin1String("FSDevice")) {
0723                             m_menuDev->menu()->addAction(act);
0724                         } else {
0725                             menu->addAction(act);
0726                         }
0727                     } else {
0728                         if (!m_firstFileEntry) {
0729                             m_firstFileEntry = &entry;
0730                             // If there is a shortcut available in the action collection, use it.
0731                             QAction *act2 = m_actionCollection->action(QStringLiteral("create_file"));
0732                             if (act2) {
0733                                 act->setShortcuts(act2->shortcuts());
0734                                 // Both actions have now the same shortcut, so this will prevent the "Ambiguous shortcut detected" dialog.
0735                                 act->setShortcutContext(Qt::WidgetShortcut);
0736                                 // We also need to react to shortcut changes.
0737                                 QObject::connect(act2, &QAction::changed, act, [=]() {
0738                                     act->setShortcuts(act2->shortcuts());
0739                                 });
0740                             }
0741                         }
0742                         menu->addAction(act);
0743                     }
0744                 }
0745             }
0746             lastTemplatePath = entry.templatePath;
0747         } else { // Separate system from personal templates
0748             Q_ASSERT(entry.entryType != 0);
0749             menu->addSeparator();
0750         }
0751     }
0752 
0753     if (m_supportedMimeTypes.isEmpty()) {
0754         menu->addSeparator();
0755         if (linkURL) {
0756             menu->addAction(linkURL);
0757         }
0758         if (linkPath) {
0759             menu->addAction(linkPath);
0760         }
0761         if (linkApp) {
0762             menu->addAction(linkApp);
0763         }
0764         Q_ASSERT(m_menuDev);
0765         if (!m_menuDev->menu()->isEmpty()) {
0766             menu->addAction(m_menuDev);
0767         }
0768     }
0769 }
0770 
0771 QUrl KNewFileMenuPrivate::mostLocalUrl(const QUrl &url)
0772 {
0773     if (url.isLocalFile()) {
0774         return url;
0775     }
0776 
0777     KIO::StatJob *job = KIO::stat(url);
0778     KJobWidgets::setWindow(job, m_parentWidget);
0779 
0780     if (!job->exec()) {
0781         return url;
0782     }
0783 
0784     KIO::UDSEntry entry = job->statResult();
0785     const QString path = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH);
0786 
0787     return path.isEmpty() ? url : QUrl::fromLocalFile(path);
0788 }
0789 
0790 void KNewFileMenuPrivate::_k_slotAbortDialog()
0791 {
0792     m_text = QString();
0793 }
0794 
0795 void KNewFileMenuPrivate::_k_slotActionTriggered(QAction *action)
0796 {
0797     q->trigger(); // was for kdesktop's slotNewMenuActivated() in kde3 times. Can't hurt to keep it...
0798 
0799     if (action == m_newDirAction) {
0800         q->createDirectory();
0801         return;
0802     }
0803     const int id = action->data().toInt();
0804     Q_ASSERT(id > 0);
0805 
0806     KNewFileMenuSingleton *s = kNewMenuGlobals();
0807     const KNewFileMenuSingleton::Entry entry = s->templatesList->at(id - 1);
0808 
0809     const bool createSymlink = entry.templatePath == QLatin1String("__CREATE_SYMLINK__");
0810 
0811     m_copyData = KNewFileMenuCopyData();
0812 
0813     if (createSymlink) {
0814         m_copyData.m_isSymlink = true;
0815         executeSymLink(entry);
0816     } else if (KDesktopFile::isDesktopFile(entry.templatePath)) {
0817         KDesktopFile df(entry.templatePath);
0818         if (df.readType() == QLatin1String("Link")) {
0819             executeUrlDesktopFile(entry);
0820         } else { // any other desktop file (Device, App, etc.)
0821             executeOtherDesktopFile(entry);
0822         }
0823     } else {
0824         executeRealFileOrDir(entry);
0825     }
0826 
0827 }
0828 
0829 void KNewFileMenuPrivate::_k_slotCreateDirectory(bool writeHiddenDir)
0830 {
0831     QUrl url;
0832     QUrl baseUrl = m_popupFiles.first();
0833 
0834     QString name = expandTilde(m_text);
0835 
0836     if (!name.isEmpty()) {
0837         if (QDir::isAbsolutePath(name)) {
0838             url = QUrl::fromLocalFile(name);
0839         } else {
0840             if (name == QLatin1Char('.') || name == QLatin1String("..")) {
0841                 KGuiItem enterNewNameGuiItem(KStandardGuiItem::ok());
0842                 enterNewNameGuiItem.setText(i18nc("@action:button", "Enter a Different Name"));
0843                 enterNewNameGuiItem.setIcon(QIcon::fromTheme(QStringLiteral("edit-rename")));
0844 
0845                 QDialog *confirmDialog = new QDialog(m_parentWidget);
0846                 confirmDialog->setWindowTitle(i18n("Invalid Directory Name"));
0847                 confirmDialog->setModal(m_modal);
0848                 confirmDialog->setAttribute(Qt::WA_DeleteOnClose);
0849 
0850                 m_buttonBox = new QDialogButtonBox(confirmDialog);
0851                 m_buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
0852                 KGuiItem::assign(m_buttonBox->button(QDialogButtonBox::Ok), enterNewNameGuiItem);
0853 
0854                 KMessageBox::createKMessageBox(confirmDialog, m_buttonBox, QMessageBox::Critical,
0855                                    xi18nc("@info", "Could not create a folder with the name <filename>%1</filename><nl/>because it is reserved for use by the operating system.", name),
0856                                    QStringList(),
0857                                    QString(),
0858                                    nullptr,
0859                                    KMessageBox::NoExec,
0860                                    QString());
0861 
0862                 m_creatingDirectory = true;
0863                 QObject::connect(m_buttonBox, &QDialogButtonBox::accepted, q, &KNewFileMenu::createDirectory);
0864                 m_fileDialog = confirmDialog;
0865                 confirmDialog->show();
0866                 _k_slotAbortDialog();
0867                 return;
0868             }
0869             url = baseUrl;
0870             url.setPath(concatPaths(url.path(), name));
0871         }
0872     }
0873 
0874     KIO::Job *job;
0875     if (name.contains(QLatin1Char('/'))) {
0876         // If the name contains any slashes, use mkpath so that a/b/c works.
0877         job = KIO::mkpath(url, baseUrl);
0878         KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Mkpath, QList<QUrl>(), url, job);
0879     } else {
0880         // If not, use mkdir so it will fail if the name of an existing folder was used
0881         job = KIO::mkdir(url);
0882         KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Mkdir, QList<QUrl>(), url, job);
0883     }
0884     job->setProperty("newDirectoryURL", url);
0885     job->uiDelegate()->setAutoErrorHandlingEnabled(true);
0886     KJobWidgets::setWindow(job, m_parentWidget);
0887 
0888     if (job) {
0889         // We want the error handling to be done by slotResult so that subclasses can reimplement it
0890         job->uiDelegate()->setAutoErrorHandlingEnabled(false);
0891         QObject::connect(job, &KJob::result, q, &KNewFileMenu::slotResult);
0892     }
0893     _k_slotAbortDialog();
0894 }
0895 
0896 void KNewFileMenuPrivate::_k_slotCreateHiddenDirectory()
0897 {
0898     _k_slotCreateDirectory(true);
0899 }
0900 
0901 struct EntryWithName {
0902     QString key;
0903     KNewFileMenuSingleton::Entry entry;
0904 };
0905 
0906 void KNewFileMenuPrivate::_k_slotFillTemplates()
0907 {
0908     KNewFileMenuSingleton *s = kNewMenuGlobals();
0909     //qDebug();
0910 
0911     const QStringList qrcTemplates = { QStringLiteral(":/kio5/newfile-templates") };
0912     QStringList installedTemplates = { QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("templates"), QStandardPaths::LocateDirectory) };
0913     // Qt does not provide an easy way to receive the xdg dir for templates so we have to find it on our own
0914     #ifdef Q_OS_UNIX
0915         QString xdgUserDirs = QStandardPaths::locate(QStandardPaths::ConfigLocation, QStringLiteral("user-dirs.dirs"), QStandardPaths::LocateFile);
0916         QFile xdgUserDirsFile(xdgUserDirs);
0917         if (!xdgUserDirs.isEmpty() && xdgUserDirsFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
0918             QTextStream in(&xdgUserDirsFile);
0919             while (!in.atEnd()) {
0920                 QString line = in.readLine();
0921                 if (line.startsWith(QLatin1String("XDG_TEMPLATES_DIR="))) {
0922                     QString xdgTemplates = line.mid(19, line.size()-20);
0923                     xdgTemplates.replace(QStringLiteral("$HOME"), QDir::homePath());
0924                     QDir xdgTemplatesDir(xdgTemplates);
0925                     if (xdgTemplatesDir.exists()) {
0926                         installedTemplates << xdgTemplates;
0927                     }
0928                     break;
0929                 }
0930             }
0931         }
0932     #endif
0933 
0934     const QStringList templates = qrcTemplates + installedTemplates;
0935 
0936     // Ensure any changes in the templates dir will call this
0937     if (! s->dirWatch) {
0938         s->dirWatch = new KDirWatch;
0939         for (const QString &dir : qAsConst(installedTemplates)) {
0940             s->dirWatch->addDir(dir);
0941         }
0942         QObject::connect(s->dirWatch, SIGNAL(dirty(QString)),
0943                          q, SLOT(_k_slotFillTemplates()));
0944         QObject::connect(s->dirWatch, SIGNAL(created(QString)),
0945                          q, SLOT(_k_slotFillTemplates()));
0946         QObject::connect(s->dirWatch, SIGNAL(deleted(QString)),
0947                          q, SLOT(_k_slotFillTemplates()));
0948         // Ok, this doesn't cope with new dirs in XDG_DATA_DIRS, but that's another story
0949     }
0950     ++s->templatesVersion;
0951     s->filesParsed = false;
0952 
0953     s->templatesList->clear();
0954 
0955     // Look into "templates" dirs.
0956     QStringList files;
0957     QDir dir;
0958 
0959     for (const QString &path : templates) {
0960         dir.setPath(path);
0961         const QStringList &entryList(dir.entryList(QStringList() << QStringLiteral("*.desktop"), QDir::Files));
0962         files.reserve(files.size() + entryList.size());
0963         for (const QString &entry : entryList) {
0964             const QString file = concatPaths(dir.path(), entry);
0965             files.append(file);
0966         }
0967     }
0968 
0969     QMap<QString, KNewFileMenuSingleton::Entry> slist; // used for sorting
0970     QMap<QString, EntryWithName> ulist; // entries with unique URLs
0971     for (const QString &file : qAsConst(files)) {
0972         //qDebug() << file;
0973         if (file[0] != QLatin1Char('.')) {
0974             KNewFileMenuSingleton::Entry e;
0975             e.filePath = file;
0976             e.entryType = KNewFileMenuSingleton::Unknown; // not parsed yet
0977 
0978             // Put Directory first in the list (a bit hacky),
0979             // and TextFile before others because it's the most used one.
0980             // This also sorts by user-visible name.
0981             // The rest of the re-ordering is done in fillMenu.
0982             const KDesktopFile config(file);
0983             QString url = config.desktopGroup().readEntry("URL");
0984             QString key = config.desktopGroup().readEntry("Name");
0985             if (file.endsWith(QLatin1String("Directory.desktop"))) {
0986                 key.prepend(QLatin1Char('0'));
0987             } else if (file.startsWith(QDir::homePath())) {
0988                 key.prepend(QLatin1Char('1'));
0989             } else if (file.endsWith(QLatin1String("TextFile.desktop"))) {
0990                 key.prepend(QLatin1Char('2'));
0991             } else {
0992                 key.prepend(QLatin1Char('3'));
0993             }
0994             EntryWithName en = { key, e };
0995             if (ulist.contains(url)) {
0996                 ulist.remove(url);
0997             }
0998             ulist.insert(url, en);
0999         }
1000     }
1001     QMap<QString, EntryWithName>::iterator it = ulist.begin();
1002     for (; it != ulist.end(); ++it) {
1003         EntryWithName ewn = *it;
1004         slist.insert(ewn.key, ewn.entry);
1005     }
1006     (*s->templatesList) += slist.values();
1007 }
1008 
1009 void KNewFileMenuPrivate::_k_slotOtherDesktopFile()
1010 {
1011     // The properties dialog took care of the copying, so we're done
1012     KPropertiesDialog *dialog = qobject_cast<KPropertiesDialog *>(q->sender());
1013     emit q->fileCreated(dialog->url());
1014 }
1015 
1016 void KNewFileMenuPrivate::_k_slotOtherDesktopFileClosed()
1017 {
1018     QFile::remove(m_tempFileToDelete);
1019 }
1020 
1021 void KNewFileMenuPrivate::_k_slotRealFileOrDir()
1022 {
1023     m_copyData.m_chosenFileName = m_text;
1024     _k_slotAbortDialog();
1025     executeStrategy();
1026 }
1027 
1028 void KNewFileMenuPrivate::_k_slotSymLink()
1029 {
1030     KNameAndUrlInputDialog *dlg = static_cast<KNameAndUrlInputDialog *>(m_fileDialog);
1031 
1032     m_copyData.m_chosenFileName = dlg->name(); // no path
1033     const QString linkTarget = dlg->urlText();
1034 
1035     if (m_copyData.m_chosenFileName.isEmpty() || linkTarget.isEmpty()) {
1036         return;
1037     }
1038 
1039     m_copyData.m_src = linkTarget;
1040     executeStrategy();
1041 }
1042 
1043 void KNewFileMenuPrivate::_k_slotTextChanged(const QString &text)
1044 {
1045     m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
1046     // Validate input, displaying a KMessageWidget for questionable names
1047 
1048     if (text.length() == 0) {
1049         m_messageWidget->hide();
1050         m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
1051     }
1052 
1053     // Don't allow creating folders that would mask . or ..
1054     else if (text == QLatin1Char('.') || text == QLatin1String("..")) {
1055         m_messageWidget->setText(xi18nc("@info", "The name <filename>%1</filename> cannot be used because it is reserved for use by the operating system.", text));
1056         m_messageWidget->setMessageType(KMessageWidget::Error);
1057         m_messageWidget->animatedShow();
1058         m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
1059     }
1060 
1061     // File or folder would be hidden; show warning
1062     else if (text.startsWith(QLatin1Char('.'))) {
1063         m_messageWidget->setText(xi18nc("@info", "The name <filename>%1</filename> starts with a dot, so it will be hidden by default.", text));
1064         m_messageWidget->setMessageType(KMessageWidget::Warning);
1065         m_messageWidget->animatedShow();
1066     }
1067 
1068 #ifndef Q_OS_WIN
1069     // Inform the user that slashes in folder names create a directory tree
1070     else if (text.contains(QLatin1Char('/'))) {
1071         if (m_creatingDirectory) {
1072             QStringList folders = text.split(QLatin1Char('/'));
1073             if (!folders.isEmpty()) {
1074                 if (folders.first().isEmpty()) {
1075                     folders.removeFirst();
1076                 }
1077             }
1078             QString label;
1079             if (folders.count() > 1) {
1080                 label = i18n("Using slashes in folder names will create sub-folders, like so:");
1081                 QString indentation = QString();
1082                 for (const QString &folder : qAsConst(folders)) {
1083                     label.append(QLatin1Char('\n'));
1084                     label.append(indentation);
1085                     label.append(folder);
1086                     label.append(QStringLiteral("/"));
1087                     indentation.append(QStringLiteral("    "));
1088                 }
1089             } else {
1090                 label = i18n("Using slashes in folder names will create sub-folders.");
1091             }
1092             m_messageWidget->setText(label);
1093             m_messageWidget->setMessageType(KMessageWidget::Information);
1094             m_messageWidget->animatedShow();
1095         }
1096     }
1097 #endif
1098 
1099 #ifdef Q_OS_WIN
1100     // Slashes and backslashes are not allowed in Windows filenames; show error
1101     else if (text.contains(QLatin1Char('/'))) {
1102         m_messageWidget->setText(i18n("Slashes cannot be used in file and folder names."));
1103         m_messageWidget->setMessageType(KMessageWidget::Error);
1104         m_messageWidget->animatedShow();
1105         m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
1106     }
1107     else if (text.contains(QLatin1Char('\\'))) {
1108         m_messageWidget->setText(i18n("Backslashes cannot be used in file and folder names."));
1109         m_messageWidget->setMessageType(KMessageWidget::Error);
1110         m_messageWidget->animatedShow();
1111         m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
1112     }
1113 #endif
1114 
1115     // Using a tilde to begin a file or folder name is not recommended
1116     else if (text.startsWith(QLatin1Char('~'))) {
1117         m_messageWidget->setText(i18n("Starting a file or folder name with a tilde is not recommended because it may be confusing or dangerous when using the terminal to delete things."));
1118         m_messageWidget->setMessageType(KMessageWidget::Warning);
1119         m_messageWidget->animatedShow();
1120     }
1121 
1122     else {
1123         m_messageWidget->hide();
1124     }
1125     m_text = text;
1126 }
1127 
1128 void KNewFileMenuPrivate::_k_slotUrlDesktopFile()
1129 {
1130     KNameAndUrlInputDialog *dlg = static_cast<KNameAndUrlInputDialog *>(m_fileDialog);
1131 
1132     m_copyData.m_chosenFileName = dlg->name(); // no path
1133     QUrl linkUrl = dlg->url();
1134 
1135     // Filter user input so that short uri entries, e.g. www.kde.org, are
1136     // handled properly. This not only makes the icon detection below work
1137     // properly, but opening the URL link where the short uri will not be
1138     // sent to the application (opening such link Konqueror fails).
1139     KUriFilterData uriData;
1140     uriData.setData(linkUrl); // the url to put in the file
1141     uriData.setCheckForExecutables(false);
1142 
1143     if (KUriFilter::self()->filterUri(uriData, QStringList() << QStringLiteral("kshorturifilter"))) {
1144         linkUrl = uriData.uri();
1145     }
1146 
1147     if (m_copyData.m_chosenFileName.isEmpty() || linkUrl.isEmpty()) {
1148         return;
1149     }
1150 
1151     // It's a "URL" desktop file; we need to make a temp copy of it, to modify it
1152     // before copying it to the final destination [which could be a remote protocol]
1153     QTemporaryFile tmpFile;
1154     tmpFile.setAutoRemove(false); // done below
1155     if (!tmpFile.open()) {
1156         qCritical() << "Couldn't create temp file!";
1157         return;
1158     }
1159 
1160     if (!checkSourceExists(m_copyData.m_templatePath)) {
1161         return;
1162     }
1163 
1164     // First copy the template into the temp file
1165     QFile file(m_copyData.m_templatePath);
1166     if (!file.open(QIODevice::ReadOnly)) {
1167         qCritical() << "Couldn't open template" << m_copyData.m_templatePath;
1168         return;
1169     }
1170     const QByteArray data = file.readAll();
1171     tmpFile.write(data);
1172     const QString tempFileName = tmpFile.fileName();
1173     Q_ASSERT(!tempFileName.isEmpty());
1174     tmpFile.close();
1175     file.close();
1176 
1177     KDesktopFile df(tempFileName);
1178     KConfigGroup group = df.desktopGroup();
1179     group.writeEntry("Icon", KProtocolInfo::icon(linkUrl.scheme()));
1180     group.writePathEntry("URL", linkUrl.toDisplayString());
1181     df.sync();
1182 
1183     m_copyData.m_src = tempFileName;
1184     m_copyData.m_tempFileToDelete = tempFileName;
1185 
1186     executeStrategy();
1187 }
1188 
1189 KNewFileMenu::KNewFileMenu(KActionCollection *collection, const QString &name, QObject *parent)
1190     : KActionMenu(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Create New"), parent),
1191       d(new KNewFileMenuPrivate(collection, this))
1192 {
1193     // Don't fill the menu yet
1194     // We'll do that in checkUpToDate (should be connected to aboutToShow)
1195     d->m_newMenuGroup = new QActionGroup(this);
1196     connect(d->m_newMenuGroup, SIGNAL(triggered(QAction*)), this, SLOT(_k_slotActionTriggered(QAction*)));
1197     d->m_parentWidget = qobject_cast<QWidget *>(parent);
1198     d->m_newDirAction = nullptr;
1199 
1200     if (d->m_actionCollection) {
1201         d->m_actionCollection->addAction(name, this);
1202     }
1203 
1204     d->m_menuDev = new KActionMenu(QIcon::fromTheme(QStringLiteral("drive-removable-media")), i18n("Link to Device"), this);
1205 }
1206 
1207 KNewFileMenu::~KNewFileMenu()
1208 {
1209     //qDebug() << this;
1210     delete d;
1211 }
1212 
1213 void KNewFileMenu::checkUpToDate()
1214 {
1215     KNewFileMenuSingleton *s = kNewMenuGlobals();
1216     //qDebug() << this << "m_menuItemsVersion=" << d->m_menuItemsVersion
1217     //              << "s->templatesVersion=" << s->templatesVersion;
1218     if (d->m_menuItemsVersion < s->templatesVersion || s->templatesVersion == 0) {
1219         //qDebug() << "recreating actions";
1220         // We need to clean up the action collection
1221         // We look for our actions using the group
1222         qDeleteAll(d->m_newMenuGroup->actions());
1223 
1224         if (!s->templatesList) { // No templates list up to now
1225             s->templatesList = new KNewFileMenuSingleton::EntryList;
1226             d->_k_slotFillTemplates();
1227             s->parseFiles();
1228         }
1229 
1230         // This might have been already done for other popupmenus,
1231         // that's the point in s->filesParsed.
1232         if (!s->filesParsed) {
1233             s->parseFiles();
1234         }
1235 
1236         d->fillMenu();
1237 
1238         d->m_menuItemsVersion = s->templatesVersion;
1239     }
1240 }
1241 
1242 void KNewFileMenu::createDirectory()
1243 {
1244     if (d->m_popupFiles.isEmpty()) {
1245         return;
1246     }
1247 
1248     QUrl baseUrl = d->m_popupFiles.first();
1249 
1250     KIO::StatJob *job = KIO::mostLocalUrl(baseUrl);
1251 
1252     if (job->exec()) {
1253         baseUrl = job->mostLocalUrl();
1254     }
1255 
1256     QString name = d->m_text.isEmpty() ? i18nc("Default name for a new folder", "New Folder") :
1257                    d->m_text;
1258 
1259     if (baseUrl.isLocalFile() && QFileInfo::exists(baseUrl.toLocalFile() + QLatin1Char('/') + name)) {
1260         name = KFileUtils::suggestName(baseUrl, name);
1261     }
1262 
1263     QDialog *fileDialog = new QDialog(d->m_parentWidget);
1264     fileDialog->setModal(isModal());
1265     fileDialog->setAttribute(Qt::WA_DeleteOnClose);
1266     fileDialog->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
1267     fileDialog->setWindowTitle(i18nc("@title:window", "New Folder"));
1268     d->m_fileDialog = fileDialog;
1269 
1270     QVBoxLayout *layout = new QVBoxLayout;
1271     layout->setSizeConstraint(QLayout::SetFixedSize);
1272 
1273     d->m_messageWidget = new KMessageWidget(fileDialog);
1274     d->m_messageWidget->setCloseButtonVisible(false);
1275     d->m_messageWidget->setWordWrap(true);
1276     d->m_messageWidget->hide();
1277     QLabel *label = new QLabel(i18n("Create new folder in %1:", baseUrl.toDisplayString(QUrl::PreferLocalFile)), fileDialog);
1278 
1279     QLineEdit *lineEdit = new QLineEdit(fileDialog);
1280     lineEdit->setClearButtonEnabled(true);
1281     lineEdit->setText(name);
1282     lineEdit->setMinimumWidth(400);
1283 
1284     d->m_buttonBox = new QDialogButtonBox(fileDialog);
1285     d->m_buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
1286     QObject::connect(d->m_buttonBox, &QDialogButtonBox::accepted, fileDialog, &QDialog::accept);
1287     QObject::connect(d->m_buttonBox, &QDialogButtonBox::rejected, fileDialog, &QDialog::reject);
1288 
1289     d->m_creatingDirectory = true;
1290     d->_k_slotTextChanged(name); // have to save string in d->m_text in case user does not touch dialog
1291     connect(lineEdit, SIGNAL(textChanged(QString)), this, SLOT(_k_slotTextChanged(QString)));
1292 
1293     layout->addWidget(label);
1294     layout->addWidget(lineEdit);
1295     layout->addWidget(d->m_buttonBox);
1296     layout->addWidget(d->m_messageWidget);
1297     layout->addStretch();
1298 
1299     fileDialog->setLayout(layout);
1300     connect(fileDialog, SIGNAL(accepted()), this, SLOT(_k_slotCreateDirectory()));
1301     connect(fileDialog, SIGNAL(rejected()), this, SLOT(_k_slotAbortDialog()));
1302 
1303 
1304     fileDialog->show();
1305     lineEdit->selectAll();
1306     lineEdit->setFocus();
1307 }
1308 
1309 void KNewFileMenu::createFile()
1310 {
1311     if (d->m_popupFiles.isEmpty()) {
1312         return;
1313     }
1314 
1315     checkUpToDate();
1316     if (!d->m_firstFileEntry) {
1317         return;
1318     }
1319 
1320     d->executeRealFileOrDir(*d->m_firstFileEntry);
1321 }
1322 
1323 bool KNewFileMenu::isModal() const
1324 {
1325     return d->m_modal;
1326 }
1327 
1328 QList<QUrl> KNewFileMenu::popupFiles() const
1329 {
1330     return d->m_popupFiles;
1331 }
1332 
1333 void KNewFileMenu::setModal(bool modal)
1334 {
1335     d->m_modal = modal;
1336 }
1337 
1338 void KNewFileMenu::setPopupFiles(const QList<QUrl> &files)
1339 {
1340     d->m_popupFiles = files;
1341     if (files.isEmpty()) {
1342         d->m_newMenuGroup->setEnabled(false);
1343     } else {
1344         const QUrl &firstUrl = files.first();
1345         if (KProtocolManager::supportsWriting(firstUrl)) {
1346             d->m_newMenuGroup->setEnabled(true);
1347             if (d->m_newDirAction) {
1348                 d->m_newDirAction->setEnabled(KProtocolManager::supportsMakeDir(firstUrl)); // e.g. trash:/
1349             }
1350         } else {
1351             d->m_newMenuGroup->setEnabled(true);
1352         }
1353     }
1354 }
1355 
1356 void KNewFileMenu::setParentWidget(QWidget *parentWidget)
1357 {
1358     d->m_parentWidget = parentWidget;
1359 }
1360 
1361 void KNewFileMenu::setSupportedMimeTypes(const QStringList &mime)
1362 {
1363     d->m_supportedMimeTypes = mime;
1364 }
1365 
1366 void KNewFileMenu::setViewShowsHiddenFiles(bool b)
1367 {
1368     d->m_viewShowsHiddenFiles = b;
1369 }
1370 
1371 void KNewFileMenu::slotResult(KJob *job)
1372 {
1373     if (job->error()) {
1374         static_cast<KIO::Job *>(job)->uiDelegate()->showErrorMessage();
1375     } else {
1376         // Was this a copy or a mkdir?
1377         if (job->property("newDirectoryURL").isValid()) {
1378             QUrl newDirectoryURL = job->property("newDirectoryURL").toUrl();
1379             emit directoryCreated(newDirectoryURL);
1380         } else {
1381             KIO::CopyJob *copyJob = ::qobject_cast<KIO::CopyJob *>(job);
1382             if (copyJob) {
1383                 const QUrl destUrl = copyJob->destUrl();
1384                 const QUrl localUrl = d->mostLocalUrl(destUrl);
1385                 if (localUrl.isLocalFile()) {
1386                     // Normal (local) file. Need to "touch" it, kio_file copied the mtime.
1387                     (void) ::utime(QFile::encodeName(localUrl.toLocalFile()).constData(), nullptr);
1388                 }
1389                 emit fileCreated(destUrl);
1390             } else if (KIO::SimpleJob *simpleJob = ::qobject_cast<KIO::SimpleJob *>(job)) {
1391                 // Called in the storedPut() case
1392                 org::kde::KDirNotify::emitFilesAdded(simpleJob->url().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash));
1393                 emit fileCreated(simpleJob->url());
1394             }
1395         }
1396     }
1397     if (!d->m_tempFileToDelete.isEmpty()) {
1398         QFile::remove(d->m_tempFileToDelete);
1399     }
1400 }
1401 
1402 QStringList KNewFileMenu::supportedMimeTypes() const
1403 {
1404     return d->m_supportedMimeTypes;
1405 }
1406 
1407 #include "moc_knewfilemenu.cpp"
1408