1
2
3 #include "cleanuppopup.h"
4
5 // Toonz includes
6 #include "tapp.h"
7 #include "imageviewer.h"
8 #include "cleanupsettingsmodel.h"
9 #include "cellselection.h"
10 #include "columnselection.h"
11 #include "mainwindow.h"
12
13 // ToonzQt includes
14 #include "toonzqt/gutil.h"
15 #include "toonzqt/dvdialog.h"
16 #include "toonzqt/lineedit.h"
17 #include "toonzqt/menubarcommand.h"
18 #include "toonzqt/icongenerator.h"
19
20 // ToonzLib includes
21 #include "toonz/toonzscene.h"
22 #include "toonz/txshcell.h"
23 #include "toonz/txshsimplelevel.h"
24 #include "toonz/txshleveltypes.h"
25 #include "toonz/levelproperties.h"
26 #include "toonz/imagemanager.h"
27 #include "toonz/levelupdater.h"
28 #include "toonz/tcleanupper.h"
29 #include "toonz/preferences.h"
30 #include "toonz/tscenehandle.h"
31 #include "toonz/txsheethandle.h"
32 #include "toonz/txshlevelhandle.h"
33 #include "toonz/palettecontroller.h"
34 #include "toonz/tpalettehandle.h"
35 #include "toonz/toonzfolders.h"
36
37 // TnzCore includes
38 #include "tsystem.h"
39 #include "tlevel_io.h"
40 #include "timageinfo.h"
41 #include "tstream.h"
42
43 // Qt includes
44 #include <QLabel>
45 #include <QPushButton>
46 #include <QRadioButton>
47 #include <QButtonGroup>
48 #include <QCoreApplication>
49 #include <QMainWindow>
50 #include <QGroupBox>
51
52 // STL includes
53 #include <set>
54 #include <map>
55 #include <numeric>
56 #include <functional>
57
58 //*****************************************************************************
59 // Local namespace stuff
60 //*****************************************************************************
61
62 namespace {
63
64 enum Resolution {
65 NO_RESOLUTION, //!< No required resolution.
66 CANCEL, //!< Validation was canceled.
67 OVERWRITE, //!< Does not delete old cleanupped levels, but overwrites found
68 //! frames.
69 WRITE_NEW, //!< Like above, but does not overwrite. Just adds not cleanupped
70 //! frames.
71 REPLACE, //!< Destroy the old level and build one anew.
72 ADD_SUFFIX, //!< Add a suffix to the output path.
73 NOPAINT_ONLY //!< overwrite the result only in "nopaint" folder
74 };
75
76 //-----------------------------------------------------------------------------
77
78 static const std::wstring unpaintedStr = L"-unpainted";
79
80 //-----------------------------------------------------------------------------
81
suffix(int num)82 inline QString suffix(int num) { return QString("_") + QString::number(num); }
83
84 //-----------------------------------------------------------------------------
85
withSuffix(const TFilePath & fp,int num)86 inline TFilePath withSuffix(const TFilePath &fp, int num) {
87 return fp.withName(fp.getWideName() + suffix(num).toStdWString());
88 }
89
90 //-----------------------------------------------------------------------------
91
exists(const TFilePath & fp)92 inline bool exists(const TFilePath &fp) {
93 return TSystem::doesExistFileOrLevel(
94 TApp::instance()->getCurrentScene()->getScene()->decodeFilePath(fp));
95 }
96
97 //-----------------------------------------------------------------------------
98
exists(const TFilePath & fp,int num)99 inline bool exists(const TFilePath &fp, int num) {
100 return exists(withSuffix(fp, num));
101 }
102
103 //-----------------------------------------------------------------------------
104
loadCleanupParams(CleanupParameters * params,TXshSimpleLevel * sl)105 void loadCleanupParams(CleanupParameters *params, TXshSimpleLevel *sl) {
106 params->assign(CleanupSettingsModel::instance()->getCurrentParameters());
107 CleanupSettingsModel::loadSettings(params,
108 CleanupSettingsModel::getClnPath(sl));
109 }
110
111 //-----------------------------------------------------------------------------
112
getBestFactor(QSize viewSize,QSize imageSize)113 double getBestFactor(QSize viewSize, QSize imageSize) {
114 if (abs(viewSize.width() - imageSize.width()) >
115 abs(viewSize.height() - imageSize.height())) {
116 if (viewSize.width() > imageSize.width())
117 return double(imageSize.width()) / double(viewSize.width());
118 else
119 return double(viewSize.width()) / double(imageSize.width());
120 } else {
121 if (viewSize.height() > imageSize.height())
122 return double(imageSize.height()) / double(viewSize.height());
123 else
124 return double(viewSize.height()) / double(imageSize.height());
125 }
126
127 return 0;
128 }
129
130 //-----------------------------------------------------------------------------
131 /*! cleanup後のファイルlevelPathに対してUnpaintedファイルを作る。
132 Cleanup後のUnpaintedの保存先を1階層下げる(nopaintフォルダ内に入れ、
133 "A_np.tlv"のように"_np"を付ける。"_unpainted"は長いので)
134 Paletteをキープするかどうかのフラグを追加
135 */
saveUnpaintedLevel(const TFilePath & levelPath,TXshSimpleLevel * sl,std::vector<TFrameId> fids,bool keepOriginalPalette)136 void saveUnpaintedLevel(const TFilePath &levelPath, TXshSimpleLevel *sl,
137 std::vector<TFrameId> fids, bool keepOriginalPalette) {
138 try {
139 /*---nopaintフォルダの作成---*/
140 TFilePath nopaintDir = levelPath.getParentDir() + "nopaint";
141 if (!TFileStatus(nopaintDir).doesExist()) {
142 try {
143 TSystem::mkDir(nopaintDir);
144 } catch (...) {
145 return;
146 }
147 }
148
149 TFilePath unpaintedLevelPath =
150 levelPath.getParentDir() + "nopaint\\" +
151 TFilePath(levelPath.getName() + "_np." + levelPath.getType());
152
153 if (!TSystem::doesExistFileOrLevel(unpaintedLevelPath)) {
154 // No unpainted level exists. So, just copy the output file.
155 TSystem::copyFile(unpaintedLevelPath, levelPath);
156 if (keepOriginalPalette) return;
157
158 TFilePath levelPalettePath(levelPath.withType("tpl"));
159 TFilePath unpaintedLevelPalettePath =
160 levelPalettePath.getParentDir() + "nopaint\\" +
161 TFilePath(levelPalettePath.getName() + "_np." +
162 levelPalettePath.getType());
163
164 TSystem::copyFile(unpaintedLevelPalettePath, levelPalettePath);
165
166 return;
167 }
168
169 TLevelWriterP lw(unpaintedLevelPath);
170
171 if (keepOriginalPalette) lw->setOverwritePaletteFlag(false);
172
173 int i, fidsCount = fids.size();
174 for (i = 0; i < fidsCount; ++i) {
175 const TFrameId &fid = fids[i];
176
177 TToonzImageP ti = sl->getFrame(fid, false);
178 if (!ti) continue;
179
180 lw->getFrameWriter(fid)->save(ti);
181 }
182 } catch (...) {
183 }
184 }
185
186 //------------------------------------------------------------------------------
187 /*! Cleanup後のデフォルトPaletteを追加する。
188 TODO:
189 Cleanup後にデフォルトPaletteの内容を追加する仕様、Preferencesでオプション化
190 2016/1/16 shun_iwasawa
191 */
addCleanupDefaultPalette(TXshSimpleLevelP sl)192 void addCleanupDefaultPalette(TXshSimpleLevelP sl) {
193 /*--- CleanupデフォルトパレットはStudioPaletteフォルダ内に入れる ---*/
194 TFilePath palettePath =
195 ToonzFolder::getStudioPaletteFolder() + "cleanup_default.tpl";
196 TFileStatus pfs(palettePath);
197
198 if (!pfs.doesExist() || !pfs.isReadable()) {
199 DVGui::warning(
200 QString("CleanupDefaultPalette file: %1 is not found!")
201 .arg(QString::fromStdWString(palettePath.getWideString())));
202 return;
203 }
204
205 TIStream is(palettePath);
206 if (!is) {
207 DVGui::warning(
208 QString("CleanupDefaultPalette file: failed to get TIStream"));
209 return;
210 }
211
212 std::string tagName;
213 if (!is.matchTag(tagName) || tagName != "palette") {
214 DVGui::warning(
215 QString("CleanupDefaultPalette file: This is not palette file"));
216 return;
217 }
218
219 std::string gname;
220 is.getTagParam("name", gname);
221 TPalette *defaultPalette = new TPalette();
222 defaultPalette->loadData(is);
223
224 sl->getPalette()->setIsCleanupPalette(false);
225
226 TPalette::Page *dstPage = sl->getPalette()->getPage(0);
227 TPalette::Page *srcPage = defaultPalette->getPage(0);
228
229 for (int srcIndexInPage = 0; srcIndexInPage < srcPage->getStyleCount();
230 srcIndexInPage++) {
231 int id = srcPage->getStyleId(srcIndexInPage);
232
233 bool isUsedInCleanupPalette;
234 isUsedInCleanupPalette = false;
235
236 for (int dstIndexInPage = 0; dstIndexInPage < dstPage->getStyleCount();
237 dstIndexInPage++) {
238 if (dstPage->getStyleId(dstIndexInPage) == id) {
239 isUsedInCleanupPalette = true;
240 break;
241 }
242 }
243
244 if (isUsedInCleanupPalette)
245 continue;
246
247 else {
248 int addedId = sl->getPalette()->addStyle(
249 srcPage->getStyle(srcIndexInPage)->clone());
250 dstPage->addStyle(addedId);
251 /*---
252 * StudioPalette由来のDefaultPaletteの場合、GrobalName(リンク)を消去する
253 * ---*/
254 sl->getPalette()->getStyle(addedId)->setGlobalName(L"");
255 sl->getPalette()->getStyle(addedId)->setOriginalName(L"");
256 }
257 }
258 delete defaultPalette;
259 }
260
261 } // namespace
262
263 //*****************************************************************************
264 // CleanupLevel definition
265 //*****************************************************************************
266
267 struct CleanupPopup::CleanupLevel {
268 TXshSimpleLevel *m_sl; //!< Level to be cleanupped.
269 TFilePath m_outputPath; //!< Output path for the cleanupped level.
270 std::vector<TFrameId> m_frames; //!< Frames to cleanup.
271 Resolution m_resolution; //!< Resolution for verified file conflicts.
272
273 public:
CleanupLevelCleanupPopup::CleanupLevel274 CleanupLevel(TXshSimpleLevel *sl, const CleanupParameters ¶ms)
275 : m_sl(sl)
276 , m_outputPath(CleanupSettingsModel::getOutputPath(m_sl, ¶ms))
277 , m_resolution(NO_RESOLUTION) {}
278
emptyCleanupPopup::CleanupLevel279 bool empty() const { return m_frames.empty(); }
280 };
281
282 //*****************************************************************************
283 // CleanupPopup implementation
284 //*****************************************************************************
285
CleanupPopup()286 CleanupPopup::CleanupPopup()
287 : QDialog(TApp::instance()->getMainWindow())
288 , m_params(new CleanupParameters)
289 , m_updater(new LevelUpdater)
290 , m_originalLevelPath()
291 , m_originalPalette(0)
292 , m_firstLevelFrame(true) {
293 setWindowTitle(tr("Cleanup"));
294 // Progress Bar
295 m_progressLabel = new QLabel(tr("Cleanup in progress"));
296 m_progressBar = new QProgressBar;
297 // Text
298 m_cleanupQuestionLabel = new QLabel(tr("Do you want to cleanup this frame?"));
299
300 m_imageViewer = new ImageViewer(0, 0, false);
301
302 // Buttons
303 m_cleanupButton = new QPushButton(tr("Cleanup"));
304 m_skipButton = new QPushButton(tr("Skip"));
305 m_cleanupAllButton = new QPushButton(tr("Cleanup All"));
306 QPushButton *cancelButton = new QPushButton(tr("Cancel"));
307 m_imgViewBox = new QGroupBox(tr("View"), this);
308
309 m_imgViewBox->setCheckable(true);
310 m_imgViewBox->setChecked(false);
311 m_imageViewer->setVisible(false);
312 m_imageViewer->resize(406, 306);
313 ImagePainter::VisualSettings settings;
314 settings.m_bg = 0x80000; // set to white regardless of the flipbook bg
315 m_imageViewer->setVisual(settings);
316
317 //---layout
318 QVBoxLayout *mainLayout = new QVBoxLayout();
319 mainLayout->setMargin(5);
320 mainLayout->setSpacing(5);
321 {
322 mainLayout->addWidget(m_progressLabel, 0);
323 mainLayout->addWidget(m_progressBar, 0);
324
325 mainLayout->addWidget(m_cleanupQuestionLabel);
326
327 QVBoxLayout *imgBoxLay = new QVBoxLayout();
328 imgBoxLay->setMargin(5);
329 { imgBoxLay->addWidget(m_imageViewer); }
330 m_imgViewBox->setLayout(imgBoxLay);
331 mainLayout->addWidget(m_imgViewBox, 1);
332
333 QHBoxLayout *buttonLay = new QHBoxLayout();
334 buttonLay->setMargin(0);
335 buttonLay->setSpacing(5);
336 {
337 buttonLay->addWidget(m_cleanupButton);
338 buttonLay->addWidget(m_skipButton);
339 buttonLay->addWidget(m_cleanupAllButton);
340
341 buttonLay->addWidget(cancelButton);
342 }
343 mainLayout->addLayout(buttonLay);
344 mainLayout->addStretch();
345 }
346 setLayout(mainLayout);
347
348 //--- signal-slot connections
349
350 bool ret = true;
351 ret = ret && connect(m_progressBar, SIGNAL(valueChanged(int)), this,
352 SLOT(onValueChanged(int)));
353
354 // NOTE: On MAC it seems that QAbstractButton's pressed() signal is reemitted
355 // at
356 // every mouseMoveEvent when the button is pressed...
357 // This is why clicked() substitutes pressed() below.
358
359 ret = ret && connect(m_cleanupButton, SIGNAL(clicked()), this,
360 SLOT(onCleanupFrame()));
361 ret = ret &&
362 connect(m_skipButton, SIGNAL(clicked()), this, SLOT(onSkipFrame()));
363 ret = ret && connect(m_cleanupAllButton, SIGNAL(clicked()), this,
364 SLOT(onCleanupAllFrame()));
365 ret = ret &&
366 connect(cancelButton, SIGNAL(clicked()), this, SLOT(onCancelCleanup()));
367 ret = ret && connect(m_imgViewBox, SIGNAL(toggled(bool)), this,
368 SLOT(onImgViewBoxToggled(bool)));
369
370 assert(ret);
371
372 reset(); // Initialize remaining variables
373
374 resize(450, 400);
375 }
376
377 //-----------------------------------------------------------------------------
378
~CleanupPopup()379 CleanupPopup::~CleanupPopup() {}
380
381 //-----------------------------------------------------------------------------
382
closeEvent(QCloseEvent * ce)383 void CleanupPopup::closeEvent(QCloseEvent *ce) { reset(); }
384
385 //-----------------------------------------------------------------------------
386
reset()387 void CleanupPopup::reset() {
388 closeLevel();
389
390 m_idx = m_completion = std::pair<int, int>(-1, -1);
391 m_cleanupLevels.clear();
392
393 m_imageViewer->setImage(TImageP());
394
395 TCleanupper::instance()->setParameters(
396 CleanupSettingsModel::instance()->getCurrentParameters());
397
398 m_cleanupQuestionLabel->show();
399
400 m_levelAlreadyExists.clear();
401
402 /*---タイトルバーを元の表示に戻す---*/
403 MainWindow *mainWin =
404 qobject_cast<MainWindow *>(TApp::instance()->getMainWindow());
405 if (mainWin) mainWin->changeWindowTitle();
406 }
407
408 //-----------------------------------------------------------------------------
409
buildCleanupList()410 void CleanupPopup::buildCleanupList() {
411 struct locals {
412 static inline bool supportsCleanup(TXshSimpleLevel *sl) {
413 return (
414 sl->getType() & FULLCOLOR_TYPE ||
415 (sl->getType() == TZP_XSHLEVEL && !sl->getScannedPath().isEmpty()));
416 }
417 }; // locals
418
419 typedef std::set<TFrameId> FramesList;
420
421 /*--- これからCleanupするLevel/FrameIdのリスト ---*/
422 std::map<TXshSimpleLevel *, FramesList>
423 cleanupList; // List of frames to be cleanupped
424 std::vector<TXshSimpleLevel *>
425 levelsList; // List of levels in the cleanup list,
426 // ordered as found in the xsheet.
427 m_cleanupLevels.clear();
428
429 // Retrieve current selection
430 TCellSelection *selection =
431 dynamic_cast<TCellSelection *>(TSelection::getCurrent());
432 TColumnSelection *columnSel =
433 dynamic_cast<TColumnSelection *>(TSelection::getCurrent());
434 /*--- セル選択でも、カラム選択でも無い場合はCleanup自体を無効にする ---*/
435 if (!selection && !columnSel) return;
436
437 TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
438
439 /*--- セル選択の場合 ---*/
440 if (selection) {
441 if (selection->isEmpty()) return;
442 // Store frames in the specified selection
443 int r0, c0, r1, c1;
444 selection->getSelectedCells(r0, c0, r1, c1);
445
446 int r, c;
447 for (c = c0; c <= c1; ++c) {
448 for (r = r0; r <= r1; ++r) {
449 /*---選択範囲内にLevelが無い場合はcontinue---*/
450 const TXshCell &cell = xsh->getCell(r, c);
451 if (cell.isEmpty()) continue;
452 TXshSimpleLevel *sl = cell.getSimpleLevel();
453 if (!(sl && locals::supportsCleanup(sl))) continue;
454 /*---もし新しいLevelなら、Levelのリストに登録---*/
455 std::map<TXshSimpleLevel *, FramesList>::iterator it =
456 cleanupList.find(sl);
457 if (it == cleanupList.end()) {
458 it = cleanupList.insert(std::make_pair(sl, FramesList())).first;
459 levelsList.push_back(sl);
460 }
461 /*---TFrameIdを登録---*/
462 it->second.insert(cell.getFrameId());
463 }
464 }
465 }
466 /*--- カラム選択の場合 ---*/
467 else {
468 int frameCount = xsh->getFrameCount();
469 if (columnSel->isEmpty() || frameCount <= 0) return;
470 /*--- 選択された各カラムについて ---*/
471 std::set<int>::const_iterator it = columnSel->getIndices().begin();
472 for (; it != columnSel->getIndices().end(); ++it) {
473 int c = (*it);
474 for (int r = 0; r < frameCount; r++) {
475 /*--- 選択範囲内にLevelが無い場合はcontinue ---*/
476 const TXshCell &cell = xsh->getCell(r, c);
477 if (cell.isEmpty()) continue;
478 TXshSimpleLevel *sl = cell.getSimpleLevel();
479 if (!sl && locals::supportsCleanup(sl)) continue;
480 /*---もし新しいLevelなら、Levelのリストに登録---*/
481 std::map<TXshSimpleLevel *, FramesList>::iterator it =
482 cleanupList.find(sl);
483 if (it == cleanupList.end()) {
484 it = cleanupList.insert(std::make_pair(sl, FramesList())).first;
485 levelsList.push_back(sl);
486 }
487 /*---TFrameIdを登録---*/
488 it->second.insert(cell.getFrameId());
489 }
490 }
491 }
492
493 // Finally, copy the retrieved data to the sorted output vector
494 std::vector<TXshSimpleLevel *>::iterator lt, lEnd = levelsList.end();
495 for (lt = levelsList.begin(); lt != lEnd; ++lt) {
496 loadCleanupParams(
497 m_params.get(),
498 *lt); // Load cleanup parameters associated with current level.
499 // This is necessary since the output path is specified among them.
500 m_cleanupLevels.push_back(CleanupLevel(*lt, *m_params.get()));
501 CleanupLevel &cl = m_cleanupLevels.back();
502
503 FramesList &framesList = cleanupList[cl.m_sl];
504 cl.m_frames.assign(framesList.begin(), framesList.end());
505 }
506 }
507
508 //-----------------------------------------------------------------------------
509
analyzeCleanupList()510 bool CleanupPopup::analyzeCleanupList() {
511 ToonzScene *scene = TApp::instance()->getCurrentScene()->getScene();
512
513 bool shownOverwriteDialog = false, shownWritingOnSourceFile = false;
514
515 /*--- 消されるLevel名の確認ダイアログを出すため ---*/
516 QList<TFilePath> filePathsToBeDeleted;
517 /* Cleanup前に既存のLevelを消す場合、セルのStatusをScannedに変更するために、
518 TXshSimpleLevelポインタを格納しておく
519 */
520 QList<TXshSimpleLevel *> levelsToBeDeleted;
521
522 // Traverse the cleanup list
523 for (auto &clt : m_cleanupLevels) {
524 TXshSimpleLevel *sl = clt.m_sl;
525
526 /*--- Cleanup対象LevelのCleanupSettingを取得 ---*/
527 loadCleanupParams(m_params.get(),
528 sl); // Needed to retrieve output level resolution
529
530 // Check level existence
531 /*--- Cleanup後に得られるであろうTLVのパス ---*/
532 TFilePath outputPath = scene->decodeFilePath(clt.m_outputPath);
533 {
534 /*-- 出力先にTLVファイルが無ければ問題なし(このLevelはCleanupする) --*/
535 if (!TSystem::doesExistFileOrLevel(outputPath)) {
536 m_levelAlreadyExists[sl] = false;
537 continue; // Newly cleaned up level. No problem.
538 } else
539 m_levelAlreadyExists[sl] = true;
540
541 // Check whether output file == input file
542 /*--- 入力となるScanned(TIFなど)のパスを得る ---*/
543 const TFilePath &inputPath =
544 scene->decodeFilePath(CleanupSettingsModel::getInputPath(sl));
545
546 if (!shownWritingOnSourceFile && inputPath == outputPath) {
547 shownWritingOnSourceFile = true;
548
549 int ret = DVGui::MsgBox(tr("Selected drawings will overwrite the "
550 "original files after the cleanup process.\n"
551 "Do you want to continue?"),
552 tr("Ok"), tr("Cancel"));
553
554 if (ret != 1) return false;
555 }
556
557 /*---一度も上書き確認のダイアログを出していなかったら、ここで出す---*/
558 // Reset the overwrite dialog
559 if (!shownOverwriteDialog) {
560 shownOverwriteDialog = true;
561
562 if (!m_overwriteDialog)
563 m_overwriteDialog.reset(new OverwriteDialog);
564 else
565 m_overwriteDialog->reset();
566 }
567
568 // Prompt user for file conflict resolution
569 clt.m_resolution =
570 Resolution(m_overwriteDialog->execute(&clt.m_outputPath));
571 switch (clt.m_resolution) {
572 case CANCEL:
573 return false;
574
575 case NO_RESOLUTION:
576 assert(false);
577 break;
578 case REPLACE:
579 /*--- 既存のTLVを消すオプションの場合、消されるファイルのリストを作る
580 * ---*/
581 break;
582 case ADD_SUFFIX:
583 continue;
584 case NOPAINT_ONLY:
585 /*--- NOPAINT_ONLY の場合は、nopaintのみを変更。
586 ただし、nopaintのLevelは消さず、処理したフレームを Overwrite
587 する
588 ---*/
589 outputPath =
590 outputPath.getParentDir() + "nopaint\\" +
591 TFilePath(outputPath.getName() + "_np." + outputPath.getType());
592 /*--- nopaintの有無を確かめる。無ければ次のLevelへ
593 * (このLevelはCleanupする) ---*/
594 if (!TSystem::doesExistFileOrLevel(outputPath)) {
595 m_levelAlreadyExists[sl] = false;
596 continue;
597 }
598 default:
599 break;
600 }
601
602 TLevelP level(0); // Current level info. Yeah the init is a shame... :(
603 /*--- 元のLevelと新しいCleanup結果が混合する場合。REPLACE以外 ---*/
604 if (clt.m_resolution == OVERWRITE || clt.m_resolution == WRITE_NEW ||
605 clt.m_resolution == NOPAINT_ONLY) {
606 // Check output resolution consistency
607 // Retrieve file resolution
608 /*---現在在るTLVのサイズと、CleanupSettingsのサイズが一致しているかチェック---*/
609 TDimension oldRes(0, 0);
610 try {
611 TLevelReaderP lr(outputPath);
612 level = lr->loadInfo();
613
614 if (const TImageInfo *imageInfo = lr->getImageInfo())
615 oldRes = TDimension(imageInfo->m_lx, imageInfo->m_ly);
616 else
617 throw TException();
618 } catch (...) {
619 // There was a problem reading the existing level data.
620 // Thus, the conservative approach is not feasible.
621
622 // Inform the user and abort cleanup
623 DVGui::warning(
624 tr("There were errors opening the existing level "
625 "\"%1\".\n\nPlease choose to delete the existing level and "
626 "create a new one\nwhen running the cleanup process.")
627 .arg(QString::fromStdWString(outputPath.getLevelNameW())));
628
629 return false;
630 }
631
632 // Retrieve output resolution
633 TDimension outRes(0, 0);
634 TPointD outDpi;
635 m_params->getOutputImageInfo(outRes, outDpi.x, outDpi.y);
636
637 if (oldRes != outRes) {
638 DVGui::warning(
639 tr("The resulting resolution of level \"%1\"\ndoes not match "
640 "with that of previously cleaned up level drawings.\n\nPlease "
641 "set the right camera resolution and closest field, or choose "
642 "to delete\nthe existing level and create a new one when "
643 "running the cleanup process.")
644 .arg(QString::fromStdWString(outputPath.getLevelNameW())));
645
646 return false;
647 }
648 }
649 /*--- REPLACEの場合、消されるファイルパスのリストを作る ---*/
650 else if (clt.m_resolution == REPLACE) {
651 filePathsToBeDeleted.push_back(outputPath);
652
653 levelsToBeDeleted.push_back(sl);
654
655 /*--- パレットファイルも、あれば消す ---*/
656 TFilePath palettePath =
657 (outputPath.getParentDir() + outputPath.getName()).withType("tpl");
658 if (TSystem::doesExistFileOrLevel(palettePath))
659 filePathsToBeDeleted.push_back(palettePath);
660 /*--- つぎに、nopaintのTLV。これは、REPLACE、NOPAINT_ONLY 両方で消す
661 * ---*/
662 TFilePath unpaintedLevelPath =
663 outputPath.getParentDir() + "nopaint\\" +
664 TFilePath(outputPath.getName() + "_np." + outputPath.getType());
665 if (TSystem::doesExistFileOrLevel(unpaintedLevelPath)) {
666 filePathsToBeDeleted.push_back(unpaintedLevelPath);
667 filePathsToBeDeleted.push_back(
668 (unpaintedLevelPath.getParentDir() + unpaintedLevelPath.getName())
669 .withType("tpl"));
670 }
671 }
672
673 // Finally, apply resolution to individual frames.
674 /*--- WRITE_NEW は、「未Cleanupのフレームだけ処理する」オプション ---*/
675 if (clt.m_resolution == WRITE_NEW) {
676 const TLevel::Table *table = level->getTable();
677
678 clt.m_frames.erase(
679 std::remove_if(clt.m_frames.begin(), clt.m_frames.end(),
680 [table](TLevel::Table::key_type const &key) {
681 return table->count(key);
682 }),
683 clt.m_frames.end());
684 }
685 }
686 }
687
688 /*--- ファイル消去の確認ダイアログを表示 ---*/
689 if (!filePathsToBeDeleted.isEmpty()) {
690 QString question = QObject::tr(
691 "Delete and Re-cleanup : The following files will be deleted.\n\n");
692 for (int i = 0; i < filePathsToBeDeleted.size(); i++) {
693 question +=
694 " " +
695 QString::fromStdWString(filePathsToBeDeleted[i].getWideString()) +
696 "\n";
697 }
698 question += QObject::tr("\nAre you sure ?");
699
700 int ret = DVGui::MsgBox(question, QObject::tr("Delete"),
701 QObject::tr("Cancel"), 0);
702 if (ret == 0 || ret == 2) {
703 return false;
704 } else if (ret == 1) {
705 /*--- 先にCleanup処理で出力先となるファイルを消す ---*/
706 try {
707 for (int i = 0; i < filePathsToBeDeleted.size(); i++) {
708 TSystem::removeFileOrLevel_throw(filePathsToBeDeleted[i]);
709 }
710 } catch (...) {
711 return false;
712 }
713
714 // Reset level status
715 for (int i = 0; i < levelsToBeDeleted.size(); i++) {
716 TXshSimpleLevel *lev = levelsToBeDeleted.at(i);
717 /*--- TLVだった場合、Scanned(TIFレベル)に戻す ---*/
718 TFilePath scannedPath = lev->getScannedPath();
719 if (scannedPath != TFilePath()) {
720 lev->setScannedPath(TFilePath());
721 lev->setPath(scannedPath, true);
722 lev->clearFrames();
723 lev->setType(OVL_XSHLEVEL); // OVL_XSHLEVEL
724 lev->setPalette(0);
725 if (lev == TApp::instance()->getCurrentLevel()->getLevel())
726 TApp::instance()
727 ->getPaletteController()
728 ->getCurrentLevelPalette()
729 ->setPalette(0);
730
731 lev->load();
732 int i, frameCount = lev->getFrameCount();
733 for (i = 0; i < frameCount; i++) {
734 TFrameId id = lev->index2fid(i);
735 IconGenerator::instance()->invalidate(lev, id);
736 }
737 IconGenerator::instance()->invalidateSceneIcon();
738 }
739 }
740 }
741 }
742
743 // Before returning, erase levels whose frames list is empty
744 /*--- Cleanup対象フレームが無くなったLevelを対象から外す ---*/
745 m_cleanupLevels.erase(
746 std::remove_if(m_cleanupLevels.begin(), m_cleanupLevels.end(),
747 std::mem_fn(&CleanupLevel::empty)),
748 m_cleanupLevels.end());
749
750 return true;
751 }
752
753 //-----------------------------------------------------------------------------
754
isValidPosition(const std::pair<int,int> & pos) const755 bool CleanupPopup::isValidPosition(const std::pair<int, int> &pos) const {
756 if (pos.first < 0 || int(m_cleanupLevels.size()) <= pos.first) return false;
757
758 const CleanupLevel &cl = m_cleanupLevels[pos.first];
759
760 if (pos.second < 0 || int(cl.m_frames.size()) <= pos.second) return false;
761
762 return true;
763 }
764
765 //-----------------------------------------------------------------------------
766
currentString() const767 QString CleanupPopup::currentString() const {
768 if (!isValidPosition(m_idx)) return QString();
769
770 const CleanupLevel &cl = m_cleanupLevels[m_idx.first];
771
772 TXshSimpleLevel *sl = cl.m_sl;
773 const TFrameId &fid = cl.m_frames[m_idx.second];
774
775 TFilePath scannedPath(sl->getScannedPath());
776 if (scannedPath.isEmpty()) scannedPath = sl->getPath();
777
778 TFilePath levelName(scannedPath.getLevelNameW());
779 QString imageName = toQString(levelName.withFrame(fid));
780
781 return tr("Cleanup in progress: ") + imageName + " " +
782 QString::number(m_completion.first) + "/" +
783 QString::number(m_completion.second);
784 }
785
786 //-----------------------------------------------------------------------------
787 /*--- これからCleanupするフレームを取得 ---*/
currentImage() const788 TImageP CleanupPopup::currentImage() const {
789 if (!isValidPosition(m_idx)) return TImageP();
790
791 const CleanupLevel &cl = m_cleanupLevels[m_idx.first];
792 return cl.m_sl->getFrameToCleanup(cl.m_frames[m_idx.second]);
793 }
794
795 //-----------------------------------------------------------------------------
796
execute()797 void CleanupPopup::execute() {
798 struct locals {
799 static inline int addFrames(int a, const CleanupLevel &cl) {
800 return a + int(cl.m_frames.size());
801 }
802 }; // locals
803
804 // Re-initialize the list of frames to be cleanupped
805 /*--- Cleanup対象のリストを作る ---*/
806 buildCleanupList();
807
808 // In case some cleaned up level already exists, let the user decide what to
809 // do
810 if (!analyzeCleanupList()) {
811 reset();
812 return;
813 }
814
815 // Initialize completion variable
816 m_completion = std::pair<int, int>(
817 0, std::accumulate(m_cleanupLevels.begin(), m_cleanupLevels.end(), 0,
818 locals::addFrames));
819
820 // If there are no (more) frames to cleanup, warn and quit
821 int framesCount = m_completion.second;
822 if (!framesCount) {
823 DVGui::info(
824 tr("It is not possible to cleanup: the cleanup list is empty."));
825
826 reset();
827 return;
828 }
829
830 // Initialize the cleanup process and show the popup
831
832 m_idx = std::pair<int, int>(0, 0);
833
834 // Reset progress bar
835 m_progressLabel->setText(currentString());
836
837 m_progressBar->setRange(0, framesCount);
838 m_progressBar->setValue(0);
839
840 // show the progress to the main window's title bar
841 updateTitleString();
842
843 TImageP image = currentImage();
844 if (image) {
845 m_imageViewer->setImage(image);
846
847 // Set the zoom factor depending on image and viewer sizes, so that the
848 // image fits
849 // the preview area.
850
851 QSize viewSize = m_imageViewer->size();
852 QSize imageSize = QSize(image->getBBox().getLx(), image->getBBox().getLy());
853 double factor = getBestFactor(viewSize, imageSize);
854 TPointD delta(0, 0);
855 TAffine viewAff =
856 TTranslation(delta) * TScale(factor) * TTranslation(-delta);
857 m_imageViewer->setViewAff(viewAff);
858 }
859
860 show();
861 }
862
863 //-----------------------------------------------------------------------------
864
setupLevel()865 QString CleanupPopup::setupLevel() {
866 // Level's pre-cleanup stuff initialization.
867 // Invoked right before the cleanupFrame() of the first level frame.
868
869 assert(isValidPosition(m_idx));
870
871 TApp *app = TApp::instance();
872 ToonzScene *scene = app->getCurrentScene()->getScene();
873
874 /*--- これからCleanupするLevel ---*/
875 CleanupLevel &cl = m_cleanupLevels[m_idx.first];
876 TXshSimpleLevel *sl = cl.m_sl;
877
878 /*--- 保存先のTLVが既に存在する、かつ、REPLACE でも
879 NOPAINT_ONLY でもない場合、Paletteを変更せず維持する ---*/
880 if (cl.m_resolution != REPLACE && cl.m_resolution != NOPAINT_ONLY &&
881 m_levelAlreadyExists[sl] == true)
882 m_keepOriginalPalette = true;
883 else
884 m_keepOriginalPalette = false;
885
886 // Update cleanup parameters, loading a cln if necessary
887 TCleanupper *cleanupper = TCleanupper::instance();
888
889 /*--- CleanupSettingsを読み込み、TCleanupperに渡す ---*/
890 loadCleanupParams(m_params.get(), sl);
891 cleanupper->setParameters(m_params.get());
892
893 // Touch the output parent directory
894 TFilePath &outputPath = cl.m_outputPath;
895
896 /*--- 保存先PathをFull Pathにする ---*/
897 TFilePath decodedPath(scene->decodeFilePath(outputPath));
898 if (!TSystem::touchParentDir(decodedPath))
899 return tr("Couldn't create directory \"%1\"").arg(decodedPath.getQString());
900
901 /*---
902 * 上書きオプションが選択されているとき、既存のLevel,Palette,Nopaintファイルを消す
903 * ---*/
904 // If the user decided to remove any existing level, do so now.
905 if (cl.m_resolution == REPLACE) {
906 const QString &err = resetLevel();
907
908 if (!err.isEmpty()) return err;
909 }
910 /*--- Nopaintのみ上書きのオプションが選択されているとき(再Clenaupの場合)
911 ---*/
912 else if (cl.m_resolution == NOPAINT_ONLY) {
913 m_originalLevelPath = outputPath;
914 /*--- 必要なら、nopaintフォルダを作成 ---*/
915 TFilePath nopaintDir = decodedPath.getParentDir() + "nopaint";
916 if (!TFileStatus(nopaintDir).doesExist()) {
917 try {
918 TSystem::mkDir(nopaintDir);
919 } catch (...) {
920 return NULL;
921 }
922 }
923 /*--- 保存先のパスをnopaintの方に変更 ---*/
924 outputPath =
925 outputPath.getParentDir() + "nopaint\\" +
926 TFilePath(outputPath.getName() + "_np." + outputPath.getType());
927 decodedPath = scene->decodeFilePath(outputPath);
928 }
929
930 // Frames are cleaned-up at full-sampling. Thus, clear subsampling on the
931 // level.
932 if (sl->getProperties()->getSubsampling() != 1) {
933 sl->getProperties()->setSubsampling(1);
934 sl->invalidateFrames();
935 }
936
937 bool lineProcessing = (m_params->m_lineProcessingMode != lpNone),
938 notLineProcessed = (sl->getType() != TZP_XSHLEVEL);
939
940 if (lineProcessing) {
941 /*--- Keep original palette which will be reverted after cleanup ---*/
942 if (m_keepOriginalPalette) {
943 if ((sl->getType() == TZP_XSHLEVEL || sl->getType() == TZI_XSHLEVEL) &&
944 sl->getPalette() != NULL)
945 m_originalPalette = sl->getPalette()->clone();
946 else /*--- In case the level has been already cleanupped,
947 and is cleanupped again from raster level ---*/
948 {
949 /*--- Load and keep the palette from destination TLV ---*/
950 TFilePath targetPalettePath = outputPath.getParentDir() +
951 TFilePath(outputPath.getName() + ".tpl");
952 TFileStatus pfs(targetPalettePath);
953 if (pfs.doesExist() && pfs.isReadable()) {
954 TIStream is(targetPalettePath);
955 std::string tagName;
956 if (!is.matchTag(tagName) || tagName != "palette") {
957 DVGui::warning(QString(
958 "CleanupDefaultPalette file: This is not palette file"));
959 return NULL;
960 }
961 m_originalPalette = new TPalette();
962 m_originalPalette->loadData(is);
963 } else
964 m_originalPalette = 0;
965 }
966 }
967
968 if (notLineProcessed) {
969 /*-- Type, Pathを切り替えてTLVにする --*/
970 // The level type changes to TLV
971 sl->makeTlv(outputPath);
972
973 // Remap all current images under the IM control to Scanned status.
974 int f, fCount = sl->getFrameCount();
975
976 for (f = 0; f != fCount; ++f) {
977 const TFrameId &fid = sl->getFrameId(f);
978
979 /*--- 「スキャン済み」のステータスにし、画像、アイコンのIDを切り替える
980 * ---*/
981 sl->setFrameStatus(fid, TXshSimpleLevel::Scanned);
982 ImageManager::instance()->rebind(
983 sl->getImageId(fid, 0),
984 sl->getImageId(fid, TXshSimpleLevel::Scanned));
985
986 const std::string &oldIconId = sl->getIconId(fid, 0);
987 const std::string &newIconId =
988 sl->getIconId(fid, TXshSimpleLevel::Scanned);
989
990 IconGenerator::instance()->remap(newIconId, oldIconId);
991 }
992 }
993 /*--- 対象が既にTLVファイルで、出力先パスが違う場合、切り替える ---*/
994 else if (outputPath != sl->getPath()) {
995 // Just wants to be written to a different destination path. Update it.
996 sl->setPath(outputPath, false);
997 }
998
999 /*-- Cleanup用のパレットを作る --*/
1000 // Update the level palette
1001 TPaletteP palette =
1002 TCleanupper::instance()->createToonzPaletteFromCleanupPalette();
1003
1004 sl->setPalette(palette.getPointer());
1005
1006 /*--- カレントPaletteを切り替える ---*/
1007 if (sl == app->getCurrentLevel()->getLevel())
1008 app->getPaletteController()->getCurrentLevelPalette()->setPalette(
1009 palette.getPointer());
1010
1011 // Notify the xsheet that the level has changed visual type informations
1012 // (either the level type,
1013 // cleanup status, etc)
1014 app->getCurrentXsheet()->notifyXsheetChanged();
1015 } else if (!m_params->getPath(scene)
1016 .isEmpty()) // Should never be empty, AFAIK...
1017 {
1018 // No line processing
1019
1020 if (notLineProcessed) {
1021 // Just change paths
1022 if (sl->getScannedPath().isEmpty()) sl->setScannedPath(sl->getPath());
1023
1024 sl->setPath(outputPath, false); // Reload frames from the result, too
1025 } else {
1026 // Return to scan level type
1027 sl->clearFrames();
1028 sl->setType(OVL_XSHLEVEL);
1029 sl->setPath(outputPath);
1030 }
1031 }
1032
1033 // Finally, open the LevelUpdater on the level.
1034 assert(!m_updater->opened());
1035 try {
1036 m_updater->open(sl);
1037 } catch (...) {
1038 return tr("Couldn't open \"%1\" for write").arg(outputPath.getQString());
1039 }
1040
1041 m_updater->getLevelWriter()->setOverwritePaletteFlag(!m_keepOriginalPalette);
1042
1043 return QString();
1044 }
1045
1046 //-----------------------------------------------------------------------------
1047 /* setupLevel()から、 m_overwriteAction == REPLACE のとき呼ばれる。
1048 選択LevelのCleanup後Levelを消す
1049 */
resetLevel()1050 QString CleanupPopup::resetLevel() {
1051 struct locals {
1052 static bool removeFileOrLevel(const TFilePath &fp) {
1053 return (!TSystem::doesExistFileOrLevel(fp) ||
1054 TSystem::removeFileOrLevel(fp));
1055 }
1056
1057 static QString decorate(const TFilePath &fp) {
1058 return tr("Couldn't remove file \"%1\"").arg(fp.getQString());
1059 }
1060 };
1061
1062 // Try to remove the existing level
1063 const CleanupLevel &cl = m_cleanupLevels[m_idx.first];
1064
1065 TXshSimpleLevel *sl = cl.m_sl;
1066 assert(sl);
1067
1068 ToonzScene *scene = TApp::instance()->getCurrentScene()->getScene();
1069
1070 /*--- Cleanup後のTLVファイルパスを得る。すなわちこれから消すファイル ---*/
1071 // Ensure outputPath != inputPath
1072 const TFilePath &outputPath = scene->decodeFilePath(cl.m_outputPath),
1073 &inputPath = scene->decodeFilePath(
1074 CleanupSettingsModel::getInputPath(sl));
1075
1076 if (inputPath == outputPath)
1077 return QString(); // Cannot remove source file - however, it can be
1078 // overwritten
1079
1080 // Remove existing output files
1081 if (!locals::removeFileOrLevel(outputPath))
1082 return locals::decorate(outputPath);
1083
1084 if (m_params->m_lineProcessingMode != lpNone) {
1085 TFilePath fp;
1086
1087 // Line processing on - remove palette too
1088 if (!locals::removeFileOrLevel(fp = outputPath.withType("tpl")))
1089 return locals::decorate(fp);
1090
1091 // Also remove unpainted output path if any
1092 const TFilePath &unpaintedPath(
1093 outputPath.getParentDir() + "nopaint\\" +
1094 TFilePath(outputPath.getName() + "_np." + outputPath.getType()));
1095
1096 if (!locals::removeFileOrLevel(unpaintedPath))
1097 return locals::decorate(unpaintedPath);
1098
1099 if (!locals::removeFileOrLevel(fp = unpaintedPath.withType("tpl")))
1100 return locals::decorate(fp);
1101 }
1102
1103 // Reset level status
1104 sl->setPath(sl->getPath(), false); // false rebuilds level data
1105 // NOTE: sl is the INPUT level - so this instruction
1106 return QString(); // should take place AFTER output availability
1107 // has been ensured.
1108 }
1109
1110 //-----------------------------------------------------------------------------
1111 /*---
1112 * 現在処理を行っているLevelの最後のフレームの処理が終わってから、フレームを進めるときに呼ばれる
1113 * ---*/
closeLevel()1114 void CleanupPopup::closeLevel() {
1115 if (m_cleanuppedLevelFrames.empty()) {
1116 if (m_updater->opened()) m_updater->close();
1117 return;
1118 }
1119
1120 // Save the unpainted level if necessary
1121 const CleanupLevel &cl = m_cleanupLevels[m_idx.first];
1122
1123 TXshSimpleLevel *sl = cl.m_sl;
1124 assert(sl);
1125
1126 /*--- Nopaintのみ上書きの場合、Cleanup前に戻す ---*/
1127 if (cl.m_resolution == NOPAINT_ONLY && !m_originalLevelPath.isEmpty()) {
1128 sl->setPath(m_originalLevelPath);
1129 sl->invalidateFrames();
1130 std::vector<TFrameId> fIds;
1131 sl->getFids(fIds);
1132 invalidateIcons(sl, fIds);
1133 m_originalLevelPath = TFilePath();
1134 }
1135 /*--- Paletteを更新しない場合は、ここで取っておいたPaletteデータを元に戻す
1136 * ---*/
1137 if (m_keepOriginalPalette && m_originalPalette) {
1138 sl->setPalette(m_originalPalette);
1139 if (sl == TApp::instance()->getCurrentLevel()->getLevel())
1140 TApp::instance()
1141 ->getPaletteController()
1142 ->getCurrentLevelPalette()
1143 ->setPalette(m_originalPalette);
1144 sl->invalidateFrames();
1145 std::vector<TFrameId> fIds;
1146 sl->getFids(fIds);
1147 invalidateIcons(sl, fIds);
1148 TApp::instance()->getCurrentPalette()->notifyPaletteSwitched();
1149 }
1150
1151 // Close the level updater. Silence any exception (ok, something went bad, old
1152 // story now).
1153 try {
1154 m_updater->close();
1155 } catch (...) {
1156 }
1157
1158 if (sl->getType() == TZP_XSHLEVEL &&
1159 Preferences::instance()->isSaveUnpaintedInCleanupEnable() &&
1160 cl.m_resolution !=
1161 NOPAINT_ONLY) /*--- 再Cleanupの場合は既にNoPaintに上書きしている ---*/
1162 {
1163 const TFilePath &outputPath = cl.m_outputPath;
1164
1165 ToonzScene *scene = TApp::instance()->getCurrentScene()->getScene();
1166 TFilePath decodedPath(scene->decodeFilePath(outputPath));
1167
1168 if (outputPath.getLevelNameW().find(L"-np.") == std::wstring::npos &&
1169 TFileStatus(decodedPath).doesExist()) {
1170 saveUnpaintedLevel(decodedPath, sl, m_cleanuppedLevelFrames,
1171 (m_keepOriginalPalette && m_originalPalette));
1172 }
1173 }
1174
1175 m_cleanuppedLevelFrames.clear();
1176 m_firstLevelFrame = true;
1177 }
1178
1179 //-----------------------------------------------------------------------------
1180 /*-- 各フレームの処理 --*/
cleanupFrame()1181 void CleanupPopup::cleanupFrame() {
1182 assert(isValidPosition(m_idx));
1183
1184 TRasterImageP original(currentImage());
1185 if (!original) return;
1186
1187 CleanupLevel &cl = m_cleanupLevels[m_idx.first];
1188 TXshSimpleLevel *sl = cl.m_sl;
1189 const TFrameId &fid = cl.m_frames[m_idx.second];
1190
1191 assert(sl);
1192
1193 // Perform image cleanup
1194 {
1195 TCleanupper *cl = TCleanupper::instance();
1196 const CleanupParameters *params = cl->getParameters();
1197
1198 if (params->m_lineProcessingMode == lpNone) {
1199 // No line processing
1200
1201 TRasterImageP ri(original);
1202 if (params->m_autocenterType != CleanupTypes::AUTOCENTER_NONE) {
1203 bool autocentered;
1204 ri = cl->autocenterOnly(original, false, autocentered);
1205 if (!autocentered)
1206 DVGui::warning(
1207 QObject::tr("The autocentering failed on the current drawing."));
1208 }
1209
1210 sl->setFrame(fid, ri);
1211
1212 // Update the associated file. In case the operation throws, oh well the
1213 // image gets skipped.
1214 try {
1215 m_updater->update(fid, ri);
1216 } catch (...) {
1217 }
1218
1219 IconGenerator::instance()->invalidate(sl, fid);
1220 } else {
1221 // Perform main processing
1222
1223 // Obtain the source dpi. Changed it to be done once at the first frame of
1224 // each level in order to avoid the following problem:
1225 // If the original raster level has no dpi (such as TGA images), obtaining
1226 // dpi in every frame causes dpi mismatch between the first frame and the
1227 // following frames, since the value
1228 // TXshSimpleLevel::m_properties->getDpi() will be changed to the
1229 // dpi of cleanup camera (= TLV's dpi) after finishing the first frame.
1230 if (m_firstLevelFrame) {
1231 TPointD dpi;
1232 original->getDpi(dpi.x, dpi.y);
1233 if (dpi.x == 0 && dpi.y == 0) dpi = sl->getProperties()->getDpi();
1234 cl->setSourceDpi(dpi);
1235 }
1236
1237 CleanupPreprocessedImage *cpi;
1238 {
1239 TRasterImageP resampledRaster;
1240 cpi = cl->process(original, m_firstLevelFrame, resampledRaster);
1241 }
1242
1243 if (!cpi) return;
1244
1245 // Perform post-processing
1246 TToonzImageP ti(cl->finalize(cpi));
1247
1248 /*--- Cleanup Default Paletteを作成、適用 ---*/
1249 if (m_firstLevelFrame) {
1250 addCleanupDefaultPalette(sl);
1251 sl->getPalette()->setPaletteName(sl->getName());
1252 }
1253
1254 ti->setPalette(sl->getPalette()); // Assigned to sl in setupLevel()
1255 assert(sl->getPalette());
1256
1257 // Update the level data about the cleanupped frame
1258 sl->setFrameStatus(fid,
1259 sl->getFrameStatus(fid) | TXshSimpleLevel::Cleanupped);
1260
1261 // sl->setFrame(fid, TImageP()); // Invalidate the old image data
1262 sl->setFrame(fid, ti); // replace with the new image data
1263
1264 // Output the cleanupped image to disk
1265 try {
1266 m_updater->update(fid, ti);
1267 } // The file image data will be reloaded upon request
1268 catch (...) {
1269 }
1270
1271 // Invalidate icons
1272 IconGenerator::instance()->invalidate(sl, fid);
1273
1274 int autocenterType = params->m_autocenterType;
1275 if (autocenterType == CleanupTypes::AUTOCENTER_FDG &&
1276 !cpi->m_autocentered)
1277 DVGui::warning(
1278 QObject::tr("The autocentering failed on the current drawing."));
1279
1280 delete cpi;
1281
1282 if (m_firstLevelFrame) {
1283 // Update result-dependent level data
1284 TPointD dpi(0, 0);
1285 ti->getDpi(dpi.x, dpi.y);
1286 if (dpi.x != 0 && dpi.y != 0) sl->getProperties()->setDpi(dpi);
1287
1288 sl->getProperties()->setImageRes(ti->getSize());
1289 sl->getProperties()->setBpp(32);
1290 }
1291 }
1292 }
1293
1294 // this enables to view the level during cleanup by another user. this
1295 // behavior may abort Toonz.
1296 /*
1297 try { m_updater->flush(); } // Release
1298 the opened level from writing
1299 catch(...) {} // It is
1300 required to have it open for read
1301 // when
1302 rebuilding icons... (still dangerous though)
1303 */
1304 m_firstLevelFrame = false;
1305 m_cleanuppedLevelFrames.push_back(fid);
1306
1307 TApp *app = TApp::instance();
1308 app->getCurrentLevel()->notifyLevelChange();
1309 app->getCurrentXsheet()->notifyXsheetChanged();
1310 }
1311
1312 //-----------------------------------------------------------------------------
1313
advanceFrame()1314 void CleanupPopup::advanceFrame() {
1315 assert(isValidPosition(m_idx));
1316
1317 std::pair<int, int> newIdx = std::make_pair(m_idx.first, m_idx.second + 1);
1318
1319 // In case the level was completely processed, close down the old level
1320 if (!isValidPosition(newIdx)) {
1321 if (!m_cleanuppedLevelFrames.empty()) closeLevel();
1322
1323 newIdx = std::make_pair(m_idx.first + 1, 0);
1324 }
1325
1326 // Advance in the cleanup list
1327 m_idx = newIdx;
1328
1329 if (m_imgViewBox->isChecked()) {
1330 TImageP image = currentImage();
1331 if (image) m_imageViewer->setImage(image);
1332 }
1333
1334 // show the progress in the mainwindow's title bar
1335 updateTitleString();
1336
1337 // Update the progress bar
1338 m_progressBar->setValue(++m_completion.first);
1339 }
1340
1341 //-----------------------------------------------------------------------------
1342
onValueChanged(int value)1343 void CleanupPopup::onValueChanged(int value) {
1344 if (value == m_progressBar->maximum()) {
1345 close();
1346 return;
1347 }
1348
1349 m_progressLabel->setText(currentString());
1350 }
1351
1352 //-----------------------------------------------------------------------------
1353
onCleanupFrame()1354 void CleanupPopup::onCleanupFrame() {
1355 /*--- Busy時にボタンをUnableする ---*/
1356 m_cleanupAllButton->setEnabled(false);
1357 m_cleanupButton->setEnabled(false);
1358 m_skipButton->setEnabled(false);
1359
1360 /*--- 新しいLevelに取り掛かり始めたとき ---*/
1361 if (m_cleanuppedLevelFrames.empty()) {
1362 const QString &err = setupLevel();
1363
1364 if (!err.isEmpty()) {
1365 DVGui::error(err);
1366 return;
1367 }
1368 }
1369
1370 cleanupFrame();
1371 advanceFrame();
1372
1373 /*--- ボタンを元に戻す---*/
1374 m_cleanupAllButton->setEnabled(true);
1375 m_cleanupButton->setEnabled(true);
1376 m_skipButton->setEnabled(true);
1377 }
1378
1379 //-----------------------------------------------------------------------------
1380
onSkipFrame()1381 void CleanupPopup::onSkipFrame() { advanceFrame(); }
1382
1383 //-----------------------------------------------------------------------------
1384
onCleanupAllFrame()1385 void CleanupPopup::onCleanupAllFrame() {
1386 m_cleanupQuestionLabel->hide();
1387
1388 /*--- Busy時にボタンをUnableする ---*/
1389 m_cleanupAllButton->setEnabled(false);
1390 m_cleanupButton->setEnabled(false);
1391 m_skipButton->setEnabled(false);
1392
1393 while (isValidPosition(m_idx)) {
1394 if (m_cleanuppedLevelFrames.empty()) {
1395 const QString &err = setupLevel();
1396
1397 if (!err.isEmpty()) {
1398 DVGui::error(err);
1399 return;
1400 }
1401 }
1402
1403 cleanupFrame();
1404 advanceFrame();
1405
1406 QCoreApplication::processEvents(); // Allow cancels to be received
1407 }
1408 /*--- ボタンを元に戻す---*/
1409 m_cleanupAllButton->setEnabled(true);
1410 m_cleanupButton->setEnabled(true);
1411 m_skipButton->setEnabled(true);
1412
1413 close();
1414 }
1415
1416 //-----------------------------------------------------------------------------
1417
onCancelCleanup()1418 void CleanupPopup::onCancelCleanup() { close(); }
1419
1420 //*****************************************************************************
1421 // CleanupPopup::OverwriteDialog implementation
1422 //*****************************************************************************
1423
OverwriteDialog()1424 CleanupPopup::OverwriteDialog::OverwriteDialog()
1425 : DVGui::ValidatedChoiceDialog(TApp::instance()->getMainWindow()) {
1426 setWindowTitle(tr("Warning!"));
1427
1428 bool ret = connect(m_buttonGroup, SIGNAL(buttonClicked(int)),
1429 SLOT(onButtonClicked(int)));
1430 assert(ret);
1431
1432 // Option 1: OVERWRITE
1433 QRadioButton *radioButton = new QRadioButton;
1434 radioButton->setText(
1435 tr("Cleanup all selected drawings overwriting those previously cleaned "
1436 "up.*"));
1437 radioButton->setFixedHeight(20);
1438 radioButton->setChecked(true); // initial option: OVERWRITE
1439
1440 m_buttonGroup->addButton(radioButton, OVERWRITE);
1441 addWidget(radioButton);
1442
1443 // Option 2: WRITE_NEW
1444 radioButton = new QRadioButton;
1445 radioButton->setText(
1446 tr("Cleanup only non-cleaned up drawings and keep those previously "
1447 "cleaned up.*"));
1448 radioButton->setFixedHeight(20);
1449
1450 m_buttonGroup->addButton(radioButton, WRITE_NEW);
1451 addWidget(radioButton);
1452
1453 // Option 3: REPLACE
1454 radioButton = new QRadioButton;
1455 radioButton->setText(
1456 tr("Delete existing level and create a new level with selected drawings "
1457 "only."));
1458 radioButton->setFixedHeight(20);
1459
1460 m_buttonGroup->addButton(radioButton, REPLACE);
1461 addWidget(radioButton);
1462
1463 // Option 4: ADD_SUFFIX
1464 QHBoxLayout *suffixLayout = new QHBoxLayout;
1465 {
1466 radioButton = new QRadioButton;
1467 radioButton->setText(tr("Rename the new level adding the suffix "));
1468 radioButton->setFixedHeight(20);
1469
1470 m_buttonGroup->addButton(radioButton, ADD_SUFFIX);
1471 suffixLayout->addWidget(radioButton);
1472
1473 m_suffix = new DVGui::LineEdit;
1474 m_suffix->setEnabled(false);
1475
1476 suffixLayout->addWidget(m_suffix);
1477 }
1478 addLayout(suffixLayout); // Couldnt' place it right after allocation,
1479 // DVGui::Dialog::addLayout() crashed...
1480 // Option 5: NOPAINT_ONLY
1481 radioButton = new QRadioButton(this);
1482 radioButton->setText(
1483 tr("This is Re-Cleanup. Overwrite only to the no-paint files."));
1484 radioButton->setFixedHeight(20);
1485 m_buttonGroup->addButton(radioButton, NOPAINT_ONLY);
1486 addWidget(radioButton);
1487
1488 QLabel *note = new QLabel(tr("* Palette will not be changed."), this);
1489 note->setStyleSheet("font-size: 10px; font: italic;");
1490 addWidget(note);
1491
1492 endVLayout();
1493
1494 layout()->setSizeConstraint(QLayout::SetFixedSize);
1495 }
1496
1497 //-----------------------------------------------------------------------------
1498
reset()1499 void CleanupPopup::OverwriteDialog::reset() {
1500 ValidatedChoiceDialog::reset();
1501 m_suffixText.clear();
1502 }
1503
1504 //-----------------------------------------------------------------------------
1505
acceptResolution(void * obj,int resolution,bool applyToAll)1506 QString CleanupPopup::OverwriteDialog::acceptResolution(void *obj,
1507 int resolution,
1508 bool applyToAll) {
1509 struct locals {
1510 static inline QString existsStr(const TFilePath &fp) {
1511 return tr("File \"%1\" already exists.\nWhat do you want to do?")
1512 .arg(fp.getQString());
1513 }
1514 }; // locals
1515
1516 assert(obj);
1517
1518 TFilePath &fp = *static_cast<TFilePath *>(obj);
1519
1520 QString error;
1521
1522 switch (resolution) {
1523 case NO_RESOLUTION:
1524 // fp was already found to be invalid
1525 assert(::exists(fp));
1526 error = locals::existsStr(fp);
1527
1528 // Restore previous apply-to-all options if necessary
1529 if (!error.isEmpty() && appliedToAll()) {
1530 assert(!m_suffixText.isEmpty());
1531 m_suffix->setText(m_suffixText);
1532 }
1533 break;
1534
1535 case ADD_SUFFIX:
1536 // Save resolution options if necessary
1537 if (applyToAll) m_suffixText = m_suffix->text();
1538
1539 // Test produced file path
1540 const TFilePath &fp_suf =
1541 fp.withName(fp.getWideName() + m_suffix->text().toStdWString());
1542
1543 if (::exists(fp_suf))
1544 error = locals::existsStr(fp_suf);
1545 else
1546 fp = fp_suf;
1547
1548 break;
1549 }
1550
1551 return error;
1552 }
1553
1554 //-----------------------------------------------------------------------------
1555
initializeUserInteraction(const void * obj)1556 void CleanupPopup::OverwriteDialog::initializeUserInteraction(const void *obj) {
1557 const TFilePath &fp = *static_cast<const TFilePath *>(obj);
1558
1559 // Generate a suitable initial suffix
1560 int num = 1;
1561 for (; ::exists(fp, num); ++num)
1562 ;
1563
1564 m_suffix->setText(::suffix(num));
1565 }
1566
1567 //-----------------------------------------------------------------------------
1568
onButtonClicked(int buttonId)1569 void CleanupPopup::OverwriteDialog::onButtonClicked(int buttonId) {
1570 m_suffix->setEnabled(buttonId == ADD_SUFFIX);
1571 }
1572
1573 //-----------------------------------------------------------------------------
1574
onImgViewBoxToggled(bool on)1575 void CleanupPopup::onImgViewBoxToggled(bool on) {
1576 m_imageViewer->setVisible(on);
1577 }
1578
1579 //-----------------------------------------------------------------------------
1580 /*! Show the progress in the mainwindow's title bar
1581 */
updateTitleString()1582 void CleanupPopup::updateTitleString() {
1583 if (!TApp::instance()->getMainWindow()) return;
1584 MainWindow *mainWin =
1585 qobject_cast<MainWindow *>(TApp::instance()->getMainWindow());
1586 if (!mainWin) return;
1587
1588 QString str = QString::number(m_completion.first) + "/" +
1589 QString::number(m_completion.second) +
1590 tr(" : Cleanup in progress");
1591
1592 mainWin->changeWindowTitle(str);
1593 }
1594
1595 //*****************************************************************************
1596 // CleanupCommand definition
1597 //*****************************************************************************
1598
1599 class CleanupCommand final : public MenuItemHandler {
1600 public:
CleanupCommand()1601 CleanupCommand() : MenuItemHandler("MI_Cleanup") {}
1602
execute()1603 void execute() override {
1604 static CleanupPopup *popup = new CleanupPopup;
1605 popup->execute();
1606 }
1607
1608 } CleanupCommand;
1609