You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

1368 lines
45 KiB

/*
Filter Dialog
Author: Marc Mutz <Marc@Mutz.com>
based upon work by Stefan Taferner <taferner@kde.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kmfilterdlg.h"
// other KMail headers:
#include "kmsearchpatternedit.h"
#include "kmfiltermgr.h"
#include "kmmainwidget.h"
#include "accountmanager.h"
using KMail::AccountManager;
#include "filterimporterexporter.h"
using KMail::FilterImporterExporter;
// other KDE headers:
#include <kmessagebox.h>
#include <kdebug.h>
#include <klocale.h>
#include <kinputdialog.h>
#include <kiconloader.h>
#include <kwindowsystem.h>
#include <kconfig.h>
#include <kicondialog.h>
#include <kkeysequencewidget.h>
#include <k3listview.h>
#include <kpushbutton.h>
#include <kconfiggroup.h>
#include <kvbox.h>
// Qt headers:
#include <QCheckBox>
#include <QGridLayout>
#include <QLabel>
#include <QListWidget>
#include <QStackedWidget>
#include <QVBoxLayout>
// other headers:
#include <assert.h>
// What's this help texts
const char * _wt_filterlist =
I18N_NOOP( "<qt><p>This is the list of defined filters. "
"They are processed top-to-bottom.</p>"
"<p>Click on any filter to edit it "
"using the controls in the right-hand half "
"of the dialog.</p></qt>" );
const char * _wt_filterlist_new =
I18N_NOOP( "<qt><p>Click this button to create a new filter.</p>"
"<p>The filter will be inserted just before the currently-"
"selected one, but you can always change that "
"later on.</p>"
"<p>If you have clicked this button accidentally, you can undo this "
"by clicking on the <em>Delete</em> button.</p></qt>" );
const char * _wt_filterlist_copy =
I18N_NOOP( "<qt><p>Click this button to copy a filter.</p>"
"<p>If you have clicked this button accidentally, you can undo this "
"by clicking on the <em>Delete</em> button.</p></qt>" );
const char * _wt_filterlist_delete =
I18N_NOOP( "<qt><p>Click this button to <em>delete</em> the currently-"
"selected filter from the list above.</p>"
"<p>There is no way to get the filter back once "
"it is deleted, but you can always leave the "
"dialog by clicking <em>Cancel</em> to discard the "
"changes made.</p></qt>" );
const char * _wt_filterlist_up =
I18N_NOOP( "<qt><p>Click this button to move the currently-"
"selected filter <em>up</em> one in the list above.</p>"
"<p>This is useful since the order of the filters in the list "
"determines the order in which they are tried on messages: "
"The topmost filter gets tried first.</p>"
"<p>If you have clicked this button accidentally, you can undo this "
"by clicking on the <em>Down</em> button.</p></qt>" );
const char * _wt_filterlist_down =
I18N_NOOP( "<qt><p>Click this button to move the currently-"
"selected filter <em>down</em> one in the list above.</p>"
"<p>This is useful since the order of the filters in the list "
"determines the order in which they are tried on messages: "
"The topmost filter gets tried first.</p>"
"<p>If you have clicked this button accidentally, you can undo this "
"by clicking on the <em>Up</em> button.</p></qt>" );
const char * _wt_filterlist_rename =
I18N_NOOP( "<qt><p>Click this button to rename the currently-selected filter.</p>"
"<p>Filters are named automatically, as long as they start with "
"\"&lt;\".</p>"
"<p>If you have renamed a filter accidentally and want automatic "
"naming back, click this button and select <em>Clear</em> followed "
"by <em>OK</em> in the appearing dialog.</p></qt>" );
const char * _wt_filterdlg_showLater =
I18N_NOOP( "<qt><p>Check this button to force the confirmation dialog to be "
"displayed.</p><p>This is useful if you have defined a ruleset that tags "
"messages to be downloaded later. Without the possibility to force "
"the dialog popup, these messages could never be downloaded if no "
"other large messages were waiting on the server, or if you wanted to "
"change the ruleset to tag the messages differently.</p></qt>" );
// The anchor of the filter dialog's help.
const char * KMFilterDlgHelpAnchor = "filters-id" ;
const char * KMPopFilterDlgHelpAnchor = "popfilters-id" ;
//=============================================================================
//
// class KMFilterDlg (the filter dialog)
//
//=============================================================================
KMFilterDlg::KMFilterDlg(QWidget* parent, bool popFilter, bool createDummyFilter )
: KDialog( parent ),
bPopFilter(popFilter)
{
if ( popFilter )
setCaption( i18n("POP3 Filter Rules") );
else
setCaption( i18n("Filter Rules") );
setButtons( Help|Ok|Apply|Cancel|User1|User2 );
setModal( false );
KWindowSystem::setIcons( winId(), qApp->windowIcon().pixmap(IconSize(KIconLoader::Desktop),IconSize(KIconLoader::Desktop)), qApp->windowIcon().pixmap(IconSize(KIconLoader::Small),IconSize(KIconLoader::Small)) );
setHelp( (bPopFilter)? KMPopFilterDlgHelpAnchor: KMFilterDlgHelpAnchor );
setButtonText( User1, i18n("Import...") );
setButtonText( User2, i18n("Export...") );
connect( this, SIGNAL(user1Clicked()),
this, SLOT( slotImportFilters()) );
connect( this, SIGNAL(user2Clicked()),
this, SLOT( slotExportFilters()) );
QWidget *w = new QWidget( this );
setMainWidget( w );
QHBoxLayout *topLayout = new QHBoxLayout( w );
topLayout->setObjectName( "topLayout" );
topLayout->setSpacing( spacingHint() );
topLayout->setMargin( 0 );
QHBoxLayout *hbl = topLayout;
QVBoxLayout *vbl2 = 0;
QWidget *page1 = 0;
QWidget *page2 = 0;
mFilterList = new KMFilterListBox( i18n("Available Filters"), w, 0, bPopFilter);
topLayout->addWidget( mFilterList, 1 /*stretch*/ );
if(!bPopFilter) {
QTabWidget *tabWidget = new QTabWidget( w );
tabWidget->setObjectName( "kmfd_tab" );
topLayout->addWidget( tabWidget );
page1 = new QWidget( tabWidget );
tabWidget->addTab( page1, i18n("&General") );
hbl = new QHBoxLayout( page1 );
hbl->setObjectName( "kmfd_hbl" );
hbl->setSpacing( spacingHint() );
hbl->setMargin( 0 );
page2 = new QWidget( tabWidget );
tabWidget->addTab( page2, i18n("A&dvanced") );
vbl2 = new QVBoxLayout( page2 );
vbl2->setObjectName( "kmfd_vbl2" );
vbl2->setSpacing( spacingHint() );
vbl2->setMargin( 0 );
}
QVBoxLayout *vbl = new QVBoxLayout();
hbl->addLayout( vbl );
vbl->setObjectName( "kmfd_vbl" );
vbl->setSpacing( spacingHint() );
hbl->setStretchFactor( vbl, 2 );
mPatternEdit = new KMSearchPatternEdit( i18n("Filter Criteria"), bPopFilter ? w : page1 , "spe", bPopFilter);
vbl->addWidget( mPatternEdit, 0, Qt::AlignTop );
if(bPopFilter){
mActionGroup = new KMPopFilterActionWidget( i18n("Filter Action"), w );
vbl->addWidget( mActionGroup, 0, Qt::AlignTop );
mGlobalsBox = new QGroupBox( i18n("Global Options"), w);
QHBoxLayout *layout = new QHBoxLayout;
mShowLaterBtn = new QCheckBox( i18n("Always &show matched 'Download Later' messages in confirmation dialog"), mGlobalsBox);
mShowLaterBtn->setWhatsThis( i18n(_wt_filterdlg_showLater) );
layout->addWidget( mShowLaterBtn );
mGlobalsBox->setLayout( layout );
vbl->addWidget( mGlobalsBox, 0, Qt::AlignTop );
}
else {
QGroupBox *agb = new QGroupBox( i18n("Filter Actions"), page1 );
QHBoxLayout *layout = new QHBoxLayout;
mActionLister = new KMFilterActionWidgetLister( agb );
layout->addWidget( mActionLister );
agb->setLayout( layout );
vbl->addWidget( agb, 0, Qt::AlignTop );
mAdvOptsGroup = new QGroupBox (i18n("Advanced Options"), page2);
{
QGridLayout *gl = new QGridLayout();
QVBoxLayout *vbl3 = new QVBoxLayout();
gl->addLayout( vbl3, 0, 0 );
vbl3->setObjectName( "vbl3" );
vbl3->setSpacing( spacingHint() );
vbl3->addStretch( 1 );
mApplyOnIn = new QCheckBox( i18n("Apply this filter to incoming messages:"), mAdvOptsGroup );
vbl3->addWidget( mApplyOnIn );
QButtonGroup *bg = new QButtonGroup( mAdvOptsGroup );
bg->setObjectName( "bg" );
mApplyOnForAll = new QRadioButton( i18n("from all accounts"), mAdvOptsGroup );
bg->addButton( mApplyOnForAll );
vbl3->addWidget( mApplyOnForAll );
mApplyOnForTraditional = new QRadioButton( i18n("from all but online IMAP accounts"), mAdvOptsGroup );
bg->addButton( mApplyOnForTraditional );
vbl3->addWidget( mApplyOnForTraditional );
mApplyOnForChecked = new QRadioButton( i18n("from checked accounts only"), mAdvOptsGroup );
bg->addButton( mApplyOnForChecked );
vbl3->addWidget( mApplyOnForChecked );
vbl3->addStretch( 2 );
mAccountList = new K3ListView( mAdvOptsGroup );
mAccountList->setObjectName( "accountList" );
mAccountList->addColumn( i18n("Account Name") );
mAccountList->addColumn( i18n("Type") );
mAccountList->setAllColumnsShowFocus( true );
mAccountList->setFrameStyle( QFrame::WinPanel + QFrame::Sunken );
mAccountList->setSorting( -1 );
gl->addWidget( mAccountList, 0, 1, 4, 3 );
mApplyOnOut = new QCheckBox( i18n("Apply this filter to &sent messages"), mAdvOptsGroup );
gl->addWidget( mApplyOnOut, 4, 0, 1, 4 );
mApplyOnCtrlJ = new QCheckBox( i18n("Apply this filter on manual &filtering"), mAdvOptsGroup );
gl->addWidget( mApplyOnCtrlJ, 5, 0, 1, 4 );
mStopProcessingHere = new QCheckBox( i18n("If this filter &matches, stop processing here"), mAdvOptsGroup );
gl->addWidget( mStopProcessingHere, 6, 0, 1, 4 );
mConfigureShortcut = new QCheckBox( i18n("Add this filter to the Apply Filter menu"), mAdvOptsGroup );
gl->addWidget( mConfigureShortcut, 7, 0, 1, 2 );
QLabel *keyButtonLabel = new QLabel( i18n( "Shortcut:" ), mAdvOptsGroup );
keyButtonLabel->setAlignment( Qt::AlignVCenter | Qt::AlignRight );
gl->addWidget( keyButtonLabel, 7, 2, 1, 1);
mKeySeqWidget = new KKeySequenceWidget( mAdvOptsGroup );
mKeySeqWidget->setObjectName( "FilterShortcutSelector" );
gl->addWidget( mKeySeqWidget, 7, 3, 1, 1);
mKeySeqWidget->setEnabled( false );
#if KDE_IS_VERSION(4,0,60)
mKeySeqWidget->setCheckActionCollections(
kmkernel->getKMMainWidget()->actionCollections() );
#else
mKeySeqWidget->setCheckActionList(
kmkernel->getKMMainWidget()->actionList() );
#endif
mConfigureToolbar = new QCheckBox( i18n("Additionally add this filter to the toolbar"), mAdvOptsGroup );
gl->addWidget( mConfigureToolbar, 8, 0, 1, 4 );
mConfigureToolbar->setEnabled( false );
KHBox *hbox = new KHBox( mAdvOptsGroup );
mFilterActionLabel = new QLabel( i18n( "Icon for this filter:" ),
hbox );
mFilterActionLabel->setEnabled( false );
mFilterActionIconButton = new KIconButton( hbox );
mFilterActionLabel->setBuddy( mFilterActionIconButton );
mFilterActionIconButton->setIconType( KIconLoader::NoGroup, KIconLoader::Action, false );
mFilterActionIconButton->setIconSize( 16 );
mFilterActionIconButton->setIcon( "system-run" );
mFilterActionIconButton->setEnabled( false );
gl->addWidget( hbox, 9, 0, 1, 4 );
mAdvOptsGroup->setLayout( gl );
}
vbl2->addWidget( mAdvOptsGroup, 0, Qt::AlignTop );
}
// spacer:
vbl->addStretch( 1 );
// load the filter parts into the edit widgets
connect( mFilterList, SIGNAL(filterSelected(KMFilter*)),
this, SLOT(slotFilterSelected(KMFilter*)) );
if (bPopFilter){
// set the state of the global setting 'show later msgs'
connect( mShowLaterBtn, SIGNAL(toggled(bool)),
mFilterList, SLOT(slotShowLaterToggled(bool)));
// set the action in the filter when changed
connect( mActionGroup, SIGNAL(actionChanged(const KMPopFilterAction)),
this, SLOT(slotActionChanged(const KMPopFilterAction)) );
} else {
// transfer changes from the 'Apply this filter on...'
// combo box to the filter
connect( mApplyOnIn, SIGNAL(clicked()),
this, SLOT(slotApplicabilityChanged()) );
connect( mApplyOnForAll, SIGNAL(clicked()),
this, SLOT(slotApplicabilityChanged()) );
connect( mApplyOnForTraditional, SIGNAL(clicked()),
this, SLOT(slotApplicabilityChanged()) );
connect( mApplyOnForChecked, SIGNAL(clicked()),
this, SLOT(slotApplicabilityChanged()) );
connect( mApplyOnOut, SIGNAL(clicked()),
this, SLOT(slotApplicabilityChanged()) );
connect( mApplyOnCtrlJ, SIGNAL(clicked()),
this, SLOT(slotApplicabilityChanged()) );
connect( mAccountList, SIGNAL(clicked(Q3ListViewItem*)),
this, SLOT(slotApplicableAccountsChanged()) );
connect( mAccountList, SIGNAL(spacePressed(Q3ListViewItem*)),
this, SLOT(slotApplicableAccountsChanged()) );
// transfer changes from the 'stop processing here'
// check box to the filter
connect( mStopProcessingHere, SIGNAL(toggled(bool)),
this, SLOT(slotStopProcessingButtonToggled(bool)) );
connect( mConfigureShortcut, SIGNAL(toggled(bool)),
this, SLOT(slotConfigureShortcutButtonToggled(bool)) );
connect( mKeySeqWidget, SIGNAL( keySequenceChanged( const QKeySequence& ) ),
this, SLOT( slotShortcutChanged( const QKeySequence& ) ) );
connect( mConfigureToolbar, SIGNAL(toggled(bool)),
this, SLOT(slotConfigureToolbarButtonToggled(bool)) );
connect( mFilterActionIconButton, SIGNAL( iconChanged( const QString& ) ),
this, SLOT( slotFilterActionIconChanged( const QString& ) ) );
}
// reset all widgets here
connect( mFilterList, SIGNAL(resetWidgets()),
this, SLOT(slotReset()) );
connect( mFilterList, SIGNAL( applyWidgets() ),
this, SLOT( slotUpdateFilter() ) );
// support auto-naming the filter
connect( mPatternEdit, SIGNAL(maybeNameChanged()),
mFilterList, SLOT(slotUpdateFilterName()) );
// apply changes on 'Apply'
connect( this, SIGNAL(applyClicked()),
mFilterList, SLOT(slotApplyFilterChanges()) );
// apply changes on 'OK'
connect( this, SIGNAL(okClicked()),
mFilterList, SLOT(slotApplyFilterChanges()) );
// save dialog size on 'OK'
connect( this, SIGNAL(okClicked()),
this, SLOT(slotSaveSize()) );
// destruct the dialog on OK, close and Cancel
connect( this, SIGNAL(finished()),
this, SLOT(slotFinished()) );
KConfigGroup geometry( KMKernel::config(), "Geometry");
const char * configKey
= bPopFilter ? "popFilterDialogSize" : "filterDialogSize";
if ( geometry.hasKey( configKey ) )
resize( geometry.readEntry( configKey, QSize() ));
else
adjustSize();
// load the filter list (emits filterSelected())
mFilterList->loadFilterList( createDummyFilter );
}
void KMFilterDlg::slotFinished() {
deleteLater();
}
void KMFilterDlg::slotSaveSize() {
KConfigGroup geometry( KMKernel::config(), "Geometry" );
geometry.writeEntry( bPopFilter ? "popFilterDialogSize" : "filterDialogSize", size() );
}
/** Set action of popFilter */
void KMFilterDlg::slotActionChanged(const KMPopFilterAction aAction)
{
mFilter->setAction(aAction);
}
void KMFilterDlg::slotFilterSelected( KMFilter* aFilter )
{
assert( aFilter );
if (bPopFilter){
mActionGroup->setAction( aFilter->action() );
mGlobalsBox->setEnabled(true);
mShowLaterBtn->setChecked(mFilterList->showLaterMsgs());
} else {
mActionLister->setActionList( aFilter->actions() );
mAdvOptsGroup->setEnabled( true );
}
mPatternEdit->setSearchPattern( aFilter->pattern() );
mFilter = aFilter;
if (!bPopFilter) {
kDebug(5006) <<"apply on inbound =="
<< aFilter->applyOnInbound();
kDebug(5006) <<"apply on outbound =="
<< aFilter->applyOnOutbound();
kDebug(5006) <<"apply on explicit =="
<< aFilter->applyOnExplicit();
// NOTE: setting these values activates the slot that sets them in
// the filter! So make sure we have the correct values _before_ we
// set the first one:
const bool applyOnIn = aFilter->applyOnInbound();
const bool applyOnForAll = aFilter->applicability() == KMFilter::All;
const bool applyOnTraditional = aFilter->applicability() == KMFilter::ButImap;
const bool applyOnOut = aFilter->applyOnOutbound();
const bool applyOnExplicit = aFilter->applyOnExplicit();
const bool stopHere = aFilter->stopProcessingHere();
const bool configureShortcut = aFilter->configureShortcut();
const bool configureToolbar = aFilter->configureToolbar();
const QString icon = aFilter->icon();
const KShortcut shortcut( aFilter->shortcut() );
mApplyOnIn->setChecked( applyOnIn );
mApplyOnForAll->setEnabled( applyOnIn );
mApplyOnForTraditional->setEnabled( applyOnIn );
mApplyOnForChecked->setEnabled( applyOnIn );
mApplyOnForAll->setChecked( applyOnForAll );
mApplyOnForTraditional->setChecked( applyOnTraditional );
mApplyOnForChecked->setChecked( !applyOnForAll && !applyOnTraditional );
mAccountList->setEnabled( mApplyOnForChecked->isEnabled() && mApplyOnForChecked->isChecked() );
slotUpdateAccountList();
mApplyOnOut->setChecked( applyOnOut );
mApplyOnCtrlJ->setChecked( applyOnExplicit );
mStopProcessingHere->setChecked( stopHere );
mConfigureShortcut->setChecked( configureShortcut );
mKeySeqWidget->setKeySequence( shortcut.primary(),
KKeySequenceWidget::NoValidate );
mConfigureToolbar->setChecked( configureToolbar );
mFilterActionIconButton->setIcon( icon );
}
}
void KMFilterDlg::slotReset()
{
mFilter = 0;
mPatternEdit->reset();
if(bPopFilter) {
mActionGroup->reset();
mGlobalsBox->setEnabled( false );
} else {
mActionLister->reset();
mAdvOptsGroup->setEnabled( false );
slotUpdateAccountList();
}
}
void KMFilterDlg::slotUpdateFilter()
{
mPatternEdit->updateSearchPattern();
if ( !bPopFilter ) {
mActionLister->updateActionList();
}
}
void KMFilterDlg::slotApplicabilityChanged()
{
if ( mFilter ) {
mFilter->setApplyOnInbound( mApplyOnIn->isChecked() );
mFilter->setApplyOnOutbound( mApplyOnOut->isChecked() );
mFilter->setApplyOnExplicit( mApplyOnCtrlJ->isChecked() );
if ( mApplyOnForAll->isChecked() )
mFilter->setApplicability( KMFilter::All );
else if ( mApplyOnForTraditional->isChecked() )
mFilter->setApplicability( KMFilter::ButImap );
else if ( mApplyOnForChecked->isChecked() )
mFilter->setApplicability( KMFilter::Checked );
mApplyOnForAll->setEnabled( mApplyOnIn->isChecked() );
mApplyOnForTraditional->setEnabled( mApplyOnIn->isChecked() );
mApplyOnForChecked->setEnabled( mApplyOnIn->isChecked() );
mAccountList->setEnabled( mApplyOnForChecked->isEnabled() && mApplyOnForChecked->isChecked() );
// Advanced tab functionality - Update list of accounts this filter applies to
Q3ListViewItemIterator it( mAccountList );
while ( it.current() ) {
Q3CheckListItem *item = dynamic_cast<Q3CheckListItem*>( it.current() );
if (item) {
int id = item->text( 2 ).toInt();
item->setOn( mFilter->applyOnAccount( id ) );
}
++it;
}
kDebug(5006) <<"KMFilterDlg: setting filter to be applied at"
<< ( mFilter->applyOnInbound() ? "incoming " : "" )
<< ( mFilter->applyOnOutbound() ? "outgoing " : "" )
<< ( mFilter->applyOnExplicit() ? "explicit CTRL-J" : "" );
}
}
void KMFilterDlg::slotApplicableAccountsChanged()
{
if ( mFilter && mApplyOnForChecked->isEnabled() && mApplyOnForChecked->isChecked() ) {
// Advanced tab functionality - Update list of accounts this filter applies to
Q3ListViewItemIterator it( mAccountList );
while ( it.current() ) {
Q3CheckListItem *item = dynamic_cast<Q3CheckListItem*>( it.current() );
if (item) {
int id = item->text( 2 ).toInt();
mFilter->setApplyOnAccount( id, item->isOn() );
}
++it;
}
}
}
void KMFilterDlg::slotStopProcessingButtonToggled( bool aChecked )
{
if ( mFilter )
mFilter->setStopProcessingHere( aChecked );
}
void KMFilterDlg::slotConfigureShortcutButtonToggled( bool aChecked )
{
if ( mFilter ) {
mFilter->setConfigureShortcut( aChecked );
mKeySeqWidget->setEnabled( aChecked );
mConfigureToolbar->setEnabled( aChecked );
mFilterActionIconButton->setEnabled( aChecked );
mFilterActionLabel->setEnabled( aChecked );
}
}
void KMFilterDlg::slotShortcutChanged( const QKeySequence &newSeq )
{
if ( mFilter )
{
mKeySeqWidget->applyStealShortcut();
mFilter->setShortcut( KShortcut( newSeq ) );
}
}
void KMFilterDlg::slotConfigureToolbarButtonToggled( bool aChecked )
{
if ( mFilter )
mFilter->setConfigureToolbar( aChecked );
}
void KMFilterDlg::slotFilterActionIconChanged( const QString &icon )
{
if ( mFilter )
mFilter->setIcon( icon );
}
void KMFilterDlg::slotUpdateAccountList()
{
mAccountList->clear();
Q3ListViewItem *top = 0;
for( KMAccount *a = kmkernel->acctMgr()->first(); a!=0;
a = kmkernel->acctMgr()->next() ) {
Q3CheckListItem *listItem =
new Q3CheckListItem( mAccountList, top, a->name(), Q3CheckListItem::CheckBox );
listItem->setText( 1, KAccount::displayNameForType( a->type() ) );
listItem->setText( 2, QString( "%1" ).arg( a->id() ) );
if ( mFilter )
listItem->setOn( mFilter->applyOnAccount( a->id() ) );
top = listItem;
}
Q3ListViewItem *listItem = mAccountList->firstChild();
if ( listItem ) {
mAccountList->setCurrentItem( listItem );
mAccountList->setSelected( listItem, true );
}
}
//=============================================================================
//
// class KMFilterListBox (the filter list manipulator)
//
//=============================================================================
KMFilterListBox::KMFilterListBox( const QString & title, QWidget *parent,
const char* name, bool popFilter )
: QGroupBox( title, parent ),
bPopFilter(popFilter)
{
setObjectName( name );
QVBoxLayout *layout = new QVBoxLayout();
mIdxSelItem = -1;
//----------- the list box
mListWidget = new QListWidget(this);
mListWidget->setMinimumWidth(150);
mListWidget->setWhatsThis( i18n(_wt_filterlist) );
layout->addWidget( mListWidget );
//----------- the first row of buttons
KHBox *hb = new KHBox(this);
hb->setSpacing(4);
mBtnUp = new KPushButton( QString(), hb );
mBtnUp->setAutoRepeat( true );
mBtnUp->setIcon( KIcon( "go-up" ) );
mBtnUp->setIconSize( QSize( KIconLoader::SizeSmall, KIconLoader::SizeSmall ) );
mBtnUp->setMinimumSize( mBtnUp->sizeHint() * 1.2 );
mBtnDown = new KPushButton( QString(), hb );
mBtnDown->setAutoRepeat( true );
mBtnDown->setIcon( KIcon( "go-down" ) );
mBtnDown->setIconSize( QSize( KIconLoader::SizeSmall, KIconLoader::SizeSmall ) );
mBtnDown->setMinimumSize( mBtnDown->sizeHint() * 1.2 );
mBtnUp->setToolTip( i18n("Up") );
mBtnDown->setToolTip( i18n("Down") );
mBtnUp->setWhatsThis( i18n(_wt_filterlist_up) );
mBtnDown->setWhatsThis( i18n(_wt_filterlist_down) );
layout->addWidget( hb );
//----------- the second row of buttons
hb = new KHBox(this);
hb->setSpacing(4);
mBtnNew = new QPushButton( QString(), hb );
mBtnNew->setIcon( KIcon( "document-new" ) );
mBtnNew->setIconSize( QSize( KIconLoader::SizeSmall, KIconLoader::SizeSmall ) );
mBtnNew->setMinimumSize( mBtnNew->sizeHint() * 1.2 );
mBtnCopy = new QPushButton( QString(), hb );
mBtnCopy->setIcon( KIcon( "edit-copy" ) );
mBtnCopy->setIconSize( QSize( KIconLoader::SizeSmall, KIconLoader::SizeSmall ) );
mBtnCopy->setMinimumSize( mBtnCopy->sizeHint() * 1.2 );
mBtnDelete = new QPushButton( QString(), hb );
mBtnDelete->setIcon( KIcon( "edit-delete" ) );
mBtnDelete->setIconSize( QSize( KIconLoader::SizeSmall, KIconLoader::SizeSmall ) );
mBtnDelete->setMinimumSize( mBtnDelete->sizeHint() * 1.2 );
mBtnRename = new QPushButton( i18n("Rename..."), hb );
mBtnNew->setToolTip( i18n("New") );
mBtnCopy->setToolTip( i18n("Copy") );
mBtnDelete->setToolTip( i18n("Delete"));
mBtnNew->setWhatsThis( i18n(_wt_filterlist_new) );
mBtnCopy->setWhatsThis( i18n(_wt_filterlist_copy) );
mBtnDelete->setWhatsThis( i18n(_wt_filterlist_delete) );
mBtnRename->setWhatsThis( i18n(_wt_filterlist_rename) );
layout->addWidget( hb );
setLayout( layout );
//----------- now connect everything
connect( mListWidget, SIGNAL(currentRowChanged(int)),
this, SLOT(slotSelected(int)) );
connect( mListWidget, SIGNAL(itemDoubleClicked(QListWidgetItem *)),
this, SLOT( slotRename()) );
connect( mBtnUp, SIGNAL(clicked()),
this, SLOT(slotUp()) );
connect( mBtnDown, SIGNAL(clicked()),
this, SLOT(slotDown()) );
connect( mBtnNew, SIGNAL(clicked()),
this, SLOT(slotNew()) );
connect( mBtnCopy, SIGNAL(clicked()),
this, SLOT(slotCopy()) );
connect( mBtnDelete, SIGNAL(clicked()),
this, SLOT(slotDelete()) );
connect( mBtnRename, SIGNAL(clicked()),
this, SLOT(slotRename()) );
// the dialog should call loadFilterList()
// when all signals are connected.
enableControls();
}
KMFilterListBox::~KMFilterListBox()
{
qDeleteAll( mFilterList );
}
void KMFilterListBox::createFilter( const QByteArray & field,
const QString & value )
{
KMSearchRule *newRule = KMSearchRule::createInstance( field, KMSearchRule::FuncContains, value );
KMFilter *newFilter = new KMFilter( bPopFilter );
newFilter->pattern()->append( newRule );
newFilter->pattern()->setName( QString("<%1>:%2").arg( QString::fromLatin1( field ) ).arg( value) );
KMFilterActionDesc *desc = kmkernel->filterActionDict()->value( "transfer" );
if ( desc )
newFilter->actions()->append( desc->create() );
insertFilter( newFilter );
enableControls();
}
bool KMFilterListBox::showLaterMsgs()
{
return mShowLater;
}
void KMFilterListBox::slotUpdateFilterName()
{
if ( mIdxSelItem < 0 ) {
kDebug(5006) <<"KMFilterListBox::slotUpdateFilterName called while no filter is selected, ignoring. idx=" << mIdxSelItem;
return;
}
KMSearchPattern *p = mFilterList.at(mIdxSelItem)->pattern();
if ( !p ) return;
QString shouldBeName = p->name();
QString displayedName = mListWidget->item( mIdxSelItem )->text();
if ( shouldBeName.trimmed().isEmpty() ) {
mFilterList.at(mIdxSelItem)->setAutoNaming( true );
}
if ( mFilterList.at(mIdxSelItem)->isAutoNaming() ) {
// auto-naming of patterns
if ( p->first() && !p->first()->field().trimmed().isEmpty() )
shouldBeName = QString( "<%1>: %2" ).arg( QString::fromLatin1( p->first()->field() ) ).arg( p->first()->contents() );
else
shouldBeName = '<' + i18n("unnamed") + '>';
p->setName( shouldBeName );
}
if ( displayedName == shouldBeName ) return;
mListWidget->blockSignals(true);
mListWidget->item( mIdxSelItem )->setText( shouldBeName );
mListWidget->blockSignals(false);
}
void KMFilterListBox::slotShowLaterToggled(bool aOn)
{
mShowLater = aOn;
}
void KMFilterListBox::slotApplyFilterChanges()
{
if ( mIdxSelItem >= 0 ) {
emit applyWidgets();
slotSelected( mListWidget->currentRow() );
}
// by now all edit widgets should have written back
// their widget's data into our filter list.
KMFilterMgr *fm;
if ( bPopFilter )
fm = kmkernel->popFilterMgr();
else
fm = kmkernel->filterMgr();
QList<KMFilter *> newFilters = filtersForSaving();
if ( bPopFilter )
fm->setShowLaterMsgs( mShowLater );
fm->setFilters( newFilters );
if ( fm->atLeastOneOnlineImapFolderTarget() ) {
QString str = i18n("At least one filter targets a folder on an online "
"IMAP account. Such filters will only be applied "
"when manually filtering and when filtering "
"incoming online IMAP mail.");
KMessageBox::information( this, str, QString(), "filterDlgOnlineImapCheck" );
}
}
QList<KMFilter *> KMFilterListBox::filtersForSaving() const
{
const_cast<KMFilterListBox*>( this )->applyWidgets(); // signals aren't const
QList<KMFilter *> filters;
QStringList emptyFilters;
foreach ( KMFilter *const it, mFilterList ) {
KMFilter *f = new KMFilter( *it ); // deep copy
f->purify();
if ( !f->isEmpty() )
// the filter is valid:
filters.append( f );
else {
// the filter is invalid:
emptyFilters << f->name();
delete f;
}
}
// report on invalid filters:
if ( !emptyFilters.empty() ) {
QString msg = i18n("The following filters have not been saved because they "
"were invalid (e.g. containing no actions or no search "
"rules).");
KMessageBox::informationList( 0, msg, emptyFilters, QString(),
"ShowInvalidFilterWarning" );
}
return filters;
}
void KMFilterListBox::slotSelected( int aIdx )
{
kDebug(5006) <<"KMFilterListBox::slotSelected called. idx=" << aIdx;
mIdxSelItem = aIdx;
if ( mIdxSelItem >= 0 && mIdxSelItem < mFilterList.count() ) {
KMFilter *f = mFilterList.at(aIdx);
if ( f )
emit filterSelected( f );
else
emit resetWidgets();
} else {
emit resetWidgets();
}
enableControls();
}
void KMFilterListBox::slotNew()
{
// just insert a new filter.
insertFilter( new KMFilter( bPopFilter ) );
enableControls();
}
void KMFilterListBox::slotCopy()
{
if ( mIdxSelItem < 0 ) {
kDebug(5006) <<"KMFilterListBox::slotCopy called while no filter is selected, ignoring.";
return;
}
// make sure that all changes are written to the filter before we copy it
emit applyWidgets();
KMFilter *filter = mFilterList.at( mIdxSelItem );
// enableControls should make sure this method is
// never called when no filter is selected.
assert( filter );
// inserts a copy of the current filter.
insertFilter( new KMFilter( *filter ) );
enableControls();
}
void KMFilterListBox::slotDelete()
{
if ( mIdxSelItem < 0 ) {
kDebug(5006) <<"KMFilterListBox::slotDelete called while no filter is selected, ignoring.";
return;
}
int oIdxSelItem = mIdxSelItem;
mIdxSelItem = -1;
// unselect all
// TODO remove this line: mListWidget->clearSelection();
// broadcast that all widgets let go
// of the filter
emit resetWidgets();
// remove the filter from both the filter list...
mFilterList.takeAt( oIdxSelItem );
// and the listbox
mListWidget->takeItem( oIdxSelItem );
int count = mListWidget->count();
// and set the new current item.
if ( count > oIdxSelItem )
// oIdxItem is still a valid index
mListWidget->setCurrentRow( oIdxSelItem );
else if ( count )
// oIdxSelIdx is no longer valid, but the
// list box isn't empty
mListWidget->setCurrentRow( count - 1 );
// work around a problem when deleting the first item in a QListWidget:
// after takeItem, slotSelectionChanged is emitted with 1, but the row 0
// remains selected and another selectCurrentRow(0) does not trigger the
// selectionChanged signal
// (qt-copy as of 2006-12-22 / gungl)
if ( oIdxSelItem == 0 )
slotSelected( 0 );
mIdxSelItem = mListWidget->currentRow();
enableControls();
}
void KMFilterListBox::slotUp()
{
if ( mIdxSelItem < 0 ) {
kDebug(5006) <<"KMFilterListBox::slotUp called while no filter is selected, ignoring.";
return;
}
if ( mIdxSelItem == 0 ) {
kDebug(5006) <<"KMFilterListBox::slotUp called while the _topmost_ filter is selected, ignoring.";
return;
}
swapNeighbouringFilters( mIdxSelItem, mIdxSelItem - 1 );
enableControls();
}
void KMFilterListBox::slotDown()
{
if ( mIdxSelItem < 0 ) {
kDebug(5006) <<"KMFilterListBox::slotDown called while no filter is selected, ignoring.";
return;
}
if ( mIdxSelItem == (int)mListWidget->count() - 1 ) {
kDebug(5006) <<"KMFilterListBox::slotDown called while the _last_ filter is selected, ignoring.";
return;
}
swapNeighbouringFilters( mIdxSelItem, mIdxSelItem + 1);
enableControls();
}
void KMFilterListBox::slotRename()
{
if ( mIdxSelItem < 0 ) {
kDebug(5006) <<"KMFilterListBox::slotRename called while no filter is selected, ignoring.";
return;
}
bool okPressed = false;
KMFilter *filter = mFilterList.at( mIdxSelItem );
// enableControls should make sure this method is
// never called when no filter is selected.
assert( filter );
// allow empty names - those will turn auto-naming on again
QValidator *validator = new QRegExpValidator( QRegExp( ".*" ), 0 );
QString newName = KInputDialog::getText
(
i18n("Rename Filter"),
i18n("Rename filter \"%1\" to:\n(leave the field empty for automatic naming)",
filter->pattern()->name() ) /*label*/,
filter->pattern()->name() /* initial value */,
&okPressed, topLevelWidget(), validator
);
delete validator;
if ( !okPressed ) return;
if ( newName.isEmpty() ) {
// bait for slotUpdateFilterName to
// use automatic naming again.
filter->pattern()->setName( "<>" );
filter->setAutoNaming( true );
} else {
filter->pattern()->setName( newName );
filter->setAutoNaming( false );
}
slotUpdateFilterName();
}
void KMFilterListBox::enableControls()
{
bool theFirst = ( mIdxSelItem == 0 );
bool theLast = ( mIdxSelItem >= (int)mFilterList.count() - 1 );
bool aFilterIsSelected = ( mIdxSelItem >= 0 );
mBtnUp->setEnabled( aFilterIsSelected && !theFirst );
mBtnDown->setEnabled( aFilterIsSelected && !theLast );
mBtnCopy->setEnabled( aFilterIsSelected );
mBtnDelete->setEnabled( aFilterIsSelected );
mBtnRename->setEnabled( aFilterIsSelected );
if ( aFilterIsSelected )
mListWidget->scrollToItem( mListWidget->currentItem() );
}
void KMFilterListBox::loadFilterList( bool createDummyFilter )
{
assert(mListWidget);
setEnabled(false);
emit resetWidgets();
// we don't want the insertion to
// cause flicker in the edit widgets.
blockSignals(true);
// clear both lists
mFilterList.clear();
mListWidget->clear();
const KMFilterMgr *manager = 0;
if(bPopFilter)
{
mShowLater = kmkernel->popFilterMgr()->showLaterMsgs();
manager = kmkernel->popFilterMgr();
}
else
{
manager = kmkernel->filterMgr();
}
Q_ASSERT( manager );
QList<KMFilter*>::const_iterator it;
for ( it = manager->filters().begin() ; it != manager->filters().end() ; ++it ) {
mFilterList.append( new KMFilter( **it ) ); // deep copy
mListWidget->addItem( (*it)->pattern()->name() );
}
blockSignals(false);
setEnabled(true);
// create an empty filter when there's none, to avoid a completely
// disabled dialog (usability tests indicated that the new-filter
// button is too hard to find that way):
if ( !mListWidget->count() && createDummyFilter )
slotNew();
if ( mListWidget->count() > 0 )
mListWidget->setCurrentRow( 0 );
enableControls();
}
void KMFilterListBox::insertFilter( KMFilter* aFilter )
{
// must be really a filter...
assert( aFilter );
// if mIdxSelItem < 0, QListBox::insertItem will append.
mListWidget->insertItem( mIdxSelItem, aFilter->pattern()->name() );
if ( mIdxSelItem < 0 ) {
// none selected -> append
mFilterList.append( aFilter );
mListWidget->setCurrentRow( mListWidget->count() - 1 );
} else {
// insert just before selected
mFilterList.insert( mIdxSelItem, aFilter );
mListWidget->setCurrentRow( mIdxSelItem );
}
}
void KMFilterListBox::appendFilter( KMFilter* aFilter )
{
mFilterList.append( aFilter );
mListWidget->addItems( QStringList( aFilter->pattern()->name() ) );
}
void KMFilterListBox::swapNeighbouringFilters( int untouchedOne, int movedOne )
{
// must be neighbours...
assert( untouchedOne - movedOne == 1 || movedOne - untouchedOne == 1 );
// untouchedOne is at idx. to move it down(up),
// remove item at idx+(-)1 w/o deleting it.
QListWidgetItem *item = mListWidget->item( movedOne );
mListWidget->takeItem( movedOne );
// now selected item is at idx(idx-1), so
// insert the other item at idx, ie. above(below).
mListWidget->insertItem( untouchedOne, item );
KMFilter* filter = mFilterList.takeAt( movedOne );
mFilterList.insert( untouchedOne, filter );
mIdxSelItem += movedOne - untouchedOne;
}
//=============================================================================
//
// class KMFilterActionWidget
//
//=============================================================================
KMFilterActionWidget::KMFilterActionWidget( QWidget *parent, const char* name )
: KHBox( parent )
{
setObjectName( name );
int i;
mComboBox = new QComboBox( this );
mComboBox->setEditable( false );
assert( mComboBox );
mWidgetStack = new QStackedWidget(this);
assert( mWidgetStack );
setSpacing( 4 );
QList<KMFilterActionDesc*> list = kmkernel->filterActionDict()->list();
QList<KMFilterActionDesc*>::const_iterator it;
for ( i=0, it = list.begin() ; it != list.end() ; ++it, ++i ) {
//create an instance:
KMFilterAction *a = (*it)->create();
// append to the list of actions:
mActionList.append( a );
// add parameter widget to widget stack:
mWidgetStack->insertWidget( i, a->createParamWidget( mWidgetStack ) );
// add (i18n-ized) name to combo box
mComboBox->addItem( (*it)->label );
}
// widget for the case where no action is selected.
mWidgetStack->insertWidget( i,new QLabel( i18n("Please select an action."), mWidgetStack ) );
mWidgetStack->setCurrentIndex(i);
mComboBox->addItem( " " );
mComboBox->setCurrentIndex(i);
// don't show scroll bars.
mComboBox->setMaxCount( mComboBox->count() );
// layout management:
// o the combo box is not to be made larger than it's sizeHint(),
// the parameter widget should grow instead.
// o the whole widget takes all space horizontally, but is fixed vertically.
mComboBox->adjustSize();
mComboBox->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) );
setSizePolicy( QSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed ) );
updateGeometry();
// redirect focus to the filter action combo box
setFocusProxy( mComboBox );
// now connect the combo box and the widget stack
connect( mComboBox, SIGNAL(activated(int)),
mWidgetStack, SLOT(setCurrentIndex (int)) );
}
KMFilterActionWidget::~KMFilterActionWidget()
{
qDeleteAll( mActionList );
}
void KMFilterActionWidget::setAction( const KMFilterAction* aAction )
{
int i=0;
bool found = false;
int count = mComboBox->count() - 1 ; // last entry is the empty one
QString label = ( aAction ) ? aAction->label() : QString() ;
// find the index of typeOf(aAction) in mComboBox
// and clear the other widgets on the way.
for ( ; i < count ; i++ )
if ( aAction && mComboBox->itemText(i) == label ) {
//...set the parameter widget to the settings
// of aAction...
aAction->setParamWidgetValue( mWidgetStack->widget(i) );
//...and show the correct entry of
// the combo box
mComboBox->setCurrentIndex(i); // (mm) also raise the widget, but doesn't
mWidgetStack->setCurrentIndex(i);
found = true;
} else // clear the parameter widget
mActionList.at(i)->clearParamWidget( mWidgetStack->widget(i) );
if ( found ) return;
// not found, so set the empty widget
mComboBox->setCurrentIndex( count ); // last item
mWidgetStack->setCurrentIndex( count) ;
}
KMFilterAction * KMFilterActionWidget::action()
{
// look up the action description via the label
// returned by QComboBox::currentText()...
KMFilterActionDesc *desc = kmkernel->filterActionDict()->value( mComboBox->currentText() );
if ( desc ) {
// ...create an instance...
KMFilterAction *fa = desc->create();
if ( fa ) {
// ...and apply the setting of the parameter widget.
fa->applyParamWidgetValue( mWidgetStack->currentWidget() );
return fa;
}
}
return 0;
}
//=============================================================================
//
// class KMFilterActionWidgetLister (the filter action editor)
//
//=============================================================================
KMFilterActionWidgetLister::KMFilterActionWidgetLister( QWidget *parent, const char* name )
: KWidgetLister( 1, FILTER_MAX_ACTIONS, parent, name )
{
mActionList = 0;
}
KMFilterActionWidgetLister::~KMFilterActionWidgetLister()
{
}
void KMFilterActionWidgetLister::setActionList( QList<KMFilterAction*> *aList )
{
assert ( aList );
if ( mActionList )
regenerateActionListFromWidgets();
mActionList = aList;
((QWidget*)parent())->setEnabled( true );
if ( aList->count() == 0 ) {
slotClear();
return;
}
int superfluousItems = (int)mActionList->count() - mMaxWidgets ;
if ( superfluousItems > 0 ) {
kDebug(5006) <<"KMFilterActionWidgetLister: Clipping action list to"
<< mMaxWidgets << "items!";
for ( ; superfluousItems ; superfluousItems-- )
mActionList->removeLast();
}
// set the right number of widgets
setNumberOfShownWidgetsTo( mActionList->count() );
// load the actions into the widgets
QList<KMFilterAction*>::const_iterator aIt;
Q3PtrListIterator<QWidget> wIt( mWidgetList );
for ( aIt = mActionList->begin(), wIt.toFirst() ;
(aIt != mActionList->end()) && wIt.current() ; ++aIt, ++wIt )
((KMFilterActionWidget*)(*wIt))->setAction( (*aIt) );
}
void KMFilterActionWidgetLister::reset()
{
if ( mActionList )
regenerateActionListFromWidgets();
mActionList = 0;
slotClear();
((QWidget*)parent())->setEnabled( false );
}
QWidget* KMFilterActionWidgetLister::createWidget( QWidget *parent )
{
return new KMFilterActionWidget(parent);
}
void KMFilterActionWidgetLister::clearWidget( QWidget *aWidget )
{
if ( aWidget )
((KMFilterActionWidget*)aWidget)->setAction(0);
}
void KMFilterActionWidgetLister::regenerateActionListFromWidgets()
{
if ( !mActionList ) return;
mActionList->clear();
Q3PtrListIterator<QWidget> it( mWidgetList );
for ( it.toFirst() ; it.current() ; ++it ) {
KMFilterAction *a = ((KMFilterActionWidget*)(*it))->action();
if ( a )
mActionList->append( a );
}
}
//=============================================================================
//
// class KMPopFilterActionWidget
//
//=============================================================================
KMPopFilterActionWidget::KMPopFilterActionWidget( const QString& title, QWidget *parent, const char* name )
: QGroupBox( title, parent )
{
setObjectName( name );
QVBoxLayout *layout = new QVBoxLayout( this );
QButtonGroup *bg = new QButtonGroup( this );
QRadioButton *downBtn = new QRadioButton( i18n("&Download mail"), this );
QRadioButton *laterBtn = new QRadioButton( i18n("Download mail la&ter"), this );
QRadioButton *deleteBtn = new QRadioButton( i18n("D&elete mail from server"), this );
layout->addWidget( downBtn );
layout->addWidget( laterBtn );
layout->addWidget( deleteBtn );
bg->addButton( downBtn );
bg->addButton( laterBtn );
bg->addButton( deleteBtn );
mActionMap.insert( Down, downBtn );
mActionMap.insert( Later, laterBtn );
mActionMap.insert( Delete, deleteBtn );
mButtonMap.insert( downBtn, Down );
mButtonMap.insert( laterBtn, Later );
mButtonMap.insert( deleteBtn, Delete );
connect( bg, SIGNAL(buttonClicked(QAbstractButton*)),
this, SLOT(slotActionClicked(QAbstractButton*)) );
}
void KMPopFilterActionWidget::setAction( KMPopFilterAction aAction )
{
if( aAction == NoAction)
{
aAction = Later;
}
mAction = aAction;
blockSignals( true );
if(!mActionMap[aAction]->isChecked())
{
mActionMap[aAction]->setChecked(true);
}
blockSignals( false );
setEnabled(true);
}
KMPopFilterAction KMPopFilterActionWidget::action()
{
return mAction;
}
void KMPopFilterActionWidget::slotActionClicked( QAbstractButton *btn )
{
emit actionChanged( mButtonMap[btn] );
setAction( mButtonMap[btn] );
}
void KMPopFilterActionWidget::reset()
{
blockSignals(true);
mActionMap[Down]->setChecked( true );
blockSignals(false);
setEnabled( false );
}
void KMFilterDlg::slotImportFilters()
{
FilterImporterExporter importer( this, bPopFilter );
QList<KMFilter *> filters = importer.importFilters();
// FIXME message box how many were imported?
if ( filters.isEmpty() ) return;
QList<KMFilter*>::ConstIterator it;
for ( it = filters.constBegin() ; it != filters.constEnd() ; ++it ) {
mFilterList->appendFilter( *it ); // no need to deep copy, ownership passes to the list
}
}
void KMFilterDlg::slotExportFilters()
{
FilterImporterExporter exporter( this, bPopFilter );
QList<KMFilter *> filters = mFilterList->filtersForSaving();
exporter.exportFilters( filters );
QList<KMFilter *>::iterator it;
for ( it = filters.begin(); it != filters.end(); ++it )
delete *it;
}
#include "kmfilterdlg.moc"