Salome HOME
updated copyright message
[modules/gui.git] / src / Qtx / Qtx.cxx
1 // Copyright (C) 2007-2023  CEA, EDF, OPEN CASCADE
2 //
3 // Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
4 // CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
5 //
6 // This library is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU Lesser General Public
8 // License as published by the Free Software Foundation; either
9 // version 2.1 of the License, or (at your option) any later version.
10 //
11 // This library 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 GNU
14 // Lesser General Public License for more details.
15 //
16 // You should have received a copy of the GNU Lesser General Public
17 // License along with this library; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
19 //
20 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
21 //
22
23 // File:      Qtx.cxx
24 // Author:    Sergey TELKOV
25 //
26 #include "Qtx.h"
27
28 #include <QDir>
29 #include <QMenu>
30 #include <QRegExp>
31 #include <QBitmap>
32 #include <QWidget>
33 #include <QLayout>
34 #include <QPainter>
35 #include <QDirModel>
36 #include <QFileInfo>
37 #include <QCompleter>
38 #include <QApplication>
39 #include <QDesktopWidget>
40 #include <QtDebug>
41 #include <QSurfaceFormat>
42
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <stdarg.h>
46 #include <clocale>
47
48 #ifdef WIN32
49 #include <windows.h>
50 #define MAX_VALUE_SIZE 32767 // Limit according to http://msdn.microsoft.com/en-us/library/ms683188.aspx
51 #endif
52
53 #include <iostream>
54
55 #define BICOLOR_CHANGE_HUE
56
57 /*!
58   \brief Auxiliary function converting string \a str to the integer value.
59   Parameter \a defVal specifies default value that is returned if conversion can't be done.
60   Parameters \a minVal and \a maxVal limit the resulting value.
61   \param str string being converted
62   \param defVal default value
63   \param minVal minimum allowed value
64   \param maxVal maximum allowed value
65   \return integer value obtained from the string
66   \internal
67 */
68 static int stringToInt( const QString& str, int defVal, int minVal, int maxVal )
69 {
70   bool ok;
71   int v = str.toInt( &ok );
72   if ( !ok ) v = defVal;
73   return qMin( qMax( v, minVal ), maxVal );
74 }
75
76 /*!
77   \class Qtx
78   \brief A set of helpful utility functions.
79
80   The class implements a set of the static functions which can be used
81   for the different purposes:
82   - specify tab order for the set of widgets: setTabOrder()
83   - align one widget to the coordinates of the another one: alignWidget()
84   - remove extra separators from the menu or toolbar: simplifySeparators()
85   - retrieve directory, file name and extension parts of the path:
86   dir(), file(), extension()
87   - get the path to the temporary directory: tmpDir()
88   - create or remove a directory (recursively): mkDir(), rmDir()
89   - convert text file from DOS to UNIX native format: dos2unix()
90   - convert a picture to the gray scale: grayscale()
91   - other
92 */
93
94 /*!
95   \brief Convert character array (ASCII string) to the QString.
96   \param str character array
97   \param len array length, if < 0, the array should be zero-terminated
98   \return QString object
99  */
100 QString Qtx::toQString( const char* str, const int len )
101 {
102   return toQString( (unsigned char*)str, len );
103 }
104
105 /*!
106   \brief Convert integer array (UNICODE string) to the QString.
107   \param str integer array
108   \param len array length, if < 0, the array should be zero-terminated
109   \return QString object
110  */
111 QString Qtx::toQString( const short* str, const int len )
112 {
113   return toQString( (unsigned short*)str, len );
114 }
115
116 /*!
117   \brief Convert character array (ASCII string) to the QString.
118   \param str character array
119   \param len array length, if < 0, the array should be zero-terminated
120   \return QString object
121  */
122 QString Qtx::toQString( const unsigned char* str, const int len )
123 {
124   QString res;
125   const unsigned char* s = str;
126   while ( len < 0 || res.length() < len )
127   {
128     if ( *s == '\0' )
129       break;
130
131     res.append( QChar( *s ) );
132     s++;
133   }
134   return res;
135 }
136
137 /*!
138   \brief Convert integer array (UNICODE string) to the QString.
139   \param str integer array
140   \param len array length, if < 0, the array should be zero-terminated
141   \return QString object
142  */
143 QString Qtx::toQString( const unsigned short* str, const int len )
144 {
145   QString res;
146   const unsigned short* s = str;
147   while ( len < 0 || res.length() < len )
148   {
149     if ( *s == '\0' )
150       break;
151
152     res.append( QChar( *s ) );
153     s++;
154   }
155   return res;
156 }
157
158 /*!
159   \brief Set tab order for specified list of widgets.
160
161   The function has arbitrary number of parameters, each should be
162   hovewer of QWidget* type. Last parameter should be null pointer.
163
164   \param first first widget in the sequence
165 */
166 void Qtx::setTabOrder( QWidget* first, ... )
167 {
168   va_list wids;
169   va_start( wids, first );
170
171   QWidgetList widList;
172
173   QWidget* cur = first;
174   while ( cur )
175   {
176     widList.append( cur );
177     cur = va_arg( wids, QWidget* );
178   }
179
180   setTabOrder( widList );
181 }
182
183 /*!
184   \brief Set tab order for specified list of widgets.
185   \param widgets list of widgets
186 */
187 void Qtx::setTabOrder( const QWidgetList& widgets )
188 {
189   if ( widgets.count() < 2 )
190     return;
191
192   QWidget* prev = 0;
193   for ( QWidgetList::const_iterator it = widgets.begin(); it!= widgets.end(); ++it )
194   {
195     QWidget* next = *it;
196     if ( prev && next )
197       QWidget::setTabOrder( prev, next );
198     prev = next;
199   }
200 }
201
202 /*!
203   \brief Align widget \a src relative to widget \a ref acording to the 
204          alignment flags \a alignFlags.
205   \param src source widget (being aligned)
206   \param ref reference widget (source widget being aligned to)
207   \param alignFlags alignment flags (Qtx::AlignmentFlags)
208 */
209 void Qtx::alignWidget( QWidget* src, const QWidget* ref, const int alignFlags )
210 {
211   if ( !src || !ref || !alignFlags )
212     return;
213
214   QPoint srcOri = src->pos();
215   QPoint refOri = ref->pos();
216   if ( src->parentWidget() && !src->isTopLevel() )
217     srcOri = src->parentWidget()->mapToGlobal( srcOri );
218   if ( ref->parentWidget() && !ref->isTopLevel() )
219     refOri = ref->parentWidget()->mapToGlobal( refOri );
220
221   int x = srcOri.x(), y = srcOri.y();
222   int refWidth = ref->frameGeometry().width(), refHei = ref->frameGeometry().height();
223   int srcWidth = src->frameGeometry().width(), srcHei = src->frameGeometry().height();
224
225   if ( srcWidth <= 0 )
226     srcWidth = src->sizeHint().width();
227   if ( srcHei <= 0 )
228     srcHei = src->sizeHint().height();
229
230   int border = 0;
231   if ( ref->isTopLevel() && ref->isMaximized() &&
232        src->isTopLevel() && !src->isMaximized() )
233     border = ( src->frameGeometry().width() - src->width() ) / 2;
234
235   if ( alignFlags & Qtx::AlignLeft )
236     x = refOri.x() + border;
237   if ( alignFlags & Qtx::AlignOutLeft )
238     x = refOri.x() - srcWidth - border;
239   if ( alignFlags & Qtx::AlignRight )
240     x = refOri.x() + refWidth - srcWidth - border;
241   if ( alignFlags & Qtx::AlignOutRight )
242     x = refOri.x() + refWidth + border;
243   if ( alignFlags & Qtx::AlignTop )
244     y = refOri.y() + border;
245   if ( alignFlags & Qtx::AlignOutTop )
246     y = refOri.y() - srcHei - border;
247   if ( alignFlags & Qtx::AlignBottom )
248     y = refOri.y() + refHei - srcHei - border;
249   if ( alignFlags & Qtx::AlignOutBottom )
250     y = refOri.y() + refHei + border;
251   if ( alignFlags & Qtx::AlignHCenter )
252     x = refOri.x() + ( refWidth - srcWidth ) / 2;
253   if ( alignFlags & Qtx::AlignVCenter )
254     y = refOri.y() + ( refHei - srcHei ) / 2;
255
256   if ( src->parentWidget() && !src->isTopLevel() )
257   {
258     QPoint pos = src->parentWidget()->mapFromGlobal( QPoint( x, y ) );
259     x = pos.x();
260     y = pos.y();
261   }
262
263   QWidget* desk = QApplication::desktop();
264   if ( desk && x + srcWidth + border > desk->width() )
265     x = desk->width() - srcWidth - border;
266   if ( desk && y + srcHei + border > desk->height() )
267     y = desk->height() - srcHei - border;
268
269   x = qMax( x, 0 );
270   y = qMax( y, 0 );
271
272   src->move( x, y );
273 }
274
275 /*!
276   \brief Remove (recursively) unnecessary separators from the menu or toolbar.
277   \param wid widget, should be of QMenu* or QToolBar* class
278 */
279 void Qtx::simplifySeparators( QWidget* wid, const bool recursive )
280 {
281   if ( !wid )
282     return;
283
284   if ( wid->inherits( "QMenu") || wid->inherits( "QMenuBar") )
285   {
286     if ( qobject_cast<QMenu*>( wid ) )
287       qobject_cast<QMenu*>( wid )->setSeparatorsCollapsible( true );
288     if ( recursive )
289     {
290       foreach ( QAction* action, wid->actions() )
291       {
292         if ( action->menu() )
293           simplifySeparators( action->menu(), recursive );
294       }
295     }
296   }
297   else
298   {
299     QList<QAction*> actions = wid->actions();
300     if ( actions.isEmpty() )
301       return;
302
303     bool is_action = false;
304     for ( int i = 0; i < actions.count(); i++ )
305     {
306       QAction* action = actions[i];
307       if ( action->isSeparator() )
308       {
309         action->setVisible( is_action );
310         is_action = false;
311       }
312       else if ( action->isVisible() )
313       {
314         is_action = true;
315       }
316     }
317     is_action = false;
318     for ( int i = actions.count() - 1; i > 0; i-- )
319     {
320       QAction* action = actions[i];
321       if ( action->isSeparator() )
322       {
323         action->setVisible( is_action );
324         is_action = false;
325       }
326       else if ( action->isVisible() )
327       {
328         is_action = true;
329       }
330     }
331   }
332 }
333
334 /*!
335   \brief Return \c true if specified \a parent is a parent object
336          of given \a child (in terms of QObject).
337
338   This function works recursively. It means that \a true is also
339   returned if \a parent is a grand-father, grand-grand-father, etc
340   of \a child. If the same object is given as both \a parent and
341   \a child, \c true is also returned.
342
343   \param child child object
344   \param parent parent object
345   \return \c true if the \a parent is a parent of \a child
346 */
347 bool Qtx::isParent( QObject* child, QObject* parent )
348 {
349   if ( !child || !parent )
350     return false;
351
352   bool res = false;
353   QObject* obj = child;
354   while ( !res && obj )
355   {
356     res = obj == parent;
357     obj = obj->parent();
358   }
359   return res;
360 }
361
362 /*!
363   \brief Find the parent object of class specified by \a className (in terms of QObject).
364
365   \param obj current object
366   \param className class name of the parent
367   \return parent object or null pointer if the parent not found
368 */
369 QObject* Qtx::findParent( QObject* obj, const char* className )
370 {
371   if ( !obj )
372     return 0;
373
374   if ( !className || !strlen( className ) )
375     return obj->parent();
376
377   QObject* res = 0;
378   QObject* p = obj->parent();
379   while ( p && !res )
380   {
381     if ( p->inherits( className ) )
382       res = p;
383     p = p->parent();
384   }
385
386   return res;
387 }
388
389 /*!
390   \brief Return directory part of the file path.
391
392   If the file path does not include directory part (the file is in the
393   current directory), null string is returned.
394
395   \param path file path
396   \param abs if true (default) \a path parameter is treated as absolute file path
397   \return directory part of the file path
398 */
399 QString Qtx::dir( const QString& path, const bool abs )
400 {
401   QDir aDir = QFileInfo( path ).dir();
402   QString dirPath = abs ? aDir.absolutePath() : aDir.path();
403   if ( dirPath == QString( "." ) )
404     dirPath = QString();
405   return dirPath;
406 }
407
408 /*!
409   \brief Return file name part of the file path.
410
411   \param path file path
412   \param withExt if true (default) complete file name (with all
413          extension except the last) is returned, otherwise only base name
414          is returned
415   \return file name part of the file path
416 */
417 QString Qtx::file( const QString& path, bool withExt )
418 {
419   QString fPath = path;
420   while ( !fPath.isEmpty() && ( fPath[fPath.length() - 1] == '\\' || fPath[fPath.length() - 1] == '/' ) )
421     fPath.remove( fPath.length() - 1, 1 );
422
423   if ( withExt )
424     return QFileInfo( fPath ).fileName();
425   else
426     return QFileInfo( fPath ).completeBaseName();
427 }
428
429 /*!
430   \brief Return extension part of the file path.
431
432   \param path file path
433   \param full if true complete extension (all extensions, dot separated)
434          is returned, otherwise (default) only last extension is returned
435   \return extension part of the file path
436 */
437 QString Qtx::extension( const QString& path, const bool full )
438 {
439   return full ? QFileInfo( path ).completeSuffix() : QFileInfo( path ).suffix();
440 }
441
442 /*!
443   \brief EXtract base library name.
444
445   The function removes platform-specific prefix (lib) and suffix (.dll/.so)
446   from the library file name.
447   For example, if \a str = "libmylib.so", "mylib" is returned..
448
449   \param libName library name
450   \return base library name
451 */
452 QString Qtx::libraryName( const QString& libName )
453 {
454   QString fullName = file( libName );
455 #if defined(WIN32)
456   QString libExt = QString( "dll" );
457 #elif defined(__APPLE__)
458   QString libExt = QString( "dylib" );
459 #else
460   QString libExt = QString( "so" );
461 #endif
462   if ( extension( fullName ).toLower() == libExt )
463     fullName.truncate( fullName.length() - libExt.length() - 1 );
464 #ifndef WIN32
465   QString prefix = QString( "lib" );
466   if ( fullName.startsWith( prefix ) )
467     fullName.remove( 0, prefix.length() );
468 #endif
469   return fullName;
470 }
471
472 /*!
473   \brief Convert the given parameter to the platform-specific library name.
474
475   The function appends platform-specific prefix (lib) and suffix (.dll/.so)
476   to the library file name.
477   For example, if \a libName = "mylib", "libmylib.so" is returned for Linux and
478   mylib.dll for Windows.
479
480   \param libName short library name
481   \return full library name
482 */
483 QString Qtx::library( const QString& libName )
484 {
485   QString path = dir( libName, false );
486   QString name = file( libName, false );
487   QString ext  = extension( libName );
488
489 #ifndef WIN32
490   if ( !name.startsWith( "lib" ) )
491     name = QString( "lib" ) + name;
492 #endif
493
494 #if defined(WIN32)
495   QString libExt( "dll" );
496 #elif defined(__APPLE__)
497   QString libExt( "dylib" );
498 #else
499   QString libExt( "so" );
500 #endif
501
502   if ( ext.toLower() != QString( "so" ) && ext.toLower() != QString( "dll" ) && ext.toLower() != QString( "dylib" ) )
503   {
504     if ( !name.isEmpty() && !ext.isEmpty() )
505       name += QString( "." );
506     name += ext;
507   }
508
509   ext = libExt;
510
511   QString fileName = addSlash( path ) + name + QString( "." ) + ext;
512
513   return fileName;
514 }
515
516 /*!
517   \brief Get the temporary directory name.
518   \return temporary directory (platform specific)
519 */
520 QString Qtx::tmpDir()
521 {
522   QString tmpdir = getenv( "TEMP" );
523   if ( tmpdir.isEmpty() )
524     tmpdir = getenv ( "TMP" );
525   if ( tmpdir.isEmpty() )
526   {
527 #ifdef WIN32
528     tmpdir = QString("C:\\");
529 #else
530     tmpdir = QString("/tmp");
531 #endif
532   }
533   return tmpdir;
534 }
535
536 /*!
537   \brief Create directory recursively including all intermediate sub directories.
538   \return \c true if the directory is successfully created and \c false otherwise
539 */
540 bool Qtx::mkDir( const QString& dirPath )
541 {
542   return QDir().mkpath( dirPath );
543 }
544
545 /*!
546   \brief Remove directory recursively including all subdirectories and files.
547   \return \c true if the directory is successfully removed and \c false otherwise
548 */
549 bool Qtx::rmDir( const QString& thePath )
550 {
551   QFileInfo fi( thePath );
552   if ( !fi.exists() )
553     return true;
554
555   bool stat = true;
556   if ( fi.isFile() )
557     stat = QFile::remove( thePath );
558   else if ( fi.isDir() )
559   {
560     QDir aDir( thePath );
561     QFileInfoList anEntries = aDir.entryInfoList();
562     for ( QFileInfoList::iterator it = anEntries.begin(); it != anEntries.end(); ++it )
563     {
564       QFileInfo inf = *it;
565       if ( inf.fileName() == "." || inf.fileName() == ".." )
566         continue;
567       stat = stat && rmDir( inf.absoluteFilePath() );
568     }
569     stat = stat && aDir.rmdir( thePath );
570   }
571   return stat;
572 }
573
574 /*!
575   \brief Add a slash (platform-specific) to the end of \a path
576          if it is not already there.
577   \param path directory path
578   \return modified path (with slash added to the end)
579 */
580 QString Qtx::addSlash( const QString& path )
581 {
582   QString res = path;
583   if ( !res.isEmpty() && res.at( res.length() - 1 ) != QChar( '/' ) &&
584        res.at( res.length() - 1 ) != QChar( '\\' ) )
585   res += QDir::separator();
586   return res;
587 }
588
589 /*!
590   \brief Return full path obtained by joining given components with
591   the native directory separator.
592   \param path Separate path components
593   \return complete path
594 */
595 QString Qtx::joinPath( const QStringList& path )
596 {
597   return path.join( QDir::separator() );
598 }
599
600 /*!
601   \brief Convert text file from DOS format to UNIX.
602
603   The function replaces "LF/CR" symbols sequence by "LF" symbol.
604
605   \param absName file name
606   \return \c true if the file is converted successfully and \c false in
607          case of any error
608 */
609 bool Qtx::dos2unix( const QString& absName )
610 {
611   FILE* src = ::fopen( absName.toUtf8(), "rb" );
612   if ( !src )
613     return false;
614
615   /* we'll use temporary file */
616   char temp[512] = { '\0' };
617   QString dir = Qtx::dir( absName );
618   FILE* tgt = ::fopen( strcpy( temp, ::tempnam( dir.toUtf8(), "__x" ) ), "wb" );
619   if ( !tgt )
620     return false;
621
622   /* temp -> result of conversion */
623   const char CR = 0x0d;
624   const char LF = 0x0a;
625   bool waitingLF = false;
626
627   while( true )
628   {
629     int  outcnt = 0;
630     char inbuf[512], outbuf[512];
631
632     /* convert buffer */
633     int nbread = (int)::fread( inbuf, 1, sizeof( inbuf ), src ); //!< TODO: conversion from 'size_t' to 'int'
634     for ( int incnt = 0; incnt < nbread; incnt++  )
635     {
636       if ( waitingLF )
637       {
638         waitingLF = false;
639         if ( inbuf[incnt] == LF )
640           outbuf[outcnt++] = LF;
641         else
642           outbuf[outcnt++] = CR;
643       }
644       else if ( inbuf[incnt] == CR )
645         waitingLF = true;
646       else
647         outbuf[outcnt++] = inbuf[incnt];
648     }
649
650     /* check last sym in buffer */
651     waitingLF = ( inbuf[nbread - 1] == CR );
652
653     /* write converted buffer to temp file */
654     int nbwri = (int)::fwrite( outbuf, 1, outcnt, tgt ); //!< TODO: conversion from 'size_t' to 'int'
655     if ( nbwri != outcnt )
656     {
657       ::fclose( src );
658                         ::fclose( tgt );
659       QFile::remove( QString( temp ) );
660       return false;
661     }
662     if ( nbread != sizeof( inbuf ) )
663       break;              /* converted ok */
664   }
665   ::fclose( src );
666   ::fclose( tgt );
667
668   /* rename temp -> src */
669   if ( !QFile::remove( absName ) )
670     return false;
671
672   return QDir().rename( QString( temp ), absName );
673 }
674
675 /*!
676   \brief Create path completer which can be used in the widgets
677   to provide auto completions.
678
679   Create an instance of QCompleter class and returns the pointer on it.
680   The calling function is responsible to the desstroying of the created 
681   completer object.
682
683   The QCompleter class provides completions based on a item model and can be
684   used in such as QLineEdit and QComboBox. 
685   When the user starts typing a word, QCompleter suggests possible ways of 
686   completing the word, based on a word list. 
687
688   \param type path type (Qtx::PathType)
689   \param filter file/directory filters (list of wildcards, separated by ";;")
690   \return a pointer to the created completer
691 */
692 QCompleter* Qtx::pathCompleter( const PathType type, const QString& filter )
693 {
694   QStringList extList;
695   QStringList filterList = filter.split( ";;" );
696   for ( QStringList::const_iterator it = filterList.begin(); it != filterList.end(); ++it )
697   {
698     QRegExp rx( "[\\s\\w,;]*\\(?\\*\\.([\\w]+)\\)?[\\d\\s\\w]*" );
699     int index = 0;
700     while ( ( index = rx.indexIn( *it, index ) ) != -1 )
701     {
702       extList.append( QString( "*.%1" ).arg( rx.cap( 1 ) ) );
703       index += rx.matchedLength();
704     }
705   }
706
707   QDir::Filters filters = 0;
708   switch ( type )
709   {
710   case PT_OpenFile:
711   case PT_SaveFile:
712     filters = QDir::AllEntries | QDir::AllDirs | QDir::NoDotAndDotDot;
713     break;
714   case PT_Directory:
715     filters = QDir::Drives | QDir::Dirs | QDir::NoDotAndDotDot;
716     break;
717   }
718
719   QDirModel* dm = new QDirModel( extList, filters, QDir::Unsorted );
720   QCompleter* cmp = new QCompleter( dm, 0 );
721   dm->setParent( cmp );
722
723   return cmp;
724 }
725
726 /*!
727   \brief Parse given string to retrieve environment variable.
728
729   Looks through the string for the environment variable patterns.
730   If string contains variable satisfying any pattern, the variable name
731   is returned, start index of the variable is returned in the \a start parameter,
732   and length of the variable is returned in the \a len parameter.
733
734   Supported environment variables definitions:
735   - ${name} or $name : Linux shell variable
736   - $(name)          : GNU make substitution
737   - %name%           : Windows shell variable
738   - %(name)s         : Python substitutions:
739
740   \param str string being processed
741   \param start if variable is found, this parameter contains its starting 
742          position in the \a str
743   \param len if variable is found, this parameter contains its length 
744   \return first found variable or null QString if there is no ones
745 */
746 QString Qtx::findEnvVar( const QString& str, int& start, int& len )
747 {
748   QString varName;
749   len = 0;
750
751   QStringList rxList;
752   rxList << "\\$\\{([a-zA-Z][a-zA-Z_0-9]*)\\}"; // ${name}
753   rxList << "\\$([a-zA-Z][a-zA-Z_0-9]*)";       // $name
754   rxList << "\\$\\(([a-zA-Z][a-zA-Z_0-9]*)\\)"; // $(name)
755   rxList << "%([a-zA-Z][a-zA-Z0-9_]*)%";        // %name%
756   rxList << "%\\(([a-zA-Z][a-zA-Z_0-9]*)\\)s";  // %(name)s
757   
758   for ( int i = 0; i < rxList.count() && varName.isEmpty(); ++i ) 
759   {
760     QRegExp rx(rxList[i]);
761     int pos = rx.indexIn( str, start );
762     if ( pos != -1 )
763     {
764       varName = rx.cap( 1 );
765       start = pos;
766       len = rx.matchedLength();
767     }
768   }
769   return varName;
770 }
771
772 /*!
773   \brief Substitute environment variables by their values.
774
775   Environment variable is substituted by its value.
776
777   \param str string to be processed
778   \return processed string (with all substitutions made)
779 */
780 QString Qtx::makeEnvVarSubst( const QString& str, const SubstMode mode )
781 {
782   QString res = str;
783   if ( mode != Never )
784   {
785     QMap<QString, int> ignoreMap;
786
787     int start( 0 ), len( 0 );
788     while ( true )
789     {
790       QString envName = findEnvVar( res, start, len );
791       if ( envName.isNull() )
792         break;
793
794       QString newStr;
795       if ( getenv( envName ).isEmpty() || mode == Always )
796         newStr = QString( getenv( envName ) );
797
798       if ( newStr.isNull() )
799       {
800         if ( ignoreMap.contains( envName ) )
801         {
802           start += len;
803           continue;
804         }
805         ignoreMap.insert( envName, 0 );
806       }
807       res.replace( start, len, newStr );
808     }
809
810     res.replace( "$$", "$" );
811     res.replace( "%%", "%" );
812   }
813
814   return res;
815 }
816
817 /*!
818   \brief Pack the specified color into integer RGB set.
819   \param c unpacked color
820   \return packed color
821 */
822 int Qtx::rgbSet( const QColor& c )
823 {
824   return rgbSet( c.red(), c.green(), c.blue() );
825 }
826
827 /*!
828   \brief Pack the specified RGB color components into integer RGB set.
829   \param r red component
830   \param g green component
831   \param b blue component
832   \return packed color
833 */
834 int Qtx::rgbSet( const int r, const int g, const int b )
835 {
836   return ( ( ( 0xff & r ) << 16 ) + ( ( 0xff & g ) << 8 ) + ( 0xff & b ) );
837 }
838
839 /*!
840   \brief Unpack the specified integer RGB set to the color.
841   \param rgb packed color
842   \return unpacked color (QColor)
843 */
844 QColor Qtx::rgbSet( const int rgb )
845 {
846   int r, g, b;
847   rgbSet( rgb, r, g, b );
848   return QColor( r, g, b );
849 }
850
851 /*!
852   \brief Unpack the specified integer RGB set to the three RGB components.
853   \param rgb packed color
854   \param r returned unpacked red component
855   \param g returned unpacked green component
856   \param b returned unpacked blue component
857 */
858 void Qtx::rgbSet( const int rgb, int& r, int& g, int& b )
859 {
860   r = ( rgb >> 16 ) & 0xff;
861   g = ( rgb >> 8 ) & 0xff;
862   b = rgb & 0xff;
863 }
864
865 /*!
866   \brief Return the color specified by the index between min (blue) and max (red).
867   \param index color index
868   \param min required minimum hue value
869   \param max required maximum hue value
870   \return resulting color
871 */
872 QColor Qtx::scaleColor( const int index, const int min, const int max )
873 {
874   static const int HUE[10] = {230, 210, 195, 180, 160, 80, 60, 50, 30, 0};
875
876   int hue = HUE[0];
877
878   if ( min != max )
879   {
880     double aPosition = 9.0 * ( index - min ) / ( max - min );
881     if ( aPosition > 0.0 )
882     {
883       if ( aPosition >= 9.0 )
884         hue = HUE[9];
885       else
886       {
887         int idx = (int)aPosition;
888         hue = HUE[idx] + int( ( aPosition - idx ) * ( HUE[idx + 1] - HUE[idx] ) );
889       }
890     }
891   }
892
893   return QColor::fromHsv( hue, 255, 255 );
894 }
895
896 /*!
897   \brief Generate required number of colors aligned from blue to red.
898   \param num required number of colors
899   \param lst returned set of colors
900 */
901 void Qtx::scaleColors( const int num, QColorList& lst )
902 {
903   lst.clear();
904   for ( int i = 0; i < num; i++ )
905     lst.append( scaleColor( i, 0, num - 1 ) );
906 }
907
908 /*!
909   \brief Scale the pixmap to the required size.
910
911   If \a h is 0 (default) the value of \a w is used instead (to create
912   square pixmap).
913
914   \param icon pixmap to be resized
915   \param w required pixmap width
916   \param h required pixmap height
917   \return scaled pixmap
918 */
919 QPixmap Qtx::scaleIcon( const QPixmap& icon, const unsigned w, const unsigned h )
920 {
921   QPixmap p;
922   int aw = w, ah = h <= 0 ? w : h;
923   if ( icon.isNull() || aw <= 0 || ah <= 0 || ( aw == icon.width() && ah == icon.height() ) )
924     p = icon;
925   else
926     p = icon.fromImage( icon.toImage().scaled( aw, ah, Qt::KeepAspectRatio, Qt::SmoothTransformation ) );
927   return p;
928 }
929
930 /*!
931   \brief Convert given image to the grayscale format.
932   \param img initial image
933   \return converted to the grayscale image
934 */
935 QImage Qtx::grayscale( const QImage& img )
936 {
937   QImage res = img;
938
939   int colNum = res.colorCount();
940   if ( colNum )
941   {
942     for ( int i = 0; i < colNum; i++ )
943       res.setColor( i, qGray( res.color( i ) ) );
944   }
945   else
946   {
947     for ( int y = 0; y < res.height(); y++ )
948     {
949       for ( int x = 0; x < res.width(); x++ )
950       {
951         QRgb pix = res.pixel( x, y );
952         res.setPixel( x, y, qRgba( qGray( pix ), qGray( pix ), qGray( pix ), qAlpha( pix ) ) );
953       }
954     }
955   }
956
957   return res;
958 }
959
960 /*!
961   \brief Convert given pixmap to the grayscale format.
962   \param pix initial pixmap
963   \return converted to the grayscale pixmap
964 */
965 QPixmap Qtx::grayscale( const QPixmap& pix )
966 {
967   QPixmap res;
968   res.fromImage( grayscale( pix.toImage() ) );
969   return res;
970 }
971
972 /*!
973   \brief Create transparent image.
974   \param w required image width
975   \param h required image height
976   \param d required image depth
977   \return generated image
978 */
979 QImage Qtx::transparentImage( const int w, const int h, const int d )
980 {
981   QImage::Format fmt;
982   switch ( d )
983   {
984   case 1:
985     fmt = QImage::Format_Mono;
986     break;
987   case 8:
988     fmt = QImage::Format_Indexed8;
989     break;
990   case 16:
991   case 24:
992   case 32:
993   default:
994     fmt = QImage::Format_ARGB32;
995     break;
996   }
997
998   QImage img( w, h, fmt );
999   if ( !img.isNull() )
1000   {
1001 //    img.setAlphaBuffer( true );
1002     for ( int i = 0; i < img.height(); i++ )
1003       for ( int j = 0; j < img.width(); j++ )
1004         img.setPixel( j, i, qRgba( 0, 0, 0, 0 ) );
1005   }
1006   return img;
1007 }
1008
1009 /*!
1010   \brief Create transparent pixmap.
1011   \param w required image width
1012   \param h required pixmap height
1013   \param d required pixmap depth
1014   \return generated pixmap
1015 */
1016 QPixmap Qtx::transparentPixmap( const int w, const int h, const int d )
1017 {
1018   QPixmap pix;
1019   QImage img = transparentImage( w, h, d );
1020   if ( !img.isNull() )
1021     pix.fromImage( img );
1022   return pix;
1023 }
1024
1025 /*!
1026   \brief Create composite pixmap. 
1027
1028   Pixmap \a pix is drawn over pixmap \a dest with coordinates
1029   specified relatively to the upper left corner of \a dest.
1030   If \a dest is not given, the new empty pixmap with appropriate size created instead.
1031
1032   \param pix source pixmap
1033   \param x horizontal shift
1034   \param y vertical shift
1035   \param dest background pixmap
1036   \return resulting pixmap
1037 */
1038 QPixmap Qtx::composite( const QPixmap& pix, const int x, const int y, const QPixmap& dest )
1039 {
1040   if ( pix.isNull() )
1041     return dest;
1042
1043   int width = qMax( pix.width() + x, dest.width() );
1044   int height = qMax( pix.height() + y, dest.height() );
1045
1046   QPixmap res( width, height );
1047   QImage img = transparentImage( width, height, 32 );
1048
1049   QPainter p;
1050   p.begin( &res );
1051   p.fillRect( 0, 0, width, height, QBrush( Qt::white ) );
1052
1053   if ( !dest.isNull() )
1054   {
1055     p.drawPixmap( 0, 0, dest );
1056     QImage temp = dest.toImage();
1057     for ( int i = 0; i < temp.width() && i < img.width(); i++ )
1058     {
1059       for ( int j = 0; j < temp.height() && j < img.height(); j++ )
1060       {
1061         if ( temp.hasAlphaChannel() )
1062           img.setPixel( i, j, temp.pixel( i, j ) );
1063         else
1064         {
1065           QRgb p = temp.pixel( i, j );
1066           img.setPixel( i, j, qRgba( qRed( p ), qGreen( p ), qBlue( p ), 255 ) );
1067         }
1068       }
1069     }
1070   }
1071
1072   p.drawPixmap( x, y, pix );
1073   QImage temp = pix.toImage();
1074   for ( int c = x; c < temp.width() + x && c < img.width(); c++ )
1075   {
1076     for ( int r = y; r < temp.height() + y && r < img.height(); r++ )
1077     {
1078       if ( qAlpha( temp.pixel( c - x, r - y ) ) > 0 )
1079         img.setPixel( c, r, temp.pixel( c - x, r - y ) );
1080     }
1081   }
1082
1083   p.end();
1084
1085   for ( int ai = 0; ai < img.width(); ai++ )
1086   {
1087     for ( int aj = 0; aj < img.height(); aj++ )
1088     {
1089       if ( qAlpha( img.pixel( ai, aj ) ) < 1 )
1090         img.setPixel( ai, aj, qRgba( 255, 255, 255, 255 ) );
1091       else
1092         img.setPixel( ai, aj, qRgba( 0, 0, 0, 0 ) );
1093     }
1094   }
1095
1096   QBitmap bmp( width, height );
1097   bmp.fromImage( img, Qt::ColorMode_Mask | Qt::ThresholdDither );
1098   res.setMask( bmp );
1099
1100   return res;
1101 }
1102
1103 /*!
1104   \brief Convert color to the string representation.
1105   
1106   The resulting string is in the one of two possible formats
1107   (\c RR, \c GG, \c BB and \c AA value represent red, green, blue
1108   and alpha components of the color):
1109   - if color has alpha channel : "#RR,#GG,#BB,#AA"
1110   - if color does not have alpha channel : "#RRGGBB" 
1111
1112   If color is invalid, null string is returned.
1113
1114   Backward conversion can be done with stringToColor() method.
1115
1116   \param color color to be converted
1117   \return string representation of the color
1118
1119   \sa stringToColor()
1120 */
1121 QString Qtx::colorToString( const QColor& color )
1122 {
1123   QString str;
1124   if ( color.isValid() )
1125   {
1126     if ( color.alpha() != 255 )
1127     {
1128       QStringList vals;
1129       vals << QString( "#%1" ).arg( color.red(),   0, 16 );
1130       vals << QString( "#%1" ).arg( color.green(), 0, 16 );
1131       vals << QString( "#%1" ).arg( color.blue(),  0, 16 );
1132       vals << QString( "#%1" ).arg( color.alpha(), 0, 16 );
1133       str = vals.join( "," );
1134     }
1135     else
1136     {
1137       str = color.name();
1138     }
1139   }
1140   return str;
1141 }
1142
1143 /*!
1144   \brief Create color from the string representation.
1145   
1146   The parameter \a str must be in the one of following formats
1147   (\c RR, \c GG, \c BB and \c AA value represent red, green, blue
1148   and alpha components of the color):
1149   - "#RR,#GG,#BB[,#AA]" or "#RR #GG #BB[ #AA]" (\c RR, \c GG, \c BB
1150   and optional \c AA values represent red, green, blue and alpha
1151   components of the color in hexadecimal form)
1152   - "RR,GG,BB[,AA]" or "RR GG BB[ AA]" (\c RR, \c GG, \c BB
1153   and optional \c AA values represent red, green, blue and alpha
1154   components of the color in decimal form)
1155   - "#RRGGBB" - (\c RR, \c GG and \c BB values represent red, green and blue
1156   components of the color in hexadecimal form)
1157   - an integer value representing packed color components (see rgbSet())
1158   - a name from the list of colors defined in the list of SVG color keyword names
1159   provided by the World Wide Web Consortium; for example, "steelblue" or "gainsboro".
1160
1161   Backward conversion can be done with colorToString() method.
1162
1163   \param str string representation of the color
1164   \param color resulting color value
1165   \return \c true if the conversion is successful and \c false otherwise
1166
1167   \sa colorToString(), rgbSet()
1168 */
1169 bool Qtx::stringToColor( const QString& str, QColor& color )
1170 {
1171   bool res = true;
1172   QStringList vals = str.split( QRegExp( "[\\s|,]" ), QString::SkipEmptyParts );
1173
1174   QIntList nums;
1175   for ( QStringList::const_iterator it = vals.begin(); it != vals.end() && res; ++it )
1176   {
1177     int num = 0;
1178     if ( (*it).startsWith( "#" ) )
1179       num = (*it).mid( 1 ).toInt( &res, 16 );
1180     else
1181       num = (*it).toInt( &res, 10 );
1182     if ( res )
1183       nums.append( num );
1184   }
1185
1186   res = res && nums.count() >= 3;
1187   if ( res )
1188     color.setRgb( nums[0], nums[1], nums[2] );
1189
1190   if ( !res )
1191   {
1192     int pack = str.toInt( &res );
1193     if ( res )
1194       color = Qtx::rgbSet( pack );
1195   }
1196
1197   if ( !res )
1198   {
1199     color = QColor( str );
1200     res = color.isValid();
1201   }
1202
1203   return res;
1204 }
1205
1206 /*!
1207   \brief Convert bi-color value to the string representation.
1208   
1209   Bi-color value is specified as main color and integer delta
1210   value that is used to calculate secondary color by changing
1211   paremeters of the main color ("saturation" and "value"
1212   components in HSV notation).
1213
1214   The resulting string consists of two sub-strings separated by
1215   '|' symbol. The first part represents main color
1216   (see colorToString() for more details), the second part is a
1217   delta value.
1218
1219   Backward conversion can be done with stringToBiColor() method.
1220
1221   \param color color to be converted
1222   \param delta delta value
1223   \return string representation of the bi-color value
1224
1225   \sa stringToBiColor(), stringToColor()
1226 */
1227 QString Qtx::biColorToString( const QColor& color, const int delta )
1228 {
1229   return QString("%1|%2").arg( Qtx::colorToString( color ) ).arg( delta );
1230 }
1231
1232 /*!
1233   \brief Restore bi-color value from the string representation.
1234
1235   Bi-color value is specified as main color and integer delta
1236   value that is used to calculate secondary color by changing
1237   paremeters of the main color ("saturation" and "value"
1238   components in HSV notation).
1239
1240   The parameter \a str should consist of two sub-strings separated
1241   by '|' symbol. The first part represents main color
1242   (see stringToColor() for more details), the second part is a
1243   delta value.
1244
1245   Backward conversion can be done with biColorToString() method.
1246
1247   \param str string representation of the bi-color value
1248   \param color resulting color value
1249   \param delta resulting delta value
1250   \return \c true if the conversion is successful and \c false otherwise
1251
1252   \sa biColorToString(), stringToColor(), rgbSet()
1253 */
1254 bool Qtx::stringToBiColor( const QString& str, QColor& color, int& delta )
1255 {
1256   QStringList data = str.split( "|", QString::KeepEmptyParts );
1257   QColor c;
1258   int d = 0;
1259   bool ok = data.count() > 0 && Qtx::stringToColor( data[0], c );
1260   bool dok = false;
1261   if ( data.count() > 1 ) d = data[1].toInt( &dok );
1262   ok = ok && dok;
1263   color = ok ? c : QColor();
1264   delta = ok ? d : 0;
1265   return ok;
1266 }
1267
1268 /*!
1269   \brief Compute secondary color value from specified main color
1270   and delta.
1271
1272   Secondary color is calculated by changing paremeters of the main
1273   color ("saturation" and "value" components in HSV notation) using
1274   specified delta.
1275
1276   If main color is invalid, result of the function is also invalid color.
1277
1278   \param color source main color
1279   \param delta delta value
1280   \return resulting secondary color
1281   
1282   \sa biColorToString(), stringToBiColor()
1283 */
1284 QColor Qtx::mainColorToSecondary( const QColor& color, int delta )
1285 {
1286   QColor cs = color;
1287   if ( cs.isValid() ) {
1288     int val = qMin( 255, qMax( cs.value() + delta, 0 ) );
1289     int sat = qMin( 255, qMax( cs.saturation() + delta-(val-cs.value()), 0 ) );
1290 #ifdef BICOLOR_CHANGE_HUE
1291     const int BICOLOR_HUE_MAXDELTA = 40;
1292     int dh = delta-(val-cs.value())-(sat-cs.saturation());
1293     dh = qMin( BICOLOR_HUE_MAXDELTA, qAbs( dh ) ) * ( dh > 0 ? 1 : -1 );
1294     //int hue = qMin( 359, qMax( cs.hue() + delta-(val-cs.value())-(sat-cs.saturation()), 0 ) );
1295     //int hue = qMin( 359, qMax( cs.hue() + delta-(val-cs.value())-ds, 0 ) );
1296     int hue = cs.hue() + dh;
1297     if ( hue < 0 ) hue = 360 - hue;
1298 #else
1299     int hue = cs.hue();
1300 #endif
1301     cs.setHsv( hue, sat, val );
1302   }
1303   return cs;
1304 }
1305
1306 /*!
1307   \brief Dump linear gradient to the string description.
1308   \param gradient linear gradient to be converted
1309   \return string representation of the linear gradient
1310   \sa stringToLinearGradient()
1311 */
1312 QString Qtx::gradientToString( const QLinearGradient& gradient )
1313 {
1314   QStringList data;
1315   data << "linear";
1316   data << QString::number( gradient.start().x() );
1317   data << QString::number( gradient.start().y() );
1318   data << QString::number( gradient.finalStop().x() );
1319   data << QString::number( gradient.finalStop().y() );
1320   switch( gradient.spread() ) 
1321   {
1322   case QGradient::PadSpread:
1323     data << "pad";
1324     break;
1325   case QGradient::RepeatSpread:
1326     data << "repeat";
1327     break;
1328   case QGradient::ReflectSpread:
1329     data << "reflect";
1330     break;
1331   default:
1332     break;
1333   }
1334   QGradientStops stops = gradient.stops();
1335   QGradientStop stop;
1336   foreach ( stop, stops ) 
1337   {
1338     data << QString::number( stop.first );
1339     data << colorToString( stop.second );
1340   }
1341   return data.join( "|" );
1342 }
1343
1344 /*!
1345   \brief Dump radial gradient to the string description.
1346   \param gradient radial gradient to be converted
1347   \return string representation of the radial gradient
1348   \sa stringToRadialGradient()
1349 */
1350 QString Qtx::gradientToString( const QRadialGradient& gradient )
1351 {
1352   QStringList data;
1353   data << "radial";
1354   data << QString::number( gradient.center().x() );
1355   data << QString::number( gradient.center().y() );
1356   data << QString::number( gradient.focalPoint().x() );
1357   data << QString::number( gradient.focalPoint().y() );
1358   data << QString::number( gradient.radius() );
1359   switch( gradient.spread() ) 
1360   {
1361   case QGradient::PadSpread:
1362     data << "pad";
1363     break;
1364   case QGradient::RepeatSpread:
1365     data << "repeat";
1366     break;
1367   case QGradient::ReflectSpread:
1368     data << "reflect";
1369     break;
1370   default:
1371     break;
1372   }
1373   QGradientStops stops = gradient.stops();
1374   QGradientStop stop;
1375   foreach ( stop, stops ) 
1376   {
1377     data << QString::number( stop.first );
1378     data << colorToString( stop.second );
1379   }
1380   return data.join( "|" );
1381 }
1382
1383 /*!
1384   \brief Dump conical gradient to the string description.
1385   \param gradient conical gradient to be converted
1386   \return string representation of the conical gradient
1387   \sa stringToConicalGradient()
1388 */
1389 QString Qtx::gradientToString( const QConicalGradient& gradient )
1390 {
1391   QStringList data;
1392   data << "conical";
1393   data << QString::number( gradient.center().x() );
1394   data << QString::number( gradient.center().y() );
1395   data << QString::number( gradient.angle() );
1396   switch( gradient.spread() ) 
1397   {
1398   case QGradient::PadSpread:
1399     data << "pad";
1400     break;
1401   case QGradient::RepeatSpread:
1402     data << "repeat";
1403     break;
1404   case QGradient::ReflectSpread:
1405     data << "reflect";
1406     break;
1407   default:
1408     break;
1409   }
1410   QGradientStops stops = gradient.stops();
1411   QGradientStop stop;
1412   foreach ( stop, stops ) 
1413   {
1414     data << QString::number( stop.first );
1415     data << colorToString( stop.second );
1416   }
1417   return data.join( "|" );
1418 }
1419
1420 /*!
1421   \brief Create linear gradient from its string representation.
1422   \param str string representation of the linear gradient
1423   \param gradient resulting linear gradient object
1424   \return \c true if the conversion is successful and \c false otherwise
1425   \sa gradientToString()
1426 */
1427 bool Qtx::stringToLinearGradient( const QString& str, QLinearGradient& gradient )
1428 {
1429   bool success = false;
1430   QStringList vals = str.split( "|", QString::SkipEmptyParts );
1431   if ( vals.count() > 4 && ( vals[0] == "linear" || vals[0] == "lg" ) )
1432   {
1433     // start and end points 
1434     double x1, y1, x2, y2;
1435     bool bOk1, bOk2, bOk3, bOk4;
1436     x1 = vals[1].toDouble( &bOk1 );
1437     y1 = vals[2].toDouble( &bOk2 );
1438     x2 = vals[3].toDouble( &bOk3 );
1439     y2 = vals[4].toDouble( &bOk4 );
1440     if ( bOk1 && bOk2 && bOk3 && bOk4 )
1441     {
1442       gradient = QLinearGradient( x1, y1, x2, y2 );
1443       // spread type
1444       if ( vals.count() > 5 )
1445       {
1446         QString spread = vals[ 5 ].trimmed().toLower();
1447         if ( spread == "pad" || spread == "0" )
1448           gradient.setSpread( QGradient::PadSpread );
1449         else if ( spread == "repeat" || spread == "2" )
1450           gradient.setSpread( QGradient::RepeatSpread );
1451         else if ( spread == "reflect" || spread == "1" )
1452           gradient.setSpread( QGradient::ReflectSpread );
1453       }
1454       // stop points
1455       QGradientStops stops;
1456       for ( int i = 6; i < vals.count(); i+=2 )
1457       {
1458         bool bOk5, bOk6 = false;
1459         QColor c;
1460         double stop = vals[i].toDouble( &bOk5 );
1461         if ( i+1 < vals.count() )
1462           bOk6 = stringToColor( vals[ i+1 ], c );
1463         if ( bOk5 && stop >= 0.0 && stop <= 1.0 && bOk6 && c.isValid() )
1464           stops.append( QGradientStop( stop, c ) );
1465       }
1466       gradient.setStops( stops );
1467       success = true;
1468     }
1469   }
1470   return success;
1471 }
1472
1473 /*!
1474   \brief Create radial gradient from its string representation.
1475   \param str string representation of the radial gradient
1476   \param gradient resulting radial gradient object
1477   \return \c true if the conversion is successful and \c false otherwise
1478   \sa gradientToString()
1479 */
1480 bool Qtx::stringToRadialGradient( const QString& str, QRadialGradient& gradient )
1481 {
1482   bool success = false;
1483   QStringList vals = str.split( "|", QString::SkipEmptyParts );
1484   if ( vals.count() > 5 && ( vals[0] == "radial" || vals[0] == "rg" ) )
1485   {
1486     // center, radius and focal point
1487     double cx, cy, r, fx, fy;
1488     bool bOk1, bOk2, bOk3, bOk4, bOk5;
1489     cx = vals[1].toDouble( &bOk1 );
1490     cy = vals[2].toDouble( &bOk2 );
1491     fx = vals[3].toDouble( &bOk4 );
1492     fy = vals[4].toDouble( &bOk5 );
1493     r  = vals[5].toDouble( &bOk3 );
1494     if ( bOk1 && bOk2 && bOk3 && bOk4 && bOk5 )
1495     {
1496       gradient = QRadialGradient( cx, cy, r, fx, fy );
1497       // spread type
1498       if ( vals.count() > 6 )
1499       {
1500         QString spread = vals[ 6 ].trimmed().toLower();
1501         if ( spread == "pad" || spread == "0" )
1502           gradient.setSpread( QGradient::PadSpread );
1503         else if ( spread == "repeat" || spread == "2" )
1504           gradient.setSpread( QGradient::RepeatSpread );
1505         else if ( spread == "reflect" || spread == "1" )
1506           gradient.setSpread( QGradient::ReflectSpread );
1507       }
1508       // stop points
1509       QGradientStops stops;
1510       for ( int i = 7; i < vals.count(); i+=2 )
1511       {
1512         bool bOk7, bOk8 = false;
1513         QColor c;
1514         double stop = vals[i].toDouble( &bOk7 );
1515         if ( i+1 < vals.count() )
1516           bOk8 = stringToColor( vals[ i+1 ], c );
1517         if ( bOk7 && stop >= 0.0 && stop <= 1.0 && bOk8 && c.isValid() )
1518           stops.append( QGradientStop( stop, c ) );
1519       }
1520       gradient.setStops( stops );
1521       success = true;
1522     }
1523   }
1524   return success;
1525 }
1526
1527 /*!
1528   \brief Create conical gradient from its string representation.
1529   \param str string representation of the conical gradient
1530   \param gradient resulting conical gradient object
1531   \return \c true if the conversion is successful and \c false otherwise
1532   \sa gradientToString()
1533 */
1534 bool Qtx::stringToConicalGradient( const QString& str, QConicalGradient& gradient )
1535 {
1536   bool success = false;
1537   QStringList vals = str.split( "|", QString::SkipEmptyParts );
1538   if ( vals.count() > 3 && ( vals[0] == "conical" || vals[0] == "cg" ) )
1539   {
1540     // center and angle
1541     double cx, cy, a;
1542     bool bOk1, bOk2, bOk3;
1543     cx = vals[1].toDouble( &bOk1 );
1544     cy = vals[2].toDouble( &bOk2 );
1545     a = vals[3].toDouble( &bOk3 );
1546     if ( bOk1 && bOk2 && bOk3 )
1547     {
1548       gradient = QConicalGradient( cx, cy, a );
1549       // spread type
1550       if ( vals.count() > 4 )
1551       {
1552         QString spread = vals[ 4 ].trimmed().toLower();
1553         if ( spread == "pad" || spread == "0" )
1554           gradient.setSpread( QGradient::PadSpread );
1555         else if ( spread == "repeat" || spread == "2" )
1556           gradient.setSpread( QGradient::RepeatSpread );
1557         else if ( spread == "reflect" || spread == "1" )
1558           gradient.setSpread( QGradient::ReflectSpread );
1559       }
1560       // stop points
1561       QGradientStops stops;
1562       for ( int i = 5; i < vals.count(); i+=2 )
1563       {
1564         bool bOk4, bOk5 = false;
1565         QColor c;
1566         double stop = vals[i].toDouble( &bOk4 );
1567         if ( i+1 < vals.count() )
1568           bOk5 = stringToColor( vals[ i+1 ], c );
1569         if ( bOk4 && stop >= 0.0 && stop <= 1.0 && bOk5 && c.isValid() )
1570           stops.append( QGradientStop( stop, c ) );
1571       }
1572       gradient.setStops( stops );
1573       success = true;
1574     }
1575   }
1576   return success;
1577 }
1578
1579 /*!
1580   \brief Convert background data to the string representation.
1581   The resulting string consists of several sub-strings separated by ';' symbol. 
1582   These sub-strings represent:
1583   1. background type (enumerator, see Qtx::BackgroundMode)
1584   2. texture image file name (string)
1585   3. texture mode (enumerator, see Qtx::TextureMode)
1586   4. "show texture" flag (boolean)
1587   5. first color (for simple gradient data) or solid color (for single-colored mode)
1588   6. second color (for simple gradient data)
1589   7. type of simple gradient (some integer identifier)
1590   8. complex gradient data (for custom gradient mode)
1591   Each sub-string consists of keyword/value couple, in form of "<keyword>=<value>".
1592
1593   Backward conversion can be done with stringToBackground() method.
1594
1595   \param bgData background data
1596   \return string representation of the background data
1597
1598   \sa stringToBackground()
1599 */
1600 QString Qtx::backgroundToString( const Qtx::BackgroundData& bgData )
1601 {
1602   const QString dtSep         = ";";
1603   const QString kwSep         = "=";
1604   const QString kwBgType      = "bt";
1605   const QString kwFileName    = "fn";
1606   const QString kwTextureMode = "tm";
1607   const QString kwShowTexture = "ts";
1608   const QString kwFirstColor  = "c1";
1609   const QString kwSecondColor = "c2";
1610   const QString kwGrType      = "gt";
1611   const QString kwGrData      = "gr";
1612
1613   Qtx::BackgroundMode bgMode       = bgData.mode();
1614   QString             fileName;
1615   Qtx::TextureMode    textureMode  = bgData.texture( fileName );
1616   bool                showTexture  = bgData.isTextureShown();
1617   QColor              c1, c2;
1618   int                 gradientType = bgData.gradient( c1, c2 );
1619   const QGradient*    gradient     = bgData.gradient();
1620   QString             grString;
1621   if ( gradient ) {
1622     switch ( gradient->type() ) {
1623     case QGradient::LinearGradient:
1624       grString = gradientToString( *(static_cast<const QLinearGradient*>( gradient )) );
1625       break;
1626     case QGradient::RadialGradient:
1627       grString = gradientToString( *(static_cast<const QRadialGradient*>( gradient )) );
1628       break;
1629     case QGradient::ConicalGradient:
1630       grString = gradientToString( *(static_cast<const QConicalGradient*>( gradient )) );
1631       break;
1632     default:
1633       break;
1634     }
1635   }
1636   QStringList data;
1637   data << QString( "%1%2%3" ).arg( kwBgType ).arg( kwSep ).arg( (int)bgMode );
1638   data << QString( "%1%2%3" ).arg( kwFileName ).arg( kwSep ).arg( fileName );
1639   data << QString( "%1%2%3" ).arg( kwTextureMode ).arg( kwSep ).arg( (int)textureMode );
1640   data << QString( "%1%2%3" ).arg( kwShowTexture ).arg( kwSep ).arg( showTexture ? "true" : "false" );
1641   data << QString( "%1%2%3" ).arg( kwFirstColor ).arg( kwSep ).arg( Qtx::colorToString( c1 ) );
1642   data << QString( "%1%2%3" ).arg( kwSecondColor ).arg( kwSep ).arg( Qtx::colorToString( c2 ) );
1643   data << QString( "%1%2%3" ).arg( kwGrType ).arg( kwSep ).arg( gradientType );
1644   data << QString( "%1%2%3" ).arg( kwGrData ).arg( kwSep ).arg( grString );
1645
1646   return data.join( dtSep );
1647 }
1648
1649 /*!
1650   \brief Restore background data from the string representation.
1651
1652   The string should consist of several sub-strings separated by ';' symbol. 
1653   Each sub-string consists of keyword/value couple, in form of "<keyword>=<value>".
1654   The sub-strings can follow in arbitrary order, some keywords can be missing.
1655   The background data is described by the following values:
1656   - background type (enumerator, see Qtx::BackgroundMode), keyword "bt"
1657   - texture image file name (string), keyword "fn"
1658   - texture mode (enumerator, see Qtx::TextureMode), keyword "tm"
1659   - "show texture" flag (boolean), keyword "ts"
1660   - first color (for simple gradient data) or solid color (for single-colored mode), keyword "c1"
1661   - second color (for simple gradient data), keyword "c2"
1662   - name of gradient type (string), keyword "gt"
1663   - complex gradient data (for custom gradient mode), keyword "gr"
1664
1665   Also, for backward compatibility, background data can be represented by
1666   single color value, see stringToColor().
1667
1668   Backward conversion can be done with backgroundToString() method.
1669   Returns invalid background if conversion could not be done.
1670
1671   \code
1672   Qtx::BackgroundData bgData = Qtx::stringToBackground( str );
1673   if ( bgData.isValid() ) ) doSomething( bgData );
1674   \endcode
1675
1676   \param theString string representation of the background data
1677   \return resulting background data (invalid if conversion has failed)
1678
1679   \sa backgroundToString()
1680 */
1681
1682 Qtx::BackgroundData Qtx::stringToBackground( const QString& str )
1683 {
1684   const QString dtSep         = ";";
1685   const QString kwSep         = "=";
1686   const QString kwBgType      = "bt";
1687   const QString kwFileName    = "fn";
1688   const QString kwTextureMode = "tm";
1689   const QString kwShowTexture = "ts";
1690   const QString kwFirstColor  = "c1";
1691   const QString kwSecondColor = "c2";
1692   const QString kwGrType      = "gt";
1693   const QString kwGrData      = "gr";
1694
1695   Qtx::BackgroundData bgData = BackgroundData();
1696
1697   QStringList data = str.split( dtSep, QString::KeepEmptyParts );
1698   
1699   QColor c;
1700   if ( data.count() == 1 && !data.contains( kwSep ) && stringToColor( data[0], c ) ) {
1701     // solid color mode, for backward compatibility
1702     bgData.setColor( c );
1703   }
1704   else {
1705     QMap<QString, QString> dmap;
1706     // background data
1707     foreach( QString d, data ) {
1708       QStringList items = d.split( kwSep, QString::KeepEmptyParts );
1709       if ( items.count() > 0 ) {
1710         QString kw  = items.takeFirst().trimmed().toLower(); // keyword
1711         QString val = items.join( kwSep ).trimmed(); // if value contains "=" symbol, we have to restore it
1712         dmap[ kw ] = val;
1713       }
1714     }
1715     QString bgMode       = dmap.value( kwBgType,      QString() );
1716     QString fileName     = dmap.value( kwFileName,    QString() );
1717     QString textureMode  = dmap.value( kwTextureMode, QString() );
1718     QString showTexture  = dmap.value( kwShowTexture, QString() );
1719     QString color1       = dmap.value( kwFirstColor,  QString() );
1720     QString color2       = dmap.value( kwSecondColor, QString() );
1721     QString gradientType = dmap.value( kwGrType,      QString() );
1722     QString gradient     = dmap.value( kwGrData,      QString() );
1723     
1724     // texture data
1725     if ( !fileName.isEmpty() || !textureMode.isEmpty() || !showTexture.isEmpty() ) {
1726       Qtx::TextureMode m = (Qtx::TextureMode)( stringToInt( textureMode,        Qtx::CenterTexture, 
1727                                                             Qtx::CenterTexture, Qtx::StretchTexture ) );
1728       bgData.setTexture( fileName, m );
1729       QStringList boolvars; boolvars << "true" << "yes" << "ok" << "1";
1730       if ( boolvars.contains( showTexture.trimmed().toLower() ) )
1731         bgData.setTextureShown( true );
1732     }
1733     QColor c1, c2;
1734     // try color mode
1735     bool ok = Qtx::stringToColor( color1, c1 );
1736     if ( ok ) {
1737       bgData.setColor( c1 );
1738     }
1739     // try simple gradient mode
1740     ok = Qtx::stringToColor( color2, c2 );
1741     if ( ok || !gradientType.isEmpty() ) {
1742       int gt = gradientType.toInt( &ok );
1743       bgData.setGradient( ok ? gt : -1, c1, c2 );
1744     }
1745     // try custom gradient mode
1746     QLinearGradient  lg;
1747     QConicalGradient cg;
1748     QRadialGradient  rg;
1749     ok = Qtx::stringToLinearGradient( gradient, lg );
1750     if ( ok ) {
1751       bgData.setGradient( lg );
1752     }
1753     ok = Qtx::stringToRadialGradient( gradient, rg );
1754     if ( ok ) {
1755       bgData.setGradient( rg );
1756     }
1757     ok = Qtx::stringToConicalGradient( gradient, cg );
1758     if ( ok ) {
1759       bgData.setGradient( cg );
1760     }
1761     
1762     // finally set background mode
1763     Qtx::BackgroundMode m = (Qtx::BackgroundMode)( stringToInt( bgMode,            Qtx::ColorBackground, 
1764                                                                 Qtx::NoBackground, Qtx::CustomGradientBackground ) );
1765     bgData.setMode( m );
1766   }
1767
1768   return bgData;
1769 }
1770
1771 /*!
1772   \class Qtx::Localizer
1773   \brief Localization helper
1774
1775   This helper class can be used to solve the localization problems,
1776   usually related to the textual files reading/writing, namely when
1777   floating point values are read / written with API functions.
1778   The problem relates to such locale specific settings as decimal point
1779   separator, thousands separator, etc.
1780   
1781   To use the Localizer class, just create a local variable in the beginning
1782   of the code where you need to read / write data from textual file(s).
1783   The constructor of the class forces setting "C" locale temporarily.
1784   The destructor switches back to the initial locale.
1785
1786   There are two ways to create a localizer.
1787   First constructor accepts category and locale value to be forced as parameters.
1788   The second constructor does not take parameters, and is just a shortcut to the
1789   first one, setting LC_NUMERIC as a category and "C" as a locale to force.
1790   
1791   \code
1792   Qtx::Localizer loc;
1793   readSomething();
1794   writeSomething();
1795   \endcode
1796 */
1797
1798 /*!
1799   \brief Default constructor. Forces "C" locale to be set as LC_NUMERIC.
1800 */
1801 Qtx::Localizer::Localizer()
1802 {
1803   init( LC_NUMERIC, "C" );
1804 }
1805
1806 /*!
1807   \brief Constructor. Forces \a locale to be set for \a category.
1808 */
1809 Qtx::Localizer::Localizer( int category, const char* locale )
1810 {
1811   init( category, locale );
1812 }
1813
1814 /*!
1815   \brief Internal initialization
1816   \internal
1817 */
1818 void Qtx::Localizer::init( int category, const char* locale )
1819 {
1820   myCategory = category;
1821   myOriginalLocale = setlocale( category, NULL );
1822   setlocale( category, locale );
1823 }
1824
1825 /*!
1826   \brief Destructor. Reverts back to the initial locale.
1827 */
1828 Qtx::Localizer::~Localizer()
1829 {
1830   setlocale( myCategory, myOriginalLocale.toLatin1().constData() );
1831 }
1832
1833 /*!
1834   \class Qtx::BackgroundData
1835   \brief Stores background data
1836
1837   This class is used to store background data. Depending on the mode,
1838   the background can be specified by:
1839   - image (by assigning the file name to be used as background texture), see setTexture(), setTextureShown()
1840   - single color (by assigning any color), see setColor()
1841   - simple two-color gradient (with the gradient type id and two colors), see setGradient( int, const QColor&, const QColor& )
1842   - complex gradient (by assigning arbitrary gradient data), see setGradient( const QGradient& )
1843
1844   The class stores all the data passed to it, so switching between different modes can be done
1845   just by calling setMode() function.
1846
1847   \note Texture is used with combination of the background mode.
1848
1849   \note Two-color gradient is specified by two colors and integer identifier. The interpretation of 
1850   this identifier should be done in the calling code.
1851
1852   \code
1853   Qtx::BackgroundData bg;
1854   bg.setColor( QColor(100, 100, 100) );                  // bg is switched to Qtx::ColorBackground mode
1855   bg.setGradient( Qt::Horizontal, Qt::gray, Qt::white ); // bg is switched to Qtx::ColorBackground mode
1856   QLinearGradient grad( 0,0,1,1 ); 
1857   grad.setColorAt( 0.0, Qt::gray ); 
1858   grad.setColorAt( 0.5, Qt::white );
1859   grad.setColorAt( 1.0, Qt::green );
1860   grad.setSpread( QGradient::PadSpread );
1861   bg.setGradient( grad );                                // bg is switched to Qtx::CustomGradientBackground mode
1862   bg.setMode( Qtx::ColorBackground );                    // bg is switched back to Qtx::ColorBackground mode
1863   bg.setTexture( "/data/images/background.png" );        // specify texture (in the centered mode by default)
1864   bg.setTextureShown( true );                            // draw texture on the solid color background
1865   \endcode
1866 */
1867
1868 /*!
1869   \brief Default constructor.
1870   Creates invalid background data.
1871 */
1872 Qtx::BackgroundData::BackgroundData()
1873   : myTextureMode( Qtx::CenterTexture ), myGradientType( -1 ), myTextureShown( false )
1874 {
1875   setMode( Qtx::NoBackground );
1876 }
1877
1878 /*!
1879   \brief Constructor.
1880   Creates background data initialized with the specified color
1881   \param c color
1882 */
1883 Qtx::BackgroundData::BackgroundData( const QColor& c )
1884   : myTextureMode( Qtx::CenterTexture ), myGradientType( -1 ), myTextureShown( false )
1885 {
1886   setColor( c );
1887 }
1888
1889 /*!
1890   \brief Constructor.
1891   Creates background data initialized with the specified two-color gradient
1892   \param type gradient type identifier
1893   \param c1 first gradient color
1894   \param c2 second gradient color
1895   \note the interpretation of the gradient identifier should be done in the calling code
1896 */
1897 Qtx::BackgroundData::BackgroundData( int type, const QColor& c1, const QColor& c2 )
1898   : myTextureMode( Qtx::CenterTexture ), myGradientType( -1 ), myTextureShown( false )
1899 {
1900   setGradient( type, c1, c2 );
1901 }
1902
1903 /*!
1904   \brief Constructor.
1905   Creates background data initialized with the arbirtary gradient data
1906   \param grad gradient data
1907 */
1908 Qtx::BackgroundData::BackgroundData( const QGradient& grad )
1909   : myTextureMode( Qtx::CenterTexture ), myGradientType( -1 ), myTextureShown( false )
1910 {
1911   setGradient( grad );
1912 }
1913
1914 /*!
1915   \brief Destructor.
1916 */
1917 Qtx::BackgroundData::~BackgroundData()
1918 {
1919 }
1920
1921 /*!
1922   \brief Compares two background data objects
1923 */
1924 bool Qtx::BackgroundData::operator==( const Qtx::BackgroundData& other ) const
1925 {
1926   return 
1927     ( myMode         == other.myMode )         && 
1928     ( myTextureMode  == other.myTextureMode )  &&
1929     ( myFileName     == other.myFileName )     &&
1930     ( myColors       == other.myColors )       &&
1931     ( myGradientType == other.myGradientType ) &&
1932     ( myGradient     == other.myGradient )     &&
1933     ( myTextureShown == other.myTextureShown );
1934 }
1935
1936 /*!
1937   \brief Returns \c false if background data is not set (invalid)
1938   \return \c true if background data is valid or \c false otherwise
1939   \sa mode()
1940 */
1941 bool Qtx::BackgroundData::isValid() const
1942 {
1943   return myMode != Qtx::NoBackground;
1944 }
1945
1946 /*!
1947   \brief Get background mode
1948   \return current background mode
1949   \sa setMode()
1950 */
1951 Qtx::BackgroundMode Qtx::BackgroundData::mode() const
1952 {
1953   return myMode;
1954 }
1955
1956 /*!
1957   \brief Set background mode
1958   \param m background mode being set
1959   \sa mode()
1960 */
1961 void Qtx::BackgroundData::setMode( const Qtx::BackgroundMode m )
1962 {
1963   myMode = m;
1964 }
1965
1966 /*!
1967   \brief Get file name used as a texture image
1968   \return path to the texture image file
1969   \sa setTexture(), setTextureShown()
1970 */
1971 Qtx::TextureMode Qtx::BackgroundData::texture( QString& fileName ) const
1972 {
1973   fileName = myFileName;
1974   return myTextureMode;
1975 }
1976
1977 /*!
1978   \brief Set file name to be used as a texture image.
1979
1980   \note To show texture image on the background it is necessary to call additionally
1981   setTextureShown() method.
1982
1983   \param fileName path to the texture image file name
1984   \param m texture mode (Qtx::CenterTexture by default)
1985   \sa texture(), setTextureShown()
1986 */
1987 void Qtx::BackgroundData::setTexture( const QString& fileName, const Qtx::TextureMode m )
1988 {
1989   myFileName = fileName;
1990   myTextureMode = m;
1991 }
1992
1993 /*!
1994   \brief Check if "show texture" flag is switched on
1995   \return \c true if "show texture" flag is set or \c false otherwise
1996   \sa setTextureShown(), texture()
1997 */
1998 bool Qtx::BackgroundData::isTextureShown() const
1999 {
2000   return myTextureShown;
2001 }
2002
2003 /*!
2004   \brief Specify if texture should be shown on the background or no.
2005   \param on \c true if texture should be shown or \c false otherwise
2006   \sa isTextureShown(), texture()
2007 */
2008 void Qtx::BackgroundData::setTextureShown( bool on )
2009 {
2010   myTextureShown = on;
2011 }
2012
2013 /*!
2014   \brief Get background color. Returns null QColor if color is not set
2015   \return solid background color
2016   \sa setColor(), mode()
2017 */
2018 QColor Qtx::BackgroundData::color() const
2019 {
2020   return myColors.count() > 0 ? myColors[0] : QColor();
2021 }
2022
2023 /*!
2024   \brief Set background color and switch to the Qtx::ColorBackground mode
2025   \param c color
2026   \sa color(), mode()
2027 */
2028 void Qtx::BackgroundData::setColor( const QColor& c )
2029 {
2030   myColors.clear();
2031   myColors << c;
2032   setMode( Qtx::ColorBackground );
2033 }
2034
2035 /*!
2036   \brief Get simple gradient data.
2037   Returns -1 and null QColor for \a c1 and \a c2 if gradient data is not set
2038   \param c1 first gradient color is returned via this parameter
2039   \param c2 second gradient color is returned via this parameter
2040   \return current two-colored gradient mode type identifier
2041   \note the interpretation of the gradient identifier should be done in the calling code
2042   \sa setGradient(int, const QColor&, const QColor&), mode()
2043 */
2044 int Qtx::BackgroundData::gradient( QColor& c1, QColor& c2 ) const
2045 {
2046   c1 = myColors.count() > 0 ? myColors[0] : QColor();
2047   c2 = myColors.count() > 1 ? myColors[1] : ( myColors.count() > 0 ? myColors[0] : QColor() );
2048   return myGradientType;
2049 }
2050
2051 /*!
2052   \brief Set simple background gradient data and switch to the Qtx::SimpleGradientBackground mode
2053   \param type two-colored gradient mode type identifier
2054   \param c1 first gradient color is returned via this parameter
2055   \param c2 second gradient color is returned via this parameter
2056   \note the interpretation of the gradient identifier should be done in the calling code
2057   \sa gradient(QColor&, QColor&), mode()
2058 */
2059 void Qtx::BackgroundData::setGradient( int type, const QColor& c1, const QColor& c2 )
2060 {
2061   myColors.clear();
2062   myColors << c1 << c2;
2063   myGradientType = type;
2064   setMode( Qtx::SimpleGradientBackground );
2065 }
2066
2067 /*!
2068   \brief Get complex gradient data.
2069   Returns QGradient of QGradient::NoGradient if gradient data is not set
2070   \note This function does not transform simple gradient data set with 
2071   setGradient( const QString&, const QColor&, const QColor& ) to QGradient class
2072   \return gradient data
2073   \sa setGradient(const QGradient&), mode()
2074 */
2075 const QGradient* Qtx::BackgroundData::gradient() const
2076 {
2077   return &myGradient;
2078 }
2079
2080 /*!
2081   \brief Set complex background gradient data and switch to the Qtx::CustomGradientBackground mode
2082   \param grad gradient data (QLinearGradient, QRadialGradient or QConicalGradient)
2083   \sa gradient(), mode()
2084 */
2085 void Qtx::BackgroundData::setGradient( const QGradient& grad )
2086 {
2087   myGradient = grad;
2088   setMode( Qtx::CustomGradientBackground );
2089 }
2090
2091 /*!
2092   \brief Convert string representation of version identifier to the numerical value.
2093   Resulting value can be used for comparison of different versions (lower, higher, equal).
2094
2095   String representation of the version consists of zero or more components:
2096
2097   [major[.minor[.release[patchid]]]]
2098  
2099   where
2100   - major is version major number
2101   - minor is version minor number
2102   - release is version release number
2103   - patchid is a version dev identifier which is one of the following
2104     * 1 letter optionally followed by 1 or 2 digits, e.g. "a" for "alpha", "b1" for "beta 1"
2105     * "rc" optionally followed by 1 or 2 digits, e.g. "rc1" for "release candidate 1"
2106     * "dev" for development version (note: 7.4.0dev > 7.4.0, 7.4.0dev < 7.4.1, 7.4.0dev < 7.4.0a1)
2107
2108   If version string does not include any component or has invalid format, the function returns 0.
2109
2110   Examples:
2111     1.0      - version 1.0
2112     1.2.3a   - version 1.2.3 alpha
2113     3.3.3b1  - version 3.3.3 beta 1
2114     7.4.0rc1 - version 7.4.0 release candidate 1
2115     7.4.0dev - dev version, i.e. future version 7.4.1 (or 7.5.0)
2116
2117   \param version string representation of version
2118   \return numerical identifier of the version
2119 */
2120 long Qtx::versionToId( const QString& version )
2121 {
2122   long id = 0;
2123
2124   QRegExp vers_exp( "^([0-9]+)([A-Z]|RC|DEV)?([0-9]{0,2})$", Qt::CaseInsensitive );
2125   
2126   QStringList vers = version.split( ".", QString::SkipEmptyParts );
2127   int major=0, minor=0;
2128   int release = 0, dev1 = 0, dev2 = 0;
2129   if ( vers.count() > 0 ) major = vers[0].toInt();
2130   if ( vers.count() > 1 ) minor = vers[1].toInt();
2131   if ( vers.count() > 2 ) {
2132     if ( vers_exp.indexIn( vers[2] ) != -1 ) {
2133       release = vers_exp.cap( 1 ).toInt();
2134       QString tag = vers_exp.cap( 2 ).toLower();
2135       if ( !tag.isEmpty() ) {
2136         // patchid is subtracted from version number
2137         // a   = 55 --> -(55 * 100) + patch number --> 4500..4599, e.g. 7.4.1a1  -> 704004501
2138         // b   = 54 --> -(54 * 100) + patch number --> 4600..4699, e.g. 7.4.1b1  -> 704004601
2139         // c   = 53 --> -(53 * 100) + patch number --> 4700..4799, e.g. 7.4.1c1  -> 704004701
2140         // ...
2141         // z   = 30 --> -( 1 * 100) + patch number --> 7000..7099, e.g. 7.4.1z1  -> 704007001
2142         // rc  =  1 --> -( 1 * 100) + patch number --> 9900..9999, e.g. 7.4.1rc1 -> 704009901
2143         // dev = -1 --> +( 1 * 100) + patch number --> 0100..0199, e.g. 7.4.1dev -> 704010100
2144         // ---
2145         // i.e. "a" < "b" < ... < "z" < "rc" < [stable] < "dev"
2146         if ( tag == "rc" )
2147           dev1 = 1;
2148         else if ( tag == "dev" )
2149           dev1 = -1;
2150         else
2151           dev1 = (int)( QChar('z').toLatin1() ) - (int)( tag[ 0 ].toLatin1() ) + 30;
2152       }
2153       if ( !vers_exp.cap( 3 ).isEmpty() )
2154         dev2 = vers_exp.cap( 3 ).toInt();
2155     }
2156   }
2157   
2158   int dev = dev1*100-dev2;
2159   id = major;
2160   id*=100; id+=minor;
2161   id*=100; id+=release;
2162   id*=10000;
2163   id-=dev;
2164
2165   return id;
2166 }
2167
2168 /*!
2169   \brief Get Qt installation directory
2170   
2171   The function tries to detect qt installation directory by analyzing the system variables in the following order:
2172   - QT5_ROOT_DIR
2173   - QT_ROOT_DIR
2174   - QTDIR
2175
2176   Optional parameter \a context allows obtaining subdirectory in the Qt installation directory.
2177
2178   \param context optional sub-directory
2179   \return path to the Qt installation directory (or its sub-folder, if \a context is specified)
2180 */
2181
2182 QString Qtx::qtDir( const QString& context )
2183 {
2184
2185   QStringList vars = { "QT5_ROOT_DIR", "QT_ROOT_DIR", "QTDIR" };
2186   QString qtPath;
2187   for (int i = 0; i < vars.length() && qtPath.isEmpty(); i++ ) {
2188     qtPath = getenv(vars[i]);
2189   }
2190   if ( !qtPath.isEmpty() && !context.isEmpty() )
2191     qtPath = QDir( qtPath ).absoluteFilePath( context );
2192   return qtPath;
2193 }
2194
2195 /*!
2196   Creates font from string description
2197 */
2198 QFont Qtx::stringToFont( const QString& fontDescription )
2199 {
2200   QFont font;
2201   if ( fontDescription.trimmed().isEmpty() || !font.fromString( fontDescription ) )
2202     font = QFont( "Courier", 11 );
2203   return font;
2204 }
2205
2206 QString Qtx::getenv(const QString & envVar)
2207 {
2208         QString value;
2209 #ifndef WIN32
2210         value = qgetenv(envVar.toLocal8Bit().constData());
2211 #else
2212         LPTSTR buff = new TCHAR[MAX_VALUE_SIZE];
2213 #ifdef UNICODE
2214         LPTSTR anEnvVar = new TCHAR[envVar.length() + 1];       
2215         anEnvVar[envVar.toWCharArray(anEnvVar)] = '\0';
2216 #else
2217         const TCHAR* anEnvVar = envVar.toLocal8Bit(buff).constData();
2218 #endif 
2219         const DWORD ret = GetEnvironmentVariable(anEnvVar, buff, MAX_VALUE_SIZE);
2220         buff[ret] = '\0';
2221         if (ret > 0) {
2222 #ifdef UNICODE
2223                 value = QString::fromWCharArray(buff);
2224 #else
2225                 value = QString::fromLocal8Bit(buff);
2226 #endif 
2227         }
2228         delete buff;
2229 #ifdef UNICODE
2230         delete anEnvVar;
2231 #endif
2232 #endif
2233         return value;
2234 }
2235
2236 #if !defined WIN32 && !defined __APPLE__ 
2237
2238 #include <X11/Xlib.h>
2239 #include <GL/glx.h>
2240
2241 /*!
2242   \brief Open the default X display and returns pointer to it.
2243          This method is available on Linux only.
2244   \return Pointer to X display.
2245   \sa getVisual()
2246 */
2247 void* Qtx::getDisplay()
2248 {
2249   static Display* pDisplay = NULL;
2250   if ( !pDisplay )
2251     pDisplay = XOpenDisplay( NULL );
2252   return pDisplay;
2253 }
2254
2255 /*!
2256   \brief Returns pointer to X visual suitable for 3D rendering.
2257          This method is available on Linux only.
2258   \return Pointer to X visual.
2259   \sa getDisplay()
2260 */
2261 Qt::HANDLE Qtx::getVisual()
2262 {
2263   Qt::HANDLE res = (Qt::HANDLE)NULL;
2264
2265   Display* pDisplay = (Display*)getDisplay();
2266   if ( !pDisplay )
2267     return res;
2268
2269   int errorBase;
2270   int eventBase;
2271
2272   // Make sure OpenGL's GLX extension supported
2273   if( !glXQueryExtension( pDisplay, &errorBase, &eventBase ) ){
2274     qCritical( "Could not find glx extension" );
2275     return res;
2276   }
2277
2278   // Find an appropriate visual
2279
2280   int doubleBufferVisual[]  = {
2281     GLX_RENDER_TYPE, GLX_RGBA_BIT, // Needs to support OpenGL
2282     GLX_RED_SIZE, 1,
2283     GLX_GREEN_SIZE, 1,
2284     GLX_BLUE_SIZE, 1,
2285     GLX_ALPHA_SIZE, 1,
2286     GLX_DEPTH_SIZE, 16,    // Needs to support a 16 bit depth buffer
2287     GLX_DOUBLEBUFFER, 1,   // Needs to support double-buffering
2288     GLX_STEREO, 1,         // Needs to support stereo rendering
2289     GLX_STENCIL_SIZE, 1,
2290     0x20B2, 1,             // Needs to support srgb profile
2291     None                   // end of list
2292   };
2293
2294   // Try for the double-bufferd visual first
2295   XVisualInfo *visualInfo = NULL;
2296   visualInfo = glXChooseVisual( pDisplay, DefaultScreen(pDisplay), doubleBufferVisual );
2297
2298   if( visualInfo == NULL ){
2299     qCritical( "Could not find matching glx visual" );
2300     return res;
2301   }
2302
2303   qDebug() << "Picked visual 0x" << hex << XVisualIDFromVisual( visualInfo->visual );
2304   res = (Qt::HANDLE)( visualInfo->visual );
2305  
2306   return res;
2307 }
2308
2309 #endif // WIN32
2310
2311 /*!
2312   \brief Set default QSurfaceFormat for an application.
2313
2314   This application property should be set before a creation of the QApplication.
2315 */  
2316 void Qtx::initDefaultSurfaceFormat()
2317 {
2318   // Settings from Paraview: 
2319   // This piece of code was taken from QVTKOpenGLWidget::defaultFormat() method in
2320   // order to avoid dependency of the SALOME_Session_Server on vtk libraries
2321   QSurfaceFormat fmt;
2322   fmt.setRenderableType(QSurfaceFormat::OpenGL);
2323   fmt.setVersion(3, 2);
2324   fmt.setProfile(QSurfaceFormat::CoreProfile);
2325   fmt.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
2326   fmt.setRedBufferSize(1);
2327   fmt.setGreenBufferSize(1);
2328   fmt.setBlueBufferSize(1);
2329   fmt.setDepthBufferSize(1);
2330   fmt.setStencilBufferSize(0);
2331 #ifdef WIN32
2332   fmt.setAlphaBufferSize(0);
2333 #else
2334   fmt.setAlphaBufferSize(0);
2335 #endif
2336   fmt.setStereo(false);
2337   fmt.setSamples(0);
2338   
2339   // Settings for OCCT viewer window:
2340   fmt.setDepthBufferSize(16);
2341   fmt.setStencilBufferSize(1);
2342   //  fmt.setProfile(QSurfaceFormat::CompatibilityProfile);
2343
2344   QSurfaceFormat::setDefaultFormat(fmt);
2345 }
2346
2347 /*!
2348   \class Qtx::CmdLineArgs
2349   \brief Get access to the command line arguments in the C-like manner.
2350
2351   This class translates command line arguments stored in QApplication in form of QStrlingList
2352   to the char* array, in the same way as they specified to main() function.
2353
2354   Constructor of class allocates required memory to store arguments; destructor deallocates it,
2355   This allows using this class as a local variable:
2356
2357   \code
2358   Qtx::CmdLineArgs args;
2359   some_function(args.argc(), args.argv()); // function that has main()-like syntax.
2360   \endcode
2361 */
2362
2363 /*!
2364   \brief Default constructor.
2365 */
2366 Qtx::CmdLineArgs::CmdLineArgs()
2367 {
2368   QStringList args = QCoreApplication::arguments();
2369   myArgc = args.size();
2370   myArgv = new char*[myArgc];
2371   for ( int i = 0; i < myArgc; i++ ) {
2372     QByteArray ba = args[i].toUtf8();
2373     myArgv[i] = qstrdup(ba.constData());
2374   }
2375 }
2376
2377 /*!
2378   \brief Destructor. Deallocates the array with command line arguments
2379 */
2380 Qtx::CmdLineArgs::~CmdLineArgs()
2381 {
2382   for ( int i = 0; i < myArgc; i++ )
2383     delete[] myArgv[i];
2384   delete[] myArgv;
2385 }
2386
2387 /*!
2388   \brief Get number of command line arguments
2389   \return number of arguments
2390 */
2391 int Qtx::CmdLineArgs::argc() const
2392 {
2393   return myArgc;
2394 }
2395
2396 /*!
2397   \brief Get command line arguments
2398   \return command line arguments
2399 */
2400 char** Qtx::CmdLineArgs::argv() const
2401 {
2402   return myArgv;
2403 }