"Fossies" - the Fresh Open Source Software Archive

Member "MP3Diags-unstable-1.5.01/src/Helpers.cpp" (3 Apr 2019, 56078 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 "Helpers.cpp" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 1.3.04_vs_1.5.01.

    1 /***************************************************************************
    2  *   MP3 Diags - diagnosis, repairs and tag editing for MP3 files          *
    3  *                                                                         *
    4  *   Copyright (C) 2009 by Marian Ciobanu                                  *
    5  *   ciobi@inbox.com                                                       *
    6  *                                                                         *
    7  *   This program is free software; you can redistribute it and/or modify  *
    8  *   it under the terms of the GNU General Public License version 2 as     *
    9  *   published by the Free Software Foundation.                            *
   10  *                                                                         *
   11  *   This program is distributed in the hope that it will be useful,       *
   12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
   13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
   14  *   GNU General Public License for more details.                          *
   15  *                                                                         *
   16  *   You should have received a copy of the GNU General Public License     *
   17  *   along with this program; if not, write to the                         *
   18  *   Free Software Foundation, Inc.,                                       *
   19  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
   20  ***************************************************************************/
   21 
   22 
   23 #include  <iostream>
   24 #include  "fstream_unicode.h"
   25 #include  <sstream>
   26 #include  <iomanip>
   27 #include  <cstdio>
   28 
   29 #include  <boost/version.hpp>
   30 
   31 #include  <QDesktopServices>
   32 #include  <QTableView>
   33 
   34 #ifndef WIN32
   35     #include  <QDir>
   36     #include  <sys/utsname.h>
   37     #include  <unistd.h>
   38 #else
   39     #include  <windows.h>
   40     #include  <psapi.h>
   41     #include  <QSettings>
   42 #endif
   43 
   44 #include  <QWidget>
   45 #include  <QUrl>
   46 #include  <QFileInfo>
   47 #include  <QDir>
   48 #include  <QSettings>
   49 #include  <QProcess>
   50 
   51 #include  "Helpers.h"
   52 #include  "Widgets.h"
   53 #include  "Version.h"
   54 #include  "OsFile.h"
   55 
   56 #include  "DataStream.h" // for translation
   57 
   58 
   59 using namespace std;
   60 using namespace Version;
   61 
   62 
   63 void assertBreakpoint()
   64 {
   65     cout << endl;
   66     //qDebug("assert");
   67 }
   68 
   69 
   70 void appendFilePart(istream& in, ostream& out, streampos pos, streamoff nSize)
   71 {
   72     const int BFR_SIZE (1024*128);
   73     char* pBfr (new char[BFR_SIZE]);
   74     pearl::ArrayPtrRelease<char> rel (pBfr);
   75     in.seekg(pos);
   76 
   77     for (; nSize > 0;)
   78     {
   79         streamoff nCrtRead (nSize > BFR_SIZE ? BFR_SIZE : nSize);
   80         CB_CHECK (nCrtRead == read(in, pBfr, nCrtRead), EndOfFile);
   81         out.write(pBfr, nCrtRead);
   82 
   83         nSize -= nCrtRead;
   84     }
   85 
   86     if (!out)
   87     {
   88         TRACER("appendFilePart() failed");
   89     }
   90 
   91     CB_CHECK (out, WriteError);
   92 }
   93 
   94 
   95 
   96 
   97 // prints to stdout the content of a memory location, as ASCII and hex;
   98 // GDB has a tendency to not see char arrays and other local variables; actually GCC seems to be the culprit (bug 34767);
   99 void inspect(const void* q, int nSize)
  100 {
  101     ostringstream out;
  102     const char* p ((const char*)q);
  103     for (int i = 0; i < nSize; ++i)
  104     {
  105         char c (p[i]);
  106         if (c < 32 || c > 126) { c = '.'; }
  107         out << c;
  108     }
  109     out << "\n(";
  110 
  111     out << hex << setfill('0');
  112     bool b (false);
  113     for (int i = 0; i < nSize; ++i)
  114     {
  115         if (b) { out << " "; }
  116         b = true;
  117         unsigned char c (p[i]);
  118         out << setw(2) << (int)c;
  119     }
  120     out << dec << ")\n";
  121     qDebug("%s", out.str().c_str());
  122     //logToGlobalFile(out.str());
  123 }
  124 
  125 
  126 int get32BitBigEndian(const char* bfr)
  127 {
  128     const unsigned char* p (reinterpret_cast<const unsigned char*>(bfr));
  129     int n ((p[0] << 24) + (p[1] << 16) + (p[2] << 8) + (p[3] << 0));
  130     return n;
  131 }
  132 
  133 
  134 void put32BitBigEndian(int n, char* bfr)
  135 {
  136     unsigned u (n);
  137     bfr[3] = u & 0xff; u >>= 8;
  138     bfr[2] = u & 0xff; u >>= 8;
  139     bfr[1] = u & 0xff; u >>= 8;
  140     bfr[0] = u & 0xff;
  141 }
  142 
  143 
  144 
  145 string utf8FromLatin1(const string& strSrc)
  146 {
  147     int i (0);
  148     int n (cSize(strSrc));
  149 
  150     for (; i < n; ++i)
  151     {
  152         unsigned char c (strSrc[i]);
  153         if (c >= 128) { goto e1; }
  154     }
  155     return strSrc;
  156 
  157 e1:
  158     string s (strSrc.substr(0, i));
  159     for (; i < n; ++i)
  160     {
  161         unsigned char c (strSrc[i]);
  162         if (c < 128)
  163         {
  164             s += char(c);
  165         }
  166         else
  167         {
  168             unsigned char c1 (0xc0 | (c >> 6));
  169             unsigned char c2 (0x80 | (c & 0x3f));
  170             s += char(c1);
  171             s += char(c2);
  172         }
  173     }
  174     return s;
  175 }
  176 
  177 
  178 // removes whitespaces at the end of the string
  179 bool CB_LIB_CALL rtrim(string& s)
  180 {
  181     int n (cSize(s));
  182     int i (n - 1);
  183     for (; i >= 0; --i)
  184     {
  185         unsigned char c (s[i]);
  186         if (c >= 128 || !isspace(c)) { break; } //!!! isspace() returns true for some non-ASCII chars, e.g. 0x9f, at least on MinGW (it matters that char is signed)
  187     }
  188 
  189     if (i < n - 1)
  190     {
  191         s.erase(i + 1);
  192         return true;
  193     }
  194     return false;
  195 }
  196 
  197 
  198 // removes whitespaces at the beginning of the string
  199 bool CB_LIB_CALL ltrim(string& s)
  200 {
  201     int n (cSize(s));
  202     int i (0);
  203     for (; i < n; ++i)
  204     {
  205         unsigned char c (s[i]);
  206         if (c >= 128 || !isspace(c)) { break; } //!!! isspace() returns true for some non-ASCII chars, e.g. 0x9f, at least on MinGW
  207     }
  208 
  209     if (i > 0)
  210     {
  211         s.erase(0, i);
  212         return true;
  213     }
  214     return false;
  215 }
  216 
  217 bool CB_LIB_CALL trim(string& s)
  218 {
  219     bool b1 (ltrim(s));
  220     bool b2 (rtrim(s));
  221     return b1 || b2;
  222 }
  223 
  224 
  225 
  226 /*
  227 // multi-line hex printing
  228 void printHex(const string& s, ostream& out, bool bShowAsciiCode = true) //ttt3 see if anybody needs this
  229 {
  230     int nSize (cSize(s));
  231     int nCrt (0);
  232 
  233     for (;;)
  234     {
  235         if (nCrt >= nSize) { return; }
  236         int nMax (16);
  237         if (nCrt + nMax > nSize)
  238         {
  239             nMax = nSize - nCrt;
  240         }
  241 
  242         for (int i = 0; i < nMax; ++i)
  243         {
  244             char c (s[i + nCrt]);
  245             if (c < 32 || c >= 127) { c = '?'; }
  246             out << " " << c << " ";
  247         }
  248         out << endl;
  249 
  250         for (int i = 0; i < nMax; ++i)
  251         {
  252             unsigned int x ((unsigned char)s[i + nCrt]);
  253             if (!bShowAsciiCode && x >= 32 && x < 127)
  254             {
  255                 out << "   ";
  256             }
  257             else
  258             {
  259                 out << setw(2) << hex << x << dec << " ";
  260             }
  261         }
  262         out << endl;
  263         nCrt += 16;
  264     }
  265 }
  266 */
  267 
  268 
  269 
  270 std::string asHex(const char* p, int nSize)
  271 {
  272     ostringstream out;
  273     out << "\"";
  274     for (int i = 0; i < nSize; ++i)
  275     {
  276         char c (p[i]);
  277         out << (c >= 32 && c < 127 ? c : '.');
  278     }
  279     out << "\" (" << hex;
  280     for (int i = 0; i < nSize; ++i)
  281     {
  282         if (i > 0) { out << " "; }
  283         unsigned char c (p[i]);
  284         out << setw(2) << setfill('0') << (int)c;
  285     }
  286     out << ")";
  287     return out.str();
  288 }
  289 
  290 
  291 
  292 
  293 // the total memory currently used by the current process, in kB
  294 long getMemUsage()
  295 {
  296 #ifndef WIN32
  297     //pid_t
  298     int n ((int)getpid());
  299     char a [30];
  300     sprintf (a, "/proc/%d/status", n); // ttt2 linux-specific; not sure how version-specific this is;
  301     // sprintf (a, "/proc/self/status", n); // ttt2 try this (after checking portability)
  302     ifstream_utf8 in (a);
  303     string s;
  304     while (getline(in, s))
  305     {
  306         if (0 == s.find("VmSize:"))
  307         {
  308             return atol(s.c_str() + 7);
  309         }
  310     }
  311     return 0;
  312 #else
  313     PROCESS_MEMORY_COUNTERS pmc;
  314     if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc)))
  315     {
  316         return pmc.WorkingSetSize;
  317     }
  318 
  319     return 0;
  320 #endif
  321 
  322 }
  323 
  324 
  325 void logToGlobalFile(const string& s) //tttc make sure it is disabled in public releases
  326 {
  327 #ifndef WIN32
  328     ofstream_utf8 out (
  329             "/tmp/Mp3DiagsLog.txt",
  330             ios_base::app);
  331 #else
  332     char a [500];
  333     int n (GetModuleFileNameA(NULL, a, 500)); //ttt3 using GetModuleFileNameA isn't quite right, but since it's a debug function ...
  334     a[n - 4] = 0;
  335     ofstream_utf8 out (
  336             //"C:/Mp3DiagsLog.txt",
  337             //"Mp3DiagsLog.txt",
  338             //"C:/temp/Mp3DiagsLog.txt",
  339             (string(a) + "Log.txt").c_str(),
  340             ios_base::app);
  341 #endif
  342     out << s << endl;
  343 }
  344 
  345 
  346 
  347 
  348 
  349 
  350 
  351 
  352 
  353 
  354 
  355 
  356 
  357 
  358 
  359 
  360 
  361 
  362 
  363 
  364 
  365 
  366 #define DECODE_CHECK(COND, MSG) { if (!(COND)) { bRes = false; return MSG; } }
  367 
  368 namespace
  369 {
  370 
  371     struct Decoder
  372     {
  373         enum Version { MPEG1, MPEG2 };
  374         enum Layer { LAYER1, LAYER2, LAYER3 };
  375         enum ChannelMode { STEREO, JOINT_STEREO, DUAL_CHANNEL, SINGLE_CHANNEL };
  376 
  377         Version m_eVersion;
  378         Layer m_eLayer;
  379         int m_nBitrate;
  380         int m_nFrequency;
  381         int m_nPadding;
  382         ChannelMode m_eChannelMode;
  383 
  384         int m_nSize;
  385         bool m_bCrc;
  386 
  387         const char* getSzVersion() const;
  388         const char* getSzLayer() const;
  389         const char* getSzChannelMode() const;
  390 
  391         QString initialize(const unsigned char* bfr, bool* pbIsValid);
  392         QString decodeMpegFrame(const unsigned char* bfr, const char* szSep, bool* pbIsValid);
  393         string decodeMpegFrameAsXml(const unsigned char* bfr, bool* pbIsValid);
  394     };
  395 
  396     const char* Decoder::getSzVersion() const
  397     {
  398         static const char* s_versionName[] = { QT_TRANSLATE_NOOP("DataStream", "MPEG-1"), QT_TRANSLATE_NOOP("DataStream", "MPEG-2") };
  399         return s_versionName[m_eVersion];
  400     }
  401 
  402     const char* Decoder::getSzLayer() const
  403     {
  404         static const char* s_layerName[] = { QT_TRANSLATE_NOOP("DataStream", "Layer I"), QT_TRANSLATE_NOOP("DataStream", "Layer II"), QT_TRANSLATE_NOOP("DataStream", "Layer III") };
  405         return s_layerName[m_eLayer];
  406     }
  407 
  408     const char* Decoder::getSzChannelMode() const
  409     {
  410         static const char* s_channelModeName[] = { QT_TRANSLATE_NOOP("DataStream", "Stereo"), QT_TRANSLATE_NOOP("DataStream", "Joint stereo"), QT_TRANSLATE_NOOP("DataStream", "Dual channel"), QT_TRANSLATE_NOOP("DataStream", "Single channel") };
  411         return s_channelModeName[m_eChannelMode];
  412     }
  413 
  414 
  415     QString Decoder::initialize(const unsigned char* bfr, bool* pbIsValid) //ttt2 perhaps unify with MpegFrameBase::MpegFrameBase(), using the char* constructor; note that they also share translations
  416     {
  417         bool b;
  418         bool& bRes (0 == pbIsValid ? b : *pbIsValid);
  419         bRes = true;
  420         const unsigned char* pHeader (bfr);
  421     //inspect(bfr, BFR_SIZE);
  422         DECODE_CHECK (0xff == *pHeader && 0xe0 == (0xe0 & *(pHeader + 1)), DataStream::tr("Not an MPEG frame. Synch missing."));
  423         ++pHeader;
  424 
  425         {
  426             int nVer ((*pHeader & 0x18) >> 3);
  427             switch (nVer)
  428             {//TRACE
  429             case 0x00: bRes = false; return DataStream::tr("Not an MPEG frame. Unsupported version (2.5)."); //ttt2 see about supporting this: search for MPEG1 to find other places
  430                 // ttt2 in a way it would make more sense to warn that it's not supported, with "MP3_THROW(SUPPORT, ...)", but before warn, make sure it's a valid 2.5 frame, followed by another frame ...
  431 
  432             case 0x02: m_eVersion = MPEG2; break;
  433             case 0x03: m_eVersion = MPEG1; break;
  434 
  435             default: bRes = false; return DataStream::tr("Not an MPEG frame. Invalid version.");
  436             }
  437         }
  438 
  439         {
  440             int nLayer ((*pHeader & 0x06) >> 1);
  441             switch (nLayer)
  442             {
  443             case 0x01: m_eLayer = LAYER3; break;
  444             case 0x02: m_eLayer = LAYER2; break;
  445             case 0x03: m_eLayer = LAYER1; break;
  446 
  447             default: bRes = false; return DataStream::tr("Not an MPEG frame. Invalid layer.");
  448             }
  449         }
  450 
  451         {
  452             m_bCrc = !(*pHeader & 0x01);
  453         }
  454 
  455         ++pHeader;
  456         {
  457             static int s_bitrates [14][5] =
  458                 {
  459                     {  32,      32,      32,      32,       8 },
  460                     {  64,      48,      40,      48,      16 },
  461                     {  96,      56,      48,      56,      24 },
  462                     { 128,      64,      56,      64,      32 },
  463                     { 160,      80,      64,      80,      40 },
  464                     { 192,      96,      80,      96,      48 },
  465                     { 224,     112,      96,     112,      56 },
  466                     { 256,     128,     112,     128,      64 },
  467                     { 288,     160,     128,     144,      80 },
  468                     { 320,     192,     160,     160,      96 },
  469                     { 352,     224,     192,     176,     112 },
  470                     { 384,     256,     224,     192,     128 },
  471                     { 416,     320,     256,     224,     144 },
  472                     { 448,     384,     320,     256,     160 }
  473                 };
  474             int nRateIndex ((*pHeader & 0xf0) >> 4);
  475             DECODE_CHECK (nRateIndex >= 1 && nRateIndex <= 14, DataStream::tr("Not an MPEG frame. Invalid bitrate."));
  476             int nTypeIndex (m_eVersion*3 + m_eLayer);
  477             if (nTypeIndex == 5) { nTypeIndex = 4; }
  478             m_nBitrate = s_bitrates[nRateIndex - 1][nTypeIndex]*1000;
  479         }
  480 
  481         {
  482             int nSmpl ((*pHeader & 0x0c) >> 2);
  483             switch (m_eVersion)
  484             {
  485             case MPEG1:
  486                 switch (nSmpl)
  487                 {
  488                 case 0x00: m_nFrequency = 44100; break;
  489                 case 0x01: m_nFrequency = 48000; break;
  490                 case 0x02: m_nFrequency = 32000; break;
  491 
  492                 default: bRes = false; return DataStream::tr("Not an MPEG frame. Invalid frequency for MPEG1.");
  493                 }
  494                 break;
  495 
  496             case MPEG2:
  497                 switch (nSmpl)
  498                 {
  499                 case 0x00: m_nFrequency = 22050; break;
  500                 case 0x01: m_nFrequency = 24000; break;
  501                 case 0x02: m_nFrequency = 16000; break;
  502 
  503                 default: bRes = false; return DataStream::tr("Not an MPEG frame. Invalid frequency for MPEG2.");
  504                 }
  505                 break;
  506 
  507             default: CB_THROW(CbRuntimeError); // it should have thrown before getting here
  508             }
  509         }
  510 
  511         {
  512             m_nPadding = (0x02 & *pHeader) >> 1;
  513         }
  514 
  515         ++pHeader;
  516         {
  517             int nChMode ((*pHeader & 0xc0) >> 6);
  518             m_eChannelMode = (ChannelMode)nChMode;
  519         }
  520 
  521         switch (m_eLayer)
  522         {
  523         case LAYER1:
  524             m_nSize = (12*m_nBitrate/m_nFrequency + m_nPadding)*4;
  525             break;
  526 
  527         case LAYER2:
  528             m_nSize = 144*m_nBitrate/m_nFrequency + m_nPadding;
  529             break;
  530 
  531         case LAYER3:
  532             m_nSize = (MPEG1 == m_eVersion ? 144*m_nBitrate/m_nFrequency + m_nPadding : 72*m_nBitrate/m_nFrequency + m_nPadding);
  533             break;
  534 
  535         default: CB_THROW(CbRuntimeError); // it should have thrown before getting here
  536         }
  537 
  538         return "";
  539     }
  540 
  541 
  542     QString Decoder::decodeMpegFrame(const unsigned char* bfr, const char* szSep, bool* pbIsValid) //ttt2 perhaps unify with MpegFrameBase::MpegFrameBase(), using the char* constructor
  543     {
  544         QString s (initialize(bfr, pbIsValid));
  545         if (!s.isEmpty()) { return s; }
  546 
  547         //ostringstream out;
  548         /*out << getSzVersion() << " " << getSzLayer() << ", " << m_nBitrate/1000 << "kbps, " << m_nFrequency << "Hz, " << getSzChannelMode() << ", padding=" << (m_nPadding ? "true" : "false") << ", length " <<
  549                 m_nSize << " (0x" << hex << m_nSize << dec << ")";*/
  550 
  551 
  552         /*out << boolalpha <<
  553                getSzVersion() << " " <<
  554                getSzLayer() <<
  555                szSep <<
  556                getSzChannelMode()/ *4* / <<
  557                 szSep <<
  558                m_nFrequency << "Hz" <<
  559                szSep <<
  560                m_nBitrate / *8* / << "bps" <<
  561                szSep << "CRC=" <<
  562                boolAsYesNo(m_bCrc) <<
  563                szSep / *11* /<< "length " <<
  564                m_nSize <<
  565                " (0x" << hex << m_nSize << dec << ")" <<
  566                szSep / *14* /<< "padding=" <<
  567                (m_nPadding ? "true" : "false");*/
  568 
  569 
  570 
  571 
  572         return DataStream::tr("%1 %2%3%4%5%6Hz%7%8bps%9CRC=%10%11length %12 (0x%13)%14padding=%15")
  573                 .arg(DataStream::tr(getSzVersion()))
  574                 .arg(DataStream::tr(getSzLayer()))
  575                 .arg(szSep)
  576                 .arg(DataStream::tr(getSzChannelMode()))
  577                 .arg(szSep)
  578                 .arg(m_nFrequency)
  579                 .arg(szSep)
  580                 .arg(m_nBitrate)
  581                 .arg(szSep)
  582                 .arg(GlobalTranslHlp::tr(boolAsYesNo(m_bCrc)))
  583                 .arg(szSep)
  584                 .arg(m_nSize)
  585                 .arg(m_nSize, 0, 16)
  586                 .arg(szSep)
  587                 .arg(GlobalTranslHlp::tr(boolAsYesNo(m_nPadding)));
  588     }
  589 
  590 
  591     string Decoder::decodeMpegFrameAsXml(const unsigned char* bfr, bool* pbIsValid) //ttt2 perhaps unify with MpegFrameBase::MpegFrameBase(), using the char* constructor
  592     {
  593         QString s (initialize(bfr, pbIsValid)); // !!! XML is not translated
  594         if (!s.isEmpty()) { return convStr(s); }
  595 
  596         ostringstream out;
  597         out << " version=\"" << getSzVersion() << "\""
  598             << " layer=\"" << getSzLayer() << "\""
  599             << " channelMode=\"" << getSzChannelMode() << "\""
  600             << " frequency=\"" << m_nFrequency << "\""
  601             << " bps=\"" << m_nBitrate << "\""
  602             << " crc=\"" << boolAsYesNo(m_bCrc) << "\""
  603 
  604             << " mpegSize=\"" << m_nSize << "\""
  605             << " padding=\"" << boolAsYesNo(m_nPadding) << "\""; // !!! XML isn't translated
  606 
  607         return out.str();
  608     }
  609 } // namespace
  610 
  611 
  612 
  613 string decodeMpegFrame(unsigned int x, const char* szSep, bool* pbIsValid /* = 0*/)
  614 {
  615     Decoder d;
  616     unsigned char bfr [4];
  617     unsigned char* q (reinterpret_cast<unsigned char*>(&x));
  618     bfr[0] = q[3]; bfr[1] = q[2]; bfr[2] = q[1]; bfr[3] = q[0];
  619 
  620     return convStr(d.decodeMpegFrame(bfr, szSep, pbIsValid));
  621 }
  622 
  623 
  624 string decodeMpegFrame(const char* bfr, const char* szSep, bool* pbIsValid /* = 0*/)
  625 {
  626     Decoder d;
  627     const unsigned char* q (reinterpret_cast<const unsigned char*>(bfr));
  628     return convStr(d.decodeMpegFrame(q, szSep, pbIsValid));
  629 }
  630 
  631 
  632 string decodeMpegFrameAsXml(const char* bfr, bool* pbIsValid /* = 0*/)
  633 {
  634     Decoder d;
  635     const unsigned char* q (reinterpret_cast<const unsigned char*>(bfr));
  636     return d.decodeMpegFrameAsXml(q, pbIsValid);
  637 }
  638 
  639 
  640 StreamStateRestorer::StreamStateRestorer(istream& in) : m_in(in), m_pos(in.tellg()), m_bOk(false)
  641 {
  642 }
  643 
  644 
  645 StreamStateRestorer::~StreamStateRestorer()
  646 {
  647     if (!m_bOk)
  648     {
  649         m_in.clear();
  650         m_in.seekg(m_pos);
  651     }
  652     m_in.clear();
  653 }
  654 
  655 char getPathSep()
  656 {
  657     return '/'; // ttt2 linux-specific
  658 }
  659 
  660 const string& getPathSepAsStr()
  661 {
  662     static string s ("/");
  663     return s; // ttt2 linux-specific // ttt look at QDir::fromNativeSeparators
  664 }
  665 
  666 
  667 
  668 
  669 streampos getSize(istream& in)
  670 {
  671     streampos crt (in.tellg());
  672     in.seekg(0, ios_base::end);
  673     streampos size (in.tellg());
  674     in.seekg(crt);
  675     return size;
  676 }
  677 
  678 
  679 
  680 void writeZeros(ostream& out, int nCnt)
  681 {
  682     CB_ASSERT (nCnt >= 0);
  683     char c (0);
  684     for (int i = 0; i < nCnt; ++i) //ttt2 perhaps make this faster
  685     {
  686         out.write(&c, 1);
  687     }
  688 
  689     CB_CHECK (out, WriteError);
  690 }
  691 
  692 
  693 void listWidget(QWidget* p, int nIndent /* = 0*/)
  694 {
  695     //if (nIndent > 1) { return; }
  696     if (0 == nIndent) { cout << "\n----------------------------\n"; }
  697     cout << string(nIndent*2, ' ') << convStr(p->objectName()) << " " << p->x() << " " << p->y() << " " << p->width() << " " << p->height() << endl;
  698     QList<QWidget*> lst (p->findChildren<QWidget*>());
  699     for (QList<QWidget*>::iterator it = lst.begin(); it != lst.end(); ++it)
  700     {
  701         QWidget* q (*it);
  702         if (q->parentWidget() == p) // !!! needed because findChildren() reurns all descendants, not only children
  703         {
  704             listWidget(q, nIndent + 1);
  705         }
  706     }
  707 }
  708 
  709 
  710 // replaces invalid HTTP characters like ' ' or '"' with their hex code (%20 or %22)
  711 string escapeHttp(const string& s)
  712 {
  713     QUrl url (convStr(s));
  714     return convStr(QString(url.toEncoded()));
  715 }
  716 
  717 
  718 
  719 
  720 vector<string> convStr(const vector<QString>& v)
  721 {
  722     vector<string> u;
  723     for (int i = 0, n = cSize(v); i < n; ++i)
  724     {
  725         u.push_back(convStr(v[i]));
  726     }
  727     return u;
  728 }
  729 
  730 vector<QString> convStr(const vector<string>& v)
  731 {
  732     vector<QString> u;
  733     for (int i = 0, n = cSize(v); i < n; ++i)
  734     {
  735         u.push_back(convStr(v[i]));
  736     }
  737     return u;
  738 }
  739 
  740 namespace {
  741 
  742 struct DesktopDetector {
  743     enum Desktop { Unknown, Gnome2 = 1, Gnome3 = 2, Kde3 = 4, Kde4 = 8, Gnome = Gnome2 | Gnome3, Kde = Kde3 | Kde4 };
  744     DesktopDetector();
  745     Desktop m_eDesktop;
  746     const char* m_szDesktop;
  747     bool onDesktop(Desktop desktop) const
  748     {
  749         return (desktop & m_eDesktop) != 0;
  750     }
  751 };
  752 
  753 
  754 #if defined(__linux__)
  755 
  756 DesktopDetector::DesktopDetector() : m_eDesktop(Unknown)
  757 {
  758     FileSearcher fs ("/proc");
  759     string strBfr;
  760 
  761     bool bIsKde (false);
  762     bool bIsKde4 (false);
  763 
  764     while (fs)
  765     {
  766         if (fs.isDir())
  767         {
  768             string strCmdLineName (fs.getName());
  769 
  770             if (isdigit(strCmdLineName[6]))
  771             {
  772 
  773 #if 0
  774                 char szBfr[5000] = "mP3DiAgS";
  775                 szBfr[0] = 0;
  776 
  777                 int k (readlink((strCmdLineName + "/exe").c_str(), szBfr, sizeof(szBfr)));
  778                 if (k >= 0)
  779                 {
  780                     szBfr[k] = 0;
  781                 }
  782                 cout << strCmdLineName << "          ";
  783                 szBfr[5000 - 1] = 0;
  784                 //if (0 != szBfr[0])
  785                     cout << szBfr << "        |";
  786                     //cout << szBfr << endl;//*/
  787 
  788 
  789                 strCmdLineName += "/cmdline";
  790                 //cout << strCmdLineName << endl;
  791                 ifstream in (strCmdLineName.c_str());
  792                 if (in)
  793                 {
  794                     getline(in, strBfr);
  795                     //if (!strBfr.empty()) { cout << "<<<   " << strBfr.c_str() << "   >>>" << endl; }
  796                     //if (!strBfr.empty()) { cout << strBfr.c_str() << endl; }
  797                     cout << "          " << strBfr.c_str();
  798                     if (string::npos != strBfr.find("gnome-settings-daemon"))
  799                     {
  800                         m_eDesktop = string::npos != strBfr.find("gnome-settings-daemon-3.") ? Gnome3 : Gnome2;
  801                         break;
  802                     }
  803                 }//*/
  804                 cout << endl;
  805 
  806 #endif
  807                 //char szBfr[5000] = "mP3DiAgS";
  808                 //szBfr[0] = 0;
  809 
  810 
  811                 strCmdLineName += "/cmdline";
  812                 //cout << strCmdLineName << endl;
  813                 ifstream in (strCmdLineName.c_str());
  814                 if (in)
  815                 {
  816                     getline(in, strBfr);
  817                     //if (!strBfr.empty()) { cout << "<<<   " << strBfr.c_str() << "   >>>" << endl; }
  818                     //if (!strBfr.empty()) { cout << strBfr.c_str() << endl; }
  819                     //cout << strBfr.c_str() << endl;
  820                     if (string::npos != strBfr.find("gnome-settings-daemon"))
  821                     {
  822                         m_eDesktop = string::npos != strBfr.find("gnome-settings-daemon-3.") ? Gnome3 : Gnome2;
  823                         break;
  824                     }
  825 
  826                     if (string::npos != strBfr.find("kdeinit"))
  827                     {
  828                         bIsKde = true;
  829                     }
  830 
  831                     //if (string::npos != strBfr.find("kde4/libexec")) // this gives false positives on openSUSE 11.4 from KDE 3:
  832                     // for i in `ls /proc | grep '^[0-9]'` ; do a=`cat /proc/$i/cmdline 2>/dev/null` ; echo $a | grep kde4/libexec ; done
  833                     if (string::npos != strBfr.find("kde4/libexec/start")) // ttt2 probably works only on Suse and only in some cases
  834                     {
  835                         bIsKde4 = true; //ttt9 update
  836                     }
  837                 }//*/
  838 
  839             }
  840         }
  841         //if (string::npos != strBfr.find("kdeinit"))
  842 
  843         fs.findNext();
  844     }
  845 
  846     if (m_eDesktop == Unknown)
  847     {
  848         if (bIsKde4)
  849         {
  850             m_eDesktop = Kde4;
  851         }
  852         else if (bIsKde)
  853         {
  854             m_eDesktop = Kde3;
  855         }
  856     }
  857 
  858     if (Gnome2 == m_eDesktop)
  859     { // while on openSUSE there's gnome-settings-daemon-3, on Fedora it's always gnome-settings-daemon, regardless of the Gnome version; so if Gnome 3 seems to be installed, we'll override a "Gnome2" value
  860         QDir dir ("/usr/share/gnome-shell");
  861         if (dir.exists())
  862         {
  863             m_eDesktop = Gnome3;
  864         }
  865     }
  866 
  867     switch (m_eDesktop)
  868     {
  869     case Gnome2: m_szDesktop = "Gnome 2"; break;
  870     case Gnome3: m_szDesktop = "Gnome 3"; break;
  871     case Kde3: m_szDesktop = "KDE 3"; break;
  872     case Kde4: m_szDesktop = "KDE 4"; break;
  873     default: m_szDesktop = "Unknown";
  874     }
  875     //cout << "desktop: " << m_eDesktop << endl;
  876 }
  877 
  878 #else // #if defined(__linux__)
  879 
  880 DesktopDetector::DesktopDetector() : m_eDesktop(Unknown) {}
  881 
  882 #endif
  883 
  884 const DesktopDetector& getDesktopDetector()
  885 {
  886     static DesktopDetector desktopDetector;
  887     return desktopDetector;
  888 }
  889 
  890 
  891 } // namespace
  892 
  893 
  894 bool getDefaultForShowCustomCloseButtons()
  895 {
  896     return DesktopDetector::Gnome3 == getDesktopDetector().m_eDesktop;
  897 }
  898 
  899 
  900 /*
  901 Gnome:
  902 
  903 Qt::Window - minimize, maximize, close; dialog gets its own taskbar entry
  904 Qt::WindowTitleHint - close
  905 Qt::Dialog - close
  906 Qt::WindowTitleHint | Qt::WindowMaximizeButtonHint - nothing
  907 Qt::Dialog | Qt::WindowMaximizeButtonHint - nothing
  908 Qt::Window | Qt::WindowMaximizeButtonHint - maximize
  909 Qt::Window | Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint - maximize, minimize
  910 Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint - nothing
  911 
  912 Ideally a modal dialog should minimize its parent. If that's not possible, it shouldn't be minimizable.
  913 */
  914 
  915 //ttt0 look at Qt::CustomizeWindowHint
  916 #ifndef WIN32
  917     //Qt::WindowFlags getMainWndFlags() { return isRunningOnGnome() ? Qt::Window : Qt::WindowTitleHint; } // !!! these are incorrect, but seem the best option; the values used for Windows are supposed to be OK; they work as expected with KDE but not with Gnome (asking for maximize button not only fails to show it, but causes the "Close" button to disappear as well); Since in KDE min/max buttons are shown when needed anyway, it's sort of OK // ttt2 see if there is workaround/fix
  918     Qt::WindowFlags getMainWndFlags() { const DesktopDetector& dd = getDesktopDetector(); return dd.onDesktop(DesktopDetector::Kde) ? Qt::WindowTitleHint : Qt::Window; }
  919 #if QT_VERSION >= 0x040500
  920     //Qt::WindowFlags getDialogWndFlags() { const DesktopDetector& dd = getDesktopDetector(); return dd.onDesktop(DesktopDetector::Kde) ? Qt::WindowTitleHint | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint : (dd.onDesktop(DesktopDetector::Gnome3) ? Qt::Window : Qt::WindowTitleHint); }
  921     Qt::WindowFlags getDialogWndFlags() { const DesktopDetector& dd = getDesktopDetector(); return dd.onDesktop(DesktopDetector::Kde) ? Qt::WindowTitleHint | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint : (/*dd.onDesktop(DesktopDetector::Gnome3) ? Qt::Window :*/ Qt::WindowTitleHint); }
  922 #else
  923     //Qt::WindowFlags getDialogWndFlags() { const DesktopDetector& dd = getDesktopDetector(); return dd.onDesktop(DesktopDetector::Gnome3) ? Qt::Window : Qt::WindowTitleHint; }
  924     Qt::WindowFlags getDialogWndFlags() { const DesktopDetector& dd = getDesktopDetector(); return /*dd.onDesktop(DesktopDetector::Gnome3) ? Qt::Window :*/ Qt::WindowTitleHint; }
  925     // ttt0 perhaps better to make sure all dialogs have their ok/cancel buttons, so there's no need for a dedicated close button and let the app look more "native"
  926 #endif
  927     Qt::WindowFlags getNoResizeWndFlags() { return Qt::WindowTitleHint; }
  928 #else
  929     Qt::WindowFlags getMainWndFlags() { 
  930         return Qt::WindowTitleHint | Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint;
  931     } // minimize, maximize, no "what's this"
  932     Qt::WindowFlags getDialogWndFlags() { return Qt::WindowTitleHint | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint; } // minimize, no "what's this"
  933     Qt::WindowFlags getNoResizeWndFlags() { return Qt::WindowTitleHint; } // no "what's this" // ttt2 review if we want Qt::WindowCloseButtonHint
  934 #endif
  935 
  936 
  937 
  938 #if 0
  939 //ttt2 add desktop, distribution, WM, ...
  940 #ifndef WIN32
  941     utsname info;
  942     uname(&info);
  943 
  944     cat /proc/version
  945     cat /etc/issue
  946     dmesg | grep "Linux version"
  947     cat /etc/*-release
  948 */
  949     cat /etc/*_version
  950 
  951 */
  952 #else
  953 //
  954 #endif
  955 #endif
  956 
  957 #ifndef WIN32
  958 static void removeStr(string& main, const string& sub)
  959 {
  960     for (;;)
  961     {
  962         string::size_type n (main.find(sub));
  963         if (string::npos == n) { return; }
  964         main.erase(n, sub.size());
  965     }
  966 }
  967 #endif
  968 
  969 
  970 // !!! don't translate
  971 QString getSystemInfo() //ttt2 perhaps store this at startup, so fewer things may go wrong fhen the assertion handler needs it
  972 {
  973     QString s ("OS: ");
  974     QString qstrDesktop;
  975 
  976 #ifndef WIN32
  977     QDir dir ("/etc");
  978 
  979     QStringList filters;
  980     filters << "*-release" << "*_version";
  981     dir.setNameFilters(filters);
  982     QStringList lFiles (dir.entryList(QDir::Files));
  983     utsname utsInfo;
  984     uname(&utsInfo);
  985     s += utsInfo.sysname; s += " ";
  986     s += utsInfo.release; s += " ";
  987     s += utsInfo.version; s += " ";
  988     for (int i = 0; i < lFiles.size(); ++i)
  989     {
  990         //qDebug("%s", lFiles[i].toUtf8().constData());
  991         if ("lsb-release" != lFiles[i])
  992         {
  993             QFile f ("/etc/" + lFiles[i]);
  994             if (f.open(QIODevice::ReadOnly))
  995             {
  996                 QByteArray b (f.read(1000));
  997                 s += b;
  998                 s += " ";
  999             }
 1000         }
 1001     }
 1002 
 1003     QFile f ("/etc/issue");
 1004     if (f.open(QIODevice::ReadOnly))
 1005     {
 1006         QByteArray b (f.read(1000));
 1007         string s1 (b.constData());
 1008 
 1009         removeStr(s1, "Welcome to");
 1010         removeStr(s1, "Kernel");
 1011         trim(s1);
 1012 
 1013         string::size_type n (s1.find('\\'));
 1014         if (string::npos != n)
 1015         {
 1016             s1.erase(n);
 1017         }
 1018         trim(s1);
 1019 
 1020         if (endsWith(s1, "-"))
 1021         {
 1022             s1.erase(s1.size() - 1);
 1023             trim(s1);
 1024         }
 1025 
 1026         s += convStr(s1);
 1027 //qDebug("a: %s", s.toUtf8().constData());
 1028         /*for (;;)
 1029         {
 1030             string::size_type n (s.find('\n'));
 1031             if (string::npos == n) { break; }
 1032             s1[n] = ' ';
 1033         }*/
 1034 //qDebug("b: %s", s.toUtf8().constData());
 1035     }
 1036 //ttt2 search /proc for kwin, metacity, ...
 1037 
 1038     const DesktopDetector& dd = getDesktopDetector();
 1039     qstrDesktop = "Desktop: " + QString(dd.m_szDesktop) + "\n";
 1040 
 1041 #else
 1042     //qstrVer += QString(" Windows version ID: %1").arg(QSysInfo::WinVersion);
 1043     QSettings settings ("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", QSettings::NativeFormat);
 1044     //qstrVer += QString(" Windows version: %1").arg(WIN32);
 1045     s += QString(" Windows version: %1 %2 Build %3 %4").arg(settings.value("ProductName").toString())
 1046                .arg(settings.value("CSDVersion").toString())
 1047                .arg(settings.value("CurrentBuildNumber").toString())
 1048                .arg(settings.value("BuildLab").toString());
 1049 
 1050 #endif
 1051     s.replace('\n', ' ');
 1052     s = QString("Version: %1 %2\nWord size: %3 bit\nQt version: %4\nBoost version: %5\n").arg(getAppName()).arg(getAppVer()).arg(QSysInfo::WordSize).arg(qVersion()).arg(BOOST_LIB_VERSION) + qstrDesktop + s;
 1053     return s;
 1054 }
 1055 
 1056 
 1057 // sets colors at various points to emulate a non-linear gradient that better suits our needs;
 1058 // dStart and dEnd should be between 0 and 1, with dStart < dEnd; they may also be both -1, in which case the gradient will have a solid color
 1059 void configureGradient(QGradient& grad, const QColor& col, double dStart, double dEnd)
 1060 {
 1061     if (-1 == dStart && -1 == dEnd)
 1062     {
 1063         grad.setColorAt(0, col);
 1064         grad.setColorAt(1, col);
 1065         return;
 1066     }
 1067 
 1068     CB_ASSERT (dStart < dEnd && 0 <= dStart && dEnd < 1.0001);
 1069     static vector<double> vdPoints;
 1070     static vector<double> vdValues;
 1071     static bool s_bInit (false);
 1072     static int SIZE;
 1073     if (!s_bInit)
 1074     {
 1075         s_bInit = true;
 1076 /*        vdPoints.push_back(-0.1); vdValues.push_back(1.3);
 1077         vdPoints.push_back(0.0); vdValues.push_back(1.3);
 1078         vdPoints.push_back(0.1); vdValues.push_back(1.2);
 1079         vdPoints.push_back(0.2); vdValues.push_back(1.15);
 1080         vdPoints.push_back(0.8); vdValues.push_back(0.85);
 1081         vdPoints.push_back(0.9); vdValues.push_back(0.8);
 1082         vdPoints.push_back(1.0); vdValues.push_back(0.7);
 1083         vdPoints.push_back(1.1); vdValues.push_back(0.7);*/
 1084 
 1085         /*vdPoints.push_back(-0.1); vdValues.push_back(1.1);
 1086         vdPoints.push_back(0.0); vdValues.push_back(1.1);
 1087         vdPoints.push_back(0.1); vdValues.push_back(1.07);
 1088         vdPoints.push_back(0.2); vdValues.push_back(1.03);
 1089         vdPoints.push_back(0.8); vdValues.push_back(0.97);
 1090         vdPoints.push_back(0.9); vdValues.push_back(0.93);
 1091         vdPoints.push_back(1.0); vdValues.push_back(0.9);
 1092         vdPoints.push_back(1.1); vdValues.push_back(0.9);*/
 1093 
 1094         /*vdPoints.push_back(-0.1); vdValues.push_back(1.3);
 1095         vdPoints.push_back(0.0); vdValues.push_back(1.3);
 1096         vdPoints.push_back(0.1); vdValues.push_back(1.15);
 1097         vdPoints.push_back(0.2); vdValues.push_back(1.03);
 1098         vdPoints.push_back(0.8); vdValues.push_back(0.95);
 1099         vdPoints.push_back(0.9); vdValues.push_back(0.9);
 1100         vdPoints.push_back(1.0); vdValues.push_back(0.8);
 1101         vdPoints.push_back(1.1); vdValues.push_back(0.8);*/
 1102 
 1103         vdPoints.push_back(-0.1); vdValues.push_back(1.03);
 1104         vdPoints.push_back(0.0); vdValues.push_back(1.03);
 1105         vdPoints.push_back(0.1); vdValues.push_back(1.02);
 1106         vdPoints.push_back(0.2); vdValues.push_back(1.01);
 1107         vdPoints.push_back(0.8); vdValues.push_back(0.95);
 1108         vdPoints.push_back(0.9); vdValues.push_back(0.9);
 1109         vdPoints.push_back(1.0); vdValues.push_back(0.8);
 1110         vdPoints.push_back(1.1); vdValues.push_back(0.8);
 1111 
 1112         SIZE = cSize(vdPoints);
 1113 
 1114 //findFont();
 1115     }
 1116 
 1117 #if 1
 1118     for (int i = 0; i < SIZE; ++i)
 1119     {
 1120         double x0 (vdPoints[i]), y0 (vdValues[i]), x1 (vdPoints[i + 1]), y1 (vdValues[i + 1]);
 1121         double x;
 1122         x = dStart;
 1123         if (x0 <= x && x < x1)
 1124         {
 1125             double y (y0 + (y1 - y0)*(x - x0)/(x1 - x0));
 1126             grad.setColorAt((x - dStart)/(dEnd - dStart), col.lighter(int(100*y)));
 1127         }
 1128 
 1129         if (dStart < x0 && x0 < dEnd)
 1130         {
 1131             grad.setColorAt((x0 - dStart)/(dEnd - dStart), col.lighter(int(100*y0)));
 1132         }
 1133 
 1134         x = dEnd;
 1135         if (x < x1)
 1136         {
 1137             double y (y0 + (y1 - y0)*(x - x0)/(x1 - x0));
 1138             grad.setColorAt((x - dStart)/(dEnd - dStart), col.lighter(int(100*y)));
 1139             break;
 1140         }
 1141     }
 1142 #else
 1143     grad.setColorAt(0, col.lighter(dStart < 0.0001 ? 119 : 100)); //ttt2 perhaps use this or at least add an option
 1144     grad.setColorAt(0.48, col);
 1145     grad.setColorAt(0.52, col);
 1146     grad.setColorAt(1, col.lighter(dEnd > 0.9999 ? 80 : 100));
 1147 #endif
 1148 }
 1149 
 1150 vector<QString> getLocalHelpDirs()
 1151 {
 1152     static vector<QString> s_v;
 1153     if (s_v.empty())
 1154     {
 1155 #ifndef WIN32
 1156         //s_v.push_back("/home/ciobi/cpp/Mp3Utils/mp3diags/trunk/mp3diags/doc/html/");
 1157         s_v.push_back(QString("/usr/share/") + getHelpPackageName() + "-doc/html/"); //ttt0 lower/uppercase variations
 1158         s_v.push_back(QString("/usr/share/doc/") + getHelpPackageName() + "/html/");
 1159         s_v.push_back(QString("/usr/share/doc/") + getHelpPackageName() + "-1.5.01/html/");
 1160 #else
 1161         wchar_t wszModule [200];
 1162         int nRes (GetModuleFileName(0, wszModule, 200));
 1163         //qDebug("%s", QString::fromWCharArray(wszModule).toUtf8().constData());
 1164         if (0 < nRes && nRes < 200)
 1165         {
 1166             s_v.push_back(QFileInfo(
 1167                     fromNativeSeparators(QString::fromWCharArray(wszModule))).dir().absolutePath() + "/doc/");
 1168             //qDebug("%s", s_v.back().toUtf8().constData());
 1169         }
 1170 #endif
 1171     }
 1172 
 1173     return s_v;
 1174 }
 1175 
 1176 
 1177 // opens a web page from the documentation in the default browser;
 1178 // first looks in several places on the local computer; if the file can't be found there, it goes to SourceForge
 1179 void openHelp(const string& strFileName)
 1180 {
 1181     const vector<QString>& v (getLocalHelpDirs());
 1182     QString strDir;
 1183     for (int i = 0; i < cSize(v); ++i)
 1184     {
 1185         if (QFileInfo(v[i] + convStr(strFileName)).isFile())
 1186         {
 1187             strDir = v[i];
 1188             break;
 1189         }
 1190     }
 1191 
 1192     QString qs (strDir);
 1193     if (qs.isEmpty())
 1194     {
 1195         qs = "http://mp3diags.sourceforge.net" + QString(getWebBranch()) + "/";
 1196     }
 1197     else
 1198     {
 1199         qs = QUrl::fromLocalFile(qs).toString();
 1200     }
 1201 
 1202     qs = qs + convStr(strFileName);
 1203 
 1204 
 1205 //qDebug("open %s", qs.toUtf8().constData());
 1206 //logToGlobalFile(qs.toUtf8().constData());
 1207     CursorOverrider ovr;
 1208     QDesktopServices::openUrl(QUrl(qs, QUrl::TolerantMode));
 1209 }
 1210 
 1211 
 1212 // meant for displaying tooltips; converts some spaces to \n, so the tooltips have several short lines instead of a single wide line
 1213 QString makeMultiline(const QString& qstrDescr)
 1214 {
 1215     QString s (qstrDescr);
 1216     int SIZE (50);
 1217     for (int i = SIZE; i < qstrDescr.size(); ++i)
 1218     {
 1219         if (' ' == s[i])
 1220         {
 1221             s[i] = '\n';
 1222             i += SIZE;
 1223         }
 1224     }
 1225     return s;
 1226 }
 1227 
 1228 
 1229 QString toNativeSeparators(const QString& s)
 1230 {
 1231     return QDir::toNativeSeparators(s);
 1232 }
 1233 
 1234 QString fromNativeSeparators(const QString& s)
 1235 {
 1236     return QDir::fromNativeSeparators(s);
 1237 }
 1238 
 1239 QString getTempDir()
 1240 {
 1241 /*
 1242 #ifndef WIN32
 1243     return "/tmp";
 1244 #else
 1245     wchar_t wszTmp [200];
 1246     if (GetTempPath(200, wszTmp) > 1999) { return ""; }
 1247     return QString::fromWCharArray(wszTmp);
 1248 #endif*/
 1249 
 1250     static QString s; // ttt3 these static variables are not really thread safe, but in this case it doesn't matter, because they all get called from a single thread (the UI thread)
 1251     if (s.isEmpty())
 1252     {
 1253         s = QDir::tempPath();
 1254         if (s.endsWith(getPathSep()))
 1255         {
 1256             s.remove(s.size() - 1, 1);
 1257         }
 1258     }
 1259     return s;
 1260 }
 1261 
 1262 
 1263 
 1264 //=============================================================================================
 1265 //=============================================================================================
 1266 //=============================================================================================
 1267 
 1268 
 1269 #if defined(__linux__)
 1270 
 1271 namespace {
 1272 
 1273 //const char* DSK_FOLDER ("~/.local/share/applications/");
 1274 const string& getDesktopIntegrationDir()
 1275 {
 1276     static string s_s;
 1277     if (s_s.empty())
 1278     {
 1279         s_s = convStr(QDir::homePath() + "/.local/share/applications");
 1280         try
 1281         {
 1282             createDir(s_s);
 1283         }
 1284         catch (const exception& ex)
 1285         {
 1286             cerr << "failed to create dir " << s_s << "; reason: " << ex.what() << endl;
 1287         }
 1288         catch (...)
 1289         { // nothing; this will cause shell integration to be disabled
 1290             cerr << "failed to create dir " << s_s << endl;
 1291         }
 1292         s_s += "/";
 1293     }
 1294     return s_s;
 1295 }
 1296 
 1297 const char* DSK_EXT (".desktop");
 1298 
 1299 class ShellIntegrator
 1300 {
 1301     Q_DECLARE_TR_FUNCTIONS(ShellIntegrator)
 1302 
 1303     string m_strFileName;
 1304     string m_strAppName;
 1305     string m_strArg;
 1306     bool m_bRebuildAssoc;
 1307 
 1308     static string escape(const string& s)
 1309     {
 1310         string s1;
 1311         static string s_strReserved (" '\\><~|&;$*?#()`"); // see http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
 1312         for (int i = 0; i < cSize(s); ++i)
 1313         {
 1314             char c (s[i]);
 1315             if (s_strReserved.find(c) != string::npos)
 1316             {
 1317                 s1 += '\\';
 1318             }
 1319             s1 += c;
 1320         }
 1321         return s1;
 1322     }
 1323 
 1324 public:
 1325     ShellIntegrator(const string& strFileNameBase, const char* szSessType, const string& strArg, bool bRebuildAssoc) :
 1326         m_strFileName(getDesktopIntegrationDir() + strFileNameBase + DSK_EXT),
 1327         m_strAppName(convStr(tr("%1 - %2").arg(getAppName()).arg(tr(szSessType)))),
 1328         m_strArg(strArg),
 1329         m_bRebuildAssoc(bRebuildAssoc) {}
 1330 
 1331     enum { DONT_REBUILD_ASSOC = 0, REBUILD_ASSOC = 1 };
 1332 
 1333     bool isEnabled()
 1334     {
 1335         return fileExists(m_strFileName);
 1336     }
 1337 
 1338     void enable(bool b)
 1339     {
 1340         if (!(b ^ isEnabled())) { return; }
 1341 
 1342         if (b)
 1343         {
 1344             char szBfr [5000] = "mP3DiAgS";
 1345             szBfr[0] = 0;
 1346             int k (readlink("/proc/self/exe", szBfr, sizeof(szBfr)));
 1347             if (k >= 0)
 1348             {
 1349                 szBfr[k] = 0;
 1350             }
 1351             szBfr[5000 - 1] = 0;
 1352 
 1353             ofstream_utf8 out (m_strFileName.c_str());
 1354             if (!out)
 1355             {
 1356                 qDebug("couldn't open file %s", m_strFileName.c_str());
 1357             }
 1358 
 1359             out << "[Desktop Entry]" << endl;
 1360             out << "Comment=" << m_strAppName << endl;
 1361             out << "Encoding=UTF-8" << endl;
 1362             out << "Exec=" << escape(szBfr) << " " << m_strArg << " %f" << endl;
 1363             out << "GenericName=" << m_strAppName << endl;
 1364             out << "Icon=" << getIconName() << endl;
 1365             out << "Name=" << m_strAppName << endl;
 1366             out << "Path=" << endl;
 1367             out << "StartupNotify=false" << endl;
 1368             out << "Terminal=0" << endl;
 1369             out << "TerminalOptions=" << endl;
 1370             out << "Type=Application" << endl;
 1371             out << "X-KDE-SubstituteUID=false" << endl;
 1372             out << "X-KDE-Username=" << endl;
 1373             out << "MimeType=inode/directory" << endl;
 1374             out << "NoDisplay=true" << endl;
 1375 
 1376             out.close();
 1377 
 1378             static bool s_bErrorReported (false);
 1379             bool bError (false);
 1380 
 1381             if (m_bRebuildAssoc && getDesktopDetector().onDesktop(DesktopDetector::Kde))
 1382             {
 1383                 TRACER1A("ShellIntegrator::enable()", 1);
 1384                 QProcess kbuildsycoca4;
 1385                 kbuildsycoca4.start("kbuildsycoca4");
 1386                 TRACER1A("ShellIntegrator::enable()", 2);
 1387 
 1388                 if (!kbuildsycoca4.waitForStarted()) //ttt1 switch to non-blocking calls if these don't work well enough
 1389                 {
 1390                     TRACER1A("ShellIntegrator::enable()", 3);
 1391                     bError = true;
 1392                 }
 1393                 else
 1394                 {
 1395                     TRACER1A("ShellIntegrator::enable()", 4);
 1396                     kbuildsycoca4.closeWriteChannel();
 1397                     TRACER1A("ShellIntegrator::enable()", 5);
 1398                     if (!kbuildsycoca4.waitForFinished())
 1399                     {
 1400                         TRACER1A("ShellIntegrator::enable()", 6);
 1401                         bError = true;
 1402                     }
 1403                 }
 1404                 TRACER1A("ShellIntegrator::enable()", 7);
 1405 
 1406                 if (bError && !s_bErrorReported)
 1407                 {
 1408                     s_bErrorReported = true;
 1409                     HtmlMsg::msg(0, 0, 0, 0, HtmlMsg::CRITICAL, tr("Error setting up shell integration"), tr("It appears that setting up shell integration didn't complete successfully. You might have to configure it manually.") + "<p/>"
 1410                                  + tr("This message will not be shown again until the program is restarted, even if more errors occur.")
 1411                                  , 400, 300, tr("O&K"));
 1412                 }
 1413             }
 1414         }
 1415         else
 1416         {
 1417             try
 1418             {
 1419                 deleteFile(m_strFileName);
 1420             }
 1421             catch (const exception&)
 1422             { //ttt2 do something
 1423             }
 1424             catch (...)
 1425             { //ttt2 do something
 1426             }
 1427         }
 1428     }
 1429 };
 1430 
 1431 ShellIntegrator g_tempShellIntegrator ("mp3DiagsTempSess", QT_TRANSLATE_NOOP("ShellIntegrator", "temporary folder"), "-t", ShellIntegrator::REBUILD_ASSOC);
 1432 ShellIntegrator g_hiddenShellIntegrator ("mp3DiagsHiddenSess", QT_TRANSLATE_NOOP("ShellIntegrator", "hidden folder"), "-f", ShellIntegrator::REBUILD_ASSOC);
 1433 ShellIntegrator g_visibleShellIntegrator ("mp3DiagsVisibleSess", QT_TRANSLATE_NOOP("ShellIntegrator", "visible folder"), "-v", ShellIntegrator::REBUILD_ASSOC);
 1434 
 1435 ShellIntegrator g_testShellIntegrator ("mp3DiagsTestSess_000", "test", "", ShellIntegrator::DONT_REBUILD_ASSOC);
 1436 
 1437 } // namespace
 1438 
 1439 
 1440 /*static*/ bool ShellIntegration::isShellIntegrationEditable()
 1441 {
 1442     g_testShellIntegrator.enable(true);
 1443     bool b (g_testShellIntegrator.isEnabled());
 1444     g_testShellIntegrator.enable(false);
 1445     return b;
 1446 }
 1447 
 1448 
 1449 /*static*/ string ShellIntegration::getShellIntegrationError()
 1450 {
 1451     return "";
 1452 }
 1453 
 1454 
 1455 /*static*/ void ShellIntegration::enableTempSession(bool b)
 1456 {
 1457     g_tempShellIntegrator.enable(b);
 1458 }
 1459 
 1460 /*static*/ bool ShellIntegration::isTempSessionEnabled()
 1461 {
 1462     return g_tempShellIntegrator.isEnabled();
 1463 }
 1464 
 1465 /*static*/ void ShellIntegration::enableVisibleSession(bool b)
 1466 {
 1467     g_visibleShellIntegrator.enable(b);
 1468 }
 1469 
 1470 /*static*/ bool ShellIntegration::isVisibleSessionEnabled()
 1471 {
 1472     return g_visibleShellIntegrator.isEnabled();
 1473 }
 1474 
 1475 /*static*/ void ShellIntegration::enableHiddenSession(bool b)
 1476 {
 1477     g_hiddenShellIntegrator.enable(b);
 1478 }
 1479 
 1480 /*static*/ bool ShellIntegration::isHiddenSessionEnabled()
 1481 {
 1482     return g_hiddenShellIntegrator.isEnabled();
 1483 }
 1484 
 1485 
 1486 #elif defined (WIN32)
 1487 
 1488 namespace {
 1489 
 1490 //ttt2 use a class instead of functions, to handle errors better
 1491 
 1492 struct RegKey
 1493 {
 1494     HKEY m_hKey;
 1495     RegKey() : m_hKey(0) {}
 1496 
 1497     ~RegKey()
 1498     {
 1499         RegCloseKey(m_hKey);
 1500     }
 1501 };
 1502 
 1503 /*
 1504 {
 1505     HKEY hkey;
 1506     if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_CLASSES_ROOT, L"Directory\\shell", 0, KEY_READ, &hkey))
 1507     {
 1508         RegCloseKey(hkey);
 1509     }
 1510 
 1511     if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_CLASSES_ROOT, L"Directory\\shell", 0, KEY_WRITE, &hkey))
 1512     {
 1513         HKEY hSubkey;
 1514         if (ERROR_SUCCESS == RegCreateKeyEx(hkey, L"MySubkey", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hSubkey, NULL))
 1515         {
 1516             if (ERROR_SUCCESS == RegSetValueExA(hSubkey, NULL, 0, REG_SZ, (const BYTE*)("my string"), 10))
 1517             {
 1518                 cout << "OK\n";
 1519             }
 1520             RegCloseKey(hSubkey);
 1521         }
 1522         RegCloseKey(hkey);
 1523     }
 1524 }
 1525 
 1526 */
 1527 
 1528 
 1529 //bool doesKeyExist(const wchar_t* wszPath)
 1530 bool doesKeyExist(const char* szPath)
 1531 {
 1532     RegKey key;
 1533     return ERROR_SUCCESS == RegOpenKeyExA(HKEY_CLASSES_ROOT, szPath, 0, KEY_READ, &key.m_hKey);
 1534 }
 1535 
 1536 //bool createEntries(const wchar_t* wszPath, const wchar_t* wszSubkey, const wchar_t* wszDescr, const wchar_t* wszCommand)
 1537 bool createEntries(const char* szPath, const char* szSubkey, const char* szDescr, const char* szParam)
 1538 {
 1539     string s (string("\"") + _pgmptr + "\" " + szParam);
 1540     const char* szCommand (s.c_str());
 1541 
 1542     RegKey key;
 1543 
 1544     if (ERROR_SUCCESS != RegOpenKeyExA(HKEY_CLASSES_ROOT, szPath, 0, KEY_WRITE, &key.m_hKey)) { return false; }
 1545 
 1546     RegKey subkey;
 1547     if (ERROR_SUCCESS != RegCreateKeyExA(key.m_hKey, szSubkey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &subkey.m_hKey, NULL)) { return false; }
 1548     if (ERROR_SUCCESS != RegSetValueExA(subkey.m_hKey, NULL, 0, REG_SZ, (const BYTE*)szDescr, strlen(szDescr) + 1)) { return false; }
 1549 
 1550     RegKey commandKey;
 1551     if (ERROR_SUCCESS != RegCreateKeyExA(subkey.m_hKey, "command", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &commandKey.m_hKey, NULL)) { return false; }
 1552     if (ERROR_SUCCESS != RegSetValueExA(commandKey.m_hKey, NULL, 0, REG_SZ, (const BYTE*)szCommand, strlen(szCommand) + 1)) { return false; }
 1553 
 1554     return true;
 1555 }
 1556 
 1557 bool deleteKey(const char* szPath, const char* szSubkey)
 1558 {
 1559     RegKey key;
 1560 
 1561     if (ERROR_SUCCESS != RegOpenKeyExA(HKEY_CLASSES_ROOT, szPath, 0, KEY_WRITE, &key.m_hKey)) { return false; }
 1562 
 1563     RegKey subkey;
 1564     if (ERROR_SUCCESS != RegCreateKeyExA(key.m_hKey, szSubkey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &subkey.m_hKey, NULL)) { return false; }
 1565 
 1566     if (ERROR_SUCCESS != RegDeleteKeyA(subkey.m_hKey, "command")) { return false; }
 1567 
 1568     if (ERROR_SUCCESS != RegDeleteKeyA(key.m_hKey, szSubkey)) { return false; }
 1569 
 1570     return true;
 1571 }
 1572 
 1573 } // namespace
 1574 
 1575 //--------------------------------------------------------------
 1576 
 1577 //ttt1 use something like ShellIntegrator in the Linux code
 1578 
 1579 /*static*/ bool ShellIntegration::isShellIntegrationEditable()
 1580 {
 1581     //RegKey key;
 1582     // return ERROR_SUCCESS == RegOpenKeyExA(HKEY_CLASSES_ROOT, "Directory\\shell", 0, KEY_WRITE, &key.m_hKey); //!!! if compiled with MinGW on XP there's this issue on W7: it seems to work but it looks like it uses a temporary path, which gets soon deleted
 1583 
 1584     createEntries("Directory\\shell", "test_000_mp3diags", "test", "-t \"%1\"");
 1585     if (!doesKeyExist("Directory\\shell\\test_000_mp3diags")) { return false; }
 1586 
 1587     deleteKey("Directory\\shell", "test_000_mp3diags");
 1588 
 1589     return true;
 1590 }
 1591 
 1592 
 1593 /*static*/ string ShellIntegration::getShellIntegrationError()
 1594 {
 1595     if (isShellIntegrationEditable()) { return ""; }
 1596     return convStr(GlobalTranslHlp::tr("These settings cannot currently be changed. In order to make changes you should probably run the program as an administrator."));
 1597 }
 1598 
 1599 
 1600 
 1601 /*static*/ void ShellIntegration::enableTempSession(bool b)
 1602 {
 1603     if (!(isTempSessionEnabled() ^ b)) { return; } // no change needed
 1604     if (b)
 1605     {
 1606         createEntries("Directory\\shell", "mp3diags_temp_dir", "Open as temporary folder in MP3 Diags", "-t \"%1\"");
 1607         createEntries("Drive\\shell", "mp3diags_temp_dir", "Open as temporary folder in MP3 Diags", "-t %1");
 1608     }
 1609     else
 1610     {
 1611         deleteKey("Directory\\shell", "mp3diags_temp_dir");
 1612         deleteKey("Drive\\shell", "mp3diags_temp_dir");
 1613     }
 1614 }
 1615 
 1616 /*static*/ bool ShellIntegration::isTempSessionEnabled()
 1617 {
 1618     return doesKeyExist("Directory\\shell\\mp3diags_temp_dir");
 1619 }
 1620 
 1621 
 1622 /*static*/ void ShellIntegration::enableVisibleSession(bool b)
 1623 {
 1624     if (!(isVisibleSessionEnabled() ^ b)) { return; } // no change needed
 1625     if (b)
 1626     {
 1627         createEntries("Directory\\shell", "mp3diags_visible_dir", "Open as visible folder in MP3 Diags", "-v \"%1\"");
 1628         createEntries("Drive\\shell", "mp3diags_visible_dir", "Open as visible folder in MP3 Diags", "-v %1");
 1629     }
 1630     else
 1631     {
 1632         deleteKey("Directory\\shell", "mp3diags_visible_dir");
 1633         deleteKey("Drive\\shell", "mp3diags_visible_dir");
 1634     }
 1635 }
 1636 
 1637 /*static*/ bool ShellIntegration::isVisibleSessionEnabled()
 1638 {
 1639     return doesKeyExist("Directory\\shell\\mp3diags_visible_dir");
 1640 }
 1641 
 1642 
 1643 /*static*/ void ShellIntegration::enableHiddenSession(bool b)
 1644 {
 1645     if (!(isHiddenSessionEnabled() ^ b)) { return; } // no change needed
 1646     if (b)
 1647     {
 1648         createEntries("Directory\\shell", "mp3diags_hidden_dir", "Open as hidden folder in MP3 Diags", "-f \"%1\"");
 1649         createEntries("Drive\\shell", "mp3diags_hidden_dir", "Open as hidden folder in MP3 Diags", "-f %1");
 1650     }
 1651     else
 1652     {
 1653         deleteKey("Directory\\shell", "mp3diags_hidden_dir");
 1654         deleteKey("Drive\\shell", "mp3diags_hidden_dir");
 1655     }
 1656 }
 1657 
 1658 /*static*/ bool ShellIntegration::isHiddenSessionEnabled()
 1659 {
 1660     return doesKeyExist("Directory\\shell\\mp3diags_hidden_dir");
 1661 }
 1662 
 1663 #else
 1664 
 1665 /*static*/ bool ShellIntegration::isShellIntegrationEditable()
 1666 {
 1667     return false;
 1668 }
 1669 
 1670 
 1671 /*static*/ string ShellIntegration::getShellIntegrationError()
 1672 {
 1673     return convStr(GlobalTranslHlp::tr("Platform not supported"));
 1674 }
 1675 
 1676 
 1677 /*static*/ void ShellIntegration::enableTempSession(bool)
 1678 {
 1679 }
 1680 
 1681 /*static*/ bool ShellIntegration::isTempSessionEnabled()
 1682 {
 1683     return false;
 1684 }
 1685 
 1686 /*static*/ void ShellIntegration::enableVisibleSession(bool)
 1687 {
 1688 }
 1689 
 1690 /*static*/ bool ShellIntegration::isVisibleSessionEnabled()
 1691 {
 1692     return false;
 1693 }
 1694 
 1695 /*static*/ void ShellIntegration::enableHiddenSession(bool)
 1696 {
 1697 }
 1698 
 1699 /*static*/ bool ShellIntegration::isHiddenSessionEnabled()
 1700 {
 1701     return false;
 1702 }
 1703 
 1704 #endif
 1705 
 1706 
 1707 
 1708 //=============================================================================================
 1709 //=============================================================================================
 1710 //=============================================================================================
 1711 
 1712 Tracer::Tracer(const std::string& s) : m_s(s)
 1713 {
 1714     traceToFile("> " + s, 1);
 1715 #ifdef OUTPUT_TRACE_TO_CONSOLE
 1716     qDebug("> %s", m_s.c_str());
 1717 #endif
 1718 }
 1719 
 1720 Tracer::~Tracer()
 1721 {
 1722     traceToFile(" < " + m_s, -1);
 1723 #ifdef OUTPUT_TRACE_TO_CONSOLE
 1724     qDebug("< %s", m_s.c_str());
 1725 #endif
 1726 }
 1727 
 1728 
 1729 //=============================================================================================
 1730 
 1731 LastStepTracer::LastStepTracer(const std::string& s) : m_s(s)
 1732 {
 1733     traceLastStep("> " + s, 1);
 1734 }
 1735 
 1736 LastStepTracer::~LastStepTracer()
 1737 {
 1738     traceLastStep(" < " + m_s, -1);
 1739 }
 1740 
 1741 //=============================================================================================
 1742 //=============================================================================================
 1743 //=============================================================================================
 1744 
 1745 // Patch by Elbert Pol, to make the linker work on OS/2. Not sure why adding "LIBS += -lrt" to src.pro didn't seem to work.
 1746 # if defined OS2
 1747 inline int
 1748 clock_gettime(int clock_id, struct timespec *ts)
 1749 {
 1750      struct timeval tv;
 1751 
 1752 #if 0
 1753      if (clock_id != CLOCK_REALTIME) {
 1754          errno = EINVAL;
 1755          return (-1);
 1756      }
 1757 #endif
 1758      if (gettimeofday(&tv, NULL) < 0)
 1759          return (-1);
 1760      ts->tv_sec = tv.tv_sec;
 1761      ts->tv_nsec = tv.tv_usec * 1000;
 1762      return (0);
 1763 }
 1764 #endif
 1765 
 1766 int64_t CB_LIB_CALL Timer::getCrtTime() const // returns time in nanoseconds
 1767 {
 1768 #ifdef _WIN32
 1769     LARGE_INTEGER li;
 1770     QueryPerformanceCounter(&li);
 1771     return li.QuadPart*m_nDurMul;
 1772 #else
 1773     timespec ts;
 1774     //clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts); // CLOCK_MONOTONIC
 1775     clock_gettime(CLOCK_REALTIME, &ts);
 1776     return ts.tv_sec*1000000000LL + ts.tv_nsec;
 1777 #endif
 1778 }
 1779 
 1780 
 1781 CB_LIB_CALL Timer::Timer(bool bStart /*= true*/) : m_nStart(-1), m_nFinish(-1), m_pStoredDuration(0)
 1782 {
 1783 #ifdef _WIN32
 1784     LARGE_INTEGER li;
 1785     if (QueryPerformanceFrequency(&li))
 1786     {
 1787         m_nDurMul = (int64_t)(1e9/li.QuadPart);
 1788     }
 1789     else
 1790     {
 1791         m_nDurMul = 0;
 1792     }
 1793 #endif
 1794     if (bStart) start();
 1795 }
 1796 
 1797 CB_LIB_CALL Timer::Timer(int64_t& storedDuration, bool bStart /*= true*/) : m_nStart(-1), m_nFinish(-1), m_pStoredDuration(&storedDuration)
 1798 {
 1799 #ifdef _WIN32
 1800     LARGE_INTEGER li;
 1801     if (QueryPerformanceFrequency(&li))
 1802     {
 1803         m_nDurMul = (int64_t)(1e9/li.QuadPart);
 1804     }
 1805     else
 1806     {
 1807         m_nDurMul = 0;
 1808     }
 1809 #endif
 1810     if (bStart) start();
 1811 }
 1812 
 1813 
 1814 Timer::~Timer() {
 1815     if (m_pStoredDuration != 0) {
 1816         if (m_nFinish == -1) {
 1817             *m_pStoredDuration = stop();
 1818         } else {
 1819             *m_pStoredDuration = getDuration();
 1820         }
 1821     }
 1822 }
 1823 
 1824 /*static*/ std::string CB_LIB_CALL Timer::addThSep(int64_t nTime) // to be used when converting to milli- / micro- seconds
 1825 {
 1826     char a [25];
 1827     //sprintf(a, "%f", double(nTime)); //
 1828     //sprintf(a, "%lld", nTime);
 1829     sprintf(a, "%ld", nTime);//ttt00 warning in Ubuntu 9.04 due to type mismatch
 1830     int n ((int)strlen(a));
 1831     std::string strRes;
 1832     for (int i = 0; i < n; ++i)
 1833     {
 1834         if (!strRes.empty() && 0 == (n - i)%3) { strRes += ','; } //ttt2 assumes that "a" doesn't have separators already
 1835         strRes += a[i];
 1836     }
 1837     return strRes;
 1838 }
 1839 
 1840 
 1841 /*static*/ std::string Timer::getLongFmt(int64_t dur) {
 1842     int msec (dur / 1000000);
 1843     int sec (msec / 1000); msec %= 1000;
 1844     int min (sec / 60); sec %= 60;
 1845     int hrs (min / 60); min %= 60;
 1846     char a[40];
 1847     sprintf(a, "%d:%02d:%02d.%03d", hrs, min, sec, msec);
 1848     return a;
 1849 }
 1850 
 1851 //=============================================================================================
 1852 //=============================================================================================
 1853 //=============================================================================================
 1854 
 1855 void decreaseRowHeaderFont(QTableView& qtableView)
 1856 {
 1857 #ifdef WIN32
 1858     QFont font(qtableView.verticalHeader()->font());
 1859     auto sz(font.pointSizeF());
 1860     font.setPointSizeF(sz * 0.85);
 1861     qtableView.verticalHeader()->setFont(font);
 1862 #endif
 1863 }
 1864 
 1865 
 1866 //ttt2 F1 help was very slow on XP once, not sure why; later it was OK
 1867 
 1868 
 1869 
 1870 
 1871 
 1872 //ttt1 maybe switch to new spec, lower-case for exe name, package, and icons
 1873 
 1874 
 1875