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

messagelist

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

Search



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

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