From 428fe6b6b455969fa862f27f80f01751e7fe292d Mon Sep 17 00:00:00 2001 From: shun-iwasawa Date: Dec 17 2021 02:50:06 +0000 Subject: Note column enhancement --- diff --git a/toonz/sources/toonz/exportxsheetpdf.cpp b/toonz/sources/toonz/exportxsheetpdf.cpp index 04d312f..c084b1b 100644 --- a/toonz/sources/toonz/exportxsheetpdf.cpp +++ b/toonz/sources/toonz/exportxsheetpdf.cpp @@ -23,6 +23,8 @@ #include "toonz/tstageobject.h" #include "toonz/preferences.h" #include "toonz/toonzfolders.h" +#include "toonz/txshsoundtextcolumn.h" +#include "toonz/txshsoundtextlevel.h" // TnzCore includes #include "tsystem.h" @@ -74,6 +76,8 @@ TEnv::IntVar XShPdfExportPrintSoundtrack("XShPdfExportPrintSoundtrack", 0); TEnv::IntVar XShPdfExportSerialFrameNumber("XShPdfExportSerialFrameNumber", 0); // print level name on the bottom TEnv::IntVar XShPdfExportLevelNameOnBottom("XShPdfExportLevelNameOnBottom", 0); +// print dialogue +TEnv::IntVar XShPdfExportPrintDialogue("XShPdfExportPrintDialogue", 0); // print scene name TEnv::IntVar XShPdfExportPrintSceneName("XShPdfExportPrintSceneName", 0); // template font @@ -738,7 +742,7 @@ void XSheetPDFTemplate::drawDialogBlock(QPainter& painter, const int framePage, painter.restore(); // register sound cells - if (m_info.drawSound) + if (m_info.drawSound || m_noteColumn) registerSoundRects(painter, param(DialogColWidth), bodyId); } painter.restore(); @@ -1131,6 +1135,153 @@ void XSheetPDFTemplate::drawSound(QPainter& painter, int framePage) { } } +void XSheetPDFTemplate::drawDialogue(QPainter& painter, int framePage) { + if (!m_noteColumn || m_soundCellRects.isEmpty()) return; + + QFont font = painter.font(); + font.setPixelSize(m_soundCellRects[0].height()); + painter.setFont(font); + + QFont smallFont(font); + smallFont.setPixelSize(font.pixelSize() * 2 / 3); + + QFont largeFont(font); + largeFont.setPixelSize(font.pixelSize() * 13 / 10); + int heightThres = QFontMetrics(largeFont).height(); + + int r0, r1; + m_noteColumn->getRange(r0, r1); + + // obtain frame range to be printed in the current page + int printFrameR0 = framePage * param(FrameLength); + int printFrameR1 = printFrameR0 + param(FrameLength) - 1; + + // compute for each body + int framesPerBody = 72; + int bodyFrameR0 = printFrameR0; + int bodyFrameR1 = bodyFrameR0 + framesPerBody - 1; + while (bodyFrameR1 <= printFrameR1) { + // move to next body if the current body is out of range + if (r1 < bodyFrameR0 || bodyFrameR1 < r0) { + // to next body + bodyFrameR0 = bodyFrameR1 + 1; + bodyFrameR1 = bodyFrameR0 + framesPerBody - 1; + continue; + } + + // frame range to be printed + int drawStart = std::max(r0, bodyFrameR0); + int drawEnd = std::min(r1, std::min(bodyFrameR1, printFrameR1)); + + int rStart = drawStart; + int rEnd = drawEnd; + // obtain top row of the fist note block + if (!m_noteColumn->getCell(drawStart).isEmpty()) { + while (rStart > 0 && m_noteColumn->getCell(rStart - 1) == + m_noteColumn->getCell(drawStart)) { + rStart--; + } + } + // obtain bottom row of the last note block + if (!m_noteColumn->getCell(drawEnd).isEmpty()) { + while (m_noteColumn->getCell(rEnd + 1) == + m_noteColumn->getCell(drawEnd)) { + rEnd++; + } + } + + for (int row = rStart; row <= drawEnd; row++) { + TXshCell cell = m_noteColumn->getCell(row); + if (cell.isEmpty()) continue; + + // check how long the same content continues + int rowTo = row; + while (m_noteColumn->getCell(rowTo + 1) == cell) { + rowTo++; + } + int blockLength = rowTo - row + 1; + + QString text = cell.getSoundTextLevel()->getFrameText( + cell.m_frameId.getNumber() - 1); + int textCount = text.count(); + // separate text if it overflows the body + if (row < drawStart) { + int partialBlockLength = rowTo - drawStart + 1; + int partialTextCount = (int)std::round( + (double)(textCount * partialBlockLength) / (double)blockLength); + text = text.mid(textCount - partialTextCount); + textCount = partialTextCount; + row = drawStart; + blockLength = partialBlockLength; + } + // draw start mark + else { + int topRectId = row - printFrameR0; + QRect rect = m_soundCellRects.at(topRectId); + painter.drawLine(rect.topLeft(), rect.topRight()); + } + + if (rowTo > drawEnd) { + int partialBlockLength = drawEnd - row + 1; + int partialTextCount = (int)std::round( + (double)(textCount * partialBlockLength) / (double)blockLength); + text = text.mid(0, partialTextCount); + textCount = partialTextCount; + rowTo = drawEnd; + } + // draw end mark + else if (m_noteColumn->getCell(rowTo + 1).isEmpty()) { + int bottomRectId = rowTo - printFrameR0; + QRect rect = m_soundCellRects.at(bottomRectId); + drawEndMark(painter, rect); + } + if (text.isEmpty()) { + row = rowTo; + continue; + } + + int normalLettersPerChunk = textCount * m_soundCellRects[0].width() / + QFontMetrics(font).boundingRect(text).width(); + int maxLettersPerChunk = + textCount * m_soundCellRects[0].width() / + QFontMetrics(smallFont).boundingRect(text).width(); + + int lettersPerChunk = + (int)std::ceil((double)textCount / ((double)blockLength)); + lettersPerChunk = std::min(lettersPerChunk, maxLettersPerChunk); + int chunkCount = + (int)std::ceil((double)textCount / (double)(lettersPerChunk)); + chunkCount = std::min(chunkCount, (int)((double)(blockLength)*1.5)); + + int topRectId = row - printFrameR0; + int bottomRectId = rowTo - printFrameR0; + // unite the cell rects and divide by the amount of chunks + QRect unitedRect = m_soundCellRects.at(topRectId).united( + m_soundCellRects.at(bottomRectId)); + // check if the large font is available + if (lettersPerChunk == 1 && unitedRect.height() / textCount > heightThres) + painter.setFont(largeFont); + else if (lettersPerChunk > normalLettersPerChunk) + painter.setFont(smallFont); + else + painter.setFont(font); + // draw text + for (int c = 0; c < chunkCount; c++) { + int y0 = unitedRect.top() + unitedRect.height() * c / chunkCount; + int y1 = unitedRect.top() + unitedRect.height() * (c + 1) / chunkCount; + QRect tmpRect(unitedRect.left(), y0, unitedRect.width(), y1 - y0 + 1); + painter.drawText(tmpRect, Qt::AlignCenter, + text.mid(c * lettersPerChunk, lettersPerChunk)); + } + + row = rowTo; + } + // to next body + bodyFrameR0 = bodyFrameR1 + 1; + bodyFrameR1 = bodyFrameR0 + framesPerBody - 1; + } +} + XSheetPDFTemplate::XSheetPDFTemplate( const QList>& columns, const int duration) : m_columns(columns), m_duration(duration), m_useExtraColumns(false) {} @@ -1208,6 +1359,10 @@ void XSheetPDFTemplate::drawXsheetContents(QPainter& painter, int framePage, painter.setPen( QPen(Qt::black, mm2px(0.5), Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); painter.setFont(m_info.contentsFontFamily); + + // draw dialogue + drawDialogue(painter, framePage); + int colsInPage = columnsInPage(); int startColId = colsInPage * parallelPage; int startFrame = param(FrameLength) * framePage; @@ -1718,8 +1873,10 @@ ExportXsheetPdfPopup::ExportXsheetPdfPopup() new QCheckBox(tr("Put Serial Frame Numbers Over Pages"), this); m_levelNameOnBottomCB = new QCheckBox(tr("Print Level Names On The Bottom"), this); - m_sceneNameEdit = new QLineEdit(this); - m_memoEdit = new QTextEdit(this); + m_drawDialogueCB = new QCheckBox(tr("Print Dialogue"), this); + m_dialogueColCombo = new QComboBox(); + m_sceneNameEdit = new QLineEdit(this); + m_memoEdit = new QTextEdit(this); m_logoTxtRB = new QRadioButton(tr("Text"), this); m_logoImgRB = new QRadioButton(tr("Image"), this); @@ -1761,6 +1918,7 @@ ExportXsheetPdfPopup::ExportXsheetPdfPopup() m_memoEdit->setStyleSheet( "background:white;\ncolor:black;\nborder:1 solid black;"); m_memoEdit->setFixedHeight(150); + m_dialogueColCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents); m_sceneNameEdit->setFixedWidth(100); m_continuousLineCombo->addItem(tr("Always"), Line_Always); @@ -1883,7 +2041,10 @@ ExportXsheetPdfPopup::ExportXsheetPdfPopup() checksLay->addWidget(m_addSceneNameCB, 1, 1); checksLay->addWidget(m_sceneNameEdit, 1, 2, Qt::AlignLeft | Qt::AlignVCenter); - checksLay->addWidget(m_levelNameOnBottomCB, 2, 0, 1, 3); + checksLay->addWidget(m_levelNameOnBottomCB, 2, 0); + checksLay->addWidget(m_drawDialogueCB, 2, 1); + checksLay->addWidget(m_dialogueColCombo, 2, 2, + Qt::AlignLeft | Qt::AlignVCenter); } checksLay->setColumnStretch(0, 2); checksLay->setColumnStretch(1, 1); @@ -1975,6 +2136,11 @@ ExportXsheetPdfPopup::ExportXsheetPdfPopup() SLOT(updatePreview())); connect(m_levelNameOnBottomCB, SIGNAL(clicked(bool)), this, SLOT(updatePreview())); + connect(m_drawDialogueCB, SIGNAL(clicked(bool)), this, SLOT(updatePreview())); + connect(m_drawDialogueCB, SIGNAL(clicked(bool)), m_dialogueColCombo, + SLOT(setEnabled(bool))); + connect(m_dialogueColCombo, SIGNAL(activated(int)), this, + SLOT(updatePreview())); connect(m_addSceneNameCB, SIGNAL(clicked(bool)), this, SLOT(updatePreview())); connect(m_addSceneNameCB, SIGNAL(clicked(bool)), m_sceneNameEdit, SLOT(setEnabled(bool))); @@ -2086,6 +2252,7 @@ void ExportXsheetPdfPopup::initialize() { m_columns.clear(); m_soundColumns.clear(); + m_noteColumns.clear(); for (int col = 0; col < xsheet->getColumnCount(); col++) { if (xsheet->isColumnEmpty(col)) continue; @@ -2095,6 +2262,13 @@ void ExportXsheetPdfPopup::initialize() { continue; } + TXshSoundTextColumn* noteColumn = + xsheet->getColumn(col)->getSoundTextColumn(); + if (noteColumn) { + m_noteColumns.insert(col, noteColumn); + continue; + } + TXshLevelColumn* column = xsheet->getColumn(col)->getLevelColumn(); if (!column) continue; // do not export if the "eye" (render) button is off @@ -2118,6 +2292,12 @@ void ExportXsheetPdfPopup::initialize() { m_sceneNameEdit->setText( (scene->isUntitled()) ? "" : QString::fromStdWString(scene->getSceneName())); + m_drawDialogueCB->setDisabled(m_noteColumns.isEmpty()); + m_dialogueColCombo->setEnabled(!m_noteColumns.isEmpty() && + m_drawDialogueCB->isChecked()); + m_dialogueColCombo->clear(); + for (auto colId : m_noteColumns.keys()) + m_dialogueColCombo->addItem(tr("Col%1").arg(colId + 1), colId); initTemplate(); @@ -2141,6 +2321,7 @@ void ExportXsheetPdfPopup::saveSettings() { XShPdfExportPrintSceneName = (m_addSceneNameCB->isChecked()) ? 1 : 0; XShPdfExportSerialFrameNumber = (m_serialFrameNumberCB->isChecked()) ? 1 : 0; XShPdfExportLevelNameOnBottom = (m_levelNameOnBottomCB->isChecked()) ? 1 : 0; + XShPdfExportPrintDialogue = (m_drawDialogueCB->isChecked()) ? 1 : 0; XShPdfExportTemplateFont = m_templateFontCB->currentFont().family().toStdString(); XShPdfExportOutputFont = @@ -2177,6 +2358,7 @@ void ExportXsheetPdfPopup::loadSettings() { m_addSceneNameCB->setChecked(XShPdfExportPrintSceneName != 0); m_serialFrameNumberCB->setChecked(XShPdfExportSerialFrameNumber != 0); m_levelNameOnBottomCB->setChecked(XShPdfExportLevelNameOnBottom != 0); + m_drawDialogueCB->setChecked(XShPdfExportPrintDialogue != 0); QString tmplFont = QString::fromStdString(XShPdfExportTemplateFont); if (!tmplFont.isEmpty()) m_templateFontCB->setCurrentFont(QFont(tmplFont)); @@ -2199,6 +2381,8 @@ void ExportXsheetPdfPopup::loadSettings() { m_logoTextEdit->setEnabled(m_logoTxtRB->isChecked()); m_logoImgPathField->setEnabled(m_logoImgRB->isChecked()); m_sceneNameEdit->setEnabled(m_addSceneNameCB->isChecked()); + m_dialogueColCombo->setEnabled(m_drawDialogueCB->isChecked() && + m_drawDialogueCB->isEnabled()); int id = XShPdfExportTick1Id; m_tick1IdCombo->setCurrentIndex(m_tick1IdCombo->findData(id)); @@ -2280,6 +2464,12 @@ void ExportXsheetPdfPopup::setInfo() { m_currentTmpl->setInfo(info); + if (m_drawDialogueCB->isChecked() && m_drawDialogueCB->isEnabled()) + m_currentTmpl->setNoteColumn(m_noteColumns.value( + m_dialogueColCombo->currentData().toInt(), nullptr)); + else + m_currentTmpl->setNoteColumn(nullptr); + if (!m_logoImgRB->isChecked()) return; // prepare logo image diff --git a/toonz/sources/toonz/exportxsheetpdf.h b/toonz/sources/toonz/exportxsheetpdf.h index b4c11f8..0fe5bf0 100644 --- a/toonz/sources/toonz/exportxsheetpdf.h +++ b/toonz/sources/toonz/exportxsheetpdf.h @@ -21,6 +21,7 @@ class QComboBox; class QCheckBox; class TXshLevelColumn; class TXshSoundColumn; +class TXshSoundTextColumn; namespace DVGui { class FileField; class ColorField; @@ -149,6 +150,7 @@ protected: // column and column name (if manually specified) QList> m_columns; QList m_soundColumns; + TXshSoundTextColumn* m_noteColumn; int m_duration; bool m_useExtraColumns; @@ -191,6 +193,7 @@ protected: bool isBottom = false); void drawLogo(QPainter& painter); void drawSound(QPainter& painter, int framePage); + void drawDialogue(QPainter& painter, int framePage); int param(const std::string& id, int defaultValue = 0) { if (!m_params.contains(id)) std::cout << id << std::endl; @@ -214,6 +217,9 @@ public: void setSoundColumns(const QList& soundColumns) { m_soundColumns = soundColumns; } + void setNoteColumn(TXshSoundTextColumn* noteColumn) { + m_noteColumn = noteColumn; + } void setInfo(const XSheetPDFFormatInfo& info); }; @@ -275,11 +281,13 @@ class ExportXsheetPdfPopup final : public DVGui::Dialog { XsheetPdfPreviewArea* m_previewArea; DVGui::FileField* m_pathFld; QLineEdit* m_fileNameFld; - QComboBox *m_templateCombo, *m_exportAreaCombo, *m_continuousLineCombo; + QComboBox *m_templateCombo, *m_exportAreaCombo, *m_continuousLineCombo, + *m_dialogueColCombo; DVGui::ColorField* m_lineColorFld; QCheckBox *m_addDateTimeCB, *m_addScenePathCB, *m_drawSoundCB, - *m_addSceneNameCB, *m_serialFrameNumberCB, *m_levelNameOnBottomCB; + *m_addSceneNameCB, *m_serialFrameNumberCB, *m_levelNameOnBottomCB, + *m_drawDialogueCB; QFontComboBox *m_templateFontCB, *m_contentsFontCB; QTextEdit* m_memoEdit; @@ -300,6 +308,7 @@ class ExportXsheetPdfPopup final : public DVGui::Dialog { // column and column name (if manually specified) QList> m_columns; QList m_soundColumns; + QMap m_noteColumns; int m_duration; XSheetPDFTemplate* m_currentTmpl; diff --git a/toonz/sources/toonz/xshcellviewer.cpp b/toonz/sources/toonz/xshcellviewer.cpp index 8c2578b..6b4de9d 100644 --- a/toonz/sources/toonz/xshcellviewer.cpp +++ b/toonz/sources/toonz/xshcellviewer.cpp @@ -724,67 +724,85 @@ void RenameCellField::showInRowCol(int row, int col, bool multiColumnSelected) { void RenameCellField::renameSoundTextColumn(TXshSoundTextColumn *sndTextCol, const QString &s) { - TXsheet *xsheet = m_viewer->getXsheet(); - QString oldText = "changeMe"; // text for undo - changed later - TXshCell cell = xsheet->getCell(m_row, m_col); - TXshCell oldCell = cell; - // the text index is always one less than the frame number - int textIndex = cell.getFrameId().getNumber() - 1; - if (!cell.m_level) { // cell not part of a level - oldText = ""; - int lastFrame = sndTextCol->getMaxFrame(); - TXshSoundTextLevel *sndTextLevel; - TXshCell lastCell; - TFrameId newId; - if (lastFrame < 0) { // no level on column - sndTextLevel = new TXshSoundTextLevel(); - sndTextLevel->setType(SND_TXT_XSHLEVEL); - newId = TFrameId(1); - cell = TXshCell(sndTextLevel, newId); - sndTextCol->setCell(m_row, cell); - textIndex = 0; - } else { - TXshCell lastCell = xsheet->getCell(lastFrame, m_col); - TXshSoundTextLevel *sndTextLevel = lastCell.m_level->getSoundTextLevel(); - int textSize = sndTextLevel->m_framesText.size(); - textIndex = textSize; - newId = TFrameId(textSize + 1); - cell = TXshCell(sndTextLevel, newId); - sndTextCol->setCell(m_row, cell); - } - } - - TXshCell prevCell = xsheet->getCell(m_row - 1, m_col); - TXshSoundTextLevel *textLevel = cell.m_level->getSoundTextLevel(); - if (oldText == "changeMe") - oldText = textLevel->getFrameText(cell.getFrameId().getNumber() - 1); - if (!prevCell.isEmpty()) { - // check if the previous cell had the same content as the entered text - // just extend the frame if so - if (textLevel->getFrameText(prevCell.getFrameId().getNumber() - 1) == s) { - sndTextCol->setCell(m_row, prevCell); - RenameTextCellUndo *undo = new RenameTextCellUndo( - m_row, m_col, oldCell, prevCell, oldText, s, textLevel); - TUndoManager::manager()->add(undo); - return; + if (sndTextCol->isLocked()) return; + TXsheet *xsheet = m_viewer->getXsheet(); + + int r0, c0, r1, c1; + TCellSelection *cellSelection = dynamic_cast( + TApp::instance()->getCurrentSelection()->getSelection()); + if (!cellSelection) { + r0 = m_row; + r1 = m_row; + } else + cellSelection->getSelectedCells(r0, c0, r1, c1); + + TUndoManager::manager()->beginBlock(); + for (int row = r0; row <= r1; row++) { + QString oldText = "changeMe"; // text for undo - changed later + TXshCell cell = xsheet->getCell(row, m_col); + TXshCell oldCell = cell; + // the text index is always one less than the frame number + int textIndex = cell.getFrameId().getNumber() - 1; + if (!cell.m_level) { // cell not part of a level + oldText = ""; + int lastFrame = sndTextCol->getMaxFrame(); + TXshSoundTextLevel *sndTextLevel; + TXshCell lastCell; + TFrameId newId; + if (lastFrame < 0) { // no level on column + sndTextLevel = new TXshSoundTextLevel(); + sndTextLevel->setType(SND_TXT_XSHLEVEL); + newId = TFrameId(1); + cell = TXshCell(sndTextLevel, newId); + sndTextCol->setCell(row, cell); + textIndex = 0; + } else { + TXshCell lastCell = xsheet->getCell(lastFrame, m_col); + TXshSoundTextLevel *sndTextLevel = + lastCell.m_level->getSoundTextLevel(); + int textSize = sndTextLevel->m_framesText.size(); + textIndex = textSize; + newId = TFrameId(textSize + 1); + cell = TXshCell(sndTextLevel, newId); + sndTextCol->setCell(row, cell); + } } - // check if the cell was part of an extended frame, but now has different - // text - else if (textLevel->getFrameText(textIndex) == - textLevel->getFrameText(prevCell.getFrameId().getNumber() - - 1) && - textLevel->getFrameText(textIndex) != s) { - int textSize = textLevel->m_framesText.size(); - textIndex = textSize; - TFrameId newId = TFrameId(textSize + 1); - cell = TXshCell(textLevel, newId); - sndTextCol->setCell(m_row, cell); - } - } - RenameTextCellUndo *undo = new RenameTextCellUndo(m_row, m_col, oldCell, cell, - oldText, s, textLevel); - TUndoManager::manager()->add(undo); - textLevel->setFrameText(textIndex, s); + + TXshCell prevCell = xsheet->getCell(row - 1, m_col); + TXshSoundTextLevel *textLevel = cell.m_level->getSoundTextLevel(); + if (oldText == "changeMe") + oldText = textLevel->getFrameText(cell.getFrameId().getNumber() - 1); + if (!prevCell.isEmpty()) { + QString prevCellText = + textLevel->getFrameText(prevCell.getFrameId().getNumber() - 1); + // check if the previous cell had the same content as the entered text + // just extend the frame if so + // Pressing enter with empty input field will also continue the previous + // cell text. + if (prevCellText == s || s.isEmpty()) { + sndTextCol->setCell(row, prevCell); + RenameTextCellUndo *undo = new RenameTextCellUndo( + row, m_col, oldCell, prevCell, oldText, prevCellText, textLevel); + TUndoManager::manager()->add(undo); + continue; + } + // check if the cell was part of an extended frame, but now has different + // text + else if (textLevel->getFrameText(textIndex) == prevCellText && + textLevel->getFrameText(textIndex) != s) { + int textSize = textLevel->m_framesText.size(); + textIndex = textSize; + TFrameId newId = TFrameId(textSize + 1); + cell = TXshCell(textLevel, newId); + sndTextCol->setCell(row, cell); + } + } + RenameTextCellUndo *undo = new RenameTextCellUndo(row, m_col, oldCell, cell, + oldText, s, textLevel); + TUndoManager::manager()->add(undo); + textLevel->setFrameText(textIndex, s); + } + TUndoManager::manager()->endBlock(); TApp::instance()->getCurrentXsheet()->notifyXsheetChanged(); } @@ -1258,36 +1276,40 @@ void CellArea::drawCells(QPainter &p, const QRect toBeUpdated) { isReference = false; } - // for each frame - for (row = r0; row <= r1; row++) { - if (col >= 0 && !isColumn) { - drawFrameSeparator(p, row, col, true); - if (TApp::instance()->getCurrentFrame()->isEditingScene() && - !m_viewer->orientation()->isVerticalTimeline() && - row == m_viewer->getCurrentRow() && - Preferences::instance()->isCurrentTimelineIndicatorEnabled()) { - QPoint xy = m_viewer->positionToXY(CellPosition(row, col)); - int x = xy.x(); - int y = xy.y(); - if (row == 0) { - if (m_viewer->orientation()->isVerticalTimeline()) - xy.setY(xy.y() + 1); - else - xy.setX(xy.x() + 1); + if (isSoundTextColumn) + drawSoundTextColumn(p, r0, r1, col); + else { + // for each frame + for (row = r0; row <= r1; row++) { + if (col >= 0 && !isColumn) { + drawFrameSeparator(p, row, col, true); + if (TApp::instance()->getCurrentFrame()->isEditingScene() && + !m_viewer->orientation()->isVerticalTimeline() && + row == m_viewer->getCurrentRow() && + Preferences::instance()->isCurrentTimelineIndicatorEnabled()) { + QPoint xy = m_viewer->positionToXY(CellPosition(row, col)); + int x = xy.x(); + int y = xy.y(); + if (row == 0) { + if (m_viewer->orientation()->isVerticalTimeline()) + xy.setY(xy.y() + 1); + else + xy.setX(xy.x() + 1); + } + drawCurrentTimeIndicator(p, xy); } - drawCurrentTimeIndicator(p, xy); + continue; } - continue; + // Cells appearance depending on the type of column + if (isSoundColumn) + drawSoundCell(p, row, col, isReference); + else if (isPaletteColumn) + drawPaletteCell(p, row, col, isReference); + // else if (isSoundTextColumn) // I left these lines just in case + // drawSoundTextCell(p, row, col); + else + drawLevelCell(p, row, col, isReference, showLevelName); } - // Cells appearance depending on the type of column - if (isSoundColumn) - drawSoundCell(p, row, col, isReference); - else if (isPaletteColumn) - drawPaletteCell(p, row, col, isReference); - else if (isSoundTextColumn) - drawSoundTextCell(p, row, col); - else - drawLevelCell(p, row, col, isReference, showLevelName); } // draw vertical line @@ -2305,6 +2327,268 @@ void CellArea::drawSoundTextCell(QPainter &p, int row, int col) { //----------------------------------------------------------------------------- +void CellArea::drawSoundTextColumn(QPainter &p, int r0, int r1, int col) { + const Orientation *o = m_viewer->orientation(); + TXsheet *xsh = m_viewer->getXsheet(); + + struct CellInfo { + int row; + QRect rect; + QPoint xy; + int markId; + QColor markColor; + QRect markRect; + QRect nameRect; + }; + + auto getCellInfo = [&](int r) { + CellInfo ret; + ret.row = r; + ret.xy = m_viewer->positionToXY(CellPosition(r, col)); + QPoint frameAdj = m_viewer->getFrameZoomAdjustment(); + QRect cellRect = o->rect(PredefinedRect::CELL).translated(ret.xy); + cellRect.adjust(0, 0, -frameAdj.x(), -frameAdj.y()); + ret.rect = cellRect.adjusted(1, 1, 0, 1); + + ret.markId = xsh->getColumn(col)->getCellColumn()->getCellMark(r); + if (ret.markId >= 0) { + TPixel32 col = TApp::instance() + ->getCurrentScene() + ->getScene() + ->getProperties() + ->getCellMark(ret.markId) + .color; + ret.markColor = QColor(col.r, col.g, col.b, 196); // semi transparent + ret.markRect = + o->rect(PredefinedRect::CELL_MARK_AREA) + .adjusted(0, -std::round(double(frameAdj.y()) * 0.1), + -frameAdj.y(), -std::round(double(frameAdj.y()) * 0.9)) + .translated(ret.xy); + if (ret.markRect.right() > ret.rect.right()) + ret.markRect.setRight(ret.rect.right()); + } + ret.nameRect = o->rect(PredefinedRect::CELL_NAME) + .translated(ret.xy) + .adjusted(0, 0, -frameAdj.x(), -frameAdj.y()); + + return ret; + }; + + QString fontName = Preferences::instance()->getInterfaceFont(); + if (fontName == "") { +#ifdef _WIN32 + fontName = "Arial"; +#else + fontName = "Helvetica"; +#endif + } + static QFont font(fontName, -1, QFont::Normal); + font.setPixelSize(XSHEET_FONT_PX_SIZE); + static QFont largeFont(font); + largeFont.setPixelSize(XSHEET_FONT_PX_SIZE * 13 / 10); + QFontMetrics fm(font); + int heightThres = QFontMetrics(largeFont).height(); + + int rStart = r0; + int rEnd = r1; + // obtain top row of the fist note block + if (!xsh->getCell(r0, col).isEmpty()) { + while (rStart > 0 && + xsh->getCell(rStart - 1, col) == xsh->getCell(r0, col)) { + rStart--; + } + } + // obtain bottom row of the last note block + if (!xsh->getCell(r1, col).isEmpty()) { + while (xsh->getCell(rEnd + 1, col) == xsh->getCell(r1, col)) { + rEnd++; + } + } + + QColor cellColor = m_viewer->getSoundTextColumnColor(); + QColor selectedCellColor = m_viewer->getSelectedSoundTextColumnColor(); + QColor sideColor = m_viewer->getSoundTextColumnBorderColor(); + + TCellSelection *cellSelection = m_viewer->getCellSelection(); + TColumnSelection *columnSelection = m_viewer->getColumnSelection(); + bool isColSelected = columnSelection->isColumnSelected(col); + + // for each row + for (int row = rStart; row <= rEnd; row++) { + TXshCell cell = xsh->getCell(row, col); + + // if the cell is empty + if (cell.isEmpty()) { + CellInfo info = getCellInfo(row); + drawFrameSeparator(p, row, col, true); + // draw X shape after the occupied cell + TXshCell prevCell; + if (row > 0) prevCell = xsh->getCell(row - 1, col); + if (!prevCell.isEmpty()) { + QColor levelEndColor = m_viewer->getTextColor(); + levelEndColor.setAlphaF(0.3); + p.setPen(levelEndColor); + p.drawLine(info.rect.topLeft(), info.rect.bottomRight()); + p.drawLine(info.rect.topRight(), info.rect.bottomLeft()); + } + if (TApp::instance()->getCurrentFrame()->isEditingScene() && + !m_viewer->orientation()->isVerticalTimeline() && + row == m_viewer->getCurrentRow() && + Preferences::instance()->isCurrentTimelineIndicatorEnabled()) + drawCurrentTimeIndicator(p, info.xy); + // draw mark + if (info.markId >= 0) { + p.setBrush(info.markColor); + p.setPen(Qt::NoPen); + p.drawEllipse(info.markRect); + } + continue; + } + + //---- if the current cell is occupied + + QList infoList; + // check how long the same content continues + int rowTo = row; + infoList.append(getCellInfo(row)); + while (xsh->getCell(rowTo + 1, col) == cell) { + rowTo++; + infoList.append(getCellInfo(rowTo)); + } + + // for each cell block with the same content + + // paint background and other stuffs + for (auto info : infoList) { + bool heldFrame = (!o->isVerticalTimeline() && info.row != row); + drawFrameSeparator(p, info.row, col, false, heldFrame); + + bool isSelected = + isColSelected || cellSelection->isCellSelected(info.row, col); + QColor tmpCellColor = (isSelected) ? selectedCellColor : cellColor; + if (!o->isVerticalTimeline() && info.row != rowTo) + info.rect.adjust(0, 0, 2, 0); + p.fillRect(info.rect, QBrush(tmpCellColor)); + + if (TApp::instance()->getCurrentFrame()->isEditingScene() && + !o->isVerticalTimeline() && info.row == m_viewer->getCurrentRow() && + Preferences::instance()->isCurrentTimelineIndicatorEnabled()) + drawCurrentTimeIndicator(p, info.xy); + + drawDragHandle(p, info.xy, sideColor); + drawEndOfDragHandle(p, info.row == rowTo, info.xy, tmpCellColor); + drawLockedDottedLine(p, xsh->getColumn(col)->isLocked(), info.xy, + tmpCellColor); + // draw mark + if (info.markId >= 0) { + p.setBrush(info.markColor); + p.setPen(Qt::NoPen); + p.drawEllipse(info.markRect); + } + } + + // draw text from here + + QString text = + cell.getSoundTextLevel()->getFrameText(cell.m_frameId.getNumber() - 1); + if (text.isEmpty()) { + // advance the current row + row = rowTo; + continue; + } + int textCount = text.count(); + + p.setPen(Qt::black); + // Vertical case + if (o->isVerticalTimeline()) { + int lettersPerChunk = + (int)std::ceil((double)textCount / (double)(rowTo - row + 1)); + int chunkCount = + (int)std::ceil((double)textCount / (double)(lettersPerChunk)); + bool isChunkOverflow = false; + for (int c = 0; c < chunkCount; c++) { + int chunkWidth = + fm.boundingRect(text.mid(c * lettersPerChunk, lettersPerChunk)) + .width(); + if (chunkWidth > infoList.front().nameRect.width()) { + isChunkOverflow = true; + break; + } + } + // if any chunk overflows the cell width + if (isChunkOverflow) { + p.setFont(font); + // arrange text from the top cell and elide at the last cell + int textPos = 0; + for (auto info : infoList) { + // add letter and check if the text can be inside the cell + int len = 1; + while (textPos + len < textCount && + fm.boundingRect(text.mid(textPos, len + 1)).width() <= + info.nameRect.width()) { + len++; + } + // elide text at the last row + QString curText = + (info.row == rowTo) + ? elideText(text.mid(textPos), fm, info.nameRect.width(), "~") + : text.mid(textPos, len); + + p.drawText(info.nameRect, Qt::AlignCenter, curText); + textPos += len; + if (textPos >= textCount) break; + } + } + // if all text chunks can be inside the cells + else { + // unite the cell rects and divide by the amount of chunks + QRect unitedRect = + infoList.front().nameRect.united(infoList.last().nameRect); + // check if the large font is available + if (lettersPerChunk == 1 && + unitedRect.height() / textCount > heightThres) + p.setFont(largeFont); + else + p.setFont(font); + // draw text + for (int c = 0; c < chunkCount; c++) { + int y0 = unitedRect.top() + unitedRect.height() * c / chunkCount; + int y1 = + unitedRect.top() + unitedRect.height() * (c + 1) / chunkCount; + QRect tmpRect(unitedRect.left(), y0, unitedRect.width(), y1 - y0 + 1); + p.drawText(tmpRect, Qt::AlignCenter, + text.mid(c * lettersPerChunk, lettersPerChunk)); + } + } + } + // Horizontal case + else { + p.setFont(font); + // unite the cell rects + QRect unitedRect = + infoList.front().nameRect.united(infoList.last().nameRect); + int extraWidth = unitedRect.width() - fm.boundingRect(text).width(); + if (extraWidth >= 0) { + int margin = extraWidth / (2 * textCount); + // Qt::TextJustificationForced flag is needed to make Qt::AlignJustify + // to work on the single-line text + p.drawText( + unitedRect.adjusted(margin, 0, -margin, 0), + Qt::TextJustificationForced | Qt::AlignJustify | Qt::AlignVCenter, + text); + } else { + QString elided = elideText(text, fm, unitedRect.width(), "~"); + p.drawText(unitedRect, Qt::AlignLeft | Qt::AlignVCenter, elided); + } + } + + // advance the current row + row = rowTo; + } +} + +//----------------------------------------------------------------------------- + void CellArea::drawPaletteCell(QPainter &p, int row, int col, bool isReference) { const Orientation *o = m_viewer->orientation(); diff --git a/toonz/sources/toonz/xshcellviewer.h b/toonz/sources/toonz/xshcellviewer.h index 8e27466..c7477ef 100644 --- a/toonz/sources/toonz/xshcellviewer.h +++ b/toonz/sources/toonz/xshcellviewer.h @@ -110,6 +110,7 @@ class CellArea final : public QWidget { void drawLevelCell(QPainter &p, int row, int col, bool isReference = false, bool showLevelName = true); void drawSoundTextCell(QPainter &p, int row, int col); + void drawSoundTextColumn(QPainter &p, int r0, int r1, int col); void drawSoundCell(QPainter &p, int row, int col, bool isReference = false); void drawPaletteCell(QPainter &p, int row, int col, bool isReference = false); diff --git a/toonz/sources/toonzlib/txshsoundtextcolumn.cpp b/toonz/sources/toonzlib/txshsoundtextcolumn.cpp index 65e4752..8226e43 100644 --- a/toonz/sources/toonzlib/txshsoundtextcolumn.cpp +++ b/toonz/sources/toonzlib/txshsoundtextcolumn.cpp @@ -68,14 +68,22 @@ void TXshSoundTextColumn::loadData(TIStream &is) { if (tagName == "cells") { while (is.openChild(tagName)) { if (tagName == "cell") { - TPersist *p = 0; - int row = 1; - int fidNumber = 1; + TPersist *p = 0; + std::string rowRangeStr = "1"; + int fidNumber = 1; TFilePath path; - is >> row >> fidNumber >> p; + is >> rowRangeStr >> fidNumber >> p; TXshLevel *xshLevel = dynamic_cast(p); TXshCell cell(xshLevel, TFrameId(fidNumber)); - setCell(row, cell); + + QString _rowRangeStr = QString::fromStdString(rowRangeStr); + QStringList rows = _rowRangeStr.split('-'); + if (rows.size() == 1) + setCell(rows[0].toInt(), cell); + else if (rows.size() == 2) { + for (int r = rows[0].toInt(); r <= rows[1].toInt(); r++) + setCell(r, cell); + } } else throw TException("TXshLevelColumn, unknown tag(2): " + tagName); is.closeChild(); @@ -94,11 +102,42 @@ void TXshSoundTextColumn::saveData(TOStream &os) { int r0, r1; if (getRange(r0, r1)) { os.openChild("cells"); + TXshCell prevCell; + int fromR = r0; for (int r = r0; r <= r1; r++) { TXshCell cell = getCell(r); - if (cell.isEmpty()) continue; - TFrameId fid = cell.m_frameId; - os.child("cell") << r << fid.getNumber() << cell.m_level.getPointer(); + + if (cell != prevCell) { + if (!prevCell.isEmpty()) { + int toR = r - 1; + TFrameId fid = prevCell.m_frameId; + if (fromR == toR) + os.child("cell") + << toR << fid.getNumber() << prevCell.m_level.getPointer(); + else { + QString rangeStr = QString("%1-%2").arg(fromR).arg(toR); + os.child("cell") << rangeStr.toStdString() << fid.getNumber() + << prevCell.m_level.getPointer(); + } + } + prevCell = cell; + fromR = r; + } + assert(cell == prevCell); + if (r == r1) { + if (!cell.isEmpty()) { + int toR = r; + TFrameId fid = cell.m_frameId; + if (fromR == toR) + os.child("cell") + << toR << fid.getNumber() << cell.m_level.getPointer(); + else { + QString rangeStr = QString("%1-%2").arg(fromR).arg(toR); + os.child("cell") << rangeStr.toStdString() << fid.getNumber() + << cell.m_level.getPointer(); + } + } + } } os.closeChild(); }