Out with the old...

This commit is contained in:
Pietro Gagliardi 2014-08-30 23:01:08 -04:00
parent 3d4e54822d
commit 155899c65e
84 changed files with 0 additions and 25627 deletions

View File

@ -1,7 +0,0 @@
Please don't use this package as it stands now. It is being rewritten. You can watch progress in redo/, but keep in mind that it can and will experience major API changes.
Hopefully the rewrite will complete before the end of August.
Note that anyone using this after Go 1.3 will experience intermittent crashes if their allocated objects don't escape to the heap. [Go issue 8310](https://code.google.com/p/go/issues/detail?id=8310) will make that worse as well, but until the Go team makes their proposal public, I don't have much of an alternative.
Note 2: due to missing header files, the Windows version of the rewrite requires [mingw-w64](http://mingw-w64.sourceforge.net/). Make sure your MinGW uses that version instead. If you're running on Windows and not sure what to download, get the mingw-builds distribution.

392
area.go
View File

@ -1,392 +0,0 @@
// 14 march 2014
package ui
import (
"fmt"
"image"
"reflect"
"unsafe"
)
// Area represents a blank canvas upon which programs may draw anything and receive arbitrary events from the user.
// An Area has an explicit size, represented in pixels, that may be different from the size shown in its Window.
// For information on scrollbars, see "Scrollbars" in the Overview.
// The coordinate system of an Area always has an origin of (0,0) which maps to the top-left corner; all image.Points and image.Rectangles sent across Area's channels conform to this.
// The size of an Area must be at least 1x1 (that is, neither its width nor its height may be zero or negative).
// For control layout purposes, an Area prefers to be at the size you set it to (so if an Area is not stretchy in its layout, it will ask to have that size).
//
// To handle events to the Area, an Area must be paired with an AreaHandler.
// See AreaHandler for details.
//
// Do not use an Area if you intend to read text.
// Area reads keys based on their position on a standard
// 101-key keyboard, and does no character processing.
// Character processing methods differ across operating
// systems; trying ot recreate these yourself is only going
// to lead to trouble.
// [FOR FUTURE PLANNING Use TextArea instead, providing a TextAreaHandler.]
type Area struct {
created bool
sysData *sysData
handler AreaHandler
initwidth int
initheight int
}
// AreaHandler represents the events that an Area should respond to.
// These methods are all executed on the main goroutine, not necessarily the same one that you created the AreaHandler in; you are responsible for the thread safety of any members of the actual type that implements ths interface.
// (Having to use this interface does not strike me as being particularly Go-like, but the nature of Paint makes channel-based event handling a non-option; in practice, deadlocks occur.)
type AreaHandler interface {
// Paint is called when the Area needs to be redrawn.
// The part of the Area that needs to be redrawn is stored in cliprect.
// Before Paint() is called, this region is cleared with a system-defined background color.
// You MUST handle this event, and you MUST return a valid image, otherwise deadlocks and panicking will occur.
// The image returned must have the same size as rect (but does not have to have the same origin points).
// Example:
// imgFromFile, _, err := image.Decode(file)
// if err != nil { panic(err) }
// img := image.NewRGBA(imgFromFile.Rect)
// draw.Draw(img, img.Rect, imgFromFile, image.ZP, draw.Over)
// // ...
// func (h *myAreaHandler) Paint(rect image.Rectangle) *image.RGBA {
// return img.SubImage(rect).(*image.RGBA)
// }
Paint(cliprect image.Rectangle) *image.RGBA
// Mouse is called when the Area receives a mouse event.
// You are allowed to do nothing in this handler (to ignore mouse events).
// See MouseEvent for details.
// If repaint is true, the Area is marked as needing to be redrawn.
// After handling the mouse event, package ui will decide whether to perform platform-dependent event chain continuation based on that platform's designated action (so it is not possible to override global mouse events this way).
Mouse(e MouseEvent) (repaint bool)
// Key is called when the Area receives a keyboard event.
// You are allowed to do nothing in this handler (to ignore keyboard events).
// See KeyEvent for details.
// If repaint is true, the Area is marked as needing to be redrawn.
// After handling the key event, package ui will decide whether to perform platform-dependent event chain continuation based on that platform's designated action (so it is not possible to override global key events, such as Alt-Tab, this way).
Key(e KeyEvent) (repaint bool)
}
// MouseEvent contains all the information for a mous event sent by Area.Mouse.
// Mouse button IDs start at 1, with 1 being the left mouse button, 2 being the middle mouse button, and 3 being the right mouse button.
// If additional buttons are supported, they will be returned with 4 being the first additional button.
// For example, on Unix systems where mouse buttons 4 through 7 are pseudobuttons for the scroll wheel directions, the next button, button 8, will be returned as 4, 9 as 5, etc.
// The association between button numbers and physical buttons are system-defined.
// For example, on Windows, buttons 4 and 5 are mapped to what are internally referred to as "XBUTTON1" and "XBUTTON2", which often correspond to the dedicated back/forward navigation buttons on the sides of many mice.
// The examples here are NOT a guarantee as to how many buttons maximum will be available on a given system.
//
// If the user clicked on the Area to switch to the Window it is contained in from another window in the OS, the Area will receive a MouseEvent for that click.
type MouseEvent struct {
// Pos is the position of the mouse in the Area at the time of the event.
Pos image.Point
// If the event was generated by a mouse button being pressed, Down contains the ID of that button.
// Otherwise, Down contains 0.
// If Down contains nonzero, the Area will also receive keyboard focus.
Down uint
// If the event was generated by a mouse button being released, Up contains the ID of that button.
// Otherwise, Up contains 0.
// If both Down and Up are 0, the event represents mouse movement (with optional held buttons for dragging; see below).
// Down and Up shall not both be nonzero.
Up uint
// If Down is nonzero, Count indicates the number of clicks: 1 for single-click, 2 for double-click, 3 for triple-click, and so on.
// The order of events will be Down:Count=1 -> Up -> Down:Count=2 -> Up -> Down:Count=3 -> Up -> ...
Count uint
// Modifiers is a bit mask indicating the modifier keys being held during the event.
Modifiers Modifiers
// Held is a slice of button IDs that indicate which mouse buttons are being held during the event.
// Held will not include Down and Up.
// Held will be sorted.
// Only buttons 1, 2, and 3 are guaranteed to be detected by Held properly; whether or not any others are is implementation-defined.
//
// If Held is non-empty but Up and Down are both zero, the mouse is being dragged, with all the buttons in Held being held.
// Whether or not a drag into an Area generates MouseEvents is implementation-defined.
// Whether or not a drag over an Area when the program is inactive generates MouseEvents is also implementation-defined.
// Moving the mouse over an Area when the program is inactive and no buttons are held will, however, generate MouseEvents.
Held []uint
}
// HeldBits returns Held as a bit mask.
// Bit 0 maps to button 1, bit 1 maps to button 2, etc.
func (e MouseEvent) HeldBits() (h uintptr) {
for _, x := range e.Held {
h |= uintptr(1) << (x - 1)
}
return h
}
// A KeyEvent represents a keypress in an Area.
//
// Key presses are based on their positions on a standard
// 101-key keyboard found on most computers. The
// names chosen for keys here are based on their names
// on US English QWERTY keyboards; see Key for details.
//
// If a key is pressed that is not supported by Key, ExtKey,
// or Modifiers, no KeyEvent will be produced.
type KeyEvent struct {
// Key is a byte representing a character pressed
// in the typewriter section of the keyboard.
// The value, which is independent of whether the
// Shift key is held, is a constant with one of the
// following (case-sensitive) values, drawn according
// to the key's position on the keyboard.
// ` 1 2 3 4 5 6 7 8 9 0 - =
// q w e r t y u i o p [ ] \
// a s d f g h j k l ; '
// z x c v b n m , . /
// The actual key entered will be the key at the respective
// position on the user's keyboard, regardless of the actual
// layout. (Some keyboards move \ to either the row above
// or the row below but in roughly the same spot; this is
// accounted for. Some keyboards have an additonal key
// to the left of 'z' or additional keys to the right of '='; these
// cannot be read.)
// In addition, Key will contain
// - ' ' (space) if the spacebar was pressed
// - '\t' if Tab was pressed, regardless of Modifiers
// - '\n' if the typewriter Enter key was pressed
// - '\b' if the typewriter Backspace key was pressed
// If this value is zero, see ExtKey.
Key byte
// If Key is zero, ExtKey contains a predeclared identifier
// naming an extended key. See ExtKey for details.
// If both Key and ExtKey are zero, a Modifier by itself
// was pressed. Key and ExtKey will not both be nonzero.
ExtKey ExtKey
// If both Key and ExtKey are zero, Modifier will contain exactly one of its bits set, indicating which Modifier was pressed or released.
// As with Modifiers itself, there is no way to differentiate between left and right modifier keys.
// As such, the result of pressing and/or releasing both left and right of the same Modifier is system-defined.
// Furthermore, the result of holding down a Key or ExtKey, then pressing a Modifier, and then releasing the original key is system-defined.
// Under no condition shall Key, ExtKey, AND Modifier all be zero.
Modifier Modifiers
// Modifiers contains all the modifier keys currently being held at the time of the KeyEvent.
// If Modifier is nonzero, Modifiers will not contain Modifier itself.
Modifiers Modifiers
// If Up is true, the key was released; if not, the key was pressed.
// There is no guarantee that all pressed keys shall have
// corresponding release events (for instance, if the user switches
// programs while holding the key down, then releases the key).
// Keys that have been held down are reported as multiple
// key press events.
Up bool
}
// ExtKey represents keys that are not in the typewriter section of the keyboard.
type ExtKey uintptr
const (
Escape ExtKey = iota + 1
Insert // equivalent to "Help" on Apple keyboards
Delete
Home
End
PageUp
PageDown
Up
Down
Left
Right
F1 // F1..F12 are guaranteed to be consecutive
F2
F3
F4
F5
F6
F7
F8
F9
F10
F11
F12
N0 // numpad keys; independent of Num Lock state
N1 // N0..N9 are guaranteed to be consecutive
N2
N3
N4
N5
N6
N7
N8
N9
NDot
NEnter
NAdd
NSubtract
NMultiply
NDivide
_nextkeys // for sanity check
)
// EffectiveKey returns e.Key if it is set.
// Otherwise, if e.ExtKey denotes a numpad key,
// EffectiveKey returns the equivalent e.Key value
// ('0'..'9', '.', '\n', '+', '-', '*', or '/').
// Otherwise, EffectiveKey returns zero.
func (e KeyEvent) EffectiveKey() byte {
if e.Key != 0 {
return e.Key
}
k := e.ExtKey
switch {
case k >= N0 && k <= N9:
return byte(k-N0) + '0'
case k == NDot:
return '.'
case k == NEnter:
return '\n'
case k == NAdd:
return '+'
case k == NSubtract:
return '-'
case k == NMultiply:
return '*'
case k == NDivide:
return '/'
}
return 0
}
// Modifiers indicates modifier keys being held during an event.
// There is no way to differentiate between left and right modifier keys.
// As such, what KeyEvents get sent if the user does something unusual with both of a certain modifier key at once is undefined.
type Modifiers uintptr
const (
Ctrl Modifiers = 1 << iota // the keys labelled Ctrl or Control on all platforms
Alt // the keys labelled Alt or Option or Meta on all platforms
Shift // the Shift keys
Super // the Super keys on platforms that have one, or the Windows keys on Windows, or the Command keys on Mac OS X
)
func checkAreaSize(width int, height int, which string) {
if width <= 0 || height <= 0 {
panic(fmt.Errorf("invalid size %dx%d in %s", width, height, which))
}
}
// NewArea creates a new Area with the given size and handler.
// It panics if handler is nil or if width or height is zero or negative.
func NewArea(width int, height int, handler AreaHandler) *Area {
checkAreaSize(width, height, "NewArea()")
if handler == nil {
panic("handler passed to NewArea() must not be nil")
}
return &Area{
sysData: mksysdata(c_area),
handler: handler,
initwidth: width,
initheight: height,
}
}
// SetSize sets the Area's internal drawing size.
// It has no effect on the actual control size.
// SetSize is safe for concurrent use; if the Area is being repainted or is handling an event, SetSize will wait for that to complete before changing the Area's size.
// SetSize will also signal the entirety of the Area to be redrawn as in RepaintAll.
// It panics if width or height is zero or negative.
func (a *Area) SetSize(width int, height int) {
checkAreaSize(width, height, "Area.SetSize()")
if a.created {
a.sysData.setAreaSize(width, height)
return
}
a.initwidth = width
a.initheight = height
}
// RepaintAll signals the entirety of the Area for redraw.
// If called before the Window containing the Area is created, RepaintAll does nothing.
func (a *Area) RepaintAll() {
if !a.created {
return
}
a.sysData.repaintAll()
}
func (a *Area) make(window *sysData) error {
a.sysData.handler = a.handler
err := a.sysData.make(window)
if err != nil {
return err
}
a.sysData.setAreaSize(a.initwidth, a.initheight)
a.created = true
return nil
}
func (a *Area) allocate(x int, y int, width int, height int, d *sysSizeData) []*allocation {
return []*allocation{&allocation{
x: x,
y: y,
width: width,
height: height,
this: a,
}}
}
func (a *Area) preferredSize(d *sysSizeData) (width int, height int) {
return a.sysData.preferredSize(d)
}
func (a *Area) commitResize(c *allocation, d *sysSizeData) {
a.sysData.commitResize(c, d)
}
func (a *Area) getAuxResizeInfo(d *sysSizeData) {
a.sysData.getAuxResizeInfo(d)
}
// internal function, but shared by all system implementations: &img.Pix[0] is not necessarily the first pixel in the image
func pixelDataPos(img *image.RGBA) int {
return img.PixOffset(img.Rect.Min.X, img.Rect.Min.Y)
}
func pixelData(img *image.RGBA) *uint8 {
return &img.Pix[pixelDataPos(img)]
}
// some platforms require pixels in ARGB order in their native endianness (because they treat the pixel array as an array of uint32s)
// this does the conversion
// you need to convert somewhere (Windows and cairo give us memory to use; Windows has stride==width but cairo might not)
func toARGB(i *image.RGBA, memory uintptr, memstride int) {
var realbits []byte
rbs := (*reflect.SliceHeader)(unsafe.Pointer(&realbits))
rbs.Data = memory
rbs.Len = 4 * i.Rect.Dx() * i.Rect.Dy()
rbs.Cap = rbs.Len
p := pixelDataPos(i)
q := 0
for y := i.Rect.Min.Y; y < i.Rect.Max.Y; y++ {
nextp := p + i.Stride
nextq := q + memstride
for x := i.Rect.Min.X; x < i.Rect.Max.X; x++ {
argb := uint32(i.Pix[p+3]) << 24 // A
argb |= uint32(i.Pix[p+0]) << 16 // R
argb |= uint32(i.Pix[p+1]) << 8 // G
argb |= uint32(i.Pix[p+2]) // B
// the magic of conversion
native := (*[4]byte)(unsafe.Pointer(&argb))
realbits[q+0] = native[0]
realbits[q+1] = native[1]
realbits[q+2] = native[2]
realbits[q+3] = native[3]
p += 4
q += 4
}
p = nextp
q = nextq
}
}

View File

@ -1,186 +0,0 @@
// 29 march 2014
package ui
import (
"image"
"unsafe"
)
// #include <stdlib.h>
//// #include <HIToolbox/Events.h>
// #include "objc_darwin.h"
import "C"
func makeArea(parentWindow C.id, alternate bool, s *sysData) C.id {
area := C.makeArea()
area = makeScrollView(area)
addControl(parentWindow, area)
return area
}
func areaInScrollView(scrollview C.id) C.id {
return getScrollViewContent(scrollview)
}
//export areaView_drawRect
func areaView_drawRect(self C.id, rect C.struct_xrect) {
s := getSysData(self)
// no need to clear the clip rect; the NSScrollView does that for us (see the setDrawsBackground: call in objc_darwin.m)
// rectangles in Cocoa are origin/size, not point0/point1; if we don't watch for this, weird things will happen when scrolling
cliprect := image.Rect(int(rect.x), int(rect.y), int(rect.x+rect.width), int(rect.y+rect.height))
max := C.frame(self)
cliprect = image.Rect(0, 0, int(max.width), int(max.height)).Intersect(cliprect)
if cliprect.Empty() { // no intersection; nothing to paint
return
}
i := s.handler.Paint(cliprect)
C.drawImage(
unsafe.Pointer(pixelData(i)), C.intptr_t(i.Rect.Dx()), C.intptr_t(i.Rect.Dy()), C.intptr_t(i.Stride),
C.intptr_t(cliprect.Min.X), C.intptr_t(cliprect.Min.Y))
}
func parseModifiers(e C.id) (m Modifiers) {
const (
_NSShiftKeyMask = 1 << 17
_NSControlKeyMask = 1 << 18
_NSAlternateKeyMask = 1 << 19
_NSCommandKeyMask = 1 << 20
)
mods := uintptr(C.modifierFlags(e))
if (mods & _NSControlKeyMask) != 0 {
m |= Ctrl
}
if (mods & _NSAlternateKeyMask) != 0 {
m |= Alt
}
if (mods & _NSShiftKeyMask) != 0 {
m |= Shift
}
if (mods & _NSCommandKeyMask) != 0 {
m |= Super
}
return m
}
func areaMouseEvent(self C.id, e C.id, click bool, up bool) {
var me MouseEvent
s := getSysData(self)
xp := C.getTranslatedEventPoint(self, e)
me.Pos = image.Pt(int(xp.x), int(xp.y))
// for the most part, Cocoa won't geenerate an event outside the Area... except when dragging outside the Area, so check for this
max := C.frame(self)
if !me.Pos.In(image.Rect(0, 0, int(max.width), int(max.height))) {
return
}
me.Modifiers = parseModifiers(e)
which := uint(C.buttonNumber(e)) + 1
if which == 3 { // swap middle and right button numbers
which = 2
} else if which == 2 {
which = 3
}
if click && up {
me.Up = which
} else if click {
me.Down = which
// this already works the way we want it to so nothing special needed like with Windows and GTK+
me.Count = uint(C.clickCount(e))
} else {
which = 0 // reset for Held processing below
}
// the docs do say don't use this for tracking (mouseMoved:) since it returns the state now, and mouse move events work by tracking, but as far as I can tell dragging the mouse over the inactive window does not generate an event on Mac OS X, so :/ (tracking doesn't touch dragging anyway except during mouseEntered: and mouseExited:, which we don't handle, and the only other tracking message, cursorChanged:, we also don't handle (yet...? need to figure out if this is how to set custom cursors or not), so)
held := C.pressedMouseButtons()
if which != 1 && (held&1) != 0 { // button 1
me.Held = append(me.Held, 1)
}
if which != 2 && (held&4) != 0 { // button 2; mind the swap
me.Held = append(me.Held, 2)
}
if which != 3 && (held&2) != 0 { // button 3
me.Held = append(me.Held, 3)
}
held >>= 3
for i := uint(4); held != 0; i++ {
if which != i && (held&1) != 0 {
me.Held = append(me.Held, i)
}
held >>= 1
}
repaint := s.handler.Mouse(me)
if repaint {
C.display(self)
}
}
//export areaView_mouseMoved_mouseDragged
func areaView_mouseMoved_mouseDragged(self C.id, e C.id) {
// for moving, this is handled by the tracking rect stuff above
// for dragging, if multiple buttons are held, only one of their xxxMouseDragged: messages will be sent, so this is OK to do
areaMouseEvent(self, e, false, false)
}
//export areaView_mouseDown
func areaView_mouseDown(self C.id, e C.id) {
// no need to manually set focus; Mac OS X has already done that for us by this point since we set our view to be a first responder
areaMouseEvent(self, e, true, false)
}
//export areaView_mouseUp
func areaView_mouseUp(self C.id, e C.id) {
areaMouseEvent(self, e, true, true)
}
func sendKeyEvent(self C.id, ke KeyEvent) {
s := getSysData(self)
repaint := s.handler.Key(ke)
if repaint {
C.display(self)
}
}
func areaKeyEvent(self C.id, e C.id, up bool) {
var ke KeyEvent
keyCode := uintptr(C.keyCode(e))
ke, ok := fromKeycode(keyCode)
if !ok {
// no such key; modifiers by themselves are handled by -[self flagsChanged:]
return
}
// either ke.Key or ke.ExtKey will be set at this point
ke.Modifiers = parseModifiers(e)
ke.Up = up
sendKeyEvent(self, ke)
}
//export areaView_keyDown
func areaView_keyDown(self C.id, e C.id) {
areaKeyEvent(self, e, false)
}
//export areaView_keyUp
func areaView_keyUp(self C.id, e C.id) {
areaKeyEvent(self, e, true)
}
//export areaView_flagsChanged
func areaView_flagsChanged(self C.id, e C.id) {
var ke KeyEvent
// Mac OS X sends this event on both key up and key down.
// Fortunately -[e keyCode] IS valid here, so we can simply map from key code to Modifiers, get the value of [e modifierFlags], and check if the respective bit is set or not — that will give us the up/down state
keyCode := uintptr(C.keyCode(e))
mod, ok := keycodeModifiers[keyCode] // comma-ok form to avoid adding entries
if !ok { // unknown modifier; ignore
return
}
ke.Modifiers = parseModifiers(e)
ke.Up = (ke.Modifiers & mod) == 0
ke.Modifier = mod
// don't include the modifier in ke.Modifiers
ke.Modifiers &^= mod
sendKeyEvent(self, ke)
}

View File

@ -1,179 +0,0 @@
// 13 may 2014
#include "objc_darwin.h"
#include "_cgo_export.h"
#import <AppKit/NSView.h>
#import <AppKit/NSTrackingArea.h>
#import <Foundation/NSGeometry.h>
#import <AppKit/NSEvent.h>
#import <AppKit/NSBitmapImageRep.h>
#define to(T, x) ((T *) (x))
#define toNSEvent(x) to(NSEvent, (x))
#define toAreaView(x) to(areaView, (x))
#define toNSInteger(x) ((NSInteger) (x))
#define fromNSInteger(x) ((intptr_t) (x))
#define toNSUInteger(x) ((NSUInteger) (x))
#define fromNSUInteger(x) ((uintptr_t) (x))
extern NSRect dummyRect;
@interface areaView : NSView {
NSTrackingArea *trackingArea;
}
@end
@implementation areaView
- (id)initWithFrame:(NSRect)r
{
self = [super initWithFrame:r];
if (self)
[self retrack];
// TODO other properties?
return self;
}
- (void)drawRect:(NSRect)cliprect
{
struct xrect rect;
rect.x = (intptr_t) cliprect.origin.x;
rect.y = (intptr_t) cliprect.origin.y;
rect.width = (intptr_t) cliprect.size.width;
rect.height = (intptr_t) cliprect.size.height;
areaView_drawRect(self, rect);
}
- (BOOL)isFlipped
{
return YES;
}
- (BOOL)acceptsFirstResponder
{
return YES;
}
// this will have the Area receive a click that switches to the Window it is in from another one
- (BOOL)acceptsFirstMouse:(NSEvent *)e
{
return YES;
}
- (void)retrack
{
trackingArea = [[NSTrackingArea alloc]
initWithRect:[self bounds]
// this bit mask (except for NSTrackingInVisibleRect, which was added later to prevent events from being triggered outside the visible area of the Area) comes from https://github.com/andlabs/misctestprogs/blob/master/cocoaviewmousetest.m (and I wrote this bit mask on 25 april 2014) and yes I know it includes enter/exit even though we don't watch those events; it probably won't really matter anyway but if it does I can change it easily
options:(NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways | NSTrackingEnabledDuringMouseDrag | NSTrackingInVisibleRect)
owner:self
userInfo:nil];
[self addTrackingArea:trackingArea];
}
- (void)updateTrackingAreas
{
[self removeTrackingArea:trackingArea];
[trackingArea release];
[self retrack];
}
#define event(m, f) \
- (void)m:(NSEvent *)e \
{ \
f(self, e); \
}
event(mouseMoved, areaView_mouseMoved_mouseDragged)
event(mouseDragged, areaView_mouseMoved_mouseDragged)
event(rightMouseDragged, areaView_mouseMoved_mouseDragged)
event(otherMouseDragged, areaView_mouseMoved_mouseDragged)
event(mouseDown, areaView_mouseDown)
event(rightMouseDown, areaView_mouseDown)
event(otherMouseDown, areaView_mouseDown)
event(mouseUp, areaView_mouseUp)
event(rightMouseUp, areaView_mouseUp)
event(otherMouseUp, areaView_mouseUp)
event(keyDown, areaView_keyDown)
event(keyUp, areaView_keyUp)
event(flagsChanged, areaView_flagsChanged)
@end
Class areaClass;
void initAreaClass(void)
{
areaClass = [areaView class];
}
id makeArea(void)
{
return [[areaView alloc]
initWithFrame:dummyRect];
}
void drawImage(void *pixels, intptr_t width, intptr_t height, intptr_t stride, intptr_t xdest, intptr_t ydest)
{
unsigned char *planes[1]; // NSBitmapImageRep wants an array of planes; we have one plane
NSBitmapImageRep *bitmap;
planes[0] = (unsigned char *) pixels;
bitmap = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:planes
pixelsWide:toNSInteger(width)
pixelsHigh:toNSInteger(height)
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSCalibratedRGBColorSpace // TODO NSDeviceRGBColorSpace?
bitmapFormat:0 // this is where the flag for placing alpha first would go if alpha came first; the default is alpha last, which is how we're doing things (otherwise the docs say "Color planes are arranged in the standard order—for example, red before green before blue for RGB color."); this is also where the flag for non-premultiplied colors would go if we used it (the default is alpha-premultiplied)
bytesPerRow:toNSInteger(stride)
bitsPerPixel:32];
// TODO this CAN fali; check error
[bitmap drawInRect:NSMakeRect((CGFloat) xdest, (CGFloat) ydest, (CGFloat) width, (CGFloat) height)
fromRect:NSZeroRect // draw whole image
operation:NSCompositeSourceOver
fraction:1.0
respectFlipped:YES
hints:nil];
[bitmap release];
}
uintptr_t modifierFlags(id e)
{
return fromNSUInteger([toNSEvent(e) modifierFlags]);
}
struct xpoint getTranslatedEventPoint(id area, id e)
{
NSPoint p;
struct xpoint q;
p = [toAreaView(area) convertPoint:[toNSEvent(e) locationInWindow] fromView:nil];
q.x = (intptr_t) p.x;
q.y = (intptr_t) p.y;
return q;
}
intptr_t buttonNumber(id e)
{
return fromNSInteger([toNSEvent(e) buttonNumber]);
}
intptr_t clickCount(id e)
{
return fromNSInteger([toNSEvent(e) clickCount]);
}
uintptr_t pressedMouseButtons(void)
{
return fromNSUInteger([NSEvent pressedMouseButtons]);
}
uintptr_t keyCode(id e)
{
return (uintptr_t) ([toNSEvent(e) keyCode]);
}

View File

@ -1,345 +0,0 @@
// +build !windows,!darwin,!plan9
// 14 march 2014
package ui
import (
"fmt"
"image"
"unsafe"
)
// #include "gtk_unix.h"
// extern gboolean our_area_draw_callback(GtkWidget *, cairo_t *, gpointer);
// extern gboolean our_area_button_press_event_callback(GtkWidget *, GdkEvent *, gpointer);
// extern gboolean our_area_button_release_event_callback(GtkWidget *, GdkEvent *, gpointer);
// extern gboolean our_area_motion_notify_event_callback(GtkWidget *, GdkEvent *, gpointer);
// extern gboolean our_area_enterleave_notify_event_callback(GtkWidget *, GdkEvent *, gpointer);
// extern gboolean our_area_key_press_event_callback(GtkWidget *, GdkEvent *, gpointer);
// extern gboolean our_area_key_release_event_callback(GtkWidget *, GdkEvent *, gpointer);
// /* because cgo doesn't like ... */
// static inline void gtkGetDoubleClickSettings(GtkSettings *settings, gint *maxTime, gint *maxDistance)
// {
// g_object_get(settings,
// "gtk-double-click-time", maxTime,
// "gtk-double-click-distance", maxDistance,
// NULL);
// }
import "C"
func gtkAreaNew() *C.GtkWidget {
drawingarea := C.gtk_drawing_area_new()
// the Area's size will be set later
// we need to explicitly subscribe to mouse events with GtkDrawingArea
C.gtk_widget_add_events(drawingarea,
C.GDK_BUTTON_PRESS_MASK|C.GDK_BUTTON_RELEASE_MASK|C.GDK_POINTER_MOTION_MASK|C.GDK_BUTTON_MOTION_MASK|C.GDK_ENTER_NOTIFY_MASK|C.GDK_LEAVE_NOTIFY_MASK)
// and we need to allow focusing on a GtkDrawingArea to enable keyboard events
C.gtk_widget_set_can_focus(drawingarea, C.TRUE)
scrollarea := C.gtk_scrolled_window_new((*C.GtkAdjustment)(nil), (*C.GtkAdjustment)(nil))
// need a viewport because GtkDrawingArea isn't natively scrollable
C.gtk_scrolled_window_add_with_viewport((*C.GtkScrolledWindow)(unsafe.Pointer(scrollarea)), drawingarea)
return scrollarea
}
func gtkAreaGetControl(scrollarea *C.GtkWidget) *C.GtkWidget {
viewport := C.gtk_bin_get_child((*C.GtkBin)(unsafe.Pointer(scrollarea)))
control := C.gtk_bin_get_child((*C.GtkBin)(unsafe.Pointer(viewport)))
return control
}
//export our_area_draw_callback
func our_area_draw_callback(widget *C.GtkWidget, cr *C.cairo_t, data C.gpointer) C.gboolean {
var x0, y0, x1, y1 C.double
var maxwid, maxht C.gint
s := (*sysData)(unsafe.Pointer(data))
// thanks to desrt in irc.gimp.net/#gtk+
// these are in user coordinates, which match what coordinates we want by default, even out of a draw event handler (thanks johncc3, mclasen, and Company in irc.gimp.net/#gtk+)
C.cairo_clip_extents(cr, &x0, &y0, &x1, &y1)
// we do not need to clear the cliprect; GtkDrawingArea did it for us beforehand
cliprect := image.Rect(int(x0), int(y0), int(x1), int(y1))
// the cliprect can actually fall outside the size of the Area; clip it by intersecting the two rectangles
C.gtk_widget_get_size_request(widget, &maxwid, &maxht)
cliprect = image.Rect(0, 0, int(maxwid), int(maxht)).Intersect(cliprect)
if cliprect.Empty() { // no intersection; nothing to paint
return C.FALSE // signals handled without stopping the event chain (thanks to desrt again)
}
i := s.handler.Paint(cliprect)
surface := C.cairo_image_surface_create(
C.CAIRO_FORMAT_ARGB32, // alpha-premultiplied; native byte order
C.int(i.Rect.Dx()),
C.int(i.Rect.Dy()))
if status := C.cairo_surface_status(surface); status != C.CAIRO_STATUS_SUCCESS {
panic(fmt.Errorf("cairo_create_image_surface() failed: %s\n",
C.GoString(C.cairo_status_to_string(status))))
}
// the flush and mark_dirty calls are required; see the cairo docs and https://git.gnome.org/browse/gtk+/tree/gdk/gdkcairo.c#n232 (thanks desrt in irc.gimp.net/#gtk+)
C.cairo_surface_flush(surface)
toARGB(i, uintptr(unsafe.Pointer(C.cairo_image_surface_get_data(surface))),
int(C.cairo_image_surface_get_stride(surface)))
C.cairo_surface_mark_dirty(surface)
C.cairo_set_source_surface(cr,
surface,
0, 0) // origin of the surface
// that just set the brush that cairo uses: we have to actually draw now
// (via https://developer.gnome.org/gtkmm-tutorial/stable/sec-draw-images.html.en)
C.cairo_rectangle(cr, x0, y0, x1, y1) // breaking the nrom here since we have the coordinates as a C double already
C.cairo_fill(cr)
C.cairo_surface_destroy(surface) // free surface
return C.FALSE // signals handled without stopping the event chain (thanks to desrt again)
}
var area_draw_callback = C.GCallback(C.our_area_draw_callback)
func translateModifiers(state C.guint, window *C.GdkWindow) C.guint {
// GDK doesn't initialize the modifier flags fully; we have to explicitly tell it to (thanks to Daniel_S and daniels (two different people) in irc.gimp.net/#gtk+)
C.gdk_keymap_add_virtual_modifiers(
C.gdk_keymap_get_for_display(C.gdk_window_get_display(window)),
(*C.GdkModifierType)(unsafe.Pointer(&state)))
return state
}
func makeModifiers(state C.guint) (m Modifiers) {
if (state & C.GDK_CONTROL_MASK) != 0 {
m |= Ctrl
}
if (state & C.GDK_META_MASK) != 0 { // TODO get equivalent for Alt
m |= Alt
}
if (state & C.GDK_SHIFT_MASK) != 0 {
m |= Shift
}
if (state & C.GDK_SUPER_MASK) != 0 {
m |= Super
}
return m
}
// shared code for finishing up and sending a mouse event
func finishMouseEvent(widget *C.GtkWidget, data C.gpointer, me MouseEvent, mb uint, x C.gdouble, y C.gdouble, state C.guint, gdkwindow *C.GdkWindow) {
var areawidth, areaheight C.gint
// on GTK+, mouse buttons 4-7 are for scrolling; if we got here, that's a mistake
if mb >= 4 && mb <= 7 {
return
}
s := (*sysData)(unsafe.Pointer(data))
state = translateModifiers(state, gdkwindow)
me.Modifiers = makeModifiers(state)
// the mb != # checks exclude the Up/Down button from Held
if mb != 1 && (state&C.GDK_BUTTON1_MASK) != 0 {
me.Held = append(me.Held, 1)
}
if mb != 2 && (state&C.GDK_BUTTON2_MASK) != 0 {
me.Held = append(me.Held, 2)
}
if mb != 3 && (state&C.GDK_BUTTON3_MASK) != 0 {
me.Held = append(me.Held, 3)
}
// don't check GDK_BUTTON4_MASK or GDK_BUTTON5_MASK because those are for the scrolling buttons mentioned above; there doesn't seem to be a way to detect higher buttons... (TODO)
me.Pos = image.Pt(int(x), int(y))
C.gtk_widget_get_size_request(widget, &areawidth, &areaheight)
if !me.Pos.In(image.Rect(0, 0, int(areawidth), int(areaheight))) { // outside the actual Area; no event
return
}
// and finally, if the button ID >= 8, continue counting from 4, as above and as in the MouseEvent spec
if me.Down >= 8 {
me.Down -= 4
}
if me.Up >= 8 {
me.Up -= 4
}
repaint := s.handler.Mouse(me)
if repaint {
C.gtk_widget_queue_draw(widget)
}
}
// convenience name to make our intent clear
const continueEventChain C.gboolean = C.FALSE
// checking for a mouse click that makes the program/window active is meaningless on GTK+: it's a property of the window manager/X11, and it's the WM that decides if the program should become active or not
// however, one thing is certain: the click event will ALWAYS be sent (to the window that the X11 decides to send it to)
// I assume the same is true for Wayland
// thanks Chipzz in irc.gimp.net/#gtk+
//export our_area_button_press_event_callback
func our_area_button_press_event_callback(widget *C.GtkWidget, event *C.GdkEvent, data C.gpointer) C.gboolean {
// clicking doesn't automatically transfer keyboard focus; we must do so manually (thanks tristan in irc.gimp.net/#gtk+)
C.gtk_widget_grab_focus(widget)
e := (*C.GdkEventButton)(unsafe.Pointer(event))
me := MouseEvent{
// GDK button ID == our button ID with some exceptions taken care of by finishMouseEvent()
Down: uint(e.button),
}
var maxTime C.gint
var maxDistance C.gint
if e._type != C.GDK_BUTTON_PRESS {
// ignore GDK's generated double-clicks and beyond; we handled those ourselves below
return continueEventChain
}
s := (*sysData)(unsafe.Pointer(data))
// e.time is unsigned and in milliseconds
// maxTime is also milliseconds; despite being gint, it is only allowed to be positive
// maxDistance is also only allowed to be positive
settings := C.gtk_widget_get_settings(widget)
C.gtkGetDoubleClickSettings(settings, &maxTime, &maxDistance)
me.Count = s.clickCounter.click(me.Down, int(e.x), int(e.y),
uintptr(e.time), uintptr(maxTime),
int(maxDistance), int(maxDistance))
finishMouseEvent(widget, data, me, me.Down, e.x, e.y, e.state, e.window)
return continueEventChain
}
var area_button_press_event_callback = C.GCallback(C.our_area_button_press_event_callback)
//export our_area_button_release_event_callback
func our_area_button_release_event_callback(widget *C.GtkWidget, event *C.GdkEvent, data C.gpointer) C.gboolean {
e := (*C.GdkEventButton)(unsafe.Pointer(event))
me := MouseEvent{
// GDK button ID == our button ID with some exceptions taken care of by finishMouseEvent()
Up: uint(e.button),
}
finishMouseEvent(widget, data, me, me.Up, e.x, e.y, e.state, e.window)
return continueEventChain
}
var area_button_release_event_callback = C.GCallback(C.our_area_button_release_event_callback)
//export our_area_motion_notify_event_callback
func our_area_motion_notify_event_callback(widget *C.GtkWidget, event *C.GdkEvent, data C.gpointer) C.gboolean {
e := (*C.GdkEventMotion)(unsafe.Pointer(event))
me := MouseEvent{}
finishMouseEvent(widget, data, me, 0, e.x, e.y, e.state, e.window)
return continueEventChain
}
var area_motion_notify_event_callback = C.GCallback(C.our_area_motion_notify_event_callback)
// we want switching away from the control to reset the double-click counter, like with WM_ACTIVATE on Windows
// according to tristan in irc.gimp.net/#gtk+, doing this on enter-notify-event and leave-notify-event is correct (and it seems to be true in my own tests; plus the events DO get sent when switching programs with the keyboard (just pointing that out))
// differentiating between enter-notify-event and leave-notify-event is unimportant
//export our_area_enterleave_notify_event_callback
func our_area_enterleave_notify_event_callback(widget *C.GtkWidget, event *C.GdkEvent, data C.gpointer) C.gboolean {
s := (*sysData)(unsafe.Pointer(data))
s.clickCounter.reset()
return continueEventChain
}
var area_enterleave_notify_event_callback = C.GCallback(C.our_area_enterleave_notify_event_callback)
// shared code for doing a key event
func doKeyEvent(widget *C.GtkWidget, event *C.GdkEvent, data C.gpointer, up bool) {
var ke KeyEvent
e := (*C.GdkEventKey)(unsafe.Pointer(event))
s := (*sysData)(unsafe.Pointer(data))
keyval := e.keyval
// get modifiers now in case a modifier was pressed
state := translateModifiers(e.state, e.window)
ke.Modifiers = makeModifiers(state)
if extkey, ok := extkeys[keyval]; ok {
ke.ExtKey = extkey
} else if mod, ok := modonlykeys[keyval]; ok {
ke.Modifier = mod
// don't include the modifier in ke.Modifiers
ke.Modifiers &^= mod
} else if xke, ok := fromScancode(uintptr(e.hardware_keycode) - 8); ok {
// see events_notdarwin.go for details of the above map lookup
// one of these will be nonzero
ke.Key = xke.Key
ke.ExtKey = xke.ExtKey
} else { // no match
return
}
ke.Up = up
repaint := s.handler.Key(ke)
if repaint {
C.gtk_widget_queue_draw(widget)
}
}
//export our_area_key_press_event_callback
func our_area_key_press_event_callback(widget *C.GtkWidget, event *C.GdkEvent, data C.gpointer) C.gboolean {
doKeyEvent(widget, event, data, false)
return continueEventChain
}
var area_key_press_event_callback = C.GCallback(C.our_area_key_press_event_callback)
//export our_area_key_release_event_callback
func our_area_key_release_event_callback(widget *C.GtkWidget, event *C.GdkEvent, data C.gpointer) C.gboolean {
doKeyEvent(widget, event, data, true)
return continueEventChain
}
var area_key_release_event_callback = C.GCallback(C.our_area_key_release_event_callback)
var extkeys = map[C.guint]ExtKey{
C.GDK_KEY_Escape: Escape,
C.GDK_KEY_Insert: Insert,
C.GDK_KEY_Delete: Delete,
C.GDK_KEY_Home: Home,
C.GDK_KEY_End: End,
C.GDK_KEY_Page_Up: PageUp,
C.GDK_KEY_Page_Down: PageDown,
C.GDK_KEY_Up: Up,
C.GDK_KEY_Down: Down,
C.GDK_KEY_Left: Left,
C.GDK_KEY_Right: Right,
C.GDK_KEY_F1: F1,
C.GDK_KEY_F2: F2,
C.GDK_KEY_F3: F3,
C.GDK_KEY_F4: F4,
C.GDK_KEY_F5: F5,
C.GDK_KEY_F6: F6,
C.GDK_KEY_F7: F7,
C.GDK_KEY_F8: F8,
C.GDK_KEY_F9: F9,
C.GDK_KEY_F10: F10,
C.GDK_KEY_F11: F11,
C.GDK_KEY_F12: F12,
// numpad numeric keys and . are handled in events_notdarwin.go
C.GDK_KEY_KP_Enter: NEnter,
C.GDK_KEY_KP_Add: NAdd,
C.GDK_KEY_KP_Subtract: NSubtract,
C.GDK_KEY_KP_Multiply: NMultiply,
C.GDK_KEY_KP_Divide: NDivide,
}
// sanity check
func init() {
included := make([]bool, _nextkeys)
for _, v := range extkeys {
included[v] = true
}
for i := 1; i < int(_nextkeys); i++ {
if i >= int(N0) && i <= int(N9) { // skip numpad numbers and .
continue
}
if i == int(NDot) {
continue
}
if !included[i] {
panic(fmt.Errorf("error: not all ExtKeys defined on Unix (missing %d)", i))
}
}
}
var modonlykeys = map[C.guint]Modifiers{
C.GDK_KEY_Control_L: Ctrl,
C.GDK_KEY_Control_R: Ctrl,
C.GDK_KEY_Alt_L: Alt,
C.GDK_KEY_Alt_R: Alt,
C.GDK_KEY_Meta_L: Alt,
C.GDK_KEY_Meta_R: Alt,
C.GDK_KEY_Shift_L: Shift,
C.GDK_KEY_Shift_R: Shift,
C.GDK_KEY_Super_L: Super,
C.GDK_KEY_Super_R: Super,
}

View File

@ -1,770 +0,0 @@
// 24 march 2014
package ui
import (
"fmt"
"image"
"syscall"
"unsafe"
)
const (
areastyle = _WS_HSCROLL | _WS_VSCROLL | controlstyle
areaxstyle = 0 | controlxstyle
)
var (
areaWndClass = toUTF16("gouiarea")
)
func getScrollPos(hwnd _HWND) (xpos int32, ypos int32) {
var si _SCROLLINFO
si.cbSize = uint32(unsafe.Sizeof(si))
si.fMask = _SIF_POS | _SIF_TRACKPOS
r1, _, err := _getScrollInfo.Call(
uintptr(hwnd),
uintptr(_SB_HORZ),
uintptr(unsafe.Pointer(&si)))
if r1 == 0 { // failure
panic(fmt.Errorf("error getting horizontal scroll position for Area: %v", err))
}
xpos = si.nPos
si.cbSize = uint32(unsafe.Sizeof(si)) // MSDN example code reinitializes this each time, so we'll do it too just to be safe
si.fMask = _SIF_POS | _SIF_TRACKPOS
r1, _, err = _getScrollInfo.Call(
uintptr(hwnd),
uintptr(_SB_VERT),
uintptr(unsafe.Pointer(&si)))
if r1 == 0 { // failure
panic(fmt.Errorf("error getting vertical scroll position for Area: %v", err))
}
ypos = si.nPos
return xpos, ypos
}
var (
_alphaBlend = msimg32.NewProc("AlphaBlend")
_beginPaint = user32.NewProc("BeginPaint")
_bitBlt = gdi32.NewProc("BitBlt")
_createCompatibleBitmap = gdi32.NewProc("CreateCompatibleBitmap")
_createCompatibleDC = gdi32.NewProc("CreateCompatibleDC")
_createDIBSection = gdi32.NewProc("CreateDIBSection")
_deleteDC = gdi32.NewProc("DeleteDC")
_deleteObject = gdi32.NewProc("DeleteObject")
_endPaint = user32.NewProc("EndPaint")
_fillRect = user32.NewProc("FillRect")
_getUpdateRect = user32.NewProc("GetUpdateRect")
// _selectObject in prefsize_windows.go
)
const (
areaBackgroundBrush = _HBRUSH(_COLOR_BTNFACE + 1)
)
func paintArea(s *sysData) {
var xrect _RECT
var ps _PAINTSTRUCT
r1, _, _ := _getUpdateRect.Call(
uintptr(s.hwnd),
uintptr(unsafe.Pointer(&xrect)),
uintptr(_TRUE)) // erase the update rect with the background color
if r1 == 0 { // no update rect; do nothing
return
}
hscroll, vscroll := getScrollPos(s.hwnd)
// both Windows RECT and Go image.Rect are point..point, so the following is correct
cliprect := image.Rect(int(xrect.left), int(xrect.top), int(xrect.right), int(xrect.bottom))
cliprect = cliprect.Add(image.Pt(int(hscroll), int(vscroll))) // adjust by scroll position
// make sure the cliprect doesn't fall outside the size of the Area
cliprect = cliprect.Intersect(image.Rect(0, 0, s.areawidth, s.areaheight))
if cliprect.Empty() { // still no update rect
return
}
// TODO don't do the above, but always draw the background color?
r1, _, err := _beginPaint.Call(
uintptr(s.hwnd),
uintptr(unsafe.Pointer(&ps)))
if r1 == 0 { // failure
panic(fmt.Errorf("error beginning Area repaint: %v", err))
}
hdc := _HANDLE(r1)
// very big thanks to Ninjifox for suggesting this technique and helping me go through it
// first let's create the destination image, which we fill with the windows background color
// this is how we fake drawing the background; see also http://msdn.microsoft.com/en-us/library/ms969905.aspx
r1, _, err = _createCompatibleDC.Call(uintptr(hdc))
if r1 == 0 { // failure
panic(fmt.Errorf("error creating off-screen rendering DC: %v", err))
}
rdc := _HANDLE(r1)
// the bitmap has to be compatible with the window
// if we create a bitmap compatible with the DC we just created, it'll be monochrome
// thanks to David Heffernan in http://stackoverflow.com/questions/23033636/winapi-gdi-fillrectcolor-btnface-fills-with-strange-grid-like-brush-on-window
r1, _, err = _createCompatibleBitmap.Call(
uintptr(hdc),
uintptr(xrect.right-xrect.left),
uintptr(xrect.bottom-xrect.top))
if r1 == 0 { // failure
panic(fmt.Errorf("error creating off-screen rendering bitmap: %v", err))
}
rbitmap := _HANDLE(r1)
r1, _, err = _selectObject.Call(
uintptr(rdc),
uintptr(rbitmap))
if r1 == 0 { // failure
panic(fmt.Errorf("error connecting off-screen rendering bitmap to off-screen rendering DC: %v", err))
}
prevrbitmap := _HANDLE(r1)
rrect := _RECT{
left: 0,
right: xrect.right - xrect.left,
top: 0,
bottom: xrect.bottom - xrect.top,
}
r1, _, err = _fillRect.Call(
uintptr(rdc),
uintptr(unsafe.Pointer(&rrect)),
uintptr(areaBackgroundBrush))
if r1 == 0 { // failure
panic(fmt.Errorf("error filling off-screen rendering bitmap with the system background color: %v", err))
}
i := s.handler.Paint(cliprect)
// don't convert to BRGA just yet; see below
// now we need to shove realbits into a bitmap
// technically bitmaps don't know about alpha; they just ignore the alpha byte
// AlphaBlend(), however, sees it - see http://msdn.microsoft.com/en-us/library/windows/desktop/dd183352%28v=vs.85%29.aspx
bi := _BITMAPINFO{}
bi.bmiHeader.biSize = uint32(unsafe.Sizeof(bi.bmiHeader))
bi.bmiHeader.biWidth = int32(i.Rect.Dx())
bi.bmiHeader.biHeight = -int32(i.Rect.Dy()) // negative height to force top-down drawing
bi.bmiHeader.biPlanes = 1
bi.bmiHeader.biBitCount = 32
bi.bmiHeader.biCompression = _BI_RGB
bi.bmiHeader.biSizeImage = uint32(i.Rect.Dx() * i.Rect.Dy() * 4)
// this is all we need, but because this confused me at first, I will say the two pixels-per-meter fields are unused (see http://blogs.msdn.com/b/oldnewthing/archive/2013/05/15/10418646.aspx and page 581 of Charles Petzold's Programming Windows, Fifth Edition)
ppvBits := uintptr(0) // now for the trouble: CreateDIBSection() allocates the memory for us...
r1, _, err = _createDIBSection.Call(
uintptr(_NULL), // Ninjifox does this, so do some wine tests (http://source.winehq.org/source/dlls/gdi32/tests/bitmap.c#L725, thanks vpovirk in irc.freenode.net/#winehackers) and even Raymond Chen (http://blogs.msdn.com/b/oldnewthing/archive/2006/11/16/1086835.aspx), so.
uintptr(unsafe.Pointer(&bi)),
uintptr(_DIB_RGB_COLORS),
uintptr(unsafe.Pointer(&ppvBits)),
uintptr(0), // we're not dealing with hSection or dwOffset
uintptr(0))
if r1 == 0 { // failure
panic(fmt.Errorf("error creating HBITMAP for image returned by AreaHandler.Paint(): %v", err))
}
ibitmap := _HANDLE(r1)
// now we have to do TWO MORE things before we can finally do alpha blending
// first, we need to load the bitmap memory, because Windows makes it for us
// the pixels are arranged in RGBA order, but GDI requires BGRA
// this turns out to be just ARGB in little endian; let's convert into this memory
// the bitmap Windows gives us has a stride == width
toARGB(i, ppvBits, i.Rect.Dx()*4)
// the second thing is... make a device context for the bitmap :|
// Ninjifox just makes another compatible DC; we'll do the same
r1, _, err = _createCompatibleDC.Call(uintptr(hdc))
if r1 == 0 { // failure
panic(fmt.Errorf("error creating HDC for image returned by AreaHandler.Paint(): %v", err))
}
idc := _HANDLE(r1)
r1, _, err = _selectObject.Call(
uintptr(idc),
uintptr(ibitmap))
if r1 == 0 { // failure
panic(fmt.Errorf("error connecting HBITMAP for image returned by AreaHandler.Paint() to its HDC: %v", err))
}
previbitmap := _HANDLE(r1)
// AND FINALLY WE CAN DO THE ALPHA BLENDING!!!!!!111
blendfunc := _BLENDFUNCTION{
BlendOp: _AC_SRC_OVER,
BlendFlags: 0,
SourceConstantAlpha: 255, // only use per-pixel alphas
AlphaFormat: _AC_SRC_ALPHA, // premultiplied
}
r1, _, err = _alphaBlend.Call(
uintptr(rdc), // destination
uintptr(0), // origin and size
uintptr(0),
uintptr(i.Rect.Dx()),
uintptr(i.Rect.Dy()),
uintptr(idc), // source image
uintptr(0),
uintptr(0),
uintptr(i.Rect.Dx()),
uintptr(i.Rect.Dy()),
blendfunc.arg())
if r1 == _FALSE { // failure
panic(fmt.Errorf("error alpha-blending image returned by AreaHandler.Paint() onto background: %v", err))
}
// and finally we can just blit that into the window
r1, _, err = _bitBlt.Call(
uintptr(hdc),
uintptr(xrect.left),
uintptr(xrect.top),
uintptr(xrect.right-xrect.left),
uintptr(xrect.bottom-xrect.top),
uintptr(rdc),
uintptr(0), // from the rdc's origin
uintptr(0),
uintptr(_SRCCOPY))
if r1 == 0 { // failure
panic(fmt.Errorf("error blitting Area image to Area: %v", err))
}
// now to clean up
r1, _, err = _selectObject.Call(
uintptr(idc),
uintptr(previbitmap))
if r1 == 0 { // failure
panic(fmt.Errorf("error reverting HDC for image returned by AreaHandler.Paint() to original HBITMAP: %v", err))
}
r1, _, err = _selectObject.Call(
uintptr(rdc),
uintptr(prevrbitmap))
if r1 == 0 { // failure
panic(fmt.Errorf("error reverting HDC for off-screen rendering to original HBITMAP: %v", err))
}
r1, _, err = _deleteObject.Call(uintptr(ibitmap))
if r1 == 0 { // failure
panic(fmt.Errorf("error deleting HBITMAP for image returned by AreaHandler.Paint(): %v", err))
}
r1, _, err = _deleteObject.Call(uintptr(rbitmap))
if r1 == 0 { // failure
panic(fmt.Errorf("error deleting HBITMAP for off-screen rendering: %v", err))
}
r1, _, err = _deleteDC.Call(uintptr(idc))
if r1 == 0 { // failure
panic(fmt.Errorf("error deleting HDC for image returned by AreaHandler.Paint(): %v", err))
}
r1, _, err = _deleteDC.Call(uintptr(rdc))
if r1 == 0 { // failure
panic(fmt.Errorf("error deleting HDC for off-screen rendering: %v", err))
}
// return value always nonzero according to MSDN
_endPaint.Call(
uintptr(s.hwnd),
uintptr(unsafe.Pointer(&ps)))
}
func getAreaControlSize(hwnd _HWND) (width int, height int) {
var rect _RECT
r1, _, err := _getClientRect.Call(
uintptr(hwnd),
uintptr(unsafe.Pointer(&rect)))
if r1 == 0 { // failure
panic(fmt.Errorf("error getting size of actual Area control: %v", err))
}
return int(rect.right - rect.left),
int(rect.bottom - rect.top)
}
func scrollArea(s *sysData, wparam _WPARAM, which uintptr) {
var si _SCROLLINFO
cwid, cht := getAreaControlSize(s.hwnd)
pagesize := int32(cwid)
maxsize := int32(s.areawidth)
if which == uintptr(_SB_VERT) {
pagesize = int32(cht)
maxsize = int32(s.areaheight)
}
si.cbSize = uint32(unsafe.Sizeof(si))
si.fMask = _SIF_POS | _SIF_TRACKPOS
r1, _, err := _getScrollInfo.Call(
uintptr(s.hwnd),
which,
uintptr(unsafe.Pointer(&si)))
if r1 == 0 { // failure
panic(fmt.Errorf("error getting current scroll position for scrolling: %v", err))
}
newpos := si.nPos
switch wparam & 0xFFFF {
case _SB_LEFT: // also _SB_TOP but Go won't let me
newpos = 0
case _SB_RIGHT: // also _SB_BOTTOM
// see comment in adjustAreaScrollbars() below
newpos = maxsize - pagesize
case _SB_LINELEFT: // also _SB_LINEUP
newpos--
case _SB_LINERIGHT: // also _SB_LINEDOWN
newpos++
case _SB_PAGELEFT: // also _SB_PAGEUP
newpos -= pagesize
case _SB_PAGERIGHT: // also _SB_PAGEDOWN
newpos += pagesize
case _SB_THUMBPOSITION:
// raymond chen says to just set the newpos to the SCROLLINFO nPos for this message; see http://blogs.msdn.com/b/oldnewthing/archive/2003/07/31/54601.aspx and http://blogs.msdn.com/b/oldnewthing/archive/2003/08/05/54602.aspx
// do nothing here; newpos already has nPos
case _SB_THUMBTRACK:
newpos = si.nTrackPos
} // otherwise just keep the current position (that's what MSDN example code says, anyway)
// make sure we're not out of range
if newpos < 0 {
newpos = 0
}
if newpos > (maxsize - pagesize) {
newpos = maxsize - pagesize
}
// this would be where we would put a check to not scroll if the scroll position changed, but see the note about SB_THUMBPOSITION above: Raymond Chen's code always does the scrolling anyway in this case
delta := -(newpos - si.nPos) // negative because ScrollWindowEx() scrolls in the opposite direction
dx := delta
dy := int32(0)
if which == uintptr(_SB_VERT) {
dx = int32(0)
dy = delta
}
r1, _, err = _scrollWindowEx.Call(
uintptr(s.hwnd),
uintptr(dx),
uintptr(dy),
uintptr(0), // these four change what is scrolled and record info about the scroll; we're scrolling the whole client area and don't care about the returned information here
uintptr(0),
uintptr(0),
uintptr(0),
uintptr(_SW_INVALIDATE|_SW_ERASE)) // mark the remaining rect as needing redraw and erase...
if r1 == _ERROR { // failure
panic(fmt.Errorf("error scrolling Area: %v", err))
}
// ...but don't redraw the window yet; we need to apply our scroll changes
// we actually have to commit the change back to the scrollbar; otherwise the scroll position will merely reset itself
si.cbSize = uint32(unsafe.Sizeof(si))
si.fMask = _SIF_POS
si.nPos = newpos
_setScrollInfo.Call(
uintptr(s.hwnd),
which,
uintptr(unsafe.Pointer(&si)))
// NOW redraw it
r1, _, err = _updateWindow.Call(uintptr(s.hwnd))
if r1 == 0 { // failure
panic(fmt.Errorf("error updating Area after scrolling: %v", err))
}
}
func adjustAreaScrollbars(s *sysData) {
var si _SCROLLINFO
cwid, cht := getAreaControlSize(s.hwnd)
// the trick is we want a page to be the width/height of the visible area
// so the scroll range would go from [0..image_dimension - control_dimension]
// but judging from the sample code on MSDN, we don't need to do this; the scrollbar will do it for us
// we DO need to handle it when scrolling, though, since the thumb can only go up to this upper limit
// have to do horizontal and vertical separately
si.cbSize = uint32(unsafe.Sizeof(si))
si.fMask = _SIF_RANGE | _SIF_PAGE
si.nMin = 0
si.nMax = int32(s.areawidth - 1) // the max point is inclusive, so we have to pass in the last valid value, not the first invalid one (see http://blogs.msdn.com/b/oldnewthing/archive/2003/07/31/54601.aspx); if we don't, we get weird things like the scrollbar sometimes showing one extra scroll position at the end that you can never scroll to
si.nPage = uint32(cwid)
_setScrollInfo.Call(
uintptr(s.hwnd),
uintptr(_SB_HORZ),
uintptr(unsafe.Pointer(&si)),
uintptr(_TRUE)) // redraw the scroll bar
si.cbSize = uint32(unsafe.Sizeof(si)) // MSDN sample code does this a second time; let's do it too to be safe
si.fMask = _SIF_RANGE | _SIF_PAGE
si.nMin = 0
si.nMax = int32(s.areaheight - 1)
si.nPage = uint32(cht)
_setScrollInfo.Call(
uintptr(s.hwnd),
uintptr(_SB_VERT),
uintptr(unsafe.Pointer(&si)),
uintptr(_TRUE)) // redraw the scroll bar
}
var (
_invalidateRect = user32.NewProc("InvalidateRect")
)
func repaintArea(s *sysData) {
r1, _, err := _invalidateRect.Call(
uintptr(s.hwnd),
uintptr(0), // the whole area
uintptr(_TRUE)) // have Windows erase if possible
if r1 == 0 { // failure
panic(fmt.Errorf("error flagging Area as needing repainting after event (last error: %v)", err))
}
r1, _, err = _updateWindow.Call(uintptr(s.hwnd))
if r1 == 0 { // failure
panic(fmt.Errorf("error repainting Area after event: %v", err))
}
}
var (
_getKeyState = user32.NewProc("GetKeyState")
)
func getModifiers() (m Modifiers) {
down := func(x uintptr) bool {
// GetKeyState() gets the key state at the time of the message, so this is what we want
r1, _, _ := _getKeyState.Call(x)
return (r1 & 0x80) != 0
}
if down(_VK_CONTROL) {
m |= Ctrl
}
if down(_VK_MENU) {
m |= Alt
}
if down(_VK_SHIFT) {
m |= Shift
}
if down(_VK_LWIN) || down(_VK_RWIN) {
m |= Super
}
return m
}
var (
_getMessageTime = user32.NewProc("GetMessageTime")
_getDoubleClickTime = user32.NewProc("GetDoubleClickTime")
_getSystemMetrics = user32.NewProc("GetSystemMetrics")
)
func areaMouseEvent(s *sysData, button uint, up bool, wparam _WPARAM, lparam _LPARAM) {
var me MouseEvent
xpos, ypos := getScrollPos(s.hwnd) // mouse coordinates are relative to control; make them relative to Area
xpos += lparam.X()
ypos += lparam.Y()
me.Pos = image.Pt(int(xpos), int(ypos))
if !me.Pos.In(image.Rect(0, 0, s.areawidth, s.areaheight)) { // outside the actual Area; no event
return
}
if up {
me.Up = button
} else if button != 0 { // don't run the click counter if the mouse was only moved
me.Down = button
// this returns a LONG, which is int32, but we don't need to worry about the signedness because for the same bit widths and two's complement arithmetic, s1-s2 == u1-u2 if bits(s1)==bits(s2) and bits(u1)==bits(u2) (and Windows requires two's complement: http://blogs.msdn.com/b/oldnewthing/archive/2005/05/27/422551.aspx)
// signedness isn't much of an issue for these calls anyway because http://stackoverflow.com/questions/24022225/what-are-the-sign-extension-rules-for-calling-windows-api-functions-stdcall-t and that we're only using unsigned values (think back to how you (didn't) handle signedness in assembly language) AND because of the above AND because the statistics below (time interval and width/height) really don't make sense if negative
time, _, _ := _getMessageTime.Call()
maxTime, _, _ := _getDoubleClickTime.Call()
// ignore zero returns and errors; MSDN says zero will be returned on error but that GetLastError() is meaningless
xdist, _, _ := _getSystemMetrics.Call(_SM_CXDOUBLECLK)
ydist, _, _ := _getSystemMetrics.Call(_SM_CYDOUBLECLK)
me.Count = s.clickCounter.click(button, me.Pos.X, me.Pos.Y,
time, maxTime, int(xdist/2), int(ydist/2))
}
// though wparam will contain control and shift state, let's use just one function to get modifiers for both keyboard and mouse events; it'll work the same anyway since we have to do this for alt and windows key (super)
me.Modifiers = getModifiers()
if button != 1 && (wparam&_MK_LBUTTON) != 0 {
me.Held = append(me.Held, 1)
}
if button != 2 && (wparam&_MK_MBUTTON) != 0 {
me.Held = append(me.Held, 2)
}
if button != 3 && (wparam&_MK_RBUTTON) != 0 {
me.Held = append(me.Held, 3)
}
if button != 4 && (wparam&_MK_XBUTTON1) != 0 {
me.Held = append(me.Held, 4)
}
if button != 5 && (wparam&_MK_XBUTTON2) != 0 {
me.Held = append(me.Held, 5)
}
repaint := s.handler.Mouse(me)
if repaint {
repaintArea(s)
}
}
func areaKeyEvent(s *sysData, up bool, wparam _WPARAM, lparam _LPARAM) {
var ke KeyEvent
// the numeric keypad keys when Num Lock is off are considered left-hand keys as the separate navigation buttons were added later
// the numeric keypad enter, however, is a right-hand key because it has the same virtual-key code as the typewriter enter
righthand := (lparam & 0x01000000) != 0
scancode := byte((lparam >> 16) & 0xFF)
ke.Modifiers = getModifiers()
if extkey, ok := numpadextkeys[wparam]; ok && !righthand {
// the above is special handling for numpad keys to ignore the state of Num Lock and Shift; see http://blogs.msdn.com/b/oldnewthing/archive/2004/09/06/226045.aspx and https://github.com/glfw/glfw/blob/master/src/win32_window.c#L152
ke.ExtKey = extkey
} else if wparam == _VK_RETURN && righthand {
ke.ExtKey = NEnter
} else if extkey, ok := extkeys[wparam]; ok {
ke.ExtKey = extkey
} else if mod, ok := modonlykeys[wparam]; ok {
ke.Modifier = mod
// don't include the modifier in ke.Modifiers
ke.Modifiers &^= mod
} else if xke, ok := fromScancode(uintptr(scancode)); ok {
// one of these will be nonzero
ke.Key = xke.Key
ke.ExtKey = xke.ExtKey
} else if ke.Modifiers == 0 {
// no key, extkey, or modifiers; do nothing
return
}
ke.Up = up
repaint := s.handler.Key(ke)
if repaint {
repaintArea(s)
}
}
// all mappings come from GLFW - https://github.com/glfw/glfw/blob/master/src/win32_window.c#L152
var numpadextkeys = map[_WPARAM]ExtKey{
_VK_HOME: N7,
_VK_UP: N8,
_VK_PRIOR: N9,
_VK_LEFT: N4,
_VK_CLEAR: N5,
_VK_RIGHT: N6,
_VK_END: N1,
_VK_DOWN: N2,
_VK_NEXT: N3,
_VK_INSERT: N0,
_VK_DELETE: NDot,
}
var extkeys = map[_WPARAM]ExtKey{
_VK_ESCAPE: Escape,
_VK_INSERT: Insert,
_VK_DELETE: Delete,
_VK_HOME: Home,
_VK_END: End,
_VK_PRIOR: PageUp,
_VK_NEXT: PageDown,
_VK_UP: Up,
_VK_DOWN: Down,
_VK_LEFT: Left,
_VK_RIGHT: Right,
_VK_F1: F1,
_VK_F2: F2,
_VK_F3: F3,
_VK_F4: F4,
_VK_F5: F5,
_VK_F6: F6,
_VK_F7: F7,
_VK_F8: F8,
_VK_F9: F9,
_VK_F10: F10,
_VK_F11: F11,
_VK_F12: F12,
// numpad numeric keys and . are handled in events_notdarwin.go
// numpad enter is handled in code above
_VK_ADD: NAdd,
_VK_SUBTRACT: NSubtract,
_VK_MULTIPLY: NMultiply,
_VK_DIVIDE: NDivide,
}
// sanity check
func init() {
included := make([]bool, _nextkeys)
for _, v := range extkeys {
included[v] = true
}
for i := 1; i < int(_nextkeys); i++ {
if i >= int(N0) && i <= int(N9) { // skip numpad numbers, ., and enter
continue
}
if i == int(NDot) || i == int(NEnter) {
continue
}
if !included[i] {
panic(fmt.Errorf("error: not all ExtKeys defined on Windows (missing %d)", i))
}
}
}
var modonlykeys = map[_WPARAM]Modifiers{
// even if the separate left/right aren't necessary, have them here anyway, just to be safe
_VK_CONTROL: Ctrl,
_VK_LCONTROL: Ctrl,
_VK_RCONTROL: Ctrl,
_VK_MENU: Alt,
_VK_LMENU: Alt,
_VK_RMENU: Alt,
_VK_SHIFT: Shift,
_VK_LSHIFT: Shift,
_VK_RSHIFT: Shift,
// there's no combined Windows key virtual-key code as there is with the others
_VK_LWIN: Super,
_VK_RWIN: Super,
}
var (
_setFocus = user32.NewProc("SetFocus")
)
func areaWndProc(hwnd _HWND, uMsg uint32, wParam _WPARAM, lParam _LPARAM) _LRESULT {
s := getSysData(hwnd)
if s == nil { // not yet saved
return storeSysData(hwnd, uMsg, wParam, lParam)
}
switch uMsg {
case _WM_PAINT:
paintArea(s)
return 0
case _WM_ERASEBKGND:
// don't draw a background; we'll do so when painting
// this is to make things flicker-free; see http://msdn.microsoft.com/en-us/library/ms969905.aspx
return 1
case _WM_HSCROLL:
scrollArea(s, wParam, _SB_HORZ)
return 0
case _WM_VSCROLL:
scrollArea(s, wParam, _SB_VERT)
return 0
case _WM_SIZE:
adjustAreaScrollbars(s)
return 0
case _WM_ACTIVATE:
// don't keep the double-click timer running if the user switched programs in between clicks
s.clickCounter.reset()
return 0
case _WM_MOUSEACTIVATE:
// this happens on every mouse click (apparently), so DON'T reset the click counter, otherwise it will always be reset (not an issue, as MSDN says WM_ACTIVATE is sent alongside WM_MOUSEACTIVATE when necessary)
// transfer keyboard focus to our Area on an activating click
// (see http://www.catch22.net/tuts/custom-controls)
// don't bother checking SetFocus()'s error; see http://stackoverflow.com/questions/24073695/winapi-can-setfocus-return-null-without-an-error-because-thats-what-im-see/24074912#24074912
_setFocus.Call(uintptr(s.hwnd))
// and don't eat the click, as we want to handle clicks that switch into Windows with Areas from other windows
return _MA_ACTIVATE
case _WM_MOUSEMOVE:
areaMouseEvent(s, 0, false, wParam, lParam)
return 0
case _WM_LBUTTONDOWN:
areaMouseEvent(s, 1, false, wParam, lParam)
return 0
case _WM_LBUTTONUP:
areaMouseEvent(s, 1, true, wParam, lParam)
return 0
case _WM_MBUTTONDOWN:
areaMouseEvent(s, 2, false, wParam, lParam)
return 0
case _WM_MBUTTONUP:
areaMouseEvent(s, 2, true, wParam, lParam)
return 0
case _WM_RBUTTONDOWN:
areaMouseEvent(s, 3, false, wParam, lParam)
return 0
case _WM_RBUTTONUP:
areaMouseEvent(s, 3, true, wParam, lParam)
return 0
case _WM_XBUTTONDOWN:
which := uint((wParam>>16)&0xFFFF) + 3 // values start at 1; we want them to start at 4
areaMouseEvent(s, which, false, wParam, lParam)
return _LRESULT(_TRUE) // XBUTTON messages are different!
case _WM_XBUTTONUP:
which := uint((wParam>>16)&0xFFFF) + 3
areaMouseEvent(s, which, true, wParam, lParam)
return _LRESULT(_TRUE)
case _WM_KEYDOWN:
areaKeyEvent(s, false, wParam, lParam)
return 0
case _WM_KEYUP:
areaKeyEvent(s, true, wParam, lParam)
return 0
// Alt+[anything] and F10 send these instead and require us to return to DefWindowProc() so global keystrokes such as Alt+Tab can be processed
case _WM_SYSKEYDOWN:
areaKeyEvent(s, false, wParam, lParam)
return defWindowProc(hwnd, uMsg, wParam, lParam)
case _WM_SYSKEYUP:
areaKeyEvent(s, true, wParam, lParam)
return defWindowProc(hwnd, uMsg, wParam, lParam)
case msgSetAreaSize:
s.areawidth = int(wParam) // see setAreaSize() in sysdata_windows.go
s.areaheight = int(lParam)
adjustAreaScrollbars(s)
repaintArea(s) // this calls for an update
return 0
case msgRepaintAll:
repaintArea(s)
return 0
default:
return defWindowProc(hwnd, uMsg, wParam, lParam)
}
panic(fmt.Sprintf("areaWndProc message %d did not return: internal bug in ui library", uMsg))
}
func registerAreaWndClass() (err error) {
wc := &_WNDCLASS{
style: _CS_HREDRAW | _CS_VREDRAW, // no CS_DBLCLKS because do that manually
lpszClassName: utf16ToArg(areaWndClass),
lpfnWndProc: syscall.NewCallback(areaWndProc),
hInstance: hInstance,
hIcon: icon,
hCursor: cursor,
hbrBackground: _HBRUSH(_NULL), // no brush; we handle WM_ERASEBKGND
}
r1, _, err := _registerClass.Call(uintptr(unsafe.Pointer(wc)))
if r1 == 0 { // failure
return err
}
return nil
}
type _BITMAPINFO struct {
bmiHeader _BITMAPINFOHEADER
bmiColors [32]uintptr // we don't use it; make it an arbitrary number that wouldn't cause issues
}
type _BITMAPINFOHEADER struct {
biSize uint32
biWidth int32
biHeight int32
biPlanes uint16
biBitCount uint16
biCompression uint32
biSizeImage uint32
biXPelsPerMeter int32
biYPelsPerMeter int32
biClrUsed uint32
biClrImportant uint32
}
type _BLENDFUNCTION struct {
BlendOp byte
BlendFlags byte
SourceConstantAlpha byte
AlphaFormat byte
}
// AlphaBlend() takes a BLENDFUNCTION value
func (b _BLENDFUNCTION) arg() (x uintptr) {
// little endian
x = uintptr(b.AlphaFormat) << 24
x |= uintptr(b.SourceConstantAlpha) << 16
x |= uintptr(b.BlendFlags) << 8
x |= uintptr(b.BlendOp)
return x
}
type _PAINTSTRUCT struct {
hdc _HANDLE
fErase int32 // originally BOOL
rcPaint _RECT
fRestore int32 // originally BOOL
fIncUpdate int32 // originally BOOL
rgbReserved [32]byte
}

View File

@ -1,76 +0,0 @@
// 12 february 2014
package ui
// A Button represents a clickable button with some text.
type Button struct {
// Clicked is called when the button is clicked.
// This cannot be changed after the Window containing the Button has been created.
// If you do not specify a handler, a default of "do nothing" will be used instead.
Clicked func()
created bool
sysData *sysData
initText string
}
// NewButton creates a new button with the specified text.
func NewButton(text string) (b *Button) {
return &Button{
sysData: mksysdata(c_button),
initText: text,
}
}
// SetText sets the button's text.
func (b *Button) SetText(text string) {
if b.created {
b.sysData.setText(text)
return
}
b.initText = text
}
// Text returns the button's text.
func (b *Button) Text() string {
if b.created {
return b.sysData.text()
}
return b.initText
}
func (b *Button) make(window *sysData) error {
b.sysData.event = b.Clicked
if b.sysData.event == nil {
b.sysData.event = func() {}
}
err := b.sysData.make(window)
if err != nil {
return err
}
b.sysData.setText(b.initText)
b.created = true
return nil
}
func (b *Button) allocate(x int, y int, width int, height int, d *sysSizeData) []*allocation {
return []*allocation{&allocation{
x: x,
y: y,
width: width,
height: height,
this: b,
}}
}
func (b *Button) preferredSize(d *sysSizeData) (width int, height int) {
return b.sysData.preferredSize(d)
}
func (b *Button) commitResize(a *allocation, d *sysSizeData) {
b.sysData.commitResize(a, d)
}
func (b *Button) getAuxResizeInfo(d *sysSizeData) {
b.sysData.getAuxResizeInfo(d)
}

View File

@ -1,72 +0,0 @@
// +build !windows,!darwin,!plan9
// 16 february 2014
package ui
import (
"unsafe"
)
/*
cgo doesn't support calling Go functions by default; we have to mark them for export. Not a problem, except arguments to GTK+ callbacks depend on the callback itself. Since we're generating callback functions as simple closures of one type, this file will wrap the generated callbacks in the appropriate callback type. We pass the actual generated pointer to the extra data parameter of the callback.
while we're at it the callback for our idle function will be handled here too
*/
// #include "gtk_unix.h"
// extern gboolean our_window_delete_event_callback(GtkWidget *, GdkEvent *, gpointer);
// extern gboolean our_window_configure_event_callback(GtkWidget *, GdkEvent *, gpointer);
// extern void our_button_clicked_callback(GtkButton *, gpointer);
// /* because cgo is flaky with macros; static inline because we have //exports */
// static inline void gSignalConnect(GtkWidget *widget, char *signal, GCallback callback, void *data) { g_signal_connect(widget, signal, callback, data); }
import "C"
//export our_window_delete_event_callback
func our_window_delete_event_callback(widget *C.GtkWidget, event *C.GdkEvent, what C.gpointer) C.gboolean {
// called when the user tries to close the window
s := (*sysData)(unsafe.Pointer(what))
return togbool(!s.close()) // ! because TRUE means don't close
}
var window_delete_event_callback = C.GCallback(C.our_window_delete_event_callback)
//export our_window_configure_event_callback
func our_window_configure_event_callback(widget *C.GtkWidget, event *C.GdkEvent, what C.gpointer) C.gboolean {
// called when the window is resized
s := (*sysData)(unsafe.Pointer(what))
if s.container != nil && s.allocate != nil { // wait for init
width, height := gtk_window_get_size(s.widget)
// top-left is (0,0) here
s.resizeWindow(width, height)
}
// no need to manually redraw everything: since we use gtk_widget_set_size_request(), that queues both resize and redraw for us (thanks Company in irc.gimp.net/#gtk+)
return C.FALSE // continue the event chain
}
var window_configure_event_callback = C.GCallback(C.our_window_configure_event_callback)
//export our_button_clicked_callback
func our_button_clicked_callback(button *C.GtkButton, what C.gpointer) {
// called when the user clicks a button
s := (*sysData)(unsafe.Pointer(what))
s.event()
}
var button_clicked_callback = C.GCallback(C.our_button_clicked_callback)
// this is the type of the signals fields in classData; here to avoid needing to import C
type callbackMap map[string]C.GCallback
// this is what actually connects a signal
func g_signal_connect(obj *C.GtkWidget, sig string, callback C.GCallback, sysData *sysData) {
csig := C.CString(sig)
defer C.free(unsafe.Pointer(csig))
C.gSignalConnect(obj, csig, callback, unsafe.Pointer(sysData))
}
func g_signal_connect_pointer(obj *C.GtkWidget, sig string, callback C.GCallback, p unsafe.Pointer) {
csig := C.CString(sig)
defer C.free(unsafe.Pointer(csig))
C.gSignalConnect(obj, csig, callback, p)
}

View File

@ -1,86 +0,0 @@
// 13 february 2014
package ui
// A Checkbox is a clickable square with a label. The square can be either checked or unchecked. Checkboxes start out unchecked.
type Checkbox struct {
created bool
sysData *sysData
initText string
initCheck bool
}
// NewCheckbox creates a new checkbox with the specified text.
func NewCheckbox(text string) (c *Checkbox) {
return &Checkbox{
sysData: mksysdata(c_checkbox),
initText: text,
}
}
// SetText sets the checkbox's text.
func (c *Checkbox) SetText(text string) {
if c.created {
c.sysData.setText(text)
return
}
c.initText = text
}
// Text returns the checkbox's text.
func (c *Checkbox) Text() string {
if c.created {
return c.sysData.text()
}
return c.initText
}
// SetChecked() changes the checked state of the Checkbox.
func (c *Checkbox) SetChecked(checked bool) {
if c.created {
c.sysData.setChecked(checked)
return
}
c.initCheck = checked
}
// Checked() returns whether or not the Checkbox has been checked.
func (c *Checkbox) Checked() bool {
if c.created {
return c.sysData.isChecked()
}
return c.initCheck
}
func (c *Checkbox) make(window *sysData) error {
err := c.sysData.make(window)
if err != nil {
return err
}
c.sysData.setText(c.initText)
c.sysData.setChecked(c.initCheck)
c.created = true
return nil
}
func (c *Checkbox) allocate(x int, y int, width int, height int, d *sysSizeData) []*allocation {
return []*allocation{&allocation{
x: x,
y: y,
width: width,
height: height,
this: c,
}}
}
func (c *Checkbox) preferredSize(d *sysSizeData) (width int, height int) {
return c.sysData.preferredSize(d)
}
func (c *Checkbox) commitResize(a *allocation, d *sysSizeData) {
c.sysData.commitResize(a, d)
}
func (c *Checkbox) getAuxResizeInfo(d *sysSizeData) {
c.sysData.getAuxResizeInfo(d)
}

View File

@ -1,147 +0,0 @@
// 14 february 2014
package ui
import (
"fmt"
)
// A Combobox is a drop-down list of items, of which at most one can be selected at any given time. You may optionally make the combobox editable to allow custom items. Initially, no item will be selected (and no text entered in an editable Combobox's entry field). What happens to the text shown in a Combobox if its width is too small is implementation-defined.
type Combobox struct {
created bool
sysData *sysData
initItems []string
}
func newCombobox(editable bool, items ...string) (c *Combobox) {
c = &Combobox{
sysData: mksysdata(c_combobox),
initItems: items,
}
c.sysData.alternate = editable
return c
}
// NewCombobox makes a new Combobox with the given items.
func NewCombobox(items ...string) *Combobox {
return newCombobox(false, items...)
}
// NewEditableCombobox makes a new editable Combobox with the given items.
func NewEditableCombobox(items ...string) *Combobox {
return newCombobox(true, items...)
}
// Append adds items to the end of the Combobox's list.
// Append will panic if something goes wrong on platforms that do not abort themselves.
func (c *Combobox) Append(what ...string) {
if c.created {
for _, s := range what {
c.sysData.append(s)
}
return
}
c.initItems = append(c.initItems, what...)
}
// InsertBefore inserts a new item in the Combobox before the item at the given position. It panics if the given index is out of bounds.
// InsertBefore will also panic if something goes wrong on platforms that do not abort themselves.
func (c *Combobox) InsertBefore(what string, before int) {
var m []string
if c.created {
if before < 0 || before >= c.sysData.len() {
goto badrange
}
c.sysData.insertBefore(what, before)
return
}
if before < 0 || before >= len(c.initItems) {
goto badrange
}
m = make([]string, 0, len(c.initItems)+1)
m = append(m, c.initItems[:before]...)
m = append(m, what)
c.initItems = append(m, c.initItems[before:]...)
return
badrange:
panic(fmt.Errorf("index %d out of range in Combobox.InsertBefore()", before))
}
// Delete removes the given item from the Combobox. It panics if the given index is out of bounds.
func (c *Combobox) Delete(index int) {
if c.created {
if index < 0 || index >= c.sysData.len() {
goto badrange
}
c.sysData.delete(index)
return
}
if index < 0 || index >= len(c.initItems) {
goto badrange
}
c.initItems = append(c.initItems[:index], c.initItems[index+1:]...)
return
badrange:
panic(fmt.Errorf("index %d out of range in Combobox.Delete()", index))
}
// Selection returns the current selection.
func (c *Combobox) Selection() string {
if c.created {
return c.sysData.text()
}
return ""
}
// SelectedIndex returns the index of the current selection in the Combobox. It returns -1 either if no selection was made or if text was manually entered in an editable Combobox.
func (c *Combobox) SelectedIndex() int {
if c.created {
return c.sysData.selectedIndex()
}
return -1
}
// Len returns the number of items in the Combobox.
//
// On platforms for which this function may return an error, it panics if one is returned.
func (c *Combobox) Len() int {
if c.created {
return c.sysData.len()
}
return len(c.initItems)
}
func (c *Combobox) make(window *sysData) (err error) {
err = c.sysData.make(window)
if err != nil {
return err
}
for _, s := range c.initItems {
c.sysData.append(s)
}
c.created = true
return nil
}
func (c *Combobox) allocate(x int, y int, width int, height int, d *sysSizeData) []*allocation {
return []*allocation{&allocation{
x: x,
y: y,
width: width,
height: height,
this: c,
}}
}
func (c *Combobox) preferredSize(d *sysSizeData) (width int, height int) {
return c.sysData.preferredSize(d)
}
func (c *Combobox) commitResize(a *allocation, d *sysSizeData) {
c.sysData.commitResize(a, d)
}
func (c *Combobox) getAuxResizeInfo(d *sysSizeData) {
c.sysData.getAuxResizeInfo(d)
}

View File

@ -1,105 +0,0 @@
// 17 may 2014
#include "objc_darwin.h"
#import <AppKit/NSPopUpButton.h>
#import <AppKit/NSComboBox.h>
#import <AppKit/NSArrayController.h>
/*
Cocoa doesn't have combo boxes in the sense that other systems do. NSPopUpButton is not editable and technically behaves like a menu on a menubar. NSComboBox is editable and is the more traditional combo box, but the edit field and list are even more separated than they are on other platforms.
Unfortunately, their default internal storage mechanisms exhibit the automatic selection behavior I DON'T want, so we're going to have to do that ourselves.
The NSArrayController we use in our Listboxes already behaves the way we want. Consequently, you'll notice a bunch of functions here call functions in listbox_darwin.m. How convenient =P (TODO separate into objc_darwin.m?)
TODO should we use NSComboBox's dataSource feature?
*/
extern NSRect dummyRect;
#define to(T, x) ((T *) (x))
#define toNSPopUpButton(x) to(NSPopUpButton, (x))
#define toNSComboBox(x) to(NSComboBox, (x))
#define toNSInteger(x) ((NSInteger) (x))
#define fromNSInteger(x) ((intptr_t) (x))
#define COMBOBOXKEY @"cbitem"
static NSString *comboboxKey = COMBOBOXKEY;
static NSString *comboboxBinding = @"contentValues";
static NSString *comboboxKeyPath = @"arrangedObjects." COMBOBOXKEY;
id makeCombobox(BOOL editable)
{
NSArrayController *ac;
ac = makeListboxArray();
#define BIND bind:comboboxBinding toObject:ac withKeyPath:comboboxKeyPath options:nil
// for NSPopUpButton, we need a little extra work to make it respect the NSArrayController's selection behavior properties
// thanks to stevesliva (http://stackoverflow.com/questions/23715275/cocoa-how-do-i-suppress-nspopupbutton-automatic-selection-synchronization-nsar)
// note: selectionIndex isn't listed in the Cocoa Bindings Reference for NSArrayController under exposed bindings, but is in the Cocoa Bindings Programming Topics under key-value observant properties, so we can still bind to it
#define BINDSEL bind:@"selectedIndex" toObject:ac withKeyPath:@"selectionIndex" options:nil
if (!editable) {
NSPopUpButton *pb;
pb = [[NSPopUpButton alloc]
initWithFrame:dummyRect
pullsDown:NO];
[pb BIND];
[pb BINDSEL];
return pb;
}
NSComboBox *cb;
cb = [[NSComboBox alloc]
initWithFrame:dummyRect];
[cb setUsesDataSource:NO];
[cb BIND];
// no need to bind selection
return cb;
}
id comboboxText(id c, BOOL editable)
{
if (!editable)
return [toNSPopUpButton(c) titleOfSelectedItem];
return [toNSComboBox(c) stringValue];
}
void comboboxAppend(id c, BOOL editable, id str)
{
id ac;
ac = boundListboxArray(c, comboboxBinding);
listboxArrayAppend(ac, toListboxItem(comboboxKey, str));
}
void comboboxInsertBefore(id c, BOOL editable, id str, intptr_t before)
{
id ac;
ac = boundListboxArray(c, comboboxBinding);
listboxArrayInsertBefore(ac, toListboxItem(comboboxKey, str), before);
}
intptr_t comboboxSelectedIndex(id c)
{
// both satisfy the selector
return fromNSInteger([toNSPopUpButton(c) indexOfSelectedItem]);
}
void comboboxDelete(id c, intptr_t index)
{
id ac;
ac = boundListboxArray(c, comboboxBinding);
listboxArrayDelete(ac, index);
}
intptr_t comboboxLen(id c)
{
// both satisfy the selector
return fromNSInteger([toNSPopUpButton(c) numberOfItems]);
}

View File

@ -1,126 +0,0 @@
// 25 february 2014
package ui
import (
"fmt"
"io/ioutil"
"syscall"
"unsafe"
)
// pretty much every constant here except _WM_USER is from commctrl.h, except where noted
var (
comctlManifestCookie uintptr
)
var (
_activateActCtx = kernel32.NewProc("ActivateActCtx")
_createActCtx = kernel32.NewProc("CreateActCtxW")
)
/*
Windows requires a manifest file to enable Common Controls version 6.
The only way to not require an external manifest is to synthesize the manifest ourselves.
We can use the activation context API to load it at runtime.
References:
- http://stackoverflow.com/questions/4308503/how-to-enable-visual-styles-without-a-manifest
- http://support.microsoft.com/kb/830033
*/
func forceCommonControls6() (err error) {
manifestfile, err := ioutil.TempFile("", "gouicomctl32v6manifest")
if err != nil {
return fmt.Errorf("error creating synthesized manifest file: %v", err)
}
_, err = manifestfile.Write(manifest)
if err != nil {
return fmt.Errorf("error writing synthesized manifest file: %v", err)
}
filename := manifestfile.Name()
// we now have to close the file, otherwise ActivateActCtx() will complain that it's in use by another program
// if ioutil.TempFile() ever changes so that the file is deleted when it is closed, this will need to change
manifestfile.Close()
var actctx struct {
cbSize uint32
dwFlags uint32
lpSource *uint16
wProcessorArchitecture uint16
wLangId uint16 // originally LANGID
lpAssemblyDirectory uintptr // originally LPCWSTR
lpResourceName uintptr // originally LPCWSTR
lpApplicationName uintptr // originally LPCWSTR
hModule _HANDLE // originally HMODULE
}
actctx.cbSize = uint32(unsafe.Sizeof(actctx))
// make this context the process default, just to be safe
actctx.dwFlags = _ACTCTX_FLAG_SET_PROCESS_DEFAULT
actctx.lpSource = toUTF16(filename)
r1, _, err := _createActCtx.Call(uintptr(unsafe.Pointer(&actctx)))
// don't negConst() INVALID_HANDLE_VALUE; windowsconstgen was given a pointer by windows.h, and pointers are unsigned, so converting it back to signed doesn't work
if r1 == _INVALID_HANDLE_VALUE { // failure
return fmt.Errorf("error creating activation context for synthesized manifest file: %v", err)
}
r1, _, err = _activateActCtx.Call(
r1,
uintptr(unsafe.Pointer(&comctlManifestCookie)))
if r1 == uintptr(_FALSE) { // failure
return fmt.Errorf("error activating activation context for synthesized manifest file: %v", err)
}
return nil
}
func initCommonControls() (err error) {
var icc struct {
dwSize uint32
dwICC uint32
}
err = forceCommonControls6()
if err != nil {
return fmt.Errorf("error forcing Common Controls version 6 (or newer): %v", err)
}
icc.dwSize = uint32(unsafe.Sizeof(icc))
icc.dwICC = _ICC_PROGRESS_CLASS
comctl32 = syscall.NewLazyDLL("comctl32.dll")
r1, _, err := comctl32.NewProc("InitCommonControlsEx").Call(uintptr(unsafe.Pointer(&icc)))
if r1 == _FALSE { // failure
return fmt.Errorf("error initializing Common Controls (comctl32.dll); Windows last error: %v", err)
}
return nil
}
// Common Controls class names.
const (
// x (lowercase) prefix to avoid being caught by the constants generator
x_PROGRESS_CLASS = "msctls_progress32"
)
var manifest = []byte(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
version="1.0.0.0"
processorArchitecture="*"
name="CompanyName.ProductName.YourApplication"
type="win32"
/>
<description>Your application description here.</description>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
</assembly>
`)

View File

@ -1,107 +0,0 @@
// 7 february 2014
package ui
import (
"syscall"
"unsafe"
)
var (
user32 = syscall.NewLazyDLL("user32.dll")
kernel32 = syscall.NewLazyDLL("kernel32.dll")
gdi32 = syscall.NewLazyDLL("gdi32.dll")
comctl32 *syscall.LazyDLL // comctl32 not defined here; see comctl_windows.go
msimg32 = syscall.NewLazyDLL("msimg32.dll")
)
type _HANDLE uintptr
type _HWND _HANDLE
type _HBRUSH _HANDLE
type _HMENU _HANDLE
// In MSDN, _LPARAM and _LRESULT are listed as signed pointers, however their interpretation is message-specific. Ergo, just cast them yourself; it'll be the same. (Thanks to Tv` in #go-nuts for helping me realize this.)
type _WPARAM uintptr
type _LPARAM uintptr
type _LRESULT uintptr
func (w _WPARAM) LOWORD() uint16 {
// according to windef.h
return uint16(w & 0xFFFF)
}
func (w _WPARAM) HIWORD() uint16 {
// according to windef.h
return uint16((w >> 16) & 0xFFFF)
}
func (l _LPARAM) X() int32 {
// according to windowsx.h
loword := uint16(l & 0xFFFF)
short := int16(loword) // convert to signed...
return int32(short) // ...and sign extend
}
func (l _LPARAM) Y() int32 {
// according to windowsx.h
hiword := uint16((l & 0xFFFF0000) >> 16)
short := int16(hiword) // convert to signed...
return int32(short) // ...and sign extend
}
type _POINT struct {
x int32
y int32
}
type _RECT struct {
left int32
top int32
right int32
bottom int32
}
// Go doesn't allow negative constants to be forced into unsigned types at compile-time; this will do it at runtime.
// This is safe; see http://stackoverflow.com/questions/24022225/what-are-the-sign-extension-rules-for-calling-windows-api-functions-stdcall-t
func negConst(c int) uintptr {
return uintptr(c)
}
// the next two are convenience wrappers
// the intention is not to say utf16ToArg(toUTF16(s)) - even though it appears to work fine, that's just because the garbage collector scheduling makes it run long after we're finished; if we store these uintptrs globally instead, then things will break
// instead, call them separately - s := toUTF16(str); winapifunc.Call(utf16ToArg(s))
func toUTF16(s string) *uint16 {
return syscall.StringToUTF16Ptr(s)
}
func utf16ToArg(s *uint16) uintptr {
return uintptr(unsafe.Pointer(s))
}
func utf16ToLPARAM(s *uint16) uintptr {
return uintptr(_LPARAM(unsafe.Pointer(s)))
}
var (
_adjustWindowRectEx = user32.NewProc("AdjustWindowRectEx")
_createWindowEx = user32.NewProc("CreateWindowExW")
_getClientRect = user32.NewProc("GetClientRect")
_moveWindow = user32.NewProc("MoveWindow")
_setWindowPos = user32.NewProc("SetWindowPos")
_setWindowText = user32.NewProc("SetWindowTextW")
_showWindow = user32.NewProc("ShowWindow")
_getWindowRect = user32.NewProc("GetWindowRect")
)
type _MINMAXINFO struct {
ptReserved _POINT
ptMaxSize _POINT
ptMaxPosition _POINT
ptMinTrackSize _POINT
ptMaxTrackSize _POINT
}
func (l _LPARAM) MINMAXINFO() *_MINMAXINFO {
return (*_MINMAXINFO)(unsafe.Pointer(l))
}

View File

@ -1,9 +0,0 @@
// 11 february 2014
package ui
// A Control represents an UI control. Note that Control contains unexported members; this has the consequence that you can't build custom controls that interface directly with the system-specific code (fo rinstance, to import an unsupported control), or at least not without some hackery. If you want to make your own controls, create an Area and provide an AreaHandler that does what you need.
type Control interface {
make(window *sysData) error
controlSizing
}

View File

@ -1,42 +0,0 @@
// 9 february 2014
package ui
import (
// "syscall"
// "unsafe"
)
/*
var (
_checkRadioButton = user32.NewProc("CheckRadioButton")
)
func CheckRadioButton(hDlg HWND, nIDFirstButton int, nIDLastButton int, nIDCheckButton int) (err error) {
r1, _, err := _checkRadioButton.Call(
uintptr(hDlg),
uintptr(nIDFirstButton),
uintptr(nIDLastButton),
uintptr(nIDCheckButton))
if r1 == 0 { // failure
return err
}
return nil
}
*/
var (
_getScrollInfo = user32.NewProc("GetScrollInfo")
_setScrollInfo = user32.NewProc("SetScrollInfo")
_scrollWindowEx = user32.NewProc("ScrollWindowEx")
)
type _SCROLLINFO struct {
cbSize uint32
fMask uint32
nMin int32 // originally int
nMax int32 // originally int
nPage uint32
nPos int32 // originally int
nTrackPos int32 // originally int
}

View File

@ -1,48 +0,0 @@
// 25 june 2014
package ui
type allocation struct {
x int
y int
width int
height int
this Control
neighbor Control
}
type cSysSizeData struct {
xmargin int
ymargin int
xpadding int
ypadding int
}
// for verification; see sysdata.go
type sysDataSizingFunctions interface {
beginResize() *sysSizeData
endResize(*sysSizeData)
translateAllocationCoords([]*allocation, int, int)
preferredSize(*sysSizeData) (int, int)
commitResize(*allocation, *sysSizeData)
getAuxResizeInfo(*sysSizeData)
}
func (s *sysData) resizeWindow(width, height int) {
d := s.beginResize()
allocations := s.allocate(0, 0, width, height, d)
s.translateAllocationCoords(allocations, width, height)
// move in reverse so as to approximate right->left order so neighbors make sense
for i := len(allocations) - 1; i >= 0; i-- {
allocations[i].this.commitResize(allocations[i], d)
}
s.endResize(d)
}
// non-layout controls: allocate() should just return a one-element slice; preferredSize(), commitResize(), and getAuxResizeInfo() should defer to their sysData equivalents
type controlSizing interface {
allocate(x int, y int, width int, height int, d *sysSizeData) []*allocation
preferredSize(d *sysSizeData) (width, height int)
commitResize(c *allocation, d *sysSizeData)
getAuxResizeInfo(d *sysSizeData)
}

View File

@ -1,124 +0,0 @@
// 1 march 2014
package ui
// #include "objc_darwin.h"
import "C"
type sysSizeData struct {
cSysSizeData
// for size calculations
// nothing for mac
// for the actual resizing
neighborAlign C.struct_xalignment
}
// THIS IS A GUESS. TODO.
// The only indication that this is remotely correct is the Auto Layout Guide implying that 12 pixels is the "Aqua space".
const (
macXMargin = 12
macYMargin = 12
macXPadding = 12
macYPadding = 12
)
func (s *sysData) beginResize() (d *sysSizeData) {
d = new(sysSizeData)
if s.spaced {
d.xmargin = macXMargin
d.ymargin = macYMargin
d.xpadding = macXPadding
d.ypadding = macYPadding
}
return d
}
func (s *sysData) endResize(d *sysSizeData) {
// redraw
}
func (s *sysData) translateAllocationCoords(allocations []*allocation, winwidth, winheight int) {
for _, a := range allocations {
// winheight - y because (0,0) is the bottom-left corner of the window and not the top-left corner
// (winheight - y) - height because (x, y) is the bottom-left corner of the control and not the top-left
a.y = (winheight - a.y) - a.height
}
}
func (s *sysData) commitResize(c *allocation, d *sysSizeData) {
if s.ctype == c_label && !s.alternate && c.neighbor != nil {
c.neighbor.getAuxResizeInfo(d)
if d.neighborAlign.baseline != 0 { // no adjustment needed if the given control has no baseline
// in order for the baseline value to be correct, the label MUST BE AT THE HEIGHT THAT OS X WANTS IT TO BE!
// otherwise, the baseline calculation will be relative to the bottom of the control, and everything will be wrong
origsize := C.controlPrefSize(s.id)
c.height = int(origsize.height)
newrect := C.struct_xrect{
x: C.intptr_t(c.x),
y: C.intptr_t(c.y),
width: C.intptr_t(c.width),
height: C.intptr_t(c.height),
}
ourAlign := C.alignmentInfo(s.id, newrect)
// we need to find the exact Y positions of the baselines
// fortunately, this is easy now that (x,y) is the bottom-left corner
thisbasey := ourAlign.alignmentRect.y + ourAlign.baseline
neighborbasey := d.neighborAlign.alignmentRect.y + d.neighborAlign.baseline
// now the amount we have to move the label down by is easy to find
yoff := neighborbasey - thisbasey
// and we just add that
c.y += int(yoff)
}
// TODO if there's no baseline, the alignment should be to the top /of the alignment rect/, not the frame
}
C.setRect(s.id, C.intptr_t(c.x), C.intptr_t(c.y), C.intptr_t(c.width), C.intptr_t(c.height))
}
func (s *sysData) getAuxResizeInfo(d *sysSizeData) {
d.neighborAlign = C.alignmentInfo(s.id, C.frame(s.id))
}
/*
Cocoa doesn't provide a reliable way to get the preferred size of a control (you're supposed to use Interface Builder and have it set up autoresizing for you). The best we can do is call [control sizeToFit] (which is defined for NSControls and has a custom implementation for the other types here) and read the preferred size. Though this changes the size, we're immediately overriding the change on return from sysData.preferredSize(), so no harm done. (This is similar to what we are doing with GTK+, except GTK+ does not actually change the size.)
*/
// standard case: control immediately passed in
func controlPrefSize(control C.id) (width int, height int) {
r := C.controlPrefSize(control)
return int(r.width), int(r.height)
}
// NSTableView is actually in a NSScrollView so we have to get it out first
func listboxPrefSize(control C.id) (width int, height int) {
r := C.listboxPrefSize(control)
return int(r.width), int(r.height)
}
// and for type checking reasons, progress bars are separate despite responding to -[sizeToFit]
func pbarPrefSize(control C.id) (width int, height int) {
r := C.pbarPrefSize(control)
return int(r.width), int(r.height)
}
// Areas know their own preferred size
func areaPrefSize(control C.id) (width int, height int) {
r := C.areaPrefSize(control)
return int(r.width), int(r.height)
}
var prefsizefuncs = [nctypes]func(C.id) (int, int){
c_button: controlPrefSize,
c_checkbox: controlPrefSize,
c_combobox: controlPrefSize,
c_lineedit: controlPrefSize,
c_label: controlPrefSize,
c_listbox: listboxPrefSize,
c_progressbar: pbarPrefSize,
c_area: areaPrefSize,
}
func (s *sysData) preferredSize(d *sysSizeData) (width int, height int) {
return prefsizefuncs[s.ctype](s.id)
}

View File

@ -1,78 +0,0 @@
// +build !windows,!darwin,!plan9
// 23 february 2014
package ui
type sysSizeData struct {
cSysSizeData
// for size calculations
// gtk+ needs nothing
// for the actual resizing
shouldVAlignTop bool
}
const (
gtkXMargin = 12
gtkYMargin = 12
gtkXPadding = 12
gtkYPadding = 6
)
func (s *sysData) beginResize() (d *sysSizeData) {
d = new(sysSizeData)
if s.spaced {
d.xmargin = gtkXMargin
d.ymargin = gtkYMargin
d.xpadding = gtkXPadding
d.ypadding = gtkYPadding
}
return d
}
func (s *sysData) endResize(d *sysSizeData) {
// redraw
}
func (s *sysData) translateAllocationCoords(allocations []*allocation, winwidth, winheight int) {
// no need for coordinate conversion with gtk+
}
func (s *sysData) commitResize(c *allocation, d *sysSizeData) {
if s.ctype == c_label && !s.alternate && c.neighbor != nil {
c.neighbor.getAuxResizeInfo(d)
if d.shouldVAlignTop {
// TODO should it be center-aligned to the first line or not
gtk_misc_set_alignment(s.widget, 0, 0)
} else {
gtk_misc_set_alignment(s.widget, 0, 0.5)
}
}
// TODO merge this here
s.setRect(c.x, c.y, c.width, c.height, 0)
}
func (s *sysData) getAuxResizeInfo(d *sysSizeData) {
d.shouldVAlignTop = (s.ctype == c_listbox) || (s.ctype == c_area)
}
// GTK+ 3 makes this easy: controls can tell us what their preferred size is!
// ...actually, it tells us two things: the "minimum size" and the "natural size".
// The "minimum size" is the smallest size we /can/ display /anything/. The "natural size" is the smallest size we would /prefer/ to display.
// The difference? Minimum size takes into account things like truncation with ellipses: the minimum size of a label can allot just the ellipses!
// So we use the natural size instead.
// There is a warning about height-for-width controls, but in my tests this isn't an issue.
// For Areas, we manually save the Area size and use that, just to be safe.
// We don't need to worry about y-offset because label alignment is "vertically center", which GtkLabel does for us.
func (s *sysData) preferredSize(d *sysSizeData) (width int, height int) {
if s.ctype == c_area {
return s.areawidth, s.areaheight
}
_, _, width, height = gtk_widget_get_preferred_size(s.widget)
return width, height
}

View File

@ -1,250 +0,0 @@
// 24 february 2014
package ui
import (
"fmt"
"unsafe"
)
type sysSizeData struct {
cSysSizeData
// for size calculations
baseX int
baseY int
// for the actual resizing
// possibly the HDWP
}
const (
marginDialogUnits = 7
paddingDialogUnits = 4
)
func (s *sysData) beginResize() (d *sysSizeData) {
d = new(sysSizeData)
dc := getTextDC(s.hwnd)
defer releaseTextDC(s.hwnd, dc)
var tm _TEXTMETRICS
r1, _, err := _getTextMetrics.Call(
uintptr(dc),
uintptr(unsafe.Pointer(&tm)))
if r1 == 0 { // failure
panic(fmt.Errorf("error getting text metrics for preferred size calculations: %v", err))
}
d.baseX = int(tm.tmAveCharWidth) // TODO not optimal; third reference has better way
d.baseY = int(tm.tmHeight)
if s.spaced {
d.xmargin = muldiv(marginDialogUnits, d.baseX, 4)
d.ymargin = muldiv(marginDialogUnits, d.baseY, 8)
d.xpadding = muldiv(paddingDialogUnits, d.baseX, 4)
d.ypadding = muldiv(paddingDialogUnits, d.baseY, 8)
}
return d
}
func (s *sysData) endResize(d *sysSizeData) {
// redraw
}
func (s *sysData) translateAllocationCoords(allocations []*allocation, winwidth, winheight int) {
// no translation needed on windows
}
func (s *sysData) commitResize(c *allocation, d *sysSizeData) {
yoff := stdDlgSizes[s.ctype].yoff
if s.alternate {
yoff = stdDlgSizes[s.ctype].yoffalt
}
if yoff != 0 {
yoff = muldiv(yoff, d.baseY, 8)
}
c.y += yoff
// TODO move this here
s.setRect(c.x, c.y, c.width, c.height, 0)
}
func (s *sysData) getAuxResizeInfo(d *sysSizeData) {
// do nothing
}
// For Windows, Microsoft just hands you a list of preferred control sizes as part of the MSDN documentation and tells you to roll with it.
// These sizes are given in "dialog units", which are independent of the font in use.
// We need to convert these into standard pixels, which requires we get the device context of the OS window.
// References:
// - http://msdn.microsoft.com/en-us/library/windows/desktop/aa511279.aspx#controlsizing for control sizes
// - http://msdn.microsoft.com/en-us/library/ms645502%28VS.85%29.aspx - the calculation needed
// - http://support.microsoft.com/kb/125681 - to get the base X and Y
// (thanks to http://stackoverflow.com/questions/58620/default-button-size)
// For push buttons, date/time pickers, links (which we don't use), toolbars, and rebars (another type of toolbar), Common Controls version 6 provides convenient methods to use instead, falling back to the old way if it fails.
// As we are left with incomplete data, an arbitrary size will be chosen
const (
defaultWidth = 100 // 2 * preferred width of buttons
)
type dlgunits struct {
width int
height int
longest bool // TODO actually use this
getsize uintptr
area bool // use area sizes instead
yoff int
yoffalt int
}
var stdDlgSizes = [nctypes]dlgunits{
c_button: dlgunits{
width: 50,
height: 14,
getsize: _BCM_GETIDEALSIZE,
},
c_checkbox: dlgunits{
// widtdh is not defined here so assume longest
longest: true,
height: 10,
},
c_combobox: dlgunits{
// technically the height of a combobox has to include the drop-down list (this is a historical accident: originally comboboxes weren't drop-down)
// but since we're forcing Common Controls version 6, we can take advantage of one of its mechanisms to automatically fix this mistake (bad practice but whatever)
// see also: http://blogs.msdn.com/b/oldnewthing/archive/2006/03/10/548537.aspx
// note that the Microsoft guidelines pages don't take the list size into account
longest: true,
height: 12, // from http://msdn.microsoft.com/en-us/library/windows/desktop/bb226818%28v=vs.85%29.aspx; the page linked above says 14
},
c_lineedit: dlgunits{
longest: true,
height: 14,
},
c_label: dlgunits{
longest: true,
height: 8,
yoff: 3,
yoffalt: 0,
},
c_listbox: dlgunits{
longest: true,
// height is not clearly defined here ("an integral number of items (3 items minimum)") so just use a three-line edit control
height: 14 + 10 + 10,
},
c_progressbar: dlgunits{
width: 237, // the first reference says 107 also works; TODO decide which to use
height: 8,
},
c_area: dlgunits{
area: true,
},
}
var (
_selectObject = gdi32.NewProc("SelectObject")
_getDC = user32.NewProc("GetDC")
_getTextExtentPoint32 = gdi32.NewProc("GetTextExtentPoint32W")
_getTextMetrics = gdi32.NewProc("GetTextMetricsW")
_releaseDC = user32.NewProc("ReleaseDC")
)
func getTextDC(hwnd _HWND) (dc _HANDLE) {
r1, _, err := _getDC.Call(uintptr(hwnd))
if r1 == 0 { // failure
panic(fmt.Errorf("error getting DC for preferred size calculations: %v", err))
}
dc = _HANDLE(r1)
r1, _, err = _selectObject.Call(
uintptr(dc),
uintptr(controlFont))
if r1 == 0 { // failure
panic(fmt.Errorf("error loading control font into device context for preferred size calculation: %v", err))
}
return dc
}
func releaseTextDC(hwnd _HWND, dc _HANDLE) {
r1, _, err := _releaseDC.Call(
uintptr(hwnd),
uintptr(dc))
if r1 == 0 { // failure
panic(fmt.Errorf("error releasing DC for preferred size calculations: %v", err))
}
}
func (s *sysData) preferredSize(d *sysSizeData) (width int, height int) {
// the preferred size of an Area is its size
if stdDlgSizes[s.ctype].area {
return s.areawidth, s.areaheight
}
if msg := stdDlgSizes[s.ctype].getsize; msg != 0 {
var size _SIZE
r1, _, _ := _sendMessage.Call(
uintptr(s.hwnd),
msg,
uintptr(0),
uintptr(unsafe.Pointer(&size)))
if r1 != uintptr(_FALSE) { // success
return int(size.cx), int(size.cy)
}
// otherwise the message approach failed, so fall back to the regular approach
println("message failed; falling back")
}
width = stdDlgSizes[s.ctype].width
if width == 0 {
width = defaultWidth
}
height = stdDlgSizes[s.ctype].height
width = muldiv(width, d.baseX, 4) // equivalent to right of rect
height = muldiv(height, d.baseY, 8) // equivalent to bottom of rect
return width, height
}
var (
_mulDiv = kernel32.NewProc("MulDiv")
)
func muldiv(ma int, mb int, div int) int {
// div will not be 0 in the usages above
// we also ignore overflow; that isn't likely to happen for our use case anytime soon
r1, _, _ := _mulDiv.Call(
uintptr(int32(ma)),
uintptr(int32(mb)),
uintptr(int32(div)))
return int(int32(r1))
}
type _SIZE struct {
cx int32 // originally LONG
cy int32
}
type _TEXTMETRICS struct {
tmHeight int32
tmAscent int32
tmDescent int32
tmInternalLeading int32
tmExternalLeading int32
tmAveCharWidth int32
tmMaxCharWidth int32
tmWeight int32
tmOverhang int32
tmDigitizedAspectX int32
tmDigitizedAspectY int32
tmFirstChar uint16
tmLastChar uint16
tmDefaultChar uint16
tmBreakChar uint16
tmItalic byte
tmUnderlined byte
tmStruckOut byte
tmPitchAndFamily byte
tmCharSet byte
}

2
d32
View File

@ -1,2 +0,0 @@
# use this script to build 32-bit darwin binaries
GOARCH=386 CGO_ENABLED=1 "$@"

View File

@ -1,45 +0,0 @@
// 27 february 2014
package ui
/*
This creates a class goAppDelegate that will be used as the delegate for /everything/. Specifically, it:
- handles window close events (windowShouldClose:)
- handles window resize events (windowDidResize:)
- handles button click events (buttonClicked:)
- handles the application-global Quit event (such as from the Dock) (applicationShouldTerminate)
*/
// #include <stdlib.h>
// #include "objc_darwin.h"
import "C"
var (
appDelegate C.id
)
func makeAppDelegate() {
appDelegate = C.makeAppDelegate()
}
//export appDelegate_windowShouldClose
func appDelegate_windowShouldClose(win C.id) C.BOOL {
sysData := getSysData(win)
return toBOOL(sysData.close())
}
//export appDelegate_windowDidResize
func appDelegate_windowDidResize(win C.id) {
s := getSysData(win)
wincv := C.windowGetContentView(win) // we want the content view's size, not the window's
r := C.frame(wincv)
// (0,0) is the bottom left corner but this is handled in sysData.translateAllocationCoords()
s.resizeWindow(int(r.width), int(r.height))
C.display(win) // redraw everything
}
//export appDelegate_buttonClicked
func appDelegate_buttonClicked(button C.id) {
sysData := getSysData(button)
sysData.event()
}

View File

@ -1,169 +0,0 @@
// 13 may 2014
#include "objc_darwin.h"
#include "_cgo_export.h"
#import <Foundation/NSObject.h>
#import <Foundation/NSValue.h>
#import <Foundation/NSNotification.h>
#import <AppKit/NSApplication.h>
#import <AppKit/NSWindow.h>
#import <Foundation/NSAutoreleasePool.h>
#import <AppKit/NSEvent.h>
#import <AppKit/NSAlert.h>
extern NSRect dummyRect;
@interface ourApplication : NSApplication
@end
@implementation ourApplication
// by default, NSApplication eats some key events
// this prevents that from happening with Area
// see http://stackoverflow.com/questions/24099063/how-do-i-detect-keyup-in-my-nsview-with-the-command-key-held and http://lists.apple.com/archives/cocoa-dev/2003/Oct/msg00442.html
- (void)sendEvent:(NSEvent *)e
{
NSEventType type;
type = [e type];
if (type == NSKeyDown || type == NSKeyUp || type == NSFlagsChanged) {
id focused;
focused = [[e window] firstResponder];
// TODO can focused be nil? the isKindOfClass: docs don't say if it handles nil receivers
if ([focused isKindOfClass:areaClass])
switch (type) {
case NSKeyDown:
[focused keyDown:e];
return;
case NSKeyUp:
[focused keyUp:e];
return;
case NSFlagsChanged:
[focused flagsChanged:e];
return;
}
// else fall through
}
// otherwise, let NSApplication do it
[super sendEvent:e];
}
@end
@interface appDelegate : NSObject
@end
@implementation appDelegate
- (void)uipost:(id)pv
{
NSValue *v = (NSValue *) pv;
appDelegate_uipost([v pointerValue]);
}
- (BOOL)windowShouldClose:(id)win
{
return appDelegate_windowShouldClose(win);
}
- (void)windowDidResize:(NSNotification *)n
{
appDelegate_windowDidResize([n object]);
}
- (void)buttonClicked:(id)button
{
appDelegate_buttonClicked(button);
}
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)app
{
NSArray *windows;
NSUInteger i;
// try to close all windows
windows = [NSApp windows];
for (i = 0; i < [windows count]; i++)
[[windows objectAtIndex:i] performClose:self];
// if any windows are left, cancel
if ([[NSApp windows] count] != 0)
return NSTerminateCancel;
// no windows are left; we're good
return NSTerminateNow;
}
- (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)data
{
NSInteger *ret = (NSInteger *) data;
*ret = returnCode;
}
@end
id makeAppDelegate(void)
{
return [appDelegate new];
}
id windowGetContentView(id window)
{
return [((NSWindow *) window) contentView];
}
BOOL initCocoa(id appDelegate)
{
// on 10.6 the -[NSApplication setDelegate:] method complains if we don't have one
NSAutoreleasePool *pool;
pool = [NSAutoreleasePool new];
dummyRect = NSMakeRect(0, 0, 100, 100);
initAreaClass();
[ourApplication sharedApplication]; // makes NSApp an object of type ourApplication
if ([NSApp setActivationPolicy:NSApplicationActivationPolicyRegular] != YES)
return NO;
[NSApp activateIgnoringOtherApps:YES]; // TODO actually do C.NO here? Russ Cox does YES in his devdraw; the docs say the Finder does NO
[NSApp setDelegate:appDelegate];
[pool release];
return YES;
}
void uipost(id appDelegate, void *data)
{
// need an autorelease pool here
NSAutoreleasePool *pool;
pool = [NSAutoreleasePool new];
[appDelegate performSelectorOnMainThread:@selector(uipost:)
withObject:[NSValue valueWithPointer:data]
waitUntilDone:YES]; // note this bit; see uitask_darwin.go for details
[pool release];
}
void breakMainLoop(void)
{
NSEvent *e;
// -[NSApplication stop:] stops the event loop; it won't do a clean termination, but we're not too concerned with that (at least not on the other platforms either so)
// we can't call -[NSApplication terminate:] because that will just quit the program, ensuring we never leave ui.Go()
[NSApp stop:NSApp];
// simply calling -[NSApplication stop:] is not good enough, as the stop flag is only checked when an event comes in
// we have to create a "proper" event; a blank event will just throw an exception
e = [NSEvent otherEventWithType:NSApplicationDefined
location:NSZeroPoint
modifierFlags:0
timestamp:0
windowNumber:0
context:nil
subtype:0
data1:0
data2:0];
[NSApp postEvent:e atStart:NO]; // not at start, just in case there are other events pending (TODO is this correct?)
}
void cocoaMainLoop(void)
{
[NSApp run];
}

View File

@ -1,52 +0,0 @@
// 7 february 2014
package ui
// Response denotes a response from the user to a Dialog.
type Response uint
const (
NotDone Response = iota
OK
)
// sentinel (not nil so programmer errors don't go undetected)
// this window is invalid and cannot be used directly
// notice the support it uses
var dialogWindow = new(Window)
// MsgBox displays an informational message box to the user with just an OK button.
// primaryText should be a short string describing the message, and will be displayed with additional emphasis on platforms that support it.
// Optionally, secondaryText can be used to show additional information.
// If you pass an empty string for secondaryText, neither additional information nor space for additional information will be shown.
// On platforms that allow for the message box window to have a title, os.Args[0] is used.
//
// See "On Dialogs" in the package overview for behavioral information.
func MsgBox(primaryText string, secondaryText string) {
dialogWindow.msgBox(primaryText, secondaryText)
}
// MsgBox is the Window method version of the package-scope function MsgBox.
// See that function's documentation and "On Dialogs" in the package overview for more information.
func (w *Window) MsgBox(primaryText string, secondaryText string) {
if !w.created {
panic("parent window passed to Window.MsgBox() before it was created")
}
w.msgBox(primaryText, secondaryText)
}
// MsgBoxError displays a message box to the user with just an OK button and an icon indicating an error.
// Otherwise, it behaves like MsgBox.
//
// See "On Dialogs" in the package overview for more information.
func MsgBoxError(primaryText string, secondaryText string) {
dialogWindow.msgBoxError(primaryText, secondaryText)
}
// MsgBoxError is the Window method version of the package-scope function MsgBoxError.
// See that function's documentation and "On Dialogs" in the package overview for more information.
func (w *Window) MsgBoxError(primaryText string, secondaryText string) {
if !w.created {
panic("parent window passed to Window.MsgBox() before it was created")
}
w.msgBoxError(primaryText, secondaryText)
}

View File

@ -1,49 +0,0 @@
// 2 march 2014
package ui
import (
"fmt"
"unsafe"
)
// #include "objc_darwin.h"
import "C"
//export dialog_send
func dialog_send(pchan unsafe.Pointer, res C.intptr_t) {
rchan := (*chan int)(pchan)
go func() { // send it in a new goroutine like we do with everything else
*rchan <- int(res)
}()
}
func _msgBox(parent *Window, primarytext string, secondarytext string, style uintptr) Response {
var pwin C.id = nil
if parent != dialogWindow {
pwin = parent.sysData.id
}
primary := toNSString(primarytext)
secondary := C.id(nil)
if secondarytext != "" {
secondary = toNSString(secondarytext)
}
switch style {
case 0: // normal
C.msgBox(pwin, primary, secondary)
return OK
case 1: // error
C.msgBoxError(pwin, primary, secondary)
return OK
}
panic(fmt.Errorf("unknown message box style %d\n", style))
}
func (w *Window) msgBox(primarytext string, secondarytext string) {
_msgBox(w, primarytext, secondarytext, 0)
}
func (w *Window) msgBoxError(primarytext string, secondarytext string) {
_msgBox(w, primarytext, secondarytext, 1)
}

View File

@ -1,45 +0,0 @@
// 15 may 2014
#include "objc_darwin.h"
#include "_cgo_export.h"
#import <AppKit/NSAlert.h>
#import <AppKit/NSWindow.h>
#import <AppKit/NSApplication.h>
#define to(T, x) ((T *) (x))
#define toNSWindow(x) to(NSWindow, (x))
static intptr_t alert(id parent, NSString *primary, NSString *secondary, NSAlertStyle style)
{
NSAlert *box;
box = [NSAlert new];
[box setMessageText:primary];
if (secondary != nil)
[box setInformativeText:secondary];
[box setAlertStyle:style];
// TODO is there a named constant? will also need to be changed when we add different dialog types
[box addButtonWithTitle:@"OK"];
if (parent == nil)
return (intptr_t) [box runModal];
else {
NSInteger ret;
[box beginSheetModalForWindow:toNSWindow(parent)
modalDelegate:[NSApp delegate]
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:)
contextInfo:&ret];
// TODO
return (intptr_t) ret;
}
}
void msgBox(id parent, id primary, id secondary)
{
alert(parent, (NSString *) primary, (NSString *) secondary, NSInformationalAlertStyle);
}
void msgBoxError(id parent, id primary, id secondary)
{
alert(parent, (NSString *) primary, (NSString *) secondary, NSCriticalAlertStyle);
}

View File

@ -1,106 +0,0 @@
// +build !windows,!darwin,!plan9
// 7 february 2014
package ui
import (
"unsafe"
)
// #include "gtk_unix.h"
// /* because cgo seems to choke on ... */
// /* parent will be NULL if there is no parent, so this is fine */
// static inline GtkWidget *gtkNewMsgBox(GtkWindow *parent, GtkMessageType type, GtkButtonsType buttons, char *title, char *text)
// {
// GtkWidget *k;
//
// k = gtk_message_dialog_new(parent, GTK_DIALOG_MODAL, type, buttons, "%s", (gchar *) title);
// if (text != NULL)
// gtk_message_dialog_format_secondary_text((GtkMessageDialog *) k, "%s", (gchar *) text);
// return k;
// }
import "C"
// the actual type of these constants is GtkResponseType but gtk_dialog_run() returns a gint
var dialogResponses = map[C.gint]Response{
C.GTK_RESPONSE_OK: OK,
}
// dialog performs the bookkeeping involved for having a GtkDialog behave the way we want.
type dialog struct {
parent *Window
pwin *C.GtkWindow
hadgroup C.gboolean
prevgroup *C.GtkWindowGroup
newgroup *C.GtkWindowGroup
result Response
}
func mkdialog(parent *Window) *dialog {
return &dialog{
parent: parent,
}
}
func (d *dialog) prepare() {
// to implement parent, we need to put the GtkMessageDialog into a new window group along with parent
// a GtkWindow can only be part of one group
// so we use this to save the parent window group (if there is one) and store the new window group
// after showing the message box, we restore the previous window group, so future parent == dialogWindow can work properly
// thanks to pbor and mclasen in irc.gimp.net/#gtk+
if d.parent != dialogWindow {
d.pwin = togtkwindow(d.parent.sysData.widget)
d.hadgroup = C.gtk_window_has_group(d.pwin)
// we can't remove a window from the "default window group"; otherwise this throws up Gtk-CRITICAL warnings
if d.hadgroup != C.FALSE {
d.prevgroup = C.gtk_window_get_group(d.pwin)
C.gtk_window_group_remove_window(d.prevgroup, d.pwin)
}
d.newgroup = C.gtk_window_group_new()
C.gtk_window_group_add_window(d.newgroup, d.pwin)
}
}
func (d *dialog) cleanup(box *C.GtkWidget) {
// have to explicitly close the dialog box, otherwise wacky things will happen
C.gtk_widget_destroy(box)
if d.parent != dialogWindow {
C.gtk_window_group_remove_window(d.newgroup, d.pwin)
C.g_object_unref(C.gpointer(unsafe.Pointer(d.newgroup))) // free the group
if d.prevgroup != nil {
C.gtk_window_group_add_window(d.prevgroup, d.pwin)
} // otherwise it'll go back into the default group on its own
}
}
func (d *dialog) run(mk func() *C.GtkWidget) {
d.prepare()
box := mk()
r := C.gtk_dialog_run((*C.GtkDialog)(unsafe.Pointer(box)))
d.cleanup(box)
d.result = dialogResponses[r]
}
func _msgBox(parent *Window, primarytext string, secondarytext string, msgtype C.GtkMessageType, buttons C.GtkButtonsType) (response Response) {
d := mkdialog(parent)
cprimarytext := C.CString(primarytext)
defer C.free(unsafe.Pointer(cprimarytext))
csecondarytext := (*C.char)(nil)
if secondarytext != "" {
csecondarytext = C.CString(secondarytext)
defer C.free(unsafe.Pointer(csecondarytext))
}
d.run(func() *C.GtkWidget {
return C.gtkNewMsgBox(d.pwin, msgtype, buttons, cprimarytext, csecondarytext)
})
return d.result
}
func (w *Window) msgBox(primarytext string, secondarytext string) {
_msgBox(w, primarytext, secondarytext, C.GtkMessageType(C.GTK_MESSAGE_OTHER), C.GtkButtonsType(C.GTK_BUTTONS_OK))
}
func (w *Window) msgBoxError(primarytext string, secondarytext string) {
_msgBox(w, primarytext, secondarytext, C.GtkMessageType(C.GTK_MESSAGE_ERROR), C.GtkButtonsType(C.GTK_BUTTONS_OK))
}

View File

@ -1,50 +0,0 @@
// 7 february 2014
package ui
import (
"fmt"
"os"
)
var (
_messageBox = user32.NewProc("MessageBoxW")
)
var dialogResponses = map[uintptr]Response{
_IDOK: OK,
}
func _msgBox(parent *Window, primarytext string, secondarytext string, uType uint32) Response {
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa511267.aspx says "Use task dialogs whenever appropriate to achieve a consistent look and layout. Task dialogs require Windows Vista® or later, so they aren't suitable for earlier versions of Windows. If you must use a message box, separate the main instruction from the supplemental instruction with two line breaks."
text := primarytext
if secondarytext != "" {
text += "\n\n" + secondarytext
}
ptext := toUTF16(text)
ptitle := toUTF16(os.Args[0])
parenthwnd := _HWND(_NULL)
if parent != dialogWindow {
parenthwnd = parent.sysData.hwnd
uType |= _MB_APPLMODAL // only for this window
} else {
uType |= _MB_TASKMODAL // make modal to every window in the program (they're all windows of the uitask, which is a single thread)
}
r1, _, err := _messageBox.Call(
uintptr(parenthwnd),
utf16ToArg(ptext),
utf16ToArg(ptitle),
uintptr(uType))
if r1 == 0 { // failure
panic(fmt.Sprintf("error displaying message box to user: %v\nstyle: 0x%08X\ntitle: %q\ntext:\n%s", err, uType, os.Args[0], text))
}
return dialogResponses[r1]
}
func (w *Window) msgBox(primarytext string, secondarytext string) {
_msgBox(w, primarytext, secondarytext, _MB_OK)
}
func (w *Window) msgBoxError(primarytext string, secondarytext string) {
_msgBox(w, primarytext, secondarytext, _MB_OK|_MB_ICONERROR)
}

87
doc.go
View File

@ -1,87 +0,0 @@
// 12 march 2014
/*
NOTE: THE FOLLOWING IS OUTDATED; PLEASE SEE THE REPOSITORY FOR MORE INFORMATION.
Package ui is a simple package which provides a way to write portable GUI programs quickly and easily. It aims to run on as many systems as Go itself, but presently has support for Windows, Mac OS X, and other Unix systems using the Windows API, Cocoa, and GTK+ 3, respectively. It also aims to be Go-like: events are transmitted via channels, and the library is fully safe for concurrent use.
To use the library, place your main program code in another function and call Go(), passing that function as a parameter. (This is necessary due to threading restrictions on some environments, such as Cocoa.) Once in the function you pass to Go(), you can safely use the rest of the library. When this function returns, so does Go(), and package functions become unavailable.
Building GUIs is as simple as creating a Window, populating it with Controls, and then calling Open() on the Window. A Window only has one Control: you pack multiple Controls into a Window by arranging them in layouts (Layouts are also Controls). There are presently two Layouts, Stack and Grid, each with different semantics on sizing and placement. See their documentation.
Once a Window is open, you cannot make layout or event channel changes.
Once your Window is open, you can begin to handle events. Handling events is simple: because all events are channels exposed as exported members of the Window and Control types, simply select on them. Event channels are initialized by default. However, before you Open a Window, you can freely reassign event channels, such that multiple events trigger the same channel, making event logic more compact. You may also choose not to handle events; events are sent asynchronously so the GUI loop is not initerrupted.
Here is a simple, complete program that asks the user for their name and greets them after clicking a button.
package main
import (
"github.com/andlabs/ui"
)
func myMain() {
w := ui.NewWindow("Hello", 400, 100)
ui.AppQuit = w.Closing // treat quitting the application like closing the main window
nameField := ui.NewLineEdit("Enter Your Name Here")
button := ui.NewButton("Click Here For a Greeting")
w.Open(ui.NewVerticalStack(nameField, button))
for {
select {
case <-w.Closing: // user tries to close the window or quit the program
return
case <-button.Clicked: // user clicked the button
ui.MsgBox(w, "Hello, " + nameField.Text() + "!", "")
}
}
}
func main() {
err := ui.Go(myMain)
if err != nil {
panic(err)
}
}
On Dialogs
The following functions provide dialogs. They exist both in package scope and as methods on Window.
MsgBox()
MsgBoxError()
Dialogs opened by using the package-scope functions are modal to the entire application: the user cannot interact with any other window until they are dismissed.
Whether or not resizing Windows will still be allowed is implementation-defined; if the implementation does allow it, resizes will still work properly.
Whether or not the dialog box stays above all other Windows in the program is also implementation-defined.
Dialogs opened by using the Window methods are modal to the receiver Window only.
Attempts to interact with the receiver Window will be blocked, but all other Windows in the application can still be used properly.
The dialog box will also stay above the receiver Window.
Whether the receiver Window can be resized while the dialog box is displayed is implementation-defined, but will work properly if allowed.
If the receiver Window has not yet been created, the methods panic.
If the receiver Window has not been shown yet or is currently hidden, what the methods do is implementation-defined.
The return type also differs between the two types of functions.
Both ultimately either yield a signal that the dialog has been dismissed or a code specifying what the user decided to do with the dialog (if it has multiple choices).
The package-scope functions wait for the dialog box to be dismissed and merely return the code (or nothing if no code is needed).
The Window methods return immediately with a channel that will eventually receive either the signal or the return code.
Package ui does not close these channels, nor does it send multiple values on the same channel.
Scrollbars
The following Controls have scrolling support built in:
Listbox *
Area
All of the above controls have both horizontal and vertical scrollbars.
These scrollbars hide themselves when not needed.
[FUTURE DISCUSSIONS: scrolling programmatically, MouseEvent/KeyEvent scroll overrides]
[FUTURE PLAN: Controls that are not marked with a * in the above list can have their scrollbars disabled completely in code.]
The result of resizing the window such that the scrollbars consider themselves too small is implementation-defined.
*/
package ui

View File

@ -1,132 +0,0 @@
// 30 march 2014
package ui
/*
Mac OS X uses its own set of hardware key codes that are different from PC keyboard scancodes, but are positional (like PC keyboard scancodes). These are defined in <HIToolbox/Events.h>, a Carbon header. As far as I can tell, there's no way to include this header without either using an absolute path or linking Carbon into the program, so the constant values are used here instead.
The Cocoa docs do guarantee that -[NSEvent keyCode] results in key codes that are the same as those returned by Carbon; that is, these codes.
*/
// use uintptr to be safe
var keycodeKeys = map[uintptr]byte{
0x00: 'a',
0x01: 's',
0x02: 'd',
0x03: 'f',
0x04: 'h',
0x05: 'g',
0x06: 'z',
0x07: 'x',
0x08: 'c',
0x09: 'v',
0x0B: 'b',
0x0C: 'q',
0x0D: 'w',
0x0E: 'e',
0x0F: 'r',
0x10: 'y',
0x11: 't',
0x12: '1',
0x13: '2',
0x14: '3',
0x15: '4',
0x16: '6',
0x17: '5',
0x18: '=',
0x19: '9',
0x1A: '7',
0x1B: '-',
0x1C: '8',
0x1D: '0',
0x1E: ']',
0x1F: 'o',
0x20: 'u',
0x21: '[',
0x22: 'i',
0x23: 'p',
0x25: 'l',
0x26: 'j',
0x27: '\'',
0x28: 'k',
0x29: ';',
0x2A: '\\',
0x2B: ',',
0x2C: '/',
0x2D: 'n',
0x2E: 'm',
0x2F: '.',
0x32: '`',
0x24: '\n',
0x30: '\t',
0x31: ' ',
0x33: '\b',
}
var keycodeExtKeys = map[uintptr]ExtKey{
0x41: NDot,
0x43: NMultiply,
0x45: NAdd,
0x4B: NDivide,
0x4C: NEnter,
0x4E: NSubtract,
0x52: N0,
0x53: N1,
0x54: N2,
0x55: N3,
0x56: N4,
0x57: N5,
0x58: N6,
0x59: N7,
0x5B: N8,
0x5C: N9,
0x35: Escape,
0x60: F5,
0x61: F6,
0x62: F7,
0x63: F3,
0x64: F8,
0x65: F9,
0x67: F11,
0x6D: F10,
0x6F: F12,
0x72: Insert, // listed as the Help key but it's in the same position on an Apple keyboard as the Insert key on a Windows keyboard; thanks to SeanieB from irc.badnik.net and Psy in irc.freenode.net/#macdev for confirming they have the same code
0x73: Home,
0x74: PageUp,
0x75: Delete,
0x76: F4,
0x77: End,
0x78: F2,
0x79: PageDown,
0x7A: F1,
0x7B: Left,
0x7C: Right,
0x7D: Down,
0x7E: Up,
}
var keycodeModifiers = map[uintptr]Modifiers{
0x37: Super, // left command
0x38: Shift, // left shift
0x3A: Alt, // left option
0x3B: Ctrl, // left control
0x3C: Shift, // right shift
0x3D: Alt, // right alt
0x3E: Ctrl, // right control
// the following is not in Events.h for some reason
// thanks to Nicole and jedivulcan from irc.badnik.net
0x36: Super, // right command
}
func fromKeycode(keycode uintptr) (ke KeyEvent, ok bool) {
if key, ok := keycodeKeys[keycode]; ok {
ke.Key = key
return ke, true
}
if extkey, ok := keycodeExtKeys[keycode]; ok {
ke.ExtKey = extkey
return ke, true
}
return ke, false
}

View File

@ -1,154 +0,0 @@
// +build !darwin
// Mac OS X uses its own set of position-independent key codes
// 29 march 2014
package ui
import (
"image"
)
/*
Windows and GTK+ have a limit of 2 and 3 clicks, respectively, natively supported. Fortunately, we can simulate the double/triple-click behavior to build higher-order clicks. We can use the same algorithm Windows uses on both:
http://blogs.msdn.com/b/oldnewthing/archive/2004/10/18/243925.aspx
For GTK+, we pull the double-click time and double-click distance, which work the same as the equivalents on Windows (so the distance is in all directions), from the GtkSettings system.
On GTK+ this will also allow us to discard the GDK_BUTTON_2PRESS and GDK_BUTTON_3PRESS events, so the button press stream will be just like on other platforms.
Thanks to mclasen, garnacho_, halfline, and tristan in irc.gimp.net/#gtk+.
TODO - technically a GDK_BUTTON_3PRESS is detected in half the time as a GDK_BUTTON_2PRESS... handle?
*/
// the zero value is a reset clickCounter ready for use
// it doesn't matter that all the non-count fields are zero: the first click will fail the curButton test straightaway, so it'll return 1 and set the rest of the structure accordingly
type clickCounter struct {
curButton uint
rect image.Rectangle
prevTime uintptr
count uint
}
// x, y, xdist, ydist, and c.rect must have the same units
// so must time, maxTime, and c.prevTime
func (c *clickCounter) click(button uint, x int, y int, time uintptr, maxTime uintptr, xdist int, ydist int) uint {
if button != c.curButton { // different button; start over
c.count = 0
}
if !image.Pt(x, y).In(c.rect) { // not in the allowed region for a double-click; don't count
c.count = 0
}
if (time - c.prevTime) > maxTime { // too slow; don't count
// note the above expression; time > (c.prevTime + maxTime) can overflow!
c.count = 0
}
c.count++ // if either of the above ifs happened, this will make the click count 1; otherwise it will make the click count 2, 3, 4, 5, ...
// now we need to update the internal structures for the next test
c.curButton = button
c.prevTime = time
c.rect = image.Rect(x-xdist, y-ydist,
x+xdist, y+ydist)
return c.count
}
// call this when losing focus, etc.
func (c *clickCounter) reset() {
c.count = 0
}
/*
For position independence across international keyboard layouts, typewriter keys are read using scancodes (which are always set 1).
Windows provides the scancodes directly in the LPARAM.
GTK+ provides the scancodes directly from the underlying window system via GdkEventKey.hardware_keycode.
On X11, this is scancode + 8 (because X11 keyboard codes have a range of [8,255]).
Wayland is guaranteed to give the same result (thanks ebassi in irc.gimp.net/#gtk+).
On Linux, where evdev is used instead of polling scancodes directly from the keyboard, evdev's typewriter section key code constants are the same as scancodes anyway, so the rules above apply.
Typewriter section scancodes are the same across international keyboards with some exceptions that have been accounted for (see KeyEvent's documentation); see http://www.quadibloc.com/comp/scan.htm for details.
Non-typewriter keys can be handled safely using constants provided by the respective backend API.
Because GTK+ keysyms may or may not obey Num Lock, we also handle the 0-9 and . keys on the numeric keypad with scancodes (they match too).
*/
// use uintptr to be safe; the size of the scancode/hardware key code field on each platform is different
var scancodeKeys = map[uintptr]byte{
0x02: '1',
0x03: '2',
0x04: '3',
0x05: '4',
0x06: '5',
0x07: '6',
0x08: '7',
0x09: '8',
0x0A: '9',
0x0B: '0',
0x0C: '-',
0x0D: '=',
0x0E: '\b', // seems to be safe on GTK+; TODO safe on windows?
0x0F: '\t', // seems to be safe on GTK+; TODO safe on windows?
0x10: 'q',
0x11: 'w',
0x12: 'e',
0x13: 'r',
0x14: 't',
0x15: 'y',
0x16: 'u',
0x17: 'i',
0x18: 'o',
0x19: 'p',
0x1A: '[',
0x1B: ']',
0x1C: '\n', // seems to be safe on GTK+; TODO safe on windows?
0x1E: 'a',
0x1F: 's',
0x20: 'd',
0x21: 'f',
0x22: 'g',
0x23: 'h',
0x24: 'j',
0x25: 'k',
0x26: 'l',
0x27: ';',
0x28: '\'',
0x29: '`',
0x2B: '\\',
0x2C: 'z',
0x2D: 'x',
0x2E: 'c',
0x2F: 'v',
0x30: 'b',
0x31: 'n',
0x32: 'm',
0x33: ',',
0x34: '.',
0x35: '/',
0x39: ' ',
}
var scancodeExtKeys = map[uintptr]ExtKey{
0x47: N7,
0x48: N8,
0x49: N9,
0x4B: N4,
0x4C: N5,
0x4D: N6,
0x4F: N1,
0x50: N2,
0x51: N3,
0x52: N0,
0x53: NDot,
}
func fromScancode(scancode uintptr) (ke KeyEvent, ok bool) {
if key, ok := scancodeKeys[scancode]; ok {
ke.Key = key
return ke, true
}
if extkey, ok := scancodeExtKeys[scancode]; ok {
ke.ExtKey = extkey
return ke, true
}
return ke, false
}

Binary file not shown.

View File

@ -1,178 +0,0 @@
// WINDOWS (works)
style: _LBS_NOTIFY | _LBS_NOINTEGRALHEIGHT | _WS_HSCROLL | _WS_VSCROLL | controlstyle,
xstyle: _WS_EX_CLIENTEDGE | controlxstyle,
altStyle: _LBS_EXTENDEDSEL | _LBS_NOTIFY | _LBS_NOINTEGRALHEIGHT | _WS_HSCROLL | _WS_VSCROLL | controlstyle,
(call recalcListboxWidth() from sysData.append(), sysData.insertBefore(), and sysData.delete())
// List Boxes do not dynamically handle horizontal scrollbars.
// We have to manually handle this ourselves.
// TODO make this run on the main thread when we switch to uitask taking function literals
// TODO this is inefficient; some caching would be nice
func recalcListboxWidth(hwnd _HWND) {
var size _SIZE
ret := make(chan uiret)
defer close(ret)
uitask <- &uimsg{
call: _sendMessage,
p: []uintptr{
uintptr(hwnd),
uintptr(_LB_GETCOUNT),
uintptr(0),
uintptr(0),
},
ret: ret,
}
r := <-ret
if r.ret == uintptr(_LB_ERR) { // failure
panic(fmt.Errorf("error getting number of items for Listbox width calculations: %v", r.err))
}
n := int(r.ret)
uitask <- &uimsg{
call: _getWindowDC,
p: []uintptr{uintptr(hwnd)},
ret: ret,
}
r = <-ret
if r.ret == 0 { // failure
panic(fmt.Errorf("error getting DC for Listbox width calculations: %v", r.err))
}
dc := _HANDLE(r.ret)
uitask <- &uimsg{
call: _selectObject,
p: []uintptr{
uintptr(dc),
uintptr(controlFont),
},
ret: ret,
}
r = <-ret
if r.ret == 0 { // failure
panic(fmt.Errorf("error loading control font into device context for Listbox width calculation: %v", r.err))
}
hextent := uintptr(0)
for i := 0; i < n; i++ {
uitask <- &uimsg{
call: _sendMessage,
p: []uintptr{
uintptr(hwnd),
uintptr(_LB_GETTEXTLEN),
uintptr(_WPARAM(i)),
uintptr(0),
},
ret: ret,
}
r := <-ret
if r.ret == uintptr(_LB_ERR) {
panic("UI library internal error: LB_ERR from LB_GETTEXTLEN in what we know is a valid listbox index (came from LB_GETSELITEMS)")
}
str := make([]uint16, r.ret)
uitask <- &uimsg{
call: _sendMessage,
p: []uintptr{
uintptr(hwnd),
uintptr(_LB_GETTEXT),
uintptr(_WPARAM(i)),
uintptr(_LPARAM(unsafe.Pointer(&str[0]))),
},
ret: ret,
}
r = <-ret
if r.ret == uintptr(_LB_ERR) {
panic("UI library internal error: LB_ERR from LB_GETTEXT in what we know is a valid listbox index (came from LB_GETSELITEMS)")
}
// r.ret is still the length of the string; this time without the null terminator
uitask <- &uimsg{
call: _getTextExtentPoint32,
p: []uintptr{
uintptr(dc),
uintptr(unsafe.Pointer(&str[0])),
r.ret,
uintptr(unsafe.Pointer(&size)),
},
ret: ret,
}
r = <-ret
if r.ret == 0 { // failure
panic(fmt.Errorf("error getting width of item %d text for Listbox width calculation: %v", i, r.err))
}
if hextent < uintptr(size.cx) {
hextent = uintptr(size.cx)
}
}
uitask <- &uimsg{
call: _releaseDC,
p: []uintptr{
uintptr(hwnd),
uintptr(dc),
},
ret: ret,
}
r = <-ret
if r.ret == 0 { // failure
panic(fmt.Errorf("error releasing DC for Listbox width calculations: %v", r.err))
}
uitask <- &uimsg{
call: _sendMessage,
p: []uintptr{
uintptr(hwnd),
uintptr(_LB_SETHORIZONTALEXTENT),
hextent,
uintptr(0),
},
ret: ret,
}
<-ret
}
// DARWIN (does not work)
// NSTableView is actually in a NSScrollView so we have to get it out first
// NSTableView and NSTableColumn both provide sizeToFit methods, but they don't do what we want (NSTableView's sizes to fit the parent; NSTableColumn's sizes to fit the column header)
// We have to get the width manually; see also http://stackoverflow.com/questions/4674163/nstablecolumn-size-to-fit-contents
// We can use the NSTableView sizeToFit to get the height, though.
// TODO this is inefficient!
// TODO move this to listbox_darwin.go
var (
_dataCellForRow = sel_getUid("dataCellForRow:")
_cellSize = sel_getUid("cellSize")
_setMinWidth = sel_getUid("setMinWidth:")
_setWidth = sel_getUid("setWidth:")
)
func listboxPrefSize(control C.id) (width int, height int) {
var maxwidth C.intptr_t
listbox := listboxInScrollView(control)
_, height = controlPrefSize(listbox)
column := listboxTableColumn(listbox)
n := C.objc_msgSend_intret_noargs(listbox, _numberOfRows)
for i := C.intptr_t(0); i < n; i++ {
cell := C.objc_msgSend_int(column, _dataCellForRow, i)
csize := C.objc_msgSend_stret_size_noargs(cell, _cellSize)
if maxwidth < csize.width {
maxwidth = csize.width
}
}
// and in order for horizontal scrolling to work, we need to set the column width to this
C.objc_msgSend_cgfloat(column, _setMinWidth, C.double(maxwidth))
C.objc_msgSend_cgfloat(column, _setWidth, C.double(maxwidth))
return int(maxwidth), height
}
func (s *sysData) setRect(x int, y int, width int, height int, winheight int) error {
// winheight - y because (0,0) is the bottom-left corner of the window and not the top-left corner
// (winheight - y) - height because (x, y) is the bottom-left corner of the control and not the top-left
C.objc_msgSend_rect(s.id, _setFrame,
C.intptr_t(x), C.intptr_t((winheight - y) - height), C.intptr_t(width), C.intptr_t(height))
// TODO having this here is a hack; split it into a separate function in listbox_darwin.go
// the NSTableView:NSTableColumn ratio is what determines horizontal scrolling; see http://stackoverflow.com/questions/7050497/enable-scrolling-for-nstableview
if s.ctype == c_listbox {
listbox := listboxInScrollView(s.id)
C.objc_msgSend_rect(listbox, _setFrame,
0, 0, C.intptr_t(width), C.intptr_t(height))
}
return nil
}

View File

@ -1,47 +0,0 @@
func (s *sysData) setWindowSize(width int, height int) error {
var rect _RECT
ret := make(chan uiret)
defer close(ret)
// we need the size to cover only the client rect
// unfortunately, Windows only provides general client->window rect conversion functions (AdjustWindowRect()/AdjustWindowRectEx()), not any that can pull from an existing window
// so as long as we don't change the window styles during execution we're good
// now we tell it to adjust (0,0)..(width,height); this will give us the approrpiate rect to get the proper width/height from
// then we call SetWindowPos() to set the size without moving the window
// see also: http://blogs.msdn.com/b/oldnewthing/archive/2003/09/11/54885.aspx
// TODO do the WM_NCCALCSIZE stuff on that post when adding menus
rect.Right = int32(width)
rect.Bottom = int32(height)
uitask <- &uimsg{
call: _adjustWindowRectEx,
p: []uintptr{
uintptr(unsafe.Pointer(&rect)),
uintptr(classTypes[c_window].style),
uintptr(_FALSE), // TODO change this when adding menus
uintptr(classTypes[c_window].xstyle),
},
ret: ret,
}
r := <-ret
if r.ret == 0 { // failure
panic(fmt.Errorf("error computing window rect from client rect for resize: %v", r.err))
}
uitask <- &uimsg{
call: _setWindowPos,
p: []uintptr{
uintptr(s.hwnd),
uintptr(_NULL), // not changing the Z-order
uintptr(0),
uintptr(0),
uintptr(rect.Right - rect.Left),
uintptr(rect.Bottom - rect.Top),
uintptr(_SWP_NOMOVE | _SWP_NOZORDER),
},
ret: ret,
}
r = <-ret
if r.ret == 0 { // failure
return fmt.Errorf("error actually resizing window: %v", r.err)
}
return nil
}

245
grid.go
View File

@ -1,245 +0,0 @@
// 25 february 2014
package ui
import (
"fmt"
)
// A Grid arranges Controls in a two-dimensional grid.
// The height of each row and the width of each column is the maximum preferred height and width (respectively) of all the controls in that row or column (respectively).
// Controls are aligned to the top left corner of each cell.
// All Controls in a Grid maintain their preferred sizes by default; if a Control is marked as being "filling", it will be sized to fill its cell.
// Even if a Control is marked as filling, its preferred size is used to calculate cell sizes.
// One Control can be marked as "stretchy": when the Window containing the Grid is resized, the cell containing that Control resizes to take any remaining space; its row and column are adjusted accordingly (so other filling controls in the same row and column will fill to the new height and width, respectively).
// A stretchy Control implicitly fills its cell.
// All cooridnates in a Grid are given in (row,column) form with (0,0) being the top-left cell.
type Grid struct {
created bool
controls [][]Control
filling [][]bool
stretchyrow, stretchycol int
widths, heights [][]int // caches to avoid reallocating each time
rowheights, colwidths []int
}
// NewGrid creates a new Grid with the given Controls.
// NewGrid needs to know the number of Controls in a row (alternatively, the number of columns); it will determine the number in a column from the number of Controls given.
// NewGrid panics if not given a full grid of Controls.
// Example:
// grid := NewGrid(3,
// control00, control01, control02,
// control10, control11, control12,
// control20, control21, control22)
func NewGrid(nPerRow int, controls ...Control) *Grid {
if len(controls)%nPerRow != 0 {
panic(fmt.Errorf("incomplete grid given to NewGrid() (not enough controls to evenly divide %d controls into rows of %d controls each)", len(controls), nPerRow))
}
nRows := len(controls) / nPerRow
cc := make([][]Control, nRows)
cf := make([][]bool, nRows)
cw := make([][]int, nRows)
ch := make([][]int, nRows)
i := 0
for row := 0; row < nRows; row++ {
cc[row] = make([]Control, nPerRow)
cf[row] = make([]bool, nPerRow)
cw[row] = make([]int, nPerRow)
ch[row] = make([]int, nPerRow)
for x := 0; x < nPerRow; x++ {
cc[row][x] = controls[i]
i++
}
}
return &Grid{
controls: cc,
filling: cf,
stretchyrow: -1,
stretchycol: -1,
widths: cw,
heights: ch,
rowheights: make([]int, nRows),
colwidths: make([]int, nPerRow),
}
}
// SetFilling marks the given Control of the Grid as filling its cell instead of staying at its preferred size.
// This function cannot be called after the Window that contains the Grid has been created.
// It panics if the given coordinate is invalid.
func (g *Grid) SetFilling(row int, column int) {
if g.created {
panic(fmt.Errorf("Grid.SetFilling() called after window create"))
}
if row < 0 || column < 0 || row > len(g.filling) || column > len(g.filling[row]) {
panic(fmt.Errorf("coordinate (%d,%d) out of range passed to Grid.SetFilling()", row, column))
}
g.filling[row][column] = true
}
// SetStretchy marks the given Control of the Grid as stretchy.
// Stretchy implies filling.
// Only one control can be stretchy per Grid; calling SetStretchy multiple times merely changes which control is stretchy.
// This function cannot be called after the Window that contains the Grid has been created.
// It panics if the given coordinate is invalid.
func (g *Grid) SetStretchy(row int, column int) {
if g.created {
panic(fmt.Errorf("Grid.SetFilling() called after window create"))
}
if row < 0 || column < 0 || row > len(g.filling) || column > len(g.filling[row]) {
panic(fmt.Errorf("coordinate (%d,%d) out of range passed to Grid.SetStretchy()", row, column))
}
g.stretchyrow = row
g.stretchycol = column
// don't set filling here in case we call SetStretchy() multiple times; the filling is committed in make() below
}
func (g *Grid) make(window *sysData) error {
// commit filling for the stretchy control now (see SetStretchy() above)
if g.stretchyrow != -1 && g.stretchycol != -1 {
g.filling[g.stretchyrow][g.stretchycol] = true
} else if (g.stretchyrow == -1 && g.stretchycol != -1) || // sanity check
(g.stretchyrow != -1 && g.stretchycol == -1) {
panic(fmt.Errorf("internal inconsistency in Grid: stretchy (%d,%d) impossible (one component, not both, is -1/no stretchy control) in Grid.make()", g.stretchyrow, g.stretchycol))
}
for row, xcol := range g.controls {
for col, c := range xcol {
err := c.make(window)
if err != nil {
return fmt.Errorf("error adding control (%d,%d) to Grid: %v", row, col, err)
}
}
}
g.created = true
return nil
}
func (g *Grid) allocate(x int, y int, width int, height int, d *sysSizeData) (allocations []*allocation) {
max := func(a int, b int) int {
if a > b {
return a
}
return b
}
var current *allocation // for neighboring
// TODO return if nControls == 0?
// before we do anything, steal the margin so nested Stacks/Grids don't double down
xmargin := d.xmargin
ymargin := d.ymargin
d.xmargin = 0
d.ymargin = 0
// 0) inset the available rect by the margins and needed padding
x += xmargin
y += ymargin
width -= xmargin * 2
height -= ymargin * 2
width -= (len(g.colwidths) - 1) * d.xpadding
height -= (len(g.rowheights) - 1) * d.ypadding
// 1) clear data structures
for i := range g.rowheights {
g.rowheights[i] = 0
}
for i := range g.colwidths {
g.colwidths[i] = 0
}
// 2) get preferred sizes; compute row/column sizes
for row, xcol := range g.controls {
for col, c := range xcol {
w, h := c.preferredSize(d)
g.widths[row][col] = w
g.heights[row][col] = h
g.rowheights[row] = max(g.rowheights[row], h)
g.colwidths[col] = max(g.colwidths[col], w)
}
}
// 3) handle the stretchy control
if g.stretchyrow != -1 && g.stretchycol != -1 {
for i, w := range g.colwidths {
if i != g.stretchycol {
width -= w
}
}
for i, h := range g.rowheights {
if i != g.stretchyrow {
height -= h
}
}
g.colwidths[g.stretchycol] = width
g.rowheights[g.stretchyrow] = height
}
// 4) draw
startx := x
for row, xcol := range g.controls {
current = nil // reset on new columns
for col, c := range xcol {
w := g.widths[row][col]
h := g.heights[row][col]
if g.filling[row][col] {
w = g.colwidths[col]
h = g.rowheights[row]
}
as := c.allocate(x, y, w, h, d)
if current != nil { // connect first left to first right
current.neighbor = c
}
if len(as) != 0 {
current = as[0] // next left is first subwidget
} else {
current = nil // spaces don't have allocation data
}
allocations = append(allocations, as...)
x += g.colwidths[col] + d.xpadding
}
x = startx
y += g.rowheights[row] + d.ypadding
}
return
}
// filling and stretchy are ignored for preferred size calculation
// We don't consider the margins here, but will need to if Window.SizeToFit() is ever made a thing.
func (g *Grid) preferredSize(d *sysSizeData) (width int, height int) {
max := func(a int, b int) int {
if a > b {
return a
}
return b
}
width -= (len(g.colwidths) - 1) * d.xpadding
height -= (len(g.rowheights) - 1) * d.ypadding
// 1) clear data structures
for i := range g.rowheights {
g.rowheights[i] = 0
}
for i := range g.colwidths {
g.colwidths[i] = 0
}
// 2) get preferred sizes; compute row/column sizes
for row, xcol := range g.controls {
for col, c := range xcol {
w, h := c.preferredSize(d)
g.widths[row][col] = w
g.heights[row][col] = h
g.rowheights[row] = max(g.rowheights[row], h)
g.colwidths[col] = max(g.colwidths[col], w)
}
}
// 3) now compute
for _, w := range g.colwidths {
width += w
}
for _, h := range g.rowheights {
height += h
}
return width, height
}
func (g *Grid) commitResize(c *allocation, d *sysSizeData) {
// this is to satisfy Control; nothing to do here
}
func (g *Grid) getAuxResizeInfo(d *sysSizeData) {
// this is to satisfy Control; nothing to do here
}

View File

@ -1,25 +0,0 @@
/* 16 march 2014 */
/* This header file is a convenience to ensure that the compatibility flags below are included in all Go files that include <gtk/gtk.h> */
/*
MIN_REQUIRED signals that programs are expected to run on at least GLib 2.32/GTK+ 3.4 and thus deprectation warnings for newer versions (such as gtk_scrolled_window_add_with_viewport() being deprecated in GTK+ 3.8) should be suppressed.
MAX_ALLOWED signals that programs will not use features introduced in newer versions of GLib/GTK+ and that the compiler should warn us if we slip.
Thanks to desrt in irc.gimp.net/#gtk+
*/
/* GLib/GObject */
#define GLIB_VERSION_MIN_REQUIRED GLIB_VERSION_2_32
#define GLIB_VERSION_MAX_ALLOWED GLIB_VERSION_2_32
/* GDK/GTK+ */
#define GDK_VERSION_MIN_REQUIRED GDK_VERSION_3_4
#define GDK_VERSION_MAX_ALLOWED GDK_VERSION_3_4
/* TODO are there equivalent compatibility macros for the other components of GTK+? Specifically:
cairo
gdk-pixbuf
*/
#include <stdlib.h>
#include <gtk/gtk.h>

View File

@ -1,290 +0,0 @@
// +build !windows,!darwin,!plan9
// this is manual but either this or the opposite (listing all valid systems) really are the only ways to do it; proposals for a 'unix' tag were rejected (https://code.google.com/p/go/issues/detail?id=6325)
// 16 february 2014
package ui
import (
"fmt"
"unsafe"
)
// #include "gtk_unix.h"
// static inline void gtkSetComboBoxArbitrarilyResizeable(GtkWidget *w)
// {
// /* we can safely assume that the cell renderers of a GtkComboBoxText are a single GtkCellRendererText by default */
// /* thanks mclasen and tristan in irc.gimp.net/#gtk+ */
// GtkCellLayout *cbox = (GtkCellLayout *) w;
// GList *renderer;
//
// renderer = gtk_cell_layout_get_cells(cbox);
// g_object_set(renderer->data,
// "ellipsize-set", TRUE, /* with no ellipsizing or wrapping, the combobox won't resize */
// "ellipsize", PANGO_ELLIPSIZE_END,
// "width-chars", 0,
// NULL);
// g_list_free(renderer);
// }
import "C"
var gtkStyles = []byte(`
/*
this first one is for ProgressBar so we can arbitrarily resize it
min-horizontal-bar-width is a style property; we do it through CSS
thanks tristan in irc.gimp.net/#gtk+
*/
* {
-GtkProgressBar-min-horizontal-bar-width: 1;
}
`)
func gtk_init() error {
var err *C.GError = nil // redundant in Go, but let's explicitly assign it anyway
// gtk_init_with_args() gives us error info (thanks chpe in irc.gimp.net/#gtk+)
// don't worry about GTK+'s command-line arguments; they're also available as environment variables (thanks mclasen in irc.gimp.net/#gtk+)
result := C.gtk_init_with_args(nil, nil, nil, nil, nil, &err)
if result == C.FALSE {
return fmt.Errorf("error actually initilaizing GTK+: %s", fromgstr(err.message))
}
// now apply our custom program-global styles
provider := C.gtk_css_provider_new()
if C.gtk_css_provider_load_from_data(provider, (*C.gchar)(unsafe.Pointer(&gtkStyles[0])), C.gssize(len(gtkStyles)), &err) == C.FALSE {
return fmt.Errorf("error applying package ui's custom program-global styles to GTK+: %v", fromgstr(err.message))
}
// GDK (at least as far back as GTK+ 3.4, but officially documented as of 3.10) merges all screens into one big one, so we don't need to worry about multimonitor
// thanks to baedert and mclasen in irc.gimp.net/#gtk+
C.gtk_style_context_add_provider_for_screen(C.gdk_screen_get_default(),
(*C.GtkStyleProvider)(unsafe.Pointer(provider)),
C.GTK_STYLE_PROVIDER_PRIORITY_USER)
return nil
}
func gtk_main_quit() {
C.gtk_main_quit()
}
func gtk_window_new() *C.GtkWidget {
return C.gtk_window_new(C.GTK_WINDOW_TOPLEVEL)
}
func gtk_widget_show(widget *C.GtkWidget) {
C.gtk_widget_show_all(widget)
}
func gtk_widget_hide(widget *C.GtkWidget) {
C.gtk_widget_hide(widget)
}
func gtk_window_set_title(window *C.GtkWidget, title string) {
ctitle := C.CString(title)
defer C.free(unsafe.Pointer(ctitle))
C.gtk_window_set_title(togtkwindow(window), togstr(ctitle))
}
func gtk_window_get_title(window *C.GtkWidget) string {
return fromgstr(C.gtk_window_get_title(togtkwindow(window)))
}
func gtk_window_resize(window *C.GtkWidget, width int, height int) {
C.gtk_window_resize(togtkwindow(window), C.gint(width), C.gint(height))
}
func gtk_window_get_size(window *C.GtkWidget) (int, int) {
var width, height C.gint
C.gtk_window_get_size(togtkwindow(window), &width, &height)
return int(width), int(height)
}
// this should allow us to resize the window arbitrarily
// thanks to Company in irc.gimp.net/#gtk+
func gtkNewWindowLayout() *C.GtkWidget {
layout := C.gtk_layout_new(nil, nil)
scrollarea := C.gtk_scrolled_window_new((*C.GtkAdjustment)(nil), (*C.GtkAdjustment)(nil))
C.gtk_container_add((*C.GtkContainer)(unsafe.Pointer(scrollarea)), layout)
// never show scrollbars; we're just doing this to allow arbitrary resizes
C.gtk_scrolled_window_set_policy((*C.GtkScrolledWindow)(unsafe.Pointer(scrollarea)),
C.GTK_POLICY_NEVER, C.GTK_POLICY_NEVER)
return scrollarea
}
func gtk_container_add(container *C.GtkWidget, widget *C.GtkWidget) {
C.gtk_container_add(togtkcontainer(container), widget)
}
func gtkAddWidgetToLayout(container *C.GtkWidget, widget *C.GtkWidget) {
layout := C.gtk_bin_get_child((*C.GtkBin)(unsafe.Pointer(container)))
C.gtk_container_add((*C.GtkContainer)(unsafe.Pointer(layout)), widget)
}
func gtkMoveWidgetInLayout(container *C.GtkWidget, widget *C.GtkWidget, x int, y int) {
layout := C.gtk_bin_get_child((*C.GtkBin)(unsafe.Pointer(container)))
C.gtk_layout_move((*C.GtkLayout)(unsafe.Pointer(layout)), widget,
C.gint(x), C.gint(y))
}
func gtk_widget_set_size_request(widget *C.GtkWidget, width int, height int) {
C.gtk_widget_set_size_request(widget, C.gint(width), C.gint(height))
}
func gtk_button_new() *C.GtkWidget {
return C.gtk_button_new()
}
func gtk_button_set_label(button *C.GtkWidget, label string) {
clabel := C.CString(label)
defer C.free(unsafe.Pointer(clabel))
C.gtk_button_set_label(togtkbutton(button), togstr(clabel))
}
func gtk_button_get_label(button *C.GtkWidget) string {
return fromgstr(C.gtk_button_get_label(togtkbutton(button)))
}
func gtk_check_button_new() *C.GtkWidget {
return C.gtk_check_button_new()
}
func gtk_toggle_button_get_active(widget *C.GtkWidget) bool {
return fromgbool(C.gtk_toggle_button_get_active(togtktogglebutton(widget)))
}
func gtk_toggle_button_set_active(widget *C.GtkWidget, checked bool) {
C.gtk_toggle_button_set_active(togtktogglebutton(widget), togbool(checked))
}
func gtk_combo_box_text_new() *C.GtkWidget {
w := C.gtk_combo_box_text_new()
C.gtkSetComboBoxArbitrarilyResizeable(w)
return w
}
func gtk_combo_box_text_new_with_entry() *C.GtkWidget {
w := C.gtk_combo_box_text_new_with_entry()
// we need to set the entry's width-chars to achieve the arbitrary sizing we want
// thanks to tristan in irc.gimp.net/#gtk+
// in my tests, this is sufficient to get the effect we want; setting the cell renderer isn't necessary like it is with an entry-less combobox
entry := togtkentry(C.gtk_bin_get_child((*C.GtkBin)(unsafe.Pointer(w))))
C.gtk_entry_set_width_chars(entry, 0)
return w
}
func gtk_combo_box_text_get_active_text(widget *C.GtkWidget) string {
return fromgstr(C.gtk_combo_box_text_get_active_text(togtkcombobox(widget)))
}
func gtk_combo_box_text_append_text(widget *C.GtkWidget, text string) {
ctext := C.CString(text)
defer C.free(unsafe.Pointer(ctext))
C.gtk_combo_box_text_append_text(togtkcombobox(widget), togstr(ctext))
}
func gtk_combo_box_text_insert_text(widget *C.GtkWidget, index int, text string) {
ctext := C.CString(text)
defer C.free(unsafe.Pointer(ctext))
C.gtk_combo_box_text_insert_text(togtkcombobox(widget), C.gint(index), togstr(ctext))
}
func gtk_combo_box_get_active(widget *C.GtkWidget) int {
cb := (*C.GtkComboBox)(unsafe.Pointer(widget))
return int(C.gtk_combo_box_get_active(cb))
}
func gtk_combo_box_text_remove(widget *C.GtkWidget, index int) {
C.gtk_combo_box_text_remove(togtkcombobox(widget), C.gint(index))
}
func gtkComboBoxLen(widget *C.GtkWidget) int {
cb := (*C.GtkComboBox)(unsafe.Pointer(widget))
model := C.gtk_combo_box_get_model(cb)
// this is the same as with a Listbox so
return gtkTreeModelListLen(model)
}
func gtk_entry_new() *C.GtkWidget {
e := C.gtk_entry_new()
// allows the GtkEntry to be resized with the window smaller than what it thinks the size should be
// thanks to Company in irc.gimp.net/#gtk+
C.gtk_entry_set_width_chars(togtkentry(e), 0)
return e
}
func gtkPasswordEntryNew() *C.GtkWidget {
e := gtk_entry_new()
C.gtk_entry_set_visibility(togtkentry(e), C.FALSE)
return e
}
func gtk_entry_set_text(widget *C.GtkWidget, text string) {
ctext := C.CString(text)
defer C.free(unsafe.Pointer(ctext))
C.gtk_entry_set_text(togtkentry(widget), togstr(ctext))
}
func gtk_entry_get_text(widget *C.GtkWidget) string {
return fromgstr(C.gtk_entry_get_text(togtkentry(widget)))
}
var _emptystring = [1]C.gchar{0}
var emptystring = &_emptystring[0]
func gtk_misc_set_alignment(widget *C.GtkWidget, x float64, y float64) {
C.gtk_misc_set_alignment((*C.GtkMisc)(unsafe.Pointer(widget)), C.gfloat(x), C.gfloat(y))
}
func gtk_label_new() *C.GtkWidget {
label := C.gtk_label_new(emptystring)
C.gtk_label_set_line_wrap(togtklabel(label), C.FALSE) // turn off line wrap
// don't call gtk_label_set_line_wrap_mode(); there's no "wrap none" value there anyway
C.gtk_label_set_ellipsize(togtklabel(label), C.PANGO_ELLIPSIZE_NONE) // turn off ellipsizing; this + line wrapping above will guarantee cutoff as documented
// there's a function gtk_label_set_justify() that indicates GTK_JUSTIFY_LEFT is the default
// but this actually is NOT the control justification, just the multi-line justification
// so we need to do THIS instead
// this will valign to the center, which is what the HIG says (https://developer.gnome.org/hig-book/3.4/design-text-labels.html.en) for all controls that label to the side; thankfully this means we don't need to do any extra positioning magic
// this will also valign to the top
// thanks to mclasen in irc.gimp.net/#gtk+
gtk_misc_set_alignment(label, 0, 0.5)
return label
}
func gtk_label_new_standalone() *C.GtkWidget {
label := gtk_label_new()
// this will valign to the top
gtk_misc_set_alignment(label, 0, 0)
return label
}
func gtk_label_set_text(widget *C.GtkWidget, text string) {
ctext := C.CString(text)
defer C.free(unsafe.Pointer(ctext))
C.gtk_label_set_text(togtklabel(widget), togstr(ctext))
}
func gtk_label_get_text(widget *C.GtkWidget) string {
return fromgstr(C.gtk_label_get_text(togtklabel(widget)))
}
func gtk_widget_get_preferred_size(widget *C.GtkWidget) (minWidth int, minHeight int, natWidth int, natHeight int) {
var minimum, natural C.GtkRequisition
C.gtk_widget_get_preferred_size(widget, &minimum, &natural)
return int(minimum.width), int(minimum.height),
int(natural.width), int(natural.height)
}
func gtk_progress_bar_new() *C.GtkWidget {
return C.gtk_progress_bar_new()
}
func gtk_progress_bar_set_fraction(w *C.GtkWidget, percent int) {
p := C.gdouble(percent) / 100
C.gtk_progress_bar_set_fraction(togtkprogressbar(w), p)
}
func gtk_progress_bar_pulse(w *C.GtkWidget) {
C.gtk_progress_bar_pulse(togtkprogressbar(w))
}

View File

@ -1,109 +0,0 @@
// +build !windows,!darwin,!plan9
// 17 february 2014
package ui
import (
"unsafe"
)
// this file contains functions that wrap around complex pointer casts to satisfy GTK+'s dumb type aliasing system
// fromxxx() converts from GTK+ type to Go type
// toxxxx() converts from Go type to GTK+ type
// Listbox casts are stored in listbox_unix.go
// #include "gtk_unix.h"
import "C"
func fromgbool(b C.gboolean) bool {
return b != C.FALSE
}
func togbool(b bool) C.gboolean {
if b {
return C.TRUE
}
return C.FALSE
}
func fromgstr(what *C.gchar) string {
cstr := (*C.char)(unsafe.Pointer(what))
return C.GoString(cstr)
}
func togstr(what *C.char) *C.gchar {
return (*C.gchar)(unsafe.Pointer(what))
}
func fromgtkwindow(x *C.GtkWindow) *C.GtkWidget {
return (*C.GtkWidget)(unsafe.Pointer(x))
}
func togtkwindow(what *C.GtkWidget) *C.GtkWindow {
return (*C.GtkWindow)(unsafe.Pointer(what))
}
func fromgtkcontainer(x *C.GtkContainer) *C.GtkWidget {
return (*C.GtkWidget)(unsafe.Pointer(x))
}
func togtkcontainer(what *C.GtkWidget) *C.GtkContainer {
return (*C.GtkContainer)(unsafe.Pointer(what))
}
func fromgtklayout(x *C.GtkLayout) *C.GtkWidget {
return (*C.GtkWidget)(unsafe.Pointer(x))
}
func togtklayout(what *C.GtkWidget) *C.GtkLayout {
return (*C.GtkLayout)(unsafe.Pointer(what))
}
func fromgtkbutton(x *C.GtkButton) *C.GtkWidget {
return (*C.GtkWidget)(unsafe.Pointer(x))
}
func togtkbutton(what *C.GtkWidget) *C.GtkButton {
return (*C.GtkButton)(unsafe.Pointer(what))
}
func fromgtktogglebutton(x *C.GtkToggleButton) *C.GtkWidget {
return (*C.GtkWidget)(unsafe.Pointer(x))
}
func togtktogglebutton(what *C.GtkWidget) *C.GtkToggleButton {
return (*C.GtkToggleButton)(unsafe.Pointer(what))
}
func fromgtkcombobox(x *C.GtkComboBoxText) *C.GtkWidget {
return (*C.GtkWidget)(unsafe.Pointer(x))
}
func togtkcombobox(what *C.GtkWidget) *C.GtkComboBoxText {
return (*C.GtkComboBoxText)(unsafe.Pointer(what))
}
func fromgtkentry(x *C.GtkEntry) *C.GtkWidget {
return (*C.GtkWidget)(unsafe.Pointer(x))
}
func togtkentry(what *C.GtkWidget) *C.GtkEntry {
return (*C.GtkEntry)(unsafe.Pointer(what))
}
func fromgtklabel(x *C.GtkLabel) *C.GtkWidget {
return (*C.GtkWidget)(unsafe.Pointer(x))
}
func togtklabel(what *C.GtkWidget) *C.GtkLabel {
return (*C.GtkLabel)(unsafe.Pointer(what))
}
func fromgtkprogressbar(x *C.GtkProgressBar) *C.GtkWidget {
return (*C.GtkWidget)(unsafe.Pointer(x))
}
func togtkprogressbar(what *C.GtkWidget) *C.GtkProgressBar {
return (*C.GtkProgressBar)(unsafe.Pointer(what))
}

View File

@ -1,85 +0,0 @@
// 8 february 2014
package ui
import (
"fmt"
"unsafe"
)
var (
hInstance _HANDLE
nCmdShow int
)
// TODO is this documented?
func getWinMainhInstance() (err error) {
r1, _, err := kernel32.NewProc("GetModuleHandleW").Call(uintptr(_NULL))
if r1 == 0 { // failure
return err
}
hInstance = _HANDLE(r1)
return nil
}
// this is what MinGW-w64 does (for instance, http://sourceforge.net/p/mingw-w64/code/6604/tree/trunk/mingw-w64-crt/crt/crtexe.c#l320); Burgundy in irc.freenode.net/#winapi said that the Visual C++ runtime does this too
func getWinMainnCmdShow() {
var info struct {
cb uint32
lpReserved *uint16
lpDesktop *uint16
lpTitle *uint16
dwX uint32
dwY uint32
dwXSize uint32
dwYSzie uint32
dwXCountChars uint32
dwYCountChars uint32
dwFillAttribute uint32
dwFlags uint32
wShowWindow uint16
cbReserved2 uint16
lpReserved2 *byte
hStdInput _HANDLE
hStdOutput _HANDLE
hStdError _HANDLE
}
// does not fail according to MSDN
kernel32.NewProc("GetStartupInfoW").Call(uintptr(unsafe.Pointer(&info)))
if info.dwFlags&_STARTF_USESHOWWINDOW != 0 {
nCmdShow = int(info.wShowWindow)
} else {
nCmdShow = _SW_SHOWDEFAULT
}
}
func doWindowsInit() (err error) {
err = getWinMainhInstance()
if err != nil {
return fmt.Errorf("error getting WinMain hInstance: %v", err)
}
getWinMainnCmdShow()
err = initWndClassInfo()
if err != nil {
return fmt.Errorf("error initializing standard window class auxiliary info: %v", err)
}
err = registerStdWndClass()
if err != nil {
return fmt.Errorf("error registering standard window class (for Window): %v", err)
}
err = registerAreaWndClass()
if err != nil {
return fmt.Errorf("error registering Area window class: %v", err)
}
err = getStandardWindowFonts()
if err != nil {
return fmt.Errorf("error getting standard window fonts: %v", err)
}
err = initCommonControls()
if err != nil {
return fmt.Errorf("error initializing Common Controls (comctl32.dll): %v", err)
}
// others go here
return nil // all ready to go
}

View File

@ -1,83 +0,0 @@
// 14 february 2014
package ui
// A Label is a static line of text used to mark other controls.
// Label text is drawn on a single line; text that does not fit is truncated.
// A Label can appear in one of two places: bound to a control or standalone.
// This determines the vertical alignment of the label.
type Label struct {
created bool
sysData *sysData
initText string
standalone bool
}
// NewLabel creates a new Label with the specified text.
// The label is set to be bound to a control, so its vertical position depends on its vertical cell size in an implementation-defined manner.
func NewLabel(text string) *Label {
return &Label{
sysData: mksysdata(c_label),
initText: text,
}
}
// NewStandaloneLabel creates a new Label with the specified text.
// The label is set to be standalone, so its vertical position will always be at the top of the vertical space assigned to it.
func NewStandaloneLabel(text string) *Label {
return &Label{
sysData: mksysdata(c_label),
initText: text,
standalone: true,
}
}
// SetText sets the Label's text.
func (l *Label) SetText(text string) {
if l.created {
l.sysData.setText(text)
return
}
l.initText = text
}
// Text returns the Label's text.
func (l *Label) Text() string {
if l.created {
return l.sysData.text()
}
return l.initText
}
func (l *Label) make(window *sysData) error {
l.sysData.alternate = l.standalone
err := l.sysData.make(window)
if err != nil {
return err
}
l.sysData.setText(l.initText)
l.created = true
return nil
}
func (l *Label) allocate(x int, y int, width int, height int, d *sysSizeData) []*allocation {
return []*allocation{&allocation{
x: x,
y: y,
width: width,
height: height,
this: l,
}}
}
func (l *Label) preferredSize(d *sysSizeData) (width int, height int) {
return l.sysData.preferredSize(d)
}
func (l *Label) commitResize(a *allocation, d *sysSizeData) {
l.sysData.commitResize(a, d)
}
func (l *Label) getAuxResizeInfo(d *sysSizeData) {
l.sysData.getAuxResizeInfo(d)
}

View File

@ -1,77 +0,0 @@
// 14 february 2014
package ui
// A LineEdit is a control which allows you to enter a single line of text.
type LineEdit struct {
created bool
sysData *sysData
initText string
password bool
}
// NewLineEdit makes a new LineEdit with the specified text.
func NewLineEdit(text string) *LineEdit {
return &LineEdit{
sysData: mksysdata(c_lineedit),
initText: text,
}
}
// NewPasswordEdit makes a new LineEdit which allows the user to enter a password.
func NewPasswordEdit() *LineEdit {
return &LineEdit{
sysData: mksysdata(c_lineedit),
password: true,
}
}
// SetText sets the LineEdit's text.
func (l *LineEdit) SetText(text string) {
if l.created {
l.sysData.setText(text)
return
}
l.initText = text
}
// Text returns the LineEdit's text.
func (l *LineEdit) Text() string {
if l.created {
return l.sysData.text()
}
return l.initText
}
func (l *LineEdit) make(window *sysData) error {
l.sysData.alternate = l.password
err := l.sysData.make(window)
if err != nil {
return err
}
l.sysData.setText(l.initText)
l.created = true
return nil
}
func (l *LineEdit) allocate(x int, y int, width int, height int, d *sysSizeData) []*allocation {
return []*allocation{&allocation{
x: x,
y: y,
width: width,
height: height,
this: l,
}}
}
func (l *LineEdit) preferredSize(d *sysSizeData) (width int, height int) {
return l.sysData.preferredSize(d)
}
func (l *LineEdit) commitResize(a *allocation, d *sysSizeData) {
l.sysData.commitResize(a, d)
}
func (l *LineEdit) getAuxResizeInfo(d *sysSizeData) {
l.sysData.getAuxResizeInfo(d)
}

View File

@ -1,150 +0,0 @@
// 14 february 2014
package ui
import (
"fmt"
)
// A Listbox is a vertical list of items, of which either at most one or any number of items can be selected at any given time.
// On creation, no item is selected.
// For information on scrollbars, see "Scrollbars" in the Overview.
// Due to implementation issues, the presence of horizontal scrollbars is currently implementation-defined.
type Listbox struct {
created bool
sysData *sysData
initItems []string
}
func newListbox(multiple bool, items ...string) (l *Listbox) {
l = &Listbox{
sysData: mksysdata(c_listbox),
initItems: items,
}
l.sysData.alternate = multiple
return l
}
// NewListbox creates a new single-selection Listbox with the given items loaded initially.
func NewListbox(items ...string) *Listbox {
return newListbox(false, items...)
}
// NewMultiSelListbox creates a new multiple-selection Listbox with the given items loaded initially.
func NewMultiSelListbox(items ...string) *Listbox {
return newListbox(true, items...)
}
// Append adds items to the end of the Listbox's list.
// Append will panic if something goes wrong on platforms that do not abort themselves.
func (l *Listbox) Append(what ...string) {
if l.created {
for _, s := range what {
l.sysData.append(s)
}
return
}
l.initItems = append(l.initItems, what...)
}
// InsertBefore inserts a new item in the Listbox before the item at the given position. It panics if the given index is out of bounds.
// InsertBefore will also panic if something goes wrong on platforms that do not abort themselves.
func (l *Listbox) InsertBefore(what string, before int) {
var m []string
if l.created {
if before < 0 || before >= l.sysData.len() {
goto badrange
}
l.sysData.insertBefore(what, before)
return
}
if before < 0 || before >= len(l.initItems) {
goto badrange
}
m = make([]string, 0, len(l.initItems)+1)
m = append(m, l.initItems[:before]...)
m = append(m, what)
l.initItems = append(m, l.initItems[before:]...)
return
badrange:
panic(fmt.Errorf("index %d out of range in Listbox.InsertBefore()", before))
}
// Delete removes the given item from the Listbox. It panics if the given index is out of bounds.
func (l *Listbox) Delete(index int) {
if l.created {
if index < 0 || index >= l.sysData.len() {
goto badrange
}
l.sysData.delete(index)
return
}
if index < 0 || index >= len(l.initItems) {
goto badrange
}
l.initItems = append(l.initItems[:index], l.initItems[index+1:]...)
return
badrange:
panic(fmt.Errorf("index %d out of range in Listbox.Delete()", index))
}
// Selection returns a list of strings currently selected in the Listbox, or an empty list if none have been selected. This list will have at most one item on a single-selection Listbox.
func (l *Listbox) Selection() []string {
if l.created {
return l.sysData.selectedTexts()
}
return nil
}
// SelectedIndices returns a list of the currently selected indexes in the Listbox, or an empty list if none have been selected. This list will have at most one item on a single-selection Listbox.
func (l *Listbox) SelectedIndices() []int {
if l.created {
return l.sysData.selectedIndices()
}
return nil
}
// Len returns the number of items in the Listbox.
//
// On platforms for which this function may return an error, it panics if one is returned.
func (l *Listbox) Len() int {
if l.created {
return l.sysData.len()
}
return len(l.initItems)
}
func (l *Listbox) make(window *sysData) (err error) {
err = l.sysData.make(window)
if err != nil {
return err
}
for _, s := range l.initItems {
l.sysData.append(s)
}
l.created = true
return nil
}
func (l *Listbox) allocate(x int, y int, width int, height int, d *sysSizeData) []*allocation {
return []*allocation{&allocation{
x: x,
y: y,
width: width,
height: height,
this: l,
}}
}
func (l *Listbox) preferredSize(d *sysSizeData) (width int, height int) {
return l.sysData.preferredSize(d)
}
func (l *Listbox) commitResize(a *allocation, d *sysSizeData) {
l.sysData.commitResize(a, d)
}
func (l *Listbox) getAuxResizeInfo(d *sysSizeData) {
l.sysData.getAuxResizeInfo(d)
}

View File

@ -1,213 +0,0 @@
// 2 march 2014
package ui
/*
The Cocoa API was not designed to be used directly in code; you were intended to build your user interfaces with Interface Builder. There is no dedicated listbox class; we have to synthesize it with a NSTableView. And this is difficult in code.
Under normal circumstances we would have to build our own data source class, as Cocoa doesn't provide premade data sources. Thankfully, Mac OS X 10.3 introduced the bindings system, which avoids all that. It's just not documented too well (again, because you're supposed to use Interface Builder). Bear with me here.
After switching from using the Objective-C runtime to using Objective-C directly, you will now need to look both here and in listbox_darwin.m to get what's going on.
*/
// #include <stdlib.h>
// #include "objc_darwin.h"
import "C"
/*
We bind our sole NSTableColumn to a NSArrayController.
NSArrayController is a subclass of NSObjectController, which handles key-value pairs. The object of a NSObjectController by default is a NSMutableDictionary of key-value pairs. The keys are the critical part here.
In effect, each object in our NSArrayController is a NSMutableDictionary with one item: a marker key and the actual string as the value.
*/
const (
_listboxItemKey = "listboxitem"
)
var (
listboxItemKey = toNSString(_listboxItemKey)
)
func toListboxItem(what string) C.id {
return C.toListboxItem(listboxItemKey, toNSString(what))
}
func fromListboxItem(dict C.id) string {
return fromNSString(C.fromListboxItem(dict, listboxItemKey))
}
/*
NSArrayController is what we bind.
This is what provides the actual list modification methods.
- (void)addObject:(id)object
adds object to the end of the list
- (void)insertObject:(id)object atArrangedObjectIndex:(NSInteger)index
adds object in the list before index
- (void)removeObjectAtArrangedObjectIndex:(NSInteger)index
removes the object at index
- (id)arrangedObjects
returns the underlying array; really a NSArray
But what is arrangedObjects? Why care about arranging objects? We don't have to arrange the objects; if we don't, they won't be arranged, and arrangedObjects just acts as the unarranged array.
Of course, Mac OS X 10.5 adds the ability to automatically arrange objects. So let's just turn that off to be safe.
*/
func makeListboxArray() C.id {
return C.makeListboxArray()
}
func listboxArrayAppend(array C.id, what string) {
C.listboxArrayAppend(array, toListboxItem(what))
}
func listboxArrayInsertBefore(array C.id, what string, before int) {
C.listboxArrayInsertBefore(array, toListboxItem(what), C.uintptr_t(before))
}
func listboxArrayDelete(array C.id, index int) {
C.listboxArrayDelete(array, C.uintptr_t(index))
}
func listboxArrayItemAt(array C.id, index int) string {
dict := C.listboxArrayItemAt(array, C.uintptr_t(index))
return fromListboxItem(dict)
}
/*
Now we have to establish the binding. To do this, we need the following things:
- the object to bind (NSTableColumn)
- the property of the object to bind (in this case, cell values, so the string @"value")
- the object to bind to (NSArrayController)
- the "key path" of the data to get from the array controller
- any options for binding; we won't have any
The key path is easy: it's [the name of the list].[the key to grab]. The name of the list is arrangedObjects, as established above.
We will also need to be able to get the NSArrayController out to do things with it later; alas, the only real way to do it without storing it separately is to get the complete information set on our binding each time (a NSDictionary) and extract just the bound object.
*/
const (
_listboxItemKeyPath = "arrangedObjects." + _listboxItemKey
)
var (
tableColumnBinding = toNSString("value")
listboxItemKeyPath = toNSString(_listboxItemKeyPath)
)
func bindListboxArray(tableColumn C.id, array C.id) {
C.bindListboxArray(tableColumn, tableColumnBinding,
array, listboxItemKeyPath)
}
func boundListboxArray(tableColumn C.id) C.id {
return C.boundListboxArray(tableColumn, tableColumnBinding)
}
/*
Now with all that done, we're ready to creat a table column.
Columns need string identifiers; we'll just reuse the item key.
Editability is also handled here, as opposed to in NSTableView itself.
*/
func makeListboxTableColumn() C.id {
column := C.makeListboxTableColumn(listboxItemKey)
bindListboxArray(column, makeListboxArray())
return column
}
func listboxTableColumn(listbox C.id) C.id {
return C.listboxTableColumn(listbox, listboxItemKey)
}
/*
The NSTableViews don't draw their own scrollbars; we have to drop our NSTableViews in NSScrollViews for this. The NSScrollView is also what provides the Listbox's border.
The actual creation code was moved to objc_darwin.go.
*/
func makeListboxScrollView(listbox C.id) C.id {
scrollview := makeScrollView(listbox)
C.giveScrollViewBezelBorder(scrollview) // this is what Interface Builder gives the scroll view
return scrollview
}
func listboxInScrollView(scrollview C.id) C.id {
return getScrollViewContent(scrollview)
}
/*
And now, a helper function that takes a scroll view and gets out the array.
*/
func listboxArray(listbox C.id) C.id {
return boundListboxArray(listboxTableColumn(listboxInScrollView(listbox)))
}
/*
...and finally, we work with the NSTableView directly. These are the methods sysData calls.
We'll handle selections from the NSTableView too. The only trickery is dealing with the return value of -[NSTableView selectedRowIndexes]: NSIndexSet. The only way to get indices out of a NSIndexSet is to get them all out wholesale, and working with C arrays in Go is Not Fun.
*/
func makeListbox(parentWindow C.id, alternate bool, s *sysData) C.id {
listbox := C.makeListbox(makeListboxTableColumn(), toBOOL(alternate))
listbox = makeListboxScrollView(listbox)
addControl(parentWindow, listbox)
return listbox
}
func listboxAppend(listbox C.id, what string, alternate bool) {
array := listboxArray(listbox)
listboxArrayAppend(array, what)
}
func listboxInsertBefore(listbox C.id, what string, before int, alternate bool) {
array := listboxArray(listbox)
listboxArrayInsertBefore(array, what, before)
}
// technique from http://stackoverflow.com/questions/3773180/how-to-get-indexes-from-nsindexset-into-an-nsarray-in-cocoa
// we don't care that the indices were originally NSUInteger since by this point we have a problem anyway; Go programs generally use int indices anyway
// we also don't care about NSNotFound because we check the count first AND because NSIndexSet is always sorted (and NSNotFound can be a valid index if the list is large enough... since it's NSIntegerMax, not NSUIntegerMax)
func listboxSelectedIndices(listbox C.id) (list []int) {
indices := C.listboxSelectedRowIndexes(listboxInScrollView(listbox))
count := int(C.listboxIndexesCount(indices))
if count == 0 {
return nil
}
list = make([]int, count)
list[0] = int(C.listboxIndexesFirst(indices))
for i := 1; i < count; i++ {
list[i] = int(C.listboxIndexesNext(indices, C.uintptr_t(list[i-1])))
}
return list
}
func listboxSelectedTexts(listbox C.id) (texts []string) {
indices := listboxSelectedIndices(listbox)
if len(indices) == 0 {
return nil
}
array := listboxArray(listbox)
texts = make([]string, len(indices))
for i := 0; i < len(texts); i++ {
texts[i] = listboxArrayItemAt(array, indices[i])
}
return texts
}
func listboxDelete(listbox C.id, index int) {
array := listboxArray(listbox)
listboxArrayDelete(array, index)
}
func listboxLen(listbox C.id) int {
return int(C.listboxLen(listboxInScrollView(listbox)))
}

View File

@ -1,142 +0,0 @@
// 13 may 2014
#include "objc_darwin.h"
#import <Foundation/NSDictionary.h>
#import <AppKit/NSArrayController.h>
#import <AppKit/NSTableColumn.h>
#import <AppKit/NSTableView.h>
#import <Foundation/NSIndexSet.h>
#define to(T, x) ((T *) (x))
#define toNSMutableDictionary(x) to(NSMutableDictionary, (x))
#define toNSArrayController(x) to(NSArrayController, (x))
#define toNSTableColumn(x) to(NSTableColumn, (x))
#define toNSTableView(x) to(NSTableView, (x))
#define toNSIndexSet(x) to(NSIndexSet, (x))
#define toNSInteger(x) ((NSInteger) (x))
#define fromNSInteger(x) ((intptr_t) (x))
#define toNSUInteger(x) ((NSUInteger) (x))
#define fromNSUInteger(x) ((uintptr_t) (x))
extern NSRect dummyRect;
id toListboxItem(id key, id value)
{
return [NSMutableDictionary dictionaryWithObject:value forKey:key];
}
id fromListboxItem(id item, id key)
{
return [toNSMutableDictionary(item) objectForKey:key];
}
id makeListboxArray(void)
{
NSArrayController *ac;
ac = [NSArrayController new];
[ac setAutomaticallyRearrangesObjects:NO];
// we don't want Cocoa to change the selection when items are inserted
// found via http://stackoverflow.com/a/5765318/3408572; not sure how I missed it the first time
[ac setSelectsInsertedObjects:NO]; // for insertions
[ac setAvoidsEmptySelection:NO]; // for deletions
return ac;
}
void listboxArrayAppend(id ac, id item)
{
[toNSArrayController(ac) addObject:item];
}
void listboxArrayInsertBefore(id ac, id item, uintptr_t before)
{
[toNSArrayController(ac) insertObject:item atArrangedObjectIndex:toNSUInteger(before)];
}
void listboxArrayDelete(id ac, uintptr_t index)
{
[toNSArrayController(ac) removeObjectAtArrangedObjectIndex:toNSUInteger(index)];
}
id listboxArrayItemAt(id ac, uintptr_t index)
{
NSArrayController *array;
array = toNSArrayController(ac);
return [[array arrangedObjects] objectAtIndex:toNSUInteger(index)];
}
void bindListboxArray(id tableColumn, id bindwhat, id ac, id keyPath)
{
[toNSTableColumn(tableColumn) bind:bindwhat
toObject:ac
withKeyPath:keyPath
options:nil]; // no options
}
id boundListboxArray(id tableColumn, id boundwhat)
{
return [[toNSTableColumn(tableColumn) infoForBinding:boundwhat]
objectForKey:NSObservedObjectKey];
}
id makeListboxTableColumn(id identifier)
{
NSTableColumn *column;
NSCell *dataCell;
column = [[NSTableColumn alloc] initWithIdentifier:identifier];
[column setEditable:NO];
// to set the font for each item, we set the font of the "data cell", which is more aptly called the "cell template"
dataCell = [column dataCell];
// technically not a NSControl, but still accepts the selector, so we can call it anyway
applyStandardControlFont(dataCell);
[column setDataCell:dataCell];
// TODO other properties?
return column;
}
id listboxTableColumn(id listbox, id identifier)
{
return [toNSTableView(listbox) tableColumnWithIdentifier:identifier];
}
id makeListbox(id tableColumn, BOOL multisel)
{
NSTableView *listbox;
listbox = [[NSTableView alloc]
initWithFrame:dummyRect];
[listbox addTableColumn:tableColumn];
[listbox setAllowsMultipleSelection:multisel];
[listbox setAllowsEmptySelection:YES];
[listbox setHeaderView:nil];
// TODO other prperties?
return listbox;
}
id listboxSelectedRowIndexes(id listbox)
{
return [toNSTableView(listbox) selectedRowIndexes];
}
uintptr_t listboxIndexesCount(id indexes)
{
return fromNSUInteger([toNSIndexSet(indexes) count]);
}
uintptr_t listboxIndexesFirst(id indexes)
{
return fromNSUInteger([toNSIndexSet(indexes) firstIndex]);
}
uintptr_t listboxIndexesNext(id indexes, uintptr_t prev)
{
return fromNSUInteger([toNSIndexSet(indexes) indexGreaterThanIndex:toNSUInteger(prev)]);
}
intptr_t listboxLen(id listbox)
{
return fromNSInteger([toNSTableView(listbox) numberOfRows]);
}

View File

@ -1,211 +0,0 @@
// +build !windows,!darwin,!plan9
// 17 february 2014
package ui
import (
"fmt"
"unsafe"
)
/*
GTK+ 3.10 introduces a dedicated GtkListView type for simple listboxes like our Listbox. Unfortunately, since I want to target at least GTK+ 3.4, I need to do things the old, long, and hard way: manually with a GtkTreeView and GtkListStore model.
You are not expected to understand this.
if you must though:
GtkTreeViews are model/view. We use a GtkListStore as a model.
GtkTreeViews also separate selections into another type, but the GtkTreeView creates the selection object for us.
GtkTreeViews can scroll, but do not draw scrollbars or borders; we need to use a GtkScrolledWindow to hold the GtkTreeView to do so. We return the GtkScrolledWindow and get its control out when we want to access the GtkTreeView.
Like with Windows, there's a difference between signle-selection and multi-selection GtkTreeViews when it comes to getting the list of selections that we can exploit. The GtkTreeSelection class hands us an iterator and the model (for some reason). We pull a GtkTreePath out of the iterator, which we can then use to get the indices or text data.
For more information, read
https://developer.gnome.org/gtk3/3.4/TreeWidget.html
http://ubuntuforums.org/showthread.php?t=1208655
http://scentric.net/tutorial/sec-treemodel-remove-row.html
http://gtk.10911.n7.nabble.com/Scrollbars-in-a-GtkTreeView-td58076.html
http://stackoverflow.com/questions/11407447/gtk-treeview-get-current-row-index-in-python (I think; I don't remember if I wound up using this one as a reference or not; I know after that I found the ubuntuforums link above)
and the GTK+ reference documentation.
*/
// #include "gtk_unix.h"
// /* because cgo seems to choke on ... */
// void gtkTreeModelGet(GtkTreeModel *model, GtkTreeIter *iter, gchar **gs)
// {
// /* 0 is the column #; we only have one column here */
// gtk_tree_model_get(model, iter, 0, gs, -1);
// }
// GtkListStore *gtkListStoreNew(void)
// {
// /* 1 column that stores strings */
// return gtk_list_store_new(1, G_TYPE_STRING);
// }
// void gtkListStoreSet(GtkListStore *ls, GtkTreeIter *iter, char *gs)
// {
// /* same parameters as in gtkTreeModelGet() */
// gtk_list_store_set(ls, iter, 0, (gchar *) gs, -1);
// }
// GtkTreeViewColumn *gtkTreeViewColumnNewWithAttributes(GtkCellRenderer *renderer)
// {
// /* "" is the column header; "text" associates the text of the column with column 0 */
// return gtk_tree_view_column_new_with_attributes("", renderer, "text", 0, NULL);
// }
import "C"
func fromgtktreemodel(x *C.GtkTreeModel) *C.GtkWidget {
return (*C.GtkWidget)(unsafe.Pointer(x))
}
func togtktreemodel(what *C.GtkWidget) *C.GtkTreeModel {
return (*C.GtkTreeModel)(unsafe.Pointer(what))
}
func fromgtktreeview(x *C.GtkTreeView) *C.GtkWidget {
return (*C.GtkWidget)(unsafe.Pointer(x))
}
func togtktreeview(what *C.GtkWidget) *C.GtkTreeView {
return (*C.GtkTreeView)(unsafe.Pointer(what))
}
func gListboxNew(multisel bool) *C.GtkWidget {
store := C.gtkListStoreNew()
widget := C.gtk_tree_view_new_with_model((*C.GtkTreeModel)(unsafe.Pointer(store)))
tv := (*C.GtkTreeView)(unsafe.Pointer(widget))
column := C.gtkTreeViewColumnNewWithAttributes(C.gtk_cell_renderer_text_new())
C.gtk_tree_view_column_set_sizing(column, C.GTK_TREE_VIEW_COLUMN_AUTOSIZE)
C.gtk_tree_view_column_set_resizable(column, C.FALSE) // not resizeable by the user; just autoresize
C.gtk_tree_view_append_column(tv, column)
C.gtk_tree_view_set_headers_visible(tv, C.FALSE)
sel := C.GTK_SELECTION_SINGLE
if multisel {
sel = C.GTK_SELECTION_MULTIPLE
}
C.gtk_tree_selection_set_mode(C.gtk_tree_view_get_selection(tv), C.GtkSelectionMode(sel))
scrollarea := C.gtk_scrolled_window_new((*C.GtkAdjustment)(nil), (*C.GtkAdjustment)(nil))
// thanks to jlindgren in irc.gimp.net/#gtk+
C.gtk_scrolled_window_set_shadow_type((*C.GtkScrolledWindow)(unsafe.Pointer(scrollarea)), C.GTK_SHADOW_IN)
C.gtk_container_add((*C.GtkContainer)(unsafe.Pointer(scrollarea)), widget)
return scrollarea
}
func gListboxNewSingle() *C.GtkWidget {
return gListboxNew(false)
}
func gListboxNewMulti() *C.GtkWidget {
return gListboxNew(true)
}
func getTreeViewFrom(widget *C.GtkWidget) *C.GtkTreeView {
wid := C.gtk_bin_get_child((*C.GtkBin)(unsafe.Pointer(widget)))
return (*C.GtkTreeView)(unsafe.Pointer(wid))
}
func gListboxText(widget *C.GtkWidget) string {
var model *C.GtkTreeModel
var iter C.GtkTreeIter
var gs *C.gchar
tv := getTreeViewFrom(widget)
sel := C.gtk_tree_view_get_selection(tv)
if !fromgbool(C.gtk_tree_selection_get_selected(sel, &model, &iter)) {
return ""
}
C.gtkTreeModelGet(model, &iter, &gs)
return fromgstr(gs)
}
func gListboxAppend(widget *C.GtkWidget, what string) {
var iter C.GtkTreeIter
tv := getTreeViewFrom(widget)
ls := (*C.GtkListStore)(unsafe.Pointer(C.gtk_tree_view_get_model(tv)))
C.gtk_list_store_append(ls, &iter)
cwhat := C.CString(what)
defer C.free(unsafe.Pointer(cwhat))
C.gtkListStoreSet(ls, &iter, cwhat)
}
func gListboxInsert(widget *C.GtkWidget, index int, what string) {
var iter C.GtkTreeIter
tv := getTreeViewFrom(widget)
ls := (*C.GtkListStore)(unsafe.Pointer(C.gtk_tree_view_get_model(tv)))
C.gtk_list_store_insert(ls, &iter, C.gint(index))
cwhat := C.CString(what)
defer C.free(unsafe.Pointer(cwhat))
C.gtkListStoreSet(ls, &iter, cwhat)
}
func gListboxSelectedMulti(widget *C.GtkWidget) (indices []int) {
var model *C.GtkTreeModel
tv := getTreeViewFrom(widget)
sel := C.gtk_tree_view_get_selection(tv)
rows := C.gtk_tree_selection_get_selected_rows(sel, &model)
defer C.g_list_free_full(rows, C.GDestroyNotify(unsafe.Pointer(C.gtk_tree_path_free)))
// g_list_length() is O(N), but we need the length below, alas
len := C.g_list_length(rows)
if len == 0 {
return nil
}
indices = make([]int, len)
for i := C.guint(0); i < len; i++ {
path := (*C.GtkTreePath)(unsafe.Pointer(rows.data))
idx := C.gtk_tree_path_get_indices(path)
indices[i] = int(*idx)
rows = rows.next
}
return indices
}
func gListboxSelMultiTexts(widget *C.GtkWidget) (texts []string) {
var model *C.GtkTreeModel
var iter C.GtkTreeIter
var gs *C.gchar
tv := getTreeViewFrom(widget)
sel := C.gtk_tree_view_get_selection(tv)
rows := C.gtk_tree_selection_get_selected_rows(sel, &model)
defer C.g_list_free_full(rows, C.GDestroyNotify(unsafe.Pointer(C.gtk_tree_path_free)))
len := C.g_list_length(rows)
if len == 0 {
return nil
}
texts = make([]string, len)
for i := C.guint(0); i < len; i++ {
path := (*C.GtkTreePath)(unsafe.Pointer(rows.data))
if C.gtk_tree_model_get_iter(model, &iter, path) == C.FALSE {
panic("gtk_tree_model_get_iter() failed getting Listbox selected texts; reason unknown")
}
C.gtkTreeModelGet(model, &iter, &gs)
texts[i] = fromgstr(gs)
rows = rows.next
}
return texts
}
func gListboxDelete(widget *C.GtkWidget, index int) {
var iter C.GtkTreeIter
tv := getTreeViewFrom(widget)
ls := (*C.GtkListStore)(unsafe.Pointer(C.gtk_tree_view_get_model(tv)))
if C.gtk_tree_model_iter_nth_child((*C.GtkTreeModel)(unsafe.Pointer(ls)), &iter, (*C.GtkTreeIter)(nil), C.gint(index)) == C.FALSE {
panic(fmt.Errorf("error deleting row %d from GTK+ Listbox: no such index or some other error", index))
}
C.gtk_list_store_remove(ls, &iter)
}
// this is a separate function because Combobox uses it too
func gtkTreeModelListLen(model *C.GtkTreeModel) int {
// "As a special case, if iter is NULL, then the number of toplevel nodes is returned."
return int(C.gtk_tree_model_iter_n_children(model, (*C.GtkTreeIter)(nil)))
}
func gListboxLen(widget *C.GtkWidget) int {
tv := getTreeViewFrom(widget)
model := C.gtk_tree_view_get_model(tv)
return gtkTreeModelListLen(model)
}

View File

@ -1,39 +0,0 @@
// 28 february 2014
package ui
import (
"unsafe"
)
// #include <stdlib.h>
// #include "objc_darwin.h"
import "C"
func toNSString(str string) C.id {
cstr := C.CString(str)
defer C.free(unsafe.Pointer(cstr))
return C.toNSString(cstr)
}
func fromNSString(str C.id) string {
return C.GoString(C.fromNSString(str))
}
func toBOOL(what bool) C.BOOL {
if what {
return C.YES
}
return C.NO
}
// These consolidate the NSScrollView code (used by listbox_darwin.go and area_darwin.go) into a single place.
func makeScrollView(content C.id) C.id {
return C.makeScrollView(content)
}
func getScrollViewContent(scrollview C.id) C.id {
return C.scrollViewContent(scrollview)
}

View File

@ -1,137 +0,0 @@
/* 28 february 2014 */
/* apparently this header is included by other headers generated by cgo, wrecking the structures below, so wheee include guards */
/* the change that introduced this was [master 9b4e30c] ("Started to build a single global delegate object; now to fix issues.") */
#ifndef __GO_UI_OBJC_DARWIN_H__
#define __GO_UI_OBJC_DARWIN_H__
#define MAC_OS_X_VERSION_MIN_REQUIRED MAC_OS_X_VERSION_10_7
#define MAC_OS_X_VERSION_MAX_ALLOWED MAC_OS_X_VERSION_10_7
/* the Objective-C runtime headers, for id */
#include <objc/message.h>
#include <objc/objc.h>
#include <objc/runtime.h>
#include <stdint.h>
/* wrapper types since the meaning of NSRect, NSSize, and NSPoint are CPU architecture3d-dependent; also because they're in an Objective-C-only header */
struct xrect {
intptr_t x;
intptr_t y;
intptr_t width;
intptr_t height;
};
struct xsize {
intptr_t width;
intptr_t height;
};
struct xpoint {
intptr_t x;
intptr_t y;
};
struct xalignment {
struct xrect alignmentRect;
intptr_t baseline;
};
/* objc_darwin.m */
extern id toNSString(char *);
extern char *fromNSString(id);
extern void display(id);
extern struct xrect frame(id);
extern id makeScrollView(id);
extern void giveScrollViewBezelBorder(id);
extern id scrollViewContent(id);
/* area_darwin.m */
extern Class areaClass;
extern void initAreaClass(void);
extern id makeArea(void);
extern void drawImage(void *, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t);
extern uintptr_t modifierFlags(id);
extern struct xpoint getTranslatedEventPoint(id, id);
extern intptr_t buttonNumber(id);
extern intptr_t clickCount(id);
extern uintptr_t pressedMouseButtons(void);
extern uintptr_t keyCode(id);
/* delegateuitask_darwin.m */
extern id makeAppDelegate(void);
extern id windowGetContentView(id);
extern BOOL initCocoa(id);
extern void uipost(id, void *);
extern void breakMainLoop(void);
extern void cocoaMainLoop(void);
/* dialog_darwin.m */
extern void msgBox(id, id, id);
extern void msgBoxError(id, id, id);
/* listbox_darwin.m */
extern id toListboxItem(id, id);
extern id fromListboxItem(id, id);
extern id makeListboxArray(void);
extern void listboxArrayAppend(id, id);
extern void listboxArrayInsertBefore(id, id, uintptr_t);
extern void listboxArrayDelete(id, uintptr_t);
extern id listboxArrayItemAt(id, uintptr_t);
extern void bindListboxArray(id, id, id, id);
extern id boundListboxArray(id, id);
extern id makeListboxTableColumn(id);
extern id listboxTableColumn(id, id);
extern id makeListbox(id, BOOL);
extern id listboxSelectedRowIndexes(id);
extern uintptr_t listboxIndexesCount(id);
extern uintptr_t listboxIndexesFirst(id);
extern uintptr_t listboxIndexesNext(id, uintptr_t);
extern intptr_t listboxLen(id);
/* prefsize_darwin.m */
extern struct xsize controlPrefSize(id);
extern struct xsize listboxPrefSize(id);
extern struct xsize pbarPrefSize(id);
extern struct xsize areaPrefSize(id);
extern struct xalignment alignmentInfo(id, struct xrect);
/* sysdata_darwin.m */
extern void addControl(id, id);
extern void controlShow(id);
extern void controlHide(id);
extern void applyStandardControlFont(id);
extern id makeWindow(id);
extern void windowShow(id);
extern void windowHide(id);
extern void windowSetTitle(id, id);
extern id windowTitle(id);
extern id makeButton(void);
extern void buttonSetTargetAction(id, id);
extern void buttonSetText(id, id);
extern id buttonText(id);
extern id makeCheckbox(void);
extern id makeLineEdit(BOOL);
extern void lineeditSetText(id, id);
extern id lineeditText(id);
extern id makeLabel(void);
extern id makeProgressBar(void);
extern void setRect(id, intptr_t, intptr_t, intptr_t, intptr_t);
extern BOOL isCheckboxChecked(id);
extern void windowSetContentSize(id, intptr_t, intptr_t);
extern void setProgress(id, intptr_t);
extern void setAreaSize(id, intptr_t, intptr_t);
extern void center(id);
extern void setCheckboxChecked(id, BOOL);
/* combobox_darwin.m */
extern id makeCombobox(BOOL);
extern id comboboxText(id, BOOL);
extern void comboboxAppend(id, BOOL, id);
extern void comboboxInsertBefore(id, BOOL, id, intptr_t);
extern intptr_t comboboxSelectedIndex(id);
extern void comboboxDelete(id, intptr_t);
extern intptr_t comboboxLen(id);
#endif

View File

@ -1,75 +0,0 @@
// 15 may 2014
#include "objc_darwin.h"
#import <Foundation/NSString.h>
#import <AppKit/NSView.h>
#import <AppKit/NSScrollView.h>
#define to(T, x) ((T *) (x))
#define _toNSString(x) to(NSString, (x))
#define toNSView(x) to(NSView, (x))
#define toNSScrollView(x) to(NSScrollView, (x))
// because the only way to make a new NSControl/NSView is with a frame (it gets overridden later)
NSRect dummyRect;
// this can be called before our NSApp is created, so keep a pool to keep 10.6 happy
id toNSString(char *str)
{
NSAutoreleasePool *pool;
NSString *s;
pool = [NSAutoreleasePool new];
s = [NSString stringWithUTF8String:str];
[s retain]; // keep alive after releasing the pool
[pool release];
return s;
}
char *fromNSString(id str)
{
return [_toNSString(str) UTF8String];
}
void display(id view)
{
[toNSView(view) display];
}
struct xrect frame(id view)
{
NSRect r;
struct xrect s;
r = [toNSView(view) frame];
s.x = (intptr_t) r.origin.x;
s.y = (intptr_t) r.origin.y;
s.width = (intptr_t) r.size.width;
s.height = (intptr_t) r.size.height;
return s;
}
id makeScrollView(id content)
{
NSScrollView *scrollview;
scrollview = [[NSScrollView alloc]
initWithFrame:dummyRect];
[scrollview setHasHorizontalScroller:YES];
[scrollview setHasVerticalScroller:YES];
[scrollview setAutohidesScrollers:YES];
// Interface Builder sets this for NSTableViews; we also want this on Areas
[scrollview setDrawsBackground:YES];
[scrollview setDocumentView:toNSView(content)];
return scrollview;
}
void giveScrollViewBezelBorder(id scrollview)
{
[toNSScrollView(scrollview) setBorderType:NSBezelBorder];
}
id scrollViewContent(id scrollview)
{
return [toNSScrollView(scrollview) documentView];
}

View File

@ -1,26 +0,0 @@
language: go
go:
- tip
# TODO
# linux/386 build tries to do apt's legendary remove all 64-bit libraries and install 32-bit ones instead (else it can't find sys/types.h) so that build is disabled for now
before_install:
- sudo apt-get update -qq
- sudo apt-get install -y libgtk-3-dev multiarch-support # libgtk-3-0:i386 gcc-multilib
- go tool dist install cmd/8a
- go tool dist install cmd/8c
- go tool dist install cmd/8g
- go tool dist install cmd/8l
# - GOOS=linux GOARCH=386 CGO_ENABLED=1 CFLAGS=-m32 LDFLAGS=-m32 go tool dist install pkg/runtime
# - GOOS=linux GOARCH=386 CGO_ENABLED=1 CFLAGS=-m32 LDFLAGS=-m32 go install std
- GOOS=windows GOARCH=386 go tool dist install pkg/runtime
- GOOS=windows GOARCH=386 go install std
- GOOS=windows GOARCH=amd64 go tool dist install pkg/runtime
- GOOS=windows GOARCH=amd64 go install std
install:
- GOOS=linux GOARCH=amd64 ./test.sh -v -x
- GOOS=windows GOARCH=386 ./test.sh -v -x
- GOOS=windows GOARCH=amd64 ./test.sh -v -x

File diff suppressed because it is too large Load Diff

View File

@ -1,78 +0,0 @@
NSPopUpButton (non-editable combo box)
make:
b = [[NSPopUpButton alloc]
initWithFrame:(0, 0, 100, 100)
pullsDown:NO]
add:
[b addItemWithTitle:toNSString(s)]
insertBefore:
[b insertItemWithTitle:toNSString(s)
atIndex:index] (NSInteger)
remove:
[b removeItemAtIndex:index] (NSInteger)
selection:
fromNSString([b titleOfSelectedItem])
(returns nil if nothing is selected; need to edit to return "" if so)
selectedIndex:
[b indexOfSelectedItem] (NSInteger)
(returns -1 if nothing is selected)
NSComboBox (editable combo box)
make:
b = [[NSComboBox alloc]
initWithFrame:(0, 0, 100, 100)]
[b setUsesDataSource:NO] // internal data soruce
add:
[b addItemWithObjectValue:toNSString(s)]
insertBefore:
[b insertItemWithObjectValue:toNSString(s)
atIndex:index] (NSInteger)
remove:
[b removeItemAtIndex:index] (NSInteger)
selection:
this depends on if the /user/ selecting an item changes the edit box
this appears to be the case, so
fromNSString([b stringValue])
note that if we ever add Combobox.SetText(), we are responsible for managing both the edit field AND the list, as they are programmatically separate
selectedIndex:
[b indexOfSelectedItem] (NSInteger)
(returns -1 if nothing is selected)
(TODO custom text?)
NSTableView (listbox)
make:
b = [[NSTableView alloc]
initWithFrame:(0, 0, 100, 100)]
col = [[NSTableColumn alloc]
initWithIdentifier:@"listboxcolumn"]
listDict = [NSMutableDictionary xxxx]
listItems = [[xxx]]
[listItems addObject:listDict]
[col bind:@"value"
toObject:listItems
withKeyPath:@"xxxxx.listboxcolumn"
options:nilid]
[b addTableColumn:col]
// TODO autoresizing
add:
insertBefore:
remove:
selection:
idx = [b selectedRow] (NSInteger)
if idx == -1 {
return ""
}
dataSource = [b dataSource]
selectedIndex:
[b selectedRow] (NSInteger)
(returns -1 if none selected)
selectedIndices:
nsidx = [b selectedRowIndexes]
c = [nsidx count] (NSUInteger)
nsidxbuf = C.makeNSUIntegerArray(c)
[nsidx getIndexes:nsidxbuf
maxCont:c
inIndexRange:nilid]
// and just copy out of nsidxbuf somehow
// I think this is going to have to make 2 temporary arrays; a better option will be needed! TODO
selectedTexts:
indices := selectedIndices()
dataSource = [b dataSource]

View File

@ -1,71 +0,0 @@
## UPDATE 18 March 2014: this document is out of date and could use some rewriting
# ui library implementation information
All platform-specific functionality is isolated in a mega-type `sysData` that stores OS toolkit handles and provides methods that do the work for the frontend API. The file `sysdata.go` defines a type `cSysData` that contains everything all platforms have in common and dummy definitions of the `sysData` functions that panic. The platform-specific `sysData` embeds `cSysData`.
The key `sysData` function is `sysData.make()`. It takes two arguments: the initial text of the control (if any), and a pointer to a `sysData` that represents the window that holds the control. If this pointer is `nil`, we are creating a window instead, so any window-specific actions are taken here.
`cSysData` contains the control type as a number, an `event` channel where the object's standard event (close button for `Window`, click for `Button`, etc.) is stored, and a `resize()` function variable that is called to resize child controls on window resize.
Controls need only two functions: a `make()` function that builds the control, and a `setRect()` function that positions the control in its parent window. `make()` takes a single argument: the parent window. This window's `sysData` field gets passed to the control's `sysdata.make()` to indicate that it is a child control.
To keep things thread safe, all UI functions should be run on a separate thread whose OS thread is locked. This thread is goroutine `ui()`, presently started by `init()` (but it may be started by a dedicated function later). This goroutine is defined per platform, but takes the general form of
``` go
func ui(initErrors chan error) {
runtime.LockOSThread()
initErrors <- doPlatformInit() // inform init() of success or failure
for {
select {
case message <- uitask:
do(message)
default:
platformEventLoopIteration()
}
}
}
```
`uitask` is a channel that transmits messages. These messages indicate platform-specific functions to call and their arguments.
All `sysData` methods except `sysData.setRect()` and `sysData.preferredSize()` dispatch through `uitask`. Control resizing is handled within the UI goroutine itself, so `sysData.setRect()` (and thus `sysData.resize()`) call resizing functions directly. (The GTK+ backend broke spectacularly otherwise.) `sysData.preferredSize()` is called by the resizing functions, so thus must also run on directly.
## Windows
On Windows, all controls are windows, window classes are used to define their type, and messages are used to perform actions on windows and dispatch(different word? TODO) events. The data that we need to store, then, is the class name, initial styles, and combobox/listbox messages.
For `Window`s, a new window class is created for each window that you open. This window class is only different by its message handling function, or window procedure/WndProc. The WndProc is generated as a closure, so that it can safely absorb the window's `sysData` (so we don't need t look it up manually). This is all in `stdwndclass_windows.go`.
For controls, Windows uses an ID number to identify controls to the parent window, rather than passing around the window handles directly. The `sysData` for a window takes care of all this.
`uitask` transmits structures of type `uimsg`, which contain three things:
<ul><li> A `syscall.LazyProc` to call
<li> The parameters, as a `[]uintptr` (just like `syscall.LazyProc.Call()`)
<li> A channel to transmit `system.LazyProc.Call()`'s return values on</ul>
The `sysData` functions create the return channel, wait for results on it, then destroy the channel. As all DLL dispatch is done through `syscall.LazyDLL`, the Windows backend does not use cgo.
## Unix (except Mac OS X)
GTK+ is strange: there are constructor functions that return `GtkWidget *`, but anything that actually accesses a control requires the `GtkWidget *` to be cast to the appropriate control type. Fortunately, we can do this at time of call and just store `GtkWidget *`s for everything. And as most of these control methods take the same form, we can just store a list of functions to call for each control type.
`uitask` is a channel that takes `func()` literals. These are closures generated by the `sysData` functions that contain the GTK+ calls and a channel send for return values (like with Windows above). If no return value is needed, the channel send just sends a `struct{}`. The UI goroutine merely calls these functions.
As the GTK+ main loop system does not quite run in a sane way (it allows recursion, and the `gtk_main_loop_iteration_do` function only onperates on the innermost call), we cannot use the `for`/`select` template for `ui()`. Fortunately, GDK provides gdk_threads_add_idle(), which allows us to run, and optionally (and importantly) run only once, a function on the `gtk_main()` thread when not processing events. We use this, combined with a goroutine for dispatching, to handle `uitask` requests. See `our_idle_callback` in callbacks_unix.go for details.
GTK+ layout managers are not used since the UI library's layout managers are coded in a portable way. (A `GtkLayout` in a `GtkScrolledWindow` is used instead. This is done instead of just a `GtkFixed` so that a window can be resized smaller than the size requests of its contents.) This isn't ideal, but it works for now.
All event handlers take the `sysData` as their user data parameter; this means all the event-handling code is stored in static functions in callbacks_unix.go. (Early versions of the package generated signal handlers for each control on the fly, but this needed to be changed to accommodoate Area, which not only needs the `sysData` but also needs to connect to a subwidget of a subwidget (specifically the subwidget of the `GtkViewport` of a `GtkScrolledWindow`); the current setup also avoids creating closures for each and every Window and Button created, and also means we can stop having to shove those callbacks in an ever-growing slice to prevent them from being garbage collected.) Should the widget actually be a child widget of a `GtkScrolledWindow`, the `child` function and `childsigs` signal list are used to assign signals as well.
The only major snag with the GTK+ implementation is the implementation of `Listbox`; see `listbox_unix.go` for details.
## Mac OS X
The Mac OS X backend uses Cocoa. As Cocoa uses Objective-C, we work through the Objective-C runtime, whose various methods are called directly from the Go code. The biggest problem is that the message dispatch function, `objc_msgSend`, takes a variable number of arguments, and cgo can't handle this. Consequently, I have to use a bunch of wrapper functions, made in `objc_darwin.h` and `bleh_darwin.m`, instead. `bleh_darwin.m` implements a bunch of edge cases where limitations of cgo or problems with the documentation or implementation do not allow otherwise (hence the filename).
Before the Mac OS X backend was written, you put your code in `main.main()`. Unfortunately, Cocoa **requires** that you place its event loop on the very first OS thread created, called the "main thread", and provides no way to move things to another thread. (Windows and GTK+ don't seem to care which thread you use so long as you use one thread.) This is why you have to put your code in another function and call `ui.Go()` to use the library.
Otherwise, the `sysData` for Cocoa just uses a bunch of functions that call the necessary Objective-C functions ("selectors") to create and modify controls.
Event handling is done with a delegate class in `delegate_darwin.go`. This class is created at runtime (a feature that seems to have been introduced in Mac OS X 10.5) and a single instance is shared by everything. This instance is also responsible for `uitask` dispatch: Cocoa provides facilities for running an object's selector on the main thread. `uitask` communicates `func()` literals, just like the GTK+ backend.
Cocoa coordinate systems have (0,0) at the bottom-left corner of each rectangle (be it the screen, a window, or a control) and has Y go up as it increases; everything else places (0,0) at the top-left and has Y go down as it increases. The various `setSize()` methods have thus been adorned with the window's content area's height to do the necessary fixup. This was done to avoid polling the window's height many times each time a window is resized.
The biggest problem with Cocoa is that you were never intended to use it directly for GUI work: you were intended to use Interface Builder for everything. For the most part, we can get by, as there aren't too many functions and most things are documented well enough. Listboxes and other cases of NSTableView, on the other hand, are not. Read `listbox_darwin.go` for more details.
Other limitations caused by Cocoa are described in their respective soruce files.

View File

@ -1,25 +0,0 @@
A Stack is a stack of controls arranged horizontally or vertically.
If horizontal, all controls take the same heights, and use their preferred widths.
If vertical, all controls take the same widths, and use their preferred heights.
PROBLEM
We want to have controls that stretch with the window
SOLUTION
Delegate one control as the "stretchy" control.
s := NewVerticalStack(c1, c2, c3)
s.SetStretchy(1) // c2
When drawing, all other controls have their positions and sizes determiend, then the stretchy one's is.
PROBLEM
We want to have a control that's anchored (resizes with) the top left and one that's anchored with the top right.
SOLUTION
Allow stretchiness for arbitrary controls
s.SetStretchy(1)
s.SetStretchy(2)
When drawing, the sizes of non-stretchy controls are determined first, then all stretchy controls are given equal amounts of the rest.
The preferred size of the stack is the preferred size of non-stretchy controls + (the number of stretchy controls * the largest preferred size of the stretchy controls).
PROBLEM
Non-equal size distribution of stretchy controls: for instance, in a horizontal stack, a navigation bar is usually a fixed size and the content area fills the rest.
SOLUTION
I'm not entirely sure how to fix this one yet; I do know for a navigation pane the user is usually in control of the size, so... will figure out later.

View File

@ -1,69 +0,0 @@
I had a mental breakdown watching everything fall apart miserably and so I decided to just start over, this time designing around the underlying APIs, not around what I actually want the API to look like.
WINDOWS
GUI work can be done on multiple threads; just run a message loop on each thread (and set COM threading to STA)
each thread owns whatever window handles were created on that thread
will need a master thread to coordinate everything
dialogs are code-modal; no result until dialog closed and blocks owner hwnd
open-close order important; threads circumvent this
owner hwnd required to keep on top; can't keep on top unconditionally
changing parents is possible; initially unowned might not be? TODO
might not be possible if we have one thread per window? TODO googling fails
creating windows and controls before main loop begins possible
sending a message across threads will hang up the first thread during the send if you so choose
POSSIBLE BACKEND DESIGN
each top-level window exists on its own thread
each request would be a SendMessage(hwndParent, msgDoThing, hwnd, &arg)
this allows the API to be fully multithreaded, and allows us to have callabcks just be normal
the ui.Go() function would sit waiting for Stop to be sent, at which point it would signal all open windows to quit unconditionally
GTK+
GUI work must be done on the main thread; what thread this is isn't particularly clear but I'm assuming it's the one that calls gtk_init()
IIRC windows/controls can only be made on the main thread as well
dialogs can either be code modal or not
dialogs are modal to all windows in the same window group; only the transient window is actually DISABLED, however
not sure if open/close order can be affected since gtk_dialog_run() does that transient window thing
can't keep dialog window always on top (X11-based limitation); only above the transient window (if any)
changing parents is possible but IIRC is done in a control-dependent manner? also requires incrementing the refcount
creating windows and controls before main loop begins possible
sending a message across threads will NOT hang up the first thread during the send, and the GTK+ designers don't think this is good design
- there is gdk_threads_enter()/gdk_threads_leave() but they're X11-only and deprecated as of GTK+ 3.6
- gdk_threads_add_idle() does not block
- g_main_context_invoke() also doesn't block if not on the main thread
- g_signal_emit() is not thread safe
COCOA
only one thread, must be thread main() is called on
cannot create new windows/controls on any other thread
there are complex rules involving windows but let's not bother
everything is coordinated with the NSApp delegate
two types of dialogs:
- code-modal; stops ALL interaction with program
- non-code-modal; affects current window only but need to sit and wait for a callback to come in before you know it's done
I have a stackoverflow question asking about this but no one has given me a real answer yet, just more questions
- font and color choosers are neither, but since they're NSPanels which are NSWindows I could coerce them to be :S would take a fair bit of work though
not sure if changing parents is possible TODO
creating windows/controls before [NSApp run] possible
TODO documented?
sending a message across threads will hang up the first thread during the send if you so choose
POSSIBLE BACKEND DESIGN
all calls are done with performSelectorOnMainThread:
each widget calls a function that creates a window
this allows the API to be fully multithreaded, and allows us to have callabcks just be normal
the ui.Go() function would sit waiting for Stop to be sent, at which point it would signal all open windows to quit unconditionally, then break the event loop without calling terminate
for window-specific dialogs, a goroutine/channel hack may be necessary if this SO question doesn't get an answer
the answer might also be victim to ordering...
GENERAL NOTES
what about spinning an inner message pump during each event and run event handlers in their own goroutine?
no, still subject to the same race condition that could lead to a double button press
another caveat I neve rnoticed before
[20:20] <andlabs> ,..ack
[20:20] <andlabs> I just realized osmething else about my API design
[20:21] <andlabs> I can't stop preemption during an event handle
[20:21] <andlabs> r
[20:23] <andlabs> no matter how I slice it I might have to have tow different sets of funcitons
[20:23] <andlabs> one for outside evernts and one for inside events
[20:23] <andlabs> yuck

View File

@ -1,69 +0,0 @@
[![Build Status](https://travis-ci.org/andlabs/ui.png?branch=master)](https://travis-ci.org/andlabs/ui)
# Native UI library for Go
### THIS README NEEDS REWRITING. Cleaning out TODOs causes me to change a bunch of different things at the API. Window.Open() no longer returns an error, and the handled return from AreaHandler.KeyEvent() is gone. While for the most part the API is stable enough, it won't be stable as far as what I have implemented now goes until I get all these TODOs nailed. Bear with me here.
### THIS PACKAGE IS UNDER ACTIVE DEVELOPMENT. It can be used; the API is stable enough at this point, but keep in mind there may still be crashes and API changes, as suggestions are always open. If you can help, please do! Run `./test` to build a test binary `test/test` which runs a (mostly) feature-complete UI test. Run `./d32 ./test` to build a 32-bit version (you will need a cgo-enabled 32-bit go environment, and I have only tested this on Mac OS X). For Windows targets, you will need a cgo-enabled windows/386 *AND* windows/amd64 to regenerate the autogenerated zconstants_windows_*.go files (cross-compiling multiple cgo-enabled targets in Go isn't possible yet; [I have a CL pending that will make it so](https://codereview.appspot.com/93580043) (it's too late for Go 1.3 but it might land in 1.4; you can try applying it yourself but; I also don't intend on adding a flag to suppress regeneration lest I get complacent and start using it myself and introduce some mystery bug); native Windows targets should be able to work fine, but I haven't tested it).
### UPDATE 12 March 2014: Windows 2000 is no longer supported [as it is no longer supported by Go](https://codereview.appspot.com/74790043).
### UPDATE 18 March 2014: Resizes are now assumed to stop other UI event processing, and thus do not run with locks anymore. I changed real control resizing so that it doesn't need to lock (it just fills an array with data fed in), but real control `preferredSize()` and `Stack`/`Grid.setRect()` could potentially still be racy... if I am right it won't be an issue, but if anyone else knows, please let me know. (Everything else is thread-safe again.)
### UPDATE 27 March 2014: Unix builds now require go tip due to a [cgo fix that affected this package](https://code.google.com/p/go/issues/detail?id=7548).
### UPDATE 12 April 2014: Areas now require their images to be `image.RGBA`, not `image.NRGBA`. Update your code appropriately.
This is a simple library for building cross-platform GUI programs in Go. It targets Windows, Mac OS X, Linux, and other Unixes, and provides a thread-safe, channel-based API. The API itself is minimal; it aims to provide only what is necessary for GUI program design. That being said, suggestions are welcome. Layout is done using various layout managers, and some effort is taken to conform to the target platform's UI guidelines. Otherwise, the library uses native toolkits.
ui aims to run on all supported versions of supported platforms. To be more precise, the system requirements are:
* Windows: Windows XP or newer. The Windows backend uses package `syscall` and calls Windows DLLs directly, so does not rely on cgo.
* Note: it does, however, rely on a pregenerated set of constants based on the Windows header files. I locally build a new one each time and these are included in the repo as zconstants_windows_*.go; you can rebuild them yourself with the included tools/windowsconstgen.go (see test.sh).
* Mac OS X: Mac OS X 10.6 (Snow Leopard) or newer. Objective-C code is used directly for maximum portability, and thus this uses cgo.
* Note: you will need Go 1.3 or newer (so until it is released, [go tip](http://tip.golang.org/doc/install/source#head)) for this verison, as it uses Objective-C.
* Other Unixes: The Unix backend uses GTK+, and thus cgo. It requires GTK+ 3.4 or newer; for Ubuntu this means 12.04 LTS (Precise Pangolin) at minimum. Check your distribution.
* Note: you will need Go 1.3 or newer (as above) due to various cgo errors fixed in this version (such as [this one](https://code.google.com/p/go/issues/detail?id=7548)).
ui itself has no outside Go package dependencies; it is entirely self-contained.
To install, simply `go get` this package. On Mac OS X, make sure you have the Apple development headers. On other Unixes, make sure you have the GTK+ development files:
* for Ubuntu, `libgtk-3-dev` is sufficient
* for FreeBSD with the pkgng system, `gtk3` is sufficient, however you will need to manually install `pkgconf` to please cgo (and you may need to specify `CC=gcc47`) (this should be put on the Go wiki)
Package documentation is available at http://godoc.org/github.com/andlabs/ui.
For an example of how ui is used, see https://github.com/andlabs/wakeup, which is a small program that implements a basic alarm clock.
## Known To Have Ever Been Built Matrices
For convenience's sake, here are matrices of builds that I have personally done at least once. Each cell represents the run status. These matrices represent builds that I have done at any point in development; it is not a guarantee that the current version works. (I built this list to answer questions of whether or not ui works with a specific configuration.) Only configurations marked with a * are tested during active development. "(invalid)" means the given OS/arch combination is not supported by Go.
| 386 | amd64 | arm
----- | ----- | ----- | -----
windows | works on windows; works on wine* | works on windows; fails on wine | (invalid)
linux | see table below | see table below | Raspian: works
darwin (Mac OS X) | works* (cross-compiled from 64-bit) | works* | (invalid)
dragonfly | untested | untested | (invalid)
freebsd | works | untested (VM failure) | untested
netbsd | untested | untested | untested
openbsd | untested | untested | (invalid)
solaris | (invalid) | Oracle Solaris 11: **GTK+ 3 not available from official repos** | (invalid)
plan9 | (not written yet; problems building Go) | (not written) | (invalid)
nacl | (not sure how to handle) | (not sure how to handle) | (invalid)
linux | 386 | amd64
----- | ----- | ----- | -----
Kubuntu (14.04) | works; cross-compiling on 64-bit Linux fails due to nonexistent .so symlinks | works*
Fedora | untested | untested
openSUSE | untested | untested
Arch Linux | untested | untested
Mandriva (TODO choose between PCLinuxOS and Mageia - it appears the original Mandriva is either dead or nonfree and I would rather choose the fork that structures packages identically for parity; do they both?) | untested | untested
Slackware | untested | untested
Gentoo | untested | untested
(The above list should cover all the bases of major Linux distributions and variants thereof; I might add a dedicated Debian test later but other than that... suggestions welcome. Kubuntu 64-bit is my main system and the main development platform; the Windows builds are cross-compiled from here. And yes, this also implies I seriously consider a Plan 9 port of the library using [libcontrol](http://plan9.bell-labs.com/magic/man2html/2/control), though I'm guessing this will blow up in my face due to any possible conflicts between libthread and Go's runtime (I need to see how the Go runtime implements OS threads on Plan 9).)
## Contributing
Contributions are welcome. File issues, pull requests, approach me on IRC (pietro10 in #go-nuts; andlabs elsewhere), etc. Even suggestions are welcome: while I'm mainly drawing from my own GUI programming experience, everyone is different. I have received emails, however I am not likely to see those right away, so I don't suggest contacting me by email if your communication is urgent.
If you want to dive in, read implementation.md: this is a description of how the library works. (Feel free to suggest improvements to this as well.) The other .md files in this repository contain various development notes.
Please suggest documentation improvements as well.

View File

@ -1,71 +0,0 @@
# Go UI package planning
Pietro Gagliardi
http://github.com/andlabs
## Goals
- Simple, easy to use GUI library for hard-coding GUI layouts
- Go-like: uses Go's concurrency features, interfaces, etc. and behaves like other Go libraries
- Portable; runs on all OSs Go supports and uses native toolkits (wherever possible)
- Minimal: only support what's absolutely necessary (for instance, only events that we will actually use in a program); if functionality can be done cleanly in an existing thing, use that (for instnaces, if adjustable sliding dividers are ever added, they can be made part of `Stack` instead of their own thing)
- Lightweight and fast
- Error-safe
- Correct: uses APIs properly and conforms to system-specific UI design guidelines
## Support
- Windows: all versions listed as supported by Go; that means Windows 2000 and newer
- Mac: all versions listed as supported by Go; that means Mac OS X 10.6 (Snow Leopard) and newer
- other Unix: GTK+
- I am deciding to support at least the versions of glib/gobject and GDK/GTK+ supported by Ubuntu 12.04 LTS (Precise Pangolin; the earliest LTS that ships with GTK+ 3); that is, glib/gobject 2.32.1 and GDK/GTK+ 3.4.1
- however the avaialble (on http://developer.gnome.org/) documentation is actually for glib/gobject 2.32.4 and GDK/GTK+ 3.4.4; I hope the point differences won't hurt me
## Layouts
Layouts control positioning and sizing. Layouts are controls, so they can be added recursively. The layout types are:
* `Stack`: a stack of controls, all sized alike, with padding between controls and spacing around the whole set. Controls can be arranged horizontally or vertically. (Analogues: Qt's `QBoxLayout`)
>* TODO change the name?
* `RadioSet`: like `Stack` but for radio buttons: only has radio buttons and handles exclusivity automatically (this is also the only way to add radio buttons)
* `Grid`: a grid of controls; they size themselves. Spacing is handled like `Stack`. (Analogues: Qt's `QGridLayout`)
* `Form`: a set of label-control pairs arranged to resemble options on a dialog form. Sizing, positioning, and spacing are handled in an OS-dependent way. (Analogues: Qt's `QFormLayout`)
## Windows
There's only one (maybe two, if I choose to add floating toolboxes) window type. You can add one control to the content area of a window.
In the case of dialogue boxes, you can call a function, say `RunDaialogue()` , that runs the dialogue modal, and adds standard OK/Cancel/Apply buttons for you.
## An example
``` go
package main
import (
"github.com/andlabs/ui"
)
func main() {
win := ui.NewWindow("Hello")
form := ui.NewForm()
name := ui.NewLineEntry()
form.Append("Enter your name:", name)
button := ui.NewButton("Click Me")
form.Append("", button)
win.SetControl(form)
events, err := win.RunDialogue(ui.OkCancel)
if err != nil {
panic(err)
}
done := false
for !done {
select {
case event := <-events:
switch event {
case ui.Ok:
ui.MsgBox("Hi", "Hello, " + name.Text(), ui.Ok)
case ui.Cancel:
done = true
}
case <-button.Click:
ui.MsgBox("Hi", "You clicked me!", ui.Ok)
}
}
window.Close()
}
```

View File

@ -1,68 +0,0 @@
In the new setup, Windows have WindowHandlers. A WindowHandler is defined as
``` go
type WindowHandler interface {
Event(e Event, c interface{})
}
```
Whenever an event to the window or any control within the window comes in, the handler's `Event()` method is called with the event type and a context-specific value (usually the control that triggered the event) as an argument.
``` go
type Event int
const (
Close Event = iota
Clicked
Checked
Selected
Dismissed // for dialogs
// ...
CustomEvent = 5000 // arbitrary but high enough
)
```
The argument to `Close` is a pointer to a value that determnes whether to continue the closing of the window or not. The semantics of this value (type, possible values, and default; a special case in Cocoa means there could be three possible values) have yet to be defined.
The argument to all events `e` such that `Clicked` < `e` < `CustomEvent` is a pointer to the Control (or Dialog; see below) that triggered the event.
`CustomEvent` represents the first free ID that can be used by the program for whatever it wants, as a substitute for channels. The argument type is program-defened. To trigger a custom event, use the `Window.Send(e, data)` method. `Send()` panics if the event requested is not custom.
As an example, the timer from `wakeup` might be run on a goroutine:
``` go
func (w *MainWin) timerGoroutine() {
for {
select {
case t := <-w.start:
// set the timer up
case <-w.timerChan:
w.win.Send(CustomEvent, nil)
case <-w.stop:
// stop the timer
}
}
}
```
The underlying OS event handler is not existed until the event handling function returns.
With the exception of `Window.Create()`, `Window.Open()`, and `Window.Send()`, no objects and methods are safe for concurrent use anymore. They can only be used within an event handler. They can be used within `AreaHandler` methods as well as from the `WindowHandler` method.
`ui.Go()` no longer takes any arguments. Instead, when initiailization completes, it sends /and waits for the receipt of/ a semaphore value across the `ui.Started` channel, which is immeidately closed after first receipt. Programs should use this flag to know when it is safe to call `Window.Create()`, `Window.Open()`, and `Window.Send()`. A send of a semaphore value to `ui.Stop` will tell `ui.Go()` to return. This return is immediate; there is no opportunity for cleanup.
The semantics of dialogs will also need changing. It may be (I'm not sure yet) no longer possible to have "application-modal" dialogs. The standard dialog box methods on Window will still exist, but instead of returning a Control, they will return a new type Dialog which can be defined as
``` go
type Dialog interface {
Result() Result
Selection() interface{} // string for file dialogs; some other type for other dialogs
// TODO might contain hidden or unexported fields to prevent creating something that's compatible with Dialog but cannot be used as one for the sake of custom Dialogs; see below
// TODO make it compatible with Control?
}
```
When the dialog is dismissed, a `Dismissed` event will be raised with that dialog as an argument; get the result code by calling `Result()`.
It might still be possible to have dialog boxes that do not return until the user takes an action and returns the result of that action. I do not know how these will work yet, or what names will be used for either type.
The Dialog specification above would still allow custom dialogs to be made. In fact, they could be built on top of Window perhaps (or even as a mode of Window), but they would need to be reusable somehow...

View File

@ -1,4 +0,0 @@
This is a file to keep track of API restrictions that simplify the implementation of the package. I would like to eliminate them, but...
- Once you open a window, the controls are finalized: you cannot change the window's control or add/remove controls to layouts.
- Once you open a window, you cannot change its event channels or its controls's event channels.

View File

@ -1,97 +0,0 @@
// 15 may 2014
#include "objc_darwin.h"
#import <AppKit/NSControl.h>
#import <AppKit/NSScrollView.h>
#import <AppKit/NSTableView.h>
#import <AppKit/NSProgressIndicator.h>
#import <AppKit/NSView.h>
// needed for the methods called by alignmentInfo()
#import <AppKit/NSLayoutConstraint.h>
#define to(T, x) ((T *) (x))
#define toNSControl(x) to(NSControl, (x))
#define toNSScrollView(x) to(NSScrollView, (x))
#define toNSTableView(x) to(NSTableView, (x))
#define toNSProgressIndicator(x) to(NSProgressIndicator, (x))
#define toNSView(x) to(NSView, (x))
#define inScrollView(x) ([toNSScrollView((x)) documentView])
#define listboxInScrollView(x) toNSTableView(inScrollView((x)))
#define areaInScrollView(x) inScrollView((x))
struct xsize controlPrefSize(id control)
{
NSControl *c;
NSRect r;
struct xsize s;
c = toNSControl(control);
[c sizeToFit];
r = [c frame];
s.width = (intptr_t) r.size.width;
s.height = (intptr_t) r.size.height;
return s;
}
struct xsize listboxPrefSize(id scrollview)
{
NSTableView *c;
NSRect r;
struct xsize s;
c = listboxInScrollView(toNSScrollView(scrollview));
[c sizeToFit];
r = [c frame];
s.width = (intptr_t) r.size.width;
s.height = (intptr_t) r.size.height;
return s;
}
struct xsize pbarPrefSize(id control)
{
NSProgressIndicator *c;
NSRect r;
struct xsize s;
c = toNSProgressIndicator(control);
[c sizeToFit];
r = [c frame];
s.width = (intptr_t) r.size.width;
s.height = (intptr_t) r.size.height;
return s;
}
struct xsize areaPrefSize(id scrollview)
{
NSView *c;
NSRect r;
struct xsize s;
c = areaInScrollView(toNSScrollView(scrollview));
// don't size to fit; the frame size is already the size we want
r = [c frame];
s.width = (intptr_t) r.size.width;
s.height = (intptr_t) r.size.height;
return s;
}
struct xalignment alignmentInfo(id c, struct xrect newrect)
{
NSView *v;
struct xalignment a;
NSRect r;
v = toNSView(c);
r = NSMakeRect((CGFloat) newrect.x,
(CGFloat) newrect.y,
(CGFloat) newrect.width,
(CGFloat) newrect.height);
r = [v alignmentRectForFrame:r];
a.alignmentRect.x = (intptr_t) r.origin.x;
a.alignmentRect.y = (intptr_t) r.origin.y;
a.alignmentRect.width = (intptr_t) r.size.width;
a.alignmentRect.height = (intptr_t) r.size.height;
a.baseline = (intptr_t) [v baselineOffsetFromBottom];
return a;
}

BIN
prevlib.tar Normal file

Binary file not shown.

View File

@ -1,68 +0,0 @@
// 25 february 2014
package ui
// A ProgressBar is a horizontal rectangle that fills up from left to right to indicate the progress of a long-running task.
// This progress is represented by an integer within the range [0,100], representing a percentage.
// Alternatively, a progressbar can show an animation indicating that progress is being made but how much is indeterminate.
// Newly-created ProgressBars default to showing 0% progress.
type ProgressBar struct {
created bool
sysData *sysData
initProg int
}
// NewProgressBar creates a new ProgressBar.
func NewProgressBar() *ProgressBar {
return &ProgressBar{
sysData: mksysdata(c_progressbar),
}
}
// SetProgress sets the currently indicated progress amount on the ProgressBar.
// If percent is in the range [0,100], the progressBar shows that much percent complete.
// If percent is -1, the ProgressBar is made indeterminate.
// Otherwise, SetProgress panics.
// Calling SetProgress(-1) repeatedly will neither leave indeterminate mode nor stop any animation involved in indeterminate mode indefinitely; any other side-effect of doing so is implementation-defined.
func (p *ProgressBar) SetProgress(percent int) {
if percent < -1 || percent > 100 {
panic("percent value out of range")
}
if p.created {
p.sysData.setProgress(percent)
return
}
p.initProg = percent
}
func (p *ProgressBar) make(window *sysData) error {
err := p.sysData.make(window)
if err != nil {
return err
}
p.sysData.setProgress(p.initProg)
p.created = true
return nil
}
func (p *ProgressBar) allocate(x int, y int, width int, height int, d *sysSizeData) []*allocation {
return []*allocation{&allocation{
x: x,
y: y,
width: width,
height: height,
this: p,
}}
}
func (p *ProgressBar) preferredSize(d *sysSizeData) (width int, height int) {
return p.sysData.preferredSize(d)
}
func (p *ProgressBar) commitResize(a *allocation, d *sysSizeData) {
p.sysData.commitResize(a, d)
}
func (p *ProgressBar) getAuxResizeInfo(d *sysSizeData) {
p.sysData.getAuxResizeInfo(d)
}

222
stack.go
View File

@ -1,222 +0,0 @@
// 13 february 2014
package ui
import (
"fmt"
)
type orientation bool
const (
horizontal orientation = false
vertical orientation = true
)
// A Stack stacks controls horizontally or vertically within the Stack's parent.
// A horizontal Stack gives all controls the same height and their preferred widths.
// A vertical Stack gives all controls the same width and their preferred heights.
// Any extra space at the end of a Stack is left blank.
// Some controls may be marked as "stretchy": when the Window they are in changes size, stretchy controls resize to take up the remaining space after non-stretchy controls are laid out. If multiple controls are marked stretchy, they are alloted equal distribution of the remaining space.
type Stack struct {
created bool
orientation orientation
controls []Control
stretchy []bool
width, height []int // caches to avoid reallocating these each time
}
func newStack(o orientation, controls ...Control) *Stack {
return &Stack{
orientation: o,
controls: controls,
stretchy: make([]bool, len(controls)),
width: make([]int, len(controls)),
height: make([]int, len(controls)),
}
}
// NewHorizontalStack creates a new Stack that arranges the given Controls horizontally.
func NewHorizontalStack(controls ...Control) *Stack {
return newStack(horizontal, controls...)
}
// NewVerticalStack creates a new Stack that arranges the given Controls vertically.
func NewVerticalStack(controls ...Control) *Stack {
return newStack(vertical, controls...)
}
// SetStretchy marks a control in a Stack as stretchy. This cannot be called once the Window containing the Stack has been created.
// It panics if index is out of range.
func (s *Stack) SetStretchy(index int) {
if s.created {
panic("call to Stack.SetStretchy() after Stack has been created")
}
if index < 0 || index > len(s.stretchy) {
panic(fmt.Errorf("index %d out of range in Stack.SetStretchy()", index))
}
s.stretchy[index] = true
}
func (s *Stack) make(window *sysData) error {
for i, c := range s.controls {
err := c.make(window)
if err != nil {
return fmt.Errorf("error adding control %d to Stack: %v", i, err)
}
}
s.created = true
return nil
}
func (s *Stack) allocate(x int, y int, width int, height int, d *sysSizeData) (allocations []*allocation) {
var stretchywid, stretchyht int
var current *allocation // for neighboring
if len(s.controls) == 0 { // do nothing if there's nothing to do
return nil
}
// before we do anything, steal the margin so nested Stacks/Grids don't double down
xmargin := d.xmargin
ymargin := d.ymargin
d.xmargin = 0
d.ymargin = 0
// 0) inset the available rect by the margins and needed padding
x += xmargin
y += ymargin
width -= xmargin * 2
height -= ymargin * 2
if s.orientation == horizontal {
width -= (len(s.controls) - 1) * d.xpadding
} else {
height -= (len(s.controls) - 1) * d.ypadding
}
// 1) get height and width of non-stretchy controls; figure out how much space is alloted to stretchy controls
stretchywid = width
stretchyht = height
nStretchy := 0
for i, c := range s.controls {
if s.stretchy[i] {
nStretchy++
continue
}
w, h := c.preferredSize(d)
if s.orientation == horizontal { // all controls have same height
s.width[i] = w
s.height[i] = height
stretchywid -= w
} else { // all controls have same width
s.width[i] = width
s.height[i] = h
stretchyht -= h
}
}
// 2) figure out size of stretchy controls
if nStretchy != 0 {
if s.orientation == horizontal { // split rest of width
stretchywid /= nStretchy
} else { // split rest of height
stretchyht /= nStretchy
}
}
for i := range s.controls {
if !s.stretchy[i] {
continue
}
s.width[i] = stretchywid
s.height[i] = stretchyht
}
// 3) now actually place controls
for i, c := range s.controls {
as := c.allocate(x, y, s.width[i], s.height[i], d)
if s.orientation == horizontal { // no vertical neighbors
if current != nil { // connect first left to first right
current.neighbor = c
}
if len(as) != 0 {
current = as[0] // next left is first subwidget
} else {
current = nil // spaces don't have allocation data
}
}
allocations = append(allocations, as...)
if s.orientation == horizontal {
x += s.width[i] + d.xpadding
} else {
y += s.height[i] + d.ypadding
}
}
return allocations
}
// The preferred size of a Stack is the sum of the preferred sizes of non-stretchy controls + (the number of stretchy controls * the largest preferred size among all stretchy controls).
// We don't consider the margins here, but will need to if Window.SizeToFit() is ever made a thing.
func (s *Stack) preferredSize(d *sysSizeData) (width int, height int) {
max := func(a int, b int) int {
if a > b {
return a
}
return b
}
var nStretchy int
var maxswid, maxsht int
if len(s.controls) == 0 { // no controls, so return emptiness
return 0, 0
}
if s.orientation == horizontal {
width = (len(s.controls) - 1) * d.xpadding
} else {
height = (len(s.controls) - 1) * d.ypadding
}
for i, c := range s.controls {
w, h := c.preferredSize(d)
if s.stretchy[i] {
nStretchy++
maxswid = max(maxswid, w)
maxsht = max(maxsht, h)
}
if s.orientation == horizontal { // max vertical size
if !s.stretchy[i] {
width += w
}
height = max(height, h)
} else {
width = max(width, w)
if !s.stretchy[i] {
height += h
}
}
}
if s.orientation == horizontal {
width += nStretchy * maxswid
} else {
height += nStretchy * maxsht
}
return
}
func (s *Stack) commitResize(c *allocation, d *sysSizeData) {
// this is to satisfy Control; nothing to do here
}
func (s *Stack) getAuxResizeInfo(d *sysSizeData) {
// this is to satisfy Control; nothing to do here
}
// Space returns a null Control intended for padding layouts with blank space.
// It appears to its owner as a Control of 0x0 size.
//
// For a Stack, Space can be used to insert spaces in the beginning or middle of Stacks (Stacks by nature handle spaces at the end themselves). In order for this to work properly, make the Space stretchy.
//
// For a Grid, Space can be used to have an empty cell. A stretchy Grid cell with a Space can be used to anchor the perimeter of a Grid to the respective Window edges without making one of the other controls stretchy instead (leaving empty space in the Window otherwise). Otherwise, you do not need to do anything special for the Space to work (though remember that an entire row or column of Spaces will appear as having height or width zero, respectively, unless one is marked as stretchy).
//
// The value returned from Space() may or may not be unique.
func Space() Control {
return space
}
// As above, a Stack with no controls draws nothing and reports no errors; its parent will still size it properly if made stretchy.
var space Control = newStack(horizontal)

View File

@ -1,100 +0,0 @@
// 10 february 2014
package ui
import (
"fmt"
"unsafe"
)
var (
controlFont _HANDLE // really the font for messagebox text, but everyone and everything says to use it
titleFont _HANDLE
smallTitleFont _HANDLE
menubarFont _HANDLE
statusbarFont _HANDLE
)
type _LOGFONT struct {
lfHeight int32
lfWidth int32
lfEscapement int32
lfOrientation int32
lfWeight int32
lfItalic byte
lfUnderline byte
lfStrikeOut byte
lfCharSet byte
lfOutPrecision byte
lfClipPrecision byte
lfQuality byte
lfPitchAndFamily byte
lfFaceName [_LF_FACESIZE]uint16
}
type _NONCLIENTMETRICS struct {
cbSize uint32
iBorderWidth int32 // originally int
iScrollWidth int32 // originally int
iScrollHeight int32 // originally int
iCaptionWidth int32 // originally int
iCaptionHeight int32 // originally int
lfCaptionFont _LOGFONT
iSmCaptionWidth int32 // originally int
iSmCaptionHeight int32 // originally int
lfSmCaptionFont _LOGFONT
iMenuWidth int32 // originally int
iMenuHeight int32 // originally int
lfMenuFont _LOGFONT
lfStatusFont _LOGFONT
lfMessageFont _LOGFONT
}
var (
_systemParametersInfo = user32.NewProc("SystemParametersInfoW")
_createFontIndirect = gdi32.NewProc("CreateFontIndirectW")
)
func getStandardWindowFonts() (err error) {
var ncm _NONCLIENTMETRICS
ncm.cbSize = uint32(unsafe.Sizeof(ncm))
r1, _, err := _systemParametersInfo.Call(
uintptr(_SPI_GETNONCLIENTMETRICS),
uintptr(unsafe.Sizeof(ncm)),
uintptr(unsafe.Pointer(&ncm)),
0)
if r1 == 0 { // failure
return fmt.Errorf("error getting system parameters: %v", err)
}
getfont := func(which *_LOGFONT, what string) (_HANDLE, error) {
r1, _, err = _createFontIndirect.Call(uintptr(unsafe.Pointer(which)))
if r1 == 0 { // failure
return _NULL, fmt.Errorf("error getting %s font; Windows last error: %v", what, err)
}
return _HANDLE(r1), nil
}
controlFont, err = getfont(&ncm.lfMessageFont, "control")
if err != nil {
return err
}
titleFont, err = getfont(&ncm.lfCaptionFont, "titlebar")
if err != nil {
return err
}
smallTitleFont, err = getfont(&ncm.lfSmCaptionFont, "small titlebar")
if err != nil {
return err
}
menubarFont, err = getfont(&ncm.lfMenuFont, "menubar")
if err != nil {
return err
}
statusbarFont, err = getfont(&ncm.lfStatusFont, "statusbar")
if err != nil {
return err
}
return nil // all good
}

View File

@ -1,238 +0,0 @@
// 8 february 2014
package ui
import (
"fmt"
"syscall"
"unsafe"
)
var (
stdWndClass = toUTF16("gouiwnd")
)
var (
_defWindowProc = user32.NewProc("DefWindowProcW")
)
func defWindowProc(hwnd _HWND, uMsg uint32, wParam _WPARAM, lParam _LPARAM) _LRESULT {
r1, _, _ := _defWindowProc.Call(
uintptr(hwnd),
uintptr(uMsg),
uintptr(wParam),
uintptr(lParam))
return _LRESULT(r1)
}
// don't worry about error returns from GetWindowLongPtr()/SetWindowLongPtr()
// see comments of http://blogs.msdn.com/b/oldnewthing/archive/2014/02/03/10496248.aspx
func getWindowLongPtr(hwnd _HWND, what uintptr) uintptr {
r1, _, _ := _getWindowLongPtr.Call(
uintptr(hwnd),
what)
return r1
}
func setWindowLongPtr(hwnd _HWND, what uintptr, value uintptr) {
_setWindowLongPtr.Call(
uintptr(hwnd),
what,
value)
}
// we can store a pointer in extra space provided by Windows
// we'll store sysData there
// see http://blogs.msdn.com/b/oldnewthing/archive/2005/03/03/384285.aspx
func getSysData(hwnd _HWND) *sysData {
return (*sysData)(unsafe.Pointer(getWindowLongPtr(hwnd, negConst(_GWLP_USERDATA))))
}
func storeSysData(hwnd _HWND, uMsg uint32, wParam _WPARAM, lParam _LPARAM) _LRESULT {
// we can get the lpParam from CreateWindowEx() in WM_NCCREATE and WM_CREATE
// we can freely skip any messages that come prior
// see http://blogs.msdn.com/b/oldnewthing/archive/2005/04/22/410773.aspx and http://blogs.msdn.com/b/oldnewthing/archive/2014/02/03/10496248.aspx (note the date on the latter one!)
if uMsg == _WM_NCCREATE {
// the lpParam to CreateWindowEx() is the first uintptr of the CREATESTRUCT
// so rather than create that whole structure, we'll just grab the uintptr at the address pointed to by lParam
cs := (*uintptr)(unsafe.Pointer(lParam))
saddr := *cs
setWindowLongPtr(hwnd, negConst(_GWLP_USERDATA), saddr)
// also set s.hwnd here so it can be used by other window messages right away
s := (*sysData)(unsafe.Pointer(saddr))
s.hwnd = hwnd
}
// then regardless of what happens, defer to DefWindowProc() (if you trace the execution of the above links, this is what they do)
return defWindowProc(hwnd, uMsg, wParam, lParam)
}
var (
_getFocus = user32.NewProc("GetFocus")
_isChild = user32.NewProc("IsChild")
// _setFocus in area_windows.go
)
// this is needed to ensure focus is preserved when switching away from and back to our program
// from http://blogs.msdn.com/b/oldnewthing/archive/2014/05/21/10527168.aspx
func (s *sysData) handleFocus(wParam _WPARAM) {
// parameter splitting from Microsoft's windowsx.h
state := uint32(wParam.LOWORD()) // originally UINT
minimized := wParam.HIWORD() != 0
if minimized { // don't do anything on minimize
return
}
if state == _WA_INACTIVE { // focusing out
old, _, _ := _getFocus.Call()
if _HWND(old) != _HWND(_NULL) { // if there is one
r1, _, _ := _isChild.Call(
uintptr(s.hwnd),
old)
if r1 != 0 {
s.lastfocus = _HWND(old)
}
}
} else { // focusing in
if s.lastfocus != _HWND(_NULL) { // if we have one
// don't bother checking SetFocus()'s error; see http://stackoverflow.com/questions/24073695/winapi-can-setfocus-return-null-without-an-error-because-thats-what-im-see/24074912#24074912
_setFocus.Call(uintptr(s.lastfocus))
}
}
}
func stdWndProc(hwnd _HWND, uMsg uint32, wParam _WPARAM, lParam _LPARAM) _LRESULT {
s := getSysData(hwnd)
if s == nil { // not yet saved
return storeSysData(hwnd, uMsg, wParam, lParam)
}
switch uMsg {
case msgPost:
data := (*interface{})(unsafe.Pointer(lParam))
s.post(*data)
return 0
case _WM_COMMAND:
id := _HMENU(wParam.LOWORD())
s.childrenLock.Lock()
ss := s.children[id]
s.childrenLock.Unlock()
switch ss.ctype {
case c_button:
if wParam.HIWORD() == _BN_CLICKED {
ss.event()
}
case c_checkbox:
// we opt into doing this ourselves because http://blogs.msdn.com/b/oldnewthing/archive/2014/05/22/10527522.aspx
if wParam.HIWORD() == _BN_CLICKED {
state, _, _ := _sendMessage.Call(
uintptr(ss.hwnd),
uintptr(_BM_GETCHECK),
uintptr(0),
uintptr(0))
if state == _BST_CHECKED {
state = _BST_UNCHECKED
} else if state == _BST_UNCHECKED {
state = _BST_CHECKED
}
_sendMessage.Call(
uintptr(ss.hwnd),
uintptr(_BM_SETCHECK),
state, // already uintptr
uintptr(0))
}
}
return 0
case _WM_ACTIVATE:
s.handleFocus(wParam)
return 0
case _WM_GETMINMAXINFO:
mm := lParam.MINMAXINFO()
// ... minimum size
_ = mm
return 0
case _WM_SIZE:
if s.allocate != nil {
var r _RECT
r1, _, err := _getClientRect.Call(
uintptr(hwnd),
uintptr(unsafe.Pointer(&r)))
if r1 == 0 {
panic("GetClientRect failed: " + err.Error())
}
// top-left corner of a client rect is always (0,0) so no need for left/top
s.resizeWindow(int(r.right), int(r.bottom))
// TODO use the Defer movement functions here?
// TODO redraw window and all children here?
}
return 0
case _WM_CLOSE:
if s.close() {
// TODO destroy
s.hide()
}
return 0
default:
return defWindowProc(hwnd, uMsg, wParam, lParam)
}
panic(fmt.Sprintf("stdWndProc message %d did not return: internal bug in ui library", uMsg))
}
type _WNDCLASS struct {
style uint32
lpfnWndProc uintptr
cbClsExtra int32 // originally int
cbWndExtra int32 // originally int
hInstance _HANDLE
hIcon _HANDLE
hCursor _HANDLE
hbrBackground _HBRUSH
lpszMenuName *uint16
lpszClassName uintptr
}
var (
icon, cursor _HANDLE
)
var (
_registerClass = user32.NewProc("RegisterClassW")
)
func registerStdWndClass() (err error) {
wc := &_WNDCLASS{
lpszClassName: utf16ToArg(stdWndClass),
lpfnWndProc: syscall.NewCallback(stdWndProc),
hInstance: hInstance,
hIcon: icon,
hCursor: cursor,
hbrBackground: _HBRUSH(_COLOR_BTNFACE + 1),
}
r1, _, err := _registerClass.Call(uintptr(unsafe.Pointer(wc)))
if r1 == 0 { // failure
return err
}
return nil
}
// no need to use/recreate MAKEINTRESOURCE() here as the Windows constant generator already took care of that because Microsoft's headers do already
func initWndClassInfo() (err error) {
r1, _, err := user32.NewProc("LoadIconW").Call(
uintptr(_NULL),
uintptr(_IDI_APPLICATION))
if r1 == 0 { // failure
return fmt.Errorf("error getting window icon: %v", err)
}
icon = _HANDLE(r1)
r1, _, err = user32.NewProc("LoadCursorW").Call(
uintptr(_NULL),
uintptr(_IDC_ARROW))
if r1 == 0 { // failure
return fmt.Errorf("error getting window cursor: %v", err)
}
cursor = _HANDLE(r1)
return nil
}

View File

@ -1,62 +0,0 @@
// 11 february 2014
package ui
// The sysData type contains all system data. It provides the system-specific underlying implementation. It is guaranteed to have the following by embedding:
type cSysData struct {
ctype int
allocate func(x int, y int, width int, height int, d *sysSizeData) []*allocation
spaced bool
alternate bool // editable for Combobox, multi-select for listbox, password for lineedit
handler AreaHandler // for Areas; TODO rename to areahandler
close func() bool // provided by each Window
post func(interface{}) // provided by each Window
event func() // provided by each control
}
// this interface is used to make sure all sysDatas are synced
var _xSysData interface {
sysDataSizingFunctions
make(window *sysData) error
firstShow() error
show()
hide()
setText(text string)
setRect(x int, y int, width int, height int, winheight int) error
isChecked() bool
text() string
append(string)
insertBefore(string, int)
selectedIndex() int
selectedIndices() []int
selectedTexts() []string
setWindowSize(int, int) error
setProgress(int)
len() int
setAreaSize(int, int)
repaintAll()
center()
setChecked(bool)
} = &sysData{} // this line will error if there's an inconsistency
const (
c_window = iota
c_button
c_checkbox
c_combobox
c_lineedit
c_label
c_listbox
c_progressbar
c_area
nctypes
)
func mksysdata(ctype int) *sysData {
s := &sysData{
cSysData: cSysData{
ctype: ctype,
},
}
return s
}

View File

@ -1,324 +0,0 @@
// 1 march 2014
package ui
import (
"fmt"
"sync"
)
// #include "objc_darwin.h"
import "C"
type sysData struct {
cSysData
id C.id
trackingArea C.id // for Area
}
type classData struct {
make func(parentWindow C.id, alternate bool, s *sysData) C.id
getinside func(scrollview C.id) C.id
show func(what C.id)
hide func(what C.id)
settext func(what C.id, text C.id)
text func(what C.id, alternate bool) C.id
append func(id C.id, what string, alternate bool)
insertBefore func(id C.id, what string, before int, alternate bool)
selIndex func(id C.id) int
selIndices func(id C.id) []int
selTexts func(id C.id) []string
delete func(id C.id, index int)
len func(id C.id) int
}
func addControl(parentWindow C.id, control C.id) {
C.addControl(parentWindow, control)
}
func controlShow(what C.id) {
C.controlShow(what)
}
func controlHide(what C.id) {
C.controlHide(what)
}
// By default some controls do not use the correct font.
// These functions set the appropriate control font.
// Which one is used on each control was determined by comparing https://developer.apple.com/library/mac/documentation/UserExperience/Conceptual/AppleHIGuidelines/Characteristics/Characteristics.html#//apple_ref/doc/uid/TP40002721-SW10 to what Interface Builder says for each control.
// (not applicable to ProgressBar, Area)
// Button, Checkbox, Combobox, LineEdit, Label, Listbox
func applyStandardControlFont(id C.id) {
C.applyStandardControlFont(id)
}
var classTypes = [nctypes]*classData{
c_window: &classData{
make: func(parentWindow C.id, alternate bool, s *sysData) C.id {
return C.makeWindow(appDelegate)
},
show: func(what C.id) {
C.windowShow(what)
},
hide: func(what C.id) {
C.windowHide(what)
},
settext: func(what C.id, text C.id) {
C.windowSetTitle(what, text)
},
text: func(what C.id, alternate bool) C.id {
return C.windowTitle(what)
},
},
c_button: &classData{
make: func(parentWindow C.id, alternate bool, s *sysData) C.id {
button := C.makeButton()
C.buttonSetTargetAction(button, appDelegate)
applyStandardControlFont(button)
addControl(parentWindow, button)
return button
},
show: controlShow,
hide: controlHide,
settext: func(what C.id, text C.id) {
C.buttonSetText(what, text)
},
text: func(what C.id, alternate bool) C.id {
return C.buttonText(what)
},
},
c_checkbox: &classData{
make: func(parentWindow C.id, alternate bool, s *sysData) C.id {
checkbox := C.makeCheckbox()
applyStandardControlFont(checkbox)
addControl(parentWindow, checkbox)
return checkbox
},
show: controlShow,
hide: controlHide,
settext: func(what C.id, text C.id) {
C.buttonSetText(what, text)
},
text: func(what C.id, alternate bool) C.id {
return C.buttonText(what)
},
},
c_combobox: &classData{
make: func(parentWindow C.id, alternate bool, s *sysData) C.id {
combobox := C.makeCombobox(toBOOL(alternate))
applyStandardControlFont(combobox)
addControl(parentWindow, combobox)
return combobox
},
show: controlShow,
hide: controlHide,
text: func(what C.id, alternate bool) C.id {
return C.comboboxText(what, toBOOL(alternate))
},
append: func(id C.id, what string, alternate bool) {
C.comboboxAppend(id, toBOOL(alternate), toNSString(what))
},
insertBefore: func(id C.id, what string, before int, alternate bool) {
C.comboboxInsertBefore(id, toBOOL(alternate),
toNSString(what), C.intptr_t(before))
},
selIndex: func(id C.id) int {
return int(C.comboboxSelectedIndex(id))
},
delete: func(id C.id, index int) {
C.comboboxDelete(id, C.intptr_t(index))
},
len: func(id C.id) int {
return int(C.comboboxLen(id))
},
},
c_lineedit: &classData{
make: func(parentWindow C.id, alternate bool, s *sysData) C.id {
lineedit := C.makeLineEdit(toBOOL(alternate))
applyStandardControlFont(lineedit)
addControl(parentWindow, lineedit)
return lineedit
},
show: controlShow,
hide: controlHide,
settext: func(what C.id, text C.id) {
C.lineeditSetText(what, text)
},
text: func(what C.id, alternate bool) C.id {
return C.lineeditText(what)
},
},
c_label: &classData{
make: func(parentWindow C.id, alternate bool, s *sysData) C.id {
label := C.makeLabel()
applyStandardControlFont(label)
addControl(parentWindow, label)
return label
},
show: controlShow,
hide: controlHide,
settext: func(what C.id, text C.id) {
C.lineeditSetText(what, text)
},
text: func(what C.id, alternate bool) C.id {
return C.lineeditText(what)
},
},
c_listbox: &classData{
make: makeListbox,
show: controlShow,
hide: controlHide,
append: listboxAppend,
insertBefore: listboxInsertBefore,
selIndices: listboxSelectedIndices,
selTexts: listboxSelectedTexts,
delete: listboxDelete,
len: listboxLen,
},
c_progressbar: &classData{
make: func(parentWindow C.id, alternate bool, s *sysData) C.id {
pbar := C.makeProgressBar()
addControl(parentWindow, pbar)
return pbar
},
show: controlShow,
hide: controlHide,
},
c_area: &classData{
make: makeArea,
getinside: areaInScrollView,
show: controlShow,
hide: controlHide,
},
}
// I need to access sysData from appDelegate, but appDelegate doesn't store any data. So, this.
var (
sysdatas = make(map[C.id]*sysData)
sysdatalock sync.Mutex
)
func addSysData(key C.id, value *sysData) {
sysdatalock.Lock()
sysdatas[key] = value
sysdatalock.Unlock()
}
func getSysData(key C.id) *sysData {
sysdatalock.Lock()
defer sysdatalock.Unlock()
v, ok := sysdatas[key]
if !ok {
panic(fmt.Errorf("internal error: getSysData(%v) unknown", key))
}
return v
}
func (s *sysData) make(window *sysData) error {
var parentWindow C.id
ct := classTypes[s.ctype]
if window != nil {
parentWindow = window.id
}
s.id = ct.make(parentWindow, s.alternate, s)
if ct.getinside != nil {
addSysData(ct.getinside(s.id), s)
} else {
addSysData(s.id, s)
}
return nil
}
// used for Windows; nothing special needed elsewhere
func (s *sysData) firstShow() error {
s.show()
return nil
}
func (s *sysData) show() {
classTypes[s.ctype].show(s.id)
}
func (s *sysData) hide() {
classTypes[s.ctype].hide(s.id)
}
func (s *sysData) setText(text string) {
classTypes[s.ctype].settext(s.id, toNSString(text))
}
func (s *sysData) setRect(x int, y int, width int, height int, winheight int) error {
// winheight - y because (0,0) is the bottom-left corner of the window and not the top-left corner
// (winheight - y) - height because (x, y) is the bottom-left corner of the control and not the top-left
C.setRect(s.id,
C.intptr_t(x), C.intptr_t((winheight-y)-height),
C.intptr_t(width), C.intptr_t(height))
return nil
}
func (s *sysData) isChecked() bool {
return C.isCheckboxChecked(s.id) != C.NO
}
func (s *sysData) text() string {
str := classTypes[s.ctype].text(s.id, s.alternate)
return fromNSString(str)
}
func (s *sysData) append(what string) {
classTypes[s.ctype].append(s.id, what, s.alternate)
}
func (s *sysData) insertBefore(what string, before int) {
classTypes[s.ctype].insertBefore(s.id, what, before, s.alternate)
}
func (s *sysData) selectedIndex() int {
return classTypes[s.ctype].selIndex(s.id)
}
func (s *sysData) selectedIndices() []int {
return classTypes[s.ctype].selIndices(s.id)
}
func (s *sysData) selectedTexts() []string {
return classTypes[s.ctype].selTexts(s.id)
}
func (s *sysData) setWindowSize(width int, height int) error {
C.windowSetContentSize(s.id, C.intptr_t(width), C.intptr_t(height))
return nil
}
func (s *sysData) delete(index int) {
classTypes[s.ctype].delete(s.id, index)
}
func (s *sysData) setProgress(percent int) {
C.setProgress(s.id, C.intptr_t(percent))
}
func (s *sysData) len() int {
return classTypes[s.ctype].len(s.id)
}
func (s *sysData) setAreaSize(width int, height int) {
C.setAreaSize(s.id, C.intptr_t(width), C.intptr_t(height))
}
func (s *sysData) repaintAll() {
C.display(s.id)
}
func (s *sysData) center() {
C.center(s.id)
}
func (s *sysData) setChecked(checked bool) {
C.setCheckboxChecked(s.id, toBOOL(checked))
}

View File

@ -1,240 +0,0 @@
// 12 may 2014
#include "objc_darwin.h"
#import <Foundation/NSGeometry.h>
#import <AppKit/NSWindow.h>
#import <AppKit/NSView.h>
#import <AppKit/NSFont.h>
#import <AppKit/NSControl.h>
#import <AppKit/NSButton.h>
#import <AppKit/NSTextField.h>
#import <AppKit/NSSecureTextField.h>
#import <AppKit/NSProgressIndicator.h>
#import <AppKit/NSScrollView.h>
// general TODO: go through all control constructors and their equivalent controls in Interface Builder to see if there's any qualities I'm missing
extern NSRect dummyRect;
#define to(T, x) ((T *) (x))
#define toNSWindow(x) to(NSWindow, (x))
#define toNSView(x) to(NSView, (x))
#define toNSControl(x) to(NSControl, (x))
#define toNSButton(x) to(NSButton, (x))
#define toNSTextField(x) to(NSTextField, (x))
#define toNSProgressIndicator(x) to(NSProgressIndicator, (x))
#define toNSScrollView(x) to(NSScrollView, (x))
#define toNSInteger(x) ((NSInteger) (x))
#define fromNSInteger(x) ((intptr_t) (x))
#define inScrollView(x) ([toNSScrollView((x)) documentView])
#define areaInScrollView(x) inScrollView((x))
void addControl(id parentWindow, id control)
{
[[toNSWindow(parentWindow) contentView] addSubview:control];
}
void controlShow(id what)
{
[toNSView(what) setHidden:NO];
}
void controlHide(id what)
{
[toNSView(what) setHidden:YES];
}
#define systemFontOfSize(s) ([NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:(s)]])
void applyStandardControlFont(id what)
{
[toNSControl(what) setFont:systemFontOfSize(NSRegularControlSize)];
}
id makeWindow(id delegate)
{
NSWindow *w;
w = [[NSWindow alloc]
initWithContentRect:dummyRect
styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)
backing:NSBackingStoreBuffered
defer:YES]; // defer creation of device until we show the window
[w setDelegate:delegate];
// we do not need setAcceptsMouseMovedEvents: here since we are using a tracking rect in Areas for that
return w;
}
void windowShow(id window)
{
[toNSWindow(window) makeKeyAndOrderFront:window];
}
void windowHide(id window)
{
[toNSWindow(window) orderOut:window];
}
void windowSetTitle(id window, id title)
{
[toNSWindow(window) setTitle:title];
}
id windowTitle(id window)
{
return [toNSWindow(window) title];
}
id makeButton(void)
{
NSButton *button;
button = [[NSButton alloc]
initWithFrame:dummyRect];
[button setBezelStyle:NSRoundedBezelStyle];
return button;
}
void buttonSetTargetAction(id button, id delegate)
{
[toNSButton(button) setTarget:delegate];
[toNSButton(button) setAction:@selector(buttonClicked:)];
}
void buttonSetText(id button, id text)
{
[toNSButton(button) setTitle:text];
}
id buttonText(id button)
{
return [toNSButton(button) title];
}
id makeCheckbox(void)
{
NSButton *checkbox;
checkbox = [[NSButton alloc]
initWithFrame:dummyRect];
[checkbox setButtonType:NSSwitchButton];
return checkbox;
}
id makeLineEdit(BOOL password)
{
id c;
if (password)
c = [[NSSecureTextField alloc]
initWithFrame:dummyRect];
else
c = [[NSTextField alloc]
initWithFrame:dummyRect];
// Interface Builder does this to make the text box behave properly
// see makeLabel() for other side effects
[[toNSTextField(c) cell] setLineBreakMode:NSLineBreakByClipping];
// Interface Builder also sets this to allow horizontal scrolling
[[toNSTextField(c) cell] setScrollable:YES];
return c;
}
void lineeditSetText(id lineedit, id text)
{
[toNSTextField(lineedit) setStringValue:text];
}
id lineeditText(id lineedit)
{
return [toNSTextField(lineedit) stringValue];
}
id makeLabel(void)
{
NSTextField *label;
label = [[NSTextField alloc]
initWithFrame:dummyRect];
[label setEditable:NO];
[label setBordered:NO];
[label setDrawsBackground:NO];
// this disables both word wrap AND ellipsizing in one fell swoop
// we have to send to the control's cell for this
// Interface Builder also sets this for its labels, so...
[[label cell] setLineBreakMode:NSLineBreakByClipping];
// for a multiline label, we either use WordWrapping and send setTruncatesLastVisibleLine: to disable ellipsizing OR use one of those ellipsizing styles
return label;
}
id makeProgressBar(void)
{
NSProgressIndicator *pbar;
pbar = [[NSProgressIndicator alloc]
initWithFrame:dummyRect];
[pbar setStyle:NSProgressIndicatorBarStyle];
[pbar setIndeterminate:NO];
[pbar stopAnimation:pbar];
return pbar;
}
void setRect(id what, intptr_t x, intptr_t y, intptr_t width, intptr_t height)
{
[toNSView(what) setFrame:NSMakeRect((CGFloat) x, (CGFloat) y, (CGFloat) width, (CGFloat) height)];
}
BOOL isCheckboxChecked(id checkbox)
{
return [toNSButton(checkbox) state] == NSOnState;
}
void windowSetContentSize(id window, intptr_t width, intptr_t height)
{
NSWindow *win;
win = toNSWindow(window);
// use -[NSWindow setContentSize:], which will resize the window without taking the titlebar as part of the given size and without needing us to consider the window's position (the function takes care of both for us)
[win setContentSize:NSMakeSize((CGFloat) width, (CGFloat) height)];
[win display]; // TODO needed?
}
void setProgress(id pbar, intptr_t percent)
{
NSProgressIndicator *p;
p = toNSProgressIndicator(pbar);
if (percent == -1) {
[p setIndeterminate:YES];
[p startAnimation:p];
return;
}
[p stopAnimation:p]; // will have no effect if we were already determinate
[p setIndeterminate:NO];
[p setDoubleValue:((double) percent)];
}
void setAreaSize(id scrollview, intptr_t width, intptr_t height)
{
NSView *area;
area = areaInScrollView(scrollview);
[area setFrame:NSMakeRect(0, 0, (CGFloat) width, (CGFloat) height)];
[area display]; // and redraw
}
void center(id w)
{
[toNSWindow(w) center];
}
void setCheckboxChecked(id checkbox, BOOL check)
{
// -[NSButton setState:] takes a NSInteger but the state constants are NSCellStateValue which is NSUInteger (despite NSMixedState being -1); let's play it safe here
if (check) {
[toNSButton(checkbox) setState:NSOnState];
return;
}
[toNSButton(checkbox) setState:NSOffState];
}

View File

@ -1,287 +0,0 @@
// +build !windows,!darwin,!plan9
// 16 february 2014
package ui
import (
"unsafe"
)
// #include "gtk_unix.h"
// extern gboolean our_pulse_callback(gpointer);
import "C"
type sysData struct {
cSysData
widget *C.GtkWidget
container *C.GtkWidget // for moving
pulseTimer C.guint // for indeterminate progress bars
clickCounter clickCounter // for Areas
// we probably don't need to save these, but we'll do so for sysData.preferredSize() just in case
areawidth int
areaheight int
}
type classData struct {
make func() *C.GtkWidget
makeAlt func() *C.GtkWidget
setText func(widget *C.GtkWidget, text string)
text func(widget *C.GtkWidget) string
append func(widget *C.GtkWidget, text string)
insert func(widget *C.GtkWidget, index int, text string)
selected func(widget *C.GtkWidget) int
selMulti func(widget *C.GtkWidget) []int
smtexts func(widget *C.GtkWidget) []string
delete func(widget *C.GtkWidget, index int)
len func(widget *C.GtkWidget) int
// ...
signals callbackMap
child func(widget *C.GtkWidget) *C.GtkWidget
childsigs callbackMap
}
var classTypes = [nctypes]*classData{
c_window: &classData{
make: gtk_window_new,
setText: gtk_window_set_title,
text: gtk_window_get_title,
signals: callbackMap{
"delete-event": window_delete_event_callback,
"configure-event": window_configure_event_callback,
},
},
c_button: &classData{
make: gtk_button_new,
setText: gtk_button_set_label,
text: gtk_button_get_label,
signals: callbackMap{
"clicked": button_clicked_callback,
},
},
c_checkbox: &classData{
make: gtk_check_button_new,
setText: gtk_button_set_label,
text: gtk_button_get_label,
},
c_combobox: &classData{
make: gtk_combo_box_text_new,
makeAlt: gtk_combo_box_text_new_with_entry,
text: gtk_combo_box_text_get_active_text,
append: gtk_combo_box_text_append_text,
insert: gtk_combo_box_text_insert_text,
selected: gtk_combo_box_get_active,
delete: gtk_combo_box_text_remove,
len: gtkComboBoxLen,
},
c_lineedit: &classData{
make: gtk_entry_new,
makeAlt: gtkPasswordEntryNew,
setText: gtk_entry_set_text,
text: gtk_entry_get_text,
},
c_label: &classData{
make: gtk_label_new,
makeAlt: gtk_label_new_standalone,
setText: gtk_label_set_text,
text: gtk_label_get_text,
},
c_listbox: &classData{
make: gListboxNewSingle,
makeAlt: gListboxNewMulti,
text: gListboxText,
append: gListboxAppend,
insert: gListboxInsert,
selMulti: gListboxSelectedMulti,
smtexts: gListboxSelMultiTexts,
delete: gListboxDelete,
len: gListboxLen,
},
c_progressbar: &classData{
make: gtk_progress_bar_new,
},
c_area: &classData{
make: gtkAreaNew,
child: gtkAreaGetControl,
childsigs: callbackMap{
"draw": area_draw_callback,
"button-press-event": area_button_press_event_callback,
"button-release-event": area_button_release_event_callback,
"motion-notify-event": area_motion_notify_event_callback,
"enter-notify-event": area_enterleave_notify_event_callback,
"leave-notify-event": area_enterleave_notify_event_callback,
"key-press-event": area_key_press_event_callback,
"key-release-event": area_key_release_event_callback,
},
},
}
func (s *sysData) make(window *sysData) error {
ct := classTypes[s.ctype]
if s.alternate {
s.widget = ct.makeAlt()
} else {
s.widget = ct.make()
}
if window == nil {
fixed := gtkNewWindowLayout()
gtk_container_add(s.widget, fixed)
for signame, sigfunc := range ct.signals {
g_signal_connect(s.widget, signame, sigfunc, s)
}
s.container = fixed
} else {
s.container = window.container
gtkAddWidgetToLayout(s.container, s.widget)
for signame, sigfunc := range ct.signals {
g_signal_connect(s.widget, signame, sigfunc, s)
}
if ct.child != nil {
child := ct.child(s.widget)
for signame, sigfunc := range ct.childsigs {
g_signal_connect(child, signame, sigfunc, s)
}
}
}
return nil
}
// see sysData.center()
func (s *sysData) resetposition() {
C.gtk_window_set_position(togtkwindow(s.widget), C.GTK_WIN_POS_NONE)
}
// used for Windows; nothing special needed elsewhere
func (s *sysData) firstShow() error {
s.show()
return nil
}
func (s *sysData) show() {
gtk_widget_show(s.widget)
s.resetposition()
}
func (s *sysData) hide() {
gtk_widget_hide(s.widget)
s.resetposition()
}
func (s *sysData) setText(text string) {
classTypes[s.ctype].setText(s.widget, text)
}
func (s *sysData) setRect(x int, y int, width int, height int, winheight int) error {
gtkMoveWidgetInLayout(s.container, s.widget, x, y)
gtk_widget_set_size_request(s.widget, width, height)
return nil
}
func (s *sysData) isChecked() bool {
return gtk_toggle_button_get_active(s.widget)
}
func (s *sysData) text() string {
return classTypes[s.ctype].text(s.widget)
}
func (s *sysData) append(what string) {
classTypes[s.ctype].append(s.widget, what)
}
func (s *sysData) insertBefore(what string, before int) {
classTypes[s.ctype].insert(s.widget, before, what)
}
func (s *sysData) selectedIndex() int {
return classTypes[s.ctype].selected(s.widget)
}
func (s *sysData) selectedIndices() []int {
return classTypes[s.ctype].selMulti(s.widget)
}
func (s *sysData) selectedTexts() []string {
return classTypes[s.ctype].smtexts(s.widget)
}
func (s *sysData) setWindowSize(width int, height int) error {
// does not take window geometry into account (and cannot, since the window manager won't give that info away)
// thanks to TingPing in irc.gimp.net/#gtk+
gtk_window_resize(s.widget, width, height)
return nil
}
func (s *sysData) delete(index int) {
classTypes[s.ctype].delete(s.widget, index)
}
// With GTK+, we must manually pulse the indeterminate progressbar ourselves.
// To ensure pulsing runs on the main lop and doesn't cause any other weird racy things, we'll use g_timeout_add().
// Zenity 3.4 does this too (https://git.gnome.org/browse/zenity/tree/src/progress.c?id=3.4.0).
// The following is Zenity 3.4's pulse rate.
const pulseRate = 100 // in milliseconds
//export our_pulse_callback
func our_pulse_callback(data C.gpointer) C.gboolean {
// TODO this can be called when closing the window
s := (*sysData)(unsafe.Pointer(data))
gtk_progress_bar_pulse(s.widget)
return C.TRUE // continue processing
}
func (s *sysData) setProgress(percent int) {
if s.pulseTimer != 0 { // kill current timer
// TODO only if not indeterminate already?
C.g_source_remove(s.pulseTimer)
s.pulseTimer = 0
}
if percent == -1 {
gtk_progress_bar_pulse(s.widget) // start animation now
s.pulseTimer = C.g_timeout_add(pulseRate,
C.GSourceFunc(C.our_pulse_callback),
C.gpointer(unsafe.Pointer(s)))
return
}
gtk_progress_bar_set_fraction(s.widget, percent)
}
func (s *sysData) len() int {
return classTypes[s.ctype].len(s.widget)
}
func (s *sysData) setAreaSize(width int, height int) {
c := gtkAreaGetControl(s.widget)
gtk_widget_set_size_request(c, width, height)
s.areawidth = width // for sysData.preferredSize()
s.areaheight = height
C.gtk_widget_queue_draw(c)
}
// TODO should this be made safe? (TODO move to area.go)
func (s *sysData) repaintAll() {
c := gtkAreaGetControl(s.widget)
C.gtk_widget_queue_draw(c)
}
func (s *sysData) center() {
if C.gtk_widget_get_visible(s.widget) == C.FALSE {
// hint to the WM to make it centered when it is shown again
// thanks to Jasper in irc.gimp.net/#gtk+
C.gtk_window_set_position(togtkwindow(s.widget), C.GTK_WIN_POS_CENTER)
} else {
var width, height C.gint
s.resetposition()
//we should be able to use gravity to simplify this, but it doesn't take effect immediately, and adding show calls does nothing (thanks Jasper in irc.gimp.net/#gtk+)
C.gtk_window_get_size(togtkwindow(s.widget), &width, &height)
C.gtk_window_move(togtkwindow(s.widget),
(C.gdk_screen_width() / 2) - (width / 2),
(C.gdk_screen_height() / 2) - (width / 2))
}
}
func (s *sysData) setChecked(checked bool) {
gtk_toggle_button_set_active(s.widget, checked)
}

View File

@ -1,525 +0,0 @@
// 11 february 2014
package ui
import (
"fmt"
"sync"
"syscall"
"unsafe"
)
type sysData struct {
cSysData
hwnd _HWND
children map[_HMENU]*sysData
nextChildID _HMENU
childrenLock sync.Mutex
isMarquee bool // for sysData.setProgress()
// unlike with GTK+ and Mac OS X, we're responsible for sizing Area properly ourselves
areawidth int
areaheight int
clickCounter clickCounter
lastfocus _HWND
}
type classData struct {
name *uint16
style uint32
xstyle uint32
altStyle uint32
storeSysData bool
doNotLoadFont bool
appendMsg uintptr
insertBeforeMsg uintptr
deleteMsg uintptr
selectedIndexMsg uintptr
selectedIndexErr uintptr
addSpaceErr uintptr
lenMsg uintptr
}
const controlstyle = _WS_CHILD | _WS_VISIBLE | _WS_TABSTOP
const controlxstyle = 0
var classTypes = [nctypes]*classData{
c_window: &classData{
name: stdWndClass,
style: _WS_OVERLAPPEDWINDOW,
xstyle: 0,
storeSysData: true,
doNotLoadFont: true,
},
c_button: &classData{
name: toUTF16("BUTTON"),
style: _BS_PUSHBUTTON | controlstyle,
xstyle: 0 | controlxstyle,
},
c_checkbox: &classData{
name: toUTF16("BUTTON"),
// don't use BS_AUTOCHECKBOX because http://blogs.msdn.com/b/oldnewthing/archive/2014/05/22/10527522.aspx
style: _BS_CHECKBOX | controlstyle,
xstyle: 0 | controlxstyle,
},
c_combobox: &classData{
name: toUTF16("COMBOBOX"),
style: _CBS_DROPDOWNLIST | _WS_VSCROLL | controlstyle,
xstyle: 0 | controlxstyle,
altStyle: _CBS_DROPDOWN | _CBS_AUTOHSCROLL | _WS_VSCROLL | controlstyle,
appendMsg: _CB_ADDSTRING,
insertBeforeMsg: _CB_INSERTSTRING,
deleteMsg: _CB_DELETESTRING,
selectedIndexMsg: _CB_GETCURSEL,
selectedIndexErr: negConst(_CB_ERR),
addSpaceErr: negConst(_CB_ERRSPACE),
lenMsg: _CB_GETCOUNT,
},
c_lineedit: &classData{
name: toUTF16("EDIT"),
// WS_EX_CLIENTEDGE without WS_BORDER will apply visual styles
// thanks to MindChild in irc.efnet.net/#winprog
style: _ES_AUTOHSCROLL | controlstyle,
xstyle: _WS_EX_CLIENTEDGE | controlxstyle,
altStyle: _ES_PASSWORD | _ES_AUTOHSCROLL | controlstyle,
},
c_label: &classData{
name: toUTF16("STATIC"),
// SS_NOPREFIX avoids accelerator translation; SS_LEFTNOWORDWRAP clips text past the end
// controls are vertically aligned to the top by default (thanks Xeek in irc.freenode.net/#winapi)
// also note that tab stops are remove dfor labels
style: (_SS_NOPREFIX | _SS_LEFTNOWORDWRAP | controlstyle) &^ _WS_TABSTOP,
xstyle: 0 | controlxstyle,
// MAKE SURE THIS IS THE SAME
altStyle: (_SS_NOPREFIX | _SS_LEFTNOWORDWRAP | controlstyle) &^ _WS_TABSTOP,
},
c_listbox: &classData{
name: toUTF16("LISTBOX"),
// we don't use LBS_STANDARD because it sorts (and has WS_BORDER; see above)
// LBS_NOINTEGRALHEIGHT gives us exactly the size we want
// LBS_MULTISEL sounds like it does what we want but it actually doesn't; instead, it toggles item selection regardless of modifier state, which doesn't work like anything else (see http://msdn.microsoft.com/en-us/library/windows/desktop/bb775149%28v=vs.85%29.aspx and http://msdn.microsoft.com/en-us/library/windows/desktop/aa511485.aspx)
style: _LBS_NOTIFY | _LBS_NOINTEGRALHEIGHT | _WS_VSCROLL | controlstyle,
xstyle: _WS_EX_CLIENTEDGE | controlxstyle,
altStyle: _LBS_EXTENDEDSEL | _LBS_NOTIFY | _LBS_NOINTEGRALHEIGHT | _WS_VSCROLL | controlstyle,
appendMsg: _LB_ADDSTRING,
insertBeforeMsg: _LB_INSERTSTRING,
deleteMsg: _LB_DELETESTRING,
selectedIndexMsg: _LB_GETCURSEL,
selectedIndexErr: negConst(_LB_ERR),
addSpaceErr: negConst(_LB_ERRSPACE),
lenMsg: _LB_GETCOUNT,
},
c_progressbar: &classData{
name: toUTF16(x_PROGRESS_CLASS),
// note that tab stops are disabled for progress bars
style: (_PBS_SMOOTH | controlstyle) &^ _WS_TABSTOP,
xstyle: 0 | controlxstyle,
doNotLoadFont: true,
},
c_area: &classData{
name: areaWndClass,
style: areastyle,
xstyle: areaxstyle,
storeSysData: true,
doNotLoadFont: true,
},
}
func (s *sysData) addChild(child *sysData) _HMENU {
s.childrenLock.Lock()
defer s.childrenLock.Unlock()
s.nextChildID++ // start at 1
if s.children == nil {
s.children = map[_HMENU]*sysData{}
}
s.children[s.nextChildID] = child
return s.nextChildID
}
func (s *sysData) delChild(id _HMENU) {
s.childrenLock.Lock()
defer s.childrenLock.Unlock()
delete(s.children, id)
}
var (
_blankString = toUTF16("")
blankString = utf16ToArg(_blankString)
)
func (s *sysData) make(window *sysData) (err error) {
ct := classTypes[s.ctype]
cid := _HMENU(0)
pwin := uintptr(_NULL)
if window != nil { // this is a child control
cid = window.addChild(s)
pwin = uintptr(window.hwnd)
}
style := uintptr(ct.style)
if s.alternate {
style = uintptr(ct.altStyle)
}
lpParam := uintptr(_NULL)
if ct.storeSysData {
lpParam = uintptr(unsafe.Pointer(s))
}
r1, _, err := _createWindowEx.Call(
uintptr(ct.xstyle),
utf16ToArg(ct.name),
blankString, // we set the window text later
style,
negConst(_CW_USEDEFAULT),
negConst(_CW_USEDEFAULT),
negConst(_CW_USEDEFAULT),
negConst(_CW_USEDEFAULT),
pwin,
uintptr(cid),
uintptr(hInstance),
lpParam)
if r1 == 0 { // failure
if window != nil {
window.delChild(cid)
}
panic(fmt.Errorf("error actually creating window/control: %v", err))
}
if !ct.storeSysData { // regular control; store s.hwnd ourselves
s.hwnd = _HWND(r1)
} else if s.hwnd != _HWND(r1) { // we store sysData in storeSysData(); sanity check
panic(fmt.Errorf("hwnd mismatch creating window/control: storeSysData() stored 0x%X but CreateWindowEx() returned 0x%X", s.hwnd, r1))
}
if !ct.doNotLoadFont {
_sendMessage.Call(
uintptr(s.hwnd),
uintptr(_WM_SETFONT),
uintptr(_WPARAM(controlFont)),
uintptr(_LPARAM(_TRUE)))
}
return nil
}
var (
_updateWindow = user32.NewProc("UpdateWindow")
)
// if the object is a window, we need to do the following the first time
// ShowWindow(hwnd, nCmdShow);
// UpdateWindow(hwnd);
func (s *sysData) firstShow() error {
_showWindow.Call(
uintptr(s.hwnd),
uintptr(nCmdShow))
r1, _, err := _updateWindow.Call(uintptr(s.hwnd))
if r1 == 0 { // failure
panic(fmt.Errorf("error updating window for the first time: %v", err))
}
return nil
}
func (s *sysData) show() {
_showWindow.Call(
uintptr(s.hwnd),
uintptr(_SW_SHOW))
}
func (s *sysData) hide() {
_showWindow.Call(
uintptr(s.hwnd),
uintptr(_SW_HIDE))
}
func (s *sysData) setText(text string) {
ptext := toUTF16(text)
r1, _, err := _setWindowText.Call(
uintptr(s.hwnd),
utf16ToArg(ptext))
if r1 == 0 { // failure
panic(fmt.Errorf("error setting window/control text: %v", err))
}
}
func (s *sysData) setRect(x int, y int, width int, height int, winheight int) error {
r1, _, err := _moveWindow.Call(
uintptr(s.hwnd),
uintptr(x),
uintptr(y),
uintptr(width),
uintptr(height),
uintptr(_TRUE))
if r1 == 0 { // failure
return fmt.Errorf("error setting window/control rect: %v", err)
}
return nil
}
func (s *sysData) isChecked() bool {
r1, _, _ := _sendMessage.Call(
uintptr(s.hwnd),
uintptr(_BM_GETCHECK),
uintptr(0),
uintptr(0))
return r1 == _BST_CHECKED
}
func (s *sysData) text() (str string) {
var tc []uint16
r1, _, _ := _sendMessage.Call(
uintptr(s.hwnd),
uintptr(_WM_GETTEXTLENGTH),
uintptr(0),
uintptr(0))
length := r1 + 1 // terminating null
tc = make([]uint16, length)
_sendMessage.Call(
uintptr(s.hwnd),
uintptr(_WM_GETTEXT),
uintptr(_WPARAM(length)),
uintptr(_LPARAM(unsafe.Pointer(&tc[0]))))
return syscall.UTF16ToString(tc)
}
func (s *sysData) append(what string) {
pwhat := toUTF16(what)
r1, _, err := _sendMessage.Call(
uintptr(s.hwnd),
uintptr(classTypes[s.ctype].appendMsg),
uintptr(_WPARAM(0)),
utf16ToLPARAM(pwhat))
if r1 == uintptr(classTypes[s.ctype].addSpaceErr) {
panic(fmt.Errorf("out of space adding item to combobox/listbox (last error: %v)", err))
} else if r1 == uintptr(classTypes[s.ctype].selectedIndexErr) {
panic(fmt.Errorf("failed to add item to combobox/listbox (last error: %v)", err))
}
}
func (s *sysData) insertBefore(what string, index int) {
pwhat := toUTF16(what)
r1, _, err := _sendMessage.Call(
uintptr(s.hwnd),
uintptr(classTypes[s.ctype].insertBeforeMsg),
uintptr(_WPARAM(index)),
utf16ToLPARAM(pwhat))
if r1 == uintptr(classTypes[s.ctype].addSpaceErr) {
panic(fmt.Errorf("out of space adding item to combobox/listbox (last error: %v)", err))
} else if r1 == uintptr(classTypes[s.ctype].selectedIndexErr) {
panic(fmt.Errorf("failed to add item to combobox/listbox (last error: %v)", err))
}
}
func (s *sysData) selectedIndex() int {
r1, _, _ := _sendMessage.Call(
uintptr(s.hwnd),
uintptr(classTypes[s.ctype].selectedIndexMsg),
uintptr(_WPARAM(0)),
uintptr(_LPARAM(0)))
if r1 == uintptr(classTypes[s.ctype].selectedIndexErr) { // no selection or manually entered text (apparently, for the latter)
return -1
}
return int(r1)
}
func (s *sysData) selectedIndices() []int {
if !s.alternate { // single-selection list box; use single-selection method
index := s.selectedIndex()
if index == -1 {
return nil
}
return []int{index}
}
r1, _, err := _sendMessage.Call(
uintptr(s.hwnd),
uintptr(_LB_GETSELCOUNT),
uintptr(0),
uintptr(0))
if r1 == negConst(_LB_ERR) {
panic(fmt.Errorf("error: LB_ERR from LB_GETSELCOUNT in what we know is a multi-selection listbox: %v", err))
}
if r1 == 0 { // nothing selected
return nil
}
indices := make([]int, r1)
r1, _, err = _sendMessage.Call(
uintptr(s.hwnd),
uintptr(_LB_GETSELITEMS),
uintptr(_WPARAM(r1)),
uintptr(_LPARAM(unsafe.Pointer(&indices[0]))))
if r1 == negConst(_LB_ERR) {
panic(fmt.Errorf("error: LB_ERR from LB_GETSELITEMS in what we know is a multi-selection listbox: %v", err))
}
return indices
}
func (s *sysData) selectedTexts() []string {
indices := s.selectedIndices()
strings := make([]string, len(indices))
for i, v := range indices {
r1, _, err := _sendMessage.Call(
uintptr(s.hwnd),
uintptr(_LB_GETTEXTLEN),
uintptr(_WPARAM(v)),
uintptr(0))
if r1 == negConst(_LB_ERR) {
panic(fmt.Errorf("error: LB_ERR from LB_GETTEXTLEN in what we know is a valid listbox index (came from LB_GETSELITEMS): %v", err))
}
str := make([]uint16, r1)
r1, _, err = _sendMessage.Call(
uintptr(s.hwnd),
uintptr(_LB_GETTEXT),
uintptr(_WPARAM(v)),
uintptr(_LPARAM(unsafe.Pointer(&str[0]))))
if r1 == negConst(_LB_ERR) {
panic(fmt.Errorf("error: LB_ERR from LB_GETTEXT in what we know is a valid listbox index (came from LB_GETSELITEMS): %v", err))
}
strings[i] = syscall.UTF16ToString(str)
}
return strings
}
func (s *sysData) setWindowSize(width int, height int) error {
var rect _RECT
r1, _, err := _getClientRect.Call(
uintptr(s.hwnd),
uintptr(unsafe.Pointer(&rect)))
if r1 == 0 {
panic(fmt.Errorf("error getting upper-left of window for resize: %v", err))
}
// TODO AdjustWindowRect() on the result
// 0 because (0,0) is top-left so no winheight
err = s.setRect(int(rect.left), int(rect.top), width, height, 0)
if err != nil {
panic(fmt.Errorf("error actually resizing window: %v", err))
}
return nil
}
func (s *sysData) delete(index int) {
r1, _, err := _sendMessage.Call(
uintptr(s.hwnd),
uintptr(classTypes[s.ctype].deleteMsg),
uintptr(_WPARAM(index)),
uintptr(0))
if r1 == uintptr(classTypes[s.ctype].selectedIndexErr) {
panic(fmt.Errorf("failed to delete item from combobox/listbox (last error: %v)", err))
}
}
func (s *sysData) setIndeterminate() {
r1, _, err := _setWindowLongPtr.Call(
uintptr(s.hwnd),
negConst(_GWL_STYLE),
uintptr(classTypes[s.ctype].style | _PBS_MARQUEE))
if r1 == 0 {
panic(fmt.Errorf("error setting progress bar style to enter indeterminate mode: %v", err))
}
_sendMessage.Call(
uintptr(s.hwnd),
uintptr(_PBM_SETMARQUEE),
uintptr(_WPARAM(_TRUE)),
uintptr(0))
s.isMarquee = true
}
func (s *sysData) setProgress(percent int) {
if percent == -1 {
s.setIndeterminate()
return
}
if s.isMarquee {
// turn off marquee before switching back
_sendMessage.Call(
uintptr(s.hwnd),
uintptr(_PBM_SETMARQUEE),
uintptr(_WPARAM(_FALSE)),
uintptr(0))
r1, _, err := _setWindowLongPtr.Call(
uintptr(s.hwnd),
negConst(_GWL_STYLE),
uintptr(classTypes[s.ctype].style))
if r1 == 0 {
panic(fmt.Errorf("error setting progress bar style to leave indeterminate mode (percent %d): %v", percent, err))
}
s.isMarquee = false
}
send := func(msg uintptr, n int, l _LPARAM) {
_sendMessage.Call(
uintptr(s.hwnd),
msg,
uintptr(_WPARAM(n)),
uintptr(l))
}
// Windows 7 has a non-disableable slowly-animating progress bar increment
// there isn't one for decrement, so we'll work around by going one higher and then lower again
// for the case where percent == 100, we need to increase the range temporarily
// sources: http://social.msdn.microsoft.com/Forums/en-US/61350dc7-6584-4c4e-91b0-69d642c03dae/progressbar-disable-smooth-animation http://stackoverflow.com/questions/2217688/windows-7-aero-theme-progress-bar-bug http://discuss.joelonsoftware.com/default.asp?dotnet.12.600456.2 http://stackoverflow.com/questions/22469876/progressbar-lag-when-setting-position-with-pbm-setpos http://stackoverflow.com/questions/6128287/tprogressbar-never-fills-up-all-the-way-seems-to-be-updating-too-fast
if percent == 100 {
send(_PBM_SETRANGE32, 0, 101)
}
send(_PBM_SETPOS, percent+1, 0)
send(_PBM_SETPOS, percent, 0)
if percent == 100 {
send(_PBM_SETRANGE32, 0, 100)
}
}
func (s *sysData) len() int {
r1, _, err := _sendMessage.Call(
uintptr(s.hwnd),
uintptr(classTypes[s.ctype].lenMsg),
uintptr(_WPARAM(0)),
uintptr(_LPARAM(0)))
if r1 == uintptr(classTypes[s.ctype].selectedIndexErr) {
panic(fmt.Errorf("unexpected error return from sysData.len(); GetLastError() says %v", err))
}
return int(r1)
}
func (s *sysData) setAreaSize(width int, height int) {
_sendMessage.Call(
uintptr(s.hwnd),
uintptr(msgSetAreaSize),
uintptr(width), // WPARAM is UINT_PTR on Windows XP and newer at least, so we're good with this
uintptr(height))
}
func (s *sysData) repaintAll() {
_sendMessage.Call(
uintptr(s.hwnd),
uintptr(msgRepaintAll),
uintptr(0),
uintptr(0))
}
func (s *sysData) center() {
var ws _RECT
r1, _, err := _getWindowRect.Call(
uintptr(s.hwnd),
uintptr(unsafe.Pointer(&ws)))
if r1 == 0 {
panic(fmt.Errorf("error getting window rect for sysData.center(): %v", err))
}
// TODO should this be using the monitor functions instead? http://blogs.msdn.com/b/oldnewthing/archive/2005/05/05/414910.aspx
// error returns from GetSystemMetrics() is meaningless because the return value, 0, is still valid
// TODO should this be using the client rect and not the window rect?
dw, _, _ := _getSystemMetrics.Call(uintptr(_SM_CXFULLSCREEN))
dh, _, _ := _getSystemMetrics.Call(uintptr(_SM_CYFULLSCREEN))
ww := ws.right - ws.left
wh := ws.bottom - ws.top
wx := (int32(dw) / 2) - (ww / 2)
wy := (int32(dh) / 2) - (wh / 2)
s.setRect(int(wx), int(wy), int(ww), int(wh), 0)
}
func (s *sysData) setChecked(checked bool) {
c := uintptr(_BST_CHECKED)
if !checked {
c = uintptr(_BST_UNCHECKED)
}
_sendMessage.Call(
uintptr(s.hwnd),
uintptr(_BM_SETCHECK),
c,
uintptr(0))
}

View File

@ -1,72 +0,0 @@
# andlabs/ui table view/tree view proposal
<tt><ins>(text that looks like this is optional)</ins></tt>
```go
type TableView struct {
Selected chan struct{}
Data interface{}
// contains hidden or unexported fields
}
```
A TableView is a Control that displays rows of structured data organized by columns.
Data is a slice of objects of a structure containing multiple fields. Each field represents a column in the table. Column names are, by default, the exported field name; the struct tag ui:"Custom name" can be used to specify a custom field name. For example:
```go
type Person struct {
Name string
Address Address
PhoneNumber PhoneNumber `ui:"Phone Number"`
}
```
Data is displayed using the fmt package's %v rule. The structure must satisfy sync.Locker.
<tt><ins>If one of the members is of type slice of the structure type, then any element of the main slice with a Children whose length is nonzero represents child nodes. For example:</ins></tt>
```go
type File struct {
Filename string
Size int64
Type FileType
Contents []File
}
```
<tt><ins>In this case, File.Contents specifies children of the parent File.</ins></tt>
```go
func NewTableView(initData interface{}) *TableView
```
Creates a new TableView with the specified initial data. This also determines the data type of the TableView; after this, all accesses to the data are made through the Data field of TableView. NewTableView() panics if initData is nil or not a slice of structures. The slice may be empty. (TODO slice of pointers to structures?) <tt><ins>NewTableView() also panics if the structure has more than one possible children field.</ins></tt>
```go
// if trees are not supported
func (t *TableView) Append(items ...interface{})
func (t *TableView) InsertBefore(index int, items ...interface{})
func (t *TableView) Delete(indices ...int)
// if trees are supported
func (t *TableView) Append(path []int, items ...interface{})
func (t *TableView) InsertBefore(path []int, items ...interface{})
func (t *TableView) Delete(path []int, indices ...int)
```
Standard methods to manipulate data in the TableView. These methods hold the write lock upon entry and release it upon exit. They panic if any index is invalid. <tt><ins>path specifies which node of the tree to append to. If path has length zero, the operation is performed on the top level; if path has length one, the operation is performed on the children of the first entry in the list; and so on and so forth. Each element of path is the index relative to the first item at the level (so []int{4, 2, 1} specifies the fifth entry's third child's second child's children).</ins></tt>
```go
func (t *TableView) Lock()
func (t *TableView) Unlock()
func (t *TableView) RLock()
func (t *TableView) RUnlock()
```
For more complex manipulations, TableView acts as a sync.RWMutex. Any goroutine holding the read lock may access t.Data, but cannot change it. Any goroutine holding the regular lock may modify t.Data. Before t.Unlock() returns, it automatically refreshes the TaleView with the new contents of Data.
```go
// if trees are not supported
func (t *TableView) Selection() []int
func (t *TableView) Select(indices ...int)
// if trees are supported
func (t *TableView) Selection() [][]int
func (t *TableView) Select(indices ...[]int)
// or should these be SelectedIndices() and SelectIndices() for consistency?
```
Methods that act on TableView row selection. These methods hold the read lock on entry and release it on exit. <tt><ins>Each entry in the returned slice consists of a path followed by the selected index of the child. A slice of length 1 indicates that a top-level entry has been selected. The slices shall not be of length zero; passing one in will panic. (TODO this means that multiple children node will have a copy of path each; that should be fixed...)</ins></tt>

12
test.sh
View File

@ -1,12 +0,0 @@
if [ ! -f tools/windowsconstgen.go ]; then
echo error: $0 must be run from the package source root 1>&2
exit 1
fi
set -e
if [ x$GOOS = xwindows ]; then
# have to invoke go run with the host $GOOS/$GOARCH
GOOS= GOARCH= go run tools/windowsconstgen.go . 386 "$@"
GOOS= GOARCH= go run tools/windowsconstgen.go . amd64 "$@"
fi
cd test
go build "$@"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,53 +0,0 @@
// 26 june 2014
package main
import (
"flag"
"image"
"image/color"
"image/draw"
. "github.com/andlabs/ui"
)
// spacing test
type solidColor struct {
c color.Color
}
func (s solidColor) Paint(r image.Rectangle) *image.RGBA {
i := image.NewRGBA(r)
draw.Draw(i, r, &image.Uniform{s.c}, image.ZP, draw.Src)
return i
}
func (s solidColor) Mouse(m MouseEvent) bool { return false }
func (s solidColor) Key(e KeyEvent) bool { return false }
var spacetest = flag.String("spacetest", "", "test space idempotency; arg is x or y; overrides -area")
func spaceTest() {
w := 100
h := 50
ng := 1
gsx, gsy := 1, 0
f := NewVerticalStack
if *spacetest == "x" {
w = 50
h = 100
ng = 2
gsx, gsy = 0, 1
f = NewHorizontalStack
}
ah := solidColor{color.NRGBA{0,0,255,255}}
a1 := NewArea(w, h, ah)
a2 := NewArea(w, h, ah)
a3 := NewArea(w, h, ah)
a4 := NewArea(w, h, ah)
win := NewWindow("Stack", 250, 250)
win.SetSpaced(true)
win.Open(f(a1, a2))
win = NewWindow("Grid", 250, 250)
win.SetSpaced(true)
g := NewGrid(ng, a3, a4)
g.SetFilling(0, 0)
g.SetStretchy(gsx, gsy)
win.Open(g)
}

47
todo.md
View File

@ -1,47 +0,0 @@
ALL:
- vertical alignment of labels still has some flaws
- gtk+: currently requires labels to be filling for this to work: grids don't do this by default, for instance
- won't cause any issues, just an inconvenience that should be addressed
- make sure tab stop behavior for Areas makes sense, or provide a handler function
- the following seems weird and will not allow clean removal of the last window; think of something better?
```
case ui.Closing:
*(d.(*bool)) = true
ui.Stop <- struct{}{}
```
- implement the concept of closing a window (which renders it unavilable for future use) and make it part of the Closing documentation
- make sure message boxes can be closed in arbitrary order meaningfully
- windows: the first message box closing will destroy the window properly and reenable the parent, but the actual call to MessageBox() won't return until the second dialog does
- docs don't say anything about threads but implies you can't call MessageBox() from a different thread
- GTK+: ???
- OS X: ???
- describe the thread safety of Window (needs to clarify construct, create, open, close, show, hide first)
MAC OS X:
- NSComboBox scans the entered text to see if it matches one of the items and returns the index of that item if it does; find out how to suppress this so that it returns -1 unless the item was chosen from the list (like the other platforms)
- asked: http://stackoverflow.com/questions/23046414/cocoa-how-do-i-get-nscombobox-indexofselecteditem-to-return-1-if-the-user-m
- make sure Areas get keyboard focus when clicking outside the actual Area space on Mac OS X
- http://stackoverflow.com/questions/24102367/how-do-i-make-it-so-clicking-outside-the-actual-nsview-in-a-nsscrollview-but-wit
- on initially starting the Area test, layout is totally wrong
- probably use fittingSize instead of sizeToFit
- use cascadeTopLeftFromPoint: for NSWindow or a similar routine
WINDOWS:
- windows: windows key handling is just wrong; figure out how to avoid (especially since Windows intercepts that key by default)
- control sizing is a MAJOR pain
- http://stackoverflow.com/questions/24130548/is-there-a-proper-way-to-get-the-preferred-size-of-windows-controls-there-are-s
- redrawing controls after a window resize on Windows does not work properly
- when adding IsDialogMessage() find out if that makes the area in the area bounds test automatically focused
- vertical alignment of labels can be wrong (I think; need to check again - TODO)
UNIX:
- figure out how to detect the alt key and mouse buttons above 5 properly for modifiers/Held[]
- http://stackoverflow.com/questions/24053012/gdk-is-there-a-way-i-can-get-if-an-arbitrary-keyval-or-mouse-button-is-down-dur
- double-check to make sure MouseEvent.Held[] is sorted on Unix after we figure out how to detect buttons above button 5
- sizing with client-side decorations (Wayland) don't work
- several people suggested connecting to size-allocate of the GtkLayout, but then I can wind up in a situation where there's extra padding or border space in the direction I resized
- [12:55] <myklgo> pietro10: I meant to mention: 1073): Gtk-WARNING **: Theme parsing error: gtk.css:72:20: Not using units is deprecated. Assuming 'px'. twice.
- figure out why Page Up/Page Down does tab stops
ALL PLATFORMS:
- windows code presently wraps entire function bodies in uitask; make sure the other platforms do too

View File

@ -1,233 +0,0 @@
// 24 may 2014
package main
import (
"fmt"
"os"
"strings"
"go/token"
"go/ast"
"go/parser"
"sort"
"io/ioutil"
"path/filepath"
"os/exec"
)
func getPackage(path string) (pkg *ast.Package) {
fileset := token.NewFileSet() // parser.ParseDir() actually writes to this; not sure why it doesn't return one instead
filter := func(i os.FileInfo) bool {
return strings.HasSuffix(i.Name(), "_windows.go")
}
pkgs, err := parser.ParseDir(fileset, path, filter, parser.AllErrors)
if err != nil {
panic(err)
}
if len(pkgs) != 1 {
panic("more than one package found")
}
for k, _ := range pkgs { // get the sole key
pkg = pkgs[k]
}
return pkg
}
type walker struct {
desired func(string) bool
}
var known = map[string]string{}
var unknown = map[string]struct{}{}
func (w *walker) Visit(node ast.Node) ast.Visitor {
if n, ok := node.(*ast.Ident); ok {
if w.desired(n.Name) {
if n.Obj != nil {
delete(unknown, n.Name)
kind := n.Obj.Kind.String()
if known[n.Name] != "" && known[n.Name] != kind {
panic(n.Name + "(" + kind + ") already known to be a " + known[n.Name])
}
known[n.Name] = kind
} else if _, ok := known[n.Name]; !ok { // only if not known
unknown[n.Name] = struct{}{}
}
}
}
return w
}
func gatherNames(pkg *ast.Package) {
desired := func(name string) bool {
if strings.HasPrefix(name, "_") && len(name) > 1 {
return !strings.ContainsAny(name,
"abcdefghijklmnopqrstuvwxyz")
}
return false
}
for _, f := range pkg.Files {
for _, d := range f.Decls {
ast.Walk(&walker{desired}, d)
}
}
}
// some constants confuse cgo into thinking they're external symbols for some reason
// fortunately all these constants are pointers
// TODO debug cgo
var hacknames = map[string]string{
"_INVALID_HANDLE_VALUE": "x_INVALID_HANDLE_VALUE",
"_NULL": "x_NULL",
"_IDI_APPLICATION": "x_IDI_APPLICATION",
"_IDC_ARROW": "x_IDC_ARROW",
"_HWND_MESSAGE": "x_HWND_MESSAGE",
}
func hacknamesPreamble() string {
if len(hacknames) == 0 {
return ""
}
// keep sorted for git
hn := make([]string, 0, len(hacknames))
for origname, _ := range hacknames {
hn = append(hn, origname)
}
sort.Strings(hn)
s := "// /* because cgo has issues with these */\n"
s += "// #include <stdint.h>\n"
for _, origname := range hn {
s += "// uintptr_t " + hacknames[origname] + " = (uintptr_t) (" +
origname[1:] + ");\n" // strip leading _
}
return s
}
func preamble(pkg string) string {
return "// autogenerated by windowsconstgen; do not edit\n" +
"package " + pkg + "\n\n" // two newlines to please go fmt
}
// for backwards compatibiilty reasons, Windows defines GetWindowLongPtr()/SetWindowLongPtr() as a macro which expands to GetWindowLong()/SetWindowLong() on 32-bit systems
// we'll just simulate that here
var gwlpNames = map[string]string{
"386": "etWindowLongW",
"amd64": "etWindowLongPtrW",
}
func printConst(f *os.File, goconst string, winconst string) {
fmt.Fprintf(f, " fmt.Println(\"const %s =\", C.%s)\n", goconst, winconst)
}
func printBlankLine(f *os.File) {
fmt.Fprintf(f, " fmt.Println()\n")
}
func printGWLPName(f *os.File, which string, char string, targetarch string) {
fmt.Fprintf(f, " fmt.Println(\"var %s = user32.NewProc(\\\"%s\\\")\")\n",
which, char + gwlpNames[targetarch])
}
func main() {
if len(os.Args) < 3 {
panic("usage: " + os.Args[0] + " path goarch [go-command-options...]")
}
pkgpath := os.Args[1]
targetarch := os.Args[2]
if _, ok := gwlpNames[targetarch]; !ok {
panic("unknown target windows/" + targetarch)
}
goopts := os.Args[3:] // valid if len(os.Args) == 3; in that case this will just be a slice of length zero
pkg := getPackage(pkgpath)
gatherNames(pkg)
// if we still have some known, I didn't clean things up completely
knowns := ""
for ident, kind := range known {
if kind != "var" && kind != "const" {
continue
}
knowns += "\n" + ident + " (" + kind + ")"
}
if knowns != "" {
panic("error: the following are still known!" + knowns) // has a newline already
}
// keep sorted for git
consts := make([]string, 0, len(unknown))
for ident, _ := range unknown {
if hackname, ok := hacknames[ident]; ok {
consts = append(consts, hackname)
continue
}
consts = append(consts, ident)
}
sort.Strings(consts)
// thanks to james4k in irc.freenode.net/#go-nuts
tmpdir, err := ioutil.TempDir("", "windowsconstgen")
if err != nil {
panic(err)
}
genoutname := filepath.Join(tmpdir, "gen.go")
f, err := os.Create(genoutname)
if err != nil {
panic(err)
}
fmt.Fprintf(f, "%s" +
"import \"fmt\"\n" +
"// #include <windows.h>\n" +
"// #include <commctrl.h>\n" +
"%s" +
"import \"C\"\n" +
"func main() {\n" +
" fmt.Print(%q)\n",
preamble("main"), hacknamesPreamble(), preamble("ui"))
for _, ident := range consts {
if ident[0] == 'x' {
// hack name; strip the leading x (but not the _ after it) from the constant name but keep the value name unchanged
printConst(f, ident[1:], ident)
continue
}
// not a hack name; strip the leading _ from the value name but keep the constant name unchanged
printConst(f, ident, ident[1:])
}
printBlankLine(f) // to please go fmt
// and now for _getWindowLongPtr/_setWindowLongPtr
printGWLPName(f, "_getWindowLongPtr", "G", targetarch)
printGWLPName(f, "_setWindowLongPtr", "S", targetarch)
fmt.Fprintf(f, "}\n")
f.Close()
cmd := exec.Command("go", "run")
cmd.Args = append(cmd.Args, goopts...) // valid if len(goopts) == 0; in that case this will just be a no-op
cmd.Args = append(cmd.Args, genoutname)
f, err = os.Create(filepath.Join(pkgpath, "zconstants_windows_" + targetarch + ".go"))
if err != nil {
panic(err)
}
defer f.Close()
cmd.Stdout = f
cmd.Stderr = os.Stderr
// we need to preserve the environment EXCEPT FOR the variables we're overriding
// thanks to raggi and smw in irc.freenode.net/#go-nuts
for _, ev := range os.Environ() {
if strings.HasPrefix(ev, "GOOS=") ||
strings.HasPrefix(ev, "GOARCH=") ||
strings.HasPrefix(ev, "CGO_ENABLED=") {
continue
}
cmd.Env = append(cmd.Env, ev)
}
cmd.Env = append(cmd.Env,
"GOOS=windows",
"GOARCH=" + targetarch,
"CGO_ENABLED=1") // needed as it's not set by default in cross-compiles
err = cmd.Run()
if err != nil {
// TODO find a way to get the exit code
os.Exit(1)
}
// TODO remove the temporary directory
}

View File

@ -1,41 +0,0 @@
// 11 february 2014
package ui
import (
"runtime"
)
// Go sets up the UI environment.
// If initialization fails, Go returns an error and Ready is not pulsed.
// Otherwise, Go first runs start(), which should contain code to create the first Window, and then fires up the event loop, not returning to its caller until Stop is pulsed, at which point Go() will return nil.
// After Go() returns, you cannot call future ui functions/methods meaningfully.
// Pulsing Stop will cause Go() to return immediately; the programmer is responsible for cleaning up (for instance, hiding open Windows) beforehand.
//
// It is not safe to call ui.Go() in a goroutine. It must be called directly from main(). This means if your code calls other code-modal servers (such as http.ListenAndServe()), they must be run from goroutines. (This is due to limitations in various OSs, such as Mac OS X.)
//
// Go() does not process the command line for flags (that is, it does not call flag.Parse()), nor does package ui add any of the underlying toolkit's supported command-line flags.
// If you must, and if the toolkit also has environment variable equivalents to these flags (for instance, GTK+), use those instead.
func Go(start func()) error {
runtime.LockOSThread()
if err := uiinit(); err != nil {
return err
}
start()
ui()
return nil
}
// Post issues a request to the given Window to do something on the main thread.
// Note the name of the function: there is no guarantee that the request will be handled immediately.
// Because this can be safely called from any goroutine, it is a package-level function, and not a method on Window.
// TODO garbage collection
func Post(w *Window, data interface{}) {
uipost(w, data)
}
// TODO this needs to be replaced with a function
// Stop should be pulsed when you are ready for Go() to return.
// Pulsing Stop will cause Go() to return immediately; the programmer is responsible for cleaning up (for instance, hiding open Windows) beforehand.
// Do not pulse Stop more than once.
var Stop = make(chan struct{})

View File

@ -1,64 +0,0 @@
// 28 february 2014
package ui
import (
"fmt"
"unsafe"
)
// #cgo CFLAGS: -mmacosx-version-min=10.7 -DMACOSX_DEPLOYMENT_TARGET=10.7
// #cgo LDFLAGS: -mmacosx-version-min=10.7 -lobjc -framework Foundation -framework AppKit
// /* application compatibilty stuff via https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/cross_development/Configuring/configuring.html, http://www.cocoawithlove.com/2009/09/building-for-earlier-os-versions-in.html, http://opensource.apple.com/source/xnu/xnu-2422.1.72/EXTERNAL_HEADERS/AvailabilityMacros.h (via http://stackoverflow.com/questions/20485797/what-macro-to-use-to-identify-mavericks-osx-10-9-in-c-c-code), and Beelsebob and LookyLuke_ICBM on irc.freenode.net/#macdev */
// #include "objc_darwin.h"
import "C"
func uiinit() error {
err := initCocoa()
if err != nil {
return err
}
return nil
}
func ui() {
// Cocoa must run on the first thread created by the program, so we run our dispatcher on another thread instead
go func() {
<-Stop
// TODO is this function thread-safe?
C.breakMainLoop()
}()
C.cocoaMainLoop()
}
// we're going to call the appDelegate selector with waitUntilDone:YES so we don't have to worry about garbage collection
// we DO need to collect the two pointers together, though
type uipostmsg struct {
w *Window
data interface{}
}
//export appDelegate_uipost
func appDelegate_uipost(xmsg unsafe.Pointer) {
msg := (*uipostmsg)(xmsg)
msg.w.sysData.post(msg.data)
}
func uipost(w *Window, data interface{}) {
msg := &uipostmsg{
w: w,
data: data,
}
C.uipost(appDelegate, unsafe.Pointer(msg))
}
func initCocoa() (err error) {
makeAppDelegate()
if C.initCocoa(appDelegate) != C.YES {
return fmt.Errorf("error setting NSApplication activation policy (basically identifies our program as a separate program; needed for several things, such as Dock icon, application menu, window resizing, etc.) (unknown reason)")
}
return nil
}

View File

@ -1,76 +0,0 @@
// +build !windows,!darwin,!plan9
// 16 february 2014
package ui
import (
"fmt"
"unsafe"
)
// #cgo pkg-config: gtk+-3.0
// #include "gtk_unix.h"
// /* this is called when we're done */
// static inline gboolean our_quit_callback(gpointer data)
// {
// gtk_main_quit();
// return FALSE; /* remove from idle handler queue (not like it matters) */
// }
// /* I would call gdk_threads_add_idle() directly from ui() but cgo whines, so; trying to access our_quit_callback() in any way other than a call would cause _cgo_main.c to complain too */
// static inline void signalQuit(void)
// {
// gdk_threads_add_idle(our_quit_callback, NULL);
// }
// extern gboolean our_post_callback(gpointer);
import "C"
func uiinit() error {
err := gtk_init()
if err != nil {
return fmt.Errorf("gtk_init() failed: %v", err)
}
return nil
}
func ui() {
go func() {
<-Stop
C.signalQuit()
// TODO wait for it to return?
}()
C.gtk_main()
}
// we DO need to worry about keeping data alive here
// so we do the posting in a new goroutine that waits instead
type uipostmsg struct {
w *Window
data interface{}
done chan struct{}
}
//export our_post_callback
func our_post_callback(xmsg C.gpointer) C.gboolean {
msg := (*uipostmsg)(unsafe.Pointer(xmsg))
msg.w.sysData.post(msg.data)
msg.done <- struct{}{}
return C.FALSE // remove from idle handler queue
}
func uipost(w *Window, data interface{}) {
go func() {
msg := &uipostmsg{
w: w,
data: data,
done: make(chan struct{}),
}
C.gdk_threads_add_idle(C.GSourceFunc(C.our_post_callback),
C.gpointer(unsafe.Pointer(msg)))
<-msg.done
close(msg.done)
}()
}

View File

@ -1,173 +0,0 @@
// 11 february 2014
package ui
import (
"fmt"
"syscall"
"unsafe"
)
/*
TODO rewrite this comment block
problem: messages have to be dispatched on the same thread as system calls, and we can't mux GetMessage() with select, and PeekMessage() every iteration is wasteful (and leads to lag for me (only) with the concurrent garbage collector sweep)
possible: solution: use PostThreadMessage() to send uimsgs out to the message loop, which runs on its own goroutine
(I had come up with this first but wanted to try other things before doing it (and wasn't really sure if user-defined messages were safe, not quite understanding the system); nsf came up with it independently and explained that this was really the only right way to do it, so thanks to him)
problem: if the thread isn't in its main message pump, the thread message is simply lost (see, for example, http://blogs.msdn.com/b/oldnewthing/archive/2005/04/26/412116.aspx)
this happened when scrolling Areas (as scrolling is modal; see http://blogs.msdn.com/b/oldnewthing/archive/2005/04/27/412565.aspx)
the only recourse, and the one both Microsoft (http://support.microsoft.com/kb/183116) and Raymond Chen (http://blogs.msdn.com/b/oldnewthing/archive/2008/12/23/9248851.aspx) suggest (and Treeki/Ninjifox confirmed), is to create an invisible window to dispatch messages instead.
yay.
*/
var msghwnd _HWND
const (
msgQuit = _WM_APP + iota + 1 // + 1 just to be safe
msgSetAreaSize
msgRepaintAll
msgPost
)
func uiinit() error {
err := doWindowsInit()
if err != nil {
return fmt.Errorf("error doing general Windows initialization: %v", err)
}
msghwnd, err = makeMessageHandler()
if err != nil {
return fmt.Errorf("error making invisible window for handling events: %v", err)
}
return nil
}
var (
_postMessage = user32.NewProc("PostMessageW")
)
func ui() {
go func() {
<-Stop
// PostMessage() so it gets handled after any events currently being processed complete
r1, _, err := _postMessage.Call(
uintptr(msghwnd),
msgQuit,
uintptr(0),
uintptr(0))
if r1 == 0 { // failure
panic("error sending quit message to message loop: " + err.Error())
}
}()
msgloop()
}
// we'll use SendMessage() here, which will do a thread switch, call the function immediately, and wait for it to return, so we don't have to worry about the garbage collector collecting data
func uipost(w *Window, data interface{}) {
_sendMessage.Call(
uintptr(w.sysData.hwnd), // note: we pass this directly to the window
msgPost,
0,
uintptr(unsafe.Pointer(&data)))
}
var (
_dispatchMessage = user32.NewProc("DispatchMessageW")
_getActiveWindow = user32.NewProc("GetActiveWindow")
_getMessage = user32.NewProc("GetMessageW")
_isDialogMessage = user32.NewProc("IsDialogMessageW")
_postQuitMessage = user32.NewProc("PostQuitMessage")
_sendMessage = user32.NewProc("SendMessageW")
_translateMessage = user32.NewProc("TranslateMessage")
)
func msgloop() {
var msg struct {
hwnd _HWND
message uint32
wParam _WPARAM
lParam _LPARAM
time uint32
pt _POINT
}
for {
r1, _, err := _getMessage.Call(
uintptr(unsafe.Pointer(&msg)),
uintptr(_NULL),
uintptr(0),
uintptr(0))
if r1 == negConst(-1) { // error
panic("error getting message in message loop: " + err.Error())
}
if r1 == 0 { // WM_QUIT message
return
}
// this next bit handles tab stops
r1, _, _ = _getActiveWindow.Call()
r1, _, _ = _isDialogMessage.Call(
r1, // active window
uintptr(unsafe.Pointer(&msg)))
if r1 != 0 {
continue
}
_translateMessage.Call(uintptr(unsafe.Pointer(&msg)))
_dispatchMessage.Call(uintptr(unsafe.Pointer(&msg)))
}
}
var (
msghandlerclass = toUTF16("gomsghandler")
msghandlertitle = toUTF16("ui package message window")
)
func makeMessageHandler() (hwnd _HWND, err error) {
wc := &_WNDCLASS{
lpszClassName: utf16ToArg(msghandlerclass),
lpfnWndProc: syscall.NewCallback(messageHandlerWndProc),
hInstance: hInstance,
hIcon: icon,
hCursor: cursor,
hbrBackground: _HBRUSH(_COLOR_BTNFACE + 1),
}
r1, _, err := _registerClass.Call(uintptr(unsafe.Pointer(wc)))
if r1 == 0 { // failure
return _HWND(_NULL), fmt.Errorf("error registering the class of the invisible window for handling events: %v", err)
}
r1, _, err = _createWindowEx.Call(
uintptr(0),
utf16ToArg(msghandlerclass),
utf16ToArg(msghandlertitle),
uintptr(0),
negConst(_CW_USEDEFAULT),
negConst(_CW_USEDEFAULT),
negConst(_CW_USEDEFAULT),
negConst(_CW_USEDEFAULT),
// don't negConst() HWND_MESSAGE; windowsconstgen was given a pointer by windows.h, and pointers are unsigned, so converting it back to signed doesn't work
uintptr(_HWND_MESSAGE),
uintptr(_NULL),
uintptr(hInstance),
uintptr(_NULL))
if r1 == 0 { // failure
return _HWND(_NULL), fmt.Errorf("error actually creating invisible window for handling events: %v", err)
}
return _HWND(r1), nil
}
func messageHandlerWndProc(hwnd _HWND, uMsg uint32, wParam _WPARAM, lParam _LPARAM) _LRESULT {
switch uMsg {
case msgQuit:
// does not return a value according to MSDN
_postQuitMessage.Call(0)
return 0
}
return defWindowProc(hwnd, uMsg, wParam, lParam)
}

144
window.go
View File

@ -1,144 +0,0 @@
// 11 february 2014
package ui
import (
"fmt"
)
// Window represents an on-screen window.
type Window struct {
// Closing is called when the Close button is pressed by the user, or when the application needs to be quit (should the underlying system provide a concept of application distinct from window).
// Return true to allow the window to be closed; false otherwise.
// You cannot change this field after the Window has been created.
// [TODO close vs. hide]
// If Closing is nil, a default which rejects the close will be used.
Closing func() bool
// Posted is called when Post() is called with the given Window as an argument.
// It receives the data passed to Post() as an argument.
// If Posted is nil, a default handler which does nothing will be used.
Posted func(data interface{})
created bool
sysData *sysData
initTitle string
initWidth int
initHeight int
shownOnce bool
spaced bool
}
// NewWindow allocates a new Window with the given title and size. The window is not created until a call to Create() or Open().
func NewWindow(title string, width int, height int) *Window {
return &Window{
sysData: mksysdata(c_window),
initTitle: title,
initWidth: width,
initHeight: height,
}
}
// SetTitle sets the window's title.
func (w *Window) SetTitle(title string) {
if w.created {
w.sysData.setText(title)
return
}
w.initTitle = title
}
// SetSize sets the window's size.
func (w *Window) SetSize(width int, height int) (err error) {
if w.created {
err := w.sysData.setWindowSize(width, height)
if err != nil {
return fmt.Errorf("error setting window size: %v", err)
}
return nil
}
w.initWidth = width
w.initHeight = height
return nil
}
// SetSpaced sets whether the Window's child control takes padding and spacing into account.
// That is, with w.SetSpaced(true), w's child will have a margin around the window frame and will have sub-controls separated by an implementation-defined amount.
// Currently, only Stack and Grid explicitly understand this property.
// This property is visible recursively throughout the widget hierarchy of the Window.
// This property cannot be set after the Window has been created.
func (w *Window) SetSpaced(spaced bool) {
if w.created {
panic(fmt.Errorf("Window.SetSpaced() called after window created"))
}
w.spaced = spaced
}
// Open creates the Window with Create and then shows the Window with Show. As with Create, you cannot call Open more than once per window.
func (w *Window) Open(control Control) {
w.Create(control)
w.Show()
}
// Create creates the Window, setting its control to the given control. It does not show the window. This can only be called once per window, and finalizes all initialization of the control.
func (w *Window) Create(control Control) {
if w.created {
panic("window already open")
}
w.sysData.spaced = w.spaced
w.sysData.close = w.Closing
if w.sysData.close == nil {
w.sysData.close = func() bool {
return false
}
}
w.sysData.post = w.Posted
if w.sysData.post == nil {
w.sysData.post = func(data interface{}) {}
}
err := w.sysData.make(nil)
if err != nil {
panic(fmt.Errorf("error opening window: %v", err))
}
if control != nil {
w.sysData.allocate = control.allocate
err = control.make(w.sysData)
if err != nil {
panic(fmt.Errorf("error adding window's control: %v", err))
}
}
err = w.sysData.setWindowSize(w.initWidth, w.initHeight)
if err != nil {
panic(fmt.Errorf("error setting window size (in Window.Open()): %v", err))
}
w.sysData.setText(w.initTitle)
w.created = true
}
// Show shows the window.
func (w *Window) Show() {
if !w.shownOnce {
w.shownOnce = true
err := w.sysData.firstShow()
if err != nil {
panic(fmt.Errorf("error showing window for the first time: %v", err))
}
return
}
w.sysData.show()
}
// Hide hides the window.
func (w *Window) Hide() {
w.sysData.hide()
}
// Center centers the Window on-screen.
// The concept of "screen" in the case of a multi-monitor setup is implementation-defined.
// It presently panics if the Window has not been created.
func (w *Window) Center() {
if !w.created {
panic("attempt to center Window before it has been created")
}
w.sysData.center()
}

View File

@ -1,181 +0,0 @@
// autogenerated by windowsconstgen; do not edit
package ui
const _ACTCTX_FLAG_SET_PROCESS_DEFAULT = 16
const _AC_SRC_ALPHA = 1
const _AC_SRC_OVER = 0
const _BCM_GETIDEALSIZE = 5633
const _BI_RGB = 0
const _BM_GETCHECK = 240
const _BM_SETCHECK = 241
const _BN_CLICKED = 0
const _BST_CHECKED = 1
const _BST_UNCHECKED = 0
const _BS_CHECKBOX = 2
const _BS_PUSHBUTTON = 0
const _CBS_AUTOHSCROLL = 64
const _CBS_DROPDOWN = 2
const _CBS_DROPDOWNLIST = 3
const _CB_ADDSTRING = 323
const _CB_DELETESTRING = 324
const _CB_ERR = -1
const _CB_ERRSPACE = -2
const _CB_GETCOUNT = 326
const _CB_GETCURSEL = 327
const _CB_INSERTSTRING = 330
const _COLOR_BTNFACE = 15
const _CS_HREDRAW = 2
const _CS_VREDRAW = 1
const _CW_USEDEFAULT = -2147483648
const _DIB_RGB_COLORS = 0
const _ERROR = 0
const _ES_AUTOHSCROLL = 128
const _ES_PASSWORD = 32
const _FALSE = 0
const _GWLP_USERDATA = -21
const _GWL_STYLE = -16
const _ICC_PROGRESS_CLASS = 32
const _IDOK = 1
const _LBS_EXTENDEDSEL = 2048
const _LBS_NOINTEGRALHEIGHT = 256
const _LBS_NOTIFY = 1
const _LB_ADDSTRING = 384
const _LB_DELETESTRING = 386
const _LB_ERR = -1
const _LB_ERRSPACE = -2
const _LB_GETCOUNT = 395
const _LB_GETCURSEL = 392
const _LB_GETSELCOUNT = 400
const _LB_GETSELITEMS = 401
const _LB_GETTEXT = 393
const _LB_GETTEXTLEN = 394
const _LB_INSERTSTRING = 385
const _LF_FACESIZE = 32
const _MA_ACTIVATE = 1
const _MB_APPLMODAL = 0
const _MB_ICONERROR = 16
const _MB_OK = 0
const _MB_TASKMODAL = 8192
const _MK_LBUTTON = 1
const _MK_MBUTTON = 16
const _MK_RBUTTON = 2
const _MK_XBUTTON1 = 32
const _MK_XBUTTON2 = 64
const _PBM_SETMARQUEE = 1034
const _PBM_SETPOS = 1026
const _PBM_SETRANGE32 = 1030
const _PBS_MARQUEE = 8
const _PBS_SMOOTH = 1
const _SB_HORZ = 0
const _SB_LEFT = 6
const _SB_LINELEFT = 0
const _SB_LINERIGHT = 1
const _SB_PAGELEFT = 2
const _SB_PAGERIGHT = 3
const _SB_RIGHT = 7
const _SB_THUMBPOSITION = 4
const _SB_THUMBTRACK = 5
const _SB_VERT = 1
const _SIF_PAGE = 2
const _SIF_POS = 4
const _SIF_RANGE = 1
const _SIF_TRACKPOS = 16
const _SM_CXDOUBLECLK = 36
const _SM_CXFULLSCREEN = 16
const _SM_CYDOUBLECLK = 37
const _SM_CYFULLSCREEN = 17
const _SPI_GETNONCLIENTMETRICS = 41
const _SRCCOPY = 13369376
const _SS_LEFTNOWORDWRAP = 12
const _SS_NOPREFIX = 128
const _STARTF_USESHOWWINDOW = 1
const _SW_ERASE = 4
const _SW_HIDE = 0
const _SW_INVALIDATE = 2
const _SW_SHOW = 5
const _SW_SHOWDEFAULT = 10
const _TRUE = 1
const _VK_ADD = 107
const _VK_CLEAR = 12
const _VK_CONTROL = 17
const _VK_DELETE = 46
const _VK_DIVIDE = 111
const _VK_DOWN = 40
const _VK_END = 35
const _VK_ESCAPE = 27
const _VK_F1 = 112
const _VK_F10 = 121
const _VK_F11 = 122
const _VK_F12 = 123
const _VK_F2 = 113
const _VK_F3 = 114
const _VK_F4 = 115
const _VK_F5 = 116
const _VK_F6 = 117
const _VK_F7 = 118
const _VK_F8 = 119
const _VK_F9 = 120
const _VK_HOME = 36
const _VK_INSERT = 45
const _VK_LCONTROL = 162
const _VK_LEFT = 37
const _VK_LMENU = 164
const _VK_LSHIFT = 160
const _VK_LWIN = 91
const _VK_MENU = 18
const _VK_MULTIPLY = 106
const _VK_NEXT = 34
const _VK_PRIOR = 33
const _VK_RCONTROL = 163
const _VK_RETURN = 13
const _VK_RIGHT = 39
const _VK_RMENU = 165
const _VK_RSHIFT = 161
const _VK_RWIN = 92
const _VK_SHIFT = 16
const _VK_SUBTRACT = 109
const _VK_UP = 38
const _WA_INACTIVE = 0
const _WM_ACTIVATE = 6
const _WM_APP = 32768
const _WM_CLOSE = 16
const _WM_COMMAND = 273
const _WM_ERASEBKGND = 20
const _WM_GETMINMAXINFO = 36
const _WM_GETTEXT = 13
const _WM_GETTEXTLENGTH = 14
const _WM_HSCROLL = 276
const _WM_KEYDOWN = 256
const _WM_KEYUP = 257
const _WM_LBUTTONDOWN = 513
const _WM_LBUTTONUP = 514
const _WM_MBUTTONDOWN = 519
const _WM_MBUTTONUP = 520
const _WM_MOUSEACTIVATE = 33
const _WM_MOUSEMOVE = 512
const _WM_NCCREATE = 129
const _WM_PAINT = 15
const _WM_RBUTTONDOWN = 516
const _WM_RBUTTONUP = 517
const _WM_SETFONT = 48
const _WM_SIZE = 5
const _WM_SYSKEYDOWN = 260
const _WM_SYSKEYUP = 261
const _WM_VSCROLL = 277
const _WM_XBUTTONDOWN = 523
const _WM_XBUTTONUP = 524
const _WS_CHILD = 1073741824
const _WS_EX_CLIENTEDGE = 512
const _WS_HSCROLL = 1048576
const _WS_OVERLAPPEDWINDOW = 13565952
const _WS_TABSTOP = 65536
const _WS_VISIBLE = 268435456
const _WS_VSCROLL = 2097152
const _HWND_MESSAGE = 4294967293
const _IDC_ARROW = 32512
const _IDI_APPLICATION = 32512
const _INVALID_HANDLE_VALUE = 4294967295
const _NULL = 0
var _getWindowLongPtr = user32.NewProc("GetWindowLongW")
var _setWindowLongPtr = user32.NewProc("SetWindowLongW")

View File

@ -1,181 +0,0 @@
// autogenerated by windowsconstgen; do not edit
package ui
const _ACTCTX_FLAG_SET_PROCESS_DEFAULT = 16
const _AC_SRC_ALPHA = 1
const _AC_SRC_OVER = 0
const _BCM_GETIDEALSIZE = 5633
const _BI_RGB = 0
const _BM_GETCHECK = 240
const _BM_SETCHECK = 241
const _BN_CLICKED = 0
const _BST_CHECKED = 1
const _BST_UNCHECKED = 0
const _BS_CHECKBOX = 2
const _BS_PUSHBUTTON = 0
const _CBS_AUTOHSCROLL = 64
const _CBS_DROPDOWN = 2
const _CBS_DROPDOWNLIST = 3
const _CB_ADDSTRING = 323
const _CB_DELETESTRING = 324
const _CB_ERR = -1
const _CB_ERRSPACE = -2
const _CB_GETCOUNT = 326
const _CB_GETCURSEL = 327
const _CB_INSERTSTRING = 330
const _COLOR_BTNFACE = 15
const _CS_HREDRAW = 2
const _CS_VREDRAW = 1
const _CW_USEDEFAULT = -2147483648
const _DIB_RGB_COLORS = 0
const _ERROR = 0
const _ES_AUTOHSCROLL = 128
const _ES_PASSWORD = 32
const _FALSE = 0
const _GWLP_USERDATA = -21
const _GWL_STYLE = -16
const _ICC_PROGRESS_CLASS = 32
const _IDOK = 1
const _LBS_EXTENDEDSEL = 2048
const _LBS_NOINTEGRALHEIGHT = 256
const _LBS_NOTIFY = 1
const _LB_ADDSTRING = 384
const _LB_DELETESTRING = 386
const _LB_ERR = -1
const _LB_ERRSPACE = -2
const _LB_GETCOUNT = 395
const _LB_GETCURSEL = 392
const _LB_GETSELCOUNT = 400
const _LB_GETSELITEMS = 401
const _LB_GETTEXT = 393
const _LB_GETTEXTLEN = 394
const _LB_INSERTSTRING = 385
const _LF_FACESIZE = 32
const _MA_ACTIVATE = 1
const _MB_APPLMODAL = 0
const _MB_ICONERROR = 16
const _MB_OK = 0
const _MB_TASKMODAL = 8192
const _MK_LBUTTON = 1
const _MK_MBUTTON = 16
const _MK_RBUTTON = 2
const _MK_XBUTTON1 = 32
const _MK_XBUTTON2 = 64
const _PBM_SETMARQUEE = 1034
const _PBM_SETPOS = 1026
const _PBM_SETRANGE32 = 1030
const _PBS_MARQUEE = 8
const _PBS_SMOOTH = 1
const _SB_HORZ = 0
const _SB_LEFT = 6
const _SB_LINELEFT = 0
const _SB_LINERIGHT = 1
const _SB_PAGELEFT = 2
const _SB_PAGERIGHT = 3
const _SB_RIGHT = 7
const _SB_THUMBPOSITION = 4
const _SB_THUMBTRACK = 5
const _SB_VERT = 1
const _SIF_PAGE = 2
const _SIF_POS = 4
const _SIF_RANGE = 1
const _SIF_TRACKPOS = 16
const _SM_CXDOUBLECLK = 36
const _SM_CXFULLSCREEN = 16
const _SM_CYDOUBLECLK = 37
const _SM_CYFULLSCREEN = 17
const _SPI_GETNONCLIENTMETRICS = 41
const _SRCCOPY = 13369376
const _SS_LEFTNOWORDWRAP = 12
const _SS_NOPREFIX = 128
const _STARTF_USESHOWWINDOW = 1
const _SW_ERASE = 4
const _SW_HIDE = 0
const _SW_INVALIDATE = 2
const _SW_SHOW = 5
const _SW_SHOWDEFAULT = 10
const _TRUE = 1
const _VK_ADD = 107
const _VK_CLEAR = 12
const _VK_CONTROL = 17
const _VK_DELETE = 46
const _VK_DIVIDE = 111
const _VK_DOWN = 40
const _VK_END = 35
const _VK_ESCAPE = 27
const _VK_F1 = 112
const _VK_F10 = 121
const _VK_F11 = 122
const _VK_F12 = 123
const _VK_F2 = 113
const _VK_F3 = 114
const _VK_F4 = 115
const _VK_F5 = 116
const _VK_F6 = 117
const _VK_F7 = 118
const _VK_F8 = 119
const _VK_F9 = 120
const _VK_HOME = 36
const _VK_INSERT = 45
const _VK_LCONTROL = 162
const _VK_LEFT = 37
const _VK_LMENU = 164
const _VK_LSHIFT = 160
const _VK_LWIN = 91
const _VK_MENU = 18
const _VK_MULTIPLY = 106
const _VK_NEXT = 34
const _VK_PRIOR = 33
const _VK_RCONTROL = 163
const _VK_RETURN = 13
const _VK_RIGHT = 39
const _VK_RMENU = 165
const _VK_RSHIFT = 161
const _VK_RWIN = 92
const _VK_SHIFT = 16
const _VK_SUBTRACT = 109
const _VK_UP = 38
const _WA_INACTIVE = 0
const _WM_ACTIVATE = 6
const _WM_APP = 32768
const _WM_CLOSE = 16
const _WM_COMMAND = 273
const _WM_ERASEBKGND = 20
const _WM_GETMINMAXINFO = 36
const _WM_GETTEXT = 13
const _WM_GETTEXTLENGTH = 14
const _WM_HSCROLL = 276
const _WM_KEYDOWN = 256
const _WM_KEYUP = 257
const _WM_LBUTTONDOWN = 513
const _WM_LBUTTONUP = 514
const _WM_MBUTTONDOWN = 519
const _WM_MBUTTONUP = 520
const _WM_MOUSEACTIVATE = 33
const _WM_MOUSEMOVE = 512
const _WM_NCCREATE = 129
const _WM_PAINT = 15
const _WM_RBUTTONDOWN = 516
const _WM_RBUTTONUP = 517
const _WM_SETFONT = 48
const _WM_SIZE = 5
const _WM_SYSKEYDOWN = 260
const _WM_SYSKEYUP = 261
const _WM_VSCROLL = 277
const _WM_XBUTTONDOWN = 523
const _WM_XBUTTONUP = 524
const _WS_CHILD = 1073741824
const _WS_EX_CLIENTEDGE = 512
const _WS_HSCROLL = 1048576
const _WS_OVERLAPPEDWINDOW = 13565952
const _WS_TABSTOP = 65536
const _WS_VISIBLE = 268435456
const _WS_VSCROLL = 2097152
const _HWND_MESSAGE = 18446744073709551613
const _IDC_ARROW = 32512
const _IDI_APPLICATION = 32512
const _INVALID_HANDLE_VALUE = 18446744073709551615
const _NULL = 0
var _getWindowLongPtr = user32.NewProc("GetWindowLongPtrW")
var _setWindowLongPtr = user32.NewProc("SetWindowLongPtrW")