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

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2009-11-03
0007  * Description : A dialog base class which can handle multiple pages.
0008  *
0009  * SPDX-FileCopyrightText: 2009-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  * SPDX-FileCopyrightText: 2007      by Rafael Fernández López <ereslibre at kde dot org>
0011  * SPDX-FileCopyrightText: 2006      by Tobias Koenig <tokoe at kde dot org>
0012  *
0013  * SPDX-License-Identifier: GPL-2.0-or-later
0014  *
0015  * ============================================================ */
0016 
0017 #include "dconfigdlgview_p.h"
0018 
0019 // Qt includes
0020 
0021 #include <QApplication>
0022 #include <QHeaderView>
0023 #include <QPainter>
0024 #include <QTextLayout>
0025 #include <QVBoxLayout>
0026 
0027 // Local includes
0028 
0029 #include "dconfigdlgmodels.h"
0030 
0031 namespace Digikam
0032 {
0033 
0034 using namespace DConfigDlgInternal;
0035 
0036 DConfigDlgPlainView::DConfigDlgPlainView(QWidget* const parent)
0037     : QAbstractItemView(parent)
0038 {
0039     hide();
0040 }
0041 
0042 QModelIndex DConfigDlgPlainView::indexAt(const QPoint&) const
0043 {
0044     return QModelIndex();
0045 }
0046 
0047 void DConfigDlgPlainView::scrollTo(const QModelIndex&, ScrollHint)
0048 {
0049 }
0050 
0051 QRect DConfigDlgPlainView::visualRect(const QModelIndex&) const
0052 {
0053     return QRect();
0054 }
0055 
0056 QModelIndex DConfigDlgPlainView::moveCursor(QAbstractItemView::CursorAction, Qt::KeyboardModifiers)
0057 {
0058     return QModelIndex();
0059 }
0060 
0061 int DConfigDlgPlainView::horizontalOffset() const
0062 {
0063     return 0;
0064 }
0065 
0066 int DConfigDlgPlainView::verticalOffset() const
0067 {
0068     return 0;
0069 }
0070 
0071 bool DConfigDlgPlainView::isIndexHidden(const QModelIndex&) const
0072 {
0073     return false;
0074 }
0075 
0076 void DConfigDlgPlainView::setSelection(const QRect&, QFlags<QItemSelectionModel::SelectionFlag>)
0077 {
0078 }
0079 
0080 QRegion DConfigDlgPlainView::visualRegionForSelection(const QItemSelection&) const
0081 {
0082     return QRegion();
0083 }
0084 
0085 // ------------------------------------------------------------------------------------------------------
0086 
0087 DConfigDlgListView::DConfigDlgListView(QWidget* const parent)
0088     : QListView(parent)
0089 {
0090     setViewMode(QListView::ListMode);
0091     setMovement(QListView::Static);
0092     setVerticalScrollMode(QListView::ScrollPerPixel);
0093 
0094     QFont boldFont(font());
0095     boldFont.setBold(true);
0096     setFont(boldFont);
0097 
0098     setItemDelegate(new DConfigDlgListViewDelegate(this));
0099 }
0100 
0101 DConfigDlgListView::~DConfigDlgListView()
0102 {
0103 }
0104 
0105 void DConfigDlgListView::setModel(QAbstractItemModel* model)
0106 {
0107 /*
0108       DConfigDlgListViewProxy* const proxy = new DConfigDlgListViewProxy( this );
0109       proxy->setSourceModel( model );
0110       proxy->rebuildMap();
0111 
0112       connect(model, SIGNAL(layoutChanged()),
0113               proxy, SLOT(rebuildMap()) );
0114 */
0115 
0116     connect(model, &QAbstractItemModel::layoutChanged,
0117             this, &DConfigDlgListView::updateWidth);
0118 
0119 /*
0120     QListView::setModel(proxy);
0121 */
0122     QListView::setModel(model);
0123 
0124     // Set our own selection model, which won't allow our current selection to be cleared
0125 
0126     setSelectionModel(new DConfigDlgInternal::SelectionModel(model, this));
0127 
0128     updateWidth();
0129 }
0130 
0131 void DConfigDlgListView::updateWidth()
0132 {
0133     if (!model())
0134     {
0135         return;
0136     }
0137 
0138     int rows = model()->rowCount();
0139 
0140     int width = 0;
0141 
0142     for (int i = 0 ; i < rows ; ++i)
0143     {
0144         width = qMax(width, sizeHintForIndex(model()->index(i, 0)).width());
0145     }
0146 
0147     setFixedWidth(width + 25);
0148 }
0149 
0150 // -------------------------------------------------------------------------------------------
0151 
0152 DConfigDlgTreeView::DConfigDlgTreeView(QWidget* const parent)
0153     : QTreeView(parent)
0154 {
0155     setUniformRowHeights(true);
0156     header()->hide();
0157 }
0158 
0159 void DConfigDlgTreeView::setModel(QAbstractItemModel* model)
0160 {
0161     connect(model, &QAbstractItemModel::layoutChanged,
0162             this, &DConfigDlgTreeView::updateWidth);
0163 
0164     QTreeView::setModel(model);
0165 
0166     // Set our own selection model, which won't allow our current selection to be cleared
0167 
0168     setSelectionModel(new DConfigDlgInternal::SelectionModel(model, this));
0169 
0170     updateWidth();
0171 }
0172 
0173 void DConfigDlgTreeView::updateWidth()
0174 {
0175     if (!model())
0176     {
0177         return;
0178     }
0179 
0180     int columns = model()->columnCount();
0181 
0182     expandItems();
0183 
0184     int width = 0;
0185 
0186     for (int i = 0 ; i < columns ; ++i)
0187     {
0188         resizeColumnToContents(i);
0189         width = qMax(width, sizeHintForColumn(i));
0190     }
0191 
0192     setFixedWidth(width + 25);
0193 }
0194 
0195 void DConfigDlgTreeView::expandItems(const QModelIndex& index)
0196 {
0197     setExpanded(index, true);
0198 
0199     const int count = model()->rowCount(index);
0200 
0201     for (int i = 0 ; i < count ; ++i)
0202     {
0203         expandItems(model()->index(i, 0, index));
0204     }
0205 }
0206 
0207 // ---------------------------------------------------------------------------
0208 
0209 DConfigDlgTabbedView::DConfigDlgTabbedView(QWidget* const parent)
0210     : QAbstractItemView(parent)
0211 {
0212     // hide the viewport of the QAbstractScrollArea
0213 
0214     const QList<QWidget *> list = findChildren<QWidget *>();
0215 
0216     for (int i = 0 ; i < list.count() ; ++i)
0217     {
0218         list[i]->hide();
0219     }
0220 
0221     setFrameShape(NoFrame);
0222 
0223     QVBoxLayout* const layout = new QVBoxLayout(this);
0224     layout->setContentsMargins(QMargins());
0225 
0226     mTabWidget = new QTabWidget(this);
0227 
0228     connect(mTabWidget, &QTabWidget::currentChanged, this,
0229             &DConfigDlgTabbedView::currentPageChanged);
0230 
0231     layout->addWidget(mTabWidget);
0232 }
0233 
0234 DConfigDlgTabbedView::~DConfigDlgTabbedView()
0235 {
0236     if (model())
0237     {
0238         for (int i = 0 ; i < mTabWidget->count() ; ++i)
0239         {
0240             QWidget* const page = qvariant_cast<QWidget *>(model()->data(model()->index(i, 0), DConfigDlgModel::WidgetRole));
0241 
0242             if (page)
0243             {
0244                 page->setVisible(false);
0245                 page->setParent(nullptr); // reparent our children before they are deleted
0246             }
0247         }
0248     }
0249 }
0250 
0251 void DConfigDlgTabbedView::setModel(QAbstractItemModel* model)
0252 {
0253     QAbstractItemView::setModel(model);
0254 
0255     connect(model, &QAbstractItemModel::layoutChanged, this,
0256             &DConfigDlgTabbedView::layoutChanged);
0257 
0258     layoutChanged();
0259 }
0260 
0261 QModelIndex DConfigDlgTabbedView::indexAt(const QPoint&) const
0262 {
0263     if (model())
0264     {
0265         return model()->index(0, 0);
0266     }
0267     else
0268     {
0269         return QModelIndex();
0270     }
0271 }
0272 
0273 void DConfigDlgTabbedView::scrollTo(const QModelIndex& index, ScrollHint)
0274 {
0275     if (!index.isValid())
0276     {
0277         return;
0278     }
0279 
0280     mTabWidget->setCurrentIndex(index.row());
0281 }
0282 
0283 QRect DConfigDlgTabbedView::visualRect(const QModelIndex&) const
0284 {
0285     return QRect();
0286 }
0287 
0288 QSize DConfigDlgTabbedView::minimumSizeHint() const
0289 {
0290     return mTabWidget->minimumSizeHint();
0291 }
0292 
0293 QModelIndex DConfigDlgTabbedView::moveCursor(QAbstractItemView::CursorAction, Qt::KeyboardModifiers)
0294 {
0295     return QModelIndex();
0296 }
0297 
0298 int DConfigDlgTabbedView::horizontalOffset() const
0299 {
0300     return 0;
0301 }
0302 
0303 int DConfigDlgTabbedView::verticalOffset() const
0304 {
0305     return 0;
0306 }
0307 
0308 bool DConfigDlgTabbedView::isIndexHidden(const QModelIndex& index) const
0309 {
0310     return (mTabWidget->currentIndex() != index.row());
0311 }
0312 
0313 void DConfigDlgTabbedView::setSelection(const QRect& , QFlags<QItemSelectionModel::SelectionFlag>)
0314 {
0315 }
0316 
0317 QRegion DConfigDlgTabbedView::visualRegionForSelection(const QItemSelection&) const
0318 {
0319     return QRegion();
0320 }
0321 
0322 void DConfigDlgTabbedView::currentPageChanged(int index)
0323 {
0324     if (!model())
0325     {
0326         return;
0327     }
0328 
0329     QModelIndex modelIndex = model()->index(index, 0);
0330 
0331     selectionModel()->setCurrentIndex(modelIndex, QItemSelectionModel::ClearAndSelect);
0332 }
0333 
0334 void DConfigDlgTabbedView::layoutChanged()
0335 {
0336     // save old position
0337 
0338     int pos = mTabWidget->currentIndex();
0339 
0340     // clear tab bar
0341 
0342     int count = mTabWidget->count();
0343 
0344     for (int i = 0 ; i < count ; ++i)
0345     {
0346         mTabWidget->removeTab(0);
0347     }
0348 
0349     if (!model())
0350     {
0351         return;
0352     }
0353 
0354     // add new tabs
0355 
0356     for (int i = 0 ; i < model()->rowCount() ; ++i)
0357     {
0358         const QString title = model()->data(model()->index(i, 0)).toString();
0359         const QIcon icon    = model()->data(model()->index(i, 0), Qt::DecorationRole).value<QIcon>();
0360         QWidget* const page = qvariant_cast<QWidget *>(model()->data(model()->index(i, 0), DConfigDlgModel::WidgetRole));
0361 
0362         if (page)
0363         {
0364             QWidget* const widget     = new QWidget(this);
0365             QVBoxLayout* const layout = new QVBoxLayout(widget);
0366             widget->setLayout(layout);
0367             layout->addWidget(page);
0368             page->setVisible(true);
0369             mTabWidget->addTab(widget, icon, title);
0370         }
0371     }
0372 
0373     mTabWidget->setCurrentIndex(pos);
0374 }
0375 
0376 void DConfigDlgTabbedView::dataChanged(const QModelIndex& index, const QModelIndex&, const QVector<int>& roles)
0377 {
0378     if (!index.isValid())
0379     {
0380         return;
0381     }
0382 
0383     if ((index.row() < 0) || (index.row() >= mTabWidget->count()))
0384     {
0385         return;
0386     }
0387 
0388     if (roles.isEmpty() || roles.contains(Qt::DisplayRole) || roles.contains(Qt::DecorationRole))
0389     {
0390         const QString title = model()->data(index).toString();
0391         const QIcon icon    = model()->data(index, Qt::DecorationRole).value<QIcon>();
0392 
0393         mTabWidget->setTabText(index.row(), title);
0394         mTabWidget->setTabIcon(index.row(), icon);
0395     }
0396 }
0397 
0398 // -----------------------------------------------------------------------------------------------
0399 
0400 DConfigDlgListViewDelegate::DConfigDlgListViewDelegate(QObject* const parent)
0401     : QAbstractItemDelegate(parent)
0402 {
0403 }
0404 
0405 static int layoutText(QTextLayout* layout, int maxWidth)
0406 {
0407     qreal height  = 0;
0408     int textWidth = 0;
0409     layout->beginLayout();
0410 
0411     while (true)
0412     {
0413         QTextLine line = layout->createLine();
0414 
0415         if (!line.isValid())
0416         {
0417             break;
0418         }
0419 
0420         line.setLineWidth(maxWidth);
0421         line.setPosition(QPointF(0, height));
0422         height    += line.height();
0423         textWidth  = qMax(textWidth, qRound(line.naturalTextWidth() + 0.5));
0424     }
0425 
0426     layout->endLayout();
0427     return textWidth;
0428 }
0429 
0430 void DConfigDlgListViewDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
0431 {
0432     if (!index.isValid())
0433     {
0434         return;
0435     }
0436 
0437     QStyleOptionViewItem opt(option);
0438     opt.showDecorationSelected = true;
0439     QStyle* const style        = opt.widget ? opt.widget->style() : QApplication::style();
0440 
0441     int iconSize               = style->pixelMetric(QStyle::PM_IconViewIconSize);
0442     const QString text         = index.model()->data(index, Qt::DisplayRole).toString();
0443     const QIcon icon           = index.model()->data(index, Qt::DecorationRole).value<QIcon>();
0444     const QPixmap pixmap       = icon.pixmap(iconSize, iconSize);
0445 
0446     QFontMetrics fm            = painter->fontMetrics();
0447     int wp                     = pixmap.width()  / pixmap.devicePixelRatio();
0448     int hp                     = pixmap.height() / pixmap.devicePixelRatio();
0449 
0450     QTextLayout iconTextLayout(text, option.font);
0451     QTextOption textOption(Qt::AlignHCenter);
0452     iconTextLayout.setTextOption(textOption);
0453     int maxWidth               = qMax(3 * wp, 8 * fm.height());
0454     layoutText(&iconTextLayout, maxWidth);
0455 
0456     QPen pen                   = painter->pen();
0457     QPalette::ColorGroup cg    = (option.state & QStyle::State_Enabled) ? QPalette::Normal
0458                                                                         : QPalette::Disabled;
0459 
0460     if ((cg == QPalette::Normal) && !(option.state & QStyle::State_Active))
0461     {
0462         cg = QPalette::Inactive;
0463     }
0464 
0465     style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget);
0466 
0467     if (option.state & QStyle::State_Selected)
0468     {
0469         painter->setPen(option.palette.color(cg, QPalette::HighlightedText));
0470     }
0471     else
0472     {
0473         painter->setPen(option.palette.color(cg, QPalette::Text));
0474     }
0475 
0476     painter->drawPixmap(option.rect.x() + (option.rect.width() / 2) - (wp / 2), option.rect.y() + 5, pixmap);
0477 
0478     if (!text.isEmpty())
0479     {
0480         iconTextLayout.draw(painter, QPoint(option.rect.x() + (option.rect.width() / 2) - (maxWidth / 2), option.rect.y() + hp + 7));
0481     }
0482 
0483     painter->setPen(pen);
0484 
0485     drawFocus(painter, option, option.rect);
0486 }
0487 
0488 QSize DConfigDlgListViewDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
0489 {
0490     if (!index.isValid())
0491     {
0492         return QSize(0, 0);
0493     }
0494 
0495     QStyleOptionViewItem opt(option);
0496     opt.showDecorationSelected = true;
0497     QStyle* const style        = opt.widget ? opt.widget->style() : QApplication::style();
0498 
0499     int iconSize               = style->pixelMetric(QStyle::PM_IconViewIconSize);
0500     const QString text         = index.model()->data(index, Qt::DisplayRole).toString();
0501     const QIcon icon           = index.model()->data(index, Qt::DecorationRole).value<QIcon>();
0502     const QPixmap pixmap       = icon.pixmap(iconSize, iconSize);
0503 
0504     QFontMetrics fm            = option.fontMetrics;
0505     int gap                    = fm.height();
0506     int wp                     = pixmap.width() / pixmap.devicePixelRatio();
0507     int hp                     = pixmap.height() / pixmap.devicePixelRatio();
0508 
0509     if (hp == 0)
0510     {
0511         /**
0512          * No pixmap loaded yet, we'll use the default icon size in this case.
0513          */
0514         hp = iconSize;
0515         wp = iconSize;
0516     }
0517 
0518     QTextLayout iconTextLayout(text, option.font);
0519     int wt = layoutText(&iconTextLayout, qMax(3 * wp, 8 * fm.height()));
0520     int ht = iconTextLayout.boundingRect().height();
0521 
0522     int width, height;
0523 
0524     if (text.isEmpty())
0525     {
0526         height = hp;
0527     }
0528     else
0529     {
0530         height = hp + ht + 10;
0531     }
0532 
0533     width = qMax(wt, wp) + gap;
0534 
0535     return QSize(width, height);
0536 }
0537 
0538 void DConfigDlgListViewDelegate::drawFocus(QPainter* painter, const QStyleOptionViewItem& option, const QRect& rect) const
0539 {
0540     if (option.state & QStyle::State_HasFocus)
0541     {
0542         QStyleOptionFocusRect o;
0543 
0544         o.QStyleOption::operator = (option);
0545         o.rect                   = rect;
0546         o.state                 |= QStyle::State_KeyboardFocusChange;
0547         QPalette::ColorGroup cg  = (option.state & QStyle::State_Enabled)
0548                                    ? QPalette::Normal : QPalette::Disabled;
0549         o.backgroundColor        = option.palette.color(cg, (option.state & QStyle::State_Selected)
0550                                    ? QPalette::Highlight : QPalette::Window);
0551 
0552         QApplication::style()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter);
0553     }
0554 }
0555 
0556 // ------------------------------------------------------------------------------------------------------
0557 
0558 DConfigDlgListViewProxy::DConfigDlgListViewProxy(QObject* const parent)
0559     : QAbstractProxyModel(parent)
0560 {
0561 }
0562 
0563 DConfigDlgListViewProxy::~DConfigDlgListViewProxy()
0564 {
0565 }
0566 
0567 int DConfigDlgListViewProxy::rowCount(const QModelIndex&) const
0568 {
0569     return mList.count();
0570 }
0571 
0572 int DConfigDlgListViewProxy::columnCount(const QModelIndex&) const
0573 {
0574     return 1;
0575 }
0576 
0577 QModelIndex DConfigDlgListViewProxy::index(int row, int column, const QModelIndex&) const
0578 {
0579     if ((column > 1) || (row >= mList.count()))
0580     {
0581         return QModelIndex();
0582     }
0583     else
0584     {
0585         return createIndex(row, column, mList[ row ].internalPointer());
0586     }
0587 }
0588 
0589 QModelIndex DConfigDlgListViewProxy::parent(const QModelIndex&) const
0590 {
0591     return QModelIndex();
0592 }
0593 
0594 QVariant DConfigDlgListViewProxy::data(const QModelIndex& index, int role) const
0595 {
0596     if (!index.isValid())
0597     {
0598         return QVariant();
0599     }
0600 
0601     if (index.row() >= mList.count())
0602     {
0603         return QVariant();
0604     }
0605 
0606     return sourceModel()->data(mList[ index.row() ], role);
0607 }
0608 
0609 QModelIndex DConfigDlgListViewProxy::mapFromSource(const QModelIndex& index) const
0610 {
0611     if (!index.isValid())
0612     {
0613         return QModelIndex();
0614     }
0615 
0616     for (int i = 0 ; i < mList.count() ; ++i)
0617     {
0618         if (mList[i] == index)
0619         {
0620             return createIndex(i, 0, index.internalPointer());
0621         }
0622     }
0623 
0624     return QModelIndex();
0625 }
0626 
0627 QModelIndex DConfigDlgListViewProxy::mapToSource(const QModelIndex& index) const
0628 {
0629     if (!index.isValid())
0630     {
0631         return QModelIndex();
0632     }
0633 
0634     return mList[index.row()];
0635 }
0636 
0637 void DConfigDlgListViewProxy::rebuildMap()
0638 {
0639     mList.clear();
0640 
0641     const QAbstractItemModel* model = sourceModel();
0642 
0643     if (!model)
0644     {
0645         return;
0646     }
0647 
0648     for (int i = 0 ; i < model->rowCount() ; ++i)
0649     {
0650         addMapEntry(model->index(i, 0));
0651     }
0652 
0653     for (int i = 0 ; i < mList.count() ; ++i)
0654     {
0655         qDebug("%d:0 -> %d:%d", i, mList[ i ].row(), mList[ i ].column());
0656     }
0657 
0658     Q_EMIT layoutChanged();
0659 }
0660 
0661 void DConfigDlgListViewProxy::addMapEntry(const QModelIndex& index)
0662 {
0663     if (sourceModel()->rowCount(index) == 0)
0664     {
0665         mList.append(index);
0666     }
0667     else
0668     {
0669         const int count = sourceModel()->rowCount(index);
0670 
0671         for (int i = 0 ; i < count ; ++i)
0672         {
0673             addMapEntry(sourceModel()->index(i, 0, index));
0674         }
0675     }
0676 }
0677 
0678 // ---------------------------------------------------------------------------------------
0679 
0680 SelectionModel::SelectionModel(QAbstractItemModel* const model, QObject* const parent)
0681     : QItemSelectionModel(model, parent)
0682 {
0683 }
0684 
0685 void SelectionModel::clear()
0686 {
0687     // Don't allow the current selection to be cleared
0688 }
0689 
0690 void SelectionModel::select(const QModelIndex& index, QItemSelectionModel::SelectionFlags command)
0691 {
0692     // Don't allow the current selection to be cleared
0693 
0694     if (!index.isValid() && (command & QItemSelectionModel::Clear))
0695     {
0696         return;
0697     }
0698 
0699     QItemSelectionModel::select(index, command);
0700 }
0701 
0702 void SelectionModel::select(const QItemSelection& selection, QItemSelectionModel::SelectionFlags command)
0703 {
0704     // Don't allow the current selection to be cleared
0705 
0706     if (!selection.count() && (command & QItemSelectionModel::Clear))
0707     {
0708         return;
0709     }
0710 
0711     QItemSelectionModel::select(selection, command);
0712 }
0713 
0714 } // namespace Digikam
0715 
0716 #include "moc_dconfigdlgview_p.cpp"