collectionstatisticsdelegate.cpp 12.8 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
#include "collectionstatisticsmodel.h"
23 24

#include <kcolorscheme.h>
Laurent Montel's avatar
Laurent Montel committed
25
#include <qdebug.h>
26
#include <kio/global.h>
27

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

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

40 41 42 43
using namespace Akonadi;

namespace Akonadi {

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 151
void CollectionStatisticsDelegate::initStyleOption(QStyleOptionViewItem *option,
                                                   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 159 160
    QStyleOptionViewItemV4 *noTextOption =
        qstyleoption_cast<QStyleOptionViewItemV4 *>(option);
    QStyledItemDelegate::initStyleOption(noTextOption, index);
    if (option->decorationPosition != QStyleOptionViewItem::Top) {
        noTextOption->text.clear();
    }
161

Guy Maurel's avatar
Guy Maurel committed
162
    if (d->animator) {
163

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

Guy Maurel's avatar
Guy Maurel committed
170
        d->animator->push(index);
171

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

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

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

Guy Maurel's avatar
Guy Maurel committed
192
private:
Thomas McGuire's avatar
Thomas McGuire committed
193 194 195
    QPainter *mPainter;
};

Guy Maurel's avatar
Guy Maurel committed
196 197 198
void CollectionStatisticsDelegate::paint(QPainter *painter,
                                         const QStyleOptionViewItem &option,
                                         const QModelIndex &index) const
199
{
Guy Maurel's avatar
Guy Maurel committed
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
    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);

    // No, we retrieve the correct style option by calling intiStyleOption from
    // 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());
227 228
    }

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

Guy Maurel's avatar
Guy Maurel committed
231
    if (!collection.isValid()) {
232
        qCritical() << "Invalid collection: " << collection;
233 234
    }

Guy Maurel's avatar
Guy Maurel committed
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
    Q_ASSERT(collection.isValid());   // TODO: I seem to hit this when removing a duplicated "Personal Contacts" or "Personal Calendar"

    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;
251
    }
252

Guy Maurel's avatar
Guy Maurel committed
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 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
    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;
//     qDebug() << expanded << unreadCount << unreadRecursiveCount;
        if (expanded && unreadCount > 0) {
            unread = QString::fromLatin1(" (%1)").arg(unreadCount);
        } else if (!expanded) {
            if (unreadCount != unreadRecursiveCount) {
                unread = QString::fromLatin1(" (%1 + %2)").arg(unreadCount).arg(unreadRecursiveCount - unreadCount);
            } else if (unreadCount > 0) {
                unread = QString::fromLatin1(" (%1)").arg(unreadCount);
            }
        }

        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 ||
            option.decorationPosition == QStyleOptionViewItem::Right) {
            // 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());
            if (textColor.isValid()) {
                painter->setPen(textColor);
            }

            // 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;
319 320
    }

Guy Maurel's avatar
Guy Maurel committed
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338
    // 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);
            }
        }
339

Guy Maurel's avatar
Guy Maurel committed
340 341 342
        painter->drawText(textRect, Qt::AlignRight | Qt::AlignVCenter, sumText);
        painter->setFont(savedFont);
        return;
343
    }
Guy Maurel's avatar
Guy Maurel committed
344 345 346 347 348 349 350 351 352 353 354 355 356 357

    //total size
    if (index.column() == 3 && !expanded) {
        if (textColor.isValid()) {
            painter->setPen(textColor);
        }
        painter->drawText(textRect, option4.displayAlignment | Qt::AlignVCenter, KIO::convertSize((KIO::filesize_t)totalSize));
        return;
    }

    if (textColor.isValid()) {
        painter->setPen(textColor);
    }
    painter->drawText(textRect, option4.displayAlignment | Qt::AlignVCenter, text);
358
}
359

360 361
void CollectionStatisticsDelegate::updatePalette()
{
Guy Maurel's avatar
Guy Maurel committed
362
    Q_D(CollectionStatisticsDelegate);
363 364
    d->updateColor();
}