File indexing completed on 2025-01-19 03:50:50

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2013-03-02
0007  * Description : Table view: Tree view subelement
0008  *
0009  * SPDX-FileCopyrightText: 2017-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  * SPDX-FileCopyrightText: 2013      by Michael G. Hansen <mike at mghansen dot de>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 #include "tableview_treeview.h"
0017 
0018 // Qt includes
0019 
0020 #include <QContextMenuEvent>
0021 #include <QHeaderView>
0022 #include <QMenu>
0023 #include <QAction>
0024 
0025 // KDE includes
0026 
0027 #include <klocalizedstring.h>
0028 
0029 // Local includes
0030 
0031 #include "digikam_debug.h"
0032 #include "contextmenuhelper.h"
0033 #include "iteminfo.h"
0034 #include "itemmodel.h"
0035 #include "tableview_column_configuration_dialog.h"
0036 #include "tableview_model.h"
0037 #include "tableview_selection_model_syncer.h"
0038 #include "tableview_treeview_delegate.h"
0039 #include "thumbnailsize.h"
0040 
0041 namespace Digikam
0042 {
0043 
0044 class Q_DECL_HIDDEN TableViewTreeView::Private
0045 {
0046 public:
0047 
0048     explicit Private()
0049       : headerContextMenuActiveColumn         (-1),
0050         actionHeaderContextMenuRemoveColumn   (nullptr),
0051         actionHeaderContextMenuConfigureColumn(nullptr),
0052         dragDropThumbnailSize                 ()
0053     {
0054     }
0055 
0056 public:
0057 
0058     int           headerContextMenuActiveColumn;
0059     QAction*      actionHeaderContextMenuRemoveColumn;
0060     QAction*      actionHeaderContextMenuConfigureColumn;
0061     ThumbnailSize dragDropThumbnailSize;
0062 };
0063 
0064 TableViewTreeView::TableViewTreeView(TableViewShared* const tableViewShared, QWidget* const parent)
0065     : QTreeView(parent),
0066       d        (new Private()),
0067       s        (tableViewShared)
0068 {
0069     setModel(s->tableViewModel);
0070     setSelectionModel(s->tableViewSelectionModel);
0071 
0072     s->itemDelegate = new TableViewItemDelegate(s, this);
0073     setSelectionMode(QAbstractItemView::ExtendedSelection);
0074     setItemDelegate(s->itemDelegate);
0075     setAlternatingRowColors(true);
0076     setAllColumnsShowFocus(true);
0077     setUniformRowHeights(true);
0078     setSortingEnabled(true);
0079     setDragEnabled(true);
0080     setAcceptDrops(true);
0081     setWordWrap(true);
0082 /*
0083     viewport()->setAcceptDrops(true);
0084 */
0085     d->actionHeaderContextMenuRemoveColumn    = new QAction(QIcon::fromTheme(QLatin1String("edit-table-delete-column")), i18n("Remove this column"), this);
0086 
0087     connect(d->actionHeaderContextMenuRemoveColumn, SIGNAL(triggered(bool)),
0088             this, SLOT(slotHeaderContextMenuActionRemoveColumnTriggered()));
0089 
0090     d->actionHeaderContextMenuConfigureColumn = new QAction(QIcon::fromTheme(QLatin1String("configure")), i18n("Configure this column"), this);
0091 
0092     connect(d->actionHeaderContextMenuConfigureColumn, SIGNAL(triggered(bool)),
0093             this, SLOT(slotHeaderContextMenuConfigureColumn()));
0094 
0095     header()->installEventFilter(this);
0096 
0097     slotModelGroupingModeChanged();
0098 
0099     connect(s->tableViewModel, SIGNAL(signalGroupingModeChanged()),
0100             this, SLOT(slotModelGroupingModeChanged()));
0101 }
0102 
0103 TableViewTreeView::~TableViewTreeView()
0104 {
0105 }
0106 
0107 bool TableViewTreeView::eventFilter(QObject* watched, QEvent* event)
0108 {
0109     QHeaderView* const headerView = header();
0110 
0111     if ((watched == headerView) && (event->type() == QEvent::ContextMenu))
0112     {
0113         showHeaderContextMenu(event);
0114 
0115         return true;
0116     }
0117 
0118     return QObject::eventFilter(watched, event);
0119 }
0120 
0121 void TableViewTreeView::addColumnDescriptionsToMenu(const QList<TableViewColumnDescription>& columnDescriptions, QMenu* const menu)
0122 {
0123     for (int i = 0 ; i < columnDescriptions.count() ; ++i)
0124     {
0125         const TableViewColumnDescription& desc = columnDescriptions.at(i);
0126         QAction* const action                  = new QAction(desc.columnTitle, menu);
0127 
0128         if (!desc.columnIcon.isEmpty())
0129         {
0130             action->setIcon(QIcon::fromTheme(desc.columnIcon));
0131         }
0132 
0133         if (desc.subColumns.isEmpty())
0134         {
0135             connect(action, SIGNAL(triggered(bool)),
0136                     this, SLOT(slotHeaderContextMenuAddColumn()));
0137 
0138             action->setData(QVariant::fromValue<TableViewColumnDescription>(desc));
0139         }
0140         else
0141         {
0142             QMenu* const subMenu = new QMenu(menu);
0143             addColumnDescriptionsToMenu(desc.subColumns, subMenu);
0144 
0145             action->setMenu(subMenu);
0146         }
0147 
0148         menu->addAction(action);
0149     }
0150 }
0151 
0152 void TableViewTreeView::showHeaderContextMenu(QEvent* const event)
0153 {
0154     QContextMenuEvent* const e                = static_cast<QContextMenuEvent*>(event);
0155     QHeaderView* const headerView             = header();
0156     d->headerContextMenuActiveColumn          = headerView->logicalIndexAt(e->pos());
0157     const TableViewColumn* const columnObject = s->tableViewModel->getColumnObject(d->headerContextMenuActiveColumn);
0158     QMenu* const menu                         = new QMenu(this);
0159 
0160     d->actionHeaderContextMenuRemoveColumn->setEnabled(s->tableViewModel->columnCount(QModelIndex())>1);
0161     menu->addAction(d->actionHeaderContextMenuRemoveColumn);
0162     const bool columnCanConfigure = columnObject->getColumnFlags().testFlag(TableViewColumn::ColumnHasConfigurationWidget);
0163     d->actionHeaderContextMenuConfigureColumn->setEnabled(columnCanConfigure);
0164     menu->addAction(d->actionHeaderContextMenuConfigureColumn);
0165     menu->addSeparator();
0166 
0167     // add actions for all columns
0168 
0169     QList<TableViewColumnDescription> columnDescriptions = s->columnFactory->getColumnDescriptionList();
0170     addColumnDescriptionsToMenu(columnDescriptions, menu);
0171 
0172     menu->exec(e->globalPos());
0173 }
0174 
0175 void TableViewTreeView::slotHeaderContextMenuAddColumn()
0176 {
0177     QAction* const triggeredAction = qobject_cast<QAction*>(sender());
0178 
0179     const QVariant actionData = triggeredAction->data();
0180 
0181     if (!actionData.canConvert<TableViewColumnDescription>())
0182     {
0183         return;
0184     }
0185 
0186     const TableViewColumnDescription desc = actionData.value<TableViewColumnDescription>();
0187     qCDebug(DIGIKAM_GENERAL_LOG) << "clicked: " << desc.columnTitle;
0188     const int newColumnLogicalIndex       = d->headerContextMenuActiveColumn+1;
0189     s->tableViewModel->addColumnAt(desc, newColumnLogicalIndex);
0190 
0191     // since the header column order is not the same as the model's column order, we need
0192     // to make sure the new column is moved directly behind the current column in the header:
0193 
0194     const int clickedVisualIndex   = header()->visualIndex(d->headerContextMenuActiveColumn);
0195     const int newColumnVisualIndex = header()->visualIndex(newColumnLogicalIndex);
0196     int newColumnVisualTargetIndex = clickedVisualIndex + 1;
0197 
0198     // If the column is inserted before the clicked column, we have to
0199     // subtract one from the target index because it looks like QHeaderView first removes
0200     // the column and then inserts it.
0201 
0202     if (newColumnVisualIndex < clickedVisualIndex)
0203     {
0204         newColumnVisualTargetIndex--;
0205     }
0206 
0207     if (newColumnVisualIndex!=newColumnVisualTargetIndex)
0208     {
0209         header()->moveSection(newColumnVisualIndex, newColumnVisualTargetIndex);
0210     }
0211 
0212     // Ensure that the newly created column is visible.
0213     // This is especially important if the new column is the last one,
0214     // because then it can be outside of the viewport.
0215 
0216     const QModelIndex topIndex = indexAt(QPoint(0, 0));
0217     const QModelIndex targetIndex = s->tableViewModel->index(topIndex.row(), newColumnLogicalIndex, topIndex.parent());
0218     scrollTo(targetIndex, EnsureVisible);
0219 }
0220 
0221 void TableViewTreeView::slotHeaderContextMenuActionRemoveColumnTriggered()
0222 {
0223     qCDebug(DIGIKAM_GENERAL_LOG) << "remove column " << d->headerContextMenuActiveColumn;
0224     s->tableViewModel->removeColumnAt(d->headerContextMenuActiveColumn);
0225 }
0226 
0227 void TableViewTreeView::slotHeaderContextMenuConfigureColumn()
0228 {
0229     TableViewConfigurationDialog* const configurationDialog = new TableViewConfigurationDialog(s, d->headerContextMenuActiveColumn, this);
0230     const int result                                        = configurationDialog->exec();
0231 
0232     if (result!=QDialog::Accepted)
0233     {
0234         return;
0235     }
0236 
0237     const TableViewColumnConfiguration newConfiguration     = configurationDialog->getNewConfiguration();
0238     s->tableViewModel->getColumnObject(d->headerContextMenuActiveColumn)->setConfiguration(newConfiguration);
0239 }
0240 
0241 AbstractItemDragDropHandler* TableViewTreeView::dragDropHandler() const
0242 {
0243     qCDebug(DIGIKAM_GENERAL_LOG)<<s->imageModel->dragDropHandler();
0244 
0245     return s->imageModel->dragDropHandler();
0246 }
0247 
0248 QModelIndex TableViewTreeView::mapIndexForDragDrop(const QModelIndex& index) const
0249 {
0250     // "index" is a TableViewModel index.
0251     // We are using the drag-drop-handler of ItemModel, thus
0252     // we have to convert it to an index of ItemModel.
0253 
0254     // map to ItemModel
0255 
0256     const QModelIndex imageModelIndex = s->tableViewModel->toItemModelIndex(index);
0257 
0258     return imageModelIndex;
0259 }
0260 
0261 QPixmap TableViewTreeView::pixmapForDrag(const QList< QModelIndex >& indexes) const
0262 {
0263     const QModelIndex& firstIndex = indexes.at(0);
0264     const ItemInfo info           = s->tableViewModel->imageInfo(firstIndex);
0265     const QString path            = info.filePath();
0266 
0267     QPixmap thumbnailPixmap;
0268 
0269     /// @todo The first thumbnail load always fails. We have to add thumbnail pre-generation
0270     ///       like in ItemModel. Getting thumbnails from ItemModel does not help, because it
0271     ///       does not necessarily prepare them the same way.
0272     /// @todo Make a central drag-drop thumbnail generator?
0273 
0274     if (!s->thumbnailLoadThread->find(info.thumbnailIdentifier(), thumbnailPixmap, d->dragDropThumbnailSize.size()))
0275     {
0276         /// @todo better default pixmap?
0277 
0278         thumbnailPixmap.fill();
0279     }
0280 
0281     /// @todo Decorate the pixmap like the other drag-drop implementations?
0282     /// @todo Write number of images onto the pixmap
0283 
0284     return thumbnailPixmap;
0285 /*
0286     const QModelIndex& firstIndex      = indexes.at(0);
0287     const QModelIndex& imageModelIndex = s->sortModel->toItemModelIndex(firstIndex);
0288     ItemModel* const imageModel        = s->imageFilterModel->sourceItemModel();
0289 
0290     /// @todo Determine how other views choose the size
0291 
0292     const QSize thumbnailSize(60, 60);
0293 
0294     imageModel->setData(imageModelIndex, qMax(thumbnailSize.width(), thumbnailSize.height()), ItemModel::ThumbnailRole);
0295     QVariant thumbnailData  = imageModel->data(imageModelIndex, ItemModel::ThumbnailRole);
0296     imageModel->setData(imageModelIndex, QVariant(), ItemModel::ThumbnailRole);
0297 
0298     QPixmap thumbnailPixmap = thumbnailData.value<QPixmap>();
0299 
0300     /// @todo Write number of images onto the pixmap
0301 
0302     return thumbnailPixmap;
0303 */
0304 }
0305 
0306 Album* TableViewTreeView::albumAt(const QPoint& pos) const
0307 {
0308     Q_UNUSED(pos)
0309 
0310     ItemAlbumModel* const albumModel = qobject_cast<ItemAlbumModel*>(s->imageModel);
0311 
0312     if (albumModel)
0313     {
0314         if (!(albumModel->currentAlbums().isEmpty()))
0315         {
0316             return albumModel->currentAlbums().constFirst();
0317         }
0318     }
0319 
0320     return nullptr;
0321 }
0322 
0323 void TableViewTreeView::wheelEvent(QWheelEvent* event)
0324 {
0325     if (event->modifiers() & Qt::ControlModifier)
0326     {
0327         const int delta = event->angleDelta().y();
0328 
0329         if      (delta > 0)
0330         {
0331             Q_EMIT signalZoomInStep();
0332         }
0333         else if (delta < 0)
0334         {
0335             Q_EMIT signalZoomOutStep();
0336         }
0337 
0338         event->accept();
0339 
0340         return;
0341     }
0342 
0343     QTreeView::wheelEvent(event);
0344 }
0345 
0346 bool TableViewTreeView::hasHiddenGroupedImages(const ItemInfo& info) const
0347 {
0348         return (
0349                 info.hasGroupedImages() &&
0350                 (
0351                  (s->tableViewModel->groupingMode() == s->tableViewModel->GroupingMode::GroupingHideGrouped)    ||
0352                   ((s->tableViewModel->groupingMode() == s->tableViewModel->GroupingMode::GroupingShowSubItems) &&
0353                    (!s->treeView->isExpanded(s->tableViewModel->indexFromImageId(info.id(), 0)))
0354                  )
0355                 )
0356                );
0357 }
0358 
0359 void TableViewTreeView::slotModelGroupingModeChanged()
0360 {
0361     setRootIsDecorated(s->tableViewModel->groupingMode() == TableViewModel::GroupingShowSubItems);
0362 }
0363 
0364 } // namespace Digikam
0365 
0366 #include "moc_tableview_treeview.cpp"