diff --git a/toonz/sources/common/timage_io/tlevel_io.cpp b/toonz/sources/common/timage_io/tlevel_io.cpp index 0a4ba5e..3100832 100644 --- a/toonz/sources/common/timage_io/tlevel_io.cpp +++ b/toonz/sources/common/timage_io/tlevel_io.cpp @@ -139,6 +139,11 @@ TLevelP TLevelReader::loadInfo() { m_frameFormat = TFrameId::FOUR_ZEROS; else m_frameFormat = TFrameId::UNDERSCORE_FOUR_ZEROS; + } else if (ws.rfind(L'0') == 1) { // leads with any number of zeros + if (ws.rfind(L'_') == (int)wstring::npos) + m_frameFormat = TFrameId::CUSTOM_PAD; + else + m_frameFormat = TFrameId::UNDERSCORE_CUSTOM_PAD; } else { if (ws.rfind(L'_') == (int)wstring::npos) m_frameFormat = TFrameId::NO_PAD; diff --git a/toonz/sources/common/tsystem/tfilepath.cpp b/toonz/sources/common/tsystem/tfilepath.cpp index 37afe97..22ba475 100644 --- a/toonz/sources/common/tsystem/tfilepath.cpp +++ b/toonz/sources/common/tsystem/tfilepath.cpp @@ -35,10 +35,46 @@ namespace { * toSeg位置は含まず、それらの間に挟まれている文字列が「数字4ケタ」ならtrueを返す * --*/ bool isNumbers(std::wstring str, int fromSeg, int toSeg) { - if (toSeg - fromSeg != 5) return false; + /* + if (toSeg - fromSeg != 5) return false; + for (int pos = fromSeg + 1; pos < toSeg; pos++) { + if (str[pos] < '0' || str[pos] > '9') return false; + } + */ + // Let's check if it follows the format ####A (i.e 00001 or 00001a) + int numDigits = 0, numLetters = 0; for (int pos = fromSeg + 1; pos < toSeg; pos++) { - if (str[pos] < '0' || str[pos] > '9') return false; + if ((str[pos] >= 'A' && str[pos] <= 'Z') || + (str[pos] >= 'a' && str[pos] <= 'z')) { + // Not the right format if we ran into a letter without first finding a + // number + if (!numDigits) return false; + + // We'll keep track of the number of letters we find. + // NOTE: From here on out we should only see letters + numLetters++; + } else if (str[pos] >= '0' && str[pos] <= '9') { + // Not the right format if we ran into a number that followed a letter. + // This format is not something we expect currently + if (numLetters) return false; // not right format + + // We'll keep track of the number of digits we find. + numDigits++; + } else // Not the right format if we found something we didn't expect + return false; } + + // Not the right format if we see too many letters. + // At the time of this logic, we only expect 1 letter. Can expand to 2 or + // more later, if we want. + if (numLetters > 1) return false; + + return true; // We're good! +} + +bool checkForSeqNum(QString type) { + if (type == "myb" || type == "tlv" || type == "pli" || type == "tpl") + return false; return true; } }; @@ -55,6 +91,11 @@ std::string TFrameId::expand(FrameFormat format) const { o_buff.width(4); o_buff << m_frame; o_buff.width(0); + } else if (format == CUSTOM_PAD || format == UNDERSCORE_CUSTOM_PAD) { + o_buff.fill('0'); + o_buff.width(m_zeroPadding); + o_buff << m_frame; + o_buff.width(0); } else { o_buff << m_frame; } @@ -483,6 +524,7 @@ bool TFilePath::isRoot() const { // ritorna ""(niente tipo, niente punto), "." (file con tipo) o ".." (file con // tipo e frame) std::string TFilePath::getDots() const { + QString type = QString::fromStdString(getType()).toLower(); if (isFfmpegType()) return "."; int i = getLastSlash(m_path); std::wstring str = m_path.substr(i + 1); @@ -490,16 +532,14 @@ std::string TFilePath::getDots() const { i = str.rfind(L"."); if (i == (int)std::wstring::npos || str == L"..") return ""; - if (str.substr(0, i).rfind(L".") != std::wstring::npos) - return ".."; - else if (m_underscoreFormatAllowed) { - int j = str.substr(0, i).rfind(L"_"); - /*-- j == i-1は、フレーム番号を抜いて"A_.tga"のような場合の条件 --*/ - return (j != (int)std::wstring::npos && - (j == i - 1 || isNumbers(str, j, i))) - ? ".." - : "."; - } else + int j = str.substr(0, i).rfind(L"."); + if (j == (int)std::wstring::npos && m_underscoreFormatAllowed) + j = str.substr(0, i).rfind(L"_"); + + if (j != (int)std::wstring::npos) + return (j == i - 1 || (checkForSeqNum(type) && isNumbers(str, j, i))) ? ".." + : "."; + else return "."; } @@ -532,16 +572,19 @@ std::string TFilePath::getUndottedType() std::wstring TFilePath::getWideName() const // noDot! noSlash! { + QString type = QString::fromStdString(getType()).toLower(); int i = getLastSlash(m_path); // cerco l'ultimo slash std::wstring str = m_path.substr(i + 1); i = str.rfind(L"."); if (i == (int)std::wstring::npos) return str; int j = str.substr(0, i).rfind(L"."); - if (j != (int)std::wstring::npos) - i = j; - else if (m_underscoreFormatAllowed) { + if (j != (int)std::wstring::npos) { + if (checkForSeqNum(type) && isNumbers(str, j, i)) i = j; + } else if (m_underscoreFormatAllowed) { j = str.substr(0, i).rfind(L"_"); - if (j != (int)std::wstring::npos && isNumbers(str, j, i)) i = j; + if (j != (int)std::wstring::npos && checkForSeqNum(type) && + isNumbers(str, j, i)) + i = j; } return str.substr(0, i); } @@ -565,6 +608,7 @@ std::string TFilePath::getLevelName() const { std::wstring TFilePath::getLevelNameW() const { int i = getLastSlash(m_path); // cerco l'ultimo slash std::wstring str = m_path.substr(i + 1); // str e' m_path senza directory + QString type = QString::fromStdString(getType()).toLower(); if (isFfmpegType()) return str; int j = str.rfind(L"."); // str[j..] = ".type" if (j == (int)std::wstring::npos) return str; // no frame; no type @@ -575,7 +619,7 @@ std::wstring TFilePath::getLevelNameW() const { if (j == i || j - i == 1) // prova.tif o prova..tif return str; - if (!isNumbers(str, i, j)) return str; + if (!checkForSeqNum(type) || !isNumbers(str, i, j)) return str; // prova.0001.tif return str.erase(i + 1, j - i - 1); } @@ -601,7 +645,8 @@ TFilePath TFilePath::getParentDir() const // noSlash! //----------------------------------------------------------------------------- bool TFilePath::isLevelName() const { - if (isFfmpegType()) return false; + QString type = QString::fromStdString(getType()).toLower(); + if (isFfmpegType() || !checkForSeqNum(type)) return false; try { return getFrame() == TFrameId(TFrameId::EMPTY_FRAME); } @@ -614,6 +659,7 @@ bool TFilePath::isLevelName() const { TFrameId TFilePath::getFrame() const { int i = getLastSlash(m_path); // cerco l'ultimo slash std::wstring str = m_path.substr(i + 1); // str e' il path senza parentdir + QString type = QString::fromStdString(getType()).toLower(); i = str.rfind(L'.'); if (i == (int)std::wstring::npos || str == L"." || str == L"..") return TFrameId(TFrameId::NO_FRAME); @@ -628,11 +674,14 @@ TFrameId TFilePath::getFrame() const { /*-- 間が数字でない場合(ファイル名にまぎれた"_" や "."がある場合)を除外する * --*/ - if (!isNumbers(str, j, i)) return TFrameId(TFrameId::NO_FRAME); + if (!checkForSeqNum(type) || !isNumbers(str, j, i)) + return TFrameId(TFrameId::NO_FRAME); - int k, number = 0; - for (k = j + 1; k < i && iswdigit(str[k]); k++) - number = number * 10 + str[k] - L'0'; + int k, number = 0, digits = 0; + for (k = j + 1; k < i && iswdigit(str[k]); k++) { + digits++; + number = number * 10 + str[k] - L'0'; + } char letter = '\0'; if (iswalpha(str[k])) letter = str[k++] + ('a' - L'a'); @@ -641,7 +690,11 @@ TFrameId TFilePath::getFrame() const { *this, str + L": " + QObject::tr("Malformed frame name").toStdWString()); - return TFrameId(number, letter); + int padding = 0; + + if (str[j + 1] == '0') padding = digits; + + return TFrameId(number, letter, padding, str[j]); } //----------------------------------------------------------------------------- @@ -692,6 +745,7 @@ TFilePath TFilePath::withName(const std::string &name) const { TFilePath TFilePath::withName(const std::wstring &name) const { int i = getLastSlash(m_path); // cerco l'ultimo slash std::wstring str = m_path.substr(i + 1); // str e' il path senza parentdir + QString type = QString::fromStdString(getType()).toLower(); int j; j = str.rfind(L'.'); @@ -711,7 +765,7 @@ TFilePath TFilePath::withName(const std::wstring &name) const { if (k == (int)(std::wstring::npos)) k = j; - else if (k != j - 1 && !isNumbers(str, k, j)) + else if (k != j - 1 && (!checkForSeqNum(type) || !isNumbers(str, k, j))) k = j; return TFilePath(m_path.substr(0, i + 1) + name + str.substr(k)); @@ -731,11 +785,16 @@ TFilePath TFilePath::withFrame(const TFrameId &frame, const std::wstring dot = L".", dotDot = L".."; int i = getLastSlash(m_path); // cerco l'ultimo slash std::wstring str = m_path.substr(i + 1); // str e' il path senza parentdir + QString type = QString::fromStdString(getType()).toLower(); assert(str != dot && str != dotDot); int j = str.rfind(L'.'); const char *ch = "."; + // Override format input because it may be wrong. + if (!isFfmpegType() && checkForSeqNum(type)) + format = frame.getCurrentFormat(); if (m_underscoreFormatAllowed && (format == TFrameId::UNDERSCORE_FOUR_ZEROS || - format == TFrameId::UNDERSCORE_NO_PAD)) + format == TFrameId::UNDERSCORE_NO_PAD || + format == TFrameId::UNDERSCORE_CUSTOM_PAD)) ch = "_"; if (j == (int)std::wstring::npos) { if (frame.isEmptyFrame() || frame.isNoFrame()) @@ -744,14 +803,23 @@ TFilePath TFilePath::withFrame(const TFrameId &frame, return TFilePath(m_path + ::to_wstring(ch + frame.expand(format))); } + int k = str.substr(0, j).rfind(L'.'); + + bool hasValidFrameNum = false; + if (!isFfmpegType() && checkForSeqNum(type) && isNumbers(str, k, j)) + hasValidFrameNum = true; std::string frameString; - if (frame.isNoFrame()) - frameString = ""; - else + if (frame.isNoFrame() || + (!frame.isEmptyFrame() && getDots() != "." && !hasValidFrameNum)) { + if (k != (int)std::wstring::npos) { + std::wstring wstr = str.substr(k, j - k); + std::string str2(wstr.begin(), wstr.end()); + frameString = str2; + } else + frameString = ""; + } else frameString = ch + frame.expand(format); - int k = str.substr(0, j).rfind(L'.'); - if (k != (int)std::wstring::npos) return TFilePath(m_path.substr(0, k + i + 1) + ::to_wstring(frameString) + str.substr(j)); @@ -759,7 +827,9 @@ TFilePath TFilePath::withFrame(const TFrameId &frame, k = str.substr(0, j).rfind(L'_'); if (k != (int)std::wstring::npos && (k == j - 1 || - isNumbers(str, k, j))) /*-- "_." の並びか、"_[数字]."の並びのとき --*/ + (checkForSeqNum(type) && + isNumbers(str, k, + j)))) /*-- "_." の並びか、"_[数字]."の並びのとき --*/ return TFilePath(m_path.substr(0, k + i + 1) + ((frame.isNoFrame()) ? L"" diff --git a/toonz/sources/include/tfilepath.h b/toonz/sources/include/tfilepath.h index 8ee09ca..b71f727 100644 --- a/toonz/sources/include/tfilepath.h +++ b/toonz/sources/include/tfilepath.h @@ -30,6 +30,8 @@ class DVAPI TFrameId { int m_frame; char m_letter; // serve per i frame "aggiunti" del tipo pippo.0001a.tzp => // f=1 c='a' + int m_zeroPadding; + char m_startSeqInd; public: enum { @@ -41,11 +43,20 @@ public: FOUR_ZEROS, NO_PAD, UNDERSCORE_FOUR_ZEROS, // pippo_0001.tif - UNDERSCORE_NO_PAD + UNDERSCORE_NO_PAD, + CUSTOM_PAD, + UNDERSCORE_CUSTOM_PAD, + USE_CURRENT_FORMAT }; // pippo_1.tif - TFrameId(int f = EMPTY_FRAME) : m_frame(f), m_letter(0) {} - TFrameId(int f, char c) : m_frame(f), m_letter(c) {} + TFrameId(int f = EMPTY_FRAME) + : m_frame(f), m_letter(0), m_zeroPadding(4), m_startSeqInd('.') {} + TFrameId(int f, char c) + : m_frame(f), m_letter(c), m_zeroPadding(4), m_startSeqInd('.') {} + TFrameId(int f, char c, int p) + : m_frame(f), m_letter(c), m_zeroPadding(p), m_startSeqInd('.') {} + TFrameId(int f, char c, int p, char s) + : m_frame(f), m_letter(c), m_zeroPadding(p), m_startSeqInd(s) {} inline bool operator==(const TFrameId &f) const { return f.m_frame == m_frame && f.m_letter == m_letter; @@ -77,6 +88,25 @@ public: std::string expand(FrameFormat format = FOUR_ZEROS) const; int getNumber() const { return m_frame; } char getLetter() const { return m_letter; } + + void setZeroPadding(int p) { m_zeroPadding = p; } + int getZeroPadding() const { return m_zeroPadding; } + + void setStartSeqInd(char c) { m_startSeqInd = c; } + char getStartSeqInd() const { return m_startSeqInd; } + + FrameFormat getCurrentFormat() const { + switch (m_zeroPadding) { + case 0: + return (m_startSeqInd == '.' ? NO_PAD : UNDERSCORE_NO_PAD); + case 4: + return (m_startSeqInd == '.' ? FOUR_ZEROS : UNDERSCORE_FOUR_ZEROS); + default: + break; + } + + return (m_startSeqInd == '.' ? CUSTOM_PAD : UNDERSCORE_CUSTOM_PAD); + } }; //----------------------------------------------------------------------------- @@ -196,8 +226,9 @@ type is a string that indicate the filename extension(ex:. bmp or .bmp)*/ /*!Return a TFilePath without parent directory */ TFilePath withoutParentDir() const { return withParentDir(TFilePath()); } /*!Return a TFilePath with frame "frame".*/ - TFilePath withFrame(const TFrameId &frame, TFrameId::FrameFormat format = - TFrameId::FOUR_ZEROS) const; + TFilePath withFrame( + const TFrameId &frame, + TFrameId::FrameFormat format = TFrameId::USE_CURRENT_FORMAT) const; /*!Return a TFilePath with a frame identified by an integer number "f".*/ TFilePath withFrame(int f) const { return withFrame(TFrameId(f)); } /*!Return a TFilePath with a frame identified by an integer and by a diff --git a/toonz/sources/toonz/filmstrip.cpp b/toonz/sources/toonz/filmstrip.cpp index 75fcb89..7081364 100644 --- a/toonz/sources/toonz/filmstrip.cpp +++ b/toonz/sources/toonz/filmstrip.cpp @@ -605,7 +605,9 @@ void FilmstripFrames::paintEvent(QPaintEvent *evt) { } // for sequencial frame else { - text = QString::number(fid.getNumber()).rightJustified(4, '0'); + char letter = fid.getLetter(); + text = QString::number(fid.getNumber()).rightJustified(4, '0') + + (letter != '\0' ? QString(letter) : ""); } p.drawText(tmp_frameRect.adjusted(0, 0, -3, 2), text, QTextOption(Qt::AlignRight | Qt::AlignBottom)); diff --git a/toonz/sources/toonz/iocommand.cpp b/toonz/sources/toonz/iocommand.cpp index a25f29e..02f5608 100644 --- a/toonz/sources/toonz/iocommand.cpp +++ b/toonz/sources/toonz/iocommand.cpp @@ -2293,16 +2293,50 @@ int IoCmd::loadResources(LoadResourceArguments &args, bool updateRecentFile, LoadResourceArguments::IMPORT); } + vector paths; + int all = 0; + // Loop for all the resources to load for (int r = 0; r != rCount; ++r) { if (importDialog.aborted()) break; + QString origName = + args.resourceDatas[r].m_path.withoutParentDir().getQString(); + LoadResourceArguments::ResourceData rd(args.resourceDatas[r]); TFilePath &path = rd.m_path; if (!rd.m_path.isLevelName()) path = TFilePath(path.getLevelNameW()).withParentDir(path.getParentDir()); + if (std::find(paths.begin(), paths.end(), path) != paths.end()) { + if (!all) { + QString question = + QObject::tr( + "File '%1' will reload level '%2' as a duplicate column in the " + "xsheet.\n\nAllow duplicate?") + .arg(origName) + .arg(QString::fromStdString(path.getName())); + QString Yes = QObject::tr("Allow"); + QString YesAll = QObject::tr("Allow All Dups"); + QString No = QObject::tr("No"); + QString NoAll = QObject::tr("No to All Dups"); + int ret = DVGui::MsgBox(question, Yes, YesAll, No, NoAll, 0); + switch (ret) { + case 2: + all = 1; // YesAll + case 1: + break; // Yes + case 4: + all = 2; // NoAll + case 3: + continue; + } + } else if (all == 2) + continue; + } + paths.push_back(path); + if (progressDialog) { if (progressDialog->wasCanceled()) break;