SOLVED additional custom functionality in “Display...” menu. Possible? If yes, How?



  • Hey guys
    Is there an easy way to add custom checkboxes/items into Display... menu in Glyph Window that would interact with custom methods/objects?

    c6aa0aa7-eccc-44e1-a10a-77ef408a7a0c-image.png

    I want to trigger with the checkboxes in this menu scripts that will draw some stuff with the drawing observers.

    Also, I have some ideas to add some additional subviews with canvas groups, that will show some interesting things. I also would like to trigger them with those checkboxes.

    I really Hope I made my question clear and I hope it is possible.

    PS
    if this menu is sacred, or something, is there option to create additional one on the same line? next to it?



  • added to the docs as an example: Custom display menu in the Glyph Window – thanks!



  • Cool, Thanks Guys! Will try first thing tomorrow


  • admin

    to get the checkboxes right you need to dive a bit into vanilla/AppKit objects...

    from mojo.UI import CurrentGlyphWindow
    from vanilla import PopUpButton
    
    w = CurrentGlyphWindow()
    
    if w:
        bar = w.getGlyphStatusBar()
        if hasattr(bar, "myButton"):
            del bar.myButton
        
        def callback(sender):
            # toggle the state
            item = sender.getNSPopUpButton().selectedItem()
            item.setState_(not item.state())
            print(sender.get())
            
        bar.myButton = PopUpButton((120, 0, 85, 16), ["hello world…", 'A', 'B', 'C'], sizeStyle="small", callback=callback)
        bar.myButton.getNSPopUpButton().setPullsDown_(True)
        bar.myButton.getNSPopUpButton().setBordered_(False)
        
        for menuItem in bar.myButton.getNSPopUpButton().itemArray()[1:]:
            # set the state
            menuItem.setState_(True)
    


  • I imagine what is the best way to save the settings of my custom menu. So if I close RF, then after opening it will look the same (will have the same checkboxes checked). I've been thinking about storing data in .plist or .ini file next to the script. Is it a good solution?

    you can use setExtensionDefault and getExtensionDefault for that:

    from mojo.extensions import setExtensionDefault, getExtensionDefault
    key = "com.rafalbuchner.toolName"
    myMenuItems = [('A', True), ('B', True), ('C', False)]
    setExtensionDefault(key, myMenuItems)
    print(getExtensionDefault(key))
    


  • here’s how you can make your custom menu look the same as the Display menu:

    Screen Shot 2019-02-07 at 08.30.20_.png

    from mojo.UI import CurrentGlyphWindow
    from vanilla import PopUpButton
    
    w = CurrentGlyphWindow()
    
    if w:
        bar = w.getGlyphStatusBar()
        if hasattr(bar, "myButton"):
            del bar.myButton
        bar.myButton = PopUpButton((120, 0, 85, 16), ["hello world…", 'A', 'B', 'C'], sizeStyle="small")
        bar.myButton.getNSPopUpButton().setPullsDown_(True)
        bar.myButton.getNSPopUpButton().setBordered_(False)
    


  • I'm reopening this thread, because I thought, that maybe anyone of you would like to share the opinion about this script.

    The script that I'm sharing with you is my solution for creating simple, generic custom display functionality.

    I would like to know how to make the menu look more like the "Display...".

    The stuff that I would like to do (but I don't know how to make it work):

    1. "Display..." has separate place next to each item's title for the check label
    2. "Display..."'s main title is a little bit smaller
    3. "Display..."'s main label doesn't have this white background with rounded corners
    4. I imagine what is the best way to save the settings of my custom menu. So if I close RF, then after opening it will look the same (will have the same checkboxes checked). I've been thinking about storing data in .plist or .ini file next to the script. Is it a good solution?

    By the way, Feel free to use it anyhow you want. The example of how to use the script is at the bottom of it. Everything under if __name__ == "__main__": are the classes that build this thing.

    from mojo.UI import CurrentGlyphWindow
    from mojo.events import addObserver, removeObserver
    import vanilla 
    from vanilla.vanillaBase import _reverseSizeStyleMap
    from AppKit import NSMenuItem
    
    class CustomMenuItem:
        """This object is made to interact with CustomDisplayOptions"""
        def __init__(self, title, observer, callback):
            self.title = title # title in the menu
            self.observer = observer
            self.callback = callback
                    
        def addObservers(self):
            addObserver(self, "callback", self.observer)
            
        def removeObservers(self):
            removeObserver(self, self.observer)
    
            
    class CustomDisplayOptions:
        """it adds addtional slot with pupup menu that works 
        similar to "Display…" menu in CurrentGlyphWindow"""
        def __init__(self, window, customDisplayMenuItems):
            
            self.window = window
            self.customDisplayMenuItems = customDisplayMenuItems
            self.bar = self.window.getGlyphStatusBar()
            self.initCustomMenuItems()
            
            if hasattr(self.bar, "displayMenu"):
                del self.bar.displayMenu
    
            self.bar.displayMenu = MyActionButton( (-75, 2, 60, 16),  self.items, sizeStyle="mini")
            self.bar.displayMenu.setTitle("Show...")
            
        def initCustomMenuItems(self):
            itemObjs = []
            if isinstance(self.customDisplayMenuItems[0],dict):
                # If the CustomDisplayOptions was list of dict as customDisplayMenuItems
                for menuItemDescritption in self.customDisplayMenuItems:
                    title, observer, callback = menuItemDescritption["title"],menuItemDescritption["observer"],menuItemDescritption["callback"]
                    itemObjs += [CustomMenuItem(title, observer, callback)]
            else:
                # If the CustomDisplayOptions was list of CustomMenuItem objects as customDisplayMenuItems
                itemObjs = self.customDisplayMenuItems
                
            self.items = [ dict(title=menuItem.title, callback=self.checkboxCallback) for menuItem in itemObjs ]
            self.itemObjsDict = {menuItem.title:menuItem for menuItem in itemObjs}
            self.checkboxValue = {item["title"]:False for item in self.items}
            
        def removeObservers(self):
            for itemTitle in self.itemObjsDict:
                self.itemObjsDict[itemTitle].removeObservers()
            
        def checkboxCallback(self, sender):
            # check box functionality
            itemTitle = sender.title()
            check = "✓ "
            if check in itemTitle:
                itemTitle = itemTitle.replace(check,"")
                
            
            if sender.title() == itemTitle:
                sender.setTitle_(check+itemTitle)
                self.checkboxValue[itemTitle] = True
                self.itemObjsDict[itemTitle].addObservers()
            else:
                sender.setTitle_(itemTitle)
                self.checkboxValue[itemTitle] = False
                self.itemObjsDict[itemTitle].removeObservers()
            
                          
            
    class MyActionButton(vanilla.ActionButton):
        # Custom version without gearwheel
        def __init__(self,posSize, items, sizeStyle="regular", bordered=True):
             super(MyActionButton, self).__init__(posSize, items, sizeStyle="regular", bordered=True)
        
        def getFirstItem(self):
            sizeStyle = _reverseSizeStyleMap[self._nsObject.cell().controlSize()]
            firstActionItem = NSMenuItem.alloc().init()
            firstActionItem.setTitle_("")
            return firstActionItem            
           
    class CustomDisplayMenuForCurrentGlyphWindow:
        """Environment that triggers objects described above"""
        def test(self):
            self.w = vanilla.FloatingWindow((100,100,100,100),"TEST")
            self.w.open()
            self.w.bind("close",self.testwinClosed)
        def testwinClosed(self,sender):
            removeObserver(self,"glyphWindowDidOpen")
            removeObserver(self,"glyphWindowWillClose")
            
        def __init__(self, customDisplayMenuItems):
            self.test()
            self.customDisplayMenuItems = customDisplayMenuItems
            addObserver(self, "glyphWindowDidOpen_Observer","glyphWindowDidOpen")
            addObserver(self, "glyphWindowWillClose_Observer","glyphWindowWillClose")
            
        def glyphWindowDidOpen_Observer(self, info):
            if not hasattr(self, "displayOpt"):
                self.displayOpt = CustomDisplayOptions(info["window"], self.customDisplayMenuItems)
            
        def glyphWindowWillClose_Observer(self, info):
            if hasattr(self, "displayOpt"):
                self.displayOpt.removeObservers()
                delattr(self, "displayOpt")
    
    
        
    if __name__ == "__main__":
        ### the example 
        import mojo.drawingTools as dt
        
        # creating actions for the displaying
        def drawRedCirce(sender):
            dt.fill(1,0,0)
            dt.oval(0,0,100,100)
        drawRedCircle = drawRedCirce # if I won't do it, it throws error. I don't know how to go around that
        
        def drawBlueRect(sender):
            dt.fill(0,0,1)
            dt.rect(0,0,100,100) 
        drawBlueRect = drawBlueRect  # if I won't do it, it throws error. I don't know how to go around that
        
        # Creating list of special CustomMenuItems objects
        customDisplayMenuItems = [
            dict(title="red circle", observer="draw", callback=drawRedCircle),
            dict(title="blue rect", observer="draw", callback=drawBlueRect),
            ]
            
        CustomDisplayMenuForCurrentGlyphWindow(customDisplayMenuItems)
    


  • @frederik
    I think I figured it out, I will do it with the action popup button

    from vanilla import *
    class ActionPopUpButtonDemo(object):
        def __init__(self):
            self.w = Window((100, 40))
            items = [
                    dict(title="first", callback=self.firstCallback),
                    dict(title="second", callback=self.secondCallback),
                    dict(title="third", items=[
                            dict(title="sub first", callback=self.subFirstCallback)
                        ])
                ]
            self.w.actionPopUpButton = ActionButton((10, 10, 30, 20),
                                  items,
                                  )
            self.w.open()
        def firstCallback(self, sender):
            print("first")
        def secondCallback(self, sender):
            print("second")
        def subFirstCallback(self, sender):
            print("sub first")
    ActionPopUpButtonDemo()
    


  • Thanks, @frederik!
    I promise, the last question (lately I'm a real pain :D ): is there an easy way to create popup menu with similar functionality to the display menu?

    What I mean is:
    it is basically the list of the checkboxes that appear after you press the title "Display" which never changes.
    I've been thinking of creating the button that will open a new window, but it wouldn't look that good


  • admin

    The "Display.." menu is not so easily accessible...

    Since RF3.2 the statusbar is available to set data or adding other views.

    from mojo.UI import CurrentGlyphWindow
    import vanilla
    
    
    w = CurrentGlyphWindow()
    bar = w.getGlyphStatusBar()
    
    # set info in the statusbar
    bar.set(["hello", "world"], fadeOut=True, warning=True)
    
    # add an other vanilla control
    if hasattr(bar, "myButton"):
        del bar.myButton
    bar.myButton = vanilla.Button((130, 2, 50, 16), "hit me", sizeStyle="mini")