File indexing completed on 2024-05-12 15:59:57

0001 /* This file is part of the KDE project
0002    SPDX-FileCopyrightText: 2002 Patrick Julien <freak@codepimps.org>
0003    SPDX-FileCopyrightText: 2007 Jan Hambrecht <jaham@gmx.net>
0004    SPDX-FileCopyrightText: 2007 Sven Langkamp <sven.langkamp@gmail.com>
0005    SPDX-FileCopyrightText: 2011 Srikanth Tiyyagura <srikanth.tulasiram@gmail.com>
0006    SPDX-FileCopyrightText: 2011 José Luis Vergara <pentalis@gmail.com>
0007    SPDX-FileCopyrightText: 2013 Sascha Suelzer <s.suelzer@gmail.com>
0008 
0009    SPDX-License-Identifier: LGPL-2.0-or-later
0010 */
0011 #include "KisResourceItemChooser.h"
0012 
0013 #include <math.h>
0014 
0015 #include <QGridLayout>
0016 #include <QButtonGroup>
0017 #include <QHeaderView>
0018 #include <QAbstractProxyModel>
0019 #include <QLabel>
0020 #include <QScrollArea>
0021 #include <QImage>
0022 #include <QPixmap>
0023 #include <QPainter>
0024 #include <QSplitter>
0025 #include <QToolButton>
0026 #include <QWheelEvent>
0027 #include <QLineEdit>
0028 #include <QComboBox>
0029 #include <QStandardPaths>
0030 
0031 #include <klocalizedstring.h>
0032 
0033 #include <KoIcon.h>
0034 #include <KoFileDialog.h>
0035 #include <KisKineticScroller.h>
0036 #include <KisMimeDatabase.h>
0037 #include "KisPopupButton.h"
0038 
0039 #include <KisResourceModel.h>
0040 #include <KisTagFilterResourceProxyModel.h>
0041 #include <KisResourceLoaderRegistry.h>
0042 
0043 #include "KisResourceItemListView.h"
0044 #include "KisResourceItemDelegate.h"
0045 #include "KisTagFilterWidget.h"
0046 #include "KisTagChooserWidget.h"
0047 #include "KisResourceItemChooserSync.h"
0048 #include "KisResourceTaggingManager.h"
0049 #include <KisResourceUserOperations.h>
0050 
0051 
0052 #include "KisStorageChooserWidget.h"
0053 #include "kis_assert.h"
0054 
0055 
0056 class Q_DECL_HIDDEN KisResourceItemChooser::Private
0057 {
0058 public:
0059     Private(QString _resourceType)
0060         : resourceType(_resourceType)
0061     {}
0062 
0063     QString resourceType;
0064 
0065     KisTagFilterResourceProxyModel *tagFilterProxyModel {0};
0066 
0067     KisResourceTaggingManager *tagManager {0};
0068     KisResourceItemListView *view {0};
0069     QButtonGroup *buttonGroup {0};
0070     KisPopupButton *viewModeButton {0};
0071     KisStorageChooserWidget *storagePopupButton {0};
0072 
0073     QScrollArea *previewScroller {0};
0074     QLabel *previewLabel {0};
0075     QSplitter *splitter {0};
0076     QGridLayout *buttonLayout {0};
0077 
0078     QToolButton *importButton {0};
0079     QToolButton *deleteButton {0};
0080 
0081     bool usePreview {false};
0082     bool tiledPreview {false};
0083     bool grayscalePreview {false};
0084     bool synced {false};
0085     bool updatesBlocked {false};
0086 
0087     QModelIndex savedResourceWhileReset; // Indexes on the proxyModel, not the source resource model
0088 
0089     QList<QAbstractButton*> customButtons;
0090 
0091     KoResourceSP currentResource;
0092 };
0093 
0094 KisResourceItemChooser::KisResourceItemChooser(const QString &resourceType, bool usePreview, QWidget *parent)
0095     : QWidget(parent)
0096     , d(new Private(resourceType))
0097 {
0098     d->splitter = new QSplitter(this);
0099 
0100     d->view = new KisResourceItemListView(this);
0101     d->view->setObjectName("ResourceItemview");
0102     d->view->setStrictSelectionMode(true);
0103 
0104     if (d->resourceType == ResourceType::Gradients) {
0105         d->view->setFixedToolTipThumbnailSize(QSize(256, 64));
0106         d->view->setToolTipShouldRenderCheckers(true);
0107     }
0108     else if (d->resourceType == ResourceType::PaintOpPresets) {
0109         d->view->setFixedToolTipThumbnailSize(QSize(128, 128));
0110     } else if (d->resourceType == ResourceType::Patterns || d->resourceType == ResourceType::Palettes) {
0111         d->view->setToolTipShouldRenderCheckers(false);
0112         d->view->setFixedToolTipThumbnailSize(QSize(256, 256));
0113     }
0114 
0115     d->view->setItemDelegate(new KisResourceItemDelegate(this));
0116     d->view->setSelectionMode(QAbstractItemView::SingleSelection);
0117     d->view->viewport()->installEventFilter(this);
0118 
0119     d->tagFilterProxyModel = new KisTagFilterResourceProxyModel(resourceType, this);
0120     d->view->setModel(d->tagFilterProxyModel);
0121     d->tagFilterProxyModel->sort(Qt::DisplayRole);
0122 
0123     connect(d->tagFilterProxyModel, SIGNAL(afterFilterChanged()), this, SLOT(afterFilterChanged()));
0124 
0125     connect(d->view, SIGNAL(currentResourceChanged(QModelIndex)), this, SLOT(activate(QModelIndex)));
0126     connect(d->view, SIGNAL(currentResourceClicked(QModelIndex)), this, SLOT(clicked(QModelIndex)));
0127     connect(d->view, SIGNAL(contextMenuRequested(QPoint)), this, SLOT(contextMenuRequested(QPoint)));
0128     connect(d->view, SIGNAL(sigSizeChanged()), this, SLOT(updateView()));
0129 
0130     d->splitter->addWidget(d->view);
0131     d->splitter->setStretchFactor(0, 2);
0132 
0133     d->usePreview = usePreview;
0134     if (d->usePreview) {
0135         d->previewScroller = new QScrollArea(this);
0136         d->previewScroller->setWidgetResizable(true);
0137         d->previewScroller->setBackgroundRole(QPalette::Dark);
0138         d->previewScroller->setVisible(true);
0139         d->previewScroller->setAlignment(Qt::AlignCenter);
0140         d->previewLabel = new QLabel(this);
0141         d->previewScroller->setWidget(d->previewLabel);
0142         d->splitter->addWidget(d->previewScroller);
0143 
0144         if (d->splitter->count() == 2) {
0145             d->splitter->setSizes(QList<int>() << 280 << 160);
0146         }
0147 
0148         QScroller* scroller = KisKineticScroller::createPreconfiguredScroller(d->previewScroller);
0149         if (scroller) {
0150             connect(scroller, SIGNAL(stateChanged(QScroller::State)), this, SLOT(slotScrollerStateChanged(QScroller::State)));
0151         }
0152     }
0153 
0154     d->splitter->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
0155     connect(d->splitter, SIGNAL(splitterMoved(int,int)), SIGNAL(splitterMoved()));
0156 
0157     d->buttonGroup = new QButtonGroup(this);
0158     d->buttonGroup->setExclusive(false);
0159 
0160     QGridLayout *layout = new QGridLayout(this);
0161 
0162     d->buttonLayout = new QGridLayout();
0163 
0164     d->importButton = new QToolButton(this);
0165     d->importButton->setToolTip(i18nc("@info:tooltip", "Import resource"));
0166     d->importButton->setAutoRaise(true);
0167     d->importButton->setEnabled(true);
0168     d->buttonGroup->addButton(d->importButton, Button_Import);
0169     d->buttonLayout->addWidget(d->importButton, 0, 0);
0170 
0171     d->deleteButton = new QToolButton(this);
0172     d->deleteButton->setToolTip(i18nc("@info:tooltip", "Delete resource"));
0173     d->deleteButton->setEnabled(false);
0174     d->deleteButton->setAutoRaise(true);
0175     d->buttonGroup->addButton(d->deleteButton, Button_Remove);
0176     d->buttonLayout->addWidget(d->deleteButton, 0, 1);
0177 
0178     connect(d->buttonGroup, SIGNAL(buttonClicked(int)), this, SLOT(slotButtonClicked(int)));
0179 
0180     d->buttonLayout->setColumnStretch(0, 1);
0181     d->buttonLayout->setColumnStretch(1, 1);
0182     d->buttonLayout->setColumnStretch(2, 2);
0183     d->buttonLayout->setSpacing(0);
0184     d->buttonLayout->setMargin(0);
0185 
0186     d->viewModeButton = new KisPopupButton(this);
0187     d->viewModeButton->setVisible(false);
0188     d->viewModeButton->setArrowVisible(false);
0189     d->viewModeButton->setAutoRaise(true);
0190     d->tagManager = new KisResourceTaggingManager(resourceType, d->tagFilterProxyModel, this);
0191 
0192     d->storagePopupButton = new KisStorageChooserWidget(resourceType, this);
0193     d->storagePopupButton->setToolTip(i18n("Storage Resources"));
0194     d->storagePopupButton->setAutoRaise(true);
0195     d->storagePopupButton->setArrowVisible(false);
0196 
0197     layout->addWidget(d->tagManager->tagChooserWidget(), 0, 0);
0198     layout->addWidget(d->viewModeButton, 0, 1);
0199     layout->addWidget(d->storagePopupButton, 0, 2);
0200     layout->addWidget(d->splitter, 1, 0, 1, 3);
0201     layout->addWidget(d->tagManager->tagFilterWidget(), 2, 0, 1, 3);
0202     layout->addLayout(d->buttonLayout, 3, 0, 1, 3);
0203     layout->setMargin(0);
0204     layout->setSpacing(0);
0205 
0206     updateView();
0207 
0208     updateButtonState();
0209     showTaggingBar(false);
0210 }
0211 
0212 KisResourceItemChooser::~KisResourceItemChooser()
0213 {
0214     disconnect();
0215     delete d;
0216 }
0217 
0218 KisTagFilterResourceProxyModel *KisResourceItemChooser::tagFilterModel() const
0219 {
0220     return d->tagFilterProxyModel;
0221 }
0222 
0223 void KisResourceItemChooser::slotButtonClicked(int button)
0224 {
0225     if (button == Button_Import) {
0226         QStringList mimeTypes = KisResourceLoaderRegistry::instance()->mimeTypes(d->resourceType);
0227         KoFileDialog dialog(0, KoFileDialog::OpenFiles, "OpenDocument");
0228         dialog.setMimeTypeFilters(mimeTypes);
0229         dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
0230         dialog.setCaption(i18nc("@title:window", "Choose File to Add"));
0231         Q_FOREACH(const QString &filename, dialog.filenames()) {
0232             if (QFileInfo(filename).exists() && QFileInfo(filename).isReadable()) {
0233 
0234                 KoResourceSP previousResource = this->currentResource();
0235                 KoResourceSP newResource = KisResourceUserOperations::importResourceFileWithUserInput(this, "", d->resourceType, filename);
0236 
0237                 if (previousResource && newResource && !currentResource()) {
0238                     /// We have overridden the currently selected resource and
0239                     /// nothing is selected now
0240                     setCurrentResource(newResource);
0241                 } else if (currentResource() == newResource) {
0242                     /// We have overridden the currently selected resource and
0243                     /// the model has managed to track the selection under it
0244                     /// (that is not possible right now, but can theoretically
0245                     /// happen under some circumstances)
0246                     const QModelIndex index = d->tagFilterProxyModel->indexForResource(newResource);
0247                     updatePreview(index);
0248                 }
0249             }
0250         }
0251         tagFilterModel()->sort(Qt::DisplayRole);
0252     }
0253     else if (button == Button_Remove) {
0254         QModelIndex index = d->view->currentIndex();
0255         if (index.isValid()) {
0256             d->tagFilterProxyModel->setResourceInactive(index);
0257         }
0258         int row = index.row();
0259         int rowMin = --row;
0260         row = qBound(0, rowMin, row);
0261         setCurrentItem(row);
0262         activate(d->tagFilterProxyModel->index(row, index.column()));
0263     }
0264     updateButtonState();
0265 }
0266 
0267 void KisResourceItemChooser::showButtons(bool show)
0268 {
0269     foreach (QAbstractButton * button, d->buttonGroup->buttons()) {
0270         show ? button->show() : button->hide();
0271     }
0272 
0273     Q_FOREACH (QAbstractButton *button, d->customButtons) {
0274         show ? button->show() : button->hide();
0275     }
0276 }
0277 
0278 void KisResourceItemChooser::addCustomButton(QAbstractButton *button, int cell)
0279 {
0280     d->buttonLayout->addWidget(button, 0, cell);
0281     d->buttonLayout->setColumnStretch(2, 1);
0282     d->buttonLayout->setColumnStretch(3, 1);
0283 }
0284 
0285 void KisResourceItemChooser::showTaggingBar(bool show)
0286 {
0287     d->tagManager->showTaggingBar(show);
0288 }
0289 
0290 int KisResourceItemChooser::rowCount() const
0291 {
0292     return d->view->model()->rowCount();
0293 }
0294 
0295 void KisResourceItemChooser::setRowHeight(int rowHeight)
0296 {
0297     d->view->setItemSize(QSize(d->view->gridSize().width(), rowHeight));
0298 }
0299 
0300 void KisResourceItemChooser::setColumnWidth(int columnWidth)
0301 {
0302     d->view->setItemSize(QSize(columnWidth, d->view->gridSize().height()));
0303 }
0304 
0305 void KisResourceItemChooser::setItemDelegate(QAbstractItemDelegate *delegate)
0306 {
0307     d->view->setItemDelegate(delegate);
0308 }
0309 
0310 KoResourceSP KisResourceItemChooser::currentResource(bool includeHidden) const
0311 {
0312     if (includeHidden || d->view->selectionModel()->isSelected(d->view->currentIndex())) {
0313         return d->currentResource;
0314     }
0315 
0316     return nullptr;
0317 }
0318 
0319 void KisResourceItemChooser::setCurrentResource(KoResourceSP resource)
0320 {
0321     // don't update if the change came from the same chooser
0322     if (d->updatesBlocked) {
0323         return;
0324     }
0325     QModelIndex index = d->tagFilterProxyModel->indexForResource(resource);
0326     d->view->setCurrentIndex(index);
0327 
0328     // The resource may currently be filtered out, but we want to be able
0329     // to select it if the filter changes and includes the resource.
0330     // Otherwise, activate() already took care of setting the current resource.
0331     if (!index.isValid()) {
0332         d->currentResource = resource;
0333     }
0334     updatePreview(index);
0335 }
0336 
0337 void KisResourceItemChooser::setPreviewOrientation(Qt::Orientation orientation)
0338 {
0339     d->splitter->setOrientation(orientation);
0340 }
0341 
0342 void KisResourceItemChooser::setPreviewTiled(bool tiled)
0343 {
0344     d->tiledPreview = tiled;
0345 }
0346 
0347 void KisResourceItemChooser::setGrayscalePreview(bool grayscale)
0348 {
0349     d->grayscalePreview = grayscale;
0350 }
0351 
0352 void KisResourceItemChooser::setCurrentItem(int row)
0353 {
0354     QModelIndex index = d->view->model()->index(row, 0);
0355     if (!index.isValid())
0356         return;
0357 
0358     d->view->setCurrentIndex(index);
0359     if (index.isValid()) {
0360         updatePreview(index);
0361     }
0362 }
0363 
0364 void KisResourceItemChooser::activate(const QModelIndex &index)
0365 {
0366     if (!index.isValid())
0367     {
0368         updateButtonState();
0369         return;
0370     }
0371 
0372     KoResourceSP resource = resourceFromModelIndex(index);
0373 
0374     if (resource && resource->valid()) {
0375         if (resource != d->currentResource) {
0376             d->currentResource = resource;
0377             d->updatesBlocked = true;
0378             emit resourceSelected(resource);
0379             d->updatesBlocked = false;
0380         }
0381         updatePreview(index);
0382         updateButtonState();
0383     }
0384 }
0385 
0386 void KisResourceItemChooser::clicked(const QModelIndex &index)
0387 {
0388     Q_UNUSED(index);
0389 
0390     KoResourceSP resource = currentResource();
0391     if (resource) {
0392         emit resourceClicked(resource);
0393     }
0394 }
0395 
0396 void KisResourceItemChooser::updateButtonState()
0397 {
0398     QAbstractButton *removeButton = d->buttonGroup->button(Button_Remove);
0399     if (! removeButton)
0400         return;
0401 
0402     KoResourceSP resource = currentResource();
0403     if (resource) {
0404         removeButton->setEnabled(!resource->permanent());
0405         return;
0406     }
0407     removeButton->setEnabled(false);
0408 }
0409 
0410 void KisResourceItemChooser::updatePreview(const QModelIndex &idx)
0411 {
0412     if (!d->usePreview) return;
0413 
0414     if (!idx.isValid()) {
0415         d->previewLabel->setPixmap(QPixmap());
0416         return;
0417     }
0418 
0419     QImage image = idx.data(Qt::UserRole + KisAbstractResourceModel::Thumbnail).value<QImage>();
0420 
0421     if (image.format() != QImage::Format_RGB32 &&
0422         image.format() != QImage::Format_ARGB32 &&
0423         image.format() != QImage::Format_ARGB32_Premultiplied) {
0424 
0425         image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
0426     }
0427 
0428     if (d->tiledPreview) {
0429         int width = d->previewScroller->width() * 4;
0430         int height = d->previewScroller->height() * 4;
0431         QImage img(width, height, image.format());
0432         QPainter gc(&img);
0433         gc.fillRect(img.rect(), Qt::white);
0434         gc.setPen(Qt::NoPen);
0435         gc.setBrush(QBrush(image));
0436         gc.drawRect(img.rect());
0437         image = img;
0438     }
0439 
0440     // Only convert to grayscale if it is rgb. Otherwise, it's gray already.
0441     if (d->grayscalePreview && !image.isGrayscale()) {
0442         QRgb *pixel = reinterpret_cast<QRgb *>(image.bits());
0443         for (int row = 0; row < image.height(); ++row) {
0444             for (int col = 0; col < image.width(); ++col) {
0445                 const QRgb currentPixel = pixel[row * image.width() + col];
0446                 const int red = qRed(currentPixel);
0447                 const int green = qGreen(currentPixel);
0448                 const int blue = qBlue(currentPixel);
0449                 const int grayValue = (red * 11 + green * 16 + blue * 5) / 32;
0450                 pixel[row * image.width() + col] = qRgb(grayValue, grayValue, grayValue);
0451             }
0452         }
0453     }
0454     d->previewLabel->setPixmap(QPixmap::fromImage(image));
0455 }
0456 
0457 KoResourceSP KisResourceItemChooser::resourceFromModelIndex(const QModelIndex &index) const
0458 {
0459     if (!index.isValid()) {
0460         return 0;
0461     }
0462     KoResourceSP r = d->tagFilterProxyModel->resourceForIndex(index);
0463     return r;
0464 }
0465 
0466 QSize KisResourceItemChooser::viewSize() const
0467 {
0468     return d->view->size();
0469 }
0470 
0471 KisResourceItemListView *KisResourceItemChooser::itemView() const
0472 {
0473     return d->view;
0474 }
0475 
0476 void KisResourceItemChooser::contextMenuRequested(const QPoint &pos)
0477 {
0478     d->tagManager->contextMenuRequested(currentResource(), pos);
0479 }
0480 
0481 void KisResourceItemChooser::setStoragePopupButtonVisible(bool visible)
0482 {
0483     d->storagePopupButton->setVisible(visible);
0484 }
0485 
0486 void KisResourceItemChooser::setViewModeButtonVisible(bool visible)
0487 {
0488     d->viewModeButton->setVisible(visible);
0489 }
0490 
0491 KisPopupButton *KisResourceItemChooser::viewModeButton() const
0492 {
0493     return d->viewModeButton;
0494 }
0495 
0496 void KisResourceItemChooser::setSynced(bool sync)
0497 {
0498     if (d->synced == sync)
0499         return;
0500 
0501     d->synced = sync;
0502     KisResourceItemChooserSync *chooserSync = KisResourceItemChooserSync::instance();
0503     if (sync) {
0504         connect(chooserSync, SIGNAL(baseLengthChanged(int)), SLOT(baseLengthChanged(int)));
0505         baseLengthChanged(chooserSync->baseLength());
0506     } else {
0507         chooserSync->disconnect(this);
0508     }
0509 }
0510 
0511 void KisResourceItemChooser::baseLengthChanged(int length)
0512 {
0513     if (d->synced) {
0514         d->view->setItemSize(QSize(length, length));
0515     }
0516 }
0517 
0518 void KisResourceItemChooser::afterFilterChanged()
0519 {
0520     // Note: Item model reset events silently reset the view's selection model too.
0521     // This currently only covers models resets as part of filter changes.
0522     QModelIndex idx = d->tagFilterProxyModel->indexForResource(d->currentResource);
0523 
0524     if (idx.isValid()) {
0525         d->view->setCurrentIndex(idx);
0526     }
0527 
0528     updateButtonState();
0529 }
0530 
0531 bool KisResourceItemChooser::eventFilter(QObject *object, QEvent *event)
0532 {
0533     if (d->synced && event->type() == QEvent::Wheel) {
0534         KisResourceItemChooserSync *chooserSync = KisResourceItemChooserSync::instance();
0535         QWheelEvent *qwheel = static_cast<QWheelEvent *>(event);
0536         if (qwheel->modifiers() & Qt::ControlModifier) {
0537 
0538             int degrees = qwheel->delta() / 8;
0539             int newBaseLength = chooserSync->baseLength() + degrees / 15 * 10;
0540             chooserSync->setBaseLength(newBaseLength);
0541             return true;
0542         }
0543     }
0544     return QObject::eventFilter(object, event);
0545 }
0546 
0547 
0548 void KisResourceItemChooser::resizeEvent(QResizeEvent *event)
0549 {
0550     QWidget::resizeEvent(event);
0551     updateView();
0552 }
0553 
0554 void KisResourceItemChooser::showEvent(QShowEvent *event)
0555 {
0556     QWidget::showEvent(event);
0557     updateView();
0558 }
0559 
0560 void KisResourceItemChooser::updateView()
0561 {
0562     if (d->synced) {
0563         KisResourceItemChooserSync *chooserSync = KisResourceItemChooserSync::instance();
0564         baseLengthChanged(chooserSync->baseLength());
0565     }
0566 
0567     /// helps to set icons here in case the theme is changed
0568     d->viewModeButton->setIcon(KisIconUtils::loadIcon("view-choose"));
0569     d->importButton->setIcon(koIcon("document-import-16"));
0570     d->deleteButton->setIcon(koIcon("edit-delete"));
0571     d->storagePopupButton->setIcon(koIcon("bundle_archive"));
0572     d->tagManager->tagChooserWidget()->updateIcons();
0573 }