1 /* This file is part of the KDE project
2 Copyright (C) 1998, 1999 David Faure <faure@kde.org>
3 2003 Sven Leiber <s.leiber@web.de>
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License version 2 as published by the Free Software Foundation.
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301, USA.
21 #include "knewmenu_p.h"
22 #include "konq_operations.h"
25 #include <QVBoxLayout>
27 #include <kactioncollection.h>
29 #include <kdesktopfile.h>
30 #include <kdirwatch.h>
32 #include <kcomponentdata.h>
33 #include <kinputdialog.h>
35 #include <kmessagebox.h>
36 #include <kstandarddirs.h>
37 #include <kprotocolinfo.h>
38 #include <kprotocolmanager.h>
41 #include <kio/copyjob.h>
42 #include <kio/jobuidelegate.h>
43 #include <kio/renamedialog.h>
44 #include <kio/netaccess.h>
45 #include <kio/fileundomanager.h>
47 #include <kpropertiesdialog.h>
48 #include <ktemporaryfile.h>
51 // For KUrlDesktopFileDlg
53 #include <klineedit.h>
54 #include <kurlrequester.h>
57 // Singleton, with data shared by all KNewMenu instances
58 class KNewMenuSingleton
76 QString filePath
; // empty for SEPARATOR
77 QString templatePath
; // same as filePath for TEMPLATE
82 // NOTE: only filePath is known before we call parseFiles
85 * List of all template files. It is important that they are in
86 * the same order as the 'New' menu.
88 typedef QList
<Entry
> EntryList
;
89 EntryList
* templatesList
;
92 * Is increased when templatesList has been updated and
93 * menu needs to be re-filled. Menus have their own version and compare it
94 * to templatesVersion before showing up
99 * Set back to false each time new templates are found,
100 * and to true on the first call to parseFiles
103 KDirWatch
* dirWatch
;
106 K_GLOBAL_STATIC(KNewMenuSingleton
, kNewMenuGlobals
)
108 class KNewMenu::KNewMenuPrivate
112 : menuItemsVersion(0)
114 KActionCollection
* m_actionCollection
;
115 QWidget
*m_parentWidget
;
116 KActionMenu
*m_menuDev
;
117 QAction
* m_newDirAction
;
119 int menuItemsVersion
;
122 * When the user pressed the right mouse button over an URL a popup menu
123 * is displayed. The URL belonging to this popup menu is stored here.
125 KUrl::List popupFiles
;
128 * True when a desktop file with Type=URL is being copied
130 bool m_isUrlDesktopFile
;
131 QString m_tempFileToDelete
; // set when a tempfile was created for a Type=URL desktop file
134 * The action group that our actions belong to
136 QActionGroup
* m_newMenuGroup
;
139 KNewMenu::KNewMenu( KActionCollection
*parent
, QWidget
* parentWidget
, const QString
& name
)
140 : KActionMenu( KIcon("document-new"), i18n( "Create New" ), parentWidget
)
142 // Don't fill the menu yet
143 // We'll do that in slotCheckUpToDate (should be connected to aboutToShow)
144 d
= new KNewMenuPrivate
;
145 d
->m_newMenuGroup
= new QActionGroup(this);
146 connect(d
->m_newMenuGroup
, SIGNAL(triggered(QAction
*)), this, SLOT(slotActionTriggered(QAction
*)));
147 d
->m_actionCollection
= parent
;
148 d
->m_parentWidget
= parentWidget
;
150 d
->m_actionCollection
->addAction( name
, this );
155 KNewMenu::~KNewMenu()
157 //kDebug(1203) << "KNewMenu::~KNewMenu " << this;
161 void KNewMenu::makeMenus()
163 d
->m_menuDev
= new KActionMenu( KIcon("drive-removable-media"), i18n( "Link to Device" ), this );
166 void KNewMenu::slotCheckUpToDate( )
168 KNewMenuSingleton
* s
= kNewMenuGlobals
;
169 //kDebug(1203) << "KNewMenu::slotCheckUpToDate() " << this
170 // << " : menuItemsVersion=" << d->menuItemsVersion
171 // << " s->templatesVersion=" << s->templatesVersion;
172 if (d
->menuItemsVersion
< s
->templatesVersion
|| s
->templatesVersion
== 0) {
173 //kDebug(1203) << "KNewMenu::slotCheckUpToDate() : recreating actions";
174 // We need to clean up the action collection
175 // We look for our actions using the group
176 foreach (QAction
* action
, d
->m_newMenuGroup
->actions())
179 if (!s
->templatesList
) { // No templates list up to now
180 s
->templatesList
= new KNewMenuSingleton::EntryList
;
185 // This might have been already done for other popupmenus,
186 // that's the point in s->filesParsed.
187 if ( !s
->filesParsed
)
192 d
->menuItemsVersion
= s
->templatesVersion
;
196 void KNewMenu::parseFiles()
198 KNewMenuSingleton
* s
= kNewMenuGlobals
;
199 //kDebug(1203) << "KNewMenu::parseFiles()";
200 s
->filesParsed
= true;
201 KNewMenuSingleton::EntryList::iterator templ
= s
->templatesList
->begin();
202 const KNewMenuSingleton::EntryList::iterator templ_end
= s
->templatesList
->end();
203 for ( ; templ
!= templ_end
; ++templ
)
206 QString filePath
= (*templ
).filePath
;
207 if ( !filePath
.isEmpty() )
210 QString templatePath
;
211 // If a desktop file, then read the name from it.
212 // Otherwise (or if no name in it?) use file name
213 if ( KDesktopFile::isDesktopFile( filePath
) ) {
214 KDesktopFile
desktopFile( filePath
);
215 const KConfigGroup config
= desktopFile
.desktopGroup();
216 text
= config
.readEntry("Name");
217 (*templ
).icon
= config
.readEntry("Icon");
218 (*templ
).comment
= config
.readEntry("Comment");
219 QString type
= config
.readEntry( "Type" );
220 if ( type
== "Link" )
222 templatePath
= config
.readPathEntry("URL", QString());
223 if ( templatePath
[0] != '/' )
225 if ( templatePath
.startsWith("file:/") )
226 templatePath
= KUrl(templatePath
).path();
229 // A relative path, then (that's the default in the files we ship)
230 QString linkDir
= filePath
.left( filePath
.lastIndexOf( '/' ) + 1 /*keep / */ );
231 //kDebug(1203) << "linkDir=" << linkDir;
232 templatePath
= linkDir
+ templatePath
;
236 if ( templatePath
.isEmpty() )
238 // No dest, this is an old-style template
239 (*templ
).entryType
= TEMPLATE
;
240 (*templ
).templatePath
= (*templ
).filePath
; // we'll copy the file
242 (*templ
).entryType
= LINKTOTEMPLATE
;
243 (*templ
).templatePath
= templatePath
;
249 text
= KUrl(filePath
).fileName();
250 if ( text
.endsWith(".desktop") )
251 text
.truncate( text
.length() - 8 );
253 (*templ
).text
= text
;
254 /*kDebug(1203) << "Updating entry with text=" << text
255 << "entryType=" << (*templ).entryType
256 << "templatePath=" << (*templ).templatePath;*/
259 (*templ
).entryType
= SEPARATOR
;
264 void KNewMenu::fillMenu()
266 //kDebug(1203) << "KNewMenu::fillMenu()";
268 d
->m_menuDev
->menu()->clear();
269 d
->m_newDirAction
= 0;
271 QSet
<QString
> seenTexts
;
272 QAction
*linkURL
= 0, *linkApp
= 0; // these shall be put at special positions
274 KNewMenuSingleton
* s
= kNewMenuGlobals
;
276 KNewMenuSingleton::EntryList::const_iterator templ
= s
->templatesList
->constBegin();
277 const KNewMenuSingleton::EntryList::const_iterator templ_end
= s
->templatesList
->constEnd();
278 for ( ; templ
!= templ_end
; ++templ
, ++i
)
280 if ( (*templ
).entryType
!= SEPARATOR
)
282 // There might be a .desktop for that one already, if it's a kdelnk
283 // This assumes we read .desktop files before .kdelnk files ...
285 // In fact, we skip any second item that has the same text as another one.
286 // Duplicates in a menu look bad in any case.
288 bool bSkip
= seenTexts
.contains((*templ
).text
);
290 kDebug(1203) << "KNewMenu: skipping" << (*templ
).filePath
;
292 seenTexts
.insert((*templ
).text
);
293 const KNewMenuSingleton::Entry entry
= s
->templatesList
->at( i
-1 );
295 // The best way to identify the "Create Directory", "Link to Location", "Link to Application" was the template
296 if ( (*templ
).templatePath
.endsWith( "emptydir" ) )
298 QAction
* act
= new QAction( this );
299 d
->m_newDirAction
= act
;
300 act
->setIcon( KIcon((*templ
).icon
) );
301 act
->setText( (*templ
).text
);
302 act
->setActionGroup( d
->m_newMenuGroup
);
303 menu()->addAction( act
);
305 QAction
*sep
= new QAction(this);
306 sep
->setSeparator( true );
307 menu()->addAction( sep
);
311 QAction
* act
= new QAction(this);
313 act
->setIcon( KIcon((*templ
).icon
) );
314 act
->setText( (*templ
).text
);
315 act
->setActionGroup( d
->m_newMenuGroup
);
317 if ( (*templ
).templatePath
.endsWith( "URL.desktop" ) )
321 else if ( (*templ
).templatePath
.endsWith( "Program.desktop" ) )
325 else if ( KDesktopFile::isDesktopFile( entry
.templatePath
) )
327 KDesktopFile
df( entry
.templatePath
);
328 if(df
.readType() == "FSDevice")
329 d
->m_menuDev
->menu()->addAction( act
);
331 menu()->addAction( act
);
335 menu()->addAction( act
);
339 } else { // Separate system from personal templates
340 Q_ASSERT( (*templ
).entryType
!= 0 );
342 QAction
*sep
= new QAction( this );
343 sep
->setSeparator( true );
344 menu()->addAction( sep
);
348 QAction
*sep
= new QAction( this );
349 sep
->setSeparator( true );
350 menu()->addAction( sep
);
351 if ( linkURL
) menu()->addAction( linkURL
);
352 if ( linkApp
) menu()->addAction( linkApp
);
353 Q_ASSERT(d
->m_menuDev
);
354 menu()->addAction( d
->m_menuDev
);
358 void KNewMenu::slotFillTemplates()
360 KNewMenuSingleton
* s
= kNewMenuGlobals
;
361 //kDebug(1203) << "KNewMenu::slotFillTemplates()";
362 // Ensure any changes in the templates dir will call this
363 if ( ! s
->dirWatch
) {
364 s
->dirWatch
= new KDirWatch
;
365 const QStringList dirs
= d
->m_actionCollection
->componentData().dirs()->resourceDirs("templates");
366 for ( QStringList::const_iterator it
= dirs
.constBegin() ; it
!= dirs
.constEnd() ; ++it
) {
367 //kDebug(1203) << "Templates resource dir:" << *it;
368 s
->dirWatch
->addDir( *it
);
370 connect ( s
->dirWatch
, SIGNAL( dirty( const QString
& ) ),
371 this, SLOT ( slotFillTemplates() ) );
372 connect ( s
->dirWatch
, SIGNAL( created( const QString
& ) ),
373 this, SLOT ( slotFillTemplates() ) );
374 connect ( s
->dirWatch
, SIGNAL( deleted( const QString
& ) ),
375 this, SLOT ( slotFillTemplates() ) );
376 // Ok, this doesn't cope with new dirs in KDEDIRS, but that's another story
378 ++s
->templatesVersion
;
379 s
->filesParsed
= false;
381 s
->templatesList
->clear();
383 // Look into "templates" dirs.
384 const QStringList files
= d
->m_actionCollection
->componentData().dirs()->findAllResources("templates");
385 QMap
<QString
, KNewMenuSingleton::Entry
> slist
; // used for sorting
386 for ( QStringList::const_iterator it
= files
.constBegin() ; it
!= files
.constEnd() ; ++it
)
388 //kDebug(1203) << *it;
389 if ( (*it
)[0] != '.' )
391 KNewMenuSingleton::Entry e
;
393 e
.entryType
= 0; // not parsed yet
394 // put Directory etc. with special order (see fillMenu()) first in the list (a bit hacky)
395 if ( (*it
).endsWith( "Directory.desktop" ) ||
396 (*it
).endsWith( "linkProgram.desktop" ) ||
397 (*it
).endsWith( "linkURL.desktop" ) )
398 s
->templatesList
->prepend( e
);
401 KDesktopFile
config( *it
);
403 // tricky solution to ensure that TextFile is at the beginning
404 // because this filetype is the most used (according kde-core discussion)
405 QString key
= config
.desktopGroup().readEntry("Name");
406 if ( (*it
).endsWith( "TextFile.desktop" ) )
411 slist
.insert( key
, e
);
415 (*s
->templatesList
) += slist
.values();
418 void KNewMenu::newDir()
420 if (d
->popupFiles
.isEmpty())
423 KIO::SimpleJob
* job
= KonqOperations::newDir(d
->m_parentWidget
, d
->popupFiles
.first());
425 // We want the error handling to be done by slotResult so that subclasses can reimplement it
426 job
->ui()->setAutoErrorHandlingEnabled(false);
427 connect( job
, SIGNAL( result( KJob
* ) ),
428 SLOT( slotResult( KJob
* ) ) );
433 void KNewMenu::slotActionTriggered(QAction
* action
)
435 trigger(); // for KDIconView::slotNewMenuActivated()
437 if (action
== d
->m_newDirAction
) {
441 const int id
= action
->data().toInt();
444 KNewMenuSingleton
* s
= kNewMenuGlobals
;
445 const KNewMenuSingleton::Entry entry
= s
->templatesList
->at( id
- 1 );
446 //kDebug(1203) << "sFile=" << sFile;
448 if ( !QFile::exists( entry
.templatePath
) ) {
449 kWarning(1203) << entry
.templatePath
<< "doesn't exist" ;
450 KMessageBox::sorry( 0L, i18n("<qt>The template file <b>%1</b> does not exist.</qt>", entry
.templatePath
));
454 // true when a desktop file with Type=URL is being copied
455 d
->m_isUrlDesktopFile
= false;
456 KUrl linkUrl
; // the url to put in the file
459 if ( KDesktopFile::isDesktopFile( entry
.templatePath
) )
461 KDesktopFile
df( entry
.templatePath
);
462 //kDebug(1203) << df.readType();
463 if ( df
.readType() == "Link" )
465 d
->m_isUrlDesktopFile
= true;
466 // entry.comment contains i18n("Enter link to location (URL):"). JFYI :)
467 KUrlDesktopFileDlg
dlg( i18n("File name:"), entry
.comment
, d
->m_parentWidget
);
468 // TODO dlg.setCaption( i18n( ... ) );
471 name
= dlg
.fileName();
473 if ( name
.isEmpty() || linkUrl
.isEmpty() )
475 if ( !name
.endsWith( ".desktop" ) )
481 else // any other desktop file (Device, App, etc.)
483 KUrl::List::Iterator it
= d
->popupFiles
.begin();
484 for ( ; it
!= d
->popupFiles
.end(); ++it
)
486 //kDebug(1203) << "first arg=" << entry.templatePath;
487 //kDebug(1203) << "second arg=" << (*it).url();
488 //kDebug(1203) << "third arg=" << entry.text;
489 QString text
= entry
.text
;
490 text
.replace( "...", QString() ); // the ... is fine for the menu item but not for the default filename
492 KUrl
defaultFile( *it
);
493 defaultFile
.addPath( KIO::encodeFileName( text
) );
494 if ( defaultFile
.isLocalFile() && QFile::exists( defaultFile
.path() ) )
495 text
= KIO::RenameDialog::suggestName( *it
, text
);
497 KUrl
templateUrl( entry
.templatePath
);
498 KPropertiesDialog
dlg( templateUrl
, *it
, text
, d
->m_parentWidget
);
501 return; // done, exit.
506 // The template is not a desktop file
507 // Show the small dialog for getting the destination filename
509 QString text
= entry
.text
;
510 text
.replace( "...", QString() ); // the ... is fine for the menu item but not for the default filename
512 KUrl
defaultFile( *(d
->popupFiles
.begin()) );
513 defaultFile
.addPath( KIO::encodeFileName( text
) );
514 if ( defaultFile
.isLocalFile() && QFile::exists( defaultFile
.path() ) )
515 text
= KIO::RenameDialog::suggestName( *(d
->popupFiles
.begin()), text
);
517 name
= KInputDialog::getText( QString(), entry
.comment
,
518 text
, &ok
, d
->m_parentWidget
);
523 QString src
= entry
.templatePath
;
525 if (d
->m_isUrlDesktopFile
) {
526 // It's a "URL" desktop file; we need to make a temp copy of it, to modify it
527 // before copying it to the final destination [which could be a remote protocol]
528 KTemporaryFile tmpFile
;
529 tmpFile
.setAutoRemove(false); // done below
530 if (!tmpFile
.open()) {
531 kError() << "Couldn't create temp file!";
534 // First copy the template into the temp file
536 if (!file
.open(QIODevice::ReadOnly
)) {
537 kError() << "Couldn't open template" << src
;
540 const QByteArray data
= file
.readAll();
542 const QString tempFileName
= tmpFile
.fileName();
543 Q_ASSERT(!tempFileName
.isEmpty());
546 KDesktopFile
df(tempFileName
);
547 KConfigGroup group
= df
.desktopGroup();
548 group
.writeEntry("Icon", KProtocolInfo::icon(linkUrl
.protocol()));
549 group
.writePathEntry("URL", linkUrl
.prettyUrl());
552 d
->m_tempFileToDelete
= tempFileName
;
555 // The template is not a desktop file [or it's a URL one]
557 KUrl::List::const_iterator it
= d
->popupFiles
.constBegin();
558 for ( ; it
!= d
->popupFiles
.constEnd(); ++it
)
561 dest
.addPath( KIO::encodeFileName(name
) ); // Chosen destination file name
564 //kDebug(1203) << "KNewMenu : KIO::copyAs(" << uSrc.url() << "," << dest.url() << ")";
565 KIO::CopyJob
* job
= KIO::copyAs( uSrc
, dest
);
566 job
->setDefaultPermissions( true );
567 job
->ui()->setWindow( d
->m_parentWidget
);
568 connect( job
, SIGNAL( result( KJob
* ) ),
569 SLOT( slotResult( KJob
* ) ) );
572 KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Copy
, lst
, dest
, job
);
576 void KNewMenu::slotResult( KJob
* job
)
579 static_cast<KIO::Job
*>( job
)->ui()->showErrorMessage();
581 // Was this a copy or a mkdir?
582 KIO::CopyJob
* copyJob
= ::qobject_cast
<KIO::CopyJob
*>(job
);
584 const KUrl destUrl
= copyJob
->destUrl();
585 const KUrl localUrl
= KIO::NetAccess::mostLocalUrl(destUrl
, d
->m_parentWidget
);
586 if (localUrl
.isLocalFile()) {
587 // Normal (local) file. Need to "touch" it, kio_file copied the mtime.
588 (void) ::utime(QFile::encodeName(localUrl
.path()), 0);
592 if (!d
->m_tempFileToDelete
.isEmpty())
593 QFile::remove(d
->m_tempFileToDelete
);
596 void KNewMenu::setPopupFiles(const KUrl::List
& files
)
598 d
->popupFiles
= files
;
599 if (files
.isEmpty()) {
600 d
->m_newMenuGroup
->setEnabled(false);
602 KUrl firstUrl
= files
.first();
603 if (KProtocolManager::supportsWriting(firstUrl
)) {
604 d
->m_newMenuGroup
->setEnabled(true);
605 if (d
->m_newDirAction
) {
606 d
->m_newDirAction
->setEnabled(KProtocolManager::supportsMakeDir(firstUrl
)); // e.g. trash:/
609 d
->m_newMenuGroup
->setEnabled(true);
616 KUrlDesktopFileDlg::KUrlDesktopFileDlg( const QString
& textFileName
, const QString
& textUrl
, QWidget
*parent
)
619 setButtons( Ok
| Cancel
| User1
);
620 setButtonGuiItem( User1
, KStandardGuiItem::clear() );
621 showButtonSeparator( true );
623 initDialog( textFileName
, QString(), textUrl
, QString() );
626 void KUrlDesktopFileDlg::initDialog( const QString
& textFileName
, const QString
& defaultName
, const QString
& textUrl
, const QString
& defaultUrl
)
628 QFrame
*plainPage
= new QFrame( this );
629 setMainWidget( plainPage
);
631 QVBoxLayout
* topLayout
= new QVBoxLayout( plainPage
);
632 topLayout
->setMargin( 0 );
633 topLayout
->setSpacing( spacingHint() );
635 // First line: filename
636 KHBox
* fileNameBox
= new KHBox( plainPage
);
637 topLayout
->addWidget( fileNameBox
);
639 QLabel
* label
= new QLabel( textFileName
, fileNameBox
);
640 m_leFileName
= new KLineEdit( fileNameBox
);
641 m_leFileName
->setMinimumWidth(m_leFileName
->sizeHint().width() * 3);
642 label
->setBuddy(m_leFileName
); // please "scheck" style
643 m_leFileName
->setText( defaultName
);
644 m_leFileName
->setSelection(0, m_leFileName
->text().length()); // autoselect
645 connect( m_leFileName
, SIGNAL(textChanged(const QString
&)),
646 SLOT(slotNameTextChanged(const QString
&)) );
649 KHBox
* urlBox
= new KHBox( plainPage
);
650 topLayout
->addWidget( urlBox
);
651 label
= new QLabel( textUrl
, urlBox
);
652 m_urlRequester
= new KUrlRequester( defaultUrl
, urlBox
);
653 m_urlRequester
->setMode( KFile::File
| KFile::Directory
);
655 m_urlRequester
->setMinimumWidth( m_urlRequester
->sizeHint().width() * 3 );
656 connect( m_urlRequester
->lineEdit(), SIGNAL(textChanged(const QString
&)),
657 SLOT(slotURLTextChanged(const QString
&)) );
658 label
->setBuddy(m_urlRequester
); // please "scheck" style
660 m_urlRequester
->setFocus();
661 enableButtonOk( !defaultName
.isEmpty() && !defaultUrl
.isEmpty() );
662 connect( this, SIGNAL(user1Clicked()), this, SLOT(slotClear()) );
663 m_fileNameEdited
= false;
666 KUrl
KUrlDesktopFileDlg::url() const
668 if ( result() == QDialog::Accepted
)
669 return m_urlRequester
->url();
674 QString
KUrlDesktopFileDlg::fileName() const
676 if ( result() == QDialog::Accepted
)
677 return m_leFileName
->text();
682 void KUrlDesktopFileDlg::slotClear()
684 m_leFileName
->clear();
685 m_urlRequester
->clear();
686 m_fileNameEdited
= false;
689 void KUrlDesktopFileDlg::slotNameTextChanged( const QString
& )
692 m_fileNameEdited
= true;
693 enableButtonOk( !m_leFileName
->text().isEmpty() && !m_urlRequester
->url().isEmpty() );
696 void KUrlDesktopFileDlg::slotURLTextChanged( const QString
& )
698 if ( !m_fileNameEdited
)
700 // use URL as default value for the filename
701 // (we copy only its filename if protocol supports listing,
702 // but for HTTP we don't want tons of index.html links)
703 KUrl
url( m_urlRequester
->url() );
704 if (KProtocolManager::supportsListing(url
) && !url
.fileName().isEmpty())
705 m_leFileName
->setText( url
.fileName() );
707 m_leFileName
->setText( url
.url() );
708 m_fileNameEdited
= false; // slotNameTextChanged set it to true erroneously
710 enableButtonOk( !m_leFileName
->text().isEmpty() && !m_urlRequester
->url().isEmpty() );
714 #include "knewmenu.moc"
715 #include "knewmenu_p.moc"