1 /*
2 * Copyright (c) 2012 Sven Langkamp <sven.langkamp@gmail.com>
3 *
4 * This library is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19 #include "compositiondocker_dock.h"
20
21 #include <QGridLayout>
22 #include <QListView>
23 #include <QHeaderView>
24 #include <QStyledItemDelegate>
25 #include <QPainter>
26 #include <QInputDialog>
27 #include <QThread>
28 #include <QAction>
29 #include <QStandardPaths>
30 #include <QMenu>
31 #include <QAction>
32
33 #include <klocalizedstring.h>
34 #include <kactioncollection.h>
35
36 #include <kis_icon.h>
37 #include <KoCanvasBase.h>
38 #include <KoFileDialog.h>
39
40 #include <KisPart.h>
41 #include <KisViewManager.h>
42 #include <kis_canvas2.h>
43 #include <KisKineticScroller.h>
44
45 #include <KisDocument.h>
46 #include <kis_group_layer.h>
47 #include <kis_painter.h>
48 #include <kis_paint_layer.h>
49 #include <kis_action.h>
50 #include <kis_action_manager.h>
51 #include <kis_action_registry.h>
52
53 #include <dialogs/KisAsyncAnimationFramesSaveDialog.h>
54 #include <animation/KisAnimationRenderingOptions.h>
55 #include <animation/KisAnimationRender.h>
56 #include <kis_image_animation_interface.h>
57 #include <kis_time_range.h>
58 #include <KisMimeDatabase.h>
59
60
61 #include "compositionmodel.h"
62
63
CompositionDockerDock()64 CompositionDockerDock::CompositionDockerDock( )
65 : QDockWidget(i18n("Compositions"))
66 , m_canvas(0)
67 {
68 QWidget* widget = new QWidget(this);
69 setupUi(widget);
70 m_model = new CompositionModel(this);
71 compositionView->setModel(m_model);
72 compositionView->installEventFilter(this);
73 deleteButton->setIcon(KisIconUtils::loadIcon("edit-delete"));
74 saveButton->setIcon(KisIconUtils::loadIcon("list-add"));
75 moveUpButton->setIcon(KisIconUtils::loadIcon("arrow-up"));
76 moveDownButton->setIcon(KisIconUtils::loadIcon("arrow-down"));
77
78 deleteButton->setToolTip(i18n("Delete Composition"));
79 saveButton->setToolTip(i18n("New Composition"));
80 exportCompositions->setToolTip(i18n("Export Composition"));
81 moveUpButton->setToolTip(i18n("Move Composition Up"));
82 moveDownButton->setToolTip(i18n("Move Composition Down"));
83
84 setWidget(widget);
85
86 connect( compositionView, SIGNAL(doubleClicked(QModelIndex)),
87 this, SLOT(activated(QModelIndex)) );
88
89 compositionView->setContextMenuPolicy(Qt::CustomContextMenu);
90 connect( compositionView, SIGNAL(customContextMenuRequested(QPoint)),
91 this, SLOT(customContextMenuRequested(QPoint)));
92
93 connect( deleteButton, SIGNAL(clicked(bool)), this, SLOT(deleteClicked()));
94 connect( saveButton, SIGNAL(clicked(bool)), this, SLOT(saveClicked()));
95 connect( moveUpButton, SIGNAL(clicked(bool)), this, SLOT(moveCompositionUp()));
96 connect( moveDownButton, SIGNAL(clicked(bool)), this, SLOT(moveCompositionDown()));
97
98 QAction* imageAction = new QAction(KisIconUtils::loadIcon("document-export"), i18n("Export Images"), this);
99 connect(imageAction, SIGNAL(triggered(bool)), this, SLOT(exportImageClicked()));
100
101 QAction* animationAction = new QAction(KisIconUtils::loadIcon("addblankframe"), i18n("Export Animations"), this);
102 connect(animationAction, SIGNAL(triggered(bool)), this, SLOT(exportAnimationClicked()));
103
104 exportCompositions->setDefaultAction(imageAction);
105
106 QMenu* exportMenu = new QMenu(this);
107 exportMenu->addAction(imageAction);
108 exportMenu->addAction(animationAction);
109
110 exportCompositions->setMenu(exportMenu);
111
112 connect(exportMenu, &QMenu::triggered, [this](QAction* triggered){
113 exportCompositions->setDefaultAction(triggered);
114 });
115
116 saveNameEdit->setPlaceholderText(i18n("Insert Name"));
117
118 QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(compositionView);
119 if (scroller) {
120 connect(scroller, SIGNAL(stateChanged(QScroller::State)), this, SLOT(slotScrollerStateChanged(QScroller::State)));
121 }
122
123 }
124
~CompositionDockerDock()125 CompositionDockerDock::~CompositionDockerDock()
126 {
127
128 }
129
setCanvas(KoCanvasBase * canvas)130 void CompositionDockerDock::setCanvas(KoCanvasBase * canvas)
131 {
132 if (m_canvas && m_canvas->viewManager()) {
133 Q_FOREACH (KisAction *action, m_actions) {
134 m_canvas->viewManager()->actionManager()->takeAction(action);
135 }
136 }
137
138 unsetCanvas();
139 setEnabled(canvas != 0);
140
141 m_canvas = dynamic_cast<KisCanvas2*>(canvas);
142 if (m_canvas && m_canvas->viewManager()) {
143 if (m_actions.isEmpty()) {
144 KisAction *updateAction = m_canvas->viewManager()->actionManager()->createAction("update_composition");
145 connect(updateAction, SIGNAL(triggered()), this, SLOT(updateComposition()));
146 m_actions.append(updateAction);
147
148 KisAction *renameAction = m_canvas->viewManager()->actionManager()->createAction("rename_composition");
149 connect(renameAction, SIGNAL(triggered()), this, SLOT(renameComposition()));
150 m_actions.append(renameAction);
151
152 } else {
153 Q_FOREACH (KisAction *action, m_actions) {
154 m_canvas->viewManager()->actionManager()->addAction(action->objectName(), action);
155 }
156 }
157 updateModel();
158 }
159 }
160
unsetCanvas()161 void CompositionDockerDock::unsetCanvas()
162 {
163 setEnabled(false);
164 m_canvas = 0;
165 m_model->setCompositions(QList<KisLayerCompositionSP>());
166 }
167
activated(const QModelIndex & index)168 void CompositionDockerDock::activated(const QModelIndex& index)
169 {
170 KisLayerCompositionSP composition = m_model->compositionFromIndex(index);
171 composition->apply();
172 }
173
deleteClicked()174 void CompositionDockerDock::deleteClicked()
175 {
176 QModelIndex index = compositionView->currentIndex();
177 if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) {
178 KisLayerCompositionSP composition = m_model->compositionFromIndex(index);
179 m_canvas->viewManager()->image()->removeComposition(composition);
180 updateModel();
181 }
182 }
183
saveClicked()184 void CompositionDockerDock::saveClicked()
185 {
186 KisImageWSP image = m_canvas->viewManager()->image();
187 if (!image) return;
188
189 // format as 001, 002 ...
190 QString name = saveNameEdit->text();
191 if (name.isEmpty()) {
192 bool found = false;
193 int i = 1;
194 do {
195 name = QString("%1").arg(i, 3, 10, QChar('0'));
196 found = false;
197 Q_FOREACH (KisLayerCompositionSP composition, m_canvas->viewManager()->image()->compositions()) {
198 if (composition->name() == name) {
199 found = true;
200 break;
201 }
202 }
203 i++;
204 } while(found && i < 1000);
205 }
206 KisLayerCompositionSP composition(new KisLayerComposition(image, name));
207 composition->store();
208 image->addComposition(composition);
209 saveNameEdit->clear();
210 updateModel();
211 compositionView->setCurrentIndex(m_model->index(image->compositions().count()-1, 0));
212 image->setModified();
213 }
214
moveCompositionUp()215 void CompositionDockerDock::moveCompositionUp()
216 {
217 QModelIndex index = compositionView->currentIndex();
218 if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) {
219 KisLayerCompositionSP composition = m_model->compositionFromIndex(index);
220 m_canvas->viewManager()->image()->moveCompositionUp(composition);
221 updateModel();
222 compositionView->setCurrentIndex(m_model->index(m_canvas->viewManager()->image()->compositions().indexOf(composition),0));
223 }
224 }
225
moveCompositionDown()226 void CompositionDockerDock::moveCompositionDown()
227 {
228 QModelIndex index = compositionView->currentIndex();
229 if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) {
230 KisLayerCompositionSP composition = m_model->compositionFromIndex(index);
231 m_canvas->viewManager()->image()->moveCompositionDown(composition);
232 updateModel();
233 compositionView->setCurrentIndex(m_model->index(m_canvas->viewManager()->image()->compositions().indexOf(composition),0));
234 }
235 }
236
updateModel()237 void CompositionDockerDock::updateModel()
238 {
239 if (m_model && m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image()) {
240 m_model->setCompositions(m_canvas->viewManager()->image()->compositions());
241 }
242 }
243
exportImageClicked()244 void CompositionDockerDock::exportImageClicked()
245 {
246 if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image()) {
247 QString path;
248
249 KoFileDialog dialog(0, KoFileDialog::OpenDirectory, "compositiondockerdock");
250 dialog.setCaption(i18n("Select a Directory"));
251 dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
252 path = dialog.filename();
253
254
255 if (path.isNull()) return;
256
257 if (!path.endsWith('/')) {
258 path.append('/');
259 }
260
261 KisImageSP image = m_canvas->viewManager()->image();
262 QString filename = m_canvas->viewManager()->document()->localFilePath();
263 if (!filename.isEmpty()) {
264 QFileInfo info(filename);
265 path += info.completeBaseName() + '_';
266 }
267
268 KisLayerCompositionSP currentComposition = toQShared(new KisLayerComposition(image, "temp"));
269 currentComposition->store();
270
271 Q_FOREACH (KisLayerCompositionSP composition, image->compositions()) {
272 if (!composition->isExportEnabled()) {
273 continue;
274 }
275
276 composition->apply();
277 image->waitForDone();
278 image->refreshGraph();
279
280 QRect r = image->bounds();
281
282 KisDocument *d = KisPart::instance()->createDocument();
283
284 KisImageSP dst = new KisImage(d->createUndoStore(), r.width(), r.height(), image->colorSpace(), composition->name());
285 dst->setResolution(image->xRes(), image->yRes());
286 d->setCurrentImage(dst);
287 KisPaintLayer* paintLayer = new KisPaintLayer(dst, "projection", OPACITY_OPAQUE_U8);
288 KisPainter gc(paintLayer->paintDevice());
289 gc.bitBlt(QPoint(0, 0), image->rootLayer()->projection(), r);
290 dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0));
291
292 dst->refreshGraph();
293 dst->waitForDone();
294
295 d->setFileBatchMode(true);
296
297 d->exportDocumentSync(QUrl::fromLocalFile(path + composition->name() + ".png"), "image/png");
298 d->deleteLater();
299 }
300
301 currentComposition->apply();
302 image->waitForDone();
303 image->refreshGraph();
304 }
305
306 }
307
exportAnimationClicked()308 void CompositionDockerDock::exportAnimationClicked()
309 {
310 KisConfig cfg(true);
311 KisPropertiesConfigurationSP settings = cfg.exportConfiguration("ANIMATION_EXPORT");
312 KisAnimationRenderingOptions exportOptions;
313 exportOptions.fromProperties(settings);
314
315 if (m_canvas &&
316 m_canvas->viewManager() &&
317 m_canvas->viewManager()->image() &&
318 m_canvas->viewManager()->image()->animationInterface() &&
319 m_canvas->viewManager()->document()) {
320
321 QString path;
322
323 KoFileDialog dialog(0, KoFileDialog::OpenDirectory, "compositiondockerdock");
324 dialog.setCaption(i18n("Select a Directory"));
325 dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
326 path = dialog.filename();
327
328
329 if (path.isNull()) return;
330
331 if (!path.endsWith('/')) {
332 path.append('/');
333 }
334
335 KisImageSP image = m_canvas->viewManager()->image();
336 QString filename = m_canvas->viewManager()->document()->localFilePath();
337 if (!filename.isEmpty()) {
338 QFileInfo info(filename);
339 path += info.completeBaseName();
340 }
341
342 KisLayerCompositionSP currentComposition = toQShared(new KisLayerComposition(image, "temp"));
343 currentComposition->store();
344
345 const QString frameMimeType = settings->getPropertyLazy("frame_mimetype", frameMimeType);
346 const QString imageExtension = KisMimeDatabase::suffixesForMimeType(frameMimeType).first();
347 const QString videoExtension = KisMimeDatabase::suffixesForMimeType(exportOptions.videoMimeType).first();
348
349 Q_FOREACH (KisLayerCompositionSP composition, image->compositions()) {
350 if(!composition->isExportEnabled())
351 continue;
352
353 composition->apply();
354 image->waitForDone();
355 image->refreshGraph();
356
357 KisTimeRange range = image->animationInterface()->fullClipRange();
358
359 exportOptions.firstFrame = range.start();
360 exportOptions.lastFrame = range.end();
361 exportOptions.width = image->width();
362 exportOptions.height = image->height();
363 exportOptions.videoFileName = QString("%1/%2/video.%3").arg(path).arg(composition->name()).arg(videoExtension);
364 exportOptions.directory = QString("%1/%2").arg(path).arg(composition->name());
365 exportOptions.basename = QString("frame");
366 exportOptions.wantsOnlyUniqueFrameSequence = true;
367
368 KisAnimationRender::render(m_canvas->viewManager()->document(), m_canvas->viewManager(), exportOptions);
369 }
370
371 currentComposition->apply();
372 image->waitForDone();
373 image->refreshGraph();
374 }
375 }
376
eventFilter(QObject * obj,QEvent * event)377 bool CompositionDockerDock::eventFilter(QObject* obj, QEvent* event)
378 {
379 if (event->type() == QEvent::KeyPress ) {
380 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
381 if (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down) {
382 // new index will be set after the method is called
383 QTimer::singleShot(0, this, SLOT(activateCurrentIndex()));
384 }
385 return false;
386 } else {
387 return QObject::eventFilter(obj, event);
388 }
389 }
390
activateCurrentIndex()391 void CompositionDockerDock::activateCurrentIndex()
392 {
393 QModelIndex index = compositionView->currentIndex();
394 if (index.isValid()) {
395 activated(index);
396 }
397 }
398
customContextMenuRequested(QPoint pos)399 void CompositionDockerDock::customContextMenuRequested(QPoint pos)
400 {
401 if (m_actions.isEmpty()) return;
402
403 QMenu menu;
404 Q_FOREACH (KisAction *action, m_actions) {
405 menu.addAction(action);
406
407 }
408 menu.exec(compositionView->mapToGlobal(pos));
409 }
410
updateComposition()411 void CompositionDockerDock::updateComposition()
412 {
413 QModelIndex index = compositionView->currentIndex();
414 if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) {
415 KisLayerCompositionSP composition = m_model->compositionFromIndex(index);
416 composition->store();
417 m_canvas->image()->setModified();
418 }
419 }
420
renameComposition()421 void CompositionDockerDock::renameComposition()
422 {
423 dbgKrita << "rename";
424 QModelIndex index = compositionView->currentIndex();
425 if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) {
426 KisLayerCompositionSP composition = m_model->compositionFromIndex(index);
427 bool ok;
428 QString name = QInputDialog::getText(this, i18n("Rename Composition"),
429 i18n("New Name:"), QLineEdit::Normal,
430 composition->name(), &ok);
431 if (ok && !name.isEmpty()) {
432 composition->setName(name);
433 m_canvas->image()->setModified();
434 }
435 }
436 }
437
438
439