171 : QDockWidget(i18nc(
"Storyboard Docker",
"Storyboard"))
173 , m_ui(new Ui_WdgStoryboardDock())
174 , m_exportMenu(new QMenu(this))
176 , m_commentMenu(new
CommentMenu(this, m_commentModel))
181 QWidget* mainWidget =
new QWidget(
this);
182 setWidget(mainWidget);
183 m_ui->setupUi(mainWidget);
186 m_ui->btnExport->setPopupMode(QToolButton::InstantPopup);
204 m_ui->btnComment->setPopupMode(QToolButton::InstantPopup);
207 i18nc(
"Freeze keyframe positions and ignore storyboard adjustments",
"Freeze Keyframe Data"),
m_ui->btnLock);
210 m_ui->btnLock->setIconSize(QSize(16, 16));
214 m_ui->btnArrange->setPopupMode(QToolButton::InstantPopup);
216 m_ui->btnArrange->setAutoRaise(
true);
217 m_ui->btnArrange->setIconSize(QSize(16, 16));
236 QAction* action =
new QAction(i18nc(
"Add new scene as the last storyboard",
"Add Scene"),
this);
237 connect(action, &QAction::triggered,
this, [
this](
bool){
240 QModelIndex currentSelection =
m_ui->sceneView->currentIndex();
241 if (currentSelection.parent().isValid()) {
242 currentSelection = currentSelection.parent();
248 m_ui->btnCreateScene->setAutoRaise(
true);
249 m_ui->btnCreateScene->setIconSize(QSize(22,22));
250 m_ui->btnCreateScene->setDefaultAction(action);
252 action =
new QAction(i18nc(
"Remove current scene from storyboards",
"Remove Scene"),
this);
253 connect(action, &QAction::triggered,
this, [
this](
bool){
256 QModelIndex currentSelection =
m_ui->sceneView->currentIndex();
257 if (currentSelection.parent().isValid()) {
258 currentSelection = currentSelection.parent();
261 if (currentSelection.isValid()) {
262 int row = currentSelection.row();
263 KisRemoveStoryboardCommand *command = new KisRemoveStoryboardCommand(row, m_storyboardModel->getData().at(row), m_storyboardModel.data());
265 m_storyboardModel->removeItem(currentSelection, command);
266 m_storyboardModel->pushUndoCommand(command);
270 m_ui->btnDeleteScene->setAutoRaise(
true);
271 m_ui->btnDeleteScene->setIconSize(QSize(22,22));
272 m_ui->btnDeleteScene->setDefaultAction(action);
381 const QString imageFileName = fileInfo.baseName();
386 if (dlg.exec() == QDialog::Accepted) {
388 KisCursorOverrideLock cursorLock(Qt::WaitCursor);
391 QPrinter printer(QPrinter::HighResolution);
395 QSvgGenerator generator;
397 QFont font = painter.font();
404 if (layoutSpecifiedBySvg) {
409 int rows = dlg.
rows();
412 printer.setPageSize(dlg.
pageSize());
414 painter.begin(&printer);
415 painter.setFont(font);
419 printer.pageLayout().paintRectPixels(printer.resolution()),
420 painter.fontMetrics());
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."));
435 int firstItemRow = firstIndex.row();
436 int lastItemRow = lastIndex.row();
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."));
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()));
456 printer.setOutputFormat(QPrinter::PdfFormat);
457 painter.begin(&printer);
458 painter.setFont(font);
459 painter.setBackgroundMode(Qt::BGMode::OpaqueMode);
464 int pageDurationInFrames = 0;
466 for (
int currentBoard = 0; currentBoard < numBoards; currentBoard++) {
467 if (currentBoard % layoutPage.
elements.length() == 0) {
469 if (currentBoard != 0) {
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);
481 if (currentBoard != 0 ) {
485 pageDurationInFrames = 0;
490 if (groups.contains(
"overlay")) {
491 QMapIterator<QString, QDomNode> iter(groups);
492 while(iter.hasNext()) {
494 if (iter.key() ==
"overlay" || iter.key() ==
"layout") {
495 iter.value().toElement().setAttribute(
"display",
"none");
497 iter.value().toElement().setAttribute(
"display",
"inline");
501 QSvgRenderer renderer(layoutPage.
svg->toByteArray());
502 renderer.render(&painter);
508 QPixmap pxmp = qvariant_cast<QPixmap>(data.
pixmap);
510 const int numComments = comments.size();
514 QPen pen(QColor(1, 0, 0));
521 QSizeF resizedImage = QSizeF(pxmp.size()).scaled(layoutShot->
cutImageRect->size(), Qt::KeepAspectRatio);
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,
527 painter.drawPixmap(imgRect, pxmp, pxmp.rect());
533 painter.setBackgroundMode(Qt::TransparentMode);
538 painter.drawText(layoutShot->
cutNameRect.value().translated(painter.fontMetrics().averageCharWidth() / 2, 0), Qt::AlignLeft | Qt::AlignVCenter, str);
540 if (!layoutPage.
svg) {
547 painter.drawText(layoutShot->
cutNumberRect.value(), Qt::AlignCenter, QString::number(currentBoard + firstItemRow));
549 if (!layoutPage.
svg) {
554 QModelIndex boardIndex =
m_storyboardModel->index(currentBoard + firstItemRow, 0);
557 pageDurationInFrames += boardDurationFrames;
562 int durationSecondsPart = boardDurationFrames /
m_storyboardModel->getFramesPerSecond();
563 int durationFramesPart = boardDurationFrames %
m_storyboardModel->getFramesPerSecond();
568 if (!layoutPage.
svg) {
578 for (
int commentIndex = 0; commentIndex < numComments; commentIndex++) {
579 if (!layoutShot->
commentRects.contains(comments[commentIndex].name))
582 const QString& commentName = comments[commentIndex].name;
585 doc.setDocumentMargin(0);
586 doc.setDefaultFont(painter.font());
588 comment +=
"<p><b>" + commentName +
"</b></p>";
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> " + originalCommentText +
"</p>";
592 const int MARGIN = painter.fontMetrics().averageCharWidth() / 2;
594 doc.setHtml(comment);
597 QAbstractTextDocumentLayout::PaintContext ctx;
598 ctx.palette.setColor(QPalette::Text, painter.pen().color());
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);
608 painter.drawRect(layoutShot->
commentRects[commentName]);
612 if ((currentBoard % layoutPage.
elements.length()) == (layoutPage.
elements.length() - 1) || (currentBoard == numBoards - 1)) {
615 painter.setBackgroundMode(Qt::TransparentMode);
618 painter.drawText(layoutPage.
pageNumberRect.value(), Qt::AlignCenter, QString::number(pageNumber));
622 int pageDurationSecondsPart = pageDurationInFrames /
m_storyboardModel->getFramesPerSecond();
623 int pageDurationFramesPart = pageDurationInFrames %
m_storyboardModel->getFramesPerSecond();
628 if (layoutPage.
svg) {
630 if (groups.contains(
"overlay")) {
631 QMapIterator<QString, QDomNode> iter(groups);
632 while(iter.hasNext()) {
634 if (iter.key() ==
"overlay") {
635 iter.value().toElement().setAttribute(
"display",
"inline");
637 iter.value().toElement().setAttribute(
"display",
"none");
641 QSvgRenderer renderer(layoutPage.
svg->toByteArray());
642 renderer.render(&painter);
723 QSizeF pageSize = pageRect.size();
724 QRectF border = pageRect;
725 QSizeF cellSize(pageSize.width() / columns, pageSize.height() / rows);
728 int numericFontWidth = fontMetrics.horizontalAdvance(
"0");
730 for (
int row = 0; row < rows; row++) {
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);
744 for (
int i = 0; i < rects.length(); i++) {
745 QRectF& cellRect = rects[i];
747 const bool horizontal = cellRect.width() > cellRect.height();
751 const int numComments = comments.size();
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());
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++) {
764 layout.commentRects.insert(comments[i].name,
rect);
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());
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++) {
778 layout.commentRects.insert(comments[i].name,
rect);
783 elements.push_back(layout);
787 layout.elements = elements;
798 QFile f(layoutSvgFileName);
799 if (!f.open(QIODevice::ReadOnly ))
801 qDebug()<<
"svg layout file didn't open";
808 QDomElement eroot = svgDoc.documentElement();
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());
818 commentLayers.push_back(channel.
name);
821 QMap<int, ExportPageShot> elementMap;
828 groupsMap[
"layout"].toElement().setAttribute(
"display",
"none");
829 if (groupsMap.contains(
"overlay")) {
830 groupsMap[
"overlay"].toElement().setAttribute(
"display",
"nonde");
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();
838 for (
int j = 0; j < attrMap.length(); j++) {
839 QDomAttr attribute = attrMap.item(j).toAttr();
840 QString afterNamespace = attribute.name().split(
":").last();
842 auto isValidLabel = [&](QString label,
int& index) ->
bool {
843 if (attribute.value().startsWith(label)) {
844 if (attribute.value() == label) {
849 QString indexString = attribute.value().remove(0, label.length());
851 index = indexString.toInt(&ok);
853 if (!elementMap.contains(index))
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);
869 if (afterNamespace ==
"label") {
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)) {
881 }
else if (isValidLabel(
"page-number", index)) {
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;
890 elementMap[index].commentRects.insert(comment,
rect.value());
903 std::sort(indices.begin(), indices.end(), [](
const int& a,
const int& b){
907 Q_FOREACH(
const int& index, indices){
908 elements.push_back(elementMap[index]);