File indexing completed on 2024-05-12 16:01:53

0001 /*
0002  *  SPDX-FileCopyrightText: 2018 Michael Zhou <simeirxh@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include <QHash>
0008 #include <QString>
0009 #include <QScopedPointer>
0010 #include <QPointer>
0011 #include <QFormLayout>
0012 #include <QCheckBox>
0013 #include <QLineEdit>
0014 #include <QLabel>
0015 #include <QSpinBox>
0016 #include <QComboBox>
0017 #include <QMessageBox>
0018 
0019 #include <KoDialog.h>
0020 #include <KoFileDialog.h>
0021 #include <KoColorSet.h>
0022 #include <KisSwatchGroup.h>
0023 #include <kis_signal_compressor.h>
0024 #include <KisViewManager.h>
0025 #include <KisDocument.h>
0026 #include <KoResourceServer.h>
0027 #include <KisStorageModel.h>
0028 #include <KoResourceServerProvider.h>
0029 #include <KisPaletteModel.h>
0030 #include <kis_color_button.h>
0031 
0032 #include "KisPaletteEditor.h"
0033 #include <KisGlobalResourcesInterface.h>
0034 #include <KisResourceUserOperations.h>
0035 
0036 struct KisPaletteEditor::PaletteInfo {
0037     QString name;
0038     QString filename;
0039     int columnCount;
0040     QString storageLocation;
0041     QHash<QString, KisSwatchGroup> groups;
0042 };
0043 
0044 struct KisPaletteEditor::Private
0045 {
0046     bool isNameModified {false};
0047     bool isColumnCountModified {false};
0048     QSet<QString> modifiedGroupNames; // key is original group name
0049     QSet<QString> newGroupNames;
0050     QSet<QString> keepColorGroups;
0051     QSet<QString> pathsToRemove;
0052     QString groupBeingRenamed;
0053     QPointer<KisPaletteModel> model;
0054     QPointer<KisViewManager> view;
0055     PaletteInfo modified;
0056     QPointer<KoDialog> query;
0057     KoResourceServer<KoColorSet> *rServer {0};
0058 
0059     QPalette normalPalette;
0060     QPalette warnPalette;
0061 
0062 };
0063 
0064 KisPaletteEditor::KisPaletteEditor(QObject *parent)
0065     : QObject(parent)
0066     , m_d(new Private)
0067 {
0068     m_d->rServer = KoResourceServerProvider::instance()->paletteServer();
0069     m_d->warnPalette.setColor(QPalette::Text, Qt::red);
0070 }
0071 
0072 KisPaletteEditor::~KisPaletteEditor()
0073 { }
0074 
0075 void KisPaletteEditor::setPaletteModel(KisPaletteModel *model)
0076 {
0077     if (!model) { return; }
0078     m_d->model = model;
0079     slotPaletteChanged();
0080     connect(model, SIGNAL(sigPaletteChanged()), SLOT(slotPaletteChanged()));
0081     connect(model, SIGNAL(sigPaletteModified()), SLOT(slotSetDocumentModified()));
0082 }
0083 
0084 void KisPaletteEditor::setView(KisViewManager *view)
0085 {
0086     m_d->view = view;
0087 }
0088 
0089 KoColorSetSP KisPaletteEditor::addPalette()
0090 {
0091     if (!m_d->view) { return 0; }
0092     if (!m_d->view->document()) { return 0; }
0093 
0094     KoColorSetSP colorSet(new KoColorSet());
0095 
0096     KoDialog dialog;
0097     QFormLayout *layout = new QFormLayout(dialog.mainWidget());
0098     QLineEdit *le = new QLineEdit(i18nc("Default name for a new palette","New Palette"));
0099     layout->addRow(i18n("New palette name:"), le);
0100 
0101     QString saveLocation = m_d->rServer->saveLocation();
0102 
0103 
0104     QCheckBox *chkSaveInDocument = new QCheckBox(i18n("Save Palette in the Current Document"));
0105     chkSaveInDocument->setChecked(false);
0106     layout->addRow(chkSaveInDocument);
0107 
0108     if (dialog.exec() != QDialog::Accepted) { return 0; }
0109 
0110     QString name = le->text();
0111     colorSet->setPaletteType(KoColorSet::KPL);
0112     colorSet->setValid(true);
0113     colorSet->setName(name);
0114     colorSet->setFilename(name.split(" ").join("_")+colorSet->defaultFileExtension());
0115 
0116     QString resourceLocation = "";
0117     if (chkSaveInDocument->isChecked()) {
0118         resourceLocation = m_d->view->document()->linkedResourcesStorageId();
0119     }
0120 
0121     if (KisResourceUserOperations::addResourceWithUserInput(m_d->view->mainWindowAsQWidget(), colorSet, resourceLocation)) {
0122 
0123         return colorSet;
0124 
0125     }
0126 
0127     return 0;
0128 }
0129 
0130 KoColorSetSP KisPaletteEditor::importPalette()
0131 {
0132     KoFileDialog dialog(nullptr, KoFileDialog::OpenFile, "Open Palette");
0133     dialog.setCaption(i18n("Import Palette"));
0134 
0135     dialog.setDefaultDir(QDir::homePath());
0136     dialog.setMimeTypeFilters(QStringList() << "application/x-krita-palette" << "application/x-gimp-color-palette");
0137 
0138     QString filename = dialog.filename();
0139     if (filename.isEmpty()) {
0140         return nullptr;
0141     }
0142 
0143     QMessageBox messageBox;
0144     messageBox.setText(i18n("Do you want to store this palette in your current image?"));
0145     messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
0146     QString storageLocation = "";
0147     if (messageBox.exec() == QMessageBox::Yes) {
0148         storageLocation = m_d->view->document()->linkedResourcesStorageId();
0149     }
0150     KoResourceSP resource = KisResourceUserOperations::importResourceFileWithUserInput(m_d->view->mainWindowAsQWidget(), storageLocation, ResourceType::Palettes, filename);
0151 
0152     KoColorSetSP palette;
0153     if (resource) {
0154         palette = resource.dynamicCast<KoColorSet>();
0155     }
0156 
0157     return palette;
0158 }
0159 
0160 void KisPaletteEditor::removePalette(KoColorSetSP cs)
0161 {
0162     if (!m_d->view) { return; }
0163     if (!m_d->view->document()) { return; }
0164     if (!cs) { return; }
0165     m_d->rServer->removeResourceFromServer(cs);
0166 }
0167 
0168 int KisPaletteEditor::rowNumberOfGroup(const QString &oriName) const
0169 {
0170     if (!m_d->modified.groups.contains(oriName)) { return 0; }
0171     return m_d->modified.groups[oriName].rowCount();
0172 }
0173 
0174 bool KisPaletteEditor::duplicateExistsGroupName(const QString &name) const
0175 {
0176     if (name == m_d->groupBeingRenamed) { return false; }
0177     Q_FOREACH (const KisSwatchGroup &g, m_d->modified.groups.values()) {
0178         if (name == g.name()) { return true; }
0179     }
0180     return false;
0181 }
0182 
0183 bool KisPaletteEditor::duplicateExistsOriginalGroupName(const QString &name) const
0184 {
0185     return m_d->modified.groups.contains(name);
0186 }
0187 
0188 QString KisPaletteEditor::oldNameFromNewName(const QString &newName) const
0189 {
0190     Q_FOREACH (const QString &oldGroupName, m_d->modified.groups.keys()) {
0191         if (m_d->modified.groups[oldGroupName].name() == newName) {
0192             return oldGroupName;
0193         }
0194     }
0195     return QString();
0196 }
0197 
0198 void KisPaletteEditor::rename(const QString &newName)
0199 {
0200     if (newName.isEmpty()) { return; }
0201     m_d->isNameModified = true;
0202     m_d->modified.name = newName;
0203 }
0204 
0205 void KisPaletteEditor::changeColCount(int newCount)
0206 {
0207     m_d->isColumnCountModified = true;
0208     m_d->modified.columnCount = newCount;
0209 }
0210 
0211 QString KisPaletteEditor::addGroup()
0212 {
0213     KoDialog dialog;
0214     m_d->query = &dialog;
0215 
0216     QVBoxLayout *layout = new QVBoxLayout(dialog.mainWidget());
0217 
0218     layout->addWidget(new QLabel(i18n("New swatch group name:")));
0219     QLineEdit *leName = new QLineEdit(newGroupName());
0220     connect(leName, SIGNAL(textChanged(QString)), SLOT(slotGroupNameChanged(QString)));
0221     layout->addWidget(leName);
0222     layout->addWidget(new QLabel(i18n("Rows of swatches in group:")));
0223     QSpinBox *spxRow = new QSpinBox();
0224     spxRow->setValue(20);
0225     layout->addWidget(spxRow);
0226 
0227     if (dialog.exec() != QDialog::Accepted) { return QString(); }
0228     if (duplicateExistsGroupName(leName->text())) { return QString(); }
0229 
0230     QString realName = leName->text();
0231     QString name = realName;
0232     if (duplicateExistsOriginalGroupName(name)) {
0233         name = newGroupName();
0234     }
0235     m_d->modified.groups[name] = KisSwatchGroup();
0236     KisSwatchGroup &newGroup = m_d->modified.groups[name];
0237     newGroup.setName(realName);
0238     m_d->newGroupNames.insert(name);
0239     newGroup.setRowCount(spxRow->value());
0240     return realName;
0241 }
0242 
0243 bool KisPaletteEditor::removeGroup(const QString &name)
0244 {
0245     KoDialog dialog;
0246     dialog.setWindowTitle(i18nc("@title:dialog", "Removing Swatch Group"));
0247     QFormLayout *editableItems = new QFormLayout(dialog.mainWidget());
0248     QCheckBox *chkKeep = new QCheckBox();
0249 
0250     editableItems->addRow(i18nc("Shows up when deleting a swatch group", "Keep the Colors"), chkKeep);
0251     if (dialog.exec() != KoDialog::Accepted) { return false; }
0252 
0253     m_d->modified.groups.remove(name);
0254     m_d->newGroupNames.remove(name);
0255     if (chkKeep->isChecked()) {
0256         m_d->keepColorGroups.insert(name);
0257     }
0258     return true;
0259 }
0260 
0261 QString KisPaletteEditor::renameGroup(const QString &oldName)
0262 {
0263     if (oldName.isEmpty() || oldName == KoColorSet::GLOBAL_GROUP_NAME) { return QString(); }
0264 
0265     KoDialog dialog;
0266     m_d->query = &dialog;
0267     m_d->groupBeingRenamed = m_d->modified.groups[oldName].name();
0268 
0269     QFormLayout *form = new QFormLayout(dialog.mainWidget());
0270 
0271     QLineEdit *leNewName = new QLineEdit();
0272     connect(leNewName, SIGNAL(textChanged(QString)), SLOT(slotGroupNameChanged(QString)));
0273     leNewName->setText(m_d->modified.groups[oldName].name());
0274 
0275     form->addRow(i18n("New swatch group name:"), leNewName);
0276 
0277     if (dialog.exec() != KoDialog::Accepted) { return QString(); }
0278     if (leNewName->text().isEmpty()) { return QString(); }
0279     if (duplicateExistsGroupName(leNewName->text())) { return QString(); }
0280 
0281     m_d->modified.groups[oldName].setName(leNewName->text());
0282     m_d->modifiedGroupNames.insert(oldName);
0283 
0284     return leNewName->text();
0285 }
0286 
0287 void KisPaletteEditor::slotGroupNameChanged(const QString &newName)
0288 {
0289     QLineEdit *leGroupName = qobject_cast<QLineEdit*>(sender());
0290     if (duplicateExistsGroupName(newName) || newName == QString()) {
0291         leGroupName->setPalette(m_d->warnPalette);
0292         if (m_d->query->button(KoDialog::Ok)) {
0293             m_d->query->button(KoDialog::Ok)->setEnabled(false);
0294         }
0295         return;
0296     }
0297     leGroupName->setPalette(m_d->normalPalette);
0298     if (m_d->query->button(KoDialog::Ok)) {
0299         m_d->query->button(KoDialog::Ok)->setEnabled(true);
0300     }
0301 }
0302 
0303 void KisPaletteEditor::changeGroupRowCount(const QString &name, int newRowCount)
0304 {
0305     if (!m_d->modified.groups.contains(name)) { return; }
0306     m_d->modified.groups[name].setRowCount(newRowCount);
0307     m_d->modifiedGroupNames.insert(name);
0308 }
0309 
0310 void KisPaletteEditor::setStorageLocation(QString location)
0311 {
0312    m_d->modified.storageLocation = location;
0313 }
0314 
0315 void KisPaletteEditor::setEntry(const KoColor &color, const QModelIndex &index)
0316 {
0317     Q_ASSERT(m_d->model);
0318     if (!m_d->view) { return; }
0319     if (!m_d->view->document()) { return; }
0320     KisSwatch c = KisSwatch(color);
0321     c.setId(QString::number(m_d->model->colorSet()->colorCount() + 1));
0322     c.setName(i18nc("Default name for a color swatch","Color %1", QString::number(m_d->model->colorSet()->colorCount()+1)));
0323     m_d->model->setEntry(c, index);
0324 }
0325 
0326 void KisPaletteEditor::slotSetDocumentModified()
0327 {
0328     if (m_d->modified.storageLocation == m_d->view->document()->linkedResourcesStorageId()) {
0329         updatePalette();
0330         KisResourceUserOperations::updateResourceWithUserInput(m_d->view->mainWindowAsQWidget(), m_d->model->colorSet());
0331         m_d->view->document()->setModified(true);
0332     }
0333     m_d->model->colorSet()->setDirty(true);
0334 }
0335 
0336 void KisPaletteEditor::removeEntry(const QModelIndex &index)
0337 {
0338     Q_ASSERT(m_d->model);
0339     if (!m_d->view) { return; }
0340     if (!m_d->view->document()) { return; }
0341     if (qvariant_cast<bool>(index.data(KisPaletteModel::IsGroupNameRole))) {
0342         removeGroup(qvariant_cast<QString>(index.data(KisPaletteModel::GroupNameRole)));
0343     } else {
0344         m_d->model->removeEntry(index, false);
0345     }
0346     updatePalette();
0347 }
0348 
0349 void KisPaletteEditor::modifyEntry(const QModelIndex &index)
0350 {
0351     if (!m_d->view) { return; }
0352     if (!m_d->view->document()) { return; }
0353 
0354     KoDialog dialog;
0355     dialog.setCaption(i18nc("@title:dialog", "Add a new Color Swatch"));
0356     QFormLayout *editableItems = new QFormLayout(dialog.mainWidget());
0357 
0358     QString groupName = qvariant_cast<QString>(index.data(Qt::DisplayRole));
0359     if (qvariant_cast<bool>(index.data(KisPaletteModel::IsGroupNameRole))) {
0360         renameGroup(groupName);
0361         updatePalette();
0362     }
0363     else {
0364 
0365         QLineEdit *lnIDName = new QLineEdit();
0366         QLineEdit *lnGroupName = new QLineEdit();
0367         KisColorButton *bnColor = new KisColorButton();
0368         QCheckBox *chkSpot = new QCheckBox();
0369         chkSpot->setToolTip(i18nc("@info:tooltip", "A spot color is a color that the printer is able to print without mixing the paints it has available to it. The opposite is called a process color."));
0370 
0371         KisSwatch entry = m_d->model->getEntry(index);
0372 
0373         editableItems->addRow(i18n("Swatch ID:"), lnIDName);
0374         editableItems->addRow(i18n("Color swatch name:"), lnGroupName);
0375         editableItems->addRow(i18nc("Color as the Color of a Swatch in a Palette", "Color:"), bnColor);
0376         editableItems->addRow(i18n("Spot color:"), chkSpot);
0377 
0378         lnGroupName->setText(entry.name());
0379         lnIDName->setText(entry.id());
0380         bnColor->setColor(entry.color());
0381         chkSpot->setChecked(entry.spotColor());
0382 
0383         if (dialog.exec() == KoDialog::Accepted) {
0384             entry.setName(lnGroupName->text());
0385             entry.setId(lnIDName->text());
0386             entry.setColor(bnColor->color());
0387             entry.setSpotColor(chkSpot->isChecked());
0388             m_d->model->setEntry(entry, index);
0389         }
0390     }
0391 }
0392 
0393 void KisPaletteEditor::addEntry(const KoColor &color)
0394 {
0395     Q_ASSERT(m_d->model);
0396     if (!m_d->view) { return; }
0397     if (!m_d->view->document()) { return; }
0398 
0399     KoDialog dialog;
0400     dialog.setWindowTitle(i18nc("@title:dialog", "Add a new Color Swatch"));
0401 
0402     QFormLayout *editableItems = new QFormLayout(dialog.mainWidget());
0403 
0404     QComboBox *cmbGroups = new QComboBox();
0405     cmbGroups->addItems(m_d->model->colorSet()->getGroupNames());
0406     cmbGroups->setCurrentIndex(0);
0407 
0408     QLineEdit *lnIDName = new QLineEdit();
0409     lnIDName->setText(QString::number(m_d->model->colorSet()->colorCount() + 1));
0410 
0411     QLineEdit *lnName = new QLineEdit();
0412     lnName->setText(i18nc("Default name for a color swatch","Color %1", QString::number(m_d->model->colorSet()->colorCount()+1)));
0413 
0414     KisColorButton *bnColor = new KisColorButton();
0415     bnColor->setColor(color);
0416 
0417     QCheckBox *chkSpot = new QCheckBox();
0418     chkSpot->setChecked(false);
0419     chkSpot->setToolTip(i18nc("@info:tooltip", "A spot color is a color that the printer is able to print without mixing the paints it has available to it. The opposite is called a process color."));
0420 
0421     editableItems->addRow(i18n("Swatch Group:"), cmbGroups);
0422     editableItems->addRow(i18n("Swatch ID:"), lnIDName);
0423     editableItems->addRow(i18n("Color swatch name:"), lnName);
0424     editableItems->addRow(i18nc("Color as the Color of a Swatch in a Palette", "Color:"), bnColor);
0425     editableItems->addRow(i18n("Spot color:"), chkSpot);
0426 
0427     if (dialog.exec() != KoDialog::Accepted) { return; }
0428 
0429     QString groupName = cmbGroups->currentText();
0430 
0431     KisSwatch newEntry;
0432     newEntry.setColor(bnColor->color());
0433     newEntry.setName(lnName->text());
0434     newEntry.setId(lnIDName->text());
0435     newEntry.setSpotColor(chkSpot->isChecked());
0436     m_d->model->addEntry(newEntry, groupName);
0437     m_d->modifiedGroupNames.insert(groupName);
0438     m_d->modified.groups[groupName].addEntry(newEntry);
0439 }
0440 
0441 bool KisPaletteEditor::isModified() const
0442 {
0443     if (m_d->model->colorSet()) {
0444         return m_d->model->colorSet()->isDirty();
0445     } else {
0446         return false;
0447     }
0448 }
0449 
0450 void KisPaletteEditor::updatePalette()
0451 {
0452     dbgResources << Q_FUNC_INFO << "updating the palette model inside the palette editor object";
0453     Q_ASSERT(m_d->model);
0454     Q_ASSERT(m_d->model->colorSet());
0455     if (!m_d->view) { return; }
0456     if (!m_d->view->document()) { return; }
0457     KoColorSetSP palette = m_d->model->colorSet();
0458     PaletteInfo &modified = m_d->modified;
0459 
0460     if (m_d->isColumnCountModified) {
0461         palette->setColumnCount(modified.columnCount);
0462     }
0463     if (m_d->isNameModified) {
0464         KisResourceUserOperations::renameResourceWithUserInput(m_d->view->mainWindowAsQWidget(), palette, m_d->modified.name);
0465     }
0466     QString resourceLocation = m_d->model->colorSet()->storageLocation();
0467     if (resourceLocation != m_d->modified.storageLocation) {
0468         // We need functionality for moving the resource to the new resource storage...
0469     }
0470 
0471     Q_FOREACH (const QString &groupName, palette->getGroupNames()) {
0472         if (!modified.groups.contains(groupName)) {
0473             m_d->model->removeGroup(groupName, m_d->keepColorGroups.contains(groupName));
0474         }
0475     }
0476     m_d->keepColorGroups.clear();
0477     Q_FOREACH (const QString &groupName, palette->getGroupNames()) {
0478         if (m_d->modifiedGroupNames.contains(groupName)) {
0479             m_d->model->setRowNumber(groupName, modified.groups[groupName].rowCount());
0480             if (groupName != modified.groups[groupName].name()) {
0481                 m_d->model->renameGroup(groupName, modified.groups[groupName].name());
0482                 modified.groups[modified.groups[groupName].name()] = modified.groups[groupName];
0483                 modified.groups.remove(groupName);
0484             }
0485         }
0486     }
0487     m_d->modifiedGroupNames.clear();
0488     Q_FOREACH (const QString &newGroupName, m_d->newGroupNames) {
0489         m_d->model->addGroup(modified.groups[newGroupName]);
0490     }
0491     m_d->newGroupNames.clear();
0492 }
0493 
0494 void KisPaletteEditor::saveNewPaletteVersion()
0495 {
0496     if (!m_d->model || !m_d->model->colorSet()) { return; }
0497 
0498     QModelIndex index = m_d->rServer->resourceModel()->indexForResource(m_d->model->colorSet());
0499     bool isGlobal = false;
0500     if (index.isValid()) {
0501         bool ok = false;
0502         int storageId = m_d->rServer->resourceModel()->data(index, Qt::UserRole + KisAllResourcesModel::StorageId).toInt(&ok);
0503         if (ok) {
0504             KisStorageModel storageModel;
0505             KisResourceStorageSP storage = storageModel.storageForId(storageId);
0506             isGlobal = storage->type() != KisResourceStorage::StorageType::Memory;
0507         }
0508     }
0509     bool res = false;
0510     if (isGlobal) {
0511         if (m_d->view) {
0512             res = KisResourceUserOperations::updateResourceWithUserInput(m_d->view->mainWindowAsQWidget(), m_d->model->colorSet());
0513         } else if (m_d->model->colorSet()->version() >= 0) {
0514             //if the version is -1, then the resource should not be updated, because it was never saved to begin with...
0515             res = m_d->rServer->resourceModel()->updateResource(m_d->model->colorSet());
0516             dbgResources << Q_FUNC_INFO << "-- Updating resource without user input: " << m_d->model->colorSet()->name() << "Result:" << res;
0517         }
0518     }
0519 
0520     // let's not change it to true if it wasn't modified at all
0521     m_d->model->colorSet()->setDirty(m_d->model->colorSet()->isDirty() && !res);
0522 }
0523 
0524 void KisPaletteEditor::slotPaletteChanged()
0525 {
0526     Q_ASSERT(m_d->model);
0527     if (!m_d->model->colorSet()) { return; }
0528     KoColorSetSP palette = m_d->model->colorSet();
0529     m_d->modified.groups.clear();
0530     m_d->keepColorGroups.clear();
0531     m_d->newGroupNames.clear();
0532     m_d->modifiedGroupNames.clear();
0533 
0534     m_d->modified.name = palette->name();
0535     m_d->modified.storageLocation = palette->storageLocation();
0536     m_d->modified.columnCount = palette->columnCount();
0537 
0538     Q_FOREACH (const QString &groupName, palette->getGroupNames()) {
0539         KisSwatchGroup *cs = palette->getGroup(groupName);
0540         m_d->modified.groups[groupName] = KisSwatchGroup(*cs);
0541     }
0542 }
0543 
0544 QString KisPaletteEditor::relativePathFromSaveLocation() const
0545 {
0546     return filenameFromPath(m_d->modified.filename);
0547 }
0548 
0549 QString KisPaletteEditor::newGroupName() const
0550 {
0551     int i = 1;
0552     QString groupname = i18nc("Default new group name", "New Group %1", QString::number(i));
0553     while (m_d->modified.groups.contains(groupname)) {
0554         i++;
0555         groupname = i18nc("Default new group name", "New Group %1", QString::number(i));
0556     }
0557     return groupname;
0558 }
0559 
0560 QString KisPaletteEditor::filenameFromPath(const QString &path) const
0561 {
0562     return QDir::fromNativeSeparators(path).section('/', -1, -1);
0563 }