• Skip to content
  • Skip to link menu
KDE API Reference
  • KDE API Reference
  • kdepim API Reference
  • KDE Home
  • Contact Us
 

kmail

  • sources
  • kde-4.14
  • kdepim
  • kmail
  • searchdialog
searchwindow.cpp
Go to the documentation of this file.
1 /*
2  * kmail: KDE mail client
3  * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
4  * Copyright (c) 2001 Aaron J. Seigo <aseigo@kde.org>
5  * Copyright (c) 2010 Till Adam <adam@kde.org>
6  * Copyright (C) 2011-2015 Laurent Montel <montel@kde.org>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21  *
22  */
23 
24 #include "searchwindow.h"
25 
26 #include "folderrequester.h"
27 #include "kmcommands.h"
28 #include "kmmainwidget.h"
29 #include "mailcommon/kernel/mailkernel.h"
30 #include "mailcommon/search/searchpatternedit.h"
31 #include "mailcommon/widgets/regexplineedit.h"
32 #include "searchdescriptionattribute.h"
33 #include "foldertreeview.h"
34 #include "kmsearchmessagemodel.h"
35 #include "kmsearchfilterproxymodel.h"
36 #include "searchpatternwarning.h"
37 #include "pimcommon/folderdialog/selectmulticollectiondialog.h"
38 
39 #include <Akonadi/CollectionModifyJob>
40 #include <Akonadi/CollectionFetchJob>
41 #include <Akonadi/EntityTreeView>
42 #include <akonadi/persistentsearchattribute.h>
43 #include <Akonadi/SearchCreateJob>
44 #include <Akonadi/ChangeRecorder>
45 #include <akonadi/standardactionmanager.h>
46 #include <Akonadi/EntityMimeTypeFilterModel>
47 #include <KActionMenu>
48 #include <KDebug>
49 #include <KIcon>
50 #include <KIconLoader>
51 #include <kmime/kmime_message.h>
52 #include <KStandardAction>
53 #include <KStandardGuiItem>
54 #include <KWindowSystem>
55 #include <KMessageBox>
56 
57 #include <QCheckBox>
58 #include <QCloseEvent>
59 #include <QCursor>
60 #include <QHeaderView>
61 #include <QKeyEvent>
62 #include <QMenu>
63 #include <QVBoxLayout>
64 
65 using namespace KPIM;
66 using namespace MailCommon;
67 
68 namespace KMail {
69 
70 SearchWindow::SearchWindow( KMMainWidget *widget, const Akonadi::Collection &collection )
71  : KDialog( 0 ),
72  mCloseRequested( false ),
73  mSortColumn( 0 ),
74  mSortOrder( Qt::AscendingOrder ),
75  mSearchJob( 0 ),
76  mResultModel( 0 ),
77  mKMMainWidget( widget ),
78  mAkonadiStandardAction( 0 )
79 {
80  setCaption( i18n( "Find Messages" ) );
81 
82  KWindowSystem::setIcons( winId(), qApp->windowIcon().pixmap( IconSize( KIconLoader::Desktop ),
83  IconSize( KIconLoader::Desktop ) ),
84  qApp->windowIcon().pixmap( IconSize( KIconLoader::Small ),
85  IconSize( KIconLoader::Small ) ) );
86 
87  QWidget *topWidget = new QWidget;
88  QVBoxLayout *lay = new QVBoxLayout;
89  lay->setMargin(0);
90  topWidget->setLayout(lay);
91  mSearchPatternWidget = new SearchPatternWarning;
92  lay->addWidget(mSearchPatternWidget);
93  setMainWidget( topWidget );
94 
95  QWidget *searchWidget = new QWidget( this );
96  mUi.setupUi( searchWidget );
97 
98  lay->addWidget(searchWidget);
99 
100 
101  setButtons( None );
102  mStartSearchGuiItem = KGuiItem( i18nc( "@action:button Search for messages", "&Search" ), QLatin1String("edit-find") );
103  mStopSearchGuiItem = KStandardGuiItem::stop();
104  mSearchButton = mUi.mButtonBox->addButton( mStartSearchGuiItem, QDialogButtonBox::ActionRole );
105  connect( mUi.mButtonBox, SIGNAL(rejected()), SLOT(slotClose()) );
106  searchWidget->layout()->setMargin( 0 );
107 
108  mUi.mCbxFolders->setMustBeReadWrite( false );
109  mUi.mCbxFolders->setNotAllowToCreateNewFolder( true );
110 
111  bool currentFolderIsSearchFolder = false;
112 
113  if ( !collection.hasAttribute<Akonadi::PersistentSearchAttribute>() ) {
114  // it's not a search folder, make a new search
115  mSearchPattern.append( SearchRule::createInstance( "Subject" ) );
116  mUi.mCbxFolders->setCollection( collection );
117  } else {
118  // it's a search folder
119  if ( collection.hasAttribute<Akonadi::SearchDescriptionAttribute>() ) {
120  currentFolderIsSearchFolder = true; // FIXME is there a better way to tell?
121 
122  const Akonadi::SearchDescriptionAttribute* searchDescription = collection.attribute<Akonadi::SearchDescriptionAttribute>();
123  mSearchPattern.deserialize( searchDescription->description() );
124 
125  const QList<Akonadi::Collection::Id> lst = searchDescription->listCollection();
126  if (!lst.isEmpty()) {
127  mUi.mChkMultiFolders->setChecked(true);
128  mCollectionId.clear();
129  Q_FOREACH (Akonadi::Collection::Id col, lst) {
130  mCollectionId.append(Akonadi::Collection(col));
131  }
132  } else {
133  const Akonadi::Collection col = searchDescription->baseCollection();
134  if ( col.isValid() ) {
135  mUi.mChkbxSpecificFolders->setChecked( true );
136  mUi.mCbxFolders->setCollection( col );
137  mUi.mChkSubFolders->setChecked( searchDescription->recursive() );
138  } else {
139  mUi.mChkbxAllFolders->setChecked( true );
140  mUi.mChkSubFolders->setChecked( searchDescription->recursive() );
141  }
142  }
143  } else {
144  // it's a search folder, but not one of ours, warn the user that we can't edit it
145  // FIXME show results, but disable edit GUI
146  kWarning() << "This search was not created with KMail. It cannot be edited within it.";
147  mSearchPattern.clear();
148  }
149  }
150 
151  mUi.mPatternEdit->setSearchPattern( &mSearchPattern );
152  connect( mUi.mPatternEdit, SIGNAL(returnPressed()),
153  this, SLOT(slotSearch()) );
154 
155  // enable/disable widgets depending on radio buttons:
156  connect( mUi.mChkbxAllFolders, SIGNAL(toggled(bool)),
157  this, SLOT(setEnabledSearchButton(bool)) );
158 
159  mUi.mLbxMatches->setXmlGuiClient( mKMMainWidget->guiClient() );
160 
161  /*
162  Default is to sort by date. TODO: Unfortunately this sorts *while*
163  inserting, which looks rather strange - the user cannot read
164  the results so far as they are constantly re-sorted --dnaber
165 
166  Sorting is now disabled when a search is started and reenabled
167  when it stops. Items are appended to the list. This not only
168  solves the above problem, but speeds searches with many hits
169  up considerably. - till
170 
171  TODO: subclass QTreeWidgetItem and do proper (and performant)
172  compare functions
173  */
174  mUi.mLbxMatches->setSortingEnabled( true );
175 
176  connect( mUi.mLbxMatches, SIGNAL(customContextMenuRequested(QPoint)),
177  this, SLOT(slotContextMenuRequested(QPoint)) );
178  connect( mUi.mLbxMatches, SIGNAL(doubleClicked(Akonadi::Item)),
179  this, SLOT(slotViewMsg(Akonadi::Item)) );
180  connect( mUi.mLbxMatches, SIGNAL(currentChanged(Akonadi::Item)),
181  this, SLOT(slotCurrentChanged(Akonadi::Item)) );
182  connect( mUi.selectMultipleFolders, SIGNAL(clicked()),
183  this, SLOT(slotSelectMultipleFolders()));
184 
185  connect( KMKernel::self()->folderCollectionMonitor(), SIGNAL(collectionStatisticsChanged(Akonadi::Collection::Id,Akonadi::CollectionStatistics)), this, SLOT(updateCollectionStatistic(Akonadi::Collection::Id,Akonadi::CollectionStatistics)) );
186 
187  if ( currentFolderIsSearchFolder ) {
188  mFolder = collection;
189  mUi.mSearchFolderEdt->setText( collection.name() );
190  Q_ASSERT ( !mResultModel );
191  createSearchModel();
192  } else {
193  mUi.mSearchFolderEdt->setText( i18n( "Last Search" ) );
194  // find last search and reuse it if possible
195  mFolder = CommonKernel->collectionFromId( GlobalSettings::lastSearchCollectionId() );
196  // when the last folder got renamed, create a new one
197  if ( mFolder.isValid() && mFolder.name() != mUi.mSearchFolderEdt->text() ) {
198  mFolder = Akonadi::Collection();
199  }
200  }
201 
202  connect( mUi.mSearchFolderEdt, SIGNAL(textChanged(QString)),
203  this, SLOT(scheduleRename(QString)) );
204  connect( &mRenameTimer, SIGNAL(timeout()),
205  this, SLOT(renameSearchFolder()) );
206  connect( mUi.mSearchFolderOpenBtn, SIGNAL(clicked()),
207  this, SLOT(openSearchFolder()) );
208 
209  connect( mUi.mSearchResultOpenBtn, SIGNAL(clicked()),
210  this, SLOT(slotViewSelectedMsg()) );
211 
212  const int mainWidth = GlobalSettings::self()->searchWidgetWidth();
213  const int mainHeight = GlobalSettings::self()->searchWidgetHeight();
214 
215  if ( mainWidth || mainHeight )
216  resize( mainWidth, mainHeight );
217 
218  connect( mSearchButton, SIGNAL(clicked()), SLOT(slotSearch()) );
219  connect( this, SIGNAL(finished()), this, SLOT(deleteLater()) );
220  connect( this, SIGNAL(closeClicked()),this,SLOT(slotClose()) );
221 
222  // give focus to the value field of the first search rule
223  RegExpLineEdit* r = mUi.mPatternEdit->findChild<RegExpLineEdit*>( QLatin1String("regExpLineEdit") );
224  if ( r )
225  r->setFocus();
226  else
227  kDebug() << "SearchWindow: regExpLineEdit not found";
228 
229  //set up actions
230  KActionCollection *ac = actionCollection();
231  mReplyAction = new KAction( KIcon( QLatin1String("mail-reply-sender") ), i18n( "&Reply..." ), this );
232  actionCollection()->addAction( QLatin1String("search_reply"), mReplyAction );
233  connect( mReplyAction, SIGNAL(triggered(bool)), SLOT(slotReplyToMsg()) );
234 
235  mReplyAllAction = new KAction( KIcon( QLatin1String("mail-reply-all") ), i18n( "Reply to &All..." ), this );
236  actionCollection()->addAction( QLatin1String("search_reply_all"), mReplyAllAction );
237  connect( mReplyAllAction, SIGNAL(triggered(bool)), SLOT(slotReplyAllToMsg()) );
238 
239  mReplyListAction = new KAction( KIcon( QLatin1String("mail-reply-list") ), i18n( "Reply to Mailing-&List..." ), this );
240  actionCollection()->addAction(QLatin1String( "search_reply_list"), mReplyListAction );
241  connect( mReplyListAction, SIGNAL(triggered(bool)), SLOT(slotReplyListToMsg()) );
242 
243  mForwardActionMenu = new KActionMenu( KIcon( QLatin1String("mail-forward") ), i18nc( "Message->", "&Forward" ), this );
244  actionCollection()->addAction( QLatin1String("search_message_forward"), mForwardActionMenu );
245  connect( mForwardActionMenu, SIGNAL(triggered(bool)), this, SLOT(slotForwardMsg()) );
246 
247  mForwardInlineAction = new KAction( KIcon( QLatin1String("mail-forward") ),
248  i18nc( "@action:inmenu Forward message inline.", "&Inline..." ),
249  this );
250  actionCollection()->addAction( QLatin1String("search_message_forward_inline"), mForwardInlineAction );
251  connect( mForwardInlineAction, SIGNAL(triggered(bool)), SLOT(slotForwardMsg()) );
252 
253  mForwardAttachedAction = new KAction( KIcon( QLatin1String("mail-forward") ), i18nc( "Message->Forward->", "As &Attachment..." ), this );
254  actionCollection()->addAction( QLatin1String("search_message_forward_as_attachment"), mForwardAttachedAction );
255  connect( mForwardAttachedAction, SIGNAL(triggered(bool)), SLOT(slotForwardAttachedMsg()) );
256 
257  if ( GlobalSettings::self()->forwardingInlineByDefault() ) {
258  mForwardActionMenu->addAction( mForwardInlineAction );
259  mForwardActionMenu->addAction( mForwardAttachedAction );
260  } else {
261  mForwardActionMenu->addAction( mForwardAttachedAction );
262  mForwardActionMenu->addAction( mForwardInlineAction );
263  }
264 
265  mSaveAsAction = actionCollection()->addAction( KStandardAction::SaveAs, QLatin1String("search_file_save_as"), this, SLOT(slotSaveMsg()) );
266 
267  mSaveAtchAction = new KAction( KIcon( QLatin1String("mail-attachment") ), i18n( "Save Attachments..." ), this );
268  actionCollection()->addAction( QLatin1String("search_save_attachments"), mSaveAtchAction );
269  connect( mSaveAtchAction, SIGNAL(triggered(bool)), SLOT(slotSaveAttachments()) );
270 
271  mPrintAction = actionCollection()->addAction( KStandardAction::Print, QLatin1String("search_print"), this, SLOT(slotPrintMsg()) );
272 
273  mClearAction = new KAction( i18n( "Clear Selection" ), this );
274  actionCollection()->addAction( QLatin1String("search_clear_selection"), mClearAction );
275  connect( mClearAction, SIGNAL(triggered(bool)), SLOT(slotClearSelection()) );
276 
277  mJumpToFolderAction = new KAction( i18n( "Jump to original folder" ), this );
278  actionCollection()->addAction( QLatin1String("search_jump_folder"), mJumpToFolderAction );
279  connect( mJumpToFolderAction, SIGNAL(triggered(bool)), SLOT(slotJumpToFolder()) );
280 
281 
282  connect( mUi.mCbxFolders, SIGNAL(folderChanged(Akonadi::Collection)),
283  this, SLOT(slotFolderActivated()) );
284 
285  ac->addAssociatedWidget( this );
286  foreach ( QAction* action, ac->actions() )
287  action->setShortcutContext( Qt::WidgetWithChildrenShortcut );
288 }
289 
290 SearchWindow::~SearchWindow()
291 {
292  if ( mResultModel ) {
293  if ( mUi.mLbxMatches->columnWidth( 0 ) > 0 ) {
294  GlobalSettings::self()->setCollectionWidth( mUi.mLbxMatches->columnWidth( 0 ) );
295  }
296  if ( mUi.mLbxMatches->columnWidth( 1 ) > 0 ) {
297  GlobalSettings::self()->setSubjectWidth( mUi.mLbxMatches->columnWidth( 1 ) );
298  }
299  if ( mUi.mLbxMatches->columnWidth( 2 ) > 0 ) {
300  GlobalSettings::self()->setSenderWidth( mUi.mLbxMatches->columnWidth( 2 ) );
301  }
302  if ( mUi.mLbxMatches->columnWidth( 3 ) > 0 ) {
303  GlobalSettings::self()->setReceiverWidth( mUi.mLbxMatches->columnWidth( 3 ) );
304  }
305  if ( mUi.mLbxMatches->columnWidth( 4 ) > 0 ) {
306  GlobalSettings::self()->setDateWidth( mUi.mLbxMatches->columnWidth( 4 ) );
307  }
308  if ( mUi.mLbxMatches->columnWidth( 5 ) > 0 ) {
309  GlobalSettings::self()->setFolderWidth( mUi.mLbxMatches->columnWidth( 5 ) );
310  }
311  GlobalSettings::self()->setSearchWidgetWidth( width() );
312  GlobalSettings::self()->setSearchWidgetHeight( height() );
313  GlobalSettings::self()->requestSync();
314  mResultModel->deleteLater();
315  }
316 }
317 
318 void SearchWindow::createSearchModel()
319 {
320  if ( mResultModel ) {
321  mResultModel->deleteLater();
322  }
323  mResultModel = new KMSearchMessageModel( this );
324  mResultModel->setCollection( mFolder );
325  KMSearchFilterProxyModel *sortproxy = new KMSearchFilterProxyModel( mResultModel );
326  sortproxy->setSourceModel( mResultModel );
327  mUi.mLbxMatches->setModel( sortproxy );
328 
329  mUi.mLbxMatches->setColumnWidth( 0, GlobalSettings::self()->collectionWidth() );
330  mUi.mLbxMatches->setColumnWidth( 1, GlobalSettings::self()->subjectWidth() );
331  mUi.mLbxMatches->setColumnWidth( 2, GlobalSettings::self()->senderWidth() );
332  mUi.mLbxMatches->setColumnWidth( 3, GlobalSettings::self()->receiverWidth() );
333  mUi.mLbxMatches->setColumnWidth( 4, GlobalSettings::self()->dateWidth() );
334  mUi.mLbxMatches->setColumnWidth( 5, GlobalSettings::self()->folderWidth() );
335  mUi.mLbxMatches->setColumnHidden( 6, true );
336  mUi.mLbxMatches->setColumnHidden( 7, true );
337  mUi.mLbxMatches->header()->setSortIndicator( 2, Qt::DescendingOrder );
338  mUi.mLbxMatches->header()->setStretchLastSection( false );
339  mUi.mLbxMatches->header()->restoreState( mHeaderState );
340  //mUi.mLbxMatches->header()->setResizeMode( 3, QHeaderView::Stretch );
341  if(!mAkonadiStandardAction)
342  mAkonadiStandardAction = new Akonadi::StandardMailActionManager( actionCollection(), this );
343  mAkonadiStandardAction->setItemSelectionModel( mUi.mLbxMatches->selectionModel() );
344  mAkonadiStandardAction->setCollectionSelectionModel( mKMMainWidget->folderTreeView()->selectionModel() );
345 
346 }
347 
348 
349 void SearchWindow::setEnabledSearchButton( bool )
350 {
351  //Make sure that button is enable
352  //Before when we selected a folder == "Local Folder" as that it was not a folder
353  //search button was disable, and when we select "Search in all local folder"
354  //Search button was never enabled :(
355  mSearchButton->setEnabled( true );
356 }
357 
358 void SearchWindow::updateCollectionStatistic(Akonadi::Collection::Id id, const Akonadi::CollectionStatistics &statistic)
359 {
360  QString genMsg;
361  if ( id == mFolder.id() ) {
362  genMsg = i18np( "%1 match", "%1 matches", statistic.count() );
363  }
364  mUi.mStatusLbl->setText( genMsg );
365 }
366 
367 void SearchWindow::keyPressEvent( QKeyEvent *event )
368 {
369  if ( event->key() == Qt::Key_Escape && mSearchJob ) {
370  slotStop();
371  return;
372  }
373 
374  KDialog::keyPressEvent( event );
375 }
376 
377 void SearchWindow::slotFolderActivated()
378 {
379  mUi.mChkbxSpecificFolders->setChecked( true );
380 }
381 
382 void SearchWindow::activateFolder( const Akonadi::Collection &collection )
383 {
384  mUi.mChkbxSpecificFolders->setChecked( true );
385  mUi.mCbxFolders->setCollection( collection );
386 }
387 
388 void SearchWindow::slotSearch()
389 {
390  if (mFolder.isValid()) {
391  doSearch();
392  return;
393  }
394  //We're going to try to create a new search folder, let's ensure first the name is not yet used.
395 
396  //Fetch all search collections
397  Akonadi::CollectionFetchJob *fetchJob = new Akonadi::CollectionFetchJob(Akonadi::Collection(1), Akonadi::CollectionFetchJob::FirstLevel);
398  connect(fetchJob, SIGNAL(result(KJob*)), this, SLOT(slotSearchCollectionsFetched(KJob*)));
399 }
400 
401 void SearchWindow::slotSearchCollectionsFetched(KJob *job)
402 {
403  if (job->error()) {
404  kWarning() << job->errorString();
405  }
406  Akonadi::CollectionFetchJob *fetchJob = static_cast<Akonadi::CollectionFetchJob*>(job);
407  Q_FOREACH(const Akonadi::Collection &col, fetchJob->collections()) {
408  if (col.name() == mUi.mSearchFolderEdt->text()) {
409  mFolder = col;
410  }
411  }
412  doSearch();
413 }
414 
415 void SearchWindow::doSearch()
416 {
417  mSearchPatternWidget->hideWarningPattern();
418  if ( mUi.mSearchFolderEdt->text().isEmpty() ) {
419  mUi.mSearchFolderEdt->setText( i18n( "Last Search" ) );
420  }
421 
422  if ( mResultModel )
423  mHeaderState = mUi.mLbxMatches->header()->saveState();
424 
425  mUi.mLbxMatches->setModel( 0 );
426 
427  mSortColumn = mUi.mLbxMatches->header()->sortIndicatorSection();
428  mSortOrder = mUi.mLbxMatches->header()->sortIndicatorOrder();
429  mUi.mLbxMatches->setSortingEnabled( false );
430 
431  if ( mSearchJob ) {
432  mSearchJob->kill( KJob::Quietly );
433  mSearchJob->deleteLater();
434  mSearchJob = 0;
435  }
436 
437  mUi.mSearchFolderEdt->setEnabled( false );
438 
439  Akonadi::Collection::List searchCollections;
440  bool recursive = false;
441  if ( mUi.mChkbxSpecificFolders->isChecked() ) {
442  const Akonadi::Collection col = mUi.mCbxFolders->collection();
443  if (!col.isValid()) {
444  mSearchPatternWidget->showWarningPattern(QStringList()<<i18n("You did not selected a valid folder."));
445  mUi.mSearchFolderEdt->setEnabled( true );
446  return;
447  }
448  searchCollections << col;
449  if ( mUi.mChkSubFolders->isChecked() ) {
450  recursive = true;
451  }
452  } else if (mUi.mChkMultiFolders->isChecked()) {
453  if (!mSelectMultiCollectionDialog) {
454  mSearchPatternWidget->showWarningPattern(QStringList()<<i18n("You forgot to select collections."));
455  return;
456  }
457  mCollectionId = mSelectMultiCollectionDialog->selectedCollection();
458  Q_FOREACH(const Akonadi::Collection &col, mCollectionId) {
459  searchCollections << col;
460  }
461  if (searchCollections.isEmpty()) {
462  mUi.mSearchFolderEdt->setEnabled( true );
463  mSearchPatternWidget->showWarningPattern(QStringList()<<i18n("You forgot to select collections."));
464  mQuery = Akonadi::SearchQuery();
465  return;
466  }
467  }
468 
469  mUi.mPatternEdit->updateSearchPattern();
470 
471  SearchPattern searchPattern( mSearchPattern );
472  searchPattern.purify();
473 
474  MailCommon::SearchPattern::SparqlQueryError queryError = MailCommon::SearchPattern::NoError;
475  queryError = searchPattern.asAkonadiQuery(mQuery);
476  switch(queryError) {
477  case MailCommon::SearchPattern::NoError:
478  break;
479  case MailCommon::SearchPattern::MissingCheck:
480  mUi.mSearchFolderEdt->setEnabled( true );
481  mSearchPatternWidget->showWarningPattern(QStringList()<<i18n("You forgot to define condition."));
482  mQuery = Akonadi::SearchQuery();
483  return;
484  case MailCommon::SearchPattern::FolderEmptyOrNotIndexed:
485  mUi.mSearchFolderEdt->setEnabled( true );
486  mSearchPatternWidget->showWarningPattern(QStringList()<<i18n("All folders selected are empty or were not indexed."));
487  mQuery = Akonadi::SearchQuery();
488  return;
489  case MailCommon::SearchPattern::EmptyResult:
490  mUi.mSearchFolderEdt->setEnabled( true );
491  mQuery = Akonadi::SearchQuery();
492  mSearchPatternWidget->showWarningPattern(QStringList()<<i18n("You forgot to add conditions."));
493  return;
494  case MailCommon::SearchPattern::NotEnoughCharacters:
495  mUi.mSearchFolderEdt->setEnabled( true );
496  mSearchPatternWidget->showWarningPattern(QStringList()<<i18n("Contains condition cannot be used with a number of characters inferior to 4."));
497  mQuery = Akonadi::SearchQuery();
498  return;
499  }
500  mSearchPatternWidget->hideWarningPattern();
501  kDebug() << mQuery.toJSON();
502  mUi.mSearchFolderOpenBtn->setEnabled( true );
503 
504  if ( !mFolder.isValid() ) {
505  kDebug()<<" create new folder " << mUi.mSearchFolderEdt->text();
506  Akonadi::SearchCreateJob *searchJob = new Akonadi::SearchCreateJob( mUi.mSearchFolderEdt->text(), mQuery, this );
507  searchJob->setSearchMimeTypes( QStringList() << QLatin1String("message/rfc822") );
508  searchJob->setSearchCollections( searchCollections );
509  searchJob->setRecursive( recursive );
510  searchJob->setRemoteSearchEnabled( false );
511  mSearchJob = searchJob;
512  } else {
513  kDebug()<<" use existing folder " << mFolder.id();
514  Akonadi::PersistentSearchAttribute *attribute = new Akonadi::PersistentSearchAttribute();
515  mFolder.setContentMimeTypes(QStringList() << QLatin1String("message/rfc822"));
516  attribute->setQueryLanguage( QLatin1String("akonadi") );
517  attribute->setQueryString( QString::fromLatin1(mQuery.toJSON()) );
518  attribute->setQueryCollections( searchCollections );
519  attribute->setRecursive( recursive );
520  attribute->setRemoteSearchEnabled( false );
521  mFolder.addAttribute(attribute);
522  mSearchJob = new Akonadi::CollectionModifyJob( mFolder, this );
523  }
524 
525  connect( mSearchJob, SIGNAL(result(KJob*)), SLOT(searchDone(KJob*)) );
526  mUi.mProgressIndicator->start();
527  enableGUI();
528  mUi.mStatusLbl->setText( i18n( "Searching..." ) );
529 }
530 
531 void SearchWindow::searchDone( KJob* job )
532 {
533  Q_ASSERT( job == mSearchJob );
534  QMetaObject::invokeMethod( this, "enableGUI", Qt::QueuedConnection );
535  mUi.mProgressIndicator->stop();
536  if ( job->error() ) {
537  kDebug() << job->errorString();
538  KMessageBox::sorry( this, i18n("Cannot get search result. %1", job->errorString() ) );
539  if ( mSearchJob ) {
540  mSearchJob = 0;
541  }
542  enableGUI();
543  mUi.mSearchFolderEdt->setEnabled( true );
544  mUi.mStatusLbl->setText( i18n("Search failed.") );
545  }
546  else
547  {
548  if ( Akonadi::SearchCreateJob *searchJob = qobject_cast<Akonadi::SearchCreateJob*>( mSearchJob ) ) {
549  mFolder = searchJob->createdCollection();
550  } else if ( Akonadi::CollectionModifyJob *modifyJob = qobject_cast<Akonadi::CollectionModifyJob*>( mSearchJob ) ) {
551  mFolder = modifyJob->collection();
552  }
554  Q_ASSERT( mFolder.isValid() );
555  Q_ASSERT( mFolder.hasAttribute<Akonadi::PersistentSearchAttribute>() );
556 
557  GlobalSettings::setLastSearchCollectionId( mFolder.id() );
558  GlobalSettings::self()->writeConfig();
559  GlobalSettings::self()->requestSync();
560 
561  // store the kmail specific serialization of the search in an attribute on
562  // the server, for easy retrieval when editing it again
563  const QByteArray search = mSearchPattern.serialize();
564  Q_ASSERT( !search.isEmpty() );
565  Akonadi::SearchDescriptionAttribute *searchDescription = mFolder.attribute<Akonadi::SearchDescriptionAttribute>( Akonadi::Entity::AddIfMissing );
566  searchDescription->setDescription( search );
567  if ( mUi.mChkMultiFolders->isChecked()) {
568  searchDescription->setBaseCollection( Akonadi::Collection() );
569  QList<Akonadi::Collection::Id> lst;
570  Q_FOREACH (const Akonadi::Collection &col, mCollectionId) {
571  lst << col.id();
572  }
573  searchDescription->setListCollection(lst);
574  } else if (mUi.mChkbxSpecificFolders->isChecked()) {
575  const Akonadi::Collection collection = mUi.mCbxFolders->collection();
576  searchDescription->setBaseCollection( collection );
577  } else {
578  searchDescription->setBaseCollection( Akonadi::Collection() );
579  }
580  searchDescription->setRecursive( mUi.mChkSubFolders->isChecked() );
581  new Akonadi::CollectionModifyJob( mFolder, this );
582  mSearchJob = 0;
583 
584  mUi.mStatusLbl->setText( i18n("Search complete.") );
585  createSearchModel();
586 
587  if ( mCloseRequested )
588  close();
589 
590  mUi.mLbxMatches->setSortingEnabled( true );
591  mUi.mLbxMatches->header()->setSortIndicator( mSortColumn, mSortOrder );
592 
593  mUi.mSearchFolderEdt->setEnabled( true );
594  }
595 }
596 
597 void SearchWindow::slotStop()
598 {
599  mUi.mProgressIndicator->stop();
600  if ( mSearchJob ) {
601  mSearchJob->kill( KJob::Quietly );
602  mSearchJob->deleteLater();
603  mSearchJob = 0;
604  mUi.mStatusLbl->setText( i18n("Search stopped.") );
605  }
606 
607  enableGUI();
608 }
609 
610 void SearchWindow::slotClose()
611 {
612  accept();
613 }
614 
615 void SearchWindow::closeEvent( QCloseEvent *event )
616 {
617  if ( mSearchJob ) {
618  mCloseRequested = true;
619  //Cancel search in progress
620  mSearchJob->kill( KJob::Quietly );
621  mSearchJob->deleteLater();
622  mSearchJob = 0;
623  QTimer::singleShot( 0, this, SLOT(slotClose()) );
624  } else {
625  KDialog::closeEvent( event );
626  }
627 }
628 
629 void SearchWindow::scheduleRename( const QString &text )
630 {
631  if ( !text.isEmpty() ) {
632  mRenameTimer.setSingleShot( true );
633  mRenameTimer.start( 250 );
634  mUi.mSearchFolderOpenBtn->setEnabled( false );
635  } else {
636  mRenameTimer.stop();
637  mUi.mSearchFolderOpenBtn->setEnabled( !text.isEmpty() );
638  }
639 }
640 
641 void SearchWindow::renameSearchFolder()
642 {
643  const QString name = mUi.mSearchFolderEdt->text();
644  if ( mFolder.isValid() ) {
645  const QString oldFolderName = mFolder.name();
646  if ( oldFolderName != name ) {
647  mFolder.setName( name );
648  Akonadi::CollectionModifyJob *job = new Akonadi::CollectionModifyJob( mFolder, this );
649  job->setProperty("oldfoldername", oldFolderName);
650  connect( job, SIGNAL(result(KJob*)),
651  this, SLOT(slotSearchFolderRenameDone(KJob*)) );
652  }
653  mUi.mSearchFolderOpenBtn->setEnabled( true );
654  }
655 }
656 
657 void SearchWindow::slotSearchFolderRenameDone( KJob *job )
658 {
659  Q_ASSERT( job );
660  if ( job->error() ) {
661  kWarning() << "Job failed:" << job->errorText();
662  KMessageBox::information( this, i18n( "There was a problem renaming your search folder. "
663  "A common reason for this is that another search folder "
664  "with the same name already exists. Error returned \"%1\".", job->errorText() ) );
665  mUi.mSearchFolderEdt->blockSignals(true);
666  mUi.mSearchFolderEdt->setText(job->property("oldfoldername").toString());
667  mUi.mSearchFolderEdt->blockSignals(false);
668  }
669 }
670 
671 void SearchWindow::openSearchFolder()
672 {
673  Q_ASSERT( mFolder.isValid() );
674  renameSearchFolder();
675  mKMMainWidget->slotSelectCollectionFolder( mFolder );
676  slotClose();
677 }
678 
679 void SearchWindow::slotViewSelectedMsg()
680 {
681  mKMMainWidget->slotMessageActivated( selectedMessage() );
682 }
683 
684 void SearchWindow::slotViewMsg( const Akonadi::Item &item )
685 {
686  if ( item.isValid() ) {
687  mKMMainWidget->slotMessageActivated( item );
688  }
689 }
690 
691 void SearchWindow::slotCurrentChanged( const Akonadi::Item &item )
692 {
693  mUi.mSearchResultOpenBtn->setEnabled( item.isValid() );
694 }
695 
696 void SearchWindow::enableGUI()
697 {
698  const bool searching = (mSearchJob != 0);
699 
700  mSearchButton->setGuiItem( searching ? mStopSearchGuiItem : mStartSearchGuiItem );
701  if ( searching ) {
702  disconnect( mSearchButton, SIGNAL(clicked()), this, SLOT(slotSearch()) );
703  connect( mSearchButton, SIGNAL(clicked()), SLOT(slotStop()) );
704  } else {
705  disconnect( mSearchButton, SIGNAL(clicked()), this, SLOT(slotStop()) );
706  connect( mSearchButton, SIGNAL(clicked()), SLOT(slotSearch()) );
707  }
708 }
709 
710 Akonadi::Item::List SearchWindow::selectedMessages() const
711 {
712  Akonadi::Item::List messages;
713 
714  foreach ( const QModelIndex &index, mUi.mLbxMatches->selectionModel()->selectedRows() ) {
715  const Akonadi::Item item = index.data( Akonadi::ItemModel::ItemRole ).value<Akonadi::Item>();
716  if ( item.isValid() )
717  messages.append( item );
718  }
719 
720  return messages;
721 }
722 
723 Akonadi::Item SearchWindow::selectedMessage() const
724 {
725  return mUi.mLbxMatches->currentIndex().data( Akonadi::ItemModel::ItemRole ).value<Akonadi::Item>();
726 }
727 
728 void SearchWindow::updateContextMenuActions()
729 {
730  const int count = selectedMessages().count();
731  const bool singleActions = (count == 1);
732  const bool notEmpty = (count > 0);
733 
734  mJumpToFolderAction->setEnabled( singleActions );
735 
736  mReplyAction->setEnabled( singleActions );
737  mReplyAllAction->setEnabled( singleActions );
738  mReplyListAction->setEnabled( singleActions );
739  mPrintAction->setEnabled( singleActions );
740  mSaveAtchAction->setEnabled( notEmpty );
741  mSaveAsAction->setEnabled( notEmpty );
742  mClearAction->setEnabled( notEmpty );
743 }
744 
745 void SearchWindow::slotContextMenuRequested( const QPoint& )
746 {
747  if ( !selectedMessage().isValid() || selectedMessages().isEmpty() )
748  return;
749 
750  QMenu *menu = new QMenu( this );
751  updateContextMenuActions();
752 
753  // show most used actions
754  menu->addAction( mReplyAction );
755  menu->addAction( mReplyAllAction );
756  menu->addAction( mReplyListAction );
757  menu->addAction( mForwardActionMenu );
758  menu->addSeparator();
759  menu->addAction( mJumpToFolderAction );
760  menu->addSeparator();
761  KAction *act = mAkonadiStandardAction->createAction( Akonadi::StandardActionManager::CopyItems );
762  mAkonadiStandardAction->setActionText( Akonadi::StandardActionManager::CopyItems, ki18np( "Copy Message", "Copy %1 Messages" ) );
763  menu->addAction( act );
764  act = mAkonadiStandardAction->createAction( Akonadi::StandardActionManager::CutItems );
765  mAkonadiStandardAction->setActionText( Akonadi::StandardActionManager::CutItems, ki18np( "Cut Message", "Cut %1 Messages" ) );
766  menu->addAction( act );
767  menu->addAction( mAkonadiStandardAction->createAction( Akonadi::StandardActionManager::CopyItemToMenu ) );
768  menu->addAction( mAkonadiStandardAction->createAction( Akonadi::StandardActionManager::MoveItemToMenu ) );
769  menu->addSeparator();
770  menu->addAction( mSaveAsAction );
771  menu->addAction( mSaveAtchAction );
772  menu->addAction( mPrintAction );
773  menu->addSeparator();
774  menu->addAction( mClearAction );
775  menu->exec( QCursor::pos(), 0 );
776 
777  delete menu;
778 }
779 
780 void SearchWindow::slotClearSelection()
781 {
782  mUi.mLbxMatches->clearSelection();
783 }
784 
785 void SearchWindow::slotReplyToMsg()
786 {
787  KMCommand *command = new KMReplyCommand( this, selectedMessage(), MessageComposer::ReplySmart );
788  command->start();
789 }
790 
791 void SearchWindow::slotReplyAllToMsg()
792 {
793  KMCommand *command = new KMReplyCommand( this, selectedMessage(),MessageComposer::ReplyAll );
794  command->start();
795 }
796 
797 void SearchWindow::slotReplyListToMsg()
798 {
799  KMCommand *command = new KMReplyCommand( this, selectedMessage(),MessageComposer::ReplyList );
800  command->start();
801 }
802 
803 void SearchWindow::slotForwardMsg()
804 {
805  KMCommand *command = new KMForwardCommand( this, selectedMessages() );
806  command->start();
807 }
808 
809 void SearchWindow::slotForwardAttachedMsg()
810 {
811  KMCommand *command = new KMForwardAttachedCommand( this, selectedMessages() );
812  command->start();
813 }
814 
815 void SearchWindow::slotSaveMsg()
816 {
817  KMSaveMsgCommand *saveCommand = new KMSaveMsgCommand( this, selectedMessages() );
818  saveCommand->start();
819 }
820 
821 void SearchWindow::slotSaveAttachments()
822 {
823  KMSaveAttachmentsCommand *saveCommand = new KMSaveAttachmentsCommand( this, selectedMessages() );
824  saveCommand->start();
825 }
826 
827 void SearchWindow::slotPrintMsg()
828 {
829  KMCommand *command = new KMPrintCommand( this, selectedMessage() );
830  command->start();
831 }
832 
833 void SearchWindow::addRulesToSearchPattern( const SearchPattern &pattern )
834 {
835  SearchPattern p( mSearchPattern );
836  p.purify();
837 
838  QList<SearchRule::Ptr>::const_iterator it;
839  QList<SearchRule::Ptr>::const_iterator end(pattern.constEnd() ) ;
840 
841  for ( it = pattern.constBegin() ; it != end ; ++it ) {
842  p.append( SearchRule::createInstance( **it ) );
843  }
844 
845  mSearchPattern = p;
846  mUi.mPatternEdit->setSearchPattern( &mSearchPattern );
847 }
848 
849 void SearchWindow::slotSelectMultipleFolders()
850 {
851  mUi.mChkMultiFolders->setChecked(true);
852  if (!mSelectMultiCollectionDialog) {
853  QList<Akonadi::Collection::Id> lst;
854  Q_FOREACH (const Akonadi::Collection &col, mCollectionId) {
855  lst << col.id();
856  }
857  mSelectMultiCollectionDialog = new PimCommon::SelectMultiCollectionDialog(KMime::Message::mimeType(), lst, this);
858  }
859  mSelectMultiCollectionDialog->show();
860 }
861 
862 void SearchWindow::slotJumpToFolder()
863 {
864  if (selectedMessage().isValid()) {
865  mKMMainWidget->slotSelectCollectionFolder( selectedMessage().parentCollection() );
866  }
867 }
868 
869 }
870 
QWidget::layout
QLayout * layout() const
Akonadi::SearchDescriptionAttribute::description
QByteArray description() const
Definition: searchdescriptionattribute.cpp:67
KMail::SearchWindow::selectedMessage
Akonadi::Item selectedMessage() const
Provides access to the currently selected message.
Definition: searchwindow.cpp:723
QList::clear
void clear()
KMReplyCommand
Definition: kmcommands.h:319
QModelIndex
Akonadi::SearchDescriptionAttribute::setDescription
void setDescription(const QByteArray &desc)
Definition: searchdescriptionattribute.cpp:72
QWidget
QAction::setShortcutContext
void setShortcutContext(Qt::ShortcutContext context)
kmmainwidget.h
searchpatternwarning.h
QByteArray
QSortFilterProxyModel::setSourceModel
virtual void setSourceModel(QAbstractItemModel *sourceModel)
text
virtual QByteArray text(quint32 serialNumber) const =0
QMenu::addAction
void addAction(QAction *action)
QByteArray::isEmpty
bool isEmpty() const
QVariant::value
T value() const
KMMainWidget::slotMessageActivated
void slotMessageActivated(const Akonadi::Item &)
Open a separate viewer window containing the specified message.
Definition: kmmainwidget.cpp:2515
QPoint
KDialog
QList::const_iterator
KMail::SearchWindow::selectedMessages
Akonadi::Item::List selectedMessages() const
Provides access to the list of currently selected message in the listview.
Definition: searchwindow.cpp:710
KMKernel::self
static KMKernel * self()
normal control stuff
Definition: kmkernel.cpp:1471
searchwindow.h
KMMainWidget::folderTreeView
MailCommon::FolderTreeView * folderTreeView() const
Definition: kmmainwidget.h:148
QCloseEvent
KMSaveAttachmentsCommand
Definition: kmcommands.h:298
QBoxLayout::addWidget
void addWidget(QWidget *widget, int stretch, QFlags< Qt::AlignmentFlag > alignment)
QList::append
void append(const T &value)
Akonadi::SearchDescriptionAttribute::recursive
bool recursive() const
Definition: searchdescriptionattribute.cpp:87
KMail::SearchWindow::closeEvent
void closeEvent(QCloseEvent *)
Reimplemented to stop searching when the window is closed.
Definition: searchwindow.cpp:615
KMCommand
Small helper structure which encapsulates the KMMessage created when creating a reply, and.
Definition: kmcommands.h:48
Akonadi::SearchDescriptionAttribute::baseCollection
Akonadi::Collection baseCollection() const
Definition: searchdescriptionattribute.cpp:77
QList::isEmpty
bool isEmpty() const
QString::isEmpty
bool isEmpty() const
KMCommand::start
void start()
Definition: kmcommands.cpp:233
Akonadi::SearchDescriptionAttribute
Definition: searchdescriptionattribute.h:30
QVBoxLayout
KMail::SearchWindow::createSearchModel
void createSearchModel()
Definition: searchwindow.cpp:318
KMMainWidget
Definition: kmmainwidget.h:91
KMail::SearchWindow::addRulesToSearchPattern
void addRulesToSearchPattern(const MailCommon::SearchPattern &pattern)
Loads a search pattern into the search window, appending its rules to the current one...
Definition: searchwindow.cpp:833
kmsearchmessagemodel.h
QMenu::addSeparator
QAction * addSeparator()
QString
QList< Akonadi::Collection::Id >
QLayout::setMargin
void setMargin(int margin)
QMenu::exec
QAction * exec()
QStringList
KMail::KMSearchFilterProxyModel
Definition: kmsearchfilterproxymodel.h:37
KMail::SearchWindow::~SearchWindow
~SearchWindow()
Destroys the search window.
Definition: searchwindow.cpp:290
QKeyEvent::key
int key() const
QMenu
KMForwardAttachedCommand
Definition: kmcommands.h:355
GlobalSettings::requestSync
void requestSync()
Call this slot instead of directly KConfig::sync() to minimize the overall config writes...
Definition: globalsettings.cpp:47
QTimer::stop
void stop()
KMMainWidget::slotSelectCollectionFolder
void slotSelectCollectionFolder(const Akonadi::Collection &col)
Definition: kmmainwidget.cpp:2133
QMetaObject::invokeMethod
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
KMForwardCommand
Definition: kmcommands.h:336
QKeyEvent
GlobalSettings::self
static GlobalSettings * self()
Definition: globalsettings.cpp:30
KActionMenu
KMail::SearchPatternWarning
Definition: searchpatternwarning.h:25
KMail::SearchWindow::keyPressEvent
void keyPressEvent(QKeyEvent *)
Reimplemented to react to Escape.
Definition: searchwindow.cpp:367
QCursor::pos
QPoint pos()
QModelIndex::data
QVariant data(int role) const
QLatin1String
KMSearchMessageModel
Definition: kmsearchmessagemodel.h:33
QAction
Akonadi::SearchDescriptionAttribute::listCollection
QList< Akonadi::Collection::Id > listCollection() const
Definition: searchdescriptionattribute.cpp:102
KMPrintCommand
Definition: kmcommands.h:384
QString::fromLatin1
QString fromLatin1(const char *str, int size)
KMail::SearchPatternWarning::showWarningPattern
void showWarningPattern(const QStringList &lstError)
Definition: searchpatternwarning.cpp:41
QTimer::start
void start(int msec)
KMail::SearchPatternWarning::hideWarningPattern
void hideWarningPattern()
Definition: searchpatternwarning.cpp:47
kmsearchfilterproxymodel.h
kmcommands.h
KMSaveMsgCommand
Definition: kmcommands.h:261
KJob
KMMainWidget::guiClient
KXMLGUIClient * guiClient() const
Returns the XML GUI client.
Definition: kmmainwidget.h:153
QAction::setEnabled
void setEnabled(bool)
KMail::SearchWindow::activateFolder
void activateFolder(const Akonadi::Collection &folder)
Changes the base folder for search operations to a different folder.
Definition: searchwindow.cpp:382
searchdescriptionattribute.h
QTimer::singleShot
singleShot
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Mon Jun 22 2020 13:34:33 by doxygen 1.8.7 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

kmail

Skip menu "kmail"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members

kdepim API Reference

Skip menu "kdepim API Reference"
  • akonadi_next
  • akregator
  • blogilo
  • calendarsupport
  • console
  •   kabcclient
  •   konsolekalendar
  • kaddressbook
  • kalarm
  •   lib
  • kdgantt2
  • kjots
  • kleopatra
  • kmail
  • knode
  • knotes
  • kontact
  • korgac
  • korganizer
  • ktimetracker
  • libkdepim
  • libkleo
  • libkpgp
  • mailcommon
  • messagelist
  • messageviewer
  • pimprint

Search



Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal