dialog_quick_tab

Creates a Modal Dialog displaying a different SubDialog according to the selected entry of the QuickTab. Demonstrates how to add, flushes, remove tab interactively.

dialog_quick_tab.py

"""Creates a Modal Dialog displaying a different SubDialog according to the selected entry of the QuickTab. Demonstrates how to add, flushes, remove tab interactively.

Topics:
    * c4d.gui.QuickTabCustomGui
    * QuickTabCustomGui.ClearStrings()
    * QuickTabCustomGui.AppendString()
    * c4d.gui.GeDialog
    * GeDialog.CreateLayout()
    * GeDialog.InitValues()
    * GeDialog.Command()
    * GeDialog.HideElement()
    * GeDialog.RemoveElement()
    * c4d.gui.SubDialog
"""
__author__ = "Maxime Adam"
__copyright__ = "Copyright (C) 2026 MAXON Computer GmbH"
__license__ = "Apache-2.0 License"
__version__ = "R19"

import c4d

# Ids used in our Dialog
ID_MAINGROUP = 100000  # ID used for the Group that holds all the other group representing the tab content
ID_QUICKTAB_BAR = 110000  # ID for the quicktab customGui
ID_QUICKTAB_BASE_GROUP = 120000  # Base ID for each SubDialog

BUTTON_PRINT_TEXT = 1200  # ID used for the Print text Button
BUTTON_PRINT_SELECTED = 1201  # ID used for the Print Selected Button
BUTTON_FLUSH_ALL = 1203  # ID used for the Flush All Button
BUTTON_ADD = 1204  # ID used for the Add Button
BUTTON_REMOVE = 1205  # ID used for the Remove Button

# Id used in our SubDialog
CUSTOM_GROUP_ID_TEXT_BASE = 100000  # Defines the ID for the string to be displayed


class CustomGroup(c4d.gui.SubDialog):
    """A SubDialog to display the passed string, its used as example for the actual content of a Tab"""
    def __init__(self, data):
        self._data = data

    def CreateLayout(self):
        for i, data in enumerate(self._data):
            self.AddStaticText(CUSTOM_GROUP_ID_TEXT_BASE + i, c4d.BFH_SCALEFIT, name=data)
        return True


class QuickTabDialogExample(c4d.gui.GeDialog):

    def __init__(self):
        self._quickTab = None  # Stores the quicktab custom GUI
        self._tabList = {}  # Stores the TabName and the SubDialog that represents each tab of the QuickTab

    def _DrawQuickTabGroup(self):
        """Creates and draws all the SubDialog for each tab, take care it does not hide these according to a selection state.

        Returns: 
            True if success otherwise False.
        """

        # Checks if the quicktab is defined
        if self._quickTab is None:
            return False

        # Flush the content of the group that holds all ours SubDialogs
        self.LayoutFlushGroup(ID_MAINGROUP)

        # Iterates over the number of tab to create and attach the correct SubDialog
        for tabId, (tabName, tabGui) in enumerate(self._tabList.items()):
            self.AddSubDialog(ID_QUICKTAB_BASE_GROUP + tabId, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, 0, 0)
            self.AttachSubDialog(tabGui, ID_QUICKTAB_BASE_GROUP + tabId)

        # Notifies the content of the MainGroup has changed
        self.LayoutChanged(ID_MAINGROUP)

        return True

    def GetActiveTabs(self):
        """Retrieves two list of currently selected tabs from the self._quickTab.

        Returns:
            list(int), list(name): The first list, contains tabs Id (from self._quickTab the dict) and the second list contains all names of the selected tabs.
        """
        # Checks if the quicktab is defined
        if self._quickTab is None:
            return False, False

        returnIds = []
        returnNames = []

        for tabId, (tabName, tabGui) in enumerate(self._tabList.items()):
            if self._quickTab.IsSelected(tabId):
                returnIds.append(tabId)
                returnNames.append(tabName)

        return returnIds, returnNames

    def DisplayCorrectGroup(self):
        """Hides all unused groups and display the correct one.

        Returns: 
            True if success otherwise False.
        """
        # Retrieves the selected tab
        activeIds, activeNames = self.GetActiveTabs()

        # Iterates each CustomGui and defines if they are hidden or not
        for tabId in range(len(self._tabList)):
            toDisplay = tabId in activeIds
            self.HideElement(ID_QUICKTAB_BASE_GROUP + tabId, not toDisplay)

        # Notifies the content of the MainGroup has changed
        self.LayoutChanged(ID_MAINGROUP)
        return True

    def AppendTab(self, tabName, content, active=True):
        """Appends a tab to the current quicktab with the associated content to be displayed.

        Args:
            tabName (str): The name the tab should have.
            content (c4d.gui.SubDialog): The SubDialog to be drawn/linked when the tab is selected.
            active (bool, optional): If True, the inserted tab will be selected. Defaults to True.

        Returns:
            True if success otherwise False.
        """
        # Checks if the quicktab is defined
        if self._quickTab is None:
            return False

        # Adds the tab entry n the quicktab
        self._quickTab.AppendString(len(self._tabList), tabName, False)

        # Updates our current tabList with tabName and the Subdialog to be linked
        self._tabList.update({tabName: content})

        # Retrieves the current selected tab
        previousActiveId, previousActiveName = self.GetActiveTabs()

        # Draws the quicktab SubDialog (in order to have the new one drawn)
        self._DrawQuickTabGroup()

        # Defines the just added tab according state
        self._quickTab.Select(len(self._tabList) - 1, active)

        # Defines previous active tab
        for tabId in previousActiveId:
            self._quickTab.Select(tabId, True)

        # Display only the selected tab and hides all others
        self.DisplayCorrectGroup()

        return True

    def FlushAllTabs(self):
        """Removes all tabs and their content from the GUI.

        Returns: 
            True if success otherwise False.
        """
        # Checks if the quicktab is defined
        if self._quickTab is None:
            return False

        # Removes all the tabs
        self._quickTab.ClearStrings()

        # Removes all the customGui
        for tabId in range(len(self._tabList)):
            self.RemoveElement(ID_QUICKTAB_BASE_GROUP + tabId)

        # Reinitializes the stored tablist to an empty dict
        self._tabList = {}

        # Notifies the content of the MainGroup has changed
        self.LayoutChanged(ID_MAINGROUP)

        return True

    def RemoveTab(self, tabNameToRemove):
        """Removes a tab by its name

        Args:
            tabNameToRemove (str): The tab to remove.

        Returns:
            True if success otherwise False.
        """
        # Checks if the quicktab is defined
        if self._quickTab is None:
            return False

        # Copies the tabList
        newDict = dict(self._tabList)

        # Checks if the entry exist
        if tabNameToRemove not in newDict:
            return True

        # Removes the entry we want to delete
        del newDict[tabNameToRemove]

        # Removes all groups
        self.FlushAllTabs()

        # Re-adds all the one from our copy
        for tabName, tabGui in newDict.items():
            self.AppendTab(tabName, tabGui)

        return True

    def CreateLayout(self):
        """This Method is called automatically when Cinema 4D Create the Layout (display) of the Dialog."""

        # Creates a QuickTab Custom Gui
        bc = c4d.BaseContainer()
        bc.SetBool(c4d.QUICKTAB_BAR, False)
        bc.SetBool(c4d.QUICKTAB_SHOWSINGLE, True)
        bc.SetBool(c4d.QUICKTAB_NOMULTISELECT, False)
        self._quickTab = self.AddCustomGui(ID_QUICKTAB_BAR, c4d.CUSTOMGUI_QUICKTAB, '',
                                           c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, 0, 0, bc)

        # Creates a group that will contain all the group representing each tab
        self.GroupBegin(ID_MAINGROUP, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, 0, 0, '', 0)
        self.GroupEnd()

        # Creates a group with 5 button in order to do some operation with the QuickTab CustomGUI
        if self.GroupBegin(0, c4d.BFH_SCALEFIT, 5, 1, '', 0):
            self.AddButton(BUTTON_PRINT_TEXT, c4d.BFH_SCALEFIT, name="Print text")
            self.AddButton(BUTTON_PRINT_SELECTED, c4d.BFH_SCALEFIT, name="Print Selected")
            self.AddButton(BUTTON_FLUSH_ALL, c4d.BFH_SCALEFIT, name="Flush All")
            self.AddButton(BUTTON_ADD, c4d.BFH_SCALEFIT, name="Add")
            self.AddButton(BUTTON_REMOVE, c4d.BFH_SCALEFIT, name="Remove")
        self.GroupEnd()
        return True

    def InitValues(self):
        """This Method is called automatically after the GUI is initialized."""
        # Creates the first Tab
        cg1 = CustomGroup(["This is the first Tab", "Just dummy text here"])
        self.AppendTab("First Tab", cg1, True)

        # Creates the second Tab
        cg2 = CustomGroup(["This is the second Tab", "Just another dummy text here"])
        self.AppendTab("Second Tab", cg2, False)
        return True

    def Command(self, id, msg):
        """This Method is called automatically when the user clicks on a gadget and/or changes its value this function will be called.
         It is also called when a string menu item is selected.

        Args:
            id: The ID of the gadget that triggered the event.
            msg: The original message container

        Returns:
            False if there was an error, otherwise True.
        """

        # If the user interacts with the quicktab, we make sure to display the CustomGUI linked to the active one
        if id == ID_QUICKTAB_BAR and self._quickTab:
            self.DisplayCorrectGroup()
            return True

        # Displays all the Tab name
        if id == BUTTON_PRINT_TEXT:
            print([key for key in self._tabList])
            return True

        # Displays the ID and name of the selected tab
        if id == BUTTON_PRINT_SELECTED:
            print(self.GetActiveTabs())

        # Removes all tabs
        if id == BUTTON_FLUSH_ALL:
            self.FlushAllTabs()

        # Adds a new Tab to the quicktab
        if id == BUTTON_ADD:
            cg3 = CustomGroup(["This is the third Tab"])
            self.AppendTab("Third Tab", cg3, True)

        # Removes the first tab of the quicktab
        if id == BUTTON_REMOVE:
            self.RemoveTab("First Tab")

        return True


# Main function
def main():
    # Initializes a QuickTabDialogExample Dialog
    diag = QuickTabDialogExample()

    # Opens the Dialog in modal mode
    diag.Open(dlgtype=c4d.DLG_TYPE_MODAL, defaultw=400, defaulth=400)


# Execute main()
if __name__ == '__main__':
    main()