Skip to content

Instantly share code, notes, and snippets.

@typesupply
Created April 13, 2021 12:49
Show Gist options
  • Save typesupply/b028fdb502b4a6d1c3d30433f38114bc to your computer and use it in GitHub Desktop.
Save typesupply/b028fdb502b4a6d1c3d30433f38114bc to your computer and use it in GitHub Desktop.
vanilla.py version 0.0000000000000000000000000000000001, March 25, 2005 2:35 AM
"""
To do:
- auto alignment bugs?
- get rid of the frigging background override in List. UGH!
- implement all Window methods from W
- add all (or at least all feasable) init methods form W
- lots of other stuff.
- zzzzzzzzzzzz.
"""
from AppKit import *
from Foundation import *
def _frameMagic(parentFrame, objFrame):
#
# NOTE!
#
# this was adapted from Wbase.Widget._calcbounds.
# the doc there states that this algorithm was written by
# Peter Kreins and Petr van Blokland and that parts of the
# algo are applied for patents by Ericsson, Sweden.
#
(pL, pB), (pW, pH) = parentFrame
(oL, oT), (oW, oH) = objFrame
if oW < 0:
# right is relative to the parent width
oW = pW + oW - oL
if oL < 0:
# left is relative to the parent width
oL = pW + oL
if oH < 0:
# bottom is relative to parent height
oH = pH + oH - oT
if oT < 0:
# top is relative to the parent height
oT = pH + oT
return (oL, oT), (oW, oH)
def _calcFrame(parentFrame, objFrame):
(pL, pB), (pW, pH) = parentFrame
(oL, oT), (oW, oH) = _frameMagic(parentFrame, objFrame)
oB = pH - oT - oH
return (oL, oB), (oW, oH)
class Window(object):
def __init__(self, posSize, title="", minSize=None, maxSize=None, textured=False):
mask = NSTitledWindowMask
mask = mask + NSClosableWindowMask
mask = mask + NSMiniaturizableWindowMask
if minSize or maxSize:
mask = mask + NSResizableWindowMask
if textured:
mask = mask + NSTexturedBackgroundWindowMask
# start the window
## too magical?
if len(posSize) == 2:
l = t = 100
w, h = posSize
else:
l, t, w, h = posSize
frame = _calcFrame(NSScreen.mainScreen().visibleFrame(), ((l, t), (w, h)))
self._window = NSWindow.alloc().initWithContentRect_styleMask_backing_defer_(
frame, mask, NSBackingStoreBuffered, False)
if minSize is not None:
self._window.setMinSize_(minSize)
if maxSize is not None:
self._window.setMaxSize_(maxSize)
# set the basic attributes
self._window.setTitle_(title)
# make it a normal floating window
self._window.setLevel_(NSNormalWindowLevel)
self._windowController = NSWindowController.alloc().init()
self._windowController.setWindow_(self._window)
self._contentView = self._window.contentView()
def __setattr__(self, attr, value):
if isinstance(value, VanillaBaseObject):
# position the object relative to the top, left of the window
winFrame = self._contentView.frame()
value._setFrame(winFrame)
# add the object to the window
self._contentView.addSubview_(value._nsObject)
super(Window, self).__setattr__(attr, value)
def __delattr__(self, attr):
raise NotImplementedError, "need to ask Just about this"
def open(self, parentWindow=None):
if parentWindow is not None:
NSApp().beginSheet_modalForWindow_modalDelegate_didEndSelector_contextInfo_(
self._window, parentWindow._window, None, None, 0)
else:
self._windowController.showWindow_(self._window)
def close(self):
if self._window.isSheet():
NSApp().endSheet_(self._window)
self._window.orderOut_(self._windowController)
else:
self._windowController.close()
class VanillaCallbackWrapper(NSObject):
def initWithCallback_(self, callback):
self = self.init()
self.callback = callback
return self
def action_(self, arg):
self.callback()
class VanillaBaseObject(object):
def __init__(self, nsCls, posSize, callback=None):
#self._nsObject = nsCls.alloc().initWithFrame_(((l, t), (w, h)))
self._nsObject = nsCls.alloc().init()
self._posSize = posSize
self._setCallback(callback)
self._setAutosizingFromPosSize()
def _setCallback(self, callback):
if callback is not None:
self._target = VanillaCallbackWrapper.alloc().initWithCallback_(callback)
self._nsObject.setTarget_(self._target)
self._nsObject.setAction_("action:")
def _setAutosizingFromPosSize(self):
l, t, w, h = self._posSize
widthFixed = w > 0
leftFixed = l > 0
rightFixed = w < 0
heightFixed = h > 0
topFixed = t > 0
bottomFixed = h < 0
self._setAutosizing(widthFixed=widthFixed, heightFixed=heightFixed,
leftFixed=leftFixed, rightFixed=rightFixed,
topFixed=topFixed, bottomFixed=bottomFixed,
)
def _setAutosizing(self, widthFixed=True, heightFixed=True,
leftFixed=True, rightFixed=True,
topFixed=True, bottomFixed=True):
mask = None
if not widthFixed:
mask = NSViewWidthSizable
if not heightFixed:
if mask is None:
mask = NSViewHeightSizable
else:
mask = mask + NSViewHeightSizable
if not leftFixed:
if mask is None:
mask = NSViewMinXMargin
else:
mask = mask + NSViewMinXMargin
if not rightFixed:
if mask is None:
mask = NSViewMaxXMargin
else:
mask = mask + NSViewMaxXMargin
if not bottomFixed:
if mask is None:
mask = NSViewMinYMargin
else:
mask = mask + NSViewMinYMargin
if not topFixed:
if mask is None:
mask = NSViewMaxYMargin
else:
mask = mask + NSViewMaxYMargin
if mask is None:
mask = NSViewNotSizable
self._nsObject.setAutoresizingMask_(mask)
def _setFrame(self, parentFrame):
l, t, w, h = self._posSize
frame = _calcFrame(parentFrame, ((l, t), (w, h)))
self._nsObject.setFrame_(frame)
def enable(self, onOff):
self._nsObject.setEnabled_(onOff)
def show(self, onOff):
hidden = onOff == False
self._nsObject.setHidden_(hidden)
##
## Text
##
class TextBox(VanillaBaseObject):
def __init__(self, posSize, text=""):
super(TextBox, self).__init__(NSTextField, posSize)
self._nsObject.setStringValue_(text)
self._nsObject.setDrawsBackground_(False)
self._nsObject.setBezeled_(False)
self._nsObject.setSelectable_(False)
def get(self):
return self._nsObject.stringValue()
def set(self, value):
self._nsObject.setStringValue_(value)
class EditText(VanillaBaseObject):
def __init__(self, posSize, text="", callback=None, readOnly=False):
super(EditText, self).__init__(NSTextField, posSize, callback=callback)
self._nsObject.setStringValue_(text)
self._nsObject.setDrawsBackground_(True)
self._nsObject.setBezeled_(True)
selectable = readOnly == False
self._nsObject.setSelectable_(selectable)
def get(self):
return self._nsObject.stringValue()
def set(self, value):
self._nsObject.setStringValue_(value)
class TextEditor(VanillaBaseObject):
def __init__(self, posSize, text="", callback=None, readOnly=False):
super(TextEditor, self).__init__(NSTextView, posSize, callback=callback)
self._nsObject.setString_(text)
def get(self):
return self._nsObject.stringValue()
def set(self, value):
self._nsObject.setString_(value)
##
## Controls
##
class VanillaBaseButton(VanillaBaseObject):
def setTitle(self, title):
self._nsObject.setTitle_(title)
def getTitle(self):
return self._nsObject.getTitle()
def set(self, value):
raise NotImplementedError
def get(self):
raise NotImplementedError
class Button(VanillaBaseButton):
def __init__(self, posSize, title="Button", callback=None):
super(Button, self).__init__(NSButton, posSize, callback=callback)
self._nsObject.setTitle_(title)
self._nsObject.setBezelStyle_(NSRoundedBezelStyle)
class CheckBox(VanillaBaseButton):
def __init__(self, posSize, title="Checkbox", callback=None, value=False):
super(CheckBox, self).__init__(NSButton, posSize, callback=callback)
self._nsObject.setTitle_(title)
self._nsObject.setButtonType_(NSSwitchButton)
def set(self, value):
self._nsObject.setState_(value)
def get(self):
return self._nsObject.state()
##
## Lists
##
class VanillaListDataSourceAndDelegate(NSObject):
def initWithItems_(self, items):
self = self.init()
self.setItems_(items)
self.callback = None
return self
def setItems_(self, items):
self.items = list(items)
def numberOfRowsInTableView_(self, aTableView):
return len(self.items)
def tableView_objectValueForTableColumn_row_(self, aTableView, aTableColumn, rowIndex):
return self.items[rowIndex]
def tableView_shouldEditTableColumn_row_(self, aTableView, aTableColumn, rowIndex):
return False
def tableViewSelectionDidChange_(self, notification):
if self.callback is not None:
self.callback()
class List(VanillaBaseObject):
# XXX!
"""
api idea:
if the user wants more than one column, then the items list needs
to contain dicts with keys that match the column titles. the deleate can
then easily get the correct info.
"""
def __init__(self, posSize, items=[], columnTitles=[], showColumnTitles=True, callback=None):
# don't use super to establish the nsObject
# because we will need several of them here
self._posSize = posSize
self._nsObject = NSScrollView.alloc().init()
self._nsObject.setAutohidesScrollers_(True)
self._nsObject.setHasHorizontalScroller_(True)
self._nsObject.setHasVerticalScroller_(True)
self._nsObject.setBorderType_(NSBezelBorder)
self._nsObject.setDrawsBackground_(False)
#
self._tableView = NSTableView.alloc().init()
self._nsObject.setDocumentView_(self._tableView)
# handle the table columns
if columnTitles:
for title in columnTitles:
column = NSTableColumn.alloc().initWithIdentifier_(title)
header = column.headerCell().setTitle_(title)
self._tableView.addTableColumn_(column)
else:
column = NSTableColumn.alloc().initWithIdentifier_("VanillaColumn")
self._tableView.addTableColumn_(column)
# hide the header
if not showColumnTitles or not columnTitles:
self._tableView.setHeaderView_(None)
self._tableView.setCornerView_(None)
# set the table attributes
self._tableView.setUsesAlternatingRowBackgroundColors_(True)
self._tableView.setRowHeight_(17.0)
self._tableView.setAllowsEmptySelection_(True)
self._tableView.setAllowsMultipleSelection_(True)
self._tableView.setAutoresizesAllColumnsToFit_(True)
#
self._dataSource = VanillaListDataSourceAndDelegate.alloc().initWithItems_(items)
self._tableView.setDataSource_(self._dataSource)
self._tableView.setDelegate_(self._dataSource)
# do the base object init methods
self._setCallback(callback)
self._setAutosizingFromPosSize()
def _setFrame(self, parentFrame):
l, t, w, h = self._posSize
frame = _calcFrame(parentFrame, ((l, t), (w, h)))
self._tableView.setFrame_(((0, 0), frame[1]))
self._nsObject.setFrame_(frame)
def _setCallback(self, callback):
if callback is not None:
self._dataSource.callback = callback
###
### horribly ineffecient list methods
###
def __len__(self):
return len(self._dataSource.items)
def __getitem__(self, index):
return self._dataSource.items[index]
def __setitem__(self, index, value):
self._dataSource.items[index] = item
self._tableView.reloadData()
def __delitem__(self, index):
del self._dataSource.items[index]
self._tableView.reloadData()
def __getslice__(self, a, b):
return self._dataSource.items[a:b]
def __delslice__(self, a, b):
del self._dataSource.items[a:b]
self._tableView.reloadData()
def __setslice__(self, a, b, items):
self._dataSource.items[a:b] = items
self._tableView.reloadData()
def append(self, item):
self._dataSource.items.append(item)
self._tableView.reloadData()
def remove(self, item):
index = self._dataSource.items.index(item)
del self._dataSource.items[index]
self._tableView.reloadData()
def index(self, item):
return self._dataSource.items.index(item)
def insert(self, index, item):
self._dataSource.items.insert(index, item)
self._tableView.reloadData()
###
def set(self, value):
self._dataSource.setItems_(value)
self._tableView.reloadData()
def get(self):
return list(self._dataSource.items)
def getSelection(self):
return [i for i in self._tableView.selectedRowEnumerator()]
def setSelection(self, selection):
self._tableView.selectRowIndexes_byExtendingSelection_(NSIndexSet.alloc().init(), False)
# ugh. is it possible to make an index set *without* a continuous range?
for index in selection:
self._tableView.selectRowIndexes_byExtendingSelection_(NSIndexSet.alloc().initWithIndex_(index), True)
self._tableView.scrollRowToVisible_(min(selection))
##
## Tabs
##
class _TabItem(VanillaBaseObject):
"""This should never be instantiated directly."""
def __init__(self, title):
self._nsObject = NSTabViewItem.alloc().initWithIdentifier_(title)
self._nsObject.setLabel_(title)
def __setattr__(self, attr, value):
if isinstance(value, VanillaBaseObject):
view = self._nsObject.view()
frame = view.frame()
value._setFrame(frame)
view.addSubview_(value._nsObject)
super(_TabItem, self).__setattr__(attr, value)
def __delattr__(self, attr):
raise NotImplementedError, "need to ask Just about this"
class Tabs(VanillaBaseObject):
def __init__(self, posSize, titles=["Tab"]):
super(Tabs, self).__init__(NSTabView, posSize)
self._tabItems = []
for title in titles:
tab = _TabItem(title)
self._tabItems.append(tab)
self._nsObject.addTabViewItem_(tab._nsObject)
# hm. best API for this?
# maybe it should be more dict like and use the titles as keys?
def __getitem__(self, index):
return self._tabItems[index]
##
## Test
##
import sys
_testList = list(sys.path)
class Test(object):
def __init__(self):
self.w = Window((200, 300), "This is a window!", minSize=(100, 100), textured=False)
self.w.et = EditText((10, 10, -10, 22), "This is a text entry field", callback=self.editTextCallback)
self.w.t = TextBox((10, 42, -10, 20), "This is some text")
self.w.b = Button((4, 62, -4, 32), "This is a Button", callback=self.buttonCallback)
self.w.cb = CheckBox((10, 95, -10, 20), "This is a checkbox", callback=self.checkboxCallback, value=True)
self.w.l = List((10, 125, -10, -10), _testList, callback=self.listCallback)
self.w2 = Window((320, 320))
self.w2.tab = Tabs((10, 10, -10, -50), titles=["Tab One", "Tab Two"])
self.w2.tab[0].t = EditText((10, 10, -10, -10), "Check it out! This is a sheet! Oh. And look! It's a tab view!")
self.w2.tab[1].t = TextBox((10, 10, -10, -10), "Nothing to see here.")
self.w2.b = Button((10, -40, -10, 32), "Close this sheet because it is freaking me out", callback=self.closeSheet)
#self.w.te = TextEditor((10, 150, -10, -10), "This is a real text editor. And it is (currently) really broken.")
self.w.open()
def buttonCallback(self):
self.w.t.set("You hit the button!")
_testList.reverse()
self.w.l.set(_testList)
self.w2.open(self.w)
def checkboxCallback(self):
self.w.t.set("Checkbox value %d!"%self.w.cb.get())
def editTextCallback(self):
print self.w.et.get()
def listCallback(self):
print "selected rows:", self.w.l.getSelection()
def closeSheet(self):
self.w2.close()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment