/* This file is part of the KDE project Copyright (C) 1998-2009 David Faure 2003 Sven Leiber This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 or at your option version 3. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "knewfilemenu.h" #include "knameandurlinputdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static QString expandTilde(const QString& name, bool isfile = false) { if (!name.isEmpty() && (!isfile || name[0] == '\\')) { const QString expandedName = KShell::tildeExpand(name); // When a tilde mark cannot be properly expanded, the above call // returns an empty string... if (!expandedName.isEmpty()) return expandedName; } return name; } // Singleton, with data shared by all KNewFileMenu instances class KNewFileMenuSingleton { public: KNewFileMenuSingleton() : dirWatch(0), filesParsed(false), templatesList(0), templatesVersion(0) { } ~KNewFileMenuSingleton() { delete dirWatch; delete templatesList; } /** * Opens the desktop files and completes the Entry list * Input: the entry list. Output: the entry list ;-) */ void parseFiles(); /** * For entryType * LINKTOTEMPLATE: a desktop file that points to a file or dir to copy * TEMPLATE: a real file to copy as is (the KDE-1.x solution) * SEPARATOR: to put a separator in the menu * 0 means: not parsed, i.e. we don't know */ enum EntryType { Unknown, LinkToTemplate = 1, Template, Separator }; KDirWatch * dirWatch; struct Entry { QString text; QString filePath; // empty for Separator QString templatePath; // same as filePath for Template QString icon; EntryType entryType; QString comment; QString mimeType; }; // NOTE: only filePath is known before we call parseFiles /** * List of all template files. It is important that they are in * the same order as the 'New' menu. */ typedef QList EntryList; /** * Set back to false each time new templates are found, * and to true on the first call to parseFiles */ bool filesParsed; EntryList * templatesList; /** * Is increased when templatesList has been updated and * menu needs to be re-filled. Menus have their own version and compare it * to templatesVersion before showing up */ int templatesVersion; }; void KNewFileMenuSingleton::parseFiles() { //kDebug(1203); filesParsed = true; QMutableListIterator templIter(*templatesList); while (templIter.hasNext()) { KNewFileMenuSingleton::Entry& templ = templIter.next(); const QString filePath = templ.filePath; if (!filePath.isEmpty()) { QString text; QString templatePath; // If a desktop file, then read the name from it. // Otherwise (or if no name in it?) use file name if (KDesktopFile::isDesktopFile(filePath)) { KDesktopFile desktopFile( filePath); if (desktopFile.noDisplay()) { templIter.remove(); continue; } text = desktopFile.readName(); templ.icon = desktopFile.readIcon(); templ.comment = desktopFile.readComment(); QString type = desktopFile.readType(); if (type == "Link") { templatePath = desktopFile.desktopGroup().readPathEntry("URL", QString()); if (templatePath[0] != '/' && !templatePath.startsWith("__")) { if (templatePath.startsWith("file:/")) templatePath = KUrl(templatePath).toLocalFile(); else { // A relative path, then (that's the default in the files we ship) QString linkDir = filePath.left(filePath.lastIndexOf('/') + 1 /*keep / */); //kDebug(1203) << "linkDir=" << linkDir; templatePath = linkDir + templatePath; } } } if (templatePath.isEmpty()) { // No URL key, this is an old-style template templ.entryType = KNewFileMenuSingleton::Template; templ.templatePath = templ.filePath; // we'll copy the file } else { templ.entryType = KNewFileMenuSingleton::LinkToTemplate; templ.templatePath = templatePath; } } if (text.isEmpty()) { text = KUrl(filePath).fileName(); if (text.endsWith(".desktop")) text.truncate(text.length() - 8); } templ.text = text; /*kDebug(1203) << "Updating entry with text=" << text << "entryType=" << templ.entryType << "templatePath=" << templ.templatePath;*/ } else { templ.entryType = KNewFileMenuSingleton::Separator; } } } K_GLOBAL_STATIC(KNewFileMenuSingleton, kNewMenuGlobals) class KNewFileMenuCopyData { public: KNewFileMenuCopyData() { m_isSymlink = false;} ~KNewFileMenuCopyData() {} QString chosenFileName() const { return m_chosenFileName; } // If empty, no copy is performed. QString sourceFileToCopy() const { return m_src; } QString tempFileToDelete() const { return m_tempFileToDelete; } bool m_isSymlink; QString m_chosenFileName; QString m_src; QString m_tempFileToDelete; QString m_templatePath; }; class KNewFileMenuPrivate { public: KNewFileMenuPrivate(KNewFileMenu* qq) : m_menuItemsVersion(0), m_modal(true), m_viewShowsHiddenFiles(false), q(qq) {} bool checkSourceExists(const QString& src); /** * Asks user whether to create a hidden directory with a dialog */ void confirmCreatingHiddenDir(const QString& name); /** * The strategy used for other desktop files than Type=Link. Example: Application, Device. */ void executeOtherDesktopFile(const KNewFileMenuSingleton::Entry& entry); /** * The strategy used for "real files or directories" (the common case) */ void executeRealFileOrDir(const KNewFileMenuSingleton::Entry& entry); /** * Actually performs file handling. Reads in m_copyData for needed data, that has been collected by execute*() before */ void executeStrategy(); /** * The strategy used when creating a symlink */ void executeSymLink(const KNewFileMenuSingleton::Entry& entry); /** * The strategy used for "url" desktop files */ void executeUrlDesktopFile(const KNewFileMenuSingleton::Entry& entry); /** * Fills the menu from the templates list. */ void fillMenu(); /** * Just clears the string buffer d->m_text, but I need a slot for this to occur */ void _k_slotAbortDialog(); /** * Called when New->* is clicked */ void _k_slotActionTriggered(QAction* action); /** * Callback function that reads in directory name from dialog and processes it */ void _k_slotCreateDirectory(bool writeHiddenDir = false); /** * Callback function that reads in directory name from dialog and processes it. This will wirte * a hidden directory without further questions */ void _k_slotCreateHiddenDirectory(); /** * Fills the templates list. */ void _k_slotFillTemplates(); /** * Callback in KNewFileMenu for the OtherDesktopFile Dialog. Handles dialog input and gives over * to executeStrategy() */ void _k_slotOtherDesktopFile(); /** * Callback in KNewFileMenu for the RealFile Dialog. Handles dialog input and gives over * to executeStrategy() */ void _k_slotRealFileOrDir(); /** * Dialogs use this slot to write the changed string into KNewFile menu when the user * changes touches them */ void _k_slotTextChanged(const QString & text); /** * Callback in KNewFileMenu for the Symlink Dialog. Handles dialog input and gives over * to executeStrategy() */ void _k_slotSymLink(); /** * Callback in KNewFileMenu for the Url/Desktop Dialog. Handles dialog input and gives over * to executeStrategy() */ void _k_slotUrlDesktopFile(); KActionCollection * m_actionCollection; KDialog* m_fileDialog; KActionMenu *m_menuDev; int m_menuItemsVersion; bool m_modal; QAction* m_newDirAction; /** * The action group that our actions belong to */ QActionGroup* m_newMenuGroup; QWidget *m_parentWidget; /** * When the user pressed the right mouse button over an URL a popup menu * is displayed. The URL belonging to this popup menu is stored here. */ KUrl::List m_popupFiles; QStringList m_supportedMimeTypes; QString m_tempFileToDelete; // set when a tempfile was created for a Type=URL desktop file QString m_text; bool m_viewShowsHiddenFiles; KNewFileMenu* q; KNewFileMenuCopyData m_copyData; }; bool KNewFileMenuPrivate::checkSourceExists(const QString& src) { if (!QFile::exists(src)) { kWarning(1203) << src << "doesn't exist" ; KDialog* dialog = new KDialog(m_parentWidget); dialog->setCaption( i18n("Sorry") ); dialog->setButtons( KDialog::Ok ); dialog->setObjectName( "sorry" ); dialog->setModal(q->isModal()); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setDefaultButton( KDialog::Ok ); dialog->setEscapeButton( KDialog::Ok ); KMessageBox::createKMessageBox(dialog, QMessageBox::Warning, i18n("The template file %1 does not exist.", src), QStringList(), QString(), 0, KMessageBox::NoExec, QString()); dialog->show(); return false; } return true; } void KNewFileMenuPrivate::confirmCreatingHiddenDir(const QString& name) { if(!KMessageBox::shouldBeShownContinue("confirm_create_hidden_dir")){ _k_slotCreateHiddenDirectory(); return; } KGuiItem continueGuiItem(KStandardGuiItem::cont()); continueGuiItem.setText(i18nc("@action:button", "Create directory")); KGuiItem cancelGuiItem(KStandardGuiItem::cancel()); cancelGuiItem.setText(i18nc("@action:button", "Enter a different name")); KDialog* confirmDialog = new KDialog(m_parentWidget); confirmDialog->setCaption(i18n("Create hidden directory?")); confirmDialog->setModal(m_modal); confirmDialog->setAttribute(Qt::WA_DeleteOnClose); KMessageBox::createKMessageBox(confirmDialog, QMessageBox::Warning, i18n("The name \"%1\" starts with a dot, so the directory will be hidden by default.", name), QStringList(), i18n("Do not ask again"), 0, KMessageBox::NoExec, QString()); confirmDialog->setButtonGuiItem(KDialog::Ok, continueGuiItem); confirmDialog->setButtonGuiItem(KDialog::Cancel, cancelGuiItem); QObject::connect(confirmDialog, SIGNAL(accepted()), q, SLOT(_k_slotCreateHiddenDirectory())); QObject::connect(confirmDialog, SIGNAL(rejected()), q, SLOT(createDirectory())); m_fileDialog = confirmDialog; confirmDialog->show(); } void KNewFileMenuPrivate::executeOtherDesktopFile(const KNewFileMenuSingleton::Entry& entry) { if (!checkSourceExists(entry.templatePath)) { return; } KUrl::List::const_iterator it = m_popupFiles.constBegin(); for (; it != m_popupFiles.constEnd(); ++it) { QString text = entry.text; text.remove("..."); // the ... is fine for the menu item but not for the default filename text = text.trimmed(); // In some languages, there is a space in front of "...", see bug 268895 // KDE5 TODO: remove the "..." from link*.desktop files and use i18n("%1...") when making // the action. KUrl defaultFile(*it); defaultFile.addPath(KIO::encodeFileName(text)); if (defaultFile.isLocalFile() && QFile::exists(defaultFile.toLocalFile())) text = KIO::RenameDialog::suggestName(*it, text); const KUrl templateUrl(entry.templatePath); KDialog* dlg = new KPropertiesDialog(templateUrl, *it, text, m_parentWidget); dlg->setModal(q->isModal()); dlg->setAttribute(Qt::WA_DeleteOnClose); QObject::connect(dlg, SIGNAL(applied()), q, SLOT(_k_slotOtherDesktopFile())); dlg->show(); } // We don't set m_src here -> there will be no copy, we are done. } void KNewFileMenuPrivate::executeRealFileOrDir(const KNewFileMenuSingleton::Entry& entry) { // The template is not a desktop file // Show the small dialog for getting the destination filename QString text = entry.text; text.remove("..."); // the ... is fine for the menu item but not for the default filename text = text.trimmed(); // In some languages, there is a space in front of "...", see bug 268895 m_copyData.m_src = entry.templatePath; KUrl defaultFile(m_popupFiles.first()); defaultFile.addPath(KIO::encodeFileName(text)); if (defaultFile.isLocalFile() && QFile::exists(defaultFile.toLocalFile())) text = KIO::RenameDialog::suggestName(m_popupFiles.first(), text); KDialog* fileDialog = new KDialog(m_parentWidget); fileDialog->setAttribute(Qt::WA_DeleteOnClose); fileDialog->setModal(q->isModal()); fileDialog->setButtons(KDialog::Ok | KDialog::Cancel); QWidget* mainWidget = new QWidget(fileDialog); QVBoxLayout *layout = new QVBoxLayout(mainWidget); QLabel *label = new QLabel(entry.comment); // We don't set the text of lineEdit in its constructor because the clear button would not be shown then. // It seems that setClearButtonShown(true) must be called *before* the text is set to make it work. // TODO: should probably be investigated and fixed in KLineEdit. KLineEdit *lineEdit = new KLineEdit; lineEdit->setClearButtonShown(true); lineEdit->setText(text); _k_slotTextChanged(text); QObject::connect(lineEdit, SIGNAL(textChanged(QString)), q, SLOT(_k_slotTextChanged(QString))); layout->addWidget(label); layout->addWidget(lineEdit); fileDialog->setMainWidget(mainWidget); QObject::connect(fileDialog, SIGNAL(accepted()), q, SLOT(_k_slotRealFileOrDir())); QObject::connect(fileDialog, SIGNAL(rejected()), q, SLOT(_k_slotAbortDialog())); fileDialog->show(); lineEdit->selectAll(); lineEdit->setFocus(); } void KNewFileMenuPrivate::executeSymLink(const KNewFileMenuSingleton::Entry& entry) { KNameAndUrlInputDialog* dlg = new KNameAndUrlInputDialog(i18n("File name:"), entry.comment, m_popupFiles.first(), m_parentWidget); dlg->setModal(q->isModal()); dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->setCaption(i18n("Create Symlink")); m_fileDialog = dlg; QObject::connect(dlg, SIGNAL(accepted()), q, SLOT(_k_slotSymLink())); dlg->show(); } void KNewFileMenuPrivate::executeStrategy() { m_tempFileToDelete = m_copyData.tempFileToDelete(); const QString src = m_copyData.sourceFileToCopy(); QString chosenFileName = expandTilde(m_copyData.chosenFileName(), true); if (src.isEmpty()) return; KUrl uSrc(src); if (uSrc.isLocalFile()) { // In case the templates/.source directory contains symlinks, resolve // them to the target files. Fixes bug #149628. KFileItem item(uSrc, QString(), KFileItem::Unknown); if (item.isLink()) uSrc.setPath(item.linkDest()); if (!m_copyData.m_isSymlink) { // If the file is not going to be detected as a desktop file, due to a // known extension (e.g. ".pl"), append ".desktop". #224142. QFile srcFile(uSrc.toLocalFile()); if (srcFile.open(QIODevice::ReadOnly)) { KMimeType::Ptr wantedMime = KMimeType::findByUrl(uSrc); KMimeType::Ptr mime = KMimeType::findByNameAndContent(m_copyData.m_chosenFileName, srcFile.read(1024)); //kDebug() << "mime=" << mime->name() << "wantedMime=" << wantedMime->name(); if (!mime->is(wantedMime->name())) chosenFileName += wantedMime->mainExtension(); } } } // The template is not a desktop file [or it's a URL one] // Copy it. KUrl::List::const_iterator it = m_popupFiles.constBegin(); for (; it != m_popupFiles.constEnd(); ++it) { KUrl dest(*it); dest.addPath(KIO::encodeFileName(chosenFileName)); KUrl::List lstSrc; lstSrc.append(uSrc); KIO::Job* kjob; if (m_copyData.m_isSymlink) { kjob = KIO::symlink(src, dest); // This doesn't work, FileUndoManager registers new links in copyingLinkDone, // which KIO::symlink obviously doesn't emit... Needs code in FileUndoManager. //KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Link, lstSrc, dest, kjob); } else { //kDebug(1203) << "KIO::copyAs(" << uSrc.url() << "," << dest.url() << ")"; KIO::CopyJob * job = KIO::copyAs(uSrc, dest); job->setDefaultPermissions(true); kjob = job; KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Copy, lstSrc, dest, job); } kjob->ui()->setWindow(m_parentWidget); QObject::connect(kjob, SIGNAL(result(KJob*)), q, SLOT(slotResult(KJob*))); } } void KNewFileMenuPrivate::executeUrlDesktopFile(const KNewFileMenuSingleton::Entry& entry) { KNameAndUrlInputDialog* dlg = new KNameAndUrlInputDialog(i18n("File name:"), entry.comment, m_popupFiles.first(), m_parentWidget); m_copyData.m_templatePath = entry.templatePath; dlg->setModal(q->isModal()); dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->setCaption(i18n("Create link to URL")); m_fileDialog = dlg; QObject::connect(dlg, SIGNAL(accepted()), q, SLOT(_k_slotUrlDesktopFile())); dlg->show(); } void KNewFileMenuPrivate::fillMenu() { QMenu* menu = q->menu(); menu->clear(); m_menuDev->menu()->clear(); m_newDirAction = 0; QSet seenTexts; // these shall be put at special positions QAction* linkURL = 0; QAction* linkApp = 0; QAction* linkPath = 0; KNewFileMenuSingleton* s = kNewMenuGlobals; int i = 1; KNewFileMenuSingleton::EntryList::iterator templ = s->templatesList->begin(); const KNewFileMenuSingleton::EntryList::iterator templ_end = s->templatesList->end(); for (; templ != templ_end; ++templ, ++i) { KNewFileMenuSingleton::Entry& entry = *templ; if (entry.entryType != KNewFileMenuSingleton::Separator) { // There might be a .desktop for that one already, if it's a kdelnk // This assumes we read .desktop files before .kdelnk files ... // In fact, we skip any second item that has the same text as another one. // Duplicates in a menu look bad in any case. const bool bSkip = seenTexts.contains(entry.text); if (bSkip) { kDebug(1203) << "skipping" << entry.filePath; } else { seenTexts.insert(entry.text); //const KNewFileMenuSingleton::Entry entry = templatesList->at(i-1); const QString templatePath = entry.templatePath; // The best way to identify the "Create Directory", "Link to Location", "Link to Application" was the template if (templatePath.endsWith("emptydir")) { QAction * act = new QAction(q); m_newDirAction = act; act->setIcon(KIcon(entry.icon)); act->setText(i18nc("@item:inmenu Create New", "%1", entry.text)); act->setActionGroup(m_newMenuGroup); menu->addAction(act); QAction *sep = new QAction(q); sep->setSeparator(true); menu->addAction(sep); } else { if (!m_supportedMimeTypes.isEmpty()) { bool keep = false; // We need to do mimetype filtering, for real files. const bool createSymlink = entry.templatePath == "__CREATE_SYMLINK__"; if (createSymlink) { keep = true; } else if (!KDesktopFile::isDesktopFile(entry.templatePath)) { // Determine mimetype on demand KMimeType::Ptr mime; if (entry.mimeType.isEmpty()) { mime = KMimeType::findByPath(entry.templatePath); if (mime) { //kDebug() << entry.templatePath << "is" << mime->name(); entry.mimeType = mime->name(); } else { entry.mimeType = KMimeType::defaultMimeType(); } } else { mime = KMimeType::mimeType(entry.mimeType); } Q_FOREACH(const QString& supportedMime, m_supportedMimeTypes) { if (mime && mime->is(supportedMime)) { keep = true; break; } } } if (!keep) { //kDebug() << "Not keeping" << entry.templatePath; continue; } } QAction * act = new QAction(q); act->setData(i); act->setIcon(KIcon(entry.icon)); act->setText(i18nc("@item:inmenu Create New", "%1", entry.text)); act->setActionGroup(m_newMenuGroup); //kDebug() << templatePath << entry.filePath; if (templatePath.endsWith("/URL.desktop")) { linkURL = act; } else if (templatePath.endsWith("/Program.desktop")) { linkApp = act; } else if (entry.filePath.endsWith("/linkPath.desktop")) { linkPath = act; } else if (KDesktopFile::isDesktopFile(templatePath)) { KDesktopFile df(templatePath); if (df.readType() == "FSDevice") m_menuDev->menu()->addAction(act); else menu->addAction(act); } else { menu->addAction(act); } } } } else { // Separate system from personal templates Q_ASSERT(entry.entryType != 0); QAction *sep = new QAction(q); sep->setSeparator(true); menu->addAction(sep); } } if (m_supportedMimeTypes.isEmpty()) { QAction *sep = new QAction(q); sep->setSeparator(true); menu->addAction(sep); if (linkURL) menu->addAction(linkURL); if (linkPath) menu->addAction(linkPath); if (linkApp) menu->addAction(linkApp); Q_ASSERT(m_menuDev); menu->addAction(m_menuDev); } } void KNewFileMenuPrivate::_k_slotAbortDialog() { m_text = QString(); } void KNewFileMenuPrivate::_k_slotActionTriggered(QAction* action) { q->trigger(); // was for kdesktop's slotNewMenuActivated() in kde3 times. Can't hurt to keep it... if (action == m_newDirAction) { q->createDirectory(); return; } const int id = action->data().toInt(); Q_ASSERT(id > 0); KNewFileMenuSingleton* s = kNewMenuGlobals; const KNewFileMenuSingleton::Entry entry = s->templatesList->at(id - 1); const bool createSymlink = entry.templatePath == "__CREATE_SYMLINK__"; m_copyData = KNewFileMenuCopyData(); if (createSymlink) { m_copyData.m_isSymlink = true; executeSymLink(entry); } else if (KDesktopFile::isDesktopFile(entry.templatePath)) { KDesktopFile df(entry.templatePath); if (df.readType() == "Link") { executeUrlDesktopFile(entry); } else { // any other desktop file (Device, App, etc.) executeOtherDesktopFile(entry); } } else { executeRealFileOrDir(entry); } } void KNewFileMenuPrivate::_k_slotCreateDirectory(bool writeHiddenDir) { KUrl url; KUrl baseUrl = m_popupFiles.first(); bool askAgain = false; QString name = expandTilde(m_text); if (!name.isEmpty()) { if ((name[0] == '/')) url.setPath(name); else { if (!m_viewShowsHiddenFiles && name.startsWith('.')) { if (!writeHiddenDir) { confirmCreatingHiddenDir(name); return; } } name = KIO::encodeFileName( name ); url = baseUrl; url.addPath( name ); } } if (!askAgain) { KIO::SimpleJob * job = KIO::mkdir(url); job->setProperty("isMkdirJob", true); // KDE5: cast to MkdirJob in slotResult instead job->ui()->setWindow(m_parentWidget); job->ui()->setAutoErrorHandlingEnabled(true); KIO::FileUndoManager::self()->recordJob( KIO::FileUndoManager::Mkdir, KUrl(), url, job ); if (job) { // We want the error handling to be done by slotResult so that subclasses can reimplement it job->ui()->setAutoErrorHandlingEnabled(false); QObject::connect(job, SIGNAL(result(KJob*)), q, SLOT(slotResult(KJob*))); } } else { q->createDirectory(); // ask again for the name } _k_slotAbortDialog(); } void KNewFileMenuPrivate::_k_slotCreateHiddenDirectory() { _k_slotCreateDirectory(true); } void KNewFileMenuPrivate::_k_slotFillTemplates() { KNewFileMenuSingleton* s = kNewMenuGlobals; //kDebug(1203); // Ensure any changes in the templates dir will call this if (! s->dirWatch) { s->dirWatch = new KDirWatch; const QStringList dirs = m_actionCollection->componentData().dirs()->resourceDirs("templates"); for (QStringList::const_iterator it = dirs.constBegin() ; it != dirs.constEnd() ; ++it) { //kDebug(1203) << "Templates resource dir:" << *it; s->dirWatch->addDir(*it); } QObject::connect(s->dirWatch, SIGNAL(dirty(QString)), q, SLOT(_k_slotFillTemplates())); QObject::connect(s->dirWatch, SIGNAL(created(QString)), q, SLOT(_k_slotFillTemplates())); QObject::connect(s->dirWatch, SIGNAL(deleted(QString)), q, SLOT(_k_slotFillTemplates())); // Ok, this doesn't cope with new dirs in KDEDIRS, but that's another story } ++s->templatesVersion; s->filesParsed = false; s->templatesList->clear(); // Look into "templates" dirs. const QStringList files = m_actionCollection->componentData().dirs()->findAllResources("templates"); QMap slist; // used for sorting Q_FOREACH(const QString& file, files) { //kDebug(1203) << file; if (file[0] != '.') { KNewFileMenuSingleton::Entry e; e.filePath = file; e.entryType = KNewFileMenuSingleton::Unknown; // not parsed yet // Put Directory first in the list (a bit hacky), // and TextFile before others because it's the most used one. // This also sorts by user-visible name. // The rest of the re-ordering is done in fillMenu. const KDesktopFile config(file); QString key = config.desktopGroup().readEntry("Name"); if (file.endsWith("Directory.desktop")) { key.prepend('0'); } else if (file.endsWith("TextFile.desktop")) { key.prepend('1'); } else { key.prepend('2'); } slist.insert(key, e); } } (*s->templatesList) += slist.values(); } void KNewFileMenuPrivate::_k_slotOtherDesktopFile() { executeStrategy(); } void KNewFileMenuPrivate::_k_slotRealFileOrDir() { m_copyData.m_chosenFileName = m_text; _k_slotAbortDialog(); executeStrategy(); } void KNewFileMenuPrivate::_k_slotSymLink() { KNameAndUrlInputDialog* dlg = static_cast(m_fileDialog); m_copyData.m_chosenFileName = dlg->name(); // no path KUrl linkUrl = dlg->url(); // the url to put in the file if (m_copyData.m_chosenFileName.isEmpty() || linkUrl.isEmpty()) return; if (linkUrl.isRelative()) m_copyData.m_src = QUrl(linkUrl).toString(); else if (linkUrl.isLocalFile()) m_copyData.m_src = linkUrl.toLocalFile(); else { KDialog* dialog = new KDialog(m_parentWidget); dialog->setCaption( i18n("Sorry") ); dialog->setButtons( KDialog::Ok ); dialog->setObjectName( "sorry" ); dialog->setModal(m_modal); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setDefaultButton( KDialog::Ok ); dialog->setEscapeButton( KDialog::Ok ); m_fileDialog = dialog; KMessageBox::createKMessageBox(dialog, QMessageBox::Warning, i18n("Basic links can only point to local files or directories.\nPlease use \"Link to Location\" for remote URLs."), QStringList(), QString(), 0, KMessageBox::NoExec, QString()); dialog->show(); return; } executeStrategy(); } void KNewFileMenuPrivate::_k_slotTextChanged(const QString & text) { m_text = text; } void KNewFileMenuPrivate::_k_slotUrlDesktopFile() { KNameAndUrlInputDialog* dlg = static_cast(m_fileDialog); m_copyData.m_chosenFileName = dlg->name(); // no path KUrl linkUrl = dlg->url(); // Filter user input so that short uri entries, e.g. www.kde.org, are // handled properly. This not only makes the icon detection below work // properly, but opening the URL link where the short uri will not be // sent to the application (opening such link Konqueror fails). KUriFilterData uriData; uriData.setData(linkUrl); // the url to put in the file uriData.setCheckForExecutables(false); if (KUriFilter::self()->filterUri(uriData, QStringList() << QLatin1String("kshorturifilter"))) { linkUrl = uriData.uri(); } if (m_copyData.m_chosenFileName.isEmpty() || linkUrl.isEmpty()) return; // It's a "URL" desktop file; we need to make a temp copy of it, to modify it // before copying it to the final destination [which could be a remote protocol] KTemporaryFile tmpFile; tmpFile.setAutoRemove(false); // done below if (!tmpFile.open()) { kError() << "Couldn't create temp file!"; return; } if (!checkSourceExists(m_copyData.m_templatePath)) { return; } // First copy the template into the temp file QFile file(m_copyData.m_templatePath); if (!file.open(QIODevice::ReadOnly)) { kError() << "Couldn't open template" << m_copyData.m_templatePath; return; } const QByteArray data = file.readAll(); tmpFile.write(data); const QString tempFileName = tmpFile.fileName(); Q_ASSERT(!tempFileName.isEmpty()); tmpFile.close(); file.close(); KDesktopFile df(tempFileName); KConfigGroup group = df.desktopGroup(); group.writeEntry("Icon", KProtocolInfo::icon(linkUrl.protocol())); group.writePathEntry("URL", linkUrl.prettyUrl()); df.sync(); m_copyData.m_src = tempFileName; m_copyData.m_tempFileToDelete = tempFileName; executeStrategy(); } KNewFileMenu::KNewFileMenu(KActionCollection* collection, const QString& name, QObject* parent) : KActionMenu(KIcon("document-new"), i18n("Create New"), parent), d(new KNewFileMenuPrivate(this)) { // Don't fill the menu yet // We'll do that in checkUpToDate (should be connected to aboutToShow) d->m_newMenuGroup = new QActionGroup(this); connect(d->m_newMenuGroup, SIGNAL(triggered(QAction*)), this, SLOT(_k_slotActionTriggered(QAction*))); d->m_actionCollection = collection; d->m_parentWidget = qobject_cast(parent); d->m_newDirAction = 0; d->m_actionCollection->addAction(name, this); d->m_menuDev = new KActionMenu(KIcon("drive-removable-media"), i18n("Link to Device"), this); } KNewFileMenu::~KNewFileMenu() { //kDebug(1203) << this; delete d; } void KNewFileMenu::checkUpToDate() { KNewFileMenuSingleton* s = kNewMenuGlobals; //kDebug(1203) << this << "m_menuItemsVersion=" << d->m_menuItemsVersion // << "s->templatesVersion=" << s->templatesVersion; if (d->m_menuItemsVersion < s->templatesVersion || s->templatesVersion == 0) { //kDebug(1203) << "recreating actions"; // We need to clean up the action collection // We look for our actions using the group foreach (QAction* action, d->m_newMenuGroup->actions()) delete action; if (!s->templatesList) { // No templates list up to now s->templatesList = new KNewFileMenuSingleton::EntryList; d->_k_slotFillTemplates(); s->parseFiles(); } // This might have been already done for other popupmenus, // that's the point in s->filesParsed. if (!s->filesParsed) { s->parseFiles(); } d->fillMenu(); d->m_menuItemsVersion = s->templatesVersion; } } void KNewFileMenu::createDirectory() { if (d->m_popupFiles.isEmpty()) return; KUrl baseUrl = d->m_popupFiles.first(); QString name = d->m_text.isEmpty()? i18nc("Default name for a new folder", "New Folder") : d->m_text; if (baseUrl.isLocalFile() && QFileInfo(baseUrl.toLocalFile(KUrl::AddTrailingSlash) + name).exists()) name = KIO::RenameDialog::suggestName(baseUrl, name); KDialog* fileDialog = new KDialog(d->m_parentWidget); fileDialog->setModal(isModal()); fileDialog->setAttribute(Qt::WA_DeleteOnClose); fileDialog->setButtons(KDialog::Ok | KDialog::Cancel); fileDialog->setCaption(i18nc("@title:window", "New Folder")); QWidget* mainWidget = new QWidget(fileDialog); QVBoxLayout *layout = new QVBoxLayout(mainWidget); QLabel *label = new QLabel(i18n("Create new folder in:\n%1", baseUrl.pathOrUrl())); // We don't set the text of lineEdit in its constructor because the clear button would not be shown then. // It seems that setClearButtonShown(true) must be called *before* the text is set to make it work. // TODO: should probably be investigated and fixed in KLineEdit. KLineEdit *lineEdit = new KLineEdit; lineEdit->setClearButtonShown(true); lineEdit->setText(name); d->_k_slotTextChanged(name); // have to save string in d->m_text in case user does not touch dialog connect(lineEdit, SIGNAL(textChanged(QString)), this, SLOT(_k_slotTextChanged(QString))); layout->addWidget(label); layout->addWidget(lineEdit); fileDialog->setMainWidget(mainWidget); connect(fileDialog, SIGNAL(accepted()), this, SLOT(_k_slotCreateDirectory())); connect(fileDialog, SIGNAL(rejected()), this, SLOT(_k_slotAbortDialog())); d->m_fileDialog = fileDialog; fileDialog->show(); lineEdit->selectAll(); lineEdit->setFocus(); } bool KNewFileMenu::isModal() const { return d->m_modal; } KUrl::List KNewFileMenu::popupFiles() const { return d->m_popupFiles; } void KNewFileMenu::setModal(bool modal) { d->m_modal = modal; } void KNewFileMenu::setPopupFiles(const KUrl::List& files) { d->m_popupFiles = files; if (files.isEmpty()) { d->m_newMenuGroup->setEnabled(false); } else { KUrl firstUrl = files.first(); if (KProtocolManager::supportsWriting(firstUrl)) { d->m_newMenuGroup->setEnabled(true); if (d->m_newDirAction) { d->m_newDirAction->setEnabled(KProtocolManager::supportsMakeDir(firstUrl)); // e.g. trash:/ } } else { d->m_newMenuGroup->setEnabled(true); } } } void KNewFileMenu::setParentWidget(QWidget* parentWidget) { d->m_parentWidget = parentWidget; } void KNewFileMenu::setSupportedMimeTypes(const QStringList& mime) { d->m_supportedMimeTypes = mime; } void KNewFileMenu::setViewShowsHiddenFiles(bool b) { d->m_viewShowsHiddenFiles = b; } void KNewFileMenu::slotResult(KJob * job) { if (job->error()) { static_cast(job)->ui()->showErrorMessage(); } else { // Was this a copy or a mkdir? KIO::CopyJob* copyJob = ::qobject_cast(job); if (copyJob) { const KUrl destUrl = copyJob->destUrl(); const KUrl localUrl = KIO::NetAccess::mostLocalUrl(destUrl, d->m_parentWidget); if (localUrl.isLocalFile()) { // Normal (local) file. Need to "touch" it, kio_file copied the mtime. (void) ::utime(QFile::encodeName(localUrl.toLocalFile()), 0); } emit fileCreated(destUrl); } else if (KIO::SimpleJob* simpleJob = ::qobject_cast(job)) { // Can be mkdir or symlink if (simpleJob->property("isMkdirJob").toBool() == true) { kDebug() << "Emit directoryCreated" << simpleJob->url(); emit directoryCreated(simpleJob->url()); } else { emit fileCreated(simpleJob->url()); } } } if (!d->m_tempFileToDelete.isEmpty()) QFile::remove(d->m_tempFileToDelete); } QStringList KNewFileMenu::supportedMimeTypes() const { return d->m_supportedMimeTypes; } #include "knewfilemenu.moc"