669 lines
16 KiB
Python
Executable File
669 lines
16 KiB
Python
Executable File
"""
|
|
Dialogs. Not just for FontLab any more.
|
|
Cross-platform and cross-application compatible. Mostly anyway.
|
|
(Not all dialogs work on PCs outside of FontLab. Some dialogs are for FontLab only. Sorry.)
|
|
|
|
Mac and FontLab implementation written by the RoboFab development team.
|
|
PC implementation by Eigi Eigendorf and is (C)2002 Eigi Eigendorf.
|
|
"""
|
|
|
|
#to add:
|
|
#W version of TwoFields
|
|
|
|
|
|
import os
|
|
import sys
|
|
#from robofab import RoboFabError
|
|
RoboFabError = "RoboFabError"
|
|
|
|
MAC = False
|
|
PC = False
|
|
haveMacfs = False
|
|
|
|
if sys.platform in ('mac', 'darwin'):
|
|
MAC = True
|
|
elif os.name == 'nt':
|
|
PC = True
|
|
else:
|
|
raise RoboFabError, "dialogs.py only supports Mac and PC platforms."
|
|
pyVersion = sys.version_info[:3]
|
|
|
|
inFontLab = False
|
|
try:
|
|
from FL import *
|
|
inFontLab = True
|
|
except ImportError: pass
|
|
|
|
if MAC:
|
|
if pyVersion < (2, 3, 0):
|
|
import macfs
|
|
haveMacfs = True
|
|
elif PC and not inFontLab:
|
|
from win32com.shell import shell
|
|
import win32ui
|
|
import win32con
|
|
|
|
|
|
def _raisePlatformError(dialog):
|
|
"""error raiser"""
|
|
if MAC:
|
|
p = 'Macintosh'
|
|
elif PC:
|
|
p = 'PC'
|
|
raise RoboFabError, "%s is not currently available on the %s platform"%(dialog, p)
|
|
|
|
|
|
class _FontLabDialogOneList:
|
|
"""A one list dialog for FontLab. This class should not be called directly. Use the OneList function."""
|
|
|
|
def __init__(self, list, message, title='RoboFab'):
|
|
self.message = message
|
|
self.selected = None
|
|
self.list = list
|
|
self.d = Dialog(self)
|
|
self.d.size = Point(250, 250)
|
|
self.d.title = title
|
|
self.d.Center()
|
|
self.d.AddControl(LISTCONTROL, Rect(12, 30, 238, 190), "list", STYLE_LIST, self.message)
|
|
self.list_index = 0
|
|
|
|
def Run(self):
|
|
return self.d.Run()
|
|
|
|
def on_cancel(self, code):
|
|
self.selected = None
|
|
|
|
def on_ok(self, code):
|
|
self.d.GetValue('list')
|
|
self.selected = self.list_index
|
|
|
|
|
|
class _FontLabDialogSearchList:
|
|
"""A dialog for searching through a list. It contains a text field and a results list FontLab. This class should not be called directly. Use the SearchList function."""
|
|
|
|
def __init__(self, aList, message, title="RoboFab"):
|
|
self.d = Dialog(self)
|
|
self.d.size = Point(250, 290)
|
|
self.d.title = title
|
|
self.d.Center()
|
|
|
|
self.message = message
|
|
self._fullContent = aList
|
|
self.possibleHits = list(aList)
|
|
self.possibleHits.sort()
|
|
self.possibleHits_index = 0
|
|
self.entryField = ""
|
|
self.selected = None
|
|
|
|
self.d.AddControl(STATICCONTROL, Rect(10, 10, 240, 30), "message", STYLE_LABEL, message)
|
|
self.d.AddControl(EDITCONTROL, Rect(10, 30, 240, aAUTO), "entryField", STYLE_EDIT, "")
|
|
self.d.AddControl(LISTCONTROL, Rect(12, 60, 238, 230), "possibleHits", STYLE_LIST, "")
|
|
|
|
|
|
def run(self):
|
|
self.d.Run()
|
|
|
|
def on_entryField(self, code):
|
|
self.d.GetValue("entryField")
|
|
entry = self.entryField
|
|
count = len(entry)
|
|
possibleHits = [
|
|
i for i in self._fullContent
|
|
if len(i) >= count
|
|
and i[:count] == entry
|
|
]
|
|
possibleHits.sort()
|
|
self.possibleHits = possibleHits
|
|
self.possibleHits_index = 0
|
|
self.d.PutValue("possibleHits")
|
|
|
|
def on_ok(self, code):
|
|
self.d.GetValue("possibleHits")
|
|
sel = self.possibleHits_index
|
|
if sel == -1:
|
|
self.selected = None
|
|
else:
|
|
self.selected = self.possibleHits[sel]
|
|
|
|
def on_cancel(self, code):
|
|
self.selected = None
|
|
|
|
|
|
class _FontLabDialogTwoFields:
|
|
"""A two field dialog for FontLab. This class should not be called directly. Use the TwoFields function."""
|
|
|
|
def __init__(self, title_1, value_1, title_2, value_2, title='RoboFab'):
|
|
self.d = Dialog(self)
|
|
self.d.size = Point(200, 125)
|
|
self.d.title = title
|
|
self.d.Center()
|
|
self.d.AddControl(EDITCONTROL, Rect(120, 10, aIDENT2, aAUTO), "v1edit", STYLE_EDIT, title_1)
|
|
self.d.AddControl(EDITCONTROL, Rect(120, 40, aIDENT2, aAUTO), "v2edit", STYLE_EDIT, title_2)
|
|
self.v1edit = value_1
|
|
self.v2edit = value_2
|
|
|
|
def Run(self):
|
|
return self.d.Run()
|
|
|
|
def on_cancel(self, code):
|
|
self.v1edit = None
|
|
self.v2edit = None
|
|
|
|
def on_ok(self, code):
|
|
self.d.GetValue("v1edit")
|
|
self.d.GetValue("v2edit")
|
|
self.v1 = self.v1edit
|
|
self.v2 = self.v2edit
|
|
|
|
class _FontLabDialogTwoChecks:
|
|
"""A two check box dialog for FontLab. This class should not be called directly. Use the TwoChecks function."""
|
|
|
|
def __init__(self, title_1, title_2, value1=1, value2=1, title='RoboFab'):
|
|
self.d = Dialog(self)
|
|
self.d.size = Point(200, 105)
|
|
self.d.title = title
|
|
self.d.Center()
|
|
self.d.AddControl(CHECKBOXCONTROL, Rect(10, 10, aIDENT2, aAUTO), "check1", STYLE_CHECKBOX, title_1)
|
|
self.d.AddControl(CHECKBOXCONTROL, Rect(10, 30, aIDENT2, aAUTO), "check2", STYLE_CHECKBOX, title_2)
|
|
self.check1 = value1
|
|
self.check2 = value2
|
|
|
|
def Run(self):
|
|
return self.d.Run()
|
|
|
|
def on_cancel(self, code):
|
|
self.check1 = None
|
|
self.check2 = None
|
|
|
|
def on_ok(self, code):
|
|
self.d.GetValue("check1")
|
|
self.d.GetValue("check2")
|
|
|
|
|
|
class _FontLabDialogAskString:
|
|
"""A one simple string prompt dialog for FontLab. This class should not be called directly. Use the GetString function."""
|
|
|
|
def __init__(self, prompt, value, title='RoboFab'):
|
|
self.d = Dialog(self)
|
|
self.d.size = Point(350, 130)
|
|
self.d.title = title
|
|
self.d.Center()
|
|
self.d.AddControl(STATICCONTROL, Rect(aIDENT, aIDENT, aIDENT, aAUTO), "label", STYLE_LABEL, prompt)
|
|
self.d.AddControl(EDITCONTROL, Rect(aIDENT, 40, aIDENT, aAUTO), "value", STYLE_EDIT, '')
|
|
self.value=value
|
|
|
|
def Run(self):
|
|
return self.d.Run()
|
|
|
|
def on_cancel(self, code):
|
|
self.value = None
|
|
|
|
def on_ok(self, code):
|
|
self.d.GetValue("value")
|
|
|
|
class _FontLabDialogMessage:
|
|
"""A simple message dialog for FontLab. This class should not be called directly. Use the SimpleMessage function."""
|
|
|
|
def __init__(self, message, title='RoboFab'):
|
|
self.d = Dialog(self)
|
|
self.d.size = Point(350, 130)
|
|
self.d.title = title
|
|
self.d.Center()
|
|
self.d.AddControl(STATICCONTROL, Rect(aIDENT, aIDENT, aIDENT, 80), "label", STYLE_LABEL, message)
|
|
|
|
def Run(self):
|
|
return self.d.Run()
|
|
|
|
class _FontLabDialogGetYesNoCancel:
|
|
"""A yes no cancel prompt dialog for FontLab. This class should not be called directly. Use the YesNoCancel function."""
|
|
|
|
def __init__(self, message, title='RoboFab'):
|
|
self.d = Dialog(self)
|
|
self.d.size = Point(350, 130)
|
|
self.d.title = title
|
|
self.d.Center()
|
|
self.d.ok = 'Yes'
|
|
self.d.AddControl(STATICCONTROL, Rect(aIDENT, aIDENT, aIDENT, 80), "label", STYLE_LABEL, message)
|
|
self.d.AddControl(BUTTONCONTROL, Rect(100, 95, 172, 115), "button", STYLE_BUTTON, "No")
|
|
self.value = 0
|
|
|
|
def Run(self):
|
|
return self.d.Run()
|
|
|
|
def on_ok(self, code):
|
|
self.value = 1
|
|
|
|
def on_cancel(self, code):
|
|
self.value = -1
|
|
|
|
def on_button(self, code):
|
|
self.value = 0
|
|
self.d.End()
|
|
|
|
|
|
class _MacOneList:
|
|
"""A one list dialog for Macintosh. This class should not be called directly. Use the OneList function."""
|
|
|
|
def __init__(self, list, message='Make a selection'):
|
|
import W
|
|
self.list = list
|
|
self.selected = None
|
|
self.w = W.ModalDialog((200, 240))
|
|
self.w.message = W.TextBox((10, 10, -10, 30), message)
|
|
self.w.list = W.List((10, 35, -10, -50), list)
|
|
self.w.l = W.HorizontalLine((10, -40, -10, 1), 1)
|
|
self.w.cancel = W.Button((10, -30, 87, -10), 'Cancel', self.cancel)
|
|
self.w.ok = W.Button((102, -30, 88, -10), 'OK', self.ok)
|
|
self.w.setdefaultbutton(self.w.ok)
|
|
self.w.bind('cmd.', self.w.cancel.push)
|
|
self.w.open()
|
|
|
|
def ok(self):
|
|
if len(self.w.list.getselection()) == 1:
|
|
self.selected = self.w.list.getselection()[0]
|
|
self.w.close()
|
|
|
|
def cancel(self):
|
|
self.selected = None
|
|
self.w.close()
|
|
|
|
class _MacTwoChecks:
|
|
|
|
def __init__(self, title_1, title_2, value1=1, value2=1, title='RoboFab'):
|
|
import W
|
|
self.check1 = value1
|
|
self.check2 = value2
|
|
self.w = W.ModalDialog((200, 100))
|
|
self.w.check1 = W.CheckBox((10, 10, -10, 16), title_1, value=value1)
|
|
self.w.check2 = W.CheckBox((10, 35, -10, 16), title_2, value=value2)
|
|
self.w.l = W.HorizontalLine((10, 60, -10, 1), 1)
|
|
self.w.cancel = W.Button((10, 70, 85, 20), 'Cancel', self.cancel)
|
|
self.w.ok = W.Button((105, 70, 85, 20), 'OK', self.ok)
|
|
self.w.setdefaultbutton(self.w.ok)
|
|
self.w.bind('cmd.', self.w.cancel.push)
|
|
self.w.open()
|
|
|
|
def ok(self):
|
|
self.check1 = self.w.check1.get()
|
|
self.check2 = self.w.check2.get()
|
|
self.w.close()
|
|
|
|
def cancel(self):
|
|
self.check1 = None
|
|
self.check2 = None
|
|
self.w.close()
|
|
|
|
|
|
class ProgressBar:
|
|
def __init__(self, title='RoboFab...', ticks=0, label=''):
|
|
"""
|
|
A progress bar.
|
|
Availability: FontLab, Mac
|
|
"""
|
|
self._tickValue = 1
|
|
|
|
if inFontLab:
|
|
fl.BeginProgress(title, ticks)
|
|
elif MAC:
|
|
import EasyDialogs
|
|
self._bar = EasyDialogs.ProgressBar(title, maxval=ticks, label=label)
|
|
else:
|
|
_raisePlatformError('Progress')
|
|
|
|
def getCurrentTick(self):
|
|
return self._tickValue
|
|
|
|
|
|
def tick(self, tickValue=None):
|
|
"""
|
|
Tick the progress bar.
|
|
Availability: FontLab, Mac
|
|
"""
|
|
if not tickValue:
|
|
tickValue = self._tickValue
|
|
|
|
if inFontLab:
|
|
fl.TickProgress(tickValue)
|
|
elif MAC:
|
|
self._bar.set(tickValue)
|
|
else:
|
|
pass
|
|
|
|
self._tickValue = tickValue + 1
|
|
|
|
def label(self, label):
|
|
"""
|
|
Set the label on the progress bar.
|
|
Availability: Mac
|
|
"""
|
|
if inFontLab:
|
|
pass
|
|
elif MAC:
|
|
self._bar.label(label)
|
|
else:
|
|
pass
|
|
|
|
|
|
def close(self):
|
|
"""
|
|
Close the progressbar.
|
|
Availability: FontLab, Mac
|
|
"""
|
|
if inFontLab:
|
|
fl.EndProgress()
|
|
elif MAC:
|
|
del self._bar
|
|
else:
|
|
pass
|
|
|
|
|
|
def SelectFont(message="Select a font:", title='RoboFab'):
|
|
"""
|
|
Returns font instance if there is one, otherwise it returns None.
|
|
Availability: FontLab
|
|
"""
|
|
from robofab.world import RFont
|
|
if inFontLab:
|
|
list = []
|
|
for i in range(fl.count):
|
|
list.append(fl[i].full_name)
|
|
name = OneList(list, message, title)
|
|
if name is None:
|
|
return None
|
|
else:
|
|
return RFont(fl[list.index(name)])
|
|
else:
|
|
_raisePlatformError('SelectFont')
|
|
|
|
def SelectGlyph(font, message="Select a glyph:", title='RoboFab'):
|
|
"""
|
|
Returns glyph instance if there is one, otherwise it returns None.
|
|
Availability: FontLab
|
|
"""
|
|
from fontTools.misc.textTools import caselessSort
|
|
|
|
if inFontLab:
|
|
tl = font.keys()
|
|
list = caselessSort(tl)
|
|
glyphname = OneList(list, message, title)
|
|
if glyphname is None:
|
|
return None
|
|
else:
|
|
return font[glyphname]
|
|
else:
|
|
_raisePlatformError('SelectGlyph')
|
|
|
|
def FindGlyph(font, message="Search for a glyph:", title='RoboFab'):
|
|
"""
|
|
Returns glyph instance if there is one, otherwise it returns None.
|
|
Availability: FontLab
|
|
"""
|
|
|
|
if inFontLab:
|
|
glyphname = SearchList(font.keys(), message, title)
|
|
if glyphname is None:
|
|
return None
|
|
else:
|
|
return font[glyphname]
|
|
else:
|
|
_raisePlatformError('SelectGlyph')
|
|
|
|
def OneList(list, message="Select an item:", title='RoboFab'):
|
|
"""
|
|
Returns selected item, otherwise it returns None.
|
|
Availability: FontLab, Macintosh
|
|
"""
|
|
if inFontLab:
|
|
ol = _FontLabDialogOneList(list, message)
|
|
ol.Run()
|
|
selected = ol.selected
|
|
if selected is None:
|
|
return None
|
|
else:
|
|
try:
|
|
return list[selected]
|
|
except:
|
|
return None
|
|
elif MAC:
|
|
d = _MacOneList(list, message)
|
|
sel = d.selected
|
|
if sel is None:
|
|
return None
|
|
else:
|
|
return list[sel]
|
|
elif PC:
|
|
_raisePlatformError('OneList')
|
|
|
|
def SearchList(list, message="Select an item:", title='RoboFab'):
|
|
"""
|
|
Returns selected item, otherwise it returns None.
|
|
Availability: FontLab
|
|
"""
|
|
if inFontLab:
|
|
sl = _FontLabDialogSearchList(list, message, title)
|
|
sl.run()
|
|
selected = sl.selected
|
|
if selected is None:
|
|
return None
|
|
else:
|
|
return selected
|
|
else:
|
|
_raisePlatformError('SearchList')
|
|
|
|
def TwoFields(title_1="One:", value_1="0", title_2="Two:", value_2="0", title='RoboFab'):
|
|
"""
|
|
Returns (value 1, value 2).
|
|
Availability: FontLab
|
|
"""
|
|
if inFontLab:
|
|
tf = _FontLabDialogTwoFields(title_1, value_1, title_2, value_2, title)
|
|
tf.Run()
|
|
try:
|
|
v1 = tf.v1
|
|
v2 = tf.v2
|
|
return (v1, v2)
|
|
except:
|
|
return None
|
|
else:
|
|
_raisePlatformError('TwoFields')
|
|
|
|
def TwoChecks(title_1="One", title_2="Two", value1=1, value2=1, title='RoboFab'):
|
|
"""
|
|
Returns check value:
|
|
1 if check box 1 is checked
|
|
2 if check box 2 is checked
|
|
3 if both are checked
|
|
0 if neither are checked
|
|
None if cancel is clicked.
|
|
|
|
Availability: FontLab, Macintosh
|
|
"""
|
|
tc = None
|
|
if inFontLab:
|
|
tc = _FontLabDialogTwoChecks(title_1, title_2, value1, value2, title)
|
|
tc.Run()
|
|
elif MAC:
|
|
tc = _MacTwoChecks(title_1, title_2, value1, value2, title)
|
|
elif PC:
|
|
_raisePlatformError('TwoChecks')
|
|
c1 = tc.check1
|
|
c2 = tc.check2
|
|
if c1 == 1 and c2 == 0:
|
|
return 1
|
|
elif c1 == 0 and c2 == 1:
|
|
return 2
|
|
elif c1 == 1 and c2 == 1:
|
|
return 3
|
|
elif c1 == 0 and c2 == 0:
|
|
return 0
|
|
else:
|
|
return None
|
|
|
|
def Message(message, title='RoboFab'):
|
|
"""
|
|
A simple message dialog.
|
|
Availability: FontLab, Macintosh
|
|
"""
|
|
if inFontLab:
|
|
_FontLabDialogMessage(message, title).Run()
|
|
elif MAC:
|
|
import EasyDialogs
|
|
EasyDialogs.Message(message)
|
|
elif PC:
|
|
_raisePlatformError('Message')
|
|
|
|
def AskString(prompt, value='', title='RoboFab'):
|
|
"""
|
|
Returns entered string.
|
|
Availability: FontLab, Macintosh
|
|
"""
|
|
if inFontLab:
|
|
askString = _FontLabDialogAskString(prompt, value, title)
|
|
askString.Run()
|
|
v = askString.value
|
|
if v is None:
|
|
return None
|
|
else:
|
|
return v
|
|
elif MAC:
|
|
import EasyDialogs
|
|
askString = EasyDialogs.AskString(prompt)
|
|
if askString is None:
|
|
return None
|
|
if len(askString) == 0:
|
|
return None
|
|
else:
|
|
return askString
|
|
elif PC:
|
|
_raisePlatformError('GetString')
|
|
|
|
def AskYesNoCancel(prompt, title='RoboFab', default=0):
|
|
"""
|
|
Returns 1 for 'Yes', 0 for 'No' and -1 for 'Cancel'.
|
|
Availability: FontLab, Macintosh
|
|
("default" argument only available on Macintosh)
|
|
"""
|
|
if inFontLab:
|
|
gync = _FontLabDialogGetYesNoCancel(prompt, title)
|
|
gync.Run()
|
|
v = gync.value
|
|
return v
|
|
elif MAC:
|
|
import EasyDialogs
|
|
gync = EasyDialogs.AskYesNoCancel(prompt, default=default)
|
|
return gync
|
|
|
|
elif PC:
|
|
_raisePlatformError('GetYesNoCancel')
|
|
|
|
def GetFile(message=None):
|
|
"""
|
|
Select file dialog. Returns path if one is selected. Otherwise it returns None.
|
|
Availability: FontLab, Macintosh, PC
|
|
"""
|
|
path = None
|
|
if MAC:
|
|
if haveMacfs:
|
|
fss, ok = macfs.PromptGetFile(message)
|
|
if ok:
|
|
path = fss.as_pathname()
|
|
else:
|
|
from robofab.interface.mac.getFileOrFolder import GetFile
|
|
path = GetFile(message)
|
|
elif PC:
|
|
if inFontLab:
|
|
if not message:
|
|
message = ''
|
|
path = fl.GetFileName(1, message, '', '')
|
|
else:
|
|
openFlags = win32con.OFN_FILEMUSTEXIST|win32con.OFN_EXPLORER
|
|
mode_open = 1
|
|
myDialog = win32ui.CreateFileDialog(mode_open,None,None,openFlags)
|
|
myDialog.SetOFNTitle(message)
|
|
is_OK = myDialog.DoModal()
|
|
if is_OK == 1:
|
|
path = myDialog.GetPathName()
|
|
else:
|
|
_raisePlatformError('GetFile')
|
|
return path
|
|
|
|
def GetFolder(message=None):
|
|
"""
|
|
Select folder dialog. Returns path if one is selected. Otherwise it returns None.
|
|
Availability: FontLab, Macintosh, PC
|
|
"""
|
|
path = None
|
|
if MAC:
|
|
if haveMacfs:
|
|
fss, ok = macfs.GetDirectory(message)
|
|
if ok:
|
|
path = fss.as_pathname()
|
|
else:
|
|
from robofab.interface.mac.getFileOrFolder import GetFileOrFolder
|
|
# This _also_ allows the user to select _files_, but given the
|
|
# package/folder dichotomy, I think we have no other choice.
|
|
path = GetFileOrFolder(message)
|
|
elif PC:
|
|
if inFontLab:
|
|
if not message:
|
|
message = ''
|
|
path = fl.GetPathName('', message)
|
|
else:
|
|
myTuple = shell.SHBrowseForFolder(0, None, message, 64)
|
|
try:
|
|
path = shell.SHGetPathFromIDList(myTuple[0])
|
|
except:
|
|
pass
|
|
else:
|
|
_raisePlatformError('GetFile')
|
|
return path
|
|
|
|
GetDirectory = GetFolder
|
|
|
|
def PutFile(message=None, defaultName=None):
|
|
"""
|
|
Save file dialog. Returns path if one is entered. Otherwise it returns None.
|
|
Availability: FontLab, Macintosh, PC
|
|
"""
|
|
path = None
|
|
if MAC:
|
|
if haveMacfs:
|
|
fss, ok = macfs.StandardPutFile(message, defaultName)
|
|
if ok:
|
|
path = fss.as_pathname()
|
|
else:
|
|
import EasyDialogs
|
|
path = EasyDialogs.AskFileForSave(message, savedFileName=defaultName)
|
|
elif PC:
|
|
if inFontLab:
|
|
if not message:
|
|
message = ''
|
|
if not defaultName:
|
|
defaultName = ''
|
|
path = fl.GetFileName(0, message, defaultName, '')
|
|
else:
|
|
openFlags = win32con.OFN_OVERWRITEPROMPT|win32con.OFN_EXPLORER
|
|
mode_save = 0
|
|
myDialog = win32ui.CreateFileDialog(mode_save, None, defaultName, openFlags)
|
|
myDialog.SetOFNTitle(message)
|
|
is_OK = myDialog.DoModal()
|
|
if is_OK == 1:
|
|
path = myDialog.GetPathName()
|
|
else:
|
|
_raisePlatformError('GetFile')
|
|
return path
|
|
|
|
if __name__=='__main__':
|
|
#print TwoFields()
|
|
print TwoChecks('hello', 'world', 1, 0, 'ugh')
|
|
print OneList(['a', 'b', 'c'], 'hello world')
|
|
Message('hello world')
|
|
print AskString('hello world')
|
|
print AskYesNoCancel('hello world')
|
|
b = ProgressBar('hello', 50, 'world')
|
|
for i in range(50):
|
|
if i == 25:
|
|
b.label('ugh.')
|
|
b.tick(i)
|
|
b.close()
|