Made startup logic consistent across platforms.

This means that underlying widgets aren't created in the constructor;
they're created in _startup(). It also means windows know which app they
are associated with, and widgets know which window (and app) they're
associated with.
This commit is contained in:
Russell Keith-Magee 2014-05-31 13:39:07 +08:00
parent 0f1c156d23
commit 844fa1f448
20 changed files with 325 additions and 118 deletions

View file

@ -49,14 +49,49 @@ class Widget(WidgetBase):
class Container(Widget):
def __init__(self):
super(Container, self).__init__()
self.children = []
self.constraints = {}
self._impl = None
def _startup(self):
self._layout_manager = LayoutManager(self._bounding_box)
self._impl = self._create_container()
for child in self.children:
self._add(child)
for constraint, impl in ((c, i) for c, i in self.constraints.items() if i is None):
self._constrain(constraint)
def add(self, widget):
self._impl.add(widget._impl)
self.children.append(widget)
print("Add widget to container")
if self._impl:
self._add(widget)
def _add(self, widget):
# Assign the widget to the same app and window as the container.
# This initiates startup logic.
widget.window = self.window
widget.app = self.app
self._layout_manager.add_widget(widget)
self._impl.add(widget._impl)
def constrain(self, constraint):
"Add the given constraint to the widget."
if constraint in self.constraints:
return
if self._impl:
print ("Add constraint")
self._constrain(constraint)
else:
print("Defer constraint until later")
self.constraints[constraint] = None
def _constrain(self, constraint):
widget = constraint.attr.widget
identifier = constraint.attr.identifier

View file

@ -23,11 +23,15 @@ class MainWindow(Window):
class App(object):
def __init__(self, name, app_id):
self._impl = NSApplication.sharedApplication()
self._impl.setActivationPolicy_(NSApplicationActivationPolicyRegular)
self.name = name
self.app_id = app_id
self.main_window = MainWindow()
def _startup(self):
self._impl = NSApplication.sharedApplication()
self._impl.setActivationPolicy_(NSApplicationActivationPolicyRegular)
app_name = sys.argv[0]
self.menu = NSMenu.alloc().initWithTitle_(get_NSString('MainMenu'))
@ -78,6 +82,8 @@ class App(object):
self._impl.setMainMenu_(self.menu)
def main_loop(self):
self._startup()
self.main_window.app = self
self.main_window.show()
self._impl.activateIgnoringOtherApps_(True)
self._impl.run()

View file

@ -20,13 +20,16 @@ class Button(Widget):
super(Button, self).__init__()
self.on_press = on_press
self.label = label
self._impl = None
def _startup(self):
self._impl = ButtonImpl.alloc().init()
self._impl.interface = self
self._impl.setBezelStyle_(NSRoundedBezelStyle)
self._impl.setButtonType_(NSMomentaryPushInButton)
self._impl.setTitle_(get_NSString(label))
self._impl.setTitle_(get_NSString(self.label))
self._impl.setTarget_(self._impl)
self._impl.setAction_(get_selector('onPress:'))
self._impl.setTranslatesAutoresizingMaskIntoConstraints_(False)

View file

@ -31,12 +31,28 @@ class Container(Widget):
def __init__(self):
super(Container, self).__init__()
self._impl = NSView.alloc().init()
self.children = []
self.constraints = {}
self._impl = None
def _startup(self):
self._impl = NSView.alloc().init()
for child in self.children:
self._add(child)
for constraint, impl in ((c, i) for c, i in self.constraints.items() if i is None):
self._constrain(constraint)
def add(self, widget):
self.children.append(widget)
if self._impl:
self._add(widget)
def _add(self, widget):
# Assign the widget to the same app as the window.
# This initiates startup logic.
widget.app = self.app
self._impl.addSubview_(widget._impl)
def constrain(self, constraint):
@ -44,6 +60,15 @@ class Container(Widget):
if constraint in self.constraints:
return
if self._impl:
print ("Add constraint")
self._constrain(constraint)
else:
print("Defer constraint until later")
self.constraints[constraint] = None
def _constrain(self, constraint):
widget = constraint.attr.widget._impl
identifier = constraint.attr.identifier
@ -69,5 +94,4 @@ class Container(Widget):
)
self._impl.addConstraint_(constraint._impl)
self.constraints[constraint] = constraint._impl

View file

@ -10,6 +10,7 @@ class SplitView(Widget):
def __init__(self, direction=VERTICAL):
super(SplitView, self).__init__()
self.direction = direction
self._impl = NSSplitView.alloc().init()
self._impl.setVertical_(direction)

View file

@ -18,8 +18,14 @@ WindowDelegate = ObjCClass('WindowDelegate')
class Window(object):
def __init__(self, position=(100, 100), size=(640, 480)):
self.position = position
self.size = size
self._impl = None
self._app = None
def _startup(self):
self._impl = NSWindow.alloc().initWithContentRect_styleMask_backing_defer_(
NSMakeRect(position[0], position[1], size[0], size[1]),
NSMakeRect(self.position[0], self.position[1], self.size[0], self.size[1]),
NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask,
NSBackingStoreBuffered,
False)
@ -29,6 +35,27 @@ class Window(object):
self._impl.setDelegate_(self._delegate)
self.on_startup()
if self.content:
# Assign the widget to the same app as the window.
# This initiates startup logic.
self.content.app = self.app
self._impl.setContentView_(self._content._impl)
@property
def app(self):
return self._app
@app.setter
def app(self, app):
if self._app:
raise Exception("Window is already associated with an App")
self._app = app
self._startup()
@property
def content(self):
return self._content
@ -36,11 +63,20 @@ class Window(object):
@content.setter
def content(self, widget):
self._content = widget
self._impl.setContentView_(self._content._impl)
self._content.window = self
if self._impl:
# Assign the widget to the same app as the window.
# This initiates startup logic.
widget.app = self.app
self._impl.setContentView_(self._content._impl)
def show(self):
self._impl.makeKeyAndOrderFront_(None)
# self._impl.visualizeConstraints_(self._impl.contentView().constraints())
def on_startup(self):
pass
def on_close(self):
pass

View file

@ -17,17 +17,19 @@ class App(object):
# GLib.set_application_name(name.encode('ascii'))
self._impl = Gtk.Application(application_id=app_id, flags=Gio.ApplicationFlags.FLAGS_NONE)
self.main_window = MainWindow(self)
self.main_window = MainWindow()
self._impl.connect('startup', self._startup)
self._impl.connect('activate', self._activate)
self._impl.connect('shutdown', self._shutdown)
def _startup(self, data=None):
self.main_window.app = self
self._impl.add_window(self.main_window._impl)
action = Gio.SimpleAction.new('stuff', None)
action.connect('activate', self._on_quit)
action.connect('activate', self._quit)
self._impl.add_action(action)
app_name = sys.argv[0]
@ -71,18 +73,28 @@ class App(object):
self._impl.set_menubar(self.menu_bar)
self.main_window.show()
self.on_startup()
def on_startup(self):
pass
def _activate(self, data=None):
self.on_activate()
def on_activate(self):
pass
def _shutdown(self, data=None):
self.on_shutdown()
def on_shutdown(self):
pass
def main_loop(self):
self._impl.run(None)
def _on_quit(self, widget, data=None):
self.quit()
def _quit(self, widget, data=None):
self.on_quit()
def quit(self):
def on_quit(self):
self._impl.quit()

View file

@ -15,11 +15,12 @@ def wrapped_handler(widget, handler):
class Button(Widget):
def __init__(self, label, on_press=None):
super(Button, self).__init__()
self.label = label
self.on_press = on_press
# Buttons have a fixed drawn height. If their space allocation is
# greater than what is provided, center the button vertically.
self._expand_vertical = False
self.on_press = on_press
self._impl = Gtk.Button(label=label)
def _startup(self):
self._impl = Gtk.Button(label=self.label)
self._impl.connect("clicked", wrapped_handler(self, self.on_press))

View file

@ -31,7 +31,7 @@ class GtkContainer(Gtk.Fixed):
with self.layout_manager.layout(allocation.width, allocation.height):
for widget in self.layout_manager.children:
print(widget, widget._bounding_box)
# print(widget, widget._bounding_box)
if not widget._impl.get_visible():
print("CHILD NOT VISIBLE")
else:
@ -64,11 +64,9 @@ class GtkContainer(Gtk.Fixed):
class Container(CassowaryContainer):
def __init__(self):
super(Container, self).__init__()
self._impl = GtkContainer(self._layout_manager)
def add(self, widget):
self._impl.add(widget._impl)
self._layout_manager.add_widget(widget)
def _create_container(self):
return GtkContainer(self._layout_manager)
@property
def _width_hint(self):

View file

@ -7,8 +7,32 @@ class Window(object):
_IMPL_CLASS = Gtk.Window
def __init__(self, position=(100, 100), size=(640, 480)):
self._app = None
self._impl = None
def _startup(self):
self._impl = self._IMPL_CLASS()
self._impl.connect("delete-event", self._on_close)
self.on_startup()
if self.content:
# Assign the widget to the same app as the window.
# This initiates startup logic.
self.content.app = self.app
self._impl.add(self.content._impl)
@property
def app(self):
return self._app
@app.setter
def app(self, app):
if self._app:
raise Exception("Window is already associated with an App")
self._app = app
self._startup()
@property
def content(self):
@ -17,11 +41,20 @@ class Window(object):
@content.setter
def content(self, widget):
self._content = widget
self._impl.add(self._content._impl)
self._content.window = self
if self._impl:
# Assign the widget to the same app as the window.
# This initiates startup logic.
widget.app = self.app
self._impl.add(self._content._impl)
def show(self):
self._impl.show_all()
def on_startup(self):
pass
def _on_close(self, widget, data):
self.on_close()

View file

@ -4,17 +4,16 @@ from .libs import *
from .window import Window
from .widgets import *
_apps = {}
# The global variable used to store the app instance.
_app = None
class MainWindow(Window):
def __init__(self):
super(MainWindow, self).__init__(self)
super(MainWindow, self).__init__()
print ("SET BACKGROUND COLOR")
def _startup(self):
super(MainWindow, self)._startup()
def on_startup(self):
self._impl.backgroundColor = UIColor.whiteColor()
@ -28,11 +27,7 @@ class AppDelegate_impl(object):
@AppDelegate.method('B@@')
def application_didFinishLaunchingWithOptions_(self, application, launchOptions):
print("FINISHED LAUNCHING")
# FIXME - there's got to be a better way to pass the Toga instance
# into the delegate.
app = _apps[None]
app._startup()
_app._startup()
return True
@ -42,15 +37,17 @@ AppDelegate = ObjCClass('AppDelegate')
class App(object):
def __init__(self, name, app_id):
global _app
_app = self
self.name = name
self.app_id = app_id
_apps[None] = self
self.main_window = MainWindow()
def _startup(self):
self.main_window._startup()
# Assign the window to the app; this initiates startup
self.main_window.app = self
self.main_window.show()
def main_loop(self):

View file

@ -4,4 +4,4 @@ from toga.widget import Widget as WidgetBase
class Widget(WidgetBase):
pass
pass

View file

@ -22,6 +22,7 @@ class Button(Widget):
self.on_press = on_press
self.label = label
self._impl = None
def _startup(self):

View file

@ -45,19 +45,23 @@ class Container(Widget):
self._controller.view = self._impl
for child in self.children:
child._startup()
self._impl.addSubview_(child._impl)
self._add(child)
for constraint, impl in ((c, i) for c, i in self.constraints.items() if i is None):
self._constrain(constraint)
self._impl.addConstraint_(constraint._impl)
self.constraints[constraint] = constraint._impl
def add(self, widget):
print("ADD SUBVIEW")
self.children.append(widget)
if self._impl:
self._impl.addSubview_(widget._impl)
self._add(widget)
def _add(self, widget):
# Assign the widget to the same app as the window.
# This initiates startup logic.
widget.app = self.app
self._impl.addSubview_(widget._impl)
def constrain(self, constraint):
"Add the given constraint to the widget."
@ -67,8 +71,6 @@ class Container(Widget):
if self._impl:
print ("Add constraint")
self._constrain(constraint)
self._impl.addConstraint_(constraint._impl)
self.constraints[constraint] = constraint._impl
else:
print("Defer constraint until later")
self.constraints[constraint] = None
@ -98,3 +100,6 @@ class Container(Widget):
related_widget, self._IDENTIFIER[related_identifier],
multiplier, constant,
)
self._impl.addConstraint_(constraint._impl)
self.constraints[constraint] = constraint._impl

View file

@ -3,21 +3,37 @@ from __future__ import print_function, absolute_import, division
from .libs import *
class Window(object):
def __init__(self, position=(100, 100), size=(640, 480)):
self._app = None
self._content = None
self._impl = None
def _startup(self):
print ("startup WINDOW")
self._impl = UIWindow.alloc().initWithFrame_(UIScreen.mainScreen().bounds())
self.on_startup()
self._content._startup()
if self.content:
# Assign the widget to the same app as the window.
# This initiates startup logic.
self.content.app = self.app
print("SET ROOT VIEW CONTROLLER")
self._impl.addSubview_(self.content._impl)
self._impl.rootViewController = self._content._controller
print("SET ROOT VIEW CONTROLLER")
self._impl.addSubview_(self.content._impl)
self._impl.rootViewController = self.content._controller
@property
def app(self):
return self._app
@app.setter
def app(self, app):
if self._app:
raise Exception("Window is already associated with an App")
self._app = app
self._startup()
@property
def content(self):
@ -26,7 +42,13 @@ class Window(object):
@content.setter
def content(self, widget):
self._content = widget
self._content.window = self
if self._impl:
# Assign the widget to the same app as the window.
# This initiates startup logic.
widget.app = self.app
# We now know the widget impl exists; add it.
self._impl.addSubview_(widget._impl)
print("SET ROOT VIEW CONTROLLER")
@ -37,3 +59,6 @@ class Window(object):
self._impl.makeKeyAndVisible()
# self._impl.visualizeConstraints_(self._impl.contentView().constraints())
def on_startup(self):
pass

View file

@ -16,6 +16,7 @@ class App(object):
self.main_window = MainWindow()
def main_loop(self):
self.main_window.app = self
self.main_window.show()
# Main message handling loop.

View file

@ -17,20 +17,24 @@ class Button(Widget):
self._expand_vertical = False
self.label = label
self.on_press = on_press
self._impl = None
self.window = None
def _create(self, window, x, y, width, height):
print "CREATE AT ", x, y, width, height
identifier = window._allocate_id()
def _startup(self):
x, y, width, height = self._geometry
print("CREATE AT ", x, y, width, height)
identifier = self.window._allocate_id()
self._impl = user32.CreateWindowExW(0, c_wchar_p("button"), c_wchar_p(self.label),
WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON | BS_TEXT,
int(x), int(y), int(width), int(height),
window._impl, identifier, 0, 0)
self.window._impl, identifier, 0, 0)
print "CREATE BUTTON", identifier, self.label
window._widgets[identifier] = self
print("CREATE BUTTON", identifier, self.label)
self.window._widgets[identifier] = self
def _resize(self, x, y, width, height):
print "RESIZE", self.label,x,y,width,height
def _resize(self):
x, y, width, height = self._geometry
print("RESIZE", self.label,x,y,width,height)
user32.SetWindowPos(self._impl, HWND_TOP,
int(x), int(y), int(width), int(height),
0)
@ -42,3 +46,24 @@ class Button(Widget):
@property
def _height_hint(self):
return 30, 30
@property
def _geometry(self):
min_width, preferred_width = self._width_hint
min_height, preferred_height = self._height_hint
x_pos = self._bounding_box.x.value
if self._expand_horizontal:
width = self._bounding_box.width.value
else:
x_pos = x_pos + ((self._bounding_box.width.value - preferred_width) / 2.0)
width = preferred_width
y_pos = self._bounding_box.y.value
if self._expand_vertical:
height = self._bounding_box.height.value
else:
y_pos = y_pos + ((self._bounding_box.height.value - preferred_height) / 2.0)
height = preferred_height
return (x_pos, y_pos, width, height)

View file

@ -3,61 +3,24 @@ from __future__ import print_function, absolute_import, division
from toga.cassowary.widget import Container as CassowaryContainer
class Win32Container(object):
def add(self, widget):
pass
class Container(CassowaryContainer):
def __init__(self):
super(Container, self).__init__()
self._window = None
self.window = None
def add(self, widget):
self._layout_manager.add_widget(widget)
if self._window:
widget._create(self._window)
def _create(self, window, x, y, width, height):
self._window = window
for widget in self._layout_manager.children:
min_width, preferred_width = widget._width_hint
min_height, preferred_height = widget._height_hint
x_pos = widget._bounding_box.x.value
if widget._expand_horizontal:
width = widget._bounding_box.width.value
else:
x_pos = x_pos + ((widget._bounding_box.width.value - preferred_width) / 2.0)
width = preferred_width
y_pos = widget._bounding_box.y.value
if widget._expand_vertical:
height = widget._bounding_box.height.value
else:
y_pos = y_pos + ((widget._bounding_box.height.value - preferred_height) / 2.0)
height = preferred_height
widget._create(window, x_pos, y_pos, width, height)
def _resize(self, x, y, width, height):
def _create_container(self):
# No impl is requried for a container, but we need a placeholder
# to keep the cross-platform logic happy.
return Win32Container()
def _resize(self, width, height):
with self._layout_manager.layout(width, height):
for widget in self._layout_manager.children:
min_width, preferred_width = widget._width_hint
min_height, preferred_height = widget._height_hint
x_pos = widget._bounding_box.x.value
if widget._expand_horizontal:
width = widget._bounding_box.width.value
else:
x_pos = x_pos + ((widget._bounding_box.width.value - preferred_width) / 2.0)
width = preferred_width
y_pos = widget._bounding_box.y.value
if widget._expand_vertical:
height = widget._bounding_box.height.value
else:
y_pos = y_pos + ((widget._bounding_box.height.value - preferred_height) / 2.0)
height = preferred_height
widget._resize(x_pos, y_pos, width, height)
widget._resize()
@property
def _width_hint(self):

View file

@ -4,11 +4,17 @@ from toga.platform.win32.libs import *
import ctypes
class Window(object):
def __init__(self, position=(100, 100), size=(640, 480)):
self._allocated = 0
self._widgets = {}
self.position = position
self.size = size
self._impl = None
self._app = None
def _startup(self):
module = kernel32.GetModuleHandleW(None)
brush = user32.GetSysColorBrush(COLOR_WINDOW)
self._window_class = WNDCLASS()
@ -28,10 +34,10 @@ class Window(object):
self._window_class.lpszClassName,
u'',
WS_OVERLAPPEDWINDOW,
position[0],
position[0], # CW_USEDEFAULT,
size[0],
size[1],
self.position[0],
self.position[1], # CW_USEDEFAULT,
self.size[0],
self.size[1],
0,
0,
self._window_class.hInstance,
@ -44,7 +50,27 @@ class Window(object):
ctypes.windll.UxTheme.SetWindowTheme(self._impl, c_wchar_p('Explorer'), 0)
user32.SetWindowTextW(self._impl, c_wchar_p("Hello World"))
print(2,self._impl)
print(2, self._impl)
self.on_startup()
if self.content:
self.content.app = self.app
def on_startup(self):
pass
@property
def app(self):
return self._app
@app.setter
def app(self, app):
if self._app:
raise Exception("Window is already associated with an App")
self._app = app
self._startup()
@property
def content(self):
@ -52,13 +78,10 @@ class Window(object):
@content.setter
def content(self, widget):
self._widgets = {}
self._content = widget
min_width, preferred_width = self._content._width_hint
min_height, preferred_height = self._content._height_hint
widget._create(self, 0, 0, preferred_width, preferred_height)
self._content.window = self
if self._impl:
widget.app = self.app
def show(self):
print(3,self._impl)
@ -103,7 +126,7 @@ class Window(object):
height = HIWORD(lParam)
print("REQUESTED SIZE", width, height)
if self._content:
self._content._resize(0, 0, width, height)
self._content._resize(width, height)
return 0
def _wm_close(self, msg, wParam, lParam):

View file

@ -5,6 +5,9 @@ from toga.constraint import Attribute
class Widget(object):
def __init__(self):
self._app = None
self.window = None
self.LEFT = Attribute(self, Attribute.LEFT)
self.RIGHT = Attribute(self, Attribute.RIGHT)
self.TOP = Attribute(self, Attribute.TOP)
@ -16,3 +19,18 @@ class Widget(object):
self.CENTER_X = Attribute(self, Attribute.CENTER_X)
self.CENTER_Y = Attribute(self, Attribute.CENTER_Y)
# self.BASELINE = Attribute(self, Attribute.BASELINE)
@property
def app(self):
return self._app
@app.setter
def app(self, app):
if self._app:
raise Exception("Widget is already associated with an App")
self._app = app
self._startup()
def _startup(self):
pass