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 &params)
275       : m_sl(sl)
276       , m_outputPath(CleanupSettingsModel::getOutputPath(m_sl, &params))
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