1 #include <memory>
2 
3 #include "cellselection.h"
4 #include "cellkeyframeselection.h"
5 #include "keyframeselection.h"
6 #include "keyframedata.h"
7 
8 // Tnz6 includes
9 #include "tapp.h"
10 #include "duplicatepopup.h"
11 #include "overwritepopup.h"
12 #include "selectionutils.h"
13 #include "columnselection.h"
14 #include "reframepopup.h"
15 
16 // TnzQt includes
17 #include "toonzqt/tselectionhandle.h"
18 #include "toonzqt/gutil.h"
19 #include "historytypes.h"
20 
21 // TnzLib includes
22 #include "toonz/txshcell.h"
23 #include "toonz/txshsimplelevel.h"
24 #include "toonz/levelset.h"
25 #include "toonz/tstageobject.h"
26 #include "toonz/toonzscene.h"
27 #include "toonz/txsheethandle.h"
28 #include "toonz/tscenehandle.h"
29 #include "toonz/tobjecthandle.h"
30 #include "toonz/stageobjectutil.h"
31 #include "toonz/hook.h"
32 #include "toonz/levelproperties.h"
33 #include "toonz/childstack.h"
34 #include "toonz/tframehandle.h"
35 #include "toonz/tcolumnhandle.h"
36 
37 // TnzCore includes
38 #include "tsystem.h"
39 #include "tundo.h"
40 #include "tmsgcore.h"
41 #include "trandom.h"
42 #include "tpalette.h"
43 
44 // Qt includes
45 #include <QLabel>
46 #include <QPushButton>
47 #include <QMainWindow>
48 
49 // tcg includes
50 #include "tcg/tcg_macros.h"
51 
52 // STD includes
53 #include <ctime>
54 
55 //*********************************************************************************
56 //    Reverse Cells  command
57 //*********************************************************************************
58 
59 namespace {
60 
61 class ReverseUndo final : public TUndo {
62   int m_r0, m_c0, m_r1, m_c1;
63 
64 public:
ReverseUndo(int r0,int c0,int r1,int c1)65   ReverseUndo(int r0, int c0, int r1, int c1)
66       : m_r0(r0), m_c0(c0), m_r1(r1), m_c1(c1) {}
67 
68   void redo() const override;
undo() const69   void undo() const override { redo(); }  // Reverse is idempotent :)
70 
getSize() const71   int getSize() const override { return sizeof(*this); }
72 
getHistoryString()73   QString getHistoryString() override { return QObject::tr("Reverse"); }
getHistoryType()74   int getHistoryType() override { return HistoryType::Xsheet; }
75 };
76 
77 //-----------------------------------------------------------------------------
78 
redo() const79 void ReverseUndo::redo() const {
80   TCG_ASSERT(m_r1 >= m_r0 && m_c1 >= m_c0, return );
81 
82   TApp::instance()->getCurrentXsheet()->getXsheet()->reverseCells(m_r0, m_c0,
83                                                                   m_r1, m_c1);
84 
85   TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
86   TApp::instance()->getCurrentScene()->setDirtyFlag(true);
87 }
88 
89 }  // namespace
90 
91 //=============================================================================
92 
reverseCells()93 void TCellSelection::reverseCells() {
94   if (isEmpty() || areAllColSelectedLocked()) return;
95 
96   TUndo *undo =
97       new ReverseUndo(m_range.m_r0, m_range.m_c0, m_range.m_r1, m_range.m_c1);
98   TUndoManager::manager()->add(undo);
99 
100   undo->redo();
101 }
102 
103 //*********************************************************************************
104 //    Swing Cells  command
105 //*********************************************************************************
106 
107 namespace {
108 
109 class SwingUndo final : public TUndo {
110   int m_r0, m_c0, m_r1, m_c1;
111 
112 public:
SwingUndo(int r0,int c0,int r1,int c1)113   SwingUndo(int r0, int c0, int r1, int c1)
114       : m_r0(r0), m_c0(c0), m_r1(r1), m_c1(c1) {}
115 
116   void redo() const override;
117   void undo() const override;
118 
getSize() const119   int getSize() const override { return sizeof(*this); }
120 
getHistoryString()121   QString getHistoryString() override { return QObject::tr("Swing"); }
getHistoryType()122   int getHistoryType() override { return HistoryType::Xsheet; }
123 };
124 
125 //-----------------------------------------------------------------------------
126 
redo() const127 void SwingUndo::redo() const {
128   TApp::instance()->getCurrentXsheet()->getXsheet()->swingCells(m_r0, m_c0,
129                                                                 m_r1, m_c1);
130 
131   TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
132   TApp::instance()->getCurrentScene()->setDirtyFlag(true);
133 }
134 
135 //-----------------------------------------------------------------------------
136 
undo() const137 void SwingUndo::undo() const {
138   TCG_ASSERT(m_r1 >= m_r0 && m_c1 >= m_c0, return );
139 
140   for (int c = m_c0; c <= m_c1; ++c)
141     TApp::instance()->getCurrentXsheet()->getXsheet()->removeCells(m_r1 + 1, c,
142                                                                    m_r1 - m_r0);
143 
144   TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
145   TApp::instance()->getCurrentScene()->setDirtyFlag(true);
146 }
147 
148 }  // namespace
149 
150 //=============================================================================
151 
swingCells()152 void TCellSelection::swingCells() {
153   if (isEmpty() || areAllColSelectedLocked()) return;
154 
155   TUndo *undo =
156       new SwingUndo(m_range.m_r0, m_range.m_c0, m_range.m_r1, m_range.m_c1);
157   TUndoManager::manager()->add(undo);
158 
159   undo->redo();
160 }
161 
162 //*********************************************************************************
163 //    Increment Cells  command
164 //*********************************************************************************
165 
166 namespace {
167 
168 class IncrementUndo final : public TUndo {
169   int m_r0, m_c0, m_r1, m_c1;
170   mutable std::vector<std::pair<TRect, TXshCell>> m_undoCells;
171 
172 public:
173   mutable bool m_ok;
174 
175 public:
IncrementUndo(int r0,int c0,int r1,int c1)176   IncrementUndo(int r0, int c0, int r1, int c1)
177       : m_r0(r0), m_c0(c0), m_r1(r1), m_c1(c1), m_ok(true) {}
178 
179   void redo() const override;
180   void undo() const override;
181 
getSize() const182   int getSize() const override { return sizeof(*this); }
183 
getHistoryString()184   QString getHistoryString() override { return QObject::tr("Autoexpose"); }
getHistoryType()185   int getHistoryType() override { return HistoryType::Xsheet; }
186 };
187 
188 //-----------------------------------------------------------------------------
189 
redo() const190 void IncrementUndo::redo() const {
191   TCG_ASSERT(m_r1 >= m_r0 && m_c1 >= m_c0, return );
192 
193   m_undoCells.clear();
194   m_ok = TApp::instance()->getCurrentXsheet()->getXsheet()->incrementCells(
195       m_r0, m_c0, m_r1, m_c1, m_undoCells);
196 
197   TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
198   TApp::instance()->getCurrentScene()->setDirtyFlag(true);
199 }
200 
201 //-----------------------------------------------------------------------------
202 
undo() const203 void IncrementUndo::undo() const {
204   TCG_ASSERT(m_r1 >= m_r0 && m_c1 >= m_c0 && m_ok, return );
205 
206   TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
207 
208   for (int i = m_undoCells.size() - 1; i >= 0; --i) {
209     const TRect &r = m_undoCells[i].first;
210     int size       = r.x1 - r.x0 + 1;
211 
212     if (m_undoCells[i].second.isEmpty())
213       xsh->removeCells(r.x0, r.y0, size);
214     else {
215       xsh->insertCells(r.x0, r.y0, size);
216       for (int j = 0; j < size; ++j)
217         xsh->setCell(r.x0 + j, r.y0, m_undoCells[i].second);
218     }
219   }
220 
221   TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
222   TApp::instance()->getCurrentScene()->setDirtyFlag(true);
223 }
224 
225 }  // namespace
226 
227 //=============================================================================
228 
incrementCells()229 void TCellSelection::incrementCells() {
230   if (isEmpty() || areAllColSelectedLocked()) return;
231 
232   TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
233 
234   std::unique_ptr<IncrementUndo> undo(new IncrementUndo(
235       m_range.m_r0, m_range.m_c0, m_range.m_r1, m_range.m_c1));
236 
237   if (undo->redo(), !undo->m_ok) {
238     DVGui::error(
239         QObject::tr("Invalid selection: each selected column must contain one "
240                     "single level with increasing frame numbering."));
241     return;
242   }
243 
244   TUndoManager::manager()->add(undo.release());
245 }
246 
247 //*********************************************************************************
248 //    Random Cells  command
249 //*********************************************************************************
250 
251 namespace {
252 
253 class RandomUndo final : public TUndo {
254   int m_r0, m_c0, m_r1, m_c1;
255 
256   std::vector<int> m_shuffle;  //!< Shuffled indices
257   std::vector<int> m_elffuhs;  //!< Inverse shuffle indices
258 
259 public:
260   RandomUndo(int r0, int c0, int r1, int c1);
261 
262   void shuffleCells(int row, int col, const std::vector<int> &data) const;
263 
264   void redo() const override;
265   void undo() const override;
266 
getSize() const267   int getSize() const override {
268     return sizeof(*this) + 2 * sizeof(int) * m_shuffle.size();
269   }
270 
getHistoryString()271   QString getHistoryString() override { return QObject::tr("Random"); }
getHistoryType()272   int getHistoryType() override { return HistoryType::Xsheet; }
273 };
274 
275 //-----------------------------------------------------------------------------
276 
RandomUndo(int r0,int c0,int r1,int c1)277 RandomUndo::RandomUndo(int r0, int c0, int r1, int c1)
278     : m_r0(r0), m_c0(c0), m_r1(r1), m_c1(c1) {
279   TCG_ASSERT(m_r1 >= m_r0 && m_c1 >= m_c0, return );
280 
281   int r, rowCount = r1 - r0 + 1;
282   std::vector<std::pair<unsigned int, int>> rndTable(rowCount);
283 
284   TRandom rnd(std::time(0));  // Standard seeding
285   for (r = 0; r < rowCount; ++r) rndTable[r] = std::make_pair(rnd.getUInt(), r);
286 
287   std::sort(rndTable.begin(), rndTable.end());  // Random sort shuffle
288 
289   m_shuffle.resize(rowCount);
290   m_elffuhs.resize(rowCount);
291   for (r = 0; r < rowCount; ++r) {
292     m_shuffle[r]                  = rndTable[r].second;
293     m_elffuhs[rndTable[r].second] = r;
294   }
295 }
296 
297 //-----------------------------------------------------------------------------
298 
shuffleCells(int row,int col,const std::vector<int> & data) const299 void RandomUndo::shuffleCells(int row, int col,
300                               const std::vector<int> &data) const {
301   int rowCount = data.size();
302   assert(rowCount > 0);
303 
304   TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
305 
306   std::vector<TXshCell> bCells(rowCount), aCells(rowCount);
307   xsh->getCells(row, col, rowCount, &bCells[0]);
308 
309   for (int i = 0; i < rowCount; ++i) aCells[data[i]] = bCells[i];
310 
311   xsh->setCells(row, col, rowCount, &aCells[0]);
312 }
313 
314 //-----------------------------------------------------------------------------
315 
undo() const316 void RandomUndo::undo() const {
317   for (int c = m_c0; c <= m_c1; ++c) shuffleCells(m_r0, c, m_elffuhs);
318 
319   TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
320   TApp::instance()->getCurrentScene()->setDirtyFlag(true);
321 }
322 
323 //-----------------------------------------------------------------------------
324 
redo() const325 void RandomUndo::redo() const {
326   for (int c = m_c0; c <= m_c1; ++c) shuffleCells(m_r0, c, m_shuffle);
327 
328   TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
329   TApp::instance()->getCurrentScene()->setDirtyFlag(true);
330 }
331 
332 }  // namespace
333 
334 //=============================================================================
335 
randomCells()336 void TCellSelection::randomCells() {
337   if (isEmpty() || areAllColSelectedLocked()) return;
338 
339   TUndo *undo =
340       new RandomUndo(m_range.m_r0, m_range.m_c0, m_range.m_r1, m_range.m_c1);
341   TUndoManager::manager()->add(undo);
342 
343   undo->redo();
344 }
345 
346 //*********************************************************************************
347 //    Step Cells  command
348 //*********************************************************************************
349 
350 namespace {
351 
352 class StepUndo final : public TUndo {
353   int m_r0, m_c0, m_r1, m_c1;
354   int m_rowsCount, m_colsCount;
355 
356   int m_step;
357   int m_newRows;
358 
359   std::unique_ptr<TXshCell[]> m_cells;
360 
361 public:
362   StepUndo(int r0, int c0, int r1, int c1, int step);
363 
364   void redo() const override;
365   void undo() const override;
366 
getSize() const367   int getSize() const override { return sizeof(*this); }
368 
getHistoryString()369   QString getHistoryString() override {
370     return QObject::tr("Step %1").arg(QString::number(m_step));
371   }
getHistoryType()372   int getHistoryType() override { return HistoryType::Xsheet; }
373 };
374 
375 //-----------------------------------------------------------------------------
376 
StepUndo(int r0,int c0,int r1,int c1,int step)377 StepUndo::StepUndo(int r0, int c0, int r1, int c1, int step)
378     : m_r0(r0)
379     , m_c0(c0)
380     , m_r1(r1)
381     , m_c1(c1)
382     , m_rowsCount(r1 - r0 + 1)
383     , m_colsCount(c1 - c0 + 1)
384     , m_step(step)
385     , m_newRows(m_rowsCount * (step - 1))
386     , m_cells(new TXshCell[m_rowsCount * m_colsCount]) {
387   assert(m_rowsCount > 0 && m_colsCount > 0 && step > 0);
388   assert(m_cells);
389 
390   int k = 0;
391   for (int r = r0; r <= r1; ++r)
392     for (int c = c0; c <= c1; ++c)
393       m_cells[k++] =
394           TApp::instance()->getCurrentXsheet()->getXsheet()->getCell(r, c);
395 }
396 
397 //-----------------------------------------------------------------------------
398 
redo() const399 void StepUndo::redo() const {
400   TCG_ASSERT(m_rowsCount > 0 && m_colsCount > 0, return );
401 
402   TApp::instance()->getCurrentXsheet()->getXsheet()->stepCells(m_r0, m_c0, m_r1,
403                                                                m_c1, m_step);
404 
405   TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
406   TApp::instance()->getCurrentScene()->setDirtyFlag(true);
407 }
408 
409 //-----------------------------------------------------------------------------
410 
undo() const411 void StepUndo::undo() const {
412   TCG_ASSERT(m_rowsCount > 0 && m_colsCount > 0 && m_cells, return );
413 
414   TApp *app    = TApp::instance();
415   TXsheet *xsh = app->getCurrentXsheet()->getXsheet();
416 
417   for (int c = m_c0; c <= m_c1; ++c) xsh->removeCells(m_r1 + 1, c, m_newRows);
418 
419   int k = 0;
420   for (int r = m_r0; r <= m_r1; ++r)
421     for (int c = m_c0; c <= m_c1; ++c) {
422       if (m_cells[k].isEmpty())
423         xsh->clearCells(r, c);
424       else
425         xsh->setCell(r, c, m_cells[k]);
426       k++;
427     }
428   app->getCurrentXsheet()->notifyXsheetChanged();
429   app->getCurrentScene()->setDirtyFlag(true);
430 }
431 
432 }  // namespace
433 
434 //=============================================================================
435 
stepCells(int step)436 void TCellSelection::stepCells(int step) {
437   if (isEmpty() || areAllColSelectedLocked()) return;
438 
439   TUndo *undo = new StepUndo(m_range.m_r0, m_range.m_c0, m_range.m_r1,
440                              m_range.m_c1, step);
441   TUndoManager::manager()->add(undo);
442 
443   undo->redo();
444   m_range.m_r1 += (step - 1) * m_range.getRowCount();
445 }
446 
447 //*********************************************************************************
448 //    Each Cells  command
449 //*********************************************************************************
450 
451 namespace {
452 
453 class EachUndo final : public TUndo {
454   int m_r0, m_c0, m_r1, m_c1;
455   int m_rowsCount, m_colsCount;
456 
457   int m_each;
458   int m_newRows;
459 
460   std::unique_ptr<TXshCell[]> m_cells;
461 
462 public:
463   EachUndo(int r0, int c0, int r1, int c1, int each);
464 
465   void redo() const override;
466   void undo() const override;
467 
getSize() const468   int getSize() const override { return sizeof(*this); }
469 
getHistoryString()470   QString getHistoryString() override {
471     return QObject::tr("Each %1").arg(QString::number(m_each));
472   }
getHistoryType()473   int getHistoryType() override { return HistoryType::Xsheet; }
474 };
475 
476 //-----------------------------------------------------------------------------
477 
EachUndo(int r0,int c0,int r1,int c1,int each)478 EachUndo::EachUndo(int r0, int c0, int r1, int c1, int each)
479     : m_r0(r0)
480     , m_c0(c0)
481     , m_r1(r1)
482     , m_c1(c1)
483     , m_rowsCount(r1 - r0 + 1)
484     , m_colsCount(c1 - c0 + 1)
485     , m_each(each)
486     , m_newRows((m_rowsCount % each) ? m_rowsCount / each + 1
487                                      : m_rowsCount / each)
488     , m_cells(new TXshCell[m_rowsCount * m_colsCount]) {
489   assert(m_rowsCount > 0 && m_colsCount > 0 && each > 0);
490   assert(m_cells);
491 
492   int k = 0;
493   for (int r = r0; r <= r1; ++r)
494     for (int c = c0; c <= c1; ++c)
495       m_cells[k++] =
496           TApp::instance()->getCurrentXsheet()->getXsheet()->getCell(r, c);
497 }
498 
499 //-----------------------------------------------------------------------------
500 
redo() const501 void EachUndo::redo() const {
502   TCG_ASSERT(m_rowsCount > 0 && m_colsCount > 0, return );
503 
504   TApp::instance()->getCurrentXsheet()->getXsheet()->eachCells(m_r0, m_c0, m_r1,
505                                                                m_c1, m_each);
506 
507   TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
508   TApp::instance()->getCurrentScene()->setDirtyFlag(true);
509 }
510 
511 //-----------------------------------------------------------------------------
512 
undo() const513 void EachUndo::undo() const {
514   TCG_ASSERT(m_rowsCount > 0 && m_colsCount > 0 && m_cells, return );
515 
516   TApp *app    = TApp::instance();
517   TXsheet *xsh = app->getCurrentXsheet()->getXsheet();
518 
519   for (int c = m_c0; c <= m_c1; ++c)
520     xsh->insertCells(m_r0 + m_newRows, c, m_rowsCount - m_newRows);
521 
522   int k = 0;
523   for (int r = m_r0; r <= m_r1; ++r)
524     for (int c = m_c0; c <= m_c1; ++c) {
525       if (m_cells[k].isEmpty())
526         xsh->clearCells(r, c);
527       else
528         xsh->setCell(r, c, m_cells[k]);
529       k++;
530     }
531 
532   app->getCurrentXsheet()->notifyXsheetChanged();
533   app->getCurrentScene()->setDirtyFlag(true);
534 }
535 
536 }  // namespace
537 
538 //=============================================================================
539 
eachCells(int each)540 void TCellSelection::eachCells(int each) {
541   if (isEmpty() || areAllColSelectedLocked()) return;
542 
543   TUndo *undo = new EachUndo(m_range.m_r0, m_range.m_c0, m_range.m_r1,
544                              m_range.m_c1, each);
545   TUndoManager::manager()->add(undo);
546 
547   undo->redo();
548   m_range.m_r1 = m_range.m_r0 + (m_range.m_r1 - m_range.m_r0 + each) / each - 1;
549 }
550 
551 //*********************************************************************************
552 //    Reframe command : 強制的にNコマ打ちにする
553 //*********************************************************************************
554 
555 namespace {
556 
557 class ReframeUndo final : public TUndo {
558   int m_r0, m_r1;
559   int m_type;
560   int m_nr;
561   int m_withBlank;
562   std::unique_ptr<TXshCell[]> m_cells;
563 
564 public:
565   std::vector<int> m_newRows;
566 
567   std::vector<int> m_columnIndeces;
568 
569   ReframeUndo(int r0, int r1, std::vector<int> columnIndeces, int type,
570               int withBlank = -1);
571   ~ReframeUndo();
572   void undo() const override;
573   void redo() const override;
574   void repeat() const;
575 
getSize() const576   int getSize() const override { return sizeof(*this); }
577 
getHistoryString()578   QString getHistoryString() override {
579     if (m_withBlank == -1)
580       return QObject::tr("Reframe to %1's").arg(QString::number(m_type));
581     else
582       return QObject::tr("Reframe to %1's with %2 blanks")
583           .arg(QString::number(m_type))
584           .arg(QString::number(m_withBlank));
585   }
getHistoryType()586   int getHistoryType() override { return HistoryType::Xsheet; }
587 };
588 
589 //-----------------------------------------------------------------------------
590 
ReframeUndo(int r0,int r1,std::vector<int> columnIndeces,int type,int withBlank)591 ReframeUndo::ReframeUndo(int r0, int r1, std::vector<int> columnIndeces,
592                          int type, int withBlank)
593     : m_r0(r0)
594     , m_r1(r1)
595     , m_type(type)
596     , m_nr(0)
597     , m_columnIndeces(columnIndeces)
598     , m_withBlank(withBlank) {
599   m_nr = m_r1 - m_r0 + 1;
600   assert(m_nr > 0);
601   m_cells.reset(new TXshCell[m_nr * (int)m_columnIndeces.size()]);
602   assert(m_cells);
603   int k = 0;
604   for (int r = r0; r <= r1; r++)
605     for (int c     = 0; c < (int)m_columnIndeces.size(); c++)
606       m_cells[k++] = TApp::instance()->getCurrentXsheet()->getXsheet()->getCell(
607           r, m_columnIndeces[c]);
608 
609   m_newRows.clear();
610 }
611 
612 //-----------------------------------------------------------------------------
613 
~ReframeUndo()614 ReframeUndo::~ReframeUndo() {}
615 
616 //-----------------------------------------------------------------------------
617 
undo() const618 void ReframeUndo::undo() const {
619   TApp *app    = TApp::instance();
620   TXsheet *xsh = app->getCurrentXsheet()->getXsheet();
621   int rowCount = m_r1 - m_r0;
622   if (rowCount < 0 || m_columnIndeces.size() < 1) return;
623 
624   for (int c = 0; c < m_columnIndeces.size(); c++) {
625     /*-- コマンド後に縮んだカラムはその分引き伸ばす --*/
626     if (m_newRows[c] < m_nr)
627       xsh->insertCells(m_r0 + m_newRows[c], m_columnIndeces[c],
628                        m_nr - m_newRows[c]);
629     /*-- コマンド後に延びたカラムはその分縮める --*/
630     else if (m_newRows[c] > m_nr)
631       xsh->removeCells(m_r1 + 1, m_columnIndeces[c], m_newRows[c] - m_nr);
632   }
633 
634   if (m_cells) {
635     int k = 0;
636     for (int r = m_r0; r <= m_r1; r++)
637       for (int c = 0; c < m_columnIndeces.size(); c++) {
638         if (m_cells[k].isEmpty())
639           xsh->clearCells(r, m_columnIndeces[c]);
640         else
641           xsh->setCell(r, m_columnIndeces[c], m_cells[k]);
642         k++;
643       }
644   }
645   app->getCurrentXsheet()->notifyXsheetChanged();
646 }
647 
648 //-----------------------------------------------------------------------------
649 
redo() const650 void ReframeUndo::redo() const {
651   if (m_r1 - m_r0 < 0 || m_columnIndeces.size() < 1) return;
652 
653   TApp *app = TApp::instance();
654 
655   for (int c = 0; c < m_columnIndeces.size(); c++)
656     app->getCurrentXsheet()->getXsheet()->reframeCells(
657         m_r0, m_r1, m_columnIndeces[c], m_type, m_withBlank);
658 
659   app->getCurrentXsheet()->notifyXsheetChanged();
660 }
661 
662 //-----------------------------------------------------------------------------
663 
repeat() const664 void ReframeUndo::repeat() const {}
665 
666 }  // namespace
667 
668 //=============================================================================
669 
reframeCells(int count)670 void TCellSelection::reframeCells(int count) {
671   if (isEmpty() || areAllColSelectedLocked()) return;
672 
673   std::vector<int> colIndeces;
674   for (int c = m_range.m_c0; c <= m_range.m_c1; c++) colIndeces.push_back(c);
675 
676   ReframeUndo *undo =
677       new ReframeUndo(m_range.m_r0, m_range.m_r1, colIndeces, count);
678 
679   for (int c = m_range.m_c0; c <= m_range.m_c1; c++) {
680     int nrows = TApp::instance()->getCurrentXsheet()->getXsheet()->reframeCells(
681         m_range.m_r0, m_range.m_r1, c, count);
682     undo->m_newRows.push_back(nrows);
683   }
684 
685   TUndoManager::manager()->add(undo);
686 
687   TApp::instance()->getCurrentScene()->setDirtyFlag(true);
688   TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
689 }
690 
reframeCells(int count)691 void TColumnSelection::reframeCells(int count) {
692   if (isEmpty()) return;
693 
694   int rowCount =
695       TApp::instance()->getCurrentXsheet()->getXsheet()->getFrameCount();
696   std::vector<int> colIndeces;
697   std::set<int>::const_iterator it;
698   for (it = m_indices.begin(); it != m_indices.end(); it++)
699     colIndeces.push_back(*it);
700 
701   ReframeUndo *undo = new ReframeUndo(0, rowCount - 1, colIndeces, count);
702 
703   for (int c = 0; c < (int)colIndeces.size(); c++) {
704     int nrows = TApp::instance()->getCurrentXsheet()->getXsheet()->reframeCells(
705         0, rowCount - 1, colIndeces[c], count);
706     undo->m_newRows.push_back(nrows);
707   }
708 
709   TUndoManager::manager()->add(undo);
710 
711   TApp::instance()->getCurrentScene()->setDirtyFlag(true);
712   TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
713 }
714 
715 //=============================================================================
716 
reframeWithEmptyInbetweens()717 void TCellSelection::reframeWithEmptyInbetweens() {
718   if (isEmpty() || areAllColSelectedLocked()) return;
719 
720   std::vector<int> colIndeces;
721   for (int c = m_range.m_c0; c <= m_range.m_c1; c++) colIndeces.push_back(c);
722 
723   // destruction of m_reframePopup will be done along with the main window
724   if (!m_reframePopup) m_reframePopup = new ReframePopup();
725   int ret                             = m_reframePopup->exec();
726   if (ret == QDialog::Rejected) return;
727 
728   int step, blank;
729   m_reframePopup->getValues(step, blank);
730 
731   ReframeUndo *undo =
732       new ReframeUndo(m_range.m_r0, m_range.m_r1, colIndeces, step, blank);
733 
734   int maximumRow = 0;
735   for (int c = m_range.m_c0; c <= m_range.m_c1; c++) {
736     int nrows = TApp::instance()->getCurrentXsheet()->getXsheet()->reframeCells(
737         m_range.m_r0, m_range.m_r1, c, step, blank);
738     undo->m_newRows.push_back(nrows);
739     if (maximumRow < nrows) maximumRow = nrows;
740   }
741 
742   if (maximumRow == 0) {
743     delete undo;
744     return;
745   }
746 
747   TUndoManager::manager()->add(undo);
748 
749   // select reframed range
750   selectCells(m_range.m_r0, m_range.m_c0, m_range.m_r0 + maximumRow - 1,
751               m_range.m_c1);
752 
753   TApp::instance()->getCurrentScene()->setDirtyFlag(true);
754   TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
755 }
756 
reframeWithEmptyInbetweens()757 void TColumnSelection::reframeWithEmptyInbetweens() {
758   if (isEmpty()) return;
759 
760   int rowCount =
761       TApp::instance()->getCurrentXsheet()->getXsheet()->getFrameCount();
762   std::vector<int> colIndeces;
763   std::set<int>::const_iterator it;
764   for (it = m_indices.begin(); it != m_indices.end(); it++)
765     colIndeces.push_back(*it);
766 
767   if (!m_reframePopup) m_reframePopup = new ReframePopup();
768   int ret                             = m_reframePopup->exec();
769   if (ret == QDialog::Rejected) return;
770 
771   int step, blank;
772   m_reframePopup->getValues(step, blank);
773 
774   ReframeUndo *undo = new ReframeUndo(0, rowCount - 1, colIndeces, step, blank);
775 
776   bool commandExecuted = false;
777   for (int c = 0; c < (int)colIndeces.size(); c++) {
778     int nrows = TApp::instance()->getCurrentXsheet()->getXsheet()->reframeCells(
779         0, rowCount - 1, colIndeces[c], step, blank);
780     undo->m_newRows.push_back(nrows);
781     if (nrows > 0) commandExecuted = true;
782   }
783 
784   if (!commandExecuted) {
785     delete undo;
786     return;
787   }
788 
789   TUndoManager::manager()->add(undo);
790 
791   TApp::instance()->getCurrentScene()->setDirtyFlag(true);
792   TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
793 }
794 
795 //*********************************************************************************
796 //    Reset Step Cells  command
797 //*********************************************************************************
798 
799 namespace {
800 
801 class ResetStepUndo final : public TUndo {
802   int m_r0, m_c0, m_r1, m_c1;
803   int m_rowsCount, m_colsCount;
804 
805   std::unique_ptr<TXshCell[]> m_cells;
806   QMap<int, int> m_insertedCells;  //!< Count of inserted cells, by column
807 
808 public:
809   ResetStepUndo(int r0, int c0, int r1, int c1);
810 
811   void redo() const override;
812   void undo() const override;
813 
getSize() const814   int getSize() const override { return sizeof(*this); }
815 };
816 
817 //-----------------------------------------------------------------------------
818 
ResetStepUndo(int r0,int c0,int r1,int c1)819 ResetStepUndo::ResetStepUndo(int r0, int c0, int r1, int c1)
820     : m_r0(r0)
821     , m_c0(c0)
822     , m_r1(r1)
823     , m_c1(c1)
824     , m_rowsCount(m_r1 - m_r0 + 1)
825     , m_colsCount(m_c1 - m_c0 + 1)
826     , m_cells(new TXshCell[m_rowsCount * m_colsCount]) {
827   assert(m_rowsCount > 0 && m_colsCount > 0);
828   assert(m_cells);
829 
830   TApp *app = TApp::instance();
831 
832   int k = 0;
833   for (int c = c0; c <= c1; ++c) {
834     TXshCell prevCell;
835     m_insertedCells[c] = 0;
836 
837     for (int r = r0; r <= r1; ++r) {
838       const TXshCell &cell =
839           app->getCurrentXsheet()->getXsheet()->getCell(r, c);
840       m_cells[k++] = cell;
841 
842       if (prevCell != cell) {
843         prevCell = cell;
844         m_insertedCells[c]++;
845       }
846     }
847   }
848 }
849 
850 //-----------------------------------------------------------------------------
851 
redo() const852 void ResetStepUndo::redo() const {
853   TCG_ASSERT(m_rowsCount > 0 && m_colsCount > 0, return );
854 
855   TApp::instance()->getCurrentXsheet()->getXsheet()->resetStepCells(m_r0, m_c0,
856                                                                     m_r1, m_c1);
857 
858   TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
859   TApp::instance()->getCurrentScene()->setDirtyFlag(true);
860 }
861 
862 //-----------------------------------------------------------------------------
863 
undo() const864 void ResetStepUndo::undo() const {
865   TApp *app    = TApp::instance();
866   TXsheet *xsh = app->getCurrentXsheet()->getXsheet();
867 
868   int k = 0;
869   for (int c = m_c0; c <= m_c1; ++c) {
870     xsh->removeCells(m_r0, c, m_insertedCells[c]);
871 
872     xsh->insertCells(m_r0, c, m_rowsCount);
873     for (int r = m_r0; r <= m_r1; ++r) xsh->setCell(r, c, m_cells[k++]);
874   }
875 
876   app->getCurrentXsheet()->notifyXsheetChanged();
877   TApp::instance()->getCurrentScene()->setDirtyFlag(true);
878 }
879 
880 }  // namespace
881 
882 //=============================================================================
883 
resetStepCells()884 void TCellSelection::resetStepCells() {
885   if (isEmpty() || areAllColSelectedLocked()) return;
886 
887   TUndo *undo =
888       new ResetStepUndo(m_range.m_r0, m_range.m_c0, m_range.m_r1, m_range.m_c1);
889   TUndoManager::manager()->add(undo);
890 
891   undo->redo();
892 }
893 
894 //*********************************************************************************
895 //    Increase Step Cells  command
896 //*********************************************************************************
897 
898 namespace {
899 
900 class IncreaseStepUndo final : public TUndo {
901   int m_r0, m_c0, m_r1, m_c1;
902   int m_rowsCount, m_colsCount;
903 
904   std::unique_ptr<TXshCell[]> m_cells;
905   QMap<int, int> m_insertedCells;
906 
907 public:
908   mutable int m_newR1;  //!< r1 updated by TXsheet::increaseStepCells()
909 
910 public:
911   IncreaseStepUndo(int r0, int c0, int r1, int c1);
912 
913   void redo() const override;
914   void undo() const override;
915 
getSize() const916   int getSize() const override { return sizeof(*this); }
917 };
918 
919 //-----------------------------------------------------------------------------
920 
IncreaseStepUndo(int r0,int c0,int r1,int c1)921 IncreaseStepUndo::IncreaseStepUndo(int r0, int c0, int r1, int c1)
922     : m_r0(r0)
923     , m_c0(c0)
924     , m_r1(r1)
925     , m_c1(c1)
926     , m_rowsCount(m_r1 - m_r0 + 1)
927     , m_colsCount(m_c1 - m_c0 + 1)
928     , m_cells(new TXshCell[m_rowsCount * m_colsCount])
929     , m_newR1(m_r1) {
930   assert(m_cells);
931 
932   int k = 0;
933   for (int c = c0; c <= c1; ++c) {
934     TXshCell prevCell;
935     m_insertedCells[c] = 0;
936 
937     for (int r = r0; r <= r1; ++r) {
938       const TXshCell &cell =
939           TApp::instance()->getCurrentXsheet()->getXsheet()->getCell(r, c);
940       m_cells[k++] = cell;
941 
942       if (prevCell != cell) {
943         prevCell = cell;
944         m_insertedCells[c]++;
945       }
946     }
947   }
948 }
949 
950 //-----------------------------------------------------------------------------
951 
redo() const952 void IncreaseStepUndo::redo() const {
953   m_newR1 = m_r1;
954   TApp::instance()->getCurrentXsheet()->getXsheet()->increaseStepCells(
955       m_r0, m_c0, m_newR1, m_c1);
956 
957   TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
958   TApp::instance()->getCurrentScene()->setDirtyFlag(true);
959 }
960 
961 //-----------------------------------------------------------------------------
962 
undo() const963 void IncreaseStepUndo::undo() const {
964   TApp *app    = TApp::instance();
965   TXsheet *xsh = app->getCurrentXsheet()->getXsheet();
966 
967   int k = 0;
968   for (int c = m_c0; c <= m_c1; ++c) {
969     xsh->removeCells(m_r0, c, m_rowsCount + m_insertedCells[c]);
970 
971     xsh->insertCells(m_r0, c, m_rowsCount);
972     for (int r = m_r0; r <= m_r1; ++r) xsh->setCell(r, c, m_cells[k++]);
973   }
974 
975   app->getCurrentXsheet()->notifyXsheetChanged();
976   TApp::instance()->getCurrentScene()->setDirtyFlag(true);
977 }
978 
979 }  // namespace
980 
981 //=============================================================================
982 
increaseStepCells()983 void TCellSelection::increaseStepCells() {
984   if (isEmpty()) {
985     int row = TTool::getApplication()->getCurrentFrame()->getFrame();
986     int col = TTool::getApplication()->getCurrentColumn()->getColumnIndex();
987     TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
988     m_range.m_r0 = row;
989     m_range.m_r1 = row;
990     m_range.m_c0 = col;
991     m_range.m_c1 = col;
992     TXshCell cell;
993     cell = xsh->getCell(row, col);
994     if (cell.isEmpty()) return;
995   }
996   if (areAllColSelectedLocked()) return;
997 
998   IncreaseStepUndo *undo = new IncreaseStepUndo(m_range.m_r0, m_range.m_c0,
999                                                 m_range.m_r1, m_range.m_c1);
1000   TUndoManager::manager()->add(undo);
1001 
1002   undo->redo();
1003 
1004   if (undo->m_newR1 != m_range.m_r1) {
1005     m_range.m_r1 = undo->m_newR1;
1006     TApp::instance()->getCurrentSelection()->notifySelectionChanged();
1007   }
1008 }
1009 
1010 //*********************************************************************************
1011 //    Decrease Step Cells  command
1012 //*********************************************************************************
1013 
1014 namespace {
1015 
1016 class DecreaseStepUndo final : public TUndo {
1017   int m_r0, m_c0, m_r1, m_c1;
1018   int m_rowsCount, m_colsCount;
1019 
1020   std::unique_ptr<TXshCell[]> m_cells;
1021   QMap<int, int> m_removedCells;
1022 
1023 public:
1024   mutable int m_newR1;  //!< r1 updated by TXsheet::decreaseStepCells()
1025 
1026 public:
1027   DecreaseStepUndo(int r0, int c0, int r1, int c1);
1028 
1029   void redo() const override;
1030   void undo() const override;
1031 
getSize() const1032   int getSize() const override { return sizeof(*this); }
1033 };
1034 
1035 //-----------------------------------------------------------------------------
1036 
DecreaseStepUndo(int r0,int c0,int r1,int c1)1037 DecreaseStepUndo::DecreaseStepUndo(int r0, int c0, int r1, int c1)
1038     : m_r0(r0)
1039     , m_c0(c0)
1040     , m_r1(r1)
1041     , m_c1(c1)
1042     , m_rowsCount(m_r1 - m_r0 + 1)
1043     , m_colsCount(m_c1 - m_c0 + 1)
1044     , m_cells(new TXshCell[m_rowsCount * m_colsCount])
1045     , m_newR1(m_r1) {
1046   assert(m_cells);
1047 
1048   int k = 0;
1049   for (int c = c0; c <= c1; ++c) {
1050     TXshCell prevCell =
1051         TApp::instance()->getCurrentXsheet()->getXsheet()->getCell(r0, c);
1052     m_removedCells[c] = 0;
1053 
1054     bool removed = false;
1055     m_cells[k++] = prevCell;
1056 
1057     for (int r = r0 + 1; r <= r1; ++r) {
1058       const TXshCell &cell =
1059           TApp::instance()->getCurrentXsheet()->getXsheet()->getCell(r, c);
1060       m_cells[k++] = cell;
1061 
1062       if (prevCell == cell) {
1063         if (!removed) {
1064           removed = true;
1065           m_removedCells[c]++;
1066         }
1067       } else {
1068         removed  = false;
1069         prevCell = cell;
1070       }
1071     }
1072   }
1073 }
1074 
1075 //-----------------------------------------------------------------------------
1076 
redo() const1077 void DecreaseStepUndo::redo() const {
1078   m_newR1 = m_r1;
1079   TApp::instance()->getCurrentXsheet()->getXsheet()->decreaseStepCells(
1080       m_r0, m_c0, m_newR1, m_c1);
1081 
1082   TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
1083   TApp::instance()->getCurrentScene()->setDirtyFlag(true);
1084 }
1085 
1086 //-----------------------------------------------------------------------------
1087 
undo() const1088 void DecreaseStepUndo::undo() const {
1089   TApp *app    = TApp::instance();
1090   TXsheet *xsh = app->getCurrentXsheet()->getXsheet();
1091 
1092   int k = 0;
1093   for (int c = m_c0; c <= m_c1; ++c) {
1094     xsh->removeCells(m_r0, c, m_rowsCount - m_removedCells[c]);
1095 
1096     xsh->insertCells(m_r0, c, m_rowsCount);
1097     for (int r = m_r0; r <= m_r1; ++r) xsh->setCell(r, c, m_cells[k++]);
1098   }
1099 
1100   app->getCurrentXsheet()->notifyXsheetChanged();
1101   app->getCurrentScene()->setDirtyFlag(true);
1102 }
1103 
1104 }  // namespace
1105 
1106 //=============================================================================
1107 
decreaseStepCells()1108 void TCellSelection::decreaseStepCells() {
1109   if (isEmpty()) {
1110     int row = TTool::getApplication()->getCurrentFrame()->getFrame();
1111     int col = TTool::getApplication()->getCurrentColumn()->getColumnIndex();
1112     int r1  = row;
1113     TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
1114     TXshCell cell;
1115     TXshCell nextCell;
1116     bool sameCells = true;
1117     cell           = xsh->getCell(row, col);
1118     if (cell.isEmpty()) return;
1119 
1120     for (int i = 1; sameCells; i++) {
1121       nextCell = xsh->getCell(row + i, col);
1122       if (nextCell.m_frameId == cell.m_frameId &&
1123           nextCell.m_level == cell.m_level) {
1124         r1 = row + i;
1125       } else
1126         sameCells = false;
1127     }
1128     m_range.m_r0 = row;
1129     m_range.m_r1 = r1;
1130     m_range.m_c0 = col;
1131     m_range.m_c1 = col;
1132     TApp::instance()->getCurrentSelection()->notifySelectionChanged();
1133   }
1134   DecreaseStepUndo *undo = new DecreaseStepUndo(m_range.m_r0, m_range.m_c0,
1135                                                 m_range.m_r1, m_range.m_c1);
1136   TUndoManager::manager()->add(undo);
1137 
1138   undo->redo();
1139 
1140   if (undo->m_newR1 != m_range.m_r1) {
1141     m_range.m_r1 = undo->m_newR1;
1142     TApp::instance()->getCurrentSelection()->notifySelectionChanged();
1143   }
1144 }
1145 
1146 //*********************************************************************************
1147 //    Rollup Cells  command
1148 //*********************************************************************************
1149 
1150 namespace {
1151 
1152 class RollupUndo : public TUndo {
1153   int m_r0, m_c0, m_r1, m_c1;
1154 
1155 public:
RollupUndo(int r0,int c0,int r1,int c1)1156   RollupUndo(int r0, int c0, int r1, int c1)
1157       : m_r0(r0), m_c0(c0), m_r1(r1), m_c1(c1) {}
1158 
redo() const1159   void redo() const override {
1160     TApp *app    = TApp::instance();
1161     TXsheet *xsh = app->getCurrentXsheet()->getXsheet();
1162 
1163     xsh->rollupCells(m_r0, m_c0, m_r1, m_c1);
1164 
1165     app->getCurrentXsheet()->notifyXsheetChanged();
1166     app->getCurrentScene()->setDirtyFlag(true);
1167   }
1168 
undo() const1169   void undo() const override {
1170     TApp *app    = TApp::instance();
1171     TXsheet *xsh = app->getCurrentXsheet()->getXsheet();
1172 
1173     xsh->rolldownCells(m_r0, m_c0, m_r1, m_c1);
1174 
1175     app->getCurrentXsheet()->notifyXsheetChanged();
1176     app->getCurrentScene()->setDirtyFlag(true);
1177   }
1178 
getSize() const1179   int getSize() const override { return sizeof(*this); }
1180 
getHistoryString()1181   QString getHistoryString() override { return QObject::tr("Roll Up"); }
getHistoryType()1182   int getHistoryType() override { return HistoryType::Xsheet; }
1183 };
1184 
1185 }  // namespace
1186 
1187 //=============================================================================
1188 
rollupCells()1189 void TCellSelection::rollupCells() {
1190   TUndo *undo =
1191       new RollupUndo(m_range.m_r0, m_range.m_c0, m_range.m_r1, m_range.m_c1);
1192   TUndoManager::manager()->add(undo);
1193 
1194   undo->redo();
1195 }
1196 
1197 //*********************************************************************************
1198 //    Rolldown Cells  command
1199 //*********************************************************************************
1200 
1201 namespace {
1202 
1203 class RolldownUndo final : public RollupUndo {
1204 public:
RolldownUndo(int r0,int c0,int r1,int c1)1205   RolldownUndo(int r0, int c0, int r1, int c1) : RollupUndo(r0, c0, r1, c1) {}
1206 
redo() const1207   void redo() const override { RollupUndo::undo(); }
undo() const1208   void undo() const override { RollupUndo::redo(); }
1209 
getHistoryString()1210   QString getHistoryString() override { return QObject::tr("Roll Down"); }
1211 };
1212 
1213 }  // namespace
1214 
1215 //=============================================================================
1216 
rolldownCells()1217 void TCellSelection::rolldownCells() {
1218   TUndo *undo =
1219       new RolldownUndo(m_range.m_r0, m_range.m_c0, m_range.m_r1, m_range.m_c1);
1220   TUndoManager::manager()->add(undo);
1221 
1222   undo->redo();
1223 }
1224 
1225 //*********************************************************************************
1226 //    Set Keyframes  command
1227 //*********************************************************************************
1228 
setKeyframes()1229 void TCellSelection::setKeyframes() {
1230   if (isEmpty()) return;
1231 
1232   // Preliminary data-fetching
1233   TApp *app = TApp::instance();
1234 
1235   TXsheetHandle *xshHandle = app->getCurrentXsheet();
1236   TXsheet *xsh             = xshHandle->getXsheet();
1237 
1238   int row = m_range.m_r0, col = m_range.m_c0;
1239 
1240   const TXshCell &cell = xsh->getCell(row, col);
1241   if (cell.getSoundLevel() || cell.getSoundTextLevel()) return;
1242 
1243   const TStageObjectId &id =
1244       col >= 0 ? TStageObjectId::ColumnId(col)
1245                : TStageObjectId::CameraId(xsh->getCameraColumnIndex());
1246 
1247   TStageObject *obj = xsh->getStageObject(id);
1248   if (!obj) return;
1249 
1250   // Command body
1251   if (obj->isFullKeyframe(row)) {
1252     const TStageObject::Keyframe &key = obj->getKeyframe(row);
1253 
1254     UndoRemoveKeyFrame *undo = new UndoRemoveKeyFrame(id, row, key, xshHandle);
1255     undo->setObjectHandle(app->getCurrentObject());
1256 
1257     TUndoManager::manager()->add(undo);
1258     undo->redo();
1259   } else {
1260     UndoSetKeyFrame *undo = new UndoSetKeyFrame(id, row, xshHandle);
1261     undo->setObjectHandle(app->getCurrentObject());
1262 
1263     TUndoManager::manager()->add(undo);
1264     undo->redo();
1265   }
1266 
1267   TApp::instance()->getCurrentScene()->setDirtyFlag(
1268       true);  // Should be moved inside the undos!
1269 }
1270 
1271 //*********************************************************************************
1272 //    Clone Level  command
1273 //*********************************************************************************
1274 
1275 namespace {
1276 
1277 class CloneLevelUndo final : public TUndo {
1278   typedef std::map<TXshSimpleLevel *, TXshLevelP> InsertedLevelsMap;
1279   typedef std::set<int> InsertedColumnsSet;
1280 
1281   struct ExistsFunc;
1282   class LevelNamePopup;
1283 
1284 private:
1285   TCellSelection::Range m_range;
1286 
1287   mutable InsertedLevelsMap m_insertedLevels;
1288   mutable InsertedColumnsSet m_insertedColumns;
1289   mutable bool m_clonedLevels;
1290 
1291 public:
1292   mutable bool m_ok;
1293 
1294 public:
CloneLevelUndo(const TCellSelection::Range & range)1295   CloneLevelUndo(const TCellSelection::Range &range)
1296       : m_range(range), m_clonedLevels(false), m_ok(false) {}
1297 
1298   void redo() const override;
1299   void undo() const override;
1300 
getSize() const1301   int getSize() const override {
1302     return sizeof *this +
1303            (sizeof(TXshLevelP) + sizeof(TXshSimpleLevel *)) *
1304                m_insertedLevels.size();
1305   }
1306 
getHistoryString()1307   QString getHistoryString() override {
1308     if (m_insertedLevels.empty()) return QString();
1309     QString str;
1310     if (m_insertedLevels.size() == 1) {
1311       str = QObject::tr("Clone  Level : %1 > %2")
1312                 .arg(QString::fromStdWString(
1313                     m_insertedLevels.begin()->first->getName()))
1314                 .arg(QString::fromStdWString(
1315                     m_insertedLevels.begin()->second->getName()));
1316     } else {
1317       str = QObject::tr("Clone  Levels : ");
1318       std::map<TXshSimpleLevel *, TXshLevelP>::const_iterator it =
1319           m_insertedLevels.begin();
1320       for (; it != m_insertedLevels.end(); ++it) {
1321         str += QString("%1 > %2, ")
1322                    .arg(QString::fromStdWString(it->first->getName()))
1323                    .arg(QString::fromStdWString(it->second->getName()));
1324       }
1325     }
1326     return str;
1327   }
getHistoryType()1328   int getHistoryType() override { return HistoryType::Xsheet; }
1329 
1330 private:
1331   TXshSimpleLevel *cloneLevel(const TXshSimpleLevel *srcSl,
1332                               const TFilePath &dstPath,
1333                               const std::set<TFrameId> &frames) const;
1334 
1335   bool chooseLevelName(TFilePath &fp) const;
1336   bool chooseOverwrite(OverwriteDialog *dialog, TFilePath &dstPath,
1337                        TXshSimpleLevel *&dstSl) const;
1338 
1339   void cloneLevels() const;
1340   void insertLevels() const;
1341   void insertCells() const;
1342 };
1343 
1344 //-----------------------------------------------------------------------------
1345 
1346 struct CloneLevelUndo::ExistsFunc final : public OverwriteDialog::ExistsFunc {
1347   ToonzScene *m_scene;
1348 
1349 public:
ExistsFunc__anonbf6bb6d40d11::CloneLevelUndo::ExistsFunc1350   ExistsFunc(ToonzScene *scene) : m_scene(scene) {}
1351 
conflictString__anonbf6bb6d40d11::CloneLevelUndo::ExistsFunc1352   QString conflictString(const TFilePath &fp) const override {
1353     return OverwriteDialog::tr(
1354                "Level \"%1\" already exists.\n\nWhat do you want to do?")
1355         .arg(QString::fromStdWString(fp.withoutParentDir().getWideString()));
1356   }
1357 
operator ()__anonbf6bb6d40d11::CloneLevelUndo::ExistsFunc1358   bool operator()(const TFilePath &fp) const override {
1359     return TSystem::doesExistFileOrLevel(fp) ||
1360            m_scene->getLevelSet()->getLevel(*m_scene, fp);
1361   }
1362 };
1363 
1364 //-----------------------------------------------------------------------------
1365 
1366 class CloneLevelUndo::LevelNamePopup final : public DVGui::Dialog {
1367   DVGui::LineEdit *m_name;
1368   QPushButton *m_ok, *m_cancel;
1369 
1370 public:
LevelNamePopup(const std::wstring & defaultLevelName)1371   LevelNamePopup(const std::wstring &defaultLevelName)
1372       : DVGui::Dialog(TApp::instance()->getMainWindow(), true, true,
1373                       "Clone Level") {
1374     setWindowTitle(
1375         QObject::tr("Clone Level", "CloneLevelUndo::LevelNamePopup"));
1376 
1377     beginHLayout();
1378 
1379     QLabel *label = new QLabel(
1380         QObject::tr("Level Name:", "CloneLevelUndo::LevelNamePopup"));
1381     addWidget(label);
1382 
1383     m_name = new DVGui::LineEdit;
1384     addWidget(m_name);
1385 
1386     m_name->setText(QString::fromStdWString(defaultLevelName));
1387 
1388     endHLayout();
1389 
1390     m_ok     = new QPushButton(QObject::tr("Ok"));
1391     m_cancel = new QPushButton(QObject::tr("Cancel"));
1392     addButtonBarWidget(m_ok, m_cancel);
1393 
1394     m_ok->setDefault(true);
1395 
1396     connect(m_ok, SIGNAL(clicked()), this, SLOT(accept()));
1397     connect(m_cancel, SIGNAL(clicked()), this, SLOT(reject()));
1398   }
1399 
getName() const1400   QString getName() const { return m_name->text(); }
1401 };
1402 
1403 //-----------------------------------------------------------------------------
1404 
cloneLevel(const TXshSimpleLevel * srcSl,const TFilePath & dstPath,const std::set<TFrameId> & frames) const1405 TXshSimpleLevel *CloneLevelUndo::cloneLevel(
1406     const TXshSimpleLevel *srcSl, const TFilePath &dstPath,
1407     const std::set<TFrameId> &frames) const {
1408   ToonzScene *scene = TApp::instance()->getCurrentScene()->getScene();
1409 
1410   int levelType = srcSl->getType();
1411   assert(levelType > 0);
1412 
1413   const std::wstring &dstName = dstPath.getWideName();
1414 
1415   TXshSimpleLevel *dstSl =
1416       scene->createNewLevel(levelType, dstName)->getSimpleLevel();
1417 
1418   assert(dstSl);
1419   dstSl->setPath(scene->codeFilePath(dstPath));
1420   dstSl->setName(dstName);
1421   dstSl->clonePropertiesFrom(srcSl);
1422   *dstSl->getHookSet() = *srcSl->getHookSet();
1423 
1424   if (levelType == TZP_XSHLEVEL || levelType == PLI_XSHLEVEL) {
1425     TPalette *palette = srcSl->getPalette();
1426     assert(palette);
1427 
1428     dstSl->setPalette(palette->clone());
1429     dstSl->getPalette()->setDirtyFlag(true);
1430   }
1431 
1432   // The level clone shell was created. Now, clone the associated frames found
1433   // in the selection
1434   std::set<TFrameId>::const_iterator ft, fEnd(frames.end());
1435   for (ft = frames.begin(); ft != fEnd; ++ft) {
1436     const TFrameId &fid = *ft;
1437 
1438     TImageP img = srcSl->getFullsampledFrame(*ft, ImageManager::dontPutInCache);
1439     if (!img) continue;
1440 
1441     dstSl->setFrame(*ft, img->cloneImage());
1442   }
1443 
1444   dstSl->setDirtyFlag(true);
1445 
1446   return dstSl;
1447 }
1448 
1449 //-----------------------------------------------------------------------------
1450 
chooseLevelName(TFilePath & fp) const1451 bool CloneLevelUndo::chooseLevelName(TFilePath &fp) const {
1452   std::unique_ptr<LevelNamePopup> levelNamePopup(
1453       new LevelNamePopup(fp.getWideName()));
1454   if (levelNamePopup->exec() == QDialog::Accepted) {
1455     const QString &levelName = levelNamePopup->getName();
1456 
1457     if (isValidFileName_message(levelName) &&
1458         !isReservedFileName_message(levelName)) {
1459       fp = fp.withName(levelName.toStdWString());
1460       return true;
1461     }
1462   }
1463 
1464   return false;
1465 }
1466 
1467 //-----------------------------------------------------------------------------
1468 
chooseOverwrite(OverwriteDialog * dialog,TFilePath & dstPath,TXshSimpleLevel * & dstSl) const1469 bool CloneLevelUndo::chooseOverwrite(OverwriteDialog *dialog,
1470                                      TFilePath &dstPath,
1471                                      TXshSimpleLevel *&dstSl) const {
1472   ToonzScene *scene = TApp::instance()->getCurrentScene()->getScene();
1473   ExistsFunc exists(scene);
1474 
1475   OverwriteDialog::Resolution acceptedRes = OverwriteDialog::ALL_RESOLUTIONS;
1476 
1477   TXshLevel *xl = scene->getLevelSet()->getLevel(*scene, dstPath);
1478   if (xl)
1479     acceptedRes =
1480         OverwriteDialog::Resolution(acceptedRes & ~OverwriteDialog::OVERWRITE);
1481 
1482   // Apply user's decision
1483   switch (dialog->execute(dstPath, exists, acceptedRes,
1484                           OverwriteDialog::APPLY_TO_ALL_FLAG)) {
1485   default:
1486     return false;
1487 
1488   case OverwriteDialog::KEEP_OLD:
1489     // Load the level at the preferred clone path
1490     if (!xl) xl = scene->loadLevel(dstPath);  // Hard load - from disk
1491 
1492     assert(xl);
1493     dstSl = xl->getSimpleLevel();
1494     break;
1495 
1496   case OverwriteDialog::OVERWRITE:
1497     assert(!xl);
1498     break;
1499 
1500   case OverwriteDialog::RENAME:
1501     break;
1502   }
1503 
1504   return true;
1505 }
1506 
1507 //-----------------------------------------------------------------------------
1508 
cloneLevels() const1509 void CloneLevelUndo::cloneLevels() const {
1510   TApp *app         = TApp::instance();
1511   ToonzScene *scene = app->getCurrentScene()->getScene();
1512   TXsheet *xsh      = app->getCurrentXsheet()->getXsheet();
1513 
1514   // Retrieve the simple levels and associated frames in the specified range
1515   typedef std::set<TFrameId> FramesSet;
1516   typedef std::map<TXshSimpleLevel *, FramesSet> LevelsMap;
1517 
1518   LevelsMap levels;
1519   getSelectedFrames(*xsh, m_range.m_r0, m_range.m_c0, m_range.m_r1,
1520                     m_range.m_c1, levels);
1521 
1522   if (!levels.empty()) {
1523     bool askCloneName = (levels.size() == 1);
1524 
1525     // Now, try to clone every found level in the associated range
1526     std::unique_ptr<OverwriteDialog> dialog;
1527     ExistsFunc exists(scene);
1528 
1529     LevelsMap::iterator lt, lEnd(levels.end());
1530     for (lt = levels.begin(); lt != lEnd; ++lt) {
1531       assert(lt->first && !lt->second.empty());
1532 
1533       TXshSimpleLevel *srcSl = lt->first;
1534       if (srcSl->getPath().getType() == "psd" ||
1535           srcSl->getPath().getType() == "gif" ||
1536           srcSl->getPath().getType() == "mp4" ||
1537           srcSl->getPath().getType() == "webm")
1538         continue;
1539 
1540       const TFilePath &srcPath = srcSl->getPath();
1541 
1542       // Build the destination level data
1543       TXshSimpleLevel *dstSl = 0;
1544       TFilePath dstPath      = scene->decodeFilePath(
1545           srcPath.withName(srcPath.getWideName() + L"_clone"));
1546 
1547       // Ask user to suggest an appropriate level name
1548       if (askCloneName && !chooseLevelName(dstPath)) continue;
1549 
1550       // Get a unique level path
1551       if (exists(dstPath)) {
1552         // Ask user for action
1553         if (!dialog.get()) dialog.reset(new OverwriteDialog);
1554 
1555         if (!chooseOverwrite(dialog.get(), dstPath, dstSl)) continue;
1556       }
1557 
1558       // If the destination level was not retained from existing data, it must
1559       // be created and cloned
1560       if (!dstSl) dstSl = cloneLevel(srcSl, dstPath, lt->second);
1561 
1562       assert(dstSl);
1563       m_insertedLevels[srcSl] = dstSl;
1564     }
1565   }
1566 }
1567 
1568 //-----------------------------------------------------------------------------
1569 
insertLevels() const1570 void CloneLevelUndo::insertLevels() const {
1571   ToonzScene *scene = TApp::instance()->getCurrentScene()->getScene();
1572 
1573   InsertedLevelsMap::iterator lt, lEnd = m_insertedLevels.end();
1574   for (lt = m_insertedLevels.begin(); lt != lEnd; ++lt)
1575     scene->getLevelSet()->insertLevel(lt->second.getPointer());
1576 }
1577 
1578 //-----------------------------------------------------------------------------
1579 
insertCells() const1580 void CloneLevelUndo::insertCells() const {
1581   TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
1582 
1583   m_insertedColumns.clear();
1584 
1585   // If necessary, insert blank columns AFTER the columns range.
1586   // Remember the indices of inserted columns, too.
1587   for (int c = 1; c <= m_range.getColCount(); ++c) {
1588     int colIndex = m_range.m_c1 + c;
1589     if (xsh->isColumnEmpty(
1590             colIndex))  // If there already is a hole, no need to insert -
1591       continue;         // we'll just use it.
1592 
1593     xsh->insertColumn(colIndex);
1594     m_insertedColumns.insert(colIndex);
1595   }
1596 
1597   // Now, re-traverse the selected range, and add corresponding cells
1598   // in the destination range
1599   for (int c = m_range.m_c0; c <= m_range.m_c1; ++c) {
1600     for (int r = m_range.m_r0; r <= m_range.m_r1; ++r) {
1601       const TXshCell &srcCell = xsh->getCell(r, c);
1602       if (TXshSimpleLevel *srcSl = srcCell.getSimpleLevel()) {
1603         std::map<TXshSimpleLevel *, TXshLevelP>::iterator lt =
1604             m_insertedLevels.find(srcSl);
1605         if (lt != m_insertedLevels.end()) {
1606           TXshCell dstCell(lt->second, srcCell.getFrameId());
1607           xsh->setCell(r, c + m_range.getColCount(), dstCell);
1608         }
1609       }
1610     }
1611   }
1612 }
1613 
1614 //-----------------------------------------------------------------------------
1615 
redo() const1616 void CloneLevelUndo::redo() const {
1617   if (m_clonedLevels)
1618     insertLevels();
1619   else {
1620     m_clonedLevels = true;
1621     cloneLevels();
1622   }
1623 
1624   if (m_insertedLevels.empty()) return;
1625 
1626   // Command succeeded, let's deal with the xsheet
1627   m_ok = true;
1628   insertCells();
1629 
1630   // Finally, emit notifications
1631   TApp *app = TApp::instance();
1632 
1633   app->getCurrentXsheet()->notifyXsheetChanged();
1634   app->getCurrentScene()->setDirtyFlag(true);
1635   app->getCurrentScene()->notifyCastChange();
1636 }
1637 
1638 //-----------------------------------------------------------------------------
1639 
undo() const1640 void CloneLevelUndo::undo() const {
1641   assert(!m_insertedLevels.empty());
1642 
1643   TApp *app         = TApp::instance();
1644   ToonzScene *scene = app->getCurrentScene()->getScene();
1645 
1646   TXsheet *xsh    = scene->getXsheet();
1647   TXsheet *topXsh = scene->getChildStack()->getTopXsheet();
1648 
1649   // Erase inserted columns from the xsheet
1650   for (int i = m_range.getColCount(); i > 0; --i) {
1651     int index                        = m_range.m_c1 + i;
1652     std::set<int>::const_iterator it = m_insertedColumns.find(index);
1653     xsh->removeColumn(index);
1654     if (it == m_insertedColumns.end()) xsh->insertColumn(index);
1655   }
1656 
1657   // Attempt removal of inserted columns from the cast
1658   // NOTE: Cloned levels who were KEEP_OLD'd may have already been present in
1659   // the cast
1660 
1661   std::map<TXshSimpleLevel *, TXshLevelP>::const_iterator lt,
1662       lEnd = m_insertedLevels.end();
1663   for (lt = m_insertedLevels.begin(); lt != lEnd; ++lt) {
1664     if (!topXsh->isLevelUsed(lt->second.getPointer()))
1665       scene->getLevelSet()->removeLevel(lt->second.getPointer());
1666   }
1667 
1668   app->getCurrentXsheet()->notifyXsheetChanged();
1669   app->getCurrentScene()->setDirtyFlag(true);
1670   app->getCurrentScene()->notifyCastChange();
1671 }
1672 
1673 }  // namespace
1674 
1675 //-----------------------------------------------------------------------------
1676 
cloneLevel()1677 void TCellSelection::cloneLevel() {
1678   std::unique_ptr<CloneLevelUndo> undo(new CloneLevelUndo(m_range));
1679 
1680   if (undo->redo(), undo->m_ok) TUndoManager::manager()->add(undo.release());
1681 }
1682 
1683 //=============================================================================
1684 
shiftKeyframes(int direction)1685 void TCellSelection::shiftKeyframes(int direction) {
1686   if (isEmpty() || areAllColSelectedLocked()) return;
1687 
1688   int shift = m_range.getRowCount() * direction;
1689   if (!shift) return;
1690 
1691   TXsheetHandle *xsheet = TApp::instance()->getCurrentXsheet();
1692   TXsheet *xsh          = xsheet->getXsheet();
1693   TCellKeyframeSelection *cellKeyframeSelection = new TCellKeyframeSelection(
1694       new TCellSelection(), new TKeyframeSelection());
1695 
1696   cellKeyframeSelection->setXsheetHandle(xsheet);
1697 
1698   TUndoManager::manager()->beginBlock();
1699   for (int col = m_range.m_c0; col <= m_range.m_c1; col++) {
1700     TXshColumn *column = xsh->getColumn(col);
1701     if (!column || column->isLocked()) continue;
1702 
1703     TStageObjectId colId =
1704         col < 0 ? TStageObjectId::ColumnId(xsh->getCameraColumnIndex())
1705                 : TStageObjectId::ColumnId(col);
1706     TStageObject *colObj = xsh->getStageObject(colId);
1707     TStageObject::KeyframeMap keyframes;
1708     colObj->getKeyframes(keyframes);
1709     if (!keyframes.size()) continue;
1710     int row = m_range.m_r0;
1711     for (TStageObject::KeyframeMap::iterator it = keyframes.begin();
1712          it != keyframes.end(); it++) {
1713       if (it->first < m_range.m_r0) continue;
1714       row = it->first;
1715       cellKeyframeSelection->selectCellsKeyframes(row, col,
1716                                                   xsh->getFrameCount(), col);
1717       cellKeyframeSelection->getKeyframeSelection()->shiftKeyframes(
1718           row, row + shift, col, col);
1719       break;
1720     }
1721   }
1722   TUndoManager::manager()->endBlock();
1723 
1724   delete cellKeyframeSelection;
1725 }
1726