SessionItem

Introduction

SessionItem class is a base element to build a hierarchical structure representing all the data of the GUI application. SessionItem can contain an arbitrary amount of basic data types, and can be a parent for other SessionItems.

The tree of SessionItem objects can be built programmatically via SessionItem API, or be reconstructed from persistent XML content.

While being an end leaf in some ramified hierarchy the SessionItem often plays a role of a single editable/displayable entity. For example, a SessionItem can be seen as an aggregate of information necessary to display/edit a single integer number 42 in the context of some view. Then, it will carry:

  • A display name of the value, e.g. width.

  • A value, i.e integer number with the value set to 42.

  • A collection of appearance flags, stating if the value is visible, is read-only, should be shown as disabled (grayed out), and so on.

  • Other auxiliary information (tooltips to be shown, allowed limits to change, and similar).

The data of SessionItem

The data carried by SessionItem is always associated with the role - a unique integer number defining the context in which the data has to be used. They both came in pairs, and the item can have multiple data/roles defined:

// currently supported elementary data types
using variant_t = std::variant<std::monostate, boolean, char8, int8, uint8, int16, uint16, int32, uint32, int64, uint64, float32, float64, std::string, std::vector<float64>, ComboProperty, ExternalProperty>;

// convenience type
using datarole_t = std::pair<variant_t, int>;

// collection of predefined roles
namespace DataRole
{
  const int kIdentifier = 0;  //!< item unique identifier
  const int kData = 1;        //!< main data role
  const int kDisplay = 2;     //!< display name
  const int kAppearance = 3;  //!< appearance flag
  const int kTooltip = 4;     //!< tooltip for item's data
  const int kEditor = 5;      //!< type of custom editor for the data role
  const int kLowerLimit = 6;  //!< lower limit on main data role
  const int kUpperLimit = 7;  //!< upper limit on main data role
}

In the snipped below, the data is set and then accessed for two roles, the display role holding a label and the data role, holding the value.:

SessionItem item;
item.SetData(42, kData);
item.SetData("Width [nm]", kDisplay)

auto number = item.Data<int>(kData);
auto label = item.Data<std::string>(kDisplay);

Inheriting from SessionItem

The SessionItem class type name is stored in a string variable and can be accessed via the GetType() method:

SessionItem item;
std::cout << item.GetType() << std::endl;
>>> "SessionItem"

This name is used during item serialization/deserialization and during undo/redo operations to create objects of the correct type in item factories.

To inherit from SessionItem the new unique name has to be provided in the constructor of the derived class. It is convenient to make this name identical to the class name itself:

class SegmentItem : public SessionItem
{
public:
  const static std::string Type = "SegmentItem";
  SegmentItem() : SessionItem(Type) {}
}

Children of SessionItem

SessionItem can have an arbitrary amount of children stored in named containers. In pseudo code, it can be expressed:

class SessionItem
{
  using named_container_t = std::pair<std::string, std::vector<SessionItem*>>;
  std::vector<named_container_t> m_tagged_items;
}

Named containers are a convenient way to have items tied to a certain context. The name of the container, tag, and the position in it, index, can be used to access and manipulate items through their parent SessionItem. Before adding any child to SessionItem, the container has to be created and its properties defined.

The TagInfo class

The TagInfo specifies information about children that can be added to a SessionItem. A TagInfo has a name, min, max allowed number of children, and vector of all types that children can have.

In the snippet below we register a tag with the name ITEMS intended for storing unlimited amount of other SessionItems:

SessionItem item;
item.RegisterTag(TagInfo("ITEMS", 0, -1));

An equivalent way of doing the same is to use convenience factory methods of the TagInfo class:

SessionItem item;
item.RegisterTag(TagInfo::CreateUniversalTag("ITEMS"));

Internally, it leads to the creation of a corresponding named container ready for items to be inserted. In another example, we define a tag with the name Position intended for storing the only item of type VectorItem:

item.RegisterTag(TagInfo("Position", 1, 1, {VectorItem::Type});

// or
// item.RegisterTag(TagInfo::CreatePropertyTag("Position", VectorItem::Type));

The TagIndex class

The TagIndex class is a simple aggregate carrying a string with container name, and an index indicating the position in the container:

struct TagIndex
{
  std::string tag = {};
  int index = -1;
}

The TagIndex class uniquely defines the position of a child and it is used in the SessionItem interface to access and manipulate items in containers.

Adding children

There are multiple ways to add children to a parent. In snipped below we register a tag with the name “ITEMS” intended for storing an unlimited amount of items of any type. In the next step, we insert a child into the corresponding container and modify its display name. Later, we access the child using the known TagIndex to print the child’s display name:

const std::string tag("ITEMS");
SessionItem item;
item.RegisterTag(TagInfo::CreateUniversalTag(tag));

auto child0 = item.InsertItem({tag, 0});
child0->SetDisplayName("Child");

std::cout << item.GetItem(tag)->GetDisplayName() << "\n";
>>> "Child"

There are other alternative ways to add children:

// appends new SessionItem
auto child0 = item.InsertItem({tag, -1});

//! appends new PropertyItem
auto child1 = item.InsertItem<PropertyItem>({tag, -1});

// inserts child between child0 and child1 using move semantic
auto another = std::make_unique<VectorItem>
auto child2 = item.InsertItem(std::move(another), {tag, 1});