Krita Source Code Documentation
Loading...
Searching...
No Matches
compositiondocker_dock.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2012 Sven Langkamp <sven.langkamp@gmail.com>
3 *
4 * SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
8
9#include <QGridLayout>
10#include <QListView>
11#include <QHeaderView>
12#include <QPainter>
13#include <QInputDialog>
14#include <QThread>
15#include <QAction>
16#include <QStandardPaths>
17#include <QMenu>
18#include <QAction>
19#include <QProgressDialog>
20
21#include <klocalizedstring.h>
22#include <kactioncollection.h>
23
24#include <kis_icon.h>
25#include <KoCanvasBase.h>
26#include <KoFileDialog.h>
27
28#include <KisPart.h>
29#include <KisViewManager.h>
30#include <kis_canvas2.h>
31#include <KisKineticScroller.h>
32
33#include <KisDocument.h>
34#include <kis_group_layer.h>
35#include <kis_painter.h>
36#include <kis_paint_layer.h>
37#include <kis_action.h>
38#include <kis_action_manager.h>
39#include <kis_action_registry.h>
40
45#include <kis_time_span.h>
46#include <KisMimeDatabase.h>
47
48#ifdef Q_OS_ANDROID
50#endif
51
52
53#include "compositionmodel.h"
54
55
57 : QDockWidget(i18n("Compositions"))
58 , m_canvas(0)
59{
60 QWidget* widget = new QWidget(this);
61 setupUi(widget);
62 m_model = new CompositionModel(this);
63 compositionView->setModel(m_model);
64 compositionView->installEventFilter(this);
65 deleteButton->setIcon(KisIconUtils::loadIcon("edit-delete"));
66 saveButton->setIcon(KisIconUtils::loadIcon("list-add"));
67 moveUpButton->setIcon(KisIconUtils::loadIcon("arrow-up"));
68 moveDownButton->setIcon(KisIconUtils::loadIcon("arrow-down"));
69
70 deleteButton->setToolTip(i18n("Delete Composition"));
71 saveButton->setToolTip(i18n("New Composition"));
72 exportCompositions->setToolTip(i18n("Export Composition"));
73 moveUpButton->setToolTip(i18n("Move Composition Up"));
74 moveDownButton->setToolTip(i18n("Move Composition Down"));
75
76 setWidget(widget);
77
78 connect( compositionView, SIGNAL(doubleClicked(QModelIndex)),
79 this, SLOT(activated(QModelIndex)) );
80
81 compositionView->setContextMenuPolicy(Qt::CustomContextMenu);
82 connect( compositionView, SIGNAL(customContextMenuRequested(QPoint)),
83 this, SLOT(customContextMenuRequested(QPoint)));
84
85 connect( deleteButton, SIGNAL(clicked(bool)), this, SLOT(deleteClicked()));
86 connect( saveButton, SIGNAL(clicked(bool)), this, SLOT(saveClicked()));
87 connect( moveUpButton, SIGNAL(clicked(bool)), this, SLOT(moveCompositionUp()));
88 connect( moveDownButton, SIGNAL(clicked(bool)), this, SLOT(moveCompositionDown()));
89
90 QAction* imageAction = new QAction(KisIconUtils::loadIcon("document-export-16"), i18n("Export Images"), this);
91 connect(imageAction, SIGNAL(triggered(bool)), this, SLOT(exportImageClicked()));
92
93 QAction* animationAction = new QAction(KisIconUtils::loadIcon("addblankframe-16"), i18n("Export Animations"), this);
94 connect(animationAction, SIGNAL(triggered(bool)), this, SLOT(exportAnimationClicked()));
95
96 exportCompositions->setDefaultAction(imageAction);
97
98 QMenu* exportMenu = new QMenu(this);
99 exportMenu->addAction(imageAction);
100 exportMenu->addAction(animationAction);
101
102 exportCompositions->setMenu(exportMenu);
103
104 connect(exportMenu, &QMenu::triggered, [this](QAction* triggered){
105 exportCompositions->setDefaultAction(triggered);
106 });
107
108 saveNameEdit->setPlaceholderText(i18n("Insert Name"));
109
110 QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(compositionView);
111 if (scroller) {
112 connect(scroller, SIGNAL(stateChanged(QScroller::State)), this, SLOT(slotScrollerStateChanged(QScroller::State)));
113 }
114
115}
116
121
123{
124 if (m_canvas && m_canvas->viewManager()) {
125 Q_FOREACH (KisAction *action, m_actions) {
126 m_canvas->viewManager()->actionManager()->takeAction(action);
127 }
128 }
129
130 unsetCanvas();
131 setEnabled(canvas != 0);
132
133 m_canvas = dynamic_cast<KisCanvas2*>(canvas);
134 if (m_canvas && m_canvas->viewManager()) {
135 if (m_actions.isEmpty()) {
136 KisAction *updateAction = m_canvas->viewManager()->actionManager()->createAction("update_composition");
137 connect(updateAction, SIGNAL(triggered()), this, SLOT(updateComposition()));
138 m_actions.append(updateAction);
139
140 KisAction *renameAction = m_canvas->viewManager()->actionManager()->createAction("rename_composition");
141 connect(renameAction, SIGNAL(triggered()), this, SLOT(renameComposition()));
142 m_actions.append(renameAction);
143
144 } else {
145 Q_FOREACH (KisAction *action, m_actions) {
146 m_canvas->viewManager()->actionManager()->addAction(action->objectName(), action);
147 }
148 }
149 updateModel();
150 }
151}
152
159
160void CompositionDockerDock::activated(const QModelIndex& index)
161{
163 composition->apply();
164}
165
167{
168 QModelIndex index = compositionView->currentIndex();
169 if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) {
171 m_canvas->viewManager()->image()->removeComposition(composition);
172 updateModel();
173
174 const int compositionCount = compositionView->model()->rowCount();
175 if (compositionCount > 0) {
176 if (index.row() == compositionCount) {
177 compositionView->setCurrentIndex(index.siblingAtRow(compositionCount-1));
178 } else {
179 compositionView->setCurrentIndex(index);
180 }
181 }
182 }
183}
184
186{
187 KisImageWSP image = m_canvas->viewManager()->image();
188 if (!image) return;
189
190 // format as 001, 002 ...
191 QString name = saveNameEdit->text();
192 if (name.isEmpty()) {
193 bool found = false;
194 int i = 1;
195 do {
196 name = QString("%1").arg(i, 3, 10, QChar('0'));
197 found = false;
198 Q_FOREACH (KisLayerCompositionSP composition, m_canvas->viewManager()->image()->compositions()) {
199 if (composition->name() == name) {
200 found = true;
201 break;
202 }
203 }
204 i++;
205 } while(found && i < 1000);
206 }
207 KisLayerCompositionSP composition(new KisLayerComposition(image, name));
208 composition->store();
209 image->addComposition(composition);
210 saveNameEdit->clear();
211 updateModel();
212 compositionView->setCurrentIndex(m_model->index(image->compositions().count()-1, 0));
213 image->setModifiedWithoutUndo();
214}
215
217{
218 QModelIndex index = compositionView->currentIndex();
219 if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) {
221 m_canvas->viewManager()->image()->moveCompositionUp(composition);
222 updateModel();
223 compositionView->setCurrentIndex(m_model->index(m_canvas->viewManager()->image()->compositions().indexOf(composition),0));
224 }
225}
226
228{
229 QModelIndex index = compositionView->currentIndex();
230 if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) {
232 m_canvas->viewManager()->image()->moveCompositionDown(composition);
233 updateModel();
234 compositionView->setCurrentIndex(m_model->index(m_canvas->viewManager()->image()->compositions().indexOf(composition),0));
235 }
236}
237
239{
240 if (m_model && m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image()) {
241 m_model->setCompositions(m_canvas->viewManager()->image()->compositions());
242 }
243}
244
246{
247 if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image()) {
248 QString path;
249
250 KoFileDialog dialog(0, KoFileDialog::OpenDirectory, "compositiondockerdock");
251 dialog.setCaption(i18n("Select a Directory"));
252 dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
253 path = dialog.filename();
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 const QString exportWindowTitle = i18n("Exporting Compositions...");
272 QSharedPointer<QProgressDialog> m_progressBar(new QProgressDialog(exportWindowTitle, i18n("Cancel"), 0, 0, this));
273 const int compositionCount = image->compositions().size();
274 m_progressBar->setCancelButton(0); // TODO: This task should be threaded in the future so that canceling is possible,
275 // Additionally, we should also be copying the image and working off that copy to
276 // improve runtime performance and allow for the current krita instance to work
277 // without locking up. **For now, we will simply make this operation uncancelable.**
278 m_progressBar->setMaximum(compositionCount);
279 m_progressBar->setMinimum(0);
280 m_progressBar->setValue(0);
281 int compositionsExported = 0;
282
283 Q_FOREACH (KisLayerCompositionSP composition, image->compositions()) {
284 if (m_progressBar->wasCanceled()) {
285 break;
286 }
287
288 if (!composition->isExportEnabled()) {
289 compositionsExported++;
290 continue;
291 }
292
293 if (m_progressBar->isHidden()) {
294 m_progressBar->show();
295 }
296
297 m_progressBar->setLabelText(i18n("Exporting composition: %1...", composition->name()));
298
299 // TODO: Extension of above, but this algorithm is highly inefficient.
300 // We should simply a) Deep Copy Image, b) Apply Composition, c) RefreshImageGraph,
301 // d) Export Image.
302 // This task can then be threaded to work in parallel.
303 composition->apply();
304 image->refreshGraphAsync();
305 image->waitForDone();
306
307 QRect r = image->bounds();
308
310
311 KisImageSP dst = new KisImage(d->createUndoStore(), r.width(), r.height(), image->colorSpace(), composition->name());
312 dst->setResolution(image->xRes(), image->yRes());
313 d->setCurrentImage(dst);
314 KisPaintLayer* paintLayer = new KisPaintLayer(dst, "projection", OPACITY_OPAQUE_U8);
315 KisPainter gc(paintLayer->paintDevice());
316 gc.bitBlt(QPoint(0, 0), image->rootLayer()->projection(), r);
317 dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0));
318
319 dst->refreshGraphAsync();
320 dst->waitForDone();
321
322 d->setFileBatchMode(true);
323
324 d->exportDocumentSync(path + composition->name() + ".png", "image/png");
325 compositionsExported++;
326 m_progressBar->setValue(compositionsExported);
327 d->deleteLater();
328 }
329
330 currentComposition->apply();
331
332 image->refreshGraphAsync();
333 image->waitForDone();
334 }
335
336}
337
339{
340 KisConfig cfg(true);
341 KisPropertiesConfigurationSP settings = cfg.exportConfiguration("ANIMATION_EXPORT");
342 KisAnimationRenderingOptions exportOptions;
343 exportOptions.fromProperties(settings);
344
345#ifdef Q_OS_ANDROID
346 KisMediaEncoderFormat *format = KisMediaEncoderWrapper::getFormatByKey(exportOptions.videoFormatKey);
347 // If there's no format configured, try to grab the first available one.
348 if (!format) {
350 if (!supportedFormats.isEmpty()) {
351 format = supportedFormats.first();
352 }
353 }
354 // If there's still none, bail out.
355 if (!format) {
356 return;
357 }
358#endif
359
360 if (m_canvas &&
361 m_canvas->viewManager() &&
362 m_canvas->viewManager()->image() &&
363 m_canvas->viewManager()->image()->animationInterface() &&
364 m_canvas->viewManager()->document()) {
365
366 QString path;
367
368 KoFileDialog dialog(0, KoFileDialog::OpenDirectory, "compositiondockerdock");
369 dialog.setCaption(i18n("Select a Directory"));
370 dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
371 path = dialog.filename();
372
373 if (path.isNull()) return;
374
375 if (!path.endsWith('/')) {
376 path.append('/');
377 }
378
379 KisImageSP image = m_canvas->viewManager()->image();
380 QString filename = m_canvas->viewManager()->document()->localFilePath();
381 if (!filename.isEmpty()) {
382 QFileInfo info(filename);
383 path += info.completeBaseName();
384 }
385
386 KisLayerCompositionSP currentComposition = toQShared(new KisLayerComposition(image, "temp"));
387 currentComposition->store();
388
389#ifdef Q_OS_ANDROID
390 const QString videoExtension = format->extension();
391#else
392 const QString videoExtension = KisMimeDatabase::suffixesForMimeType(exportOptions.videoMimeType).first();
393#endif
394
395 Q_FOREACH (KisLayerCompositionSP composition, image->compositions()) {
396 if(!composition->isExportEnabled())
397 continue;
398
399 composition->apply();
400 image->refreshGraphAsync();
401 image->waitForDone();
402
404
405 exportOptions.firstFrame = range.start();
406 exportOptions.lastFrame = range.end();
407 exportOptions.width = image->width();
408 exportOptions.height = image->height();
409 exportOptions.basename = QString("frame");
410 exportOptions.videoFileName = QString("%1/%2/video.%3").arg(path, composition->name(), videoExtension);
411 exportOptions.directory = QString("%1/%2").arg(path, composition->name());
412 exportOptions.wantsOnlyUniqueFrameSequence = true;
413
414 bool success = KisAnimationRender::render(m_canvas->viewManager()->document(), m_canvas->viewManager(), exportOptions);
415
416 if (!success) break;
417 }
418
419 currentComposition->apply();
420 image->refreshGraphAsync();
421 image->waitForDone();
422 }
423}
424
425bool CompositionDockerDock::eventFilter(QObject* obj, QEvent* event)
426{
427 if (event->type() == QEvent::KeyPress ) {
428 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
429 if (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down) {
430 // new index will be set after the method is called
431 QTimer::singleShot(0, this, SLOT(activateCurrentIndex()));
432 }
433 return false;
434 } else {
435 return QObject::eventFilter(obj, event);
436 }
437}
438
440{
441 QModelIndex index = compositionView->currentIndex();
442 if (index.isValid()) {
443 activated(index);
444 }
445}
446
448{
449 if (m_actions.isEmpty()) return;
450
451 QMenu menu;
452 Q_FOREACH (KisAction *action, m_actions) {
453 menu.addAction(action);
454
455 }
456 menu.exec(compositionView->mapToGlobal(pos));
457}
458
460{
461 QModelIndex index = compositionView->currentIndex();
462 if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) {
464 composition->store();
465 m_canvas->image()->setModifiedWithoutUndo();
466 }
467}
468
470{
471 dbgKrita << "rename";
472 QModelIndex index = compositionView->currentIndex();
473 if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) {
475 bool ok;
476 QString name = QInputDialog::getText(this, i18n("Rename Composition"),
477 i18n("New Name:"), QLineEdit::Normal,
478 composition->name(), &ok);
479 if (ok && !name.isEmpty()) {
480 composition->setName(name);
481 m_canvas->image()->setModifiedWithoutUndo();
482 }
483 }
484}
485
486
const quint8 OPACITY_OPAQUE_U8
void activated(const QModelIndex &index)
void setCanvas(KoCanvasBase *canvas) override
QPointer< KisCanvas2 > m_canvas
void customContextMenuRequested(QPoint pos)
void slotScrollerStateChanged(QScroller::State state)
bool eventFilter(QObject *obj, QEvent *event) override
QVector< KisAction * > m_actions
void setCompositions(QList< KisLayerCompositionSP > compositions)
KisLayerCompositionSP compositionFromIndex(const QModelIndex &index)
void fromProperties(KisPropertiesConfigurationSP config)
KisPropertiesConfigurationSP exportConfiguration(const QString &filterId, bool defaultValue=false) const
const KisTimeSpan & documentPlaybackRange() const
documentPlaybackRange
void waitForDone()
void refreshGraphAsync(KisNodeSP root, const QVector< QRect > &rects, const QRect &cropRect, KisProjectionUpdateFlags flags=KisProjectionUpdateFlag::None) override
KisGroupLayerSP rootLayer() const
const KoColorSpace * colorSpace() const
KisImageAnimationInterface * animationInterface() const
qint32 width() const
void addComposition(KisLayerCompositionSP composition)
double xRes() const
double yRes() const
qint32 height() const
QList< KisLayerCompositionSP > compositions()
QRect bounds() const override
void setModifiedWithoutUndo()
void setResolution(double xres, double yres)
virtual QString extension() const =0
static KisMediaEncoderFormat * getFormatByKey(const QString &key)
static const QVector< KisMediaEncoderFormat * > & getSupportedFormats()
static QStringList suffixesForMimeType(const QString &mimeType)
void bitBlt(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight)
static KisPart * instance()
Definition KisPart.cpp:130
KisDocument * createDocument() const
Definition KisPart.cpp:228
int start() const
int end() const
KoCanvasObserverBasePrivate *const d
#define dbgKrita
Definition kis_debug.h:48
QSharedPointer< T > toQShared(T *ptr)
KRITAUI_EXPORT bool render(KisDocument *doc, KisViewManager *viewManager, KisAnimationRenderingOptions encoderOptions)
QIcon loadIcon(const QString &name)
KRITAWIDGETUTILS_EXPORT QScroller * createPreconfiguredScroller(QAbstractScrollArea *target)
KisPaintDeviceSP projection() const override
Definition kis_layer.cc:826
bool addNode(KisNodeSP node, KisNodeSP parent=KisNodeSP(), KisNodeAdditionFlags flags=KisNodeAdditionFlag::None)
KisPaintDeviceSP paintDevice