"Fossies" - the Fresh Open Source Software Archive

Member "MP3Diags-unstable-1.5.01/src/MainFormDlgImpl.cpp" (3 Apr 2019, 138951 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 "MainFormDlgImpl.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 #ifdef MSVC_QMAKE
   24     #pragma warning (disable : 4100)
   25 #endif
   26 
   27 
   28 #include  <algorithm>
   29 #include  <sstream>
   30 //#include  <iostream>
   31 
   32 #include  <QFileDialog>
   33 #include  <QKeyEvent>
   34 #include  <QStackedLayout>
   35 #include  <QHeaderView>
   36 #include  <QTimer>
   37 #include  <QDesktopWidget>
   38 #include  <QToolTip>
   39 #include  <QSettings>
   40 #include  <QTime>
   41 #include  <QHttp>
   42 #include  <QHttpRequestHeader>
   43 #include  <QHttpResponseHeader>
   44 #include  <QDesktopServices>
   45 #include  <QProcess>
   46 
   47 #ifndef WIN32
   48     //#include <sys/utsname.h>
   49 #else
   50     #include  <windows.h>
   51     #include  <QDateTime>
   52 #endif
   53 
   54 #include  "MainFormDlgImpl.h"
   55 
   56 #include  "DirFilterDlgImpl.h"
   57 #include  "NoteFilterDlgImpl.h"
   58 #include  "Helpers.h"
   59 #include  "OsFile.h"
   60 #include  "FilesModel.h"
   61 #include  "NotesModel.h"
   62 #include  "StreamsModel.h"
   63 #include  "UniqueNotesModel.h"
   64 #include  "TagReadPanel.h"
   65 #include  "ThreadRunnerDlgImpl.h"
   66 #include  "ConfigDlgImpl.h"
   67 #include  "AboutDlgImpl.h"
   68 #include  "Widgets.h"
   69 #include  "DataStream.h"
   70 #include  "ExternalToolDlgImpl.h"
   71 #include  "DebugDlgImpl.h"
   72 #include  "TagEditorDlgImpl.h"
   73 #include  "Mp3TransformThread.h"
   74 #include  "FileRenamerDlgImpl.h"
   75 #include  "ScanDlgImpl.h"
   76 #include  "SessionEditorDlgImpl.h"
   77 #include  "Id3Transf.h"
   78 #include  "ExportDlgImpl.h"
   79 #include  "Version.h"
   80 #include  "Translation.h"
   81 
   82 
   83 using namespace std;
   84 using namespace pearl;
   85 using namespace Version;
   86 
   87 
   88 //#define LOG_ANYWAY
   89 
   90 //ttt2 try to switch from QDialog to QWidget, to see if min/max in gnome show up; or add Qt::Dialog flag (didn't seem to work, though)
   91 
   92 MainFormDlgImpl* getGlobalDlg();  //ttt2 review
   93 
   94 void trace(const string& s)
   95 {
   96     MainFormDlgImpl* p (getGlobalDlg());
   97     //p->m_pContentM->append(convStr(s));
   98     //p->m_pCommonData->m_qstrContent += convStr(s);
   99     //p->m_pCommonData->m_qstrContent += "\n";
  100     if (0 != p && 0 != p->m_pCommonData)
  101     {
  102         p->m_pCommonData->trace(s); //ttt if p->m_pCommonData==0 or p==0 use logToGlobalFile(); 2009.09.08 - better not: this is "trace"; it makes sense to log errors before p->m_pCommonData is set up, but this is not the place to do it
  103     }
  104 }
  105 
  106 
  107 
  108 namespace
  109 {
  110 
  111 
  112 #ifndef WIN32
  113     class NativeFile
  114     {
  115         string m_strFileName;
  116     public:
  117         NativeFile() {}
  118         bool open(const string& strFileName) // doesn't truncate the file
  119         {
  120             m_strFileName = strFileName;
  121             ofstream_utf8 out (m_strFileName.c_str(), ios_base::app);
  122             return (bool)out;
  123         }
  124 
  125         void close() { m_strFileName.clear(); }
  126 
  127         bool write(const string& s)
  128         {
  129             ofstream_utf8 out (m_strFileName.c_str(), ios_base::app);
  130             out << s;
  131             return (bool)out;
  132         }
  133     };
  134 #else // #ifndef WIN32
  135     void CB_LIB_CALL displayOsErrorIfExists(const char* szTitle)
  136     {
  137         unsigned int nErr (GetLastError());
  138         if (0 == nErr) return;
  139 
  140         LPVOID lpMsgBuf;
  141 
  142         FormatMessageA(
  143             FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
  144             NULL,
  145             nErr,
  146             MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
  147             (LPSTR) &lpMsgBuf,
  148             0,
  149             NULL
  150         );
  151 
  152         string strRes ((LPSTR)lpMsgBuf);
  153 
  154         // Free the buffer.
  155         LocalFree(lpMsgBuf);
  156         unsigned int nSize ((unsigned int)strRes.size());
  157         if (nSize - 2 == strRes.rfind("\r\n"))
  158         {
  159             strRes.resize(nSize - 2);
  160         }
  161 
  162         showCritical(0, szTitle, convStr(strRes));
  163     }
  164 
  165 
  166     class NativeFile
  167     {
  168         HANDLE m_hStepFile;
  169     public:
  170         NativeFile() : m_hStepFile (INVALID_HANDLE_VALUE) {}
  171 
  172         bool open(const string& strFileName)
  173         {
  174             m_hStepFile = CreateFileA(strFileName.c_str(),
  175                          GENERIC_WRITE,
  176                          FILE_SHARE_WRITE | FILE_SHARE_READ,
  177                          0,
  178                          OPEN_ALWAYS,
  179                          //CREATE_ALWAYS,
  180                          FILE_ATTRIBUTE_TEMPORARY,
  181                          //FILE_ATTRIBUTE_NORMAL,
  182                          0);
  183             if (INVALID_HANDLE_VALUE == m_hStepFile)
  184             {
  185                 displayOsErrorIfExists("Error");
  186                 return false;
  187             }
  188 
  189             SetFilePointer(m_hStepFile, 0, 0, FILE_END);
  190             return true;
  191         }
  192 
  193         void close()
  194         {
  195             CloseHandle(m_hStepFile);
  196             m_hStepFile = INVALID_HANDLE_VALUE;
  197         }
  198 
  199         bool write(const string& s)
  200         {
  201             DWORD dwWrt;
  202             //WriteFile(s_hStepFile, a, strlen(a), &dwWrt, 0);
  203             WriteFile(m_hStepFile, s.c_str(), s.size(), &dwWrt, 0);
  204             //WriteFile(s_hStepFile, "\r\n", 2, &dwWrt, 0);
  205             return (dwWrt == s.size());
  206         }
  207     };
  208 #endif // #ifndef WIN32
  209 
  210 
  211 
  212     class FileTracer
  213     {
  214         string m_strTraceFile;
  215         vector<string> m_vstrStepFile;
  216 
  217         bool m_bEnabled1, m_bEnabled2; // both have to be true for the trace to actually be enabled; they are not symmetrical: changing m_bEnabled2 while m_bEnabled1 is true also removes the files, but doesn't happen the other way; the reason for doing this is that we want trace files from previous run deleted (regardless of how it finished) but we don't want them deleted too early, so the user has a chance to mail them; to complicate the things, there is loading the settings, which may call enable2(true);
  218 
  219         int m_nStepFile;
  220         int m_nCrtStepSize;
  221         int m_nPage;
  222         int m_nStepLevel;
  223         int m_nTraceLevel;
  224 
  225         NativeFile m_stepFile;
  226         void setupFiles();
  227         void removeFiles();
  228 
  229         NativeFile m_traceFile;
  230 
  231     public:
  232         FileTracer() : m_bEnabled1(false), m_bEnabled2(false), m_nStepFile(-1), m_nCrtStepSize(-1), m_nPage(-1), m_nStepLevel(-1), m_nTraceLevel(-1)
  233         {
  234         }
  235 
  236         //void setupTraceToFile(bool bEnable);
  237         void setName(const string& strNameRoot); // also disables
  238         void enable1(bool); // used as a master control, by MainFormDlgImpl and assert; if m_bEnabled1 is true, changing m_bEnabled2 triggers the removal of files
  239         void enable2(bool); // set based on config
  240         //void removeFilesIfDisabled();
  241 
  242         void traceToFile(const string& s, int nLevelChange);
  243         void traceLastStep(const string& s, int nLevelChange);
  244 
  245         const string& getTraceFile() const { return m_strTraceFile; }
  246         const vector<string>& getStepFiles() const { return m_vstrStepFile; }
  247     };
  248 
  249 
  250     void FileTracer::traceToFile(const string& s, int nLevelChange)
  251     {
  252         if (!m_bEnabled1 || !m_bEnabled2 || m_strTraceFile.empty()) { return; }
  253 
  254         static QMutex mutex;
  255         QMutexLocker lck (&mutex);
  256 
  257         m_nTraceLevel += nLevelChange;
  258         if (m_nTraceLevel < 0) { m_nTraceLevel = 20; }
  259         string s1 (m_nTraceLevel, ' '); // !!! may happen in this case: trace gets enabled by assert, then the Tracer destructor runs, trying to decrease what was never increased
  260         s1 += s;
  261 
  262         QTime t (QTime::currentTime());
  263         char a [15];
  264         sprintf(a, "%02d:%02d:%02d.%03d", t.hour(), t.minute(), t.second(), t.msec());
  265 
  266         //ofstream_utf8 out (m_strTraceFile.c_str(), ios_base::app);
  267         //out << a << s1 << endl;
  268 #ifndef WIN32
  269         m_traceFile.write(a + s1 + "\n");
  270 #else
  271         m_traceFile.write(a + s1 + "\r\n");
  272 #endif
  273     }
  274 
  275     void FileTracer::traceLastStep(const string& s, int nLevelChange)
  276     {
  277         if (!m_bEnabled1 || !m_bEnabled2 || m_strTraceFile.empty()) { return; }
  278 
  279         static QMutex mutex;
  280         QMutexLocker lck (&mutex);
  281 
  282         m_nStepLevel += nLevelChange;
  283         if (m_nStepLevel < 0) { m_nStepLevel = 20; } // !!! may happen in this case: trace gets enabled by assert, then the LastStepTracer destructor runs, trying to decrease what was never increased
  284         string s1 (m_nStepLevel, ' ');
  285         //string s1; char b [20]; sprintf(b, "%d", s_nLevel); s1 = b;
  286         s1 += s;
  287 
  288         char a [15];
  289         a[0] = 0;
  290         //QTime t (QTime::currentTime()); sprintf(a, "%02d:%02d:%02d.%03d ", t.hour(), t.minute(), t.second(), t.msec());
  291 
  292 #ifndef WIN32
  293         m_stepFile.write(a + s1 + "\n");
  294         m_nCrtStepSize += s1.size() + 1;
  295 #else
  296         m_stepFile.write(a + s1 + "\r\n");
  297         m_nCrtStepSize += s1.size() + 2;
  298 #endif
  299 
  300 
  301         if (m_nCrtStepSize > 50000)
  302         {
  303             m_stepFile.close();
  304 
  305             m_nCrtStepSize = 0;
  306             m_nStepFile = 1 - m_nStepFile;
  307             try
  308             {
  309                 deleteFile(m_vstrStepFile[m_nStepFile]);
  310             }
  311             catch (const exception&)
  312             { //ttt2
  313             }
  314             catch (...)
  315             { //ttt2
  316             }
  317 
  318             {
  319                 ofstream_utf8 out (m_vstrStepFile[m_nStepFile].c_str(), ios_base::app);
  320                 out << "page " << ++m_nPage << endl;
  321             }
  322 
  323             m_stepFile.open(m_vstrStepFile[m_nStepFile]);
  324         }
  325     }
  326 
  327 
  328 
  329     void FileTracer::setName(const string& strNameRoot)
  330     {
  331         m_stepFile.close();
  332         m_traceFile.close();
  333         m_bEnabled1 = m_bEnabled2 = false;
  334 
  335         CB_ASSERT (!strNameRoot.empty());
  336 
  337         m_strTraceFile = strNameRoot + "_trace.txt";
  338 
  339         m_vstrStepFile.clear();
  340         m_vstrStepFile.push_back(strNameRoot + "_step1.txt");
  341         m_vstrStepFile.push_back(strNameRoot + "_step2.txt");
  342     }
  343 
  344     /*void FileTracer::removeFilesIfDisabled()
  345     {
  346         if (!m_bEnabled1 || !m_bEnabled2)
  347         {
  348             removeFiles();
  349         }
  350     }*/
  351 
  352     void FileTracer::removeFiles()
  353     {
  354         m_nStepFile = 0;
  355         m_nCrtStepSize = 0;
  356         m_nPage = 0;
  357         m_nStepLevel = 0;
  358         m_nTraceLevel = 0;
  359 
  360         try
  361         {
  362             deleteFile(m_strTraceFile);
  363             deleteFile(m_vstrStepFile[0]);
  364             deleteFile(m_vstrStepFile[1]);
  365         }
  366         catch (const exception&)
  367         {
  368         }
  369         catch (...)
  370         { //ttt2
  371         }
  372     }
  373 
  374     void FileTracer::setupFiles()
  375     {
  376         if (!m_bEnabled1 || !m_bEnabled2)
  377         {
  378             if (m_bEnabled1)
  379             {
  380                 removeFiles();
  381             }
  382             return;
  383         }
  384 
  385         removeFiles();
  386 
  387         m_stepFile.open(m_vstrStepFile[0]);
  388         m_traceFile.open(m_strTraceFile);
  389 
  390         traceToFile(convStr(getSystemInfo()), 0);
  391         traceLastStep(convStr(getSystemInfo()), 0);
  392     }
  393 
  394     void FileTracer::enable1(bool b)
  395     {
  396         if (b == m_bEnabled1 || m_vstrStepFile.empty()) { return; }
  397         m_bEnabled1 = b;
  398         setupFiles();
  399     }
  400 
  401     void FileTracer::enable2(bool b)
  402     {
  403         if (b == m_bEnabled2 || m_vstrStepFile.empty()) { return; }
  404         m_bEnabled2 = b;
  405         setupFiles();
  406     }
  407 
  408     FileTracer s_fileTracer;
  409 }
  410 
  411 
  412 
  413 void traceToFile(const string& s, int nLevelChange)
  414 {
  415     s_fileTracer.traceToFile(s, nLevelChange);
  416 }
  417 
  418 void setupTraceToFile(bool b)
  419 {
  420     s_fileTracer.enable2(b);
  421 }
  422 
  423 
  424 
  425 void traceLastStep(const string& s, int nLevelChange)
  426 {
  427     s_fileTracer.traceLastStep(s, nLevelChange);
  428 }
  429 
  430 
  431 
  432 //ttt2 add checkbox to uninst on wnd to remove data and settings; //ttt2 uninst should remove log file as well, including the file created when "debug/log transf" is turned on
  433 //static QString s_strAssertTitle ("Assertion failure");
  434 //static QString s_strCrashWarnTitle ("Crash detected");
  435 static QString s_qstrErrorMsg;
  436 static bool s_bMainAssertOut;
  437 
  438 static QString g_qstrCrashMail ("<a href=\"mailto:mp3diags@gmail.com?subject=000 MP3 Diags crash/\">mp3diags@gmail.com</a>");
  439 static QString g_qstrSupportMail ("<a href=\"mailto:mp3diags@gmail.com?subject=000 MP3 Diags support note/\">mp3diags@gmail.com</a>");
  440 static QString g_qstrBugReport ("<a href=\"http://sourceforge.net/apps/mantisbt/mp3diags/\">http://sourceforge.net/apps/mantisbt/mp3diags/</a>");//ttt0 replace mantis references
  441 
  442 
  443 static void showAssertMsg(QWidget* pParent)
  444 {
  445     HtmlMsg::msg(pParent, 0, 0, 0, HtmlMsg::SHOW_SYS_INFO, MainFormDlgImpl::tr("Assertion failure"),
  446         s_qstrErrorMsg.toHtmlEscaped() +
  447             "<p style=\"margin-bottom:1px; margin-top:12px; \">" +
  448             (
  449                 s_fileTracer.getStepFiles().empty() ?
  450                     MainFormDlgImpl::tr("Plese report this problem to the project's Issue Tracker at %1").arg(g_qstrBugReport) :
  451                     MainFormDlgImpl::tr("Please restart the application for instructions about how to report this issue")
  452             ) +
  453             "</p>",
  454         750, 300, MainFormDlgImpl::tr("Exit"));
  455 }
  456 
  457 
  458 
  459 void MainFormDlgImpl::showRestartAfterCrashMsg(const QString& qstrText, const QString& qstrCloseBtn)
  460 {
  461     HtmlMsg::msg(this, 0, 0, 0, HtmlMsg::SHOW_SYS_INFO, tr("Restarting after crash"), qstrText, 750, 450, qstrCloseBtn);
  462 }
  463 
  464 
  465 void logAssert(const char* szFile, int nLine, const char* szCond)
  466 {
  467     //showCritical(0, "Assertion failure", QString("Assertion failure in file %1, line %2: %3").arg(szFile).arg(nLine).arg(szCond), QMessageBox::Close);
  468     /*QMessageBox dlg (QMessageBox::Critical, "Assertion failure", QString("Assertion failure in file %1, line %2: %3").arg(szFile).arg(nLine).arg(szCond), QMessageBox::Close, getThreadLocalDlgList().getDlg(), Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint | Qt::WindowStaysOnTopHint);*/
  469 
  470     s_qstrErrorMsg = QString("Assertion failure in file %1, line %2: %3. The program will exit.").arg(szFile).arg(nLine).arg(szCond);
  471     s_fileTracer.enable1(true);
  472     s_fileTracer.enable2(true);
  473     traceToFile(convStr(s_qstrErrorMsg), 0);
  474     qDebug("Assertion failure in file %s, line %d: %s", szFile, nLine, szCond);
  475     s_fileTracer.enable1(false); // !!! to avoid logging irrelevant info about cells drawn after the message was shown (there is some risk of not detecting that messages aren't shown although they should be, but this has never been reported, while trace files full of FilesModel::headerData and the like are common)
  476 
  477     MainFormDlgImpl* p (getGlobalDlg());
  478 
  479     if (0 != p)
  480     {
  481         s_bMainAssertOut = false;
  482         //QTimer::singleShot(1, p, SLOT(onShowAssert())); //ttt2 see why this doesn't work
  483         AssertSender s (p);
  484 
  485         for (;;)
  486         {
  487             if (s_bMainAssertOut) { break; }
  488             //sleep(1);
  489 #ifndef WIN32
  490             timespec ts;
  491             ts.tv_sec = 0;
  492             ts.tv_nsec = 100000000; // 0.1s
  493             nanosleep(&ts, 0);
  494 #else
  495             Sleep(100);
  496 #endif
  497         }
  498     }
  499     else
  500     {
  501         /*QMessageBox dlg (QMessageBox::Critical, s_strAssertTitle, s_strErrorMsg, QMessageBox::Close, 0, Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint | Qt::WindowStaysOnTopHint); // ttt2 this might fail / crash, as it may be called from a secondary thread
  502 
  503         dlg.exec();*/
  504         showAssertMsg(0);
  505     }
  506 }
  507 
  508 void logAssert(const char* szFile, int nLine, const char* szCond, const std::string& strAddtlInfo)
  509 {
  510     string s (string(szCond) + "; additional info: " + strAddtlInfo);
  511     logAssert(szFile, nLine, s.c_str());
  512 }
  513 
  514 
  515 void MainFormDlgImpl::onShowAssert()
  516 {
  517     /*QMessageBox dlg (QMessageBox::Critical, s_strAssertTitle, s_strErrorMsg, QMessageBox::Close, this, Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint | Qt::WindowStaysOnTopHint);
  518 
  519     dlg.exec();*/
  520     showAssertMsg(this);
  521 
  522     s_bMainAssertOut = true;
  523 }
  524 
  525 
  526 
  527 // resizes a dialog with inexisting/invalid size settings, so it covers an area slightly smaller than MainWnd; however, if the dialog is alrady bigger than that, it doesn't get shrinked
  528 void defaultResize(QDialog& dlg)
  529 {
  530     QSize s (dlg.size());
  531     QDialog& mainDlg (*getGlobalDlg());
  532     s.rwidth() = max(s.rwidth(), mainDlg.width() - 100); //ttt2  doesn't do what it should for the case when working with small fonts and small resolutions
  533     s.rheight() = max(s.rheight(), mainDlg.height() - 100);
  534     dlg.resize(s.width(), s.height());
  535 }
  536 
  537 
  538 
  539 //=====================================================================================================================
  540 //=====================================================================================================================
  541 //=====================================================================================================================
  542 
  543 
  544 
  545 
  546 
  547 void MainFormDlgImpl::showBackupWarn()
  548 {
  549     if (m_pCommonData->m_bWarnedAboutBackup) { return; }
  550 
  551     HtmlMsg::msg(this, 0, 0, &m_pCommonData->m_bWarnedAboutBackup, HtmlMsg::CRITICAL, tr("Warning"), "<p>" + tr("Because MP3 Diags changes the content of your MP3 files if asked to, it has a significant destructive potential, especially in cases where the user doesn't read the documentation and simply expects the program to do other things than what it was designed to do.") + "</p><p>" + tr("Therefore, it is highly advisable to back up your files first.") + "</p><p>" + tr("Also, although MP3 Diags is very stable on the developer's computer, who hasn't experienced a crash in a long time and never needed to restore MP3 files from a backup, there are several crash reports that haven't been addressed, as the developer couldn't reproduce the crashes and those who reported the crashes didn't answer the developer's questions that might have helped isolate the problem.") + "</p>", 520, 300, tr("O&K"));
  552 
  553     if (!m_pCommonData->m_bWarnedAboutBackup) { return; }
  554 
  555     m_settings.saveMiscConfigSettings(m_pCommonData);
  556 }
  557 
  558 
  559 
  560 void MainFormDlgImpl::showSelWarn()
  561 {
  562     if (m_pCommonData->m_bWarnedAboutSel) { return; }
  563 
  564     HtmlMsg::msg(this, 0, 0, &m_pCommonData->m_bWarnedAboutSel, HtmlMsg::DEFAULT, tr("Note"), tr("If you simply left-click, all the visible files get processed. However, it is possible to process only the selected files. To do that, either keep SHIFT pressed down while clicking or use the right button, as described at %1").arg("<a href=\"http://mp3diags.sourceforge.net" + QString(getWebBranch()) + "/140_main_window_tools.html\">http://mp3diags.sourceforge.net" + QString(getWebBranch()) + "/140_main_window_tools.html</a>"), 520, 300, tr("O&K"));
  565 
  566     if (!m_pCommonData->m_bWarnedAboutSel) { return; }
  567 
  568     m_settings.saveMiscConfigSettings(m_pCommonData);
  569 }
  570 
  571 
  572 
  573 
  574 //=====================================================================================================================
  575 //=====================================================================================================================
  576 //=====================================================================================================================
  577 
  578 void MainFormDlgImpl::saveIgnored()
  579 {
  580     const vector<int>& v (m_pCommonData->getIgnoredNotes());
  581 
  582     vector<string> u;
  583     for (int i = 0, n = cSize(v); i < n; ++i)
  584     {
  585         u.push_back(Notes::getNote(v[i])->getDescription());
  586     }
  587 
  588     m_settings.saveVector("ignored/list", u);
  589 }
  590 
  591 
  592 void MainFormDlgImpl::loadIgnored()
  593 {
  594     bool bRes (true); //ttt2 ? use
  595     vector<string> v (m_settings.loadVector("ignored/list", bRes));
  596 
  597     vector<int> vnIgnored;
  598     vector<string> vstrNotFoundNotes;
  599 
  600     int n (cSize(v));
  601     if (0 == n)
  602     { // use default
  603         vnIgnored = Notes::getDefaultIgnoredNoteIds();
  604     }
  605     else
  606     {
  607         for (int i = 0; i < n; ++i)
  608         {
  609             const string& strDescr (v[i]);
  610             //qDebug("%s", strDescr.c_str());
  611             const Note* pNote (Notes::getNote(strDescr));
  612             if (0 == pNote)
  613             {
  614                 vstrNotFoundNotes.push_back(strDescr);
  615             }
  616             else
  617             {
  618                 vnIgnored.push_back(pNote->getNoteId());
  619             }
  620         }
  621     }
  622 
  623     {
  624         m_pCommonData->setIgnoredNotes(vnIgnored);
  625         int n (cSize(vstrNotFoundNotes));
  626         if (n > 0)
  627         {
  628             QString s;
  629             if (1 == n)
  630             {
  631                 s = tr("An unknown note was found in the configuration. This note is unknown:\n\n%1").arg(convStr(vstrNotFoundNotes[0]));
  632             }
  633             else
  634             {
  635                 QString s1;
  636                 for (int i = 0; i < n; ++i)
  637                 {
  638                     s1 += convStr(vstrNotFoundNotes[i]);
  639                     if (i < n - 1)
  640                     {
  641                         s1 += "\n";
  642                     }
  643                 }
  644                 s = tr("Unknown notes were found in the configuration. These notes are unknown:\n\n%1").arg(s1);
  645             }
  646 
  647             showWarning(this, tr("Error setting up the \"ignored notes\" list"), s + tr("\n\nYou may want to check again the list and add any notes that you want to ignore.\n\n(If you didn't change the settings file manually, this is probably due to a code enhanement that makes some notes no longer needed, and you can safely ignore this message.)")); //ttt2 use MP3 Diags icon
  648 
  649             saveIgnored();
  650         }
  651     }
  652 }
  653 
  654 
  655 static bool s_bToldAboutSupportInCrtRun (false); // to limit to 1 per run the number of times the user is told about support
  656 static bool s_bToldAboutXingRebuildInCrtRun (false);
  657 static bool s_bToldAboutXingRemoveInCrtRun (false);
  658 
  659 //=====================================================================================================================
  660 //=====================================================================================================================
  661 //=====================================================================================================================
  662 
  663 
  664 /*
  665 
  666 Signals:
  667 
  668 currentFileChanged() - sent by m_pCommonData->m_pFilesModel and received by both MainFormDlgImpl (to create a new m_pTagDetailsW) and CommonData (to update current notes and streams, which calls m_pStreamsModel->emitLayoutChanged() and m_pNotesModel->emitLayoutChanged())
  669 
  670 filterChanged() - sent by m_pCommonData->m_filter and received by m_pCommonData; it updates m_pCommonData->m_vpFltHandlers and calls m_pCommonData->updateWidgets(); updateWidgets() calls m_pFilesModel->selectRow(), which triggers currentFileChanged(), which causes the note and stream grids to be updated
  671 
  672 
  673 // ttt2 finish documenting the signal flow (mainly changing of "current" for both main window and tag editor); then check that it is properly implemented; pay attention to not calling signal handlers directly unless there's a very good reason to do so
  674 
  675 
  676 */
  677 
  678 
  679 static MainFormDlgImpl* s_pGlobalDlg (0); //ttt2 review this
  680 
  681 MainFormDlgImpl* getGlobalDlg()
  682 {
  683     return s_pGlobalDlg;
  684 }
  685 
  686 QWidget* getMainForm()
  687 {
  688     return getGlobalDlg();
  689 }
  690 
  691 static PausableThread* s_pSerThread;
  692 PausableThread* getSerThread() //ttt2 global function
  693 {
  694     return s_pSerThread;
  695 }
  696 
  697 
  698 namespace {
  699 
  700 struct SerLoadThread : public PausableThread
  701 {
  702     CommonData* m_pCommonData;
  703     const string& m_strSession;
  704     string& m_strErr;
  705     SerLoadThread(CommonData* pCommonData, const string& strSession, string& strErr) : m_pCommonData(pCommonData), m_strSession(strSession), m_strErr(strErr) {}
  706 
  707     /*override*/ void run()
  708     {
  709         try
  710         {
  711             CompleteNotif notif(this);
  712 
  713             bool bAborted (!load());
  714 
  715             notif.setSuccess(!bAborted);
  716         }
  717         catch (const exception& ex)
  718         {
  719             TRACER1("SerLoadThread::run()", 1);
  720             TRACER1(ex.what(), 2);
  721             CB_ASSERT1 (false, ex.what());
  722         }
  723         catch (...)
  724         {
  725             TRACER("SerLoadThread::run() - unknown exception");
  726             CB_ASSERT (false);
  727         }
  728     }
  729 
  730     bool load()
  731     {
  732         m_strErr = m_pCommonData->load(SessionEditorDlgImpl::getDataFileName(m_strSession));
  733         //m_pCommonData->m_strTransfLog = SessionEditorDlgImpl::getLogFileName(m_strSession);
  734         //{ TRACER("001 m_strTransfLog=" + m_pCommonData->m_strTransfLog);  }
  735         return true;
  736     }
  737 };
  738 
  739 
  740 struct SerSaveThread : public PausableThread
  741 {
  742     CommonData* m_pCommonData;
  743     const string& m_strSession;
  744     string& m_strErr;
  745     SerSaveThread(CommonData* pCommonData, const string& strSession, string& strErr) : m_pCommonData(pCommonData), m_strSession(strSession), m_strErr(strErr) {}
  746 
  747     /*override*/ void run()
  748     {
  749         try
  750         {
  751             CompleteNotif notif(this);
  752 
  753             bool bAborted (!save());
  754 
  755             notif.setSuccess(!bAborted);
  756         }
  757         catch (const exception& ex)
  758         {
  759             TRACER1("SerSaveThread::run()", 1);
  760             TRACER1(ex.what(), 2);
  761             CB_ASSERT1 (false, ex.what());
  762         }
  763         catch (...)
  764         {
  765             TRACER("SerSaveThread::run() - unknown exception");
  766             CB_ASSERT (false);
  767         }
  768     }
  769 
  770     bool save()
  771     {
  772         m_strErr = m_pCommonData->save(SessionEditorDlgImpl::getDataFileName(m_strSession));
  773         return true;
  774     }
  775 };
  776 
  777 //ttt1 "unstable" in html for analytics
  778 
  779 void listKnownFormats()
  780 {
  781     for (unsigned i = 0x240; i < 1024; ++i) // everything below 0x240 is invalid
  782     {
  783         unsigned x (0xffe00000);
  784         x += (i & 0x300) << (19 - 8);
  785         x += (i & 0x0c0) << (17 - 6);
  786 
  787         x += (i & 0x03c) << (12 - 2);
  788         x += (i & 0x003) << (6 - 0);
  789         qDebug("%x: %s", x, decodeMpegFrame(x, ", ").c_str());
  790     }
  791 }
  792 
  793 
  794 } // namespace
  795 
  796 
  797 
  798 MainFormDlgImpl::MainFormDlgImpl(const string& strSession, bool bDefaultForVisibleSessBtn) : QDialog(0, getMainWndFlags()), m_settings(strSession), m_nLastKey(0)/*, m_settings("Ciobi", "Mp3Diags_v01")*/ /*, m_nPrevTabIndex(-1), m_bTagEdtWasEntered(false)*/, m_pCommonData(0), m_strSession(strSession), m_bShowMaximized(false), m_nScanWidth(0), m_pQHttp(0), m_nGlobalX(0), m_nGlobalY(0)
  799 {
  800 //int x (2); CB_ASSERT(x > 4);
  801 //CB_ASSERT("345" == "ab");
  802 //CB_ASSERT(false);
  803     s_fileTracer.setName(SessionEditorDlgImpl::getBaseName(strSession)); // also disables both flags
  804 
  805     s_pGlobalDlg = 0;
  806     setupUi(this);
  807 
  808     //listKnownFormats(); // ttt2 sizes for many formats seem way too low (e.g. "MPEG-1 Layer I, 44100Hz 32000bps" or "MPEG-2 Layer III, 22050Hz 8000bps")
  809 
  810     {
  811         /*KbdNotifTableView* pStreamsG (new KbdNotifTableView(m_pStreamsG));
  812         connect(pStreamsG, SIGNAL(keyPressed(int)), this, SLOT(onStreamsGKeyPressed(int)));
  813 
  814         m_pStreamsG = pStreamsG;*/
  815         m_pStreamsG->installEventFilter(this);
  816         m_pFilesG->installEventFilter(this);
  817     }
  818 
  819     m_pCommonData = new CommonData(m_settings, m_pFilesG, m_pNotesG, m_pStreamsG, m_pUniqueNotesG, /*m_pCurrentFileG, m_pCurrentAlbumG,*/ /*m_pLogG,*/ /*m_pAssignedB,*/ m_pNoteFilterB, m_pDirFilterB, m_pModeAllB, m_pModeAlbumB, m_pModeSongB, bDefaultForVisibleSessBtn);
  820 
  821     m_settings.loadMiscConfigSettings(m_pCommonData, SessionSettings::INIT_GUI);
  822 
  823     m_pCommonData->m_bScanAtStartup = m_settings.loadScanAtStartup();
  824 
  825     string strVersion;
  826     m_settings.loadVersion(strVersion);
  827     if (strVersion == getAppVer())
  828     {
  829         bool bDirty;
  830         m_settings.loadDbDirty(bDirty);
  831         bool bCrashedAtStartup;
  832         m_settings.loadCrashedAtStartup(bCrashedAtStartup);
  833 
  834         if (bDirty || bCrashedAtStartup)
  835         {
  836             if (m_pCommonData->isTraceToFileEnabled() || fileExists(s_fileTracer.getTraceFile())) // !!! fileExists(s_strTraceFile) allows new asserts to be reported (when m_pCommonData->isTraceToFileEnabled() would still return false)
  837             {
  838                 if (fileExists(s_fileTracer.getTraceFile()))
  839                 {
  840                     vector<string> v;
  841                     if (fileExists(s_fileTracer.getTraceFile())) { v.push_back(s_fileTracer.getTraceFile()); }
  842                     if (fileExists(s_fileTracer.getStepFiles()[0])) { v.push_back(s_fileTracer.getStepFiles()[0]); }
  843                     if (fileExists(s_fileTracer.getStepFiles()[1])) { v.push_back(s_fileTracer.getStepFiles()[1]); }
  844                     CB_ASSERT (!v.empty()); // really v should have at least 2 elements
  845 
  846                     QString qstrFiles ("<p style=\"margin-bottom:8px; margin-top:1px; \">" + tr("MP3 Diags is restarting after a crash."));
  847                     switch (v.size())
  848                     {
  849                     case 1:
  850                             qstrFiles += tr("Information in the file %1%5%2 may help identify the cause of the crash so please make it available to the developer by mailing it to %3, by reporting an issue to the project's Issue Tracker at %4 and attaching the files to the report, or by some other means (like putting it on a file sharing site.)", "%1 and %2 are HTML elements")
  851                                 .arg("<b>")
  852                                 .arg("</b>")
  853                                 .arg(g_qstrCrashMail)
  854                                 .arg(g_qstrBugReport)
  855                                 .arg(toNativeSeparators(convStr(v[0])).toHtmlEscaped());
  856                         break;
  857                     case 2:
  858                         qstrFiles += tr("Information in the files %1%5%2 and %1%6%2 may help identify the cause of the crash so please make them available to the developer by mailing them to %3, by reporting an issue to the project's Issue Tracker at %4 and attaching the files to the report, or by some other means (like putting them on a file sharing site.)", "%1 and %2 are HTML elements")
  859                                 .arg("<b>")
  860                                 .arg("</b>")
  861                                 .arg(g_qstrCrashMail)
  862                                 .arg(g_qstrBugReport)
  863                                 .arg(toNativeSeparators(convStr(v[0])).toHtmlEscaped())
  864                                 .arg(toNativeSeparators(convStr(v[1])).toHtmlEscaped());
  865                         break;
  866                     case 3:
  867                         qstrFiles += tr("Information in the files %1%5%2, %1%6%2, and %1%7%2 may help identify the cause of the crash so please make them available to the developer by mailing them to %3, by reporting an issue to the project's Issue Tracker at %4 and attaching the files to the report, or by some other means (like putting them on a file sharing site.)", "%1 and %2 are HTML elements")
  868                                 .arg("<b>")
  869                                 .arg("</b>")
  870                                 .arg(g_qstrCrashMail)
  871                                 .arg(g_qstrBugReport)
  872                                 .arg(toNativeSeparators(convStr(v[0])).toHtmlEscaped())
  873                                 .arg(toNativeSeparators(convStr(v[1])).toHtmlEscaped())
  874                                 .arg(toNativeSeparators(convStr(v[2])).toHtmlEscaped());
  875                         break;
  876                     }
  877                     qstrFiles += " </p>";
  878 
  879                     showRestartAfterCrashMsg(qstrFiles +
  880 
  881                                              "<p style=\"margin-bottom:8px; margin-top:1px; \">" + tr("These are plain text files, which you can review before sending, if you have privacy concerns.") + "</p>"
  882                                              "<p style=\"margin-bottom:8px; margin-top:1px; \">" + tr("After getting the files, the developer will probably want to contact you for more details, so please check back on the status of your report.") + "</p>"
  883                                              "<p style=\"margin-bottom:8px; margin-top:1px; \">" + tr("Note that these files <b>will be removed</b> when you close this window.") + "</p>" +
  884 
  885                                              (m_pCommonData->isTraceToFileEnabled() ?
  886                                                   "<p style=\"margin-bottom:8px; margin-top:1px; \">" + tr("If there is a name of an MP3 file at the end of <b>%1</b>, that might be a file that consistently causes a crash. Please check if it is so. Then, if confirmed, please make that file available by mailing it to %2 or by putting it on a file sharing site.").arg(toNativeSeparators(convStr(v[0]))).toHtmlEscaped().arg(g_qstrCrashMail) + "</p>" :
  887                                                   "<p style=\"margin-bottom:8px; margin-top:1px; \">" + tr("Please also try to <b>repeat the steps that led to the crash</b> before reporting the crash, which will probably result in a new set of files being generated; these files are more likely to contain relevant information than the current set of files, because they will also have information on what happened before the crash, while the current files only tell where the crash occured.") + "</p>"
  888                                              )
  889 
  890                                              + "<p style=\"margin-bottom:8px; margin-top:1px; \">" + tr("You should include in your report any other details that seem relevant (what might have caused the failure, steps to reproduce it, ...)") + "</p>", tr("Remove these files and continue"));
  891                 }
  892                 else
  893                 {
  894                     showRestartAfterCrashMsg("<p style=\"margin-bottom:8px; margin-top:1px; \">" + tr("MP3 Diags is restarting after a crash. There was supposed to be some information about what led to the crash in the file <b>%1</b>, but that file cannot be found. Please report this issue to the project's Issue Tracker at %2.").arg(toNativeSeparators(convStr(s_fileTracer.getTraceFile())).toHtmlEscaped()).arg(g_qstrBugReport) + "</p>"
  895                                              + "<p style=\"margin-bottom:8px; margin-top:1px; \">" + tr("The developer will probably want to contact you for more details, so please check back on the status of your report.</p><p style=\"margin-bottom:8px; margin-top:1px; \">Make sure to include the data below, as well as any other detail that seems relevant (what might have caused the failure, steps to reproduce it, ...)") + "</p>", "OK");
  896                 }
  897             }
  898 
  899 
  900             if (!m_pCommonData->isTraceToFileEnabled())
  901             {
  902                 showRestartAfterCrashMsg("<p style=\"margin-bottom:8px; margin-top:1px; \">" + tr("MP3 Diags is restarting after a crash. To help determine the reason for the crash, the <i>Log program state to _trace and _step files</i> option has been activated. This logs to 3 files what the program is doing, which might make it slightly slower.") + "</p><p style=\"margin-bottom:8px; margin-top:1px; \">" + tr("It is recommended to not process more than several thousand MP3 files while this option is turned on. You can turn it off manually, in the configuration dialog, in the <i>Others</i> tab, but keeping it turned on may provide very useful feedback to the developer, should the program crash again. With this feedback, future versions of MP3 Diags will get closer to being bug free.") + "</p>", "OK");
  903 
  904                 m_pCommonData->setTraceToFile(true);
  905                 m_settings.saveMiscConfigSettings(m_pCommonData);
  906             }
  907 
  908             // !!! don't change "dirty"; 1) there's no point; 2) ser needs it
  909         }
  910 
  911         // !!! nothing to do if not dirty
  912     }
  913     else
  914     { // it's a new version, so we start over //ttt2 perhaps also use some counter, like "20 runs without crash"
  915         m_pCommonData->setTraceToFile(false);
  916         m_settings.saveMiscConfigSettings(m_pCommonData);
  917     }
  918 
  919 #ifdef LOG_ANYWAY
  920     s_fileTracer.enable2(true);
  921 #else
  922     s_fileTracer.enable2(m_pCommonData->isTraceToFileEnabled()); // this might get called a second time (the first time is from within m_pCommonData->setTraceToFile()), but that's OK
  923 #endif
  924     s_fileTracer.enable1(true); // !!! after loadMiscConfigSettings(), so the previous files aren't deleted too early
  925 
  926     m_settings.saveVersion(getAppVer());
  927 
  928     TRACER("MainFormDlgImpl constr");
  929 
  930     {
  931         m_pCommonData->m_pFilesModel = new FilesModel(m_pCommonData);
  932         m_pFilesG->setModel(m_pCommonData->m_pFilesModel);
  933 
  934         FilesGDelegate* pDel (new FilesGDelegate(m_pCommonData, m_pFilesG));
  935         m_pFilesG->setItemDelegate(pDel);
  936 
  937         m_pFilesG->setHorizontalHeader(new FileHeaderView(m_pCommonData, m_pFilesG));
  938 
  939         m_pFilesG->horizontalHeader()->setMinimumSectionSize(CELL_WIDTH);
  940         m_pFilesG->verticalHeader()->setMinimumSectionSize(CELL_HEIGHT);
  941         m_pFilesG->verticalHeader()->setDefaultSectionSize(CELL_HEIGHT);
  942         decreaseRowHeaderFont(*m_pFilesG);
  943 
  944         connect(m_pFilesG->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), m_pCommonData->m_pFilesModel, SLOT(onFilesGSelChanged()));
  945         connect(m_pFilesG, SIGNAL(clicked(const QModelIndex &)), m_pCommonData->m_pFilesModel, SLOT(onFilesGSelChanged()));
  946 
  947         m_pFilesG->horizontalHeader()->setDefaultSectionSize(CELL_WIDTH);
  948         m_pFilesG->verticalHeader()->setDefaultSectionSize(CELL_HEIGHT);
  949 
  950         m_pFilesG->verticalHeader()->setDefaultAlignment(Qt::AlignRight | Qt::AlignVCenter);
  951 
  952         connect(m_pCommonData->m_pFilesModel, SIGNAL(currentFileChanged()), this, SLOT(onCrtFileChanged()));
  953         connect(m_pCommonData->m_pFilesModel, SIGNAL(currentFileChanged()), m_pCommonData, SLOT(onCrtFileChanged()));
  954     }
  955 
  956     {
  957         m_pCommonData->m_pNotesModel = new NotesModel(m_pCommonData);
  958         m_pNotesG->setModel(m_pCommonData->m_pNotesModel);
  959 
  960         NotesGDelegate* pNotesGDelegate = new NotesGDelegate(m_pCommonData);
  961         m_pNotesG->setItemDelegate(pNotesGDelegate);
  962 
  963         m_pNotesG->horizontalHeader()->setMinimumSectionSize(CELL_WIDTH + 10);
  964         
  965         /*QFont font (m_pNotesG->verticalHeader()->font());
  966         auto sz(font.pointSizeF());
  967         font.setPointSizeF(sz * 0.85);
  968         m_pNotesG->verticalHeader()->setFont(font);*/
  969         //m_pNotesG->verticalHeader()->setMinimumSectionSize(CELL_HEIGHT + 10);
  970         //m_pNotesG->verticalHeader()->setDefaultSectionSize(CELL_HEIGHT + 10);
  971         m_pNotesG->verticalHeader()->setMinimumSectionSize(CELL_HEIGHT);
  972         m_pNotesG->verticalHeader()->setDefaultSectionSize(CELL_HEIGHT);
  973         decreaseRowHeaderFont(*m_pNotesG);
  974 
  975         m_pNotesG->verticalHeader()->setDefaultAlignment(Qt::AlignRight | Qt::AlignVCenter);
  976 
  977         connect(m_pNotesG->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), m_pCommonData->m_pNotesModel, SLOT(onNotesGSelChanged()));
  978         connect(m_pNotesG, SIGNAL(clicked(const QModelIndex &)), m_pCommonData->m_pNotesModel, SLOT(onNotesGSelChanged()));
  979 
  980         connect(m_pNotesG->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), m_pNotesG, SLOT(resizeRowsToContents()));
  981     }
  982 
  983     {
  984         m_pCommonData->m_pStreamsModel = new StreamsModel(m_pCommonData);
  985         m_pStreamsG->setModel(m_pCommonData->m_pStreamsModel);
  986 
  987         StreamsGDelegate* pStreamsGDelegate = new StreamsGDelegate(m_pCommonData);
  988         m_pStreamsG->setItemDelegate(pStreamsGDelegate);
  989 
  990         m_pStreamsG->horizontalHeader()->setMinimumSectionSize(CELL_WIDTH + 10);
  991         m_pStreamsG->verticalHeader()->setMinimumSectionSize(CELL_HEIGHT);
  992         m_pStreamsG->verticalHeader()->setDefaultSectionSize(CELL_HEIGHT);
  993         decreaseRowHeaderFont(*m_pStreamsG);
  994 
  995         m_pStreamsG->verticalHeader()->setDefaultAlignment(Qt::AlignRight | Qt::AlignVCenter);
  996 
  997         connect(m_pStreamsG->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), m_pCommonData->m_pStreamsModel, SLOT(onStreamsGSelChanged()));
  998         connect(m_pStreamsG, SIGNAL(clicked(const QModelIndex &)), m_pCommonData->m_pStreamsModel, SLOT(onStreamsGSelChanged()));
  999 
 1000         connect(m_pStreamsG->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), m_pStreamsG, SLOT(resizeRowsToContents()));
 1001     }
 1002 
 1003     {
 1004         m_pCommonData->m_pUniqueNotesModel = new UniqueNotesModel(m_pCommonData);
 1005         m_pUniqueNotesG->setModel(m_pCommonData->m_pUniqueNotesModel);
 1006 
 1007         UniqueNotesGDelegate* pDel = new UniqueNotesGDelegate(m_pCommonData);
 1008         m_pUniqueNotesG->setItemDelegate(pDel);
 1009 
 1010         m_pUniqueNotesG->horizontalHeader()->setMinimumSectionSize(CELL_WIDTH + 10);
 1011         m_pUniqueNotesG->verticalHeader()->setMinimumSectionSize(CELL_HEIGHT);
 1012         m_pUniqueNotesG->verticalHeader()->setDefaultSectionSize(CELL_HEIGHT);
 1013         decreaseRowHeaderFont(*m_pUniqueNotesG);
 1014 
 1015         m_pUniqueNotesG->verticalHeader()->setDefaultAlignment(Qt::AlignRight | Qt::AlignVCenter);
 1016 
 1017         connect(m_pUniqueNotesG->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), m_pUniqueNotesG, SLOT(resizeRowsToContents()));
 1018     }
 1019 
 1020 
 1021     m_pTagDetailsLayout = new QHBoxLayout(m_pTagDetailsTab);
 1022     m_pTagDetailsTab->setLayout(m_pTagDetailsLayout);
 1023 
 1024     m_pTagDetailsW = new QWidget(m_pTagDetailsTab);
 1025     m_pTagDetailsLayout->addWidget(m_pTagDetailsW);
 1026     //m_pTagDetailsLayout->setContentsMargins(1, 1, 1, 1);
 1027     //m_pTagDetailsLayout->setContentsMargins(6, 6, 6, 6);
 1028     m_pTagDetailsLayout->setContentsMargins(0, 0, 0, 0);
 1029 
 1030 
 1031 
 1032     /*connect(m_pFilesG->selectionModel(), SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), m_pCommonData->m_pFilesModel, SLOT(onFilesGSelChanged())); //ttt2 see if needed (in addition to selectionChanged); apparently not: this signal is sent "the next time", so first clicking in a grid doesn't send any message, regardless of what was "current" before; then, when the message is sent, the value in the model is not yet updated, so using "m_pCommonData->m_pNotesG->selectionModel()->selection().indexes()" returns the indexes from the last time; perhaps using the first QModelIndex parameter would allow getting to the "current" cell, but not to the whole selection anyway;
 1033     connect(m_pNotesG->selectionModel(), SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), m_pCommonData->m_pNotesModel, SLOT(onNotesGSelChanged()));
 1034     connect(m_pStreamsG->selectionModel(), SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), m_pCommonData->m_pStreamsModel, SLOT(onStreamsGSelChanged()));*/
 1035 
 1036 
 1037     /* !!! There's this use case:
 1038 
 1039         1) the user selects a file that has multiple instances of the same error
 1040         2) the user clicks on one of the duplicates errors in m_pNotesG; the corresponding cell in m_pFilesG gets selected;
 1041         3) the user clicks on the selected cell in m_pFilesG; both of the duplicate errors should get selected;
 1042 
 1043     If nothing is done, nothing happens, and just one error stays selected; making m_pFilesG's current be (-1,-1) in FilesModel::matchSelToNotes() doesn't work anyaway because this doesn't change the selection either and it messes up keyboard navigation (assuming that it can be done).
 1044 
 1045     Instead, m_pFilesG's clicked() signal is connected to onFilesGSelChanged().
 1046     */
 1047 
 1048     //cout << convStr(m_settings.fileName()) << endl;
 1049 
 1050     { ModifInfoToolButton* p (new ModifInfoToolButton(m_pCustomTransform1B)); m_vpTransfButtons.push_back(p); connect(p, SIGNAL(clicked()), this, SLOT(on_m_pCustomTransform1B_clicked())); m_pCustomTransform1B = p; }
 1051     { ModifInfoToolButton* p (new ModifInfoToolButton(m_pCustomTransform2B)); m_vpTransfButtons.push_back(p); connect(p, SIGNAL(clicked()), this, SLOT(on_m_pCustomTransform2B_clicked())); m_pCustomTransform2B = p; }
 1052     { ModifInfoToolButton* p (new ModifInfoToolButton(m_pCustomTransform3B)); m_vpTransfButtons.push_back(p); connect(p, SIGNAL(clicked()), this, SLOT(on_m_pCustomTransform3B_clicked())); m_pCustomTransform3B = p; }
 1053     { ModifInfoToolButton* p (new ModifInfoToolButton(m_pCustomTransform4B)); m_vpTransfButtons.push_back(p); connect(p, SIGNAL(clicked()), this, SLOT(on_m_pCustomTransform4B_clicked())); m_pCustomTransform4B = p; } // CUSTOM_TRANSF_CNT
 1054 
 1055     { m_pModifNormalizeB = new ModifInfoToolButton(m_pNormalizeB); connect(m_pModifNormalizeB, SIGNAL(clicked()), this, SLOT(on_m_pNormalizeB_clicked())); m_pNormalizeB = m_pModifNormalizeB; }
 1056     { m_pModifReloadB = new ModifInfoToolButton(m_pReloadB); connect(m_pModifReloadB, SIGNAL(clicked()), this, SLOT(on_m_pReloadB_clicked())); m_pReloadB = m_pModifReloadB; }
 1057 
 1058     { m_pModifRenameFilesB = new ModifInfoToolButton(m_pRenameFilesB); connect(m_pModifRenameFilesB, SIGNAL(clicked()), this, SLOT(on_m_pRenameFilesB_clicked())); m_pRenameFilesB = m_pModifRenameFilesB; }
 1059 
 1060     { QAction* p (new QAction(this)); p->setShortcut(QKeySequence("F1")); connect(p, SIGNAL(triggered()), this, SLOT(onHelp())); addAction(p); }
 1061 
 1062     /*{ QAction* p (new QAction(this)); p->setShortcut(QKeySequence("Ctrl+N")); connect(p, SIGNAL(triggered()), this, SLOT(onNext())); addAction(p); } //p->setShortcutContext(Qt::ApplicationShortcut);
 1063     { QAction* p (new QAction(this)); p->setShortcut(QKeySequence("Ctrl+P")); connect(p, SIGNAL(triggered()), this, SLOT(onPrev())); addAction(p); }
 1064     { QAction* p (new QAction(this)); p->setShortcut(QKeySequence("Ctrl+V")); connect(p, SIGNAL(triggered()), this, SLOT(onPaste())); addAction(p); }
 1065     { QAction* p (new QAction(this)); p->setShortcut(QKeySequence("Ctrl+S")); connect(p, SIGNAL(triggered()), this, SLOT(on_m_pScanB_clicked())); addAction(p); }*/
 1066 
 1067     //{ QAction* p (new QAction(this)); p->setShortcut(QKeySequence(Qt::Key_Escape)); connect(p, SIGNAL(triggered()), this, SLOT(emptySlot())); addAction(p); } // !!! 2009.01.13 - no longer usable, because this also prevents edits in QTableView from exiting with ESC; so the m_nLastKey alternative is used; // 2009.03.31 - probably usable again, since the tag editor got moved to a separate window
 1068 
 1069     //m_pCurrentAlbumG->setEditTriggers(QAbstractItemView::AllEditTriggers);//EditKeyPressed);
 1070 
 1071     loadIgnored();
 1072 
 1073     {
 1074         if (!m_settings.loadTransfConfig(m_transfConfig))
 1075         {
 1076             m_settings.saveTransfConfig(m_transfConfig);
 1077         }
 1078 
 1079         for (int i = 0; i < CUSTOM_TRANSF_CNT; ++i)
 1080         {
 1081             loadCustomTransf(i);
 1082         }
 1083     }
 1084 
 1085 //ttt2 perhaps have "experimental" transforms, different color (or just have the names begin with "experimental")
 1086     {
 1087         loadVisibleTransf();
 1088         if (m_pCommonData->getVisibleTransf().empty())
 1089         {
 1090             vector<int> v;
 1091             initDefaultVisibleTransf(v, m_pCommonData);
 1092             m_pCommonData->setVisibleTransf(v);
 1093         }
 1094     }
 1095 
 1096     {
 1097         loadExternalTools();
 1098     }
 1099 
 1100     {
 1101         initializeUi();
 1102     }
 1103 
 1104     setTransfTooltips();
 1105 
 1106     {
 1107         delete m_pRemovableL;
 1108 
 1109         delete m_pLowerHalfTablesW->layout();
 1110         m_pLowerHalfLayout = new QStackedLayout(m_pLowerHalfTablesW);
 1111         //m_pLowerHalfLayout->setContentsMargins(0, 50, 50, 0);
 1112         //m_pLowerHalfTablesW->setLayout(m_pLowerHalfLayout);
 1113         m_pLowerHalfLayout->addWidget(m_pFileInfoTab);
 1114         m_pLowerHalfLayout->addWidget(m_pAllNotesTab);
 1115         m_pLowerHalfLayout->addWidget(m_pTagDetailsTab);
 1116 
 1117 
 1118         delete m_pDetailsTabWidget;
 1119 
 1120         int nHeight (QApplication::fontMetrics().height() + 7);
 1121 
 1122         m_pLowerHalfBtnW->setMinimumHeight(nHeight);
 1123         m_pLowerHalfBtnW->setMaximumHeight(nHeight);
 1124     }
 1125 
 1126     connect(this, SIGNAL(tagEditorClosed()), m_pCommonData, SLOT(onFilterChanged())); // !!! needed because CommonData::mergeHandlerChanges() adds changed files that shouldn't normally be there in album / filter mode; the reason it does this is to allow comparisons after making changes, but this doesn't make much sense when those changes are done in the tag editor, (saving in the tag editor is different from regular transformations because album navigation is allowed)
 1127 
 1128     s_pGlobalDlg = this;
 1129 
 1130     { QAction* p (new QAction(this)); p->setShortcut(QKeySequence("Ctrl+A")); connect(p, SIGNAL(triggered()), this, SLOT(emptySlot())); addAction(p); } // !!! needed because it takes a lot of time, during which the app seems frozen (caused by the cells being selected and unselected automatically) // ttt2 see if possible to disable selecting "note" cells with SHIFT pressed
 1131 
 1132 
 1133     QTimer::singleShot(1, this, SLOT(onShow()));
 1134 }
 1135 
 1136 
 1137 
 1138 
 1139 
 1140 
 1141 
 1142 
 1143 /*override*/ void MainFormDlgImpl::keyPressEvent(QKeyEvent* pEvent)
 1144 {
 1145 //qDebug("key prs %x", pEvent->key());
 1146     m_nLastKey = pEvent->key();
 1147 
 1148     pEvent->ignore();
 1149 }
 1150 
 1151 
 1152 void MainFormDlgImpl::onHelp()
 1153 {
 1154     if (m_pViewFileInfoB->isChecked())
 1155     {
 1156         openHelp("130_main_window.html");
 1157     }
 1158     else if (m_pViewAllNotesB->isChecked())
 1159     {
 1160         openHelp("150_main_window_all_notes.html");
 1161     }
 1162     else if (m_pViewTagDetailsB->isChecked())
 1163     {
 1164         openHelp("160_main_window_tag_details.html");
 1165     }
 1166     else
 1167     {
 1168         CB_ASSERT(false);
 1169     }
 1170 }
 1171 
 1172 /*override*/ void MainFormDlgImpl::keyReleaseEvent(QKeyEvent* pEvent)
 1173 {
 1174 //qDebug("key rel %d", pEvent->key());
 1175     if (Qt::Key_Escape == pEvent->key())
 1176     {
 1177         //on_m_pAbortB_clicked();
 1178         pEvent->ignore();
 1179     }
 1180     else
 1181     {
 1182         QDialog::keyReleaseEvent(pEvent);
 1183     }
 1184     //pEvent->ignore(); // ttt2 not sure this is the way to do it, but the point is to disable the ESC key
 1185 }
 1186 
 1187 
 1188 
 1189 
 1190 void MainFormDlgImpl::initializeUi()
 1191 {
 1192     int nWidth, nHeight;
 1193     int nNotesGW0, nNotesGW2, nStrmsGW0, nStrmsGW1, nStrmsGW2, nStrmsGW3, nUnotesGW0;
 1194     int nIconSize;
 1195     QByteArray stateMainSpl, stateLwrSpl;
 1196     m_settings.loadMainSettings(nWidth, nHeight, nNotesGW0, nNotesGW2, nStrmsGW0, nStrmsGW1, nStrmsGW2, nStrmsGW3, nUnotesGW0, stateMainSpl, stateLwrSpl, nIconSize, m_nScanWidth);
 1197 
 1198     if (m_nScanWidth <= 400)
 1199     {
 1200         QRect r (QApplication::desktop()->availableGeometry());
 1201         m_nScanWidth = min(1600, r.width());
 1202         if (m_nScanWidth > 1200)
 1203         {
 1204             m_nScanWidth = m_nScanWidth*3/4;
 1205         }
 1206         else
 1207         {
 1208             m_nScanWidth = m_nScanWidth*4/5;
 1209         }
 1210         //qDebug("%d %d %d %d", r.x(), r.y(), r.width(), r.height());
 1211     }
 1212 
 1213     if (nWidth > 400 && nHeight > 400)
 1214     {
 1215         QRect r (QApplication::desktop()->availableGeometry());
 1216         const int nApprox (16);
 1217 
 1218 #ifndef WIN32
 1219         int nTitleHeight(0);
 1220 #else
 1221         int nTitleHeight(GetSystemMetrics(SM_CYSIZE)); // ttt2 actually there's a pixel missing but not obvious where to get it from; nApprox should allow enough tolerance, though
 1222 #endif
 1223 
 1224         if (r.width() - nWidth < nApprox && r.height() - nHeight - nTitleHeight < nApprox)
 1225         {
 1226             m_bShowMaximized = true;
 1227         }
 1228         else
 1229         {
 1230             resize(nWidth, nHeight);
 1231         }
 1232     }
 1233     else
 1234     {
 1235         //QRect r (QApplication::desktop()->availableGeometry());
 1236         //qDebug("%d %d %d %d", r.x(), r.y(), r.width(), r.height());
 1237         m_bShowMaximized = true; // ttt2 perhaps implement m_bShowMaximized for all windows
 1238     }
 1239 
 1240     m_pCommonData->m_nMainWndIconSize = nIconSize;
 1241 
 1242     {
 1243         if (nNotesGW0 < CELL_WIDTH + 8) { nNotesGW0 = CELL_WIDTH + 8; }
 1244         if (nNotesGW2 < 10) { nNotesGW2 = 65; }
 1245 
 1246         m_pNotesG->horizontalHeader()->resizeSection(0, nNotesGW0); // ttt2 apparently a call to resizeColumnsToContents() in NotesModel::updateCurrentNotes() should make columns 0 and 2 have the right size, but that's not the case at all; (see further notes there)
 1247         m_pNotesG->horizontalHeader()->resizeSection(2, nNotesGW2);
 1248         m_pNotesG->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
 1249         //m_pNotesG->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch);
 1250     }
 1251 
 1252     {
 1253         if (nStrmsGW0 < 10) { nStrmsGW0 = 80; } // ttt2 "80" hard-coded
 1254         if (nStrmsGW1 < 10) { nStrmsGW1 = 80; }
 1255         if (nStrmsGW2 < 10) { nStrmsGW2 = 80; }
 1256         if (nStrmsGW3 < 10) { nStrmsGW3 = 110; }
 1257 
 1258         m_pStreamsG->horizontalHeader()->resizeSection(0, nStrmsGW0);
 1259         m_pStreamsG->horizontalHeader()->resizeSection(1, nStrmsGW1);
 1260         m_pStreamsG->horizontalHeader()->resizeSection(2, nStrmsGW2);
 1261         m_pStreamsG->horizontalHeader()->resizeSection(3, nStrmsGW3);
 1262         m_pStreamsG->horizontalHeader()->setSectionResizeMode(4, QHeaderView::Stretch);
 1263     }
 1264 
 1265     {
 1266         if (nUnotesGW0 < CELL_WIDTH + 8) { nUnotesGW0 = CELL_WIDTH + 8; } // ttt2 replace CELL_WIDTH
 1267 
 1268         m_pUniqueNotesG->horizontalHeader()->resizeSection(0, nUnotesGW0);
 1269         m_pUniqueNotesG->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
 1270     }
 1271 
 1272 
 1273     {
 1274         if (!stateMainSpl.isNull())
 1275         {
 1276             m_pMainSplitter->restoreState(stateMainSpl);
 1277         }
 1278         m_pMainSplitter->setOpaqueResize(false);
 1279 
 1280         if (!stateLwrSpl.isNull())
 1281         {
 1282             m_pLowerSplitter->restoreState(stateLwrSpl);
 1283         }
 1284     }
 1285 
 1286 
 1287     m_pCrtDirE->setFocus();
 1288 
 1289     if (!m_pCommonData->m_bShowExport)
 1290     {
 1291         m_pExportB->hide();
 1292     }
 1293 
 1294     if (!m_pCommonData->m_bShowDebug)
 1295     {
 1296         m_pDebugB->hide();
 1297     }
 1298 
 1299     bool bShowSessions = m_pCommonData->m_bShowSessions;
 1300     if (!bShowSessions)
 1301     {
 1302         GlobalSettings st;
 1303         bShowSessions = st.getSessionCount() > 1;
 1304     }
 1305     if (!bShowSessions)
 1306     {
 1307         m_pSessionsB->hide();
 1308         if (!m_pCommonData->m_bShowExport)
 1309         {
 1310             m_pOptBtn1W->hide();
 1311         }
 1312     }
 1313 
 1314     resizeIcons();
 1315 }
 1316 
 1317 
 1318 void MainFormDlgImpl::onShow()
 1319 {
 1320     TRACER("MainFormDlgImpl::onShow()");
 1321     bool bLoadErr (false);
 1322 
 1323     bool bCrashedAtStartup;
 1324     m_pCommonData->m_strTransfLog = SessionEditorDlgImpl::getLogFileName(m_strSession);
 1325     m_settings.loadCrashedAtStartup(bCrashedAtStartup);
 1326     if (bCrashedAtStartup)
 1327     {
 1328         showCritical(this, tr("Error"), tr("MP3 Diags crashed while reading song data from the disk. The whole collection will be rescanned."));
 1329     }
 1330     else
 1331     {
 1332         m_settings.saveCrashedAtStartup(true);
 1333         string strErr;
 1334         SerLoadThread* p (new SerLoadThread(m_pCommonData, m_strSession, strErr));
 1335 
 1336         ThreadRunnerDlgImpl dlg (this, getNoResizeWndFlags(), p, ThreadRunnerDlgImpl::SHOW_COUNTER, ThreadRunnerDlgImpl::TRUNCATE_BEGIN, ThreadRunnerDlgImpl::HIDE_PAUSE_ABORT);
 1337         CB_ASSERT (m_nScanWidth > 400);
 1338         dlg.resize(m_nScanWidth, dlg.height());
 1339         dlg.setWindowIcon(QIcon(":/images/logo.svg"));
 1340 
 1341         dlg.setWindowTitle(tr("Loading data"));
 1342         s_pSerThread = p;
 1343         dlg.exec();
 1344         s_pSerThread = 0;
 1345         m_nScanWidth = dlg.width();
 1346         m_pCommonData->setCrtAtStartup();
 1347 
 1348         if (!strErr.empty())
 1349         {
 1350             bLoadErr = true;
 1351             showCritical(this, tr("Error"), tr("An error occured while loading the MP3 information. Your files will be rescanned.\n\n%1").arg(convStr(strErr)));
 1352         }
 1353     }
 1354 
 1355     m_settings.saveCrashedAtStartup(false);
 1356 
 1357     bool bDirty;
 1358     m_settings.loadDbDirty(bDirty);
 1359     if (bDirty)
 1360     {
 1361         /*s_strErrorMsg = "Rescanning files after crash.";
 1362         showErrorDlg(this, false);*/
 1363 
 1364         if (m_transfConfig.m_options.m_bKeepOrigTime)
 1365         {
 1366             showWarning(this, tr("Warning"), tr("It seems that MP3 Diags is restarting after a crash. Your files will be rescanned.\n\n(Since this may take a long time for large collections, you may want to abort the full rescanning and apply a filter to include only the files that you changed since the last time the program closed correctly, then manually rescan only those files.)"));
 1367         }
 1368         else
 1369         {
 1370             bDirty = false; // !!! if original time is not kept, any changes will be detected anyway, no need to force a full reload
 1371         }
 1372     }
 1373 
 1374     m_settings.saveDbDirty(true);
 1375 
 1376     if (m_pCommonData->m_bScanAtStartup || bDirty)
 1377     {
 1378         fullReload(bDirty || bLoadErr || bCrashedAtStartup ? FORCE : DONT_FORCE);
 1379     }
 1380 
 1381     resizeEvent(0);
 1382 
 1383     // !!! without these the the file grid may look bad if it has a horizontal scrollbar and one of the last files is current
 1384     string strCrt (m_pCommonData->getCrtName());
 1385     m_pFilesG->setCurrentIndex(m_pFilesG->model()->index(0, 0));
 1386     m_pCommonData->updateWidgets(strCrt);
 1387 
 1388 #ifndef DISABLE_CHECK_FOR_UPDATES
 1389     checkForNewVersion();
 1390 #endif
 1391 }
 1392 
 1393 
 1394 void MainFormDlgImpl::fullReload(bool bForceReload)
 1395 {
 1396     CommonData::ViewMode eMode (m_pCommonData->getViewMode());
 1397     m_pCommonData->setViewMode(CommonData::ALL, m_pCommonData->getCrtMp3Handler());
 1398     m_pCommonData->m_filter.disableAll();
 1399     reload(IGNORE_SEL, bForceReload);
 1400     m_pCommonData->m_filter.restoreAll();
 1401     m_pCommonData->setViewMode(eMode, m_pCommonData->getCrtMp3Handler());
 1402 }
 1403 
 1404 /*override*/ void MainFormDlgImpl::closeEvent(QCloseEvent*)
 1405 {
 1406     string strErr;
 1407     SerSaveThread* p (new SerSaveThread(m_pCommonData, m_strSession, strErr));
 1408 //Qt::WindowStaysOnTopHint
 1409     ThreadRunnerDlgImpl dlg (this, getNoResizeWndFlags(), p, ThreadRunnerDlgImpl::SHOW_COUNTER, ThreadRunnerDlgImpl::TRUNCATE_BEGIN, ThreadRunnerDlgImpl::HIDE_PAUSE_ABORT);
 1410     CB_ASSERT (m_nScanWidth > 400);
 1411     dlg.resize(m_nScanWidth, dlg.height());
 1412     dlg.setWindowIcon(QIcon(":/images/logo.svg"));
 1413 
 1414     dlg.setWindowTitle(tr("Saving data"));
 1415     s_pSerThread = p;
 1416     dlg.exec();
 1417     s_pSerThread = 0;
 1418     m_nScanWidth = dlg.width();
 1419 
 1420     if (!strErr.empty())
 1421     {
 1422         showCritical(this, tr("Error"), tr("An error occured while saving the MP3 information. You will have to scan your files again.\n\n%1").arg(convStr(strErr)));
 1423     }
 1424 }
 1425 
 1426 
 1427 
 1428 MainFormDlgImpl::~MainFormDlgImpl()
 1429 {
 1430     TRACER("MainFormDlgImpl destr");
 1431     s_pGlobalDlg = 0;
 1432 
 1433     m_settings.saveMainSettings(
 1434         width(),
 1435         height(),
 1436         m_pNotesG->horizontalHeader()->sectionSize(0),
 1437         m_pNotesG->horizontalHeader()->sectionSize(2),
 1438 
 1439         m_pStreamsG->horizontalHeader()->sectionSize(0),
 1440         m_pStreamsG->horizontalHeader()->sectionSize(1),
 1441         m_pStreamsG->horizontalHeader()->sectionSize(2),
 1442         m_pStreamsG->horizontalHeader()->sectionSize(3),
 1443 
 1444         m_pUniqueNotesG->horizontalHeader()->sectionSize(0),
 1445 
 1446         m_pMainSplitter->saveState(),
 1447         m_pLowerSplitter->saveState(),
 1448 
 1449         m_pCommonData->m_nMainWndIconSize,
 1450 
 1451         m_nScanWidth
 1452         );
 1453 
 1454     m_settings.saveDbDirty(false); // !!! it would seem better to delay marking the data clean until a full reload is completed; however, if the user aborted the rescan, it was probably for a good reason (e.g. to rescan only a part of the files), so it makes more sense to mark the data as clean regardless of how it was when it was loaded and if the rescan completed or not
 1455 
 1456 
 1457     //QMessageBox dlg (this); dlg.show();
 1458     //CursorOverrider crs;// (Qt::ArrowCursor);
 1459 
 1460 
 1461     delete m_pCommonData;
 1462 }
 1463 
 1464 void SessionSettings::saveDbDirty(bool bDirty)
 1465 {
 1466     m_pSettings->setValue("main/dirty", bDirty);
 1467     m_pSettings->sync();
 1468 }
 1469 
 1470 void SessionSettings::loadDbDirty(bool& bDirty)
 1471 {
 1472     bDirty = m_pSettings->value("main/dirty", false).toBool();
 1473 }
 1474 
 1475 
 1476 
 1477 void SessionSettings::saveCrashedAtStartup(bool bCrashedAtStartup)
 1478 {
 1479     m_pSettings->setValue("debug/crashedAtStartup", bCrashedAtStartup);
 1480     m_pSettings->sync();
 1481 }
 1482 
 1483 void SessionSettings::loadCrashedAtStartup(bool& bCrashedAtStartup)
 1484 {
 1485     bCrashedAtStartup = m_pSettings->value("debug/crashedAtStartup", false).toBool();
 1486 }
 1487 
 1488 
 1489 
 1490 void SessionSettings::saveVersion(const string& strVersion)
 1491 {
 1492     m_pSettings->setValue("main/version", convStr(strVersion));
 1493 }
 1494 
 1495 void SessionSettings::loadVersion(string& strVersion)
 1496 {
 1497     strVersion = convStr(m_pSettings->value("main/version", "x").toString());
 1498 }
 1499 
 1500 
 1501 
 1502 
 1503 
 1504 MainFormDlgImpl::CloseOption MainFormDlgImpl::run()
 1505 {
 1506     if (m_bShowMaximized)
 1507     {
 1508         showMaximized();
 1509     }
 1510     return QDialog::Accepted == exec() ? OPEN_SESS_DLG : EXIT;
 1511 }
 1512 
 1513 
 1514 
 1515 
 1516 
 1517 void MainFormDlgImpl::onCrtFileChanged()
 1518 {
 1519     delete m_pTagDetailsW;
 1520     m_pTagDetailsW = new QWidget(m_pTagDetailsTab);
 1521     m_pTagDetailsLayout->addWidget(m_pTagDetailsW);
 1522 
 1523     int nCrtFile (m_pCommonData->getFilesGCrtRow());
 1524     if (-1 == nCrtFile)
 1525     {
 1526         m_pCrtDirE->setText("");
 1527         return;
 1528     }
 1529 
 1530     QHBoxLayout* pLayout = new QHBoxLayout();
 1531     pLayout->setSpacing(12);
 1532     //pLayout->setContentsMargins(6, 6, 6, 6);
 1533     pLayout->setContentsMargins(1, 1, 1, 1);
 1534     m_pTagDetailsW->setLayout(pLayout);
 1535 
 1536     //pLayout->setSpacing(1);
 1537 
 1538     const Mp3Handler* pMp3Handler (m_pCommonData->getViewHandlers()[nCrtFile]);
 1539     const vector<DataStream*>& vpStreams (pMp3Handler->getStreams());
 1540 
 1541     if (m_pViewTagDetailsB->isChecked())
 1542     {
 1543         for (int i = 0, n = cSize(vpStreams); i < n; ++i)
 1544         {
 1545             DataStream* pStream (vpStreams[i]);
 1546             TagReader* pReader (dynamic_cast<TagReader*>(pStream));
 1547             if (0 != pReader)
 1548             {
 1549                 TagReadPanel* pPanel (new TagReadPanel(m_pTagDetailsW, pReader));
 1550                 pLayout->addWidget(pPanel, 10);
 1551             }
 1552         }
 1553     }
 1554 
 1555     QString qs (toNativeSeparators(convStr(m_pCommonData->getViewHandlers()[nCrtFile]->getDir())));
 1556 #ifndef WIN32
 1557 #else
 1558     if (2 == qs.size() && ':' == qs[1])
 1559     {
 1560         qs += "\\";
 1561     }
 1562 #endif
 1563     m_pCrtDirE->setText(qs);
 1564 
 1565     pLayout->addStretch(0);
 1566 }
 1567 
 1568 
 1569 
 1570 
 1571 /*override*/ void MainFormDlgImpl::resizeEvent(QResizeEvent* pEvent)
 1572 {
 1573     if (m_pCommonData->m_bAutoSizeIcons || m_pCommonData->m_nMainWndIconSize < 16)
 1574     {
 1575         //const QRect& r (QApplication::desktop()->availableGeometry());
 1576         int w (width());
 1577         int k (w <= 800 ? 28 : w <= 1180 ? 32 : w <= 1400 ? 40 : w <= 1600 ? 48 : w <= 1920 ? 56 : 64);
 1578         m_pCommonData->m_nMainWndIconSize = k;
 1579         resizeIcons();
 1580     }
 1581 
 1582     m_pCommonData->resizeFilesGCols();
 1583 
 1584     m_pUniqueNotesG->resizeRowsToContents();
 1585     m_pNotesG->resizeRowsToContents();
 1586     m_pStreamsG->resizeRowsToContents();
 1587 
 1588     QDialog::resizeEvent(pEvent);
 1589 }
 1590 
 1591 
 1592 namespace {
 1593 
 1594 struct Mp3ProcThread : public PausableThread
 1595 {
 1596     FileEnumerator& m_fileEnum;
 1597     bool m_bForce;
 1598     CommonData* m_pCommonData;
 1599     vector<const Mp3Handler*> m_vpAdd, m_vpDel;
 1600     deque<const Mp3Handler*> m_vpExisting; // some (or all) of these will may get copied to m_vpDel
 1601     vector<const Mp3Handler*> m_vpKeep; // subset of m_vpExisting
 1602 
 1603     Mp3ProcThread(FileEnumerator& fileEnum, bool bForce, CommonData* pCommonData, deque<const Mp3Handler*> vpExisting) : m_fileEnum(fileEnum), m_bForce(bForce), m_pCommonData(pCommonData), m_vpExisting(vpExisting) {}
 1604 
 1605     /*override*/ void run()
 1606     {
 1607         Timer timer;
 1608         try
 1609         {
 1610             CompleteNotif notif(this);
 1611 
 1612             bool bAborted (!scan());
 1613 
 1614             if (!bAborted)
 1615             {
 1616                 sort(m_vpKeep.begin(), m_vpKeep.end(), CmpMp3HandlerPtrByName());
 1617                 set_difference(m_vpExisting.begin(), m_vpExisting.end(), m_vpKeep.begin(), m_vpKeep.end(), back_inserter(m_vpDel), CmpMp3HandlerPtrByName());
 1618             }
 1619 
 1620             notif.setSuccess(!bAborted);
 1621         }
 1622         catch (const exception& ex)
 1623         {
 1624             TRACER1("Mp3ProcThread::run()", 1);
 1625             TRACER1(ex.what(), 2);
 1626             CB_ASSERT1 (false, ex.what());
 1627         }
 1628         catch (...)
 1629         {
 1630             TRACER("Mp3ProcThread::run() - unknown exception");
 1631             CB_ASSERT (false); //ttt0 triggered according to https://sourceforge.net/apps/mantisbt/mp3diags/view.php?id=50 and https://sourceforge.net/apps/mantisbt/mp3diags/view.php?id=54  2014.11.13 - one way to get here was disabled, by catching some exceptions
 1632         }
 1633         TRACER("Scanning took " + Timer::addThSep(timer.stop() / 1000000) + " milliseconds");
 1634     }
 1635 
 1636     bool scan();
 1637 };
 1638 
 1639 
 1640 // a subset of m_vpExisting  gets copied to m_vpDel; so if m_vpExisting is empty, m_vpDel will be empty too;
 1641 bool Mp3ProcThread::scan()
 1642 {
 1643     //cout << "################### procRec(" << strDir << ")\n";
 1644     //FileSearcher fs ((strDir + "/*").c_str());
 1645     m_fileEnum.reset();
 1646 
 1647     for (;;)
 1648     {
 1649         string strName (m_fileEnum.next());
 1650         if (strName.empty()) { return true; }
 1651         QString qs;
 1652         if (strName.size() > 4)
 1653         {
 1654             qs = convStr(strName.substr(strName.size() - 4)).toLower();
 1655         }
 1656 
 1657         if (qs == ".mp3" || qs == ".id3")
 1658         {
 1659             if (isAborted()) { return false; }
 1660             checkPause();
 1661 
 1662             StrList l;
 1663             l.push_back(toNativeSeparators(convStr(strName)));
 1664             emit stepChanged(l, -1);
 1665             if (!m_bForce)
 1666             {
 1667                 deque<const Mp3Handler*>::iterator it (lower_bound(m_vpExisting.begin(), m_vpExisting.end(), strName, CmpMp3HandlerPtrByName()));
 1668                 if (m_vpExisting.end() != it && (*it)->getName() == strName && !(*it)->needsReload(Mp3Handler::DONT_USE_FAST_SAVE))
 1669                 {
 1670                     m_vpKeep.push_back(*it);
 1671                     continue;
 1672                 }
 1673             }
 1674 
 1675             try
 1676             {
 1677                 const Mp3Handler* p (Mp3Handler::create(strName, m_pCommonData->m_bUseAllNotes, m_pCommonData->getQualThresholds()));
 1678                 m_vpAdd.push_back(p);
 1679             }
 1680             catch (const Mp3Handler::FileNotFound&) //ttt2 see if it should catch more
 1681             {
 1682             }
 1683         }
 1684     }
 1685 }
 1686 
 1687 } // namespace
 1688 
 1689 //ttt2 perhaps: Too many notes that you don't care about are cluttering your screen? You can hide such notes, so you don't see them again. To do this, open the configuration dialog and go to the "Ignored notes" tab. See more details at ...
 1690 
 1691 //ttt2 album detection: folder / tags /both
 1692 
 1693 void MainFormDlgImpl::scan(FileEnumerator& fileEnum, bool bForce, deque<const Mp3Handler*> vpExisting, int nKeepWhenUpdate)
 1694 {
 1695     m_pCommonData->clearLog();
 1696 
 1697     //m_pModeAllB->setChecked(true);
 1698 
 1699     vector<const Mp3Handler*> vpAdd, vpDel;
 1700     {
 1701         Mp3ProcThread* p (new Mp3ProcThread(fileEnum, bForce, m_pCommonData, vpExisting));
 1702 
 1703         ThreadRunnerDlgImpl dlg (this, getNoResizeWndFlags(), p, ThreadRunnerDlgImpl::SHOW_COUNTER, ThreadRunnerDlgImpl::TRUNCATE_BEGIN);
 1704         CB_ASSERT (m_nScanWidth > 400);
 1705         dlg.resize(m_nScanWidth, dlg.height());
 1706         dlg.setWindowIcon(QIcon(":/images/logo.svg"));
 1707 
 1708         dlg.setWindowTitle(tr("Scanning MP3 files"));
 1709         dlg.exec(); //ttt2 perhaps see if it ended with ok/reject and clear all on reject
 1710         m_nScanWidth = dlg.width();
 1711         vpAdd = p->m_vpAdd;
 1712         vpDel = p->m_vpDel;
 1713     }
 1714 
 1715     m_pCommonData->mergeHandlerChanges(vpAdd, vpDel, nKeepWhenUpdate);
 1716 
 1717     /*
 1718     if (!m_pCommonData->m_bToldAboutSupport && !s_bToldAboutSupportInCrtRun)
 1719     {
 1720         const vector<const Note*>& v (m_pCommonData->getUniqueNotes().getFltVec());
 1721         vector<const Note*>::const_iterator it (lower_bound(v.begin(), v.end(), &Notes::unsupportedFound(), CmpNotePtrById()));
 1722         if (v.end() != it && &Notes::unsupportedFound() == *it)
 1723         {
 1724             s_bToldAboutSupportInCrtRun = true;
 1725 
 1726             HtmlMsg::msg(this, 0, 0, &m_pCommonData->m_bToldAboutSupport, HtmlMsg::DEFAULT, tr("Info"), "<p style=\"margin-bottom:1px; margin-top:12px; \">" + tr("Your files are not fully supported by the current version of MP3 Diags. The main reason for this is that the developer is aware of some MP3 features but doesn't have actual MP3 files to implement support for those features and test the code.") + "</p>"
 1727 
 1728             "<p style=\"margin-bottom:1px; margin-top:12px; \">" + tr("You can help improve MP3 Diags by making files with unsupported notes available to the developer. The preferred way to do this is to report an issue on the project's Issue Tracker at %1, after checking if others made similar files available. To actually send the files, you can mail them to %2 or put them on a file sharing site. It would be a good idea to make sure that you have the latest version of MP3 Diags.").arg(g_qstrBugReport).arg(g_qstrSupportMail) + "</p>"
 1729 
 1730             "<p style=\"margin-bottom:1px; margin-top:12px; \">" + tr("You can identify unsupported notes by the blue color that is used for their labels.") + "</p>", 750, 300, tr("O&K"));
 1731 
 1732             if (m_pCommonData->m_bToldAboutSupport)
 1733             {
 1734                 m_settings.saveMiscConfigSettings(m_pCommonData);
 1735             }
 1736         }
 1737     }*/
 1738 }
 1739 
 1740 
 1741 
 1742 
 1743 
 1744 void MainFormDlgImpl::on_m_pScanB_clicked()
 1745 {
 1746     bool bForce;
 1747     bool bOk;
 1748     {
 1749         ScanDlgImpl dlg (this, m_pCommonData);
 1750         bOk = dlg.run(bForce);
 1751     }
 1752 
 1753     if (!bOk) { return; }
 1754 
 1755     scan(bForce);
 1756 }
 1757 
 1758 
 1759 void MainFormDlgImpl::scan(bool bForce)
 1760 {
 1761     long nPrevMem(getMemUsage());
 1762 
 1763     m_pCommonData->m_filter.disableNote();
 1764     m_pCommonData->m_filter.setDirs(vector<string>());
 1765     m_pCommonData->setViewMode(CommonData::ALL);
 1766 
 1767     //scan(m_pCommonData->m_dirTreeEnum, bForce, m_pCommonData->getViewHandlers(), CommonData::SEL | CommonData::CURRENT);
 1768     scan(m_pCommonData->m_dirTreeEnum, bForce, m_pCommonData->getViewHandlers(), CommonData::NOTHING);
 1769     long nCrtMem(getMemUsage());
 1770 
 1771     ostringstream out;
 1772     time_t t (time(0));
 1773     out << "current memory used by the whole program: " << nCrtMem << "; memory used by the current data (might be very inaccurate): " << nCrtMem - nPrevMem << "; time: " << ctime(&t);
 1774 
 1775     string s (out.str());
 1776     s.erase(s.size() - 1); // needed because ctime() uses a terminating '\n'
 1777 
 1778     qDebug("%s", s.c_str());
 1779 
 1780     trace("");
 1781     trace("************************* " + s);
 1782     //exportAsText();
 1783 }
 1784 
 1785 
 1786 
 1787 
 1788 
 1789 
 1790 
 1791 
 1792 
 1793 void MainFormDlgImpl::on_m_pNoteFilterB_clicked()
 1794 {
 1795     if (m_pCommonData->m_filter.isNoteEnabled())
 1796     {
 1797         m_pCommonData->m_filter.disableNote();
 1798         return;
 1799     }
 1800 
 1801     NoteFilterDlgImpl dlg (m_pCommonData, this);
 1802 
 1803     if (QDialog::Accepted == dlg.exec())
 1804     {
 1805         // !!! no need to do anything with m_pCommonData->m_filter, because dlg.exec() took care of that
 1806     }
 1807     else
 1808     {
 1809         m_pNoteFilterB->setChecked(false); // !!! no guard needed, because the event that calls the filter is "clicked", not "checked"
 1810     }
 1811 }
 1812 
 1813 
 1814 
 1815 
 1816 void MainFormDlgImpl::on_m_pDirFilterB_clicked()
 1817 {
 1818     if (m_pCommonData->m_filter.isDirEnabled())
 1819     {
 1820         m_pCommonData->m_filter.disableDir();
 1821         return;
 1822     }
 1823 
 1824     DirFilterDlgImpl dlg (m_pCommonData, this);
 1825 
 1826     if (QDialog::Accepted == dlg.exec())
 1827     {
 1828         // !!! no need to do anything with m_pCommonData->m_filter, because dlg.exec() took care of that
 1829     }
 1830     else
 1831     {
 1832         m_pDirFilterB->setChecked(false); // !!! no guard needed, because the event that calls the filter is "clicked", not "checked"
 1833     }
 1834 }
 1835 
 1836 
 1837 
 1838 
 1839 //ttt2 ? operation to "add null frame", which would allow to rescue some frames in case of overwriting
 1840 
 1841 
 1842 
 1843 void MainFormDlgImpl::saveCustomTransf(int k)
 1844 {
 1845     const vector<int>& v (m_pCommonData->getCustomTransf()[k]);
 1846     int n (cSize(v));
 1847     vector<string> vstrActionNames;
 1848     for (int i = 0; i < n; ++i)
 1849     {
 1850         vstrActionNames.push_back(m_pCommonData->getAllTransf()[v[i]]->getActionName());
 1851     }
 1852     char bfr [50];
 1853     sprintf(bfr, "customTransf/set%04d", k);
 1854     m_settings.saveVector(bfr, vstrActionNames);
 1855 }
 1856 
 1857 
 1858 
 1859 
 1860 void MainFormDlgImpl::loadCustomTransf(int k)
 1861 {
 1862     char bfr [50];
 1863     sprintf(bfr, "customTransf/set%04d", k);
 1864     //vector<string> vstrNames (m_settings.loadCustomTransf(k));
 1865     bool bErr;
 1866     vector<string> vstrNames (m_settings.loadVector(bfr, bErr));
 1867     vector<int> v;
 1868     const vector<Transformation*>& u (m_pCommonData->getAllTransf());
 1869     int m (cSize(u));
 1870     for (int i = 0, n = cSize(vstrNames); i < n; ++i)
 1871     {
 1872         string strName (vstrNames[i]);
 1873         int j (0);
 1874         for (; j < m; ++j)
 1875         {
 1876             if (u[j]->getActionName() == strName)
 1877             {
 1878                 v.push_back(j);
 1879                 break;
 1880             }
 1881         }
 1882 
 1883         if (j == m)
 1884         {
 1885             showWarning(this, tr("Error setting up custom transformations"), tr("Couldn't find a transformation with the name \"%1\". The program will proceed, but you should review the custom transformations lists.").arg(convStr(strName)));
 1886         }
 1887     }
 1888 
 1889 
 1890     if (v.empty())
 1891     {
 1892         vector<vector<int> > vv (CUSTOM_TRANSF_CNT);
 1893         initDefaultCustomTransf(k, vv, m_pCommonData);
 1894         v = vv[k];
 1895     }
 1896 
 1897     m_pCommonData->setCustomTransf(k, v);
 1898 }
 1899 
 1900 
 1901 
 1902 void MainFormDlgImpl::saveVisibleTransf()
 1903 {
 1904     const vector<int>& v (m_pCommonData->getVisibleTransf());
 1905     int n (cSize(v));
 1906     vector<string> vstrActionNames;
 1907     for (int i = 0; i < n; ++i)
 1908     {
 1909         vstrActionNames.push_back(m_pCommonData->getAllTransf()[v[i]]->getActionName());
 1910     }
 1911     m_settings.saveVector("visibleTransf", vstrActionNames);
 1912 }
 1913 
 1914 
 1915 void MainFormDlgImpl::loadVisibleTransf()
 1916 {
 1917     bool bErr;
 1918     vector<string> vstrNames (m_settings.loadVector("visibleTransf", bErr)); //ttt2 check bErr
 1919     vector<int> v;
 1920     const vector<Transformation*>& u (m_pCommonData->getAllTransf());
 1921     int m (cSize(u));
 1922     for (int i = 0, n = cSize(vstrNames); i < n; ++i)
 1923     {
 1924         string strName (vstrNames[i]);
 1925         int j (0);
 1926         for (; j < m; ++j)
 1927         {
 1928             if (u[j]->getActionName() == strName)
 1929             {
 1930                 v.push_back(j);
 1931                 break;
 1932             }
 1933         }
 1934 
 1935         if (j == m)
 1936         {
 1937             showWarning(this, tr("Error setting up visible transformations"), tr("Couldn't find a transformation with the name \"%1\". The program will proceed, but you should review the visible transformations list.").arg(convStr(strName)));
 1938         }
 1939     }
 1940 
 1941     m_pCommonData->setVisibleTransf(v);
 1942 }
 1943 
 1944 
 1945 
 1946 void MainFormDlgImpl::saveExternalTools()
 1947 {
 1948     vector<string> vstrExternalTools;
 1949     for (int i = 0, n = cSize(m_pCommonData->m_vExternalToolInfos); i < n; ++i)
 1950     {
 1951         vstrExternalTools.push_back(m_pCommonData->m_vExternalToolInfos[i].asString());
 1952     }
 1953     m_settings.saveVector("externalTools", vstrExternalTools);
 1954 }
 1955 
 1956 
 1957 void MainFormDlgImpl::loadExternalTools()
 1958 {
 1959     bool bErr;
 1960     vector<string> vstrExternalTools (m_settings.loadVector("externalTools", bErr)); //ttt2 check bErr
 1961     m_pCommonData->m_vExternalToolInfos.clear();
 1962     for (int i = 0, n = cSize(vstrExternalTools); i < n; ++i)
 1963     {
 1964         try
 1965         {
 1966             m_pCommonData->m_vExternalToolInfos.push_back(ExternalToolInfo(vstrExternalTools[i]));
 1967         }
 1968         catch (const ExternalToolInfo::InvalidExternalToolInfo&)
 1969         {
 1970             showWarning(this, tr("Error setting up external tools"), tr("Unable to parse \"%1\". The program will proceed, but you should review the external tools list.").arg(convStr(vstrExternalTools[i])));
 1971         }
 1972     }
 1973 }
 1974 
 1975 
 1976 
 1977 
 1978 
 1979 
 1980 //ttt2 keyboard shortcuts: next, prev, ... ;
 1981 //ttt3 set focus on some edit box when changing tabs;
 1982 
 1983 
 1984 //ttt2 perhaps add "whatsThis" texts
 1985 //ttt2 perhaps add "what's this" tips
 1986 
 1987 
 1988 
 1989 void MainFormDlgImpl::on_m_pTransformB_clicked() //ttt2 an alternative is to use QToolButton::setMenu(); see if that is really simpler
 1990 {
 1991     showBackupWarn();
 1992     showSelWarn();
 1993 
 1994     ModifInfoMenu menu;
 1995     vector<QAction*> vpAct;
 1996 
 1997     const vector<Transformation*>& vpTransf (m_pCommonData->getAllTransf());
 1998     const vector<int>& vnVisualNdx (m_pCommonData->getVisibleTransf());
 1999 
 2000     for (int i = 0, n = cSize(vnVisualNdx); i < n; ++i)
 2001     {
 2002         Transformation* pTransf (vpTransf.at(vnVisualNdx[i]));
 2003         QAction* pAct (new QAction(pTransf->getVisibleActionName(), &menu));
 2004         pAct->setToolTip(makeMultiline(Transformation::tr(pTransf->getDescription())));
 2005 
 2006         //connect(pAct, SIGNAL(triggered()), this, SLOT(onExecTransform(i))); // !!! Qt doesn't seem to support parameter binding
 2007         menu.addAction(pAct);
 2008         vpAct.push_back(pAct);
 2009     }
 2010 
 2011     connect(&menu, SIGNAL(hovered(QAction*)), this, SLOT(onMenuHovered(QAction*)));
 2012 
 2013     QAction* p (menu.exec(m_pTransformB->mapToGlobal(QPoint(0, m_pTransformB->height()))));
 2014     if (0 != p)
 2015     {
 2016         int nIndex (std::find(vpAct.begin(), vpAct.end(), p) - vpAct.begin());
 2017         vector<Transformation*> v;
 2018         v.push_back(vpTransf.at(vnVisualNdx[nIndex]));
 2019         transform(v, 0 == (Qt::ShiftModifier & menu.getModifiers()) ? ALL : SELECTED);
 2020     }
 2021 }
 2022 
 2023 
 2024 void MainFormDlgImpl::onMenuHovered(QAction* pAction)
 2025 {
 2026     //QToolTip::showText(QCursor::pos(), ""); // this was needed initially but at some time tooltips on menus stopped working (e.g. in 11.4) and commenting this out fixed the problem
 2027     QToolTip::showText(QCursor::pos(), pAction->toolTip());
 2028     // see http://www.mail-archive.com/pyqt@riverbankcomputing.com/msg17214.html and http://www.mail-archive.com/pyqt@riverbankcomputing.com/msg17245.html ; apparently there's some inconsistency in when the menus are shown
 2029 }
 2030 
 2031 
 2032 void MainFormDlgImpl::on_m_pNormalizeB_clicked()
 2033 {
 2034     showBackupWarn();
 2035     showSelWarn();
 2036 
 2037     bool bSel (0 != (Qt::ShiftModifier & m_pModifNormalizeB->getModifiers()));
 2038     const deque<const Mp3Handler*>& vpHndlr (bSel ? m_pCommonData->getSelHandlers() : m_pCommonData->getViewHandlers());
 2039 
 2040     QStringList l;
 2041     for (int i = 0, n = cSize(vpHndlr); i < n; ++i)
 2042     {
 2043         l << vpHndlr[i]->getUiName();
 2044     }
 2045     if (l.isEmpty())
 2046     {
 2047         showCritical(this, tr("Error"), tr("There are no files to normalize."));
 2048         return;
 2049     }
 2050 
 2051     int nIssueCount (0);
 2052     QString qstrWarn;
 2053     if (bSel && cSize(m_pCommonData->getViewHandlers()) != cSize(m_pCommonData->getSelHandlers()))
 2054     {
 2055         ++nIssueCount;
 2056         qstrWarn += "\n- " + tr("you are requesting to normalize only some of the files");
 2057     }
 2058 
 2059     if (CommonData::FOLDER != m_pCommonData->getViewMode())
 2060     {
 2061         ++nIssueCount;
 2062         qstrWarn += "\n- " + tr("the \"Album\" mode is not selected");
 2063     }
 2064 
 2065     if (m_pCommonData->m_filter.isNoteEnabled() || m_pCommonData->m_filter.isDirEnabled())
 2066     {
 2067         ++nIssueCount;
 2068         if (m_pCommonData->m_filter.isNoteEnabled() && m_pCommonData->m_filter.isDirEnabled())
 2069         {
 2070             qstrWarn += "\n- " + tr("filters are applied");
 2071         }
 2072         else
 2073         {
 2074             qstrWarn += "\n- " + tr("a filter is applied");
 2075         }
 2076     }
 2077 
 2078     if (cSize(vpHndlr) > 50)
 2079     {
 2080         ++nIssueCount;
 2081         qstrWarn += "\n- " + tr("the normalization will process more than 50 files, which is more than what an album usually has");
 2082     }
 2083 
 2084     if (0 != nIssueCount)
 2085     {
 2086         QString s;
 2087         if (1 == nIssueCount)
 2088         {
 2089             qstrWarn.remove(0, 3);
 2090             s = tr("Normalization should process one whole album at a time, so it should only be run in \"Album\" mode, when no filters are active, and it should be applied to all the files in that album. But in the current case %1.").arg(qstrWarn);
 2091         }
 2092         else
 2093         {
 2094             s = tr("Normalization should process one whole album at a time, so it should only be run in \"Album\" mode, when no filters are active, and it should be applied to all the files in that album. But in the current case  there are some issues:\n%1").arg(qstrWarn);
 2095         }
 2096 
 2097         int k (showMessage(this, QMessageBox::Warning, 1, 1, tr("Warning"), s + "\n\n" + tr("Normalize anyway?"), tr("Normalize"), tr("Cancel")));
 2098 
 2099         if (k != 0)
 2100         {
 2101             return;
 2102         }
 2103     }
 2104 
 2105     if (0 == nIssueCount)
 2106     {
 2107         if (0 != showMessage(this, QMessageBox::Question, 1, 1, tr("Confirm"), tr("Normalize all the files in the current album? (Note that normalization is done \"in place\", by an external program, so it doesn't care about the transformation settings for original and modified files.)"), tr("Normalize"), tr("Cancel")))
 2108         {
 2109             return;
 2110         }
 2111     }
 2112 
 2113     ExternalToolDlgImpl dlg (this, m_pCommonData->m_bKeepNormWndOpen, m_settings, m_pCommonData, "Normalize", "230_normalize.html");
 2114     dlg.run(convStr(m_pCommonData->m_strNormalizeCmd), l);
 2115 
 2116     reload(bSel, FORCE);
 2117 }
 2118 
 2119 
 2120 
 2121 
 2122 
 2123 void MainFormDlgImpl::on_m_pReloadB_clicked()
 2124 {
 2125     bool bSel (0 != (Qt::ShiftModifier & m_pModifReloadB->getModifiers()));
 2126     bool bForce (0 != (Qt::ControlModifier & m_pModifReloadB->getModifiers()));
 2127     reload(bSel, bForce);
 2128 }
 2129 
 2130 
 2131 
 2132 void MainFormDlgImpl::reload(bool bSelOnly, bool bForce)
 2133 {
 2134     auto_ptr<FileEnumerator> ptr;
 2135     FileEnumerator* pEnum;
 2136     const deque<const Mp3Handler*>* pvpExisting;
 2137     bool bNoteFlt (m_pCommonData->m_filter.isNoteEnabled());
 2138     bool bDirFlt (m_pCommonData->m_filter.isDirEnabled());
 2139 
 2140     if (bSelOnly || bNoteFlt || bDirFlt)
 2141     {
 2142         vector<string> v;
 2143         const deque<const Mp3Handler*>& vpHndlr (bSelOnly ? m_pCommonData->getSelHandlers() : m_pCommonData->getViewHandlers());
 2144         for (int i = 0, n = cSize(vpHndlr); i < n; ++i)
 2145         {
 2146             v.push_back(vpHndlr[i]->getName());
 2147         }
 2148 
 2149         pEnum = new ListEnumerator(v);
 2150         ptr.reset(pEnum);
 2151     }
 2152     else
 2153     {
 2154         CB_ASSERT (!m_pCommonData->m_filter.isNoteEnabled() && !m_pCommonData->m_filter.isDirEnabled());
 2155 
 2156         const deque<const Mp3Handler*>& vpHndlr (m_pCommonData->getViewHandlers());
 2157 
 2158         switch (m_pCommonData->getViewMode())
 2159         {
 2160         case CommonData::ALL:
 2161             pEnum = &m_pCommonData->m_dirTreeEnum;
 2162             break;
 2163 
 2164         case CommonData::FOLDER:
 2165             pEnum = new ListEnumerator(vpHndlr[0]->getDir());
 2166             ptr.reset(pEnum);
 2167             break;
 2168 
 2169         case CommonData::FILE:
 2170             {
 2171                 vector<string> v;
 2172                 v.push_back(vpHndlr[0]->getName());
 2173                 pEnum = new ListEnumerator(v);
 2174                 ptr.reset(pEnum);
 2175             }
 2176             break;
 2177 
 2178         default:
 2179             CB_ASSERT (false);
 2180         }
 2181     }
 2182 
 2183     pvpExisting = &(bSelOnly ? m_pCommonData->getSelHandlers() : m_pCommonData->getViewHandlers());
 2184 
 2185     scan(*pEnum, bForce, *pvpExisting, CommonData::CURRENT | CommonData::SEL);
 2186 
 2187     if (bNoteFlt || bDirFlt)
 2188     {
 2189         m_pCommonData->m_filter.disableAll();
 2190         m_pCommonData->m_filter.restoreAll();
 2191 
 2192         if (!m_pCommonData->m_filter.isNoteEnabled() && !m_pCommonData->m_filter.isDirEnabled() && !bSelOnly)
 2193         { // the filter got removed, because nothing matched; so wee need to repeat; (otherwise the user has to press twice: once to remove the filter and a second time to see what's new)
 2194             reload(false, bForce);
 2195         }
 2196     }
 2197 }
 2198 
 2199 
 2200 
 2201 void MainFormDlgImpl::applyCustomTransf(int k)
 2202 {
 2203     showBackupWarn();
 2204     showSelWarn();
 2205 
 2206     vector<Transformation*> v;
 2207     for (int i = 0, n = cSize(m_pCommonData->getCustomTransf()[k]); i < n; ++i)
 2208     {
 2209         v.push_back(m_pCommonData->getAllTransf()[m_pCommonData->getCustomTransf()[k][i]]);
 2210     }
 2211     transform(v, 0 == (Qt::ShiftModifier & m_vpTransfButtons[k]->getModifiers()) ? ALL : SELECTED);
 2212 }
 2213 
 2214 
 2215 
 2216 
 2217 // The file list is updated in the sense that if a file was changed or removed, this is reflected in the UI. However, new files are not seen. For one thing, rebuilding a 10000-file list takes a lot of time. OTOH perhaps just the new files could be added. Also, perhaps the user could be asked about updating the list.
 2218 //ttt2 review
 2219 void MainFormDlgImpl::transform(std::vector<Transformation*>& vpTransf, Subset eSubset)
 2220 {
 2221     if (m_pCommonData->getViewHandlers().empty())
 2222     {
 2223         showWarning(this, tr("Warning"), tr("The file list is empty, therefore no transformations can be applied.\n\nExiting ..."));
 2224         return;
 2225     }
 2226 
 2227     QString qstrListInfo;
 2228     switch (eSubset)
 2229     {
 2230         case ALL:
 2231         {
 2232             int nCnt (cSize(m_pCommonData->getViewHandlers()));
 2233             if (nCnt < 10)
 2234             {
 2235                 qstrListInfo = tr("all the files shown in the file list");
 2236             }
 2237             else
 2238             {
 2239                 qstrListInfo = tr("all %1 files shown in the file list").arg(nCnt);
 2240             }
 2241 
 2242             break;
 2243         }
 2244 
 2245     case SELECTED:
 2246         {
 2247             int nCnt (cSize(m_pCommonData->getSelHandlers()));
 2248             if (0 == nCnt)
 2249             {
 2250                 showWarning(this, tr("Warning"), tr("No file is selected, therefore no transformations can be applied.\n\nExiting ..."));
 2251                 return;
 2252             }
 2253             else if (1 == nCnt)
 2254             {
 2255                 qstrListInfo = "\"" + convStr(m_pCommonData->getSelHandlers()[0]->getShortName()) + "\"";
 2256             }
 2257             else if (2 == nCnt)
 2258             {
 2259                 qstrListInfo = "\"" + convStr(m_pCommonData->getSelHandlers()[0]->getShortName()) + "\" " + tr("and the other selected file");
 2260             }
 2261             else
 2262             {
 2263                 qstrListInfo = "\"" + convStr(m_pCommonData->getSelHandlers()[0]->getShortName()) + "\" " + tr("and the other %1 selected files").arg(nCnt - 1);
 2264             }
 2265             break;
 2266         }
 2267 
 2268     case CURRENT:
 2269         {
 2270             CB_ASSERT (0 != m_pCommonData->getCrtMp3Handler());
 2271             qstrListInfo = "\"" + convStr(m_pCommonData->getCrtMp3Handler()->getShortName()) + "\"";
 2272             break;
 2273         }
 2274 
 2275     default:
 2276         CB_ASSERT (false);
 2277     }
 2278 
 2279 
 2280     if (!m_pCommonData->m_bToldAboutXingRebuild && !s_bToldAboutXingRebuildInCrtRun)
 2281     {
 2282         for (int i = 0; i < cSize(vpTransf); ++i)
 2283         {
 2284             if (dynamic_cast<VbrRebuilder*>(vpTransf[i]) != 0)
 2285             {
 2286                 s_bToldAboutXingRebuildInCrtRun = true;
 2287 
 2288                 int res = HtmlMsg::msg(this, 0, 1, &m_pCommonData->m_bToldAboutXingRebuild, HtmlMsg::DEFAULT, tr("Confirm"), tr("<p>Rebuilding VBR data in Xing / LAME headers can destroy gapless playing information, causing albums that are supposed to be gapless to be played with short gaps between tracks.</p><p>Note that this shouldn't matter for regular, non-gapless, albums.</p><p>Proceed?</p>"), 750, 300, tr("&Yes"), tr("&No"));
 2289 
 2290                 if (m_pCommonData->m_bToldAboutXingRebuild)
 2291                 {
 2292                     m_settings.saveMiscConfigSettings(m_pCommonData);
 2293                 }
 2294 
 2295                 if (res != 0) { return; }
 2296                 break;
 2297             }
 2298         }
 2299     }
 2300     // ttt1 not sure about VbrRepairer, which also has the potential to destroy gapless info; probably better without it
 2301 
 2302     if (!m_pCommonData->m_bToldAboutXingRemove && !s_bToldAboutXingRemoveInCrtRun)
 2303     {
 2304         for (int i = 0; i < cSize(vpTransf); ++i)
 2305         {
 2306             if (dynamic_cast<XingRemover*>(vpTransf[i]) != 0 || dynamic_cast<LameRemover*>(vpTransf[i]) != 0 || dynamic_cast<MismatchedXingRemover*>(vpTransf[i]) != 0 || dynamic_cast<XingLameCbrRemover*>(vpTransf[i]) != 0)
 2307             {
 2308                 s_bToldAboutXingRemoveInCrtRun = true;
 2309 
 2310                 int res = HtmlMsg::msg(this, 0, 1, &m_pCommonData->m_bToldAboutXingRemove, HtmlMsg::DEFAULT, tr("Confirm"), tr("<p>Removing Xing / LAME headers destroys gapless playing information, causing albums that are supposed to be gapless to be played with short gaps between tracks.</p><p>Note that this shouldn't matter for regular, non-gapless, albums.</p><p>Proceed?</p>"), 750, 300, tr("&Yes"), tr("&No"));
 2311 
 2312                 if (m_pCommonData->m_bToldAboutXingRemove)
 2313                 {
 2314                     m_settings.saveMiscConfigSettings(m_pCommonData);
 2315                 }
 2316 
 2317                 if (res != 0) { return; }
 2318                 break;
 2319             }
 2320         }
 2321     }
 2322 
 2323 
 2324 
 2325     QString qstrConf;
 2326     if (vpTransf.empty())
 2327     {
 2328         if (TransfConfig::Options::UPO_DONT_CHG == m_transfConfig.m_options.m_eUnprocOrigChange)
 2329         {
 2330             showWarning(this, tr("Warning"), tr("The transformation list is empty.\n\nBased on the configuration, it is possible for changes to the files in the list to be performed, even in this case (the files may still be moved, renamed or erased). However, the current settings are to leave the original files unchanged, so currently there's no point in applying an empty transformation list.\n\nExiting ..."));
 2331             return;
 2332         }
 2333         qstrConf = tr("Apply an empty transformation list to all the files shown in the file list? (Note that even if no transformations are performed, the files may still be moved, renamed or erased, based on the current settings.)");
 2334     }
 2335     else if (1 == cSize(vpTransf))
 2336     {
 2337         qstrConf = tr("Apply transformation \"%1\" to %2?").arg(vpTransf[0]->getVisibleActionName()).arg(qstrListInfo);
 2338     }
 2339     else
 2340     {
 2341         qstrConf = tr("Apply the following transformations to %1?").arg(qstrListInfo);
 2342         for (int i = 0, n = cSize(vpTransf); i < n; ++i)
 2343         {
 2344             qstrConf += "\n      ";
 2345             qstrConf += vpTransf[i]->getVisibleActionName();
 2346         }
 2347     }
 2348 
 2349     {
 2350         const char* aOrig[] = {
 2351             QT_TR_NOOP("don't change"),
 2352             QT_TR_NOOP("erase"),
 2353             QT_TR_NOOP("move"),
 2354             QT_TR_NOOP("move"),
 2355             QT_TR_NOOP("rename"),
 2356             QT_TR_NOOP("move if destination doesn't exist") };
 2357 
 2358         if ((m_transfConfig.m_options.m_eProcOrigChange != TransfConfig::Options::PO_MOVE_OR_ERASE && m_transfConfig.m_options.m_eProcOrigChange != TransfConfig::Options::PO_ERASE) || m_transfConfig.m_options.m_eUnprocOrigChange != TransfConfig::Options::UPO_DONT_CHG) //ttt2 improve
 2359         {
 2360             qstrConf += "\n\n" + tr("Actions to be taken:");
 2361 
 2362             if (!vpTransf.empty())
 2363             {
 2364                 qstrConf += "\n- " + tr("original file that has been transformed: %1").arg(tr(aOrig[m_transfConfig.m_options.m_eProcOrigChange]));
 2365             }
 2366 
 2367             qstrConf += "\n- " + tr("original file that has not been transformed: %1").arg(tr(aOrig[m_transfConfig.m_options.m_eUnprocOrigChange]));
 2368         }
 2369     }
 2370 
 2371     if (showMessage(this, QMessageBox::Question, 1, 1, tr("Confirm"), qstrConf, tr("&Yes"), tr("&No")) != 0) { return; }
 2372 
 2373     deque<const Mp3Handler*> vpCrt;
 2374     const deque<const Mp3Handler*>* pvpHandlers;
 2375     switch (eSubset)
 2376     {
 2377     case SELECTED: pvpHandlers = &m_pCommonData->getSelHandlers(); break;
 2378     case ALL: pvpHandlers = &m_pCommonData->getViewHandlers(); break;
 2379     case CURRENT: vpCrt.push_back(m_pCommonData->getCrtMp3Handler()); pvpHandlers = &vpCrt; break;
 2380     default: CB_ASSERT (false);
 2381     }
 2382 
 2383     ::transform(*pvpHandlers, vpTransf, convStr(tr("Applying transformations to MP3 files")), this, m_pCommonData, m_transfConfig);
 2384 }
 2385 
 2386 
 2387 
 2388 
 2389 
 2390 
 2391 //=====================================================================================================================
 2392 //=====================================================================================================================
 2393 //=====================================================================================================================
 2394 
 2395 
 2396 void MainFormDlgImpl::on_m_pConfigB_clicked()
 2397 {
 2398     ConfigDlgImpl dlg (m_transfConfig, m_pCommonData, this, ConfigDlgImpl::ALL_TABS);
 2399     string s (m_pCommonData->getCrtName());
 2400 
 2401     if (dlg.run())
 2402     {
 2403         m_settings.saveMiscConfigSettings(m_pCommonData);
 2404         m_settings.saveScanAtStartup(m_pCommonData->m_bScanAtStartup);
 2405         m_settings.saveTransfConfig(m_transfConfig); // transformation
 2406 
 2407         updateUi(s);
 2408     }
 2409 }
 2410 
 2411 
 2412 void MainFormDlgImpl::setTransfTooltip(int k)
 2413 {
 2414     QString s1 (tr("Apply custom transformation list #%1\n").arg(k + 1));
 2415     QString s2;
 2416     for (int i = 0, n = cSize(m_pCommonData->getCustomTransf()[k]); i < n; ++i)
 2417     {
 2418         s2 += "    ";
 2419         s2 += m_pCommonData->getAllTransf()[m_pCommonData->getCustomTransf()[k][i]]->getVisibleActionName();
 2420         if (i < n - 1) { s2 += "\n"; }
 2421     }
 2422     if (s2.isEmpty()) { s2 = tr("   <empty list>\n\n(you can edit the list in the Settings dialog)"); } // well; at startup it will get repopulated, so the only way for this to be empty is if it was configured like this in the current session
 2423     m_vpTransfButtons[k]->setToolTip(s1 + s2);
 2424 }
 2425 
 2426 
 2427 void MainFormDlgImpl::on_m_pModeAllB_clicked()
 2428 {
 2429     m_pCommonData->setViewMode(CommonData::ALL, m_pCommonData->getCrtMp3Handler());
 2430     m_pFilesG->setFocus();
 2431 }
 2432 
 2433 
 2434 void MainFormDlgImpl::on_m_pModeAlbumB_clicked()
 2435 {
 2436     m_pCommonData->setViewMode(CommonData::FOLDER, m_pCommonData->getCrtMp3Handler());
 2437     m_pFilesG->setFocus();
 2438 }
 2439 
 2440 
 2441 void MainFormDlgImpl::on_m_pModeSongB_clicked()
 2442 {
 2443     m_pCommonData->setViewMode(CommonData::FILE, m_pCommonData->getCrtMp3Handler());
 2444     m_pFilesG->setFocus();
 2445 }
 2446 
 2447 
 2448 void MainFormDlgImpl::on_m_pPrevB_clicked()
 2449 {
 2450 //LAST_STEP("MainFormDlgImpl::on_m_pPrevB_clicked");
 2451 //CB_ASSERT("345" == "ab");
 2452 //traceLastStep("tsterr", 0); char* p (0); *p = 11;
 2453 //throw 1;
 2454 //int x (2), y (3); CB_ASSERT(x >= y);
 2455 
 2456     m_pCommonData->previous();
 2457     //updateWidgets();
 2458     m_pFilesG->setFocus();
 2459 }
 2460 
 2461 void MainFormDlgImpl::on_m_pNextB_clicked()
 2462 {
 2463     m_pCommonData->next();
 2464     //updateWidgets();
 2465     m_pFilesG->setFocus();
 2466 }
 2467 
 2468 
 2469 void MainFormDlgImpl::on_m_pTagEdtB_clicked()
 2470 {
 2471     showBackupWarn();
 2472 
 2473     if (m_pCommonData->getViewHandlers().empty())
 2474     {
 2475         showCritical(this, tr("Error"), tr("The file list is empty. You need to populate it before opening the tag editor."));
 2476         return;
 2477     }
 2478 
 2479     m_pCommonData->setSongInCrtAlbum();
 2480 
 2481     bool bDataSaved (false);
 2482     TagEditorDlgImpl dlg (this, m_pCommonData, m_transfConfig, bDataSaved);
 2483 
 2484     //if (QDialog::Accepted == dlg.exec())
 2485     bool bToldAboutPatterns (m_pCommonData->m_bToldAboutPatterns);
 2486     string strCrt (dlg.run());
 2487     if (!bToldAboutPatterns && m_pCommonData->m_bToldAboutPatterns)
 2488     {
 2489         m_settings.saveMiscConfigSettings(m_pCommonData);
 2490     }
 2491 
 2492     updateUi(strCrt); // needed because the tag editor might have called the config and changed things; it would be nicer to send a signal when config changes, but IIRC in Qt 4.3.1 resizing things in a dialog that opened another one doesn't work very well; (see also TagEditorDlgImpl::on_m_pQueryDiscogsB_clicked())
 2493 
 2494     if (m_pCommonData->useFastSave())
 2495     {
 2496         if (bDataSaved) { fullReload(DONT_FORCE); }
 2497     }
 2498     else
 2499     {
 2500         emit tagEditorClosed();
 2501     }
 2502 }
 2503 
 2504 
 2505 void MainFormDlgImpl::on_m_pRenameFilesB_clicked()
 2506 {
 2507     bool bUseCrtView (0 != (Qt::ControlModifier & m_pModifRenameFilesB->getModifiers()));
 2508 
 2509     if (m_pCommonData->getViewHandlers().empty())
 2510     {
 2511         showCritical(this, tr("Error"), tr("The file list is empty. You need to populate it before opening the file rename tool."));
 2512         return;
 2513     }
 2514 
 2515     m_pCommonData->setSongInCrtAlbum();
 2516 
 2517     FileRenamerDlgImpl dlg (this, m_pCommonData, bUseCrtView);
 2518 
 2519     //if (QDialog::Accepted == dlg.exec())
 2520     string strCrt (dlg.run());
 2521 
 2522     updateUi(strCrt);
 2523 }
 2524 
 2525 
 2526 void MainFormDlgImpl::updateUi(const string& strCrt) // strCrt may be empty
 2527 {
 2528     saveIgnored();
 2529 
 2530     { // custom transf
 2531         for (int i = 0; i < CUSTOM_TRANSF_CNT; ++i) { saveCustomTransf(i); }
 2532         setTransfTooltips();
 2533     }
 2534 
 2535     saveVisibleTransf();
 2536     saveExternalTools();
 2537     bool bShowSessions = m_pCommonData->m_bShowSessions;
 2538     if (!bShowSessions)
 2539     {
 2540         GlobalSettings st;
 2541         bShowSessions = st.getSessionCount() > 1;
 2542     }
 2543 
 2544     if (m_pCommonData->m_bShowExport || bShowSessions)
 2545     {
 2546         m_pOptBtn1W->show();
 2547     }
 2548     else
 2549     {
 2550         m_pOptBtn1W->hide();
 2551     }
 2552 
 2553 
 2554     if (m_pCommonData->m_bShowExport)
 2555     {
 2556         m_pExportB->show();
 2557         m_pExportB->parentWidget()->layout()->update(); // it is probably a Qt bug the fact that this is needed; should have been automatic;
 2558     }
 2559     else
 2560     {
 2561         m_pExportB->hide();
 2562     }
 2563 
 2564 
 2565     if (m_pCommonData->m_bShowDebug)
 2566     {
 2567         m_pDebugB->show();
 2568         m_pDebugB->parentWidget()->layout()->update(); // it is probably a Qt bug the fact that this is needed; should have been automatic;
 2569     }
 2570     else
 2571     {
 2572         m_pDebugB->hide();
 2573     }
 2574 
 2575 
 2576     if (bShowSessions)
 2577     {
 2578         m_pSessionsB->show();
 2579         m_pSessionsB->parentWidget()->layout()->update(); // it is probably a Qt bug the fact that this is needed; should have been automatic;
 2580     }
 2581     else
 2582     {
 2583         m_pSessionsB->hide();
 2584     }
 2585 
 2586 
 2587     resizeIcons();
 2588     m_pCommonData->updateWidgets(strCrt); // needed for filters and unique notes
 2589 }
 2590 
 2591 
 2592 void MainFormDlgImpl::on_m_pDebugB_clicked()
 2593 {
 2594     DebugDlgImpl dlg (this, m_pCommonData);
 2595     dlg.run();
 2596     m_settings.saveMiscConfigSettings(m_pCommonData);
 2597 }
 2598 
 2599 
 2600 
 2601 void MainFormDlgImpl::on_m_pExportB_clicked()
 2602 {
 2603     //TranslatorHandler& hndl (TranslatorHandler::getGlobalTranslator());
 2604     //hndl.setTranslation(hndl.getTranslations().back());
 2605     //retranslateUi(this);
 2606 
 2607     ExportDlgImpl dlg (this);
 2608     dlg.run(); //ttt2 perhaps use ModifInfoToolButton
 2609     m_settings.saveMiscConfigSettings(m_pCommonData);
 2610 }
 2611 
 2612 
 2613 
 2614 
 2615 
 2616 
 2617 void MainFormDlgImpl::on_m_pAboutB_clicked()
 2618 {
 2619     AboutDlgImpl dlg (this);
 2620     dlg.exec();
 2621 }
 2622 
 2623 
 2624 class AddrRemover : public GenericRemover
 2625 {
 2626     /*override*/ bool matches(DataStream* p) const { return m_spToRemove.count(p) > 0; }
 2627 public:
 2628     /*override*/ const char* getActionName() const { return getClassName(); }
 2629     /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "Removes selected streams."); }
 2630 
 2631     static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Remove selected stream(s)"); }
 2632 
 2633     set<DataStream*> m_spToRemove;
 2634 };
 2635 
 2636 
 2637 
 2638 //void MainFormDlgImpl::onStreamsGKeyPressed(int nKey)
 2639 /*override*/ bool MainFormDlgImpl::eventFilter(QObject* pObj, QEvent* pEvent)
 2640 {
 2641 //qDebug("type %d", pEvent->type());
 2642 //if (pObj == m_pFilesG) qDebug("type %d", pEvent->type());
 2643     QKeyEvent* pKeyEvent (dynamic_cast<QKeyEvent*>(pEvent));
 2644     int nKey (0 == pKeyEvent ? 0 : pKeyEvent->key());
 2645     if (m_pStreamsG == pObj && 0 != pKeyEvent && Qt::Key_Delete == nKey && QEvent::ShortcutOverride == pKeyEvent->type())
 2646     {
 2647         showBackupWarn();
 2648         //showSelWarn();
 2649 
 2650     //qDebug("type %d", pKeyEvent->type());
 2651         QItemSelectionModel* pSelModel (m_pStreamsG->selectionModel());
 2652         QModelIndexList lstSel (pSelModel->selection().indexes());
 2653 
 2654         set<int> sStreams;
 2655         for (QModelIndexList::iterator it = lstSel.begin(), end = lstSel.end(); it != end; ++it)
 2656         {
 2657             sStreams.insert(it->row());
 2658         }
 2659         if (sStreams.empty()) { return true; }
 2660 
 2661         AddrRemover rmv;
 2662         for (set<int>::iterator it = sStreams.begin(), end = sStreams.end(); it != end; ++it)
 2663         {
 2664             rmv.m_spToRemove.insert(m_pCommonData->getCrtStreams()[*it]);
 2665         }
 2666 
 2667         vector<Transformation*> v;
 2668         v.push_back(&rmv);
 2669         transform(v, CURRENT);
 2670         return true;
 2671     }
 2672     else if (m_pFilesG == pObj && 0 != pKeyEvent && Qt::Key_Delete == nKey && QEvent::ShortcutOverride == pKeyEvent->type())
 2673     {
 2674         const deque<const Mp3Handler*>& vpSelHandlers (m_pCommonData->getSelHandlers());
 2675         if (askConfirm(vpSelHandlers, tr("Delete %1?")))
 2676         {
 2677             for (int i = 0; i < cSize(vpSelHandlers); ++i)
 2678             {
 2679                 try
 2680                 {
 2681                     deleteFile(vpSelHandlers[i]->getName());
 2682                 }
 2683                 catch (const CannotDeleteFile&)
 2684                 {
 2685                     showCritical(this, tr("Error"), tr("Cannot delete file %1").arg(convStr(vpSelHandlers[i]->getName())));
 2686                     break;
 2687                 }
 2688             }
 2689             reload(IGNORE_SEL, DONT_FORCE);
 2690         }
 2691 
 2692         return true;
 2693     }
 2694     else if (pObj == m_pFilesG)
 2695     {
 2696         //qDebug("type %d", pEvent->type());
 2697         QContextMenuEvent* pCtx (dynamic_cast<QContextMenuEvent*>(pEvent));
 2698         if (0 != pCtx)
 2699         {
 2700             m_nGlobalX = pCtx->globalX();
 2701             m_nGlobalY = pCtx->globalY();
 2702             QTimer::singleShot(1, this, SLOT(onMainGridRightClick()));
 2703         }
 2704     }
 2705     return QDialog::eventFilter(pObj, pEvent);
 2706 }
 2707 
 2708 
 2709 void MainFormDlgImpl::on_m_pViewFileInfoB_clicked()
 2710 {
 2711     m_pLowerHalfLayout->setCurrentWidget(m_pFileInfoTab);
 2712     m_pViewFileInfoB->setChecked(true); //ttt2 use autoExclusive instead
 2713     m_pViewAllNotesB->setChecked(false);
 2714     m_pViewTagDetailsB->setChecked(false);
 2715     m_pNotesG->resizeRowsToContents();
 2716     m_pStreamsG->resizeRowsToContents();
 2717 }
 2718 
 2719 
 2720 void MainFormDlgImpl::on_m_pViewAllNotesB_clicked()
 2721 {
 2722     m_pLowerHalfLayout->setCurrentWidget(m_pAllNotesTab);
 2723     m_pViewFileInfoB->setChecked(false);
 2724     m_pViewAllNotesB->setChecked(true);
 2725     m_pViewTagDetailsB->setChecked(false);
 2726     m_pUniqueNotesG->resizeRowsToContents();
 2727 }
 2728 
 2729 
 2730 void MainFormDlgImpl::on_m_pViewTagDetailsB_clicked()
 2731 {
 2732     m_pLowerHalfLayout->setCurrentWidget(m_pTagDetailsTab);
 2733     m_pViewFileInfoB->setChecked(false);
 2734     m_pViewAllNotesB->setChecked(false);
 2735     m_pViewTagDetailsB->setChecked(true);
 2736 
 2737     onCrtFileChanged(); // to populate m_pTagDetailsW
 2738 }
 2739 
 2740 
 2741 void MainFormDlgImpl::resizeIcons()
 2742 {
 2743     vector<QToolButton*> v;
 2744     v.push_back(m_pScanB);
 2745     v.push_back(m_pSessionsB);
 2746     v.push_back(m_pNoteFilterB);
 2747     v.push_back(m_pDirFilterB);
 2748     v.push_back(m_pModeAllB);
 2749     v.push_back(m_pModeAlbumB);
 2750     v.push_back(m_pModeSongB);
 2751     v.push_back(m_pPrevB);
 2752     v.push_back(m_pNextB);
 2753     v.push_back(m_pTransformB);
 2754     v.push_back(m_pCustomTransform1B);
 2755     v.push_back(m_pCustomTransform2B);
 2756     v.push_back(m_pCustomTransform3B);
 2757     v.push_back(m_pCustomTransform4B);
 2758     v.push_back(m_pTagEdtB);
 2759     v.push_back(m_pNormalizeB);
 2760     v.push_back(m_pRenameFilesB);
 2761     v.push_back(m_pReloadB);
 2762     v.push_back(m_pConfigB);
 2763     v.push_back(m_pDebugB);
 2764     v.push_back(m_pAboutB);
 2765     v.push_back(m_pExportB);
 2766 
 2767     int k (m_pCommonData->m_nMainWndIconSize);
 2768     for (int i = 0, n = cSize(v); i < n; ++i)
 2769     {
 2770         QToolButton* p (v[i]);
 2771         p->setMaximumSize(k, k);
 2772         p->setMinimumSize(k, k);
 2773         p->setIconSize(QSize(k - 4, k - 4));
 2774     }
 2775 }
 2776 
 2777 
 2778 
 2779 void MainFormDlgImpl::on_m_pSessionsB_clicked()
 2780 {
 2781     closeEvent(0);
 2782     accept();
 2783 }
 2784 
 2785 
 2786 
 2787 
 2788 void MainFormDlgImpl::checkForNewVersion() // returns immediately; when the request completes it will send a signal
 2789 {
 2790     const int MIN_INTERVAL_BETWEEN_CHECKS (24); // hours
 2791 
 2792     if ("yes" != m_pCommonData->m_strCheckForNewVersions && "no" != m_pCommonData->m_strCheckForNewVersions)
 2793     {
 2794         int nRes (HtmlMsg::msg(this, 0, 0, 0, HtmlMsg::DEFAULT, tr("Info"),
 2795         "<p style=\"margin-bottom:1px; margin-top:12px; \">" + tr("MP3 Diags can check at startup if a new version of the program has been released. Here's how this is supposed to work:") +
 2796             "<ul>"
 2797                 "<li>" + tr("The check is done in the background, when the program starts, so there should be no performance penalties") + "</li>"
 2798                 "<li>" + tr("A notification message is displayed only if there's a new version available") + "</li>"
 2799                 "<li>" + tr("The update is manual. You are told that there is a new version and are offered links to see what's new, but nothing gets downloaded and / or installed automatically") + "</li>"
 2800                 "<li>" + tr("There is no System Tray process checking periodically for updates") + "</li>"
 2801                 "<li>" + tr("You can turn the notifications on and off from the configuration dialog") + "</li>"
 2802                 "<li>" + tr("If you restart the program within a day after a check, no new check is done") + "</li>"
 2803             "</ul>"
 2804         "</p>"
 2805         /*"
 2806         "<p style=\"margin-bottom:1px; margin-top:12px; \">QQQ</p>"
 2807         "<p style=\"margin-bottom:1px; margin-top:12px; \">QQQ</p>"
 2808         "<p style=\"margin-bottom:1px; margin-top:12px; \">QQQ</p>"
 2809         */
 2810         , 750, 300, tr("Disable checking for new versions"), tr("Enable checking for new versions")));
 2811         qDebug("ret %d", nRes);
 2812 
 2813         m_pCommonData->m_strCheckForNewVersions = (1 == nRes ? "yes" : "no");
 2814         m_settings.saveMiscConfigSettings(m_pCommonData);
 2815     }
 2816 
 2817     if ("yes" != m_pCommonData->m_strCheckForNewVersions) { return; }
 2818 
 2819     QDateTime t1 (QDateTime::currentDateTime());
 2820     t1 = t1.addSecs(-MIN_INTERVAL_BETWEEN_CHECKS*3600);
 2821     //qDebug("ini: %s, crt: %s", m_pCommonData->m_timeLastNewVerCheck.toString().toUtf8().constData(), t1.toString().toUtf8().constData());
 2822     if (t1 < m_pCommonData->m_timeLastNewVerCheck)
 2823     {
 2824         return;
 2825     }
 2826 
 2827     m_pCommonData->m_timeLastNewVerCheck = QDateTime(QDateTime::currentDateTime());
 2828 
 2829     m_pQHttp = new QHttp (this);
 2830 
 2831     connect(m_pQHttp, SIGNAL(requestFinished(int, bool)), this, SLOT(onNewVersionQueryFinished(int, bool)));
 2832     //connect(m_pQHttp, SIGNAL(responseHeaderReceived(const QHttpResponseHeader &)), this, SLOT(readResponseHeader(const QHttpResponseHeader&)));
 2833 
 2834     m_pQHttp->setHost("mp3diags.sourceforge.net");
 2835     //http://mp3diags.sourceforge.net/010_getting_the_program.html
 2836     QHttpRequestHeader header ("GET", QString(getWebBranch()) + "/version.txt"); header.setValue("Host", "mp3diags.sourceforge.net");
 2837     //QHttpRequestHeader header ("GET", "/mciobanu/mp3diags/010_getting_the_program.html"); header.setValue("Host", "web.clicknet.ro");
 2838     m_pQHttp->request(header);
 2839 }
 2840 //mp3diags.sourceforge.net/010_getting_the_program.html
 2841 //web.clicknet.ro/mciobanu/mp3diags/010_getting_the_program.html
 2842 
 2843 void MainFormDlgImpl::readResponseHeader(const QHttpResponseHeader& h)
 2844 {
 2845     qDebug("status %d", h.statusCode());
 2846 }
 2847 
 2848 
 2849 
 2850 
 2851 void MainFormDlgImpl::onNewVersionQueryFinished(int /*nId*/, bool bError)
 2852 {
 2853     if (bError)
 2854     { //ttt2 log something, tell user after a while
 2855         qDebug("HTTP error");
 2856         return;
 2857     }
 2858 
 2859     qint64 nAv (m_pQHttp->bytesAvailable());
 2860     if (0 == nAv)
 2861     {
 2862         // !!! JUST return; empty responses come for no identifiable requests and they should be just ignored
 2863         return;
 2864     }
 2865 
 2866     QByteArray b (m_pQHttp->readAll());
 2867     CB_ASSERT (b.size() == nAv);
 2868 
 2869     qDebug("ver: %s", b.constData());
 2870 
 2871     m_qstrNewVer = b;
 2872     m_qstrNewVer = m_qstrNewVer.trimmed();
 2873 
 2874     if (m_qstrNewVer.size() > 50)
 2875     { //ttt2
 2876         return; // most likely some error message
 2877     }
 2878 
 2879     if (getAppVer() == m_qstrNewVer)
 2880     {
 2881         return;
 2882     }
 2883 
 2884 #if 0
 2885     //const int WAIT (15); //crashes
 2886     //const int WAIT (12); // doesn't crash
 2887     const int WAIT (14); //crashes
 2888 #ifndef WIN32
 2889     qDebug("wait %d seconds", WAIT); sleep(WAIT); showCritical(this, "resume", "resume resume resume resume resume"); // crashes
 2890     //ttt2 see if this crash affects discogs dwnld //??? why doesn't crash if no message is shown?
 2891 #else
 2892     //Sleep(WAIT*1000); // Qt 4.5 doesn't seem to crash
 2893 #endif
 2894 #endif
 2895 
 2896     QTimer::singleShot(1, this, SLOT(onNewVersionQueryFinished2()));
 2897 }
 2898 
 2899 
 2900 void MainFormDlgImpl::onNewVersionQueryFinished2()
 2901 {
 2902     //QMessageBox::critical(this, "wait", "bb urv huervhuervhuerv erve rvhu ervervhuer vher vrhe rv evr ev erv erv");
 2903     //qDebug("ver: %s", b.constData());
 2904     //QMessageBox::critical(this, "resume", "bb urv huervhuervhuerv erve rvhu ervervhuer vher vrhe rv evr ev erv erv");
 2905     if (m_pCommonData->m_strDontTellAboutVer == convStr(m_qstrNewVer)) { return; }
 2906 
 2907     QString qstrMsg;
 2908 
 2909     qstrMsg = "<p style=\"margin-bottom:1px; margin-top:12px; \">" +
 2910             tr("Version %1 has been published. You are running %2. You can see what's new in %3. A more technical list with changes can be seen in %4.")
 2911             .arg(m_qstrNewVer)
 2912             .arg(getAppVer())
 2913             .arg(
 2914                 tr("the %1MP3 Diags blog%2", "arguments are HTML elements")
 2915                 .arg("<a href=\"http://mp3diags.blogspot.com/\">")
 2916                 .arg("</a>"))
 2917             .arg(
 2918                 tr("the %1change log%2", "arguments are HTML elements")
 2919                 .arg("<a href=\"http://mp3diags.sourceforge.net" + QString(getWebBranch()) + "/015_changelog.html\">")
 2920                 .arg("</a>"))
 2921             + "</p>";
 2922 
 2923 
 2924 #ifndef WIN32
 2925     qstrMsg += "<p style=\"margin-bottom:1px; margin-top:12px; \">" + tr("This notification is about the availability of the source code. Binaries may or may not be available at this time, depending on your particular platform.") + "</p>";
 2926 #else
 2927 #endif
 2928     qstrMsg += "<p style=\"margin-bottom:1px; margin-top:12px; \">" + tr("You should review the changes and decide if you want to upgrade or not.") + "</p>";
 2929     qstrMsg += "<p style=\"margin-bottom:1px; margin-top:12px; \">" + tr("Note: if you want to upgrade, you should %1close MP3 Diags%2 first.", "arguments are HTML elements").arg("<b>").arg("</b>") + "</p>";
 2930     qstrMsg += "<hr/><p style=\"margin-bottom:1px; margin-top:12px; \">" + tr("Choose what do you want to do:") + "</p>";
 2931     /*"<p style=\"margin-bottom:1px; margin-top:12px; \">QQQ</p>"*/
 2932 
 2933     int nRes (HtmlMsg::msg(this, 0, 0, 0, HtmlMsg::VERT_BUTTONS, tr("Info"), qstrMsg
 2934         , 600, 400, tr("Just close this message"), tr("Don't tell me about version %1 again").arg(m_qstrNewVer), tr("Disable checking for new versions")));
 2935 
 2936     //qDebug("ret %d", nRes);
 2937     switch (nRes)
 2938     {
 2939     case 0: m_pCommonData->m_strDontTellAboutVer.clear(); break;
 2940     case 1: m_pCommonData->m_strDontTellAboutVer = convStr(m_qstrNewVer); break;
 2941     case 2: m_pCommonData->m_strDontTellAboutVer.clear(); m_pCommonData->m_strCheckForNewVersions = "no"; break;
 2942     default: CB_ASSERT (false);
 2943     }
 2944 
 2945     m_settings.saveMiscConfigSettings(m_pCommonData);
 2946 }
 2947 
 2948 
 2949 
 2950 //=============================================================================================================================
 2951 //=============================================================================================================================
 2952 //=============================================================================================================================
 2953 
 2954 
 2955 namespace
 2956 {
 2957     struct CmpTransfAndName
 2958     {
 2959         const char* m_szName;
 2960         CmpTransfAndName(const char* szName) : m_szName(szName) {}
 2961 
 2962         bool operator()(const Transformation* p) const
 2963         {
 2964             return 0 == strcmp(p->getActionName(), m_szName);
 2965         }
 2966     };
 2967 
 2968 
 2969     class FixedAddrRemover : public GenericRemover
 2970     {
 2971         Q_DECLARE_TR_FUNCTIONS(FixedAddrRemover)
 2972         /*override*/ bool matches(DataStream* p) const
 2973         {
 2974             return m_pStream == p; // !!! normally there might be an issue with comparing pointers, in case something else gets allocated at the same address as a deleted object; however, in this case the stream passed on setStream() doesn't get destroyed
 2975         }
 2976         streampos m_pos;
 2977         QString m_qstrAction;
 2978         const DataStream* m_pStream;
 2979     public:
 2980         FixedAddrRemover() : m_pos(-1), m_pStream(0) {}
 2981 
 2982         void setStream(const DataStream* p)
 2983         {
 2984             m_pStream = p;
 2985             m_pos = p->getPos();
 2986             m_qstrAction = tr("Remove stream %1 at address 0x%2").arg(p->getTranslatedDisplayName()).arg(m_pos, 0, 16);
 2987         }
 2988 
 2989         /*override*/ const char* getActionName() const { return getClassName(); }
 2990         /*override*/ QString getVisibleActionName() const { return m_qstrAction; }
 2991         /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "Removes specified stream."); }
 2992 
 2993         static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Remove specified stream"); }
 2994     };
 2995 }
 2996 
 2997 
 2998 vector<Transformation*> MainFormDlgImpl::getFixes(const Note* pNote, const Mp3Handler* pHndl) const // what might fix a note
 2999 {
 3000 //qDebug("strm %s", pStream ? pStream->getDisplayName() : "");
 3001     //CB_ASSERT (0 == pStream || (pNote->getPos() >= pStream->getPos() && pNote->getPos() < pStream->getPos() + pStream->getSize()));
 3002     //CB_ASSERT (0 == pHndl || -1 != pNote->getPos());
 3003 
 3004     static map<int, vector<Transformation*> > s_mFixes;
 3005     static bool s_bInitialized (false);
 3006     const vector<Transformation*>& vpAllTransf (m_pCommonData->getAllTransf());
 3007 
 3008     if (!s_bInitialized)
 3009     {
 3010         s_bInitialized = true;
 3011 
 3012         vector<Transformation*>::const_iterator it;
 3013 
 3014         #define ADD_FIX(NOTE, TRANSF) \
 3015         it = find_if(vpAllTransf.begin(), vpAllTransf.end(), CmpTransfAndName(TRANSF::getClassName())); \
 3016         CB_ASSERT (vpAllTransf.end() != it); \
 3017         s_mFixes[Notes::NOTE().getNoteId()].push_back(*it);
 3018 
 3019         ADD_FIX(twoAudio, InnerNonAudioRemover);
 3020         ADD_FIX(twoAudio, SingleBitRepairer);
 3021         ADD_FIX(incompleteFrameInAudio, TruncatedMpegDataStreamRemover);
 3022         ADD_FIX(incompleteFrameInAudio, TruncatedAudioPadder);
 3023 
 3024         //ADD_FIX(twoLame, VbrRepairer);
 3025         //ADD_FIX(twoLame, VbrRebuilder);
 3026 
 3027         ADD_FIX(xingAddedByMp3Fixer, VbrRepairer);
 3028         ADD_FIX(xingFrameCountMismatch, VbrRepairer);
 3029         ADD_FIX(xingFrameCountMismatch, MismatchedXingRemover);
 3030         //ADD_FIX(twoXing, VbrRepairer); //ttt2
 3031         ADD_FIX(xingNotBeforeAudio, VbrRepairer);
 3032         ADD_FIX(incompatXing, VbrRebuilder);
 3033         ADD_FIX(missingXing, VbrRebuilder);
 3034         ADD_FIX(xingFrameInCount, VbrRebuilder);
 3035 
 3036         ADD_FIX(vbriFound, VbrRepairer);
 3037         ADD_FIX(foundVbriAndXing, VbrRepairer);
 3038 
 3039         ADD_FIX(id3v2FrameTooShort, Id3V2Rescuer);
 3040         //ADD_FIX(id3v2FrameTooShort, Id3V2Cleaner);
 3041         ADD_FIX(id3v2InvalidName, Id3V2Rescuer);
 3042         //ADD_FIX(id3v2InvalidName, Id3V2Cleaner);
 3043         ADD_FIX(id3v2TextError, Id3V2Rescuer);
 3044         //ADD_FIX(id3v2TextError, Id3V2Cleaner);
 3045         ADD_FIX(id3v2HasLatin1NonAscii, Id3V2UnicodeTransformer);
 3046         ADD_FIX(id3v2EmptyTcon, Id3V2Rescuer);
 3047         //ADD_FIX(id3v2EmptyTcon, Id3V2Cleaner);
 3048         ADD_FIX(id3v2MultipleFramesWithSameName, Id3V2Rescuer);
 3049         ADD_FIX(id3v2MultipleFramesWithSameName, Id3V2Cleaner);
 3050         ADD_FIX(id3v2PaddingTooLarge, Id3V2Compactor);
 3051         ADD_FIX(id3v2UnsuppVer, UnsupportedId3V2Remover);
 3052         ADD_FIX(id3v2UnsuppFlag, UnsupportedDataStreamRemover);
 3053         ADD_FIX(id3v2UnsuppFlags1, UnsupportedDataStreamRemover);
 3054         ADD_FIX(id3v2UnsuppFlags2, UnsupportedDataStreamRemover);
 3055 
 3056         ADD_FIX(id3v2CouldntLoadPic, Id3V2Rescuer);
 3057         //ADD_FIX(id3v2CouldntLoadPic, Id3V2Cleaner);
 3058         ADD_FIX(id3v2NotCoverPicture, SmallerImageRemover);
 3059         ADD_FIX(id3v2ErrorLoadingApic, Id3V2Rescuer);
 3060         //ADD_FIX(id3v2ErrorLoadingApic, Id3V2Cleaner);
 3061         ADD_FIX(id3v2ErrorLoadingApicTooShort, Id3V2Rescuer);
 3062         //ADD_FIX(id3v2ErrorLoadingApicTooShort, Id3V2Cleaner);
 3063         ADD_FIX(id3v2DuplicatePic, Id3V2Rescuer);
 3064         ADD_FIX(id3v2DuplicatePic, Id3V2Cleaner);
 3065         ADD_FIX(id3v2DuplicatePic, SmallerImageRemover);
 3066         ADD_FIX(id3v2MultipleApic, Id3V2Rescuer);
 3067         ADD_FIX(id3v2MultipleApic, Id3V2Cleaner);
 3068         ADD_FIX(id3v2MultipleApic, SmallerImageRemover);
 3069         ADD_FIX(id3v2UnsupApicTextEnc, Id3V2Rescuer);
 3070         ADD_FIX(id3v2UnsupApicTextEnc, Id3V2Cleaner);
 3071         ADD_FIX(id3v2LinkInApic, Id3V2Rescuer);
 3072         ADD_FIX(id3v2LinkInApic, Id3V2Cleaner);
 3073 
 3074         ADD_FIX(twoId3V230, MultipleId3StreamRemover);
 3075         ADD_FIX(bothId3V230_V240, MultipleId3StreamRemover);
 3076         ADD_FIX(id3v230UsesUtf8, Id3V2Rescuer);
 3077         //ADD_FIX(id3v230UsesUtf8, Id3V2Cleaner);
 3078 
 3079         ADD_FIX(twoId3V240, MultipleId3StreamRemover);
 3080         ADD_FIX(id3v240IncorrectSynch, Id3V2Rescuer);
 3081         ADD_FIX(id3v240IncorrectSynch, Id3V2Cleaner);
 3082 
 3083         ADD_FIX(id3v240DeprTyerAndTdrc, Id3V2Rescuer);
 3084         ADD_FIX(id3v240DeprTyerAndTdrc, Id3V2Cleaner);
 3085         ADD_FIX(id3v240DeprTyer, Id3V2Rescuer);
 3086         ADD_FIX(id3v240DeprTyer, Id3V2Cleaner);
 3087         ADD_FIX(id3v240DeprTdatAndTdrc, Id3V2Rescuer);
 3088         ADD_FIX(id3v240DeprTdatAndTdrc, Id3V2Cleaner);
 3089         ADD_FIX(id3v240DeprTdat, Id3V2Rescuer);
 3090         ADD_FIX(id3v240DeprTdat, Id3V2Cleaner);
 3091 
 3092         ADD_FIX(twoId3V1, MultipleId3StreamRemover);
 3093 
 3094         ADD_FIX(brokenAtTheEnd, BrokenDataStreamRemover);
 3095         ADD_FIX(brokenInTheMiddle, BrokenDataStreamRemover);
 3096 
 3097         ADD_FIX(truncAudioWithWholeFile, TruncatedMpegDataStreamRemover);
 3098         ADD_FIX(truncAudioWithWholeFile, TruncatedAudioPadder);
 3099         ADD_FIX(truncAudio, TruncatedMpegDataStreamRemover);
 3100         ADD_FIX(truncAudio, TruncatedAudioPadder);
 3101 
 3102         ADD_FIX(unknownAtTheEnd, UnknownDataStreamRemover);
 3103         ADD_FIX(unknownInTheMiddle, UnknownDataStreamRemover);
 3104         ADD_FIX(foundNull, NullStreamRemover);
 3105     }
 3106 
 3107     vector<Transformation*> vpTransf (s_mFixes[pNote->getNoteId()]);
 3108 
 3109 
 3110 /*    if (0 != pStream)
 3111     {
 3112         if (pNote->getNoteId() == Notes::audioTooShort().getNoteId())
 3113         {
 3114             if (pStream->getDisplayName() == TruncatedMpegDataStream::getClassDisplayName())
 3115             {
 3116                 vector<Transformation*>::const_iterator it (find_if(vpAllTransf.begin(), vpAllTransf.end(), CmpTransfAndName(TruncatedMpegDataStreamRemover::getClassName())));
 3117                 CB_ASSERT (vpAllTransf.end() != it);
 3118                 vpTransf.push_back(*it);
 3119             }
 3120         }
 3121     }*/
 3122 
 3123     #define ADD_CUSTOM_FIX(NOTE, STREAM, TRANSF) \
 3124     if (pNote->getNoteId() == Notes::NOTE().getNoteId()) \
 3125     { \
 3126         if (pCrtStream->getDisplayName() == STREAM::getClassDisplayName()) \
 3127         { \
 3128             vector<Transformation*>::const_iterator it (find_if(vpAllTransf.begin(), vpAllTransf.end(), CmpTransfAndName(TRANSF::getClassName()))); \
 3129             CB_ASSERT (vpAllTransf.end() != it); \
 3130             Transformation* pTransf (*it); \
 3131             if (0 == spTransf.count(pTransf)) \
 3132             { \
 3133                 vpTransf.push_back(pTransf); \
 3134                 spTransf.insert(pTransf); \
 3135             } \
 3136         } \
 3137     }
 3138 
 3139 
 3140     //ttt2 None of the ADD_CUSTOM_FIX fixes is shown for header (e.g. SingleBitRepairer is shown for validFrameDiffVer only when clicking on circle, not when clicking on header), maybe we should drop the stream test; OTOH the case of audioTooShort shows that it matters what stream the error is occuring on; so maybe drop the stream check only for some ... // workaround: see what's available for single song, then use the menu for all;
 3141     //ttt2 other example: mismatched xing fixable by SingleBitRepairer to other stream; probably document that all the notes should be looked at;
 3142     // or: add all transforms that in some context might fix a note
 3143     // or: extend ADD_CUSTOM_FIX to look at the other notes, similarly to how it looks at the stream it's in; add some transform if some other note is present; (note: use a set to not have duplicates entered via different rules)
 3144     // perhaps unknown size 16 between xing and audio -> repair vbr, but seems too specific
 3145     // unknown between audio and audio -> restore flipped
 3146     //ttt2 perhaps just this: if there's a chance that by using transf T the note N will disappear, show it; well, this is again context-dependant
 3147 
 3148     if (0 != pHndl)
 3149     { // for each matching note, see if additional fixes exist that take into account the stream the note is in and (in the future) other streams, their sizes, ...
 3150         const vector<Note*>& vpHndlNotes (pHndl->getNotes().getList());
 3151         const vector<DataStream*>& vpStreams (pHndl->getStreams());
 3152 
 3153         set<Transformation*> spTransf (vpTransf.begin(), vpTransf.end());
 3154 
 3155         static vector<FixedAddrRemover> s_vFixedAddrRemovers;
 3156         s_vFixedAddrRemovers.clear();
 3157         set<const DataStream*> spRemovableStreams;
 3158 
 3159         for (int i = 0; i < cSize(vpHndlNotes); ++i)
 3160         {
 3161             if (-1 == vpHndlNotes[i]->getNoteId()) { goto e1; } // takes care of trace notes
 3162 
 3163             const Note* pCrtNote (vpHndlNotes[i]);
 3164 
 3165             if (pCrtNote->getNoteId() == pNote->getNoteId() && -1 != pCrtNote->getPos())
 3166             {
 3167                 for (int i = 0, n = cSize(vpStreams); i < n; ++i)
 3168                 {
 3169                     //qDebug("s %s", v[i]->getDisplayName());
 3170                     if (n - 1 == i || pCrtNote->getPos() < vpStreams[i + 1]->getPos()) //ttt2 lower_bound
 3171                     {
 3172                         const DataStream* pCrtStream (vpStreams[i]);
 3173                         CB_ASSERT (pCrtNote->getPos() >= pCrtStream->getPos() && pCrtNote->getPos() < pCrtStream->getPos() + pCrtStream->getSize());
 3174 
 3175                         ADD_CUSTOM_FIX(validFrameDiffVer, UnknownDataStream, SingleBitRepairer);
 3176                         ADD_CUSTOM_FIX(validFrameDiffLayer, UnknownDataStream, SingleBitRepairer);
 3177                         ADD_CUSTOM_FIX(validFrameDiffMode, UnknownDataStream, SingleBitRepairer);
 3178                         ADD_CUSTOM_FIX(validFrameDiffFreq, UnknownDataStream, SingleBitRepairer);
 3179                         ADD_CUSTOM_FIX(validFrameDiffCrc, UnknownDataStream, SingleBitRepairer);
 3180 
 3181                         ADD_CUSTOM_FIX(audioTooShort, TruncatedMpegDataStream, TruncatedMpegDataStreamRemover);
 3182                         ADD_CUSTOM_FIX(audioTooShort, TruncatedMpegDataStream, TruncatedAudioPadder);
 3183 
 3184                         ADD_CUSTOM_FIX(audioTooShort, UnknownDataStream, UnknownDataStreamRemover);
 3185                         ADD_CUSTOM_FIX(audioTooShort, UnknownDataStream, SingleBitRepairer);
 3186 
 3187                         // ADD_CUSTOM_FIX(brokenInTheMiddle, ??? , Id3V2Rescuer); //ttt2 see about this
 3188 
 3189 
 3190                         if (pCrtNote->allowErase() && 0 == spRemovableStreams.count(pCrtStream))
 3191                         {
 3192                             spRemovableStreams.insert(pCrtStream);
 3193                             s_vFixedAddrRemovers.push_back(FixedAddrRemover());
 3194                             s_vFixedAddrRemovers.back().setStream(pCrtStream);
 3195                         }
 3196 
 3197                         break;
 3198                     }
 3199                 }
 3200             }
 3201         }
 3202 e1:;
 3203         for (int i = 0; i < cSize(s_vFixedAddrRemovers); ++i)
 3204         {
 3205             vpTransf.push_back(&s_vFixedAddrRemovers[i]);
 3206         }
 3207     }
 3208 
 3209 
 3210     return vpTransf;
 3211 }
 3212 
 3213 
 3214 int getHeaderDrawOffset();
 3215 
 3216 
 3217 void MainFormDlgImpl::onMainGridRightClick()
 3218 {
 3219     QPoint coords (m_pFilesG->mapFromGlobal(QPoint(m_nGlobalX, m_nGlobalY)));
 3220     int nCol (m_pFilesG->columnAt(coords.x() - m_pFilesG->verticalHeader()->width()));
 3221     if (nCol >= 1)
 3222     {
 3223         fixCurrentNote(coords);
 3224         return;
 3225     } // header or file name
 3226 
 3227     if (0 == nCol && coords.y() >= m_pFilesG->horizontalHeader()->height())
 3228     {
 3229         showExternalTools();
 3230     }
 3231 }
 3232 
 3233 void MainFormDlgImpl::fixCurrentNote(const QPoint& coords)
 3234 {
 3235 //LAST_STEP("MainFormDlgImpl::onFixCurrentNote()");
 3236     //QPoint coords (m_pFilesG->mapFromGlobal(QPoint(m_nGlobalX, m_nGlobalY)));
 3237     //int nHorHdrHght ();
 3238     //if (coords.x() < nVertHdrWdth) { return; }
 3239     int nCol (m_pFilesG->columnAt(coords.x() - m_pFilesG->verticalHeader()->width()));
 3240     //if (nCol < 1) { return; } // header or file name
 3241     CB_ASSERT(nCol >= 1);
 3242 
 3243     if (coords.y() < m_pFilesG->horizontalHeader()->height())
 3244     {
 3245         int x (coords.x());
 3246         x -= 2; // hard-coded
 3247         x += getHeaderDrawOffset() / 2; //ttt2 this "/2" doesn't make sense but it works best
 3248         //qDebug("offs %d", getHeaderDrawOffset());
 3249         nCol = m_pFilesG->columnAt(x - m_pFilesG->verticalHeader()->width());
 3250 
 3251         if (nCol != m_pFilesG->columnAt(x - m_pFilesG->verticalHeader()->width() - 3) ||
 3252                 nCol != m_pFilesG->columnAt(x - m_pFilesG->verticalHeader()->width() + 3))
 3253         { // too close to adjacent cells; return to avoid confusions
 3254             return;
 3255         }
 3256 
 3257         fixCurrentNoteAllFiles(nCol - 1);
 3258     }
 3259     else
 3260     {
 3261         fixCurrentNoteOneFile();
 3262     }
 3263 
 3264     //qDebug("r %d, c %d", m_pFilesG->rowAt(coords.y() - nHorHdrHght), );
 3265 }
 3266 
 3267 
 3268 void MainFormDlgImpl::fixCurrentNoteOneFile()
 3269 {
 3270     QModelIndex ndx (m_pFilesG->currentIndex());
 3271     if (!ndx.isValid() || 0 == ndx.column()) { return; }
 3272 
 3273     //qDebug("fixCurrentNoteOneFile %d %d", nGlobalX, nGlobalY);
 3274     const Mp3Handler* pHndl (m_pCommonData->getCrtMp3Handler());
 3275 
 3276     const vector<const Note*>& vpNotes (m_pCommonData->getUniqueNotes().getFltVec());
 3277     const Note* pNote (vpNotes.at(ndx.column() - 1));
 3278     //qDebug("fixing note '%s' for file '%s'", pNote->getDescription(), pHndl->getName().c_str());
 3279 
 3280     //int nCnt (0);
 3281 
 3282     vector<Transformation*> vpTransf (getFixes(pNote, pHndl));
 3283     if (vpTransf.empty()) { return; }
 3284 
 3285     showFixes(vpTransf, CURRENT);
 3286 
 3287 }
 3288 
 3289 
 3290 void MainFormDlgImpl::fixCurrentNoteAllFiles(int nCol)
 3291 {
 3292     const vector<const Note*>& vpNotes (m_pCommonData->getUniqueNotes().getFltVec());
 3293     const Note* pNote (vpNotes.at(nCol));
 3294 
 3295     vector<Transformation*> vpTransf (getFixes(pNote, 0));
 3296     if (vpTransf.empty()) { return; }
 3297 
 3298     showFixes(vpTransf, ALL);
 3299 }
 3300 
 3301 
 3302 void MainFormDlgImpl::showFixes(vector<Transformation*>& vpTransf, Subset eSubset)
 3303 {
 3304     ModifInfoMenu menu;
 3305     vector<QAction*> vpAct;
 3306 
 3307     for (int i = 0, n = cSize(vpTransf); i < n; ++i)
 3308     {
 3309         Transformation* pTransf (vpTransf[i]);
 3310         QAction* pAct (new QAction(pTransf->getVisibleActionName(), &menu));
 3311         pAct->setToolTip(makeMultiline(Transformation::tr(pTransf->getDescription())));
 3312 
 3313         //connect(pAct, SIGNAL(triggered()), this, SLOT(onExecTransform(i))); // !!! Qt doesn't seem to support parameter binding
 3314         menu.addAction(pAct);
 3315         vpAct.push_back(pAct);
 3316     }
 3317 
 3318     connect(&menu, SIGNAL(hovered(QAction*)), this, SLOT(onMenuHovered(QAction*)));
 3319 
 3320     //QAction* p (menu.exec(m_pTransformB->mapToGlobal(QPoint(0, m_pTransformB->height()))));
 3321     QAction* p (menu.exec(QPoint(m_nGlobalX, m_nGlobalY + 10)));
 3322     if (0 != p)
 3323     {
 3324         int nIndex (std::find(vpAct.begin(), vpAct.end(), p) - vpAct.begin());
 3325         vector<Transformation*> v;
 3326         v.push_back(vpTransf.at(nIndex));
 3327 
 3328         if (ALL == eSubset)
 3329         {
 3330             eSubset = 0 == (Qt::ShiftModifier & menu.getModifiers()) ? ALL : SELECTED;
 3331         }
 3332 
 3333         transform(v, eSubset);
 3334     }
 3335 }
 3336 
 3337 
 3338 void MainFormDlgImpl::showExternalTools()
 3339 {
 3340     ModifInfoMenu menu;
 3341     vector<QAction*> vpAct;
 3342 
 3343     QAction* pAct (new QAction(tr("Open containing folder ..."), &menu));
 3344     menu.addAction(pAct);
 3345     vpAct.push_back(pAct);
 3346 
 3347     if (!m_pCommonData->m_vExternalToolInfos.empty())
 3348     {
 3349         menu.addSeparator();
 3350     }
 3351     for (int i = 0; i < cSize(m_pCommonData->m_vExternalToolInfos); ++i)
 3352     {
 3353         QAction* pAct (new QAction(convStr(m_pCommonData->m_vExternalToolInfos[i].m_strName), &menu));
 3354         menu.addAction(pAct);
 3355         vpAct.push_back(pAct);
 3356     }
 3357 
 3358     QAction* p (menu.exec(QPoint(m_nGlobalX, m_nGlobalY + 10)));
 3359     if (0 != p)
 3360     {
 3361         int nIndex (std::find(vpAct.begin(), vpAct.end(), p) - vpAct.begin());
 3362         //qDebug("pressed %d", nIndex);
 3363         if (0 == nIndex)
 3364         {
 3365             CB_ASSERT (0 != m_pCommonData->getCrtMp3Handler()); //ttt0 triggered according to mail on 2015.03.07 // ttt0 2018.04.29 one way to toggle this is be in an empty folder (e.g. by starting from CLI)
 3366             QString qstrDir (convStr(m_pCommonData->getCrtMp3Handler()->getDir()));
 3367 #if defined(WIN32) || defined(__OS2__)
 3368             //qstrDir = QDir::toNativeSeparators(qstrDir);
 3369             QDesktopServices::openUrl(QUrl("file:///" + qstrDir, QUrl::TolerantMode));
 3370 #else
 3371             QDesktopServices::openUrl(QUrl("file://" + qstrDir, QUrl::TolerantMode));
 3372 #endif
 3373         }
 3374         else
 3375         { // ttt1 copied from void MainFormDlgImpl::transform(std::vector<Transformation*>& vpTransf, Subset eSubset)
 3376             const ExternalToolInfo& info (m_pCommonData->m_vExternalToolInfos[nIndex - 1]);
 3377             Subset eSubset (0 != (Qt::ControlModifier & menu.getModifiers()) ? ALL : SELECTED); //ttt0 it's confusing that the external tools apply to selected files by default (you have to press CTRL to get all) while transformations apply to all files by default (you have to right-click to process selected ones; OTOH it seems to make sense that the default for transforms to be all the files while the default for external tools to be a single file
 3378             //deque<const Mp3Handler*> vpCrt;
 3379             const deque<const Mp3Handler*>* pvpHandlers;
 3380             switch (eSubset)
 3381             {
 3382             case SELECTED: pvpHandlers = &m_pCommonData->getSelHandlers(); break;
 3383             case ALL: pvpHandlers = &m_pCommonData->getViewHandlers(); break;
 3384             //case CURRENT: vpCrt.push_back(m_pCommonData->getCrtMp3Handler()); pvpHandlers = &vpCrt; break;
 3385             default: CB_ASSERT (false);
 3386             }
 3387             //qDebug("ctrl=%d", eSubset);
 3388 
 3389             QString qstrAction (tr("Run \"%1\" on %2?").arg(convStr(info.m_strName)));
 3390             qstrAction.replace("%2", "%1");
 3391 
 3392             if (info.m_bConfirmLaunch && !askConfirm(*pvpHandlers, qstrAction))
 3393             {
 3394                 return;
 3395             }
 3396 
 3397             QStringList lFiles;
 3398             for (int i = 0; i < cSize(*pvpHandlers); ++i)
 3399             {
 3400                 lFiles << convStr((*pvpHandlers)[i]->getName());
 3401             }
 3402             switch (info.m_eLaunchOption)
 3403             {
 3404             case ExternalToolInfo::WAIT_AND_KEEP_WINDOW_OPEN:
 3405             case ExternalToolInfo::WAIT_THEN_CLOSE_WINDOW:
 3406                 {
 3407                     ExternalToolDlgImpl dlg (this, info.m_eLaunchOption == ExternalToolInfo::WAIT_AND_KEEP_WINDOW_OPEN, m_settings, m_pCommonData, info.m_strName, "299_ext_tools.html");
 3408                     dlg.run(convStr(info.m_strCommand), lFiles);
 3409                 }
 3410                 break;
 3411             case ExternalToolInfo::DONT_WAIT:
 3412                 {
 3413                     QString qstrProg;
 3414                     QStringList lArgs;
 3415                     ExternalToolDlgImpl::prepareArgs(convStr(info.m_strCommand), lFiles, qstrProg, lArgs);
 3416                     if (!QProcess::startDetached(qstrProg, lArgs))
 3417                     {
 3418                         showCritical(this, tr("Error"), tr("Cannot start process. Check that the executable name and the parameters are correct.")); //ttt0 add process name
 3419                     }
 3420                 }
 3421                 break;
 3422             }
 3423 
 3424             //l << "p1" << "p 2" << "p:3'" << "p\"4" << "p5";
 3425             //QProcess proc (this);
 3426             //proc.startDetached("konqueror");
 3427             //proc.start("konqueror"); PausableThread::sleep(10);
 3428             //proc.start("ParamsGui", l); PausableThread::sleep(10);
 3429             //proc.startDetached("ParamsGui", l);
 3430 
 3431             //proc.kill();
 3432         }
 3433     }
 3434     //add external tools
 3435 }
 3436 
 3437 
 3438 bool MainFormDlgImpl::askConfirm(const deque<const Mp3Handler*>& vpHandlers, const QString& qstrAction)
 3439 {
 3440     if (vpHandlers.empty())
 3441     {
 3442         return false;
 3443     }
 3444 
 3445     QString qstrList;
 3446 
 3447 
 3448     if (vpHandlers.size() == 1)
 3449     {
 3450         qstrList = convStr(vpHandlers[0]->getShortName());
 3451     }
 3452     else if (vpHandlers.size() == 2)
 3453     {
 3454         qstrList = tr("%1 and %2").arg(convStr(vpHandlers[0]->getShortName())).arg(convStr(vpHandlers[1]->getShortName()));
 3455     }
 3456     else if (vpHandlers.size() == 3)
 3457     {
 3458         qstrList = tr("%1, %2 and %3").arg(convStr(vpHandlers[0]->getShortName())).arg(convStr(vpHandlers[1]->getShortName())).arg(convStr(vpHandlers[2]->getShortName()));
 3459     }
 3460     else
 3461     {
 3462         qstrList = tr("%1, %2 and %3 other files").arg(convStr(vpHandlers[0]->getShortName())).arg(convStr(vpHandlers[1]->getShortName())).arg((int)vpHandlers.size() - 2);
 3463     }
 3464 
 3465     return showMessage(this, QMessageBox::Question, 1, 1, tr("Confirm"), qstrAction.arg(qstrList), tr("&Yes"), tr("&No")) == 0;
 3466 }
 3467 
 3468 //=============================================================================================================================
 3469 //=============================================================================================================================
 3470 //=============================================================================================================================
 3471 
 3472 
 3473 void MainFormDlgImpl::testSlot()
 3474 {
 3475     static int c (0);
 3476     qDebug("%d", c++);
 3477 }
 3478 
 3479 
 3480 
 3481 
 3482 
 3483 //=============================================================================================================================
 3484 //=============================================================================================================================
 3485 //=============================================================================================================================
 3486 
 3487 
 3488 
 3489 
 3490 
 3491 struct TestThread01 : public PausableThread
 3492 {
 3493     /*override*/ void run()
 3494     {
 3495         CompleteNotif notif(this);
 3496         for (int i = 0; i < 7; ++i)
 3497         {
 3498             if (isAborted()) return;
 3499             checkPause();
 3500 
 3501             QString s;
 3502             s.sprintf("step %d", i);
 3503             StrList l;
 3504             l.push_back(s);
 3505             emit stepChanged(l, -1);
 3506 //qDebug("step %d", i);
 3507 
 3508             sleep(1);
 3509         }
 3510 
 3511         notif.setSuccess(true);
 3512     }
 3513 };
 3514 
 3515 
 3516 
 3517 
 3518 
 3519 
 3520 
 3521 
 3522 
 3523 //=============================================================================================================================
 3524 //=============================================================================================================================
 3525 //=============================================================================================================================
 3526 
 3527 
 3528 
 3529 
 3530 
 3531 /*
 3532 ttt2 To look at:
 3533 QDir
 3534 QDirModel
 3535 
 3536 QFontComboBox
 3537 
 3538 QStyle ; ./myapplication -style motif
 3539 
 3540 */
 3541 
 3542 //ttt2 ? option to discard errors in unknown streams: probably not; at any rate, that's the only chance to learn that there was an error there (instead of a really unknown stream)
 3543 
 3544 
 3545 
 3546 
 3547 
 3548 // ttt2 make sure that everything that doesn't take a "parent" on the constructor gets deleted;
 3549 
 3550 
 3551 
 3552 /*
 3553 
 3554 Development machine:
 3555 
 3556 -style=Plastique
 3557 -style=Cleanlooks
 3558 -style=CDE
 3559 -style=Motif
 3560 -style=Oxygen
 3561 
 3562 ?? Windows, Windows Vista, Plastik
 3563 
 3564 
 3565 
 3566 
 3567 */
 3568 
 3569 //ttt2 build: not sure is possible, but have the .pro file include another file, which is generated by a "pre-config" script, which figures out if the lib is installed
 3570 
 3571 //ttt2 perhaps a "consider unknown" function for streams; then it would be possible for a truncated id3v1 tag that seems ok to be marked as unknown, thus allowing the following tag's start to be detected (see "c09 Mark Knopfler - The Long Road.mp3")
 3572 
 3573 
 3574 //ttt2 handle symbolic links to ancestors
 3575 
 3576 
 3577 //ttt2 fix on right-click for notes table
 3578 
 3579 
 3580 
 3581 
 3582 
 3583 //ttt0 look at /d/test_mp3/1/tmp4/tmp2/unsupported/bad-decoding
 3584 
 3585 // favicon.ico : not sure how to create it; currently it's done with GIMP, with 8bpp/1 bit alpha, not compressed; however, konqueror doesn't show it when using a local page
 3586 
 3587 //ttt0 run CppCheck
 3588 
 3589 
 3590 
 3591 
 3592 
 3593 
 3594 //ttt1 warn when folders are missing (perhaps as network drives are not mounted, usb sticks not inserted, ...), to avoid erasing the database
 3595 
 3596 
 3597 
 3598 
 3599 //ttt0 link from stable to unstable in doc. perhaps also have a notification popup
 3600 
 3601 
 3602 /*
 3603 
 3604   ttt0:
 3605 http://doc.qt.nokia.com/4.7-snapshot/internationalization.html
 3606 
 3607 Use QKeySequence() for Accelerator Values
 3608 Accelerator values such as Ctrl+Q or Alt+F need to be translated too. If you hardcode Qt::CTRL + Qt::Key_Q for "quit" in your application, translators won't be able to override it. The correct idiom is
 3609      exitAct = new QAction(tr("E&xit"), this);
 3610      exitAct->setShortcuts(QKeySequence::Quit);
 3611 
 3612 
 3613 
 3614 Typically, your application's main() function will look like this:
 3615  int main(int argc, char *argv[])
 3616  {
 3617      QApplication app(argc, argv);
 3618 
 3619      QTranslator qtTranslator;
 3620      qtTranslator.load("qt_" + QLocale::system().name(),
 3621              QLibraryInfo::location(QLibraryInfo::TranslationsPath));
 3622      app.installTranslator(&qtTranslator);
 3623 
 3624      QTranslator myappTranslator;
 3625      myappTranslator.load("myapp_" + QLocale::system().name());
 3626      app.installTranslator(&myappTranslator);
 3627 
 3628      ...
 3629      return app.exec();
 3630  }
 3631 Note the use of QLibraryInfo::location() to locate the Qt translations. Developers should request the path to the translations at run-time by passing QLibraryInfo::TranslationsPath to this function instead of using the QTDIR environment variable in their applications.
 3632 */
 3633 
 3634 //ttt0 doc & screenshots for translation
 3635 //ttt0 something to add to SF in unstable, to get the date to change
 3636 
 3637 
 3638 //ttt0 german translates "Previous [Ctrl+P]" as "Vorherige [Strg+V]"
 3639 
 3640 //ttt0 copy ID3V2 to ID3V1
 3641 
 3642 //ttt0 update references based on traffic volume
 3643 
 3644 //ttt0 don't scan backup dir if it's inside the source
 3645 //ttt0 compute bitrate in VBR headers //ttt0 see why the bitrate computed manually based on VBR data doesn't match exactly the one computed for the audio (see mail sent on 2012.10.14)
 3646 
 3647 //ttt1 mail on 2012.12.16 - It would be nice if MP3 Diags had more explicit support for multiple artists, too. For example, in WMP 11, if you select an artist field that contains multiple artists and try to edit the artist, it will only select one of the artists - clearly it knows that there are separate individual artists.
 3648 
 3649 //ttt2 ID3V2 frame TBPM should be numeric - see: 20 Rimsky-Korsakov - Flight of the Bumblebee from Tsar Sultan.mp3
 3650 
 3651 //ttt0 https://sourceforge.net/projects/mp3diags/forums/forum/947206/topic/6884554 - use http://coverartarchive.org/
 3652 
 3653 //ttt2 when processing a file (e.g. deleting a stream) the program will tend to scroll the file view so that file is the last; it should stay where it was.
 3654 
 3655 //ttt1 switch to album mode and move between folders that need/don't need scrollbar; the horizontal resizing doesn't work well, so many times there is either a scrollbar or empty space
 3656 
 3657 //ttt0 screenshots for language selection
 3658 
 3659 //ttt2 the .deb installs translations for stable to unstable: for i in `dpkg -L mp3diags` ; do if [ -f $i ] ; then ls -l $i ; fi ; done
 3660 //   2016.06.22 - not 100% sure, but probably fixed in changelist 781, while this bug entry wasn't checked in until 795; as the name of the package is lowercase, it is about the Ubuntu-built variant, which probably installed translations to /usr/...unstable...);
 3661 //ttt00 better Ubuntu integration: the package is there in 16.04 but doesn't appear in "Ubuntu software", it's rather old, to install it you need Synaptic (which must be installed itself), doesn't have translations, then it doesn't show up in search, ...
 3662 //   https://launchpad.net/ubuntu/xenial/+package/mp3diags
 3663 
 3664 //ttt2 https://sourceforge.net/p/mp3diags/discussion/947206/thread/1f7a776e/
 3665 
 3666 //ttt00 1x1 images from MusicBrainz, due to http://images.amazon.com/images/P/B00AD2IYNK.01.LZZZZZZZ.jpg no longer being there (this ASIN is .fr only)
 3667 
 3668 //ttt0 amarok fail in /d/test_mp3/1/tmp2/crt_test/Amarok-errors
 3669 
 3670 //ttt2 individual color for each note
 3671 //ttt2 copy ID3V2 to ID3V1
 3672 
 3673 //ttt2 start an older version and it shows errors about transforms not found, then crashes; 2016.06.22: not sure anything can be done: the "transforms not found" are not relevant, and what matters is the ".dat" file, which causes a SIGSEGV rather than some (expected) serialization exception; since it's hard to tell what is messed up, it's probably better to not try to handle the signal, which causes the program to crash and in turn triggers a rescan next time it's started; since both versions were built with the same libraries, the cause is probably failure to account for extra fields in the newer version (but still, this should probably have been serialization exception rather than segfault)
 3674 
 3675 //ttt1 freedb.org
 3676 //ttt1 https://sourceforge.net/p/mp3diags/discussion/947206/thread/cb3417ae/?limit=25#ef4c/6e05
 3677 
 3678 //ttt0 blind accessible - https://sourceforge.net/p/mp3diags/tickets/3099/
 3679 
 3680 //ttt0 symlinks not handled correctly when choosing dirs: select something on /d and /video gets used; the issue is worse when you want to unselect: there is nothing checked
 3681 //ttt0 https://sourceforge.net/p/mp3diags/tickets/3102/
 3682 
 3683 //ttt0 simulate crash in opening the session dialog at startup to se what and where gets logged; see mail on 2017.02.14
 3684 
 3685 //ttt0 make patterns accept "-" (see "/d/test_mp3/1/tmp2/crt_test/PatternTest/artist1 - 2000 - alb1" and https://sourceforge.net/p/mp3diags/discussion/947206/thread/3ca2d0f8/?limit=25#a622
 3686 
 3687 //ttt1 <li><a href="https://tecnoarena.net/come-riparare-file-mp3-danneggiati-con-mp3-diags/">tecnoarena.net</a></li>