Skip to content

OptionContainer

Usage

The content of an OptionContainer is a list of widgets that will form discrete tabs in the display. Each tab can be identified by a label, and, optionally, an icon. This list of content can be modified after initial construction:

import toga

pizza = toga.Box()
pasta = toga.Box()

# Create 2 initial tabs; one with an icon, and one without.
container = toga.OptionContainer(
    content=[("Pizza", pizza), ("Pasta", pasta, toga.Icon("pasta"))]
)

# Add another tab of content, without an icon.
salad = toga.Box()
container.content.append("Salad", salad)

# Add another tab of content, with an icon
icecream = toga.Box()
container.content.append("Ice Cream", icecream, toga.Icon("icecream"))

OptionContainer content can also be specified by using toga.OptionItem instances instead of tuples. This enables you to be explicit when setting an icon or enabled status; it also allows you to set the initial enabled status without setting an icon:

import toga

pizza = toga.Box()
pasta = toga.Box()

# Create 2 initial tabs; one with an icon, and one without.
container = toga.OptionContainer(
    content=[`
      toga.OptionItem("Pizza", pizza),
      toga.OptionItem("Pasta", pasta, icon=toga.Icon("pasta"))
    ]
)

# Add another tab of content, initially disabled, without an icon.
salad = toga.Box()
container.content.append(toga.OptionItem("Salad", salad, enabled=False))

When retrieving or deleting items, or when specifying the currently selected item, you can specify an item using:

  • The index of the item in the list of content:

    # Insert a new second tab
    container.content.insert(1, "Soup", toga.Box())
    # Make the third tab the currently active tab
    container.current_tab = 2
    # Delete the second tab
    del container.content[1]
    
  • The string label of the tab:

    # Insert a tab at the index currently occupied by a tab labeled "Pasta"
    container.content.insert("Pasta", "Soup", toga.Box())
    # Make the tab labeled "Pasta" the currently active tab
    container.current_tab = "Pasta"
    # Delete tab labeled "Pasta"
    del container.content["Pasta"]
    
  • A reference to an toga.OptionItem:

    # Get a reference to the "Pasta" tab
    pasta_tab = container.content["Pasta"]
    # Insert content at the index currently occupied by the pasta tab
    container.content.insert(pasta_tab, "Soup", toga.Box())
    # Make the pasta tab the currently active tab
    container.current_tab = pasta_tab
    # Delete the pasta tab
    del container.content[pasta_tab]
    

System requirements

  • Using OptionContainer on Android requires the Material package in your project's Gradle dependencies. Ensure your app declares a dependency on com.google.android.material:material:1.12.0 or later.

Notes

  • The use of icons on tabs varies between platforms. If the platform requires icons, and no icon is provided, a default icon will be used. If the platform does not support icons, any icon provided will be ignored, and requests to retrieve the icon will return None.
  • The behavior of disabled tabs varies between platforms. Some platforms will display the tab, but put it in an unselectable state; some will hide the tab. A hidden tab can still be referenced by index - the tab index refers to the logical order, not the visible order.
  • On iOS and Android, OptionContainer should only be used as the root content of a Window. Toga's API will allow the creation of an OptionContainer anywhere in a layout; but the behavior of the OptionContainer in those layouts is undefined. This includes adding an OptionContainer as a child of a Box, and nesting an OptionContainer inside another OptionContainer.
  • On iOS:
    • OptionContainer can only display 5 tabs. If there are more than 5 visible tabs in an OptionContainer, the last item will be converted into a "More" option that will allow the user to select the additional items. While the "More" menu is displayed, the current tab will return as None.
    • The user can rearrange icons on an OptionContainer. When referring to tabs by index, user re-ordering is ignored; the logical order as configured in Toga itself is used to identify tabs.
    • Icons for OptionContainer tabs should be 25x25px alpha masks.
  • On Android:
    • OptionContainer can only display 5 tabs. The API will allow you to add more than 5 tabs, and will allow you to programmatically control tabs past the 5-item limit, but any tabs past the limit will not be displayed or be selectable by user interaction. If the OptionContainer has more than 5 tabs, and one of the visible tabs is removed, one of the previously unselectable tabs will become visible and selectable.
    • Icons for Android OptionContainer tabs should be 24x24px alpha masks.

Reference

Bases: Widget

Source code in core/src/toga/widgets/optioncontainer.py
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
class OptionContainer(Widget):
    _USE_DEBUG_BACKGROUND = True

    def __init__(
        self,
        id: str | None = None,
        style: StyleT | None = None,
        content: Iterable[OptionContainerContentT] | None = None,
        on_select: toga.widgets.optioncontainer.OnSelectHandler | None = None,
        **kwargs,
    ):
        """Create a new OptionContainer.

        :param id: The ID for the widget.
        :param style: A style object. If no style is provided, a default style will be
            applied to the widget.
        :param content: The initial
            [OptionContainer content][toga.widgets.optioncontainer.OptionContainerContentT]
            to display in the OptionContainer.
        :param on_select: Initial [`on_select`][toga.OptionContainer.on_select] handler.
        :param kwargs: Initial style properties.
        """  # noqa: E501
        self._content = OptionList(self)
        self.on_select = None

        super().__init__(id, style, **kwargs)

        if content is not None:
            for item in content:
                match item:
                    case OptionItem():
                        self.content.append(item)
                        continue
                    case text, widget:
                        icon = None
                        enabled = True
                    case text, widget, icon:
                        enabled = True
                    case text, widget, icon, enabled:
                        pass
                    case _:
                        raise ValueError(
                            "Content items must be an OptionItem instance, or "
                            "tuples of (title, widget), (title, widget, icon), or "
                            "(title, widget, icon, enabled)"
                        )

                self.content.append(text, widget, enabled=enabled, icon=icon)

        self.on_select = on_select

    def _create(self) -> Any:
        return self.factory.OptionContainer(interface=self)

    @property
    def enabled(self) -> bool:
        """Is the widget currently enabled? i.e., can the user interact with the widget?

        OptionContainer widgets cannot be disabled; this property will always return
        True; any attempt to modify it will be ignored.
        """
        return True

    @enabled.setter
    def enabled(self, value: object) -> None:
        pass

    def focus(self) -> None:
        """No-op; OptionContainer cannot accept input focus."""

    @property
    def content(self) -> OptionList:
        """The tabs of content currently managed by the OptionContainer."""
        return self._content

    @property
    def current_tab(self) -> OptionItem | None:
        """The currently selected tab of content, or `None` if there are no tabs,
        or the OptionContainer is in a state where no tab is currently selected.

        This property can also be set with an `int` index, or a `str` label.
        """
        index = self._impl.get_current_tab_index()
        if index is None:
            return None
        return self._content[index]

    @current_tab.setter
    def current_tab(self, value: OptionItem | str | int) -> None:
        index = self._content.index(value)
        if not self._impl.is_option_enabled(index):
            raise ValueError("A disabled tab cannot be made the current tab.")

        self._impl.set_current_tab_index(index)

    @Widget.app.setter
    def app(self, app) -> None:
        # Invoke the superclass property setter
        Widget.app.fset(self, app)

        # Also assign the app to the content in the container
        for item in self._content:
            item._content.app = app

    @Widget.window.setter
    def window(self, window) -> None:
        # Invoke the superclass property setter
        Widget.window.fset(self, window)

        # Also assign the window to the content in the container
        for item in self._content:
            item._content.window = window

    @property
    def on_select(self) -> OnSelectHandler:
        """The callback to invoke when a new tab of content is selected."""
        return self._on_select

    @on_select.setter
    def on_select(self, handler: toga.widgets.optioncontainer.OnSelectHandler) -> None:
        self._on_select = wrapped_handler(self, handler)

content property

The tabs of content currently managed by the OptionContainer.

current_tab property writable

The currently selected tab of content, or None if there are no tabs, or the OptionContainer is in a state where no tab is currently selected.

This property can also be set with an int index, or a str label.

enabled property writable

Is the widget currently enabled? i.e., can the user interact with the widget?

OptionContainer widgets cannot be disabled; this property will always return True; any attempt to modify it will be ignored.

on_select property writable

The callback to invoke when a new tab of content is selected.

__init__(id=None, style=None, content=None, on_select=None, **kwargs)

Create a new OptionContainer.

:param id: The ID for the widget. :param style: A style object. If no style is provided, a default style will be applied to the widget. :param content: The initial OptionContainer content to display in the OptionContainer. :param on_select: Initial on_select handler. :param kwargs: Initial style properties.

Source code in core/src/toga/widgets/optioncontainer.py
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
def __init__(
    self,
    id: str | None = None,
    style: StyleT | None = None,
    content: Iterable[OptionContainerContentT] | None = None,
    on_select: toga.widgets.optioncontainer.OnSelectHandler | None = None,
    **kwargs,
):
    """Create a new OptionContainer.

    :param id: The ID for the widget.
    :param style: A style object. If no style is provided, a default style will be
        applied to the widget.
    :param content: The initial
        [OptionContainer content][toga.widgets.optioncontainer.OptionContainerContentT]
        to display in the OptionContainer.
    :param on_select: Initial [`on_select`][toga.OptionContainer.on_select] handler.
    :param kwargs: Initial style properties.
    """  # noqa: E501
    self._content = OptionList(self)
    self.on_select = None

    super().__init__(id, style, **kwargs)

    if content is not None:
        for item in content:
            match item:
                case OptionItem():
                    self.content.append(item)
                    continue
                case text, widget:
                    icon = None
                    enabled = True
                case text, widget, icon:
                    enabled = True
                case text, widget, icon, enabled:
                    pass
                case _:
                    raise ValueError(
                        "Content items must be an OptionItem instance, or "
                        "tuples of (title, widget), (title, widget, icon), or "
                        "(title, widget, icon, enabled)"
                    )

            self.content.append(text, widget, enabled=enabled, icon=icon)

    self.on_select = on_select

focus()

No-op; OptionContainer cannot accept input focus.

Source code in core/src/toga/widgets/optioncontainer.py
469
470
def focus(self) -> None:
    """No-op; OptionContainer cannot accept input focus."""
Source code in core/src/toga/widgets/optioncontainer.py
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
class OptionItem:
    def __init__(
        self,
        text: str,
        content: Widget,
        *,
        icon: IconContentT | None = None,
        enabled: bool = True,
    ):
        """A tab of content in an OptionContainer.

        :param text: The text label for the new tab.
        :param content: The content widget to use for the new tab.
        :param icon: The [icon content][toga.icons.IconContentT] to use to represent the
            tab.
        :param enabled: Should the new tab be enabled?
        """
        if content is None:
            raise ValueError("Content widget cannot be None.")

        self._content = content
        # These properties only exist while the item is in construction. Once the tab is
        # actual content, these attributes will be deleted and the native implementation
        # will become the source of truth. Initially prime the attributes with None (so
        # that the attribute exists), then use the setter to enforce validation on the
        # provided values.
        self._text: str = None
        self._icon: toga.Icon = None
        self._enabled: bool = None

        self.text = text
        self.icon = icon
        self.enabled = enabled

        # Prime the attributes for properties that will be set when the OptionItem is
        # set as content.
        self._interface: OptionContainer = None
        self._index: int = None

    @property
    def interface(self) -> OptionContainer:
        """The OptionContainer that contains this tab.

        Returns `None` if the tab isn't currently part of an OptionContainer.
        """
        return self._interface

    @property
    def enabled(self) -> bool:
        """Is the panel of content available for selection?"""
        try:
            return self._enabled
        except AttributeError:
            return self._interface._impl.is_option_enabled(self.index)

    @enabled.setter
    def enabled(self, value: object) -> None:
        enable = bool(value)
        if hasattr(self, "_enabled"):
            self._enabled = enable
        else:
            if (
                not enable
                and self.index == self._interface._impl.get_current_tab_index()
            ):
                raise ValueError("The currently selected tab cannot be disabled.")

            self._interface._impl.set_option_enabled(self.index, enable)

    @property
    def text(self) -> str:
        """The label for the tab of content."""
        try:
            return self._text
        except AttributeError:
            return self._interface._impl.get_option_text(self.index)

    @text.setter
    def text(self, value: object) -> None:
        if value is None:
            raise ValueError("Item text cannot be None")

        text = str(value)
        if not text:
            raise ValueError("Item text cannot be blank")

        if hasattr(self, "_text"):
            self._text = text
        else:
            self._interface._impl.set_option_text(self.index, text)

    @property
    def icon(self) -> toga.Icon:
        """The Icon for the tab of content.

        Can be specified as any valid [icon content][toga.icons.IconContentT].

        If the platform does not support the display of icons, this property
        will return `None` regardless of any value provided.
        """
        try:
            return self._icon
        except AttributeError:
            return self._interface._impl.get_option_icon(self.index)

    @icon.setter
    def icon(self, icon_or_name: IconContentT | None) -> None:
        if get_factory().OptionContainer.uses_icons:
            match icon_or_name:
                case toga.Icon() | None as icon:
                    pass
                case _:
                    icon = toga.Icon(icon_or_name)

            if hasattr(self, "_icon"):
                self._icon = icon
            else:
                self._interface._impl.set_option_icon(self.index, icon)

    @property
    def index(self) -> int | None:
        """The index of the tab in the OptionContainer.

        Returns `None` if the tab isn't currently part of an OptionContainer.
        """
        return self._index

    @property
    def content(self) -> Widget:
        """The content widget displayed in this tab of the OptionContainer."""
        return self._content

    def _preserve_option(self) -> None:
        # Move the ground truth back to the OptionItem instance
        self._text = self.text
        self._icon = self.icon
        self._enabled = self.enabled

        # Clear
        self._index = None
        self._interface = None

    def _add_as_option(self, index: int, interface: OptionContainer) -> None:
        text = self._text
        del self._text

        icon = self._icon
        del self._icon

        enabled = self._enabled
        del self._enabled

        self._content.app = interface.app
        self._content.window = interface.window

        self._index = index
        self._interface = interface
        interface._impl.add_option(index, text, self.content._impl, icon)

        # The option now exists on the implementation; finalize the display properties
        # that can't be resolved until the implementation exists.
        interface.refresh()
        self.enabled = enabled

content property

The content widget displayed in this tab of the OptionContainer.

enabled property writable

Is the panel of content available for selection?

icon property writable

The Icon for the tab of content.

Can be specified as any valid icon content.

If the platform does not support the display of icons, this property will return None regardless of any value provided.

index property

The index of the tab in the OptionContainer.

Returns None if the tab isn't currently part of an OptionContainer.

interface property

The OptionContainer that contains this tab.

Returns None if the tab isn't currently part of an OptionContainer.

text property writable

The label for the tab of content.

__init__(text, content, *, icon=None, enabled=True)

A tab of content in an OptionContainer.

:param text: The text label for the new tab. :param content: The content widget to use for the new tab. :param icon: The icon content to use to represent the tab. :param enabled: Should the new tab be enabled?

Source code in core/src/toga/widgets/optioncontainer.py
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
def __init__(
    self,
    text: str,
    content: Widget,
    *,
    icon: IconContentT | None = None,
    enabled: bool = True,
):
    """A tab of content in an OptionContainer.

    :param text: The text label for the new tab.
    :param content: The content widget to use for the new tab.
    :param icon: The [icon content][toga.icons.IconContentT] to use to represent the
        tab.
    :param enabled: Should the new tab be enabled?
    """
    if content is None:
        raise ValueError("Content widget cannot be None.")

    self._content = content
    # These properties only exist while the item is in construction. Once the tab is
    # actual content, these attributes will be deleted and the native implementation
    # will become the source of truth. Initially prime the attributes with None (so
    # that the attribute exists), then use the setter to enforce validation on the
    # provided values.
    self._text: str = None
    self._icon: toga.Icon = None
    self._enabled: bool = None

    self.text = text
    self.icon = icon
    self.enabled = enabled

    # Prime the attributes for properties that will be set when the OptionItem is
    # set as content.
    self._interface: OptionContainer = None
    self._index: int = None
Source code in core/src/toga/widgets/optioncontainer.py
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
class OptionList:
    def __init__(self, interface: Any):
        self.interface = interface
        self._options: list[OptionItem] = []

    def __repr__(self) -> str:
        items = ", ".join(repr(option.text) for option in self)
        return f"<OptionList {items}>"

    def __getitem__(self, index: int | str | OptionItem) -> OptionItem:
        """Obtain a specific tab of content."""
        return self._options[self.index(index)]

    def __delitem__(self, index: int | str | OptionItem) -> None:
        """Same as [`remove`][toga.widgets.optioncontainer.OptionList.remove]."""
        self.remove(index)

    def remove(self, index: int | str | OptionItem) -> None:
        """Remove the specified tab of content.

        The currently selected item cannot be deleted.

        :param index: The index where the new tab should be inserted.
        """
        index = self.index(index)
        if index == self.interface._impl.get_current_tab_index():
            raise ValueError("The currently selected tab cannot be deleted.")

        # Ensure that the current ground truth of the item to be deleted is preserved as
        # attributes on the item
        deleted_item = self._options[index]
        deleted_item._preserve_option()
        deleted_item._content.window = None
        deleted_item._content.app = None

        self.interface._impl.remove_option(index)
        del self._options[index]
        # Update the index for each of the options
        # after the one that was removed.
        for option in self._options[index:]:
            option._index -= 1

        # Refresh the widget
        self.interface.refresh()

    def __len__(self) -> int:
        """The number of tabs of content in the OptionContainer."""
        return len(self._options)

    def index(self, value: str | int | OptionItem) -> int:
        """Find the index of the tab that matches the given value.

        :param value: The value to look for. An integer is returned as-is;
            if an [`OptionItem`][toga.widgets.optioncontainer.OptionItem] is provided,
            that item's index is returned;
            any other value will be converted into a string, and the first
            tab with a label matching that string will be returned.
        :raises ValueError: If no tab matching the value can be found.
        """
        match value:
            case int():
                return value
            case OptionItem():
                return value.index
            case _:
                try:
                    return next(tab for tab in self if tab.text == str(value)).index
                except StopIteration as exc:
                    raise ValueError(f"No tab named {value!r}") from exc

    @overload
    def append(
        self,
        text_or_item: OptionItem,
    ) -> None: ...

    @overload
    def append(
        self,
        text_or_item: str,
        content: Widget,
        *,
        icon: IconContentT | None = None,
        enabled: bool | None = True,
    ) -> None: ...

    def append(
        self,
        text_or_item: str | OptionItem,
        content: Widget | None = None,
        *,
        icon: IconContentT | None = None,
        enabled: bool | None = None,
    ) -> None:
        """Add a new tab of content to the OptionContainer.

        The new tab can be specified as an existing
        [`OptionItem`][toga.widgets.optioncontainer.OptionItem] instance, or by
        specifying the full details of the new tab of content. If an
        [`OptionItem`][toga.widgets.optioncontainer.OptionItem]
        is provided, specifying `content`, `icon` or `enabled` will raise an
        error.

        :param text_or_item: An [`OptionItem`][toga.widgets.optioncontainer.OptionItem];
            or, the text label for the new tab.
        :param content: The content widget to use for the new tab.
        :param icon: The [icon content][toga.icons.IconContentT] to use to represent the
            tab.
        :param enabled: Should the new tab be enabled? (Default: `True`)
        """
        self.insert(len(self), text_or_item, content, icon=icon, enabled=enabled)

    @overload
    def insert(
        self,
        index: int | str | OptionItem,
        text_or_item: OptionItem,
    ) -> None: ...

    @overload
    def insert(
        self,
        index: int | str | OptionItem,
        text_or_item: str,
        content: Widget,
        *,
        icon: IconContentT | None = None,
        enabled: bool | None = True,
    ) -> None: ...

    def insert(
        self,
        index: int | str | OptionItem,
        text_or_item: str | OptionItem,
        content: Widget | None = None,
        *,
        icon: IconContentT | None = None,
        enabled: bool | None = None,
    ) -> None:
        """Insert a new tab of content to the OptionContainer at the specified index.

        The new tab can be specified as an existing
        [`OptionItem`][toga.widgets.optioncontainer.OptionItem] instance, or by
        specifying the full details of the new tab of content. If an
        [`OptionItem`][toga.widgets.optioncontainer.OptionItem]
        is provided, specifying `content`, `icon` or `enabled` will raise an
        error.

        :param index: The index where the new tab should be inserted.
        :param text_or_item: An [`OptionItem`][toga.widgets.optioncontainer.OptionItem];
            or, the text label for the new tab.
        :param content: The content widget to use for the new tab.
        :param icon: The [icon content][toga.icons.IconContentT] to use to represent the
            tab.
        :param enabled: Should the new tab be enabled? (Default: `True`)
        """
        if isinstance(text_or_item, OptionItem):
            if content is not None:
                raise ValueError(
                    "Cannot specify content if using an OptionItem instance."
                )
            if icon is not None:
                raise ValueError("Cannot specify icon if using an OptionItem instance.")
            if enabled is not None:
                raise ValueError(
                    "Cannot specify enabled if using an OptionItem instance."
                )
            item = text_or_item
        else:
            # Create an interface wrapper for the option.
            item = OptionItem(
                text_or_item,
                content,
                icon=icon,
                enabled=enabled if enabled is not None else True,
            )

        # Convert the index into an integer, and assign to the item.
        index = self.index(index)

        # Add the option to the list maintained on the interface,
        # and increment the index of all items after the one that was added.
        self._options.insert(index, item)
        for option in self._options[index + 1 :]:
            option._index += 1

        # Add the content to the implementation.
        # This will cause the native implementation to be created.
        item._add_as_option(index, self.interface)

__delitem__(index)

Same as remove.

Source code in core/src/toga/widgets/optioncontainer.py
224
225
226
def __delitem__(self, index: int | str | OptionItem) -> None:
    """Same as [`remove`][toga.widgets.optioncontainer.OptionList.remove]."""
    self.remove(index)

__getitem__(index)

Obtain a specific tab of content.

Source code in core/src/toga/widgets/optioncontainer.py
220
221
222
def __getitem__(self, index: int | str | OptionItem) -> OptionItem:
    """Obtain a specific tab of content."""
    return self._options[self.index(index)]

__len__()

The number of tabs of content in the OptionContainer.

Source code in core/src/toga/widgets/optioncontainer.py
256
257
258
def __len__(self) -> int:
    """The number of tabs of content in the OptionContainer."""
    return len(self._options)

append(text_or_item, content=None, *, icon=None, enabled=None)

append(text_or_item: OptionItem) -> None
append(
    text_or_item: str,
    content: Widget,
    *,
    icon: IconContentT | None = None,
    enabled: bool | None = True,
) -> None

Add a new tab of content to the OptionContainer.

The new tab can be specified as an existing OptionItem instance, or by specifying the full details of the new tab of content. If an OptionItem is provided, specifying content, icon or enabled will raise an error.

:param text_or_item: An OptionItem; or, the text label for the new tab. :param content: The content widget to use for the new tab. :param icon: The icon content to use to represent the tab. :param enabled: Should the new tab be enabled? (Default: True)

Source code in core/src/toga/widgets/optioncontainer.py
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
def append(
    self,
    text_or_item: str | OptionItem,
    content: Widget | None = None,
    *,
    icon: IconContentT | None = None,
    enabled: bool | None = None,
) -> None:
    """Add a new tab of content to the OptionContainer.

    The new tab can be specified as an existing
    [`OptionItem`][toga.widgets.optioncontainer.OptionItem] instance, or by
    specifying the full details of the new tab of content. If an
    [`OptionItem`][toga.widgets.optioncontainer.OptionItem]
    is provided, specifying `content`, `icon` or `enabled` will raise an
    error.

    :param text_or_item: An [`OptionItem`][toga.widgets.optioncontainer.OptionItem];
        or, the text label for the new tab.
    :param content: The content widget to use for the new tab.
    :param icon: The [icon content][toga.icons.IconContentT] to use to represent the
        tab.
    :param enabled: Should the new tab be enabled? (Default: `True`)
    """
    self.insert(len(self), text_or_item, content, icon=icon, enabled=enabled)

index(value)

Find the index of the tab that matches the given value.

:param value: The value to look for. An integer is returned as-is; if an OptionItem is provided, that item's index is returned; any other value will be converted into a string, and the first tab with a label matching that string will be returned. :raises ValueError: If no tab matching the value can be found.

Source code in core/src/toga/widgets/optioncontainer.py
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
def index(self, value: str | int | OptionItem) -> int:
    """Find the index of the tab that matches the given value.

    :param value: The value to look for. An integer is returned as-is;
        if an [`OptionItem`][toga.widgets.optioncontainer.OptionItem] is provided,
        that item's index is returned;
        any other value will be converted into a string, and the first
        tab with a label matching that string will be returned.
    :raises ValueError: If no tab matching the value can be found.
    """
    match value:
        case int():
            return value
        case OptionItem():
            return value.index
        case _:
            try:
                return next(tab for tab in self if tab.text == str(value)).index
            except StopIteration as exc:
                raise ValueError(f"No tab named {value!r}") from exc

insert(index, text_or_item, content=None, *, icon=None, enabled=None)

insert(
    index: int | str | OptionItem, text_or_item: OptionItem
) -> None
insert(
    index: int | str | OptionItem,
    text_or_item: str,
    content: Widget,
    *,
    icon: IconContentT | None = None,
    enabled: bool | None = True,
) -> None

Insert a new tab of content to the OptionContainer at the specified index.

The new tab can be specified as an existing OptionItem instance, or by specifying the full details of the new tab of content. If an OptionItem is provided, specifying content, icon or enabled will raise an error.

:param index: The index where the new tab should be inserted. :param text_or_item: An OptionItem; or, the text label for the new tab. :param content: The content widget to use for the new tab. :param icon: The icon content to use to represent the tab. :param enabled: Should the new tab be enabled? (Default: True)

Source code in core/src/toga/widgets/optioncontainer.py
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
def insert(
    self,
    index: int | str | OptionItem,
    text_or_item: str | OptionItem,
    content: Widget | None = None,
    *,
    icon: IconContentT | None = None,
    enabled: bool | None = None,
) -> None:
    """Insert a new tab of content to the OptionContainer at the specified index.

    The new tab can be specified as an existing
    [`OptionItem`][toga.widgets.optioncontainer.OptionItem] instance, or by
    specifying the full details of the new tab of content. If an
    [`OptionItem`][toga.widgets.optioncontainer.OptionItem]
    is provided, specifying `content`, `icon` or `enabled` will raise an
    error.

    :param index: The index where the new tab should be inserted.
    :param text_or_item: An [`OptionItem`][toga.widgets.optioncontainer.OptionItem];
        or, the text label for the new tab.
    :param content: The content widget to use for the new tab.
    :param icon: The [icon content][toga.icons.IconContentT] to use to represent the
        tab.
    :param enabled: Should the new tab be enabled? (Default: `True`)
    """
    if isinstance(text_or_item, OptionItem):
        if content is not None:
            raise ValueError(
                "Cannot specify content if using an OptionItem instance."
            )
        if icon is not None:
            raise ValueError("Cannot specify icon if using an OptionItem instance.")
        if enabled is not None:
            raise ValueError(
                "Cannot specify enabled if using an OptionItem instance."
            )
        item = text_or_item
    else:
        # Create an interface wrapper for the option.
        item = OptionItem(
            text_or_item,
            content,
            icon=icon,
            enabled=enabled if enabled is not None else True,
        )

    # Convert the index into an integer, and assign to the item.
    index = self.index(index)

    # Add the option to the list maintained on the interface,
    # and increment the index of all items after the one that was added.
    self._options.insert(index, item)
    for option in self._options[index + 1 :]:
        option._index += 1

    # Add the content to the implementation.
    # This will cause the native implementation to be created.
    item._add_as_option(index, self.interface)

remove(index)

Remove the specified tab of content.

The currently selected item cannot be deleted.

:param index: The index where the new tab should be inserted.

Source code in core/src/toga/widgets/optioncontainer.py
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
def remove(self, index: int | str | OptionItem) -> None:
    """Remove the specified tab of content.

    The currently selected item cannot be deleted.

    :param index: The index where the new tab should be inserted.
    """
    index = self.index(index)
    if index == self.interface._impl.get_current_tab_index():
        raise ValueError("The currently selected tab cannot be deleted.")

    # Ensure that the current ground truth of the item to be deleted is preserved as
    # attributes on the item
    deleted_item = self._options[index]
    deleted_item._preserve_option()
    deleted_item._content.window = None
    deleted_item._content.app = None

    self.interface._impl.remove_option(index)
    del self._options[index]
    # Update the index for each of the options
    # after the one that was removed.
    for option in self._options[index:]:
        option._index -= 1

    # Refresh the widget
    self.interface.refresh()
Source code in core/src/toga/widgets/optioncontainer.py
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
class OptionItem:
    def __init__(
        self,
        text: str,
        content: Widget,
        *,
        icon: IconContentT | None = None,
        enabled: bool = True,
    ):
        """A tab of content in an OptionContainer.

        :param text: The text label for the new tab.
        :param content: The content widget to use for the new tab.
        :param icon: The [icon content][toga.icons.IconContentT] to use to represent the
            tab.
        :param enabled: Should the new tab be enabled?
        """
        if content is None:
            raise ValueError("Content widget cannot be None.")

        self._content = content
        # These properties only exist while the item is in construction. Once the tab is
        # actual content, these attributes will be deleted and the native implementation
        # will become the source of truth. Initially prime the attributes with None (so
        # that the attribute exists), then use the setter to enforce validation on the
        # provided values.
        self._text: str = None
        self._icon: toga.Icon = None
        self._enabled: bool = None

        self.text = text
        self.icon = icon
        self.enabled = enabled

        # Prime the attributes for properties that will be set when the OptionItem is
        # set as content.
        self._interface: OptionContainer = None
        self._index: int = None

    @property
    def interface(self) -> OptionContainer:
        """The OptionContainer that contains this tab.

        Returns `None` if the tab isn't currently part of an OptionContainer.
        """
        return self._interface

    @property
    def enabled(self) -> bool:
        """Is the panel of content available for selection?"""
        try:
            return self._enabled
        except AttributeError:
            return self._interface._impl.is_option_enabled(self.index)

    @enabled.setter
    def enabled(self, value: object) -> None:
        enable = bool(value)
        if hasattr(self, "_enabled"):
            self._enabled = enable
        else:
            if (
                not enable
                and self.index == self._interface._impl.get_current_tab_index()
            ):
                raise ValueError("The currently selected tab cannot be disabled.")

            self._interface._impl.set_option_enabled(self.index, enable)

    @property
    def text(self) -> str:
        """The label for the tab of content."""
        try:
            return self._text
        except AttributeError:
            return self._interface._impl.get_option_text(self.index)

    @text.setter
    def text(self, value: object) -> None:
        if value is None:
            raise ValueError("Item text cannot be None")

        text = str(value)
        if not text:
            raise ValueError("Item text cannot be blank")

        if hasattr(self, "_text"):
            self._text = text
        else:
            self._interface._impl.set_option_text(self.index, text)

    @property
    def icon(self) -> toga.Icon:
        """The Icon for the tab of content.

        Can be specified as any valid [icon content][toga.icons.IconContentT].

        If the platform does not support the display of icons, this property
        will return `None` regardless of any value provided.
        """
        try:
            return self._icon
        except AttributeError:
            return self._interface._impl.get_option_icon(self.index)

    @icon.setter
    def icon(self, icon_or_name: IconContentT | None) -> None:
        if get_factory().OptionContainer.uses_icons:
            match icon_or_name:
                case toga.Icon() | None as icon:
                    pass
                case _:
                    icon = toga.Icon(icon_or_name)

            if hasattr(self, "_icon"):
                self._icon = icon
            else:
                self._interface._impl.set_option_icon(self.index, icon)

    @property
    def index(self) -> int | None:
        """The index of the tab in the OptionContainer.

        Returns `None` if the tab isn't currently part of an OptionContainer.
        """
        return self._index

    @property
    def content(self) -> Widget:
        """The content widget displayed in this tab of the OptionContainer."""
        return self._content

    def _preserve_option(self) -> None:
        # Move the ground truth back to the OptionItem instance
        self._text = self.text
        self._icon = self.icon
        self._enabled = self.enabled

        # Clear
        self._index = None
        self._interface = None

    def _add_as_option(self, index: int, interface: OptionContainer) -> None:
        text = self._text
        del self._text

        icon = self._icon
        del self._icon

        enabled = self._enabled
        del self._enabled

        self._content.app = interface.app
        self._content.window = interface.window

        self._index = index
        self._interface = interface
        interface._impl.add_option(index, text, self.content._impl, icon)

        # The option now exists on the implementation; finalize the display properties
        # that can't be resolved until the implementation exists.
        interface.refresh()
        self.enabled = enabled

content property

The content widget displayed in this tab of the OptionContainer.

enabled property writable

Is the panel of content available for selection?

icon property writable

The Icon for the tab of content.

Can be specified as any valid icon content.

If the platform does not support the display of icons, this property will return None regardless of any value provided.

index property

The index of the tab in the OptionContainer.

Returns None if the tab isn't currently part of an OptionContainer.

interface property

The OptionContainer that contains this tab.

Returns None if the tab isn't currently part of an OptionContainer.

text property writable

The label for the tab of content.

__init__(text, content, *, icon=None, enabled=True)

A tab of content in an OptionContainer.

:param text: The text label for the new tab. :param content: The content widget to use for the new tab. :param icon: The icon content to use to represent the tab. :param enabled: Should the new tab be enabled?

Source code in core/src/toga/widgets/optioncontainer.py
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
def __init__(
    self,
    text: str,
    content: Widget,
    *,
    icon: IconContentT | None = None,
    enabled: bool = True,
):
    """A tab of content in an OptionContainer.

    :param text: The text label for the new tab.
    :param content: The content widget to use for the new tab.
    :param icon: The [icon content][toga.icons.IconContentT] to use to represent the
        tab.
    :param enabled: Should the new tab be enabled?
    """
    if content is None:
        raise ValueError("Content widget cannot be None.")

    self._content = content
    # These properties only exist while the item is in construction. Once the tab is
    # actual content, these attributes will be deleted and the native implementation
    # will become the source of truth. Initially prime the attributes with None (so
    # that the attribute exists), then use the setter to enforce validation on the
    # provided values.
    self._text: str = None
    self._icon: toga.Icon = None
    self._enabled: bool = None

    self.text = text
    self.icon = icon
    self.enabled = enabled

    # Prime the attributes for properties that will be set when the OptionItem is
    # set as content.
    self._interface: OptionContainer = None
    self._index: int = None

Bases: Protocol

Source code in core/src/toga/widgets/optioncontainer.py
36
37
38
39
40
41
42
43
class OnSelectHandler(Protocol):
    def __call__(self, widget: OptionContainer, **kwargs: Any) -> None:
        """A handler that will be invoked when a new tab is selected
        in the OptionContainer.

        :param widget: The OptionContainer that had a selection change.
        :param kwargs: Ensures compatibility with arguments added in future versions.
        """

__call__(widget, **kwargs)

A handler that will be invoked when a new tab is selected in the OptionContainer.

:param widget: The OptionContainer that had a selection change. :param kwargs: Ensures compatibility with arguments added in future versions.

Source code in core/src/toga/widgets/optioncontainer.py
37
38
39
40
41
42
43
def __call__(self, widget: OptionContainer, **kwargs: Any) -> None:
    """A handler that will be invoked when a new tab is selected
    in the OptionContainer.

    :param widget: The OptionContainer that had a selection change.
    :param kwargs: Ensures compatibility with arguments added in future versions.
    """

An item of content to add to an OptionContainer. This content can be:

  • a 2-tuple, containing the title for the tab, and the content widget;
  • a 3-tuple, containing the title, content widget, and icon for the tab;
  • a 4-tuple, containing the title, content widget, icon for the tab, and enabled status; or
  • an toga.OptionItem instance.