collectionstatisticsdelegate.cpp 12.5 KB
Newer Older
1 2
/*
    Copyright (c) 2008 Thomas McGuire <thomas.mcguire@gmx.net>
3
    Copyright (c) 2012 Laurent Montel <montel@kde.org>
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

    This library is free software; you can redistribute it and/or modify it
    under the terms of the GNU Library General Public License as published by
    the Free Software Foundation; either version 2 of the License, or (at your
    option) any later version.

    This library is distributed in the hope that it will be useful, but WITHOUT
    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
    License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to the
    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301, USA.
*/
20

Allen Winter's avatar
Allen Winter committed
21
#include "collectionstatisticsdelegate.h"
22 23

#include <kcolorscheme.h>
24
#include <KFormat>
Laurent Montel's avatar
Laurent Montel committed
25
#include "akonadiwidgets_debug.h"
26

27 28 29 30 31 32
#include <QPainter>
#include <QStyle>
#include <QStyleOption>
#include <QStyleOptionViewItemV4>
#include <QAbstractItemView>
#include <QTreeView>
33

34 35
#include "entitytreemodel.h"
#include "collectionstatistics.h"
36
#include "collection.h"
37
#include "progressspinnerdelegate_p.h"
38

39 40
using namespace Akonadi;

Laurent Montel's avatar
Laurent Montel committed
41 42
namespace Akonadi
{
43

Guy Maurel's avatar
Guy Maurel committed
44 45 46
enum CountType {
    UnreadCount,
    TotalCount
47 48
};

Thomas McGuire's avatar
Thomas McGuire committed
49
class CollectionStatisticsDelegatePrivate
50
{
Guy Maurel's avatar
Guy Maurel committed
51
public:
52
    QAbstractItemView *parent;
53
    bool drawUnreadAfterFolder;
54
    DelegateAnimator *animator;
55 56
    QColor mSelectedUnreadColor;
    QColor mDeselectedUnreadColor;
57

Guy Maurel's avatar
Guy Maurel committed
58 59 60 61
    CollectionStatisticsDelegatePrivate(QAbstractItemView *treeView)
        : parent(treeView)
        , drawUnreadAfterFolder(false)
        , animator(0)
62
    {
Guy Maurel's avatar
Guy Maurel committed
63
        updateColor();
64 65
    }

Guy Maurel's avatar
Guy Maurel committed
66
    void getCountRecursive(const QModelIndex &index, qint64 &totalCount, qint64 &unreadCount, qint64 &totalSize) const
67
    {
Guy Maurel's avatar
Guy Maurel committed
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
        Collection collection = qvariant_cast<Collection>(index.data(EntityTreeModel::CollectionRole));
        // Do not assert on invalid collections, since a collection may be deleted
        // in the meantime and deleted collections are invalid.
        if (collection.isValid()) {
            CollectionStatistics statistics = collection.statistics();
            totalCount += qMax(0LL, statistics.count());
            unreadCount += qMax(0LL, statistics.unreadCount());
            totalSize += qMax(0LL, statistics.size());
            if (index.model()->hasChildren(index)) {
                const int rowCount = index.model()->rowCount(index);
                for (int row = 0; row < rowCount; row++) {
                    static const int column = 0;
                    getCountRecursive(index.model()->index(row, column, index), totalCount, unreadCount, totalSize);
                }
            }
83
        }
84
    }
85 86 87

    void updateColor()
    {
Guy Maurel's avatar
Guy Maurel committed
88 89 90 91
        mSelectedUnreadColor = KColorScheme(QPalette::Active, KColorScheme::Selection)
                               .foreground(KColorScheme::LinkText).color();
        mDeselectedUnreadColor = KColorScheme(QPalette::Active, KColorScheme::View)
                                 .foreground(KColorScheme::LinkText).color();
92
    }
93 94 95 96
};

}

Guy Maurel's avatar
Guy Maurel committed
97 98 99
CollectionStatisticsDelegate::CollectionStatisticsDelegate(QAbstractItemView *parent)
    : QStyledItemDelegate(parent)
    , d_ptr(new CollectionStatisticsDelegatePrivate(parent))
100 101 102 103
{

}

Guy Maurel's avatar
Guy Maurel committed
104 105 106
CollectionStatisticsDelegate::CollectionStatisticsDelegate(QTreeView *parent)
    : QStyledItemDelegate(parent)
    , d_ptr(new CollectionStatisticsDelegatePrivate(parent))
107
{
108

109 110
}

Thomas McGuire's avatar
Thomas McGuire committed
111
CollectionStatisticsDelegate::~CollectionStatisticsDelegate()
112
{
Guy Maurel's avatar
Guy Maurel committed
113
    delete d_ptr;
114 115
}

Guy Maurel's avatar
Guy Maurel committed
116
void CollectionStatisticsDelegate::setUnreadCountShown(bool enable)
117
{
Guy Maurel's avatar
Guy Maurel committed
118 119
    Q_D(CollectionStatisticsDelegate);
    d->drawUnreadAfterFolder = enable;
120 121
}

122 123
bool CollectionStatisticsDelegate::unreadCountShown() const
{
Guy Maurel's avatar
Guy Maurel committed
124 125
    Q_D(const CollectionStatisticsDelegate);
    return d->drawUnreadAfterFolder;
126 127
}

Guy Maurel's avatar
Guy Maurel committed
128
void CollectionStatisticsDelegate::setProgressAnimationEnabled(bool enable)
129
{
Guy Maurel's avatar
Guy Maurel committed
130 131 132 133 134 135 136 137 138 139 140 141
    Q_D(CollectionStatisticsDelegate);
    if (enable == (d->animator != 0)) {
        return;
    }
    if (enable) {
        Q_ASSERT(!d->animator);
        Akonadi::DelegateAnimator *animator = new Akonadi::DelegateAnimator(d->parent);
        d->animator = animator;
    } else {
        delete d->animator;
        d->animator = 0;
    }
142 143 144 145
}

bool CollectionStatisticsDelegate::progressAnimationEnabled() const
{
Guy Maurel's avatar
Guy Maurel committed
146 147
    Q_D(const CollectionStatisticsDelegate);
    return d->animator != 0;
148 149
}

Guy Maurel's avatar
Guy Maurel committed
150
void CollectionStatisticsDelegate::initStyleOption(QStyleOptionViewItem *option,
Laurent Montel's avatar
Laurent Montel committed
151
        const QModelIndex &index) const
152
{
Guy Maurel's avatar
Guy Maurel committed
153
    Q_D(const CollectionStatisticsDelegate);
154

Guy Maurel's avatar
Guy Maurel committed
155 156 157 158
    QStyleOptionViewItemV4 *noTextOption =
        qstyleoption_cast<QStyleOptionViewItemV4 *>(option);
    QStyledItemDelegate::initStyleOption(noTextOption, index);
    if (option->decorationPosition != QStyleOptionViewItem::Top) {
159 160 161
        if (noTextOption) {
            noTextOption->text.clear();
        }
Guy Maurel's avatar
Guy Maurel committed
162
    }
163

Guy Maurel's avatar
Guy Maurel committed
164
    if (d->animator) {
165

Guy Maurel's avatar
Guy Maurel committed
166 167 168 169 170
        const QVariant fetchState = index.data(Akonadi::EntityTreeModel::FetchStateRole);
        if (!fetchState.isValid() || fetchState.toInt() != Akonadi::EntityTreeModel::FetchingState) {
            d->animator->pop(index);
            return;
        }
171

Guy Maurel's avatar
Guy Maurel committed
172
        d->animator->push(index);
173

Guy Maurel's avatar
Guy Maurel committed
174 175 176
        if (QStyleOptionViewItemV4 *v4 = qstyleoption_cast<QStyleOptionViewItemV4 *>(option)) {
            v4->icon = d->animator->sequenceFrame(index);
        }
177
    }
178 179
}

Thomas McGuire's avatar
Thomas McGuire committed
180 181
class PainterStateSaver
{
Guy Maurel's avatar
Guy Maurel committed
182 183
public:
    PainterStateSaver(QPainter *painter)
Thomas McGuire's avatar
Thomas McGuire committed
184
    {
Guy Maurel's avatar
Guy Maurel committed
185 186
        mPainter = painter;
        mPainter->save();
Thomas McGuire's avatar
Thomas McGuire committed
187 188 189 190
    }

    ~PainterStateSaver()
    {
Guy Maurel's avatar
Guy Maurel committed
191
        mPainter->restore();
Thomas McGuire's avatar
Thomas McGuire committed
192 193
    }

Guy Maurel's avatar
Guy Maurel committed
194
private:
195
    Q_DISABLE_COPY(PainterStateSaver)
Thomas McGuire's avatar
Thomas McGuire committed
196 197 198
    QPainter *mPainter;
};

Guy Maurel's avatar
Guy Maurel committed
199
void CollectionStatisticsDelegate::paint(QPainter *painter,
Laurent Montel's avatar
Laurent Montel committed
200 201
        const QStyleOptionViewItem &option,
        const QModelIndex &index) const
202
{
Guy Maurel's avatar
Guy Maurel committed
203 204 205 206 207 208 209 210
    Q_D(const CollectionStatisticsDelegate);
    PainterStateSaver stateSaver(painter);

    const QColor textColor = index.data(Qt::ForegroundRole).value<QColor>();
    // First, paint the basic, but without the text. We remove the text
    // in initStyleOption(), which gets called by QStyledItemDelegate::paint().
    QStyledItemDelegate::paint(painter, option, index);

211
    // Now, we retrieve the correct style option by calling initStyleOption from
Guy Maurel's avatar
Guy Maurel committed
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
    // the superclass.
    QStyleOptionViewItemV4 option4 = option;
    QStyledItemDelegate::initStyleOption(&option4, index);
    QString text = option4.text;

    // Now calculate the rectangle for the text
    QStyle *s = d->parent->style();
    const QWidget *widget = option4.widget;
    const QRect textRect = s->subElementRect(QStyle::SE_ItemViewItemText, &option4, widget);

    // When checking if the item is expanded, we need to check that for the first
    // column, as Qt only recognises the index as expanded for the first column
    const QModelIndex firstColumn = index.sibling(index.row(), 0);
    QTreeView *treeView = qobject_cast<QTreeView *>(d->parent);
    bool expanded = treeView && treeView->isExpanded(firstColumn);

    if (option.state & QStyle::State_Selected) {
        painter->setPen(textColor.isValid() ? textColor : option.palette.highlightedText().color());
230 231
    } else {
        painter->setPen(textColor.isValid() ? textColor : option.palette.text().color());
232 233
    }

Guy Maurel's avatar
Guy Maurel committed
234
    Collection collection = firstColumn.data(EntityTreeModel::CollectionRole).value<Collection>();
235

Guy Maurel's avatar
Guy Maurel committed
236
    if (!collection.isValid()) {
237
        qCCritical(AKONADIWIDGETS_LOG) << "Invalid collection: " << collection;
238
        return;
239 240
    }

Guy Maurel's avatar
Guy Maurel committed
241 242 243 244 245 246 247 248 249 250 251 252 253 254
    CollectionStatistics statistics = collection.statistics();

    qint64 unreadCount = qMax(0LL, statistics.unreadCount());
    qint64 totalRecursiveCount = 0;
    qint64 unreadRecursiveCount = 0;
    qint64 totalSize = 0;
    bool needRecursiveCounts = false;
    bool needTotalSize = false;
    if (d->drawUnreadAfterFolder && index.column() == 0) {
        needRecursiveCounts = true;
    } else if ((index.column() == 1 || index.column() == 2)) {
        needRecursiveCounts = true;
    } else if (index.column() == 3 && !expanded) {
        needTotalSize = true;
255
    }
256

Guy Maurel's avatar
Guy Maurel committed
257 258 259 260 261 262 263 264 265
    if (needRecursiveCounts || needTotalSize) {
        d->getCountRecursive(firstColumn, totalRecursiveCount, unreadRecursiveCount, totalSize);
    }

    // Draw the unread count after the folder name (in parenthesis)
    if (d->drawUnreadAfterFolder && index.column() == 0) {
        // Construct the string which will appear after the foldername (with the
        // unread count)
        QString unread;
Laurent Montel's avatar
Laurent Montel committed
266
//     qCDebug(AKONADIWIDGETS_LOG) << expanded << unreadCount << unreadRecursiveCount;
Guy Maurel's avatar
Guy Maurel committed
267
        if (expanded && unreadCount > 0) {
268
            unread = QStringLiteral(" (%1)").arg(unreadCount);
Guy Maurel's avatar
Guy Maurel committed
269 270
        } else if (!expanded) {
            if (unreadCount != unreadRecursiveCount) {
271
                unread = QStringLiteral(" (%1 + %2)").arg(unreadCount).arg(unreadRecursiveCount - unreadCount);
Guy Maurel's avatar
Guy Maurel committed
272
            } else if (unreadCount > 0) {
273
                unread = QStringLiteral(" (%1)").arg(unreadCount);
Guy Maurel's avatar
Guy Maurel committed
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
            }
        }

        PainterStateSaver stateSaver(painter);

        if (!unread.isEmpty()) {
            QFont font = painter->font();
            font.setBold(true);
            painter->setFont(font);
        }

        const QColor unreadColor = (option.state & QStyle::State_Selected) ? d->mSelectedUnreadColor : d->mDeselectedUnreadColor;
        const QRect iconRect = s->subElementRect(QStyle::SE_ItemViewItemDecoration, &option4, widget);

        if (option.decorationPosition == QStyleOptionViewItem::Left ||
Laurent Montel's avatar
Laurent Montel committed
289
                option.decorationPosition == QStyleOptionViewItem::Right) {
Guy Maurel's avatar
Guy Maurel committed
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
            // Squeeze the folder text if it is to big and calculate the rectangles
            // where the folder text and the unread count will be drawn to
            QString folderName = text;
            QFontMetrics fm(painter->fontMetrics());
            const int unreadWidth = fm.width(unread);
            int folderWidth(fm.width(folderName));
            const bool enoughPlaceForText = (option.rect.width() > (folderWidth + unreadWidth + iconRect.width()));

            if (!enoughPlaceForText && (folderWidth + unreadWidth > textRect.width())) {
                folderName = fm.elidedText(folderName, Qt::ElideRight,
                                           option.rect.width() - unreadWidth - iconRect.width());
                folderWidth = fm.width(folderName);
            }
            QRect folderRect = textRect;
            QRect unreadRect = textRect;
            folderRect.setRight(textRect.left() + folderWidth);
            unreadRect = QRect(folderRect.right(), folderRect.top(), unreadRect.width(), unreadRect.height());

            // Draw folder name and unread count
            painter->drawText(folderRect, Qt::AlignLeft | Qt::AlignVCenter, folderName);
            painter->setPen(unreadColor);
            painter->drawText(unreadRect, Qt::AlignLeft | Qt::AlignVCenter, unread);
        } else if (option.decorationPosition == QStyleOptionViewItem::Top) {
            if (unreadCount > 0) {
                // draw over the icon
                painter->setPen(unreadColor);
                painter->drawText(iconRect, Qt::AlignCenter, QString::number(unreadCount));
            }
        }
        return;
320 321
    }

Guy Maurel's avatar
Guy Maurel committed
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
    // For the unread/total column, paint the summed up count if the item
    // is collapsed
    if ((index.column() == 1 || index.column() == 2)) {

        QFont savedFont = painter->font();
        QString sumText;
        if (index.column() == 1 && ((!expanded && unreadRecursiveCount > 0) || (expanded && unreadCount > 0))) {
            QFont font = painter->font();
            font.setBold(true);
            painter->setFont(font);
            sumText = QString::number(expanded ? unreadCount : unreadRecursiveCount);
        } else {

            qint64 totalCount = statistics.count();
            if (index.column() == 2 && ((!expanded && totalRecursiveCount > 0) || (expanded && totalCount > 0))) {
                sumText = QString::number(expanded ? totalCount : totalRecursiveCount);
            }
        }
340

Guy Maurel's avatar
Guy Maurel committed
341 342 343
        painter->drawText(textRect, Qt::AlignRight | Qt::AlignVCenter, sumText);
        painter->setFont(savedFont);
        return;
344
    }
Guy Maurel's avatar
Guy Maurel committed
345 346 347

    //total size
    if (index.column() == 3 && !expanded) {
348 349
        painter->drawText(textRect, option4.displayAlignment | Qt::AlignVCenter,
                          KFormat().formatByteSize(totalSize));
Guy Maurel's avatar
Guy Maurel committed
350 351 352 353
        return;
    }

    painter->drawText(textRect, option4.displayAlignment | Qt::AlignVCenter, text);
354
}
355

356 357
void CollectionStatisticsDelegate::updatePalette()
{
Guy Maurel's avatar
Guy Maurel committed
358
    Q_D(CollectionStatisticsDelegate);
359 360
    d->updateColor();
}