Imagine that your UFO sources are stored in subfolders of a main folder, with one folder for each family:

myLibrary ├── myFamily1 │ ├── Bold.ufo │ ├── Condensed.ufo │ ├── Italic.ufo │ └── Regular.ufo ├── myFamily2 │ └── … └── myFamily3 └── …

This example shows how to create a simple tool to browse through the family folders and open the selected fonts:

The code could be extended to do more than just opening the selected fonts – for example setting font infos, generating fonts etc.

Some things to notice in this example:

layout variables
The buttons and lists are placed and sized using layout variables (padding, button height, column widths, etc). This makes it easier to change dimensions during development.
Get Folder dialog
  1. When the user clicks on the get root folder… button, a getFolder dialog is opened.
  2. After the folder has been selected, a list of all its subfolders is displayed in the left column.
list selection

Items selection works differently in each list:

  • The list of families allows only one item to be selected at once.
  • The list of fonts allows selection of multiple items (default List behavior).
show fonts in folder
As the selection in the left column changes, the right column is updated to show a list of UFOs in the selected family folder.

Here’s the code:

import os
from vanilla import FloatingWindow, Button, List
from vanilla.dialogs import getFolder

class FontLibraryBrowser(object):

    # root folder for families
    rootFolder = None

    # current family
    family = None

    def __init__(self):

        # UI layout attributes
        padding = 10
        buttonHeight = 20
        columnFamilies = 120
        columnFonts = 200
        listHeight = 240
        width = columnFamilies + columnFonts + padding*3
        height = listHeight + buttonHeight*2 + padding*4

        self.w = FloatingWindow((width, height), "myFontLibrary")

        # a button to get the root folder
        x = y = padding
        self.w.getFolderButton = Button(
                (x, y, -padding, buttonHeight),
                "get root folder...",
                callback=self.getFolderCallback)

        # list of folders in root folder
        y += buttonHeight + padding
        self.w.families = List(
                (x, y, columnFamilies, listHeight),
                [],
                selectionCallback=self.getFontsCallback,
                allowsMultipleSelection=False)

        # list of fonts in folder
        x += columnFamilies + padding
        self.w.fonts = List(
                (x, y, columnFonts, listHeight),
                [])

        # open selected fonts
        x = padding
        y += listHeight + padding
        self.w.openFontsButton = Button(
                (x, y, -padding, buttonHeight),
                "open selected fonts",
                callback=self.openFontsCallback)

        # open the dialog
        self.w.open()

    # ---------
    # callbacks
    # ---------

    def getFolderCallback(self, sender):
        '''Get the main folder where all family folders live.'''

        # open a dialog to select the root folder
        folder = getFolder()

        # no folder selected
        if folder is None:
            return

        # get folder
        else:
            self.rootFolder = folder[0]

        # update families list
        self.updateFamiliesList()

    def getFontsCallback(self, sender):
        '''Get UFOs in current family folder.'''

        # get families
        families = sender.get()

        if not len(families):
            return

        # get selected family
        selection = sender.getSelection()

        if not len(selection):
            return

        self.family = families[selection[0]]

        # list all ufos in family folder
        self.updateFontsList()

    def openFontsCallback(self, sender):
        '''Open selected fonts in current family.'''

        # get fonts
        allFonts = self.w.fonts.get()

        if not len(allFonts):
            return

        # get selection
        fontsSelection = self.w.fonts.getSelection()

        if not len(fontsSelection):
            return

        # get selected fonts
        selectedFonts = [f for i, f in enumerate(allFonts) if i in fontsSelection]

        # open selected fonts
        familyFolder = os.path.join(self.rootFolder, self.family)
        for font in selectedFonts:
            # get ufo path for font
            ufoPath = os.path.join(familyFolder, '%s.ufo' % font)
            # open ufo
            print('opening %s/%s.ufo...' % (self.family, font))
            OpenFont(ufoPath)

    # ---------
    # functions
    # ---------

    def updateFamiliesList(self):
        '''Update the list of families in the UI.'''

        # get subfolders
        subFolders = []
        for f in os.listdir(self.rootFolder):
            fileOrFolder = os.path.join(self.rootFolder, f)
            if os.path.isdir(fileOrFolder):
                subFolders.append(f)

        # update families list
        self.w.families.set(subFolders)

    def updateFontsList(self):
        '''Update the list of fonts in the UI.'''

        # get folder for family
        familyFolder = os.path.join(self.rootFolder, self.family)

        # get ufos in folder
        ufos = [os.path.splitext(f)[0] for f in os.listdir(familyFolder) if os.path.splitext(f)[-1] == '.ufo']

        # update fonts list
        self.w.fonts.set(ufos)

# ---------------
# open the dialog
# ---------------

FontLibraryBrowser()
Last edited on 21/02/2019