From cd46940982ca966f9a3f95795d7c12026734914d Mon Sep 17 00:00:00 2001 From: shun-iwasawa Date: Oct 16 2018 06:24:43 +0000 Subject: New Feature : Adding Clapperboard (#2314) * clapperboard * fixed image item and added aspect ratio prop * board -> clapperboard --- diff --git a/stuff/library/clapperboards/images/board1.png b/stuff/library/clapperboards/images/board1.png new file mode 100644 index 0000000..2d03ef7 Binary files /dev/null and b/stuff/library/clapperboards/images/board1.png differ diff --git a/stuff/library/clapperboards/preset_sample.clapperboard b/stuff/library/clapperboards/preset_sample.clapperboard new file mode 100644 index 0000000..bcc3fd3 --- /dev/null +++ b/stuff/library/clapperboards/preset_sample.clapperboard @@ -0,0 +1,190 @@ + + + 24 + + + + + ScenePath_Full + + + "Scene file path" + + + 0.15 0.566708 0.7 0.0433166 + + + 300 + + + 0 0 0 255 + + + Verdana 0 0 + + + + + UserName + + + "User name" + + + 0.7 0.9 0.2 0.05 + + + 300 + + + 0 128 0 255 + + + Verdana 0 1 + + + + + CurrentDate + + + "Rendering date" + + + 0.1 0.9 0.2 0.05 + + + 300 + + + 0 0 0 255 + + + Verdana 0 0 + + + + + FreeText + + + "Type some description here" + + + 0.15 0.629176 0.55 0.248329 + + + "" + + + 40 + + + 0 0 64 255 + + + Verdana 0 0 + + + + + Duration_SecFrame + + + "Scene duration" + + + 0.351376 0.424454 0.3 0.125546 + + + 300 + + + 0 0 0 255 + + + Verdana 0 0 + + + + + SceneName + + + Scene + + + 0.35 0.25 0.3 0.2 + + + 300 + + + 0 0 0 255 + + + Verdana 1 0 + + + + + FreeText + + + "Episode #" + + + 0.15 0.3 0.2 0.2 + + + "#" + + + 120 + + + 0 0 0 255 + + + Verdana 0 0 + + + + + ProjectName + + + Title + + + 0.15 0.1 0.7 0.15 + + + 300 + + + 0 0 0 255 + + + Verdana 1 0 + + + + + Image + + + Background + + + 0 0 1 1 + + + 1 "clapperboards\\images\\board1.png" + + + 0 + + + + diff --git a/toonz/sources/include/toonz/boardsettings.h b/toonz/sources/include/toonz/boardsettings.h new file mode 100644 index 0000000..98dacbb --- /dev/null +++ b/toonz/sources/include/toonz/boardsettings.h @@ -0,0 +1,141 @@ +#pragma once + +#ifndef BOARDSETTINGS_H +#define BOARDSETTINGS_H + +#include "traster.h" +#include "tfilepath.h" + +// TnzCore includes +#include "tstream.h" + +#include +#include +#include +#include + +#undef DVAPI +#undef DVVAR +#ifdef TOONZLIB_EXPORTS +#define DVAPI DV_EXPORT_API +#define DVVAR DV_EXPORT_VAR +#else +#define DVAPI DV_IMPORT_API +#define DVVAR DV_IMPORT_VAR +#endif + +class ToonzScene; + +class DVAPI BoardItem { +public: + enum Type { + FreeText = 0, + ProjectName, + SceneName, + Duration_Frame, + Duration_SecFrame, + Duration_HHMMSSFF, + CurrentDate, + CurrentDateTime, + UserName, + ScenePath_Aliased, + ScenePath_Full, + MoviePath_Aliased, + MoviePath_Full, + Image, + TypeCount + }; + +private: + QString m_name; + Type m_type; + + // item region, represented by ratio to the whole + QRectF m_rect; + + // Basically the font size will be automatically + // adjusted to fit the item region. + // The maximum font size can be used to make the font + // size smaller. + int m_maximumFontSize; + + // font color + QColor m_color; + + // font (familiy, bold, italic) + QFont m_font; + + QString m_text; + TFilePath m_imgPath; + Qt::AspectRatioMode m_imgARMode = Qt::KeepAspectRatio; + + QString getContentText(ToonzScene*); + +public: + BoardItem(); + + QRectF getRatioRect() { return m_rect; } + void setRatioRect(QRectF rect) { m_rect = rect; } + + // returns the item rect in pixels + QRectF getItemRect(QSize imgSize); + void drawItem(QPainter& p, QSize imgSize, int shrink, ToonzScene* scene); + + QString getName() { return m_name; } + void setName(QString name) { m_name = name; } + + Type getType() { return m_type; } + void setType(Type type) { m_type = type; } + + int getMaximumFontSize() { return m_maximumFontSize; } + void setMaximumFontSize(int size) { m_maximumFontSize = size; } + + QColor getColor() { return m_color; } + void setColor(QColor color) { m_color = color; } + + QFont& font() { return m_font; } + + QString getFreeText() { return m_text; } + void setFreeText(QString text) { m_text = text; } + + TFilePath getImgPath() { return m_imgPath; } + void setImgPath(TFilePath path) { m_imgPath = path; } + + Qt::AspectRatioMode getImgARMode() { return m_imgARMode; } + void setImgARMode(Qt::AspectRatioMode mode) { m_imgARMode = mode; } + + void saveData(TOStream& os); + void loadData(TIStream& is); +}; + +class DVAPI BoardSettings { + bool m_active = false; + int m_duration = 0; + QList m_items; + +public: + BoardSettings(); + + QImage getBoardImage(TDimension& dim, int shrink, ToonzScene* scene); + + TRaster32P getBoardRaster(TDimension& dim, int shrink, ToonzScene* scene); + + int getDuration() { return m_duration; } + + bool isActive() { return m_active; } + void setActive(bool on) { m_active = on; } + + int getItemCount() { return m_items.count(); } + BoardItem& getItem(int index) { return m_items[index]; } + + void setDuration(int f) { m_duration = f; } + + void addNewItem(int insertAt = 0); + void removeItem(int index); + void swapItems(int, int); + + void saveData(TOStream& os, bool forPreset = false); + void loadData(TIStream& is); +}; + +#endif \ No newline at end of file diff --git a/toonz/sources/include/toonzqt/colorfield.h b/toonz/sources/include/toonzqt/colorfield.h index 19b9c02..9aa5683 100644 --- a/toonz/sources/include/toonzqt/colorfield.h +++ b/toonz/sources/include/toonzqt/colorfield.h @@ -96,7 +96,7 @@ class DVAPI ChannelField final : public QWidget { public: ChannelField(QWidget *parent = 0, const QString &string = "", int value = 0, int maxValue = 255, bool horizontal = false, int labelWidth = 13, - int sliderWidth = 40); + int sliderWidth = -1); ~ChannelField() {} @@ -145,7 +145,7 @@ public: ColorField(QWidget *parent = 0, bool isAlphaActive = true, TPixel32 color = TPixel32(0, 0, 0, 255), int squareSize = 40, - bool useStyleEditor = true); + bool useStyleEditor = true, int sliderWidth = -1); ~ColorField() {} diff --git a/toonz/sources/include/toutputproperties.h b/toonz/sources/include/toutputproperties.h index 8c48652..ded931c 100644 --- a/toonz/sources/include/toutputproperties.h +++ b/toonz/sources/include/toutputproperties.h @@ -20,6 +20,7 @@ class TPropertyGroup; class TWidget; class TRenderSettings; +class BoardSettings; //============================================================================= //! The TOutputProperties class provides a container for output properties and @@ -91,6 +92,8 @@ private: bool m_subcameraPreview; + BoardSettings *m_boardSettings; + public: /*! Constructs TOutputProperties with default value. @@ -216,6 +219,8 @@ machine's CPU). bool isSubcameraPreview() const { return m_subcameraPreview; } void setSubcameraPreview(bool enabled) { m_subcameraPreview = enabled; } + + BoardSettings *getBoardSettings() const { return m_boardSettings; } }; //-------------------------------------------- diff --git a/toonz/sources/toonz/CMakeLists.txt b/toonz/sources/toonz/CMakeLists.txt index 004b5b1..ca889ff 100644 --- a/toonz/sources/toonz/CMakeLists.txt +++ b/toonz/sources/toonz/CMakeLists.txt @@ -158,6 +158,7 @@ set(MOC_HEADERS reframepopup.h autoinputcellnumberpopup.h colormodelbehaviorpopup.h + boardsettingspopup.h # Tracker file dummyprocessor.h metnum.h @@ -331,6 +332,7 @@ set(SOURCES reframepopup.cpp autoinputcellnumberpopup.cpp colormodelbehaviorpopup.cpp + boardsettingspopup.cpp # Tracker file dummyprocessor.cpp metnum.cpp diff --git a/toonz/sources/toonz/Resources/down.svg b/toonz/sources/toonz/Resources/down.svg new file mode 100644 index 0000000..4e43af1 --- /dev/null +++ b/toonz/sources/toonz/Resources/down.svg @@ -0,0 +1,49 @@ + +image/svg+xml \ No newline at end of file diff --git a/toonz/sources/toonz/Resources/up.svg b/toonz/sources/toonz/Resources/up.svg new file mode 100644 index 0000000..5621f63 --- /dev/null +++ b/toonz/sources/toonz/Resources/up.svg @@ -0,0 +1,49 @@ + +image/svg+xml \ No newline at end of file diff --git a/toonz/sources/toonz/boardsettingspopup.cpp b/toonz/sources/toonz/boardsettingspopup.cpp new file mode 100644 index 0000000..78aecf9 --- /dev/null +++ b/toonz/sources/toonz/boardsettingspopup.cpp @@ -0,0 +1,1089 @@ +#include "boardsettingspopup.h" + +// Tnz6 includes +#include "tapp.h" +#include "filebrowser.h" + +// TnzQt includes +#include "toonzqt/filefield.h" +#include "toonzqt/colorfield.h" +#include "toonzqt/intfield.h" + +// TnzLib includes +#include "toutputproperties.h" +#include "toonz/tscenehandle.h" +#include "toonz/toonzscene.h" +#include "toonz/sceneproperties.h" +#include "toonz/tcamera.h" +#include "toonz/boardsettings.h" +#include "toonz/toonzfolders.h" + +// TnzBase includes +#include "trasterfx.h" + +// TnzCore includes +#include "tsystem.h" +#include "tlevel_io.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +BoardItem* BoardSettingsPopup::currentBoardItem = nullptr; + +namespace { +QMap stringByItemType; + +BoardItem* currentBoardItem() { return BoardSettingsPopup::currentBoardItem; } +void setCurrentBoardItem(BoardItem* item) { + BoardSettingsPopup::currentBoardItem = item; +} + +void editListWidgetItem(QListWidgetItem* listItem, BoardItem* item) { + QString itemText = item->getName() + "\n(" + + stringByItemType.value(item->getType(), "Unknown type") + + ")"; + listItem->setText(itemText); + + if (item->getType() == BoardItem::Image) { + ToonzScene* scene = TApp::instance()->getCurrentScene()->getScene(); + TFilePath decodedImgPath = scene->decodeFilePath(item->getImgPath()); + QPixmap iconPm = QPixmap::fromImage( + QImage(decodedImgPath.getQString()).scaled(QSize(20, 20))); + listItem->setIcon(QIcon(iconPm)); + } else { + QPixmap iconPm(20, 20); + iconPm.fill(item->getColor()); + listItem->setIcon(QIcon(iconPm)); + } +} +} + +//============================================================================= + +BoardView::BoardView(QWidget* parent) : QWidget(parent) { + setMouseTracking(true); +} + +void BoardView::paintEvent(QPaintEvent* event) { + ToonzScene* scene = TApp::instance()->getCurrentScene()->getScene(); + if (!scene) return; + TOutputProperties* outProp = scene->getProperties()->getOutputProperties(); + BoardSettings* boardSettings = outProp->getBoardSettings(); + + QPainter p(this); + + p.fillRect(rect(), Qt::black); + + // Duration must be more than 0 to save the clapperboard. + // Scene file can maintain compatibility with older versions if you set the + // duration to 0. + if (boardSettings->getDuration() == 0) { + p.setPen(Qt::white); + QFont font = p.font(); + font.setPixelSize(30); + p.setFont(font); + p.drawText( + rect().adjusted(5, 5, -5, -5), Qt::AlignCenter | Qt::TextWordWrap, + tr("Please set the duration more than 0 frame first, or the " + "clapperboard settings will not be saved in the scene at all!")); + + p.setPen(QPen(Qt::red, 2)); + p.drawLine(5, 5, 150, 5); + p.drawLine(5, 10, 150, 10); + + return; + } + + if (!m_valid) { + int shrinkX = outProp->getRenderSettings().m_shrinkX, + shrinkY = outProp->getRenderSettings().m_shrinkY; + TDimension frameSize = scene->getCurrentCamera()->getRes(); + TDimension cameraRes(frameSize.lx / shrinkX, frameSize.ly / shrinkY); + + m_boardImg = boardSettings->getBoardImage(cameraRes, shrinkX, scene); + m_valid = true; + } + + p.drawImage(m_boardImgRect, m_boardImg); + + p.translate(m_boardImgRect.topLeft()); + + p.setBrush(Qt::NoBrush); + for (int i = 0; i < boardSettings->getItemCount(); i++) { + BoardItem* item = &(boardSettings->getItem(i)); + if (item == currentBoardItem()) { + p.setPen(QPen(QColor(255, 100, 0), 2)); + } else { + p.setPen(QPen(QColor(64, 64, 255), 1, Qt::DashLine)); + } + p.drawRect(item->getItemRect(m_boardImgRect.size().toSize())); + } +} + +void BoardView::resizeEvent(QResizeEvent* event) { + QSize boardRes; + if (m_valid) { + boardRes = m_boardImg.size(); + } else { + ToonzScene* scene = TApp::instance()->getCurrentScene()->getScene(); + TOutputProperties* outProp = scene->getProperties()->getOutputProperties(); + int shrinkX = outProp->getRenderSettings().m_shrinkX, + shrinkY = outProp->getRenderSettings().m_shrinkY; + TDimension frameSize = scene->getCurrentCamera()->getRes(); + boardRes = QSize(frameSize.lx / shrinkX, frameSize.ly / shrinkY); + } + + float ratio = std::min((float)width() / (float)boardRes.width(), + (float)height() / (float)boardRes.height()); + QSizeF imgSize = QSizeF(boardRes) * ratio; + QPointF imgTopLeft(((float)width() - imgSize.width()) * 0.5, + ((float)height() - imgSize.height()) * 0.5); + + m_boardImgRect = QRectF(imgTopLeft, imgSize); +} + +void BoardView::mouseMoveEvent(QMouseEvent* event) { + // mouse move - change drag item + if (!(event->buttons() & Qt::LeftButton)) { + if (!currentBoardItem()) { + m_dragItem = None; + setCursor(Qt::ArrowCursor); + return; + } + + QPointF posOnImg = QPointF(event->pos()) - m_boardImgRect.topLeft(); + + QRectF itemRect = + currentBoardItem()->getItemRect(m_boardImgRect.size().toSize()); + + qreal distance = 10.0; + + if ((itemRect.topLeft() - posOnImg).manhattanLength() < distance) { + m_dragItem = TopLeftCorner; + setCursor(Qt::SizeFDiagCursor); + } else if ((itemRect.topRight() - posOnImg).manhattanLength() < distance) { + m_dragItem = TopRightCorner; + setCursor(Qt::SizeBDiagCursor); + } else if ((itemRect.bottomRight() - posOnImg).manhattanLength() < + distance) { + m_dragItem = BottomRightCorner; + setCursor(Qt::SizeFDiagCursor); + } else if ((itemRect.bottomLeft() - posOnImg).manhattanLength() < + distance) { + m_dragItem = BottomLeftCorner; + setCursor(Qt::SizeBDiagCursor); + } else if (std::abs(itemRect.top() - posOnImg.y()) < distance && + itemRect.left() < posOnImg.x() && + itemRect.right() > posOnImg.x()) { + m_dragItem = TopEdge; + setCursor(Qt::SizeVerCursor); + } else if (std::abs(itemRect.right() - posOnImg.x()) < distance && + itemRect.top() < posOnImg.y() && + itemRect.bottom() > posOnImg.y()) { + m_dragItem = RightEdge; + setCursor(Qt::SizeHorCursor); + } else if (std::abs(itemRect.bottom() - posOnImg.y()) < distance && + itemRect.left() < posOnImg.x() && + itemRect.right() > posOnImg.x()) { + m_dragItem = BottomEdge; + setCursor(Qt::SizeVerCursor); + } else if (std::abs(itemRect.left() - posOnImg.x()) < distance && + itemRect.top() < posOnImg.y() && + itemRect.bottom() > posOnImg.y()) { + m_dragItem = LeftEdge; + setCursor(Qt::SizeHorCursor); + } else if (itemRect.contains(posOnImg)) { + m_dragItem = Translate; + setCursor(Qt::SizeAllCursor); + } else { + m_dragItem = None; + setCursor(Qt::ArrowCursor); + } + + return; + } + + // left mouse drag + if (m_dragItem == None) return; + + if (m_dragStartItemRect.isNull()) return; + + QPointF posOnImg = QPointF(event->pos()) - m_boardImgRect.topLeft(); + QPointF ratioPos = QPointF(posOnImg.x() / m_boardImgRect.width(), + posOnImg.y() / m_boardImgRect.height()); + + // with alt : center resize + bool altPressed = event->modifiers() & Qt::AltModifier; + // with shift : align to discrete position + bool shiftPressed = event->modifiers() & Qt::ShiftModifier; + + auto adjVal = [&](double p) { + double step = 0.05; + if (!shiftPressed) return p; + return std::round(p / step) * step; + }; + auto adjPos = [&](QPointF p) { + if (!shiftPressed) return p; + return QPointF(adjVal(p.x()), adjVal(p.y())); + }; + + QRectF newRect = m_dragStartItemRect; + QPointF d = ratioPos - m_dragStartPos; + switch (m_dragItem) { + case Translate: + newRect.translate(ratioPos - m_dragStartPos); + if (shiftPressed) { + newRect.setTopLeft(adjPos(newRect.topLeft())); + newRect.setBottomRight(adjPos(newRect.bottomRight())); + } + break; + + case TopLeftCorner: + newRect.setTopLeft(adjPos(newRect.topLeft() + d)); + if (altPressed) newRect.setBottomRight(adjPos(newRect.bottomRight() - d)); + break; + + case TopRightCorner: + newRect.setTopRight(adjPos(newRect.topRight() + d)); + if (altPressed) newRect.setBottomLeft(adjPos(newRect.bottomLeft() - d)); + break; + + case BottomRightCorner: + newRect.setBottomRight(adjPos(newRect.bottomRight() + d)); + if (altPressed) newRect.setTopLeft(adjPos(newRect.topLeft() - d)); + break; + + case BottomLeftCorner: + newRect.setBottomLeft(adjPos(newRect.bottomLeft() + d)); + if (altPressed) newRect.setTopRight(adjPos(newRect.topRight() - d)); + break; + + case TopEdge: + newRect.setTop(adjVal(newRect.top() + d.y())); + if (altPressed) newRect.setBottom(adjVal(newRect.bottom() - d.y())); + break; + + case RightEdge: + newRect.setRight(adjVal(newRect.right() + d.x())); + if (altPressed) newRect.setLeft(adjVal(newRect.left() - d.x())); + break; + + case BottomEdge: + newRect.setBottom(adjVal(newRect.bottom() + d.y())); + if (altPressed) newRect.setTop(adjVal(newRect.top() - d.y())); + break; + + case LeftEdge: + newRect.setLeft(adjVal(newRect.left() + d.x())); + if (altPressed) newRect.setRight(adjVal(newRect.right() - d.x())); + break; + default: + break; + } + + currentBoardItem()->setRatioRect(newRect.normalized()); + invalidate(); + update(); +} + +void BoardView::mousePressEvent(QMouseEvent* event) { + // only the left button counts + if (event->button() != Qt::LeftButton) return; + // store the current item rect and mouse pos, relative to the image size + m_dragStartItemRect = currentBoardItem()->getRatioRect(); + QPointF posOnImg = QPointF(event->pos()) - m_boardImgRect.topLeft(); + m_dragStartPos = QPointF(posOnImg.x() / m_boardImgRect.width(), + posOnImg.y() / m_boardImgRect.height()); +} + +void BoardView::mouseReleaseEvent(QMouseEvent* event) { + m_dragStartItemRect = QRectF(); + m_dragStartPos = QPointF(); +} + +//============================================================================= + +ItemInfoView::ItemInfoView(QWidget* parent) : QStackedWidget(parent) { + m_nameEdit = new QLineEdit(this); + m_maxFontSizeEdit = new DVGui::IntLineEdit(this, 1, 1); + m_typeCombo = new QComboBox(this); + m_textEdit = new QTextEdit(this); + m_imgPathField = new DVGui::FileField(this); + m_fontCombo = new QFontComboBox(this); + m_boldButton = + new QPushButton(QIcon(":Resources/bold_on.png"), tr("Bold"), this); + m_italicButton = + new QPushButton(QIcon(":Resources/italic_on.png"), tr("Italic"), this); + m_fontColorField = + new DVGui::ColorField(this, true, TPixel32(0, 0, 0, 255), 25, false, 54); + m_imgARModeCombo = new QComboBox(this); + + m_fontPropBox = new QWidget(this); + m_imgPropBox = new QWidget(this); + + for (int i = 0; i < BoardItem::TypeCount; i++) { + m_typeCombo->addItem(stringByItemType[(BoardItem::Type)i]); + } + + m_textEdit->setAcceptRichText(false); + m_textEdit->setStyleSheet( + "background:white;\ncolor:black;\nborder:1 solid black;"); + + m_boldButton->setIconSize(QSize(30, 30)); + m_boldButton->setFixedHeight(25); + m_boldButton->setCheckable(true); + m_italicButton->setIconSize(QSize(30, 30)); + m_italicButton->setFixedHeight(25); + m_italicButton->setCheckable(true); + + QStringList filters; + for (QByteArray& format : QImageReader::supportedImageFormats()) + filters += format; + m_imgPathField->setFilters(filters); + m_imgPathField->setFileMode(QFileDialog::ExistingFile); + + m_imgARModeCombo->addItem(tr("Ignore"), Qt::IgnoreAspectRatio); + m_imgARModeCombo->addItem(tr("Keep"), Qt::KeepAspectRatio); + + //----- layout + + QGridLayout* mainLay = new QGridLayout(); + mainLay->setMargin(5); + mainLay->setHorizontalSpacing(3); + mainLay->setVerticalSpacing(10); + { + mainLay->addWidget(new QLabel(tr("Name:"), this), 0, 0, + Qt::AlignRight | Qt::AlignVCenter); + mainLay->addWidget(m_nameEdit, 0, 1); + + mainLay->addWidget(new QLabel(tr("Type:"), this), 1, 0, + Qt::AlignRight | Qt::AlignVCenter); + mainLay->addWidget(m_typeCombo, 1, 1); + + QVBoxLayout* extraInfoLay = new QVBoxLayout(); + extraInfoLay->setMargin(0); + extraInfoLay->setSpacing(0); + { + extraInfoLay->addWidget(m_textEdit, 1); + + QGridLayout* imgPropLay = new QGridLayout(); + imgPropLay->setMargin(0); + imgPropLay->setHorizontalSpacing(3); + imgPropLay->setVerticalSpacing(10); + { + imgPropLay->addWidget(new QLabel(tr("Path:"), this), 0, 0, + Qt::AlignRight | Qt::AlignVCenter); + imgPropLay->addWidget(m_imgPathField, 0, 1, 1, 2); + + imgPropLay->addWidget(new QLabel(tr("Aspect Ratio:"), this), 1, 0, + Qt::AlignRight | Qt::AlignVCenter); + imgPropLay->addWidget(m_imgARModeCombo, 1, 1); + } + imgPropLay->setColumnStretch(0, 0); + imgPropLay->setColumnStretch(1, 0); + imgPropLay->setColumnStretch(2, 1); + m_imgPropBox->setLayout(imgPropLay); + extraInfoLay->addWidget(m_imgPropBox, 0); + + extraInfoLay->addSpacing(5); + + QGridLayout* fontPropLay = new QGridLayout(); + fontPropLay->setMargin(0); + fontPropLay->setHorizontalSpacing(3); + fontPropLay->setVerticalSpacing(10); + { + fontPropLay->addWidget(new QLabel(tr("Font:"), this), 0, 0, + Qt::AlignRight | Qt::AlignVCenter); + fontPropLay->addWidget(m_fontCombo, 0, 1, 1, 4); + + fontPropLay->addWidget(new QLabel(tr("Max Size:"), this), 1, 0, + Qt::AlignRight | Qt::AlignVCenter); + fontPropLay->addWidget(m_maxFontSizeEdit, 1, 1); + + fontPropLay->addWidget(m_boldButton, 1, 2); + fontPropLay->addWidget(m_italicButton, 1, 3); + + fontPropLay->addWidget(m_fontColorField, 2, 0, 1, 5); + } + fontPropLay->setColumnStretch(0, 0); + fontPropLay->setColumnStretch(1, 0); + fontPropLay->setColumnStretch(2, 0); + fontPropLay->setColumnStretch(3, 0); + fontPropLay->setColumnStretch(4, 1); + m_fontPropBox->setLayout(fontPropLay); + extraInfoLay->addWidget(m_fontPropBox, 0); + } + mainLay->addLayout(extraInfoLay, 2, 0, 1, 2); + } + mainLay->setColumnStretch(0, 0); + mainLay->setColumnStretch(1, 1); + mainLay->setRowStretch(0, 0); + mainLay->setRowStretch(1, 0); + mainLay->setRowStretch(2, 1); + + QWidget* mainWidget = new QWidget(this); + mainWidget->setLayout(mainLay); + addWidget(mainWidget); + + addWidget(new QLabel(tr("No item selected."), this)); + + bool ret = true; + ret = ret && connect(m_nameEdit, SIGNAL(editingFinished()), this, + SLOT(onNameEdited())); + ret = ret && connect(m_maxFontSizeEdit, SIGNAL(editingFinished()), this, + SLOT(onMaxFontSizeEdited())); + ret = ret && connect(m_typeCombo, SIGNAL(activated(int)), this, + SLOT(onTypeComboActivated(int))); + ret = ret && connect(m_textEdit, SIGNAL(textChanged()), this, + SLOT(onFreeTextChanged())); + ret = ret && connect(m_imgPathField, SIGNAL(pathChanged()), this, + SLOT(onImgPathChanged())); + ret = ret && connect(m_fontCombo, SIGNAL(currentFontChanged(const QFont&)), + this, SLOT(onFontComboChanged(const QFont&))); + ret = ret && connect(m_boldButton, SIGNAL(clicked(bool)), this, + SLOT(onBoldButtonClicked(bool))); + ret = ret && connect(m_italicButton, SIGNAL(clicked(bool)), this, + SLOT(onItalicButtonClicked(bool))); + ret = ret && + connect(m_fontColorField, SIGNAL(colorChanged(const TPixel32&, bool)), + this, SLOT(onFontColorChanged(const TPixel32&, bool))); + ret = ret && connect(m_imgARModeCombo, SIGNAL(activated(int)), this, + SLOT(onImgARModeComboActivated())); + assert(ret); +} + +// called on switching the current item +void ItemInfoView::setCurrentItem(int index) { + if (index == -1) { + if (!currentBoardItem()) return; + setCurrentBoardItem(nullptr); + setCurrentIndex(1); // set to "no item seleceted" page. + return; + } + + setCurrentIndex(0); // set to normal page. + + ToonzScene* scene = TApp::instance()->getCurrentScene()->getScene(); + if (!scene) return; + TOutputProperties* outProp = scene->getProperties()->getOutputProperties(); + BoardSettings* boardSettings = outProp->getBoardSettings(); + + if (index >= boardSettings->getItemCount()) return; + + BoardItem* newItem = &(boardSettings->getItem(index)); + if (currentBoardItem() == newItem) return; + + setCurrentBoardItem(newItem); + + // sync + m_nameEdit->setText(newItem->getName()); + m_maxFontSizeEdit->setValue(newItem->getMaximumFontSize()); + m_typeCombo->setCurrentIndex((int)newItem->getType()); + m_textEdit->setText(newItem->getFreeText()); + m_imgPathField->setPath(newItem->getImgPath().getQString()); + m_fontCombo->setCurrentFont(newItem->font()); + m_boldButton->setChecked(newItem->font().bold()); + m_italicButton->setChecked(newItem->font().italic()); + QColor col = newItem->getColor(); + m_fontColorField->setColor( + TPixel32(col.red(), col.green(), col.blue(), col.alpha())); + int ARModeIndex = m_imgARModeCombo->findData(newItem->getImgARMode()); + if (ARModeIndex >= 0) m_imgARModeCombo->setCurrentIndex(ARModeIndex); + + switch (newItem->getType()) { + case BoardItem::FreeText: + m_textEdit->show(); + m_imgPropBox->hide(); + m_fontPropBox->show(); + break; + case BoardItem::Image: + m_textEdit->hide(); + m_imgPropBox->show(); + m_fontPropBox->hide(); + break; + default: + m_textEdit->hide(); + m_imgPropBox->hide(); + m_fontPropBox->show(); + break; + } +} + +void ItemInfoView::onNameEdited() { + assert(currentBoardItem()); + + QString newName = m_nameEdit->text(); + + if (newName.isEmpty()) { + newName = tr("Item"); + m_nameEdit->setText(newName); + } + + if (currentBoardItem()->getName() == newName) return; + + currentBoardItem()->setName(newName); + + emit itemPropertyChanged(true); +} + +void ItemInfoView::onMaxFontSizeEdited() { + assert(currentBoardItem()); + + int maxFontSize = m_maxFontSizeEdit->getValue(); + + if (currentBoardItem()->getMaximumFontSize() == maxFontSize) return; + + currentBoardItem()->setMaximumFontSize(maxFontSize); + + emit itemPropertyChanged(false); +} + +void ItemInfoView::onTypeComboActivated(int) { + assert(currentBoardItem()); + + BoardItem::Type newType = (BoardItem::Type)m_typeCombo->currentIndex(); + + if (currentBoardItem()->getType() == newType) return; + + currentBoardItem()->setType(newType); + + switch (newType) { + case BoardItem::FreeText: + m_textEdit->show(); + m_imgPropBox->hide(); + m_fontPropBox->show(); + break; + case BoardItem::Image: + m_textEdit->hide(); + m_imgPropBox->show(); + m_fontPropBox->hide(); + break; + default: + m_textEdit->hide(); + m_imgPropBox->hide(); + m_fontPropBox->show(); + break; + } + + emit itemPropertyChanged(true); +} + +void ItemInfoView::onFreeTextChanged() { + assert(currentBoardItem()); + + currentBoardItem()->setFreeText(m_textEdit->toPlainText()); + + emit itemPropertyChanged(false); +} + +void ItemInfoView::onImgPathChanged() { + assert(currentBoardItem()); + TFilePath fp(m_imgPathField->getPath()); + if (fp.isLevelName()) { + ToonzScene* scene = TApp::instance()->getCurrentScene()->getScene(); + TLevelReaderP lr(scene->decodeFilePath(fp)); + TLevelP level; + if (lr) level = lr->loadInfo(); + if (level.getPointer() && level->getTable()->size() > 0) { + TFrameId firstFrame = level->begin()->first; + fp = fp.withFrame(firstFrame); + m_imgPathField->setPath(fp.getQString()); + } + } + currentBoardItem()->setImgPath(fp); + + emit itemPropertyChanged(true); +} + +void ItemInfoView::onFontComboChanged(const QFont& newFont) { + assert(currentBoardItem()); + + currentBoardItem()->font().setFamily(newFont.family()); + + emit itemPropertyChanged(false); +} + +void ItemInfoView::onBoldButtonClicked(bool on) { + assert(currentBoardItem()); + + currentBoardItem()->font().setBold(on); + + emit itemPropertyChanged(false); +} + +void ItemInfoView::onItalicButtonClicked(bool on) { + assert(currentBoardItem()); + + currentBoardItem()->font().setItalic(on); + + emit itemPropertyChanged(false); +} + +void ItemInfoView::onFontColorChanged(const TPixel32& color, bool isDragging) { + assert(currentBoardItem()); + + if (isDragging) return; + + QColor newColor((int)color.r, (int)color.g, (int)color.b, (int)color.m); + if (currentBoardItem()->getColor() == newColor) return; + currentBoardItem()->setColor(newColor); + + emit itemPropertyChanged(true); +} + +void ItemInfoView::onImgARModeComboActivated() { + assert(currentBoardItem()); + + currentBoardItem()->setImgARMode( + (Qt::AspectRatioMode)m_imgARModeCombo->currentData().toInt()); + + emit itemPropertyChanged(false); +} + +//============================================================================= + +ItemListView::ItemListView(QWidget* parent) : QWidget(parent) { + QPushButton* newItemBtn = + new QPushButton(QIcon(":Resources/plus.svg"), tr("Add"), this); + m_deleteItemBtn = + new QPushButton(QIcon(":Resources/delete_on.svg"), tr("Remove"), this); + m_moveUpBtn = + new QPushButton(QIcon(":Resources/up.svg"), tr("Move Up"), this); + m_moveDownBtn = + new QPushButton(QIcon(":Resources/down.svg"), tr("Move Down"), this); + + m_list = new QListWidget(this); + + QSize iconSize(20, 20); + + newItemBtn->setIconSize(iconSize); + m_deleteItemBtn->setIconSize(iconSize); + m_moveUpBtn->setIconSize(iconSize); + m_moveDownBtn->setIconSize(iconSize); + + m_list->setIconSize(QSize(20, 20)); + m_list->setAlternatingRowColors(true); + m_list->setMaximumWidth(225); + + QHBoxLayout* mainLay = new QHBoxLayout(); + mainLay->setMargin(5); + mainLay->setSpacing(5); + { + QVBoxLayout* buttonsLay = new QVBoxLayout(); + buttonsLay->setMargin(0); + buttonsLay->setSpacing(3); + { + buttonsLay->addWidget(newItemBtn, 0); + buttonsLay->addWidget(m_deleteItemBtn, 0); + buttonsLay->addWidget(m_moveUpBtn, 0); + buttonsLay->addWidget(m_moveDownBtn, 0); + buttonsLay->addStretch(1); + } + mainLay->addLayout(buttonsLay, 0); + + mainLay->addWidget(m_list, 1); + } + setLayout(mainLay); + + bool ret = true; + ret = ret && connect(m_list, SIGNAL(currentRowChanged(int)), this, + SLOT(onCurrentItemSwitched(int))); + ret = ret && connect(newItemBtn, SIGNAL(clicked(bool)), this, + SLOT(onNewItemButtonClicked())); + ret = ret && connect(m_deleteItemBtn, SIGNAL(clicked(bool)), this, + SLOT(onDeleteItemButtonClicked())); + ret = ret && connect(m_moveUpBtn, SIGNAL(clicked(bool)), this, + SLOT(onMoveUpButtonClicked())); + ret = ret && connect(m_moveDownBtn, SIGNAL(clicked(bool)), this, + SLOT(onMoveDownButtonClicked())); + assert(ret); +} + +void ItemListView::initialize() { + ToonzScene* scene = TApp::instance()->getCurrentScene()->getScene(); + if (!scene) return; + TOutputProperties* outProp = scene->getProperties()->getOutputProperties(); + BoardSettings* boardSettings = outProp->getBoardSettings(); + + // first, clear all the list items + m_list->clear(); + + if (boardSettings->getItemCount() == 0) return; + + for (int i = 0; i < boardSettings->getItemCount(); i++) { + BoardItem* item = &(boardSettings->getItem(i)); + QListWidgetItem* listItem = new QListWidgetItem(m_list); + + editListWidgetItem(listItem, item); + } + + // this will update information view + m_list->setCurrentRow(0, QItemSelectionModel::ClearAndSelect); +} + +// called when the infoView is edited +void ItemListView::updateCurrentItem() { + ToonzScene* scene = TApp::instance()->getCurrentScene()->getScene(); + if (!scene) return; + TOutputProperties* outProp = scene->getProperties()->getOutputProperties(); + BoardSettings* boardSettings = outProp->getBoardSettings(); + + int currentItemIndex = m_list->currentRow(); + + if (currentItemIndex < 0 || currentItemIndex >= boardSettings->getItemCount()) + return; + + BoardItem* item = &(boardSettings->getItem(currentItemIndex)); + editListWidgetItem(m_list->item(currentItemIndex), item); +} + +void ItemListView::onCurrentItemSwitched(int currentRow) { + if (currentRow == -1) { + m_moveUpBtn->setEnabled(false); + m_moveDownBtn->setEnabled(false); + } else { + m_moveUpBtn->setEnabled(currentRow != 0); + m_moveDownBtn->setEnabled(currentRow != m_list->count() - 1); + } + emit currentItemSwitched(currentRow); +} + +void ItemListView::onNewItemButtonClicked() { + ToonzScene* scene = TApp::instance()->getCurrentScene()->getScene(); + if (!scene) return; + TOutputProperties* outProp = scene->getProperties()->getOutputProperties(); + BoardSettings* boardSettings = outProp->getBoardSettings(); + + boardSettings->addNewItem(); + + QListWidgetItem* listItem = new QListWidgetItem(); + BoardItem* item = &(boardSettings->getItem(0)); + + editListWidgetItem(listItem, item); + + m_list->insertItem(0, listItem); + m_list->setCurrentRow(0, QItemSelectionModel::ClearAndSelect); + + m_deleteItemBtn->setEnabled(true); + + emit itemAddedOrDeleted(); +} + +void ItemListView::onDeleteItemButtonClicked() { + ToonzScene* scene = TApp::instance()->getCurrentScene()->getScene(); + if (!scene) return; + TOutputProperties* outProp = scene->getProperties()->getOutputProperties(); + BoardSettings* boardSettings = outProp->getBoardSettings(); + + int currentRow = m_list->currentRow(); + + assert(currentRow != -1); + + boardSettings->removeItem(currentRow); + + if (m_list->count() == 1) { + m_deleteItemBtn->setEnabled(false); + m_list->setCurrentRow(-1, QItemSelectionModel::ClearAndSelect); + } else if (currentRow == m_list->count() - 1) + m_list->setCurrentRow(m_list->count() - 2, + QItemSelectionModel::ClearAndSelect); + + delete m_list->takeItem(currentRow); + + emit itemAddedOrDeleted(); +} + +void ItemListView::onMoveUpButtonClicked() { + ToonzScene* scene = TApp::instance()->getCurrentScene()->getScene(); + if (!scene) return; + TOutputProperties* outProp = scene->getProperties()->getOutputProperties(); + BoardSettings* boardSettings = outProp->getBoardSettings(); + + int currentRow = m_list->currentRow(); + assert(currentRow > 0); + + boardSettings->swapItems(currentRow - 1, currentRow); + + m_list->insertItem(currentRow - 1, m_list->takeItem(currentRow)); + m_list->setCurrentRow(currentRow - 1); + + emit itemAddedOrDeleted(); +} + +void ItemListView::onMoveDownButtonClicked() { + ToonzScene* scene = TApp::instance()->getCurrentScene()->getScene(); + if (!scene) return; + TOutputProperties* outProp = scene->getProperties()->getOutputProperties(); + BoardSettings* boardSettings = outProp->getBoardSettings(); + + int currentRow = m_list->currentRow(); + assert(currentRow < m_list->count() - 1); + + boardSettings->swapItems(currentRow, currentRow + 1); + + QListWidgetItem* item = m_list->takeItem(currentRow); + m_list->insertItem(currentRow + 1, item); + m_list->setCurrentRow(currentRow + 1); + + emit itemAddedOrDeleted(); +} + +//============================================================================= + +BoardSettingsPopup::BoardSettingsPopup(QWidget* parent) + : DVGui::Dialog(parent, true, false, "ClapperboardSettings") { + setWindowTitle(tr("Clapperboard Settings")); + + initializeItemTypeString(); + + m_boardView = new BoardView(this); + m_itemInfoView = new ItemInfoView(this); + m_itemListView = new ItemListView(this); + + m_durationEdit = new DVGui::IntLineEdit(this, 1, 0); + + QPushButton* loadPresetBtn = + new QPushButton(QIcon(":Resources/load_on.svg"), tr("Load Preset"), this); + QPushButton* savePresetBtn = new QPushButton(QIcon(":Resources/save_on.svg"), + tr("Save as Preset"), this); + + QPushButton* closeButton = new QPushButton(tr("Close"), this); + + //--- layout + + QHBoxLayout* mainLay = new QHBoxLayout(); + mainLay->setMargin(0); + mainLay->setSpacing(10); + { + QVBoxLayout* leftLay = new QVBoxLayout(); + leftLay->setMargin(0); + leftLay->setSpacing(0); + { + QHBoxLayout* leftTopLay = new QHBoxLayout(); + leftTopLay->setMargin(5); + leftTopLay->setSpacing(3); + { + leftTopLay->addWidget(new QLabel(tr("Duration (frames):"), this), 0); + leftTopLay->addWidget(m_durationEdit, 0); + + leftTopLay->addSpacing(10); + + leftTopLay->addWidget(loadPresetBtn, 0); + leftTopLay->addWidget(savePresetBtn, 0); + + leftTopLay->addStretch(1); + } + leftLay->addLayout(leftTopLay, 0); + + leftLay->addWidget(m_boardView, 1); + } + mainLay->addLayout(leftLay, 1); + + QVBoxLayout* rightLay = new QVBoxLayout(); + rightLay->setMargin(0); + rightLay->setSpacing(15); + { + rightLay->addWidget(m_itemInfoView, 0); + rightLay->addWidget(m_itemListView, 1); + } + mainLay->addLayout(rightLay, 0); + } + m_topLayout->addLayout(mainLay, 1); + + addButtonBarWidget(closeButton); + + bool ret = true; + ret = ret && connect(m_itemListView, SIGNAL(currentItemSwitched(int)), this, + SLOT(onCurrentItemSwitched(int))); + ret = ret && connect(m_itemListView, SIGNAL(itemAddedOrDeleted()), this, + SLOT(onItemAddedOrDeleted())); + ret = ret && connect(m_itemInfoView, SIGNAL(itemPropertyChanged(bool)), this, + SLOT(onItemPropertyChanged(bool))); + ret = ret && connect(m_durationEdit, SIGNAL(editingFinished()), this, + SLOT(onDurationEdited())); + ret = ret && connect(closeButton, SIGNAL(pressed()), this, SLOT(close())); + ret = ret && + connect(loadPresetBtn, SIGNAL(pressed()), this, SLOT(onLoadPreset())); + ret = ret && + connect(savePresetBtn, SIGNAL(pressed()), this, SLOT(onSavePreset())); + + assert(ret); +} + +void BoardSettingsPopup::initialize() { + ToonzScene* scene = TApp::instance()->getCurrentScene()->getScene(); + if (!scene) return; + TOutputProperties* outProp = scene->getProperties()->getOutputProperties(); + BoardSettings* boardSettings = outProp->getBoardSettings(); + + m_durationEdit->setValue(boardSettings->getDuration()); + + m_itemListView->initialize(); + + m_boardView->invalidate(); + m_boardView->update(); +} + +void BoardSettingsPopup::initializeItemTypeString() { + if (!stringByItemType.isEmpty()) return; + + stringByItemType[BoardItem::FreeText] = tr("Text"); // free text (m_text) + stringByItemType[BoardItem::ProjectName] = tr("Project name"); + stringByItemType[BoardItem::SceneName] = tr("Scene name"); + stringByItemType[BoardItem::Duration_Frame] = tr("Duration : Frame"); + stringByItemType[BoardItem::Duration_SecFrame] = tr("Duration : Sec + Frame"); + stringByItemType[BoardItem::Duration_HHMMSSFF] = tr("Duration : HH:MM:SS:FF"); + stringByItemType[BoardItem::CurrentDate] = tr("Current date"); + stringByItemType[BoardItem::CurrentDateTime] = tr("Current date and time"); + stringByItemType[BoardItem::UserName] = tr("User name"); + stringByItemType[BoardItem::ScenePath_Aliased] = + tr("Scene location : Aliased path"); + stringByItemType[BoardItem::ScenePath_Full] = + tr("Scene location : Full path"); + stringByItemType[BoardItem::MoviePath_Aliased] = + tr("Output location : Aliased path"); + stringByItemType[BoardItem::MoviePath_Full] = + tr("Output location : Full path"); + stringByItemType[BoardItem::Image] = tr("Image"); // m_imgPath +} + +void BoardSettingsPopup::hideEvent(QHideEvent* event) { + setCurrentBoardItem(nullptr); +} + +// called on changing the current row of ItemListView +void BoardSettingsPopup::onCurrentItemSwitched(int index) { + // updat Info + m_itemInfoView->setCurrentItem(index); + + m_boardView->update(); +} + +void BoardSettingsPopup::onItemAddedOrDeleted() { + m_boardView->invalidate(); + m_boardView->update(); +} + +// called on modifying the infoView +void BoardSettingsPopup::onItemPropertyChanged(bool updateListView) { + m_boardView->invalidate(); + m_boardView->update(); + + if (updateListView) m_itemListView->updateCurrentItem(); +} + +void BoardSettingsPopup::onDurationEdited() { + ToonzScene* scene = TApp::instance()->getCurrentScene()->getScene(); + if (!scene) return; + TOutputProperties* outProp = scene->getProperties()->getOutputProperties(); + BoardSettings* boardSettings = outProp->getBoardSettings(); + + boardSettings->setDuration(m_durationEdit->getValue()); + + m_boardView->update(); +} + +void BoardSettingsPopup::onLoadPreset() { + LoadBoardPresetFilePopup popup; + + TFilePath fp = popup.getPath(); + if (fp.isEmpty()) return; + + TIStream is(fp); + if (!is) throw TException(fp.getWideString() + L": Can't open file"); + try { + std::string tagName = ""; + if (!is.matchTag(tagName)) throw TException("Bad file format"); + if (tagName == "clapperboardSettingsPreset") { + ToonzScene* scene = TApp::instance()->getCurrentScene()->getScene(); + if (!scene) return; + TOutputProperties* outProp = + scene->getProperties()->getOutputProperties(); + BoardSettings* boardSettings = outProp->getBoardSettings(); + + boardSettings->loadData(is); + } else + throw TException("Bad file format"); + } catch (const TSystemException& se) { + DVGui::warning(QString::fromStdWString(se.getMessage())); + } catch (...) { + DVGui::error(QObject::tr("Couldn't load %1").arg(fp.getQString())); + } + + initialize(); +} + +void BoardSettingsPopup::onSavePreset() { + SaveBoardPresetFilePopup popup; + + TFilePath fp = popup.getPath(); + if (fp.isEmpty()) return; + + try { + TFileStatus fs(fp); + if (fs.doesExist() && !fs.isWritable()) { + throw TSystemException( + fp, "The preset cannot be saved: it is a read only file."); + } + + TOStream os(fp, false); + if (!os.checkStatus()) throw TException("Could not open file"); + + ToonzScene* scene = TApp::instance()->getCurrentScene()->getScene(); + if (!scene) return; + TOutputProperties* outProp = scene->getProperties()->getOutputProperties(); + BoardSettings* boardSettings = outProp->getBoardSettings(); + + os.openChild("clapperboardSettingsPreset"); + boardSettings->saveData(os, true); + os.closeChild(); + + bool status = os.checkStatus(); + if (!status) throw TException("Could not complete the save preset"); + + } catch (const TSystemException& se) { + DVGui::warning(QString::fromStdWString(se.getMessage())); + } catch (...) { + DVGui::error(QObject::tr("Couldn't save %1").arg(fp.getQString())); + } +} + +//============================================================================= + +SaveBoardPresetFilePopup::SaveBoardPresetFilePopup() + : GenericSaveFilePopup(tr("Save Clapperboard Settings As Preset")) { + m_browser->enableGlobalSelection(false); + setFilterTypes(QStringList("clapperboard")); +} + +void SaveBoardPresetFilePopup::showEvent(QShowEvent* e) { + FileBrowserPopup::showEvent(e); + setFolder(ToonzFolder::getLibraryFolder() + "clapperboards"); +} + +//============================================================================= + +LoadBoardPresetFilePopup::LoadBoardPresetFilePopup() + : GenericLoadFilePopup(tr("Load Clapperboard Settings Preset")) { + m_browser->enableGlobalSelection(false); + setFilterTypes(QStringList("clapperboard")); +} + +void LoadBoardPresetFilePopup::showEvent(QShowEvent* e) { + FileBrowserPopup::showEvent(e); + setFolder(ToonzFolder::getLibraryFolder() + "clapperboards"); +} \ No newline at end of file diff --git a/toonz/sources/toonz/boardsettingspopup.h b/toonz/sources/toonz/boardsettingspopup.h new file mode 100644 index 0000000..fb86d46 --- /dev/null +++ b/toonz/sources/toonz/boardsettingspopup.h @@ -0,0 +1,175 @@ +#pragma once + +#ifndef BOARDSETTINGSPOPUP_H +#define BOARDSETTINGSPOPUP_H + +#include "toonzqt/dvdialog.h" +#include "tpixel.h" +#include "filebrowserpopup.h" +#include +#include + +class TOutputProperties; +class QLineEdit; +class QTextEdit; +class QComboBox; +class QFontComboBox; +class QListWidget; +class BoardItem; + +namespace DVGui { +class FileField; +class ColorField; +class IntLineEdit; +} + +//============================================================================= + +class BoardView : public QWidget { + Q_OBJECT + + enum DragItem { + None = 0, + Translate, + TopLeftCorner, + TopRightCorner, + BottomRightCorner, + BottomLeftCorner, + TopEdge, + RightEdge, + BottomEdge, + LeftEdge + } m_dragItem = None; + + QImage m_boardImg; + bool m_valid = false; + + QRectF m_boardImgRect; + + QRectF m_dragStartItemRect; + QPointF m_dragStartPos; + +public: + BoardView(QWidget* parent = nullptr); + void invalidate() { m_valid = false; } + +protected: + void paintEvent(QPaintEvent* event) override; + void resizeEvent(QResizeEvent* event) override; + + void mouseMoveEvent(QMouseEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; +}; + +//============================================================================= + +class ItemInfoView : public QStackedWidget { + Q_OBJECT + + QLineEdit* m_nameEdit; + DVGui::IntLineEdit* m_maxFontSizeEdit; + QComboBox* m_typeCombo; + QTextEdit* m_textEdit; + DVGui::FileField* m_imgPathField; + QFontComboBox* m_fontCombo; + QPushButton *m_boldButton, *m_italicButton; + DVGui::ColorField* m_fontColorField; + QComboBox* m_imgARModeCombo; + + QWidget *m_fontPropBox, *m_imgPropBox; + +public: + ItemInfoView(QWidget* parent = nullptr); + void setCurrentItem(int index); + +protected slots: + void onNameEdited(); + void onMaxFontSizeEdited(); + void onTypeComboActivated(int); + void onFreeTextChanged(); + void onImgPathChanged(); + void onFontComboChanged(const QFont&); + void onBoldButtonClicked(bool); + void onItalicButtonClicked(bool); + void onFontColorChanged(const TPixel32&, bool); + void onImgARModeComboActivated(); + +signals: + // if updateListView is true then update the list view as well + void itemPropertyChanged(bool updateListView); +}; + +//============================================================================= + +class ItemListView : public QWidget { + Q_OBJECT + QListWidget* m_list; + QPushButton *m_deleteItemBtn, *m_moveUpBtn, *m_moveDownBtn; + +public: + ItemListView(QWidget* parent = nullptr); + void initialize(); + void updateCurrentItem(); +protected slots: + void onCurrentItemSwitched(int); + void onNewItemButtonClicked(); + void onDeleteItemButtonClicked(); + void onMoveUpButtonClicked(); + void onMoveDownButtonClicked(); +signals: + void currentItemSwitched(int); + void itemAddedOrDeleted(); +}; + +//============================================================================= + +class BoardSettingsPopup : public DVGui::Dialog { + Q_OBJECT + + BoardView* m_boardView; + ItemInfoView* m_itemInfoView; + ItemListView* m_itemListView; + + DVGui::IntLineEdit* m_durationEdit; + + void initialize(); + void initializeItemTypeString(); // call once on the first launch +public: + static BoardItem* currentBoardItem; + + BoardSettingsPopup(QWidget* parent = nullptr); + +protected: + void showEvent(QShowEvent*) override { initialize(); } + void hideEvent(QHideEvent*) override; +protected slots: + void onCurrentItemSwitched(int); + void onItemAddedOrDeleted(); + void onItemPropertyChanged(bool updateListView); + void onDurationEdited(); + void onLoadPreset(); + void onSavePreset(); +}; + +//============================================================================= + +class SaveBoardPresetFilePopup final : public GenericSaveFilePopup { +public: + SaveBoardPresetFilePopup(); + +protected: + void showEvent(QShowEvent*) override; +}; + +//============================================================================= + +class LoadBoardPresetFilePopup final : public GenericLoadFilePopup { +public: + LoadBoardPresetFilePopup(); + +protected: + void showEvent(QShowEvent*) override; +}; + +#endif diff --git a/toonz/sources/toonz/filebrowserpopup.h b/toonz/sources/toonz/filebrowserpopup.h index 291ea79..a74d202 100644 --- a/toonz/sources/toonz/filebrowserpopup.h +++ b/toonz/sources/toonz/filebrowserpopup.h @@ -156,7 +156,7 @@ public: //! The GenericLoadFilePopup is a simple, standard Toonz popup that //! asks the user for \a single file to be loaded from disk. -class GenericLoadFilePopup final : public FileBrowserPopup { +class GenericLoadFilePopup : public FileBrowserPopup { public: GenericLoadFilePopup(const QString &title); @@ -175,7 +175,7 @@ protected: //! The GenericSaveFilePopup is a simple, standard Toonz popup that //! asks the user for a \a single file path to save something to. -class GenericSaveFilePopup final : public FileBrowserPopup { +class GenericSaveFilePopup : public FileBrowserPopup { public: GenericSaveFilePopup(const QString &title); diff --git a/toonz/sources/toonz/outputsettingspopup.cpp b/toonz/sources/toonz/outputsettingspopup.cpp index f664f4c..a025f1a 100644 --- a/toonz/sources/toonz/outputsettingspopup.cpp +++ b/toonz/sources/toonz/outputsettingspopup.cpp @@ -8,6 +8,7 @@ #include "tapp.h" #include "camerasettingspopup.h" #include "pane.h" +#include "boardsettingspopup.h" // TnzQt includes #include "toonzqt/menubarcommand.h" @@ -25,6 +26,7 @@ #include "toonz/preferences.h" #include "toutputproperties.h" #include "toonz/tcamera.h" +#include "toonz/boardsettings.h" // TnzBase includes #include "trasterfx.h" @@ -166,6 +168,11 @@ OutputSettingsPopup::OutputSettingsPopup(bool isPreview) otherSettingsLabel = new QLabel(tr("Other Settings"), this); otherSettingsFrame = new QFrame(this); m_renderButton = new QPushButton(tr("Render"), this); + + // Board + m_addBoard = new DVGui::CheckBox(tr("Add Clapperboard"), this); + m_boardSettingsBtn = new QPushButton(tr("Edit Clapperboard..."), this); + // Gamma m_gammaFld = new DVGui::DoubleLineEdit(); // Dominant Field @@ -457,39 +464,43 @@ OutputSettingsPopup::OutputSettingsPopup(bool isPreview) otherSettingsLay->setHorizontalSpacing(5); otherSettingsLay->setVerticalSpacing(10); { + // clapperboard + otherSettingsLay->addWidget(m_addBoard, 0, 0); + otherSettingsLay->addWidget(m_boardSettingsBtn, 0, 2, 1, 2); + // Gamma - otherSettingsLay->addWidget(new QLabel(tr("Gamma:"), this), 0, 0, + otherSettingsLay->addWidget(new QLabel(tr("Gamma:"), this), 1, 0, Qt::AlignRight | Qt::AlignVCenter); - otherSettingsLay->addWidget(m_gammaFld, 0, 1, 1, 3); + otherSettingsLay->addWidget(m_gammaFld, 1, 1, 1, 3); // Dominant Field otherSettingsLay->addWidget(new QLabel(tr("Dominant Field:"), this), - 1, 0, Qt::AlignRight | Qt::AlignVCenter); - otherSettingsLay->addWidget(m_dominantFieldOm, 1, 1, 1, 4); + 2, 0, Qt::AlignRight | Qt::AlignVCenter); + otherSettingsLay->addWidget(m_dominantFieldOm, 2, 1, 1, 4); // Scene Settings' FPS otherSettingsLay->addWidget( - new QLabel(tr("Frame Rate (linked to Scene Settings):"), this), 2, + new QLabel(tr("Frame Rate (linked to Scene Settings):"), this), 3, 0, Qt::AlignRight | Qt::AlignVCenter); - otherSettingsLay->addWidget(m_frameRateFld, 2, 1, 1, 3); + otherSettingsLay->addWidget(m_frameRateFld, 3, 1, 1, 3); // Strech otherSettingsLay->addWidget(new QLabel(tr("Stretch from FPS:"), this), - 3, 0, Qt::AlignRight | Qt::AlignVCenter); - otherSettingsLay->addWidget(m_stretchFromFld, 3, 1); - otherSettingsLay->addWidget(new QLabel(tr(" To:"), this), 3, 2, + 4, 0, Qt::AlignRight | Qt::AlignVCenter); + otherSettingsLay->addWidget(m_stretchFromFld, 4, 1); + otherSettingsLay->addWidget(new QLabel(tr(" To:"), this), 4, 2, Qt::AlignRight | Qt::AlignVCenter); - otherSettingsLay->addWidget(m_stretchToFld, 3, 3); + otherSettingsLay->addWidget(m_stretchToFld, 4, 3); // new in V6.1 // Multimedia rendering enum otherSettingsLay->addWidget( - new QLabel(tr("Multiple Rendering:"), this), 4, 0, + new QLabel(tr("Multiple Rendering:"), this), 5, 0, Qt::AlignRight | Qt::AlignVCenter); - otherSettingsLay->addWidget(m_multimediaOm, 4, 1, 1, 4); + otherSettingsLay->addWidget(m_multimediaOm, 5, 1, 1, 4); - otherSettingsLay->addWidget(m_doStereoscopy, 5, 0); - otherSettingsLay->addWidget(new QLabel(tr("Camera Shift:")), 5, 1, + otherSettingsLay->addWidget(m_doStereoscopy, 6, 0); + otherSettingsLay->addWidget(new QLabel(tr("Camera Shift:")), 6, 1, Qt::AlignRight | Qt::AlignVCenter); - otherSettingsLay->addWidget(m_stereoShift, 5, 2); + otherSettingsLay->addWidget(m_stereoShift, 6, 2); - otherSettingsLay->addLayout(bottomGridLay, 6, 0, 4, 5); + otherSettingsLay->addLayout(bottomGridLay, 7, 0, 4, 5); } otherSettingsLay->setColumnStretch(0, 0); otherSettingsLay->setColumnStretch(1, 0); @@ -548,6 +559,12 @@ OutputSettingsPopup::OutputSettingsPopup(bool isPreview) SLOT(onChannelWidthChanged(int))); if (!isPreview) { + // clapperboard + ret = ret && connect(m_addBoard, SIGNAL(stateChanged(int)), this, + SLOT(onAddBoardChecked(int))); + ret = ret && connect(m_boardSettingsBtn, SIGNAL(clicked()), this, + SLOT(onBoardSettingsBtnClicked())); + ret = ret && connect(m_gammaFld, SIGNAL(editingFinished()), SLOT(onGammaFldEditFinished())); ret = ret && connect(m_dominantFieldOm, SIGNAL(currentIndexChanged(int)), @@ -862,6 +879,18 @@ void OutputSettingsPopup::updateField() { m_stretchToFld->setValue(renderSettings.m_timeStretchTo); m_frameRateFld->setValue(prop->getFrameRate()); + + // clapperboard + BoardSettings *boardSettings = prop->getBoardSettings(); + m_addBoard->setChecked(boardSettings->isActive()); + // clapperboard is only available with movie formats + if (isMovieType(m_fileFormat->currentText().toStdString())) { + m_addBoard->setEnabled(true); + m_boardSettingsBtn->setEnabled(m_addBoard->isChecked()); + } else { + m_addBoard->setEnabled(false); + m_boardSettingsBtn->setEnabled(false); + } } //----------------------------------------------------------------------------- @@ -960,6 +989,15 @@ void OutputSettingsPopup::onFormatChanged(const QString &str) { m_threadsComboOm->setDisabled(false); m_threadsComboOm->setCurrentIndex(2); } + + // clapperboard is only available with movie formats + if (isMovieType(str.toStdString())) { + m_addBoard->setEnabled(true); + m_boardSettingsBtn->setEnabled(m_addBoard->isChecked()); + } else { + m_addBoard->setEnabled(false); + m_boardSettingsBtn->setEnabled(false); + } } //----------------------------------------------------------------------------- @@ -1574,6 +1612,21 @@ void OutputSettingsPopup::onFrameRateEditingFinished() { //----------------------------------------------------------------------------- +void OutputSettingsPopup::onAddBoardChecked(int state) { + BoardSettings *boardSettings = getProperties()->getBoardSettings(); + boardSettings->setActive(state == Qt::Checked); + + m_boardSettingsBtn->setEnabled(state == Qt::Checked); +} + +void OutputSettingsPopup::onBoardSettingsBtnClicked() { + std::cout << "board settings button clicked" << std::endl; + BoardSettingsPopup popup(this); + popup.exec(); +} + +//----------------------------------------------------------------------------- + OpenPopupCommandHandler openOutputSettingsPopup( MI_OutputSettings); OpenPopupCommandHandler openPreviewSettingsPopup( diff --git a/toonz/sources/toonz/outputsettingspopup.h b/toonz/sources/toonz/outputsettingspopup.h index 21668d3..6f047bf 100644 --- a/toonz/sources/toonz/outputsettingspopup.h +++ b/toonz/sources/toonz/outputsettingspopup.h @@ -53,6 +53,10 @@ class OutputSettingsPopup : public DVGui::Dialog { CameraSettingsPopup *m_cameraSettings; QComboBox *m_presetCombo; + // clapperboard + DVGui::CheckBox *m_addBoard; + QPushButton *m_boardSettingsBtn; + bool m_isPreviewSettings; void updatePresetComboItems(); @@ -97,6 +101,9 @@ protected slots: void onCameraSettingsChanged(); /*-- Scene Settings のFPSを編集できるようにする --*/ void onFrameRateEditingFinished(); + // clapperboard + void onAddBoardChecked(int state); + void onBoardSettingsBtnClicked(); }; class PreviewSettingsPopup final : public OutputSettingsPopup { diff --git a/toonz/sources/toonz/toonz.qrc b/toonz/sources/toonz/toonz.qrc index bc223d9..7a2dd82 100644 --- a/toonz/sources/toonz/toonz.qrc +++ b/toonz/sources/toonz/toonz.qrc @@ -468,5 +468,7 @@ Resources/shift_and_trace.svg Resources/shift_and_trace_edit.svg Resources/shift_and_trace_reset.svg + Resources/up.svg + Resources/down.svg diff --git a/toonz/sources/toonzlib/CMakeLists.txt b/toonz/sources/toonzlib/CMakeLists.txt index bc03c7c..7648d6c 100644 --- a/toonz/sources/toonzlib/CMakeLists.txt +++ b/toonz/sources/toonzlib/CMakeLists.txt @@ -156,6 +156,7 @@ set(MOC_HEADERS texturemanager.h imagebuilders.h ../include/orientation.h + ../include/toonz/boardsettings.h ) set(HEADERS ${MOC_HEADERS}) @@ -317,6 +318,7 @@ set(SOURCES plasticdeformerfx.cpp txshmeshcolumn.cpp textureutils.cpp + boardsettings.cpp ) if(BUILD_TARGET_WIN) diff --git a/toonz/sources/toonzlib/boardsettings.cpp b/toonz/sources/toonzlib/boardsettings.cpp new file mode 100644 index 0000000..16d4628 --- /dev/null +++ b/toonz/sources/toonzlib/boardsettings.cpp @@ -0,0 +1,364 @@ +#include "toonz/boardsettings.h" + +// TnzLib includes +#include "toonz/toonzscene.h" +#include "toonz/tproject.h" +#include "toonz/sceneproperties.h" +#include "toonz/toonzfolders.h" +#include "toutputproperties.h" +#include "tsystem.h" + +#include +#include +#include +#include +#include + +namespace { +QMap strs = { + {BoardItem::FreeText, L"FreeText"}, + {BoardItem::ProjectName, L"ProjectName"}, + {BoardItem::SceneName, L"SceneName"}, + {BoardItem::Duration_Frame, L"Duration_Frame"}, + {BoardItem::Duration_SecFrame, L"Duration_SecFrame"}, + {BoardItem::Duration_HHMMSSFF, L"Duration_HHMMSSFF"}, + {BoardItem::CurrentDate, L"CurrentDate"}, + {BoardItem::CurrentDateTime, L"CurrentDateTime"}, + {BoardItem::UserName, L"UserName"}, + {BoardItem::ScenePath_Aliased, L"ScenePath_Aliased"}, + {BoardItem::ScenePath_Full, L"ScenePath_Full"}, + {BoardItem::MoviePath_Aliased, L"MoviePath_Aliased"}, + {BoardItem::MoviePath_Full, L"MoviePath_Full"}, + {BoardItem::Image, L"Image"}}; + +std::wstring type2String(BoardItem::Type type) { return strs.value(type, L""); } +BoardItem::Type string2Type(std::wstring str) { + return strs.key(str, BoardItem::TypeCount); +} +}; + +BoardItem::BoardItem() { + m_name = "Item"; + m_type = ProjectName; + m_rect = QRectF(0.1, 0.1, 0.8, 0.8); + m_maximumFontSize = 300; + m_color = Qt::black; +} + +QString BoardItem::getContentText(ToonzScene *scene) { + switch (m_type) { + case FreeText: + return m_text; + break; + case ProjectName: + return scene->getProject()->getName().getQString(); + break; + case SceneName: + return QString::fromStdWString(scene->getSceneName()); + break; + case Duration_Frame: + return QString::number(scene->getFrameCount()); + break; + case Duration_SecFrame: { + TOutputProperties *oprop = scene->getProperties()->getOutputProperties(); + int fps = (int)oprop->getFrameRate(); + int frame = scene->getFrameCount(); + return QString("%1 + %2").arg(QString::number(frame / fps), + QString::number(frame % fps)); + } break; + case Duration_HHMMSSFF: { + TOutputProperties *oprop = scene->getProperties()->getOutputProperties(); + int fps = (int)oprop->getFrameRate(); + int frame = scene->getFrameCount(); + int hh = frame / (fps * 60 * 60); + frame -= hh * fps * 60 * 60; + int mm = frame / (fps * 60); + frame -= mm * fps * 60; + int ss = frame / fps; + int ff = frame % fps; + return QString::number(hh).rightJustified(2, '0') + ":" + + QString::number(mm).rightJustified(2, '0') + ":" + + QString::number(ss).rightJustified(2, '0') + ":" + + QString::number(ff).rightJustified(2, '0'); + } break; + case CurrentDate: + return QDate::currentDate().toString(Qt::DefaultLocaleLongDate); + break; + case CurrentDateTime: + return QDateTime::currentDateTime().toString(Qt::DefaultLocaleLongDate); + break; + case UserName: + return TSystem::getUserName(); + break; + case ScenePath_Aliased: + return scene->codeFilePath(scene->getScenePath()).getQString(); + break; + case ScenePath_Full: + return scene->decodeFilePath(scene->getScenePath()).getQString(); + break; + case MoviePath_Aliased: { + TOutputProperties *oprop = scene->getProperties()->getOutputProperties(); + return scene->codeFilePath(oprop->getPath()).getQString(); + } break; + case MoviePath_Full: { + TOutputProperties *oprop = scene->getProperties()->getOutputProperties(); + return scene->decodeFilePath(oprop->getPath()).getQString(); + } break; + } + return QString(); +} + +QRectF BoardItem::getItemRect(QSize imgSize) { + QSizeF imgSizeF(imgSize); + return QRectF( + imgSizeF.width() * m_rect.left(), imgSizeF.height() * m_rect.top(), + imgSizeF.width() * m_rect.width(), imgSizeF.height() * m_rect.height()); +} + +void BoardItem::drawItem(QPainter &p, QSize imgSize, int shrink, + ToonzScene *scene) { + QRectF itemRect = getItemRect(imgSize); + + if (m_type == Image) { + if (m_imgPath.isEmpty()) return; + TFilePath decodedPath = scene->decodeFilePath(m_imgPath); + QImage img(decodedPath.getQString()); + if (m_imgARMode == Qt::KeepAspectRatio) { + float ratio = std::min((float)itemRect.width() / (float)img.width(), + (float)itemRect.height() / (float)img.height()); + QSizeF imgSize((float)img.width() * ratio, (float)img.height() * ratio); + QPointF imgTopLeft = + itemRect.topLeft() + + QPointF((itemRect.width() - imgSize.width()) * 0.5f, + (itemRect.height() - imgSize.height()) * 0.5f); + + p.drawImage(QRectF(imgTopLeft, imgSize), img); + } else if (m_imgARMode == Qt::IgnoreAspectRatio) + p.drawImage(itemRect, img); + return; + } + + QString contentText = getContentText(scene); + + QFont tmpFont(m_font); + tmpFont.setPixelSize(100); + QFontMetricsF tmpFm(tmpFont); + QRectF tmpBounding = + tmpFm.boundingRect(itemRect, Qt::AlignLeft | Qt::AlignTop, contentText); + + float ratio = std::min(itemRect.width() / tmpBounding.width(), + itemRect.height() / tmpBounding.height()); + + // compute the font size which will just fit the item region + int fontSize = (int)(100.0f * ratio); + tmpFont.setPixelSize(fontSize); + tmpFm = QFontMetricsF(tmpFont); + tmpBounding = + tmpFm.boundingRect(itemRect, Qt::AlignLeft | Qt::AlignTop, contentText); + bool isInRect; + if (itemRect.width() >= tmpBounding.width() && + itemRect.height() >= tmpBounding.height()) + isInRect = true; + else + isInRect = false; + while (1) { + fontSize += (isInRect) ? 1 : -1; + if (fontSize <= 0) // cannot draw + return; + tmpFont.setPixelSize(fontSize); + tmpFm = QFontMetricsF(tmpFont); + tmpBounding = + tmpFm.boundingRect(itemRect, Qt::AlignLeft | Qt::AlignTop, contentText); + + bool newIsInRect = (itemRect.width() >= tmpBounding.width() && + itemRect.height() >= tmpBounding.height()); + if (isInRect != newIsInRect) { + if (isInRect) fontSize--; + break; + } + } + + //---- + fontSize = std::min(fontSize, m_maximumFontSize / shrink); + + QFont font(m_font); + font.setPixelSize(fontSize); + + p.setFont(font); + p.setPen(m_color); + + if (m_type == FreeText) + p.drawText(itemRect, Qt::AlignLeft | Qt::AlignTop, contentText); + else + p.drawText(itemRect, Qt::AlignCenter, contentText); +} + +void BoardItem::saveData(TOStream &os) { + os.child("type") << type2String(m_type); + os.child("name") << m_name; + os.child("rect") << m_rect.x() << m_rect.y() << m_rect.width() + << m_rect.height(); + + if (m_type == Image) { + // if the path is in library folder, then save the realtive path + TFilePath libFp = ToonzFolder::getLibraryFolder(); + if (libFp.isAncestorOf(m_imgPath)) + os.child("imgPath") << 1 << m_imgPath - libFp; + else + os.child("imgPath") << 0 << m_imgPath; + os.child("imgARMode") << (int)m_imgARMode; + } else { + if (m_type == FreeText) os.child("text") << m_text; + + os.child("maximumFontSize") << m_maximumFontSize; + os.child("color") << m_color.red() << m_color.green() << m_color.blue() + << m_color.alpha(); + os.child("font") << m_font.family() << (int)(m_font.bold() ? 1 : 0) + << (int)(m_font.italic() ? 1 : 0); + } +} + +void BoardItem::loadData(TIStream &is) { + std::string tagName; + while (is.matchTag(tagName)) { + if (tagName == "type") { + std::wstring typeStr; + is >> typeStr; + m_type = string2Type(typeStr); + } else if (tagName == "name") { + std::wstring str; + is >> str; + m_name = QString::fromStdWString(str); + } else if (tagName == "rect") { + double x, y, width, height; + is >> x >> y >> width >> height; + m_rect = QRectF(x, y, width, height); + } else if (tagName == "imgPath") { + int isInLibrary; + TFilePath fp; + is >> isInLibrary >> fp; + if (isInLibrary == 1) + m_imgPath = ToonzFolder::getLibraryFolder() + fp; + else + m_imgPath = fp; + } else if (tagName == "imgARMode") { + int mode; + is >> mode; + m_imgARMode = (Qt::AspectRatioMode)mode; + } else if (tagName == "text") { + std::wstring str; + is >> str; + m_text = QString::fromStdWString(str); + } else if (tagName == "maximumFontSize") { + is >> m_maximumFontSize; + } else if (tagName == "color") { + int r, g, b, a; + is >> r >> g >> b >> a; + m_color = QColor(r, g, b, a); + } else if (tagName == "font") { + QString family; + int isBold, isItalic; + is >> family >> isBold >> isItalic; + m_font.setFamily(family); + m_font.setBold(isBold == 1); + m_font.setItalic(isItalic == 1); + } else + throw TException("unexpected tag: " + tagName); + is.closeChild(); + } +} + +//====================================================================================== + +BoardSettings::BoardSettings() { + // add one item as an example + m_items.push_back(BoardItem()); +} + +QImage BoardSettings::getBoardImage(TDimension &dim, int shrink, + ToonzScene *scene) { + QImage img(dim.lx, dim.ly, QImage::Format_ARGB32); + + QPainter painter(&img); + + painter.fillRect(img.rect(), Qt::white); + + // draw each item + for (int i = m_items.size() - 1; i >= 0; i--) + m_items[i].drawItem(painter, img.rect().size(), shrink, scene); + + painter.end(); + + return img; +} + +TRaster32P BoardSettings::getBoardRaster(TDimension &dim, int shrink, + ToonzScene *scene) { + QImage img = getBoardImage(dim, shrink, scene); + + // convert QImage to TRaster + TRaster32P boardRas(dim); + int img_y = img.height() - 1; + for (int j = 0; j < dim.ly; j++, img_y--) { + TPixel32 *pix = boardRas->pixels(j); + QRgb *img_p = (QRgb *)img.scanLine(img_y); + for (int i = 0; i < dim.lx; i++, pix++, img_p++) { + (*pix).r = (typename TPixel32::Channel)(qRed(*img_p)); + (*pix).g = (typename TPixel32::Channel)(qGreen(*img_p)); + (*pix).b = (typename TPixel32::Channel)(qBlue(*img_p)); + (*pix).m = (typename TPixel32::Channel)(qAlpha(*img_p)); + } + } + return boardRas; +} + +void BoardSettings::addNewItem(int insertAt) { + m_items.insert(insertAt, BoardItem()); +} + +void BoardSettings::removeItem(int index) { + if (index < 0 || index >= m_items.size()) return; + m_items.removeAt(index); +} + +void BoardSettings::swapItems(int i, int j) { m_items.swap(i, j); } + +void BoardSettings::saveData(TOStream &os, bool forPreset) { + if (!forPreset) os.child("active") << (int)((m_active) ? 1 : 0); + os.child("duration") << m_duration; + if (!m_items.isEmpty()) { + os.openChild("boardItems"); + for (int i = 0; i < getItemCount(); i++) { + os.openChild("item"); + m_items[i].saveData(os); + os.closeChild(); + } + os.closeChild(); + } +} + +void BoardSettings::loadData(TIStream &is) { + std::string tagName; + while (is.matchTag(tagName)) { + if (tagName == "active") { + int val; + is >> val; + setActive(val == 1); + } else if (tagName == "duration") { + is >> m_duration; + } else if (tagName == "boardItems") { + m_items.clear(); + while (is.matchTag(tagName)) { + if (tagName == "item") { + BoardItem item; + item.loadData(is); + m_items.append(item); + } else + throw TException("unexpected tag: " + tagName); + is.closeChild(); + } + } else + throw TException("unexpected tag: " + tagName); + is.closeChild(); + } +} \ No newline at end of file diff --git a/toonz/sources/toonzlib/movierenderer.cpp b/toonz/sources/toonzlib/movierenderer.cpp index c11a4c2..1d8b306 100644 --- a/toonz/sources/toonzlib/movierenderer.cpp +++ b/toonz/sources/toonzlib/movierenderer.cpp @@ -20,6 +20,7 @@ #include "toonz/trasterimageutils.h" #include "toonz/levelupdater.h" #include "toutputproperties.h" +#include "toonz/boardsettings.h" // tcg includes #include "tcg/tcg_macros.h" @@ -160,6 +161,8 @@ public: std::pair saveFrame(double frame, const std::pair &rasters); std::string getRenderCacheId(); + + void addBoard(); }; //--------------------------------------------------------- @@ -374,7 +377,15 @@ std::pair MovieRenderer::Imp::saveFrame( m_renderSettings.m_timeStretchFrom; int fr = (stretchFac != 1) ? tround(frame * stretchFac) : int(frame); - TFrameId fid(fr + 1); + + int boardDuration = 0; + if (m_movieType) { + BoardSettings *bs = + m_scene->getProperties()->getOutputProperties()->getBoardSettings(); + boardDuration = (bs->isActive()) ? bs->getDuration() : 0; + } + + TFrameId fid(fr + 1 + boardDuration); if (m_levelUpdaterA.get()) { assert(m_levelUpdaterB.get() || !rasters.second); @@ -474,6 +485,8 @@ void MovieRenderer::Imp::doRenderRasterCompleted(const RenderData &renderData) { if (m_levelUpdaterB.get()) m_levelUpdaterB->getLevelWriter()->saveSoundTrack(m_st.getPointer()); } + + addBoard(); } // Output frames must be *cloned*, since the supplied rasters will be @@ -732,6 +745,40 @@ void MovieRenderer::Imp::onRenderFinished(bool isCanceled) { // eventually be deleted. } +//--------------------------------------------------------- + +void MovieRenderer::Imp::addBoard() { + BoardSettings *boardSettings = + m_scene->getProperties()->getOutputProperties()->getBoardSettings(); + if (!boardSettings->isActive()) return; + int duration = boardSettings->getDuration(); + if (duration == 0) return; + // Get the image size + int shrinkX = m_renderSettings.m_shrinkX, + shrinkY = m_renderSettings.m_shrinkY; + TDimensionD cameraRes(double(m_frameSize.lx) / shrinkX, + double(m_frameSize.ly) / shrinkY); + TDimension cameraResI(cameraRes.lx, cameraRes.ly); + + TRaster32P boardRas = + boardSettings->getBoardRaster(cameraResI, shrinkX, m_scene); + + if (m_levelUpdaterA.get()) { + // Flush images + try { + TRasterImageP img(boardRas); + for (int f = 0; f < duration; f++) { + m_levelUpdaterA->update(TFrameId(f + 1), img); + if (m_levelUpdaterB.get()) + m_levelUpdaterB->update(TFrameId(f + 1), img); + } + } catch (...) { + // Nothing. The images could not be saved for whatever reason. + // Failure is reported. + } + } +} + //====================================================================================== //====================== diff --git a/toonz/sources/toonzlib/outputproperties.cpp b/toonz/sources/toonzlib/outputproperties.cpp index 0ea61b2..2b90dde 100644 --- a/toonz/sources/toonzlib/outputproperties.cpp +++ b/toonz/sources/toonzlib/outputproperties.cpp @@ -2,6 +2,9 @@ #include "toutputproperties.h" +// TnzLib includes +#include "toonz/boardsettings.h" + // TnzBase includes #include "trasterfx.h" @@ -37,7 +40,8 @@ TOutputProperties::TOutputProperties() , m_multimediaRendering(0) , m_maxTileSizeIndex(0) , m_threadIndex(2) - , m_subcameraPreview(false) { + , m_subcameraPreview(false) + , m_boardSettings(new BoardSettings()) { m_renderSettings = new TRenderSettings(); } @@ -56,7 +60,8 @@ TOutputProperties::TOutputProperties(const TOutputProperties &src) , m_multimediaRendering(src.m_multimediaRendering) , m_maxTileSizeIndex(src.m_maxTileSizeIndex) , m_threadIndex(src.m_threadIndex) - , m_subcameraPreview(src.m_subcameraPreview) { + , m_subcameraPreview(src.m_subcameraPreview) + , m_boardSettings(new BoardSettings(*src.m_boardSettings)) { std::map::iterator ft, fEnd = m_formatProperties.end(); for (ft = m_formatProperties.begin(); ft != fEnd; ++ft) { @@ -100,6 +105,9 @@ TOutputProperties &TOutputProperties::operator=(const TOutputProperties &src) { for (sft = src.m_formatProperties.begin(); sft != sfEnd; ++sft) m_formatProperties[sft->first] = sft->second->clone(); + delete m_boardSettings; + m_boardSettings = new BoardSettings(*src.m_boardSettings); + return *this; } diff --git a/toonz/sources/toonzlib/sceneproperties.cpp b/toonz/sources/toonzlib/sceneproperties.cpp index acf0699..7bc3c59 100644 --- a/toonz/sources/toonzlib/sceneproperties.cpp +++ b/toonz/sources/toonzlib/sceneproperties.cpp @@ -11,6 +11,7 @@ #include "toonz/txshleveltypes.h" #include "toonz/preferences.h" #include "cleanuppalette.h" +#include "toonz/boardsettings.h" // TnzBase includes #include "toutputproperties.h" @@ -293,6 +294,12 @@ void TSceneProperties::saveData(TOStream &os) const { } os.closeChild(); + if (out.getBoardSettings() && out.getBoardSettings()->getDuration()) { + os.openChild("clapperboardSettings"); + out.getBoardSettings()->saveData(os); + os.closeChild(); + } + os.closeChild(); // } os.closeChild(); @@ -654,6 +661,9 @@ void TSceneProperties::loadData(TIStream &is, bool isLoadingProject) { } else throw TException("unexpected tag: " + tagName); } // end while + } else if (tagName == "clapperboardSettings") { + assert(out.getBoardSettings()); + out.getBoardSettings()->loadData(is); } else { throw TException("unexpected property tag: " + tagName); } diff --git a/toonz/sources/toonzqt/colorfield.cpp b/toonz/sources/toonzqt/colorfield.cpp index 2d91a76..407aee8 100644 --- a/toonz/sources/toonzqt/colorfield.cpp +++ b/toonz/sources/toonzqt/colorfield.cpp @@ -203,6 +203,7 @@ ChannelField::ChannelField(QWidget *parent, const QString &string, int value, m_channelSlider->setRange(0, maxValue); m_channelSlider->setValue(value); + if (sliderWidth > 0) m_channelSlider->setFixedWidth(sliderWidth); //----layout QGridLayout *mainLayout = new QGridLayout(this); @@ -339,7 +340,7 @@ ColorField::ColorFieldEditorController *ColorField::m_editorController = 0; Return ColorField current color. */ ColorField::ColorField(QWidget *parent, bool isAlphaActive, TPixel32 color, - int squareSize, bool useStyleEditor) + int squareSize, bool useStyleEditor, int sliderWidth) : QWidget(parent) , m_color(color) , m_notifyEditingChange(true) @@ -355,16 +356,20 @@ ColorField::ColorField(QWidget *parent, bool isAlphaActive, TPixel32 color, m_colorSample = new StyleSample(this, squareSize, squareSize); m_colorSample->setColor(m_color); - m_redChannel = new ChannelField(this, tr("R:"), m_color.r); + m_redChannel = + new ChannelField(this, tr("R:"), m_color.r, 255, false, 13, sliderWidth); connect(m_redChannel, SIGNAL(valueChanged(int, bool)), SLOT(onRedChannelChanged(int, bool))); - m_greenChannel = new ChannelField(this, tr("G:"), m_color.g); + m_greenChannel = + new ChannelField(this, tr("G:"), m_color.g, 255, false, 13, sliderWidth); connect(m_greenChannel, SIGNAL(valueChanged(int, bool)), SLOT(onGreenChannelChanged(int, bool))); - m_blueChannel = new ChannelField(this, tr("B:"), m_color.b); + m_blueChannel = + new ChannelField(this, tr("B:"), m_color.b, 255, false, 13, sliderWidth); connect(m_blueChannel, SIGNAL(valueChanged(int, bool)), SLOT(onBlueChannelChanged(int, bool))); - m_alphaChannel = new ChannelField(this, tr("A:"), m_color.m); + m_alphaChannel = + new ChannelField(this, tr("A:"), m_color.m, 255, false, 13, sliderWidth); connect(m_alphaChannel, SIGNAL(valueChanged(int, bool)), SLOT(onAlphaChannelChanged(int, bool)));