Solved How to Override DoubleClick() from the TreeViewFunctions?

Hi,

By default, when you double click in a TreeView item, an editable text box appears and allows you to rename the TreeView item. Using the code from @Donovan Keith, it results to the following:
https://www.dropbox.com/s/oxbr1o68vwjfwd3/c4d268_override_double_click_treeview.mp4?dl=0

It works if you are using the c4d objects but not if you are using custom nodes.
I don't know how to override the DoubleClick. The following code prints the statement properly but it doesn't call the "renaming" feature or fetch the new string.

    def DoubleClick(self, root, userdata, obj, col, mouseinfo):

        print "Please Rename Me"
        return True

The result can be seen here.

Is there a way around this?

Regards,
Ben

hi,

Your DoubleClick function is returning True, so the renaming function is not kicking in.

You could open a "rename" dialogbox and do the rename in the DoubleClick function or set a "global" variable where you can store the col and let the GetName and SetName

Cheers,
Manuel

MAXON SDK Specialist

MAXON Registered Developer

@m_magalhaes

Thanks for the response.

RE: You could open a "rename" dialogbox
I understand I can do that, but I prefer the default behavior of the double click: An edit text appears and when I hit enter the treeview item is immediately renamed.

Is this still possible with non native C4D objects?

I guess in more direct term what is the UI code for
when I double click an edit text appears and when I hit enter the treeview item is immediately renamed.

See video above in the first post for reference.

Hm,

probably not what you are looking for, but when you run out of other options, you could try to use mouseinfo and the screen position of your hosting dialog to open a borderless dialog just in the right place, i.e. over the element you want to rename. Will probably require some fiddling, but should be possible.

Cheers,
zipit

MAXON SDK Specialist
developers.maxon.net

hi,

Sorry my answer wasn't clear. I'm not using c4d object.

The rename default behaviour will not happen if your double click function return True. (meaning the double click have been handled)

It's not related with using default c4d objects or not.

So don't return true in your DoubleClick function.

The "problem" is that it will use the GetName/SetName functions. Those functions doesn't know what columns have been clicked.
When you double click, the GetName function is called to display the text that you want to rename, and when you press enter, the SetName is called.

Imagine you have FirstName and LastName. You double click the FirstName. In the DoubleClick function you will set a variable "FirstName"
In the SetName function you will be able to use this kind of "code"

# just to get the idea
if self.firstName:
   obj._data.firstName = str
else:
   obj._data.lastName = str

Cheers
Manuel.

MAXON SDK Specialist

MAXON Registered Developer

Thanks for the response

@zipit
Yea I think that would be the last resort as it might end up with more code than just having ah popup rename dialog. I was just thinking of leveraging the default behavior and fetching the old name and the new name

@m_magalhaes

RE: If I omit the Return True
It will give me an error of TypeError: DoubleClick expected bool, not None

RE: GetName function is called to display the text
RE: when you press enter, the SetName is called.
Correct me if I'm wrong the functions GetName and SetName are only available only on default C4D objects. I can't use it.

RE: Imagine you have FirstName and LastName. You double click the FirstName
Sorry I don't follow this. In my video example above, I don't have any other column, except 1.

Apologies if this is longer than expected, but it is there I can fetch the new name without using the GetName as it does not work on my obj since its not a native C4D.
For instance,

  1. There is a name variable
  2. Current value is "Folder A". Corresponds with the name of the Tree Item.
  3. Double Click. In place add edit text appears. Then Rename it to "Folder B".
  4. Store the new string to name variable
  5. Print name variable. Should be "Folder B"

@bentraje said in How to Override DoubleClick() from the TreeViewFunctions?:

RE: If I omit the Return True

the documentation say :

Returns:

True if the event was handled, otherwise False.

So you double click, it call the double click function. If you return True, cinema4D will think you have handle that double click and will not call the default "rename" function.
If you return False, Cinema4D will think that you couldn't handle it and will launch it's default function to rename the object.

You have to differentiate the data and the UI. It's two thing different with different purpose and functions.

The TreeViewFunction have GetName and SetName functions. This tree purpose is to display hierarchies. That's why it also have those function, InsertUnder, GetNext etc etc.
Those function are here to make the UI understand how to retrieve and handle your data.

When you are using the c4d's object, they come with all those function by default. But that doesn't mean you can't use your own data with there own function.
It's easier to use c4d's object but not mandatory.

I've picked maxime's example and adapt it so you can understand.

It has two columns. Depending on where you click, it will either set a data do "default" or return false and c4d will call the SetName of the TreeViewFunction were you can change your data.

I didn't implemented SetName on my object were i store my data.

I also passed a link of the GUI to the TreeViewFunction so i can refresh it inside the DoubleClickFunction

#More information http://www.plugincafe.com/forum/forum_posts.asp?TID=14102&PID=56287#56287
import c4d

# Be sure to use a unique ID obtained from http://www.plugincafe.com/.
PLUGIN_ID = 1000010 # TEST ID ONLY

# TreeView Column IDs.
ID_CHECKBOX = 1
ID_NAME = 2
ID_OTHER = 3

class TextureObject(object):
    """
    Class which represent a texture, aka an Item in our list
    """
    texturePath = "TexPath"
    otherData = "OtherData"
    _selected = False

    def __init__(self, texturePath):
        self.texturePath = texturePath
        self.otherData += texturePath

    @property
    def IsSelected(self):
        return self._selected

    def Select(self):
        self._selected = True

    def Deselect(self):
        self._selected = False

    def __repr__(self):
        return str(self)

    def __str__(self):
        return self.texturePath


class ListView(c4d.gui.TreeViewFunctions):

    def __init__(self, dlgPointer):
        self.listOfTexture = list() # Store all objects we need to display in this list
        self.dlgPointer = dlgPointer
        # Add some defaults values 
        t1 = TextureObject("T1")
        t2 = TextureObject("T2")
        t3 = TextureObject("T3")
        t4 = TextureObject("T4")

        self.listOfTexture.extend([t1, t2, t3, t4])


    def IsResizeColAllowed(self, root, userdata, lColID):
        return True

    def IsTristate(self, root, userdata):
        return False

    def GetColumnWidth(self, root, userdata, obj, col, area):
        return 80  # All have the same initial width

    def IsMoveColAllowed(self, root, userdata, lColID):
        # The user is allowed to move all columns.
        # TREEVIEW_MOVE_COLUMN must be set in the container of AddCustomGui.
        return True

    def GetFirst(self, root, userdata):
        """
        Return the first element in the hierarchy, or None if there is no element.
        """
        rValue = None if not self.listOfTexture else self.listOfTexture[0]
        return rValue

    def GetDown(self, root, userdata, obj):
        """
        Return a child of a node, since we only want a list, we return None everytime
        """
        return None

    def GetNext(self, root, userdata, obj):
        """
        Returns the next Object to display after arg:'obj'
        """
        rValue = None
        currentObjIndex = self.listOfTexture.index(obj)
        nextIndex = currentObjIndex + 1
        if nextIndex < len(self.listOfTexture):
            rValue = self.listOfTexture[nextIndex]

        return rValue

    def GetPred(self, root, userdata, obj):
        """
        Returns the previous Object to display before arg:'obj'
        """
        rValue = None
        currentObjIndex = self.listOfTexture.index(obj)
        predIndex = currentObjIndex - 1
        if 0 <= predIndex < len(self.listOfTexture):
            rValue = self.listOfTexture[predIndex]

        return rValue

    def GetId(self, root, userdata, obj):
        """
        Return a unique ID for the element in the TreeView.
        """
        return hash(obj)

    def Select(self, root, userdata, obj, mode):
        """
        Called when the user selects an element.
        """
        if mode == c4d.SELECTION_NEW:
            for tex in self.listOfTexture:
                tex.Deselect()
            obj.Select()
        elif mode == c4d.SELECTION_ADD:
            obj.Select()
        elif mode == c4d.SELECTION_SUB:
            obj.Deselect()

    def IsSelected(self, root, userdata, obj):
        """
        Returns: True if *obj* is selected, False if not.
        """
        return obj.IsSelected

    def SetCheck(self, root, userdata, obj, column, checked, msg):
        """
        Called when the user clicks on a checkbox for an object in a
        `c4d.LV_CHECKBOX` column.
        """
        if checked:
            obj.Select()
        else:
            obj.Deselect()

    def IsChecked(self, root, userdata, obj, column):
        """
        Returns: (int): Status of the checkbox in the specified *column* for *obj*.
        """
        if obj.IsSelected:
            return c4d.LV_CHECKBOX_CHECKED | c4d.LV_CHECKBOX_ENABLED
        else:
            return c4d.LV_CHECKBOX_ENABLED
    def SetName(self, root, userdata, obj, str):
        obj.otherData = str


    def GetName(self, root, userdata, obj):
        """
        Returns the name to display for arg:'obj', only called for column of type LV_TREE
        """
        return str(obj) # Or obj.texturePath

    def DrawCell(self, root, userdata, obj, col, drawinfo, bgColor):
        """
        Draw into a Cell, only called for column of type LV_USER
        """
        rgbSelectedColor = c4d.gui.GeUserArea().GetColorRGB(c4d.COLOR_TEXT_SELECTED)
        selectedColor = c4d.Vector(rgbSelectedColor["r"], rgbSelectedColor["g"], rgbSelectedColor["b"]) / 255.0
        txtColor = selectedColor if obj.IsSelected else c4d.Vector(0.2, 0.4, 0.8)
        drawinfo["frame"].DrawSetTextCol(txtColor, drawinfo["bgCol"])

        if col == ID_NAME:
            name = str(obj)
            geUserArea = drawinfo["frame"]
            w = geUserArea.DrawGetTextWidth(name)
            h = geUserArea.DrawGetFontHeight()
            xpos = drawinfo["xpos"]
            ypos = drawinfo["ypos"] + drawinfo["height"]
            drawinfo["frame"].DrawText(name, xpos, ypos - h * 1.1)
            xpos = drawinfo["xpos"]
            ypos = drawinfo["ypos"] + drawinfo["height"]

        if col == ID_OTHER:
            name = obj.otherData
            geUserArea = drawinfo["frame"]
            w = geUserArea.DrawGetTextWidth(name)
            h = geUserArea.DrawGetFontHeight()
            xpos = drawinfo["xpos"]
            ypos = drawinfo["ypos"] + drawinfo["height"]
            drawinfo["frame"].DrawText(name, xpos, ypos - h * 1.1)

    def DoubleClick(self, root, userdata, obj, col, mouseinfo):
        """
        Called when the user double-clicks on an entry in the TreeView.

        Returns:
          (bool): True if the double-click was handled, False if the
            default action should kick in. The default action will invoke
            the rename procedure for the object, causing `SetName()` to be
            called.
        """
        c4d.gui.MessageDialog("You clicked on " + str(obj))
        # if the col 2 is double clicked
        if (col == 2):
            obj.otherData = "default"
            self.dlgPointer._treegui.Refresh()
            return True
        return False

    def DeletePressed(self, root, userdata):
        "Called when a delete event is received."
        for tex in reversed(self.listOfTexture):
            if tex.IsSelected:
                self.listOfTexture.remove(tex)

class TestDialog(c4d.gui.GeDialog):
    _treegui = None # Our CustomGui TreeView
    
    def __init__(self):
        self._listView = ListView(self) # Our Instance of c4d.gui.TreeViewFunctions
        
    def CreateLayout(self):
        # Create the TreeView GUI.
        customgui = c4d.BaseContainer()
        customgui.SetBool(c4d.TREEVIEW_BORDER, c4d.BORDER_THIN_IN)
        customgui.SetBool(c4d.TREEVIEW_HAS_HEADER, True) # True if the tree view may have a header line.
        customgui.SetBool(c4d.TREEVIEW_HIDE_LINES, False) # True if no lines should be drawn.
        customgui.SetBool(c4d.TREEVIEW_MOVE_COLUMN, True) # True if the user can move the columns.
        customgui.SetBool(c4d.TREEVIEW_RESIZE_HEADER, True) # True if the column width can be changed by the user.
        customgui.SetBool(c4d.TREEVIEW_FIXED_LAYOUT, True) # True if all lines have the same height.
        customgui.SetBool(c4d.TREEVIEW_ALTERNATE_BG, True) # Alternate background per line.
        customgui.SetBool(c4d.TREEVIEW_CURSORKEYS, True) # True if cursor keys should be processed.
        customgui.SetBool(c4d.TREEVIEW_NOENTERRENAME, False) # Suppresses the rename popup when the user presses enter.

        self._treegui = self.AddCustomGui( 1000, c4d.CUSTOMGUI_TREEVIEW, "", c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, 300, 300, customgui)
        if not self._treegui:
            print "[ERROR]: Could not create TreeView"
            return False

        self.AddButton(1001, c4d.BFH_CENTER, name="Add")
        return True

    def InitValues(self):
        # Initialize the column layout for the TreeView.
        layout = c4d.BaseContainer()
        layout.SetLong(ID_CHECKBOX, c4d.LV_CHECKBOX)
        layout.SetLong(ID_NAME, c4d.LV_USER)
        layout.SetLong(ID_OTHER, c4d.LV_USER)
        self._treegui.SetLayout(3, layout)

        # Set the header titles.
        self._treegui.SetHeaderText(ID_CHECKBOX, "Check")
        self._treegui.SetHeaderText(ID_NAME, "Name")
        self._treegui.SetHeaderText(ID_OTHER, "Other")
        self._treegui.Refresh()

        # Set TreeViewFunctions instance used by our CUSTOMGUI_TREEVIEW
        self._treegui.SetRoot(self._treegui, self._listView, None)
        return True

    def Command(self, id, msg):
        # Click on button
        if id == 1001:
            # Add data to our DataStructure (ListView)
            newID = len(self._listView.listOfTexture) + 1 
            tex = TextureObject("T{}".format(newID))
            self._listView.listOfTexture.append(tex)

            # Refresh the TreeView
            self._treegui.Refresh()

        return True


def main():
    global dlg
    dlg = TestDialog()
    dlg.Open(c4d.DLG_TYPE_ASYNC, PLUGIN_ID, defaulth=600, defaultw=600)

if __name__ == "__main__":
    main()

let me know if something is not clear. I'm trying to be more generic than just answer your question 🙂

in your problem just do something like

obj.name = "my new name"

Cheers,
Manuel

MAXON SDK Specialist

MAXON Registered Developer

This post is deleted!

@m_magalhaes

Thanks for the response.
The confusion is entirely mine.

I was confused because I initially thought the GetName and SetName is only for typical C4D objects.
but there is actually a separate GetName and SetName functions for the TreeView objects.

I was able to retrieve the new string by just this code:

    def SetName(self,root, userdata, obj, name):

        print name # new name when you hit enter

I can now use the name variable to use in my separate renaming function (i.e. rename a folder for which the TreeView was based on).

Thanks!