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