Removed prev/.
This commit is contained in:
parent
c9b32c1333
commit
52f7d276a6
|
@ -1 +0,0 @@
|
|||
This directory contains the pre-libui package ui and its various development files. They will be removed at a later date. Do not use this package.
|
370
prev/area.go
370
prev/area.go
|
@ -1,370 +0,0 @@
|
|||
// 14 march 2014
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/draw"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Area represents a blank canvas upon which programs may draw anything and receive arbitrary events from the user.
|
||||
// An Area has an explicit size, represented in pixels, that may be different from the size shown in its Window.
|
||||
// For information on scrollbars, see "Scrollbars" in the Overview.
|
||||
// The coordinate system of an Area always has an origin of (0,0) which maps to the top-left corner; all image.Points and image.Rectangles sent across Area's channels conform to this.
|
||||
// The size of an Area must be at least 1x1 (that is, neither its width nor its height may be zero or negative).
|
||||
// For control layout purposes, an Area prefers to be at the size you set it to (so if an Area is not stretchy in its layout, it will ask to have that size).
|
||||
//
|
||||
// To handle events to the Area, an Area must be paired with an AreaHandler.
|
||||
// See AreaHandler for details.
|
||||
//
|
||||
// Area will accept keyboard focus if tabbed into, but will refuse to relinquish keyboard focus if tabbed out.
|
||||
//
|
||||
// Do not use an Area if you intend to read text.
|
||||
// Area reads keys based on their position on a standard
|
||||
// 101-key keyboard, and does no character processing.
|
||||
// Character processing methods differ across operating
|
||||
// systems; trying ot recreate these yourself is only going
|
||||
// to lead to trouble.
|
||||
// If you absolutely need to enter text somehow, use OpenTextFieldAt() and its related methods.
|
||||
type Area interface {
|
||||
Control
|
||||
|
||||
// SetSize sets the Area's internal drawing size.
|
||||
// It has no effect on the actual control size.
|
||||
// SetSize will also signal the entirety of the Area to be redrawn as in RepaintAll.
|
||||
// It panics if width or height is zero or negative.
|
||||
SetSize(width int, height int)
|
||||
|
||||
// Repaint marks the given rectangle of the Area as needing to be redrawn.
|
||||
// The given rectangle is clipped to the Area's size.
|
||||
// If, after clipping, the rectangle is empty, Repaint does nothing.
|
||||
Repaint(r image.Rectangle)
|
||||
|
||||
// RepaintAll marks the entirety of the Area as needing to be redrawn.
|
||||
RepaintAll()
|
||||
|
||||
// OpenTextFieldAt opens a TextField with the top-left corner at the given coordinates of the Area.
|
||||
// It panics if the coordinates fall outside the Area.
|
||||
// Any text previously in the TextField (be it by the user or by a call to SetTextFieldText()) is retained.
|
||||
// The TextField receives the input focus so the user can type things; when the TextField loses the input focus, it hides itself and signals the event set by OnTextFieldDismissed.
|
||||
// The TextField will also dismiss itself on some platforms when the user "completes editing"; the exact meaning of this is platform-specific.
|
||||
OpenTextFieldAt(x int, y int)
|
||||
|
||||
// TextFieldText and TextFieldSetText get and set the OpenTextFieldAt TextField's text, respectively.
|
||||
TextFieldText() string
|
||||
SetTextFieldText(text string)
|
||||
|
||||
// OnTextFieldDismissed is an event that is fired when the OpenTextFieldAt TextField is dismissed.
|
||||
OnTextFieldDismissed(f func())
|
||||
}
|
||||
|
||||
type areabase struct {
|
||||
width int
|
||||
height int
|
||||
handler AreaHandler
|
||||
}
|
||||
|
||||
// AreaHandler represents the events that an Area should respond to.
|
||||
// These methods are all executed on the main goroutine, not necessarily the same one that you created the AreaHandler in; you are responsible for the thread safety of any members of the actual type that implements ths interface.
|
||||
// (Having to use this interface does not strike me as being particularly Go-like, but the nature of Paint makes channel-based event handling a non-option; in practice, deadlocks occur.)
|
||||
type AreaHandler interface {
|
||||
// Paint is called when the Area needs to be redrawn.
|
||||
// The part of the Area that needs to be redrawn is stored in cliprect.
|
||||
// Before Paint() is called, this region is cleared with a system-defined background color.
|
||||
// You MUST handle this event, and you MUST return a valid image, otherwise deadlocks and panicking will occur.
|
||||
// The image returned must have the same size as rect (but does not have to have the same origin points).
|
||||
// Example:
|
||||
// imgFromFile, _, err := image.Decode(file)
|
||||
// if err != nil { panic(err) }
|
||||
// img := image.NewRGBA(imgFromFile.Rect)
|
||||
// draw.Draw(img, img.Rect, imgFromFile, image.ZP, draw.Over)
|
||||
// // ...
|
||||
// func (h *myAreaHandler) Paint(rect image.Rectangle) *image.RGBA {
|
||||
// return img.SubImage(rect).(*image.RGBA)
|
||||
// }
|
||||
Paint(cliprect image.Rectangle) *image.RGBA
|
||||
|
||||
// Mouse is called when the Area receives a mouse event.
|
||||
// You are allowed to do nothing in this handler (to ignore mouse events).
|
||||
// See MouseEvent for details.
|
||||
// After handling the mouse event, package ui will decide whether to perform platform-dependent event chain continuation based on that platform's designated action (so it is not possible to override global mouse events this way).
|
||||
Mouse(e MouseEvent)
|
||||
|
||||
// Key is called when the Area receives a keyboard event.
|
||||
// Return true to indicate that you handled the event; return false to indicate that you did not and let the system handle the event.
|
||||
// You are allowed to do nothing in this handler (to ignore keyboard events); in this case, return false.
|
||||
// See KeyEvent for details.
|
||||
Key(e KeyEvent) (handled bool)
|
||||
}
|
||||
|
||||
// MouseEvent contains all the information for a mous event sent by Area.Mouse.
|
||||
// Mouse button IDs start at 1, with 1 being the left mouse button, 2 being the middle mouse button, and 3 being the right mouse button.
|
||||
// If additional buttons are supported, they will be returned with 4 being the first additional button.
|
||||
// For example, on Unix systems where mouse buttons 4 through 7 are pseudobuttons for the scroll wheel directions, the next button, button 8, will be returned as 4, 9 as 5, etc.
|
||||
// The association between button numbers and physical buttons are system-defined.
|
||||
// For example, on Windows, buttons 4 and 5 are mapped to what are internally referred to as "XBUTTON1" and "XBUTTON2", which often correspond to the dedicated back/forward navigation buttons on the sides of many mice.
|
||||
// The examples here are NOT a guarantee as to how many buttons maximum will be available on a given system.
|
||||
//
|
||||
// If the user clicked on the Area to switch to the Window it is contained in from another window in the OS, the Area will receive a MouseEvent for that click.
|
||||
type MouseEvent struct {
|
||||
// Pos is the position of the mouse in the Area at the time of the event.
|
||||
Pos image.Point
|
||||
|
||||
// If the event was generated by a mouse button being pressed, Down contains the ID of that button.
|
||||
// Otherwise, Down contains 0.
|
||||
// If Down contains nonzero, the Area will also receive keyboard focus.
|
||||
Down uint
|
||||
|
||||
// If the event was generated by a mouse button being released, Up contains the ID of that button.
|
||||
// Otherwise, Up contains 0.
|
||||
// If both Down and Up are 0, the event represents mouse movement (with optional held buttons for dragging; see below).
|
||||
// Down and Up shall not both be nonzero.
|
||||
Up uint
|
||||
|
||||
// If Down is nonzero, Count indicates the number of clicks: 1 for single-click, 2 for double-click, 3 for triple-click, and so on.
|
||||
// The order of events will be Down:Count=1 -> Up -> Down:Count=2 -> Up -> Down:Count=3 -> Up -> ...
|
||||
Count uint
|
||||
|
||||
// Modifiers is a bit mask indicating the modifier keys being held during the event.
|
||||
Modifiers Modifiers
|
||||
|
||||
// Held is a slice of button IDs that indicate which mouse buttons are being held during the event.
|
||||
// Held will not include Down and Up.
|
||||
// Held will be sorted.
|
||||
// Only buttons 1, 2, and 3 are guaranteed to be detected by Held properly; whether or not any others are is implementation-defined.
|
||||
//
|
||||
// If Held is non-empty but Up and Down are both zero, the mouse is being dragged, with all the buttons in Held being held.
|
||||
// Whether or not a drag into an Area generates MouseEvents is implementation-defined.
|
||||
// Whether or not a drag over an Area when the program is inactive generates MouseEvents is also implementation-defined.
|
||||
// Moving the mouse over an Area when the program is inactive and no buttons are held will, however, generate MouseEvents.
|
||||
Held []uint
|
||||
}
|
||||
|
||||
// HeldBits returns Held as a bit mask.
|
||||
// Bit 0 maps to button 1, bit 1 maps to button 2, etc.
|
||||
func (e MouseEvent) HeldBits() (h uintptr) {
|
||||
for _, x := range e.Held {
|
||||
h |= uintptr(1) << (x - 1)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
// A KeyEvent represents a keypress in an Area.
|
||||
//
|
||||
// Key presses are based on their positions on a standard
|
||||
// 101-key keyboard found on most computers. The
|
||||
// names chosen for keys here are based on their names
|
||||
// on US English QWERTY keyboards; see Key for details.
|
||||
//
|
||||
// If a key is pressed that is not supported by Key, ExtKey,
|
||||
// or Modifiers, no KeyEvent will be produced and package ui will behave as if false was returned from the event handler.
|
||||
type KeyEvent struct {
|
||||
// Key is a byte representing a character pressed
|
||||
// in the typewriter section of the keyboard.
|
||||
// The value, which is independent of whether the
|
||||
// Shift key is held, is a constant with one of the
|
||||
// following (case-sensitive) values, drawn according
|
||||
// to the key's position on the keyboard.
|
||||
// ` 1 2 3 4 5 6 7 8 9 0 - =
|
||||
// q w e r t y u i o p [ ] \
|
||||
// a s d f g h j k l ; '
|
||||
// z x c v b n m , . /
|
||||
// The actual key entered will be the key at the respective
|
||||
// position on the user's keyboard, regardless of the actual
|
||||
// layout. (Some keyboards move \ to either the row above
|
||||
// or the row below but in roughly the same spot; this is
|
||||
// accounted for. Some keyboards have an additonal key
|
||||
// to the left of 'z' or additional keys to the right of '='; these
|
||||
// cannot be read.)
|
||||
// In addition, Key will contain
|
||||
// - ' ' (space) if the spacebar was pressed
|
||||
// - '\t' if Tab was pressed, regardless of Modifiers
|
||||
// - '\n' if the typewriter Enter key was pressed
|
||||
// - '\b' if the typewriter Backspace key was pressed
|
||||
// If this value is zero, see ExtKey.
|
||||
Key byte
|
||||
|
||||
// If Key is zero, ExtKey contains a predeclared identifier
|
||||
// naming an extended key. See ExtKey for details.
|
||||
// If both Key and ExtKey are zero, a Modifier by itself
|
||||
// was pressed. Key and ExtKey will not both be nonzero.
|
||||
ExtKey ExtKey
|
||||
|
||||
// If both Key and ExtKey are zero, Modifier will contain exactly one of its bits set, indicating which Modifier was pressed or released.
|
||||
// As with Modifiers itself, there is no way to differentiate between left and right modifier keys.
|
||||
// As such, the result of pressing and/or releasing both left and right of the same Modifier is system-defined.
|
||||
// Furthermore, the result of holding down a Key or ExtKey, then pressing a Modifier, and then releasing the original key is system-defined.
|
||||
// Under no condition shall Key, ExtKey, AND Modifier all be zero.
|
||||
Modifier Modifiers
|
||||
|
||||
// Modifiers contains all the modifier keys currently being held at the time of the KeyEvent.
|
||||
// If Modifier is nonzero, Modifiers will not contain Modifier itself.
|
||||
Modifiers Modifiers
|
||||
|
||||
// If Up is true, the key was released; if not, the key was pressed.
|
||||
// There is no guarantee that all pressed keys shall have
|
||||
// corresponding release events (for instance, if the user switches
|
||||
// programs while holding the key down, then releases the key).
|
||||
// Keys that have been held down are reported as multiple
|
||||
// key press events.
|
||||
Up bool
|
||||
}
|
||||
|
||||
// ExtKey represents keys that are not in the typewriter section of the keyboard.
|
||||
type ExtKey uintptr
|
||||
|
||||
const (
|
||||
Escape ExtKey = iota + 1
|
||||
Insert // equivalent to "Help" on Apple keyboards
|
||||
Delete
|
||||
Home
|
||||
End
|
||||
PageUp
|
||||
PageDown
|
||||
Up
|
||||
Down
|
||||
Left
|
||||
Right
|
||||
F1 // F1..F12 are guaranteed to be consecutive
|
||||
F2
|
||||
F3
|
||||
F4
|
||||
F5
|
||||
F6
|
||||
F7
|
||||
F8
|
||||
F9
|
||||
F10
|
||||
F11
|
||||
F12
|
||||
N0 // numpad keys; independent of Num Lock state
|
||||
N1 // N0..N9 are guaranteed to be consecutive
|
||||
N2
|
||||
N3
|
||||
N4
|
||||
N5
|
||||
N6
|
||||
N7
|
||||
N8
|
||||
N9
|
||||
NDot
|
||||
NEnter
|
||||
NAdd
|
||||
NSubtract
|
||||
NMultiply
|
||||
NDivide
|
||||
_nextkeys // for sanity check
|
||||
)
|
||||
|
||||
// EffectiveKey returns e.Key if it is set.
|
||||
// Otherwise, if e.ExtKey denotes a numpad key,
|
||||
// EffectiveKey returns the equivalent e.Key value
|
||||
// ('0'..'9', '.', '\n', '+', '-', '*', or '/').
|
||||
// Otherwise, EffectiveKey returns zero.
|
||||
func (e KeyEvent) EffectiveKey() byte {
|
||||
if e.Key != 0 {
|
||||
return e.Key
|
||||
}
|
||||
k := e.ExtKey
|
||||
switch {
|
||||
case k >= N0 && k <= N9:
|
||||
return byte(k-N0) + '0'
|
||||
case k == NDot:
|
||||
return '.'
|
||||
case k == NEnter:
|
||||
return '\n'
|
||||
case k == NAdd:
|
||||
return '+'
|
||||
case k == NSubtract:
|
||||
return '-'
|
||||
case k == NMultiply:
|
||||
return '*'
|
||||
case k == NDivide:
|
||||
return '/'
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Modifiers indicates modifier keys being held during an event.
|
||||
// There is no way to differentiate between left and right modifier keys.
|
||||
// As such, what KeyEvents get sent if the user does something unusual with both of a certain modifier key at once is undefined.
|
||||
type Modifiers uintptr
|
||||
|
||||
const (
|
||||
Ctrl Modifiers = 1 << iota // the keys labelled Ctrl or Control on all platforms
|
||||
Alt // the keys labelled Alt or Option or Meta on all platforms
|
||||
Shift // the Shift keys
|
||||
Super // the Super keys on platforms that have one, or the Windows keys on Windows, or the Command keys on Mac OS X
|
||||
)
|
||||
|
||||
func checkAreaSize(width int, height int, which string) {
|
||||
if width <= 0 || height <= 0 {
|
||||
panic(fmt.Errorf("invalid size %dx%d in %s", width, height, which))
|
||||
}
|
||||
}
|
||||
|
||||
// NewArea creates a new Area with the given size and handler.
|
||||
// It panics if handler is nil or if width or height is zero or negative.
|
||||
func NewArea(width int, height int, handler AreaHandler) Area {
|
||||
checkAreaSize(width, height, "NewArea()")
|
||||
if handler == nil {
|
||||
panic("handler passed to NewArea() must not be nil")
|
||||
}
|
||||
return newArea(&areabase{
|
||||
width: width,
|
||||
height: height,
|
||||
handler: handler,
|
||||
})
|
||||
}
|
||||
|
||||
// internal function, but shared by all system implementations: &img.Pix[0] is not necessarily the first pixel in the image
|
||||
func pixelDataPos(img *image.RGBA) int {
|
||||
return img.PixOffset(img.Rect.Min.X, img.Rect.Min.Y)
|
||||
}
|
||||
|
||||
func pixelData(img *image.RGBA) *uint8 {
|
||||
return &img.Pix[pixelDataPos(img)]
|
||||
}
|
||||
|
||||
// some platforms require pixels in ARGB order in their native endianness (because they treat the pixel array as an array of uint32s)
|
||||
// this does the conversion
|
||||
// you need to convert somewhere (Windows and cairo give us memory to use; Windows has stride==width but cairo might not)
|
||||
func toARGB(i *image.RGBA, memory uintptr, memstride int, toNRGBA bool) {
|
||||
var realbits []byte
|
||||
|
||||
rbs := (*reflect.SliceHeader)(unsafe.Pointer(&realbits))
|
||||
rbs.Data = memory
|
||||
rbs.Len = 4 * i.Rect.Dx() * i.Rect.Dy()
|
||||
rbs.Cap = rbs.Len
|
||||
p := pixelDataPos(i)
|
||||
q := 0
|
||||
iPix := i.Pix
|
||||
if toNRGBA { // for Windows image lists
|
||||
j := image.NewNRGBA(i.Rect)
|
||||
draw.Draw(j, j.Rect, i, i.Rect.Min, draw.Src)
|
||||
iPix = j.Pix
|
||||
}
|
||||
for y := i.Rect.Min.Y; y < i.Rect.Max.Y; y++ {
|
||||
nextp := p + i.Stride
|
||||
nextq := q + memstride
|
||||
for x := i.Rect.Min.X; x < i.Rect.Max.X; x++ {
|
||||
argb := uint32(iPix[p+3]) << 24 // A
|
||||
argb |= uint32(iPix[p+0]) << 16 // R
|
||||
argb |= uint32(iPix[p+1]) << 8 // G
|
||||
argb |= uint32(iPix[p+2]) // B
|
||||
// the magic of conversion
|
||||
native := (*[4]byte)(unsafe.Pointer(&argb))
|
||||
realbits[q+0] = native[0]
|
||||
realbits[q+1] = native[1]
|
||||
realbits[q+2] = native[2]
|
||||
realbits[q+3] = native[3]
|
||||
p += 4
|
||||
q += 4
|
||||
}
|
||||
p = nextp
|
||||
q = nextq
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -1,236 +0,0 @@
|
|||
// 13 may 2014
|
||||
|
||||
#include "objc_darwin.h"
|
||||
#include "_cgo_export.h"
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#define toNSEvent(x) ((NSEvent *) (x))
|
||||
#define toNSView(x) ((NSView *) (x))
|
||||
#define toNSObject(x) ((NSObject *) (x))
|
||||
#define toNSTextField(x) ((NSTextField *) (x))
|
||||
|
||||
#define toNSInteger(x) ((NSInteger) (x))
|
||||
#define fromNSInteger(x) ((intptr_t) (x))
|
||||
#define toNSUInteger(x) ((NSUInteger) (x))
|
||||
#define fromNSUInteger(x) ((uintptr_t) (x))
|
||||
|
||||
@interface goAreaView : NSView <NSTextFieldDelegate> {
|
||||
@public
|
||||
void *goarea;
|
||||
NSTrackingArea *trackingArea;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation goAreaView
|
||||
|
||||
- (id)initWithFrame:(NSRect)r
|
||||
{
|
||||
self = [super initWithFrame:r];
|
||||
if (self)
|
||||
[self retrack];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)drawRect:(NSRect)cliprect
|
||||
{
|
||||
struct xrect rect;
|
||||
|
||||
rect.x = (intptr_t) cliprect.origin.x;
|
||||
rect.y = (intptr_t) cliprect.origin.y;
|
||||
rect.width = (intptr_t) cliprect.size.width;
|
||||
rect.height = (intptr_t) cliprect.size.height;
|
||||
areaView_drawRect(self, rect, self->goarea);
|
||||
}
|
||||
|
||||
- (BOOL)isFlipped
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)acceptsFirstResponder
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
// this will have the Area receive a click that switches to the Window it is in from another one
|
||||
- (BOOL)acceptsFirstMouse:(NSEvent *)e
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)retrack
|
||||
{
|
||||
self->trackingArea = [[NSTrackingArea alloc]
|
||||
initWithRect:[self bounds]
|
||||
// this bit mask (except for NSTrackingInVisibleRect, which was added later to prevent events from being triggered outside the visible area of the Area) comes from https://github.com/andlabs/misctestprogs/blob/master/cocoaviewmousetest.m (and I wrote this bit mask on 25 april 2014) and yes I know it includes enter/exit even though we don't watch those events; it probably won't really matter anyway but if it does I can change it easily
|
||||
options:(NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways | NSTrackingEnabledDuringMouseDrag | NSTrackingInVisibleRect)
|
||||
owner:self
|
||||
userInfo:nil];
|
||||
[self addTrackingArea:self->trackingArea];
|
||||
}
|
||||
|
||||
- (void)updateTrackingAreas
|
||||
{
|
||||
[self removeTrackingArea:self->trackingArea];
|
||||
[self->trackingArea release];
|
||||
[self retrack];
|
||||
}
|
||||
|
||||
#define event(m, f) \
|
||||
- (void)m:(NSEvent *)e \
|
||||
{ \
|
||||
f(self, e, self->goarea); \
|
||||
}
|
||||
event(mouseMoved, areaView_mouseMoved_mouseDragged)
|
||||
event(mouseDragged, areaView_mouseMoved_mouseDragged)
|
||||
event(rightMouseDragged, areaView_mouseMoved_mouseDragged)
|
||||
event(otherMouseDragged, areaView_mouseMoved_mouseDragged)
|
||||
event(mouseDown, areaView_mouseDown)
|
||||
event(rightMouseDown, areaView_mouseDown)
|
||||
event(otherMouseDown, areaView_mouseDown)
|
||||
event(mouseUp, areaView_mouseUp)
|
||||
event(rightMouseUp, areaView_mouseUp)
|
||||
event(otherMouseUp, areaView_mouseUp)
|
||||
|
||||
#define retevent(m, f) \
|
||||
- (BOOL)m:(NSEvent *)e \
|
||||
{ \
|
||||
return f(self, e, self->goarea); \
|
||||
}
|
||||
retevent(doKeyDown, areaView_keyDown)
|
||||
retevent(doKeyUp, areaView_keyUp)
|
||||
retevent(doFlagsChanged, areaView_flagsChanged)
|
||||
|
||||
// seems to be triggered when the user would have finished editing the NSTextField anyway according to the system's rules on that (at least on Mountain Lion)
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
|
||||
{
|
||||
areaTextFieldDismissed(self->goarea);
|
||||
[toNSObject(object) removeObserver:self forKeyPath:@"firstResponder"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Class getAreaClass(void)
|
||||
{
|
||||
return [goAreaView class];
|
||||
}
|
||||
|
||||
id newArea(void *goarea)
|
||||
{
|
||||
goAreaView *a;
|
||||
|
||||
a = [[goAreaView alloc] initWithFrame:NSZeroRect];
|
||||
a->goarea = goarea;
|
||||
return (id) a;
|
||||
}
|
||||
|
||||
BOOL drawImage(void *pixels, intptr_t width, intptr_t height, intptr_t stride, intptr_t xdest, intptr_t ydest)
|
||||
{
|
||||
unsigned char *planes[1]; // NSBitmapImageRep wants an array of planes; we have one plane
|
||||
NSBitmapImageRep *bitmap;
|
||||
BOOL success;
|
||||
|
||||
planes[0] = (unsigned char *) pixels;
|
||||
bitmap = [[NSBitmapImageRep alloc]
|
||||
initWithBitmapDataPlanes:planes
|
||||
pixelsWide:toNSInteger(width)
|
||||
pixelsHigh:toNSInteger(height)
|
||||
bitsPerSample:8
|
||||
samplesPerPixel:4
|
||||
hasAlpha:YES
|
||||
isPlanar:NO
|
||||
// NSCalibratedRGBColorSpace changes the colors; let's not
|
||||
// thanks to JtRip in irc.freenode.net/#macdev
|
||||
colorSpaceName:NSDeviceRGBColorSpace
|
||||
bitmapFormat:0 // this is where the flag for placing alpha first would go if alpha came first; the default is alpha last, which is how we're doing things (otherwise the docs say "Color planes are arranged in the standard order—for example, red before green before blue for RGB color."); this is also where the flag for non-premultiplied colors would go if we used it (the default is alpha-premultiplied)
|
||||
bytesPerRow:toNSInteger(stride)
|
||||
bitsPerPixel:32];
|
||||
success = [bitmap drawInRect:NSMakeRect((CGFloat) xdest, (CGFloat) ydest, (CGFloat) width, (CGFloat) height)
|
||||
fromRect:NSZeroRect // draw whole image
|
||||
operation:NSCompositeSourceOver
|
||||
fraction:1.0
|
||||
respectFlipped:YES
|
||||
hints:nil];
|
||||
[bitmap release];
|
||||
return success;
|
||||
}
|
||||
|
||||
// can't include the header file with these from the Go side since it's an Objective-C header file; keep them here to be safe
|
||||
const uintptr_t cNSShiftKeyMask = (uintptr_t) NSShiftKeyMask;
|
||||
const uintptr_t cNSControlKeyMask = (uintptr_t) NSControlKeyMask;
|
||||
const uintptr_t cNSAlternateKeyMask = (uintptr_t) NSAlternateKeyMask;
|
||||
const uintptr_t cNSCommandKeyMask = (uintptr_t) NSCommandKeyMask;
|
||||
|
||||
uintptr_t modifierFlags(id e)
|
||||
{
|
||||
return fromNSUInteger([toNSEvent(e) modifierFlags]);
|
||||
}
|
||||
|
||||
struct xpoint getTranslatedEventPoint(id area, id e)
|
||||
{
|
||||
NSPoint p;
|
||||
struct xpoint q;
|
||||
|
||||
p = [toNSView(area) convertPoint:[toNSEvent(e) locationInWindow] fromView:nil];
|
||||
q.x = (intptr_t) p.x;
|
||||
q.y = (intptr_t) p.y;
|
||||
return q;
|
||||
}
|
||||
|
||||
intptr_t buttonNumber(id e)
|
||||
{
|
||||
return fromNSInteger([toNSEvent(e) buttonNumber]);
|
||||
}
|
||||
|
||||
intptr_t clickCount(id e)
|
||||
{
|
||||
return fromNSInteger([toNSEvent(e) clickCount]);
|
||||
}
|
||||
|
||||
uintptr_t pressedMouseButtons(void)
|
||||
{
|
||||
return fromNSUInteger([NSEvent pressedMouseButtons]);
|
||||
}
|
||||
|
||||
uintptr_t keyCode(id e)
|
||||
{
|
||||
return (uintptr_t) ([toNSEvent(e) keyCode]);
|
||||
}
|
||||
|
||||
void areaRepaint(id view, struct xrect r)
|
||||
{
|
||||
NSRect s;
|
||||
|
||||
s.origin.x = (CGFloat) r.x;
|
||||
s.origin.y = (CGFloat) r.y;
|
||||
s.size.width = (CGFloat) r.width;
|
||||
s.size.height = (CGFloat) r.height;
|
||||
[toNSView(view) displayRect:s];
|
||||
}
|
||||
|
||||
void areaRepaintAll(id view)
|
||||
{
|
||||
[toNSView(view) display];
|
||||
}
|
||||
|
||||
void areaSetTextField(id area, id textfield)
|
||||
{
|
||||
goAreaView *a = (goAreaView *) area;
|
||||
NSTextField *tf = toNSTextField(textfield);
|
||||
|
||||
[a addSubview:tf];
|
||||
}
|
||||
|
||||
void areaTextFieldOpen(id area, id textfield, intptr_t x, intptr_t y)
|
||||
{
|
||||
goAreaView *a = (goAreaView *) area;
|
||||
NSTextField *tf = toNSTextField(textfield);
|
||||
|
||||
// see TextField.preferredSize() in textfield_darwin.go
|
||||
[tf sizeToFit];
|
||||
[tf setFrameSize:NSMakeSize(textfieldWidth, [tf frame].size.height)];
|
||||
[tf setFrameOrigin:NSMakePoint((CGFloat) x, (CGFloat) y)];
|
||||
[tf setHidden:NO];
|
||||
[[tf window] makeFirstResponder:tf];
|
||||
[[tf window] addObserver:a forKeyPath:@"firstResponder" options:NSKeyValueObservingOptionNew context:NULL];
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -1,337 +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
|
||||
// TODO use GetObject() and get the stride from the resultant BITMAP to be *absolutely* sure
|
||||
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
|
||||
}
|
|
@ -1,537 +0,0 @@
|
|||
general list:
|
||||
- events:
|
||||
- Checkbox.Toggled (.Clicked? or some other name?)
|
||||
- Combobox.Selected
|
||||
- LineEdit.Typing
|
||||
- LineEdit.Finished? or will that be a property of dialog boxes?
|
||||
- Listbox.Selected
|
||||
- Grid niceness
|
||||
- ability to have controls span rows and columns
|
||||
- ability to horizontally or vertically align controls within their cells
|
||||
- Window.SizeToFit() or WIndow.OptimalSize() (use: `Window.SetOptimalSize())`) for sizing a window to the control's interest
|
||||
- with the current code, will be a bit of a kludge, because preferredSize() assumes it's running on the main thread without locks
|
||||
- Control.Show()/Control.Hide()
|
||||
- Groupbox
|
||||
- character-limited entry fields (not for passwords), numeric entry fields, multiline entry fields
|
||||
- possible rename of LineEdit?
|
||||
- especially for password fields - NewPasswordEntry()?
|
||||
- allow Combobox to have initial settings
|
||||
- Combobox and Listbox insertions and deletions should allow bulk (...string/...int)
|
||||
- Combobox/Listbox.DeleteAll
|
||||
- Combobox/Listbox.Select (with Listbox.Select allowing bulk)
|
||||
- Listbox.SelectAll
|
||||
- Listbox/Combobox.Index(n)
|
||||
- Index(n) is the name used by reflect.Value; use a different one?
|
||||
- figure out where to auto-place windows in Cocoa (also window coordinates are still not flipped properly so (0,0) on screen is the bottom-left)
|
||||
- make Combobox and Listbox satisfy sort.Interface?
|
||||
- should a noneditable Combobox be allowed to return to unselected mode by the user?
|
||||
- provide a way for MouseEvent/KeyEvent to signal that the keypress caused the Area to gain/lose focus
|
||||
- provide an event for leaving focus so a focus rectangle can be drawn
|
||||
- when adding menus:
|
||||
- provide automated About, Preferneces, and Quit that place these in the correct location
|
||||
- Quit should pulse AppQuit
|
||||
- will probably want to bring back Event() as NewEvent() should that facility be necesary for menus, etc.
|
||||
- figure out why at least the 64-bit build hates being run under Application Verifier
|
||||
- make sure the preferred size of a Listbox is the minimum size needed to display everything on all platforms (capped at the screen height, of course?)
|
||||
- make sure the image drawn on an Area looks correct on all platforms (is not cropped incorrectly or blurred)
|
||||
```
|
||||
[01:16] <andlabs> so now I need an image that if it's drawn blurry, even slightly, the blur is very obvious... not sure what image that would be though
|
||||
[01:16] *** gburd gives gburd permission to talk.
|
||||
[01:16] <jsimnz> closeup of a face, pretty easy to tell if its blurry
|
||||
[01:16] *** pascalj gives pascalj permission to talk.
|
||||
[01:17] *** gburd takes the permission to talk from gburd.
|
||||
[01:18] <andlabs> in what way
|
||||
[01:19] <andlabs> (also it'd need to be public domain and I can't take a photo o fmy face right now)
|
||||
[01:19] *** pascalj takes the permission to talk from pascalj.
|
||||
[01:19] <jsimnz> hd photo of face, between pores, and wrinkles, noticing unfocus or blur is pretty easy, in my experiance
|
||||
[01:21] <andlabs> all right
|
||||
[01:21] <andlabs> I'll keep a note of that thanks
|
||||
```
|
||||
|
||||
issues of policy:
|
||||
- LineEdit heights on Windows seem too big; either that or LineEdit, Button, and Label text is not vertically centered properly
|
||||
- are Checkboxes and Comboboxes too small?
|
||||
- consolidate scroll view code in GTK+? it's not a lot, but it's rather divergent...
|
||||
- I wonder if I should be reporting if keyboard events ar ehandled or not (force them to be marked as not handled so Windows can work properly or something else??? need to see how Mac OS X does it (calling `super`?))
|
||||
- left/right modifiers are presently undefined; see if we can make them not
|
||||
|
||||
problem points:
|
||||
- because the main event loop is not called if initialization fails, it is presently impossible for MsgBoxError() to work if UI initialization fails; this basically means we cannot allow initializiation to fail on Mac OS X if we want to be able to report UI init failures to the user with one (which would be desirable, maybe (would violate Windows HIG?))
|
||||
- make sure GTK+ documentation version point differences (x in 4.3.x) don't matter
|
||||
- I found a GTK+ version number meaning page somewhere; have to find it again (TODO)
|
||||
|
||||
twists of fate:
|
||||
- listboxes spanning the vertical height of the window don't always align with the bottom border of the edit control attached to the bottom of the window...
|
||||
- this specifically only happens when the window has an odd height; I don't think this can be fixed unless we explicitly ignore the extra pixel everywhere
|
||||
- need a way to get ideal size for all controls on Windows, not just push buttons (Microsoft...)
|
||||
- Cocoa controls have padding around them; this padding is **opaque** so we can't just use the control's cell rect and some shuffling around
|
||||
- when programs that use this package are running, you will not be able to shut down/log off/etc. on Mac OS X because they indiscriminately respond to requests to close with "no" so as to give Go and our program which does not strictly play by NSApplication's rules a fair shot at proper cleanup; this is really a consequence of the way applications work in Cocoa...
|
||||
- non-editable comboboxes in GTK+ have extra stuff to make them wider than other controls and thus cut off slightly but noticeably if the window is too small despite having the correct width
|
||||
|
||||
style changes:
|
||||
- make specific wording in documentation consistent (make/create, etc.)
|
||||
- document minor details like wha thappens on specific events so that they are guaranteed to work the same on all platforms (are there any left?)
|
||||
- what happens when the user clicks and drags on a listbox
|
||||
- I think this is a platform behavior...
|
||||
- should field descriptions in method comments include the receiver name? (for instance e.Held vs. Held) - see what Go's own documentation does
|
||||
- need to figure out exactly how to indicate that a struct{}{} is sent on an event channel (I use about six or so different wordings so far...)
|
||||
- "package ui", "the package", "ui package" (in code and comments)
|
||||
- also "library" both in docs and comments and code, etc.
|
||||
- make passing of parameters and type conversions of parameters to uitask on Windows consistent: explicit _WPARAM(xxx)/_LPARAM(xxx)/uintptr(xxx), for example
|
||||
- do this for type signatures in exported functions: (err error) or just error?
|
||||
- do this for the names of GTK+ helper functions (gtkXXX or gXXX)
|
||||
- areaView -> goArea (or change the class name to be like the delegate name?) in area_darwin.go?
|
||||
|
||||
far off:
|
||||
- localization
|
||||
- strip unused constants from the Windows files
|
||||
- combine more Windows files; rename some?
|
||||
- tab stops
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/10/21/55384.aspx
|
||||
- related: if an Area is the only control, it may or may not ge tkeyboard focus; it doesn't on Windows XP, for instance
|
||||
- using the name Stack means we'll need to figure out what to call what are effectively tabless notebooks (probably that)
|
||||
- I won't rename Stack to Box because Box means a lot of different things (for example, in GTK+ it's Stack, but in Cocoa it's groupbox)
|
||||
- maybe change multiple selection lists to checkbox lists?
|
||||
- windows HIG refernece: http://msdn.microsoft.com/en-us/library/windows/desktop/aa511485.aspx - conflicting, confusing info
|
||||
- gtk+ HIG reference: https://developer.gnome.org/hig-book/3.4/controls-lists.html.en
|
||||
- mac HIG reference: ???
|
||||
- change the MsgBox() calls to encourage good alert dialog design??????? maybe? TODO
|
||||
- make gcc (Unix)/clang (Mac OS X) pedantic about warnings/errors; also -Werror
|
||||
- problem: cgo-generated files trip -Werror up; I can't seem to turn off unused argument warnings with the -Wall/-Wextra/-pedantic options
|
||||
- strip non-Windows XP constants from the Windows constants files? or at least just comment them all out...
|
||||
- add a check to ensure Common Controls 6 is loaded; that is, to ensure the activation context was actually activated and that some other package didn't unwittingly give us the wrong one
|
||||
- keyboard and mouse wheel scrolling in Areas
|
||||
- probably as a toggle (Area.SetKeyboardScrollable()) that inhibits those KeyEvents
|
||||
- then the numpad becomes an issue
|
||||
|
||||
far off/notes to self:
|
||||
- "Version #.##" in MSDN refers to the Windows version, not the version of Common Controls; clear this up wherever (thanks to lh_mouse in irc.oftc.net/#mingw-w64)
|
||||
- or maybe not? it's some oddity with LVGS_COLLAPSIBLE...
|
||||
- see if eXene solved my Area drawing concurrency problems
|
||||
|
||||
big things:
|
||||
- make sure every sysData function only performs a single invocation to uitask; see http://blogs.msdn.com/b/oldnewthing/archive/2005/10/10/479124.aspx#479182
|
||||
- windows: this requires major restructuring
|
||||
- gtk, mac: this just requires checking
|
||||
- steamroll ALL errors, especially on windows
|
||||
- gtk: no way to catch errors
|
||||
- cocoa: discouraged
|
||||
- make fully lock free
|
||||
- prerequisite is the above two
|
||||
- locks are used because of initial state; we can override by creating controls at construct time
|
||||
- cocoa, gtk: no real issues
|
||||
- windows: now required to specify no parent window at create time and set both the parent window AND the child window ID later
|
||||
- http://msdn.microsoft.com/en-us/library/windows/desktop/ms633541%28v=vs.85%29.aspx
|
||||
- don't worry about UI state messages yet; this is before opening the UI anyway (these might be needed when we add tab stops)
|
||||
- http://msdn.microsoft.com/en-us/library/windows/desktop/ms644898%28v=vs.85%29.aspx GWLP_ID
|
||||
- preferred sizes in general are awkward: on Windows, no text-based size calculation is performed, so we have weird things like Labels always having the same width (so if you place a Label in a Stack by itself and forget to make it stretchy, it'll be truncated on Windows (but not on GTK+ or OS X?!))
|
||||
|
||||
big dumb things:
|
||||
- listboxes should have horizontal scrollbars on all platforms; this is way too hard on OS X and doesn't work; my code is in experiments/
|
||||
- also moved the Windows code there for the sake of efficiency
|
||||
- GTK+ works just fine though
|
||||
- window sizes need to not include the window decoration; while Mac OS X and GTK+ both obey this, I've only had issues with Windows; check the experiments/ folder
|
||||
- also will need to be documented in window.go
|
||||
|
||||
specifics:
|
||||
|
||||
WINDOWS
|
||||
<br>TODO re-evaluate this list for Common Controls 6-only stuff (controls and features) I passed up on the first itme
|
||||
- DateTime Picker
|
||||
- ListView for Tables
|
||||
- either Property Sheets or Tabs for Tabs
|
||||
- either Rebar or Toolbar for Toolbars
|
||||
- Status Bar
|
||||
- Tooltip (should be a property of each control)
|
||||
- Trackbar for Sliders
|
||||
- cannot automatically snap to custom step; need to do it manually
|
||||
- Tree View
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2011/11/25/10241394.aspx
|
||||
- Up-Down Control for Spinners
|
||||
- maybe:
|
||||
- swap ComboBox for ComboBoxEx (probably only if requested enough)
|
||||
- IP Address control (iff GTK+ and Cocoa have it; maybe not necessary if we allow arbitrary target addresses?)
|
||||
- ListView for its Icon View?
|
||||
- something similar to Task Dialog might be useful to have as a convenience template later
|
||||
- TODO
|
||||
- commcntl.h has stuff on a font control that isn't documented?
|
||||
- actually not a control, but localization support: http://msdn.microsoft.com/en-us/library/windows/desktop/bb775454%28v=vs.85%29.aspx
|
||||
- notes to self:
|
||||
- groupbox is a mode of the BUTTON class (????)
|
||||
- OpenGL: http://msdn.microsoft.com/en-us/library/windows/desktop/dd374379%28v=vs.85%29.aspx
|
||||
- don't use ES_NUMBER for number-only text boxes, as "it is still possible to paste non-digits into the edit control." (though a commenter on MSDN says that's wrong?)
|
||||
- might want to just have spinners and not numeric text boxes???
|
||||
|
||||
GTK+
|
||||
- GtkCalendar for date selection (TODO doesn't handle times)
|
||||
- GtkNotebook for Tabs
|
||||
- GtkScale for Sliders
|
||||
- cannot automatically snap to INTEGERS (let alone to custom steps); need to do it manually
|
||||
- natural size is 0x0 for some reason
|
||||
- GtkSpinButton for Spinners
|
||||
- GtkStatusBar
|
||||
- GtkToolbar
|
||||
- maybe:
|
||||
- GtkFontButton would be nice but unless ComboBoxEx provides it Windows doesn't
|
||||
- same for GtkColorButton
|
||||
- GtkIconView
|
||||
- GtkSeparator (I think Windows makes this a mode of Static controls?)
|
||||
- notes to self:
|
||||
- groupbox is GtkFrame
|
||||
- GtkTreeView can do tree views and Tables
|
||||
- OpenGL is done outside GTK+: https://projects.gnome.org/gtkglext/
|
||||
- only an issue if I want to provide OpenGL by default...
|
||||
- http://stackoverflow.com/questions/3815806/gtk-and-opengl-bindings suggest GtkGLArea is better but that seems to be a Mono thing? also indicates Clutter (with its Cogl) is not an option because it locks you out of using the OpenGL API directly
|
||||
- er no, the Mono thing is just the homepage... but it doesn't say if this targets GTK+ 2 or GTK+ 3, hm. (also it appears to not have been updated since Precise; in Ubuntu it's libgtkgl)
|
||||
- and gtkglext doesn't support GTK+ 3 officially anyway
|
||||
- and cairo doesn't seem to support OpenGL explicitly so it looks like I will need to communicate with glx directly: http://stackoverflow.com/questions/17628241/how-can-i-use-gtk3-and-opengl-together
|
||||
- except replace glx with EGL/GLES2 because of Wayland: http://wayland.freedesktop.org/faq.html#heading_toc_j_0 (assuming EGL/GLES2 can work on X11)
|
||||
|
||||
COCOA
|
||||
- NSDatePicker for date/time selection
|
||||
- NSOutlineView for tree views
|
||||
- NSSlider for Sliders
|
||||
- NSStatusBar
|
||||
- NSStepper for Spinners
|
||||
- TODO does this require me to manually pair it with a single-line text entry field?
|
||||
- NSTabView for Tabs
|
||||
- NSTableView for Tables
|
||||
- NSToolbar
|
||||
- maybe:
|
||||
- NSBrowser seems nice...???
|
||||
- NSCollectionView for Icon View?
|
||||
- NSColorWell is the color button
|
||||
- NSOpenGLView for OpenGL; need to see how much OpenGL-specific stuff I need to expose
|
||||
- NSRuleEditor/NSPredicateEditor look nice too but
|
||||
- notes to self:
|
||||
- groupbox is NSBox
|
||||
- don't look at NSForm; though it arranges in the ideal form layout, it only allows single-line text entry fields as controls
|
||||
- TODO:
|
||||
- what does NSPathControl look like?
|
||||
|
||||
ALL THREE; LIST BY FEATURE (TODO merge the above into this)
|
||||
- LineEdit prompts
|
||||
- Windows: http://msdn.microsoft.com/en-us/library/windows/desktop/bb761639%28v=vs.85%29.aspx
|
||||
being able to use comctl32.dll version 6 now that win2k support is gone is a good thing, isn't it?
|
||||
- GTK+: TODO
|
||||
- Cocoa: TODO
|
||||
|
||||
OTHER NOTES
|
||||
- on webpage controls: http://blogs.msdn.com/b/oldnewthing/archive/2005/02/11/371042.aspx
|
||||
|
||||
THOUGHTS
|
||||
- privilege escalation requests
|
||||
- windows vista and up use a button with a shield icon and osme other API that I don't know before performing the privileged action
|
||||
- typically graphical unix programs nowadays use a graphical sudo prompt provided by the desktop environment (gksu or kdesudo or what not) either before the program even runs or after clicking a button to start the process with elevated privileges
|
||||
- however, GTK+ also provides https://developer.gnome.org/gtk3/3.4/GtkLockButton.html, which is similar to the Cocoa one below:
|
||||
- Cocoa uses a button with a lock on it that will disable the entire form when unlocked, then ask for permission when clicked
|
||||
|
||||
# Slider Capabilities
|
||||
Capability | Windows | GTK+ | Cocoa
|
||||
----- | ----- | ----- | -----
|
||||
Data Type | int | float | float
|
||||
Can Simulate ints? | yes | TODO | TODO
|
||||
Mouse Step Snap | 1, fixed | something; likely 0.1 but not sure | yes (`setAllowsTickMarkValuesOnly:`); caveat: must specify an exact number of ticks (see below)
|
||||
Keyboard Step Snap | configurable | configurable | TODO (same as mouse?)
|
||||
Current Value Display | tooltip during drag | label, always visible | TODO
|
||||
Tooltips? | TODO | TODO | TODO
|
||||
Ticks | configurable display, configurable interval | TODO | configurable display; configurable COUNT (not interval!)
|
||||
Can Catch Mouse Events to Snap? | I think this is how to do it | TODO | TODO
|
||||
Preferred Size | given in UI guidelines | natural: 0x0; minimum: TODO | TODO
|
||||
|
||||
# Spinner Capabilities
|
||||
Capability | Windows | GTK+ | Cocoa
|
||||
----- | ----- | ----- | -----
|
||||
Data Type | int | float | flaot
|
||||
Can Simulate ints? | yes | yes | TODO
|
||||
Mouse Step Snap | 1, fixed | configurable | configurable
|
||||
Keyboard Step Snap | 1, fixed | configurable (uses same value as mouse) | TODO (same as mouse?)
|
||||
Can Catch Events To Snap? | TODO | no need | TODO
|
||||
Preferred Size | TODO | TODO | TODO
|
||||
|
||||
|
||||
# Dialog box hijack
|
||||
## Open/Save Dialogs
|
||||
| Windows | GTK+ | Cocoa
|
||||
----- | ----- | ----- | -----
|
||||
Directories | no (separate facility provided by the shell) | open and save | open only
|
||||
Network vs. local only (URI vs. filename) | Network button enabled by default; can be switched off (**TODO** how are network filenames returned?) | yes (default local only; if local only, changing to, say, smb://127.0.0.1/ will pop up an error box; if nonlocal allowed, filename can be null) | xxx
|
||||
Multiple selection | yes | yes | open only
|
||||
Hidden files | user-specified; can be switched on in code (but is a no-op?) | hidden by default; can be switched on in code (but is a no-op?) and also by the user | xxx
|
||||
Overwrite confirmation | available; must be explicitly enabled | available; must be explicitly enabled | xxx
|
||||
New Folder button | xxx | optional (I think enabled by default? should do it explicitly to be safe, anyway) | optional
|
||||
Preview widget | xxx | yes; optional, custom | xxx
|
||||
Extra custom widget | xxx | yes; optional | yes; optional
|
||||
File filters | Specified by "patterns" (consisting of filename characters and * but not space; I assume the only safe ones are *.ext and *.*); multiple patterns separated by semicolons; can have custom labels | Specified by MIME type (handles subtypes; has wildcards) or pattern ("shell-style glob", so I assume over whole basename) or by custom function; can have multiple of the above; can have custom labels; also has a shortcut to add all gdk-pixbuf-supported formats | Specified by "UTI"s or by individual filename extensions (format not documented but appears to be just the extension without embellishments); cannot have labels; 1:1 filter:extension mapping.
|
||||
File filter list format | `"Label\0Filter-list\0Label\0Filter-list\0...Label\0FIlter-list\0\0"`; filter for all files is canonically `"All Files\0*.*\0\0"` in the docs (specifically this due to handling of shortcut links); also provides a way for users to write in their own filters | Add or remove individual GtkFileFIlter objects; can select one specified in the list to show by default; default behavior is all files; if selected one when none has been specified, filter selection disabled; filter for all files specified in docs under gtk_file_filter_new() (except doesn't set a name) | NSArray of filter strings, or nil for All Files. There is no provision to have an "all files" option: you either specify a set of filters or you don't. (See filename extension auto-append below.). All filters are applied at once; there is no way to select. We might need to introduce an accessory panel (extra widget) to fake the filtering rules of other platforms...
|
||||
Default file name | settable | settable | settable (as the filename label)
|
||||
Initial directory | complex rules that have changed over time; we can pass an absolute filename (the previous filename or a default filename) and have its path used (if we specify just a path it will either be used as the filename or the program will crash); or we can give it a directory; or Windows will remember for us for some time, or... | pass previous filename or URI to show; overrides default file name; intended only for saving files (so I don't know if it's possible to remember current directory for opening??????); effect of passing containing directory undocumented(???? in my tests the given folder itself is selected) | has some rules; there is a way to specify a custom one; seems to have the undocumented effect that it selects the file if a file is named
|
||||
Confirmation and cancel buttons | xxx | GTK_STOCK_OPEN, GTK_STOCK_SAVE, GTK_STOCK_SAVE_AS / GTK_STOCK_CANCEL | cancel button predefined; confirmation button can be changed (setPrompt:) but **TODO** the docs imply prompt is actually a global property?
|
||||
Returned filename rules | xxxx | memory provided by GTK+ itself (so no need to worry about size limits); can return a single filename or URI or a GSList of filenames or URIs | xxx
|
||||
Window title | optional; defaults to either Open or Save As | required(?) | optional for save (defaults to Save); unknown (**TODO**) for Open
|
||||
Prompt to create new files | available; must be explicitly enabled; seems to only apply to Open File dialogs (**TODO**) | xxx | xxx
|
||||
Adds file to a Recent Documents list | available; must be explicitly disabled | xxx | xxx
|
||||
Allows nonexistent files to be created in Open dialogs | yes; can be switched off | xxx | xxx
|
||||
"Open as read-only"/"Save as read-only" checkbox | provided; default; can be switched off | xxx | xxx
|
||||
Navigating changes the current working directory of the program | yes; can be switched off for Save dialogs only (????) | xxx | xxx
|
||||
Link following | For .lnk files, enabled by default iff a filter is specified; the All Files filter above is listed as being necessary to follow links; can be shut off with a flag in all cases | xxx | For Finder aliases, setResolvesAliases:
|
||||
Help button | Available; old-style dialog boxes need a parent window (Explorer-style ones don't; they just need a hook function) | xxx | xxx
|
||||
Extension auto-appending | Optional; three-character maximum; doesn't seem to be available on a per-filter basis | xxx | **NOT OPTIONAL.** The only way to avoid this is to not specify any filters. You can't even circumvent this with a delegate. If the user specifies another extension, they are asked to choose one if setAllowsOtherFileTypes: is set. (**TODO** could we use nameFieldStringValue to circumvent?)
|
||||
Other labels | xxx | xxx | field before filename entry can be changed; also can provide an additional optional message
|
||||
Multiple selection allows empty selection | xxx | xxx | xxx
|
||||
|
||||
TODO
|
||||
* Windows: OFN_NOTESTFILECREATE might be necessary
|
||||
* Windows: OFN_NOVALIDATE - see what happens without a hook
|
||||
* Windows: OFN_SHAREAWARE - this is a weird one but it's network related
|
||||
* Windows: templates seem to be how to provide extra parameters, but their usage isn't documented on the OPENFILENAME struct help page; check the rest of MSDN
|
||||
* Mac OS X: turn on both setExtensionHidden: and setCanSelectHiddenExtension: to show the extnesion in the dialog
|
||||
* Mac OS X: turn on setTreatsFilePackagesAsDirectories: since file packages (bundles) are an OS X-specific concept
|
||||
|
||||
## The Scrollbar Series
|
||||
This actually turns out to be one of the very first things that Raymond ever blogged about, so if you just go to the last page of posts on The Old New Thing, it'll be there. But for my own convenience:
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/07/23/54576.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/07/25/54582.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/07/29/54591.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/07/30/54600.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/07/31/54601.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/08/05/54602.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/08/05/54610.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/08/07/54615.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/08/07/54617.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/08/11/54624.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/08/11/54629.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/08/13/54639.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/08/15/54647.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/08/18/54668.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/09/09/54826.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/09/11/54885.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/09/13/54917.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/09/15/54925.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/09/17/54944.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/09/17/54945.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/09/17/54946.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/10/16/55344.aspx
|
||||
- not really part of, so to speak, but still http://blogs.msdn.com/b/oldnewthing/archive/2004/05/10/129068.aspx
|
||||
|
||||
|
||||
TODO SORT
|
||||
(TODO the original time formats article)
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/08/26/54708.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/09/05/54805.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/10/01/55108.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/10/13/55279.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/10/14/55286.aspx
|
||||
- TODO get article on spurious mouse moves
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/10/21/55384.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/10/24/55413.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/10/27/55461.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/10/29/55479.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/11/07/55619.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/12/29/46371.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2003/12/30/46594.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2004/01/21/61101.aspx (on maximum pathname length)
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2004/02/27/81155.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2004/03/29/101121.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2004/04/19/115912.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2004/07/01/170856.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2004/07/12/180642.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2004/07/29/200653.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2004/08/02/205624.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2004/08/04/208005.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2004/08/26/220873.aspx (on arrays of size 1 in various data structures; will need to double-check alignments)
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2004/10/15/242761.aspx (on distinguishing clicks and double-clicks)
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2004/10/18/243925.aspx (triple-clicks and higher)
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2004/11/30/272262.aspx
|
||||
- shell drag and drop (TODO might need to get the older drag and drop articles???)
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2004/12/06/275659.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2004/12/07/277581.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2004/12/08/278295.aspx
|
||||
- starting to get the feeling this isn't actually the kind of drag and drop I was thinking of...
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2004/12/09/278914.aspx
|
||||
- this one might be though
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2004/12/10/279530.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2004/12/14/300204.aspx **VALID DIALOG IDS** holy shit why didn't this come up when I was searching for it
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/01/10/349894.aspx some info on notification bubbles
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/01/31/363790.aspx 64-bit type equivalency
|
||||
- modality
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/02/18/376080.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/02/21/377337.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/02/22/378018.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/02/23/378866.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/02/24/379635.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/02/28/381591.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/03/01/382380.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/03/02/383562.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/03/03/384285.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/03/04/385100.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/03/07/387044.aspx (aside: timed context menu)
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/03/09/390706.aspx on SystemParametersInfo(); includes icon label font (not part of the non-client metrics)
|
||||
- the dialog manager
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/03/29/403298.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/03/30/403711.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/03/31/404108.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/04/01/404531.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/04/04/405207.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/04/05/405518.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/04/06/405827.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/04/07/406012.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/04/08/406509.aspx
|
||||
- dialog templates, especially dynamic generation of them
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/04/29/412577.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/05/03/414317.aspx accelerators
|
||||
- a complex program: covers character set conversion with Microsoft C++ and then the Windows API, mmap() on Windows, list views, list view columns, list view text callbacks, owner-data/virtual list views, xxxxxx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/05/09/415714.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/05/10/415991.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/05/11/416430.aspx
|
||||
- (2005/05/12 is unrelated)
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/05/13/417183.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/05/16/417865.aspx
|
||||
- (2005/05/17 is unrelated)
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/05/18/419130.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/05/19/420038.aspx
|
||||
- (period between is unrelated)
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/06/13/428534.aspx
|
||||
- this also sets the iItem of the LVITEM to the result of inserting the item... huh?! TODO
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/06/14/428892.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/06/15/429338.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/05/25/421707.aspx SetCursor() can be problematic
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/05/30/423202.aspx faking keyboard input (if I ever want to do automated tests) is unreliable
|
||||
|
||||
interesting things
|
||||
- low-tech models http://blogs.msdn.com/b/oldnewthing/archive/2003/10/07/55202.aspx
|
||||
- JIT http://blogs.msdn.com/b/oldnewthing/archive/2003/11/04/55560.aspx
|
||||
- comments on frameworks (!) http://blogs.msdn.com/b/oldnewthing/archive/2003/11/10/55635.aspx
|
||||
- dialog box entry autoselection http://blogs.msdn.com/b/oldnewthing/archive/2003/11/14/55678.aspx
|
||||
- more on dialog codes http://blogs.msdn.com/b/oldnewthing/archive/2003/11/26/55872.aspx
|
||||
- custom window class styles http://blogs.msdn.com/b/oldnewthing/archive/2003/12/03/55927.aspx
|
||||
- plug and play device changes http://blogs.msdn.com/b/oldnewthing/archive/2003/12/05/55936.aspx
|
||||
- more JIT-related http://blogs.msdn.com/b/oldnewthing/archive/2003/12/08/55954.aspx
|
||||
- suspending threads http://blogs.msdn.com/b/oldnewthing/archive/2003/12/09/55988.aspx
|
||||
- UI design http://blogs.msdn.com/b/oldnewthing/archive/2003/12/09/55995.aspx
|
||||
- passing data to child processes (is this useful for detecting multiple instances?) http://blogs.msdn.com/b/oldnewthing/archive/2003/12/11/56043.aspx
|
||||
- on the need for cbSize fields http://blogs.msdn.com/b/oldnewthing/archive/2003/12/12/56061.aspx
|
||||
- notes on critical sections http://blogs.msdn.com/b/oldnewthing/archive/2003/12/18/44425.aspx http://blogs.msdn.com/b/oldnewthing/archive/2003/12/22/45152.aspx
|
||||
- on COM interfaces and control panels (also uses activation contexts for SxS manifests) http://blogs.msdn.com/b/oldnewthing/archive/2003/12/26/45979.aspx
|
||||
- return address attacks http://blogs.msdn.com/b/oldnewthing/archive/2004/01/01/47042.aspx
|
||||
- on GetProcAddress() http://blogs.msdn.com/b/oldnewthing/archive/2004/01/12/57833.aspx
|
||||
- DllMain() http://blogs.msdn.com/b/oldnewthing/archive/2004/01/27/63401.aspx http://blogs.msdn.com/b/oldnewthing/archive/2004/01/28/63880.aspx
|
||||
- string resources http://blogs.msdn.com/b/oldnewthing/archive/2004/01/30/65013.aspx
|
||||
- text/unicode macros http://blogs.msdn.com/b/oldnewthing/archive/2004/02/12/71851.aspx
|
||||
- version numbers http://blogs.msdn.com/b/oldnewthing/archive/2004/02/13/72476.aspx
|
||||
- RECT endpoint-exclusivity (everything does this, not just Windows, so it's useful to point people to) http://blogs.msdn.com/b/oldnewthing/archive/2004/02/18/75652.aspx
|
||||
- on fixing a problem everywhere (see RECT above) http://blogs.msdn.com/b/oldnewthing/archive/2004/02/19/76368.aspx
|
||||
- proper use of COM http://blogs.msdn.com/b/oldnewthing/archive/2004/02/20/77120.aspx
|
||||
- invalid thread and process IDs http://blogs.msdn.com/b/oldnewthing/archive/2004/02/23/78395.aspx
|
||||
- on GetDesktopWindow() http://blogs.msdn.com/b/oldnewthing/archive/2004/02/24/79212.aspx
|
||||
- links in notification bubbles http://blogs.msdn.com/b/oldnewthing/archive/2004/02/25/79842.aspx
|
||||
- dialog boxes are initially hidden http://blogs.msdn.com/b/oldnewthing/archive/2004/03/11/87941.aspx
|
||||
- Win16 resource format changes (probably irrelevant since NE vs. PE) http://blogs.msdn.com/b/oldnewthing/archive/2004/03/19/92648.aspx
|
||||
- COM is fickle because people are fickle http://blogs.msdn.com/b/oldnewthing/archive/2004/03/26/96777.aspx
|
||||
- on reference counting http://blogs.msdn.com/b/oldnewthing/archive/2004/04/06/108395.aspx
|
||||
- taskbar program names http://blogs.msdn.com/b/oldnewthing/archive/2004/04/08/109775.aspx
|
||||
- random number generator seeds http://blogs.msdn.com/b/oldnewthing/archive/2004/04/12/111596.aspx
|
||||
- Unicode collation http://blogs.msdn.com/b/oldnewthing/archive/2004/04/13/112234.aspx
|
||||
- on Unicode digits http://blogs.msdn.com/b/oldnewthing/archive/2004/04/16/114651.aspx
|
||||
- accessibility http://blogs.msdn.com/b/oldnewthing/archive/2004/04/23/118893.aspx
|
||||
- HWND_BROADCAST is not for multiple instances http://blogs.msdn.com/b/oldnewthing/archive/2004/05/05/126427.aspx
|
||||
- COM threading http://blogs.msdn.com/b/oldnewthing/archive/2004/05/11/129760.aspx
|
||||
- disabling vs hiding http://blogs.msdn.com/b/oldnewthing/archive/2004/05/14/131881.aspx
|
||||
- when to put ... after a command name http://blogs.msdn.com/b/oldnewthing/archive/2004/05/17/133181.aspx
|
||||
- COM destructors http://blogs.msdn.com/b/oldnewthing/archive/2004/05/20/135841.aspx http://blogs.msdn.com/b/oldnewthing/archive/2004/05/21/136701.aspx
|
||||
- high performance multithreading http://blogs.msdn.com/b/oldnewthing/archive/2004/05/28/143769.aspx
|
||||
- getting icons for known file types http://blogs.msdn.com/b/oldnewthing/archive/2004/06/01/145428.aspx
|
||||
- COM threading models http://blogs.msdn.com/b/oldnewthing/archive/2004/06/02/146671.aspx
|
||||
- why am I posting so much on COM? in case I need to use shell functions in the future that aren't flat functions...
|
||||
- thread messages and message reentrancy (and some DDE for good measure) http://blogs.msdn.com/b/oldnewthing/archive/2004/06/08/150929.aspx
|
||||
- windows language IDs http://blogs.msdn.com/b/oldnewthing/archive/2004/06/09/151689.aspx
|
||||
- combobox/listbox speed improvements http://blogs.msdn.com/b/oldnewthing/archive/2004/06/10/152612.aspx
|
||||
- dialog templates
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2004/06/17/158175.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2004/06/18/159248.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2004/06/21/161375.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2004/06/22/162360.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2004/06/23/163596.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2004/06/24/164737.aspx
|
||||
- COM and DLL unloading http://blogs.msdn.com/b/oldnewthing/archive/2004/06/28/167800.aspx
|
||||
- thread safety vs reentrancy http://blogs.msdn.com/b/oldnewthing/archive/2004/06/29/168719.aspx
|
||||
- shell and COM allocation http://blogs.msdn.com/b/oldnewthing/archive/2004/07/05/173226.aspx
|
||||
- minimized window coordinates (should I move this one to the higher list?) http://blogs.msdn.com/b/oldnewthing/archive/2004/07/07/175285.aspx
|
||||
- DPI (may or may not be related to future developments in the library) http://blogs.msdn.com/b/oldnewthing/archive/2004/07/14/182971.aspx
|
||||
- more on UNICODE http://blogs.msdn.com/b/oldnewthing/archive/2004/07/15/184076.aspx
|
||||
- PROPER UNICODE RENDERING IN WINDOWS (MIGHT BE MOVED UP???) http://blogs.msdn.com/b/oldnewthing/archive/2004/07/16/185261.aspx
|
||||
- callback templates http://blogs.msdn.com/b/oldnewthing/archive/2004/07/19/187415.aspx
|
||||
- more info on shell interop http://blogs.msdn.com/b/oldnewthing/archive/2004/07/20/188696.aspx
|
||||
- proof about WM_CLOSE for GUI windows from the task manager (see also our AppQuit channel) http://blogs.msdn.com/b/oldnewthing/archive/2004/07/22/191123.aspx
|
||||
- nested dialogs http://blogs.msdn.com/b/oldnewthing/archive/2004/07/30/201988.aspx
|
||||
- `.shared` memory for IPC is a security hole (related to multiple instances) http://blogs.msdn.com/b/oldnewthing/archive/2004/08/04/208003.aspx
|
||||
- fake menus http://blogs.msdn.com/b/oldnewthing/archive/2004/08/20/217684.aspx
|
||||
- STRRET (more shell stuff) http://blogs.msdn.com/b/oldnewthing/archive/2004/08/23/218837.aspx
|
||||
- reading from the registry may not yield valid C strings http://blogs.msdn.com/b/oldnewthing/archive/2004/08/24/219444.aspx
|
||||
- using COM to fake add/remove programs http://blogs.msdn.com/b/oldnewthing/archive/2004/08/31/223271.aspx
|
||||
- thread safety of function calls http://blogs.msdn.com/b/oldnewthing/archive/2004/09/03/225238.aspx
|
||||
- interlocking pitfalls http://blogs.msdn.com/b/oldnewthing/archive/2004/09/15/229915.aspx
|
||||
- WM_CONTEXTMENU pitfalls http://blogs.msdn.com/b/oldnewthing/archive/2004/09/21/232369.aspx
|
||||
- right-click menu defaults http://blogs.msdn.com/b/oldnewthing/archive/2004/09/29/235737.aspx
|
||||
- creating COM interfaces without IDL files http://blogs.msdn.com/b/oldnewthing/archive/2004/10/05/238050.aspx
|
||||
- RegisterClass() atoms http://blogs.msdn.com/b/oldnewthing/archive/2004/10/11/240744.aspx
|
||||
- SHCreateThreadRef() (I have no idea what this is) http://blogs.msdn.com/b/oldnewthing/archive/2004/10/14/242241.aspx
|
||||
- file associations and long filenames http://blogs.msdn.com/b/oldnewthing/archive/2004/10/20/245072.aspx
|
||||
- WMI for system information http://blogs.msdn.com/b/oldnewthing/archive/2004/10/21/245652.aspx
|
||||
- accessibility notes (may need to move to the upper list) http://blogs.msdn.com/b/oldnewthing/archive/2004/10/27/248464.aspx
|
||||
- note on screen coordinates http://blogs.msdn.com/b/oldnewthing/archive/2004/10/29/249567.aspx
|
||||
- keyboard security http://blogs.msdn.com/b/oldnewthing/archive/2004/11/15/257565.aspx
|
||||
- forms of SendMessage() http://blogs.msdn.com/b/oldnewthing/archive/2004/11/19/266664.aspx
|
||||
- ShellExecuteEx() http://blogs.msdn.com/b/oldnewthing/archive/2004/11/26/270710.aspx
|
||||
- on command line password security (useful for showing anyone who asks a similar question) http://blogs.msdn.com/b/oldnewthing/archive/2004/11/29/271551.aspx
|
||||
- on planar graphics (useful for linking people) http://blogs.msdn.com/b/oldnewthing/archive/2004/12/01/273018.aspx
|
||||
- shell printing (might need to move to the above list) http://blogs.msdn.com/b/oldnewthing/archive/2004/12/03/274312.aspx
|
||||
- E_NOINTERFACE from CoCreateInstance() http://blogs.msdn.com/b/oldnewthing/archive/2004/12/13/281910.aspx
|
||||
- on misleading optimizations http://blogs.msdn.com/b/oldnewthing/archive/2004/12/16/317157.aspx
|
||||
- caching vs recalculating http://blogs.msdn.com/b/oldnewthing/archive/2004/12/20/327369.aspx
|
||||
- BOOL and BOOLEAN http://blogs.msdn.com/b/oldnewthing/archive/2004/12/22/329884.aspx
|
||||
- strcpy() must be used with care http://blogs.msdn.com/b/oldnewthing/archive/2005/01/07/348437.aspx
|
||||
- DS_SHELLFONT posts (no longer relevant to the above, at least I don't think)
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/02/04/366987.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/02/07/368423.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/02/08/369090.aspx
|
||||
- dangers of filtering messages http://blogs.msdn.com/b/oldnewthing/archive/2005/02/09/369804.aspx
|
||||
- the shell Animation control (is this the same as the one in common controls version 6?) is limited (might be needed for the above) http://blogs.msdn.com/b/oldnewthing/archive/2005/02/16/374397.aspx
|
||||
- MsgWaitForMultipleObjects() requires care http://blogs.msdn.com/b/oldnewthing/archive/2005/02/17/375307.aspx
|
||||
- danger of setting parameter info http://blogs.msdn.com/b/oldnewthing/archive/2005/03/10/392118.aspx
|
||||
- danger of startup helpers and other misleading "performance boosts" http://blogs.msdn.com/b/oldnewthing/archive/2005/03/11/394249.aspx
|
||||
- danger of saving/restoring window coordinates http://blogs.msdn.com/b/oldnewthing/archive/2005/03/14/395271.aspx
|
||||
- on the cost of creating and maintaining windows http://blogs.msdn.com/b/oldnewthing/archive/2005/03/15/395866.aspx
|
||||
- more on version checks http://blogs.msdn.com/b/oldnewthing/archive/2005/04/13/407835.aspx
|
||||
- on hInstance in CreateWindow()/RegisterClass() http://blogs.msdn.com/b/oldnewthing/archive/2005/04/18/409205.aspx
|
||||
- on the DC brush (useful for drawing controls) http://blogs.msdn.com/b/oldnewthing/archive/2005/04/20/410031.aspx
|
||||
- the new scratch program (probably want to move to the top list if it proves useful enough) http://blogs.msdn.com/b/oldnewthing/archive/2005/04/22/410773.aspx
|
||||
- thread messages:
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/04/26/412116.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/04/27/412565.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/04/28/412574.aspx (doubles as one on message filters)
|
||||
- full-screen windows http://blogs.msdn.com/b/oldnewthing/archive/2005/05/05/414910.aspx
|
||||
- window visibility http://blogs.msdn.com/b/oldnewthing/archive/2005/05/20/420425.aspx
|
||||
- DLLs loading other DLLs http://blogs.msdn.com/b/oldnewthing/archive/2005/05/23/421024.aspx
|
||||
- ternary raster operations (GDI) http://blogs.msdn.com/b/oldnewthing/archive/2005/05/24/421440.aspx
|
||||
- x << 1 and x >> 1 are dangerous microoptimizations http://blogs.msdn.com/b/oldnewthing/archive/2005/05/27/422551.aspx
|
||||
- windows has named events, apparently (also synchronization) http://blogs.msdn.com/b/oldnewthing/archive/2005/06/06/425611.aspx http://blogs.msdn.com/b/oldnewthing/archive/2005/06/07/426296.aspx
|
||||
|
||||
articles on time
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2004/02/26/80492.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2004/08/25/220195.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2004/09/02/224672.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/04/14/408106.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/04/15/408507.aspx
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2005/05/31/423407.aspx (about timers, but the last paragraph is also relevant)
|
||||
|
||||
up to
|
||||
http://blogs.msdn.com/b/oldnewthing/default.aspx?PageIndex=344
|
||||
last one: What is the difference between "Unpin from Start menu" and "Remove from this list"?
|
||||
|
||||
windows stock icons
|
||||
- the standard IDI_** values, plus a few others I forget now
|
||||
- http://msdn.microsoft.com/en-us/library/windows/desktop/bb762542%28v=vs.85%29.aspx (TODO really vista only?)
|
||||
|
||||
a few more oldnewthing things
|
||||
- comment on WM_SIZE - http://blogs.msdn.com/b/oldnewthing/archive/2005/07/06/436043.aspx#436090
|
|
@ -1,196 +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)
|
||||
|
||||
// ReadOnly and SetReadOnly get and set whether the TextField is read-only.
|
||||
// A read-only TextField cannot be changed by the user, but its text can still be manipulated in other ways (selecting, copying, etc.).
|
||||
ReadOnly() bool
|
||||
SetReadOnly(readonly bool)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// Textbox represents a multi-line text entry box.
|
||||
// Text in a Textbox is unformatted, and scrollbars are applied automatically.
|
||||
// TODO rename to TextBox? merge with TextField (but cannot use Invalid())? enable/disable line wrapping?
|
||||
// TODO events
|
||||
// TODO Tab key - insert horizontal tab or tab stop?
|
||||
// TODO ReadOnly
|
||||
// TODO line endings
|
||||
type Textbox interface {
|
||||
Control
|
||||
|
||||
// Text and SetText get and set the Textbox's text.
|
||||
Text() string
|
||||
SetText(text string)
|
||||
}
|
||||
|
||||
// NewTextbox creates a new Textbox.
|
||||
func NewTextbox() Textbox {
|
||||
return newTextbox()
|
||||
}
|
||||
|
||||
// Spinbox is a Control that provides a text entry field that accepts integers and up and down buttons to increment and decrement those values.
|
||||
// This control is in its preliminary state.
|
||||
// TODO everything:
|
||||
// - TODO set increment? (work on windows)
|
||||
// - TODO set page step?
|
||||
// - TODO wrapping
|
||||
// - TODO negative values
|
||||
type Spinbox interface {
|
||||
Control
|
||||
|
||||
// Value and SetValue get and set the current value of the Spinbox, respectively.
|
||||
// For SetValue, if the new value is outside the current range of the Spinbox, it is set to the nearest extremity.
|
||||
Value() int
|
||||
SetValue(value int)
|
||||
|
||||
// OnChanged sets the event handler for when the Spinbox's value is changed.
|
||||
// Under what conditions this event is raised when the user types into the Spinbox's edit field is platform-defined.
|
||||
OnChanged(func())
|
||||
}
|
||||
|
||||
// NewSpinbox creates a new Spinbox with the given minimum and maximum.
|
||||
// The initial value will be the minimum value.
|
||||
// NewSpinbox() panics if min > max.
|
||||
func NewSpinbox(min int, max int) Spinbox {
|
||||
if min > max {
|
||||
panic("min > max in NewSpinbox()")
|
||||
}
|
||||
return newSpinbox(min, max)
|
||||
}
|
||||
|
||||
// ProgressBar is a Control that displays a horizontal bar which shows the level of completion of an operation.
|
||||
// TODO indetermiante
|
||||
type ProgressBar interface {
|
||||
Control
|
||||
|
||||
// Percent and SetPrecent get and set the current percentage indicated by the ProgressBar, respectively.
|
||||
// This value must be between 0 and 100; all other values cause SetPercent to panic.
|
||||
// TODO rename to Progress/SetProgress?
|
||||
Percent() int
|
||||
SetPercent(percent int)
|
||||
}
|
||||
|
||||
// NewProgressBar creates a new ProgressBar.
|
||||
// It will initially show a progress of 0%.
|
||||
func NewProgressBar() ProgressBar {
|
||||
return newProgressBar()
|
||||
}
|
|
@ -1,314 +0,0 @@
|
|||
// 16 july 2014
|
||||
|
||||
#import "objc_darwin.h"
|
||||
#import "_cgo_export.h"
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#define toNSButton(x) ((NSButton *) (x))
|
||||
#define toNSTextField(x) ((NSTextField *) (x))
|
||||
#define toNSView(x) ((NSView *) (x))
|
||||
#define toNSWindow(x) ((NSWindow *) (x))
|
||||
#define toNSBox(x) ((NSBox *) (x))
|
||||
#define toNSTextView(x) ((NSTextView *) (x))
|
||||
#define toNSProgressIndicator(x) ((NSProgressIndicator *) (x))
|
||||
|
||||
@interface goControlDelegate : NSObject <NSTextFieldDelegate> {
|
||||
@public
|
||||
void *gocontrol;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation goControlDelegate
|
||||
|
||||
- (IBAction)buttonClicked:(id)sender
|
||||
{
|
||||
buttonClicked(self->gocontrol);
|
||||
}
|
||||
|
||||
- (IBAction)checkboxToggled:(id)sender
|
||||
{
|
||||
checkboxToggled(self->gocontrol);
|
||||
}
|
||||
|
||||
- (void)controlTextDidChange:(NSNotification *)note
|
||||
{
|
||||
textfieldChanged(self->gocontrol);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
id newButton(void)
|
||||
{
|
||||
NSButton *b;
|
||||
|
||||
b = [[NSButton alloc] initWithFrame:NSZeroRect];
|
||||
[b setButtonType:NSMomentaryPushInButton];
|
||||
[b setBordered:YES];
|
||||
[b setBezelStyle:NSRoundedBezelStyle];
|
||||
setStandardControlFont((id) b);
|
||||
return (id) b;
|
||||
}
|
||||
|
||||
void buttonSetDelegate(id button, void *b)
|
||||
{
|
||||
goControlDelegate *d;
|
||||
|
||||
d = [goControlDelegate new];
|
||||
d->gocontrol = b;
|
||||
[toNSButton(button) setTarget:d];
|
||||
[toNSButton(button) setAction:@selector(buttonClicked:)];
|
||||
}
|
||||
|
||||
const char *buttonText(id button)
|
||||
{
|
||||
return [[toNSButton(button) title] UTF8String];
|
||||
}
|
||||
|
||||
void buttonSetText(id button, char *text)
|
||||
{
|
||||
[toNSButton(button) setTitle:[NSString stringWithUTF8String:text]];
|
||||
}
|
||||
|
||||
id newCheckbox(void)
|
||||
{
|
||||
NSButton *c;
|
||||
|
||||
c = [[NSButton alloc] initWithFrame:NSZeroRect];
|
||||
[c setButtonType:NSSwitchButton];
|
||||
[c setBordered:NO];
|
||||
setStandardControlFont((id) c);
|
||||
return (id) c;
|
||||
}
|
||||
|
||||
void checkboxSetDelegate(id checkbox, void *b)
|
||||
{
|
||||
goControlDelegate *d;
|
||||
|
||||
d = [goControlDelegate new];
|
||||
d->gocontrol = b;
|
||||
[toNSButton(checkbox) setTarget:d];
|
||||
[toNSButton(checkbox) setAction:@selector(checkboxToggled:)];
|
||||
}
|
||||
|
||||
BOOL checkboxChecked(id c)
|
||||
{
|
||||
if ([toNSButton(c) state] == NSOnState)
|
||||
return YES;
|
||||
return NO;
|
||||
}
|
||||
|
||||
void checkboxSetChecked(id c, BOOL checked)
|
||||
{
|
||||
NSInteger state;
|
||||
|
||||
state = NSOnState;
|
||||
if (checked == NO)
|
||||
state = NSOffState;
|
||||
[toNSButton(c) setState:state];
|
||||
}
|
||||
|
||||
// also good for labels
|
||||
// not static because area_darwin.m uses it
|
||||
id finishNewTextField(id _t, BOOL bordered)
|
||||
{
|
||||
NSTextField *t = toNSTextField(_t);
|
||||
|
||||
// same for text fields, password fields, and labels
|
||||
setStandardControlFont((id) t);
|
||||
// these three are the same across text fields, password fields, and labels; the only difference is the setBezeled: value, and it's only different on labels
|
||||
// THE ORDER OF THESE CALLS IS IMPORTANT; CHANGE IT AND THE BORDERS WILL DISAPPEAR
|
||||
[t setBordered:NO];
|
||||
[t setBezelStyle:NSTextFieldSquareBezel];
|
||||
[t setBezeled:bordered];
|
||||
// smart quotes and other autocorrect features are handled by the window; see newWindow() in window_darwin.m for details
|
||||
// Interface Builder does this to make the text box behave properly
|
||||
// this disables both word wrap AND ellipsizing in one fell swoop
|
||||
// however, we need to send it to the control's cell, not to the control directly
|
||||
[[t cell] setLineBreakMode:NSLineBreakByClipping];
|
||||
// Interface Builder also sets this to allow horizontal scrolling
|
||||
// it also sets this for labels, despite those not being scrollable
|
||||
[[t cell] setScrollable:YES];
|
||||
return (id) t;
|
||||
}
|
||||
|
||||
id newTextField(void)
|
||||
{
|
||||
NSTextField *t;
|
||||
|
||||
t = [[NSTextField alloc] initWithFrame:NSZeroRect];
|
||||
[t setSelectable:YES]; // otherwise the setting is masked by the editable default of YES
|
||||
return finishNewTextField((id) t, YES);
|
||||
}
|
||||
|
||||
id newPasswordField(void)
|
||||
{
|
||||
NSSecureTextField *t;
|
||||
|
||||
t = [[NSSecureTextField alloc] initWithFrame:NSZeroRect];
|
||||
[t setSelectable:YES]; // otherwise the setting is masked by the editable default of YES
|
||||
return finishNewTextField((id) t, YES);
|
||||
}
|
||||
|
||||
void textfieldSetDelegate(id textfield, void *t)
|
||||
{
|
||||
goControlDelegate *d;
|
||||
|
||||
d = [goControlDelegate new];
|
||||
d->gocontrol = t;
|
||||
[toNSTextField(textfield) setDelegate:d];
|
||||
}
|
||||
|
||||
// also good for labels
|
||||
const char *textfieldText(id t)
|
||||
{
|
||||
return [[toNSTextField(t) stringValue] UTF8String];
|
||||
}
|
||||
|
||||
// also good for labels
|
||||
void textfieldSetText(id t, char *text)
|
||||
{
|
||||
[toNSTextField(t) setStringValue:[NSString stringWithUTF8String:text]];
|
||||
}
|
||||
|
||||
id textfieldOpenInvalidPopover(id textfield, char *reason)
|
||||
{
|
||||
id popover;
|
||||
|
||||
popover = newWarningPopover(reason);
|
||||
warningPopoverShow(popover, textfield);
|
||||
NSBeep();
|
||||
return (id) popover;
|
||||
}
|
||||
|
||||
void textfieldCloseInvalidPopover(id popover)
|
||||
{
|
||||
[toNSWindow(popover) close];
|
||||
// don't release; close does that already
|
||||
}
|
||||
|
||||
BOOL textfieldEditable(id textfield)
|
||||
{
|
||||
return [toNSTextField(textfield) isEditable];
|
||||
}
|
||||
|
||||
void textfieldSetEditable(id textfield, BOOL editable)
|
||||
{
|
||||
[toNSTextField(textfield) setEditable:editable];
|
||||
}
|
||||
|
||||
id newLabel(void)
|
||||
{
|
||||
NSTextField *l;
|
||||
|
||||
l = [[NSTextField alloc] initWithFrame:NSZeroRect];
|
||||
[l setEditable:NO];
|
||||
[l setSelectable:NO];
|
||||
[l setDrawsBackground:NO];
|
||||
return finishNewTextField((id) l, NO);
|
||||
}
|
||||
|
||||
id newGroup(id container)
|
||||
{
|
||||
NSBox *group;
|
||||
|
||||
group = [[NSBox alloc] initWithFrame:NSZeroRect];
|
||||
[group setBorderType:NSLineBorder];
|
||||
[group setBoxType:NSBoxPrimary];
|
||||
[group setTransparent:NO];
|
||||
// can't use setSmallControlFont() here because the selector is different
|
||||
[group setTitleFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]]];
|
||||
[group setTitlePosition:NSAtTop];
|
||||
[group setContentView:toNSView(container)];
|
||||
return (id) group;
|
||||
}
|
||||
|
||||
const char *groupText(id group)
|
||||
{
|
||||
return [[toNSBox(group) title] UTF8String];
|
||||
}
|
||||
|
||||
void groupSetText(id group, char *text)
|
||||
{
|
||||
[toNSBox(group) setTitle:[NSString stringWithUTF8String:text]];
|
||||
}
|
||||
|
||||
id newTextbox(void)
|
||||
{
|
||||
NSTextView *tv;
|
||||
|
||||
tv = [[NSTextView alloc] initWithFrame:NSZeroRect];
|
||||
// verified against Interface Builder, except for rich text options
|
||||
[tv setAllowsDocumentBackgroundColorChange:NO];
|
||||
[tv setBackgroundColor:[NSColor textBackgroundColor]];
|
||||
[tv setTextColor:[NSColor textColor]];
|
||||
[tv setAllowsUndo:YES];
|
||||
[tv setEditable:YES];
|
||||
[tv setSelectable:YES];
|
||||
[tv setRichText:NO];
|
||||
[tv setImportsGraphics:NO];
|
||||
[tv setBaseWritingDirection:NSWritingDirectionNatural];
|
||||
// TODO default paragraph format
|
||||
[tv setAllowsImageEditing:NO];
|
||||
[tv setAutomaticQuoteSubstitutionEnabled:NO];
|
||||
[tv setAutomaticLinkDetectionEnabled:NO];
|
||||
[tv setUsesRuler:NO];
|
||||
[tv setRulerVisible:NO];
|
||||
[tv setUsesInspectorBar:NO];
|
||||
[tv setSelectionGranularity:NSSelectByCharacter];
|
||||
//TODO [tv setInsertionPointColor:[NSColor insertionColor]];
|
||||
[tv setContinuousSpellCheckingEnabled:NO];
|
||||
[tv setGrammarCheckingEnabled:NO];
|
||||
[tv setUsesFontPanel:NO];
|
||||
[tv setEnabledTextCheckingTypes:0];
|
||||
[tv setAutomaticDashSubstitutionEnabled:NO];
|
||||
[tv setAutomaticSpellingCorrectionEnabled:NO];
|
||||
[tv setAutomaticTextReplacementEnabled:NO];
|
||||
[tv setSmartInsertDeleteEnabled:NO];
|
||||
[tv setLayoutOrientation:NSTextLayoutOrientationHorizontal];
|
||||
// TODO default find panel behavior
|
||||
// now just to be safe; this will do some of the above but whatever
|
||||
disableAutocorrect((id) tv);
|
||||
// this option is complex; just set it to the Interface Builder default
|
||||
[[tv layoutManager] setAllowsNonContiguousLayout:YES];
|
||||
// this will work because it's the same selector
|
||||
setStandardControlFont((id) tv);
|
||||
return (id) tv;
|
||||
}
|
||||
|
||||
char *textboxText(id tv)
|
||||
{
|
||||
return [[toNSTextView(tv) string] UTF8String];
|
||||
}
|
||||
|
||||
void textboxSetText(id tv, char *text)
|
||||
{
|
||||
[toNSTextView(tv) setString:[NSString stringWithUTF8String:text]];
|
||||
}
|
||||
|
||||
id newProgressBar(void)
|
||||
{
|
||||
NSProgressIndicator *pi;
|
||||
|
||||
pi = [[NSProgressIndicator alloc] initWithFrame:NSZeroRect];
|
||||
[pi setStyle:NSProgressIndicatorBarStyle];
|
||||
[pi setControlSize:NSRegularControlSize];
|
||||
[pi setControlTint:NSDefaultControlTint];
|
||||
[pi setBezeled:YES];
|
||||
[pi setDisplayedWhenStopped:YES];
|
||||
[pi setUsesThreadedAnimation:YES];
|
||||
[pi setIndeterminate:NO];
|
||||
[pi setMinValue:0];
|
||||
[pi setMaxValue:100];
|
||||
[pi setDoubleValue:0];
|
||||
return (id) pi;
|
||||
}
|
||||
|
||||
intmax_t progressbarPercent(id pbar)
|
||||
{
|
||||
return (intmax_t) [toNSProgressIndicator(pbar) doubleValue];
|
||||
}
|
||||
|
||||
void progressbarSetPercent(id pbar, intmax_t percent)
|
||||
{
|
||||
[toNSProgressIndicator(pbar) setDoubleValue:((double) percent)];
|
||||
}
|
|
@ -1,254 +0,0 @@
|
|||
// 17 july 2014
|
||||
|
||||
#include "winapi_windows.h"
|
||||
#include "_cgo_export.h"
|
||||
|
||||
static LRESULT CALLBACK buttonSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR id, DWORD_PTR data)
|
||||
{
|
||||
switch (uMsg) {
|
||||
case msgCOMMAND:
|
||||
if (HIWORD(wParam) == BN_CLICKED) {
|
||||
buttonClicked((void *) data);
|
||||
return 0;
|
||||
}
|
||||
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
|
||||
case WM_NCDESTROY:
|
||||
if ((*fv_RemoveWindowSubclass)(hwnd, buttonSubProc, id) == FALSE)
|
||||
xpanic("error removing Button subclass (which was for its own event handler)", GetLastError());
|
||||
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
|
||||
default:
|
||||
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
xmissedmsg("Button", "buttonSubProc()", uMsg);
|
||||
return 0; // unreached
|
||||
}
|
||||
|
||||
void setButtonSubclass(HWND hwnd, void *data)
|
||||
{
|
||||
if ((*fv_SetWindowSubclass)(hwnd, buttonSubProc, 0, (DWORD_PTR) data) == FALSE)
|
||||
xpanic("error subclassing Button to give it its own event handler", GetLastError());
|
||||
}
|
||||
|
||||
static LRESULT CALLBACK checkboxSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR id, DWORD_PTR data)
|
||||
{
|
||||
switch (uMsg) {
|
||||
case msgCOMMAND:
|
||||
if (HIWORD(wParam) == BN_CLICKED) {
|
||||
WPARAM check;
|
||||
|
||||
// we didn't use BS_AUTOCHECKBOX (see controls_windows.go) so we have to manage the check state ourselves
|
||||
check = BST_CHECKED;
|
||||
if (SendMessage(hwnd, BM_GETCHECK, 0, 0) == BST_CHECKED)
|
||||
check = BST_UNCHECKED;
|
||||
SendMessage(hwnd, BM_SETCHECK, check, 0);
|
||||
checkboxToggled((void *) data);
|
||||
return 0;
|
||||
}
|
||||
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
|
||||
case WM_NCDESTROY:
|
||||
if ((*fv_RemoveWindowSubclass)(hwnd, checkboxSubProc, id) == FALSE)
|
||||
xpanic("error removing Checkbox subclass (which was for its own event handler)", GetLastError());
|
||||
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
|
||||
default:
|
||||
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
xmissedmsg("Checkbox", "checkboxSubProc()", uMsg);
|
||||
return 0; // unreached
|
||||
}
|
||||
|
||||
void setCheckboxSubclass(HWND hwnd, void *data)
|
||||
{
|
||||
if ((*fv_SetWindowSubclass)(hwnd, checkboxSubProc, 0, (DWORD_PTR) data) == FALSE)
|
||||
xpanic("error subclassing Checkbox to give it its own event handler", GetLastError());
|
||||
}
|
||||
|
||||
BOOL checkboxChecked(HWND hwnd)
|
||||
{
|
||||
if (SendMessage(hwnd, BM_GETCHECK, 0, 0) == BST_UNCHECKED)
|
||||
return FALSE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void checkboxSetChecked(HWND hwnd, BOOL c)
|
||||
{
|
||||
WPARAM check;
|
||||
|
||||
check = BST_CHECKED;
|
||||
if (c == FALSE)
|
||||
check = BST_UNCHECKED;
|
||||
SendMessage(hwnd, BM_SETCHECK, check, 0);
|
||||
}
|
||||
|
||||
static LRESULT CALLBACK textfieldSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR id, DWORD_PTR data)
|
||||
{
|
||||
switch (uMsg) {
|
||||
case msgCOMMAND:
|
||||
if (HIWORD(wParam) == EN_CHANGE) {
|
||||
textfieldChanged((void *) data);
|
||||
return 0;
|
||||
}
|
||||
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
|
||||
case WM_NCDESTROY:
|
||||
if ((*fv_RemoveWindowSubclass)(hwnd, textfieldSubProc, id) == FALSE)
|
||||
xpanic("error removing TextField subclass (which was for its own event handler)", GetLastError());
|
||||
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
|
||||
default:
|
||||
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
xmissedmsg("TextField", "textfieldSubProc()", uMsg);
|
||||
return 0; // unreached
|
||||
}
|
||||
|
||||
void setTextFieldSubclass(HWND hwnd, void *data)
|
||||
{
|
||||
if ((*fv_SetWindowSubclass)(hwnd, textfieldSubProc, 0, (DWORD_PTR) data) == FALSE)
|
||||
xpanic("error subclassing TextField to give it its own event handler", GetLastError());
|
||||
}
|
||||
|
||||
void textfieldSetAndShowInvalidBalloonTip(HWND hwnd, WCHAR *text)
|
||||
{
|
||||
EDITBALLOONTIP ti;
|
||||
|
||||
ZeroMemory(&ti, sizeof (EDITBALLOONTIP));
|
||||
ti.cbStruct = sizeof (EDITBALLOONTIP);
|
||||
// this is required to show the error icon
|
||||
// this probably should be localized...
|
||||
ti.pszTitle = L"Invalid Input";
|
||||
ti.pszText = text;
|
||||
ti.ttiIcon = TTI_ERROR;
|
||||
if (SendMessageW(hwnd, EM_SHOWBALLOONTIP, 0, (LPARAM) (&ti)) == FALSE)
|
||||
xpanic("error showing TextField.Invalid() balloon tip", GetLastError());
|
||||
if (MessageBeep(0xFFFFFFFF) == 0)
|
||||
xpanic("error beeping in response to TextField.Invalid()", GetLastError());
|
||||
}
|
||||
|
||||
void textfieldHideInvalidBalloonTip(HWND hwnd)
|
||||
{
|
||||
if (SendMessageW(hwnd, EM_HIDEBALLOONTIP, 0, 0) == FALSE)
|
||||
xpanic("error hiding TextField.Invalid() balloon tip", GetLastError());
|
||||
}
|
||||
|
||||
// also good for Textbox
|
||||
int textfieldReadOnly(HWND hwnd)
|
||||
{
|
||||
return (GetWindowLongPtrW(hwnd, GWL_STYLE) & ES_READONLY) != 0;
|
||||
}
|
||||
|
||||
// also good for Textbox
|
||||
void textfieldSetReadOnly(HWND hwnd, BOOL readonly)
|
||||
{
|
||||
if (SendMessageW(hwnd, EM_SETREADONLY, (WPARAM) readonly, 0) == 0)
|
||||
xpanic("error setting TextField/Textbox as read-only/not read-only", GetLastError());
|
||||
}
|
||||
|
||||
static LRESULT CALLBACK groupSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR id, DWORD_PTR data)
|
||||
{
|
||||
LRESULT lResult;
|
||||
RECT r;
|
||||
|
||||
if (sharedWndProc(hwnd, uMsg, wParam, lParam, &lResult))
|
||||
return lResult;
|
||||
switch (uMsg) {
|
||||
// don't do this on WM_WINDOWPOSCHANGING; weird redraw issues will happen
|
||||
case WM_WINDOWPOSCHANGED:
|
||||
// don't use the WINDOWPOS rect here; the coordinates of the controls have to be in real client coordinates
|
||||
if (GetClientRect(hwnd, &r) == 0)
|
||||
xpanic("error getting client rect of Group for resizing its child Control", GetLastError());
|
||||
groupResized((void *) data, r);
|
||||
// and chain up
|
||||
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
|
||||
case WM_NCDESTROY:
|
||||
if ((*fv_RemoveWindowSubclass)(hwnd, groupSubProc, id) == FALSE)
|
||||
xpanic("error removing Group subclass (which was for its own event handler)", GetLastError());
|
||||
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
|
||||
default:
|
||||
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
xmissedmsg("Group", "groupSubProc()", uMsg);
|
||||
return 0; // unreached
|
||||
}
|
||||
|
||||
void setGroupSubclass(HWND hwnd, void *data)
|
||||
{
|
||||
if ((*fv_SetWindowSubclass)(hwnd, groupSubProc, 0, (DWORD_PTR) data) == FALSE)
|
||||
xpanic("error subclassing Group to give it its own event handler", GetLastError());
|
||||
}
|
||||
|
||||
static LRESULT CALLBACK updownSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR id, DWORD_PTR data)
|
||||
{
|
||||
NMHDR *nmhdr = (NMHDR *) lParam;
|
||||
|
||||
switch (uMsg) {
|
||||
case msgNOTIFY:
|
||||
switch (nmhdr->code) {
|
||||
case UDN_DELTAPOS:
|
||||
spinboxUpDownClicked((void *) data, (NMUPDOWN *) lParam);
|
||||
return FALSE; // allow change
|
||||
}
|
||||
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
|
||||
case WM_NCDESTROY:
|
||||
if ((*fv_RemoveWindowSubclass)(hwnd, updownSubProc, id) == FALSE)
|
||||
xpanic("error removing Spinbox up-down control subclass (which was for its own event handler)", GetLastError());
|
||||
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
|
||||
default:
|
||||
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
xmissedmsg("Spinbox up-down control", "updownSubProc()", uMsg);
|
||||
return 0; // unreached
|
||||
}
|
||||
|
||||
HWND newUpDown(HWND prevUpDown, void *data)
|
||||
{
|
||||
HWND hwnd;
|
||||
HWND parent;
|
||||
|
||||
parent = msgwin; // for the first up-down
|
||||
if (prevUpDown != NULL) {
|
||||
parent = GetParent(prevUpDown);
|
||||
if (parent == NULL)
|
||||
xpanic("error getting parent of old up-down in Spinbox resize for new up-down", GetLastError());
|
||||
if (DestroyWindow(prevUpDown) == 0)
|
||||
xpanic("error destroying previous up-down in Spinbox resize", GetLastError());
|
||||
}
|
||||
hwnd = CreateWindowExW(0,
|
||||
UPDOWN_CLASSW, L"",
|
||||
// no WS_VISIBLE; we set visibility ourselves
|
||||
WS_CHILD | UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_HOTTRACK | UDS_NOTHOUSANDS | UDS_SETBUDDYINT,
|
||||
// this is important; it's necessary for autosizing to work
|
||||
0, 0, 0, 0,
|
||||
parent, NULL, hInstance, NULL);
|
||||
if (hwnd == NULL)
|
||||
xpanic("error creating up-down control for Spinbox", GetLastError());
|
||||
if ((*fv_SetWindowSubclass)(hwnd, updownSubProc, 0, (DWORD_PTR) data) == FALSE)
|
||||
xpanic("error subclassing Spinbox up-down control to give it its own event handler", GetLastError());
|
||||
return hwnd;
|
||||
}
|
||||
|
||||
static LRESULT CALLBACK spinboxEditSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR id, DWORD_PTR data)
|
||||
{
|
||||
switch (uMsg) {
|
||||
case msgCOMMAND:
|
||||
if (HIWORD(wParam) == EN_CHANGE) {
|
||||
spinboxEditChanged((void *) data);
|
||||
return 0;
|
||||
}
|
||||
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
|
||||
case WM_NCDESTROY:
|
||||
if ((*fv_RemoveWindowSubclass)(hwnd, spinboxEditSubProc, id) == FALSE)
|
||||
xpanic("error removing Spinbox edit control subclass (which was for its own event handler)", GetLastError());
|
||||
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
|
||||
default:
|
||||
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
xmissedmsg("Spinbox edit control", "spinboxEditSubProc()", uMsg);
|
||||
return 0; // unreached
|
||||
}
|
||||
|
||||
void setSpinboxEditSubclass(HWND hwnd, void *data)
|
||||
{
|
||||
if ((*fv_SetWindowSubclass)(hwnd, spinboxEditSubProc, 0, (DWORD_PTR) data) == FALSE)
|
||||
xpanic("error subclassing Spinbox edit control to give it its own event handler", GetLastError());
|
||||
}
|
||||
|
||||
// provided for cgo's benefit
|
||||
LPWSTR xPROGRESS_CLASS = PROGRESS_CLASS;
|
|
@ -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()
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -1,131 +0,0 @@
|
|||
// 17 july 2014
|
||||
|
||||
#include "winapi_windows.h"
|
||||
|
||||
static ULONG_PTR comctlManifestCookie;
|
||||
static HMODULE comctl32;
|
||||
|
||||
// these are listed as WINAPI in both Microsoft's and MinGW's headers, but not on MSDN for some reason
|
||||
BOOL (*WINAPI fv_SetWindowSubclass)(HWND, SUBCLASSPROC, UINT_PTR, DWORD_PTR);
|
||||
BOOL (*WINAPI fv_RemoveWindowSubclass)(HWND, SUBCLASSPROC, UINT_PTR);
|
||||
LRESULT (*WINAPI fv_DefSubclassProc)(HWND, UINT, WPARAM, LPARAM);
|
||||
|
||||
// these are listed as WINAPI on MSDN
|
||||
BOOL (*WINAPI fv__TrackMouseEvent)(LPTRACKMOUSEEVENT);
|
||||
|
||||
#define wantedICCClasses ( \
|
||||
ICC_PROGRESS_CLASS | /* progress bars */ \
|
||||
ICC_TAB_CLASSES | /* tabs */ \
|
||||
ICC_LISTVIEW_CLASSES | /* table headers */ \
|
||||
ICC_UPDOWN_CLASS | /* spinboxes */ \
|
||||
0)
|
||||
|
||||
// note that this is an 8-bit character string we're writing; see the encoding clause
|
||||
static const char manifest[] = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\">\n<assemblyIdentity\n version=\"1.0.0.0\"\n processorArchitecture=\"*\"\n name=\"CompanyName.ProductName.YourApplication\"\n type=\"win32\"\n/>\n<description>Your application description here.</description>\n<dependency>\n <dependentAssembly>\n <assemblyIdentity\n type=\"win32\"\n name=\"Microsoft.Windows.Common-Controls\"\n version=\"6.0.0.0\"\n processorArchitecture=\"*\"\n publicKeyToken=\"6595b64144ccf1df\"\n language=\"*\"\n />\n </dependentAssembly>\n</dependency>\n</assembly>\n";
|
||||
|
||||
/*
|
||||
Windows requires a manifest file to enable Common Controls version 6.
|
||||
The only way to not require an external manifest is to synthesize the manifest ourselves.
|
||||
We can use the activation context API to load it at runtime.
|
||||
References:
|
||||
- http://stackoverflow.com/questions/4308503/how-to-enable-visual-styles-without-a-manifest
|
||||
- http://support.microsoft.com/kb/830033
|
||||
Because neither Go nor MinGW have ways to compile in resources like this (as far as I know), we have to do the work ourselves.
|
||||
*/
|
||||
DWORD initCommonControls(char **errmsg)
|
||||
{
|
||||
WCHAR temppath[MAX_PATH + 1];
|
||||
WCHAR filename[MAX_PATH + 1];
|
||||
HANDLE file;
|
||||
DWORD nExpected, nGot;
|
||||
ACTCTX actctx;
|
||||
HANDLE ac;
|
||||
INITCOMMONCONTROLSEX icc;
|
||||
FARPROC f;
|
||||
// this is listed as WINAPI in both Microsoft's and MinGW's headers, but not on MSDN for some reason
|
||||
BOOL (*WINAPI ficc)(const LPINITCOMMONCONTROLSEX);
|
||||
|
||||
if (GetTempPathW(MAX_PATH + 1, temppath) == 0) {
|
||||
*errmsg = "error getting temporary path for writing manifest file";
|
||||
return GetLastError();
|
||||
}
|
||||
if (GetTempFileNameW(temppath, L"manifest", 0, filename) == 0) {
|
||||
*errmsg = "error getting temporary filename for writing manifest file";
|
||||
return GetLastError();
|
||||
}
|
||||
file = CreateFileW(filename, GENERIC_WRITE,
|
||||
0, // don't share while writing
|
||||
NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if (file == NULL) {
|
||||
*errmsg = "error creating manifest file";
|
||||
return GetLastError();
|
||||
}
|
||||
nExpected = (sizeof manifest / sizeof manifest[0]) - 1; // - 1 to omit the terminating null character)
|
||||
SetLastError(0); // catch errorless short writes
|
||||
if (WriteFile(file, manifest, nExpected, &nGot, NULL) == 0) {
|
||||
*errmsg = "error writing manifest file";
|
||||
return GetLastError();
|
||||
}
|
||||
if (nGot != nExpected) {
|
||||
DWORD lasterr;
|
||||
|
||||
lasterr = GetLastError();
|
||||
*errmsg = "short write to manifest file";
|
||||
if (lasterr == 0)
|
||||
*errmsg = "short write to manifest file without error code";
|
||||
return lasterr;
|
||||
}
|
||||
if (CloseHandle(file) == 0) {
|
||||
*errmsg = "error closing manifest file (this IS an error here because not doing so will prevent Windows from being able to use the manifest file in an activation context)";
|
||||
return GetLastError();
|
||||
}
|
||||
|
||||
ZeroMemory(&actctx, sizeof (ACTCTX));
|
||||
actctx.cbSize = sizeof (ACTCTX);
|
||||
actctx.dwFlags = ACTCTX_FLAG_SET_PROCESS_DEFAULT;
|
||||
actctx.lpSource = filename;
|
||||
ac = CreateActCtx(&actctx);
|
||||
if (ac == INVALID_HANDLE_VALUE) {
|
||||
*errmsg = "error creating activation context for synthesized manifest file";
|
||||
return GetLastError();
|
||||
}
|
||||
if (ActivateActCtx(ac, &comctlManifestCookie) == FALSE) {
|
||||
*errmsg = "error activating activation context for synthesized manifest file";
|
||||
return GetLastError();
|
||||
}
|
||||
|
||||
ZeroMemory(&icc, sizeof (INITCOMMONCONTROLSEX));
|
||||
icc.dwSize = sizeof (INITCOMMONCONTROLSEX);
|
||||
icc.dwICC = wantedICCClasses;
|
||||
|
||||
comctl32 = LoadLibraryW(L"comctl32.dll");
|
||||
if (comctl32 == NULL) {
|
||||
*errmsg = "error loading comctl32.dll";
|
||||
return GetLastError();
|
||||
}
|
||||
|
||||
// GetProcAddress() only takes a multibyte string
|
||||
#define LOAD(fn) f = GetProcAddress(comctl32, fn); \
|
||||
if (f == NULL) { \
|
||||
*errmsg = "error loading " fn "()"; \
|
||||
return GetLastError(); \
|
||||
}
|
||||
|
||||
LOAD("InitCommonControlsEx");
|
||||
ficc = (BOOL (*WINAPI)(const LPINITCOMMONCONTROLSEX)) f;
|
||||
LOAD("SetWindowSubclass");
|
||||
fv_SetWindowSubclass = (BOOL (*WINAPI)(HWND, SUBCLASSPROC, UINT_PTR, DWORD_PTR)) f;
|
||||
LOAD("RemoveWindowSubclass");
|
||||
fv_RemoveWindowSubclass = (BOOL (*WINAPI)(HWND, SUBCLASSPROC, UINT_PTR)) f;
|
||||
LOAD("DefSubclassProc");
|
||||
fv_DefSubclassProc = (LRESULT (*WINAPI)(HWND, UINT, WPARAM, LPARAM)) f;
|
||||
LOAD("_TrackMouseEvent");
|
||||
fv__TrackMouseEvent = (HIMAGELIST (*WINAPI)(int, int, UINT, int, int)) f;
|
||||
|
||||
if ((*ficc)(&icc) == FALSE) {
|
||||
*errmsg = "error initializing Common Controls (comctl32.dll)";
|
||||
return GetLastError();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
// 20 july 2014
|
||||
|
||||
package ui
|
||||
|
||||
// #include "objc_darwin.h"
|
||||
import "C"
|
||||
|
||||
func fromBOOL(b C.BOOL) bool {
|
||||
if b != C.NO {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func toBOOL(b bool) C.BOOL {
|
||||
if b == true {
|
||||
return C.YES
|
||||
}
|
||||
return C.NO
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
// 11 august 2014
|
||||
|
||||
#include "objc_darwin.h"
|
||||
#include <Cocoa/Cocoa.h>
|
||||
|
||||
void disableAutocorrect(id onwhat)
|
||||
{
|
||||
NSTextView *tv;
|
||||
|
||||
tv = (NSTextView *) onwhat;
|
||||
[tv setEnabledTextCheckingTypes:0];
|
||||
[tv setAutomaticDashSubstitutionEnabled:NO];
|
||||
// don't worry about automatic data detection; it won't change stringValue (thanks pretty_function in irc.freenode.net/#macdev)
|
||||
[tv setAutomaticSpellingCorrectionEnabled:NO];
|
||||
[tv setAutomaticTextReplacementEnabled:NO];
|
||||
[tv setAutomaticQuoteSubstitutionEnabled:NO];
|
||||
[tv setAutomaticLinkDetectionEnabled:NO];
|
||||
[tv setSmartInsertDeleteEnabled:NO];
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
// +build !windows,!darwin
|
||||
|
||||
// 7 july 2014
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// #include "gtk_unix.h"
|
||||
// /* because cgo doesn't like g_signal_connect() */
|
||||
// void gSignalConnect(gpointer obj, gchar *sig, GCallback callback, gpointer data)
|
||||
// {
|
||||
// g_signal_connect(obj, sig, callback, data);
|
||||
// }
|
||||
// void gSignalConnectAfter(gpointer obj, gchar *sig, GCallback callback, gpointer data)
|
||||
// {
|
||||
// g_signal_connect_after(obj, sig, callback, data);
|
||||
// }
|
||||
import "C"
|
||||
|
||||
func fromgstr(s *C.gchar) string {
|
||||
return C.GoString((*C.char)(unsafe.Pointer(s)))
|
||||
}
|
||||
|
||||
func togstr(s string) *C.gchar {
|
||||
return (*C.gchar)(unsafe.Pointer(C.CString(s)))
|
||||
}
|
||||
|
||||
func freegstr(s *C.gchar) {
|
||||
C.free(unsafe.Pointer(s))
|
||||
}
|
||||
|
||||
func fromgbool(b C.gboolean) bool {
|
||||
return b != C.FALSE
|
||||
}
|
||||
|
||||
func togbool(b bool) C.gboolean {
|
||||
if b == true {
|
||||
return C.TRUE
|
||||
}
|
||||
return C.FALSE
|
||||
}
|
||||
|
||||
func g_signal_connect(object C.gpointer, name string, to C.GCallback, data C.gpointer) {
|
||||
cname := togstr(name)
|
||||
defer freegstr(cname)
|
||||
C.gSignalConnect(object, cname, to, data)
|
||||
}
|
||||
|
||||
func g_signal_connect_after(object C.gpointer, name string, to C.GCallback, data C.gpointer) {
|
||||
cname := togstr(name)
|
||||
defer freegstr(cname)
|
||||
C.gSignalConnectAfter(object, cname, to, data)
|
||||
}
|
|
@ -1,165 +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;
|
||||
}
|
||||
|
||||
// this is a helper function that takes the logic of determining window classes and puts it all in one place
|
||||
// there are a number of places where we need to know what window class an arbitrary handle has
|
||||
// theoretically we could use the class atom to avoid a _wcsicmp()
|
||||
// however, raymond chen advises against this - http://blogs.msdn.com/b/oldnewthing/archive/2004/10/11/240744.aspx (and we're not in control of the Tab class, before you say anything)
|
||||
// usage: windowClassOf(hwnd, L"class 1", L"class 2", ..., NULL)
|
||||
int windowClassOf(HWND hwnd, ...)
|
||||
{
|
||||
// MSDN says 256 is the maximum length of a class name; add a few characters just to be safe (because it doesn't say whether this includes the terminating null character)
|
||||
#define maxClassName 260
|
||||
WCHAR classname[maxClassName + 1];
|
||||
va_list ap;
|
||||
WCHAR *curname;
|
||||
int i;
|
||||
|
||||
if (GetClassNameW(hwnd, classname, maxClassName) == 0)
|
||||
xpanic("error getting name of window class in windowClassOf()", GetLastError());
|
||||
va_start(ap, hwnd);
|
||||
i = 0;
|
||||
for (;;) {
|
||||
curname = va_arg(ap, WCHAR *);
|
||||
if (curname == NULL)
|
||||
break;
|
||||
if (_wcsicmp(classname, curname) == 0) {
|
||||
va_end(ap);
|
||||
return i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
// no match
|
||||
va_end(ap);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
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:
|
||||
// read-only TextFields and Textboxes are exempt
|
||||
// this is because read-only edit controls count under WM_CTLCOLORSTATIC
|
||||
if (windowClassOf((HWND) lParam, L"edit", NULL) == 0)
|
||||
if (textfieldReadOnly((HWND) lParam))
|
||||
return FALSE;
|
||||
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, pOrig;
|
||||
|
||||
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;
|
||||
// skip groupboxes; they're (supposed to be) transparent
|
||||
if (windowClassOf(parent, L"button", NULL) != 0)
|
||||
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());
|
||||
if (SetWindowOrgEx(dc, p.x, p.y, &pOrig) == 0)
|
||||
xpanic("error moving window origin in paintControlBackground()", GetLastError());
|
||||
SendMessageW(parent, WM_PRINTCLIENT, (WPARAM) dc, PRF_CLIENT);
|
||||
if (SetWindowOrgEx(dc, pOrig.x, pOrig.y, NULL) == 0)
|
||||
xpanic("error resetting window origin in paintControlBackground()", GetLastError());
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
// 12 july 2014
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// #include "winapi_windows.h"
|
||||
import "C"
|
||||
|
||||
//export xpanic
|
||||
func xpanic(msg *C.char, lasterr C.DWORD) {
|
||||
panic(fmt.Errorf("%s: %s", C.GoString(msg), syscall.Errno(lasterr)))
|
||||
}
|
||||
|
||||
//export xpanichresult
|
||||
func xpanichresult(msg *C.char, hresult C.HRESULT) {
|
||||
panic(fmt.Errorf("%s; HRESULT: 0x%X", C.GoString(msg), hresult))
|
||||
}
|
||||
|
||||
//export xpaniccomdlg
|
||||
func xpaniccomdlg(msg *C.char, err C.DWORD) {
|
||||
panic(fmt.Errorf("%s; comdlg32.dll extended error: 0x%X", C.GoString(msg), err))
|
||||
}
|
||||
|
||||
//export xmissedmsg
|
||||
func xmissedmsg(purpose *C.char, f *C.char, uMsg C.UINT) {
|
||||
panic(fmt.Errorf("%s window procedure message %d does not return a value (bug in %s)", C.GoString(purpose), uMsg, C.GoString(f)))
|
||||
}
|
||||
|
||||
func toUTF16(s string) C.LPWSTR {
|
||||
return C.LPWSTR(unsafe.Pointer(syscall.StringToUTF16Ptr(s)))
|
||||
}
|
||||
|
||||
func getWindowText(hwnd C.HWND) string {
|
||||
// WM_GETTEXTLENGTH and WM_GETTEXT return the count /without/ the terminating null character
|
||||
// but WM_GETTEXT expects the buffer size handed to it to /include/ the terminating null character
|
||||
n := C.getWindowTextLen(hwnd)
|
||||
buf := make([]uint16, int(n+1))
|
||||
C.getWindowText(hwnd, C.WPARAM(n),
|
||||
C.LPWSTR(unsafe.Pointer(&buf[0])))
|
||||
return syscall.UTF16ToString(buf)
|
||||
}
|
||||
|
||||
func wstrToString(wstr *C.WCHAR) string {
|
||||
n := C.wcslen((*C.wchar_t)(unsafe.Pointer(wstr)))
|
||||
xbuf := &reflect.SliceHeader{
|
||||
Data: uintptr(unsafe.Pointer(wstr)),
|
||||
Len: int(n + 1),
|
||||
Cap: int(n + 1),
|
||||
}
|
||||
buf := (*[]uint16)(unsafe.Pointer(xbuf))
|
||||
return syscall.UTF16ToString(*buf)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
*/
|
|
@ -1,79 +0,0 @@
|
|||
// 4 august 2014
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// #include "objc_darwin.h"
|
||||
import "C"
|
||||
|
||||
type container struct {
|
||||
id C.id
|
||||
resize func(x int, y int, width int, height int, d *sizing)
|
||||
margined bool
|
||||
}
|
||||
|
||||
type sizing struct {
|
||||
sizingbase
|
||||
|
||||
// for size calculations
|
||||
// nothing on Mac OS X
|
||||
|
||||
// for the actual resizing
|
||||
neighborAlign C.struct_xalignment
|
||||
}
|
||||
|
||||
// containerResized() gets called early so we have to do this in the constructor
|
||||
func newContainer(resize func(x int, y int, width int, height int, d *sizing)) *container {
|
||||
c := new(container)
|
||||
c.resize = resize
|
||||
c.id = C.newContainerView(unsafe.Pointer(c))
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *container) parent() *controlParent {
|
||||
return &controlParent{c.id}
|
||||
}
|
||||
|
||||
//export containerResized
|
||||
func containerResized(data unsafe.Pointer) {
|
||||
c := (*container)(data)
|
||||
d := beginResize()
|
||||
// TODO make this a parameter
|
||||
b := C.containerBounds(c.id)
|
||||
if c.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
|
||||
}
|
||||
c.resize(int(b.x), int(b.y), int(b.width), int(b.height), d)
|
||||
}
|
||||
|
||||
// These are based on measurements from Interface Builder.
|
||||
// TODO reverify these against /layout rects/, not /frame rects/
|
||||
const (
|
||||
macXMargin = 20
|
||||
macYMargin = 20
|
||||
macXPadding = 8
|
||||
macYPadding = 8
|
||||
)
|
||||
|
||||
func 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
|
||||
}
|
||||
}
|
||||
*/
|
|
@ -1,66 +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
|
||||
|
||||
- (void)setFrameSize:(NSSize)s
|
||||
{
|
||||
[super setFrameSize:s];
|
||||
containerResized(self->gocontainer);
|
||||
}
|
||||
|
||||
@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;
|
||||
// here's the magic: what we specified was what we want the alignment rect to be; make it the actual frame
|
||||
frame = [v frameForAlignmentRect:frame];
|
||||
[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;
|
||||
}
|
|
@ -1,103 +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);
|
||||
containerResize(GOCONTAINER(widget)->gocontainer, 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);
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
// +build !windows,!darwin
|
||||
|
||||
// 23 february 2014
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// #include "gtk_unix.h"
|
||||
import "C"
|
||||
|
||||
// TODO avoid direct access to contents?
|
||||
type container struct {
|
||||
widget *C.GtkWidget
|
||||
container *C.GtkContainer
|
||||
resize func(x int, y int, width int, height int, d *sizing)
|
||||
margined bool
|
||||
}
|
||||
|
||||
type sizing struct {
|
||||
sizingbase
|
||||
|
||||
// for size calculations
|
||||
// gtk+ needs nothing
|
||||
|
||||
// for the actual resizing
|
||||
// gtk+ needs nothing
|
||||
}
|
||||
|
||||
func newContainer() *container {
|
||||
c := new(container)
|
||||
c.widget = C.newContainer(unsafe.Pointer(c))
|
||||
c.container = (*C.GtkContainer)(unsafe.Pointer(c.widget))
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *container) parent() *controlParent {
|
||||
return &controlParent{c.container}
|
||||
}
|
||||
|
||||
//export containerResize
|
||||
func containerResize(data unsafe.Pointer, aorig *C.GtkAllocation) {
|
||||
c := (*container)(data)
|
||||
d := beginResize()
|
||||
// copy aorig
|
||||
a := *aorig
|
||||
if c.margined {
|
||||
a.x += C.int(gtkXMargin)
|
||||
a.y += C.int(gtkYMargin)
|
||||
a.width -= C.int(gtkXMargin) * 2
|
||||
a.height -= C.int(gtkYMargin) * 2
|
||||
}
|
||||
c.resize(int(a.x), int(a.y), int(a.width), int(a.height), d)
|
||||
}
|
||||
|
||||
const (
|
||||
gtkXMargin = 12
|
||||
gtkYMargin = 12
|
||||
gtkXPadding = 12
|
||||
gtkYPadding = 6
|
||||
)
|
||||
|
||||
func beginResize() (d *sizing) {
|
||||
d = new(sizing)
|
||||
d.xpadding = gtkXPadding
|
||||
d.ypadding = gtkYPadding
|
||||
return d
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
// 17 july 2014
|
||||
|
||||
#include "winapi_windows.h"
|
||||
#include "_cgo_export.h"
|
||||
|
||||
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());
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
// 4 august 2014
|
||||
|
||||
package ui
|
||||
|
||||
// #include "winapi_windows.h"
|
||||
import "C"
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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 beginResize(hwnd C.HWND) (d *sizing) {
|
||||
var baseX, baseY C.int
|
||||
var internalLeading C.LONG
|
||||
|
||||
d = new(sizing)
|
||||
|
||||
C.calculateBaseUnits(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))
|
||||
}
|
|
@ -1,51 +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
|
||||
|
||||
// these are provided for Tab on Windows, where we have to show and hide the individual tab pages manually
|
||||
// if we ever get something like a SidebarStack of some sort, we'll need to implement this everywhere
|
||||
containerShow() // show if and only if programmer said to show
|
||||
containerHide() // hide regardless of whether programmer said to hide
|
||||
}
|
||||
|
||||
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
|
||||
fcontainerShow func()
|
||||
fcontainerHide func()
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
func (c *controlbase) containerShow() {
|
||||
c.fcontainerShow()
|
||||
}
|
||||
|
||||
func (c *controlbase) containerHide() {
|
||||
c.fcontainerHide()
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
// 30 july 2014
|
||||
|
||||
#import "objc_darwin.h"
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#define toNSView(x) ((NSView *) (x))
|
||||
#define toNSControl(x) ((NSControl *) (x))
|
||||
|
||||
void parent(id control, id parentid)
|
||||
{
|
||||
[toNSView(parentid) addSubview:toNSView(control)];
|
||||
}
|
||||
|
||||
void controlSetHidden(id control, BOOL hidden)
|
||||
{
|
||||
[toNSView(control) setHidden:hidden];
|
||||
}
|
||||
|
||||
// also fine for NSCells and NSTexts (NSTextViews)
|
||||
void setStandardControlFont(id control)
|
||||
{
|
||||
[toNSControl(control) setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]]];
|
||||
}
|
||||
|
||||
// also fine for NSCells
|
||||
void setSmallControlFont(id control)
|
||||
{
|
||||
[toNSControl(control) setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]]];
|
||||
}
|
||||
|
||||
// also good for NSBox and NSProgressIndicator
|
||||
struct xsize controlPreferredSize(id control)
|
||||
{
|
||||
NSControl *c;
|
||||
NSRect r;
|
||||
struct xsize s;
|
||||
|
||||
c = toNSControl(control);
|
||||
[c sizeToFit];
|
||||
// use alignmentRect here instead of frame because we'll be resizing based on that
|
||||
r = [c alignmentRectForFrame:[c frame]];
|
||||
s.width = (intptr_t) r.size.width;
|
||||
s.height = (intptr_t) r.size.height;
|
||||
return s;
|
||||
}
|
||||
|
||||
id newScrollView(id content, BOOL bordered)
|
||||
{
|
||||
NSScrollView *sv;
|
||||
|
||||
sv = [[NSScrollView alloc] initWithFrame:NSZeroRect];
|
||||
[sv setDocumentView:toNSView(content)];
|
||||
[sv setHasHorizontalScroller:YES];
|
||||
[sv setHasVerticalScroller:YES];
|
||||
[sv setAutohidesScrollers:YES];
|
||||
if (bordered)
|
||||
[sv setBorderType:NSBezelBorder];
|
||||
else
|
||||
[sv setBorderType:NSNoBorder];
|
||||
return (id) sv;
|
||||
}
|
||||
|
||||
// these function are safe to call on Areas; they'll just return the frame and a baseline of 0 since they use the default NSView implementations
|
||||
|
||||
static struct xalignment doAlignmentInfo(NSView *v, NSRect r)
|
||||
{
|
||||
struct xalignment a;
|
||||
|
||||
r = [v alignmentRectForFrame:r];
|
||||
a.rect.x = (intptr_t) r.origin.x;
|
||||
a.rect.y = (intptr_t) r.origin.y;
|
||||
a.rect.width = (intptr_t) r.size.width;
|
||||
a.rect.height = (intptr_t) r.size.height;
|
||||
// I'm not sure if we need to set the frame for -[NSView baselineOffsetFromBottom], but let's do it just to be safe
|
||||
[v setFrame:r];
|
||||
a.baseline = (intptr_t) [v baselineOffsetFromBottom];
|
||||
return a;
|
||||
}
|
||||
|
||||
struct xalignment alignmentInfo(id c, struct xrect newrect)
|
||||
{
|
||||
NSView *v;
|
||||
NSRect r;
|
||||
|
||||
v = toNSView(c);
|
||||
r = NSMakeRect((CGFloat) newrect.x,
|
||||
(CGFloat) newrect.y,
|
||||
(CGFloat) newrect.width,
|
||||
(CGFloat) newrect.height);
|
||||
return doAlignmentInfo(v, r);
|
||||
}
|
||||
|
||||
struct xalignment alignmentInfoFrame(id c)
|
||||
{
|
||||
NSView *v;
|
||||
|
||||
v = toNSView(c);
|
||||
return doAlignmentInfo(v, [v frame]);
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
// 17 july 2014
|
||||
|
||||
#include "winapi_windows.h"
|
||||
#include "_cgo_export.h"
|
||||
|
||||
HWND newControl(LPWSTR class, DWORD style, DWORD extstyle)
|
||||
{
|
||||
HWND hwnd;
|
||||
|
||||
hwnd = CreateWindowExW(
|
||||
extstyle,
|
||||
class, L"",
|
||||
style | WS_CHILD | WS_VISIBLE,
|
||||
CW_USEDEFAULT, CW_USEDEFAULT,
|
||||
CW_USEDEFAULT, CW_USEDEFAULT,
|
||||
// the following has the consequence of making the control message-only at first
|
||||
// this shouldn't cause any problems... hopefully not
|
||||
// but see the msgwndproc() for caveat info
|
||||
// also don't use low control IDs as they will conflict with dialog boxes (IDCANCEL, etc.)
|
||||
msgwin, (HMENU) 100, hInstance, NULL);
|
||||
if (hwnd == NULL)
|
||||
xpanic("error creating control", GetLastError());
|
||||
return hwnd;
|
||||
}
|
||||
|
||||
void controlSetParent(HWND control, HWND parent)
|
||||
{
|
||||
if (SetParent(control, parent) == NULL)
|
||||
xpanic("error changing control parent", GetLastError());
|
||||
}
|
||||
|
||||
void controlSetControlFont(HWND which)
|
||||
{
|
||||
SendMessageW(which, WM_SETFONT, (WPARAM) controlFont, TRUE);
|
||||
}
|
||||
|
||||
void moveWindow(HWND hwnd, int x, int y, int width, int height)
|
||||
{
|
||||
if (MoveWindow(hwnd, x, y, width, height, TRUE) == 0)
|
||||
xpanic("error setting window/control rect", GetLastError());
|
||||
}
|
||||
|
||||
LONG controlTextLength(HWND hwnd, LPWSTR text)
|
||||
{
|
||||
HDC dc;
|
||||
HFONT prev;
|
||||
SIZE size;
|
||||
|
||||
dc = GetDC(hwnd);
|
||||
if (dc == NULL)
|
||||
xpanic("error getting DC of control for text length", GetLastError());
|
||||
prev = SelectObject(dc, controlFont);
|
||||
if (prev == NULL)
|
||||
xpanic("error setting control font to DC for text length", GetLastError());
|
||||
if (GetTextExtentPoint32W(dc, text, wcslen(text), &size) == 0)
|
||||
xpanic("error actually getting text length", GetLastError());
|
||||
if (SelectObject(dc, prev) != controlFont)
|
||||
xpanic("error restoring previous control font to DC for text length", GetLastError());
|
||||
if (ReleaseDC(hwnd, dc) == 0)
|
||||
xpanic("error releasing DC of control for text length", GetLastError());
|
||||
return size.cx;
|
||||
}
|
|
@ -1,69 +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
|
||||
},
|
||||
fcontainerShow: func() {
|
||||
C.ShowWindow(c.hwnd, C.SW_SHOW)
|
||||
},
|
||||
fcontainerHide: func() {
|
||||
C.ShowWindow(c.hwnd, C.SW_HIDE)
|
||||
},
|
||||
}
|
||||
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)
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
// 18 august 2014
|
||||
|
||||
package ui
|
||||
|
||||
type windowDialog interface {
|
||||
openFile(f func(filename string))
|
||||
}
|
||||
|
||||
// OpenFile opens a dialog box that asks the user to choose a file.
|
||||
// The dialog box is modal to win, which mut not be nil.
|
||||
// Some time after the dialog box is closed, OpenFile runs f on the main thread, passing filename.
|
||||
// filename is the selected filename, or an empty string if no file was chosen.
|
||||
// OpenFile does not ensure that f remains alive; the programmer is responsible for this.
|
||||
// If possible on a given system, OpenFile() will not dereference links; it will return the link file itself.
|
||||
// Hidden files will not be hidden by OpenFile().
|
||||
func OpenFile(win Window, f func(filename string)) {
|
||||
if win == nil {
|
||||
panic("Window passed to OpenFile() cannot be nil")
|
||||
}
|
||||
win.openFile(f)
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
// 19 august 2014
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// #include "objc_darwin.h"
|
||||
import "C"
|
||||
|
||||
func (w *window) openFile(f func(filename string)) {
|
||||
C.openFile(w.id, unsafe.Pointer(&f))
|
||||
}
|
||||
|
||||
//export finishOpenFile
|
||||
func finishOpenFile(fname *C.char, data unsafe.Pointer) {
|
||||
f := (*func(string))(data)
|
||||
if fname == nil {
|
||||
(*f)("")
|
||||
return
|
||||
}
|
||||
defer C.free(unsafe.Pointer(fname))
|
||||
(*f)(C.GoString(fname))
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
// 19 august 2014
|
||||
|
||||
#import "objc_darwin.h"
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#define toNSWindow(x) ((NSWindow *) (x))
|
||||
|
||||
void openFile(id parent, void *data)
|
||||
{
|
||||
NSOpenPanel *op;
|
||||
|
||||
op = [NSOpenPanel openPanel];
|
||||
[op setCanChooseFiles:YES];
|
||||
[op setCanChooseDirectories:NO];
|
||||
[op setResolvesAliases:NO];
|
||||
[op setAllowsMultipleSelection:NO];
|
||||
[op setShowsHiddenFiles:YES];
|
||||
[op setCanSelectHiddenExtension:NO];
|
||||
[op setExtensionHidden:NO];
|
||||
[op setAllowsOtherFileTypes:YES];
|
||||
[op setTreatsFilePackagesAsDirectories:YES];
|
||||
[op beginSheetModalForWindow:toNSWindow(parent) completionHandler:^(NSInteger ret){
|
||||
if (ret != NSFileHandlingPanelOKButton) {
|
||||
finishOpenFile(NULL, data);
|
||||
return;
|
||||
}
|
||||
// string freed on the Go side
|
||||
finishOpenFile(strdup([[[op URL] path] UTF8String]), data);
|
||||
}];
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
// +build !windows,!darwin
|
||||
|
||||
// 19 august 2014
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// #include "gtk_unix.h"
|
||||
// extern void our_openfile_response_callback(GtkDialog *, gint, gpointer);
|
||||
// /* because cgo doesn't like ... */
|
||||
// static inline GtkWidget *newOpenFileDialog(GtkWindow *parent)
|
||||
// {
|
||||
// return gtk_file_chooser_dialog_new(NULL, /* default title */
|
||||
// parent,
|
||||
// GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||
// GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
|
||||
// GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
|
||||
// NULL);
|
||||
// }
|
||||
import "C"
|
||||
|
||||
func (w *window) openFile(f func(filename string)) {
|
||||
widget := C.newOpenFileDialog(w.window)
|
||||
window := (*C.GtkWindow)(unsafe.Pointer(widget))
|
||||
dialog := (*C.GtkDialog)(unsafe.Pointer(widget))
|
||||
fc := (*C.GtkFileChooser)(unsafe.Pointer(widget))
|
||||
// non-local filenames are relevant mainly to GIO where we can open *anything*, not to Go os.File; see https://twitter.com/braket/status/506142849654870016
|
||||
C.gtk_file_chooser_set_local_only(fc, C.TRUE)
|
||||
C.gtk_file_chooser_set_select_multiple(fc, C.FALSE)
|
||||
C.gtk_file_chooser_set_show_hidden(fc, C.TRUE)
|
||||
C.gtk_window_set_modal(window, C.TRUE)
|
||||
g_signal_connect(
|
||||
C.gpointer(unsafe.Pointer(dialog)),
|
||||
"response",
|
||||
C.GCallback(C.our_openfile_response_callback),
|
||||
C.gpointer(unsafe.Pointer(&f)))
|
||||
C.gtk_widget_show_all(widget)
|
||||
}
|
||||
|
||||
//export our_openfile_response_callback
|
||||
func our_openfile_response_callback(dialog *C.GtkDialog, response C.gint, data C.gpointer) {
|
||||
f := (*func(string))(unsafe.Pointer(data))
|
||||
if response != C.GTK_RESPONSE_ACCEPT {
|
||||
(*f)("")
|
||||
C.gtk_widget_destroy((*C.GtkWidget)(unsafe.Pointer(dialog)))
|
||||
return
|
||||
}
|
||||
filename := C.gtk_file_chooser_get_filename((*C.GtkFileChooser)(unsafe.Pointer(dialog)))
|
||||
if filename == nil {
|
||||
panic("chosen filename NULL in OpenFile()")
|
||||
}
|
||||
realfilename := fromgstr(filename)
|
||||
C.g_free(C.gpointer(unsafe.Pointer(filename)))
|
||||
C.gtk_widget_destroy((*C.GtkWidget)(unsafe.Pointer(dialog)))
|
||||
(*f)(realfilename)
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
// 18 august 2014
|
||||
|
||||
#include "winapi_windows.h"
|
||||
#include "_cgo_export.h"
|
||||
|
||||
// this should be reasonable
|
||||
#define NFILENAME 4096
|
||||
|
||||
struct openFileData {
|
||||
HWND parent;
|
||||
void *f;
|
||||
WCHAR *filenameBuffer;
|
||||
};
|
||||
|
||||
static DWORD WINAPI doOpenFile(LPVOID data)
|
||||
{
|
||||
struct openFileData *o = (struct openFileData *) data;
|
||||
OPENFILENAMEW ofn;
|
||||
DWORD err;
|
||||
|
||||
o->filenameBuffer[0] = L'\0'; // required by GetOpenFileName() to indicate no previous filename
|
||||
ZeroMemory(&ofn, sizeof (OPENFILENAMEW));
|
||||
ofn.lStructSize = sizeof (OPENFILENAMEW);
|
||||
ofn.hwndOwner = o->parent;
|
||||
ofn.hInstance = hInstance;
|
||||
ofn.lpstrFilter = NULL; // no filters
|
||||
ofn.lpstrFile = o->filenameBuffer;
|
||||
ofn.nMaxFile = NFILENAME + 1; // seems to include null terminator according to docs
|
||||
ofn.lpstrInitialDir = NULL; // let system decide
|
||||
ofn.lpstrTitle = NULL; // let system decide
|
||||
// TODO OFN_SHAREAWARE?
|
||||
// better question: TODO keep networking?
|
||||
ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_FORCESHOWHIDDEN | OFN_HIDEREADONLY | OFN_LONGNAMES | OFN_NOCHANGEDIR | OFN_NODEREFERENCELINKS | OFN_NOTESTFILECREATE | OFN_PATHMUSTEXIST;
|
||||
if (GetOpenFileNameW(&ofn) == FALSE) {
|
||||
err = CommDlgExtendedError();
|
||||
if (err != 0) // user cancelled
|
||||
xpaniccomdlg("error running open file dialog", err);
|
||||
free(o->filenameBuffer); // free now so we can set it to NULL without leaking
|
||||
o->filenameBuffer = NULL;
|
||||
}
|
||||
if (PostMessageW(msgwin, msgOpenFileDone, (WPARAM) (o->filenameBuffer), (LPARAM) (o->f)) == 0)
|
||||
xpanic("error posting OpenFile() finished message to message-only window", GetLastError());
|
||||
free(o); // won't free o->f or o->filenameBuffer in above invocation
|
||||
return 0;
|
||||
}
|
||||
|
||||
void openFile(HWND hwnd, void *f)
|
||||
{
|
||||
struct openFileData *o;
|
||||
|
||||
// freed by the thread
|
||||
o = (struct openFileData *) malloc(sizeof (struct openFileData));
|
||||
if (o == NULL)
|
||||
xpanic("memory exhausted allocating data structure in OpenFile()", GetLastError());
|
||||
o->parent = hwnd;
|
||||
o->f = f;
|
||||
// freed on the Go side
|
||||
o->filenameBuffer = (WCHAR *) malloc((NFILENAME + 1) * sizeof (WCHAR));
|
||||
if (o->filenameBuffer == NULL)
|
||||
xpanic("memory exhausted allocating filename buffer in OpenFile()", GetLastError());
|
||||
if (CreateThread(NULL, 0, doOpenFile, (LPVOID) o, 0, NULL) == NULL)
|
||||
xpanic("error creating thread for running OpenFIle()", GetLastError());
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
// 18 august 2014
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// #include "winapi_windows.h"
|
||||
import "C"
|
||||
|
||||
func (w *window) openFile(f func(filename string)) {
|
||||
C.openFile(w.hwnd, unsafe.Pointer(&f))
|
||||
}
|
||||
|
||||
//export finishOpenFile
|
||||
func finishOpenFile(name *C.WCHAR, fp unsafe.Pointer) {
|
||||
f := (*func(string))(fp)
|
||||
if name == nil {
|
||||
(*f)("")
|
||||
return
|
||||
}
|
||||
defer C.free(unsafe.Pointer(name))
|
||||
(*f)(wstrToString(name))
|
||||
}
|
12
prev/doc.go
12
prev/doc.go
|
@ -1,12 +0,0 @@
|
|||
// 14 august 2014
|
||||
|
||||
/*
|
||||
[this is being written]
|
||||
|
||||
notes:
|
||||
- default behavior of event handlers is to do nothing
|
||||
- default behavior of event handlers that return bool is to do nothing but return false
|
||||
- passing nil to an event handler set function restores default behavior
|
||||
- only functions safe for calling outside Do() are Go(), Do(), and Stop()
|
||||
*/
|
||||
package ui
|
|
@ -1,132 +0,0 @@
|
|||
// 30 march 2014
|
||||
|
||||
package ui
|
||||
|
||||
/*
|
||||
Mac OS X uses its own set of hardware key codes that are different from PC keyboard scancodes, but are positional (like PC keyboard scancodes). These are defined in <HIToolbox/Events.h>, a Carbon header. As far as I can tell, there's no way to include this header without either using an absolute path or linking Carbon into the program, so the constant values are used here instead.
|
||||
|
||||
The Cocoa docs do guarantee that -[NSEvent keyCode] results in key codes that are the same as those returned by Carbon; that is, these codes.
|
||||
*/
|
||||
|
||||
// use uintptr to be safe
|
||||
var keycodeKeys = map[uintptr]byte{
|
||||
0x00: 'a',
|
||||
0x01: 's',
|
||||
0x02: 'd',
|
||||
0x03: 'f',
|
||||
0x04: 'h',
|
||||
0x05: 'g',
|
||||
0x06: 'z',
|
||||
0x07: 'x',
|
||||
0x08: 'c',
|
||||
0x09: 'v',
|
||||
0x0B: 'b',
|
||||
0x0C: 'q',
|
||||
0x0D: 'w',
|
||||
0x0E: 'e',
|
||||
0x0F: 'r',
|
||||
0x10: 'y',
|
||||
0x11: 't',
|
||||
0x12: '1',
|
||||
0x13: '2',
|
||||
0x14: '3',
|
||||
0x15: '4',
|
||||
0x16: '6',
|
||||
0x17: '5',
|
||||
0x18: '=',
|
||||
0x19: '9',
|
||||
0x1A: '7',
|
||||
0x1B: '-',
|
||||
0x1C: '8',
|
||||
0x1D: '0',
|
||||
0x1E: ']',
|
||||
0x1F: 'o',
|
||||
0x20: 'u',
|
||||
0x21: '[',
|
||||
0x22: 'i',
|
||||
0x23: 'p',
|
||||
0x25: 'l',
|
||||
0x26: 'j',
|
||||
0x27: '\'',
|
||||
0x28: 'k',
|
||||
0x29: ';',
|
||||
0x2A: '\\',
|
||||
0x2B: ',',
|
||||
0x2C: '/',
|
||||
0x2D: 'n',
|
||||
0x2E: 'm',
|
||||
0x2F: '.',
|
||||
0x32: '`',
|
||||
0x24: '\n',
|
||||
0x30: '\t',
|
||||
0x31: ' ',
|
||||
0x33: '\b',
|
||||
}
|
||||
|
||||
var keycodeExtKeys = map[uintptr]ExtKey{
|
||||
0x41: NDot,
|
||||
0x43: NMultiply,
|
||||
0x45: NAdd,
|
||||
0x4B: NDivide,
|
||||
0x4C: NEnter,
|
||||
0x4E: NSubtract,
|
||||
0x52: N0,
|
||||
0x53: N1,
|
||||
0x54: N2,
|
||||
0x55: N3,
|
||||
0x56: N4,
|
||||
0x57: N5,
|
||||
0x58: N6,
|
||||
0x59: N7,
|
||||
0x5B: N8,
|
||||
0x5C: N9,
|
||||
0x35: Escape,
|
||||
0x60: F5,
|
||||
0x61: F6,
|
||||
0x62: F7,
|
||||
0x63: F3,
|
||||
0x64: F8,
|
||||
0x65: F9,
|
||||
0x67: F11,
|
||||
0x6D: F10,
|
||||
0x6F: F12,
|
||||
0x72: Insert, // listed as the Help key but it's in the same position on an Apple keyboard as the Insert key on a Windows keyboard; thanks to SeanieB from irc.badnik.net and Psy in irc.freenode.net/#macdev for confirming they have the same code
|
||||
0x73: Home,
|
||||
0x74: PageUp,
|
||||
0x75: Delete,
|
||||
0x76: F4,
|
||||
0x77: End,
|
||||
0x78: F2,
|
||||
0x79: PageDown,
|
||||
0x7A: F1,
|
||||
0x7B: Left,
|
||||
0x7C: Right,
|
||||
0x7D: Down,
|
||||
0x7E: Up,
|
||||
}
|
||||
|
||||
var keycodeModifiers = map[uintptr]Modifiers{
|
||||
0x37: Super, // left command
|
||||
0x38: Shift, // left shift
|
||||
0x3A: Alt, // left option
|
||||
0x3B: Ctrl, // left control
|
||||
0x3C: Shift, // right shift
|
||||
0x3D: Alt, // right alt
|
||||
0x3E: Ctrl, // right control
|
||||
|
||||
// the following is not in Events.h for some reason
|
||||
// thanks to Nicole and jedivulcan from irc.badnik.net
|
||||
0x36: Super, // right command
|
||||
}
|
||||
|
||||
func fromKeycode(keycode uintptr) (ke KeyEvent, ok bool) {
|
||||
if key, ok := keycodeKeys[keycode]; ok {
|
||||
ke.Key = key
|
||||
return ke, true
|
||||
}
|
||||
if extkey, ok := keycodeExtKeys[keycode]; ok {
|
||||
ke.ExtKey = extkey
|
||||
return ke, true
|
||||
}
|
||||
return ke, false
|
||||
}
|
|
@ -1,152 +0,0 @@
|
|||
// +build !darwin
|
||||
// Mac OS X uses its own set of position-independent key codes
|
||||
|
||||
// 29 march 2014
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"image"
|
||||
)
|
||||
|
||||
/*
|
||||
Windows and GTK+ have a limit of 2 and 3 clicks, respectively, natively supported. Fortunately, we can simulate the double/triple-click behavior to build higher-order clicks. We can use the same algorithm Windows uses on both:
|
||||
http://blogs.msdn.com/b/oldnewthing/archive/2004/10/18/243925.aspx
|
||||
For GTK+, we pull the double-click time and double-click distance, which work the same as the equivalents on Windows (so the distance is in all directions), from the GtkSettings system.
|
||||
|
||||
On GTK+ this will also allow us to discard the GDK_BUTTON_2PRESS and GDK_BUTTON_3PRESS events, so the button press stream will be just like on other platforms.
|
||||
|
||||
Thanks to mclasen, garnacho_, halfline, and tristan in irc.gimp.net/#gtk+.
|
||||
*/
|
||||
|
||||
// the zero value is a reset clickCounter ready for use
|
||||
// it doesn't matter that all the non-count fields are zero: the first click will fail the curButton test straightaway, so it'll return 1 and set the rest of the structure accordingly
|
||||
type clickCounter struct {
|
||||
curButton uint
|
||||
rect image.Rectangle
|
||||
prevTime uintptr
|
||||
count uint
|
||||
}
|
||||
|
||||
// x, y, xdist, ydist, and c.rect must have the same units
|
||||
// so must time, maxTime, and c.prevTime
|
||||
func (c *clickCounter) click(button uint, x int, y int, time uintptr, maxTime uintptr, xdist int, ydist int) uint {
|
||||
if button != c.curButton { // different button; start over
|
||||
c.count = 0
|
||||
}
|
||||
if !image.Pt(x, y).In(c.rect) { // not in the allowed region for a double-click; don't count
|
||||
c.count = 0
|
||||
}
|
||||
if (time - c.prevTime) > maxTime { // too slow; don't count
|
||||
// note the above expression; time > (c.prevTime + maxTime) can overflow!
|
||||
c.count = 0
|
||||
}
|
||||
c.count++ // if either of the above ifs happened, this will make the click count 1; otherwise it will make the click count 2, 3, 4, 5, ...
|
||||
|
||||
// now we need to update the internal structures for the next test
|
||||
c.curButton = button
|
||||
c.prevTime = time
|
||||
c.rect = image.Rect(x-xdist, y-ydist,
|
||||
x+xdist, y+ydist)
|
||||
|
||||
return c.count
|
||||
}
|
||||
|
||||
// call this when losing focus, etc.
|
||||
func (c *clickCounter) reset() {
|
||||
c.count = 0
|
||||
}
|
||||
|
||||
/*
|
||||
For position independence across international keyboard layouts, typewriter keys are read using scancodes (which are always set 1).
|
||||
Windows provides the scancodes directly in the LPARAM.
|
||||
GTK+ provides the scancodes directly from the underlying window system via GdkEventKey.hardware_keycode.
|
||||
On X11, this is scancode + 8 (because X11 keyboard codes have a range of [8,255]).
|
||||
Wayland is guaranteed to give the same result (thanks ebassi in irc.gimp.net/#gtk+).
|
||||
On Linux, where evdev is used instead of polling scancodes directly from the keyboard, evdev's typewriter section key code constants are the same as scancodes anyway, so the rules above apply.
|
||||
Typewriter section scancodes are the same across international keyboards with some exceptions that have been accounted for (see KeyEvent's documentation); see http://www.quadibloc.com/comp/scan.htm for details.
|
||||
Non-typewriter keys can be handled safely using constants provided by the respective backend API.
|
||||
|
||||
Because GTK+ keysyms may or may not obey Num Lock, we also handle the 0-9 and . keys on the numeric keypad with scancodes (they match too).
|
||||
*/
|
||||
|
||||
// use uintptr to be safe; the size of the scancode/hardware key code field on each platform is different
|
||||
var scancodeKeys = map[uintptr]byte{
|
||||
0x02: '1',
|
||||
0x03: '2',
|
||||
0x04: '3',
|
||||
0x05: '4',
|
||||
0x06: '5',
|
||||
0x07: '6',
|
||||
0x08: '7',
|
||||
0x09: '8',
|
||||
0x0A: '9',
|
||||
0x0B: '0',
|
||||
0x0C: '-',
|
||||
0x0D: '=',
|
||||
0x0E: '\b',
|
||||
0x0F: '\t',
|
||||
0x10: 'q',
|
||||
0x11: 'w',
|
||||
0x12: 'e',
|
||||
0x13: 'r',
|
||||
0x14: 't',
|
||||
0x15: 'y',
|
||||
0x16: 'u',
|
||||
0x17: 'i',
|
||||
0x18: 'o',
|
||||
0x19: 'p',
|
||||
0x1A: '[',
|
||||
0x1B: ']',
|
||||
0x1C: '\n',
|
||||
0x1E: 'a',
|
||||
0x1F: 's',
|
||||
0x20: 'd',
|
||||
0x21: 'f',
|
||||
0x22: 'g',
|
||||
0x23: 'h',
|
||||
0x24: 'j',
|
||||
0x25: 'k',
|
||||
0x26: 'l',
|
||||
0x27: ';',
|
||||
0x28: '\'',
|
||||
0x29: '`',
|
||||
0x2B: '\\',
|
||||
0x2C: 'z',
|
||||
0x2D: 'x',
|
||||
0x2E: 'c',
|
||||
0x2F: 'v',
|
||||
0x30: 'b',
|
||||
0x31: 'n',
|
||||
0x32: 'm',
|
||||
0x33: ',',
|
||||
0x34: '.',
|
||||
0x35: '/',
|
||||
0x39: ' ',
|
||||
}
|
||||
|
||||
var scancodeExtKeys = map[uintptr]ExtKey{
|
||||
0x47: N7,
|
||||
0x48: N8,
|
||||
0x49: N9,
|
||||
0x4B: N4,
|
||||
0x4C: N5,
|
||||
0x4D: N6,
|
||||
0x4F: N1,
|
||||
0x50: N2,
|
||||
0x51: N3,
|
||||
0x52: N0,
|
||||
0x53: NDot,
|
||||
}
|
||||
|
||||
func fromScancode(scancode uintptr) (ke KeyEvent, ok bool) {
|
||||
if key, ok := scancodeKeys[scancode]; ok {
|
||||
ke.Key = key
|
||||
return ke, true
|
||||
}
|
||||
if extkey, ok := scancodeExtKeys[scancode]; ok {
|
||||
ke.ExtKey = extkey
|
||||
return ke, true
|
||||
}
|
||||
return ke, false
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/andlabs/ui"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// This runs the code that displays our GUI.
|
||||
// All code that interfaces with package ui (except event handlers) must be run from within a ui.Do() call.
|
||||
go ui.Do(gui)
|
||||
|
||||
err := ui.Go()
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
}
|
||||
|
||||
func gui() {
|
||||
// All windows must have a control inside.
|
||||
// ui.Space() creates a control that is just a blank space for us to use.
|
||||
newControl := ui.Space()
|
||||
|
||||
// Then we create a window.
|
||||
w := ui.NewWindow("Window", 280, 350, newControl)
|
||||
|
||||
// We tell package ui to destroy our window and shut down cleanly when the user closes the window by clicking the X button in the titlebar.
|
||||
w.OnClosing(func() bool {
|
||||
// This informs package ui to shut down cleanly when it can.
|
||||
ui.Stop()
|
||||
// And this informs package ui that we want to hide AND destroy the window.
|
||||
return true
|
||||
})
|
||||
|
||||
// And finally, we need to show the window.
|
||||
w.Show()
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
This is not a single-file example; both main.go and icons.go need to be built together. icons.go is a separate file because it contains the binary data for the icons. At some point in the future the file will not contain any package ui-related code (if it doesn't already), and will only be responsible for parsing the images and producing the structure for the embedded Table (if even).
|
|
@ -1,684 +0,0 @@
|
|||
// 16 august 2014
|
||||
// updated for widget gallery example 30 august 2014
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"bytes"
|
||||
"image"
|
||||
"image/draw"
|
||||
_ "image/png"
|
||||
)
|
||||
|
||||
type icon struct {
|
||||
Name string
|
||||
Icon *image.RGBA
|
||||
Bool bool
|
||||
}
|
||||
|
||||
var firstimg *image.RGBA
|
||||
|
||||
func readIcons() []icon {
|
||||
out := make([]icon, len(icons))
|
||||
for i := range icons {
|
||||
r := bytes.NewReader(icons[i].data)
|
||||
png, _, err := image.Decode(r)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("error loading image %d (%q): %v", i, icons[i].name, err))
|
||||
}
|
||||
img := image.NewRGBA(png.Bounds())
|
||||
draw.Draw(img, img.Rect, png, image.ZP, draw.Src)
|
||||
if firstimg == nil {
|
||||
firstimg = img
|
||||
}
|
||||
out[i].Icon = img
|
||||
out[i].Name = icons[i].name
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func tileImage(times int) *image.RGBA {
|
||||
dx := firstimg.Rect.Dx()
|
||||
dy := firstimg.Rect.Dy()
|
||||
res := image.NewRGBA(image.Rect(0, 0, times * dx, times * dy))
|
||||
r := image.Rect(0, 0, dx, dy)
|
||||
for y := 0; y < times; y++ {
|
||||
rr := r.Add(image.Pt(0, y * dy))
|
||||
for x := 0; x < times; x++ {
|
||||
draw.Draw(res, rr, firstimg, image.ZP, draw.Src)
|
||||
rr = rr.Add(image.Pt(dx, 0))
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
var icons = []struct {
|
||||
data []byte
|
||||
name string
|
||||
}{
|
||||
{ __16x16_categories_applications_accessories_png, "16x16/categories/applications-accessories.png", },
|
||||
{ __16x16_places_folder_png, "16x16/places/folder.png", },
|
||||
{ __16x16_mimetypes_x_office_spreadsheet_png, "16x16/mimetypes/x-office-spreadsheet.png", },
|
||||
{ __32x32_categories_applications_accessories_png, "32x32/categories/applications-accessories.png", },
|
||||
{ __32x32_places_folder_png, "32x32/places/folder.png", },
|
||||
{ __32x32_mimetypes_x_office_spreadsheet_png, "32x32/mimetypes/x-office-spreadsheet.png", },
|
||||
}
|
||||
|
||||
// from http://tango.freedesktop.org/releases/tango-icon-theme-0.8.90.tar.gz, which is public domain
|
||||
|
||||
var __16x16_categories_applications_accessories_png = []byte{
|
||||
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
|
||||
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10,
|
||||
0x08, 0x06, 0x00, 0x00, 0x00, 0x1f, 0xf3, 0xff, 0x61, 0x00, 0x00, 0x00,
|
||||
0x06, 0x62, 0x4b, 0x47, 0x44, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0xa0,
|
||||
0xbd, 0xa7, 0x93, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00,
|
||||
0x00, 0x0b, 0x13, 0x00, 0x00, 0x0b, 0x13, 0x01, 0x00, 0x9a, 0x9c, 0x18,
|
||||
0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, 0x45, 0x07, 0xd5, 0x0b, 0x1c,
|
||||
0x17, 0x38, 0x22, 0x38, 0xae, 0xb6, 0xe0, 0x00, 0x00, 0x03, 0x36, 0x49,
|
||||
0x44, 0x41, 0x54, 0x38, 0xcb, 0x5d, 0x93, 0x4b, 0x4c, 0x5c, 0x75, 0x14,
|
||||
0xc6, 0xbf, 0xff, 0x63, 0xe6, 0xde, 0x79, 0x80, 0x73, 0x41, 0x60, 0x66,
|
||||
0x80, 0xa2, 0x13, 0x04, 0x9a, 0x49, 0x53, 0x13, 0x17, 0xc5, 0x57, 0xdd,
|
||||
0x18, 0x4a, 0xd2, 0x8d, 0xa9, 0x55, 0x1b, 0x1f, 0xab, 0x36, 0xc1, 0x54,
|
||||
0x6b, 0xd5, 0xd2, 0x66, 0x48, 0x43, 0x1a, 0x62, 0xa2, 0x0d, 0x94, 0x58,
|
||||
0x9b, 0x34, 0xac, 0x4c, 0x88, 0x8f, 0x85, 0x1b, 0x5c, 0x11, 0x12, 0xbb,
|
||||
0xa8, 0xa6, 0x34, 0x5d, 0x18, 0xb5, 0x75, 0x0c, 0x1d, 0xa8, 0x34, 0xf2,
|
||||
0x98, 0x29, 0x65, 0x18, 0xc6, 0x0e, 0x33, 0x70, 0xe7, 0xce, 0xfd, 0xff,
|
||||
0x8f, 0x1b, 0xc0, 0x49, 0xbf, 0xd5, 0x39, 0xdf, 0xc9, 0xf7, 0xcb, 0x49,
|
||||
0x4e, 0x0e, 0xc3, 0xb6, 0x46, 0x46, 0x2f, 0xf6, 0x02, 0xf8, 0xce, 0x34,
|
||||
0xcd, 0x87, 0xa7, 0x3e, 0xf8, 0x38, 0x8e, 0x2a, 0x8d, 0x8c, 0x5e, 0x1c,
|
||||
0x93, 0x52, 0xbe, 0xf5, 0xc9, 0xe9, 0xfe, 0xba, 0x6a, 0x7f, 0x7a, 0x1c,
|
||||
0xef, 0xf1, 0x9d, 0x46, 0x08, 0x31, 0xf1, 0xe1, 0xc9, 0xd3, 0x96, 0x6d,
|
||||
0xdb, 0x8b, 0x57, 0xc7, 0xae, 0x0c, 0xee, 0xf8, 0x97, 0xbf, 0x1a, 0xcd,
|
||||
0x1c, 0xea, 0xe9, 0xed, 0x03, 0x70, 0xe7, 0xb1, 0xf0, 0xcb, 0x00, 0xbe,
|
||||
0xde, 0x05, 0x28, 0xa5, 0xcc, 0x7b, 0xf3, 0xb3, 0xfc, 0xc4, 0xf1, 0xbe,
|
||||
0x1e, 0xa5, 0xd4, 0x7e, 0x00, 0x18, 0xbe, 0xf4, 0xc5, 0x2b, 0xd1, 0xe6,
|
||||
0x68, 0xa4, 0x58, 0xda, 0x60, 0x5a, 0xeb, 0xee, 0xaa, 0x70, 0x3b, 0xe3,
|
||||
0x62, 0x92, 0x31, 0xe1, 0xd9, 0x05, 0x70, 0xce, 0x4b, 0x0b, 0x0b, 0xff,
|
||||
0xc0, 0x34, 0x0c, 0x56, 0x2e, 0x97, 0xe3, 0x57, 0xc7, 0xae, 0x0c, 0x4a,
|
||||
0x29, 0xaf, 0xc5, 0x62, 0x31, 0xac, 0x3c, 0x58, 0xd1, 0x5a, 0xeb, 0xf9,
|
||||
0xed, 0xb0, 0xc5, 0x85, 0xb8, 0xde, 0xd1, 0xfd, 0x69, 0x50, 0x78, 0x6a,
|
||||
0x54, 0xf5, 0x06, 0x3d, 0xa9, 0x54, 0x0a, 0xcb, 0xe9, 0x25, 0x76, 0xe2,
|
||||
0x78, 0x5f, 0xa7, 0x6d, 0xdb, 0xef, 0x77, 0x75, 0x75, 0x79, 0x18, 0x38,
|
||||
0xe6, 0xef, 0xcf, 0x03, 0xc0, 0x0f, 0xd3, 0xe3, 0xf0, 0x70, 0x21, 0xa6,
|
||||
0x5a, 0xe3, 0x47, 0x9b, 0xea, 0x5a, 0x0e, 0x31, 0x00, 0xd8, 0x05, 0x9c,
|
||||
0xeb, 0x1f, 0xb8, 0xc5, 0x39, 0x5f, 0x9c, 0x9b, 0x9b, 0x85, 0x69, 0x98,
|
||||
0xcc, 0x0a, 0x59, 0x51, 0xd2, 0x40, 0x76, 0x6d, 0x55, 0x03, 0x98, 0x3c,
|
||||
0x7b, 0x26, 0xf1, 0x19, 0x17, 0x62, 0xbc, 0xbe, 0xf9, 0xc0, 0xfe, 0x68,
|
||||
0xc7, 0x9b, 0x1e, 0x50, 0x09, 0x44, 0xf4, 0x3f, 0x00, 0x00, 0xb4, 0xd6,
|
||||
0x47, 0x66, 0xee, 0xce, 0xd0, 0xea, 0xea, 0x0a, 0x5c, 0xe5, 0x62, 0x63,
|
||||
0xa3, 0x80, 0x64, 0x32, 0xe9, 0x00, 0x18, 0xba, 0xf9, 0x0d, 0x3f, 0xef,
|
||||
0x0f, 0xb5, 0xbd, 0x16, 0x7b, 0xee, 0x94, 0xe9, 0x16, 0x27, 0xa1, 0xdd,
|
||||
0x0d, 0x00, 0x8f, 0x01, 0xce, 0x9e, 0x49, 0xfc, 0xc6, 0x39, 0xff, 0x76,
|
||||
0x76, 0x2e, 0xa5, 0x43, 0xa1, 0x10, 0x32, 0x0f, 0x32, 0x44, 0x44, 0xe3,
|
||||
0xcf, 0xd7, 0x27, 0x9e, 0xf2, 0x78, 0x6b, 0xcf, 0x77, 0x76, 0x0f, 0xf8,
|
||||
0xdd, 0xd2, 0x34, 0x2a, 0x5b, 0x8b, 0xd0, 0xca, 0x06, 0x88, 0xc0, 0xaa,
|
||||
0x01, 0x13, 0xe1, 0xf0, 0xa8, 0x4b, 0x74, 0xd2, 0xcd, 0xe7, 0x4d, 0xb8,
|
||||
0x2e, 0x9e, 0x0e, 0x37, 0x21, 0x4d, 0x7a, 0x33, 0x78, 0xd0, 0xf6, 0xbe,
|
||||
0x70, 0x69, 0x40, 0x4a, 0xbe, 0x86, 0x62, 0xee, 0x06, 0x0c, 0xd3, 0x84,
|
||||
0x37, 0xf4, 0x06, 0x6e, 0xff, 0x94, 0x50, 0x02, 0x00, 0x26, 0x22, 0x91,
|
||||
0xc1, 0x23, 0x95, 0xca, 0xf5, 0x4e, 0xcb, 0xea, 0x0e, 0x09, 0x21, 0x95,
|
||||
0x72, 0xa9, 0xec, 0x3a, 0xa8, 0x0b, 0xd4, 0xb0, 0x46, 0xd3, 0xf0, 0x18,
|
||||
0xcb, 0x26, 0xff, 0x75, 0x78, 0x0a, 0x9e, 0xc6, 0x1c, 0xb2, 0x5b, 0x77,
|
||||
0x11, 0x6a, 0x6c, 0x02, 0xf7, 0xb6, 0x63, 0xf5, 0xfe, 0xcf, 0xc4, 0x7f,
|
||||
0x8c, 0x46, 0xdf, 0x75, 0x72, 0xb9, 0xa1, 0xce, 0x70, 0x58, 0xcf, 0x2c,
|
||||
0x2d, 0x5d, 0x2b, 0x6c, 0x6d, 0x3a, 0xf9, 0xc2, 0x06, 0xf7, 0x1d, 0xd6,
|
||||
0x2d, 0x42, 0x32, 0xfc, 0x6b, 0x97, 0x50, 0x88, 0x99, 0x68, 0x6f, 0xa8,
|
||||
0x47, 0x32, 0x31, 0x87, 0xfc, 0x94, 0x44, 0xc5, 0x71, 0xa0, 0xdd, 0x32,
|
||||
0x08, 0x04, 0xee, 0x3a, 0xce, 0xf0, 0xbe, 0xd6, 0x56, 0x4a, 0x2d, 0x2f,
|
||||
0x5f, 0x38, 0xa6, 0x54, 0x6f, 0xad, 0xe9, 0x53, 0xc2, 0xe7, 0x53, 0x8d,
|
||||
0xaf, 0xf3, 0xef, 0xff, 0xce, 0xac, 0x20, 0x18, 0x0c, 0x20, 0xfe, 0x65,
|
||||
0x03, 0xf2, 0x2f, 0xad, 0xb9, 0x7b, 0xa3, 0x11, 0x2a, 0xde, 0xf0, 0x42,
|
||||
0x55, 0x2a, 0x70, 0xdd, 0x4d, 0x80, 0x08, 0x1c, 0xe5, 0x72, 0xbd, 0xe3,
|
||||
0x38, 0xea, 0x98, 0x52, 0x9f, 0x4f, 0x06, 0x02, 0x97, 0x3d, 0x52, 0x9a,
|
||||
0xc2, 0x42, 0xc1, 0x6f, 0xb5, 0x1f, 0x08, 0x3d, 0xbb, 0x07, 0x7e, 0x92,
|
||||
0xb8, 0x37, 0xf4, 0x17, 0xcc, 0x57, 0xb7, 0x24, 0x18, 0x31, 0x5d, 0xd0,
|
||||
0x70, 0x1d, 0x07, 0xaa, 0xb2, 0x09, 0x80, 0x20, 0xe1, 0xf5, 0x66, 0x82,
|
||||
0x7e, 0xff, 0x9e, 0xdb, 0x6d, 0x6d, 0x65, 0xad, 0xb5, 0x9c, 0x59, 0x4b,
|
||||
0x17, 0x9b, 0x2f, 0x3c, 0xe1, 0x6f, 0xd9, 0x7b, 0xd4, 0x08, 0x9c, 0xfb,
|
||||
0x05, 0xc9, 0x8f, 0xfe, 0x40, 0xc7, 0x9d, 0x30, 0x2a, 0xbf, 0xd7, 0x40,
|
||||
0x6b, 0x82, 0xac, 0x95, 0x50, 0xda, 0xdd, 0x06, 0x00, 0xe2, 0x6d, 0xcb,
|
||||
0xca, 0xa5, 0xd7, 0xd7, 0x5f, 0x5c, 0x77, 0x1c, 0x99, 0x67, 0xc4, 0xc2,
|
||||
0xfd, 0x5a, 0x3c, 0x73, 0xf8, 0x1d, 0x83, 0x73, 0x0f, 0x8c, 0x86, 0x27,
|
||||
0x61, 0x44, 0xeb, 0x90, 0x5e, 0xc8, 0x22, 0x9b, 0x2d, 0xe0, 0x91, 0x57,
|
||||
0x21, 0x3e, 0x72, 0x10, 0xbe, 0x48, 0x0c, 0x44, 0x0c, 0x8f, 0x1e, 0xfe,
|
||||
0x49, 0xac, 0xea, 0x41, 0xf6, 0x01, 0xb8, 0xc5, 0x98, 0x34, 0x85, 0xf4,
|
||||
0x3b, 0x00, 0x01, 0x44, 0xdb, 0x53, 0xda, 0x3d, 0x35, 0xed, 0xd4, 0x44,
|
||||
0x50, 0xca, 0xf6, 0xfd, 0x07, 0x11, 0xa6, 0x73, 0x42, 0xc7, 0xee, 0xef,
|
||||
0xaa, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60,
|
||||
0x82,
|
||||
}
|
||||
|
||||
var __16x16_places_folder_png = []byte{
|
||||
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
|
||||
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10,
|
||||
0x08, 0x06, 0x00, 0x00, 0x00, 0x1f, 0xf3, 0xff, 0x61, 0x00, 0x00, 0x00,
|
||||
0x04, 0x73, 0x42, 0x49, 0x54, 0x08, 0x08, 0x08, 0x08, 0x7c, 0x08, 0x64,
|
||||
0x88, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0d,
|
||||
0xd7, 0x00, 0x00, 0x0d, 0xd7, 0x01, 0x42, 0x28, 0x9b, 0x78, 0x00, 0x00,
|
||||
0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61,
|
||||
0x72, 0x65, 0x00, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x6e, 0x6b, 0x73, 0x63,
|
||||
0x61, 0x70, 0x65, 0x2e, 0x6f, 0x72, 0x67, 0x9b, 0xee, 0x3c, 0x1a, 0x00,
|
||||
0x00, 0x01, 0xc2, 0x49, 0x44, 0x41, 0x54, 0x38, 0x8d, 0xa5, 0x90, 0x31,
|
||||
0x6b, 0x93, 0x51, 0x14, 0x86, 0x9f, 0x73, 0xef, 0x57, 0x2c, 0x29, 0xe2,
|
||||
0xe0, 0x10, 0xba, 0x14, 0x1c, 0x9c, 0x32, 0x76, 0xeb, 0x2e, 0x99, 0x1c,
|
||||
0x82, 0xd0, 0xd5, 0x5f, 0xa0, 0xbb, 0xd0, 0x3f, 0xe0, 0xe2, 0x56, 0x57,
|
||||
0xff, 0x87, 0x28, 0xc4, 0xb9, 0x60, 0xc4, 0x59, 0x29, 0x8a, 0x24, 0x92,
|
||||
0x50, 0x69, 0xbf, 0x26, 0x25, 0xdf, 0xbd, 0xe7, 0x9e, 0xe3, 0x90, 0x26,
|
||||
0x36, 0x58, 0x5a, 0xc1, 0x77, 0x39, 0xdc, 0x7b, 0xcf, 0x79, 0xee, 0x79,
|
||||
0x5f, 0xe9, 0x76, 0xbb, 0xfc, 0x8f, 0xaa, 0xdd, 0xdd, 0xdd, 0x37, 0xee,
|
||||
0xfe, 0xf8, 0x9a, 0xb7, 0x0b, 0x77, 0x7f, 0x32, 0x18, 0x0c, 0x8e, 0x6e,
|
||||
0x04, 0x98, 0x59, 0xaf, 0xd7, 0xeb, 0xdd, 0x33, 0x33, 0xcc, 0x0c, 0x77,
|
||||
0x07, 0xe0, 0xf4, 0xf4, 0xf4, 0x7e, 0xbf, 0xdf, 0x3f, 0xec, 0x74, 0x3a,
|
||||
0xcf, 0x97, 0xcd, 0x39, 0x67, 0x42, 0x08, 0x65, 0x38, 0x1c, 0x7e, 0xac,
|
||||
0xeb, 0xba, 0x00, 0x54, 0x39, 0x67, 0x57, 0x55, 0x26, 0x93, 0x09, 0xa5,
|
||||
0x94, 0x15, 0x24, 0x84, 0xc0, 0xf6, 0xf6, 0x76, 0x07, 0x78, 0xbb, 0x84,
|
||||
0x02, 0x4c, 0xa7, 0xd3, 0xaa, 0x94, 0xf2, 0xaa, 0xae, 0xeb, 0x83, 0x2b,
|
||||
0x80, 0x8c, 0xaa, 0x62, 0x66, 0x6b, 0x90, 0x76, 0xbb, 0xdd, 0xda, 0xdc,
|
||||
0xdc, 0xa4, 0xd5, 0x6a, 0x11, 0x63, 0x24, 0x84, 0xc0, 0x68, 0x34, 0x62,
|
||||
0x3c, 0x1e, 0x3f, 0x5c, 0x59, 0x50, 0x55, 0x53, 0x2d, 0x1c, 0x7e, 0x48,
|
||||
0xcc, 0x9a, 0x3f, 0x3f, 0x81, 0x00, 0xe0, 0xcc, 0x81, 0xf9, 0xea, 0xd6,
|
||||
0x3d, 0x00, 0x7b, 0xfb, 0xec, 0xec, 0xed, 0xc7, 0x18, 0xbe, 0x55, 0x39,
|
||||
0x67, 0x4f, 0x29, 0x31, 0x6d, 0x9c, 0x97, 0xcf, 0x1e, 0x21, 0x72, 0xd9,
|
||||
0x78, 0x35, 0x29, 0x5f, 0x3b, 0x01, 0x90, 0xd4, 0x39, 0x78, 0xfd, 0x7e,
|
||||
0xa7, 0x4a, 0x29, 0x99, 0xaa, 0x02, 0x4e, 0x0c, 0xf0, 0xe9, 0xf8, 0xec,
|
||||
0xea, 0x02, 0xcb, 0xb2, 0x9e, 0x7c, 0x14, 0x1e, 0xb4, 0xb7, 0x40, 0xdc,
|
||||
0x2b, 0x55, 0x25, 0xe7, 0x8c, 0x88, 0x10, 0x83, 0x10, 0xe3, 0x75, 0x23,
|
||||
0xeb, 0xca, 0x6a, 0xb8, 0x39, 0x82, 0x78, 0x95, 0x73, 0xb6, 0x9c, 0x33,
|
||||
0xb8, 0x13, 0x44, 0xd8, 0xb8, 0x01, 0xa0, 0xc5, 0x69, 0xb2, 0x51, 0xcc,
|
||||
0x09, 0x41, 0x00, 0x16, 0x00, 0xd5, 0xc5, 0x06, 0xf3, 0x54, 0x08, 0x80,
|
||||
0xf9, 0xa2, 0xb9, 0xb8, 0x63, 0xe6, 0x98, 0x3b, 0x59, 0x17, 0x75, 0x69,
|
||||
0xa1, 0x49, 0xb6, 0x02, 0xb8, 0x6a, 0x41, 0xa4, 0x62, 0x52, 0x37, 0x0c,
|
||||
0x7f, 0xcd, 0x69, 0xd4, 0x6e, 0xb4, 0x50, 0x45, 0x61, 0x72, 0x36, 0x5f,
|
||||
0x00, 0x54, 0xd5, 0x52, 0x4a, 0xe0, 0x91, 0x2f, 0xa3, 0x73, 0x4e, 0xa6,
|
||||
0x0d, 0x7f, 0x67, 0xbe, 0xae, 0x18, 0x84, 0x9f, 0x67, 0x97, 0x16, 0x44,
|
||||
0xc4, 0xcc, 0x0c, 0x11, 0xa1, 0xc9, 0x46, 0xeb, 0x4e, 0x75, 0x6b, 0x88,
|
||||
0x22, 0xc2, 0x45, 0x52, 0x80, 0x52, 0xb9, 0x3b, 0xc7, 0x27, 0x86, 0x08,
|
||||
0x7c, 0xfe, 0x3a, 0xba, 0x75, 0x78, 0xa9, 0xd9, 0x6c, 0x8e, 0xc0, 0x51,
|
||||
0x65, 0x1b, 0x5b, 0xfd, 0x77, 0xc7, 0x77, 0x9f, 0x82, 0xf3, 0xe3, 0xfb,
|
||||
0xf8, 0x9f, 0x01, 0x22, 0x8c, 0x45, 0xc2, 0x8b, 0xdf, 0x47, 0x16, 0x21,
|
||||
0x9e, 0x1c, 0x63, 0xa7, 0x31, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e,
|
||||
0x44, 0xae, 0x42, 0x60, 0x82,
|
||||
}
|
||||
|
||||
var __16x16_mimetypes_x_office_spreadsheet_png = []byte{
|
||||
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
|
||||
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10,
|
||||
0x08, 0x06, 0x00, 0x00, 0x00, 0x1f, 0xf3, 0xff, 0x61, 0x00, 0x00, 0x00,
|
||||
0x06, 0x62, 0x4b, 0x47, 0x44, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0xa0,
|
||||
0xbd, 0xa7, 0x93, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00,
|
||||
0x00, 0x0b, 0x13, 0x00, 0x00, 0x0b, 0x13, 0x01, 0x00, 0x9a, 0x9c, 0x18,
|
||||
0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, 0x45, 0x07, 0xd5, 0x04, 0x16,
|
||||
0x14, 0x0d, 0x09, 0xd9, 0x88, 0x44, 0xfa, 0x00, 0x00, 0x02, 0x4d, 0x49,
|
||||
0x44, 0x41, 0x54, 0x38, 0xcb, 0x95, 0x92, 0xdf, 0x4b, 0x53, 0x61, 0x18,
|
||||
0xc7, 0x3f, 0x67, 0x9b, 0x6e, 0x7a, 0xd6, 0xfc, 0x55, 0xe0, 0x59, 0x8a,
|
||||
0x95, 0x71, 0x08, 0x52, 0x21, 0x91, 0x2c, 0x8a, 0x0a, 0x94, 0x11, 0x14,
|
||||
0x78, 0x21, 0x99, 0x14, 0x81, 0xdd, 0x48, 0x7f, 0x45, 0x63, 0x5d, 0x75,
|
||||
0x1b, 0x34, 0xd0, 0x9b, 0x52, 0x83, 0x0a, 0xad, 0x0b, 0x43, 0x72, 0xd0,
|
||||
0x85, 0x14, 0x14, 0x68, 0x77, 0x39, 0xcd, 0x6c, 0x94, 0x0c, 0xf4, 0x48,
|
||||
0x4d, 0x5b, 0x5b, 0x4b, 0xcf, 0x39, 0x3b, 0xef, 0xe9, 0x62, 0x3f, 0xec,
|
||||
0xd7, 0x2e, 0x7c, 0xae, 0xbe, 0x2f, 0xef, 0xf3, 0xfd, 0xbc, 0xdf, 0xf7,
|
||||
0x7d, 0x5e, 0x69, 0x78, 0x78, 0xf8, 0xc9, 0xfa, 0xfa, 0x7a, 0x2f, 0xbb,
|
||||
0xab, 0xcb, 0xc1, 0x60, 0x70, 0x1c, 0x80, 0x50, 0x28, 0x64, 0xef, 0xb6,
|
||||
0x42, 0xa1, 0x90, 0x5d, 0x20, 0xb9, 0x0a, 0x22, 0x12, 0x89, 0xe4, 0x95,
|
||||
0x84, 0xa6, 0x69, 0xf8, 0xfd, 0x0a, 0x00, 0x9a, 0xa6, 0xa1, 0x28, 0xfe,
|
||||
0xbc, 0x5e, 0x63, 0x60, 0x60, 0xe0, 0x8f, 0x28, 0x45, 0x80, 0xa6, 0x69,
|
||||
0x48, 0x52, 0x0e, 0x20, 0x49, 0xb9, 0xf5, 0xce, 0xde, 0x5a, 0xc9, 0xbb,
|
||||
0x14, 0x01, 0x8a, 0x5f, 0xc1, 0x2b, 0x7b, 0x01, 0x88, 0xc5, 0x62, 0xf4,
|
||||
0xf4, 0xf4, 0x00, 0x10, 0x8d, 0x46, 0x69, 0x69, 0x6d, 0x41, 0xb2, 0x25,
|
||||
0xe6, 0xa3, 0xf3, 0xa5, 0x01, 0x86, 0x6e, 0x90, 0x21, 0x83, 0x84, 0x04,
|
||||
0x40, 0x2a, 0x95, 0x22, 0x2f, 0x49, 0x7f, 0x4f, 0x91, 0x8f, 0x57, 0x1a,
|
||||
0xe0, 0x71, 0xbb, 0x91, 0xbd, 0x5e, 0x0a, 0xaf, 0xe3, 0xf3, 0x55, 0x15,
|
||||
0xfc, 0xf8, 0xaa, 0x76, 0x74, 0x49, 0xc0, 0xb6, 0xae, 0xe7, 0x4f, 0xcc,
|
||||
0xb5, 0x7e, 0x8c, 0x3c, 0x67, 0xf5, 0xee, 0x1d, 0xb6, 0x16, 0x16, 0x89,
|
||||
0xa7, 0x33, 0xb9, 0x26, 0xb9, 0x82, 0xc9, 0xb6, 0x56, 0xbc, 0x6d, 0xed,
|
||||
0xff, 0x49, 0xe0, 0xf1, 0x20, 0xcb, 0x32, 0x00, 0xf6, 0xe8, 0x3d, 0x3e,
|
||||
0xcd, 0xcd, 0xd1, 0x74, 0xe5, 0x22, 0x65, 0x9d, 0x2a, 0xc2, 0x21, 0x91,
|
||||
0x15, 0x02, 0xc3, 0x14, 0x48, 0x0e, 0x0f, 0x4d, 0xcf, 0xa6, 0x78, 0x74,
|
||||
0xbc, 0xe3, 0x65, 0xff, 0xec, 0xdb, 0xfe, 0x22, 0x40, 0xd7, 0x75, 0x00,
|
||||
0x36, 0x1e, 0x8c, 0x51, 0xb3, 0xb2, 0x4c, 0xc3, 0x8d, 0x3e, 0xb2, 0xe9,
|
||||
0x24, 0xc9, 0xf8, 0x2a, 0xa6, 0x10, 0x18, 0x96, 0x8d, 0xb3, 0xaa, 0x16,
|
||||
0x53, 0x08, 0xac, 0x4e, 0x15, 0xe7, 0xd2, 0xea, 0x99, 0x11, 0xf5, 0xf0,
|
||||
0xed, 0x22, 0xc0, 0xed, 0x76, 0x23, 0xcb, 0x32, 0x89, 0xc8, 0x34, 0xfb,
|
||||
0xaf, 0x9e, 0xe7, 0x47, 0x3c, 0x56, 0x34, 0x9a, 0x42, 0xf0, 0xa1, 0xfe,
|
||||
0x10, 0x9b, 0x86, 0x0b, 0x53, 0x64, 0x31, 0x24, 0x0b, 0xef, 0xb1, 0x7a,
|
||||
0xd4, 0xa7, 0x93, 0x7d, 0xff, 0x24, 0xb0, 0x37, 0xbf, 0x61, 0x19, 0x5b,
|
||||
0xe8, 0xd6, 0x8e, 0x59, 0x52, 0xca, 0xd9, 0xd3, 0xe1, 0x26, 0xfc, 0xb5,
|
||||
0x0b, 0xc3, 0xd4, 0x51, 0x6b, 0xbd, 0x08, 0xcb, 0xe4, 0xe8, 0xe8, 0x43,
|
||||
0x8f, 0xe3, 0xef, 0x04, 0x66, 0x75, 0x0d, 0xce, 0x7d, 0x0d, 0xe0, 0xab,
|
||||
0x41, 0x17, 0x16, 0xba, 0x25, 0x70, 0xaa, 0x95, 0x34, 0x37, 0xa7, 0xa9,
|
||||
0xcf, 0xae, 0x71, 0xb2, 0x71, 0x2f, 0x5f, 0x52, 0x29, 0x7c, 0x2b, 0xef,
|
||||
0xc9, 0x78, 0x3c, 0xdb, 0xc5, 0x04, 0x81, 0x40, 0x20, 0x37, 0x8d, 0xc1,
|
||||
0x41, 0xde, 0xdd, 0x1f, 0xe3, 0xe0, 0xf5, 0x5e, 0xea, 0x0e, 0x1c, 0xc1,
|
||||
0x76, 0xb9, 0xa8, 0x3e, 0x5d, 0x87, 0xa8, 0x70, 0x10, 0xec, 0x90, 0x78,
|
||||
0xfc, 0x39, 0x4d, 0x63, 0x7c, 0x91, 0xf6, 0xc9, 0x09, 0x2a, 0x55, 0x75,
|
||||
0x5c, 0x0a, 0x87, 0xc3, 0x53, 0x89, 0x44, 0xe2, 0xc2, 0xef, 0xb3, 0xad,
|
||||
0x9d, 0x99, 0x41, 0x7e, 0xf3, 0x1a, 0xf3, 0xdc, 0x09, 0xca, 0x1a, 0xaa,
|
||||
0xf1, 0x9c, 0xb5, 0xd1, 0x0d, 0x41, 0x66, 0xc3, 0x64, 0x7a, 0x4a, 0xd0,
|
||||
0x33, 0xfb, 0x8a, 0x0a, 0x55, 0x7d, 0x71, 0x6d, 0x61, 0x21, 0x50, 0xea,
|
||||
0x7f, 0x30, 0xd2, 0xdd, 0x7d, 0x49, 0x5f, 0x5a, 0xba, 0xe9, 0x4e, 0x26,
|
||||
0x55, 0xe7, 0xcf, 0x4c, 0xb9, 0x6d, 0x83, 0x90, 0x65, 0xc3, 0xa5, 0x28,
|
||||
0xcb, 0x07, 0xbb, 0xba, 0x6e, 0x9d, 0x1a, 0x1a, 0x9a, 0x00, 0xf8, 0x05,
|
||||
0xf4, 0xf5, 0x23, 0xe9, 0x30, 0xeb, 0x2d, 0xf9, 0x00, 0x00, 0x00, 0x00,
|
||||
0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
|
||||
}
|
||||
|
||||
var __32x32_categories_applications_accessories_png = []byte{
|
||||
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
|
||||
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20,
|
||||
0x08, 0x06, 0x00, 0x00, 0x00, 0x73, 0x7a, 0x7a, 0xf4, 0x00, 0x00, 0x00,
|
||||
0x04, 0x73, 0x42, 0x49, 0x54, 0x08, 0x08, 0x08, 0x08, 0x7c, 0x08, 0x64,
|
||||
0x88, 0x00, 0x00, 0x08, 0x66, 0x49, 0x44, 0x41, 0x54, 0x58, 0x85, 0xcd,
|
||||
0x97, 0x7b, 0x70, 0x54, 0xd5, 0x1d, 0xc7, 0xbf, 0xe7, 0x9c, 0xfb, 0xd8,
|
||||
0x57, 0x36, 0x4b, 0xb2, 0xcb, 0x12, 0x24, 0x04, 0x08, 0x41, 0x9e, 0x85,
|
||||
0x00, 0x2d, 0x10, 0x71, 0x24, 0xf2, 0x68, 0x67, 0x64, 0xa0, 0x58, 0xed,
|
||||
0xbb, 0xb4, 0x5a, 0x19, 0x2d, 0x4a, 0x18, 0x1e, 0x82, 0x05, 0x9c, 0x59,
|
||||
0x0d, 0x2a, 0x82, 0xe3, 0x28, 0x1d, 0x5b, 0x69, 0x61, 0x04, 0x44, 0x0a,
|
||||
0x8c, 0x8f, 0x62, 0x47, 0x0a, 0x33, 0x3c, 0x85, 0x20, 0x52, 0x85, 0x28,
|
||||
0x98, 0x28, 0x12, 0x1e, 0x9b, 0x40, 0x08, 0xc9, 0xe6, 0xb1, 0x8f, 0xec,
|
||||
0xde, 0xc7, 0x39, 0xa7, 0x7f, 0x24, 0x4b, 0x13, 0x12, 0x20, 0xfe, 0xd5,
|
||||
0xfe, 0x66, 0x7e, 0x73, 0xce, 0xde, 0xfd, 0x9e, 0xdf, 0xef, 0x73, 0x7f,
|
||||
0xe7, 0x9e, 0x73, 0xcf, 0x05, 0xfe, 0x07, 0x76, 0x74, 0x33, 0x9e, 0x4c,
|
||||
0xf7, 0x49, 0x77, 0x82, 0x35, 0x6b, 0xd6, 0x64, 0x82, 0xf2, 0x66, 0x42,
|
||||
0xc9, 0x4f, 0x96, 0x2f, 0x5d, 0xf1, 0xfe, 0x9d, 0x02, 0xae, 0x5d, 0xbb,
|
||||
0xb6, 0x8f, 0x80, 0x55, 0x2b, 0x89, 0x98, 0xfe, 0xc7, 0xa7, 0x9f, 0xdd,
|
||||
0x7f, 0x3b, 0xed, 0x91, 0xb7, 0x50, 0xc0, 0x08, 0xce, 0x4d, 0xfe, 0x5d,
|
||||
0x5b, 0x6e, 0xda, 0x9d, 0x48, 0x51, 0xb1, 0xc2, 0xed, 0xf6, 0x70, 0x46,
|
||||
0x95, 0x0d, 0xa1, 0x50, 0xc8, 0x75, 0x27, 0x00, 0x45, 0x27, 0xaf, 0x7b,
|
||||
0xbd, 0x99, 0x42, 0x53, 0x1d, 0x7f, 0xdd, 0xb0, 0x61, 0x83, 0x7a, 0x2b,
|
||||
0xdd, 0xae, 0x5d, 0x60, 0x9a, 0xaa, 0xbc, 0xd7, 0xf1, 0x5a, 0x17, 0x80,
|
||||
0xb5, 0x6b, 0x4b, 0xf3, 0x29, 0x65, 0x4f, 0xce, 0x99, 0x3d, 0x87, 0xe5,
|
||||
0x0f, 0x1c, 0xe4, 0x76, 0x79, 0x1c, 0xcf, 0xde, 0x2e, 0xf9, 0x9a, 0x35,
|
||||
0xab, 0x8b, 0x28, 0xa1, 0x33, 0x67, 0xcd, 0x9c, 0x4d, 0xf3, 0x72, 0xfb,
|
||||
0x07, 0x5b, 0x62, 0x4d, 0x8b, 0x6e, 0xa5, 0xed, 0x97, 0xa2, 0x2b, 0x33,
|
||||
0x83, 0xdf, 0xcb, 0xbf, 0x2d, 0x00, 0x61, 0xca, 0xaf, 0x47, 0x8d, 0x1a,
|
||||
0xe5, 0xe0, 0x5c, 0x62, 0xec, 0xb8, 0xef, 0x3b, 0x09, 0x25, 0x25, 0xeb,
|
||||
0xd6, 0x95, 0x0e, 0xec, 0xfe, 0x8e, 0x76, 0x31, 0xcd, 0xa1, 0x6f, 0x2e,
|
||||
0x9e, 0x32, 0xd5, 0x69, 0x59, 0x36, 0xc6, 0x8c, 0x1e, 0xeb, 0x52, 0x14,
|
||||
0xb6, 0xa0, 0x3b, 0xed, 0xf1, 0x2d, 0x28, 0xa4, 0x4c, 0x7f, 0x66, 0xf0,
|
||||
0x84, 0xce, 0x15, 0xed, 0x02, 0xc0, 0x05, 0x8f, 0x85, 0xc3, 0xe1, 0x24,
|
||||
0xa5, 0x14, 0x90, 0x40, 0xd1, 0xc4, 0x22, 0x9d, 0x69, 0x8e, 0x37, 0xbb,
|
||||
0x0b, 0x7a, 0xa9, 0xfa, 0xc2, 0x13, 0x7e, 0x7f, 0x76, 0x4e, 0xa6, 0xd7,
|
||||
0x47, 0xa4, 0x94, 0x68, 0x88, 0xd4, 0x0b, 0xce, 0x45, 0xf3, 0xcd, 0xba,
|
||||
0x3d, 0xeb, 0xa1, 0x13, 0xca, 0xde, 0xbb, 0x7b, 0xd2, 0x42, 0x87, 0xa2,
|
||||
0xb8, 0x3b, 0xfd, 0xd7, 0x05, 0x20, 0x95, 0xb0, 0x5f, 0x6b, 0x8e, 0xb6,
|
||||
0xd4, 0x44, 0x1a, 0xeb, 0xa5, 0x94, 0x12, 0xb9, 0xfd, 0xf2, 0x98, 0xc2,
|
||||
0xd4, 0xc9, 0x2f, 0xad, 0x2b, 0x9d, 0xd6, 0x51, 0xf7, 0xe2, 0x8b, 0x2f,
|
||||
0x06, 0x28, 0x21, 0x2f, 0x4d, 0xbe, 0x67, 0x8a, 0x87, 0x73, 0x0e, 0x4a,
|
||||
0x09, 0xca, 0x8e, 0x1f, 0x33, 0x2c, 0xc9, 0x1f, 0xbe, 0x39, 0x66, 0x2f,
|
||||
0x9f, 0xb2, 0x36, 0xab, 0xdf, 0x84, 0x9c, 0xcc, 0xe0, 0x44, 0x02, 0x88,
|
||||
0xdb, 0x03, 0x84, 0x42, 0x21, 0x61, 0x1b, 0xc6, 0x23, 0x87, 0x8f, 0x1c,
|
||||
0x4a, 0x32, 0x46, 0x61, 0xdb, 0x36, 0xa6, 0x16, 0x4f, 0x73, 0xe9, 0x8a,
|
||||
0xfe, 0xb7, 0x50, 0x28, 0xa4, 0xa4, 0x75, 0x0e, 0x97, 0xfa, 0xea, 0x98,
|
||||
0xd1, 0x85, 0x3a, 0x64, 0xdb, 0xef, 0x2f, 0xbe, 0x3c, 0x95, 0x02, 0xb0,
|
||||
0x71, 0xc5, 0xd2, 0x15, 0x5f, 0x77, 0x8c, 0x77, 0xec, 0x2d, 0xdc, 0x4b,
|
||||
0x35, 0xc7, 0xbc, 0x41, 0x63, 0x17, 0x3a, 0x64, 0xf2, 0x20, 0x8e, 0xef,
|
||||
0x9a, 0x7e, 0x7b, 0x00, 0x00, 0x58, 0xbe, 0x7c, 0xd5, 0x09, 0x2e, 0xc4,
|
||||
0xbe, 0xf3, 0x55, 0xe7, 0x2d, 0x00, 0x70, 0xb9, 0xdc, 0x08, 0xf6, 0xe9,
|
||||
0xd3, 0xdb, 0xe3, 0xd5, 0x17, 0x00, 0xc0, 0xcb, 0x2f, 0xaf, 0x1e, 0x4f,
|
||||
0x08, 0x1e, 0x1c, 0x9c, 0x3f, 0x44, 0x13, 0x42, 0x80, 0x73, 0x1b, 0xe7,
|
||||
0xab, 0xaa, 0x5a, 0x63, 0xac, 0x75, 0x55, 0xa7, 0xe4, 0x9b, 0x90, 0xc1,
|
||||
0x14, 0xb6, 0x6b, 0xd8, 0xa4, 0xc5, 0x4e, 0x2a, 0xaf, 0x40, 0xda, 0x97,
|
||||
0x51, 0xf4, 0xf0, 0x47, 0x77, 0x06, 0x00, 0x80, 0xa4, 0x30, 0x16, 0x7c,
|
||||
0x76, 0xea, 0xdf, 0x16, 0xa5, 0x04, 0x9c, 0x73, 0x14, 0x4d, 0xbc, 0xd7,
|
||||
0x45, 0x08, 0x7d, 0xfe, 0x85, 0x17, 0x5e, 0x08, 0xaa, 0x9a, 0xb6, 0x75,
|
||||
0x6a, 0xf1, 0x0c, 0xa7, 0x65, 0x59, 0x50, 0x14, 0x86, 0x23, 0x47, 0x0f,
|
||||
0x25, 0x6c, 0xce, 0x4b, 0x42, 0x25, 0xa1, 0x68, 0xa7, 0xe0, 0x3a, 0x7b,
|
||||
0x23, 0x38, 0xe8, 0x3e, 0x9f, 0x27, 0x7b, 0x04, 0x44, 0xaa, 0x0c, 0x52,
|
||||
0x08, 0x48, 0xc9, 0x7b, 0x06, 0xb0, 0x6a, 0xf1, 0xaa, 0x2b, 0x04, 0xe4,
|
||||
0xa5, 0x93, 0x9f, 0x9d, 0x68, 0xa5, 0x94, 0xc0, 0xb6, 0x6d, 0x14, 0x16,
|
||||
0x8e, 0xd3, 0x74, 0xa7, 0xba, 0x3b, 0x2b, 0xdb, 0xdf, 0xdf, 0xed, 0xf6,
|
||||
0x10, 0x29, 0x25, 0x22, 0x8d, 0x11, 0x11, 0x8b, 0xc5, 0xbe, 0x59, 0xb6,
|
||||
0xe4, 0x99, 0xed, 0x1d, 0xc7, 0x97, 0x6d, 0xc6, 0x03, 0x8a, 0xea, 0x7d,
|
||||
0x28, 0x77, 0xe4, 0x63, 0x0e, 0x9e, 0xd8, 0x0f, 0xc1, 0x53, 0x10, 0x42,
|
||||
0xe0, 0x93, 0x77, 0x67, 0xf5, 0x0c, 0x00, 0x00, 0x34, 0xc5, 0xb1, 0xae,
|
||||
0xba, 0xba, 0x3a, 0x66, 0x5a, 0x26, 0xa4, 0x94, 0x18, 0x98, 0x97, 0xaf,
|
||||
0x51, 0x4a, 0x26, 0x8c, 0x1c, 0x3e, 0xd2, 0x6d, 0xdb, 0x36, 0x74, 0x5d,
|
||||
0xc3, 0xc7, 0x47, 0x0f, 0xa6, 0x6c, 0xd3, 0xfc, 0x3d, 0x21, 0x44, 0xa6,
|
||||
0xc7, 0x7d, 0xba, 0x05, 0xd9, 0x84, 0xb2, 0x6d, 0x43, 0x27, 0x2d, 0x74,
|
||||
0x4a, 0xab, 0x0a, 0xdc, 0xac, 0x81, 0xe0, 0x02, 0x82, 0x73, 0x4c, 0x98,
|
||||
0xb3, 0xb3, 0xe7, 0x00, 0x25, 0x25, 0x25, 0x86, 0x6d, 0xda, 0x4f, 0x1c,
|
||||
0x3a, 0x74, 0x20, 0xae, 0xaa, 0x2a, 0x4c, 0xd3, 0xc4, 0xfd, 0xc5, 0xd3,
|
||||
0xf0, 0xc5, 0x99, 0x72, 0x00, 0x40, 0x45, 0xe5, 0x59, 0x83, 0x73, 0xb9,
|
||||
0x73, 0xd9, 0xb2, 0x95, 0xe5, 0x1d, 0xc7, 0x09, 0xa6, 0x6c, 0xee, 0x3b,
|
||||
0x64, 0xba, 0xcb, 0x99, 0x91, 0x0b, 0xbb, 0xf5, 0x04, 0x2c, 0xd3, 0x02,
|
||||
0xe7, 0x16, 0x84, 0xe0, 0x90, 0xa2, 0x87, 0x53, 0x90, 0xb6, 0xe5, 0xcb,
|
||||
0x57, 0xfc, 0xc3, 0x30, 0x52, 0x5f, 0x36, 0x44, 0xae, 0x0b, 0x21, 0x04,
|
||||
0x74, 0x5d, 0x03, 0x63, 0x0a, 0x6a, 0xaf, 0x5d, 0x41, 0x45, 0x65, 0x85,
|
||||
0xc1, 0x48, 0x72, 0x71, 0x47, 0x7d, 0xd9, 0x56, 0xfc, 0x46, 0xd5, 0xb3,
|
||||
0xa6, 0xe6, 0x14, 0xfc, 0x5c, 0xb3, 0xe2, 0x07, 0x20, 0x6c, 0x03, 0x57,
|
||||
0x2f, 0x9e, 0x03, 0x37, 0x2d, 0x08, 0x2e, 0x70, 0x72, 0xf7, 0x2f, 0xbf,
|
||||
0x1b, 0x00, 0x00, 0x98, 0x75, 0x75, 0x0b, 0x8e, 0x1e, 0x3e, 0x48, 0x9d,
|
||||
0x4e, 0x07, 0x52, 0x86, 0x81, 0xc9, 0x45, 0xf7, 0xa2, 0xfc, 0xd8, 0x61,
|
||||
0x69, 0x35, 0x46, 0x4a, 0x17, 0x2d, 0x0a, 0xdd, 0xd8, 0x78, 0x4e, 0x6c,
|
||||
0x43, 0x3f, 0x02, 0xfa, 0xe7, 0x82, 0x09, 0x7f, 0x70, 0x72, 0xa3, 0x02,
|
||||
0xdc, 0xb8, 0x86, 0x78, 0xb4, 0x19, 0xf5, 0x35, 0x61, 0x48, 0xc1, 0x21,
|
||||
0x04, 0xc7, 0xf8, 0x99, 0x9b, 0x3a, 0xc5, 0x56, 0xba, 0x64, 0xeb, 0xc6,
|
||||
0x3c, 0xef, 0xee, 0x58, 0xa6, 0x4c, 0x9c, 0x8c, 0x43, 0xbd, 0xb2, 0xe0,
|
||||
0xfd, 0xf0, 0x7d, 0xb4, 0x9e, 0x3d, 0x03, 0xe7, 0xb5, 0xab, 0xc4, 0xe5,
|
||||
0xeb, 0xf5, 0x14, 0x80, 0x57, 0x00, 0x40, 0x4a, 0x90, 0x13, 0xef, 0x28,
|
||||
0x3b, 0xfa, 0x0d, 0xfd, 0x91, 0x43, 0x77, 0x64, 0xc1, 0x68, 0xde, 0x0d,
|
||||
0x29, 0x6d, 0x84, 0x2b, 0xcf, 0x26, 0x00, 0xb8, 0x05, 0x17, 0x20, 0xe0,
|
||||
0x90, 0xc2, 0xee, 0x39, 0xc0, 0x1e, 0x40, 0xbf, 0xe6, 0x41, 0xbe, 0xbc,
|
||||
0x5a, 0x33, 0x6b, 0x4a, 0xe5, 0x39, 0x7c, 0x23, 0x80, 0xea, 0xfd, 0xfb,
|
||||
0x30, 0xcc, 0x17, 0x44, 0x9f, 0xdc, 0xa1, 0x38, 0x5d, 0x1f, 0xee, 0xbd,
|
||||
0xc1, 0x85, 0x07, 0x9c, 0xad, 0x38, 0x7c, 0x7c, 0x2b, 0x7d, 0xd4, 0xe5,
|
||||
0x0d, 0x14, 0x06, 0x06, 0xcc, 0x54, 0x8c, 0xe8, 0xbf, 0x20, 0x85, 0x8d,
|
||||
0xba, 0xea, 0x4b, 0x36, 0xb7, 0xad, 0xd3, 0x84, 0x92, 0x71, 0x42, 0x08,
|
||||
0x27, 0x21, 0xc0, 0xe7, 0x7b, 0x1e, 0xef, 0x94, 0xa3, 0xcb, 0x14, 0xec,
|
||||
0x02, 0x3c, 0xdb, 0x29, 0x5d, 0xbd, 0x93, 0xd2, 0x6a, 0xcb, 0xed, 0xbe,
|
||||
0xe2, 0xe3, 0x8e, 0x2f, 0x32, 0x6d, 0xcd, 0x99, 0x6c, 0x8e, 0x22, 0xb1,
|
||||
0x77, 0x77, 0x92, 0x12, 0xba, 0xf1, 0x52, 0xa2, 0x31, 0x19, 0x6f, 0x6d,
|
||||
0xc5, 0x88, 0xec, 0xbb, 0x9c, 0x4e, 0xa6, 0x7e, 0xd8, 0xd7, 0x9f, 0x5d,
|
||||
0x13, 0x59, 0xa2, 0xbd, 0x1e, 0xdb, 0x98, 0xed, 0x8a, 0x9e, 0x2f, 0x03,
|
||||
0x37, 0x23, 0x48, 0xc6, 0x63, 0x68, 0xb8, 0x5a, 0x63, 0x9a, 0x92, 0xcf,
|
||||
0x05, 0x00, 0x21, 0x38, 0x04, 0x17, 0x18, 0x33, 0xe3, 0xb5, 0x5b, 0x57,
|
||||
0x60, 0x3b, 0x70, 0x3f, 0x18, 0xdb, 0x91, 0x17, 0x0c, 0xfa, 0xdc, 0x9a,
|
||||
0xa6, 0x9a, 0xb6, 0x0d, 0x4d, 0xd3, 0x00, 0x21, 0x50, 0x71, 0xf9, 0x92,
|
||||
0x6c, 0xd5, 0xf9, 0x3b, 0xf3, 0xe2, 0x62, 0xde, 0x26, 0x2f, 0xf1, 0x36,
|
||||
0xd8, 0xad, 0x0f, 0x06, 0x4d, 0xaa, 0x0c, 0xf3, 0xe7, 0xd0, 0x2c, 0xb7,
|
||||
0xd7, 0xa7, 0x32, 0x86, 0xd4, 0x25, 0x0b, 0x27, 0x7f, 0xfc, 0x01, 0xfa,
|
||||
0x3f, 0x1e, 0x84, 0x35, 0xb2, 0x36, 0x21, 0x09, 0x9e, 0xbd, 0x6f, 0x2e,
|
||||
0x2e, 0x96, 0x6d, 0x05, 0x84, 0x10, 0x20, 0x00, 0x84, 0xb0, 0xba, 0xaf,
|
||||
0xc0, 0x16, 0x60, 0x9a, 0xee, 0x74, 0xbe, 0x5f, 0x90, 0x9b, 0x1b, 0x48,
|
||||
0x24, 0x93, 0x66, 0x65, 0x38, 0x5c, 0x73, 0xb1, 0xb6, 0xf6, 0x4f, 0x95,
|
||||
0xe1, 0xcb, 0x11, 0x49, 0x08, 0x72, 0x7c, 0xbd, 0xa4, 0x92, 0xc0, 0x69,
|
||||
0x00, 0xa8, 0x8e, 0xf2, 0x5f, 0xd5, 0xb6, 0x34, 0x71, 0xa2, 0x50, 0x44,
|
||||
0x92, 0x31, 0xb3, 0x6a, 0x40, 0xd8, 0xfe, 0x46, 0x8f, 0xa3, 0x26, 0x5a,
|
||||
0x8f, 0xd1, 0x39, 0xf9, 0xa8, 0xdb, 0x14, 0x41, 0xe4, 0x03, 0xa3, 0xe5,
|
||||
0xaa, 0x43, 0xac, 0x4f, 0xc7, 0x17, 0x9c, 0x43, 0x70, 0x8e, 0x2f, 0xf7,
|
||||
0x3f, 0xdd, 0x15, 0x60, 0x3b, 0xe0, 0xd7, 0x15, 0xe5, 0xef, 0x03, 0x02,
|
||||
0x81, 0xcc, 0x9a, 0xfa, 0xfa, 0x96, 0x48, 0x4b, 0xcb, 0xd6, 0x3e, 0x42,
|
||||
0x0c, 0xfc, 0x19, 0xe7, 0x25, 0xb6, 0x90, 0xeb, 0x0c, 0xd3, 0x94, 0xba,
|
||||
0xc3, 0x41, 0x15, 0x4a, 0x87, 0x00, 0x40, 0x08, 0xb0, 0xbd, 0x52, 0x4f,
|
||||
0xd8, 0x90, 0x48, 0x58, 0x31, 0x75, 0xfc, 0xfa, 0xa7, 0x94, 0x71, 0x6f,
|
||||
0x17, 0x21, 0xb0, 0xd0, 0x8b, 0x33, 0xd7, 0x2e, 0x60, 0x44, 0x70, 0x20,
|
||||
0x5a, 0xff, 0xa9, 0xb9, 0x93, 0x3f, 0xc5, 0x8d, 0xc3, 0x47, 0xdb, 0x1e,
|
||||
0x20, 0x30, 0xe2, 0xbe, 0xe7, 0xba, 0x02, 0x50, 0x4a, 0x57, 0xe6, 0xfa,
|
||||
0xfd, 0x59, 0x49, 0xc3, 0xe0, 0xf1, 0x54, 0xea, 0xe8, 0x2f, 0x84, 0x98,
|
||||
0x5f, 0x0c, 0xd8, 0x00, 0xa0, 0x53, 0xfa, 0x03, 0x4d, 0x51, 0x48, 0xd2,
|
||||
0x30, 0x52, 0xdc, 0xb6, 0x3f, 0xef, 0x70, 0x47, 0xf1, 0x60, 0x86, 0x0f,
|
||||
0x8c, 0x39, 0x08, 0x6f, 0x49, 0xc0, 0x4c, 0x54, 0xc0, 0x5f, 0x9c, 0x09,
|
||||
0x6d, 0xba, 0x69, 0xd6, 0x36, 0x47, 0xec, 0x31, 0x83, 0x87, 0x78, 0x3d,
|
||||
0xba, 0xbe, 0x26, 0xad, 0x97, 0xed, 0x3b, 0xe1, 0xcd, 0xab, 0x80, 0x02,
|
||||
0x80, 0x24, 0xe4, 0x21, 0x87, 0xa6, 0xd1, 0xeb, 0xcd, 0xcd, 0x11, 0x8b,
|
||||
0xf3, 0x1b, 0x27, 0xd6, 0xb7, 0x81, 0xd1, 0x9a, 0xaa, 0x4e, 0xd1, 0x54,
|
||||
0x15, 0xd7, 0x1b, 0x1b, 0xe3, 0x75, 0xc0, 0x47, 0x00, 0x18, 0x00, 0xa6,
|
||||
0x16, 0xf2, 0x48, 0x4b, 0x6b, 0x1c, 0xb9, 0x19, 0x01, 0x7c, 0xf5, 0xdc,
|
||||
0x4e, 0x08, 0xce, 0x11, 0x6d, 0x6a, 0x90, 0xae, 0x62, 0xab, 0xf6, 0xe2,
|
||||
0xd5, 0xda, 0x26, 0x8f, 0xae, 0x13, 0x00, 0xf7, 0x00, 0x80, 0x4c, 0x3f,
|
||||
0x84, 0x82, 0xa3, 0xe2, 0x68, 0x69, 0x57, 0x00, 0x02, 0xb8, 0x08, 0x00,
|
||||
0xcb, 0xb6, 0xd9, 0x5c, 0xa0, 0x16, 0x80, 0x73, 0x1d, 0x30, 0x4d, 0x57,
|
||||
0xd5, 0xfd, 0x63, 0x0b, 0x0a, 0xb2, 0xae, 0x47, 0xa3, 0x89, 0x46, 0x29,
|
||||
0xb7, 0x2d, 0x06, 0x9c, 0x00, 0x72, 0x4a, 0xe7, 0x63, 0x8e, 0x98, 0x8d,
|
||||
0xe1, 0x5f, 0x37, 0xd5, 0x21, 0xe0, 0xce, 0x80, 0x71, 0xc2, 0xc0, 0x57,
|
||||
0xaf, 0xd5, 0xe0, 0xc2, 0xd7, 0x61, 0x12, 0x37, 0x45, 0x1e, 0xc9, 0x92,
|
||||
0x01, 0x0a, 0x40, 0xef, 0xa5, 0xf4, 0xfe, 0xf8, 0x2f, 0x90, 0x10, 0xd2,
|
||||
0x99, 0x7e, 0x17, 0x0c, 0x99, 0xb8, 0xa4, 0x13, 0x40, 0x7a, 0x15, 0xc4,
|
||||
0x02, 0x3e, 0x5f, 0x56, 0x34, 0x99, 0x54, 0xb7, 0x37, 0x35, 0x1d, 0x00,
|
||||
0x21, 0x19, 0x01, 0x8f, 0x27, 0x7f, 0x58, 0xff, 0xfe, 0x19, 0x97, 0x1a,
|
||||
0x1a, 0x52, 0x35, 0x8d, 0x8d, 0x15, 0x8b, 0x6d, 0x7b, 0x33, 0x80, 0xde,
|
||||
0x03, 0x73, 0x90, 0xd1, 0x3b, 0x9b, 0x6e, 0x60, 0x39, 0xf7, 0xe8, 0xfa,
|
||||
0x7c, 0xa0, 0xfc, 0x8d, 0x53, 0x18, 0xdb, 0x77, 0x10, 0xaa, 0xcb, 0x1a,
|
||||
0x70, 0xe5, 0x80, 0x03, 0x8e, 0x7c, 0x07, 0x02, 0x9a, 0x0e, 0x55, 0x55,
|
||||
0xc1, 0x21, 0x11, 0xbc, 0x7b, 0x08, 0x54, 0x06, 0x48, 0x22, 0x21, 0x85,
|
||||
0x84, 0xe0, 0x9d, 0x57, 0x01, 0x01, 0x80, 0x1d, 0x8c, 0x3d, 0xe6, 0xcf,
|
||||
0xcc, 0x7c, 0x75, 0x78, 0x6e, 0x6e, 0x86, 0x68, 0xbf, 0x98, 0xb2, 0x2c,
|
||||
0x59, 0x5e, 0x55, 0x15, 0x6d, 0x16, 0x62, 0xdf, 0x52, 0xcb, 0x2a, 0x6d,
|
||||
0x6a, 0xab, 0x96, 0xf2, 0xca, 0x12, 0xb2, 0xba, 0xef, 0x5d, 0xfd, 0xa6,
|
||||
0xf6, 0xce, 0x9b, 0xa6, 0x99, 0xb1, 0x4f, 0xc0, 0x2b, 0x5b, 0x60, 0x6c,
|
||||
0x6a, 0xc2, 0x50, 0x7f, 0x2e, 0x02, 0x9e, 0x4c, 0x48, 0x29, 0xa1, 0xab,
|
||||
0x2a, 0x2e, 0x34, 0xd5, 0x21, 0x36, 0x21, 0x8e, 0xbe, 0xbf, 0xf5, 0x82,
|
||||
0x31, 0x40, 0x4a, 0x00, 0x52, 0xe2, 0x5a, 0xf8, 0x02, 0x00, 0x20, 0xfd,
|
||||
0x5d, 0x70, 0xe3, 0xc3, 0x64, 0x07, 0xa5, 0x0b, 0x18, 0x63, 0x4b, 0x28,
|
||||
0x63, 0x4c, 0x08, 0x41, 0x84, 0x10, 0xe5, 0x95, 0xb6, 0xfd, 0x7c, 0x08,
|
||||
0xa8, 0x6a, 0xaf, 0x14, 0x7d, 0x65, 0x09, 0x66, 0x14, 0xe4, 0xa9, 0x6f,
|
||||
0xe6, 0x8d, 0x7a, 0xd4, 0x61, 0x25, 0xbf, 0x85, 0x9d, 0x0a, 0x83, 0x10,
|
||||
0x09, 0x18, 0x1c, 0xb1, 0xf7, 0x62, 0x48, 0x7e, 0x9a, 0x02, 0x03, 0x83,
|
||||
0x6d, 0xdb, 0xe8, 0x75, 0xbf, 0x0b, 0x39, 0x8f, 0x78, 0x01, 0x22, 0xdb,
|
||||
0x1e, 0x02, 0x48, 0x48, 0x29, 0xe1, 0xf6, 0x4f, 0xc7, 0xc5, 0xf2, 0x8d,
|
||||
0x5d, 0x01, 0xee, 0x64, 0x87, 0xb6, 0xc3, 0xaf, 0x59, 0xe4, 0xdb, 0xbe,
|
||||
0x05, 0x3f, 0xf4, 0xb9, 0xb3, 0x0a, 0x01, 0x69, 0x00, 0x84, 0x41, 0x4a,
|
||||
0x09, 0x48, 0x01, 0x09, 0x09, 0x08, 0xde, 0xd6, 0x4a, 0x01, 0x29, 0x05,
|
||||
0x00, 0x01, 0x48, 0x09, 0x29, 0xdb, 0x4f, 0x42, 0xed, 0xfd, 0xcb, 0x67,
|
||||
0xb6, 0xde, 0x00, 0xe8, 0xd1, 0xcb, 0x08, 0x00, 0x54, 0x03, 0x9b, 0x25,
|
||||
0x91, 0xbe, 0x2b, 0xe7, 0xf6, 0x02, 0xd8, 0xdb, 0xd3, 0x61, 0xb7, 0x0d,
|
||||
0x09, 0xc0, 0xea, 0x71, 0x05, 0xd0, 0x06, 0xab, 0x01, 0xd0, 0xdb, 0x5b,
|
||||
0xb5, 0x43, 0x9b, 0x76, 0x86, 0xff, 0xee, 0xae, 0x1c, 0x6d, 0x7b, 0x89,
|
||||
0xd5, 0xc1, 0xcd, 0x76, 0x4f, 0xb6, 0xb7, 0xf6, 0x77, 0x01, 0xb8, 0xd9,
|
||||
0x48, 0x7b, 0x32, 0xda, 0xde, 0x4f, 0x7b, 0xda, 0x64, 0x07, 0x6f, 0x9f,
|
||||
0x0f, 0x48, 0xfc, 0xbf, 0xd9, 0x7f, 0x00, 0x76, 0x69, 0xe5, 0x89, 0x4b,
|
||||
0x1e, 0x7c, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae,
|
||||
0x42, 0x60, 0x82,
|
||||
}
|
||||
|
||||
var __32x32_places_folder_png = []byte{
|
||||
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
|
||||
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20,
|
||||
0x08, 0x06, 0x00, 0x00, 0x00, 0x73, 0x7a, 0x7a, 0xf4, 0x00, 0x00, 0x00,
|
||||
0x04, 0x73, 0x42, 0x49, 0x54, 0x08, 0x08, 0x08, 0x08, 0x7c, 0x08, 0x64,
|
||||
0x88, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0d,
|
||||
0xd7, 0x00, 0x00, 0x0d, 0xd7, 0x01, 0x42, 0x28, 0x9b, 0x78, 0x00, 0x00,
|
||||
0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61,
|
||||
0x72, 0x65, 0x00, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x6e, 0x6b, 0x73, 0x63,
|
||||
0x61, 0x70, 0x65, 0x2e, 0x6f, 0x72, 0x67, 0x9b, 0xee, 0x3c, 0x1a, 0x00,
|
||||
0x00, 0x04, 0x15, 0x49, 0x44, 0x41, 0x54, 0x58, 0x85, 0xed, 0x97, 0x3d,
|
||||
0x6f, 0x1c, 0x45, 0x18, 0xc7, 0x7f, 0x33, 0xbb, 0x77, 0x7b, 0xaf, 0xf6,
|
||||
0x39, 0x91, 0x30, 0x09, 0x05, 0x56, 0x94, 0xc2, 0x1f, 0x00, 0x09, 0x05,
|
||||
0x21, 0x53, 0x6e, 0x95, 0x82, 0x1a, 0x89, 0x02, 0x5a, 0x3a, 0x3e, 0x40,
|
||||
0x24, 0x3e, 0x40, 0x4a, 0x84, 0x44, 0x85, 0x68, 0xa1, 0x43, 0x48, 0xa7,
|
||||
0x40, 0x17, 0xc9, 0x8a, 0x02, 0x8a, 0xd2, 0x45, 0xc2, 0xc6, 0xc4, 0x28,
|
||||
0x16, 0xc6, 0xf7, 0xba, 0x77, 0xde, 0x97, 0x99, 0x9d, 0x19, 0x8a, 0x7b,
|
||||
0xc9, 0x3a, 0x77, 0x8e, 0x13, 0x72, 0x52, 0x28, 0xf8, 0x4b, 0x8f, 0x76,
|
||||
0x76, 0x76, 0xf7, 0x79, 0x7e, 0xf3, 0x3c, 0x33, 0xb3, 0xbb, 0x22, 0x0c,
|
||||
0x43, 0x5e, 0xa7, 0xe4, 0x6b, 0x8d, 0xfe, 0x5f, 0x00, 0xf0, 0x01, 0x6e,
|
||||
0xdc, 0xb8, 0xe1, 0x2b, 0xa5, 0x7e, 0x36, 0xc6, 0xbc, 0x77, 0xd1, 0x03,
|
||||
0x42, 0x88, 0xdc, 0x5a, 0xfb, 0xf9, 0xc3, 0x87, 0x0f, 0xbf, 0x5c, 0x19,
|
||||
0x40, 0x9a, 0xa6, 0x5b, 0x95, 0x4a, 0xe5, 0x9d, 0x9b, 0x37, 0x6f, 0xfa,
|
||||
0x49, 0x92, 0xe0, 0x9c, 0x03, 0xc0, 0x5a, 0x0b, 0x30, 0x3f, 0x9f, 0xb6,
|
||||
0xfd, 0x3b, 0x77, 0xee, 0xdc, 0xde, 0xde, 0xde, 0xfe, 0xfe, 0xd1, 0xa3,
|
||||
0x47, 0xc7, 0x2b, 0x01, 0x50, 0x4a, 0x65, 0xd5, 0x6a, 0xd5, 0x1d, 0x1d,
|
||||
0x1d, 0xd1, 0xe9, 0x74, 0x70, 0xce, 0x2d, 0x35, 0x00, 0x21, 0x04, 0x9b,
|
||||
0x9b, 0x9b, 0xf9, 0xc1, 0xc1, 0xc1, 0x47, 0xc0, 0xed, 0x95, 0x00, 0x68,
|
||||
0xad, 0xd3, 0x2c, 0xcb, 0xc4, 0xa5, 0x4b, 0x1b, 0x18, 0x63, 0xb0, 0xd6,
|
||||
0x9e, 0x09, 0x5c, 0xcc, 0x84, 0x10, 0x82, 0x6a, 0xb5, 0x5a, 0x3f, 0x3c,
|
||||
0x3c, 0xfc, 0x62, 0x7b, 0x7b, 0xfb, 0xd6, 0xac, 0x7f, 0x99, 0x9c, 0x73,
|
||||
0xda, 0x5a, 0xfb, 0xf1, 0xde, 0xde, 0xde, 0x8f, 0xcf, 0x05, 0x00, 0xb2,
|
||||
0x2c, 0xcb, 0xbc, 0x4e, 0xa7, 0x4b, 0xa7, 0xd3, 0x99, 0x03, 0xcc, 0x8e,
|
||||
0xb3, 0x20, 0xc5, 0x40, 0x5b, 0x5b, 0x5b, 0xf5, 0x46, 0xa3, 0x41, 0xb3,
|
||||
0xd9, 0x44, 0x08, 0x81, 0x10, 0x02, 0x00, 0x29, 0xe5, 0xbc, 0x1d, 0x45,
|
||||
0x11, 0xf7, 0xee, 0xdd, 0xbb, 0x05, 0x3c, 0x1f, 0xe0, 0xf2, 0xe5, 0xcb,
|
||||
0x69, 0xbf, 0xdf, 0xf7, 0x36, 0x36, 0x5a, 0x68, 0xad, 0x17, 0x00, 0x96,
|
||||
0x95, 0x62, 0xa6, 0x38, 0x8e, 0x01, 0xe6, 0x10, 0x45, 0x8b, 0xe3, 0x18,
|
||||
0x63, 0x4c, 0xed, 0xbc, 0xe0, 0x73, 0x80, 0xdd, 0xdd, 0x5d, 0x75, 0xed,
|
||||
0xda, 0x35, 0xd9, 0xeb, 0xf5, 0xe6, 0x19, 0x00, 0x96, 0x82, 0xcc, 0xb2,
|
||||
0x51, 0xd4, 0x6c, 0xc4, 0xc5, 0xe0, 0x52, 0xca, 0x19, 0x40, 0xe5, 0x42,
|
||||
0x00, 0x00, 0x63, 0x8c, 0x6d, 0x36, 0xd7, 0x64, 0x9a, 0x66, 0xdc, 0x3f,
|
||||
0xb4, 0xdc, 0xdd, 0x3f, 0x1b, 0xe4, 0xbc, 0x3a, 0x3f, 0xab, 0x19, 0xcc,
|
||||
0xe4, 0x99, 0x16, 0xce, 0x5d, 0xb9, 0xce, 0xdb, 0x1f, 0xe8, 0xe2, 0x3d,
|
||||
0x9e, 0x27, 0xbe, 0x5d, 0xff, 0xfd, 0x9b, 0x4f, 0xce, 0x00, 0x38, 0xe7,
|
||||
0x74, 0xb7, 0xdb, 0xf5, 0xbb, 0xdd, 0x2e, 0x8f, 0x3b, 0x0d, 0x3e, 0xfd,
|
||||
0xf0, 0x5d, 0xae, 0xbf, 0xb5, 0x51, 0x70, 0x36, 0x3d, 0x3e, 0x2f, 0xfa,
|
||||
0x12, 0xc8, 0x69, 0xcf, 0x3c, 0xce, 0x93, 0xce, 0x98, 0xaf, 0xbe, 0xbb,
|
||||
0xff, 0xfe, 0xec, 0xbc, 0x98, 0x01, 0xdd, 0x68, 0xd4, 0xab, 0x5a, 0x6b,
|
||||
0xd4, 0x1f, 0x92, 0x66, 0xb5, 0xcc, 0xfe, 0xdf, 0x63, 0xa2, 0x38, 0x9f,
|
||||
0x8c, 0x6c, 0xe9, 0x68, 0xcf, 0x23, 0x11, 0x0b, 0x4d, 0x01, 0x34, 0x2a,
|
||||
0x3e, 0x2a, 0xd3, 0x08, 0x41, 0x67, 0x76, 0x79, 0xbe, 0x15, 0x1b, 0x63,
|
||||
0x74, 0xbf, 0x3f, 0xa0, 0xdb, 0xed, 0x12, 0x67, 0x96, 0x7a, 0xb5, 0x84,
|
||||
0x35, 0x93, 0x1b, 0xe4, 0xd4, 0xc1, 0xb3, 0x86, 0x3b, 0xcf, 0xdc, 0x53,
|
||||
0xb3, 0x13, 0xb3, 0xd6, 0x91, 0x64, 0x86, 0x34, 0xcb, 0xc1, 0x89, 0xf9,
|
||||
0x06, 0x56, 0x2c, 0x41, 0x56, 0xa9, 0x04, 0xb4, 0x5a, 0x2d, 0x32, 0x23,
|
||||
0xa8, 0x05, 0x25, 0xac, 0x53, 0x78, 0x72, 0x35, 0xaf, 0x8b, 0x71, 0x9a,
|
||||
0x53, 0x6f, 0xfa, 0x8c, 0x86, 0x39, 0xd6, 0xba, 0x27, 0x0b, 0x00, 0x79,
|
||||
0x9e, 0xab, 0x28, 0x1a, 0xd1, 0xef, 0x0f, 0xd0, 0xe6, 0x0d, 0xaa, 0x81,
|
||||
0x8f, 0x73, 0x0e, 0x4f, 0x9e, 0x9b, 0xe7, 0x17, 0x93, 0x83, 0x51, 0xaa,
|
||||
0xd1, 0xb9, 0x25, 0xf0, 0x25, 0x71, 0xaa, 0xad, 0x31, 0xf9, 0xd1, 0x02,
|
||||
0x80, 0x31, 0x26, 0x2b, 0x97, 0x4b, 0x54, 0x9b, 0x1b, 0xac, 0xd5, 0xca,
|
||||
0x38, 0x40, 0x0a, 0xb1, 0xbc, 0xf8, 0x2f, 0x28, 0xe3, 0x1c, 0xa3, 0x54,
|
||||
0x93, 0x5b, 0x87, 0xe7, 0x09, 0x82, 0xb2, 0xc7, 0x69, 0x92, 0x29, 0x21,
|
||||
0xc4, 0xc9, 0x02, 0x80, 0xb5, 0x36, 0x1b, 0x8d, 0xc6, 0x1c, 0x0f, 0x14,
|
||||
0xeb, 0xf5, 0x4b, 0xe8, 0xdc, 0xbe, 0xd2, 0xe8, 0x33, 0x6d, 0x89, 0x33,
|
||||
0x03, 0x4e, 0xe0, 0x4f, 0xfd, 0x94, 0x3d, 0xc9, 0xe8, 0x54, 0xe5, 0x82,
|
||||
0xa7, 0x93, 0xb0, 0x98, 0x81, 0xd4, 0xf7, 0x7d, 0xbc, 0x20, 0xa0, 0x49,
|
||||
0x99, 0x4c, 0xd9, 0x0b, 0x83, 0x4c, 0xe6, 0x98, 0xc3, 0xba, 0xc9, 0x3e,
|
||||
0x61, 0xec, 0xc4, 0x54, 0x6e, 0x97, 0xad, 0x48, 0x9c, 0x83, 0xc1, 0x28,
|
||||
0xb5, 0xb9, 0x73, 0x8b, 0x00, 0xd6, 0xda, 0x64, 0x3c, 0x1e, 0xd3, 0x19,
|
||||
0xf8, 0x54, 0x5a, 0x65, 0xa2, 0x44, 0x93, 0xe9, 0xe9, 0x4b, 0x08, 0xc8,
|
||||
0x8d, 0x45, 0x1b, 0x87, 0xce, 0x2d, 0xb9, 0x99, 0x6e, 0xcb, 0x2f, 0x99,
|
||||
0x95, 0x44, 0x19, 0x86, 0xe3, 0xc4, 0x13, 0xd2, 0x5b, 0x5a, 0x82, 0x54,
|
||||
0x08, 0x81, 0x0c, 0x1a, 0x94, 0x4a, 0x3e, 0xa3, 0x44, 0x13, 0x25, 0x9a,
|
||||
0x54, 0x19, 0x94, 0xb6, 0x2f, 0x1d, 0x6c, 0x99, 0xe2, 0x2c, 0xe7, 0x34,
|
||||
0xd1, 0x3e, 0xe7, 0x94, 0x20, 0x8e, 0xe3, 0x53, 0x7a, 0x51, 0x85, 0x4a,
|
||||
0x0d, 0x1e, 0x9f, 0xc4, 0x0c, 0x63, 0xbd, 0xd4, 0xd1, 0xbf, 0x91, 0x00,
|
||||
0x86, 0xb1, 0x42, 0x19, 0x53, 0xfa, 0xf5, 0xea, 0x5e, 0x2f, 0x7c, 0xfc,
|
||||
0x0c, 0x80, 0xb5, 0x36, 0x06, 0x81, 0xf5, 0x6b, 0x64, 0xb9, 0x25, 0x8a,
|
||||
0x35, 0x4a, 0x5f, 0x3c, 0x0f, 0x5e, 0x54, 0x52, 0x0a, 0x4e, 0x86, 0x09,
|
||||
0x52, 0xc8, 0x38, 0xdc, 0xdd, 0x9d, 0x3b, 0x3e, 0x33, 0x07, 0x92, 0x24,
|
||||
0x21, 0x4a, 0x0c, 0x57, 0xaf, 0x04, 0x54, 0x03, 0x8f, 0xa0, 0xbc, 0xba,
|
||||
0x6f, 0x56, 0x29, 0x04, 0x69, 0xaa, 0x90, 0x52, 0x0c, 0x8b, 0xfd, 0xc5,
|
||||
0x12, 0x24, 0x00, 0x9a, 0x12, 0xeb, 0x55, 0x8f, 0x7a, 0x69, 0x15, 0x55,
|
||||
0x7f, 0x2a, 0xe7, 0x1c, 0xfd, 0x24, 0x41, 0x58, 0x3b, 0x58, 0x00, 0x68,
|
||||
0xb7, 0xdb, 0x62, 0x67, 0x67, 0xc7, 0x44, 0xb1, 0x62, 0x70, 0xea, 0xf8,
|
||||
0xf3, 0x64, 0x88, 0xec, 0xbe, 0xe2, 0x0e, 0xb8, 0x44, 0xc7, 0xc7, 0x43,
|
||||
0x9b, 0x24, 0xc3, 0x07, 0xbb, 0xed, 0x76, 0x2d, 0x0c, 0xc3, 0x78, 0x0e,
|
||||
0x00, 0x78, 0x51, 0x14, 0xfd, 0xf4, 0xcb, 0xa1, 0xf8, 0x4c, 0xad, 0xe3,
|
||||
0xff, 0xb6, 0xff, 0xca, 0x1f, 0xbb, 0x67, 0xe4, 0xc0, 0x02, 0x58, 0x9d,
|
||||
0x1e, 0x1c, 0x3f, 0xf8, 0xe1, 0xeb, 0xe2, 0x35, 0x31, 0xfb, 0x35, 0x6b,
|
||||
0xb7, 0xdb, 0xcd, 0x60, 0x6d, 0xed, 0xcd, 0x5a, 0xd0, 0xda, 0x74, 0x38,
|
||||
0x29, 0x85, 0x5c, 0xc9, 0x12, 0xc8, 0x8d, 0x31, 0x2a, 0x4b, 0x6b, 0xb9,
|
||||
0x10, 0x79, 0x3e, 0x3a, 0xe9, 0x01, 0x7f, 0x85, 0x61, 0xd8, 0x5b, 0x00,
|
||||
0x98, 0x42, 0x48, 0x20, 0x60, 0x92, 0x99, 0x7c, 0x15, 0x00, 0x53, 0x05,
|
||||
0x40, 0x0a, 0xa8, 0x30, 0x0c, 0xcf, 0x2c, 0x2d, 0xf1, 0xff, 0xcf, 0xe9,
|
||||
0xeb, 0x06, 0xf8, 0x07, 0xbc, 0x32, 0xa5, 0x97, 0xcb, 0x08, 0x37, 0x2b,
|
||||
0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
|
||||
}
|
||||
|
||||
var __32x32_mimetypes_x_office_spreadsheet_png = []byte{
|
||||
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
|
||||
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20,
|
||||
0x08, 0x06, 0x00, 0x00, 0x00, 0x73, 0x7a, 0x7a, 0xf4, 0x00, 0x00, 0x00,
|
||||
0x04, 0x73, 0x42, 0x49, 0x54, 0x08, 0x08, 0x08, 0x08, 0x7c, 0x08, 0x64,
|
||||
0x88, 0x00, 0x00, 0x05, 0xa5, 0x49, 0x44, 0x41, 0x54, 0x58, 0x85, 0xe5,
|
||||
0x97, 0xcd, 0x6b, 0x54, 0x57, 0x18, 0xc6, 0x7f, 0xe7, 0xde, 0x3b, 0x93,
|
||||
0x51, 0x67, 0x92, 0x8c, 0x66, 0x32, 0x6a, 0x26, 0x4d, 0x94, 0x18, 0x27,
|
||||
0xa9, 0x50, 0xbf, 0x5a, 0x2d, 0x94, 0x76, 0xd1, 0x85, 0x05, 0x41, 0x8b,
|
||||
0x60, 0x75, 0x53, 0x70, 0x21, 0x0a, 0x55, 0xba, 0x71, 0x51, 0xa8, 0xb4,
|
||||
0x7f, 0x40, 0xd7, 0xd5, 0x45, 0xa5, 0x90, 0x6e, 0x5a, 0xa8, 0x20, 0x34,
|
||||
0x15, 0x0a, 0x6d, 0xa1, 0x25, 0x58, 0x2c, 0xa4, 0xd6, 0x42, 0x4d, 0xa2,
|
||||
0xf9, 0x20, 0xc9, 0xc4, 0x64, 0x3e, 0x4c, 0xa2, 0x13, 0x93, 0xcc, 0xdc,
|
||||
0xef, 0x2e, 0x32, 0xf7, 0xf4, 0xde, 0xc9, 0x97, 0x59, 0x74, 0xd5, 0x03,
|
||||
0x87, 0x7b, 0xde, 0xf3, 0x9e, 0x73, 0xde, 0xe7, 0x3c, 0xef, 0xf3, 0xde,
|
||||
0xb9, 0x03, 0xff, 0xf7, 0x26, 0xaa, 0x27, 0xae, 0x5d, 0xbb, 0x76, 0x4a,
|
||||
0xd3, 0xb4, 0x2e, 0x20, 0xa6, 0xaa, 0x2a, 0x8a, 0xa2, 0xe0, 0x38, 0x0e,
|
||||
0xb6, 0x6d, 0xcb, 0x6e, 0x59, 0x96, 0x7c, 0xfa, 0xc7, 0x6b, 0xcd, 0x01,
|
||||
0xcf, 0x2d, 0xcb, 0x3a, 0xd7, 0xd5, 0xd5, 0x75, 0xcb, 0x1f, 0x4f, 0x5b,
|
||||
0x86, 0x48, 0x88, 0x2f, 0xcf, 0x9f, 0x3f, 0x1f, 0xab, 0x8c, 0xe5, 0xbc,
|
||||
0xeb, 0xba, 0x81, 0x75, 0x7e, 0xfb, 0x05, 0xc7, 0xb1, 0x4b, 0x97, 0x2e,
|
||||
0x7d, 0x09, 0xac, 0x0d, 0xc0, 0xb6, 0xed, 0x7a, 0x80, 0xd1, 0xd1, 0x51,
|
||||
0x84, 0x10, 0x12, 0x84, 0xff, 0xe9, 0x07, 0xe6, 0x1f, 0xaf, 0x64, 0x7b,
|
||||
0x40, 0x1a, 0x1b, 0x1b, 0x31, 0x0c, 0xa3, 0xbe, 0xda, 0xb7, 0x0c, 0x80,
|
||||
0xeb, 0xba, 0x32, 0xc8, 0x8d, 0x1b, 0x37, 0x48, 0x24, 0x12, 0x81, 0xa0,
|
||||
0xf9, 0x7c, 0x9e, 0x64, 0x32, 0x29, 0xed, 0x42, 0xa1, 0x40, 0x32, 0x99,
|
||||
0x94, 0xfb, 0x0b, 0x85, 0x02, 0x8d, 0x8d, 0x8d, 0xd2, 0x9e, 0x9e, 0x9e,
|
||||
0xe6, 0xc2, 0x85, 0x0b, 0xcb, 0x40, 0xad, 0x0a, 0xc0, 0x71, 0x1c, 0x79,
|
||||
0x93, 0x54, 0x2a, 0x45, 0x2a, 0x95, 0x0a, 0xdc, 0xaa, 0xa6, 0xa6, 0x26,
|
||||
0x30, 0x17, 0x89, 0x44, 0x48, 0xa5, 0x52, 0x72, 0x8f, 0xdf, 0xf6, 0xfc,
|
||||
0xde, 0xda, 0xea, 0x34, 0xae, 0x0a, 0xc0, 0x63, 0xc1, 0x3b, 0xb4, 0xfa,
|
||||
0x59, 0x9d, 0x9a, 0x95, 0x68, 0xf7, 0xb7, 0xb5, 0xfc, 0x2b, 0x69, 0x40,
|
||||
0x8e, 0xa7, 0xa6, 0xa6, 0xb0, 0x6d, 0x3b, 0x10, 0xac, 0x50, 0x28, 0x04,
|
||||
0xd6, 0xe4, 0xf3, 0x79, 0x4c, 0xd3, 0x94, 0xfe, 0x7c, 0x3e, 0xef, 0xa9,
|
||||
0x5e, 0xfa, 0x37, 0xcc, 0x80, 0x77, 0xd8, 0xce, 0x9d, 0x3b, 0x03, 0xf4,
|
||||
0x0a, 0x21, 0xd0, 0x34, 0x8d, 0xe3, 0xc7, 0x8f, 0xcb, 0x43, 0x47, 0x46,
|
||||
0x46, 0x68, 0x6b, 0x6b, 0x93, 0x6b, 0x86, 0x87, 0x87, 0xa5, 0x0d, 0x30,
|
||||
0x3c, 0x3c, 0x8c, 0xa2, 0x28, 0x2f, 0x0e, 0xc0, 0xbb, 0x9d, 0x10, 0x82,
|
||||
0x6c, 0x36, 0x8b, 0xe3, 0x38, 0xcb, 0x44, 0x38, 0x34, 0x34, 0x24, 0xd7,
|
||||
0x4f, 0x4c, 0x4c, 0x04, 0x40, 0x67, 0x32, 0x19, 0x69, 0x03, 0x64, 0x32,
|
||||
0x19, 0xd2, 0xe9, 0xf4, 0xc6, 0x01, 0x00, 0xec, 0xd8, 0xb1, 0x83, 0xe6,
|
||||
0xe6, 0xe6, 0x00, 0x00, 0x55, 0x55, 0xd9, 0xbb, 0x77, 0xaf, 0xb4, 0x43,
|
||||
0xa1, 0x50, 0x80, 0x01, 0x4d, 0xd3, 0x02, 0x0c, 0x68, 0x9a, 0xb6, 0xb1,
|
||||
0x14, 0xd8, 0xb6, 0x2d, 0x45, 0x98, 0xcb, 0xe5, 0x96, 0x6d, 0x28, 0x14,
|
||||
0x0a, 0x0c, 0x0e, 0x0e, 0xca, 0x80, 0xff, 0x19, 0x03, 0x9e, 0x06, 0x9a,
|
||||
0x9b, 0x9b, 0xa5, 0x4f, 0x08, 0x41, 0x28, 0x14, 0x92, 0x07, 0x7a, 0xb6,
|
||||
0x9f, 0x01, 0x55, 0x55, 0xd9, 0xb3, 0x67, 0x8f, 0xdc, 0xa3, 0xaa, 0xaa,
|
||||
0x64, 0xc0, 0x0f, 0x6c, 0x5d, 0x00, 0x80, 0xd4, 0x80, 0x77, 0xb8, 0xa7,
|
||||
0xf2, 0x47, 0x8f, 0x1e, 0x49, 0x3b, 0x93, 0xc9, 0x04, 0x40, 0xaf, 0xc4,
|
||||
0x40, 0x47, 0x47, 0xc7, 0xc6, 0x18, 0xf0, 0x52, 0xe0, 0x55, 0x81, 0xbf,
|
||||
0xf6, 0x55, 0x55, 0x25, 0x9d, 0x4e, 0x07, 0xec, 0xf6, 0xf6, 0x76, 0x09,
|
||||
0x40, 0xd3, 0xb4, 0x00, 0x03, 0x7e, 0x0d, 0xbc, 0x10, 0x03, 0xfe, 0x1a,
|
||||
0xf6, 0xbf, 0x07, 0xbc, 0x9e, 0xcb, 0xe5, 0x02, 0x1a, 0x18, 0x1f, 0x1b,
|
||||
0x23, 0x7b, 0xef, 0x1e, 0xf9, 0xfb, 0xf7, 0x79, 0x36, 0x32, 0xc2, 0x7c,
|
||||
0x2e, 0x47, 0x44, 0x51, 0x70, 0x34, 0x8d, 0xf0, 0xd6, 0xad, 0x28, 0x89,
|
||||
0x04, 0xe1, 0x73, 0xe7, 0xe8, 0x7c, 0xfb, 0xed, 0x8d, 0xa5, 0xc0, 0xaf,
|
||||
0x81, 0xea, 0x2a, 0x48, 0xa7, 0xd3, 0xd8, 0xa6, 0x49, 0xef, 0x57, 0x5f,
|
||||
0x31, 0x70, 0xfd, 0x3a, 0xfb, 0x1a, 0x1a, 0x78, 0x6b, 0xd7, 0x2e, 0x5a,
|
||||
0x3a, 0x3b, 0x89, 0x1d, 0x3a, 0x84, 0xeb, 0x38, 0x38, 0xa6, 0x49, 0x71,
|
||||
0x6e, 0x8e, 0xb1, 0x5c, 0x8e, 0xbe, 0x2b, 0x57, 0xe8, 0x2e, 0x97, 0x89,
|
||||
0xd4, 0xd5, 0x71, 0x1a, 0xc2, 0x37, 0xc1, 0x58, 0x17, 0x80, 0xc7, 0xc0,
|
||||
0x4a, 0xef, 0x81, 0x3f, 0x7b, 0x7a, 0xb8, 0xf3, 0xc9, 0x27, 0x1c, 0x88,
|
||||
0x46, 0xb9, 0x7a, 0xf2, 0x24, 0x9b, 0xc3, 0x61, 0x60, 0x29, 0xc7, 0xb6,
|
||||
0x61, 0xe0, 0x98, 0x26, 0x8e, 0x69, 0x12, 0x11, 0x82, 0xf6, 0x86, 0x06,
|
||||
0xda, 0xea, 0xea, 0x58, 0x58, 0x5c, 0xe4, 0xa7, 0x81, 0x01, 0xee, 0xc2,
|
||||
0x6f, 0x1f, 0xc0, 0xa9, 0xeb, 0x30, 0xb1, 0x2a, 0x00, 0x4f, 0x03, 0x4d,
|
||||
0x4d, 0x4d, 0x92, 0x01, 0x8f, 0x15, 0x61, 0x9a, 0xdc, 0xbd, 0x7a, 0x95,
|
||||
0x77, 0x3b, 0x3b, 0x39, 0xdc, 0xd6, 0x06, 0x95, 0x94, 0x39, 0xa6, 0x89,
|
||||
0x6d, 0x59, 0x32, 0xb8, 0x63, 0x9a, 0xd8, 0xbe, 0x71, 0xc8, 0xb2, 0x78,
|
||||
0xa7, 0xb5, 0x95, 0x44, 0x28, 0x74, 0xf8, 0xbb, 0xe1, 0xe1, 0x1f, 0x2f,
|
||||
0xc0, 0xd1, 0x2f, 0xa0, 0xb8, 0xae, 0x06, 0x3c, 0x06, 0x3c, 0x00, 0x43,
|
||||
0x37, 0x6f, 0x72, 0x28, 0x1e, 0xe7, 0x70, 0x47, 0x07, 0xe8, 0x3a, 0x8e,
|
||||
0x6d, 0xaf, 0x18, 0x30, 0x30, 0xe7, 0x03, 0xb6, 0x2f, 0x1e, 0x67, 0x68,
|
||||
0xdb, 0xb6, 0x74, 0xff, 0xcc, 0xcc, 0x47, 0xc0, 0xc7, 0x6b, 0xa6, 0xa0,
|
||||
0x5a, 0x03, 0x42, 0x08, 0xfa, 0x1f, 0x3e, 0xe4, 0xf5, 0xb3, 0x67, 0x71,
|
||||
0x2d, 0x0b, 0xbb, 0x54, 0x5a, 0x37, 0xe0, 0x32, 0x50, 0xae, 0xcb, 0x81,
|
||||
0x44, 0x82, 0xbe, 0x99, 0x99, 0xb3, 0xab, 0x02, 0xf0, 0xea, 0x75, 0x6a,
|
||||
0x6a, 0x4a, 0x8e, 0x3d, 0x16, 0xf4, 0xe9, 0x69, 0x6a, 0x6b, 0x6b, 0xb1,
|
||||
0x9e, 0x3e, 0xc5, 0x29, 0x95, 0xfe, 0x0d, 0x50, 0x09, 0x6a, 0x9b, 0x26,
|
||||
0xe3, 0x6f, 0xec, 0xe7, 0xdb, 0xf2, 0x6b, 0xe0, 0xba, 0x98, 0x86, 0x81,
|
||||
0x6e, 0x18, 0x58, 0x86, 0x81, 0x6e, 0x98, 0x38, 0xb6, 0x49, 0xab, 0x53,
|
||||
0x44, 0x3c, 0xfc, 0xf4, 0x25, 0x40, 0x59, 0x93, 0x01, 0x4f, 0x03, 0x32,
|
||||
0xff, 0x42, 0x70, 0xdf, 0x75, 0x79, 0x92, 0xcd, 0xb2, 0x79, 0xcb, 0x16,
|
||||
0xec, 0x72, 0x79, 0xd9, 0x0d, 0x6d, 0xd7, 0xa6, 0xfc, 0x7e, 0x03, 0xdb,
|
||||
0xba, 0x8b, 0xfc, 0xb9, 0x98, 0x46, 0xd7, 0x0d, 0x0c, 0x43, 0x47, 0xd7,
|
||||
0x0d, 0x74, 0xdd, 0x20, 0xa2, 0xb9, 0x44, 0x67, 0x17, 0x01, 0x54, 0x20,
|
||||
0xba, 0xaa, 0x06, 0x84, 0x10, 0x4c, 0x4e, 0x4e, 0x06, 0xde, 0x84, 0x00,
|
||||
0x4a, 0x2c, 0x46, 0x6f, 0x4f, 0x0f, 0xc9, 0x33, 0x67, 0x50, 0x66, 0x66,
|
||||
0xb0, 0xab, 0x58, 0x28, 0xbe, 0x12, 0x27, 0x17, 0xce, 0xb2, 0x6b, 0xff,
|
||||
0xdf, 0xf4, 0xf6, 0xec, 0xc2, 0xb2, 0xec, 0x4a, 0xb7, 0x08, 0x6b, 0xd0,
|
||||
0x1e, 0xd1, 0xc9, 0x67, 0xc6, 0x88, 0x43, 0x1e, 0x88, 0x28, 0x6b, 0xa5,
|
||||
0xa0, 0xa9, 0xa9, 0x89, 0x96, 0x96, 0x16, 0xd9, 0x5b, 0x5b, 0x5b, 0xa9,
|
||||
0xdd, 0xb7, 0x8f, 0x81, 0xc9, 0x49, 0xfe, 0xf8, 0xe1, 0x07, 0x4a, 0xc9,
|
||||
0x24, 0x6e, 0x32, 0xb9, 0xf4, 0xe9, 0x5d, 0x2e, 0x63, 0x95, 0x4a, 0x64,
|
||||
0x0e, 0x0b, 0xa6, 0xf5, 0x31, 0x1e, 0xef, 0x78, 0xcc, 0x2b, 0xa1, 0xaf,
|
||||
0x97, 0x2e, 0xe4, 0xda, 0x6c, 0x8f, 0x6f, 0xe2, 0xb5, 0x58, 0x99, 0xc1,
|
||||
0xc7, 0x05, 0xe2, 0x7d, 0x3f, 0x53, 0x82, 0x3b, 0x80, 0xbd, 0x6a, 0x0a,
|
||||
0x5c, 0xd7, 0x5d, 0x91, 0x01, 0xf7, 0xe0, 0x41, 0x9e, 0xf7, 0xf5, 0x71,
|
||||
0x6f, 0x74, 0x94, 0x85, 0x62, 0x91, 0x9d, 0xbb, 0x77, 0xb3, 0xbd, 0xad,
|
||||
0x8d, 0x70, 0xa9, 0x84, 0x3d, 0x37, 0x4b, 0xf6, 0x48, 0x84, 0xb2, 0x55,
|
||||
0xc4, 0xb2, 0x75, 0xa2, 0x6f, 0x3d, 0xe0, 0xe5, 0xde, 0xd3, 0xd4, 0x1b,
|
||||
0x30, 0x3d, 0xd8, 0x47, 0xcf, 0xa2, 0xc6, 0x9e, 0xde, 0x6e, 0xc8, 0x8f,
|
||||
0x3c, 0xfb, 0x0b, 0x3e, 0x07, 0x16, 0xd6, 0x2c, 0xc3, 0x8b, 0x17, 0x2f,
|
||||
0x06, 0x2a, 0x40, 0x08, 0x81, 0x7d, 0xe2, 0x04, 0x03, 0x6f, 0xbe, 0xc9,
|
||||
0xed, 0xcb, 0x97, 0x79, 0x9e, 0xcf, 0xb3, 0xd7, 0x30, 0x98, 0xe8, 0xef,
|
||||
0x67, 0x53, 0x34, 0x8a, 0x95, 0x8e, 0x32, 0xf7, 0x57, 0x8c, 0x90, 0x02,
|
||||
0xb8, 0xb0, 0x50, 0xd6, 0x31, 0xfe, 0xee, 0xe2, 0xfb, 0x85, 0xa3, 0x34,
|
||||
0x17, 0xb3, 0xec, 0xef, 0xbd, 0x85, 0x9e, 0x1b, 0x99, 0x7e, 0x00, 0x1f,
|
||||
0x3e, 0x80, 0x7e, 0xa0, 0x5c, 0x0d, 0x20, 0xa6, 0x28, 0xca, 0x9c, 0xeb,
|
||||
0xba, 0xb5, 0xf5, 0xf5, 0xff, 0x7e, 0xc2, 0x57, 0x7f, 0x54, 0x1e, 0x3a,
|
||||
0x76, 0x8c, 0x86, 0xdb, 0xb7, 0xf9, 0xe5, 0xb3, 0xcf, 0xf8, 0xbd, 0xbb,
|
||||
0x9b, 0x58, 0x28, 0x44, 0x52, 0xd7, 0x51, 0x1f, 0x2f, 0x30, 0x3e, 0xfe,
|
||||
0x0c, 0x67, 0x93, 0xc0, 0x71, 0x5d, 0x42, 0x33, 0x16, 0x4a, 0x5f, 0x3f,
|
||||
0xaf, 0x3e, 0xfc, 0x15, 0xa7, 0x58, 0xa0, 0xb0, 0x75, 0xab, 0x75, 0x07,
|
||||
0x2e, 0x66, 0xa1, 0x17, 0x78, 0x02, 0xc1, 0xbf, 0x66, 0x31, 0x20, 0x71,
|
||||
0xe4, 0xc8, 0x91, 0xf7, 0x5a, 0x5a, 0x5a, 0x3e, 0x05, 0x36, 0x55, 0xb3,
|
||||
0xe3, 0x6f, 0xae, 0xeb, 0x62, 0x18, 0x06, 0xd6, 0xfc, 0x3c, 0x35, 0x93,
|
||||
0x93, 0x44, 0x66, 0x67, 0xa9, 0x99, 0x9f, 0x47, 0x33, 0x0c, 0x14, 0xcb,
|
||||
0xc2, 0xd1, 0x34, 0xcc, 0x50, 0x08, 0x3d, 0x1a, 0xa5, 0x14, 0x8f, 0x53,
|
||||
0xda, 0xbe, 0xdd, 0x18, 0xcd, 0xe5, 0xbe, 0x18, 0x1c, 0x1c, 0xfc, 0x06,
|
||||
0x18, 0x07, 0xb2, 0x80, 0xe3, 0x07, 0x10, 0x06, 0x92, 0xc0, 0x36, 0xa0,
|
||||
0x1e, 0xa8, 0x61, 0xa9, 0x54, 0xd6, 0x6b, 0x0a, 0x10, 0x01, 0x36, 0x57,
|
||||
0xf6, 0x68, 0x95, 0x39, 0x00, 0x87, 0xa5, 0x1f, 0x9e, 0xa7, 0x80, 0x0e,
|
||||
0x3c, 0x03, 0x66, 0x59, 0xfa, 0x1d, 0x98, 0xaf, 0x66, 0xc0, 0x6b, 0x35,
|
||||
0x95, 0x03, 0xc3, 0xab, 0xf8, 0xd7, 0x6b, 0xa2, 0xd2, 0xbd, 0xaf, 0x0f,
|
||||
0xb7, 0x02, 0xc4, 0xac, 0x80, 0xd0, 0x7d, 0x3e, 0xfe, 0x01, 0x9f, 0x1e,
|
||||
0x98, 0x64, 0x1e, 0x77, 0xb2, 0x47, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45,
|
||||
0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
// 30 august 2014
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"image"
|
||||
"reflect"
|
||||
"github.com/andlabs/ui"
|
||||
)
|
||||
|
||||
var w ui.Window
|
||||
|
||||
type areaHandler struct {
|
||||
img *image.RGBA
|
||||
}
|
||||
|
||||
func (a *areaHandler) Paint(rect image.Rectangle) *image.RGBA {
|
||||
return a.img.SubImage(rect).(*image.RGBA)
|
||||
}
|
||||
|
||||
func (a *areaHandler) Mouse(me ui.MouseEvent) {}
|
||||
func (a *areaHandler) Key(ke ui.KeyEvent) bool { return false }
|
||||
|
||||
func initGUI() {
|
||||
b := ui.NewButton("Button")
|
||||
c := ui.NewCheckbox("Checkbox")
|
||||
tf := ui.NewTextField()
|
||||
tf.SetText("Text Field")
|
||||
pf := ui.NewPasswordField()
|
||||
pf.SetText("Password Field")
|
||||
l := ui.NewLabel("Label")
|
||||
|
||||
t := ui.NewTab()
|
||||
t.Append("Tab 1", ui.Space())
|
||||
t.Append("Tab 2", ui.Space())
|
||||
t.Append("Tab 3", ui.Space())
|
||||
|
||||
g := ui.NewGroup("Group", ui.Space())
|
||||
|
||||
icons := readIcons()
|
||||
table := ui.NewTable(reflect.TypeOf(icons[0]))
|
||||
table.Lock()
|
||||
d := table.Data().(*[]icon)
|
||||
*d = icons
|
||||
table.Unlock()
|
||||
|
||||
area := ui.NewArea(200, 200, &areaHandler{tileImage(20)})
|
||||
|
||||
stack := ui.NewVerticalStack(
|
||||
b,
|
||||
c,
|
||||
tf,
|
||||
pf,
|
||||
l,
|
||||
t,
|
||||
g,
|
||||
table,
|
||||
area)
|
||||
stack.SetStretchy(5)
|
||||
stack.SetStretchy(6)
|
||||
stack.SetStretchy(7)
|
||||
stack.SetStretchy(8)
|
||||
|
||||
w = ui.NewWindow("Window", 400, 500, stack)
|
||||
w.OnClosing(func() bool {
|
||||
ui.Stop()
|
||||
return true
|
||||
})
|
||||
w.Show()
|
||||
}
|
||||
|
||||
func main() {
|
||||
go ui.Do(initGUI)
|
||||
err := ui.Go()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 36 KiB |
159
prev/future
159
prev/future
|
@ -1,159 +0,0 @@
|
|||
another TODO: capture support in Area
|
||||
another TODO: intptr_t -> intmax_t everywhere
|
||||
another TODO: https://msdn.microsoft.com/en-us/library/dd162593%28v=vs.85%29.aspx?
|
||||
another TODO: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646275%28v=vs.85%29.aspx ?
|
||||
another TODO: use MapWindowRect() instead of ScreenToClient()?
|
||||
|
||||
mac os x
|
||||
- investigate [NSWindow allowsToolTipsWhenApplicationIsInactive] to see what we should set it to
|
||||
|
||||
Table
|
||||
- with our custom Windows Table we no longer really need image lists...
|
||||
- TODO what about icon views with groups?
|
||||
|
||||
windows
|
||||
- WM_WININICHANGED, etc. (TODO get a full list) causing size recomputations
|
||||
- check +, =, and - key handling for checkboxes
|
||||
- Area and sharedWndProc(): http://blogs.msdn.com/b/oldnewthing/archive/2006/01/03/508694.aspx
|
||||
- probably not necessary but http://blogs.msdn.com/b/aaron_margosis/archive/2006/08/07/luabuglight.aspx
|
||||
- handle WM_PRINTCLIENT in Area
|
||||
- merge uses of SetScrollInfo() in Area to avoid potential weirdness
|
||||
|
||||
C interop
|
||||
- substitute intmax_t and uintmax_t for all uses of intptr_t and uintptr_t
|
||||
|
||||
mac os x sizing is now completely broken
|
||||
- need to catch window initial sizes?
|
||||
- unrelated: https://files.slack.com/files-pri/T029RQSE6-F0321TX9B/pasted_image_at_2014_11_23_12_35_pm.png com.apple.main-thread
|
||||
|
||||
password fields: caps lock warnings
|
||||
|
||||
tab on windows
|
||||
- windows xp: weird redraw issues on the bottom right corner of the first tab
|
||||
tab, group, windows on windows
|
||||
- all platforms: only resize when the size changed
|
||||
- all platforms: redrawing issue on resizes
|
||||
|
||||
mac os x
|
||||
Textbox
|
||||
refuse first responder
|
||||
Textbox does not work at all
|
||||
sizing
|
||||
things can get cut off (top of NSStepper) with alignment rect based positioning...
|
||||
|
||||
new control stuff
|
||||
Tab, Group
|
||||
- Tab needs Margined
|
||||
|
||||
more flexible sizing determination
|
||||
textfield should have a method that allows control over the preferred number of characters, for sizing purposes
|
||||
|
||||
Multiline text fields and standalone labels
|
||||
for the Form layout
|
||||
|
||||
Group
|
||||
Mac OS X: NSBox
|
||||
container_darwin.m: figure out if our setFrameSize: thing applies to resizing the groupbox as well
|
||||
|
||||
Control
|
||||
Enable()
|
||||
Disable()
|
||||
|
||||
Table
|
||||
refresh only selected rows of view to avoid flicker (especially on Windows)
|
||||
add functions for header manipulation
|
||||
background color
|
||||
column titles
|
||||
|
||||
Tab
|
||||
// [TODO if each tab of your Tab is going to have the same content Controls, then use LikeTab instead, to conserve resources]
|
||||
// probably not a good idea
|
||||
Delete()
|
||||
// Delete removes the given tab.
|
||||
// It panics if index is out of range.
|
||||
// After Delete(), the effect of accessing the Control of the deleted tab or any of its children is undefned. [TODO reword?]
|
||||
investigate close buttons (especially for LikeTab)
|
||||
LikeTab is probably going to have to be a custom control
|
||||
definitely on OS X?
|
||||
|
||||
Area
|
||||
keyboard scrolling
|
||||
OpenTextAreaAt() to allow editing of text blocks from within Areas (only after I add TextArea; it will use TextAreas themselves, rather than providing its own)
|
||||
OpenTextFieldAt(): more platform-specific finished editing options; escape key for cancelling too?
|
||||
bounds checking in Select()
|
||||
DPI()
|
||||
|
||||
Tree
|
||||
Mac OS X: make sure newScrollView() has the correct parameters for Table and Tree (and that Area has the appropriate ones from both)
|
||||
|
||||
TextArea
|
||||
Mac OS X: be sure to call disableAutocorrect()
|
||||
|
||||
Mac OS X
|
||||
label alignment
|
||||
// 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
|
||||
other controls
|
||||
same rules? checkboxes seem damning...
|
||||
ok in general
|
||||
spacing applies to alignment rects
|
||||
will conflict with Stack/Grid precalculation
|
||||
unless we resize the control and move it around to suit
|
||||
Tabs seem to have differnt margin rules
|
||||
need to check padding rules within Tabs
|
||||
text alignment (think labels and checkboxes) may be a perpetual problem though
|
||||
activateIgnoringOtherApps: and command line programs: evaluate just how much it will matter
|
||||
http://stackoverflow.com/a/25318870/3408572
|
||||
use the undocumented _CFRunLoopSetCurrent() API to drop the stupid "must run on the man thread" restriction
|
||||
|
||||
TextField
|
||||
figure out numerics because you CAN paste into numeric boxes on Windows and GTK+ has no built-in number validator as far as I know
|
||||
will likely just use Invalid()
|
||||
|
||||
Stack, SimpleGrid, Grid
|
||||
method calls should trigger re-layout of windows
|
||||
default buttons
|
||||
figure out how they interact with Areas (especially on GTK+, where GtkEntry somehow has special handling for this)
|
||||
|
||||
in general
|
||||
new moving stack and future moving heap make package ui a pain in general
|
||||
issue 8310: watch for changes
|
||||
Combobox/Listbox
|
||||
Select() for selecting a row, like in Table
|
||||
|
||||
dialog boxes
|
||||
find out if Stop has an effect before a dialog box function returns
|
||||
our solution for common dialogs is rather heavyweight and doesn't work for user-created dialogs
|
||||
foreign events are a problem
|
||||
do what raymond chen suggested
|
||||
|
||||
so I don't forget, some TODOs:
|
||||
windows
|
||||
- Area isn't transparent
|
||||
- hard: has to be excluded from scrolling
|
||||
- tab order is backwards
|
||||
- extra space on first column of all Tables on real Windows
|
||||
- fine-tune Table checkbox behavior (especially with regards to selection)
|
||||
- open dialog, resize small window, hscroll size area, double click file -> stack points in no man's land (yay attached threads?)
|
||||
- uitask pointer bug again?
|
||||
- groupbox line behind groupbox text
|
||||
- scrolling edit controls in Areas back into view don't cause redraw
|
||||
- need to figure out the WM_MOUSELEAVE tango (see mergeback/table_mouseleave_windows.c)
|
||||
- won't be an issue once I rewrite Table completely
|
||||
- tell wine that WM_CTLCOLOR*** doesn't get sent early ?????
|
||||
- definitely tell wine that the tab control responds to WM_PRINTCLIENT
|
||||
- don't let empty Tabs be tab stops
|
||||
gtk+
|
||||
- Area: figure out how Enter is processed in Entry
|
||||
https://git.gnome.org/browse/gtk+/tree/gtk/gtkwindow.c#n1229
|
||||
- Table: figure out how to allow empty selection by clicking in an empty area
|
||||
- After applying the AreaHandler.Key() change, -small crashes in 3.10/distribution but not 3.14/jhbuild
|
||||
- will need to install 14.04 somewhere for this
|
||||
mac os x
|
||||
- http://stackoverflow.com/questions/25558728/how-can-i-be-notified-when-a-control-becomes-key-i-want-t-o-have-my-custom-warn/25562783?noredirect=1#comment39949908_25562783
|
||||
gtk+, windows
|
||||
- Area: keyboard scrolling
|
||||
gtk+, mac os x
|
||||
- figure out how dialogs and Areas work together
|
||||
- once that is done, document the behavior of Areas
|
||||
all
|
||||
- rename Selected to Selection?
|
477
prev/grid.go
477
prev/grid.go
|
@ -1,477 +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
|
||||
parent *controlParent
|
||||
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{},
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
if g.parent != nil {
|
||||
control.setParent(g.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.parent = p
|
||||
for _, c := range g.controls {
|
||||
c.control.setParent(g.parent)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *grid) containerShow() {
|
||||
for _, c := range g.controls {
|
||||
c.control.containerShow()
|
||||
}
|
||||
}
|
||||
|
||||
func (g *grid) containerHide() {
|
||||
for _, c := range g.controls {
|
||||
c.control.containerHide()
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if len(g.controls) == 0 {
|
||||
// nothing to do
|
||||
return
|
||||
}
|
||||
|
||||
// -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
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
// 16 august 2014
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// #include "objc_darwin.h"
|
||||
import "C"
|
||||
|
||||
type group struct {
|
||||
*controlSingleObject
|
||||
|
||||
child Control
|
||||
container *container
|
||||
}
|
||||
|
||||
func newGroup(text string, control Control) Group {
|
||||
g := new(group)
|
||||
g.child = control
|
||||
g.container = newContainer(g.child.resize)
|
||||
g.child.setParent(g.container.parent())
|
||||
g.controlSingleObject = newControlSingleObject(C.newGroup(g.container.id))
|
||||
g.SetText(text)
|
||||
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.container.margined
|
||||
}
|
||||
|
||||
func (g *group) SetMargined(margined bool) {
|
||||
g.container.margined = margined
|
||||
}
|
||||
|
||||
// no need to override resize; the child container handles that for us
|
|
@ -1,79 +0,0 @@
|
|||
// +build !windows,!darwin
|
||||
|
||||
// 15 august 2014
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// #include "gtk_unix.h"
|
||||
import "C"
|
||||
|
||||
// TODO on which sides do margins get applied?
|
||||
|
||||
type group struct {
|
||||
*controlSingleWidget
|
||||
gcontainer *C.GtkContainer
|
||||
frame *C.GtkFrame
|
||||
|
||||
child Control
|
||||
container *container
|
||||
}
|
||||
|
||||
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.resize = g.child.resize
|
||||
C.gtk_container_add(g.gcontainer, g.container.widget)
|
||||
|
||||
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.container.margined
|
||||
}
|
||||
|
||||
func (g *group) SetMargined(margined bool) {
|
||||
g.container.margined = margined
|
||||
}
|
||||
|
||||
// no need to override resize; the child container handles that for us
|
|
@ -1,91 +0,0 @@
|
|||
// 15 august 2014
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// #include "winapi_windows.h"
|
||||
import "C"
|
||||
|
||||
type group struct {
|
||||
*controlSingleHWNDWithText
|
||||
child Control
|
||||
margined bool
|
||||
}
|
||||
|
||||
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.fnTabStops = control.nTabStops // groupbox itself is not tabbable but the contents might be
|
||||
g.SetText(text)
|
||||
C.controlSetControlFont(g.hwnd)
|
||||
C.setGroupSubclass(g.hwnd, unsafe.Pointer(g))
|
||||
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
|
||||
// 1 character cell (4DLU x, 8DLU y) on each side (but only 3DLU on the bottom) should be enough to make up for that; TODO is not, we can change it
|
||||
// TODO make these named constants
|
||||
marginRectDLU(&r, -8, -3, -4, -4, d)
|
||||
}
|
||||
return int(r.right - r.left), int(r.bottom - r.top)
|
||||
}
|
||||
|
||||
//export groupResized
|
||||
func groupResized(data unsafe.Pointer, r C.RECT) {
|
||||
g := (*group)(unsafe.Pointer(data))
|
||||
d := beginResize(g.hwnd)
|
||||
if g.margined {
|
||||
// see above
|
||||
marginRectDLU(&r, groupYMarginTop, groupYMarginBottom, groupXMargin, groupXMargin, d)
|
||||
} else {
|
||||
marginRectDLU(&r, 8, 3, 4, 4, d)
|
||||
}
|
||||
g.child.resize(int(r.left), int(r.top), int(r.right - r.left), int(r.bottom - r.top), d)
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
// 16 march 2014
|
||||
|
||||
#ifndef __GO_UI_GTK_UNIX_H__
|
||||
#define __GO_UI_GTK_UNIX_H__
|
||||
|
||||
/*
|
||||
MIN_REQUIRED signals that programs are expected to run on at least GLib 2.32/GTK+ 3.4 and thus deprectation warnings for newer versions (such as gtk_scrolled_window_add_with_viewport() being deprecated in GTK+ 3.8) should be suppressed.
|
||||
MAX_ALLOWED signals that programs will not use features introduced in newer versions of GLib/GTK+ and that the compiler should warn us if we slip.
|
||||
Thanks to desrt in irc.gimp.net/#gtk+
|
||||
*/
|
||||
|
||||
// GLib/GObject
|
||||
#define GLIB_VERSION_MIN_REQUIRED GLIB_VERSION_2_32
|
||||
#define GLIB_VERSION_MAX_ALLOWED GLIB_VERSION_2_32
|
||||
|
||||
// GDK/GTK+
|
||||
#define GDK_VERSION_MIN_REQUIRED GDK_VERSION_3_4
|
||||
#define GDK_VERSION_MAX_ALLOWED GDK_VERSION_3_4
|
||||
|
||||
// cairo has no such macros (thanks Company in irc.gimp.net/#gtk+)
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
// table_unix.c
|
||||
extern void tableAppendColumn(GtkTreeView *, gint, gchar *, GtkCellRenderer *, gchar *);
|
||||
typedef struct goTableModel goTableModel;
|
||||
typedef struct goTableModelClass goTableModelClass;
|
||||
struct goTableModel {
|
||||
GObject parent_instance;
|
||||
void *gotable;
|
||||
};
|
||||
struct goTableModelClass {
|
||||
GObjectClass parent_class;
|
||||
};
|
||||
extern goTableModel *newTableModel(void *);
|
||||
extern void tableUpdate(goTableModel *, gint, gint);
|
||||
|
||||
// container_unix.c
|
||||
extern GtkWidget *newContainer(void *);
|
||||
|
||||
#endif
|
|
@ -1,140 +0,0 @@
|
|||
// 9 january 2015
|
||||
#include "dtp.h"
|
||||
|
||||
/* notes:
|
||||
https://git.gnome.org/browse/gtk+/tree/gtk/gtkcombobox.c?h=gtk-3-4
|
||||
*/
|
||||
|
||||
#define GDTP(x) ((goDateTimePicker *) x)
|
||||
#define PRIV(x) (GDTP(x)->priv)
|
||||
#define GDTPC(x) ((goDateTimePickerClass *) x)
|
||||
|
||||
struct goDateTimePickerPrivate {
|
||||
gint year;
|
||||
gint month;
|
||||
gint day;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE(goDateTimePicker, goDateTimePicker, GTK_TYPE_BOX,
|
||||
;)
|
||||
|
||||
// TODO figure out how to share these between C and Go
|
||||
enum {
|
||||
gtkMargin = 12,
|
||||
gtkXPadding = 12,
|
||||
gtkYPadding = 6,
|
||||
};
|
||||
|
||||
static void goDateTimePicker_init(goDateTimePicker *dtp)
|
||||
{
|
||||
dtp->priv = G_TYPE_INSTANCE_GET_PRIVATE(dtp, goDateTimePicker_get_type(), goDateTimePickerPrivate);
|
||||
}
|
||||
|
||||
static void goDateTimePicker_dispose(GObject *obj)
|
||||
{
|
||||
goDateTimePickerPrivate *d = PRIV(obj);
|
||||
|
||||
// TODO really with g_clear_object()?
|
||||
G_OBJECT_CLASS(goDateTimePicker_parent_class)->dispose(obj);
|
||||
}
|
||||
|
||||
static void goDateTimePicker_finalize(GObject *obj)
|
||||
{
|
||||
G_OBJECT_CLASS(goDateTimePicker_parent_class)->finalize(obj);
|
||||
}
|
||||
|
||||
enum {
|
||||
pYear = 1,
|
||||
pMonth,
|
||||
pDay,
|
||||
nParams,
|
||||
};
|
||||
|
||||
static GParamSpec *gdtpParams[] = {
|
||||
NULL, // always null
|
||||
NULL, // year
|
||||
NULL, // month
|
||||
NULL, // day
|
||||
};
|
||||
|
||||
static void goDateTimePicker_set_property(GObject *obj, guint prop, const GValue *value, GParamSpec *spec)
|
||||
{
|
||||
goDateTimePickerPrivate *d = PRIV(obj);
|
||||
|
||||
switch (prop) {
|
||||
case pYear:
|
||||
d->year = g_value_get_int(value);
|
||||
break;
|
||||
case pMonth:
|
||||
d->month = g_value_get_int(value);
|
||||
break;
|
||||
case pDay:
|
||||
d->day = g_value_get_int(value);
|
||||
// see note on GtkCalendar comaptibility below
|
||||
if (d->day == 0)
|
||||
; // TODO
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop, spec);
|
||||
return;
|
||||
}
|
||||
// TODO refresh everything here
|
||||
}
|
||||
|
||||
static void goDateTimePicker_get_property(GObject *obj, guint prop, GValue *value, GParamSpec *spec)
|
||||
{
|
||||
goDateTimePickerPrivate *d = PRIV(obj);
|
||||
|
||||
switch (prop) {
|
||||
case pYear:
|
||||
g_value_set_int(value, d->year);
|
||||
break;
|
||||
case pMonth:
|
||||
g_value_set_int(value, d->month);
|
||||
break;
|
||||
case pDay:
|
||||
g_value_set_int(value, d->day);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop, spec);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void goDateTimePicker_class_init(goDateTimePickerClass *class)
|
||||
{
|
||||
g_type_class_add_private(class, sizeof (goDateTimePickerPrivate));
|
||||
|
||||
G_OBJECT_CLASS(class)->dispose = goDateTimePicker_dispose;
|
||||
G_OBJECT_CLASS(class)->finalize = goDateTimePicker_finalize;
|
||||
G_OBJECT_CLASS(class)->set_property = goDateTimePicker_set_property;
|
||||
G_OBJECT_CLASS(class)->get_property = goDateTimePicker_get_property;
|
||||
|
||||
// types and values are to be compatible with the 3.4 GtkCalendar parameters
|
||||
gdtpParams[pYear] = g_param_spec_int("year",
|
||||
"current year",
|
||||
"Current year",
|
||||
0,
|
||||
G_MAXINT >> 9,
|
||||
0,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||
gdtpParams[pMonth] = g_param_spec_uint("month",
|
||||
"current month",
|
||||
"Current month",
|
||||
0,
|
||||
11,
|
||||
0,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||
// because of the requirement to be compatible with GtkCalendar, we have to follow its rules about dates
|
||||
// values are 1..31 with 0 meaning no date selected
|
||||
// we will not allow no date to be selected, so we will set the default to 1 instead of 0
|
||||
// TODO is this an issue for binding?
|
||||
gdtpParams[pDay] = g_param_spec_uint("day",
|
||||
"current day",
|
||||
"Current day",
|
||||
0,
|
||||
31,
|
||||
1,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||
g_object_class_install_properties(G_OBJECT_CLASS(class), nParams, gdtpParams);
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
// 9 january 2015
|
||||
#define GLIB_VERSION_MIN_REQUIRED GLIB_VERSION_2_32
|
||||
#define GLIB_VERSION_MAX_ALLOWED GLIB_VERSION_2_32
|
||||
#define GDK_VERSION_MIN_REQUIRED GDK_VERSION_3_4
|
||||
#define GDK_VERSION_MAX_ALLOWED GDK_VERSION_3_4
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
typedef struct goDateTimePicker goDateTimePicker;
|
||||
typedef struct goDateTimePickerClass goDateTimePickerClass;
|
||||
typedef struct goDateTimePickerPrivate goDateTimePickerPrivate;
|
||||
|
||||
struct goDateTimePicker {
|
||||
GtkBox parent_instance;
|
||||
goDateTimePickerPrivate *priv;
|
||||
};
|
||||
|
||||
struct goDateTimePickerClass {
|
||||
GtkBoxClass parent_class;
|
||||
};
|
||||
|
||||
GType goDateTimePicker_get_type(void);
|
|
@ -1,299 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.18.3 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.12"/>
|
||||
<object class="GtkAdjustment" id="adjustment1">
|
||||
<property name="lower">1</property>
|
||||
<property name="upper">12</property>
|
||||
<property name="step_increment">1</property>
|
||||
<property name="page_increment">10</property>
|
||||
</object>
|
||||
<object class="GtkAdjustment" id="adjustment2">
|
||||
<property name="upper">59</property>
|
||||
<property name="step_increment">1</property>
|
||||
<property name="page_increment">10</property>
|
||||
</object>
|
||||
<object class="GtkWindow" id="window1">
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="box1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">12</property>
|
||||
<property name="margin_right">12</property>
|
||||
<property name="margin_top">12</property>
|
||||
<property name="margin_bottom">12</property>
|
||||
<property name="spacing">12</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="box2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Unopened</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBoxText" id="comboboxtext1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="has_entry">True</property>
|
||||
<child internal-child="entry">
|
||||
<object class="GtkEntry" id="comboboxtext-entry">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="text" translatable="yes">10 Jan 2015 7:01 PM</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Each component of the date/time string is a clickable item; free-form text cannot be entered. Clicking an item selects it; typing and the arrow keys change the values. Right clicking produces a Copy item and possibly an Undo item; the standard keyboard shortcuts will also work.</property>
|
||||
<property name="wrap">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator" id="separator1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="box3">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label3">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Opened</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBoxText" id="comboboxtext2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="has_entry">True</property>
|
||||
<child internal-child="entry">
|
||||
<object class="GtkEntry" id="comboboxtext-entry1">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="text" translatable="yes">10 Jan 2015 7:01 PM</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame" id="frame1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label_xalign">0</property>
|
||||
<property name="shadow_type">out</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="box4">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">12</property>
|
||||
<property name="margin_right">12</property>
|
||||
<property name="margin_top">12</property>
|
||||
<property name="margin_bottom">12</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkCalendar" id="calendar1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="year">2015</property>
|
||||
<property name="day">10</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="box5">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkSpinButton" id="spinbutton1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="adjustment">adjustment1</property>
|
||||
<property name="value">7</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label4">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSpinButton" id="spinbutton2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="adjustment">adjustment2</property>
|
||||
<property name="value">1</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBoxText" id="comboboxtext3">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="active">1</property>
|
||||
<items>
|
||||
<item translatable="yes">AM</item>
|
||||
<item translatable="yes">PM</item>
|
||||
</items>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="box6">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="spacing">6</property>
|
||||
<property name="homogeneous">True</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="button1">
|
||||
<property name="label" translatable="yes">Today</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="button2">
|
||||
<property name="label" translatable="yes">Revert</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label_item">
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label5">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">The combobox part works exactly like in the unopened mode. When opened, the popup shows a calendar, more conventional time editing, and buttons to select the current date/time and undo any changes made while the popup was open. Closing the popup (in the same way you would close a combobox popup) applies changes.
|
||||
The label of the Today button changes depending on whether dates only, times only, or both can be chosen.</property>
|
||||
<property name="wrap">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
|
@ -1,16 +0,0 @@
|
|||
// 9 january 2015
|
||||
#include "dtp.h"
|
||||
|
||||
// #qo pkg-config: gtk+-3.0
|
||||
|
||||
int main(void)
|
||||
{
|
||||
GtkWidget *mainwin;
|
||||
|
||||
gtk_init(NULL, NULL);
|
||||
mainwin = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
||||
gtk_container_add(GTK_CONTAINER(mainwin), g_object_new(goDateTimePicker_get_type(), NULL));
|
||||
gtk_widget_show_all(mainwin);
|
||||
gtk_main();
|
||||
return 0;
|
||||
}
|
|
@ -1,201 +0,0 @@
|
|||
// 10 october 2014
|
||||
// #qo pkg-config: gtk+-3.0
|
||||
#define GLIB_VERSION_MIN_REQUIRED GLIB_VERSION_2_32
|
||||
#define GLIB_VERSION_MAX_ALLOWED GLIB_VERSION_2_32
|
||||
#define GDK_VERSION_MIN_REQUIRED GDK_VERSION_3_4
|
||||
#define GDK_VERSION_MAX_ALLOWED GDK_VERSION_3_4
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
typedef gint LONG;
|
||||
|
||||
typedef struct POINT POINT;
|
||||
|
||||
struct POINT {
|
||||
LONG x;
|
||||
LONG y;
|
||||
};
|
||||
|
||||
struct popover {
|
||||
void *gopopover;
|
||||
|
||||
// a nice consequence of this design is that it allows four arrowheads to jut out at once; in practice only one will ever be used, but hey — simple implementation!
|
||||
LONG arrowLeft;
|
||||
LONG arrowRight;
|
||||
LONG arrowTop;
|
||||
LONG arrowBottom;
|
||||
};
|
||||
|
||||
struct popover _p = { NULL, -1, -1, 20, -1 };
|
||||
struct popover *p = &_p;
|
||||
|
||||
#define ARROWHEIGHT 8
|
||||
#define ARROWWIDTH 8 /* should be the same for smooth lines */
|
||||
|
||||
void makePopoverPath(cairo_t *cr, LONG width, LONG height)
|
||||
{
|
||||
POINT pt[20];
|
||||
int n;
|
||||
LONG xmax, ymax;
|
||||
|
||||
cairo_new_path(cr);
|
||||
n = 0;
|
||||
|
||||
// figure out the xmax and ymax of the box
|
||||
xmax = width;
|
||||
if (p->arrowRight >= 0)
|
||||
xmax -= ARROWWIDTH;
|
||||
ymax = height;
|
||||
if (p->arrowBottom >= 0)
|
||||
ymax -= ARROWHEIGHT;
|
||||
|
||||
// the first point is either at (0,0), (0,arrowHeight), (arrowWidth,0), or (arrowWidth,arrowHeight)
|
||||
pt[n].x = 0;
|
||||
if (p->arrowLeft >= 0)
|
||||
pt[n].x = ARROWWIDTH;
|
||||
pt[n].y = 0;
|
||||
if (p->arrowTop >= 0)
|
||||
pt[n].y = ARROWHEIGHT;
|
||||
n++;
|
||||
|
||||
// the left side
|
||||
pt[n].x = pt[n - 1].x;
|
||||
if (p->arrowLeft >= 0) {
|
||||
pt[n].y = pt[n - 1].y + p->arrowLeft;
|
||||
n++;
|
||||
pt[n].x = pt[n - 1].x - ARROWWIDTH;
|
||||
pt[n].y = pt[n - 1].y + ARROWHEIGHT;
|
||||
n++;
|
||||
pt[n].x = pt[n - 1].x + ARROWWIDTH;
|
||||
pt[n].y = pt[n - 1].y + ARROWHEIGHT;
|
||||
n++;
|
||||
pt[n].x = pt[n - 1].x;
|
||||
}
|
||||
pt[n].y = ymax;
|
||||
n++;
|
||||
|
||||
// the bottom side
|
||||
pt[n].y = pt[n - 1].y;
|
||||
if (p->arrowBottom >= 0) {
|
||||
pt[n].x = pt[n - 1].x + p->arrowBottom;
|
||||
n++;
|
||||
pt[n].x = pt[n - 1].x + ARROWWIDTH;
|
||||
pt[n].y = pt[n - 1].y + ARROWHEIGHT;
|
||||
n++;
|
||||
pt[n].x = pt[n - 1].x + ARROWWIDTH;
|
||||
pt[n].y = pt[n - 1].y - ARROWHEIGHT;
|
||||
n++;
|
||||
pt[n].y = pt[n - 1].y;
|
||||
}
|
||||
pt[n].x = xmax;
|
||||
n++;
|
||||
|
||||
// the right side
|
||||
pt[n].x = pt[n - 1].x;
|
||||
if (p->arrowRight >= 0) {
|
||||
pt[n].y = pt[0].y + p->arrowRight + (ARROWHEIGHT * 2);
|
||||
n++;
|
||||
pt[n].x = pt[n - 1].x + ARROWWIDTH;
|
||||
pt[n].y = pt[n - 1].y - ARROWHEIGHT;
|
||||
n++;
|
||||
pt[n].x = pt[n - 1].x - ARROWWIDTH;
|
||||
pt[n].y = pt[n - 1].y - ARROWHEIGHT;
|
||||
n++;
|
||||
pt[n].x = pt[n - 1].x;
|
||||
}
|
||||
pt[n].y = pt[0].y;
|
||||
n++;
|
||||
|
||||
// the top side
|
||||
pt[n].y = pt[n - 1].y;
|
||||
if (p->arrowTop >= 0) {
|
||||
pt[n].x = pt[0].x + p->arrowTop + (ARROWWIDTH * 2);
|
||||
n++;
|
||||
pt[n].x = pt[n - 1].x - ARROWWIDTH;
|
||||
pt[n].y = pt[n - 1].y - ARROWHEIGHT;
|
||||
n++;
|
||||
pt[n].x = pt[n - 1].x - ARROWWIDTH;
|
||||
pt[n].y = pt[n - 1].y + ARROWHEIGHT;
|
||||
n++;
|
||||
pt[n].y = pt[n - 1].y;
|
||||
}
|
||||
pt[n].x = pt[0].x;
|
||||
n++;
|
||||
|
||||
int i;
|
||||
|
||||
// TODO right and bottom edge
|
||||
cairo_set_line_width(cr, 1);
|
||||
cairo_move_to(cr, pt[0].x + 0.5, pt[0].y + 0.5);
|
||||
for (i = 1; i < n; i++)
|
||||
cairo_line_to(cr, pt[i].x + 0.5, pt[i].y + 0.5);
|
||||
}
|
||||
|
||||
void drawPopoverFrame(GtkWidget *widget, cairo_t *cr, LONG width, LONG height, int forceAlpha)
|
||||
{
|
||||
GtkStyleContext *context;
|
||||
GdkRGBA background, border;
|
||||
|
||||
// TODO see what GtkPopover itself does
|
||||
// TODO drop shadow
|
||||
context = gtk_widget_get_style_context(widget);
|
||||
gtk_style_context_add_class(widget, GTK_STYLE_CLASS_BACKGROUND);
|
||||
gtk_style_context_get_background_color(context, GTK_STATE_FLAG_NORMAL, &background);
|
||||
gtk_style_context_get_border_color(context, GTK_STATE_FLAG_NORMAL, &border);
|
||||
if (forceAlpha) {
|
||||
background.alpha = 1;
|
||||
border.alpha = 1;
|
||||
}
|
||||
makePopoverPath(cr, width, height);
|
||||
cairo_set_source_rgba(cr, background.red, background.green, background.blue, background.alpha);
|
||||
cairo_fill_preserve(cr);
|
||||
cairo_set_source_rgba(cr, border.red, border.green, border.blue, border.alpha);
|
||||
cairo_stroke(cr);
|
||||
}
|
||||
|
||||
gboolean popoverDraw(GtkWidget *widget, cairo_t *cr, gpointer data)
|
||||
{
|
||||
gint width, height;
|
||||
|
||||
width = gtk_widget_get_allocated_width(widget);
|
||||
height = gtk_widget_get_allocated_height(widget);
|
||||
drawPopoverFrame(widget, cr, width, height, 0);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void popoverSetSize(GtkWidget *widget, LONG width, LONG height)
|
||||
{
|
||||
GdkWindow *gdkwin;
|
||||
cairo_t *cr;
|
||||
cairo_surface_t *cs;
|
||||
cairo_region_t *region;
|
||||
|
||||
gtk_window_resize(GTK_WINDOW(widget), width, height);
|
||||
gdkwin = gtk_widget_get_window(widget);
|
||||
gdk_window_shape_combine_region(gdkwin, NULL, 0, 0);
|
||||
// TODO check errors
|
||||
cs = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
|
||||
cr = cairo_create(cs);
|
||||
drawPopoverFrame(widget, cr, width, height, 1);
|
||||
region = gdk_cairo_region_create_from_surface(cs);
|
||||
gdk_window_shape_combine_region(gdkwin, region, 0, 0);
|
||||
cairo_destroy(cr);
|
||||
cairo_surface_destroy(cs);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
GtkWidget *w;
|
||||
|
||||
gtk_init(NULL, NULL);
|
||||
w = gtk_window_new(GTK_WINDOW_POPUP);
|
||||
gtk_window_set_decorated(GTK_WINDOW(w), FALSE);
|
||||
gtk_widget_set_app_paintable(w, TRUE);
|
||||
g_signal_connect(w, "draw", G_CALLBACK(popoverDraw), NULL);
|
||||
gtk_widget_set_has_window(w, TRUE);
|
||||
gtk_widget_realize(w);
|
||||
popoverSetSize(w, 200, 200);
|
||||
gtk_window_move(GTK_WINDOW(w), 50, 50);
|
||||
gtk_widget_show_all(w);
|
||||
gtk_main();
|
||||
return 0;
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
// 16 august 2014
|
||||
|
||||
#import "objc_darwin.h"
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#define toNSInteger(x) ((NSInteger) (x))
|
||||
|
||||
id toTableImage(void *pixels, intptr_t width, intptr_t height, intptr_t stride)
|
||||
{
|
||||
NSBitmapImageRep *bitmap;
|
||||
NSImage *image;
|
||||
|
||||
// we can't just hand it pixels; we need to make a copy
|
||||
bitmap = [[NSBitmapImageRep alloc]
|
||||
initWithBitmapDataPlanes:NULL
|
||||
pixelsWide:toNSInteger(width)
|
||||
pixelsHigh:toNSInteger(height)
|
||||
bitsPerSample:8
|
||||
samplesPerPixel:4
|
||||
hasAlpha:YES
|
||||
isPlanar:NO
|
||||
colorSpaceName:NSDeviceRGBColorSpace
|
||||
bitmapFormat:0
|
||||
bytesPerRow:toNSInteger(stride)
|
||||
bitsPerPixel:32];
|
||||
memcpy((void *) [bitmap bitmapData], pixels, [bitmap bytesPerPlane]);
|
||||
image = [[NSImage alloc] initWithSize:NSMakeSize((CGFloat) width, (CGFloat) height)];
|
||||
[image addRepresentation:bitmap];
|
||||
// TODO release bitmap?
|
||||
return (id) image;
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
// +build !windows,!darwin
|
||||
|
||||
// 16 august 2014
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// #include "gtk_unix.h"
|
||||
import "C"
|
||||
|
||||
// this is what GtkFileChooserWidget uses
|
||||
// technically it uses max(width from that, height from that) if the call below fails and 16x16 otherwise, but we won't worry about that here (yet?)
|
||||
const scaleTo = C.GTK_ICON_SIZE_MENU
|
||||
|
||||
func toIconSizedGdkPixbuf(img *image.RGBA) *C.GdkPixbuf {
|
||||
var width, height C.gint
|
||||
|
||||
surface := C.cairo_image_surface_create(C.CAIRO_FORMAT_ARGB32,
|
||||
C.int(img.Rect.Dx()),
|
||||
C.int(img.Rect.Dy()))
|
||||
if status := C.cairo_surface_status(surface); status != C.CAIRO_STATUS_SUCCESS {
|
||||
panic(fmt.Errorf("cairo_create_image_surface() failed in toIconSizedGdkPixbuf(): %s\n",
|
||||
C.GoString(C.cairo_status_to_string(status))))
|
||||
}
|
||||
C.cairo_surface_flush(surface)
|
||||
toARGB(img, 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)
|
||||
basepixbuf := C.gdk_pixbuf_get_from_surface(surface, 0, 0, C.gint(img.Rect.Dx()), C.gint(img.Rect.Dy()))
|
||||
if basepixbuf == nil {
|
||||
panic(fmt.Errorf("gdk_pixbuf_get_from_surface() failed in toIconSizedGdkPixbuf() (no reason available)"))
|
||||
}
|
||||
|
||||
if C.gtk_icon_size_lookup(scaleTo, &width, &height) == C.FALSE {
|
||||
panic(fmt.Errorf("gtk_icon_size_lookup() failed in toIconSizedGdkPixbuf() (no reason available)"))
|
||||
}
|
||||
if int(width) == img.Rect.Dx() && int(height) == img.Rect.Dy() {
|
||||
// just return the base pixbuf; we're good
|
||||
C.cairo_surface_destroy(surface)
|
||||
return basepixbuf
|
||||
}
|
||||
// else scale
|
||||
pixbuf := C.gdk_pixbuf_scale_simple(basepixbuf, C.int(width), C.int(height), C.GDK_INTERP_NEAREST)
|
||||
if pixbuf == nil {
|
||||
panic(fmt.Errorf("gdk_pixbuf_scale_simple() failed in toIconSizedGdkPixbuf() (no reason available)"))
|
||||
}
|
||||
|
||||
C.g_object_unref(C.gpointer(unsafe.Pointer(basepixbuf)))
|
||||
C.cairo_surface_destroy(surface)
|
||||
return pixbuf
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
// 16 august 2014
|
||||
|
||||
#include "winapi_windows.h"
|
||||
#include "_cgo_export.h"
|
||||
|
||||
// TODO rename to images_windows.c?
|
||||
|
||||
HBITMAP toBitmap(void *i, intptr_t dx, intptr_t dy)
|
||||
{
|
||||
BITMAPINFO bi;
|
||||
VOID *ppvBits;
|
||||
HBITMAP bitmap;
|
||||
|
||||
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);
|
||||
bitmap = CreateDIBSection(NULL, &bi, DIB_RGB_COLORS, &ppvBits, 0, 0);
|
||||
if (bitmap == NULL)
|
||||
xpanic("error creating HBITMAP in toBitmap()", GetLastError());
|
||||
// image lists use non-premultiplied RGBA - see http://stackoverflow.com/a/25578789/3408572
|
||||
// the TRUE here does the conversion
|
||||
dotoARGB(i, (void *) ppvBits, TRUE);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
void freeBitmap(uintptr_t bitmap)
|
||||
{
|
||||
if (DeleteObject((HBITMAP) bitmap) == 0)
|
||||
xpanic("error deleting bitmap in freeBitmap()", GetLastError());
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
// 17 july 2014
|
||||
|
||||
#include "winapi_windows.h"
|
||||
|
||||
HINSTANCE hInstance;
|
||||
int nCmdShow;
|
||||
|
||||
HICON hDefaultIcon;
|
||||
HCURSOR hArrowCursor;
|
||||
|
||||
HFONT controlFont;
|
||||
HFONT titleFont;
|
||||
HFONT smallTitleFont;
|
||||
HFONT menubarFont;
|
||||
HFONT statusbarFont;
|
||||
|
||||
HBRUSH hollowBrush;
|
||||
|
||||
DWORD initWindows(char **errmsg)
|
||||
{
|
||||
STARTUPINFOW si;
|
||||
NONCLIENTMETRICSW ncm;
|
||||
|
||||
// WinMain() parameters
|
||||
hInstance = GetModuleHandleW(NULL);
|
||||
if (hInstance == NULL) {
|
||||
*errmsg = "error getting hInstance";
|
||||
return GetLastError();
|
||||
}
|
||||
nCmdShow = SW_SHOWDEFAULT;
|
||||
GetStartupInfoW(&si);
|
||||
if ((si.dwFlags & STARTF_USESHOWWINDOW) != 0)
|
||||
nCmdShow = si.wShowWindow;
|
||||
|
||||
// icons and cursors
|
||||
hDefaultIcon = LoadIconW(NULL, IDI_APPLICATION);
|
||||
if (hDefaultIcon == NULL) {
|
||||
*errmsg = "error loading default icon";
|
||||
return GetLastError();
|
||||
}
|
||||
hArrowCursor = LoadCursorW(NULL, IDC_ARROW);
|
||||
if (hArrowCursor == NULL) {
|
||||
*errmsg = "error loading arrow (default) cursor";
|
||||
return GetLastError();
|
||||
}
|
||||
|
||||
// standard fonts
|
||||
#define GETFONT(l, f, n) l = CreateFontIndirectW(&ncm.f); \
|
||||
if (l == NULL) { \
|
||||
*errmsg = "error loading " n " font"; \
|
||||
return GetLastError(); \
|
||||
}
|
||||
|
||||
ZeroMemory(&ncm, sizeof (NONCLIENTMETRICSW));
|
||||
ncm.cbSize = sizeof (NONCLIENTMETRICSW);
|
||||
if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof (NONCLIENTMETRICSW), &ncm, sizeof (NONCLIENTMETRICSW)) == 0) {
|
||||
*errmsg = "error getting non-client metrics parameters";
|
||||
return GetLastError();
|
||||
}
|
||||
GETFONT(controlFont, lfMessageFont, "control");
|
||||
GETFONT(titleFont, lfCaptionFont, "titlebar");
|
||||
GETFONT(smallTitleFont, lfSmCaptionFont, "small title bar");
|
||||
GETFONT(menubarFont, lfMenuFont, "menu bar");
|
||||
GETFONT(statusbarFont, lfStatusFont, "status bar");
|
||||
|
||||
hollowBrush = GetStockObject(HOLLOW_BRUSH);
|
||||
if (hollowBrush == NULL) {
|
||||
*errmsg = "error getting hollow brush";
|
||||
return GetLastError();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
*/
|
|
@ -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)
|
||||
}
|
||||
*/
|
|
@ -1,66 +0,0 @@
|
|||
// 15 july 2014
|
||||
|
||||
package ui
|
||||
|
||||
// #include "winapi_windows.h"
|
||||
import "C"
|
||||
|
||||
type label struct {
|
||||
*controlSingleHWNDWithText
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
*/
|
|
@ -1,36 +0,0 @@
|
|||
package ui
|
||||
|
||||
// Recursively replaces nils with stretchy empty spaces and changes the orientation
|
||||
// of inner stack so they are perpenticular to each other.
|
||||
func resetControls(parent *Stack) {
|
||||
for i, control := range parent.controls {
|
||||
switch control.(type) {
|
||||
case *Stack:
|
||||
stack := control.(*Stack)
|
||||
stack.orientation = !parent.orientation
|
||||
resetControls(stack)
|
||||
case nil:
|
||||
emptySpace := newStack(horizontal)
|
||||
parent.controls[i] = emptySpace
|
||||
parent.stretchy[i] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a new Stack from the given controls. The topmost Stack will have
|
||||
// vertical orientation and margin borders, with each nested stack being
|
||||
// oriented oppositely. Controls are displayed with a default padding
|
||||
// between them.
|
||||
func Layout(controls ...Control) *Stack {
|
||||
stack := &Stack{
|
||||
orientation: vertical,
|
||||
controls: controls,
|
||||
stretchy: make([]bool, len(controls)),
|
||||
width: make([]int, len(controls)),
|
||||
height: make([]int, len(controls)),
|
||||
}
|
||||
|
||||
resetControls(stack)
|
||||
|
||||
return stack
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
case WM_MOUSELEAVE:
|
||||
tablePushed(t->gotable, -1, -1); // in case button held as drag out
|
||||
// and let the list view do its thing
|
||||
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
|
|
@ -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 pre-existing Window window class for each tab page. This makes showing and hiding tabs a matter of showing and hiding one control.
|
||||
|
||||
TODO
|
||||
- make sure all tabs cannot be deselected (that is, make sure the current tab can never have index -1)
|
||||
*/
|
||||
|
||||
type tab struct {
|
||||
_hwnd C.HWND
|
||||
tabs []*container
|
||||
switchrect C.RECT // size that new tab should take when switching to it
|
||||
}
|
||||
|
||||
func newTab() Tab {
|
||||
hwnd := C.newControl(C.xWC_TABCONTROL,
|
||||
C.TCS_TOOLTIPS | C.WS_TABSTOP,
|
||||
0)
|
||||
t := &tab{
|
||||
_hwnd: hwnd,
|
||||
}
|
||||
C.controlSetControlFont(t._hwnd)
|
||||
C.setTabSubclass(t._hwnd, unsafe.Pointer(t))
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *tab) Append(name string, control Control) {
|
||||
c := newContainer(control)
|
||||
c.setParent(&controlParent{t._hwnd})
|
||||
t.tabs = append(t.tabs, c)
|
||||
// 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)
|
||||
// resize the new tab...
|
||||
t.tabs[int(new)].move(&t.switchrect)
|
||||
// ...then show
|
||||
t.tabs[int(new)].show()
|
||||
}
|
||||
|
||||
func (t *tab) hwnd() C.HWND {
|
||||
return t._hwnd
|
||||
}
|
||||
|
||||
func (t *tab) setParent(p *controlParent) {
|
||||
basesetParent(t, p)
|
||||
}
|
||||
|
||||
func (t *tab) allocate(x int, y int, width int, height int, d *sizing) []*allocation {
|
||||
return baseallocate(t, x, y, width, height, d)
|
||||
}
|
||||
|
||||
func (t *tab) preferredSize(d *sizing) (width, height int) {
|
||||
// TODO only consider the size of the current tab?
|
||||
for _, s := range t.tabs {
|
||||
w, h := s.child.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) commitResize(c *allocation, d *sizing) {
|
||||
var r C.RECT
|
||||
|
||||
// figure out what the rect for each child is...
|
||||
// the tab contents are children of the tab itself, so ignore c.x and c.y, which are relative to the window!
|
||||
r.left = C.LONG(0)
|
||||
r.top = C.LONG(0)
|
||||
r.right = C.LONG(c.width)
|
||||
r.bottom = C.LONG(c.height)
|
||||
C.tabGetContentRect(t._hwnd, &r)
|
||||
// and resize tabs
|
||||
// resize only the current tab; we trigger a resize on a tab change to make sure things look correct
|
||||
if len(t.tabs) > 0 {
|
||||
t.tabs[C.SendMessageW(t._hwnd, C.TCM_GETCURSEL, 0, 0)].move(&r)
|
||||
}
|
||||
// save the tab size so we can
|
||||
t.switchrect = r
|
||||
// and now resize the tab control itself
|
||||
basecommitResize(t, c, d)
|
||||
}
|
||||
|
||||
func (t *tab) getAuxResizeInfo(d *sizing) {
|
||||
basegetAuxResizeInfo(t, d)
|
||||
}
|
|
@ -1,166 +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 BOOL textfieldEditable(id);
|
||||
extern void textfieldSetEditable(id, BOOL);
|
||||
extern id newLabel(void);
|
||||
extern id newGroup(id);
|
||||
extern const char *groupText(id);
|
||||
extern void groupSetText(id, char *);
|
||||
extern id newTextbox(void);
|
||||
extern char *textboxText(id);
|
||||
extern void textboxSetText(id, char *);
|
||||
extern id newProgressBar(void);
|
||||
extern intmax_t progressbarPercent(id);
|
||||
extern void progressbarSetPercent(id, intmax_t);
|
||||
|
||||
/* 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);
|
||||
|
||||
/* image_darwin.m */
|
||||
extern id toTableImage(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);
|
||||
|
||||
/* spinbox_darwin.m */
|
||||
extern id newSpinbox(void *, intmax_t, intmax_t);
|
||||
extern id spinboxTextField(id);
|
||||
extern id spinboxStepper(id);
|
||||
extern intmax_t spinboxValue(id);
|
||||
extern void spinboxSetValue(id, intmax_t);
|
||||
|
||||
#endif
|
|
@ -1,227 +0,0 @@
|
|||
// 9 october 2014
|
||||
#include "../wininclude_windows.h"
|
||||
#include "popover.h"
|
||||
|
||||
// #qo LIBS: user32 kernel32 gdi32
|
||||
|
||||
// TODO
|
||||
// - should the parent window appear deactivated?
|
||||
|
||||
HWND popoverWindow;
|
||||
|
||||
void xpanic(char *msg, DWORD err)
|
||||
{
|
||||
printf("%d | %s\n", err, msg);
|
||||
abort();
|
||||
}
|
||||
|
||||
popover *p;
|
||||
|
||||
HRGN makePopoverRegion(HDC dc, LONG width, LONG height)
|
||||
{
|
||||
popoverPoint ppt[20];
|
||||
POINT pt[20];
|
||||
int i, n;
|
||||
HRGN region;
|
||||
|
||||
n = popoverMakeFramePoints(p, (intptr_t) width, (intptr_t) height, ppt);
|
||||
for (i = 0; i < n; i++) {
|
||||
pt[i].x = (LONG) (ppt[i].x);
|
||||
pt[i].y = (LONG) (ppt[i].y);
|
||||
}
|
||||
|
||||
if (BeginPath(dc) == 0)
|
||||
xpanic("error beginning path for Popover shape", GetLastError());
|
||||
if (Polyline(dc, pt, n) == 0)
|
||||
xpanic("error drawing lines in Popover shape", GetLastError());
|
||||
if (EndPath(dc) == 0)
|
||||
xpanic("error ending path for Popover shape", GetLastError());
|
||||
region = PathToRegion(dc);
|
||||
if (region == NULL)
|
||||
xpanic("error converting Popover shape path to region", GetLastError());
|
||||
return region;
|
||||
}
|
||||
|
||||
#define msgPopoverPrepareLeftRight (WM_APP+50)
|
||||
#define msgPopoverPrepareTopBottom (WM_APP+51)
|
||||
|
||||
LRESULT CALLBACK popoverproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
PAINTSTRUCT ps;
|
||||
HDC dc;
|
||||
HRGN region;
|
||||
RECT r;
|
||||
LONG width;
|
||||
LONG height;
|
||||
WINDOWPOS *wp;
|
||||
HBRUSH brush;
|
||||
|
||||
switch (uMsg) {
|
||||
case WM_NCPAINT:
|
||||
if (GetWindowRect(hwnd, &r) == 0)
|
||||
xpanic("error getting Popover window rect for shape redraw", GetLastError());
|
||||
width = r.right - r.left;
|
||||
height = r.bottom - r.top;
|
||||
dc = GetWindowDC(hwnd);
|
||||
if (dc == NULL)
|
||||
xpanic("error getting Popover window DC for drawing border", GetLastError());
|
||||
region = makePopoverRegion(dc, width, height);
|
||||
// don't call FillRgn(); WM_ERASEBKGND seems to do this to the non-client area for us already :S (TODO confirm)
|
||||
// TODO arrow is black in wine
|
||||
brush = (HBRUSH) GetStockObject(BLACK_BRUSH);
|
||||
if (brush == NULL)
|
||||
xpanic("error getting Popover border brush", GetLastError());
|
||||
if (FrameRgn(dc, region, brush, 1, 1) == 0)
|
||||
xpanic("error drawing Popover border", GetLastError());
|
||||
if (DeleteObject(region) == 0)
|
||||
xpanic("error deleting Popover shape region", GetLastError());
|
||||
if (ReleaseDC(hwnd, dc) == 0)
|
||||
xpanic("error releasing Popover window DC for shape drawing", GetLastError());
|
||||
return 0;
|
||||
case WM_WINDOWPOSCHANGED:
|
||||
// this must be here; if it's in WM_NCPAINT weird things happen (see http://stackoverflow.com/questions/26288303/why-is-my-client-rectangle-drawing-behaving-bizarrely-pictures-provided-if-i-t)
|
||||
wp = (WINDOWPOS *) lParam;
|
||||
if ((wp->flags & SWP_NOSIZE) == 0) {
|
||||
dc = GetWindowDC(hwnd);
|
||||
if (dc == NULL)
|
||||
xpanic("error getting Popover window DC for reshaping", GetLastError());
|
||||
region = makePopoverRegion(dc, wp->cx, wp->cy);
|
||||
if (SetWindowRgn(hwnd, region, TRUE) == 0)
|
||||
xpanic("error setting Popover shape", GetLastError());
|
||||
// don't delete the region; the window manager owns it now
|
||||
if (ReleaseDC(hwnd, dc) == 0)
|
||||
xpanic("error releasing Popover window DC for reshaping", GetLastError());
|
||||
}
|
||||
break; // defer to DefWindowProc()
|
||||
case WM_NCCALCSIZE:
|
||||
{
|
||||
RECT *r = (RECT *) lParam;
|
||||
NCCALCSIZE_PARAMS *np = (NCCALCSIZE_PARAMS *) lParam;
|
||||
popoverRect pr;
|
||||
|
||||
if (wParam != FALSE)
|
||||
r = &np->rgrc[0];
|
||||
pr.left = (intptr_t) (r->left);
|
||||
pr.top = (intptr_t) (r->top);
|
||||
pr.right = (intptr_t) (r->right);
|
||||
pr.bottom = (intptr_t) (r->bottom);
|
||||
popoverWindowSizeToClientSize(p, &pr);
|
||||
r->left = (LONG) (pr.left);
|
||||
r->top = (LONG) (pr.top);
|
||||
r->right = (LONG) (pr.right);
|
||||
r->bottom = (LONG) (pr.bottom);
|
||||
return 0;
|
||||
}
|
||||
case WM_PAINT:
|
||||
dc = BeginPaint(hwnd, &ps);
|
||||
GetClientRect(hwnd, &r);
|
||||
FillRect(dc, &r, GetSysColorBrush(COLOR_ACTIVECAPTION));
|
||||
FrameRect(dc, &r, GetStockPen(WHITE_BRUSH));
|
||||
EndPaint(hwnd, &ps);
|
||||
return 0;
|
||||
case msgPopoverPrepareLeftRight:
|
||||
case msgPopoverPrepareTopBottom:
|
||||
// TODO window edge detection
|
||||
{
|
||||
RECT r;
|
||||
LONG width = 200, height = 200;
|
||||
popoverRect control;
|
||||
uintptr_t side;
|
||||
popoverRect out;
|
||||
|
||||
if (GetWindowRect((HWND) wParam, &r) == 0)
|
||||
xpanic("error getting window rect of Popover target", GetLastError());
|
||||
control.left = (intptr_t) (r.left);
|
||||
control.top = (intptr_t) (r.top);
|
||||
control.right = (intptr_t) (r.right);
|
||||
control.bottom = (intptr_t) (r.bottom);
|
||||
switch (uMsg) {
|
||||
case msgPopoverPrepareLeftRight:
|
||||
side = popoverPointLeft;
|
||||
break;
|
||||
case msgPopoverPrepareTopBottom:
|
||||
side = popoverPointTop;
|
||||
break;
|
||||
}
|
||||
out = popoverPointAt(p, control, (intptr_t) width, (intptr_t) height, side);
|
||||
if (MoveWindow(hwnd, out.left, out.top, out.right - out.left, out.bottom - out.top, TRUE) == 0)
|
||||
xpanic("error repositioning Popover", GetLastError());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
|
||||
HWND button;
|
||||
|
||||
LRESULT CALLBACK wndproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
switch (uMsg) {
|
||||
case WM_COMMAND:
|
||||
if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == 100) {
|
||||
SendMessageW(popoverWindow, msgPopoverPrepareLeftRight, (WPARAM) button, 0);
|
||||
ShowWindow(popoverWindow, SW_SHOW);
|
||||
UpdateWindow(popoverWindow);
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case WM_CLOSE:
|
||||
PostQuitMessage(0);
|
||||
return 0;
|
||||
}
|
||||
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
WNDCLASSW wc;
|
||||
HWND mainwin;
|
||||
MSG msg;
|
||||
|
||||
p = popoverDataNew(NULL);
|
||||
// TODO null check
|
||||
|
||||
ZeroMemory(&wc, sizeof (WNDCLASSW));
|
||||
wc.lpszClassName = L"popover";
|
||||
wc.lpfnWndProc = popoverproc;
|
||||
wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
|
||||
wc.style = CS_DROPSHADOW | CS_NOCLOSE;
|
||||
if (RegisterClassW(&wc) == 0)
|
||||
abort();
|
||||
popoverWindow = CreateWindowExW(WS_EX_TOPMOST,
|
||||
L"popover", L"",
|
||||
WS_POPUP,
|
||||
0, 0, 150, 100,
|
||||
NULL, NULL, NULL, NULL);
|
||||
if (popoverWindow == NULL)
|
||||
abort();
|
||||
|
||||
ZeroMemory(&wc, sizeof (WNDCLASSW));
|
||||
wc.lpszClassName = L"mainwin";
|
||||
wc.lpfnWndProc = wndproc;
|
||||
wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
|
||||
if (RegisterClassW(&wc) == 0)
|
||||
abort();
|
||||
mainwin = CreateWindowExW(0,
|
||||
L"mainwin", L"Main Window",
|
||||
WS_OVERLAPPEDWINDOW,
|
||||
0, 0, 150, 100,
|
||||
NULL, NULL, NULL, NULL);
|
||||
if (mainwin == NULL)
|
||||
abort();
|
||||
button = CreateWindowExW(0,
|
||||
L"button", L"Click Me",
|
||||
BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE,
|
||||
20, 20, 100, 40,
|
||||
mainwin, (HMENU) 100, NULL, NULL);
|
||||
if (button == NULL)
|
||||
abort();
|
||||
ShowWindow(mainwin, SW_SHOWDEFAULT);
|
||||
if (UpdateWindow(mainwin) == 0)
|
||||
abort();
|
||||
while (GetMessageW(&msg, NULL, 0, 0) > 0) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessageW(&msg);
|
||||
}
|
||||
return 0;
|
||||
}
|
|
@ -1,174 +0,0 @@
|
|||
// 9 october 2014
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include "popover.h"
|
||||
|
||||
#define ARROWHEIGHT 8
|
||||
#define ARROWWIDTH 8 /* should be the same for smooth lines on Windows (TODO is there a better/nicer looking way?) */
|
||||
|
||||
struct popover {
|
||||
void *gopopover;
|
||||
|
||||
// a nice consequence of this design is that it allows four arrowheads to jut out at once; in practice only one will ever be used, but hey — simple implementation!
|
||||
intptr_t arrowLeft;
|
||||
intptr_t arrowTop;
|
||||
intptr_t arrowRight;
|
||||
intptr_t arrowBottom;
|
||||
};
|
||||
|
||||
popover *popoverDataNew(void *gopopover)
|
||||
{
|
||||
popover *p;
|
||||
|
||||
p = (popover *) malloc(sizeof (popover));
|
||||
if (p != NULL) {
|
||||
p->gopopover = gopopover;
|
||||
p->arrowLeft = -1;
|
||||
p->arrowTop = 20;//TODO-1;
|
||||
p->arrowRight = -1;
|
||||
p->arrowBottom = -1;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
int popoverMakeFramePoints(popover *p, intptr_t width, intptr_t height, popoverPoint pt[20])
|
||||
{
|
||||
int n;
|
||||
intptr_t xmax, ymax;
|
||||
|
||||
n = 0;
|
||||
|
||||
// figure out the xmax and ymax of the box
|
||||
xmax = width;
|
||||
if (p->arrowRight >= 0)
|
||||
xmax -= ARROWWIDTH;
|
||||
ymax = height;
|
||||
if (p->arrowBottom >= 0)
|
||||
ymax -= ARROWHEIGHT;
|
||||
|
||||
// the first point is either at (0,0), (0,arrowHeight), (arrowWidth,0), or (arrowWidth,arrowHeight)
|
||||
pt[n].x = 0;
|
||||
if (p->arrowLeft >= 0)
|
||||
pt[n].x = ARROWWIDTH;
|
||||
pt[n].y = 0;
|
||||
if (p->arrowTop >= 0)
|
||||
pt[n].y = ARROWHEIGHT;
|
||||
n++;
|
||||
|
||||
// the left side
|
||||
pt[n].x = pt[n - 1].x;
|
||||
if (p->arrowLeft >= 0) {
|
||||
pt[n].y = pt[n - 1].y + p->arrowLeft;
|
||||
n++;
|
||||
pt[n].x = pt[n - 1].x - ARROWWIDTH;
|
||||
pt[n].y = pt[n - 1].y + ARROWHEIGHT;
|
||||
n++;
|
||||
pt[n].x = pt[n - 1].x + ARROWWIDTH;
|
||||
pt[n].y = pt[n - 1].y + ARROWHEIGHT;
|
||||
n++;
|
||||
pt[n].x = pt[n - 1].x;
|
||||
}
|
||||
pt[n].y = ymax;
|
||||
n++;
|
||||
|
||||
// the bottom side
|
||||
pt[n].y = pt[n - 1].y;
|
||||
if (p->arrowBottom >= 0) {
|
||||
pt[n].x = pt[n - 1].x + p->arrowBottom;
|
||||
n++;
|
||||
pt[n].x = pt[n - 1].x + ARROWWIDTH;
|
||||
pt[n].y = pt[n - 1].y + ARROWHEIGHT;
|
||||
n++;
|
||||
pt[n].x = pt[n - 1].x + ARROWWIDTH;
|
||||
pt[n].y = pt[n - 1].y - ARROWHEIGHT;
|
||||
n++;
|
||||
pt[n].y = pt[n - 1].y;
|
||||
}
|
||||
pt[n].x = xmax;
|
||||
n++;
|
||||
|
||||
// the right side
|
||||
pt[n].x = pt[n - 1].x;
|
||||
if (p->arrowRight >= 0) {
|
||||
pt[n].y = pt[0].y + p->arrowRight + (ARROWHEIGHT * 2);
|
||||
n++;
|
||||
pt[n].x = pt[n - 1].x + ARROWWIDTH;
|
||||
pt[n].y = pt[n - 1].y - ARROWHEIGHT;
|
||||
n++;
|
||||
pt[n].x = pt[n - 1].x - ARROWWIDTH;
|
||||
pt[n].y = pt[n - 1].y - ARROWHEIGHT;
|
||||
n++;
|
||||
pt[n].x = pt[n - 1].x;
|
||||
}
|
||||
pt[n].y = pt[0].y;
|
||||
n++;
|
||||
|
||||
// the top side
|
||||
pt[n].y = pt[n - 1].y;
|
||||
if (p->arrowTop >= 0) {
|
||||
pt[n].x = pt[0].x + p->arrowTop + (ARROWWIDTH * 2);
|
||||
n++;
|
||||
pt[n].x = pt[n - 1].x - ARROWWIDTH;
|
||||
pt[n].y = pt[n - 1].y - ARROWHEIGHT;
|
||||
n++;
|
||||
pt[n].x = pt[n - 1].x - ARROWWIDTH;
|
||||
pt[n].y = pt[n - 1].y + ARROWHEIGHT;
|
||||
n++;
|
||||
pt[n].y = pt[n - 1].y;
|
||||
}
|
||||
pt[n].x = pt[0].x;
|
||||
n++;
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
void popoverWindowSizeToClientSize(popover *p, popoverRect *r)
|
||||
{
|
||||
r->left++;
|
||||
r->top++;
|
||||
r->right--;
|
||||
r->bottom--;
|
||||
if (p->arrowLeft >= 0)
|
||||
r->left += ARROWWIDTH;
|
||||
if (p->arrowRight >= 0)
|
||||
r->right -= ARROWWIDTH;
|
||||
if (p->arrowTop >= 0)
|
||||
r->top += ARROWHEIGHT;
|
||||
if (p->arrowBottom >= 0)
|
||||
r->bottom -= ARROWHEIGHT;
|
||||
}
|
||||
|
||||
// TODO window edge detection
|
||||
popoverRect popoverPointAt(popover *p, popoverRect control, intptr_t width, intptr_t height, unsigned int side)
|
||||
{
|
||||
intptr_t x, y;
|
||||
popoverRect out;
|
||||
|
||||
// account for border
|
||||
width += 2;
|
||||
height += 2;
|
||||
p->arrowLeft = -1;
|
||||
p->arrowRight = -1;
|
||||
p->arrowTop = -1;
|
||||
p->arrowBottom = -1;
|
||||
// TODO right and bottom
|
||||
switch (side) {
|
||||
case popoverPointLeft:
|
||||
width += ARROWWIDTH;
|
||||
p->arrowLeft = height / 2 - ARROWHEIGHT;
|
||||
x = control.right;
|
||||
y = control.top - ((height - (control.bottom - control.top)) / 2);
|
||||
break;
|
||||
case popoverPointTop:
|
||||
height += ARROWHEIGHT;
|
||||
p->arrowTop = width / 2 - ARROWWIDTH;
|
||||
x = control.left - ((width - (control.right - control.left)) / 2);
|
||||
y = control.bottom;
|
||||
break;
|
||||
}
|
||||
out.left = x;
|
||||
out.top = y;
|
||||
out.right = x + width;
|
||||
out.bottom = y + height;
|
||||
return out;
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
// 11 october 2014
|
||||
|
||||
typedef struct popover popover;
|
||||
typedef struct popoverPoint popoverPoint;
|
||||
typedef struct popoverRect popoverRect;
|
||||
|
||||
struct popoverPoint {
|
||||
intptr_t x;
|
||||
intptr_t y;
|
||||
};
|
||||
|
||||
struct popoverRect {
|
||||
intptr_t left;
|
||||
intptr_t top;
|
||||
intptr_t right;
|
||||
intptr_t bottom;
|
||||
};
|
||||
|
||||
// note the order: flipping sides is as easy as side ^ 1
|
||||
enum {
|
||||
popoverPointLeft,
|
||||
popoverPointRight,
|
||||
popoverPointTop,
|
||||
popoverPointBottom,
|
||||
};
|
||||
|
||||
popover *popoverDataNew(void *);
|
||||
int popoverMakeFramePoints(popover *, intptr_t, intptr_t, popoverPoint[20]);
|
||||
void popoverWindowSizeToClientSize(popover *, popoverRect *);
|
||||
popoverRect popoverPointAt(popover *, popoverRect, intptr_t, intptr_t, unsigned int);
|
|
@ -1,117 +0,0 @@
|
|||
// 11 october 2014
|
||||
// #qo pkg-config: gtk+-3.0
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#define GOPOPOVER_TYPE (goPopover_get_type())
|
||||
#define GOPOPOVER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GOPOPOVER_TYPE, goPopover))
|
||||
#define IS_GOPOPOVER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GOPOPOVER_TYPE))
|
||||
#define GOPOPOVER_CLASS(class) (G_TYPE_CHECK_CLASS_CAST((class), GOPOPOVER_TYPE, goPopoverClass))
|
||||
#define IS_GOPOPOVER_CLASS(class) (G_TYPE_CHECK_CLASS_TYPE((class), GOPOPOVER_TYPE))
|
||||
#define GOPOPOVER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GOPOPOVER_TYPE, goPopoverClass))
|
||||
|
||||
typedef struct goPopover goPopover;
|
||||
typedef struct goPopoverClass goPopoverClass;
|
||||
|
||||
struct goPopover {
|
||||
GtkBin parent_instance;
|
||||
void *gocontainer;
|
||||
GdkWindow *gdkwin;
|
||||
};
|
||||
|
||||
struct goPopoverClass {
|
||||
GtkBinClass parent_class;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(goPopover, goPopover, GTK_TYPE_BIN)
|
||||
|
||||
static void goPopover_init(goPopover *p)
|
||||
{
|
||||
gtk_widget_set_has_window(GTK_WIDGET(p), TRUE);
|
||||
}
|
||||
|
||||
static void goPopover_dispose(GObject *obj)
|
||||
{
|
||||
G_OBJECT_CLASS(goPopover_parent_class)->dispose(obj);
|
||||
}
|
||||
|
||||
static void goPopover_finalize(GObject *obj)
|
||||
{
|
||||
G_OBJECT_CLASS(goPopover_parent_class)->finalize(obj);
|
||||
}
|
||||
|
||||
static void goPopover_realize(GtkWidget *widget)
|
||||
{
|
||||
GdkWindowAttr attr;
|
||||
goPopover *p = GOPOPOVER(widget);
|
||||
|
||||
attr.x = 0;
|
||||
attr.y = 0;
|
||||
attr.width = 200;
|
||||
attr.height = 200;
|
||||
attr.wclass = GDK_INPUT_OUTPUT;
|
||||
attr.event_mask = gtk_widget_get_events(GTK_WIDGET(p)) | GDK_POINTER_MOTION_MASK | GDK_BUTTON_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_EXPOSURE_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK;
|
||||
attr.visual = gtk_widget_get_visual(GTK_WIDGET(p));
|
||||
attr.window_type = GDK_WINDOW_CHILD; // GtkPopover does this; TODO what does GtkWindow(GTK_WINDOW_POPUP) do?
|
||||
p->gdkwin = gdk_window_new(gtk_widget_get_parent_window(GTK_WIDGET(p)),
|
||||
&attr, GDK_WA_VISUAL);
|
||||
gtk_widget_set_window(GTK_WIDGET(p), p->gdkwin);
|
||||
gtk_widget_register_window(GTK_WIDGET(p), p->gdkwin);
|
||||
gtk_widget_set_realized(GTK_WIDGET(p), TRUE);
|
||||
}
|
||||
|
||||
static void goPopover_map(GtkWidget *widget)
|
||||
{
|
||||
gdk_window_show(GOPOPOVER(widget)->gdkwin);
|
||||
GTK_WIDGET_CLASS(goPopover_parent_class)->map(widget);
|
||||
}
|
||||
|
||||
static void goPopover_unmap(GtkWidget *widget)
|
||||
{
|
||||
gdk_window_hide(GOPOPOVER(widget)->gdkwin);
|
||||
GTK_WIDGET_CLASS(goPopover_parent_class)->unmap(widget);
|
||||
}
|
||||
|
||||
static gboolean goPopover_draw(GtkWidget *widget, cairo_t *cr)
|
||||
{
|
||||
GtkStyleContext *context;
|
||||
|
||||
context = gtk_widget_get_style_context(widget);
|
||||
gtk_render_background(context, cr, 0, 0, 200, 200);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void goPopover_class_init(goPopoverClass *class)
|
||||
{
|
||||
G_OBJECT_CLASS(class)->dispose = goPopover_dispose;
|
||||
G_OBJECT_CLASS(class)->finalize = goPopover_finalize;
|
||||
GTK_WIDGET_CLASS(class)->realize = goPopover_realize;
|
||||
GTK_WIDGET_CLASS(class)->map = goPopover_map;
|
||||
GTK_WIDGET_CLASS(class)->unmap = goPopover_unmap;
|
||||
GTK_WIDGET_CLASS(class)->draw = goPopover_draw;
|
||||
}
|
||||
|
||||
void buttonClicked(GtkWidget *button, gpointer data)
|
||||
{
|
||||
GtkWidget *popover;
|
||||
|
||||
popover = g_object_new(GOPOPOVER_TYPE, NULL);
|
||||
gtk_widget_set_parent(popover, gtk_widget_get_parent(button));
|
||||
gtk_widget_show(popover);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
GtkWidget *mainwin;
|
||||
GtkWidget *button;
|
||||
|
||||
gtk_init(NULL, NULL);
|
||||
mainwin = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
||||
gtk_window_resize(GTK_WINDOW(mainwin), 150, 50);
|
||||
g_signal_connect(mainwin, "destroy", gtk_main_quit, NULL);
|
||||
button = gtk_button_new_with_label("Click Me");
|
||||
g_signal_connect(button, "clicked", G_CALLBACK(buttonClicked), NULL);
|
||||
gtk_container_add(GTK_CONTAINER(mainwin), button);
|
||||
gtk_widget_show_all(mainwin);
|
||||
gtk_main();
|
||||
return 0;
|
||||
}
|
BIN
prev/prevlib.tar
BIN
prev/prevlib.tar
Binary file not shown.
|
@ -1,31 +0,0 @@
|
|||
// 4 november 2014
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// #include "objc_darwin.h"
|
||||
import "C"
|
||||
|
||||
type progressbar struct {
|
||||
*controlSingleObject
|
||||
}
|
||||
|
||||
func newProgressBar() ProgressBar {
|
||||
return &progressbar{
|
||||
controlSingleObject: newControlSingleObject(C.newProgressBar()),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *progressbar) Percent() int {
|
||||
return int(C.progressbarPercent(p.id))
|
||||
}
|
||||
|
||||
func (p *progressbar) SetPercent(percent int) {
|
||||
if percent < 0 || percent > 100 {
|
||||
panic(fmt.Errorf("given ProgressBar percentage %d out of range", percent))
|
||||
}
|
||||
C.progressbarSetPercent(p.id, C.intmax_t(percent))
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
// +build !windows,!darwin
|
||||
|
||||
// 4 november 2014
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// #include "gtk_unix.h"
|
||||
import "C"
|
||||
|
||||
type progressbar struct {
|
||||
*controlSingleWidget
|
||||
pbar *C.GtkProgressBar
|
||||
}
|
||||
|
||||
func newProgressBar() ProgressBar {
|
||||
widget := C.gtk_progress_bar_new();
|
||||
p := &progressbar{
|
||||
controlSingleWidget: newControlSingleWidget(widget),
|
||||
pbar: (*C.GtkProgressBar)(unsafe.Pointer(widget)),
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *progressbar) Percent() int {
|
||||
return int(C.gtk_progress_bar_get_fraction(p.pbar) * 100)
|
||||
}
|
||||
|
||||
func (p *progressbar) SetPercent(percent int) {
|
||||
if percent < 0 || percent > 100 {
|
||||
panic(fmt.Errorf("given ProgressBar percentage %d out of range", percent))
|
||||
}
|
||||
C.gtk_progress_bar_set_fraction(p.pbar, C.gdouble(percent) / 100)
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
// 4 november 2014
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// #include "winapi_windows.h"
|
||||
import "C"
|
||||
|
||||
type progressbar struct {
|
||||
*controlSingleHWND
|
||||
}
|
||||
|
||||
func newProgressBar() ProgressBar {
|
||||
hwnd := C.newControl(C.xPROGRESS_CLASS,
|
||||
C.PBS_SMOOTH,
|
||||
0)
|
||||
p := &progressbar{
|
||||
controlSingleHWND: newControlSingleHWND(hwnd),
|
||||
}
|
||||
p.fpreferredSize = p.xpreferredSize
|
||||
p.fnTabStops = func() int {
|
||||
// progress bars are not tab stops
|
||||
return 0
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *progressbar) Percent() int {
|
||||
return int(C.SendMessageW(p.hwnd, C.PBM_GETPOS, 0, 0))
|
||||
}
|
||||
|
||||
func (p *progressbar) SetPercent(percent int) {
|
||||
if percent < 0 || percent > 100 {
|
||||
panic(fmt.Errorf("given ProgressBar percentage %d out of range", percent))
|
||||
}
|
||||
// TODO circumvent aero
|
||||
C.SendMessageW(p.hwnd, C.PBM_SETPOS, C.WPARAM(percent), 0)
|
||||
}
|
||||
|
||||
const (
|
||||
// via http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx
|
||||
// this is the double-width option
|
||||
progressbarWidth = 237
|
||||
progressbarHeight = 8
|
||||
)
|
||||
|
||||
func (p *progressbar) xpreferredSize(d *sizing) (width, height int) {
|
||||
return fromdlgunitsX(progressbarWidth, d), fromdlgunitsY(progressbarHeight, d)
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
# Sidebar Control
|
||||
|
||||
```go
|
||||
type Sidebar interface {
|
||||
Control
|
||||
|
||||
AppendCategory(text string)
|
||||
DeleteCategory(index int)
|
||||
|
||||
AppendItem(category int, name string)
|
||||
DeleteItem(category int, index int)
|
||||
|
||||
Selection() (category int, index int) // or Selected()?
|
||||
Select(category int, index int)
|
||||
|
||||
OnSelected(func())
|
||||
}
|
||||
```
|
||||
|
||||
Simple two-level sidebars.
|
||||
|
||||
Could have images on each item in the future.
|
||||
|
||||
## Mac OS X
|
||||
Source List NSTableView (need to see how this will work)
|
||||
|
||||
## GTK+
|
||||
GTK_STYLE_CLASS_SIDEBAR (available in 3.4); see how GtkPlacesSidebar implements this
|
||||
- other programs that do: Rhythmbox
|
||||
|
||||
## Windows
|
||||
????
|
|
@ -1,35 +0,0 @@
|
|||
# Tree
|
||||
|
||||
Unlike Table, Tree can only store a set of a single data type. (Blame Windows.)
|
||||
|
||||
```go
|
||||
type TreeData struct {
|
||||
Checked bool
|
||||
Image ImageIndex
|
||||
Text string
|
||||
Children []TreeData // TODO does this need to be *[]TreeData?
|
||||
}
|
||||
```
|
||||
|
||||
(the facilities for Images has yet to be designed)
|
||||
|
||||
Tree itself will operate similarly to Table:
|
||||
|
||||
```go
|
||||
type Tree struct {
|
||||
Control
|
||||
sync.Locker // with Unlock() refreshing the view
|
||||
Data() *[]TreeData
|
||||
SetHasCheckboxes(bool)
|
||||
SetHasImages(bool)
|
||||
}
|
||||
```
|
||||
|
||||
By default, a Tree only shows the Text field.
|
||||
|
||||
A Tree path is just an `[]int` with each element set to the consecutive index in Children. For example:
|
||||
|
||||
```go
|
||||
i := []int{3, 4, 5}
|
||||
value := tree.Data()[i[0]].Children[i[1]].Children[i[2]].Text
|
||||
```
|
|
@ -1,4 +0,0 @@
|
|||
Window.SetSize()
|
||||
Window.Center() (only really effective on Mac OS X)
|
||||
Control.Show()/Control.Hide() (too much effort to get right)
|
||||
Multiple selection Tables (Windows and GTK+ event handling is some wacky dumb thing; could reconsider for Windows if I can write a good replacement but GTK+ still kills it)
|
|
@ -1,262 +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
|
||||
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),
|
||||
}
|
||||
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) {
|
||||
for _, cc := range g.controls {
|
||||
for _, c := range cc {
|
||||
c.setParent(parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *simpleGrid) containerShow() {
|
||||
for _, cc := range g.controls {
|
||||
for _, c := range cc {
|
||||
c.containerShow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *simpleGrid) containerHide() {
|
||||
for _, cc := range g.controls {
|
||||
for _, c := range cc {
|
||||
c.containerHide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if len(g.controls) == 0 {
|
||||
return
|
||||
}
|
||||
// -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
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
// 28 october 2014
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// #include "objc_darwin.h"
|
||||
import "C"
|
||||
|
||||
// interface builder notes
|
||||
// - the tops of the alignment rects should be identical
|
||||
// - spinner properties: auto repeat
|
||||
// - http://stackoverflow.com/questions/702829/integrate-nsstepper-with-nstextfield we'll need to bind the int value :S
|
||||
// - TODO experiment with a dummy project
|
||||
// - http://juliuspaintings.co.uk/cgi-bin/paint_css/animatedPaint/059-NSStepper-NSTextField.pl
|
||||
// - http://www.youtube.com/watch?v=ZZSHU-O7HVo
|
||||
// - http://andrehoffmann.wordpress.com/tag/nsstepper/ ?
|
||||
// TODO
|
||||
// - proper spacing between edit and spinner: Interface Builder isn't clear; NSDatePicker doesn't spill the beans
|
||||
|
||||
type spinbox struct {
|
||||
id C.id
|
||||
changed *event
|
||||
}
|
||||
|
||||
func newSpinbox(min int, max int) Spinbox {
|
||||
s := new(spinbox)
|
||||
s.id = C.newSpinbox(unsafe.Pointer(s), C.intmax_t(min), C.intmax_t(max))
|
||||
s.changed = newEvent()
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *spinbox) Value() int {
|
||||
return int(C.spinboxValue(s.id))
|
||||
}
|
||||
|
||||
func (s *spinbox) SetValue(value int) {
|
||||
C.spinboxSetValue(s.id, C.intmax_t(value))
|
||||
}
|
||||
|
||||
func (s *spinbox) OnChanged(e func()) {
|
||||
s.changed.set(e)
|
||||
}
|
||||
|
||||
//export spinboxChanged
|
||||
func spinboxChanged(data unsafe.Pointer) {
|
||||
s := (*spinbox)(data)
|
||||
s.changed.fire()
|
||||
}
|
||||
|
||||
func (s *spinbox) textfield() C.id {
|
||||
return C.spinboxTextField(s.id)
|
||||
}
|
||||
|
||||
func (s *spinbox) stepper() C.id {
|
||||
return C.spinboxStepper(s.id)
|
||||
}
|
||||
|
||||
func (s *spinbox) setParent(p *controlParent) {
|
||||
C.parent(s.textfield(), p.id)
|
||||
C.parent(s.stepper(), p.id)
|
||||
}
|
||||
|
||||
func (s *spinbox) preferredSize(d *sizing) (width, height int) {
|
||||
// TODO
|
||||
return 20, 20
|
||||
}
|
||||
|
||||
func (s *spinbox) resize(x int, y int, width int, height int, d *sizing) {
|
||||
// TODO
|
||||
C.moveControl(s.textfield(), C.intptr_t(x), C.intptr_t(y), C.intptr_t(width - 20), C.intptr_t(height))
|
||||
C.moveControl(s.stepper(), C.intptr_t(x + width - 15), C.intptr_t(y), C.intptr_t(15), C.intptr_t(height))
|
||||
}
|
||||
|
||||
func (s *spinbox) nTabStops() int {
|
||||
// TODO does the stepper count?
|
||||
return 1
|
||||
}
|
||||
|
||||
func (s *spinbox) containerShow() {
|
||||
// only provided for the Windows backend
|
||||
}
|
||||
|
||||
func (s *spinbox) containerHide() {
|
||||
// only provided for the Windows backend
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
// 29 october 2014
|
||||
|
||||
#include "objc_darwin.h"
|
||||
#include "_cgo_export.h"
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#define togoSpinbox(x) ((goSpinbox *) (x))
|
||||
|
||||
@interface goSpinbox : NSObject {
|
||||
@public
|
||||
void *gospinbox;
|
||||
NSTextField *textfield;
|
||||
NSNumberFormatter *formatter;
|
||||
NSStepper *stepper;
|
||||
|
||||
NSInteger value;
|
||||
NSInteger minimum;
|
||||
NSInteger maximum;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation goSpinbox
|
||||
|
||||
- (id)initWithMinimum:(NSInteger)minimum maximum:(NSInteger)maximum
|
||||
{
|
||||
self = [super init];
|
||||
if (self == nil)
|
||||
return nil;
|
||||
|
||||
self->textfield = (NSTextField *) newTextField();
|
||||
|
||||
self->formatter = [NSNumberFormatter new];
|
||||
[self->formatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
|
||||
[self->formatter setLocalizesFormat:NO];
|
||||
[self->formatter setUsesGroupingSeparator:NO];
|
||||
[self->formatter setHasThousandSeparators:NO];
|
||||
[self->formatter setAllowsFloats:NO];
|
||||
// TODO partial string validation?
|
||||
[self->textfield setFormatter:self->formatter];
|
||||
|
||||
self->stepper = [[NSStepper alloc] initWithFrame:NSZeroRect];
|
||||
[self->stepper setIncrement:1];
|
||||
[self->stepper setValueWraps:NO];
|
||||
[self->stepper setAutorepeat:YES]; // hold mouse button to step repeatedly
|
||||
|
||||
// TODO how SHOULD the formatter treat invald input?
|
||||
|
||||
[self setMinimum:minimum];
|
||||
[self setMaximum:maximum];
|
||||
[self setValue:self->minimum];
|
||||
|
||||
[self->textfield setDelegate:self];
|
||||
[self->stepper setTarget:self];
|
||||
[self->stepper setAction:@selector(stepperClicked:)];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setValue:(NSInteger)value
|
||||
{
|
||||
self->value = value;
|
||||
if (self->value < self->minimum)
|
||||
self->value = self->minimum;
|
||||
if (self->value > self->maximum)
|
||||
self->value = self->maximum;
|
||||
[self->textfield setIntegerValue:self->value];
|
||||
[self->stepper setIntegerValue:self->value];
|
||||
}
|
||||
|
||||
- (void)setMinimum:(NSInteger)min
|
||||
{
|
||||
self->minimum = min;
|
||||
[self->formatter setMinimum:[NSNumber numberWithInteger:self->minimum]];
|
||||
[self->stepper setMinValue:((double) (self->minimum))];
|
||||
}
|
||||
|
||||
- (void)setMaximum:(NSInteger)max
|
||||
{
|
||||
self->maximum = max;
|
||||
[self->formatter setMaximum:[NSNumber numberWithInteger:self->maximum]];
|
||||
[self->stepper setMaxValue:((double) (self->maximum))];
|
||||
}
|
||||
|
||||
- (IBAction)stepperClicked:(id)sender
|
||||
{
|
||||
[self setValue:[self->stepper integerValue]];
|
||||
spinboxChanged(self->gospinbox);
|
||||
}
|
||||
|
||||
- (void)controlTextDidChange:(NSNotification *)note
|
||||
{
|
||||
[self setValue:[self->textfield integerValue]];
|
||||
spinboxChanged(self->gospinbox);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
id newSpinbox(void *gospinbox, intmax_t minimum, intmax_t maximum)
|
||||
{
|
||||
goSpinbox *s;
|
||||
|
||||
s = [[goSpinbox new] initWithMinimum:((NSInteger) minimum) maximum:((NSInteger) maximum)];
|
||||
s->gospinbox = gospinbox;
|
||||
return s;
|
||||
}
|
||||
|
||||
id spinboxTextField(id spinbox)
|
||||
{
|
||||
return (id) (togoSpinbox(spinbox)->textfield);
|
||||
}
|
||||
|
||||
id spinboxStepper(id spinbox)
|
||||
{
|
||||
return (id) (togoSpinbox(spinbox)->stepper);
|
||||
}
|
||||
|
||||
intmax_t spinboxValue(id spinbox)
|
||||
{
|
||||
return (intmax_t) (togoSpinbox(spinbox)->value);
|
||||
}
|
||||
|
||||
void spinboxSetValue(id spinbox, intmax_t value)
|
||||
{
|
||||
[togoSpinbox(spinbox) setValue:((NSInteger) value)];
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
// +build !windows,!darwin
|
||||
|
||||
// 28 october 2014
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// #include "gtk_unix.h"
|
||||
// extern void spinboxChanged(GtkSpinButton *, gpointer);
|
||||
import "C"
|
||||
|
||||
// TODO preferred width may be too wide
|
||||
|
||||
type spinbox struct {
|
||||
*controlSingleWidget
|
||||
spinbutton *C.GtkSpinButton
|
||||
changed *event
|
||||
}
|
||||
|
||||
func newSpinbox(min int, max int) Spinbox {
|
||||
// gtk_spin_button_new_with_range() initially sets its value to the minimum value
|
||||
widget := C.gtk_spin_button_new_with_range(C.gdouble(min), C.gdouble(max), 1)
|
||||
s := &spinbox{
|
||||
controlSingleWidget: newControlSingleWidget(widget),
|
||||
spinbutton: (*C.GtkSpinButton)(unsafe.Pointer(widget)),
|
||||
changed: newEvent(),
|
||||
}
|
||||
C.gtk_spin_button_set_digits(s.spinbutton, 0) // integers
|
||||
C.gtk_spin_button_set_numeric(s.spinbutton, C.TRUE) // digits only
|
||||
// this isn't specifically documented as the signal to connect to until 3.14
|
||||
// it has existed as far back as 3.4, though, if not earlier
|
||||
// there's also ::change-value which is for keyboard changing
|
||||
g_signal_connect(
|
||||
C.gpointer(unsafe.Pointer(s.spinbutton)),
|
||||
"value-changed",
|
||||
C.GCallback(C.spinboxChanged),
|
||||
C.gpointer(unsafe.Pointer(s)))
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *spinbox) Value() int {
|
||||
return int(C.gtk_spin_button_get_value(s.spinbutton))
|
||||
}
|
||||
|
||||
func (s *spinbox) SetValue(value int) {
|
||||
var min, max C.gdouble
|
||||
|
||||
C.gtk_spin_button_get_range(s.spinbutton, &min, &max)
|
||||
if value < int(min) {
|
||||
value = int(min)
|
||||
}
|
||||
if value > int(max) {
|
||||
value = int(max)
|
||||
}
|
||||
C.gtk_spin_button_set_value(s.spinbutton, C.gdouble(value))
|
||||
}
|
||||
|
||||
func (s *spinbox) OnChanged(e func()) {
|
||||
s.changed.set(e)
|
||||
}
|
||||
|
||||
//export spinboxChanged
|
||||
func spinboxChanged(swid *C.GtkSpinButton, data C.gpointer) {
|
||||
s := (*spinbox)(unsafe.Pointer(data))
|
||||
s.changed.fire()
|
||||
}
|
|
@ -1,148 +0,0 @@
|
|||
// 28 october 2014
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// #include "winapi_windows.h"
|
||||
import "C"
|
||||
|
||||
// TODO do we have to manually monitor user changes to the edit control?
|
||||
// TODO WS_EX_CLIENTEDGE on the updown?
|
||||
|
||||
type spinbox struct {
|
||||
hwndEdit C.HWND
|
||||
hwndUpDown C.HWND
|
||||
changed *event
|
||||
// updown state
|
||||
updownVisible bool
|
||||
// keep these here to avoid having to get them out
|
||||
value int
|
||||
min int
|
||||
max int
|
||||
}
|
||||
|
||||
func newSpinbox(min int, max int) Spinbox {
|
||||
s := new(spinbox)
|
||||
s.hwndEdit = C.newControl(editclass,
|
||||
C.textfieldStyle | C.ES_NUMBER,
|
||||
C.textfieldExtStyle)
|
||||
s.changed = newEvent()
|
||||
s.updownVisible = true // initially shown
|
||||
s.min = min
|
||||
s.max = max
|
||||
s.value = s.min
|
||||
s.remakeUpDown()
|
||||
C.controlSetControlFont(s.hwndEdit)
|
||||
C.setSpinboxEditSubclass(s.hwndEdit, unsafe.Pointer(s))
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *spinbox) cap() {
|
||||
if s.value < s.min {
|
||||
s.value = s.min
|
||||
}
|
||||
if s.value > s.max {
|
||||
s.value = s.max
|
||||
}
|
||||
}
|
||||
|
||||
func (s *spinbox) Value() int {
|
||||
return s.value
|
||||
}
|
||||
|
||||
func (s *spinbox) SetValue(value int) {
|
||||
// UDM_SETPOS32 is documented to do what we want, but since we're keeping a copy of value we need to do it anyway
|
||||
s.value = value
|
||||
s.cap()
|
||||
C.SendMessageW(s.hwndUpDown, C.UDM_SETPOS32, 0, C.LPARAM(s.value))
|
||||
}
|
||||
|
||||
func (s *spinbox) OnChanged(e func()) {
|
||||
s.changed.set(e)
|
||||
}
|
||||
|
||||
//export spinboxUpDownClicked
|
||||
func spinboxUpDownClicked(data unsafe.Pointer, nud *C.NMUPDOWN) {
|
||||
// this is where we do custom increments
|
||||
s := (*spinbox)(data)
|
||||
s.value = int(nud.iPos + nud.iDelta)
|
||||
// this can go above or below the bounds (the spinbox only rejects invalid values after the UDN_DELTAPOS notification is processed)
|
||||
// because we have a copy of the value, we need to fix that here
|
||||
s.cap()
|
||||
s.changed.fire()
|
||||
}
|
||||
|
||||
//export spinboxEditChanged
|
||||
func spinboxEditChanged(data unsafe.Pointer) {
|
||||
// we're basically on our own here
|
||||
s := (*spinbox)(unsafe.Pointer(data))
|
||||
// this basically does what OS X does: values too low get clamped to the minimum, values too high get clamped to the maximum, and deleting everything clamps to the minimum
|
||||
value, err := strconv.Atoi(getWindowText(s.hwndEdit))
|
||||
if err != nil {
|
||||
// best we can do fo rnow in this case :S
|
||||
// a partial atoi() like in C would be more optimal
|
||||
// it handles the deleting everything case just fine
|
||||
value = s.min
|
||||
}
|
||||
s.value = value
|
||||
s.cap()
|
||||
C.SendMessageW(s.hwndUpDown, C.UDM_SETPOS32, 0, C.LPARAM(s.value))
|
||||
// TODO position the insertion caret at the end (or wherever is appropriate)
|
||||
s.changed.fire()
|
||||
}
|
||||
|
||||
func (s *spinbox) setParent(p *controlParent) {
|
||||
C.controlSetParent(s.hwndEdit, p.hwnd)
|
||||
C.controlSetParent(s.hwndUpDown, p.hwnd)
|
||||
}
|
||||
|
||||
// an up-down control will only properly position itself the first time
|
||||
// stupidly, there are no messages to force a size calculation, nor can I seem to reset the buddy window to force a new position
|
||||
// alas, we have to make a new up/down control each time :(
|
||||
// TODO we'll need to store a copy of the current position and range for this
|
||||
func (s *spinbox) remakeUpDown() {
|
||||
// destroying the previous one, setting the parent properly, and subclassing are handled here
|
||||
s.hwndUpDown = C.newUpDown(s.hwndUpDown, unsafe.Pointer(s))
|
||||
// for this to work, hwndUpDown needs to have rect [0 0 0 0]
|
||||
C.moveWindow(s.hwndUpDown, 0, 0, 0, 0)
|
||||
C.SendMessageW(s.hwndUpDown, C.UDM_SETBUDDY, C.WPARAM(uintptr(unsafe.Pointer(s.hwndEdit))), 0)
|
||||
C.SendMessageW(s.hwndUpDown, C.UDM_SETRANGE32, C.WPARAM(s.min), C.LPARAM(s.max))
|
||||
C.SendMessageW(s.hwndUpDown, C.UDM_SETPOS32, 0, C.LPARAM(s.value))
|
||||
if s.updownVisible {
|
||||
C.ShowWindow(s.hwndUpDown, C.SW_SHOW)
|
||||
}
|
||||
}
|
||||
|
||||
// use the same height as normal text fields
|
||||
// TODO constrain the width somehow
|
||||
func (s *spinbox) preferredSize(d *sizing) (width, height int) {
|
||||
return fromdlgunitsX(textfieldWidth, d), fromdlgunitsY(textfieldHeight, d)
|
||||
}
|
||||
|
||||
func (s *spinbox) resize(x int, y int, width int, height int, d *sizing) {
|
||||
C.moveWindow(s.hwndEdit, C.int(x), C.int(y), C.int(width), C.int(height))
|
||||
s.remakeUpDown()
|
||||
}
|
||||
|
||||
func (s *spinbox) nTabStops() int {
|
||||
// TODO does the up-down control count?
|
||||
return 1
|
||||
}
|
||||
|
||||
// TODO be sure to modify this when we add Show()/Hide()
|
||||
func (s *spinbox) containerShow() {
|
||||
C.ShowWindow(s.hwndEdit, C.SW_SHOW)
|
||||
C.ShowWindow(s.hwndUpDown, C.SW_SHOW)
|
||||
s.updownVisible = true
|
||||
}
|
||||
|
||||
// TODO be sure to modify this when we add Show()/Hide()
|
||||
func (s *spinbox) containerHide() {
|
||||
C.ShowWindow(s.hwndEdit, C.SW_HIDE)
|
||||
C.ShowWindow(s.hwndUpDown, C.SW_HIDE)
|
||||
s.updownVisible = false
|
||||
}
|
236
prev/stack.go
236
prev/stack.go
|
@ -1,236 +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
|
||||
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)),
|
||||
}
|
||||
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) {
|
||||
for _, c := range s.controls {
|
||||
c.setParent(parent)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stack) containerShow() {
|
||||
for _, c := range s.controls {
|
||||
c.containerShow()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stack) containerHide() {
|
||||
for _, c := range s.controls {
|
||||
c.containerHide()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stack) resize(x int, y int, width int, height int, d *sizing) {
|
||||
var stretchywid, stretchyht int
|
||||
|
||||
if len(s.controls) == 0 { // do nothing if there's nothing to do
|
||||
return
|
||||
}
|
||||
// -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)
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
// 25 july 2014
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// #include "objc_darwin.h"
|
||||
import "C"
|
||||
|
||||
type tab struct {
|
||||
*controlSingleObject
|
||||
tabs []*container
|
||||
children []Control
|
||||
}
|
||||
|
||||
func newTab() Tab {
|
||||
t := &tab{
|
||||
controlSingleObject: newControlSingleObject(C.newTab()),
|
||||
}
|
||||
t.fpreferredSize = t.xpreferredSize
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *tab) Append(name string, control Control) {
|
||||
c := newContainer(control.resize)
|
||||
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)
|
||||
}
|
||||
|
||||
// no need to handle resize; the children containers handle that for us
|
|
@ -1,40 +0,0 @@
|
|||
// 25 july 2014
|
||||
|
||||
#import "objc_darwin.h"
|
||||
#import "_cgo_export.h"
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#define toNSTabView(x) ((NSTabView *) (x))
|
||||
#define toNSView(x) ((NSView *) (x))
|
||||
|
||||
id newTab(void)
|
||||
{
|
||||
NSTabView *t;
|
||||
|
||||
t = [[NSTabView alloc] initWithFrame:NSZeroRect];
|
||||
setStandardControlFont((id) t); // safe; same selector provided by NSTabView
|
||||
return (id) t;
|
||||
}
|
||||
|
||||
void tabAppend(id t, char *name, id view)
|
||||
{
|
||||
NSTabViewItem *i;
|
||||
|
||||
i = [[NSTabViewItem alloc] initWithIdentifier:nil];
|
||||
[i setLabel:[NSString stringWithUTF8String:name]];
|
||||
[i setView:toNSView(view)];
|
||||
[toNSTabView(t) addTabViewItem:i];
|
||||
}
|
||||
|
||||
struct xsize tabPreferredSize(id control)
|
||||
{
|
||||
NSTabView *tv;
|
||||
NSSize s;
|
||||
struct xsize t;
|
||||
|
||||
tv = toNSTabView(control);
|
||||
s = [tv minimumSize];
|
||||
t.width = (intptr_t) s.width;
|
||||
t.height = (intptr_t) s.height;
|
||||
return t;
|
||||
}
|
|
@ -1,51 +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
|
||||
}
|
||||
|
||||
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)),
|
||||
}
|
||||
// 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.gtk_container_add(t.container, c.widget)
|
||||
control.setParent(c.parent())
|
||||
c.resize = control.resize
|
||||
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)
|
||||
}
|
||||
|
||||
// no need to handle resize; the children containers handle that for us
|
|
@ -1,123 +0,0 @@
|
|||
// 25 july 2014
|
||||
|
||||
#include "winapi_windows.h"
|
||||
#include "_cgo_export.h"
|
||||
|
||||
// provided for cgo's benefit
|
||||
LPWSTR xWC_TABCONTROL = WC_TABCONTROL;
|
||||
|
||||
static LRESULT CALLBACK tabSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR id, DWORD_PTR data)
|
||||
{
|
||||
NMHDR *nmhdr = (NMHDR *) lParam;
|
||||
LRESULT lResult, r;
|
||||
RECT resizeRect;
|
||||
WINDOWPOS *wp;
|
||||
|
||||
if (sharedWndProc(hwnd, uMsg, wParam, lParam, &lResult))
|
||||
return lResult;
|
||||
switch (uMsg) {
|
||||
case msgNOTIFY:
|
||||
switch (nmhdr->code) {
|
||||
case TCN_SELCHANGING:
|
||||
r = SendMessageW(hwnd, TCM_GETCURSEL, 0, 0);
|
||||
if (r == (LRESULT) -1) // no tab currently selected
|
||||
return FALSE;
|
||||
tabChanging((void *) data, r);
|
||||
return FALSE; // allow change
|
||||
case TCN_SELCHANGE:
|
||||
tabChanged((void *) data, SendMessageW(hwnd, TCM_GETCURSEL, 0, 0));
|
||||
return 0;
|
||||
}
|
||||
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
|
||||
case msgTabCurrentTabHasChildren:
|
||||
return (LRESULT) tabTabHasChildren((void *) data, SendMessageW(hwnd, TCM_GETCURSEL, 0, 0));
|
||||
// don't do this on WM_WINDOWPOSCHANGING; weird redraw issues will happen
|
||||
case WM_WINDOWPOSCHANGED:
|
||||
wp = (WINDOWPOS *) lParam;
|
||||
resizeRect.left = wp->x;
|
||||
resizeRect.top = wp->y;
|
||||
resizeRect.right = wp->x + wp->cx;
|
||||
resizeRect.bottom = wp->y + wp->cy;
|
||||
tabGetContentRect(hwnd, &resizeRect);
|
||||
tabResized((void *) data, resizeRect);
|
||||
// and chain up
|
||||
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
|
||||
case WM_NCDESTROY:
|
||||
if ((*fv_RemoveWindowSubclass)(hwnd, tabSubProc, id) == FALSE)
|
||||
xpanic("error removing Tab subclass (which was for its own event handler)", GetLastError());
|
||||
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
|
||||
default:
|
||||
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
xmissedmsg("Tab", "tabSubProc()", uMsg);
|
||||
return 0; // unreached
|
||||
}
|
||||
|
||||
void setTabSubclass(HWND hwnd, void *data)
|
||||
{
|
||||
if ((*fv_SetWindowSubclass)(hwnd, tabSubProc, 0, (DWORD_PTR) data) == FALSE)
|
||||
xpanic("error subclassing Tab to give it its own event handler", GetLastError());
|
||||
}
|
||||
|
||||
void tabAppend(HWND hwnd, LPWSTR name)
|
||||
{
|
||||
TCITEM item;
|
||||
LRESULT n;
|
||||
|
||||
ZeroMemory(&item, sizeof (TCITEM));
|
||||
item.mask = TCIF_TEXT;
|
||||
item.pszText = name;
|
||||
// MSDN's example code uses the first invalid index directly for this
|
||||
n = SendMessageW(hwnd, TCM_GETITEMCOUNT, 0, 0);
|
||||
if (SendMessageW(hwnd, TCM_INSERTITEM, (WPARAM) n, (LPARAM) (&item)) == (LRESULT) -1)
|
||||
xpanic("error adding tab to Tab", GetLastError());
|
||||
}
|
||||
|
||||
void tabGetContentRect(HWND hwnd, RECT *r)
|
||||
{
|
||||
// not &r; already a pointer (thanks MindChild in irc.efnet.net/#winprog for spotting my failure)
|
||||
SendMessageW(hwnd, TCM_ADJUSTRECT, FALSE, (LPARAM) r);
|
||||
}
|
||||
|
||||
// theoretically we don't need to iterate over every tab for this, but let's do it just to be safe
|
||||
LONG tabGetTabHeight(HWND hwnd)
|
||||
{
|
||||
RECT r;
|
||||
LRESULT i, n;
|
||||
LONG tallest;
|
||||
|
||||
n = SendMessageW(hwnd, TCM_GETITEMCOUNT, 0, 0);
|
||||
// if there are no tabs, then the control just draws a box over the full window rect, reserving no space for tabs; this is handled with the next line
|
||||
tallest = 0;
|
||||
for (i = 0; i < n; i++) {
|
||||
if (SendMessageW(hwnd, TCM_GETITEMRECT, (WPARAM) i, (LPARAM) (&r)) == FALSE)
|
||||
xpanic("error getting tab height for Tab.preferredSize()", GetLastError());
|
||||
if (tallest < (r.bottom - r.top))
|
||||
tallest = r.bottom - r.top;
|
||||
}
|
||||
return tallest;
|
||||
}
|
||||
|
||||
void tabEnterChildren(HWND hwnd)
|
||||
{
|
||||
DWORD style, xstyle;
|
||||
|
||||
style = (DWORD) GetWindowLongPtrW(hwnd, GWL_STYLE);
|
||||
xstyle = (DWORD) GetWindowLongPtrW(hwnd, GWL_EXSTYLE);
|
||||
style &= ~((DWORD) WS_TABSTOP);
|
||||
xstyle |= WS_EX_CONTROLPARENT;
|
||||
SetWindowLongPtrW(hwnd, GWL_STYLE, (LONG_PTR) style);
|
||||
SetWindowLongPtrW(hwnd, GWL_EXSTYLE, (LONG_PTR) xstyle);
|
||||
}
|
||||
|
||||
void tabLeaveChildren(HWND hwnd)
|
||||
{
|
||||
DWORD style, xstyle;
|
||||
|
||||
style = (DWORD) GetWindowLongPtrW(hwnd, GWL_STYLE);
|
||||
xstyle = (DWORD) GetWindowLongPtrW(hwnd, GWL_EXSTYLE);
|
||||
style |= WS_TABSTOP;
|
||||
xstyle &= ~((DWORD) WS_EX_CONTROLPARENT);
|
||||
SetWindowLongPtrW(hwnd, GWL_STYLE, (LONG_PTR) style);
|
||||
SetWindowLongPtrW(hwnd, GWL_EXSTYLE, (LONG_PTR) xstyle);
|
||||
}
|
|
@ -1,107 +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.
|
||||
*/
|
||||
|
||||
// TODO FIGURE OUT HOW OR WHY THIS IS PARTIALLY MARGINED WTF
|
||||
// it's probably something I did a while ago and forgot but still wow
|
||||
|
||||
type tab struct {
|
||||
*controlSingleHWND
|
||||
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) {
|
||||
control.setParent(&controlParent{t.hwnd})
|
||||
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.children) != 1 {
|
||||
t.children[len(t.children)-1].containerHide()
|
||||
}
|
||||
C.tabAppend(t.hwnd, toUTF16(name))
|
||||
}
|
||||
|
||||
//export tabChanging
|
||||
func tabChanging(data unsafe.Pointer, current C.LRESULT) {
|
||||
t := (*tab)(data)
|
||||
t.children[int(current)].containerHide()
|
||||
}
|
||||
|
||||
//export tabChanged
|
||||
func tabChanged(data unsafe.Pointer, new C.LRESULT) {
|
||||
t := (*tab)(data)
|
||||
t.children[int(new)].containerShow()
|
||||
}
|
||||
|
||||
//export tabTabHasChildren
|
||||
func tabTabHasChildren(data unsafe.Pointer, which C.LRESULT) C.BOOL {
|
||||
t := (*tab)(data)
|
||||
if len(t.children) == 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))
|
||||
}
|
||||
|
||||
// no need to resize the other controls; we do that in tabResized() which is called by the tab subclass handler
|
||||
func (t *tab) xresize(x int, y int, width int, height int, d *sizing) {
|
||||
// just chain up to the container base to keep the Z-order correct
|
||||
t.chainresize(x, y, width, height, d)
|
||||
}
|
||||
|
||||
//export tabResized
|
||||
func tabResized(data unsafe.Pointer, r C.RECT) {
|
||||
t := (*tab)(data)
|
||||
if len(t.children) == 0 { // nothing to do
|
||||
return
|
||||
}
|
||||
d := beginResize(t.hwnd)
|
||||
// only need to resize the current tab; we resize new tabs when the tab changes in tabChanged() above
|
||||
// because each widget is actually a child of the Window, the origin is the one we calculated above
|
||||
for i := 0; i < len(t.children); i++ {
|
||||
t.children[i].resize(int(r.left), int(r.top), int(r.right - r.left), int(r.bottom - r.top), d)
|
||||
}
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
// 28 july 2014
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Table is a Control that displays a list of like-structured data in a grid where each row represents an item and each column represents a bit of data.
|
||||
// Tables store and render a slice of struct values.
|
||||
// Each field of the struct of type *image.RGBA is rendered as an icon.
|
||||
// The Table itself will resize the image to an icon size if needed; the original *image.RGBA will not be modified and the icon size is implementation-defined.
|
||||
// Each field whose type is bool or equivalent to bool is rendered as a checkbox.
|
||||
// All other fields are rendered as strings formatted with package fmt's %v format specifier.
|
||||
//
|
||||
// Tables are read-only by default, except for checkboxes, which are user-settable.
|
||||
//
|
||||
// Tables have headers on top of all columns.
|
||||
// By default, the name of the header is the same as the name of the field.
|
||||
// If the struct field has a tag "uicolumn", its value is used as the header string instead.
|
||||
//
|
||||
// Tables maintain their own storage behind a sync.RWMutex-compatible sync.Locker; use Table.Lock()/Table.Unlock() to make changes and Table.RLock()/Table.RUnlock() to merely read values.
|
||||
type Table interface {
|
||||
Control
|
||||
|
||||
// Lock and Unlock lock and unlock Data for reading or writing.
|
||||
// RLock and RUnlock lock and unlock Data for reading only.
|
||||
// These methods have identical semantics to the analogous methods of sync.RWMutex.
|
||||
// In addition, Unlock() will request an update of the Table to account for whatever was changed.
|
||||
Lock()
|
||||
Unlock()
|
||||
RLock()
|
||||
RUnlock()
|
||||
|
||||
// Data returns the internal data.
|
||||
// The returned value will contain an object of type pointer to slice of some structure; use a type assertion to get the properly typed object out.
|
||||
// Do not call this outside a Lock()..Unlock() or RLock()..RUnlock() pair.
|
||||
Data() interface{}
|
||||
|
||||
// Selected and Select get and set the currently selected item in the Table.
|
||||
// Selected returns -1 if no item is selected.
|
||||
// Pass -1 to Select to deselect all items.
|
||||
Selected() int
|
||||
Select(index int)
|
||||
|
||||
// OnSelected is an event that gets triggered after the selection in the Table changes in whatever way (item selected or item deselected).
|
||||
OnSelected(func())
|
||||
}
|
||||
|
||||
type tablebase struct {
|
||||
lock sync.RWMutex
|
||||
data interface{}
|
||||
}
|
||||
|
||||
// NewTable creates a new Table.
|
||||
// Currently, the argument must be a reflect.Type representing the structure that each item in the Table will hold, and the Table will be initially empty.
|
||||
// This will change in the future.
|
||||
func NewTable(ty reflect.Type) Table {
|
||||
if ty.Kind() != reflect.Struct {
|
||||
panic(fmt.Errorf("unknown or unsupported type %v given to NewTable()", ty))
|
||||
}
|
||||
b := new(tablebase)
|
||||
// we want a pointer to a slice
|
||||
b.data = reflect.New(reflect.SliceOf(ty)).Interface()
|
||||
return finishNewTable(b, ty)
|
||||
}
|
||||
|
||||
func (b *tablebase) Lock() {
|
||||
b.lock.Lock()
|
||||
}
|
||||
|
||||
// Unlock() is defined on each backend implementation of Table
|
||||
// they should all call this, however
|
||||
func (b *tablebase) unlock() {
|
||||
b.lock.Unlock()
|
||||
}
|
||||
|
||||
func (b *tablebase) RLock() {
|
||||
b.lock.RLock()
|
||||
}
|
||||
|
||||
func (b *tablebase) RUnlock() {
|
||||
b.lock.RUnlock()
|
||||
}
|
||||
|
||||
func (b *tablebase) Data() interface{} {
|
||||
return b.data
|
||||
}
|
|
@ -1,138 +0,0 @@
|
|||
// 29 july 2014
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
"image"
|
||||
)
|
||||
|
||||
// #include "objc_darwin.h"
|
||||
import "C"
|
||||
|
||||
type table struct {
|
||||
*tablebase
|
||||
|
||||
*scroller
|
||||
|
||||
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++ {
|
||||
colname := ty.Field(i).Tag.Get("uicolumn")
|
||||
if colname == "" {
|
||||
colname = ty.Field(i).Name
|
||||
}
|
||||
cname := C.CString(colname)
|
||||
coltype := C.colTypeText
|
||||
editable := false
|
||||
switch {
|
||||
case ty.Field(i).Type == reflect.TypeOf((*image.RGBA)(nil)):
|
||||
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) 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((*image.RGBA)(nil)):
|
||||
*outtype = C.colTypeImage
|
||||
d := datum.Interface().(*image.RGBA)
|
||||
img := C.toTableImage(unsafe.Pointer(pixelData(d)), C.intptr_t(d.Rect.Dx()), C.intptr_t(d.Rect.Dy()), C.intptr_t(d.Stride))
|
||||
return unsafe.Pointer(img)
|
||||
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)
|
||||
}
|
|
@ -1,195 +0,0 @@
|
|||
// 29 july 2014
|
||||
|
||||
#import "objc_darwin.h"
|
||||
#import "_cgo_export.h"
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#define toNSTableView(x) ((NSTableView *) (x))
|
||||
|
||||
// NSTableColumn provides no provision to store an integer data
|
||||
// it does provide an identifier tag, but that's a NSString, and I'd rather not risk the conversion overhead
|
||||
@interface goTableColumn : NSTableColumn {
|
||||
@public
|
||||
intptr_t gocolnum;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation goTableColumn
|
||||
@end
|
||||
|
||||
@interface goTableDataSource : NSObject <NSTableViewDataSource, NSTableViewDelegate> {
|
||||
@public
|
||||
void *gotable;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation goTableDataSource
|
||||
|
||||
- (NSInteger)numberOfRowsInTableView:(NSTableView *)view
|
||||
{
|
||||
return (NSInteger) goTableDataSource_getRowCount(self->gotable);
|
||||
}
|
||||
|
||||
- (id)tableView:(NSTableView *)view objectValueForTableColumn:(NSTableColumn *)col row:(NSInteger)row
|
||||
{
|
||||
void *ret;
|
||||
NSString *s;
|
||||
intptr_t colnum;
|
||||
char *str;
|
||||
int type = colTypeText;
|
||||
|
||||
colnum = ((goTableColumn *) col)->gocolnum;
|
||||
ret = goTableDataSource_getValue(self->gotable, (intptr_t) row, colnum, &type);
|
||||
switch (type) {
|
||||
case colTypeImage:
|
||||
// TODO free the returned image when done somehow
|
||||
return (id) ret;
|
||||
case colTypeCheckbox:
|
||||
if (ret == NULL)
|
||||
return nil;
|
||||
return (id) [NSNumber numberWithInt:1];
|
||||
}
|
||||
str = (char *) ret;
|
||||
s = [NSString stringWithUTF8String:str];
|
||||
free(str); // allocated with C.CString() on the Go side
|
||||
return (id) s;
|
||||
}
|
||||
|
||||
- (void)tableView:(NSTableView *)view setObjectValue:(id)value forTableColumn:(NSTableColumn *)col row:(NSInteger)row
|
||||
{
|
||||
intptr_t colnum;
|
||||
NSNumber *number = (NSNumber *) value; // thanks to mikeash in irc.freenode.net/#macdev
|
||||
|
||||
colnum = ((goTableColumn *) col)->gocolnum;
|
||||
goTableDataSource_toggled(self->gotable, (intptr_t) row, colnum, [number boolValue]);
|
||||
}
|
||||
|
||||
- (void)tableViewSelectionDidChange:(NSNotification *)note
|
||||
{
|
||||
tableSelectionChanged(self->gotable);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
id newTable(void)
|
||||
{
|
||||
NSTableView *t;
|
||||
|
||||
t = [[NSTableView alloc] initWithFrame:NSZeroRect];
|
||||
[t setAllowsColumnReordering:NO];
|
||||
[t setAllowsColumnResizing:YES];
|
||||
[t setAllowsMultipleSelection:NO];
|
||||
[t setAllowsEmptySelection:YES];
|
||||
[t setAllowsColumnSelection:NO];
|
||||
return (id) t;
|
||||
}
|
||||
|
||||
void tableAppendColumn(id t, intptr_t colnum, char *name, int type, BOOL editable)
|
||||
{
|
||||
goTableColumn *c;
|
||||
NSImageCell *ic;
|
||||
NSButtonCell *bc;
|
||||
NSLineBreakMode lbm = NSLineBreakByTruncatingTail; // default for most types
|
||||
|
||||
c = [[goTableColumn alloc] initWithIdentifier:nil];
|
||||
c->gocolnum = colnum;
|
||||
switch (type) {
|
||||
case colTypeImage:
|
||||
ic = [[NSImageCell alloc] initImageCell:nil];
|
||||
// this is the behavior we want, which differs from the Interface Builder default of proportionally down
|
||||
[ic setImageScaling:NSImageScaleProportionallyUpOrDown];
|
||||
// these two, however, ARE Interface Builder defaults
|
||||
[ic setImageFrameStyle:NSImageFrameNone];
|
||||
[ic setImageAlignment:NSImageAlignCenter];
|
||||
[c setDataCell:ic];
|
||||
break;
|
||||
case colTypeCheckbox:
|
||||
bc = [[NSButtonCell alloc] init];
|
||||
[bc setBezelStyle:NSRegularSquareBezelStyle]; // not explicitly stated as such in Interface Builder; extracted with a test program
|
||||
[bc setButtonType:NSSwitchButton];
|
||||
[bc setBordered:NO];
|
||||
[bc setTransparent:NO];
|
||||
[bc setAllowsMixedState:NO];
|
||||
[bc setTitle:@""]; // no label
|
||||
lbm = NSLineBreakByWordWrapping; // Interface Builder sets this mode for this type
|
||||
[c setDataCell:bc];
|
||||
break;
|
||||
}
|
||||
// otherwise just use the current cell
|
||||
[c setEditable:editable];
|
||||
[[c headerCell] setStringValue:[NSString stringWithUTF8String:name]];
|
||||
setSmallControlFont((id) [c headerCell]);
|
||||
setStandardControlFont((id) [c dataCell]);
|
||||
// the following are according to Interface Builder
|
||||
// for the header cell, a stub program had to be written because Interface Builder doesn't support editing header cells directly
|
||||
[[c headerCell] setScrollable:NO];
|
||||
[[c headerCell] setWraps:NO];
|
||||
[[c headerCell] setLineBreakMode:NSLineBreakByTruncatingTail];
|
||||
[[c headerCell] setUsesSingleLineMode:NO];
|
||||
[[c headerCell] setTruncatesLastVisibleLine:NO];
|
||||
[[c dataCell] setScrollable:NO];
|
||||
[[c dataCell] setWraps:NO];
|
||||
[[c dataCell] setLineBreakMode:lbm];
|
||||
[[c dataCell] setUsesSingleLineMode:NO];
|
||||
[[c dataCell] setTruncatesLastVisibleLine:NO];
|
||||
[toNSTableView(t) addTableColumn:c];
|
||||
}
|
||||
|
||||
void tableUpdate(id t)
|
||||
{
|
||||
[toNSTableView(t) reloadData];
|
||||
}
|
||||
|
||||
// also sets the delegate
|
||||
void tableMakeDataSource(id table, void *gotable)
|
||||
{
|
||||
goTableDataSource *model;
|
||||
|
||||
model = [goTableDataSource new];
|
||||
model->gotable = gotable;
|
||||
[toNSTableView(table) setDataSource:model];
|
||||
[toNSTableView(table) setDelegate:model];
|
||||
}
|
||||
|
||||
// -[NSTableView sizeToFit] does not actually size to fit
|
||||
// -[NSTableColumn sizeToFit] is just for the header
|
||||
// -[NSTableColumn sizeToFit] can work for guessing but overrides user settings
|
||||
// -[[NSTableColumn headerCell] cellSize] does NOT (despite being documented as returning the minimum needed size)
|
||||
// Let's write our own to prefer:
|
||||
// - width of the sum of all columns's current widths
|
||||
// - height of 5 rows (arbitrary count; seems reasonable) + header view height
|
||||
// Hopefully this is reasonable.
|
||||
struct xsize tablePreferredSize(id control)
|
||||
{
|
||||
NSTableView *t;
|
||||
NSArray *columns;
|
||||
struct xsize s;
|
||||
NSUInteger i, n;
|
||||
NSTableColumn *c;
|
||||
|
||||
t = toNSTableView(control);
|
||||
columns = [t tableColumns];
|
||||
n = [columns count];
|
||||
s.width = 0;
|
||||
for (i = 0; i < n; i++) {
|
||||
CGFloat width;
|
||||
|
||||
c = (NSTableColumn *) [columns objectAtIndex:i];
|
||||
s.width += (intptr_t) [c width];
|
||||
}
|
||||
s.height = 5 * (intptr_t) [t rowHeight];
|
||||
s.height += (intptr_t) [[t headerView] frame].size.height;
|
||||
return s;
|
||||
}
|
||||
|
||||
intptr_t tableSelected(id table)
|
||||
{
|
||||
return (intptr_t) [toNSTableView(table) selectedRow];
|
||||
}
|
||||
|
||||
void tableSelect(id table, intptr_t row)
|
||||
{
|
||||
[toNSTableView(table) deselectAll:table];
|
||||
if (row != -1)
|
||||
[toNSTableView(table) selectRowIndexes:[NSIndexSet indexSetWithIndex:((NSUInteger) row)] byExtendingSelection:NO];
|
||||
}
|
|
@ -1,270 +0,0 @@
|
|||
// +build !windows,!darwin
|
||||
|
||||
// 29 july 2014
|
||||
|
||||
#include "gtk_unix.h"
|
||||
#include "_cgo_export.h"
|
||||
|
||||
void tableAppendColumn(GtkTreeView *table, gint index, gchar *name, GtkCellRenderer *renderer, gchar *attribute)
|
||||
{
|
||||
GtkTreeViewColumn *col;
|
||||
|
||||
col = gtk_tree_view_column_new_with_attributes(name, renderer,
|
||||
attribute, index,
|
||||
NULL);
|
||||
// allow columns to be resized
|
||||
gtk_tree_view_column_set_resizable(col, TRUE);
|
||||
gtk_tree_view_append_column(table, col);
|
||||
}
|
||||
|
||||
/*
|
||||
how our GtkTreeIters are stored:
|
||||
stamp: either GOOD_STAMP or BAD_STAMP
|
||||
user_data: row index
|
||||
Thanks to Company in irc.gimp.net/#gtk+ for suggesting the GSIZE_TO_POINTER() trick.
|
||||
*/
|
||||
#define GOOD_STAMP 0x1234
|
||||
#define BAD_STAMP 0x5678
|
||||
#define FROM(x) ((gint) GPOINTER_TO_SIZE((x)))
|
||||
#define TO(x) GSIZE_TO_POINTER((gsize) (x))
|
||||
|
||||
static void goTableModel_initGtkTreeModel(GtkTreeModelIface *);
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE(goTableModel, goTableModel, G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE(GTK_TYPE_TREE_MODEL, goTableModel_initGtkTreeModel))
|
||||
|
||||
static void goTableModel_init(goTableModel *t)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
static void goTableModel_dispose(GObject *obj)
|
||||
{
|
||||
G_OBJECT_CLASS(goTableModel_parent_class)->dispose(obj);
|
||||
}
|
||||
|
||||
static void goTableModel_finalize(GObject *obj)
|
||||
{
|
||||
G_OBJECT_CLASS(goTableModel_parent_class)->finalize(obj);
|
||||
}
|
||||
|
||||
// and now for the interface function definitions
|
||||
|
||||
static GtkTreeModelFlags goTableModel_get_flags(GtkTreeModel *model)
|
||||
{
|
||||
return GTK_TREE_MODEL_LIST_ONLY;
|
||||
}
|
||||
|
||||
// get_n_columns and get_column_type in Go
|
||||
|
||||
static gboolean goTableModel_get_iter(GtkTreeModel *model, GtkTreeIter *iter, GtkTreePath *path)
|
||||
{
|
||||
goTableModel *t = (goTableModel *) model;
|
||||
gint index;
|
||||
|
||||
if (gtk_tree_path_get_depth(path) != 1)
|
||||
goto bad;
|
||||
index = gtk_tree_path_get_indices(path)[0];
|
||||
if (index < 0)
|
||||
goto bad;
|
||||
if (index >= goTableModel_getRowCount(t->gotable))
|
||||
goto bad;
|
||||
iter->stamp = GOOD_STAMP;
|
||||
iter->user_data = TO(index);
|
||||
return TRUE;
|
||||
bad:
|
||||
iter->stamp = BAD_STAMP;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static GtkTreePath *goTableModel_get_path(GtkTreeModel *model, GtkTreeIter *iter)
|
||||
{
|
||||
// note: from this point forward, the GOOD_STAMP checks ensure that the index stored in iter is nonnegative
|
||||
if (iter->stamp != GOOD_STAMP)
|
||||
return NULL; // this is what both GtkListStore and GtkTreeStore do
|
||||
return gtk_tree_path_new_from_indices(FROM(iter->user_data), -1);
|
||||
}
|
||||
|
||||
static void goTableModel_get_value(GtkTreeModel *model, GtkTreeIter *iter, gint column, GValue *value)
|
||||
{
|
||||
goTableModel *t = (goTableModel *) model;
|
||||
|
||||
if (iter->stamp != GOOD_STAMP)
|
||||
return; // this is what both GtkListStore and GtkTreeStore do
|
||||
goTableModel_do_get_value(t->gotable, FROM(iter->user_data), column, value);
|
||||
}
|
||||
|
||||
static gboolean goTableModel_iter_next(GtkTreeModel *model, GtkTreeIter *iter)
|
||||
{
|
||||
goTableModel *t = (goTableModel *) model;
|
||||
gint index;
|
||||
|
||||
if (iter->stamp != GOOD_STAMP)
|
||||
return FALSE; // this is what both GtkListStore and GtkTreeStore do
|
||||
index = FROM(iter->user_data);
|
||||
index++;
|
||||
if (index >= goTableModel_getRowCount(t->gotable)) {
|
||||
iter->stamp = BAD_STAMP;
|
||||
return FALSE;
|
||||
}
|
||||
iter->user_data = TO(index);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean goTableModel_iter_previous(GtkTreeModel *model, GtkTreeIter *iter)
|
||||
{
|
||||
goTableModel *t = (goTableModel *) model;
|
||||
gint index;
|
||||
|
||||
if (iter->stamp != GOOD_STAMP)
|
||||
return FALSE; // this is what both GtkListStore and GtkTreeStore do
|
||||
index = FROM(iter->user_data);
|
||||
if (index <= 0) {
|
||||
iter->stamp = BAD_STAMP;
|
||||
return FALSE;
|
||||
}
|
||||
index--;
|
||||
iter->user_data = TO(index);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean goTableModel_iter_children(GtkTreeModel *model, GtkTreeIter *child, GtkTreeIter *parent)
|
||||
{
|
||||
goTableModel *t = (goTableModel *) model;
|
||||
|
||||
if (parent == NULL && goTableModel_getRowCount(t->gotable) > 0) {
|
||||
child->stamp = GOOD_STAMP;
|
||||
child->user_data = 0;
|
||||
return TRUE;
|
||||
}
|
||||
child->stamp = BAD_STAMP;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean goTableModel_iter_has_child(GtkTreeModel *model, GtkTreeIter *iter)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gint goTableModel_iter_n_children(GtkTreeModel *model, GtkTreeIter *iter)
|
||||
{
|
||||
goTableModel *t = (goTableModel *) model;
|
||||
|
||||
if (iter == NULL)
|
||||
return goTableModel_getRowCount(t->gotable);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static gboolean goTableModel_iter_nth_child(GtkTreeModel *model, GtkTreeIter *child, GtkTreeIter *parent, gint n)
|
||||
{
|
||||
goTableModel *t = (goTableModel *) model;
|
||||
|
||||
if (parent == NULL && n >= 0 && n < goTableModel_getRowCount(t->gotable)) {
|
||||
child->stamp = GOOD_STAMP;
|
||||
child->user_data = TO(n);
|
||||
return TRUE;
|
||||
}
|
||||
child->stamp = BAD_STAMP;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean goTableModel_iter_parent(GtkTreeModel *model, GtkTreeIter *parent, GtkTreeIter *child)
|
||||
{
|
||||
parent->stamp = BAD_STAMP;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// end of interface definitions
|
||||
|
||||
static void goTableModel_initGtkTreeModel(GtkTreeModelIface *interface)
|
||||
{
|
||||
// don't chain; we have nothing to chain to
|
||||
#define DEF(x) interface->x = goTableModel_ ## x;
|
||||
DEF(get_flags)
|
||||
DEF(get_n_columns)
|
||||
DEF(get_column_type)
|
||||
DEF(get_iter)
|
||||
DEF(get_path)
|
||||
DEF(get_value)
|
||||
DEF(iter_next)
|
||||
DEF(iter_previous)
|
||||
DEF(iter_children)
|
||||
DEF(iter_has_child)
|
||||
DEF(iter_n_children)
|
||||
DEF(iter_nth_child)
|
||||
DEF(iter_parent)
|
||||
// no need for ref_node and unref_node
|
||||
}
|
||||
|
||||
static GParamSpec *goTableModelProperties[2];
|
||||
|
||||
static void goTableModel_set_property(GObject *obj, guint id, const GValue *value, GParamSpec *pspec)
|
||||
{
|
||||
goTableModel *t = (goTableModel *) obj;
|
||||
|
||||
if (id == 1) {
|
||||
t->gotable = (void *) g_value_get_pointer(value);
|
||||
return;
|
||||
}
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(t, id, pspec);
|
||||
}
|
||||
|
||||
static void goTableModel_get_property(GObject *obj, guint id, GValue *value, GParamSpec *pspec)
|
||||
{
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID((goTableModel *) obj, id, pspec);
|
||||
}
|
||||
|
||||
static void goTableModel_class_init(goTableModelClass *class)
|
||||
{
|
||||
G_OBJECT_CLASS(class)->dispose = goTableModel_dispose;
|
||||
G_OBJECT_CLASS(class)->finalize = goTableModel_finalize;
|
||||
G_OBJECT_CLASS(class)->set_property = goTableModel_set_property;
|
||||
G_OBJECT_CLASS(class)->get_property = goTableModel_get_property;
|
||||
|
||||
goTableModelProperties[1] = g_param_spec_pointer(
|
||||
"gotable",
|
||||
"go-table",
|
||||
"Go-side *table value",
|
||||
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
|
||||
g_object_class_install_properties(G_OBJECT_CLASS(class), 2, goTableModelProperties);
|
||||
}
|
||||
|
||||
goTableModel *newTableModel(void *gotable)
|
||||
{
|
||||
return (goTableModel *) g_object_new(goTableModel_get_type(), "gotable", (gpointer) gotable, NULL);
|
||||
}
|
||||
|
||||
// somewhat naive, but the only alternatives seem to be unloading/reloading the model (or the view!), which is bleh
|
||||
void tableUpdate(goTableModel *t, gint old, gint new)
|
||||
{
|
||||
gint i;
|
||||
gint nUpdate;
|
||||
GtkTreePath *path;
|
||||
GtkTreeIter iter;
|
||||
|
||||
iter.stamp = GOOD_STAMP;
|
||||
// first, append extra items
|
||||
if (old < new) {
|
||||
for (i = old; i < new; i++) {
|
||||
path = gtk_tree_path_new_from_indices(i, -1);
|
||||
iter.user_data = TO(i);
|
||||
g_signal_emit_by_name(t, "row-inserted", path, &iter);
|
||||
}
|
||||
nUpdate = old;
|
||||
} else
|
||||
nUpdate = new;
|
||||
// next, update existing items
|
||||
for (i = 0; i < nUpdate; i++) {
|
||||
path = gtk_tree_path_new_from_indices(i, -1);
|
||||
iter.user_data = TO(i);
|
||||
g_signal_emit_by_name(t, "row-changed", path, &iter);
|
||||
}
|
||||
// finally, remove deleted items
|
||||
if (old > new)
|
||||
for (i = new; i < old; i++) {
|
||||
// note that we repeatedly remove the row at index new, as that changes with each removal; NOT i
|
||||
path = gtk_tree_path_new_from_indices(new, -1);
|
||||
// row-deleted has no iter
|
||||
g_signal_emit_by_name(t, "row-deleted", path);
|
||||
}
|
||||
}
|
|
@ -1,224 +0,0 @@
|
|||
// +build !windows,!darwin
|
||||
|
||||
// 29 july 2014
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
"image"
|
||||
)
|
||||
|
||||
// #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
|
||||
|
||||
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++ {
|
||||
colname := ty.Field(i).Tag.Get("uicolumn")
|
||||
if colname == "" {
|
||||
colname = ty.Field(i).Name
|
||||
}
|
||||
cname := togstr(colname)
|
||||
switch {
|
||||
case ty.Field(i).Type == reflect.TypeOf((*image.RGBA)(nil)):
|
||||
// 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) 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((*image.RGBA)(nil)):
|
||||
d := datum.Interface().(*image.RGBA)
|
||||
pixbuf := toIconSizedGdkPixbuf(d)
|
||||
C.g_value_init(value, C.gdk_pixbuf_get_type())
|
||||
object := C.gpointer(unsafe.Pointer(pixbuf))
|
||||
// use g_value_take_object() so the GtkTreeView becomes the pixbuf's owner
|
||||
C.g_value_take_object(value, object)
|
||||
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()
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
// 28 july 2014
|
||||
|
||||
#include "winapi_windows.h"
|
||||
#include "_cgo_export.h"
|
||||
|
||||
#include "wintable/main.h"
|
||||
|
||||
// provided for cgo's benefit
|
||||
LPWSTR xtableWindowClass = tableWindowClass;
|
||||
|
||||
void doInitTable(void)
|
||||
{
|
||||
initTable(xpanic, fv__TrackMouseEvent);
|
||||
}
|
||||
|
||||
static LRESULT CALLBACK tableSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR id, DWORD_PTR data)
|
||||
{
|
||||
NMHDR *nmhdr = (NMHDR *) lParam;
|
||||
tableNM *tnm = (tableNM *) lParam;
|
||||
void *gotable = (void *) data;
|
||||
|
||||
switch (uMsg) {
|
||||
case msgNOTIFY:
|
||||
switch (nmhdr->code) {
|
||||
case tableNotificationGetCellData:
|
||||
return tableGetCell(gotable, tnm);
|
||||
case tableNotificationFinishedWithCellData:
|
||||
tableFreeCellData(gotable, tnm->data);
|
||||
return 0;
|
||||
case tableNotificationCellCheckboxToggled:
|
||||
tableToggled(gotable, tnm->row, tnm->column);
|
||||
return 0;
|
||||
case tableNotificationSelectionChanged:
|
||||
tableSelectionChanged(gotable);
|
||||
return 0;
|
||||
}
|
||||
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
|
||||
/* TODO
|
||||
// see table.autoresize() in table_windows.go for the column autosize policy
|
||||
case WM_NOTIFY: // from the contained header control
|
||||
if (nmhdr->code == HDN_BEGINTRACK)
|
||||
tableStopColumnAutosize(t->gotable);
|
||||
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
|
||||
*/
|
||||
case WM_NCDESTROY:
|
||||
if ((*fv_RemoveWindowSubclass)(hwnd, tableSubProc, id) == FALSE)
|
||||
xpanic("error removing Table subclass (which was for its own event handler)", GetLastError());
|
||||
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
|
||||
default:
|
||||
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
xmissedmsg("Table", "tableSubProc()", uMsg);
|
||||
return 0; // unreached
|
||||
}
|
||||
|
||||
void setTableSubclass(HWND hwnd, void *data)
|
||||
{
|
||||
if ((*fv_SetWindowSubclass)(hwnd, tableSubProc, 0, (DWORD_PTR) data) == FALSE)
|
||||
xpanic("error subclassing Table to give it its own event handler", GetLastError());
|
||||
}
|
||||
|
||||
// TODO rename all of these functions to start with gotable, and all the exported ones in Go too
|
||||
void gotableSetRowCount(HWND hwnd, intptr_t count)
|
||||
{
|
||||
SendMessageW(hwnd, tableSetRowCount, 0, (LPARAM) (&count));
|
||||
}
|
||||
|
||||
void tableAutosizeColumns(HWND hwnd, int nColumns)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < nColumns; i++)
|
||||
if (SendMessageW(hwnd, LVM_SETCOLUMNWIDTH, (WPARAM) i, (LPARAM) LVSCW_AUTOSIZE_USEHEADER) == FALSE)
|
||||
xpanic("error resizing columns of results list view", GetLastError());
|
||||
}
|
||||
|
||||
intptr_t tableSelectedItem(HWND hwnd)
|
||||
{
|
||||
intptr_t row;
|
||||
|
||||
SendMessageW(hwnd, tableGetSelection, (WPARAM) (&row), (LPARAM) NULL);
|
||||
return row;
|
||||
}
|
||||
|
||||
void tableSelectItem(HWND hwnd, intptr_t index)
|
||||
{
|
||||
SendMessageW(hwnd, tableSetSelection, (WPARAM) (&index), (LPARAM) NULL);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue