# Model

The central component of the TreeView widget. It is the application's dynamic
data structure, independent of the user interface, and it directly manages the
nested data. It follows the Model-Dlegate-View pattern closely. It's abstract,
and it defines the standard interface to be able to interoperate with the
components of the model-view architecture. It is not supposed to be instantiated
directly. Instead, the user should subclass it to create a new model.
 
The item model doesn't return the data itself. Instead, it returns the value
model that can contain any data type and supports callbacks. Thus, the model
client can track the changes in both the item model and any value it holds.

The item model can get both the value model and the nested items from any item.
Therefore, the model is flexible to represent anything from color to complicated
tree-table construction.

## Item

Item is the object that is associated with the data entity of the model.

The item should be created and stored by the model implementation. And can
contain any data in it. Another option would be to use it as a raw pointer to
the data. In any case, it's the choice of the model how to manage this class.

## Hierarchial Model

Usually, the model is a hierarchical system where the item can have any number
of child items. The model can be populated at the moment the user expands the
item to save resources. The following example demonstrates that the model can be
infinitely long.

```execute 200
class Item(ui.AbstractItem):
    """Single item of the model"""

    def __init__(self, text):
        super().__init__()
        self.name_model = ui.SimpleStringModel(text)
        self.children = None

class Model(ui.AbstractItemModel):
    def __init__(self, *args):
        super().__init__()
        self._children = [Item(t) for t in args]

    def get_item_children(self, item):
        """Returns all the children when the widget asks it."""
        if item is not None:
            if not item.children:
                item.children = [Item(f"Child #{i}") for i in range(5)]
            return item.children

        return self._children

    def get_item_value_model_count(self, item):
        """The number of columns"""
        return 1

    def get_item_value_model(self, item, column_id):
        """
        Return value model.
        It's the object that tracks the specific value.
        In our case we use ui.SimpleStringModel.
        """
        return item.name_model

with ui.ScrollingFrame(
    height=200,
    horizontal_scrollbar_policy=ui.ScrollBarPolicy.SCROLLBAR_ALWAYS_OFF,
    vertical_scrollbar_policy=ui.ScrollBarPolicy.SCROLLBAR_ALWAYS_ON,
    style_type_name_override="TreeView",
):
    self._model = Model("Root", "Items")
    ui.TreeView(self._model, root_visible=False, style={"margin": 0.5})
```

## Nested Model

Since the model doesn't keep any data and serves as an API protocol, sometimes
it's very helpful to merge multiple models into one single model. The parent
model should redirect the calls to the children.

In the following example, three different models are merged into one.

```execute 200
class Item(ui.AbstractItem):
    def __init__(self, text, name, d=5):
        super().__init__()
        self.name_model = ui.SimpleStringModel(text)
        self.children = [Item(f"Child {name}{i}", name, d - 1) for i in range(d)]

class Model(ui.AbstractItemModel):
    def __init__(self, name):
        super().__init__()
        self._children = [Item(f"Model {name}", name)]

    def get_item_children(self, item):
        return item.children if item else self._children

    def get_item_value_model_count(self, item):
        return 1

    def get_item_value_model(self, item, column_id):
        return item.name_model

class NestedItem(ui.AbstractItem):
    def __init__(self, source_item, source_model):
        super().__init__()
        self.source = source_item
        self.model = source_model
        self.children = None

class NestedModel(ui.AbstractItemModel):
    def __init__(self):
        super().__init__()
        models = [Model("A"), Model("B"), Model("C")]
        self.children = [
            NestedItem(i, m) for m in models for i in m.get_item_children(None)]

    def get_item_children(self, item):
        if item is None:
            return self.children

        if item.children is None:
            m = item.model
            item.children = [
                NestedItem(i, m) for i in m.get_item_children(item.source)]

        return item.children

    def get_item_value_model_count(self, item):
        return 1

    def get_item_value_model(self, item, column_id):
        return item.model.get_item_value_model(item.source, column_id)

with ui.ScrollingFrame(
    height=200,
    horizontal_scrollbar_policy=ui.ScrollBarPolicy.SCROLLBAR_ALWAYS_OFF,
    vertical_scrollbar_policy=ui.ScrollBarPolicy.SCROLLBAR_ALWAYS_ON,
    style_type_name_override="TreeView",
):
    self._model = NestedModel()
    ui.TreeView(self._model, root_visible=False, style={"margin": 0.5})
```

## Drag and Drop

### Drag

If the model has the item to drag, the method `get_drag_mime_data` should be
reimplemented. This method should return the string with the drag and drop
payload. This data can be dropped to any model or to any `omni.ui` widget that
accepts this data.

```
def get_drag_mime_data(self, item):
```

### Drop

The model supports drag and drop. The drag source can be an item from any model
or any draggable data from omni.ui. When the cursor with draggable data enters
the area of the model's item, the method `drop_accepted` is called where the
model needs to decide if the data is compatible with the model. If the model can
accept the drop, it should return `True`. After that if the user drops the data
to the item, the method `drop` will be called.

```
def drop_accepted(self, target_item, source):
```

```
def drop(self, target_item, source):
```

```execute 200
class Item(ui.AbstractItem):
    """Single item of the model"""

    def __init__(self, text):
        super().__init__()
        self.name_model = ui.SimpleStringModel(text)

    def __repr__(self):
        return self.name_model.as_string

class Model(ui.AbstractItemModel):
    def __init__(self, label, *args):
        super().__init__()
        self._children = [Item(t) for t in args]
        self._label = label

    def get_item_children(self, item):
        if item is not None:
            return []

        return self._children

    def get_item_value_model_count(self, item):
        return 1

    def get_item_value_model(self, item, column_id):
        return item.name_model

    def get_drag_mime_data(self, item):
        """Returns data for be able to drop this item somewhere"""
        return item.name_model.as_string

    def drop_accepted(self, target_item, source):
        """Return true to accept the current drop"""
        # Accept anything
        return True

    def drop(self, target_item, source):
        """Called when the user releases the mouse"""
        # Change text on the label
        self._label.text = f"Dropped {source} to {target_item}"

label = ui.Label("Drop something to the following TreeView")
with ui.ScrollingFrame(
    height=200,
    horizontal_scrollbar_policy=ui.ScrollBarPolicy.SCROLLBAR_ALWAYS_OFF,
    vertical_scrollbar_policy=ui.ScrollBarPolicy.SCROLLBAR_ALWAYS_ON,
    style_type_name_override="TreeView",
):
    self._model = Model(label, *[f"Item #{i}" for i in range(50)])
    ui.TreeView(self._model, root_visible=False, style={"margin": 0.5})
```
