How to add a custom plugin for Left Sidebar in Edit View

Is any info on adding a custom plugin for the new G4 left sidebar…maybe a plugin template?

Also, I am wondering if it could be possible to integrate something like the Text Preview panel directly into the left sidebar?

No template or guide yet, but here is a draft that should help you get started:

You need a class that is a subclass of NSViewController and conforms to GSEditViewSidebarControllerProtocol. Then, call addEditViewLeftSidebarController with your class as the argument on GSCallbackHandler.

See the GSEditViewSidebarControllerProtocol docs for details. Feel free to ask if you get stuck.

You might want to get in touch with @HugoJ; he was been experimenting in this direction, but I don’t know if it’s ready for show just yet.

1 Like

Hi @gor.jious, I’ve built a handful of these for some of my plugins. Florian’s summary covers the whole API. Beyond that there are a few non-obvious traps, and the snippet handles them.

Here’s a recap from Claude with a short snippet, along with the issues it ran into along the way. Hope it helps :slight_smile:

Code snippet
# encoding: utf-8
import objc
from AppKit import (NSImage, NSMakeRect, NSTextField, NSView, NSViewController,
                    NSViewMinYMargin, NSViewWidthSizable)
from GlyphsApp.plugins import GeneralPlugin

TAB_TITLE = "My Sidebar"
GSCallbackHandler = objc.lookUpClass("GSCallbackHandler")
# Glyphs checks conformsToProtocol:, and a Python class only conforms
# if the protocol is declared when the class is created.
SidebarProtocol = objc.protocolNamed("GSEditViewSidebarControllerProtocol")


class MySidebarController(NSViewController, protocols=[SidebarProtocol]):

    @classmethod
    def title(cls):
        return TAB_TITLE  # tab label AND tab identifier: keep it unique

    @classmethod
    def tabBarImage(cls):
        return NSImage.imageWithSystemSymbolName_accessibilityDescription_("star", TAB_TITLE)

    def loadView(self):
        # Required: Glyphs creates the controller without a nib.
        view = NSView.alloc().initWithFrame_(NSMakeRect(0, 0, 240, 400))
        label = NSTextField.labelWithString_("Hello from the sidebar")
        label.setFrame_(NSMakeRect(10, 370, 220, 20))
        label.setAutoresizingMask_(NSViewWidthSizable | NSViewMinYMargin)
        view.addSubview_(label)
        self.setView_(view)

    def setController_(self, controller):
        # Glyphs hands you the Edit tab's controller here.
        self.setTitle_(TAB_TITLE)  # instance title = tooltip on the tab icon
        self._controller = controller

    def controller(self):
        return getattr(self, "_controller", None)


class MySidebar(GeneralPlugin):

    @objc.python_method
    def settings(self):
        self.name = TAB_TITLE

    @objc.python_method
    def start(self):
        GSCallbackHandler.addEditViewLeftSidebarController_(MySidebarController)

Things I ran into
  • protocols=[…] is mandatory. Without it Glyphs logs “invalid left Sidebar Controller, Has to inherit from NSViewController”. The message is misleading: inheritance is fine, the protocol check is what failed.
  • loadView is mandatory too. Otherwise NSViewController looks for a nib named after the class and raises.
  • Register at launch. Each window snapshots the list of tabs when it opens, so a tab registered later only appears in windows opened after that.
  • One controller instance per Edit tab, not per window.
  • No fixed width. The user can drag the sidebar wider or narrower (minimum about 140 pt), so use autoresizing masks or Auto Layout, not a hardcoded width.
  • Don’t paint an opaque background on your root view. The sidebar sits on a glass material, and a solid fill looks like a flat slab.
  • Tables: an NSTableView (vanilla List) inside a sidebar defaults to the Inset style, with rounded capsule rows. Call tableView.setStyle_(4) (Plain) for flat rows.
  • Keyboard focus: clicking a control in your tab takes focus away from the canvas, and it doesn’t come back on its own. If your tool relies on key events, call window.makeFirstResponder_(self.controller().graphicView()) after the action.
  • Plugin reload during development fails because the ObjC class name is already registered. Restart Glyphs, or guard the class definition with objc.lookUpClass.

On your Text Preview question: yes. The tab is just an NSView, so anything you can build in a palette or a window fits in there. self.controller() is the Edit tab’s controller (activeLayer(), graphicView()), so you can follow what’s being edited. I have a live text preview running in the sidebar this way, and it works well. Do your heavy drawing only while the tab is visible, using viewDidAppear / viewDidDisappear.


2 Likes

Not necessarily. You can specify a Nib by using initWithNibName:bundle: from the init of the class. I am not sure how that is done in Python using PyObjc, but it should be possible.

Thank you Florian and @HugoJ the snippet and insights were incredibly helpful. Still got more things to tweak and figure out but it’s working!

One more thing @FlorianPircher can you provide some guidance on how is the Text Preview panel is able to handle so much text without it being bogged down?

For large amounts of text, you want to export to a binary font, read that as an NSFont (or CTFont) and use that as the font of an NSTextView. That export step is not trivial and highly task-specific, so there is no single solution that Glyphs provides as an easy API.