#include "cleanuppopup.h" // Toonz includes #include "tapp.h" #include "imageviewer.h" #include "cleanupsettingsmodel.h" #include "cellselection.h" #include "columnselection.h" #include "mainwindow.h" // ToonzQt includes #include "toonzqt/gutil.h" #include "toonzqt/dvdialog.h" #include "toonzqt/lineedit.h" #include "toonzqt/menubarcommand.h" #include "toonzqt/icongenerator.h" // ToonzLib includes #include "toonz/toonzscene.h" #include "toonz/txshcell.h" #include "toonz/txshsimplelevel.h" #include "toonz/txshleveltypes.h" #include "toonz/levelproperties.h" #include "toonz/imagemanager.h" #include "toonz/levelupdater.h" #include "toonz/tcleanupper.h" #include "toonz/preferences.h" #include "toonz/tscenehandle.h" #include "toonz/txsheethandle.h" #include "toonz/txshlevelhandle.h" #include "toonz/palettecontroller.h" #include "toonz/tpalettehandle.h" #include "toonz/toonzfolders.h" // TnzCore includes #include "tsystem.h" #include "tlevel_io.h" #include "timageinfo.h" #include "tstream.h" // Qt includes #include #include #include #include #include #include #include // STL includes #include #include #include #include //***************************************************************************** // Local namespace stuff //***************************************************************************** namespace { enum Resolution { NO_RESOLUTION, //!< No required resolution. CANCEL, //!< Validation was canceled. OVERWRITE, //!< Does not delete old cleanupped levels, but overwrites found //! frames. WRITE_NEW, //!< Like above, but does not overwrite. Just adds not cleanupped //! frames. REPLACE, //!< Destroy the old level and build one anew. ADD_SUFFIX, //!< Add a suffix to the output path. NOPAINT_ONLY //!< overwrite the result only in "nopaint" folder }; //----------------------------------------------------------------------------- static const std::wstring unpaintedStr = L"-unpainted"; //----------------------------------------------------------------------------- inline QString suffix(int num) { return QString("_") + QString::number(num); } //----------------------------------------------------------------------------- inline TFilePath withSuffix(const TFilePath &fp, int num) { return fp.withName(fp.getWideName() + suffix(num).toStdWString()); } //----------------------------------------------------------------------------- inline bool exists(const TFilePath &fp) { return TSystem::doesExistFileOrLevel( TApp::instance()->getCurrentScene()->getScene()->decodeFilePath(fp)); } //----------------------------------------------------------------------------- inline bool exists(const TFilePath &fp, int num) { return exists(withSuffix(fp, num)); } //----------------------------------------------------------------------------- void loadCleanupParams(CleanupParameters *params, TXshSimpleLevel *sl) { params->assign(CleanupSettingsModel::instance()->getCurrentParameters()); CleanupSettingsModel::loadSettings(params, CleanupSettingsModel::getClnPath(sl)); } //----------------------------------------------------------------------------- double getBestFactor(QSize viewSize, QSize imageSize) { if (abs(viewSize.width() - imageSize.width()) > abs(viewSize.height() - imageSize.height())) { if (viewSize.width() > imageSize.width()) return double(imageSize.width()) / double(viewSize.width()); else return double(viewSize.width()) / double(imageSize.width()); } else { if (viewSize.height() > imageSize.height()) return double(imageSize.height()) / double(viewSize.height()); else return double(viewSize.height()) / double(imageSize.height()); } return 0; } //----------------------------------------------------------------------------- /*! cleanup後のファイルlevelPathに対してUnpaintedファイルを作る。 Cleanup後のUnpaintedの保存先を1階層下げる(nopaintフォルダ内に入れ、 "A_np.tlv"のように"_np"を付ける。"_unpainted"は長いので) Paletteをキープするかどうかのフラグを追加 */ void saveUnpaintedLevel(const TFilePath &levelPath, TXshSimpleLevel *sl, std::vector fids, bool keepOriginalPalette) { try { /*---nopaintフォルダの作成---*/ TFilePath nopaintDir = levelPath.getParentDir() + "nopaint"; if (!TFileStatus(nopaintDir).doesExist()) { try { TSystem::mkDir(nopaintDir); } catch (...) { return; } } TFilePath unpaintedLevelPath = levelPath.getParentDir() + "nopaint\\" + TFilePath(levelPath.getName() + "_np." + levelPath.getType()); if (!TSystem::doesExistFileOrLevel(unpaintedLevelPath)) { // No unpainted level exists. So, just copy the output file. TSystem::copyFile(unpaintedLevelPath, levelPath); if (keepOriginalPalette) return; TFilePath levelPalettePath(levelPath.withType("tpl")); TFilePath unpaintedLevelPalettePath = levelPalettePath.getParentDir() + "nopaint\\" + TFilePath(levelPalettePath.getName() + "_np." + levelPalettePath.getType()); TSystem::copyFile(unpaintedLevelPalettePath, levelPalettePath); return; } TLevelWriterP lw(unpaintedLevelPath); if (keepOriginalPalette) lw->setOverwritePaletteFlag(false); int i, fidsCount = fids.size(); for (i = 0; i < fidsCount; ++i) { const TFrameId &fid = fids[i]; TToonzImageP ti = sl->getFrame(fid, false); if (!ti) continue; lw->getFrameWriter(fid)->save(ti); } } catch (...) { } } //------------------------------------------------------------------------------ /*! Cleanup後のデフォルトPaletteを追加する。 TODO: Cleanup後にデフォルトPaletteの内容を追加する仕様、Preferencesでオプション化 2016/1/16 shun_iwasawa */ void addCleanupDefaultPalette(TXshSimpleLevelP sl) { /*--- CleanupデフォルトパレットはStudioPaletteフォルダ内に入れる ---*/ TFilePath palettePath = ToonzFolder::getStudioPaletteFolder() + "cleanup_default.tpl"; TFileStatus pfs(palettePath); if (!pfs.doesExist() || !pfs.isReadable()) { DVGui::warning( QString("CleanupDefaultPalette file: %1 is not found!") .arg(QString::fromStdWString(palettePath.getWideString()))); return; } TIStream is(palettePath); if (!is) { DVGui::warning( QString("CleanupDefaultPalette file: failed to get TIStream")); return; } std::string tagName; if (!is.matchTag(tagName) || tagName != "palette") { DVGui::warning( QString("CleanupDefaultPalette file: This is not palette file")); return; } std::string gname; is.getTagParam("name", gname); TPalette *defaultPalette = new TPalette(); defaultPalette->loadData(is); sl->getPalette()->setIsCleanupPalette(false); TPalette::Page *dstPage = sl->getPalette()->getPage(0); TPalette::Page *srcPage = defaultPalette->getPage(0); for (int srcIndexInPage = 0; srcIndexInPage < srcPage->getStyleCount(); srcIndexInPage++) { int id = srcPage->getStyleId(srcIndexInPage); bool isUsedInCleanupPalette; isUsedInCleanupPalette = false; for (int dstIndexInPage = 0; dstIndexInPage < dstPage->getStyleCount(); dstIndexInPage++) { if (dstPage->getStyleId(dstIndexInPage) == id) { isUsedInCleanupPalette = true; break; } } if (isUsedInCleanupPalette) continue; else { int addedId = sl->getPalette()->addStyle( srcPage->getStyle(srcIndexInPage)->clone()); dstPage->addStyle(addedId); /*--- * StudioPalette由来のDefaultPaletteの場合、GrobalName(リンク)を消去する * ---*/ sl->getPalette()->getStyle(addedId)->setGlobalName(L""); sl->getPalette()->getStyle(addedId)->setOriginalName(L""); } } delete defaultPalette; } } // namespace //***************************************************************************** // CleanupLevel definition //***************************************************************************** struct CleanupPopup::CleanupLevel { TXshSimpleLevel *m_sl; //!< Level to be cleanupped. TFilePath m_outputPath; //!< Output path for the cleanupped level. std::vector m_frames; //!< Frames to cleanup. Resolution m_resolution; //!< Resolution for verified file conflicts. public: CleanupLevel(TXshSimpleLevel *sl, const CleanupParameters ¶ms) : m_sl(sl) , m_outputPath(CleanupSettingsModel::getOutputPath(m_sl, ¶ms)) , m_resolution(NO_RESOLUTION) {} bool empty() const { return m_frames.empty(); } }; //***************************************************************************** // CleanupPopup implementation //***************************************************************************** CleanupPopup::CleanupPopup() : QDialog(TApp::instance()->getMainWindow()) , m_params(new CleanupParameters) , m_updater(new LevelUpdater) , m_originalLevelPath() , m_originalPalette(0) , m_firstLevelFrame(true) { setWindowTitle(tr("Cleanup")); // Progress Bar m_progressLabel = new QLabel(tr("Cleanup in progress")); m_progressBar = new QProgressBar; // Text m_cleanupQuestionLabel = new QLabel(tr("Do you want to cleanup this frame?")); m_imageViewer = new ImageViewer(0, 0, false); // Buttons m_cleanupButton = new QPushButton(tr("Cleanup")); m_skipButton = new QPushButton(tr("Skip")); m_cleanupAllButton = new QPushButton(tr("Cleanup All")); QPushButton *cancelButton = new QPushButton(tr("Cancel")); m_imgViewBox = new QGroupBox(tr("View"), this); m_imgViewBox->setCheckable(true); m_imgViewBox->setChecked(false); m_imageViewer->setVisible(false); m_imageViewer->resize(406, 306); ImagePainter::VisualSettings settings; settings.m_bg = 0x80000; // set to white regardless of the flipbook bg m_imageViewer->setVisual(settings); //---layout QVBoxLayout *mainLayout = new QVBoxLayout(); mainLayout->setMargin(5); mainLayout->setSpacing(5); { mainLayout->addWidget(m_progressLabel, 0); mainLayout->addWidget(m_progressBar, 0); mainLayout->addWidget(m_cleanupQuestionLabel); QVBoxLayout *imgBoxLay = new QVBoxLayout(); imgBoxLay->setMargin(5); { imgBoxLay->addWidget(m_imageViewer); } m_imgViewBox->setLayout(imgBoxLay); mainLayout->addWidget(m_imgViewBox, 1); QHBoxLayout *buttonLay = new QHBoxLayout(); buttonLay->setMargin(0); buttonLay->setSpacing(5); { buttonLay->addWidget(m_cleanupButton); buttonLay->addWidget(m_skipButton); buttonLay->addWidget(m_cleanupAllButton); buttonLay->addWidget(cancelButton); } mainLayout->addLayout(buttonLay); mainLayout->addStretch(); } setLayout(mainLayout); //--- signal-slot connections bool ret = true; ret = ret && connect(m_progressBar, SIGNAL(valueChanged(int)), this, SLOT(onValueChanged(int))); // NOTE: On MAC it seems that QAbstractButton's pressed() signal is reemitted // at // every mouseMoveEvent when the button is pressed... // This is why clicked() substitutes pressed() below. ret = ret && connect(m_cleanupButton, SIGNAL(clicked()), this, SLOT(onCleanupFrame())); ret = ret && connect(m_skipButton, SIGNAL(clicked()), this, SLOT(onSkipFrame())); ret = ret && connect(m_cleanupAllButton, SIGNAL(clicked()), this, SLOT(onCleanupAllFrame())); ret = ret && connect(cancelButton, SIGNAL(clicked()), this, SLOT(onCancelCleanup())); ret = ret && connect(m_imgViewBox, SIGNAL(toggled(bool)), this, SLOT(onImgViewBoxToggled(bool))); assert(ret); reset(); // Initialize remaining variables resize(450, 400); } //----------------------------------------------------------------------------- CleanupPopup::~CleanupPopup() {} //----------------------------------------------------------------------------- void CleanupPopup::closeEvent(QCloseEvent *ce) { reset(); } //----------------------------------------------------------------------------- void CleanupPopup::reset() { closeLevel(); m_idx = m_completion = std::pair(-1, -1); m_cleanupLevels.clear(); m_imageViewer->setImage(TImageP()); TCleanupper::instance()->setParameters( CleanupSettingsModel::instance()->getCurrentParameters()); m_cleanupQuestionLabel->show(); m_levelAlreadyExists.clear(); /*---タイトルバーを元の表示に戻す---*/ MainWindow *mainWin = qobject_cast(TApp::instance()->getMainWindow()); if (mainWin) mainWin->changeWindowTitle(); } //----------------------------------------------------------------------------- void CleanupPopup::buildCleanupList() { struct locals { static inline bool supportsCleanup(TXshSimpleLevel *sl) { return ( sl->getType() & FULLCOLOR_TYPE || (sl->getType() == TZP_XSHLEVEL && !sl->getScannedPath().isEmpty())); } }; // locals typedef std::set FramesList; /*--- これからCleanupするLevel/FrameIdのリスト ---*/ std::map cleanupList; // List of frames to be cleanupped std::vector levelsList; // List of levels in the cleanup list, // ordered as found in the xsheet. m_cleanupLevels.clear(); // Retrieve current selection TCellSelection *selection = dynamic_cast(TSelection::getCurrent()); TColumnSelection *columnSel = dynamic_cast(TSelection::getCurrent()); /*--- セル選択でも、カラム選択でも無い場合はCleanup自体を無効にする ---*/ if (!selection && !columnSel) return; TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet(); /*--- セル選択の場合 ---*/ if (selection) { if (selection->isEmpty()) return; // Store frames in the specified selection int r0, c0, r1, c1; selection->getSelectedCells(r0, c0, r1, c1); int r, c; for (c = c0; c <= c1; ++c) { for (r = r0; r <= r1; ++r) { /*---選択範囲内にLevelが無い場合はcontinue---*/ const TXshCell &cell = xsh->getCell(r, c); if (cell.isEmpty()) continue; TXshSimpleLevel *sl = cell.getSimpleLevel(); if (!(sl && locals::supportsCleanup(sl))) continue; /*---もし新しいLevelなら、Levelのリストに登録---*/ std::map::iterator it = cleanupList.find(sl); if (it == cleanupList.end()) { it = cleanupList.insert(std::make_pair(sl, FramesList())).first; levelsList.push_back(sl); } /*---TFrameIdを登録---*/ it->second.insert(cell.getFrameId()); } } } /*--- カラム選択の場合 ---*/ else { int frameCount = xsh->getFrameCount(); if (columnSel->isEmpty() || frameCount <= 0) return; /*--- 選択された各カラムについて ---*/ std::set::const_iterator it = columnSel->getIndices().begin(); for (; it != columnSel->getIndices().end(); ++it) { int c = (*it); for (int r = 0; r < frameCount; r++) { /*--- 選択範囲内にLevelが無い場合はcontinue ---*/ const TXshCell &cell = xsh->getCell(r, c); if (cell.isEmpty()) continue; TXshSimpleLevel *sl = cell.getSimpleLevel(); if (!sl && locals::supportsCleanup(sl)) continue; /*---もし新しいLevelなら、Levelのリストに登録---*/ std::map::iterator it = cleanupList.find(sl); if (it == cleanupList.end()) { it = cleanupList.insert(std::make_pair(sl, FramesList())).first; levelsList.push_back(sl); } /*---TFrameIdを登録---*/ it->second.insert(cell.getFrameId()); } } } // Finally, copy the retrieved data to the sorted output vector std::vector::iterator lt, lEnd = levelsList.end(); for (lt = levelsList.begin(); lt != lEnd; ++lt) { loadCleanupParams( m_params.get(), *lt); // Load cleanup parameters associated with current level. // This is necessary since the output path is specified among them. m_cleanupLevels.push_back(CleanupLevel(*lt, *m_params.get())); CleanupLevel &cl = m_cleanupLevels.back(); FramesList &framesList = cleanupList[cl.m_sl]; cl.m_frames.assign(framesList.begin(), framesList.end()); } } //----------------------------------------------------------------------------- bool CleanupPopup::analyzeCleanupList() { ToonzScene *scene = TApp::instance()->getCurrentScene()->getScene(); bool shownOverwriteDialog = false, shownWritingOnSourceFile = false; /*--- 消されるLevel名の確認ダイアログを出すため ---*/ QList filePathsToBeDeleted; /* Cleanup前に既存のLevelを消す場合、セルのStatusをScannedに変更するために、 TXshSimpleLevelポインタを格納しておく */ QList levelsToBeDeleted; // Traverse the cleanup list for (auto &clt : m_cleanupLevels) { TXshSimpleLevel *sl = clt.m_sl; /*--- Cleanup対象LevelのCleanupSettingを取得 ---*/ loadCleanupParams(m_params.get(), sl); // Needed to retrieve output level resolution // Check level existence /*--- Cleanup後に得られるであろうTLVのパス ---*/ TFilePath outputPath = scene->decodeFilePath(clt.m_outputPath); { /*-- 出力先にTLVファイルが無ければ問題なし(このLevelはCleanupする) --*/ if (!TSystem::doesExistFileOrLevel(outputPath)) { m_levelAlreadyExists[sl] = false; continue; // Newly cleaned up level. No problem. } else m_levelAlreadyExists[sl] = true; // Check whether output file == input file /*--- 入力となるScanned(TIFなど)のパスを得る ---*/ const TFilePath &inputPath = scene->decodeFilePath(CleanupSettingsModel::getInputPath(sl)); if (!shownWritingOnSourceFile && inputPath == outputPath) { shownWritingOnSourceFile = true; int ret = DVGui::MsgBox(tr("Selected drawings will overwrite the " "original files after the cleanup process.\n" "Do you want to continue?"), tr("Ok"), tr("Cancel")); if (ret != 1) return false; } /*---一度も上書き確認のダイアログを出していなかったら、ここで出す---*/ // Reset the overwrite dialog if (!shownOverwriteDialog) { shownOverwriteDialog = true; if (!m_overwriteDialog) m_overwriteDialog.reset(new OverwriteDialog); else m_overwriteDialog->reset(); } // Prompt user for file conflict resolution clt.m_resolution = Resolution(m_overwriteDialog->execute(&clt.m_outputPath)); switch (clt.m_resolution) { case CANCEL: return false; case NO_RESOLUTION: assert(false); break; case REPLACE: /*--- 既存のTLVを消すオプションの場合、消されるファイルのリストを作る * ---*/ break; case ADD_SUFFIX: continue; case NOPAINT_ONLY: /*--- NOPAINT_ONLY の場合は、nopaintのみを変更。 ただし、nopaintのLevelは消さず、処理したフレームを Overwrite する ---*/ outputPath = outputPath.getParentDir() + "nopaint\\" + TFilePath(outputPath.getName() + "_np." + outputPath.getType()); /*--- nopaintの有無を確かめる。無ければ次のLevelへ * (このLevelはCleanupする) ---*/ if (!TSystem::doesExistFileOrLevel(outputPath)) { m_levelAlreadyExists[sl] = false; continue; } default: break; } TLevelP level(0); // Current level info. Yeah the init is a shame... :( /*--- 元のLevelと新しいCleanup結果が混合する場合。REPLACE以外 ---*/ if (clt.m_resolution == OVERWRITE || clt.m_resolution == WRITE_NEW || clt.m_resolution == NOPAINT_ONLY) { // Check output resolution consistency // Retrieve file resolution /*---現在在るTLVのサイズと、CleanupSettingsのサイズが一致しているかチェック---*/ TDimension oldRes(0, 0); try { TLevelReaderP lr(outputPath); level = lr->loadInfo(); if (const TImageInfo *imageInfo = lr->getImageInfo()) oldRes = TDimension(imageInfo->m_lx, imageInfo->m_ly); else throw TException(); } catch (...) { // There was a problem reading the existing level data. // Thus, the conservative approach is not feasible. // Inform the user and abort cleanup DVGui::warning( tr("There were errors opening the existing level " "\"%1\".\n\nPlease choose to delete the existing level and " "create a new one\nwhen running the cleanup process.") .arg(QString::fromStdWString(outputPath.getLevelNameW()))); return false; } // Retrieve output resolution TDimension outRes(0, 0); TPointD outDpi; m_params->getOutputImageInfo(outRes, outDpi.x, outDpi.y); if (oldRes != outRes) { DVGui::warning( tr("The resulting resolution of level \"%1\"\ndoes not match " "with that of previously cleaned up level drawings.\n\nPlease " "set the right camera resolution and closest field, or choose " "to delete\nthe existing level and create a new one when " "running the cleanup process.") .arg(QString::fromStdWString(outputPath.getLevelNameW()))); return false; } } /*--- REPLACEの場合、消されるファイルパスのリストを作る ---*/ else if (clt.m_resolution == REPLACE) { filePathsToBeDeleted.push_back(outputPath); levelsToBeDeleted.push_back(sl); /*--- パレットファイルも、あれば消す ---*/ TFilePath palettePath = (outputPath.getParentDir() + outputPath.getName()).withType("tpl"); if (TSystem::doesExistFileOrLevel(palettePath)) filePathsToBeDeleted.push_back(palettePath); /*--- つぎに、nopaintのTLV。これは、REPLACE、NOPAINT_ONLY 両方で消す * ---*/ TFilePath unpaintedLevelPath = outputPath.getParentDir() + "nopaint\\" + TFilePath(outputPath.getName() + "_np." + outputPath.getType()); if (TSystem::doesExistFileOrLevel(unpaintedLevelPath)) { filePathsToBeDeleted.push_back(unpaintedLevelPath); filePathsToBeDeleted.push_back( (unpaintedLevelPath.getParentDir() + unpaintedLevelPath.getName()) .withType("tpl")); } } // Finally, apply resolution to individual frames. /*--- WRITE_NEW は、「未Cleanupのフレームだけ処理する」オプション ---*/ if (clt.m_resolution == WRITE_NEW) { const TLevel::Table *table = level->getTable(); clt.m_frames.erase( std::remove_if(clt.m_frames.begin(), clt.m_frames.end(), [table](TLevel::Table::key_type const &key) { return table->count(key); }), clt.m_frames.end()); } } } /*--- ファイル消去の確認ダイアログを表示 ---*/ if (!filePathsToBeDeleted.isEmpty()) { QString question = QObject::tr( "Delete and Re-cleanup : The following files will be deleted.\n\n"); for (int i = 0; i < filePathsToBeDeleted.size(); i++) { question += " " + QString::fromStdWString(filePathsToBeDeleted[i].getWideString()) + "\n"; } question += QObject::tr("\nAre you sure ?"); int ret = DVGui::MsgBox(question, QObject::tr("Delete"), QObject::tr("Cancel"), 0); if (ret == 0 || ret == 2) { return false; } else if (ret == 1) { /*--- 先にCleanup処理で出力先となるファイルを消す ---*/ try { for (int i = 0; i < filePathsToBeDeleted.size(); i++) { TSystem::removeFileOrLevel_throw(filePathsToBeDeleted[i]); } } catch (...) { return false; } // Reset level status for (int i = 0; i < levelsToBeDeleted.size(); i++) { TXshSimpleLevel *lev = levelsToBeDeleted.at(i); /*--- TLVだった場合、Scanned(TIFレベル)に戻す ---*/ TFilePath scannedPath = lev->getScannedPath(); if (scannedPath != TFilePath()) { lev->setScannedPath(TFilePath()); lev->setPath(scannedPath, true); lev->clearFrames(); lev->setType(OVL_XSHLEVEL); // OVL_XSHLEVEL lev->setPalette(0); if (lev == TApp::instance()->getCurrentLevel()->getLevel()) TApp::instance() ->getPaletteController() ->getCurrentLevelPalette() ->setPalette(0); lev->load(); int i, frameCount = lev->getFrameCount(); for (i = 0; i < frameCount; i++) { TFrameId id = lev->index2fid(i); IconGenerator::instance()->invalidate(lev, id); } IconGenerator::instance()->invalidateSceneIcon(); } } } } // Before returning, erase levels whose frames list is empty /*--- Cleanup対象フレームが無くなったLevelを対象から外す ---*/ m_cleanupLevels.erase( std::remove_if(m_cleanupLevels.begin(), m_cleanupLevels.end(), std::mem_fn(&CleanupLevel::empty)), m_cleanupLevels.end()); return true; } //----------------------------------------------------------------------------- bool CleanupPopup::isValidPosition(const std::pair &pos) const { if (pos.first < 0 || int(m_cleanupLevels.size()) <= pos.first) return false; const CleanupLevel &cl = m_cleanupLevels[pos.first]; if (pos.second < 0 || int(cl.m_frames.size()) <= pos.second) return false; return true; } //----------------------------------------------------------------------------- QString CleanupPopup::currentString() const { if (!isValidPosition(m_idx)) return QString(); const CleanupLevel &cl = m_cleanupLevels[m_idx.first]; TXshSimpleLevel *sl = cl.m_sl; const TFrameId &fid = cl.m_frames[m_idx.second]; TFilePath scannedPath(sl->getScannedPath()); if (scannedPath.isEmpty()) scannedPath = sl->getPath(); TFilePath levelName(scannedPath.getLevelNameW()); QString imageName = toQString(levelName.withFrame(fid)); return tr("Cleanup in progress: ") + imageName + " " + QString::number(m_completion.first) + "/" + QString::number(m_completion.second); } //----------------------------------------------------------------------------- /*--- これからCleanupするフレームを取得 ---*/ TImageP CleanupPopup::currentImage() const { if (!isValidPosition(m_idx)) return TImageP(); const CleanupLevel &cl = m_cleanupLevels[m_idx.first]; return cl.m_sl->getFrameToCleanup(cl.m_frames[m_idx.second]); } //----------------------------------------------------------------------------- void CleanupPopup::execute() { struct locals { static inline int addFrames(int a, const CleanupLevel &cl) { return a + int(cl.m_frames.size()); } }; // locals // Re-initialize the list of frames to be cleanupped /*--- Cleanup対象のリストを作る ---*/ buildCleanupList(); // In case some cleaned up level already exists, let the user decide what to // do if (!analyzeCleanupList()) { reset(); return; } // Initialize completion variable m_completion = std::pair( 0, std::accumulate(m_cleanupLevels.begin(), m_cleanupLevels.end(), 0, locals::addFrames)); // If there are no (more) frames to cleanup, warn and quit int framesCount = m_completion.second; if (!framesCount) { DVGui::info( tr("It is not possible to cleanup: the cleanup list is empty.")); reset(); return; } // Initialize the cleanup process and show the popup m_idx = std::pair(0, 0); // Reset progress bar m_progressLabel->setText(currentString()); m_progressBar->setRange(0, framesCount); m_progressBar->setValue(0); // show the progress to the main window's title bar updateTitleString(); TImageP image = currentImage(); if (image) { m_imageViewer->setImage(image); // Set the zoom factor depending on image and viewer sizes, so that the // image fits // the preview area. QSize viewSize = m_imageViewer->size(); QSize imageSize = QSize(image->getBBox().getLx(), image->getBBox().getLy()); double factor = getBestFactor(viewSize, imageSize); TPointD delta(0, 0); TAffine viewAff = TTranslation(delta) * TScale(factor) * TTranslation(-delta); m_imageViewer->setViewAff(viewAff); } show(); } //----------------------------------------------------------------------------- QString CleanupPopup::setupLevel() { // Level's pre-cleanup stuff initialization. // Invoked right before the cleanupFrame() of the first level frame. assert(isValidPosition(m_idx)); TApp *app = TApp::instance(); ToonzScene *scene = app->getCurrentScene()->getScene(); /*--- これからCleanupするLevel ---*/ CleanupLevel &cl = m_cleanupLevels[m_idx.first]; TXshSimpleLevel *sl = cl.m_sl; /*--- 保存先のTLVが既に存在する、かつ、REPLACE でも NOPAINT_ONLY でもない場合、Paletteを変更せず維持する ---*/ if (cl.m_resolution != REPLACE && cl.m_resolution != NOPAINT_ONLY && m_levelAlreadyExists[sl] == true) m_keepOriginalPalette = true; else m_keepOriginalPalette = false; // Update cleanup parameters, loading a cln if necessary TCleanupper *cleanupper = TCleanupper::instance(); /*--- CleanupSettingsを読み込み、TCleanupperに渡す ---*/ loadCleanupParams(m_params.get(), sl); cleanupper->setParameters(m_params.get()); // Touch the output parent directory TFilePath &outputPath = cl.m_outputPath; /*--- 保存先PathをFull Pathにする ---*/ TFilePath decodedPath(scene->decodeFilePath(outputPath)); if (!TSystem::touchParentDir(decodedPath)) return tr("Couldn't create directory \"%1\"").arg(decodedPath.getQString()); /*--- * 上書きオプションが選択されているとき、既存のLevel,Palette,Nopaintファイルを消す * ---*/ // If the user decided to remove any existing level, do so now. if (cl.m_resolution == REPLACE) { const QString &err = resetLevel(); if (!err.isEmpty()) return err; } /*--- Nopaintのみ上書きのオプションが選択されているとき(再Clenaupの場合) ---*/ else if (cl.m_resolution == NOPAINT_ONLY) { m_originalLevelPath = outputPath; /*--- 必要なら、nopaintフォルダを作成 ---*/ TFilePath nopaintDir = decodedPath.getParentDir() + "nopaint"; if (!TFileStatus(nopaintDir).doesExist()) { try { TSystem::mkDir(nopaintDir); } catch (...) { return NULL; } } /*--- 保存先のパスをnopaintの方に変更 ---*/ outputPath = outputPath.getParentDir() + "nopaint\\" + TFilePath(outputPath.getName() + "_np." + outputPath.getType()); decodedPath = scene->decodeFilePath(outputPath); } // Frames are cleaned-up at full-sampling. Thus, clear subsampling on the // level. if (sl->getProperties()->getSubsampling() != 1) { sl->getProperties()->setSubsampling(1); sl->invalidateFrames(); } bool lineProcessing = (m_params->m_lineProcessingMode != lpNone), notLineProcessed = (sl->getType() != TZP_XSHLEVEL); if (lineProcessing) { /*--- Keep original palette which will be reverted after cleanup ---*/ if (m_keepOriginalPalette) { if ((sl->getType() == TZP_XSHLEVEL || sl->getType() == TZI_XSHLEVEL) && sl->getPalette() != NULL) m_originalPalette = sl->getPalette()->clone(); else /*--- In case the level has been already cleanupped, and is cleanupped again from raster level ---*/ { /*--- Load and keep the palette from destination TLV ---*/ TFilePath targetPalettePath = outputPath.getParentDir() + TFilePath(outputPath.getName() + ".tpl"); TFileStatus pfs(targetPalettePath); if (pfs.doesExist() && pfs.isReadable()) { TIStream is(targetPalettePath); std::string tagName; if (!is.matchTag(tagName) || tagName != "palette") { DVGui::warning(QString( "CleanupDefaultPalette file: This is not palette file")); return NULL; } m_originalPalette = new TPalette(); m_originalPalette->loadData(is); } else m_originalPalette = 0; } } if (notLineProcessed) { /*-- Type, Pathを切り替えてTLVにする --*/ // The level type changes to TLV sl->makeTlv(outputPath); // Remap all current images under the IM control to Scanned status. int f, fCount = sl->getFrameCount(); for (f = 0; f != fCount; ++f) { const TFrameId &fid = sl->getFrameId(f); /*--- 「スキャン済み」のステータスにし、画像、アイコンのIDを切り替える * ---*/ sl->setFrameStatus(fid, TXshSimpleLevel::Scanned); ImageManager::instance()->rebind( sl->getImageId(fid, 0), sl->getImageId(fid, TXshSimpleLevel::Scanned)); const std::string &oldIconId = sl->getIconId(fid, 0); const std::string &newIconId = sl->getIconId(fid, TXshSimpleLevel::Scanned); IconGenerator::instance()->remap(newIconId, oldIconId); } } /*--- 対象が既にTLVファイルで、出力先パスが違う場合、切り替える ---*/ else if (outputPath != sl->getPath()) { // Just wants to be written to a different destination path. Update it. sl->setPath(outputPath, false); } /*-- Cleanup用のパレットを作る --*/ // Update the level palette TPaletteP palette = TCleanupper::instance()->createToonzPaletteFromCleanupPalette(); sl->setPalette(palette.getPointer()); /*--- カレントPaletteを切り替える ---*/ if (sl == app->getCurrentLevel()->getLevel()) app->getPaletteController()->getCurrentLevelPalette()->setPalette( palette.getPointer()); // Notify the xsheet that the level has changed visual type informations // (either the level type, // cleanup status, etc) app->getCurrentXsheet()->notifyXsheetChanged(); } else if (!m_params->getPath(scene) .isEmpty()) // Should never be empty, AFAIK... { // No line processing if (notLineProcessed) { // Just change paths if (sl->getScannedPath().isEmpty()) sl->setScannedPath(sl->getPath()); sl->setPath(outputPath, false); // Reload frames from the result, too } else { // Return to scan level type sl->clearFrames(); sl->setType(OVL_XSHLEVEL); sl->setPath(outputPath); } } // Finally, open the LevelUpdater on the level. assert(!m_updater->opened()); try { m_updater->open(sl); } catch (...) { return tr("Couldn't open \"%1\" for write").arg(outputPath.getQString()); } m_updater->getLevelWriter()->setOverwritePaletteFlag(!m_keepOriginalPalette); return QString(); } //----------------------------------------------------------------------------- /* setupLevel()から、 m_overwriteAction == REPLACE のとき呼ばれる。 選択LevelのCleanup後Levelを消す */ QString CleanupPopup::resetLevel() { struct locals { static bool removeFileOrLevel(const TFilePath &fp) { return (!TSystem::doesExistFileOrLevel(fp) || TSystem::removeFileOrLevel(fp)); } static QString decorate(const TFilePath &fp) { return tr("Couldn't remove file \"%1\"").arg(fp.getQString()); } }; // Try to remove the existing level const CleanupLevel &cl = m_cleanupLevels[m_idx.first]; TXshSimpleLevel *sl = cl.m_sl; assert(sl); ToonzScene *scene = TApp::instance()->getCurrentScene()->getScene(); /*--- Cleanup後のTLVファイルパスを得る。すなわちこれから消すファイル ---*/ // Ensure outputPath != inputPath const TFilePath &outputPath = scene->decodeFilePath(cl.m_outputPath), &inputPath = scene->decodeFilePath( CleanupSettingsModel::getInputPath(sl)); if (inputPath == outputPath) return QString(); // Cannot remove source file - however, it can be // overwritten // Remove existing output files if (!locals::removeFileOrLevel(outputPath)) return locals::decorate(outputPath); if (m_params->m_lineProcessingMode != lpNone) { TFilePath fp; // Line processing on - remove palette too if (!locals::removeFileOrLevel(fp = outputPath.withType("tpl"))) return locals::decorate(fp); // Also remove unpainted output path if any const TFilePath &unpaintedPath( outputPath.getParentDir() + "nopaint\\" + TFilePath(outputPath.getName() + "_np." + outputPath.getType())); if (!locals::removeFileOrLevel(unpaintedPath)) return locals::decorate(unpaintedPath); if (!locals::removeFileOrLevel(fp = unpaintedPath.withType("tpl"))) return locals::decorate(fp); } // Reset level status sl->setPath(sl->getPath(), false); // false rebuilds level data // NOTE: sl is the INPUT level - so this instruction return QString(); // should take place AFTER output availability // has been ensured. } //----------------------------------------------------------------------------- /*--- * 現在処理を行っているLevelの最後のフレームの処理が終わってから、フレームを進めるときに呼ばれる * ---*/ void CleanupPopup::closeLevel() { if (m_cleanuppedLevelFrames.empty()) { if (m_updater->opened()) m_updater->close(); return; } // Save the unpainted level if necessary const CleanupLevel &cl = m_cleanupLevels[m_idx.first]; TXshSimpleLevel *sl = cl.m_sl; assert(sl); /*--- Nopaintのみ上書きの場合、Cleanup前に戻す ---*/ if (cl.m_resolution == NOPAINT_ONLY && !m_originalLevelPath.isEmpty()) { sl->setPath(m_originalLevelPath); sl->invalidateFrames(); std::vector fIds; sl->getFids(fIds); invalidateIcons(sl, fIds); m_originalLevelPath = TFilePath(); } /*--- Paletteを更新しない場合は、ここで取っておいたPaletteデータを元に戻す * ---*/ if (m_keepOriginalPalette && m_originalPalette) { sl->setPalette(m_originalPalette); if (sl == TApp::instance()->getCurrentLevel()->getLevel()) TApp::instance() ->getPaletteController() ->getCurrentLevelPalette() ->setPalette(m_originalPalette); sl->invalidateFrames(); std::vector fIds; sl->getFids(fIds); invalidateIcons(sl, fIds); TApp::instance()->getCurrentPalette()->notifyPaletteSwitched(); } // Close the level updater. Silence any exception (ok, something went bad, old // story now). try { m_updater->close(); } catch (...) { } if (sl->getType() == TZP_XSHLEVEL && Preferences::instance()->isSaveUnpaintedInCleanupEnable() && cl.m_resolution != NOPAINT_ONLY) /*--- 再Cleanupの場合は既にNoPaintに上書きしている ---*/ { const TFilePath &outputPath = cl.m_outputPath; ToonzScene *scene = TApp::instance()->getCurrentScene()->getScene(); TFilePath decodedPath(scene->decodeFilePath(outputPath)); if (outputPath.getLevelNameW().find(L"-np.") == std::wstring::npos && TFileStatus(decodedPath).doesExist()) { saveUnpaintedLevel(decodedPath, sl, m_cleanuppedLevelFrames, (m_keepOriginalPalette && m_originalPalette)); } } m_cleanuppedLevelFrames.clear(); m_firstLevelFrame = true; } //----------------------------------------------------------------------------- /*-- 各フレームの処理 --*/ void CleanupPopup::cleanupFrame() { assert(isValidPosition(m_idx)); TRasterImageP original(currentImage()); if (!original) return; CleanupLevel &cl = m_cleanupLevels[m_idx.first]; TXshSimpleLevel *sl = cl.m_sl; const TFrameId &fid = cl.m_frames[m_idx.second]; assert(sl); // Perform image cleanup { TCleanupper *cl = TCleanupper::instance(); const CleanupParameters *params = cl->getParameters(); if (params->m_lineProcessingMode == lpNone) { // No line processing TRasterImageP ri(original); if (params->m_autocenterType != CleanupTypes::AUTOCENTER_NONE) { bool autocentered; ri = cl->autocenterOnly(original, false, autocentered); if (!autocentered) DVGui::warning( QObject::tr("The autocentering failed on the current drawing.")); } sl->setFrame(fid, ri); // Update the associated file. In case the operation throws, oh well the // image gets skipped. try { m_updater->update(fid, ri); } catch (...) { } IconGenerator::instance()->invalidate(sl, fid); } else { // Perform main processing // Obtain the source dpi. Changed it to be done once at the first frame of // each level in order to avoid the following problem: // If the original raster level has no dpi (such as TGA images), obtaining // dpi in every frame causes dpi mismatch between the first frame and the // following frames, since the value // TXshSimpleLevel::m_properties->getDpi() will be changed to the // dpi of cleanup camera (= TLV's dpi) after finishing the first frame. if (m_firstLevelFrame) { TPointD dpi; original->getDpi(dpi.x, dpi.y); if (dpi.x == 0 && dpi.y == 0) dpi = sl->getProperties()->getDpi(); cl->setSourceDpi(dpi); } CleanupPreprocessedImage *cpi; { TRasterImageP resampledRaster; cpi = cl->process(original, m_firstLevelFrame, resampledRaster); } if (!cpi) return; // Perform post-processing TToonzImageP ti(cl->finalize(cpi)); /*--- Cleanup Default Paletteを作成、適用 ---*/ if (m_firstLevelFrame) { addCleanupDefaultPalette(sl); sl->getPalette()->setPaletteName(sl->getName()); } ti->setPalette(sl->getPalette()); // Assigned to sl in setupLevel() assert(sl->getPalette()); // Update the level data about the cleanupped frame sl->setFrameStatus(fid, sl->getFrameStatus(fid) | TXshSimpleLevel::Cleanupped); // sl->setFrame(fid, TImageP()); // Invalidate the old image data sl->setFrame(fid, ti); // replace with the new image data // Output the cleanupped image to disk try { m_updater->update(fid, ti); } // The file image data will be reloaded upon request catch (...) { } // Invalidate icons IconGenerator::instance()->invalidate(sl, fid); int autocenterType = params->m_autocenterType; if (autocenterType == CleanupTypes::AUTOCENTER_FDG && !cpi->m_autocentered) DVGui::warning( QObject::tr("The autocentering failed on the current drawing.")); delete cpi; if (m_firstLevelFrame) { // Update result-dependent level data TPointD dpi(0, 0); ti->getDpi(dpi.x, dpi.y); if (dpi.x != 0 && dpi.y != 0) sl->getProperties()->setDpi(dpi); sl->getProperties()->setImageRes(ti->getSize()); sl->getProperties()->setBpp(32); } } } // this enables to view the level during cleanup by another user. this // behavior may abort Toonz. /* try { m_updater->flush(); } // Release the opened level from writing catch(...) {} // It is required to have it open for read // when rebuilding icons... (still dangerous though) */ m_firstLevelFrame = false; m_cleanuppedLevelFrames.push_back(fid); TApp *app = TApp::instance(); app->getCurrentLevel()->notifyLevelChange(); app->getCurrentXsheet()->notifyXsheetChanged(); } //----------------------------------------------------------------------------- void CleanupPopup::advanceFrame() { assert(isValidPosition(m_idx)); std::pair newIdx = std::make_pair(m_idx.first, m_idx.second + 1); // In case the level was completely processed, close down the old level if (!isValidPosition(newIdx)) { if (!m_cleanuppedLevelFrames.empty()) closeLevel(); newIdx = std::make_pair(m_idx.first + 1, 0); } // Advance in the cleanup list m_idx = newIdx; if (m_imgViewBox->isChecked()) { TImageP image = currentImage(); if (image) m_imageViewer->setImage(image); } // show the progress in the mainwindow's title bar updateTitleString(); // Update the progress bar m_progressBar->setValue(++m_completion.first); } //----------------------------------------------------------------------------- void CleanupPopup::onValueChanged(int value) { if (value == m_progressBar->maximum()) { close(); return; } m_progressLabel->setText(currentString()); } //----------------------------------------------------------------------------- void CleanupPopup::onCleanupFrame() { /*--- Busy時にボタンをUnableする ---*/ m_cleanupAllButton->setEnabled(false); m_cleanupButton->setEnabled(false); m_skipButton->setEnabled(false); /*--- 新しいLevelに取り掛かり始めたとき ---*/ if (m_cleanuppedLevelFrames.empty()) { const QString &err = setupLevel(); if (!err.isEmpty()) { DVGui::error(err); return; } } cleanupFrame(); advanceFrame(); /*--- ボタンを元に戻す---*/ m_cleanupAllButton->setEnabled(true); m_cleanupButton->setEnabled(true); m_skipButton->setEnabled(true); } //----------------------------------------------------------------------------- void CleanupPopup::onSkipFrame() { advanceFrame(); } //----------------------------------------------------------------------------- void CleanupPopup::onCleanupAllFrame() { m_cleanupQuestionLabel->hide(); /*--- Busy時にボタンをUnableする ---*/ m_cleanupAllButton->setEnabled(false); m_cleanupButton->setEnabled(false); m_skipButton->setEnabled(false); while (isValidPosition(m_idx)) { if (m_cleanuppedLevelFrames.empty()) { const QString &err = setupLevel(); if (!err.isEmpty()) { DVGui::error(err); return; } } cleanupFrame(); advanceFrame(); QCoreApplication::processEvents(); // Allow cancels to be received } /*--- ボタンを元に戻す---*/ m_cleanupAllButton->setEnabled(true); m_cleanupButton->setEnabled(true); m_skipButton->setEnabled(true); close(); } //----------------------------------------------------------------------------- void CleanupPopup::onCancelCleanup() { close(); } //***************************************************************************** // CleanupPopup::OverwriteDialog implementation //***************************************************************************** CleanupPopup::OverwriteDialog::OverwriteDialog() : DVGui::ValidatedChoiceDialog(TApp::instance()->getMainWindow()) { setWindowTitle(tr("Warning!")); bool ret = connect(m_buttonGroup, SIGNAL(buttonClicked(int)), SLOT(onButtonClicked(int))); assert(ret); // Option 1: OVERWRITE QRadioButton *radioButton = new QRadioButton; radioButton->setText( tr("Cleanup all selected drawings overwriting those previously cleaned " "up.*")); radioButton->setFixedHeight(20); radioButton->setChecked(true); // initial option: OVERWRITE m_buttonGroup->addButton(radioButton, OVERWRITE); addWidget(radioButton); // Option 2: WRITE_NEW radioButton = new QRadioButton; radioButton->setText( tr("Cleanup only non-cleaned up drawings and keep those previously " "cleaned up.*")); radioButton->setFixedHeight(20); m_buttonGroup->addButton(radioButton, WRITE_NEW); addWidget(radioButton); // Option 3: REPLACE radioButton = new QRadioButton; radioButton->setText( tr("Delete existing level and create a new level with selected drawings " "only.")); radioButton->setFixedHeight(20); m_buttonGroup->addButton(radioButton, REPLACE); addWidget(radioButton); // Option 4: ADD_SUFFIX QHBoxLayout *suffixLayout = new QHBoxLayout; { radioButton = new QRadioButton; radioButton->setText(tr("Rename the new level adding the suffix ")); radioButton->setFixedHeight(20); m_buttonGroup->addButton(radioButton, ADD_SUFFIX); suffixLayout->addWidget(radioButton); m_suffix = new DVGui::LineEdit; m_suffix->setEnabled(false); suffixLayout->addWidget(m_suffix); } addLayout(suffixLayout); // Couldnt' place it right after allocation, // DVGui::Dialog::addLayout() crashed... // Option 5: NOPAINT_ONLY radioButton = new QRadioButton(this); radioButton->setText( tr("This is Re-Cleanup. Overwrite only to the no-paint files.")); radioButton->setFixedHeight(20); m_buttonGroup->addButton(radioButton, NOPAINT_ONLY); addWidget(radioButton); QLabel *note = new QLabel(tr("* Palette will not be changed."), this); note->setStyleSheet("font-size: 10px; font: italic;"); addWidget(note); endVLayout(); layout()->setSizeConstraint(QLayout::SetFixedSize); } //----------------------------------------------------------------------------- void CleanupPopup::OverwriteDialog::reset() { ValidatedChoiceDialog::reset(); m_suffixText.clear(); } //----------------------------------------------------------------------------- QString CleanupPopup::OverwriteDialog::acceptResolution(void *obj, int resolution, bool applyToAll) { struct locals { static inline QString existsStr(const TFilePath &fp) { return tr("File \"%1\" already exists.\nWhat do you want to do?") .arg(fp.getQString()); } }; // locals assert(obj); TFilePath &fp = *static_cast(obj); QString error; switch (resolution) { case NO_RESOLUTION: // fp was already found to be invalid assert(::exists(fp)); error = locals::existsStr(fp); // Restore previous apply-to-all options if necessary if (!error.isEmpty() && appliedToAll()) { assert(!m_suffixText.isEmpty()); m_suffix->setText(m_suffixText); } break; case ADD_SUFFIX: // Save resolution options if necessary if (applyToAll) m_suffixText = m_suffix->text(); // Test produced file path const TFilePath &fp_suf = fp.withName(fp.getWideName() + m_suffix->text().toStdWString()); if (::exists(fp_suf)) error = locals::existsStr(fp_suf); else fp = fp_suf; break; } return error; } //----------------------------------------------------------------------------- void CleanupPopup::OverwriteDialog::initializeUserInteraction(const void *obj) { const TFilePath &fp = *static_cast(obj); // Generate a suitable initial suffix int num = 1; for (; ::exists(fp, num); ++num) ; m_suffix->setText(::suffix(num)); } //----------------------------------------------------------------------------- void CleanupPopup::OverwriteDialog::onButtonClicked(int buttonId) { m_suffix->setEnabled(buttonId == ADD_SUFFIX); } //----------------------------------------------------------------------------- void CleanupPopup::onImgViewBoxToggled(bool on) { m_imageViewer->setVisible(on); } //----------------------------------------------------------------------------- /*! Show the progress in the mainwindow's title bar */ void CleanupPopup::updateTitleString() { if (!TApp::instance()->getMainWindow()) return; MainWindow *mainWin = qobject_cast(TApp::instance()->getMainWindow()); if (!mainWin) return; QString str = QString::number(m_completion.first) + "/" + QString::number(m_completion.second) + tr(" : Cleanup in progress"); mainWin->changeWindowTitle(str); } //***************************************************************************** // CleanupCommand definition //***************************************************************************** class CleanupCommand final : public MenuItemHandler { public: CleanupCommand() : MenuItemHandler("MI_Cleanup") {} void execute() override { static CleanupPopup *popup = new CleanupPopup; popup->execute(); } } CleanupCommand;