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 }