diff --git a/toonz/sources/include/orientation.h b/toonz/sources/include/orientation.h index eda11c4..2c382b8 100644 --- a/toonz/sources/include/orientation.h +++ b/toonz/sources/include/orientation.h @@ -135,7 +135,8 @@ enum class PredefinedRect { PANEL_EYE, PANEL_PREVIEW_LAYER, PANEL_LOCK, - PANEL_LAYER_NAME + PANEL_LAYER_NAME, + NAVIGATION_TAG_AREA }; enum class PredefinedLine { LOCKED, //! dotted vertical line when cell is locked @@ -168,7 +169,8 @@ enum class PredefinedPath { VOLUME_SLIDER_TRACK, //! slider track VOLUME_SLIDER_HEAD, //! slider head TIME_INDICATOR_HEAD, //! current time indicator head - FRAME_MARKER_DIAMOND + FRAME_MARKER_DIAMOND, + NAVIGATION_TAG }; enum class PredefinedPoint { KEY_HIDDEN, //! move extender handle that much if key icons are disabled diff --git a/toonz/sources/include/toonz/navigationtags.h b/toonz/sources/include/toonz/navigationtags.h new file mode 100644 index 0000000..06d9fd8 --- /dev/null +++ b/toonz/sources/include/toonz/navigationtags.h @@ -0,0 +1,73 @@ +#pragma once + +#ifndef NAVIGATION_TAGS_INCLUDED +#define NAVIGATION_TAGS_INCLUDED + +#include "tcommon.h" + +#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 + +#include +#include + +class TOStream; +class TIStream; + +class DVAPI NavigationTags { +public: + struct Tag { + int m_frame; + QString m_label; + QColor m_color; + Tag() : m_frame(-1), m_label(), m_color(Qt::magenta) {} + Tag(int frame) : m_frame(frame), m_label(), m_color(Qt::magenta) {} + Tag(int frame, QString label) + : m_frame(frame), m_label(label), m_color(Qt::magenta) {} + Tag(int frame, QString label, QColor color) + : m_frame(frame), m_label(label), m_color(color) {} + ~Tag() {} + + bool operator<(const Tag &otherTag) const { + return (m_frame < otherTag.m_frame); + } + }; + std::vector m_tags; + + QColor m_lastTagColorUsed; + + NavigationTags(); + ~NavigationTags() {} + + std::vector getTags() { return m_tags; } + + int getCount() const; + + Tag getTag(int frame); + void addTag(int frame, QString label = ""); + void removeTag(int frame); + void clearTags(); + bool isTagged(int frame); + int getPrevTag(int currentFrame); + int getNextTag(int currentFrame); + void moveTag(int fromFrame, int toFrame); + void shiftTags(int startFrame, int shift); + + QString getTagLabel(int frame); + void setTagLabel(int frame, QString label); + + QColor getTagColor(int frame); + void setTagColor(int frame, QColor color); + + void saveData(TOStream &os); + void loadData(TIStream &is); +}; + +#endif diff --git a/toonz/sources/include/toonz/txsheet.h b/toonz/sources/include/toonz/txsheet.h index 23abc7b..ae7dfbb 100644 --- a/toonz/sources/include/toonz/txsheet.h +++ b/toonz/sources/include/toonz/txsheet.h @@ -53,6 +53,7 @@ class TFrameId; class Orientation; class TXsheetColumnChangeObserver; class ExpressionReferenceMonitor; +class NavigationTags; //============================================================================= @@ -158,6 +159,7 @@ private: std::unique_ptr m_imp; TXshNoteSet *m_notes; SoundProperties *m_soundProperties; + NavigationTags *m_navigationTags; int m_cameraColumnIndex; TXshColumn *m_cameraColumn; @@ -580,6 +582,10 @@ in TXsheetImp. void notifyStageObjectAdded(const TStageObjectId id); bool isReferenceManagementIgnored(TDoubleParam *); + NavigationTags *getNavigationTags() const { return m_navigationTags; } + bool isFrameTagged(int frame) const; + void toggleTaggedFrame(int frame); + protected: bool checkCircularReferences(TXsheet *childCandidate); diff --git a/toonz/sources/toonz/CMakeLists.txt b/toonz/sources/toonz/CMakeLists.txt index b468f19..6499001 100644 --- a/toonz/sources/toonz/CMakeLists.txt +++ b/toonz/sources/toonz/CMakeLists.txt @@ -143,12 +143,14 @@ if(PLATFORM EQUAL 64) ../stopmotion/stopmotionlight.h cameracapturelevelcontrol.h penciltestpopup.h + navtageditorpopup.h ) else() set(MOC_HEADERS ${MOC_HEADERS} cameracapturelevelcontrol_qt.h penciltestpopup_qt.h + navtageditorpopup.h ) endif() @@ -399,12 +401,14 @@ if(PLATFORM EQUAL 64) ../stopmotion/stopmotionlight.cpp cameracapturelevelcontrol.cpp penciltestpopup.cpp + navtageditorpopup.cpp ) else() set(SOURCES ${SOURCES} cameracapturelevelcontrol_qt.cpp penciltestpopup_qt.cpp + navtageditorpopup.cpp ) endif() diff --git a/toonz/sources/toonz/icons/dark/actions/16/next_nav_tag.svg b/toonz/sources/toonz/icons/dark/actions/16/next_nav_tag.svg new file mode 100644 index 0000000..be7b62d --- /dev/null +++ b/toonz/sources/toonz/icons/dark/actions/16/next_nav_tag.svg @@ -0,0 +1,77 @@ + + + + diff --git a/toonz/sources/toonz/icons/dark/actions/16/prev_nav_tag.svg b/toonz/sources/toonz/icons/dark/actions/16/prev_nav_tag.svg new file mode 100644 index 0000000..30e5a25 --- /dev/null +++ b/toonz/sources/toonz/icons/dark/actions/16/prev_nav_tag.svg @@ -0,0 +1,83 @@ + + + + diff --git a/toonz/sources/toonz/icons/dark/actions/16/toggle_nav_tag.svg b/toonz/sources/toonz/icons/dark/actions/16/toggle_nav_tag.svg new file mode 100644 index 0000000..61f7c30 --- /dev/null +++ b/toonz/sources/toonz/icons/dark/actions/16/toggle_nav_tag.svg @@ -0,0 +1,66 @@ + + + + diff --git a/toonz/sources/toonz/mainwindow.cpp b/toonz/sources/toonz/mainwindow.cpp index 354ad65..f007b0f 100644 --- a/toonz/sources/toonz/mainwindow.cpp +++ b/toonz/sources/toonz/mainwindow.cpp @@ -1937,6 +1937,20 @@ void MainWindow::defineActions() { QT_TR_NOOP("&Apply Auto Lip Sync to Column"), "Ctrl+Alt+L", "dialogue"); + createMenuXsheetAction(MI_ToggleTaggedFrame, + QT_TR_NOOP("Toggle Navigation Tag"), "", + "toggle_nav_tag"); + createMenuXsheetAction(MI_NextTaggedFrame, QT_TR_NOOP("Next Tag"), "", + "next_nav_tag"); + createMenuXsheetAction(MI_PrevTaggedFrame, QT_TR_NOOP("Previous Tag"), "", + "prev_nav_tag"); + createMenuXsheetAction(MI_EditTaggedFrame, QT_TR_NOOP("Edit Tag"), "", ""); + createMenuXsheetAction(MI_ClearTags, QT_TR_NOOP("Remove Tags"), "", ""); + CommandManager::instance()->enable(MI_NextTaggedFrame, false); + CommandManager::instance()->enable(MI_PrevTaggedFrame, false); + CommandManager::instance()->enable(MI_EditTaggedFrame, false); + CommandManager::instance()->enable(MI_ClearTags, false); + // Menu - Cells createMenuCellsAction(MI_MergeFrames, QT_TR_NOOP("&Merge"), "", "merge"); diff --git a/toonz/sources/toonz/menubarcommandids.h b/toonz/sources/toonz/menubarcommandids.h index 694a6ed..7e69a89 100644 --- a/toonz/sources/toonz/menubarcommandids.h +++ b/toonz/sources/toonz/menubarcommandids.h @@ -473,4 +473,12 @@ #define MI_CustomPanelEditor "MI_CustomPanelEditor" #define MI_ConvertTZPInFolder "MI_ConvertTZPInFolder" + +// Navigation tags +#define MI_ToggleTaggedFrame "MI_ToggleTaggedFrame" +#define MI_EditTaggedFrame "MI_EditTaggedFrame" +#define MI_NextTaggedFrame "MI_NextTaggedFrame" +#define MI_PrevTaggedFrame "MI_PrevTaggedFrame" +#define MI_ClearTags "MI_ClearTags" + #endif diff --git a/toonz/sources/toonz/navtageditorpopup.cpp b/toonz/sources/toonz/navtageditorpopup.cpp new file mode 100644 index 0000000..c884267 --- /dev/null +++ b/toonz/sources/toonz/navtageditorpopup.cpp @@ -0,0 +1,157 @@ +#include "navtageditorpopup.h" + +#include "../toonz/tapp.h" + +#include +#include +#include + +namespace { +const QIcon getColorChipIcon(QColor color) { + QPixmap pixmap(12, 12); + pixmap.fill(color); + return QIcon(pixmap); +} +} + +void NavTagEditorPopup::accept() { + m_label = QString(m_labelFld->text()); + + Dialog::accept(); +} + +NavTagEditorPopup::NavTagEditorPopup(int frame, QString label, QColor color) + : Dialog(TApp::instance()->getMainWindow(), true, true, "Edit Tag") + , m_label(label) + , m_color(color) { + bool ret = true; + + setWindowTitle(tr("Edit Tag")); + + m_labelFld = new DVGui::LineEdit(m_label); + m_labelFld->setMaximumHeight(DVGui::WidgetHeight); + addWidget(tr("Frame %1 Label:").arg(frame + 1), m_labelFld); + + m_colorCB = new QComboBox(this); + m_colorCB->addItem(getColorChipIcon(Qt::magenta), tr("Magenta"), + TagColors::Magenta); + m_colorCB->addItem(getColorChipIcon(Qt::red), tr("Red"), TagColors::Red); + m_colorCB->addItem(getColorChipIcon(Qt::green), tr("Green"), + TagColors::Green); + m_colorCB->addItem(getColorChipIcon(Qt::blue), tr("Blue"), TagColors::Blue); + m_colorCB->addItem(getColorChipIcon(Qt::yellow), tr("Yellow"), + TagColors::Yellow); + m_colorCB->addItem(getColorChipIcon(Qt::cyan), tr("Cyan"), TagColors::Cyan); + m_colorCB->addItem(getColorChipIcon(Qt::white), tr("White"), + TagColors::White); + m_colorCB->addItem(getColorChipIcon(Qt::darkMagenta), tr("Dark Magenta"), + TagColors::DarkMagenta); + m_colorCB->addItem(getColorChipIcon(Qt::darkRed), tr("Dark Red"), + TagColors::DarkRed); + m_colorCB->addItem(getColorChipIcon(Qt::darkGreen), tr("Dark Green"), + TagColors::DarkGreen); + m_colorCB->addItem(getColorChipIcon(Qt::darkBlue), tr("Dark Blue"), + TagColors::DarkBlue); + m_colorCB->addItem(getColorChipIcon(Qt::darkYellow), tr("Dark Yellow"), + TagColors::DarkYellow); + m_colorCB->addItem(getColorChipIcon(Qt::darkCyan), tr("Dark Cyan"), + TagColors::DarkCyan); + m_colorCB->addItem(getColorChipIcon(Qt::darkGray), tr("Dark Gray"), + TagColors::DarkGray); + + addWidget(tr("Color:"), m_colorCB); + + if (color == Qt::magenta) + m_colorCB->setCurrentIndex(TagColors::Magenta); + else if (color == Qt::red) + m_colorCB->setCurrentIndex(TagColors::Red); + else if (color == Qt::green) + m_colorCB->setCurrentIndex(TagColors::Green); + else if (color == Qt::blue) + m_colorCB->setCurrentIndex(TagColors::Blue); + else if (color == Qt::yellow) + m_colorCB->setCurrentIndex(TagColors::Yellow); + else if (color == Qt::cyan) + m_colorCB->setCurrentIndex(TagColors::Cyan); + else if (color == Qt::white) + m_colorCB->setCurrentIndex(TagColors::White); + else if (color == Qt::darkMagenta) + m_colorCB->setCurrentIndex(TagColors::DarkMagenta); + else if (color == Qt::darkRed) + m_colorCB->setCurrentIndex(TagColors::DarkRed); + else if (color == Qt::darkGreen) + m_colorCB->setCurrentIndex(TagColors::DarkGreen); + else if (color == Qt::darkBlue) + m_colorCB->setCurrentIndex(TagColors::DarkBlue); + else if (color == Qt::darkYellow) + m_colorCB->setCurrentIndex(TagColors::DarkYellow); + else if (color == Qt::darkCyan) + m_colorCB->setCurrentIndex(TagColors::DarkCyan); + else if (color == Qt::darkGray) + m_colorCB->setCurrentIndex(TagColors::DarkGray); + + ret = ret && + connect(m_labelFld, SIGNAL(editingFinished()), SLOT(onLabelChanged())); + ret = ret && + connect(m_colorCB, SIGNAL(activated(int)), SLOT(onColorChanged(int))); + + QPushButton *okBtn = new QPushButton(tr("Ok"), this); + okBtn->setDefault(true); + QPushButton *cancelBtn = new QPushButton(tr("Cancel"), this); + connect(okBtn, SIGNAL(clicked()), this, SLOT(accept())); + connect(cancelBtn, SIGNAL(clicked()), this, SLOT(reject())); + + addButtonBarWidget(okBtn, cancelBtn); +} + +void NavTagEditorPopup::onColorChanged(int index) { + QColor color; + + switch (index) { + case TagColors::Magenta: + default: + color = Qt::magenta; + break; + case TagColors::Red: + color = Qt::red; + break; + case TagColors::Green: + color = Qt::green; + break; + case TagColors::Blue: + color = Qt::blue; + break; + case TagColors::Yellow: + color = Qt::yellow; + break; + case TagColors::Cyan: + color = Qt::cyan; + break; + case TagColors::White: + color = Qt::white; + break; + case TagColors::DarkMagenta: + color = Qt::darkMagenta; + break; + case TagColors::DarkRed: + color = Qt::darkRed; + break; + case TagColors::DarkGreen: + color = Qt::darkGreen; + break; + case TagColors::DarkBlue: + color = Qt::darkBlue; + break; + case TagColors::DarkYellow: + color = Qt::darkYellow; + break; + case TagColors::DarkCyan: + color = Qt::darkCyan; + break; + case TagColors::DarkGray: + color = Qt::darkGray; + break; + } + + m_color = color; +} diff --git a/toonz/sources/toonz/navtageditorpopup.h b/toonz/sources/toonz/navtageditorpopup.h new file mode 100644 index 0000000..c3fcdb6 --- /dev/null +++ b/toonz/sources/toonz/navtageditorpopup.h @@ -0,0 +1,56 @@ +#pragma once + +#ifndef TAGEDITORPOPUP_INCLUDED +#define TAGEDITORPOPUP_INCLUDED + +#include "tcommon.h" +#include "toonzqt/dvdialog.h" +#include "toonzqt/lineedit.h" + +#include +#include +#include + +class NavTagEditorPopup final : public DVGui::Dialog { + Q_OBJECT + + DVGui::LineEdit *m_labelFld; + QComboBox *m_colorCB; + + QString m_label; + QColor m_color; + +public: + enum TagColors { + Magenta = 0, + Red, + Green, + Blue, + Yellow, + Cyan, + White, + DarkMagenta, + DarkRed, + DarkGreen, + DarkBlue, + DarkYellow, + DarkCyan, + DarkGray + }; + +public: + NavTagEditorPopup(int frame, QString label, QColor color); + + void accept() override; + + QString getLabel() { return m_label; } + + QColor getColor() { return m_color; } + +protected slots: + + void onLabelChanged() {} + void onColorChanged(int); +}; + +#endif diff --git a/toonz/sources/toonz/toonz.qrc b/toonz/sources/toonz/toonz.qrc index 1084d80..8cd89a3 100644 --- a/toonz/sources/toonz/toonz.qrc +++ b/toonz/sources/toonz/toonz.qrc @@ -377,7 +377,11 @@ icons/dark/actions/16/unlink.svg icons/dark/actions/16/macro.svg - + icons/dark/actions/16/toggle_nav_tag.svg + icons/dark/actions/16/next_nav_tag.svg + icons/dark/actions/16/prev_nav_tag.svg + + icons/dark/actions/16/ink_check.svg icons/dark/actions/16/inks_only.svg icons/dark/actions/16/transparency_check.svg diff --git a/toonz/sources/toonz/xsheetcmd.cpp b/toonz/sources/toonz/xsheetcmd.cpp index 7e360dd..54108c5 100644 --- a/toonz/sources/toonz/xsheetcmd.cpp +++ b/toonz/sources/toonz/xsheetcmd.cpp @@ -42,6 +42,7 @@ #include "toonz/tfxhandle.h" #include "toonz/scenefx.h" #include "toonz/preferences.h" +#include "toonz/navigationtags.h" // TnzQt includes #include "toonzqt/tselectionhandle.h" @@ -49,6 +50,7 @@ #include "toonzqt/menubarcommand.h" #include "toonzqt/stageobjectsdata.h" #include "historytypes.h" +#include "xsheetviewer.h" // Tnz6 includes #include "cellselection.h" @@ -60,6 +62,7 @@ #include "menubarcommandids.h" #include "columncommand.h" #include "xshcellviewer.h" // SetCellMarkUndo +#include "navtageditorpopup.h" // Qt includes #include @@ -176,6 +179,8 @@ void InsertSceneFrameUndo::doInsertSceneFrame(int frame) { if (TStageObject *obj = xsh->getStageObject(objectId)) insertFrame(obj, frame); } + + xsh->getNavigationTags()->shiftTags(frame, 1); } //----------------------------------------------------------------------------- @@ -199,6 +204,9 @@ void InsertSceneFrameUndo::doRemoveSceneFrame(int frame) { if (TStageObject *pegbar = xsh->getStageObject(objectId)) removeFrame(pegbar, frame); } + + if (xsh->isFrameTagged(frame)) xsh->getNavigationTags()->removeTag(frame); + xsh->getNavigationTags()->shiftTags(frame, -1); } //----------------------------------------------------------------------------- @@ -242,6 +250,7 @@ public: class RemoveSceneFrameUndo final : public InsertSceneFrameUndo { std::vector m_cells; std::vector m_keyframes; + NavigationTags::Tag m_tag; public: RemoveSceneFrameUndo(int frame) : InsertSceneFrameUndo(frame) { @@ -252,6 +261,7 @@ public: m_cells.resize(colsCount); m_keyframes.resize(colsCount + 1); + m_tag = xsh->getNavigationTags()->getTag(frame); // Inserting the eventual camera keyframe at the end TStageObject *cameraObj = xsh->getStageObject( @@ -296,6 +306,10 @@ public: } } + // Restore tag if there was one + if (m_tag.m_frame != -1) + xsh->getNavigationTags()->addTag(m_tag.m_frame, m_tag.m_label); + TApp::instance()->getCurrentScene()->setDirtyFlag(true); TApp::instance()->getCurrentXsheet()->notifyXsheetChanged(); } @@ -2183,4 +2197,106 @@ SetCellMarkCommand CellMarkCommand7(7); SetCellMarkCommand CellMarkCommand8(8); SetCellMarkCommand CellMarkCommand9(9); SetCellMarkCommand CellMarkCommand10(10); -SetCellMarkCommand CellMarkCommand11(11); \ No newline at end of file +SetCellMarkCommand CellMarkCommand11(11); + +//============================================================ + +class ToggleTaggedFrame final : public MenuItemHandler { +public: + ToggleTaggedFrame() : MenuItemHandler(MI_ToggleTaggedFrame) {} + void execute() override { + TApp *app = TApp::instance(); + int frame = app->getCurrentFrame()->getFrame(); + assert(frame >= 0); + TXsheet *xsh = app->getCurrentXsheet()->getXsheet(); + + xsh->toggleTaggedFrame(frame); + + NavigationTags *navTags = xsh->getNavigationTags(); + CommandManager::instance()->enable(MI_EditTaggedFrame, + navTags->isTagged(frame)); + CommandManager::instance()->enable(MI_ClearTags, (navTags->getCount() > 0)); + + TApp::instance()->getCurrentXsheetViewer()->update(); + } +} ToggleTaggedFrame; + +//============================================================ + +class EditTaggedFrame final : public MenuItemHandler { +public: + EditTaggedFrame() : MenuItemHandler(MI_EditTaggedFrame) {} + void execute() override { + TApp *app = TApp::instance(); + int frame = app->getCurrentFrame()->getFrame(); + assert(frame >= 0); + TXsheet *xsh = app->getCurrentXsheet()->getXsheet(); + + NavigationTags *tags = xsh->getNavigationTags(); + QString label = tags->getTagLabel(frame); + QColor color = tags->getTagColor(frame); + NavTagEditorPopup navTagEditor(frame, label, color); + if (navTagEditor.exec() != QDialog::Accepted) return; + tags->setTagLabel(frame, navTagEditor.getLabel()); + tags->setTagColor(frame, navTagEditor.getColor()); + } +} EditTaggedFrame; + +//============================================================ + +class NextTaggedFrame final : public MenuItemHandler { +public: + NextTaggedFrame() : MenuItemHandler(MI_NextTaggedFrame) {} + void execute() override { + TApp *app = TApp::instance(); + int frame = app->getCurrentFrame()->getFrame(); + assert(frame >= 0); + TXsheet *xsh = app->getCurrentXsheet()->getXsheet(); + + NavigationTags *navTags = xsh->getNavigationTags(); + int nextFrame = navTags->getNextTag(frame); + if (nextFrame != -1) + app->getCurrentXsheetViewer()->setCurrentRow(nextFrame); + } +} NextTaggedFrame; + +//============================================================ + +class PrevTaggedFrame final : public MenuItemHandler { +public: + PrevTaggedFrame() : MenuItemHandler(MI_PrevTaggedFrame) {} + void execute() override { + TApp *app = TApp::instance(); + int frame = app->getCurrentFrame()->getFrame(); + assert(frame >= 0); + TXsheet *xsh = app->getCurrentXsheet()->getXsheet(); + + NavigationTags *navTags = xsh->getNavigationTags(); + int prevFrame = navTags->getPrevTag(frame); + if (prevFrame != -1) + app->getCurrentXsheetViewer()->setCurrentRow(prevFrame); + } +} PrevTaggedFrame; + +//============================================================ + +class ClearTags final : public MenuItemHandler { +public: + ClearTags() : MenuItemHandler(MI_ClearTags) {} + void execute() override { + TApp *app = TApp::instance(); + int frame = app->getCurrentFrame()->getFrame(); + assert(frame >= 0); + TXsheet *xsh = app->getCurrentXsheet()->getXsheet(); + + NavigationTags *navTags = xsh->getNavigationTags(); + navTags->clearTags(); + + CommandManager::instance()->enable(MI_NextTaggedFrame, false); + CommandManager::instance()->enable(MI_PrevTaggedFrame, false); + CommandManager::instance()->enable(MI_EditTaggedFrame, false); + CommandManager::instance()->enable(MI_ClearTags, false); + + TApp::instance()->getCurrentXsheetViewer()->update(); + } +} ClearTags; diff --git a/toonz/sources/toonz/xsheetdragtool.cpp b/toonz/sources/toonz/xsheetdragtool.cpp index 3de2307..2ae1f0b 100644 --- a/toonz/sources/toonz/xsheetdragtool.cpp +++ b/toonz/sources/toonz/xsheetdragtool.cpp @@ -50,6 +50,7 @@ #include "toutputproperties.h" #include "toonz/preferences.h" #include "toonz/columnfan.h" +#include "toonz/navigationtags.h" // TnzBase includes #include "tfx.h" @@ -2200,3 +2201,49 @@ XsheetGUI::DragTool *XsheetGUI::DragTool::makeDragAndDropDataTool( XsheetViewer *viewer) { return new DataDragTool(viewer); } + +//============================================================================= +// NavigationTagDragTool +//----------------------------------------------------------------------------- + +namespace { + +class NavigationTagDragTool final : public XsheetGUI::DragTool { + int m_taggedRow; + +public: + NavigationTagDragTool(XsheetViewer *viewer) : DragTool(viewer) {} + + void onClick(const CellPosition &pos) override { + int row = pos.frame(); + m_taggedRow = row; + refreshRowsArea(); + } + + void onDrag(const CellPosition &pos) override { + int row = pos.frame(); + if (row < 0) row = 0; + onRowChange(row); + refreshRowsArea(); + } + + void onRowChange(int row) { + if (row < 0) return; + + TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet(); + NavigationTags *navTags = xsh->getNavigationTags(); + + if (m_taggedRow == row || navTags->isTagged(row)) return; + + navTags->moveTag(m_taggedRow, row); + m_taggedRow = row; + } +}; +//----------------------------------------------------------------------------- +} // namespace +//----------------------------------------------------------------------------- + +XsheetGUI::DragTool *XsheetGUI::DragTool::makeNavigationTagDragTool( + XsheetViewer *viewer) { + return new NavigationTagDragTool(viewer); +} diff --git a/toonz/sources/toonz/xsheetdragtool.h b/toonz/sources/toonz/xsheetdragtool.h index 3689461..154bfdc 100644 --- a/toonz/sources/toonz/xsheetdragtool.h +++ b/toonz/sources/toonz/xsheetdragtool.h @@ -69,6 +69,8 @@ public: static DragTool *makeColumnLinkTool(XsheetViewer *viewer); static DragTool *makeColumnMoveTool(XsheetViewer *viewer); static DragTool *makeVolumeDragTool(XsheetViewer *viewer); + + static DragTool *makeNavigationTagDragTool(XsheetViewer *viewer); }; void setPlayRange(int r0, int r1, int step, bool withUndo = true); diff --git a/toonz/sources/toonz/xsheetviewer.cpp b/toonz/sources/toonz/xsheetviewer.cpp index f4ee0d4..cd5b1a4 100644 --- a/toonz/sources/toonz/xsheetviewer.cpp +++ b/toonz/sources/toonz/xsheetviewer.cpp @@ -35,6 +35,7 @@ #include "toonz/txshlevelhandle.h" #include "toonz/tproject.h" #include "tconvert.h" +#include "toonz/navigationtags.h" #include "tenv.h" @@ -1390,6 +1391,18 @@ void XsheetViewer::onSceneSwitched() { void XsheetViewer::onXsheetChanged() { refreshContentSize(0, 0); updateAllAree(); + + int row = TApp::instance()->getCurrentFrame()->getFrame(); + TXsheet *xsh = getXsheet(); + NavigationTags *navTags = xsh->getNavigationTags(); + int lastTag = navTags->getPrevTag(INT_MAX); + int firstTag = navTags->getNextTag(-1); + CommandManager::instance()->enable(MI_NextTaggedFrame, (row < lastTag)); + CommandManager::instance()->enable(MI_PrevTaggedFrame, + firstTag != -1 && row > firstTag); + CommandManager::instance()->enable(MI_EditTaggedFrame, + navTags->isTagged(row)); + CommandManager::instance()->enable(MI_ClearTags, (navTags->getCount() > 0)); } //----------------------------------------------------------------------------- @@ -1414,6 +1427,16 @@ void XsheetViewer::onCurrentFrameSwitched() { } m_isCurrentFrameSwitched = false; scrollToRow(row); + + TXsheet *xsh = getXsheet(); + NavigationTags *navTags = xsh->getNavigationTags(); + int lastTag = navTags->getPrevTag(INT_MAX); + int firstTag = navTags->getNextTag(-1); + CommandManager::instance()->enable(MI_NextTaggedFrame, (row < lastTag)); + CommandManager::instance()->enable(MI_PrevTaggedFrame, + firstTag != -1 && row > firstTag); + CommandManager::instance()->enable(MI_EditTaggedFrame, + navTags->isTagged(row)); } //----------------------------------------------------------------------------- diff --git a/toonz/sources/toonz/xshrowviewer.cpp b/toonz/sources/toonz/xshrowviewer.cpp index 640df20..793d32b 100644 --- a/toonz/sources/toonz/xshrowviewer.cpp +++ b/toonz/sources/toonz/xshrowviewer.cpp @@ -28,6 +28,7 @@ #include "tools/toolcommandids.h" #include "toonz/tstageobject.h" #include "toonz/tpinnedrangeset.h" +#include "toonz/navigationtags.h" #include #include @@ -439,6 +440,50 @@ void RowArea::drawStopMotionCameraIndicator(QPainter &p) { //----------------------------------------------------------------------------- #endif +void RowArea::drawNavigationTags(QPainter &p, int r0, int r1) { + TApp *app = TApp::instance(); + TXsheet *xsh = app->getCurrentScene()->getScene()->getXsheet(); + assert(xsh); + + QPoint frameAdj = m_viewer->getFrameZoomAdjustment(); + + NavigationTags *tags = xsh->getNavigationTags(); + + for (int r = r0; r <= r1; r++) { + if (!xsh->isFrameTagged(r)) continue; + + QPoint topLeft = m_viewer->positionToXY(CellPosition(r, -1)); + if (!m_viewer->orientation()->isVerticalTimeline()) + topLeft.setY(0); + else + topLeft.setX(0); + + QRect tagRect = m_viewer->orientation() + ->rect(PredefinedRect::NAVIGATION_TAG_AREA) + .translated(topLeft) + .translated(-frameAdj / 2); + + int frameMid, frameTop; + if (m_viewer->orientation()->isVerticalTimeline()) { + frameMid = tagRect.left() - 3; + frameTop = tagRect.top() + (tagRect.height() / 2); + } else { + frameMid = tagRect.left() + (tagRect.height() / 2) - 3; + frameTop = tagRect.top() + 1; + } + + QPainterPath tag = m_viewer->orientation() + ->path(PredefinedPath::NAVIGATION_TAG) + .translated(QPoint(frameMid, frameTop)); + p.setPen(Qt::black); + p.setBrush(tags->getTagColor(r)); + p.drawPath(tag); + p.setBrush(Qt::NoBrush); + } +} + +//----------------------------------------------------------------------------- + void RowArea::drawOnionSkinBackground(QPainter &p, int r0, int r1) { const Orientation *o = m_viewer->orientation(); @@ -949,6 +994,8 @@ void RowArea::paintEvent(QPaintEvent *event) { Preferences::instance()->isCurrentTimelineIndicatorEnabled()) drawCurrentTimeLine(p); + drawNavigationTags(p, r0, r1); + drawRows(p, r0, r1); if (TApp::instance()->getCurrentFrame()->isEditingScene()) { @@ -1064,7 +1111,13 @@ void RowArea::mousePressEvent(QMouseEvent *event) { PredefinedDimension::FRAME_AREA_EXPANSION); } - if (playR1 == -1) { // getFrameCount = 0 i.e. xsheet is empty + if (xsh->getNavigationTags()->isTagged(row) && + o->rect(PredefinedRect::NAVIGATION_TAG_AREA) + .adjusted(0, 0, -frameAdj.x(), -frameAdj.y()) + .contains(mouseInCell)) { + setDragTool(XsheetGUI::DragTool::makeNavigationTagDragTool(m_viewer)); + frameAreaIsClicked = true; + } else if (playR1 == -1) { // getFrameCount = 0 i.e. xsheet is empty setDragTool( XsheetGUI::DragTool::makeCurrentFrameModifierTool(m_viewer)); frameAreaIsClicked = true; @@ -1259,7 +1312,14 @@ void RowArea::mouseMoveEvent(QMouseEvent *event) { m_tooltip = tr("Pinned Center : Col%1%2") .arg(pinnedCenterColumnId + 1) .arg((isRootBonePinned) ? " (Root)" : ""); - else if (row == currentRow) { + else if (o->rect(PredefinedRect::NAVIGATION_TAG_AREA) + .adjusted(0, 0, -frameAdj.x(), -frameAdj.y()) + .contains(mouseInCell)) { + TXsheet *xsh = m_viewer->getXsheet(); + QString label = xsh->getNavigationTags()->getTagLabel(m_row); + if (label.isEmpty()) label = "-"; + if (xsh->isFrameTagged(m_row)) m_tooltip = tr("Tag: %1").arg(label); + } else if (row == currentRow) { if (Preferences::instance()->isOnionSkinEnabled() && o->rect(PredefinedRect::ONION) .translated(-frameAdj / 2) @@ -1338,6 +1398,34 @@ void RowArea::contextMenuEvent(QContextMenuEvent *event) { menu->addAction(cmdManager->getAction(MI_NoShift)); menu->addAction(cmdManager->getAction(MI_ResetShift)); + // Tags + menu->addSeparator(); + menu->addAction(cmdManager->getAction(MI_ToggleTaggedFrame)); + menu->addAction(cmdManager->getAction(MI_EditTaggedFrame)); + + QMenu *tagMenu = menu->addMenu(tr("Tags")); + NavigationTags *navTags = m_viewer->getXsheet()->getNavigationTags(); + QAction *tagAction; + if (!navTags->getCount()) { + tagAction = tagMenu->addAction("Empty"); + tagAction->setEnabled(false); + } else { + std::vector tags = navTags->getTags(); + for (int i = 0; i < tags.size(); i++) { + int frame = tags[i].m_frame; + QString label = tr("Frame %1").arg(frame + 1); + if (!tags[i].m_label.isEmpty()) label += ": " + tags[i].m_label; + tagAction = tagMenu->addAction(label); + tagAction->setData(frame); + connect(tagAction, SIGNAL(triggered()), this, SLOT(onJumpToTag())); + } + tagMenu->addSeparator(); + tagMenu->addAction(cmdManager->getAction(MI_ClearTags)); + } + + menu->addAction(cmdManager->getAction(MI_NextTaggedFrame)); + menu->addAction(cmdManager->getAction(MI_PrevTaggedFrame)); + menu->exec(event->globalPos()); } @@ -1480,4 +1568,11 @@ void RowArea::onRemoveMarkers() { //----------------------------------------------------------------------------- +void RowArea::onJumpToTag() { + QAction *senderAction = qobject_cast(sender()); + assert(senderAction); + int frame = senderAction->data().toInt(); + m_viewer->setCurrentRow(frame); +} + } // namespace XsheetGUI diff --git a/toonz/sources/toonz/xshrowviewer.h b/toonz/sources/toonz/xshrowviewer.h index 288eb7e..094ab2d 100644 --- a/toonz/sources/toonz/xshrowviewer.h +++ b/toonz/sources/toonz/xshrowviewer.h @@ -61,6 +61,8 @@ class RowArea final : public QWidget { void drawStopMotionCameraIndicator(QPainter &p); #endif + void drawNavigationTags(QPainter &p, int r0, int r1); + DragTool *getDragTool() const; void setDragTool(DragTool *dragTool); @@ -101,6 +103,9 @@ protected slots: // set both the from and to markers at the specified row void onPreviewThis(); + +protected slots: + void onJumpToTag(); }; } // namespace XsheetGUI diff --git a/toonz/sources/toonzlib/CMakeLists.txt b/toonz/sources/toonzlib/CMakeLists.txt index d5b8b24..4267473 100644 --- a/toonz/sources/toonzlib/CMakeLists.txt +++ b/toonz/sources/toonzlib/CMakeLists.txt @@ -164,6 +164,7 @@ set(HEADERS ../include/toonz/txsheetcolumnchange.h ../include/toonz/expressionreferencemonitor.h ../include/toonz/filepathproperties.h + ../include/toonz/navigationtags.h ) set(SOURCES @@ -324,6 +325,7 @@ set(SOURCES textureutils.cpp boardsettings.cpp filepathproperties.cpp + navigationtags.cpp ) if(BUILD_TARGET_WIN) diff --git a/toonz/sources/toonzlib/navigationtags.cpp b/toonz/sources/toonzlib/navigationtags.cpp new file mode 100644 index 0000000..2fdf12a --- /dev/null +++ b/toonz/sources/toonzlib/navigationtags.cpp @@ -0,0 +1,230 @@ +#include "toonz/navigationtags.h" + +#include "tstream.h" +#include "texception.h" +#include "tenv.h" + +#ifndef _WIN32 +#include +#endif + +TEnv::IntVar NavigationTagLastColorR("NavigationTagLastColorR", 255); +TEnv::IntVar NavigationTagLastColorG("NavigationTagLastColorG", 0); +TEnv::IntVar NavigationTagLastColorB("NavigationTagLastColorB", 255); + +NavigationTags::NavigationTags() { + m_lastTagColorUsed = QColor(NavigationTagLastColorR, NavigationTagLastColorG, + NavigationTagLastColorB); +} + +//----------------------------------------------------------------------------- + +int NavigationTags::getCount() const { + if (m_tags.empty()) return 0; + return m_tags.size(); +} + +//----------------------------------------------------------------------------- + +NavigationTags::Tag NavigationTags::getTag(int frame) { + for (int i = 0; i < m_tags.size(); i++) + if (m_tags[i].m_frame == frame) return m_tags[i]; + + return Tag(); +} + +//----------------------------------------------------------------------------- + +void NavigationTags::addTag(int frame, QString label) { + if (frame < 0 || isTagged(frame)) return; + + m_tags.push_back(Tag(frame, label, m_lastTagColorUsed)); + + std::sort(m_tags.begin(), m_tags.end()); +} + +//----------------------------------------------------------------------------- + +void NavigationTags::removeTag(int frame) { + if (frame < 0) return; + + Tag tag = getTag(frame); + if (tag.m_frame == -1) return; + + std::vector::iterator it; + for (it = m_tags.begin(); it != m_tags.end(); it++) + if (it->m_frame == frame) { + m_tags.erase(it); + break; + } +} + +//----------------------------------------------------------------------------- + +void NavigationTags::clearTags() { m_tags.clear(); } + +//----------------------------------------------------------------------------- + +bool NavigationTags::isTagged(int frame) { + if (frame < 0) return false; + + for (int i = 0; i < m_tags.size(); i++) + if (m_tags[i].m_frame == frame) return true; + + return false; +} + +//----------------------------------------------------------------------------- + +void NavigationTags::moveTag(int fromFrame, int toFrame) { + if (fromFrame < 0 || toFrame < 0 || isTagged(toFrame)) return; + + for (int i = 0; i < m_tags.size(); i++) + if (m_tags[i].m_frame == fromFrame) { + m_tags[i].m_frame = toFrame; + std::sort(m_tags.begin(), m_tags.end()); + break; + } +} + +//----------------------------------------------------------------------------- + +// WARNING: When shifting left, shiftTag callers MUST take care of shifting tags +// to frame < 0 or handle possible frame collisions. This will not do it +// for you! +void NavigationTags::shiftTags(int startFrame, int shift) { + if (!m_tags.size()) return; + + for (int i = 0; i < m_tags.size(); i++) { + if (m_tags[i].m_frame < startFrame) continue; + m_tags[i].m_frame += shift; + } +} + +//----------------------------------------------------------------------------- + +int NavigationTags::getPrevTag(int currentFrame) { + if (currentFrame < 0) return -1; + + int index = -1; + int closestFrame = -1; + for (int i = 0; i < m_tags.size(); i++) + if (m_tags[i].m_frame < currentFrame && m_tags[i].m_frame > closestFrame) { + index = i; + closestFrame = m_tags[i].m_frame; + } + + return index >= 0 ? m_tags[index].m_frame : -1; +} + +//----------------------------------------------------------------------------- + +int NavigationTags::getNextTag(int currentFrame) { + int index = -1; + int closestFrame = INT_MAX; + for (int i = 0; i < m_tags.size(); i++) + if (m_tags[i].m_frame > currentFrame && m_tags[i].m_frame < closestFrame) { + index = i; + closestFrame = m_tags[i].m_frame; + } + + return index >= 0 ? m_tags[index].m_frame : -1; +} + +//----------------------------------------------------------------------------- + +QString NavigationTags::getTagLabel(int frame) { + Tag tag = getTag(frame); + + return tag.m_label; +} + +//----------------------------------------------------------------------------- + +void NavigationTags::setTagLabel(int frame, QString label) { + if (frame < 0) return; + + for (int i = 0; i < m_tags.size(); i++) + if (m_tags[i].m_frame == frame) { + m_tags[i].m_label = label; + break; + } +} + +//----------------------------------------------------------------------------- + +QColor NavigationTags::getTagColor(int frame) { + Tag tag = getTag(frame); + + return (tag.m_frame == -1) ? m_lastTagColorUsed : tag.m_color; +} + +//----------------------------------------------------------------------------- + +void NavigationTags::setTagColor(int frame, QColor color) { + if (frame < 0) return; + + for (int i = 0; i < m_tags.size(); i++) + if (m_tags[i].m_frame == frame) { + m_tags[i].m_color = color; + break; + } + + m_lastTagColorUsed = color; + NavigationTagLastColorR = color.red(); + NavigationTagLastColorG = color.green(); + NavigationTagLastColorB = color.blue(); +} + +//----------------------------------------------------------------------------- + +void NavigationTags::saveData(TOStream &os) { + int i; + os.openChild("Tags"); + for (i = 0; i < getCount(); i++) { + os.openChild("tag"); + Tag tag = m_tags.at(i); + os << tag.m_frame; + os << tag.m_label; + os << tag.m_color.red(); + os << tag.m_color.green(); + os << tag.m_color.blue(); + os.closeChild(); + } + os.closeChild(); +} + +//----------------------------------------------------------------------------- + +void NavigationTags::loadData(TIStream &is) { + while (!is.eos()) { + std::string tagName; + if (is.matchTag(tagName)) { + if (tagName == "Tags") { + while (!is.eos()) { + std::string tagName; + if (is.matchTag(tagName)) { + if (tagName == "tag") { + Tag tag; + is >> tag.m_frame; + std::wstring text; + is >> text; + tag.m_label = QString::fromStdWString(text); + int r, g, b; + is >> r; + is >> g; + is >> b; + tag.m_color = QColor(r, g, b); + m_tags.push_back(tag); + } + } else + throw TException("expected "); + is.closeChild(); + } + } else + throw TException("expected "); + is.closeChild(); + } else + throw TException("expected tag"); + } +} diff --git a/toonz/sources/toonzlib/orientation.cpp b/toonz/sources/toonzlib/orientation.cpp index 6a18b4b..c7dea3c 100644 --- a/toonz/sources/toonzlib/orientation.cpp +++ b/toonz/sources/toonzlib/orientation.cpp @@ -12,7 +12,9 @@ const int KEY_ICON_WIDTH = 11; const int KEY_ICON_HEIGHT = 13; const int EASE_TRIANGLE_SIZE = 4; const int PLAY_MARKER_SIZE = 10; -const int PINNED_SIZE = 10; +const int PINNED_SIZE = 11; +const int NAV_TAG_WIDTH = 7; +const int NAV_TAG_HEIGHT = 13; const int FRAME_MARKER_SIZE = 4; const int FOLDED_CELL_SIZE = 9; const int SHIFTTRACE_DOT_SIZE = 12; @@ -413,6 +415,10 @@ TopToBottomOrientation::TopToBottomOrientation() { SHIFTTRACE_DOT_SIZE, SHIFTTRACE_DOT_SIZE)); addRect(PredefinedRect::SHIFTTRACE_DOT_AREA, QRect(SHIFTTRACE_DOT_OFFSET, 0, SHIFTTRACE_DOT_SIZE, CELL_HEIGHT)); + addRect( + PredefinedRect::NAVIGATION_TAG_AREA, + QRect((FRAME_HEADER_WIDTH - NAV_TAG_HEIGHT) / 2, + (CELL_HEIGHT - NAV_TAG_WIDTH) / 2, NAV_TAG_HEIGHT, NAV_TAG_WIDTH)); // Column viewer addRect(PredefinedRect::LAYER_HEADER, @@ -1000,6 +1006,14 @@ TopToBottomOrientation::TopToBottomOrientation() { } addPath(PredefinedPath::VOLUME_SLIDER_HEAD, head); + QPainterPath tag(QPointF(0, -3)); + tag.lineTo(QPointF(9, -3)); + tag.lineTo(QPointF(13, 0)); + tag.lineTo(QPointF(9, 3)); + tag.lineTo(QPointF(0, 3)); + tag.lineTo(QPointF(0, -3)); + addPath(PredefinedPath::NAVIGATION_TAG, tag); + // // Points // @@ -1197,6 +1211,11 @@ LeftToRightOrientation::LeftToRightOrientation() { .adjusted(-1, 0, -1, 0)); addRect(PredefinedRect::SHIFTTRACE_DOT_AREA, QRect(0, SHIFTTRACE_DOT_OFFSET, CELL_WIDTH, SHIFTTRACE_DOT_SIZE)); + addRect(PredefinedRect::NAVIGATION_TAG_AREA, + QRect((CELL_WIDTH - NAV_TAG_WIDTH) / 2, + (FRAME_HEADER_HEIGHT - NAV_TAG_HEIGHT) / 2, NAV_TAG_WIDTH, + NAV_TAG_HEIGHT)); + // Column viewer addRect(PredefinedRect::LAYER_HEADER, @@ -1413,6 +1432,14 @@ LeftToRightOrientation::LeftToRightOrientation() { timeIndicator.lineTo(QPointF(0, 0)); addPath(PredefinedPath::TIME_INDICATOR_HEAD, timeIndicator); + QPainterPath tag(QPointF(-3, 0)); + tag.lineTo(QPointF(3, 0)); + tag.lineTo(QPointF(3, 9)); + tag.lineTo(QPointF(0, 13)); + tag.lineTo(QPointF(-3, 9)); + tag.lineTo(QPointF(-3, 0)); + addPath(PredefinedPath::NAVIGATION_TAG, tag); + // // Points // diff --git a/toonz/sources/toonzlib/txsheet.cpp b/toonz/sources/toonzlib/txsheet.cpp index cc7d02b..1103bc2 100644 --- a/toonz/sources/toonzlib/txsheet.cpp +++ b/toonz/sources/toonzlib/txsheet.cpp @@ -33,6 +33,7 @@ #include "xshhandlemanager.h" #include "orientation.h" #include "toonz/expressionreferencemonitor.h" +#include "toonz/navigationtags.h" #include "toonz/txsheet.h" #include "toonz/preferences.h" @@ -197,7 +198,8 @@ TXsheet::TXsheet() , m_imp(new TXsheet::TXsheetImp) , m_notes(new TXshNoteSet()) , m_cameraColumnIndex(0) - , m_observer(nullptr) { + , m_observer(nullptr) + , m_navigationTags(new NavigationTags()) { // extern TSyntax::Grammar *createXsheetGrammar(TXsheet*); m_soundProperties = new TXsheet::SoundProperties(); m_imp->m_handleManager = new XshHandleManager(this); @@ -218,6 +220,7 @@ TXsheet::~TXsheet() { assert(m_imp); if (m_notes) delete m_notes; if (m_soundProperties) delete m_soundProperties; + if (m_navigationTags) delete m_navigationTags; } //----------------------------------------------------------------------------- @@ -1264,6 +1267,8 @@ void TXsheet::loadData(TIStream &is) { m_imp->copyFoldedState(); } else if (tagName == "noteSet") { m_notes->loadData(is); + } else if (tagName == "navigationTags") { + m_navigationTags->loadData(is); } else { throw TException("xsheet, unknown tag: " + tagName); } @@ -1313,6 +1318,13 @@ void TXsheet::saveData(TOStream &os) { notes->saveData(os); os.closeChild(); } + + NavigationTags *navigationTags = getNavigationTags(); + if (navigationTags->getCount() > 0) { + os.openChild("navigationTags"); + navigationTags->saveData(os); + os.closeChild(); + } } //----------------------------------------------------------------------------- @@ -1840,3 +1852,20 @@ ExpressionReferenceMonitor *TXsheet::getExpRefMonitor() const { return m_imp->m_expRefMonitor; } //--------------------------------------------------------- + +bool TXsheet::isFrameTagged(int frame) const { + if (frame < 0) return false; + + return m_navigationTags->isTagged(frame); +} + +//--------------------------------------------------------- + +void TXsheet::toggleTaggedFrame(int frame) { + if (frame < 0) return; + + if (isFrameTagged(frame)) + m_navigationTags->removeTag(frame); + else + m_navigationTags->addTag(frame); +}