Remvoed the newctrl working directory.

This commit is contained in:
Pietro Gagliardi 2014-10-18 17:03:38 -04:00
parent 62048303a3
commit aed423a09f
51 changed files with 0 additions and 6736 deletions

View File

@ -1,244 +0,0 @@
// 29 march 2014
package ui
import (
"fmt"
"image"
"unsafe"
)
//// #include <HIToolbox/Events.h>
// #include "objc_darwin.h"
import "C"
type area struct {
*areabase
*scroller
textfield C.id
textfielddone *event
}
func newArea(ab *areabase) Area {
a := &area{
areabase: ab,
textfielddone: newEvent(),
}
id := C.newArea(unsafe.Pointer(a))
a.scroller = newScroller(id, false) // no border on Area
a.fpreferredSize = a.xpreferredSize
a.SetSize(a.width, a.height)
a.textfield = C.newTextField()
C.areaSetTextField(a.id, a.textfield)
return a
}
func (a *area) SetSize(width, height int) {
a.width = width
a.height = height
// set the frame size to set the area's effective size on the Cocoa side
C.moveControl(a.id, 0, 0, C.intptr_t(a.width), C.intptr_t(a.height))
}
func (a *area) Repaint(r image.Rectangle) {
var s C.struct_xrect
r = image.Rect(0, 0, a.width, a.height).Intersect(r)
if r.Empty() {
return
}
s.x = C.intptr_t(r.Min.X)
s.y = C.intptr_t(r.Min.Y)
s.width = C.intptr_t(r.Dx())
s.height = C.intptr_t(r.Dy())
C.areaRepaint(a.id, s)
}
func (a *area) RepaintAll() {
C.areaRepaintAll(a.id)
}
func (a *area) OpenTextFieldAt(x, y int) {
if x < 0 || x >= a.width || y < 0 || y >= a.height {
panic(fmt.Errorf("point (%d,%d) outside Area in Area.OpenTextFieldAt()", x, y))
}
C.areaTextFieldOpen(a.id, a.textfield, C.intptr_t(x), C.intptr_t(y))
}
func (a *area) TextFieldText() string {
return C.GoString(C.textfieldText(a.textfield))
}
func (a *area) SetTextFieldText(text string) {
ctext := C.CString(text)
defer C.free(unsafe.Pointer(ctext))
C.textfieldSetText(a.textfield, ctext)
}
func (a *area) OnTextFieldDismissed(f func()) {
a.textfielddone.set(f)
}
//export areaTextFieldDismissed
func areaTextFieldDismissed(data unsafe.Pointer) {
a := (*area)(unsafe.Pointer(data))
C.controlSetHidden(a.textfield, C.YES)
a.textfielddone.fire()
}
//export areaView_drawRect
func areaView_drawRect(self C.id, rect C.struct_xrect, data unsafe.Pointer) {
a := (*area)(data)
// 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))
cliprect = image.Rect(0, 0, int(a.width), int(a.height)).Intersect(cliprect)
if cliprect.Empty() { // no intersection; nothing to paint
return
}
i := a.handler.Paint(cliprect)
success := 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))
if success == C.NO {
panic("error drawing into Area (exactly what is unknown)")
}
}
func parseModifiers(e C.id) (m Modifiers) {
mods := C.modifierFlags(e)
if (mods & C.cNSControlKeyMask) != 0 {
m |= Ctrl
}
if (mods & C.cNSAlternateKeyMask) != 0 {
m |= Alt
}
if (mods & C.cNSShiftKeyMask) != 0 {
m |= Shift
}
if (mods & C.cNSCommandKeyMask) != 0 {
m |= Super
}
return m
}
func areaMouseEvent(self C.id, e C.id, click bool, up bool, data unsafe.Pointer) {
var me MouseEvent
a := (*area)(data)
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
if !me.Pos.In(image.Rect(0, 0, int(a.width), int(a.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
}
a.handler.Mouse(me)
}
//export areaView_mouseMoved_mouseDragged
func areaView_mouseMoved_mouseDragged(self C.id, e C.id, data unsafe.Pointer) {
// 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, data)
}
//export areaView_mouseDown
func areaView_mouseDown(self C.id, e C.id, data unsafe.Pointer) {
// 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, data)
}
//export areaView_mouseUp
func areaView_mouseUp(self C.id, e C.id, data unsafe.Pointer) {
areaMouseEvent(self, e, true, true, data)
}
func sendKeyEvent(self C.id, ke KeyEvent, data unsafe.Pointer) C.BOOL {
a := (*area)(data)
handled := a.handler.Key(ke)
return toBOOL(handled)
}
func areaKeyEvent(self C.id, e C.id, up bool, data unsafe.Pointer) C.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 C.NO
}
// either ke.Key or ke.ExtKey will be set at this point
ke.Modifiers = parseModifiers(e)
ke.Up = up
return sendKeyEvent(self, ke, data)
}
//export areaView_keyDown
func areaView_keyDown(self C.id, e C.id, data unsafe.Pointer) C.BOOL {
return areaKeyEvent(self, e, false, data)
}
//export areaView_keyUp
func areaView_keyUp(self C.id, e C.id, data unsafe.Pointer) C.BOOL {
return areaKeyEvent(self, e, true, data)
}
//export areaView_flagsChanged
func areaView_flagsChanged(self C.id, e C.id, data unsafe.Pointer) C.BOOL {
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 C.NO
}
ke.Modifiers = parseModifiers(e)
ke.Up = (ke.Modifiers & mod) == 0
ke.Modifier = mod
// don't include the modifier in ke.Modifiers
ke.Modifiers &^= mod
return sendKeyEvent(self, ke, data)
}
func (a *area) xpreferredSize(d *sizing) (width, height int) {
// the preferred size of an Area is its size
return a.width, a.height
}

View File

@ -1,497 +0,0 @@
// +build !windows,!darwin,!plan9
// 14 march 2014
package ui
import (
"fmt"
"image"
"unsafe"
)
// #include "gtk_unix.h"
// extern gboolean our_area_get_child_position_callback(GtkOverlay *, GtkWidget *, GdkRectangle *, gpointer);
// extern void our_area_textfield_populate_popup_callback(GtkEntry *, GtkMenu *, gpointer);
// extern gboolean our_area_textfield_focus_out_event_callback(GtkWidget *, GdkEvent *, gpointer);
// 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"
type area struct {
*areabase
*scroller
drawingarea *C.GtkDrawingArea
clickCounter *clickCounter
textfieldw *C.GtkWidget
textfield *C.GtkEntry
textfieldx int
textfieldy int
textfielddone *event
inmenu bool
}
func newArea(ab *areabase) Area {
widget := 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(widget,
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(widget, C.TRUE)
textfieldw := C.gtk_entry_new()
a := &area{
areabase: ab,
drawingarea: (*C.GtkDrawingArea)(unsafe.Pointer(widget)),
scroller: newScroller(widget, false, false, true), // not natively scrollable; no border; have an overlay for OpenTextFieldAt()
clickCounter: new(clickCounter),
textfieldw: textfieldw,
textfield: (*C.GtkEntry)(unsafe.Pointer(textfieldw)),
textfielddone: newEvent(),
}
a.fpreferredSize = a.xpreferredSize
for _, c := range areaCallbacks {
g_signal_connect(
C.gpointer(unsafe.Pointer(a.drawingarea)),
c.name,
c.callback,
C.gpointer(unsafe.Pointer(a)))
}
a.SetSize(a.width, a.height)
C.gtk_overlay_add_overlay(a.scroller.overlayoverlay, a.textfieldw)
g_signal_connect(
C.gpointer(unsafe.Pointer(a.scroller.overlayoverlay)),
"get-child-position",
area_get_child_position_callback,
C.gpointer(unsafe.Pointer(a)))
// this part is important
// entering the context menu is considered focusing out
// so we connect to populate-popup to mark that we're entering the context menu (thanks slaf in irc.gimp.net/#gtk+)
// and we have to connect_after to focus-out-event so that it runs after the populate-popup
g_signal_connect(
C.gpointer(unsafe.Pointer(a.textfield)),
"populate-popup",
area_textfield_populate_popup_callback,
C.gpointer(unsafe.Pointer(a)))
g_signal_connect_after(
C.gpointer(unsafe.Pointer(a.textfield)),
"focus-out-event",
area_textfield_focus_out_event_callback,
C.gpointer(unsafe.Pointer(a)))
// the widget shows up initially
C.gtk_widget_set_no_show_all(a.textfieldw, C.TRUE)
return a
}
func (a *area) SetSize(width, height int) {
a.width = width
a.height = height
C.gtk_widget_set_size_request(a.widget, C.gint(a.width), C.gint(a.height))
}
func (a *area) Repaint(r image.Rectangle) {
r = image.Rect(0, 0, a.width, a.height).Intersect(r)
if r.Empty() {
return
}
C.gtk_widget_queue_draw_area(a.widget, C.gint(r.Min.X), C.gint(r.Min.Y), C.gint(r.Dx()), C.gint(r.Dy()))
}
func (a *area) RepaintAll() {
C.gtk_widget_queue_draw(a.widget)
}
func (a *area) OpenTextFieldAt(x, y int) {
if x < 0 || x >= a.width || y < 0 || y >= a.height {
panic(fmt.Errorf("point (%d,%d) outside Area in Area.OpenTextFieldAt()", x, y))
}
a.textfieldx = x
a.textfieldy = y
a.inmenu = false // to start
// we disabled this for the initial Area show; we don't need to anymore
C.gtk_widget_set_no_show_all(a.textfieldw, C.FALSE)
C.gtk_widget_show_all(a.textfieldw)
C.gtk_widget_grab_focus(a.textfieldw)
}
func (a *area) TextFieldText() string {
return fromgstr(C.gtk_entry_get_text(a.textfield))
}
func (a *area) SetTextFieldText(text string) {
ctext := togstr(text)
defer freegstr(ctext)
C.gtk_entry_set_text(a.textfield, ctext)
}
func (a *area) OnTextFieldDismissed(f func()) {
a.textfielddone.set(f)
}
//export our_area_get_child_position_callback
func our_area_get_child_position_callback(overlay *C.GtkOverlay, widget *C.GtkWidget, rect *C.GdkRectangle, data C.gpointer) C.gboolean {
var nat C.GtkRequisition
a := (*area)(unsafe.Pointer(data))
rect.x = C.int(a.textfieldx)
rect.y = C.int(a.textfieldy)
C.gtk_widget_get_preferred_size(a.textfieldw, nil, &nat)
rect.width = C.int(nat.width)
rect.height = C.int(nat.height)
return C.TRUE
}
var area_get_child_position_callback = C.GCallback(C.our_area_get_child_position_callback)
//export our_area_textfield_populate_popup_callback
func our_area_textfield_populate_popup_callback(entry *C.GtkEntry, menu *C.GtkMenu, data C.gpointer) {
a := (*area)(unsafe.Pointer(data))
a.inmenu = true
}
var area_textfield_populate_popup_callback = C.GCallback(C.our_area_textfield_populate_popup_callback)
//export our_area_textfield_focus_out_event_callback
func our_area_textfield_focus_out_event_callback(widget *C.GtkWidget, event *C.GdkEvent, data C.gpointer) C.gboolean {
a := (*area)(unsafe.Pointer(data))
if !a.inmenu {
C.gtk_widget_hide(a.textfieldw)
a.textfielddone.fire()
}
a.inmenu = false // for next time
return continueEventChain
}
var area_textfield_focus_out_event_callback = C.GCallback(C.our_area_textfield_focus_out_event_callback)
var areaCallbacks = []struct {
name string
callback C.GCallback
}{
{"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},
}
//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
a := (*area)(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
cliprect = image.Rect(0, 0, a.width, a.height).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 := a.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)), false) // not NRGBA
C.cairo_surface_mark_dirty(surface)
C.cairo_set_source_surface(cr,
surface,
x0, y0) // point on cairo_t where we want to draw (thanks Company in irc.gimp.net/#gtk+)
// 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 norm 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 {
m |= Alt
}
if (state & C.GDK_MOD1_MASK) != 0 { // GTK+ itself requires this to be Alt (just read through gtkaccelgroup.c)
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
}
a := (*area)(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
// GDK expressly does not support any more buttons in the GdkModifierType; see https://git.gnome.org/browse/gtk+/tree/gdk/x11/gdkdevice-xi2.c#n763 (thanks mclasen in irc.gimp.net/#gtk+)
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
}
a.handler.Mouse(me)
}
// convenience name to make our intent clear
const continueEventChain C.gboolean = C.FALSE
const stopEventChain C.gboolean = C.TRUE
// 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
}
a := (*area)(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 = a.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 {
a := (*area)(unsafe.Pointer(data))
a.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) bool {
var ke KeyEvent
e := (*C.GdkEventKey)(unsafe.Pointer(event))
a := (*area)(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 false
}
ke.Up = up
return a.handler.Key(ke)
}
//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 {
if doKeyEvent(widget, event, data, false) == true {
return stopEventChain
}
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 {
if doKeyEvent(widget, event, data, true) == true {
return stopEventChain
}
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,
}
func (a *area) xpreferredSize(d *sizing) (width, height int) {
// the preferred size of an Area is its size
return a.width, a.height
}

View File

@ -1,500 +0,0 @@
// 24 march 2014
#include "winapi_windows.h"
#include "_cgo_export.h"
static void getScrollPos(HWND hwnd, int *xpos, int *ypos)
{
SCROLLINFO si;
ZeroMemory(&si, sizeof (SCROLLINFO));
si.cbSize = sizeof (SCROLLINFO);
si.fMask = SIF_POS | SIF_TRACKPOS;
if (GetScrollInfo(hwnd, SB_HORZ, &si) == 0)
xpanic("error getting horizontal scroll position for Area", GetLastError());
*xpos = si.nPos;
// MSDN example code reinitializes this each time, so we'll do it too just to be safe
ZeroMemory(&si, sizeof (SCROLLINFO));
si.cbSize = sizeof (SCROLLINFO);
si.fMask = SIF_POS | SIF_TRACKPOS;
if (GetScrollInfo(hwnd, SB_VERT, &si) == 0)
xpanic("error getting vertical scroll position for Area", GetLastError());
*ypos = si.nPos;
}
#define areaBackgroundBrush ((HBRUSH) (COLOR_BTNFACE + 1))
static void paintArea(HWND hwnd, void *data)
{
RECT xrect;
PAINTSTRUCT ps;
HDC hdc;
HDC rdc;
HBITMAP rbitmap, prevrbitmap;
RECT rrect;
BITMAPINFO bi;
VOID *ppvBits;
HBITMAP ibitmap;
HDC idc;
HBITMAP previbitmap;
BLENDFUNCTION blendfunc;
void *i;
intptr_t dx, dy;
int hscroll, vscroll;
// FALSE here indicates don't send WM_ERASEBKGND
if (GetUpdateRect(hwnd, &xrect, FALSE) == 0)
return; // no update rect; do nothing
getScrollPos(hwnd, &hscroll, &vscroll);
hdc = BeginPaint(hwnd, &ps);
if (hdc == NULL)
xpanic("error beginning Area repaint", GetLastError());
// 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
rdc = CreateCompatibleDC(hdc);
if (rdc == NULL)
xpanic("error creating off-screen rendering DC", GetLastError());
// 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
rbitmap = CreateCompatibleBitmap(hdc, xrect.right - xrect.left, xrect.bottom - xrect.top);
if (rbitmap == NULL)
xpanic("error creating off-screen rendering bitmap", GetLastError());
prevrbitmap = (HBITMAP) SelectObject(rdc, rbitmap);
if (prevrbitmap == NULL)
xpanic("error connecting off-screen rendering bitmap to off-screen rendering DC", GetLastError());
rrect.left = 0;
rrect.right = xrect.right - xrect.left;
rrect.top = 0;
rrect.bottom = xrect.bottom - xrect.top;
if (FillRect(rdc, &rrect, areaBackgroundBrush) == 0)
xpanic("error filling off-screen rendering bitmap with the system background color", GetLastError());
i = doPaint(&xrect, hscroll, vscroll, data, &dx, &dy);
if (i == NULL) // cliprect empty
goto nobitmap; // we need to blit the background no matter what
// 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
ZeroMemory(&bi, sizeof (BITMAPINFO));
bi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER);
bi.bmiHeader.biWidth = (LONG) dx;
bi.bmiHeader.biHeight = -((LONG) dy); // negative height to force top-down drawing
bi.bmiHeader.biPlanes = 1;
bi.bmiHeader.biBitCount = 32;
bi.bmiHeader.biCompression = BI_RGB;
bi.bmiHeader.biSizeImage = (DWORD) (dx * 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)
// now for the trouble: CreateDIBSection() allocates the memory for us...
ibitmap = CreateDIBSection(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.
&bi, DIB_RGB_COLORS, &ppvBits, 0, 0);
if (ibitmap == NULL)
xpanic("error creating HBITMAP for image returned by AreaHandler.Paint()", GetLastError());
// 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
dotoARGB(i, (void *) ppvBits, FALSE); // FALSE = not NRGBA
// the second thing is... make a device context for the bitmap :|
// Ninjifox just makes another compatible DC; we'll do the same
idc = CreateCompatibleDC(hdc);
if (idc == NULL)
xpanic("error creating HDC for image returned by AreaHandler.Paint()", GetLastError());
previbitmap = (HBITMAP) SelectObject(idc, ibitmap);
if (previbitmap == NULL)
xpanic("error connecting HBITMAP for image returned by AreaHandler.Paint() to its HDC", GetLastError());
// AND FINALLY WE CAN DO THE ALPHA BLENDING!!!!!!111
blendfunc.BlendOp = AC_SRC_OVER;
blendfunc.BlendFlags = 0;
blendfunc.SourceConstantAlpha = 255; // only use per-pixel alphas
blendfunc.AlphaFormat = AC_SRC_ALPHA; // premultiplied
if (AlphaBlend(rdc, 0, 0, (int) dx, (int) dy, // destination
idc, 0, 0, (int) dx, (int)dy, // source
blendfunc) == FALSE)
xpanic("error alpha-blending image returned by AreaHandler.Paint() onto background", GetLastError());
// clean up after idc/ibitmap here because of the goto nobitmap
if (SelectObject(idc, previbitmap) != ibitmap)
xpanic("error reverting HDC for image returned by AreaHandler.Paint() to original HBITMAP", GetLastError());
if (DeleteObject(ibitmap) == 0)
xpanic("error deleting HBITMAP for image returned by AreaHandler.Paint()", GetLastError());
if (DeleteDC(idc) == 0)
xpanic("error deleting HDC for image returned by AreaHandler.Paint()", GetLastError());
nobitmap:
// and finally we can just blit that into the window
if (BitBlt(hdc, xrect.left, xrect.top, xrect.right - xrect.left, xrect.bottom - xrect.top,
rdc, 0, 0, // from the rdc's origin
SRCCOPY) == 0)
xpanic("error blitting Area image to Area", GetLastError());
// now to clean up
if (SelectObject(rdc, prevrbitmap) != rbitmap)
xpanic("error reverting HDC for off-screen rendering to original HBITMAP", GetLastError());
if (DeleteObject(rbitmap) == 0)
xpanic("error deleting HBITMAP for off-screen rendering", GetLastError());
if (DeleteDC(rdc) == 0)
xpanic("error deleting HDC for off-screen rendering", GetLastError());
EndPaint(hwnd, &ps);
}
static SIZE getAreaControlSize(HWND hwnd)
{
RECT rect;
SIZE size;
if (GetClientRect(hwnd, &rect) == 0)
xpanic("error getting size of actual Area control", GetLastError());
size.cx = (LONG) (rect.right - rect.left);
size.cy = (LONG) (rect.bottom - rect.top);
return size;
}
static void scrollArea(HWND hwnd, void *data, WPARAM wParam, int which)
{
SCROLLINFO si;
SIZE size;
LONG cwid, cht;
LONG pagesize, maxsize;
LONG newpos;
LONG delta;
LONG dx, dy;
size = getAreaControlSize(hwnd);
cwid = size.cx;
cht = size.cy;
if (which == SB_HORZ) {
pagesize = cwid;
maxsize = areaWidthLONG(data);
} else if (which == SB_VERT) {
pagesize = cht;
maxsize = areaHeightLONG(data);
} else
xpanic("invalid which sent to scrollArea()", 0);
ZeroMemory(&si, sizeof (SCROLLINFO));
si.cbSize = sizeof (SCROLLINFO);
si.fMask = SIF_POS | SIF_TRACKPOS;
if (GetScrollInfo(hwnd, which, &si) == 0)
xpanic("error getting current scroll position for scrolling", GetLastError());
newpos = (LONG) si.nPos;
switch (LOWORD(wParam)) {
case SB_LEFT: // also SB_TOP; C won't let me have both (C89 §6.6.4.2; C99 §6.8.4.2)
newpos = 0;
break;
case SB_RIGHT: // also SB_BOTTOM
// see comment in adjustAreaScrollbars() below
newpos = maxsize - pagesize;
break;
case SB_LINELEFT: // also SB_LINEUP
newpos--;
break;
case SB_LINERIGHT: // also SB_LINEDOWN
newpos++;
break;
case SB_PAGELEFT: // also SB_PAGEUP
newpos -= pagesize;
break;
case SB_PAGERIGHT: // also SB_PAGEDOWN
newpos += pagesize;
break;
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
break;
case SB_THUMBTRACK:
newpos = (LONG) 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 = 0;
if (which == SB_VERT) {
dx = 0;
dy = delta;
}
// this automatically scrolls the edit control, if any
if (ScrollWindowEx(hwnd,
(int) dx, (int) dy,
// 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
NULL, NULL, NULL, NULL,
// mark the remaining rect as needing redraw and erase...
SW_INVALIDATE | SW_ERASE) == ERROR)
xpanic("error scrolling Area", GetLastError());
// ...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
ZeroMemory(&si, sizeof (SCROLLINFO));
si.cbSize = sizeof (SCROLLINFO);
si.fMask = SIF_POS;
si.nPos = (int) newpos;
// this is not expressly documented as returning an error so IDK what the error code is so assume there is none
SetScrollInfo(hwnd, which, &si, TRUE); // redraw scrollbar
// NOW redraw it
if (UpdateWindow(hwnd) == 0)
xpanic("error updating Area after scrolling", GetLastError());
if ((HWND) GetWindowLongPtrW(hwnd, 0) != NULL)
if (UpdateWindow((HWND) GetWindowLongPtrW(hwnd, 0)) == 0)
xpanic("error updating Area TextField after scrolling", GetLastError());
}
static void adjustAreaScrollbars(HWND hwnd, void *data)
{
SCROLLINFO si;
SIZE size;
LONG cwid, cht;
size = getAreaControlSize(hwnd);
cwid = size.cx;
cht = size.cy;
// 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
ZeroMemory(&si, sizeof (SCROLLINFO));
si.cbSize = sizeof (SCROLLINFO);
si.fMask = SIF_RANGE | SIF_PAGE;
si.nMin = 0;
si.nMax = (int) (areaWidthLONG(data) - 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 = (UINT) cwid;
SetScrollInfo(hwnd, SB_HORZ, &si, TRUE); // redraw the scroll bar
// MSDN sample code does this a second time; let's do it too to be safe
ZeroMemory(&si, sizeof (SCROLLINFO));
si.cbSize = sizeof (SCROLLINFO);
si.fMask = SIF_RANGE | SIF_PAGE;
si.nMin = 0;
si.nMax = (int) (areaHeightLONG(data) - 1);
si.nPage = (UINT) cht;
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
}
void repaintArea(HWND hwnd, RECT *r)
{
// NULL - the whole area; TRUE - have windows erase if possible
if (InvalidateRect(hwnd, r, TRUE) == 0)
xpanic("error flagging Area as needing repainting after event", GetLastError());
if (UpdateWindow(hwnd) == 0)
xpanic("error repainting Area after event", GetLastError());
}
void areaMouseEvent(HWND hwnd, void *data, DWORD button, BOOL up, uintptr_t heldButtons, LPARAM lParam)
{
int xpos, ypos;
// mouse coordinates are relative to control; make them relative to Area
getScrollPos(hwnd, &xpos, &ypos);
xpos += GET_X_LPARAM(lParam);
ypos += GET_Y_LPARAM(lParam);
finishAreaMouseEvent(data, button, up, heldButtons, xpos, ypos);
}
static LRESULT CALLBACK areaWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
void *data;
DWORD which;
uintptr_t heldButtons = (uintptr_t) wParam;
LRESULT lResult;
data = getWindowData(hwnd, uMsg, wParam, lParam, &lResult);
if (data == NULL)
return lResult;
switch (uMsg) {
case WM_PAINT:
paintArea(hwnd, data);
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(hwnd, data, wParam, SB_HORZ);
return 0;
case WM_VSCROLL:
scrollArea(hwnd, data, wParam, SB_VERT);
return 0;
case WM_SIZE:
adjustAreaScrollbars(hwnd, data);
return 0;
case WM_ACTIVATE:
// don't keep the double-click timer running if the user switched programs in between clicks
areaResetClickCounter(data);
return 0;
case WM_MOUSEMOVE:
areaMouseEvent(hwnd, data, 0, FALSE, heldButtons, lParam);
return 0;
case WM_LBUTTONDOWN:
SetFocus(hwnd);
areaMouseEvent(hwnd, data, 1, FALSE, heldButtons, lParam);
return 0;
case WM_LBUTTONUP:
areaMouseEvent(hwnd, data, 1, TRUE, heldButtons, lParam);
return 0;
case WM_MBUTTONDOWN:
SetFocus(hwnd);
areaMouseEvent(hwnd, data, 2, FALSE, heldButtons, lParam);
return 0;
case WM_MBUTTONUP:
areaMouseEvent(hwnd, data, 2, TRUE, heldButtons, lParam);
return 0;
case WM_RBUTTONDOWN:
SetFocus(hwnd);
areaMouseEvent(hwnd, data, 3, FALSE, heldButtons, lParam);
return 0;
case WM_RBUTTONUP:
areaMouseEvent(hwnd, data, 3, TRUE, heldButtons, lParam);
return 0;
case WM_XBUTTONDOWN:
SetFocus(hwnd);
// values start at 1; we want them to start at 4
which = (DWORD) GET_XBUTTON_WPARAM(wParam) + 3;
heldButtons = (uintptr_t) GET_KEYSTATE_WPARAM(wParam);
areaMouseEvent(hwnd, data, which, FALSE, heldButtons, lParam);
return TRUE; // XBUTTON messages are different!
case WM_XBUTTONUP:
which = (DWORD) GET_XBUTTON_WPARAM(wParam) + 3;
heldButtons = (uintptr_t) GET_KEYSTATE_WPARAM(wParam);
areaMouseEvent(hwnd, data, which, TRUE, heldButtons, lParam);
return TRUE;
case msgAreaKeyDown:
return (LRESULT) areaKeyEvent(data, FALSE, wParam, lParam);
case msgAreaKeyUp:
return (LRESULT) areaKeyEvent(data, TRUE, wParam, lParam);
case msgAreaSizeChanged:
adjustAreaScrollbars(hwnd, data);
repaintArea(hwnd, NULL); // this calls for an update
return 0;
case msgAreaGetScroll:
getScrollPos(hwnd, (int *) wParam, (int *) lParam);
return 0;
case msgAreaRepaint:
repaintArea(hwnd, (RECT *) lParam);
return 0;
case msgAreaRepaintAll:
repaintArea(hwnd, NULL);
return 0;
default:
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
xmissedmsg("Area", "areaWndProc()", uMsg);
return 0; // unreached
}
DWORD makeAreaWindowClass(char **errmsg)
{
WNDCLASSW wc;
ZeroMemory(&wc, sizeof (WNDCLASSW));
wc.style = CS_HREDRAW | CS_VREDRAW; // no CS_DBLCLKS because do that manually
wc.lpszClassName = areaWindowClass;
wc.lpfnWndProc = areaWndProc;
wc.hInstance = hInstance;
wc.hIcon = hDefaultIcon;
wc.hCursor = hArrowCursor,
wc.hbrBackground = NULL; // no brush; we handle WM_ERASEBKGND
wc.cbWndExtra = 3 * sizeof (LONG_PTR); // text field handle, text field current x, text field current y
if (RegisterClassW(&wc) == 0) {
*errmsg = "error registering Area window class";
return GetLastError();
}
return 0;
}
HWND newArea(void *data)
{
HWND hwnd;
hwnd = CreateWindowExW(
0,
areaWindowClass, L"",
WS_HSCROLL | WS_VSCROLL | WS_CHILD | WS_VISIBLE | WS_TABSTOP,
CW_USEDEFAULT, CW_USEDEFAULT,
100, 100,
msgwin, NULL, hInstance, data);
if (hwnd == NULL)
xpanic("container creation failed", GetLastError());
return hwnd;
}
static LRESULT CALLBACK areaTextFieldSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR id, DWORD_PTR data)
{
switch (uMsg) {
case WM_KILLFOCUS:
ShowWindow(hwnd, SW_HIDE);
areaTextFieldDone((void *) data);
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
case WM_NCDESTROY:
if ((*fv_RemoveWindowSubclass)(hwnd, areaTextFieldSubProc, id) == FALSE)
xpanic("error removing Area TextField subclass (which was for handling WM_KILLFOCUS)", GetLastError());
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
default:
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
}
xmissedmsg("Area TextField", "areaTextFieldSubProc()", uMsg);
return 0; // unreached
}
HWND newAreaTextField(HWND area, void *goarea)
{
HWND tf;
tf = CreateWindowExW(textfieldExtStyle,
L"edit", L"",
textfieldStyle | WS_CHILD,
0, 0, 0, 0,
area, NULL, hInstance, NULL);
if (tf == NULL)
xpanic("error making Area TextField", GetLastError());
if ((*fv_SetWindowSubclass)(tf, areaTextFieldSubProc, 0, (DWORD_PTR) goarea) == FALSE)
xpanic("error subclassing Area TextField to give it its own WM_KILLFOCUS handler", GetLastError());
return tf;
}
void areaOpenTextField(HWND area, HWND textfield, int x, int y, int width, int height)
{
int sx, sy;
int baseX, baseY;
LONG unused;
getScrollPos(area, &sx, &sy);
x += sx;
y += sy;
calculateBaseUnits(textfield, &baseX, &baseY, &unused);
width = MulDiv(width, baseX, 4);
height = MulDiv(height, baseY, 8);
if (MoveWindow(textfield, x, y, width, height, TRUE) == 0)
xpanic("error moving Area TextField in Area.OpenTextFieldAt()", GetLastError());
ShowWindow(textfield, SW_SHOW);
if (SetFocus(textfield) == NULL)
xpanic("error giving Area TextField focus", GetLastError());
}
void areaMarkTextFieldDone(HWND area)
{
SetWindowLongPtrW(area, 0, (LONG_PTR) NULL);
}

View File

@ -1,336 +0,0 @@
// 24 march 2014
package ui
import (
"fmt"
"image"
"syscall"
"unsafe"
)
// #include "winapi_windows.h"
import "C"
type area struct {
*areabase
*controlSingleHWND
clickCounter *clickCounter
textfield C.HWND
textfielddone *event
}
func makeAreaWindowClass() error {
var errmsg *C.char
err := C.makeAreaWindowClass(&errmsg)
if err != 0 || errmsg != nil {
return fmt.Errorf("%s: %v", C.GoString(errmsg), syscall.Errno(err))
}
return nil
}
func newArea(ab *areabase) Area {
a := &area{
areabase: ab,
clickCounter: new(clickCounter),
textfielddone: newEvent(),
}
a.controlSingleHWND = newControlSingleHWND(C.newArea(unsafe.Pointer(a)))
a.fpreferredSize = a.xpreferredSize
a.SetSize(a.width, a.height)
a.textfield = C.newAreaTextField(a.hwnd, unsafe.Pointer(a))
C.controlSetControlFont(a.textfield)
return a
}
func (a *area) SetSize(width, height int) {
a.width = width
a.height = height
C.SendMessageW(a.hwnd, C.msgAreaSizeChanged, 0, 0)
}
func (a *area) Repaint(r image.Rectangle) {
var hscroll, vscroll C.int
var rect C.RECT
C.SendMessageW(a.hwnd, C.msgAreaGetScroll, C.WPARAM(uintptr(unsafe.Pointer(&hscroll))), C.LPARAM(uintptr(unsafe.Pointer(&vscroll))))
r = r.Add(image.Pt(int(hscroll), int(vscroll))) // adjust by scroll position
r = image.Rect(0, 0, a.width, a.height).Intersect(r)
if r.Empty() {
return
}
rect.left = C.LONG(r.Min.X)
rect.top = C.LONG(r.Min.Y)
rect.right = C.LONG(r.Max.X)
rect.bottom = C.LONG(r.Max.Y)
C.SendMessageW(a.hwnd, C.msgAreaRepaint, 0, C.LPARAM(uintptr(unsafe.Pointer(&rect))))
}
func (a *area) RepaintAll() {
C.SendMessageW(a.hwnd, C.msgAreaRepaintAll, 0, 0)
}
func (a *area) OpenTextFieldAt(x, y int) {
if x < 0 || x >= a.width || y < 0 || y >= a.height {
panic(fmt.Errorf("point (%d,%d) outside Area in Area.OpenTextFieldAt()", x, y))
}
C.areaOpenTextField(a.hwnd, a.textfield, C.int(x), C.int(y), textfieldWidth, textfieldHeight)
}
func (a *area) TextFieldText() string {
return getWindowText(a.textfield)
}
func (a *area) SetTextFieldText(text string) {
t := toUTF16(text)
C.setWindowText(a.textfield, t)
}
func (a *area) OnTextFieldDismissed(f func()) {
a.textfielddone.set(f)
}
//export areaTextFieldDone
func areaTextFieldDone(data unsafe.Pointer) {
a := (*area)(data)
C.areaMarkTextFieldDone(a.hwnd)
a.textfielddone.fire()
}
//export doPaint
func doPaint(xrect *C.RECT, hscroll C.int, vscroll C.int, data unsafe.Pointer, dx *C.intptr_t, dy *C.intptr_t) unsafe.Pointer {
a := (*area)(data)
// 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, a.width, a.height))
if !cliprect.Empty() { // we have an update rect
i := a.handler.Paint(cliprect)
*dx = C.intptr_t(i.Rect.Dx())
*dy = C.intptr_t(i.Rect.Dy())
return unsafe.Pointer(i)
}
return nil
}
//export dotoARGB
func dotoARGB(img unsafe.Pointer, ppvBits unsafe.Pointer, toNRGBA C.BOOL) {
i := (*image.RGBA)(unsafe.Pointer(img))
t := toNRGBA != C.FALSE
// the bitmap Windows gives us has a stride == width
toARGB(i, uintptr(ppvBits), i.Rect.Dx()*4, t)
}
//export areaWidthLONG
func areaWidthLONG(data unsafe.Pointer) C.LONG {
a := (*area)(data)
return C.LONG(a.width)
}
//export areaHeightLONG
func areaHeightLONG(data unsafe.Pointer) C.LONG {
a := (*area)(data)
return C.LONG(a.height)
}
func getModifiers() (m Modifiers) {
down := func(x C.int) bool {
// GetKeyState() gets the key state at the time of the message, so this is what we want
return (C.GetKeyState(x) & 0x80) != 0
}
if down(C.VK_CONTROL) {
m |= Ctrl
}
if down(C.VK_MENU) {
m |= Alt
}
if down(C.VK_SHIFT) {
m |= Shift
}
if down(C.VK_LWIN) || down(C.VK_RWIN) {
m |= Super
}
return m
}
//export finishAreaMouseEvent
func finishAreaMouseEvent(data unsafe.Pointer, cbutton C.DWORD, up C.BOOL, heldButtons C.uintptr_t, xpos C.int, ypos C.int) {
var me MouseEvent
a := (*area)(data)
button := uint(cbutton)
me.Pos = image.Pt(int(xpos), int(ypos))
if !me.Pos.In(image.Rect(0, 0, a.width, a.height)) { // outside the actual Area; no event
return
}
if up != C.FALSE {
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 := C.GetMessageTime()
maxTime := C.GetDoubleClickTime()
// ignore zero returns and errors; MSDN says zero will be returned on error but that GetLastError() is meaningless
xdist := C.GetSystemMetrics(C.SM_CXDOUBLECLK)
ydist := C.GetSystemMetrics(C.SM_CYDOUBLECLK)
me.Count = a.clickCounter.click(button, me.Pos.X, me.Pos.Y,
uintptr(time), uintptr(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 && (heldButtons&C.MK_LBUTTON) != 0 {
me.Held = append(me.Held, 1)
}
if button != 2 && (heldButtons&C.MK_MBUTTON) != 0 {
me.Held = append(me.Held, 2)
}
if button != 3 && (heldButtons&C.MK_RBUTTON) != 0 {
me.Held = append(me.Held, 3)
}
if button != 4 && (heldButtons&C.MK_XBUTTON1) != 0 {
me.Held = append(me.Held, 4)
}
if button != 5 && (heldButtons&C.MK_XBUTTON2) != 0 {
me.Held = append(me.Held, 5)
}
a.handler.Mouse(me)
}
//export areaKeyEvent
func areaKeyEvent(data unsafe.Pointer, up C.BOOL, wParam C.WPARAM, lParam C.LPARAM) C.BOOL {
var ke KeyEvent
a := (*area)(data)
lp := uint32(lParam) // to be safe
// 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 := (lp & 0x01000000) != 0
scancode := byte((lp >> 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 == C.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 C.FALSE
}
ke.Up = up != C.FALSE
handled := a.handler.Key(ke)
if handled {
return C.TRUE
}
return C.FALSE
}
// all mappings come from GLFW - https://github.com/glfw/glfw/blob/master/src/win32_window.c#L152
var numpadextkeys = map[C.WPARAM]ExtKey{
C.VK_HOME: N7,
C.VK_UP: N8,
C.VK_PRIOR: N9,
C.VK_LEFT: N4,
C.VK_CLEAR: N5,
C.VK_RIGHT: N6,
C.VK_END: N1,
C.VK_DOWN: N2,
C.VK_NEXT: N3,
C.VK_INSERT: N0,
C.VK_DELETE: NDot,
}
var extkeys = map[C.WPARAM]ExtKey{
C.VK_ESCAPE: Escape,
C.VK_INSERT: Insert,
C.VK_DELETE: Delete,
C.VK_HOME: Home,
C.VK_END: End,
C.VK_PRIOR: PageUp,
C.VK_NEXT: PageDown,
C.VK_UP: Up,
C.VK_DOWN: Down,
C.VK_LEFT: Left,
C.VK_RIGHT: Right,
C.VK_F1: F1,
C.VK_F2: F2,
C.VK_F3: F3,
C.VK_F4: F4,
C.VK_F5: F5,
C.VK_F6: F6,
C.VK_F7: F7,
C.VK_F8: F8,
C.VK_F9: F9,
C.VK_F10: F10,
C.VK_F11: F11,
C.VK_F12: F12,
// numpad numeric keys and . are handled in events_notdarwin.go
// numpad enter is handled in code above
C.VK_ADD: NAdd,
C.VK_SUBTRACT: NSubtract,
C.VK_MULTIPLY: NMultiply,
C.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[C.WPARAM]Modifiers{
// even if the separate left/right aren't necessary, have them here anyway, just to be safe
C.VK_CONTROL: Ctrl,
C.VK_LCONTROL: Ctrl,
C.VK_RCONTROL: Ctrl,
C.VK_MENU: Alt,
C.VK_LMENU: Alt,
C.VK_RMENU: Alt,
C.VK_SHIFT: Shift,
C.VK_LSHIFT: Shift,
C.VK_RSHIFT: Shift,
// there's no combined Windows key virtual-key code as there is with the others
C.VK_LWIN: Super,
C.VK_RWIN: Super,
}
//export areaResetClickCounter
func areaResetClickCounter(data unsafe.Pointer) {
a := (*area)(data)
a.clickCounter.reset()
}
func (a *area) xpreferredSize(d *sizing) (width, height int) {
// the preferred size of an Area is its size
return a.width, a.height
}

View File

@ -1,123 +0,0 @@
// 7 july 2014
package ui
// Button is a clickable button that performs some task.
type Button interface {
Control
// OnClicked sets the event handler for when the Button is clicked.
OnClicked(func())
// Text and SetText get and set the Button's label text.
Text() string
SetText(text string)
}
// NewButton creates a new Button with the given label text.
func NewButton(text string) Button {
return newButton(text)
}
// Checkbox is a clickable box that indicates some Boolean value.
type Checkbox interface {
Control
// OnToggled sets the event handler for when the Checkbox is toggled.
OnToggled(func())
// Text and SetText get and set the Checkbox's label text.
Text() string
SetText(text string)
// Checked and SetChecked get and set the Checkbox's check state.
Checked() bool
SetChecked(checked bool)
}
// NewCheckbox creates a new Checkbox with the given label text.
// The Checkbox will be initially unchecked.
func NewCheckbox(text string) Checkbox {
return newCheckbox(text)
}
// TextField is a Control in which the user can enter a single line of text.
type TextField interface {
Control
// Text and SetText are Requests that get and set the TextField's text.
Text() string
SetText(text string)
// OnChanged is triggered when the text in a TextField is changed somehow.
// Do not bother trying to figure out how the text was changed; instead, perform your validation and use Invalid to inform the user that the entered text is invalid instead.
OnChanged(func())
// Invalid throws a non-modal alert (whose nature is system-defined) on or near the TextField that alerts the user that input is invalid.
// The string passed to Invalid will be displayed to the user to inform them of what specifically is wrong with the input.
// Pass an empty string to remove the warning.
Invalid(reason string)
}
// NewTextField creates a new TextField.
func NewTextField() TextField {
return newTextField()
}
// NewPasswordField creates a new TextField for entering passwords; that is, it hides the text being entered.
func NewPasswordField() TextField {
return newPasswordField()
}
// Tab is a Control that contains multiple pages of tabs, each containing a single Control.
// You can add and remove tabs from the Tab at any time.
// The appearance of a Tab with no tabs is implementation-defined.
type Tab interface {
Control
// Append adds a new tab to Tab.
// The tab is added to the end of the current list of tabs.
Append(name string, control Control)
}
// NewTab creates a new Tab with no tabs.
func NewTab() Tab {
return newTab()
}
// Label is a Control that shows a static line of text.
// Label shows one line of text; any text that does not fit is truncated.
// Labels are left-aligned. [FUTURE PLANS: For platform-specific horizontal alignment rules, use a Form.]
type Label interface {
Control
// Text and SetText get and set the Label's text.
Text() string
SetText(text string)
}
// NewLabel creates a new Label with the given text.
func NewLabel(text string) Label {
return newLabel(text)
}
// Group is a Control that holds a single Control; if that Control also contains other Controls, then the Controls will appear visually grouped together.
// The appearance of a Group varies from system to system; for the most part a Group consists of a thin frame.
// All Groups have a text label indicating what the Group is for.
type Group interface {
Control
// Text and SetText get and set the Group's label text.
Text() string
SetText(text string)
// Margined and SetMargined get and set whether the contents of the Group have a margin around them.
// The size of the margin is platform-dependent.
Margined() bool
SetMargined(margined bool)
}
// NewGroup creates a new Group with the given text label and child Control.
func NewGroup(text string, control Control) Group {
return newGroup(text, control)
}

View File

@ -1,47 +0,0 @@
// 16 july 2014
package ui
import (
"unsafe"
)
// #include "objc_darwin.h"
import "C"
type button struct {
*controlSingleObject
clicked *event
}
func newButton(text string) *button {
ctext := C.CString(text)
defer C.free(unsafe.Pointer(ctext))
b := &button{
controlSingleObject: newControlSingleObject(C.newButton()),
clicked: newEvent(),
}
C.buttonSetText(b.id, ctext)
C.buttonSetDelegate(b.id, unsafe.Pointer(b))
return b
}
func (b *button) OnClicked(e func()) {
b.clicked.set(e)
}
func (b *button) Text() string {
return C.GoString(C.buttonText(b.id))
}
func (b *button) SetText(text string) {
ctext := C.CString(text)
defer C.free(unsafe.Pointer(ctext))
C.buttonSetText(b.id, ctext)
}
//export buttonClicked
func buttonClicked(xb unsafe.Pointer) {
b := (*button)(unsafe.Pointer(xb))
b.clicked.fire()
}

View File

@ -1,57 +0,0 @@
// +build !windows,!darwin
// 7 july 2014
package ui
import (
"unsafe"
)
// #include "gtk_unix.h"
// extern void buttonClicked(GtkButton *, gpointer);
import "C"
type button struct {
*controlSingleWidget
button *C.GtkButton
clicked *event
}
// shared code for setting up buttons, check boxes, etc.
func newButton(text string) *button {
ctext := togstr(text)
defer freegstr(ctext)
widget := C.gtk_button_new_with_label(ctext)
b := &button{
controlSingleWidget: newControlSingleWidget(widget),
button: (*C.GtkButton)(unsafe.Pointer(widget)),
clicked: newEvent(),
}
g_signal_connect(
C.gpointer(unsafe.Pointer(b.button)),
"clicked",
C.GCallback(C.buttonClicked),
C.gpointer(unsafe.Pointer(b)))
return b
}
func (b *button) OnClicked(e func()) {
b.clicked.set(e)
}
func (b *button) Text() string {
return fromgstr(C.gtk_button_get_label(b.button))
}
func (b *button) SetText(text string) {
ctext := togstr(text)
defer freegstr(ctext)
C.gtk_button_set_label(b.button, ctext)
}
//export buttonClicked
func buttonClicked(bwid *C.GtkButton, data C.gpointer) {
b := (*button)(unsafe.Pointer(data))
b.clicked.fire()
}

View File

@ -1,71 +0,0 @@
// 15 july 2014
package ui
import (
"unsafe"
)
// #include "winapi_windows.h"
import "C"
type button struct {
*controlSingleHWNDWithText
clicked *event
}
var buttonclass = toUTF16("BUTTON")
func newButton(text string) *button {
hwnd := C.newControl(buttonclass,
C.BS_PUSHBUTTON|C.WS_TABSTOP,
0)
b := &button{
controlSingleHWNDWithText: newControlSingleHWNDWithText(hwnd),
clicked: newEvent(),
}
b.fpreferredSize = b.xpreferredSize
b.SetText(text)
C.controlSetControlFont(b.hwnd)
C.setButtonSubclass(b.hwnd, unsafe.Pointer(b))
return b
}
func (b *button) OnClicked(e func()) {
b.clicked.set(e)
}
func (b *button) Text() string {
return b.text()
}
func (b *button) SetText(text string) {
b.setText(text)
}
//export buttonClicked
func buttonClicked(data unsafe.Pointer) {
b := (*button)(data)
b.clicked.fire()
}
const (
// from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing
buttonHeight = 14
)
func (b *button) xpreferredSize(d *sizing) (width, height int) {
// comctl32.dll version 6 thankfully provides a method to grab this...
var size C.SIZE
size.cx = 0 // explicitly ask for ideal size
size.cy = 0
if C.SendMessageW(b.hwnd, C.BCM_GETIDEALSIZE, 0, C.LPARAM(uintptr(unsafe.Pointer(&size)))) != C.FALSE {
return int(size.cx), int(size.cy)
}
// that failed, fall back
println("message failed; falling back")
// don't worry about the error return from GetSystemMetrics(); there's no way to tell (explicitly documented as such)
xmargins := 2 * int(C.GetSystemMetrics(C.SM_CXEDGE))
return xmargins + int(b.textlen), fromdlgunitsY(buttonHeight, d)
}

View File

@ -1,55 +0,0 @@
// 16 july 2014
package ui
import (
"unsafe"
)
// #include "objc_darwin.h"
import "C"
type checkbox struct {
*controlSingleObject
toggled *event
}
func newCheckbox(text string) *checkbox {
ctext := C.CString(text)
defer C.free(unsafe.Pointer(ctext))
c := &checkbox{
controlSingleObject: newControlSingleObject(C.newCheckbox()),
toggled: newEvent(),
}
C.buttonSetText(c.id, ctext)
C.checkboxSetDelegate(c.id, unsafe.Pointer(c))
return c
}
func (c *checkbox) OnToggled(e func()) {
c.toggled.set(e)
}
func (c *checkbox) Text() string {
return C.GoString(C.buttonText(c.id))
}
func (c *checkbox) SetText(text string) {
ctext := C.CString(text)
defer C.free(unsafe.Pointer(ctext))
C.buttonSetText(c.id, ctext)
}
func (c *checkbox) Checked() bool {
return fromBOOL(C.checkboxChecked(c.id))
}
func (c *checkbox) SetChecked(checked bool) {
C.checkboxSetChecked(c.id, toBOOL(checked))
}
//export checkboxToggled
func checkboxToggled(xc unsafe.Pointer) {
c := (*checkbox)(unsafe.Pointer(xc))
c.toggled.fire()
}

View File

@ -1,68 +0,0 @@
// +build !windows,!darwin
// 7 july 2014
package ui
import (
"unsafe"
)
// #include "gtk_unix.h"
// extern void checkboxToggled(GtkToggleButton *, gpointer);
import "C"
type checkbox struct {
*controlSingleWidget
button *C.GtkButton
toggle *C.GtkToggleButton
checkbox *C.GtkCheckButton
toggled *event
}
func newCheckbox(text string) *checkbox {
ctext := togstr(text)
defer freegstr(ctext)
widget := C.gtk_check_button_new_with_label(ctext)
c := &checkbox{
controlSingleWidget: newControlSingleWidget(widget),
button: (*C.GtkButton)(unsafe.Pointer(widget)),
toggle: (*C.GtkToggleButton)(unsafe.Pointer(widget)),
checkbox: (*C.GtkCheckButton)(unsafe.Pointer(widget)),
toggled: newEvent(),
}
g_signal_connect(
C.gpointer(unsafe.Pointer(c.checkbox)),
"toggled",
C.GCallback(C.checkboxToggled),
C.gpointer(unsafe.Pointer(c)))
return c
}
func (c *checkbox) OnToggled(e func()) {
c.toggled.set(e)
}
func (c *checkbox) Text() string {
return fromgstr(C.gtk_button_get_label(c.button))
}
func (c *checkbox) SetText(text string) {
ctext := togstr(text)
defer freegstr(ctext)
C.gtk_button_set_label(c.button, ctext)
}
func (c *checkbox) Checked() bool {
return fromgbool(C.gtk_toggle_button_get_active(c.toggle))
}
func (c *checkbox) SetChecked(checked bool) {
C.gtk_toggle_button_set_active(c.toggle, togbool(checked))
}
//export checkboxToggled
func checkboxToggled(bwid *C.GtkToggleButton, data C.gpointer) {
c := (*checkbox)(unsafe.Pointer(data))
c.toggled.fire()
}

View File

@ -1,74 +0,0 @@
// 15 july 2014
package ui
import (
"unsafe"
)
// #include "winapi_windows.h"
import "C"
type checkbox struct {
*controlSingleHWNDWithText
toggled *event
}
func newCheckbox(text string) *checkbox {
// don't use BS_AUTOCHECKBOX here because it creates problems when refocusing (see http://blogs.msdn.com/b/oldnewthing/archive/2014/05/22/10527522.aspx)
// we'll handle actually toggling the check state ourselves (see controls_windows.c)
hwnd := C.newControl(buttonclass,
C.BS_CHECKBOX|C.WS_TABSTOP,
0)
c := &checkbox{
controlSingleHWNDWithText: newControlSingleHWNDWithText(hwnd),
toggled: newEvent(),
}
c.fpreferredSize = c.xpreferredSize
c.SetText(text)
C.controlSetControlFont(c.hwnd)
C.setCheckboxSubclass(c.hwnd, unsafe.Pointer(c))
return c
}
func (c *checkbox) OnToggled(e func()) {
c.toggled.set(e)
}
func (c *checkbox) Text() string {
return c.text()
}
func (c *checkbox) SetText(text string) {
c.setText(text)
}
func (c *checkbox) Checked() bool {
return C.checkboxChecked(c.hwnd) != C.FALSE
}
func (c *checkbox) SetChecked(checked bool) {
if checked {
C.checkboxSetChecked(c.hwnd, C.TRUE)
return
}
C.checkboxSetChecked(c.hwnd, C.FALSE)
}
//export checkboxToggled
func checkboxToggled(data unsafe.Pointer) {
c := (*checkbox)(data)
c.toggled.fire()
}
const (
// from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing
checkboxHeight = 10
// from http://msdn.microsoft.com/en-us/library/windows/desktop/bb226818%28v=vs.85%29.aspx
checkboxXFromLeftOfBoxToLeftOfLabel = 12
)
func (c *checkbox) xpreferredSize(d *sizing) (width, height int) {
return fromdlgunitsX(checkboxXFromLeftOfBoxToLeftOfLabel, d) + int(c.textlen),
fromdlgunitsY(checkboxHeight, d)
}

View File

@ -1,135 +0,0 @@
// 17 july 2014
#include "winapi_windows.h"
#include "_cgo_export.h"
LRESULT getWindowTextLen(HWND hwnd)
{
return SendMessageW(hwnd, WM_GETTEXTLENGTH, 0, 0);
}
void getWindowText(HWND hwnd, WPARAM n, LPWSTR buf)
{
SetLastError(0);
if (SendMessageW(hwnd, WM_GETTEXT, n + 1, (LPARAM) buf) != (LRESULT) n)
xpanic("WM_GETTEXT did not copy the correct number of characters out", GetLastError());
}
void setWindowText(HWND hwnd, LPWSTR text)
{
switch (SendMessageW(hwnd, WM_SETTEXT, 0, (LPARAM) text)) {
case FALSE:
xpanic("WM_SETTEXT failed", GetLastError());
}
}
void updateWindow(HWND hwnd)
{
if (UpdateWindow(hwnd) == 0)
xpanic("error calling UpdateWindow()", GetLastError());
}
void *getWindowData(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *lResult)
{
CREATESTRUCTW *cs = (CREATESTRUCTW *) lParam;
void *data;
data = (void *) GetWindowLongPtrW(hwnd, GWLP_USERDATA);
if (data == NULL) {
// the lpParam is available during WM_NCCREATE and WM_CREATE
if (uMsg == WM_NCCREATE)
SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR) (cs->lpCreateParams));
// act as if we're not ready yet, even during WM_NCCREATE (nothing important to the switch statement below happens here anyway)
*lResult = DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
return data;
}
/*
all container windows (including the message-only window, hence this is not in container_windows.c) have to call the sharedWndProc() to ensure messages go in the right place and control colors are handled properly
*/
/*
all controls that have events receive the events themselves through subclasses
to do this, all container windows (including the message-only window; see http://support.microsoft.com/default.aspx?scid=KB;EN-US;Q104069) forward WM_COMMAND to each control with this function, WM_NOTIFY with forwardNotify, etc.
*/
static LRESULT forwardCommand(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
HWND control = (HWND) lParam;
// don't generate an event if the control (if there is one) is unparented (a child of the message-only window)
if (control != NULL && IsChild(msgwin, control) == 0)
return SendMessageW(control, msgCOMMAND, wParam, lParam);
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
static LRESULT forwardNotify(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
NMHDR *nmhdr = (NMHDR *) lParam;
HWND control = nmhdr->hwndFrom;
// don't generate an event if the control (if there is one) is unparented (a child of the message-only window)
if (control != NULL && IsChild(msgwin, control) == 0)
return SendMessageW(control, msgNOTIFY, wParam, lParam);
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
BOOL sharedWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *lResult)
{
switch (uMsg) {
case WM_COMMAND:
*lResult = forwardCommand(hwnd, uMsg, wParam, lParam);
return TRUE;
case WM_NOTIFY:
*lResult = forwardNotify(hwnd, uMsg, wParam, lParam);
return TRUE;
case WM_CTLCOLORSTATIC:
case WM_CTLCOLORBTN:
if (SetBkMode((HDC) wParam, TRANSPARENT) == 0)
xpanic("error setting transparent background mode to Labels", GetLastError());
paintControlBackground((HWND) lParam, (HDC) wParam);
*lResult = (LRESULT) hollowBrush;
return TRUE;
}
return FALSE;
}
void paintControlBackground(HWND hwnd, HDC dc)
{
HWND parent;
RECT r;
POINT p;
int saved;
WCHAR classname[128] = L""; // more than enough to avoid collisions
parent = hwnd;
for (;;) {
parent = GetParent(parent);
if (parent == NULL)
xpanic("error getting parent control of control in paintControlBackground()", GetLastError());
// wine sends these messages early, yay...
if (parent == msgwin)
return;
if (GetClassNameW(parent, classname, 128) == 0)
xpanic("error getting name of focused window class in paintControlBackground()", GetLastError());
// skip container and groupboxes
if (_wcsicmp(classname, containerclass) != 0) // container
if (_wcsicmp(classname, L"button") != 0) // groupbox
break;
}
if (GetWindowRect(hwnd, &r) == 0)
xpanic("error getting control's window rect in paintControlBackground()", GetLastError());
// the above is a window rect; convert to client rect
p.x = r.left;
p.y = r.top;
if (ScreenToClient(parent, &p) == 0)
xpanic("error getting client origin of control in paintControlBackground()", GetLastError());
saved = SaveDC(dc);
if (saved == 0)
xpanic("error saving DC info in paintControlBackground()", GetLastError());
if (SetWindowOrgEx(dc, p.x, p.y, NULL) == 0)
xpanic("error moving window origin in paintControlBackground()", GetLastError());
SendMessageW(parent, WM_PRINTCLIENT, (WPARAM) dc, PRF_CLIENT);
if (RestoreDC(dc, saved) == 0)
xpanic("error restoring DC info in paintControlBackground()", GetLastError());
}

View File

@ -1,26 +0,0 @@
// 25 june 2014
package ui
type sizingbase struct {
xpadding int
ypadding int
}
// The container type, which is defined per-platform, is an internal Control that is only used to house other Controls from the underlying UI toolkit's point of view
/* TODO
func (c *container) resize(x, y, width, height int) {
if c.child == nil { // no children; nothing to do
return
}
d := c.beginResize()
allocations := c.child.allocate(x+d.xmargin, y+d.ymargintop,
width-(2*d.xmargin), height-d.ymargintop-d.ymarginbottom, d)
c.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)
}
}
*/

View File

@ -1,76 +0,0 @@
// 4 august 2014
package ui
import (
"unsafe"
)
// #include "objc_darwin.h"
import "C"
type container struct {
*controlSingleObject
}
type sizing struct {
sizingbase
// for size calculations
// nothing on Mac OS X
// for the actual resizing
neighborAlign C.struct_xalignment
}
func newContainer() *container {
c := new(container)
c.controlSingleObject = newControlSingleObject(C.newContainerView(unsafe.Pointer(c)))
return c
}
func (c *container) parent() *controlParent {
return &controlParent{c.id}
}
func (c *container) allocation(margined bool) C.struct_xrect {
b := C.containerBounds(c.id)
if margined {
b.x += C.intptr_t(macXMargin)
b.y += C.intptr_t(macYMargin)
b.width -= C.intptr_t(macXMargin) * 2
b.height -= C.intptr_t(macYMargin) * 2
}
return b
}
// we can just return these values as is
func (c *container) bounds(d *sizing) (int, int, int, int) {
b := C.containerBounds(c.id)
return int(b.x), int(b.y), int(b.width), int(b.height)
}
// These are based on measurements from Interface Builder.
const (
macXMargin = 20
macYMargin = 20
macXPadding = 8
macYPadding = 8
)
func (w *window) beginResize() (d *sizing) {
d = new(sizing)
d.xpadding = macXPadding
d.ypadding = macYPadding
return d
}
/*TODO
func (c *container) 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
}
}
*/

View File

@ -1,58 +0,0 @@
// 4 august 2014
#include "objc_darwin.h"
#include "_cgo_export.h"
#include <Cocoa/Cocoa.h>
#define toNSView(x) ((NSView *) (x))
// calling -[className] on the content views of NSWindow, NSTabItem, and NSBox all return NSView, so I'm assuming I just need to override these
// fornunately:
// - NSWindow resizing calls -[setFrameSize:] (but not -[setFrame:])
// - NSTab resizing calls both -[setFrame:] and -[setFrameSIze:] on the current tab
// - NSTab switching tabs calls both -[setFrame:] and -[setFrameSize:] on the new tab
// so we just override setFrameSize:
// thanks to mikeash and JtRip in irc.freenode.net/#macdev
@interface goContainerView : NSView {
@public
void *gocontainer;
}
@end
@implementation goContainerView
@end
id newContainerView(void *gocontainer)
{
goContainerView *c;
c = [[goContainerView alloc] initWithFrame:NSZeroRect];
c->gocontainer = gocontainer;
return (id) c;
}
void moveControl(id c, intptr_t x, intptr_t y, intptr_t width, intptr_t height)
{
NSView *v;
NSRect frame;
frame = NSMakeRect((CGFloat) x, (CGFloat) y, (CGFloat) width, (CGFloat) height);
// mac os x coordinate system has (0,0) in the lower-left
v = toNSView(c);
frame.origin.y = ([[v superview] bounds].size.height - frame.size.height) - frame.origin.y;
[v setFrame:frame];
}
struct xrect containerBounds(id view)
{
NSRect b;
struct xrect r;
b = [toNSView(view) bounds];
r.x = (intptr_t) b.origin.x;
r.y = (intptr_t) b.origin.y;
r.width = (intptr_t) b.size.width;
r.height = (intptr_t) b.size.height;
return r;
}

View File

@ -1,102 +0,0 @@
// +build !windows,!darwin
// 13 august 2014
#include "gtk_unix.h"
#include "_cgo_export.h"
#define GOCONTAINER_TYPE (goContainer_get_type())
#define GOCONTAINER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GOCONTAINER_TYPE, goContainer))
#define IS_GOCONTAINER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GOCONTAINER_TYPE))
#define GOCONTAINER_CLASS(class) (G_TYPE_CHECK_CLASS_CAST((class), GOCONTAINER_TYPE, goContainerClass))
#define IS_GOCONTAINER_CLASS(class) (G_TYPE_CHECK_CLASS_TYPE((class), GOCONTAINER_TYPE))
#define GOCONTAINER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GOCONTAINER_TYPE, goContainerClass))
typedef struct goContainer goContainer;
typedef struct goContainerClass goContainerClass;
struct goContainer {
GtkContainer parent_instance;
void *gocontainer;
GPtrArray *children; // for forall()
};
struct goContainerClass {
GtkContainerClass parent_class;
};
G_DEFINE_TYPE(goContainer, goContainer, GTK_TYPE_CONTAINER)
static void goContainer_init(goContainer *c)
{
c->children = g_ptr_array_new();
gtk_widget_set_has_window(GTK_WIDGET(c), FALSE);
}
static void goContainer_dispose(GObject *obj)
{
g_ptr_array_unref(GOCONTAINER(obj)->children);
G_OBJECT_CLASS(goContainer_parent_class)->dispose(obj);
}
static void goContainer_finalize(GObject *obj)
{
G_OBJECT_CLASS(goContainer_parent_class)->finalize(obj);
}
static void goContainer_add(GtkContainer *container, GtkWidget *widget)
{
gtk_widget_set_parent(widget, GTK_WIDGET(container));
g_ptr_array_add(GOCONTAINER(container)->children, widget);
}
static void goContainer_remove(GtkContainer *container, GtkWidget *widget)
{
gtk_widget_unparent(widget);
g_ptr_array_remove(GOCONTAINER(container)->children, widget);
}
static void goContainer_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
{
gtk_widget_set_allocation(widget, allocation);
}
struct forall {
GtkCallback callback;
gpointer data;
};
static void doforall(gpointer obj, gpointer data)
{
struct forall *s = (struct forall *) data;
(*(s->callback))(GTK_WIDGET(obj), s->data);
}
static void goContainer_forall(GtkContainer *container, gboolean includeInternals, GtkCallback callback, gpointer data)
{
struct forall s;
s.callback = callback;
s.data = data;
g_ptr_array_foreach(GOCONTAINER(container)->children, doforall, &s);
}
static void goContainer_class_init(goContainerClass *class)
{
G_OBJECT_CLASS(class)->dispose = goContainer_dispose;
G_OBJECT_CLASS(class)->finalize = goContainer_finalize;
GTK_WIDGET_CLASS(class)->size_allocate = goContainer_size_allocate;
GTK_CONTAINER_CLASS(class)->add = goContainer_add;
GTK_CONTAINER_CLASS(class)->remove = goContainer_remove;
GTK_CONTAINER_CLASS(class)->forall = goContainer_forall;
}
GtkWidget *newContainer(void *gocontainer)
{
goContainer *c;
c = (goContainer *) g_object_new(GOCONTAINER_TYPE, NULL);
c->gocontainer = gocontainer;
return GTK_WIDGET(c);
}

View File

@ -1,74 +0,0 @@
// +build !windows,!darwin
// 23 february 2014
package ui
import (
"unsafe"
)
// #include "gtk_unix.h"
import "C"
type container struct {
*controlSingleWidget
container *C.GtkContainer
}
type sizing struct {
sizingbase
// for size calculations
// gtk+ needs nothing
// for the actual resizing
// gtk+ needs nothing
}
func newContainer() *container {
c := new(container)
c.controlSingleWidget = newControlSingleWidget(C.newContainer(unsafe.Pointer(c)))
c.container = (*C.GtkContainer)(unsafe.Pointer(c.widget))
return c
}
func (c *container) parent() *controlParent {
return &controlParent{c.container}
}
func (c *container) allocation(margined bool) C.GtkAllocation {
var a C.GtkAllocation
C.gtk_widget_get_allocation(c.widget, &a)
if margined {
a.x += C.int(gtkXMargin)
a.y += C.int(gtkYMargin)
a.width -= C.int(gtkXMargin) * 2
a.height -= C.int(gtkYMargin) * 2
}
return a
}
// we can just return these values as is
// note that allocations of a widget on GTK+ have their origin in the /window/ origin
func (c *container) bounds(d *sizing) (int, int, int, int) {
var a C.GtkAllocation
C.gtk_widget_get_allocation(c.widget, &a)
return int(a.x), int(a.y), int(a.width), int(a.height)
}
const (
gtkXMargin = 12
gtkYMargin = 12
gtkXPadding = 12
gtkYPadding = 6
)
func (w *window) beginResize() (d *sizing) {
d = new(sizing)
d.xpadding = gtkXPadding
d.ypadding = gtkYPadding
return d
}

View File

@ -1,93 +0,0 @@
// 17 july 2014
#include "winapi_windows.h"
#include "_cgo_export.h"
/*
This could all just be part of Window, but doing so just makes things complex.
In this case, I chose to waste a window handle rather than keep things super complex.
If this is seriously an issue in the future, I can roll it back.
*/
static LRESULT CALLBACK containerWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LRESULT lResult;
if (sharedWndProc(hwnd, uMsg, wParam, lParam, &lResult))
return lResult;
switch (uMsg) {
default:
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
xmissedmsg("container", "containerWndProc()", uMsg);
return 0; // unreached
}
DWORD makeContainerWindowClass(char **errmsg)
{
WNDCLASSW wc;
ZeroMemory(&wc, sizeof (WNDCLASSW));
wc.lpfnWndProc = containerWndProc;
wc.hInstance = hInstance;
wc.hIcon = hDefaultIcon;
wc.hCursor = hArrowCursor;
wc.hbrBackground = NULL;//(HBRUSH) (COLOR_BTNFACE + 1);
wc.lpszClassName = containerclass;
if (RegisterClassW(&wc) == 0) {
*errmsg = "error registering container window class";
return GetLastError();
}
return 0;
}
HWND newContainer(void)
{
HWND hwnd;
hwnd = CreateWindowExW(
WS_EX_CONTROLPARENT | WS_EX_TRANSPARENT,
containerclass, L"",
WS_CHILD | WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT,
100, 100,
msgwin, NULL, hInstance, NULL);
if (hwnd == NULL)
xpanic("container creation failed", GetLastError());
return hwnd;
}
RECT containerBounds(HWND hwnd)
{
RECT r;
if (GetClientRect(hwnd, &r) == 0)
xpanic("error getting container client rect for container.bounds()", GetLastError());
return r;
}
void calculateBaseUnits(HWND hwnd, int *baseX, int *baseY, LONG *internalLeading)
{
HDC dc;
HFONT prevFont;
TEXTMETRICW tm;
SIZE size;
dc = GetDC(hwnd);
if (dc == NULL)
xpanic("error getting DC for preferred size calculations", GetLastError());
prevFont = (HFONT) SelectObject(dc, controlFont);
if (prevFont == NULL)
xpanic("error loading control font into device context for preferred size calculation", GetLastError());
if (GetTextMetricsW(dc, &tm) == 0)
xpanic("error getting text metrics for preferred size calculations", GetLastError());
if (GetTextExtentPoint32W(dc, L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 52, &size) == 0)
xpanic("error getting text extent point for preferred size calculations", GetLastError());
*baseX = (int) ((size.cx / 26 + 1) / 2);
*baseY = (int) tm.tmHeight;
*internalLeading = tm.tmInternalLeading;
if (SelectObject(dc, prevFont) != controlFont)
xpanic("error restoring previous font into device context after preferred size calculations", GetLastError());
if (ReleaseDC(hwnd, dc) == 0)
xpanic("error releasing DC for preferred size calculations", GetLastError());
}

View File

@ -1,114 +0,0 @@
// 4 august 2014
package ui
import (
"fmt"
"syscall"
)
// #include "winapi_windows.h"
import "C"
type container struct {
*controlSingleHWND
}
type sizing struct {
sizingbase
// for size calculations
baseX C.int
baseY C.int
internalLeading C.LONG // for Label; see Label.commitResize() for details
// for the actual resizing
// possibly the HDWP
}
func makeContainerWindowClass() error {
var errmsg *C.char
err := C.makeContainerWindowClass(&errmsg)
if err != 0 || errmsg != nil {
return fmt.Errorf("%s: %v", C.GoString(errmsg), syscall.Errno(err))
}
return nil
}
func newContainer() *container {
// don't set preferredSize(); it should never be called
return &container{
controlSingleHWND: newControlSingleHWND(C.newContainer()),
}
}
// TODO merge with controlSingleHWND
func (c *container) show() {
C.ShowWindow(c.hwnd, C.SW_SHOW)
}
// TODO merge with controlSingleHWND
func (c *container) hide() {
C.ShowWindow(c.hwnd, C.SW_HIDE)
}
func (c *container) parent() *controlParent {
return &controlParent{c.hwnd}
}
func (c *container) bounds(d *sizing) (int, int, int, int) {
r := C.containerBounds(c.hwnd)
return int(r.left), int(r.top), int(r.right - r.left), int(r.bottom - r.top)
}
// 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/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)
// In my tests (see https://github.com/andlabs/windlgunits), the GetTextExtentPoint32() option for getting the base X produces much more accurate results than the tmAveCharWidth option when tested against the sample values given in http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing, but can be off by a pixel in either direction (probably due to rounding errors).
// note on MulDiv():
// div will not be 0 in the usages below
// we also ignore overflow; that isn't likely to happen for our use case anytime soon
func fromdlgunitsX(du int, d *sizing) int {
return int(C.MulDiv(C.int(du), d.baseX, 4))
}
func fromdlgunitsY(du int, d *sizing) int {
return int(C.MulDiv(C.int(du), d.baseY, 8))
}
const (
// shared by multiple containers
marginDialogUnits = 7
paddingDialogUnits = 4
)
func (w *window) beginResize() (d *sizing) {
var baseX, baseY C.int
var internalLeading C.LONG
d = new(sizing)
C.calculateBaseUnits(w.hwnd, &baseX, &baseY, &internalLeading)
d.baseX = baseX
d.baseY = baseY
d.internalLeading = internalLeading
d.xpadding = fromdlgunitsX(paddingDialogUnits, d)
d.ypadding = fromdlgunitsY(paddingDialogUnits, d)
return d
}
func marginRectDLU(r *C.RECT, top int, bottom int, left int, right int, d *sizing) {
r.left += C.LONG(fromdlgunitsX(left, d))
r.top += C.LONG(fromdlgunitsY(top, d))
r.right -= C.LONG(fromdlgunitsX(right, d))
r.bottom -= C.LONG(fromdlgunitsY(bottom, d))
}

View File

@ -1,36 +0,0 @@
// 30 july 2014
package ui
// Control represents a control.
type Control interface {
setParent(p *controlParent) // controlParent defined per-platform
preferredSize(d *sizing) (width, height int)
resize(x int, y int, width int, height int, d *sizing)
nTabStops() int // used by the Windows backend
}
type controlbase struct {
fsetParent func(p *controlParent)
fpreferredSize func(d *sizing) (width, height int)
fresize func(x int, y int, width int, height int, d *sizing)
fnTabStops func() int
}
// children should not use the same name as these, otherwise weird things will happen
func (c *controlbase) setParent(p *controlParent) {
c.fsetParent(p)
}
func (c *controlbase) preferredSize(d *sizing) (width, height int) {
return c.fpreferredSize(d)
}
func (c *controlbase) resize(x int, y int, width int, height int, d *sizing) {
c.fresize(x, y, width, height, d)
}
func (c *controlbase) nTabStops() int {
return c.fnTabStops()
}

View File

@ -1,56 +0,0 @@
// 30 july 2014
package ui
// #include "objc_darwin.h"
import "C"
type controlParent struct {
id C.id
}
type controlSingleObject struct {
*controlbase
id C.id
}
func newControlSingleObject(id C.id) *controlSingleObject {
c := new(controlSingleObject)
c.controlbase = &controlbase{
fsetParent: c.xsetParent,
fpreferredSize: c.xpreferredSize,
fresize: c.xresize,
}
c.id = id
return c
}
func (c *controlSingleObject) xsetParent(p *controlParent) {
// redrawing the new window handled by C.parent()
C.parent(c.id, p.id)
}
func (c *controlSingleObject) xpreferredSize(d *sizing) (int, int) {
s := C.controlPreferredSize(c.id)
return int(s.width), int(s.height)
}
func (c *controlSingleObject) xresize(x int, y int, width int, height int, d *sizing) {
C.moveControl(c.id, C.intptr_t(x), C.intptr_t(y), C.intptr_t(width), C.intptr_t(height))
}
type scroller struct {
*controlSingleObject
scroller *controlSingleObject
}
func newScroller(child C.id, bordered bool) *scroller {
sid := C.newScrollView(child, toBOOL(bordered))
s := &scroller{
controlSingleObject: newControlSingleObject(child),
scroller: newControlSingleObject(sid),
}
s.fsetParent = s.scroller.fsetParent
s.fresize = s .scroller.fresize
return s
}

View File

@ -1,121 +0,0 @@
// +build !windows,!darwin
// 30 july 2014
package ui
import (
"unsafe"
)
// #include "gtk_unix.h"
import "C"
type controlParent struct {
c *C.GtkContainer
}
type controlSingleWidget struct {
*controlbase
widget *C.GtkWidget
}
func newControlSingleWidget(widget *C.GtkWidget) *controlSingleWidget {
c := new(controlSingleWidget)
c.controlbase = &controlbase{
fsetParent: c.xsetParent,
fpreferredSize: c.xpreferredSize,
fresize: c.xresize,
}
c.widget = widget
return c
}
func (c *controlSingleWidget) xsetParent(p *controlParent) {
C.gtk_container_add(p.c, c.widget)
// make sure the new widget is shown if not explicitly hidden
// TODO why did I have this again?
C.gtk_widget_show_all(c.widget)
}
func (c *controlSingleWidget) xpreferredSize(d *sizing) (int, int) {
// 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.
var r C.GtkRequisition
C.gtk_widget_get_preferred_size(c.widget, nil, &r)
return int(r.width), int(r.height)
}
func (c *controlSingleWidget) xresize(x int, y int, width int, height int, d *sizing) {
// as we resize on size-allocate, we have to also use size-allocate on our children
// this is fine anyway; in fact, this allows us to move without knowing what the container is!
// this is what GtkBox does anyway
// thanks to tristan in irc.gimp.net/#gtk+
var r C.GtkAllocation
r.x = C.int(x)
r.y = C.int(y)
r.width = C.int(width)
r.height = C.int(height)
C.gtk_widget_size_allocate(c.widget, &r)
}
type scroller struct {
*controlSingleWidget
scroller *controlSingleWidget
scrollwidget *C.GtkWidget
scrollcontainer *C.GtkContainer
scrollwindow *C.GtkScrolledWindow
overlay *controlSingleWidget
overlaywidget *C.GtkWidget
overlaycontainer *C.GtkContainer
overlayoverlay *C.GtkOverlay
}
func newScroller(widget *C.GtkWidget, native bool, bordered bool, overlay bool) *scroller {
s := new(scroller)
s.controlSingleWidget = newControlSingleWidget(widget)
s.scrollwidget = C.gtk_scrolled_window_new(nil, nil)
s.scrollcontainer = (*C.GtkContainer)(unsafe.Pointer(s.scrollwidget))
s.scrollwindow = (*C.GtkScrolledWindow)(unsafe.Pointer(s.scrollwidget))
// any actual changing operations need to be done to the GtkScrolledWindow
// that is, everything /except/ preferredSize() are done to the GtkScrolledWindow
s.scroller = newControlSingleWidget(s.scrollwidget)
s.fsetParent = s.scroller.fsetParent
s.fresize = s.scroller.fresize
// in GTK+ 3.4 we still technically need to use the separate gtk_scrolled_window_add_with_viewpoint()/gtk_container_add() spiel for adding the widget to the scrolled window
if native {
C.gtk_container_add(s.scrollcontainer, s.widget)
} else {
C.gtk_scrolled_window_add_with_viewport(s.scrollwindow, s.widget)
}
// give the scrolled window a border (thanks to jlindgren in irc.gimp.net/#gtk+)
if bordered {
C.gtk_scrolled_window_set_shadow_type(s.scrollwindow, C.GTK_SHADOW_IN)
}
if overlay {
// ok things get REALLY fun now
// we now have to do all of the above again
s.overlaywidget = C.gtk_overlay_new()
s.overlaycontainer = (*C.GtkContainer)(unsafe.Pointer(s.overlaywidget))
s.overlayoverlay = (*C.GtkOverlay)(unsafe.Pointer(s.overlaywidget))
s.overlay = newControlSingleWidget(s.overlaywidget)
s.fsetParent = s.overlay.fsetParent
s.fresize = s.overlay.fresize
C.gtk_container_add(s.overlaycontainer, s.scrollwidget)
}
return s
}

View File

@ -1,63 +0,0 @@
// 30 july 2014
package ui
// #include "winapi_windows.h"
import "C"
type controlParent struct {
hwnd C.HWND
}
// don't specify preferredSize in any of these; they're per-control
type controlSingleHWND struct {
*controlbase
hwnd C.HWND
}
func newControlSingleHWND(hwnd C.HWND) *controlSingleHWND {
c := new(controlSingleHWND)
c.controlbase = &controlbase{
fsetParent: c.xsetParent,
fresize: c.xresize,
fnTabStops: func() int {
// most controls count as one tab stop
return 1
},
}
c.hwnd = hwnd
return c
}
func (c *controlSingleHWND) xsetParent(p *controlParent) {
C.controlSetParent(c.hwnd, p.hwnd)
}
func (c *controlSingleHWND) xresize(x int, y int, width int, height int, d *sizing) {
C.moveWindow(c.hwnd, C.int(x), C.int(y), C.int(width), C.int(height))
}
// these are provided for convenience
type controlSingleHWNDWithText struct {
*controlSingleHWND
textlen C.LONG
}
func newControlSingleHWNDWithText(h C.HWND) *controlSingleHWNDWithText {
return &controlSingleHWNDWithText{
controlSingleHWND: newControlSingleHWND(h),
}
}
// TODO export these instead of requiring dummy declarations in each implementation
func (c *controlSingleHWNDWithText) text() string {
return getWindowText(c.hwnd)
}
func (c *controlSingleHWNDWithText) setText(text string) {
t := toUTF16(text)
C.setWindowText(c.hwnd, t)
c.textlen = C.controlTextLength(c.hwnd, t)
}

View File

@ -1,465 +0,0 @@
// 31 august 2014
package ui
import (
"fmt"
)
// Grid is a Control that arranges other Controls in a grid.
// Grid is a very powerful container: it can position and size each Control in several ways and can (and must) have Controls added to it at any time, in any direction.
// it can also have Controls spanning multiple rows and columns.
//
// Each Control in a Grid has associated "expansion" and "alignment" values in both the X and Y direction.
// Expansion determines whether all cells in the same row/column are given whatever space is left over after figuring out how big the rest of the Grid should be.
// Alignment determines the position of a Control relative to its cell after computing the above.
// The special alignment Fill can be used to grow a Control to fit its cell.
// Note that expansion and alignment are independent variables.
// For more information on expansion and alignment, read https://developer.gnome.org/gtk3/unstable/ch28s02.html.
type Grid interface {
Control
// Add adds a Control to the Grid.
// If this is the first Control in the Grid, it is merely added; nextTo should be nil.
// Otherwise, it is placed relative to nextTo.
// If nextTo is nil, it is placed next to the previously added Control.
// The effect of adding the same Control multiple times is undefined, as is the effect of adding a Control next to one not present in the Grid.
// The effect of overlapping spanning Controls is also undefined.
// Add panics if either xspan or yspan are zero or negative.
Add(control Control, nextTo Control, side Side, xexpand bool, xalign Align, yexpand bool, yalign Align, xspan int, yspan int)
// Padded and SetPadded get and set whether the controls of the Grid have padding between them.
// The size of the padding is platform-dependent.
Padded() bool
SetPadded(padded bool)
}
// Align represents the alignment of a Control in its cell of a Grid.
type Align uint
const (
LeftTop Align = iota
Center
RightBottom
Fill
)
// Side represents a side of a Control to add other Controls to a Grid to.
type Side uint
const (
West Side = iota
East
North
South
nSides
)
type grid struct {
controls []gridCell
indexof map[Control]int
prev int
container *container
padded bool
xmax int
ymax int
}
type gridCell struct {
control Control
xexpand bool
xalign Align
yexpand bool
yalign Align
xspan int
yspan int
x int
y int
finalx int
finaly int
finalwidth int
finalheight int
prefwidth int
prefheight int
}
// NewGrid creates a new Grid with no Controls.
func NewGrid() Grid {
return &grid{
indexof: map[Control]int{},
container: newContainer(),
}
}
// ensures that all (x, y) pairs are 0-based
// also computes g.xmax/g.ymax
func (g *grid) reorigin() {
xmin := 0
ymin := 0
for i := range g.controls {
if g.controls[i].x < xmin {
xmin = g.controls[i].x
}
if g.controls[i].y < ymin {
ymin = g.controls[i].y
}
}
xmin = -xmin
ymin = -ymin
g.xmax = 0
g.ymax = 0
for i := range g.controls {
g.controls[i].x += xmin
g.controls[i].y += ymin
if g.xmax < g.controls[i].x+g.controls[i].xspan {
g.xmax = g.controls[i].x + g.controls[i].xspan
}
if g.ymax < g.controls[i].y+g.controls[i].yspan {
g.ymax = g.controls[i].y + g.controls[i].yspan
}
}
}
func (g *grid) Add(control Control, nextTo Control, side Side, xexpand bool, xalign Align, yexpand bool, yalign Align, xspan int, yspan int) {
if xspan <= 0 || yspan <= 0 {
panic(fmt.Errorf("invalid span %dx%d given to Grid.Add()", xspan, yspan))
}
cell := gridCell{
control: control,
xexpand: xexpand,
xalign: xalign,
yexpand: yexpand,
yalign: yalign,
xspan: xspan,
yspan: yspan,
}
control.setParent(g.container.parent())
// if this is the first control, just add it in directly
if len(g.controls) != 0 {
next := g.prev
if nextTo != nil {
next = g.indexof[nextTo]
}
switch side {
case West:
cell.x = g.controls[next].x - cell.xspan
cell.y = g.controls[next].y
case North:
cell.x = g.controls[next].x
cell.y = g.controls[next].y - cell.yspan
case East:
cell.x = g.controls[next].x + g.controls[next].xspan
cell.y = g.controls[next].y
case South:
cell.x = g.controls[next].x
cell.y = g.controls[next].y + g.controls[next].yspan
default:
panic(fmt.Errorf("invalid side %d in Grid.Add()", side))
}
}
g.controls = append(g.controls, cell)
g.prev = len(g.controls) - 1
g.indexof[control] = g.prev
g.reorigin()
}
func (g *grid) Padded() bool {
return g.padded
}
func (g *grid) SetPadded(padded bool) {
g.padded = padded
}
func (g *grid) setParent(p *controlParent) {
g.container.setParent(p)
}
// builds the topological cell grid; also makes colwidths and rowheights
func (g *grid) mkgrid() (gg [][]int, colwidths []int, rowheights []int) {
gg = make([][]int, g.ymax)
for y := 0; y < g.ymax; y++ {
gg[y] = make([]int, g.xmax)
for x := 0; x < g.xmax; x++ {
gg[y][x] = -1
}
}
for i := range g.controls {
for y := g.controls[i].y; y < g.controls[i].y+g.controls[i].yspan; y++ {
for x := g.controls[i].x; x < g.controls[i].x+g.controls[i].xspan; x++ {
gg[y][x] = i
}
}
}
return gg, make([]int, g.xmax), make([]int, g.ymax)
}
func (g *grid) resize(x int, y int, width int, height int, d *sizing) {
g.container.resize(x, y, width, height, d)
if len(g.controls) == 0 {
// nothing to do
return
}
x, y, width, height = g.container.bounds(d)
// -2) get this Grid's padding
xpadding := d.xpadding
ypadding := d.ypadding
if !g.padded {
xpadding = 0
ypadding = 0
}
// -1) discount padding from width/height
width -= (g.xmax - 1) * xpadding
height -= (g.ymax - 1) * ypadding
// 0) build necessary data structures
gg, colwidths, rowheights := g.mkgrid()
xexpand := make([]bool, g.xmax)
yexpand := make([]bool, g.ymax)
// 1) compute colwidths and rowheights before handling expansion
// we only count non-spanning controls to avoid weirdness
for y := 0; y < len(gg); y++ {
for x := 0; x < len(gg[y]); x++ {
i := gg[y][x]
if i == -1 {
continue
}
w, h := g.controls[i].control.preferredSize(d)
if g.controls[i].xspan == 1 {
if colwidths[x] < w {
colwidths[x] = w
}
}
if g.controls[i].yspan == 1 {
if rowheights[y] < h {
rowheights[y] = h
}
}
// save these for step 6
g.controls[i].prefwidth = w
g.controls[i].prefheight = h
}
}
// 2) figure out which rows/columns expand but not span
// we need to know which expanding rows/columns don't span before we can handle the ones that do
for i := range g.controls {
if g.controls[i].xexpand && g.controls[i].xspan == 1 {
xexpand[g.controls[i].x] = true
}
if g.controls[i].yexpand && g.controls[i].yspan == 1 {
yexpand[g.controls[i].y] = true
}
}
// 2) figure out which rows/columns expand that do span
// the way we handle this is simple: if none of the spanned rows/columns expand, make all rows/columns expand
for i := range g.controls {
if g.controls[i].xexpand && g.controls[i].xspan != 1 {
do := true
for x := g.controls[i].x; x < g.controls[i].x+g.controls[i].xspan; x++ {
if xexpand[x] {
do = false
break
}
}
if do {
for x := g.controls[i].x; x < g.controls[i].x+g.controls[i].xspan; x++ {
xexpand[x] = true
}
}
}
if g.controls[i].yexpand && g.controls[i].yspan != 1 {
do := true
for y := g.controls[i].y; y < g.controls[i].y+g.controls[i].yspan; y++ {
if yexpand[y] {
do = false
break
}
}
if do {
for y := g.controls[i].y; y < g.controls[i].y+g.controls[i].yspan; y++ {
yexpand[y] = true
}
}
}
}
// 4) compute and assign expanded widths/heights
nxexpand := 0
nyexpand := 0
for x, expand := range xexpand {
if expand {
nxexpand++
} else {
width -= colwidths[x]
}
}
for y, expand := range yexpand {
if expand {
nyexpand++
} else {
height -= rowheights[y]
}
}
for x, expand := range xexpand {
if expand {
colwidths[x] = width / nxexpand
}
}
for y, expand := range yexpand {
if expand {
rowheights[y] = height / nyexpand
}
}
// 5) reset the final coordinates for the next step
for i := range g.controls {
g.controls[i].finalx = 0
g.controls[i].finaly = 0
g.controls[i].finalwidth = 0
g.controls[i].finalheight = 0
}
// 6) compute cell positions and sizes
for y := 0; y < g.ymax; y++ {
curx := 0
prev := -1
for x := 0; x < g.xmax; x++ {
i := gg[y][x]
if i != -1 && y == g.controls[i].y { // don't repeat this step if the control spans vertically
if i != prev {
g.controls[i].finalx = curx
} else {
g.controls[i].finalwidth += xpadding
}
g.controls[i].finalwidth += colwidths[x]
}
curx += colwidths[x] + xpadding
prev = i
}
}
for x := 0; x < g.xmax; x++ {
cury := 0
prev := -1
for y := 0; y < g.ymax; y++ {
i := gg[y][x]
if i != -1 && x == g.controls[i].x { // don't repeat this step if the control spans horizontally
if i != prev {
g.controls[i].finaly = cury
} else {
g.controls[i].finalheight += ypadding
}
g.controls[i].finalheight += rowheights[y]
}
cury += rowheights[y] + ypadding
prev = i
}
}
// 7) everything as it stands now is set for xalign == Fill yalign == Fill; set the correct alignments
// this is why we saved prefwidth/prefheight above
for i := range g.controls {
if g.controls[i].xalign != Fill {
switch g.controls[i].xalign {
case RightBottom:
g.controls[i].finalx += g.controls[i].finalwidth - g.controls[i].prefwidth
case Center:
g.controls[i].finalx += (g.controls[i].finalwidth - g.controls[i].prefwidth) / 2
}
g.controls[i].finalwidth = g.controls[i].prefwidth // for all three
}
if g.controls[i].yalign != Fill {
switch g.controls[i].yalign {
case RightBottom:
g.controls[i].finaly += g.controls[i].finalheight - g.controls[i].prefheight
case Center:
g.controls[i].finaly += (g.controls[i].finalheight - g.controls[i].prefheight) / 2
}
g.controls[i].finalheight = g.controls[i].prefheight // for all three
}
}
// 8) and FINALLY we draw
for _, ycol := range gg {
for _, i := range ycol {
if i != -1 { // treat empty cells like spaces
g.controls[i].control.resize(
g.controls[i].finalx+x, g.controls[i].finaly+y,
g.controls[i].finalwidth, g.controls[i].finalheight, d)
}
}
}
return
}
func (g *grid) preferredSize(d *sizing) (width, height int) {
if len(g.controls) == 0 {
// nothing to do
return 0, 0
}
// -1) get this Grid's padding
xpadding := d.xpadding
ypadding := d.ypadding
if !g.padded {
xpadding = 0
ypadding = 0
}
// 0) build necessary data structures
gg, colwidths, rowheights := g.mkgrid()
// 1) compute colwidths and rowheights before handling expansion
// TODO put this in its own function (but careful about the spanning calculation in allocate())
for y := 0; y < len(gg); y++ {
for x := 0; x < len(gg[y]); x++ {
i := gg[y][x]
if i == -1 {
continue
}
w, h := g.controls[i].control.preferredSize(d)
// allot equal space in the presence of spanning to keep things sane
if colwidths[x] < w/g.controls[i].xspan {
colwidths[x] = w / g.controls[i].xspan
}
if rowheights[y] < h/g.controls[i].yspan {
rowheights[y] = h / g.controls[i].yspan
}
// save these for step 6
g.controls[i].prefwidth = w
g.controls[i].prefheight = h
}
}
// 2) compute total column width/row height
colwidth := 0
rowheight := 0
for _, w := range colwidths {
colwidth += w
}
for _, h := range rowheights {
rowheight += h
}
// and that's it; just account for padding
return colwidth + (g.xmax-1) * xpadding,
rowheight + (g.ymax-1) * ypadding
}
func (g *grid) nTabStops() int {
n := 0
for _, c := range g.controls {
n += c.control.nTabStops()
}
return n
}

View File

@ -1,60 +0,0 @@
// 16 august 2014
package ui
import (
"unsafe"
)
// #include "objc_darwin.h"
import "C"
type group struct {
*controlSingleObject
child Control
container *container
margined bool
chainresize func(x int, y int, width int, height int, d *sizing)
}
func newGroup(text string, control Control) Group {
g := new(group)
g.container = newContainer()
g.controlSingleObject = newControlSingleObject(C.newGroup(g.container.id))
g.child = control
g.child.setParent(g.container.parent())
g.SetText(text)
g.chainresize = g.fresize
g.fresize = g.xresize
return g
}
func (g *group) Text() string {
return C.GoString(C.groupText(g.id))
}
func (g *group) SetText(text string) {
ctext := C.CString(text)
defer C.free(unsafe.Pointer(ctext))
C.groupSetText(g.id, ctext)
}
func (g *group) Margined() bool {
return g.margined
}
func (g *group) SetMargined(margined bool) {
g.margined = margined
}
func (g *group) xresize(x int, y int, width int, height int, d *sizing) {
// first, chain up to change the GtkFrame and its child container
g.chainresize(x, y, width, height, d)
// now that the container has the correct size, we can resize the child
a := g.container.allocation(g.margined)
g.child.resize(int(a.x), int(a.y), int(a.width), int(a.height), d)
}

View File

@ -1,90 +0,0 @@
// +build !windows,!darwin
// 15 august 2014
package ui
import (
"unsafe"
)
// #include "gtk_unix.h"
import "C"
type group struct {
*controlSingleWidget
gcontainer *C.GtkContainer
frame *C.GtkFrame
child Control
container *container
margined bool
chainresize func(x int, y int, width int, height int, d *sizing)
}
func newGroup(text string, control Control) Group {
ctext := togstr(text)
defer freegstr(ctext)
widget := C.gtk_frame_new(ctext)
g := &group{
controlSingleWidget: newControlSingleWidget(widget),
gcontainer: (*C.GtkContainer)(unsafe.Pointer(widget)),
frame: (*C.GtkFrame)(unsafe.Pointer(widget)),
child: control,
}
// with GTK+, groupboxes by default have frames and slightly x-offset regular text
// they should have no frame and fully left-justified, bold text
var yalign C.gfloat
// preserve default y-alignment
C.gtk_frame_get_label_align(g.frame, nil, &yalign)
C.gtk_frame_set_label_align(g.frame, 0, yalign)
C.gtk_frame_set_shadow_type(g.frame, C.GTK_SHADOW_NONE)
label := (*C.GtkLabel)(unsafe.Pointer(C.gtk_frame_get_label_widget(g.frame)))
// this is the boldness level used by GtkPrintUnixDialog
// (it technically uses "bold" but see pango's pango-enum-types.c for the name conversion; GType is weird)
bold := C.pango_attr_weight_new(C.PANGO_WEIGHT_BOLD)
boldlist := C.pango_attr_list_new()
C.pango_attr_list_insert(boldlist, bold)
C.gtk_label_set_attributes(label, boldlist)
C.pango_attr_list_unref(boldlist) // thanks baedert in irc.gimp.net/#gtk+
g.container = newContainer()
g.child.setParent(g.container.parent())
g.container.setParent(&controlParent{g.gcontainer})
g.chainresize = g.fresize
g.fresize = g.xresize
return g
}
func (g *group) Text() string {
return fromgstr(C.gtk_frame_get_label(g.frame))
}
func (g *group) SetText(text string) {
ctext := togstr(text)
defer freegstr(ctext)
C.gtk_frame_set_label(g.frame, ctext)
}
func (g *group) Margined() bool {
return g.margined
}
func (g *group) SetMargined(margined bool) {
g.margined = margined
}
func (g *group) xresize(x int, y int, width int, height int, d *sizing) {
// first, chain up to change the GtkFrame and its child container
g.chainresize(x, y, width, height, d)
// now that the container has the correct size, we can resize the child
a := g.container.allocation(g.margined)
g.child.resize(int(a.x), int(a.y), int(a.width), int(a.height), d)
}

View File

@ -1,98 +0,0 @@
// 15 august 2014
package ui
// #include "winapi_windows.h"
import "C"
type group struct {
*controlSingleHWNDWithText
child Control
margined bool
chainresize func(x int, y int, width int, height int, d *sizing)
}
func newGroup(text string, control Control) Group {
hwnd := C.newControl(buttonclass,
C.BS_GROUPBOX,
C.WS_EX_CONTROLPARENT)
g := &group{
controlSingleHWNDWithText: newControlSingleHWNDWithText(hwnd),
child: control,
}
g.fpreferredSize = g.xpreferredSize
g.chainresize = g.fresize
g.fresize = g.xresize
g.fnTabStops = control.nTabStops // groupbox itself is not tabbable but the contents might be
g.SetText(text)
C.controlSetControlFont(g.hwnd)
control.setParent(&controlParent{g.hwnd})
return g
}
func (g *group) Text() string {
return g.text()
}
func (g *group) SetText(text string) {
g.setText(text)
}
func (g *group) Margined() bool {
return g.margined
}
func (g *group) SetMargined(margined bool) {
g.margined = margined
}
const (
groupXMargin = 6
groupYMarginTop = 11 // note this value /includes the groupbox label/
groupYMarginBottom = 7
)
func (g *group) xpreferredSize(d *sizing) (width, height int) {
var r C.RECT
width, height = g.child.preferredSize(d)
if width < int(g.textlen) { // if the text is longer, try not to truncate
width = int(g.textlen)
}
r.left = 0
r.top = 0
r.right = C.LONG(width)
r.bottom = C.LONG(height)
// use negative numbers to increase the size of the rectangle
if g.margined {
marginRectDLU(&r, -groupYMarginTop, -groupYMarginBottom, -groupXMargin, -groupXMargin, d)
} else {
// unforutnately, as mentioned above, the size of a groupbox includes the label and border
// 1DLU on each side should be enough to make up for that; TODO is not, we can change it
// TODO make these named constants
marginRectDLU(&r, -1, -1, -1, -1, d)
}
return int(r.right - r.left), int(r.bottom - r.top)
}
func (g *group) xresize(x int, y int, width int, height int, d *sizing) {
// first, chain up to the container base to keep the Z-order correct
g.chainresize(x, y, width, height, d)
// now resize the child container
var r C.RECT
// pretend that the client area of the group box only includes the actual empty space
// container will handle the necessary adjustments properly
r.left = 0
r.top = 0
r.right = C.LONG(width)
r.bottom = C.LONG(height)
if g.margined {
// see above
marginRectDLU(&r, groupYMarginTop, groupYMarginBottom, groupXMargin, groupXMargin, d)
} else {
marginRectDLU(&r, 1, 1, 1, 1, d)
}
g.child.resize(int(r.left), int(r.top), int(r.right - r.left), int(r.bottom - r.top), d)
}

View File

@ -1,63 +0,0 @@
// 16 july 2014
package ui
import (
"unsafe"
)
// #include "objc_darwin.h"
import "C"
type label struct {
*controlSingleObject
}
func newLabel(text string) Label {
l := &label{
controlSingleObject: newControlSingleObject(C.newLabel()),
}
l.SetText(text)
return l
}
func (l *label) Text() string {
return C.GoString(C.textfieldText(l.id))
}
func (l *label) SetText(text string) {
ctext := C.CString(text)
defer C.free(unsafe.Pointer(ctext))
C.textfieldSetText(l.id, ctext)
}
/*TODO
func (l *label) commitResize(c *allocation, d *sizing) {
if !l.standalone && 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.controlPreferredSize(l._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(l._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.rect.y + ourAlign.baseline
neighborbasey := d.neighborAlign.rect.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)
}
// in the other case, the most correct thing would be for Label to be aligned to the alignment rect, but I can't get this working, and it looks fine as it is anyway
}
basecommitResize(l, c, d)
}
*/

View File

@ -1,64 +0,0 @@
// +build !windows,!darwin
// 7 july 2014
package ui
import (
"unsafe"
)
// #include "gtk_unix.h"
import "C"
type label struct {
*controlSingleWidget
misc *C.GtkMisc
label *C.GtkLabel
}
func newLabel(text string) Label {
ctext := togstr(text)
defer freegstr(ctext)
widget := C.gtk_label_new(ctext)
l := &label{
controlSingleWidget: newControlSingleWidget(widget),
misc: (*C.GtkMisc)(unsafe.Pointer(widget)),
label: (*C.GtkLabel)(unsafe.Pointer(widget)),
}
return l
}
/*TODO
func newStandaloneLabel(text string) Label {
l := finishNewLabel(text, true)
// standalone labels are always at the top left
C.gtk_misc_set_alignment(l.misc, 0, 0)
return l
}
*/
func (l *label) Text() string {
return fromgstr(C.gtk_label_get_text(l.label))
}
func (l *label) SetText(text string) {
ctext := togstr(text)
defer freegstr(ctext)
C.gtk_label_set_text(l.label, ctext)
}
/*TODO
func (l *label) commitResize(c *allocation, d *sizing) {
if !l.standalone && c.neighbor != nil {
c.neighbor.getAuxResizeInfo(d)
if d.shouldVAlignTop {
// don't bother aligning it to the first line of text in the control; this is harder than it's worth (thanks gregier in irc.gimp.net/#gtk+)
C.gtk_misc_set_alignment(l.misc, 0, 0)
} else {
C.gtk_misc_set_alignment(l.misc, 0, 0.5)
}
}
basecommitResize(l, c, d)
}
*/

View File

@ -1,67 +0,0 @@
// 15 july 2014
package ui
// #include "winapi_windows.h"
import "C"
type label struct {
*controlSingleHWNDWithText
standalone bool
}
var labelclass = toUTF16("STATIC")
func newLabel(text string) Label {
hwnd := C.newControl(labelclass,
// 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)
C.SS_NOPREFIX|C.SS_LEFTNOWORDWRAP,
C.WS_EX_TRANSPARENT)
l := &label{
controlSingleHWNDWithText: newControlSingleHWNDWithText(hwnd),
}
l.fpreferredSize = l.xpreferredSize
l.fnTabStops = func() int {
// labels are not tab stops
return 0
}
l.SetText(text)
C.controlSetControlFont(l.hwnd)
return l
}
func (l *label) Text() string {
return l.text()
}
func (l *label) SetText(text string) {
l.setText(text)
}
const (
// via http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing
labelHeight = 8
labelYOffset = 3
)
func (l *label) xpreferredSize(d *sizing) (width, height int) {
return int(l.textlen), fromdlgunitsY(labelHeight, d)
}
/*TODO
func (l *label) commitResize(c *allocation, d *sizing) {
if !l.standalone {
yoff := fromdlgunitsY(labelYOffset, d)
c.y += yoff
c.height -= yoff
// by default, labels are drawn offset by the internal leading (the space reserved for accents on uppercase letters)
// the above calculation assumes otherwise, so account for the difference
// there will be enough space left over for the internal leading anyway (at least on the standard fonts)
// don't do this to standalone labels, otherwise those accents get cut off!
c.y -= int(d.internalLeading)
c.height += int(d.internalLeading)
}
basecommitResize(l, c, d)
}
*/

View File

@ -1,151 +0,0 @@
/* 8 july 2014 */
/* cgo will include this file multiple times */
#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
#include <stdlib.h>
#include <stdint.h>
#include <objc/message.h>
#include <objc/objc.h>
#include <objc/runtime.h>
/* Objective-C -> Go types for max safety */
struct xsize {
intptr_t width;
intptr_t height;
};
struct xrect {
intptr_t x;
intptr_t y;
intptr_t width;
intptr_t height;
};
struct xalignment {
struct xrect rect;
intptr_t baseline;
};
struct xpoint {
intptr_t x;
intptr_t y;
};
/* uitask_darwin.m */
extern id getAppDelegate(void); /* used by the other .m files */
extern void uiinit(char **);
extern void uimsgloop(void);
extern void uistop(void);
extern void beginModal(void);
extern void endModal(void);
extern void issue(void *);
/* window_darwin.m */
extern id newWindow(intptr_t, intptr_t);
extern void windowSetDelegate(id, void *);
extern void windowSetContentView(id, id);
extern const char *windowTitle(id);
extern void windowSetTitle(id, const char *);
extern void windowShow(id);
extern void windowHide(id);
extern void windowClose(id);
extern id windowContentView(id);
extern void windowRedraw(id);
/* basicctrls_darwin.m */
#define textfieldWidth (96) /* according to Interface Builder */
extern id newButton(void);
extern void buttonSetDelegate(id, void *);
extern const char *buttonText(id);
extern void buttonSetText(id, char *);
extern id newCheckbox(void);
extern void checkboxSetDelegate(id, void *);
extern BOOL checkboxChecked(id);
extern void checkboxSetChecked(id, BOOL);
extern id finishNewTextField(id, BOOL);
extern id newTextField(void);
extern id newPasswordField(void);
extern void textfieldSetDelegate(id, void *);
extern const char *textfieldText(id);
extern void textfieldSetText(id, char *);
extern id textfieldOpenInvalidPopover(id, char *);
extern void textfieldCloseInvalidPopover(id);
extern id newLabel(void);
extern id newGroup(id);
extern const char *groupText(id);
extern void groupSetText(id, char *);
/* container_darwin.m */
extern id newContainerView(void *);
extern void moveControl(id, intptr_t, intptr_t, intptr_t, intptr_t);
extern struct xrect containerBounds(id);
/* tab_darwin.m */
extern id newTab(void);
extern void tabAppend(id, char *, id);
extern struct xsize tabPreferredSize(id);
/* table_darwin.m */
enum {
colTypeText,
colTypeImage,
colTypeCheckbox,
};
extern id newTable(void);
extern void tableAppendColumn(id, intptr_t, char *, int, BOOL);
extern void tableUpdate(id);
extern void tableMakeDataSource(id, void *);
extern struct xsize tablePreferredSize(id);
extern intptr_t tableSelected(id);
extern void tableSelect(id, intptr_t);
/* control_darwin.m */
extern void parent(id, id);
extern void controlSetHidden(id, BOOL);
extern void setStandardControlFont(id);
extern void setSmallControlFont(id);
extern struct xsize controlPreferredSize(id);
extern id newScrollView(id, BOOL);
extern struct xalignment alignmentInfo(id, struct xrect);
extern struct xalignment alignmentInfoFrame(id);
/* area_darwin.h */
extern Class getAreaClass(void);
extern id newArea(void *);
extern BOOL drawImage(void *, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t);
extern const uintptr_t cNSShiftKeyMask;
extern const uintptr_t cNSControlKeyMask;
extern const uintptr_t cNSAlternateKeyMask;
extern const uintptr_t cNSCommandKeyMask;
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);
extern void areaRepaint(id, struct xrect);
extern void areaRepaintAll(id);
extern void areaTextFieldOpen(id, id, intptr_t, intptr_t);
extern void areaSetTextField(id, id);
extern void areaEndTextFieldEditing(id, id);
/* common_darwin.m */
extern void disableAutocorrect(id);
/* imagerep_darwin.m */
extern id toImageListImage(void *, intptr_t, intptr_t, intptr_t);
/* dialog_darwin.m */
extern void openFile(id, void *);
/* warningpopover_darwin.m */
extern id newWarningPopover(char *);
extern void warningPopoverShow(id, id);
#endif

View File

@ -1,252 +0,0 @@
// 25 february 2014
package ui
import (
"fmt"
)
// A SimpleGrid 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 SimpleGrid 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 SimpleGrid 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 SimpleGrid are given in (row,column) form with (0,0) being the top-left cell.
type SimpleGrid interface {
Control
// SetFilling marks the given Control of the SimpleGrid as filling its cell instead of staying at its preferred size.
// It panics if the given coordinate is invalid.
SetFilling(row int, column int)
// SetStretchy marks the given Control of the SimpleGrid as stretchy.
// Stretchy implies filling.
// Only one control can be stretchy per SimpleGrid; calling SetStretchy multiple times merely changes which control is stretchy (preserving the previous filling value).
// It panics if the given coordinate is invalid.
SetStretchy(row int, column int)
// Padded and SetPadded get and set whether the controls of the SimpleGrid have padding between them.
// The size of the padding is platform-dependent.
Padded() bool
SetPadded(padded bool)
}
type simpleGrid struct {
controls [][]Control
filling [][]bool
stretchyrow, stretchycol int
stretchyfill bool
widths, heights [][]int // caches to avoid reallocating each time
rowheights, colwidths []int
container *container
padded bool
}
// NewSimpleGrid creates a new SimpleGrid with the given Controls.
// NewSimpleGrid 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.
// NewSimpleGrid panics if not given a full grid of Controls.
// Example:
// grid := NewSimpleGrid(3,
// control00, control01, control02,
// control10, control11, control12,
// control20, control21, control22)
func NewSimpleGrid(nPerRow int, controls ...Control) SimpleGrid {
if len(controls)%nPerRow != 0 {
panic(fmt.Errorf("incomplete simpleGrid given to NewSimpleGrid() (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++
}
}
g := &simpleGrid{
controls: cc,
filling: cf,
stretchyrow: -1,
stretchycol: -1,
widths: cw,
heights: ch,
rowheights: make([]int, nRows),
colwidths: make([]int, nPerRow),
container: newContainer(),
}
p := g.container.parent()
for _, cc := range g.controls {
for _, c := range cc {
c.setParent(p)
}
}
return g
}
func (g *simpleGrid) SetFilling(row int, column int) {
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 SimpleGrid.SetFilling()", row, column))
}
g.filling[row][column] = true
}
func (g *simpleGrid) SetStretchy(row int, column int) {
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 SimpleGrid.SetStretchy()", row, column))
}
if g.stretchyrow != -1 || g.stretchycol != -1 {
g.filling[g.stretchyrow][g.stretchycol] = g.stretchyfill
}
g.stretchyrow = row
g.stretchycol = column
g.stretchyfill = g.filling[g.stretchyrow][g.stretchycol] // save previous value in case it changes later
g.filling[g.stretchyrow][g.stretchycol] = true
}
func (g *simpleGrid) Padded() bool {
return g.padded
}
func (g *simpleGrid) SetPadded(padded bool) {
g.padded = padded
}
func (g *simpleGrid) setParent(parent *controlParent) {
g.container.setParent(parent)
}
func (g *simpleGrid) resize(x int, y int, width int, height int, d *sizing) {
max := func(a int, b int) int {
if a > b {
return a
}
return b
}
g.container.resize(x, y, width, height, d)
if len(g.controls) == 0 {
return
}
x, y, width, height = g.container.bounds(d)
// -1) get this SimpleGrid's padding
xpadding := d.xpadding
ypadding := d.ypadding
if !g.padded {
xpadding = 0
ypadding = 0
}
// 0) inset the available rect by the needed padding and reset x/y for children
width -= (len(g.colwidths) - 1) * xpadding
height -= (len(g.rowheights) - 1) * 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 {
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]
}
c.resize(x, y, w, h, d)
x += g.colwidths[col] + xpadding
}
x = startx
y += g.rowheights[row] + ypadding
}
}
// filling and stretchy are ignored for preferred size calculation
func (g *simpleGrid) preferredSize(d *sizing) (width int, height int) {
max := func(a int, b int) int {
if a > b {
return a
}
return b
}
xpadding := d.xpadding
ypadding := d.ypadding
if !g.padded {
xpadding = 0
ypadding = 0
}
width -= (len(g.colwidths) - 1) * xpadding
height -= (len(g.rowheights) - 1) * 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 *simpleGrid) nTabStops() int {
n := 0
for _, cc := range g.controls {
for _, c := range cc {
n += c.nTabStops()
}
}
return n
}

View File

@ -1,230 +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 interface {
Control
// SetStretchy marks a control in a Stack as stretchy.
// It panics if index is out of range.
SetStretchy(index int)
// Padded and SetPadded get and set whether the controls of the Stack have padding between them.
// The size of the padding is platform-dependent.
Padded() bool
SetPadded(padded bool)
}
type stack struct {
orientation orientation
controls []Control
stretchy []bool
width, height []int // caches to avoid reallocating these each time
container *container
padded bool
}
func newStack(o orientation, controls ...Control) Stack {
s := &stack{
orientation: o,
controls: controls,
stretchy: make([]bool, len(controls)),
width: make([]int, len(controls)),
height: make([]int, len(controls)),
container: newContainer(),
}
p := s.container.parent()
for _, c := range s.controls {
c.setParent(p)
}
return s
}
// 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...)
}
func (s *stack) SetStretchy(index int) {
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) Padded() bool {
return s.padded
}
func (s *stack) SetPadded(padded bool) {
s.padded = padded
}
func (s *stack) setParent(parent *controlParent) {
s.container.setParent(parent)
}
func (s *stack) resize(x int, y int, width int, height int, d *sizing) {
var stretchywid, stretchyht int
s.container.resize(x, y, width, height, d)
if len(s.controls) == 0 { // do nothing if there's nothing to do
return
}
x, y, width, height = s.container.bounds(d)
// -1) get this Stack's padding
xpadding := d.xpadding
ypadding := d.ypadding
if !s.padded {
xpadding = 0
ypadding = 0
}
// 0) inset the available rect by the needed padding
if s.orientation == horizontal {
width -= (len(s.controls) - 1) * xpadding
} else {
height -= (len(s.controls) - 1) * 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 {
c.resize(x, y, s.width[i], s.height[i], d)
if s.orientation == horizontal {
x += s.width[i] + xpadding
} else {
y += s.height[i] + ypadding
}
}
return
}
// 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).
func (s *stack) preferredSize(d *sizing) (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
}
xpadding := d.xpadding
ypadding := d.ypadding
if !s.padded {
xpadding = 0
ypadding = 0
}
if s.orientation == horizontal {
width = (len(s.controls) - 1) * xpadding
} else {
height = (len(s.controls) - 1) * 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) nTabStops() int {
n := 0
for _, c := range s.controls {
n += c.nTabStops()
}
return n
}
// TODO the below needs to be changed
// 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 SimpleGrid, 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() is guaranteed to be unique.
func Space() Control {
// Grid's rules require this to be unique on every call
return newStack(horizontal)
}

View File

@ -1,53 +0,0 @@
// 25 july 2014
package ui
import (
"unsafe"
)
// #include "objc_darwin.h"
import "C"
type tab struct {
*controlSingleObject
tabs []*container
children []Control
chainresize func(x int, y int, width int, height int, d *sizing)
}
func newTab() Tab {
t := &tab{
controlSingleObject: newControlSingleObject(C.newTab()),
}
t.fpreferredSize = t.xpreferredSize
t.chainresize = t.fresize
t.fresize = t.xresize
return t
}
func (t *tab) Append(name string, control Control) {
c := newContainer()
t.tabs = append(t.tabs, c)
control.setParent(c.parent())
t.children = append(t.children, control)
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
C.tabAppend(t.id, cname, c.id)
}
func (t *tab) xpreferredSize(d *sizing) (width, height int) {
s := C.tabPreferredSize(t.id)
return int(s.width), int(s.height)
}
func (t *tab) xresize(x int, y int, width int, height int, d *sizing) {
// first, chain up to change the GtkFrame and its child container
t.chainresize(x, y, width, height, d)
// now that the containers have the correct size, we can resize the children
for i, _ := range t.tabs {
a := t.tabs[i].allocation(false/*TODO*/)
t.children[i].resize(int(a.x), int(a.y), int(a.width), int(a.height), d)
}
}

View File

@ -1,63 +0,0 @@
// +build !windows,!darwin
// 25 july 2014
package ui
import (
"unsafe"
)
// #include "gtk_unix.h"
import "C"
type tab struct {
*controlSingleWidget
container *C.GtkContainer
notebook *C.GtkNotebook
tabs []*container
children []Control
chainresize func(x int, y int, width int, height int, d *sizing)
}
func newTab() Tab {
widget := C.gtk_notebook_new()
t := &tab{
controlSingleWidget: newControlSingleWidget(widget),
container: (*C.GtkContainer)(unsafe.Pointer(widget)),
notebook: (*C.GtkNotebook)(unsafe.Pointer(widget)),
}
t.chainresize = t.fresize
t.fresize = t.xresize
// there are no scrolling arrows by default; add them in case there are too many tabs
C.gtk_notebook_set_scrollable(t.notebook, C.TRUE)
return t
}
func (t *tab) Append(name string, control Control) {
c := newContainer()
t.tabs = append(t.tabs, c)
// this calls gtk_container_add(), which, according to gregier in irc.gimp.net/#gtk+, acts just like gtk_notebook_append_page()
c.setParent(&controlParent{t.container})
control.setParent(c.parent())
t.children = append(t.children, control)
cname := togstr(name)
defer freegstr(cname)
C.gtk_notebook_set_tab_label_text(t.notebook,
// unfortunately there does not seem to be a gtk_notebook_set_nth_tab_label_text()
C.gtk_notebook_get_nth_page(t.notebook, C.gint(len(t.tabs)-1)),
cname)
}
func (t *tab) xresize(x int, y int, width int, height int, d *sizing) {
// first, chain up to change the GtkFrame and its child container
t.chainresize(x, y, width, height, d)
// now that the containers have the correct size, we can resize the children
for i, _ := range t.tabs {
a := t.tabs[i].allocation(false/*TODO*/)
t.children[i].resize(int(a.x), int(a.y), int(a.width), int(a.height), d)
}
}

View File

@ -1,115 +0,0 @@
// 25 july 2014
package ui
import (
"unsafe"
)
// #include "winapi_windows.h"
import "C"
/*
On Windows, container controls are just regular controls that notify their parent when the user wants to do things; changing the contents of a switching container (such as a tab control) must be done manually.
We'll create a dummy window using the container window class for each tab page. This makes showing and hiding tabs a matter of showing and hiding one control.
*/
type tab struct {
*controlSingleHWND
tabs []*container
children []Control
chainresize func(x int, y int, width int, height int, d *sizing)
}
func newTab() Tab {
hwnd := C.newControl(C.xWC_TABCONTROL,
C.TCS_TOOLTIPS|C.WS_TABSTOP,
0) // don't set WS_EX_CONTROLPARENT here; see uitask_windows.c
t := &tab{
controlSingleHWND: newControlSingleHWND(hwnd),
}
t.fpreferredSize = t.xpreferredSize
t.chainresize = t.fresize
t.fresize = t.xresize
// count tabs as 1 tab stop; the actual number of tab stops varies
C.controlSetControlFont(t.hwnd)
C.setTabSubclass(t.hwnd, unsafe.Pointer(t))
return t
}
// TODO margined
func (t *tab) Append(name string, control Control) {
c := newContainer()
control.setParent(&controlParent{c.hwnd})
c.setParent(&controlParent{t.hwnd})
t.tabs = append(t.tabs, c)
t.children = append(t.children, control)
// initially hide tab 1..n controls; if we don't, they'll appear over other tabs, resulting in weird behavior
if len(t.tabs) != 1 {
t.tabs[len(t.tabs)-1].hide()
}
C.tabAppend(t.hwnd, toUTF16(name))
}
//export tabChanging
func tabChanging(data unsafe.Pointer, current C.LRESULT) {
t := (*tab)(data)
t.tabs[int(current)].hide()
}
//export tabChanged
func tabChanged(data unsafe.Pointer, new C.LRESULT) {
t := (*tab)(data)
t.tabs[int(new)].show()
}
//export tabTabHasChildren
func tabTabHasChildren(data unsafe.Pointer, which C.LRESULT) C.BOOL {
t := (*tab)(data)
if len(t.tabs) == 0 { // currently no tabs
return C.FALSE
}
if t.children[int(which)].nTabStops() > 0 {
return C.TRUE
}
return C.FALSE
}
func (t *tab) xpreferredSize(d *sizing) (width, height int) {
for _, c := range t.children {
w, h := c.preferredSize(d)
if width < w {
width = w
}
if height < h {
height = h
}
}
return width, height + int(C.tabGetTabHeight(t.hwnd))
}
// a tab control contains other controls; size appropriately
func (t *tab) xresize(x int, y int, width int, height int, d *sizing) {
// first, chain up to the container base to keep the Z-order correct
t.chainresize(x, y, width, height, d)
// now resize the children
var r C.RECT
// figure out what the rect for each child is...
// the tab contents are children of the tab itself, so ignore x and y, which are relative to the window!
r.left = C.LONG(0)
r.top = C.LONG(0)
r.right = C.LONG(width)
r.bottom = C.LONG(height)
C.tabGetContentRect(t.hwnd, &r)
// and resize tabs
// don't resize just the current tab; resize all tabs!
for i, _ := range t.tabs {
// because each widget is actually a child of the Window, the origin is the one we calculated above
t.tabs[i].resize(int(r.left), int(r.top), int(r.right - r.left), int(r.bottom - r.top), d)
// TODO get the actual client rect
t.children[i].resize(int(0), int(0), int(r.right - r.left), int(r.bottom - r.top), d)
}
}

View File

@ -1,137 +0,0 @@
// 29 july 2014
package ui
import (
"fmt"
"reflect"
"unsafe"
)
// #include "objc_darwin.h"
import "C"
type table struct {
*tablebase
*scroller
images []C.id
selected *event
}
func finishNewTable(b *tablebase, ty reflect.Type) Table {
id := C.newTable()
t := &table{
scroller: newScroller(id, true), // border on Table
tablebase: b,
selected: newEvent(),
}
t.fpreferredSize = t.xpreferredSize
// also sets the delegate
C.tableMakeDataSource(t.id, unsafe.Pointer(t))
for i := 0; i < ty.NumField(); i++ {
cname := C.CString(ty.Field(i).Name)
coltype := C.colTypeText
editable := false
switch {
case ty.Field(i).Type == reflect.TypeOf(ImageIndex(0)):
coltype = C.colTypeImage
case ty.Field(i).Type.Kind() == reflect.Bool:
coltype = C.colTypeCheckbox
editable = true
}
C.tableAppendColumn(t.id, C.intptr_t(i), cname, C.int(coltype), toBOOL(editable))
C.free(unsafe.Pointer(cname)) // free now (not deferred) to conserve memory
}
return t
}
func (t *table) Unlock() {
t.unlock()
// there's a possibility that user actions can happen at this point, before the view is updated
// alas, this is something we have to deal with, because Unlock() can be called from any thread
go func() {
Do(func() {
t.RLock()
defer t.RUnlock()
C.tableUpdate(t.id)
})
}()
}
func (t *table) LoadImageList(i ImageList) {
i.apply(&t.images)
}
func (t *table) Selected() int {
t.RLock()
defer t.RUnlock()
return int(C.tableSelected(t.id))
}
func (t *table) Select(index int) {
t.RLock()
defer t.RUnlock()
C.tableSelect(t.id, C.intptr_t(index))
}
func (t *table) OnSelected(f func()) {
t.selected.set(f)
}
//export goTableDataSource_getValue
func goTableDataSource_getValue(data unsafe.Pointer, row C.intptr_t, col C.intptr_t, outtype *C.int) unsafe.Pointer {
t := (*table)(data)
t.RLock()
defer t.RUnlock()
d := reflect.Indirect(reflect.ValueOf(t.data))
datum := d.Index(int(row)).Field(int(col))
switch {
case datum.Type() == reflect.TypeOf(ImageIndex(0)):
*outtype = C.colTypeImage
d := datum.Interface().(ImageIndex)
return unsafe.Pointer(t.images[d])
case datum.Kind() == reflect.Bool:
*outtype = C.colTypeCheckbox
if datum.Bool() == true {
// return a non-nil pointer
// outtype isn't Go-side so it'll work
return unsafe.Pointer(outtype)
}
return nil
default:
s := fmt.Sprintf("%v", datum)
return unsafe.Pointer(C.CString(s))
}
}
//export goTableDataSource_getRowCount
func goTableDataSource_getRowCount(data unsafe.Pointer) C.intptr_t {
t := (*table)(data)
t.RLock()
defer t.RUnlock()
d := reflect.Indirect(reflect.ValueOf(t.data))
return C.intptr_t(d.Len())
}
//export goTableDataSource_toggled
func goTableDataSource_toggled(data unsafe.Pointer, row C.intptr_t, col C.intptr_t, checked C.BOOL) {
t := (*table)(data)
t.Lock()
defer t.Unlock()
d := reflect.Indirect(reflect.ValueOf(t.data))
datum := d.Index(int(row)).Field(int(col))
datum.SetBool(fromBOOL(checked))
}
//export tableSelectionChanged
func tableSelectionChanged(data unsafe.Pointer) {
t := (*table)(data)
t.selected.fire()
}
func (t *table) xpreferredSize(d *sizing) (width, height int) {
s := C.tablePreferredSize(t.id)
return int(s.width), int(s.height)
}

View File

@ -1,222 +0,0 @@
// +build !windows,!darwin
// 29 july 2014
package ui
import (
"fmt"
"reflect"
"unsafe"
)
// #include "gtk_unix.h"
// extern void goTableModel_toggled(GtkCellRendererToggle *, gchar *, gpointer);
// extern void tableSelectionChanged(GtkTreeSelection *, gpointer);
import "C"
type table struct {
*tablebase
*scroller
treeview *C.GtkTreeView
model *C.goTableModel
modelgtk *C.GtkTreeModel
selection *C.GtkTreeSelection
pixbufs []*C.GdkPixbuf
selected *event
// stuff required by GtkTreeModel
nColumns C.gint
old C.gint
types []C.GType
crtocol map[*C.GtkCellRendererToggle]int
}
var (
attribText = togstr("text")
attribPixbuf = togstr("pixbuf")
attribActive = togstr("active")
)
func finishNewTable(b *tablebase, ty reflect.Type) Table {
widget := C.gtk_tree_view_new()
t := &table{
scroller: newScroller(widget, true, true, false), // natively scrollable; has a border; no overlay
tablebase: b,
treeview: (*C.GtkTreeView)(unsafe.Pointer(widget)),
crtocol: make(map[*C.GtkCellRendererToggle]int),
selected: newEvent(),
}
model := C.newTableModel(unsafe.Pointer(t))
t.model = model
t.modelgtk = (*C.GtkTreeModel)(unsafe.Pointer(model))
t.selection = C.gtk_tree_view_get_selection(t.treeview)
g_signal_connect(
C.gpointer(unsafe.Pointer(t.selection)),
"changed",
C.GCallback(C.tableSelectionChanged),
C.gpointer(unsafe.Pointer(t)))
C.gtk_tree_view_set_model(t.treeview, t.modelgtk)
for i := 0; i < ty.NumField(); i++ {
cname := togstr(ty.Field(i).Name)
switch {
case ty.Field(i).Type == reflect.TypeOf(ImageIndex(0)):
// can't use GDK_TYPE_PIXBUF here because it's a macro that expands to a function and cgo hates that
t.types = append(t.types, C.gdk_pixbuf_get_type())
C.tableAppendColumn(t.treeview, C.gint(i), cname,
C.gtk_cell_renderer_pixbuf_new(), attribPixbuf)
case ty.Field(i).Type.Kind() == reflect.Bool:
t.types = append(t.types, C.G_TYPE_BOOLEAN)
cr := C.gtk_cell_renderer_toggle_new()
crt := (*C.GtkCellRendererToggle)(unsafe.Pointer(cr))
t.crtocol[crt] = i
g_signal_connect(C.gpointer(unsafe.Pointer(cr)),
"toggled",
C.GCallback(C.goTableModel_toggled),
C.gpointer(unsafe.Pointer(t)))
C.tableAppendColumn(t.treeview, C.gint(i), cname,
cr, attribActive)
default:
t.types = append(t.types, C.G_TYPE_STRING)
C.tableAppendColumn(t.treeview, C.gint(i), cname,
C.gtk_cell_renderer_text_new(), attribText)
}
freegstr(cname) // free now (not deferred) to conserve memory
}
// and for some GtkTreeModel boilerplate
t.nColumns = C.gint(ty.NumField())
return t
}
func (t *table) Lock() {
t.tablebase.Lock()
d := reflect.Indirect(reflect.ValueOf(t.data))
t.old = C.gint(d.Len())
}
func (t *table) Unlock() {
t.unlock()
// there's a possibility that user actions can happen at this point, before the view is updated
// alas, this is something we have to deal with, because Unlock() can be called from any thread
go func() {
Do(func() {
t.RLock()
defer t.RUnlock()
d := reflect.Indirect(reflect.ValueOf(t.data))
new := C.gint(d.Len())
C.tableUpdate(t.model, t.old, new)
})
}()
}
func (t *table) LoadImageList(i ImageList) {
i.apply(&t.pixbufs)
}
func (t *table) Selected() int {
var iter C.GtkTreeIter
t.RLock()
defer t.RUnlock()
if C.gtk_tree_selection_get_selected(t.selection, nil, &iter) == C.FALSE {
return -1
}
path := C.gtk_tree_model_get_path(t.modelgtk, &iter)
if path == nil {
panic(fmt.Errorf("invalid iter in Table.Selected()"))
}
defer C.gtk_tree_path_free(path)
return int(*C.gtk_tree_path_get_indices(path))
}
func (t *table) Select(index int) {
t.RLock()
defer t.RUnlock()
C.gtk_tree_selection_unselect_all(t.selection)
if index == -1 {
return
}
path := C.gtk_tree_path_new()
defer C.gtk_tree_path_free(path)
C.gtk_tree_path_append_index(path, C.gint(index))
C.gtk_tree_selection_select_path(t.selection, path)
}
func (t *table) OnSelected(f func()) {
t.selected.set(f)
}
//export goTableModel_get_n_columns
func goTableModel_get_n_columns(model *C.GtkTreeModel) C.gint {
tm := (*C.goTableModel)(unsafe.Pointer(model))
t := (*table)(tm.gotable)
return t.nColumns
}
//export goTableModel_get_column_type
func goTableModel_get_column_type(model *C.GtkTreeModel, column C.gint) C.GType {
tm := (*C.goTableModel)(unsafe.Pointer(model))
t := (*table)(tm.gotable)
return t.types[column]
}
//export goTableModel_do_get_value
func goTableModel_do_get_value(data unsafe.Pointer, row C.gint, col C.gint, value *C.GValue) {
t := (*table)(data)
t.RLock()
defer t.RUnlock()
d := reflect.Indirect(reflect.ValueOf(t.data))
datum := d.Index(int(row)).Field(int(col))
switch {
case datum.Type() == reflect.TypeOf(ImageIndex(0)):
d := datum.Interface().(ImageIndex)
C.g_value_init(value, C.gdk_pixbuf_get_type())
C.g_value_set_object(value, C.gpointer(unsafe.Pointer(t.pixbufs[d])))
case datum.Kind() == reflect.Bool:
d := datum.Interface().(bool)
C.g_value_init(value, C.G_TYPE_BOOLEAN)
C.g_value_set_boolean(value, togbool(d))
default:
s := fmt.Sprintf("%v", datum)
str := togstr(s)
defer freegstr(str)
C.g_value_init(value, C.G_TYPE_STRING)
C.g_value_set_string(value, str)
}
}
//export goTableModel_getRowCount
func goTableModel_getRowCount(data unsafe.Pointer) C.gint {
t := (*table)(data)
t.RLock()
defer t.RUnlock()
d := reflect.Indirect(reflect.ValueOf(t.data))
return C.gint(d.Len())
}
//export goTableModel_toggled
func goTableModel_toggled(cr *C.GtkCellRendererToggle, pathstr *C.gchar, data C.gpointer) {
t := (*table)(unsafe.Pointer(data))
t.Lock()
defer t.Unlock()
path := C.gtk_tree_path_new_from_string(pathstr)
if len := C.gtk_tree_path_get_depth(path); len != 1 {
panic(fmt.Errorf("invalid path of depth %d given to goTableModel_toggled()", len))
}
// dereference return value to get our sole member
row := *C.gtk_tree_path_get_indices(path)
col := t.crtocol[cr]
d := reflect.Indirect(reflect.ValueOf(t.data))
datum := d.Index(int(row)).Field(int(col))
datum.SetBool(!datum.Bool())
}
//export tableSelectionChanged
func tableSelectionChanged(sel *C.GtkTreeSelection, data C.gpointer) {
t := (*table)(unsafe.Pointer(data))
t.selected.fire()
}

View File

@ -1,235 +0,0 @@
// 28 july 2014
package ui
import (
"fmt"
"reflect"
"unsafe"
)
// #include "winapi_windows.h"
import "C"
type table struct {
*tablebase
*controlSingleHWND
noautosize bool
colcount C.int
hotrow C.int
hotcol C.int
pushedrow C.int
pushedcol C.int
selected *event
chainresize func(x int, y int, width int, height int, d *sizing)
}
func finishNewTable(b *tablebase, ty reflect.Type) Table {
hwnd := C.newControl(C.xWC_LISTVIEW,
C.LVS_REPORT|C.LVS_OWNERDATA|C.LVS_NOSORTHEADER|C.LVS_SHOWSELALWAYS|C.LVS_SINGLESEL|C.WS_HSCROLL|C.WS_VSCROLL|C.WS_TABSTOP,
C.WS_EX_CLIENTEDGE) // WS_EX_CLIENTEDGE without WS_BORDER will show the canonical visual styles border (thanks to MindChild in irc.efnet.net/#winprog)
t := &table{
controlSingleHWND: newControlSingleHWND(hwnd),
tablebase: b,
hotrow: -1,
hotcol: -1,
pushedrow: -1,
pushedcol: -1,
selected: newEvent(),
}
t.fpreferredSize = t.xpreferredSize
t.chainresize = t.fresize
t.fresize = t.xresize
C.setTableSubclass(t.hwnd, unsafe.Pointer(t))
// LVS_EX_FULLROWSELECT gives us selection across the whole row, not just the leftmost column; this makes the list view work like on other platforms
// LVS_EX_SUBITEMIMAGES gives us images in subitems, which will be important when both images and checkboxes are added
C.tableAddExtendedStyles(t.hwnd, C.LVS_EX_FULLROWSELECT|C.LVS_EX_SUBITEMIMAGES)
// this must come after the subclass because it uses one of our private messages
C.SendMessageW(t.hwnd, C.msgTableMakeInitialCheckboxImageList, 0, 0)
for i := 0; i < ty.NumField(); i++ {
C.tableAppendColumn(t.hwnd, C.int(i), toUTF16(ty.Field(i).Name))
}
t.colcount = C.int(ty.NumField())
return t
}
func (t *table) Unlock() {
t.unlock()
// there's a possibility that user actions can happen at this point, before the view is updated
// alas, this is something we have to deal with, because Unlock() can be called from any thread
go func() {
Do(func() {
t.RLock()
defer t.RUnlock()
C.tableUpdate(t.hwnd, C.int(reflect.Indirect(reflect.ValueOf(t.data)).Len()))
})
}()
}
func (t *table) LoadImageList(il ImageList) {
il.apply(t.hwnd, C.msgLoadImageList)
}
func (t *table) Selected() int {
t.RLock()
defer t.RUnlock()
return int(C.tableSelectedItem(t.hwnd))
}
func (t *table) Select(index int) {
t.RLock()
defer t.RUnlock()
C.tableSelectItem(t.hwnd, C.intptr_t(index))
}
func (t *table) OnSelected(f func()) {
t.selected.set(f)
}
//export tableGetCell
func tableGetCell(data unsafe.Pointer, item *C.LVITEMW) {
t := (*table)(data)
t.RLock()
defer t.RUnlock()
d := reflect.Indirect(reflect.ValueOf(t.data))
datum := d.Index(int(item.iItem)).Field(int(item.iSubItem))
isText := true
if item.mask&C.LVIF_IMAGE != 0 {
if datum.Type() == reflect.TypeOf(ImageIndex(0)) {
item.iImage = C.int(datum.Interface().(ImageIndex))
isText = false
}
// else let the default behavior work
}
if item.mask&C.LVIF_INDENT != 0 {
// always have an indent of zero
item.iIndent = 0
}
if item.mask&C.LVIF_STATE != 0 {
// start by not changing any state
item.stateMask = 0
if datum.Kind() == reflect.Bool {
item.stateMask = C.LVIS_STATEIMAGEMASK
// state image index is 1-based
curstate := ((item.state & C.LVIS_STATEIMAGEMASK) >> 12)
if curstate > 0 {
curstate--
}
if datum.Bool() == true {
curstate |= C.checkboxStateChecked
} else {
curstate &^= C.checkboxStateChecked
}
if item.iItem == t.hotrow && item.iSubItem == t.hotcol {
curstate |= C.checkboxStateHot
} else {
curstate &^= C.checkboxStateHot
}
if item.iItem == t.pushedrow && item.iSubItem == t.pushedcol {
curstate |= C.checkboxStatePushed
} else {
curstate &^= C.checkboxStatePushed
}
item.state = (curstate + 1) << 12
isText = false
}
}
if item.mask&C.LVIF_TEXT != 0 {
if isText {
s := fmt.Sprintf("%v", datum)
item.pszText = toUTF16(s)
}
// else let the default handler work
}
}
// the column autoresize policy is simple:
// on every table.commitResize() call, if the columns have not been resized by the user, autoresize
func (t *table) autoresize() {
t.RLock()
defer t.RUnlock()
if !t.noautosize {
C.tableAutosizeColumns(t.hwnd, t.colcount)
}
}
//export tableStopColumnAutosize
func tableStopColumnAutosize(data unsafe.Pointer) {
t := (*table)(data)
t.noautosize = true
}
//export tableColumnCount
func tableColumnCount(data unsafe.Pointer) C.int {
t := (*table)(data)
return t.colcount
}
//export tableSetHot
func tableSetHot(data unsafe.Pointer, row C.int, col C.int) {
t := (*table)(data)
redraw := (row != t.hotrow || col != t.hotcol)
t.hotrow = row
t.hotcol = col
if redraw {
C.tableUpdate(t.hwnd, C.int(reflect.Indirect(reflect.ValueOf(t.data)).Len()))
}
}
//export tablePushed
func tablePushed(data unsafe.Pointer, row C.int, col C.int) {
t := (*table)(data)
t.pushedrow = row
t.pushedcol = col
C.tableUpdate(t.hwnd, C.int(reflect.Indirect(reflect.ValueOf(t.data)).Len()))
}
//export tableToggled
func tableToggled(data unsafe.Pointer, row C.int, col C.int) {
t := (*table)(data)
t.Lock()
defer func() {
// reset for next time
t.pushedrow = -1
t.pushedcol = -1
// and THEN unlock so the reset takes effect
t.Unlock()
}()
if row == -1 || col == -1 { // discard extras sent by handle() in table_windows.c
return
}
if row != t.pushedrow || col != t.pushedcol { // mouse moved out
return
}
d := reflect.Indirect(reflect.ValueOf(t.data))
datum := d.Index(int(row)).Field(int(col))
if datum.Kind() == reflect.Bool {
datum.SetBool(!datum.Bool())
return
}
panic(fmt.Errorf("tableSetHot() on non-checkbox at (%d, %d)", row, col))
}
//export tableSelectionChanged
func tableSelectionChanged(data unsafe.Pointer) {
t := (*table)(data)
t.selected.fire()
}
const (
// from C++ Template 05 in http://msdn.microsoft.com/en-us/library/windows/desktop/bb226818%28v=vs.85%29.aspx as this is the best I can do for now
// there IS a message LVM_APPROXIMATEVIEWRECT that can do calculations, but it doesn't seem to work right when asked to base its calculations on the current width/height on Windows and wine...
tableWidth = 183
tableHeight = 50
)
func (t *table) xpreferredSize(d *sizing) (width, height int) {
return fromdlgunitsX(tableWidth, d), fromdlgunitsY(tableHeight, d)
}
func (t *table) xresize(x int, y int, width int, height int, d *sizing) {
t.chainresize(x, y, width, height, d)
t.RLock()
defer t.RUnlock()
t.autoresize()
}

View File

@ -1,75 +0,0 @@
// 16 july 2014
package ui
import (
"unsafe"
)
// #include "objc_darwin.h"
import "C"
type textfield struct {
*controlSingleObject
changed *event
invalid C.id
chainpreferredSize func(d *sizing) (int, int)
}
func finishNewTextField(id C.id) *textfield {
t := &textfield{
controlSingleObject: newControlSingleObject(id),
changed: newEvent(),
}
C.textfieldSetDelegate(t.id, unsafe.Pointer(t))
t.chainpreferredSize = t.fpreferredSize
t.fpreferredSize = t.xpreferredSize
return t
}
func newTextField() *textfield {
return finishNewTextField(C.newTextField())
}
func newPasswordField() *textfield {
return finishNewTextField(C.newPasswordField())
}
func (t *textfield) Text() string {
return C.GoString(C.textfieldText(t.id))
}
func (t *textfield) SetText(text string) {
ctext := C.CString(text)
defer C.free(unsafe.Pointer(ctext))
C.textfieldSetText(t.id, ctext)
}
func (t *textfield) OnChanged(f func()) {
t.changed.set(f)
}
func (t *textfield) Invalid(reason string) {
if t.invalid != nil {
C.textfieldCloseInvalidPopover(t.invalid)
t.invalid = nil
}
if reason == "" {
return
}
creason := C.CString(reason)
defer C.free(unsafe.Pointer(creason))
t.invalid = C.textfieldOpenInvalidPopover(t.id, creason)
}
//export textfieldChanged
func textfieldChanged(data unsafe.Pointer) {
t := (*textfield)(data)
t.changed.fire()
}
func (t *textfield) xpreferredSize(d *sizing) (width, height int) {
_, height = t.chainpreferredSize(d)
// the returned width is based on the contents; use this instead
return C.textfieldWidth, height
}

View File

@ -1,81 +0,0 @@
// +build !windows,!darwin
// 7 july 2014
package ui
import (
"unsafe"
)
// #include "gtk_unix.h"
// extern void textfieldChanged(GtkEditable *, gpointer);
// /* because cgo doesn't like GTK_STOCK_DIALOG_ERROR */
// static inline void setErrorIcon(GtkEntry *entry)
// {
// gtk_entry_set_icon_from_stock(entry, GTK_ENTRY_ICON_SECONDARY, GTK_STOCK_DIALOG_ERROR);
// }
import "C"
type textfield struct {
*controlSingleWidget
entry *C.GtkEntry
changed *event
}
func startNewTextField() *textfield {
widget := C.gtk_entry_new()
t := &textfield{
controlSingleWidget: newControlSingleWidget(widget),
entry: (*C.GtkEntry)(unsafe.Pointer(widget)),
changed: newEvent(),
}
g_signal_connect(
C.gpointer(unsafe.Pointer(t.widget)),
"changed",
C.GCallback(C.textfieldChanged),
C.gpointer(unsafe.Pointer(t)))
return t
}
func newTextField() *textfield {
return startNewTextField()
}
func newPasswordField() *textfield {
t := startNewTextField()
C.gtk_entry_set_visibility(t.entry, C.FALSE)
return t
}
func (t *textfield) Text() string {
return fromgstr(C.gtk_entry_get_text(t.entry))
}
func (t *textfield) SetText(text string) {
ctext := togstr(text)
defer freegstr(ctext)
C.gtk_entry_set_text(t.entry, ctext)
}
func (t *textfield) OnChanged(f func()) {
t.changed.set(f)
}
func (t *textfield) Invalid(reason string) {
if reason == "" {
C.gtk_entry_set_icon_from_stock(t.entry, C.GTK_ENTRY_ICON_SECONDARY, nil)
return
}
C.setErrorIcon(t.entry)
creason := togstr(reason)
defer freegstr(creason)
C.gtk_entry_set_icon_tooltip_text(t.entry, C.GTK_ENTRY_ICON_SECONDARY, creason)
C.gtk_widget_error_bell(t.widget)
}
//export textfieldChanged
func textfieldChanged(editable *C.GtkEditable, data C.gpointer) {
t := (*textfield)(unsafe.Pointer(data))
t.changed.fire()
}

View File

@ -1,75 +0,0 @@
// 15 july 2014
package ui
import (
"unsafe"
)
// #include "winapi_windows.h"
import "C"
type textfield struct {
*controlSingleHWNDWithText
changed *event
}
var editclass = toUTF16("EDIT")
func startNewTextField(style C.DWORD) *textfield {
hwnd := C.newControl(editclass,
style|C.textfieldStyle,
C.textfieldExtStyle) // WS_EX_CLIENTEDGE without WS_BORDER will show the canonical visual styles border (thanks to MindChild in irc.efnet.net/#winprog)
t := &textfield{
controlSingleHWNDWithText: newControlSingleHWNDWithText(hwnd),
changed: newEvent(),
}
t.fpreferredSize = t.xpreferredSize
C.controlSetControlFont(t.hwnd)
C.setTextFieldSubclass(t.hwnd, unsafe.Pointer(t))
return t
}
func newTextField() *textfield {
return startNewTextField(0)
}
func newPasswordField() *textfield {
return startNewTextField(C.ES_PASSWORD)
}
func (t *textfield) Text() string {
return t.text()
}
func (t *textfield) SetText(text string) {
t.setText(text)
}
func (t *textfield) OnChanged(f func()) {
t.changed.set(f)
}
func (t *textfield) Invalid(reason string) {
if reason == "" {
C.textfieldHideInvalidBalloonTip(t.hwnd)
return
}
C.textfieldSetAndShowInvalidBalloonTip(t.hwnd, toUTF16(reason))
}
//export textfieldChanged
func textfieldChanged(data unsafe.Pointer) {
t := (*textfield)(data)
t.changed.fire()
}
const (
// from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing
textfieldWidth = 107 // this is actually the shorter progress bar width, but Microsoft only indicates as wide as necessary
textfieldHeight = 14
)
func (t *textfield) xpreferredSize(d *sizing) (width, height int) {
return fromdlgunitsX(textfieldWidth, d), fromdlgunitsY(textfieldHeight, d)
}

View File

@ -1,156 +0,0 @@
// 17 july 2014
// cgo will include this file multiple times
#ifndef __GO_UI_WINAPI_WINDOWS_H__
#define __GO_UI_WINAPI_WINDOWS_H__
#define UNICODE
#define _UNICODE
#define STRICT
#define STRICT_TYPED_ITEMIDS
// get Windows version right; right now Windows XP
#define WINVER 0x0501
#define _WIN32_WINNT 0x0501
#define _WIN32_WINDOWS 0x0501 /* according to Microsoft's winperf.h */
#define _WIN32_IE 0x0600 /* according to Microsoft's sdkddkver.h */
#define NTDDI_VERSION 0x05010000 /* according to Microsoft's sdkddkver.h */
#include <windows.h>
#include <commctrl.h>
#include <stdint.h>
#include <uxtheme.h>
#include <string.h>
#include <wchar.h>
#include <windowsx.h>
#include <vsstyle.h>
#include <vssym32.h>
// global messages unique to everything
enum {
msgRequest = WM_APP + 1, // + 1 just to be safe
msgCOMMAND, // WM_COMMAND proxy; see forwardCommand() in controls_windows.go
msgNOTIFY, // WM_NOTIFY proxy
msgAreaSizeChanged,
msgAreaGetScroll,
msgAreaRepaint,
msgAreaRepaintAll,
msgTabCurrentTabHasChildren,
msgAreaKeyDown,
msgAreaKeyUp,
msgLoadImageList,
msgTableMakeInitialCheckboxImageList,
msgOpenFileDone,
};
// uitask_windows.c
extern void uimsgloop(void);
extern void issue(void *);
extern HWND msgwin;
extern DWORD makemsgwin(char **);
// comctl32_windows.c
extern DWORD initCommonControls(char **);
// these are listed as WINAPI in both Microsoft's and MinGW's headers, but not on MSDN for some reason
extern BOOL (*WINAPI fv_SetWindowSubclass)(HWND, SUBCLASSPROC, UINT_PTR, DWORD_PTR);
extern BOOL (*WINAPI fv_RemoveWindowSubclass)(HWND, SUBCLASSPROC, UINT_PTR);
extern LRESULT (*WINAPI fv_DefSubclassProc)(HWND, UINT, WPARAM, LPARAM);
extern HIMAGELIST (*WINAPI fv_ImageList_Create)(int, int, UINT, int, int);
extern int (*WINAPI fv_ImageList_Add)(HIMAGELIST, HBITMAP, HBITMAP);
extern BOOL (*WINAPI fv_ImageList_Destroy)(HIMAGELIST);
// control_windows.c
extern HWND newControl(LPWSTR, DWORD, DWORD);
extern void controlSetParent(HWND, HWND);
extern void controlSetControlFont(HWND);
extern void moveWindow(HWND, int, int, int, int);
extern LONG controlTextLength(HWND, LPWSTR);
// basicctrls_windows.c
extern void setButtonSubclass(HWND, void *);
extern void setCheckboxSubclass(HWND, void *);
extern BOOL checkboxChecked(HWND);
extern void checkboxSetChecked(HWND, BOOL);
#define textfieldStyle (ES_AUTOHSCROLL | ES_LEFT | ES_NOHIDESEL | WS_TABSTOP)
#define textfieldExtStyle (WS_EX_CLIENTEDGE)
extern void setTextFieldSubclass(HWND, void *);
extern void textfieldSetAndShowInvalidBalloonTip(HWND, WCHAR *);
extern void textfieldHideInvalidBalloonTip(HWND);
// init_windows.c
extern HINSTANCE hInstance;
extern int nCmdShow;
extern HICON hDefaultIcon;
extern HCURSOR hArrowCursor;
extern HFONT controlFont;
extern HFONT titleFont;
extern HFONT smallTitleFont;
extern HFONT menubarFont;
extern HFONT statusbarFont;
extern HBRUSH hollowBrush;
extern DWORD initWindows(char **);
// window_windows.c
extern DWORD makeWindowWindowClass(char **);
extern HWND newWindow(LPWSTR, int, int, void *);
extern void windowClose(HWND);
// common_windows.c
extern LRESULT getWindowTextLen(HWND);
extern void getWindowText(HWND, WPARAM, LPWSTR);
extern void setWindowText(HWND, LPWSTR);
extern void updateWindow(HWND);
extern void *getWindowData(HWND, UINT, WPARAM, LPARAM, LRESULT *);
extern BOOL sharedWndProc(HWND, UINT, WPARAM, LPARAM, LRESULT *);
extern void paintControlBackground(HWND, HDC);
// tab_windows.go
extern LPWSTR xWC_TABCONTROL;
extern void setTabSubclass(HWND, void *);
extern void tabAppend(HWND, LPWSTR);
extern void tabGetContentRect(HWND, RECT *);
extern LONG tabGetTabHeight(HWND);
extern void tabEnterChildren(HWND);
extern void tabLeaveChildren(HWND);
// table_windows.go
extern LPWSTR xWC_LISTVIEW;
extern void setTableSubclass(HWND, void *);
extern void tableAppendColumn(HWND, int, LPWSTR);
extern void tableUpdate(HWND, int);
extern void tableAddExtendedStyles(HWND, LPARAM);
extern void tableAutosizeColumns(HWND, int);
extern intptr_t tableSelectedItem(HWND);
extern void tableSelectItem(HWND, intptr_t);
// container_windows.c
#define containerclass L"gouicontainer"
extern DWORD makeContainerWindowClass(char **);
extern HWND newContainer();
extern RECT containerBounds(HWND);
extern void calculateBaseUnits(HWND, int *, int *, LONG *);
// area_windows.c
#define areaWindowClass L"gouiarea"
extern void repaintArea(HWND, RECT *);
extern DWORD makeAreaWindowClass(char **);
extern HWND newArea(void *);
extern HWND newAreaTextField(HWND, void *);
extern void areaOpenTextField(HWND, HWND, int, int, int, int);
extern void areaMarkTextFieldDone(HWND);
// imagelist_windows.c
extern HBITMAP unscaledBitmap(void *, intptr_t, intptr_t);
extern HIMAGELIST newImageList(int, int);
extern void addImage(HIMAGELIST, HWND, HBITMAP, int, int, int, int);
extern void applyImageList(HWND, UINT, WPARAM, HIMAGELIST, HIMAGELIST);
enum {
checkboxStateChecked = 1 << 0,
checkboxStateHot = 1 << 1,
checkboxStatePushed = 1 << 2,
checkboxnStates = 1 << 3,
};
extern HIMAGELIST makeCheckboxImageList(HWND, HTHEME *);
// dialog_windows.c
extern void openFile(HWND, void *);
#endif

View File

@ -1,40 +0,0 @@
// 7 july 2014
package ui
// Window represents a top-level window on screen that contains other Controls.
// Windows in package ui can only contain one control; the Stack, Grid, and SimpleGrid layout Controls allow you to pack multiple Controls in a Window.
// Note that a Window is not itself a Control.
type Window interface {
// Title and SetTitle get and set the Window's title, respectively.
Title() string
SetTitle(title string)
// Show and Hide bring the Window on-screen and off-screen, respectively.
Show()
Hide()
// Close closes the Window.
// Any Controls within the Window are destroyed, and the Window itself is also destroyed.
// Attempting to use a Window after it has been closed results in undefined behavior.
// Close unconditionally closes the Window; it neither raises OnClosing nor checks for a return from OnClosing.
Close()
// OnClosing registers an event handler that is triggered when the user clicks the Window's close button.
// On systems where whole applications own windows, OnClosing is also triggered when the user asks to close the application.
// If this handler returns true, the Window is closed as defined by Close above.
// If this handler returns false, the Window is not closed.
OnClosing(func() bool)
// Margined and SetMargined get and set whether the contents of the Window have a margin around them.
// The size of the margin is platform-dependent.
Margined() bool
SetMargined(margined bool)
windowDialog
}
// NewWindow creates a new Window with the given title text, size, and control.
func NewWindow(title string, width int, height int, control Control) Window {
return newWindow(title, width, height, control)
}

View File

@ -1,94 +0,0 @@
// 8 july 2014
package ui
import (
"unsafe"
)
// #include "objc_darwin.h"
import "C"
type window struct {
id C.id
closing *event
child Control
container *container
margined bool
}
func newWindow(title string, width int, height int, control Control) *window {
id := C.newWindow(C.intptr_t(width), C.intptr_t(height))
ctitle := C.CString(title)
defer C.free(unsafe.Pointer(ctitle))
C.windowSetTitle(id, ctitle)
w := &window{
id: id,
closing: newEvent(),
child: control,
container: newContainer(),
}
C.windowSetDelegate(w.id, unsafe.Pointer(w))
C.windowSetContentView(w.id, w.container.id)
w.child.setParent(w.container.parent())
// trigger an initial resize
return w
}
func (w *window) Title() string {
return C.GoString(C.windowTitle(w.id))
}
func (w *window) SetTitle(title string) {
ctitle := C.CString(title)
defer C.free(unsafe.Pointer(ctitle))
C.windowSetTitle(w.id, ctitle)
}
func (w *window) Show() {
C.windowShow(w.id)
// trigger an initial resize
// TODO fine-tune this
windowResized(unsafe.Pointer(w))
}
func (w *window) Hide() {
C.windowHide(w.id)
}
func (w *window) Close() {
C.windowClose(w.id)
}
func (w *window) OnClosing(e func() bool) {
w.closing.setbool(e)
}
func (w *window) Margined() bool {
return w.margined
}
func (w *window) SetMargined(margined bool) {
w.margined = margined
}
//export windowClosing
func windowClosing(xw unsafe.Pointer) C.BOOL {
w := (*window)(unsafe.Pointer(xw))
close := w.closing.fire()
if close {
return C.YES
}
return C.NO
}
//export windowResized
func windowResized(data unsafe.Pointer) {
w := (*window)(data)
a := w.container.allocation(w.margined)
d := w.beginResize()
w.child.resize(int(a.x), int(a.y), int(a.width), int(a.height), d)
}

View File

@ -1,91 +0,0 @@
// 8 july 2014
#import "objc_darwin.h"
#import "_cgo_export.h"
#import <Cocoa/Cocoa.h>
#define toNSWindow(x) ((NSWindow *) (x))
#define toNSView(x) ((NSView *) (x))
@interface goWindowDelegate : NSObject <NSWindowDelegate> {
@public
void *gowin;
}
@end
@implementation goWindowDelegate
- (BOOL)windowShouldClose:(id)win
{
return windowClosing(self->gowin);
}
- (void)windowDidResize:(NSNotification *)note
{
windowResized(self->gowin);
}
@end
id newWindow(intptr_t width, intptr_t height)
{
NSWindow *w;
NSTextView *tv;
w = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, (CGFloat) width, (CGFloat) height)
styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)
backing:NSBackingStoreBuffered
defer:YES];
// we do not want substitutions
// text fields, labels, etc. take their smart quotes and other autocorrect settings from their parent window, which provides a shared "field editor"
// so we have to turn them off here
// thanks akempgen in irc.freenode.net/#macdev
// for some reason, this selector returns NSText but is documented to return NSTextView...
disableAutocorrect((id) [w fieldEditor:YES forObject:nil]);
return w;
}
void windowSetDelegate(id win, void *w)
{
goWindowDelegate *d;
d = [goWindowDelegate new];
d->gowin = w;
[toNSWindow(win) setDelegate:d];
}
void windowSetContentView(id win, id view)
{
[toNSWindow(win) setContentView:toNSView(view)];
}
const char *windowTitle(id win)
{
return [[toNSWindow(win) title] UTF8String];
}
void windowSetTitle(id win, const char * title)
{
[toNSWindow(win) setTitle:[NSString stringWithUTF8String:title]];
}
void windowShow(id win)
{
[toNSWindow(win) makeKeyAndOrderFront:toNSWindow(win)];
// no need to worry about reshowing the window initially; that's handled by our container view (container_darwin.m)
}
void windowHide(id win)
{
[toNSWindow(win) orderOut:toNSWindow(win)];
}
void windowClose(id win)
{
[toNSWindow(win) close];
}
id windowContentView(id win)
{
return (id) [toNSWindow(win) contentView];
}

View File

@ -1,116 +0,0 @@
// +build !windows,!darwin
// 7 july 2014
package ui
import (
"unsafe"
)
// #include "gtk_unix.h"
// extern gboolean windowClosing(GtkWidget *, GdkEvent *, gpointer);
// extern void windowResized(GtkWidget *, GdkRectangle *, gpointer);
import "C"
type window struct {
widget *C.GtkWidget
wc *C.GtkContainer
bin *C.GtkBin
window *C.GtkWindow
group *C.GtkWindowGroup
closing *event
child Control
container *container
margined bool
}
func newWindow(title string, width int, height int, control Control) *window {
widget := C.gtk_window_new(C.GTK_WINDOW_TOPLEVEL)
ctitle := togstr(title)
defer freegstr(ctitle)
w := &window{
widget: widget,
wc: (*C.GtkContainer)(unsafe.Pointer(widget)),
bin: (*C.GtkBin)(unsafe.Pointer(widget)),
window: (*C.GtkWindow)(unsafe.Pointer(widget)),
closing: newEvent(),
child: control,
}
C.gtk_window_set_title(w.window, ctitle)
g_signal_connect(
C.gpointer(unsafe.Pointer(w.window)),
"delete-event",
C.GCallback(C.windowClosing),
C.gpointer(unsafe.Pointer(w)))
C.gtk_window_resize(w.window, C.gint(width), C.gint(height))
w.container = newContainer()
w.child.setParent(w.container.parent())
w.container.setParent(&controlParent{w.wc})
// notice that we connect this to the container
g_signal_connect_after( // so we get it after the child container has been allocated
C.gpointer(unsafe.Pointer(w.container.widget)),
"size-allocate",
C.GCallback(C.windowResized),
C.gpointer(unsafe.Pointer(w)))
// for dialogs; otherwise, they will be modal to all windows, not just this one
w.group = C.gtk_window_group_new()
C.gtk_window_group_add_window(w.group, w.window)
return w
}
func (w *window) Title() string {
return fromgstr(C.gtk_window_get_title(w.window))
}
func (w *window) SetTitle(title string) {
ctitle := togstr(title)
defer freegstr(ctitle)
C.gtk_window_set_title(w.window, ctitle)
}
func (w *window) Show() {
C.gtk_widget_show_all(w.widget)
}
func (w *window) Hide() {
C.gtk_widget_hide(w.widget)
}
func (w *window) Close() {
C.gtk_widget_destroy(w.widget)
}
func (w *window) OnClosing(e func() bool) {
w.closing.setbool(e)
}
func (w *window) Margined() bool {
return w.margined
}
func (w *window) SetMargined(margined bool) {
w.margined = margined
}
//export windowClosing
func windowClosing(wid *C.GtkWidget, e *C.GdkEvent, data C.gpointer) C.gboolean {
w := (*window)(unsafe.Pointer(data))
close := w.closing.fire()
if close {
return C.GDK_EVENT_PROPAGATE // will do gtk_widget_destroy(), which is what we want (thanks ebassi in irc.gimp.net/#gtk+)
}
return C.GDK_EVENT_STOP // keeps window alive
}
//export windowResized
func windowResized(wid *C.GtkWidget, r *C.GdkRectangle, data C.gpointer) {
w := (*window)(unsafe.Pointer(data))
a := w.container.allocation(w.margined)
d := w.beginResize()
w.child.resize(int(a.x), int(a.y), int(a.width), int(a.height), d)
}

View File

@ -1,84 +0,0 @@
// 17 july 2014
#include "winapi_windows.h"
#include "_cgo_export.h"
#define windowclass L"gouiwindow"
#define windowBackground ((HBRUSH) (COLOR_BTNFACE + 1))
static LRESULT CALLBACK windowWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
void *data;
RECT r;
LRESULT lResult;
data = (void *) getWindowData(hwnd, uMsg, wParam, lParam, &lResult);
if (data == NULL)
return lResult;
if (sharedWndProc(hwnd, uMsg, wParam, lParam, &lResult))
return lResult;
switch (uMsg) {
case WM_PRINTCLIENT:
// the return value of this message is not documented
// just to be safe, do this first, returning its value later
lResult = DefWindowProcW(hwnd, uMsg, wParam, lParam);
if (GetClientRect(hwnd, &r) == 0)
xpanic("error getting client rect for Window in WM_PRINTCLIENT", GetLastError());
if (FillRect((HDC) wParam, &r, windowBackground) == 0)
xpanic("error filling WM_PRINTCLIENT DC with window background color", GetLastError());
return lResult;
case WM_SIZE:
if (GetClientRect(hwnd, &r) == 0)
xpanic("error getting client rect for Window in WM_SIZE", GetLastError());
windowResize(data, &r);
return 0;
case WM_CLOSE:
windowClosing(data);
return 0;
default:
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
xmissedmsg("Window", "windowWndProc()", uMsg);
return 0; // unreached
}
DWORD makeWindowWindowClass(char **errmsg)
{
WNDCLASSW wc;
ZeroMemory(&wc, sizeof (WNDCLASSW));
wc.lpfnWndProc = windowWndProc;
wc.hInstance = hInstance;
wc.hIcon = hDefaultIcon;
wc.hCursor = hArrowCursor;
wc.hbrBackground = windowBackground;
wc.lpszClassName = windowclass;
if (RegisterClassW(&wc) == 0) {
*errmsg = "error registering Window window class";
return GetLastError();
}
return 0;
}
HWND newWindow(LPWSTR title, int width, int height, void *data)
{
HWND hwnd;
hwnd = CreateWindowExW(
0,
windowclass, title,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
width, height,
NULL, NULL, hInstance, data);
if (hwnd == NULL)
xpanic("Window creation failed", GetLastError());
return hwnd;
}
void windowClose(HWND hwnd)
{
if (DestroyWindow(hwnd) == 0)
xpanic("error destroying window", GetLastError());
}

View File

@ -1,103 +0,0 @@
// 12 july 2014
package ui
import (
"fmt"
"syscall"
"unsafe"
)
// #include "winapi_windows.h"
import "C"
type window struct {
hwnd C.HWND
shownbefore bool
closing *event
child Control
margined bool
}
func makeWindowWindowClass() error {
var errmsg *C.char
err := C.makeWindowWindowClass(&errmsg)
if err != 0 || errmsg != nil {
return fmt.Errorf("%s: %v", C.GoString(errmsg), syscall.Errno(err))
}
return nil
}
func newWindow(title string, width int, height int, control Control) *window {
w := &window{
closing: newEvent(),
child: control,
}
w.hwnd = C.newWindow(toUTF16(title), C.int(width), C.int(height), unsafe.Pointer(w))
hresult := C.EnableThemeDialogTexture(w.hwnd, C.ETDT_ENABLE|C.ETDT_USETABTEXTURE)
if hresult != C.S_OK {
panic(fmt.Errorf("error setting tab background texture on Window; HRESULT: 0x%X", hresult))
}
w.child.setParent(&controlParent{w.hwnd})
return w
}
func (w *window) Title() string {
return getWindowText(w.hwnd)
}
func (w *window) SetTitle(title string) {
C.setWindowText(w.hwnd, toUTF16(title))
}
func (w *window) Show() {
if !w.shownbefore {
C.ShowWindow(w.hwnd, C.nCmdShow)
C.updateWindow(w.hwnd)
w.shownbefore = true
} else {
C.ShowWindow(w.hwnd, C.SW_SHOW)
}
}
func (w *window) Hide() {
C.ShowWindow(w.hwnd, C.SW_HIDE)
}
func (w *window) Close() {
C.windowClose(w.hwnd)
}
func (w *window) OnClosing(e func() bool) {
w.closing.setbool(e)
}
func (w *window) Margined() bool {
return w.margined
}
func (w *window) SetMargined(margined bool) {
w.margined = margined
}
//export windowResize
func windowResize(data unsafe.Pointer, r *C.RECT) {
w := (*window)(data)
d := w.beginResize()
if w.margined {
marginRectDLU(r, marginDialogUnits, marginDialogUnits, marginDialogUnits, marginDialogUnits, d)
}
w.child.resize(int(r.left), int (r.top), int(r.right - r.left), int(r.bottom - r.top), d)
}
//export windowClosing
func windowClosing(data unsafe.Pointer) {
w := (*window)(data)
close := w.closing.fire()
if close {
C.windowClose(w.hwnd)
}
}

View File

@ -1,128 +0,0 @@
// 21 august 2014
package ui
import (
"image"
"image/color"
"image/draw"
"strconv"
)
type repainter struct {
img *image.RGBA
area Area
x TextField
y TextField
width TextField
height TextField
repaint Button
all Button
grid Grid
xv int
yv int
wv int
hv int
}
func newRepainter(times int) *repainter {
r := new(repainter)
r.img = tileImage(times)
r.area = NewArea(r.img.Rect.Dx(), r.img.Rect.Dy(), r)
r.area.OnTextFieldDismissed(r.tfdone)
r.x = NewTextField()
r.x.OnChanged(r.setx)
r.y = NewTextField()
r.y.OnChanged(r.sety)
r.width = NewTextField()
r.width.OnChanged(r.setwidth)
r.height = NewTextField()
r.height.OnChanged(r.setheight)
r.repaint = NewButton("Rect")
r.repaint.OnClicked(r.dorect)
r.all = NewButton("All")
r.all.OnClicked(r.doall)
grid := NewGrid()
grid.Add(r.area, nil, South, true, Fill, true, Fill, 3, 6)
grid.Add(r.x, nil, East, true, Fill, false, LeftTop, 1, 1)
grid.Add(r.y, nil, South, true, Fill, false, LeftTop, 1, 1)
grid.Add(r.width, nil, South, true, Fill, false, LeftTop, 1, 1)
grid.Add(r.height, nil, South, true, Fill, false, LeftTop, 1, 1)
grid.Add(r.repaint, nil, South, true, Fill, true, Fill, 1, 1)
grid.Add(r.all, nil, South, true, Center, false, LeftTop, 1, 1)
r.grid = grid
r.grid.SetPadded(*spaced)
return r
}
func (r *repainter) Paint(rect image.Rectangle) *image.RGBA {
return r.img.SubImage(rect).(*image.RGBA)
}
func (r *repainter) Mouse(me MouseEvent) {
if me.Up == 1 {
r.area.OpenTextFieldAt(me.Pos.X, me.Pos.Y)
}
}
func (r *repainter) tfdone() {
println(r.area.TextFieldText())
}
func (r *repainter) Key(ke KeyEvent) bool { return false }
func (r *repainter) setx() {
i, err := strconv.Atoi(r.x.Text())
if err != nil {
r.x.Invalid(err.Error())
return
}
r.x.Invalid("")
r.xv = i
}
func (r *repainter) sety() {
i, err := strconv.Atoi(r.y.Text())
if err != nil {
r.y.Invalid(err.Error())
return
}
r.y.Invalid("")
r.yv = i
}
func (r *repainter) setwidth() {
i, err := strconv.Atoi(r.width.Text())
if err != nil {
r.width.Invalid(err.Error())
return
}
r.width.Invalid("")
r.wv = i
}
func (r *repainter) setheight() {
i, err := strconv.Atoi(r.height.Text())
if err != nil {
r.height.Invalid(err.Error())
return
}
r.height.Invalid("")
r.hv = i
}
func (r *repainter) alter(rect image.Rectangle, c color.Color) {
draw.Draw(r.img, rect, &image.Uniform{c}, image.ZP, draw.Over)
}
func (r *repainter) dorect() {
rect := image.Rect(r.xv, r.yv, r.xv+r.wv, r.yv+r.hv)
r.alter(rect, color.RGBA{255, 0, 255, 128})
r.area.Repaint(rect)
}
func (r *repainter) doall() {
r.alter(r.img.Rect, color.RGBA{255, 255, 0, 128})
r.area.RepaintAll()
}

View File

@ -1,302 +0,0 @@
// 8 july 2014
package ui
// This file is called zz_test.go to keep it separate from the other files in this package (and because go test won't accept just test.go)
import (
"flag"
"fmt"
"image"
"image/color"
"image/draw"
"reflect"
"strings"
"testing"
"time"
)
var closeOnClick = flag.Bool("close", false, "close on click")
var smallWindow = flag.Bool("small", false, "open a small window (test Mac OS X initial control sizing)")
var spaced = flag.Bool("spaced", false, "enable spacing")
func newHorizontalStack(c ...Control) Stack {
s := NewHorizontalStack(c...)
s.SetPadded(*spaced)
return s
}
func newVerticalStack(c ...Control) Stack {
s := NewVerticalStack(c...)
s.SetPadded(*spaced)
return s
}
func newSimpleGrid(n int, c ...Control) SimpleGrid {
g := NewSimpleGrid(n, c...)
g.SetPadded(*spaced)
return g
}
type dtype struct {
Name string
Address string
}
var ddata = []dtype{
{"alpha", "beta"},
{"gamma", "delta"},
{"epsilon", "zeta"},
{"eta", "theta"},
{"iota", "kappa"},
}
type testwin struct {
t Tab
w Window
repainter *repainter
fe *ForeignEvent
festack Stack
festart Button
felabel Label
festop Button
vedit TextField
openbtn Button
fnlabel Label
icons []icon
il ImageList
icontbl Table
group2 Group
group Group
simpleGrid SimpleGrid
nt Tab
a Area
spw Stack
sph Stack
s Stack
l Label
table Table
b Button
c Checkbox
e TextField
e2 TextField
wsmall Window
}
type areaHandler struct {
handled bool
}
func (a *areaHandler) Paint(r image.Rectangle) *image.RGBA {
i := image.NewRGBA(r)
draw.Draw(i, r, &image.Uniform{color.RGBA{128, 0, 128, 255}}, image.ZP, draw.Src)
return i
}
func (a *areaHandler) Mouse(me MouseEvent) { fmt.Printf("%#v\n", me) }
func (a *areaHandler) Key(ke KeyEvent) bool { fmt.Printf("%#v %q\n", ke, ke.Key); return a.handled }
func (tw *testwin) openFile(fn string) {
if fn == "" {
fn = "<no file selected>"
}
tw.fnlabel.SetText(fn)
}
func (tw *testwin) addfe() {
tw.festart = NewButton("Start")
tw.festart.OnClicked(func() {
if tw.fe != nil {
tw.fe.Stop()
}
ticker := time.NewTicker(1 * time.Second)
tw.fe = NewForeignEvent(ticker.C, func(d interface{}) {
t := d.(time.Time)
tw.felabel.SetText(t.String())
})
})
tw.felabel = NewLabel("<stopped>")
tw.festop = NewButton("Stop")
tw.festop.OnClicked(func() {
if tw.fe != nil {
tw.fe.Stop()
tw.felabel.SetText("<stopped>")
tw.fe = nil
}
})
tw.vedit = NewTextField()
tw.vedit.OnChanged(func() {
if strings.Contains(tw.vedit.Text(), "bad") {
tw.vedit.Invalid("bad entered")
} else {
tw.vedit.Invalid("")
}
})
tw.openbtn = NewButton("Open")
tw.openbtn.OnClicked(func() {
OpenFile(tw.w, tw.openFile)
})
tw.fnlabel = NewLabel("<no file selected>")
tw.festack = newVerticalStack(tw.festart,
tw.felabel,
tw.festop,
NewCheckbox("This is a checkbox test"),
Space(),
tw.vedit,
Space(),
NewCheckbox("This is a checkbox test"),
tw.openbtn, tw.fnlabel)
tw.festack.SetStretchy(4)
tw.festack.SetStretchy(6)
tw.festack = newHorizontalStack(tw.festack, Space())
tw.festack.SetStretchy(0)
tw.festack.SetStretchy(1)
tw.t.Append("Foreign Events", tw.festack)
}
func (tw *testwin) make(done chan struct{}) {
tw.t = NewTab()
tw.w = NewWindow("Hello", 320, 240, tw.t)
tw.w.SetMargined(*spaced)
tw.w.OnClosing(func() bool {
if *closeOnClick {
panic("window closed normally in close on click mode (should not happen)")
}
println("window close event received")
Stop()
done <- struct{}{}
return true
})
tw.icons, tw.il = readIcons() // repainter uses these
tw.repainter = newRepainter(15)
tw.t.Append("Repaint", tw.repainter.grid)
tw.addfe()
tw.icontbl = NewTable(reflect.TypeOf(icon{}))
tw.icontbl.Lock()
idq := tw.icontbl.Data().(*[]icon)
*idq = tw.icons
tw.icontbl.Unlock()
tw.icontbl.LoadImageList(tw.il)
tw.icontbl.OnSelected(func() {
s := fmt.Sprintf("%d ", tw.icontbl.Selected())
tw.icontbl.RLock()
defer tw.icontbl.RUnlock()
idq := tw.icontbl.Data().(*[]icon)
for _, v := range *idq {
s += strings.ToUpper(fmt.Sprintf("%v", v.Bool)[0:1]) + " "
}
tw.w.SetTitle(s)
})
tw.t.Append("Image List Table", tw.icontbl)
tw.group2 = NewGroup("Group", NewButton("Button in Group"))
tw.t.Append("Empty Group", NewGroup("Group", Space()))
tw.t.Append("Filled Group", tw.group2)
tw.group2.SetMargined(*spaced)
tw.group = NewGroup("Group", newVerticalStack(NewCheckbox("Checkbox in Group")))
tw.group.SetMargined(*spaced)
tw.t.Append("Group", tw.group)
tw.simpleGrid = newSimpleGrid(3,
NewLabel("0,0"), NewTextField(), NewLabel("0,2"),
NewButton("1,0"), NewButton("1,1"), NewButton("1,2"),
NewLabel("2,0"), NewTextField(), NewLabel("2,2"))
tw.simpleGrid.SetFilling(2, 1)
tw.simpleGrid.SetFilling(1, 2)
tw.simpleGrid.SetStretchy(1, 1)
tw.t.Append("Simple Grid", tw.simpleGrid)
tw.t.Append("Blank Tab", NewTab())
tw.nt = NewTab()
tw.nt.Append("Tab 1", Space())
tw.nt.Append("Tab 2", Space())
tw.t.Append("Tab", tw.nt)
tw.t.Append("Space", Space())
tw.a = NewArea(200, 200, &areaHandler{false})
tw.t.Append("Area", tw.a)
tw.spw = newHorizontalStack(
NewButton("hello"),
NewCheckbox("hello"),
NewTextField(),
NewPasswordField(),
NewTable(reflect.TypeOf(struct{ A, B, C int }{})),
NewLabel("hello"))
tw.t.Append("Pref Width", tw.spw)
tw.sph = newVerticalStack(
NewButton("hello"),
NewCheckbox("hello"),
NewTextField(),
NewPasswordField(),
NewTable(reflect.TypeOf(struct{ A, B, C int }{})),
NewLabel("hello ÉÀÔ"))
tw.t.Append("Pref Height", tw.sph)
stack1 := newHorizontalStack(NewLabel("Test"), NewTextField())
stack1.SetStretchy(1)
stack2 := newHorizontalStack(NewLabel("ÉÀÔ"), NewTextField())
stack2.SetStretchy(1)
stack3 := newHorizontalStack(NewLabel("Test 2"),
NewTable(reflect.TypeOf(struct{ A, B, C int }{})))
stack3.SetStretchy(1)
tw.s = newVerticalStack(stack1, stack2, stack3)
tw.s.SetStretchy(2)
tw.t.Append("Stack", tw.s)
tw.l = NewLabel("hello")
tw.t.Append("Label", tw.l)
tw.table = NewTable(reflect.TypeOf(ddata[0]))
tw.table.Lock()
dq := tw.table.Data().(*[]dtype)
*dq = ddata
tw.table.Unlock()
tw.t.Append("Table", tw.table)
tw.b = NewButton("There")
if *closeOnClick {
tw.b.SetText("Click to Close")
}
tw.b.OnClicked(func() {
println("in OnClicked()")
if *closeOnClick {
tw.w.Close()
Stop()
done <- struct{}{}
}
})
tw.t.Append("Button", tw.b)
tw.c = NewCheckbox("You Should Now See Me Instead")
tw.c.OnToggled(func() {
tw.w.SetTitle(fmt.Sprint(tw.c.Checked()))
})
tw.t.Append("Checkbox", tw.c)
tw.e = NewTextField()
tw.t.Append("Text Field", tw.e)
tw.e2 = NewPasswordField()
tw.t.Append("Password Field", tw.e2)
tw.w.Show()
if *smallWindow {
tw.wsmall = NewWindow("Small", 80, 80,
newVerticalStack(
NewButton("Small"),
NewButton("Small 2"),
NewArea(200, 200, &areaHandler{true})))
tw.wsmall.Show()
}
}
// this must be on the heap thanks to moving stacks
// soon even this won't be enough...
var tw *testwin
// because Cocoa hates being run off the main thread, even if it's run exclusively off the main thread
func init() {
flag.Parse()
go func() {
tw = new(testwin)
done := make(chan struct{})
Do(func() { tw.make(done) })
<-done
}()
err := Go()
if err != nil {
panic(err)
}
}
func TestDummy(t *testing.T) {
// do nothing
}