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