Salome HOME
updated copyright message
[modules/gui.git] / src / Qtx / QtxResourceMgr.cxx
index c5cd1d74ec0ae36a0d4bd509dfdac4ee842b8a6a..5ee5f95c7d7678b2b78f83c1b1abcc7940e0cffe 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2007-2016  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>
@@ -462,8 +464,9 @@ QString QtxResourceMgr::Resources::makeSubstitution( const QString& str, const Q
     QString newStr = constants.value( envName, QString() );
 
     // Then we check for environment variable
-    if ( newStr.isEmpty() && ::getenv( envName.toLatin1() ) )
-      newStr = QString( ::getenv( envName.toLatin1() ) );
+       QString tmpValue = Qtx::getenv( envName );
+    if ( newStr.isEmpty() && !tmpValue.isEmpty() )
+      newStr = tmpValue;
 
     if ( newStr.isEmpty() )
     {
@@ -535,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)
@@ -617,8 +620,8 @@ bool QtxResourceMgr::IniFormat::load( const QString& fname, QMap<QString, Sectio
       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 ) )
       {
@@ -1031,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.
@@ -1126,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 );
 }
@@ -1167,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.
 
@@ -1251,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
@@ -1262,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" ) );
 }
@@ -1337,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;
 
@@ -2186,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();
@@ -2379,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
@@ -2684,6 +2965,35 @@ 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.
 
@@ -2703,28 +3013,19 @@ QString QtxResourceMgr::defaultLanguage() const
   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() );
-
-  QString lang = l;
-  if ( lang.isEmpty() )
-    lang = defaultLanguage();
-  if ( lang.isEmpty() )
-    value( langSection(), "language", lang );
+  if ( !appName().isEmpty() )
+    substMap.insert( 'A', appName() );
 
-  if ( lang.isEmpty() )
-  {
-    lang = QString( "en" );
-    qWarning() << "QtxResourceMgr: Language not specified. Assumed:" << lang;
-  }
+  QString lang = language( preferableLanguage );
 
   substMap.insert( 'L', lang );
 
@@ -2966,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;