1/*
2 This program is free software; you can redistribute it and/or
3 modify it under the terms of the GNU General Public License
4 as published by the Free Software Foundation; either version 2
5 of the License, or (at your option) any later version.
6
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY; without even the implied warranty of
9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 GNU General Public License for more details.
11
12 You should have received a copy of the GNU General Public License
13 along with this program; if not, write to the Free Software
14 Foundation, Inc., 51 Franklin Street, Fifth Floor,
15 Boston, MA 02110-1301, USA.
16
17 ---
18 Copyright (C) 2004, Anders Lund <anders@alweb.dk>
19 */
20
21//BEGIN Includes
22#include "filetemplates.h"
23
24
25#include <kaboutdata.h>
26#include <kaction.h>
27#include <kactionmenu.h>
28#include <kactioncollection.h>
29#include <kcombobox.h>
30#include <kconfig.h>
31#include <kdialog.h>
32#include <kdirwatch.h>
33#include <kfiledialog.h>
34#include <kglobal.h>
35#include <kicondialog.h>
36#include <kiconloader.h>
37#include <kio/netaccess.h>
38#include <klineedit.h>
39#include <klocale.h>
40#include <kmessagebox.h>
41#include <kstandarddirs.h>
42#include <ktemporaryfile.h>
43#include <kurlrequester.h>
44#include <kuser.h>
45#include <kxmlguifactory.h>
46
47#include <qbuttongroup.h>
48#include <qcheckbox.h>
49#include <qcursor.h>
50#include <qdatetime.h>
51#include <qlabel.h>
52#include <qlayout.h>
53#include <qpushbutton.h>
54#include <qradiobutton.h>
55#include <qregexp.h>
56#include <qstyle.h>
57//Added by qt3to4:
58#include <QTextStream>
59#include <QGridLayout>
60#include <QVBoxLayout>
61#include <QStyle>
62#include <QApplication>
63#include <khbox.h>
64
65#include <QWizardPage>
66#include <QTreeWidget>
67
68#include <stdlib.h>
69
70#include <kdebug.h>
71#include <ktexteditor/templateinterface.h>
72#include <krecentfilesaction.h>
73
74#include <kpluginfactory.h>
75#include <kpluginloader.h>
76#include <kaboutdata.h>
77#include <kmenu.h>
78//END Includes
79
80//BEGIN plugin + factory stuff
81
82K_PLUGIN_FACTORY(KateFileTemplatesFactory, registerPlugin<KateFileTemplates>();)
83K_EXPORT_PLUGIN(KateFileTemplatesFactory(KAboutData("katefiletemplates","katefiletemplates",ki18n("File Templates"), "0.1", ki18n("Create files from templates"), KAboutData::License_LGPL_V2)) )
84
85//END
86
87//BEGIN PluginViewKateFileTemplates
88PluginViewKateFileTemplates::PluginViewKateFileTemplates(KateFileTemplates *plugin, Kate::MainWindow *mainwindow):
89 Kate::PluginView(mainwindow),Kate::XMLGUIClient(KateFileTemplatesFactory::componentData()),m_plugin(plugin)
90{
91 QAction *a = actionCollection()->addAction("settings_manage_templates");
92 a->setText(i18n("&Manage Templates..."));
93 connect( a, SIGNAL(triggered(bool)), plugin, SLOT(slotEditTemplate()) );
94
95 a=new KActionMenu( i18n("New From &Template"), actionCollection());
96 actionCollection()->addAction("file_new_fromtemplate",a);
97 refreshMenu();
98
99 mainwindow->guiFactory()->addClient (this);
100
101}
102
103void PluginViewKateFileTemplates::refreshMenu()
104{
105 m_plugin->refreshMenu( (static_cast<KActionMenu*>(actionCollection()->action("file_new_fromtemplate")))->menu());
106}
107
108PluginViewKateFileTemplates::~PluginViewKateFileTemplates()
109{
110 mainWindow()->guiFactory()->removeClient (this);
111}
112//END PluginViewKateFileTemplates
113
114//BEGIN TemplateInfo
115class TemplateInfo
116{
117 public:
118 TemplateInfo( const QString& fn, const QString &t, const QString &g )
119 : filename( fn ), tmplate ( t ), group( g ) { ; }
120 ~TemplateInfo() { ; }
121
122 QString filename;
123 QString tmplate;
124 QString group;
125 QString description;
126 QString author;
127 QString highlight;
128 QString icon;
129};
130Q_DECLARE_METATYPE(TemplateInfo*)
131//END TemplateInfo
132
133//BEGIN KateFileTemplates
134KateFileTemplates::KateFileTemplates( QObject* parent, const QList<QVariant> &dummy)
135 : Kate::Plugin ( (Kate::Application*)parent)
136{
137 Q_UNUSED(dummy)
138// // create actions, so that they are shared.
139// // We plug them into each view's menus, and update them centrally, so that
140// // new plugins can automatically become visible in all windows.
141 mActionAny = new KAction ( i18n("Any File..."), this );
142 connect( mActionAny, SIGNAL(triggered(bool)), this, SLOT(slotAny()) );
143
144
145 // template menu
146 m_dw = new KDirWatch( this);
147 m_dw->setObjectName( "template_dirwatch" );
148 const QStringList dirs = KGlobal::dirs()->findDirs("data", "kate/plugins/katefiletemplates/templates");
149 for ( QStringList::const_iterator it = dirs.begin(); it != dirs.end(); ++it )
150 {
151 m_dw->addDir( *it, KDirWatch::WatchFiles );
152 }
153
154 connect( m_dw, SIGNAL(dirty(QString)),
155 this, SLOT(updateTemplateDirs(QString)) );
156 connect( m_dw, SIGNAL(created(QString)),
157 this, SLOT(updateTemplateDirs(QString)) );
158 connect( m_dw, SIGNAL(deleted(QString)),
159 this, SLOT(updateTemplateDirs(QString)) );
160
161// m_templates.setAutoDelete( true );
162 updateTemplateDirs();
163
164 m_user = 0;
165 m_emailstuff = 0;
166}
167
168/**
169 * Called whenever the template dir is changed. Recreates the templates list.
170 */
171void KateFileTemplates::updateTemplateDirs(const QString &d)
172{
173 kDebug()<<"updateTemplateDirs called with arg "<<d;
174
175 const QStringList templates = KGlobal::dirs()->findAllResources(
176 "data","kate/plugins/katefiletemplates/templates/*.katetemplate",
177 KStandardDirs::NoDuplicates);
178
179 m_templates.clear();
180
181 QRegExp re( "\\b(\\w+)\\s*=\\s*(.+)(?:\\s+\\w+=|$)" );
182 re.setMinimal( true );
183
184 KConfigGroup cg( KGlobal::config(), "KateFileTemplates" );
185 QStringList hidden;
186 cg.readXdgListEntry( "Hidden", hidden ); // XXX this is bogus
187
188 for ( QStringList::const_iterator it=templates.begin(); it != templates.end(); ++it )
189 {
190 QFile _f( *it );
191 if ( _f.open( QIODevice::ReadOnly ) )
192 {
193 QString fname = (*it).section( '/', -1 );
194
195 // skip if hidden
196 if ( hidden.contains( fname ) )
197 continue;
198
199 // Read the first line of the file, to get the group/name
200 TemplateInfo *tmp = new TemplateInfo( *it, fname, i18nc( "@item:inmenu", "Other" ) );
201 bool trymore ( true );
202 QTextStream stream(&_f);
203 while ( trymore )
204 {
205 QString _line = stream.readLine();
206 trymore = _line.startsWith( "katetemplate:" );
207 if ( ! trymore ) break;
208
209 int pos ( 0 );
210 while ( ( ( pos = re.indexIn( _line, pos ) ) >= 0 ) )
211 {
212 pos += re.cap( 1 ).length();
213 if ( re.cap( 1 ).toLower() == "template" )
214 tmp->tmplate = i18nc( "@item:inmenu", re.cap( 2 ).toUtf8() );
215 if ( re.cap( 1 ).toLower() == "group" )
216 tmp->group = i18nc( "@item:inmenu", re.cap( 2 ).toUtf8() );
217 if ( re.cap( 1 ).toLower() == "description" )
218 tmp->description = i18nc( "@info:whatsthis", re.cap( 2 ).toUtf8() );
219 if ( re.cap( 1 ).toLower() == "author" )
220 tmp->author = i18nc( "@info:credit", re.cap( 2 ).toUtf8() );
221 if ( re.cap( 1 ).toLower() == "highlight" )
222 tmp->highlight = re.cap( 2 );
223 if ( re.cap( 1 ) == "icon" )
224 tmp->icon = re.cap( 2 );
225 }
226 }
227
228 m_templates.append( tmp );
229 _f.close();
230 }
231 }
232
233 emit triggerMenuRefresh();
234
235}
236
237KateFileTemplates::~KateFileTemplates()
238{
239// m_acRecentTemplates->saveEntries( KGlobal::config(), "Recent Templates" );
240 delete m_emailstuff;
241 delete m_user;
242}
243
244Kate::PluginView *KateFileTemplates::createView (Kate::MainWindow *mainWindow)
245{
246 PluginViewKateFileTemplates *view=new PluginViewKateFileTemplates(this,mainWindow);
247 connect(this,SIGNAL(triggerMenuRefresh()),view,SLOT(refreshMenu()));
248 return view;
249}
250
251QStringList KateFileTemplates::groups()
252{
253 QStringList l;
254 QString s;
255
256 for ( int i = 0; i < m_templates.count(); i++ )
257 {
258 s = m_templates[ i ]->group;
259 if ( ! l.contains( s ) )
260 l.append( s );
261 }
262
263 return l;
264}
265
266void KateFileTemplates::refreshMenu( KMenu *menu )
267{
268
269 // clear the menu for templates
270 menu->clear();
271
272 // restore it
273 menu->addAction( mActionAny );
274 menu->addSeparator();
275
276 QMap<QString, QMenu*> submenus; // ### QMAP
277 for ( int i = 0; i < m_templates.count(); i++ )
278 {
279 if ( ! submenus[ m_templates[ i ]->group ] )
280 {
281 QMenu *sm=menu->addMenu(m_templates[ i ]->group);
282 submenus.insert( m_templates[ i ]->group, sm );
283 }
284// kDebug()<<"=== ICON: '"<<m_templates[ i ].icon<<"'";
285 QMenu *sm=submenus[m_templates.at(i)->group];
286 QAction *a;
287 if ( ! m_templates[ i ]->icon.isEmpty() )
288 a=sm->addAction(
289 KIcon( m_templates[ i ]->icon ),
290 m_templates[ i ]->tmplate);
291 else
292 a=sm->addAction(
293 m_templates[ i ]->tmplate);
294 a->setData(i);
295 connect(a,SIGNAL(triggered(bool)),this,SLOT(slotOpenTemplate()));
296
297 // add whatsthis containing the description and author
298 QString w ( m_templates[ i ]->description );
299 if( ! m_templates[ i ]->author.isEmpty() )
300 {
301 w.append( "<p>" );
302 w.append( i18n("Author: ") );
303 w.append( m_templates[ i ]->author );
304 }
305 if ( ! w.isEmpty() )
306 w.prepend( "<p>" );
307
308 if ( ! w.isEmpty() )
309 a->setWhatsThis( w );
310 }
311}
312
313/**
314 * Action slot: use any file as a template.
315 * Get a URL and pass it on.
316 */
317void KateFileTemplates::slotAny()
318{
319 if (!application()->activeMainWindow())
320 return;
321
322 // get a URL and pass that to slotOpenTemplate
323 QString fn = KFileDialog::getOpenFileName(
324 KUrl(),
325 QString(),
326 application()->activeMainWindow()->activeView(),
327 i18n("Open as Template") );
328 if ( ! fn.isEmpty() )
329 slotOpenTemplate( KUrl( fn ) );
330}
331
332/**
333 * converts template [index] to a URL and passes that
334 */
335void KateFileTemplates::slotOpenTemplate()
336{
337 int index=static_cast<QAction*>(sender())->data().toInt();
338 kDebug()<<"slotOpenTemplate( "<<index<<" )";
339 if ( index < 0 || index > m_templates.count() ) return;
340 slotOpenTemplate( KUrl( m_templates.at( index )->filename ) );
341}
342
343void KateFileTemplates::slotOpenTemplate( const KUrl &url )
344{
345 // check if the file can be opened
346 QString tmpfile;
347 QString filename = url.fileName();
348 kDebug()<<"file: "<<filename;
349 if ( KIO::NetAccess::download( url, tmpfile, 0L ) )
350 {
351 bool isTemplate ( filename.endsWith( ".katetemplate" ) );
352 QString docname;
353
354 // open the file and parse for template variables and macros
355 QFile file(tmpfile);
356 if ( ! file.open( QIODevice::ReadOnly ) )
357 {
358 KMessageBox::sorry( application()->activeMainWindow()->activeView(),
359 i18n("<qt>Error opening the file<br /><strong>%1</strong><br />for reading. The document will not be created.</qt>", filename),
360 i18n("Template Plugin"), 0 );
361 KIO::NetAccess::removeTempFile( tmpfile );
362 return;
363 }
364
365 // this may take a moment..
366 QApplication::setOverrideCursor( QCursor(Qt::WaitCursor) );
367
368 // create a new document
369 application()->activeMainWindow()->openUrl( KUrl() );
370 KTextEditor::View *view = application()->activeMainWindow()->activeView();
371 KTextEditor::Document *doc = view->document();
372
373
374 QTextStream stream(&file);
375 QString str, tmp;
376 uint numlines = 0;
377 uint doneheader = 0;
378 while ( !stream.atEnd() ) {
379 tmp = stream.readLine();
380 if ( ! numlines && isTemplate && tmp.startsWith( "katetemplate:" ) )
381 {
382 // look for document name, highlight
383 if ( ! (doneheader & 1) )
384 {
385 QRegExp reName( "\\bdocumentname\\s*=\\s*(.+)(?:\\s+\\w+\\s*=|$)", Qt::CaseInsensitive );
386 reName.setMinimal( true );
387 if ( reName.indexIn( tmp ) > -1 )
388 {
389 docname = reName.cap( 1 );
390 docname = docname.replace( "%N", "%1" );
391 doneheader |= 1;
392 }
393 }
394
395 if ( ! (doneheader & 2) )
396 {
397 QRegExp reHl( "\\bhighlight\\s*=\\s*(.+)(?:\\s+\\w+\\s*=|$)", Qt::CaseInsensitive );
398 reHl.setMinimal( true );
399 kDebug()<<"looking for a hl mode";
400 if ( reHl.indexIn( tmp ) > -1 )
401 {
402 kDebug()<<"looking for a hl mode -- "<<reHl.cap();
403 // this is overly complex, too bad the interface is
404 // not providing a reasonable method..
405 QString hlmode = reHl.cap( 1 );
406 doc->setMode (hlmode);
407
408 doneheader |= 2;
409 }
410 }
411
412 continue; // skip this line
413 }
414 if ( numlines )
415 str += '\n';
416 str += tmp;
417 numlines++;
418 }
419 file.close();
420 KIO::NetAccess::removeTempFile( tmpfile );
421
422 uint line, col;
423 line = col = 0;
424
425 if ( ! isTemplate )
426 {
427 int d = filename.lastIndexOf('.');
428// ### warning i18n: Hack to have localized number later...
429 docname = i18n("Untitled %1", QString("%1"));
430 if ( d > 0 ) docname += filename.mid( d );
431 } else if ( docname.isEmpty() )
432 docname = filename.left( filename.length() - 13 );
433
434 // check for other documents matching this naming scheme,
435 // and do a count before choosing a name for this one
436 QString p = docname;
437 p.replace( "%1", "\\d+" );
438 p.replace( ".", "\\." );
439 p.prepend( "^" );
440 p.append( "$" );
441 QRegExp reName( p );
442
443 int count = 1;
444 const QList<KTextEditor::Document*> docs=application()->documentManager()->documents();
445 foreach(const KTextEditor::Document *doc,docs) {
446 if ( ( reName.indexIn( doc->documentName() ) > -1 ) )
447 count++;
448 }
449 if ( docname.contains( "%1" ) )
450//### warning i18n: ...localized number here
451
452 docname = docname.arg( i18n("%1", count) );
453//### warning FIXME, setDocName is gone
454#if 0
455 doc->setDocName( docname );
456#endif
457 doc->setModified( false );
458
459 QApplication::restoreOverrideCursor();
460// m_acRecentTemplates->addUrl( url );
461
462 // clean up
463 delete m_user;
464 m_user = 0;
465 delete m_emailstuff;
466 m_emailstuff = 0;
467 if (isTemplate) {
468 KTextEditor::TemplateInterface *ti=qobject_cast<KTextEditor::TemplateInterface*>(doc->activeView());
469 ti->insertTemplateText(KTextEditor::Cursor(0,0),str,QMap<QString,QString>());
470 } else {
471 doc->insertText( KTextEditor::Cursor(0, 0), str );
472 view->setCursorPosition(KTextEditor::Cursor(line, col));
473 }
474 }
475}
476
477
478QWidget *KateFileTemplates::parentWindow()
479{
480 return dynamic_cast<QWidget*>(application()->activeMainWindow());
481}
482
483// The next part are tools to aid the creation and editing of templates
484// /////////////////////////////////////////////////////////////////////
485// Steps to produce a template
486// * Choose a file to start from (optional)
487// * Ask for a location to store the file -- suggesting either the file
488// directory, or the local template directory.
489// Set the URL
490// * Get the template properties -- provide a dialog, which has filled in what
491// we already know -- the author name, list of known groups
492//
493// Combine those data into the editor, and tell the user to position the cursor
494// and edit the file as she wants to...
495void KateFileTemplates::slotCreateTemplate()
496{
497 KateTemplateWizard w( parentWindow(), this );
498 w.exec();
499
500 updateTemplateDirs();
501}
502
503// Tools for editing the existing templates
504// Editing a template:
505// * Select the template to edit
506// * Open the template
507// * Set the URL to a writable one if required
508void KateFileTemplates::slotEditTemplate()
509{
510 KDialog dlg( parentWindow());
511 dlg.setModal(true);
512 dlg.setCaption(i18n("Manage File Templates"));
513 dlg.setButtons(KDialog::Close);
514 dlg.setMainWidget( new KateTemplateManager( this, &dlg ) );
515 dlg.exec();
516}
517//END KateFileTemplates
518
519//BEGIN KateTemplateInfoWidget
520// This widget can be used to change the data of a TemplateInfo object
521KateTemplateInfoWidget::KateTemplateInfoWidget( QWidget *parent, TemplateInfo *info, KateFileTemplates *kft )
522 : QWidget( parent ),
523 info( info ),
524 kft( kft )
525{
526 QGridLayout *lo = new QGridLayout( this );
527 lo->setSpacing( KDialog::spacingHint() );
528
529 QLabel *l = new QLabel( i18n("&Template:"), this );
530 KHBox *hb = new KHBox( this );
531 hb->setSpacing( KDialog::spacingHint() );
532 leTemplate = new KLineEdit( hb );
533 l->setBuddy( leTemplate );
534 leTemplate->setToolTip( i18n("<p>This string is used as the template's name "
535 "and is displayed, for example, in the Template menu. It should describe the "
536 "meaning of the template, for example 'HTML Document'.</p>") );
537 ibIcon = new KIconButton( hb );
538 ibIcon->setToolTip(i18n(
539 "Press to select or change the icon for this template") );
540
541 l = new QLabel( i18n("&Group:"), this );
542 cmbGroup = new KComboBox( true, this );
543 cmbGroup->insertItems( 0, kft->groups() );
544 l->setBuddy( cmbGroup );
545 cmbGroup->setToolTip(i18n("<p>The group is used for choosing a "
546 "submenu for the plugin. If it is empty, 'Other' is used.</p>"
547 "<p>You can type any string to add a new group to your menu.</p>") );
548
549 l = new QLabel( i18n("Document &name:"), this );
550 leDocumentName = new KLineEdit( this );
551 l->setBuddy( leDocumentName );
552 leDocumentName->setToolTip( i18n("<p>This string will be used to set a name "
553 "for the new document, to display in the title bar and file list.</p>"
554 "<p>If the string contains '%N', that will be replaced with a number "
555 "increasing with each similarly named file.</p><p> For example, if the "
556 "Document Name is 'New shellscript (%N).sh', the first document will be "
557 "named 'New shellscript (1).sh', the second 'New shellscipt (2).sh', and "
558 "so on.</p>") );
559
560 l = new QLabel( i18n( "&Highlight:"), this );
561 btnHighlight = new QPushButton( i18n("None"), this );
562 l->setBuddy( btnHighlight );
563 btnHighlight->setToolTip( i18n("<p>Select the highlight to use for the "
564 "template. If 'None' is chosen, the property will not be set.</p>") );
565
566 l = new QLabel( i18n("&Description:"), this );
567 leDescription = new KLineEdit( this );
568 l->setBuddy( leDescription );
569 leDescription->setToolTip(i18n("<p>This string is used, for example, as "
570 "context help for this template (such as the 'whatsthis' help for the "
571 "menu item.)</p>") );
572
573 l = new QLabel( i18n("&Author:"), this );
574 leAuthor = new KLineEdit( this );
575 l->setBuddy( leAuthor );
576 leAuthor->setToolTip(i18n("<p>You can set this if you want to share your "
577 "template with other users.</p>"
578 "<p>the recommended form is like an Email "
579 "address: 'Anders Lund &lt;anders@alweb.dk&gt;'</p>") );
580
581 // if we have a object ! null
582 if ( info )
583 {
584 if ( ! info->icon.isEmpty() )
585 ibIcon->setIcon( info->icon );
586 leTemplate->setText( info->tmplate );
587 int i = cmbGroup->findText( info->group );
588 if ( i != -1 ) {
589 cmbGroup->setCurrentIndex( i );
590 } else {
591 cmbGroup->setEditText( info->group );
592 }
593 leDescription->setText( info->description );
594 leAuthor->setText( info->author );
595 if ( ! info->highlight.isEmpty() )
596 btnHighlight->setText( info->highlight );
597 }
598
599 // fill in the Hl menu
600 KTextEditor::Document *doc = kft->application()->activeMainWindow()->activeView()->document();
601 if ( doc )
602 {
603 QStringList highlightModes = doc->highlightingModes();
604 QMenu *m = new QMenu( btnHighlight );
605 connect( m, SIGNAL(triggered(QAction*)), this, SLOT(slotHlSet(QAction*)) );
606 QMap<QString, QMenu*> submenus;
607 for ( int n = 0; n < highlightModes.count(); n++ )
608 {
609 // create the sub menu if it does not exist
610 QString text( doc->highlightingModeSection( n ) );
611 if ( ! text.isEmpty() )
612 {
613 if ( ! submenus[ text ] )
614 {
615 QMenu *sm = m->addMenu( text );
616 connect( sm, SIGNAL(triggered(QAction*)), this, SLOT(slotHlSet(QAction*)) );
617 submenus.insert( text, sm );
618 }
619 // create the item
620 QAction *a = submenus[ text ]->addAction( highlightModes[ n ] );
621 a->setData( n );
622 }
623 else {
624 QAction *a = m->addAction( highlightModes[ n ] );
625 a->setData( n );
626 }
627 }
628 btnHighlight->setMenu( m );
629 }
630}
631
632void KateTemplateInfoWidget::slotHlSet( QAction *action )
633{
634 KTextEditor::Document *doc=kft->application()->activeMainWindow()->activeView()->document();
635 if (doc)
636 highlightName = doc->highlightingModes()[action->data().toInt()];
637 btnHighlight->setText( action->text() ); // fixme
638}
639//END KateTemplateInfoWidget
640
641//BEGIN KateTemplateWizard
642// A simple wizard to help create a new template :-)
643KateTemplateWizard::KateTemplateWizard( QWidget *parent, KateFileTemplates *kft )
644 : QWizard( parent ),
645 kft( kft )
646{
647 // 1) Optionally chose a file or existing template to start from
648 QWizardPage *page = new QWizardPage;
649 page->setTitle( i18n("Template Origin") );
650 page->setSubTitle( i18n("If you want to base this "
651 "template on an existing file or template, select the appropriate option "
652 "below.") );
653
654 addPage( page );
655
656 QGridLayout *glo = new QGridLayout( page );
657 glo->setSpacing( KDialog::spacingHint() );
658
659 bgOrigin = new QButtonGroup( page );
660// bgOrigin->hide();
661 bgOrigin->setExclusive( true );
662
663 QRadioButton *rb = new QRadioButton( i18n("Start with an &empty document" ), page );
664 bgOrigin->addButton( rb, 1 );
665 glo->addWidget( rb, 1, 1, 1, 2 );
666 rb->setChecked( true );
667
668 rb = new QRadioButton( i18n("Use an existing file:"), page );
669 bgOrigin->addButton( rb, 2 );
670 glo->addWidget( rb, 2, 1, 1, 2 );
671// ### warning 0 could be wrong here: it crashes with Plastik
672 // int marg = rb->style()->subElementRect( QStyle::SE_RadioButtonIndicator, 0,rb ).width();
673 int marg = KDialog::marginHint();
674 glo->addItem( new QSpacerItem( marg, 1, QSizePolicy::Fixed ), 3, 1 );
675 urOrigin = new KUrlRequester( page );
676 glo->addWidget( urOrigin, 3, 2 );
677
678 rb = new QRadioButton( i18n("Use an existing template:"), page );
679 bgOrigin->addButton( rb, 3 );
680 glo->addWidget( rb, 4, 1, 1, 2 );
681 glo->addItem( new QSpacerItem( marg, 1, QSizePolicy::Fixed ), 5, 1 );
682 btnTmpl = new QPushButton( page );
683 glo->addWidget( btnTmpl, 5, 2 );
684 QMenu *m = new QMenu( btnTmpl );
685 connect( m, SIGNAL(triggered(QAction*)), this, SLOT(slotTmplateSet(QAction*)) );
686
687 QMap<QString, QMenu*> submenus;
688 for ( int i = 0; i < kft->templates().count(); i++ )
689 {
690 if ( ! submenus[ kft->templates()[ i ]->group ] )
691 {
692 QMenu *sm = m->addMenu( kft->templates()[ i ]->group );
693 connect( sm, SIGNAL(triggered(QAction*)), this, SLOT(slotTmplateSet(QAction*)) );
694 submenus.insert( kft->templates()[ i ]->group, sm );
695 }
696
697 QAction *a = submenus[kft->templates()[ i ]->group]->addAction(
698 kft->templates()[ i ]->tmplate );
699 a->setData( i );
700 }
701 btnTmpl->setMenu( m );
702
703 connect( bgOrigin, SIGNAL(buttonClicked(int)), this, SLOT(slotStateChanged()) );
704 connect( urOrigin, SIGNAL(textChanged(QString)), this, SLOT(slotStateChanged()) );
705
706 glo->addItem( new QSpacerItem( 1, 1, QSizePolicy::Expanding, QSizePolicy::Expanding ), 7, 7, 1, 2 );
707
708 // 2) edit the template properties
709 page = new QWizardPage;
710 page->setTitle( i18n("Edit Template Properties") );
711 page->setSubTitle( i18n("Specify the main properties of your plugin. You can leave fields empty for which you have no meaningful value.") );
712 glo = new QGridLayout( page );
713 kti = new KateTemplateInfoWidget( page, 0, kft );
714 glo->addWidget( kti, 1, 1 );
715 addPage( page );
716
717 // get liekly values from KTE
718 QMap<QString, QString> map;
719 map[ "fullname" ] = "";
720 map[ "email" ] = "";
721
722 KTextEditor::TemplateInterface::expandMacros( map, parent );
723 QString sFullname = map["fullname"];
724 QString sEmail = map["email"];
725 QString _s = sFullname;
726 if ( ! sEmail.isEmpty() )
727 _s += " <" + sEmail + '>';
728 kti->leAuthor->setText( _s );
729
730 // 3) chose a location - either the template directory (default) or
731 // a custom location
732 page = new QWizardPage;
733 page->setTitle( i18n("Choose Location") );
734 page->setSubTitle( i18n("<p>Choose a location for the "
735 "template. If you store it in the template directory, it will "
736 "automatically be added to the template menu.</p>") );
737 glo = new QGridLayout( page );
738 glo->setSpacing( KDialog::spacingHint() );
739
740 bgLocation = new QButtonGroup( page );
741// bgLocation->hide();
742 bgLocation->setExclusive( true );
743
744 rb = new QRadioButton( i18n("Template directory"), page );
745 bgLocation->addButton( rb, 1 );
746 glo->addWidget( rb, 2, 1, 1, 2 );
747 rb->setChecked( true );
748
749 glo->addItem( new QSpacerItem( marg, 1, QSizePolicy::Fixed ), 3, 1, 2, 1 );
750 leTemplateFileName = new KLineEdit( page );
751 QLabel *l = new QLabel( i18n("Template &file name:"), page );
752 l->setBuddy( leTemplateFileName );
753
754 glo->addWidget( l, 3, 2 );
755 glo->addWidget( leTemplateFileName, 4, 2 );
756
757 rb = new QRadioButton( i18n("Custom location:"), page );
758 bgLocation->addButton( rb, 2 );
759 glo->addWidget( rb, 5, 1, 1, 2 );
760
761 glo->addItem( new QSpacerItem( marg, 1, QSizePolicy::Fixed ), 6, 1 );
762 urLocation = new KUrlRequester( page );
763 glo->addWidget( urLocation, 6, 2 );
764
765 connect( bgLocation, SIGNAL(buttonClicked(int)), this, SLOT(slotStateChanged()) );
766 connect( urLocation, SIGNAL(textChanged(QString)), this, SLOT(slotStateChanged()) );
767 connect( leTemplateFileName, SIGNAL(textChanged(QString)), this, SLOT(slotStateChanged()) );
768
769 glo->addItem( new QSpacerItem( 1, 1, QSizePolicy::Expanding, QSizePolicy::Expanding ), 7, 1, 1, 2 );
770
771 addPage( page );
772 // 4) Should we edit the text to add some macros, replacing username etc?
773 // This is *only* relevant if the origin is a non-template file.
774 page = new QWizardPage;
775 page->setTitle( i18n("Autoreplace Macros") );
776 page->setSubTitle( i18n( "You can replace certain strings in the text with "
777 "template macros. If any of the data below is incorrect or missing, "
778 "edit the data in the personal kaddressbook entry.") );
779 QVBoxLayout *lo = new QVBoxLayout( page );
780 lo->setSpacing( KDialog::spacingHint() );
781
782 cbRRealname = new QCheckBox( i18n("Replace full name '%1' with the "
783 "'%{fullname}' macro", sFullname ), page );
784 cbRRealname->setEnabled( ! sFullname.isEmpty() );
785 lo->addWidget( cbRRealname );
786
787 cbREmail = new QCheckBox( i18n("Replace email address '%1' with the "
788 "'%email' macro", sEmail ), page);
789 cbREmail->setEnabled( ! sEmail.isEmpty() );
790 lo->addWidget( cbREmail );
791
792 lo->addStretch();
793
794 addPage( page );
795
796 // 5) Display a summary
797 page = new QWizardPage;
798 page->setTitle( i18n("Create Template") );
799 page->setSubTitle( i18n("The template will now be created and saved to the chosen "
800 "location. To position the cursor put the string ${|} where you "
801 "want it in files created from the template.") );
802 lo = new QVBoxLayout( page );
803 lo->setSpacing( KDialog::spacingHint() );
804
805 cbOpenTemplate = new QCheckBox( i18n("Open the template for editing in Kate"), page );
806
807 lo->addWidget( cbOpenTemplate );
808
809 lo->addStretch();
810
811 addPage( page );
812 connect( this, SIGNAL(currentIdChanged(int)), this, SLOT(slotStateChanged()) );
813}
814
815void KateTemplateWizard::slotTmplateSet( QAction *action )
816{
817 int idx = action->data().toInt();
818 btnTmpl->setText( kft->templates().at( idx )->tmplate );
819 selectedTemplateIdx = idx;
820 slotStateChanged();
821}
822
823/**
824 * When the state of any button in any setup page is changed, set the
825 * enabled state of the next button accordingly.
826 *
827 * Origin:
828 * if file is chosen, the URLRequester must have a valid URL in it
829 * if template is chosen, one must be selected in the menu button.
830 *
831 * Props:
832 * anything goes, but if the user wants to store the template in the template
833 * directory, she should be encouraged to fill in information.
834*/
835void KateTemplateWizard::slotStateChanged()
836{
837 bool sane( true );
838 switch ( currentId() )
839 {
840 case 0: // origin
841 {
842 int _t = bgOrigin->checkedId();
843 kDebug()<<"selected button:"<<_t;
844 sane = ( _t == 1 ||
845 ( _t == 2 && ! urOrigin->url().isEmpty() ) ||
846 ( _t == 3 && ! btnTmpl->text().isEmpty() ) );
847// setAppropriate( page(3), _t == 2 );
848 }
849 break;
850 case 1: // template properties
851 // if origin is a existing template, let us try setting some of the properties
852 if ( bgOrigin->checkedId() == 3 )
853 {
854 TemplateInfo *info = kft->templateInfo( selectedTemplateIdx );
855 int i = kti->cmbGroup->findText( info->group );
856 if ( i != -1 ) {
857 kti->cmbGroup->setCurrentIndex( i );
858 } else {
859 kti->cmbGroup->setEditText( info->group );
860 }
861 }
862 break;
863 case 2: // location
864 {
865 // If there is a template name, and the user did not enter text into
866 // the template file name entry, we will construct the name from the
867 // template name.
868 int _t = bgLocation->checkedId();
869 sane = ( ( _t == 1 && (! leTemplateFileName->text().isEmpty() || ! kti->leTemplate->text().isEmpty() ) ) ||
870 ( _t == 2 && ! urLocation->url().isEmpty() ) );
871 }
872 break;
873 default:
874 break;
875 }
876 kDebug()<<"enabling 'next' button:"<<sane;
877 button( QWizard::NextButton )->setEnabled( sane );
878}
879
880int KateTemplateWizard::nextId() const
881{
882 switch ( currentId() )
883 {
884// case 0:
885// if ( bgOrigin->checkedId() == 2 ) return 2;
886// else return 1;
887 default:
888 return QWizard::nextId();
889 }
890}
891/**
892 * This will create the new template based on the collected information.
893 */
894void KateTemplateWizard::accept()
895{
896 // TODO check that everything is kosher, so that we can get a save location
897 // etc.
898
899 // check that we can combine a valid URL
900 KUrl templateUrl;
901 if ( bgLocation->checkedId() == 1 )
902 {
903 QString suggestion;
904 if ( ! leTemplateFileName->text().isEmpty() )
905 suggestion = leTemplateFileName->text();
906 else
907 suggestion = kti->leTemplate->text();
908
909 suggestion.remove(' ');
910
911 if ( ! suggestion.endsWith(".katetemplate") )
912 suggestion.append(".katetemplate");
913
914 QString dir = KGlobal::dirs()->saveLocation( "data", "kate/plugins/katefiletemplates/templates/", true );
915
916 templateUrl = dir + suggestion;
917
918 if ( QFile::exists( templateUrl.toLocalFile() ) )
919 {
920 if ( KMessageBox::warningContinueCancel( this, i18n(
921 "<p>The file <br /><strong>'%1'</strong><br /> already exists; if you "
922 "do not want to overwrite it, change the template file name to "
923 "something else.</p>", templateUrl.pathOrUrl() ),
924 i18n("File Exists"), KGuiItem(i18n("Overwrite") ))
925 == KMessageBox::Cancel )
926 return;
927 }
928 }
929 else
930 {
931 templateUrl = urLocation->url();
932 }
933
934 QWizard::accept();
935 // The following must be done:
936 // 1) add the collected template information to the top
937 uint ln = 0;
938 QString s, str;
939 if ( ! kti->leTemplate->text().isEmpty() )
940 s += " Template=" + kti->leTemplate->text();
941 if ( ! kti->cmbGroup->currentText().isEmpty() )
942 s += " Group=" + kti->cmbGroup->currentText();
943 if ( ! kti->leDocumentName->text().isEmpty() )
944 s += " Documentname=" + kti->leDocumentName->text();
945 if ( ! kti->ibIcon->icon().isEmpty() )
946 s += " Icon=" + kti->ibIcon->icon();
947 if ( ! kti->highlightName.isEmpty() && kti->highlightName != "None" )
948 s += " Highlight=" + kti->highlightName;
949
950 str = "katetemplate:" + s;
951
952 if ( ! (s = kti->leAuthor->text()).isEmpty() )
953 str += "\nkatetemplate: Author=" + s;
954
955 if ( ! (s = kti->leDescription->text()).isEmpty() )
956 str += "\nkatetemplate: Description=" + s;
957
958 // 2) If a file or template is chosen, open that. and fill the data into a string
959 int toid = bgOrigin->checkedId(); // 1 = blank, 2 = file, 3 = template
960 kDebug()<<"=== create template: origin type "<<toid;
961 if ( toid > 1 )
962 {
963 KUrl u;
964 if ( toid == 2 ) // file
965 u = KUrl( urOrigin->url() );
966 else // template
967 u = KUrl( kft->templates().at( selectedTemplateIdx )->filename );
968
969 QString tmpfile, tmp;
970 if ( KIO::NetAccess::download( u, tmpfile, 0L ) )
971 {
972 QFile file(tmpfile);
973 if ( ! file.open( QIODevice::ReadOnly ) )
974 {
975 KMessageBox::sorry( this, i18n(
976 "<qt>Error opening the file<br /><strong>%1</strong><br />for reading. "
977 "The document will not be created</qt>", u.pathOrUrl()),
978 i18n("Template Plugin"), 0 );
979
980 KIO::NetAccess::removeTempFile( tmpfile );
981 return;
982 }
983
984 QTextStream stream(&file);
985 QString ln;
986 bool trymore = true;
987 while ( !stream.atEnd() )
988 {
989 // skip template headers
990 ln = stream.readLine();
991 if ( trymore && ln.startsWith("katetemplate:") )
992 continue;
993
994 trymore = false;
995 tmp += '\n' + ln;
996 }
997
998 file.close();
999 KIO::NetAccess::removeTempFile( tmpfile );
1000 }
1001
1002 if ( toid == 2 ) // file
1003 {
1004 // 3) if the file is not already a template, escape any "%" and "^" in it,
1005 // and try do do some replacement of the authors username, name and email.
1006 tmp.replace( QRegExp("%(?=\\{[^}]+\\})"), "\\%" );
1007 tmp.replace( QRegExp("\\$(?=\\{[^}]+\\})"), "\\$" );
1008 //tmp.replace( "^", "\\^" );
1009
1010 if ( cbRRealname->isChecked() && ! sFullname.isEmpty() )
1011 tmp.replace( sFullname, "%{realname}" );
1012
1013
1014 if ( cbREmail->isChecked() && ! sEmail.isEmpty() )
1015 tmp.replace( sEmail, "%{email}" );
1016 }
1017
1018 str += tmp;
1019 }
1020
1021 // 4) Save the document to the suggested URL if possible
1022
1023 bool succes = false;
1024
1025 if ( templateUrl.isValid() )
1026 {
1027 if ( templateUrl.isLocalFile() )
1028 {
1029 QFile file( templateUrl.toLocalFile() );
1030 if ( file.open(QIODevice::WriteOnly) )
1031 {
1032 kDebug()<<"file opened with succes";
1033 QTextStream stream( &file );
1034 stream << str;
1035 file.close();
1036 succes = true;
1037 }
1038 }
1039 else
1040 {
1041 KTemporaryFile tmp;
1042 tmp.open();
1043 QString fn = tmp.fileName();
1044 QTextStream stream( &tmp );
1045 stream << str;
1046 stream.flush();
1047
1048 succes = KIO::NetAccess::upload( fn, templateUrl, 0 );
1049 }
1050 }
1051
1052 if ( ! succes )
1053 {
1054 KMessageBox::sorry( this, i18n(
1055 "Unable to save the template to '%1'.\n\nThe template will be opened, "
1056 "so you can save it from the editor.", templateUrl.pathOrUrl() ),
1057 i18n("Save Failed") );
1058
1059 kft->application()->activeMainWindow()->openUrl( KUrl() );
1060 KTextEditor::View *view = kft->application()->activeMainWindow()->activeView();
1061 KTextEditor::Document *doc = view->document();
1062 doc->insertText( KTextEditor::Cursor(ln++, 0), str );
1063 }
1064 else if ( cbOpenTemplate->isChecked() )
1065 kft->application()->activeMainWindow()->openUrl( templateUrl );
1066}
1067//END KateTemplateWizard
1068
1069//BEGIN KateTemplateManager
1070KateTemplateManager::KateTemplateManager( KateFileTemplates *kft, QWidget *parent )
1071 : QWidget( parent )
1072 , kft( kft )
1073{
1074 QGridLayout *lo = new QGridLayout( this );
1075 lo->setSpacing( KDialog::spacingHint() );
1076 lvTemplates = new QTreeWidget( this );
1077 lvTemplates->setHeaderLabel( i18n("Template") );
1078 lvTemplates->setSelectionMode( QAbstractItemView::SingleSelection );
1079 lo->addWidget( lvTemplates, 1, 1, 1, 4 );
1080 connect( lvTemplates, SIGNAL(itemSelectionChanged()), this, SLOT(slotUpdateState()) );
1081
1082 btnNew = new QPushButton( i18nc("@action:button Template", "New..."), this );
1083 connect( btnNew, SIGNAL(clicked()), kft, SLOT(slotCreateTemplate()) );
1084 lo->addWidget( btnNew, 2, 2 );
1085
1086 btnEdit = new QPushButton( i18nc("@action:button Template", "Edit..."), this );
1087 connect( btnEdit, SIGNAL(clicked()), this, SLOT(slotEditTemplate()) );
1088 lo->addWidget( btnEdit, 2, 3 );
1089
1090 btnRemove = new QPushButton( i18nc("@action:button Template", "Remove"), this );
1091 connect( btnRemove, SIGNAL(clicked()), this, SLOT(slotRemoveTemplate()) );
1092 lo->addWidget( btnRemove, 2, 4 );
1093
1094 lo->setColumnStretch( 1, 1 );
1095
1096 reload();
1097 slotUpdateState();
1098}
1099
1100void KateTemplateManager::apply()
1101{
1102 // if any files were removed, delete them unless they are not writeable, in
1103 // which case a link .filename should be put in the writable directory.
1104}
1105
1106#define KATETEMPLATEITEM 1001
1107void KateTemplateManager::reload()
1108{
1109 lvTemplates->clear();
1110
1111 QMap<QString, QTreeWidgetItem*> groupitems;
1112 for ( int i = 0; i < kft->templates().count(); i++ )
1113 {
1114 if ( ! groupitems[ kft->templates()[ i ]->group ] )
1115 {
1116 groupitems.insert( kft->templates()[ i ]->group , new QTreeWidgetItem( lvTemplates ) );
1117 groupitems[ kft->templates()[ i ]->group ]->setText( 0, kft->templates()[ i ]->group );
1118 groupitems[ kft->templates()[ i ]->group ]->setExpanded( true );
1119 }
1120 QTreeWidgetItem *item = new QTreeWidgetItem( groupitems[ kft->templates()[ i ]->group ], KATETEMPLATEITEM );
1121 item->setText( 0, kft->templates()[ i ]->tmplate );
1122 item->setData( 0, Qt::UserRole, QVariant::fromValue( kft->templates()[ i ] ) );
1123 }
1124}
1125
1126void KateTemplateManager::slotUpdateState()
1127{
1128 // enable/disable buttons wrt the current item in the list view.
1129 // we are in single selection mode, so currentItem() is selected.
1130 QTreeWidgetItem *item = lvTemplates->currentItem();
1131 bool cool = item && item->type() == KATETEMPLATEITEM;
1132
1133 btnEdit->setEnabled( cool );
1134 btnRemove->setEnabled( cool );
1135}
1136
1137void KateTemplateManager::slotEditTemplate()
1138{
1139 // open the template file in kate
1140 // TODO show the properties dialog, and modify the file if the data was changed.
1141 QList<QTreeWidgetItem*> selection = lvTemplates->selectedItems();
1142
1143 if ( selection.count() ) {
1144 QTreeWidgetItem *item = selection[ 0 ];
1145 if ( item->type() != KATETEMPLATEITEM )
1146 return;
1147 TemplateInfo *info = item->data(0, Qt::UserRole ).value<TemplateInfo*>();
1148 kft->application()->activeMainWindow()->openUrl( info->filename );
1149 }
1150}
1151
1152void KateTemplateManager::slotRemoveTemplate()
1153{
1154 QTreeWidgetItem *item = lvTemplates->selectedItems().first();
1155 if ( item && item->type() == KATETEMPLATEITEM )
1156 {
1157 // Find all instances of filename, and try to delete them.
1158 // If it fails (there was a global, unwritable instance), add to a
1159 // list of removed templates
1160 KSharedConfig::Ptr config = KGlobal::config();
1161 TemplateInfo *info = item->data(0, Qt::UserRole).value<TemplateInfo*>();
1162 QString fname = info->filename.section( '/', -1 );
1163 const QStringList templates = KGlobal::dirs()->findAllResources(
1164 "data", fname.prepend( "kate/plugins/katefiletemplates/templates/" ),KStandardDirs::NoDuplicates);
1165 int failed = 0;
1166 int removed = 0;
1167 for ( QStringList::const_iterator it=templates.begin(); it!=templates.end(); ++it )
1168 {
1169 if ( ! QFile::remove(*it) )
1170 failed++;
1171 else
1172 removed++;
1173 }
1174
1175 if ( failed )
1176 {
1177 KConfigGroup cg( config, "KateFileTemplates" );
1178 QStringList l;
1179 cg.readXdgListEntry( "Hidden", l ); // XXX this is bogus
1180 l << fname;
1181 cg.writeXdgListEntry( "Hidden", l ); // XXX this is bogus
1182 }
1183
1184 kft->updateTemplateDirs();
1185 reload();
1186 }
1187}
1188//END KateTemplateManager
1189
1190// kate: space-indent on; indent-width 2; replace-tabs on;
1191
1192#include "filetemplates.moc"
1193