1 
2 
3 // TnzCore includes
4 #include "tsystem.h"
5 #include "tundo.h"
6 #include "tpalette.h"
7 
8 // TnzLib includes
9 #include "toonz/txsheet.h"
10 #include "toonz/toonzscene.h"
11 #include "toonz/txshcell.h"
12 #include "toonz/txshsimplelevel.h"
13 #include "toonz/levelset.h"
14 #include "toonz/hook.h"
15 #include "toonz/levelproperties.h"
16 #include "toonz/txshlevelhandle.h"
17 #include "toonz/txsheethandle.h"
18 #include "toonz/tscenehandle.h"
19 #include "toonz/txshleveltypes.h"
20 
21 // TnzQt includes
22 #include "toonzqt/menubarcommand.h"
23 
24 // Tnz6 includes
25 #include "tapp.h"
26 #include "cellselection.h"
27 #include "columnselection.h"
28 #include "keyframeselection.h"
29 #include "filmstripselection.h"
30 #include "menubarcommandids.h"
31 #include "columncommand.h"
32 
33 // Qt includes
34 #include <QCoreApplication>
35 #include <QPushButton>
36 #include <QMainWindow>
37 
38 #include "matchline.h"
39 
40 //*****************************************************************************
41 //    MergeCmappedDialog  implementation
42 //*****************************************************************************
43 
accept()44 void MergeCmappedDialog::accept() {
45   m_levelPath = TFilePath(QString(m_saveInFileFld->getPath() + "\\" +
46                                   m_fileNameFld->text() + ".tlv")
47                               .toStdString());
48   TFilePath fp =
49       TApp::instance()->getCurrentScene()->getScene()->decodeFilePath(
50           m_levelPath);
51 
52   if (TSystem::doesExistFileOrLevel(fp)) {
53     if (DVGui::MsgBox(
54             tr("Level %1 already exists! Are you sure you want to overwrite "
55                "it?")
56                 .arg(QString::fromStdWString(m_levelPath.getWideString())),
57             tr("Ok"), tr("Cancel")) != 1)
58       return;
59     else {
60       TSystem::removeFileOrLevel(fp);
61       TSystem::removeFileOrLevel(fp.withType("tpl"));
62     }
63   }
64 
65   Dialog::accept();
66 }
67 
68 //------------------------------------------------------------------------------
69 
MergeCmappedDialog(TFilePath & levelPath)70 MergeCmappedDialog::MergeCmappedDialog(TFilePath &levelPath)
71     : Dialog(TApp::instance()->getMainWindow(), true, true, "Merge Tlv")
72     , m_levelPath(levelPath) {
73   bool ret = true;
74 
75   QString path =
76       QString::fromStdWString(m_levelPath.getParentDir().getWideString());
77   QString name = QString::fromStdString(m_levelPath.getName());
78 
79   setWindowTitle(tr(" Merge Tlv Levels"));
80   m_saveInFileFld = new DVGui::FileField(0, path);
81   ret             = ret && connect(m_saveInFileFld, SIGNAL(pathChanged()), this,
82                        SLOT(onPathChanged()));
83   addWidget(tr("Save in:"), m_saveInFileFld);
84 
85   m_fileNameFld = new DVGui::LineEdit(name + "_merged");
86   m_fileNameFld->setMaximumHeight(DVGui::WidgetHeight);
87   ret = ret && connect(m_fileNameFld, SIGNAL(editingFinished()),
88                        SLOT(onNameChanged()));
89   addWidget(tr("File Name:"), m_fileNameFld);
90 
91   QPushButton *okBtn = new QPushButton(tr("Apply"), this);
92   okBtn->setDefault(true);
93   QPushButton *cancelBtn = new QPushButton(tr("Cancel"), this);
94   connect(okBtn, SIGNAL(clicked()), this, SLOT(accept()));
95   connect(cancelBtn, SIGNAL(clicked()), this, SLOT(reject()));
96 
97   addButtonBarWidget(okBtn, cancelBtn);
98 }
99 
100 //*****************************************************************************
101 //    MergeColumns  command
102 //*****************************************************************************
103 
isVectorColumn(const std::set<int> & columns)104 static bool isVectorColumn(const std::set<int> &columns) {
105   TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
106   std::set<int>::const_iterator column = columns.begin();
107   int start, end;
108   xsh->getCellRange(*column, start, end);
109 
110   if (start > end) return false;
111   // a cell at "start" must be occupied
112   TXshCell cell = xsh->getCell(start, *column);
113   return cell.m_level->getType() == PLI_XSHLEVEL;
114 }
115 
116 class MergeColumnsCommand final : public MenuItemHandler {
117 public:
MergeColumnsCommand()118   MergeColumnsCommand() : MenuItemHandler(MI_MergeColumns) {}
119 
execute()120   void execute() override {
121     TColumnSelection *selection =
122         dynamic_cast<TColumnSelection *>(TSelection::getCurrent());
123 
124     std::set<int> indices =
125         selection ? selection->getIndices() : std::set<int>();
126 
127     if (indices.empty()) {
128       DVGui::warning(QObject::tr(
129           "It is not possible to execute the merge column command because "
130           "no column was selected."));
131       return;
132     }
133 
134     if (indices.size() == 1) {
135       DVGui::warning(QObject::tr(
136           "It is not possible to execute the merge column command because "
137           "only one columns is selected."));
138       return;
139     }
140 
141     bool groupLevels = true;
142     if (isVectorColumn(indices)) {
143       int opt = DVGui::MsgBox(QObject::tr("Group strokes by vector levels?"),
144                               QObject::tr("Yes"), QObject::tr("No"),
145                               QObject::tr("Cancel"));
146       if (opt == 0 || opt == 3)
147         return;
148       else {
149         groupLevels = (opt == 1);
150       };
151     }
152 
153     mergeColumns(indices, groupLevels);
154     TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
155   }
156 
157 } MergeColumnsCommand;
158 
159 //*****************************************************************************
160 //    ApplyMatchlines  command
161 //*****************************************************************************
162 
163 class ApplyMatchlinesCommand final : public MenuItemHandler {
164 public:
ApplyMatchlinesCommand()165   ApplyMatchlinesCommand() : MenuItemHandler(MI_ApplyMatchLines) {}
166 
execute()167   void execute() override {
168     TColumnSelection *selection =
169         dynamic_cast<TColumnSelection *>(TSelection::getCurrent());
170     if (!selection) {
171       DVGui::warning(QObject::tr(
172           "It is not possible to apply the match lines because no column "
173           "was selected."));
174       return;
175     }
176 
177     std::set<int> indices = selection->getIndices();
178 
179     if (indices.size() != 2) {
180       DVGui::warning(QObject::tr(
181           "It is not possible to apply the match lines because two columns "
182           "have to be selected."));
183       return;
184     }
185 
186     std::set<int>::iterator it = indices.begin();
187     int i = *it++, j = *it;
188 
189     doMatchlines(i, j, -1, -1);
190     TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
191   }
192 
193 } ApplyMatchlinesCommand;
194 
195 //--------------------------------------------
196 
197 namespace {
198 
checkColumnValidity(int column)199 bool checkColumnValidity(int column) {
200   TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
201   int start, end;
202 
203   xsh->getCellRange(column, start, end);
204 
205   if (start > end) return false;
206   std::vector<TXshCell> cell(end - start + 1);
207 
208   xsh->getCells(start, column, cell.size(), &(cell[0]));
209 
210   TXshSimpleLevel *level = 0;
211 
212   for (int i = 0; i < (int)cell.size(); i++) {
213     if (cell[i].isEmpty()) continue;
214     if (!level) level = cell[i].getSimpleLevel();
215 
216     if (cell[i].getSimpleLevel()->getType() != TZP_XSHLEVEL) {
217       DVGui::warning(QObject::tr(
218           "Match lines can be applied to Toonz raster levels only."));
219       return false;
220     }
221     if (level != cell[i].getSimpleLevel()) {
222       DVGui::warning(
223           QObject::tr("It is not possible to merge tlv columns containing more "
224                       "than one level"));
225       return false;
226     }
227   }
228 
229   if (!level) return false;
230 
231   if (!level->getPalette()) {
232     DVGui::warning(
233         QObject::tr("The level you are using has not a valid palette."));
234     return false;
235   }
236   return true;
237 }
238 
239 //---------------------------------------------------------------------------------------------------------
240 
241 void doCloneLevelNoSave(const TCellSelection::Range &range,
242                         const QString &newLevelName, bool withUndo);
243 
244 //-----------------------------------------------------------------------------
245 namespace {
246 //-----------------------------------------------------------------------------
247 
248 class CloneLevelNoSaveUndo final : public TUndo {
249   std::map<TXshSimpleLevel *, TXshLevelP> m_createdLevels;
250   std::set<int> m_insertedColumnIndices;
251 
252   TCellSelection::Range m_range;
253   QString m_levelname;
254 
255 public:
CloneLevelNoSaveUndo(const TCellSelection::Range & range,const std::map<TXshSimpleLevel *,TXshLevelP> & createdLevels,const std::set<int> & insertedColumnIndices,const QString & levelname)256   CloneLevelNoSaveUndo(
257       const TCellSelection::Range &range,
258       const std::map<TXshSimpleLevel *, TXshLevelP> &createdLevels,
259       const std::set<int> &insertedColumnIndices, const QString &levelname)
260       : m_createdLevels(createdLevels)
261       , m_range(range)
262       , m_insertedColumnIndices(insertedColumnIndices)
263       , m_levelname(levelname) {}
264 
undo() const265   void undo() const override {
266     TApp *app         = TApp::instance();
267     ToonzScene *scene = app->getCurrentScene()->getScene();
268     TXsheet *xsh      = scene->getXsheet();
269     int i;
270     for (i = m_range.getColCount(); i > 0; i--) {
271       int index                        = m_range.m_c1 + i;
272       std::set<int>::const_iterator it = m_insertedColumnIndices.find(index);
273       xsh->removeColumn(index);
274       if (it == m_insertedColumnIndices.end()) xsh->insertColumn(index);
275     }
276 
277     std::map<TXshSimpleLevel *, TXshLevelP>::const_iterator it =
278         m_createdLevels.begin();
279 
280     for (; it != m_createdLevels.end(); ++it) {
281       it->second->addRef();
282       scene->getLevelSet()->removeLevel(it->second.getPointer());
283     }
284     app->getCurrentXsheet()->notifyXsheetChanged();
285   }
redo() const286   void redo() const override {
287     doCloneLevelNoSave(m_range, m_levelname, false);
288   }
289 
getSize() const290   int getSize() const override {
291     return sizeof *this + (sizeof(TXshLevelP) + sizeof(TXshSimpleLevel *)) *
292                               m_createdLevels.size();
293   }
294 };
295 
296 //-----------------------------------------------------------------------------
297 }  // namespace
298 //-----------------------------------------------------------------------------
299 
doCloneLevelNoSave(const TCellSelection::Range & range,const QString & newLevelName=QString (),bool withUndo=true)300 void doCloneLevelNoSave(const TCellSelection::Range &range,
301                         const QString &newLevelName = QString(),
302                         bool withUndo               = true) {
303   std::map<TXshSimpleLevel *, TXshLevelP> createdLevels;
304 
305   TApp *app         = TApp::instance();
306   TXsheet *xsh      = app->getCurrentXsheet()->getXsheet();
307   ToonzScene *scene = app->getCurrentScene()->getScene();
308 
309   // Build indices of inserted columns
310   std::set<int> insertedColumnIndices;
311   int c;
312   for (c = 1; c <= range.getColCount(); ++c) {
313     int colIndex = range.m_c1 + c;
314     if (xsh->isColumnEmpty(colIndex)) continue;
315 
316     xsh->insertColumn(colIndex);
317     insertedColumnIndices.insert(colIndex);
318   }
319 
320   bool isOneCellCloned = false;
321   for (c = range.m_c0; c <= range.m_c1; ++c) {
322     TXshLevelP xl;
323     TXshSimpleLevel *sl = 0;
324     TFrameId fid(1);
325 
326     bool keepOldLevel = false;
327 
328     // OverwriteDialog* dialog = new OverwriteDialog();
329     for (int r = range.m_r0; r <= range.m_r1; ++r) {
330       TXshCell cell = xsh->getCell(r, c);
331 
332       TImageP img = cell.getImage(true);
333       if (!img) continue;
334 
335       fid = cell.getFrameId();
336 
337       if (cell.getSimpleLevel() == 0 ||
338           cell.getSimpleLevel()->getPath().getType() == "psd" ||
339           cell.getSimpleLevel()->getPath().getType() == "gif" ||
340           cell.getSimpleLevel()->getPath().getType() == "mp4" ||
341           cell.getSimpleLevel()->getPath().getType() == "webm")
342         continue;
343 
344       std::map<TXshSimpleLevel *, TXshLevelP>::iterator it =
345           createdLevels.find(cell.getSimpleLevel());
346       if (it == createdLevels.end()) {
347         // Create a new level if not already done
348 
349         TXshSimpleLevel *oldSl = cell.getSimpleLevel();
350         {
351           int levelType = oldSl->getType();
352           assert(levelType > 0);
353 
354           xl = scene->createNewLevel(levelType, newLevelName.toStdWString());
355           sl = xl->getSimpleLevel();
356           // if(levelType == OVL_XSHLEVEL)
357           //  dstPath = dstPath.withType(oldSl->getPath().getType());
358           assert(sl);
359           // sl->setPath(scene->codeFilePath(dstPath));
360           // sl->setName(newName);
361           sl->clonePropertiesFrom(oldSl);
362           *sl->getHookSet() = *oldSl->getHookSet();
363 
364           if (levelType == TZP_XSHLEVEL || levelType == PLI_XSHLEVEL) {
365             TPalette *palette = oldSl->getPalette();
366             assert(palette);
367 
368             sl->setPalette(palette->clone());
369           }
370         }
371         createdLevels[cell.getSimpleLevel()] = xl;
372       } else {
373         xl = it->second;
374         sl = xl->getSimpleLevel();
375       }
376 
377       TXshCell oldCell(cell);
378       cell.m_level = xl;
379       int k;
380       for (k = range.m_r0; k < r; k++) {
381         if (xsh->getCell(k, c).getImage(true).getPointer() ==
382             img.getPointer()) {
383           TFrameId oldFid = xsh->getCell(k, c).getFrameId();
384           assert(fid == oldFid);
385           sl->setFrame(fid,
386                        xsh->getCell(k, c + range.getColCount()).getImage(true));
387           break;
388         }
389       }
390 
391       if (!keepOldLevel && k >= r) {
392         TImageP newImg(img->cloneImage());
393         assert(newImg);
394 
395         sl->setFrame(fid, newImg);
396       }
397 
398       cell.m_frameId = fid;
399       xsh->setCell(r, c + range.getColCount(), cell);
400       isOneCellCloned = true;
401     }
402     if (sl) sl->getProperties()->setDirtyFlag(true);
403   }
404 
405   // Se non e' stata inserita nessuna cella rimuovo le colonne aggiunte e
406   // ritorno.
407   if (!isOneCellCloned) {
408     if (!insertedColumnIndices.empty()) {
409       int i;
410       for (i = range.getColCount(); i > 0; i--) {
411         int index                        = range.m_c1 + i;
412         std::set<int>::const_iterator it = insertedColumnIndices.find(index);
413         xsh->removeColumn(index);
414       }
415     }
416     return;
417   }
418   if (withUndo)
419     TUndoManager::manager()->add(new CloneLevelNoSaveUndo(
420         range, createdLevels, insertedColumnIndices, newLevelName));
421 
422   app->getCurrentXsheet()->notifyXsheetChanged();
423   app->getCurrentScene()->setDirtyFlag(true);
424   app->getCurrentScene()->notifyCastChange();
425 }
426 
cloneColumn(const TCellSelection::Range & cells,const TFilePath & newLevelPath)427 void cloneColumn(const TCellSelection::Range &cells,
428                  const TFilePath &newLevelPath) {
429   std::set<std::pair<int, int>> positions;
430   for (int i = cells.m_r0; i <= cells.m_r1; i++)
431     positions.insert(std::pair<int, int>(i, cells.m_c0));
432 
433   TKeyframeSelection ks(positions);
434   ks.copyKeyframes();
435   doCloneLevelNoSave(cells,
436                      QString::fromStdString(newLevelPath.getName() + "_"));
437   ColumnCmd::deleteColumn(cells.m_c0);
438   ks.pasteKeyframes();
439 }
440 
441 }  // namespace
442 
443 class MergeCmappedCommand final : public MenuItemHandler {
444 public:
MergeCmappedCommand()445   MergeCmappedCommand() : MenuItemHandler(MI_MergeCmapped) {}
execute()446   void execute() override {
447     TColumnSelection *selection =
448         dynamic_cast<TColumnSelection *>(TSelection::getCurrent());
449     if (!selection) {
450       DVGui::warning(QObject::tr(
451           "It is not possible to merge tlv columns because no column was "
452           "selected."));
453       return;
454     }
455 
456     std::set<int> indices = selection->getIndices();
457 
458     if (indices.size() < 2) {
459       DVGui::warning(QObject::tr(
460           "It is not possible to merge tlv columns because at least two "
461           "columns have to be selected."));
462       return;
463     }
464 
465     std::set<int>::iterator it = indices.begin();
466     int destColumn             = *it;
467 
468     TCellSelection::Range cells;
469     cells.m_c0 = cells.m_c1 = destColumn;
470     TXshColumn *column =
471         TApp::instance()->getCurrentXsheet()->getXsheet()->getColumn(
472             destColumn);
473     column->getRange(cells.m_r0, cells.m_r1);
474 
475     // column->getLevelColumn()
476 
477     TFilePath newLevelPath;
478     TXshCell c = TApp::instance()->getCurrentXsheet()->getXsheet()->getCell(
479         cells.m_r0, destColumn);
480     if (!c.isEmpty() && c.getSimpleLevel())
481       newLevelPath = c.getSimpleLevel()->getPath();
482 
483     if (MergeCmappedDialog(newLevelPath).exec() != QDialog::Accepted) return;
484 
485     it = indices.begin();
486     for (; it != indices.end(); ++it)
487       if (!checkColumnValidity(*it)) return;
488 
489     DVGui::ProgressDialog progress(QObject::tr("Merging Tlv Levels..."),
490                                    QString(), 0, indices.size() - 1,
491                                    TApp::instance()->getMainWindow());
492     progress.setWindowModality(Qt::WindowModal);
493     progress.setWindowTitle(QObject::tr("Merging Tlv Levels..."));
494     progress.setValue(0);
495     progress.show();
496 
497     QCoreApplication::instance()->processEvents();
498 
499     TUndoManager::manager()->beginBlock();
500 
501     cloneColumn(cells, newLevelPath);
502 
503     TFilePath tmpPath = newLevelPath.withName(
504         QString::fromStdString(newLevelPath.getName() + "_tmp").toStdWString());
505 
506     it = indices.begin();
507     ++it;
508     for (int count = 0; it != indices.end();) {
509       int index = *it;
510       it++;
511       mergeCmapped(destColumn, index - count,
512                    it == indices.end() ? newLevelPath.getQString()
513                                        : tmpPath.getQString(),
514                    false);
515       ColumnCmd::deleteColumn(index - count);
516       progress.setValue(++count);
517       QCoreApplication::instance()->processEvents();
518     }
519     TUndoManager::manager()->endBlock();
520     TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
521   }
522 
523 } MergeCmappedCommand;
524 
525 //-----------------------------------------------------------------------------
526 namespace {
doDeleteCommand(bool isMatchline)527 void doDeleteCommand(bool isMatchline) {
528   TRect r;
529 
530   TCellSelection *sel =
531       dynamic_cast<TCellSelection *>(TSelection::getCurrent());
532   if (!sel) {
533     TFilmstripSelection *filmstripSelection =
534         dynamic_cast<TFilmstripSelection *>(TSelection::getCurrent());
535     TColumnSelection *columnSelection =
536         dynamic_cast<TColumnSelection *>(TSelection::getCurrent());
537     std::set<int> indices;
538     std::set<TFrameId> fids;
539     if (filmstripSelection &&
540         (fids = filmstripSelection->getSelectedFids()).size() > 0) {
541       TXshSimpleLevel *sl =
542           TApp::instance()->getCurrentLevel()->getSimpleLevel();
543       if (isMatchline)
544         deleteMatchlines(sl, fids);
545       else
546         deleteInk(sl, fids);
547       TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
548       return;
549     } else if (!columnSelection ||
550                (indices = columnSelection->getIndices()).size() != 1) {
551       DVGui::warning(
552           QObject::tr("It is not possible to delete lines because no column, "
553                       "cell or level strip frame was selected."));
554       return;
555     }
556     int from, to;
557     int columnIndex = *indices.begin();
558     TXsheet *xsh    = TApp::instance()->getCurrentXsheet()->getXsheet();
559     if (!xsh->getCellRange(*indices.begin(), from, to)) {
560       DVGui::warning(QObject::tr("The selected column is empty."));
561       return;
562     }
563     r.y0 = from;
564     r.y1 = to;
565     r.x0 = r.x1 = columnIndex;
566   } else
567     sel->getSelectedCells(r.y0, r.x0, r.y1, r.x1);
568 
569   if (r.x0 != r.x1) {
570     DVGui::warning(QObject::tr("Selected cells must be in the same column."));
571     return;
572   }
573 
574   TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
575 
576   int i;
577 
578   for (i = r.y0; i <= r.y1; i++) {
579     TXshCell cell = xsh->getCell(i, r.x0);
580     if (cell.isEmpty()) {
581       DVGui::warning(
582           QObject::tr("It is not possible to delete lines because no column, "
583                       "cell or level strip frame was selected."));
584       return;
585     }
586     if (cell.m_level->getType() != TZP_XSHLEVEL) {
587       DVGui::warning(QObject::tr(
588           "Match lines can be deleted from Toonz raster levels only"));
589       return;
590     }
591   }
592 
593   std::set<TFrameId> fids;
594   for (i = r.y0; i <= r.y1; i++) {
595     const TXshCell &cell = xsh->getCell(i, r.x0);
596     fids.insert(cell.getFrameId());
597   }
598 
599   TXshSimpleLevel *sl = xsh->getCell(r.y0, r.x0).getSimpleLevel();
600   assert(sl);
601 
602   if (isMatchline)
603     deleteMatchlines(sl, fids);
604   else
605     deleteInk(sl, fids);
606 
607   TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
608 }
609 
610 }  // namespace
611 
612 //-----------------------------------------------------------------------------
613 
614 class DeleteInkCommand final : public MenuItemHandler {
615 public:
DeleteInkCommand()616   DeleteInkCommand() : MenuItemHandler(MI_DeleteInk) {}
execute()617   void execute() override { doDeleteCommand(false); }
618 
619 } DeleteInkCommand;
620 
621 //-----------------------------------------------------------------------------
622 
623 class DeleteMatchlinesCommand final : public MenuItemHandler {
624 public:
DeleteMatchlinesCommand()625   DeleteMatchlinesCommand() : MenuItemHandler(MI_DeleteMatchLines) {}
execute()626   void execute() override { doDeleteCommand(true); }
627 
628 } DeleteMatchlinesCommand;
629