Make the boss happy.
[kdepim.git] / kmail / kmfilterdlg.cpp
blob0f993d51de852cc57d3e9e12315d4a4a197bec20
1 /*
2 Filter Dialog
3 Author: Marc Mutz <Marc@Mutz.com>
4 based upon work by Stefan Taferner <taferner@kde.org>
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program 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
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License along
17 with this program; if not, write to the Free Software Foundation, Inc.,
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 #include "kmfilterdlg.h"
23 // other KMail headers:
24 #include "mailcommon/searchpatternedit.h"
25 #include "mailcommon/filtermanager.h"
26 #include "kmmainwidget.h"
27 #include "mailcommon/filterimporterexporter.h"
28 #include "mailcommon/filteractionwidget.h"
29 #include "mailcommon/mailutil.h"
30 using MailCommon::FilterImporterExporter;
32 // KDEPIMLIBS headers
33 #include <Akonadi/AgentType>
34 #include <Akonadi/AgentInstance>
36 // other KDE headers:
37 #include <kcombobox.h>
38 #include <kmessagebox.h>
39 #include <kdebug.h>
40 #include <klocale.h>
41 #include <kinputdialog.h>
42 #include <kiconloader.h>
43 #include <kwindowsystem.h>
44 #include <kconfig.h>
45 #include <kicondialog.h>
46 #include <kkeysequencewidget.h>
47 #include <kpushbutton.h>
48 #include <kconfiggroup.h>
49 #include <ktabwidget.h>
50 #include <kvbox.h>
51 #include <klistwidgetsearchline.h>
53 // Qt headers:
54 #include <QCheckBox>
55 #include <QGridLayout>
56 #include <QLabel>
57 #include <QListWidget>
58 #include <QButtonGroup>
59 #include <QVBoxLayout>
60 #include <QTreeWidget>
62 // other headers:
63 #include <assert.h>
65 using namespace MailCommon;
67 // What's this help texts
68 const char * _wt_filterlist =
69 I18N_NOOP( "<qt><p>This is the list of defined filters. "
70 "They are processed top-to-bottom.</p>"
71 "<p>Click on any filter to edit it "
72 "using the controls in the right-hand half "
73 "of the dialog.</p></qt>" );
74 const char * _wt_filterlist_new =
75 I18N_NOOP( "<qt><p>Click this button to create a new filter.</p>"
76 "<p>The filter will be inserted just before the currently-"
77 "selected one, but you can always change that "
78 "later on.</p>"
79 "<p>If you have clicked this button accidentally, you can undo this "
80 "by clicking on the <em>Delete</em> button.</p></qt>" );
81 const char * _wt_filterlist_copy =
82 I18N_NOOP( "<qt><p>Click this button to copy a filter.</p>"
83 "<p>If you have clicked this button accidentally, you can undo this "
84 "by clicking on the <em>Delete</em> button.</p></qt>" );
85 const char * _wt_filterlist_delete =
86 I18N_NOOP( "<qt><p>Click this button to <em>delete</em> the currently-"
87 "selected filter from the list above.</p>"
88 "<p>There is no way to get the filter back once "
89 "it is deleted, but you can always leave the "
90 "dialog by clicking <em>Cancel</em> to discard the "
91 "changes made.</p></qt>" );
92 const char * _wt_filterlist_up =
93 I18N_NOOP( "<qt><p>Click this button to move the currently-"
94 "selected filter <em>up</em> one in the list above.</p>"
95 "<p>This is useful since the order of the filters in the list "
96 "determines the order in which they are tried on messages: "
97 "The topmost filter gets tried first.</p>"
98 "<p>If you have clicked this button accidentally, you can undo this "
99 "by clicking on the <em>Down</em> button.</p></qt>" );
100 const char * _wt_filterlist_down =
101 I18N_NOOP( "<qt><p>Click this button to move the currently-"
102 "selected filter <em>down</em> one in the list above.</p>"
103 "<p>This is useful since the order of the filters in the list "
104 "determines the order in which they are tried on messages: "
105 "The topmost filter gets tried first.</p>"
106 "<p>If you have clicked this button accidentally, you can undo this "
107 "by clicking on the <em>Up</em> button.</p></qt>" );
108 const char * _wt_filterlist_rename =
109 I18N_NOOP( "<qt><p>Click this button to rename the currently-selected filter.</p>"
110 "<p>Filters are named automatically, as long as they start with "
111 "\"&lt;\".</p>"
112 "<p>If you have renamed a filter accidentally and want automatic "
113 "naming back, click this button and select <em>Clear</em> followed "
114 "by <em>OK</em> in the appearing dialog.</p></qt>" );
115 const char * _wt_filterdlg_showLater =
116 I18N_NOOP( "<qt><p>Check this button to force the confirmation dialog to be "
117 "displayed.</p><p>This is useful if you have defined a ruleset that tags "
118 "messages to be downloaded later. Without the possibility to force "
119 "the dialog popup, these messages could never be downloaded if no "
120 "other large messages were waiting on the server, or if you wanted to "
121 "change the ruleset to tag the messages differently.</p></qt>" );
123 // The anchor of the filter dialog's help.
124 const char * KMFilterDlgHelpAnchor = "filters" ;
126 //=============================================================================
128 // class KMFilterDlg (the filter dialog)
130 //=============================================================================
132 KMFilterDlg::KMFilterDlg(QWidget* parent, bool createDummyFilter )
133 : KDialog( parent ),
134 mDoNotClose( false ),
135 mIgnoreFilterUpdates( true )
137 setCaption( i18n("Filter Rules") );
138 setButtons( Help|Ok|Apply|Cancel|User1|User2 );
139 setModal( false );
140 setButtonFocus( Ok );
141 KWindowSystem::setIcons( winId(), qApp->windowIcon().pixmap(IconSize(KIconLoader::Desktop),IconSize(KIconLoader::Desktop)), qApp->windowIcon().pixmap(IconSize(KIconLoader::Small),IconSize(KIconLoader::Small)) );
142 setHelp( KMFilterDlgHelpAnchor, "kmail" );
143 setButtonText( User1, i18n("Import...") );
144 setButtonText( User2, i18n("Export...") );
145 connect( this, SIGNAL(user1Clicked()),
146 this, SLOT(slotImportFilters()) );
147 connect( this, SIGNAL(user2Clicked()),
148 this, SLOT(slotExportFilters()) );
149 enableButtonApply( false );
151 QWidget *w = new QWidget( this );
152 setMainWidget( w );
153 QHBoxLayout *topLayout = new QHBoxLayout( w );
154 topLayout->setObjectName( "topLayout" );
155 topLayout->setSpacing( spacingHint() );
156 topLayout->setMargin( 0 );
157 QHBoxLayout *hbl = topLayout;
158 QVBoxLayout *vbl2 = 0;
159 QWidget *page1 = 0;
160 QWidget *page2 = 0;
162 mFilterList = new KMFilterListBox( i18n("Available Filters"), w );
163 topLayout->addWidget( mFilterList, 1 /*stretch*/ );
165 KTabWidget *tabWidget = new KTabWidget( w );
166 tabWidget->setObjectName( "kmfd_tab" );
167 topLayout->addWidget( tabWidget );
169 page1 = new QWidget( tabWidget );
170 tabWidget->addTab( page1, i18nc("General mail filter settings.", "General") );
171 hbl = new QHBoxLayout( page1 );
172 hbl->setObjectName( "kmfd_hbl" );
173 hbl->setSpacing( spacingHint() );
174 hbl->setMargin( marginHint() );
176 page2 = new QWidget( tabWidget );
177 tabWidget->addTab( page2, i18nc("Advanced mail filter settings.","Advanced") );
178 vbl2 = new QVBoxLayout( page2 );
179 vbl2->setObjectName( "kmfd_vbl2" );
180 vbl2->setSpacing( spacingHint() );
181 vbl2->setMargin( marginHint() );
183 QVBoxLayout *vbl = new QVBoxLayout();
184 hbl->addLayout( vbl );
185 vbl->setObjectName( "kmfd_vbl" );
186 vbl->setSpacing( spacingHint() );
187 hbl->setStretchFactor( vbl, 2 );
189 QGroupBox *patternGroupBox = new QGroupBox( i18n("Filter Criteria"), page1 );
190 QHBoxLayout *layout = new QHBoxLayout( patternGroupBox );
191 layout->setContentsMargins( 0, 0, 0, 0 );
192 mPatternEdit = new MailCommon::SearchPatternEdit(patternGroupBox);
193 layout->addWidget( mPatternEdit );
195 vbl->addWidget( patternGroupBox, 0, Qt::AlignTop );
197 QGroupBox *agb = new QGroupBox( i18n("Filter Actions"), page1 );
198 QHBoxLayout *layout2 = new QHBoxLayout;
199 mActionLister = new MailCommon::FilterActionWidgetLister( agb );
200 layout2->addWidget( mActionLister );
201 agb->setLayout( layout2 );
202 vbl->addWidget( agb, 0, Qt::AlignTop );
204 mAdvOptsGroup = new QGroupBox (i18n("Advanced Options"), page2);
206 QGridLayout *gl = new QGridLayout();
207 QVBoxLayout *vbl3 = new QVBoxLayout();
208 gl->addLayout( vbl3, 0, 0 );
209 vbl3->setObjectName( "vbl3" );
210 vbl3->setSpacing( spacingHint() );
211 vbl3->addStretch( 1 );
212 mApplyOnIn = new QCheckBox( i18n("Apply this filter to incoming messages:"), mAdvOptsGroup );
213 vbl3->addWidget( mApplyOnIn );
214 QButtonGroup *bg = new QButtonGroup( mAdvOptsGroup );
215 bg->setObjectName( "bg" );
216 mApplyOnForAll = new QRadioButton( i18n("from all accounts"), mAdvOptsGroup );
217 bg->addButton( mApplyOnForAll );
218 vbl3->addWidget( mApplyOnForAll );
219 mApplyOnForTraditional = new QRadioButton( i18n("from all but online IMAP accounts"), mAdvOptsGroup );
220 bg->addButton( mApplyOnForTraditional );
221 vbl3->addWidget( mApplyOnForTraditional );
222 mApplyOnForChecked = new QRadioButton( i18n("from checked accounts only"), mAdvOptsGroup );
223 bg->addButton( mApplyOnForChecked );
224 vbl3->addWidget( mApplyOnForChecked );
225 vbl3->addStretch( 2 );
227 mAccountList = new QTreeWidget( mAdvOptsGroup );
228 mAccountList->setObjectName( "accountList" );
229 mAccountList->setColumnCount( 2 );
230 QStringList headerNames;
231 headerNames << i18n("Account Name") << i18n("Type");
232 mAccountList->setHeaderItem( new QTreeWidgetItem( headerNames ) );
233 mAccountList->setAllColumnsShowFocus( true );
234 mAccountList->setFrameStyle( QFrame::WinPanel + QFrame::Sunken );
235 mAccountList->setSortingEnabled( false );
236 mAccountList->setRootIsDecorated( false );
237 gl->addWidget( mAccountList, 0, 1, 4, 3 );
239 mApplyBeforeOut = new QCheckBox( i18n("Apply this filter &before sending messages"), mAdvOptsGroup );
240 mApplyBeforeOut->setToolTip( i18n( "<p>The filter will be triggered <b>before</b> the message is sent and it will affect both the local copy and the sent copy of the message.</p>"
241 "<p>This is required if the recipient's copy also needs to be modified.</p>" ) );
242 gl->addWidget( mApplyBeforeOut, 5, 0, 1, 4 );
244 mApplyOnOut = new QCheckBox( i18n("Apply this filter to &sent messages"), mAdvOptsGroup );
245 mApplyOnOut->setToolTip( i18n( "<p>The filter will be triggered <b>after</b> the message is sent and it will only affect the local copy of the message.</p>"
246 "<p>If the recipient's copy also needs to be modified, please use \"Apply this filter <b>before</b> sending messages\".</p>" ) );
247 gl->addWidget( mApplyOnOut, 4, 0, 1, 4 );
249 mApplyOnCtrlJ = new QCheckBox( i18n("Apply this filter on manual &filtering"), mAdvOptsGroup );
250 gl->addWidget( mApplyOnCtrlJ, 6, 0, 1, 4 );
252 mStopProcessingHere = new QCheckBox( i18n("If this filter &matches, stop processing here"), mAdvOptsGroup );
253 gl->addWidget( mStopProcessingHere, 7, 0, 1, 4 );
254 mConfigureShortcut = new QCheckBox( i18n("Add this filter to the Apply Filter menu"), mAdvOptsGroup );
255 gl->addWidget( mConfigureShortcut, 8, 0, 1, 2 );
256 QLabel *keyButtonLabel = new QLabel( i18n( "Shortcut:" ), mAdvOptsGroup );
257 keyButtonLabel->setAlignment( Qt::AlignVCenter | Qt::AlignRight );
258 gl->addWidget( keyButtonLabel, 8, 2, 1, 1);
259 mKeySeqWidget = new KKeySequenceWidget( mAdvOptsGroup );
260 mKeySeqWidget->setObjectName( "FilterShortcutSelector" );
261 gl->addWidget( mKeySeqWidget, 8, 3, 1, 1);
262 mKeySeqWidget->setEnabled( false );
263 mKeySeqWidget->setModifierlessAllowed( true );
264 mKeySeqWidget->setCheckActionCollections(
265 kmkernel->getKMMainWidget()->actionCollections() );
266 mConfigureToolbar = new QCheckBox( i18n("Additionally add this filter to the toolbar"), mAdvOptsGroup );
267 gl->addWidget( mConfigureToolbar, 9, 0, 1, 4 );
268 mConfigureToolbar->setEnabled( false );
270 KHBox *hbox = new KHBox( mAdvOptsGroup );
271 mFilterActionLabel = new QLabel( i18n( "Icon for this filter:" ),
272 hbox );
273 mFilterActionLabel->setEnabled( false );
275 mFilterActionIconButton = new KIconButton( hbox );
276 mFilterActionLabel->setBuddy( mFilterActionIconButton );
277 mFilterActionIconButton->setIconType( KIconLoader::NoGroup, KIconLoader::Action, false );
278 mFilterActionIconButton->setIconSize( 16 );
279 mFilterActionIconButton->setIcon( "system-run" );
280 mFilterActionIconButton->setEnabled( false );
282 gl->addWidget( hbox, 10, 0, 1, 4 );
284 mAdvOptsGroup->setLayout( gl );
286 vbl2->addWidget( mAdvOptsGroup, 0, Qt::AlignTop );
288 // spacer:
289 vbl->addStretch( 1 );
291 // load the filter parts into the edit widgets
292 connect( mFilterList, SIGNAL(filterSelected(MailCommon::MailFilter*)),
293 this, SLOT(slotFilterSelected(MailCommon::MailFilter*)) );
295 // transfer changes from the 'Apply this filter on...'
296 // combo box to the filter
297 connect( mApplyOnIn, SIGNAL(clicked()),
298 this, SLOT(slotApplicabilityChanged()) );
299 connect( mApplyOnForAll, SIGNAL(clicked()),
300 this, SLOT(slotApplicabilityChanged()) );
301 connect( mApplyOnForTraditional, SIGNAL(clicked()),
302 this, SLOT(slotApplicabilityChanged()) );
303 connect( mApplyOnForChecked, SIGNAL(clicked()),
304 this, SLOT(slotApplicabilityChanged()) );
305 connect( mApplyBeforeOut, SIGNAL(clicked()),
306 this, SLOT(slotApplicabilityChanged()) );
307 connect( mApplyOnOut, SIGNAL(clicked()),
308 this, SLOT(slotApplicabilityChanged()) );
309 connect( mApplyOnCtrlJ, SIGNAL(clicked()),
310 this, SLOT(slotApplicabilityChanged()) );
311 connect( mAccountList, SIGNAL(itemChanged(QTreeWidgetItem*,int)),
312 this, SLOT(slotApplicableAccountsChanged()) );
314 // transfer changes from the 'stop processing here'
315 // check box to the filter
316 connect( mStopProcessingHere, SIGNAL(toggled(bool)),
317 this, SLOT(slotStopProcessingButtonToggled(bool)) );
319 connect( mConfigureShortcut, SIGNAL(toggled(bool)),
320 this, SLOT(slotConfigureShortcutButtonToggled(bool)) );
322 connect( mKeySeqWidget, SIGNAL(keySequenceChanged(QKeySequence)),
323 this, SLOT(slotShortcutChanged(QKeySequence)) );
325 connect( mConfigureToolbar, SIGNAL(toggled(bool)),
326 this, SLOT(slotConfigureToolbarButtonToggled(bool)) );
328 connect( mFilterActionIconButton, SIGNAL(iconChanged(QString)),
329 this, SLOT(slotFilterActionIconChanged(QString)) );
331 // reset all widgets here
332 connect( mFilterList, SIGNAL(resetWidgets()),
333 this, SLOT(slotReset()) );
335 connect( mFilterList, SIGNAL(applyWidgets()),
336 this, SLOT(slotUpdateFilter()) );
338 // support auto-naming the filter
339 connect( mPatternEdit, SIGNAL(maybeNameChanged()),
340 mFilterList, SLOT(slotUpdateFilterName()) );
342 // save filters on 'Apply' or 'OK'
343 connect( this, SIGNAL(buttonClicked(KDialog::ButtonCode)),
344 mFilterList, SLOT(slotApplyFilterChanges(KDialog::ButtonCode)) );
345 connect( button( KDialog::Apply ), SIGNAL(clicked(bool)), this, SLOT(slotApply()) );
347 // save dialog size on 'OK'
348 connect( this, SIGNAL(okClicked()),
349 this, SLOT(slotSaveSize()) );
351 // destruct the dialog on close and Cancel
352 connect( this, SIGNAL(closeClicked()),
353 this, SLOT(slotFinished()) );
354 connect( this, SIGNAL(cancelClicked()),
355 this, SLOT(slotFinished()) );
357 // disable closing when user wants to continue editing
358 connect( mFilterList, SIGNAL(abortClosing()),
359 this, SLOT(slotDisableAccept()) );
361 connect( mFilterList, SIGNAL(filterCreated()), this, SLOT(slotDialogUpdated()) );
362 connect( mFilterList, SIGNAL(filterRemoved(MailCommon::MailFilter*)),
363 this, SLOT(slotDialogUpdated()) );
364 connect( mFilterList, SIGNAL(filterUpdated(MailCommon::MailFilter*)),
365 this, SLOT(slotDialogUpdated()) );
366 connect( mFilterList, SIGNAL(filterOrderAltered()), this, SLOT(slotDialogUpdated()) );
367 connect( mPatternEdit, SIGNAL(patternChanged()), this, SLOT(slotDialogUpdated()) );
368 connect( mActionLister, SIGNAL(widgetAdded(QWidget*)), this, SLOT(slotDialogUpdated()) );
369 connect( mActionLister, SIGNAL(widgetRemoved()), this, SLOT(slotDialogUpdated()) );
370 connect( mActionLister, SIGNAL(filterModified()), this, SLOT(slotDialogUpdated()) );
372 if ( GlobalSettings::self()->filterDialogSize() != QSize() )
373 resize( GlobalSettings::self()->filterDialogSize() );
374 else
375 adjustSize();
377 // load the filter list (emits filterSelected())
378 mFilterList->loadFilterList( createDummyFilter );
379 mIgnoreFilterUpdates = false;
382 void KMFilterDlg::accept()
384 if ( mDoNotClose ) {
385 mDoNotClose = false; // only abort current close attempt
386 } else {
387 KDialog::accept();
388 slotFinished();
392 void KMFilterDlg::slotApply()
394 enableButtonApply( false );
397 void KMFilterDlg::slotFinished() {
398 deleteLater();
401 void KMFilterDlg::slotSaveSize() {
402 GlobalSettings::self()->setFilterDialogSize( size() );
405 void KMFilterDlg::slotFilterSelected( MailFilter* aFilter )
407 assert( aFilter );
408 mIgnoreFilterUpdates = true;
409 mActionLister->setActionList( aFilter->actions() );
411 mAdvOptsGroup->setEnabled( true );
413 mPatternEdit->setSearchPattern( aFilter->pattern() );
414 mFilter = aFilter;
416 kDebug() << "apply on inbound ==" << aFilter->applyOnInbound();
417 kDebug() << "apply on outbound ==" << aFilter->applyOnOutbound();
418 kDebug() << "apply before outbound == " << aFilter->applyBeforeOutbound();
419 kDebug() << "apply on explicit ==" << aFilter->applyOnExplicit();
421 // NOTE: setting these values activates the slot that sets them in
422 // the filter! So make sure we have the correct values _before_ we
423 // set the first one:
424 const bool applyOnIn = aFilter->applyOnInbound();
425 const bool applyOnForAll = aFilter->applicability() == MailFilter::All;
426 const bool applyOnTraditional = aFilter->applicability() == MailFilter::ButImap;
427 const bool applyBeforeOut = aFilter->applyBeforeOutbound();
428 const bool applyOnOut = aFilter->applyOnOutbound();
429 const bool applyOnExplicit = aFilter->applyOnExplicit();
430 const bool stopHere = aFilter->stopProcessingHere();
431 const bool configureShortcut = aFilter->configureShortcut();
432 const bool configureToolbar = aFilter->configureToolbar();
433 const QString icon = aFilter->icon();
434 const KShortcut shortcut( aFilter->shortcut() );
436 mApplyOnIn->setChecked( applyOnIn );
437 mApplyOnForAll->setEnabled( applyOnIn );
438 mApplyOnForTraditional->setEnabled( applyOnIn );
439 mApplyOnForChecked->setEnabled( applyOnIn );
440 mApplyOnForAll->setChecked( applyOnForAll );
441 mApplyOnForTraditional->setChecked( applyOnTraditional );
442 mApplyOnForChecked->setChecked( !applyOnForAll && !applyOnTraditional );
443 mAccountList->setEnabled( mApplyOnForChecked->isEnabled() && mApplyOnForChecked->isChecked() );
444 slotUpdateAccountList();
445 mApplyBeforeOut->setChecked( applyBeforeOut );
446 mApplyOnOut->setChecked( applyOnOut );
447 mApplyOnCtrlJ->setChecked( applyOnExplicit );
448 mStopProcessingHere->setChecked( stopHere );
449 mConfigureShortcut->setChecked( configureShortcut );
450 mKeySeqWidget->setKeySequence( shortcut.primary(),
451 KKeySequenceWidget::NoValidate );
452 mConfigureToolbar->setChecked( configureToolbar );
453 mFilterActionIconButton->setIcon( icon );
454 mIgnoreFilterUpdates = false;
457 void KMFilterDlg::slotReset()
459 mFilter = 0;
460 mPatternEdit->reset();
462 mActionLister->reset();
463 mAdvOptsGroup->setEnabled( false );
464 slotUpdateAccountList();
467 void KMFilterDlg::slotUpdateFilter()
469 mPatternEdit->updateSearchPattern();
470 mActionLister->updateActionList();
473 void KMFilterDlg::slotApplicabilityChanged()
475 if ( mFilter ) {
476 mFilter->setApplyOnInbound( mApplyOnIn->isChecked() );
477 mFilter->setApplyBeforeOutbound( mApplyBeforeOut->isChecked() );
478 mFilter->setApplyOnOutbound( mApplyOnOut->isChecked() );
479 mFilter->setApplyOnExplicit( mApplyOnCtrlJ->isChecked() );
480 if ( mApplyOnForAll->isChecked() )
481 mFilter->setApplicability( MailFilter::All );
482 else if ( mApplyOnForTraditional->isChecked() )
483 mFilter->setApplicability( MailFilter::ButImap );
484 else if ( mApplyOnForChecked->isChecked() )
485 mFilter->setApplicability( MailFilter::Checked );
487 mApplyOnForAll->setEnabled( mApplyOnIn->isChecked() );
488 mApplyOnForTraditional->setEnabled( mApplyOnIn->isChecked() );
489 mApplyOnForChecked->setEnabled( mApplyOnIn->isChecked() );
490 mAccountList->setEnabled( mApplyOnForChecked->isEnabled() && mApplyOnForChecked->isChecked() );
492 // Advanced tab functionality - Update list of accounts this filter applies to
493 QTreeWidgetItemIterator it( mAccountList );
494 while( QTreeWidgetItem * item = *it ) {
495 QString id = item->text( 2 );
496 item->setCheckState( 0, mFilter->applyOnAccount( id ) ? Qt::Checked :
497 Qt::Unchecked );
498 ++it;
501 // Enable the apply button
502 slotDialogUpdated();
504 kDebug() << "Setting filter to be applied at"
505 << ( mFilter->applyOnInbound() ? "incoming " : "" )
506 << ( mFilter->applyOnOutbound() ? "outgoing " : "" )
507 << ( mFilter->applyBeforeOutbound() ? "before_outgoing " : "" )
508 << ( mFilter->applyOnExplicit() ? "explicit CTRL-J" : "" );
512 void KMFilterDlg::slotApplicableAccountsChanged()
514 // Advanced tab functionality - Update list of accounts this filter applies to
515 if ( mFilter && mApplyOnForChecked->isEnabled() && mApplyOnForChecked->isChecked() ) {
517 QTreeWidgetItemIterator it( mAccountList );
519 while( QTreeWidgetItem *item = *it ) {
520 const QString id = item->text( 2 );
521 mFilter->setApplyOnAccount( id, item->checkState( 0 ) == Qt::Checked );
522 ++it;
525 // Enable the apply button
526 slotDialogUpdated();
530 void KMFilterDlg::slotStopProcessingButtonToggled( bool aChecked )
532 if ( mFilter ) {
533 mFilter->setStopProcessingHere( aChecked );
535 // Enable the apply button
536 slotDialogUpdated();
540 void KMFilterDlg::slotConfigureShortcutButtonToggled( bool aChecked )
542 if ( mFilter ) {
543 mFilter->setConfigureShortcut( aChecked );
544 mKeySeqWidget->setEnabled( aChecked );
545 mConfigureToolbar->setEnabled( aChecked );
546 mFilterActionIconButton->setEnabled( aChecked );
547 mFilterActionLabel->setEnabled( aChecked );
549 // Enable the apply button
550 slotDialogUpdated();
554 void KMFilterDlg::slotShortcutChanged( const QKeySequence &newSeq )
556 if ( mFilter ) {
557 mKeySeqWidget->applyStealShortcut();
558 mFilter->setShortcut( KShortcut( newSeq ) );
560 // Enable the apply button
561 slotDialogUpdated();
565 void KMFilterDlg::slotConfigureToolbarButtonToggled( bool aChecked )
567 if ( mFilter )
568 mFilter->setConfigureToolbar( aChecked );
571 void KMFilterDlg::slotFilterActionIconChanged( const QString &icon )
573 if ( mFilter )
574 mFilter->setIcon( icon );
577 void KMFilterDlg::slotUpdateAccountList()
579 mAccountList->clear();
581 QTreeWidgetItem *top = 0;
582 // Block the signals here, otherwise we end up calling
583 // slotApplicableAccountsChanged(), which will read the incomplete item
584 // state and write that back to the filter
585 mAccountList->blockSignals( true );
586 const Akonadi::AgentInstance::List lst = MailCommon::Util::agentInstances();
587 const int nbAccount = lst.count();
588 for ( int i = 0; i <nbAccount; ++i ) {
589 if( lst.at( i ).type().identifier() == QLatin1String( "akonadi_nepomuktag_resource" ) )
590 continue;
591 QTreeWidgetItem *listItem = new QTreeWidgetItem( mAccountList, top );
592 listItem->setText( 0, lst.at( i ).name() );
593 listItem->setText( 1, lst.at( i ).type().name() );
594 listItem->setText( 2, QString( "%1" ).arg( lst.at( i ).identifier() ) );
595 if ( mFilter )
596 listItem->setCheckState( 0, mFilter->applyOnAccount( lst.at( i ).identifier() ) ?
597 Qt::Checked : Qt::Unchecked );
598 top = listItem;
600 mAccountList->blockSignals( false );
602 // make sure our hidden column is really hidden (Qt tends to re-show it)
603 mAccountList->hideColumn( 2 );
604 mAccountList->resizeColumnToContents( 0 );
605 mAccountList->resizeColumnToContents( 1 );
607 top = mAccountList->topLevelItem( 0 );
608 if ( top ) {
609 mAccountList->setCurrentItem( top );
613 //=============================================================================
615 // class KMFilterListBox (the filter list manipulator)
617 //=============================================================================
619 KMFilterListBox::KMFilterListBox( const QString & title, QWidget *parent )
620 : QGroupBox( title, parent )
622 QVBoxLayout *layout = new QVBoxLayout();
624 mIdxSelItem = -1;
626 //----------- the list box
627 mListWidget = new QListWidget(this);
628 mListWidget->setMinimumWidth(150);
629 mListWidget->setWhatsThis( i18n(_wt_filterlist) );
631 KListWidgetSearchLine* mSearchListWidget = new KListWidgetSearchLine( this, mListWidget );
632 mSearchListWidget->setClickMessage( i18nc( "@info/plain Displayed grayed-out inside the "
633 "textbox, verb to search", "Search" ) );
635 layout->addWidget( mSearchListWidget );
636 layout->addWidget( mListWidget );
638 //----------- the first row of buttons
639 KHBox *hb = new KHBox(this);
640 hb->setSpacing(4);
641 mBtnUp = new KPushButton( QString(), hb );
642 mBtnUp->setAutoRepeat( true );
643 mBtnUp->setIcon( KIcon( "go-up" ) );
644 mBtnUp->setIconSize( QSize( KIconLoader::SizeSmall, KIconLoader::SizeSmall ) );
645 mBtnUp->setMinimumSize( mBtnUp->sizeHint() * 1.2 );
646 mBtnDown = new KPushButton( QString(), hb );
647 mBtnDown->setAutoRepeat( true );
648 mBtnDown->setIcon( KIcon( "go-down" ) );
649 mBtnDown->setIconSize( QSize( KIconLoader::SizeSmall, KIconLoader::SizeSmall ) );
650 mBtnDown->setMinimumSize( mBtnDown->sizeHint() * 1.2 );
651 mBtnUp->setToolTip( i18nc("Move selected filter up.", "Up") );
652 mBtnDown->setToolTip( i18nc("Move selected filter down.", "Down") );
653 mBtnUp->setWhatsThis( i18n(_wt_filterlist_up) );
654 mBtnDown->setWhatsThis( i18n(_wt_filterlist_down) );
656 layout->addWidget( hb );
658 //----------- the second row of buttons
659 hb = new KHBox(this);
660 hb->setSpacing(4);
661 mBtnNew = new QPushButton( QString(), hb );
662 mBtnNew->setIcon( KIcon( "document-new" ) );
663 mBtnNew->setIconSize( QSize( KIconLoader::SizeSmall, KIconLoader::SizeSmall ) );
664 mBtnNew->setMinimumSize( mBtnNew->sizeHint() * 1.2 );
665 mBtnCopy = new QPushButton( QString(), hb );
666 mBtnCopy->setIcon( KIcon( "edit-copy" ) );
667 mBtnCopy->setIconSize( QSize( KIconLoader::SizeSmall, KIconLoader::SizeSmall ) );
668 mBtnCopy->setMinimumSize( mBtnCopy->sizeHint() * 1.2 );
669 mBtnDelete = new QPushButton( QString(), hb );
670 mBtnDelete->setIcon( KIcon( "edit-delete" ) );
671 mBtnDelete->setIconSize( QSize( KIconLoader::SizeSmall, KIconLoader::SizeSmall ) );
672 mBtnDelete->setMinimumSize( mBtnDelete->sizeHint() * 1.2 );
673 mBtnRename = new QPushButton( i18n("Rename..."), hb );
674 mBtnNew->setToolTip( i18nc("@action:button in filter list manipulator", "New") );
675 mBtnCopy->setToolTip( i18n("Copy") );
676 mBtnDelete->setToolTip( i18n("Delete"));
677 mBtnNew->setWhatsThis( i18n(_wt_filterlist_new) );
678 mBtnCopy->setWhatsThis( i18n(_wt_filterlist_copy) );
679 mBtnDelete->setWhatsThis( i18n(_wt_filterlist_delete) );
680 mBtnRename->setWhatsThis( i18n(_wt_filterlist_rename) );
682 layout->addWidget( hb );
683 setLayout( layout );
685 //----------- now connect everything
686 connect( mListWidget, SIGNAL(currentRowChanged(int)),
687 this, SLOT(slotSelected(int)) );
688 connect( mListWidget, SIGNAL(itemDoubleClicked(QListWidgetItem*)),
689 this, SLOT(slotRename()) );
690 connect( mBtnUp, SIGNAL(clicked()),
691 this, SLOT(slotUp()) );
692 connect( mBtnDown, SIGNAL(clicked()),
693 this, SLOT(slotDown()) );
694 connect( mBtnNew, SIGNAL(clicked()),
695 this, SLOT(slotNew()) );
696 connect( mBtnCopy, SIGNAL(clicked()),
697 this, SLOT(slotCopy()) );
698 connect( mBtnDelete, SIGNAL(clicked()),
699 this, SLOT(slotDelete()) );
700 connect( mBtnRename, SIGNAL(clicked()),
701 this, SLOT(slotRename()) );
703 // the dialog should call loadFilterList()
704 // when all signals are connected.
705 enableControls();
709 KMFilterListBox::~KMFilterListBox()
711 qDeleteAll( mFilterList );
715 void KMFilterListBox::createFilter( const QByteArray & field,
716 const QString & value )
718 SearchRule::Ptr newRule = SearchRule::createInstance( field, SearchRule::FuncContains, value );
720 MailFilter *newFilter = new MailFilter();
721 newFilter->pattern()->append( newRule );
722 newFilter->pattern()->setName( QString("<%1>:%2").arg( QString::fromLatin1( field ) ).arg( value) );
724 FilterActionDesc *desc = kmkernel->filterActionDict()->value( "transfer" );
725 if ( desc )
726 newFilter->actions()->append( desc->create() );
728 insertFilter( newFilter );
729 enableControls();
732 void KMFilterListBox::slotUpdateFilterName()
734 if ( mIdxSelItem < 0 ) {
735 kDebug() << "Called while no filter is selected, ignoring. idx=" << mIdxSelItem;
736 return;
739 SearchPattern *p = mFilterList.at(mIdxSelItem)->pattern();
740 if ( !p ) return;
742 QString shouldBeName = p->name();
743 QString displayedName = mListWidget->item( mIdxSelItem )->text();
745 if ( shouldBeName.trimmed().isEmpty() ) {
746 mFilterList.at(mIdxSelItem)->setAutoNaming( true );
749 if ( mFilterList.at(mIdxSelItem)->isAutoNaming() ) {
750 // auto-naming of patterns
751 if ( !p->isEmpty() && p->first() && !p->first()->field().trimmed().isEmpty() )
752 shouldBeName = QString( "<%1>: %2" ).arg( QString::fromLatin1( p->first()->field() ) ).arg( p->first()->contents() );
753 else
754 shouldBeName = '<' + i18n("unnamed") + '>';
755 p->setName( shouldBeName );
758 if ( displayedName == shouldBeName ) return;
760 mListWidget->blockSignals(true);
761 mListWidget->item( mIdxSelItem )->setText( shouldBeName );
762 mListWidget->blockSignals(false);
765 void KMFilterListBox::slotApplyFilterChanges( KDialog::ButtonCode button )
767 bool closeAfterSaving;
768 if ( button == KDialog::Ok )
769 closeAfterSaving = true;
770 else if ( button == KDialog::Apply )
771 closeAfterSaving = false;
772 else
773 return; // ignore close and cancel
775 if ( mIdxSelItem >= 0 ) {
776 emit applyWidgets();
777 slotSelected( mListWidget->currentRow() );
780 // by now all edit widgets should have written back
781 // their widget's data into our filter list.
783 FilterManager *fm = kmkernel->filterManager();
785 const QList<MailFilter*> newFilters = filtersForSaving( closeAfterSaving );
787 fm->setFilters( newFilters );
790 QList<MailFilter *> KMFilterListBox::filtersForSaving( bool closeAfterSaving ) const
792 const_cast<KMFilterListBox*>( this )->applyWidgets(); // signals aren't const
793 QList<MailFilter *> filters;
794 QStringList emptyFilters;
795 foreach ( MailFilter *const it, mFilterList ) {
796 MailFilter *f = new MailFilter( *it ); // deep copy
797 f->purify();
798 if ( !f->isEmpty() )
799 // the filter is valid:
800 filters.append( f );
801 else {
802 // the filter is invalid:
803 emptyFilters << f->name();
804 delete f;
808 // report on invalid filters:
809 if ( !emptyFilters.empty() ) {
810 if ( closeAfterSaving ) {
811 // Ok clicked. Give option to continue editing
812 int response = KMessageBox::warningContinueCancelList(
814 i18n( "The following filters are invalid (e.g. containing no actions "
815 "or no search rules). Discard or edit invalid filters?" ),
816 emptyFilters,
817 QString(),
818 KGuiItem( i18n( "Discard" ) ),
819 KStandardGuiItem::cancel(),
820 "ShowInvalidFilterWarning" );
821 if ( response == KMessageBox::Cancel )
822 emit abortClosing();
823 } else {
824 // Apply clicked. Just warn.
825 KMessageBox::informationList(
827 i18n( "The following filters have not been saved because they were invalid "
828 "(e.g. containing no actions or no search rules)." ),
829 emptyFilters,
830 QString(),
831 "ShowInvalidFilterWarning" );
834 return filters;
837 void KMFilterListBox::slotSelected( int aIdx )
839 kDebug() << "idx=" << aIdx;
840 mIdxSelItem = aIdx;
842 if ( mIdxSelItem >= 0 && mIdxSelItem < mFilterList.count() ) {
843 MailFilter *f = mFilterList.at(aIdx);
844 if ( f )
845 emit filterSelected( f );
846 else
847 emit resetWidgets();
848 } else {
849 emit resetWidgets();
851 enableControls();
854 void KMFilterListBox::slotNew()
856 // just insert a new filter.
857 insertFilter( new MailFilter() );
858 enableControls();
861 void KMFilterListBox::slotCopy()
863 if ( mIdxSelItem < 0 ) {
864 kDebug() << "Called while no filter is selected, ignoring.";
865 return;
868 // make sure that all changes are written to the filter before we copy it
869 emit applyWidgets();
871 MailFilter *filter = mFilterList.at( mIdxSelItem );
873 // enableControls should make sure this method is
874 // never called when no filter is selected.
875 assert( filter );
877 // inserts a copy of the current filter.
878 insertFilter( new MailFilter( *filter ) );
879 enableControls();
882 void KMFilterListBox::slotDelete()
884 if ( mIdxSelItem < 0 ) {
885 kDebug() << "Called while no filter is selected, ignoring.";
886 return;
889 int oIdxSelItem = mIdxSelItem;
890 mIdxSelItem = -1;
891 // unselect all
892 // TODO remove this line: mListWidget->clearSelection();
893 // broadcast that all widgets let go
894 // of the filter
895 emit resetWidgets();
897 // remove the filter from both the listbox
898 mListWidget->takeItem( oIdxSelItem );
899 // and the filter list...
900 MailCommon::MailFilter *deletedFilter = mFilterList.takeAt( oIdxSelItem );
903 int count = mListWidget->count();
904 // and set the new current item.
905 if ( count > oIdxSelItem )
906 // oIdxItem is still a valid index
907 mListWidget->setCurrentRow( oIdxSelItem );
908 else if ( count )
909 // oIdxSelIdx is no longer valid, but the
910 // list box isn't empty
911 mListWidget->setCurrentRow( count - 1 );
913 // work around a problem when deleting the first item in a QListWidget:
914 // after takeItem, slotSelectionChanged is emitted with 1, but the row 0
915 // remains selected and another selectCurrentRow(0) does not trigger the
916 // selectionChanged signal
917 // (qt-copy as of 2006-12-22 / gungl)
918 if ( oIdxSelItem == 0 )
919 slotSelected( 0 );
921 mIdxSelItem = mListWidget->currentRow();
922 enableControls();
924 emit filterRemoved( deletedFilter );
927 void KMFilterListBox::slotUp()
929 if ( mIdxSelItem < 0 ) {
930 kDebug() << "Called while no filter is selected, ignoring.";
931 return;
933 if ( mIdxSelItem == 0 ) {
934 kDebug() << "Called while the _topmost_ filter is selected, ignoring.";
935 return;
938 swapNeighbouringFilters( mIdxSelItem, mIdxSelItem - 1 );
939 enableControls();
941 emit filterOrderAltered();
944 void KMFilterListBox::slotDown()
946 if ( mIdxSelItem < 0 ) {
947 kDebug() << "Called while no filter is selected, ignoring.";
948 return;
950 if ( mIdxSelItem == (int)mListWidget->count() - 1 ) {
951 kDebug() << "Called while the _last_ filter is selected, ignoring.";
952 return;
955 swapNeighbouringFilters( mIdxSelItem, mIdxSelItem + 1);
956 enableControls();
958 emit filterOrderAltered();
961 void KMFilterListBox::slotRename()
963 if ( mIdxSelItem < 0 ) {
964 kDebug() << "Called while no filter is selected, ignoring.";
965 return;
968 bool okPressed = false;
969 MailFilter *filter = mFilterList.at( mIdxSelItem );
971 // enableControls should make sure this method is
972 // never called when no filter is selected.
973 assert( filter );
975 // allow empty names - those will turn auto-naming on again
976 QValidator *validator = new QRegExpValidator( QRegExp( ".*" ), 0 );
977 QString newName = KInputDialog::getText
979 i18n("Rename Filter"),
980 i18n("Rename filter \"%1\" to:\n(leave the field empty for automatic naming)",
981 filter->pattern()->name() ) /*label*/,
982 filter->pattern()->name() /* initial value */,
983 &okPressed, window(), validator
985 delete validator;
987 if ( !okPressed ) return;
989 if ( newName.isEmpty() ) {
990 // bait for slotUpdateFilterName to
991 // use automatic naming again.
992 filter->pattern()->setName( "<>" );
993 filter->setAutoNaming( true );
994 } else {
995 filter->pattern()->setName( newName );
996 filter->setAutoNaming( false );
999 slotUpdateFilterName();
1001 emit filterUpdated( filter );
1004 void KMFilterListBox::enableControls()
1006 bool theFirst = ( mIdxSelItem == 0 );
1007 bool theLast = ( mIdxSelItem >= (int)mFilterList.count() - 1 );
1008 bool aFilterIsSelected = ( mIdxSelItem >= 0 );
1010 mBtnUp->setEnabled( aFilterIsSelected && !theFirst );
1011 mBtnDown->setEnabled( aFilterIsSelected && !theLast );
1012 mBtnCopy->setEnabled( aFilterIsSelected );
1013 mBtnDelete->setEnabled( aFilterIsSelected );
1014 mBtnRename->setEnabled( aFilterIsSelected );
1016 if ( aFilterIsSelected )
1017 mListWidget->scrollToItem( mListWidget->currentItem() );
1020 void KMFilterListBox::loadFilterList( bool createDummyFilter )
1022 assert(mListWidget);
1023 setEnabled(false);
1024 emit resetWidgets();
1025 // we don't want the insertion to
1026 // cause flicker in the edit widgets.
1027 blockSignals(true);
1029 // clear both lists
1030 mFilterList.clear();
1031 mListWidget->clear();
1033 const FilterManager *manager = kmkernel->filterManager();
1034 Q_ASSERT( manager );
1036 QList<MailFilter*>::const_iterator it;
1037 for ( it = manager->filters().constBegin() ;
1038 it != manager->filters().constEnd();
1039 ++it ) {
1040 mFilterList.append( new MailFilter( **it ) ); // deep copy
1041 mListWidget->addItem( (*it)->pattern()->name() );
1044 blockSignals(false);
1045 setEnabled(true);
1047 // create an empty filter when there's none, to avoid a completely
1048 // disabled dialog (usability tests indicated that the new-filter
1049 // button is too hard to find that way):
1050 if ( !mListWidget->count() && createDummyFilter )
1051 slotNew();
1053 if ( mListWidget->count() > 0 )
1054 mListWidget->setCurrentRow( 0 );
1056 enableControls();
1059 void KMFilterListBox::insertFilter( MailFilter* aFilter )
1061 // must be really a filter...
1062 assert( aFilter );
1064 // if mIdxSelItem < 0, QListBox::insertItem will append.
1065 mListWidget->insertItem( mIdxSelItem, aFilter->pattern()->name() );
1066 if ( mIdxSelItem < 0 ) {
1067 // none selected -> append
1068 mFilterList.append( aFilter );
1069 mListWidget->setCurrentRow( mListWidget->count() - 1 );
1070 } else {
1071 // insert just before selected
1072 mFilterList.insert( mIdxSelItem, aFilter );
1073 mListWidget->setCurrentRow( mIdxSelItem );
1076 emit filterCreated();
1077 emit filterOrderAltered();
1080 void KMFilterListBox::appendFilter( MailFilter* aFilter )
1082 mFilterList.append( aFilter );
1083 mListWidget->addItems( QStringList( aFilter->pattern()->name() ) );
1085 emit filterCreated();
1088 void KMFilterListBox::swapNeighbouringFilters( int untouchedOne, int movedOne )
1090 // must be neighbours...
1091 assert( untouchedOne - movedOne == 1 || movedOne - untouchedOne == 1 );
1093 // untouchedOne is at idx. to move it down(up),
1094 // remove item at idx+(-)1 w/o deleting it.
1095 QListWidgetItem *item = mListWidget->item( movedOne );
1096 mListWidget->takeItem( movedOne );
1097 // now selected item is at idx(idx-1), so
1098 // insert the other item at idx, ie. above(below).
1099 mListWidget->insertItem( untouchedOne, item );
1101 MailFilter* filter = mFilterList.takeAt( movedOne );
1102 mFilterList.insert( untouchedOne, filter );
1104 mIdxSelItem += movedOne - untouchedOne;
1108 void KMFilterDlg::slotImportFilters()
1110 FilterImporterExporter importer( this );
1111 QList<MailFilter *> filters = importer.importFilters();
1113 // FIXME message box how many were imported?
1114 if ( filters.isEmpty() ) return;
1116 QList<MailFilter*>::ConstIterator it;
1118 for ( it = filters.constBegin() ; it != filters.constEnd() ; ++it ) {
1119 mFilterList->appendFilter( *it ); // no need to deep copy, ownership passes to the list
1123 void KMFilterDlg::slotExportFilters()
1125 FilterImporterExporter exporter( this );
1126 QList<MailFilter *> filters = mFilterList->filtersForSaving( false );
1127 exporter.exportFilters( filters );
1128 QList<MailFilter *>::ConstIterator it;
1129 for ( it = filters.constBegin(); it != filters.constEnd(); ++it )
1130 delete *it;
1133 void KMFilterDlg::slotDisableAccept()
1135 mDoNotClose = true;
1138 void KMFilterDlg::slotDialogUpdated()
1140 kDebug() << "Detected a change in data bound to the dialog!";
1141 if ( !mIgnoreFilterUpdates ) {
1142 enableButtonApply( true );
1147 #include "kmfilterdlg.moc"