Export plugin development: Listener to run Python function whenever dialog is loaded

Hi all,

I am writing a custom export plugin using the File Format template:

https://github.com/schriftgestalt/GlyphsSDK/tree/master/Python%20Templates/File%20Format

The checkboxes all have listeners (connections) to run a particular action (Python function) when they are updated, e.g.:

But I would like to pre-fill some of these checkboxes depending on the font being exported by adding a listener to the export dialog that is activated whenever the dialog is opened (and not on start, as the plugin is loaded whenever Glyphs is started - and no font is yet selected).

I have tried adding a connection in the same way to the customView tag, but this did not work. Can someone familiar with Apple UI programming help me achieve this?

Thanks!

1 Like

//Edit: This post is not relevant anymore – see further comments

Hi @tamirhassan,
If I understand correctly:
you want to edit your plugin’s UI elements everytime you open the export dialogue.

I have quick hacky solution, with pyobjc objects, which will help you achieve that.
I think, I could also later try to work on solution where you would be able to create your own NSView with vanilla objects instead of creating xib / nib files.

In the attachment you will find the template with some additional example code in plugin.py file, that allows you to call some custom stuff when the dialog is being opened.

Let me go into the details:

For this example, I added new text line to the original template’s xib file (“Family Name:”).
This TextField will be used to display current font.familyName attribute.
image

I added following lines to the original code:
<on line 22>

import AppKit

that is straight forward import of objc AppKit

<on lines 29 to 44>

class ExportWindowDelegate(AppKit.NSObject):
	def initWithFormatPluginObject_(self, object):
		self.plugin = object
		return self

	def windowDidBecomeKey_(self, event):
		# looking for the NSView that we created with xib file
		# in the topLevelObjects attribute
		for obj in self.plugin.topLevelObjects:
			if isinstance(obj, AppKit.NSView):
				# iterating through the elements of this NSView
				# (in this example I'm looking for NSTextFields with title )
				for sub_obj in obj.subviews():
					if isinstance(sub_obj, AppKit.NSTextField):
						if sub_obj.stringValue().startswith("Family Name: "):
							sub_obj.setStringValue_("Family Name: {}".format(Glyphs.font.familyName))

This is the most important part of the edits that I’ve made.
This is delegate that I’m later setting for exportDialog, which will allow you to do stuff, whenever this dialog will be opened.

Your code should go into the windowDidBecomeKey_ method, preferably after for sub_obj in obj.subviews(): – this is where all the edits of UI elements take place.

In my case, I looked for TextField object, which string value starts with "Family Name: " and adding current family name to it (lines 43, 44).

<on lines 60 to 62>

		exportWindow = Glyphs.delegate().exportWindow()
		self.windowDelegate = ExportWindowDelegate.alloc().initWithFormatPluginObject_(self)
		exportWindow.setDelegate_(self.windowDelegate)
  1. line 60 – im defining variable, that will contain exportWindow obj (I’m stealing it from Glyphs’ delegate object)
  2. line 61 – I’m initialising ExportWindowDelegate object by passing PluginClassName to it. Thanks to this passing, we are able to iterate through the UI elements in the ExportWindowDelegate class
  3. line 62 – I’m linking ExportWindowDelegate to exportWindow as a delegate

Hope that helps.
If you have any questions, feel free to ask them.

Here is this example:
PluginTest.glyphsFileFormat.zip (89.6 KB)

1 Like

Please don’t mess with the delegate of objects that don’t belong to you.

Use a NSViewController for the view and it has this callback: - (void)viewWillAppear; that will let you do stuff just before the view is shown.

Use a NSViewController for the view and it has this callback: - (void)viewWillAppear; that will let you do stuff just before the view is shown.

Ok, I can redo the example later tomorrow. @tamirhassan, the example will be super similar to the one I just sent you.

The easiest is to implement setFont_() that is called before the dialog is shown with the font instance that is about to be exported.

def setFont_(font):
    self._font = font
    # setup UI here

Or, specially if you are using a .xib for the UI and the checkboxes represent direct properties of the font, you can use bindings to connect the UI to those properties.

2 Likes

@tamirhassan,
Here is better solution for you, with implemented setFont_. In this example I deleted the previously added lines. Instead I added method setFont_, which would be safer if you wouldn’t mess with (there is comment saying about it). This method calls another new method, viewDidLoad, which returns NSView object. this is where you do your work:

<lines 54…59>

	@objc.python_method
	def viewDidLoaded(self, view):
		for sub_obj in view.subviews():
			if isinstance(sub_obj, AppKit.NSTextField):
				if sub_obj.stringValue().startswith("Family Name: "):
					sub_obj.setStringValue_("Family Name: {}".format(Glyphs.font.familyName))

Here is basically the same functionality that I implemented in the last example plugin. But I used a bit different, safer approach. Have fun
PluginTest.glyphsFileFormat.zip (89.9 KB)

1 Like

There are some detours in the code. I cleaned it up. There are outlets that are meant to be able to get to a view.
PluginTest.glyphsFileFormat GS.zip (91.2 KB)

2 Likes

Hi, after moving to Glyphs 3 this solution doesn’t seem to work any more as the setFont_ method doesn’t seem to be run when the dialog is shown. Any idea what I can do to get it working in Glyphs 3? Has it been renamed?

The setFont_ method should still be called. Can you show the full code?

Thanks, Georg. I can’t post the code here in the forum, but I’ve already sent it to Rafal, who should be taking a look. I’ll forward you the email.

The problem was that the setFont_ method had a @objc.python_method decorator. Those should only be used for methods that are only meant to be used from within python.