knewfilemenu.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE project 00002 Copyright (C) 1998-2009 David Faure <faure@kde.org> 00003 2003 Sven Leiber <s.leiber@web.de> 00004 00005 This library is free software; you can redistribute it and/or 00006 modify it under the terms of the GNU Library General Public 00007 License as published by the Free Software Foundation; either 00008 version 2 or at your option version 3. 00009 00010 This library is distributed in the hope that it will be useful, 00011 but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00013 Library General Public License for more details. 00014 00015 You should have received a copy of the GNU Library General Public License 00016 along with this library; see the file COPYING.LIB. If not, write to 00017 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00018 Boston, MA 02110-1301, USA. 00019 */ 00020 00021 #include "knewfilemenu.h" 00022 #include "knameandurlinputdialog.h" 00023 00024 #include <QDir> 00025 #include <QVBoxLayout> 00026 #include <QList> 00027 #include <QLabel> 00028 #include <kactioncollection.h> 00029 #include <kdebug.h> 00030 #include <kdesktopfile.h> 00031 #include <kdirwatch.h> 00032 #include <kicon.h> 00033 #include <kcomponentdata.h> 00034 #include <kinputdialog.h> 00035 #include <kdialog.h> 00036 #include <klocale.h> 00037 #include <klineedit.h> 00038 #include <kmessagebox.h> 00039 #include <kstandarddirs.h> 00040 #include <kprotocolinfo.h> 00041 #include <kprotocolmanager.h> 00042 #include <kmenu.h> 00043 #include <krun.h> 00044 #include <kshell.h> 00045 #include <kio/job.h> 00046 #include <kio/copyjob.h> 00047 #include <kio/jobuidelegate.h> 00048 #include <kio/renamedialog.h> 00049 #include <kio/netaccess.h> 00050 #include <kio/fileundomanager.h> 00051 #include <kio/kurifilter.h> 00052 00053 #include <kpropertiesdialog.h> 00054 #include <ktemporaryfile.h> 00055 #include <utime.h> 00056 00057 static QString expandTilde(const QString& name, bool isfile = false) 00058 { 00059 if (!name.isEmpty() && (!isfile || name[0] == '\\')) 00060 { 00061 const QString expandedName = KShell::tildeExpand(name); 00062 // When a tilde mark cannot be properly expanded, the above call 00063 // returns an empty string... 00064 if (!expandedName.isEmpty()) 00065 return expandedName; 00066 } 00067 00068 return name; 00069 } 00070 00071 // Singleton, with data shared by all KNewFileMenu instances 00072 class KNewFileMenuSingleton 00073 { 00074 public: 00075 KNewFileMenuSingleton() 00076 : dirWatch(0), 00077 filesParsed(false), 00078 templatesList(0), 00079 templatesVersion(0) 00080 { 00081 } 00082 00083 ~KNewFileMenuSingleton() 00084 { 00085 delete dirWatch; 00086 delete templatesList; 00087 } 00088 00089 00094 void parseFiles(); 00095 00103 enum EntryType { Unknown, LinkToTemplate = 1, Template, Separator }; 00104 00105 KDirWatch * dirWatch; 00106 00107 struct Entry { 00108 QString text; 00109 QString filePath; // empty for Separator 00110 QString templatePath; // same as filePath for Template 00111 QString icon; 00112 EntryType entryType; 00113 QString comment; 00114 QString mimeType; 00115 }; 00116 // NOTE: only filePath is known before we call parseFiles 00117 00122 typedef QList<Entry> EntryList; 00123 00128 bool filesParsed; 00129 EntryList * templatesList; 00130 00136 int templatesVersion; 00137 }; 00138 00139 void KNewFileMenuSingleton::parseFiles() 00140 { 00141 //kDebug(1203); 00142 filesParsed = true; 00143 KNewFileMenuSingleton::EntryList::iterator templ = templatesList->begin(); 00144 const KNewFileMenuSingleton::EntryList::iterator templ_end = templatesList->end(); 00145 for (; templ != templ_end; ++templ) 00146 { 00147 QString iconname; 00148 QString filePath = (*templ).filePath; 00149 if (!filePath.isEmpty()) 00150 { 00151 QString text; 00152 QString templatePath; 00153 // If a desktop file, then read the name from it. 00154 // Otherwise (or if no name in it?) use file name 00155 if (KDesktopFile::isDesktopFile(filePath)) { 00156 KDesktopFile desktopFile( filePath); 00157 text = desktopFile.readName(); 00158 (*templ).icon = desktopFile.readIcon(); 00159 (*templ).comment = desktopFile.readComment(); 00160 QString type = desktopFile.readType(); 00161 if (type == "Link") 00162 { 00163 templatePath = desktopFile.desktopGroup().readPathEntry("URL", QString()); 00164 if (templatePath[0] != '/' && !templatePath.startsWith("__")) 00165 { 00166 if (templatePath.startsWith("file:/")) 00167 templatePath = KUrl(templatePath).toLocalFile(); 00168 else 00169 { 00170 // A relative path, then (that's the default in the files we ship) 00171 QString linkDir = filePath.left(filePath.lastIndexOf('/') + 1 /*keep / */); 00172 //kDebug(1203) << "linkDir=" << linkDir; 00173 templatePath = linkDir + templatePath; 00174 } 00175 } 00176 } 00177 if (templatePath.isEmpty()) 00178 { 00179 // No URL key, this is an old-style template 00180 (*templ).entryType = KNewFileMenuSingleton::Template; 00181 (*templ).templatePath = (*templ).filePath; // we'll copy the file 00182 } else { 00183 (*templ).entryType = KNewFileMenuSingleton::LinkToTemplate; 00184 (*templ).templatePath = templatePath; 00185 } 00186 00187 } 00188 if (text.isEmpty()) 00189 { 00190 text = KUrl(filePath).fileName(); 00191 if (text.endsWith(".desktop")) 00192 text.truncate(text.length() - 8); 00193 } 00194 (*templ).text = text; 00195 /*kDebug(1203) << "Updating entry with text=" << text 00196 << "entryType=" << (*templ).entryType 00197 << "templatePath=" << (*templ).templatePath;*/ 00198 } 00199 else { 00200 (*templ).entryType = KNewFileMenuSingleton::Separator; 00201 } 00202 } 00203 } 00204 00205 K_GLOBAL_STATIC(KNewFileMenuSingleton, kNewMenuGlobals) 00206 00207 00208 class KNewFileMenuStrategy 00209 { 00210 friend class KNewFileMenuPrivate; 00211 public: 00212 KNewFileMenuStrategy() { m_isSymlink = false;} 00213 ~KNewFileMenuStrategy() {} 00214 QString chosenFileName() const { return m_chosenFileName; } 00215 00216 // If empty, no copy is performed. 00217 QString sourceFileToCopy() const { return m_src; } 00218 QString tempFileToDelete() const { return m_tempFileToDelete; } 00219 bool m_isSymlink; 00220 00221 protected: 00222 QString m_chosenFileName; 00223 QString m_src; 00224 QString m_tempFileToDelete; 00225 QString m_templatePath; 00226 }; 00227 00228 class KNewFileMenuPrivate 00229 { 00230 public: 00231 KNewFileMenuPrivate(KNewFileMenu* qq) 00232 : m_menuItemsVersion(0), 00233 m_modal(true), 00234 m_viewShowsHiddenFiles(false), 00235 q(qq) 00236 {} 00237 00238 bool checkSourceExists(const QString& src); 00239 00243 void confirmCreatingHiddenDir(const QString& name); 00244 00248 void executeOtherDesktopFile(const KNewFileMenuSingleton::Entry& entry); 00249 00253 void executeRealFileOrDir(const KNewFileMenuSingleton::Entry& entry); 00254 00258 void executeStrategy(); 00259 00263 void executeSymLink(const KNewFileMenuSingleton::Entry& entry); 00264 00268 void executeUrlDesktopFile(const KNewFileMenuSingleton::Entry& entry); 00269 00273 void fillMenu(); 00274 00278 void _k_slotAbortDialog(); 00279 00283 void _k_slotActionTriggered(QAction* action); 00284 00288 void _k_slotCreateDirectory(bool writeHiddenDir = false); 00289 00294 void _k_slotCreateHiddenDirectory(); 00295 00299 void _k_slotFillTemplates(); 00300 00305 void _k_slotOtherDesktopFile(); 00306 00311 void _k_slotRealFileOrDir(); 00312 00317 void _k_slotTextChanged(const QString & text); 00318 00323 void _k_slotSymLink(); 00324 00329 void _k_slotUrlDesktopFile(); 00330 00331 00332 KActionCollection * m_actionCollection; 00333 KDialog* m_fileDialog; 00334 00335 KActionMenu *m_menuDev; 00336 int m_menuItemsVersion; 00337 bool m_modal; 00338 QAction* m_newDirAction; 00339 00343 QActionGroup* m_newMenuGroup; 00344 QWidget *m_parentWidget; 00345 00350 KUrl::List m_popupFiles; 00351 00352 QStringList m_supportedMimeTypes; 00353 QString m_tempFileToDelete; // set when a tempfile was created for a Type=URL desktop file 00354 QString m_text; 00355 bool m_viewShowsHiddenFiles; 00356 00357 KNewFileMenu* q; 00358 00359 class Strategy; 00360 KNewFileMenuStrategy m_strategy; 00361 }; 00362 00363 bool KNewFileMenuPrivate::checkSourceExists(const QString& src) 00364 { 00365 if (!QFile::exists(src)) { 00366 kWarning(1203) << src << "doesn't exist" ; 00367 00368 KDialog* dialog = new KDialog(m_parentWidget); 00369 dialog->setCaption( i18n("Sorry") ); 00370 dialog->setButtons( KDialog::Ok ); 00371 dialog->setObjectName( "sorry" ); 00372 dialog->setModal(q->isModal()); 00373 dialog->setAttribute(Qt::WA_DeleteOnClose); 00374 dialog->setDefaultButton( KDialog::Ok ); 00375 dialog->setEscapeButton( KDialog::Ok ); 00376 00377 KMessageBox::createKMessageBox(dialog, QMessageBox::Warning, 00378 i18n("<qt>The template file <b>%1</b> does not exist.</qt>", src), 00379 QStringList(), QString(), 0, KMessageBox::NoExec, 00380 QString()); 00381 00382 dialog->show(); 00383 00384 return false; 00385 } 00386 return true; 00387 } 00388 00389 void KNewFileMenuPrivate::confirmCreatingHiddenDir(const QString& name) 00390 { 00391 if(!KMessageBox::shouldBeShownContinue("confirm_create_hidden_dir")){ 00392 _k_slotCreateHiddenDirectory(); 00393 return; 00394 } 00395 00396 KGuiItem continueGuiItem(KStandardGuiItem::cont()); 00397 continueGuiItem.setText(i18nc("@action:button", "Create directory")); 00398 KGuiItem cancelGuiItem(KStandardGuiItem::cancel()); 00399 cancelGuiItem.setText(i18nc("@action:button", "Enter a different name")); 00400 00401 KDialog* confirmDialog = new KDialog(m_parentWidget); 00402 confirmDialog->setCaption(i18n("Create hidden directory?")); 00403 confirmDialog->setModal(m_modal); 00404 confirmDialog->setAttribute(Qt::WA_DeleteOnClose); 00405 KMessageBox::createKMessageBox(confirmDialog, QMessageBox::Warning, 00406 i18n("The name \"%1\" starts with a dot, so the directory will be hidden by default.", name), 00407 QStringList(), 00408 i18n("Do not ask again"), 00409 0, 00410 KMessageBox::NoExec, 00411 QString()); 00412 confirmDialog->setButtonGuiItem(KDialog::Ok, continueGuiItem); 00413 confirmDialog->setButtonGuiItem(KDialog::Cancel, cancelGuiItem); 00414 00415 QObject::connect(confirmDialog, SIGNAL(accepted()), q, SLOT(_k_slotCreateHiddenDirectory())); 00416 QObject::connect(confirmDialog, SIGNAL(rejected()), q, SLOT(createDirectory())); 00417 00418 m_fileDialog = confirmDialog; 00419 confirmDialog->show(); 00420 00421 } 00422 00423 void KNewFileMenuPrivate::executeOtherDesktopFile(const KNewFileMenuSingleton::Entry& entry) 00424 { 00425 if (!checkSourceExists(entry.templatePath)) { 00426 return; 00427 } 00428 00429 KUrl::List::const_iterator it = m_popupFiles.constBegin(); 00430 for (; it != m_popupFiles.constEnd(); ++it) 00431 { 00432 QString text = entry.text; 00433 text.remove("..."); // the ... is fine for the menu item but not for the default filename 00434 text = text.trimmed(); // In some languages, there is a space in front of "...", see bug 268895 00435 // KDE5 TODO: remove the "..." from link*.desktop files and use i18n("%1...") when making 00436 // the action. 00437 00438 KUrl defaultFile(*it); 00439 defaultFile.addPath(KIO::encodeFileName(text)); 00440 if (defaultFile.isLocalFile() && QFile::exists(defaultFile.toLocalFile())) 00441 text = KIO::RenameDialog::suggestName(*it, text); 00442 00443 const KUrl templateUrl(entry.templatePath); 00444 00445 KDialog* dlg = new KPropertiesDialog(templateUrl, *it, text, m_parentWidget); 00446 dlg->setModal(q->isModal()); 00447 dlg->setAttribute(Qt::WA_DeleteOnClose); 00448 QObject::connect(dlg, SIGNAL(applied()), q, SLOT(_k_slotOtherDesktopFile())); 00449 dlg->show(); 00450 } 00451 // We don't set m_src here -> there will be no copy, we are done. 00452 } 00453 00454 void KNewFileMenuPrivate::executeRealFileOrDir(const KNewFileMenuSingleton::Entry& entry) 00455 { 00456 // The template is not a desktop file 00457 // Show the small dialog for getting the destination filename 00458 QString text = entry.text; 00459 text.remove("..."); // the ... is fine for the menu item but not for the default filename 00460 text = text.trimmed(); // In some languages, there is a space in front of "...", see bug 268895 00461 m_strategy.m_src = entry.templatePath; 00462 00463 KUrl defaultFile(m_popupFiles.first()); 00464 defaultFile.addPath(KIO::encodeFileName(text)); 00465 if (defaultFile.isLocalFile() && QFile::exists(defaultFile.toLocalFile())) 00466 text = KIO::RenameDialog::suggestName(m_popupFiles.first(), text); 00467 00468 KDialog* fileDialog = new KDialog(m_parentWidget); 00469 fileDialog->setAttribute(Qt::WA_DeleteOnClose); 00470 fileDialog->setModal(q->isModal()); 00471 fileDialog->setButtons(KDialog::Ok | KDialog::Cancel); 00472 00473 QWidget* mainWidget = new QWidget(fileDialog); 00474 QVBoxLayout *layout = new QVBoxLayout(mainWidget); 00475 QLabel *label = new QLabel(entry.comment); 00476 00477 // We don't set the text of lineEdit in its constructor because the clear button would not be shown then. 00478 // It seems that setClearButtonShown(true) must be called *before* the text is set to make it work. 00479 // TODO: should probably be investigated and fixed in KLineEdit. 00480 KLineEdit *lineEdit = new KLineEdit; 00481 lineEdit->setClearButtonShown(true); 00482 lineEdit->setText(text); 00483 00484 _k_slotTextChanged(text); 00485 QObject::connect(lineEdit, SIGNAL(textChanged(const QString &)), q, SLOT(_k_slotTextChanged(const QString &))); 00486 00487 layout->addWidget(label); 00488 layout->addWidget(lineEdit); 00489 00490 fileDialog->setMainWidget(mainWidget); 00491 QObject::connect(fileDialog, SIGNAL(accepted()), q, SLOT(_k_slotRealFileOrDir())); 00492 QObject::connect(fileDialog, SIGNAL(rejected()), q, SLOT(_k_slotAbortDialog())); 00493 00494 fileDialog->show(); 00495 lineEdit->selectAll(); 00496 lineEdit->setFocus(); 00497 } 00498 00499 void KNewFileMenuPrivate::executeSymLink(const KNewFileMenuSingleton::Entry& entry) 00500 { 00501 KNameAndUrlInputDialog* dlg = new KNameAndUrlInputDialog(i18n("File name:"), entry.comment, m_popupFiles.first(), m_parentWidget); 00502 dlg->setModal(q->isModal()); 00503 dlg->setAttribute(Qt::WA_DeleteOnClose); 00504 dlg->setCaption(i18n("Create Symlink")); 00505 m_fileDialog = dlg; 00506 QObject::connect(dlg, SIGNAL(accepted()), q, SLOT(_k_slotSymLink())); 00507 dlg->show(); 00508 } 00509 00510 void KNewFileMenuPrivate::executeStrategy() 00511 { 00512 m_tempFileToDelete = m_strategy.tempFileToDelete(); 00513 const QString src = m_strategy.sourceFileToCopy(); 00514 QString chosenFileName = expandTilde(m_strategy.chosenFileName(), true); 00515 00516 if (src.isEmpty()) 00517 return; 00518 KUrl uSrc(src); 00519 00520 if (uSrc.isLocalFile()) { 00521 // In case the templates/.source directory contains symlinks, resolve 00522 // them to the target files. Fixes bug #149628. 00523 KFileItem item(uSrc, QString(), KFileItem::Unknown); 00524 if (item.isLink()) 00525 uSrc.setPath(item.linkDest()); 00526 } 00527 00528 // The template is not a desktop file [or it's a URL one] 00529 // Copy it. 00530 KUrl::List::const_iterator it = m_popupFiles.constBegin(); 00531 for (; it != m_popupFiles.constEnd(); ++it) 00532 { 00533 KUrl dest(*it); 00534 dest.addPath(KIO::encodeFileName(chosenFileName)); 00535 00536 KUrl::List lstSrc; 00537 lstSrc.append(uSrc); 00538 KIO::Job* kjob; 00539 if (m_strategy.m_isSymlink) { 00540 kjob = KIO::symlink(src, dest); 00541 // This doesn't work, FileUndoManager registers new links in copyingLinkDone, 00542 // which KIO::symlink obviously doesn't emit... Needs code in FileUndoManager. 00543 //KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Link, lstSrc, dest, kjob); 00544 } else { 00545 //kDebug(1203) << "KIO::copyAs(" << uSrc.url() << "," << dest.url() << ")"; 00546 KIO::CopyJob * job = KIO::copyAs(uSrc, dest); 00547 job->setDefaultPermissions(true); 00548 kjob = job; 00549 KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Copy, lstSrc, dest, job); 00550 } 00551 kjob->ui()->setWindow(m_parentWidget); 00552 QObject::connect(kjob, SIGNAL(result(KJob*)), q, SLOT(slotResult(KJob*))); 00553 } 00554 } 00555 00556 void KNewFileMenuPrivate::executeUrlDesktopFile(const KNewFileMenuSingleton::Entry& entry) 00557 { 00558 KNameAndUrlInputDialog* dlg = new KNameAndUrlInputDialog(i18n("File name:"), entry.comment, m_popupFiles.first(), m_parentWidget); 00559 m_strategy.m_templatePath = entry.templatePath; 00560 dlg->setModal(q->isModal()); 00561 dlg->setAttribute(Qt::WA_DeleteOnClose); 00562 dlg->setCaption(i18n("Create link to URL")); 00563 m_fileDialog = dlg; 00564 QObject::connect(dlg, SIGNAL(accepted()), q, SLOT(_k_slotUrlDesktopFile())); 00565 dlg->show(); 00566 } 00567 00568 void KNewFileMenuPrivate::fillMenu() 00569 { 00570 QMenu* menu = q->menu(); 00571 menu->clear(); 00572 m_menuDev->menu()->clear(); 00573 m_newDirAction = 0; 00574 00575 QSet<QString> seenTexts; 00576 // these shall be put at special positions 00577 QAction* linkURL = 0; 00578 QAction* linkApp = 0; 00579 QAction* linkPath = 0; 00580 00581 KNewFileMenuSingleton* s = kNewMenuGlobals; 00582 int i = 1; 00583 KNewFileMenuSingleton::EntryList::iterator templ = s->templatesList->begin(); 00584 const KNewFileMenuSingleton::EntryList::iterator templ_end = s->templatesList->end(); 00585 for (; templ != templ_end; ++templ, ++i) 00586 { 00587 KNewFileMenuSingleton::Entry& entry = *templ; 00588 if (entry.entryType != KNewFileMenuSingleton::Separator) { 00589 // There might be a .desktop for that one already, if it's a kdelnk 00590 // This assumes we read .desktop files before .kdelnk files ... 00591 00592 // In fact, we skip any second item that has the same text as another one. 00593 // Duplicates in a menu look bad in any case. 00594 00595 const bool bSkip = seenTexts.contains(entry.text); 00596 if (bSkip) { 00597 kDebug(1203) << "skipping" << entry.filePath; 00598 } else { 00599 seenTexts.insert(entry.text); 00600 //const KNewFileMenuSingleton::Entry entry = templatesList->at(i-1); 00601 00602 const QString templatePath = entry.templatePath; 00603 // The best way to identify the "Create Directory", "Link to Location", "Link to Application" was the template 00604 if (templatePath.endsWith("emptydir")) { 00605 QAction * act = new QAction(q); 00606 m_newDirAction = act; 00607 act->setIcon(KIcon(entry.icon)); 00608 act->setText(i18nc("@item:inmenu Create New", "%1", entry.text)); 00609 act->setActionGroup(m_newMenuGroup); 00610 menu->addAction(act); 00611 00612 QAction *sep = new QAction(q); 00613 sep->setSeparator(true); 00614 menu->addAction(sep); 00615 } else { 00616 00617 if (!m_supportedMimeTypes.isEmpty()) { 00618 bool keep = false; 00619 00620 // We need to do mimetype filtering, for real files. 00621 const bool createSymlink = entry.templatePath == "__CREATE_SYMLINK__"; 00622 if (createSymlink) { 00623 keep = true; 00624 } else if (!KDesktopFile::isDesktopFile(entry.templatePath)) { 00625 00626 // Determine mimetype on demand 00627 KMimeType::Ptr mime; 00628 if (entry.mimeType.isEmpty()) { 00629 mime = KMimeType::findByPath(entry.templatePath); 00630 if (mime) { 00631 //kDebug() << entry.templatePath << "is" << mime->name(); 00632 entry.mimeType = mime->name(); 00633 } else { 00634 entry.mimeType = KMimeType::defaultMimeType(); 00635 } 00636 } else { 00637 mime = KMimeType::mimeType(entry.mimeType); 00638 } 00639 Q_FOREACH(const QString& supportedMime, m_supportedMimeTypes) { 00640 if (mime && mime->is(supportedMime)) { 00641 keep = true; 00642 break; 00643 } 00644 } 00645 } 00646 00647 if (!keep) { 00648 //kDebug() << "Not keeping" << entry.templatePath; 00649 continue; 00650 } 00651 } 00652 00653 QAction * act = new QAction(q); 00654 act->setData(i); 00655 act->setIcon(KIcon(entry.icon)); 00656 act->setText(i18nc("@item:inmenu Create New", "%1", entry.text)); 00657 act->setActionGroup(m_newMenuGroup); 00658 00659 //kDebug() << templatePath << entry.filePath; 00660 00661 if (templatePath.endsWith("/URL.desktop")) { 00662 linkURL = act; 00663 } else if (templatePath.endsWith("/Program.desktop")) { 00664 linkApp = act; 00665 } else if (entry.filePath.endsWith("/linkPath.desktop")) { 00666 linkPath = act; 00667 } else if (KDesktopFile::isDesktopFile(templatePath)) { 00668 KDesktopFile df(templatePath); 00669 if (df.readType() == "FSDevice") 00670 m_menuDev->menu()->addAction(act); 00671 else 00672 menu->addAction(act); 00673 } 00674 else 00675 { 00676 menu->addAction(act); 00677 } 00678 } 00679 } 00680 } else { // Separate system from personal templates 00681 Q_ASSERT(entry.entryType != 0); 00682 00683 QAction *sep = new QAction(q); 00684 sep->setSeparator(true); 00685 menu->addAction(sep); 00686 } 00687 } 00688 00689 if (m_supportedMimeTypes.isEmpty()) { 00690 QAction *sep = new QAction(q); 00691 sep->setSeparator(true); 00692 menu->addAction(sep); 00693 if (linkURL) menu->addAction(linkURL); 00694 if (linkPath) menu->addAction(linkPath); 00695 if (linkApp) menu->addAction(linkApp); 00696 Q_ASSERT(m_menuDev); 00697 menu->addAction(m_menuDev); 00698 } 00699 } 00700 00701 void KNewFileMenuPrivate::_k_slotAbortDialog() 00702 { 00703 m_text = QString(); 00704 } 00705 00706 void KNewFileMenuPrivate::_k_slotActionTriggered(QAction* action) 00707 { 00708 q->trigger(); // was for kdesktop's slotNewMenuActivated() in kde3 times. Can't hurt to keep it... 00709 00710 if (action == m_newDirAction) { 00711 q->createDirectory(); 00712 return; 00713 } 00714 const int id = action->data().toInt(); 00715 Q_ASSERT(id > 0); 00716 00717 KNewFileMenuSingleton* s = kNewMenuGlobals; 00718 const KNewFileMenuSingleton::Entry entry = s->templatesList->at(id - 1); 00719 00720 const bool createSymlink = entry.templatePath == "__CREATE_SYMLINK__"; 00721 00722 m_strategy = KNewFileMenuStrategy(); 00723 00724 if (createSymlink) { 00725 m_strategy.m_isSymlink = true; 00726 executeSymLink(entry); 00727 } 00728 else if (KDesktopFile::isDesktopFile(entry.templatePath)) { 00729 KDesktopFile df(entry.templatePath); 00730 if (df.readType() == "Link") { 00731 executeUrlDesktopFile(entry); 00732 } else { // any other desktop file (Device, App, etc.) 00733 executeOtherDesktopFile(entry); 00734 } 00735 } 00736 else { 00737 executeRealFileOrDir(entry); 00738 } 00739 00740 } 00741 00742 void KNewFileMenuPrivate::_k_slotCreateDirectory(bool writeHiddenDir) 00743 { 00744 KUrl url; 00745 KUrl baseUrl = m_popupFiles.first(); 00746 bool askAgain = false; 00747 00748 QString name = expandTilde(m_text); 00749 00750 if (!name.isEmpty()) { 00751 if ((name[0] == '/')) 00752 url.setPath(name); 00753 else { 00754 if (!m_viewShowsHiddenFiles && name.startsWith('.')) { 00755 if (!writeHiddenDir) { 00756 confirmCreatingHiddenDir(name); 00757 return; 00758 } 00759 } 00760 name = KIO::encodeFileName( name ); 00761 url = baseUrl; 00762 url.addPath( name ); 00763 } 00764 } 00765 00766 if (!askAgain) { 00767 KIO::SimpleJob * job = KIO::mkdir(url); 00768 job->setProperty("isMkdirJob", true); // KDE5: cast to MkdirJob in slotResult instead 00769 job->ui()->setWindow(m_parentWidget); 00770 job->ui()->setAutoErrorHandlingEnabled(true); 00771 KIO::FileUndoManager::self()->recordJob( KIO::FileUndoManager::Mkdir, KUrl(), url, job ); 00772 00773 if (job) { 00774 // We want the error handling to be done by slotResult so that subclasses can reimplement it 00775 job->ui()->setAutoErrorHandlingEnabled(false); 00776 QObject::connect(job, SIGNAL(result(KJob *)), q, SLOT(slotResult(KJob *))); 00777 } 00778 } 00779 else { 00780 q->createDirectory(); // ask again for the name 00781 } 00782 _k_slotAbortDialog(); 00783 } 00784 00785 void KNewFileMenuPrivate::_k_slotCreateHiddenDirectory() 00786 { 00787 _k_slotCreateDirectory(true); 00788 } 00789 00790 void KNewFileMenuPrivate::_k_slotFillTemplates() 00791 { 00792 KNewFileMenuSingleton* s = kNewMenuGlobals; 00793 //kDebug(1203); 00794 // Ensure any changes in the templates dir will call this 00795 if (! s->dirWatch) { 00796 s->dirWatch = new KDirWatch; 00797 const QStringList dirs = m_actionCollection->componentData().dirs()->resourceDirs("templates"); 00798 for (QStringList::const_iterator it = dirs.constBegin() ; it != dirs.constEnd() ; ++it) { 00799 //kDebug(1203) << "Templates resource dir:" << *it; 00800 s->dirWatch->addDir(*it); 00801 } 00802 QObject::connect(s->dirWatch, SIGNAL(dirty(const QString &)), 00803 q, SLOT(_k_slotFillTemplates())); 00804 QObject::connect(s->dirWatch, SIGNAL(created(const QString &)), 00805 q, SLOT(_k_slotFillTemplates())); 00806 QObject::connect(s->dirWatch, SIGNAL(deleted(const QString &)), 00807 q, SLOT(_k_slotFillTemplates())); 00808 // Ok, this doesn't cope with new dirs in KDEDIRS, but that's another story 00809 } 00810 ++s->templatesVersion; 00811 s->filesParsed = false; 00812 00813 s->templatesList->clear(); 00814 00815 // Look into "templates" dirs. 00816 const QStringList files = m_actionCollection->componentData().dirs()->findAllResources("templates"); 00817 QMap<QString, KNewFileMenuSingleton::Entry> slist; // used for sorting 00818 Q_FOREACH(const QString& file, files) { 00819 //kDebug(1203) << file; 00820 if (file[0] != '.') { 00821 KNewFileMenuSingleton::Entry e; 00822 e.filePath = file; 00823 e.entryType = KNewFileMenuSingleton::Unknown; // not parsed yet 00824 00825 // Put Directory first in the list (a bit hacky), 00826 // and TextFile before others because it's the most used one. 00827 // This also sorts by user-visible name. 00828 // The rest of the re-ordering is done in fillMenu. 00829 const KDesktopFile config(file); 00830 QString key = config.desktopGroup().readEntry("Name"); 00831 if (file.endsWith("Directory.desktop")) { 00832 key.prepend('0'); 00833 } else if (file.endsWith("TextFile.desktop")) { 00834 key.prepend('1'); 00835 } else { 00836 key.prepend('2'); 00837 } 00838 slist.insert(key, e); 00839 } 00840 } 00841 (*s->templatesList) += slist.values(); 00842 } 00843 00844 void KNewFileMenuPrivate::_k_slotOtherDesktopFile() 00845 { 00846 executeStrategy(); 00847 } 00848 00849 void KNewFileMenuPrivate::_k_slotRealFileOrDir() 00850 { 00851 m_strategy.m_chosenFileName = m_text; 00852 _k_slotAbortDialog(); 00853 executeStrategy(); 00854 } 00855 00856 void KNewFileMenuPrivate::_k_slotSymLink() 00857 { 00858 KNameAndUrlInputDialog* dlg = static_cast<KNameAndUrlInputDialog*>(m_fileDialog); 00859 00860 m_strategy.m_chosenFileName = dlg->name(); // no path 00861 KUrl linkUrl = dlg->url(); // the url to put in the file 00862 00863 if (m_strategy.m_chosenFileName.isEmpty() || linkUrl.isEmpty()) 00864 return; 00865 00866 if (linkUrl.isRelative()) 00867 m_strategy.m_src = linkUrl.url(); 00868 else if (linkUrl.isLocalFile()) 00869 m_strategy.m_src = linkUrl.toLocalFile(); 00870 else { 00871 KDialog* dialog = new KDialog(m_parentWidget); 00872 dialog->setCaption( i18n("Sorry") ); 00873 dialog->setButtons( KDialog::Ok ); 00874 dialog->setObjectName( "sorry" ); 00875 dialog->setModal(m_modal); 00876 dialog->setAttribute(Qt::WA_DeleteOnClose); 00877 dialog->setDefaultButton( KDialog::Ok ); 00878 dialog->setEscapeButton( KDialog::Ok ); 00879 m_fileDialog = dialog; 00880 00881 KMessageBox::createKMessageBox(dialog, QMessageBox::Warning, 00882 i18n("Basic links can only point to local files or directories.\nPlease use \"Link to Location\" for remote URLs."), 00883 QStringList(), QString(), 0, KMessageBox::NoExec, 00884 QString()); 00885 00886 dialog->show(); 00887 return; 00888 } 00889 executeStrategy(); 00890 } 00891 00892 void KNewFileMenuPrivate::_k_slotTextChanged(const QString & text) 00893 { 00894 m_text = text; 00895 } 00896 00897 void KNewFileMenuPrivate::_k_slotUrlDesktopFile() 00898 { 00899 KNameAndUrlInputDialog* dlg = (KNameAndUrlInputDialog*) m_fileDialog; 00900 00901 m_strategy.m_chosenFileName = dlg->name(); // no path 00902 KUrl linkUrl = dlg->url(); 00903 00904 // Filter user input so that short uri entries, e.g. www.kde.org, are 00905 // handled properly. This not only makes the icon detection below work 00906 // properly, but opening the URL link where the short uri will not be 00907 // sent to the application (opening such link Konqueror fails). 00908 KUriFilterData uriData; 00909 uriData.setData(linkUrl); // the url to put in the file 00910 uriData.setCheckForExecutables(false); 00911 00912 if (KUriFilter::self()->filterUri(uriData, QStringList() << QLatin1String("kshorturifilter"))) { 00913 linkUrl = uriData.uri(); 00914 } 00915 00916 if (m_strategy.m_chosenFileName.isEmpty() || linkUrl.isEmpty()) 00917 return; 00918 00919 // It's a "URL" desktop file; we need to make a temp copy of it, to modify it 00920 // before copying it to the final destination [which could be a remote protocol] 00921 KTemporaryFile tmpFile; 00922 tmpFile.setAutoRemove(false); // done below 00923 if (!tmpFile.open()) { 00924 kError() << "Couldn't create temp file!"; 00925 return; 00926 } 00927 00928 if (!checkSourceExists(m_strategy.m_templatePath)) { 00929 return; 00930 } 00931 00932 // First copy the template into the temp file 00933 QFile file(m_strategy.m_templatePath); 00934 if (!file.open(QIODevice::ReadOnly)) { 00935 kError() << "Couldn't open template" << m_strategy.m_templatePath; 00936 return; 00937 } 00938 const QByteArray data = file.readAll(); 00939 tmpFile.write(data); 00940 const QString tempFileName = tmpFile.fileName(); 00941 Q_ASSERT(!tempFileName.isEmpty()); 00942 tmpFile.close(); 00943 file.close(); 00944 00945 KDesktopFile df(tempFileName); 00946 KConfigGroup group = df.desktopGroup(); 00947 group.writeEntry("Icon", KProtocolInfo::icon(linkUrl.protocol())); 00948 group.writePathEntry("URL", linkUrl.prettyUrl()); 00949 df.sync(); 00950 00951 m_strategy.m_src = tempFileName; 00952 m_strategy.m_tempFileToDelete = tempFileName; 00953 00954 executeStrategy(); 00955 } 00956 00957 KNewFileMenu::KNewFileMenu(KActionCollection* collection, const QString& name, QObject* parent) 00958 : KActionMenu(KIcon("document-new"), i18n("Create New"), parent), 00959 d(new KNewFileMenuPrivate(this)) 00960 { 00961 // Don't fill the menu yet 00962 // We'll do that in checkUpToDate (should be connected to aboutToShow) 00963 d->m_newMenuGroup = new QActionGroup(this); 00964 connect(d->m_newMenuGroup, SIGNAL(triggered(QAction*)), this, SLOT(_k_slotActionTriggered(QAction*))); 00965 d->m_actionCollection = collection; 00966 d->m_parentWidget = qobject_cast<QWidget*>(parent); 00967 d->m_newDirAction = 0; 00968 00969 d->m_actionCollection->addAction(name, this); 00970 00971 d->m_menuDev = new KActionMenu(KIcon("drive-removable-media"), i18n("Link to Device"), this); 00972 } 00973 00974 KNewFileMenu::~KNewFileMenu() 00975 { 00976 //kDebug(1203) << this; 00977 delete d; 00978 } 00979 00980 void KNewFileMenu::checkUpToDate() 00981 { 00982 KNewFileMenuSingleton* s = kNewMenuGlobals; 00983 //kDebug(1203) << this << "m_menuItemsVersion=" << d->m_menuItemsVersion 00984 // << "s->templatesVersion=" << s->templatesVersion; 00985 if (d->m_menuItemsVersion < s->templatesVersion || s->templatesVersion == 0) { 00986 //kDebug(1203) << "recreating actions"; 00987 // We need to clean up the action collection 00988 // We look for our actions using the group 00989 foreach (QAction* action, d->m_newMenuGroup->actions()) 00990 delete action; 00991 00992 if (!s->templatesList) { // No templates list up to now 00993 s->templatesList = new KNewFileMenuSingleton::EntryList; 00994 d->_k_slotFillTemplates(); 00995 s->parseFiles(); 00996 } 00997 00998 // This might have been already done for other popupmenus, 00999 // that's the point in s->filesParsed. 01000 if (!s->filesParsed) { 01001 s->parseFiles(); 01002 } 01003 01004 d->fillMenu(); 01005 01006 d->m_menuItemsVersion = s->templatesVersion; 01007 } 01008 } 01009 01010 void KNewFileMenu::createDirectory() 01011 { 01012 if (d->m_popupFiles.isEmpty()) 01013 return; 01014 01015 KUrl baseUrl = d->m_popupFiles.first(); 01016 QString name = d->m_text.isEmpty()? i18nc("Default name for a new folder", "New Folder") : 01017 d->m_text; 01018 01019 if (baseUrl.isLocalFile() && QFileInfo(baseUrl.toLocalFile(KUrl::AddTrailingSlash) + name).exists()) 01020 name = KIO::RenameDialog::suggestName(baseUrl, name); 01021 01022 KDialog* fileDialog = new KDialog(d->m_parentWidget); 01023 fileDialog->setModal(isModal()); 01024 fileDialog->setAttribute(Qt::WA_DeleteOnClose); 01025 fileDialog->setButtons(KDialog::Ok | KDialog::Cancel); 01026 fileDialog->setCaption(i18nc("@title:window", "New Folder")); 01027 01028 QWidget* mainWidget = new QWidget(fileDialog); 01029 QVBoxLayout *layout = new QVBoxLayout(mainWidget); 01030 QLabel *label = new QLabel(i18n("Create new folder in:\n%1", baseUrl.pathOrUrl())); 01031 01032 // We don't set the text of lineEdit in its constructor because the clear button would not be shown then. 01033 // It seems that setClearButtonShown(true) must be called *before* the text is set to make it work. 01034 // TODO: should probably be investigated and fixed in KLineEdit. 01035 KLineEdit *lineEdit = new KLineEdit; 01036 lineEdit->setClearButtonShown(true); 01037 lineEdit->setText(name); 01038 01039 d->_k_slotTextChanged(name); // have to save string in d->m_text in case user does not touch dialog 01040 connect(lineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(_k_slotTextChanged(const QString &))); 01041 layout->addWidget(label); 01042 layout->addWidget(lineEdit); 01043 01044 fileDialog->setMainWidget(mainWidget); 01045 connect(fileDialog, SIGNAL(accepted()), this, SLOT(_k_slotCreateDirectory())); 01046 connect(fileDialog, SIGNAL(rejected()), this, SLOT(_k_slotAbortDialog())); 01047 01048 d->m_fileDialog = fileDialog; 01049 01050 fileDialog->show(); 01051 lineEdit->selectAll(); 01052 lineEdit->setFocus(); 01053 } 01054 01055 bool KNewFileMenu::isModal() const 01056 { 01057 return d->m_modal; 01058 } 01059 01060 KUrl::List KNewFileMenu::popupFiles() const 01061 { 01062 return d->m_popupFiles; 01063 } 01064 01065 void KNewFileMenu::setModal(bool modal) 01066 { 01067 d->m_modal = modal; 01068 } 01069 01070 void KNewFileMenu::setPopupFiles(const KUrl::List& files) 01071 { 01072 d->m_popupFiles = files; 01073 if (files.isEmpty()) { 01074 d->m_newMenuGroup->setEnabled(false); 01075 } else { 01076 KUrl firstUrl = files.first(); 01077 if (KProtocolManager::supportsWriting(firstUrl)) { 01078 d->m_newMenuGroup->setEnabled(true); 01079 if (d->m_newDirAction) { 01080 d->m_newDirAction->setEnabled(KProtocolManager::supportsMakeDir(firstUrl)); // e.g. trash:/ 01081 } 01082 } else { 01083 d->m_newMenuGroup->setEnabled(true); 01084 } 01085 } 01086 } 01087 01088 01089 void KNewFileMenu::setParentWidget(QWidget* parentWidget) 01090 { 01091 d->m_parentWidget = parentWidget; 01092 } 01093 01094 void KNewFileMenu::setSupportedMimeTypes(const QStringList& mime) 01095 { 01096 d->m_supportedMimeTypes = mime; 01097 } 01098 01099 void KNewFileMenu::setViewShowsHiddenFiles(bool b) 01100 { 01101 d->m_viewShowsHiddenFiles = b; 01102 } 01103 01104 void KNewFileMenu::slotResult(KJob * job) 01105 { 01106 if (job->error()) { 01107 static_cast<KIO::Job*>(job)->ui()->showErrorMessage(); 01108 } else { 01109 // Was this a copy or a mkdir? 01110 KIO::CopyJob* copyJob = ::qobject_cast<KIO::CopyJob*>(job); 01111 if (copyJob) { 01112 const KUrl destUrl = copyJob->destUrl(); 01113 const KUrl localUrl = KIO::NetAccess::mostLocalUrl(destUrl, d->m_parentWidget); 01114 if (localUrl.isLocalFile()) { 01115 // Normal (local) file. Need to "touch" it, kio_file copied the mtime. 01116 (void) ::utime(QFile::encodeName(localUrl.toLocalFile()), 0); 01117 } 01118 emit fileCreated(destUrl); 01119 } else if (KIO::SimpleJob* simpleJob = ::qobject_cast<KIO::SimpleJob*>(job)) { 01120 // Can be mkdir or symlink 01121 if (simpleJob->property("isMkdirJob").toBool() == true) { 01122 kDebug() << "Emit directoryCreated" << simpleJob->url(); 01123 emit directoryCreated(simpleJob->url()); 01124 } else { 01125 emit fileCreated(simpleJob->url()); 01126 } 01127 } 01128 } 01129 if (!d->m_tempFileToDelete.isEmpty()) 01130 QFile::remove(d->m_tempFileToDelete); 01131 } 01132 01133 01134 QStringList KNewFileMenu::supportedMimeTypes() const 01135 { 01136 return d->m_supportedMimeTypes; 01137 } 01138 01139 01140 #include "knewfilemenu.moc" 01141
KDE 4.8 API Reference