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 }