Krita Source Code Documentation
Loading...
Searching...
No Matches
StoryboardDockerDock.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2020 Saurabh Kumar <saurabhk660@gmail.com>
3 *
4 * SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
8#include "CommentDelegate.h"
9#include "CommentModel.h"
10#include "StoryboardModel.h"
11#include "StoryboardDelegate.h"
12#include "StoryboardView.h"
13#include "StoryboardUtils.h"
14#include "DlgExportStoryboard.h"
16
17#include <QMenu>
18#include <QButtonGroup>
19#include <QDebug>
20#include <QStringListModel>
21#include <QListView>
22#include <QItemSelection>
23#include <QSize>
24#include <QPrinter>
25#include <QTextDocument>
26#include <QAbstractTextDocumentLayout>
27#include <QSvgGenerator>
28#include <QSvgRenderer>
29#include <QMessageBox>
30#include <QSizePolicy>
31
32#include <klocalizedstring.h>
33
34#include <KisPart.h>
35#include <KisViewManager.h>
36#include <kis_node_manager.h>
37#include <KisDocument.h>
38#include <kis_icon.h>
40#include <kis_time_span.h>
41#include <kis_global.h>
43
44#include "ui_wdgstoryboarddock.h"
45#include "ui_wdgcommentmenu.h"
46#include "ui_wdgarrangemenu.h"
47
53
59
60class CommentMenu: public QMenu
61{
62 Q_OBJECT
63public:
64 CommentMenu(QWidget *parent, StoryboardCommentModel *m_model)
65 : QMenu(parent)
66 , m_menuUI(new Ui_WdgCommentMenu())
67 , model(m_model)
68 , delegate(new CommentDelegate(this))
69 {
70 QWidget* commentWidget = new QWidget(this);
71 m_menuUI->setupUi(commentWidget);
72
73 m_menuUI->fieldListView->setDragEnabled(true);
74 m_menuUI->fieldListView->setAcceptDrops(true);
75 m_menuUI->fieldListView->setDropIndicatorShown(true);
76 m_menuUI->fieldListView->setDragDropMode(QAbstractItemView::InternalMove);
77
78 m_menuUI->fieldListView->setModel(model);
79 m_menuUI->fieldListView->setItemDelegate(delegate);
80
81 m_menuUI->fieldListView->setEditTriggers(QAbstractItemView::AnyKeyPressed |
82 QAbstractItemView::DoubleClicked );
83
84 m_menuUI->btnAddField->setIcon(KisIconUtils::loadIcon("list-add"));
85 m_menuUI->btnDeleteField->setIcon(KisIconUtils::loadIcon("edit-delete"));
86 m_menuUI->btnAddField->setIconSize(QSize(16, 16));
87 m_menuUI->btnDeleteField->setIconSize(QSize(16, 16));
88 connect(m_menuUI->btnAddField, SIGNAL(clicked()), this, SLOT(slotaddItem()));
89 connect(m_menuUI->btnDeleteField, SIGNAL(clicked()), this, SLOT(slotdeleteItem()));
90
91 KisAction *commentAction = new KisAction(commentWidget);
92 commentAction->setDefaultWidget(commentWidget);
93 this->addAction(commentAction);
94 }
95
96private Q_SLOTS:
98 {
99 int row = m_menuUI->fieldListView->currentIndex().row()+1;
100 model->insertRows(row, 1);
101
102 QModelIndex index = model->index(row);
103 m_menuUI->fieldListView->setCurrentIndex(index);
104 m_menuUI->fieldListView->edit(index);
105 }
106
108 {
109 model->removeRows(m_menuUI->fieldListView->currentIndex().row(), 1);
110 }
111
112private:
113 QScopedPointer<Ui_WdgCommentMenu> m_menuUI;
116};
117
118class ArrangeMenu: public QMenu
119{
120public:
121 ArrangeMenu(QWidget *parent)
122 : QMenu(parent)
123 , m_menuUI(new Ui_WdgArrangeMenu())
124 , modeGroup(new QButtonGroup(this))
125 , viewGroup(new QButtonGroup(this))
126 {
127 QWidget* arrangeWidget = new QWidget(this);
128 m_menuUI->setupUi(arrangeWidget);
129
130 modeGroup->addButton(m_menuUI->btnColumnMode, Mode::Column);
131 modeGroup->addButton(m_menuUI->btnRowMode, Mode::Row);
132 modeGroup->addButton(m_menuUI->btnGridMode, Mode::Grid);
133
134 viewGroup->addButton(m_menuUI->btnAllView, View::All);
135 viewGroup->addButton(m_menuUI->btnThumbnailsView, View::ThumbnailsOnly);
136 viewGroup->addButton(m_menuUI->btnCommentsView, View::CommentsOnly);
137
138 KisAction *arrangeAction = new KisAction(arrangeWidget);
139 arrangeAction->setDefaultWidget(arrangeWidget);
140 this->addAction(arrangeAction);
141 }
142
143 QButtonGroup* getModeGroup(){ return modeGroup;}
144 QButtonGroup* getViewGroup(){ return viewGroup;}
145
146private:
147 QScopedPointer<Ui_WdgArrangeMenu> m_menuUI;
148 QButtonGroup *modeGroup;
149 QButtonGroup *viewGroup;
150};
151
152
153inline QMap<QString, QDomNode> rootItemsInSvg(const QDomDocument &d){
154 QMap<QString, QDomNode> nodeMap;
155 QDomNodeList svgs = d.elementsByTagName("svg");
156 KIS_ASSERT_RECOVER_RETURN_VALUE(svgs.size() > 0, nodeMap);
157 QDomNode svg = svgs.at(0);
158 QDomNodeList children = svg.toElement().childNodes();
159 for (int i = 0; i < children.count(); i++) {
160 QString id = children.at(i).toElement().attribute("id");
161 if (id.isEmpty())
162 continue;
163
164 nodeMap.insert(id, children.at(i));
165 }
166 return nodeMap;
167};
168
169
171 : QDockWidget(i18nc("Storyboard Docker", "Storyboard"))
172 , m_canvas(0)
173 , m_ui(new Ui_WdgStoryboardDock())
174 , m_exportMenu(new QMenu(this))
175 , m_commentModel(new StoryboardCommentModel(this))
176 , m_commentMenu(new CommentMenu(this, m_commentModel))
177 , m_arrangeMenu(new ArrangeMenu(this))
178 , m_storyboardModel(new StoryboardModel(this))
179 , m_storyboardDelegate(new StoryboardDelegate(this))
180{
181 QWidget* mainWidget = new QWidget(this);
182 setWidget(mainWidget);
183 m_ui->setupUi(mainWidget);
184
185 m_ui->btnExport->setMenu(m_exportMenu);
186 m_ui->btnExport->setPopupMode(QToolButton::InstantPopup);
187
188 m_exportAsPdfAction = new KisAction(i18nc("Export storyboard as PDF", "Export as PDF"), m_exportMenu);
190
191 m_exportAsSvgAction = new KisAction(i18nc("Export storyboard as SVG", "Export as SVG"), m_exportMenu);
193 connect(m_exportAsPdfAction, SIGNAL(triggered()), this, SLOT(slotExportAsPdf()));
194 connect(m_exportAsSvgAction, SIGNAL(triggered()), this, SLOT(slotExportAsSvg()));
195
196 //Setup dynamic QListView Width Based on Comment Model Columns...
198 connect(m_storyboardModel.data(), &StoryboardModel::rowsInserted, this, &StoryboardDockerDock::slotUpdateMinimumWidth);
199
200 connect(m_storyboardModel.data(), &StoryboardModel::rowsInserted, this, &StoryboardDockerDock::slotModelChanged);
201 connect(m_storyboardModel.data(), &StoryboardModel::rowsRemoved, this, &StoryboardDockerDock::slotModelChanged);
202
203 m_ui->btnComment->setMenu(m_commentMenu);
204 m_ui->btnComment->setPopupMode(QToolButton::InstantPopup);
205
207 i18nc("Freeze keyframe positions and ignore storyboard adjustments", "Freeze Keyframe Data"), m_ui->btnLock);
208 m_lockAction->setCheckable(true);
209 m_ui->btnLock->setDefaultAction(m_lockAction);
210 m_ui->btnLock->setIconSize(QSize(16, 16));
211 connect(m_lockAction, SIGNAL(toggled(bool)), this, SLOT(slotLockClicked(bool)));
212
213 m_ui->btnArrange->setMenu(m_arrangeMenu);
214 m_ui->btnArrange->setPopupMode(QToolButton::InstantPopup);
215 m_ui->btnArrange->setIcon(KisIconUtils::loadIcon("view-choose"));
216 m_ui->btnArrange->setAutoRaise(true);
217 m_ui->btnArrange->setIconSize(QSize(16, 16));
218
221
222 connect(m_modeGroup, SIGNAL(buttonClicked(QAbstractButton*)), this, SLOT(slotModeChanged(QAbstractButton*)));
223 connect(m_viewGroup, SIGNAL(buttonClicked(QAbstractButton*)), this, SLOT(slotViewChanged(QAbstractButton*)));
224
225 m_storyboardDelegate->setView(m_ui->sceneView);
226 m_storyboardModel->setView(m_ui->sceneView);
227 m_ui->sceneView->setModel(m_storyboardModel.data());
228 m_ui->sceneView->setItemDelegate(m_storyboardDelegate);
229
230 m_storyboardModel->setCommentModel(m_commentModel);
231
232 m_modeGroup->button(Mode::Row)->click();
233 m_viewGroup->button(View::All)->click();
234
235 { // Footer section...
236 QAction* action = new QAction(i18nc("Add new scene as the last storyboard", "Add Scene"), this);
237 connect(action, &QAction::triggered, this, [this](bool){
238 if (!m_canvas) return;
239
240 QModelIndex currentSelection = m_ui->sceneView->currentIndex();
241 if (currentSelection.parent().isValid()) {
242 currentSelection = currentSelection.parent();
243 }
244
245 m_storyboardModel->insertItem(currentSelection, true);
246 });
247 action->setIcon(KisIconUtils::loadIcon("list-add"));
248 m_ui->btnCreateScene->setAutoRaise(true);
249 m_ui->btnCreateScene->setIconSize(QSize(22,22));
250 m_ui->btnCreateScene->setDefaultAction(action);
251
252 action = new QAction(i18nc("Remove current scene from storyboards", "Remove Scene"), this);
253 connect(action, &QAction::triggered, this, [this](bool){
254 if (!m_canvas) return;
255
256 QModelIndex currentSelection = m_ui->sceneView->currentIndex();
257 if (currentSelection.parent().isValid()) {
258 currentSelection = currentSelection.parent();
259 }
260
261 if (currentSelection.isValid()) {
262 int row = currentSelection.row();
263 KisRemoveStoryboardCommand *command = new KisRemoveStoryboardCommand(row, m_storyboardModel->getData().at(row), m_storyboardModel.data());
264
265 m_storyboardModel->removeItem(currentSelection, command);
266 m_storyboardModel->pushUndoCommand(command);
267 }
268 });
269 action->setIcon(KisIconUtils::loadIcon("edit-delete"));
270 m_ui->btnDeleteScene->setAutoRaise(true);
271 m_ui->btnDeleteScene->setIconSize(QSize(22,22));
272 m_ui->btnDeleteScene->setDefaultAction(action);
273 }
274
275 setEnabled(false);
276}
277
284
286{
287 if (m_canvas == canvas) {
288 return;
289 }
290
291 if (m_canvas) {
292 disconnect(m_storyboardModel.data(), SIGNAL(sigStoryboardItemListChanged()), this, SLOT(slotUpdateDocumentList()));
293 disconnect(m_commentModel, SIGNAL(sigCommentListChanged()), this, SLOT(slotUpdateDocumentList()));
294 disconnect(m_canvas->imageView()->document(), SIGNAL(sigStoryboardItemListChanged()), this, SLOT(slotUpdateStoryboardModelList()));
295 disconnect(m_canvas->imageView()->document(), SIGNAL(sigStoryboardItemListChanged()), this, SLOT(slotUpdateCommentModelList()));
296
297 //update the lists in KisDocument and empty storyboardModel's list and commentModel's list
301 m_storyboardModel->slotSetActiveNode(nullptr);
302 }
303
304 m_canvas = dynamic_cast<KisCanvas2*>(canvas);
305 setEnabled(m_canvas != 0);
306
307 if (m_canvas && m_canvas->image()) {
308 //sync data between KisDocument and models
311
312 connect(m_storyboardModel.data(), SIGNAL(sigStoryboardItemListChanged()), SLOT(slotUpdateDocumentList()), Qt::UniqueConnection);
313 connect(m_commentModel, SIGNAL(sigCommentListChanged()), SLOT(slotUpdateDocumentList()), Qt::UniqueConnection);
314 connect(m_canvas->imageView()->document(), SIGNAL(sigStoryboardItemListChanged()), this, SLOT(slotUpdateStoryboardModelList()), Qt::UniqueConnection);
315 connect(m_canvas->imageView()->document(), SIGNAL(sigStoryboardCommentListChanged()), this, SLOT(slotUpdateCommentModelList()), Qt::UniqueConnection);
316
317 m_storyboardModel->setImage(m_canvas->image());
318 m_storyboardDelegate->setImageSize(m_canvas->image()->size());
319 connect(m_canvas->image(), SIGNAL(sigAboutToBeDeleted()), SLOT(notifyImageDeleted()), Qt::UniqueConnection);
320
321 if (m_nodeManager) {
322 m_storyboardModel->slotSetActiveNode(m_nodeManager->activeNode());
323 }
324 }
325
328}
329
334
336{
337 m_nodeManager = kisview->nodeManager();
338 if (m_nodeManager) {
339 connect(m_nodeManager, SIGNAL(sigNodeActivated(KisNodeSP)), m_storyboardModel.data(), SLOT(slotSetActiveNode(KisNodeSP)));
340 }
341}
342
343
345{
346 //if there is no image
347 if (!m_canvas || !m_canvas->image()){
348 m_storyboardModel->setImage(0);
349 }
350}
351
353{
354 m_canvas->imageView()->document()->setStoryboardItemList(m_storyboardModel->getData());
355 m_canvas->imageView()->document()->setStoryboardCommentList(m_commentModel->getData());
356}
357
359{
360 m_storyboardModel->resetData(m_canvas->imageView()->document()->getStoryboardItemList());
361}
362
364{
365 m_commentModel->resetData(m_canvas->imageView()->document()->getStoryboardCommentsList());
366}
367
372
377
379{
380 QFileInfo fileInfo(m_canvas->imageView()->document()->path());
381 const QString imageFileName = fileInfo.baseName();
382 const int storyboardCount = m_storyboardModel->rowCount();
383 KIS_SAFE_ASSERT_RECOVER_RETURN(storyboardCount > 0);
385
386 if (dlg.exec() == QDialog::Accepted) {
387 dlg.hide();
388 KisCursorOverrideLock cursorLock(Qt::WaitCursor);
389
390 ExportPage layoutPage;
391 QPrinter printer(QPrinter::HighResolution);
392
393 // Setup export parameters...
394 QPainter painter;
395 QSvgGenerator generator;
396
397 QFont font = painter.font();
398 font.setPointSize(dlg.fontSize());
399
400 StoryboardItemList storyboardList = m_storyboardModel->getData();
401
402 // Setup per-element layout details...
403 bool layoutSpecifiedBySvg = dlg.layoutSpecifiedBySvgFile();
404 if (layoutSpecifiedBySvg) {
405 QString svgFileName = dlg.layoutSvgFile();
406 layoutPage = getPageLayout(svgFileName, &printer);
407 }
408 else {
409 int rows = dlg.rows();
410 int columns = dlg.columns();
411 printer.setOutputFileName(dlg.saveFileName());
412 printer.setPageSize(dlg.pageSize());
413 printer.setPageOrientation(dlg.pageOrientation());
414 painter.begin(&printer); // We need to begin the painter temporarily for font metrics.
415 painter.setFont(font);
416 layoutPage = getPageLayout(rows,
417 columns,
418 QRect(0, 0, m_canvas->image()->width(), m_canvas->image()->height()),
419 printer.pageLayout().paintRectPixels(printer.resolution()),
420 painter.fontMetrics());
421 painter.end(); // End temporary painter begin.
422 }
423
424 KIS_SAFE_ASSERT_RECOVER_RETURN(layoutPage.elements.length() > 0);
425
426 // Get a range of items to render. Used to be configurable in an older version but now simplified...
427 QModelIndex firstIndex = m_storyboardModel->index(0,0);
428 QModelIndex lastIndex = m_storyboardModel->index(m_storyboardModel->rowCount() - 1, 0);
429
430 if (!firstIndex.isValid() || !lastIndex.isValid()) {
431 QMessageBox::warning((QWidget*)(&dlg), i18nc("@title:window", "Krita"), i18n("Please enter correct range. There are no panels in the range of frames provided."));
432 return;
433 }
434
435 int firstItemRow = firstIndex.row();
436 int lastItemRow = lastIndex.row();
437
438 int numBoards = lastItemRow - firstItemRow + 1;
439 if (numBoards <= 0) {
440 QMessageBox::warning((QWidget*)(&dlg), i18nc("@title:window", "Krita"), i18n("Please enter correct range. There are no panels in the range of frames provided."));
441 return;
442 }
443
444 if (dlg.format() == ExportFormat::SVG) {
445 generator.setFileName(dlg.saveFileName() + "/" + imageFileName + "0.svg");
446 QSize sz = printer.pageLayout().paintRectPixels(printer.resolution()).size();
447 generator.setSize(sz);
448 generator.setViewBox(QRect(0, 0, sz.width(), sz.height()));
449 generator.setResolution(printer.resolution());
450 painter.begin(&generator);
451 painter.setBrush(QBrush(QColor(255,255,255)));
452 painter.drawRect(QRect(0,0, sz.width(), sz.height()));
453 }
454 else {
455 printer.setOutputFileName(dlg.saveFileName());
456 printer.setOutputFormat(QPrinter::PdfFormat);
457 painter.begin(&printer);
458 painter.setFont(font);
459 painter.setBackgroundMode(Qt::BGMode::OpaqueMode);
460 }
461
462 // Paint boards
463 int pageNumber = 1;
464 int pageDurationInFrames = 0;
465
466 for (int currentBoard = 0; currentBoard < numBoards; currentBoard++) {
467 if (currentBoard % layoutPage.elements.length() == 0) {
468 if (dlg.format() == ExportFormat::SVG) {
469 if (currentBoard != 0) {
470 painter.end();
471 painter.eraseRect(printer.pageLayout().paintRectPixels(printer.resolution()));
472 generator.setFileName(dlg.saveFileName() + "/" + imageFileName + QString::number(currentBoard / layoutPage.elements.length()) + ".svg");
473 QSize sz = printer.pageLayout().paintRectPixels(printer.resolution()).size();
474 generator.setSize(sz);
475 generator.setViewBox(QRect(0, 0, sz.width(), sz.height()));
476 generator.setResolution(printer.resolution());
477 painter.begin(&generator);
478 }
479 }
480 else {
481 if (currentBoard != 0 ) { // New page!
482 printer.newPage();
483
484 pageNumber++;
485 pageDurationInFrames = 0;
486 }
487
488 if(layoutPage.svg) {
489 QMap<QString, QDomNode> groups = rootItemsInSvg(layoutPage.svg.value());
490 if (groups.contains("overlay")) {
491 QMapIterator<QString, QDomNode> iter(groups);
492 while(iter.hasNext()) {
493 iter.next();
494 if (iter.key() == "overlay" || iter.key() == "layout") {
495 iter.value().toElement().setAttribute("display","none");
496 } else {
497 iter.value().toElement().setAttribute("display","inline");
498 }
499 }
500 }
501 QSvgRenderer renderer(layoutPage.svg->toByteArray());
502 renderer.render(&painter);
503 }
504 }
505 }
506
507 ThumbnailData data = qvariant_cast<ThumbnailData>(storyboardList.at(currentBoard + firstItemRow)->child(StoryboardItem::FrameNumber)->data());
508 QPixmap pxmp = qvariant_cast<QPixmap>(data.pixmap);
509 QVector<StoryboardComment> comments = m_commentModel->getData();
510 const int numComments = comments.size();
511
512 const ExportPageShot* const layoutShot = &layoutPage.elements[currentBoard % layoutPage.elements.length()];
513
514 QPen pen(QColor(1, 0, 0));
515 pen.setWidth(5);
516 painter.setPen(pen);
517
518 // Draw image
519 if (layoutShot->cutImageRect.has_value()) {
520 QRectF imgRect = layoutShot->cutImageRect.value();
521 QSizeF resizedImage = QSizeF(pxmp.size()).scaled(layoutShot->cutImageRect->size(), Qt::KeepAspectRatio);
522 const int MARGIN = -2;
523 resizedImage = QSize(resizedImage.width() + MARGIN * 2, resizedImage.height() + MARGIN * 2);
524 imgRect.setSize(resizedImage);
525 imgRect.translate((layoutShot->cutImageRect.value().width() - imgRect.size().width()) / 2 - MARGIN,
526 (layoutShot->cutImageRect->height() - imgRect.size().height()) / 2 - MARGIN);
527 painter.drawPixmap(imgRect, pxmp, pxmp.rect());
528 painter.drawRect(layoutShot->cutImageRect.value());
529 }
530
531 { // Insert shot text elements...
532 painter.save();
533 painter.setBackgroundMode(Qt::TransparentMode);
534
535 // Draw shot name
536 if (layoutShot->cutNameRect.has_value()) {
537 QString str = storyboardList.at(currentBoard + firstItemRow)->child(StoryboardItem::ItemName)->data().toString();
538 painter.drawText(layoutShot->cutNameRect.value().translated(painter.fontMetrics().averageCharWidth() / 2, 0), Qt::AlignLeft | Qt::AlignVCenter, str);
539
540 if (!layoutPage.svg) {
541 painter.drawRect(layoutShot->cutNameRect.value());
542 }
543 }
544
545 // Draw shot number
546 if (layoutShot->cutNumberRect.has_value()) {
547 painter.drawText(layoutShot->cutNumberRect.value(), Qt::AlignCenter, QString::number(currentBoard + firstItemRow));
548
549 if (!layoutPage.svg) {
550 painter.drawRect(layoutShot->cutNumberRect.value());
551 }
552 }
553
554 QModelIndex boardIndex = m_storyboardModel->index(currentBoard + firstItemRow, 0);
555
556 const int boardDurationFrames = m_storyboardModel->data(boardIndex, StoryboardModel::TotalSceneDurationInFrames).toInt();
557 pageDurationInFrames += boardDurationFrames;
558
559 // Draw shot duration
560 if (layoutShot->cutDurationRect.has_value()) {
561 // Split shot duration into tuple of seconds + frames (remainder)..
562 int durationSecondsPart = boardDurationFrames / m_storyboardModel->getFramesPerSecond();
563 int durationFramesPart = boardDurationFrames % m_storyboardModel->getFramesPerSecond();
564
565 painter.drawText(layoutShot->cutDurationRect.value(), Qt::AlignCenter,
566 buildDurationString(durationSecondsPart, durationFramesPart));
567
568 if (!layoutPage.svg) {
569 painter.drawRect(layoutShot->cutDurationRect.value());
570 }
571 }
572
573 painter.restore();
574 }
575
576
577 // Draw shot comments
578 for (int commentIndex = 0; commentIndex < numComments; commentIndex++) {
579 if (!layoutShot->commentRects.contains(comments[commentIndex].name))
580 continue;
581
582 const QString& commentName = comments[commentIndex].name;
583
584 QTextDocument doc;
585 doc.setDocumentMargin(0);
586 doc.setDefaultFont(painter.font());
587 QString comment;
588 comment += "<p><b>" + commentName + "</b></p>"; // if arrange options are used check for visibility
589 QString originalCommentText = qvariant_cast<CommentBox>(storyboardList.at(currentBoard + firstItemRow)->child(StoryboardItem::Comments + commentIndex)->data()).content.toString();
590 originalCommentText = originalCommentText.replace('\n', "</p><p>");
591 comment += "<p>&nbsp;" + originalCommentText + "</p>";
592 const int MARGIN = painter.fontMetrics().averageCharWidth() / 2;
593
594 doc.setHtml(comment);
595 doc.setTextWidth(layoutShot->commentRects[commentName].width() - MARGIN * 2);
596
597 QAbstractTextDocumentLayout::PaintContext ctx;
598 ctx.palette.setColor(QPalette::Text, painter.pen().color());
599
600 //draw the comments
601 painter.save();
602 painter.translate(layoutShot->commentRects[commentName].topLeft() + QPoint(MARGIN, MARGIN));
603 painter.setClipRegion(QRegion(0,0,layoutShot->commentRects[commentName].width(), layoutShot->commentRects[commentName].height() - painter.fontMetrics().height()));
604 painter.setBackgroundMode(Qt::TransparentMode);
605 doc.documentLayout()->draw(&painter, ctx);
606 painter.restore();
607
608 painter.drawRect(layoutShot->commentRects[commentName]);
609 }
610
611 // Draw overlays after drawing last element on page or after drawing last element in general.
612 if ((currentBoard % layoutPage.elements.length()) == (layoutPage.elements.length() - 1) || (currentBoard == numBoards - 1)) {
613
614 painter.save();
615 painter.setBackgroundMode(Qt::TransparentMode);
616
617 if (layoutPage.pageNumberRect) {
618 painter.drawText(layoutPage.pageNumberRect.value(), Qt::AlignCenter, QString::number(pageNumber));
619 }
620
621 if (layoutPage.pageTimeRect) {
622 int pageDurationSecondsPart = pageDurationInFrames / m_storyboardModel->getFramesPerSecond();
623 int pageDurationFramesPart = pageDurationInFrames % m_storyboardModel->getFramesPerSecond();
624
625 painter.drawText(layoutPage.pageTimeRect.value(), Qt::AlignCenter, buildDurationString(pageDurationSecondsPart, pageDurationFramesPart));
626 }
627
628 if (layoutPage.svg) {
629 QMap<QString, QDomNode> groups = rootItemsInSvg(layoutPage.svg.value());
630 if (groups.contains("overlay")) {
631 QMapIterator<QString, QDomNode> iter(groups);
632 while(iter.hasNext()) {
633 iter.next();
634 if (iter.key() == "overlay") {
635 iter.value().toElement().setAttribute("display","inline");
636 } else {
637 iter.value().toElement().setAttribute("display","none");
638 }
639 }
640 }
641 QSvgRenderer renderer(layoutPage.svg->toByteArray());
642 renderer.render(&painter);
643 }
644
645 painter.restore();
646 }
647 }
648 painter.end();
649 }
650}
651
653 if (isLocked) {
654 m_lockAction->setIcon(KisIconUtils::loadIcon("locked"));
655 m_storyboardModel->setLocked(true);
656 }
657 else {
658 m_lockAction->setIcon(KisIconUtils::loadIcon("unlocked"));
659 m_storyboardModel->setLocked(false);
660 }
661}
662
664{
665 int mode = m_modeGroup->id(button);
666 if (mode == Mode::Column) {
667 m_ui->sceneView->setFlow(QListView::LeftToRight);
668 m_ui->sceneView->setWrapping(false);
669 m_ui->sceneView->setItemOrientation(Qt::Vertical);
670 m_viewGroup->button(View::CommentsOnly)->setEnabled(true);
671 }
672 else if (mode == Mode::Row) {
673 m_ui->sceneView->setFlow(QListView::TopToBottom);
674 m_ui->sceneView->setWrapping(false);
675 m_ui->sceneView->setItemOrientation(Qt::Horizontal);
676 m_viewGroup->button(View::CommentsOnly)->setEnabled(false); //disable the comments only view
677 }
678 else if (mode == Mode::Grid) {
679 m_ui->sceneView->setFlow(QListView::LeftToRight);
680 m_ui->sceneView->setWrapping(true);
681 m_ui->sceneView->setItemOrientation(Qt::Vertical);
682 m_viewGroup->button(View::CommentsOnly)->setEnabled(true);
683 }
684 m_storyboardModel->layoutChanged();
685}
686
688{
689 int view = m_viewGroup->id(button);
690 if (view == View::All) {
691 m_ui->sceneView->setCommentVisibility(true);
692 m_ui->sceneView->setThumbnailVisibility(true);
693 m_modeGroup->button(Mode::Row)->setEnabled(true);
694 }
695 else if (view == View::ThumbnailsOnly) {
696 m_ui->sceneView->setCommentVisibility(false);
697 m_ui->sceneView->setThumbnailVisibility(true);
698 m_modeGroup->button(Mode::Row)->setEnabled(true);
699 }
700
701 else if (view == View::CommentsOnly) {
702 m_ui->sceneView->setCommentVisibility(true);
703 m_ui->sceneView->setThumbnailVisibility(false);
704 m_modeGroup->button(Mode::Row)->setEnabled(false); //disable the row mode
705 }
706 m_storyboardModel->layoutChanged();
707}
708
710{
711 m_ui->sceneView->setMinimumSize(m_ui->sceneView->sizeHint());
712}
713
715{
716 if (m_storyboardModel) {
717 m_ui->btnExport->setDisabled(m_storyboardModel->rowCount() == 0);
718 }
719}
720
721StoryboardDockerDock::ExportPage StoryboardDockerDock::getPageLayout(int rows, int columns, const QRect& imageSize, const QRect& pageRect, const QFontMetrics& fontMetrics)
722{
723 QSizeF pageSize = pageRect.size();
724 QRectF border = pageRect;
725 QSizeF cellSize(pageSize.width() / columns, pageSize.height() / rows);
726 QVector<QRectF> rects;
727
728 int numericFontWidth = fontMetrics.horizontalAdvance("0");
729
730 for (int row = 0; row < rows; row++) {
731
732 QRectF cellRect = border;
733 cellRect.moveTop(border.top() + row * cellSize.height());
734 cellRect.setSize(cellSize - QSize(200,200));
735 for (int column = 0; column < columns; column++) {
736 cellRect.moveLeft(border.left() + column * cellSize.width());
737 cellRect.setSize(cellSize * 0.9);
738 rects.push_back(cellRect);
739 }
740 }
741
743
744 for (int i = 0; i < rects.length(); i++) {
745 QRectF& cellRect = rects[i];
746
747 const bool horizontal = cellRect.width() > cellRect.height(); // Determine general image / text flow orientation.
748 ExportPageShot layout;
749
750 QVector<StoryboardComment> comments = m_commentModel->getData();
751 const int numComments = comments.size();
752
753 if (horizontal) {
754 QRectF sourceRect = cellRect;
755 layout.cutDurationRect = kisTrimTop(fontMetrics.height() * 1.5, sourceRect);
756 layout.cutNameRect = kisTrimLeft(layout.cutDurationRect.value().width() - numericFontWidth * 6, layout.cutDurationRect.value());
757
758 const int imageWidth = sourceRect.height() * static_cast<qreal>(imageSize.width()) / static_cast<qreal>(imageSize.height());
759 layout.cutImageRect = kisTrimLeft(imageWidth, sourceRect);
760 const float commentWidth = sourceRect.width() / numComments;
761 if (commentWidth > 100) {
762 for (int i = 0; i < numComments; i++) {
763 QRectF rect = kisTrimLeft(commentWidth, sourceRect);
764 layout.commentRects.insert(comments[i].name, rect);
765 }
766 }
767 } else {
768 QRectF sourceRect = cellRect;
769 layout.cutDurationRect = kisTrimTop(fontMetrics.height() * 1.5, sourceRect);
770 layout.cutNameRect = kisTrimLeft(layout.cutDurationRect.value().width() - numericFontWidth * 6, layout.cutDurationRect.value());
771
772 const int imageHeight = sourceRect.width() * static_cast<qreal>(imageSize.height()) / static_cast<qreal>(imageSize.width());
773 layout.cutImageRect = kisTrimTop(imageHeight, sourceRect);
774 const float commentHeight = sourceRect.height() / numComments;
775 if (commentHeight > 200) {
776 for (int i = 0; i < numComments; i++) {
777 QRectF rect = kisTrimTop(commentHeight, sourceRect);
778 layout.commentRects.insert(comments[i].name, rect);
779 }
780 }
781 }
782
783 elements.push_back(layout);
784 }
785
786 ExportPage layout;
787 layout.elements = elements;
788 return layout;
789}
790
792{
793 QDomDocument svgDoc;
794 ExportPage page;
796
797 // Load DOM from file...
798 QFile f(layoutSvgFileName);
799 if (!f.open(QIODevice::ReadOnly ))
800 {
801 qDebug()<<"svg layout file didn't open";
802 return page;
803 }
804
805 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(svgDoc.setContent(&f), page);
806 f.close();
807
808 QDomElement eroot = svgDoc.documentElement();
809
810 QStringList lst = eroot.attribute("viewBox").split(" ");
811 QSizeF sizeMM(lst.at(2).toDouble(), lst.at(3).toDouble());
812 printer->setPageSize(QPageSize(sizeMM, QPageSize::Millimeter));
813 QSizeF size = printer->pageLayout().paintRectPixels(printer->resolution()).size();
814 QSizeF scaling = QSizeF( size.width() / sizeMM.width(), size.height() / sizeMM.height());
815
816 QVector<QString> commentLayers;
817 Q_FOREACH( StoryboardComment channel, m_commentModel->getData()) {
818 commentLayers.push_back(channel.name);
819 }
820
821 QMap<int, ExportPageShot> elementMap;
822
823 { // Go through all root-level svg data and preconfigure...
824 QMap<QString, QDomNode> groupsMap = rootItemsInSvg(svgDoc);
825
826 KIS_ASSERT_RECOVER_RETURN_VALUE(groupsMap.contains("layout"), page);
827
828 groupsMap["layout"].toElement().setAttribute("display", "none");
829 if (groupsMap.contains("overlay")) {
830 groupsMap["overlay"].toElement().setAttribute("display", "nonde");
831 }
832
833 QDomNodeList nodeList = groupsMap["layout"].toElement().elementsByTagName("rect");
834 for(int i = 0; i < nodeList.size(); i++) {
835 QDomNode node = nodeList.at(i);
836 QDomNamedNodeMap attrMap = node.attributes();
837
838 for (int j = 0; j < attrMap.length(); j++) {
839 QDomAttr attribute = attrMap.item(j).toAttr();
840 QString afterNamespace = attribute.name().split(":").last();
841
842 auto isValidLabel = [&](QString label, int& index) -> bool {
843 if (attribute.value().startsWith(label)) {
844 if (attribute.value() == label) {
845 index = 0;
846 return true;
847 }
848
849 QString indexString = attribute.value().remove(0, label.length());
850 bool ok = false;
851 index = indexString.toInt(&ok);
852 if (ok) {
853 if (!elementMap.contains(index))
854 elementMap.insert(index, ExportPageShot());
855 return true;
856 }
857 }
858 return false;
859 };
860
861 auto extractRect = [&](boost::optional<QRectF>& to) {
862 double x = scaling.width() * attrMap.namedItem("x").nodeValue().toDouble();
863 double y = scaling.height() * attrMap.namedItem("y").nodeValue().toDouble();
864 double width = scaling.width() * attrMap.namedItem("width").nodeValue().toDouble();
865 double height = scaling.height() * attrMap.namedItem("height").nodeValue().toDouble();
866 to = QRectF(x,y, width, height);
867 };
868
869 if (afterNamespace == "label") {
870 int index = 0;
871 if (isValidLabel("image", index)) {
872 extractRect(elementMap[index].cutImageRect);
873 } else if (isValidLabel("time",index)) {
874 extractRect(elementMap[index].cutDurationRect);
875 } else if (isValidLabel("name", index)) {
876 extractRect(elementMap[index].cutNameRect);
877 } else if (isValidLabel("shot", index)) {
878 extractRect(elementMap[index].cutNumberRect);
879 } else if (isValidLabel("page-time", index)) {
880 extractRect(page.pageTimeRect);
881 } else if (isValidLabel("page-number", index)) {
882 extractRect(page.pageNumberRect);
883 } else {
884 for(int commentIndex = 0; commentIndex < commentLayers.length(); commentIndex++) {
885 const QString& comment = commentLayers[commentIndex];
886 if (isValidLabel(comment.toLower(), index)) {
887 boost::optional<QRectF> rect;
888 extractRect(rect);
889 if (rect) {
890 elementMap[index].commentRects.insert(comment, rect.value());
891 }
892 }
893 }
894 }
895 }
896 }
897
898 }
899 }
900
901 // Sort fetched elements and push to array to return...
902 QList<int> indices = elementMap.keys();
903 std::sort(indices.begin(), indices.end(), [](const int& a, const int& b){
904 return a < b;
905 });
906
907 Q_FOREACH(const int& index, indices){
908 elements.push_back(elementMap[index]);
909 }
910
911 page.svg = svgDoc;
912 page.elements = elements;
913 return page;
914}
915
916QString StoryboardDockerDock::buildDurationString(int seconds, int frames)
917{
918 QString durationString = QString::number(seconds);
919 durationString += i18nc("suffix in spin box in storyboard that means 'seconds'", "s");
920 durationString += "+";
921 durationString += QString::number(frames);
922 durationString += i18nc("suffix in spin box in storyboard that means 'frames'", "f");
923 return durationString;
924}
925
926#include "StoryboardDockerDock.moc"
927
#define MARGIN
QMap< QString, QDomNode > rootItemsInSvg(const QDomDocument &d)
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
QButtonGroup * modeGroup
QButtonGroup * getModeGroup()
QButtonGroup * viewGroup
QButtonGroup * getViewGroup()
ArrangeMenu(QWidget *parent)
QScopedPointer< Ui_WdgArrangeMenu > m_menuUI
Paints the comment menu of the storyboard docker and creates widgets for editing data in CommentModel...
QScopedPointer< Ui_WdgCommentMenu > m_menuUI
CommentDelegate * delegate
CommentMenu(QWidget *parent, StoryboardCommentModel *m_model)
StoryboardCommentModel * model
QPageLayout::Orientation pageOrientation() const
bool layoutSpecifiedBySvgFile() const
QPageSize pageSize() const
ExportFormat format() const
KisImageWSP image() const
QPointer< KisView > imageView() const
qint32 width() const
QSize size() const
Definition kis_image.h:547
qint32 height() const
KisNodeSP activeNode()
Convenience function to get the active layer or mask.
KisNodeManager * nodeManager() const
The node manager handles everything about nodes.
void sigCommentListChanged()
Emitted whenever m_items is changed. it is used to keep the StoryboardItemList in KisDocument in sync...
bool insertRows(int position, int rows, const QModelIndex &index=QModelIndex()) override
bool removeRows(int position, int rows, const QModelIndex &index=QModelIndex()) override
void slotModelChanged()
called to reflect changes to the model.
void slotUpdateMinimumWidth()
called to update minimum width on reaction to model changes. Should change based on available content...
QScopedPointer< Ui_WdgStoryboardDock > m_ui
void setCanvas(KoCanvasBase *canvas) override
void slotLockClicked(bool)
called when lock toggle button is clicked.
void slotUpdateStoryboardModelList()
sets the StoryboardModel's storyboardItemList to be the same as KisDocument's storyboardItemList
QPointer< StoryboardCommentModel > m_commentModel
void slotUpdateCommentModelList()
sets the CommentModel's comment list to be the same as KisDocument's storyboardCommentList
void slotModeChanged(QAbstractButton *)
called when a mode option is selected in Arrange menu.
QPointer< StoryboardDelegate > m_storyboardDelegate
void slotExport(ExportFormat format)
Creates the DlgExportStoryboard and performs the actual export.
void slotViewChanged(QAbstractButton *)
called when a view option is selected in Arrange menu.
void notifyImageDeleted()
sets the image in Model to nullptr if there is no canvas set or no KisImage
QSharedPointer< StoryboardModel > m_storyboardModel
void slotUpdateDocumentList()
sets the KisDocument's storyboardItemList to be the same as StoryboardModel's storyboardItemList and ...
KisNodeManager * m_nodeManager
QString buildDurationString(int seconds, int frames)
ExportPage getPageLayout(int rows, int columns, const QRect &imageSize, const QRect &pageRect, const QFontMetrics &painter)
void setViewManager(KisViewManager *kisview) override
void slotExportAsPdf()
calls slotExport(ExportFormat) with PDF parameter.
void slotExportAsSvg()
calls slotExport(ExportFormat) with SVG parameter.
@ FrameNumber
Store the frame number at index 0. Data type stored here should be ThumbnailData.
@ ItemName
Store the item name at index 1. Data type stored here should be string.
@ Comments
Store the comments at indices greater_than_or_equal_to to index 4. Data type stored here should be Co...
The main storyboard model. This class manages a StoryboardItemList which is a list of StoryboardItem ...
This class is a simple combination of two QVariants. It can be converted to and from QVariant type an...
QVariant pixmap
a scaled down thumbnail version of the frame
Definition View.h:25
#define KIS_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:85
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
QRectF kisTrimTop(int height, QRectF &toTakeFrom)
Definition kis_global.h:305
QRectF kisTrimLeft(int width, QRectF &toTakeFrom)
Definition kis_global.h:288
QString button(const QWheelEvent &ev)
QVector< StoryboardItemSP > StoryboardItemList
Definition kis_types.h:324
QIcon loadIcon(const QString &name)
boost::optional< QRectF > cutDurationRect
boost::optional< QRectF > pageTimeRect
QVector< ExportPageShot > elements
boost::optional< QDomDocument > svg
boost::optional< QRectF > pageNumberRect