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