File indexing completed on 2025-01-19 03:56:05

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2009-03-23
0007  * Description : Qt Model for Albums
0008  *
0009  * SPDX-FileCopyrightText: 2008-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0010  * SPDX-FileCopyrightText: 2010      by Andi Clemens <andi dot clemens at gmail dot com>
0011  * SPDX-FileCopyrightText: 2012-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0012  *
0013  * SPDX-License-Identifier: GPL-2.0-or-later
0014  *
0015  * ============================================================ */
0016 
0017 #include "abstractalbummodel_p.h"
0018 
0019 namespace Digikam
0020 {
0021 
0022 class Q_DECL_HIDDEN AbstractCheckableAlbumModel::Private
0023 {
0024 public:
0025 
0026 #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
0027 
0028     explicit Private()
0029         : extraFlags                          (),
0030           rootIsCheckable                     (true),
0031           addExcludeTristate                  (false),
0032           recursive                           (false),
0033           staticVectorContainingCheckStateRole(1, Qt::CheckStateRole)
0034 
0035     {
0036     }
0037 
0038 #else
0039 
0040     explicit Private()
0041         : extraFlags                          (nullptr),
0042           rootIsCheckable                     (true),
0043           addExcludeTristate                  (false),
0044           recursive                           (false),
0045           staticVectorContainingCheckStateRole(1, Qt::CheckStateRole)
0046 
0047     {
0048     }
0049 
0050 #endif
0051 
0052     Qt::ItemFlags                 extraFlags;
0053     bool                          rootIsCheckable;
0054     bool                          addExcludeTristate;
0055     bool                          recursive;
0056     QHash<Album*, Qt::CheckState> checkedAlbums;
0057 
0058     QVector<int> staticVectorContainingCheckStateRole;
0059 };
0060 
0061 AbstractCheckableAlbumModel::AbstractCheckableAlbumModel(Album::Type albumType,
0062                                                          Album* const rootAlbum,
0063                                                          RootAlbumBehavior rootBehavior,
0064                                                          QObject* const parent)
0065     : AbstractCountingAlbumModel(albumType, rootAlbum, rootBehavior, parent),
0066       d(new Private)
0067 {
0068     setup();
0069 }
0070 
0071 AbstractCheckableAlbumModel::~AbstractCheckableAlbumModel()
0072 {
0073     delete d;
0074 }
0075 
0076 void AbstractCheckableAlbumModel::setCheckable(bool isCheckable)
0077 {
0078     if (isCheckable)
0079     {
0080         d->extraFlags |= Qt::ItemIsUserCheckable;
0081     }
0082     else
0083     {
0084         d->extraFlags &= ~Qt::ItemIsUserCheckable;
0085         resetCheckedAlbums();
0086     }
0087 }
0088 
0089 bool AbstractCheckableAlbumModel::isCheckable() const
0090 {
0091     return d->extraFlags & Qt::ItemIsUserCheckable;
0092 }
0093 
0094 void AbstractCheckableAlbumModel::setRootCheckable(bool isCheckable)
0095 {
0096     d->rootIsCheckable = isCheckable;
0097     Album* const root  = rootAlbum();
0098 
0099     if (!d->rootIsCheckable && root)
0100     {
0101         setChecked(root, false);
0102     }
0103 }
0104 
0105 bool AbstractCheckableAlbumModel::rootIsCheckable() const
0106 {
0107     return (d->rootIsCheckable && isCheckable());
0108 }
0109 
0110 void AbstractCheckableAlbumModel::setTristate(bool isTristate)
0111 {
0112     if (isTristate)
0113     {
0114         d->extraFlags |= Qt::ItemIsAutoTristate;
0115     }
0116     else
0117     {
0118         d->extraFlags &= ~Qt::ItemIsAutoTristate;
0119     }
0120 }
0121 
0122 bool AbstractCheckableAlbumModel::isTristate() const
0123 {
0124     return (d->extraFlags & Qt::ItemIsAutoTristate);
0125 }
0126 
0127 void AbstractCheckableAlbumModel::setAddExcludeTristate(bool b)
0128 {
0129     d->addExcludeTristate = b;
0130     setCheckable(true);
0131     setTristate(b);
0132 }
0133 
0134 bool AbstractCheckableAlbumModel::isAddExcludeTristate() const
0135 {
0136     return (d->addExcludeTristate && isTristate());
0137 }
0138 
0139 bool AbstractCheckableAlbumModel::isChecked(Album* album) const
0140 {
0141     return (d->checkedAlbums.value(album, Qt::Unchecked) == Qt::Checked);
0142 }
0143 
0144 Qt::CheckState AbstractCheckableAlbumModel::checkState(Album* album) const
0145 {
0146     return (d->checkedAlbums.value(album, Qt::Unchecked));
0147 }
0148 
0149 void AbstractCheckableAlbumModel::setChecked(Album* album, bool isChecked)
0150 {
0151     setData(indexForAlbum(album), isChecked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole);
0152 }
0153 
0154 void AbstractCheckableAlbumModel::setCheckState(Album* album, Qt::CheckState state)
0155 {
0156     setData(indexForAlbum(album), state, Qt::CheckStateRole);
0157 }
0158 
0159 void AbstractCheckableAlbumModel::toggleChecked(Album* album)
0160 {
0161     if (checkState(album) != Qt::PartiallyChecked)
0162     {
0163         setChecked(album, !isChecked(album));
0164     }
0165 }
0166 
0167 QList<Album*> AbstractCheckableAlbumModel::checkedAlbums() const
0168 {
0169     // return a list with all keys with value Qt::Checked
0170 
0171     return d->checkedAlbums.keys(Qt::Checked);
0172 }
0173 
0174 QList<Album*> AbstractCheckableAlbumModel::partiallyCheckedAlbums() const
0175 {
0176     // return a list with all keys with value Qt::PartiallyChecked
0177 
0178     return d->checkedAlbums.keys(Qt::PartiallyChecked);
0179 }
0180 
0181 void AbstractCheckableAlbumModel::setRecursive(bool recursive)
0182 {
0183     d->recursive = recursive;
0184 }
0185 
0186 void AbstractCheckableAlbumModel::resetAllCheckedAlbums()
0187 {
0188     const QHash<Album*, Qt::CheckState> oldChecked = d->checkedAlbums;
0189     d->checkedAlbums.clear();
0190 
0191     for (QHash<Album*, Qt::CheckState>::const_iterator it = oldChecked.begin() ;
0192          it != oldChecked.end() ; ++it)
0193     {
0194         if (it.value() != Qt::Unchecked)
0195         {
0196             QModelIndex index = indexForAlbum(it.key());
0197             Q_EMIT dataChanged(index, index, d->staticVectorContainingCheckStateRole);
0198             Q_EMIT checkStateChanged(it.key(), Qt::Unchecked);
0199         }
0200     }
0201 }
0202 
0203 void AbstractCheckableAlbumModel::setDataForChildren(const QModelIndex& parent, const QVariant& value, int role)
0204 {
0205     for (int row = 0 ; row < rowCount(parent) ; ++row)
0206     {
0207         QModelIndex childIndex = index(row, 0, parent);
0208         setData(childIndex, value, role, true);
0209     }
0210 }
0211 
0212 void AbstractCheckableAlbumModel::resetCheckedAlbums(const QModelIndex& parent)
0213 {
0214     if (parent == rootAlbumIndex())
0215     {
0216         resetAllCheckedAlbums();
0217         return;
0218     }
0219 
0220     setDataForChildren(parent, Qt::Unchecked, Qt::CheckStateRole);
0221 }
0222 
0223 void AbstractCheckableAlbumModel::setDataForParents(const QModelIndex& child, const QVariant& value, int role)
0224 {
0225     if (!child.isValid())
0226     {
0227         return;
0228     }
0229 
0230     QModelIndex current = parent(child);
0231 
0232     while (current.isValid() && (current != rootAlbumIndex()))
0233     {
0234         setData(current, value, role, false);
0235         current = parent(current);
0236     }
0237 }
0238 
0239 void AbstractCheckableAlbumModel::resetCheckedParentAlbums(const QModelIndex& child)
0240 {
0241     setDataForParents(child, Qt::Unchecked, Qt::CheckStateRole);
0242 }
0243 
0244 void AbstractCheckableAlbumModel::checkAllParentAlbums(const QModelIndex& child)
0245 {
0246     setDataForParents(child, Qt::Checked, Qt::CheckStateRole);
0247 }
0248 
0249 void AbstractCheckableAlbumModel::checkAllAlbums(const QModelIndex& parent)
0250 {
0251     setDataForChildren(parent, Qt::Checked, Qt::CheckStateRole);
0252 }
0253 
0254 void AbstractCheckableAlbumModel::invertCheckedAlbums(const QModelIndex& parent)
0255 {
0256     Album* const album = albumForIndex(parent);
0257 
0258     if (album)
0259     {
0260         toggleChecked(album);
0261     }
0262 
0263     for (int row = 0 ; row < rowCount(parent) ; ++row)
0264     {
0265         invertCheckedAlbums(index(row, 0, parent));
0266     }
0267 }
0268 
0269 void AbstractCheckableAlbumModel::setCheckStateForChildren(Album* album, Qt::CheckState state)
0270 {
0271     QModelIndex index = indexForAlbum(album);
0272     setDataForChildren(index, state, Qt::CheckStateRole);
0273 }
0274 
0275 void AbstractCheckableAlbumModel::setCheckStateForParents(Album* album, Qt::CheckState state)
0276 {
0277     QModelIndex index = indexForAlbum(album);
0278     setDataForParents(index, state, Qt::CheckStateRole);
0279 }
0280 
0281 QVariant AbstractCheckableAlbumModel::albumData(Album* a, int role) const
0282 {
0283     if (role == Qt::CheckStateRole)
0284     {
0285         if ((d->extraFlags & Qt::ItemIsUserCheckable) &&
0286             (!a->isRoot() || d->rootIsCheckable))
0287         {
0288             // with Qt::Unchecked as default, albums not in the hash (initially all)
0289             // are simply regarded as unchecked
0290 
0291             Qt::CheckState state = d->checkedAlbums.value(a, Qt::Unchecked);
0292 
0293             if (d->addExcludeTristate)
0294             {
0295                 // Use Qt::PartiallyChecked only internally, do not expose it to the TreeView
0296 
0297                 return ((state == Qt::Unchecked) ? Qt::Unchecked : Qt::Checked);
0298             }
0299 
0300             return state;
0301         }
0302     }
0303 
0304     return AbstractCountingAlbumModel::albumData(a, role);
0305 }
0306 
0307 void AbstractCheckableAlbumModel::prepareAddExcludeDecoration(Album* a, QPixmap& icon) const
0308 {
0309     if (!d->addExcludeTristate)
0310     {
0311         return;
0312     }
0313 
0314     Qt::CheckState state = checkState(a);
0315 
0316     if (state != Qt::Unchecked)
0317     {
0318         int iconSize     = qMax(icon.width(), icon.height());
0319         int overlay_size = qMin(iconSize, qMax(16, iconSize * 2 / 3));
0320         QPainter p(&icon);
0321         p.drawPixmap((icon.width()  - overlay_size) / 2,
0322                      (icon.height() - overlay_size) / 2,
0323                      QIcon::fromTheme(state == Qt::PartiallyChecked ? QLatin1String("list-remove")
0324                                                                     : QLatin1String("list-add")).pixmap(overlay_size, overlay_size));
0325     }
0326 }
0327 
0328 Qt::ItemFlags AbstractCheckableAlbumModel::flags(const QModelIndex& index) const
0329 {
0330     Qt::ItemFlags extraFlags = d->extraFlags;
0331 
0332     if (!d->rootIsCheckable)
0333     {
0334         QModelIndex root = rootAlbumIndex();
0335 
0336         if (root.isValid() && (index == root))
0337         {
0338             extraFlags &= ~Qt::ItemIsUserCheckable;
0339         }
0340     }
0341 
0342     return AbstractCountingAlbumModel::flags(index) | extraFlags;
0343 }
0344 
0345 bool AbstractCheckableAlbumModel::setData(const QModelIndex& index, const QVariant& value, int role)
0346 {
0347     return setData(index, value, role, d->recursive);
0348 }
0349 
0350 bool AbstractCheckableAlbumModel::setData(const QModelIndex& index, const QVariant& value, int role, bool recursive)
0351 {
0352     if (role == Qt::CheckStateRole)
0353     {
0354         Qt::CheckState state = (Qt::CheckState)value.toInt();
0355         Album* const album   = albumForIndex(index);
0356 
0357         if (!album)
0358         {
0359             return false;
0360         }
0361 /*
0362         qCDebug(DIGIKAM_GENERAL_LOG) << "Updating check state for album" << album->title() << "to" << value;
0363 */
0364         d->checkedAlbums.insert(album, state);
0365         Q_EMIT dataChanged(index, index);
0366         Q_EMIT checkStateChanged(album, state);
0367 
0368         if (recursive)
0369         {
0370             setCheckStateForChildren(album, state);
0371         }
0372 
0373         return true;
0374     }
0375     else
0376     {
0377         return AbstractCountingAlbumModel::setData(index, value, role);
0378     }
0379 }
0380 
0381 void AbstractCheckableAlbumModel::albumCleared(Album* album)
0382 {
0383     // preserve check state if album is only being moved
0384 
0385     if (!AlbumManager::instance()->isMovingAlbum(album))
0386     {
0387         d->checkedAlbums.remove(album);
0388     }
0389 
0390     AbstractCountingAlbumModel::albumCleared(album);
0391 }
0392 
0393 void AbstractCheckableAlbumModel::allAlbumsCleared()
0394 {
0395     d->checkedAlbums.clear();
0396     AbstractCountingAlbumModel::allAlbumsCleared();
0397 }
0398 
0399 } // namespace Digikam