SettingsStorage

class SettingsStorage

The SettingsStorage class manages persistent settings (through QSettings)

The SettingsStorage class proides a unified interface for classes that need to read from or write to persistent storage through QSettings. For read-only access to QSettings, a SettingsStorage object can be created at any point in the code and initialized with the appropriate keys that refer to the group that needs to be read from. All functions that modify values in QSettings are protected. Classes that wish to use SettingsStorage to write to persistent storage need to inherit SettingsStorage and initialize it in their constructors. If any of the set/register functions are called, values will be written to storage during the function call (if the optional write argument is true) or when the object is deleted. To prevent automatic saving, see discardChanges(). This will not affect previously-written values though! Note that if any getters have been registered, the objects they refer to must still exist or the code will crash! A common scenario is to register a getter on an object in the user interface. If the ui pointer is deleted in the derived class’s destructor, then any getter registered on a UI element will crash! Call clearGetters() in the derived class destructor to avoid this.

SettingsStorage does not inherit any other classes, and it is suitable for use in multiple inheritance with QObject-derived classes. However: classes that inherit from SettingsStorage will have their assignment and copy constructors deleted! Do not inherit from SettingsStorage in a class that needs to be passed around by value (such as data storage classes like Experiment). This class is intended for use with objects that only passed by pointer (e.g., HardwareObject, UI classes, etc).

A SettingsStorage object reads and maintains an internal copy of the QSettings keys and values associated with the group/subgroup that it is initialized with. Internally, this is done through the use of three associative containers (key-value containers): one which represents single key-value pairs, another that contains array values as structured by QSettings, and a third that contains getter functions for dynamic values. An array value is a list whose items each contain a map consisting of one or more key-value pairs.

When initializing SettingsStorage, the standard constructor is

SettingsStorage::SettingsStorage(const QStringList keys, Type type)
QSettings::beginGroup will be called for each key in the keys list. If the list is empty, then the group is set to “Blackchirp”. If type is set to SettingsStorage::Hardware and the length of the keys list is 1, then the program assumes the key in the list corresponds to a HardwareObject, and the current subKey will be added to the keys list upon opening QSettings.

To create a read-only SettingsStorage object that reads the global Blackchirp settings:

SettingsStorage s;
If instead you need read-only access to the “AWG.0/virtual” group:
SettingsStorage s({"AWG.0","virtual"});
In general, it is recommended that you use keys that are statically defined in header files for accessing items. When accessing hardware items, use the BC::Key::hwKey() function to construct the key. For read-only access to the settings associated with the current AWG implementation:
SettingsStorage s(BC::Key::hwKey(BC::Key::AWG::key,0),SettingsStorage::Hardware);
The value associated with a key can be obtained with one of the get() functions. If there is an integer associacted with the key myInt, it can be accessed as:
// returns a QVariant containing "myInt", or QVariant() if "myKey" is not found.
QVariant v = get("myInt");

// attempts to convert to an integer using QVariant::value.
// Returns default-constructed value if unsuccessful
int v2 = get<int>("myInt");

// in either case. a default argument can be supplied, which will be
// returned if the key is not found.
QVariant defaultInt = get("myInt",10);
int defaultInt2 = get<int>("myInt",10);

There is also the function getMultiple() that returns a std::map<QString,QVariant> containing all keys that match the indicated values.

Array values can be accessed with the getArray() function, which returns a const reference to the array as a std::vector<SettingsStorage::SettingsMap>. The vector will be empty if the key is not found. Alternatively, a reference to a particular SettingsStorage::SettingsMap within the array can be accesed by index with getArrayMap(). The returned map will be empty if the index is out of bounds or if the key is not found. Finally, getArrayValue() may be used to access one individual element in an array value map by its key, using an interface similar to get().

// Access "arrayKey" map at index 1, return value associated with "mapKey"
// If "arrayKey" is not present, 1 is out of bounds, or "mapKey" is
// not present, v contains defaultValue
QVariant v = getArrayValue("arrayKey",1,"mapKey",defaultValue);

// alternative form using template function
// d will contain 1.5 if lookup fails.
double d = getArrayValue("arrayKey",1,"mapKey",1.5)
The containsValue() and containsArray() functions can be used to check whether a given key exists for a standard value or an array value, respectively.

To obtain write access to persistent storage thogh the SettingsStorage interface, an object must inherit from SettingsStorage: e.g., class MyClass : public QObject, public SettingsStorage, and the constructor must initialise SettingsStorage with an initializer; e.g.,

MyClass::MyClass(QObject *parent) : QObject(parent),
    SettingsStorage({"MyClassKey","MyClassSubkey"})
{
    //other initialization
}
Again, it is preferred to define static keys in the header file of your class instead of manually writing them. When working with a subclass of SettingsStorage, the object has access to the set(), setMultiple(), and setArray() functions. Each of these takes an optional bool argument (default false) that controls whther the new value is immedately written to settings. If false, the value is just stored in memory until a call to save() is made. If the key in a call to one of the set functions does not exist, a new key-value pair is added.

In addition, a subclass may call readAll() at any point to reread all values from settings. However, any keys associated with a getter will not be read! See below for more information about getters. If this behavior is undesired, first unregister any getters before calling readAll(), ensuring that the optional write parameter is set to false.

Subclasses may also use the setDefault() or getOrSetDefault() functions to add a new key to the settings. In either case, if the key already exists, the value remains unmodified. If it does not exist, a new entry in the QSettings file is immediately created with the provided default value. The getOrSetDefault() function will return the value in the settings, while the setDefault() function can be used if the value is not needed immediately. For example:

QVariant out = getOrSetDefault("existingKey",10);
// out contains value of "existingKey", which may not be 10

QVariant out2 = getOrSetDefault("newKey",10);
// out contains 10; "newKey" added to QSettings

setDefault("newKey2",20);
// get<int>("newKey2") returns 20

setDefault("newKey",20);
// get<int>("newKey") returns 10, as this key was already added above.

Finally, subclasses may call registerGetter() to associate a function with a key. Any subsequent references to that key will call the associated function to retrieve the value. A call to save() will retrieve the value from the stored function to save. In this way, the subclass does not have to call set() every time a value changes. This mechanism only works for single key-value settings, not for array values. Getters may be cleared individually with unRegisterGetter() or all at once with clearGetters(). When unregistered or cleared, the getter function is called and the value stored in memory for later use/saving. Optionally, the value can be immediately written to QSettings.

A getter function must be a const member function that takes no arguments and returns a type known to QVariant. New types can be made known to QVariant using the Q_DECLARE_METATYPE macro; see the QVariant documentation for details. An example:

class MyClass : public SettingsStorage()
{
public:
    MyClass();

    int getInt() const { return d_int; }

private:
    int d_int = 1;
};

MyClass::MyClass() : SettingsStorage({},false)
{
    registerGetter("myInt",this,&MyClass::getInt);
    int i = get<int>("myInt");
    // i == 1

    d_int = 10;
    int j = get<int>("myInt");
    // j == 10

    QVariant k = unRegisterGetter("myInt",false);
    //k == 10; do not write 10 to QSettings

    d_int = 20;
    int l = get<int>("myInt");
    //l == 10
}

Subclassed by AuxDataViewWidget, BCSavePathDialog, BatchSequenceDialog, CatalogOverlayWidget, ChirpConfigWidget, ChirpTableModel, ClockManager, ClockTableModel, CurveAppearancePresetManager, DigitizerConfigWidget, ExperimentConfigPage, FtmwProcessingToolBar, FtmwViewWidget, GasControlWidget, GenericXYOverlayWidget, HWSettingsModel, HardwareManager, HardwareObject, LifControlWidget, LifDisplayWidget, LifProcessingWidget, OverlayBaseOptionsWidget, OverlayManagerWidget, PeakFindWidget, PeakListExportDialog, PulseConfigWidget, RfConfigWidget, SettingsStorageWrapper, TemperatureControlWidget, UnifiedOverlayWidget, ValidationModel, ZoomPanPlot

Public Types

enum Type

Used in constructor to indicate whether a hardware subkey is read from settings.

Values:

enumerator General

Use keys list explicitly

enumerator Hardware

If keys list has 1 entry, look up subKey for this hardware.

using SettingsGetter = std::function<QVariant()>

Alias for a getter function

using SettingsMap = std::map<QString, QVariant>

Alias for a map of strings and variants

Public Functions

SettingsStorage(const QStringList keys = QStringList(), Type type = General)

Constructor.

Create a QSettings object initialized to the group (and any subgroups) in the keys list with the indicated scope, and reads all values and arrays associated with that (sub)group. If keys is empty, the group is set to “Blackchirp”.

Parameters:
  • keys – The list of group/subgroup keys, passed to QSettings::beginGroup in order

  • type – If set to SettingsStorage::Hardware and the length of keys is 1, the subKey for the current hardware will be read from settings and selected.

SettingsStorage(const QString orgName, const QString appName, const QStringList keys = QStringList(), Type type = General)

Constructor that explicitly sets organization name and application name (used for unit tests and reading settings from other applications).

Parameters:
  • orgName – Organization name passed to QSettings constructor

  • appName – Application name passed to QSettings construstor

  • keys – The list of group/subgroup keys, passed to QSettings::beginGroup in order

  • type – If set to SettingsStorage::Hardware and the length of keys is 1, the subKey for the current hardware will be read from settings and selected.

SettingsStorage(const QString key, Type type = General)

Convenience constructor for a single key.

Parameters:
  • key – The key for the group in QSettings. If empty, will be set to “Blackchirp”

  • type – If set to Hardware, a subKey will be added (default: “virtual”)

virtual ~SettingsStorage()

Destructor. Saves all values to settings.

SettingsStorage(const SettingsStorage&) = delete
SettingsStorage &operator=(const SettingsStorage&) = delete
bool containsValue(const QString key) const

Returns whether key is in the storage (either as a value or getter)

Parameters:

key – The key to search for

Returns:

If key is found

bool containsArray(const QString key) const

Returns whether key is in the storage as an array.

Parameters:

key – The key to search for

Returns:

If key is found

QVariant get(const QString key, const QVariant &defaultValue = QVariant()) const

Gets the value of a setting.

If a getter function has been registered (see registerGetter()), then that getter function will be called. The optional defaultValue argument is returned if the key is not found.

Parameters:
  • key – The key associated with the value

  • defaultValue – The value returned if key is not present (default: QVariant())

Returns:

The value

template<typename T>
inline T get(const QString key, const T &defaultValue = QVariant().value<T>()) const

Gets the value of a settting. Overloaded function.

Attempts to convert the value to type T using QVariant::value. The optional defaultValue argument is returned if the key is not found. If left blank, a default-constructed value is returned for any missing keys.

Parameters:
  • key – The key associated with the value

  • defaultValue – The value returned if key is not present

Returns:

The value, or a default constructed value if the key is not present

SettingsMap getMultiple(const std::vector<QString> keys) const

Gets values associated with a list of keys. Overloaded function.

If a key in the list is not found, then it is skipped. The returned map may be empty. Recommended usage:

SettingsStorage s(keys);
auto x = getMultiple( {"key1","key2","key3"} );
auto key1Val = x.at("key1");

Parameters:

keys – The list of keys to search for

Returns:

Map containing the keys found in the values or getter maps

std::vector<SettingsMap> getArray(const QString key) const

Gets an array assocated with a key.

An array is a list of maps, each of which has its own keys and values

Parameters:

key – The key associated with the array

Returns:

The array. An empty vector is returned if the key is not found

std::size_t getArraySize(const QString key) const

Returns the size of the array value associated with key.

Parameters:

key – The key of the array value

Returns:

The number of maps in the array (0 if key does not exist);

SettingsMap getArrayMap(const QString key, std::size_t i) const

Returns a single map from an array associated with a key.

Parameters:
  • key – The key of the array

  • i – Index of the desired map

Returns:

The selected map, which will be empty if key does not exist or if i is out of bounds for the array

QVariant getArrayValue(const QString arrayKey, std::size_t i, const QString mapKey, const QVariant defaultValue = QVariant()) const

Gets a single value from a map that is part of an array value.

Calls getArrayMap(), then searches for mapKey within the returned map (which may be empty). Returns the stored QVariant or the defaultValue argument.

Parameters:
  • arrayKey – The key of the array

  • i – Index of the desired map

  • mapKey – Key of the desired value within the map

  • defaultValue – Value returned if arrayKey does not exist or i is out of bounds or mapKey does not exist

Returns:

Stored value or defaultValue

template<typename T>
inline T getArrayValue(const QString arrayKey, std::size_t i, const QString mapKey, T defaultValue = QVariant().value<T>()) const

Overloaded function. See getArrayValue()

Parameters:
  • arrayKey – The key of the array

  • i – Index of the desired map

  • mapKey – Key of the desired value within the map

  • defaultValue – Value returned if arrayKey does not exist or i is out of bounds or mapKey does not exist (dedaults to a default-constructed value)

Returns:

The value or the defaultValue, as appropriate

inline void discardChanges(bool discard = true)

Controls whether changes are wrtten to QSettings

Parameters:

discard – If true, settings are not saved.

Protected Functions

template<class T, typename Out>
inline bool registerGetter(const QString key, T *obj, Out (T::* getter)() const)

Registers a getter function for a given setting.

When a getter is associated with a key, any corresponding key is removed from the values dictionary, and instead the getter function will be called to access the value. The getter function will be called when saving the settings to disk or when calling the get function.

This function only works for single values, not arrays.

This form is intended for use with member function pointers.

Parameters:
  • key – The key for the value to be stored

  • obj – A pointer to the object containing the getter function

  • getter – A member function pointer that returns a type that can be implicitly converted to QVariant

Returns:

Whether the getter was registered

template<typename T>
inline bool registerGetter(const QString key, std::function<T()> f)

Registers a getter function for a given setting (overloaded function)

When a getter is associated with a key, any corresponding key is removed from the values dictionary, and instead the getter function will be called to access the value. The getter function will be called when saving the settings to disk or when calling the get function.

This function only works for single values, not arrays.

This form can be used with lambda functions. For example:

int x = 3;
auto f = std::function<int ()> { [x](){ return x + 1; }
registerGetter("myInt",f);
auto y = get<int>("myInt")
//y == 4

QVariant unRegisterGetter(const QString key, bool write = false)

Removes a getter function.

If key matches a previously registered getter, then the getter function is removed from the storage object. The getter itself is called, and the value is stored in the values map with the same key amd returned.

An empty QVariant is returned if no getter was found.

If the write parameter is true, the current value is written to persistent storage.

Parameters:
  • key – The key associated with the getter

  • write – If true, value is written to persistent storage immediately

Returns:

Return value of getter call, or empty QVariant if no getter was found.

void clearGetters(bool write = false)

Removes all getter functions.

All getters are removed and their values are transferred into the values map.

Parameters:

write – If true, values are written to persistent storage

QVariant getOrSetDefault(const QString key, const QVariant defaultValue)

Reads a setting, and sets a default value if it does not exist.

Searches for and returns the value associated with the indicated key. If the key does not exist in the settings file, then an entry is created and the default value written.

The intention of this function is to allow a developer to expose settings that a user may want to edit in the settings editor. For example, a Clock object has “minFreqMHz” and “maxFreqMHz” settings that correspond to the actual hardware limits. Those settings are read by the user interface to set limits on input widgets that control the desired frequency setting. The user can change these values to (presumably) narrow the range of allowed values. By calling this function for each setting that should be exposed, an entry will be guaranteed to be created in the settings file.

Parameters:
  • key – The key for the value to be stored

  • defaultValue – The desired default value written to settings if the key does not exist

Returns:

Value associated with the key. If the key did not previously exist, this will equal defaltValue

template<typename T>
inline T getOrSetDefault(const QString key, const T &defaultValue)

Reads a settings, and sets a default value if it does not exist.

Templated version of getOrSetDefault

Parameters:
  • key – The key for the value to be stored

  • defaultValue – The desired default value written to settings if the key does not exist

Returns:

Value associated with the key. If the key did not previously exist, this will equal defaltValue

void setDefault(const QString key, const QVariant defaultValue)

Sets a default value if none exists.

If a value already exists corresponding to a key, no action is taken.

Parameters:
  • key – The key for the value

  • defaultValue – Value to set if key is not found.

template<typename T>
inline void setDefault(const QString key, const T &defaultValue)

Sets a default value if none exists. Overloaded function.

If a value already exists corresponding to a key, no action is taken.

Parameters:
  • key – The key for the value

  • defaultValue – Value to set if key is not found.

bool set(const QString key, const QVariant &value, bool write = false)

Stores a key-value setting.

The value is placed into the values map and associated with the given key. If key already exists, its value is overwritten; otherwise a new key is created. The operation will not be completed if the key is associated with a getter function or with an array value.

The write argument controls whether the new setting is immediately written to persistent storage. If write is false, then the setting will not be stored until a call to save() is made.

Parameters:
  • key – The key associated with the value

  • value – The value to be stored

  • write – If true, write to persistent storage immediately

Returns:

Whether or not the setting was made. If false, the key is already associated with a getter or array value

template<typename T>
inline bool set(const QString key, const T &value, bool write = false)

Stores a key-value setting. Overloaded function.

See set(const QString key, const QVariant &value, bool write)

Parameters:
  • key – The key associated with the value

  • value – The value to be stored

  • write – If true, write to persistent storage immediately

Returns:

Whether or not the setting was made. If false, the key is already associated with a getter or array value

std::map<QString, bool> setMultiple(const SettingsMap m, bool write = false)

Sets multiple key-value settings.

Calls set() for each key-value pair in the input map. If the setting was successful and write is true, the new value is stored in QSettings immediately. The success of each setting is returned in a map.

Parameters:
  • m – Map of key-value pairs to add

  • write – If true, write to QSettings immediately

Returns:

Return value of set() for each key

void setArray(const QString key, const std::vector<SettingsMap> &array, bool write = false)

Sets (or unsets) an array value.

Stores a vector of maps that will be written using QSettings::beginWriteArray. Passing an empty array argument will remove the value from QSettings. Changes to QSettings are made immediately if write is true, and upon the next call to save() otherwise.

Parameters:
  • key – The key of the array value

  • array – The new array value (may be empty)

  • write – If true, QSettings is updated immediately

bool setArrayValue(const QString arrayKey, std::size_t i, const QString key, const QVariant &value, bool write = false)

Sets a single value within a map assocuated with an array value.

Attempts to set one key-value pair for the array value specified by arrayKey at position i. The write will fail if the array does not exist or if i is out of bounds. If the optional write parameter is true, then the updated array will be written to QSettings.

Parameters:
  • arrayKey – Key of the array value

  • i – Index of the map within the array

  • key – Key for the map

  • value – Value to be stored

  • write – If true, write updated array to QSettings

Returns:

Whether setting was successfully made

template<typename T>
inline bool setArrayValue(const QString arrayKey, std::size_t i, const QString key, const T &value, bool write = false)

Sets a single value within a map assocuated with an array value. Overloaded function.

See setArrayValue(const QString arrayKey, std::size_t i, const QString key, const QVariant &value, bool write)

Parameters:
  • arrayKey – Key of the array value

  • i – Index of the map within the array

  • key – Key for the map

  • value – Value to be stored

  • write – If true, write updated array to QSettings

Returns:

Whether setting was successfully made

void appendArrayMap(const QString key, const SettingsMap &map, bool write = false)

Appends a new map onto an array value.

If the key does not match an existing array variable, it is added. By default, the new array is not written to settings immediately. This is because QSettings essentially requires rewriting the entire array every time, and this function is intended to be called as part of a loop.

Parameters:
  • key – The key of the array value

  • map – The new map to append

  • Whether – to update QSettings immediately

void clearValue(const QString key)

Clears all data associated with a key and removes it from QSettings.

This clears all forms of data associated with the given key: regular values, getter functions, and array values. The key is immediately removed from QSettings. If the key is not found in any form, no action is taken.

Parameters:

key – The key to clear from all storage forms

void save()

Write all values to QSettings.

void readAll()

Reads all values from settings file (see note about getters)

This function reads all values from the QSettings storage. It does so by clearing out the values and arrayValues maps, and reading in all keys and array groups found in the settings file.

If a getter has been registered, that key will be skipped. First call unRegisterGetter() or clearGetters() if you wish to these keys to be re-read.

QStringList keys() const

List of all keys for normal (non-array) values.

Returns:

List of keys

QStringList arrayKeys() const

List of keys for array values.

Returns:

List of Keys

Private Functions

explicit SettingsStorage(const QStringList keys, Type type, QSettings::Scope scope)
explicit SettingsStorage(const QString orgName, const QString appName, const QStringList keys, Type type, QSettings::Scope scope)
void writeArray(const QString key)

Writes a single array to QSettings.

Parameters:

key – Key of the array to write

Private Members

SettingsMap d_values

Map of key-value pairs

bool d_discard = {false}
bool d_edited = {false}

If set to true, changes will not be stored to QSettings

std::map<QString, SettingsGetter> d_getters

Set to true when a value is changed. Map containing all registered getters

std::map<QString, std::vector<SettingsMap>> d_arrayValues

Map containing all array values

QSettings d_settings

Handle to QSettings storage object

Friends

friend class SettingsStorageTest
namespace BC

Blackchirp global namespace

namespace Key

Global SettingsStorage keys

Variables

static const QString BC = {"Blackchirp"}
static const QString exptNum = {"exptNum"}
static const QString savePath = {"savePath"}
static const QString appFont = {"appFont"}
static const QString exptDir = {"experiments"}
static const QString logDir = {"log"}
static const QString exportDir = {"textexports"}
static const QString trackingDir = {"rollingdata"}