1/* -*- c++ -*-
2 Copyright 2008 Thomas McGuire <Thomas.McGuire@gmx.net>
3 Copyright 2008 Edwin Schepers <yez@familieschepers.nl>
4 Copyright 2004 Marc Mutz <mutz@kde.org>
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2.1 of the License, or (at your option) any later version.
10
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public
17 License along with this library. If not, see <http://www.gnu.org/licenses/>.
18*/
19
20
21#include "signatureconfigurator.h"
22#include "identity.h"
23
24#include <kactioncollection.h>
25#include <klocalizedstring.h>
26#include <kdebug.h>
27#include <kdialog.h>
28#include <klineedit.h>
29#include <kurlrequester.h>
30#include <kshellcompletion.h>
31#include <ktoolbar.h>
32#include <krun.h>
33#include <KUrl>
34#include <KComboBox>
35#include <KStandardDirs>
36
37#include <kpimtextedit/textedit.h>
38
39#include <QCheckBox>
40#include <QDir>
41#include <QFileInfo>
42#include <QLabel>
43#include <QLayout>
44#include <QMimeData>
45#include <QTextEdit>
46
47#include <QStackedWidget>
48
49#include <QVBoxLayout>
50#include <QHBoxLayout>
51
52#include <assert.h>
53
54using namespace KPIMIdentities;
55
56namespace KPIMIdentities {
57
58/**
59 Private class that helps to provide binary compatibility between releases.
60 @internal
61 */
62//@cond PRIVATE
63class SignatureConfigurator::Private
64{
65 public:
66 Private( SignatureConfigurator *parent );
67 void init();
68
69 SignatureConfigurator *q;
70 bool inlinedHtml;
71 QString imageLocation;
72};
73//@endcond
74
75SignatureConfigurator::Private::Private( SignatureConfigurator *parent )
76 :q( parent ), inlinedHtml( true )
77{
78}
79
80void SignatureConfigurator::Private::init()
81{
82 // tmp. vars:
83 QLabel * label;
84 QWidget * page;
85 QHBoxLayout * hlay;
86 QVBoxLayout * vlay;
87 QVBoxLayout * page_vlay;
88
89 vlay = new QVBoxLayout( q );
90 vlay->setObjectName( QLatin1String("main layout") );
91 vlay->setMargin( 0 );
92
93 // "enable signatue" checkbox:
94 q->mEnableCheck = new QCheckBox( i18n( "&Enable signature" ), q );
95 q->mEnableCheck->setWhatsThis(
96 i18n( "Check this box if you want KMail to append a signature to mails "
97 "written with this identity." ) );
98 vlay->addWidget( q->mEnableCheck );
99
100 // "obtain signature text from" combo and label:
101 hlay = new QHBoxLayout(); // inherits spacing
102 vlay->addLayout( hlay );
103 q->mSourceCombo = new KComboBox( q );
104 q->mSourceCombo->setEditable( false );
105 q->mSourceCombo->setWhatsThis(
106 i18n( "Click on the widgets below to obtain help on the input methods." ) );
107 q->mSourceCombo->setEnabled( false ); // since !mEnableCheck->isChecked()
108 q->mSourceCombo->addItems( QStringList()
109 << i18nc( "continuation of \"obtain signature text from\"",
110 "Input Field Below" )
111 << i18nc( "continuation of \"obtain signature text from\"",
112 "File" )
113 << i18nc( "continuation of \"obtain signature text from\"",
114 "Output of Command" ) );
115 label = new QLabel( i18n( "Obtain signature &text from:" ), q );
116 label->setBuddy( q->mSourceCombo );
117 label->setEnabled( false ); // since !mEnableCheck->isChecked()
118 hlay->addWidget( label );
119 hlay->addWidget( q->mSourceCombo, 1 );
120
121 // widget stack that is controlled by the source combo:
122 QStackedWidget * widgetStack = new QStackedWidget( q );
123 widgetStack->setEnabled( false ); // since !mEnableCheck->isChecked()
124 vlay->addWidget( widgetStack, 1 );
125 q->connect( q->mSourceCombo, SIGNAL(currentIndexChanged(int)),
126 widgetStack, SLOT(setCurrentIndex(int)) );
127 q->connect( q->mSourceCombo, SIGNAL(highlighted(int)),
128 widgetStack, SLOT(setCurrentIndex(int)) );
129 // connects for the enabling of the widgets depending on
130 // signatureEnabled:
131 q->connect( q->mEnableCheck, SIGNAL(toggled(bool)),
132 q->mSourceCombo, SLOT(setEnabled(bool)) );
133 q->connect( q->mEnableCheck, SIGNAL(toggled(bool)),
134 widgetStack, SLOT(setEnabled(bool)) );
135 q->connect( q->mEnableCheck, SIGNAL(toggled(bool)),
136 label, SLOT(setEnabled(bool)) );
137 // The focus might be still in the widget that is disabled
138 q->connect( q->mEnableCheck, SIGNAL(clicked()),
139 q->mEnableCheck, SLOT(setFocus()) );
140
141 int pageno = 0;
142 // page 0: input field for direct entering:
143 page = new QWidget( widgetStack );
144 widgetStack->insertWidget( pageno, page );
145 page_vlay = new QVBoxLayout( page );
146
147#ifndef QT_NO_TOOLBAR
148 q->mEditToolBar = new KToolBar( q );
149 q->mEditToolBar->setToolButtonStyle( Qt::ToolButtonIconOnly );
150 page_vlay->addWidget( q->mEditToolBar, 0 );
151
152 q->mFormatToolBar = new KToolBar( q );
153 q->mFormatToolBar->setToolButtonStyle( Qt::ToolButtonIconOnly );
154 page_vlay->addWidget( q->mFormatToolBar, 1 );
155#endif
156
157 q->mTextEdit = new KPIMTextEdit::TextEdit( q );
158 static_cast<KPIMTextEdit::TextEdit*>( q->mTextEdit )->enableImageActions();
159 static_cast<KPIMTextEdit::TextEdit*>( q->mTextEdit )->enableInsertHtmlActions();
160 static_cast<KPIMTextEdit::TextEdit*>( q->mTextEdit )->enableInsertTableActions();
161 page_vlay->addWidget( q->mTextEdit, 2 );
162 q->mTextEdit->setWhatsThis( i18n( "Use this field to enter an arbitrary static signature." ) );
163 // exclude SupportToPlainText.
164 q->mTextEdit->setRichTextSupport( KRichTextWidget::FullTextFormattingSupport |
165 KRichTextWidget::FullListSupport |
166 KRichTextWidget::SupportAlignment |
167 KRichTextWidget::SupportRuleLine |
168 KRichTextWidget::SupportHyperlinks |
169 KRichTextWidget::SupportFormatPainting );
170
171 // Fill the toolbars.
172 KActionCollection *actionCollection = new KActionCollection( q );
173 q->mTextEdit->createActions( actionCollection );
174#ifndef QT_NO_TOOLBAR
175 q->mEditToolBar->addAction( actionCollection->action( QLatin1String("format_text_bold") ) );
176 q->mEditToolBar->addAction( actionCollection->action( QLatin1String("format_text_italic") ) );
177 q->mEditToolBar->addAction( actionCollection->action( QLatin1String("format_text_underline") ) );
178 q->mEditToolBar->addAction( actionCollection->action( QLatin1String("format_text_strikeout") ) );
179 q->mEditToolBar->addAction( actionCollection->action( QLatin1String("format_text_foreground_color") ) );
180 q->mEditToolBar->addAction( actionCollection->action( QLatin1String("format_text_background_color") ) );
181 q->mEditToolBar->addAction( actionCollection->action( QLatin1String("format_font_family") ) );
182 q->mEditToolBar->addAction( actionCollection->action( QLatin1String("format_font_size") ) );
183 q->mEditToolBar->addAction( actionCollection->action( QLatin1String("format_reset") ) );
184
185 q->mFormatToolBar->addAction( actionCollection->action( QLatin1String("format_list_style") ) );
186 q->mFormatToolBar->addAction( actionCollection->action( QLatin1String("format_list_indent_more") ) );
187 q->mFormatToolBar->addAction( actionCollection->action( QLatin1String("format_list_indent_less") ) );
188 q->mFormatToolBar->addAction( actionCollection->action( QLatin1String("format_list_indent_less") ) );
189 q->mFormatToolBar->addSeparator();
190
191 q->mFormatToolBar->addAction( actionCollection->action( QLatin1String("format_align_left") ) );
192 q->mFormatToolBar->addAction( actionCollection->action( QLatin1String("format_align_center") ) );
193 q->mFormatToolBar->addAction( actionCollection->action( QLatin1String("format_align_right") ) );
194 q->mFormatToolBar->addAction( actionCollection->action( QLatin1String("format_align_justify") ) );
195 q->mFormatToolBar->addSeparator();
196
197 q->mFormatToolBar->addAction( actionCollection->action( QLatin1String("insert_horizontal_rule") ) );
198 q->mFormatToolBar->addAction( actionCollection->action( QLatin1String("manage_link") ) );
199 q->mFormatToolBar->addAction( actionCollection->action( QLatin1String("format_painter") ) );
200
201 q->mFormatToolBar->addSeparator();
202 q->mFormatToolBar->addAction( actionCollection->action( QLatin1String("add_image") ) );
203 q->mFormatToolBar->addSeparator();
204 q->mFormatToolBar->addAction( actionCollection->action( QLatin1String("insert_html") ) );
205 q->mFormatToolBar->addAction( actionCollection->action( QLatin1String("insert_table" )) );
206#endif
207
208 hlay = new QHBoxLayout(); // inherits spacing
209 page_vlay->addLayout( hlay );
210 q->mHtmlCheck = new QCheckBox( i18n( "&Use HTML" ), page );
211 q->connect( q->mHtmlCheck, SIGNAL(clicked()),
212 q, SLOT(slotSetHtml()) );
213 hlay->addWidget( q->mHtmlCheck );
214 inlinedHtml = true;
215
216 widgetStack->setCurrentIndex( 0 ); // since mSourceCombo->currentItem() == 0
217
218 // page 1: "signature file" requester, label, "edit file" button:
219 ++pageno;
220 page = new QWidget( widgetStack );
221 widgetStack->insertWidget( pageno, page ); // force sequential numbers (play safe)
222 page_vlay = new QVBoxLayout( page );
223 page_vlay->setMargin( 0 );
224 hlay = new QHBoxLayout(); // inherits spacing
225 page_vlay->addLayout( hlay );
226 q->mFileRequester = new KUrlRequester( page );
227 q->mFileRequester->setWhatsThis(
228 i18n( "Use this requester to specify a text file that contains your "
229 "signature. It will be read every time you create a new mail or "
230 "append a new signature." ) );
231 label = new QLabel( i18n( "S&pecify file:" ), page );
232 label->setBuddy( q->mFileRequester );
233 hlay->addWidget( label );
234 hlay->addWidget( q->mFileRequester, 1 );
235 q->mFileRequester->button()->setAutoDefault( false );
236 q->connect( q->mFileRequester, SIGNAL(textChanged(QString)),
237 q, SLOT(slotEnableEditButton(QString)) );
238 q->mEditButton = new QPushButton( i18n( "Edit &File" ), page );
239 q->mEditButton->setWhatsThis( i18n( "Opens the specified file in a text editor." ) );
240 q->connect( q->mEditButton, SIGNAL(clicked()),
241 q, SLOT(slotEdit()) );
242 q->mEditButton->setAutoDefault( false );
243 q->mEditButton->setEnabled( false ); // initially nothing to edit
244 hlay->addWidget( q->mEditButton );
245 page_vlay->addStretch( 1 ); // spacer
246
247 // page 2: "signature command" requester and label:
248 ++pageno;
249 page = new QWidget( widgetStack );
250 widgetStack->insertWidget( pageno, page );
251 page_vlay = new QVBoxLayout( page );
252 page_vlay->setMargin( 0 );
253 hlay = new QHBoxLayout(); // inherits spacing
254 page_vlay->addLayout( hlay );
255 q->mCommandEdit = new KLineEdit( page );
256 q->mCommandEdit->setClearButtonShown( true );
257 q->mCommandEdit->setCompletionObject( new KShellCompletion() );
258 q->mCommandEdit->setAutoDeleteCompletionObject( true );
259 q->mCommandEdit->setWhatsThis(
260 i18n( "You can add an arbitrary command here, either with or without path "
261 "depending on whether or not the command is in your Path. For every "
262 "new mail, KMail will execute the command and use what it outputs (to "
263 "standard output) as a signature. Usual commands for use with this "
264 "mechanism are \"fortune\" or \"ksig -random\"." ) );
265 label = new QLabel( i18n( "S&pecify command:" ), page );
266 label->setBuddy( q->mCommandEdit );
267 hlay->addWidget( label );
268 hlay->addWidget( q->mCommandEdit, 1 );
269 page_vlay->addStretch( 1 ); // spacer
270}
271
272 SignatureConfigurator::SignatureConfigurator( QWidget * parent )
273 : QWidget( parent ), d( new Private( this ) )
274 {
275 d->init();
276 }
277
278 SignatureConfigurator::~SignatureConfigurator()
279 {
280 delete d;
281 }
282
283 bool SignatureConfigurator::isSignatureEnabled() const
284 {
285 return mEnableCheck->isChecked();
286 }
287
288 void SignatureConfigurator::setSignatureEnabled( bool enable )
289 {
290 mEnableCheck->setChecked( enable );
291 }
292
293 Signature::Type SignatureConfigurator::signatureType() const
294 {
295 switch ( mSourceCombo->currentIndex() ) {
296 case 0: return Signature::Inlined;
297 case 1: return Signature::FromFile;
298 case 2: return Signature::FromCommand;
299 default: return Signature::Disabled;
300 }
301 }
302
303 void SignatureConfigurator::setSignatureType( Signature::Type type )
304 {
305 int idx = 0;
306 switch ( type ) {
307 case Signature::Inlined: idx = 0; break;
308 case Signature::FromFile: idx = 1; break;
309 case Signature::FromCommand: idx = 2; break;
310 default: idx = 0; break;
311 };
312
313 mSourceCombo->setCurrentIndex( idx );
314 }
315
316 void SignatureConfigurator::setInlineText( const QString & text )
317 {
318 mTextEdit->setTextOrHtml( text );
319 }
320
321 QString SignatureConfigurator::fileURL() const
322 {
323 QString file = mFileRequester->url().path();
324
325 // Force the filename to be relative to ~ instead of $PWD depending
326 // on the rest of the code (KRun::run in Edit and KFileItem on save)
327 if ( !file.isEmpty() && QFileInfo( file ).isRelative() ) {
328 file = QDir::home().absolutePath() + QDir::separator() + file;
329 }
330 return file;
331 }
332
333 void SignatureConfigurator::setFileURL( const QString & url )
334 {
335 mFileRequester->setUrl( QUrl(url) );
336 }
337
338 QString SignatureConfigurator::commandURL() const
339 {
340 return mCommandEdit->text();
341 }
342
343 void SignatureConfigurator::setCommandURL( const QString & url )
344 {
345 mCommandEdit->setText( url );
346 }
347
348
349 Signature SignatureConfigurator::signature() const
350 {
351 Signature sig;
352 const Signature::Type sigType = signatureType();
353 switch ( sigType ) {
354 case Signature::Inlined:
355 sig.setInlinedHtml( d->inlinedHtml );
356 sig.setText( d->inlinedHtml ? asCleanedHTML() : mTextEdit->textOrHtml() );
357 if ( d->inlinedHtml ) {
358 if ( !d->imageLocation.isEmpty() ) {
359 sig.setImageLocation( d->imageLocation );
360 }
361 KPIMTextEdit::ImageWithNameList images = static_cast< KPIMTextEdit::TextEdit*>( mTextEdit )->imagesWithName();
362 foreach ( const KPIMTextEdit::ImageWithNamePtr &image, images ) {
363 sig.addImage( image->image, image->name );
364 }
365 }
366 break;
367 case Signature::FromCommand:
368 sig.setUrl( commandURL(), true );
369 break;
370 case Signature::FromFile:
371 sig.setUrl( fileURL(), false );
372 break;
373 case Signature::Disabled:
374 /* do nothing */
375 break;
376 }
377 sig.setEnabledSignature( isSignatureEnabled() );
378 sig.setType( sigType );
379 return sig;
380 }
381
382 void SignatureConfigurator::setSignature( const Signature & sig )
383 {
384 setSignatureType( sig.type() );
385 setSignatureEnabled( sig.isEnabledSignature() );
386
387 if ( sig.isInlinedHtml() ) {
388 mHtmlCheck->setCheckState( Qt::Checked );
389 } else {
390 mHtmlCheck->setCheckState( Qt::Unchecked );
391 }
392 slotSetHtml();
393
394 // Let insertIntoTextEdit() handle setting the text, as that function also adds the images.
395 mTextEdit->clear();
396 KPIMTextEdit::TextEdit * const pimEdit = static_cast<KPIMTextEdit::TextEdit*>( mTextEdit );
397 sig.insertIntoTextEdit( KPIMIdentities::Signature::Start, KPIMIdentities::Signature::AddNothing,
398 pimEdit, true );
399 if ( sig.type() == Signature::FromFile ) {
400 setFileURL( sig.url() );
401 } else {
402 setFileURL( QString() );
403 }
404
405 if ( sig.type() == Signature::FromCommand ) {
406 setCommandURL( sig.url() );
407 } else {
408 setCommandURL( QString() );
409 }
410 }
411
412 void SignatureConfigurator::slotEnableEditButton( const QString & url )
413 {
414 mEditButton->setDisabled( url.trimmed().isEmpty() );
415 }
416
417 void SignatureConfigurator::slotEdit()
418 {
419 QString url = fileURL();
420 // slotEnableEditButton should prevent this assert from being hit:
421 assert( !url.isEmpty() );
422
423 (void)KRun::runUrl( KUrl( url ), QString::fromLatin1( "text/plain" ), this );
424 }
425
426 QString SignatureConfigurator::asCleanedHTML() const
427 {
428 QString text = mTextEdit->toHtml();
429
430 // Beautiful little hack to find the html headers produced by Qt.
431 QTextDocument textDocument;
432 QString html = textDocument.toHtml();
433
434 // Now remove each line from the text, the result is clean html.
435 foreach ( const QString& line, html.split( QLatin1Char('\n') ) ) {
436 text.remove( line + QLatin1Char('\n') );
437 }
438 return text;
439 }
440
441 // "use HTML"-checkbox (un)checked
442 void SignatureConfigurator::slotSetHtml()
443 {
444 if ( mHtmlCheck->checkState() == Qt::Unchecked ) {
445 mHtmlCheck->setText( i18n( "&Use HTML" ) );
446#ifndef QT_NO_TOOLBAR
447 mEditToolBar->setVisible( false );
448 mEditToolBar->setEnabled( false );
449 mFormatToolBar->setVisible( false );
450 mFormatToolBar->setEnabled( false );
451#endif
452 mTextEdit->switchToPlainText();
453 d->inlinedHtml = false;
454 }
455 else {
456 mHtmlCheck->setText( i18n( "&Use HTML (disabling removes formatting)" ) );
457 d->inlinedHtml = true;
458#ifndef QT_NO_TOOLBAR
459 mEditToolBar->setVisible( true );
460 mEditToolBar->setEnabled( true );
461 mFormatToolBar->setVisible( true );
462 mFormatToolBar->setEnabled( true );
463#endif
464 mTextEdit->enableRichTextMode();
465 }
466 }
467
468 void SignatureConfigurator::setImageLocation ( const QString& path )
469 {
470 d->imageLocation = path;
471 }
472
473 void SignatureConfigurator::setImageLocation( const Identity &identity )
474 {
475 const QString dir = QString::fromLatin1( "emailidentities/%1/" ).arg(
476 QString::number( identity.uoid() ) );
477 setImageLocation( KStandardDirs::locateLocal( "data", dir ) );
478 }
479
480}
481
482