terminal/src/cascadia/TerminalSettingsModel/IInheritable.h
Carlos Zamora b603929214
Make Global and Profile settings inheritable (#7923)
## Summary of the Pull Request
Introduces `IInheritable` as an interface that helps move cascading settings into the Terminal Settings Model. `GlobalAppSettings` and `Profile` both are now `IInheritable`. `CascadiaSettings` was updated to `CreateChild()` for globals and each profile when we are loading the JSON data.

IInheritable does most of the heavy lifting. It introduces a two new macros and the interface. The macros help implement the fallback functionality for nullable and non-nullable settings.

## References
#7876 - Spec Addendum
#6904 - TSM Spec
#1564 - Settings UI

#7876 - `Copy()` needs to be updated to include _parent
2020-10-27 17:35:09 +00:00

213 lines
12 KiB
C++

/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- IInheritable.h
Abstract:
- An interface allowing settings objects to inherit settings from a parent
Author(s):
- Carlos Zamora - October 2020
--*/
#pragma once
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{
template<typename T>
struct IInheritable
{
public:
// Method Description:
// - Create a new instance of T, but set its parent to this instance
// Arguments:
// - <none>
// Return Value:
// - a new instance of T with this instance set as its parent
com_ptr<T> CreateChild() const
{
auto child{ winrt::make_self<T>() };
// set "this" as the parent.
// However, "this" is an IInheritable, so we need to cast it as T (the impl winrt type)
// to pass ownership over to the com_ptr.
com_ptr<T> parent;
winrt::copy_from_abi(parent, const_cast<T*>(static_cast<const T*>(this)));
child->InsertParent(parent);
child->_FinalizeInheritance();
return child;
}
void InsertParent(com_ptr<T> parent)
{
_parents.push_back(parent);
}
void InsertParent(size_t index, com_ptr<T> parent)
{
auto pos{ _parents.begin() + index };
_parents.insert(pos, parent);
}
const std::vector<com_ptr<T>>& Parents()
{
return _parents;
}
protected:
std::vector<com_ptr<T>> _parents{};
// Method Description:
// - Actions to be performed after a child was created. Generally used to set
// any extraneous data from the parent into the child.
// Arguments:
// - <none>
// Return Value:
// - <none>
virtual void _FinalizeInheritance() {}
};
// This is like std::optional, but we can use it in inheritance to determine whether the user explicitly cleared it
template<typename T>
struct NullableSetting
{
std::optional<T> setting{ std::nullopt };
bool set{ false };
};
}
// Use this macro to quickly implement both getters and the setter for an
// inheritable setting property. This is similar to the GETSET_PROPERTY macro, except...
// - Has(): checks if the user explicitly set a value for this setting
// - Getter(): return the resolved value
// - Setter(): set the value directly
// - Clear(): clear the user set value
// - the setting is saved as an optional, where nullopt means
// that we must inherit the value from our parent
#define GETSET_SETTING(type, name, ...) \
public: \
/* Returns true if the user explicitly set the value, false otherwise*/ \
bool Has##name() const \
{ \
return _##name.has_value(); \
}; \
\
/* Returns the resolved value for this setting */ \
/* fallback: user set value --> inherited value --> system set value */ \
type name() const \
{ \
const auto val{ _get##name##Impl() }; \
return val ? *val : type{ __VA_ARGS__ }; \
}; \
\
/* Overwrite the user set value */ \
void name(const type& value) \
{ \
_##name = value; \
}; \
\
/* Clear the user set value */ \
void Clear##name() \
{ \
_##name = std::nullopt; \
}; \
\
private: \
std::optional<type> _##name{ std::nullopt }; \
std::optional<type> _get##name##Impl() const \
{ \
/*return user set value*/ \
if (_##name) \
{ \
return _##name; \
} \
\
/*user set value was not set*/ \
/*iterate through parents to find a value*/ \
for (auto parent : _parents) \
{ \
if (auto val{ parent->_get##name##Impl() }) \
{ \
return val; \
} \
} \
\
/*no value was found*/ \
return std::nullopt; \
};
// This macro is similar to the one above, but is reserved for optional settings
// like Profile.Foreground (where null is interpreted
// as an acceptable value, rather than "inherit")
// "type" is exposed as an IReference
#define GETSET_NULLABLE_SETTING(type, name, ...) \
public: \
/* Returns true if the user explicitly set the value, false otherwise*/ \
bool Has##name() const \
{ \
return _##name.set; \
}; \
\
/* Returns the resolved value for this setting */ \
/* fallback: user set value --> inherited value --> system set value */ \
winrt::Windows::Foundation::IReference<type> name() const \
{ \
const auto val{ _get##name##Impl() }; \
if (val.set) \
{ \
if (val.setting) \
{ \
return *val.setting; \
} \
return nullptr; \
} \
return winrt::Windows::Foundation::IReference<type>{ __VA_ARGS__ }; \
}; \
\
/* Overwrite the user set value */ \
void name(const winrt::Windows::Foundation::IReference<type>& value) \
{ \
if (value) /*set value is different*/ \
{ \
_##name.setting = value.Value(); \
} \
else \
{ \
_##name.setting = std::nullopt; \
} \
_##name.set = true; \
}; \
\
/* Clear the user set value */ \
void Clear##name() \
{ \
_##name.set = false; \
}; \
\
private: \
NullableSetting<type> _##name{}; \
NullableSetting<type> _get##name##Impl() const \
{ \
/*return user set value*/ \
if (Has##name()) \
{ \
return _##name; \
} \
\
/*user set value was not set*/ \
/*iterate through parents to find a value*/ \
for (auto parent : _parents) \
{ \
auto val{ parent->_get##name##Impl() }; \
if (val.set) \
{ \
return val; \
} \
} \
/*no value was found*/ \
return { std::nullopt, false }; \
};