diff --git a/app/app.pro b/app/app.pro index e0a962d4b0..152428901c 100644 --- a/app/app.pro +++ b/app/app.pro @@ -105,6 +105,7 @@ HEADERS += \ src/doubleprogressdialog.h \ src/colorslider.h \ src/checkupdatesdialog.h \ + src/framecommentwidget.h \ src/presetdialog.h \ src/commandlineparser.h \ src/commandlineexporter.h \ @@ -150,6 +151,7 @@ SOURCES += \ src/doubleprogressdialog.cpp \ src/colorslider.cpp \ src/checkupdatesdialog.cpp \ + src/framecommentwidget.cpp \ src/presetdialog.cpp \ src/app_util.cpp \ src/commandlineparser.cpp \ @@ -185,6 +187,7 @@ FORMS += \ ui/filespage.ui \ ui/toolspage.ui \ ui/toolboxwidget.ui \ + ui/framecommentwidget.ui \ ui/presetdialog.ui GIT { diff --git a/app/src/framecommentwidget.cpp b/app/src/framecommentwidget.cpp new file mode 100644 index 0000000000..7cb8885144 --- /dev/null +++ b/app/src/framecommentwidget.cpp @@ -0,0 +1,196 @@ +#include "framecommentwidget.h" +#include "ui_framecommentwidget.h" + +#include "editor.h" +#include "keyframe.h" + +#include "layermanager.h" +#include "playbackmanager.h" + +FrameCommentWidget::FrameCommentWidget(QWidget *parent) : + BaseDockWidget(parent) +{ + QWidget* innerWidget = new QWidget; + setWindowTitle(tr("Frame Comments")); + + ui = new Ui::FrameComment; + ui->setupUi(innerWidget); + setWidget(innerWidget); +} + +FrameCommentWidget::~FrameCommentWidget() +{ + delete ui; +} + +void FrameCommentWidget::initUI() +{ + connect(this, &FrameCommentWidget::visibilityChanged, this, &FrameCommentWidget::updateConnections); + updateConnections(); +} + +void FrameCommentWidget::updateUI() +{ +} + +void FrameCommentWidget::setCore(Editor *editor) +{ + mEditor = editor; +} + +void FrameCommentWidget::dialogueTextChanged() +{ + int len = ui->textEditDialogue->toPlainText().length(); + ui->labelDialogueCounter->setText(tr("%1 chars").arg(QString::number(len))); +} + +void FrameCommentWidget::actionTextChanged() +{ + int len = ui->textEditAction->toPlainText().length(); + ui->labelActionCounter->setText(tr("%1 chars").arg(QString::number(len))); +} + +void FrameCommentWidget::slugTextChanged() +{ + int len = ui->textEditSlug->toPlainText().length(); + ui->labelSlugCounter->setText(tr("%1 chars").arg(QString::number(len))); +} + +void FrameCommentWidget::enableCommentFields() +{ + ui->textEditDialogue->setEnabled(true); + ui->textEditAction->setEnabled(true); + ui->textEditSlug->setEnabled(true); +} + +void FrameCommentWidget::disableCommentFields() +{ + ui->textEditDialogue->setEnabled(false); + ui->textEditAction->setEnabled(false); + ui->textEditSlug->setEnabled(false); +} + +void FrameCommentWidget::currentFrameChanged(int frame) +{ + if (!mIsPlaying) + { + if (frame >= mEditor->layers()->currentLayer()->firstKeyFramePosition()) + { + fillComments(); + } + else + { + clearFrameCommentsFields(); + } + } +} + +void FrameCommentWidget::currentLayerChanged(int index) +{ + Q_UNUSED(index) + currentFrameChanged(mEditor->currentFrame()); +} + +void FrameCommentWidget::clearFrameCommentsFields() +{ + ui->textEditDialogue->clear(); + ui->textEditAction->clear(); + ui->textEditSlug->clear(); +} + +void FrameCommentWidget::playStateChanged(bool isPlaying) +{ + mIsPlaying = isPlaying; + if (!mIsPlaying) + { + enableCommentFields(); + currentFrameChanged(mEditor->currentFrame()); + } + else + { + disableCommentFields(); + } +} + +void FrameCommentWidget::updateConnections() +{ + if (!isVisible()) + { + disconnectNotifiers(); + } + else + { + makeConnections(); + } +} + +void FrameCommentWidget::fillComments() +{ + KeyFrame* keyframe = getKeyFrame(); + if (keyframe == nullptr) { return; } + + QSignalBlocker b(ui->textEditDialogue); + QSignalBlocker b2(ui->textEditAction); + QSignalBlocker b3(ui->textEditSlug); + + ui->textEditDialogue->setPlainText(keyframe->getDialogueComment()); + ui->textEditAction->setPlainText(keyframe->getActionComment()); + ui->textEditSlug->setPlainText(keyframe->getSlugComment()); +} + +void FrameCommentWidget::applyComments() +{ + KeyFrame* keyframe = getKeyFrame(); + if (keyframe == nullptr) { return; } + + keyframe->setDialogueComment(ui->textEditDialogue->toPlainText()); + keyframe->setActionComment(ui->textEditAction->toPlainText()); + keyframe->setSlugComment(ui->textEditSlug->toPlainText()); + mEditor->layers()->currentLayer()->setModified(keyframe->pos(), true); +} + +KeyFrame* FrameCommentWidget::getKeyFrame() +{ + const int frame = mEditor->currentFrame(); + const Layer* layer = mEditor->layers()->currentLayer(); + KeyFrame* keyframe = layer->getKeyFrameAt(frame); + if (keyframe == nullptr) + keyframe = layer->getKeyFrameAt(layer->getPreviousFrameNumber(frame, true)); + if (keyframe == nullptr) { return nullptr; } + + return keyframe; +} + +void FrameCommentWidget::makeConnections() +{ + connect(ui->textEditDialogue, &QPlainTextEdit::textChanged, this, &FrameCommentWidget::dialogueTextChanged); + connect(ui->textEditAction, &QPlainTextEdit::textChanged, this, &FrameCommentWidget::actionTextChanged); + connect(ui->textEditSlug, &QPlainTextEdit::textChanged, this, &FrameCommentWidget::slugTextChanged); + connect(ui->btnClearFields, &QPushButton::clicked, this, &FrameCommentWidget::clearFrameCommentsFields); + + connect(ui->textEditSlug, &QPlainTextEdit::textChanged, this, &FrameCommentWidget::applyComments); + connect(ui->textEditAction, &QPlainTextEdit::textChanged, this, &FrameCommentWidget::applyComments); + connect(ui->textEditDialogue, &QPlainTextEdit::textChanged, this, &FrameCommentWidget::applyComments); + + connect(mEditor, &Editor::scrubbed, this, &FrameCommentWidget::currentFrameChanged); + connect(mEditor->layers(), &LayerManager::currentLayerChanged, this, &FrameCommentWidget::currentLayerChanged); + connect(mEditor, &Editor::objectLoaded, this, &FrameCommentWidget::fillComments); + connect(mEditor->playback(), &PlaybackManager::playStateChanged, this, &FrameCommentWidget::playStateChanged); +} + +void FrameCommentWidget::disconnectNotifiers() +{ + disconnect(ui->textEditDialogue, &QPlainTextEdit::textChanged, this, &FrameCommentWidget::dialogueTextChanged); + disconnect(ui->textEditAction, &QPlainTextEdit::textChanged, this, &FrameCommentWidget::actionTextChanged); + disconnect(ui->textEditSlug, &QPlainTextEdit::textChanged, this, &FrameCommentWidget::slugTextChanged); + disconnect(ui->btnClearFields, &QPushButton::clicked, this, &FrameCommentWidget::clearFrameCommentsFields); + + disconnect(ui->textEditSlug, &QPlainTextEdit::textChanged, this, &FrameCommentWidget::applyComments); + disconnect(ui->textEditAction, &QPlainTextEdit::textChanged, this, &FrameCommentWidget::applyComments); + disconnect(ui->textEditDialogue, &QPlainTextEdit::textChanged, this, &FrameCommentWidget::applyComments); + + disconnect(mEditor, &Editor::scrubbed, this, &FrameCommentWidget::currentFrameChanged); + disconnect(mEditor->layers(), &LayerManager::currentLayerChanged, this, &FrameCommentWidget::currentLayerChanged); + disconnect(mEditor, &Editor::objectLoaded, this, &FrameCommentWidget::fillComments); + disconnect(mEditor->playback(), &PlaybackManager::playStateChanged, this, &FrameCommentWidget::playStateChanged); +} diff --git a/app/src/framecommentwidget.h b/app/src/framecommentwidget.h new file mode 100644 index 0000000000..0f89d9e5f2 --- /dev/null +++ b/app/src/framecommentwidget.h @@ -0,0 +1,59 @@ +#ifndef FRAMECOMMENTWIDGET_H +#define FRAMECOMMENTWIDGET_H + +#include +#include "basedockwidget.h" + +class Editor; +class KeyFrame; +class Layer; + +namespace Ui { + class FrameComment; +} + +class FrameCommentWidget : public BaseDockWidget +{ + Q_OBJECT + +public: + explicit FrameCommentWidget(QWidget *parent = nullptr); + ~FrameCommentWidget() override; + + void initUI() override; + void updateUI() override; + void setCore(Editor* editor); + + void applyComments(); + void fillComments(); + +private: + Ui::FrameComment *ui; + + void dialogueTextChanged(); + void actionTextChanged(); + void slugTextChanged(); + + void enableCommentFields(); + void disableCommentFields(); + + void currentFrameChanged(int frame); + void currentLayerChanged(int index); + + void clearFrameCommentsFields(); + + void updateConnections(); + + void playStateChanged(bool isPlaying); + + KeyFrame* getKeyFrame(); + + void makeConnections(); + void disconnectNotifiers(); + + bool mIsPlaying = false; + + Editor* mEditor = nullptr; +}; + +#endif // FRAMECOMMENTWIDGET_H diff --git a/app/src/mainwindow2.cpp b/app/src/mainwindow2.cpp index 8013771718..8da5230e05 100644 --- a/app/src/mainwindow2.cpp +++ b/app/src/mainwindow2.cpp @@ -57,6 +57,7 @@ GNU General Public License for more details. #include "colorinspector.h" #include "colorpalettewidget.h" #include "displayoptionwidget.h" +#include "framecommentwidget.h" #include "tooloptionwidget.h" #include "preferencesdialog.h" #include "timeline.h" @@ -160,6 +161,10 @@ void MainWindow2::createDockWidgets() mDisplayOptionWidget = new DisplayOptionWidget(this); mDisplayOptionWidget->setObjectName("DisplayOption"); + mFrameComments = new FrameCommentWidget(this); + mFrameComments->setObjectName("FrameComments"); + mFrameComments->setCore(mEditor); + mOnionSkinWidget = new OnionSkinWidget(this); mOnionSkinWidget->setObjectName("Onion Skin"); @@ -181,6 +186,7 @@ void MainWindow2::createDockWidgets() << mColorInspector << mColorPalette << mDisplayOptionWidget + << mFrameComments << mOnionSkinWidget << mToolOptions << mToolBox; @@ -208,6 +214,7 @@ void MainWindow2::createDockWidgets() addDockWidget(Qt::LeftDockWidgetArea, mDisplayOptionWidget); addDockWidget(Qt::LeftDockWidgetArea, mOnionSkinWidget); addDockWidget(Qt::BottomDockWidgetArea, mTimeLine); + setDockNestingEnabled(true); /* @@ -389,6 +396,7 @@ void MainWindow2::createMenus() mTimeLine->toggleViewAction(), mDisplayOptionWidget->toggleViewAction(), mColorInspector->toggleViewAction(), + mFrameComments->toggleViewAction(), mOnionSkinWidget->toggleViewAction() }; diff --git a/app/src/mainwindow2.h b/app/src/mainwindow2.h index a8572b977e..6b0b6df68d 100644 --- a/app/src/mainwindow2.h +++ b/app/src/mainwindow2.h @@ -28,6 +28,7 @@ class ScribbleArea; class BaseDockWidget; class ColorPaletteWidget; class DisplayOptionWidget; +class FrameCommentWidget; class OnionSkinWidget; class ToolOptionWidget; class TimeLine; @@ -155,6 +156,7 @@ private slots: ColorBox* mColorBox = nullptr; ColorPaletteWidget* mColorPalette = nullptr; DisplayOptionWidget* mDisplayOptionWidget = nullptr; + FrameCommentWidget* mFrameComments = nullptr; ToolOptionWidget* mToolOptions = nullptr; ToolBoxWidget* mToolBox = nullptr; //Timeline2* mTimeline2 = nullptr; diff --git a/app/ui/framecommentwidget.ui b/app/ui/framecommentwidget.ui new file mode 100644 index 0000000000..a39acb4288 --- /dev/null +++ b/app/ui/framecommentwidget.ui @@ -0,0 +1,162 @@ + + + FrameComment + + + + 0 + 0 + 400 + 379 + + + + Frame Comments + + + + 2 + + + 4 + + + 4 + + + 4 + + + 4 + + + + + + + Dialogue: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 0 + + + + + + + + + + + + + + Action: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 0 + + + + + + + + + + + + + + Slug (Timing, Camera, ...) + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 0 + + + + + + + + + + + + + + Clear fields + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + diff --git a/core_lib/src/structure/filemanager.cpp b/core_lib/src/structure/filemanager.cpp index de1a7f9111..6c426f13b3 100644 --- a/core_lib/src/structure/filemanager.cpp +++ b/core_lib/src/structure/filemanager.cpp @@ -23,6 +23,8 @@ GNU General Public License for more details. #include "qminiz.h" #include "fileformat.h" #include "object.h" +#include "layer.h" +#include "keyframe.h" #include "layercamera.h" FileManager::FileManager(QObject* parent) : QObject(parent) @@ -172,6 +174,10 @@ bool FileManager::loadObject(Object* object, const QDomElement& root) ObjectData* projectData = loadProjectData(element); object->setData(projectData); } + else if (element.tagName() == "framecomments") + { + loadFrameComments(object, element); + } else if (element.tagName() == "version") { QVersionNumber fileVersion = QVersionNumber::fromString(element.text()); @@ -309,6 +315,8 @@ Status FileManager::save(const Object* object, const QString& sFileName) const bool saveOk = stKeyFrames.ok() && stMainXml.ok() && stPalette.ok(); + // save comment information + progressForward(); if (!isOldType) @@ -451,6 +459,78 @@ QDomElement FileManager::saveProjectData(const ObjectData* data, QDomDocument& x return rootTag; } +void FileManager::loadFrameComments(Object *obj, QDomElement &element) +{ + Layer* layer = nullptr; + KeyFrame* key = nullptr; + + int newLayerIndex = -1; + int oldLayerIndex = -1; + + for(QDomNode tag = element.firstChild(); !tag.isNull(); tag = tag.nextSibling()) + { + QDomElement comments = tag.toElement(); + if (comments.isNull()) + { + continue; + } + + oldLayerIndex = comments.attribute("layer").toInt(); + if (newLayerIndex != oldLayerIndex) { + layer = obj->getLayer(oldLayerIndex); + } + + if (layer == nullptr) { + continue; + } + + // get keyFrame + int frame = comments.attribute("frame").toInt(); + key = layer->getKeyFrameAt(frame); + + // set keyFrame comments + key->setDialogueComment(comments.attribute("dialogue")); + key->setActionComment(comments.attribute("action")); + key->setSlugComment(comments.attribute("slug")); + + newLayerIndex = oldLayerIndex; + } +} + +QDomElement FileManager::saveFrameComments(const Object* obj, QDomDocument &xmlDoc) +{ + int layers = obj->getLayerCount(); + QDomElement rootTag = xmlDoc.createElement("framecomments"); + + KeyFrame* key = nullptr; + for (int i = 0; i < layers; i++) + { + Layer* layer = obj->getLayer(i); + QString tag = "content"; + int frame = layer->firstKeyFramePosition(); + do + { + key = layer->getKeyFrameAt(frame); + if (key->frameHasComments()) + { + QDomElement tagComments = xmlDoc.createElement(tag); + tagComments.setAttribute("layer", i); + tagComments.setAttribute("frame", frame); + tagComments.setAttribute("dialogue", key->getDialogueComment()); + tagComments.setAttribute("action", key->getActionComment()); + tagComments.setAttribute("slug", key->getSlugComment()); + rootTag.appendChild(tagComments); + } + if (frame == layer->getMaxKeyFramePosition()) + frame++; + else + frame = layer->getNextKeyFramePosition(frame); + } while (frame <= layer->getMaxKeyFramePosition()); + } + + return rootTag; +} + void FileManager::extractProjectData(const QDomElement& element, ObjectData* data) { Q_ASSERT(data); @@ -648,6 +728,10 @@ Status FileManager::writeMainXml(const Object* object, const QString& mainXmlPat QDomElement objectElement = object->saveXML(xmlDoc); root.appendChild(objectElement); + // save comment information + QDomElement frameComments = saveFrameComments(object, xmlDoc); + root.appendChild(frameComments); + // save Pencil2D version QDomElement versionElem = xmlDoc.createElement("version"); versionElem.appendChild(xmlDoc.createTextNode(QString(APP_VERSION))); diff --git a/core_lib/src/structure/filemanager.h b/core_lib/src/structure/filemanager.h index a8571d87d6..5bee778f36 100644 --- a/core_lib/src/structure/filemanager.h +++ b/core_lib/src/structure/filemanager.h @@ -37,7 +37,7 @@ class FileManager : public QObject Q_OBJECT public: - FileManager(QObject* parent = 0); + FileManager(QObject* parent = nullptr); Object* load(const QString& sFilenNme); Status save(const Object*, const QString& sFileName); @@ -68,6 +68,9 @@ class FileManager : public QObject ObjectData* loadProjectData(const QDomElement& element); QDomElement saveProjectData(const ObjectData*, QDomDocument& xmlDoc); + void loadFrameComments(Object* obj, QDomElement &element); + QDomElement saveFrameComments(const Object *obj, QDomDocument& xmlDoc); + void extractProjectData(const QDomElement& element, ObjectData* data); void handleOpenProjectError(Status::ErrorCode, const DebugDetails&); diff --git a/core_lib/src/structure/keyframe.cpp b/core_lib/src/structure/keyframe.cpp index 88282e3632..b04f098097 100644 --- a/core_lib/src/structure/keyframe.cpp +++ b/core_lib/src/structure/keyframe.cpp @@ -40,6 +40,13 @@ KeyFrame::~KeyFrame() } } +bool KeyFrame::frameHasComments() +{ + if (mDialogueComment.isEmpty() && mActionComment.isEmpty() && mSlugComment.isEmpty()) + return false; + return true; +} + KeyFrame& KeyFrame::operator=(const KeyFrame& k2) { if (this == &k2) diff --git a/core_lib/src/structure/keyframe.h b/core_lib/src/structure/keyframe.h index 4412bb55cb..37447f82b3 100644 --- a/core_lib/src/structure/keyframe.h +++ b/core_lib/src/structure/keyframe.h @@ -48,6 +48,14 @@ class KeyFrame void setSelected(bool b) { mIsSelected = b; } bool isSelected() const { return mIsSelected; } + void setDialogueComment(QString comment) { mDialogueComment = comment; } + QString getDialogueComment() { return mDialogueComment; } + void setActionComment(QString comment) { mActionComment = comment; } + QString getActionComment() { return mActionComment; } + void setSlugComment(QString comment) { mSlugComment = comment; } + QString getSlugComment() { return mSlugComment; } + bool frameHasComments(); + QString fileName() const { return mAttachedFileName; } void setFileName(QString strFileName) { mAttachedFileName = strFileName; } @@ -68,6 +76,11 @@ class KeyFrame bool mIsSelected = false; QString mAttachedFileName; + // comments + QString mDialogueComment = ""; + QString mActionComment = ""; + QString mSlugComment = ""; + std::vector mEventListeners; };