Skip to content

Instantly share code, notes, and snippets.

@okay-type
Last active February 2, 2022 23:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save okay-type/496b81ab5a836672174b866267fdc8ad to your computer and use it in GitHub Desktop.
Save okay-type/496b81ab5a836672174b866267fdc8ad to your computer and use it in GitHub Desktop.
Simple Robofont interface to controlling a list of open ufos and their UI windows.
from mojo.subscriber import Subscriber, WindowController
from mojo.subscriber import registerRoboFontSubscriber
from vanilla import Window, List, CheckBoxListCell, Group, Button
from AppKit import NSDragOperationMove, NSDragOperationCopy, NSFilenamesPboardType
from mojo.UI import AskYesNoCancel, OpenSpaceCenter, OpenFontInfoSheet, OpenGlyphWindow
from os import path as ospath
from mojo.roboFont import AllFonts, OpenFont, FontsList
from defconAppKit.controls.fontInfoView import FontInfoView
from mojo.UI import OutputWindow
OutputWindow().clear()
'''
Tool's Purpose:
- see a list of all open ufos, regardless of ui-visiblity, in a specific order
- control the ui windows of those fonts (glyph overview, spacecenter, info)
- be a lighter-weight way to work on a multi-style family
- make this list available in other tools as an alternative to ALLFonts
Icons Key:
👍 placeholder : intended to be a way to flag an active/inactive state for other tools to use
ℹ️ font info : currently not working because fontinfo exists as a sheet
🔤 spacecenter
🔡 font overview
💾 save state : current not working - need to set up subscriber to watch for font change events
Other Buttons:
Sort
- sorts the list using robofont's magic sortBy
- works on the entire list or just the selected ufos
- pressing a second time will reverse the sorting
- should keep italics second
Reverse
- reverses the order of the list
- should keep italics second
Save All
- saves all ufos in the list
LATEST UPDATES:
2022-02-02
solved the bug where closing the font overview window removed the ufo from the list
fontinfo working with defcon ui (robofont's ui requires a sheet, which doesn't make sense here)
TO DO:
set up font change notifications
- font changed --> saved checkbox = False
- font saved --> saved checkbox = True
create EveryUFO() as start up variable
- use that instead of self.ufolist
- get the list info from other tools
'''
class simple_ufo_ui_controller(Subscriber, WindowController):
debug = True
def build(self):
self.fonts_ui = []
self.fonts_no_ui = []
self.ufolist = []
for f in AllFonts():
font = {}
if f.info.familyName and f.info.styleName:
font['fontname'] = f.info.familyName + ' ' + f.info.styleName
else:
font['fontname'] = 'No font name'
font['ufo'] = f
font['active'] = True
font['info'] = False
font['spacecenter'] = False
font['overview'] = True
font['saved'] = True
self.ufolist.append(font)
self.fonts_ui.append(f)
self.spacecenter_text = 'Hamburgers'
self.opened_windows = {}
self.w = Window((333, 333))
self.w.lists = Group((0, 0, 0, 0))
columnDescriptions = [
dict(title='UFO', key='ufo', width=0),
dict(title='🛸', key='fontname', editable=False),
dict(title='👍', key='active', cell=CheckBoxListCell(), width=22),
dict(title='ℹ️', key='info', cell=CheckBoxListCell(), width=22),
dict(title='🔤', key='spacecenter', cell=CheckBoxListCell(), width=22),
dict(title='🔡', key='overview', cell=CheckBoxListCell(), width=22),
dict(title='💾', key='unsaved', cell=CheckBoxListCell(), width=22),
]
self.w.lists.fontlist = List(
(0, 0, -0, -22),
items=self.ufolist,
columnDescriptions=columnDescriptions,
showColumnTitles=True,
enableDelete=True,
editCallback=self.fontlist_edit,
allowsMultipleSelection=True,
drawFocusRing=False,
dragSettings=dict(
type='genericListPboardType',
callback=self.fontlist_drag
),
selfDropSettings=dict(
type='genericListPboardType',
operation=NSDragOperationMove,
callback=self.fontlist_drop
),
otherApplicationDropSettings=dict(
type=NSFilenamesPboardType,
operation=NSDragOperationCopy,
callback=self.fontlist_add_noUI_ufo
),
)
self.w.lists.fontlist_sort = Button(
(0, -22, 111, 22),
'Sort',
sizeStyle='mini',
callback=self.fontlist_sort,
)
self.w.lists.fontlist_reverse = Button(
(111, -22, 111, 22),
'Reverse',
sizeStyle='mini',
callback=self.fontlist_reverse,
)
self.w.lists.fontlist_save_all = Button(
(222, -22, 111, 22),
'Save All',
sizeStyle='mini',
callback=self.fontlist_save_all,
)
self.w.lists.fontlist_save_all.enable(False)
self.w.bind('should close', self.window_should_close)
self.w.open()
def fontlist_drag(self, sender, indexes):
return indexes
#
def fontlist_drop(self, sender, dropInfo):
isProposal = dropInfo['isProposal']
if not isProposal:
indexes = [int(i) for i in sorted(dropInfo['data'])]
indexes.sort()
rowIndex = dropInfo['rowIndex']
items = sender.get()
toMove = [items[index] for index in indexes]
for index in reversed(indexes):
del items[index]
rowIndex -= len([index for index in indexes if index < rowIndex])
for font in toMove:
items.insert(rowIndex, font)
rowIndex += 1
sender.set(items)
return True
def fontlist_add_noUI_ufo(self, sender, dropInfo):
supportedFontFileFormats = ['ufo', 'ttf', 'otf', 'woff', 'woff2']
isProposal = dropInfo['isProposal']
paths = dropInfo['data']
paths = [path for path in paths if path not in sender.get()]
paths = [path for path in paths if ospath.splitext(path)[-1].lower() in supportedFontFileFormats or ospath.isdir(path)]
if not paths:
return False
if not isProposal:
rowIndex = dropInfo['rowIndex']
items = sender.get()
for i, path in enumerate(paths):
self.hold = True
skip = False
for ufo in self.fonts_no_ui:
if path == ufo.path:
skip = True
if skip is False:
f = OpenFont(path, showInterface=False)
font = {}
font['ufo'] = f
font['fontname'] = f.info.familyName + ' ' + f.info.styleName
font['active'] = True
font['info'] = False
font['spacecenter'] = False
font['overview'] = False
font['saved'] = True
items.insert(rowIndex, font)
rowIndex += 1
self.fonts_no_ui.append(f)
if i >= len(paths)-1:
self.hold = False
sender.set(items)
return True
def fontlist_edit(self, sender):
# sorry, tracking list changes is annoying vague
# as far as i can tell, a change happens and it's up to you to figure out what it was
# a smarter approach would be appreaciated
# get the updated list
ufolist_new = sender.get()
# if the list has changed (sometimes it doesnt becuase there were duplicate events) make a clean copy
updatedufo_dict = []
if ufolist_new != self.ufolist:
for ufoitem in ufolist_new:
updatedufo = {}
updatedufo['ufo'] = ufoitem['ufo']
updatedufo['fontname'] = ufoitem['fontname']
updatedufo['active'] = ufoitem['active']
updatedufo['info'] = ufoitem['info']
updatedufo['spacecenter'] = ufoitem['spacecenter']
updatedufo['overview'] = ufoitem['overview']
updatedufo['saved'] = ufoitem['saved']
updatedufo_dict.append(updatedufo)
# check if a no_ui font was manually deleted from the list
# if so, close it and remove it from the no_ui list
if len(ufolist_new) != len(self.ufolist):
for font_no_ui in self.fonts_no_ui:
if not any(item['ufo'] == font_no_ui for item in ufolist_new):
print('Closing', font_no_ui.info.familyName + ' ' + font_no_ui.info.styleName)
self.fonts_no_ui.remove(font_no_ui)
font_no_ui.close()
# now look to see if there were any checkbox changes
# here list length shouldn't have changed, but the values should have
if len(ufolist_new) == len(self.ufolist) and ufolist_new != self.ufolist:
# annoyingly step through the values to find the change
# compare the ufolist_new values to the older self.ufolist values
for i, ufoitem in enumerate(ufolist_new):
ufo = ufoitem['ufo']
if ufoitem['info'] != self.ufolist[i]['info']:
if ufoitem['info'] is True:
self.fontlist_open_ufo_info(ufoitem, i)
if ufoitem['info'] is False:
self.fontlist_close_ufo_info(ufoitem, i)
if ufoitem['spacecenter'] != self.ufolist[i]['spacecenter']:
if ufoitem['spacecenter'] is True:
self.fontlist_open_ufo_spacecenter(ufo, i)
if ufoitem['spacecenter'] is False:
self.fontlist_close_ufo_spacecenter(ufo, i)
if ufoitem['overview'] != self.ufolist[i]['overview']:
if ufoitem['overview'] is True:
self.fontlist_open_ufo_ui(ufo)
if ufoitem['overview'] is False:
self.fontlist_close_ufo_ui(ufo)
if ufoitem['saved'] != self.ufolist[i]['saved']:
if ufoitem['saved'] is True:
self.fontlist_save_ufo(ufoitem)
# update self.ufolist values for next time
if updatedufo_dict != []:
self.ufolist = updatedufo_dict
def fontDocumentDidOpenNew(self, notification):
f = notification['font']
font = {}
if f.info.familyName and f.info.styleName:
font['fontname'] = f.info.familyName + ' ' + f.info.styleName
else:
font['fontname'] = 'No font name'
font['ufo'] = f
font['active'] = True
font['info'] = False
font['spacecenter'] = False
font['overview'] = True
font['saved'] = False
self.w.lists.fontlist.append(font)
self.fonts_ui.append(f)
def fontDocumentDidOpen(self, notification):
f = notification['font']
if f not in self.fonts_ui:
font = {}
font['ufo'] = f
font['fontname'] = f.info.familyName + ' ' + f.info.styleName
font['active'] = True
font['info'] = False
font['spacecenter'] = False
font['overview'] = True
font['saved'] = True
self.w.lists.fontlist.append(font)
self.fonts_ui.append(f)
def fontDocumentWillClose(self, notification):
f = notification['font']
if f in self.fonts_ui:
for i, x in enumerate(self.w.lists.fontlist):
if f == x['ufo']:
del self.w.lists.fontlist[i]
self.fonts_ui.remove(f)
def window_should_close(self, sender):
if self.w.lists.fontlist_save_all.isEnabled() is True:
q = AskYesNoCancel('You have unsaved changes. Want to save these ufos before closing?')
if q == -1:
return False
elif q == 1:
self.fontlist_save_all()
return True
else:
print('Closing without saving. Good luck!')
return True
def windowWillClose(self, sender):
for font in self.fonts_no_ui:
print('Closing', font.info.familyName + ' ' + font.info.styleName)
font.close()
# font list buttons
def fontlist_sort(self, sender):
ufolist_new = self.w.lists.fontlist.get()
ufolist_selection = self.w.lists.fontlist.getSelection()
if len(ufolist_selection) < 2:
tempufos = []
for f in ufolist_new:
ufo = f['ufo']
tempufos.append(ufo)
if ufo.info.openTypeOS2WidthClass is None:
print('FYI:', ufo.info.familyName + ' ' + ufo.info.styleName, 'openType OS2 Width Class is None')
if ufo.info.openTypeOS2WeightClass is None:
print('FYI:', ufo.info.familyName + ' ' + ufo.info.styleName, 'openType OS2 Weight Class is None')
tempufos = FontsList(tempufos)
tempufos.sortBy(('familyName', 'widthValue', 'weightValue', 'isRoman',))
fonts = sorted(ufolist_new, key=lambda x: tempufos.index(x['ufo']))
# if resort, put italics first and reverse to ... essentially reverses the weight order
if fonts == ufolist_new:
tempufos.sortBy(('familyName', 'widthValue', 'weightValue', 'isItalic',))
tempufos = list(reversed(tempufos))
fonts = sorted(ufolist_new, key=lambda x: tempufos.index(x['ufo']))
if fonts != ufolist_new:
ufolist_new = fonts
self.w.lists.fontlist.set(ufolist_new)
else:
tempufos = []
otherufos = []
for i, f in enumerate(ufolist_new):
ufo = f['ufo']
if i in ufolist_selection:
tempufos.append(ufo)
otherufos.append(None)
else:
otherufos.append(ufo)
sortedtempufos = FontsList(tempufos)
sortedtempufos.sortBy(('familyName', 'widthValue', 'weightValue', 'isRoman',))
if sortedtempufos == tempufos:
sortedtempufos.sortBy(('familyName', 'widthValue', 'weightValue', 'isItalic',))
sortedtempufos = list(reversed(sortedtempufos))
n = 0
assembledufos = []
for x in otherufos:
if x is None:
assembledufos.append(sortedtempufos[n])
n += 1
else:
assembledufos.append(x)
fonts = sorted(ufolist_new, key=lambda x: assembledufos.index(x['ufo']))
if fonts != ufolist_new:
ufolist_new = fonts
self.w.lists.fontlist.set(ufolist_new)
def fontlist_reverse(self, sender):
ufolist_new = list(reversed(self.w.lists.fontlist.get()))
self.w.lists.fontlist.set(ufolist_new)
def fontlist_save_all(self, sender=None):
for ufoitem in self.ufolist:
ufoitem['ufo'].save()
def fontlist_save_ufo(self, ufoitem):
ufoitem['ufo'].save()
# watch for changes, when they occur unflag saved
# ufoitem['saved'] = False
# open info
def fontlist_open_ufo_info(self, ufoitem, i):
info = Window((500, 522), ufoitem['fontname']+' Info')
info.b = Button((0, -22, -0, 22), 'Open Info Sheet')
info.bind('should close', self.info_window_should_close)
ufo = ufoitem['ufo']
info.i = FontInfoView((0, 0, 500, -22), ufo.asDefcon())
info.open()
# infosheet = OpenFontInfoSheet(ufo, info)
# is there a way to turn a sheet into a window?
self.opened_windows[info] = i
def fontlist_close_ufo_info(self, ufoitem, i):
for info, index in self.opened_windows.items():
if index == i:
del self.opened_windows[info]
info.close()
break
def info_window_should_close(self, sender):
if sender in self.opened_windows.keys():
i = self.opened_windows[sender]
del self.opened_windows[sender]
ufolist_updated = self.w.lists.fontlist.get()
ufolist_updated[i]['info'] = False
self.w.lists.fontlist.set(ufolist_updated)
return True
# open spacecenter
def fontlist_open_ufo_spacecenter(self, ufo, i):
sc = OpenSpaceCenter(ufo, newWindow=False)
sc.setRaw(self.spacecenter_text)
self.opened_windows[sc] = i
def fontlist_close_ufo_spacecenter(self, ufo, i):
for sc, index in self.opened_windows.items():
if index == i:
print(index, i, sc)
del self.opened_windows[sc]
sc.getNSView().window().windowController().close()
break
def spaceCenterWillClose(self, subscriberevent):
sc = subscriberevent['spaceCenter']
if sc in self.opened_windows.keys():
i = self.opened_windows[sc]
del self.opened_windows[sc]
ufolist_updated = self.w.lists.fontlist.get()
ufolist_updated[i]['spacecenter'] = False
self.w.lists.fontlist.set(ufolist_updated)
# open overview
def fontlist_open_ufo_ui(self, ufo):
self.fonts_ui.append(ufo)
if ufo in self.fonts_no_ui:
self.fonts_no_ui.remove(ufo)
ufo.openInterface()
w = ufo.document().getMainWindow()
w.bind('should close', self.ui_window_should_close)
def fontlist_close_ufo_ui(self, ufo):
self.fonts_no_ui.append(ufo)
if ufo in self.fonts_ui:
self.fonts_ui.remove(ufo)
w = ufo.document().getMainWindow()
w.close()
def ui_window_should_close(self, sender):
title = sender.getTitle()
for ufo in self.fonts_ui:
if title in ufo.path:
for x in self.w.lists.fontlist:
if ufo == x['ufo']:
x['overview'] = False
return False
# track font change
def fontDidChange(self, notificaiton):
self.w.lists.fontlist_save_all.enable(True)
if __name__ == '__main__':
registerRoboFontSubscriber(simple_ufo_ui_controller)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment