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(), 0, 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 0, 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 // KDE5 TODO: remove the "..." from link*.desktop files and use i18n("%1...") when making 00435 // the action. 00436 00437 KUrl defaultFile(*it); 00438 defaultFile.addPath(KIO::encodeFileName(text)); 00439 if (defaultFile.isLocalFile() && QFile::exists(defaultFile.toLocalFile())) 00440 text = KIO::RenameDialog::suggestName(*it, text); 00441 00442 const KUrl templateUrl(entry.templatePath); 00443 00444 KDialog* dlg = new KPropertiesDialog(templateUrl, *it, text, m_parentWidget); 00445 dlg->setModal(q->isModal()); 00446 dlg->setAttribute(Qt::WA_DeleteOnClose); 00447 QObject::connect(dlg, SIGNAL(applied()), q, SLOT(_k_slotOtherDesktopFile())); 00448 dlg->show(); 00449 } 00450 // We don't set m_src here -> there will be no copy, we are done. 00451 } 00452 00453 void KNewFileMenuPrivate::executeRealFileOrDir(const KNewFileMenuSingleton::Entry& entry) 00454 { 00455 // The template is not a desktop file 00456 // Show the small dialog for getting the destination filename 00457 QString text = entry.text; 00458 text.remove("..."); // the ... is fine for the menu item but not for the default filename 00459 text = text.trimmed(); // In some languages, there is a space in front of "...", see bug 268895 00460 m_strategy.m_src = entry.templatePath; 00461 00462 KUrl defaultFile(m_popupFiles.first()); 00463 defaultFile.addPath(KIO::encodeFileName(text)); 00464 if (defaultFile.isLocalFile() && QFile::exists(defaultFile.toLocalFile())) 00465 text = KIO::RenameDialog::suggestName(m_popupFiles.first(), text); 00466 00467 KDialog* fileDialog = new KDialog(m_parentWidget); 00468 fileDialog->setAttribute(Qt::WA_DeleteOnClose); 00469 fileDialog->setModal(q->isModal()); 00470 fileDialog->setButtons(KDialog::Ok | KDialog::Cancel); 00471 00472 QWidget* mainWidget = new QWidget(fileDialog); 00473 QVBoxLayout *layout = new QVBoxLayout(mainWidget); 00474 QLabel *label = new QLabel(entry.comment); 00475 00476 // We don't set the text of lineEdit in its constructor because the clear button would not be shown then. 00477 // It seems that setClearButtonShown(true) must be called *before* the text is set to make it work. 00478 // TODO: should probably be investigated and fixed in KLineEdit. 00479 KLineEdit *lineEdit = new KLineEdit; 00480 lineEdit->setClearButtonShown(true); 00481 lineEdit->setText(text); 00482 00483 _k_slotTextChanged(text); 00484 QObject::connect(lineEdit, SIGNAL(textChanged(const QString &)), q, SLOT(_k_slotTextChanged(const QString &))); 00485 00486 layout->addWidget(label); 00487 layout->addWidget(lineEdit); 00488 00489 fileDialog->setMainWidget(mainWidget); 00490 QObject::connect(fileDialog, SIGNAL(accepted()), q, SLOT(_k_slotRealFileOrDir())); 00491 QObject::connect(fileDialog, SIGNAL(rejected()), q, SLOT(_k_slotAbortDialog())); 00492 00493 fileDialog->show(); 00494 lineEdit->selectAll(); 00495 lineEdit->setFocus(); 00496 } 00497 00498 void KNewFileMenuPrivate::executeSymLink(const KNewFileMenuSingleton::Entry& entry) 00499 { 00500 KNameAndUrlInputDialog* dlg = new KNameAndUrlInputDialog(i18n("File name:"), entry.comment, m_popupFiles.first(), m_parentWidget); 00501 dlg->setModal(q->isModal()); 00502 dlg->setAttribute(Qt::WA_DeleteOnClose); 00503 dlg->setCaption(i18n("Create Symlink")); 00504 m_fileDialog = dlg; 00505 QObject::connect(dlg, SIGNAL(accepted()), q, SLOT(_k_slotSymLink())); 00506 dlg->show(); 00507 } 00508 00509 void KNewFileMenuPrivate::executeStrategy() 00510 { 00511 m_tempFileToDelete = m_strategy.tempFileToDelete(); 00512 const QString src = m_strategy.sourceFileToCopy(); 00513 QString chosenFileName = expandTilde(m_strategy.chosenFileName(), true); 00514 00515 if (src.isEmpty()) 00516 return; 00517 KUrl uSrc(src); 00518 00519 if (uSrc.isLocalFile()) { 00520 // In case the templates/.source directory contains symlinks, resolve 00521 // them to the target files. Fixes bug #149628. 00522 KFileItem item(uSrc, QString(), KFileItem::Unknown); 00523 if (item.isLink()) 00524 uSrc.setPath(item.linkDest()); 00525 } 00526 00527 // The template is not a desktop file [or it's a URL one] 00528 // Copy it. 00529 KUrl::List::const_iterator it = m_popupFiles.constBegin(); 00530 for (; it != m_popupFiles.constEnd(); ++it) 00531 { 00532 KUrl dest(*it); 00533 dest.addPath(KIO::encodeFileName(chosenFileName)); 00534 00535 KUrl::List lstSrc; 00536 lstSrc.append(uSrc); 00537 KIO::Job* kjob; 00538 if (m_strategy.m_isSymlink) { 00539 kjob = KIO::symlink(src, dest); 00540 // This doesn't work, FileUndoManager registers new links in copyingLinkDone, 00541 // which KIO::symlink obviously doesn't emit... Needs code in FileUndoManager. 00542 //KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Link, lstSrc, dest, kjob); 00543 } else { 00544 //kDebug(1203) << "KIO::copyAs(" << uSrc.url() << "," << dest.url() << ")"; 00545 KIO::CopyJob * job = KIO::copyAs(uSrc, dest); 00546 job->setDefaultPermissions(true); 00547 kjob = job; 00548 KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Copy, lstSrc, dest, job); 00549 } 00550 kjob->ui()->setWindow(m_parentWidget); 00551 QObject::connect(kjob, SIGNAL(result(KJob*)), q, SLOT(slotResult(KJob*))); 00552 } 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(); // the url to put in the file 00903 00904 if (m_strategy.m_chosenFileName.isEmpty() || linkUrl.isEmpty()) 00905 return; 00906 00907 // It's a "URL" desktop file; we need to make a temp copy of it, to modify it 00908 // before copying it to the final destination [which could be a remote protocol] 00909 KTemporaryFile tmpFile; 00910 tmpFile.setAutoRemove(false); // done below 00911 if (!tmpFile.open()) { 00912 kError() << "Couldn't create temp file!"; 00913 return; 00914 } 00915 00916 if (!checkSourceExists(m_strategy.m_templatePath)) { 00917 return; 00918 } 00919 00920 // First copy the template into the temp file 00921 QFile file(m_strategy.m_templatePath); 00922 if (!file.open(QIODevice::ReadOnly)) { 00923 kError() << "Couldn't open template" << m_strategy.m_templatePath; 00924 return; 00925 } 00926 const QByteArray data = file.readAll(); 00927 tmpFile.write(data); 00928 const QString tempFileName = tmpFile.fileName(); 00929 Q_ASSERT(!tempFileName.isEmpty()); 00930 tmpFile.close(); 00931 file.close(); 00932 00933 KDesktopFile df(tempFileName); 00934 KConfigGroup group = df.desktopGroup(); 00935 group.writeEntry("Icon", KProtocolInfo::icon(linkUrl.protocol())); 00936 group.writePathEntry("URL", linkUrl.prettyUrl()); 00937 df.sync(); 00938 00939 m_strategy.m_src = tempFileName; 00940 m_strategy.m_tempFileToDelete = tempFileName; 00941 00942 executeStrategy(); 00943 } 00944 00945 00946 KNewFileMenu::KNewFileMenu(KActionCollection* collection, const QString& name, QObject* parent) 00947 : KActionMenu(KIcon("document-new"), i18n("Create New"), parent), 00948 d(new KNewFileMenuPrivate(this)) 00949 { 00950 // Don't fill the menu yet 00951 // We'll do that in checkUpToDate (should be connected to aboutToShow) 00952 d->m_newMenuGroup = new QActionGroup(this); 00953 connect(d->m_newMenuGroup, SIGNAL(triggered(QAction*)), this, SLOT(_k_slotActionTriggered(QAction*))); 00954 d->m_actionCollection = collection; 00955 d->m_parentWidget = qobject_cast<QWidget*>(parent); 00956 d->m_newDirAction = 0; 00957 00958 d->m_actionCollection->addAction(name, this); 00959 00960 d->m_menuDev = new KActionMenu(KIcon("drive-removable-media"), i18n("Link to Device"), this); 00961 } 00962 00963 KNewFileMenu::~KNewFileMenu() 00964 { 00965 //kDebug(1203) << this; 00966 delete d; 00967 } 00968 00969 void KNewFileMenu::checkUpToDate() 00970 { 00971 KNewFileMenuSingleton* s = kNewMenuGlobals; 00972 //kDebug(1203) << this << "m_menuItemsVersion=" << d->m_menuItemsVersion 00973 // << "s->templatesVersion=" << s->templatesVersion; 00974 if (d->m_menuItemsVersion < s->templatesVersion || s->templatesVersion == 0) { 00975 //kDebug(1203) << "recreating actions"; 00976 // We need to clean up the action collection 00977 // We look for our actions using the group 00978 foreach (QAction* action, d->m_newMenuGroup->actions()) 00979 delete action; 00980 00981 if (!s->templatesList) { // No templates list up to now 00982 s->templatesList = new KNewFileMenuSingleton::EntryList; 00983 d->_k_slotFillTemplates(); 00984 s->parseFiles(); 00985 } 00986 00987 // This might have been already done for other popupmenus, 00988 // that's the point in s->filesParsed. 00989 if (!s->filesParsed) { 00990 s->parseFiles(); 00991 } 00992 00993 d->fillMenu(); 00994 00995 d->m_menuItemsVersion = s->templatesVersion; 00996 } 00997 } 00998 00999 void KNewFileMenu::createDirectory() 01000 { 01001 if (d->m_popupFiles.isEmpty()) 01002 return; 01003 01004 KUrl baseUrl = d->m_popupFiles.first(); 01005 QString name = d->m_text.isEmpty()? i18nc("Default name for a new folder", "New Folder") : 01006 d->m_text; 01007 01008 if (baseUrl.isLocalFile() && QFileInfo(baseUrl.toLocalFile(KUrl::AddTrailingSlash) + name).exists()) 01009 name = KIO::RenameDialog::suggestName(baseUrl, name); 01010 01011 KDialog* fileDialog = new KDialog(d->m_parentWidget); 01012 fileDialog->setModal(isModal()); 01013 fileDialog->setAttribute(Qt::WA_DeleteOnClose); 01014 fileDialog->setButtons(KDialog::Ok | KDialog::Cancel); 01015 fileDialog->setCaption(i18nc("@title:window", "New Folder")); 01016 01017 QWidget* mainWidget = new QWidget(fileDialog); 01018 QVBoxLayout *layout = new QVBoxLayout(mainWidget); 01019 QLabel *label = new QLabel(i18n("Create new folder in:\n%1", baseUrl.pathOrUrl())); 01020 01021 // We don't set the text of lineEdit in its constructor because the clear button would not be shown then. 01022 // It seems that setClearButtonShown(true) must be called *before* the text is set to make it work. 01023 // TODO: should probably be investigated and fixed in KLineEdit. 01024 KLineEdit *lineEdit = new KLineEdit; 01025 lineEdit->setClearButtonShown(true); 01026 lineEdit->setText(name); 01027 01028 d->_k_slotTextChanged(name); // have to save string in d->m_text in case user does not touch dialog 01029 connect(lineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(_k_slotTextChanged(const QString &))); 01030 layout->addWidget(label); 01031 layout->addWidget(lineEdit); 01032 01033 fileDialog->setMainWidget(mainWidget); 01034 connect(fileDialog, SIGNAL(accepted()), this, SLOT(_k_slotCreateDirectory())); 01035 connect(fileDialog, SIGNAL(rejected()), this, SLOT(_k_slotAbortDialog())); 01036 01037 d->m_fileDialog = fileDialog; 01038 01039 fileDialog->show(); 01040 lineEdit->selectAll(); 01041 lineEdit->setFocus(); 01042 } 01043 01044 bool KNewFileMenu::isModal() const 01045 { 01046 return d->m_modal; 01047 } 01048 01049 KUrl::List KNewFileMenu::popupFiles() const 01050 { 01051 return d->m_popupFiles; 01052 } 01053 01054 void KNewFileMenu::setModal(bool modal) 01055 { 01056 d->m_modal = modal; 01057 } 01058 01059 void KNewFileMenu::setPopupFiles(const KUrl::List& files) 01060 { 01061 d->m_popupFiles = files; 01062 if (files.isEmpty()) { 01063 d->m_newMenuGroup->setEnabled(false); 01064 } else { 01065 KUrl firstUrl = files.first(); 01066 if (KProtocolManager::supportsWriting(firstUrl)) { 01067 d->m_newMenuGroup->setEnabled(true); 01068 if (d->m_newDirAction) { 01069 d->m_newDirAction->setEnabled(KProtocolManager::supportsMakeDir(firstUrl)); // e.g. trash:/ 01070 } 01071 } else { 01072 d->m_newMenuGroup->setEnabled(true); 01073 } 01074 } 01075 } 01076 01077 01078 void KNewFileMenu::setParentWidget(QWidget* parentWidget) 01079 { 01080 d->m_parentWidget = parentWidget; 01081 } 01082 01083 void KNewFileMenu::setSupportedMimeTypes(const QStringList& mime) 01084 { 01085 d->m_supportedMimeTypes = mime; 01086 } 01087 01088 void KNewFileMenu::setViewShowsHiddenFiles(bool b) 01089 { 01090 d->m_viewShowsHiddenFiles = b; 01091 } 01092 01093 void KNewFileMenu::slotResult(KJob * job) 01094 { 01095 if (job->error()) { 01096 static_cast<KIO::Job*>(job)->ui()->showErrorMessage(); 01097 } else { 01098 // Was this a copy or a mkdir? 01099 KIO::CopyJob* copyJob = ::qobject_cast<KIO::CopyJob*>(job); 01100 if (copyJob) { 01101 const KUrl destUrl = copyJob->destUrl(); 01102 const KUrl localUrl = KIO::NetAccess::mostLocalUrl(destUrl, d->m_parentWidget); 01103 if (localUrl.isLocalFile()) { 01104 // Normal (local) file. Need to "touch" it, kio_file copied the mtime. 01105 (void) ::utime(QFile::encodeName(localUrl.toLocalFile()), 0); 01106 } 01107 emit fileCreated(destUrl); 01108 } else if (KIO::SimpleJob* simpleJob = ::qobject_cast<KIO::SimpleJob*>(job)) { 01109 // Can be mkdir or symlink 01110 if (simpleJob->property("isMkdirJob").toBool() == true) { 01111 kDebug() << "Emit directoryCreated" << simpleJob->url(); 01112 emit directoryCreated(simpleJob->url()); 01113 } else { 01114 emit fileCreated(simpleJob->url()); 01115 } 01116 } 01117 } 01118 if (!d->m_tempFileToDelete.isEmpty()) 01119 QFile::remove(d->m_tempFileToDelete); 01120 } 01121 01122 01123 QStringList KNewFileMenu::supportedMimeTypes() const 01124 { 01125 return d->m_supportedMimeTypes; 01126 } 01127 01128 01129 #include "knewfilemenu.moc" 01130
KDE 4.7 API Reference