// Copyright 2010 The Walk Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // +build windows package walk import ( "syscall" "unsafe" ) import ( "github.com/lxn/win" ) type CaseMode uint32 const ( CaseModeMixed CaseMode = iota CaseModeUpper CaseModeLower ) const ( lineEditMinChars = 1 // 10 // number of characters needed to make a LineEdit usable lineEditGreedyLimit = 29 // 80 // fields with MaxLength larger than this will be greedy (default length is 32767) ) type LineEdit struct { WidgetBase editingFinishedPublisher EventPublisher readOnlyChangedPublisher EventPublisher textChangedPublisher EventPublisher charWidthFont *Font charWidth int // in native pixels textColor Color } func newLineEdit(parent Window) (*LineEdit, error) { le := new(LineEdit) if err := InitWindow( le, parent, "EDIT", win.WS_CHILD|win.WS_TABSTOP|win.WS_VISIBLE|win.ES_AUTOHSCROLL, win.WS_EX_CLIENTEDGE); err != nil { return nil, err } le.GraphicsEffects().Add(InteractionEffect) le.GraphicsEffects().Add(FocusEffect) le.MustRegisterProperty("ReadOnly", NewProperty( func() interface{} { return le.ReadOnly() }, func(v interface{}) error { return le.SetReadOnly(v.(bool)) }, le.readOnlyChangedPublisher.Event())) le.MustRegisterProperty("Text", NewProperty( func() interface{} { return le.Text() }, func(v interface{}) error { return le.SetText(assertStringOr(v, "")) }, le.textChangedPublisher.Event())) return le, nil } func NewLineEdit(parent Container) (*LineEdit, error) { if parent == nil { return nil, newError("parent cannot be nil") } le, err := newLineEdit(parent) if err != nil { return nil, err } var succeeded bool defer func() { if !succeeded { le.Dispose() } }() le.parent = parent if err = parent.Children().Add(le); err != nil { return nil, err } succeeded = true return le, nil } func (le *LineEdit) CueBanner() string { buf := make([]uint16, 128) if win.FALSE == le.SendMessage(win.EM_GETCUEBANNER, uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf))) { newError("EM_GETCUEBANNER failed") return "" } return syscall.UTF16ToString(buf) } func (le *LineEdit) SetCueBanner(value string) error { if win.FALSE == le.SendMessage(win.EM_SETCUEBANNER, win.FALSE, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(value)))) { return newError("EM_SETCUEBANNER failed") } return nil } func (le *LineEdit) MaxLength() int { return int(le.SendMessage(win.EM_GETLIMITTEXT, 0, 0)) } func (le *LineEdit) SetMaxLength(value int) { le.SendMessage(win.EM_LIMITTEXT, uintptr(value), 0) } func (le *LineEdit) Text() string { return le.text() } func (le *LineEdit) SetText(value string) error { return le.setText(value) } func (le *LineEdit) TextSelection() (start, end int) { le.SendMessage(win.EM_GETSEL, uintptr(unsafe.Pointer(&start)), uintptr(unsafe.Pointer(&end))) return } func (le *LineEdit) SetTextSelection(start, end int) { le.SendMessage(win.EM_SETSEL, uintptr(start), uintptr(end)) } func (le *LineEdit) TextAlignment() Alignment1D { switch win.GetWindowLong(le.hWnd, win.GWL_STYLE) & (win.ES_LEFT | win.ES_CENTER | win.ES_RIGHT) { case win.ES_CENTER: return AlignCenter case win.ES_RIGHT: return AlignFar } return AlignNear } func (le *LineEdit) SetTextAlignment(alignment Alignment1D) error { if alignment == AlignDefault { alignment = AlignNear } var bit uint32 switch alignment { case AlignCenter: bit = win.ES_CENTER case AlignFar: bit = win.ES_RIGHT default: bit = win.ES_LEFT } return le.setAndClearStyleBits(bit, win.ES_LEFT|win.ES_CENTER|win.ES_RIGHT) } func (le *LineEdit) CaseMode() CaseMode { style := uint32(win.GetWindowLong(le.hWnd, win.GWL_STYLE)) if style&win.ES_UPPERCASE != 0 { return CaseModeUpper } else if style&win.ES_LOWERCASE != 0 { return CaseModeLower } else { return CaseModeMixed } } func (le *LineEdit) SetCaseMode(mode CaseMode) error { var set, clear uint32 switch mode { case CaseModeMixed: clear = win.ES_UPPERCASE | win.ES_LOWERCASE case CaseModeUpper: set = win.ES_UPPERCASE clear = win.ES_LOWERCASE case CaseModeLower: set = win.ES_LOWERCASE clear = win.ES_UPPERCASE default: panic("invalid CaseMode") } return le.setAndClearStyleBits(set, clear) } func (le *LineEdit) PasswordMode() bool { return le.SendMessage(win.EM_GETPASSWORDCHAR, 0, 0) != 0 } func (le *LineEdit) SetPasswordMode(value bool) { var c uintptr if value { c = uintptr('*') } le.SendMessage(win.EM_SETPASSWORDCHAR, c, 0) } func (le *LineEdit) ReadOnly() bool { return le.hasStyleBits(win.ES_READONLY) } func (le *LineEdit) SetReadOnly(readOnly bool) error { if 0 == le.SendMessage(win.EM_SETREADONLY, uintptr(win.BoolToBOOL(readOnly)), 0) { return newError("SendMessage(EM_SETREADONLY)") } if readOnly != le.ReadOnly() { le.invalidateBorderInParent() } le.readOnlyChangedPublisher.Publish() return nil } // sizeHintForLimit returns size hint for given limit in native pixels func (le *LineEdit) sizeHintForLimit(limit int) (size Size) { size = le.dialogBaseUnitsToPixels(Size{50, 12}) le.initCharWidth() n := le.MaxLength() if n > limit { n = limit } size.Width = le.charWidth * (n + 1) return } func (le *LineEdit) initCharWidth() { font := le.Font() if font == le.charWidthFont { return } le.charWidthFont = font le.charWidth = 8 hdc := win.GetDC(le.hWnd) if hdc == 0 { newError("GetDC failed") return } defer win.ReleaseDC(le.hWnd, hdc) defer win.SelectObject(hdc, win.SelectObject(hdc, win.HGDIOBJ(font.handleForDPI(le.DPI())))) buf := []uint16{'M'} var s win.SIZE if !win.GetTextExtentPoint32(hdc, &buf[0], int32(len(buf)), &s) { newError("GetTextExtentPoint32 failed") return } le.charWidth = int(s.CX) } func (le *LineEdit) EditingFinished() *Event { return le.editingFinishedPublisher.Event() } func (le *LineEdit) TextChanged() *Event { return le.textChangedPublisher.Event() } func (le *LineEdit) TextColor() Color { return le.textColor } func (le *LineEdit) SetTextColor(c Color) { le.textColor = c le.Invalidate() } func (*LineEdit) NeedsWmSize() bool { return true } func (le *LineEdit) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_COMMAND: switch win.HIWORD(uint32(wParam)) { case win.EN_CHANGE: le.textChangedPublisher.Publish() } case win.WM_GETDLGCODE: if form := ancestor(le); form != nil { if dlg, ok := form.(dialogish); ok { if dlg.DefaultButton() != nil { // If the LineEdit lives in a Dialog that has a DefaultButton, // we won't swallow the return key. break } } } if wParam == win.VK_RETURN { return win.DLGC_WANTALLKEYS } case win.WM_KEYDOWN: switch Key(wParam) { case KeyA: if ControlDown() { le.SetTextSelection(0, -1) } case KeyReturn: le.editingFinishedPublisher.Publish() } case win.WM_KILLFOCUS: // FIXME: This may be dangerous, see remarks section: // http://msdn.microsoft.com/en-us/library/ms646282(v=vs.85).aspx le.editingFinishedPublisher.Publish() } return le.WidgetBase.WndProc(hwnd, msg, wParam, lParam) } func (le *LineEdit) CreateLayoutItem(ctx *LayoutContext) LayoutItem { lf := ShrinkableHorz | GrowableHorz if le.MaxLength() > lineEditGreedyLimit { lf |= GreedyHorz } return &lineEditLayoutItem{ layoutFlags: lf, idealSize: le.sizeHintForLimit(lineEditGreedyLimit), minSize: le.sizeHintForLimit(lineEditMinChars), } } type lineEditLayoutItem struct { LayoutItemBase layoutFlags LayoutFlags idealSize Size // in native pixels minSize Size // in native pixels } func (li *lineEditLayoutItem) LayoutFlags() LayoutFlags { return li.layoutFlags } func (li *lineEditLayoutItem) IdealSize() Size { return li.idealSize } func (li *lineEditLayoutItem) MinSize() Size { return li.minSize }