Search code examples
c++qtqt5qsettings

Prevent QSettings from adding organization name to the path set by QSettings::setPath()


Is there a way of setting the actual default path of QSettings storage (with or without filename), and preventing QSettings from appending stuff like organization name to that path? That with the requirement that the further usages of QSettings stay the same and have no knowledge of the settings storage path.

Consider the use case. In my main I have the following code:

QApplication a(argc, argv);

...

QCoreApplication::setApplicationName(APP_NAME);
QCoreApplication::setOrganizationDomain(MY_DOMAIN);
QCoreApplication::setOrganizationName(MY_ORGANIZATION);
QCoreApplication::setApplicationVersion(APP_VERSION);

...

a.exec();

and all over my project I use QSettings like this:

QSettings settings;

settings.setValue(somePath, someValue);

someOtherValue = settings.value(someOtherPath, someDefault);

Now I got a requirement to make the application portable. So I change the code in main to this:

QApplication a(argc, argv);

...

QCoreApplication::setApplicationName(APP_NAME);
QCoreApplication::setOrganizationDomain(MY_DOMAIN);
QCoreApplication::setOrganizationName(MY_ORGANIZATION);
QCoreApplication::setApplicationVersion(APP_VERSION);

if (PORTABLE) {
    QSettings::setDefaultFormat(QSettings::IniFormat);
    QString settingsPath = a.applicationDirPath() + "/settings";
    QSettings::setPath(QSettings::defaultFormat(), QSettings::UserScope, settingsPath);
    QSettings::setPath(QSettings::defaultFormat(), QSettings::SystemScope, settingsPath);
}

...

a.exec();

But instead of the settings being written to APP_PATH/settings/ the actual location becomes APP_PATH/settings/ORGANIZATION_NAME/, which in my opinion is a bit too ugly.

I would like to set up QSettings to store the settings in a specific location. The user code must not be aware of the storage location change. Ideally set up QSettings in main, and use the objects, constructed with default constructor further on.

What I have found out is that only the QSettings(const QString &fileName, QSettings::Format format, QObject *parent = nullptr) constructor can be used to really set the storage location. All other constructors keep adding stuff to the path. And as I don't want the user code to be aware of the application portability, using different constructors each time is not an option.

I thought about subclassing QSettings kind of like this to fix setPath():

class Settings : public QSettings {
private:
    static QString mPath = "";

public:
    Settings(QObject *parent = nullptr) :
        QSettings(
            mPath.isEmpty() ? 
            QSettings() : 
            QSettings(mPath, Settings::defaultFormat, parent)
        )
    {}

    static void setPath(QSettings::Format format, QSettings::Scope scope, const QString &path)
    {
        mPath = path;
        QSettings::setPath(format, scope, path);
    }
} 

but it's impossible because QSettings doesn't have a copy constructor. Constructing the base with QSettings(mPath, Settings::defaultFormat, parent) in every case, but setting mPath to the default path in case it hasn't been set, can't be done too, because there is no method to get a default QSettings storage path (there is setPath, but no getPath; there is getFileName and no setFileName; deriving a "path" from fileName is error prone because Qt adds some "magic", like that organization name, to the "path" to derive fileName, and that "magic" isn't really documented).

So the only solutions I came up with are:

  1. Creating a factory that is aware of the application portability, constructs a QSettings object on a heap with the appropriate constructor, and returns a pointer to it. That has a downside of having to manage the memory of that dynamically constructed object, hence unreasonably added complexity. Also it adds another entity that depends on the application portability.
  2. Doing as above but with singleton. This as well adds excess complexity while solving dead simple problem. Also in this case the QSettings object will exist over entire application lifetime.

So I have a feeling that I'm definitely missing something here, and I wonder if there is a simpler solution to this problem.

Side note: Currently I solved this by setting my organization name to "settings" if the app is portable, so the application settings are stored in APP_FOLDER/settings/APP_NAME.ini. A dirty hack, but saves a lot of complexity. I don't really use the organization name anywhere anyway.


Solution

  • My understanding is that you essentially want the QSettings to be constructed in one of two ways based on whether or not a specific path has been set.

    Instead of inheriting from QSettings you could create a class with an overloaded pointer to member -> operator that returns a pointer to a QSettings instance. That way the initialization of the QSettings can be deferred until required and make use of whatever information has been set.

    class settings {
    public:
      static void set_path (QSettings::Format format, QSettings::Scope scope, const QString &path)
        {
          s_path = path;
          QSettings::setPath(format, scope, path);
        }
      QSettings *operator-> ()
        {
          return get_settings();
        }
    private:
      QSettings *get_settings ()
        {
          if (!m_settings) {
            if (s_path.isEmpty()) {
              m_settings = std::make_unique<QSettings>();
            } else {
              m_settings = std::make_unique<QSettings>(s_path, QSettings::defaultFormat());
            }
          }
          return m_settings.get();
        }
      static QString             s_path;
      std::unique_ptr<QSettings> m_settings;
    };
    
    QString settings::s_path = "";
    

    When used with the default settings/values...

    settings settings;
    std::cout << "\nsettings file path is [" << settings->fileName() << "]";
    

    The above give something like...

    settings file path is [/home/<user>/.config/Unknown Organization/appname.conf]
    

    Setting the desired path explicitly using...

    settings::set_path(QSettings::defaultFormat(), QSettings::SystemScope, app.applicationDirPath() + "/settings.ini");
    settings settings;
    std::cout << "\nsettings file path is [" << settings->fileName() << "]";
    

    gives...

    settings file path is [/home/<user>/scratch/settings.ini]