10#include "core/aggregation.h"
11#include "core/delegate.h"
12#include "core/groupheaderitem.h"
14#include "core/messageitem.h"
15#include "core/model.h"
16#include "core/storagemodelbase.h"
17#include "core/theme.h"
18#include "core/widgetbase.h"
19#include "messagelistsettings.h"
20#include "messagelistutil.h"
21#include "messagelistutil_p.h"
23#include <MessageCore/StringUtil>
25#include <Akonadi/Item>
26#include <KTwoFingerTap>
27#include <QApplication>
28#include <QGestureEvent>
39#include "messagelist_debug.h"
40#include <KLocalizedString>
44class View::ViewPrivate
50 , mDelegate(new Delegate(owner))
55 void generalPaletteChanged();
66 Model *mModel =
nullptr;
67 Delegate *
const mDelegate;
70 Theme *mTheme =
nullptr;
71 bool mNeedToApplyThemeColumns =
false;
72 Item *mLastCurrentItem =
nullptr;
73 QPoint mMousePressPosition;
74 bool mSaveThemeColumnStateOnSectionResize =
true;
75 QTimer *mSaveThemeColumnStateTimer =
nullptr;
76 QTimer *mApplyThemeColumnsTimer =
nullptr;
77 int mLastViewportWidth = -1;
78 bool mIgnoreUpdateGeometries =
false;
80 bool mIsTouchEvent =
false;
81 bool mMousePressed =
false;
83 bool mTapAndHoldActive =
false;
90 , d(new ViewPrivate(this, pParent))
92 d->mSaveThemeColumnStateTimer =
new QTimer();
95 d->mApplyThemeColumnsTimer =
new QTimer();
98 setItemDelegate(d->mDelegate);
101 setAlternatingRowColors(
true);
102 setAllColumnsShowFocus(
true);
104 viewport()->setAcceptDrops(
true);
109 d->mScroller->setScrollerProperties(scrollerProp);
110 d->mScroller->grabGesture(viewport());
114 viewport()->grabGesture(d->mTwoFingerTap);
124 header()->setSectionsClickable(
true);
126 header()->setMinimumSectionSize(2);
127 header()->setDefaultSectionSize(2);
129 d->mModel =
new Model(
this);
138 d->expandFullThread(index);
144 if (d->mSaveThemeColumnStateTimer->isActive()) {
145 d->mSaveThemeColumnStateTimer->stop();
147 delete d->mSaveThemeColumnStateTimer;
148 if (d->mApplyThemeColumnsTimer->isActive()) {
149 d->mApplyThemeColumnsTimer->stop();
151 delete d->mApplyThemeColumnsTimer;
154 d->mApplyThemeColumnsTimer =
nullptr;
157 d->mAggregation =
nullptr;
165Delegate *View::delegate()
const
170void View::ignoreCurrentChanges(
bool ignore)
174 viewport()->setUpdatesEnabled(
false);
177 viewport()->setUpdatesEnabled(
true);
181void View::ignoreUpdateGeometries(
bool ignore)
183 d->mIgnoreUpdateGeometries = ignore;
186bool View::isScrollingLocked()
const
205 const int scrollBarPosition = verticalScrollBar()->value();
206 const int scrollBarMaximum = verticalScrollBar()->maximum();
207 const SortOrder *sortOrder = d->mModel->sortOrder();
208 const bool lockView = (
210 !d->mModel->isLoading())
218 || (scrollBarPosition == scrollBarMaximum && sortOrder->
messageSortDirection() == SortOrder::Ascending));
222void View::updateGeometries()
224 if (d->mIgnoreUpdateGeometries || !d->mModel) {
228 const int scrollBarPositionBefore = verticalScrollBar()->value();
229 const bool lockView = isScrollingLocked();
235 if (scrollBarPositionBefore != 0) {
237 if (verticalScrollBar()->value() != verticalScrollBar()->maximum()) {
238 verticalScrollBar()->setValue(verticalScrollBar()->maximum());
246 return d->mModel->storageModel();
251 d->mAggregation = aggregation;
252 d->mModel->setAggregation(aggregation);
260 d->mNeedToApplyThemeColumns =
true;
262 d->mDelegate->setTheme(theme);
263 d->mModel->setTheme(theme);
268 d->mModel->setSortOrder(sortOrder);
273 setStorageModel(storageModel());
279 d->mSaveThemeColumnStateOnSectionResize =
false;
280 d->mModel->setStorageModel(storageModel, preSelectionMode);
281 d->mSaveThemeColumnStateOnSectionResize =
true;
311void View::applyThemeColumns()
313 if (!d->mApplyThemeColumnsTimer) {
317 if (d->mApplyThemeColumnsTimer->isActive()) {
318 d->mApplyThemeColumnsTimer->stop();
333 if (!viewport()->isVisible()) {
337 if (viewport()->width() < 1) {
340 const int viewportWidth = viewport()->width();
341 d->mLastViewportWidth = viewportWidth;
372 int totalVisibleWidthHint = 0;
374 for (
const auto col : std::as_const(columns)) {
375 if (col->currentlyVisible() || (idx == 0)) {
378 const int savedWidth = col->currentWidth();
379 const int hintWidth = d->mDelegate->sizeHintForItemTypeAndColumn(Item::Message, idx).width();
380 totalVisibleWidthHint += savedWidth > 0 ? savedWidth : hintWidth;
381 lColumnSizeHints.
append(hintWidth);
386 lColumnSizeHints.
append(-1);
391 if (totalVisibleWidthHint < 16) {
392 totalVisibleWidthHint = 16;
400 int totalVisibleWidth = 0;
401 for (
const auto col : std::as_const(columns)) {
402 double savedWidth = col->currentWidth();
403 double hintWidth = savedWidth > 0 ? savedWidth : lColumnSizeHints.
at(idx);
406 if (col->currentlyVisible() || (idx == 0)) {
407 if (col->containsTextItems()) {
409 realWidth = ((hintWidth * viewportWidth) / totalVisibleWidthHint);
412 realWidth = hintWidth;
419 totalVisibleWidth += realWidth;
425 lColumnWidths.
append(realWidth);
444 if (totalVisibleWidth != viewportWidth) {
446 if (totalVisibleWidth < viewportWidth) {
451 qreal available = viewportWidth - totalVisibleWidth;
453 for (
int idx = 0; idx < columns.
count(); ++idx) {
458 lColumnWidths[idx] += available;
466 if (available >= 1) {
467 lColumnWidths[0] += available;
474 double missing = totalVisibleWidth - viewportWidth;
476 const int count = lColumnWidths.
count();
480 if (columns.
at(idx)->currentlyVisible() || (idx == 0)) {
481 double chop = lColumnWidths.
at(idx) - lColumnSizeHints.
at(idx);
483 if (chop > missing) {
486 lColumnWidths[idx] -= chop;
501 bool oldSave = d->mSaveThemeColumnStateOnSectionResize;
502 d->mSaveThemeColumnStateOnSectionResize =
false;
513 for (
const auto col : std::as_const(columns)) {
514 bool visible = (idx == 0) || col->currentlyVisible();
516 col->setCurrentlyVisible(
visible);
517 header()->setSectionHidden(idx, !
visible);
527 for (
const auto col : std::as_const(columns)) {
528 if (col->currentlyVisible()) {
529 const double columnWidth(lColumnWidths.
at(idx));
530 col->setCurrentWidth(columnWidth);
533 header()->resizeSection(idx,
static_cast<int>(columnWidth));
535 col->setCurrentWidth(-1);
542 bool bTriggeredQtBug =
false;
543 for (
const auto col : std::as_const(columns)) {
544 if (!header()->isSectionHidden(idx)) {
545 if (!col->currentlyVisible()) {
546 bTriggeredQtBug =
true;
552 setHeaderHidden(d->mTheme->viewHeaderPolicy() == Theme::NeverShowHeader);
554 d->mSaveThemeColumnStateOnSectionResize = oldSave;
555 d->mNeedToApplyThemeColumns =
false;
557 static bool bAllowRecursion =
true;
559 if (bTriggeredQtBug && bAllowRecursion) {
560 bAllowRecursion =
false;
563 bAllowRecursion =
true;
567void View::triggerDelayedApplyThemeColumns()
569 if (d->mApplyThemeColumnsTimer->isActive()) {
570 d->mApplyThemeColumnsTimer->stop();
572 d->mApplyThemeColumnsTimer->setSingleShot(
true);
573 d->mApplyThemeColumnsTimer->start(100);
576void View::saveThemeColumnState()
578 if (d->mSaveThemeColumnStateTimer->isActive()) {
579 d->mSaveThemeColumnStateTimer->stop();
586 if (d->mNeedToApplyThemeColumns) {
592 const auto columns = d->mTheme->columns();
594 if (columns.isEmpty()) {
600 for (
const auto col : std::as_const(columns)) {
601 if (header()->isSectionHidden(idx)) {
603 col->setCurrentlyVisible(
false);
604 col->setCurrentWidth(-1);
607 col->setCurrentlyVisible(
true);
608 col->setCurrentWidth(header()->sectionSize(idx));
614void View::triggerDelayedSaveThemeColumnState()
616 if (d->mSaveThemeColumnStateTimer->isActive()) {
617 d->mSaveThemeColumnStateTimer->stop();
619 d->mSaveThemeColumnStateTimer->setSingleShot(
true);
620 d->mSaveThemeColumnStateTimer->start(200);
625 qCDebug(MESSAGELIST_LOG) <<
"Resize event enter (viewport width is " << viewport()->width() <<
")";
633 if (d->mLastViewportWidth != viewport()->width()) {
634 triggerDelayedApplyThemeColumns();
637 if (header()->isVisible()) {
643 bool oldSave = d->mSaveThemeColumnStateOnSectionResize;
644 d->mSaveThemeColumnStateOnSectionResize =
false;
646 const int count = header()->count();
647 if ((count - header()->hiddenSectionCount()) < 2) {
650 for (visibleIndex = 0; visibleIndex < count; visibleIndex++) {
651 if (!header()->isSectionHidden(visibleIndex)) {
655 if (visibleIndex < count) {
656 header()->resizeSection(visibleIndex, viewport()->width() - 4);
660 d->mSaveThemeColumnStateOnSectionResize = oldSave;
662 triggerDelayedSaveThemeColumnState();
668 if ( (!model() || model()->rowCount() == 0)) {
671 QFont font = p.font();
675 if (!d->mTextColor.isValid()) {
676 d->generalPaletteChanged();
678 p.setPen(d->mTextColor);
689void View::modelAboutToEmitLayoutChanged()
692 d->mSaveThemeColumnStateOnSectionResize =
false;
695void View::modelEmittedLayoutChanged()
698 d->mSaveThemeColumnStateOnSectionResize =
true;
702void View::slotHeaderSectionResized(
int logicalIndex,
int oldWidth,
int newWidth)
704 Q_UNUSED(logicalIndex)
708 if (d->mSaveThemeColumnStateOnSectionResize) {
709 triggerDelayedSaveThemeColumnState();
713int View::sizeHintForColumn(
int logicalColumnIndex)
const
716 int w = header()->sectionSize(logicalColumnIndex);
723 w = d->mDelegate->sizeHintForItemTypeAndColumn(Item::Message, logicalColumnIndex).width();
727void View::slotHeaderContextMenuRequested(
const QPoint &pnt)
733 const auto columns = d->mTheme->columns();
735 if (columns.isEmpty()) {
743 for (
const auto col : std::as_const(columns)) {
746 act->
setChecked(!header()->isSectionHidden(idx));
751 slotShowHideColumn(idx);
770 act->
setChecked(MessageListSettings::self()->messageToolTipEnabled());
775 MessageList::Util::fillViewMenu(&menu, d->mWidget);
777 menu.
exec(header()->mapToGlobal(pnt));
780void View::slotAdjustColumnSizes()
786 d->mTheme->resetColumnSizes();
790void View::slotShowDefaultColumns()
796 d->mTheme->resetColumnState();
800void View::slotDisplayTooltips(
bool showTooltips)
802 MessageListSettings::self()->setMessageToolTipEnabled(showTooltips);
805void View::slotShowHideColumn(
int columnIdx)
811 if (columnIdx == 0) {
815 if (columnIdx >= d->mTheme->columns().count()) {
819 const bool showIt = header()->isSectionHidden(columnIdx);
825 saveThemeColumnState();
851 Item *it = currentItem();
852 if (!it || (it->
type() != Item::Message)) {
856 if (selectIfNeeded) {
859 if (!selectionModel()->isSelected(currentIndex())) {
870 qCDebug(MESSAGELIST_LOG) <<
"Setting current message to" << it->
subject();
882bool View::selectionEmpty()
const
884 return selectionModel()->selectedRows().isEmpty();
891 QModelIndexList lSelected = selectionModel()->selectedRows();
892 if (lSelected.isEmpty()) {
893 return selectedMessages;
895 for (
const auto &idx : std::as_const(lSelected)) {
903 if (!idx.isValid()) {
907 Item *selectedItem =
static_cast<Item *
>(idx.internalPointer());
908 Q_ASSERT(selectedItem);
910 if (selectedItem->
type() != Item::Message) {
914 if (!
static_cast<MessageItem *
>(selectedItem)->isValid()) {
920 if (includeCollapsedChildren && (selectedItem->
childItemCount() > 0) && (!isExpanded(idx))) {
921 static_cast<MessageItem *
>(selectedItem)->subTreeToList(selectedMessages);
927 return selectedMessages;
936 return currentThread;
948 return currentThread;
951void View::setChildrenExpanded(
const Item *root,
bool expand)
958 for (
const auto child : std::as_const(*childList)) {
964 setExpanded(idx,
true);
966 if (child->childItemCount() > 0) {
967 setChildrenExpanded(child,
true);
970 if (child->childItemCount() > 0) {
971 setChildrenExpanded(child,
false);
974 setExpanded(idx,
false);
979void View::ViewPrivate::generalPaletteChanged()
987void View::ViewPrivate::expandFullThread(
const QModelIndex &index)
994 if (item->type() != Item::Message) {
999 q->setChildrenExpanded(item,
true);
1003void View::setCurrentThreadExpanded(
bool expand)
1005 Item *it = currentItem();
1010 if (it->
type() == Item::GroupHeader) {
1011 setExpanded(currentIndex(), expand);
1012 }
else if (it->
type() == Item::Message) {
1014 while (message->parent()) {
1015 if (message->parent()->type() != Item::Message) {
1018 message =
static_cast<MessageItem *
>(message->parent());
1022 setExpanded(d->mModel->index(message, 0),
true);
1023 setChildrenExpanded(message,
true);
1025 setChildrenExpanded(message,
false);
1026 setExpanded(d->mModel->index(message, 0),
false);
1031void View::setAllThreadsExpanded(
bool expand)
1033 scheduleDelayedItemsLayout();
1036 setChildrenExpanded(d->mModel->rootItem(), expand);
1042 auto childList = d->mModel->rootItem()->childItems();
1047 for (
const auto item : std::as_const(*childList)) {
1048 setChildrenExpanded(item, expand);
1052void View::setAllGroupsExpanded(
bool expand)
1058 Item *item = d->mModel->rootItem();
1065 scheduleDelayedItemsLayout();
1066 for (
const auto item : std::as_const(*childList)) {
1067 Q_ASSERT(item->
type() == Item::GroupHeader);
1072 if (!isExpanded(idx)) {
1073 setExpanded(idx,
true);
1076 if (isExpanded(idx)) {
1077 setExpanded(idx,
false);
1086 for (
const auto mi : list) {
1091 if (!selectionModel()->isSelected(idx)) {
1094 ensureDisplayedWithParentsExpanded(mi);
1103 switch (messageTypeFilter) {
1104 case MessageTypeAny:
1107 case MessageTypeUnreadOnly:
1122 if (!storageModel()) {
1129 if (referenceItem) {
1131 if ((referenceItem->
childItemCount() > 0) && ((messageTypeFilter != MessageTypeAny) || isExpanded(d->mModel->index(referenceItem, 0)))) {
1136 Q_ASSERT(referenceItem->
parent());
1144 below = d->mModel->rootItem()->
itemBelow();
1147 if (below == referenceItem) {
1157 below = d->mModel->rootItem()->
itemBelow();
1168 QModelIndex belowIndex = d->mModel->index(below, 0);
1170 Q_ASSERT(belowIndex.
isValid());
1174 (below->
type() != Item::Message) ||
1175 (!message_type_matches(below, messageTypeFilter)) ||
1176 isRowHidden(belowIndex.
row(), parentIndex) ||
1179 if ((below->
childItemCount() > 0) && ((messageTypeFilter != MessageTypeAny) || isExpanded(belowIndex))) {
1184 Q_ASSERT(below->
parent());
1192 if (referenceItem) {
1193 below = d->mModel->rootItem()->
itemBelow();
1202 if (below == referenceItem) {
1207 parentIndex = d->mModel->index(below->
parent(), 0);
1208 belowIndex = d->mModel->index(below, 0);
1210 Q_ASSERT(belowIndex.
isValid());
1218 return messageItemAfter(
nullptr, messageTypeFilter,
false);
1223 return messageItemAfter(currentMessageItem(
false), messageTypeFilter, loop);
1226Item *View::deepestExpandedChild(
Item *referenceItem)
const
1229 if (
children > 0 && isExpanded(d->mModel->index(referenceItem, 0))) {
1232 return referenceItem;
1238 if (!storageModel()) {
1245 if (referenceItem) {
1247 Item *siblingAbove =
parent ?
parent->itemAboveChild(referenceItem) :
nullptr;
1249 if ((siblingAbove && siblingAbove != referenceItem && siblingAbove !=
parent) && (siblingAbove->
childItemCount() > 0)
1250 && ((messageTypeFilter != MessageTypeAny) || (isExpanded(d->mModel->index(siblingAbove, 0))))) {
1252 above = deepestExpandedChild(siblingAbove);
1255 Q_ASSERT(referenceItem->
parent());
1259 if ((!above) || (above == d->mModel->rootItem())) {
1265 Q_ASSERT(above != d->mModel->rootItem());
1267 if (above == referenceItem) {
1279 if (!above || !above->
parent() || (above == d->mModel->rootItem())) {
1288 QModelIndex aboveIndex = d->mModel->index(above, 0);
1290 Q_ASSERT(aboveIndex.
isValid());
1294 (above->
type() != Item::Message) ||
1295 (!message_type_matches(above, messageTypeFilter)) ||
1298 (messageTypeFilter == MessageTypeAny) &&
1299 (!isDisplayedWithParentsExpanded(above)))
1301 isRowHidden(aboveIndex.
row(), parentIndex) ||
1305 if ((!above) || (above == d->mModel->rootItem())) {
1309 if (referenceItem) {
1319 if (above == referenceItem) {
1328 parentIndex = d->mModel->index(above->
parent(), 0);
1329 aboveIndex = d->mModel->index(above, 0);
1331 Q_ASSERT(aboveIndex.
isValid());
1339 return messageItemBefore(
nullptr, messageTypeFilter,
false);
1344 return messageItemBefore(currentMessageItem(
false), messageTypeFilter, loop);
1347void View::growOrShrinkExistingSelection(
const QModelIndex &newSelectedIndex,
bool movingUp)
1351 int selectedVisualCoordinate = visualRect(newSelectedIndex).top();
1353 int topVisualCoordinate = 0xfffffff;
1354 int bottomVisualCoordinate = -(0xfffffff);
1367 QModelIndex top = d->mModel->index(range.top(), 0, range.parent());
1368 QModelIndex bottom = d->mModel->index(range.bottom(), 0, range.parent());
1379 int candidate = visualRect(bottom).bottom();
1380 if (candidate > bottomVisualCoordinate) {
1381 bottomVisualCoordinate = candidate;
1382 bottomIndex = range.bottomRight();
1385 candidate = visualRect(top).top();
1386 if (candidate < topVisualCoordinate) {
1387 topVisualCoordinate = candidate;
1388 topIndex = range.topLeft();
1394 if (selectedVisualCoordinate < topVisualCoordinate) {
1399 const QModelIndexList selectedIndexes = selection.
indexes();
1401 if ((idx.column() == 0) && (visualRect(idx).top() > selectedVisualCoordinate)) {
1407 if (selectedVisualCoordinate > bottomVisualCoordinate) {
1412 const QModelIndexList selectedIndexes = selection.
indexes();
1414 if ((idx.column() == 0) && (visualRect(idx).top() < selectedVisualCoordinate)) {
1428 Item *it = nextMessageItem(messageTypeFilter, loop);
1433 if (it->
parent() != d->mModel->rootItem()) {
1434 ensureDisplayedWithParentsExpanded(it);
1441 switch (existingSelectionBehaviour) {
1442 case ExpandExistingSelection:
1446 case GrowOrShrinkExistingSelection:
1448 growOrShrinkExistingSelection(idx,
false);
1452 setCurrentIndex(idx);
1465 Item *it = previousMessageItem(messageTypeFilter, loop);
1470 if (it->
parent() != d->mModel->rootItem()) {
1471 ensureDisplayedWithParentsExpanded(it);
1478 switch (existingSelectionBehaviour) {
1479 case ExpandExistingSelection:
1483 case GrowOrShrinkExistingSelection:
1485 growOrShrinkExistingSelection(idx,
true);
1489 setCurrentIndex(idx);
1502 Item *it = nextMessageItem(messageTypeFilter, loop);
1507 if (it->
parent() != d->mModel->rootItem()) {
1508 ensureDisplayedWithParentsExpanded(it);
1526 Item *it = previousMessageItem(messageTypeFilter, loop);
1531 if (it->
parent() != d->mModel->rootItem()) {
1532 ensureDisplayedWithParentsExpanded(it);
1548void View::selectFocusedMessageItem(
bool centerItem)
1555 if (selectionModel()->isSelected(idx)) {
1568 if (!storageModel()) {
1572 Item *it = firstMessageItem(messageTypeFilter);
1577 Q_ASSERT(it != d->mModel->rootItem());
1579 ensureDisplayedWithParentsExpanded(it);
1585 setCurrentIndex(idx);
1596 if (!storageModel()) {
1600 Item *it = lastMessageItem(messageTypeFilter);
1605 Q_ASSERT(it != d->mModel->rootItem());
1607 ensureDisplayedWithParentsExpanded(it);
1613 setCurrentIndex(idx);
1622void View::modelFinishedLoading()
1624 Q_ASSERT(storageModel());
1625 Q_ASSERT(!d->mModel->isLoading());
1632 return d->mModel->createPersistentSet(items);
1637 return d->mModel->persistentSetCurrentMessageItemList(ref);
1640void View::deletePersistentSet(MessageItemSetReference ref)
1642 d->mModel->deletePersistentSet(ref);
1648 for (
const auto mi : items) {
1649 if (mi->isValid()) {
1650 mi->setAboutToBeRemoved(
false);
1654 viewport()->update();
1694 bool clearingEntireSelection =
true;
1696 const QModelIndexList selectedIndexes = selectionModel()->selectedRows(0);
1698 if (selectedIndexes.count() > items.
count()) {
1700 clearingEntireSelection =
false;
1703 for (
const QModelIndex &selectedIndex : selectedIndexes) {
1704 Q_ASSERT(selectedIndex.isValid());
1705 Q_ASSERT(selectedIndex.column() == 0);
1707 Item *selectedItem =
static_cast<Item *
>(selectedIndex.internalPointer());
1708 Q_ASSERT(selectedItem);
1710 if (selectedItem->
type() != Item::Message) {
1717 clearingEntireSelection =
false;
1723 if (clearingEntireSelection) {
1734 int maxAttempts = items.
count();
1736 while (items.
contains(aMessage) && (maxAttempts > 0)) {
1737 Item *next = messageItemAfter(aMessage, MessageTypeAny,
false);
1743 Q_ASSERT(next->type() == Item::Message);
1750 aMessage = items.
first();
1752 maxAttempts = items.
count();
1754 while (items.
contains(aMessage) && (maxAttempts > 0)) {
1755 Item *prev = messageItemBefore(aMessage, MessageTypeAny,
false);
1761 Q_ASSERT(prev->
type() == Item::Message);
1768 QModelIndex aMessageIndex = d->mModel->index(aMessage, 0);
1769 Q_ASSERT(aMessageIndex.
isValid());
1771 Q_ASSERT(!selectionModel()->isSelected(aMessageIndex));
1772 setCurrentIndex(aMessageIndex);
1779 for (
const auto mi : items) {
1780 mi->setAboutToBeRemoved(
true);
1784 if (selectionModel()->isSelected(idx)) {
1789 viewport()->update();
1792void View::ensureDisplayedWithParentsExpanded(
Item *it)
1814 if (!isExpanded(idx)) {
1815 setExpanded(idx,
true);
1822bool View::isDisplayedWithParentsExpanded(
Item *it)
const
1851 if (it == d->mModel->rootItem()) {
1857 if (!isExpanded(d->mModel->index(it, 0))) {
1868bool View::isThreaded()
const
1870 if (!d->mAggregation) {
1882 d->mLastCurrentItem =
nullptr;
1883 d->mWidget->viewMessageSelected(
nullptr);
1884 d->mWidget->viewSelectionChanged();
1888 if (!selectionModel()->isSelected(current)) {
1889 if (selectedIndexes().count() < 1) {
1906 switch (it->
type()) {
1908 if (d->mLastCurrentItem != it) {
1909 qCDebug(MESSAGELIST_LOG) <<
"View message selected [" <<
static_cast<MessageItem *
>(it)->subject() <<
"]";
1910 d->mWidget->viewMessageSelected(
static_cast<MessageItem *
>(it));
1911 d->mLastCurrentItem = it;
1914 case Item::GroupHeader:
1915 if (d->mLastCurrentItem) {
1916 d->mWidget->viewMessageSelected(
nullptr);
1917 d->mLastCurrentItem =
nullptr;
1926 d->mWidget->viewSelectionChanged();
1932 if (!d->mDelegate->hitTest(e->
pos(),
true)) {
1938 Item *it =
static_cast<Item *
>(d->mDelegate->hitItem());
1943 switch (it->
type()) {
1951 if (d->mDelegate->hitContentItem()) {
1953 if (d->mDelegate->hitContentItem()->isIcon() && d->mDelegate->hitContentItem()->isClickable()) {
1958 d->mWidget->viewMessageActivated(
static_cast<MessageItem *
>(it));
1965 case Item::GroupHeader:
1971 setExpanded(d->mDelegate->hitIndex(), !isExpanded(d->mDelegate->hitIndex()));
1986void View::changeMessageStatusRead(
MessageItem *it,
bool read)
1997 viewport()->update();
2002 d->mWidget->viewMessageStatusChangeRequest(it, set, unset);
2024 viewport()->update();
2029 d->mWidget->viewMessageStatusChangeRequest(it, set, unset);
2034 d->mMousePressed =
true;
2035 d->mLastMouseSource = e->
source();
2037 if (d->mIsTouchEvent) {
2046 if (d->mIsTouchEvent && !d->mTapAndHoldActive) {
2055 if (d->mMousePressPosition.isNull()) {
2063 d->mTapAndHoldActive =
false;
2064 if (d->mRubberBand->isVisible()) {
2065 d->mRubberBand->hide();
2068 d->mWidget->viewStartDragRequest();
2077 QRect indexRect = this->visualRect(index);
2081 if (indexRect.
bottom() > viewport()->height()) {
2082 if (indexRect.
top() <= viewport()->height()) {
2092 if (item->type() == Item::GroupHeader) {
2093 d->mWidget->viewGroupHeaderContextPopupRequest(
static_cast< GroupHeaderItem *
>(item), viewport()->mapToGlobal(pos));
2094 }
else if (!selectionEmpty()) {
2095 d->mWidget->viewMessageListContextPopupRequest(selectionAsMessageItemList(), viewport()->mapToGlobal(pos));
2106 d->mWidget->viewDragEnterEvent(e);
2111 d->mWidget->viewDragMoveEvent(e);
2116 d->mWidget->viewDropEvent(e);
2121 switch (e->
type()) {
2123 d->mDelegate->generalFontChanged();
2131 setTheme(d->mTheme);
2147 d->mIsTouchEvent =
true;
2148 d->mMousePressed =
false;
2164 if (!MessageListSettings::self()->messageToolTipEnabled()) {
2173 QPoint pnt = viewport()->mapFromGlobal(mapToGlobal(he->pos()));
2189 Q_ASSERT(storageModel());
2193 QColor darkerColor(((bckColor.
red() * 8) + (txtColor.
red() * 2)) / 10,
2194 ((bckColor.
green() * 8) + (txtColor.
green() * 2)) / 10,
2195 ((bckColor.
blue() * 8) + (txtColor.
blue() * 2)) / 10);
2201 const QString textDirection = textIsLeftToRight ? QStringLiteral(
"left") : QStringLiteral(
"right");
2203 QString tip = QStringLiteral(
"<table width=\"100%\" border=\"0\" cellpadding=\"2\" cellspacing=\"0\">");
2205 switch (it->
type()) {
2206 case Item::Message: {
2209 tip += QStringLiteral(
2211 "<td bgcolor=\"%1\" align=\"%4\" valign=\"middle\">"
2212 "<div style=\"color: %2; font-weight: bold;\">"
2217 .
arg(txtColorName, bckColorName, mi->subject().
toHtmlEscaped(), textDirection);
2221 "<td align=\"center\" valign=\"middle\">"
2222 "<table width=\"100%\" border=\"0\" cellpadding=\"2\" cellspacing=\"0\">");
2224 const QString htmlCodeForStandardRow = QStringLiteral(
2226 "<td align=\"right\" valign=\"top\" width=\"45\">"
2227 "<div style=\"font-weight: bold;\"><nobr>"
2231 "<td align=\"left\" valign=\"top\">"
2236 if (textIsLeftToRight) {
2238 tip += htmlCodeForStandardRow.
arg(
i18nc(
"Receiver of the email",
"To"), mi->displayReceiver().
toHtmlEscaped());
2239 tip += htmlCodeForStandardRow.
arg(
i18n(
"Date"), mi->formattedDate());
2241 tip += htmlCodeForStandardRow.
arg(mi->displaySender().toHtmlEscaped(),
i18n(
"From"));
2242 tip += htmlCodeForStandardRow.
arg(mi->displayReceiver().toHtmlEscaped(),
i18nc(
"Receiver of the email",
"To"));
2243 tip += htmlCodeForStandardRow.
arg(mi->formattedDate(),
i18n(
"Date"));
2247 const QString tags = mi->tagListDescription();
2255 if (textIsLeftToRight) {
2257 tip += htmlCodeForStandardRow.
arg(
i18n(
"Size"), mi->formattedSize());
2258 tip += htmlCodeForStandardRow.
arg(
i18n(
"Folder"), mi->folder());
2261 tip += htmlCodeForStandardRow.
arg(mi->formattedSize(),
i18n(
"Size"));
2262 tip += htmlCodeForStandardRow.
arg(mi->folder(),
i18n(
"Folder"));
2265 QString content = MessageList::Util::contentSummary(mi->akonadiItem());
2267 if (textIsLeftToRight) {
2281 if (mi->hasChildren()) {
2283 mi->childItemStats(stats);
2287 statsText =
i18np(
"<b>%1</b> reply",
"<b>%1</b> replies", mi->childItemCount());
2290 statsText +=
i18np(
"<b>%1</b> message in subtree (<b>%2</b> unread)",
2291 "<b>%1</b> messages in subtree (<b>%2</b> unread)",
2292 stats.mTotalChildCount,
2293 stats.mUnreadChildCount);
2295 tip += QStringLiteral(
2297 "<td bgcolor=\"%1\" align=\"%3\" valign=\"middle\">"
2301 .
arg(darkerColorName, statsText, textDirection);
2306 case Item::GroupHeader: {
2307 auto ghi =
static_cast<GroupHeaderItem *
>(it);
2309 tip += QStringLiteral(
2311 "<td bgcolor=\"%1\" align=\"%4\" valign=\"middle\">"
2312 "<div style=\"color: %2; font-weight: bold;\">"
2317 .
arg(txtColorName, bckColorName, ghi->label(), textDirection);
2321 switch (d->mAggregation->grouping()) {
2324 switch (d->mAggregation->threadLeader()) {
2327 description =
i18nc(
"@info:tooltip Formats to something like 'Threads started on 2008-12-21'",
"Threads started on %1", ghi->label());
2329 description =
i18nc(
"@info:tooltip Formats to something like 'Threads started Yesterday'",
"Threads started %1", ghi->label());
2333 description =
i18n(
"Threads with messages dated %1", ghi->label());
2341 if (ghi->label().contains(reg)) {
2342 if (storageModel()->containsOutboundMessages()) {
2343 description =
i18nc(
"@info:tooltip Formats to something like 'Messages sent on 2008-12-21'",
"Messages sent on %1", ghi->label());
2346 i18nc(
"@info:tooltip Formats to something like 'Messages received on 2008-12-21'",
"Messages received on %1", ghi->label());
2349 if (storageModel()->containsOutboundMessages()) {
2350 description =
i18nc(
"@info:tooltip Formats to something like 'Messages sent Yesterday'",
"Messages sent %1", ghi->label());
2352 description =
i18nc(
"@info:tooltip Formats to something like 'Messages received Yesterday'",
"Messages received %1", ghi->label());
2359 switch (d->mAggregation->threadLeader()) {
2361 description =
i18n(
"Threads started within %1", ghi->label());
2364 description =
i18n(
"Threads containing messages with dates within %1", ghi->label());
2371 if (storageModel()->containsOutboundMessages()) {
2372 description =
i18n(
"Messages sent within %1", ghi->label());
2374 description =
i18n(
"Messages received within %1", ghi->label());
2381 switch (d->mAggregation->threadLeader()) {
2383 description =
i18n(
"Threads started by %1", ghi->label());
2386 description =
i18n(
"Threads with most recent message by %1", ghi->label());
2393 if (storageModel()->containsOutboundMessages()) {
2395 description =
i18n(
"Messages sent to %1", ghi->label());
2397 description =
i18n(
"Messages sent by %1", ghi->label());
2400 description =
i18n(
"Messages received from %1", ghi->label());
2406 switch (d->mAggregation->threadLeader()) {
2408 description =
i18n(
"Threads directed to %1", ghi->label());
2411 description =
i18n(
"Threads with most recent message directed to %1", ghi->label());
2418 if (storageModel()->containsOutboundMessages()) {
2419 description =
i18n(
"Messages sent to %1", ghi->label());
2421 description =
i18n(
"Messages received by %1", ghi->label());
2431 tip += QStringLiteral(
2433 "<td align=\"%2\" valign=\"middle\">"
2437 .
arg(description, textDirection);
2440 if (ghi->hasChildren()) {
2442 ghi->childItemStats(stats);
2447 statsText =
i18np(
"<b>%1</b> thread",
"<b>%1</b> threads", ghi->childItemCount());
2452 i18np(
"<b>%1</b> message (<b>%2</b> unread)",
"<b>%1</b> messages (<b>%2</b> unread)", stats.mTotalChildCount, stats.mUnreadChildCount);
2454 tip += QStringLiteral(
2456 "<td bgcolor=\"%1\" align=\"%3\" valign=\"middle\">"
2460 .
arg(darkerColorName, statsText, textDirection);
2477void View::slotExpandAllThreads()
2479 setAllThreadsExpanded(
true);
2482void View::slotCollapseAllThreads()
2484 setAllThreadsExpanded(
false);
2487void View::slotCollapseAllGroups()
2489 setAllGroupsExpanded(
false);
2492void View::slotExpandAllGroups()
2494 setAllGroupsExpanded(
true);
2497void View::slotCollapseCurrentItem()
2499 setCurrentThreadExpanded(
false);
2502void View::slotExpandCurrentItem()
2504 setCurrentThreadExpanded(
true);
2507void View::focusQuickSearch(
const QString &selectedText)
2509 d->mWidget->focusQuickSearch(selectedText);
2514 return d->mWidget->currentFilterStatus();
2519 return d->mWidget->currentOptions();
2524 return d->mWidget->currentFilterSearchString();
2533 const bool currentlyHidden = isRowHidden(row,
parent);
2535 if (currentlyHidden != hide) {
2536 if (currentMessageItem() == rowItem) {
2537 selectionModel()->clear();
2538 selectionModel()->clearSelection();
2546void View::sortOrderMenuAboutToShow(
QMenu *menu)
2548 d->mWidget->sortOrderMenuAboutToShow(menu);
2551void View::aggregationMenuAboutToShow(
QMenu *menu)
2553 d->mWidget->aggregationMenuAboutToShow(menu);
2556void View::themeMenuAboutToShow(
QMenu *menu)
2558 d->mWidget->themeMenuAboutToShow(menu);
2561void View::setCollapseItem(
const QModelIndex &index)
2564 setExpanded(index,
false);
2571 setExpanded(index,
true);
2575void View::setQuickSearchClickMessage(
const QString &msg)
2577 d->mWidget->quickSearch()->setPlaceholderText(msg);
2582 mMousePressPosition =
QPoint();
2585 if (!mDelegate->hitTest(e->
pos(),
true)) {
2591 Item *it =
static_cast<Item *
>(mDelegate->hitItem());
2599 mModel->setPreSelectionMode(PreSelectNone);
2601 switch (it->type()) {
2603 mMousePressPosition = e->
pos();
2609 if (mDelegate->hitContentItem() && (q->selectedIndexes().count() > 1)) {
2610 qCDebug(MESSAGELIST_LOG) <<
"Left hit with selectedIndexes().count() == " << q->selectedIndexes().count();
2612 switch (mDelegate->hitContentItem()->type()) {
2614 q->changeMessageStatus(
static_cast<MessageItem *
>(it),
2620 q->changeMessageStatus(
static_cast<MessageItem *
>(it),
2625 q->changeMessageStatusRead(
static_cast<MessageItem *
>(it), it->status().isRead() ?
false :
true);
2629 q->changeMessageStatus(
static_cast<MessageItem *
>(it),
2630 it->status().isSpam()
2638 q->changeMessageStatus(
static_cast<MessageItem *
>(it),
2639 it->status().isIgnored()
2642 it->status().isIgnored()
2654 q->QTreeView::mousePressEvent(e);
2659 q->QTreeView::mousePressEvent(e);
2661 mWidget->viewMessageListContextPopupRequest(q->selectionAsMessageItemList(), q->viewport()->mapToGlobal(e->
pos()));
2669 case Item::GroupHeader: {
2671 auto groupHeaderItem =
static_cast<GroupHeaderItem *
>(it);
2675 QModelIndex index = mModel->index(groupHeaderItem, 0);
2678 q->setCurrentIndex(index);
2681 if (!mDelegate->hitContentItem()) {
2686 if (groupHeaderItem->childItemCount() > 0) {
2688 q->setExpanded(mDelegate->hitIndex(), !q->isExpanded(mDelegate->hitIndex()));
2694 mWidget->viewGroupHeaderContextPopupRequest(groupHeaderItem, q->viewport()->mapToGlobal(e->
pos()));
2712 tapTriggered(
static_cast<QTapGesture *
>(gesture));
2718 twoFingerTapTriggered(
static_cast<KTwoFingerTap *
>(gesture));
2722void View::ViewPrivate::tapTriggered(
QTapGesture *tap)
2724 static bool scrollerWasScrolling =
false;
2727 mTapAndHoldActive =
false;
2731 scrollerWasScrolling =
true;
2733 scrollerWasScrolling =
false;
2738 mIsTouchEvent =
false;
2742 if (!mMousePressed) {
2746 if (mRubberBand->isVisible()) {
2747 mRubberBand->hide();
2753 q->viewport()->mapToGlobal(tap->
position()),
2758 onPressed(&fakeMousePress);
2759 mTapAndHoldActive =
false;
2763 mIsTouchEvent =
false;
2764 if (mRubberBand->isVisible()) {
2765 mRubberBand->hide();
2767 mTapAndHoldActive =
false;
2776 if (!mMousePressed) {
2786 if (!mIsTouchEvent) {
2790 mTapAndHoldActive =
true;
2794 const QPoint tapViewportPos(q->viewport()->mapFromGlobal(tap->
position().toPoint()));
2796 onPressed(&fakeMousePress);
2798 const QPoint tapIndicatorSize(80, 80);
2800 const QRect tapIndicatorRect(pos - (tapIndicatorSize / 2), pos + (tapIndicatorSize / 2));
2801 mRubberBand->setGeometry(tapIndicatorRect.normalized());
2802 mRubberBand->show();
2806void View::ViewPrivate::twoFingerTapTriggered(
KTwoFingerTap *tap)
2809 if (mTapAndHoldActive) {
2815 if (!mMousePressed) {
2822 q->viewport()->mapToGlobal(tap->
pos()),
2826 onPressed(&fakeMousePress);
2830#include "moc_view.cpp"
void fromQInt32(qint32 status)
static const MessageStatus statusSpam()
void setRead(bool read=true)
static const MessageStatus statusImportant()
static const MessageStatus statusWatched()
static const MessageStatus statusToAct()
static const MessageStatus statusIgnored()
static const MessageStatus statusHam()
A set of aggregation options that can be applied to the MessageList::Model in a single shot.
@ GroupBySender
Group by sender, always.
@ NoGrouping
Don't group messages at all.
@ GroupByDateRange
Use smart (thread leader) date ranges ("Today","Yesterday","Last Week"...)
@ GroupBySenderOrReceiver
Group by sender (incoming) or receiver (outgoing) field.
@ GroupByDate
Group the messages by the date of the thread leader.
@ GroupByReceiver
Group by receiver, always.
@ NoThreading
Perform no threading at all.
@ TopmostMessage
The thread grouping is computed from the topmost message (very similar to least recent,...
@ MostRecentMessage
The thread grouping is computed from the most recent message.
A structure used with MessageList::Item::childItemStats().
A single item of the MessageList tree managed by MessageList::Model.
Item * childItem(int idx) const
Returns the child item at position idx or 0 if idx is out of the allowable range.
const Akonadi::MessageStatus & status() const
Returns the status associated to this Item.
Item * itemAboveChild(Item *child)
Returns the item that is visually above the specified child if this item.
Item * itemAbove()
Returns the item that is visually above this item in the tree.
Type type() const
Returns the type of this item.
void setStatus(Akonadi::MessageStatus status)
Sets the status associated to this Item.
Item * itemBelowChild(Item *child)
Returns the item that is visually below the specified child if this item.
Item * parent() const
Returns the parent Item in the tree, or 0 if this item isn't attached to the tree.
Item * deepestItem()
Returns the deepest item in the subtree originating at this item.
Item * itemBelow()
Returns the item that is visually below this item in the tree.
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.
int childItemCount() const
Returns the number of children of this Item.
QList< Item * > * childItems() const
Return the list of child items.
const QString & subject() const
Returns the subject associated to this Item.
bool isViewable() const
Is this item attached to the viewable root ?
void subTreeToList(QList< MessageItem * > &list)
Appends the whole subtree originating at this item to the specified list.
This class manages the huge tree of displayable objects: GroupHeaderItems and MessageItems.
void statusMessage(const QString &message)
Notify the outside when updating the status bar with a message could be useful.
A class which holds information about sorting, e.g.
MessageSorting messageSorting() const
Returns the current message sorting option.
@ SortMessagesByDateTime
Sort the messages by date and time.
@ SortMessagesByDateTimeOfMostRecent
Sort the messages by date and time of the most recent message in subtree.
SortDirection messageSortDirection() const
Returns the current message SortDirection.
The QAbstractItemModel based interface that you need to provide for your storage to work with Message...
The Column class defines a view column available inside this theme.
void setCurrentWidth(double currentWidth)
Sets the current shared width setting for this column.
bool currentlyVisible() const
Returns the current shared visibility state for this column.
void setCurrentlyVisible(bool currentlyVisible)
Sets the current shared visibility state for this column.
bool containsTextItems() const
Returns true if this column contains text items.
@ ReadStateIcon
The icon that displays the unread/read state (never disabled)
@ WatchedIgnoredStateIcon
The Watched/Ignored state icon.
@ ImportantStateIcon
The Important tag icon.
@ ExpandedStateIcon
The Expanded state icon for group headers.
@ ActionItemStateIcon
The ActionItem state icon.
@ SpamHamStateIcon
The Spam/Ham state icon.
The Theme class defines the visual appearance of the MessageList.
Q_SCRIPTABLE CaptureState status()
QString i18np(const char *singular, const char *plural, const TYPE &arg...)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
The implementation independent part of the MessageList library.
ExistingSelectionBehaviour
This enum is used in the view message selection functions (for instance View::selectNextMessage())
PreSelectionMode
Pre-selection is the action of automatically selecting a message just after the folder has finished l...
MessageTypeFilter
This enum is used in the view message selection functions (for instance View::nextMessageItem()).
virtual bool event(QEvent *event) override
virtual void resizeEvent(QResizeEvent *event) override
void triggered(bool checked)
const QColor & color() const const
QString name(NameFormat format) const const
void setItalic(bool enable)
QGesture * gesture(Qt::GestureType type) const const
Qt::GestureType registerRecognizer(QGestureRecognizer *recognizer)
QModelIndexList indexes() const const
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
bool contains(const AT &value) const const
qsizetype count() const const
bool isEmpty() const const
void reserve(qsizetype size)
void * internalPointer() const const
bool isValid() const const
Qt::MouseEventSource source() const const
const QObjectList & children() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
virtual bool event(QEvent *e)
QObject * parent() const const
const QColor & color(ColorGroup group, ColorRole role) const const
const QBrush & text() const const
QPoint bottomLeft() const const
bool isValid() const const
QPoint topLeft() const const
QString arg(Args &&... args) const const
bool isEmpty() const const
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
QString toHtmlEscaped() const const
QString trimmed() const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void showText(const QPoint &pos, const QString &text, QWidget *w, const QRect &rect, int msecDisplayTime)
virtual void changeEvent(QEvent *event) override
virtual void mouseMoveEvent(QMouseEvent *event) override
virtual void mousePressEvent(QMouseEvent *event) override
virtual void paintEvent(QPaintEvent *event) override
void setRowHidden(int row, const QModelIndex &parent, bool hide)
virtual void updateGeometries() override