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 text = text.trimmed(); // In some languages, there is a space in front of "...", see bug 268895 00434 00435 KUrl defaultFile(*it); 00436 defaultFile.addPath(KIO::encodeFileName(text)); 00437 if (defaultFile.isLocalFile() && QFile::exists(defaultFile.toLocalFile())) 00438 text = KIO::RenameDialog::suggestName(*it, text); 00439 00440 const KUrl templateUrl(entry.templatePath); 00441 00442 KDialog* dlg = new KPropertiesDialog(templateUrl, *it, text, m_parentWidget); 00443 dlg->setModal(q->isModal()); 00444 dlg->setAttribute(Qt::WA_DeleteOnClose); 00445 QObject::connect(dlg, SIGNAL(applied()), q, SLOT(_k_slotOtherDesktopFile())); 00446 dlg->show(); 00447 } 00448 // We don't set m_src here -> there will be no copy, we are done. 00449 } 00450 00451 void KNewFileMenuPrivate::executeRealFileOrDir(const KNewFileMenuSingleton::Entry& entry) 00452 { 00453 // The template is not a desktop file 00454 // Show the small dialog for getting the destination filename 00455 QString text = entry.text; 00456 text.remove("..."); // the ... is fine for the menu item but not for the default filename 00457 text = text.trimmed(); // In some languages, there is a space in front of "...", see bug 268895 00458 m_strategy.m_src = entry.templatePath; 00459 00460 KUrl defaultFile(m_popupFiles.first()); 00461 defaultFile.addPath(KIO::encodeFileName(text)); 00462 if (defaultFile.isLocalFile() && QFile::exists(defaultFile.toLocalFile())) 00463 text = KIO::RenameDialog::suggestName(m_popupFiles.first(), text); 00464 00465 KDialog* fileDialog = new KDialog(m_parentWidget); 00466 fileDialog->setAttribute(Qt::WA_DeleteOnClose); 00467 fileDialog->setModal(q->isModal()); 00468 fileDialog->setButtons(KDialog::Ok | KDialog::Cancel); 00469 00470 QWidget* mainWidget = new QWidget(fileDialog); 00471 QVBoxLayout *layout = new QVBoxLayout(mainWidget); 00472 QLabel *label = new QLabel(entry.comment); 00473 00474 // We don't set the text of lineEdit in its constructor because the clear button would not be shown then. 00475 // It seems that setClearButtonShown(true) must be called *before* the text is set to make it work. 00476 // TODO: should probably be investigated and fixed in KLineEdit. 00477 KLineEdit *lineEdit = new KLineEdit; 00478 lineEdit->setClearButtonShown(true); 00479 lineEdit->setText(text); 00480 00481 _k_slotTextChanged(text); 00482 QObject::connect(lineEdit, SIGNAL(textChanged(const QString &)), q, SLOT(_k_slotTextChanged(const QString &))); 00483 00484 layout->addWidget(label); 00485 layout->addWidget(lineEdit); 00486 00487 fileDialog->setMainWidget(mainWidget); 00488 QObject::connect(fileDialog, SIGNAL(accepted()), q, SLOT(_k_slotRealFileOrDir())); 00489 QObject::connect(fileDialog, SIGNAL(rejected()), q, SLOT(_k_slotAbortDialog())); 00490 00491 fileDialog->show(); 00492 lineEdit->selectAll(); 00493 lineEdit->setFocus(); 00494 } 00495 00496 void KNewFileMenuPrivate::executeSymLink(const KNewFileMenuSingleton::Entry& entry) 00497 { 00498 KNameAndUrlInputDialog* dlg = new KNameAndUrlInputDialog(i18n("File name:"), entry.comment, m_popupFiles.first(), m_parentWidget); 00499 dlg->setModal(q->isModal()); 00500 dlg->setAttribute(Qt::WA_DeleteOnClose); 00501 dlg->setCaption(i18n("Create Symlink")); 00502 m_fileDialog = dlg; 00503 QObject::connect(dlg, SIGNAL(accepted()), q, SLOT(_k_slotSymLink())); 00504 dlg->show(); 00505 } 00506 00507 void KNewFileMenuPrivate::executeStrategy() 00508 { 00509 m_tempFileToDelete = m_strategy.tempFileToDelete(); 00510 const QString src = m_strategy.sourceFileToCopy(); 00511 QString chosenFileName = expandTilde(m_strategy.chosenFileName(), true); 00512 00513 if (src.isEmpty()) 00514 return; 00515 KUrl uSrc(src); 00516 00517 if (uSrc.isLocalFile()) { 00518 // In case the templates/.source directory contains symlinks, resolve 00519 // them to the target files. Fixes bug #149628. 00520 KFileItem item(uSrc, QString(), KFileItem::Unknown); 00521 if (item.isLink()) 00522 uSrc.setPath(item.linkDest()); 00523 } 00524 00525 // The template is not a desktop file [or it's a URL one] 00526 // Copy it. 00527 KUrl::List::const_iterator it = m_popupFiles.constBegin(); 00528 for (; it != m_popupFiles.constEnd(); ++it) 00529 { 00530 KUrl dest(*it); 00531 dest.addPath(KIO::encodeFileName(chosenFileName)); 00532 00533 KUrl::List lstSrc; 00534 lstSrc.append(uSrc); 00535 KIO::Job* kjob; 00536 if (m_strategy.m_isSymlink) { 00537 kjob = KIO::symlink(src, dest); 00538 // This doesn't work, FileUndoManager registers new links in copyingLinkDone, 00539 // which KIO::symlink obviously doesn't emit... Needs code in FileUndoManager. 00540 //KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Link, lstSrc, dest, kjob); 00541 } else { 00542 //kDebug(1203) << "KIO::copyAs(" << uSrc.url() << "," << dest.url() << ")"; 00543 KIO::CopyJob * job = KIO::copyAs(uSrc, dest); 00544 job->setDefaultPermissions(true); 00545 kjob = job; 00546 KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Copy, lstSrc, dest, job); 00547 } 00548 kjob->ui()->setWindow(m_parentWidget); 00549 QObject::connect(kjob, SIGNAL(result(KJob*)), q, SLOT(slotResult(KJob*))); 00550 } 00551 00552 } 00553 00554 void KNewFileMenuPrivate::executeUrlDesktopFile(const KNewFileMenuSingleton::Entry& entry) 00555 { 00556 KNameAndUrlInputDialog* dlg = new KNameAndUrlInputDialog(i18n("File name:"), entry.comment, m_popupFiles.first(), m_parentWidget); 00557 m_strategy.m_templatePath = entry.templatePath; 00558 dlg->setModal(q->isModal()); 00559 dlg->setAttribute(Qt::WA_DeleteOnClose); 00560 dlg->setCaption(i18n("Create link to URL")); 00561 m_fileDialog = dlg; 00562 QObject::connect(dlg, SIGNAL(accepted()), q, SLOT(_k_slotUrlDesktopFile())); 00563 dlg->show(); 00564 } 00565 00566 void KNewFileMenuPrivate::fillMenu() 00567 { 00568 QMenu* menu = q->menu(); 00569 menu->clear(); 00570 m_menuDev->menu()->clear(); 00571 m_newDirAction = 0; 00572 00573 QSet<QString> seenTexts; 00574 // these shall be put at special positions 00575 QAction* linkURL = 0; 00576 QAction* linkApp = 0; 00577 QAction* linkPath = 0; 00578 00579 KNewFileMenuSingleton* s = kNewMenuGlobals; 00580 int i = 1; 00581 KNewFileMenuSingleton::EntryList::iterator templ = s->templatesList->begin(); 00582 const KNewFileMenuSingleton::EntryList::iterator templ_end = s->templatesList->end(); 00583 for (; templ != templ_end; ++templ, ++i) 00584 { 00585 KNewFileMenuSingleton::Entry& entry = *templ; 00586 if (entry.entryType != KNewFileMenuSingleton::Separator) { 00587 // There might be a .desktop for that one already, if it's a kdelnk 00588 // This assumes we read .desktop files before .kdelnk files ... 00589 00590 // In fact, we skip any second item that has the same text as another one. 00591 // Duplicates in a menu look bad in any case. 00592 00593 const bool bSkip = seenTexts.contains(entry.text); 00594 if (bSkip) { 00595 kDebug(1203) << "skipping" << entry.filePath; 00596 } else { 00597 seenTexts.insert(entry.text); 00598 //const KNewFileMenuSingleton::Entry entry = templatesList->at(i-1); 00599 00600 const QString templatePath = entry.templatePath; 00601 // The best way to identify the "Create Directory", "Link to Location", "Link to Application" was the template 00602 if (templatePath.endsWith("emptydir")) { 00603 QAction * act = new QAction(q); 00604 m_newDirAction = act; 00605 act->setIcon(KIcon(entry.icon)); 00606 act->setText(entry.text); 00607 act->setActionGroup(m_newMenuGroup); 00608 menu->addAction(act); 00609 00610 QAction *sep = new QAction(q); 00611 sep->setSeparator(true); 00612 menu->addAction(sep); 00613 } else { 00614 00615 if (!m_supportedMimeTypes.isEmpty()) { 00616 bool keep = false; 00617 00618 // We need to do mimetype filtering, for real files. 00619 const bool createSymlink = entry.templatePath == "__CREATE_SYMLINK__"; 00620 if (createSymlink) { 00621 keep = true; 00622 } else if (!KDesktopFile::isDesktopFile(entry.templatePath)) { 00623 00624 // Determine mimetype on demand 00625 KMimeType::Ptr mime; 00626 if (entry.mimeType.isEmpty()) { 00627 mime = KMimeType::findByPath(entry.templatePath); 00628 if (mime) { 00629 //kDebug() << entry.templatePath << "is" << mime->name(); 00630 entry.mimeType = mime->name(); 00631 } else { 00632 entry.mimeType = KMimeType::defaultMimeType(); 00633 } 00634 } else { 00635 mime = KMimeType::mimeType(entry.mimeType); 00636 } 00637 Q_FOREACH(const QString& supportedMime, m_supportedMimeTypes) { 00638 if (mime && mime->is(supportedMime)) { 00639 keep = true; 00640 break; 00641 } 00642 } 00643 } 00644 00645 if (!keep) { 00646 //kDebug() << "Not keeping" << entry.templatePath; 00647 continue; 00648 } 00649 } 00650 00651 QAction * act = new QAction(q); 00652 act->setData(i); 00653 act->setIcon(KIcon(entry.icon)); 00654 act->setText(entry.text); 00655 act->setActionGroup(m_newMenuGroup); 00656 00657 //kDebug() << templatePath << entry.filePath; 00658 00659 if (templatePath.endsWith("/URL.desktop")) { 00660 linkURL = act; 00661 } else if (templatePath.endsWith("/Program.desktop")) { 00662 linkApp = act; 00663 } else if (entry.filePath.endsWith("/linkPath.desktop")) { 00664 linkPath = act; 00665 } else if (KDesktopFile::isDesktopFile(templatePath)) { 00666 KDesktopFile df(templatePath); 00667 if (df.readType() == "FSDevice") 00668 m_menuDev->menu()->addAction(act); 00669 else 00670 menu->addAction(act); 00671 } 00672 else 00673 { 00674 menu->addAction(act); 00675 } 00676 } 00677 } 00678 } else { // Separate system from personal templates 00679 Q_ASSERT(entry.entryType != 0); 00680 00681 QAction *sep = new QAction(q); 00682 sep->setSeparator(true); 00683 menu->addAction(sep); 00684 } 00685 } 00686 00687 if (m_supportedMimeTypes.isEmpty()) { 00688 QAction *sep = new QAction(q); 00689 sep->setSeparator(true); 00690 menu->addAction(sep); 00691 if (linkURL) menu->addAction(linkURL); 00692 if (linkPath) menu->addAction(linkPath); 00693 if (linkApp) menu->addAction(linkApp); 00694 Q_ASSERT(m_menuDev); 00695 menu->addAction(m_menuDev); 00696 } 00697 } 00698 00699 void KNewFileMenuPrivate::_k_slotAbortDialog() 00700 { 00701 m_text = QString(); 00702 } 00703 00704 void KNewFileMenuPrivate::_k_slotActionTriggered(QAction* action) 00705 { 00706 q->trigger(); // was for kdesktop's slotNewMenuActivated() in kde3 times. Can't hurt to keep it... 00707 00708 if (action == m_newDirAction) { 00709 q->createDirectory(); 00710 return; 00711 } 00712 const int id = action->data().toInt(); 00713 Q_ASSERT(id > 0); 00714 00715 KNewFileMenuSingleton* s = kNewMenuGlobals; 00716 const KNewFileMenuSingleton::Entry entry = s->templatesList->at(id - 1); 00717 00718 const bool createSymlink = entry.templatePath == "__CREATE_SYMLINK__"; 00719 00720 m_strategy = KNewFileMenuStrategy(); 00721 00722 if (createSymlink) { 00723 m_strategy.m_isSymlink = true; 00724 executeSymLink(entry); 00725 } 00726 else if (KDesktopFile::isDesktopFile(entry.templatePath)) { 00727 KDesktopFile df(entry.templatePath); 00728 if (df.readType() == "Link") { 00729 executeUrlDesktopFile(entry); 00730 } else { // any other desktop file (Device, App, etc.) 00731 executeOtherDesktopFile(entry); 00732 } 00733 } 00734 else { 00735 executeRealFileOrDir(entry); 00736 } 00737 00738 } 00739 00740 void KNewFileMenuPrivate::_k_slotCreateDirectory(bool writeHiddenDir) 00741 { 00742 KUrl url; 00743 KUrl baseUrl = m_popupFiles.first(); 00744 bool askAgain = false; 00745 00746 QString name = expandTilde(m_text); 00747 00748 if (!name.isEmpty()) { 00749 if ((name[0] == '/')) 00750 url.setPath(name); 00751 else { 00752 if (!m_viewShowsHiddenFiles && name.startsWith('.')) { 00753 if (!writeHiddenDir) { 00754 confirmCreatingHiddenDir(name); 00755 return; 00756 } 00757 } 00758 name = KIO::encodeFileName( name ); 00759 url = baseUrl; 00760 url.addPath( name ); 00761 } 00762 } 00763 00764 if(!askAgain){ 00765 KIO::SimpleJob * job = KIO::mkdir(url); 00766 job->setProperty("isMkdirJob", true); // KDE5: cast to MkdirJob in slotResult instead 00767 job->ui()->setWindow(m_parentWidget); 00768 job->ui()->setAutoErrorHandlingEnabled(true); 00769 KIO::FileUndoManager::self()->recordJob( KIO::FileUndoManager::Mkdir, KUrl(), url, job ); 00770 00771 if (job) { 00772 // We want the error handling to be done by slotResult so that subclasses can reimplement it 00773 job->ui()->setAutoErrorHandlingEnabled(false); 00774 QObject::connect(job, SIGNAL(result(KJob *)), q, SLOT(slotResult(KJob *))); 00775 } 00776 } 00777 else { 00778 q->createDirectory(); // ask again for the name 00779 } 00780 _k_slotAbortDialog(); 00781 } 00782 00783 void KNewFileMenuPrivate::_k_slotCreateHiddenDirectory() 00784 { 00785 _k_slotCreateDirectory(true); 00786 } 00787 00788 void KNewFileMenuPrivate::_k_slotFillTemplates() 00789 { 00790 KNewFileMenuSingleton* s = kNewMenuGlobals; 00791 //kDebug(1203); 00792 // Ensure any changes in the templates dir will call this 00793 if (! s->dirWatch) { 00794 s->dirWatch = new KDirWatch; 00795 const QStringList dirs = m_actionCollection->componentData().dirs()->resourceDirs("templates"); 00796 for (QStringList::const_iterator it = dirs.constBegin() ; it != dirs.constEnd() ; ++it) { 00797 //kDebug(1203) << "Templates resource dir:" << *it; 00798 s->dirWatch->addDir(*it); 00799 } 00800 QObject::connect(s->dirWatch, SIGNAL(dirty(const QString &)), 00801 q, SLOT(_k_slotFillTemplates())); 00802 QObject::connect(s->dirWatch, SIGNAL(created(const QString &)), 00803 q, SLOT(_k_slotFillTemplates())); 00804 QObject::connect(s->dirWatch, SIGNAL(deleted(const QString &)), 00805 q, SLOT(_k_slotFillTemplates())); 00806 // Ok, this doesn't cope with new dirs in KDEDIRS, but that's another story 00807 } 00808 ++s->templatesVersion; 00809 s->filesParsed = false; 00810 00811 s->templatesList->clear(); 00812 00813 // Look into "templates" dirs. 00814 const QStringList files = m_actionCollection->componentData().dirs()->findAllResources("templates"); 00815 QMap<QString, KNewFileMenuSingleton::Entry> slist; // used for sorting 00816 Q_FOREACH(const QString& file, files) { 00817 //kDebug(1203) << file; 00818 if (file[0] != '.') { 00819 KNewFileMenuSingleton::Entry e; 00820 e.filePath = file; 00821 e.entryType = KNewFileMenuSingleton::Unknown; // not parsed yet 00822 00823 // Put Directory first in the list (a bit hacky), 00824 // and TextFile before others because it's the most used one. 00825 // This also sorts by user-visible name. 00826 // The rest of the re-ordering is done in fillMenu. 00827 const KDesktopFile config(file); 00828 QString key = config.desktopGroup().readEntry("Name"); 00829 if (file.endsWith("Directory.desktop")) { 00830 key.prepend('0'); 00831 } else if (file.endsWith("TextFile.desktop")) { 00832 key.prepend('1'); 00833 } else { 00834 key.prepend('2'); 00835 } 00836 slist.insert(key, e); 00837 } 00838 } 00839 (*s->templatesList) += slist.values(); 00840 } 00841 00842 void KNewFileMenuPrivate::_k_slotOtherDesktopFile() 00843 { 00844 executeStrategy(); 00845 } 00846 00847 void KNewFileMenuPrivate::_k_slotRealFileOrDir() 00848 { 00849 m_strategy.m_chosenFileName = m_text; 00850 _k_slotAbortDialog(); 00851 executeStrategy(); 00852 } 00853 00854 void KNewFileMenuPrivate::_k_slotSymLink() 00855 { 00856 KNameAndUrlInputDialog* dlg = static_cast<KNameAndUrlInputDialog*>(m_fileDialog); 00857 00858 m_strategy.m_chosenFileName = dlg->name(); // no path 00859 KUrl linkUrl = dlg->url(); // the url to put in the file 00860 00861 if (m_strategy.m_chosenFileName.isEmpty() || linkUrl.isEmpty()) 00862 return; 00863 00864 if (linkUrl.isRelative()) 00865 m_strategy.m_src = linkUrl.url(); 00866 else if (linkUrl.isLocalFile()) 00867 m_strategy.m_src = linkUrl.toLocalFile(); 00868 else { 00869 KDialog* dialog = new KDialog(m_parentWidget); 00870 dialog->setCaption( i18n("Sorry") ); 00871 dialog->setButtons( KDialog::Ok ); 00872 dialog->setObjectName( "sorry" ); 00873 dialog->setModal(m_modal); 00874 dialog->setAttribute(Qt::WA_DeleteOnClose); 00875 dialog->setDefaultButton( KDialog::Ok ); 00876 dialog->setEscapeButton( KDialog::Ok ); 00877 m_fileDialog = dialog; 00878 00879 KMessageBox::createKMessageBox(dialog, QMessageBox::Warning, 00880 i18n("Basic links can only point to local files or directories.\nPlease use \"Link to Location\" for remote URLs."), 00881 QStringList(), QString(), false, KMessageBox::NoExec, 00882 QString()); 00883 00884 dialog->show(); 00885 return; 00886 } 00887 executeStrategy(); 00888 } 00889 00890 void KNewFileMenuPrivate::_k_slotTextChanged(const QString & text) 00891 { 00892 m_text = text; 00893 } 00894 00895 void KNewFileMenuPrivate::_k_slotUrlDesktopFile() 00896 { 00897 KNameAndUrlInputDialog* dlg = (KNameAndUrlInputDialog*) m_fileDialog; 00898 00899 m_strategy.m_chosenFileName = dlg->name(); // no path 00900 KUrl linkUrl = dlg->url(); // the url to put in the file 00901 00902 if (m_strategy.m_chosenFileName.isEmpty() || linkUrl.isEmpty()) 00903 return; 00904 00905 // It's a "URL" desktop file; we need to make a temp copy of it, to modify it 00906 // before copying it to the final destination [which could be a remote protocol] 00907 KTemporaryFile tmpFile; 00908 tmpFile.setAutoRemove(false); // done below 00909 if (!tmpFile.open()) { 00910 kError() << "Couldn't create temp file!"; 00911 return; 00912 } 00913 00914 if (!checkSourceExists(m_strategy.m_templatePath)) { 00915 return; 00916 } 00917 00918 // First copy the template into the temp file 00919 QFile file(m_strategy.m_templatePath); 00920 if (!file.open(QIODevice::ReadOnly)) { 00921 kError() << "Couldn't open template" << m_strategy.m_templatePath; 00922 return; 00923 } 00924 const QByteArray data = file.readAll(); 00925 tmpFile.write(data); 00926 const QString tempFileName = tmpFile.fileName(); 00927 Q_ASSERT(!tempFileName.isEmpty()); 00928 tmpFile.close(); 00929 file.close(); 00930 00931 KDesktopFile df(tempFileName); 00932 KConfigGroup group = df.desktopGroup(); 00933 group.writeEntry("Icon", KProtocolInfo::icon(linkUrl.protocol())); 00934 group.writePathEntry("URL", linkUrl.prettyUrl()); 00935 df.sync(); 00936 00937 m_strategy.m_src = tempFileName; 00938 m_strategy.m_tempFileToDelete = tempFileName; 00939 00940 executeStrategy(); 00941 } 00942 00943 00944 KNewFileMenu::KNewFileMenu(KActionCollection* collection, const QString& name, QObject* parent) 00945 : KActionMenu(KIcon("document-new"), i18n("Create New"), parent), 00946 d(new KNewFileMenuPrivate(this)) 00947 { 00948 // Don't fill the menu yet 00949 // We'll do that in checkUpToDate (should be connected to aboutToShow) 00950 d->m_newMenuGroup = new QActionGroup(this); 00951 connect(d->m_newMenuGroup, SIGNAL(triggered(QAction*)), this, SLOT(_k_slotActionTriggered(QAction*))); 00952 d->m_actionCollection = collection; 00953 d->m_parentWidget = qobject_cast<QWidget*>(parent); 00954 d->m_newDirAction = 0; 00955 00956 d->m_actionCollection->addAction(name, this); 00957 00958 d->m_menuDev = new KActionMenu(KIcon("drive-removable-media"), i18n("Link to Device"), this); 00959 } 00960 00961 KNewFileMenu::~KNewFileMenu() 00962 { 00963 //kDebug(1203) << this; 00964 delete d; 00965 } 00966 00967 void KNewFileMenu::checkUpToDate() 00968 { 00969 KNewFileMenuSingleton* s = kNewMenuGlobals; 00970 //kDebug(1203) << this << "m_menuItemsVersion=" << d->m_menuItemsVersion 00971 // << "s->templatesVersion=" << s->templatesVersion; 00972 if (d->m_menuItemsVersion < s->templatesVersion || s->templatesVersion == 0) { 00973 //kDebug(1203) << "recreating actions"; 00974 // We need to clean up the action collection 00975 // We look for our actions using the group 00976 foreach (QAction* action, d->m_newMenuGroup->actions()) 00977 delete action; 00978 00979 if (!s->templatesList) { // No templates list up to now 00980 s->templatesList = new KNewFileMenuSingleton::EntryList; 00981 d->_k_slotFillTemplates(); 00982 s->parseFiles(); 00983 } 00984 00985 // This might have been already done for other popupmenus, 00986 // that's the point in s->filesParsed. 00987 if (!s->filesParsed) { 00988 s->parseFiles(); 00989 } 00990 00991 d->fillMenu(); 00992 00993 d->m_menuItemsVersion = s->templatesVersion; 00994 } 00995 } 00996 00997 void KNewFileMenu::createDirectory() 00998 { 00999 if (d->m_popupFiles.isEmpty()) 01000 return; 01001 01002 KUrl baseUrl = d->m_popupFiles.first(); 01003 QString name = d->m_text.isEmpty()? i18nc("Default name for a new folder", "New Folder") : 01004 d->m_text; 01005 01006 if (baseUrl.isLocalFile() && QFileInfo(baseUrl.toLocalFile(KUrl::AddTrailingSlash) + name).exists()) 01007 name = KIO::RenameDialog::suggestName(baseUrl, name); 01008 01009 KDialog* fileDialog = new KDialog(d->m_parentWidget); 01010 fileDialog->setModal(isModal()); 01011 fileDialog->setAttribute(Qt::WA_DeleteOnClose); 01012 fileDialog->setButtons(KDialog::Ok | KDialog::Cancel); 01013 fileDialog->setCaption(i18nc("@title:window", "New Folder")); 01014 01015 QWidget* mainWidget = new QWidget(fileDialog); 01016 QVBoxLayout *layout = new QVBoxLayout(mainWidget); 01017 QLabel *label = new QLabel(i18n("Create new folder in:\n%1", baseUrl.pathOrUrl())); 01018 01019 // We don't set the text of lineEdit in its constructor because the clear button would not be shown then. 01020 // It seems that setClearButtonShown(true) must be called *before* the text is set to make it work. 01021 // TODO: should probably be investigated and fixed in KLineEdit. 01022 KLineEdit *lineEdit = new KLineEdit; 01023 lineEdit->setClearButtonShown(true); 01024 lineEdit->setText(name); 01025 01026 d->_k_slotTextChanged(name); // have to save string in d->m_text in case user does not touch dialog 01027 connect(lineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(_k_slotTextChanged(const QString &))); 01028 layout->addWidget(label); 01029 layout->addWidget(lineEdit); 01030 01031 fileDialog->setMainWidget(mainWidget); 01032 connect(fileDialog, SIGNAL(accepted()), this, SLOT(_k_slotCreateDirectory())); 01033 connect(fileDialog, SIGNAL(rejected()), this, SLOT(_k_slotAbortDialog())); 01034 01035 d->m_fileDialog = fileDialog; 01036 01037 fileDialog->show(); 01038 lineEdit->selectAll(); 01039 lineEdit->setFocus(); 01040 } 01041 01042 bool KNewFileMenu::isModal() const 01043 { 01044 return d->m_modal; 01045 } 01046 01047 KUrl::List KNewFileMenu::popupFiles() const 01048 { 01049 return d->m_popupFiles; 01050 } 01051 01052 void KNewFileMenu::setModal(bool modal) 01053 { 01054 d->m_modal = modal; 01055 } 01056 01057 void KNewFileMenu::setPopupFiles(const KUrl::List& files) 01058 { 01059 d->m_popupFiles = files; 01060 if (files.isEmpty()) { 01061 d->m_newMenuGroup->setEnabled(false); 01062 } else { 01063 KUrl firstUrl = files.first(); 01064 if (KProtocolManager::supportsWriting(firstUrl)) { 01065 d->m_newMenuGroup->setEnabled(true); 01066 if (d->m_newDirAction) { 01067 d->m_newDirAction->setEnabled(KProtocolManager::supportsMakeDir(firstUrl)); // e.g. trash:/ 01068 } 01069 } else { 01070 d->m_newMenuGroup->setEnabled(true); 01071 } 01072 } 01073 } 01074 01075 01076 void KNewFileMenu::setParentWidget(QWidget* parentWidget) 01077 { 01078 d->m_parentWidget = parentWidget; 01079 } 01080 01081 void KNewFileMenu::setSupportedMimeTypes(const QStringList& mime) 01082 { 01083 d->m_supportedMimeTypes = mime; 01084 } 01085 01086 void KNewFileMenu::setViewShowsHiddenFiles(bool b) 01087 { 01088 d->m_viewShowsHiddenFiles = b; 01089 } 01090 01091 void KNewFileMenu::slotResult(KJob * job) 01092 { 01093 if (job->error()) { 01094 static_cast<KIO::Job*>(job)->ui()->showErrorMessage(); 01095 } else { 01096 // Was this a copy or a mkdir? 01097 KIO::CopyJob* copyJob = ::qobject_cast<KIO::CopyJob*>(job); 01098 if (copyJob) { 01099 const KUrl destUrl = copyJob->destUrl(); 01100 const KUrl localUrl = KIO::NetAccess::mostLocalUrl(destUrl, d->m_parentWidget); 01101 if (localUrl.isLocalFile()) { 01102 // Normal (local) file. Need to "touch" it, kio_file copied the mtime. 01103 (void) ::utime(QFile::encodeName(localUrl.toLocalFile()), 0); 01104 } 01105 emit fileCreated(destUrl); 01106 } else if (KIO::SimpleJob* simpleJob = ::qobject_cast<KIO::SimpleJob*>(job)) { 01107 // Can be mkdir or symlink 01108 if (simpleJob->property("isMkdirJob").toBool() == true) { 01109 kDebug() << "Emit directoryCreated" << simpleJob->url(); 01110 emit directoryCreated(simpleJob->url()); 01111 } else { 01112 emit fileCreated(simpleJob->url()); 01113 } 01114 } 01115 } 01116 if (!d->m_tempFileToDelete.isEmpty()) 01117 QFile::remove(d->m_tempFileToDelete); 01118 } 01119 01120 01121 QStringList KNewFileMenu::supportedMimeTypes() const 01122 { 01123 return d->m_supportedMimeTypes; 01124 } 01125 01126 01127 #include "knewfilemenu.moc" 01128
KDE 4.6 API Reference