diff --git a/Documentation/development/settings.md b/Documentation/development/settings.md new file mode 100644 index 0000000000..7a8690dd5b --- /dev/null +++ b/Documentation/development/settings.md @@ -0,0 +1,134 @@ +# Settings Framework + +The settings framework manages application settings, as well as projects. This document explains +how to make use of the framework as well as some of its inner workings. + +[TOC] + +## Source Code Guide + +Most of the relevant code is in `common/settings` and `common/project`. + +C++ Class | Description +:------------------------|:------------- +`SETTINGS_MANAGER` | Loads and unloads settings files and projects +`JSON_SETTINGS` | The base class for all settings objects. Represents a single JSON file. +`NESTED_SETTINGS` | A `JSON_SETTINGS` object stored within another (i.e. without its own file) +`PARAM` | A parameter: helper class for storing data inside a `JSON_SETTINGS` +`APP_SETTINGS_BASE` | Base class for application (frame) settings +`COLOR_SETTINGS` | A subclass of `JSON_SETTINGS` designed for storing color themes +`COMMON_SETTINGS` | The settings available to every part of KiCad +`PROJECT_FILE` | A `JSON_SETTINGS` representing a project (`.kicad_pro`) file +`PROJECT_LOCAL_SETTINGS` | A `JSON_SETTINGS` representing a project local state (`.kicad_prl`) file + +## Where Settings are Stored + +There are four main places a setting might be stored: + +1) In `COMMON_SETTINGS`: this is a setting that is shared between all parts of KiCad. +2) In an application settings object (subclass of `APP_SETTINGS_BASE`). These objects, such as + `EESCHEMA_SETTINGS` and `PCBNEW_SETTINGS`, store settings that are specific to a portion of + KiCad. In particular, these objects are compiled inside the context of their respective + application, so they have access to data types that may not be part of `common`. +3) In the `PROJECT_FILE`, where they will be specific to a loaded project. This is true of most of + the settings found in the Board / Schematic Setup dialogs. Currently, KiCad only supports having + one project loaded at a time, and a number of places in the code expect that a `PROJECT` object + will always be available. Because of this, the `SETTINGS_MANAGER` will always ensure that a + "dummy" `PROJECT_FILE` is available even when no project has been loaded by the user. This dummy + project can be modified in memory but not saved to disk. +4) In the `PROJECT_LOCAL_SETTINGS` object, where they will be specific to a loaded project. This + file is for settings that are "local state", such as which board layers are visible, that should + (for many users, at least) not be checked in to source control. Any setting here should be + transient, meaning there will be no ill effect if the entire file is deleted. + +## JSON_SETTINGS + +The `JSON_SETTINGS` class is the backbone of the settings infrastructure. It is a subclass of the +`nlohmann::json::basic_json` class provided by `thirdparty/nlohmann_json/nlohmann/json.hpp`. As +such, anytime raw manipulation of the underlying JSON data is needed, you can use the [standard +`nlohmann::json` API](https://nlohmann.github.io/json/api/basic_json/). The JSON contents represent +the **state of the file on disk**, not the state of the data exposed to C++. Synchronization +between the two is done via parameters (see below) and takes place right after loading from disk and +right before saving to disk. + +## Parameters + +Parameters establish the link between C++ data and content in the JSON file. In general, parameters +consist of a **path**, **pointer**, and **default value**. The path is a string of the form +`"x.y.z"`, where each component represents a nested JSON dictionary key. The pointer is a pointer +to the C++ member variable that holds the data accessible to consumers of the `JSON_SETTINGS`. The +default value is used to update the pointer when the data is missing from the JSON file. + +Parameters are subclasses of `PARAM_BASE` in `include/settings/parameters.h`. There are a number of +helpful subclasses created to make it easier to store complex data in JSON files. + +The basic `PARAM` class is templated and is useful for storing any data type can be serialized to +JSON automatically. A basic instantiation of a `PARAM` might look like: + + m_params.emplace_back( new PARAM( "appearance.icon_scale", + &m_Appearance.icon_scale, 0 ) ); + +Here, `m_Appearance.icon_scale` is a public member of the settings object (an `int` inside a +`struct`). `"appearance.icon_scale"` is the **path** to store the value in the JSON file, and `0` +is the default value. This would result in JSON looking like this: + + { + "appearance": { + "icon_scale": 0 + } + } + +Note that it is possible to use custom types with `PARAM<>` as long as they have a `to_json` and +`from_json` defined. See `COLOR4D` for an example of this. + +For storing complex data types, it is sometimes easiest to use `PARAM_LAMBDA<>`, which allows you +to define a "getter" and "setter" as part of the parameter definition. You can use this to build +a `nlohmann::json` object and store it as the "value" of your parameter. For examples of how this +is done, see `NET_SETTINGS`. + +## NESTED_SETTINGS + +The `NESTED_SETTINGS` class is like a `JSON_SETTINGS` but instead of a file for a backing store, it +uses another `JSON_SETTINGS` object. The entire contents of a `NESTED_SETTINGS` are stored as the +value of a particular key in the parent file. This has two key benefits: + +1) You can split up large sets of settings in to more manageable pieces +2) You can hide knowledge about the nested settings from the parent settings object + +For example, many portions of the project file are stored as `NESTED_SETTINGS` objects inside the +`PROJECT_FILE`. These objects, including `SCHEMATIC_SETTINGS`, `NET_SETTINGS`, and +`BOARD_DESIGN_SETTINGS`, are compiled as part of eeschema or pcbnew, so they have access to data +types not available in common (where `PROJECT_FILE` is compiled). + +When the outer file is loaded, all of the data for the nested settings is there in the underlying +`nlohmann::json` data store -- it's just not used until the appropriate `NESTED_SETTINGS` is loaded. + +`NESTED_SETTINGS` objects can have shorter lifecycles than the parent. This is required because in +some cases (such as with the project file), the parent can stay resident in one frame (the KiCad +manager, for example) while a frame that uses a nested settings inside it can be created and +destroyed. When the nested settings is destroyed, it ensures that its data is stored to the JSON +data of the parent. The parent `JSON_SETTINGS` can then be saved to disk, if desired. + +## Schema Version and Migrations + +Settings objects have a **schema version**, which is a const integer that can be incremented when a +migration is needed. The schema version in the code is compared to that in the loaded file, and if +the file version is lower (older), **migrations** are run to bring the data in the file up to date. + +**Migrations** are functions that are responsible for making the necessary changes from one schema +version to another. They act on the **underlying JSON data**, before parameter loading has taken +place. + +Migrations are not always needed when changing a settings file. You are free to add or remove +parameters without changing the schema version or writing migrations. If you add parameters, they +will be added to the JSON file and initialized to their default value. If you remove parameters, +they will be silently dropped from the JSON file the next time the settings are saved. Migration is +only needed when you need to make changes to the JSON file that depend on the current state. For +example, if you decide to rename a settings key, but want to preserve the user's present setting. + +If you need to make a "breaking change" to a settings file: + +1) Increment the schema version +2) Write a migration that makes the necessary changes to the underlying `nlohmann::json` object +3) Call `JSON_SETTINGS::registerMigration` in the constructor for the object +