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 | |
54 | using namespace KPIMIdentities; |
55 | |
56 | namespace KPIMIdentities { |
57 | |
58 | /** |
59 | Private class that helps to provide binary compatibility between releases. |
60 | @internal |
61 | */ |
62 | //@cond PRIVATE |
63 | class 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 | |
75 | SignatureConfigurator::Private::Private( SignatureConfigurator *parent ) |
76 | :q( parent ), inlinedHtml( true ) |
77 | { |
78 | } |
79 | |
80 | void 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 | |