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

messagelist

  • sources
  • kde-4.12
  • kdepim
  • messagelist
  • core
view.cpp
Go to the documentation of this file.
1 /******************************************************************************
2  *
3  * Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  *
19  *******************************************************************************/
20 
21 #include "core/view.h"
22 #include "core/aggregation.h"
23 #include "core/delegate.h"
24 #include "core/groupheaderitem.h"
25 #include "core/item.h"
26 #include "core/manager.h"
27 #include "core/messageitem.h"
28 #include "core/model.h"
29 #include "core/theme.h"
30 #include "core/settings.h"
31 #include "core/storagemodelbase.h"
32 #include "core/widgetbase.h"
33 #include "messagelistutil.h"
34 
35 #include "messagecore/utils/stringutil.h"
36 
37 #include <Nepomuk2/ResourceManager>
38 
39 #include <kmime/kmime_dateformatter.h> // kdepimlibs
40 
41 #include <Akonadi/Item>
42 #include <QHelpEvent>
43 #include <QToolTip>
44 #include <QHeaderView>
45 #include <QTimer>
46 #include <QPaintEvent>
47 #include <QTextDocument>
48 #include <QApplication>
49 #include <QScrollBar>
50 #include <QSignalMapper>
51 
52 #include <KMenu>
53 #include <KLocale>
54 #include <KDebug>
55 #include <KGlobalSettings>
56 #include <KIcon>
57 
58 using namespace MessageList::Core;
59 
60 
61 class View::Private
62 {
63 public:
64  Private( View *owner, Widget *parent )
65  : q( owner ), mWidget( parent ), mModel( 0 ), mDelegate( new Delegate( owner ) ),
66  mAggregation( 0 ), mTheme( 0 ), mNeedToApplyThemeColumns( false ),
67  mLastCurrentItem( 0 ), mFirstShow( true ), mSaveThemeColumnStateOnSectionResize( true ),
68  mSaveThemeColumnStateTimer( 0 ), mApplyThemeColumnsTimer( 0 ),
69  mIgnoreUpdateGeometries( false ) { }
70 
71  void expandFullThread( const QModelIndex &index );
72 
73  View * const q;
74 
75  Widget *mWidget;
76  Model *mModel;
77  Delegate *mDelegate;
78 
79  const Aggregation *mAggregation;
80  Theme *mTheme;
81  bool mNeedToApplyThemeColumns;
82  Item *mLastCurrentItem;
83  QPoint mMousePressPosition;
84  bool mFirstShow;
85  bool mSaveThemeColumnStateOnSectionResize;
86  QTimer * mSaveThemeColumnStateTimer;
87  QTimer * mApplyThemeColumnsTimer;
88  bool mIgnoreUpdateGeometries;
89 };
90 
91 View::View( Widget *pParent )
92  : QTreeView( pParent ), d( new Private( this, pParent ) )
93 {
94  d->mSaveThemeColumnStateTimer = new QTimer();
95  connect( d->mSaveThemeColumnStateTimer, SIGNAL(timeout()), this, SLOT(saveThemeColumnState()) );
96 
97  d->mApplyThemeColumnsTimer = new QTimer();
98  connect( d->mApplyThemeColumnsTimer, SIGNAL(timeout()), this, SLOT(applyThemeColumns()) );
99 
100  setItemDelegate( d->mDelegate );
101  setVerticalScrollMode( QAbstractItemView::ScrollPerPixel );
102  setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOn );
103  setAlternatingRowColors( true );
104  setAllColumnsShowFocus( true );
105  setSelectionMode( QAbstractItemView::ExtendedSelection );
106  viewport()->setAcceptDrops( true );
107 
108  header()->setContextMenuPolicy( Qt::CustomContextMenu );
109  connect( header(), SIGNAL(customContextMenuRequested(QPoint)),
110  SLOT(slotHeaderContextMenuRequested(QPoint)) );
111  connect( header(), SIGNAL(sectionResized(int,int,int)),
112  SLOT(slotHeaderSectionResized(int,int,int)) );
113 
114  header()->setClickable( true );
115  header()->setResizeMode( QHeaderView::Interactive );
116  header()->setMinimumSectionSize( 2 ); // QTreeView overrides our sections sizes if we set them smaller than this value
117  header()->setDefaultSectionSize( 2 ); // QTreeView overrides our sections sizes if we set them smaller than this value
118 
119  d->mModel = new Model( this );
120  setModel( d->mModel );
121 
122  connect( d->mModel, SIGNAL(statusMessage(QString)),
123  pParent, SIGNAL(statusMessage(QString)) );
124 
125  //connect( selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
126  // this, SLOT(slotCurrentIndexChanged(QModelIndex,QModelIndex)) );
127  connect( selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
128  this, SLOT(slotSelectionChanged(QItemSelection,QItemSelection)),
129  Qt::UniqueConnection );
130 
131  // as in KDE3, when a root-item of a message thread is expanded, expand all children
132  connect( this, SIGNAL(expanded(QModelIndex)), this, SLOT(expandFullThread(QModelIndex)) );
133 }
134 
135 View::~View()
136 {
137  if ( d->mSaveThemeColumnStateTimer->isActive() )
138  d->mSaveThemeColumnStateTimer->stop();
139  delete d->mSaveThemeColumnStateTimer;
140  if ( d->mApplyThemeColumnsTimer->isActive() )
141  d->mApplyThemeColumnsTimer->stop();
142  delete d->mApplyThemeColumnsTimer;
143 
144  // Zero out the theme, aggregation and ApplyThemeColumnsTimer so Model will not cause accesses to them in its destruction process
145  d->mApplyThemeColumnsTimer = 0;
146 
147  d->mTheme = 0;
148  d->mAggregation = 0;
149 
150  delete d; d = 0;
151 }
152 
153 Model *View::model() const
154 {
155  return d->mModel;
156 }
157 
158 Delegate *View::delegate() const
159 {
160  return d->mDelegate;
161 }
162 
163 void View::ignoreCurrentChanges( bool ignore )
164 {
165  if ( ignore ) {
166  disconnect( selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
167  this, SLOT(slotSelectionChanged(QItemSelection,QItemSelection)) );
168  viewport()->setUpdatesEnabled( false );
169  } else {
170  connect( selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
171  this, SLOT(slotSelectionChanged(QItemSelection,QItemSelection)),
172  Qt::UniqueConnection );
173  viewport()->setUpdatesEnabled( true );
174  }
175 }
176 
177 void View::ignoreUpdateGeometries( bool ignore )
178 {
179  d->mIgnoreUpdateGeometries = ignore;
180 }
181 
182 bool View::isScrollingLocked() const
183 {
184  // There is another popular requisite: people want the view to automatically
185  // scroll in order to show new arriving mail. This actually makes sense
186  // only when the view is sorted by date and the new mail is (usually) either
187  // appended at the bottom or inserted at the top. It would be also confusing
188  // when the user is browsing some other thread in the meantime.
189  //
190  // So here we make a simple guess: if the view is scrolled somewhere in the
191  // middle then we assume that the user is browsing other threads and we
192  // try to keep the currently selected item steady on the screen.
193  // When the view is "locked" to the top (scrollbar value 0) or to the
194  // bottom (scrollbar value == maximum) then we assume that the user
195  // isn't browsing and we should attempt to show the incoming messages
196  // by keeping the view "locked".
197  //
198  // The "locking" also doesn't make sense in the first big fill view job.
199  // [Well this concept is pre-akonadi. Now the loading is all async anyway...
200  // So all this code is actually triggered during the initial loading, too.]
201  const int scrollBarPosition = verticalScrollBar()->value();
202  const int scrollBarMaximum = verticalScrollBar()->maximum();
203  const SortOrder* sortOrder = d->mModel->sortOrder();
204  const bool lockView = (
205  // not the first loading job
206  !d->mModel->isLoading()
207  ) && (
208  // messages sorted by date
209  ( sortOrder->messageSorting() == SortOrder::SortMessagesByDateTime ) ||
210  ( sortOrder->messageSorting() == SortOrder::SortMessagesByDateTimeOfMostRecent )
211  ) && (
212  // scrollbar at top (Descending order) or bottom (Ascending order)
213  ( scrollBarPosition == 0 && sortOrder->messageSortDirection() == SortOrder::Descending ) ||
214  ( scrollBarPosition == scrollBarMaximum && sortOrder->messageSortDirection() == SortOrder::Ascending )
215  );
216  return lockView;
217 }
218 
219 void View::updateGeometries()
220 {
221  if( d->mIgnoreUpdateGeometries || !d->mModel )
222  return;
223 
224  const int scrollBarPositionBefore = verticalScrollBar()->value();
225  const bool lockView = isScrollingLocked();
226 
227  QTreeView::updateGeometries();
228 
229  if ( lockView )
230  {
231  // we prefer to keep the view locked to the top or bottom
232  if ( scrollBarPositionBefore != 0 )
233  {
234  // we wanted the view to be locked to the bottom
235  if ( verticalScrollBar()->value() != verticalScrollBar()->maximum() )
236  verticalScrollBar()->setValue( verticalScrollBar()->maximum() );
237  } // else we wanted the view to be locked to top and we shouldn't need to do anything
238  }
239 }
240 
241 StorageModel * View::storageModel() const
242 {
243  return d->mModel->storageModel();
244 }
245 
246 void View::setAggregation( const Aggregation * aggregation )
247 {
248  d->mAggregation = aggregation;
249  d->mModel->setAggregation( aggregation );
250 
251  // use uniform row heights to speed up, but only if there are no group headers used
252  setUniformRowHeights( d->mAggregation->grouping() == Aggregation::NoGrouping );
253 }
254 
255 void View::setTheme( Theme * theme )
256 {
257  d->mNeedToApplyThemeColumns = true;
258  d->mTheme = theme;
259  d->mDelegate->setTheme( theme );
260  d->mModel->setTheme( theme );
261 }
262 
263 void View::setSortOrder( const SortOrder * sortOrder )
264 {
265  d->mModel->setSortOrder( sortOrder );
266 }
267 
268 void View::reload()
269 {
270  setStorageModel( storageModel() );
271 }
272 
273 void View::setStorageModel( StorageModel * storageModel, PreSelectionMode preSelectionMode )
274 {
275  // This will cause the model to be reset.
276  d->mSaveThemeColumnStateOnSectionResize = false;
277  d->mModel->setStorageModel( storageModel, preSelectionMode );
278  d->mSaveThemeColumnStateOnSectionResize = true;
279 }
280 
281 void View::modelJobBatchStarted()
282 {
283  // This is called by the model when the first job of a batch starts
284  d->mWidget->viewJobBatchStarted();
285 }
286 
287 void View::modelJobBatchTerminated()
288 {
289  // This is called by the model when all the pending jobs have been processed
290  d->mWidget->viewJobBatchTerminated();
291 }
292 
293 void View::modelHasBeenReset()
294 {
295  // This is called by Model when it has been reset.
296  if ( d && d->mNeedToApplyThemeColumns )
297  applyThemeColumns();
298 }
299 
301 // Theme column state machinery
302 //
303 // This is yet another beast to beat. The QHeaderView behaviour, at the time of writing,
304 // is quite unpredictable. This is due to the complex interaction with the model, with the QTreeView
305 // and due to its attempts to delay the layout jobs. The delayed layouts, especially, may
306 // cause the widths of the columns to quickly change in an unexpected manner in a place
307 // where previously they have been always settled to the values you set...
308 //
309 // So here we have the tools to:
310 //
311 // - Apply the saved state of the theme columns (applyThemeColumns()).
312 // This function computes the "best fit" state of the visible columns and tries
313 // to apply it to QHeaderView. It also saves the new computed state to the Theme object.
314 //
315 // - Explicitly save the column state, used when the user changes the widths or visibility manually.
316 // This is called through a delayed timer after a column has been resized or used directly
317 // when the visibility state of a column has been changed by toggling a popup menu entry.
318 //
319 // - Display the column state context popup menu and handle its actions
320 //
321 // - Apply the theme columns when the theme changes, when the model changes or in certain
322 // ugly corner cases when the widget is resized or shown.
323 //
324 // - Avoid saving a corrupted column state in that QHeaderView can be found *very* frequently.
325 //
326 
327 void View::applyThemeColumns()
328 {
329  if ( !d->mApplyThemeColumnsTimer ) {
330  return;
331  }
332 
333  if ( d->mApplyThemeColumnsTimer->isActive() )
334  d->mApplyThemeColumnsTimer->stop();
335 
336  if ( !d->mTheme )
337  return;
338 
339  //kDebug() << "Apply theme columns";
340 
341 
342  const QList< Theme::Column * > & columns = d->mTheme->columns();
343 
344  if ( columns.isEmpty() )
345  return; // bad theme
346 
347  if ( !viewport()->isVisible() )
348  return; // invisible
349 
350  if ( viewport()->width() < 1 )
351  return; // insane width
352 
353  // Now we want to distribute the available width on all the visible columns.
354  //
355  // The rules:
356  // - The visible columns will span the width of the view, if possible.
357  // - The columns with a saved width should take that width.
358  // - The columns on the left should take more space, if possible.
359  // - The columns with no text take just slightly more than their size hint.
360  // while the columns with text take possibly a lot more.
361  //
362 
363  // Note that the first column is always shown (it can't be hidden at all)
364 
365  // The algorithm below is a sort of compromise between:
366  // - Saving the user preferences for widths
367  // - Using exactly the available view space
368  //
369  // It "tends to work" in all cases:
370  // - When there are no user preferences saved and the column widths must be
371  // automatically computed to make best use of available space
372  // - When there are user preferences for only some of the columns
373  // and that should be somewhat preserved while still using all the
374  // available space.
375  // - When all the columns have well defined saved widths
376 
377  QList< Theme::Column * >::ConstIterator it;
378  int idx = 0;
379 
380  // Gather total size "hint" for visible sections: if the widths of the columns wers
381  // all saved then the total hint is equal to the total saved width.
382 
383  int totalVisibleWidthHint = 0;
384  QList< int > lColumnSizeHints;
385  QList< Theme::Column * >::ConstIterator end( columns.end() );
386 
387  for ( it = columns.constBegin(); it != end; ++it )
388  {
389  if ( ( *it )->currentlyVisible() || ( idx == 0 ) )
390  {
391  //kDebug() << "Column " << idx << " will be visible";
392  // Column visible
393  const int savedWidth = ( *it )->currentWidth();
394  const int hintWidth = d->mDelegate->sizeHintForItemTypeAndColumn( Item::Message, idx ).width();
395  totalVisibleWidthHint += savedWidth > 0 ? savedWidth : hintWidth;
396  lColumnSizeHints.append( hintWidth );
397  //kDebug() << "Column " << idx << " size hint is " << hintWidth;
398  } else {
399  //kDebug() << "Column " << idx << " will be not visible";
400  // The column is not visible
401  lColumnSizeHints.append( -1 ); // dummy
402  }
403  idx++;
404  }
405 
406  if ( totalVisibleWidthHint < 16 )
407  totalVisibleWidthHint = 16; // be reasonable
408 
409  // Now compute somewhat "proportional" widths.
410  idx = 0;
411 
412  QList< int > lColumnWidths;
413  int totalVisibleWidth = 0;
414  end = columns.constEnd();
415  for ( it = columns.constBegin(); it != end; ++it )
416  {
417  int savedWidth = ( *it )->currentWidth();
418  int hintWidth = savedWidth > 0 ? savedWidth : lColumnSizeHints[ idx ];
419  int realWidth;
420 
421  if ( ( *it )->currentlyVisible() || ( idx == 0 ) )
422  {
423  if ( ( *it )->containsTextItems() )
424  {
425  // the column contains text items, it should get more space (if possible)
426  realWidth = ( ( hintWidth * viewport()->width() ) / totalVisibleWidthHint );
427  } else {
428  // the column contains no text items, it should get exactly its hint/saved width.
429  realWidth = hintWidth;
430  }
431 
432  if ( realWidth < 2 )
433  realWidth = 2; // don't allow very insane values
434 
435  totalVisibleWidth += realWidth;
436  } else {
437  // Column not visible
438  realWidth = -1;
439  }
440 
441  lColumnWidths.append( realWidth );
442 
443  idx++;
444  }
445 
446  // Now the algorithm above may be wrong for several reasons...
447  // - We're using fixed widths for certain columns and proportional
448  // for others...
449  // - The user might have changed the width of the view from the
450  // time in that the widths have been saved
451  // - There are some (not well identified) issues with the QTreeView
452  // scrollbar that make our view appear larger or shorter by 2-3 pixels
453  // sometimes.
454  // - ...
455  // So we correct the previous estimates by trying to use exactly
456  // the available space.
457 
458  idx = 0;
459 
460  if ( totalVisibleWidth != viewport()->width() )
461  {
462  // The estimated widths were not using exactly the available space.
463  if ( totalVisibleWidth < viewport()->width() )
464  {
465  // We were using less space than available.
466 
467  // Give the additional space to the text columns
468  // also give more space to the first ones and less space to the last ones
469  int available = viewport()->width() - totalVisibleWidth;
470 
471  end = columns.end();
472  for ( it = columns.begin(); it != end; ++it )
473  {
474  if ( ( ( *it )->currentlyVisible() || ( idx == 0 ) ) && ( *it )->containsTextItems() )
475  {
476  // give more space to this column
477  available >>= 1; // eat half of the available space
478  lColumnWidths[ idx ] += available; // and give it to this column
479  if ( available < 1 )
480  break; // no more space to give away
481  }
482 
483  idx++;
484  }
485 
486  // if any space is still available, give it to the first column
487  if ( available )
488  lColumnWidths[ 0 ] += available;
489  } else {
490  // We were using more space than available
491 
492  // If the columns span just a little bit more than the view then
493  // try to squeeze them in order to make them fit
494  if ( totalVisibleWidth < ( viewport()->width() + 100 ) )
495  {
496  int missing = totalVisibleWidth - viewport()->width();
497  int count = lColumnWidths.count();
498 
499  if ( missing > 0 )
500  {
501  idx = count - 1;
502 
503  while ( idx >= 0 )
504  {
505  if ( columns.at( idx )->currentlyVisible() || ( idx == 0 ) )
506  {
507  int chop = lColumnWidths[ idx ] - lColumnSizeHints[ idx ];
508  if ( chop > 0 )
509  {
510  if ( chop > missing )
511  chop = missing;
512  lColumnWidths[ idx ] -= chop;
513  missing -= chop;
514  if ( missing < 1 )
515  break; // no more space to recover
516  }
517  } // else it's invisible
518  idx--;
519  }
520  }
521  }
522  }
523  }
524 
525  // We're ready to assign widths.
526 
527  bool oldSave = d->mSaveThemeColumnStateOnSectionResize;
528  d->mSaveThemeColumnStateOnSectionResize = false;
529 
530  // A huge problem here is that QHeaderView goes quite nuts if we show or hide sections
531  // while resizing them. This is because it has several machineries aimed to delay
532  // the layout to the last possible moment. So if we show a column, it will tend to
533  // screw up the layout of other ones.
534 
535  // We first loop showing/hiding columns then.
536 
537  idx = 0;
538 
539  //kDebug() << "Entering column show/hide loop";
540 
541  end = columns.constEnd();
542  for ( it = columns.constBegin(); it != end; ++it )
543  {
544  bool visible = ( idx == 0 ) || ( *it )->currentlyVisible();
545  //kDebug() << "Column " << idx << " visible " << visible;
546  ( *it )->setCurrentlyVisible( visible );
547  header()->setSectionHidden( idx, !visible );
548  idx++;
549  }
550 
551  // Then we loop assigning widths. This is still complicated since QHeaderView tries
552  // very badly to stretch the last section and thus will resize it in the meantime.
553  // But seems to work most of the times...
554 
555  idx = 0;
556 
557  end = columns.constEnd();
558  for ( it = columns.constBegin(); it != end; ++it )
559  {
560  if ( ( *it )->currentlyVisible() )
561  {
562  //kDebug() << "Resize section " << idx << " to " << lColumnWidths[ idx ];
563  const int columnWidth( lColumnWidths[ idx ] );
564  ( *it )->setCurrentWidth( columnWidth );
565  header()->resizeSection( idx, columnWidth );
566  } else {
567  ( *it )->setCurrentWidth( -1 );
568  }
569  idx++;
570  }
571 
572  idx = 0;
573 
574  bool bTriggeredQtBug = false;
575  end = columns.constEnd();
576  for ( it = columns.constBegin(); it != end; ++it )
577  {
578  if ( !header()->isSectionHidden( idx ) )
579  {
580  if ( !( *it )->currentlyVisible() )
581  {
582  bTriggeredQtBug = true;
583  }
584  }
585  idx++;
586  }
587 
588  setHeaderHidden( d->mTheme->viewHeaderPolicy() == Theme::NeverShowHeader );
589 
590  d->mSaveThemeColumnStateOnSectionResize = oldSave;
591  d->mNeedToApplyThemeColumns = false;
592 
593  static bool bAllowRecursion = true;
594 
595  if (bTriggeredQtBug && bAllowRecursion)
596  {
597  bAllowRecursion = false;
598  //kDebug() << "I've triggered the QHeaderView bug: trying to fix by calling myself again";
599  applyThemeColumns();
600  bAllowRecursion = true;
601  }
602 }
603 
604 void View::triggerDelayedApplyThemeColumns()
605 {
606  if ( d->mApplyThemeColumnsTimer->isActive() )
607  d->mApplyThemeColumnsTimer->stop();
608  d->mApplyThemeColumnsTimer->setSingleShot( true );
609  d->mApplyThemeColumnsTimer->start( 100 );
610 }
611 
612 void View::saveThemeColumnState()
613 {
614  if ( d->mSaveThemeColumnStateTimer->isActive() )
615  d->mSaveThemeColumnStateTimer->stop();
616 
617  if ( !d->mTheme )
618  return;
619 
620  if ( d->mNeedToApplyThemeColumns )
621  return; // don't save the state if it hasn't been applied at all
622 
623  //kDebug() << "Save theme column state";
624 
625  const QList< Theme::Column * > & columns = d->mTheme->columns();
626 
627  if ( columns.isEmpty() )
628  return; // bad theme
629 
630  int idx = 0;
631 
632  QList< Theme::Column * >::ConstIterator end(columns.constEnd());
633  for ( QList< Theme::Column * >::ConstIterator it = columns.constBegin(); it != end; ++it )
634  {
635  if ( header()->isSectionHidden( idx ) )
636  {
637  //kDebug() << "Section " << idx << " is hidden";
638  ( *it )->setCurrentlyVisible( false );
639  ( *it )->setCurrentWidth( -1 ); // reset (hmmm... we could use the "don't touch" policy here too...)
640  } else {
641  //kDebug() << "Section " << idx << " is visible and has size " << header()->sectionSize( idx );
642  ( *it )->setCurrentlyVisible( true );
643  ( *it )->setCurrentWidth( header()->sectionSize( idx ) );
644  }
645  idx++;
646  }
647 }
648 
649 void View::triggerDelayedSaveThemeColumnState()
650 {
651  if ( d->mSaveThemeColumnStateTimer->isActive() )
652  d->mSaveThemeColumnStateTimer->stop();
653  d->mSaveThemeColumnStateTimer->setSingleShot( true );
654  d->mSaveThemeColumnStateTimer->start( 200 );
655 }
656 
657 void View::resizeEvent( QResizeEvent * e )
658 {
659  kDebug() << "Resize event enter (viewport width is " << viewport()->width() << ")";
660 
661  QTreeView::resizeEvent( e );
662 
663  if ( !isVisible() )
664  return; // don't play with
665 
666  if ( (!d->mFirstShow) && d->mNeedToApplyThemeColumns )
667  triggerDelayedApplyThemeColumns();
668 
669  if ( header()->isVisible() )
670  return;
671 
672  // header invisible
673 
674  bool oldSave = d->mSaveThemeColumnStateOnSectionResize;
675  d->mSaveThemeColumnStateOnSectionResize = false;
676 
677  const int count = header()->count();
678  if ( ( count - header()->hiddenSectionCount() ) < 2 )
679  {
680  // a single column visible: resize it
681  int visibleIndex;
682  for ( visibleIndex = 0; visibleIndex < count; visibleIndex++ )
683  {
684  if ( !header()->isSectionHidden( visibleIndex ) )
685  break;
686  }
687  if ( visibleIndex < count )
688  header()->resizeSection( visibleIndex, viewport()->width() - 4 );
689  }
690 
691  d->mSaveThemeColumnStateOnSectionResize = oldSave;
692 
693  triggerDelayedSaveThemeColumnState();
694 }
695 
696 void View::modelAboutToEmitLayoutChanged()
697 {
698  // QHeaderView goes totally NUTS with a layoutChanged() call
699  d->mSaveThemeColumnStateOnSectionResize = false;
700 }
701 
702 void View::modelEmittedLayoutChanged()
703 {
704  // This is after a first chunk of work has been done by the model: do apply column states
705  d->mSaveThemeColumnStateOnSectionResize = true;
706  applyThemeColumns();
707 }
708 
709 void View::slotHeaderSectionResized( int logicalIndex, int oldWidth, int newWidth )
710 {
711  Q_UNUSED( logicalIndex );
712  Q_UNUSED( oldWidth );
713  Q_UNUSED( newWidth );
714 
715  if ( d->mSaveThemeColumnStateOnSectionResize )
716  triggerDelayedSaveThemeColumnState();
717 }
718 
719 int View::sizeHintForColumn( int logicalColumnIndex ) const
720 {
721  // QTreeView: please don't touch my column widths...
722  int w = header()->sectionSize( logicalColumnIndex );
723  if ( w > 0 )
724  return w;
725  if ( !d->mDelegate )
726  return 32; // dummy
727  w = d->mDelegate->sizeHintForItemTypeAndColumn( Item::Message, logicalColumnIndex ).width();
728  return w;
729 }
730 
731 void View::showEvent( QShowEvent *e )
732 {
733  QTreeView::showEvent( e );
734  if ( d->mFirstShow )
735  {
736  // If we're shown for the first time and the theme has been already set
737  // then we need to reapply the theme column widths since the previous
738  // application probably used invalid widths.
739  //
740  if ( d->mTheme )
741  triggerDelayedApplyThemeColumns();
742  d->mFirstShow = false;
743  }
744 }
745 
746 const int gHeaderContextMenuAdjustColumnSizesId = -1;
747 const int gHeaderContextMenuShowDefaultColumnsId = -2;
748 const int gHeaderContextMenuDisplayToolTipsId = -3;
749 
750 void View::slotHeaderContextMenuRequested( const QPoint &pnt )
751 {
752  if ( !d->mTheme )
753  return;
754 
755  const QList< Theme::Column * > & columns = d->mTheme->columns();
756 
757  if ( columns.isEmpty() )
758  return; // bad theme
759 
760  // the menu for the columns
761  KMenu menu;
762 
763  QSignalMapper *showColumnSignalMapper = new QSignalMapper( &menu );
764  int idx = 0;
765  QList< Theme::Column * >::ConstIterator end(columns.end());
766  for ( QList< Theme::Column * >::ConstIterator it = columns.begin(); it != end; ++it )
767  {
768  QAction *act = menu.addAction( ( *it )->label() );
769  act->setCheckable( true );
770  act->setChecked( !header()->isSectionHidden( idx ) );
771  if ( idx == 0 )
772  act->setEnabled( false );
773  QObject::connect( act, SIGNAL(triggered()), showColumnSignalMapper, SLOT(map()) );
774  showColumnSignalMapper->setMapping( act, idx );
775 
776  idx++;
777  }
778  QObject::connect( showColumnSignalMapper, SIGNAL(mapped(int)), this, SLOT(slotShowHideColumn(int)) );
779 
780  menu.addSeparator();
781  {
782  QAction *act = menu.addAction( i18n( "Adjust Column Sizes" ) );
783  QObject::connect( act, SIGNAL(triggered()), this, SLOT(slotAdjustColumnSizes()) );
784  }
785  {
786  QAction *act = menu.addAction( i18n( "Show Default Columns" ) );
787  QObject::connect( act, SIGNAL(triggered()), this, SLOT(slotShowDefaultColumns()) );
788  }
789  menu.addSeparator();
790  {
791  QAction *act = menu.addAction( i18n( "Display Tooltips" ) );
792  act->setCheckable( true );
793  act->setChecked( Settings::self()->messageToolTipEnabled() );
794  QObject::connect( act, SIGNAL(triggered(bool)), this, SLOT(slotDisplayTooltips(bool)) );
795  }
796  menu.addSeparator();
797 
798  MessageList::Util::fillViewMenu( &menu, d->mWidget );
799 
800  menu.exec( header()->mapToGlobal( pnt ) );
801 }
802 
803 void View::slotAdjustColumnSizes()
804 {
805  if ( !d->mTheme )
806  return;
807 
808  d->mTheme->resetColumnSizes();
809  applyThemeColumns();
810 }
811 
812 void View::slotShowDefaultColumns()
813 {
814  if ( !d->mTheme )
815  return;
816 
817  d->mTheme->resetColumnState();
818  applyThemeColumns();
819 }
820 
821 void View::slotDisplayTooltips( bool showTooltips )
822 {
823  Settings::self()->setMessageToolTipEnabled( showTooltips );
824 }
825 
826 void View::slotShowHideColumn( int columnIdx )
827 {
828  if ( !d->mTheme )
829  return; // oops
830 
831  if ( columnIdx == 0 )
832  return; // can never be hidden
833 
834  if ( columnIdx >= d->mTheme->columns().count() )
835  return;
836 
837  const bool showIt = header()->isSectionHidden( columnIdx );
838 
839  Theme::Column * column = d->mTheme->columns().at( columnIdx );
840  Q_ASSERT( column );
841 
842  // first save column state (as it is, with the column still in previous state)
843  saveThemeColumnState();
844 
845  // If a section has just been shown, invalidate its width in the skin
846  // since QTreeView assigned it a (possibly insane) default width.
847  // If a section has been hidden, then invalidate its width anyway...
848  // so finally invalidate width always, here.
849  column->setCurrentlyVisible( showIt );
850  column->setCurrentWidth( -1 );
851 
852  // then apply theme columns to re-compute proportional widths (so we hopefully stay in the view)
853  applyThemeColumns();
854 }
855 
856 Item* View::currentItem() const
857 {
858  QModelIndex idx = currentIndex();
859  if ( !idx.isValid() )
860  return 0;
861  Item * it = static_cast< Item * >( idx.internalPointer() );
862  Q_ASSERT( it );
863  return it;
864 }
865 
866 MessageItem * View::currentMessageItem( bool selectIfNeeded ) const
867 {
868  Item *it = currentItem();
869  if ( !it || ( it->type() != Item::Message ) )
870  return 0;
871 
872  if ( selectIfNeeded )
873  {
874  // Keep things coherent, if the user didn't select it, but acted on it via
875  // a shortcut, do select it now.
876  if ( !selectionModel()->isSelected( currentIndex() ) )
877  selectionModel()->select( currentIndex(), QItemSelectionModel::Select | QItemSelectionModel::Current | QItemSelectionModel::Rows );
878  }
879 
880  return static_cast< MessageItem * >( it );
881 }
882 
883 void View::setCurrentMessageItem( MessageItem * it, bool center )
884 {
885  if ( it )
886  {
887  kDebug() << "Setting current message to" << it->subject();
888 
889  const QModelIndex index = d->mModel->index( it, 0 );
890  selectionModel()->setCurrentIndex( index, QItemSelectionModel::Select |
891  QItemSelectionModel::Current | QItemSelectionModel::Rows );
892  if ( center ) {
893  scrollTo( index, QAbstractItemView::PositionAtCenter );
894  }
895  }
896  else
897  selectionModel()->setCurrentIndex( QModelIndex(), QItemSelectionModel::Current |
898  QItemSelectionModel::Clear );
899 }
900 
901 bool View::selectionEmpty() const
902 {
903  return selectionModel()->selectedRows().isEmpty();
904 }
905 
906 QList< MessageItem * > View::selectionAsMessageItemList( bool includeCollapsedChildren ) const
907 {
908  QList< MessageItem * > selectedMessages;
909 
910  QModelIndexList lSelected = selectionModel()->selectedRows();
911  if ( lSelected.isEmpty() )
912  return selectedMessages;
913  QModelIndexList::ConstIterator end( lSelected.constEnd() );
914  for ( QModelIndexList::ConstIterator it = lSelected.constBegin(); it != end; ++it )
915  {
916  // The asserts below are theoretically valid but at the time
917  // of writing they fail because of a bug in QItemSelectionModel::selectedRows()
918  // which returns also non-selectable items.
919 
920  //Q_ASSERT( selectedItem->type() == Item::Message );
921  //Q_ASSERT( ( *it ).isValid() );
922 
923  if ( !( *it ).isValid() )
924  continue;
925 
926  Item * selectedItem = static_cast< Item * >( ( *it ).internalPointer() );
927  Q_ASSERT( selectedItem );
928 
929  if ( selectedItem->type() != Item::Message )
930  continue;
931 
932  if ( !static_cast< MessageItem * >( selectedItem )->isValid() )
933  continue;
934 
935  Q_ASSERT( !selectedMessages.contains( static_cast< MessageItem * >( selectedItem ) ) );
936 
937  if ( includeCollapsedChildren && ( selectedItem->childItemCount() > 0 ) && ( !isExpanded( *it ) ) )
938  {
939  static_cast< MessageItem * >( selectedItem )->subTreeToList( selectedMessages );
940  } else {
941  selectedMessages.append( static_cast< MessageItem * >( selectedItem ) );
942  }
943  }
944 
945  return selectedMessages;
946 }
947 
948 QList< MessageItem * > View::currentThreadAsMessageItemList() const
949 {
950  QList< MessageItem * > currentThread;
951 
952  MessageItem * msg = currentMessageItem();
953  if ( !msg )
954  return currentThread;
955 
956  while ( msg->parent() )
957  {
958  if ( msg->parent()->type() != Item::Message )
959  break;
960  msg = static_cast< MessageItem * >( msg->parent() );
961  }
962 
963  msg->subTreeToList( currentThread );
964 
965  return currentThread;
966 }
967 
968 void View::setChildrenExpanded( const Item * root, bool expand )
969 {
970  Q_ASSERT( root );
971  QList< Item * > * childList = root->childItems();
972  if ( !childList )
973  return;
974  QList< Item * >::ConstIterator end( childList->constEnd() );
975  for ( QList< Item * >::ConstIterator it = childList->constBegin(); it != end; ++it )
976  {
977  QModelIndex idx = d->mModel->index( *it, 0 );
978  Q_ASSERT( idx.isValid() );
979  Q_ASSERT( static_cast< Item * >( idx.internalPointer() ) == ( *it ) );
980 
981  if ( expand )
982  {
983  setExpanded( idx, true );
984 
985  if ( ( *it )->childItemCount() > 0 )
986  setChildrenExpanded( *it, true );
987  } else {
988  if ( ( *it )->childItemCount() > 0 )
989  setChildrenExpanded( *it, false );
990 
991  setExpanded( idx, false );
992  }
993  }
994 }
995 
996 void View::Private::expandFullThread( const QModelIndex & index )
997 {
998  if ( ! index.isValid() )
999  return;
1000 
1001  Item * item = static_cast< Item * >( index.internalPointer() );
1002  if ( item->type() != Item::Message )
1003  return;
1004 
1005  if ( ! static_cast< MessageItem * >( item )->parent() ||
1006  ( static_cast< MessageItem * >( item )->parent()->type() != Item::Message ) )
1007  q->setChildrenExpanded( item, true );
1008 }
1009 
1010 void View::setCurrentThreadExpanded( bool expand )
1011 {
1012  Item *it = currentItem();
1013  if (!it)
1014  return;
1015 
1016  if ( it->type() == Item::GroupHeader ) {
1017  setExpanded( currentIndex(), expand );
1018  } else if ( it->type() == Item::Message ) {
1019  MessageItem * message = static_cast< MessageItem *>( it );
1020  while ( message->parent() )
1021  {
1022  if ( message->parent()->type() != Item::Message )
1023  break;
1024  message = static_cast< MessageItem * >( message->parent() );
1025  }
1026 
1027  if ( expand )
1028  {
1029  setExpanded( d->mModel->index( message, 0 ), true );
1030  setChildrenExpanded( message, true );
1031  } else {
1032  setChildrenExpanded( message, false );
1033  setExpanded( d->mModel->index( message, 0 ), false );
1034  }
1035  }
1036 }
1037 
1038 void View::setAllThreadsExpanded( bool expand )
1039 {
1040  if ( d->mAggregation->grouping() == Aggregation::NoGrouping )
1041  {
1042  // we have no groups so threads start under the root item: just expand/unexpand all
1043  setChildrenExpanded( d->mModel->rootItem(), expand );
1044  return;
1045  }
1046 
1047  // grouping is in effect: must expand/unexpand one level lower
1048 
1049  QList< Item * > * childList = d->mModel->rootItem()->childItems();
1050  if ( !childList )
1051  return;
1052 
1053  foreach ( Item * item, *childList )
1054  setChildrenExpanded( item, expand );
1055 }
1056 
1057 void View::setAllGroupsExpanded( bool expand )
1058 {
1059  if ( d->mAggregation->grouping() == Aggregation::NoGrouping )
1060  return; // no grouping in effect
1061 
1062  Item * item = d->mModel->rootItem();
1063 
1064  QList< Item * > * childList = item->childItems();
1065  if ( !childList )
1066  return;
1067 
1068  foreach ( Item * item, *childList )
1069  {
1070  Q_ASSERT( item->type() == Item::GroupHeader );
1071  QModelIndex idx = d->mModel->index( item, 0 );
1072  Q_ASSERT( idx.isValid() );
1073  Q_ASSERT( static_cast< Item * >( idx.internalPointer() ) == item );
1074  if ( expand ) {
1075  if ( !isExpanded( idx ) )
1076  setExpanded( idx, true );
1077  } else {
1078  if ( isExpanded( idx ) )
1079  setExpanded( idx, false );
1080  }
1081  }
1082 }
1083 
1084 void View::selectMessageItems( const QList< MessageItem * > &list )
1085 {
1086  QItemSelection selection;
1087  QList< MessageItem * >::ConstIterator end( list.constEnd() );
1088  for ( QList< MessageItem * >::ConstIterator it = list.constBegin(); it != end; ++it )
1089  {
1090  Q_ASSERT( *it );
1091  QModelIndex idx = d->mModel->index( *it, 0 );
1092  Q_ASSERT( idx.isValid() );
1093  Q_ASSERT( static_cast< MessageItem * >( idx.internalPointer() ) == ( *it ) );
1094  if ( !selectionModel()->isSelected( idx ) )
1095  selection.append( QItemSelectionRange( idx ) );
1096  ensureDisplayedWithParentsExpanded( *it );
1097  }
1098  if ( !selection.isEmpty() )
1099  selectionModel()->select( selection, QItemSelectionModel::Select | QItemSelectionModel::Rows );
1100 }
1101 
1102 static inline bool message_type_matches( Item * item, MessageTypeFilter messageTypeFilter )
1103 {
1104  switch( messageTypeFilter )
1105  {
1106  case MessageTypeAny:
1107  return true;
1108  break;
1109  case MessageTypeUnreadOnly:
1110  return !item->status().isRead();
1111  break;
1112  default:
1113  // nothing here
1114  break;
1115  }
1116 
1117  // never reached
1118  Q_ASSERT( false );
1119  return false;
1120 }
1121 
1122 Item * View::messageItemAfter( Item * referenceItem, MessageTypeFilter messageTypeFilter, bool loop )
1123 {
1124  if ( !storageModel() )
1125  return 0; // no folder
1126 
1127  // find the item to start with
1128  Item * below;
1129 
1130  if ( referenceItem )
1131  {
1132  // there was a current item: we start just below it
1133  if (
1134  ( referenceItem->childItemCount() > 0 )
1135  &&
1136  (
1137  ( messageTypeFilter != MessageTypeAny )
1138  ||
1139  isExpanded( d->mModel->index( referenceItem, 0 ) )
1140  )
1141  )
1142  {
1143  // the current item had children: either expanded or we want unread/new messages (and so we'll expand it if it isn't)
1144  below = referenceItem->itemBelow();
1145  } else {
1146  // the current item had no children: ask the parent to find the item below
1147  Q_ASSERT( referenceItem->parent() );
1148  below = referenceItem->parent()->itemBelowChild( referenceItem );
1149  }
1150 
1151  if ( !below )
1152  {
1153  // reached the end
1154  if ( loop )
1155  {
1156  // try re-starting from top
1157  below = d->mModel->rootItem()->itemBelow();
1158  Q_ASSERT( below ); // must exist (we had a current item)
1159 
1160  if ( below == referenceItem )
1161  return 0; // only one item in folder: loop complete
1162  } else {
1163  // looping not requested
1164  return 0;
1165  }
1166  }
1167 
1168  } else {
1169  // there was no current item, start from beginning
1170  below = d->mModel->rootItem()->itemBelow();
1171 
1172  if ( !below )
1173  return 0; // folder empty
1174  }
1175 
1176  // ok.. now below points to the next message.
1177  // While it doesn't satisfy our requirements, go further down
1178 
1179  QModelIndex parentIndex = d->mModel->index( below->parent(), 0 );
1180  QModelIndex belowIndex = d->mModel->index( below, 0 );
1181 
1182  Q_ASSERT( belowIndex.isValid() );
1183 
1184  while (
1185  // is not a message (we want messages, don't we ?)
1186  ( below->type() != Item::Message ) ||
1187  // message filter doesn't match
1188  ( !message_type_matches( below, messageTypeFilter ) ) ||
1189  // is hidden (and we don't want hidden items as they arent "officially" in the view)
1190  isRowHidden( belowIndex.row(), parentIndex ) ||
1191  // is not enabled or not selectable
1192  ( ( d->mModel->flags( belowIndex ) & ( Qt::ItemIsSelectable | Qt::ItemIsEnabled ) ) != ( Qt::ItemIsSelectable | Qt::ItemIsEnabled ) )
1193  )
1194  {
1195  // find the next one
1196  if ( ( below->childItemCount() > 0 ) && ( ( messageTypeFilter != MessageTypeAny ) || isExpanded( belowIndex ) ) )
1197  {
1198  // the current item had children: either expanded or we want unread messages (and so we'll expand it if it isn't)
1199  below = below->itemBelow();
1200  } else {
1201  // the current item had no children: ask the parent to find the item below
1202  Q_ASSERT( below->parent() );
1203  below = below->parent()->itemBelowChild( below );
1204  }
1205 
1206  if ( !below )
1207  {
1208  // we reached the end of the folder
1209  if ( loop )
1210  {
1211  // looping requested
1212  if ( referenceItem ) // <-- this means "we have started from something that is not the top: looping makes sense"
1213  below = d->mModel->rootItem()->itemBelow();
1214  // else mi == 0 and below == 0: we have started from the beginning and reached the end (it will fail the test below and exit)
1215  } else {
1216  // looping not requested: nothing more to do
1217  return 0;
1218  }
1219  }
1220 
1221  if( below == referenceItem )
1222  {
1223  Q_ASSERT( loop );
1224  return 0; // looped and returned back to the first message
1225  }
1226 
1227  parentIndex = d->mModel->index( below->parent(), 0 );
1228  belowIndex = d->mModel->index( below, 0 );
1229 
1230  Q_ASSERT( belowIndex.isValid() );
1231  }
1232 
1233  return below;
1234 }
1235 
1236 Item * View::nextMessageItem( MessageTypeFilter messageTypeFilter, bool loop )
1237 {
1238  return messageItemAfter( currentMessageItem( false ), messageTypeFilter, loop );
1239 }
1240 
1241 Item * View::deepestExpandedChild( Item * referenceItem ) const
1242 {
1243  const int children = referenceItem->childItemCount();
1244  if ( children > 0 &&
1245  isExpanded( d->mModel->index( referenceItem, 0 ) ) ) {
1246  return deepestExpandedChild( referenceItem->childItem( children -1 ) );
1247  }
1248  else
1249  return referenceItem;
1250 }
1251 
1252 Item * View::messageItemBefore( Item * referenceItem, MessageTypeFilter messageTypeFilter, bool loop )
1253 {
1254  if ( !storageModel() )
1255  return 0; // no folder
1256 
1257  // find the item to start with
1258  Item * above;
1259 
1260  if ( referenceItem )
1261  {
1262  Item *parent = referenceItem->parent();
1263  Item *siblingAbove = parent ?
1264  parent->itemAboveChild( referenceItem ) : 0;
1265  // there was a current item: we start just above it
1266  if ( ( siblingAbove && siblingAbove != referenceItem && siblingAbove != parent ) &&
1267  ( siblingAbove->childItemCount() > 0 ) &&
1268  (
1269  ( messageTypeFilter != MessageTypeAny ) ||
1270  ( isExpanded( d->mModel->index( siblingAbove, 0 ) ) )
1271  )
1272  )
1273  {
1274  // the current item had children: either expanded or we want unread/new messages (and so we'll expand it if it isn't)
1275  above = deepestExpandedChild( siblingAbove );
1276  } else {
1277  // the current item had no children: ask the parent to find the item above
1278  Q_ASSERT( referenceItem->parent() );
1279  above = referenceItem->parent()->itemAboveChild( referenceItem );
1280  }
1281 
1282  if ( ( !above ) || ( above == d->mModel->rootItem() ) )
1283  {
1284  // reached the beginning
1285  if ( loop )
1286  {
1287  // try re-starting from bottom
1288  above = d->mModel->rootItem()->deepestItem();
1289  Q_ASSERT( above ); // must exist (we had a current item)
1290  Q_ASSERT( above != d->mModel->rootItem() );
1291 
1292  if ( above == referenceItem )
1293  return 0; // only one item in folder: loop complete
1294  } else {
1295  // looping not requested
1296  return 0;
1297  }
1298 
1299  }
1300  } else {
1301  // there was no current item, start from end
1302  above = d->mModel->rootItem()->deepestItem();
1303 
1304  if ( !above || !above->parent() || ( above == d->mModel->rootItem() ) )
1305  return 0; // folder empty
1306  }
1307 
1308  // ok.. now below points to the previous message.
1309  // While it doesn't satisfy our requirements, go further up
1310 
1311  QModelIndex parentIndex = d->mModel->index( above->parent(), 0 );
1312  QModelIndex aboveIndex = d->mModel->index( above, 0 );
1313 
1314  Q_ASSERT( aboveIndex.isValid() );
1315 
1316  while (
1317  // is not a message (we want messages, don't we ?)
1318  ( above->type() != Item::Message ) ||
1319  // message filter doesn't match
1320  ( !message_type_matches( above, messageTypeFilter ) ) ||
1321  // we don't expand items but the item has parents unexpanded (so should be skipped)
1322  (
1323  // !expand items
1324  ( messageTypeFilter == MessageTypeAny ) &&
1325  // has unexpanded parents or is itself hidden
1326  ( ! isDisplayedWithParentsExpanded( above ) )
1327  ) ||
1328  // is hidden
1329  isRowHidden( aboveIndex.row(), parentIndex ) ||
1330  // is not enabled or not selectable
1331  ( ( d->mModel->flags( aboveIndex ) & ( Qt::ItemIsSelectable | Qt::ItemIsEnabled ) ) != ( Qt::ItemIsSelectable | Qt::ItemIsEnabled ) )
1332  )
1333  {
1334 
1335  above = above->itemAbove();
1336 
1337  if ( ( !above ) || ( above == d->mModel->rootItem() ) )
1338  {
1339  // reached the beginning
1340  if ( loop )
1341  {
1342  // looping requested
1343  if ( referenceItem ) // <-- this means "we have started from something that is not the beginning: looping makes sense"
1344  above = d->mModel->rootItem()->deepestItem();
1345  // else mi == 0 and above == 0: we have started from the end and reached the beginning (it will fail the test below and exit)
1346  } else {
1347  // looping not requested: nothing more to do
1348  return 0;
1349  }
1350  }
1351 
1352  if( above == referenceItem )
1353  {
1354  Q_ASSERT( loop );
1355  return 0; // looped and returned back to the first message
1356  }
1357 
1358  if(!above->parent())
1359  return 0;
1360 
1361  parentIndex = d->mModel->index( above->parent(), 0 );
1362  aboveIndex = d->mModel->index( above, 0 );
1363 
1364  Q_ASSERT( aboveIndex.isValid() );
1365  }
1366 
1367  return above;
1368 }
1369 
1370 Item * View::previousMessageItem( MessageTypeFilter messageTypeFilter, bool loop )
1371 {
1372  return messageItemBefore( currentMessageItem( false ), messageTypeFilter, loop );
1373 }
1374 
1375 void View::growOrShrinkExistingSelection( const QModelIndex &newSelectedIndex, bool movingUp )
1376 {
1377  // Qt: why visualIndex() is private? ...I'd really need it here...
1378 
1379  int selectedVisualCoordinate = visualRect( newSelectedIndex ).top();
1380 
1381  int topVisualCoordinate = 0xfffffff; // huuuuuge number
1382  int bottomVisualCoordinate = -(0xfffffff);
1383 
1384  int candidate;
1385 
1386  QModelIndex bottomIndex;
1387  QModelIndex topIndex;
1388 
1389  // find out the actual selection range
1390  const QItemSelection selection = selectionModel()->selection();
1391 
1392  foreach ( const QItemSelectionRange &range, selection )
1393  {
1394  // We're asking the model for the index as range.topLeft() and range.bottomRight()
1395  // can return indexes in invisible columns which have a null visualRect().
1396  // Column 0, instead, is always visible.
1397 
1398  QModelIndex top = d->mModel->index( range.top(), 0, range.parent() );
1399  QModelIndex bottom = d->mModel->index( range.bottom(), 0, range.parent() );
1400 
1401  if ( top.isValid() )
1402  {
1403  if ( !bottom.isValid() )
1404  bottom = top;
1405  } else {
1406  if ( !top.isValid() )
1407  top = bottom;
1408  }
1409  candidate = visualRect( bottom ).bottom();
1410  if ( candidate > bottomVisualCoordinate )
1411  {
1412  bottomVisualCoordinate = candidate;
1413  bottomIndex = range.bottomRight();
1414  }
1415 
1416  candidate = visualRect( top ).top();
1417  if ( candidate < topVisualCoordinate )
1418  {
1419  topVisualCoordinate = candidate;
1420  topIndex = range.topLeft();
1421  }
1422  }
1423 
1424 
1425  if ( topIndex.isValid() && bottomIndex.isValid() )
1426  {
1427  if ( movingUp )
1428  {
1429  if ( selectedVisualCoordinate < topVisualCoordinate )
1430  {
1431  // selecting something above the top: grow selection
1432  selectionModel()->select( newSelectedIndex, QItemSelectionModel::Rows | QItemSelectionModel::Select );
1433  } else {
1434  // selecting something below the top: shrink selection
1435  QModelIndexList selectedIndexes = selection.indexes();
1436  foreach ( const QModelIndex &idx, selectedIndexes )
1437  {
1438  if ( ( idx.column() == 0 ) && ( visualRect( idx ).top() > selectedVisualCoordinate ) )
1439  selectionModel()->select( idx, QItemSelectionModel::Rows | QItemSelectionModel::Deselect );
1440  }
1441  }
1442  } else {
1443  if ( selectedVisualCoordinate > bottomVisualCoordinate )
1444  {
1445  // selecting something below bottom: grow selection
1446  selectionModel()->select( newSelectedIndex, QItemSelectionModel::Rows | QItemSelectionModel::Select );
1447  } else {
1448  // selecting something above bottom: shrink selection
1449  QModelIndexList selectedIndexes = selection.indexes();
1450  foreach ( const QModelIndex &idx, selectedIndexes )
1451  {
1452  if ( ( idx.column() == 0 ) && ( visualRect( idx ).top() < selectedVisualCoordinate ) )
1453  selectionModel()->select( idx, QItemSelectionModel::Rows | QItemSelectionModel::Deselect );
1454  }
1455  }
1456  }
1457  } else {
1458  // no existing selection, just grow
1459  selectionModel()->select( newSelectedIndex, QItemSelectionModel::Rows | QItemSelectionModel::Select );
1460  }
1461 }
1462 
1463 bool View::selectNextMessageItem(
1464  MessageTypeFilter messageTypeFilter,
1465  ExistingSelectionBehaviour existingSelectionBehaviour,
1466  bool centerItem,
1467  bool loop
1468  )
1469 {
1470  Item * it = nextMessageItem( messageTypeFilter, loop );
1471  if ( !it )
1472  return false;
1473 
1474  setFocus();
1475 
1476  if ( it->parent() != d->mModel->rootItem() )
1477  ensureDisplayedWithParentsExpanded( it );
1478 
1479  QModelIndex idx = d->mModel->index( it, 0 );
1480 
1481  Q_ASSERT( idx.isValid() );
1482 
1483  switch ( existingSelectionBehaviour )
1484  {
1485  case ExpandExistingSelection:
1486  selectionModel()->setCurrentIndex( idx, QItemSelectionModel::NoUpdate );
1487  selectionModel()->select( idx, QItemSelectionModel::Rows | QItemSelectionModel::Select );
1488  break;
1489  case GrowOrShrinkExistingSelection:
1490  selectionModel()->setCurrentIndex( idx, QItemSelectionModel::NoUpdate );
1491  growOrShrinkExistingSelection( idx, false );
1492  break;
1493  default:
1494  //case ClearExistingSelection:
1495  setCurrentIndex( idx );
1496  break;
1497  }
1498 
1499  if ( centerItem )
1500  scrollTo( idx, QAbstractItemView::PositionAtCenter );
1501 
1502  return true;
1503 }
1504 
1505 bool View::selectPreviousMessageItem(
1506  MessageTypeFilter messageTypeFilter,
1507  ExistingSelectionBehaviour existingSelectionBehaviour,
1508  bool centerItem,
1509  bool loop
1510  )
1511 {
1512  Item * it = previousMessageItem( messageTypeFilter, loop );
1513  if ( !it )
1514  return false;
1515 
1516  setFocus();
1517 
1518  if ( it->parent() != d->mModel->rootItem() )
1519  ensureDisplayedWithParentsExpanded( it );
1520 
1521  QModelIndex idx = d->mModel->index( it, 0 );
1522 
1523  Q_ASSERT( idx.isValid() );
1524 
1525  switch ( existingSelectionBehaviour )
1526  {
1527  case ExpandExistingSelection:
1528  selectionModel()->setCurrentIndex( idx, QItemSelectionModel::NoUpdate );
1529  selectionModel()->select( idx, QItemSelectionModel::Rows | QItemSelectionModel::Select );
1530  break;
1531  case GrowOrShrinkExistingSelection:
1532  selectionModel()->setCurrentIndex( idx, QItemSelectionModel::NoUpdate );
1533  growOrShrinkExistingSelection( idx, true );
1534  break;
1535  default:
1536  //case ClearExistingSelection:
1537  setCurrentIndex( idx );
1538  break;
1539  }
1540 
1541  if ( centerItem )
1542  scrollTo( idx, QAbstractItemView::PositionAtCenter );
1543 
1544  return true;
1545 }
1546 
1547 bool View::focusNextMessageItem( MessageTypeFilter messageTypeFilter, bool centerItem, bool loop )
1548 {
1549  Item * it = nextMessageItem( messageTypeFilter, loop );
1550  if ( !it )
1551  return false;
1552 
1553  setFocus();
1554 
1555  if ( it->parent() != d->mModel->rootItem() )
1556  ensureDisplayedWithParentsExpanded( it );
1557 
1558  QModelIndex idx = d->mModel->index( it, 0 );
1559 
1560  Q_ASSERT( idx.isValid() );
1561 
1562  selectionModel()->setCurrentIndex( idx, QItemSelectionModel::NoUpdate );
1563 
1564  if ( centerItem )
1565  scrollTo( idx, QAbstractItemView::PositionAtCenter );
1566 
1567  return true;
1568 }
1569 
1570 bool View::focusPreviousMessageItem( MessageTypeFilter messageTypeFilter, bool centerItem, bool loop )
1571 {
1572  Item * it = previousMessageItem( messageTypeFilter, loop );
1573  if ( !it )
1574  return false;
1575 
1576  setFocus();
1577 
1578  if ( it->parent() != d->mModel->rootItem() )
1579  ensureDisplayedWithParentsExpanded( it );
1580 
1581  QModelIndex idx = d->mModel->index( it, 0 );
1582 
1583  Q_ASSERT( idx.isValid() );
1584 
1585  selectionModel()->setCurrentIndex( idx, QItemSelectionModel::NoUpdate );
1586 
1587  if ( centerItem )
1588  scrollTo( idx, QAbstractItemView::PositionAtCenter );
1589 
1590  return true;
1591 }
1592 
1593 void View::selectFocusedMessageItem( bool centerItem )
1594 {
1595  QModelIndex idx = currentIndex();
1596  if ( !idx.isValid() )
1597  return;
1598 
1599  setFocus();
1600 
1601  if ( selectionModel()->isSelected( idx ) )
1602  return;
1603 
1604  selectionModel()->select( idx, QItemSelectionModel::Select | QItemSelectionModel::Current | QItemSelectionModel::Rows );
1605 
1606  if ( centerItem )
1607  scrollTo( idx, QAbstractItemView::PositionAtCenter );
1608 }
1609 
1610 bool View::selectFirstMessageItem( MessageTypeFilter messageTypeFilter, bool centerItem )
1611 {
1612  if ( !storageModel() )
1613  return false; // nothing to do
1614 
1615  Item * it = firstMessageItem( messageTypeFilter );
1616  if ( !it )
1617  return false;
1618 
1619  Q_ASSERT( it != d->mModel->rootItem() ); // must never happen (obviously)
1620 
1621  setFocus();
1622  ensureDisplayedWithParentsExpanded( it );
1623 
1624  QModelIndex idx = d->mModel->index( it, 0 );
1625 
1626  Q_ASSERT( idx.isValid() );
1627 
1628  setCurrentIndex( idx );
1629 
1630  if ( centerItem )
1631  scrollTo( idx, QAbstractItemView::PositionAtCenter );
1632 
1633  return true;
1634 }
1635 
1636 bool View::selectLastMessageItem( MessageTypeFilter messageTypeFilter, bool centerItem )
1637 {
1638  if ( !storageModel() )
1639  return false;
1640 
1641  Item * it = lastMessageItem( messageTypeFilter );
1642  if ( !it )
1643  return false;
1644 
1645  Q_ASSERT( it != d->mModel->rootItem() );
1646 
1647  setFocus();
1648  ensureDisplayedWithParentsExpanded( it );
1649 
1650  QModelIndex idx = d->mModel->index( it, 0 );
1651 
1652  Q_ASSERT( idx.isValid() );
1653 
1654  setCurrentIndex( idx );
1655 
1656  if ( centerItem )
1657  scrollTo( idx, QAbstractItemView::PositionAtCenter );
1658 
1659  return true;
1660 }
1661 
1662 void View::modelFinishedLoading()
1663 {
1664  Q_ASSERT( storageModel() );
1665  Q_ASSERT( !d->mModel->isLoading() );
1666 
1667  // nothing here for now :)
1668 }
1669 
1670 MessageItemSetReference View::createPersistentSet( const QList< MessageItem * > &items )
1671 {
1672  return d->mModel->createPersistentSet( items );
1673 }
1674 
1675 QList< MessageItem * > View::persistentSetCurrentMessageItemList( MessageItemSetReference ref )
1676 {
1677  return d->mModel->persistentSetCurrentMessageItemList( ref );
1678 }
1679 
1680 void View::deletePersistentSet( MessageItemSetReference ref )
1681 {
1682  d->mModel->deletePersistentSet( ref );
1683 }
1684 
1685 void View::markMessageItemsAsAboutToBeRemoved( QList< MessageItem * > &items, bool bMark )
1686 {
1687  if ( !bMark )
1688  {
1689  QList< MessageItem * >::ConstIterator end( items.constEnd() );
1690  for ( QList< MessageItem * >::ConstIterator it = items.constBegin(); it != end; ++it )
1691  {
1692  if ( ( *it )->isValid() ) // hasn't been removed in the meantime
1693  ( *it )->setAboutToBeRemoved( false );
1694  }
1695 
1696  viewport()->update();
1697 
1698  return;
1699  }
1700 
1701  // ok.. we're going to mark the messages as "about to be deleted".
1702  // This means that we're going to make them non selectable.
1703 
1704  // What happens to the selection is generally an untrackable big mess.
1705  // Several components and entities are involved.
1706 
1707  // Qutie tries to apply some kind of internal logic in order to keep
1708  // "something" selected and "something" (else) to be current.
1709  // The results sometimes appear to depend on the current moon phase.
1710 
1711  // The Model will do crazy things in order to preserve the current
1712  // selection (and possibly the current item). If it's impossible then
1713  // it will make its own guesses about what should be selected next.
1714  // A problem is that the Model will do it one message at a time.
1715  // When item reparenting/reordering is involved then the guesses
1716  // can produce non-intuitive results.
1717 
1718  // Add the fact that selection and current item are distinct concepts,
1719  // their relative interaction depends on the settings and is often quite
1720  // unclear.
1721 
1722  // Add the fact that (at the time of writing) several styles don't show
1723  // the current item (only Yoda knows why) and this causes some confusion to the user.
1724 
1725  // Add the fact that the operations are asynchronous: deletion will start
1726  // a job, do some event loop processing and then complete the work at a later time.
1727  // The Qutie views also tend to accumulate the changes and perform them
1728  // all at once at the latest possible stage.
1729 
1730  // A radical approach is needed: we FIRST deal with the selection
1731  // by tring to move it away from the messages about to be deleted
1732  // and THEN mark the (hopefully no longer selected) messages as "about to be deleted".
1733 
1734  // First of all, find out if we're going to clear the entire selection (very likely).
1735 
1736  bool clearingEntireSelection = true;
1737 
1738  QModelIndexList selectedIndexes = selectionModel()->selectedRows( 0 );
1739 
1740  if ( selectedIndexes.count() > items.count() )
1741  {
1742  // the selection is bigger: we can't clear it completely
1743  clearingEntireSelection = false;
1744  } else {
1745  // the selection has same size or is smaller: we can clear it completely with our removal
1746  foreach ( const QModelIndex &selectedIndex , selectedIndexes )
1747  {
1748  Q_ASSERT( selectedIndex.isValid() );
1749  Q_ASSERT( selectedIndex.column() == 0 );
1750 
1751  Item * selectedItem = static_cast< Item * >( selectedIndex.internalPointer() );
1752  Q_ASSERT( selectedItem );
1753 
1754  if ( selectedItem->type() != Item::Message )
1755  continue;
1756 
1757  if ( !items.contains( static_cast< MessageItem * >( selectedItem ) ) )
1758  {
1759  // the selection contains something that we aren't going to remove:
1760  // we will not clear the selection completely
1761  clearingEntireSelection = false;
1762  break;
1763  }
1764  }
1765  }
1766 
1767  if ( clearingEntireSelection )
1768  {
1769  // Try to clear the current selection and select something sensible instead,
1770  // so after the deletion we will not end up with a random selection.
1771  // Pick up a message in the set (which is very likely to be contiguous), walk the tree
1772  // and select the next message that is NOT in the set.
1773 
1774  MessageItem * aMessage = items.last();
1775  Q_ASSERT( aMessage );
1776 
1777  // Avoid infinite loops by carrying only a limited number of attempts.
1778  // If there is any message that is not in the set then items.count() attemps should find it.
1779  int maxAttempts = items.count();
1780 
1781  while ( items.contains( aMessage ) && ( maxAttempts > 0 ) )
1782  {
1783  Item * next = messageItemAfter( aMessage, MessageTypeAny, false );
1784  if ( !next )
1785  {
1786  // no way
1787  aMessage = 0;
1788  break;
1789  }
1790  Q_ASSERT( next->type() == Item::Message );
1791  aMessage = static_cast< MessageItem * >( next );
1792  maxAttempts--;
1793  }
1794 
1795  if ( !aMessage )
1796  {
1797  // try backwards
1798  aMessage = items.first();
1799  Q_ASSERT( aMessage );
1800  maxAttempts = items.count();
1801 
1802  while ( items.contains( aMessage ) && ( maxAttempts > 0 ) )
1803  {
1804  Item * prev = messageItemBefore( aMessage, MessageTypeAny, false );
1805  if ( !prev )
1806  {
1807  // no way
1808  aMessage = 0;
1809  break;
1810  }
1811  Q_ASSERT( prev->type() == Item::Message );
1812  aMessage = static_cast< MessageItem * >( prev );
1813  maxAttempts--;
1814  }
1815  }
1816 
1817  if ( aMessage )
1818  {
1819  QModelIndex aMessageIndex = d->mModel->index( aMessage, 0 );
1820  Q_ASSERT( aMessageIndex.isValid() );
1821  Q_ASSERT( static_cast< MessageItem * >( aMessageIndex.internalPointer() ) == aMessage );
1822  Q_ASSERT( !selectionModel()->isSelected( aMessageIndex ) );
1823  setCurrentIndex( aMessageIndex );
1824  selectionModel()->select( aMessageIndex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
1825  }
1826  } // else we aren't clearing the entire selection so something should just stay selected.
1827 
1828  // Now mark messages as about to be removed.
1829 
1830  QList< MessageItem * >::ConstIterator end( items.constEnd() );
1831  for ( QList< MessageItem * >::ConstIterator it = items.constBegin(); it != end; ++it )
1832  {
1833  ( *it )->setAboutToBeRemoved( true );
1834  QModelIndex idx = d->mModel->index( *it, 0 );
1835  Q_ASSERT( idx.isValid() );
1836  Q_ASSERT( static_cast< MessageItem * >( idx.internalPointer() ) == *it );
1837  if ( selectionModel()->isSelected( idx ) )
1838  selectionModel()->select( idx, QItemSelectionModel::Deselect | QItemSelectionModel::Rows );
1839  }
1840 
1841  viewport()->update();
1842 }
1843 
1844 void View::ensureDisplayedWithParentsExpanded( Item * it )
1845 {
1846  Q_ASSERT( it );
1847  Q_ASSERT( it->parent() );
1848  Q_ASSERT( it->isViewable() ); // must be attached to the viewable root
1849 
1850  if ( isRowHidden( it->parent()->indexOfChildItem( it ), d->mModel->index( it->parent(), 0 ) ) )
1851  setRowHidden( it->parent()->indexOfChildItem( it ), d->mModel->index( it->parent(), 0 ), false );
1852 
1853  it = it->parent();
1854 
1855  while ( it->parent() )
1856  {
1857  if ( isRowHidden( it->parent()->indexOfChildItem( it ), d->mModel->index( it->parent(), 0 ) ) )
1858  setRowHidden( it->parent()->indexOfChildItem( it ), d->mModel->index( it->parent(), 0 ), false );
1859 
1860  QModelIndex idx = d->mModel->index( it, 0 );
1861 
1862  Q_ASSERT( idx.isValid() );
1863  Q_ASSERT( static_cast< Item * >( idx.internalPointer() ) == it );
1864 
1865  if ( !isExpanded( idx ) )
1866  setExpanded( idx, true );
1867 
1868  it = it->parent();
1869  }
1870 }
1871 
1872 bool View::isDisplayedWithParentsExpanded( Item * it ) const
1873 {
1874  // An item is currently viewable iff
1875  // - it is marked as viewable in the item structure (that is, qt knows about its existence)
1876  // (and this means that all of its parents are marked as viewable)
1877  // - it is not explicitly hidden
1878  // - all of its parents are expanded
1879 
1880  if ( !it )
1881  return false; // be nice and allow the caller not to care
1882 
1883  if ( !it->isViewable() )
1884  return false; // item not viewable (not attached to the viewable root or qt not yet aware of it)
1885 
1886  // the item and all the parents are marked as viewable.
1887 
1888  if ( isRowHidden( it->parent()->indexOfChildItem( it ), d->mModel->index( it->parent(), 0 ) ) )
1889  return false; // item qt representation explicitly hidden
1890 
1891  // the item (and theoretically all the parents) are not explicitly hidden
1892 
1893  // check the parent chain
1894 
1895  it = it->parent();
1896 
1897  while ( it )
1898  {
1899  if ( it == d->mModel->rootItem() )
1900  return true; // parent is root item: ok
1901 
1902  // parent is not root item
1903 
1904  if ( !isExpanded( d->mModel->index( it, 0 ) ) )
1905  return false; // parent is not expanded (so child not actually visible)
1906 
1907  it = it->parent(); // climb up
1908  }
1909 
1910  // parent hierarchy interrupted somewhere
1911  return false;
1912 }
1913 
1914 bool View::isThreaded() const
1915 {
1916  if ( !d->mAggregation )
1917  return false;
1918  return d->mAggregation->threading() != Aggregation::NoThreading;
1919 }
1920 
1921 void View::slotSelectionChanged( const QItemSelection &, const QItemSelection & )
1922 {
1923  // We assume that when selection changes, current item also changes.
1924  QModelIndex current = currentIndex();
1925 
1926  if ( !current.isValid() )
1927  {
1928  if ( d->mLastCurrentItem )
1929  {
1930  d->mWidget->viewMessageSelected( 0 );
1931  d->mLastCurrentItem = 0;
1932  }
1933  d->mWidget->viewMessageSelected( 0 );
1934  d->mWidget->viewSelectionChanged();
1935  return;
1936  }
1937 
1938  if ( !selectionModel()->isSelected( current ) )
1939  {
1940  if ( selectedIndexes().count() < 1 )
1941  {
1942  // It may happen after row removals: Model calls this slot on currentIndex()
1943  // that actually might have changed "silently", without being selected.
1944  QItemSelection selection;
1945  selection.append( QItemSelectionRange( current ) );
1946  selectionModel()->select( selection, QItemSelectionModel::Select | QItemSelectionModel::Rows );
1947  return; // the above recurses
1948  } else {
1949  // something is still selected anyway
1950  // This is probably a result of CTRL+Click which unselected current: leave it as it is.
1951  return;
1952  }
1953  }
1954 
1955  Item * it = static_cast< Item * >( current.internalPointer() );
1956  Q_ASSERT( it );
1957 
1958  switch ( it->type() )
1959  {
1960  case Item::Message:
1961  {
1962  if ( d->mLastCurrentItem != it )
1963  {
1964  kDebug() << "View message selected [" << static_cast< MessageItem * >( it )->subject() << "]";
1965  d->mWidget->viewMessageSelected( static_cast< MessageItem * >( it ) );
1966  d->mLastCurrentItem = it;
1967  }
1968  }
1969  break;
1970  case Item::GroupHeader:
1971  if ( d->mLastCurrentItem )
1972  {
1973  d->mWidget->viewMessageSelected( 0 );
1974  d->mLastCurrentItem = 0;
1975  }
1976  break;
1977  default:
1978  // should never happen
1979  Q_ASSERT( false );
1980  break;
1981  }
1982 
1983  d->mWidget->viewSelectionChanged();
1984 }
1985 
1986 void View::mouseDoubleClickEvent( QMouseEvent * e )
1987 {
1988  // Perform a hit test
1989  if ( !d->mDelegate->hitTest( e->pos(), true ) )
1990  return;
1991 
1992  // Something was hit :)
1993 
1994  Item * it = static_cast< Item * >( d->mDelegate->hitItem() );
1995  if ( !it )
1996  return; // should never happen
1997 
1998  switch ( it->type() )
1999  {
2000  case Item::Message:
2001  {
2002  // Let QTreeView handle the expansion
2003  QTreeView::mousePressEvent( e );
2004 
2005  switch ( e->button() )
2006  {
2007  case Qt::LeftButton:
2008 
2009  if ( d->mDelegate->hitContentItem() )
2010  {
2011  // Double clikcking on clickable icons does NOT activate the message
2012  if ( d->mDelegate->hitContentItem()->isIcon() && d->mDelegate->hitContentItem()->isClickable() )
2013  return;
2014  }
2015 
2016  d->mWidget->viewMessageActivated( static_cast< MessageItem * >( it ) );
2017  break;
2018  default:
2019  // make gcc happy
2020  break;
2021  }
2022  }
2023  break;
2024  case Item::GroupHeader:
2025  {
2026  // Don't let QTreeView handle the selection (as it deselects the curent messages)
2027  switch ( e->button() )
2028  {
2029  case Qt::LeftButton:
2030  if ( it->childItemCount() > 0 )
2031  {
2032  // toggle expanded state
2033  setExpanded( d->mDelegate->hitIndex(), !isExpanded( d->mDelegate->hitIndex() ) );
2034  }
2035  break;
2036  default:
2037  // make gcc happy
2038  break;
2039  }
2040  }
2041  break;
2042  default:
2043  // should never happen
2044  Q_ASSERT( false );
2045  break;
2046  }
2047 }
2048 
2049 void View::changeMessageStatusRead( MessageItem *it, bool read )
2050 {
2051  Akonadi::MessageStatus set = it->status();
2052  Akonadi::MessageStatus unset = it->status();
2053  if ( read ) {
2054  set.setRead( true );
2055  unset.setRead( false );
2056  } else {
2057  set.setRead( false );
2058  unset.setRead( true );
2059  }
2060  viewport()->update();
2061 
2062  // This will actually request the widget to perform a status change on the storage.
2063  // The request will be then processed by the Model and the message will be updated again.
2064 
2065  d->mWidget->viewMessageStatusChangeRequest( it, set, unset );
2066 }
2067 
2068 void View::changeMessageStatus( MessageItem * it, const Akonadi::MessageStatus &set, const Akonadi::MessageStatus &unset )
2069 {
2070  // We first change the status of MessageItem itself. This will make the change
2071  // visible to the user even if the Model is actually in the middle of a long job (maybe it's loading)
2072  // and can't process the status change request immediately.
2073  // Here we actually desynchronize the cache and trust that the later call to
2074  // d->mWidget->viewMessageStatusChangeRequest() will really perform the status change on the storage.
2075  // Well... in KMail it will unless something is really screwed. Anyway, if it will not, at the next
2076  // load the status will be just unchanged: no animals will be harmed.
2077 
2078  qint32 stat = it->status().toQInt32();
2079  stat |= set.toQInt32();
2080  stat &= ~( unset.toQInt32() );
2081  Akonadi::MessageStatus status;
2082  status.fromQInt32( stat );
2083  it->setStatus( status );
2084 
2085  // Trigger an update so the immediate change will be shown to the user
2086 
2087  viewport()->update();
2088 
2089  // This will actually request the widget to perform a status change on the storage.
2090  // The request will be then processed by the Model and the message will be updated again.
2091 
2092  d->mWidget->viewMessageStatusChangeRequest( it, set, unset );
2093 }
2094 
2095 void View::mousePressEvent( QMouseEvent * e )
2096 {
2097  d->mMousePressPosition = QPoint();
2098 
2099  // Perform a hit test
2100  if ( !d->mDelegate->hitTest( e->pos(), true ) )
2101  return;
2102 
2103  // Something was hit :)
2104 
2105  Item * it = static_cast< Item * >( d->mDelegate->hitItem() );
2106  if ( !it )
2107  return; // should never happen
2108 
2109  // Abort any pending message pre-selection as the user is probably
2110  // already navigating the view (so pre-selection would make his view jump
2111  // to an unexpected place).
2112  d->mModel->setPreSelectionMode( PreSelectNone );
2113 
2114  switch ( it->type() )
2115  {
2116  case Item::Message:
2117  {
2118  d->mMousePressPosition = e->pos();
2119 
2120  switch ( e->button() )
2121  {
2122  case Qt::LeftButton:
2123  // if we have multi selection then the meaning of hitting
2124  // the content item is quite unclear.
2125  if ( d->mDelegate->hitContentItem() && ( selectedIndexes().count() > 1 ) )
2126  {
2127  kDebug() << "Left hit with selectedIndexes().count() == " << selectedIndexes().count();
2128 
2129  switch ( d->mDelegate->hitContentItem()->type() )
2130  {
2131  case Theme::ContentItem::AnnotationIcon:
2132  static_cast< MessageItem * >( it )->editAnnotation();
2133  return; // don't select the item
2134  break;
2135  case Theme::ContentItem::ActionItemStateIcon:
2136  changeMessageStatus(
2137  static_cast< MessageItem * >( it ),
2138  it->status().isToAct() ? Akonadi::MessageStatus() : Akonadi::MessageStatus::statusToAct(),
2139  it->status().isToAct() ? Akonadi::MessageStatus::statusToAct() : Akonadi::MessageStatus()
2140  );
2141  return; // don't select the item
2142  break;
2143  case Theme::ContentItem::ImportantStateIcon:
2144  changeMessageStatus(
2145  static_cast< MessageItem * >( it ),
2146  it->status().isImportant() ? Akonadi::MessageStatus() : Akonadi::MessageStatus::statusImportant(),
2147  it->status().isImportant() ? Akonadi::MessageStatus::statusImportant() : Akonadi::MessageStatus()
2148  );
2149  return; // don't select the item
2150  case Theme::ContentItem::ReadStateIcon:
2151  changeMessageStatusRead( static_cast< MessageItem * >( it ), it->status().isRead() ? false : true );
2152  return;
2153  break;
2154  case Theme::ContentItem::SpamHamStateIcon:
2155  changeMessageStatus(
2156  static_cast< MessageItem * >( it ),
2157  it->status().isSpam() ? Akonadi::MessageStatus() : ( it->status().isHam() ? Akonadi::MessageStatus::statusSpam() : Akonadi::MessageStatus::statusHam() ),
2158  it->status().isSpam() ? Akonadi::MessageStatus::statusSpam() : ( it->status().isHam() ? Akonadi::MessageStatus::statusHam() : Akonadi::MessageStatus() )
2159  );
2160  return; // don't select the item
2161  break;
2162  case Theme::ContentItem::WatchedIgnoredStateIcon:
2163  changeMessageStatus(
2164  static_cast< MessageItem * >( it ),
2165  it->status().isIgnored() ? Akonadi::MessageStatus() : ( it->status().isWatched() ? Akonadi::MessageStatus::statusIgnored() : Akonadi::MessageStatus::statusWatched() ),
2166  it->status().isIgnored() ? Akonadi::MessageStatus::statusIgnored() : ( it->status().isWatched() ? Akonadi::MessageStatus::statusWatched() : Akonadi::MessageStatus() )
2167  );
2168  return; // don't select the item
2169  break;
2170  default:
2171  // make gcc happy
2172  break;
2173  }
2174  }
2175 
2176  // Let QTreeView handle the selection and emit the appropriate signals (slotSelectionChanged() may be called)
2177  QTreeView::mousePressEvent( e );
2178 
2179  break;
2180  case Qt::RightButton:
2181  // Let QTreeView handle the selection and emit the appropriate signals (slotSelectionChanged() may be called)
2182  QTreeView::mousePressEvent( e );
2183 
2184  d->mWidget->viewMessageListContextPopupRequest( selectionAsMessageItemList(), viewport()->mapToGlobal( e->pos() ) );
2185  break;
2186  default:
2187  // make gcc happy
2188  break;
2189  }
2190  }
2191  break;
2192  case Item::GroupHeader:
2193  {
2194  // Don't let QTreeView handle the selection (as it deselects the curent messages)
2195  GroupHeaderItem *groupHeaderItem = static_cast< GroupHeaderItem * >( it );
2196 
2197  switch ( e->button() )
2198  {
2199  case Qt::LeftButton:
2200  {
2201  QModelIndex index = d->mModel->index( groupHeaderItem, 0 );
2202 
2203  if ( index.isValid() )
2204  setCurrentIndex( index );
2205 
2206  if ( !d->mDelegate->hitContentItem() )
2207  return;
2208 
2209  if ( d->mDelegate->hitContentItem()->type() == Theme::ContentItem::ExpandedStateIcon )
2210  {
2211  if ( groupHeaderItem->childItemCount() > 0 )
2212  {
2213  // toggle expanded state
2214  setExpanded( d->mDelegate->hitIndex(), !isExpanded( d->mDelegate->hitIndex() ) );
2215  }
2216  }
2217  }
2218  break;
2219  case Qt::RightButton:
2220  d->mWidget->viewGroupHeaderContextPopupRequest( groupHeaderItem, viewport()->mapToGlobal( e->pos() ) );
2221  break;
2222  default:
2223  // make gcc happy
2224  break;
2225  }
2226  }
2227  break;
2228  default:
2229  // should never happen
2230  Q_ASSERT( false );
2231  break;
2232  }
2233 }
2234 
2235 void View::mouseMoveEvent( QMouseEvent * e )
2236 {
2237  if ( !e->buttons() & Qt::LeftButton )
2238  {
2239  QTreeView::mouseMoveEvent( e );
2240  return;
2241  }
2242 
2243  if ( d->mMousePressPosition.isNull() )
2244  return;
2245 
2246  if ( ( e->pos() - d->mMousePressPosition ).manhattanLength() <= KGlobalSettings::dndEventDelay() )
2247  return;
2248 
2249  d->mWidget->viewStartDragRequest();
2250 }
2251 
2252 void View::contextMenuEvent( QContextMenuEvent * e )
2253 {
2254  Q_UNUSED( e );
2255  QModelIndex index = currentIndex();
2256  if ( index.isValid() ) {
2257  QRect indexRect = this->visualRect( index );
2258  QPoint pos;
2259 
2260  if ( ( indexRect.isValid() ) && ( indexRect.bottom() > 0 ) ) {
2261  if ( indexRect.bottom() > viewport()->height() ) {
2262  if ( indexRect.top() <= viewport()->height() ) {
2263  pos = indexRect.topLeft();
2264  }
2265  } else {
2266  pos = indexRect.bottomLeft();
2267  }
2268  }
2269 
2270  Item *item = static_cast< Item * >( index.internalPointer() );
2271  if ( item ) {
2272  if ( item->type() == Item::GroupHeader )
2273  d->mWidget->viewGroupHeaderContextPopupRequest( static_cast< GroupHeaderItem * >( item ), viewport()->mapToGlobal( pos ) );
2274  else if ( !selectionEmpty() )
2275  d->mWidget->viewMessageListContextPopupRequest( selectionAsMessageItemList(), viewport()->mapToGlobal( pos ) );
2276  }
2277  }
2278 }
2279 
2280 void View::dragEnterEvent( QDragEnterEvent * e )
2281 {
2282  d->mWidget->viewDragEnterEvent( e );
2283 }
2284 
2285 void View::dragMoveEvent( QDragMoveEvent * e )
2286 {
2287  d->mWidget->viewDragMoveEvent( e );
2288 }
2289 
2290 void View::dropEvent( QDropEvent * e )
2291 {
2292  d->mWidget->viewDropEvent( e );
2293 }
2294 
2295 void View::changeEvent( QEvent *e )
2296 {
2297  switch ( e->type() )
2298  {
2299  case QEvent::PaletteChange:
2300  case QEvent::FontChange:
2301  case QEvent::StyleChange:
2302  case QEvent::LayoutDirectionChange:
2303  case QEvent::LocaleChange:
2304  case QEvent::LanguageChange:
2305  // All of these affect the theme's internal cache.
2306  setTheme( d->mTheme );
2307  // A layoutChanged() event will screw up the view state a bit.
2308  // Since this is a rare event we just reload the view.
2309  reload();
2310  break;
2311  default:
2312  // make gcc happy by default
2313  break;
2314  }
2315 
2316  QTreeView::changeEvent( e );
2317 }
2318 
2319 bool View::event( QEvent *e )
2320 {
2321  // We catch ToolTip events and pass everything else
2322 
2323  if( e->type() != QEvent::ToolTip )
2324  return QTreeView::event( e );
2325 
2326  if ( !Settings::self()->messageToolTipEnabled() )
2327  return true; // don't display tooltips
2328 
2329  QHelpEvent * he = dynamic_cast< QHelpEvent * >( e );
2330  if ( !he )
2331  return true; // eh ?
2332 
2333  QPoint pnt = viewport()->mapFromGlobal( mapToGlobal( he->pos() ) );
2334 
2335  if ( pnt.y() < 0 )
2336  return true; // don't display the tooltip for items hidden under the header
2337 
2338  QModelIndex idx = indexAt( pnt );
2339  if ( !idx.isValid() )
2340  return true; // may be
2341 
2342  Item * it = static_cast< Item * >( idx.internalPointer() );
2343  if ( !it )
2344  return true; // hum
2345 
2346  Q_ASSERT( storageModel() );
2347 
2348  QColor bckColor = palette().color( QPalette::ToolTipBase );
2349  QColor txtColor = palette().color( QPalette::ToolTipText );
2350  QColor darkerColor(
2351  ( ( bckColor.red() * 8 ) + ( txtColor.red() * 2 ) ) / 10,
2352  ( ( bckColor.green() * 8 ) + ( txtColor.green() * 2 ) ) / 10,
2353  ( ( bckColor.blue() * 8 ) + ( txtColor.blue() * 2 ) ) / 10
2354  );
2355 
2356  QString bckColorName = bckColor.name();
2357  QString txtColorName = txtColor.name();
2358  QString darkerColorName = darkerColor.name();
2359  const bool textIsLeftToRight = ( QApplication::layoutDirection() == Qt::LeftToRight );
2360  const QString textDirection = textIsLeftToRight ? QLatin1String( "left" ) : QLatin1String( "right" );
2361 
2362  QString tip = QString::fromLatin1(
2363  "<table width=\"100%\" border=\"0\" cellpadding=\"2\" cellspacing=\"0\">"
2364  );
2365 
2366  switch ( it->type() )
2367  {
2368  case Item::Message:
2369  {
2370  MessageItem *mi = static_cast< MessageItem * >( it );
2371 
2372  tip += QString::fromLatin1(
2373  "<tr>" \
2374  "<td bgcolor=\"%1\" align=\"%4\" valign=\"middle\">" \
2375  "<div style=\"color: %2; font-weight: bold;\">" \
2376  "%3" \
2377  "</div>" \
2378  "</td>" \
2379  "</tr>"
2380  ).arg( txtColorName ).arg( bckColorName ).arg( Qt::escape( mi->subject() ) ).arg( textDirection );
2381 
2382  tip += QString::fromLatin1(
2383  "<tr>" \
2384  "<td align=\"center\" valign=\"middle\">" \
2385  "<table width=\"100%\" border=\"0\" cellpadding=\"2\" cellspacing=\"0\">"
2386  );
2387 
2388  const QString htmlCodeForStandardRow = QString::fromLatin1(
2389  "<tr>" \
2390  "<td align=\"right\" valign=\"top\" width=\"45\">" \
2391  "<div style=\"font-weight: bold;\"><nobr>" \
2392  "%1:" \
2393  "</nobr></div>" \
2394  "</td>" \
2395  "<td align=\"left\" valign=\"top\">" \
2396  "%2" \
2397  "</td>" \
2398  "</tr>" );
2399 
2400 
2401  if ( textIsLeftToRight ) {
2402  tip += htmlCodeForStandardRow.arg( i18n( "From" ) ).arg( MessageCore::StringUtil::stripEmailAddr( mi->sender() ) );
2403  tip += htmlCodeForStandardRow.arg( i18nc( "Receiver of the email", "To" ) ).arg( MessageCore::StringUtil::stripEmailAddr( mi->receiver() ) );
2404  tip += htmlCodeForStandardRow.arg( i18n( "Date" ) ).arg( mi->formattedDate() );
2405  } else {
2406  tip += htmlCodeForStandardRow.arg( MessageCore::StringUtil::stripEmailAddr( mi->sender() ) ).arg( i18n( "From" ) );
2407  tip += htmlCodeForStandardRow.arg( MessageCore::StringUtil::stripEmailAddr( mi->receiver() ) ).arg( i18nc( "Receiver of the email", "To" ) );
2408  tip += htmlCodeForStandardRow.arg( mi->formattedDate() ).arg( i18n( "Date" ) );
2409  }
2410 
2411  QString status = mi->statusDescription(Nepomuk2::ResourceManager::instance()->initialized());
2412  const QString tags = mi->tagListDescription();
2413  if ( !tags.isEmpty () )
2414  {
2415  if ( !status.isEmpty() )
2416  status += QLatin1String( ", " );
2417  status += tags;
2418  }
2419 
2420  if ( textIsLeftToRight ) {
2421  tip += htmlCodeForStandardRow.arg( i18n( "Status" ) ).arg( status );
2422  tip += htmlCodeForStandardRow.arg( i18n( "Size" ) ).arg( mi->formattedSize() );
2423  } else {
2424  tip += htmlCodeForStandardRow.arg( status ).arg( i18n( "Status" ) );
2425  tip += htmlCodeForStandardRow.arg( mi->formattedSize() ).arg( i18n( "Size" ) );
2426  }
2427 
2428  if ( mi->hasAnnotation() ) {
2429  if ( textIsLeftToRight ) {
2430  tip += htmlCodeForStandardRow.arg( i18n( "Note" ) ).arg( mi->annotation().replace( QLatin1Char( '\n' ), QLatin1String( "<br>" ) ) );
2431  } else {
2432  tip += htmlCodeForStandardRow.arg( mi->annotation().replace( QLatin1Char( '\n' ), QLatin1String( "<br>" ) ) ).arg( i18n( "Note" ) );
2433  }
2434  }
2435 
2436  QString content = MessageList::Util::contentSummary(mi->akonadiItem().url());
2437  if ( !content.isEmpty() ) {
2438  if ( textIsLeftToRight ) {
2439  tip += htmlCodeForStandardRow.arg( i18n( "Preview" ) ).arg( content.replace( QLatin1Char( '\n' ), QLatin1String( "<br>" ) ) );
2440  } else {
2441  tip += htmlCodeForStandardRow.arg( content.replace( QLatin1Char( '\n' ), QLatin1String( "<br>" ) ) ).arg( i18n( "Preview" ) );
2442  }
2443  }
2444 
2445  tip += QString::fromLatin1(
2446  "</table" \
2447  "</td>" \
2448  "</tr>"
2449  );
2450 
2451  // FIXME: Find a way to show also CC and other header fields ?
2452 
2453  if ( mi->hasChildren() )
2454  {
2455  Item::ChildItemStats stats;
2456  mi->childItemStats( stats );
2457 
2458  QString statsText;
2459 
2460  statsText = i18np( "<b>%1</b> reply", "<b>%1</b> replies", mi->childItemCount() );
2461  statsText += QLatin1String( ", " );
2462 
2463  statsText += i18np(
2464  "<b>%1</b> message in subtree (<b>%2</b> unread)",
2465  "<b>%1</b> messages in subtree (<b>%2</b> unread)",
2466  stats.mTotalChildCount,
2467  stats.mUnreadChildCount
2468  );
2469 
2470 
2471  tip += QString::fromLatin1(
2472  "<tr>" \
2473  "<td bgcolor=\"%1\" align=\"%3\" valign=\"middle\">" \
2474  "<nobr>%2</nobr>" \
2475  "</td>" \
2476  "</tr>"
2477  ).arg( darkerColorName ).arg( statsText ).arg( textDirection );
2478  }
2479 
2480  }
2481  break;
2482  case Item::GroupHeader:
2483  {
2484  GroupHeaderItem *ghi = static_cast< GroupHeaderItem * >( it );
2485 
2486  tip += QString::fromLatin1(
2487  "<tr>" \
2488  "<td bgcolor=\"%1\" align=\"%4\" valign=\"middle\">" \
2489  "<div style=\"color: %2; font-weight: bold;\">" \
2490  "%3" \
2491  "</div>" \
2492  "</td>" \
2493  "</tr>"
2494  ).arg( txtColorName ).arg( bckColorName ).arg( ghi->label() ).arg( textDirection );
2495 
2496  QString description;
2497 
2498  switch( d->mAggregation->grouping() )
2499  {
2500  case Aggregation::GroupByDate:
2501  if ( d->mAggregation->threading() != Aggregation::NoThreading )
2502  {
2503  switch ( d->mAggregation->threadLeader() )
2504  {
2505  case Aggregation::TopmostMessage:
2506  if ( ghi->label().contains( QRegExp( QLatin1String( "[0-9]" ) ) ) )
2507  description = i18nc(
2508  "@info:tooltip Formats to something like 'Threads started on 2008-12-21'",
2509  "Threads started on %1",
2510  ghi->label()
2511  );
2512  else
2513  description = i18nc(
2514  "@info:tooltip Formats to something like 'Threads started Yesterday'",
2515  "Threads started %1",
2516  ghi->label()
2517  );
2518  break;
2519  case Aggregation::MostRecentMessage:
2520  description = i18n( "Threads with messages dated %1", ghi->label() );
2521  break;
2522  default:
2523  // nuthin, make gcc happy
2524  break;
2525  }
2526  } else {
2527  if ( ghi->label().contains( QRegExp( QLatin1String( "[0-9]" ) ) ) )
2528  {
2529  if ( storageModel()->containsOutboundMessages() )
2530  description = i18nc(
2531  "@info:tooltip Formats to something like 'Messages sent on 2008-12-21'",
2532  "Messages sent on %1",
2533  ghi->label()
2534  );
2535  else
2536  description = i18nc(
2537  "@info:tooltip Formats to something like 'Messages received on 2008-12-21'",
2538  "Messages received on %1",
2539  ghi->label()
2540  );
2541  } else {
2542  if ( storageModel()->containsOutboundMessages() )
2543  description = i18nc(
2544  "@info:tooltip Formats to something like 'Messages sent Yesterday'",
2545  "Messages sent %1",
2546  ghi->label()
2547  );
2548  else
2549  description = i18nc(
2550  "@info:tooltip Formats to something like 'Messages received Yesterday'",
2551  "Messages received %1",
2552  ghi->label()
2553  );
2554  }
2555  }
2556  break;
2557  case Aggregation::GroupByDateRange:
2558  if ( d->mAggregation->threading() != Aggregation::NoThreading )
2559  {
2560  switch ( d->mAggregation->threadLeader() )
2561  {
2562  case Aggregation::TopmostMessage:
2563  description = i18n( "Threads started within %1", ghi->label() );
2564  break;
2565  case Aggregation::MostRecentMessage:
2566  description = i18n( "Threads containing messages with dates within %1", ghi->label() );
2567  break;
2568  default:
2569  // nuthin, make gcc happy
2570  break;
2571  }
2572  } else {
2573  if ( storageModel()->containsOutboundMessages() )
2574  description = i18n( "Messages sent within %1", ghi->label() );
2575  else
2576  description = i18n( "Messages received within %1", ghi->label() );
2577  }
2578  break;
2579  case Aggregation::GroupBySenderOrReceiver:
2580  case Aggregation::GroupBySender:
2581  if ( d->mAggregation->threading() != Aggregation::NoThreading )
2582  {
2583  switch ( d->mAggregation->threadLeader() )
2584  {
2585  case Aggregation::TopmostMessage:
2586  description = i18n( "Threads started by %1", ghi->label() );
2587  break;
2588  case Aggregation::MostRecentMessage:
2589  description = i18n( "Threads with most recent message by %1", ghi->label() );
2590  break;
2591  default:
2592  // nuthin, make gcc happy
2593  break;
2594  }
2595  } else {
2596  if ( storageModel()->containsOutboundMessages() )
2597  {
2598  if ( d->mAggregation->grouping() == Aggregation::GroupBySenderOrReceiver )
2599  description = i18n( "Messages sent to %1", ghi->label() );
2600  else
2601  description = i18n( "Messages sent by %1", ghi->label() );
2602  } else {
2603  description = i18n( "Messages received from %1", ghi->label() );
2604  }
2605  }
2606  break;
2607  case Aggregation::GroupByReceiver:
2608  if ( d->mAggregation->threading() != Aggregation::NoThreading )
2609  {
2610  switch ( d->mAggregation->threadLeader() )
2611  {
2612  case Aggregation::TopmostMessage:
2613  description = i18n( "Threads directed to %1", ghi->label() );
2614  break;
2615  case Aggregation::MostRecentMessage:
2616  description = i18n( "Threads with most recent message directed to %1", ghi->label() );
2617  break;
2618  default:
2619  // nuthin, make gcc happy
2620  break;
2621  }
2622  } else {
2623  if ( storageModel()->containsOutboundMessages() )
2624  {
2625  description = i18n( "Messages sent to %1", ghi->label() );
2626  } else {
2627  description = i18n( "Messages received by %1", ghi->label() );
2628  }
2629  }
2630  break;
2631  default:
2632  // nuthin, make gcc happy
2633  break;
2634  }
2635 
2636  if ( !description.isEmpty() )
2637  {
2638  tip += QString::fromLatin1(
2639  "<tr>" \
2640  "<td align=\"%2\" valign=\"middle\">" \
2641  "%1" \
2642  "</td>" \
2643  "</tr>"
2644  ).arg( description ).arg( textDirection );
2645  }
2646 
2647  if ( ghi->hasChildren() )
2648  {
2649  Item::ChildItemStats stats;
2650  ghi->childItemStats( stats );
2651 
2652  QString statsText;
2653 
2654  if ( d->mAggregation->threading() != Aggregation::NoThreading )
2655  {
2656  statsText = i18np( "<b>%1</b> thread", "<b>%1</b> threads", ghi->childItemCount() );
2657  statsText += QLatin1String( ", " );
2658  }
2659 
2660  statsText += i18np(
2661  "<b>%1</b> message (<b>%2</b> unread)",
2662  "<b>%1</b> messages (<b>%2</b> unread)",
2663  stats.mTotalChildCount,
2664  stats.mUnreadChildCount
2665  );
2666 
2667  tip += QString::fromLatin1(
2668  "<tr>" \
2669  "<td bgcolor=\"%1\" align=\"%3\" valign=\"middle\">" \
2670  "<nobr>%2</nobr>" \
2671  "</td>" \
2672  "</tr>"
2673  ).arg( darkerColorName ).arg( statsText ).arg( textDirection );
2674  }
2675 
2676  }
2677  break;
2678  default:
2679  // nuthin (just make gcc happy for now)
2680  break;
2681  }
2682 
2683 
2684  tip += QString::fromLatin1(
2685  "</table>"
2686  );
2687 
2688  QToolTip::showText( he->globalPos(), tip, viewport(), visualRect( idx ) );
2689 
2690  return true;
2691 }
2692 
2693 void View::slotCollapseAllGroups()
2694 {
2695  setAllGroupsExpanded( false );
2696 }
2697 
2698 void View::slotExpandAllGroups()
2699 {
2700  setAllGroupsExpanded( true );
2701 }
2702 
2703 void View::slotCollapseCurrentItem()
2704 {
2705  setCurrentThreadExpanded( false );
2706 }
2707 
2708 void View::slotExpandCurrentItem()
2709 {
2710  setCurrentThreadExpanded( true );
2711 }
2712 
2713 void View::focusQuickSearch()
2714 {
2715  d->mWidget->focusQuickSearch();
2716 }
2717 
2718 Akonadi::MessageStatus View::currentFilterStatus() const
2719 {
2720  return d->mWidget->currentFilterStatus();
2721 }
2722 
2723 
2724 QString View::currentFilterSearchString() const
2725 {
2726  return d->mWidget->currentFilterSearchString();
2727 }
2728 
2729 void View::setRowHidden( int row, const QModelIndex & parent, bool hide )
2730 {
2731  const QModelIndex rowModelIndex = model()->index( row, 0, parent );
2732  const Item* const rowItem = static_cast< Item * >( rowModelIndex.internalPointer() );
2733 
2734  if ( rowItem ) {
2735  const bool currentlyHidden = isRowHidden( row, parent );
2736 
2737  if ( currentlyHidden != hide ) {
2738  if ( currentMessageItem() == rowItem ) {
2739  selectionModel()->clear();
2740  selectionModel()->clearSelection();
2741  }
2742  }
2743  }
2744 
2745  QTreeView::setRowHidden( row, parent, hide );
2746 }
2747 
2748 void View::sortOrderMenuAboutToShow(KMenu *menu)
2749 {
2750  d->mWidget->sortOrderMenuAboutToShow(menu);
2751 }
2752 
2753 void View::aggregationMenuAboutToShow(KMenu *menu)
2754 {
2755  d->mWidget->aggregationMenuAboutToShow(menu);
2756 }
2757 
2758 void View::themeMenuAboutToShow(KMenu *menu)
2759 {
2760  d->mWidget->themeMenuAboutToShow(menu);
2761 }
2762 
2763 void View::setCollapseItem(const QModelIndex& index)
2764 {
2765  if(index.isValid())
2766  setExpanded( index, false );
2767 }
2768 
2769 void View::setExpandItem(const QModelIndex& index)
2770 {
2771  if(index.isValid())
2772  setExpanded( index, true );
2773 }
2774 
2775 
2776 
2777 #include "view.moc"
MessageList::Core::SortOrder::SortMessagesByDateTimeOfMostRecent
Sort the messages by date and time of the most recent message in subtree.
Definition: sortorder.h:82
MessageList::Core::View::focusPreviousMessageItem
bool focusPreviousMessageItem(MessageTypeFilter messageTypeFilter, bool centerItem, bool loop)
Focuses the previous message item in the view without actually selecting it.
Definition: view.cpp:1570
MessageList::Core::View::slotAdjustColumnSizes
void slotAdjustColumnSizes()
Handles the Adjust Column Sizes action of the header context menu.
Definition: view.cpp:803
MessageList::Core::View::slotCollapseCurrentItem
void slotCollapseCurrentItem()
Collapses the currect item.
Definition: view.cpp:2703
MessageList::Core::View::mouseDoubleClickEvent
virtual void mouseDoubleClickEvent(QMouseEvent *e)
Reimplemented in order to handle double clicks with sub-item precision.
Definition: view.cpp:1986
MessageList::Core::Theme::ContentItem::ExpandedStateIcon
The Expanded state icon for group headers.
Definition: theme.h:186
MessageList::Core::SortOrder
A class which holds information about sorting, e.g.
Definition: sortorder.h:37
MessageList::Core::Aggregation
A set of aggregation options that can be applied to the MessageList::Model in a single shot...
Definition: aggregation.h:43
MessageList::Core::GroupHeaderItem
Definition: groupheaderitem.h:34
MessageList::Core::Theme::ContentItem::ReadStateIcon
The icon that displays the unread/read state (never disabled)
Definition: theme.h:154
MessageList::Core::View::messageItemBefore
Item * messageItemBefore(Item *referenceItem, MessageTypeFilter messageTypeFilter, bool loop)
Finds message item that comes "before" the reference item.
Definition: view.cpp:1252
MessageList::Core::View::ignoreCurrentChanges
void ignoreCurrentChanges(bool ignore)
This is called by the model to insulate us from certain QTreeView signals This is because they may be...
Definition: view.cpp:163
delegate.h
MessageList::Core::Delegate
Definition: delegate.h:34
MessageList::Core::View::previousMessageItem
Item * previousMessageItem(MessageTypeFilter messageTypeFilter, bool loop)
Finds the previous message item with respect to the current item.
Definition: view.cpp:1370
MessageList::Core::Item::childItemStats
void childItemStats(ChildItemStats &stats) const
Gathers statistics about child items.
Definition: item.cpp:55
MessageList::Core::View::Model
friend class Model
Definition: view.h:66
MessageList::Core::View::mousePressEvent
virtual void mousePressEvent(QMouseEvent *e)
Reimplemented in order to handle clicks with sub-item precision.
Definition: view.cpp:2095
MessageList::Core::MessageItem::annotation
QString annotation() const
Returns the annotation of the message, given that hasAnnotation() is true.
Definition: messageitem.cpp:296
MessageList::Core::View::setCurrentThreadExpanded
void setCurrentThreadExpanded(bool expand)
If expand is true then it expands the current thread, otherwise collapses it.
Definition: view.cpp:1010
MessageList::Core::View::dragEnterEvent
virtual void dragEnterEvent(QDragEnterEvent *e)
Reimplemented in order to handle message DnD.
Definition: view.cpp:2280
MessageList::Core::View::isDisplayedWithParentsExpanded
bool isDisplayedWithParentsExpanded(Item *it) const
Returns true if the specified item is currently displayed in the tree and has all the parents expande...
Definition: view.cpp:1872
MessageList::Core::View::changeMessageStatus
void changeMessageStatus(MessageItem *it, const Akonadi::MessageStatus &set, const Akonadi::MessageStatus &unset)
Performs a change in the specified MessageItem status.
Definition: view.cpp:2068
MessageList::Core::View::slotShowDefaultColumns
void slotShowDefaultColumns()
Handles the Show Default Columns action of the header context menu.
Definition: view.cpp:812
MessageList::Core::View::View
View(Widget *parent)
Definition: view.cpp:91
MessageList::Core::View::slotHeaderSectionResized
void slotHeaderSectionResized(int logicalIndex, int oldWidth, int newWidth)
Handles section resizes in order to save the column widths.
Definition: view.cpp:709
MessageList::Core::MessageItem
Definition: messageitem.h:50
MessageList::Core::GroupHeaderItem::label
const QString & label() const
Definition: groupheaderitem.cpp:35
MessageList::Core::Aggregation::NoThreading
Perform no threading at all.
Definition: aggregation.h:85
MessageList::Core::MessageItem::subTreeToList
void subTreeToList(QList< MessageItem * > &list)
Appends the whole subtree originating at this item to the specified list.
Definition: messageitem.cpp:635
MessageList::Core::Item::isViewable
bool isViewable() const
Is this item attached to the viewable root ?
Definition: item.cpp:313
MessageList::Core::View::sizeHintForColumn
virtual int sizeHintForColumn(int logicalColumnIndex) const
Reimplemented in order to kill the QTreeView column auto-resizing.
Definition: view.cpp:719
MessageList::Core::View::slotExpandAllGroups
void slotExpandAllGroups()
Expands all the group headers (if present in the current Aggregation)
Definition: view.cpp:2698
MessageList::Core::Aggregation::GroupBySender
Group by sender, always.
Definition: aggregation.h:58
MessageList::Core::View::selectLastMessageItem
bool selectLastMessageItem(MessageTypeFilter messageTypeFilter, bool centerItem)
Selects the last message item in the view that matches messageTypeFilter.
Definition: view.cpp:1636
MessageList::Core::View::slotExpandCurrentItem
void slotExpandCurrentItem()
Expands the currect item.
Definition: view.cpp:2708
MessageList::Core::Widget
Provides a widget which has the messagelist and the most important helper widgets, like the search line and the comboboxes for changing status filtering, aggregation etc.
Definition: widgetbase.h:60
MessageList::Core::MessageTypeAny
Definition: enums.h:58
MessageList::Core::Aggregation::GroupByDate
Group the messages by the date of the thread leader.
Definition: aggregation.h:55
MessageList::Core::Item::childItemCount
int childItemCount() const
Returns the number of children of this Item.
Definition: item.cpp:163
gHeaderContextMenuDisplayToolTipsId
const int gHeaderContextMenuDisplayToolTipsId
Definition: view.cpp:748
MessageList::Core::Item::formattedSize
QString formattedSize() const
A string with a text rappresentation of size().
Definition: item.cpp:255
MessageList::Core::View::modelJobBatchTerminated
void modelJobBatchTerminated()
This is called by Model to signal the end of a lengthy job batch.
Definition: view.cpp:287
MessageList::Core::Item::receiver
const QString & receiver() const
Returns the receiver associated to this item.
Definition: item.cpp:452
MessageList::Core::Aggregation::GroupByDateRange
Use smart (thread leader) date ranges ("Today","Yesterday","Last Week"...)
Definition: aggregation.h:56
model.h
MessageList::Core::View::selectionEmpty
bool selectionEmpty() const
Fast function that determines if the selection is empty.
Definition: view.cpp:901
theme.h
MessageList::Core::View::isScrollingLocked
bool isScrollingLocked() const
Returns true if the vertical scrollbar should keep to the top or bottom while inserting items...
Definition: view.cpp:182
MessageList::Core::View::deepestExpandedChild
Item * deepestExpandedChild(Item *referenceItem) const
Returns the deepest child that is visible (i.e.
Definition: view.cpp:1241
MessageList::Core::View::model
Model * model() const
Returns the Model attacched to this View.
Definition: view.cpp:153
MessageList::Core::View::selectionAsMessageItemList
QList< MessageItem * > selectionAsMessageItemList(bool includeCollapsedChildren=true) const
Returns the currently selected MessageItems (bound to current StorageModel).
Definition: view.cpp:906
MessageList::Core::Item::subject
const QString & subject() const
Returns the subject associated to this Item.
Definition: item.cpp:472
MessageList::Core::Item::ChildItemStats::mUnreadChildCount
unsigned int mUnreadChildCount
Definition: item.h:195
MessageList::Core::GrowOrShrinkExistingSelection
Definition: enums.h:69
MessageList::Core::View
The MessageList::View is the real display of the message list.
Definition: view.h:64
MessageList::Core::View::sortOrderMenuAboutToShow
void sortOrderMenuAboutToShow(KMenu *menu)
Definition: view.cpp:2748
MessageList::Core::View::setExpandItem
void setExpandItem(const QModelIndex &index)
Definition: view.cpp:2769
MessageList::Core::View::messageItemAfter
Item * messageItemAfter(Item *referenceItem, MessageTypeFilter messageTypeFilter, bool loop)
Finds message item that comes "after" the reference item.
Definition: view.cpp:1122
MessageList::Core::View::applyThemeColumns
void applyThemeColumns()
Applies the theme columns to this view.
Definition: view.cpp:327
MessageList::Core::Theme::ContentItem::ImportantStateIcon
The Important tag icon.
Definition: theme.h:174
MessageList::Core::View::currentItem
Item * currentItem() const
Returns the current Item (that is bound to current StorageModel).
Definition: view.cpp:856
MessageList::Core::View::currentMessageItem
MessageItem * currentMessageItem(bool selectIfNeeded=true) const
Returns the current MessageItem (that is bound to current StorageModel).
Definition: view.cpp:866
MessageList::Core::Theme::ContentItem::AnnotationIcon
Whether the message has a annotation/note.
Definition: theme.h:218
MessageList::Core::Aggregation::NoGrouping
Don't group messages at all.
Definition: aggregation.h:54
MessageList::Core::View::contextMenuEvent
virtual void contextMenuEvent(QContextMenuEvent *e)
Reimplemented in order to handle context menu request via keyboard.
Definition: view.cpp:2252
MessageList::Core::Item::ChildItemStats::mTotalChildCount
unsigned int mTotalChildCount
Definition: item.h:194
MessageList::Core::Aggregation::TopmostMessage
The thread grouping is computed from the topmost message (very similar to least recent, but might be different if timezones or machine clocks are screwed)
Definition: aggregation.h:99
gHeaderContextMenuShowDefaultColumnsId
const int gHeaderContextMenuShowDefaultColumnsId
Definition: view.cpp:747
MessageList::Core::Theme::Column
The Column class defines a view column available inside this theme.
Definition: theme.h:564
MessageList::Core::Model
This class manages the huge tree of displayable objects: GroupHeaderItems and MessageItems.
Definition: model.h:77
MessageList::Core::View::saveThemeColumnState
void saveThemeColumnState()
Saves the state of the columns (width and visility) to the currently selected theme object...
Definition: view.cpp:612
MessageList::Core::View::setAllGroupsExpanded
void setAllGroupsExpanded(bool expand)
If expand is true then it expands all the groups (only the toplevel group item: inner threads are NOT...
Definition: view.cpp:1057
MessageList::Core::View::slotShowHideColumn
void slotShowHideColumn(int columnIndex)
Handles the actions of the header context menu for showing/hiding a column.
Definition: view.cpp:826
MessageList::Core::Item::type
Type type() const
Returns the type of this item.
Definition: item.cpp:298
MessageList::Core::View::dragMoveEvent
virtual void dragMoveEvent(QDragMoveEvent *e)
Reimplemented in order to handle message DnD.
Definition: view.cpp:2285
MessageList::Core::View::createPersistentSet
MessageItemSetReference createPersistentSet(const QList< MessageItem * > &items)
Creates a persistent set for the specified MessageItems and returns its reference.
Definition: view.cpp:1670
MessageList::Core::View::aggregationMenuAboutToShow
void aggregationMenuAboutToShow(KMenu *menu)
Definition: view.cpp:2753
MessageList::Core::MessageItem::hasAnnotation
virtual bool hasAnnotation() const
Returns true if this message has an annotation.
Definition: messageitem.cpp:286
MessageList::Core::View::modelFinishedLoading
void modelFinishedLoading()
This is called by Model to signal that the initial loading stage of a newly attached StorageModel is ...
Definition: view.cpp:1662
MessageList::Core::MessageItem::akonadiItem
Akonadi::Item akonadiItem() const
Definition: messageitem.cpp:566
MessageList::Core::View::ensureDisplayedWithParentsExpanded
void ensureDisplayedWithParentsExpanded(Item *it)
Makes sure that the specified is currently viewable by the user.
Definition: view.cpp:1844
messageitem.h
message_type_matches
static bool message_type_matches(Item *item, MessageTypeFilter messageTypeFilter)
Definition: view.cpp:1102
view.h
MessageList::Core::View::event
virtual bool event(QEvent *e)
Reimplemented in order to catch QHelpEvent.
Definition: view.cpp:2319
MessageList::Core::View::firstMessageItem
Item * firstMessageItem(MessageTypeFilter messageTypeFilter)
Finds the first message item in the view.
Definition: view.h:516
MessageList::Core::SortOrder::messageSorting
MessageSorting messageSorting() const
Returns the current message sorting option.
Definition: sortorder.h:126
MessageList::Core::Aggregation::GroupByReceiver
Group by receiver, always.
Definition: aggregation.h:59
MessageList::Core::Settings::messageToolTipEnabled
static bool messageToolTipEnabled()
Get Display tooltips for messages and group headers.
Definition: settings.cpp:141
QTreeView
MessageList::Core::View::modelHasBeenReset
void modelHasBeenReset()
This is called by the model from inside setFolder().
Definition: view.cpp:293
MessageList::Core::SortOrder::messageSortDirection
SortDirection messageSortDirection() const
Returns the current message SortDirection.
Definition: sortorder.h:138
MessageList::Core::View::isThreaded
bool isThreaded() const
Returns true if the current Aggregation is threaded, false otherwise (or if there is no current Aggre...
Definition: view.cpp:1914
MessageList::Core::Item::itemAboveChild
Item * itemAboveChild(Item *child)
Returns the item that is visually above the specified child if this item.
Definition: item.cpp:134
MessageList::Core::View::storageModel
StorageModel * storageModel() const
Returns the currently displayed StorageModel.
Definition: view.cpp:241
MessageList::Core::Settings::self
static Settings * self()
Definition: settings.cpp:70
MessageList::Core::MessageItem::tagListDescription
QString tagListDescription() const
Definition: messageitem.cpp:337
MessageList::Core::View::updateGeometries
virtual void updateGeometries()
Reimplemented in order to disable update of the geometries while a job step is running (as it takes a...
Definition: view.cpp:219
MessageList::Core::PreSelectionMode
PreSelectionMode
Pre-selection is the action of automatically selecting a message just after the folder has finished l...
Definition: enums.h:44
MessageList::Core::View::lastMessageItem
Item * lastMessageItem(MessageTypeFilter messageTypeFilter)
Finds the last message item in the view.
Definition: view.h:557
MessageList::Core::View::setCurrentMessageItem
void setCurrentMessageItem(MessageItem *it, bool center=false)
Sets the current message item.
Definition: view.cpp:883
MessageList::Core::View::setAllThreadsExpanded
void setAllThreadsExpanded(bool expand)
If expand is true then it expands all the threads, otherwise collapses them.
Definition: view.cpp:1038
MessageList::Core::View::slotSelectionChanged
void slotSelectionChanged(const QItemSelection &current, const QItemSelection &)
Handles selection item management.
Definition: view.cpp:1921
MessageList::Core::Item::childItem
Item * childItem(int idx) const
Returns the child item at position idx or 0 if idx is out of the allowable range. ...
Definition: item.cpp:75
MessageList::Core::View::setCollapseItem
void setCollapseItem(const QModelIndex &index)
Definition: view.cpp:2763
aggregation.h
MessageList::Core::View::focusQuickSearch
void focusQuickSearch()
Sets the focus on the quick search line of the currently active tab.
Definition: view.cpp:2713
MessageList::Core::Theme::NeverShowHeader
Definition: theme.h:909
MessageList::Core::View::setSortOrder
void setSortOrder(const SortOrder *sortOrder)
Sets the specified sort order.
Definition: view.cpp:263
manager.h
MessageList::Core::Item::GroupHeader
This item is a GroupHeaderItem.
Definition: item.h:63
MessageList::Core::View::slotDisplayTooltips
void slotDisplayTooltips(bool showTooltips)
Handles the Display Tooltips action of the header context menu.
Definition: view.cpp:821
MessageList::Core::SortOrder::Descending
Definition: sortorder.h:70
MessageList::Core::View::changeMessageStatusRead
void changeMessageStatusRead(MessageItem *it, bool read)
Definition: view.cpp:2049
groupheaderitem.h
MessageList::Util::contentSummary
MESSAGELIST_EXPORT QString contentSummary(const KUrl &url)
Returns the first few lines of the actual email text This data is taken from Nepomuk, if available.
Definition: messagelistutil.cpp:145
MessageList::Core::Item::formattedDate
QString formattedDate() const
A string with a text rappresentation of date() obtained via Manager.
Definition: item.cpp:260
MessageList::Core::Item::Message
This item is a MessageItem.
Definition: item.h:64
MessageList::Core::View::modelAboutToEmitLayoutChanged
void modelAboutToEmitLayoutChanged()
Definition: view.cpp:696
MessageList::Core::Item
A single item of the MessageList tree managed by MessageList::Model.
Definition: item.h:52
MessageList::Core::View::slotCollapseAllGroups
void slotCollapseAllGroups()
Collapses all the group headers (if present in the current Aggregation)
Definition: view.cpp:2693
item.h
MessageList::Core::View::changeEvent
virtual void changeEvent(QEvent *e)
Reimplemented in order to catch palette, font and style changes.
Definition: view.cpp:2295
MessageList::Core::View::selectFocusedMessageItem
void selectFocusedMessageItem(bool centerItem)
Selects the currently focused message item.
Definition: view.cpp:1593
MessageList::Core::Theme::Column::setCurrentlyVisible
void setCurrentlyVisible(bool currentlyVisible)
Sets the current shared visibility state for this column.
Definition: theme.h:752
MessageList::Core::View::selectNextMessageItem
bool selectNextMessageItem(MessageTypeFilter messageTypeFilter, ExistingSelectionBehaviour existingSelectionBehaviour, bool centerItem, bool loop)
Selects the next message item in the view.
Definition: view.cpp:1463
MessageList::Core::View::deletePersistentSet
void deletePersistentSet(MessageItemSetReference ref)
Deletes the persistent set pointed by the specified reference.
Definition: view.cpp:1680
MessageList::Core::Item::childItems
QList< Item * > * childItems() const
Return the list of child items.
Definition: item.cpp:70
MessageList::Core::MessageTypeUnreadOnly
Definition: enums.h:59
MessageList::Core::View::markMessageItemsAsAboutToBeRemoved
void markMessageItemsAsAboutToBeRemoved(QList< MessageItem * > &items, bool bMark)
If bMark is true this function marks the messages as "about to be removed" so they appear dimmer and ...
Definition: view.cpp:1685
MessageList::Core::View::selectPreviousMessageItem
bool selectPreviousMessageItem(MessageTypeFilter messageTypeFilter, ExistingSelectionBehaviour existingSelectionBehaviour, bool centerItem, bool loop)
Selects the previous message item in the view.
Definition: view.cpp:1505
MessageList::Core::StorageModel
The QAbstractItemModel based interface that you need to provide for your storage to work with Message...
Definition: storagemodelbase.h:45
MessageList::Core::View::modelEmittedLayoutChanged
void modelEmittedLayoutChanged()
Definition: view.cpp:702
MessageList::Core::View::currentFilterStatus
Akonadi::MessageStatus currentFilterStatus() const
Returns the Akonadi::MessageStatus in the current quicksearch field.
Definition: view.cpp:2718
MessageList::Core::Item::ChildItemStats
A structure used with MessageList::Item::childItemStats().
Definition: item.h:191
MessageList::Core::MessageTypeFilter
MessageTypeFilter
This enum is used in the view message selection functions (for instance View::nextMessageItem()).
Definition: enums.h:56
MessageList::Core::View::currentThreadAsMessageItemList
QList< MessageItem * > currentThreadAsMessageItemList() const
Returns the MessageItems bound to the current StorageModel that are part of the current thread...
Definition: view.cpp:948
MessageList::Core::Aggregation::MostRecentMessage
The thread grouping is computed from the most recent message.
Definition: aggregation.h:100
MessageList::Core::Theme::ContentItem::SpamHamStateIcon
The Spam/Ham state icon.
Definition: theme.h:178
settings.h
messagelistutil.h
gHeaderContextMenuAdjustColumnSizesId
const int gHeaderContextMenuAdjustColumnSizesId
Definition: view.cpp:746
MessageList::Core::PreSelectNone
Definition: enums.h:46
MessageList::Core::View::growOrShrinkExistingSelection
void growOrShrinkExistingSelection(const QModelIndex &newSelectedIndex, bool movingUp)
This is used by the selection functions to grow/shrink the existing selection according to the newly ...
Definition: view.cpp:1375
MessageList::Core::Item::itemBelow
Item * itemBelow()
Returns the item that is visually below this item in the tree.
Definition: item.cpp:109
MessageList::Util::fillViewMenu
MESSAGELIST_EXPORT void fillViewMenu(KMenu *menu, QObject *receiver)
Definition: messagelistutil.cpp:124
MessageList::Core::SortOrder::Ascending
Definition: sortorder.h:69
MessageList::Core::Theme::ContentItem::ActionItemStateIcon
The ActionItem state icon.
Definition: theme.h:170
MessageList::Core::View::currentFilterSearchString
QString currentFilterSearchString() const
Returns the search term in the current quicksearch field.
Definition: view.cpp:2724
MessageList::Core::Item::statusDescription
QString statusDescription(bool hasNepomukTag) const
Returns a string describing the status e.g: "Read, Forwarded, Important".
Definition: item.cpp:213
MessageList::Core::View::triggerDelayedApplyThemeColumns
void triggerDelayedApplyThemeColumns()
Starts a short-delay timer connected to applyThemeColumns().
Definition: view.cpp:604
MessageList::Core::View::setRowHidden
virtual void setRowHidden(int row, const QModelIndex &parent, bool hide)
Called to hide or show the specified row from the view.
Definition: view.cpp:2729
MessageList::Core::View::persistentSetCurrentMessageItemList
QList< MessageItem * > persistentSetCurrentMessageItemList(MessageItemSetReference ref)
Returns the list of MessageItems that are still existing in the set pointed by the specified referenc...
Definition: view.cpp:1675
MessageList::Core::Item::sender
const QString & sender() const
Returns the sender associated to this item.
Definition: item.cpp:442
MessageList::Core::Settings::setMessageToolTipEnabled
static void setMessageToolTipEnabled(bool v)
Set Display tooltips for messages and group headers.
Definition: settings.cpp:135
MessageList::Core::Model::index
virtual QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const
Definition: model.cpp:545
MessageList::Core::View::setStorageModel
void setStorageModel(StorageModel *storageModel, PreSelectionMode preSelectionMode=PreSelectLastSelected)
Sets the StorageModel to be displayed in this view.
Definition: view.cpp:273
MessageList::Core::View::dropEvent
virtual void dropEvent(QDropEvent *e)
Reimplemented in order to handle message DnD.
Definition: view.cpp:2290
MessageList::Core::View::focusNextMessageItem
bool focusNextMessageItem(MessageTypeFilter messageTypeFilter, bool centerItem, bool loop)
Focuses the next message item in the view without actually selecting it.
Definition: view.cpp:1547
widgetbase.h
MessageList::Core::Item::itemAbove
Item * itemAbove()
Returns the item that is visually above this item in the tree.
Definition: item.cpp:149
MessageList::Core::View::slotHeaderContextMenuRequested
void slotHeaderContextMenuRequested(const QPoint &pnt)
Handles context menu requests for the header.
Definition: view.cpp:750
MessageList::Core::SortOrder::SortMessagesByDateTime
Sort the messages by date and time.
Definition: sortorder.h:81
MessageList::Core::Item::status
const Akonadi::MessageStatus & status() const
Returns the status associated to this Item.
Definition: item.cpp:402
MessageList::Core::View::modelJobBatchStarted
void modelJobBatchStarted()
This is called by Model to signal a start of a lengthy job batch.
Definition: view.cpp:281
MessageList::Core::Theme
The Theme class defines the visual appearance of the MessageList.
Definition: theme.h:65
MessageList::Core::View::themeMenuAboutToShow
void themeMenuAboutToShow(KMenu *menu)
Definition: view.cpp:2758
MessageList::Core::View::selectMessageItems
void selectMessageItems(const QList< MessageItem * > &list)
Selects the specified MessageItems.
Definition: view.cpp:1084
MessageList::Core::View::resizeEvent
virtual void resizeEvent(QResizeEvent *e)
Reimplemented in order to resize columns when header is not visible.
Definition: view.cpp:657
MessageList::Core::View::delegate
Delegate * delegate() const
Returns the Delegate attacched to this View.
Definition: view.cpp:158
MessageList::Core::Theme::ContentItem::WatchedIgnoredStateIcon
The Watched/Ignored state icon.
Definition: theme.h:182
MessageList::Core::View::mouseMoveEvent
virtual void mouseMoveEvent(QMouseEvent *e)
Reimplemented in order to handle DnD.
Definition: view.cpp:2235
MessageList::Core::ExpandExistingSelection
Definition: enums.h:68
MessageList::Core::Item::parent
Item * parent() const
Returns the parent Item in the tree, or 0 if this item isn't attached to the tree.
Definition: item.cpp:392
MessageList::Core::Aggregation::GroupBySenderOrReceiver
Group by sender (incoming) or receiver (outgoing) field.
Definition: aggregation.h:57
MessageList::Core::View::reload
void reload()
Triggers a reload of the view in order to re-display the current folder.
Definition: view.cpp:268
MessageList::Core::View::showEvent
virtual void showEvent(QShowEvent *e)
Reimplemented in order to apply theme column widths on the first show.
Definition: view.cpp:731
MessageList::Core::Item::hasChildren
bool hasChildren() const
Convenience function that returns true if this item has children.
Definition: item.cpp:168
MessageList::Core::View::nextMessageItem
Item * nextMessageItem(MessageTypeFilter messageTypeFilter, bool loop)
Finds the next message item with respect to the current item.
Definition: view.cpp:1236
storagemodelbase.h
MessageList::Core::View::selectFirstMessageItem
bool selectFirstMessageItem(MessageTypeFilter messageTypeFilter, bool centerItem)
Selects the first message item in the view that matches messageTypeFilter.
Definition: view.cpp:1610
MessageList::Core::MessageItemSetReference
long int MessageItemSetReference
Definition: messageitemsetmanager.h:33
MessageList::Core::View::setChildrenExpanded
void setChildrenExpanded(const Item *parent, bool expand)
Expands or collapses the children of the specified item, recursively.
Definition: view.cpp:968
MessageList::Core::View::setTheme
void setTheme(Theme *theme)
Sets the specified theme for this view.
Definition: view.cpp:255
MessageList::Core::ExistingSelectionBehaviour
ExistingSelectionBehaviour
This enum is used in the view message selection functions (for instance View::selectNextMessage()) ...
Definition: enums.h:65
MessageList::Core::View::~View
~View()
Definition: view.cpp:135
MessageList::Core::Item::itemBelowChild
Item * itemBelowChild(Item *child)
Returns the item that is visually below the specified child if this item.
Definition: item.cpp:91
MessageList::Core::View::triggerDelayedSaveThemeColumnState
void triggerDelayedSaveThemeColumnState()
Starts a short-delay timer connected to saveThemeColumnState().
Definition: view.cpp:649
MessageList::Core::View::ignoreUpdateGeometries
void ignoreUpdateGeometries(bool ignore)
Used to enable/disable the ignoring of updateGeometries() calls.
Definition: view.cpp:177
MessageList::Core::View::setAggregation
void setAggregation(const Aggregation *aggregation)
Sets the aggregation for this view.
Definition: view.cpp:246
MessageList::Core::Item::indexOfChildItem
int indexOfChildItem(Item *item) const
Returns the actual index of the child Item item or -1 if item is not a child of this Item...
Definition: item.cpp:173
MessageList::Core::StorageModel::containsOutboundMessages
virtual bool containsOutboundMessages() const =0
Returns true if this StorageModel (folder) contains outbound messages and false otherwise.
MessageList::Core::Theme::Column::setCurrentWidth
void setCurrentWidth(int currentWidth)
Sets the current shared width setting for this column.
Definition: theme.h:767
MessageList::Core::Item::setStatus
void setStatus(const Akonadi::MessageStatus &status)
Sets the status associated to this Item.
Definition: item.cpp:407
This file is part of the KDE documentation.
Documentation copyright © 1996-2014 The KDE developers.
Generated on Tue Oct 14 2014 22:55:32 by doxygen 1.8.7 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

messagelist

Skip menu "messagelist"
  • 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

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