"Fossies" - the Fresh Open Source Software Archive

Member "MP3Diags-unstable-1.5.01/src/FileRenamerDlgImpl.cpp" (11 Feb 2019, 40907 Bytes) of package /linux/privat/MP3Diags-unstable-1.5.01.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "FileRenamerDlgImpl.cpp" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 1.3.04_vs_1.5.01.

    1 /***************************************************************************
    2  *   MP3 Diags - diagnosis, repairs and tag editing for MP3 files          *
    3  *                                                                         *
    4  *   Copyright (C) 2009 by Marian Ciobanu                                  *
    5  *   ciobi@inbox.com                                                       *
    6  *                                                                         *
    7  *   This program is free software; you can redistribute it and/or modify  *
    8  *   it under the terms of the GNU General Public License version 2 as     *
    9  *   published by the Free Software Foundation.                            *
   10  *                                                                         *
   11  *   This program is distributed in the hope that it will be useful,       *
   12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
   13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
   14  *   GNU General Public License for more details.                          *
   15  *                                                                         *
   16  *   You should have received a copy of the GNU General Public License     *
   17  *   along with this program; if not, write to the                         *
   18  *   Free Software Foundation, Inc.,                                       *
   19  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
   20  ***************************************************************************/
   21 
   22 
   23 #include  <sstream>
   24 
   25 #include  <QTimer>
   26 #include  <QPainter>
   27 #include  <QHeaderView>
   28 #include  <QButtonGroup>
   29 
   30 #include  "FileRenamerDlgImpl.h"
   31 
   32 #include  "Helpers.h"
   33 #include  "ColumnResizer.h"
   34 #include  "CommonData.h"
   35 #include  "DataStream.h"
   36 #include  "RenamerPatternsDlgImpl.h"
   37 #include  "OsFile.h"
   38 #include  "ThreadRunnerDlgImpl.h"
   39 #include  "StoredSettings.h"
   40 #include  "Id3V230Stream.h"
   41 #include  "Widgets.h"
   42 
   43 
   44 using namespace std;
   45 using namespace pearl;
   46 
   47 using namespace FileRenamer;
   48 
   49 
   50 
   51 HndlrListModel::HndlrListModel(CommonData* pCommonData, FileRenamerDlgImpl* pFileRenamerDlgImpl, bool bUseCurrentView) : m_pFileRenamerDlgImpl(pFileRenamerDlgImpl), m_pCommonData(pCommonData), m_pRenamer(0), m_bUseCurrentView(bUseCurrentView)
   52 {
   53 }
   54 
   55 HndlrListModel::~HndlrListModel()
   56 {
   57     delete m_pRenamer;
   58 }
   59 
   60 void HndlrListModel::setRenamer(const Renamer* p)
   61 {
   62     delete m_pRenamer;
   63     m_pRenamer = p;
   64     emitLayoutChanged();
   65 }
   66 
   67 
   68 void HndlrListModel::setUnratedAsDuplicates(bool bUnratedAsDuplicate)
   69 {
   70     if (0 == m_pRenamer) { return; }
   71 
   72     m_pRenamer->m_bUnratedAsDuplicate = bUnratedAsDuplicate;
   73     emitLayoutChanged();
   74 }
   75 
   76 
   77 // returns either m_pCommonData->getCrtAlbum() or m_pCommonData->getViewHandlers(), based on m_bUseCurrentView
   78 const deque<const Mp3Handler*> HndlrListModel::getHandlerList() const
   79 {
   80     return m_bUseCurrentView ? m_pCommonData->getViewHandlers() : m_pCommonData->getCrtAlbum();
   81 }
   82 
   83 
   84 /*override*/ int HndlrListModel::rowCount(const QModelIndex&) const
   85 {
   86     return cSize(getHandlerList());
   87 }
   88 
   89 
   90 /*override*/ int HndlrListModel::columnCount(const QModelIndex&) const
   91 {
   92     return TagReader::LIST_END + 2 - 1;
   93 }
   94 
   95 
   96 /*override*/ Qt::ItemFlags HndlrListModel::flags(const QModelIndex& index) const
   97 {
   98     Qt::ItemFlags flg (QAbstractTableModel::flags(index));
   99     if (1 == index.column() && 0 != m_pRenamer)
  100     {
  101         flg = flg | Qt::ItemIsEditable;
  102     }
  103     return flg;
  104 }
  105 
  106 /*override*/ QVariant HndlrListModel::data(const QModelIndex& index, int nRole) const
  107 {
  108 //LAST_STEP("HndlrListModel::data()");
  109     if (!index.isValid()) { return QVariant(); }
  110     int i (index.row());
  111     int j (index.column());
  112 
  113     if (Qt::DisplayRole != nRole && Qt::ToolTipRole != nRole && Qt::EditRole != nRole) { return QVariant(); }
  114 
  115     QString s;
  116 
  117     const Mp3Handler* p (getHandlerList().at(i));
  118     const Id3V2StreamBase* pId3V2 (p->getId3V2Stream());
  119 
  120     if (0 == j)
  121     {
  122         s = convStr(p->getShortName());
  123     }
  124     else if (0 == pId3V2)
  125     {
  126         //s = "N/A";
  127         s = tr("<< missing ID3V2 >>");
  128     }
  129     else if (1 == j)
  130     {
  131         //s = convStr(p->getName());
  132         if (0 == m_pRenamer)
  133         {
  134             //s = "N/A";
  135             s = tr("<< no pattern defined >>");
  136         }
  137         else
  138         {
  139             s = toNativeSeparators(convStr(m_pRenamer->getNewName(p)));
  140             if (s.isEmpty())
  141             {
  142                 //s = "N/A";
  143                 s = tr("<< missing fields >>");
  144             }
  145         }
  146     }
  147     else
  148     {
  149         j -= 2;
  150         if (j >= TagReader::POS_OF_FEATURE[TagReader::IMAGE])
  151         {
  152             j += 1;
  153         }
  154         s = convStr(pId3V2->getValue((TagReader::Feature)TagReader::FEATURE_ON_POS[j]));
  155     }
  156 
  157     if (Qt::DisplayRole == nRole || Qt::EditRole == nRole)
  158     {
  159         return s;
  160     }
  161 
  162     // so it's Qt::ToolTipRole
  163 
  164     QFontMetrics fm (m_pFileRenamerDlgImpl->m_pCurrentAlbumG->fontMetrics()); // !!! no need to use "QApplication::fontMetrics()"
  165     int nWidth (fm.width(s));
  166 //qDebug("tooltip for %s, width %d
  167     int nMargin (QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1); // this "1" should probably be another "metric" constant
  168     if (nWidth + 2*nMargin + 1 <= m_pFileRenamerDlgImpl->m_pCurrentAlbumG->horizontalHeader()->sectionSize(index.column())) // ttt2 not sure this "nMargin" is correct
  169     {
  170         //return QVariant();
  171         return ""; // !!! with "return QVariant()" the previous tooltip remains until the cursor moves over another cell that has a tooltip
  172     }
  173 
  174     return s;
  175 }
  176 
  177 
  178 /*override*/ bool HndlrListModel::setData(const QModelIndex& index, const QVariant& value, int nRole /* = Qt::EditRole*/)
  179 {
  180     if (Qt::EditRole != nRole) { return false; }
  181 
  182     CB_ASSERT (0 != m_pRenamer);
  183 
  184     m_pRenamer->m_mValues[getHandlerList().at(index.row())] = convStr(fromNativeSeparators(value.toString()));
  185     return true;
  186 }
  187 
  188 
  189 
  190 /*override*/ QVariant HndlrListModel::headerData(int nSection, Qt::Orientation eOrientation, int nRole /* = Qt::DisplayRole*/) const
  191 {
  192 //LAST_STEP("HndlrListModel::headerData");
  193     if (nRole != Qt::DisplayRole) { return QVariant(); }
  194 
  195     if (Qt::Horizontal == eOrientation)
  196     {
  197         if (0 == nSection) { return tr("File name"); }
  198         else if (1 == nSection) { return tr("New file name"); }
  199         else
  200         {
  201             nSection -= 2;
  202             if (nSection >= TagReader::POS_OF_FEATURE[TagReader::IMAGE])
  203             {
  204                 nSection += 1;
  205             }
  206             return TagReader::tr(TagReader::getLabel(TagReader::FEATURE_ON_POS[nSection]));
  207         }
  208     }
  209 
  210     return nSection + 1;
  211 }
  212 
  213 
  214 //======================================================================================================================
  215 //======================================================================================================================
  216 
  217 
  218 CurrentAlbumDelegate::CurrentAlbumDelegate(QWidget* pParent, HndlrListModel* pHndlrListModel) : QItemDelegate(pParent), m_pHndlrListModel(pHndlrListModel)
  219 {
  220 }
  221 
  222 
  223 /*override*/ void CurrentAlbumDelegate::paint(QPainter* pPainter, const QStyleOptionViewItem& option, const QModelIndex& index) const
  224 {
  225     pPainter->save();
  226 
  227     const Mp3Handler* p (m_pHndlrListModel->getHandlerList().at(index.row()));
  228     const Id3V2StreamBase* pId3V2 (p->getId3V2Stream());
  229 
  230     if (0 == pId3V2)
  231     {
  232         //pPainter->fillRect(option.rect, QColor(255, 226, 236)); //ttt2 perhaps put back, but should work for "missing fields" as well
  233     }
  234 
  235     QStyleOptionViewItemV2 myOption (option);
  236 
  237     if (0 != m_pHndlrListModel->getRenamer() && index.column() == 1)
  238     {
  239         string strNewName (m_pHndlrListModel->getRenamer()->getNewName(p));
  240         if (fileExists(strNewName))
  241         {
  242             pPainter->fillRect(option.rect, strNewName == p->getName() ? QColor(226, 236, 255) : QColor(255, 226, 236));
  243         }
  244 
  245         if (m_pHndlrListModel->getRenamer()->m_mValues.count(p) > 0)
  246         {
  247             myOption.font.setItalic(true);
  248         }
  249     }
  250 
  251     QItemDelegate::paint(pPainter, myOption, index);
  252 
  253     pPainter->restore();
  254 }
  255 
  256 
  257 
  258 //======================================================================================================================
  259 //======================================================================================================================
  260 //======================================================================================================================
  261 
  262 
  263 
  264 FileRenamerDlgImpl::FileRenamerDlgImpl(QWidget* pParent, CommonData* pCommonData, bool bUseCurrentView) : QDialog(pParent, getDialogWndFlags()), Ui::FileRenamerDlg(), m_pCommonData(pCommonData), m_bUseCurrentView(bUseCurrentView), m_pEditor(0)
  265 {
  266     setupUi(this);
  267 
  268     resizeIcons();
  269 
  270     m_pHndlrListModel = new HndlrListModel(m_pCommonData, this, bUseCurrentView);
  271 
  272     {
  273         m_pCurrentAlbumG->verticalHeader()->setSectionResizeMode(QHeaderView::Interactive);
  274         m_pCurrentAlbumG->verticalHeader()->setMinimumSectionSize(CELL_HEIGHT + 1);
  275         m_pCurrentAlbumG->verticalHeader()->setDefaultSectionSize(CELL_HEIGHT + 1);//*/
  276 
  277         m_pCurrentAlbumG->setModel(m_pHndlrListModel);
  278 
  279         CurrentAlbumDelegate* pDel (new CurrentAlbumDelegate(this, m_pHndlrListModel));
  280         m_pCurrentAlbumG->setItemDelegate(pDel);
  281 
  282         m_pCurrentAlbumG->viewport()->installEventFilter(this);
  283     }
  284 
  285     m_pButtonGroup = new QButtonGroup(this);
  286 
  287     loadPatterns();
  288     updateButtons();
  289 
  290     {
  291         int nWidth, nHeight;
  292         bool bKeepOriginal, bUnratedAsDuplicate;
  293         m_pCommonData->m_settings.loadRenamerSettings(nWidth, nHeight, m_nSaButton, m_nVaButton, bKeepOriginal, bUnratedAsDuplicate);
  294         if (nWidth > 400 && nHeight > 400)
  295         {
  296             resize(nWidth, nHeight);
  297         }
  298         else
  299         {
  300             defaultResize(*this);
  301         }
  302 
  303         if (m_nVaButton >= cSize(m_vstrPatterns) || m_nVaButton <= 0) { m_nVaButton = 0; }
  304         if (m_nSaButton >= cSize(m_vstrPatterns) || m_nSaButton <= 0) { m_nSaButton = 0; }
  305         m_pKeepOriginalCkB->setChecked(bKeepOriginal);
  306         m_pMarkUnratedAsDuplicatesCkB->setChecked(bUnratedAsDuplicate);
  307     }
  308 
  309 
  310     { m_pModifRenameB = new ModifInfoToolButton(m_pRenameB); connect(m_pModifRenameB, SIGNAL(clicked()), this, SLOT(on_m_pRenameB_clicked())); m_pRenameB = m_pModifRenameB; }
  311 
  312     { QAction* p (new QAction(this)); p->setShortcut(QKeySequence("F1")); connect(p, SIGNAL(triggered()), this, SLOT(onHelp())); addAction(p); }
  313 
  314     if (m_bUseCurrentView)
  315     {
  316         m_pCrtDirTagEdtE->setEnabled(false);
  317         m_pPrevB->setEnabled(false);
  318         m_pNextB->setEnabled(false);
  319     }
  320 
  321     if (!m_pCommonData->m_bShowCustomCloseButtons)
  322     {
  323         m_pCloseB->hide();
  324     }
  325 
  326 
  327 
  328     QTimer::singleShot(1, this, SLOT(onShow())); // just calls reloadTable(); !!! needed to properly resize the table columns; album and file tables have very small widths until they are actually shown, so calling resizeTagEditor() earlier is pointless; calling update() on various layouts seems pointless as well; (see also DoubleList::resizeEvent() )
  329 }
  330 
  331 
  332 FileRenamerDlgImpl::~FileRenamerDlgImpl()
  333 {
  334     m_pCommonData->m_settings.saveRenamerSettings(width(), height(), m_nSaButton, m_nVaButton, m_pKeepOriginalCkB->isChecked(), m_pMarkUnratedAsDuplicatesCkB->isChecked());
  335 }
  336 
  337 
  338 string FileRenamerDlgImpl::run()
  339 {
  340     exec();
  341     int k (m_pCurrentAlbumG->currentIndex().row());
  342     const deque<const Mp3Handler*>& vpCrtAlbum (m_pHndlrListModel->getHandlerList());
  343     if (k < 0 || k >= cSize(vpCrtAlbum)) { return ""; }
  344     return vpCrtAlbum[k]->getName();
  345 }
  346 
  347 
  348 
  349 void FileRenamerDlgImpl::on_m_pNextB_clicked()
  350 {
  351     closeEditor();
  352     if (m_pCommonData->nextAlbum())
  353     {
  354         reloadTable();
  355     }
  356 }
  357 
  358 void FileRenamerDlgImpl::on_m_pPrevB_clicked()
  359 {
  360     closeEditor();
  361     if (m_pCommonData->prevAlbum())
  362     {
  363         reloadTable();
  364     }
  365 }
  366 
  367 
  368 void FileRenamerDlgImpl::on_m_pEditPatternsB_clicked()
  369 {
  370     RenamerPatternsDlgImpl dlg (this, m_pCommonData->m_settings);
  371     if (dlg.run(m_vstrPatterns))
  372     {
  373         m_nVaButton = m_nSaButton = 0;
  374         savePatterns();
  375         updateButtons();
  376         selectPattern();
  377     }
  378 }
  379 
  380 
  381 void FileRenamerDlgImpl::updateButtons()
  382 {
  383     m_nBtnId = 0;
  384     createButtons();
  385 }
  386 
  387 
  388 void FileRenamerDlgImpl::createButtons()
  389 {
  390     QBoxLayout* pLayout (dynamic_cast<QBoxLayout*>(m_pButtonsW->layout()));
  391     CB_ASSERT (0 != pLayout);
  392     /*int nPos (pLayout->indexOf(pOldBtn));
  393     pLayout->insertWidget(nPos, this);*/
  394 
  395     QObjectList l (m_pButtonsW->children());
  396     //qDebug("cnt: %d", l.size());
  397 
  398     for (int i = 1, n = l.size(); i < n; ++i) // l[0] is m_pButtonsW's layout (note that m_pAlbumTypeL is in m_pBtnPanelW)
  399     {
  400         delete l[i];
  401     }
  402 
  403     for (int i = 0, n = cSize(m_vstrPatterns); i < n; ++i)
  404     {
  405         QToolButton* p (new QToolButton(m_pButtonsW));
  406         p->setText(toNativeSeparators(convStr(m_vstrPatterns[i])));
  407         p->setCheckable(true);
  408         m_pButtonGroup->addButton(p, m_nBtnId++);
  409         //p->setAutoExclusive(true);
  410         connect(p, SIGNAL(clicked()), this, SLOT(onPatternClicked()));
  411         pLayout->insertWidget(i, p);
  412     }
  413 }
  414 
  415 
  416 void FileRenamerDlgImpl::onPatternClicked()
  417 {
  418     int nId (m_pButtonGroup->checkedId());
  419     int n (cSize(m_vstrPatterns));
  420     //qDebug("id=%d", nId);
  421     //CB_ASSERT (nId >= 0 && nId < 2*n);
  422     CB_ASSERT (nId >= 0 && nId < n);
  423     if (nId >= n) { nId -= n; }
  424 
  425     if (isSingleArtist())
  426     {
  427         m_nSaButton = nId;
  428     }
  429     else
  430     {
  431         m_nVaButton = nId;
  432     }
  433     m_pHndlrListModel->setRenamer(new Renamer(m_vstrPatterns[nId], m_pCommonData, m_pMarkUnratedAsDuplicatesCkB->isChecked()));
  434     resizeUi();
  435 }
  436 
  437 
  438 void FileRenamerDlgImpl::closeEditor()
  439 {
  440     if (0 != m_pEditor)
  441     {
  442         delete m_pEditor;
  443     }
  444 }
  445 
  446 
  447 /*override*/ bool FileRenamerDlgImpl::eventFilter(QObject* pObj, QEvent* pEvent)
  448 {
  449 //qDebug("ev %d", int(pEvent->type()));
  450     if (QEvent::ChildAdded == pEvent->type())
  451     {
  452         //qDebug("add");
  453         QObject* pChild (((QChildEvent*)pEvent)->child());
  454         if (pChild->isWidgetType())
  455         {
  456             CB_ASSERT (0 == m_pEditor);
  457             m_pEditor = pChild;
  458         }
  459     }
  460     else if (QEvent::ChildRemoved == pEvent->type())
  461     {
  462         //qDebug("rm");
  463         QObject* pChild (((QChildEvent*)pEvent)->child());
  464         if (pChild->isWidgetType())
  465         {
  466             CB_ASSERT (pChild == m_pEditor);
  467             m_pEditor = 0;
  468         }
  469     }
  470     return QDialog::eventFilter(pObj, pEvent);
  471 }
  472 
  473 
  474 //=====================================================================================================================
  475 //=====================================================================================================================
  476 //=====================================================================================================================
  477 
  478 
  479 
  480 namespace {
  481 
  482 struct RenameThread : public PausableThread
  483 {
  484     const deque<const Mp3Handler*>& m_vpHndl;
  485     bool m_bKeepOrig;
  486     const Renamer* m_pRenamer;
  487     CommonData* m_pCommonData;
  488     vector<const Mp3Handler*>& m_vpDel;
  489     vector<const Mp3Handler*>& m_vpAdd;
  490 
  491     QString m_qstrErr;
  492 
  493     RenameThread(const deque<const Mp3Handler*>& vpHndl, bool bKeepOrig, const Renamer* pRenamer, CommonData* pCommonData, vector<const Mp3Handler*>& vpDel, vector<const Mp3Handler*>& vpAdd) :
  494             m_vpHndl(vpHndl),
  495             m_bKeepOrig(bKeepOrig),
  496             m_pRenamer(pRenamer),
  497 
  498             m_pCommonData(pCommonData),
  499             m_vpDel(vpDel),
  500             m_vpAdd(vpAdd)
  501     {
  502     }
  503 
  504     /*override*/ void run()
  505     {
  506         CompleteNotif notif(this);
  507 
  508         notif.setSuccess(proc());
  509     }
  510 
  511     bool proc();
  512 };
  513 
  514 
  515 bool RenameThread::proc()
  516 {
  517     for (int i = 0, n = cSize(m_vpHndl); i < n; ++i)
  518     {
  519         if (isAborted()) { return false; }
  520         checkPause();
  521 
  522         QString qstrName (m_vpHndl[i]->getUiName());
  523         StrList l;
  524         l.push_back(qstrName);
  525         emit stepChanged(l, -1);
  526 
  527         string strDest (m_pRenamer->getNewName(m_vpHndl[i]));
  528 
  529         if (!strDest.empty())
  530         {
  531             CB_ASSERT (string::npos == strDest.find("//"));
  532 
  533             try
  534             {
  535                 bool bSkipped (false);
  536                 //qDebug("ren %s", strDest.c_str());
  537                 if (m_bKeepOrig)
  538                 {
  539                     copyFile2(m_vpHndl[i]->getName(), strDest);
  540                 }
  541                 else
  542                 {
  543                     if (m_vpHndl[i]->getName() == strDest) // ttt2 doesn't work well on case-insensitive file systems
  544                     {
  545                         bSkipped = true;
  546                     }
  547                     else
  548                     {
  549                         renameFile(m_vpHndl[i]->getName(), strDest);
  550                         m_vpDel.push_back(m_vpHndl[i]);
  551                     }
  552                 }
  553 
  554                 if (!bSkipped && m_pCommonData->m_dirTreeEnum.isIncluded(strDest))
  555                 {
  556                     try
  557                     {
  558                         m_vpAdd.push_back(Mp3Handler::create(strDest, m_pCommonData->m_bUseAllNotes, m_pCommonData->getQualThresholds()));
  559                     }
  560                     catch (const Mp3Handler::FileNotFound&)
  561                     {
  562                     }
  563                 }
  564             }
  565             catch (const FoundDir&)
  566             {
  567                 m_qstrErr = FileRenamerDlgImpl::tr("Source or destination is a directory");
  568             }
  569             catch (const CannotCopyFile&)
  570             {
  571                 m_qstrErr = FileRenamerDlgImpl::tr("Error during copying");
  572             }
  573             catch (const CannotRenameFile&)
  574             {
  575                 m_qstrErr = FileRenamerDlgImpl::tr("Error during renaming");
  576             }
  577             catch (const AlreadyExists&)
  578             {
  579                 m_qstrErr = FileRenamerDlgImpl::tr("Destination already exists");
  580             }
  581             catch (const NameNotFound&)
  582             {
  583                 m_qstrErr = FileRenamerDlgImpl::tr("Source not found");
  584             }
  585             catch (const CannotCreateDir& ex)
  586             {
  587                 m_qstrErr = FileRenamerDlgImpl::tr("Cannot create folder %1").arg(convStr(ex.m_strDir));
  588             }
  589             catch (const std::bad_alloc&) { throw; }
  590             catch (const IncorrectDirName& ex)
  591             {
  592                 CB_ASSERT1 (false, ex.what());
  593             }
  594             catch (const exception& ex)
  595             {
  596                 m_qstrErr = ex.what();
  597             }
  598             catch (...)
  599             {
  600                 m_qstrErr = FileRenamerDlgImpl::tr("Unknown error");
  601             }
  602 
  603             if (!m_qstrErr.isEmpty())
  604             {
  605                 return false;
  606             }
  607         }
  608     }
  609 
  610     return true;
  611 }
  612 
  613 } // namespace
  614 
  615 
  616 
  617 
  618 //=====================================================================================================================
  619 //=====================================================================================================================
  620 //=====================================================================================================================
  621 
  622 
  623 void FileRenamerDlgImpl::on_m_pRenameB_clicked()
  624 {
  625     if (m_vstrPatterns.empty())
  626     {
  627         showCritical(this, tr("No patterns exist"), tr("You must create at least a pattern before you can start renaming files."));
  628         return;
  629     }
  630 
  631     const Renamer* pRenamer (m_pHndlrListModel->getRenamer());
  632     CB_ASSERT (0 != pRenamer);
  633 
  634     bool bAll (0 == (Qt::ShiftModifier & m_pModifRenameB->getModifiers()));
  635     bool bKeepOrig (m_pKeepOriginalCkB->isChecked());
  636 
  637     const deque<const Mp3Handler*>& vpAllHndl (m_pHndlrListModel->getHandlerList());
  638     deque<const Mp3Handler*> vpHndl;
  639     if (bAll)
  640     {
  641         vpHndl = vpAllHndl;
  642     }
  643     else
  644     {
  645         QItemSelectionModel* pSelModel (m_pCurrentAlbumG->selectionModel());
  646 
  647         QModelIndexList listSel (pSelModel->selectedIndexes());
  648         set<int> snSongs;
  649         for (QModelIndexList::iterator it = listSel.begin(), end = listSel.end(); it != end; ++it)
  650         {
  651             QModelIndex ndx (*it);
  652             int nSong (ndx.row());
  653             snSongs.insert(nSong);
  654         }
  655 
  656         for (set<int>::iterator it = snSongs.begin(), end = snSongs.end(); it != end; ++it)
  657         {
  658             vpHndl.push_back(vpAllHndl[*it]);
  659         }
  660     }
  661 
  662     if (showMessage(this, QMessageBox::Question, 1, 1, tr("Confirm"), bKeepOrig ? (bAll ? tr("Copy all the files?") : tr("Copy the selected files?")) : (bAll ? tr("Rename all the files?") : tr("Rename the selected files?")), tr("&Yes"), tr("Cancel")) != 0) { return; }
  663 
  664 
  665     {
  666         set<string> s;
  667         for (int i = 0, n = cSize(vpHndl); i < n; ++i)
  668         {
  669             if (0 == vpHndl[i]->getId3V2Stream())
  670             {
  671                 showCritical(this, tr("Error"), tr("Operation aborted because file \"%1\" doesn't have an ID3V2 tag.").arg(vpHndl[i]->getUiName()));
  672                 return;
  673             }
  674 
  675             string strDest (pRenamer->getNewName(vpHndl[i]));
  676             if (strDest.empty())
  677             {
  678                 showCritical(this, tr("Error"), tr("Operation aborted because file \"%1\" is missing some required fields in its ID3V2 tag.").arg(vpHndl[i]->getUiName()));
  679                 return;
  680             }
  681 
  682             if (s.count(strDest) > 0)
  683             {
  684                 showCritical(this, tr("Error"), tr("Operation aborted because it would create 2 copies of a file called \"%1\"").arg(toNativeSeparators(convStr(strDest))));
  685                 return;
  686             }
  687 
  688             if (fileExists(strDest) && (strDest != vpHndl[i]->getName() || bKeepOrig))
  689             {
  690                 showCritical(this, tr("Error"), tr("Operation aborted because a file called \"%1\" already exists.").arg(toNativeSeparators(convStr(strDest))));
  691                 return;
  692             }
  693 
  694             s.insert(strDest);
  695         }
  696     }
  697 
  698     vector<const Mp3Handler*> vpDel, vpAdd;
  699 
  700     {
  701         RenameThread* pThread (new RenameThread(vpHndl, bKeepOrig, pRenamer, m_pCommonData, vpDel, vpAdd));
  702 
  703         ThreadRunnerDlgImpl dlg (this, getNoResizeWndFlags(), pThread, ThreadRunnerDlgImpl::SHOW_COUNTER, ThreadRunnerDlgImpl::TRUNCATE_BEGIN);
  704         dlg.setWindowTitle(bKeepOrig ? (bAll ? tr("Copying all the files in the current album") : tr("Copying the selected files in the current album")) : (bAll ? tr("Renaming all the files in the current album") : tr("Renaming the selected files in the current album")));
  705 
  706         dlg.exec();
  707         if (!pThread->m_qstrErr.isEmpty())
  708         {
  709             showCritical(this, tr("Error"), pThread->m_qstrErr);
  710         }
  711     }
  712 
  713     m_pCommonData->mergeHandlerChanges(vpAdd, vpDel, CommonData::SEL | CommonData::CURRENT);
  714 
  715     if (!bKeepOrig || pRenamer->isSameDir())
  716     {
  717         reloadTable();
  718     }
  719 }
  720 
  721 
  722 void FileRenamerDlgImpl::reloadTable()
  723 {
  724     {
  725         bool bVa (false);
  726         bool bErr (false); // it's error if any file lacks ID3V2
  727         const deque<const Mp3Handler*>& vpHndl (m_pHndlrListModel->getHandlerList());
  728         if (vpHndl.empty())
  729         {
  730             accept();
  731             return;
  732         }
  733 
  734         int n (cSize(vpHndl));
  735         CB_ASSERT (n > 0);
  736         string strArtist ("\1");
  737         for (int i = 0; i < n; ++i)
  738         {
  739             const Mp3Handler* pHndl (vpHndl[i]);
  740             const Id3V2StreamBase* pId3V2 (pHndl->getId3V2Stream());
  741             if (0 == pId3V2)
  742             {
  743                 bErr = true;
  744             }
  745             else
  746             {
  747                 if ("\1" == strArtist)
  748                 {
  749                     strArtist = pId3V2->getArtist();
  750                 }
  751                 else
  752                 {
  753                     if (strArtist != pId3V2->getArtist())
  754                     {
  755                         bVa = true;
  756                     }
  757                 }
  758             }
  759         }
  760 
  761         if (bErr)
  762         {
  763             m_eState = "\1" == strArtist ? ERR : bVa ? VARIOUS_ERR : SINGLE_ERR;
  764         }
  765         else
  766         {
  767             m_eState = bVa ? VARIOUS : SINGLE;
  768         }
  769     }
  770 
  771     selectPattern();
  772 
  773     m_pHndlrListModel->emitLayoutChanged();
  774     const Mp3Handler* p (m_pHndlrListModel->getHandlerList().at(0)); // !!! it was supposed to close the window if nothing remained
  775     if (!m_bUseCurrentView) { m_pCrtDirTagEdtE->setText(toNativeSeparators(convStr(p->getDir()))); }
  776     //m_pCrtDirTagEdtE->hide();
  777     //setWindowTitle("MP3 Diags - " + convStr(p->getDir()) + " - File renamer");
  778 
  779     QItemSelectionModel* pSelModel (m_pCurrentAlbumG->selectionModel());
  780     pSelModel->clear();
  781     pSelModel->setCurrentIndex(m_pHndlrListModel->index(0, 0), QItemSelectionModel::SelectCurrent);
  782 
  783     resizeUi();
  784 }
  785 
  786 void FileRenamerDlgImpl::resizeUi()
  787 {
  788     SimpleQTableViewWidthInterface intf (*m_pCurrentAlbumG);
  789     ColumnResizer rsz (intf, 100, ColumnResizer::FILL, ColumnResizer::CONSISTENT_RESULTS);
  790 }
  791 
  792 /*override*/ void FileRenamerDlgImpl::resizeEvent(QResizeEvent* pEvent)
  793 {
  794     resizeUi();
  795     QDialog::resizeEvent(pEvent);
  796 }
  797 
  798 
  799 // selects the appropriate pattern for a new album, based on whether it's VA or SA; sets m_nCrtPattern and checks the right button; assumes m_eState is properly set up;
  800 void FileRenamerDlgImpl::selectPattern()
  801 {
  802     if (!m_vstrPatterns.empty())
  803     {
  804         if (isSingleArtist())
  805         {
  806             m_pButtonGroup->button(m_nSaButton)->setChecked(true);
  807             m_pHndlrListModel->setRenamer(new Renamer(m_vstrPatterns[m_nSaButton], m_pCommonData, m_pMarkUnratedAsDuplicatesCkB->isChecked()));
  808         }
  809         else
  810         {
  811             m_pButtonGroup->button(m_nVaButton /*+ cSize(m_vstrPatterns)*/)->setChecked(true);
  812             m_pHndlrListModel->setRenamer(new Renamer(m_vstrPatterns[m_nVaButton], m_pCommonData, m_pMarkUnratedAsDuplicatesCkB->isChecked()));
  813         }
  814     }
  815     else
  816     {
  817         m_pHndlrListModel->setRenamer(0);
  818     }
  819 
  820     resizeUi();
  821 
  822     m_pAlbumTypeL->setText(isSingleArtist() ? tr("Single artist") : tr("Various artists")); //ttt2 see if "single" is the best word
  823 }
  824 
  825 
  826 
  827 
  828 void FileRenamerDlgImpl::loadPatterns()
  829 { // pattern readers
  830     bool bErr (false);
  831     //vector<string> v (m_pCommonData->m_settings.loadRenamerPatterns(bErr));
  832     vector<string> v (m_pCommonData->m_settings.loadVector("fileRenamer/patterns", bErr));
  833 
  834     m_vstrPatterns.clear();
  835     for (int i = 0, n = cSize(v); i < n; ++i)
  836     {
  837         string strPatt (v[i]);
  838         try
  839         {
  840             Renamer r (strPatt, m_pCommonData, m_pMarkUnratedAsDuplicatesCkB->isChecked());
  841             m_vstrPatterns.push_back(strPatt);
  842         }
  843         catch (const Renamer::InvalidPattern&)
  844         {
  845             bErr = true;
  846         }
  847     }
  848 
  849     /*if (m_vstrPatterns.empty()) // ttt2 because there is no default, the user is forced to add a pattern first; see if a meaningful default can be used
  850     { // use default (only if the user didn't remove all patterns on purpose)
  851         string s ("/tmp/%a/%b[ %y]/%r%n %t"); //ttt2 change from tmp to root/out; //ttt2 perhaps ask the user //ttt2 OS specific
  852         Renamer r (s); // may throw after porting, but that's OK, because it signals a fix is needed
  853         m_vstrPatterns.push_back(s);
  854     }*/
  855 
  856     if (bErr)
  857     {
  858         showWarning(this, tr("Error setting up patterns"), tr("An invalid value was found in the configuration file. You'll have to set up the patterns manually."));
  859     }
  860 }
  861 
  862 
  863 void FileRenamerDlgImpl::savePatterns()
  864 {
  865     //m_pCommonData->m_settings.saveRenamerPatterns(m_vstrPatterns);
  866     m_pCommonData->m_settings.saveVector("fileRenamer/patterns", m_vstrPatterns);
  867 }
  868 
  869 
  870 //ttt2 perhaps have a "reload" button
  871 
  872 void FileRenamerDlgImpl::resizeIcons()
  873 {
  874     vector<QToolButton*> v;
  875     v.push_back(m_pPrevB);
  876     v.push_back(m_pNextB);
  877     v.push_back(m_pEditPatternsB);
  878     v.push_back(m_pRenameB);
  879     v.push_back(m_pCloseB);
  880 
  881     int k (m_pCommonData->m_nMainWndIconSize);
  882     for (int i = 0, n = cSize(v); i < n; ++i)
  883     {
  884         QToolButton* p (v[i]);
  885         p->setMaximumSize(k, k);
  886         p->setMinimumSize(k, k);
  887         p->setIconSize(QSize(k - 4, k - 4));
  888     }
  889 }
  890 
  891 
  892 
  893 void FileRenamerDlgImpl::onHelp()
  894 {
  895     openHelp("240_file_renamer.html");
  896 }
  897 
  898 
  899 //void FileRenamerDlgImpl::on_m_pMarkUnratedAsDuplicatesCkB_stateChanged()
  900 void FileRenamerDlgImpl::on_m_pMarkUnratedAsDuplicatesCkB_clicked()
  901 {
  902     m_pHndlrListModel->setUnratedAsDuplicates(m_pMarkUnratedAsDuplicatesCkB->isChecked());
  903 }
  904 
  905 
  906 void FileRenamerDlgImpl::on_m_pCloseB_clicked()
  907 {
  908     reject();
  909 }
  910 
  911 
  912 
  913 //======================================================================================================================
  914 //======================================================================================================================
  915 //======================================================================================================================
  916 
  917 namespace FileRenamer {
  918 
  919 
  920 class InvalidCharsReplacer
  921 {
  922     string m_strRenamerInvalidChars;
  923     string m_strRenamerReplacementString;
  924 public:
  925     std::string fixName(std::string s) const;
  926     InvalidCharsReplacer(const string& strRenamerInvalidChars, const string& strRenamerReplacementString) : m_strRenamerInvalidChars(strRenamerInvalidChars), m_strRenamerReplacementString(strRenamerReplacementString) {}
  927 };
  928 
  929 
  930 string InvalidCharsReplacer::fixName(string s) const
  931 {
  932     CB_ASSERT (string::npos == m_strRenamerReplacementString.find_first_of(m_strRenamerInvalidChars));
  933     if (m_strRenamerInvalidChars.empty()) { return s; }
  934 
  935     for (;;)
  936     {
  937         string::size_type n (s.find_first_of(m_strRenamerInvalidChars));
  938         if (string::npos == n) { break; }
  939         s.replace(n, 1, m_strRenamerReplacementString);
  940     }
  941     return s;
  942 }
  943 
  944 
  945 
  946 
  947 struct PatternBase
  948 {
  949     const InvalidCharsReplacer* m_pInvalidCharsReplacer;
  950     PatternBase(const InvalidCharsReplacer* pInvalidCharsReplacer) : m_pInvalidCharsReplacer(pInvalidCharsReplacer) {}
  951     virtual ~PatternBase() {}
  952     virtual string getVal(const Mp3Handler*) const = 0;
  953 };
  954 
  955 struct StaticPattern : public PatternBase
  956 {
  957     StaticPattern(string strVal, const InvalidCharsReplacer* pInvalidCharsReplacer) : PatternBase(pInvalidCharsReplacer), m_strVal(strVal) {}
  958     /*override*/ string getVal(const Mp3Handler*) const { return m_strVal; }
  959 private:
  960     string m_strVal;
  961 };
  962 
  963 
  964 struct FieldPattern : public PatternBase
  965 {
  966     FieldPattern(TagReader::Feature eFeature, const InvalidCharsReplacer* pInvalidCharsReplacer) : PatternBase(pInvalidCharsReplacer), m_eFeature(eFeature) {}
  967     /*override*/ string getVal(const Mp3Handler* pHndl) const
  968     {
  969         const Id3V2StreamBase* p (pHndl->getId3V2Stream());
  970         if (0 == p) { return ""; }
  971         return m_pInvalidCharsReplacer->fixName(p->getValue(m_eFeature));
  972     }
  973 private:
  974     TagReader::Feature m_eFeature;
  975 };
  976 
  977 struct YearPattern : public PatternBase
  978 {
  979     YearPattern(const InvalidCharsReplacer* pInvalidCharsReplacer) : PatternBase(pInvalidCharsReplacer) {}
  980     /*override*/ string getVal(const Mp3Handler* pHndl) const
  981     {
  982         const Id3V2StreamBase* p (pHndl->getId3V2Stream());
  983         if (0 == p) { return ""; }
  984         return p->getTime().getYear();
  985     }
  986 };
  987 
  988 struct TrackNoPattern : public PatternBase
  989 {
  990     TrackNoPattern(const InvalidCharsReplacer* pInvalidCharsReplacer) : PatternBase(pInvalidCharsReplacer) {}
  991     /*override*/ string getVal(const Mp3Handler* pHndl) const
  992     {
  993         const Id3V2StreamBase* p (pHndl->getId3V2Stream());
  994         if (0 == p) { return "00"; }
  995         string s (p->getTrackNumber());
  996         int n (atoi(s.c_str()));
  997         if (n <= 0) { return "00"; }
  998         char a [20];
  999         sprintf(a, "%02d", n);
 1000         return a;
 1001     }
 1002 };
 1003 
 1004 
 1005 
 1006 struct RatingPattern : public PatternBase
 1007 {
 1008     RatingPattern(const InvalidCharsReplacer* pInvalidCharsReplacer, const bool& bUnratedAsDuplicate) : PatternBase(pInvalidCharsReplacer), m_bUnratedAsDuplicate(bUnratedAsDuplicate) {}
 1009     const bool& m_bUnratedAsDuplicate;
 1010     /*override*/ string getVal(const Mp3Handler* pHndl) const
 1011     {
 1012         const Id3V2StreamBase* p (pHndl->getId3V2Stream());
 1013         if (0 == p) { return ""; }
 1014         double r (p->getRating());
 1015         if (r < 0) { return m_bUnratedAsDuplicate ? "q" : ""; }
 1016 // from TrackTextReader::TrackTextReader
 1017 //                                   a    b    c    d    e    f    g    h    i    j    k    l    m    n    o    p   q   r    s    t    u    v    w    x    y    z
 1018 //static double s_ratingMap [] = { 5.0, 4.5, 4.0, 3.5, 3.0, 2.5, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 1.5, 1.0, 1.0, 1.0, -1, -1, 1.0, 1.0, 1.0, 1.0, 1.0, 0.5, 0.5, 0.0 };
 1019 // note that if this changes it should be synchronized with TrackTextReader
 1020         if (r >= 4.9) { return "a"; }
 1021         if (r >= 4.4) { return "b"; }
 1022         if (r >= 3.9) { return "c"; }
 1023         if (r >= 3.4) { return "d"; }
 1024         if (r >= 2.9) { return "e"; }
 1025         if (r >= 2.4) { return "f"; }
 1026         if (r >= 1.9) { return "g"; }
 1027         if (r >= 1.4) { return "m"; }
 1028         if (r >= 0.9) { return "n"; }
 1029         if (r >= 0.4) { return "x"; }
 1030         return "z";
 1031     }
 1032 };
 1033 
 1034 struct SequencePattern : public PatternBase
 1035 {
 1036     SequencePattern(const InvalidCharsReplacer* pInvalidCharsReplacer) : PatternBase(pInvalidCharsReplacer) {}
 1037     /*override*/ ~SequencePattern() { clearPtrContainer(m_vpPatterns); }
 1038     /*override*/ string getVal(const Mp3Handler* pHndl) const { return getVal(pHndl, ACCEPT_EMPTY); }
 1039     string getNonNullVal(const Mp3Handler* pHndl) const { return getVal(pHndl, DONT_ACCEPT_EMPTY); } // returns an empty string if any of its components are empty
 1040     void addPattern(const PatternBase* p) { m_vpPatterns.push_back(p); }
 1041 private:
 1042     enum { DONT_ACCEPT_EMPTY, ACCEPT_EMPTY };
 1043     vector<const PatternBase*> m_vpPatterns;
 1044     string getVal(const Mp3Handler* pHndl, bool bAcceptEmpty) const
 1045     {
 1046         string strRes;
 1047         for (int i = 0, n = cSize(m_vpPatterns); i < n; ++i)
 1048         {
 1049             string s (m_vpPatterns[i]->getVal(pHndl));
 1050             if (s.empty() && !bAcceptEmpty) { return s; }
 1051             strRes += s;
 1052         }
 1053         return strRes;
 1054     }
 1055 };
 1056 
 1057 struct OptionalPattern : public PatternBase
 1058 {
 1059     /*override*/ ~OptionalPattern() { delete m_pSequencePattern; }
 1060     OptionalPattern(SequencePattern* pSequencePattern, const InvalidCharsReplacer* pInvalidCharsReplacer) : PatternBase(pInvalidCharsReplacer), m_pSequencePattern(pSequencePattern) {}
 1061     /*override*/ string getVal(const Mp3Handler* pHndl) const { return m_pSequencePattern->getNonNullVal(pHndl); }
 1062 private:
 1063     SequencePattern* m_pSequencePattern;
 1064 };
 1065 
 1066 } // namespace FileRenamer
 1067 
 1068 //using namespace RenamerPatterns;
 1069 
 1070 
 1071 Renamer::Renamer(const std::string& strPattern, const CommonData* pCommonData, bool bUnratedAsDuplicate1) : m_strPattern(strPattern), m_bSameDir(string::npos == strPattern.find(getPathSep())), m_pCommonData(pCommonData), m_bUnratedAsDuplicate(bUnratedAsDuplicate1)
 1072 {
 1073     if (0 != pCommonData)
 1074     {
 1075         m_pInvalidCharsReplacer.reset(new InvalidCharsReplacer(pCommonData->m_strRenamerInvalidChars, pCommonData->m_strRenamerReplacementString));
 1076     }
 1077     else
 1078     {
 1079         m_pInvalidCharsReplacer.reset(new InvalidCharsReplacer("", ""));
 1080     }
 1081 
 1082     m_pRoot = new SequencePattern(m_pInvalidCharsReplacer.get());
 1083 
 1084     auto_ptr<SequencePattern> ap (m_pRoot);
 1085     const char* p (strPattern.c_str());
 1086 
 1087     SequencePattern* pSeq (m_pRoot); // pSeq is either m_pRoot or a new sequence, for optional elements
 1088     if (0 == *p) { CB_THROW2(InvalidPattern, strPattern, tr("A pattern cannot be empty")); } // add pattern str on constr, to always have access to the pattern //ttt1 replace these tests followed by CB_THROW with CB_CHECK
 1089 
 1090     if (!m_bSameDir)
 1091     {
 1092 
 1093 #ifndef WIN32
 1094 
 1095         if (getPathSep() != *p) { CB_THROW2(InvalidPattern, strPattern, tr("A pattern must either begin with '%1' or contain no '%1' at all").arg(getPathSep())); }
 1096 
 1097 #else
 1098 
 1099         if (cSize(strPattern) < 3 || ((p[0] < 'a' || p[0] > 'z') && (p[0] < 'A' || p[0] > 'Z')) || p[1] != ':') // ttt2 allow network drives as well
 1100         {
 1101             CB_THROW2(InvalidPattern, strPattern, tr("A pattern must either begin with \"<drive>:\\\" or contain no '\\' at all"));
 1102         }
 1103         p += 2;
 1104         m_pRoot->addPattern(new StaticPattern(strPattern.substr(0, 2), m_pInvalidCharsReplacer.get()));
 1105         if (getPathSep() != *p) { CB_THROW2(InvalidPattern, strPattern, tr("A pattern must either begin with \"<drive>:\\\" or contain no '\\' at all")); }
 1106 
 1107 #endif
 1108 
 1109     }
 1110 
 1111 
 1112 #ifndef WIN32
 1113 #else
 1114 #endif
 1115 
 1116     auto_ptr<SequencePattern> optAp;
 1117 
 1118     string strStatic;
 1119     bool bTitleFound (false);
 1120     //const char* q (p);
 1121     for (; *p != 0; ++p)
 1122     {
 1123         char c (*p);
 1124         switch (c)
 1125         {
 1126         case '%':
 1127             {
 1128                 ++p;
 1129                 char c1 (*p);
 1130                 switch (c1)
 1131                 {
 1132                 case 'n':
 1133                 case 'a':
 1134                 case 't':
 1135                 case 'b':
 1136                 case 'y':
 1137                 case 'g':
 1138                 case 'c':
 1139                 case 'r':
 1140                     if (!strStatic.empty()) { pSeq->addPattern(new StaticPattern(strStatic, m_pInvalidCharsReplacer.get())); strStatic.clear(); }
 1141                 }
 1142 
 1143                 switch (c1)
 1144                 {
 1145                 case 'n': pSeq->addPattern(new TrackNoPattern(m_pInvalidCharsReplacer.get())); break;
 1146                 case 'a': pSeq->addPattern(new FieldPattern(TagReader::ARTIST, m_pInvalidCharsReplacer.get())); break;
 1147                 case 't': pSeq->addPattern(new FieldPattern(TagReader::TITLE, m_pInvalidCharsReplacer.get())); bTitleFound = true; break;
 1148                 case 'b': pSeq->addPattern(new FieldPattern(TagReader::ALBUM, m_pInvalidCharsReplacer.get())); break;
 1149                 case 'y': pSeq->addPattern(new YearPattern(m_pInvalidCharsReplacer.get())); break;
 1150                 case 'g': pSeq->addPattern(new FieldPattern(TagReader::GENRE, m_pInvalidCharsReplacer.get())); break;
 1151                 case 'c': pSeq->addPattern(new FieldPattern(TagReader::COMPOSER, m_pInvalidCharsReplacer.get())); break;
 1152                 //ttt2 perhaps add something for "various artists"
 1153                 case 'r': pSeq->addPattern(new RatingPattern(m_pInvalidCharsReplacer.get(), m_bUnratedAsDuplicate)); break;
 1154 
 1155                 case '%':
 1156                 case '[':
 1157                 case ']':
 1158                 //case '/': // ttt linux-specific // actually there's no need for '/' to be a special character here
 1159                     strStatic += c; break;
 1160 
 1161                 default:
 1162                     {
 1163                         CB_THROW2(InvalidPattern, strPattern, tr("Error in column %1.").arg(p - strPattern.c_str())); // ttt2 more details, perhaps make tag edt errors more similar to this
 1164                     }
 1165                 }
 1166             }
 1167             break;
 1168 
 1169         case '[':
 1170             {
 1171                 if (!strStatic.empty()) { pSeq->addPattern(new StaticPattern(strStatic, m_pInvalidCharsReplacer.get())); strStatic.clear(); }
 1172                 if (pSeq != m_pRoot) { CB_THROW2(InvalidPattern, strPattern, tr("Nested optional elements are not allowed")); } //ttt2 column
 1173                 pSeq = new SequencePattern(m_pInvalidCharsReplacer.get());
 1174                 optAp.reset(pSeq);
 1175                 break;
 1176             }
 1177 
 1178         case ']':
 1179             {
 1180                 if (!strStatic.empty()) { pSeq->addPattern(new StaticPattern(strStatic, m_pInvalidCharsReplacer.get())); strStatic.clear(); }
 1181                 if (pSeq == m_pRoot) { CB_THROW2(InvalidPattern, strPattern, tr("Trying to close and optional element although none is open")); } //ttt2 column
 1182                 m_pRoot->addPattern(new OptionalPattern(pSeq, m_pInvalidCharsReplacer.get()));
 1183                 pSeq = m_pRoot;
 1184                 optAp.release();
 1185                 break;
 1186             }
 1187 
 1188         default:
 1189             strStatic += c;
 1190         }
 1191     }
 1192 
 1193     if (!strStatic.empty()) { pSeq->addPattern(new StaticPattern(strStatic, m_pInvalidCharsReplacer.get())); strStatic.clear(); }
 1194 
 1195     if (pSeq != m_pRoot) { CB_THROW2(InvalidPattern, strPattern, tr("Optional element must be closed")); } //ttt2 column
 1196 
 1197     if (!bTitleFound) { CB_THROW2(InvalidPattern, strPattern, tr("Title entry (%t) must be present")); }
 1198 
 1199     ap.release();
 1200 }
 1201 
 1202 Renamer::~Renamer()
 1203 {
 1204     delete m_pRoot;
 1205 }
 1206 
 1207 
 1208 string Renamer::getNewName(const Mp3Handler* pHndl) const
 1209 {
 1210     if (0 == pHndl->getId3V2Stream()) { return ""; }
 1211     if (m_mValues.count(pHndl) > 0) { return m_mValues[pHndl]; }
 1212 
 1213     string s (m_pRoot->getVal(pHndl));
 1214     CB_ASSERT (!m_bSameDir ^ (string::npos == s.find(getPathSep())));
 1215     if (m_bSameDir)
 1216     {
 1217         s = getParent(pHndl->getName()) + getPathSepAsStr() + s;
 1218     }
 1219 
 1220     if (string::npos != s.find("//")) { return ""; }
 1221     if (!s.empty()) { s += ".mp3"; }
 1222     return s;
 1223 }
 1224 
 1225 
 1226 //ttt2 should be possible to filter by var/single artists and do the renaming for all; or have a checkbox in the renamer, but that requires the renamer to have the concept of an album
 1227 //ttt2 add "reload()"
 1228 //ttt2 add palette
 1229