1 /*
2  * newtilesetdialog.cpp
3  * Copyright 2009-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
4  *
5  * This file is part of Tiled.
6  *
7  * This program is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by the Free
9  * Software Foundation; either version 2 of the License, or (at your option)
10  * any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
15  * more details.
16  *
17  * You should have received a copy of the GNU General Public License along with
18  * this program. If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "newtilesetdialog.h"
22 #include "ui_newtilesetdialog.h"
23 
24 #include "documentmanager.h"
25 #include "imagecolorpickerwidget.h"
26 #include "mapdocument.h"
27 #include "session.h"
28 #include "utils.h"
29 
30 #include <QFileDialog>
31 #include <QFileInfo>
32 #include <QImage>
33 #include <QMessageBox>
34 #include <QCheckBox>
35 
36 using namespace Tiled;
37 
38 namespace session {
39 static SessionOption<int> tilesetType { "tileset.type" };
40 static SessionOption<bool> embedInMap { "tileset.embedInMap" };
41 static SessionOption<bool> useTransparentColor { "tileset.useTransparentColor" };
42 static SessionOption<QColor> transparentColor { "tileset.transparentColor", Qt::magenta };
43 static SessionOption<QSize> tileSize { "tileset.tileSize", QSize(32, 32) };
44 static SessionOption<int> tilesetSpacing { "tileset.spacing" };
45 static SessionOption<int> tilesetMargin { "tileset.margin" };
46 } // namespace session
47 
48 enum TilesetType {
49     TilesetImage,
50     ImageCollection
51 };
52 
tilesetType(Ui::NewTilesetDialog * ui)53 static TilesetType tilesetType(Ui::NewTilesetDialog *ui)
54 {
55     switch (ui->tilesetType->currentIndex()) {
56     default:
57     case 0:
58         return TilesetImage;
59     case 1:
60         return ImageCollection;
61     }
62 }
63 
NewTilesetDialog(QWidget * parent)64 NewTilesetDialog::NewTilesetDialog(QWidget *parent) :
65     QDialog(parent),
66     mUi(new Ui::NewTilesetDialog),
67     mNameWasEdited(false)
68 {
69     mUi->setupUi(this);
70 #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
71     setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
72 #endif
73 
74     const QSize tileSize = session::tileSize;
75 
76     mUi->tilesetType->setCurrentIndex(session::tilesetType);
77     mUi->embedded->setChecked(session::embedInMap);
78     mUi->useTransparentColor->setChecked(session::useTransparentColor);
79     mUi->colorButton->setColor(session::transparentColor);
80     mUi->tileWidth->setValue(tileSize.width());
81     mUi->tileHeight->setValue(tileSize.height());
82     mUi->spacing->setValue(session::tilesetSpacing);
83     mUi->margin->setValue(session::tilesetMargin);
84 
85     connect(mUi->browseButton, &QAbstractButton::clicked, this, &NewTilesetDialog::browse);
86     connect(mUi->name, &QLineEdit::textEdited, this, &NewTilesetDialog::nameEdited);
87     connect(mUi->name, &QLineEdit::textChanged, this, &NewTilesetDialog::updateOkButton);
88     connect(mUi->embedded, &QCheckBox::toggled, this, &NewTilesetDialog::updateOkButton);
89     connect(mUi->image, &QLineEdit::textChanged, this, &NewTilesetDialog::updateOkButton);
90     connect(mUi->image, &QLineEdit::textChanged, this, &NewTilesetDialog::updateColorPickerButton);
91     connect(mUi->useTransparentColor, &QCheckBox::toggled, this, &NewTilesetDialog::updateColorPickerButton);
92     connect(mUi->tilesetType, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
93             this, &NewTilesetDialog::tilesetTypeChanged);
94     connect(mUi->dropperButton, &QAbstractButton::clicked, this, &NewTilesetDialog::pickColorFromImage);
95 
96     connect(mUi->buttonBox, &QDialogButtonBox::accepted, this, &NewTilesetDialog::tryAccept);
97     connect(mUi->buttonBox, &QDialogButtonBox::rejected, this, &NewTilesetDialog::reject);
98 
99     mUi->imageGroupBox->setVisible(session::tilesetType == 0);
100     updateOkButton();
101 }
102 
~NewTilesetDialog()103 NewTilesetDialog::~NewTilesetDialog()
104 {
105     delete mUi;
106 }
107 
108 /**
109  * Sets the path to start in by default.
110  *
111  * Also sets the image and name fields if the given path is a file.
112  */
setImagePath(const QString & path)113 void NewTilesetDialog::setImagePath(const QString &path)
114 {
115     mPath = path;
116 
117     const QFileInfo fileInfo(path);
118     if (fileInfo.isFile()) {
119         mUi->tilesetType->setCurrentIndex(TilesetImage);
120         mUi->image->setText(path);
121         mUi->name->setText(fileInfo.completeBaseName());
122     }
123 }
124 
setTileSize(QSize size)125 void NewTilesetDialog::setTileSize(QSize size)
126 {
127     mUi->tileWidth->setValue(size.width());
128     mUi->tileHeight->setValue(size.height());
129 }
130 
131 /**
132  * Shows the dialog and returns the created tileset. Returns 0 if the
133  * dialog was cancelled.
134  */
createTileset()135 SharedTileset NewTilesetDialog::createTileset()
136 {
137     bool couldEmbed = false;
138     if (auto document = DocumentManager::instance()->currentDocument()) {
139         if (auto mapDocument = qobject_cast<MapDocument*>(document)) {
140             couldEmbed = true;
141             setTileSize(mapDocument->map()->tileSize());
142         }
143     }
144 
145     mUi->embedded->setEnabled(couldEmbed);
146 
147     setMode(CreateTileset);
148 
149     if (exec() != QDialog::Accepted)
150         return SharedTileset();
151 
152     return mNewTileset;
153 }
154 
isEmbedded() const155 bool NewTilesetDialog::isEmbedded() const
156 {
157     return mUi->embedded->isChecked();
158 }
159 
160 /**
161  * Shows the dialog and allows to change the given parameters.
162  *
163  * Returns whether the dialog was accepted.
164  */
editTilesetParameters(TilesetParameters & parameters)165 bool NewTilesetDialog::editTilesetParameters(TilesetParameters &parameters)
166 {
167     setMode(EditTilesetParameters);
168 
169     // todo: support remote files
170     mPath = parameters.imageSource.toLocalFile();
171     mUi->image->setText(parameters.imageSource.toString(QUrl::PreferLocalFile));
172 
173     QColor transparentColor = parameters.transparentColor;
174     mUi->useTransparentColor->setChecked(transparentColor.isValid());
175     if (transparentColor.isValid())
176         mUi->colorButton->setColor(transparentColor);
177 
178     mUi->tileWidth->setValue(parameters.tileSize.width());
179     mUi->tileHeight->setValue(parameters.tileSize.height());
180     mUi->spacing->setValue(parameters.tileSpacing);
181     mUi->margin->setValue(parameters.margin);
182 
183     if (exec() != QDialog::Accepted)
184         return false;
185 
186     parameters = TilesetParameters(*mNewTileset);
187     return true;
188 }
189 
tryAccept()190 void NewTilesetDialog::tryAccept()
191 {
192     const QString name = mUi->name->text();
193 
194     SharedTileset tileset;
195 
196     if (tilesetType(mUi) == TilesetImage) {
197         const QString image = mUi->image->text();
198         const bool useTransparentColor = mUi->useTransparentColor->isChecked();
199         const QColor transparentColor = mUi->colorButton->color();
200         const int tileWidth = mUi->tileWidth->value();
201         const int tileHeight = mUi->tileHeight->value();
202         const int spacing = mUi->spacing->value();
203         const int margin = mUi->margin->value();
204 
205         tileset = Tileset::create(name,
206                                   tileWidth, tileHeight,
207                                   spacing, margin);
208 
209         if (useTransparentColor)
210             tileset->setTransparentColor(transparentColor);
211 
212         if (!image.isEmpty()) {
213             if (!tileset->loadFromImage(image)) {
214                 QMessageBox::critical(this, tr("Error"),
215                                       tr("Failed to load tileset image '%1'.")
216                                       .arg(image));
217                 return;
218             }
219 
220             if (tileset->tileCount() == 0) {
221                 QMessageBox::critical(this, tr("Error"),
222                                       tr("No tiles found in the tileset image "
223                                          "when using the given tile size, "
224                                          "margin and spacing!"));
225                 return;
226             }
227 
228             tileset->syncExpectedColumnsAndRows();
229         }
230 
231         if (mMode == CreateTileset) {
232             session::useTransparentColor = useTransparentColor;
233             session::transparentColor = transparentColor;
234             session::tileSize = QSize(tileWidth, tileHeight);
235             session::tilesetSpacing = spacing;
236             session::tilesetMargin = margin;
237         }
238     } else {
239         tileset = Tileset::create(name, 1, 1);
240     }
241 
242     if (mMode == CreateTileset) {
243         session::tilesetType = mUi->tilesetType->currentIndex();
244         session::embedInMap = mUi->embedded->isChecked();
245     }
246 
247     mNewTileset = tileset;
248     accept();
249 }
250 
setMode(Mode mode)251 void NewTilesetDialog::setMode(Mode mode)
252 {
253     mMode = mode;
254 
255     if (mode == EditTilesetParameters) {
256         mUi->tilesetType->setCurrentIndex(0);
257         setWindowTitle(QApplication::translate("NewTilesetDialog", "Edit Tileset"));
258     } else {
259         setWindowTitle(QApplication::translate("NewTilesetDialog", "New Tileset"));
260     }
261 
262     mUi->tilesetGroupBox->setVisible(mode == CreateTileset);
263     updateOkButton();
264 }
265 
browse()266 void NewTilesetDialog::browse()
267 {
268     const QString filter = Utils::readableImageFormatsFilter();
269     QString f = QFileDialog::getOpenFileName(this, tr("Tileset Image"), mPath,
270                                              filter);
271     if (!f.isEmpty()) {
272         mUi->image->setText(f);
273         mPath = f;
274 
275         if (!mNameWasEdited)
276             mUi->name->setText(QFileInfo(f).completeBaseName());
277     }
278 }
279 
nameEdited(const QString & name)280 void NewTilesetDialog::nameEdited(const QString &name)
281 {
282     mNameWasEdited = !name.isEmpty();
283 }
284 
tilesetTypeChanged(int index)285 void NewTilesetDialog::tilesetTypeChanged(int index)
286 {
287     mUi->imageGroupBox->setVisible(index == 0);
288     updateOkButton();
289 }
290 
updateOkButton()291 void NewTilesetDialog::updateOkButton()
292 {
293     QPushButton *okButton = mUi->buttonBox->button(QDialogButtonBox::Ok);
294 
295     bool enabled = true;
296     QString text;
297 
298     if (mMode == CreateTileset) {
299         enabled &= !mUi->name->text().isEmpty();
300         text = isEmbedded() ? tr("&OK") : tr("&Save As...");
301     } else {
302         text = tr("&OK");
303     }
304 
305     if (tilesetType(mUi) == TilesetImage)
306         enabled &= !mUi->image->text().isEmpty();
307 
308     okButton->setEnabled(enabled);
309     okButton->setText(text);
310 }
311 
updateColorPickerButton()312 void NewTilesetDialog::updateColorPickerButton()
313 {
314     mUi->dropperButton->setEnabled(mUi->useTransparentColor->isChecked() &&
315                                    !mUi->image->text().isEmpty());
316 }
317 
318 /**
319  * Shows the popup window used to select the color from the chosen image.
320  */
pickColorFromImage()321 void NewTilesetDialog::pickColorFromImage()
322 {
323     auto *popup = new ImageColorPickerWidget(mUi->dropperButton);
324     popup->setAttribute(Qt::WA_DeleteOnClose);
325 
326     connect(popup, &ImageColorPickerWidget::colorSelected,
327             this, &NewTilesetDialog::colorSelected);
328 
329     if (!popup->selectColor(mUi->image->text()))
330         delete popup;
331 }
332 
colorSelected(QColor color)333 void NewTilesetDialog::colorSelected(QColor color)
334 {
335     mUi->colorButton->setColor(color);
336 }
337 
338 #include "moc_newtilesetdialog.cpp"
339