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