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

0001 /*
0002    This file is part of the KDE project
0003    SPDX-FileCopyrightText: 1998, 1999 Reginald Stadlbauer <reggie@kde.org>
0004    SPDX-FileCopyrightText: 2000 Werner Trobin <trobin@kde.org>
0005    SPDX-FileCopyrightText: 2004 Nicolas GOUTTE <goutte@kde.org>
0006 
0007    SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include <KisTemplateCreateDia.h>
0011 
0012 #include <QFile>
0013 #include <QLabel>
0014 #include <QRadioButton>
0015 #include <QPushButton>
0016 #include <QCheckBox>
0017 #include <QVBoxLayout>
0018 #include <QPixmap>
0019 #include <QHBoxLayout>
0020 #include <QTreeWidget>
0021 #include <QTreeWidgetItem>
0022 #include <QGroupBox>
0023 #include <QInputDialog>
0024 #include <QTemporaryFile>
0025 #include <QLineEdit>
0026 #include <QDir>
0027 
0028 #include <klocalizedstring.h>
0029 #include <kis_icon.h>
0030 #include <KisDocument.h>
0031 #include <KisTemplates.h>
0032 #include <KisTemplateTree.h>
0033 #include <KisTemplateGroup.h>
0034 #include <KisTemplate.h>
0035 #include <QMessageBox>
0036 #include <KoResourcePaths.h>
0037 #include <kis_debug.h>
0038 #include <kconfiggroup.h>
0039 #include <QUrl>
0040 #include <KoFileDialog.h>
0041 
0042 #include <ksharedconfig.h>
0043 
0044 // ODF thumbnail extent
0045 static const int thumbnailExtent = 128;
0046 
0047 class KisTemplateCreateDiaPrivate {
0048 public:
0049     KisTemplateCreateDiaPrivate(const QString &templatesResourcePath,
0050                                 const QString &filePath, const QPixmap &thumbnail)
0051         : m_tree(templatesResourcePath, true)
0052         , m_filePath(filePath)
0053         , m_thumbnail(thumbnail)
0054     { }
0055 
0056     KisTemplateTree m_tree;
0057     QLineEdit *m_name {nullptr};
0058     QRadioButton *m_default {nullptr};
0059     QRadioButton *m_custom {nullptr};
0060     QPushButton *m_select {nullptr};
0061     QLabel *m_preview {nullptr};
0062     QString m_customFile;
0063     QPixmap m_customPixmap;
0064     QTreeWidget *m_groups {nullptr};
0065     QPushButton *m_add {nullptr};
0066     QPushButton *m_remove {nullptr};
0067     QCheckBox *m_defaultTemplate {nullptr};
0068     QString m_filePath;
0069     QPixmap m_thumbnail;
0070     bool m_changed {false};
0071 };
0072 
0073 
0074 /****************************************************************************
0075  *
0076  * Class: KisTemplateCreateDia
0077  *
0078  ****************************************************************************/
0079 
0080 KisTemplateCreateDia::KisTemplateCreateDia(const QString &templatesResourcePath,
0081                                            const QString &filePath, const QPixmap &thumbnail, QWidget *parent)
0082     : KoDialog(parent)
0083     , d(new KisTemplateCreateDiaPrivate(templatesResourcePath, filePath, thumbnail))
0084 {
0085     setButtons( KoDialog::Ok|KoDialog::Cancel );
0086     setDefaultButton( KoDialog::Ok );
0087     setCaption( i18n( "Create Template" ) );
0088     setModal( true );
0089     setObjectName( "template create dia" );
0090 
0091     QWidget *mainwidget = mainWidget();
0092     QHBoxLayout *mbox=new QHBoxLayout( mainwidget );
0093     QVBoxLayout* leftbox = new QVBoxLayout();
0094     mbox->addLayout( leftbox );
0095 
0096     QLabel *label=new QLabel(i18nc("Template name", "Name:"), mainwidget);
0097     QHBoxLayout *namefield=new QHBoxLayout();
0098     leftbox->addLayout( namefield );
0099     namefield->addWidget(label);
0100     d->m_name=new QLineEdit(mainwidget);
0101     d->m_name->setFocus();
0102     connect(d->m_name, SIGNAL(textChanged(QString)),
0103             this, SLOT(slotNameChanged(QString)));
0104     namefield->addWidget(d->m_name);
0105 
0106     label=new QLabel(i18nc("Group as in Template Group", "Group:"), mainwidget);
0107     leftbox->addWidget(label);
0108     d->m_groups = new QTreeWidget(mainwidget);
0109     leftbox->addWidget(d->m_groups);
0110     d->m_groups->setColumnCount(1);
0111     d->m_groups->setHeaderHidden(true);
0112     d->m_groups->setRootIsDecorated(true);
0113     d->m_groups->setSortingEnabled(true);
0114 
0115     fillGroupTree();
0116     d->m_groups->sortItems(0, Qt::AscendingOrder);
0117 
0118     QHBoxLayout *bbox=new QHBoxLayout();
0119     leftbox->addLayout( bbox );
0120     d->m_add=new QPushButton(i18nc("Group as in Template Group", "&Add Group..."), mainwidget);
0121     connect(d->m_add, SIGNAL(clicked()), this, SLOT(slotAddGroup()));
0122     bbox->addWidget(d->m_add);
0123     d->m_remove=new QPushButton(i18n("&Remove"), mainwidget);
0124     connect(d->m_remove, SIGNAL(clicked()), this, SLOT(slotRemove()));
0125     bbox->addWidget(d->m_remove);
0126 
0127     QVBoxLayout *rightbox=new QVBoxLayout();
0128     mbox->addLayout( rightbox );
0129     QGroupBox *pixbox = new QGroupBox(i18n("Picture"), mainwidget);
0130     rightbox->addWidget(pixbox);
0131     QVBoxLayout *pixlayout=new QVBoxLayout(pixbox );
0132     d->m_default=new QRadioButton(i18n("&Preview"), pixbox);
0133     d->m_default->setChecked(true);
0134     connect(d->m_default, SIGNAL(clicked()), this, SLOT(slotDefault()));
0135     pixlayout->addWidget(d->m_default);
0136     QHBoxLayout *custombox=new QHBoxLayout();
0137     d->m_custom=new QRadioButton(i18n("Custom:"), pixbox);
0138     d->m_custom->setChecked(false);
0139     connect(d->m_custom, SIGNAL(clicked()), this, SLOT(slotCustom()));
0140     custombox->addWidget(d->m_custom);
0141     d->m_select=new QPushButton(i18n("&Select..."), pixbox);
0142     connect(d->m_select, SIGNAL(clicked()), this, SLOT(slotSelect()));
0143     custombox->addWidget(d->m_select);
0144     custombox->addStretch(1);
0145     pixlayout->addLayout(custombox);
0146     d->m_preview=new QLabel(pixbox); // setPixmap() -> auto resize?
0147     pixlayout->addWidget(d->m_preview, 0, Qt::AlignCenter);
0148     pixlayout->addStretch(1);
0149 
0150     d->m_defaultTemplate = new QCheckBox( i18n("Use the new template as default"), mainwidget );
0151     d->m_defaultTemplate->setChecked( true );
0152     d->m_defaultTemplate->setVisible( false );
0153     d->m_defaultTemplate->setToolTip(i18n("Use the new template every time Krita starts"));
0154     rightbox->addWidget( d->m_defaultTemplate );
0155 
0156     enableButtonOk(false);
0157     d->m_changed=false;
0158     updatePixmap();
0159 
0160     connect(d->m_groups, SIGNAL(itemSelectionChanged()), this, SLOT(slotSelectionChanged()));
0161 
0162     d->m_remove->setEnabled(d->m_groups->currentItem());
0163     connect(this, SIGNAL(okClicked()), this, SLOT(slotOk()));
0164 }
0165 
0166 KisTemplateCreateDia::~KisTemplateCreateDia() {
0167     delete d;
0168 }
0169 
0170 void KisTemplateCreateDia::slotSelectionChanged()
0171 {
0172     const QTreeWidgetItem* item = d->m_groups->currentItem();
0173     d->m_remove->setEnabled( item );
0174     if ( ! item )
0175         return;
0176 
0177     if ( item->parent() != 0 )
0178     {
0179         d->m_name->setText( item->text( 0 ) );
0180     }
0181 }
0182 
0183 void KisTemplateCreateDia::createTemplate(const QString &templatesResourcePath,
0184                                           const char *suffix,
0185                                           KisDocument *document, QWidget *parent)
0186 {
0187     Q_UNUSED(suffix);
0188     QString fileName;
0189     {
0190         QTemporaryFile tempFile;
0191         if (!tempFile.open()) {
0192             qWarning("Creation of temporary file to store template failed.");
0193             return;
0194         }
0195         fileName = tempFile.fileName();
0196     }
0197 
0198     bool retval = document->exportDocumentSync(fileName, KisDocument::nativeFormatMimeType());
0199     if (!retval) {
0200         qWarning("Could not save template");
0201         return;
0202     }
0203     const QPixmap thumbnail = document->generatePreview(QSize(thumbnailExtent, thumbnailExtent));
0204     KisTemplateCreateDia *dia = new KisTemplateCreateDia(templatesResourcePath, fileName, thumbnail, parent);
0205     dia->exec();
0206     delete dia;
0207 
0208     QDir d;
0209     d.remove(fileName);
0210 }
0211 
0212 static void saveAsQuadraticPng(const QPixmap &pixmap, const QString &fileName)
0213 {
0214     QImage icon = pixmap.toImage();
0215     icon = icon.convertToFormat(QImage::Format_ARGB32);
0216     const int iconExtent = qMax(icon.width(), icon.height());
0217     icon = icon.copy((icon.width() - iconExtent) / 2, (icon.height() - iconExtent) / 2, iconExtent, iconExtent);
0218     icon.save(fileName, "PNG");
0219 }
0220 
0221 void KisTemplateCreateDia::slotOk() {
0222 
0223     // get the current item, if there is one...
0224     QTreeWidgetItem *item = d->m_groups->currentItem();
0225     if (!item)
0226         item = d->m_groups->topLevelItem(0);
0227     if (!item) {    // safe :)
0228         d->m_tree.writeTemplateTree();
0229         slotButtonClicked( KoDialog::Cancel );
0230         return;
0231     }
0232     // is it a group or a template? anyway - get the group :)
0233     if (item->parent() != 0)
0234         item=item->parent();
0235     if (!item) {    // *very* safe :P
0236         d->m_tree.writeTemplateTree();
0237         slotButtonClicked( KoDialog::Cancel );
0238         return;
0239     }
0240 
0241     KisTemplateGroup *group=d->m_tree.find(item->text(0));
0242     if (!group) {    // even safer
0243         d->m_tree.writeTemplateTree();
0244         slotButtonClicked( KoDialog::Cancel );
0245         return;
0246     }
0247 
0248     if (d->m_name->text().isEmpty()) {
0249         d->m_tree.writeTemplateTree();
0250         slotButtonClicked( KoDialog::Cancel );
0251         return;
0252     }
0253 
0254     // copy the tmp file and the picture the app provides
0255     QString dir = KoResourcePaths::saveLocation("data", d->m_tree.templatesResourcePath());
0256     dir += group->name();
0257     QString templateDir = dir+"/.source/";
0258     QString iconDir = dir+"/.icon/";
0259 
0260     QString file = KisTemplates::trimmed(d->m_name->text());
0261     QString tmpIcon = ".icon/"+file;
0262     tmpIcon += ".png";
0263     QString icon=iconDir+file;
0264     icon += ".png";
0265 
0266     QString ext = ".kra";
0267 
0268     QString dest = templateDir + file + ext;
0269     if (QFile::exists(dest)) {
0270         do {
0271             file = file.prepend( '_' );
0272             dest = templateDir + file + ext;
0273             tmpIcon=".icon/" + file + ".png";
0274             icon=iconDir + file + ".png";
0275         }
0276         while (QFile(dest).exists());
0277     }
0278     bool ignore = false;
0279     KisTemplate *t = new KisTemplate(d->m_name->text(), QString(), ".source/"+ file + ext, tmpIcon, "", "", false, true);
0280     if (!group->add(t)) {
0281         KisTemplate *existingTemplate=group->find(d->m_name->text());
0282         if (existingTemplate && !existingTemplate->isHidden()) {
0283             if (QMessageBox::warning(this,
0284                                      i18nc("@title:window", "Krita"),
0285                                      i18n("Do you really want to overwrite the existing '%1' template?", existingTemplate->name()),
0286                                      QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) {
0287                 group->add(t, true);
0288             }
0289             else {
0290                 delete t;
0291                 return;
0292             }
0293         }
0294         else {
0295             ignore = true;
0296         }
0297     }
0298 
0299     QDir path;
0300     if (!path.mkpath(templateDir) || !path.mkpath(iconDir)) {
0301         d->m_tree.writeTemplateTree();
0302         slotButtonClicked( KoDialog::Cancel );
0303         return;
0304     }
0305 
0306     QString orig;
0307     orig = d->m_filePath;
0308     // don't overwrite the hidden template file with a new non-hidden one
0309     if (!ignore) {
0310         if (!QFile::copy(d->m_filePath, dest)) {
0311             qWarning() << "Could not copy" << d->m_filePath << "to" << dest;
0312         }
0313 
0314         // save the picture as icon
0315         if (d->m_default->isChecked() && !d->m_thumbnail.isNull()) {
0316             saveAsQuadraticPng(d->m_thumbnail, icon);
0317         } else if (!d->m_customPixmap.isNull()) {
0318             saveAsQuadraticPng(d->m_customPixmap, icon);
0319         } else {
0320             warnUI << "Could not save the preview picture!";
0321         }
0322     }
0323 
0324     // if there's a .directory file, we copy this one, too
0325     bool ready=false;
0326     QStringList tmp=group->dirs();
0327     for(QStringList::ConstIterator it=tmp.constBegin(); it!=tmp.constEnd() && !ready; ++it) {
0328         if ((*it).contains(dir)==0) {
0329             orig = (*it) + ".directory";
0330             // Check if we can read the file
0331             if (QFile(orig).exists()) {
0332                 dest = dir + "/.directory";
0333                 // We copy the file with overwrite
0334                 if (!QFile(orig).copy(dest)) {
0335                     warnKrita << "Failed to copy from" << orig << "to" << dest;
0336                 }
0337                 ready = true;
0338             }
0339         }
0340     }
0341 
0342     d->m_tree.writeTemplateTree();
0343 
0344     if ( d->m_defaultTemplate->isChecked() )
0345     {
0346 
0347         KConfigGroup grp( KSharedConfig::openConfig(), "TemplateChooserDialog");
0348         grp.writeEntry( "LastReturnType", "Template" );
0349         grp.writePathEntry( "FullTemplateName", dir + '/' + t->file() );
0350         grp.writePathEntry( "AlwaysUseTemplate", dir + '/' + t->file() );
0351     }
0352 }
0353 
0354 void KisTemplateCreateDia::slotDefault() {
0355 
0356     d->m_default->setChecked(true);
0357     d->m_custom->setChecked(false);
0358     updatePixmap();
0359 }
0360 
0361 void KisTemplateCreateDia::slotCustom() {
0362 
0363     d->m_default->setChecked(false);
0364     d->m_custom->setChecked(true);
0365     if (d->m_customFile.isEmpty())
0366         slotSelect();
0367     else
0368         updatePixmap();
0369 }
0370 
0371 void KisTemplateCreateDia::slotSelect() {
0372 
0373     d->m_default->setChecked(false);
0374     d->m_custom->setChecked(true);
0375 
0376     KoFileDialog dlg(this, KoFileDialog::OpenFile, "TemplateImages");
0377     dlg.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
0378     dlg.setImageFilters();
0379     dlg.setCaption(i18n("Select an image"));
0380     QString fn = dlg.filename();
0381     if (fn.isEmpty()) {
0382         if (d->m_customFile.isEmpty()) {
0383             d->m_default->setChecked(true);
0384             d->m_custom->setChecked(false);
0385         }
0386         return;
0387     }
0388     QImage image(fn);
0389     if (image.isNull()) {
0390         QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("%1 is not a valid image file!", fn));
0391     }
0392     d->m_customFile = fn;
0393     d->m_customPixmap = QPixmap();
0394     updatePixmap();
0395 }
0396 
0397 void KisTemplateCreateDia::slotNameChanged(const QString &name) {
0398 
0399     if ( ( name.trimmed().isEmpty() || !d->m_groups->topLevelItem(0) ) && !d->m_changed )
0400         enableButtonOk(false);
0401     else
0402         enableButtonOk(true);
0403 }
0404 
0405 void KisTemplateCreateDia::slotAddGroup() {
0406 
0407     const QString name = QInputDialog::getText(this, i18nc("Group as in Template Group", "Add Group"), i18nc("Group as in Template Group", "Enter group name:"));
0408     KisTemplateGroup *group = d->m_tree.find(name);
0409     if (group && !group->isHidden()) {
0410         QMessageBox::information( this, i18n("This name has already been used."), i18nc("Group as in Template Group", "Add Group") );
0411         return;
0412     }
0413     QString dir = KoResourcePaths::saveLocation("data", d->m_tree.templatesResourcePath());
0414     dir+=name;
0415     KisTemplateGroup *newGroup=new KisTemplateGroup(name, dir, 0, true);
0416     d->m_tree.add(newGroup);
0417     QTreeWidgetItem *item = new QTreeWidgetItem(d->m_groups, QStringList() << name);
0418     d->m_groups->setCurrentItem(item);
0419     d->m_groups->sortItems(0, Qt::AscendingOrder);
0420     d->m_name->setFocus();
0421     enableButtonOk(true);
0422     d->m_changed=true;
0423 }
0424 
0425 void KisTemplateCreateDia::slotRemove() {
0426 
0427     QTreeWidgetItem *item = d->m_groups->currentItem();
0428     if (!item)
0429         return;
0430 
0431     QString what;
0432     QString removed;
0433     if (item->parent() == 0) {
0434         what =  i18nc("Group as in Template Group", "Do you really want to remove that group?");
0435         removed = i18nc("@title:window", "Remove Group");
0436     } else {
0437         what =  i18n("Do you really want to remove that template?");
0438         removed = i18nc("@title:window", "Remove Template");
0439     }
0440 
0441     if (QMessageBox::warning(this,
0442                              removed,
0443                              what,
0444                              QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox:: No) {
0445         d->m_name->setFocus();
0446         return;
0447     }
0448 
0449     if (item->parent() == 0) {
0450         KisTemplateGroup *group=d->m_tree.find(item->text(0));
0451         if (group)
0452             group->setHidden(true);
0453     }
0454     else {
0455         bool done=false;
0456         QList<KisTemplateGroup*> groups = d->m_tree.groups();
0457         QList<KisTemplateGroup*>::const_iterator it = groups.constBegin();
0458         for(; it != groups.constEnd() && !done; ++it) {
0459             KisTemplate *t = (*it)->find(item->text(0));
0460 
0461             if (t) {
0462                 t->setHidden(true);
0463                 done=true;
0464             }
0465         }
0466     }
0467     delete item;
0468     item=0;
0469     d->m_name->setFocus();
0470     d->m_changed=true;
0471     d->m_tree.writeTemplateTree();
0472 }
0473 
0474 void KisTemplateCreateDia::updatePixmap() {
0475 
0476     if (d->m_default->isChecked() && !d->m_thumbnail.isNull()) {
0477         d->m_preview->setPixmap(d->m_thumbnail);
0478     }
0479     else if (d->m_custom->isChecked() && !d->m_customFile.isEmpty()) {
0480 
0481         if (d->m_customPixmap.isNull()) {
0482             dbgUI <<"Trying to load picture" << d->m_customFile;
0483             // use the code in KisTemplate to load the image... hacky, I know :)
0484             KisTemplate t("foo", "bar", QString(), d->m_customFile);
0485             d->m_customPixmap = t.loadPicture();
0486         }
0487         else {
0488             warnUI << "Trying to load picture";
0489         }
0490 
0491         if (!d->m_customPixmap.isNull()) {
0492             d->m_preview->setPixmap(d->m_customPixmap);
0493         }
0494         else {
0495             d->m_preview->setText(i18n("Could not load picture."));
0496         }
0497     }
0498     else {
0499         d->m_preview->setText(i18n("No picture available."));
0500     }
0501 }
0502 
0503 void KisTemplateCreateDia::fillGroupTree() {
0504 
0505     Q_FOREACH (KisTemplateGroup *group, d->m_tree.groups()) {
0506         if (group->isHidden())
0507             continue;
0508         QTreeWidgetItem *groupItem=new QTreeWidgetItem(d->m_groups, QStringList() << group->name());
0509 
0510         Q_FOREACH (KisTemplate *t, group->templates()) {
0511             if (t->isHidden())
0512                 continue;
0513             (void)new QTreeWidgetItem(groupItem, QStringList() << t->name());
0514         }
0515     }
0516 }