diff --git a/stuff/config/current.txt b/stuff/config/current.txt index 62c5c93..19e32cf 100644 --- a/stuff/config/current.txt +++ b/stuff/config/current.txt @@ -1255,6 +1255,19 @@ <item>"STD_iwa_BarrelDistortFx.vignetteMidpoint" "Vignetting Midpoint"</item> <item>"STD_iwa_BarrelDistortFx.scale" "Scale"</item> + <item>"STD_iwa_TextFx" "Text Iwa"</item> + <item>"STD_iwa_TextFx.targetType" "Source"</item> + <item>"STD_iwa_TextFx.columnIndex" "Column Index"</item> + <item>"STD_iwa_TextFx.hAlign" "Holizontal Align"</item> + <item>"STD_iwa_TextFx.text" "Text"</item> + <item>"STD_iwa_TextFx.center" "Center"</item> + <item>"STD_iwa_TextFx.width" "Width"</item> + <item>"STD_iwa_TextFx.height" "Height"</item> + <item>"STD_iwa_TextFx.font" "Font"</item> + <item>"STD_iwa_TextFx.textColor" "Text Color"</item> + <item>"STD_iwa_TextFx.boxColor" "Box Color"</item> + <item>"STD_iwa_TextFx.showBorder" "Show Border"</item> + <!------------------------------ Tiled Particles Iwa -------------------------------------------> <item>STD_iwa_TiledParticlesFx "Tiled Particles Iwa" </item> diff --git a/stuff/profiles/layouts/fxs/STD_iwa_TextFx.xml b/stuff/profiles/layouts/fxs/STD_iwa_TextFx.xml new file mode 100644 index 0000000..aab4c54 --- /dev/null +++ b/stuff/profiles/layouts/fxs/STD_iwa_TextFx.xml @@ -0,0 +1,17 @@ +<fxlayout> + <page name="Text Iwa"> + <vbox> + <control>targetType</control> + <control>columnIndex</control> + <control>text</control> + <control>hAlign</control> + <control>center</control> + <control>width</control> + <control>height</control> + <control>font</control> + <control>textColor</control> + <control>boxColor</control> + <control>showBorder</control> + </vbox> + </page> +</fxlayout> diff --git a/stuff/profiles/layouts/fxs/fxs.lst b/stuff/profiles/layouts/fxs/fxs.lst index 126bc35..622ed46 100644 --- a/stuff/profiles/layouts/fxs/fxs.lst +++ b/stuff/profiles/layouts/fxs/fxs.lst @@ -146,7 +146,8 @@ STD_inopnCloudsFx STD_particlesFx STD_iwa_TiledParticlesFx - STD_iwa_TimeCodeFx + STD_iwa_PNPerspectiveFx + STD_iwa_TextFx </Render> <Stylize> STD_colorEmbossFx diff --git a/toonz/sources/common/tparam/tnotanimatableparam.cpp b/toonz/sources/common/tparam/tnotanimatableparam.cpp index dc89a62..ea206f7 100644 --- a/toonz/sources/common/tparam/tnotanimatableparam.cpp +++ b/toonz/sources/common/tparam/tnotanimatableparam.cpp @@ -8,6 +8,7 @@ PERSIST_IDENTIFIER(TBoolParam, "boolParam") PERSIST_IDENTIFIER(TFilePathParam, "filePathParam") PERSIST_IDENTIFIER(TStringParam, "stringParam") PERSIST_IDENTIFIER(TNADoubleParam, "naDoubleParam") +PERSIST_IDENTIFIER(TFontParam, "fontParam") // PERSIST_IDENTIFIER(TIntEnumParam, "intEnumParam") TPersistDeclarationT<TEnumParam> TEnumParam::m_declaration("intEnumParam"); @@ -94,6 +95,17 @@ void TNADoubleParam::saveData(TOStream &os) { os << getDefaultValue(); os << getValue(); } + +//========================================================= + +void TFontParam::loadData(TIStream &is) { + std::wstring str; + is >> str; + setValue(str, false); +} + +void TFontParam::saveData(TOStream &os) { os << getValue(); } + //========================================================= //========================================================= diff --git a/toonz/sources/include/tnotanimatableparam.h b/toonz/sources/include/tnotanimatableparam.h index b28497d..d61f07b 100644 --- a/toonz/sources/include/tnotanimatableparam.h +++ b/toonz/sources/include/tnotanimatableparam.h @@ -13,6 +13,7 @@ #include "tconvert.h" #include <set> +#include <QFont> #undef DVAPI #undef DVVAR @@ -169,6 +170,7 @@ public: using namespace std; return to_string(getValue()); } + bool hasKeyframes() const override { return 0; }; void getKeyframes(std::set<double> &) const override{}; int getNextKeyframe(double) const override { return -1; }; @@ -280,6 +282,8 @@ template class DVAPI TPersistDeclarationT<TStringParam>; class DVAPI TStringParam final : public TNotAnimatableParam<std::wstring> { PERSIST_DECLARATION(TStringParam); + bool m_multiLine = false; + public: TStringParam(std::wstring v = L"") : TNotAnimatableParam<std::wstring>(v) {} TStringParam(const TStringParam &src) @@ -287,6 +291,9 @@ public: TParam *clone() const override { return new TStringParam(*this); } void loadData(TIStream &is) override; void saveData(TOStream &os) override; + + void setMultiLineEnabled(bool enable) { m_multiLine = enable; } + bool isMultiLineEnabled() { return m_multiLine; } }; DEFINE_PARAM_SMARTPOINTER(TStringParam, std::wstring) @@ -393,6 +400,32 @@ private: DEFINE_PARAM_SMARTPOINTER(TNADoubleParam, double) +//========================================================= +// +// class TFontParam +// +//========================================================= + +#ifdef _WIN32 +template class DVAPI TNotAnimatableParam<std::wstring>; +class TFontParam; +template class DVAPI TPersistDeclarationT<TFontParam>; +#endif + +class DVAPI TFontParam final : public TNotAnimatableParam<std::wstring> { + PERSIST_DECLARATION(TFontParam); + +public: + TFontParam(std::wstring v = QFont().toString().toStdWString()) + : TNotAnimatableParam<std::wstring>(v) {} + TFontParam(const TFontParam &src) : TNotAnimatableParam<std::wstring>(src) {} + TParam *clone() const override { return new TFontParam(*this); } + void loadData(TIStream &is) override; + void saveData(TOStream &os) override; +}; + +DEFINE_PARAM_SMARTPOINTER(TFontParam, std::wstring) + //----------------------------------------------------------------------------- // TNotAnimatableParamChangeUndo //----------------------------------------------------------------------------- diff --git a/toonz/sources/include/toonzqt/paramfield.h b/toonz/sources/include/toonzqt/paramfield.h index 858f823..dbf3880 100644 --- a/toonz/sources/include/toonzqt/paramfield.h +++ b/toonz/sources/include/toonzqt/paramfield.h @@ -15,6 +15,7 @@ #include <QButtonGroup> #include <QRadioButton> #include <QPushButton> +#include <QTextEdit> #include "tgeometry.h" #include "tparam.h" @@ -40,6 +41,7 @@ class QString; class QComboBox; class QHBoxLayout; class TFxHandle; +class QFontComboBox; namespace DVGui { class LineEdit; @@ -501,11 +503,28 @@ protected slots: // StringParamField //----------------------------------------------------------------------------- +namespace component { +class MyTextEdit : public QTextEdit { + Q_OBJECT +public: + MyTextEdit(const QString &text, QWidget *parent = Q_NULLPTR) + : QTextEdit(text, parent) {} + +protected: + void keyPressEvent(QKeyEvent *event) override; + void focusOutEvent(QFocusEvent *e) override; + +signals: + void edited(); +}; +}; + class DVAPI StringParamField final : public ParamField { Q_OBJECT TStringParamP m_currentParam, m_actualParam; - DVGui::LineEdit *m_textFld; + DVGui::LineEdit *m_textFld = nullptr; + component::MyTextEdit *m_multiTextFld = nullptr; public: StringParamField(QWidget *parent, QString name, const TStringParamP ¶m); @@ -514,8 +533,41 @@ public: int frame) override; void update(int frame) override; - QSize getPreferedSize() override { return QSize(100, 20); } + QSize getPreferedSize() override { + if (m_textFld) + return QSize(100, 20); + else + return QSize(100, 80); + } +protected slots: + void onChange(); +}; + +//============================================================================= +// FontParamField +//----------------------------------------------------------------------------- + +class FontParamField final : public ParamField { + Q_OBJECT + + TFontParamP m_currentParam, m_actualParam; + + QFontComboBox *m_fontCombo; + QComboBox *m_styleCombo; + DVGui::IntField *m_sizeField; + +public: + FontParamField(QWidget *parent, QString name, const TFontParamP ¶m); + + void setParam(const TParamP ¤t, const TParamP &actual, + int frame) override; + void update(int frame) override; + + QSize getPreferedSize() override { return QSize(150, 20); } + protected slots: + void findStyles(const QFont &font); + void onSizeChange(bool); void onChange(); }; diff --git a/toonz/sources/stdfx/CMakeLists.txt b/toonz/sources/stdfx/CMakeLists.txt index b691a4e..f0a0017 100644 --- a/toonz/sources/stdfx/CMakeLists.txt +++ b/toonz/sources/stdfx/CMakeLists.txt @@ -72,9 +72,10 @@ set(HEADERS iwa_fresnel.h iwa_pnperspectivefx.h iwa_soapbubblefx.h - iwa_bokehfx.h - iwa_timecodefx.h - iwa_bokehreffx.h + iwa_bokehfx.h + iwa_timecodefx.h + iwa_bokehreffx.h + iwa_textfx.h ) set(SOURCES @@ -250,12 +251,13 @@ set(SOURCES iwa_noise1234.cpp iwa_pnperspectivefx.cpp iwa_soapbubblefx.cpp - ${SDKROOT}/kiss_fft130/kiss_fft.c - ${SDKROOT}/kiss_fft130/tools/kiss_fftnd.c - iwa_bokehfx.cpp - iwa_timecodefx.cpp - iwa_bokehreffx.cpp - iwa_barreldistortfx.cpp + ${SDKROOT}/kiss_fft130/kiss_fft.c + ${SDKROOT}/kiss_fft130/tools/kiss_fftnd.c + iwa_bokehfx.cpp + iwa_timecodefx.cpp + iwa_bokehreffx.cpp + iwa_barreldistortfx.cpp + iwa_textfx.cpp ) set(OBJCSOURCES diff --git a/toonz/sources/stdfx/iwa_textfx.cpp b/toonz/sources/stdfx/iwa_textfx.cpp new file mode 100644 index 0000000..41892f0 --- /dev/null +++ b/toonz/sources/stdfx/iwa_textfx.cpp @@ -0,0 +1,250 @@ +#include "iwa_textfx.h" + +#include "tparamuiconcept.h" +#include <QFontMetrics> +#include <QImage> +#include <QPainter> + +//------------------------------------------------------------------ + +Iwa_TextFx::Iwa_TextFx() + : m_text(L"Lorem ipsum") + , m_hAlign(new TIntEnumParam(Qt::AlignLeft, "Left")) + , m_center(TPointD(0.0, 0.0)) + , m_width(200.0) + , m_height(60.0) + , m_font(new TFontParam()) + , m_textColor(TPixel32::Black) + , m_boxColor(TPixel32::Transparent) + , m_showBorder(false) { + m_targetType->setValue(INPUT_TEXT); + + m_hAlign->addItem(Qt::AlignRight, "Right"); + m_hAlign->addItem(Qt::AlignHCenter, "Center"); + m_hAlign->addItem(Qt::AlignJustify, "Justify"); + + m_text->setMultiLineEnabled(true); + + m_center->getX()->setMeasureName("fxLength"); + m_center->getY()->setMeasureName("fxLength"); + m_width->setMeasureName("fxLength"); + m_height->setMeasureName("fxLength"); + + m_width->setValueRange(1.0, (std::numeric_limits<double>::max)()); + m_height->setValueRange(1.0, (std::numeric_limits<double>::max)()); + + // set the initial font size to 30 + QFont font; + font.fromString(QString::fromStdWString(m_font->getValue())); + font.setPixelSize(30); + m_font->setValue(font.toString().toStdWString(), true); + + bindParam(this, "targetType", m_targetType); + bindParam(this, "columnIndex", m_columnIndex); + bindParam(this, "text", m_text); + bindParam(this, "hAlign", m_hAlign); + bindParam(this, "center", m_center); + bindParam(this, "width", m_width); + bindParam(this, "height", m_height); + bindParam(this, "font", m_font); + bindParam(this, "textColor", m_textColor); + bindParam(this, "boxColor", m_boxColor); + bindParam(this, "showBorder", m_showBorder); +} + +//------------------------------------------------------------------ + +bool Iwa_TextFx::doGetBBox(double frame, TRectD &bBox, + const TRenderSettings &ri) { + bBox = TConsts::infiniteRectD; + return true; +} + +//------------------------------------------------------------------ + +void Iwa_TextFx::doCompute(TTile &tile, double frame, + const TRenderSettings &ri) { + QString text; + if (m_targetType->getValue() == INPUT_TEXT) + text = QString::fromStdWString(m_text->getValue()); + else + text = m_noteLevelStr; + if (text.isEmpty()) return; + + QFont font; + font.fromString(QString::fromStdWString(m_font->getValue())); + + double fac = sqrt(fabs(ri.m_affine.det())); + int size = (int)(fac * fabs(font.pixelSize())); + + TPoint center = convert( + fac * m_center->getValue(frame) - + (tile.m_pos + tile.getRaster()->getCenterD()) + + TPointD(ri.m_cameraBox.getLx() / 2.0, ri.m_cameraBox.getLy() / 2.0)); + + QRect textBoxRect(0, 0, fac * m_width->getValue(frame), + fac * m_height->getValue(frame)); + textBoxRect.moveCenter(QPoint(center.x, center.y)); + + Qt::AlignmentFlag hAlignFlag = (Qt::AlignmentFlag)m_hAlign->getValue(); + + // For simplification, text will always be "wrapped". + // If user would like to make the line with no break, they can just expand the + // boundary or set the smaller font size. + int flag = hAlignFlag | Qt::AlignVCenter | Qt::TextWordWrap; + + QFont tmpFont(font); + tmpFont.setPixelSize(100); + QFontMetricsF tmpFm(tmpFont); + QRectF bbox = tmpFm.boundingRect(textBoxRect, flag, text); + + float ratio = std::min(textBoxRect.width() / bbox.width(), + textBoxRect.height() / bbox.height()); + + // compute the font size which will just fit the item region + int fontSize = (int)(100.0f * ratio); + tmpFont.setPixelSize(fontSize); + tmpFm = QFontMetricsF(tmpFont); + bbox = tmpFm.boundingRect(textBoxRect, flag, text); + bool isInRect; + if (textBoxRect.width() >= bbox.width() && + textBoxRect.height() >= bbox.height()) + isInRect = true; + else + isInRect = false; + while (1) { + fontSize += (isInRect) ? 1 : -1; + if (fontSize <= 0) // cannot draw + return; + if (isInRect && fontSize >= size) break; + tmpFont.setPixelSize(fontSize); + tmpFm = QFontMetricsF(tmpFont); + bbox = tmpFm.boundingRect(textBoxRect, flag, text); + + bool newIsInRect = (textBoxRect.width() >= bbox.width() && + textBoxRect.height() >= bbox.height()); + if (isInRect != newIsInRect) { + if (isInRect) fontSize--; + break; + } + } + + if (size < fontSize) { + fontSize = size; + } + + font.setPixelSize(fontSize); + tmpFm = QFontMetricsF(font); + bbox = tmpFm.boundingRect(textBoxRect, flag, text); + + double lineWidth = 0.1 * (double)fontSize; + + // Usually the text bounding box has less horizontal margin than vertical. + // So here I added more margin to width. + QImage img(bbox.width() + (int)(lineWidth * 4), + bbox.height() + (int)(lineWidth * 2), + QImage::Format_ARGB32_Premultiplied); + img.fill(Qt::transparent); + + bbox.moveCenter(img.rect().center()); + + QPainter painter(&img); + TPixel32 boxColor = m_boxColor->getValue(frame); + TPixel32 color = m_textColor->getValue(frame); + + if (boxColor.m > 0) + painter.fillRect(img.rect(), QColor((int)boxColor.r, (int)boxColor.g, + (int)boxColor.b, (int)boxColor.m)); + + QPen pen(QColor((int)color.r, (int)color.g, (int)color.b, (int)color.m)); + painter.setPen(pen); + painter.setFont(font); + painter.drawText(bbox, flag, text); + + if (m_showBorder->getValue()) { + pen.setWidthF(lineWidth); + pen.setJoinStyle(Qt::MiterJoin); + painter.setPen(pen); + painter.drawRect(img.rect().adjusted(lineWidth / 2, lineWidth / 2, + -lineWidth / 2, -lineWidth / 2)); + } + + TPoint imgRootPos = center - TPoint(img.width() / 2, img.height() / 2); + + tile.getRaster()->clear(); + TRaster32P ras32 = (TRaster32P)tile.getRaster(); + TRaster64P ras64 = (TRaster64P)tile.getRaster(); + if (ras32) + putTextImage<TRaster32P, TPixel32>(ras32, imgRootPos, img); + else if (ras64) + putTextImage<TRaster64P, TPixel64>(ras64, imgRootPos, img); + else + throw TException("Iwa_TextFx: unsupported Pixel Type"); +} + +//------------------------------------------------------------------ + +void Iwa_TextFx::getParamUIs(TParamUIConcept *&concepts, int &length) { + concepts = new TParamUIConcept[length = 2]; + + concepts[0].m_type = TParamUIConcept::POINT; + concepts[0].m_label = "Center"; + concepts[0].m_params.push_back(m_center); + + concepts[1].m_type = TParamUIConcept::RECT; + concepts[1].m_params.push_back(m_width); + concepts[1].m_params.push_back(m_height); + concepts[1].m_params.push_back(m_center); +} + +//------------------------------------------------------------------ + +std::string Iwa_TextFx::getAlias(double frame, + const TRenderSettings &info) const { + std::string alias = getFxType(); + alias += "["; + + std::string paramalias(""); + for (int i = 0; i < getParams()->getParamCount(); ++i) { + TParam *param = getParams()->getParam(i); + paramalias += param->getName() + "=" + param->getValueAlias(frame, 3); + } + + if (m_targetType->getValue() == INPUT_TEXT) + return alias + "," + std::to_string(getIdentifier()) + paramalias + "]"; + else + return alias + std::to_string(frame) + "," + + std::to_string(getIdentifier()) + paramalias + "]"; +} + +//------------------------------------------------------------------ + +template <typename RASTER, typename PIXEL> +void Iwa_TextFx::putTextImage(const RASTER srcRas, TPoint &pos, QImage &img) { + for (int j = 0; j < img.height(); j++) { + int rasY = pos.y + j; + if (rasY < 0) continue; + if (srcRas->getLy() <= rasY) break; + + PIXEL *pix = srcRas->pixels(rasY); + QRgb *img_p = (QRgb *)img.scanLine(img.height() - j - 1); + for (int i = 0; i < img.width(); i++, img_p++) { + int rasX = pos.x + i; + if (rasX < 0) continue; + if (srcRas->getLx() <= rasX) break; + + pix[rasX].r = (typename PIXEL::Channel)( + qRed(*img_p) * (int)PIXEL::maxChannelValue / (int)UCHAR_MAX); + pix[rasX].g = (typename PIXEL::Channel)( + qGreen(*img_p) * (int)PIXEL::maxChannelValue / (int)UCHAR_MAX); + pix[rasX].b = (typename PIXEL::Channel)( + qBlue(*img_p) * (int)PIXEL::maxChannelValue / (int)UCHAR_MAX); + pix[rasX].m = (typename PIXEL::Channel)( + qAlpha(*img_p) * (int)PIXEL::maxChannelValue / (int)UCHAR_MAX); + } + } +} + +//============================================================================== + +FX_PLUGIN_IDENTIFIER(Iwa_TextFx, "iwa_TextFx"); \ No newline at end of file diff --git a/toonz/sources/stdfx/iwa_textfx.h b/toonz/sources/stdfx/iwa_textfx.h new file mode 100644 index 0000000..24ea20e --- /dev/null +++ b/toonz/sources/stdfx/iwa_textfx.h @@ -0,0 +1,47 @@ +#pragma once + +#ifndef IWA_TEXTFX_H +#define IWA_TEXTFX_H + +#include "tparamset.h" +#include "textawarebasefx.h" + +//****************************************************************** +// Iwa_Text Fx class +//****************************************************************** + +class Iwa_TextFx final : public TextAwareBaseFx { + FX_PLUGIN_DECLARATION(Iwa_TextFx) +protected: + TStringParamP m_text; + + TIntEnumParamP m_hAlign; + + TPointParamP m_center; + TDoubleParamP m_width; + TDoubleParamP m_height; + + TFontParamP m_font; + TPixelParamP m_textColor; + TPixelParamP m_boxColor; + TBoolParamP m_showBorder; + + template <typename RASTER, typename PIXEL> + void putTextImage(const RASTER srcRas, TPoint &pos, QImage &img); + +public: + Iwa_TextFx(); + + bool canHandle(const TRenderSettings &info, double frame) override { + return true; + } + + bool doGetBBox(double frame, TRectD &bBox, + const TRenderSettings &ri) override; + void doCompute(TTile &tile, double frame, const TRenderSettings &ri) override; + void getParamUIs(TParamUIConcept *&concepts, int &length) override; + + std::string getAlias(double frame, + const TRenderSettings &info) const override; +}; +#endif \ No newline at end of file diff --git a/toonz/sources/stdfx/textawarebasefx.h b/toonz/sources/stdfx/textawarebasefx.h new file mode 100644 index 0000000..a25b0da --- /dev/null +++ b/toonz/sources/stdfx/textawarebasefx.h @@ -0,0 +1,32 @@ +#pragma once +#ifndef TEXTAWAREBASEFX_H +#define TEXTAWAREBASEFX_H + +#include "tfxparam.h" +#include "stdfx.h" + +class TextAwareBaseFx : public TStandardZeraryFx { +protected: + QString m_noteLevelStr; + + TIntEnumParamP m_targetType; + TIntParamP m_columnIndex; + +public: + enum SourceType { NEARBY_COLUMN, SPECIFIED_COLUMN, INPUT_TEXT }; + + TextAwareBaseFx() + : m_targetType(new TIntEnumParam(NEARBY_COLUMN, "Nearby Note Column")) + , m_columnIndex(0) { + m_targetType->addItem(SPECIFIED_COLUMN, "Specified Note Column"); + m_targetType->addItem(INPUT_TEXT, "Input Text"); + } + + bool isZerary() const override { return true; } + + void setNoteLevelStr(QString str) { m_noteLevelStr = str; } + SourceType getSourceType() { return (SourceType)(m_targetType->getValue()); } + int getNoteColumnIndex() { return m_columnIndex->getValue() - 1; } +}; + +#endif diff --git a/toonz/sources/toonzlib/scenefx.cpp b/toonz/sources/toonzlib/scenefx.cpp index e903d80..64672fb 100644 --- a/toonz/sources/toonzlib/scenefx.cpp +++ b/toonz/sources/toonzlib/scenefx.cpp @@ -28,8 +28,11 @@ #include "toonz/stage.h" #include "toonz/preferences.h" #include "ttzpimagefx.h" +#include "toonz/txshsoundtextcolumn.h" +#include "toonz/txshsoundtextlevel.h" #include "../stdfx/motionawarebasefx.h" +#include "../stdfx/textawarebasefx.h" #include "toonz/scenefx.h" @@ -497,6 +500,26 @@ static QList<TPointD> getColumnMotionPoints(TXsheet *xsh, double row, int col, return points; } +namespace { + +QString getNoteText(TXsheet *xsh, double row, int col, int noteColumnIndex, + bool neighbor) { + int colIndex; + if (neighbor) + colIndex = col - 1; + else + colIndex = noteColumnIndex; + + TXshColumn *column = xsh->getColumn(colIndex); + if (!column || !column->getSoundTextColumn()) return QString(); + + TXshCell cell = xsh->getCell(row, colIndex); + if (cell.isEmpty() || !cell.getSoundTextLevel()) return QString(); + + return cell.getSoundTextLevel()->getFrameText(cell.m_frameId.getNumber() - 1); +} +}; + //*************************************************************************************************** // FxBuilder definition //*************************************************************************************************** @@ -939,6 +962,18 @@ PlacedFx FxBuilder::makePF(TZeraryColumnFx *zcfx) { } } + if (pf.m_fx->getFxType() == "STD_iwa_TextFx") { + TextAwareBaseFx *textFx = + dynamic_cast<TextAwareBaseFx *>(pf.m_fx.getPointer()); + if (textFx && textFx->getSourceType() != TextAwareBaseFx::INPUT_TEXT) { + int noteColumnIndex = textFx->getNoteColumnIndex(); + bool getNeighbor = + (textFx->getSourceType() == TextAwareBaseFx::NEARBY_COLUMN); + textFx->setNoteLevelStr(getNoteText(m_xsh, m_frame, pf.m_columnIndex, + noteColumnIndex, getNeighbor)); + } + } + // Add the column placement NaAffineFx if (getColumnPlacement(pf, m_xsh, m_frame, pf.m_columnIndex, m_isPreview)) return pf; diff --git a/toonz/sources/toonzqt/paramfield.cpp b/toonz/sources/toonzqt/paramfield.cpp index 5c899ec..3c1f114 100644 --- a/toonz/sources/toonzqt/paramfield.cpp +++ b/toonz/sources/toonzqt/paramfield.cpp @@ -20,6 +20,8 @@ #include <QLabel> #include <QPainter> #include <QComboBox> +#include <QFontComboBox> +#include <QKeyEvent> using namespace DVGui; @@ -448,6 +450,38 @@ public: }; //----------------------------------------------------------------------------- +/*! FontParamFieldUndo +*/ +class FontParamFieldUndo final : public FxSettingsUndo { + TFontParamP m_param; + std::wstring m_oldValue, m_newValue; + +public: + FontParamFieldUndo(const TFontParamP param, QString name, TFxHandle *fxHandle) + : FxSettingsUndo(name, fxHandle), m_param(param) { + m_oldValue = param->getValue(); + m_newValue = m_oldValue; + } + + void onAdd() override { m_newValue = m_param->getValue(); } + + void undo() const override { + m_param->setValue(m_oldValue); + if (m_fxHandle) m_fxHandle->notifyFxChanged(); + } + + void redo() const override { + m_param->setValue(m_newValue); + if (m_fxHandle) m_fxHandle->notifyFxChanged(); + } + + QString getHistoryString() override { + QString str = QObject::tr("Modify Fx Param : %1").arg(m_name); + return str; + } +}; + +//----------------------------------------------------------------------------- /*! ToneCurveParamField Undo */ class ToneCurveParamFieldUndo final : public AnimatableFxSettingsUndo { @@ -1395,17 +1429,42 @@ void IntParamField::update(int frame) { // StringParamField //----------------------------------------------------------------------------- +namespace component { + +void MyTextEdit::keyPressEvent(QKeyEvent *event) { + QTextEdit::keyPressEvent(event); + if (event->key() == Qt::Key_Return) emit edited(); +} + +void MyTextEdit::focusOutEvent(QFocusEvent *event) { + QTextEdit::focusOutEvent(event); + emit edited(); +} +}; + StringParamField::StringParamField(QWidget *parent, QString name, const TStringParamP ¶m) : ParamField(parent, name, param) { QString str; m_paramName = str.fromStdString(param->getName()); - m_textFld = new LineEdit(name, this); - m_textFld->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); - bool ret = - connect(m_textFld, SIGNAL(editingFinished()), this, SLOT(onChange())); - m_layout->addWidget(m_textFld); + bool ret = true; + if (param->isMultiLineEnabled()) { + m_multiTextFld = new component::MyTextEdit(name, this); + m_multiTextFld->setFixedHeight(80); + m_multiTextFld->setAcceptRichText(false); + m_multiTextFld->setStyleSheet( + "background:white;\ncolor:black;\nborder:1 solid black;"); + ret = ret && + connect(m_multiTextFld, SIGNAL(edited()), this, SLOT(onChange())); + m_layout->addWidget(m_multiTextFld); + } else { + m_textFld = new LineEdit(name, this); + m_textFld->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); + ret = ret && + connect(m_textFld, SIGNAL(editingFinished()), this, SLOT(onChange())); + m_layout->addWidget(m_textFld); + } setLayout(m_layout); assert(ret); } @@ -1413,12 +1472,17 @@ StringParamField::StringParamField(QWidget *parent, QString name, //----------------------------------------------------------------------------- void StringParamField::onChange() { - std::wstring value = m_textFld->text().toStdWString(); - TUndo *undo = 0; + std::wstring value; + if (m_multiTextFld) + value = m_multiTextFld->toPlainText().toStdWString(); + else + value = m_textFld->text().toStdWString(); + TUndo *undo = 0; + + if (!m_actualParam || m_actualParam->getValue() == value) return; - TStringParamP stringParam = m_actualParam; - if (stringParam && stringParam->getValue() != value) - undo = new StringParamFieldUndo(stringParam, m_interfaceName, + if (m_actualParam->getValue() != value) + undo = new StringParamFieldUndo(m_actualParam, m_interfaceName, ParamField::m_fxHandleStat); m_actualParam->setValue(value); @@ -1447,13 +1511,147 @@ void StringParamField::update(int frame) { if (!m_actualParam || !m_currentParam) return; QString str; QString strValue = str.fromStdWString(m_actualParam->getValue()); - if (m_textFld->text() == strValue) return; - m_textFld->setText(strValue); - // Faccio in modo che il cursore sia sulla prima cifra, cosi' se la stringa - // da visualizzare e' piu' lunga del campo le cifre che vengono troncate sono - // le ultime e non le prime (dovrebbero essere quelle dopo la virgola). - m_textFld->setCursorPosition(0); + if (m_textFld) { + if (m_textFld->text() == strValue) return; + m_textFld->setText(strValue); + + // Faccio in modo che il cursore sia sulla prima cifra, cosi' se la stringa + // da visualizzare e' piu' lunga del campo le cifre che vengono troncate + // sono + // le ultime e non le prime (dovrebbero essere quelle dopo la virgola). + m_textFld->setCursorPosition(0); + } else { + if (m_multiTextFld->toPlainText() == strValue) return; + m_multiTextFld->setPlainText(strValue); + } +} + +//============================================================================= +// FontParamField +//----------------------------------------------------------------------------- + +FontParamField::FontParamField(QWidget *parent, QString name, + const TFontParamP ¶m) + : ParamField(parent, name, param) { + m_paramName = QString::fromStdString(param->getName()); + + m_fontCombo = new QFontComboBox(this); + m_styleCombo = new QComboBox(this); + m_sizeField = new IntField(this, false); + + m_sizeField->setRange(1, 500); + m_sizeField->enableSlider(false); + + m_layout->addWidget(m_fontCombo); + m_layout->addSpacing(5); + m_layout->addWidget(new QLabel(tr("Style:"), this), 0, + Qt::AlignRight | Qt::AlignVCenter); + m_layout->addWidget(m_styleCombo); + m_layout->addSpacing(5); + m_layout->addWidget(new QLabel(tr("Size:"), this), 0, + Qt::AlignRight | Qt::AlignVCenter); + m_layout->addWidget(m_sizeField); + + m_layout->addStretch(); + + setLayout(m_layout); + + bool ret = true; + ret = ret && connect(m_fontCombo, &QFontComboBox::currentFontChanged, this, + &FontParamField::findStyles); + ret = ret && connect(m_fontCombo, &QFontComboBox::currentFontChanged, this, + &FontParamField::onChange); + ret = ret && connect(m_styleCombo, SIGNAL(activated(const QString &)), this, + SLOT(onChange())); + ret = ret && connect(m_sizeField, SIGNAL(valueChanged(bool)), this, + SLOT(onSizeChange(bool))); + assert(ret); + + findStyles(m_fontCombo->currentFont()); +} + +//----------------------------------------------------------------------------- + +void FontParamField::findStyles(const QFont &font) { + QFontDatabase fontDatabase; + QString currentItem = m_styleCombo->currentText(); + m_styleCombo->clear(); + + QString style; + foreach (style, fontDatabase.styles(font.family())) + m_styleCombo->addItem(style); + + int styleIndex = m_styleCombo->findText(currentItem); + + if (styleIndex == -1) + m_styleCombo->setCurrentIndex(0); + else + m_styleCombo->setCurrentIndex(styleIndex); +} + +//----------------------------------------------------------------------------- + +void FontParamField::onSizeChange(bool isDragging) { + if (isDragging) return; + onChange(); +} + +//----------------------------------------------------------------------------- + +void FontParamField::onChange() { + QString family = m_fontCombo->currentFont().family(); + QString style = m_styleCombo->currentText(); + + int size = m_sizeField->getValue(); + int min, max; + m_sizeField->getRange(min, max); + if (size < min) size = min; + + QFontDatabase fontDatabase; + QFont font = fontDatabase.font(family, style, 10); + font.setPixelSize(size); + + TUndo *undo = 0; + + TFontParamP fontParam = m_actualParam; + QFont currentFont; + currentFont.fromString(QString::fromStdWString(fontParam->getValue())); + if (fontParam && currentFont != font) + undo = new FontParamFieldUndo(fontParam, m_interfaceName, + ParamField::m_fxHandleStat); + + m_actualParam->setValue(font.toString().toStdWString()); + emit currentParamChanged(); + m_currentParam->setValue(font.toString().toStdWString()); + emit actualParamChanged(); + + if (undo) TUndoManager::manager()->add(undo); +} + +//----------------------------------------------------------------------------- + +void FontParamField::setParam(const TParamP ¤t, const TParamP &actual, + int frame) { + m_currentParam = current; + m_actualParam = actual; + assert(m_currentParam); + assert(m_actualParam); + update(frame); +} + +//----------------------------------------------------------------------------- + +void FontParamField::update(int frame) { + if (!m_actualParam || !m_currentParam) return; + QFont font; + font.fromString(QString::fromStdWString(m_actualParam->getValue())); + + if (m_fontCombo->currentText() != font.family()) + m_fontCombo->setCurrentFont(font); + + m_styleCombo->setCurrentText(font.styleName()); + m_sizeField->setValue(font.pixelSize()); } //============================================================================= @@ -1660,6 +1858,8 @@ ParamField *ParamField::create(QWidget *parent, QString name, return new StringParamField(parent, name, stringParam); else if (TToneCurveParamP toneCurveParam = param) return new ToneCurveParamField(parent, name, toneCurveParam); + else if (TFontParamP fontParam = param) + return new FontParamField(parent, name, fontParam); else return 0; }