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
49#include "compositionmodel.h"
50
51
53 : QDockWidget(i18n("Compositions"))
54 , m_canvas(0)
55{
56 QWidget* widget = new QWidget(this);
57 setupUi(widget);
58 m_model = new CompositionModel(this);
59 compositionView->setModel(m_model);
60 compositionView->installEventFilter(this);
61 deleteButton->setIcon(KisIconUtils::loadIcon("edit-delete"));
62 saveButton->setIcon(KisIconUtils::loadIcon("list-add"));
63 moveUpButton->setIcon(KisIconUtils::loadIcon("arrow-up"));
64 moveDownButton->setIcon(KisIconUtils::loadIcon("arrow-down"));
65
66 deleteButton->setToolTip(i18n("Delete Composition"));
67 saveButton->setToolTip(i18n("New Composition"));
68 exportCompositions->setToolTip(i18n("Export Composition"));
69 moveUpButton->setToolTip(i18n("Move Composition Up"));
70 moveDownButton->setToolTip(i18n("Move Composition Down"));
71
72 setWidget(widget);
73
74 connect( compositionView, SIGNAL(doubleClicked(QModelIndex)),
75 this, SLOT(activated(QModelIndex)) );
76
77 compositionView->setContextMenuPolicy(Qt::CustomContextMenu);
78 connect( compositionView, SIGNAL(customContextMenuRequested(QPoint)),
79 this, SLOT(customContextMenuRequested(QPoint)));
80
81 connect( deleteButton, SIGNAL(clicked(bool)), this, SLOT(deleteClicked()));
82 connect( saveButton, SIGNAL(clicked(bool)), this, SLOT(saveClicked()));
83 connect( moveUpButton, SIGNAL(clicked(bool)), this, SLOT(moveCompositionUp()));
84 connect( moveDownButton, SIGNAL(clicked(bool)), this, SLOT(moveCompositionDown()));
85
86 QAction* imageAction = new QAction(KisIconUtils::loadIcon("document-export-16"), i18n("Export Images"), this);
87 connect(imageAction, SIGNAL(triggered(bool)), this, SLOT(exportImageClicked()));
88
89 QAction* animationAction = new QAction(KisIconUtils::loadIcon("addblankframe-16"), i18n("Export Animations"), this);
90 connect(animationAction, SIGNAL(triggered(bool)), this, SLOT(exportAnimationClicked()));
91
92 exportCompositions->setDefaultAction(imageAction);
93
94 QMenu* exportMenu = new QMenu(this);
95 exportMenu->addAction(imageAction);
96 exportMenu->addAction(animationAction);
97
98 exportCompositions->setMenu(exportMenu);
99
100 connect(exportMenu, &QMenu::triggered, [this](QAction* triggered){
101 exportCompositions->setDefaultAction(triggered);
102 });
103
104 saveNameEdit->setPlaceholderText(i18n("Insert Name"));
105
106 QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(compositionView);
107 if (scroller) {
108 connect(scroller, SIGNAL(stateChanged(QScroller::State)), this, SLOT(slotScrollerStateChanged(QScroller::State)));
109 }
110
111}
112
117
119{
120 if (m_canvas && m_canvas->viewManager()) {
121 Q_FOREACH (KisAction *action, m_actions) {
122 m_canvas->viewManager()->actionManager()->takeAction(action);
123 }
124 }
125
126 unsetCanvas();
127 setEnabled(canvas != 0);
128
129 m_canvas = dynamic_cast<KisCanvas2*>(canvas);
130 if (m_canvas && m_canvas->viewManager()) {
131 if (m_actions.isEmpty()) {
132 KisAction *updateAction = m_canvas->viewManager()->actionManager()->createAction("update_composition");
133 connect(updateAction, SIGNAL(triggered()), this, SLOT(updateComposition()));
134 m_actions.append(updateAction);
135
136 KisAction *renameAction = m_canvas->viewManager()->actionManager()->createAction("rename_composition");
137 connect(renameAction, SIGNAL(triggered()), this, SLOT(renameComposition()));
138 m_actions.append(renameAction);
139
140 } else {
141 Q_FOREACH (KisAction *action, m_actions) {
142 m_canvas->viewManager()->actionManager()->addAction(action->objectName(), action);
143 }
144 }
145 updateModel();
146 }
147}
148
155
156void CompositionDockerDock::activated(const QModelIndex& index)
157{
159 composition->apply();
160}
161
163{
164 QModelIndex index = compositionView->currentIndex();
165 if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) {
167 m_canvas->viewManager()->image()->removeComposition(composition);
168 updateModel();
169
170 const int compositionCount = compositionView->model()->rowCount();
171 if (compositionCount > 0) {
172 if (index.row() == compositionCount) {
173 compositionView->setCurrentIndex(index.siblingAtRow(compositionCount-1));
174 } else {
175 compositionView->setCurrentIndex(index);
176 }
177 }
178 }
179}
180
182{
183 KisImageWSP image = m_canvas->viewManager()->image();
184 if (!image) return;
185
186 // format as 001, 002 ...
187 QString name = saveNameEdit->text();
188 if (name.isEmpty()) {
189 bool found = false;
190 int i = 1;
191 do {
192 name = QString("%1").arg(i, 3, 10, QChar('0'));
193 found = false;
194 Q_FOREACH (KisLayerCompositionSP composition, m_canvas->viewManager()->image()->compositions()) {
195 if (composition->name() == name) {
196 found = true;
197 break;
198 }
199 }
200 i++;
201 } while(found && i < 1000);
202 }
203 KisLayerCompositionSP composition(new KisLayerComposition(image, name));
204 composition->store();
205 image->addComposition(composition);
206 saveNameEdit->clear();
207 updateModel();
208 compositionView->setCurrentIndex(m_model->index(image->compositions().count()-1, 0));
209 image->setModifiedWithoutUndo();
210}
211
213{
214 QModelIndex index = compositionView->currentIndex();
215 if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) {
217 m_canvas->viewManager()->image()->moveCompositionUp(composition);
218 updateModel();
219 compositionView->setCurrentIndex(m_model->index(m_canvas->viewManager()->image()->compositions().indexOf(composition),0));
220 }
221}
222
224{
225 QModelIndex index = compositionView->currentIndex();
226 if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) {
228 m_canvas->viewManager()->image()->moveCompositionDown(composition);
229 updateModel();
230 compositionView->setCurrentIndex(m_model->index(m_canvas->viewManager()->image()->compositions().indexOf(composition),0));
231 }
232}
233
235{
236 if (m_model && m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image()) {
237 m_model->setCompositions(m_canvas->viewManager()->image()->compositions());
238 }
239}
240
242{
243 if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image()) {
244 QString path;
245
246 KoFileDialog dialog(0, KoFileDialog::OpenDirectory, "compositiondockerdock");
247 dialog.setCaption(i18n("Select a Directory"));
248 dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
249 path = dialog.filename();
250
251 if (path.isNull()) return;
252
253 if (!path.endsWith('/')) {
254 path.append('/');
255 }
256
257 KisImageSP image = m_canvas->viewManager()->image();
258 QString filename = m_canvas->viewManager()->document()->localFilePath();
259 if (!filename.isEmpty()) {
260 QFileInfo info(filename);
261 path += info.completeBaseName() + '_';
262 }
263
264 KisLayerCompositionSP currentComposition = toQShared(new KisLayerComposition(image, "temp"));
265 currentComposition->store();
266
267 const QString exportWindowTitle = i18n("Exporting Compositions...");
268 QSharedPointer<QProgressDialog> m_progressBar(new QProgressDialog(exportWindowTitle, i18n("Cancel"), 0, 0, this));
269 const int compositionCount = image->compositions().size();
270 m_progressBar->setCancelButton(0); // TODO: This task should be threaded in the future so that canceling is possible,
271 // Additionally, we should also be copying the image and working off that copy to
272 // improve runtime performance and allow for the current krita instance to work
273 // without locking up. **For now, we will simply make this operation uncancelable.**
274 m_progressBar->setMaximum(compositionCount);
275 m_progressBar->setMinimum(0);
276 m_progressBar->setValue(0);
277 int compositionsExported = 0;
278
279 Q_FOREACH (KisLayerCompositionSP composition, image->compositions()) {
280 if (m_progressBar->wasCanceled()) {
281 break;
282 }
283
284 if (!composition->isExportEnabled()) {
285 compositionsExported++;
286 continue;
287 }
288
289 if (m_progressBar->isHidden()) {
290 m_progressBar->show();
291 }
292
293 m_progressBar->setLabelText(i18n("Exporting composition: %1...", composition->name()));
294
295 // TODO: Extension of above, but this algorithm is highly inefficient.
296 // We should simply a) Deep Copy Image, b) Apply Composition, c) RefreshImageGraph,
297 // d) Export Image.
298 // This task can then be threaded to work in parallel.
299 composition->apply();
300 image->refreshGraphAsync();
301 image->waitForDone();
302
303 QRect r = image->bounds();
304
306
307 KisImageSP dst = new KisImage(d->createUndoStore(), r.width(), r.height(), image->colorSpace(), composition->name());
308 dst->setResolution(image->xRes(), image->yRes());
309 d->setCurrentImage(dst);
310 KisPaintLayer* paintLayer = new KisPaintLayer(dst, "projection", OPACITY_OPAQUE_U8);
311 KisPainter gc(paintLayer->paintDevice());
312 gc.bitBlt(QPoint(0, 0), image->rootLayer()->projection(), r);
313 dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0));
314
315 dst->refreshGraphAsync();
316 dst->waitForDone();
317
318 d->setFileBatchMode(true);
319
320 d->exportDocumentSync(path + composition->name() + ".png", "image/png");
321 compositionsExported++;
322 m_progressBar->setValue(compositionsExported);
323 d->deleteLater();
324 }
325
326 currentComposition->apply();
327
328 image->refreshGraphAsync();
329 image->waitForDone();
330 }
331
332}
333
335{
336 KisConfig cfg(true);
337 KisPropertiesConfigurationSP settings = cfg.exportConfiguration("ANIMATION_EXPORT");
338 KisAnimationRenderingOptions exportOptions;
339 exportOptions.fromProperties(settings);
340
341 if (m_canvas &&
342 m_canvas->viewManager() &&
343 m_canvas->viewManager()->image() &&
344 m_canvas->viewManager()->image()->animationInterface() &&
345 m_canvas->viewManager()->document()) {
346
347 QString path;
348
349 KoFileDialog dialog(0, KoFileDialog::OpenDirectory, "compositiondockerdock");
350 dialog.setCaption(i18n("Select a Directory"));
351 dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
352 path = dialog.filename();
353
354 if (path.isNull()) return;
355
356 if (!path.endsWith('/')) {
357 path.append('/');
358 }
359
360 KisImageSP image = m_canvas->viewManager()->image();
361 QString filename = m_canvas->viewManager()->document()->localFilePath();
362 if (!filename.isEmpty()) {
363 QFileInfo info(filename);
364 path += info.completeBaseName();
365 }
366
367 KisLayerCompositionSP currentComposition = toQShared(new KisLayerComposition(image, "temp"));
368 currentComposition->store();
369
370 const QString videoExtension = KisMimeDatabase::suffixesForMimeType(exportOptions.videoMimeType).first();
371
372 Q_FOREACH (KisLayerCompositionSP composition, image->compositions()) {
373 if(!composition->isExportEnabled())
374 continue;
375
376 composition->apply();
377 image->refreshGraphAsync();
378 image->waitForDone();
379
381
382 exportOptions.firstFrame = range.start();
383 exportOptions.lastFrame = range.end();
384 exportOptions.width = image->width();
385 exportOptions.height = image->height();
386 exportOptions.basename = QString("frame");
387 exportOptions.videoFileName = QString("%1/%2/video.%3").arg(path, composition->name(), videoExtension);
388 exportOptions.directory = QString("%1/%2").arg(path, composition->name());
389 exportOptions.wantsOnlyUniqueFrameSequence = true;
390
391 bool success = KisAnimationRender::render(m_canvas->viewManager()->document(), m_canvas->viewManager(), exportOptions);
392
393 if (!success) break;
394 }
395
396 currentComposition->apply();
397 image->refreshGraphAsync();
398 image->waitForDone();
399 }
400}
401
402bool CompositionDockerDock::eventFilter(QObject* obj, QEvent* event)
403{
404 if (event->type() == QEvent::KeyPress ) {
405 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
406 if (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down) {
407 // new index will be set after the method is called
408 QTimer::singleShot(0, this, SLOT(activateCurrentIndex()));
409 }
410 return false;
411 } else {
412 return QObject::eventFilter(obj, event);
413 }
414}
415
417{
418 QModelIndex index = compositionView->currentIndex();
419 if (index.isValid()) {
420 activated(index);
421 }
422}
423
425{
426 if (m_actions.isEmpty()) return;
427
428 QMenu menu;
429 Q_FOREACH (KisAction *action, m_actions) {
430 menu.addAction(action);
431
432 }
433 menu.exec(compositionView->mapToGlobal(pos));
434}
435
437{
438 QModelIndex index = compositionView->currentIndex();
439 if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) {
441 composition->store();
442 m_canvas->image()->setModifiedWithoutUndo();
443 }
444}
445
447{
448 dbgKrita << "rename";
449 QModelIndex index = compositionView->currentIndex();
450 if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) {
452 bool ok;
453 QString name = QInputDialog::getText(this, i18n("Rename Composition"),
454 i18n("New Name:"), QLineEdit::Normal,
455 composition->name(), &ok);
456 if (ok && !name.isEmpty()) {
457 composition->setName(name);
458 m_canvas->image()->setModifiedWithoutUndo();
459 }
460 }
461}
462
463
const quint8 OPACITY_OPAQUE_U8
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
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)
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:131
KisDocument * createDocument() const
Definition KisPart.cpp:230
int start() const
int end() const
KoCanvasObserverBasePrivate *const d
#define dbgKrita
Definition kis_debug.h:45
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:820
bool addNode(KisNodeSP node, KisNodeSP parent=KisNodeSP(), KisNodeAdditionFlags flags=KisNodeAdditionFlag::None)
KisPaintDeviceSP paintDevice