Salome HOME
updated copyright message
[modules/gui.git] / src / Qtx / QtxResourceMgr.cxx
index d3c2aa09886e3cc147263455fac25344a10a76f1..5ee5f95c7d7678b2b78f83c1b1abcc7940e0cffe 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2007-2014  CEA/DEN, EDF R&D, OPEN CASCADE
+// Copyright (C) 2007-2023  CEA, EDF, OPEN CASCADE
 //
 // Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
 // CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
@@ -30,6 +30,8 @@
 #include <QDir>
 #include <QFile>
 #include <QFileInfo>
+#include <QJsonDocument>
+#include <QJsonObject>
 #include <QRegExp>
 #include <QTextStream>
 #include <QApplication>
@@ -74,6 +76,10 @@ static const char* pixmap_not_found_xpm[] = {
 
 class QtxResourceMgr::Resources
 {
+private:
+  typedef QMap<QString, Section> SectionMap;
+  typedef QMap<QString, QString> OptionsMap;
+
 public:
   Resources( QtxResourceMgr*, const QString& );
   virtual ~Resources();
@@ -81,7 +87,7 @@ public:
   QString                file() const;
   void                   setFile( const QString& );
 
-  QString                value( const QString&, const QString&, const bool ) const;
+  QString                value( const QString&, const QString&, const bool, const OptionsMap& ) const;
   void                   setValue( const QString&, const QString&, const QString& );
 
   bool                   hasSection( const QString& ) const;
@@ -90,17 +96,15 @@ public:
   void                   removeSection( const QString& );
   void                   removeValue( const QString&, const QString& );
 
-  QPixmap                loadPixmap( const QString&, const QString&, const QString& ) const;
-  QTranslator*           loadTranslator( const QString&, const QString&, const QString& ) const;
-
-  QString                makeSubstitution( const QString&, const QString&, const QString& ) const;
+  QPixmap                loadPixmap( const QString&, const QString&, const QString&, const OptionsMap& ) const;
+  QTranslator*           loadTranslator( const QString&, const QString&, const QString&, const OptionsMap& ) const;
 
   void                   clear();
 
   QStringList            sections() const;
   QStringList            parameters( const QString& ) const;
 
-  QString                path( const QString&, const QString&, const QString& ) const;
+  QString                path( const QString&, const QString&, const QString&, const OptionsMap& ) const;
 
 protected:
   QtxResourceMgr*        resMgr() const;
@@ -109,10 +113,9 @@ private:
   Section                section( const QString& );
   const Section          section( const QString& ) const;
 
-  QString                fileName( const QString&, const QString&, const QString& ) const;
+  QString                makeSubstitution( const QString&, const QString&, const QString&, const OptionsMap& ) const;
 
-private:
-  typedef QMap<QString, Section> SectionMap;
+  QString                fileName( const QString&, const QString&, const QString&, const OptionsMap& ) const;
 
 private:
   QtxResourceMgr*        myMgr;             //!< resources manager
@@ -172,7 +175,7 @@ void QtxResourceMgr::Resources::setFile( const QString& fn )
   \return parameter value or null QString if there is no such parameter
   \sa setValue(), makeSubstitution()
 */
-QString QtxResourceMgr::Resources::value( const QString& sect, const QString& name, const bool subst ) const
+QString QtxResourceMgr::Resources::value( const QString& sect, const QString& name, const bool subst, const OptionsMap& constants ) const
 {
   QString val;
 
@@ -180,7 +183,7 @@ QString QtxResourceMgr::Resources::value( const QString& sect, const QString& na
   {
     val = section( sect )[name];
     if ( subst )
-      val = makeSubstitution( val, sect, name );
+      val = makeSubstitution( val, sect, name, constants );
   }
   return val;
 }
@@ -291,9 +294,9 @@ QStringList QtxResourceMgr::Resources::parameters( const QString& sec ) const
   \return absolute file path or null QString if file does not exist
   \sa fileName(), file(), makeSubstitution()
 */
-QString QtxResourceMgr::Resources::path( const QString& sec, const QString& prefix, const QString& name ) const
+QString QtxResourceMgr::Resources::path( const QString& sec, const QString& prefix, const QString& name, const OptionsMap& constants ) const
 {
-  QString filePath = fileName( sec, prefix, name );
+  QString filePath = fileName( sec, prefix, name, constants );
   if ( !filePath.isEmpty() )
   {
     if ( !QFileInfo( filePath ).exists() )
@@ -354,7 +357,7 @@ const QtxResourceMgr::Section QtxResourceMgr::Resources::section( const QString&
           does not exist in section \sec
   \sa path(), file(), makeSubstitution()
 */
-QString QtxResourceMgr::Resources::fileName( const QString& sect, const QString& prefix, const QString& name ) const
+QString QtxResourceMgr::Resources::fileName( const QString& sect, const QString& prefix, const QString& name, const OptionsMap& constants ) const
 {
   QString path;
   if ( !QFileInfo( name ).isRelative() )
@@ -365,7 +368,7 @@ QString QtxResourceMgr::Resources::fileName( const QString& sect, const QString&
   {
     if ( hasValue( sect, prefix ) )
     {
-      path = value( sect, prefix, true );
+      path = value( sect, prefix, true, constants );
       if ( !path.isEmpty() )
       {
        if ( QFileInfo( path ).isRelative() )
@@ -377,7 +380,7 @@ QString QtxResourceMgr::Resources::fileName( const QString& sect, const QString&
   }
   if( !path.isEmpty() )
   {
-    QString fname = QDir::convertSeparators( path );
+    QString fname = QDir::toNativeSeparators( path );
     QFileInfo inf( fname );
     fname = inf.absoluteFilePath();
     return fname;
@@ -397,9 +400,9 @@ QString QtxResourceMgr::Resources::fileName( const QString& sect, const QString&
   \param name pixmap file name
   \return pixmap loaded from file
 */
-QPixmap QtxResourceMgr::Resources::loadPixmap( const QString& sect, const QString& prefix, const QString& name ) const
+QPixmap QtxResourceMgr::Resources::loadPixmap( const QString& sect, const QString& prefix, const QString& name, const OptionsMap& constants ) const
 {
-  QString fname = fileName( sect, prefix, name );
+  QString fname = fileName( sect, prefix, name, constants );
   bool toCache = resMgr() ? resMgr()->isPixmapCached() : false;
   QPixmap p;
   if( toCache && myPixmapCache.contains( fname ) )
@@ -420,10 +423,10 @@ QPixmap QtxResourceMgr::Resources::loadPixmap( const QString& sect, const QStrin
   \param name translation file name
   \return just created and loaded translator or 0 in case of error
 */
-QTranslator* QtxResourceMgr::Resources::loadTranslator( const QString& sect, const QString& prefix, const QString& name ) const
+QTranslator* QtxResourceMgr::Resources::loadTranslator( const QString& sect, const QString& prefix, const QString& name, const OptionsMap& constants ) const
 {
   QTranslator* trans = new QtxTranslator( 0 );
-  QString fname = QDir::convertSeparators( fileName( sect, prefix, name ) );
+  QString fname = QDir::toNativeSeparators( fileName( sect, prefix, name, constants ) );
   if ( !trans->load( Qtx::file( fname, false ), Qtx::dir( fname ) ) )
   {
     delete trans;
@@ -443,7 +446,7 @@ QTranslator* QtxResourceMgr::Resources::loadTranslator( const QString& sect, con
   \param name name of variable which must be ignored during substitution
   \return processed string (with all substitutions made)
 */
-QString QtxResourceMgr::Resources::makeSubstitution( const QString& str, const QString& sect, const QString& name ) const
+QString QtxResourceMgr::Resources::makeSubstitution( const QString& str, const QString& sect, const QString& name, const OptionsMap& constants ) const
 {
   QString res = str;
 
@@ -457,11 +460,15 @@ QString QtxResourceMgr::Resources::makeSubstitution( const QString& str, const Q
     if ( envName.isNull() )
       break;
 
-    QString newStr;
-    if ( ::getenv( envName.toLatin1() ) )
-      newStr = QString( ::getenv( envName.toLatin1() ) );
+    // First we look in the constants map
+    QString newStr = constants.value( envName, QString() );
 
-    if ( newStr.isNull() )
+    // Then we check for environment variable
+       QString tmpValue = Qtx::getenv( envName );
+    if ( newStr.isEmpty() && !tmpValue.isEmpty() )
+      newStr = tmpValue;
+
+    if ( newStr.isEmpty() )
     {
       if ( ignoreMap.contains( envName ) )
       {
@@ -470,7 +477,7 @@ QString QtxResourceMgr::Resources::makeSubstitution( const QString& str, const Q
       }
 
       if ( hasValue( sect, envName ) )
-        newStr = value( sect, envName, false );
+        newStr = value( sect, envName, false, constants );
       ignoreMap.insert( envName, 0 );
     }
     res.replace( start, len, newStr );
@@ -531,7 +538,7 @@ bool QtxResourceMgr::IniFormat::load( const QString& fname, QMap<QString, Sectio
 
 
 /*!
-  \brief Load resources from xml-file.
+  \brief Load resources from ini-file.
   \param fname resources file name
   \param secMap resources map to be filled in
   \param importHistory list of already imported resources files (to prevent import loops)
@@ -610,11 +617,11 @@ bool QtxResourceMgr::IniFormat::load( const QString& fname, QMap<QString, Sectio
     }
     else if ( section == "import" )
     {
-      QString impFile = QDir::convertSeparators( Qtx::makeEnvVarSubst( data, Qtx::Always ) );
+      QString impFile = QDir::toNativeSeparators( Qtx::makeEnvVarSubst( data, Qtx::Always ) );
       QFileInfo impFInfo( impFile );
       if ( impFInfo.isRelative() )
-             impFInfo.setFile( aFinfo.absoluteDir(), impFile );
-    
+        impFInfo.setFile( aFinfo.absoluteDir(), impFile );
+
       QMap<QString, Section> impMap;
       if ( !load( impFInfo.absoluteFilePath(), impMap, importHistory ) )
       {
@@ -848,7 +855,7 @@ bool QtxResourceMgr::XmlFormat::load( const QString& fname, QMap<QString, Sectio
       }
       else if ( sectElem.tagName() == importTag() && sectElem.hasAttribute( nameAttribute() ) )
       {
-         QString impFile = QDir::convertSeparators( Qtx::makeEnvVarSubst( sectElem.attribute( nameAttribute() ), Qtx::Always ) );
+         QString impFile = QDir::toNativeSeparators( Qtx::makeEnvVarSubst( sectElem.attribute( nameAttribute() ), Qtx::Always ) );
              QFileInfo impFInfo( impFile );
              if ( impFInfo.isRelative() )
                 impFInfo.setFile( aFinfo.absoluteDir(), impFile );
@@ -1027,6 +1034,222 @@ QString QtxResourceMgr::XmlFormat::valueAttribute() const
   return str;
 }
 
+/*!
+  \class QtxResourceMgr::JsonFormat
+  \internal
+  \brief Reader/writer for .json resources files.
+*/
+
+class QtxResourceMgr::JsonFormat : public Format
+{
+public:
+  JsonFormat();
+  ~JsonFormat();
+
+protected:
+  JsonFormat( const QString& );
+  virtual bool load( const QString&, QMap<QString, Section>& );
+  virtual bool save( const QString&, const QMap<QString, Section>& );
+
+private:
+  bool         load( const QString&, QMap<QString, Section>&, QSet<QString>& );
+};
+
+/*!
+  \brief Constructor.
+*/
+QtxResourceMgr::JsonFormat::JsonFormat()
+: QtxResourceMgr::JsonFormat( "json" )
+{
+}
+
+/*!
+  \brief Constructor.
+*/
+QtxResourceMgr::JsonFormat::JsonFormat( const QString& fmt )
+: Format( fmt )
+{
+}
+
+/*!
+  \brief Destructor.
+*/
+QtxResourceMgr::JsonFormat::~JsonFormat()
+{
+}
+
+/*!
+  \brief Load resources from json-file.
+  \param fname resources file name
+  \param secMap resources map to be filled in
+  \return \c true on success and \c false on error
+*/
+bool QtxResourceMgr::JsonFormat::load( const QString& fname, QMap<QString, Section>& secMap )
+{
+  QSet<QString> importHistory;
+  return load( fname, secMap, importHistory );
+}
+
+/*!
+  \brief Load resources from json-file.
+  \param fname resources file name
+  \param secMap resources map to be filled in
+  \param importHistory list of already imported resources files (to prevent import loops)
+  \return \c true on success or \c false on error
+*/
+bool QtxResourceMgr::JsonFormat::load( const QString& fname, QMap<QString, Section>& secMap, QSet<QString>& importHistory )
+{
+  QString aFName = fname.trimmed();
+  if ( !QFileInfo( aFName ).exists() )
+  {
+    if ( QFileInfo( aFName + ".json" ).exists() )
+      aFName += ".json";
+    else if ( QFileInfo( aFName + ".JSON" ).exists() )
+      aFName += ".JSON";
+    else
+      return false; // file does not exist
+  }
+  QFileInfo aFinfo( aFName );
+  aFName = aFinfo.canonicalFilePath();
+
+  if ( !importHistory.contains( aFName ) )
+    importHistory.insert( aFName );
+  else
+    return true;   // already imported (prevent import loops)
+
+  QFile file( aFName );
+  if ( !file.open( QFile::ReadOnly ) )
+    return false;  // file is not accessible
+
+  QJsonDocument document = QJsonDocument::fromJson( file.readAll() );
+  if ( document.isNull() )
+    return false;  // invalid json file
+
+  QJsonObject root = document.object();
+  foreach ( QString sectionName, root.keys() )
+  {
+    if ( sectionName == "import" )
+    {
+      QString impFile = root.value( sectionName ).toString();
+      if ( impFile.isEmpty() )
+        continue;
+      QString impPath = QDir::toNativeSeparators( Qtx::makeEnvVarSubst( impFile, Qtx::Always ) );
+      QFileInfo impFInfo( impPath );
+      if ( impFInfo.isRelative() )
+        impFInfo.setFile( aFinfo.absoluteDir(), impPath );
+      QMap<QString, Section> impMap;
+      if ( !load( impFInfo.absoluteFilePath(), impMap, importHistory ) )
+      {
+        qDebug() << "QtxResourceMgr: Error with importing file:" << impPath;
+      }
+      else
+      {
+        QMap<QString, Section>::const_iterator it = impMap.constBegin();
+        for ( ; it != impMap.constEnd() ; ++it )
+        {
+          if ( !secMap.contains( it.key() ) )
+          {
+            // insert full section
+            secMap.insert( it.key(), it.value() );
+          }
+          else
+          {
+            // insert all parameters from the section
+            Section::ConstIterator paramIt = it.value().begin();
+            for ( ; paramIt != it.value().end() ; ++paramIt )
+            {
+              if ( !secMap[it.key()].contains( paramIt.key() ) )
+                secMap[it.key()].insert( paramIt.key(), paramIt.value() );
+            }
+          }
+        }
+      }
+    }
+    else
+    {
+      QJsonObject section = root.value( sectionName ).toObject();
+      if ( !section.isEmpty() )
+      {
+       // case when a top-level item is a section
+        foreach ( QString parameterName, section.keys() )
+        {
+          // each value must be a string, number, or boolean
+          QJsonValue parameter = section.value( parameterName );
+          if ( parameter.isDouble() )
+            secMap[sectionName].insert( parameterName, QString::number( parameter.toDouble() ) );
+          else if ( parameter.isBool() )
+            secMap[sectionName].insert( parameterName, QString( parameter.toBool() ? "true" : "false" ) );
+          else if ( parameter.isString() )
+            secMap[sectionName].insert( parameterName, parameter.toString() );
+        }
+      }
+      else
+      {
+        QString parameterName = sectionName;
+        sectionName = "General"; // default section name for top-level items
+        // each value must be a string, number, or boolean
+        QJsonValue parameter = root.value( parameterName );
+        if ( parameter.isDouble() )
+          secMap[sectionName].insert( parameterName, QString::number( parameter.toDouble() ) );
+        else if ( parameter.isBool() )
+          secMap[sectionName].insert( parameterName, QString( parameter.toBool() ? "true" : "false" ) );
+        else if ( parameter.isString() )
+          secMap[sectionName].insert( parameterName, parameter.toString() );
+      }
+    }
+  }
+
+  if ( !secMap.isEmpty() )
+    qDebug() << "QtxResourceMgr: File" << fname << "is loaded successfully";
+  return true;
+}
+
+/*!
+  \brief Save resources to the json-file.
+  \param fname resources file name
+  \param secMap resources map
+  \return \c true on success and \c false on error
+*/
+bool QtxResourceMgr::JsonFormat::save( const QString& fname, const QMap<QString, Section>& secMap )
+{
+  if ( !Qtx::mkDir( QFileInfo( fname ).absolutePath() ) )
+    return false;
+
+  QFile file( fname );
+  if ( !file.open( QFile::WriteOnly ) )
+    return false;
+
+  QJsonObject root;
+  for ( QMap<QString, Section>::ConstIterator it = secMap.begin(); it != secMap.end(); ++it )
+  {
+    // note: we write all values as string, as it's enough to store resources as strings
+    // anyway resource manager converts values to strings when reading JSON file
+    QJsonObject section;
+    for ( Section::ConstIterator iter = it.value().begin(); iter != it.value().end(); ++iter )
+      section.insert( iter.key(), iter.value() );
+    root.insert( it.key(), section );
+  }
+
+  QJsonDocument document;
+  document.setObject( root );
+  file.write( document.toJson() );
+  file.close();
+  return true;
+}
+
+/*!
+  \class QtxResourceMgr::SalomexFormat
+  \internal
+  \brief Reader/writer for .salomex resources files. This is an alias for JSON format.
+*/
+
+class QtxResourceMgr::SalomexFormat : public JsonFormat
+{
+public:
+  SalomexFormat() : JsonFormat( "salomex" ) {}
+};
+
+
 /*!
   \class QtxResourceMgr::Format
   \brief Generic resources files reader/writer class.
@@ -1122,9 +1345,13 @@ bool QtxResourceMgr::Format::save( Resources* res )
   if ( !res )
     return false;
 
+  QtxResourceMgr* mgr = res->resMgr();
+
+  if ( mgr->appName().isEmpty() )
+    return false;
+
   Qtx::mkDir( Qtx::dir( res->myFileName ) );
 
-  QtxResourceMgr* mgr = res->resMgr();
   QString name = mgr ? mgr->userFileName( mgr->appName(), false ) : res->myFileName;
   return save( name, res->mySections );
 }
@@ -1163,7 +1390,7 @@ bool QtxResourceMgr::Format::save( Resources* res )
   (internationalization mechanism), load pixmaps and other resources from
   external files, etc.
 
-  Currently it supports .ini and .xml resources file formats. To implement
+  Currently it supports .ini, .xml, and .json resources file formats. To implement
   own resources file format, inherit from the Format class and implement virtual
   Format::load() and Format::save() methods.
 
@@ -1247,8 +1474,9 @@ QtxResourceMgr::QtxResourceMgr( const QString& appName, const QString& resVarTem
     envVar = envVar.arg( appName );
 
   QString dirs;
-  if ( ::getenv( envVar.toLatin1() ) )
-    dirs = ::getenv( envVar.toLatin1() );
+  QString tmpValue = Qtx::getenv( envVar );
+  if ( !tmpValue.isEmpty() )
+    dirs = tmpValue;
 #ifdef WIN32
   QString dirsep = ";";      // for Windows: ";" is used as directories separator
 #else
@@ -1258,6 +1486,26 @@ QtxResourceMgr::QtxResourceMgr( const QString& appName, const QString& resVarTem
 
   installFormat( new XmlFormat() );
   installFormat( new IniFormat() );
+  installFormat( new JsonFormat() );
+  installFormat( new SalomexFormat() );
+
+  setOption( "translators", QString( "%P_msg_%L.qm|%P_images.qm" ) );
+}
+
+/*!
+  \brief Default constructor
+*/
+QtxResourceMgr::QtxResourceMgr()
+: myCheckExist( true ),
+  myDefaultPix( 0 ),
+  myIsPixmapCached( true ),
+  myHasUserValues( false ),
+  myWorkingMode( IgnoreUserValues )
+{
+  installFormat( new XmlFormat() );
+  installFormat( new IniFormat() );
+  installFormat( new JsonFormat() );
+  installFormat( new SalomexFormat() );
 
   setOption( "translators", QString( "%P_msg_%L.qm|%P_images.qm" ) );
 }
@@ -1333,13 +1581,14 @@ QStringList QtxResourceMgr::dirList() const
 */
 void QtxResourceMgr::initialize( const bool autoLoad ) const
 {
-  if ( !myResources.isEmpty() )
+  if ( !myResources.isEmpty() || appName().isEmpty() )
     return;
 
   QtxResourceMgr* that = (QtxResourceMgr*)this;
 
-  if ( !userFileName( appName() ).isEmpty() )
-    that->myResources.append( new Resources( that, userFileName( appName() ) ) );
+  QString userFile = userFileName( appName() );
+  if ( !userFile.isEmpty() )
+    that->myResources.append( new Resources( that, userFile ) );
 
   that->myHasUserValues = myResources.count() > 0;
 
@@ -1409,11 +1658,14 @@ QtxResourceMgr::WorkingMode QtxResourceMgr::workingMode() const
   Note, that setValue() method always put the value to the user settings file.
   
   \param mode new working mode
+  \return previous working mode
   \sa workingMode(), value(), hasValue(), hasSection(), setValue()
 */
-void QtxResourceMgr::setWorkingMode( WorkingMode mode )
+QtxResourceMgr::WorkingMode QtxResourceMgr::setWorkingMode( WorkingMode mode )
 {
+  WorkingMode m = myWorkingMode;
   myWorkingMode = mode;
+  return m;
 }
 
 /*!
@@ -1570,23 +1822,31 @@ bool QtxResourceMgr::value( const QString& sect, const QString& name, QByteArray
   if ( !value( sect, name, val, true ) )
     return false;
 
-  baVal.clear();
-  QStringList lst = val.split( QRegExp( "[\\s|,]" ), QString::SkipEmptyParts );
-  for ( QStringList::ConstIterator it = lst.begin(); it != lst.end(); ++it )
-  {
-    int base = 10;
-    QString str = *it;
-    if ( str.startsWith( "#" ) )
+  if ( val.startsWith( "@ByteArray(" ) && val.endsWith( ')' ) ) {
+    baVal = QByteArray( val.midRef( 11, val.size() - 12 ).toLatin1() );
+  }
+  else  {
+    if ( val.startsWith( "@ByteArray[" ) && val.endsWith( ']' ) ) {
+      val = val.mid( 11, val.size() - 12 );
+    }
+    baVal.clear();
+    QStringList lst = val.split( QRegExp( "[\\s|,]" ), QString::SkipEmptyParts );
+    for ( QStringList::ConstIterator it = lst.begin(); it != lst.end(); ++it )
     {
-      base = 16;
-      str = str.mid( 1 );
+      int base = 10;
+      QString str = *it;
+      if ( str.startsWith( "#" ) )
+      {
+        base = 16;
+        str = str.mid( 1 );
+      }
+      bool ok = false;
+      int num = str.toInt( &ok, base );
+      if ( !ok || num < 0 || num > 255 )
+        continue;
+      
+      baVal.append( (char)num );
     }
-    bool ok = false;
-    int num = str.toInt( &ok, base );
-    if ( !ok || num < 0 || num > 255 )
-      continue;
-
-    baVal.append( (char)num );
   }
   return !baVal.isEmpty();
 }
@@ -1683,7 +1943,7 @@ bool QtxResourceMgr::value( const QString& sect, const QString& name, QString& v
   {
     ok = (*it)->hasValue( sect, name );
     if ( ok )
-      val = (*it)->value( sect, name, subst );
+      val = (*it)->value( sect, name, subst, myConstants );
   }
 
   return ok;
@@ -1794,10 +2054,10 @@ QColor QtxResourceMgr::colorValue( const QString& sect, const QString& name, con
   \param def default value
   \return parameter value (or default value if parameter is not found)
 */
-QString QtxResourceMgr::stringValue( const QString& sect, const QString& name, const QString& def ) const
+QString QtxResourceMgr::stringValue( const QString& sect, const QString& name, const QString& def, const bool subst ) const
 {
   QString val;
-  if ( !value( sect, name, val ) )
+  if ( !value( sect, name, val, subst ) )
     val = def;
   return val;
 }
@@ -2056,7 +2316,9 @@ void QtxResourceMgr::setValue( const QString& sect, const QString& name, const Q
     ::sprintf( buf, "#%02X", (unsigned char)val.at( i ) );
     lst.append( QString( buf ) );
   }
-  setResource( sect, name, lst.join( " " ) );
+
+  QString result = QString( "@ByteArray[%1]" ).arg( lst.join( " " ) );
+  setResource( sect, name, result );
 }
 
 /*!
@@ -2169,7 +2431,7 @@ void QtxResourceMgr::setCurrentFormat( const QString& fmt )
   myFormats.removeAll( form );
   myFormats.prepend( form );
 
-  if ( myResources.isEmpty() )
+  if ( myResources.isEmpty() || appName().isEmpty() )
     return;
 
   ResList::Iterator resIt = myResources.begin();
@@ -2259,6 +2521,42 @@ void QtxResourceMgr::setOption( const QString& opt, const QString& val )
   myOptions.insert( opt, val );
 }
 
+/*!
+  \brief Get names of all known constants.
+  \return list of constants names
+  \sa constant(), setConstant
+*/
+QStringList QtxResourceMgr::constants() const
+{
+  return myConstants.keys();
+}
+
+/*!
+  \brief Get the value of the known constant.
+
+  If constant is not set, null QString is returned.
+
+  \param name constant name
+  \return constant value
+  \sa setConstant(), constants()
+*/
+QString QtxResourceMgr::constant( const QString& name ) const
+{
+  return myConstants.value( name, QString() );
+}
+
+/*!
+  \brief Set the value of the constant.
+  \param name constant name
+  \param value constant value
+  \sa constants(), constants()
+*/
+void QtxResourceMgr::setConstant( const QString& name, const QString& value )
+{
+  if ( !name.isEmpty() )
+    myConstants.insert( name, value );
+}
+
 /*!
   \brief Load all resources from all resource files (global and user).
   \return \c true on success and \c false on error
@@ -2326,6 +2624,42 @@ bool QtxResourceMgr::save()
   return result;
 }
 
+/*!
+  \brief Load resource from given file.
+*/
+bool QtxResourceMgr::addResource( const QString& fname )
+{
+  if ( fname.isEmpty() )
+    return false;
+
+  QFileInfo fi( fname );
+
+  if ( fi.exists() && fi.isDir() && !appName().isEmpty() )
+    fi.setFile( QDir( fname ).filePath( globalFileName( appName() ) ) );
+
+  if ( !fi.exists() )
+    return false;
+
+  QString dirName = fi.absolutePath();
+  if ( myDirList.contains( dirName ) )
+    return true; // file already loaded
+
+  Format* fmt = format( fi.suffix() );
+  if ( !fmt )
+    return false;
+
+  Resources* resource = new Resources( this, fi.absoluteFilePath() );
+  if ( !fmt->load( resource ) )
+  {
+    delete resource;
+    return false;
+  }
+
+  myDirList << dirName;
+  myResources << resource;
+  return true;
+}
+
 /*!
   \brief Get all sections names.
   \return list of section names
@@ -2481,7 +2815,7 @@ QString QtxResourceMgr::path( const QString& sect, const QString& prefix, const
     ++it;
 
   for ( ; it != myResources.end() && res.isEmpty(); ++it )
-    res = (*it)->path( sect, prefix, name );
+    res = (*it)->path( sect, prefix, name, myConstants );
   return res;
 }
 
@@ -2617,12 +2951,49 @@ QPixmap QtxResourceMgr::loadPixmap( const QString& prefix, const QString& name,
     ++it;
 
   for ( ; it != myResources.end() && pix.isNull(); ++it )
-    pix = (*it)->loadPixmap( resSection(), prefix, name );
+    pix = (*it)->loadPixmap( resSection(), prefix, name, myConstants );
   if ( pix.isNull() )
     pix = defPix;
   return pix;
 }
 
+/*!
+  \brief Specify default language for the application.
+*/
+QString QtxResourceMgr::defaultLanguage() const
+{
+  return "";
+}
+
+
+/*!
+  \brief Select language to be used.
+  \param preferableLanguage preferable language name (if empty, default language is used)
+*/
+QString QtxResourceMgr::language( const QString& preferableLanguage ) const
+{
+  // first try to select preferable language probably specified via the parameter
+  QString lang = preferableLanguage;
+
+  // then try default language; selection of default language can be redefined in successors
+  if ( lang.isEmpty() )
+    lang = defaultLanguage();
+
+  // then try language as defined in the preferences files
+  if ( lang.isEmpty() )
+    value( langSection(), "language", lang );
+
+  // finally try strongly hardcoded Ennglish
+  if ( lang.isEmpty() )
+  {
+    lang = QString( "en" );
+    qWarning() << "QtxResourceMgr: Language not specified. Assumed:" << lang;
+  }
+
+  return lang;
+}
+
+
 /*!
   \brief Load translation files according to the specified language.
 
@@ -2642,26 +3013,19 @@ QPixmap QtxResourceMgr::loadPixmap( const QString& prefix, const QString& name,
   see userFileName()). To avoid loading user settings, pass \c false as first parameter.
 
   \param pref parameter which defines translation context (for example, package name)
-  \param l language name
+  \param preferableLanguage language name
 
   \sa resSection(), langSection(), loadTranslators()
 */
-void QtxResourceMgr::loadLanguage( const QString& pref, const QString& l )
+void QtxResourceMgr::loadLanguage( const QString& pref, const QString& preferableLanguage )
 {
   initialize( true );
 
   QMap<QChar, QString> substMap;
-  substMap.insert( 'A', appName() );
+  if ( !appName().isEmpty() )
+    substMap.insert( 'A', appName() );
 
-  QString lang = l;
-  if ( lang.isEmpty() )
-    value( langSection(), "language", lang );
-
-  if ( lang.isEmpty() )
-  {
-    lang = QString( "en" );
-    qWarning() << "QtxResourceMgr: Language not specified. Assumed:" << lang;
-  }
+  QString lang = language( preferableLanguage );
 
   substMap.insert( 'L', lang );
 
@@ -2694,15 +3058,11 @@ void QtxResourceMgr::loadLanguage( const QString& pref, const QString& l )
   if ( pref.isEmpty() && lang != "en" ) {
     // load Qt resources
     QString qt_translations = QLibraryInfo::location( QLibraryInfo::TranslationsPath );
-    QString qt_dir_trpath;
-    if ( ::getenv( "QTDIR" ) )
-      qt_dir_trpath = QString( ::getenv( "QTDIR" ) );
-    if ( !qt_dir_trpath.isEmpty() )
-      qt_dir_trpath = QDir( qt_dir_trpath ).absoluteFilePath( "translations" );
-
+    QString qt_dir_trpath = Qtx::qtDir( "translations" );
     QTranslator* trans = new QtxTranslator( 0 );
-    if ( trans->load( QString("qt_%1").arg( lang ), qt_translations ) || trans->load( QString("qt_%1").arg( lang ), qt_dir_trpath ) )
-      QApplication::instance()->installTranslator( trans );
+    if ( trans->load( QString("qt_%1").arg( lang ), qt_translations ) || trans->load( QString("qt_%1").arg( lang ), qt_dir_trpath ) ) {
+      if ( QApplication::instance() ) QApplication::instance()->installTranslator( trans );
+    }
   }
 
   for ( QStringList::ConstIterator iter = prefixList.begin(); iter != prefixList.end(); ++iter )
@@ -2743,12 +3103,12 @@ void QtxResourceMgr::loadTranslators( const QString& prefix, const QStringList&
   {
     for ( QStringList::ConstIterator itr = translators.begin(); itr != translators.end(); ++itr )
     {
-      trans = (*it)->loadTranslator( resSection(), prefix, *itr );
+      trans = (*it)->loadTranslator( resSection(), prefix, *itr, myConstants );
       if ( trans )
       {
         if ( !myTranslator[prefix].contains( trans ) )
           myTranslator[prefix].append( trans );
-        QApplication::instance()->installTranslator( trans );
+        if ( QApplication::instance() ) QApplication::instance()->installTranslator( trans );
       }
     }
   }
@@ -2775,12 +3135,30 @@ void QtxResourceMgr::loadTranslator( const QString& prefix, const QString& name
     Resources* r = it.previous();
     if ( r == ur ) break;
 
-    trans = r->loadTranslator( resSection(), prefix, name );
+    trans = r->loadTranslator( resSection(), prefix, name, myConstants );
     if ( trans )
     {
       if ( !myTranslator[prefix].contains( trans ) )
         myTranslator[prefix].append( trans );
-      QApplication::instance()->installTranslator( trans );
+      if ( QApplication::instance() ) QApplication::instance()->installTranslator( trans );
+    }
+  }
+}
+
+/*!
+  \brief Add custom translator.
+  \param prefix parameter which defines translation context (for example, package name)
+  \param translator translator being installed
+  \sa loadLanguage(), loadTranslators()
+*/
+void QtxResourceMgr::addTranslator( const QString& prefix, QTranslator* translator )
+{
+  if ( translator )
+  {
+    if ( !myTranslator[prefix].contains( translator ) ) {
+      myTranslator[prefix].append( translator );
+      if ( QApplication::instance() )
+        QApplication::instance()->installTranslator( translator );
     }
   }
 }
@@ -2796,7 +3174,7 @@ void QtxResourceMgr::removeTranslators( const QString& prefix )
 
   for ( TransList::Iterator it = myTranslator[prefix].begin(); it != myTranslator[prefix].end(); ++it )
   {
-    QApplication::instance()->removeTranslator( *it );
+    if ( QApplication::instance() ) QApplication::instance()->removeTranslator( *it );
     delete *it;
   }
 
@@ -2815,8 +3193,10 @@ void QtxResourceMgr::raiseTranslators( const QString& prefix )
 
   for ( TransList::Iterator it = myTranslator[prefix].begin(); it != myTranslator[prefix].end(); ++it )
   {
-    QApplication::instance()->removeTranslator( *it );
-    QApplication::instance()->installTranslator( *it );
+    if ( QApplication::instance() ) {
+      QApplication::instance()->removeTranslator( *it );
+      QApplication::instance()->installTranslator( *it );
+    }
   }
 }
 
@@ -2887,7 +3267,7 @@ QString QtxResourceMgr::userFileName( const QString& appName, const bool /*for_l
 {
   QString fileName;
   QString pathName = QDir::homePath();
-  QString cfgAppName = QApplication::applicationName();
+  QString cfgAppName = QApplication::organizationName();
   if ( !cfgAppName.isEmpty() )
     pathName = Qtx::addSlash( Qtx::addSlash( pathName ) + QString( ".config" ) ) + cfgAppName;