Repository: lxn/walk Branch: master Commit: c389da54e794 Files: 254 Total size: 1.1 MB Directory structure: gitextract_aroynwxm/ ├── AUTHORS ├── LICENSE ├── README.mdown ├── accessibility.go ├── action.go ├── actionlist.go ├── application.go ├── bitmap.go ├── boxlayout.go ├── brush.go ├── button.go ├── cancelevent.go ├── canvas.go ├── checkbox.go ├── clipboard.go ├── closeevent.go ├── color.go ├── combobox.go ├── commondialogs.go ├── composite.go ├── condition.go ├── container.go ├── cursor.go ├── customwidget.go ├── databinding.go ├── dateedit.go ├── datelabel.go ├── declarative/ │ ├── accessibility.go │ ├── action.go │ ├── brush.go │ ├── builder.go │ ├── checkbox.go │ ├── combobox.go │ ├── composite.go │ ├── customwidget.go │ ├── databinder.go │ ├── dateedit.go │ ├── datelabel.go │ ├── dialog.go │ ├── font.go │ ├── gradientcomposite.go │ ├── groupbox.go │ ├── imageview.go │ ├── interfaces.go │ ├── label.go │ ├── layouts.go │ ├── lineedit.go │ ├── linklabel.go │ ├── listbox.go │ ├── mainwindow.go │ ├── nonwin.go │ ├── numberedit.go │ ├── numberlabel.go │ ├── progressbar.go │ ├── pushbutton.go │ ├── radiobutton.go │ ├── radiobuttongroup.go │ ├── radiobuttongroupbox.go │ ├── scrollview.go │ ├── separator.go │ ├── slider.go │ ├── spacer.go │ ├── splitbutton.go │ ├── splitter.go │ ├── tableview.go │ ├── tableviewcolumn.go │ ├── tabpage.go │ ├── tabwidget.go │ ├── textedit.go │ ├── textlabel.go │ ├── toolbar.go │ ├── toolbutton.go │ ├── treeview.go │ ├── validators.go │ └── webview.go ├── dialog.go ├── dropfilesevent.go ├── error.go ├── errorevent.go ├── event.go ├── examples/ │ ├── actions/ │ │ ├── actions.exe.manifest │ │ ├── actions.go │ │ └── rsrc.syso │ ├── clipboard/ │ │ ├── clipboard.exe.manifest │ │ ├── clipboard.go │ │ └── rsrc.syso │ ├── databinding/ │ │ ├── databinding.exe.manifest │ │ ├── databinding.go │ │ └── rsrc.syso │ ├── drawing/ │ │ ├── drawing.exe.manifest │ │ ├── drawing.go │ │ └── rsrc.syso │ ├── dropfiles/ │ │ ├── dropfiles.exe.manifest │ │ ├── dropfiles.go │ │ └── rsrc.syso │ ├── externalwidgets/ │ │ ├── externalwidgets.exe.manifest │ │ ├── externalwidgets.go │ │ └── rsrc.syso │ ├── filebrowser/ │ │ ├── filebrowser.exe.manifest │ │ ├── filebrowser.go │ │ └── rsrc.syso │ ├── gradientcomposite/ │ │ ├── gradientcomposite.exe.manifest │ │ ├── gradientcomposite.go │ │ └── rsrc.syso │ ├── imageicon/ │ │ ├── imageicon.exe.manifest │ │ ├── main.go │ │ └── rsrc.syso │ ├── imageview/ │ │ ├── imageview.exe.manifest │ │ ├── imageview.go │ │ └── rsrc.syso │ ├── imageviewer/ │ │ ├── imageviewer.exe.manifest │ │ ├── imageviewer.go │ │ └── rsrc.syso │ ├── img/ │ │ └── README │ ├── linklabel/ │ │ ├── linklabel.exe.manifest │ │ ├── linklabel.go │ │ └── rsrc.syso │ ├── listbox/ │ │ ├── listbox.exe.manifest │ │ ├── listbox.go │ │ └── rsrc.syso │ ├── listbox_ownerdrawing/ │ │ ├── listbox_ownerdrawing.exe.manifest │ │ ├── listbox_ownerdrawing.go │ │ └── rsrc.syso │ ├── logview/ │ │ ├── logview.exe.manifest │ │ ├── logview.go │ │ ├── logviewapp.go │ │ └── rsrc.syso │ ├── multiplepages/ │ │ ├── main.go │ │ ├── multipagemainwindow.go │ │ ├── multiplepages.exe.manifest │ │ └── rsrc.syso │ ├── notifyicon/ │ │ ├── notifyicon.exe.manifest │ │ ├── notifyicon.go │ │ └── rsrc.syso │ ├── progressindicator/ │ │ ├── dialog.ui │ │ ├── dialog_ui.go │ │ ├── pi.go │ │ ├── progressindicator.exe.manifest │ │ └── rsrc.syso │ ├── radiobutton/ │ │ ├── radiobutton.exe.manifest │ │ ├── radiobutton.go │ │ └── rsrc.syso │ ├── settings/ │ │ ├── rsrc.syso │ │ ├── settings.exe.manifest │ │ └── settings.go │ ├── slider/ │ │ ├── rsrc.syso │ │ ├── slider.exe.manifest │ │ └── slider.go │ ├── statusbar/ │ │ ├── rsrc.syso │ │ ├── statusbar.exe.manifest │ │ └── statusbar.go │ ├── tableview/ │ │ ├── rsrc.syso │ │ ├── tableview.exe.manifest │ │ └── tableview.go │ ├── webview/ │ │ ├── rsrc.syso │ │ ├── webview.exe.manifest │ │ └── webview.go │ └── webview_events/ │ ├── rsrc.syso │ ├── webview_events.exe.manifest │ └── webview_events.go ├── expression.go ├── flowlayout.go ├── font.go ├── fontresource.go ├── form.go ├── gradientcomposite.go ├── graphicseffects.go ├── gridlayout.go ├── groupbox.go ├── icon.go ├── iconcache.go ├── image.go ├── imagelist.go ├── imageview.go ├── inifilesettings.go ├── intevent.go ├── intrangeevent.go ├── keyboard.go ├── keyevent.go ├── l10n/ │ ├── update.bat │ ├── walk-de.tr │ └── walk-ko.tr ├── label.go ├── layout.go ├── lineedit.go ├── linklabel.go ├── listbox.go ├── mainloop_cgo.go ├── mainloop_default.go ├── mainwindow.go ├── maptablemodel.go ├── menu.go ├── messagebox.go ├── metafile.go ├── models.go ├── mouseevent.go ├── notifyicon.go ├── numberedit.go ├── numberlabel.go ├── path.go ├── pen.go ├── point.go ├── progressbar.go ├── progressindicator.go ├── property.go ├── pushbutton.go ├── radiobutton.go ├── rectangle.go ├── reflectmodels.go ├── registry.go ├── resourcemanager.go ├── scrollview.go ├── separator.go ├── simpletypes.go ├── size.go ├── slider.go ├── spacer.go ├── splitbutton.go ├── splitter.go ├── splitterhandle.go ├── splitterlayout.go ├── static.go ├── statusbar.go ├── stopwatch.go ├── stringevent.go ├── tableview.go ├── tableviewcolumn.go ├── tableviewcolumnlist.go ├── tabpage.go ├── tabpagelist.go ├── tabwidget.go ├── textedit.go ├── textlabel.go ├── toolbar.go ├── toolbutton.go ├── tools/ │ └── ui2walk/ │ └── ui2walk.go ├── tooltip.go ├── tooltiperrorpresenter.go ├── treeitemevent.go ├── treeview.go ├── util.go ├── validators.go ├── walk.go ├── webview.go ├── webview_dwebbrowserevents2.go ├── webview_events.go ├── webview_idochostuihandler.go ├── webview_ioleclientsite.go ├── webview_ioleinplaceframe.go ├── webview_ioleinplacesite.go ├── widget.go ├── widgetlist.go ├── window.go └── windowgroup.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: AUTHORS ================================================ # This is the official list of 'Walk' authors for copyright purposes. # Names should be added to this file as # Name or Organization # The email address is not required for organizations. # Please keep the list sorted. # Contributors # ============ Alexander Neumann Aman Gupta Anthony Dong Attila Tajti Audrius Karabanovas Benny Siegert Cary Cherng Dmitry Bagdanov Ham Yeongtaek Hill iquanxin James Scholes Jason A. Donenfeld Joseph Watson Joshua D. Sjoding ktye llxwj Mateusz Czapliński Michael Teichgräber Paul Wolf ryujimiya Semyon Tokarev Shawn Sun Simon Rozman Tim Dufrane Vincent Vanackere xoviat evangwt ================================================ FILE: LICENSE ================================================ Copyright (c) 2010 The Walk Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The names of the authors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: README.mdown ================================================ About Walk ========== Walk is a "Windows Application Library Kit" for the Go Programming Language. Its primarily useful for Desktop GUI development, but there is some more stuff. Setup ===== Make sure you have a working Go installation. See [Getting Started](http://golang.org/doc/install.html) ##### Note Walk currently requires Go 1.11.x or later. ##### To Install Now run `go get github.com/lxn/walk` Using Walk ========== The preferred way to create GUIs with Walk is to use its declarative sub package, as illustrated in this small example: ##### `test.go` ```go package main import ( "github.com/lxn/walk" . "github.com/lxn/walk/declarative" "strings" ) func main() { var inTE, outTE *walk.TextEdit MainWindow{ Title: "SCREAMO", MinSize: Size{600, 400}, Layout: VBox{}, Children: []Widget{ HSplitter{ Children: []Widget{ TextEdit{AssignTo: &inTE}, TextEdit{AssignTo: &outTE, ReadOnly: true}, }, }, PushButton{ Text: "SCREAM", OnClicked: func() { outTE.SetText(strings.ToUpper(inTE.Text())) }, }, }, }.Run() } ``` ##### Create Manifest `test.manifest` ```xml PerMonitorV2, PerMonitor True ``` Then either compile the manifest using the [rsrc tool](https://github.com/akavel/rsrc), like this: go get github.com/akavel/rsrc rsrc -manifest test.manifest -o rsrc.syso or rename the `test.manifest` file to `test.exe.manifest` and distribute it with the application instead. ##### Build app In the directory containing `test.go` run go build To get rid of the cmd window, instead run go build -ldflags="-H windowsgui" ##### Run app test.exe ##### Sample Output (Windows 7) ![alt tag](http://i.imgur.com/lUrgE2Q.png) ##### More Examples There are some [examples](examples) that should get you started. Application Manifest Files ========================== Walk requires Common Controls 6. This means that you must put an appropriate application manifest file either next to your executable or embedded as a resource. You can copy one of the application manifest files that come with the examples. To embed a manifest file as a resource, you can use the [rsrc tool](https://github.com/akavel/rsrc). IMPORTANT: If you don't embed a manifest as a resource, then you should not launch your executable before the manifest file is in place. If you do anyway, the program will not run properly. And worse, Windows will not recognize a manifest file, you later drop next to the executable. To fix this, rebuild your executable and only launch it with a manifest file in place. CGo Optimizations ================= The usual default message loop includes calls to win32 API functions, which incurs a decent amount of runtime overhead coming from Go. As an alternative to this, you may compile Walk using an optional C implementation of the main message loop, by passing the `walk_use_cgo` build tag: go build -tags walk_use_cgo ================================================ FILE: accessibility.go ================================================ // 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 "github.com/lxn/win" // AccState enum defines the state of the window/control type AccState int32 // Window/control states const ( AccStateNormal AccState = win.STATE_SYSTEM_NORMAL AccStateUnavailable AccState = win.STATE_SYSTEM_UNAVAILABLE AccStateSelected AccState = win.STATE_SYSTEM_SELECTED AccStateFocused AccState = win.STATE_SYSTEM_FOCUSED AccStatePressed AccState = win.STATE_SYSTEM_PRESSED AccStateChecked AccState = win.STATE_SYSTEM_CHECKED AccStateMixed AccState = win.STATE_SYSTEM_MIXED AccStateIndeterminate AccState = win.STATE_SYSTEM_INDETERMINATE AccStateReadonly AccState = win.STATE_SYSTEM_READONLY AccStateHotTracked AccState = win.STATE_SYSTEM_HOTTRACKED AccStateDefault AccState = win.STATE_SYSTEM_DEFAULT AccStateExpanded AccState = win.STATE_SYSTEM_EXPANDED AccStateCollapsed AccState = win.STATE_SYSTEM_COLLAPSED AccStateBusy AccState = win.STATE_SYSTEM_BUSY AccStateFloating AccState = win.STATE_SYSTEM_FLOATING AccStateMarqueed AccState = win.STATE_SYSTEM_MARQUEED AccStateAnimated AccState = win.STATE_SYSTEM_ANIMATED AccStateInvisible AccState = win.STATE_SYSTEM_INVISIBLE AccStateOffscreen AccState = win.STATE_SYSTEM_OFFSCREEN AccStateSizeable AccState = win.STATE_SYSTEM_SIZEABLE AccStateMoveable AccState = win.STATE_SYSTEM_MOVEABLE AccStateSelfVoicing AccState = win.STATE_SYSTEM_SELFVOICING AccStateFocusable AccState = win.STATE_SYSTEM_FOCUSABLE AccStateSelectable AccState = win.STATE_SYSTEM_SELECTABLE AccStateLinked AccState = win.STATE_SYSTEM_LINKED AccStateTraversed AccState = win.STATE_SYSTEM_TRAVERSED AccStateMultiselectable AccState = win.STATE_SYSTEM_MULTISELECTABLE AccStateExtselectable AccState = win.STATE_SYSTEM_EXTSELECTABLE AccStateAlertLow AccState = win.STATE_SYSTEM_ALERT_LOW AccStateAlertMedium AccState = win.STATE_SYSTEM_ALERT_MEDIUM AccStateAlertHigh AccState = win.STATE_SYSTEM_ALERT_HIGH AccStateProtected AccState = win.STATE_SYSTEM_PROTECTED AccStateHasPopup AccState = win.STATE_SYSTEM_HASPOPUP AccStateValid AccState = win.STATE_SYSTEM_VALID ) // AccRole enum defines the role of the window/control in UI. type AccRole int32 // Window/control system roles const ( AccRoleTitlebar AccRole = win.ROLE_SYSTEM_TITLEBAR AccRoleMenubar AccRole = win.ROLE_SYSTEM_MENUBAR AccRoleScrollbar AccRole = win.ROLE_SYSTEM_SCROLLBAR AccRoleGrip AccRole = win.ROLE_SYSTEM_GRIP AccRoleSound AccRole = win.ROLE_SYSTEM_SOUND AccRoleCursor AccRole = win.ROLE_SYSTEM_CURSOR AccRoleCaret AccRole = win.ROLE_SYSTEM_CARET AccRoleAlert AccRole = win.ROLE_SYSTEM_ALERT AccRoleWindow AccRole = win.ROLE_SYSTEM_WINDOW AccRoleClient AccRole = win.ROLE_SYSTEM_CLIENT AccRoleMenuPopup AccRole = win.ROLE_SYSTEM_MENUPOPUP AccRoleMenuItem AccRole = win.ROLE_SYSTEM_MENUITEM AccRoleTooltip AccRole = win.ROLE_SYSTEM_TOOLTIP AccRoleApplication AccRole = win.ROLE_SYSTEM_APPLICATION AccRoleDocument AccRole = win.ROLE_SYSTEM_DOCUMENT AccRolePane AccRole = win.ROLE_SYSTEM_PANE AccRoleChart AccRole = win.ROLE_SYSTEM_CHART AccRoleDialog AccRole = win.ROLE_SYSTEM_DIALOG AccRoleBorder AccRole = win.ROLE_SYSTEM_BORDER AccRoleGrouping AccRole = win.ROLE_SYSTEM_GROUPING AccRoleSeparator AccRole = win.ROLE_SYSTEM_SEPARATOR AccRoleToolbar AccRole = win.ROLE_SYSTEM_TOOLBAR AccRoleStatusbar AccRole = win.ROLE_SYSTEM_STATUSBAR AccRoleTable AccRole = win.ROLE_SYSTEM_TABLE AccRoleColumnHeader AccRole = win.ROLE_SYSTEM_COLUMNHEADER AccRoleRowHeader AccRole = win.ROLE_SYSTEM_ROWHEADER AccRoleColumn AccRole = win.ROLE_SYSTEM_COLUMN AccRoleRow AccRole = win.ROLE_SYSTEM_ROW AccRoleCell AccRole = win.ROLE_SYSTEM_CELL AccRoleLink AccRole = win.ROLE_SYSTEM_LINK AccRoleHelpBalloon AccRole = win.ROLE_SYSTEM_HELPBALLOON AccRoleCharacter AccRole = win.ROLE_SYSTEM_CHARACTER AccRoleList AccRole = win.ROLE_SYSTEM_LIST AccRoleListItem AccRole = win.ROLE_SYSTEM_LISTITEM AccRoleOutline AccRole = win.ROLE_SYSTEM_OUTLINE AccRoleOutlineItem AccRole = win.ROLE_SYSTEM_OUTLINEITEM AccRolePagetab AccRole = win.ROLE_SYSTEM_PAGETAB AccRolePropertyPage AccRole = win.ROLE_SYSTEM_PROPERTYPAGE AccRoleIndicator AccRole = win.ROLE_SYSTEM_INDICATOR AccRoleGraphic AccRole = win.ROLE_SYSTEM_GRAPHIC AccRoleStatictext AccRole = win.ROLE_SYSTEM_STATICTEXT AccRoleText AccRole = win.ROLE_SYSTEM_TEXT AccRolePushbutton AccRole = win.ROLE_SYSTEM_PUSHBUTTON AccRoleCheckbutton AccRole = win.ROLE_SYSTEM_CHECKBUTTON AccRoleRadiobutton AccRole = win.ROLE_SYSTEM_RADIOBUTTON AccRoleCombobox AccRole = win.ROLE_SYSTEM_COMBOBOX AccRoleDroplist AccRole = win.ROLE_SYSTEM_DROPLIST AccRoleProgressbar AccRole = win.ROLE_SYSTEM_PROGRESSBAR AccRoleDial AccRole = win.ROLE_SYSTEM_DIAL AccRoleHotkeyfield AccRole = win.ROLE_SYSTEM_HOTKEYFIELD AccRoleSlider AccRole = win.ROLE_SYSTEM_SLIDER AccRoleSpinbutton AccRole = win.ROLE_SYSTEM_SPINBUTTON AccRoleDiagram AccRole = win.ROLE_SYSTEM_DIAGRAM AccRoleAnimation AccRole = win.ROLE_SYSTEM_ANIMATION AccRoleEquation AccRole = win.ROLE_SYSTEM_EQUATION AccRoleButtonDropdown AccRole = win.ROLE_SYSTEM_BUTTONDROPDOWN AccRoleButtonMenu AccRole = win.ROLE_SYSTEM_BUTTONMENU AccRoleButtonDropdownGrid AccRole = win.ROLE_SYSTEM_BUTTONDROPDOWNGRID AccRoleWhitespace AccRole = win.ROLE_SYSTEM_WHITESPACE AccRolePageTabList AccRole = win.ROLE_SYSTEM_PAGETABLIST AccRoleClock AccRole = win.ROLE_SYSTEM_CLOCK AccRoleSplitButton AccRole = win.ROLE_SYSTEM_SPLITBUTTON AccRoleIPAddress AccRole = win.ROLE_SYSTEM_IPADDRESS AccRoleOutlineButton AccRole = win.ROLE_SYSTEM_OUTLINEBUTTON ) // Accessibility provides basic Dynamic Annotation of windows and controls. type Accessibility struct { wb *WindowBase } // SetAccelerator sets window accelerator name using Dynamic Annotation. func (a *Accessibility) SetAccelerator(acc string) error { return a.accSetPropertyStr(a.wb.hWnd, &win.PROPID_ACC_KEYBOARDSHORTCUT, win.EVENT_OBJECT_ACCELERATORCHANGE, acc) } // SetDefaultAction sets window default action using Dynamic Annotation. func (a *Accessibility) SetDefaultAction(defAction string) error { return a.accSetPropertyStr(a.wb.hWnd, &win.PROPID_ACC_DEFAULTACTION, win.EVENT_OBJECT_DEFACTIONCHANGE, defAction) } // SetDescription sets window description using Dynamic Annotation. func (a *Accessibility) SetDescription(acc string) error { return a.accSetPropertyStr(a.wb.hWnd, &win.PROPID_ACC_DESCRIPTION, win.EVENT_OBJECT_DESCRIPTIONCHANGE, acc) } // SetHelp sets window help using Dynamic Annotation. func (a *Accessibility) SetHelp(help string) error { return a.accSetPropertyStr(a.wb.hWnd, &win.PROPID_ACC_HELP, win.EVENT_OBJECT_HELPCHANGE, help) } // SetName sets window name using Dynamic Annotation. func (a *Accessibility) SetName(name string) error { return a.accSetPropertyStr(a.wb.hWnd, &win.PROPID_ACC_NAME, win.EVENT_OBJECT_NAMECHANGE, name) } // SetRole sets window role using Dynamic Annotation. The role must be set when the window is // created and is not to be modified later. func (a *Accessibility) SetRole(role AccRole) error { return a.accSetPropertyInt(a.wb.hWnd, &win.PROPID_ACC_ROLE, 0, int32(role)) } // SetRoleMap sets window role map using Dynamic Annotation. The role map must be set when the // window is created and is not to be modified later. func (a *Accessibility) SetRoleMap(roleMap string) error { return a.accSetPropertyStr(a.wb.hWnd, &win.PROPID_ACC_ROLEMAP, 0, roleMap) } // SetState sets window state using Dynamic Annotation. func (a *Accessibility) SetState(state AccState) error { return a.accSetPropertyInt(a.wb.hWnd, &win.PROPID_ACC_STATE, win.EVENT_OBJECT_STATECHANGE, int32(state)) } // SetStateMap sets window state map using Dynamic Annotation. The state map must be set when // the window is created and is not to be modified later. func (a *Accessibility) SetStateMap(stateMap string) error { return a.accSetPropertyStr(a.wb.hWnd, &win.PROPID_ACC_STATEMAP, 0, stateMap) } // SetValueMap sets window value map using Dynamic Annotation. The value map must be set when // the window is created and is not to be modified later. func (a *Accessibility) SetValueMap(valueMap string) error { return a.accSetPropertyStr(a.wb.hWnd, &win.PROPID_ACC_VALUEMAP, 0, valueMap) } // accSetPropertyInt sets integer window property for Dynamic Annotation. func (a *Accessibility) accSetPropertyInt(hwnd win.HWND, idProp *win.MSAAPROPID, event uint32, value int32) error { accPropServices := a.wb.group.accessibilityServices() if accPropServices == nil { return newError("Dynamic Annotation not available") } var v win.VARIANT v.SetLong(value) hr := accPropServices.SetHwndProp(hwnd, win.OBJID_CLIENT, win.CHILDID_SELF, idProp, &v) if win.FAILED(hr) { return errorFromHRESULT("IAccPropServices.SetHwndProp", hr) } if win.EVENT_OBJECT_CREATE <= event && event <= win.EVENT_OBJECT_END { win.NotifyWinEvent(event, hwnd, win.OBJID_CLIENT, win.CHILDID_SELF) } return nil } // accSetPropertyStr sets string window property for Dynamic Annotation. func (a *Accessibility) accSetPropertyStr(hwnd win.HWND, idProp *win.MSAAPROPID, event uint32, value string) error { accPropServices := a.wb.group.accessibilityServices() if accPropServices == nil { return newError("Dynamic Annotation not available") } hr := accPropServices.SetHwndPropStr(hwnd, win.OBJID_CLIENT, win.CHILDID_SELF, idProp, value) if win.FAILED(hr) { return errorFromHRESULT("IAccPropServices.SetHwndPropStr", hr) } if win.EVENT_OBJECT_CREATE <= event && event <= win.EVENT_OBJECT_END { win.NotifyWinEvent(event, hwnd, win.OBJID_CLIENT, win.CHILDID_SELF) } return nil } ================================================ FILE: action.go ================================================ // 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 type actionChangedHandler interface { onActionChanged(action *Action) error onActionVisibleChanged(action *Action) error } var ( // ISSUE: When pressing enter resp. escape, // WM_COMMAND with wParam=1 resp. 2 is sent. // Maybe there is more to consider. nextActionId uint16 = 3 actionsById = make(map[uint16]*Action) shortcut2Action = make(map[Shortcut]*Action) ) type Action struct { menu *Menu triggeredPublisher EventPublisher changedHandlers []actionChangedHandler text string toolTip string image Image checkedCondition Condition checkedConditionChangedHandle int defaultCondition Condition defaultConditionChangedHandle int enabledCondition Condition enabledConditionChangedHandle int visibleCondition Condition visibleConditionChangedHandle int refCount int shortcut Shortcut enabled bool visible bool checkable bool checked bool defawlt bool exclusive bool id uint16 } func NewAction() *Action { a := &Action{ enabled: true, id: nextActionId, visible: true, } actionsById[a.id] = a nextActionId++ return a } func NewMenuAction(menu *Menu) *Action { a := NewAction() a.menu = menu return a } func NewSeparatorAction() *Action { return &Action{ enabled: true, visible: true, } } func (a *Action) addRef() { a.refCount++ } func (a *Action) release() { a.refCount-- if a.refCount == 0 { a.SetEnabledCondition(nil) a.SetVisibleCondition(nil) if a.menu != nil { a.menu.actions.Clear() a.menu.Dispose() } delete(actionsById, a.id) delete(shortcut2Action, a.shortcut) } } func (a *Action) Menu() *Menu { return a.menu } func (a *Action) Checkable() bool { return a.checkable } func (a *Action) SetCheckable(value bool) (err error) { if value != a.checkable { old := a.checkable a.checkable = value if err = a.raiseChanged(); err != nil { a.checkable = old a.raiseChanged() } } return } func (a *Action) Checked() bool { return a.checked } func (a *Action) SetChecked(value bool) (err error) { if a.checkedCondition != nil { if bp, ok := a.checkedCondition.(*boolProperty); ok { if err := bp.Set(value); err != nil { return err } } else { return newError("CheckedCondition != nil") } } if value != a.checked { old := a.checked a.checked = value if err = a.raiseChanged(); err != nil { a.checked = old a.raiseChanged() } } return } func (a *Action) CheckedCondition() Condition { return a.checkedCondition } func (a *Action) SetCheckedCondition(c Condition) { if a.checkedCondition != nil { a.checkedCondition.Changed().Detach(a.checkedConditionChangedHandle) } a.checkedCondition = c if c != nil { a.checked = c.Satisfied() a.checkedConditionChangedHandle = c.Changed().Attach(func() { if a.checked != c.Satisfied() { a.checked = !a.checked a.raiseChanged() } }) } a.raiseChanged() } func (a *Action) Default() bool { return a.defawlt } func (a *Action) SetDefault(value bool) (err error) { if a.defaultCondition != nil { if bp, ok := a.defaultCondition.(*boolProperty); ok { if err := bp.Set(value); err != nil { return err } } else { return newError("DefaultCondition != nil") } } if value != a.defawlt { old := a.defawlt a.defawlt = value if err = a.raiseChanged(); err != nil { a.defawlt = old a.raiseChanged() } } return } func (a *Action) DefaultCondition() Condition { return a.defaultCondition } func (a *Action) SetDefaultCondition(c Condition) { if a.defaultCondition != nil { a.defaultCondition.Changed().Detach(a.defaultConditionChangedHandle) } a.defaultCondition = c if c != nil { a.defawlt = c.Satisfied() a.defaultConditionChangedHandle = c.Changed().Attach(func() { if a.defawlt != c.Satisfied() { a.defawlt = !a.defawlt a.raiseChanged() } }) } a.raiseChanged() } func (a *Action) Enabled() bool { return a.enabled } func (a *Action) SetEnabled(value bool) (err error) { if a.enabledCondition != nil { return newError("EnabledCondition != nil") } if value != a.enabled { old := a.enabled a.enabled = value if err = a.raiseChanged(); err != nil { a.enabled = old a.raiseChanged() } } return } func (a *Action) EnabledCondition() Condition { return a.enabledCondition } func (a *Action) SetEnabledCondition(c Condition) { if a.enabledCondition != nil { a.enabledCondition.Changed().Detach(a.enabledConditionChangedHandle) } a.enabledCondition = c if c != nil { a.enabled = c.Satisfied() a.enabledConditionChangedHandle = c.Changed().Attach(func() { if a.enabled != c.Satisfied() { a.enabled = !a.enabled a.raiseChanged() } }) } a.raiseChanged() } func (a *Action) Exclusive() bool { return a.exclusive } func (a *Action) SetExclusive(value bool) (err error) { if value != a.exclusive { old := a.exclusive a.exclusive = value if err = a.raiseChanged(); err != nil { a.exclusive = old a.raiseChanged() } } return } func (a *Action) Image() Image { return a.image } func (a *Action) SetImage(value Image) (err error) { if value != a.image { old := a.image a.image = value if err = a.raiseChanged(); err != nil { a.image = old a.raiseChanged() } } return } func (a *Action) Shortcut() Shortcut { return a.shortcut } func (a *Action) SetShortcut(shortcut Shortcut) (err error) { if shortcut != a.shortcut { old := a.shortcut a.shortcut = shortcut defer func() { if err != nil { a.shortcut = old } }() if err = a.raiseChanged(); err != nil { a.shortcut = old a.raiseChanged() } else { if shortcut.Key == 0 { delete(shortcut2Action, old) } else { shortcut2Action[shortcut] = a } } } return } func (a *Action) Text() string { return a.text } func (a *Action) SetText(value string) (err error) { if value != a.text { old := a.text a.text = value if err = a.raiseChanged(); err != nil { a.text = old a.raiseChanged() } } return } func (a *Action) IsSeparator() bool { return a.id == 0 || a.text == "-" } func (a *Action) ToolTip() string { return a.toolTip } func (a *Action) SetToolTip(value string) (err error) { if value != a.toolTip { old := a.toolTip a.toolTip = value if err = a.raiseChanged(); err != nil { a.toolTip = old a.raiseChanged() } } return } func (a *Action) Visible() bool { return a.visible } func (a *Action) SetVisible(value bool) (err error) { if a.visibleCondition != nil { return newError("VisibleCondition != nil") } if value != a.visible { old := a.visible a.visible = value if err = a.raiseVisibleChanged(); err != nil { a.visible = old a.raiseVisibleChanged() } } return } func (a *Action) VisibleCondition() Condition { return a.visibleCondition } func (a *Action) SetVisibleCondition(c Condition) { if a.visibleCondition != nil { a.visibleCondition.Changed().Detach(a.visibleConditionChangedHandle) } a.visibleCondition = c if c != nil { a.visible = c.Satisfied() a.visibleConditionChangedHandle = c.Changed().Attach(func() { if a.visible != c.Satisfied() { a.visible = !a.visible a.raiseVisibleChanged() } }) } a.raiseChanged() } func (a *Action) Triggered() *Event { return a.triggeredPublisher.Event() } func (a *Action) raiseTriggered() { if a.Checkable() { a.SetChecked(!a.Checked()) } a.triggeredPublisher.Publish() } func (a *Action) addChangedHandler(handler actionChangedHandler) { a.changedHandlers = append(a.changedHandlers, handler) } func (a *Action) removeChangedHandler(handler actionChangedHandler) { for i, h := range a.changedHandlers { if h == handler { a.changedHandlers = append(a.changedHandlers[:i], a.changedHandlers[i+1:]...) break } } } func (a *Action) raiseChanged() error { for _, handler := range a.changedHandlers { if err := handler.onActionChanged(a); err != nil { return err } } return nil } func (a *Action) raiseVisibleChanged() error { for _, handler := range a.changedHandlers { if err := handler.onActionVisibleChanged(a); err != nil { return err } } return nil } ================================================ FILE: actionlist.go ================================================ // 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 type actionListObserver interface { onInsertedAction(action *Action) error onRemovingAction(action *Action) error onClearingActions() error } type nopActionListObserver struct{} func (nopActionListObserver) onInsertedAction(action *Action) error { return nil } func (nopActionListObserver) onRemovingAction(action *Action) error { return nil } func (nopActionListObserver) onClearingActions() error { return nil } type ActionList struct { actions []*Action observer actionListObserver } func newActionList(observer actionListObserver) *ActionList { if observer == nil { panic("observer == nil") } return &ActionList{observer: observer} } func (l *ActionList) Add(action *Action) error { return l.Insert(len(l.actions), action) } func (l *ActionList) AddMenu(menu *Menu) (*Action, error) { return l.InsertMenu(len(l.actions), menu) } func (l *ActionList) At(index int) *Action { return l.actions[index] } func (l *ActionList) Clear() error { if err := l.observer.onClearingActions(); err != nil { return err } for _, a := range l.actions { a.release() } l.actions = l.actions[:0] return nil } func (l *ActionList) Contains(action *Action) bool { return l.Index(action) > -1 } func (l *ActionList) Index(action *Action) int { for i, a := range l.actions { if a == action { return i } } return -1 } func (l *ActionList) indexInObserver(action *Action) int { var idx int for _, a := range l.actions { if a == action { return idx } if a.Visible() { idx++ } } return -1 } func (l *ActionList) Insert(index int, action *Action) error { l.actions = append(l.actions, nil) copy(l.actions[index+1:], l.actions[index:]) l.actions[index] = action if err := l.observer.onInsertedAction(action); err != nil { l.actions = append(l.actions[:index], l.actions[index+1:]...) return err } action.addRef() if action.Visible() { return l.updateSeparatorVisibility() } return nil } func (l *ActionList) InsertMenu(index int, menu *Menu) (*Action, error) { action := NewAction() action.menu = menu if err := l.Insert(index, action); err != nil { return nil, err } return action, nil } func (l *ActionList) Len() int { return len(l.actions) } func (l *ActionList) Remove(action *Action) error { index := l.Index(action) if index == -1 { return nil } return l.RemoveAt(index) } func (l *ActionList) RemoveAt(index int) error { action := l.actions[index] if action.Visible() { if err := l.observer.onRemovingAction(action); err != nil { return err } } action.release() l.actions = append(l.actions[:index], l.actions[index+1:]...) if action.Visible() { return l.updateSeparatorVisibility() } return nil } func (l *ActionList) updateSeparatorVisibility() error { var hasCurVisAct bool var curVisSep *Action for _, a := range l.actions { if visible := a.Visible(); a.IsSeparator() { toggle := visible != hasCurVisAct if toggle { visible = !visible if err := a.SetVisible(visible); err != nil { return err } } if visible { curVisSep = a } hasCurVisAct = false } else if visible { hasCurVisAct = true } } if !hasCurVisAct && curVisSep != nil { return curVisSep.SetVisible(false) } return nil } ================================================ FILE: application.go ================================================ // 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 ( "runtime" "sync" "time" "github.com/lxn/win" ) type Settings interface { Get(key string) (string, bool) Timestamp(key string) (time.Time, bool) Put(key, value string) error PutExpiring(key, value string) error Remove(key string) error ExpireDuration() time.Duration SetExpireDuration(expireDuration time.Duration) Load() error Save() error } type Persistable interface { Persistent() bool SetPersistent(value bool) SaveState() error RestoreState() error } type Application struct { mutex sync.RWMutex organizationName string productName string settings Settings exiting bool exitCode int panickingPublisher ErrorEventPublisher } var appSingleton *Application = new(Application) func App() *Application { return appSingleton } func (app *Application) OrganizationName() string { app.mutex.RLock() defer app.mutex.RUnlock() return app.organizationName } func (app *Application) SetOrganizationName(value string) { app.mutex.Lock() defer app.mutex.Unlock() app.organizationName = value } func (app *Application) ProductName() string { app.mutex.RLock() defer app.mutex.RUnlock() return app.productName } func (app *Application) SetProductName(value string) { app.mutex.Lock() defer app.mutex.Unlock() app.productName = value } func (app *Application) Settings() Settings { app.mutex.RLock() defer app.mutex.RUnlock() return app.settings } func (app *Application) SetSettings(value Settings) { app.mutex.Lock() defer app.mutex.Unlock() app.settings = value } func (app *Application) Exit(exitCode int) { app.mutex.Lock() defer app.mutex.Unlock() app.exiting = true app.exitCode = exitCode win.PostQuitMessage(int32(exitCode)) } func (app *Application) ExitCode() int { app.mutex.RLock() defer app.mutex.RUnlock() return app.exitCode } func (app *Application) Panicking() *ErrorEvent { app.mutex.RLock() defer app.mutex.RUnlock() return app.panickingPublisher.Event() } // ActiveForm returns the currently active form for the caller's thread. // It returns nil if no form is active or the caller's thread does not // have any windows associated with it. It should be called from within // synchronized functions. func (app *Application) ActiveForm() Form { runtime.LockOSThread() defer runtime.UnlockOSThread() tid := win.GetCurrentThreadId() group := wgm.Group(tid) if group == nil { return nil } return group.ActiveForm() } ================================================ FILE: bitmap.go ================================================ // 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 ( "fmt" "image" "image/color" "math" "syscall" "unsafe" "github.com/lxn/win" ) const inchesPerMeter float64 = 39.37007874 type Bitmap struct { hBmp win.HBITMAP hPackedDIB win.HGLOBAL size Size // in native pixels dpi int transparencyStatus transparencyStatus } type transparencyStatus byte const ( transparencyUnknown transparencyStatus = iota transparencyOpaque transparencyTransparent ) func BitmapFrom(src interface{}, dpi int) (*Bitmap, error) { if src == nil { return nil, nil } img, err := ImageFrom(src) if err != nil { return nil, err } return iconCache.Bitmap(img, dpi) } // NewBitmap creates an opaque bitmap with given size in 1/96" units at screen DPI. // // Deprecated: Newer applications should use NewBitmapForDPI. func NewBitmap(size Size) (*Bitmap, error) { dpi := screenDPI() return newBitmap(SizeFrom96DPI(size, dpi), false, dpi) } // NewBitmapForDPI creates an opaque bitmap with given size in native pixels and DPI. func NewBitmapForDPI(size Size, dpi int) (*Bitmap, error) { return newBitmap(size, false, dpi) } // NewBitmapWithTransparentPixels creates a transparent bitmap with given size in 1/96" units at screen DPI. // // Deprecated: Newer applications should use NewBitmapWithTransparentPixelsForDPI. func NewBitmapWithTransparentPixels(size Size) (*Bitmap, error) { dpi := screenDPI() return newBitmap(SizeFrom96DPI(size, dpi), true, dpi) } // NewBitmapWithTransparentPixelsForDPI creates a transparent bitmap with given size in native pixels and DPI. func NewBitmapWithTransparentPixelsForDPI(size Size, dpi int) (*Bitmap, error) { return newBitmap(size, true, dpi) } // newBitmap creates a bitmap with given size in native pixels and DPI. func newBitmap(size Size, transparent bool, dpi int) (bmp *Bitmap, err error) { err = withCompatibleDC(func(hdc win.HDC) error { bufSize := int(size.Width * size.Height * 4) var hdr win.BITMAPINFOHEADER hdr.BiSize = uint32(unsafe.Sizeof(hdr)) hdr.BiBitCount = 32 hdr.BiCompression = win.BI_RGB hdr.BiPlanes = 1 hdr.BiWidth = int32(size.Width) hdr.BiHeight = int32(size.Height) hdr.BiSizeImage = uint32(bufSize) dpm := int32(math.Round(float64(dpi) * inchesPerMeter)) hdr.BiXPelsPerMeter = dpm hdr.BiYPelsPerMeter = dpm var bitsPtr unsafe.Pointer hBmp := win.CreateDIBSection(hdc, &hdr, win.DIB_RGB_COLORS, &bitsPtr, 0, 0) switch hBmp { case 0, win.ERROR_INVALID_PARAMETER: return newError("CreateDIBSection failed") } if transparent { win.GdiFlush() bits := (*[1 << 24]byte)(bitsPtr) for i := 0; i < bufSize; i += 4 { // Mark pixel as not drawn to by GDI. bits[i+3] = 0x01 } } bmp, err = newBitmapFromHBITMAP(hBmp, dpi) return err }) return } // NewBitmapFromFile creates new bitmap from a bitmap file at 96dpi. // // Deprecated: Newer applications should use NewBitmapFromFileForDPI. func NewBitmapFromFile(filePath string) (*Bitmap, error) { return NewBitmapFromFileForDPI(filePath, 96) } // NewBitmapFromFileForDPI creates new bitmap from a bitmap file at given DPI. func NewBitmapFromFileForDPI(filePath string, dpi int) (*Bitmap, error) { var si win.GdiplusStartupInput si.GdiplusVersion = 1 if status := win.GdiplusStartup(&si, nil); status != win.Ok { return nil, newError(fmt.Sprintf("GdiplusStartup failed with status '%s'", status)) } defer win.GdiplusShutdown() var gpBmp *win.GpBitmap if status := win.GdipCreateBitmapFromFile(syscall.StringToUTF16Ptr(filePath), &gpBmp); status != win.Ok { return nil, newError(fmt.Sprintf("GdipCreateBitmapFromFile failed with status '%s' for file '%s'", status, filePath)) } defer win.GdipDisposeImage((*win.GpImage)(gpBmp)) var hBmp win.HBITMAP if status := win.GdipCreateHBITMAPFromBitmap(gpBmp, &hBmp, 0); status != win.Ok { return nil, newError(fmt.Sprintf("GdipCreateHBITMAPFromBitmap failed with status '%s' for file '%s'", status, filePath)) } return newBitmapFromHBITMAP(hBmp, dpi) } // NewBitmapFromImage creates a Bitmap from image.Image at 96dpi. // // Deprecated: Newer applications should use NewBitmapFromImageForDPI. func NewBitmapFromImage(im image.Image) (*Bitmap, error) { return NewBitmapFromImageForDPI(im, 96) } // NewBitmapFromImageForDPI creates a Bitmap from image.Image at given DPI. func NewBitmapFromImageForDPI(im image.Image, dpi int) (*Bitmap, error) { hBmp, err := hBitmapFromImage(im, dpi) if err != nil { return nil, err } return newBitmapFromHBITMAP(hBmp, dpi) } // NewBitmapFromResource creates a Bitmap at 96dpi from resource by name. // // Deprecated: Newer applications should use NewBitmapFromResourceForDPI. func NewBitmapFromResource(name string) (*Bitmap, error) { return newBitmapFromResource(syscall.StringToUTF16Ptr(name), 96) } // NewBitmapFromResourceForDPI creates a Bitmap at given DPI from resource by name. func NewBitmapFromResourceForDPI(name string, dpi int) (*Bitmap, error) { return newBitmapFromResource(syscall.StringToUTF16Ptr(name), dpi) } // NewBitmapFromResourceId creates a Bitmap at 96dpi from resource by ID. // // Deprecated: Newer applications should use NewBitmapFromResourceIdForDPI. func NewBitmapFromResourceId(id int) (*Bitmap, error) { return newBitmapFromResource(win.MAKEINTRESOURCE(uintptr(id)), 96) } // NewBitmapFromResourceIdForDPI creates a Bitmap at given DPI from resource by ID. func NewBitmapFromResourceIdForDPI(id int, dpi int) (*Bitmap, error) { return newBitmapFromResource(win.MAKEINTRESOURCE(uintptr(id)), dpi) } func newBitmapFromResource(res *uint16, dpi int) (bm *Bitmap, err error) { hInst := win.GetModuleHandle(nil) if hInst == 0 { err = lastError("GetModuleHandle") return } if hBmp := win.LoadImage(hInst, res, win.IMAGE_BITMAP, 0, 0, win.LR_CREATEDIBSECTION); hBmp == 0 { err = lastError("LoadImage") } else { bm, err = newBitmapFromHBITMAP(win.HBITMAP(hBmp), dpi) } return } // NewBitmapFromImageWithSize creates a bitmap with given size in native units and paints the image on it streched. func NewBitmapFromImageWithSize(image Image, size Size) (*Bitmap, error) { var disposables Disposables defer disposables.Treat() dpi := int(math.Round(float64(size.Width) / float64(image.Size().Width) * 96.0)) bmp, err := NewBitmapWithTransparentPixelsForDPI(size, dpi) if err != nil { return nil, err } disposables.Add(bmp) canvas, err := NewCanvasFromImage(bmp) if err != nil { return nil, err } defer canvas.Dispose() canvas.dpi = dpi if err := canvas.DrawImageStretchedPixels(image, Rectangle{0, 0, size.Width, size.Height}); err != nil { return nil, err } disposables.Spare() return bmp, nil } func NewBitmapFromWindow(window Window) (*Bitmap, error) { hBmp, err := hBitmapFromWindow(window) if err != nil { return nil, err } return newBitmapFromHBITMAP(hBmp, window.DPI()) } // NewBitmapFromIcon creates a new bitmap with given size in native pixels and 96dpi and paints the // icon on it. // // Deprecated: Newer applications should use NewBitmapFromIconForDPI. func NewBitmapFromIcon(icon *Icon, size Size) (*Bitmap, error) { return NewBitmapFromIconForDPI(icon, size, 96) } // NewBitmapFromIconForDPI creates a new bitmap with given size in native pixels and DPI and paints // the icon on it. func NewBitmapFromIconForDPI(icon *Icon, size Size, dpi int) (*Bitmap, error) { hBmp, err := hBitmapFromIcon(icon, size, dpi) if err != nil { return nil, err } return newBitmapFromHBITMAP(hBmp, dpi) } func (bmp *Bitmap) ToImage() (*image.RGBA, error) { var bi win.BITMAPINFO bi.BmiHeader.BiSize = uint32(unsafe.Sizeof(bi.BmiHeader)) hdc := win.GetDC(0) if ret := win.GetDIBits(hdc, bmp.hBmp, 0, 0, nil, &bi, win.DIB_RGB_COLORS); ret == 0 { return nil, newError("GetDIBits get bitmapinfo failed") } buf := make([]byte, bi.BmiHeader.BiSizeImage) bi.BmiHeader.BiCompression = win.BI_RGB if ret := win.GetDIBits(hdc, bmp.hBmp, 0, uint32(bi.BmiHeader.BiHeight), &buf[0], &bi, win.DIB_RGB_COLORS); ret == 0 { return nil, newError("GetDIBits failed") } width := int(bi.BmiHeader.BiWidth) height := int(bi.BmiHeader.BiHeight) img := image.NewRGBA(image.Rect(0, 0, width, height)) n := 0 for y := 0; y < height; y++ { for x := 0; x < width; x++ { a := buf[n+3] r := buf[n+2] g := buf[n+1] b := buf[n+0] n += int(bi.BmiHeader.BiBitCount) / 8 img.Set(x, height-y-1, color.RGBA{r, g, b, a}) } } return img, nil } func (bmp *Bitmap) hasTransparency() (bool, error) { if bmp.transparencyStatus == transparencyUnknown { if err := bmp.withPixels(func(bi *win.BITMAPINFO, hdc win.HDC, pixels *[maxPixels]bgraPixel, pixelsLen int) error { for i := 0; i < pixelsLen; i++ { if pixels[i].A == 0x00 { bmp.transparencyStatus = transparencyTransparent break } } return nil }); err != nil { return false, err } if bmp.transparencyStatus == transparencyUnknown { bmp.transparencyStatus = transparencyOpaque } } return bmp.transparencyStatus == transparencyTransparent, nil } func (bmp *Bitmap) postProcess() error { return bmp.withPixels(func(bi *win.BITMAPINFO, hdc win.HDC, pixels *[maxPixels]bgraPixel, pixelsLen int) error { for i := 0; i < pixelsLen; i++ { switch pixels[i].A { case 0x00: // The pixel has been drawn to by GDI, so we make it fully opaque. pixels[i].A = 0xff case 0x01: // The pixel has not been drawn to by GDI, so we make it fully transparent. pixels[i].A = 0x00 bmp.transparencyStatus = transparencyTransparent } } if 0 == win.SetDIBits(hdc, bmp.hBmp, 0, uint32(bi.BmiHeader.BiHeight), &pixels[0].B, bi, win.DIB_RGB_COLORS) { return newError("SetDIBits") } return nil }) } type bgraPixel struct { B byte G byte R byte A byte } const maxPixels = 2 << 27 func (bmp *Bitmap) withPixels(f func(bi *win.BITMAPINFO, hdc win.HDC, pixels *[maxPixels]bgraPixel, pixelsLen int) error) error { var bi win.BITMAPINFO bi.BmiHeader.BiSize = uint32(unsafe.Sizeof(bi.BmiHeader)) hdc := win.GetDC(0) if hdc == 0 { return newError("GetDC") } defer win.ReleaseDC(0, hdc) if ret := win.GetDIBits(hdc, bmp.hBmp, 0, 0, nil, &bi, win.DIB_RGB_COLORS); ret == 0 { return newError("GetDIBits #1") } hPixels := win.GlobalAlloc(win.GMEM_FIXED, uintptr(bi.BmiHeader.BiSizeImage)) defer win.GlobalFree(hPixels) pixels := (*[maxPixels]bgraPixel)(unsafe.Pointer(uintptr(hPixels))) bi.BmiHeader.BiCompression = win.BI_RGB if ret := win.GetDIBits(hdc, bmp.hBmp, 0, uint32(bi.BmiHeader.BiHeight), &pixels[0].B, &bi, win.DIB_RGB_COLORS); ret == 0 { return newError("GetDIBits #2") } win.GdiFlush() return f(&bi, hdc, pixels, int(bi.BmiHeader.BiSizeImage)/4) } func (bmp *Bitmap) Dispose() { if bmp.hBmp != 0 { win.DeleteObject(win.HGDIOBJ(bmp.hBmp)) win.GlobalUnlock(bmp.hPackedDIB) win.GlobalFree(bmp.hPackedDIB) bmp.hPackedDIB = 0 bmp.hBmp = 0 } } // Size returns bitmap size in 1/96" units. func (bmp *Bitmap) Size() Size { return SizeTo96DPI(bmp.size, bmp.dpi) } func (bmp *Bitmap) handle() win.HBITMAP { return bmp.hBmp } func (bmp *Bitmap) draw(hdc win.HDC, location Point) error { return bmp.drawStretched(hdc, Rectangle{X: location.X, Y: location.Y, Width: bmp.size.Width, Height: bmp.size.Height}) } func (bmp *Bitmap) drawStretched(hdc win.HDC, bounds Rectangle) error { return bmp.alphaBlend(hdc, bounds, 255) } // alphaBlend displays bitmaps that have transparent or semitransparent pixels. bounds is represented in native pixels. func (bmp *Bitmap) alphaBlend(hdc win.HDC, bounds Rectangle, opacity byte) error { return bmp.alphaBlendPart(hdc, bounds, Rectangle{0, 0, bmp.size.Width, bmp.size.Height}, opacity) } // alphaBlendPart displays bitmaps that have transparent or semitransparent pixels. dst and src are // represented in native pixels. func (bmp *Bitmap) alphaBlendPart(hdc win.HDC, dst, src Rectangle, opacity byte) error { return bmp.withSelectedIntoMemDC(func(hdcMem win.HDC) error { if opacity == 255 && (dst.Width != src.Width || dst.Height != src.Height) { transparent, err := bmp.hasTransparency() if err != nil { return err } if !transparent { if 0 == win.SetStretchBltMode(hdc, win.HALFTONE) { return newError("SetStretchBltMode") } if !win.StretchBlt( hdc, int32(dst.X), int32(dst.Y), int32(dst.Width), int32(dst.Height), hdcMem, int32(src.X), int32(src.Y), int32(src.Width), int32(src.Height), win.SRCCOPY, ) { return newError("StretchBlt failed") } return nil } } if !win.AlphaBlend( hdc, int32(dst.X), int32(dst.Y), int32(dst.Width), int32(dst.Height), hdcMem, int32(src.X), int32(src.Y), int32(src.Width), int32(src.Height), win.BLENDFUNCTION{AlphaFormat: win.AC_SRC_ALPHA, SourceConstantAlpha: opacity}, ) { return newError("AlphaBlend failed") } return nil }) } func (bmp *Bitmap) withSelectedIntoMemDC(f func(hdcMem win.HDC) error) error { return withCompatibleDC(func(hdcMem win.HDC) error { hBmpOld := win.SelectObject(hdcMem, win.HGDIOBJ(bmp.hBmp)) if hBmpOld == 0 { return newError("SelectObject failed") } defer win.SelectObject(hdcMem, hBmpOld) return f(hdcMem) }) } // newBitmapFromHBITMAP creates Bitmap from win.HBITMAP. // // The BiXPelsPerMeter and BiYPelsPerMeter fields of win.BITMAPINFOHEADER are unreliable (for // loaded PNG they are both unset). Therefore, we require caller to specify DPI explicitly. func newBitmapFromHBITMAP(hBmp win.HBITMAP, dpi int) (bmp *Bitmap, err error) { var dib win.DIBSECTION if win.GetObject(win.HGDIOBJ(hBmp), unsafe.Sizeof(dib), unsafe.Pointer(&dib)) == 0 { return nil, newError("GetObject failed") } bmih := &dib.DsBmih bmihSize := uintptr(unsafe.Sizeof(*bmih)) pixelsSize := uintptr(int32(bmih.BiBitCount)*bmih.BiWidth*bmih.BiHeight) / 8 totalSize := uintptr(bmihSize + pixelsSize) hPackedDIB := win.GlobalAlloc(win.GHND, totalSize) dest := win.GlobalLock(hPackedDIB) defer win.GlobalUnlock(hPackedDIB) src := unsafe.Pointer(&dib.DsBmih) win.MoveMemory(dest, src, bmihSize) dest = unsafe.Pointer(uintptr(dest) + bmihSize) src = dib.DsBm.BmBits win.MoveMemory(dest, src, pixelsSize) return &Bitmap{ hBmp: hBmp, hPackedDIB: hPackedDIB, size: Size{ int(bmih.BiWidth), int(bmih.BiHeight), }, dpi: dpi, }, nil } func hBitmapFromImage(im image.Image, dpi int) (win.HBITMAP, error) { var bi win.BITMAPV5HEADER bi.BiSize = uint32(unsafe.Sizeof(bi)) bi.BiWidth = int32(im.Bounds().Dx()) bi.BiHeight = -int32(im.Bounds().Dy()) bi.BiPlanes = 1 bi.BiBitCount = 32 bi.BiCompression = win.BI_BITFIELDS dpm := int32(math.Round(float64(dpi) * inchesPerMeter)) bi.BiXPelsPerMeter = dpm bi.BiYPelsPerMeter = dpm // The following mask specification specifies a supported 32 BPP // alpha format for Windows XP. bi.BV4RedMask = 0x00FF0000 bi.BV4GreenMask = 0x0000FF00 bi.BV4BlueMask = 0x000000FF bi.BV4AlphaMask = 0xFF000000 hdc := win.GetDC(0) defer win.ReleaseDC(0, hdc) var lpBits unsafe.Pointer // Create the DIB section with an alpha channel. hBitmap := win.CreateDIBSection(hdc, &bi.BITMAPINFOHEADER, win.DIB_RGB_COLORS, &lpBits, 0, 0) switch hBitmap { case 0, win.ERROR_INVALID_PARAMETER: return 0, newError("CreateDIBSection failed") } // Fill the image bitmap_array := (*[1 << 30]byte)(unsafe.Pointer(lpBits)) i := 0 for y := im.Bounds().Min.Y; y != im.Bounds().Max.Y; y++ { for x := im.Bounds().Min.X; x != im.Bounds().Max.X; x++ { r, g, b, a := im.At(x, y).RGBA() bitmap_array[i+3] = byte(a >> 8) bitmap_array[i+2] = byte(r >> 8) bitmap_array[i+1] = byte(g >> 8) bitmap_array[i+0] = byte(b >> 8) i += 4 } } return hBitmap, nil } func hBitmapFromWindow(window Window) (win.HBITMAP, error) { hdcMem := win.CreateCompatibleDC(0) if hdcMem == 0 { return 0, newError("CreateCompatibleDC failed") } defer win.DeleteDC(hdcMem) var r win.RECT if !win.GetWindowRect(window.Handle(), &r) { return 0, newError("GetWindowRect failed") } hdc := win.GetDC(window.Handle()) width, height := r.Right-r.Left, r.Bottom-r.Top hBmp := win.CreateCompatibleBitmap(hdc, width, height) win.ReleaseDC(window.Handle(), hdc) hOld := win.SelectObject(hdcMem, win.HGDIOBJ(hBmp)) flags := win.PRF_CHILDREN | win.PRF_CLIENT | win.PRF_ERASEBKGND | win.PRF_NONCLIENT | win.PRF_OWNED window.SendMessage(win.WM_PRINT, uintptr(hdcMem), uintptr(flags)) win.SelectObject(hdcMem, hOld) return hBmp, nil } // hBitmapFromIcon creates a new win.HBITMAP with given size in native pixels and DPI, and paints // the icon on it stretched. func hBitmapFromIcon(icon *Icon, size Size, dpi int) (win.HBITMAP, error) { hdc := win.GetDC(0) defer win.ReleaseDC(0, hdc) hdcMem := win.CreateCompatibleDC(hdc) if hdcMem == 0 { return 0, newError("CreateCompatibleDC failed") } defer win.DeleteDC(hdcMem) var bi win.BITMAPV5HEADER bi.BiSize = uint32(unsafe.Sizeof(bi)) bi.BiWidth = int32(size.Width) bi.BiHeight = int32(size.Height) bi.BiPlanes = 1 bi.BiBitCount = 32 bi.BiCompression = win.BI_RGB dpm := int32(math.Round(float64(dpi) * inchesPerMeter)) bi.BiXPelsPerMeter = dpm bi.BiYPelsPerMeter = dpm // The following mask specification specifies a supported 32 BPP // alpha format for Windows XP. bi.BV4RedMask = 0x00FF0000 bi.BV4GreenMask = 0x0000FF00 bi.BV4BlueMask = 0x000000FF bi.BV4AlphaMask = 0xFF000000 hBmp := win.CreateDIBSection(hdcMem, &bi.BITMAPINFOHEADER, win.DIB_RGB_COLORS, nil, 0, 0) switch hBmp { case 0, win.ERROR_INVALID_PARAMETER: return 0, newError("CreateDIBSection failed") } hOld := win.SelectObject(hdcMem, win.HGDIOBJ(hBmp)) defer win.SelectObject(hdcMem, hOld) err := icon.drawStretched(hdcMem, Rectangle{Width: size.Width, Height: size.Height}) if err != nil { return 0, err } return hBmp, nil } func withCompatibleDC(f func(hdc win.HDC) error) error { hdc := win.CreateCompatibleDC(0) if hdc == 0 { return newError("CreateCompatibleDC failed") } defer win.DeleteDC(hdc) return f(hdc) } ================================================ FILE: boxlayout.go ================================================ // 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 ( "math" "sort" "sync" "github.com/lxn/win" ) type Orientation byte const ( NoOrientation Orientation = 0 Horizontal = 1 << 0 Vertical = 1 << 1 ) type BoxLayout struct { LayoutBase orientation Orientation hwnd2StretchFactor map[win.HWND]int } func newBoxLayout(orientation Orientation) *BoxLayout { l := &BoxLayout{ LayoutBase: LayoutBase{ margins96dpi: Margins{9, 9, 9, 9}, spacing96dpi: 6, }, orientation: orientation, hwnd2StretchFactor: make(map[win.HWND]int), } l.layout = l return l } func NewHBoxLayout() *BoxLayout { return newBoxLayout(Horizontal) } func NewVBoxLayout() *BoxLayout { return newBoxLayout(Vertical) } func (l *BoxLayout) Orientation() Orientation { return l.orientation } func (l *BoxLayout) SetOrientation(value Orientation) error { if value != l.orientation { switch value { case Horizontal, Vertical: default: return newError("invalid Orientation value") } l.orientation = value l.container.RequestLayout() } return nil } func (l *BoxLayout) StretchFactor(widget Widget) int { if factor, ok := l.hwnd2StretchFactor[widget.Handle()]; ok { return factor } return 1 } func (l *BoxLayout) SetStretchFactor(widget Widget, factor int) error { if factor != l.StretchFactor(widget) { if l.container == nil { return newError("container required") } handle := widget.Handle() if !l.container.Children().containsHandle(handle) { return newError("unknown widget") } if factor < 1 { return newError("factor must be >= 1") } l.hwnd2StretchFactor[handle] = factor l.container.RequestLayout() } return nil } func (l *BoxLayout) CreateLayoutItem(ctx *LayoutContext) ContainerLayoutItem { li := &boxLayoutItem{ size2MinSize: make(map[Size]Size), orientation: l.orientation, hwnd2StretchFactor: make(map[win.HWND]int), } for hwnd, sf := range l.hwnd2StretchFactor { li.hwnd2StretchFactor[hwnd] = sf } return li } type boxLayoutItemInfo struct { item LayoutItem index int prefSize int // in native pixels minSize int // in native pixels maxSize int // in native pixels stretch int greedy bool } type boxLayoutItemInfoList []boxLayoutItemInfo func (l boxLayoutItemInfoList) Len() int { return len(l) } func (l boxLayoutItemInfoList) Less(i, j int) bool { _, iIsSpacer := l[i].item.(*spacerLayoutItem) _, jIsSpacer := l[j].item.(*spacerLayoutItem) if l[i].greedy == l[j].greedy { if iIsSpacer == jIsSpacer { minDiff := l[i].minSize - l[j].minSize if minDiff == 0 { return l[i].maxSize/l[i].stretch < l[j].maxSize/l[j].stretch } return minDiff > 0 } return jIsSpacer } return l[i].greedy } func (l boxLayoutItemInfoList) Swap(i, j int) { l[i], l[j] = l[j], l[i] } type boxLayoutItem struct { ContainerLayoutItemBase mutex sync.Mutex size2MinSize map[Size]Size // in native pixels orientation Orientation hwnd2StretchFactor map[win.HWND]int } func (li *boxLayoutItem) LayoutFlags() LayoutFlags { return boxLayoutFlags(li.orientation, li.children) } func (li *boxLayoutItem) IdealSize() Size { return li.MinSize() } func (li *boxLayoutItem) MinSize() Size { return li.MinSizeForSize(li.geometry.ClientSize) } func (li *boxLayoutItem) HeightForWidth(width int) int { return li.MinSizeForSize(Size{width, li.geometry.ClientSize.Height}).Height } func (li *boxLayoutItem) MinSizeForSize(size Size) Size { li.mutex.Lock() defer li.mutex.Unlock() if min, ok := li.size2MinSize[size]; ok { return min } bounds := Rectangle{Width: size.Width, Height: size.Height} items := boxLayoutItems(li, itemsToLayout(li.children), li.orientation, li.alignment, bounds, li.margins96dpi, li.spacing96dpi, li.hwnd2StretchFactor) margins := MarginsFrom96DPI(li.margins96dpi, li.ctx.dpi) spacing := IntFrom96DPI(li.spacing96dpi, li.ctx.dpi) s := Size{margins.HNear + margins.HFar, margins.VNear + margins.VFar} var maxSecondary int for _, item := range items { min := li.MinSizeEffectiveForChild(item.Item) if hfw, ok := item.Item.(HeightForWidther); ok && hfw.HasHeightForWidth() { item.Bounds.Height = hfw.HeightForWidth(item.Bounds.Width) } else { item.Bounds.Height = min.Height } item.Bounds.Width = min.Width if li.orientation == Horizontal { maxSecondary = maxi(maxSecondary, item.Bounds.Height) s.Width += item.Bounds.Width } else { maxSecondary = maxi(maxSecondary, item.Bounds.Width) s.Height += item.Bounds.Height } } if li.orientation == Horizontal { s.Width += (len(items) - 1) * spacing s.Height += maxSecondary } else { s.Height += (len(items) - 1) * spacing s.Width += maxSecondary } if s.Width > 0 && s.Height > 0 { li.size2MinSize[size] = s } return s } func (li *boxLayoutItem) PerformLayout() []LayoutResultItem { cb := Rectangle{Width: li.geometry.ClientSize.Width, Height: li.geometry.ClientSize.Height} return boxLayoutItems(li, itemsToLayout(li.children), li.orientation, li.alignment, cb, li.margins96dpi, li.spacing96dpi, li.hwnd2StretchFactor) } func boxLayoutFlags(orientation Orientation, children []LayoutItem) LayoutFlags { if len(children) == 0 { return ShrinkableHorz | ShrinkableVert | GrowableHorz | GrowableVert } var flags LayoutFlags for i := 0; i < len(children); i++ { item := children[i] if _, ok := item.(*splitterHandleLayoutItem); ok || !shouldLayoutItem(item) { continue } if s, ok := item.(*spacerLayoutItem); ok { if s.greedyLocallyOnly { continue } } f := item.LayoutFlags() flags |= f } return flags } // boxLayoutItems lays out items. bounds parameter is in native pixels. func boxLayoutItems(container ContainerLayoutItem, items []LayoutItem, orientation Orientation, alignment Alignment2D, bounds Rectangle, margins96dpi Margins, spacing96dpi int, hwnd2StretchFactor map[win.HWND]int) []LayoutResultItem { if len(items) == 0 { return nil } dpi := container.Context().dpi margins := MarginsFrom96DPI(margins96dpi, dpi) spacing := IntFrom96DPI(spacing96dpi, dpi) var greedyNonSpacerCount int var greedySpacerCount int var stretchFactorsTotal [3]int stretchFactors := make([]int, len(items)) var minSizesRemaining int minSizes := make([]int, len(items)) maxSizes := make([]int, len(items)) sizes := make([]int, len(items)) prefSizes2 := make([]int, len(items)) var shrinkableAmount1Total int shrinkableAmount1 := make([]int, len(items)) shrinkable2 := make([]bool, len(items)) growable2 := make([]bool, len(items)) sortedItemInfo := boxLayoutItemInfoList(make([]boxLayoutItemInfo, len(items))) for i, item := range items { sf := hwnd2StretchFactor[item.Handle()] if sf == 0 { sf = 1 } stretchFactors[i] = sf geometry := item.Geometry() flags := item.LayoutFlags() max := geometry.MaxSize var pref Size if hfw, ok := item.(HeightForWidther); !ok || !hfw.HasHeightForWidth() { if is, ok := item.(IdealSizer); ok { pref = is.IdealSize() } } if orientation == Horizontal { growable2[i] = flags&GrowableVert > 0 minSizes[i] = container.MinSizeEffectiveForChild(item).Width if max.Width > 0 { maxSizes[i] = max.Width } else if pref.Width > 0 && flags&GrowableHorz == 0 { maxSizes[i] = pref.Width } else { maxSizes[i] = 32768 } prefSizes2[i] = pref.Height sortedItemInfo[i].prefSize = pref.Width sortedItemInfo[i].greedy = flags&GreedyHorz > 0 } else { growable2[i] = flags&GrowableHorz > 0 if hfw, ok := item.(HeightForWidther); ok && hfw.HasHeightForWidth() { minSizes[i] = hfw.HeightForWidth(bounds.Width - margins.HNear - margins.HFar) } else { minSizes[i] = container.MinSizeEffectiveForChild(item).Height } if max.Height > 0 { maxSizes[i] = max.Height } else if hfw, ok := item.(HeightForWidther); ok && flags&GrowableVert == 0 && hfw.HasHeightForWidth() { maxSizes[i] = minSizes[i] } else if pref.Height > 0 && flags&GrowableVert == 0 { maxSizes[i] = pref.Height } else { maxSizes[i] = 32768 } prefSizes2[i] = pref.Width sortedItemInfo[i].prefSize = pref.Height sortedItemInfo[i].greedy = flags&GreedyVert > 0 } sortedItemInfo[i].index = i sortedItemInfo[i].minSize = minSizes[i] sortedItemInfo[i].maxSize = maxSizes[i] sortedItemInfo[i].stretch = sf sortedItemInfo[i].item = item if orientation == Horizontal && flags&(ShrinkableHorz|GrowableHorz|GreedyHorz) == ShrinkableHorz || orientation == Vertical && flags&(ShrinkableVert|GrowableVert|GreedyVert) == ShrinkableVert { if amount := sortedItemInfo[i].prefSize - minSizes[i]; amount > 0 { shrinkableAmount1[i] = amount shrinkableAmount1Total += amount } } shrinkable2[i] = orientation == Horizontal && flags&ShrinkableVert != 0 || orientation == Vertical && flags&ShrinkableHorz != 0 if shrinkableAmount1[i] > 0 { minSizesRemaining += sortedItemInfo[i].prefSize } else { minSizesRemaining += minSizes[i] } if sortedItemInfo[i].greedy { if _, isSpacer := item.(*spacerLayoutItem); !isSpacer { greedyNonSpacerCount++ stretchFactorsTotal[0] += sf } else { greedySpacerCount++ stretchFactorsTotal[1] += sf } } else { stretchFactorsTotal[2] += sf } } sort.Stable(sortedItemInfo) var start1, start2, space1, space2 int if orientation == Horizontal { start1 = bounds.X + margins.HNear start2 = bounds.Y + margins.VNear space1 = bounds.Width - margins.HNear - margins.HFar space2 = bounds.Height - margins.VNear - margins.VFar } else { start1 = bounds.Y + margins.VNear start2 = bounds.X + margins.HNear space1 = bounds.Height - margins.VNear - margins.VFar space2 = bounds.Width - margins.HNear - margins.HFar } spacingRemaining := spacing * (len(items) - 1) excess := float64(space1 - minSizesRemaining - spacingRemaining) offsets := [3]int{0, greedyNonSpacerCount, greedyNonSpacerCount + greedySpacerCount} counts := [3]int{greedyNonSpacerCount, greedySpacerCount, len(items) - greedyNonSpacerCount - greedySpacerCount} for i := 0; i < 3; i++ { stretchFactorsRemaining := stretchFactorsTotal[i] for j := 0; j < counts[i]; j++ { info := sortedItemInfo[offsets[i]+j] k := info.index stretch := stretchFactors[k] min := info.minSize max := info.maxSize var size int var corrected bool if shrinkableAmount1[k] > 0 { size = info.prefSize if excess < 0.0 { size -= mini(shrinkableAmount1[k], int(math.Round(-excess/float64(shrinkableAmount1Total)*float64(shrinkableAmount1[k])))) corrected = true } } else { size = min } if !corrected && min < max { excessSpace := float64(space1 - minSizesRemaining - spacingRemaining) size += int(math.Round(excessSpace * float64(stretch) / float64(stretchFactorsRemaining))) if size < min { size = min } else if size > max { size = max } } sizes[k] = size if shrinkableAmount1[k] > 0 { minSizesRemaining -= info.prefSize } else { minSizesRemaining -= min } stretchFactorsRemaining -= stretch space1 -= (size + spacing) spacingRemaining -= spacing } } results := make([]LayoutResultItem, 0, len(items)) excessTotal := space1 - minSizesRemaining - spacingRemaining excessShare := excessTotal / len(items) halfExcessShare := excessTotal / (len(items) * 2) p1 := start1 for i, item := range items { s1 := sizes[i] var s2 int if hfw, ok := item.(HeightForWidther); ok && orientation == Horizontal && hfw.HasHeightForWidth() { s2 = hfw.HeightForWidth(s1) } else if shrinkable2[i] || growable2[i] { s2 = space2 } else { s2 = prefSizes2[i] } align := item.Geometry().Alignment if align == AlignHVDefault { align = alignment } var x, y, w, h, p2 int if orientation == Horizontal { switch align { case AlignHNearVNear, AlignHNearVCenter, AlignHNearVFar: // nop case AlignHFarVNear, AlignHFarVCenter, AlignHFarVFar: p1 += excessShare default: p1 += halfExcessShare } switch align { case AlignHNearVNear, AlignHCenterVNear, AlignHFarVNear: p2 = start2 case AlignHNearVFar, AlignHCenterVFar, AlignHFarVFar: p2 = start2 + space2 - s2 default: p2 = start2 + (space2-s2)/2 } x, y, w, h = p1, p2, s1, s2 } else { switch align { case AlignHNearVNear, AlignHCenterVNear, AlignHFarVNear: // nop case AlignHNearVFar, AlignHCenterVFar, AlignHFarVFar: p1 += excessShare default: p1 += halfExcessShare } switch align { case AlignHNearVNear, AlignHNearVCenter, AlignHNearVFar: p2 = start2 case AlignHFarVNear, AlignHFarVCenter, AlignHFarVFar: p2 = start2 + space2 - s2 default: p2 = start2 + (space2-s2)/2 } x, y, w, h = p2, p1, s2, s1 } if orientation == Horizontal { switch align { case AlignHNearVNear, AlignHNearVCenter, AlignHNearVFar: p1 += excessShare case AlignHFarVNear, AlignHFarVCenter, AlignHFarVFar: // nop default: p1 += halfExcessShare } } else { switch align { case AlignHNearVNear, AlignHCenterVNear, AlignHFarVNear: p1 += excessShare case AlignHNearVFar, AlignHCenterVFar, AlignHFarVFar: // nop default: p1 += halfExcessShare } } p1 += s1 + spacing results = append(results, LayoutResultItem{Item: item, Bounds: Rectangle{X: x, Y: y, Width: w, Height: h}}) } return results } ================================================ FILE: brush.go ================================================ // 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 ( "unsafe" "github.com/lxn/win" ) type HatchStyle int const ( HatchHorizontal HatchStyle = win.HS_HORIZONTAL HatchVertical HatchStyle = win.HS_VERTICAL HatchForwardDiagonal HatchStyle = win.HS_FDIAGONAL HatchBackwardDiagonal HatchStyle = win.HS_BDIAGONAL HatchCross HatchStyle = win.HS_CROSS HatchDiagonalCross HatchStyle = win.HS_DIAGCROSS ) type SystemColor int const ( SysColor3DDkShadow SystemColor = win.COLOR_3DDKSHADOW SysColor3DFace SystemColor = win.COLOR_3DFACE SysColor3DHighlight SystemColor = win.COLOR_3DHIGHLIGHT SysColor3DLight SystemColor = win.COLOR_3DLIGHT SysColor3DShadow SystemColor = win.COLOR_3DSHADOW SysColorActiveBorder SystemColor = win.COLOR_ACTIVEBORDER SysColorActiveCaption SystemColor = win.COLOR_ACTIVECAPTION SysColorAppWorkspace SystemColor = win.COLOR_APPWORKSPACE SysColorBackground SystemColor = win.COLOR_BACKGROUND SysColorDesktop SystemColor = win.COLOR_DESKTOP SysColorBtnFace SystemColor = win.COLOR_BTNFACE SysColorBtnHighlight SystemColor = win.COLOR_BTNHIGHLIGHT SysColorBtnShadow SystemColor = win.COLOR_BTNSHADOW SysColorBtnText SystemColor = win.COLOR_BTNTEXT SysColorCaptionText SystemColor = win.COLOR_CAPTIONTEXT SysColorGrayText SystemColor = win.COLOR_GRAYTEXT SysColorHighlight SystemColor = win.COLOR_HIGHLIGHT SysColorHighlightText SystemColor = win.COLOR_HIGHLIGHTTEXT SysColorInactiveBorder SystemColor = win.COLOR_INACTIVEBORDER SysColorInactiveCaption SystemColor = win.COLOR_INACTIVECAPTION SysColorInactiveCaptionText SystemColor = win.COLOR_INACTIVECAPTIONTEXT SysColorInfoBk SystemColor = win.COLOR_INFOBK SysColorInfoText SystemColor = win.COLOR_INFOTEXT SysColorMenu SystemColor = win.COLOR_MENU SysColorMenuText SystemColor = win.COLOR_MENUTEXT SysColorScrollBar SystemColor = win.COLOR_SCROLLBAR SysColorWindow SystemColor = win.COLOR_WINDOW SysColorWindowFrame SystemColor = win.COLOR_WINDOWFRAME SysColorWindowText SystemColor = win.COLOR_WINDOWTEXT SysColorHotLight SystemColor = win.COLOR_HOTLIGHT SysColorGradientActiveCaption SystemColor = win.COLOR_GRADIENTACTIVECAPTION SysColorGradientInactiveCaption SystemColor = win.COLOR_GRADIENTINACTIVECAPTION ) type Brush interface { Dispose() handle() win.HBRUSH logbrush() *win.LOGBRUSH attachWindow(wb *WindowBase) detachWindow(wb *WindowBase) simple() bool } type perWindowBrush interface { Brush delegateForWindow(wb *WindowBase) Brush } type windowBrushInfo struct { SizeChangedHandle int Delegate *BitmapBrush } type brushBase struct { hBrush win.HBRUSH wb2info map[*WindowBase]*windowBrushInfo } func (bb *brushBase) Dispose() { if bb.hBrush != 0 { win.DeleteObject(win.HGDIOBJ(bb.hBrush)) bb.hBrush = 0 } } func (bb *brushBase) handle() win.HBRUSH { return bb.hBrush } func (bb *brushBase) attachWindow(wb *WindowBase) { if wb == nil { return } if bb.wb2info == nil { bb.wb2info = make(map[*WindowBase]*windowBrushInfo) } bb.wb2info[wb] = nil } func (bb *brushBase) detachWindow(wb *WindowBase) { if bb.wb2info == nil || wb == nil { return } delete(bb.wb2info, wb) if len(bb.wb2info) == 0 { bb.Dispose() } } type nullBrush struct { brushBase } func newNullBrush() *nullBrush { lb := &win.LOGBRUSH{LbStyle: win.BS_NULL} hBrush := win.CreateBrushIndirect(lb) if hBrush == 0 { panic("failed to create null brush") } return &nullBrush{brushBase: brushBase{hBrush: hBrush}} } func (b *nullBrush) Dispose() { if b == nullBrushSingleton { return } b.brushBase.Dispose() } func (*nullBrush) logbrush() *win.LOGBRUSH { return &win.LOGBRUSH{LbStyle: win.BS_NULL} } func (*nullBrush) simple() bool { return true } var ( nullBrushSingleton Brush sysColorBtnFaceBrush *SystemColorBrush ) func NullBrush() Brush { return nullBrushSingleton } type SystemColorBrush struct { brushBase sysColor SystemColor } func init() { AppendToWalkInit(func() { nullBrushSingleton = newNullBrush() sysColorBtnFaceBrush, _ = NewSystemColorBrush(SysColorBtnFace) }) } func NewSystemColorBrush(sysColor SystemColor) (*SystemColorBrush, error) { hBrush := win.GetSysColorBrush(int(sysColor)) if hBrush == 0 { return nil, newError("GetSysColorBrush failed") } return &SystemColorBrush{brushBase: brushBase{hBrush: hBrush}, sysColor: sysColor}, nil } func (b *SystemColorBrush) Color() Color { return Color(win.GetSysColor(int(b.sysColor))) } func (b *SystemColorBrush) SystemColor() SystemColor { return b.sysColor } func (*SystemColorBrush) Dispose() { // nop } func (b *SystemColorBrush) logbrush() *win.LOGBRUSH { return &win.LOGBRUSH{ LbStyle: win.BS_SOLID, LbColor: win.COLORREF(win.GetSysColor(int(b.sysColor))), } } func (*SystemColorBrush) simple() bool { return true } type SolidColorBrush struct { brushBase color Color } func NewSolidColorBrush(color Color) (*SolidColorBrush, error) { lb := &win.LOGBRUSH{LbStyle: win.BS_SOLID, LbColor: win.COLORREF(color)} hBrush := win.CreateBrushIndirect(lb) if hBrush == 0 { return nil, newError("CreateBrushIndirect failed") } return &SolidColorBrush{brushBase: brushBase{hBrush: hBrush}, color: color}, nil } func (b *SolidColorBrush) Color() Color { return b.color } func (b *SolidColorBrush) logbrush() *win.LOGBRUSH { return &win.LOGBRUSH{LbStyle: win.BS_SOLID, LbColor: win.COLORREF(b.color)} } func (*SolidColorBrush) simple() bool { return true } type HatchBrush struct { brushBase color Color style HatchStyle } func NewHatchBrush(color Color, style HatchStyle) (*HatchBrush, error) { lb := &win.LOGBRUSH{LbStyle: win.BS_HATCHED, LbColor: win.COLORREF(color), LbHatch: uintptr(style)} hBrush := win.CreateBrushIndirect(lb) if hBrush == 0 { return nil, newError("CreateBrushIndirect failed") } return &HatchBrush{brushBase: brushBase{hBrush: hBrush}, color: color, style: style}, nil } func (b *HatchBrush) Color() Color { return b.color } func (b *HatchBrush) logbrush() *win.LOGBRUSH { return &win.LOGBRUSH{LbStyle: win.BS_HATCHED, LbColor: win.COLORREF(b.color), LbHatch: uintptr(b.style)} } func (b *HatchBrush) Style() HatchStyle { return b.style } func (b *HatchBrush) simple() bool { return false } type BitmapBrush struct { brushBase bitmap *Bitmap } func NewBitmapBrush(bitmap *Bitmap) (*BitmapBrush, error) { if bitmap == nil { return nil, newError("bitmap cannot be nil") } hBrush := win.CreatePatternBrush(bitmap.hBmp) if hBrush == 0 { return nil, newError("CreatePatternBrush failed") } return &BitmapBrush{brushBase: brushBase{hBrush: hBrush}, bitmap: bitmap}, nil } func (b *BitmapBrush) logbrush() *win.LOGBRUSH { return &win.LOGBRUSH{LbStyle: win.BS_DIBPATTERN, LbColor: win.DIB_RGB_COLORS, LbHatch: uintptr(b.bitmap.hPackedDIB)} } func (b *BitmapBrush) Bitmap() *Bitmap { return b.bitmap } func (b *BitmapBrush) simple() bool { return false } type GradientStop struct { Offset float64 Color Color } type GradientVertex struct { X float64 Y float64 Color Color } type GradientTriangle struct { Vertex1 int Vertex2 int Vertex3 int } type GradientBrush struct { brushBase mainDelegate *BitmapBrush vertexes []GradientVertex triangles []GradientTriangle orientation gradientOrientation absolute bool } type gradientOrientation int const ( gradientOrientationNone gradientOrientation = iota gradientOrientationHorizontal gradientOrientationVertical ) func NewHorizontalGradientBrush(stops []GradientStop) (*GradientBrush, error) { return newGradientBrushWithOrientation(stops, gradientOrientationHorizontal) } func NewVerticalGradientBrush(stops []GradientStop) (*GradientBrush, error) { return newGradientBrushWithOrientation(stops, gradientOrientationVertical) } func newGradientBrushWithOrientation(stops []GradientStop, orientation gradientOrientation) (*GradientBrush, error) { if len(stops) < 2 { return nil, newError("at least 2 stops are required") } var vertexes []GradientVertex var triangles []GradientTriangle for i, stop := range stops { var x0, y0, x1, y1 float64 if orientation == gradientOrientationHorizontal { x0 = stop.Offset x1 = stop.Offset y1 = 1.0 } else { y0 = stop.Offset x1 = 1.0 y1 = stop.Offset } vertexes = append(vertexes, GradientVertex{X: x0, Y: y0, Color: stop.Color}) vertexes = append(vertexes, GradientVertex{X: x1, Y: y1, Color: stop.Color}) if i > 0 { triangles = append(triangles, GradientTriangle{Vertex1: i*2 - 2, Vertex2: i*2 + 1, Vertex3: i*2 - 1}) triangles = append(triangles, GradientTriangle{Vertex1: i*2 - 2, Vertex2: i * 2, Vertex3: i*2 + 1}) } } return newGradientBrush(vertexes, triangles, orientation) } func NewGradientBrush(vertexes []GradientVertex, triangles []GradientTriangle) (*GradientBrush, error) { if len(vertexes) < 3 { return nil, newError("at least 3 vertexes are required") } if len(triangles) < 1 { return nil, newError("at least 1 triangle is required") } return newGradientBrush(vertexes, triangles, gradientOrientationNone) } func newGradientBrush(vertexes []GradientVertex, triangles []GradientTriangle, orientation gradientOrientation) (*GradientBrush, error) { var size Size for _, v := range vertexes { size = maxSize(size, Size{int(v.X), int(v.Y)}) } gb := &GradientBrush{vertexes: vertexes, triangles: triangles, orientation: orientation, absolute: size.Width > 1 || size.Height > 1} if gb.absolute { bb, err := gb.create(size) if err != nil { return nil, err } gb.mainDelegate = bb gb.hBrush = bb.hBrush } return gb, nil } func (b *GradientBrush) logbrush() *win.LOGBRUSH { if b.mainDelegate == nil { return nil } return b.mainDelegate.logbrush() } func (*GradientBrush) simple() bool { return false } // create creates a gradient brush at given size in native pixels. func (b *GradientBrush) create(size Size) (*BitmapBrush, error) { var disposables Disposables defer disposables.Treat() switch b.orientation { case gradientOrientationHorizontal: size.Height = 1 case gradientOrientationVertical: size.Width = 1 } bitmap, err := NewBitmapForDPI(size, 96) // Size is in native pixels and bitmap is used for brush only => DPI is not used anywhere. if err != nil { return nil, err } disposables.Add(bitmap) canvas, err := NewCanvasFromImage(bitmap) if err != nil { return nil, err } defer canvas.Dispose() var scaleX, scaleY float64 if b.absolute { scaleX, scaleY = 1, 1 } else { scaleX, scaleY = float64(size.Width), float64(size.Height) } vertexes := make([]win.TRIVERTEX, len(b.vertexes)) for i, src := range b.vertexes { dst := &vertexes[i] dst.X = int32(src.X * scaleX) dst.Y = int32(src.Y * scaleY) dst.Red = uint16(src.Color.R()) * 256 dst.Green = uint16(src.Color.G()) * 256 dst.Blue = uint16(src.Color.B()) * 256 } triangles := make([]win.GRADIENT_TRIANGLE, len(b.triangles)) for i, src := range b.triangles { dst := &triangles[i] dst.Vertex1 = uint32(src.Vertex1) dst.Vertex2 = uint32(src.Vertex2) dst.Vertex3 = uint32(src.Vertex3) } if !win.GradientFill(canvas.hdc, &vertexes[0], uint32(len(vertexes)), unsafe.Pointer(&triangles[0]), uint32(len(triangles)), win.GRADIENT_FILL_TRIANGLE) { return nil, newError("GradientFill failed") } disposables.Spare() return NewBitmapBrush(bitmap) } func (b *GradientBrush) attachWindow(wb *WindowBase) { b.brushBase.attachWindow(wb) if b.absolute { return } var info *windowBrushInfo update := func() { if bb, err := b.create(wb.window.ClientBoundsPixels().Size()); err == nil { if info.Delegate != nil { info.Delegate.bitmap.Dispose() info.Delegate.Dispose() } info.Delegate = bb wb.Invalidate() } } info = &windowBrushInfo{ SizeChangedHandle: wb.SizeChanged().Attach(update), } update() b.wb2info[wb] = info } func (b *GradientBrush) detachWindow(wb *WindowBase) { if !b.absolute { if info, ok := b.wb2info[wb]; ok { if info.Delegate != nil { info.Delegate.bitmap.Dispose() info.Delegate.Dispose() } wb.SizeChanged().Detach(info.SizeChangedHandle) } } b.brushBase.detachWindow(wb) } func (b *GradientBrush) delegateForWindow(wb *WindowBase) Brush { if b.absolute { return b.mainDelegate } if info, ok := b.wb2info[wb]; ok && info.Delegate != nil { return info.Delegate } return nil } ================================================ FILE: button.go ================================================ // 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 ( "fmt" "unsafe" "github.com/lxn/win" ) type clickable interface { raiseClicked() } type setCheckeder interface { setChecked(checked bool) } type Button struct { WidgetBase checkedChangedPublisher EventPublisher clickedPublisher EventPublisher textChangedPublisher EventPublisher imageChangedPublisher EventPublisher image Image persistent bool } func (b *Button) init() { b.MustRegisterProperty("Checked", NewBoolProperty( func() bool { return b.Checked() }, func(v bool) error { b.SetChecked(v) return nil }, b.CheckedChanged())) b.MustRegisterProperty("Image", NewProperty( func() interface{} { return b.Image() }, func(v interface{}) error { img, err := ImageFrom(v) if err != nil { return err } b.SetImage(img) return nil }, b.imageChangedPublisher.Event())) b.MustRegisterProperty("Text", NewProperty( func() interface{} { return b.Text() }, func(v interface{}) error { return b.SetText(assertStringOr(v, "")) }, b.textChangedPublisher.Event())) } func (b *Button) ApplyDPI(dpi int) { b.WidgetBase.ApplyDPI(dpi) b.SetImage(b.image) } func (b *Button) Image() Image { return b.image } func (b *Button) SetImage(image Image) error { var typ, handle uintptr switch img := image.(type) { case nil: case *Bitmap: typ = win.IMAGE_BITMAP handle = uintptr(img.hBmp) case *Icon: typ = win.IMAGE_ICON handle = uintptr(img.handleForDPI(b.DPI())) default: bmp, err := iconCache.Bitmap(image, b.DPI()) if err != nil { return err } typ = win.IMAGE_BITMAP handle = uintptr(bmp.hBmp) } b.SendMessage(win.BM_SETIMAGE, typ, handle) b.image = image b.RequestLayout() b.imageChangedPublisher.Publish() return nil } func (b *Button) ImageChanged() *Event { return b.imageChangedPublisher.Event() } func (b *Button) Text() string { return b.text() } func (b *Button) SetText(value string) error { if value == b.Text() { return nil } if err := b.setText(value); err != nil { return err } b.RequestLayout() return nil } func (b *Button) Checked() bool { return b.SendMessage(win.BM_GETCHECK, 0, 0) == win.BST_CHECKED } func (b *Button) SetChecked(checked bool) { if checked == b.Checked() { return } b.window.(setCheckeder).setChecked(checked) } func (b *Button) setChecked(checked bool) { var chk uintptr if checked { chk = win.BST_CHECKED } else { chk = win.BST_UNCHECKED } b.SendMessage(win.BM_SETCHECK, chk, 0) b.checkedChangedPublisher.Publish() } func (b *Button) CheckedChanged() *Event { return b.checkedChangedPublisher.Event() } func (b *Button) Persistent() bool { return b.persistent } func (b *Button) SetPersistent(value bool) { b.persistent = value } func (b *Button) SaveState() error { return b.WriteState(fmt.Sprintf("%t", b.Checked())) } func (b *Button) RestoreState() error { s, err := b.ReadState() if err != nil { return err } b.SetChecked(s == "true") return nil } func (b *Button) Clicked() *Event { return b.clickedPublisher.Event() } func (b *Button) raiseClicked() { b.clickedPublisher.Publish() } func (b *Button) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_COMMAND: hiWP := win.HIWORD(uint32(wParam)) if hiWP == 0 && lParam == 0 { if a, ok := actionsById[win.LOWORD(uint32(wParam))]; ok { a.raiseTriggered() } } else { switch hiWP { case win.BN_CLICKED: b.raiseClicked() } } case win.WM_SETTEXT: b.textChangedPublisher.Publish() } return b.WidgetBase.WndProc(hwnd, msg, wParam, lParam) } // idealSize returns ideal button size in native pixels. func (b *Button) idealSize() Size { min := b.dialogBaseUnitsToPixels(Size{50, 14}) if b.Text() == "" { return min } var s win.SIZE b.SendMessage(win.BCM_GETIDEALSIZE, 0, uintptr(unsafe.Pointer(&s))) return maxSize(sizeFromSIZE(s), min) } func (b *Button) CreateLayoutItem(ctx *LayoutContext) LayoutItem { return &buttonLayoutItem{ idealSize: b.idealSize(), } } type buttonLayoutItem struct { LayoutItemBase idealSize Size // in native pixels } func (li *buttonLayoutItem) LayoutFlags() LayoutFlags { return 0 } func (li *buttonLayoutItem) IdealSize() Size { return li.MinSize() } func (li *buttonLayoutItem) MinSize() Size { return li.idealSize } ================================================ FILE: cancelevent.go ================================================ // Copyright 2011 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 type cancelEventHandlerInfo struct { handler CancelEventHandler once bool } type CancelEventHandler func(canceled *bool) type CancelEvent struct { handlers []cancelEventHandlerInfo } func (e *CancelEvent) Attach(handler CancelEventHandler) int { handlerInfo := cancelEventHandlerInfo{handler, false} for i, h := range e.handlers { if h.handler == nil { e.handlers[i] = handlerInfo return i } } e.handlers = append(e.handlers, handlerInfo) return len(e.handlers) - 1 } func (e *CancelEvent) Detach(handle int) { e.handlers[handle].handler = nil } func (e *CancelEvent) Once(handler CancelEventHandler) { i := e.Attach(handler) e.handlers[i].once = true } type CancelEventPublisher struct { event CancelEvent } func (p *CancelEventPublisher) Event() *CancelEvent { return &p.event } func (p *CancelEventPublisher) Publish(canceled *bool) { for i, h := range p.event.handlers { if h.handler != nil { h.handler(canceled) if h.once { p.event.Detach(i) } } } } ================================================ FILE: canvas.go ================================================ // Copyright 2010 The Walk Authorc. 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 ( "log" "syscall" "unicode/utf8" "unsafe" "github.com/lxn/win" ) // DrawText format flags type DrawTextFormat uint const ( TextTop DrawTextFormat = win.DT_TOP TextLeft DrawTextFormat = win.DT_LEFT TextCenter DrawTextFormat = win.DT_CENTER TextRight DrawTextFormat = win.DT_RIGHT TextVCenter DrawTextFormat = win.DT_VCENTER TextBottom DrawTextFormat = win.DT_BOTTOM TextWordbreak DrawTextFormat = win.DT_WORDBREAK TextSingleLine DrawTextFormat = win.DT_SINGLELINE TextExpandTabs DrawTextFormat = win.DT_EXPANDTABS TextTabstop DrawTextFormat = win.DT_TABSTOP TextNoClip DrawTextFormat = win.DT_NOCLIP TextExternalLeading DrawTextFormat = win.DT_EXTERNALLEADING TextCalcRect DrawTextFormat = win.DT_CALCRECT TextNoPrefix DrawTextFormat = win.DT_NOPREFIX TextInternal DrawTextFormat = win.DT_INTERNAL TextEditControl DrawTextFormat = win.DT_EDITCONTROL TextPathEllipsis DrawTextFormat = win.DT_PATH_ELLIPSIS TextEndEllipsis DrawTextFormat = win.DT_END_ELLIPSIS TextModifyString DrawTextFormat = win.DT_MODIFYSTRING TextRTLReading DrawTextFormat = win.DT_RTLREADING TextWordEllipsis DrawTextFormat = win.DT_WORD_ELLIPSIS TextNoFullWidthCharBreak DrawTextFormat = win.DT_NOFULLWIDTHCHARBREAK TextHidePrefix DrawTextFormat = win.DT_HIDEPREFIX TextPrefixOnly DrawTextFormat = win.DT_PREFIXONLY ) var gM *uint16 func init() { AppendToWalkInit(func() { gM = syscall.StringToUTF16Ptr("gM") }) } type Canvas struct { hdc win.HDC hBmpStock win.HBITMAP window Window dpi int bitmap *Bitmap recordingMetafile *Metafile measureTextMetafile *Metafile doNotDispose bool } func NewCanvasFromImage(image Image) (*Canvas, error) { switch img := image.(type) { case *Bitmap: hdc := win.CreateCompatibleDC(0) if hdc == 0 { return nil, newError("CreateCompatibleDC failed") } succeeded := false defer func() { if !succeeded { win.DeleteDC(hdc) } }() var hBmpStock win.HBITMAP if hBmpStock = win.HBITMAP(win.SelectObject(hdc, win.HGDIOBJ(img.hBmp))); hBmpStock == 0 { return nil, newError("SelectObject failed") } succeeded = true return (&Canvas{hdc: hdc, hBmpStock: hBmpStock, bitmap: img, dpi: img.dpi}).init() case *Metafile: c, err := newCanvasFromHDC(img.hdc) if err != nil { return nil, err } c.recordingMetafile = img return c, nil } return nil, newError("unsupported image type") } func newCanvasFromWindow(window Window) (*Canvas, error) { hdc := win.GetDC(window.Handle()) if hdc == 0 { return nil, newError("GetDC failed") } return (&Canvas{hdc: hdc, window: window}).init() } func newCanvasFromHDC(hdc win.HDC) (*Canvas, error) { if hdc == 0 { return nil, newError("invalid hdc") } return (&Canvas{hdc: hdc, doNotDispose: true}).init() } func (c *Canvas) init() (*Canvas, error) { if c.dpi == 0 { c.dpi = dpiForHDC(c.hdc) } if win.SetBkMode(c.hdc, win.TRANSPARENT) == 0 { return nil, newError("SetBkMode failed") } switch win.SetStretchBltMode(c.hdc, win.HALFTONE) { case 0, win.ERROR_INVALID_PARAMETER: return nil, newError("SetStretchBltMode failed") } if !win.SetBrushOrgEx(c.hdc, 0, 0, nil) { return nil, newError("SetBrushOrgEx failed") } return c, nil } func (c *Canvas) Dispose() { if !c.doNotDispose && c.hdc != 0 { if c.bitmap != nil { win.SelectObject(c.hdc, win.HGDIOBJ(c.hBmpStock)) win.DeleteDC(c.hdc) if err := c.bitmap.postProcess(); err != nil { log.Printf("*Canvas.Dispose - failed to post-process bitmap: %s", err.Error()) } } else { win.ReleaseDC(c.window.Handle(), c.hdc) } c.hdc = 0 } if c.recordingMetafile != nil { c.recordingMetafile.ensureFinished() c.recordingMetafile = nil } if c.measureTextMetafile != nil { c.measureTextMetafile.Dispose() c.measureTextMetafile = nil } } func (c *Canvas) DPI() int { if c.window != nil { return c.window.DPI() } return c.dpi } func (c *Canvas) withGdiObj(handle win.HGDIOBJ, f func() error) error { oldHandle := win.SelectObject(c.hdc, handle) if oldHandle == 0 { return newError("SelectObject failed") } defer win.SelectObject(c.hdc, oldHandle) return f() } func (c *Canvas) withBrush(brush Brush, f func() error) error { return c.withGdiObj(win.HGDIOBJ(brush.handle()), f) } func (c *Canvas) withFontAndTextColor(font *Font, color Color, f func() error) error { return c.withGdiObj(win.HGDIOBJ(font.handleForDPI(c.DPI())), func() error { oldColor := win.SetTextColor(c.hdc, win.COLORREF(color)) if oldColor == win.CLR_INVALID { return newError("SetTextColor failed") } defer func() { win.SetTextColor(c.hdc, oldColor) }() return f() }) } func (c *Canvas) HDC() win.HDC { return c.hdc } func (c *Canvas) Bounds() Rectangle { return RectangleTo96DPI(c.BoundsPixels(), c.DPI()) } func (c *Canvas) BoundsPixels() Rectangle { return Rectangle{ Width: int(win.GetDeviceCaps(c.hdc, win.HORZRES)), Height: int(win.GetDeviceCaps(c.hdc, win.VERTRES)), } } func (c *Canvas) withPen(pen Pen, f func() error) error { return c.withGdiObj(win.HGDIOBJ(pen.handleForDPI(c.dpi)), f) } func (c *Canvas) withBrushAndPen(brush Brush, pen Pen, f func() error) error { return c.withBrush(brush, func() error { return c.withPen(pen, f) }) } // ellipse draws an ellipse in 1/96" units. sizeCorrection parameter is in native pixels. // // Deprecated: Newer applications should use ellipsePixels. func (c *Canvas) ellipse(brush Brush, pen Pen, bounds Rectangle, sizeCorrection int) error { return c.ellipsePixels(brush, pen, RectangleFrom96DPI(bounds, c.DPI()), sizeCorrection) } // ellipsePixels draws an ellipse in native pixels. func (c *Canvas) ellipsePixels(brush Brush, pen Pen, bounds Rectangle, sizeCorrection int) error { return c.withBrushAndPen(brush, pen, func() error { if !win.Ellipse( c.hdc, int32(bounds.X), int32(bounds.Y), int32(bounds.X+bounds.Width+sizeCorrection), int32(bounds.Y+bounds.Height+sizeCorrection)) { return newError("Ellipse failed") } return nil }) } // DrawEllipse draws an ellipse in 1/96" units. // // Deprecated: Newer applications should use DrawEllipsePixels. func (c *Canvas) DrawEllipse(pen Pen, bounds Rectangle) error { return c.ellipse(nullBrushSingleton, pen, bounds, 0) } // DrawEllipsePixels draws an ellipse in native pixels. func (c *Canvas) DrawEllipsePixels(pen Pen, bounds Rectangle) error { return c.ellipsePixels(nullBrushSingleton, pen, bounds, 0) } // FillEllipse draws a filled ellipse in 1/96" units. // // Deprecated: Newer applications should use FillEllipsePixels. func (c *Canvas) FillEllipse(brush Brush, bounds Rectangle) error { return c.ellipse(brush, nullPenSingleton, bounds, 1) } // FillEllipsePixels draws a filled in native pixels. func (c *Canvas) FillEllipsePixels(brush Brush, bounds Rectangle) error { return c.ellipsePixels(brush, nullPenSingleton, bounds, 1) } // DrawImage draws image at given location (upper left) in 1/96" units unstretched. // // Deprecated: Newer applications should use DrawImagePixels. func (c *Canvas) DrawImage(image Image, location Point) error { return c.DrawImagePixels(image, PointFrom96DPI(location, c.DPI())) } // DrawImagePixels draws image at given location (upper left) in native pixels unstretched. func (c *Canvas) DrawImagePixels(image Image, location Point) error { if image == nil { return newError("image cannot be nil") } return image.draw(c.hdc, location) } // DrawImageStretched draws image at given location in 1/96" units stretched. // // Deprecated: Newer applications should use DrawImageStretchedPixels. func (c *Canvas) DrawImageStretched(image Image, bounds Rectangle) error { return c.DrawImageStretchedPixels(image, RectangleFrom96DPI(bounds, c.DPI())) } // DrawImageStretchedPixels draws image at given location in native pixels stretched. func (c *Canvas) DrawImageStretchedPixels(image Image, bounds Rectangle) error { if image == nil { return newError("image cannot be nil") } if dsoc, ok := image.(interface { drawStretchedOnCanvasPixels(canvas *Canvas, bounds Rectangle) error }); ok { return dsoc.drawStretchedOnCanvasPixels(c, bounds) } return image.drawStretched(c.hdc, bounds) } // DrawBitmapWithOpacity draws bitmap with opacity at given location in 1/96" units stretched. // // Deprecated: Newer applications should use DrawBitmapWithOpacityPixels. func (c *Canvas) DrawBitmapWithOpacity(bmp *Bitmap, bounds Rectangle, opacity byte) error { return c.DrawBitmapWithOpacityPixels(bmp, RectangleFrom96DPI(bounds, c.DPI()), opacity) } // DrawBitmapWithOpacityPixels draws bitmap with opacity at given location in native pixels // stretched. func (c *Canvas) DrawBitmapWithOpacityPixels(bmp *Bitmap, bounds Rectangle, opacity byte) error { if bmp == nil { return newError("bmp cannot be nil") } return bmp.alphaBlend(c.hdc, bounds, opacity) } // DrawBitmapPart draws bitmap at given location in native pixels. func (c *Canvas) DrawBitmapPart(bmp *Bitmap, dst, src Rectangle) error { return c.DrawBitmapPartWithOpacityPixels(bmp, dst, src, 0xff) } // DrawBitmapPartWithOpacity draws bitmap at given location in 1/96" units. // // Deprecated: Newer applications should use DrawBitmapPartWithOpacityPixels. func (c *Canvas) DrawBitmapPartWithOpacity(bmp *Bitmap, dst, src Rectangle, opacity byte) error { dpi := c.DPI() return c.DrawBitmapPartWithOpacityPixels(bmp, RectangleFrom96DPI(dst, dpi), RectangleFrom96DPI(src, dpi), opacity) } // DrawBitmapPartWithOpacityPixels draws bitmap at given location in native pixels. func (c *Canvas) DrawBitmapPartWithOpacityPixels(bmp *Bitmap, dst, src Rectangle, opacity byte) error { if bmp == nil { return newError("bmp cannot be nil") } return bmp.alphaBlendPart(c.hdc, dst, src, opacity) } // DrawLine draws a line between two points in 1/96" units. // // Deprecated: Newer applications should use DrawLinePixels. func (c *Canvas) DrawLine(pen Pen, from, to Point) error { dpi := c.DPI() return c.DrawLinePixels(pen, PointFrom96DPI(from, dpi), PointFrom96DPI(to, dpi)) } // DrawLinePixels draws a line between two points in native pixels. func (c *Canvas) DrawLinePixels(pen Pen, from, to Point) error { if !win.MoveToEx(c.hdc, int(from.X), int(from.Y), nil) { return newError("MoveToEx failed") } return c.withPen(pen, func() error { if !win.LineTo(c.hdc, int32(to.X), int32(to.Y)) { return newError("LineTo failed") } return nil }) } // DrawLine draws a line between given points in 1/96" units. // // Deprecated: Newer applications should use DrawLinePixels. func (c *Canvas) DrawPolyline(pen Pen, points []Point) error { if len(points) < 1 { return nil } dpi := c.DPI() pts := make([]win.POINT, len(points)) for i, p := range points { pts[i] = PointFrom96DPI(p, dpi).toPOINT() } return c.withPen(pen, func() error { if !win.Polyline(c.hdc, unsafe.Pointer(&pts[0].X), int32(len(pts))) { return newError("Polyline failed") } return nil }) } // DrawPolylinePixels draws a line between given points in native pixels. func (c *Canvas) DrawPolylinePixels(pen Pen, points []Point) error { if len(points) < 1 { return nil } pts := make([]win.POINT, len(points)) for i, p := range points { pts[i] = p.toPOINT() } return c.withPen(pen, func() error { if !win.Polyline(c.hdc, unsafe.Pointer(&pts[0].X), int32(len(pts))) { return newError("Polyline failed") } return nil }) } // rectangle draws a rectangle in 1/96" units. sizeCorrection parameter is in native pixels. // // Deprecated: Newer applications should use rectanglePixels. func (c *Canvas) rectangle(brush Brush, pen Pen, bounds Rectangle, sizeCorrection int) error { return c.rectanglePixels(brush, pen, RectangleFrom96DPI(bounds, c.DPI()), sizeCorrection) } // rectanglePixels draws a rectangle in native pixels. func (c *Canvas) rectanglePixels(brush Brush, pen Pen, bounds Rectangle, sizeCorrection int) error { return c.withBrushAndPen(brush, pen, func() error { if !win.Rectangle_( c.hdc, int32(bounds.X), int32(bounds.Y), int32(bounds.X+bounds.Width+sizeCorrection), int32(bounds.Y+bounds.Height+sizeCorrection)) { return newError("Rectangle_ failed") } return nil }) } // DrawRectangle draws a rectangle in 1/96" units. // // Deprecated: Newer applications should use DrawRectanglePixels. func (c *Canvas) DrawRectangle(pen Pen, bounds Rectangle) error { return c.rectangle(nullBrushSingleton, pen, bounds, 0) } // DrawRectanglePixels draws a rectangle in native pixels. func (c *Canvas) DrawRectanglePixels(pen Pen, bounds Rectangle) error { return c.rectanglePixels(nullBrushSingleton, pen, bounds, 0) } // FillRectangle draws a filled rectangle in 1/96" units. // // Deprecated: Newer applications should use FillRectanglePixels. func (c *Canvas) FillRectangle(brush Brush, bounds Rectangle) error { return c.rectangle(brush, nullPenSingleton, bounds, 1) } // FillRectanglePixels draws a filled rectangle in native pixels. func (c *Canvas) FillRectanglePixels(brush Brush, bounds Rectangle) error { return c.rectanglePixels(brush, nullPenSingleton, bounds, 1) } // roundedRectangle draws a rounded rectangle in 1/96" units. sizeCorrection parameter is in native // pixels. // // Deprecated: Newer applications should use roundedRectanglePixels. func (c *Canvas) roundedRectangle(brush Brush, pen Pen, bounds Rectangle, ellipseSize Size, sizeCorrection int) error { dpi := c.DPI() return c.roundedRectanglePixels(brush, pen, RectangleFrom96DPI(bounds, dpi), SizeFrom96DPI(ellipseSize, dpi), sizeCorrection) } // roundedRectanglePixels draws a rounded rectangle in native pixels. func (c *Canvas) roundedRectanglePixels(brush Brush, pen Pen, bounds Rectangle, ellipseSize Size, sizeCorrection int) error { return c.withBrushAndPen(brush, pen, func() error { if !win.RoundRect( c.hdc, int32(bounds.X), int32(bounds.Y), int32(bounds.X+bounds.Width+sizeCorrection), int32(bounds.Y+bounds.Height+sizeCorrection), int32(ellipseSize.Width), int32(ellipseSize.Height)) { return newError("RoundRect failed") } return nil }) } // DrawRoundedRectangle draws a rounded rectangle in 1/96" units. sizeCorrection parameter is in native // pixels. // // Deprecated: Newer applications should use DrawRoundedRectanglePixels. func (c *Canvas) DrawRoundedRectangle(pen Pen, bounds Rectangle, ellipseSize Size) error { return c.roundedRectangle(nullBrushSingleton, pen, bounds, ellipseSize, 0) } // DrawRoundedRectanglePixels draws a rounded rectangle in native pixels. func (c *Canvas) DrawRoundedRectanglePixels(pen Pen, bounds Rectangle, ellipseSize Size) error { return c.roundedRectanglePixels(nullBrushSingleton, pen, bounds, ellipseSize, 0) } // FillRoundedRectangle draws a filled rounded rectangle in 1/96" units. sizeCorrection parameter // is in native // pixels. // // Deprecated: Newer applications should use FillRoundedRectanglePixels. func (c *Canvas) FillRoundedRectangle(brush Brush, bounds Rectangle, ellipseSize Size) error { return c.roundedRectangle(brush, nullPenSingleton, bounds, ellipseSize, 1) } // FillRoundedRectanglePixels draws a filled rounded rectangle in native pixels. func (c *Canvas) FillRoundedRectanglePixels(brush Brush, bounds Rectangle, ellipseSize Size) error { return c.roundedRectanglePixels(brush, nullPenSingleton, bounds, ellipseSize, 1) } // GradientFillRectangle draws a gradient filled rectangle in 1/96" units. // // Deprecated: Newer applications should use GradientFillRectanglePixels. func (c *Canvas) GradientFillRectangle(color1, color2 Color, orientation Orientation, bounds Rectangle) error { return c.GradientFillRectanglePixels(color1, color2, orientation, RectangleFrom96DPI(bounds, c.DPI())) } // GradientFillRectanglePixels draws a gradient filled rectangle in native pixels. func (c *Canvas) GradientFillRectanglePixels(color1, color2 Color, orientation Orientation, bounds Rectangle) error { vertices := [2]win.TRIVERTEX{ { X: int32(bounds.X), Y: int32(bounds.Y), Red: uint16(color1.R()) * 256, Green: uint16(color1.G()) * 256, Blue: uint16(color1.B()) * 256, Alpha: 0, }, { X: int32(bounds.X + bounds.Width), Y: int32(bounds.Y + bounds.Height), Red: uint16(color2.R()) * 256, Green: uint16(color2.G()) * 256, Blue: uint16(color2.B()) * 256, Alpha: 0, }, } indices := win.GRADIENT_RECT{ UpperLeft: 0, LowerRight: 1, } var o uint32 if orientation == Vertical { o = 1 } if !win.GradientFill(c.hdc, &vertices[0], 2, unsafe.Pointer(&indices), 1, o) { return newError("GradientFill failed") } return nil } // DrawText draws text at given location in 1/96" units. // // Deprecated: Newer applications should use DrawTextPixels. func (c *Canvas) DrawText(text string, font *Font, color Color, bounds Rectangle, format DrawTextFormat) error { return c.DrawTextPixels(text, font, color, RectangleFrom96DPI(bounds, c.DPI()), format) } // DrawTextPixels draws text at given location in native pixels. func (c *Canvas) DrawTextPixels(text string, font *Font, color Color, bounds Rectangle, format DrawTextFormat) error { return c.withFontAndTextColor(font, color, func() error { rect := bounds.toRECT() ret := win.DrawTextEx( c.hdc, syscall.StringToUTF16Ptr(text), -1, &rect, uint32(format)|win.DT_EDITCONTROL, nil) if ret == 0 { return newError("DrawTextEx failed") } return nil }) } // fontHeight returns font height in native pixels. func (c *Canvas) fontHeight(font *Font) (height int, err error) { err = c.withFontAndTextColor(font, 0, func() error { var size win.SIZE if !win.GetTextExtentPoint32(c.hdc, gM, 2, &size) { return newError("GetTextExtentPoint32 failed") } height = int(size.CY) if height == 0 { return newError("invalid font height") } return nil }) return } // measureTextForDPI measures text for given DPI. Input and output bounds are in native pixels. func (c *Canvas) measureTextForDPI(text string, font *Font, bounds Rectangle, format DrawTextFormat, dpi int) (boundsMeasured Rectangle, err error) { hFont := win.HGDIOBJ(font.handleForDPI(dpi)) oldHandle := win.SelectObject(c.hdc, hFont) if oldHandle == 0 { err = newError("SelectObject failed") return } defer win.SelectObject(c.hdc, oldHandle) rect := &win.RECT{ int32(bounds.X), int32(bounds.Y), int32(bounds.X + bounds.Width), int32(bounds.Y + bounds.Height), } var params win.DRAWTEXTPARAMS params.CbSize = uint32(unsafe.Sizeof(params)) strPtr := syscall.StringToUTF16Ptr(text) dtfmt := uint32(format) | win.DT_CALCRECT | win.DT_EDITCONTROL | win.DT_NOPREFIX | win.DT_WORDBREAK height := win.DrawTextEx( c.hdc, strPtr, -1, rect, dtfmt, ¶ms) if height == 0 { err = newError("DrawTextEx failed") return } boundsMeasured = Rectangle{ int(rect.Left), int(rect.Top), int(rect.Right - rect.Left), int(height), } return } // MeasureText measures text size. Input and output bounds are in 1/96" units. // // Deprecated: Newer applications should use MeasureTextPixels. func (c *Canvas) MeasureText(text string, font *Font, bounds Rectangle, format DrawTextFormat) (boundsMeasured Rectangle, runesFitted int, err error) { dpi := c.DPI() var boundsMeasuredPixels Rectangle boundsMeasuredPixels, runesFitted, err = c.MeasureTextPixels(text, font, RectangleFrom96DPI(bounds, dpi), format) if err != nil { return } boundsMeasured = RectangleTo96DPI(boundsMeasuredPixels, dpi) return } // MeasureTextPixels measures text size. Input and output bounds are in native pixels. func (c *Canvas) MeasureTextPixels(text string, font *Font, bounds Rectangle, format DrawTextFormat) (boundsMeasured Rectangle, runesFitted int, err error) { boundsMeasured, _, runesFitted, err = c.measureAndModifyTextPixels(text, font, bounds, format) return } // MeasureAndModifyTextPixels measures text size and also supports modification // of the text which occurs if it does not fit into the specified bounds. // // Input and output bounds are in native pixels. func (c *Canvas) MeasureAndModifyTextPixels(text string, font *Font, bounds Rectangle, format DrawTextFormat) (boundsMeasured Rectangle, textDisplayed string, err error) { var textPtr *uint16 var runesFitted int if boundsMeasured, textPtr, runesFitted, err = c.measureAndModifyTextPixels(text, font, bounds, format|TextModifyString); err != nil { return } if runesFitted == utf8.RuneCountInString(text) { textDisplayed = text } else { if format&(TextEndEllipsis|TextPathEllipsis) != 0 { textDisplayed = win.UTF16PtrToString(textPtr) } else { textDisplayed = string(([]rune)(text)[:runesFitted]) } } return } func (c *Canvas) measureAndModifyTextPixels(text string, font *Font, bounds Rectangle, format DrawTextFormat) (boundsMeasured Rectangle, textPtr *uint16, runesFitted int, err error) { // HACK: We don't want to actually draw on the Canvas here, but if we use // the DT_CALCRECT flag to avoid drawing, params.UiLengthDrawn will // not contain a useful value. To work around this, we create an in-memory // metafile and draw into that instead. if c.measureTextMetafile == nil { c.measureTextMetafile, err = NewMetafile(c) if err != nil { return } } hFont := win.HGDIOBJ(font.handleForDPI(c.DPI())) oldHandle := win.SelectObject(c.measureTextMetafile.hdc, hFont) if oldHandle == 0 { err = newError("SelectObject failed") return } defer win.SelectObject(c.measureTextMetafile.hdc, oldHandle) rect := &win.RECT{ int32(bounds.X), int32(bounds.Y), int32(bounds.X + bounds.Width), int32(bounds.Y + bounds.Height), } var params win.DRAWTEXTPARAMS params.CbSize = uint32(unsafe.Sizeof(params)) strPtr := syscall.StringToUTF16Ptr(text) dtfmt := uint32(format) | win.DT_EDITCONTROL | win.DT_WORDBREAK height := win.DrawTextEx( c.measureTextMetafile.hdc, strPtr, -1, rect, dtfmt, ¶ms) if height == 0 { err = newError("DrawTextEx failed") return } boundsMeasured = Rectangle{ int(rect.Left), int(rect.Top), int(rect.Right - rect.Left), int(height), } textPtr = strPtr runesFitted = int(params.UiLengthDrawn) return } ================================================ FILE: checkbox.go ================================================ // 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 ( "strconv" "github.com/lxn/win" ) type CheckState int const ( CheckUnchecked CheckState = win.BST_UNCHECKED CheckChecked CheckState = win.BST_CHECKED CheckIndeterminate CheckState = win.BST_INDETERMINATE ) var checkBoxCheckSize Size // in native pixels type CheckBox struct { Button checkStateChangedPublisher EventPublisher } func NewCheckBox(parent Container) (*CheckBox, error) { cb := new(CheckBox) if err := InitWidget( cb, parent, "BUTTON", win.WS_TABSTOP|win.WS_VISIBLE|win.BS_AUTOCHECKBOX, 0); err != nil { return nil, err } cb.Button.init() cb.SetBackground(nullBrushSingleton) cb.GraphicsEffects().Add(InteractionEffect) cb.GraphicsEffects().Add(FocusEffect) cb.MustRegisterProperty("CheckState", NewProperty( func() interface{} { return cb.CheckState() }, func(v interface{}) error { cb.SetCheckState(CheckState(assertIntOr(v, 0))) return nil }, cb.CheckStateChanged())) return cb, nil } func (cb *CheckBox) TextOnLeftSide() bool { return cb.hasStyleBits(win.BS_LEFTTEXT) } func (cb *CheckBox) SetTextOnLeftSide(textLeft bool) error { return cb.ensureStyleBits(win.BS_LEFTTEXT, textLeft) } func (cb *CheckBox) setChecked(checked bool) { cb.Button.setChecked(checked) cb.checkStateChangedPublisher.Publish() } func (cb *CheckBox) Tristate() bool { return cb.hasStyleBits(win.BS_AUTO3STATE) } func (cb *CheckBox) SetTristate(tristate bool) error { var set, clear uint32 if tristate { set, clear = win.BS_AUTO3STATE, win.BS_AUTOCHECKBOX } else { set, clear = win.BS_AUTOCHECKBOX, win.BS_AUTO3STATE } return cb.setAndClearStyleBits(set, clear) } func (cb *CheckBox) CheckState() CheckState { return CheckState(cb.SendMessage(win.BM_GETCHECK, 0, 0)) } func (cb *CheckBox) SetCheckState(state CheckState) { if state == cb.CheckState() { return } cb.SendMessage(win.BM_SETCHECK, uintptr(state), 0) cb.checkedChangedPublisher.Publish() cb.checkStateChangedPublisher.Publish() } func (cb *CheckBox) CheckStateChanged() *Event { return cb.checkStateChangedPublisher.Event() } func (cb *CheckBox) SaveState() error { return cb.WriteState(strconv.Itoa(int(cb.CheckState()))) } func (cb *CheckBox) RestoreState() error { s, err := cb.ReadState() if err != nil { return err } cs, err := strconv.Atoi(s) if err != nil { return err } cb.SetCheckState(CheckState(cs)) return nil } func (cb *CheckBox) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_COMMAND: switch win.HIWORD(uint32(wParam)) { case win.BN_CLICKED: cb.checkedChangedPublisher.Publish() cb.checkStateChangedPublisher.Publish() } } return cb.Button.WndProc(hwnd, msg, wParam, lParam) } ================================================ FILE: clipboard.go ================================================ // Copyright 2013 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" "github.com/lxn/win" ) const clipboardWindowClass = `\o/ Walk_Clipboard_Class \o/` func init() { AppendToWalkInit(func() { MustRegisterWindowClassWithWndProcPtr(clipboardWindowClass, syscall.NewCallback(clipboardWndProc)) hwnd := win.CreateWindowEx( 0, syscall.StringToUTF16Ptr(clipboardWindowClass), nil, 0, 0, 0, 0, 0, win.HWND_MESSAGE, 0, 0, nil) if hwnd == 0 { panic("failed to create clipboard window") } if !win.AddClipboardFormatListener(hwnd) { lastError("AddClipboardFormatListener") } clipboard.hwnd = hwnd }) } func clipboardWndProc(hwnd win.HWND, msg uint32, wp, lp uintptr) uintptr { switch msg { case win.WM_CLIPBOARDUPDATE: clipboard.contentsChangedPublisher.Publish() return 0 } return win.DefWindowProc(hwnd, msg, wp, lp) } var clipboard ClipboardService // Clipboard returns an object that provides access to the system clipboard. func Clipboard() *ClipboardService { return &clipboard } // ClipboardService provides access to the system clipboard. type ClipboardService struct { hwnd win.HWND contentsChangedPublisher EventPublisher } // ContentsChanged returns an Event that you can attach to for handling // clipboard content changes. func (c *ClipboardService) ContentsChanged() *Event { return c.contentsChangedPublisher.Event() } // Clear clears the contents of the clipboard. func (c *ClipboardService) Clear() error { return c.withOpenClipboard(func() error { if !win.EmptyClipboard() { return lastError("EmptyClipboard") } return nil }) } // ContainsText returns whether the clipboard currently contains text data. func (c *ClipboardService) ContainsText() (available bool, err error) { err = c.withOpenClipboard(func() error { available = win.IsClipboardFormatAvailable(win.CF_UNICODETEXT) return nil }) return } // Text returns the current text data of the clipboard. func (c *ClipboardService) Text() (text string, err error) { err = c.withOpenClipboard(func() error { hMem := win.HGLOBAL(win.GetClipboardData(win.CF_UNICODETEXT)) if hMem == 0 { return lastError("GetClipboardData") } p := win.GlobalLock(hMem) if p == nil { return lastError("GlobalLock()") } defer win.GlobalUnlock(hMem) text = win.UTF16PtrToString((*uint16)(p)) return nil }) return } // SetText sets the current text data of the clipboard. func (c *ClipboardService) SetText(s string) error { return c.withOpenClipboard(func() error { utf16, err := syscall.UTF16FromString(s) if err != nil { return err } hMem := win.GlobalAlloc(win.GMEM_MOVEABLE, uintptr(len(utf16)*2)) if hMem == 0 { return lastError("GlobalAlloc") } p := win.GlobalLock(hMem) if p == nil { return lastError("GlobalLock()") } win.MoveMemory(p, unsafe.Pointer(&utf16[0]), uintptr(len(utf16)*2)) win.GlobalUnlock(hMem) if 0 == win.SetClipboardData(win.CF_UNICODETEXT, win.HANDLE(hMem)) { // We need to free hMem. defer win.GlobalFree(hMem) return lastError("SetClipboardData") } // The system now owns the memory referred to by hMem. return nil }) } func (c *ClipboardService) withOpenClipboard(f func() error) error { if !win.OpenClipboard(c.hwnd) { return lastError("OpenClipboard") } defer win.CloseClipboard() return f() } ================================================ FILE: closeevent.go ================================================ // Copyright 2011 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 type closeEventHandlerInfo struct { handler CloseEventHandler once bool } type CloseEventHandler func(canceled *bool, reason CloseReason) type CloseEvent struct { handlers []closeEventHandlerInfo } func (e *CloseEvent) Attach(handler CloseEventHandler) int { handlerInfo := closeEventHandlerInfo{handler, false} for i, h := range e.handlers { if h.handler == nil { e.handlers[i] = handlerInfo return i } } e.handlers = append(e.handlers, handlerInfo) return len(e.handlers) - 1 } func (e *CloseEvent) Detach(handle int) { e.handlers[handle].handler = nil } func (e *CloseEvent) Once(handler CloseEventHandler) { i := e.Attach(handler) e.handlers[i].once = true } type CloseEventPublisher struct { event CloseEvent } func (p *CloseEventPublisher) Event() *CloseEvent { return &p.event } func (p *CloseEventPublisher) Publish(canceled *bool, reason CloseReason) { for i, h := range p.event.handlers { if h.handler != nil { h.handler(canceled, reason) if h.once { p.event.Detach(i) } } } } ================================================ FILE: color.go ================================================ // 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 type Color uint32 func RGB(r, g, b byte) Color { return Color(uint32(r) | uint32(g)<<8 | uint32(b)<<16) } func (c Color) R() byte { return byte(c & 0xff) } func (c Color) G() byte { return byte((c >> 8) & 0xff) } func (c Color) B() byte { return byte((c >> 16) & 0xff) } ================================================ FILE: combobox.go ================================================ // 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 ( "fmt" "math/big" "reflect" "strconv" "syscall" "time" "unsafe" "github.com/lxn/win" ) type ComboBox struct { WidgetBase bindingValueProvider BindingValueProvider model ListModel providedModel interface{} bindingMember string displayMember string format string precision int itemsResetHandlerHandle int itemChangedHandlerHandle int itemsInsertedHandlerHandle int itemsRemovedHandlerHandle int maxItemTextWidth int // in native pixels currentValue interface{} prevCurIndex int selChangeIndex int maxLength int currentIndexChangedPublisher EventPublisher textChangedPublisher EventPublisher editingFinishedPublisher EventPublisher editOrigWndProcPtr uintptr editing bool persistent bool } var comboBoxEditWndProcPtr uintptr func init() { AppendToWalkInit(func() { comboBoxEditWndProcPtr = syscall.NewCallback(comboBoxEditWndProc) }) } func comboBoxEditWndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { cb := (*ComboBox)(unsafe.Pointer(win.GetWindowLongPtr(hwnd, win.GWLP_USERDATA))) switch msg { case win.WM_GETDLGCODE: if !cb.editing { if form := ancestor(cb); form != nil { if dlg, ok := form.(dialogish); ok { if dlg.DefaultButton() != nil { // If the ComboBox 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: if wParam != win.VK_RETURN || 0 == cb.SendMessage(win.CB_GETDROPPEDSTATE, 0, 0) { cb.handleKeyDown(wParam, lParam) } if cb.editing && wParam == win.VK_RETURN { cb.editing = false cb.editingFinishedPublisher.Publish() } case win.WM_KEYUP: if wParam != win.VK_RETURN || 0 == cb.SendMessage(win.CB_GETDROPPEDSTATE, 0, 0) { cb.handleKeyUp(wParam, lParam) } case win.WM_SETFOCUS, win.WM_KILLFOCUS: cb.invalidateBorderInParent() if cb.editing && msg == win.WM_KILLFOCUS { cb.editing = false cb.editingFinishedPublisher.Publish() } } return win.CallWindowProc(cb.editOrigWndProcPtr, hwnd, msg, wParam, lParam) } func NewComboBox(parent Container) (*ComboBox, error) { cb, err := newComboBoxWithStyle(parent, win.CBS_AUTOHSCROLL|win.CBS_DROPDOWN) if err != nil { return nil, err } editHwnd := win.GetWindow(cb.hWnd, win.GW_CHILD) win.SetWindowLongPtr(editHwnd, win.GWLP_USERDATA, uintptr(unsafe.Pointer(cb))) cb.editOrigWndProcPtr = win.SetWindowLongPtr(editHwnd, win.GWLP_WNDPROC, comboBoxEditWndProcPtr) return cb, nil } func NewDropDownBox(parent Container) (*ComboBox, error) { return newComboBoxWithStyle(parent, win.CBS_DROPDOWNLIST) } func newComboBoxWithStyle(parent Container, style uint32) (*ComboBox, error) { cb := &ComboBox{prevCurIndex: -1, selChangeIndex: -1, precision: 2} if err := InitWidget( cb, parent, "COMBOBOX", win.WS_TABSTOP|win.WS_VISIBLE|win.WS_VSCROLL|style, 0); err != nil { return nil, err } succeeded := false defer func() { if !succeeded { cb.Dispose() } }() var event *Event if style&win.CBS_DROPDOWNLIST == win.CBS_DROPDOWNLIST { event = cb.CurrentIndexChanged() } else { event = cb.TextChanged() } cb.GraphicsEffects().Add(InteractionEffect) cb.GraphicsEffects().Add(FocusEffect) cb.MustRegisterProperty("CurrentIndex", NewProperty( func() interface{} { return cb.CurrentIndex() }, func(v interface{}) error { return cb.SetCurrentIndex(assertIntOr(v, -1)) }, cb.CurrentIndexChanged())) cb.MustRegisterProperty("Text", NewProperty( func() interface{} { return cb.Text() }, func(v interface{}) error { return cb.SetText(assertStringOr(v, "")) }, event)) cb.MustRegisterProperty("CurrentItem", NewReadOnlyProperty( func() interface{} { if rlm, ok := cb.providedModel.(ReflectListModel); ok { if i := cb.CurrentIndex(); i > -1 { return reflect.ValueOf(rlm.Items()).Index(i).Interface() } } return nil }, cb.CurrentIndexChanged())) cb.MustRegisterProperty("HasCurrentItem", NewReadOnlyBoolProperty( func() bool { return cb.CurrentIndex() != -1 }, cb.CurrentIndexChanged())) cb.MustRegisterProperty("TextNotEmpty", NewReadOnlyBoolProperty( func() bool { return cb.Text() != "" }, cb.CurrentIndexChanged())) cb.MustRegisterProperty("Value", NewProperty( func() interface{} { if cb.Editable() { return cb.Text() } index := cb.CurrentIndex() if cb.bindingValueProvider == nil || index == -1 { return nil } return cb.bindingValueProvider.BindingValue(index) }, func(v interface{}) error { if cb.Editable() { return cb.SetText(assertStringOr(v, "")) } if cb.bindingValueProvider == nil { if cb.model == nil { return nil } else { return newError("Data binding is only supported using a model that implements BindingValueProvider.") } } index := -1 count := cb.model.ItemCount() for i := 0; i < count; i++ { if cb.bindingValueProvider.BindingValue(i) == v { index = i break } } return cb.SetCurrentIndex(index) }, event)) succeeded = true return cb, nil } func (cb *ComboBox) applyFont(font *Font) { cb.WidgetBase.applyFont(font) if cb.model != nil { cb.maxItemTextWidth = cb.calculateMaxItemTextWidth() cb.RequestLayout() } } func (cb *ComboBox) Editable() bool { return !cb.hasStyleBits(win.CBS_DROPDOWNLIST) } func (cb *ComboBox) itemString(index int) string { switch val := cb.model.Value(index).(type) { case string: return val case time.Time: return val.Format(cb.format) case *big.Rat: return val.FloatString(cb.precision) default: return fmt.Sprintf(cb.format, val) } panic("unreachable") } func (cb *ComboBox) insertItemAt(index int) error { str := cb.itemString(index) lp := uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(str))) if win.CB_ERR == cb.SendMessage(win.CB_INSERTSTRING, uintptr(index), lp) { return newError("SendMessage(CB_INSERTSTRING)") } return nil } func (cb *ComboBox) removeItem(index int) error { if win.CB_ERR == cb.SendMessage(win.CB_DELETESTRING, uintptr(index), 0) { return newError("SendMessage(CB_DELETESTRING") } return nil } func (cb *ComboBox) resetItems() error { cb.SetSuspended(true) defer cb.SetSuspended(false) cb.selChangeIndex = -1 if win.FALSE == cb.SendMessage(win.CB_RESETCONTENT, 0, 0) { return newError("SendMessage(CB_RESETCONTENT)") } cb.maxItemTextWidth = 0 oldValue := cb.currentValue if cb.model == nil { cb.SetCurrentIndex(-1) return nil } count := cb.model.ItemCount() for i := 0; i < count; i++ { if err := cb.insertItemAt(i); err != nil { return err } } if oldValue != nil { cb.Property("Value").Set(oldValue) } else { cb.SetCurrentIndex(-1) } cb.RequestLayout() return nil } func (cb *ComboBox) attachModel() { itemsResetHandler := func() { cb.resetItems() } cb.itemsResetHandlerHandle = cb.model.ItemsReset().Attach(itemsResetHandler) itemChangedHandler := func(index int) { if win.CB_ERR == cb.SendMessage(win.CB_DELETESTRING, uintptr(index), 0) { newError("SendMessage(CB_DELETESTRING)") } cb.insertItemAt(index) cb.SetCurrentIndex(cb.prevCurIndex) } cb.itemChangedHandlerHandle = cb.model.ItemChanged().Attach(itemChangedHandler) cb.itemsInsertedHandlerHandle = cb.model.ItemsInserted().Attach(func(from, to int) { for i := from; i <= to; i++ { cb.insertItemAt(i) } }) cb.itemsRemovedHandlerHandle = cb.model.ItemsRemoved().Attach(func(from, to int) { for i := to; i >= from; i-- { cb.removeItem(i) } }) } func (cb *ComboBox) detachModel() { cb.model.ItemsReset().Detach(cb.itemsResetHandlerHandle) cb.model.ItemChanged().Detach(cb.itemChangedHandlerHandle) cb.model.ItemsInserted().Detach(cb.itemsInsertedHandlerHandle) cb.model.ItemsRemoved().Detach(cb.itemsRemovedHandlerHandle) } // Model returns the model of the ComboBox. func (cb *ComboBox) Model() interface{} { return cb.providedModel } // SetModel sets the model of the ComboBox. // // It is required that mdl either implements walk.ListModel or // walk.ReflectListModel or be a slice of pointers to struct or a []string. func (cb *ComboBox) SetModel(mdl interface{}) error { model, ok := mdl.(ListModel) if !ok && mdl != nil { var err error if model, err = newReflectListModel(mdl); err != nil { return err } if _, ok := mdl.([]string); !ok { if badms, ok := model.(bindingAndDisplayMemberSetter); ok { var bindingMember string if cb.Editable() { bindingMember = cb.displayMember } else { bindingMember = cb.bindingMember } badms.setBindingMember(bindingMember) badms.setDisplayMember(cb.displayMember) } } } cb.providedModel = mdl if cb.model != nil { cb.detachModel() } cb.model = model cb.bindingValueProvider, _ = model.(BindingValueProvider) if model != nil { cb.attachModel() } if err := cb.resetItems(); err != nil { return err } if !cb.Editable() && model != nil && model.ItemCount() == 1 { cb.SetCurrentIndex(0) } return cb.Invalidate() } // BindingMember returns the member from the model of the ComboBox that is bound // to a field of the data source managed by an associated DataBinder. // // This is only applicable to walk.ReflectListModel models and simple slices of // pointers to struct. func (cb *ComboBox) BindingMember() string { return cb.bindingMember } // SetBindingMember sets the member from the model of the ComboBox that is bound // to a field of the data source managed by an associated DataBinder. // // This is only applicable to walk.ReflectListModel models and simple slices of // pointers to struct. // // For a model consisting of items of type S, data source field of type T and // bindingMember "Foo", this can be one of the following: // // A field Foo T // A method func (s S) Foo() T // A method func (s S) Foo() (T, error) // // If bindingMember is not a simple member name like "Foo", but a path to a // member like "A.B.Foo", members "A" and "B" both must be one of the options // mentioned above, but with T having type pointer to struct. func (cb *ComboBox) SetBindingMember(bindingMember string) error { if bindingMember != "" { if _, ok := cb.providedModel.([]string); ok { return newError("invalid for []string model") } } cb.bindingMember = bindingMember if badms, ok := cb.model.(bindingAndDisplayMemberSetter); ok { badms.setBindingMember(bindingMember) } return nil } // DisplayMember returns the member from the model of the ComboBox that is // displayed in the ComboBox. // // This is only applicable to walk.ReflectListModel models and simple slices of // pointers to struct. func (cb *ComboBox) DisplayMember() string { return cb.displayMember } // SetDisplayMember sets the member from the model of the ComboBox that is // displayed in the ComboBox. // // This is only applicable to walk.ReflectListModel models and simple slices of // pointers to struct. // // For a model consisting of items of type S, the type of the specified member T // and displayMember "Foo", this can be one of the following: // // A field Foo T // A method func (s S) Foo() T // A method func (s S) Foo() (T, error) // // If displayMember is not a simple member name like "Foo", but a path to a // member like "A.B.Foo", members "A" and "B" both must be one of the options // mentioned above, but with T having type pointer to struct. func (cb *ComboBox) SetDisplayMember(displayMember string) error { if displayMember != "" { if _, ok := cb.providedModel.([]string); ok { return newError("invalid for []string model") } } cb.displayMember = displayMember if badms, ok := cb.model.(bindingAndDisplayMemberSetter); ok { badms.setDisplayMember(displayMember) } return nil } func (cb *ComboBox) Format() string { return cb.format } func (cb *ComboBox) SetFormat(value string) { cb.format = value } func (cb *ComboBox) Precision() int { return cb.precision } func (cb *ComboBox) SetPrecision(value int) { cb.precision = value } func (cb *ComboBox) MaxLength() int { return cb.maxLength } func (cb *ComboBox) SetMaxLength(value int) { cb.SendMessage(win.CB_LIMITTEXT, uintptr(value), 0) cb.maxLength = value } // calculateMaxItemTextWidth returns maximum item text width in native pixels. func (cb *ComboBox) calculateMaxItemTextWidth() int { hdc := win.GetDC(cb.hWnd) if hdc == 0 { newError("GetDC failed") return -1 } defer win.ReleaseDC(cb.hWnd, hdc) hFontOld := win.SelectObject(hdc, win.HGDIOBJ(cb.Font().handleForDPI(cb.DPI()))) defer win.SelectObject(hdc, hFontOld) var maxWidth int count := cb.model.ItemCount() for i := 0; i < count; i++ { var s win.SIZE str := syscall.StringToUTF16(cb.itemString(i)) if !win.GetTextExtentPoint32(hdc, &str[0], int32(len(str)-1), &s) { newError("GetTextExtentPoint32 failed") return -1 } maxWidth = maxi(maxWidth, int(s.CX)) } return maxWidth } func (cb *ComboBox) CurrentIndex() int { return int(int32(cb.SendMessage(win.CB_GETCURSEL, 0, 0))) } func (cb *ComboBox) SetCurrentIndex(value int) error { index := int(int32(cb.SendMessage(win.CB_SETCURSEL, uintptr(value), 0))) if index != value { return newError("invalid index") } if value != cb.prevCurIndex { if value == -1 { cb.currentValue = nil } else { cb.currentValue = cb.Property("Value").Get() } cb.prevCurIndex = value cb.currentIndexChangedPublisher.Publish() } return nil } func (cb *ComboBox) CurrentIndexChanged() *Event { return cb.currentIndexChangedPublisher.Event() } func (cb *ComboBox) Text() string { return cb.text() } func (cb *ComboBox) SetText(value string) error { var oldText string oldText, _ = cb.currentValue.(string) if err := cb.setText(value); err != nil { return err } if value == oldText { return nil } if cb.Editable() { cb.currentValue = value } cb.textChangedPublisher.Publish() return nil } func (cb *ComboBox) TextSelection() (start, end int) { cb.SendMessage(win.CB_GETEDITSEL, uintptr(unsafe.Pointer(&start)), uintptr(unsafe.Pointer(&end))) return } func (cb *ComboBox) SetTextSelection(start, end int) { cb.SendMessage(win.CB_SETEDITSEL, 0, uintptr(win.MAKELONG(uint16(start), uint16(end)))) } func (cb *ComboBox) TextChanged() *Event { return cb.textChangedPublisher.Event() } func (cb *ComboBox) EditingFinished() *Event { return cb.editingFinishedPublisher.Event() } func (cb *ComboBox) Persistent() bool { return cb.persistent } func (cb *ComboBox) SetPersistent(value bool) { cb.persistent = value } func (cb *ComboBox) SaveState() error { cb.WriteState(strconv.Itoa(cb.CurrentIndex())) return nil } func (cb *ComboBox) RestoreState() error { state, err := cb.ReadState() if err != nil { return err } if state == "" { return nil } if i, err := strconv.Atoi(state); err == nil { cb.SetCurrentIndex(i) } return nil } func (cb *ComboBox) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_COMMAND: code := win.HIWORD(uint32(wParam)) selIndex := cb.CurrentIndex() switch code { case win.CBN_EDITCHANGE: cb.editing = true cb.selChangeIndex = -1 cb.textChangedPublisher.Publish() case win.CBN_SELCHANGE: cb.selChangeIndex = selIndex cb.currentIndexChangedPublisher.Publish() case win.CBN_SELENDCANCEL: if cb.selChangeIndex != -1 { if cb.selChangeIndex < cb.model.ItemCount() { cb.SetCurrentIndex(cb.selChangeIndex) } cb.selChangeIndex = -1 } case win.CBN_SELENDOK: if editable := cb.Editable(); editable || selIndex != cb.prevCurIndex { valueProp := cb.Property("Value") if editable && selIndex > -1 { valueProp.Set(cb.model.Value(selIndex)) } else { cb.currentValue = valueProp.Get() } cb.currentIndexChangedPublisher.Publish() cb.prevCurIndex = selIndex return 0 } cb.selChangeIndex = -1 } case win.WM_MOUSEWHEEL: if !cb.Enabled() { return 0 } case win.WM_WINDOWPOSCHANGED: wp := (*win.WINDOWPOS)(unsafe.Pointer(lParam)) if wp.Flags&win.SWP_NOSIZE != 0 { break } if cb.Editable() { result := cb.WidgetBase.WndProc(hwnd, msg, wParam, lParam) cb.SetTextSelection(0, 0) return result } } return cb.WidgetBase.WndProc(hwnd, msg, wParam, lParam) } func (*ComboBox) NeedsWmSize() bool { return true } func (cb *ComboBox) CreateLayoutItem(ctx *LayoutContext) LayoutItem { var layoutFlags LayoutFlags if cb.Editable() { layoutFlags = GrowableHorz | GreedyHorz } else { layoutFlags = GrowableHorz } defaultSize := cb.dialogBaseUnitsToPixels(Size{30, 12}) if cb.model != nil && cb.maxItemTextWidth <= 0 { cb.maxItemTextWidth = cb.calculateMaxItemTextWidth() } // FIXME: Use GetThemePartSize instead of guessing w := maxi(defaultSize.Width, cb.maxItemTextWidth+int(win.GetSystemMetricsForDpi(win.SM_CXVSCROLL, uint32(ctx.dpi)))+8) h := defaultSize.Height + 1 return &comboBoxLayoutItem{ layoutFlags: layoutFlags, idealSize: Size{w, h}, } } type comboBoxLayoutItem struct { LayoutItemBase layoutFlags LayoutFlags idealSize Size // in native pixels } func (li *comboBoxLayoutItem) LayoutFlags() LayoutFlags { return li.layoutFlags } func (li *comboBoxLayoutItem) IdealSize() Size { return li.idealSize } func (li *comboBoxLayoutItem) MinSize() Size { return li.idealSize } ================================================ FILE: commondialogs.go ================================================ // 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 ( "fmt" "path/filepath" "syscall" "unsafe" ) import ( "github.com/lxn/win" ) type FileDialog struct { Title string FilePath string FilePaths []string InitialDirPath string Filter string FilterIndex int Flags uint32 ShowReadOnlyCB bool } func (dlg *FileDialog) show(owner Form, fun func(ofn *win.OPENFILENAME) bool, flags uint32) (accepted bool, err error) { ofn := new(win.OPENFILENAME) ofn.LStructSize = uint32(unsafe.Sizeof(*ofn)) if owner != nil { ofn.HwndOwner = owner.Handle() } filter := make([]uint16, len(dlg.Filter)+2) copy(filter, syscall.StringToUTF16(dlg.Filter)) // Replace '|' with the expected '\0'. for i, c := range filter { if byte(c) == '|' { filter[i] = uint16(0) } } ofn.LpstrFilter = &filter[0] ofn.NFilterIndex = uint32(dlg.FilterIndex) ofn.LpstrInitialDir = syscall.StringToUTF16Ptr(dlg.InitialDirPath) ofn.LpstrTitle = syscall.StringToUTF16Ptr(dlg.Title) ofn.Flags = win.OFN_FILEMUSTEXIST | flags | dlg.Flags if !dlg.ShowReadOnlyCB { ofn.Flags |= win.OFN_HIDEREADONLY } var fileBuf []uint16 if flags&win.OFN_ALLOWMULTISELECT > 0 { fileBuf = make([]uint16, 65536) } else { fileBuf = make([]uint16, 1024) copy(fileBuf, syscall.StringToUTF16(dlg.FilePath)) } ofn.LpstrFile = &fileBuf[0] ofn.NMaxFile = uint32(len(fileBuf)) if !fun(ofn) { errno := win.CommDlgExtendedError() if errno != 0 { err = newError(fmt.Sprintf("Error %d", errno)) } return } dlg.FilterIndex = int(ofn.NFilterIndex) if flags&win.OFN_ALLOWMULTISELECT > 0 { split := func() [][]uint16 { var parts [][]uint16 from := 0 for i, c := range fileBuf { if c == 0 { if i == from { return parts } parts = append(parts, fileBuf[from:i]) from = i + 1 } } return parts } parts := split() if len(parts) == 1 { dlg.FilePaths = []string{syscall.UTF16ToString(parts[0])} } else { dirPath := syscall.UTF16ToString(parts[0]) dlg.FilePaths = make([]string, len(parts)-1) for i, fp := range parts[1:] { dlg.FilePaths[i] = filepath.Join(dirPath, syscall.UTF16ToString(fp)) } } } else { dlg.FilePath = syscall.UTF16ToString(fileBuf) } accepted = true return } func (dlg *FileDialog) ShowOpen(owner Form) (accepted bool, err error) { return dlg.show(owner, win.GetOpenFileName, win.OFN_NOCHANGEDIR) } func (dlg *FileDialog) ShowOpenMultiple(owner Form) (accepted bool, err error) { return dlg.show(owner, win.GetOpenFileName, win.OFN_ALLOWMULTISELECT|win.OFN_EXPLORER|win.OFN_NOCHANGEDIR) } func (dlg *FileDialog) ShowSave(owner Form) (accepted bool, err error) { return dlg.show(owner, win.GetSaveFileName, win.OFN_NOCHANGEDIR) } func pathFromPIDL(pidl uintptr) (string, error) { var path [win.MAX_PATH]uint16 if !win.SHGetPathFromIDList(pidl, &path[0]) { return "", newError("SHGetPathFromIDList failed") } return syscall.UTF16ToString(path[:]), nil } // We use this callback to disable the OK button in case of "invalid" selections. func browseFolderCallback(hwnd win.HWND, msg uint32, lp, wp uintptr) uintptr { const BFFM_SELCHANGED = 2 if msg == BFFM_SELCHANGED { _, err := pathFromPIDL(lp) var enabled uintptr if err == nil { enabled = 1 } const BFFM_ENABLEOK = win.WM_USER + 101 win.SendMessage(hwnd, BFFM_ENABLEOK, 0, enabled) } return 0 } var browseFolderCallbackPtr uintptr func init() { AppendToWalkInit(func() { browseFolderCallbackPtr = syscall.NewCallback(browseFolderCallback) }) } func (dlg *FileDialog) ShowBrowseFolder(owner Form) (accepted bool, err error) { // Calling OleInitialize (or similar) is required for BIF_NEWDIALOGSTYLE. if hr := win.OleInitialize(); hr != win.S_OK && hr != win.S_FALSE { return false, newError(fmt.Sprint("OleInitialize Error: ", hr)) } defer win.OleUninitialize() var ownerHwnd win.HWND if owner != nil { ownerHwnd = owner.Handle() } // We need to put the initial path into a buffer of at least MAX_LENGTH // length, or we may get random crashes. var buf [win.MAX_PATH]uint16 copy(buf[:], syscall.StringToUTF16(dlg.InitialDirPath)) const BIF_NEWDIALOGSTYLE = 0x00000040 bi := win.BROWSEINFO{ HwndOwner: ownerHwnd, LpszTitle: syscall.StringToUTF16Ptr(dlg.Title), UlFlags: BIF_NEWDIALOGSTYLE, Lpfn: browseFolderCallbackPtr, } win.SHParseDisplayName(&buf[0], 0, &bi.PidlRoot, 0, nil) pidl := win.SHBrowseForFolder(&bi) if pidl == 0 { return false, nil } defer win.CoTaskMemFree(pidl) dlg.FilePath, err = pathFromPIDL(pidl) accepted = dlg.FilePath != "" return } ================================================ FILE: composite.go ================================================ // 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 ( "github.com/lxn/win" ) const compositeWindowClass = `\o/ Walk_Composite_Class \o/` func init() { AppendToWalkInit(func() { MustRegisterWindowClass(compositeWindowClass) }) } type Composite struct { ContainerBase } func NewCompositeWithStyle(parent Window, style uint32) (*Composite, error) { c := new(Composite) c.children = newWidgetList(c) c.SetPersistent(true) if err := InitWidget( c, parent, compositeWindowClass, win.WS_CHILD|win.WS_VISIBLE|style, win.WS_EX_CONTROLPARENT); err != nil { return nil, err } c.SetBackground(NullBrush()) return c, nil } func NewComposite(parent Container) (*Composite, error) { return NewCompositeWithStyle(parent, 0) } ================================================ FILE: condition.go ================================================ // Copyright 2013 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 type Condition interface { Expression Satisfied() bool } type MutableCondition struct { satisfied bool changedPublisher EventPublisher } func NewMutableCondition() *MutableCondition { return new(MutableCondition) } func (mc *MutableCondition) Value() interface{} { return mc.satisfied } func (mc *MutableCondition) Satisfied() bool { return mc.satisfied } func (mc *MutableCondition) SetSatisfied(satisfied bool) error { if satisfied == mc.satisfied { return nil } mc.satisfied = satisfied mc.changedPublisher.Publish() return nil } func (mc *MutableCondition) Changed() *Event { return mc.changedPublisher.Event() } type DelegateCondition struct { satisfied func() bool changed *Event } func NewDelegateCondition(satisfied func() bool, changed *Event) *DelegateCondition { return &DelegateCondition{satisfied, changed} } func (dc *DelegateCondition) Value() interface{} { return dc.satisfied() } func (dc *DelegateCondition) Satisfied() bool { return dc.satisfied() } func (dc *DelegateCondition) Changed() *Event { return dc.changed } type compositeCondition struct { items []Condition itemsChangedHandles []int changedPublisher EventPublisher } func (cc *compositeCondition) init(items []Condition) { cc.items = append(cc.items, items...) for _, item := range items { handle := item.Changed().Attach(func() { cc.changedPublisher.Publish() }) cc.itemsChangedHandles = append(cc.itemsChangedHandles, handle) } } func (cc *compositeCondition) satisfied(all bool) bool { for _, item := range cc.items { if all != item.Satisfied() { return !all } } return all } func (cc *compositeCondition) Changed() *Event { return cc.changedPublisher.Event() } func (cc *compositeCondition) Dispose() { for i, item := range cc.items { item.Changed().Detach(cc.itemsChangedHandles[i]) } } type allCondition struct { compositeCondition } func NewAllCondition(items ...Condition) Condition { ac := new(allCondition) ac.init(items) return ac } func (ac *allCondition) Value() interface{} { return ac.Satisfied() } func (ac *allCondition) Satisfied() bool { return ac.satisfied(true) } type anyCondition struct { compositeCondition } func NewAnyCondition(items ...Condition) Condition { ac := new(anyCondition) ac.init(items) return ac } func (ac *anyCondition) Value() interface{} { return ac.Satisfied() } func (ac *anyCondition) Satisfied() bool { return ac.satisfied(false) } type negatedCondition struct { other Condition } func NewNegatedCondition(other Condition) Condition { return &negatedCondition{other} } func (nc *negatedCondition) Value() interface{} { return nc.Satisfied() } func (nc *negatedCondition) Satisfied() bool { return !nc.other.Satisfied() } func (nc *negatedCondition) Changed() *Event { return nc.other.Changed() } ================================================ FILE: container.go ================================================ // 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" "time" "unsafe" "github.com/lxn/win" ) type Container interface { Window AsContainerBase() *ContainerBase Children() *WidgetList Layout() Layout SetLayout(value Layout) error DataBinder() *DataBinder SetDataBinder(dbm *DataBinder) } type ContainerBase struct { WidgetBase layout Layout children *WidgetList dataBinder *DataBinder nextChildID int32 persistent bool } func (cb *ContainerBase) AsWidgetBase() *WidgetBase { return &cb.WidgetBase } func (cb *ContainerBase) AsContainerBase() *ContainerBase { return cb } func (cb *ContainerBase) NextChildID() int32 { cb.nextChildID++ return cb.nextChildID } func (cb *ContainerBase) applyEnabled(enabled bool) { cb.WidgetBase.applyEnabled(enabled) applyEnabledToDescendants(cb.window.(Widget), enabled) if InteractionEffect != nil { for _, wb := range cb.children.items { if wb.GraphicsEffects().Contains(InteractionEffect) { wb.invalidateBorderInParent() } } } } func (cb *ContainerBase) applyFont(font *Font) { cb.WidgetBase.applyFont(font) applyFontToDescendants(cb.window.(Widget), font) } func (cb *ContainerBase) ApplySysColors() { cb.WidgetBase.ApplySysColors() applySysColorsToDescendants(cb.window.(Widget)) } func (cb *ContainerBase) ApplyDPI(dpi int) { cb.WidgetBase.ApplyDPI(dpi) applyDPIToDescendants(cb.window.(Widget), dpi) if cb.layout != nil { if ums, ok := cb.layout.(interface { updateMargins() updateSpacing() }); ok { ums.updateMargins() ums.updateSpacing() } cb.RequestLayout() } } func (cb *ContainerBase) Children() *WidgetList { return cb.children } func (cb *ContainerBase) Layout() Layout { return cb.layout } func (cb *ContainerBase) SetLayout(value Layout) error { if cb.layout != value { if cb.layout != nil { cb.layout.SetContainer(nil) } cb.layout = value if value != nil && value.Container() != Container(cb) { value.SetContainer(cb) } } return nil } func (cb *ContainerBase) CreateLayoutItem(ctx *LayoutContext) LayoutItem { return cb.layout.CreateLayoutItem(ctx) } func (cb *ContainerBase) DataBinder() *DataBinder { return cb.dataBinder } func (cb *ContainerBase) SetDataBinder(db *DataBinder) { if db == cb.dataBinder { return } if cb.dataBinder != nil { cb.dataBinder.SetBoundWidgets(nil) } cb.dataBinder = db if db != nil { var boundWidgets []Widget walkDescendants(cb.window, func(w Window) bool { if w.Handle() == cb.hWnd { return true } if c, ok := w.(Container); ok && c.DataBinder() != nil { return false } for _, prop := range w.AsWindowBase().name2Property { if _, ok := prop.Source().(string); ok { boundWidgets = append(boundWidgets, w.(Widget)) break } } return true }) db.SetBoundWidgets(boundWidgets) } } func (cb *ContainerBase) forEachPersistableChild(f func(p Persistable) error) error { if cb.children == nil { return nil } for _, wb := range cb.children.items { if persistable, ok := wb.window.(Persistable); ok && persistable.Persistent() { if err := f(persistable); err != nil { return err } } } return nil } func (cb *ContainerBase) Persistent() bool { return cb.persistent } func (cb *ContainerBase) SetPersistent(value bool) { cb.persistent = value } func (cb *ContainerBase) SaveState() error { return cb.forEachPersistableChild(func(p Persistable) error { return p.SaveState() }) } func (cb *ContainerBase) RestoreState() error { return cb.forEachPersistableChild(func(p Persistable) error { return p.RestoreState() }) } func (cb *ContainerBase) doPaint() error { var ps win.PAINTSTRUCT hdc := win.BeginPaint(cb.hWnd, &ps) defer win.EndPaint(cb.hWnd, &ps) canvas, err := newCanvasFromHDC(hdc) if err != nil { return err } defer canvas.Dispose() for _, wb := range cb.children.items { widget := wb.window.(Widget) for _, effect := range widget.GraphicsEffects().items { switch effect { case InteractionEffect: type ReadOnlyer interface { ReadOnly() bool } if ro, ok := widget.(ReadOnlyer); ok { if ro.ReadOnly() { continue } } if hwnd := widget.Handle(); !win.IsWindowEnabled(hwnd) || !win.IsWindowVisible(hwnd) { continue } case FocusEffect: continue } b := widget.BoundsPixels().toRECT() win.ExcludeClipRect(hdc, b.Left, b.Top, b.Right, b.Bottom) if err := effect.Draw(widget, canvas); err != nil { return err } } } if FocusEffect != nil { hwndFocused := win.GetFocus() var widget Widget if wnd := windowFromHandle(hwndFocused); wnd != nil { widget, _ = wnd.(Widget) } for hwndFocused != 0 && (widget == nil || widget.Parent() == nil) { hwndFocused = win.GetParent(hwndFocused) if wnd := windowFromHandle(hwndFocused); wnd != nil { widget, _ = wnd.(Widget) } } if widget != nil && widget.Parent() != nil && widget.Parent().Handle() == cb.hWnd { for _, effect := range widget.GraphicsEffects().items { if effect == FocusEffect { b := widget.BoundsPixels().toRECT() win.ExcludeClipRect(hdc, b.Left, b.Top, b.Right, b.Bottom) if err := FocusEffect.Draw(widget, canvas); err != nil { return err } } } } } return nil } func (cb *ContainerBase) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_CTLCOLOREDIT, win.WM_CTLCOLORSTATIC: if hBrush := cb.handleWMCTLCOLOR(wParam, lParam); hBrush != 0 { return hBrush } case win.WM_PAINT: if FocusEffect == nil && InteractionEffect == nil && ValidationErrorEffect == nil { break } // If it fails, what can we do about it? Panic? That's extreme. So just ignore it. _ = cb.doPaint() return 0 case win.WM_COMMAND: if lParam == 0 { switch win.HIWORD(uint32(wParam)) { case 0: cmdId := win.LOWORD(uint32(wParam)) switch cmdId { case win.IDOK, win.IDCANCEL: form := ancestor(cb) if form == nil { break } dlg, ok := form.(dialogish) if !ok { break } var button *PushButton if cmdId == win.IDOK { button = dlg.DefaultButton() } else { button = dlg.CancelButton() } if button != nil && button.Visible() && button.Enabled() { button.raiseClicked() } break } // Menu actionId := uint16(win.LOWORD(uint32(wParam))) if action, ok := actionsById[actionId]; ok { action.raiseTriggered() return 0 } case 1: // Accelerator } } else { // The window that sent the notification shall handle it itself. hwndSrc := win.GetDlgItem(cb.hWnd, int32(win.LOWORD(uint32(wParam)))) var toolBarOnly bool if hwndSrc == 0 { toolBarOnly = true hwndSrc = win.HWND(lParam) } if window := windowFromHandle(hwndSrc); window != nil { if _, ok := window.(*ToolBar); toolBarOnly && !ok { break } window.WndProc(hwnd, msg, wParam, lParam) return 0 } } case win.WM_MEASUREITEM: mis := (*win.MEASUREITEMSTRUCT)(unsafe.Pointer(lParam)) if window := windowFromHandle(win.GetDlgItem(hwnd, int32(mis.CtlID))); window != nil { // The window that sent the notification shall handle it itself. return window.WndProc(hwnd, msg, wParam, lParam) } case win.WM_DRAWITEM: dis := (*win.DRAWITEMSTRUCT)(unsafe.Pointer(lParam)) if window := windowFromHandle(dis.HwndItem); window != nil { // The window that sent the notification shall handle it itself. return window.WndProc(hwnd, msg, wParam, lParam) } case win.WM_NOTIFY: nmh := (*win.NMHDR)(unsafe.Pointer(lParam)) if window := windowFromHandle(nmh.HwndFrom); window != nil { // The window that sent the notification shall handle it itself. return window.WndProc(hwnd, msg, wParam, lParam) } case win.WM_HSCROLL, win.WM_VSCROLL: if window := windowFromHandle(win.HWND(lParam)); window != nil { // The window that sent the notification shall handle it itself. return window.WndProc(hwnd, msg, wParam, lParam) } case win.WM_WINDOWPOSCHANGED: wp := (*win.WINDOWPOS)(unsafe.Pointer(lParam)) if wp.Flags&win.SWP_NOSIZE != 0 || cb.Layout() == nil { break } if cb.background == nullBrushSingleton { cb.Invalidate() } } return cb.WidgetBase.WndProc(hwnd, msg, wParam, lParam) } func (cb *ContainerBase) onInsertingWidget(index int, widget Widget) (err error) { return nil } func (cb *ContainerBase) onInsertedWidget(index int, widget Widget) (err error) { if parent := widget.Parent(); parent == nil || parent.Handle() != cb.hWnd { if err = widget.SetParent(cb.window.(Container)); err != nil { return } } cb.RequestLayout() widget.(applyFonter).applyFont(cb.Font()) return } func (cb *ContainerBase) onRemovingWidget(index int, widget Widget) (err error) { if widget.Parent() == nil { return } if widget.Parent().Handle() == cb.hWnd { err = widget.SetParent(nil) } return } func (cb *ContainerBase) onRemovedWidget(index int, widget Widget) (err error) { cb.RequestLayout() return } func (cb *ContainerBase) onClearingWidgets() (err error) { for i := cb.children.Len() - 1; i >= 0; i-- { widget := cb.children.At(i) if parent := widget.Parent(); parent != nil && parent.Handle() == cb.hWnd { if err = widget.SetParent(nil); err != nil { return } } } return } func (cb *ContainerBase) onClearedWidgets() (err error) { cb.RequestLayout() return } func (cb *ContainerBase) focusFirstCandidateDescendant() { window := firstFocusableDescendant(cb) if window == nil { return } if err := window.SetFocus(); err != nil { return } if textSel, ok := window.(textSelectable); ok { time.AfterFunc(time.Millisecond, func() { window.Synchronize(func() { if window.Focused() { textSel.SetTextSelection(0, -1) } }) }) } } func firstFocusableDescendantCallback(hwnd win.HWND, lParam uintptr) uintptr { if !win.IsWindowVisible(hwnd) || !win.IsWindowEnabled(hwnd) { return 1 } if win.GetWindowLong(hwnd, win.GWL_STYLE)&win.WS_TABSTOP > 0 { if rb, ok := windowFromHandle(hwnd).(radioButtonish); ok { if !rb.radioButton().Checked() { return 1 } } hwndPtr := (*win.HWND)(unsafe.Pointer(lParam)) *hwndPtr = hwnd return 0 } return 1 } var firstFocusableDescendantCallbackPtr uintptr func init() { AppendToWalkInit(func() { firstFocusableDescendantCallbackPtr = syscall.NewCallback(firstFocusableDescendantCallback) }) } func firstFocusableDescendant(container Container) Window { var hwnd win.HWND win.EnumChildWindows(container.Handle(), firstFocusableDescendantCallbackPtr, uintptr(unsafe.Pointer(&hwnd))) window := windowFromHandle(hwnd) for hwnd != 0 && window == nil { hwnd = win.GetParent(hwnd) window = windowFromHandle(hwnd) } return window } type textSelectable interface { SetTextSelection(start, end int) } func DescendantByName(container Container, name string) Widget { var widget Widget walkDescendants(container.AsContainerBase(), func(w Window) bool { if w.Name() == name { widget = w.(Widget) return false } return true }) if widget == nil { return nil } return widget } ================================================ FILE: cursor.go ================================================ // Copyright 2011 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 ( "image" ) import ( "github.com/lxn/win" ) type Cursor interface { Dispose() handle() win.HCURSOR } type stockCursor struct { hCursor win.HCURSOR } func (sc stockCursor) Dispose() { // nop } func (sc stockCursor) handle() win.HCURSOR { return sc.hCursor } func CursorArrow() Cursor { return stockCursor{win.LoadCursor(0, win.MAKEINTRESOURCE(win.IDC_ARROW))} } func CursorIBeam() Cursor { return stockCursor{win.LoadCursor(0, win.MAKEINTRESOURCE(win.IDC_IBEAM))} } func CursorWait() Cursor { return stockCursor{win.LoadCursor(0, win.MAKEINTRESOURCE(win.IDC_WAIT))} } func CursorCross() Cursor { return stockCursor{win.LoadCursor(0, win.MAKEINTRESOURCE(win.IDC_CROSS))} } func CursorUpArrow() Cursor { return stockCursor{win.LoadCursor(0, win.MAKEINTRESOURCE(win.IDC_UPARROW))} } func CursorSizeNWSE() Cursor { return stockCursor{win.LoadCursor(0, win.MAKEINTRESOURCE(win.IDC_SIZENWSE))} } func CursorSizeNESW() Cursor { return stockCursor{win.LoadCursor(0, win.MAKEINTRESOURCE(win.IDC_SIZENESW))} } func CursorSizeWE() Cursor { return stockCursor{win.LoadCursor(0, win.MAKEINTRESOURCE(win.IDC_SIZEWE))} } func CursorSizeNS() Cursor { return stockCursor{win.LoadCursor(0, win.MAKEINTRESOURCE(win.IDC_SIZENS))} } func CursorSizeAll() Cursor { return stockCursor{win.LoadCursor(0, win.MAKEINTRESOURCE(win.IDC_SIZEALL))} } func CursorNo() Cursor { return stockCursor{win.LoadCursor(0, win.MAKEINTRESOURCE(win.IDC_NO))} } func CursorHand() Cursor { return stockCursor{win.LoadCursor(0, win.MAKEINTRESOURCE(win.IDC_HAND))} } func CursorAppStarting() Cursor { return stockCursor{win.LoadCursor(0, win.MAKEINTRESOURCE(win.IDC_APPSTARTING))} } func CursorHelp() Cursor { return stockCursor{win.LoadCursor(0, win.MAKEINTRESOURCE(win.IDC_HELP))} } func CursorIcon() Cursor { return stockCursor{win.LoadCursor(0, win.MAKEINTRESOURCE(win.IDC_ICON))} } func CursorSize() Cursor { return stockCursor{win.LoadCursor(0, win.MAKEINTRESOURCE(win.IDC_SIZE))} } type customCursor struct { hCursor win.HCURSOR } func NewCursorFromImage(im image.Image, hotspot image.Point) (Cursor, error) { i, err := createAlphaCursorOrIconFromImage(im, hotspot, false) if err != nil { return nil, err } return customCursor{win.HCURSOR(i)}, nil } func (cc customCursor) Dispose() { win.DestroyIcon(win.HICON(cc.hCursor)) } func (cc customCursor) handle() win.HCURSOR { return cc.hCursor } ================================================ FILE: customwidget.go ================================================ // 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 ( "unsafe" "github.com/lxn/win" ) const customWidgetWindowClass = `\o/ Walk_CustomWidget_Class \o/` func init() { AppendToWalkInit(func() { MustRegisterWindowClass(customWidgetWindowClass) }) } // PaintFunc paints custom widget content. updateBounds is specified in 1/96" or native pixels. type PaintFunc func(canvas *Canvas, updateBounds Rectangle) error type PaintMode int const ( PaintNormal PaintMode = iota // erase background before PaintFunc PaintNoErase // PaintFunc clears background, single buffered PaintBuffered // PaintFunc clears background, double buffered ) type CustomWidget struct { WidgetBase paint PaintFunc // in 1/96" units paintPixels PaintFunc // in native pixels invalidatesOnResize bool paintMode PaintMode } // NewCustomWidget creates and initializes a new custom draw widget. // // Deprecated: PaintFunc is taking updateBounds parameter at 96dpi for backward compatibility with // clients. On high-DPI displays this is too sparse and may incur a thin unpainted edge around // control due to rounding errors. Newer applications should use NewCustomWidgetPixels. func NewCustomWidget(parent Container, style uint, paint PaintFunc) (*CustomWidget, error) { cw := &CustomWidget{paint: paint} err := cw.init(parent, style) if err != nil { return nil, err } return cw, nil } // NewCustomWidgetPixels creates and initializes a new custom draw widget. func NewCustomWidgetPixels(parent Container, style uint, paintPixels PaintFunc) (*CustomWidget, error) { cw := &CustomWidget{paintPixels: paintPixels} err := cw.init(parent, style) if err != nil { return nil, err } return cw, nil } func (cw *CustomWidget) init(parent Container, style uint) error { if err := InitWidget( cw, parent, customWidgetWindowClass, win.WS_VISIBLE|uint32(style), 0); err != nil { return err } return nil } // deprecated, use PaintMode func (cw *CustomWidget) ClearsBackground() bool { return cw.paintMode != PaintNormal } // deprecated, use SetPaintMode func (cw *CustomWidget) SetClearsBackground(value bool) { if value != cw.ClearsBackground() { if value { cw.paintMode = PaintNormal } else { cw.paintMode = PaintNoErase } } } func (cw *CustomWidget) InvalidatesOnResize() bool { return cw.invalidatesOnResize } func (cw *CustomWidget) SetInvalidatesOnResize(value bool) { cw.invalidatesOnResize = value } func (cw *CustomWidget) PaintMode() PaintMode { return cw.paintMode } func (cw *CustomWidget) SetPaintMode(value PaintMode) { cw.paintMode = value } func (cw *CustomWidget) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_PAINT: if cw.paint == nil && cw.paintPixels == nil { newError("paint(Pixels) func is nil") break } var ps win.PAINTSTRUCT var hdc win.HDC if wParam == 0 { hdc = win.BeginPaint(cw.hWnd, &ps) } else { hdc = win.HDC(wParam) } if hdc == 0 { newError("BeginPaint failed") break } defer func() { if wParam == 0 { win.EndPaint(cw.hWnd, &ps) } }() canvas, err := newCanvasFromHDC(hdc) if err != nil { newError("newCanvasFromHDC failed") break } defer canvas.Dispose() bounds := rectangleFromRECT(ps.RcPaint) if cw.paintMode == PaintBuffered { err = cw.bufferedPaint(canvas, bounds) } else if cw.paintPixels != nil { err = cw.paintPixels(canvas, bounds) } else { err = cw.paint(canvas, RectangleTo96DPI(bounds, cw.DPI())) } if err != nil { newError("paint failed") break } return 0 case win.WM_ERASEBKGND: if cw.paintMode != PaintNormal { return 1 } case win.WM_PRINTCLIENT: win.SendMessage(hwnd, win.WM_PAINT, wParam, lParam) case win.WM_WINDOWPOSCHANGED: wp := (*win.WINDOWPOS)(unsafe.Pointer(lParam)) if wp.Flags&win.SWP_NOSIZE != 0 { break } if cw.invalidatesOnResize { cw.Invalidate() } } return cw.WidgetBase.WndProc(hwnd, msg, wParam, lParam) } // bufferedPaint draws widget on a memory buffer. updateBounds are in native pixels. func (cw *CustomWidget) bufferedPaint(canvas *Canvas, updateBounds Rectangle) error { hdc := win.CreateCompatibleDC(canvas.hdc) if hdc == 0 { return newError("CreateCompatibleDC failed") } defer win.DeleteDC(hdc) buffered := Canvas{hdc: hdc, doNotDispose: true} if _, err := buffered.init(); err != nil { return err } w, h := int32(updateBounds.Width), int32(updateBounds.Height) if w < 1 { w = 1 } if h < 1 { h = 1 } hbmp := win.CreateCompatibleBitmap(canvas.hdc, w, h) if hbmp == 0 { return lastError("CreateCompatibleBitmap failed") } defer win.DeleteObject(win.HGDIOBJ(hbmp)) oldbmp := win.SelectObject(buffered.hdc, win.HGDIOBJ(hbmp)) if oldbmp == 0 { return newError("SelectObject failed") } defer win.SelectObject(buffered.hdc, oldbmp) win.SetViewportOrgEx(buffered.hdc, -int32(updateBounds.X), -int32(updateBounds.Y), nil) win.SetBrushOrgEx(buffered.hdc, -int32(updateBounds.X), -int32(updateBounds.Y), nil) var err error if cw.paintPixels != nil { err = cw.paintPixels(&buffered, updateBounds) } else { err = cw.paint(&buffered, RectangleTo96DPI(updateBounds, cw.DPI())) } if !win.BitBlt(canvas.hdc, int32(updateBounds.X), int32(updateBounds.Y), w, h, buffered.hdc, int32(updateBounds.X), int32(updateBounds.Y), win.SRCCOPY) { return lastError("buffered BitBlt failed") } return err } func (*CustomWidget) CreateLayoutItem(ctx *LayoutContext) LayoutItem { return NewGreedyLayoutItem() } ================================================ FILE: databinding.go ================================================ // Copyright 2012 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 ( "errors" "fmt" "reflect" "strings" "time" ) var ( errValidationFailed = errors.New("validation failed") ) type ErrorPresenter interface { PresentError(err error, widget Widget) } type DataBinder struct { dataSource interface{} boundWidgets []Widget properties []Property property2Widget map[Property]Widget property2ChangedHandle map[Property]int rootExpression Expression path2Expression map[string]Expression errorPresenter ErrorPresenter dataSourceChangedPublisher EventPublisher canSubmitChangedPublisher EventPublisher submittedPublisher EventPublisher resetPublisher EventPublisher autoSubmitDelay time.Duration autoSubmitTimer *time.Timer autoSubmit bool autoSubmitSuspended bool canSubmit bool inReset bool dirty bool } func NewDataBinder() *DataBinder { db := new(DataBinder) db.rootExpression = &dataBinderRootExpression{db} return db } func (db *DataBinder) AutoSubmit() bool { return db.autoSubmit } func (db *DataBinder) SetAutoSubmit(autoSubmit bool) { db.autoSubmit = autoSubmit if autoSubmit { db.canSubmit = true } } func (db *DataBinder) AutoSubmitDelay() time.Duration { return db.autoSubmitDelay } func (db *DataBinder) SetAutoSubmitDelay(delay time.Duration) { db.autoSubmitDelay = delay } func (db *DataBinder) AutoSubmitSuspended() bool { return db.autoSubmitSuspended } func (db *DataBinder) SetAutoSubmitSuspended(suspended bool) { if suspended == db.autoSubmitSuspended { return } db.autoSubmitSuspended = suspended if suspended { if db.autoSubmitTimer != nil { db.autoSubmitTimer.Stop() } } else { db.Submit() } } func (db *DataBinder) Submitted() *Event { return db.submittedPublisher.Event() } func (db *DataBinder) DataSource() interface{} { return db.dataSource } func (db *DataBinder) SetDataSource(dataSource interface{}) error { if kind := reflect.ValueOf(dataSource).Kind(); kind != reflect.Func && kind != reflect.Map && kind != reflect.Slice && kind == reflect.ValueOf(db.dataSource).Kind() && dataSource == db.dataSource { return nil } if dataSource != nil { if t := reflect.TypeOf(dataSource); t.Kind() != reflect.Map && (t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct) { return newError("dataSource must be pointer to struct or map[string]interface{}") } } db.dataSource = dataSource db.dataSourceChangedPublisher.Publish() return nil } type dataBinderRootExpression struct { db *DataBinder } func (dbre *dataBinderRootExpression) Value() interface{} { return dbre.db.dataSource } func (dbre *dataBinderRootExpression) Changed() *Event { return dbre.db.resetPublisher.Event() } func (db *DataBinder) DataSourceChanged() *Event { return db.dataSourceChangedPublisher.Event() } func (db *DataBinder) BoundWidgets() []Widget { return db.boundWidgets } func (db *DataBinder) SetBoundWidgets(boundWidgets []Widget) { for prop, handle := range db.property2ChangedHandle { prop.Changed().Detach(handle) } db.boundWidgets = boundWidgets db.property2Widget = make(map[Property]Widget) db.property2ChangedHandle = make(map[Property]int) for _, widget := range boundWidgets { widget := widget for _, prop := range widget.AsWindowBase().name2Property { prop := prop if _, ok := prop.Source().(string); !ok { continue } db.properties = append(db.properties, prop) db.property2Widget[prop] = widget db.property2ChangedHandle[prop] = prop.Changed().Attach(func() { db.dirty = true if db.autoSubmit && !db.autoSubmitSuspended { if db.autoSubmitDelay > 0 { if db.autoSubmitTimer == nil { db.autoSubmitTimer = time.AfterFunc(db.autoSubmitDelay, func() { widget.Synchronize(func() { db.Submit() }) }) } else { db.autoSubmitTimer.Reset(db.autoSubmitDelay) } } else { v := reflect.ValueOf(db.dataSource) field := db.fieldBoundToProperty(v, prop) if field == nil { return } if err := db.submitProperty(prop, field); err != nil { return } db.submittedPublisher.Publish() } } else { if !db.inReset { db.validateProperties() } } }) } } } func (db *DataBinder) Expression(path string) Expression { if db.path2Expression == nil { db.path2Expression = make(map[string]Expression) } if prop, ok := db.path2Expression[path]; ok { return prop } expr := NewReflectExpression(db.rootExpression, path) db.path2Expression[path] = expr return expr } func (db *DataBinder) validateProperties() { var hasError bool for _, prop := range db.properties { validator := prop.Validator() if validator == nil { continue } err := validator.Validate(prop.Get()) if err != nil { hasError = true } if db.errorPresenter != nil { widget := db.property2Widget[prop] db.errorPresenter.PresentError(err, widget) } } if hasError == db.canSubmit { db.canSubmit = !hasError db.canSubmitChangedPublisher.Publish() } } func (db *DataBinder) ErrorPresenter() ErrorPresenter { return db.errorPresenter } func (db *DataBinder) SetErrorPresenter(ep ErrorPresenter) { db.errorPresenter = ep } func (db *DataBinder) CanSubmit() bool { return db.canSubmit } func (db *DataBinder) CanSubmitChanged() *Event { return db.canSubmitChangedPublisher.Event() } func (db *DataBinder) Reset() error { db.inReset = true defer func() { db.inReset = false }() if err := db.forEach(func(prop Property, field DataField) error { if f64, ok := prop.Get().(float64); ok { switch v := field.Get().(type) { case float32: f64 = float64(v) case float64: f64 = v case int: f64 = float64(v) case int8: f64 = float64(v) case int16: f64 = float64(v) case int32: f64 = float64(v) case int64: f64 = float64(v) case uint: f64 = float64(v) case uint8: f64 = float64(v) case uint16: f64 = float64(v) case uint32: f64 = float64(v) case uint64: f64 = float64(v) case uintptr: f64 = float64(v) default: return newError(fmt.Sprintf("Field '%s': Can't convert %T to float64.", prop.Source().(string), field.Get())) } if err := prop.Set(f64); err != nil { return err } } else { if err := prop.Set(field.Get()); err != nil { return err } } return nil }); err != nil { return err } db.validateProperties() db.dirty = false db.resetPublisher.Publish() return nil } func (db *DataBinder) ResetFinished() *Event { return db.resetPublisher.Event() } func (db *DataBinder) Submit() error { if !db.CanSubmit() { return errValidationFailed } if err := db.forEach(func(prop Property, field DataField) error { return db.submitProperty(prop, field) }); err != nil { return err } db.dirty = false db.submittedPublisher.Publish() return nil } func (db *DataBinder) Dirty() bool { return db.dirty } func (db *DataBinder) submitProperty(prop Property, field DataField) error { if !field.CanSet() { // FIXME: handle properly return nil } value := prop.Get() if value == nil { if _, ok := db.property2Widget[prop].(*RadioButton); ok { return nil } return field.Set(field.Zero()) } if err, ok := value.(error); ok { return err } return field.Set(value) } func (db *DataBinder) forEach(f func(prop Property, field DataField) error) error { dsv := reflect.ValueOf(db.dataSource) if dsv.Kind() == reflect.Ptr && dsv.IsNil() { return nil } for _, prop := range db.properties { // if widget := db.property2Widget[prop]; !widget.Visible() { // continue // } field := db.fieldBoundToProperty(dsv, prop) if field == nil { continue } if err := f(prop, field); err != nil { return err } } return nil } func (db *DataBinder) fieldBoundToProperty(v reflect.Value, prop Property) DataField { if db.dataSource == nil { return nilField{prop: prop} } source, ok := prop.Source().(string) if !ok || source == "" { return nil } f, err := dataFieldFromPath(v, source) if err != nil { panic(fmt.Sprintf("invalid source '%s'", source)) } return f } func validateBindingMemberSyntax(member string) error { // FIXME return nil } type DataField interface { CanSet() bool Get() interface{} Set(interface{}) error Zero() interface{} } func dataFieldFromPath(root reflect.Value, path string) (DataField, error) { parent, value, err := reflectValueFromPath(root, path) if err != nil { return nil, err } // convert to DataField if i, ok := value.Interface().(DataField); ok { return i, nil } return &reflectField{parent: parent, value: value, key: path[strings.LastIndexByte(path, '.')+1:]}, nil } func reflectValueFromPath(root reflect.Value, path string) (parent, value reflect.Value, err error) { fullPath := path value = root for path != "" { var name string name, path = nextPathPart(path) var p reflect.Value for value.Kind() == reflect.Interface || value.Kind() == reflect.Ptr { p = value value = value.Elem() } switch value.Kind() { case reflect.Map: parent = value value = value.MapIndex(reflect.ValueOf(name)) case reflect.Struct: parent = value var fun reflect.Value // Try as field first. if f := value.FieldByName(name); f.IsValid() { switch f.Kind() { case reflect.Func: fun = f case reflect.Interface: if fn := f.Elem(); fn.Kind() == reflect.Func { fun = fn } else { value = f } default: value = f } } else { // No field, so let's see if we got a method. if p.IsValid() { // Try pointer receiver first. fun = p.MethodByName(name) } if !fun.IsValid() { // No pointer, try directly. fun = value.MethodByName(name) } if !fun.IsValid() { return parent, value, fmt.Errorf("bad member: '%s', path: '%s'", path, fullPath) } } if fun.IsValid() { // We assume it takes no args and returns one mandatory value plus // maybe an error. rvs := fun.Call(nil) switch len(rvs) { case 1: value = rvs[0] case 2: rv2 := rvs[1].Interface() if err, ok := rv2.(error); ok { return parent, value, err } else if rv2 != nil { return parent, value, fmt.Errorf("Second method return value must implement error.") } value = rvs[0] default: return parent, value, fmt.Errorf("Method must return a value plus optionally an error: %s", name) } } } } return parent, value, nil } func nextPathPart(p string) (next, remaining string) { for i, r := range p { if r == '.' { return p[:i], p[i+1:] } } return p, "" } type nilField struct { prop Property } func (nilField) CanSet() bool { return false } func (f nilField) Get() interface{} { return f.Zero() } func (nilField) Set(interface{}) error { return nil } func (f nilField) Zero() interface{} { return reflect.Zero(reflect.TypeOf(f.prop.Get())).Interface() } type reflectField struct { parent reflect.Value value reflect.Value key string } func (f *reflectField) CanSet() bool { if f.parent.IsValid() && f.parent.Kind() == reflect.Map { return true } return f.value.CanSet() } func (f *reflectField) Get() interface{} { return f.value.Interface() } func (f *reflectField) Set(value interface{}) error { if f.parent.IsValid() && f.parent.Kind() == reflect.Map { f.parent.SetMapIndex(reflect.ValueOf(f.key), reflect.ValueOf(value)) return nil } if f64, ok := value.(float64); ok { switch f.value.Kind() { case reflect.Float32, reflect.Float64: f.value.SetFloat(f64) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: f.value.SetInt(int64(f64)) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: f.value.SetUint(uint64(f64)) default: return newError(fmt.Sprintf("Can't convert float64 to %s.", f.value.Type().Name())) } return nil } f.value.Set(reflect.ValueOf(value)) return nil } func (f *reflectField) Zero() interface{} { return reflect.Zero(f.value.Type()).Interface() } ================================================ FILE: dateedit.go ================================================ // Copyright 2011 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 ( "strings" "syscall" "time" "unsafe" "github.com/lxn/win" ) type DateEdit struct { WidgetBase dateChangedPublisher EventPublisher format string } func newDateEdit(parent Container, style uint32) (*DateEdit, error) { de := new(DateEdit) if err := InitWidget( de, parent, "SysDateTimePick32", win.WS_TABSTOP|win.WS_VISIBLE|win.DTS_SHORTDATEFORMAT|style, 0); err != nil { return nil, err } if style&win.DTS_SHOWNONE != 0 { de.setSystemTime(nil) } de.GraphicsEffects().Add(InteractionEffect) de.GraphicsEffects().Add(FocusEffect) de.MustRegisterProperty("Date", NewProperty( func() interface{} { return de.Date() }, func(v interface{}) error { return de.SetDate(assertTimeOr(v, time.Time{})) }, de.dateChangedPublisher.Event())) return de, nil } func NewDateEdit(parent Container) (*DateEdit, error) { return newDateEdit(parent, 0) } func NewDateEditWithNoneOption(parent Container) (*DateEdit, error) { return newDateEdit(parent, win.DTS_SHOWNONE) } func (de *DateEdit) systemTimeToTime(st *win.SYSTEMTIME) time.Time { if st == nil || !de.hasStyleBits(win.DTS_SHOWNONE) && st.WYear == 1601 && st.WMonth == 1 && st.WDay == 1 { return time.Time{} } var hour, minute, second int if de.timeOfDayDisplayed() { hour = int(st.WHour) minute = int(st.WMinute) second = int(st.WSecond) } return time.Date(int(st.WYear), time.Month(st.WMonth), int(st.WDay), hour, minute, second, 0, time.Local) } func (de *DateEdit) timeToSystemTime(t time.Time) *win.SYSTEMTIME { if t.Year() < 1601 { if de.hasStyleBits(win.DTS_SHOWNONE) { return nil } else { return &win.SYSTEMTIME{ WYear: uint16(1601), WMonth: uint16(1), WDay: uint16(1), } } } st := &win.SYSTEMTIME{ WYear: uint16(t.Year()), WMonth: uint16(t.Month()), WDay: uint16(t.Day()), } if de.timeOfDayDisplayed() { st.WHour = uint16(t.Hour()) st.WMinute = uint16(t.Minute()) st.WSecond = uint16(t.Second()) } return st } func (de *DateEdit) systemTime() (*win.SYSTEMTIME, error) { var st win.SYSTEMTIME switch de.SendMessage(win.DTM_GETSYSTEMTIME, 0, uintptr(unsafe.Pointer(&st))) { case win.GDT_VALID: return &st, nil case win.GDT_NONE: return nil, nil } return nil, newError("SendMessage(DTM_GETSYSTEMTIME)") } func (de *DateEdit) setSystemTime(st *win.SYSTEMTIME) error { var wParam uintptr if st != nil { wParam = win.GDT_VALID } else { // Ensure today's date is displayed. de.setSystemTime(de.timeToSystemTime(time.Now())) wParam = win.GDT_NONE } if 0 == de.SendMessage(win.DTM_SETSYSTEMTIME, wParam, uintptr(unsafe.Pointer(st))) { return newError("SendMessage(DTM_SETSYSTEMTIME)") } return nil } func (de *DateEdit) timeOfDayDisplayed() bool { return strings.ContainsAny(de.format, "Hhms") } func (de *DateEdit) Format() string { return de.format } func (de *DateEdit) SetFormat(format string) error { lp := uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(format))) if 0 == de.SendMessage(win.DTM_SETFORMAT, 0, lp) { return newError("DTM_SETFORMAT failed") } de.format = format return nil } func (de *DateEdit) Range() (min, max time.Time) { var st [2]win.SYSTEMTIME ret := de.SendMessage(win.DTM_GETRANGE, 0, uintptr(unsafe.Pointer(&st[0]))) if ret&win.GDTR_MIN > 0 { min = de.systemTimeToTime(&st[0]) } if ret&win.GDTR_MAX > 0 { max = de.systemTimeToTime(&st[1]) } return } func (de *DateEdit) SetRange(min, max time.Time) error { if !min.IsZero() && !max.IsZero() { if min.Year() > max.Year() || min.Year() == max.Year() && min.Month() > max.Month() || min.Year() == max.Year() && min.Month() == max.Month() && min.Day() > max.Day() { return newError("invalid range") } } var st [2]win.SYSTEMTIME var wParam uintptr if !min.IsZero() { wParam |= win.GDTR_MIN st[0] = *de.timeToSystemTime(min) } if !max.IsZero() { wParam |= win.GDTR_MAX st[1] = *de.timeToSystemTime(max) } if 0 == de.SendMessage(win.DTM_SETRANGE, wParam, uintptr(unsafe.Pointer(&st[0]))) { return newError("SendMessage(DTM_SETRANGE)") } return nil } func (de *DateEdit) Date() time.Time { st, err := de.systemTime() if err != nil || st == nil { return time.Time{} } return de.systemTimeToTime(st) } func (de *DateEdit) SetDate(date time.Time) error { stNew := de.timeToSystemTime(date) stOld, err := de.systemTime() if err != nil { return err } else if stNew == stOld || stNew != nil && stOld != nil && *stNew == *stOld { return nil } if err := de.setSystemTime(stNew); err != nil { return err } de.dateChangedPublisher.Publish() return nil } func (de *DateEdit) DateChanged() *Event { return de.dateChangedPublisher.Event() } func (de *DateEdit) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_NOTIFY: switch uint32(((*win.NMHDR)(unsafe.Pointer(lParam))).Code) { case win.DTN_DATETIMECHANGE: de.dateChangedPublisher.Publish() } } return de.WidgetBase.WndProc(hwnd, msg, wParam, lParam) } func (*DateEdit) NeedsWmSize() bool { return true } func (de *DateEdit) CreateLayoutItem(ctx *LayoutContext) LayoutItem { return &dateEditLayoutItem{ idealSize: de.dialogBaseUnitsToPixels(Size{80, 12}), } } type dateEditLayoutItem struct { LayoutItemBase idealSize Size // in native pixels } func (*dateEditLayoutItem) LayoutFlags() LayoutFlags { return GrowableHorz } func (li *dateEditLayoutItem) IdealSize() Size { return li.idealSize } func (li *dateEditLayoutItem) MinSize() Size { return li.idealSize } ================================================ FILE: datelabel.go ================================================ // Copyright 2018 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 ( "time" ) type DateLabel struct { static date time.Time dateChangedPublisher EventPublisher format string formatChangedPublisher EventPublisher } func NewDateLabel(parent Container) (*DateLabel, error) { dl := new(DateLabel) if err := dl.init(dl, parent, 0); err != nil { return nil, err } dl.SetTextAlignment(AlignFar) if _, err := dl.updateText(); err != nil { return nil, err } dl.MustRegisterProperty("Date", NewProperty( func() interface{} { return dl.Date() }, func(v interface{}) error { return dl.SetDate(assertTimeOr(v, time.Time{})) }, dl.dateChangedPublisher.Event())) dl.MustRegisterProperty("Format", NewProperty( func() interface{} { return dl.Format() }, func(v interface{}) error { return dl.SetFormat(assertStringOr(v, "")) }, dl.formatChangedPublisher.Event())) return dl, nil } func (dl *DateLabel) asStatic() *static { return &dl.static } func (dl *DateLabel) TextAlignment() Alignment1D { return dl.textAlignment1D() } func (dl *DateLabel) SetTextAlignment(alignment Alignment1D) error { if alignment == AlignDefault { alignment = AlignNear } return dl.setTextAlignment1D(alignment) } func (dl *DateLabel) Date() time.Time { return dl.date } func (dl *DateLabel) SetDate(date time.Time) error { if date == dl.date { return nil } old := dl.date dl.date = date if _, err := dl.updateText(); err != nil { dl.date = old return err } dl.dateChangedPublisher.Publish() return nil } func (dl *DateLabel) Format() string { return dl.format } func (dl *DateLabel) SetFormat(format string) error { if format == dl.format { return nil } old := dl.format dl.format = format if _, err := dl.updateText(); err != nil { dl.format = old return err } dl.formatChangedPublisher.Publish() return nil } func (dl *DateLabel) updateText() (changed bool, err error) { return dl.setText(dl.date.Format(dl.format)) } ================================================ FILE: declarative/accessibility.go ================================================ // Copyright 2012 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 declarative import ( "github.com/lxn/walk" ) // AccState enum defines the state of the window/control type AccState int32 // Window/control states const ( AccStateNormal = AccState(walk.AccStateNormal) AccStateUnavailable = AccState(walk.AccStateUnavailable) AccStateSelected = AccState(walk.AccStateSelected) AccStateFocused = AccState(walk.AccStateFocused) AccStatePressed = AccState(walk.AccStatePressed) AccStateChecked = AccState(walk.AccStateChecked) AccStateMixed = AccState(walk.AccStateMixed) AccStateIndeterminate = AccState(walk.AccStateIndeterminate) AccStateReadonly = AccState(walk.AccStateReadonly) AccStateHotTracked = AccState(walk.AccStateHotTracked) AccStateDefault = AccState(walk.AccStateDefault) AccStateExpanded = AccState(walk.AccStateExpanded) AccStateCollapsed = AccState(walk.AccStateCollapsed) AccStateBusy = AccState(walk.AccStateBusy) AccStateFloating = AccState(walk.AccStateFloating) AccStateMarqueed = AccState(walk.AccStateMarqueed) AccStateAnimated = AccState(walk.AccStateAnimated) AccStateInvisible = AccState(walk.AccStateInvisible) AccStateOffscreen = AccState(walk.AccStateOffscreen) AccStateSizeable = AccState(walk.AccStateSizeable) AccStateMoveable = AccState(walk.AccStateMoveable) AccStateSelfVoicing = AccState(walk.AccStateSelfVoicing) AccStateFocusable = AccState(walk.AccStateFocusable) AccStateSelectable = AccState(walk.AccStateSelectable) AccStateLinked = AccState(walk.AccStateLinked) AccStateTraversed = AccState(walk.AccStateTraversed) AccStateMultiselectable = AccState(walk.AccStateMultiselectable) AccStateExtselectable = AccState(walk.AccStateExtselectable) AccStateAlertLow = AccState(walk.AccStateAlertLow) AccStateAlertMedium = AccState(walk.AccStateAlertMedium) AccStateAlertHigh = AccState(walk.AccStateAlertHigh) AccStateProtected = AccState(walk.AccStateProtected) AccStateHasPopup = AccState(walk.AccStateHasPopup) AccStateValid = AccState(walk.AccStateValid) ) // AccRole enum defines the role of the window/control in UI. type AccRole int32 // Window/control system roles const ( AccRoleTitlebar = AccRole(walk.AccRoleTitlebar) AccRoleMenubar = AccRole(walk.AccRoleMenubar) AccRoleScrollbar = AccRole(walk.AccRoleScrollbar) AccRoleGrip = AccRole(walk.AccRoleGrip) AccRoleSound = AccRole(walk.AccRoleSound) AccRoleCursor = AccRole(walk.AccRoleCursor) AccRoleCaret = AccRole(walk.AccRoleCaret) AccRoleAlert = AccRole(walk.AccRoleAlert) AccRoleWindow = AccRole(walk.AccRoleWindow) AccRoleClient = AccRole(walk.AccRoleClient) AccRoleMenuPopup = AccRole(walk.AccRoleMenuPopup) AccRoleMenuItem = AccRole(walk.AccRoleMenuItem) AccRoleTooltip = AccRole(walk.AccRoleTooltip) AccRoleApplication = AccRole(walk.AccRoleApplication) AccRoleDocument = AccRole(walk.AccRoleDocument) AccRolePane = AccRole(walk.AccRolePane) AccRoleChart = AccRole(walk.AccRoleChart) AccRoleDialog = AccRole(walk.AccRoleDialog) AccRoleBorder = AccRole(walk.AccRoleBorder) AccRoleGrouping = AccRole(walk.AccRoleGrouping) AccRoleSeparator = AccRole(walk.AccRoleSeparator) AccRoleToolbar = AccRole(walk.AccRoleToolbar) AccRoleStatusbar = AccRole(walk.AccRoleStatusbar) AccRoleTable = AccRole(walk.AccRoleTable) AccRoleColumnHeader = AccRole(walk.AccRoleColumnHeader) AccRoleRowHeader = AccRole(walk.AccRoleRowHeader) AccRoleColumn = AccRole(walk.AccRoleColumn) AccRoleRow = AccRole(walk.AccRoleRow) AccRoleCell = AccRole(walk.AccRoleCell) AccRoleLink = AccRole(walk.AccRoleLink) AccRoleHelpBalloon = AccRole(walk.AccRoleHelpBalloon) AccRoleCharacter = AccRole(walk.AccRoleCharacter) AccRoleList = AccRole(walk.AccRoleList) AccRoleListItem = AccRole(walk.AccRoleListItem) AccRoleOutline = AccRole(walk.AccRoleOutline) AccRoleOutlineItem = AccRole(walk.AccRoleOutlineItem) AccRolePagetab = AccRole(walk.AccRolePagetab) AccRolePropertyPage = AccRole(walk.AccRolePropertyPage) AccRoleIndicator = AccRole(walk.AccRoleIndicator) AccRoleGraphic = AccRole(walk.AccRoleGraphic) AccRoleStatictext = AccRole(walk.AccRoleStatictext) AccRoleText = AccRole(walk.AccRoleText) AccRolePushbutton = AccRole(walk.AccRolePushbutton) AccRoleCheckbutton = AccRole(walk.AccRoleCheckbutton) AccRoleRadiobutton = AccRole(walk.AccRoleRadiobutton) AccRoleCombobox = AccRole(walk.AccRoleCombobox) AccRoleDroplist = AccRole(walk.AccRoleDroplist) AccRoleProgressbar = AccRole(walk.AccRoleProgressbar) AccRoleDial = AccRole(walk.AccRoleDial) AccRoleHotkeyfield = AccRole(walk.AccRoleHotkeyfield) AccRoleSlider = AccRole(walk.AccRoleSlider) AccRoleSpinbutton = AccRole(walk.AccRoleSpinbutton) AccRoleDiagram = AccRole(walk.AccRoleDiagram) AccRoleAnimation = AccRole(walk.AccRoleAnimation) AccRoleEquation = AccRole(walk.AccRoleEquation) AccRoleButtonDropdown = AccRole(walk.AccRoleButtonDropdown) AccRoleButtonMenu = AccRole(walk.AccRoleButtonMenu) AccRoleButtonDropdownGrid = AccRole(walk.AccRoleButtonDropdownGrid) AccRoleWhitespace = AccRole(walk.AccRoleWhitespace) AccRolePageTabList = AccRole(walk.AccRolePageTabList) AccRoleClock = AccRole(walk.AccRoleClock) AccRoleSplitButton = AccRole(walk.AccRoleSplitButton) AccRoleIPAddress = AccRole(walk.AccRoleIPAddress) AccRoleOutlineButton = AccRole(walk.AccRoleOutlineButton) ) // Accessibility properties type Accessibility struct { Accelerator string DefaultAction string Description string Help string Name string Role AccRole RoleMap string State AccState StateMap string ValueMap string } ================================================ FILE: declarative/action.go ================================================ // Copyright 2012 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 declarative import ( "fmt" "github.com/lxn/walk" ) type Shortcut struct { Modifiers walk.Modifiers Key walk.Key } type Action struct { AssignTo **walk.Action Text string Image interface{} Checked Property Enabled Property Visible Property Shortcut Shortcut OnTriggered walk.EventHandler Checkable bool } func (a Action) createAction(builder *Builder, menu *walk.Menu) (*walk.Action, error) { action := walk.NewAction() if a.AssignTo != nil { *a.AssignTo = action } if err := action.SetText(a.Text); err != nil { return nil, err } if err := setActionImage(action, a.Image, builder.dpi); err != nil { return nil, err } if err := setActionBoolOrCondition(action.SetChecked, action.SetCheckedCondition, a.Checked, "Action.Checked", builder); err != nil { return nil, err } if err := setActionBoolOrCondition(action.SetEnabled, action.SetEnabledCondition, a.Enabled, "Action.Enabled", builder); err != nil { return nil, err } if err := setActionBoolOrCondition(action.SetVisible, action.SetVisibleCondition, a.Visible, "Action.Visible", builder); err != nil { return nil, err } if err := action.SetCheckable(a.Checkable || action.CheckedCondition() != nil); err != nil { return nil, err } s := a.Shortcut if err := action.SetShortcut(walk.Shortcut{s.Modifiers, s.Key}); err != nil { return nil, err } if a.OnTriggered != nil { action.Triggered().Attach(a.OnTriggered) } if menu != nil { if err := menu.Actions().Add(action); err != nil { return nil, err } } return action, nil } type ActionRef struct { Action **walk.Action } func (ar ActionRef) createAction(builder *Builder, menu *walk.Menu) (*walk.Action, error) { if menu != nil { if err := menu.Actions().Add(*ar.Action); err != nil { return nil, err } } return *ar.Action, nil } type Menu struct { AssignTo **walk.Menu AssignActionTo **walk.Action Text string Image interface{} Enabled Property Visible Property Items []MenuItem OnTriggered walk.EventHandler } func (m Menu) createAction(builder *Builder, menu *walk.Menu) (*walk.Action, error) { subMenu, err := walk.NewMenu() if err != nil { return nil, err } var action *walk.Action if menu == nil { action = walk.NewMenuAction(subMenu) } else if action, err = menu.Actions().AddMenu(subMenu); err != nil { return nil, err } if err := action.SetText(m.Text); err != nil { return nil, err } if err := setActionImage(action, m.Image, builder.dpi); err != nil { return nil, err } if err := setActionBoolOrCondition(action.SetEnabled, action.SetEnabledCondition, m.Enabled, "Menu.Enabled", builder); err != nil { return nil, err } if err := setActionBoolOrCondition(action.SetVisible, action.SetVisibleCondition, m.Visible, "Menu.Visible", builder); err != nil { return nil, err } for _, item := range m.Items { if _, err := item.createAction(builder, subMenu); err != nil { return nil, err } } if m.OnTriggered != nil { action.Triggered().Attach(m.OnTriggered) } if m.AssignActionTo != nil { *m.AssignActionTo = action } if m.AssignTo != nil { *m.AssignTo = subMenu } return action, nil } type Separator struct { } func (s Separator) createAction(builder *Builder, menu *walk.Menu) (*walk.Action, error) { action := walk.NewSeparatorAction() if menu != nil { if err := menu.Actions().Add(action); err != nil { return nil, err } } return action, nil } func addToActionList(list *walk.ActionList, actions []*walk.Action) error { for _, a := range actions { if err := list.Add(a); err != nil { return err } } return nil } func setActionImage(action *walk.Action, image interface{}, dpi int) (err error) { var img walk.Image switch image.(type) { case *walk.Bitmap: if img, err = walk.BitmapFrom(image, dpi); err != nil { return } case walk.ExtractableIcon, *walk.Icon: if img, err = walk.IconFrom(image, dpi); err != nil { return } default: if img, err = walk.ImageFrom(image); err != nil { return } } return action.SetImage(img) } func setActionBoolOrCondition(setBool func(bool) error, setCond func(walk.Condition), value Property, path string, builder *Builder) error { if value != nil { if b, ok := value.(bool); ok { if err := setBool(b); err != nil { return err } } else if s := builder.conditionOrProperty(value); s != nil { if c, ok := s.(walk.Condition); ok { setCond(c) } else { return fmt.Errorf("value of invalid type bound to %s: %T", path, s) } } } return nil } ================================================ FILE: declarative/brush.go ================================================ // Copyright 2017 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 declarative import ( "strconv" "github.com/lxn/walk" ) type TransparentBrush struct { } func (TransparentBrush) Create() (walk.Brush, error) { return walk.NullBrush(), nil } type SolidColorBrush struct { Color walk.Color } func (scb SolidColorBrush) Create() (walk.Brush, error) { return walk.NewSolidColorBrush(scb.Color) } type SystemColorBrush struct { Color walk.SystemColor } func (scb SystemColorBrush) Create() (walk.Brush, error) { return walk.NewSystemColorBrush(scb.Color) } type BitmapBrush struct { Image interface{} } func (bb BitmapBrush) Create() (walk.Brush, error) { var bmp *walk.Bitmap var err error switch img := bb.Image.(type) { case *walk.Bitmap: bmp = img case string: if bmp, err = walk.Resources.Bitmap(img); err != nil { return nil, err } case int: if bmp, err = walk.Resources.Bitmap(strconv.Itoa(img)); err != nil { return nil, err } default: return nil, walk.ErrInvalidType } return walk.NewBitmapBrush(bmp) } type GradientBrush struct { Vertexes []walk.GradientVertex Triangles []walk.GradientTriangle } func (gb GradientBrush) Create() (walk.Brush, error) { return walk.NewGradientBrush(gb.Vertexes, gb.Triangles) } type HorizontalGradientBrush struct { Stops []walk.GradientStop } func (hgb HorizontalGradientBrush) Create() (walk.Brush, error) { return walk.NewHorizontalGradientBrush(hgb.Stops) } type VerticalGradientBrush struct { Stops []walk.GradientStop } func (vgb VerticalGradientBrush) Create() (walk.Brush, error) { return walk.NewVerticalGradientBrush(vgb.Stops) } ================================================ FILE: declarative/builder.go ================================================ // Copyright 2012 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 declarative import ( "fmt" "log" "reflect" "regexp" "strings" "github.com/lxn/walk" "gopkg.in/Knetic/govaluate.v3" ) var ( conditionsByName = make(map[string]walk.Condition) propertyRE *regexp.Regexp ) func init() { walk.AppendToWalkInit(func() { propertyRE = regexp.MustCompile("[A-Za-z]+[0-9A-Za-z]*(\\.[A-Za-z]+[0-9A-Za-z]*)+") }) } func MustRegisterCondition(name string, condition walk.Condition) { if name == "" { panic(`name == ""`) } if condition == nil { panic("condition == nil") } if _, ok := conditionsByName[name]; ok { panic("name already registered") } conditionsByName[name] = condition } type declWidget struct { d Widget w walk.Window } type Builder struct { dpi int level int rows int columns int row int col int widgetValue reflect.Value parent walk.Container declWidgets []declWidget name2Window map[string]walk.Window name2DataBinder map[string]*walk.DataBinder deferredFuncs []func() error knownCompositeConditions map[string]walk.Condition expressions map[string]walk.Expression functions map[string]govaluate.ExpressionFunction } func NewBuilder(parent walk.Container) *Builder { var dpi int if parent != nil { dpi = parent.DPI() } return &Builder{ dpi: dpi, parent: parent, name2Window: make(map[string]walk.Window), name2DataBinder: make(map[string]*walk.DataBinder), knownCompositeConditions: make(map[string]walk.Condition), expressions: make(map[string]walk.Expression), functions: make(map[string]govaluate.ExpressionFunction), } } func (b *Builder) Parent() walk.Container { return b.parent } func (b *Builder) Defer(f func() error) { b.deferredFuncs = append(b.deferredFuncs, f) } func (b *Builder) deferBuildMenuActions(menu *walk.Menu, items []MenuItem) { if len(items) > 0 { b.Defer(func() error { for _, item := range items { if _, err := item.createAction(b, menu); err != nil { return err } } return nil }) } } func (b *Builder) deferBuildActions(actionList *walk.ActionList, items []MenuItem) { if len(items) > 0 { b.Defer(func() error { for _, item := range items { action, err := item.createAction(b, nil) if err != nil { return err } if err := actionList.Add(action); err != nil { return err } } return nil }) } } func (b *Builder) InitWidget(d Widget, w walk.Window, customInit func() error) error { if b.dpi == 0 { b.dpi = w.DPI() } oldWidgetValue := b.widgetValue b.widgetValue = reflect.ValueOf(d) b.level++ defer func() { b.widgetValue = oldWidgetValue b.level-- }() var succeeded bool defer func() { if !succeeded { w.Dispose() } }() b.declWidgets = append(b.declWidgets, declWidget{d, w}) // Window b.initAccessibility(d, w) // Widget if name := b.string("Name"); name != "" { w.SetName(name) b.name2Window[name] = w } if val := b.widgetValue.FieldByName("Background"); val.IsValid() { if brush := val.Interface(); brush != nil { bg, err := brush.(Brush).Create() if err != nil { return err } w.SetBackground(bg) } } if val := b.widgetValue.FieldByName("Font"); val.IsValid() { if f, err := val.Interface().(Font).Create(); err != nil { return err } else if f != nil { w.SetFont(f) } } if err := w.SetMinMaxSize(b.size("MinSize").toW(), b.size("MaxSize").toW()); err != nil { return err } if contextMenuItems := b.menuItems("ContextMenuItems"); len(contextMenuItems) > 0 { cm, err := walk.NewMenu() if err != nil { return err } b.deferBuildMenuActions(cm, contextMenuItems) w.SetContextMenu(cm) } if handler := b.eventHandler("OnBoundsChanged"); handler != nil { w.BoundsChanged().Attach(handler) } if handler := b.keyEventHandler("OnKeyDown"); handler != nil { w.KeyDown().Attach(handler) } if handler := b.keyEventHandler("OnKeyPress"); handler != nil { w.KeyPress().Attach(handler) } if handler := b.keyEventHandler("OnKeyUp"); handler != nil { w.KeyUp().Attach(handler) } if handler := b.mouseEventHandler("OnMouseDown"); handler != nil { w.MouseDown().Attach(handler) } if handler := b.mouseEventHandler("OnMouseMove"); handler != nil { w.MouseMove().Attach(handler) } if handler := b.mouseEventHandler("OnMouseUp"); handler != nil { w.MouseUp().Attach(handler) } if handler := b.eventHandler("OnSizeChanged"); handler != nil { w.SizeChanged().Attach(handler) } if db := b.bool("DoubleBuffering"); db { w.SetDoubleBuffering(true) } if rtl := b.bool("RightToLeftReading"); rtl { w.SetRightToLeftReading(true) } row := b.int("Row") rowSpan := b.int("RowSpan") column := b.int("Column") columnSpan := b.int("ColumnSpan") rowBackup := row columnBackup := column if widget, ok := w.(walk.Widget); ok { if alignment := b.alignment(); alignment != AlignHVDefault { if err := widget.SetAlignment(walk.Alignment2D(alignment)); err != nil { return err } } if err := widget.SetAlwaysConsumeSpace(b.bool("AlwaysConsumeSpace")); err != nil { return err } if field := b.widgetValue.FieldByName("GraphicsEffects"); field.IsValid() { for _, effect := range field.Interface().([]walk.WidgetGraphicsEffect) { widget.GraphicsEffects().Add(effect) } } if p := widget.Parent(); p != nil { type SetStretchFactorer interface { SetStretchFactor(widget walk.Widget, factor int) error } stretchFactor := b.int("StretchFactor") if stretchFactor < 1 { stretchFactor = 1 } switch l := p.Layout().(type) { case SetStretchFactorer: if err := l.SetStretchFactor(widget, stretchFactor); err != nil { return err } case *walk.GridLayout: csf := l.ColumnStretchFactor(column) if csf < stretchFactor { csf = stretchFactor } l.SetColumnStretchFactor(column, csf) rsf := l.RowStretchFactor(row) if rsf < stretchFactor { rsf = stretchFactor } l.SetRowStretchFactor(row, rsf) if rowSpan < 1 { rowSpan = 1 } if columnSpan < 1 { columnSpan = 1 } if b.rows > 0 && column == 0 && row == 0 { if b.row+rowSpan > b.rows { b.col++ b.row = 0 } column = b.col row = b.row b.row += rowSpan } if b.columns > 0 && row == 0 && column == 0 { if b.col+columnSpan > b.columns { b.row++ b.col = 0 } row = b.row column = b.col b.col += columnSpan } r := walk.Rectangle{column, row, columnSpan, rowSpan} if err := l.SetRange(widget, r); err != nil { return err } } } } oldParent := b.parent // Container var db *walk.DataBinder if wc, ok := w.(walk.Container); ok { var layout Layout if val := b.widgetValue.FieldByName("Layout"); val.IsValid() { layout, _ = val.Interface().(Layout) } if layout != nil { l, err := layout.Create() if err != nil { return err } if err := wc.SetLayout(l); err != nil { return err } } type DelegateContainerer interface { DelegateContainer() walk.Container } if dc, ok := wc.(DelegateContainerer); ok { if parent := dc.DelegateContainer(); parent != nil { b.parent = parent } else { b.parent = wc } } else { b.parent = wc } defer func() { b.parent = oldParent }() if layout != nil { if g, ok := layout.(Grid); ok { rowBackup = b.row columnBackup = b.col rows := b.rows columns := b.columns defer func() { b.rows, b.columns, b.row, b.col = rows, columns, rowBackup+rowSpan, columnBackup+columnSpan }() b.rows = g.Rows b.columns = g.Columns b.row = 0 b.col = 0 } } if val := b.widgetValue.FieldByName("Children"); val.IsValid() { for _, child := range val.Interface().([]Widget) { if err := child.Create(b); err != nil { return err } } } dataBinder := b.widgetValue.FieldByName("DataBinder").Interface().(DataBinder) if dataBinder.AssignTo != nil || dataBinder.DataSource != nil { if dataB, err := dataBinder.create(); err != nil { return err } else { db = dataB b.name2DataBinder[dataBinder.Name] = db if ep := db.ErrorPresenter(); ep != nil { if dep, ok := ep.(walk.Disposable); ok { wc.AddDisposable(dep) } } } } } // Custom if customInit != nil { if err := customInit(); err != nil { return err } } b.parent = oldParent if b.level == 1 { if err := b.initProperties(); err != nil { return err } } // Call Reset on DataBinder after customInit, so a Dialog gets a chance to first // wire up its DefaultButton to the CanSubmitChanged event of a DataBinder. if db != nil { if wc, ok := w.(walk.Container); ok { b.Defer(func() error { // FIXME: Currently SetDataBinder must be called after initProperties. wc.SetDataBinder(db) if db.DataSource() == nil { return nil } return db.Reset() }) } } if b.level == 1 { for _, f := range b.deferredFuncs { if err := f(); err != nil { return err } } } succeeded = true return nil } func (b *Builder) initAccessibility(d Widget, w walk.Window) error { accessibility := b.widgetValue.FieldByName("Accessibility") if accessibility.IsValid() { a := accessibility.Interface().(Accessibility) if a.Accelerator != "" { w.Accessibility().SetAccelerator(a.Accelerator) } if a.DefaultAction != "" { w.Accessibility().SetDefaultAction(a.DefaultAction) } if a.Description != "" { w.Accessibility().SetDescription(a.Description) } if a.Help != "" { w.Accessibility().SetHelp(a.Help) } if a.Name != "" { w.Accessibility().SetName(a.Name) } if a.Role > 0 { w.Accessibility().SetRole(walk.AccRole(a.Role)) } if a.RoleMap != "" { w.Accessibility().SetRoleMap(a.RoleMap) } if a.State > 0 { w.Accessibility().SetState(walk.AccState(a.State)) } if a.StateMap != "" { w.Accessibility().SetStateMap(a.StateMap) } if a.ValueMap != "" { w.Accessibility().SetValueMap(a.ValueMap) } } return nil } func (b *Builder) alignment() Alignment2D { fieldValue := b.widgetValue.FieldByName("Alignment") if fieldValue.IsValid() { return fieldValue.Interface().(Alignment2D) } return AlignHVDefault } func (b *Builder) bool(fieldName string) bool { fieldValue := b.widgetValue.FieldByName(fieldName) if fieldValue.IsValid() { return fieldValue.Interface().(bool) } return false } func (b *Builder) eventHandler(fieldName string) walk.EventHandler { fieldValue := b.widgetValue.FieldByName(fieldName) if fieldValue.IsValid() { return fieldValue.Interface().(walk.EventHandler) } return nil } func (b *Builder) float64(fieldName string) float64 { fieldValue := b.widgetValue.FieldByName(fieldName) if fieldValue.IsValid() { return fieldValue.Interface().(float64) } return 0 } func (b *Builder) int(fieldName string) int { fieldValue := b.widgetValue.FieldByName(fieldName) if fieldValue.IsValid() { return fieldValue.Interface().(int) } return 0 } func (b *Builder) keyEventHandler(fieldName string) walk.KeyEventHandler { fieldValue := b.widgetValue.FieldByName(fieldName) if fieldValue.IsValid() { return fieldValue.Interface().(walk.KeyEventHandler) } return nil } func (b *Builder) menuItems(fieldName string) []MenuItem { fieldValue := b.widgetValue.FieldByName(fieldName) if fieldValue.IsValid() { return fieldValue.Interface().([]MenuItem) } return nil } func (b *Builder) mouseEventHandler(fieldName string) walk.MouseEventHandler { fieldValue := b.widgetValue.FieldByName(fieldName) if fieldValue.IsValid() { return fieldValue.Interface().(walk.MouseEventHandler) } return nil } func (b *Builder) size(fieldName string) Size { fieldValue := b.widgetValue.FieldByName(fieldName) if fieldValue.IsValid() { return fieldValue.Interface().(Size) } return Size{} } func (b *Builder) string(fieldName string) string { fieldValue := b.widgetValue.FieldByName(fieldName) if fieldValue.IsValid() { return fieldValue.Interface().(string) } return "" } func (b *Builder) initProperties() error { for _, dw := range b.declWidgets { d, w := dw.d, dw.w sv := reflect.ValueOf(d) st := sv.Type() if st.Kind() != reflect.Struct { panic("d must be a struct value") } wb := w.AsWindowBase() fieldCount := st.NumField() for i := 0; i < fieldCount; i++ { sf := st.Field(i) prop := wb.Property(sf.Name) switch val := sv.Field(i).Interface().(type) { case nil: // nop case bindData: if prop == nil { panic(sf.Name + " is not a property") } src := b.conditionOrProperty(val) if src == nil { // No luck so far, so we assume the expression refers to // something in the data source. src = val.expression if val.validator != nil { validator, err := val.validator.Create() if err != nil { return err } if err := prop.SetValidator(validator); err != nil { return err } } } if err := prop.SetSource(src); err != nil { return err } case walk.Condition: if prop == nil { panic(sf.Name + " is not a property") } if err := prop.SetSource(val); err != nil { return err } default: if prop == nil { continue } if err := prop.Set(val); err != nil { return err } } } } return nil } func (b *Builder) conditionOrProperty(data Property) interface{} { switch val := data.(type) { case bindData: if val.expression == "" { return nil } e := &expression{ text: val.expression, subExprsByPath: subExpressions(make(map[string]walk.Expression)), } var singleExpr walk.Expression text := propertyRE.ReplaceAllStringFunc(val.expression, func(s string) string { if _, ok := e.subExprsByPath[s]; !ok { parts := strings.Split(s, ".") if w, ok := b.name2Window[parts[0]]; ok { if prop := w.AsWindowBase().Property(parts[1]); prop != nil { if len(s) == len(val.expression) { singleExpr = prop return "" } if len(parts) == 2 { e.addSubExpression(s, prop) } else { e.addSubExpression(s, walk.NewReflectExpression(prop, s[len(parts[0])+len(parts[1])+2:])) } } else { panic(fmt.Errorf(`invalid sub expression: "%s"`, s)) } } else if db, ok := b.name2DataBinder[parts[0]]; ok { e.addSubExpression(s, db.Expression(s[len(parts[0])+1:])) } else if expr, ok := b.expressions[parts[0]]; ok { e.addSubExpression(s, walk.NewReflectExpression(expr, s[len(parts[0])+1:])) } } return strings.Replace(s, ".", "\\.", -1) }) if singleExpr != nil { return singleExpr } expr, err := govaluate.NewEvaluableExpressionWithFunctions(text, b.functions) if err != nil { panic(fmt.Errorf(`invalid expression "%s": %s`, e.text, err.Error())) } for _, token := range expr.Tokens() { if token.Kind == govaluate.VARIABLE { name := token.Value.(string) if c, ok := conditionsByName[name]; ok { e.addSubExpression(name, c) } if x, ok := b.expressions[name]; ok { e.addSubExpression(name, x) } } } e.expr = expr if _, err := e.expr.Eval(e.subExprsByPath); err != nil { // We hope for the best and leave it to a DataBinder... return nil } if _, ok := e.Value().(bool); ok { return &boolExpression{expression: e} } return e case walk.Expression: return val } return nil } type expression struct { expr *govaluate.EvaluableExpression text string subExprsByPath subExpressions subExprsChangedHandles []int changedPublisher walk.EventPublisher lastReportedValue interface{} } type subExpressions map[string]walk.Expression func (se subExpressions) Get(name string) (interface{}, error) { if sub, ok := se[name]; ok { return sub.Value(), nil } return nil, fmt.Errorf(`invalid sub expression: "%s"`, name) } func (e *expression) String() string { return e.text } func (e *expression) Value() interface{} { val, err := e.expr.Eval(e.subExprsByPath) if err != nil { log.Printf(`walk - failed to evaluate expression "%s": %s`, e.text, err.Error()) } e.lastReportedValue = val return val } func (e *expression) Changed() *walk.Event { return e.changedPublisher.Event() } func (e *expression) addSubExpression(path string, subExpr walk.Expression) { e.subExprsByPath[path] = subExpr handle := subExpr.Changed().Attach(func() { last := e.lastReportedValue if v := e.Value(); v != last { e.changedPublisher.Publish() } }) e.subExprsChangedHandles = append(e.subExprsChangedHandles, handle) } type boolExpression struct { *expression } func (be *boolExpression) Satisfied() bool { satisfied, ok := be.Value().(bool) return ok && satisfied } ================================================ FILE: declarative/checkbox.go ================================================ // Copyright 2012 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 declarative import ( "github.com/lxn/walk" ) type CheckBox struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // Button Checked Property OnCheckedChanged walk.EventHandler OnClicked walk.EventHandler Text Property // CheckBox AssignTo **walk.CheckBox CheckState Property OnCheckStateChanged walk.EventHandler TextOnLeftSide bool Tristate bool } func (cb CheckBox) Create(builder *Builder) error { w, err := walk.NewCheckBox(builder.Parent()) if err != nil { return err } if cb.AssignTo != nil { *cb.AssignTo = w } return builder.InitWidget(cb, w, func() error { w.SetPersistent(cb.Persistent) if err := w.SetTextOnLeftSide(cb.TextOnLeftSide); err != nil { return err } if err := w.SetTristate(cb.Tristate); err != nil { return err } if _, isBindData := cb.CheckState.(bindData); cb.Tristate && (cb.CheckState == nil || isBindData) { w.SetCheckState(walk.CheckIndeterminate) } if cb.OnClicked != nil { w.Clicked().Attach(cb.OnClicked) } if cb.OnCheckedChanged != nil { w.CheckedChanged().Attach(cb.OnCheckedChanged) } if cb.OnCheckStateChanged != nil { w.CheckStateChanged().Attach(cb.OnCheckStateChanged) } return nil }) } ================================================ FILE: declarative/combobox.go ================================================ // Copyright 2012 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 declarative import ( "errors" ) import ( "github.com/lxn/walk" ) type ComboBox struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // ComboBox AssignTo **walk.ComboBox BindingMember string CurrentIndex Property DisplayMember string Editable bool Format string MaxLength int Model interface{} OnCurrentIndexChanged walk.EventHandler OnEditingFinished walk.EventHandler OnTextChanged walk.EventHandler Precision int Value Property } func (cb ComboBox) Create(builder *Builder) error { if _, ok := cb.Model.([]string); ok && (cb.BindingMember != "" || cb.DisplayMember != "") { return errors.New("ComboBox.Create: BindingMember and DisplayMember must be empty for []string models.") } var w *walk.ComboBox var err error if cb.Editable { w, err = walk.NewComboBox(builder.Parent()) } else { w, err = walk.NewDropDownBox(builder.Parent()) } if err != nil { return err } if cb.AssignTo != nil { *cb.AssignTo = w } return builder.InitWidget(cb, w, func() error { w.SetPersistent(cb.Persistent) w.SetFormat(cb.Format) w.SetPrecision(cb.Precision) w.SetMaxLength(cb.MaxLength) if err := w.SetBindingMember(cb.BindingMember); err != nil { return err } if err := w.SetDisplayMember(cb.DisplayMember); err != nil { return err } if err := w.SetModel(cb.Model); err != nil { return err } if cb.OnCurrentIndexChanged != nil { w.CurrentIndexChanged().Attach(cb.OnCurrentIndexChanged) } if cb.OnEditingFinished != nil { w.EditingFinished().Attach(cb.OnEditingFinished) } if cb.OnTextChanged != nil { w.TextChanged().Attach(cb.OnTextChanged) } return nil }) } ================================================ FILE: declarative/composite.go ================================================ // Copyright 2012 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 declarative import ( "github.com/lxn/walk" "github.com/lxn/win" ) type Composite struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // Container Children []Widget DataBinder DataBinder Layout Layout // Composite AssignTo **walk.Composite Border bool Expressions func() map[string]walk.Expression Functions map[string]func(args ...interface{}) (interface{}, error) } func (c Composite) Create(builder *Builder) error { var style uint32 if c.Border { style |= win.WS_BORDER } w, err := walk.NewCompositeWithStyle(builder.Parent(), style) if err != nil { return err } if c.AssignTo != nil { *c.AssignTo = w } w.SetSuspended(true) builder.Defer(func() error { w.SetSuspended(false) return nil }) return builder.InitWidget(c, w, func() error { if c.Expressions != nil { for name, expr := range c.Expressions() { builder.expressions[name] = expr } } if c.Functions != nil { for name, fn := range c.Functions { builder.functions[name] = fn } } return nil }) } ================================================ FILE: declarative/customwidget.go ================================================ // Copyright 2012 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 declarative import ( "github.com/lxn/walk" ) type PaintMode int const ( PaintNormal PaintMode = iota // erase background before PaintFunc PaintNoErase // PaintFunc clears background, single buffered PaintBuffered // PaintFunc clears background, double buffered ) type CustomWidget struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // CustomWidget AssignTo **walk.CustomWidget ClearsBackground bool InvalidatesOnResize bool Paint walk.PaintFunc PaintPixels walk.PaintFunc PaintMode PaintMode Style uint32 } func (cw CustomWidget) Create(builder *Builder) error { var w *walk.CustomWidget var err error if cw.PaintPixels != nil { w, err = walk.NewCustomWidgetPixels(builder.Parent(), uint(cw.Style), cw.PaintPixels) } else { w, err = walk.NewCustomWidget(builder.Parent(), uint(cw.Style), cw.Paint) } if err != nil { return err } if cw.AssignTo != nil { *cw.AssignTo = w } return builder.InitWidget(cw, w, func() error { if cw.PaintMode != PaintNormal && cw.ClearsBackground { panic("PaintMode and ClearsBackground are incompatible") } w.SetClearsBackground(cw.ClearsBackground) w.SetInvalidatesOnResize(cw.InvalidatesOnResize) w.SetPaintMode(walk.PaintMode(cw.PaintMode)) return nil }) } ================================================ FILE: declarative/databinder.go ================================================ // Copyright 2012 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 declarative import ( "time" "github.com/lxn/walk" ) type DataBinder struct { AssignTo **walk.DataBinder AutoSubmit bool AutoSubmitDelay time.Duration DataSource interface{} ErrorPresenter ErrorPresenter Name string OnCanSubmitChanged walk.EventHandler OnDataSourceChanged walk.EventHandler OnReset walk.EventHandler OnSubmitted walk.EventHandler } func (db DataBinder) create() (*walk.DataBinder, error) { b := walk.NewDataBinder() if db.AssignTo != nil { *db.AssignTo = b } if db.ErrorPresenter != nil { ep, err := db.ErrorPresenter.Create() if err != nil { return nil, err } b.SetErrorPresenter(ep) } b.SetDataSource(db.DataSource) b.SetAutoSubmit(db.AutoSubmit) b.SetAutoSubmitDelay(db.AutoSubmitDelay) if db.OnCanSubmitChanged != nil { b.CanSubmitChanged().Attach(db.OnCanSubmitChanged) } if db.OnDataSourceChanged != nil { b.DataSourceChanged().Attach(db.OnDataSourceChanged) } if db.OnReset != nil { b.ResetFinished().Attach(db.OnReset) } if db.OnSubmitted != nil { b.Submitted().Attach(db.OnSubmitted) } return b, nil } ================================================ FILE: declarative/dateedit.go ================================================ // Copyright 2012 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 declarative import ( "time" ) import ( "github.com/lxn/walk" ) type DateEdit struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // DateEdit AssignTo **walk.DateEdit Date Property Format string MaxDate time.Time MinDate time.Time NoneOption bool // Deprecated: use Optional instead OnDateChanged walk.EventHandler Optional bool } func (de DateEdit) Create(builder *Builder) error { var w *walk.DateEdit var err error if de.Optional || de.NoneOption { w, err = walk.NewDateEditWithNoneOption(builder.Parent()) } else { w, err = walk.NewDateEdit(builder.Parent()) } if err != nil { return err } if de.AssignTo != nil { *de.AssignTo = w } return builder.InitWidget(de, w, func() error { if err := w.SetFormat(de.Format); err != nil { return err } if err := w.SetRange(de.MinDate, de.MaxDate); err != nil { return err } if de.OnDateChanged != nil { w.DateChanged().Attach(de.OnDateChanged) } return nil }) } ================================================ FILE: declarative/datelabel.go ================================================ // Copyright 2018 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 declarative import ( "github.com/lxn/walk" ) type DateLabel struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // static TextColor walk.Color // DateLabel AssignTo **walk.DateLabel Date Property Format Property TextAlignment Alignment1D } func (dl DateLabel) Create(builder *Builder) error { w, err := walk.NewDateLabel(builder.Parent()) if err != nil { return err } if dl.AssignTo != nil { *dl.AssignTo = w } return builder.InitWidget(dl, w, func() error { if err := w.SetTextAlignment(walk.Alignment1D(dl.TextAlignment)); err != nil { return err } w.SetTextColor(dl.TextColor) return nil }) } ================================================ FILE: declarative/dialog.go ================================================ // Copyright 2012 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 declarative import ( "github.com/lxn/walk" ) type Dialog struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftLayout bool RightToLeftReading bool ToolTipText Property Visible Property // Container DataBinder DataBinder Layout Layout Children []Widget // Form Expressions func() map[string]walk.Expression Functions map[string]func(args ...interface{}) (interface{}, error) Icon Property Title Property Size Size // Dialog AssignTo **walk.Dialog CancelButton **walk.PushButton DefaultButton **walk.PushButton FixedSize bool } func (d Dialog) Create(owner walk.Form) error { var w *walk.Dialog var err error if d.FixedSize { w, err = walk.NewDialogWithFixedSize(owner) } else { w, err = walk.NewDialog(owner) } if err != nil { return err } if d.AssignTo != nil { *d.AssignTo = w } fi := formInfo{ // Window Background: d.Background, ContextMenuItems: d.ContextMenuItems, DoubleBuffering: d.DoubleBuffering, Enabled: d.Enabled, Font: d.Font, MaxSize: d.MaxSize, MinSize: d.MinSize, Name: d.Name, OnBoundsChanged: d.OnBoundsChanged, OnKeyDown: d.OnKeyDown, OnKeyPress: d.OnKeyPress, OnKeyUp: d.OnKeyUp, OnMouseDown: d.OnMouseDown, OnMouseMove: d.OnMouseMove, OnMouseUp: d.OnMouseUp, OnSizeChanged: d.OnSizeChanged, RightToLeftReading: d.RightToLeftReading, ToolTipText: "", Visible: d.Visible, Accessibility: d.Accessibility, // Container Children: d.Children, DataBinder: d.DataBinder, Layout: d.Layout, // Form Icon: d.Icon, Title: d.Title, } var db *walk.DataBinder if d.DataBinder.AssignTo == nil { d.DataBinder.AssignTo = &db } builder := NewBuilder(nil) w.SetSuspended(true) builder.Defer(func() error { w.SetSuspended(false) return nil }) if err := w.SetRightToLeftLayout(d.RightToLeftLayout); err != nil { return err } return builder.InitWidget(fi, w, func() error { if d.Size.Width > 0 && d.Size.Height > 0 { if err := w.SetSize(d.Size.toW()); err != nil { return err } } if d.DefaultButton != nil { if err := w.SetDefaultButton(*d.DefaultButton); err != nil { return err } if db := *d.DataBinder.AssignTo; db != nil { if db.DataSource() != nil { (*d.DefaultButton).SetEnabled(db.CanSubmit()) } db.CanSubmitChanged().Attach(func() { (*d.DefaultButton).SetEnabled(db.CanSubmit()) }) } } if d.CancelButton != nil { if err := w.SetCancelButton(*d.CancelButton); err != nil { return err } } if d.Expressions != nil { for name, expr := range d.Expressions() { builder.expressions[name] = expr } } if d.Functions != nil { for name, fn := range d.Functions { builder.functions[name] = fn } } return nil }) } func (d Dialog) Run(owner walk.Form) (int, error) { var w *walk.Dialog if d.AssignTo == nil { d.AssignTo = &w } if err := d.Create(owner); err != nil { return 0, err } return (*d.AssignTo).Run(), nil } ================================================ FILE: declarative/font.go ================================================ // Copyright 2012 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 declarative import ( "github.com/lxn/walk" ) type Font struct { Family string PointSize int Bold bool Italic bool Underline bool StrikeOut bool } func (f Font) Create() (*walk.Font, error) { if f.Family == "" && f.PointSize == 0 { return nil, nil } var fs walk.FontStyle if f.Bold { fs |= walk.FontBold } if f.Italic { fs |= walk.FontItalic } if f.Underline { fs |= walk.FontUnderline } if f.StrikeOut { fs |= walk.FontStrikeOut } return walk.NewFont(f.Family, f.PointSize, fs) } ================================================ FILE: declarative/gradientcomposite.go ================================================ // Copyright 2017 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 declarative import ( "github.com/lxn/walk" "github.com/lxn/win" ) type GradientComposite struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // Container Children []Widget Layout Layout DataBinder DataBinder // GradientComposite AssignTo **walk.GradientComposite Border bool Color1 Property Color2 Property Expressions func() map[string]walk.Expression Functions map[string]func(args ...interface{}) (interface{}, error) Vertical Property } func (gc GradientComposite) Create(builder *Builder) error { var style uint32 if gc.Border { style |= win.WS_BORDER } w, err := walk.NewGradientCompositeWithStyle(builder.Parent(), style) if err != nil { return err } if gc.AssignTo != nil { *gc.AssignTo = w } w.SetSuspended(true) builder.Defer(func() error { w.SetSuspended(false) return nil }) return builder.InitWidget(gc, w, func() error { if gc.Expressions != nil { for name, expr := range gc.Expressions() { builder.expressions[name] = expr } } if gc.Functions != nil { for name, fn := range gc.Functions { builder.functions[name] = fn } } return nil }) } ================================================ FILE: declarative/groupbox.go ================================================ // Copyright 2012 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 declarative import ( "github.com/lxn/walk" ) type GroupBox struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // Container Children []Widget DataBinder DataBinder Layout Layout // GroupBox AssignTo **walk.GroupBox Checkable bool Checked Property Title string } func (gb GroupBox) Create(builder *Builder) error { w, err := walk.NewGroupBox(builder.Parent()) if err != nil { return err } if gb.AssignTo != nil { *gb.AssignTo = w } w.SetSuspended(true) builder.Defer(func() error { w.SetSuspended(false) return nil }) return builder.InitWidget(gb, w, func() error { if err := w.SetTitle(gb.Title); err != nil { return err } w.SetCheckable(gb.Checkable) return nil }) } ================================================ FILE: declarative/imageview.go ================================================ // Copyright 2012 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 declarative import ( "github.com/lxn/walk" ) type ImageViewMode int const ( ImageViewModeIdeal = ImageViewMode(walk.ImageViewModeIdeal) ImageViewModeCorner = ImageViewMode(walk.ImageViewModeCorner) ImageViewModeCenter = ImageViewMode(walk.ImageViewModeCenter) ImageViewModeShrink = ImageViewMode(walk.ImageViewModeShrink) ImageViewModeZoom = ImageViewMode(walk.ImageViewModeZoom) ImageViewModeStretch = ImageViewMode(walk.ImageViewModeStretch) ) type ImageView struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // ImageView AssignTo **walk.ImageView Image Property Margin Property Mode ImageViewMode } func (iv ImageView) Create(builder *Builder) error { w, err := walk.NewImageView(builder.Parent()) if err != nil { return err } if iv.AssignTo != nil { *iv.AssignTo = w } return builder.InitWidget(iv, w, func() error { w.SetMode(walk.ImageViewMode(iv.Mode)) return nil }) } ================================================ FILE: declarative/interfaces.go ================================================ // Copyright 2012 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 declarative import ( "path/filepath" "github.com/lxn/walk" ) func tr(source string, context ...string) string { if translation := walk.TranslationFunc(); translation != nil { return translation(source, context...) } return source } type Property interface{} type bindData struct { expression string validator Validator } func Bind(expression string, validators ...Validator) Property { bd := bindData{expression: expression} switch len(validators) { case 0: // nop case 1: bd.validator = validators[0] default: bd.validator = dMultiValidator{validators} } return bd } type SysDLLIcon struct { FileName string Index int Size int } func (sdi SysDLLIcon) FilePath_() string { root, _ := walk.SystemPath() name := sdi.FileName if filepath.Ext(name) == "" { name += ".dll" } return filepath.Join(root, name) } func (sdi SysDLLIcon) Index_() int { return sdi.Index } func (sdi SysDLLIcon) Size_() int { if sdi.Size == 0 { return 16 } return sdi.Size } type Brush interface { Create() (walk.Brush, error) } type Layout interface { Create() (walk.Layout, error) } type Widget interface { Create(builder *Builder) error } type MenuItem interface { createAction(builder *Builder, menu *walk.Menu) (*walk.Action, error) } type Validator interface { Create() (walk.Validator, error) } type ErrorPresenter interface { Create() (walk.ErrorPresenter, error) } type ErrorPresenterRef struct { ErrorPresenter *walk.ErrorPresenter } func (epr ErrorPresenterRef) Create() (walk.ErrorPresenter, error) { if epr.ErrorPresenter != nil { return *epr.ErrorPresenter, nil } return nil, nil } type ToolTipErrorPresenter struct { } func (ToolTipErrorPresenter) Create() (walk.ErrorPresenter, error) { return walk.NewToolTipErrorPresenter() } type formInfo struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler RightToLeftReading bool ToolTipText string Visible Property // Container Children []Widget DataBinder DataBinder Layout Layout // Form Icon Property Title Property } func (formInfo) Create(builder *Builder) error { return nil } ================================================ FILE: declarative/label.go ================================================ // Copyright 2012 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 declarative import ( "github.com/lxn/walk" "github.com/lxn/win" ) type EllipsisMode int const ( EllipsisNone = EllipsisMode(walk.EllipsisNone) EllipsisEnd = EllipsisMode(walk.EllipsisEnd) EllipsisPath = EllipsisMode(walk.EllipsisPath) ) type Label struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // Label AssignTo **walk.Label EllipsisMode EllipsisMode NoPrefix bool Text Property TextAlignment Alignment1D TextColor walk.Color } func (l Label) Create(builder *Builder) error { var style uint32 if l.NoPrefix { style |= win.SS_NOPREFIX } w, err := walk.NewLabelWithStyle(builder.Parent(), style) if err != nil { return err } if l.AssignTo != nil { *l.AssignTo = w } return builder.InitWidget(l, w, func() error { if err := w.SetEllipsisMode(walk.EllipsisMode(l.EllipsisMode)); err != nil { return err } if err := w.SetTextAlignment(walk.Alignment1D(l.TextAlignment)); err != nil { return err } w.SetTextColor(l.TextColor) return nil }) } ================================================ FILE: declarative/layouts.go ================================================ // Copyright 2012 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 declarative import ( "errors" "github.com/lxn/walk" ) type Orientation byte const ( Horizontal Orientation = Orientation(walk.Horizontal) Vertical Orientation = Orientation(walk.Vertical) ) type Margins struct { Left int Top int Right int Bottom int } func (m Margins) isZero() bool { return m.Left == 0 && m.Top == 0 && m.Right == 0 && m.Bottom == 0 } func (m Margins) toW() walk.Margins { return walk.Margins{m.Left, m.Top, m.Right, m.Bottom} } type Rectangle struct { X int Y int Width int Height int } func (r Rectangle) toW() walk.Rectangle { return walk.Rectangle{r.X, r.Y, r.Width, r.Height} } type Size struct { Width int Height int } func (s Size) toW() walk.Size { return walk.Size{s.Width, s.Height} } func setLayoutMargins(layout walk.Layout, margins Margins, marginsZero bool) error { if !marginsZero && margins.isZero() { margins = Margins{9, 9, 9, 9} } return layout.SetMargins(margins.toW()) } func setLayoutSpacing(layout walk.Layout, spacing int, spacingZero bool) error { if !spacingZero && spacing == 0 { spacing = 6 } return layout.SetSpacing(spacing) } type HBox struct { Margins Margins Alignment Alignment2D Spacing int MarginsZero bool SpacingZero bool } func (hb HBox) Create() (walk.Layout, error) { l := walk.NewHBoxLayout() if err := setLayoutMargins(l, hb.Margins, hb.MarginsZero); err != nil { return nil, err } if err := setLayoutSpacing(l, hb.Spacing, hb.SpacingZero); err != nil { return nil, err } if err := l.SetAlignment(walk.Alignment2D(hb.Alignment)); err != nil { return nil, err } return l, nil } type VBox struct { Margins Margins Alignment Alignment2D Spacing int MarginsZero bool SpacingZero bool } func (vb VBox) Create() (walk.Layout, error) { l := walk.NewVBoxLayout() if err := setLayoutMargins(l, vb.Margins, vb.MarginsZero); err != nil { return nil, err } if err := setLayoutSpacing(l, vb.Spacing, vb.SpacingZero); err != nil { return nil, err } if err := l.SetAlignment(walk.Alignment2D(vb.Alignment)); err != nil { return nil, err } return l, nil } type Grid struct { Rows int Columns int Margins Margins Alignment Alignment2D Spacing int MarginsZero bool SpacingZero bool } func (g Grid) Create() (walk.Layout, error) { if g.Rows > 0 && g.Columns > 0 { return nil, errors.New("only one of Rows and Columns may be > 0") } l := walk.NewGridLayout() if err := setLayoutMargins(l, g.Margins, g.MarginsZero); err != nil { return nil, err } if err := setLayoutSpacing(l, g.Spacing, g.SpacingZero); err != nil { return nil, err } if err := l.SetAlignment(walk.Alignment2D(g.Alignment)); err != nil { return nil, err } return l, nil } type Flow struct { Margins Margins Alignment Alignment2D Spacing int MarginsZero bool SpacingZero bool } func (f Flow) Create() (walk.Layout, error) { l := walk.NewFlowLayout() if err := setLayoutMargins(l, f.Margins, f.MarginsZero); err != nil { return nil, err } if err := setLayoutSpacing(l, f.Spacing, f.SpacingZero); err != nil { return nil, err } if err := l.SetAlignment(walk.Alignment2D(f.Alignment)); err != nil { return nil, err } return l, nil } ================================================ FILE: declarative/lineedit.go ================================================ // Copyright 2012 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 declarative import ( "github.com/lxn/walk" ) type CaseMode uint32 const ( CaseModeMixed CaseMode = CaseMode(walk.CaseModeMixed) CaseModeUpper CaseMode = CaseMode(walk.CaseModeUpper) CaseModeLower CaseMode = CaseMode(walk.CaseModeLower) ) type LineEdit struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // LineEdit AssignTo **walk.LineEdit CaseMode CaseMode CueBanner string MaxLength int OnEditingFinished walk.EventHandler OnTextChanged walk.EventHandler PasswordMode bool ReadOnly Property Text Property TextAlignment Alignment1D TextColor walk.Color } func (le LineEdit) Create(builder *Builder) error { w, err := walk.NewLineEdit(builder.Parent()) if err != nil { return err } if le.AssignTo != nil { *le.AssignTo = w } return builder.InitWidget(le, w, func() error { w.SetTextColor(le.TextColor) if err := w.SetTextAlignment(walk.Alignment1D(le.TextAlignment)); err != nil { return err } if le.CueBanner != "" { if err := w.SetCueBanner(le.CueBanner); err != nil { return err } } w.SetMaxLength(le.MaxLength) w.SetPasswordMode(le.PasswordMode) if err := w.SetCaseMode(walk.CaseMode(le.CaseMode)); err != nil { return err } if le.OnEditingFinished != nil { w.EditingFinished().Attach(le.OnEditingFinished) } if le.OnTextChanged != nil { w.TextChanged().Attach(le.OnTextChanged) } return nil }) } ================================================ FILE: declarative/linklabel.go ================================================ // Copyright 2017 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 declarative import ( "github.com/lxn/walk" ) type LinkLabel struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // LinkLabel AssignTo **walk.LinkLabel OnLinkActivated walk.LinkLabelLinkEventHandler Text Property } func (ll LinkLabel) Create(builder *Builder) error { w, err := walk.NewLinkLabel(builder.Parent()) if err != nil { return err } if ll.AssignTo != nil { *ll.AssignTo = w } return builder.InitWidget(ll, w, func() error { if ll.OnLinkActivated != nil { w.LinkActivated().Attach(ll.OnLinkActivated) } return nil }) } ================================================ FILE: declarative/listbox.go ================================================ // Copyright 2012 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 declarative import ( "errors" "github.com/lxn/walk" "github.com/lxn/win" ) type ListBox struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // ListBox AssignTo **walk.ListBox BindingMember string CurrentIndex Property DisplayMember string Format string ItemStyler walk.ListItemStyler Model interface{} MultiSelection bool OnCurrentIndexChanged walk.EventHandler OnItemActivated walk.EventHandler OnSelectedIndexesChanged walk.EventHandler Precision int Value Property } func (lb ListBox) Create(builder *Builder) error { var w *walk.ListBox var err error if _, ok := lb.Model.([]string); ok && (lb.BindingMember != "" || lb.DisplayMember != "") { return errors.New("ListBox.Create: BindingMember and DisplayMember must be empty for []string models.") } var style uint32 if lb.ItemStyler != nil { style |= win.LBS_OWNERDRAWVARIABLE } if lb.MultiSelection { style |= win.LBS_EXTENDEDSEL } w, err = walk.NewListBoxWithStyle(builder.Parent(), style) if err != nil { return err } if lb.AssignTo != nil { *lb.AssignTo = w } return builder.InitWidget(lb, w, func() error { if lb.ItemStyler != nil { w.SetItemStyler(lb.ItemStyler) } w.SetFormat(lb.Format) w.SetPrecision(lb.Precision) if err := w.SetBindingMember(lb.BindingMember); err != nil { return err } if err := w.SetDisplayMember(lb.DisplayMember); err != nil { return err } if err := w.SetModel(lb.Model); err != nil { return err } if lb.OnCurrentIndexChanged != nil { w.CurrentIndexChanged().Attach(lb.OnCurrentIndexChanged) } if lb.OnSelectedIndexesChanged != nil { w.SelectedIndexesChanged().Attach(lb.OnSelectedIndexesChanged) } if lb.OnItemActivated != nil { w.ItemActivated().Attach(lb.OnItemActivated) } return nil }) } ================================================ FILE: declarative/mainwindow.go ================================================ // Copyright 2012 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 declarative import "github.com/lxn/walk" type MainWindow struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftLayout bool RightToLeftReading bool ToolTipText Property Visible Property // Container Children []Widget DataBinder DataBinder Layout Layout // Form Icon Property Size Size Title Property // MainWindow AssignTo **walk.MainWindow Bounds Rectangle Expressions func() map[string]walk.Expression Functions map[string]func(args ...interface{}) (interface{}, error) MenuItems []MenuItem OnDropFiles walk.DropFilesEventHandler StatusBarItems []StatusBarItem SuspendedUntilRun bool ToolBar ToolBar ToolBarItems []MenuItem // Deprecated: use ToolBar instead } func (mw MainWindow) Create() error { w, err := walk.NewMainWindowWithCfg(&walk.MainWindowCfg{ Name: mw.Name, Bounds: mw.Bounds.toW(), }) if err != nil { return err } if mw.AssignTo != nil { *mw.AssignTo = w } fi := formInfo{ // Window Background: mw.Background, ContextMenuItems: mw.ContextMenuItems, DoubleBuffering: mw.DoubleBuffering, Enabled: mw.Enabled, Font: mw.Font, MaxSize: mw.MaxSize, MinSize: mw.MinSize, Name: mw.Name, OnBoundsChanged: mw.OnBoundsChanged, OnKeyDown: mw.OnKeyDown, OnKeyPress: mw.OnKeyPress, OnKeyUp: mw.OnKeyUp, OnMouseDown: mw.OnMouseDown, OnMouseMove: mw.OnMouseMove, OnMouseUp: mw.OnMouseUp, OnSizeChanged: mw.OnSizeChanged, RightToLeftReading: mw.RightToLeftReading, Visible: mw.Visible, Accessibility: mw.Accessibility, // Container Children: mw.Children, DataBinder: mw.DataBinder, Layout: mw.Layout, // Form Icon: mw.Icon, Title: mw.Title, } builder := NewBuilder(nil) w.SetSuspended(true) if !mw.SuspendedUntilRun { builder.Defer(func() error { w.SetSuspended(false) return nil }) } builder.deferBuildMenuActions(w.Menu(), mw.MenuItems) if err := w.SetRightToLeftLayout(mw.RightToLeftLayout); err != nil { return err } return builder.InitWidget(fi, w, func() error { if len(mw.ToolBar.Items) > 0 { var tb *walk.ToolBar if mw.ToolBar.AssignTo == nil { mw.ToolBar.AssignTo = &tb } if err := mw.ToolBar.Create(builder); err != nil { return err } old := w.ToolBar() w.SetToolBar(*mw.ToolBar.AssignTo) old.Dispose() } else { builder.deferBuildActions(w.ToolBar().Actions(), mw.ToolBarItems) } for _, sbi := range mw.StatusBarItems { s := walk.NewStatusBarItem() if sbi.AssignTo != nil { *sbi.AssignTo = s } s.SetIcon(sbi.Icon) s.SetText(sbi.Text) s.SetToolTipText(sbi.ToolTipText) if sbi.Width > 0 { s.SetWidth(sbi.Width) } if sbi.OnClicked != nil { s.Clicked().Attach(sbi.OnClicked) } w.StatusBar().Items().Add(s) } if mw.Size.Width > 0 && mw.Size.Height > 0 { if err := w.SetSize(mw.Size.toW()); err != nil { return err } } imageList, err := walk.NewImageListForDPI(walk.SizeFrom96DPI(walk.Size{16, 16}, builder.dpi), 0, builder.dpi) if err != nil { return err } w.ToolBar().SetImageList(imageList) if mw.OnDropFiles != nil { w.DropFiles().Attach(mw.OnDropFiles) } // if mw.AssignTo != nil { // *mw.AssignTo = w // } if mw.Expressions != nil { for name, expr := range mw.Expressions() { builder.expressions[name] = expr } } if mw.Functions != nil { for name, fn := range mw.Functions { builder.functions[name] = fn } } builder.Defer(func() error { if mw.Visible != false { w.Show() } return nil }) return nil }) } func (mw MainWindow) Run() (int, error) { var w *walk.MainWindow if mw.AssignTo == nil { mw.AssignTo = &w } if err := mw.Create(); err != nil { return 0, err } return (*mw.AssignTo).Run(), nil } type StatusBarItem struct { AssignTo **walk.StatusBarItem Icon *walk.Icon Text string ToolTipText string Width int OnClicked walk.EventHandler } ================================================ FILE: declarative/nonwin.go ================================================ // Copyright 2010 The win 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 declarative ================================================ FILE: declarative/numberedit.go ================================================ // Copyright 2012 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 declarative import ( "github.com/lxn/walk" ) type NumberEdit struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // NumberEdit AssignTo **walk.NumberEdit Decimals int Increment float64 MaxValue float64 MinValue float64 Prefix Property OnValueChanged walk.EventHandler ReadOnly Property SpinButtonsVisible bool Suffix Property TextColor walk.Color Value Property } func (ne NumberEdit) Create(builder *Builder) error { w, err := walk.NewNumberEdit(builder.Parent()) if err != nil { return err } if ne.AssignTo != nil { *ne.AssignTo = w } return builder.InitWidget(ne, w, func() error { w.SetTextColor(ne.TextColor) if err := w.SetDecimals(ne.Decimals); err != nil { return err } inc := ne.Increment if inc == 0 { inc = 1 } if err := w.SetIncrement(inc); err != nil { return err } if ne.MinValue != 0 || ne.MaxValue != 0 { if err := w.SetRange(ne.MinValue, ne.MaxValue); err != nil { return err } } if err := w.SetSpinButtonsVisible(ne.SpinButtonsVisible); err != nil { return err } if ne.OnValueChanged != nil { w.ValueChanged().Attach(ne.OnValueChanged) } return nil }) } ================================================ FILE: declarative/numberlabel.go ================================================ // Copyright 2018 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 declarative import ( "github.com/lxn/walk" ) type NumberLabel struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // static TextColor walk.Color // NumberLabel AssignTo **walk.NumberLabel Decimals Property Suffix Property TextAlignment Alignment1D Value Property } func (nl NumberLabel) Create(builder *Builder) error { w, err := walk.NewNumberLabel(builder.Parent()) if err != nil { return err } if nl.AssignTo != nil { *nl.AssignTo = w } return builder.InitWidget(nl, w, func() error { if err := w.SetTextAlignment(walk.Alignment1D(nl.TextAlignment)); err != nil { return err } w.SetTextColor(nl.TextColor) return nil }) } ================================================ FILE: declarative/progressbar.go ================================================ // Copyright 2012 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 declarative import ( "github.com/lxn/walk" ) type ProgressBar struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // ProgressBar AssignTo **walk.ProgressBar MarqueeMode bool MaxValue int MinValue int Value int } func (pb ProgressBar) Create(builder *Builder) error { w, err := walk.NewProgressBar(builder.Parent()) if err != nil { return err } if pb.AssignTo != nil { *pb.AssignTo = w } return builder.InitWidget(pb, w, func() error { if pb.MaxValue > pb.MinValue { w.SetRange(pb.MinValue, pb.MaxValue) } w.SetValue(pb.Value) if err := w.SetMarqueeMode(pb.MarqueeMode); err != nil { return err } return nil }) } ================================================ FILE: declarative/pushbutton.go ================================================ // Copyright 2012 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 declarative import ( "github.com/lxn/walk" ) type PushButton struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // Button Image Property OnClicked walk.EventHandler Text Property // PushButton AssignTo **walk.PushButton ImageAboveText bool } func (pb PushButton) Create(builder *Builder) error { w, err := walk.NewPushButton(builder.Parent()) if err != nil { return err } if pb.AssignTo != nil { *pb.AssignTo = w } return builder.InitWidget(pb, w, func() error { if err := w.SetImageAboveText(pb.ImageAboveText); err != nil { return err } if pb.OnClicked != nil { w.Clicked().Attach(pb.OnClicked) } return nil }) } ================================================ FILE: declarative/radiobutton.go ================================================ // Copyright 2012 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 declarative import ( "github.com/lxn/walk" ) type RadioButton struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // Button OnClicked walk.EventHandler Text Property // RadioButton AssignTo **walk.RadioButton TextOnLeftSide bool Value interface{} } func (rb RadioButton) Create(builder *Builder) error { w, err := walk.NewRadioButton(builder.Parent()) if err != nil { return err } if rb.AssignTo != nil { *rb.AssignTo = w } return builder.InitWidget(rb, w, func() error { w.SetValue(rb.Value) if err := w.SetTextOnLeftSide(rb.TextOnLeftSide); err != nil { return err } if rb.OnClicked != nil { w.Clicked().Attach(rb.OnClicked) } return nil }) } ================================================ FILE: declarative/radiobuttongroup.go ================================================ // Copyright 2013 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 declarative import ( "bytes" "errors" ) import ( "github.com/lxn/walk" ) type RadioButtonGroup struct { Buttons []RadioButton DataMember string Optional bool } func (rbg RadioButtonGroup) Create(builder *Builder) error { if len(rbg.Buttons) == 0 { return nil } var first *walk.RadioButton for _, rb := range rbg.Buttons { if first == nil { if rb.AssignTo == nil { rb.AssignTo = &first } } if err := rb.Create(builder); err != nil { return err } if first == nil { first = *rb.AssignTo } } parent := builder.Parent() builder.Defer(func() error { group := first.Group() validator := newRadioButtonGroupValidator(group, parent) for _, rb := range group.Buttons() { prop := rb.AsWindowBase().Property("CheckedValue") if err := prop.SetSource(rbg.DataMember); err != nil { return err } if err := prop.SetValidator(validator); err != nil { return err } } return nil }) return nil } type radioButtonGroupValidator struct { group *walk.RadioButtonGroup err error } func newRadioButtonGroupValidator(group *walk.RadioButtonGroup, parent walk.Container) *radioButtonGroupValidator { b := new(bytes.Buffer) if gb, ok := parent.(*walk.GroupBox); ok { b.WriteString(gb.Title()) } else { for i, rb := range group.Buttons() { if i > 0 { b.WriteString(", ") } b.WriteString(rb.Text()) } } b.WriteString(": ") b.WriteString(tr("A selection is required.", "walk")) return &radioButtonGroupValidator{group: group, err: errors.New(b.String())} } func (rbgv *radioButtonGroupValidator) Validate(v interface{}) error { if rbgv.group.CheckedButton() == nil { return rbgv.err } return nil } ================================================ FILE: declarative/radiobuttongroupbox.go ================================================ // Copyright 2013 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 declarative import ( "github.com/lxn/walk" ) type RadioButtonGroupBox struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // Container Children []Widget DataBinder DataBinder Layout Layout // GroupBox AssignTo **walk.GroupBox Checkable bool Checked Property Title string // RadioButtonGroupBox Buttons []RadioButton DataMember string Optional bool } func (rbgb RadioButtonGroupBox) Create(builder *Builder) error { w, err := walk.NewGroupBox(builder.Parent()) if err != nil { return err } if rbgb.AssignTo != nil { *rbgb.AssignTo = w } w.SetSuspended(true) builder.Defer(func() error { w.SetSuspended(false) return nil }) return builder.InitWidget(rbgb, w, func() error { if err := w.SetTitle(rbgb.Title); err != nil { return err } w.SetCheckable(rbgb.Checkable) if err := (RadioButtonGroup{ DataMember: rbgb.DataMember, Optional: rbgb.Optional, Buttons: rbgb.Buttons, }).Create(builder); err != nil { return err } return nil }) } ================================================ FILE: declarative/scrollview.go ================================================ // Copyright 2014 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 declarative import ( "github.com/lxn/walk" ) type ScrollView struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // Container Children []Widget DataBinder DataBinder Layout Layout // ScrollView AssignTo **walk.ScrollView HorizontalFixed bool VerticalFixed bool } func (sv ScrollView) Create(builder *Builder) error { w, err := walk.NewScrollView(builder.Parent()) if err != nil { return err } if sv.AssignTo != nil { *sv.AssignTo = w } w.SetSuspended(true) builder.Defer(func() error { w.SetSuspended(false) return nil }) w.SetScrollbars(!sv.HorizontalFixed, !sv.VerticalFixed) return builder.InitWidget(sv, w, func() error { return nil }) } ================================================ FILE: declarative/separator.go ================================================ // Copyright 2017 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 declarative import ( "github.com/lxn/walk" ) type HSeparator struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // Separator AssignTo **walk.Separator } func (s HSeparator) Create(builder *Builder) error { w, err := walk.NewHSeparator(builder.Parent()) if err != nil { return err } if s.AssignTo != nil { *s.AssignTo = w } return builder.InitWidget(s, w, func() error { return nil }) } type VSeparator struct { // Window Accessibility Accessibility ContextMenuItems []MenuItem Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool ToolTipText Property Visible Property // Widget AlwaysConsumeSpace bool Column int ColumnSpan int Row int RowSpan int StretchFactor int // Separator AssignTo **walk.Separator } func (s VSeparator) Create(builder *Builder) error { w, err := walk.NewVSeparator(builder.Parent()) if err != nil { return err } if s.AssignTo != nil { *s.AssignTo = w } return builder.InitWidget(s, w, func() error { return nil }) } ================================================ FILE: declarative/slider.go ================================================ // Copyright 2016 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 declarative import ( "github.com/lxn/walk" ) type Slider struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // Slider AssignTo **walk.Slider LineSize int MaxValue int MinValue int Orientation Orientation OnValueChanged walk.EventHandler PageSize int ToolTipsHidden bool Tracking bool Value Property } func (sl Slider) Create(builder *Builder) error { w, err := walk.NewSliderWithCfg(builder.Parent(), &walk.SliderCfg{ Orientation: walk.Orientation(sl.Orientation), ToolTipsHidden: sl.ToolTipsHidden, }) if err != nil { return err } if sl.AssignTo != nil { *sl.AssignTo = w } return builder.InitWidget(sl, w, func() error { w.SetPersistent(sl.Persistent) if sl.LineSize > 0 { w.SetLineSize(sl.LineSize) } if sl.PageSize > 0 { w.SetPageSize(sl.PageSize) } w.SetTracking(sl.Tracking) if sl.MaxValue > sl.MinValue { w.SetRange(sl.MinValue, sl.MaxValue) } if sl.OnValueChanged != nil { w.ValueChanged().Attach(sl.OnValueChanged) } return nil }) } ================================================ FILE: declarative/spacer.go ================================================ // Copyright 2012 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 declarative import ( "github.com/lxn/walk" ) type HSpacer struct { // Window MaxSize Size MinSize Size Name string // Widget Column int ColumnSpan int Row int RowSpan int StretchFactor int // Spacer GreedyLocallyOnly bool Size int } func (hs HSpacer) Create(builder *Builder) (err error) { var flags walk.LayoutFlags if hs.Size == 0 { flags = walk.ShrinkableHorz | walk.GrowableHorz | walk.GreedyHorz } var w *walk.Spacer if w, err = walk.NewSpacerWithCfg(builder.Parent(), &walk.SpacerCfg{ LayoutFlags: flags, SizeHint: Size{Width: hs.Size}.toW(), GreedyLocallyOnly: hs.GreedyLocallyOnly, }); err != nil { return } return builder.InitWidget(hs, w, nil) } type VSpacer struct { // Window MaxSize Size MinSize Size Name string // Widget Column int ColumnSpan int Row int RowSpan int StretchFactor int // Spacer GreedyLocallyOnly bool Size int } func (vs VSpacer) Create(builder *Builder) (err error) { var flags walk.LayoutFlags if vs.Size == 0 { flags = walk.ShrinkableVert | walk.GrowableVert | walk.GreedyVert } var w *walk.Spacer if w, err = walk.NewSpacerWithCfg(builder.Parent(), &walk.SpacerCfg{ LayoutFlags: flags, SizeHint: Size{Height: vs.Size}.toW(), GreedyLocallyOnly: vs.GreedyLocallyOnly, }); err != nil { return } return builder.InitWidget(vs, w, nil) } ================================================ FILE: declarative/splitbutton.go ================================================ // Copyright 2016 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 declarative import ( "github.com/lxn/walk" ) type SplitButton struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // Button Image Property Text Property OnClicked walk.EventHandler // SplitButton AssignTo **walk.SplitButton ImageAboveText bool MenuItems []MenuItem } func (sb SplitButton) Create(builder *Builder) error { w, err := walk.NewSplitButton(builder.Parent()) if err != nil { return err } if sb.AssignTo != nil { *sb.AssignTo = w } builder.deferBuildMenuActions(w.Menu(), sb.MenuItems) return builder.InitWidget(sb, w, func() error { if err := w.SetImageAboveText(sb.ImageAboveText); err != nil { return err } if sb.OnClicked != nil { w.Clicked().Attach(sb.OnClicked) } return nil }) } ================================================ FILE: declarative/splitter.go ================================================ // Copyright 2012 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 declarative import ( "github.com/lxn/walk" ) type HSplitter struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // Container Children []Widget DataBinder DataBinder // Splitter AssignTo **walk.Splitter HandleWidth int } func (s HSplitter) Create(builder *Builder) error { w, err := walk.NewHSplitter(builder.Parent()) if err != nil { return err } if s.AssignTo != nil { *s.AssignTo = w } w.SetSuspended(true) builder.Defer(func() error { w.SetSuspended(false) return nil }) return builder.InitWidget(s, w, func() error { if s.HandleWidth > 0 { if err := w.SetHandleWidth(s.HandleWidth); err != nil { return err } } return nil }) } type VSplitter struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget AlwaysConsumeSpace bool Column int ColumnSpan int Row int RowSpan int StretchFactor int // Container Children []Widget DataBinder DataBinder // Splitter AssignTo **walk.Splitter HandleWidth int } func (s VSplitter) Create(builder *Builder) error { w, err := walk.NewVSplitter(builder.Parent()) if err != nil { return err } if s.AssignTo != nil { *s.AssignTo = w } w.SetSuspended(true) builder.Defer(func() error { w.SetSuspended(false) return nil }) return builder.InitWidget(s, w, func() error { if s.HandleWidth > 0 { if err := w.SetHandleWidth(s.HandleWidth); err != nil { return err } } return nil }) } ================================================ FILE: declarative/tableview.go ================================================ // Copyright 2012 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 declarative import ( "github.com/lxn/walk" "github.com/lxn/win" ) type TableView struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // TableView AlternatingRowBG bool AssignTo **walk.TableView CellStyler walk.CellStyler CheckBoxes bool Columns []TableViewColumn ColumnsOrderable Property ColumnsSizable Property CustomHeaderHeight int CustomRowHeight int ItemStateChangedEventDelay int HeaderHidden bool LastColumnStretched bool Model interface{} MultiSelection bool NotSortableByHeaderClick bool OnCurrentIndexChanged walk.EventHandler OnItemActivated walk.EventHandler OnSelectedIndexesChanged walk.EventHandler SelectionHiddenWithoutFocus bool StyleCell func(style *walk.CellStyle) } type tvStyler struct { dflt walk.CellStyler colStyleCellFuncs []func(style *walk.CellStyle) } func (tvs *tvStyler) StyleCell(style *walk.CellStyle) { if tvs.dflt != nil { tvs.dflt.StyleCell(style) } if col := style.Col(); col >= 0 { if styleCell := tvs.colStyleCellFuncs[col]; styleCell != nil { styleCell(style) } } } type styleCellFunc func(style *walk.CellStyle) func (scf styleCellFunc) StyleCell(style *walk.CellStyle) { scf(style) } func (tv TableView) Create(builder *Builder) error { var w *walk.TableView var err error if tv.NotSortableByHeaderClick { w, err = walk.NewTableViewWithStyle(builder.Parent(), win.LVS_NOSORTHEADER) } else { w, err = walk.NewTableViewWithCfg(builder.Parent(), &walk.TableViewCfg{CustomHeaderHeight: tv.CustomHeaderHeight, CustomRowHeight: tv.CustomRowHeight}) } if err != nil { return err } if tv.AssignTo != nil { *tv.AssignTo = w } return builder.InitWidget(tv, w, func() error { for i := range tv.Columns { if err := tv.Columns[i].Create(w); err != nil { return err } } if err := w.SetModel(tv.Model); err != nil { return err } defaultStyler, _ := tv.Model.(walk.CellStyler) if tv.CellStyler != nil { defaultStyler = tv.CellStyler } if tv.StyleCell != nil { defaultStyler = styleCellFunc(tv.StyleCell) } var hasColStyleFunc bool for _, c := range tv.Columns { if c.StyleCell != nil { hasColStyleFunc = true break } } if defaultStyler != nil || hasColStyleFunc { var styler walk.CellStyler if hasColStyleFunc { tvs := &tvStyler{ dflt: defaultStyler, colStyleCellFuncs: make([]func(style *walk.CellStyle), len(tv.Columns)), } styler = tvs for i, c := range tv.Columns { tvs.colStyleCellFuncs[i] = c.StyleCell } } else { styler = defaultStyler } w.SetCellStyler(styler) } w.SetAlternatingRowBG(tv.AlternatingRowBG) w.SetCheckBoxes(tv.CheckBoxes) w.SetItemStateChangedEventDelay(tv.ItemStateChangedEventDelay) if err := w.SetLastColumnStretched(tv.LastColumnStretched); err != nil { return err } if err := w.SetMultiSelection(tv.MultiSelection); err != nil { return err } if err := w.SetSelectionHiddenWithoutFocus(tv.SelectionHiddenWithoutFocus); err != nil { return err } if err := w.SetHeaderHidden(tv.HeaderHidden); err != nil { return err } if tv.OnCurrentIndexChanged != nil { w.CurrentIndexChanged().Attach(tv.OnCurrentIndexChanged) } if tv.OnSelectedIndexesChanged != nil { w.SelectedIndexesChanged().Attach(tv.OnSelectedIndexesChanged) } if tv.OnItemActivated != nil { w.ItemActivated().Attach(tv.OnItemActivated) } return nil }) } ================================================ FILE: declarative/tableviewcolumn.go ================================================ // Copyright 2013 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 declarative import ( "github.com/lxn/walk" ) type Alignment1D uint const ( AlignDefault = Alignment1D(walk.AlignDefault) AlignNear = Alignment1D(walk.AlignNear) AlignCenter = Alignment1D(walk.AlignCenter) AlignFar = Alignment1D(walk.AlignFar) ) type TableViewColumn struct { Name string DataMember string Format string Title string Alignment Alignment1D Precision int Width int Hidden bool Frozen bool StyleCell func(style *walk.CellStyle) LessFunc func(i, j int) bool FormatFunc func(value interface{}) string } func (tvc TableViewColumn) Create(tv *walk.TableView) error { w := walk.NewTableViewColumn() if err := w.SetAlignment(walk.Alignment1D(tvc.Alignment)); err != nil { return err } w.SetDataMember(tvc.DataMember) if tvc.Format != "" { if err := w.SetFormat(tvc.Format); err != nil { return err } } if err := w.SetPrecision(tvc.Precision); err != nil { return err } w.SetName(tvc.Name) if err := w.SetTitle(tvc.Title); err != nil { return err } if err := w.SetVisible(!tvc.Hidden); err != nil { return err } if err := w.SetFrozen(tvc.Frozen); err != nil { return err } if err := w.SetWidth(tvc.Width); err != nil { return err } w.SetLessFunc(tvc.LessFunc) w.SetFormatFunc(tvc.FormatFunc) return tv.Columns().Add(w) } ================================================ FILE: declarative/tabpage.go ================================================ // Copyright 2012 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 declarative import ( "github.com/lxn/walk" ) type TabPage struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // Container Children []Widget DataBinder DataBinder Layout Layout // TabPage AssignTo **walk.TabPage Content Widget Image Property Title Property } func (tp TabPage) Create(builder *Builder) error { w, err := walk.NewTabPage() if err != nil { return err } if tp.AssignTo != nil { *tp.AssignTo = w } return builder.InitWidget(tp, w, func() error { if tp.Content != nil && len(tp.Children) == 0 { if err := tp.Content.Create(builder); err != nil { return err } } return nil }) } ================================================ FILE: declarative/tabwidget.go ================================================ // Copyright 2012 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 declarative import ( "github.com/lxn/walk" ) type TabWidget struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // TabWidget AssignTo **walk.TabWidget ContentMargins Margins ContentMarginsZero bool OnCurrentIndexChanged walk.EventHandler Pages []TabPage } func (tw TabWidget) Create(builder *Builder) error { w, err := walk.NewTabWidget(builder.Parent()) if err != nil { return err } if tw.AssignTo != nil { *tw.AssignTo = w } return builder.InitWidget(tw, w, func() error { for _, tp := range tw.Pages { var wp *walk.TabPage if tp.AssignTo == nil { tp.AssignTo = &wp } if tp.Content != nil && len(tp.Children) == 0 { tp.Layout = HBox{Margins: tw.ContentMargins, MarginsZero: tw.ContentMarginsZero} } if err := tp.Create(builder); err != nil { return err } if err := w.Pages().Add(*tp.AssignTo); err != nil { return err } } if tw.OnCurrentIndexChanged != nil { w.CurrentIndexChanged().Attach(tw.OnCurrentIndexChanged) } return nil }) } ================================================ FILE: declarative/textedit.go ================================================ // Copyright 2012 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 declarative import ( "github.com/lxn/walk" "github.com/lxn/win" ) type TextEdit struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // TextEdit AssignTo **walk.TextEdit CompactHeight bool HScroll bool MaxLength int OnTextChanged walk.EventHandler ReadOnly Property Text Property TextAlignment Alignment1D TextColor walk.Color VScroll bool } func (te TextEdit) Create(builder *Builder) error { var style uint32 if te.HScroll { style |= win.WS_HSCROLL } if te.VScroll { style |= win.WS_VSCROLL } w, err := walk.NewTextEditWithStyle(builder.Parent(), style) if err != nil { return err } if te.AssignTo != nil { *te.AssignTo = w } return builder.InitWidget(te, w, func() error { w.SetCompactHeight(te.CompactHeight) w.SetTextColor(te.TextColor) if err := w.SetTextAlignment(walk.Alignment1D(te.TextAlignment)); err != nil { return err } if te.MaxLength > 0 { w.SetMaxLength(te.MaxLength) } if te.OnTextChanged != nil { w.TextChanged().Attach(te.OnTextChanged) } return nil }) } ================================================ FILE: declarative/textlabel.go ================================================ // Copyright 2018 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 declarative import ( "github.com/lxn/walk" "github.com/lxn/win" ) type Alignment2D uint const ( AlignHVDefault = Alignment2D(walk.AlignHVDefault) AlignHNearVNear = Alignment2D(walk.AlignHNearVNear) AlignHCenterVNear = Alignment2D(walk.AlignHCenterVNear) AlignHFarVNear = Alignment2D(walk.AlignHFarVNear) AlignHNearVCenter = Alignment2D(walk.AlignHNearVCenter) AlignHCenterVCenter = Alignment2D(walk.AlignHCenterVCenter) AlignHFarVCenter = Alignment2D(walk.AlignHFarVCenter) AlignHNearVFar = Alignment2D(walk.AlignHNearVFar) AlignHCenterVFar = Alignment2D(walk.AlignHCenterVFar) AlignHFarVFar = Alignment2D(walk.AlignHFarVFar) ) type TextLabel struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size // Set MinSize.Width to a value > 0 to enable dynamic line wrapping. Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // static TextColor walk.Color // Text AssignTo **walk.TextLabel NoPrefix bool TextAlignment Alignment2D Text Property } func (tl TextLabel) Create(builder *Builder) error { var style uint32 if tl.NoPrefix { style |= win.SS_NOPREFIX } w, err := walk.NewTextLabelWithStyle(builder.Parent(), style) if err != nil { return err } if tl.AssignTo != nil { *tl.AssignTo = w } return builder.InitWidget(tl, w, func() error { w.SetTextColor(tl.TextColor) if err := w.SetTextAlignment(walk.Alignment2D(tl.TextAlignment)); err != nil { return err } return nil }) } ================================================ FILE: declarative/toolbar.go ================================================ // Copyright 2012 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 declarative import ( "github.com/lxn/walk" ) type ToolBarButtonStyle int const ( ToolBarButtonImageOnly ToolBarButtonStyle = iota ToolBarButtonTextOnly ToolBarButtonImageBeforeText ToolBarButtonImageAboveText ) type ToolBar struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // ToolBar Actions []*walk.Action // Deprecated, use Items instead AssignTo **walk.ToolBar ButtonStyle ToolBarButtonStyle Items []MenuItem MaxTextRows int Orientation Orientation } func (tb ToolBar) Create(builder *Builder) error { w, err := walk.NewToolBarWithOrientationAndButtonStyle(builder.Parent(), walk.Orientation(tb.Orientation), walk.ToolBarButtonStyle(tb.ButtonStyle)) if err != nil { return err } if tb.AssignTo != nil { *tb.AssignTo = w } return builder.InitWidget(tb, w, func() error { imageList, err := walk.NewImageList(walk.Size{16, 16}, 0) if err != nil { return err } w.SetImageList(imageList) mtr := tb.MaxTextRows if mtr < 1 { mtr = 1 } if err := w.SetMaxTextRows(mtr); err != nil { return err } if len(tb.Items) > 0 { builder.deferBuildActions(w.Actions(), tb.Items) } else { if err := addToActionList(w.Actions(), tb.Actions); err != nil { return err } } return nil }) } ================================================ FILE: declarative/toolbutton.go ================================================ // Copyright 2012 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 declarative import ( "github.com/lxn/walk" ) type ToolButton struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // Button Image Property OnClicked walk.EventHandler Text Property // ToolButton AssignTo **walk.ToolButton } func (tb ToolButton) Create(builder *Builder) error { w, err := walk.NewToolButton(builder.Parent()) if err != nil { return err } if tb.AssignTo != nil { *tb.AssignTo = w } return builder.InitWidget(tb, w, func() error { if tb.OnClicked != nil { w.Clicked().Attach(tb.OnClicked) } return nil }) } ================================================ FILE: declarative/treeview.go ================================================ // Copyright 2012 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 declarative import ( "github.com/lxn/walk" ) type TreeView struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // TreeView AssignTo **walk.TreeView ItemHeight int Model walk.TreeModel OnCurrentItemChanged walk.EventHandler OnExpandedChanged walk.TreeItemEventHandler OnItemActivated walk.EventHandler } func (tv TreeView) Create(builder *Builder) error { w, err := walk.NewTreeView(builder.Parent()) if err != nil { return err } if tv.AssignTo != nil { *tv.AssignTo = w } return builder.InitWidget(tv, w, func() error { if tv.ItemHeight > 0 { w.SetItemHeight(w.IntFrom96DPI(tv.ItemHeight)) // VERIFY: Item height should resize on DPI change. } if err := w.SetModel(tv.Model); err != nil { return err } if tv.OnCurrentItemChanged != nil { w.CurrentItemChanged().Attach(tv.OnCurrentItemChanged) } if tv.OnExpandedChanged != nil { w.ExpandedChanged().Attach(tv.OnExpandedChanged) } if tv.OnItemActivated != nil { w.ItemActivated().Attach(tv.OnItemActivated) } return nil }) } ================================================ FILE: declarative/validators.go ================================================ // Copyright 2012 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 declarative import ( "github.com/lxn/walk" ) type ValidatorRef struct { Validator walk.Validator } func (vr ValidatorRef) Create() (walk.Validator, error) { return vr.Validator, nil } type Range struct { Min float64 Max float64 } func (r Range) Create() (walk.Validator, error) { return walk.NewRangeValidator(r.Min, r.Max) } type Regexp struct { Pattern string } func (re Regexp) Create() (walk.Validator, error) { return walk.NewRegexpValidator(re.Pattern) } type SelRequired struct { } func (SelRequired) Create() (walk.Validator, error) { return walk.SelectionRequiredValidator(), nil } type dMultiValidator struct { validators []Validator } func (av dMultiValidator) Create() (walk.Validator, error) { var validators []walk.Validator for _, dv := range av.validators { if wv, err := dv.Create(); err != nil { return nil, err } else { validators = append(validators, wv) } } return &wMultiValidator{validators}, nil } type wMultiValidator struct { validators []walk.Validator } func (av *wMultiValidator) Validate(v interface{}) error { for _, validator := range av.validators { if err := validator.Validate(v); err != nil { return err } } return nil } ================================================ FILE: declarative/webview.go ================================================ // Copyright 2012 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 declarative import ( "github.com/lxn/walk" ) type WebView struct { // Window Accessibility Accessibility Background Brush ContextMenuItems []MenuItem DoubleBuffering bool Enabled Property Font Font MaxSize Size MinSize Size Name string OnBoundsChanged walk.EventHandler OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler Persistent bool RightToLeftReading bool ToolTipText Property Visible Property // Widget Alignment Alignment2D AlwaysConsumeSpace bool Column int ColumnSpan int GraphicsEffects []walk.WidgetGraphicsEffect Row int RowSpan int StretchFactor int // WebView AssignTo **walk.WebView NativeContextMenuEnabled Property OnBrowserVisibleChanged walk.EventHandler OnCanGoBackChanged walk.EventHandler OnCanGoForwardChanged walk.EventHandler OnDocumentCompleted walk.StringEventHandler OnDocumentTitleChanged walk.EventHandler OnDownloaded walk.EventHandler OnDownloading walk.EventHandler OnNativeContextMenuEnabledChanged walk.EventHandler OnNavigated walk.StringEventHandler OnNavigatedError walk.WebViewNavigatedErrorEventHandler OnNavigating walk.WebViewNavigatingEventHandler OnNewWindow walk.WebViewNewWindowEventHandler OnProgressChanged walk.EventHandler OnQuitting walk.EventHandler OnShortcutsEnabledChanged walk.EventHandler OnStatusBarVisibleChanged walk.EventHandler OnStatusTextChanged walk.EventHandler OnTheaterModeChanged walk.EventHandler OnToolBarEnabledChanged walk.EventHandler OnToolBarVisibleChanged walk.EventHandler OnURLChanged walk.EventHandler OnWindowClosing walk.WebViewWindowClosingEventHandler ShortcutsEnabled Property URL Property } func (wv WebView) Create(builder *Builder) error { w, err := walk.NewWebView(builder.Parent()) if err != nil { return err } if wv.AssignTo != nil { *wv.AssignTo = w } return builder.InitWidget(wv, w, func() error { if wv.OnBrowserVisibleChanged != nil { w.BrowserVisibleChanged().Attach(wv.OnBrowserVisibleChanged) } if wv.OnCanGoBackChanged != nil { w.CanGoBackChanged().Attach(wv.OnCanGoBackChanged) } if wv.OnCanGoForwardChanged != nil { w.CanGoForwardChanged().Attach(wv.OnCanGoForwardChanged) } if wv.OnDocumentCompleted != nil { w.DocumentCompleted().Attach(wv.OnDocumentCompleted) } if wv.OnDocumentTitleChanged != nil { w.DocumentTitleChanged().Attach(wv.OnDocumentTitleChanged) } if wv.OnDownloaded != nil { w.Downloaded().Attach(wv.OnDownloaded) } if wv.OnDownloading != nil { w.Downloading().Attach(wv.OnDownloading) } if wv.OnNativeContextMenuEnabledChanged != nil { w.NativeContextMenuEnabledChanged().Attach(wv.OnNativeContextMenuEnabledChanged) } if wv.OnNavigated != nil { w.Navigated().Attach(wv.OnNavigated) } if wv.OnNavigatedError != nil { w.NavigatedError().Attach(wv.OnNavigatedError) } if wv.OnNavigating != nil { w.Navigating().Attach(wv.OnNavigating) } if wv.OnNewWindow != nil { w.NewWindow().Attach(wv.OnNewWindow) } if wv.OnProgressChanged != nil { w.ProgressChanged().Attach(wv.OnProgressChanged) } if wv.OnURLChanged != nil { w.URLChanged().Attach(wv.OnURLChanged) } if wv.OnShortcutsEnabledChanged != nil { w.ShortcutsEnabledChanged().Attach(wv.OnShortcutsEnabledChanged) } if wv.OnStatusBarVisibleChanged != nil { w.StatusBarVisibleChanged().Attach(wv.OnStatusBarVisibleChanged) } if wv.OnStatusTextChanged != nil { w.StatusTextChanged().Attach(wv.OnStatusTextChanged) } if wv.OnTheaterModeChanged != nil { w.TheaterModeChanged().Attach(wv.OnTheaterModeChanged) } if wv.OnToolBarEnabledChanged != nil { w.ToolBarEnabledChanged().Attach(wv.OnToolBarEnabledChanged) } if wv.OnToolBarVisibleChanged != nil { w.ToolBarVisibleChanged().Attach(wv.OnToolBarVisibleChanged) } if wv.OnQuitting != nil { w.Quitting().Attach(wv.OnQuitting) } if wv.OnWindowClosing != nil { w.WindowClosing().Attach(wv.OnWindowClosing) } return nil }) } ================================================ FILE: dialog.go ================================================ // 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 ( "unsafe" "github.com/lxn/win" ) const ( DlgCmdNone = 0 DlgCmdOK = win.IDOK DlgCmdCancel = win.IDCANCEL DlgCmdAbort = win.IDABORT DlgCmdRetry = win.IDRETRY DlgCmdIgnore = win.IDIGNORE DlgCmdYes = win.IDYES DlgCmdNo = win.IDNO DlgCmdClose = win.IDCLOSE DlgCmdHelp = win.IDHELP DlgCmdTryAgain = win.IDTRYAGAIN DlgCmdContinue = win.IDCONTINUE DlgCmdTimeout = win.IDTIMEOUT ) const dialogWindowClass = `\o/ Walk_Dialog_Class \o/` func init() { AppendToWalkInit(func() { MustRegisterWindowClass(dialogWindowClass) }) } type dialogish interface { DefaultButton() *PushButton CancelButton() *PushButton } type Dialog struct { FormBase result int defaultButton *PushButton cancelButton *PushButton centerInOwnerWhenRun bool } func NewDialog(owner Form) (*Dialog, error) { return newDialogWithStyle(owner, win.WS_THICKFRAME) } func NewDialogWithFixedSize(owner Form) (*Dialog, error) { return newDialogWithStyle(owner, 0) } func newDialogWithStyle(owner Form, style uint32) (*Dialog, error) { dlg := &Dialog{ FormBase: FormBase{ owner: owner, }, } if err := InitWindow( dlg, owner, dialogWindowClass, win.WS_CAPTION|win.WS_SYSMENU|style, 0); err != nil { return nil, err } succeeded := false defer func() { if !succeeded { dlg.Dispose() } }() dlg.centerInOwnerWhenRun = owner != nil dlg.result = DlgCmdNone succeeded = true return dlg, nil } func (dlg *Dialog) DefaultButton() *PushButton { return dlg.defaultButton } func (dlg *Dialog) SetDefaultButton(button *PushButton) error { if button != nil && !win.IsChild(dlg.hWnd, button.hWnd) { return newError("not a descendant of the dialog") } succeeded := false if dlg.defaultButton != nil { if err := dlg.defaultButton.setAndClearStyleBits(win.BS_PUSHBUTTON, win.BS_DEFPUSHBUTTON); err != nil { return err } defer func() { if !succeeded { dlg.defaultButton.setAndClearStyleBits(win.BS_DEFPUSHBUTTON, win.BS_PUSHBUTTON) } }() } if button != nil { if err := button.setAndClearStyleBits(win.BS_DEFPUSHBUTTON, win.BS_PUSHBUTTON); err != nil { return err } } dlg.defaultButton = button succeeded = true return nil } func (dlg *Dialog) CancelButton() *PushButton { return dlg.cancelButton } func (dlg *Dialog) SetCancelButton(button *PushButton) error { if button != nil && !win.IsChild(dlg.hWnd, button.hWnd) { return newError("not a descendant of the dialog") } dlg.cancelButton = button return nil } func (dlg *Dialog) Result() int { return dlg.result } func (dlg *Dialog) Accept() { dlg.Close(DlgCmdOK) } func (dlg *Dialog) Cancel() { dlg.Close(DlgCmdCancel) } func (dlg *Dialog) Close(result int) { dlg.result = result dlg.FormBase.Close() } func (dlg *Dialog) Show() { var willRestore bool if dlg.Persistent() { state, _ := dlg.ReadState() willRestore = state != "" } if !willRestore { var size Size if layout := dlg.Layout(); layout != nil { size = maxSize(dlg.clientComposite.MinSizeHint(), dlg.MinSizePixels()) } else { size = dlg.SizePixels() } if dlg.owner != nil { ob := dlg.owner.BoundsPixels() if dlg.centerInOwnerWhenRun { dlg.SetBoundsPixels(fitRectToScreen(dlg.hWnd, Rectangle{ ob.X + (ob.Width-size.Width)/2, ob.Y + (ob.Height-size.Height)/2, size.Width, size.Height, })) } } else { b := dlg.BoundsPixels() dlg.SetBoundsPixels(Rectangle{b.X, b.Y, size.Width, size.Height}) } } dlg.FormBase.Show() dlg.startLayout() } // fitRectToScreen fits rectangle to screen. Input and output rectangles are in native pixels. func fitRectToScreen(hWnd win.HWND, r Rectangle) Rectangle { var mi win.MONITORINFO mi.CbSize = uint32(unsafe.Sizeof(mi)) if !win.GetMonitorInfo(win.MonitorFromWindow( hWnd, win.MONITOR_DEFAULTTOPRIMARY), &mi) { return r } mon := rectangleFromRECT(mi.RcWork) dpi := win.GetDpiForWindow(hWnd) mon.Height -= int(win.GetSystemMetricsForDpi(win.SM_CYCAPTION, dpi)) if r.Width <= mon.Width { switch { case r.X < mon.X: r.X = mon.X case r.X+r.Width > mon.X+mon.Width: r.X = mon.X + mon.Width - r.Width } } if r.Height <= mon.Height { switch { case r.Y < mon.Y: r.Y = mon.Y case r.Y+r.Height > mon.Y+mon.Height: r.Y = mon.Y + mon.Height - r.Height } } return r } func (dlg *Dialog) Run() int { dlg.Show() dlg.FormBase.Run() return dlg.result } func (dlg *Dialog) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_COMMAND: if win.HIWORD(uint32(wParam)) == 0 { switch win.LOWORD(uint32(wParam)) { case DlgCmdOK: if dlg.defaultButton != nil { dlg.defaultButton.raiseClicked() } case DlgCmdCancel: if dlg.cancelButton != nil { dlg.cancelButton.raiseClicked() } } } } return dlg.FormBase.WndProc(hwnd, msg, wParam, lParam) } ================================================ FILE: dropfilesevent.go ================================================ // Copyright 2011 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" "github.com/lxn/win" ) type dropFilesEventHandlerInfo struct { handler DropFilesEventHandler once bool } type DropFilesEventHandler func([]string) type DropFilesEvent struct { hWnd win.HWND handlers []dropFilesEventHandlerInfo } func (e *DropFilesEvent) Attach(handler DropFilesEventHandler) int { if len(e.handlers) == 0 { win.DragAcceptFiles(e.hWnd, true) } handlerInfo := dropFilesEventHandlerInfo{handler, false} for i, h := range e.handlers { if h.handler == nil { e.handlers[i] = handlerInfo return i } } e.handlers = append(e.handlers, handlerInfo) return len(e.handlers) - 1 } func (e *DropFilesEvent) Detach(handle int) { e.handlers[handle].handler = nil for _, h := range e.handlers { if h.handler != nil { return } } win.DragAcceptFiles(e.hWnd, false) } func (e *DropFilesEvent) Once(handler DropFilesEventHandler) { i := e.Attach(handler) e.handlers[i].once = true } type DropFilesEventPublisher struct { event DropFilesEvent } func (p *DropFilesEventPublisher) Event(hWnd win.HWND) *DropFilesEvent { p.event.hWnd = hWnd return &p.event } func (p *DropFilesEventPublisher) Publish(hDrop win.HDROP) { var files []string n := win.DragQueryFile(hDrop, 0xFFFFFFFF, nil, 0) for i := 0; i < int(n); i++ { bufSize := uint(512) buf := make([]uint16, bufSize) if win.DragQueryFile(hDrop, uint(i), &buf[0], bufSize) > 0 { files = append(files, syscall.UTF16ToString(buf)) } } win.DragFinish(hDrop) for i, h := range p.event.handlers { if h.handler != nil { h.handler(files) if h.once { p.event.Detach(i) } } } } ================================================ FILE: error.go ================================================ // Copyright 2011 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 ( "fmt" "log" "runtime/debug" ) import ( "github.com/lxn/win" ) var ( logErrors bool panicOnError bool ) type Error struct { inner error message string stack []byte } func (err *Error) Inner() error { return err.inner } func (err *Error) Message() string { if err.message != "" { return err.message } if err.inner != nil { if walkErr, ok := err.inner.(*Error); ok { return walkErr.Message() } else { return err.inner.Error() } } return "" } func (err *Error) Stack() []byte { return err.stack } func (err *Error) Error() string { return fmt.Sprintf("%s\n\nStack:\n%s", err.Message(), err.stack) } func processErrorNoPanic(err error) error { if logErrors { if walkErr, ok := err.(*Error); ok { log.Print(walkErr.Error()) } else { log.Printf("%s\n\nStack:\n%s", err, debug.Stack()) } } return err } func processError(err error) error { processErrorNoPanic(err) if panicOnError { panic(err) } return err } func newErr(message string) error { return &Error{message: message, stack: debug.Stack()} } func newError(message string) error { return processError(newErr(message)) } func newErrorNoPanic(message string) error { return processErrorNoPanic(newErr(message)) } func lastError(win32FuncName string) error { if errno := win.GetLastError(); errno != win.ERROR_SUCCESS { return newError(fmt.Sprintf("%s: Error %d", win32FuncName, errno)) } return newError(win32FuncName) } func errorFromHRESULT(funcName string, hr win.HRESULT) error { return newError(fmt.Sprintf("%s: Error %d", funcName, hr)) } func wrapErr(err error) error { if _, ok := err.(*Error); ok { return err } return &Error{inner: err, stack: debug.Stack()} } func wrapErrorNoPanic(err error) error { return processErrorNoPanic(wrapErr(err)) } func wrapError(err error) error { return processError(wrapErr(err)) } func toErrorNoPanic(x interface{}) error { switch x := x.(type) { case *Error: return x case error: return wrapErrorNoPanic(x) case string: return newErrorNoPanic(x) } return newErrorNoPanic(fmt.Sprintf("Error: %v", x)) } func toError(x interface{}) error { err := toErrorNoPanic(x) if panicOnError { panic(err) } return err } ================================================ FILE: errorevent.go ================================================ // Copyright 2011 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 type errorEventHandlerInfo struct { handler ErrorEventHandler once bool } type ErrorEventHandler func(err error) type ErrorEvent struct { handlers []errorEventHandlerInfo } func (e *ErrorEvent) Attach(handler ErrorEventHandler) int { handlerInfo := errorEventHandlerInfo{handler, false} for i, h := range e.handlers { if h.handler == nil { e.handlers[i] = handlerInfo return i } } e.handlers = append(e.handlers, handlerInfo) return len(e.handlers) - 1 } func (e *ErrorEvent) Detach(handle int) { e.handlers[handle].handler = nil } func (e *ErrorEvent) Once(handler ErrorEventHandler) { i := e.Attach(handler) e.handlers[i].once = true } type ErrorEventPublisher struct { event ErrorEvent } func (p *ErrorEventPublisher) Event() *ErrorEvent { return &p.event } func (p *ErrorEventPublisher) Publish(err error) { for i, h := range p.event.handlers { if h.handler != nil { h.handler(err) if h.once { p.event.Detach(i) } } } } ================================================ FILE: event.go ================================================ // Copyright 2011 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 type eventHandlerInfo struct { handler EventHandler once bool } type EventHandler func() type Event struct { handlers []eventHandlerInfo } func (e *Event) Attach(handler EventHandler) int { handlerInfo := eventHandlerInfo{handler, false} for i, h := range e.handlers { if h.handler == nil { e.handlers[i] = handlerInfo return i } } e.handlers = append(e.handlers, handlerInfo) return len(e.handlers) - 1 } func (e *Event) Detach(handle int) { e.handlers[handle].handler = nil } func (e *Event) Once(handler EventHandler) { i := e.Attach(handler) e.handlers[i].once = true } type EventPublisher struct { event Event } func (p *EventPublisher) Event() *Event { return &p.event } func (p *EventPublisher) Publish() { // This is a kludge to find the form that the event publisher is // affiliated with. It's only necessary because the event publisher // doesn't keep a pointer to the form on its own, and the call // to Publish isn't providing it either. if form := App().ActiveForm(); form != nil { fb := form.AsFormBase() fb.inProgressEventCount++ defer func() { fb.inProgressEventCount-- if fb.inProgressEventCount == 0 && fb.layoutScheduled { fb.layoutScheduled = false fb.startLayout() } }() } for i, h := range p.event.handlers { if h.handler != nil { h.handler() if h.once { p.event.Detach(i) } } } } ================================================ FILE: examples/actions/actions.exe.manifest ================================================  PerMonitorV2, PerMonitor True ================================================ FILE: examples/actions/actions.go ================================================ // Copyright 2013 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. package main import ( "log" ) import ( "github.com/lxn/walk" . "github.com/lxn/walk/declarative" ) var isSpecialMode = walk.NewMutableCondition() type MyMainWindow struct { *walk.MainWindow } func main() { MustRegisterCondition("isSpecialMode", isSpecialMode) mw := new(MyMainWindow) var openAction, showAboutBoxAction *walk.Action var recentMenu *walk.Menu var toggleSpecialModePB *walk.PushButton if err := (MainWindow{ AssignTo: &mw.MainWindow, Title: "Walk Actions Example", MenuItems: []MenuItem{ Menu{ Text: "&File", Items: []MenuItem{ Action{ AssignTo: &openAction, Text: "&Open", Image: "../img/open.png", Enabled: Bind("enabledCB.Checked"), Visible: Bind("!openHiddenCB.Checked"), Shortcut: Shortcut{walk.ModControl, walk.KeyO}, OnTriggered: mw.openAction_Triggered, }, Menu{ AssignTo: &recentMenu, Text: "Recent", }, Separator{}, Action{ Text: "E&xit", OnTriggered: func() { mw.Close() }, }, }, }, Menu{ Text: "&View", Items: []MenuItem{ Action{ Text: "Open / Special Enabled", Checked: Bind("enabledCB.Visible"), }, Action{ Text: "Open Hidden", Checked: Bind("openHiddenCB.Visible"), }, }, }, Menu{ Text: "&Help", Items: []MenuItem{ Action{ AssignTo: &showAboutBoxAction, Text: "About", OnTriggered: mw.showAboutBoxAction_Triggered, }, }, }, }, ToolBar: ToolBar{ ButtonStyle: ToolBarButtonImageBeforeText, Items: []MenuItem{ ActionRef{&openAction}, Menu{ Text: "New A", Image: "../img/document-new.png", Items: []MenuItem{ Action{ Text: "A", OnTriggered: mw.newAction_Triggered, }, Action{ Text: "B", OnTriggered: mw.newAction_Triggered, }, Action{ Text: "C", OnTriggered: mw.newAction_Triggered, }, }, OnTriggered: mw.newAction_Triggered, }, Separator{}, Menu{ Text: "View", Image: "../img/document-properties.png", Items: []MenuItem{ Action{ Text: "X", OnTriggered: mw.changeViewAction_Triggered, }, Action{ Text: "Y", OnTriggered: mw.changeViewAction_Triggered, }, Action{ Text: "Z", OnTriggered: mw.changeViewAction_Triggered, }, }, }, Separator{}, Action{ Text: "Special", Image: "../img/system-shutdown.png", Enabled: Bind("isSpecialMode && enabledCB.Checked"), OnTriggered: mw.specialAction_Triggered, }, }, }, ContextMenuItems: []MenuItem{ ActionRef{&showAboutBoxAction}, }, MinSize: Size{300, 200}, Layout: VBox{}, Children: []Widget{ CheckBox{ Name: "enabledCB", Text: "Open / Special Enabled", Checked: true, Accessibility: Accessibility{ Help: "Enables Open and Special", }, }, CheckBox{ Name: "openHiddenCB", Text: "Open Hidden", Checked: true, }, PushButton{ AssignTo: &toggleSpecialModePB, Text: "Enable Special Mode", OnClicked: func() { isSpecialMode.SetSatisfied(!isSpecialMode.Satisfied()) if isSpecialMode.Satisfied() { toggleSpecialModePB.SetText("Disable Special Mode") } else { toggleSpecialModePB.SetText("Enable Special Mode") } }, Accessibility: Accessibility{ Help: "Toggles special mode", }, }, }, }.Create()); err != nil { log.Fatal(err) } addRecentFileActions := func(texts ...string) { for _, text := range texts { a := walk.NewAction() a.SetText(text) a.Triggered().Attach(mw.openAction_Triggered) recentMenu.Actions().Add(a) } } addRecentFileActions("Foo", "Bar", "Baz") mw.Run() } func (mw *MyMainWindow) openAction_Triggered() { walk.MsgBox(mw, "Open", "Pretend to open a file...", walk.MsgBoxIconInformation) } func (mw *MyMainWindow) newAction_Triggered() { walk.MsgBox(mw, "New", "Newing something up... or not.", walk.MsgBoxIconInformation) } func (mw *MyMainWindow) changeViewAction_Triggered() { walk.MsgBox(mw, "Change View", "By now you may have guessed it. Nothing changed.", walk.MsgBoxIconInformation) } func (mw *MyMainWindow) showAboutBoxAction_Triggered() { walk.MsgBox(mw, "About", "Walk Actions Example", walk.MsgBoxIconInformation) } func (mw *MyMainWindow) specialAction_Triggered() { walk.MsgBox(mw, "Special", "Nothing to see here.", walk.MsgBoxIconInformation) } ================================================ FILE: examples/clipboard/clipboard.exe.manifest ================================================  PerMonitorV2, PerMonitor True ================================================ FILE: examples/clipboard/clipboard.go ================================================ // Copyright 2013 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. package main import ( "log" ) import ( "github.com/lxn/walk" . "github.com/lxn/walk/declarative" ) func main() { var te *walk.TextEdit if _, err := (MainWindow{ Title: "Walk Clipboard Example", MinSize: Size{300, 200}, Layout: VBox{}, Children: []Widget{ PushButton{ Text: "Copy", OnClicked: func() { if err := walk.Clipboard().SetText(te.Text()); err != nil { log.Print("Copy: ", err) } }, }, PushButton{ Text: "Paste", OnClicked: func() { if text, err := walk.Clipboard().Text(); err != nil { log.Print("Paste: ", err) } else { te.SetText(text) } }, }, TextEdit{ AssignTo: &te, }, }, }).Run(); err != nil { log.Fatal(err) } } ================================================ FILE: examples/databinding/databinding.exe.manifest ================================================  PerMonitorV2, PerMonitor True ================================================ FILE: examples/databinding/databinding.go ================================================ // Copyright 2013 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. package main import ( "fmt" "log" "time" "github.com/lxn/walk" . "github.com/lxn/walk/declarative" ) func main() { walk.AppendToWalkInit(func() { walk.FocusEffect, _ = walk.NewBorderGlowEffect(walk.RGB(0, 63, 255)) walk.InteractionEffect, _ = walk.NewDropShadowEffect(walk.RGB(63, 63, 63)) walk.ValidationErrorEffect, _ = walk.NewBorderGlowEffect(walk.RGB(255, 0, 0)) }) var mw *walk.MainWindow var outTE *walk.TextEdit animal := new(Animal) if _, err := (MainWindow{ AssignTo: &mw, Title: "Walk Data Binding Example", MinSize: Size{300, 200}, Layout: VBox{}, Children: []Widget{ PushButton{ Text: "Edit Animal", OnClicked: func() { if cmd, err := RunAnimalDialog(mw, animal); err != nil { log.Print(err) } else if cmd == walk.DlgCmdOK { outTE.SetText(fmt.Sprintf("%+v", animal)) } }, }, Label{ Text: "animal:", }, TextEdit{ AssignTo: &outTE, ReadOnly: true, Text: fmt.Sprintf("%+v", animal), }, }, }.Run()); err != nil { log.Fatal(err) } } type Animal struct { Name string ArrivalDate time.Time SpeciesId int Speed int Sex Sex Weight float64 PreferredFood string Domesticated bool Remarks string Patience time.Duration } func (a *Animal) PatienceField() *DurationField { return &DurationField{&a.Patience} } type Species struct { Id int Name string } func KnownSpecies() []*Species { return []*Species{ {1, "Dog"}, {2, "Cat"}, {3, "Bird"}, {4, "Fish"}, {5, "Elephant"}, } } type DurationField struct { p *time.Duration } func (*DurationField) CanSet() bool { return true } func (f *DurationField) Get() interface{} { return f.p.String() } func (f *DurationField) Set(v interface{}) error { x, err := time.ParseDuration(v.(string)) if err == nil { *f.p = x } return err } func (f *DurationField) Zero() interface{} { return "" } type Sex byte const ( SexMale Sex = 1 + iota SexFemale SexHermaphrodite ) func RunAnimalDialog(owner walk.Form, animal *Animal) (int, error) { var dlg *walk.Dialog var db *walk.DataBinder var acceptPB, cancelPB *walk.PushButton return Dialog{ AssignTo: &dlg, Title: Bind("'Animal Details' + (animal.Name == '' ? '' : ' - ' + animal.Name)"), DefaultButton: &acceptPB, CancelButton: &cancelPB, DataBinder: DataBinder{ AssignTo: &db, Name: "animal", DataSource: animal, ErrorPresenter: ToolTipErrorPresenter{}, }, MinSize: Size{300, 300}, Layout: VBox{}, Children: []Widget{ Composite{ Layout: Grid{Columns: 2}, Children: []Widget{ Label{ Text: "Name:", }, LineEdit{ Text: Bind("Name"), }, Label{ Text: "Arrival Date:", }, DateEdit{ Date: Bind("ArrivalDate"), }, Label{ Text: "Species:", }, ComboBox{ Value: Bind("SpeciesId", SelRequired{}), BindingMember: "Id", DisplayMember: "Name", Model: KnownSpecies(), }, Label{ Text: "Speed:", }, Slider{ Value: Bind("Speed"), }, RadioButtonGroupBox{ ColumnSpan: 2, Title: "Sex", Layout: HBox{}, DataMember: "Sex", Buttons: []RadioButton{ {Text: "Male", Value: SexMale}, {Text: "Female", Value: SexFemale}, {Text: "Hermaphrodite", Value: SexHermaphrodite}, }, }, Label{ Text: "Weight:", }, NumberEdit{ Value: Bind("Weight", Range{0.01, 9999.99}), Suffix: " kg", Decimals: 2, }, Label{ Text: "Preferred Food:", }, ComboBox{ Editable: true, Value: Bind("PreferredFood"), Model: []string{"Fruit", "Grass", "Fish", "Meat"}, }, Label{ Text: "Domesticated:", }, CheckBox{ Checked: Bind("Domesticated"), }, VSpacer{ ColumnSpan: 2, Size: 8, }, Label{ ColumnSpan: 2, Text: "Remarks:", }, TextEdit{ ColumnSpan: 2, MinSize: Size{100, 50}, Text: Bind("Remarks"), }, Label{ ColumnSpan: 2, Text: "Patience:", }, LineEdit{ ColumnSpan: 2, Text: Bind("PatienceField"), }, }, }, Composite{ Layout: HBox{}, Children: []Widget{ HSpacer{}, PushButton{ AssignTo: &acceptPB, Text: "OK", OnClicked: func() { if err := db.Submit(); err != nil { log.Print(err) return } dlg.Accept() }, }, PushButton{ AssignTo: &cancelPB, Text: "Cancel", OnClicked: func() { dlg.Cancel() }, }, }, }, }, }.Run(owner) } ================================================ FILE: examples/drawing/drawing.exe.manifest ================================================  PerMonitorV2, PerMonitor True ================================================ FILE: examples/drawing/drawing.go ================================================ // 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. package main import ( "log" "math" ) import ( "github.com/lxn/walk" . "github.com/lxn/walk/declarative" ) func main() { mw := new(MyMainWindow) if _, err := (MainWindow{ AssignTo: &mw.MainWindow, Title: "Walk Drawing Example", MinSize: Size{320, 240}, Size: Size{800, 600}, Layout: VBox{MarginsZero: true}, Children: []Widget{ CustomWidget{ AssignTo: &mw.paintWidget, ClearsBackground: true, InvalidatesOnResize: true, Paint: mw.drawStuff, }, }, }).Run(); err != nil { log.Fatal(err) } } type MyMainWindow struct { *walk.MainWindow paintWidget *walk.CustomWidget } func (mw *MyMainWindow) drawStuff(canvas *walk.Canvas, updateBounds walk.Rectangle) error { bmp, err := createBitmap() if err != nil { return err } defer bmp.Dispose() bounds := mw.paintWidget.ClientBounds() rectPen, err := walk.NewCosmeticPen(walk.PenSolid, walk.RGB(255, 0, 0)) if err != nil { return err } defer rectPen.Dispose() if err := canvas.DrawRectangle(rectPen, bounds); err != nil { return err } ellipseBrush, err := walk.NewHatchBrush(walk.RGB(0, 255, 0), walk.HatchCross) if err != nil { return err } defer ellipseBrush.Dispose() if err := canvas.FillEllipse(ellipseBrush, bounds); err != nil { return err } linesBrush, err := walk.NewSolidColorBrush(walk.RGB(0, 0, 255)) if err != nil { return err } defer linesBrush.Dispose() linesPen, err := walk.NewGeometricPen(walk.PenDash, 8, linesBrush) if err != nil { return err } defer linesPen.Dispose() if err := canvas.DrawLine(linesPen, walk.Point{bounds.X, bounds.Y}, walk.Point{bounds.Width, bounds.Height}); err != nil { return err } if err := canvas.DrawLine(linesPen, walk.Point{bounds.X, bounds.Height}, walk.Point{bounds.Width, bounds.Y}); err != nil { return err } points := make([]walk.Point, 10) dx := bounds.Width / (len(points) - 1) for i := range points { points[i].X = i * dx points[i].Y = int(float64(bounds.Height) / math.Pow(float64(bounds.Width/2), 2) * math.Pow(float64(i*dx-bounds.Width/2), 2)) } if err := canvas.DrawPolyline(linesPen, points); err != nil { return err } bmpSize := bmp.Size() if err := canvas.DrawImage(bmp, walk.Point{(bounds.Width - bmpSize.Width) / 2, (bounds.Height - bmpSize.Height) / 2}); err != nil { return err } return nil } func createBitmap() (*walk.Bitmap, error) { bounds := walk.Rectangle{Width: 200, Height: 200} bmp, err := walk.NewBitmap(bounds.Size()) if err != nil { return nil, err } succeeded := false defer func() { if !succeeded { bmp.Dispose() } }() canvas, err := walk.NewCanvasFromImage(bmp) if err != nil { return nil, err } defer canvas.Dispose() brushBmp, err := walk.NewBitmapFromFile("../img/plus.png") if err != nil { return nil, err } defer brushBmp.Dispose() brush, err := walk.NewBitmapBrush(brushBmp) if err != nil { return nil, err } defer brush.Dispose() if err := canvas.FillRectangle(brush, bounds); err != nil { return nil, err } font, err := walk.NewFont("Times New Roman", 40, walk.FontBold|walk.FontItalic) if err != nil { return nil, err } defer font.Dispose() if err := canvas.DrawText("Walk Drawing Example", font, walk.RGB(0, 0, 0), bounds, walk.TextWordbreak); err != nil { return nil, err } succeeded = true return bmp, nil } ================================================ FILE: examples/dropfiles/dropfiles.exe.manifest ================================================  PerMonitorV2, PerMonitor True ================================================ FILE: examples/dropfiles/dropfiles.go ================================================ // Copyright 2013 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. package main import ( "strings" "github.com/lxn/walk" . "github.com/lxn/walk/declarative" ) func main() { var textEdit *walk.TextEdit MainWindow{ Title: "Walk DropFiles Example", MinSize: Size{320, 240}, Layout: VBox{}, OnDropFiles: func(files []string) { textEdit.SetText(strings.Join(files, "\r\n")) }, Children: []Widget{ TextEdit{ AssignTo: &textEdit, ReadOnly: true, Text: "Drop files here, from windows explorer...", }, }, }.Run() } ================================================ FILE: examples/externalwidgets/externalwidgets.exe.manifest ================================================  PerMonitorV2, PerMonitor True ================================================ FILE: examples/externalwidgets/externalwidgets.go ================================================ // Copyright 2012 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. package main import ( "log" "github.com/lxn/walk" . "github.com/lxn/walk/declarative" "github.com/lxn/win" ) const myWidgetWindowClass = "MyWidget Class" func init() { walk.AppendToWalkInit(func() { walk.MustRegisterWindowClass(myWidgetWindowClass) }) } func main() { var mw *walk.MainWindow if err := (MainWindow{ AssignTo: &mw, Title: "Walk External Widgets Example", Size: Size{400, 300}, Layout: HBox{}, }).Create(); err != nil { log.Fatal(err) } for _, name := range []string{"a", "b", "c"} { if w, err := NewMyWidget(mw); err != nil { log.Fatal(err) } else { w.SetName(name) } } mpb, err := NewMyPushButton(mw) if err != nil { log.Fatal(err) } mpb.SetText("MyPushButton") mw.Run() } type MyWidget struct { walk.WidgetBase } func NewMyWidget(parent walk.Container) (*MyWidget, error) { w := new(MyWidget) if err := walk.InitWidget( w, parent, myWidgetWindowClass, win.WS_VISIBLE, 0); err != nil { return nil, err } bg, err := walk.NewSolidColorBrush(walk.RGB(0, 255, 0)) if err != nil { return nil, err } w.SetBackground(bg) return w, nil } func (*MyWidget) CreateLayoutItem(ctx *walk.LayoutContext) walk.LayoutItem { return &myWidgetLayoutItem{idealSize: walk.SizeFrom96DPI(walk.Size{50, 50}, ctx.DPI())} } type myWidgetLayoutItem struct { walk.LayoutItemBase idealSize walk.Size // in native pixels } func (li *myWidgetLayoutItem) LayoutFlags() walk.LayoutFlags { return 0 } func (li *myWidgetLayoutItem) IdealSize() walk.Size { return li.idealSize } func (w *MyWidget) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_LBUTTONDOWN: log.Printf("%s: WM_LBUTTONDOWN", w.Name()) } return w.WidgetBase.WndProc(hwnd, msg, wParam, lParam) } type MyPushButton struct { *walk.PushButton } func NewMyPushButton(parent walk.Container) (*MyPushButton, error) { pb, err := walk.NewPushButton(parent) if err != nil { return nil, err } mpb := &MyPushButton{pb} if err := walk.InitWrapperWindow(mpb); err != nil { return nil, err } return mpb, nil } func (mpb *MyPushButton) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_LBUTTONDOWN: log.Printf("%s: WM_LBUTTONDOWN", mpb.Text()) } return mpb.PushButton.WndProc(hwnd, msg, wParam, lParam) } ================================================ FILE: examples/filebrowser/filebrowser.exe.manifest ================================================  PerMonitorV2, PerMonitor True ================================================ FILE: examples/filebrowser/filebrowser.go ================================================ // Copyright 2011 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. package main import ( "log" "os" "path/filepath" "time" ) import ( "github.com/lxn/walk" . "github.com/lxn/walk/declarative" ) type Directory struct { name string parent *Directory children []*Directory } func NewDirectory(name string, parent *Directory) *Directory { return &Directory{name: name, parent: parent} } var _ walk.TreeItem = new(Directory) func (d *Directory) Text() string { return d.name } func (d *Directory) Parent() walk.TreeItem { if d.parent == nil { // We can't simply return d.parent in this case, because the interface // value then would not be nil. return nil } return d.parent } func (d *Directory) ChildCount() int { if d.children == nil { // It seems this is the first time our child count is checked, so we // use the opportunity to populate our direct children. if err := d.ResetChildren(); err != nil { log.Print(err) } } return len(d.children) } func (d *Directory) ChildAt(index int) walk.TreeItem { return d.children[index] } func (d *Directory) Image() interface{} { return d.Path() } func (d *Directory) ResetChildren() error { d.children = nil dirPath := d.Path() if err := filepath.Walk(d.Path(), func(path string, info os.FileInfo, err error) error { if err != nil { if info == nil { return filepath.SkipDir } } name := info.Name() if !info.IsDir() || path == dirPath || shouldExclude(name) { return nil } d.children = append(d.children, NewDirectory(name, d)) return filepath.SkipDir }); err != nil { return err } return nil } func (d *Directory) Path() string { elems := []string{d.name} dir, _ := d.Parent().(*Directory) for dir != nil { elems = append([]string{dir.name}, elems...) dir, _ = dir.Parent().(*Directory) } return filepath.Join(elems...) } type DirectoryTreeModel struct { walk.TreeModelBase roots []*Directory } var _ walk.TreeModel = new(DirectoryTreeModel) func NewDirectoryTreeModel() (*DirectoryTreeModel, error) { model := new(DirectoryTreeModel) drives, err := walk.DriveNames() if err != nil { return nil, err } for _, drive := range drives { switch drive { case "A:\\", "B:\\": continue } model.roots = append(model.roots, NewDirectory(drive, nil)) } return model, nil } func (*DirectoryTreeModel) LazyPopulation() bool { // We don't want to eagerly populate our tree view with the whole file system. return true } func (m *DirectoryTreeModel) RootCount() int { return len(m.roots) } func (m *DirectoryTreeModel) RootAt(index int) walk.TreeItem { return m.roots[index] } type FileInfo struct { Name string Size int64 Modified time.Time } type FileInfoModel struct { walk.SortedReflectTableModelBase dirPath string items []*FileInfo } var _ walk.ReflectTableModel = new(FileInfoModel) func NewFileInfoModel() *FileInfoModel { return new(FileInfoModel) } func (m *FileInfoModel) Items() interface{} { return m.items } func (m *FileInfoModel) SetDirPath(dirPath string) error { m.dirPath = dirPath m.items = nil if err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error { if err != nil { if info == nil { return filepath.SkipDir } } name := info.Name() if path == dirPath || shouldExclude(name) { return nil } item := &FileInfo{ Name: name, Size: info.Size(), Modified: info.ModTime(), } m.items = append(m.items, item) if info.IsDir() { return filepath.SkipDir } return nil }); err != nil { return err } m.PublishRowsReset() return nil } func (m *FileInfoModel) Image(row int) interface{} { return filepath.Join(m.dirPath, m.items[row].Name) } func shouldExclude(name string) bool { switch name { case "System Volume Information", "pagefile.sys", "swapfile.sys": return true } return false } func main() { var mainWindow *walk.MainWindow var splitter *walk.Splitter var treeView *walk.TreeView var tableView *walk.TableView var webView *walk.WebView treeModel, err := NewDirectoryTreeModel() if err != nil { log.Fatal(err) } tableModel := NewFileInfoModel() if err := (MainWindow{ AssignTo: &mainWindow, Title: "Walk File Browser Example", MinSize: Size{600, 400}, Size: Size{1024, 640}, Layout: HBox{MarginsZero: true}, Children: []Widget{ HSplitter{ AssignTo: &splitter, Children: []Widget{ TreeView{ AssignTo: &treeView, Model: treeModel, OnCurrentItemChanged: func() { dir := treeView.CurrentItem().(*Directory) if err := tableModel.SetDirPath(dir.Path()); err != nil { walk.MsgBox( mainWindow, "Error", err.Error(), walk.MsgBoxOK|walk.MsgBoxIconError) } }, }, TableView{ AssignTo: &tableView, StretchFactor: 2, Columns: []TableViewColumn{ TableViewColumn{ DataMember: "Name", Width: 192, }, TableViewColumn{ DataMember: "Size", Format: "%d", Alignment: AlignFar, Width: 64, }, TableViewColumn{ DataMember: "Modified", Format: "2006-01-02 15:04:05", Width: 120, }, }, Model: tableModel, OnCurrentIndexChanged: func() { var url string if index := tableView.CurrentIndex(); index > -1 { name := tableModel.items[index].Name dir := treeView.CurrentItem().(*Directory) url = filepath.Join(dir.Path(), name) } webView.SetURL(url) }, }, WebView{ AssignTo: &webView, StretchFactor: 2, }, }, }, }, }.Create()); err != nil { log.Fatal(err) } splitter.SetFixed(treeView, true) splitter.SetFixed(tableView, true) mainWindow.Run() } ================================================ FILE: examples/gradientcomposite/gradientcomposite.exe.manifest ================================================  PerMonitorV2, PerMonitor True ================================================ FILE: examples/gradientcomposite/gradientcomposite.go ================================================ // Copyright 2017 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. package main import ( "github.com/lxn/walk" . "github.com/lxn/walk/declarative" ) func main() { MainWindow{ Title: "Walk GradientComposite Example", MinSize: Size{400, 0}, Background: GradientBrush{ Vertexes: []walk.GradientVertex{ {X: 0, Y: 0, Color: walk.RGB(255, 255, 127)}, {X: 1, Y: 0, Color: walk.RGB(127, 191, 255)}, {X: 0.5, Y: 0.5, Color: walk.RGB(255, 255, 255)}, {X: 1, Y: 1, Color: walk.RGB(127, 255, 127)}, {X: 0, Y: 1, Color: walk.RGB(255, 127, 127)}, }, Triangles: []walk.GradientTriangle{ {0, 1, 2}, {1, 3, 2}, {3, 4, 2}, {4, 0, 2}, }, }, Layout: HBox{Margins: Margins{100, 100, 100, 100}}, Children: []Widget{ GradientComposite{ Border: true, Vertical: Bind("verticalCB.Checked"), Color1: Bind("rgb(c1RedSld.Value, c1GreenSld.Value, c1BlueSld.Value)"), Color2: Bind("rgb(c2RedSld.Value, c2GreenSld.Value, c2BlueSld.Value)"), Layout: HBox{}, Children: []Widget{ GroupBox{ Title: "Gradient Parameters", Layout: VBox{}, Children: []Widget{ CheckBox{Name: "verticalCB", Text: "Vertical", Checked: true}, GroupBox{ Title: "Color1", Layout: Grid{Columns: 2}, Children: []Widget{ Label{Text: "Red:"}, Slider{Name: "c1RedSld", Tracking: true, MaxValue: 255, Value: 95}, Label{Text: "Green:"}, Slider{Name: "c1GreenSld", Tracking: true, MaxValue: 255, Value: 191}, Label{Text: "Blue:"}, Slider{Name: "c1BlueSld", Tracking: true, MaxValue: 255, Value: 255}, }, }, GroupBox{ Title: "Color2", Layout: Grid{Columns: 2}, Children: []Widget{ Label{Text: "Red:"}, Slider{Name: "c2RedSld", Tracking: true, MaxValue: 255, Value: 239}, Label{Text: "Green:"}, Slider{Name: "c2GreenSld", Tracking: true, MaxValue: 255, Value: 63}, Label{Text: "Blue:"}, Slider{Name: "c2BlueSld", Tracking: true, MaxValue: 255, Value: 0}, }, }, }, }, }, }, }, Functions: map[string]func(args ...interface{}) (interface{}, error){ "rgb": func(args ...interface{}) (interface{}, error) { return walk.RGB(byte(args[0].(float64)), byte(args[1].(float64)), byte(args[2].(float64))), nil }, }, }.Run() } ================================================ FILE: examples/imageicon/imageicon.exe.manifest ================================================  PerMonitorV2, PerMonitor True ================================================ FILE: examples/imageicon/main.go ================================================ // Copyright 2012 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. package main import ( "image" "image/color" "image/draw" "log" ) import ( "github.com/lxn/walk" . "github.com/lxn/walk/declarative" ) func main() { var mw *walk.MainWindow var windowIcon *walk.Icon counter := 0 if _, err := (MainWindow{ AssignTo: &mw, Title: "Walk Image Icon Example", Layout: HBox{}, Children: []Widget{ HSpacer{}, PushButton{ Text: "Push me", OnClicked: func() { ic, err := walk.NewIconFromImage(makeDigitImage(counter)) if err != nil { return } counter++ mw.SetIcon(ic) if windowIcon != nil { windowIcon.Dispose() } windowIcon = ic }, }, HSpacer{}, }, }.Run()); err != nil { log.Fatal(err) } } // A // F B // G // E C // D var hexdigits = []int{0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71} //0x7E, 0x30, 0x6D, 0x79, 0x33, 0x5B, 0x5F, 0x70, 0x7F, 0x7B, 0x77, 0x1F, 0x4E, 0x3D, 0x4F, 0x47 type seg struct { sx, sy int dx, dy int } var segments = []seg{ {0, 0, 1, 0}, {1, 0, 0, 1}, {1, 1, 0, 1}, {0, 2, 1, 0}, {0, 1, 0, 1}, {0, 0, 0, 1}, {0, 1, 1, 0}, } func digit(im draw.Image, col color.Color, x, y, size, digit int) { n := hexdigits[digit] for _, s := range segments { if n&1 != 0 { xx, yy := x+s.sx*size, y+s.sy*size for i := 0; i <= size; i++ { im.Set(xx, yy, col) xx += s.dx yy += s.dy } } n >>= 1 } } func makeDigitImage(n int) image.Image { im := image.NewRGBA(image.Rect(0, 0, 16, 16)) for p := 11; p >= 0; p -= 5 { digit(im, color.Black, p, 5, 3, n%10) n /= 10 } return im } ================================================ FILE: examples/imageview/imageview.exe.manifest ================================================  PerMonitorV2, PerMonitor True ================================================ FILE: examples/imageview/imageview.go ================================================ // Copyright 2017 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. package main import ( "github.com/lxn/walk" . "github.com/lxn/walk/declarative" ) func main() { walk.Resources.SetRootDirPath("../img") type Mode struct { Name string Value ImageViewMode } modes := []Mode{ {"ImageViewModeIdeal", ImageViewModeIdeal}, {"ImageViewModeCorner", ImageViewModeCorner}, {"ImageViewModeCenter", ImageViewModeCenter}, {"ImageViewModeShrink", ImageViewModeShrink}, {"ImageViewModeZoom", ImageViewModeZoom}, {"ImageViewModeStretch", ImageViewModeStretch}, } var widgets []Widget for _, mode := range modes { widgets = append(widgets, Label{ Text: mode.Name, }, ImageView{ Background: SolidColorBrush{Color: walk.RGB(255, 191, 0)}, Image: "open.png", Margin: 10, Mode: mode.Value, }, ) } MainWindow{ Title: "Walk ImageView Example", Size: Size{400, 600}, Layout: Grid{Columns: 2}, Children: widgets, }.Run() } ================================================ FILE: examples/imageviewer/imageviewer.exe.manifest ================================================  PerMonitorV2, PerMonitor True ================================================ FILE: examples/imageviewer/imageviewer.go ================================================ // 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. package main import ( "log" "path" "strings" ) import ( "github.com/lxn/walk" . "github.com/lxn/walk/declarative" ) func main() { mw := new(MyMainWindow) var openAction *walk.Action if _, err := (MainWindow{ AssignTo: &mw.MainWindow, Title: "Walk Image Viewer Example", MenuItems: []MenuItem{ Menu{ Text: "&File", Items: []MenuItem{ Action{ AssignTo: &openAction, Text: "&Open", Image: "../img/open.png", OnTriggered: mw.openAction_Triggered, }, Separator{}, Action{ Text: "Exit", OnTriggered: func() { mw.Close() }, }, }, }, Menu{ Text: "&Help", Items: []MenuItem{ Action{ Text: "About", OnTriggered: mw.aboutAction_Triggered, }, }, }, }, ToolBarItems: []MenuItem{ ActionRef{&openAction}, }, MinSize: Size{320, 240}, Size: Size{800, 600}, Layout: VBox{MarginsZero: true}, Children: []Widget{ TabWidget{ AssignTo: &mw.tabWidget, }, }, }.Run()); err != nil { log.Fatal(err) } } type MyMainWindow struct { *walk.MainWindow tabWidget *walk.TabWidget prevFilePath string } func (mw *MyMainWindow) openAction_Triggered() { if err := mw.openImage(); err != nil { log.Print(err) } } func (mw *MyMainWindow) openImage() error { dlg := new(walk.FileDialog) dlg.FilePath = mw.prevFilePath dlg.Filter = "Image Files (*.emf;*.bmp;*.exif;*.gif;*.jpeg;*.jpg;*.png;*.tiff)|*.emf;*.bmp;*.exif;*.gif;*.jpeg;*.jpg;*.png;*.tiff" dlg.Title = "Select an Image" if ok, err := dlg.ShowOpen(mw); err != nil { return err } else if !ok { return nil } mw.prevFilePath = dlg.FilePath img, err := walk.NewImageFromFile(dlg.FilePath) if err != nil { return err } var succeeded bool defer func() { if !succeeded { img.Dispose() } }() page, err := walk.NewTabPage() if err != nil { return err } if page.SetTitle(path.Base(strings.Replace(dlg.FilePath, "\\", "/", -1))); err != nil { return err } page.SetLayout(walk.NewHBoxLayout()) defer func() { if !succeeded { page.Dispose() } }() imageView, err := walk.NewImageView(page) if err != nil { return err } defer func() { if !succeeded { imageView.Dispose() } }() imageView.SetMode(walk.ImageViewModeShrink) if err := imageView.SetImage(img); err != nil { return err } if err := mw.tabWidget.Pages().Add(page); err != nil { return err } if err := mw.tabWidget.SetCurrentIndex(mw.tabWidget.Pages().Len() - 1); err != nil { return err } succeeded = true return nil } func (mw *MyMainWindow) aboutAction_Triggered() { walk.MsgBox(mw, "About", "Walk Image Viewer Example", walk.MsgBoxIconInformation) } ================================================ FILE: examples/img/README ================================================ Most image files in this directory are from the base icon theme of the Tango Desktop Project at http://tango.freedesktop.org. Thanks for releasing those to the Public Domain. ================================================ FILE: examples/linklabel/linklabel.exe.manifest ================================================  PerMonitorV2, PerMonitor True ================================================ FILE: examples/linklabel/linklabel.go ================================================ // Copyright 2017 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. package main import ( "log" ) import ( "github.com/lxn/walk" . "github.com/lxn/walk/declarative" ) func main() { if _, err := (MainWindow{ Title: "Walk LinkLabel Example", MinSize: Size{300, 200}, Layout: VBox{}, Children: []Widget{ LinkLabel{ MaxSize: Size{100, 0}, Text: `I can contain multiple links like this or that one.`, OnLinkActivated: func(link *walk.LinkLabelLink) { log.Printf("id: '%s', url: '%s'\n", link.Id(), link.URL()) }, }, }, }).Run(); err != nil { log.Fatal(err) } } ================================================ FILE: examples/listbox/listbox.exe.manifest ================================================  PerMonitorV2, PerMonitor True ================================================ FILE: examples/listbox/listbox.go ================================================ // Copyright 2012 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. package main import ( "fmt" "log" "os" "strings" ) import ( "github.com/lxn/walk" . "github.com/lxn/walk/declarative" ) func main() { mw := &MyMainWindow{model: NewEnvModel()} if _, err := (MainWindow{ AssignTo: &mw.MainWindow, Title: "Walk ListBox Example", MinSize: Size{240, 320}, Size: Size{300, 400}, Layout: VBox{MarginsZero: true}, Children: []Widget{ HSplitter{ Children: []Widget{ ListBox{ AssignTo: &mw.lb, Model: mw.model, OnCurrentIndexChanged: mw.lb_CurrentIndexChanged, OnItemActivated: mw.lb_ItemActivated, }, TextEdit{ AssignTo: &mw.te, ReadOnly: true, }, }, }, }, }.Run()); err != nil { log.Fatal(err) } } type MyMainWindow struct { *walk.MainWindow model *EnvModel lb *walk.ListBox te *walk.TextEdit } func (mw *MyMainWindow) lb_CurrentIndexChanged() { i := mw.lb.CurrentIndex() item := &mw.model.items[i] mw.te.SetText(item.value) fmt.Println("CurrentIndex: ", i) fmt.Println("CurrentEnvVarName: ", item.name) } func (mw *MyMainWindow) lb_ItemActivated() { value := mw.model.items[mw.lb.CurrentIndex()].value walk.MsgBox(mw, "Value", value, walk.MsgBoxIconInformation) } type EnvItem struct { name string value string } type EnvModel struct { walk.ListModelBase items []EnvItem } func NewEnvModel() *EnvModel { env := os.Environ() m := &EnvModel{items: make([]EnvItem, len(env))} for i, e := range env { j := strings.Index(e, "=") if j == 0 { continue } name := e[0:j] value := strings.Replace(e[j+1:], ";", "\r\n", -1) m.items[i] = EnvItem{name, value} } return m } func (m *EnvModel) ItemCount() int { return len(m.items) } func (m *EnvModel) Value(index int) interface{} { return m.items[index].name } ================================================ FILE: examples/listbox_ownerdrawing/listbox_ownerdrawing.exe.manifest ================================================  PerMonitorV2, PerMonitor True ================================================ FILE: examples/listbox_ownerdrawing/listbox_ownerdrawing.go ================================================ // Copyright 2019 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. package main import ( "fmt" "log" "strings" "time" "github.com/lxn/walk" . "github.com/lxn/walk/declarative" "github.com/lxn/win" ) func main() { var mw *walk.MainWindow var lb *walk.ListBox var items []logEntry for i := 10000; i > 0; i-- { items = append(items, logEntry{time.Now().Add(-time.Second * time.Duration(i)), fmt.Sprintf("Some stuff just happend. %s", strings.TrimSpace(strings.Repeat("blah ", i%100)))}) } model := &logModel{items: items} styler := &Styler{ lb: &lb, model: model, dpi2StampSize: make(map[int]walk.Size), widthDPI2WsPerLine: make(map[widthDPI]int), textWidthDPI2Height: make(map[textWidthDPI]int), } if err := (MainWindow{ AssignTo: &mw, Title: "Walk ListBox Owner Drawing Example", MinSize: Size{200, 200}, Size: Size{800, 600}, Font: Font{Family: "Segoe UI", PointSize: 9}, Layout: VBox{}, Children: []Widget{ Composite{ DoubleBuffering: true, Layout: VBox{}, Children: []Widget{ ListBox{ AssignTo: &lb, MultiSelection: true, Model: model, ItemStyler: styler, }, }, }, }, }).Create(); err != nil { log.Fatal(err) } ticker := time.NewTicker(time.Second) defer ticker.Stop() cancel := make(chan bool, 1) go func() { for { select { case <-ticker.C: mw.Synchronize(func() { trackLatest := lb.ItemVisible(len(model.items)-1) && len(lb.SelectedIndexes()) <= 1 model.items = append(model.items, logEntry{time.Now(), "Some new stuff."}) index := len(model.items) - 1 model.PublishItemsInserted(index, index) if trackLatest { lb.EnsureItemVisible(len(model.items) - 1) } }) case <-cancel: break } } }() mw.Show() mw.Run() cancel <- true } type logModel struct { walk.ReflectListModelBase items []logEntry } func (m *logModel) Items() interface{} { return m.items } type logEntry struct { timestamp time.Time message string } type widthDPI struct { width int // in native pixels dpi int } type textWidthDPI struct { text string width int // in native pixels dpi int } type Styler struct { lb **walk.ListBox canvas *walk.Canvas model *logModel font *walk.Font dpi2StampSize map[int]walk.Size widthDPI2WsPerLine map[widthDPI]int textWidthDPI2Height map[textWidthDPI]int // in native pixels } func (s *Styler) ItemHeightDependsOnWidth() bool { return true } func (s *Styler) DefaultItemHeight() int { dpi := (*s.lb).DPI() marginV := walk.IntFrom96DPI(marginV96dpi, dpi) return s.StampSize().Height + marginV*2 } const ( marginH96dpi int = 6 marginV96dpi int = 2 lineW96dpi int = 1 ) func (s *Styler) ItemHeight(index, width int) int { dpi := (*s.lb).DPI() marginH := walk.IntFrom96DPI(marginH96dpi, dpi) marginV := walk.IntFrom96DPI(marginV96dpi, dpi) lineW := walk.IntFrom96DPI(lineW96dpi, dpi) msg := s.model.items[index].message twd := textWidthDPI{msg, width, dpi} if height, ok := s.textWidthDPI2Height[twd]; ok { return height + marginV*2 } canvas, err := s.Canvas() if err != nil { return 0 } stampSize := s.StampSize() wd := widthDPI{width, dpi} wsPerLine, ok := s.widthDPI2WsPerLine[wd] if !ok { bounds, _, err := canvas.MeasureTextPixels("W", (*s.lb).Font(), walk.Rectangle{Width: 9999999}, walk.TextCalcRect) if err != nil { return 0 } wsPerLine = (width - marginH*4 - lineW - stampSize.Width) / bounds.Width s.widthDPI2WsPerLine[wd] = wsPerLine } if len(msg) <= wsPerLine { s.textWidthDPI2Height[twd] = stampSize.Height return stampSize.Height + marginV*2 } bounds, _, err := canvas.MeasureTextPixels(msg, (*s.lb).Font(), walk.Rectangle{Width: width - marginH*4 - lineW - stampSize.Width, Height: 255}, walk.TextEditControl|walk.TextWordbreak|walk.TextEndEllipsis) if err != nil { return 0 } s.textWidthDPI2Height[twd] = bounds.Height return bounds.Height + marginV*2 } func (s *Styler) StyleItem(style *walk.ListItemStyle) { if canvas := style.Canvas(); canvas != nil { if style.Index()%2 == 1 && style.BackgroundColor == walk.Color(win.GetSysColor(win.COLOR_WINDOW)) { style.BackgroundColor = walk.Color(win.GetSysColor(win.COLOR_BTNFACE)) if err := style.DrawBackground(); err != nil { return } } pen, err := walk.NewCosmeticPen(walk.PenSolid, style.LineColor) if err != nil { return } defer pen.Dispose() dpi := (*s.lb).DPI() marginH := walk.IntFrom96DPI(marginH96dpi, dpi) marginV := walk.IntFrom96DPI(marginV96dpi, dpi) lineW := walk.IntFrom96DPI(lineW96dpi, dpi) b := style.BoundsPixels() b.X += marginH b.Y += marginV item := s.model.items[style.Index()] style.DrawText(item.timestamp.Format(time.StampMilli), b, walk.TextEditControl|walk.TextWordbreak) stampSize := s.StampSize() x := b.X + stampSize.Width + marginH + lineW canvas.DrawLinePixels(pen, walk.Point{x, b.Y - marginV}, walk.Point{x, b.Y - marginV + b.Height}) b.X += stampSize.Width + marginH*2 + lineW b.Width -= stampSize.Width + marginH*4 + lineW style.DrawText(item.message, b, walk.TextEditControl|walk.TextWordbreak|walk.TextEndEllipsis) } } func (s *Styler) StampSize() walk.Size { dpi := (*s.lb).DPI() stampSize, ok := s.dpi2StampSize[dpi] if !ok { canvas, err := s.Canvas() if err != nil { return walk.Size{} } bounds, _, err := canvas.MeasureTextPixels("Jan _2 20:04:05.000", (*s.lb).Font(), walk.Rectangle{Width: 9999999}, walk.TextCalcRect) if err != nil { return walk.Size{} } stampSize = bounds.Size() s.dpi2StampSize[dpi] = stampSize } return stampSize } func (s *Styler) Canvas() (*walk.Canvas, error) { if s.canvas != nil { return s.canvas, nil } canvas, err := (*s.lb).CreateCanvas() if err != nil { return nil, err } s.canvas = canvas (*s.lb).AddDisposable(canvas) return canvas, nil } ================================================ FILE: examples/logview/logview.exe.manifest ================================================  PerMonitorV2, PerMonitor True ================================================ FILE: examples/logview/logview.go ================================================ // Copyright 2012 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. package main import ( "errors" "syscall" "unsafe" ) import ( "github.com/lxn/walk" "github.com/lxn/win" ) type LogView struct { walk.WidgetBase logChan chan string } const ( TEM_APPENDTEXT = win.WM_USER + 6 ) func NewLogView(parent walk.Container) (*LogView, error) { lc := make(chan string, 1024) lv := &LogView{logChan: lc} if err := walk.InitWidget( lv, parent, "EDIT", win.WS_TABSTOP|win.WS_VISIBLE|win.WS_VSCROLL|win.ES_MULTILINE|win.ES_WANTRETURN, win.WS_EX_CLIENTEDGE); err != nil { return nil, err } lv.setReadOnly(true) lv.SendMessage(win.EM_SETLIMITTEXT, 4294967295, 0) return lv, nil } func (*LogView) CreateLayoutItem(ctx *walk.LayoutContext) walk.LayoutItem { return walk.NewGreedyLayoutItem() } func (lv *LogView) setTextSelection(start, end int) { lv.SendMessage(win.EM_SETSEL, uintptr(start), uintptr(end)) } func (lv *LogView) textLength() int { return int(lv.SendMessage(0x000E, uintptr(0), uintptr(0))) } func (lv *LogView) AppendText(value string) { textLength := lv.textLength() lv.setTextSelection(textLength, textLength) lv.SendMessage(win.EM_REPLACESEL, 0, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(value)))) } func (lv *LogView) setReadOnly(readOnly bool) error { if 0 == lv.SendMessage(win.EM_SETREADONLY, uintptr(win.BoolToBOOL(readOnly)), 0) { return errors.New("fail to call EM_SETREADONLY") } return nil } func (lv *LogView) PostAppendText(value string) { lv.logChan <- value win.PostMessage(lv.Handle(), TEM_APPENDTEXT, 0, 0) } func (lv *LogView) Write(p []byte) (int, error) { lv.PostAppendText(string(p) + "\r\n") return len(p), nil } func (lv *LogView) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_GETDLGCODE: if wParam == win.VK_RETURN { return win.DLGC_WANTALLKEYS } return win.DLGC_HASSETSEL | win.DLGC_WANTARROWS | win.DLGC_WANTCHARS case TEM_APPENDTEXT: select { case value := <-lv.logChan: lv.AppendText(value) default: return 0 } } return lv.WidgetBase.WndProc(hwnd, msg, wParam, lParam) } ================================================ FILE: examples/logview/logviewapp.go ================================================ // Copyright 2012 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. package main import ( "log" "time" ) import ( "github.com/lxn/walk" . "github.com/lxn/walk/declarative" ) func main() { var mw *walk.MainWindow if err := (MainWindow{ AssignTo: &mw, Title: "Walk LogView Example", MinSize: Size{320, 240}, Size: Size{400, 600}, Layout: VBox{MarginsZero: true}, }.Create()); err != nil { log.Fatal(err) } lv, err := NewLogView(mw) if err != nil { log.Fatal(err) } lv.PostAppendText("XXX") log.SetOutput(lv) go func() { for i := 0; i < 10000; i++ { time.Sleep(100 * time.Millisecond) log.Println("Text" + "\r\n") } }() mw.Run() } ================================================ FILE: examples/multiplepages/main.go ================================================ // Copyright 2017 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. package main import ( "bytes" ) import ( "github.com/lxn/walk" . "github.com/lxn/walk/declarative" ) func main() { walk.Resources.SetRootDirPath("../img") mw := new(AppMainWindow) cfg := &MultiPageMainWindowConfig{ Name: "mainWindow", MinSize: Size{600, 400}, MenuItems: []MenuItem{ Menu{ Text: "&Help", Items: []MenuItem{ Action{ Text: "About", OnTriggered: func() { mw.aboutAction_Triggered() }, }, }, }, }, OnCurrentPageChanged: func() { mw.updateTitle(mw.CurrentPageTitle()) }, PageCfgs: []PageConfig{ {"Foo", "document-new.png", newFooPage}, {"Bar", "document-properties.png", newBarPage}, {"Baz", "system-shutdown.png", newBazPage}, }, } mpmw, err := NewMultiPageMainWindow(cfg) if err != nil { panic(err) } mw.MultiPageMainWindow = mpmw mw.updateTitle(mw.CurrentPageTitle()) mw.Run() } type AppMainWindow struct { *MultiPageMainWindow } func (mw *AppMainWindow) updateTitle(prefix string) { var buf bytes.Buffer if prefix != "" { buf.WriteString(prefix) buf.WriteString(" - ") } buf.WriteString("Walk Multiple Pages Example") mw.SetTitle(buf.String()) } func (mw *AppMainWindow) aboutAction_Triggered() { walk.MsgBox(mw, "About Walk Multiple Pages Example", "An example that demonstrates a main window that supports multiple pages.", walk.MsgBoxOK|walk.MsgBoxIconInformation) } type FooPage struct { *walk.Composite } func newFooPage(parent walk.Container) (Page, error) { p := new(FooPage) if err := (Composite{ AssignTo: &p.Composite, Name: "fooPage", Layout: HBox{}, Children: []Widget{ HSpacer{}, Label{Text: "I'm the Foo page"}, HSpacer{}, }, }).Create(NewBuilder(parent)); err != nil { return nil, err } if err := walk.InitWrapperWindow(p); err != nil { return nil, err } return p, nil } type BarPage struct { *walk.Composite } func newBarPage(parent walk.Container) (Page, error) { p := new(BarPage) if err := (Composite{ AssignTo: &p.Composite, Name: "barPage", Layout: HBox{}, Children: []Widget{ HSpacer{}, Label{Text: "I'm the Bar page"}, HSpacer{}, }, }).Create(NewBuilder(parent)); err != nil { return nil, err } if err := walk.InitWrapperWindow(p); err != nil { return nil, err } return p, nil } type BazPage struct { *walk.Composite } func newBazPage(parent walk.Container) (Page, error) { p := new(BazPage) if err := (Composite{ AssignTo: &p.Composite, Name: "bazPage", Layout: HBox{}, Children: []Widget{ HSpacer{}, Label{Text: "I'm the Baz page"}, HSpacer{}, }, }).Create(NewBuilder(parent)); err != nil { return nil, err } if err := walk.InitWrapperWindow(p); err != nil { return nil, err } return p, nil } ================================================ FILE: examples/multiplepages/multipagemainwindow.go ================================================ // Copyright 2017 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. package main import ( "github.com/lxn/walk" . "github.com/lxn/walk/declarative" ) type MultiPageMainWindowConfig struct { Name string Enabled Property Visible Property Font Font MinSize Size MaxSize Size ContextMenuItems []MenuItem OnKeyDown walk.KeyEventHandler OnKeyPress walk.KeyEventHandler OnKeyUp walk.KeyEventHandler OnMouseDown walk.MouseEventHandler OnMouseMove walk.MouseEventHandler OnMouseUp walk.MouseEventHandler OnSizeChanged walk.EventHandler OnCurrentPageChanged walk.EventHandler Title string Size Size MenuItems []MenuItem ToolBar ToolBar PageCfgs []PageConfig } type PageConfig struct { Title string Image string NewPage PageFactoryFunc } type PageFactoryFunc func(parent walk.Container) (Page, error) type Page interface { // Provided by Walk walk.Container Parent() walk.Container SetParent(parent walk.Container) error } type MultiPageMainWindow struct { *walk.MainWindow navTB *walk.ToolBar pageCom *walk.Composite action2NewPage map[*walk.Action]PageFactoryFunc pageActions []*walk.Action currentAction *walk.Action currentPage Page currentPageChangedPublisher walk.EventPublisher } func NewMultiPageMainWindow(cfg *MultiPageMainWindowConfig) (*MultiPageMainWindow, error) { mpmw := &MultiPageMainWindow{ action2NewPage: make(map[*walk.Action]PageFactoryFunc), } if err := (MainWindow{ AssignTo: &mpmw.MainWindow, Name: cfg.Name, Title: cfg.Title, Enabled: cfg.Enabled, Visible: cfg.Visible, Font: cfg.Font, MinSize: cfg.MinSize, MaxSize: cfg.MaxSize, MenuItems: cfg.MenuItems, ToolBar: cfg.ToolBar, ContextMenuItems: cfg.ContextMenuItems, OnKeyDown: cfg.OnKeyDown, OnKeyPress: cfg.OnKeyPress, OnKeyUp: cfg.OnKeyUp, OnMouseDown: cfg.OnMouseDown, OnMouseMove: cfg.OnMouseMove, OnMouseUp: cfg.OnMouseUp, OnSizeChanged: cfg.OnSizeChanged, Layout: HBox{MarginsZero: true, SpacingZero: true}, Children: []Widget{ ScrollView{ HorizontalFixed: true, Layout: VBox{MarginsZero: true}, Children: []Widget{ Composite{ Layout: VBox{MarginsZero: true}, Children: []Widget{ ToolBar{ AssignTo: &mpmw.navTB, Orientation: Vertical, ButtonStyle: ToolBarButtonImageAboveText, MaxTextRows: 2, }, }, }, }, }, Composite{ AssignTo: &mpmw.pageCom, Name: "pageCom", Layout: HBox{MarginsZero: true, SpacingZero: true}, }, }, }).Create(); err != nil { return nil, err } succeeded := false defer func() { if !succeeded { mpmw.Dispose() } }() for _, pc := range cfg.PageCfgs { action, err := mpmw.newPageAction(pc.Title, pc.Image, pc.NewPage) if err != nil { return nil, err } mpmw.pageActions = append(mpmw.pageActions, action) } if err := mpmw.updateNavigationToolBar(); err != nil { return nil, err } if len(mpmw.pageActions) > 0 { if err := mpmw.setCurrentAction(mpmw.pageActions[0]); err != nil { return nil, err } } if cfg.OnCurrentPageChanged != nil { mpmw.CurrentPageChanged().Attach(cfg.OnCurrentPageChanged) } succeeded = true return mpmw, nil } func (mpmw *MultiPageMainWindow) CurrentPage() Page { return mpmw.currentPage } func (mpmw *MultiPageMainWindow) CurrentPageTitle() string { if mpmw.currentAction == nil { return "" } return mpmw.currentAction.Text() } func (mpmw *MultiPageMainWindow) CurrentPageChanged() *walk.Event { return mpmw.currentPageChangedPublisher.Event() } func (mpmw *MultiPageMainWindow) newPageAction(title, image string, newPage PageFactoryFunc) (*walk.Action, error) { img, err := walk.Resources.Bitmap(image) if err != nil { return nil, err } action := walk.NewAction() action.SetCheckable(true) action.SetExclusive(true) action.SetImage(img) action.SetText(title) mpmw.action2NewPage[action] = newPage action.Triggered().Attach(func() { mpmw.setCurrentAction(action) }) return action, nil } func (mpmw *MultiPageMainWindow) setCurrentAction(action *walk.Action) error { defer func() { if !mpmw.pageCom.IsDisposed() { mpmw.pageCom.RestoreState() } }() mpmw.SetFocus() if prevPage := mpmw.currentPage; prevPage != nil { mpmw.pageCom.SaveState() prevPage.SetVisible(false) prevPage.(walk.Widget).SetParent(nil) prevPage.Dispose() } newPage := mpmw.action2NewPage[action] page, err := newPage(mpmw.pageCom) if err != nil { return err } action.SetChecked(true) mpmw.currentPage = page mpmw.currentAction = action mpmw.currentPageChangedPublisher.Publish() return nil } func (mpmw *MultiPageMainWindow) updateNavigationToolBar() error { mpmw.navTB.SetSuspended(true) defer mpmw.navTB.SetSuspended(false) actions := mpmw.navTB.Actions() if err := actions.Clear(); err != nil { return err } for _, action := range mpmw.pageActions { if err := actions.Add(action); err != nil { return err } } if mpmw.currentAction != nil { if !actions.Contains(mpmw.currentAction) { for _, action := range mpmw.pageActions { if action != mpmw.currentAction { if err := mpmw.setCurrentAction(action); err != nil { return err } break } } } } return nil } ================================================ FILE: examples/multiplepages/multiplepages.exe.manifest ================================================  PerMonitorV2, PerMonitor True ================================================ FILE: examples/notifyicon/notifyicon.exe.manifest ================================================  PerMonitorV2, PerMonitor True ================================================ FILE: examples/notifyicon/notifyicon.go ================================================ // Copyright 2011 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. package main import ( "log" ) import ( "github.com/lxn/walk" ) func main() { // We need either a walk.MainWindow or a walk.Dialog for their message loop. // We will not make it visible in this example, though. mw, err := walk.NewMainWindow() if err != nil { log.Fatal(err) } // We load our icon from a file. icon, err := walk.Resources.Icon("../img/stop.ico") if err != nil { log.Fatal(err) } // Create the notify icon and make sure we clean it up on exit. ni, err := walk.NewNotifyIcon(mw) if err != nil { log.Fatal(err) } defer ni.Dispose() // Set the icon and a tool tip text. if err := ni.SetIcon(icon); err != nil { log.Fatal(err) } if err := ni.SetToolTip("Click for info or use the context menu to exit."); err != nil { log.Fatal(err) } // When the left mouse button is pressed, bring up our balloon. ni.MouseDown().Attach(func(x, y int, button walk.MouseButton) { if button != walk.LeftButton { return } if err := ni.ShowCustom( "Walk NotifyIcon Example", "There are multiple ShowX methods sporting different icons.", icon); err != nil { log.Fatal(err) } }) // We put an exit action into the context menu. exitAction := walk.NewAction() if err := exitAction.SetText("E&xit"); err != nil { log.Fatal(err) } exitAction.Triggered().Attach(func() { walk.App().Exit(0) }) if err := ni.ContextMenu().Actions().Add(exitAction); err != nil { log.Fatal(err) } // The notify icon is hidden initially, so we have to make it visible. if err := ni.SetVisible(true); err != nil { log.Fatal(err) } // Now that the icon is visible, we can bring up an info balloon. if err := ni.ShowInfo("Walk NotifyIcon Example", "Click the icon to show again."); err != nil { log.Fatal(err) } // Run the message loop. mw.Run() } ================================================ FILE: examples/progressindicator/dialog.ui ================================================ Dialog 0 0 598 300 Dialog 500 10 81 241 Qt::Vertical QDialogButtonBox::Cancel|QDialogButtonBox::Ok 40 60 161 23 161 16777215 NoProgress 40 90 161 23 161 16777215 Indeterminate 40 120 161 23 161 16777215 Normal 40 150 161 23 Error 40 180 161 23 Paused 290 180 75 23 START buttonBox accepted() Dialog accept() 248 254 157 274 buttonBox rejected() Dialog reject() 316 260 286 274 ================================================ FILE: examples/progressindicator/dialog_ui.go ================================================ // THIS FILE WAS GENERATED BY A TOOL, DO NOT EDIT! package main import ( "github.com/lxn/walk" ) type myDialogUI struct { noProgressBtn *walk.PushButton indeterminateBtn *walk.PushButton normalBtn *walk.PushButton errBtn *walk.PushButton pausedBtn *walk.PushButton startBtn *walk.PushButton } func (w *MyDialog) init(owner walk.Form) (err error) { if w.Dialog, err = walk.NewDialog(owner); err != nil { return err } succeeded := false defer func() { if !succeeded { w.Dispose() } }() var font *walk.Font if font == nil { font = nil } w.SetName("Dialog") if err := w.SetClientSize(walk.Size{598, 300}); err != nil { return err } if err := w.SetTitle(`Dialog`); err != nil { return err } // noProgressBtn if w.ui.noProgressBtn, err = walk.NewPushButton(w); err != nil { return err } w.ui.noProgressBtn.SetName("noProgressBtn") if err := w.ui.noProgressBtn.SetBounds(walk.Rectangle{40, 60, 161, 23}); err != nil { return err } if err := w.ui.noProgressBtn.SetText(`NoProgress`); err != nil { return err } if err := w.ui.noProgressBtn.SetMinMaxSize(walk.Size{0, 0}, walk.Size{161, 16777215}); err != nil { return err } // indeterminateBtn if w.ui.indeterminateBtn, err = walk.NewPushButton(w); err != nil { return err } w.ui.indeterminateBtn.SetName("indeterminateBtn") if err := w.ui.indeterminateBtn.SetBounds(walk.Rectangle{40, 90, 161, 23}); err != nil { return err } if err := w.ui.indeterminateBtn.SetText(`Indeterminate`); err != nil { return err } if err := w.ui.indeterminateBtn.SetMinMaxSize(walk.Size{0, 0}, walk.Size{161, 16777215}); err != nil { return err } // normalBtn if w.ui.normalBtn, err = walk.NewPushButton(w); err != nil { return err } w.ui.normalBtn.SetName("normalBtn") if err := w.ui.normalBtn.SetBounds(walk.Rectangle{40, 120, 161, 23}); err != nil { return err } if err := w.ui.normalBtn.SetText(`Normal`); err != nil { return err } if err := w.ui.normalBtn.SetMinMaxSize(walk.Size{0, 0}, walk.Size{161, 16777215}); err != nil { return err } // errBtn if w.ui.errBtn, err = walk.NewPushButton(w); err != nil { return err } w.ui.errBtn.SetName("errBtn") if err := w.ui.errBtn.SetBounds(walk.Rectangle{40, 150, 161, 23}); err != nil { return err } if err := w.ui.errBtn.SetText(`Error`); err != nil { return err } // pausedBtn if w.ui.pausedBtn, err = walk.NewPushButton(w); err != nil { return err } w.ui.pausedBtn.SetName("pausedBtn") if err := w.ui.pausedBtn.SetBounds(walk.Rectangle{40, 180, 161, 23}); err != nil { return err } if err := w.ui.pausedBtn.SetText(`Paused`); err != nil { return err } // startBtn if w.ui.startBtn, err = walk.NewPushButton(w); err != nil { return err } w.ui.startBtn.SetName("startBtn") if err := w.ui.startBtn.SetBounds(walk.Rectangle{290, 180, 75, 23}); err != nil { return err } if err := w.ui.startBtn.SetText(`START`); err != nil { return err } // Tab order succeeded = true return nil } ================================================ FILE: examples/progressindicator/pi.go ================================================ // Copyright 2012 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. package main import ( "fmt" "log" "time" ) import ( "github.com/lxn/walk" ) func main() { if _, err := RunMyDialog(nil); err != nil { log.Fatal(err) } } type MyDialog struct { *walk.Dialog ui myDialogUI } func (dlg *MyDialog) setState(state walk.PIState) { if err := dlg.ProgressIndicator().SetState(state); err != nil { log.Print(err) } } func RunMyDialog(owner walk.Form) (int, error) { dlg := new(MyDialog) if err := dlg.init(owner); err != nil { return 0, err } dlg.ui.indeterminateBtn.Clicked().Attach(func() { fmt.Println("SetState indeterminate") dlg.setState(walk.PIIndeterminate) }) dlg.ui.noProgressBtn.Clicked().Attach(func() { fmt.Println("SetState noprogress") dlg.setState(walk.PINoProgress) }) dlg.ui.normalBtn.Clicked().Attach(func() { fmt.Println("SetState normal") dlg.setState(walk.PINormal) }) dlg.ui.errBtn.Clicked().Attach(func() { fmt.Println("SetState error") dlg.setState(walk.PIError) }) dlg.ui.pausedBtn.Clicked().Attach(func() { fmt.Println("SetState paused") dlg.setState(walk.PIPaused) }) dlg.ui.startBtn.Clicked().Attach(func() { go func() { dlg.ProgressIndicator().SetTotal(100) var i uint32 for i = 0; i < 100; i++ { fmt.Println("SetProgress", i) time.Sleep(100 * time.Millisecond) if err := dlg.ProgressIndicator().SetCompleted(i); err != nil { log.Print(err) } } }() }) return dlg.Run(), nil } ================================================ FILE: examples/progressindicator/progressindicator.exe.manifest ================================================  PerMonitorV2, PerMonitor True ================================================ FILE: examples/radiobutton/radiobutton.exe.manifest ================================================  PerMonitorV2, PerMonitor True ================================================ FILE: examples/radiobutton/radiobutton.go ================================================ // Copyright 2013 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. package main import ( "fmt" ) import ( . "github.com/lxn/walk/declarative" ) type Foo struct { Bar string Baz int } func main() { foo := &Foo{"b", 0} MainWindow{ Title: "Walk RadioButton Example", MinSize: Size{320, 240}, Layout: VBox{}, DataBinder: DataBinder{ DataSource: foo, AutoSubmit: true, OnSubmitted: func() { fmt.Println(foo) }, }, Children: []Widget{ // RadioButtonGroup is needed for data binding only. RadioButtonGroup{ DataMember: "Bar", Buttons: []RadioButton{ RadioButton{ Name: "aRB", Text: "A", Value: "a", }, RadioButton{ Name: "bRB", Text: "B", Value: "b", }, RadioButton{ Name: "cRB", Text: "C", Value: "c", }, }, }, Label{ Text: "A", Enabled: Bind("aRB.Checked"), }, Label{ Text: "B", Enabled: Bind("bRB.Checked"), }, Label{ Text: "C", Enabled: Bind("cRB.Checked"), }, RadioButtonGroup{ DataMember: "Baz", Buttons: []RadioButton{ RadioButton{ Name: "oneRB", Text: "1", Value: 1, }, RadioButton{ Name: "twoRB", Text: "2", Value: 2, }, RadioButton{ Name: "threeRB", Text: "3", Value: 3, }, }, }, Label{ Text: "1", Enabled: Bind("oneRB.Checked"), }, Label{ Text: "2", Enabled: Bind("twoRB.Checked"), }, Label{ Text: "3", Enabled: Bind("threeRB.Checked"), }, }, }.Run() } ================================================ FILE: examples/settings/settings.exe.manifest ================================================  PerMonitorV2, PerMonitor True ================================================ FILE: examples/settings/settings.go ================================================ // Copyright 2013 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. package main import ( "log" "math/rand" "strings" "time" ) import ( "github.com/lxn/walk" . "github.com/lxn/walk/declarative" ) func main() { app := walk.App() // These specify the app data sub directory for the settings file. app.SetOrganizationName("The Walk Authors") app.SetProductName("Walk Settings Example") // Settings file name. settings := walk.NewIniFileSettings("settings.ini") // All settings marked as expiring will expire after this duration w/o use. // This applies to all widgets settings. settings.SetExpireDuration(time.Hour * 24 * 30 * 3) if err := settings.Load(); err != nil { log.Fatal(err) } app.SetSettings(settings) if err := RunMainWindow(); err != nil { log.Fatal(err) } if err := settings.Save(); err != nil { log.Fatal(err) } } func RunMainWindow() error { if _, err := (MainWindow{ Name: "mainWindow", // Name is needed for settings persistence Title: "Walk Settings Example", MinSize: Size{800, 600}, Layout: VBox{MarginsZero: true}, Children: []Widget{ TableView{ Name: "tableView", // Name is needed for settings persistence AlternatingRowBG: true, ColumnsOrderable: true, Columns: []TableViewColumn{ // Name is needed for settings persistence {Name: "#", DataMember: "Index"}, // Use DataMember, if names differ {Name: "Bar"}, {Name: "Baz", Format: "%.2f", Alignment: AlignFar}, {Name: "Quux", Format: "2006-01-02 15:04:05", Width: 150}, }, Model: NewFooModel(), }}, }.Run()); err != nil { return err } return nil } func NewFooModel() *FooModel { now := time.Now() rand.Seed(now.UnixNano()) m := &FooModel{items: make([]*Foo, 1000)} for i := range m.items { m.items[i] = &Foo{ Index: i, Bar: strings.Repeat("*", rand.Intn(5)+1), Baz: rand.Float64() * 1000, Quux: time.Unix(rand.Int63n(now.Unix()), 0), } } return m } type FooModel struct { walk.SortedReflectTableModelBase items []*Foo } func (m *FooModel) Items() interface{} { return m.items } type Foo struct { Index int Bar string Baz float64 Quux time.Time } ================================================ FILE: examples/slider/slider.exe.manifest ================================================  PerMonitorV2, PerMonitor True ================================================ FILE: examples/slider/slider.go ================================================ // Copyright 2016 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. package main import ( "log" "github.com/lxn/walk" . "github.com/lxn/walk/declarative" ) func main() { var slv, slh *walk.Slider var maxEdit, minEdit, valueEdit *walk.NumberEdit data := struct{ Min, Max, Value int }{0, 100, 30} MainWindow{ Title: "Walk Slider Example", MinSize: Size{320, 240}, Layout: HBox{}, Children: []Widget{ Slider{ AssignTo: &slv, MinValue: data.Min, MaxValue: data.Max, Value: data.Value, Orientation: Vertical, OnValueChanged: func() { data.Value = slv.Value() valueEdit.SetValue(float64(data.Value)) }, }, Composite{ Layout: Grid{Columns: 3}, StretchFactor: 4, Children: []Widget{ Label{Text: "Min value"}, Label{Text: "Value"}, Label{Text: "Max value"}, NumberEdit{ AssignTo: &minEdit, Value: float64(data.Min), OnValueChanged: func() { data.Min = int(minEdit.Value()) slh.SetRange(data.Min, data.Max) slv.SetRange(data.Min, data.Max) }, }, NumberEdit{ AssignTo: &valueEdit, Value: float64(data.Value), OnValueChanged: func() { data.Value = int(valueEdit.Value()) slh.SetValue(data.Value) slv.SetValue(data.Value) }, }, NumberEdit{ AssignTo: &maxEdit, Value: float64(data.Max), OnValueChanged: func() { data.Max = int(maxEdit.Value()) slh.SetRange(data.Min, data.Max) slv.SetRange(data.Min, data.Max) }, }, Slider{ ColumnSpan: 3, AssignTo: &slh, MinValue: data.Min, MaxValue: data.Max, Value: data.Value, OnValueChanged: func() { data.Value = slh.Value() valueEdit.SetValue(float64(data.Value)) }, }, VSpacer{}, PushButton{ ColumnSpan: 3, Text: "Print state", OnClicked: func() { log.Printf("H: < %d | %d | %d >\n", slh.MinValue(), slh.Value(), slh.MaxValue()) log.Printf("V: < %d | %d | %d >\n", slv.MinValue(), slv.Value(), slv.MaxValue()) }, }, }, }, }, }.Run() } ================================================ FILE: examples/statusbar/statusbar.exe.manifest ================================================  PerMonitorV2, PerMonitor True ================================================ FILE: examples/statusbar/statusbar.go ================================================ // Copyright 2017 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. // This example demonstrates the status bar, including a size gripper // attached to the bottom of the main window. // The status bar has two items, one is dynamically updated and one includes an icon. package main import ( "log" "github.com/lxn/walk" . "github.com/lxn/walk/declarative" ) func main() { icon1, err := walk.NewIconFromFile("../img/check.ico") if err != nil { log.Fatal(err) } icon2, err := walk.NewIconFromFile("../img/stop.ico") if err != nil { log.Fatal(err) } var sbi *walk.StatusBarItem MainWindow{ Title: "Walk Statusbar Example", MinSize: Size{600, 200}, Layout: VBox{MarginsZero: true}, StatusBarItems: []StatusBarItem{ StatusBarItem{ AssignTo: &sbi, Icon: icon1, Text: "click", Width: 80, OnClicked: func() { if sbi.Text() == "click" { sbi.SetText("again") sbi.SetIcon(icon2) } else { sbi.SetText("click") sbi.SetIcon(icon1) } }, }, StatusBarItem{ Text: "left", ToolTipText: "no tooltip for me", }, StatusBarItem{ Text: "\tcenter", }, StatusBarItem{ Text: "\t\tright", }, StatusBarItem{ Icon: icon1, ToolTipText: "An icon with a tooltip", }, }, }.Run() } ================================================ FILE: examples/tableview/tableview.exe.manifest ================================================  PerMonitorV2, PerMonitor True ================================================ FILE: examples/tableview/tableview.go ================================================ // Copyright 2011 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. package main import ( "fmt" "math/rand" "sort" "strings" "time" ) import ( "github.com/lxn/walk" . "github.com/lxn/walk/declarative" ) type Foo struct { Index int Bar string Baz float64 Quux time.Time checked bool } type FooModel struct { walk.TableModelBase walk.SorterBase sortColumn int sortOrder walk.SortOrder items []*Foo } func NewFooModel() *FooModel { m := new(FooModel) m.ResetRows() return m } // Called by the TableView from SetModel and every time the model publishes a // RowsReset event. func (m *FooModel) RowCount() int { return len(m.items) } // Called by the TableView when it needs the text to display for a given cell. func (m *FooModel) Value(row, col int) interface{} { item := m.items[row] switch col { case 0: return item.Index case 1: return item.Bar case 2: return item.Baz case 3: return item.Quux } panic("unexpected col") } // Called by the TableView to retrieve if a given row is checked. func (m *FooModel) Checked(row int) bool { return m.items[row].checked } // Called by the TableView when the user toggled the check box of a given row. func (m *FooModel) SetChecked(row int, checked bool) error { m.items[row].checked = checked return nil } // Called by the TableView to sort the model. func (m *FooModel) Sort(col int, order walk.SortOrder) error { m.sortColumn, m.sortOrder = col, order sort.SliceStable(m.items, func(i, j int) bool { a, b := m.items[i], m.items[j] c := func(ls bool) bool { if m.sortOrder == walk.SortAscending { return ls } return !ls } switch m.sortColumn { case 0: return c(a.Index < b.Index) case 1: return c(a.Bar < b.Bar) case 2: return c(a.Baz < b.Baz) case 3: return c(a.Quux.Before(b.Quux)) } panic("unreachable") }) return m.SorterBase.Sort(col, order) } func (m *FooModel) ResetRows() { // Create some random data. m.items = make([]*Foo, rand.Intn(50000)) now := time.Now() for i := range m.items { m.items[i] = &Foo{ Index: i, Bar: strings.Repeat("*", rand.Intn(5)+1), Baz: rand.Float64() * 1000, Quux: time.Unix(rand.Int63n(now.Unix()), 0), } } // Notify TableView and other interested parties about the reset. m.PublishRowsReset() m.Sort(m.sortColumn, m.sortOrder) } func main() { rand.Seed(time.Now().UnixNano()) boldFont, _ := walk.NewFont("Segoe UI", 9, walk.FontBold) goodIcon, _ := walk.Resources.Icon("../img/check.ico") badIcon, _ := walk.Resources.Icon("../img/stop.ico") barBitmap, err := walk.NewBitmap(walk.Size{100, 1}) if err != nil { panic(err) } defer barBitmap.Dispose() canvas, err := walk.NewCanvasFromImage(barBitmap) if err != nil { panic(err) } defer barBitmap.Dispose() canvas.GradientFillRectangle(walk.RGB(255, 0, 0), walk.RGB(0, 255, 0), walk.Horizontal, walk.Rectangle{0, 0, 100, 1}) canvas.Dispose() model := NewFooModel() var tv *walk.TableView MainWindow{ Title: "Walk TableView Example", Size: Size{800, 600}, Layout: VBox{MarginsZero: true}, Children: []Widget{ PushButton{ Text: "Reset Rows", OnClicked: model.ResetRows, }, PushButton{ Text: "Select first 5 even Rows", OnClicked: func() { tv.SetSelectedIndexes([]int{0, 2, 4, 6, 8}) }, }, TableView{ AssignTo: &tv, AlternatingRowBG: true, CheckBoxes: true, ColumnsOrderable: true, MultiSelection: true, Columns: []TableViewColumn{ {Title: "#"}, {Title: "Bar"}, {Title: "Baz", Alignment: AlignFar}, {Title: "Quux", Format: "2006-01-02 15:04:05", Width: 150}, }, StyleCell: func(style *walk.CellStyle) { item := model.items[style.Row()] if item.checked { if style.Row()%2 == 0 { style.BackgroundColor = walk.RGB(159, 215, 255) } else { style.BackgroundColor = walk.RGB(143, 199, 239) } } switch style.Col() { case 1: if canvas := style.Canvas(); canvas != nil { bounds := style.Bounds() bounds.X += 2 bounds.Y += 2 bounds.Width = int((float64(bounds.Width) - 4) / 5 * float64(len(item.Bar))) bounds.Height -= 4 canvas.DrawBitmapPartWithOpacity(barBitmap, bounds, walk.Rectangle{0, 0, 100 / 5 * len(item.Bar), 1}, 127) bounds.X += 4 bounds.Y += 2 canvas.DrawText(item.Bar, tv.Font(), 0, bounds, walk.TextLeft) } case 2: if item.Baz >= 900.0 { style.TextColor = walk.RGB(0, 191, 0) style.Image = goodIcon } else if item.Baz < 100.0 { style.TextColor = walk.RGB(255, 0, 0) style.Image = badIcon } case 3: if item.Quux.After(time.Now().Add(-365 * 24 * time.Hour)) { style.Font = boldFont } } }, Model: model, OnSelectedIndexesChanged: func() { fmt.Printf("SelectedIndexes: %v\n", tv.SelectedIndexes()) }, }, }, }.Run() } ================================================ FILE: examples/webview/webview.exe.manifest ================================================  PerMonitorV2, PerMonitor True ================================================ FILE: examples/webview/webview.go ================================================ // 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. package main import ( "strings" "github.com/lxn/walk" . "github.com/lxn/walk/declarative" ) func main() { var le *walk.LineEdit var wv *walk.WebView MainWindow{ Icon: Bind("'../img/' + icon(wv.URL) + '.ico'"), Title: "Walk WebView Example'", MinSize: Size{800, 600}, Layout: VBox{MarginsZero: true}, Children: []Widget{ LineEdit{ AssignTo: &le, Text: Bind("wv.URL"), OnKeyDown: func(key walk.Key) { if key == walk.KeyReturn { wv.SetURL(le.Text()) } }, }, WebView{ AssignTo: &wv, Name: "wv", URL: "https://github.com/lxn/walk", }, }, Functions: map[string]func(args ...interface{}) (interface{}, error){ "icon": func(args ...interface{}) (interface{}, error) { if strings.HasPrefix(args[0].(string), "https") { return "check", nil } return "stop", nil }, }, }.Run() } ================================================ FILE: examples/webview_events/webview_events.exe.manifest ================================================  PerMonitorV2, PerMonitor True ================================================ FILE: examples/webview_events/webview_events.go ================================================ // 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. package main import ( "fmt" "log" "strings" "github.com/lxn/walk" . "github.com/lxn/walk/declarative" ) type MainWin struct { *walk.MainWindow le *walk.LineEdit wv *walk.WebView } func main() { mainWin, err := NewMainWin() if err != nil { log.Fatal(err) } mainWin.Run() } func NewMainWin() (*MainWin, error) { mainWin := new(MainWin) err := MainWindow{ AssignTo: &mainWin.MainWindow, Icon: Bind("'../img/' + icon(mainWin.wv.URL) + '.ico'"), Title: "Walk WebView Example (With Events Printing)", MinSize: Size{800, 600}, Layout: VBox{MarginsZero: true}, Children: []Widget{ LineEdit{ AssignTo: &mainWin.le, Text: Bind("wv.URL"), OnKeyDown: func(key walk.Key) { if key == walk.KeyReturn { mainWin.wv.SetURL(mainWin.le.Text()) } }, }, WebView{ AssignTo: &mainWin.wv, Name: "wv", URL: "https://github.com/lxn/walk", ShortcutsEnabled: true, NativeContextMenuEnabled: true, OnNavigating: mainWin.webView_OnNavigating, OnNavigated: mainWin.webView_OnNavigated, OnDownloading: mainWin.webView_OnDownloading, OnDownloaded: mainWin.webView_OnDownloaded, OnDocumentCompleted: mainWin.webView_OnDocumentCompleted, OnNavigatedError: mainWin.webView_OnNavigatedError, OnNewWindow: mainWin.webView_OnNewWindow, OnQuitting: mainWin.webView_OnQuitting, OnWindowClosing: mainWin.webView_OnWindowClosing, OnStatusBarVisibleChanged: mainWin.webView_OnStatusBarVisibleChanged, OnTheaterModeChanged: mainWin.webView_OnTheaterModeChanged, OnToolBarVisibleChanged: mainWin.webView_OnToolBarVisibleChanged, OnBrowserVisibleChanged: mainWin.webView_OnBrowserVisibleChanged, OnCanGoBackChanged: mainWin.webView_OnCanGoBackChanged, OnCanGoForwardChanged: mainWin.webView_OnCanGoForwardChanged, OnToolBarEnabledChanged: mainWin.webView_OnToolBarEnabledChanged, OnProgressChanged: mainWin.webView_OnProgressChanged, OnStatusTextChanged: mainWin.webView_OnStatusTextChanged, OnDocumentTitleChanged: mainWin.webView_OnDocumentTitleChanged, }, }, Functions: map[string]func(args ...interface{}) (interface{}, error){ "icon": func(args ...interface{}) (interface{}, error) { if strings.HasPrefix(args[0].(string), "https") { return "check", nil } return "stop", nil }, }, }.Create() return mainWin, err } func (mainWin *MainWin) webView_OnNavigating(eventData *walk.WebViewNavigatingEventData) { fmt.Printf("webView_OnNavigating\r\n") fmt.Printf("Url = %+v\r\n", eventData.Url()) fmt.Printf("Flags = %+v\r\n", eventData.Flags()) fmt.Printf("PostData = %+v\r\n", eventData.PostData()) fmt.Printf("Headers = %+v\r\n", eventData.Headers()) fmt.Printf("TargetFrameName = %+v\r\n", eventData.TargetFrameName()) fmt.Printf("Canceled = %+v\r\n", eventData.Canceled()) // if you want to cancel //eventData.SetCanceled(true) } func (mainWin *MainWin) webView_OnNavigated(url string) { fmt.Printf("webView_OnNavigated\r\n") fmt.Printf("url = %+v\r\n", url) } func (mainWin *MainWin) webView_OnDownloading() { fmt.Printf("webView_OnDownloading\r\n") } func (mainWin *MainWin) webView_OnDownloaded() { fmt.Printf("webView_OnDownloaded\r\n") } func (mainWin *MainWin) webView_OnDocumentCompleted(url string) { fmt.Printf("webView_OnDocumentCompleted\r\n") fmt.Printf("url = %+v\r\n", url) } func (mainWin *MainWin) webView_OnNavigatedError(eventData *walk.WebViewNavigatedErrorEventData) { fmt.Printf("webView_OnNavigatedError\r\n") fmt.Printf("Url = %+v\r\n", eventData.Url()) fmt.Printf("TargetFrameName = %+v\r\n", eventData.TargetFrameName()) fmt.Printf("StatusCode = %+v\r\n", eventData.StatusCode()) fmt.Printf("Canceled = %+v\r\n", eventData.Canceled()) // if you want to cancel //eventData.SetCanceled(true) } func (mainWin *MainWin) webView_OnNewWindow(eventData *walk.WebViewNewWindowEventData) { fmt.Printf("webView_OnNewWindow\r\n") fmt.Printf("Canceled = %+v\r\n", eventData.Canceled()) fmt.Printf("Flags = %+v\r\n", eventData.Flags()) fmt.Printf("UrlContext = %+v\r\n", eventData.UrlContext()) fmt.Printf("Url = %+v\r\n", eventData.Url()) // if you want to cancel //eventData.SetCancel(true) } func (mainWin *MainWin) webView_OnQuitting() { fmt.Printf("webView_OnQuitting\r\n") } func (mainWin *MainWin) webView_OnWindowClosing(eventData *walk.WebViewWindowClosingEventData) { fmt.Printf("webView_OnWindowClosing\r\n") fmt.Printf("IsChildWindow = %+v\r\n", eventData.IsChildWindow()) fmt.Printf("Canceled = %+v\r\n", eventData.Canceled()) // if you want to cancel //eventData.SetCancel(true) } func (mainWin *MainWin) webView_OnStatusBarVisibleChanged() { fmt.Printf("webView_OnStatusBarVisibleChanged\r\n") fmt.Printf("StatusBarVisible = %+v\r\n", mainWin.wv.StatusBarVisible()) } func (mainWin *MainWin) webView_OnTheaterModeChanged() { fmt.Printf("webView_OnTheaterModeChanged\r\n") fmt.Printf("IsTheaterMode = %+v\r\n", mainWin.wv.IsTheaterMode()) } func (mainWin *MainWin) webView_OnToolBarVisibleChanged() { fmt.Printf("webView_OnToolBarVisibleChanged\r\n") fmt.Printf("ToolBarVisible = %+v\r\n", mainWin.wv.ToolBarVisible()) } func (mainWin *MainWin) webView_OnBrowserVisibleChanged() { fmt.Printf("webView_OnBrowserVisibleChanged\r\n") fmt.Printf("BrowserVisible = %+v\r\n", mainWin.wv.BrowserVisible()) } func (mainWin *MainWin) webView_OnCanGoBackChanged() { fmt.Printf("webView_OnCanGoBackChanged\r\n") fmt.Printf("CanGoBack = %+v\r\n", mainWin.wv.CanGoBack()) } func (mainWin *MainWin) webView_OnCanGoForwardChanged() { fmt.Printf("webView_OnCanGoForwardChanged\r\n") fmt.Printf("CanGoForward = %+v\r\n", mainWin.wv.CanGoForward()) } func (mainWin *MainWin) webView_OnToolBarEnabledChanged() { fmt.Printf("webView_OnToolBarEnabledChanged\r\n") fmt.Printf("ToolBarEnabled = %+v\r\n", mainWin.wv.ToolBarEnabled()) } func (mainWin *MainWin) webView_OnProgressChanged() { fmt.Printf("webView_OnProgressChanged\r\n") fmt.Printf("ProgressValue = %+v\r\n", mainWin.wv.ProgressValue()) fmt.Printf("ProgressMax = %+v\r\n", mainWin.wv.ProgressMax()) } func (mainWin *MainWin) webView_OnStatusTextChanged() { fmt.Printf("webView_OnStatusTextChanged\r\n") fmt.Printf("StatusText = %+v\r\n", mainWin.wv.StatusText()) } func (mainWin *MainWin) webView_OnDocumentTitleChanged() { fmt.Printf("webView_OnDocumentTitleChanged\r\n") fmt.Printf("DocumentTitle = %+v\r\n", mainWin.wv.DocumentTitle()) } ================================================ FILE: expression.go ================================================ // Copyright 2017 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 ( "log" "reflect" ) import _ "gopkg.in/Knetic/govaluate.v3" type Expression interface { Value() interface{} Changed() *Event } type reflectExpression struct { root Expression path string } func NewReflectExpression(root Expression, path string) Expression { return &reflectExpression{root: root, path: path} } func (re *reflectExpression) Value() interface{} { rootVal := re.root.Value() if rootVal == nil { return nil } _, val, err := reflectValueFromPath(reflect.ValueOf(rootVal), re.path) if err != nil { log.Print("walk - reflectExpression.Value - Error: ", err.Error()) } if !val.IsValid() { return nil } return val.Interface() } func (re *reflectExpression) Changed() *Event { return re.root.Changed() } ================================================ FILE: flowlayout.go ================================================ // Copyright 2018 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 ( "github.com/lxn/win" ) type FlowLayout struct { LayoutBase hwnd2StretchFactor map[win.HWND]int } func NewFlowLayout() *FlowLayout { l := &FlowLayout{ LayoutBase: LayoutBase{ margins96dpi: Margins{9, 9, 9, 9}, spacing96dpi: 6, }, hwnd2StretchFactor: make(map[win.HWND]int), } l.layout = l return l } func (l *FlowLayout) StretchFactor(widget Widget) int { if factor, ok := l.hwnd2StretchFactor[widget.Handle()]; ok { return factor } return 1 } func (l *FlowLayout) SetStretchFactor(widget Widget, factor int) error { if factor != l.StretchFactor(widget) { if l.container == nil { return newError("container required") } handle := widget.Handle() if !l.container.Children().containsHandle(handle) { return newError("unknown widget") } if factor < 1 { return newError("factor must be >= 1") } l.hwnd2StretchFactor[handle] = factor l.container.RequestLayout() } return nil } func (l *FlowLayout) CreateLayoutItem(ctx *LayoutContext) ContainerLayoutItem { li := &flowLayoutItem{ size2MinSize: make(map[Size]Size), hwnd2StretchFactor: make(map[win.HWND]int), } for hwnd, sf := range l.hwnd2StretchFactor { li.hwnd2StretchFactor[hwnd] = sf } return li } type flowLayoutItem struct { ContainerLayoutItemBase size2MinSize map[Size]Size // in native pixels hwnd2StretchFactor map[win.HWND]int } type flowLayoutSection struct { items []flowLayoutSectionItem primarySpaceLeft int // in native pixels secondaryMinSize int // in native pixels } type flowLayoutSectionItem struct { item LayoutItem minSize Size // in native pixels } func (*flowLayoutItem) LayoutFlags() LayoutFlags { return ShrinkableHorz | ShrinkableVert | GrowableHorz | GrowableVert | GreedyHorz | GreedyVert } func (li *flowLayoutItem) MinSize() Size { return li.MinSizeForSize(li.geometry.ClientSize) } func (li *flowLayoutItem) HeightForWidth(width int) int { return li.MinSizeForSize(Size{width, li.geometry.ClientSize.Height}).Height } func (li *flowLayoutItem) MinSizeForSize(size Size) Size { if min, ok := li.size2MinSize[size]; ok { return min } spacing := IntFrom96DPI(li.spacing96dpi, li.ctx.dpi) margins := MarginsFrom96DPI(li.margins96dpi, li.ctx.dpi) bounds := Rectangle{Width: size.Width} sections := li.sectionsForPrimarySize(size.Width) var s Size var maxPrimary int for i, section := range sections { var items []LayoutItem var sectionMinWidth int for _, sectionItem := range section.items { items = append(items, sectionItem.item) sectionMinWidth += sectionItem.minSize.Width } sectionMinWidth += (len(section.items) - 1) * spacing maxPrimary = maxi(maxPrimary, sectionMinWidth) bounds.Height = section.secondaryMinSize margins96dpi := li.margins96dpi if i > 0 { margins96dpi.VNear = 0 } if i < len(sections)-1 { margins96dpi.VFar = 0 } layoutItems := boxLayoutItems(li, items, Horizontal, li.alignment, bounds, margins96dpi, li.spacing96dpi, li.hwnd2StretchFactor) var maxSecondary int for _, item := range layoutItems { if hfw, ok := item.Item.(HeightForWidther); ok && hfw.HasHeightForWidth() { item.Bounds.Height = hfw.HeightForWidth(item.Bounds.Width) } else { min := li.MinSizeEffectiveForChild(item.Item) item.Bounds.Height = min.Height } maxSecondary = maxi(maxSecondary, item.Bounds.Height) } s.Height += maxSecondary bounds.Y += maxSecondary + spacing } s.Width = maxPrimary s.Width += margins.HNear + margins.HFar s.Height += margins.VNear + margins.VFar + (len(sections)-1)*spacing if s.Width > 0 && s.Height > 0 { li.size2MinSize[size] = s } return s } func (li *flowLayoutItem) PerformLayout() []LayoutResultItem { spacing := IntFrom96DPI(li.spacing96dpi, li.ctx.dpi) bounds := Rectangle{Width: li.geometry.ClientSize.Width, Height: li.geometry.ClientSize.Height} sections := li.sectionsForPrimarySize(bounds.Width) var resultItems []LayoutResultItem for i, section := range sections { var items []LayoutItem for _, sectionItem := range section.items { items = append(items, sectionItem.item) } bounds.Height = section.secondaryMinSize margins96dpi := li.margins96dpi if i > 0 { margins96dpi.VNear = 0 } if i < len(sections)-1 { margins96dpi.VFar = 0 } layoutItems := boxLayoutItems(li, items, Horizontal, li.alignment, bounds, margins96dpi, li.spacing96dpi, li.hwnd2StretchFactor) margins := MarginsFrom96DPI(margins96dpi, li.ctx.dpi) var maxSecondary int for _, item := range layoutItems { if hfw, ok := item.Item.(HeightForWidther); ok && hfw.HasHeightForWidth() { item.Bounds.Height = hfw.HeightForWidth(item.Bounds.Width) } else { item.Bounds.Height = li.MinSizeEffectiveForChild(item.Item).Height } maxSecondary = maxi(maxSecondary, item.Bounds.Height) } bounds.Height = maxSecondary + margins.VNear + margins.VFar resultItems = append(resultItems, boxLayoutItems(li, items, Horizontal, li.alignment, bounds, margins96dpi, li.spacing96dpi, li.hwnd2StretchFactor)...) bounds.Y += bounds.Height + spacing } return resultItems } // sectionsForPrimarySize calculates sections for primary width in native pixels. func (li *flowLayoutItem) sectionsForPrimarySize(primarySize int) []flowLayoutSection { margins := MarginsFrom96DPI(li.margins96dpi, li.ctx.dpi) spacing := IntFrom96DPI(li.spacing96dpi, li.ctx.dpi) var sections []flowLayoutSection section := flowLayoutSection{ primarySpaceLeft: primarySize - margins.HNear - margins.HFar, } addSection := func() { sections = append(sections, section) section.items = nil section.primarySpaceLeft = primarySize - margins.HNear - margins.HFar section.secondaryMinSize = 0 } for _, item := range li.children { var sectionItem flowLayoutSectionItem sectionItem.item = item if !shouldLayoutItem(item) { continue } sectionItem.minSize = li.MinSizeEffectiveForChild(item) addItem := func() { section.items = append(section.items, sectionItem) if len(section.items) > 1 { section.primarySpaceLeft -= spacing } section.primarySpaceLeft -= sectionItem.minSize.Width section.secondaryMinSize = maxi(section.secondaryMinSize, sectionItem.minSize.Height) } if section.primarySpaceLeft < sectionItem.minSize.Width && len(section.items) == 0 { addItem() addSection() } else if section.primarySpaceLeft < spacing+sectionItem.minSize.Width && len(section.items) > 0 { addSection() addItem() } else { addItem() } } if len(section.items) > 0 { addSection() } if len(sections) > 0 { sections[0].secondaryMinSize += margins.VNear sections[len(sections)-1].secondaryMinSize += margins.VFar } return sections } ================================================ FILE: font.go ================================================ // 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" ) import ( "github.com/lxn/win" ) type FontStyle byte // Font style flags const ( FontBold FontStyle = 0x01 FontItalic FontStyle = 0x02 FontUnderline FontStyle = 0x04 FontStrikeOut FontStyle = 0x08 ) var ( defaultFont *Font knownFonts = make(map[fontInfo]*Font) ) func init() { AppendToWalkInit(func() { // Initialize default font var err error if defaultFont, err = NewFont("MS Shell Dlg 2", 8, 0); err != nil { panic("failed to create default font") } }) } type fontInfo struct { family string pointSize int style FontStyle } // Font represents a typographic typeface that is used for text drawing // operations and on many GUI widgets. type Font struct { dpi2hFont map[int]win.HFONT family string pointSize int style FontStyle } // NewFont returns a new Font with the specified attributes. func NewFont(family string, pointSize int, style FontStyle) (*Font, error) { if style > FontBold|FontItalic|FontUnderline|FontStrikeOut { return nil, newError("invalid style") } fi := fontInfo{ family: family, pointSize: pointSize, style: style, } if font, ok := knownFonts[fi]; ok { return font, nil } font := &Font{ family: family, pointSize: pointSize, style: style, } knownFonts[fi] = font return font, nil } func newFontFromLOGFONT(lf *win.LOGFONT, dpi int) (*Font, error) { if lf == nil { return nil, newError("lf cannot be nil") } family := win.UTF16PtrToString(&lf.LfFaceName[0]) pointSize := int(win.MulDiv(lf.LfHeight, 72, int32(dpi))) if pointSize < 0 { pointSize = -pointSize } var style FontStyle if lf.LfWeight > win.FW_NORMAL { style |= FontBold } if lf.LfItalic == win.TRUE { style |= FontItalic } if lf.LfUnderline == win.TRUE { style |= FontUnderline } if lf.LfStrikeOut == win.TRUE { style |= FontStrikeOut } return NewFont(family, pointSize, style) } func (f *Font) createForDPI(dpi int) (win.HFONT, error) { var lf win.LOGFONT lf.LfHeight = -win.MulDiv(int32(f.pointSize), int32(dpi), 72) if f.style&FontBold > 0 { lf.LfWeight = win.FW_BOLD } else { lf.LfWeight = win.FW_NORMAL } if f.style&FontItalic > 0 { lf.LfItalic = 1 } if f.style&FontUnderline > 0 { lf.LfUnderline = 1 } if f.style&FontStrikeOut > 0 { lf.LfStrikeOut = 1 } lf.LfCharSet = win.DEFAULT_CHARSET lf.LfOutPrecision = win.OUT_TT_PRECIS lf.LfClipPrecision = win.CLIP_DEFAULT_PRECIS lf.LfQuality = win.CLEARTYPE_QUALITY lf.LfPitchAndFamily = win.VARIABLE_PITCH | win.FF_SWISS src := syscall.StringToUTF16(f.family) dest := lf.LfFaceName[:] copy(dest, src) hFont := win.CreateFontIndirect(&lf) if hFont == 0 { return 0, newError("CreateFontIndirect failed") } return hFont, nil } // Bold returns if text drawn using the Font appears with // greater weight than normal. func (f *Font) Bold() bool { return f.style&FontBold > 0 } // Dispose releases the os resources that were allocated for the Font. // // The Font can no longer be used for drawing operations or with GUI widgets // after calling this method. It is safe to call Dispose multiple times. func (f *Font) Dispose() { if len(f.dpi2hFont) == 0 { return } for dpi, hFont := range f.dpi2hFont { win.DeleteObject(win.HGDIOBJ(hFont)) delete(f.dpi2hFont, dpi) } } // Family returns the family name of the Font. func (f *Font) Family() string { return f.family } // Italic returns if text drawn using the Font appears slanted. func (f *Font) Italic() bool { return f.style&FontItalic > 0 } // HandleForDPI returns the os resource handle of the font for the specified // DPI value. func (f *Font) handleForDPI(dpi int) win.HFONT { if f.dpi2hFont == nil { f.dpi2hFont = make(map[int]win.HFONT) } else if handle, ok := f.dpi2hFont[dpi]; ok { return handle } hFont, err := f.createForDPI(dpi) if err != nil { return 0 } f.dpi2hFont[dpi] = hFont return hFont } // StrikeOut returns if text drawn using the Font appears striked out. func (f *Font) StrikeOut() bool { return f.style&FontStrikeOut > 0 } // Style returns the combination of style flags of the Font. func (f *Font) Style() FontStyle { return f.style } // Underline returns if text drawn using the font appears underlined. func (f *Font) Underline() bool { return f.style&FontUnderline > 0 } // PointSize returns the size of the Font in point units. func (f *Font) PointSize() int { return f.pointSize } func screenDPI() int { hDC := win.GetDC(0) defer win.ReleaseDC(0, hDC) return int(win.GetDeviceCaps(hDC, win.LOGPIXELSY)) } ================================================ FILE: fontresource.go ================================================ // 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 ( "github.com/lxn/win" "syscall" ) // FontMemResource represents a font resource loaded into memory from // the application's resources. type FontMemResource struct { hFontResource win.HANDLE } func newFontMemResource(resourceName *uint16) (*FontMemResource, error) { hModule := win.HMODULE(win.GetModuleHandle(nil)) if hModule == win.HMODULE(0) { return nil, lastError("GetModuleHandle") } hres := win.FindResource(hModule, resourceName, win.MAKEINTRESOURCE(8) /*RT_FONT*/) if hres == win.HRSRC(0) { return nil, lastError("FindResource") } size := win.SizeofResource(hModule, hres) if size == 0 { return nil, lastError("SizeofResource") } hResLoad := win.LoadResource(hModule, hres) if hResLoad == win.HGLOBAL(0) { return nil, lastError("LoadResource") } ptr := win.LockResource(hResLoad) if ptr == 0 { return nil, lastError("LockResource") } numFonts := uint32(0) hFontResource := win.AddFontMemResourceEx(ptr, size, nil, &numFonts) if hFontResource == win.HANDLE(0) || numFonts == 0 { return nil, lastError("AddFontMemResource") } return &FontMemResource{hFontResource: hFontResource}, nil } // NewFontMemResourceByName function loads a font resource from the executable's resources // using the resource name. // The font must be embedded into resources using corresponding operator in the // application's RC script. func NewFontMemResourceByName(name string) (*FontMemResource, error) { lpstr, err := syscall.UTF16PtrFromString(name) if err != nil { return nil, err } return newFontMemResource(lpstr) } // NewFontMemResourceById function loads a font resource from the executable's resources // using the resource ID. // The font must be embedded into resources using corresponding operator in the // application's RC script. func NewFontMemResourceById(id int) (*FontMemResource, error) { return newFontMemResource(win.MAKEINTRESOURCE(uintptr(id))) } // Dispose removes the font resource from memory func (fmr *FontMemResource) Dispose() { if fmr.hFontResource != 0 { win.RemoveFontMemResourceEx(fmr.hFontResource) fmr.hFontResource = 0 } } ================================================ FILE: form.go ================================================ // Copyright 2012 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 ( "fmt" "math" "sync" "syscall" "time" "unsafe" "github.com/lxn/win" ) type CloseReason byte const ( CloseReasonUnknown CloseReason = iota CloseReasonUser ) var ( syncFuncs struct { m sync.Mutex funcs []func() } syncMsgId uint32 taskbarButtonCreatedMsgId uint32 taskbarCreatedMsgId uint32 ) func init() { AppendToWalkInit(func() { syncMsgId = win.RegisterWindowMessage(syscall.StringToUTF16Ptr("WalkSync")) taskbarButtonCreatedMsgId = win.RegisterWindowMessage(syscall.StringToUTF16Ptr("TaskbarButtonCreated")) taskbarCreatedMsgId = win.RegisterWindowMessage(syscall.StringToUTF16Ptr("TaskbarCreated")) }) } type Form interface { Container AsFormBase() *FormBase Run() int Starting() *Event Closing() *CloseEvent Activating() *Event Deactivating() *Event Activate() error Show() Hide() Title() string SetTitle(title string) error TitleChanged() *Event Icon() Image SetIcon(icon Image) error IconChanged() *Event Owner() Form SetOwner(owner Form) error ProgressIndicator() *ProgressIndicator // RightToLeftLayout returns whether coordinates on the x axis of the // Form increase from right to left. RightToLeftLayout() bool // SetRightToLeftLayout sets whether coordinates on the x axis of the // Form increase from right to left. SetRightToLeftLayout(rtl bool) error } type FormBase struct { WindowBase clientComposite *Composite owner Form stopwatch *stopwatch inProgressEventCount int performLayout chan ContainerLayoutItem layoutResults chan []LayoutResult inSizeLoop chan bool updateStopwatch chan *stopwatch quitLayoutPerformer chan struct{} closingPublisher CloseEventPublisher activatingPublisher EventPublisher deactivatingPublisher EventPublisher startingPublisher EventPublisher titleChangedPublisher EventPublisher iconChangedPublisher EventPublisher progressIndicator *ProgressIndicator icon Image prevFocusHWnd win.HWND proposedSize Size // in native pixels closeReason CloseReason inSizingLoop bool startingLayoutViaSizingLoop bool isInRestoreState bool started bool layoutScheduled bool } func (fb *FormBase) init(form Form) error { var err error if fb.clientComposite, err = NewComposite(form); err != nil { return err } fb.clientComposite.SetName("clientComposite") fb.clientComposite.background = nil fb.clientComposite.children.observer = form.AsFormBase() fb.MustRegisterProperty("Icon", NewProperty( func() interface{} { return fb.Icon() }, func(v interface{}) error { icon, err := IconFrom(v, fb.DPI()) if err != nil { return err } var img Image if icon != nil { img = icon } fb.SetIcon(img) return nil }, fb.iconChangedPublisher.Event())) fb.MustRegisterProperty("Title", NewProperty( func() interface{} { return fb.Title() }, func(v interface{}) error { return fb.SetTitle(assertStringOr(v, "")) }, fb.titleChangedPublisher.Event())) version := win.GetVersion() if (version&0xFF) > 6 || ((version&0xFF) == 6 && (version&0xFF00>>8) > 0) { win.ChangeWindowMessageFilterEx(fb.hWnd, taskbarButtonCreatedMsgId, win.MSGFLT_ALLOW, nil) } fb.performLayout, fb.layoutResults, fb.inSizeLoop, fb.updateStopwatch, fb.quitLayoutPerformer = startLayoutPerformer(fb) return nil } func (fb *FormBase) Dispose() { if fb.hWnd != 0 { fb.quitLayoutPerformer <- struct{}{} } fb.WindowBase.Dispose() } func (fb *FormBase) AsContainerBase() *ContainerBase { if fb.clientComposite == nil { return nil } return fb.clientComposite.AsContainerBase() } func (fb *FormBase) AsFormBase() *FormBase { return fb } func (fb *FormBase) Children() *WidgetList { if fb.clientComposite == nil { return nil } return fb.clientComposite.Children() } func (fb *FormBase) Layout() Layout { if fb.clientComposite == nil { return nil } return fb.clientComposite.Layout() } func (fb *FormBase) SetLayout(value Layout) error { if fb.clientComposite == nil { return newError("clientComposite not initialized") } return fb.clientComposite.SetLayout(value) } func (fb *FormBase) SetBoundsPixels(bounds Rectangle) error { if layout := fb.Layout(); layout != nil { layoutItem := CreateLayoutItemsForContainer(fb) minSize := fb.sizeFromClientSizePixels(layoutItem.MinSizeForSize(bounds.Size())) minSize = fb.sizeFromClientSizePixels(layoutItem.MinSizeForSize(minSize)) if bounds.Width < minSize.Width { bounds.Width = minSize.Width } if bounds.Height < minSize.Height { bounds.Height = minSize.Height } } if err := fb.WindowBase.SetBoundsPixels(bounds); err != nil { return err } fb.proposedSize = bounds.Size() return nil } func (fb *FormBase) fixedSize() bool { return !fb.hasStyleBits(win.WS_THICKFRAME) } func (fb *FormBase) DataBinder() *DataBinder { return fb.clientComposite.DataBinder() } func (fb *FormBase) SetDataBinder(db *DataBinder) { fb.clientComposite.SetDataBinder(db) } func (fb *FormBase) SetSuspended(suspended bool) { if suspended == fb.suspended { return } fb.suspended = suspended if fb.clientComposite != nil { fb.clientComposite.SetSuspended(suspended) } } func (fb *FormBase) MouseDown() *MouseEvent { return fb.clientComposite.MouseDown() } func (fb *FormBase) MouseMove() *MouseEvent { return fb.clientComposite.MouseMove() } func (fb *FormBase) MouseUp() *MouseEvent { return fb.clientComposite.MouseUp() } func (fb *FormBase) onInsertingWidget(index int, widget Widget) error { return fb.clientComposite.onInsertingWidget(index, widget) } func (fb *FormBase) onInsertedWidget(index int, widget Widget) error { return fb.clientComposite.onInsertedWidget(index, widget) } func (fb *FormBase) onRemovingWidget(index int, widget Widget) error { return fb.clientComposite.onRemovingWidget(index, widget) } func (fb *FormBase) onRemovedWidget(index int, widget Widget) error { return fb.clientComposite.onRemovedWidget(index, widget) } func (fb *FormBase) onClearingWidgets() error { return fb.clientComposite.onClearingWidgets() } func (fb *FormBase) onClearedWidgets() error { return fb.clientComposite.onClearedWidgets() } func (fb *FormBase) ContextMenu() *Menu { return fb.clientComposite.ContextMenu() } func (fb *FormBase) SetContextMenu(contextMenu *Menu) { fb.clientComposite.SetContextMenu(contextMenu) } func (fb *FormBase) ContextMenuLocation() Point { return fb.clientComposite.ContextMenuLocation() } func (fb *FormBase) applyEnabled(enabled bool) { fb.WindowBase.applyEnabled(enabled) fb.clientComposite.applyEnabled(enabled) } func (fb *FormBase) applyFont(font *Font) { fb.WindowBase.applyFont(font) fb.clientComposite.applyFont(font) } func (fb *FormBase) ApplySysColors() { fb.WindowBase.ApplySysColors() fb.clientComposite.ApplySysColors() } func (fb *FormBase) Background() Brush { return fb.clientComposite.Background() } func (fb *FormBase) SetBackground(background Brush) { fb.clientComposite.SetBackground(background) } func (fb *FormBase) Title() string { return fb.text() } func (fb *FormBase) SetTitle(value string) error { return fb.setText(value) } func (fb *FormBase) TitleChanged() *Event { return fb.titleChangedPublisher.Event() } // RightToLeftLayout returns whether coordinates on the x axis of the // FormBase increase from right to left. func (fb *FormBase) RightToLeftLayout() bool { return fb.hasExtendedStyleBits(win.WS_EX_LAYOUTRTL) } // SetRightToLeftLayout sets whether coordinates on the x axis of the // FormBase increase from right to left. func (fb *FormBase) SetRightToLeftLayout(rtl bool) error { return fb.ensureExtendedStyleBits(win.WS_EX_LAYOUTRTL, rtl) } func (fb *FormBase) Run() int { if fb.owner != nil { win.EnableWindow(fb.owner.Handle(), false) invalidateDescendentBorders := func() { walkDescendants(fb.owner, func(wnd Window) bool { if widget, ok := wnd.(Widget); ok { widget.AsWidgetBase().invalidateBorderInParent() } return true }) } invalidateDescendentBorders() defer invalidateDescendentBorders() } fb.started = true fb.startingPublisher.Publish() fb.SetBoundsPixels(fb.BoundsPixels()) if fb.proposedSize == (Size{}) { fb.proposedSize = maxSize(SizeFrom96DPI(fb.minSize96dpi, fb.DPI()), fb.SizePixels()) if !fb.Suspended() { fb.startLayout() } } fb.SetSuspended(false) return fb.mainLoop() } func (fb *FormBase) handleKeyDown(msg *win.MSG) bool { ret := false key, mods := Key(msg.WParam), ModifiersDown() // Tabbing if key == KeyTab && (mods&ModControl) != 0 { doTabbing := func(tw *TabWidget) { index := tw.CurrentIndex() if (mods & ModShift) != 0 { index-- if index < 0 { index = tw.Pages().Len() - 1 } } else { index++ if index >= tw.Pages().Len() { index = 0 } } tw.SetCurrentIndex(index) } hwnd := win.GetFocus() LOOP: for hwnd != 0 { window := windowFromHandle(hwnd) switch widget := window.(type) { case nil: case *TabWidget: doTabbing(widget) return true case Widget: default: break LOOP } hwnd = win.GetParent(hwnd) } walkDescendants(fb.window, func(w Window) bool { if tw, ok := w.(*TabWidget); ok { doTabbing(tw) ret = true return false } return true }) if ret { return true } } // Shortcut actions hwnd := msg.HWnd for hwnd != 0 { if window := windowFromHandle(hwnd); window != nil { wb := window.AsWindowBase() if wb.shortcutActions != nil { for _, action := range wb.shortcutActions.actions { if action.shortcut.Key == key && action.shortcut.Modifiers == mods && action.Visible() && action.Enabled() { action.raiseTriggered() return true } } } } hwnd = win.GetParent(hwnd) } // WebView walkDescendants(fb.window, func(w Window) bool { if webView, ok := w.(*WebView); ok { webViewHWnd := webView.Handle() if webViewHWnd == msg.HWnd || win.IsChild(webViewHWnd, msg.HWnd) { _ret := webView.translateAccelerator(msg) if _ret { ret = _ret } } } return true }) return ret } func (fb *FormBase) Starting() *Event { return fb.startingPublisher.Event() } func (fb *FormBase) Activating() *Event { return fb.activatingPublisher.Event() } func (fb *FormBase) Deactivating() *Event { return fb.deactivatingPublisher.Event() } func (fb *FormBase) Activate() error { if hwndPrevActive := win.SetActiveWindow(fb.hWnd); hwndPrevActive == 0 { return lastError("SetActiveWindow") } return nil } func (fb *FormBase) Owner() Form { return fb.owner } func (fb *FormBase) SetOwner(value Form) error { fb.owner = value var ownerHWnd win.HWND if value != nil { ownerHWnd = value.Handle() } win.SetLastError(0) if 0 == win.SetWindowLong( fb.hWnd, win.GWL_HWNDPARENT, int32(ownerHWnd)) && win.GetLastError() != 0 { return lastError("SetWindowLong") } return nil } func (fb *FormBase) Icon() Image { return fb.icon } func (fb *FormBase) SetIcon(icon Image) error { var hIconSmall, hIconBig uintptr if icon != nil { dpi := fb.DPI() size96dpi := icon.Size() smallHeight := int(win.GetSystemMetricsForDpi(win.SM_CYSMICON, uint32(dpi))) smallDPI := int(math.Round(float64(smallHeight) / float64(size96dpi.Height) * 96.0)) smallIcon, err := iconCache.Icon(icon, smallDPI) if err != nil { return err } hIconSmall = uintptr(smallIcon.handleForDPI(smallDPI)) bigHeight := int(win.GetSystemMetricsForDpi(win.SM_CYICON, uint32(dpi))) bigDPI := int(math.Round(float64(bigHeight) / float64(size96dpi.Height) * 96.0)) bigIcon, err := iconCache.Icon(icon, bigDPI) if err != nil { return err } hIconBig = uintptr(bigIcon.handleForDPI(bigDPI)) } fb.SendMessage(win.WM_SETICON, 0, hIconSmall) fb.SendMessage(win.WM_SETICON, 1, hIconBig) fb.icon = icon fb.iconChangedPublisher.Publish() return nil } func (fb *FormBase) IconChanged() *Event { return fb.iconChangedPublisher.Event() } func (fb *FormBase) Hide() { fb.window.SetVisible(false) } func (fb *FormBase) Show() { fb.proposedSize = maxSize(SizeFrom96DPI(fb.minSize96dpi, fb.DPI()), fb.SizePixels()) if p, ok := fb.window.(Persistable); ok && p.Persistent() && App().Settings() != nil { p.RestoreState() } fb.window.SetVisible(true) } func (fb *FormBase) close() error { if p, ok := fb.window.(Persistable); ok && p.Persistent() && App().Settings() != nil { p.SaveState() } fb.window.Dispose() return nil } func (fb *FormBase) Close() error { fb.SendMessage(win.WM_CLOSE, 0, 0) return nil } func (fb *FormBase) Persistent() bool { return fb.clientComposite.persistent } func (fb *FormBase) SetPersistent(value bool) { fb.clientComposite.persistent = value } func (fb *FormBase) SaveState() error { if err := fb.clientComposite.SaveState(); err != nil { return err } var wp win.WINDOWPLACEMENT wp.Length = uint32(unsafe.Sizeof(wp)) if !win.GetWindowPlacement(fb.hWnd, &wp) { return lastError("GetWindowPlacement") } state := fmt.Sprint( wp.Flags, wp.ShowCmd, wp.PtMinPosition.X, wp.PtMinPosition.Y, wp.PtMaxPosition.X, wp.PtMaxPosition.Y, wp.RcNormalPosition.Left, wp.RcNormalPosition.Top, wp.RcNormalPosition.Right, wp.RcNormalPosition.Bottom) if err := fb.WriteState(state); err != nil { return err } return nil } func (fb *FormBase) RestoreState() error { if fb.isInRestoreState { return nil } fb.isInRestoreState = true defer func() { fb.isInRestoreState = false }() state, err := fb.ReadState() if err != nil { return err } if state == "" { return nil } var wp win.WINDOWPLACEMENT if _, err := fmt.Sscan(state, &wp.Flags, &wp.ShowCmd, &wp.PtMinPosition.X, &wp.PtMinPosition.Y, &wp.PtMaxPosition.X, &wp.PtMaxPosition.Y, &wp.RcNormalPosition.Left, &wp.RcNormalPosition.Top, &wp.RcNormalPosition.Right, &wp.RcNormalPosition.Bottom); err != nil { return err } wp.Length = uint32(unsafe.Sizeof(wp)) if layout := fb.Layout(); layout != nil && fb.fixedSize() { layoutItem := CreateLayoutItemsForContainer(fb) minSize := fb.sizeFromClientSizePixels(layoutItem.MinSize()) wp.RcNormalPosition.Right = wp.RcNormalPosition.Left + int32(minSize.Width) - 1 wp.RcNormalPosition.Bottom = wp.RcNormalPosition.Top + int32(minSize.Height) - 1 } if !win.SetWindowPlacement(fb.hWnd, &wp) { return lastError("SetWindowPlacement") } return fb.clientComposite.RestoreState() } func (fb *FormBase) Closing() *CloseEvent { return fb.closingPublisher.Event() } func (fb *FormBase) ProgressIndicator() *ProgressIndicator { return fb.progressIndicator } func (fb *FormBase) setStopwatch(sw *stopwatch) { fb.stopwatch = sw fb.updateStopwatch <- sw } func (fb *FormBase) startLayout() bool { if fb.performLayout == nil || fb.inSizingLoop && !fb.startingLayoutViaSizingLoop { return false } cs := fb.clientSizeFromSizePixels(fb.proposedSize) min := CreateLayoutItemsForContainer(fb.clientComposite).MinSizeForSize(fb.proposedSize) if cs.Width < min.Width || cs.Height < min.Height { cs = maxSize(cs, min) size := fb.sizeFromClientSizePixels(cs) fb.SetSizePixels(size) fb.Invalidate() } cbp := fb.window.ClientBoundsPixels() fb.clientComposite.SetBoundsPixels(Rectangle{Y: cbp.Y, Width: cs.Width, Height: cs.Height}) cli := CreateLayoutItemsForContainer(fb) cli.Geometry().ClientSize = cs fb.performLayout <- cli return true } func (fb *FormBase) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_ACTIVATE: switch win.LOWORD(uint32(wParam)) { case win.WA_ACTIVE, win.WA_CLICKACTIVE: if fb.prevFocusHWnd != 0 { win.SetFocus(fb.prevFocusHWnd) } fb.group.SetActiveForm(fb.window.(Form)) fb.activatingPublisher.Publish() case win.WA_INACTIVE: fb.prevFocusHWnd = win.GetFocus() fb.group.SetActiveForm(nil) fb.deactivatingPublisher.Publish() } return 0 case win.WM_CLOSE: fb.closeReason = CloseReasonUnknown var canceled bool fb.closingPublisher.Publish(&canceled, fb.closeReason) if !canceled { if fb.owner != nil { win.EnableWindow(fb.owner.Handle(), true) if !win.SetWindowPos(fb.owner.Handle(), win.HWND_NOTOPMOST, 0, 0, 0, 0, win.SWP_NOMOVE|win.SWP_NOSIZE|win.SWP_SHOWWINDOW) { lastError("SetWindowPos") } } fb.close() } return 0 case win.WM_COMMAND: return fb.clientComposite.WndProc(hwnd, msg, wParam, lParam) case win.WM_GETMINMAXINFO: if fb.Suspended() || fb.proposedSize == (Size{}) { break } mmi := (*win.MINMAXINFO)(unsafe.Pointer(lParam)) var min Size if layout := fb.clientComposite.layout; layout != nil { size := fb.clientSizeFromSizePixels(fb.proposedSize) layoutItem := CreateLayoutItemsForContainer(fb) min = fb.sizeFromClientSizePixels(layoutItem.MinSizeForSize(size)) if fb.proposedSize.Width < min.Width { min = fb.sizeFromClientSizePixels(layoutItem.MinSizeForSize(min)) } } minSize := SizeFrom96DPI(fb.minSize96dpi, fb.DPI()) mmi.PtMinTrackSize = Point{ maxi(min.Width, minSize.Width), maxi(min.Height, minSize.Height), }.toPOINT() return 0 case win.WM_NOTIFY: return fb.clientComposite.WndProc(hwnd, msg, wParam, lParam) case win.WM_SETTEXT: fb.titleChangedPublisher.Publish() case win.WM_ENTERSIZEMOVE: fb.inSizingLoop = true fb.inSizeLoop <- true case win.WM_EXITSIZEMOVE: fb.inSizingLoop = false fb.inSizeLoop <- false case win.WM_WINDOWPOSCHANGED: wp := (*win.WINDOWPOS)(unsafe.Pointer(lParam)) if wp.Flags&win.SWP_SHOWWINDOW != 0 { fb.startLayout() } if wp.Flags&win.SWP_NOSIZE != 0 || fb.Layout() == nil || fb.Suspended() { break } fb.proposedSize = Size{int(wp.Cx), int(wp.Cy)} const performingLayoutSubject = "*FormBase.WndProc - WM_WINDOWPOSCHANGED - full layout from sizing loop" if fb.inSizingLoop { fb.startingLayoutViaSizingLoop = true if fb.stopwatch != nil { fb.stopwatch.Start(performingLayoutSubject) } } if fb.startLayout() { if fb.inSizingLoop { fb.startingLayoutViaSizingLoop = false applyLayoutResults(<-fb.layoutResults, fb.stopwatch) if fb.stopwatch != nil { fb.stopwatch.Stop(performingLayoutSubject) } } } case win.WM_SYSCOLORCHANGE: fb.ApplySysColors() case win.WM_DPICHANGED: wasSuspended := fb.Suspended() fb.SetSuspended(true) defer fb.SetSuspended(wasSuspended) dpi := int(win.HIWORD(uint32(wParam))) seenInApplyFontToDescendantsDuringDPIChange = make(map[*WindowBase]bool) seenInApplyDPIToDescendantsDuringDPIChange = make(map[*WindowBase]bool) defer func() { seenInApplyFontToDescendantsDuringDPIChange = nil seenInApplyDPIToDescendantsDuringDPIChange = nil }() fb.clientComposite.ApplyDPI(dpi) fb.ApplyDPI(dpi) if fb.progressIndicator != nil { fb.progressIndicator.SetOverlayIcon(fb.progressIndicator.overlayIcon, fb.progressIndicator.overlayIconDescription) } applyDPIToDescendants(fb.window, dpi) fb.SetSuspended(wasSuspended) rc := (*win.RECT)(unsafe.Pointer(lParam)) bounds := rectangleFromRECT(*rc) fb.proposedSize = bounds.Size() fb.window.SetBoundsPixels(bounds) fb.SetIcon(fb.icon) time.AfterFunc(time.Second, func() { if fb.hWnd == 0 { return } fb.Synchronize(func() { for ni := range notifyIcons { // We do this on all NotifyIcons, not just ones attached to this form or descendents, because // the notify icon might be on a different screen, and since it can't get notifications itself // we hope that one of the forms did for it. We also have to delay it by a second, because the // tray usually gets resized sometime after us. This is a nasty hack! ni.applyDPI() } }) }) case win.WM_SYSCOMMAND: if wParam == win.SC_CLOSE { fb.closeReason = CloseReasonUser } case taskbarButtonCreatedMsgId: version := win.GetVersion() major := version & 0xFF minor := version & 0xFF00 >> 8 // Check that the OS is Win 7 or later (Win 7 is v6.1). if fb.progressIndicator == nil && (major > 6 || (major == 6 && minor > 0)) { fb.progressIndicator, _ = newTaskbarList3(fb.hWnd) } case taskbarCreatedMsgId: for ni := range notifyIcons { ni.readdToTaskbar() } } return fb.WindowBase.WndProc(hwnd, msg, wParam, lParam) } ================================================ FILE: gradientcomposite.go ================================================ // Copyright 2017 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 ( "unsafe" "github.com/lxn/win" ) type GradientComposite struct { *Composite vertical bool color1 Color color2 Color verticalChangedPublisher EventPublisher color1ChangedPublisher EventPublisher color2ChangedPublisher EventPublisher brush *BitmapBrush } func NewGradientComposite(parent Container) (*GradientComposite, error) { return NewGradientCompositeWithStyle(parent, 0) } func NewGradientCompositeWithStyle(parent Container, style uint32) (*GradientComposite, error) { composite, err := NewCompositeWithStyle(parent, style) if err != nil { return nil, err } gc := &GradientComposite{Composite: composite} succeeded := false defer func() { if !succeeded { gc.Dispose() } }() if err := InitWrapperWindow(gc); err != nil { return nil, err } gc.MustRegisterProperty("Vertical", NewBoolProperty( func() bool { return gc.Vertical() }, func(b bool) error { gc.SetVertical(b) return nil }, gc.verticalChangedPublisher.Event())) gc.MustRegisterProperty("Color1", NewProperty( func() interface{} { return float64(uint32(gc.Color1())) }, func(v interface{}) error { var c Color switch v := v.(type) { case Color: c = v case uint32: c = Color(v) case float64: c = Color(uint32(v)) default: return ErrInvalidType } return gc.SetColor1(c) }, gc.color1ChangedPublisher.Event())) gc.MustRegisterProperty("Color2", NewProperty( func() interface{} { return float64(uint32(gc.Color2())) }, func(v interface{}) error { var c Color switch v := v.(type) { case Color: c = v case uint32: c = Color(v) case float64: c = Color(uint32(v)) default: return ErrInvalidType } return gc.SetColor2(c) }, gc.color2ChangedPublisher.Event())) succeeded = true return gc, nil } func (gc *GradientComposite) Vertical() bool { return gc.vertical } func (gc *GradientComposite) SetVertical(vertical bool) (err error) { if vertical == gc.vertical { return nil } old := gc.vertical defer func() { if err != nil { gc.vertical = old } }() gc.vertical = vertical if err = gc.updateBackground(); err != nil { return } gc.verticalChangedPublisher.Publish() return } func (gc *GradientComposite) Color1() Color { return gc.color1 } func (gc *GradientComposite) SetColor1(c Color) (err error) { if c == gc.color1 { return nil } old := gc.color1 defer func() { if err != nil { gc.color1 = old } }() gc.color1 = c if err = gc.updateBackground(); err != nil { return } gc.color1ChangedPublisher.Publish() return } func (gc *GradientComposite) Color2() Color { return gc.color2 } func (gc *GradientComposite) SetColor2(c Color) (err error) { if c == gc.color2 { return nil } old := gc.color2 defer func() { if err != nil { gc.color2 = old } }() gc.color2 = c if err = gc.updateBackground(); err != nil { return } gc.color2ChangedPublisher.Publish() return } func (gc *GradientComposite) updateBackground() error { bounds := gc.ClientBoundsPixels() if bounds.Width < 1 || bounds.Height < 1 { return nil } if gc.brush != nil { gc.brush.Dispose() gc.brush.Bitmap().Dispose() gc.brush = nil } if gc.vertical { bounds.Width = 1 } else { bounds.Height = 1 } bmp, err := NewBitmapForDPI(bounds.Size(), gc.DPI()) if err != nil { return err } defer func() { if gc.brush == nil { bmp.Dispose() } }() canvas, err := NewCanvasFromImage(bmp) if err != nil { return err } defer canvas.Dispose() var orientation Orientation if gc.vertical { orientation = Vertical } else { orientation = Horizontal } if err := canvas.GradientFillRectanglePixels(gc.color1, gc.color2, orientation, bounds); err != nil { return err } gc.brush, err = NewBitmapBrush(bmp) if err != nil { return err } gc.SetBackground(gc.brush) return nil } func (gc *GradientComposite) Dispose() { if gc.brush != nil { gc.SetBackground(nil) gc.brush.Dispose() gc.brush.Bitmap().Dispose() gc.brush = nil } gc.Composite.Dispose() } func (gc *GradientComposite) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_WINDOWPOSCHANGED: wp := (*win.WINDOWPOS)(unsafe.Pointer(lParam)) if wp.Flags&win.SWP_NOSIZE != 0 { break } size := gc.ClientBoundsPixels().Size() if gc.brush != nil && gc.brush.bitmap.size == size { break } gc.updateBackground() } return gc.Composite.WndProc(hwnd, msg, wParam, lParam) } ================================================ FILE: graphicseffects.go ================================================ // Copyright 2017 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 "math" var ( borderGlowAlpha = []float64{0.2, 0.1, 0.075, 0.05, 0.075} InteractionEffect WidgetGraphicsEffect FocusEffect WidgetGraphicsEffect ) type WidgetGraphicsEffect interface { Draw(widget Widget, canvas *Canvas) error } type widgetGraphicsEffectBase struct { color Color dpi2Bitmap map[int]*Bitmap } func (wgeb *widgetGraphicsEffectBase) create(color Color) error { wgeb.color = color return nil } func (wgeb *widgetGraphicsEffectBase) Dispose() { if len(wgeb.dpi2Bitmap) == 0 { return } for dpi, bitmap := range wgeb.dpi2Bitmap { bitmap.Dispose() delete(wgeb.dpi2Bitmap, dpi) } } func (wgeb *widgetGraphicsEffectBase) bitmapForDPI(dpi int) (*Bitmap, error) { if wgeb.dpi2Bitmap == nil { wgeb.dpi2Bitmap = make(map[int]*Bitmap) } else if bitmap, ok := wgeb.dpi2Bitmap[dpi]; ok { return bitmap, nil } var disposables Disposables defer disposables.Treat() bitmap, err := NewBitmapWithTransparentPixelsForDPI(SizeFrom96DPI(Size{12, 12}, dpi), dpi) if err != nil { return nil, err } disposables.Add(bitmap) canvas, err := NewCanvasFromImage(bitmap) if err != nil { return nil, err } defer canvas.Dispose() for i := 1; i <= 5; i++ { size := SizeFrom96DPI(Size{i*2 + 2, i*2 + 2}, dpi) bmp, err := NewBitmapWithTransparentPixelsForDPI(size, dpi) if err != nil { return nil, err } defer bmp.Dispose() bmpCanvas, err := NewCanvasFromImage(bmp) if err != nil { return nil, err } defer bmpCanvas.Dispose() color := RGB( byte(math.Min(1.0, float64(wgeb.color.R())/255.0-0.1+0.1*float64(i))*255.0), byte(math.Min(1.0, float64(wgeb.color.G())/255.0-0.1+0.1*float64(i))*255.0), byte(math.Min(1.0, float64(wgeb.color.B())/255.0-0.1+0.1*float64(i))*255.0), ) brush, err := NewSolidColorBrush(color) if err != nil { return nil, err } defer brush.Dispose() ellipseSize := SizeFrom96DPI(Size{i * 2, i * 2}, dpi) if err := bmpCanvas.FillRoundedRectanglePixels(brush, Rectangle{0, 0, size.Width, size.Height}, ellipseSize); err != nil { return nil, err } bmpCanvas.Dispose() opacity := byte(borderGlowAlpha[i-1] * 255.0) offset := PointFrom96DPI(Point{5 - i, 5 - i}, dpi) canvas.DrawBitmapWithOpacityPixels(bmp, Rectangle{offset.X, offset.Y, size.Width, size.Height}, opacity) } disposables.Spare() wgeb.dpi2Bitmap[dpi] = bitmap return bitmap, nil } type BorderGlowEffect struct { widgetGraphicsEffectBase } func NewBorderGlowEffect(color Color) (*BorderGlowEffect, error) { bge := new(BorderGlowEffect) if err := bge.create(color); err != nil { return nil, err } return bge, nil } func (bge *BorderGlowEffect) Draw(widget Widget, canvas *Canvas) error { b := widget.BoundsPixels() dpi := canvas.DPI() bitmap, err := bge.bitmapForDPI(dpi) if err != nil { return err } off1 := IntFrom96DPI(1, dpi) off2 := IntFrom96DPI(2, dpi) off5 := IntFrom96DPI(5, dpi) canvas.DrawBitmapPart(bitmap, Rectangle{b.X - off5, b.Y - off5, off5, off5}, Rectangle{0, 0, off5, off5}) canvas.DrawBitmapPart(bitmap, Rectangle{b.X, b.Y - off5, b.Width, off5}, Rectangle{off5 + off1, 0, off1, off5}) canvas.DrawBitmapPart(bitmap, Rectangle{b.X + b.Width, b.Y - off5, off5, off5}, Rectangle{off5 + off2, 0, off5, off5}) canvas.DrawBitmapPart(bitmap, Rectangle{b.X + b.Width, b.Y, off5, b.Height}, Rectangle{off5 + off2, off5 + off1, off5, off1}) canvas.DrawBitmapPart(bitmap, Rectangle{b.X + b.Width, b.Y + b.Height, off5, off5}, Rectangle{off5 + off2, off5 + off2, off5, off5}) canvas.DrawBitmapPart(bitmap, Rectangle{b.X, b.Y + b.Height, b.Width, off5}, Rectangle{off5 + off1, off5 + off2, off1, off5}) canvas.DrawBitmapPart(bitmap, Rectangle{b.X - off5, b.Y + b.Height, off5, off5}, Rectangle{0, off5 + off2, off5, off5}) canvas.DrawBitmapPart(bitmap, Rectangle{b.X - off5, b.Y, off5, b.Height}, Rectangle{0, off5 + off1, off5, off1}) return nil } type DropShadowEffect struct { widgetGraphicsEffectBase } func NewDropShadowEffect(color Color) (*DropShadowEffect, error) { dse := new(DropShadowEffect) if err := dse.create(color); err != nil { return nil, err } return dse, nil } func (dse *DropShadowEffect) Draw(widget Widget, canvas *Canvas) error { b := widget.BoundsPixels() dpi := canvas.DPI() bitmap, err := dse.bitmapForDPI(dpi) if err != nil { return err } off1 := IntFrom96DPI(1, dpi) off2 := IntFrom96DPI(2, dpi) off5 := IntFrom96DPI(5, dpi) off10 := IntFrom96DPI(10, dpi) canvas.DrawBitmapPart(bitmap, Rectangle{b.X + b.Width, b.Y + off10 - off5, off5, off5}, Rectangle{off5 + off2, 0, off5, off5}) canvas.DrawBitmapPart(bitmap, Rectangle{b.X + b.Width, b.Y + off10, off5, b.Height - off10}, Rectangle{off5 + off2, off5 + off1, off5, off1}) canvas.DrawBitmapPart(bitmap, Rectangle{b.X + b.Width, b.Y + b.Height, off5, off5}, Rectangle{off5 + off2, off5 + off2, off5, off5}) canvas.DrawBitmapPart(bitmap, Rectangle{b.X + off10, b.Y + b.Height, b.Width - off10, off5}, Rectangle{off5 + off1, off5 + off2, off1, off5}) canvas.DrawBitmapPart(bitmap, Rectangle{b.X + off10 - off5, b.Y + b.Height, off5, off5}, Rectangle{0, off5 + off2, off5, off5}) return nil } type widgetGraphicsEffectListObserver interface { onInsertedGraphicsEffect(index int, effect WidgetGraphicsEffect) error onRemovedGraphicsEffect(index int, effect WidgetGraphicsEffect) error onClearedGraphicsEffects() error } type WidgetGraphicsEffectList struct { items []WidgetGraphicsEffect observer widgetGraphicsEffectListObserver } func newWidgetGraphicsEffectList(observer widgetGraphicsEffectListObserver) *WidgetGraphicsEffectList { return &WidgetGraphicsEffectList{observer: observer} } func (l *WidgetGraphicsEffectList) Add(effect WidgetGraphicsEffect) error { if effect == nil { return newError("effect == nil") } return l.Insert(len(l.items), effect) } func (l *WidgetGraphicsEffectList) At(index int) WidgetGraphicsEffect { return l.items[index] } func (l *WidgetGraphicsEffectList) Clear() error { observer := l.observer oldItems := l.items l.items = l.items[:0] if observer != nil { if err := observer.onClearedGraphicsEffects(); err != nil { l.items = oldItems return err } } return nil } func (l *WidgetGraphicsEffectList) Index(effect WidgetGraphicsEffect) int { for i, item := range l.items { if item == effect { return i } } return -1 } func (l *WidgetGraphicsEffectList) Contains(effect WidgetGraphicsEffect) bool { return l.Index(effect) > -1 } func (l *WidgetGraphicsEffectList) insertIntoSlice(index int, effect WidgetGraphicsEffect) { l.items = append(l.items, nil) copy(l.items[index+1:], l.items[index:]) l.items[index] = effect } func (l *WidgetGraphicsEffectList) Insert(index int, effect WidgetGraphicsEffect) error { observer := l.observer l.insertIntoSlice(index, effect) if observer != nil { if err := observer.onInsertedGraphicsEffect(index, effect); err != nil { l.items = append(l.items[:index], l.items[index+1:]...) return err } } return nil } func (l *WidgetGraphicsEffectList) Len() int { return len(l.items) } func (l *WidgetGraphicsEffectList) Remove(effect WidgetGraphicsEffect) error { index := l.Index(effect) if index == -1 { return nil } return l.RemoveAt(index) } func (l *WidgetGraphicsEffectList) RemoveAt(index int) error { observer := l.observer item := l.items[index] l.items = append(l.items[:index], l.items[index+1:]...) if observer != nil { if err := observer.onRemovedGraphicsEffect(index, item); err != nil { l.insertIntoSlice(index, item) return err } } return nil } ================================================ FILE: gridlayout.go ================================================ // Copyright 2011 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 ( "sort" "sync" ) type gridLayoutCell struct { row int column int widgetBase *WidgetBase } type gridLayoutSection struct { greedyNonSpacerCount int greedySpacerCount int } type gridLayoutWidgetInfo struct { cell *gridLayoutCell spanHorz int spanVert int minSize Size // in native pixels } type GridLayout struct { LayoutBase rowStretchFactors []int columnStretchFactors []int widgetBase2Info map[*WidgetBase]*gridLayoutWidgetInfo cells [][]gridLayoutCell } func NewGridLayout() *GridLayout { l := &GridLayout{ LayoutBase: LayoutBase{ margins96dpi: Margins{9, 9, 9, 9}, spacing96dpi: 6, }, widgetBase2Info: make(map[*WidgetBase]*gridLayoutWidgetInfo), } l.layout = l return l } func (l *GridLayout) sufficientStretchFactors(stretchFactors []int, required int) []int { oldLen := len(stretchFactors) if oldLen < required { if cap(stretchFactors) < required { temp := make([]int, required, maxi(required, len(stretchFactors)*2)) copy(temp, stretchFactors) stretchFactors = temp } else { stretchFactors = stretchFactors[:required] } for i := oldLen; i < len(stretchFactors); i++ { stretchFactors[i] = 1 } } return stretchFactors } func (l *GridLayout) ensureSufficientSize(rows, columns int) { l.rowStretchFactors = l.sufficientStretchFactors(l.rowStretchFactors, rows) l.columnStretchFactors = l.sufficientStretchFactors(l.columnStretchFactors, columns) if len(l.cells) < len(l.rowStretchFactors) { if cap(l.cells) < cap(l.rowStretchFactors) { temp := make([][]gridLayoutCell, len(l.rowStretchFactors), cap(l.rowStretchFactors)) copy(temp, l.cells) l.cells = temp } else { l.cells = l.cells[:len(l.rowStretchFactors)] } } for i := 0; i < len(l.cells); i++ { if len(l.cells[i]) < len(l.columnStretchFactors) { if cap(l.cells[i]) < cap(l.columnStretchFactors) { temp := make([]gridLayoutCell, len(l.columnStretchFactors)) copy(temp, l.cells[i]) l.cells[i] = temp } else { l.cells[i] = l.cells[i][:len(l.columnStretchFactors)] } } } // FIXME: Not sure if this works. for wb, info := range l.widgetBase2Info { l.widgetBase2Info[wb].cell = &l.cells[info.cell.row][info.cell.column] } } func (l *GridLayout) RowStretchFactor(row int) int { if row < 0 { // FIXME: Should we rather return an error? return -1 } if row >= len(l.rowStretchFactors) { return 1 } return l.rowStretchFactors[row] } func (l *GridLayout) SetRowStretchFactor(row, factor int) error { if row < 0 { return newError("row must be >= 0") } if factor != l.RowStretchFactor(row) { if l.container == nil { return newError("container required") } if factor < 1 { return newError("factor must be >= 1") } l.ensureSufficientSize(row+1, len(l.columnStretchFactors)) l.rowStretchFactors[row] = factor l.container.RequestLayout() } return nil } func (l *GridLayout) ColumnStretchFactor(column int) int { if column < 0 { // FIXME: Should we rather return an error? return -1 } if column >= len(l.columnStretchFactors) { return 1 } return l.columnStretchFactors[column] } func (l *GridLayout) SetColumnStretchFactor(column, factor int) error { if column < 0 { return newError("column must be >= 0") } if factor != l.ColumnStretchFactor(column) { if l.container == nil { return newError("container required") } if factor < 1 { return newError("factor must be >= 1") } l.ensureSufficientSize(len(l.rowStretchFactors), column+1) l.columnStretchFactors[column] = factor l.container.RequestLayout() } return nil } func rangeFromGridLayoutWidgetInfo(info *gridLayoutWidgetInfo) Rectangle { return Rectangle{ X: info.cell.column, Y: info.cell.row, Width: info.spanHorz, Height: info.spanVert, } } func (l *GridLayout) setWidgetOnCells(widget Widget, r Rectangle) { var wb *WidgetBase if widget != nil { wb = widget.AsWidgetBase() } for row := r.Y; row < r.Y+r.Height; row++ { for col := r.X; col < r.X+r.Width; col++ { l.cells[row][col].widgetBase = wb } } } func (l *GridLayout) Range(widget Widget) (r Rectangle, ok bool) { if widget == nil { return Rectangle{}, false } info := l.widgetBase2Info[widget.AsWidgetBase()] if info == nil || l.container == nil || !l.container.Children().containsHandle(widget.Handle()) { return Rectangle{}, false } return rangeFromGridLayoutWidgetInfo(info), true } func (l *GridLayout) SetRange(widget Widget, r Rectangle) error { if widget == nil { return newError("widget required") } if l.container == nil { return newError("container required") } if !l.container.Children().containsHandle(widget.Handle()) { return newError("widget must be child of container") } if r.X < 0 || r.Y < 0 { return newError("range.X and range.Y must be >= 0") } if r.Width < 1 || r.Height < 1 { return newError("range.Width and range.Height must be >= 1") } wb := widget.AsWidgetBase() info := l.widgetBase2Info[wb] if info == nil { info = new(gridLayoutWidgetInfo) } else { l.setWidgetOnCells(nil, rangeFromGridLayoutWidgetInfo(info)) } l.ensureSufficientSize(r.Y+r.Height, r.X+r.Width) cell := &l.cells[r.Y][r.X] cell.row = r.Y cell.column = r.X if info.cell == nil { // We have to do this _after_ calling ensureSufficientSize(). l.widgetBase2Info[wb] = info } info.cell = cell info.spanHorz = r.Width info.spanVert = r.Height l.setWidgetOnCells(widget, r) return nil } func (l *GridLayout) CreateLayoutItem(ctx *LayoutContext) ContainerLayoutItem { wb2Item := make(map[*WidgetBase]LayoutItem) var children []LayoutItem cells := make([][]gridLayoutItemCell, len(l.cells)) for row, srcCols := range l.cells { dstCols := make([]gridLayoutItemCell, len(srcCols)) cells[row] = dstCols for col, srcCell := range srcCols { dstCell := &dstCols[col] dstCell.row = row dstCell.column = col if srcCell.widgetBase != nil { item, ok := wb2Item[srcCell.widgetBase] if !ok { item = createLayoutItemForWidgetWithContext(srcCell.widgetBase.window.(Widget), ctx) children = append(children, item) wb2Item[srcCell.widgetBase] = item } dstCell.item = item } } } item2Info := make(map[LayoutItem]*gridLayoutItemInfo, len(l.widgetBase2Info)) for wb, info := range l.widgetBase2Info { item := wb2Item[wb] var cell *gridLayoutItemCell if info.cell != nil { cell = &cells[info.cell.row][info.cell.column] } item2Info[item] = &gridLayoutItemInfo{ cell: cell, spanHorz: info.spanHorz, spanVert: info.spanVert, minSize: info.minSize, } } return &gridLayoutItem{ ContainerLayoutItemBase: ContainerLayoutItemBase{ children: children, }, size2MinSize: make(map[Size]Size), rowStretchFactors: append([]int(nil), l.rowStretchFactors...), columnStretchFactors: append([]int(nil), l.columnStretchFactors...), item2Info: item2Info, cells: cells, } } type gridLayoutItem struct { ContainerLayoutItemBase mutex sync.Mutex size2MinSize map[Size]Size // in native pixels rowStretchFactors []int columnStretchFactors []int item2Info map[LayoutItem]*gridLayoutItemInfo cells [][]gridLayoutItemCell minSize Size // in native pixels } type gridLayoutItemInfo struct { cell *gridLayoutItemCell spanHorz int spanVert int minSize Size // in native pixels } type gridLayoutItemCell struct { row int column int item LayoutItem } func (*gridLayoutItem) stretchFactorsTotal(stretchFactors []int) int { total := 0 for _, v := range stretchFactors { total += maxi(1, v) } return total } func (li *gridLayoutItem) LayoutFlags() LayoutFlags { var flags LayoutFlags if len(li.children) == 0 { return ShrinkableHorz | ShrinkableVert | GrowableHorz | GrowableVert } else { for _, item := range li.children { if s, ok := item.(*spacerLayoutItem); ok && s.greedyLocallyOnly || !shouldLayoutItem(item) { continue } wf := item.LayoutFlags() if wf&GreedyHorz != 0 && item.Geometry().MaxSize.Width > 0 { wf &^= GreedyHorz } if wf&GreedyVert != 0 && item.Geometry().MaxSize.Height > 0 { wf &^= GreedyVert } flags |= wf } } return flags } func (li *gridLayoutItem) IdealSize() Size { return li.MinSize() } func (li *gridLayoutItem) MinSize() Size { if len(li.cells) == 0 { return Size{} } return li.MinSizeForSize(li.geometry.ClientSize) } func (li *gridLayoutItem) HeightForWidth(width int) int { return li.MinSizeForSize(Size{width, li.geometry.ClientSize.Height}).Height } func (li *gridLayoutItem) MinSizeForSize(size Size) Size { if len(li.cells) == 0 { return Size{} } li.mutex.Lock() defer li.mutex.Unlock() if min, ok := li.size2MinSize[size]; ok { return min } ws := make([]int, len(li.cells[0])) for row := 0; row < len(li.cells); row++ { for col := 0; col < len(ws); col++ { item := li.cells[row][col].item if item == nil { continue } if !shouldLayoutItem(item) { continue } min := li.MinSizeEffectiveForChild(item) info := li.item2Info[item] if info.spanHorz == 1 { ws[col] = maxi(ws[col], min.Width) } } } widths := li.sectionSizesForSpace(Horizontal, size.Width, nil) heights := li.sectionSizesForSpace(Vertical, size.Height, widths) for row := range heights { var wg sync.WaitGroup var mutex sync.Mutex var maxHeight int for col := range widths { item := li.cells[row][col].item if item == nil { continue } if !shouldLayoutItem(item) { continue } if info := li.item2Info[item]; info.spanVert == 1 { if hfw, ok := item.(HeightForWidther); ok && hfw.HasHeightForWidth() { wg.Add(1) go func() { height := hfw.HeightForWidth(li.spannedWidth(info, widths)) mutex.Lock() maxHeight = maxi(maxHeight, height) mutex.Unlock() wg.Done() }() } else { height := li.MinSizeEffectiveForChild(item).Height mutex.Lock() maxHeight = maxi(maxHeight, height) mutex.Unlock() } } } wg.Wait() heights[row] = maxHeight } margins := MarginsFrom96DPI(li.margins96dpi, li.ctx.dpi) spacing := IntFrom96DPI(li.spacing96dpi, li.ctx.dpi) width := margins.HNear + margins.HFar height := margins.VNear + margins.VFar for i, w := range ws { if w > 0 { if i > 0 { width += spacing } width += w } } for i, h := range heights { if h > 0 { if i > 0 { height += spacing } height += h } } if width > 0 && height > 0 { li.size2MinSize[size] = Size{width, height} } return Size{width, height} } // spannedWidth returns spanned width in native pixels. func (li *gridLayoutItem) spannedWidth(info *gridLayoutItemInfo, widths []int) int { spacing := IntFrom96DPI(li.spacing96dpi, li.ctx.dpi) var width int for i := info.cell.column; i < info.cell.column+info.spanHorz; i++ { if w := widths[i]; w > 0 { width += w if i > info.cell.column { width += spacing } } } return width } // spannedHeight returns spanned height in native pixels. func (li *gridLayoutItem) spannedHeight(info *gridLayoutItemInfo, heights []int) int { spacing := IntFrom96DPI(li.spacing96dpi, li.ctx.dpi) var height int for i := info.cell.row; i < info.cell.row+info.spanVert; i++ { if h := heights[i]; h > 0 { height += h if i > info.cell.row { height += spacing } } } return height } type gridLayoutSectionInfo struct { index int minSize int // in native pixels maxSize int // in native pixels stretch int hasGreedyNonSpacer bool hasGreedySpacer bool } type gridLayoutSectionInfoList []gridLayoutSectionInfo func (l gridLayoutSectionInfoList) Len() int { return len(l) } func (l gridLayoutSectionInfoList) Less(i, j int) bool { if l[i].hasGreedyNonSpacer == l[j].hasGreedyNonSpacer { if l[i].hasGreedySpacer == l[j].hasGreedySpacer { minDiff := l[i].minSize - l[j].minSize if minDiff == 0 { return l[i].maxSize/l[i].stretch < l[j].maxSize/l[j].stretch } return minDiff > 0 } return l[i].hasGreedySpacer } return l[i].hasGreedyNonSpacer } func (l gridLayoutSectionInfoList) Swap(i, j int) { l[i], l[j] = l[j], l[i] } func (li *gridLayoutItem) PerformLayout() []LayoutResultItem { widths := li.sectionSizesForSpace(Horizontal, li.geometry.ClientSize.Width, nil) heights := li.sectionSizesForSpace(Vertical, li.geometry.ClientSize.Height, widths) items := make([]LayoutResultItem, 0, len(li.item2Info)) margins := MarginsFrom96DPI(li.margins96dpi, li.ctx.dpi) spacing := IntFrom96DPI(li.spacing96dpi, li.ctx.dpi) for item, info := range li.item2Info { if !shouldLayoutItem(item) { continue } x := margins.HNear for i := 0; i < info.cell.column; i++ { if w := widths[i]; w > 0 { x += w + spacing } } y := margins.VNear for i := 0; i < info.cell.row; i++ { if h := heights[i]; h > 0 { y += h + spacing } } width := li.spannedWidth(info, widths) height := li.spannedHeight(info, heights) w := width h := height if lf := item.LayoutFlags(); lf&GrowableHorz == 0 || lf&GrowableVert == 0 { var s Size if hfw, ok := item.(HeightForWidther); !ok || !hfw.HasHeightForWidth() { if is, ok := item.(IdealSizer); ok { s = is.IdealSize() } } max := item.Geometry().MaxSize if max.Width > 0 && s.Width > max.Width { s.Width = max.Width } if lf&GrowableHorz == 0 { w = s.Width } w = mini(w, width) if hfw, ok := item.(HeightForWidther); ok && hfw.HasHeightForWidth() { h = hfw.HeightForWidth(w) } else { if max.Height > 0 && s.Height > max.Height { s.Height = max.Height } if lf&GrowableVert == 0 { h = s.Height } } h = mini(h, height) } alignment := item.Geometry().Alignment if alignment == AlignHVDefault { alignment = li.alignment } if w != width { switch alignment { case AlignHCenterVNear, AlignHCenterVCenter, AlignHCenterVFar: x += (width - w) / 2 case AlignHFarVNear, AlignHFarVCenter, AlignHFarVFar: x += width - w } } if h != height { switch alignment { case AlignHNearVCenter, AlignHCenterVCenter, AlignHFarVCenter: y += (height - h) / 2 case AlignHNearVFar, AlignHCenterVFar, AlignHFarVFar: y += height - h } } items = append(items, LayoutResultItem{Item: item, Bounds: Rectangle{X: x, Y: y, Width: w, Height: h}}) } return items } // sectionSizesForSpace returns section sizes. Input and outpus is measured in native pixels. func (li *gridLayoutItem) sectionSizesForSpace(orientation Orientation, space int, widths []int) []int { var stretchFactors []int if orientation == Horizontal { stretchFactors = li.columnStretchFactors } else { stretchFactors = li.rowStretchFactors } var sectionCountWithGreedyNonSpacer int var sectionCountWithGreedySpacer int var stretchFactorsTotal [3]int var minSizesRemaining int minSizes := make([]int, len(stretchFactors)) maxSizes := make([]int, len(stretchFactors)) sizes := make([]int, len(stretchFactors)) sortedSections := gridLayoutSectionInfoList(make([]gridLayoutSectionInfo, len(stretchFactors))) for i := 0; i < len(stretchFactors); i++ { var otherAxisCount int if orientation == Horizontal { otherAxisCount = len(li.rowStretchFactors) } else { otherAxisCount = len(li.columnStretchFactors) } for j := 0; j < otherAxisCount; j++ { var item LayoutItem if orientation == Horizontal { item = li.cells[j][i].item } else { item = li.cells[i][j].item } if item == nil { continue } if !shouldLayoutItem(item) { continue } info := li.item2Info[item] flags := item.LayoutFlags() max := item.Geometry().MaxSize var pref Size if hfw, ok := item.(HeightForWidther); !ok || !hfw.HasHeightForWidth() { if is, ok := item.(IdealSizer); ok { pref = is.IdealSize() } } if orientation == Horizontal { if info.spanHorz == 1 { minSizes[i] = maxi(minSizes[i], li.MinSizeEffectiveForChild(item).Width) } if max.Width > 0 { maxSizes[i] = maxi(maxSizes[i], max.Width) } else if pref.Width > 0 && flags&GrowableHorz == 0 { maxSizes[i] = maxi(maxSizes[i], pref.Width) } else { maxSizes[i] = 32768 } if info.spanHorz == 1 && flags&GreedyHorz > 0 { if _, isSpacer := item.(*spacerLayoutItem); isSpacer { sortedSections[i].hasGreedySpacer = true } else { sortedSections[i].hasGreedyNonSpacer = true } } } else { if info.spanVert == 1 { if hfw, ok := item.(HeightForWidther); ok && hfw.HasHeightForWidth() { minSizes[i] = maxi(minSizes[i], hfw.HeightForWidth(li.spannedWidth(info, widths))) } else { minSizes[i] = maxi(minSizes[i], li.MinSizeEffectiveForChild(item).Height) } } if max.Height > 0 { maxSizes[i] = maxi(maxSizes[i], max.Height) } else if hfw, ok := item.(HeightForWidther); ok && flags&GrowableVert == 0 && hfw.HasHeightForWidth() { maxSizes[i] = minSizes[i] } else if pref.Height > 0 && flags&GrowableVert == 0 { maxSizes[i] = maxi(maxSizes[i], pref.Height) } else { maxSizes[i] = 32768 } if info.spanVert == 1 && flags&GreedyVert > 0 { if _, isSpacer := item.(*spacerLayoutItem); isSpacer { sortedSections[i].hasGreedySpacer = true } else { sortedSections[i].hasGreedyNonSpacer = true } } } } sortedSections[i].index = i sortedSections[i].minSize = minSizes[i] sortedSections[i].maxSize = maxSizes[i] sortedSections[i].stretch = maxi(1, stretchFactors[i]) minSizesRemaining += minSizes[i] if sortedSections[i].hasGreedyNonSpacer { sectionCountWithGreedyNonSpacer++ stretchFactorsTotal[0] += stretchFactors[i] } else if sortedSections[i].hasGreedySpacer { sectionCountWithGreedySpacer++ stretchFactorsTotal[1] += stretchFactors[i] } else { stretchFactorsTotal[2] += stretchFactors[i] } } sort.Stable(sortedSections) margins := MarginsFrom96DPI(li.margins96dpi, li.ctx.dpi) spacing := IntFrom96DPI(li.spacing96dpi, li.ctx.dpi) if orientation == Horizontal { space -= margins.HNear + margins.HFar } else { space -= margins.VNear + margins.VFar } var spacingRemaining int for _, max := range maxSizes { if max > 0 { spacingRemaining += spacing } } if spacingRemaining > 0 { spacingRemaining -= spacing } offsets := [3]int{0, sectionCountWithGreedyNonSpacer, sectionCountWithGreedyNonSpacer + sectionCountWithGreedySpacer} counts := [3]int{sectionCountWithGreedyNonSpacer, sectionCountWithGreedySpacer, len(stretchFactors) - sectionCountWithGreedyNonSpacer - sectionCountWithGreedySpacer} for i := 0; i < 3; i++ { stretchFactorsRemaining := stretchFactorsTotal[i] for j := 0; j < counts[i]; j++ { info := sortedSections[offsets[i]+j] k := info.index stretch := stretchFactors[k] min := info.minSize max := info.maxSize size := min if min < max { excessSpace := float64(space - minSizesRemaining - spacingRemaining) size += int(excessSpace * float64(stretch) / float64(stretchFactorsRemaining)) if size < min { size = min } else if size > max { size = max } } sizes[k] = size minSizesRemaining -= min stretchFactorsRemaining -= stretch space -= (size + spacing) spacingRemaining -= spacing } } return sizes } ================================================ FILE: groupbox.go ================================================ // 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" "github.com/lxn/win" ) const groupBoxWindowClass = `\o/ Walk_GroupBox_Class \o/` func init() { AppendToWalkInit(func() { MustRegisterWindowClass(groupBoxWindowClass) }) } type GroupBox struct { WidgetBase hWndGroupBox win.HWND checkBox *CheckBox composite *Composite headerHeight int // in native pixels titleChangedPublisher EventPublisher } func NewGroupBox(parent Container) (*GroupBox, error) { gb := new(GroupBox) if err := InitWidget( gb, parent, groupBoxWindowClass, win.WS_VISIBLE, win.WS_EX_CONTROLPARENT); err != nil { return nil, err } succeeded := false defer func() { if !succeeded { gb.Dispose() } }() gb.hWndGroupBox = win.CreateWindowEx( 0, syscall.StringToUTF16Ptr("BUTTON"), nil, win.WS_CHILD|win.WS_VISIBLE|win.BS_GROUPBOX, 0, 0, 80, 24, gb.hWnd, 0, 0, nil) if gb.hWndGroupBox == 0 { return nil, lastError("CreateWindowEx(BUTTON)") } win.SetWindowLong(gb.hWndGroupBox, win.GWL_ID, 1) gb.applyFont(gb.Font()) gb.updateHeaderHeight() var err error gb.checkBox, err = NewCheckBox(gb) if err != nil { return nil, err } win.SetWindowLong(gb.checkBox.hWnd, win.GWL_ID, 2) gb.SetCheckable(false) gb.checkBox.SetChecked(true) gb.checkBox.CheckedChanged().Attach(func() { gb.applyEnabledFromCheckBox(gb.checkBox.Checked()) }) setWindowVisible(gb.checkBox.hWnd, false) gb.composite, err = NewComposite(gb) if err != nil { return nil, err } win.SetWindowLong(gb.composite.hWnd, win.GWL_ID, 3) gb.composite.name = "composite" win.SetWindowPos(gb.checkBox.hWnd, win.HWND_TOP, 0, 0, 0, 0, win.SWP_NOMOVE|win.SWP_NOSIZE) gb.SetBackground(NullBrush()) gb.MustRegisterProperty("Title", NewProperty( func() interface{} { return gb.Title() }, func(v interface{}) error { return gb.SetTitle(assertStringOr(v, "")) }, gb.titleChangedPublisher.Event())) gb.MustRegisterProperty("Checked", NewBoolProperty( func() bool { return gb.Checked() }, func(v bool) error { gb.SetChecked(v) return nil }, gb.CheckedChanged())) succeeded = true return gb, nil } func (gb *GroupBox) AsContainerBase() *ContainerBase { if gb.composite == nil { return nil } return gb.composite.AsContainerBase() } func (gb *GroupBox) ClientBoundsPixels() Rectangle { cb := windowClientBounds(gb.hWndGroupBox) if gb.Layout() == nil { return cb } if gb.Checkable() { s := createLayoutItemForWidget(gb.checkBox).(MinSizer).MinSize() cb.Y += s.Height cb.Height -= s.Height } padding := gb.IntFrom96DPI(1) return Rectangle{cb.X + padding, cb.Y + gb.headerHeight, cb.Width - 2*padding, cb.Height - gb.headerHeight - 2*padding} } func (gb *GroupBox) updateHeaderHeight() { gb.headerHeight = gb.calculateTextSizeImpl("gM").Height } func (gb *GroupBox) Persistent() bool { return gb.composite.Persistent() } func (gb *GroupBox) SetPersistent(value bool) { gb.composite.SetPersistent(value) } func (gb *GroupBox) SaveState() error { return gb.composite.SaveState() } func (gb *GroupBox) RestoreState() error { return gb.composite.RestoreState() } func (gb *GroupBox) applyEnabled(enabled bool) { gb.WidgetBase.applyEnabled(enabled) if gb.hWndGroupBox != 0 { setWindowEnabled(gb.hWndGroupBox, enabled) } if gb.checkBox != nil { gb.checkBox.applyEnabled(enabled) } if gb.composite != nil { gb.composite.applyEnabled(enabled) } } func (gb *GroupBox) applyEnabledFromCheckBox(enabled bool) { if gb.hWndGroupBox != 0 { setWindowEnabled(gb.hWndGroupBox, enabled) } if gb.composite != nil { gb.composite.applyEnabled(enabled) } } func (gb *GroupBox) applyFont(font *Font) { gb.WidgetBase.applyFont(font) if gb.checkBox != nil { gb.checkBox.applyFont(font) } if gb.hWndGroupBox != 0 { SetWindowFont(gb.hWndGroupBox, font) } if gb.composite != nil { gb.composite.applyFont(font) } gb.updateHeaderHeight() } func (gb *GroupBox) SetSuspended(suspend bool) { gb.composite.SetSuspended(suspend) gb.WidgetBase.SetSuspended(suspend) gb.Invalidate() } func (gb *GroupBox) DataBinder() *DataBinder { return gb.composite.dataBinder } func (gb *GroupBox) SetDataBinder(dataBinder *DataBinder) { gb.composite.SetDataBinder(dataBinder) } func (gb *GroupBox) Title() string { if gb.Checkable() { return gb.checkBox.Text() } return windowText(gb.hWndGroupBox) } func (gb *GroupBox) SetTitle(title string) error { if gb.Checkable() { if err := setWindowText(gb.hWndGroupBox, ""); err != nil { return err } return gb.checkBox.SetText(title) } return setWindowText(gb.hWndGroupBox, title) } func (gb *GroupBox) Checkable() bool { return gb.checkBox.visible } func (gb *GroupBox) SetCheckable(checkable bool) { title := gb.Title() gb.checkBox.SetVisible(checkable) gb.SetTitle(title) gb.RequestLayout() } func (gb *GroupBox) Checked() bool { return gb.checkBox.Checked() } func (gb *GroupBox) SetChecked(checked bool) { gb.checkBox.SetChecked(checked) } func (gb *GroupBox) CheckedChanged() *Event { return gb.checkBox.CheckedChanged() } func (gb *GroupBox) ApplyDPI(dpi int) { gb.WidgetBase.ApplyDPI(dpi) if gb.checkBox != nil { gb.checkBox.ApplyDPI(dpi) } if gb.composite != nil { gb.composite.ApplyDPI(dpi) } } func (gb *GroupBox) Children() *WidgetList { if gb.composite == nil { // Without this we would get into trouble in NewComposite. return nil } return gb.composite.Children() } func (gb *GroupBox) Layout() Layout { if gb.composite == nil { // Without this we would get into trouble through the call to // SetCheckable in NewGroupBox. return nil } return gb.composite.Layout() } func (gb *GroupBox) SetLayout(value Layout) error { return gb.composite.SetLayout(value) } func (gb *GroupBox) MouseDown() *MouseEvent { return gb.composite.MouseDown() } func (gb *GroupBox) MouseMove() *MouseEvent { return gb.composite.MouseMove() } func (gb *GroupBox) MouseUp() *MouseEvent { return gb.composite.MouseUp() } func (gb *GroupBox) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { if gb.composite != nil { switch msg { case win.WM_CTLCOLORSTATIC: if hBrush := gb.handleWMCTLCOLOR(wParam, lParam); hBrush != 0 { return hBrush } case win.WM_COMMAND: hwndSrc := win.GetDlgItem(gb.hWnd, int32(win.LOWORD(uint32(wParam)))) if window := windowFromHandle(hwndSrc); window != nil { window.WndProc(hwnd, msg, wParam, lParam) } case win.WM_NOTIFY: gb.composite.WndProc(hwnd, msg, wParam, lParam) case win.WM_SETTEXT: gb.titleChangedPublisher.Publish() case win.WM_PAINT: win.UpdateWindow(gb.checkBox.hWnd) case win.WM_WINDOWPOSCHANGED: wp := (*win.WINDOWPOS)(unsafe.Pointer(lParam)) if wp.Flags&win.SWP_NOSIZE != 0 { break } wbcb := gb.WidgetBase.ClientBoundsPixels() if !win.MoveWindow( gb.hWndGroupBox, int32(wbcb.X), int32(wbcb.Y), int32(wbcb.Width), int32(wbcb.Height), true) { lastError("MoveWindow") break } if gb.Checkable() { s := createLayoutItemForWidget(gb.checkBox).(MinSizer).MinSize() var x int if l := gb.Layout(); l != nil { x = gb.IntFrom96DPI(l.Margins().HNear) } else { x = gb.headerHeight * 2 / 3 } gb.checkBox.SetBoundsPixels(Rectangle{x, gb.headerHeight, s.Width, s.Height}) } } } return gb.WidgetBase.WndProc(hwnd, msg, wParam, lParam) } func (gb *GroupBox) CreateLayoutItem(ctx *LayoutContext) LayoutItem { compositePos := Point{gb.IntFrom96DPI(1), gb.headerHeight} if gb.Checkable() { idealSize := gb.checkBox.idealSize() compositePos.Y += idealSize.Height } li := &groupBoxLayoutItem{ compositePos: compositePos, } gbli := CreateLayoutItemsForContainerWithContext(gb.composite, ctx) gbli.AsLayoutItemBase().parent = li li.children = append(li.children, gbli) return li } type groupBoxLayoutItem struct { ContainerLayoutItemBase compositePos Point // in native pixels } func (li *groupBoxLayoutItem) LayoutFlags() LayoutFlags { return li.children[0].LayoutFlags() } func (li *groupBoxLayoutItem) MinSize() Size { min := li.children[0].(MinSizer).MinSize() min.Width += li.compositePos.X * 2 min.Height += li.compositePos.Y + 2 return min } func (li *groupBoxLayoutItem) MinSizeForSize(size Size) Size { return li.MinSize() } func (li *groupBoxLayoutItem) HasHeightForWidth() bool { return li.children[0].(HeightForWidther).HasHeightForWidth() } func (li *groupBoxLayoutItem) HeightForWidth(width int) int { return li.children[0].(HeightForWidther).HeightForWidth(width-li.compositePos.X*2) + li.compositePos.Y } func (li *groupBoxLayoutItem) IdealSize() Size { size := li.children[0].(IdealSizer).IdealSize() size.Height += li.compositePos.Y return size } func (li *groupBoxLayoutItem) PerformLayout() []LayoutResultItem { return []LayoutResultItem{ { Item: li.children[0], Bounds: Rectangle{X: li.compositePos.X, Y: li.compositePos.Y, Width: li.geometry.Size.Width - li.compositePos.X*2, Height: li.geometry.Size.Height - li.compositePos.Y - 4}, }, } } ================================================ FILE: icon.go ================================================ // Copyright 2011 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 ( "image" "path/filepath" "syscall" "unsafe" "golang.org/x/sys/windows" "github.com/lxn/win" ) // Icon is a bitmap that supports transparency and combining multiple // variants of an image in different resolutions. type Icon struct { filePath string index int res *uint16 dpi2hIcon map[int]win.HICON size96dpi Size isStock bool hasIndex bool } type ExtractableIcon interface { FilePath_() string Index_() int Size_() int } func IconFrom(src interface{}, dpi int) (*Icon, error) { if src == nil { return nil, nil } img, err := ImageFrom(src) if err != nil { return nil, err } return iconCache.Icon(img, dpi) } func IconApplication() *Icon { return stockIcon(win.IDI_APPLICATION) } func IconError() *Icon { return stockIcon(win.IDI_ERROR) } func IconQuestion() *Icon { return stockIcon(win.IDI_QUESTION) } func IconWarning() *Icon { return stockIcon(win.IDI_WARNING) } func IconInformation() *Icon { return stockIcon(win.IDI_INFORMATION) } func IconWinLogo() *Icon { return stockIcon(win.IDI_WINLOGO) } func IconShield() *Icon { return stockIcon(win.IDI_SHIELD) } func stockIcon(id uintptr) *Icon { return &Icon{res: win.MAKEINTRESOURCE(id), size96dpi: defaultIconSize(), isStock: true} } // NewIconFromFile returns a new Icon, using the specified icon image file and default size. func NewIconFromFile(filePath string) (*Icon, error) { return NewIconFromFileWithSize(filePath, Size{}) } // NewIconFromFileWithSize returns a new Icon, using the specified icon image file and size. func NewIconFromFileWithSize(filePath string, size Size) (*Icon, error) { if size.Width == 0 || size.Height == 0 { size = defaultIconSize() } return checkNewIcon(&Icon{filePath: filePath, size96dpi: size}) } // NewIconFromResource returns a new Icon of default size, using the specified icon resource. func NewIconFromResource(name string) (*Icon, error) { return NewIconFromResourceWithSize(name, Size{}) } // NewIconFromResourceWithSize returns a new Icon of size size, using the specified icon resource. func NewIconFromResourceWithSize(name string, size Size) (*Icon, error) { return newIconFromResource(syscall.StringToUTF16Ptr(name), size) } // NewIconFromResourceId returns a new Icon of default size, using the specified icon resource. func NewIconFromResourceId(id int) (*Icon, error) { return NewIconFromResourceIdWithSize(id, Size{}) } // NewIconFromResourceIdWithSize returns a new Icon of size size, using the specified icon resource. func NewIconFromResourceIdWithSize(id int, size Size) (*Icon, error) { return newIconFromResource(win.MAKEINTRESOURCE(uintptr(id)), size) } func newIconFromResource(res *uint16, size Size) (*Icon, error) { if size.Width == 0 || size.Height == 0 { size = defaultIconSize() } return checkNewIcon(&Icon{res: res, size96dpi: size}) } // NewIconFromSysDLL returns a new Icon, as identified by index of // size 16x16 from the system DLL identified by dllBaseName. func NewIconFromSysDLL(dllBaseName string, index int) (*Icon, error) { return NewIconFromSysDLLWithSize(dllBaseName, index, 16) } // NewIconFromSysDLLWithSize returns a new Icon, as identified by // index of the desired size from the system DLL identified by dllBaseName. func NewIconFromSysDLLWithSize(dllBaseName string, index, size int) (*Icon, error) { system32, err := windows.GetSystemDirectory() if err != nil { return nil, err } return checkNewIcon(&Icon{filePath: filepath.Join(system32, dllBaseName+".dll"), index: index, hasIndex: true, size96dpi: Size{size, size}}) } // NewIconExtractedFromFile returns a new Icon, as identified by index of size 16x16 from filePath. func NewIconExtractedFromFile(filePath string, index, _ int) (*Icon, error) { return checkNewIcon(&Icon{filePath: filePath, index: index, hasIndex: true, size96dpi: Size{16, 16}}) } // NewIconExtractedFromFileWithSize returns a new Icon, as identified by index of the desired size from filePath. func NewIconExtractedFromFileWithSize(filePath string, index, size int) (*Icon, error) { return checkNewIcon(&Icon{filePath: filePath, index: index, hasIndex: true, size96dpi: Size{size, size}}) } // NewIconFromImage returns a new Icon at 96dpi, using the specified image.Image as source. // // Deprecated: Newer applications should use NewIconFromImageForDPI. func NewIconFromImage(im image.Image) (ic *Icon, err error) { return NewIconFromImageForDPI(im, 96) } // NewIconFromImageForDPI returns a new Icon at given DPI, using the specified image.Image as source. func NewIconFromImageForDPI(im image.Image, dpi int) (ic *Icon, err error) { hIcon, err := createAlphaCursorOrIconFromImage(im, image.Pt(0, 0), true) if err != nil { return nil, err } b := im.Bounds() return newIconFromHICONAndSize(hIcon, SizeTo96DPI(Size{b.Dx(), b.Dy()}, dpi), dpi), nil } // NewIconFromImageWithSize returns a new Icon of the given size in native pixels, using the // specified Image as source. func NewIconFromImageWithSize(image Image, size Size) (*Icon, error) { bmp, err := NewBitmapFromImageWithSize(image, size) if err != nil { return nil, err } return NewIconFromBitmap(bmp) } func newIconFromImageForDPI(image Image, dpi int) (*Icon, error) { size96dpi := image.Size() size := SizeFrom96DPI(size96dpi, dpi) bmp, err := NewBitmapFromImageWithSize(image, size) if err != nil { return nil, err } hIcon, err := createAlphaCursorOrIconFromBitmap(bmp, Point{}, true) if err != nil { return nil, err } return &Icon{dpi2hIcon: map[int]win.HICON{dpi: hIcon}, size96dpi: size96dpi}, nil } // NewIconFromBitmap returns a new Icon, using the specified Bitmap as source. func NewIconFromBitmap(bmp *Bitmap) (ic *Icon, err error) { hIcon, err := createAlphaCursorOrIconFromBitmap(bmp, Point{}, true) if err != nil { return nil, err } return newIconFromHICONAndSize(hIcon, bmp.Size(), bmp.dpi), nil } // NewIconFromHICON returns a new Icon at 96dpi, using the specified win.HICON as source. // // Deprecated: Newer applications should use NewIconFromHICONForDPI. func NewIconFromHICON(hIcon win.HICON) (ic *Icon, err error) { return NewIconFromHICONForDPI(hIcon, 96) } // NewIconFromHICONForDPI returns a new Icon at given DPI, using the specified win.HICON as source. func NewIconFromHICONForDPI(hIcon win.HICON, dpi int) (ic *Icon, err error) { s, err := sizeFromHICON(hIcon) if err != nil { return nil, err } return newIconFromHICONAndSize(hIcon, SizeTo96DPI(s, dpi), dpi), nil } func newIconFromHICONAndSize(hIcon win.HICON, size Size, dpi int) *Icon { return &Icon{dpi2hIcon: map[int]win.HICON{dpi: hIcon}, size96dpi: size} } func checkNewIcon(icon *Icon) (*Icon, error) { if _, err := icon.handleForDPIWithError(96); err != nil { return nil, err } return icon, nil } func (i *Icon) handleForDPI(dpi int) win.HICON { hIcon, _ := i.handleForDPIWithError(dpi) return hIcon } func (i *Icon) handleForDPIWithError(dpi int) (win.HICON, error) { if i.dpi2hIcon == nil { i.dpi2hIcon = make(map[int]win.HICON) } else if handle, ok := i.dpi2hIcon[dpi]; ok { return handle, nil } var hInst win.HINSTANCE var name *uint16 if i.filePath != "" { absFilePath, err := filepath.Abs(i.filePath) if err != nil { return 0, err } name = syscall.StringToUTF16Ptr(absFilePath) } else { if !i.isStock { if hInst = win.GetModuleHandle(nil); hInst == 0 { return 0, lastError("GetModuleHandle") } } name = i.res } var size Size if i.size96dpi.Width == 0 || i.size96dpi.Height == 0 { size = SizeFrom96DPI(defaultIconSize(), dpi) } else { size = SizeFrom96DPI(i.size96dpi, dpi) } var hIcon win.HICON if i.hasIndex { win.SHDefExtractIcon( name, int32(i.index), 0, nil, &hIcon, win.MAKELONG(0, uint16(size.Width))) if hIcon == 0 { return 0, newError("SHDefExtractIcon") } } else { hr := win.HICON(win.LoadIconWithScaleDown( hInst, name, int32(size.Width), int32(size.Height), &hIcon)) if hr < 0 || hIcon == 0 { return 0, lastError("LoadIconWithScaleDown") } } i.dpi2hIcon[dpi] = hIcon return hIcon, nil } // Dispose releases the operating system resources associated with the Icon. func (i *Icon) Dispose() { if i.isStock || len(i.dpi2hIcon) == 0 { return } for dpi, hIcon := range i.dpi2hIcon { win.DestroyIcon(hIcon) delete(i.dpi2hIcon, dpi) } } func (i *Icon) draw(hdc win.HDC, location Point) error { dpi := dpiForHDC(hdc) size := SizeFrom96DPI(i.size96dpi, dpi) return i.drawStretched(hdc, Rectangle{location.X, location.Y, size.Width, size.Height}) } func (i *Icon) drawStretched(hdc win.HDC, bounds Rectangle) error { dpi := int(float64(bounds.Width) / float64(i.size96dpi.Width) * 96.0) hIcon := i.handleForDPI(dpi) if hIcon == 0 { var dpiAvailMax int for dpiAvail, handle := range i.dpi2hIcon { if dpiAvail > dpiAvailMax { hIcon = handle dpiAvailMax = dpiAvail } if dpiAvail > dpi { break } } } if !win.DrawIconEx(hdc, int32(bounds.X), int32(bounds.Y), hIcon, int32(bounds.Width), int32(bounds.Height), 0, 0, win.DI_NORMAL) { return lastError("DrawIconEx") } return nil } // Size returns icon size in 1/96" units. func (i *Icon) Size() Size { return i.size96dpi } // create an Alpha Icon or Cursor from an Image // http://support.microsoft.com/kb/318876 func createAlphaCursorOrIconFromImage(im image.Image, hotspot image.Point, fIcon bool) (win.HICON, error) { bmp, err := NewBitmapFromImage(im) if err != nil { return 0, err } defer bmp.Dispose() return createAlphaCursorOrIconFromBitmap(bmp, Point{hotspot.X, hotspot.Y}, fIcon) } // createAlphaCursorOrIconFromBitmap creates a cursor/icon from a bitmap. hotspot coordinates are in native pixels. func createAlphaCursorOrIconFromBitmap(bmp *Bitmap, hotspot Point, fIcon bool) (win.HICON, error) { // Create an empty mask bitmap. hMonoBitmap := win.CreateBitmap(int32(bmp.size.Width), int32(bmp.size.Height), 1, 1, nil) if hMonoBitmap == 0 { return 0, newError("CreateBitmap failed") } defer win.DeleteObject(win.HGDIOBJ(hMonoBitmap)) var ii win.ICONINFO if fIcon { ii.FIcon = win.TRUE } ii.XHotspot = uint32(hotspot.X) ii.YHotspot = uint32(hotspot.Y) ii.HbmMask = hMonoBitmap ii.HbmColor = bmp.hBmp // Create the alpha cursor with the alpha DIB section. hIconOrCursor := win.CreateIconIndirect(&ii) return hIconOrCursor, nil } // sizeFromHICON returns icon size in native pixels. func sizeFromHICON(hIcon win.HICON) (Size, error) { var ii win.ICONINFO var bi win.BITMAPINFO if !win.GetIconInfo(hIcon, &ii) { return Size{}, lastError("GetIconInfo") } defer win.DeleteObject(win.HGDIOBJ(ii.HbmMask)) var hBmp win.HBITMAP if ii.HbmColor != 0 { hBmp = ii.HbmColor defer win.DeleteObject(win.HGDIOBJ(ii.HbmColor)) } else { hBmp = ii.HbmMask } if 0 == win.GetObject(win.HGDIOBJ(hBmp), unsafe.Sizeof(bi), unsafe.Pointer(&bi)) { return Size{}, newError("GetObject") } return Size{int(bi.BmiHeader.BiWidth), int(bi.BmiHeader.BiHeight)}, nil } // defaultIconSize returns default small icon size in 1/92" units. func defaultIconSize() Size { return Size{int(win.GetSystemMetricsForDpi(win.SM_CXSMICON, 96)), int(win.GetSystemMetricsForDpi(win.SM_CYSMICON, 96))} } ================================================ FILE: iconcache.go ================================================ // Copyright 2019 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 var iconCache *IconCache func init() { AppendToWalkInit(func() { iconCache = NewIconCache() }) } type IconCache struct { imageAndDPI2Bitmap map[imageAndDPI]*Bitmap imageAndDPI2Icon map[imageAndDPI]*Icon } type imageAndDPI struct { image Image dpi int } func NewIconCache() *IconCache { return &IconCache{ imageAndDPI2Bitmap: make(map[imageAndDPI]*Bitmap), imageAndDPI2Icon: make(map[imageAndDPI]*Icon), } } func (ic *IconCache) Clear() { for key, bmp := range ic.imageAndDPI2Bitmap { bmp.Dispose() delete(ic.imageAndDPI2Bitmap, key) } for key, ico := range ic.imageAndDPI2Icon { ico.Dispose() delete(ic.imageAndDPI2Icon, key) } } func (ic *IconCache) Dispose() { ic.Clear() } func (ic *IconCache) Bitmap(image Image, dpi int) (*Bitmap, error) { key := imageAndDPI{image, dpi} if bmp, ok := ic.imageAndDPI2Bitmap[key]; ok { return bmp, nil } size := SizeFrom96DPI(image.Size(), dpi) bmp, err := NewBitmapFromImageWithSize(image, size) if err != nil { return nil, err } ic.imageAndDPI2Bitmap[key] = bmp return bmp, nil } func (ic *IconCache) Icon(image Image, dpi int) (*Icon, error) { key := imageAndDPI{image, dpi} if ico, ok := ic.imageAndDPI2Icon[key]; ok { return ico, nil } if ico, ok := image.(*Icon); ok { if ico.handleForDPI(dpi) != 0 { ic.imageAndDPI2Icon[key] = ico return ico, nil } } ico, err := newIconFromImageForDPI(image, dpi) if err != nil { return nil, err } ic.imageAndDPI2Icon[key] = ico return ico, nil } ================================================ FILE: image.go ================================================ // 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 ( "strconv" "strings" "github.com/lxn/win" ) type Image interface { // draw draws image at location (upper left) in native pixels unstreched. draw(hdc win.HDC, location Point) error // drawStretched draws image streched to given bounds in native pixels. drawStretched(hdc win.HDC, bounds Rectangle) error Dispose() // Size returns image size in 1/96" units. Size() Size } func ImageFrom(src interface{}) (img Image, err error) { switch src := src.(type) { case nil: // nop case Image: img = src case ExtractableIcon: img, err = NewIconExtractedFromFileWithSize(src.FilePath_(), src.Index_(), src.Size_()) case int: img, err = Resources.Image(strconv.Itoa(src)) case string: img, err = Resources.Image(src) default: err = ErrInvalidType } return } // NewImageFromFile loads image from file at 96dpi. Supported types are .ico, .emf, .bmp, .png... // // Deprecated: Newer applications should use NewImageFromFileForDPI. func NewImageFromFile(filePath string) (Image, error) { return NewImageFromFileForDPI(filePath, 96) } // NewImageFromFileForDPI loads image from file at given DPI. Supported types are .ico, .emf, // .bmp, .png... func NewImageFromFileForDPI(filePath string, dpi int) (Image, error) { if strings.HasSuffix(filePath, ".ico") { return NewIconFromFile(filePath) } else if strings.HasSuffix(filePath, ".emf") { return NewMetafileFromFile(filePath) } return NewBitmapFromFileForDPI(filePath, dpi) } type PaintFuncImage struct { size96dpi Size paint PaintFunc // in 1/96" units paintPixels PaintFunc // in native pixels dispose func() } // NewPaintFuncImage creates new PaintFuncImage struct. size parameter and paint function bounds // parameter are specified in 1/96" units. func NewPaintFuncImage(size Size, paint func(canvas *Canvas, bounds Rectangle) error) *PaintFuncImage { return &PaintFuncImage{size96dpi: size, paint: paint} } // NewPaintFuncImagePixels creates new PaintFuncImage struct. size parameter is specified in 1/96" // units. paint function bounds parameter is specified in native pixels. func NewPaintFuncImagePixels(size Size, paint func(canvas *Canvas, bounds Rectangle) error) *PaintFuncImage { return &PaintFuncImage{size96dpi: size, paintPixels: paint} } // NewPaintFuncImageWithDispose creates new PaintFuncImage struct. size parameter and paint // function bounds parameter are specified in 1/96" units. func NewPaintFuncImageWithDispose(size Size, paint func(canvas *Canvas, bounds Rectangle) error, dispose func()) *PaintFuncImage { return &PaintFuncImage{size96dpi: size, paint: paint, dispose: dispose} } // NewPaintFuncImagePixelsWithDispose creates new PaintFuncImage struct. size parameter is // specified in 1/96" units. paint function bounds parameter is specified in native pixels. func NewPaintFuncImagePixelsWithDispose(size Size, paint func(canvas *Canvas, bounds Rectangle) error, dispose func()) *PaintFuncImage { return &PaintFuncImage{size96dpi: size, paintPixels: paint, dispose: dispose} } func (pfi *PaintFuncImage) draw(hdc win.HDC, location Point) error { dpi := dpiForHDC(hdc) size := SizeFrom96DPI(pfi.size96dpi, dpi) return pfi.drawStretched(hdc, Rectangle{location.X, location.Y, size.Width, size.Height}) } func (pfi *PaintFuncImage) drawStretched(hdc win.HDC, bounds Rectangle) error { canvas, err := newCanvasFromHDC(hdc) if err != nil { return err } defer canvas.Dispose() return pfi.drawStretchedOnCanvasPixels(canvas, bounds) } func (pfi *PaintFuncImage) drawStretchedOnCanvasPixels(canvas *Canvas, bounds Rectangle) error { if pfi.paintPixels != nil { return pfi.paintPixels(canvas, bounds) } if pfi.paint != nil { return pfi.paint(canvas, RectangleTo96DPI(bounds, canvas.DPI())) } return newError("paint(Pixels) func is nil") } func (pfi *PaintFuncImage) Dispose() { if pfi.dispose != nil { pfi.dispose() pfi.dispose = nil } } // Size returns image size in 1/96" units. func (pfi *PaintFuncImage) Size() Size { return pfi.size96dpi } ================================================ FILE: imagelist.go ================================================ // 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" "github.com/lxn/win" ) type ImageList struct { hIml win.HIMAGELIST dpi int maskColor Color imageSize96dpi Size colorMaskedBitmap2Index map[*Bitmap]int bitmapMaskedBitmap2Index map[bitmapMaskedBitmap]int icon2Index map[*Icon]int32 } type bitmapMaskedBitmap struct { bitmap *Bitmap mask *Bitmap } // NewImageList creates an empty image list at 96dpi. imageSize parameter is specified in 1/96" // units. // // Deprecated: Newer applications should use NewImageListForDPI. func NewImageList(imageSize Size, maskColor Color) (*ImageList, error) { return NewImageListForDPI(SizeFrom96DPI(imageSize, 96), maskColor, 96) } // NewImageListForDPI creates an empty image list for image size at given DPI. imageSize is // specified in native pixels. func NewImageListForDPI(imageSize Size, maskColor Color, dpi int) (*ImageList, error) { hIml := win.ImageList_Create( int32(imageSize.Width), int32(imageSize.Height), win.ILC_MASK|win.ILC_COLOR32, 8, 8) if hIml == 0 { return nil, newError("ImageList_Create failed") } return &ImageList{ hIml: hIml, dpi: dpi, maskColor: maskColor, imageSize96dpi: SizeTo96DPI(imageSize, dpi), colorMaskedBitmap2Index: make(map[*Bitmap]int), bitmapMaskedBitmap2Index: make(map[bitmapMaskedBitmap]int), icon2Index: make(map[*Icon]int32), }, nil } func (il *ImageList) Handle() win.HIMAGELIST { return il.hIml } func (il *ImageList) Add(bitmap, maskBitmap *Bitmap) (int, error) { if bitmap == nil { return 0, newError("bitmap cannot be nil") } key := bitmapMaskedBitmap{bitmap: bitmap, mask: maskBitmap} if index, ok := il.bitmapMaskedBitmap2Index[key]; ok { return index, nil } var maskHandle win.HBITMAP if maskBitmap != nil { maskHandle = maskBitmap.handle() } index := int(win.ImageList_Add(il.hIml, bitmap.handle(), maskHandle)) if index == -1 { return 0, newError("ImageList_Add failed") } il.bitmapMaskedBitmap2Index[key] = index return index, nil } func (il *ImageList) AddMasked(bitmap *Bitmap) (int32, error) { if bitmap == nil { return 0, newError("bitmap cannot be nil") } if index, ok := il.colorMaskedBitmap2Index[bitmap]; ok { return int32(index), nil } index := win.ImageList_AddMasked( il.hIml, bitmap.handle(), win.COLORREF(il.maskColor)) if index == -1 { return 0, newError("ImageList_AddMasked failed") } il.colorMaskedBitmap2Index[bitmap] = int(index) return index, nil } func (il *ImageList) AddIcon(icon *Icon) (int32, error) { if icon == nil { return 0, newError("icon cannot be nil") } if index, ok := il.icon2Index[icon]; ok { return index, nil } index := win.ImageList_ReplaceIcon(il.hIml, -1, icon.handleForDPI(il.dpi)) if index == -1 { return 0, newError("ImageList_ReplaceIcon failed") } il.icon2Index[icon] = index return index, nil } func (il *ImageList) AddImage(image interface{}) (int32, error) { switch image.(type) { case ExtractableIcon, *Icon: icon, err := IconFrom(image, il.dpi) if err != nil { return 0, err } return il.AddIcon(icon) default: bmp, err := BitmapFrom(image, il.dpi) if err != nil { return 0, err } return il.AddMasked(bmp) } } func (il *ImageList) DrawPixels(canvas *Canvas, index int, bounds Rectangle) error { if !win.ImageList_DrawEx(il.hIml, int32(index), canvas.hdc, int32(bounds.X), int32(bounds.Y), int32(bounds.Width), int32(bounds.Height), win.CLR_DEFAULT, win.CLR_DEFAULT, win.ILD_NORMAL) { return newError("ImageList_DrawEx") } return nil } func (il *ImageList) Dispose() { if il.hIml != 0 { win.ImageList_Destroy(il.hIml) il.hIml = 0 } } func (il *ImageList) MaskColor() Color { return il.maskColor } func imageListForImage(image interface{}, dpi int) (hIml win.HIMAGELIST, isSysIml bool, err error) { if name, ok := image.(string); ok { if img, err := Resources.Image(name); err == nil { image = img } } if filePath, ok := image.(string); ok { _, hIml = iconIndexAndHImlForFilePath(filePath) isSysIml = hIml != 0 } else { w := int32(win.GetSystemMetricsForDpi(win.SM_CXSMICON, uint32(dpi))) h := int32(win.GetSystemMetricsForDpi(win.SM_CYSMICON, uint32(dpi))) hIml = win.ImageList_Create(w, h, win.ILC_MASK|win.ILC_COLOR32, 8, 8) if hIml == 0 { return 0, false, newError("ImageList_Create failed") } } return } func iconIndexAndHImlForFilePath(filePath string) (int32, win.HIMAGELIST) { var shfi win.SHFILEINFO if hIml := win.HIMAGELIST(win.SHGetFileInfo( syscall.StringToUTF16Ptr(filePath), 0, &shfi, uint32(unsafe.Sizeof(shfi)), win.SHGFI_SYSICONINDEX|win.SHGFI_SMALLICON)); hIml != 0 { return shfi.IIcon, hIml } return -1, 0 } func imageIndexMaybeAdd(image interface{}, hIml win.HIMAGELIST, isSysIml bool, imageUintptr2Index map[uintptr]int32, filePath2IconIndex map[string]int32, dpi int) int32 { if !isSysIml { return imageIndexAddIfNotExists(image, hIml, imageUintptr2Index, dpi) } else if filePath, ok := image.(string); ok { if iIcon, ok := filePath2IconIndex[filePath]; ok { return iIcon } if iIcon, _ := iconIndexAndHImlForFilePath(filePath); iIcon != -1 { filePath2IconIndex[filePath] = iIcon return iIcon } } return -1 } func imageIndexAddIfNotExists(image interface{}, hIml win.HIMAGELIST, imageUintptr2Index map[uintptr]int32, dpi int) int32 { imageIndex := int32(-1) if image != nil { if name, ok := image.(string); ok { image, _ = Resources.Image(name) } var ptr uintptr switch img := image.(type) { case *Bitmap: ptr = uintptr(unsafe.Pointer(img)) case *Icon: ptr = uintptr(unsafe.Pointer(img)) } if ptr == 0 { return -1 } if imageIndex, ok := imageUintptr2Index[ptr]; ok { return imageIndex } switch img := image.(type) { case *Bitmap: imageIndex = win.ImageList_AddMasked(hIml, img.hBmp, 0) case *Icon: imageIndex = win.ImageList_ReplaceIcon(hIml, -1, img.handleForDPI(dpi)) } if imageIndex > -1 { imageUintptr2Index[ptr] = imageIndex } } return imageIndex } ================================================ FILE: imageview.go ================================================ // 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 ( "math" "github.com/lxn/win" ) type ImageViewMode int const ( ImageViewModeIdeal ImageViewMode = iota ImageViewModeCorner ImageViewModeCenter ImageViewModeShrink ImageViewModeZoom ImageViewModeStretch ) type ImageView struct { *CustomWidget image Image imageChangedPublisher EventPublisher margin96dpi int marginChangedPublisher EventPublisher mode ImageViewMode } func NewImageView(parent Container) (*ImageView, error) { iv := new(ImageView) cw, err := NewCustomWidgetPixels(parent, 0, func(canvas *Canvas, updateBounds Rectangle) error { return iv.drawImage(canvas, updateBounds) }) if err != nil { return nil, err } iv.CustomWidget = cw if err := InitWrapperWindow(iv); err != nil { iv.Dispose() return nil, err } iv.SetInvalidatesOnResize(true) iv.SetBackground(NullBrush()) iv.MustRegisterProperty("Image", NewProperty( func() interface{} { return iv.Image() }, func(v interface{}) error { img, err := ImageFrom(v) if err != nil { return err } return iv.SetImage(img) }, iv.imageChangedPublisher.Event())) iv.MustRegisterProperty("Margin", NewProperty( func() interface{} { return iv.Margin() }, func(v interface{}) error { return iv.SetMargin(assertIntOr(v, 0)) }, iv.MarginChanged())) return iv, nil } func (iv *ImageView) Mode() ImageViewMode { return iv.mode } func (iv *ImageView) SetMode(mode ImageViewMode) { if mode == iv.mode { return } iv.mode = mode iv.Invalidate() iv.RequestLayout() } func (iv *ImageView) applyDPI(dpi int) { iv.CustomWidget.ApplyDPI(dpi) iv.Invalidate() iv.RequestLayout() } func (iv *ImageView) Image() Image { return iv.image } func (iv *ImageView) SetImage(image Image) error { if image == iv.image { return nil } var oldSize, newSize Size // in 1/96" units if iv.image != nil { oldSize = iv.image.Size() } if image != nil { newSize = image.Size() } iv.image = image _, isMetafile := image.(*Metafile) iv.SetClearsBackground(isMetafile) err := iv.Invalidate() if iv.mode == ImageViewModeIdeal && newSize != oldSize { iv.RequestLayout() } iv.imageChangedPublisher.Publish() return err } func (iv *ImageView) ImageChanged() *Event { return iv.imageChangedPublisher.Event() } func (iv *ImageView) Margin() int { return iv.margin96dpi } func (iv *ImageView) SetMargin(margin int) error { if margin == iv.margin96dpi { return nil } iv.margin96dpi = margin err := iv.Invalidate() if iv.mode == ImageViewModeIdeal { iv.RequestLayout() } iv.marginChangedPublisher.Publish() return err } func (iv *ImageView) MarginChanged() *Event { return iv.marginChangedPublisher.Event() } func (iv *ImageView) drawImage(canvas *Canvas, _ Rectangle) error { if iv.image == nil { return nil } cb := iv.ClientBoundsPixels() dpi := iv.DPI() margin := IntFrom96DPI(iv.margin96dpi, dpi) cb.Width -= margin * 2 cb.Height -= margin * 2 s := SizeFrom96DPI(iv.image.Size(), dpi) switch iv.mode { case ImageViewModeShrink, ImageViewModeZoom, ImageViewModeStretch: var bounds Rectangle if iv.mode == ImageViewModeStretch { bounds.X = margin bounds.Y = margin bounds.Width = cb.Width bounds.Height = cb.Height } else { var scale float64 if iv.mode == ImageViewModeZoom || s.Width > cb.Width || s.Height > cb.Height { sx := float64(cb.Width) / float64(s.Width) sy := float64(cb.Height) / float64(s.Height) scale = math.Min(sx, sy) } else { scale = 1.0 } bounds.Width = int(float64(s.Width) * scale) bounds.Height = int(float64(s.Height) * scale) bounds.X = margin + (cb.Width-bounds.Width)/2 bounds.Y = margin + (cb.Height-bounds.Height)/2 } return canvas.DrawImageStretchedPixels(iv.image, bounds) case ImageViewModeCorner, ImageViewModeCenter: win.IntersectClipRect(canvas.hdc, int32(margin), int32(margin), int32(cb.Width+margin), int32(cb.Height+margin)) } var bounds Rectangle switch iv.mode { case ImageViewModeIdeal, ImageViewModeCorner: bounds.X = margin bounds.Y = margin case ImageViewModeCenter: bounds.X = margin + (cb.Width-s.Width)/2 bounds.Y = margin + (cb.Height-s.Height)/2 } bounds.Width = s.Width bounds.Height = s.Height return canvas.DrawImageStretchedPixels(iv.image, bounds) } func (iv *ImageView) CreateLayoutItem(ctx *LayoutContext) LayoutItem { var layoutFlags LayoutFlags if iv.mode != ImageViewModeIdeal { layoutFlags = ShrinkableHorz | ShrinkableVert | GrowableHorz | GrowableVert | GreedyHorz | GreedyVert } dpi := iv.DPI() idealSize := SizeFrom96DPI(Size{100, 100}, dpi) var minSize Size if iv.mode == ImageViewModeIdeal { if iv.image != nil { idealSize = SizeFrom96DPI(iv.image.Size(), dpi) margin2 := IntFrom96DPI(iv.margin96dpi, dpi) * 2 idealSize.Width += margin2 idealSize.Height += margin2 } minSize = idealSize } else { s := IntFrom96DPI(iv.margin96dpi, dpi)*2 + 1 minSize = Size{s, s} } return &imageViewLayoutItem{ layoutFlags: layoutFlags, idealSize: idealSize, minSize: minSize, } } type imageViewLayoutItem struct { LayoutItemBase layoutFlags LayoutFlags idealSize Size // in native pixels minSize Size // in native pixels } func (li *imageViewLayoutItem) LayoutFlags() LayoutFlags { return li.layoutFlags } func (li *imageViewLayoutItem) IdealSize() Size { return li.idealSize } func (li *imageViewLayoutItem) MinSize() Size { return li.minSize } ================================================ FILE: inifilesettings.go ================================================ // Copyright 2011 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 ( "bufio" "os" "path/filepath" "sort" "strings" "time" ) const iniFileTimeStampFormat = "2006-01-02" type IniFileSettings struct { fileName string key2Record map[string]iniFileRecord expireDuration time.Duration portable bool } type iniFileRecord struct { value string timestamp time.Time } func NewIniFileSettings(fileName string) *IniFileSettings { return &IniFileSettings{ fileName: fileName, key2Record: make(map[string]iniFileRecord), } } func (ifs *IniFileSettings) Get(key string) (string, bool) { record, ok := ifs.key2Record[key] return record.value, ok } func (ifs *IniFileSettings) Timestamp(key string) (time.Time, bool) { record, ok := ifs.key2Record[key] return record.timestamp, ok } func (ifs *IniFileSettings) Put(key, value string) error { return ifs.put(key, value, false) } func (ifs *IniFileSettings) PutExpiring(key, value string) error { return ifs.put(key, value, true) } func (ifs *IniFileSettings) put(key, value string, expiring bool) error { if key == "" { return newError("key must not be empty") } if strings.IndexAny(key, "|=\r\n") > -1 { return newError("key contains at least one of the invalid characters '|=\\r\\n'") } if strings.IndexAny(value, "\r\n") > -1 { return newError("value contains at least one of the invalid characters '\\r\\n'") } var timestamp time.Time if expiring { timestamp = time.Now() } ifs.key2Record[key] = iniFileRecord{value, timestamp} return nil } func (ifs *IniFileSettings) Remove(key string) error { delete(ifs.key2Record, key) return nil } func (ifs *IniFileSettings) ExpireDuration() time.Duration { return ifs.expireDuration } func (ifs *IniFileSettings) SetExpireDuration(expireDuration time.Duration) { ifs.expireDuration = expireDuration } func (ifs *IniFileSettings) Portable() bool { return ifs.portable } func (ifs *IniFileSettings) SetPortable(portable bool) { ifs.portable = portable } func (ifs *IniFileSettings) FilePath() string { if ifs.portable { absPath, err := filepath.Abs(ifs.fileName) if err != nil { return "" } return absPath } appDataPath, err := AppDataPath() if err != nil { return "" } return filepath.Join( appDataPath, App().OrganizationName(), App().ProductName(), ifs.fileName) } func (ifs *IniFileSettings) fileExists() (bool, error) { filePath := ifs.FilePath() if _, err := os.Stat(filePath); err != nil { // FIXME: Not necessarily a file does not exist error. return false, nil } return true, nil } func (ifs *IniFileSettings) withFile(flags int, f func(file *os.File) error) error { filePath := ifs.FilePath() dirPath, _ := filepath.Split(filePath) if err := os.MkdirAll(dirPath, 0644); err != nil { return wrapError(err) } file, err := os.OpenFile(filePath, flags, 0644) if err != nil { return wrapError(err) } defer file.Close() return f(file) } func (ifs *IniFileSettings) Load() error { exists, err := ifs.fileExists() if err != nil { return err } if !exists { return nil } return ifs.withFile(os.O_RDONLY, func(file *os.File) error { scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() assignIndex := strings.Index(line, "=") if assignIndex == -1 { return newError("bad line format: missing '='") } key := strings.TrimSpace(line[:assignIndex]) var ts time.Time if parts := strings.Split(key, "|"); len(parts) > 1 { key = parts[0] if ts, _ = time.Parse(iniFileTimeStampFormat, parts[1]); ts.IsZero() { ts = time.Now() } } value := strings.TrimSpace(line[assignIndex+1:]) ifs.key2Record[key] = iniFileRecord{value, ts} } return scanner.Err() }) } func (ifs *IniFileSettings) Save() error { return ifs.withFile(os.O_CREATE|os.O_TRUNC|os.O_WRONLY, func(file *os.File) error { bufWriter := bufio.NewWriter(file) keys := make([]string, 0, len(ifs.key2Record)) for key, record := range ifs.key2Record { if ifs.expireDuration <= 0 || record.timestamp.IsZero() || time.Since(record.timestamp) < ifs.expireDuration { keys = append(keys, key) } } sort.Strings(keys) for _, key := range keys { record := ifs.key2Record[key] if _, err := bufWriter.WriteString(key); err != nil { return wrapError(err) } if !record.timestamp.IsZero() { if _, err := bufWriter.WriteString("|"); err != nil { return wrapError(err) } if _, err := bufWriter.WriteString(record.timestamp.Format(iniFileTimeStampFormat)); err != nil { return wrapError(err) } } if _, err := bufWriter.WriteString("="); err != nil { return wrapError(err) } if _, err := bufWriter.WriteString(record.value); err != nil { return wrapError(err) } if _, err := bufWriter.WriteString("\r\n"); err != nil { return wrapError(err) } } return bufWriter.Flush() }) } ================================================ FILE: intevent.go ================================================ // Copyright 2011 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 type intEventHandlerInfo struct { handler IntEventHandler once bool } type IntEventHandler func(n int) type IntEvent struct { handlers []intEventHandlerInfo } func (e *IntEvent) Attach(handler IntEventHandler) int { handlerInfo := intEventHandlerInfo{handler, false} for i, h := range e.handlers { if h.handler == nil { e.handlers[i] = handlerInfo return i } } e.handlers = append(e.handlers, handlerInfo) return len(e.handlers) - 1 } func (e *IntEvent) Detach(handle int) { e.handlers[handle].handler = nil } func (e *IntEvent) Once(handler IntEventHandler) { i := e.Attach(handler) e.handlers[i].once = true } type IntEventPublisher struct { event IntEvent } func (p *IntEventPublisher) Event() *IntEvent { return &p.event } func (p *IntEventPublisher) Publish(n int) { for i, h := range p.event.handlers { if h.handler != nil { h.handler(n) if h.once { p.event.Detach(i) } } } } ================================================ FILE: intrangeevent.go ================================================ // Copyright 2017 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 type intRangeEventHandlerInfo struct { handler IntRangeEventHandler once bool } type IntRangeEventHandler func(from, to int) type IntRangeEvent struct { handlers []intRangeEventHandlerInfo } func (e *IntRangeEvent) Attach(handler IntRangeEventHandler) int { handlerInfo := intRangeEventHandlerInfo{handler, false} for i, h := range e.handlers { if h.handler == nil { e.handlers[i] = handlerInfo return i } } e.handlers = append(e.handlers, handlerInfo) return len(e.handlers) - 1 } func (e *IntRangeEvent) Detach(handle int) { e.handlers[handle].handler = nil } func (e *IntRangeEvent) Once(handler IntRangeEventHandler) { i := e.Attach(handler) e.handlers[i].once = true } type IntRangeEventPublisher struct { event IntRangeEvent } func (p *IntRangeEventPublisher) Event() *IntRangeEvent { return &p.event } func (p *IntRangeEventPublisher) Publish(from, to int) { for i, h := range p.event.handlers { if h.handler != nil { h.handler(from, to) if h.once { p.event.Detach(i) } } } } ================================================ FILE: keyboard.go ================================================ // Copyright 2013 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 ( "bytes" ) import ( "github.com/lxn/win" ) type Key uint16 func (k Key) String() string { return key2string[k] } const ( KeyLButton Key = win.VK_LBUTTON KeyRButton Key = win.VK_RBUTTON KeyCancel Key = win.VK_CANCEL KeyMButton Key = win.VK_MBUTTON KeyXButton1 Key = win.VK_XBUTTON1 KeyXButton2 Key = win.VK_XBUTTON2 KeyBack Key = win.VK_BACK KeyTab Key = win.VK_TAB KeyClear Key = win.VK_CLEAR KeyReturn Key = win.VK_RETURN KeyShift Key = win.VK_SHIFT KeyControl Key = win.VK_CONTROL KeyAlt Key = win.VK_MENU KeyMenu Key = win.VK_MENU KeyPause Key = win.VK_PAUSE KeyCapital Key = win.VK_CAPITAL KeyKana Key = win.VK_KANA KeyHangul Key = win.VK_HANGUL KeyJunja Key = win.VK_JUNJA KeyFinal Key = win.VK_FINAL KeyHanja Key = win.VK_HANJA KeyKanji Key = win.VK_KANJI KeyEscape Key = win.VK_ESCAPE KeyConvert Key = win.VK_CONVERT KeyNonconvert Key = win.VK_NONCONVERT KeyAccept Key = win.VK_ACCEPT KeyModeChange Key = win.VK_MODECHANGE KeySpace Key = win.VK_SPACE KeyPrior Key = win.VK_PRIOR KeyNext Key = win.VK_NEXT KeyEnd Key = win.VK_END KeyHome Key = win.VK_HOME KeyLeft Key = win.VK_LEFT KeyUp Key = win.VK_UP KeyRight Key = win.VK_RIGHT KeyDown Key = win.VK_DOWN KeySelect Key = win.VK_SELECT KeyPrint Key = win.VK_PRINT KeyExecute Key = win.VK_EXECUTE KeySnapshot Key = win.VK_SNAPSHOT KeyInsert Key = win.VK_INSERT KeyDelete Key = win.VK_DELETE KeyHelp Key = win.VK_HELP Key0 Key = 0x30 Key1 Key = 0x31 Key2 Key = 0x32 Key3 Key = 0x33 Key4 Key = 0x34 Key5 Key = 0x35 Key6 Key = 0x36 Key7 Key = 0x37 Key8 Key = 0x38 Key9 Key = 0x39 KeyA Key = 0x41 KeyB Key = 0x42 KeyC Key = 0x43 KeyD Key = 0x44 KeyE Key = 0x45 KeyF Key = 0x46 KeyG Key = 0x47 KeyH Key = 0x48 KeyI Key = 0x49 KeyJ Key = 0x4A KeyK Key = 0x4B KeyL Key = 0x4C KeyM Key = 0x4D KeyN Key = 0x4E KeyO Key = 0x4F KeyP Key = 0x50 KeyQ Key = 0x51 KeyR Key = 0x52 KeyS Key = 0x53 KeyT Key = 0x54 KeyU Key = 0x55 KeyV Key = 0x56 KeyW Key = 0x57 KeyX Key = 0x58 KeyY Key = 0x59 KeyZ Key = 0x5A KeyLWin Key = win.VK_LWIN KeyRWin Key = win.VK_RWIN KeyApps Key = win.VK_APPS KeySleep Key = win.VK_SLEEP KeyNumpad0 Key = win.VK_NUMPAD0 KeyNumpad1 Key = win.VK_NUMPAD1 KeyNumpad2 Key = win.VK_NUMPAD2 KeyNumpad3 Key = win.VK_NUMPAD3 KeyNumpad4 Key = win.VK_NUMPAD4 KeyNumpad5 Key = win.VK_NUMPAD5 KeyNumpad6 Key = win.VK_NUMPAD6 KeyNumpad7 Key = win.VK_NUMPAD7 KeyNumpad8 Key = win.VK_NUMPAD8 KeyNumpad9 Key = win.VK_NUMPAD9 KeyMultiply Key = win.VK_MULTIPLY KeyAdd Key = win.VK_ADD KeySeparator Key = win.VK_SEPARATOR KeySubtract Key = win.VK_SUBTRACT KeyDecimal Key = win.VK_DECIMAL KeyDivide Key = win.VK_DIVIDE KeyF1 Key = win.VK_F1 KeyF2 Key = win.VK_F2 KeyF3 Key = win.VK_F3 KeyF4 Key = win.VK_F4 KeyF5 Key = win.VK_F5 KeyF6 Key = win.VK_F6 KeyF7 Key = win.VK_F7 KeyF8 Key = win.VK_F8 KeyF9 Key = win.VK_F9 KeyF10 Key = win.VK_F10 KeyF11 Key = win.VK_F11 KeyF12 Key = win.VK_F12 KeyF13 Key = win.VK_F13 KeyF14 Key = win.VK_F14 KeyF15 Key = win.VK_F15 KeyF16 Key = win.VK_F16 KeyF17 Key = win.VK_F17 KeyF18 Key = win.VK_F18 KeyF19 Key = win.VK_F19 KeyF20 Key = win.VK_F20 KeyF21 Key = win.VK_F21 KeyF22 Key = win.VK_F22 KeyF23 Key = win.VK_F23 KeyF24 Key = win.VK_F24 KeyNumlock Key = win.VK_NUMLOCK KeyScroll Key = win.VK_SCROLL KeyLShift Key = win.VK_LSHIFT KeyRShift Key = win.VK_RSHIFT KeyLControl Key = win.VK_LCONTROL KeyRControl Key = win.VK_RCONTROL KeyLAlt Key = win.VK_LMENU KeyLMenu Key = win.VK_LMENU KeyRAlt Key = win.VK_RMENU KeyRMenu Key = win.VK_RMENU KeyBrowserBack Key = win.VK_BROWSER_BACK KeyBrowserForward Key = win.VK_BROWSER_FORWARD KeyBrowserRefresh Key = win.VK_BROWSER_REFRESH KeyBrowserStop Key = win.VK_BROWSER_STOP KeyBrowserSearch Key = win.VK_BROWSER_SEARCH KeyBrowserFavorites Key = win.VK_BROWSER_FAVORITES KeyBrowserHome Key = win.VK_BROWSER_HOME KeyVolumeMute Key = win.VK_VOLUME_MUTE KeyVolumeDown Key = win.VK_VOLUME_DOWN KeyVolumeUp Key = win.VK_VOLUME_UP KeyMediaNextTrack Key = win.VK_MEDIA_NEXT_TRACK KeyMediaPrevTrack Key = win.VK_MEDIA_PREV_TRACK KeyMediaStop Key = win.VK_MEDIA_STOP KeyMediaPlayPause Key = win.VK_MEDIA_PLAY_PAUSE KeyLaunchMail Key = win.VK_LAUNCH_MAIL KeyLaunchMediaSelect Key = win.VK_LAUNCH_MEDIA_SELECT KeyLaunchApp1 Key = win.VK_LAUNCH_APP1 KeyLaunchApp2 Key = win.VK_LAUNCH_APP2 KeyOEM1 Key = win.VK_OEM_1 KeyOEMPlus Key = win.VK_OEM_PLUS KeyOEMComma Key = win.VK_OEM_COMMA KeyOEMMinus Key = win.VK_OEM_MINUS KeyOEMPeriod Key = win.VK_OEM_PERIOD KeyOEM2 Key = win.VK_OEM_2 KeyOEM3 Key = win.VK_OEM_3 KeyOEM4 Key = win.VK_OEM_4 KeyOEM5 Key = win.VK_OEM_5 KeyOEM6 Key = win.VK_OEM_6 KeyOEM7 Key = win.VK_OEM_7 KeyOEM8 Key = win.VK_OEM_8 KeyOEM102 Key = win.VK_OEM_102 KeyProcessKey Key = win.VK_PROCESSKEY KeyPacket Key = win.VK_PACKET KeyAttn Key = win.VK_ATTN KeyCRSel Key = win.VK_CRSEL KeyEXSel Key = win.VK_EXSEL KeyErEOF Key = win.VK_EREOF KeyPlay Key = win.VK_PLAY KeyZoom Key = win.VK_ZOOM KeyNoName Key = win.VK_NONAME KeyPA1 Key = win.VK_PA1 KeyOEMClear Key = win.VK_OEM_CLEAR ) var key2string = map[Key]string{ KeyLButton: "LButton", KeyRButton: "RButton", KeyCancel: "Cancel", KeyMButton: "MButton", KeyXButton1: "XButton1", KeyXButton2: "XButton2", KeyBack: "Back", KeyTab: "Tab", KeyClear: "Clear", KeyReturn: "Return", KeyShift: "Shift", KeyControl: "Control", KeyAlt: "Alt / Menu", KeyPause: "Pause", KeyCapital: "Capital", KeyKana: "Kana / Hangul", KeyJunja: "Junja", KeyFinal: "Final", KeyHanja: "Hanja / Kanji", KeyEscape: "Escape", KeyConvert: "Convert", KeyNonconvert: "Nonconvert", KeyAccept: "Accept", KeyModeChange: "ModeChange", KeySpace: "Space", KeyPrior: "Prior", KeyNext: "Next", KeyEnd: "End", KeyHome: "Home", KeyLeft: "Left", KeyUp: "Up", KeyRight: "Right", KeyDown: "Down", KeySelect: "Select", KeyPrint: "Print", KeyExecute: "Execute", KeySnapshot: "Snapshot", KeyInsert: "Insert", KeyDelete: "Delete", KeyHelp: "Help", Key0: "0", Key1: "1", Key2: "2", Key3: "3", Key4: "4", Key5: "5", Key6: "6", Key7: "7", Key8: "8", Key9: "9", KeyA: "A", KeyB: "B", KeyC: "C", KeyD: "D", KeyE: "E", KeyF: "F", KeyG: "G", KeyH: "H", KeyI: "I", KeyJ: "J", KeyK: "K", KeyL: "L", KeyM: "M", KeyN: "N", KeyO: "O", KeyP: "P", KeyQ: "Q", KeyR: "R", KeyS: "S", KeyT: "T", KeyU: "U", KeyV: "V", KeyW: "W", KeyX: "X", KeyY: "Y", KeyZ: "Z", KeyLWin: "LWin", KeyRWin: "RWin", KeyApps: "Apps", KeySleep: "Sleep", KeyNumpad0: "Numpad0", KeyNumpad1: "Numpad1", KeyNumpad2: "Numpad2", KeyNumpad3: "Numpad3", KeyNumpad4: "Numpad4", KeyNumpad5: "Numpad5", KeyNumpad6: "Numpad6", KeyNumpad7: "Numpad7", KeyNumpad8: "Numpad8", KeyNumpad9: "Numpad9", KeyMultiply: "Multiply", KeyAdd: "Add", KeySeparator: "Separator", KeySubtract: "Subtract", KeyDecimal: "Decimal", KeyDivide: "Divide", KeyF1: "F1", KeyF2: "F2", KeyF3: "F3", KeyF4: "F4", KeyF5: "F5", KeyF6: "F6", KeyF7: "F7", KeyF8: "F8", KeyF9: "F9", KeyF10: "F10", KeyF11: "F11", KeyF12: "F12", KeyF13: "F13", KeyF14: "F14", KeyF15: "F15", KeyF16: "F16", KeyF17: "F17", KeyF18: "F18", KeyF19: "F19", KeyF20: "F20", KeyF21: "F21", KeyF22: "F22", KeyF23: "F23", KeyF24: "F24", KeyNumlock: "Numlock", KeyScroll: "Scroll", KeyLShift: "LShift", KeyRShift: "RShift", KeyLControl: "LControl", KeyRControl: "RControl", KeyLMenu: "LMenu", KeyRMenu: "RMenu", KeyBrowserBack: "BrowserBack", KeyBrowserForward: "BrowserForward", KeyBrowserRefresh: "BrowserRefresh", KeyBrowserStop: "BrowserStop", KeyBrowserSearch: "BrowserSearch", KeyBrowserFavorites: "BrowserFavorites", KeyBrowserHome: "BrowserHome", KeyVolumeMute: "VolumeMute", KeyVolumeDown: "VolumeDown", KeyVolumeUp: "VolumeUp", KeyMediaNextTrack: "MediaNextTrack", KeyMediaPrevTrack: "MediaPrevTrack", KeyMediaStop: "MediaStop", KeyMediaPlayPause: "MediaPlayPause", KeyLaunchMail: "LaunchMail", KeyLaunchMediaSelect: "LaunchMediaSelect", KeyLaunchApp1: "LaunchApp1", KeyLaunchApp2: "LaunchApp2", KeyOEM1: "OEM1", KeyOEMPlus: "OEMPlus", KeyOEMComma: "OEMComma", KeyOEMMinus: "OEMMinus", KeyOEMPeriod: "OEMPeriod", KeyOEM2: "OEM2", KeyOEM3: "OEM3", KeyOEM4: "OEM4", KeyOEM5: "OEM5", KeyOEM6: "OEM6", KeyOEM7: "OEM7", KeyOEM8: "OEM8", KeyOEM102: "OEM102", KeyProcessKey: "ProcessKey", KeyPacket: "Packet", KeyAttn: "Attn", KeyCRSel: "CRSel", KeyEXSel: "EXSel", KeyErEOF: "ErEOF", KeyPlay: "Play", KeyZoom: "Zoom", KeyNoName: "NoName", KeyPA1: "PA1", KeyOEMClear: "OEMClear", } type Modifiers byte func (m Modifiers) String() string { return modifiers2string[m] } var modifiers2string = map[Modifiers]string{ ModShift: "Shift", ModControl: "Ctrl", ModControl | ModShift: "Ctrl+Shift", ModAlt: "Alt", ModAlt | ModShift: "Alt+Shift", ModAlt | ModControl: "Alt+Ctrl", ModAlt | ModControl | ModShift: "Alt+Ctrl+Shift", } const ( ModShift Modifiers = 1 << iota ModControl ModAlt ) func ModifiersDown() Modifiers { var m Modifiers if ShiftDown() { m |= ModShift } if ControlDown() { m |= ModControl } if AltDown() { m |= ModAlt } return m } type Shortcut struct { Modifiers Modifiers Key Key } func (s Shortcut) String() string { m := s.Modifiers.String() if m == "" { return s.Key.String() } b := new(bytes.Buffer) b.WriteString(m) b.WriteRune('+') b.WriteString(s.Key.String()) return b.String() } func AltDown() bool { return win.GetKeyState(int32(KeyAlt))>>15 != 0 } func ControlDown() bool { return win.GetKeyState(int32(KeyControl))>>15 != 0 } func ShiftDown() bool { return win.GetKeyState(int32(KeyShift))>>15 != 0 } ================================================ FILE: keyevent.go ================================================ // Copyright 2011 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 type keyEventHandlerInfo struct { handler KeyEventHandler once bool } type KeyEventHandler func(key Key) type KeyEvent struct { handlers []keyEventHandlerInfo } func (e *KeyEvent) Attach(handler KeyEventHandler) int { handlerInfo := keyEventHandlerInfo{handler, false} for i, h := range e.handlers { if h.handler == nil { e.handlers[i] = handlerInfo return i } } e.handlers = append(e.handlers, handlerInfo) return len(e.handlers) - 1 } func (e *KeyEvent) Detach(handle int) { e.handlers[handle].handler = nil } func (e *KeyEvent) Once(handler KeyEventHandler) { i := e.Attach(handler) e.handlers[i].once = true } type KeyEventPublisher struct { event KeyEvent } func (p *KeyEventPublisher) Event() *KeyEvent { return &p.event } func (p *KeyEventPublisher) Publish(key Key) { for i, h := range p.event.handlers { if h.handler != nil { h.handler(key) if h.once { p.event.Detach(i) } } } } ================================================ FILE: l10n/update.bat ================================================ polyglot -name="walk" -dir=".." -locales="de,fr,ko" ================================================ FILE: l10n/walk-de.tr ================================================ {"Messages":[{"Locations":[{"File":"../validators.go","Line":"148"}],"Source":"Please select one of the provided options.","Context":["walk"],"Translation":"Bitte wählen Sie eine der angebotenen Optionen."},{"Locations":[{"File":"../declarative/radiobuttongroup.go","Line":"93"}],"Source":"A selection is required.","Context":["walk"],"Translation":"Eine Auswahl wird benötigt."},{"Locations":[{"File":"../tooltiperrorpresenter.go","Line":"107"}],"Source":"Invalid Input","Context":null,"Translation":"Ungültige Eingabe"},{"Locations":[{"File":"../validators.go","Line":"80"}],"Source":"Please enter a number from %.f to %.f.","Context":["walk"],"Translation":"Bitte geben Sie eine Zahl von %.f bis %.f ein."},{"Locations":[{"File":"../validators.go","Line":"83"}],"Source":"Please enter a number from %s to %s.","Context":["walk"],"Translation":"Bitte geben Sie eine Zahl von %s bis %s ein."},{"Locations":[{"File":"../validators.go","Line":"87"}],"Source":"Number out of allowed range","Context":["walk"],"Translation":"Zahl außerhalb des gültigen Bereichs"},{"Locations":[{"File":"../validators.go","Line":"128"}],"Source":"The text does not match the required pattern.","Context":["walk"],"Translation":"Der Text entspricht nicht dem erforderlichen Muster."},{"Locations":[{"File":"../validators.go","Line":"147"}],"Source":"Selection Required","Context":["walk"],"Translation":"Auswahl benötigt"}]} ================================================ FILE: l10n/walk-ko.tr ================================================ {"Messages":[{"Locations":[{"File":"../validators.go","Line":"87"}],"Source":"Number out of allowed range","Context":["walk"],"Translation":"허용 범위 초과"},{"Locations":[{"File":"../validators.go","Line":"128"}],"Source":"The text does not match the required pattern.","Context":["walk"],"Translation":"문자열이 요구되는 형식에 맞지 않습니다."},{"Locations":[{"File":"../validators.go","Line":"147"}],"Source":"Selection Required","Context":["walk"],"Translation":"선택 필요"},{"Locations":[{"File":"../validators.go","Line":"148"}],"Source":"Please select one of the provided options.","Context":["walk"],"Translation":"옵션 중 하나를 선택하십시오"},{"Locations":[{"File":"../tooltiperrorpresenter.go","Line":"107"}],"Source":"Invalid Input","Context":null,"Translation":"잘못된 입력"},{"Locations":[{"File":"../declarative/radiobuttongroup.go","Line":"93"}],"Source":"A selection is required.","Context":["walk"],"Translation":"항목 선택이 필요합니다."},{"Locations":[{"File":"../validators.go","Line":"80"}],"Source":"Please enter a number from %.f to %.f.","Context":["walk"],"Translation":"%.f에서 %.f 사이의 숫자를 입력하십시오."},{"Locations":[{"File":"../validators.go","Line":"83"}],"Source":"Please enter a number from %s to %s.","Context":["walk"],"Translation":"%s에서 %s 사이의 숫자를 입력하십시오."}]} ================================================ FILE: label.go ================================================ // 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 "github.com/lxn/win" type EllipsisMode int const ( EllipsisNone EllipsisMode = 0 EllipsisEnd = EllipsisMode(win.SS_ENDELLIPSIS) EllipsisPath = EllipsisMode(win.SS_PATHELLIPSIS) ) type Label struct { static textChangedPublisher EventPublisher } func NewLabel(parent Container) (*Label, error) { return NewLabelWithStyle(parent, 0) } func NewLabelWithStyle(parent Container, style uint32) (*Label, error) { l := new(Label) if err := l.init(l, parent, style); err != nil { return nil, err } l.SetTextAlignment(AlignNear) l.MustRegisterProperty("Text", NewProperty( func() interface{} { return l.Text() }, func(v interface{}) error { return l.SetText(assertStringOr(v, "")) }, l.textChangedPublisher.Event())) return l, nil } func (l *Label) asStatic() *static { return &l.static } func (l *Label) EllipsisMode() EllipsisMode { return EllipsisMode(win.GetWindowLong(l.hwndStatic, win.GWL_STYLE) & (win.SS_ENDELLIPSIS | win.SS_PATHELLIPSIS)) } func (l *Label) SetEllipsisMode(mode EllipsisMode) error { oldMode := l.EllipsisMode() if mode == oldMode { return nil } if err := setAndClearWindowLongBits(l.hwndStatic, win.GWL_STYLE, uint32(mode), uint32(oldMode)); err != nil { return err } l.RequestLayout() return nil } func (l *Label) TextAlignment() Alignment1D { return l.textAlignment1D() } func (l *Label) SetTextAlignment(alignment Alignment1D) error { if alignment == AlignDefault { alignment = AlignNear } return l.setTextAlignment1D(alignment) } func (l *Label) Text() string { return l.text() } func (l *Label) SetText(text string) error { if changed, err := l.setText(text); err != nil { return err } else if !changed { return nil } l.textChangedPublisher.Publish() return nil } ================================================ FILE: layout.go ================================================ // Copyright 2019 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 ( "sync" "github.com/lxn/win" ) func createLayoutItemForWidget(widget Widget) LayoutItem { ctx := newLayoutContext(widget.Handle()) return createLayoutItemForWidgetWithContext(widget, ctx) } func createLayoutItemForWidgetWithContext(widget Widget, ctx *LayoutContext) LayoutItem { var item LayoutItem if container, ok := widget.(Container); ok { if container.Layout() == nil { return nil } item = CreateLayoutItemsForContainerWithContext(container, ctx) } else { item = widget.CreateLayoutItem(ctx) } lib := item.AsLayoutItemBase() lib.ctx = ctx lib.handle = widget.Handle() lib.visible = widget.AsWidgetBase().visible lib.geometry = widget.AsWidgetBase().geometry lib.geometry.Alignment = widget.Alignment() lib.geometry.MinSize = widget.MinSizePixels() lib.geometry.MaxSize = widget.MaxSizePixels() lib.geometry.ConsumingSpaceWhenInvisible = widget.AlwaysConsumeSpace() return item } func CreateLayoutItemsForContainer(container Container) ContainerLayoutItem { ctx := newLayoutContext(container.Handle()) return CreateLayoutItemsForContainerWithContext(container, ctx) } func CreateLayoutItemsForContainerWithContext(container Container, ctx *LayoutContext) ContainerLayoutItem { var containerItem ContainerLayoutItem var clib *ContainerLayoutItemBase layout := container.Layout() if layout == nil || container.Children().Len() == 0 { layout = NewHBoxLayout() layout.SetMargins(Margins{}) } if widget, ok := container.(Widget); ok { containerItem = widget.CreateLayoutItem(ctx).(ContainerLayoutItem) } else { containerItem = layout.CreateLayoutItem(ctx) } clib = containerItem.AsContainerLayoutItemBase() clib.ctx = ctx clib.handle = container.Handle() cb := container.AsContainerBase() clib.visible = cb.visible clib.geometry = cb.geometry clib.geometry.ConsumingSpaceWhenInvisible = cb.AlwaysConsumeSpace() if lb := layout.asLayoutBase(); lb != nil { clib.alignment = lb.alignment clib.margins96dpi = lb.margins96dpi clib.spacing96dpi = lb.spacing96dpi } if len(clib.children) == 0 { children := container.Children() count := children.Len() for i := 0; i < count; i++ { item := createLayoutItemForWidgetWithContext(children.At(i), ctx) if item != nil { lib := item.AsLayoutItemBase() lib.ctx = ctx lib.parent = containerItem clib.children = append(clib.children, item) } } } return containerItem } func startLayoutPerformer(form Form) (performLayout chan ContainerLayoutItem, layoutResults chan []LayoutResult, inSizeLoop chan bool, updateStopwatch chan *stopwatch, quit chan struct{}) { performLayout = make(chan ContainerLayoutItem) layoutResults = make(chan []LayoutResult) inSizeLoop = make(chan bool) updateStopwatch = make(chan *stopwatch) quit = make(chan struct{}) var stopwatch *stopwatch go func() { sizing := false busy := false var cancel chan struct{} done := make(chan []LayoutResult) for { select { case root := <-performLayout: if busy { close(cancel) } busy = true cancel = make(chan struct{}) go layoutTree(root, root.Geometry().ClientSize, cancel, done, stopwatch) case results := <-done: busy = false if cancel != nil { close(cancel) cancel = nil } if sizing { layoutResults <- results } else { form.AsFormBase().synchronizeLayout(&formLayoutResult{form, stopwatch, results}) } case sizing = <-inSizeLoop: case stopwatch = <-updateStopwatch: case <-quit: close(performLayout) close(layoutResults) close(inSizeLoop) close(updateStopwatch) if cancel != nil { close(cancel) } close(done) close(quit) return } } }() return } // layoutTree lays out tree. size parameter is in native pixels. func layoutTree(root ContainerLayoutItem, size Size, cancel chan struct{}, done chan []LayoutResult, stopwatch *stopwatch) { const minSizeCacheSubject = "layoutTree - populating min size cache" if stopwatch != nil { stopwatch.Start(minSizeCacheSubject) } // Populate some caches now, so we later need only read access to them from multiple goroutines. ctx := root.Context() populateContextForItem := func(item LayoutItem) { ctx.layoutItem2MinSizeEffective[item] = minSizeEffective(item) } var populateContextForContainer func(container ContainerLayoutItem) populateContextForContainer = func(container ContainerLayoutItem) { for _, child := range container.AsContainerLayoutItemBase().children { if cli, ok := child.(ContainerLayoutItem); ok { populateContextForContainer(cli) } else { populateContextForItem(child) } } populateContextForItem(container) } populateContextForContainer(root) if stopwatch != nil { stopwatch.Stop(minSizeCacheSubject) } const layoutSubject = "layoutTree - computing layout" if stopwatch != nil { stopwatch.Start(layoutSubject) } results := make(chan LayoutResult) finished := make(chan struct{}) go func() { defer func() { close(results) close(finished) }() var wg sync.WaitGroup var layoutSubtree func(container ContainerLayoutItem, size Size) layoutSubtree = func(container ContainerLayoutItem, size Size) { wg.Add(1) go func() { defer wg.Done() clib := container.AsContainerLayoutItemBase() clib.geometry.ClientSize = size items := container.PerformLayout() select { case <-cancel: return case results <- LayoutResult{container, items}: } for _, item := range items { select { case <-cancel: return default: } item.Item.Geometry().Size = item.Bounds.Size() if childContainer, ok := item.Item.(ContainerLayoutItem); ok { layoutSubtree(childContainer, item.Bounds.Size()) } } }() } layoutSubtree(root, size) wg.Wait() select { case <-cancel: return case finished <- struct{}{}: } }() var layoutResults []LayoutResult for { select { case result := <-results: layoutResults = append(layoutResults, result) case <-finished: if stopwatch != nil { stopwatch.Stop(layoutSubject) } done <- layoutResults return case <-cancel: if stopwatch != nil { stopwatch.Cancel(layoutSubject) } return } } } func applyLayoutResults(results []LayoutResult, stopwatch *stopwatch) error { if stopwatch != nil { const subject = "applyLayoutResults" stopwatch.Start(subject) defer stopwatch.Stop(subject) } var form Form for _, result := range results { if len(result.items) == 0 { continue } hdwp := win.BeginDeferWindowPos(int32(len(result.items))) if hdwp == 0 { return lastError("BeginDeferWindowPos") } var maybeInvalidate bool if wnd := windowFromHandle(result.container.Handle()); wnd != nil { if ctr, ok := wnd.(Container); ok { if cb := ctr.AsContainerBase(); cb != nil { maybeInvalidate = cb.hasComplexBackground() } } } for _, ri := range result.items { if ri.Item.Handle() != 0 { window := windowFromHandle(ri.Item.Handle()) if window == nil { continue } if form == nil { if form = window.Form(); form != nil { defer func() { hwndFocused := win.GetFocus() hwndForm := win.GetAncestor(hwndFocused, win.GA_ROOT) activeForm, _ := windowFromHandle(hwndForm).(Form) if hwndFocused == 0 || form.Handle() == hwndFocused || activeForm != window.Form() { form.AsFormBase().clientComposite.focusFirstCandidateDescendant() } }() } } widget := window.(Widget) oldBounds := widget.BoundsPixels() if ri.Bounds == oldBounds { continue } if ri.Bounds.X == oldBounds.X && ri.Bounds.Y == oldBounds.Y && ri.Bounds.Width == oldBounds.Width { if _, ok := widget.(*ComboBox); ok { if ri.Bounds.Height == oldBounds.Height+1 { continue } } else if ri.Bounds.Height == oldBounds.Height { continue } } if maybeInvalidate { if ri.Bounds.Width == oldBounds.Width && ri.Bounds.Height == oldBounds.Height && (ri.Bounds.X != oldBounds.X || ri.Bounds.Y != oldBounds.Y) { widget.Invalidate() } } if hdwp = win.DeferWindowPos( hdwp, ri.Item.Handle(), 0, int32(ri.Bounds.X), int32(ri.Bounds.Y), int32(ri.Bounds.Width), int32(ri.Bounds.Height), win.SWP_NOACTIVATE|win.SWP_NOOWNERZORDER|win.SWP_NOZORDER); hdwp == 0 { return lastError("DeferWindowPos") } if widget.GraphicsEffects().Len() == 0 { continue } widget.AsWidgetBase().invalidateBorderInParent() } } if !win.EndDeferWindowPos(hdwp) { return lastError("EndDeferWindowPos") } } return nil } // Margins define margins in 1/96" units or native pixels. type Margins struct { HNear, VNear, HFar, VFar int } func (m Margins) isZero() bool { return m.HNear == 0 && m.HFar == 0 && m.VNear == 0 && m.VFar == 0 } type Layout interface { Container() Container SetContainer(value Container) Margins() Margins SetMargins(value Margins) error Spacing() int SetSpacing(value int) error CreateLayoutItem(ctx *LayoutContext) ContainerLayoutItem asLayoutBase() *LayoutBase } type LayoutBase struct { layout Layout container Container margins96dpi Margins margins Margins // in native pixels spacing96dpi int spacing int // in native pixels alignment Alignment2D resetNeeded bool dirty bool } func (l *LayoutBase) asLayoutBase() *LayoutBase { return l } func (l *LayoutBase) Container() Container { return l.container } func (l *LayoutBase) SetContainer(value Container) { if value == l.container { return } if l.container != nil { l.container.SetLayout(nil) } l.container = value if value != nil && value.Layout() != l.layout { value.SetLayout(l.layout) } l.updateMargins() l.updateSpacing() if l.container != nil { l.container.RequestLayout() } } func (l *LayoutBase) Margins() Margins { return l.margins96dpi } func (l *LayoutBase) SetMargins(value Margins) error { if value == l.margins96dpi { return nil } if value.HNear < 0 || value.VNear < 0 || value.HFar < 0 || value.VFar < 0 { return newError("margins must be positive") } l.margins96dpi = value l.updateMargins() if l.container != nil { l.container.RequestLayout() } return nil } func (l *LayoutBase) Spacing() int { return l.spacing96dpi } func (l *LayoutBase) SetSpacing(value int) error { if value == l.spacing96dpi { return nil } if value < 0 { return newError("spacing cannot be negative") } l.spacing96dpi = value l.updateSpacing() if l.container != nil { l.container.RequestLayout() } return nil } func (l *LayoutBase) updateMargins() { if l.container != nil { l.margins = MarginsFrom96DPI(l.margins96dpi, l.container.AsWindowBase().DPI()) } } func (l *LayoutBase) updateSpacing() { if l.container != nil { l.spacing = IntFrom96DPI(l.spacing96dpi, l.container.AsWindowBase().DPI()) } } func (l *LayoutBase) Alignment() Alignment2D { return l.alignment } func (l *LayoutBase) SetAlignment(alignment Alignment2D) error { if alignment != l.alignment { if alignment < AlignHVDefault || alignment > AlignHFarVFar { return newError("invalid Alignment value") } l.alignment = alignment if l.container != nil { l.container.RequestLayout() } } return nil } type IdealSizer interface { // IdealSize returns ideal window size in native pixels. IdealSize() Size } type MinSizer interface { // MinSize returns minimum window size in native pixels. MinSize() Size } type MinSizeForSizer interface { // MinSize returns minimum window size for given size. Both sizes are in native pixels. MinSizeForSize(size Size) Size } type HeightForWidther interface { HasHeightForWidth() bool // HeightForWidth returns appropriate height if element has given width. width parameter and // return value are in native pixels. HeightForWidth(width int) int } type LayoutContext struct { layoutItem2MinSizeEffective map[LayoutItem]Size // in native pixels dpi int } func (ctx *LayoutContext) DPI() int { return ctx.dpi } func newLayoutContext(handle win.HWND) *LayoutContext { return &LayoutContext{ layoutItem2MinSizeEffective: make(map[LayoutItem]Size), dpi: int(win.GetDpiForWindow(handle)), } } type LayoutItem interface { AsLayoutItemBase() *LayoutItemBase Context() *LayoutContext Handle() win.HWND Geometry() *Geometry Parent() ContainerLayoutItem Visible() bool LayoutFlags() LayoutFlags } type ContainerLayoutItem interface { LayoutItem MinSizer MinSizeForSizer HeightForWidther AsContainerLayoutItemBase() *ContainerLayoutItemBase // MinSizeEffectiveForChild returns minimum effective size for a child in native pixels. MinSizeEffectiveForChild(child LayoutItem) Size PerformLayout() []LayoutResultItem Children() []LayoutItem containsHandle(handle win.HWND) bool } type LayoutItemBase struct { ctx *LayoutContext handle win.HWND geometry Geometry parent ContainerLayoutItem visible bool } func (lib *LayoutItemBase) AsLayoutItemBase() *LayoutItemBase { return lib } func (lib *LayoutItemBase) Context() *LayoutContext { return lib.ctx } func (lib *LayoutItemBase) Handle() win.HWND { return lib.handle } func (lib *LayoutItemBase) Geometry() *Geometry { return &lib.geometry } func (lib *LayoutItemBase) Parent() ContainerLayoutItem { return lib.parent } func (lib *LayoutItemBase) Visible() bool { return lib.visible } type ContainerLayoutItemBase struct { LayoutItemBase children []LayoutItem margins96dpi Margins spacing96dpi int alignment Alignment2D } func (clib *ContainerLayoutItemBase) AsContainerLayoutItemBase() *ContainerLayoutItemBase { return clib } var clibMinSizeEffectiveForChildMutex sync.Mutex func (clib *ContainerLayoutItemBase) MinSizeEffectiveForChild(child LayoutItem) Size { // NOTE: This map is pre-populated in startLayoutTree before performing layout. // For other usages it is not pre-populated and we assume this method will then // be called from the main goroutine exclusively. // If we want to do concurrent size measurement, we will need to pre-populate also. // FIXME: There seems to be a bug in pre-population, so we use a mutex for now. clibMinSizeEffectiveForChildMutex.Lock() if clib.ctx != nil { if size, ok := clib.ctx.layoutItem2MinSizeEffective[child]; ok { clibMinSizeEffectiveForChildMutex.Unlock() return size } } if clib.ctx == nil { if clib.parent == nil { clib.ctx = newLayoutContext(clib.Handle()) } else { clib.ctx = clib.parent.Context() } } child.AsLayoutItemBase().ctx = clib.ctx clibMinSizeEffectiveForChildMutex.Unlock() size := minSizeEffective(child) clibMinSizeEffectiveForChildMutex.Lock() if clib.ctx != nil { clib.ctx.layoutItem2MinSizeEffective[child] = size } clibMinSizeEffectiveForChildMutex.Unlock() return size } func (clib *ContainerLayoutItemBase) Children() []LayoutItem { return clib.children } func (clib *ContainerLayoutItemBase) SetChildren(children []LayoutItem) { clib.children = children } func (clib *ContainerLayoutItemBase) containsHandle(handle win.HWND) bool { for _, item := range clib.children { if item.Handle() == handle { return true } } return false } func (clib *ContainerLayoutItemBase) HasHeightForWidth() bool { for _, child := range clib.children { if hfw, ok := child.(HeightForWidther); ok && hfw.HasHeightForWidth() { return true } } return false } type greedyLayoutItem struct { LayoutItemBase } func NewGreedyLayoutItem() LayoutItem { return new(greedyLayoutItem) } func (*greedyLayoutItem) LayoutFlags() LayoutFlags { return ShrinkableHorz | GrowableHorz | GreedyHorz | ShrinkableVert | GrowableVert | GreedyVert } func (li *greedyLayoutItem) IdealSize() Size { return SizeFrom96DPI(Size{100, 100}, li.ctx.dpi) } func (li *greedyLayoutItem) MinSize() Size { return SizeFrom96DPI(Size{50, 50}, li.ctx.dpi) } type Geometry struct { Alignment Alignment2D MinSize Size // in native pixels MaxSize Size // in native pixels IdealSize Size // in native pixels Size Size // in native pixels ClientSize Size // in native pixels ConsumingSpaceWhenInvisible bool } type formLayoutResult struct { form Form stopwatch *stopwatch results []LayoutResult } type LayoutResult struct { container ContainerLayoutItem items []LayoutResultItem } type LayoutResultItem struct { Item LayoutItem Bounds Rectangle // in native pixels } func shouldLayoutItem(item LayoutItem) bool { if item == nil { return false } _, isSpacer := item.(*spacerLayoutItem) return isSpacer || item.Visible() || item.Geometry().ConsumingSpaceWhenInvisible } func itemsToLayout(allItems []LayoutItem) []LayoutItem { filteredItems := make([]LayoutItem, 0, len(allItems)) for i := 0; i < cap(filteredItems); i++ { item := allItems[i] if !shouldLayoutItem(item) { continue } var idealSize Size if hfw, ok := item.(HeightForWidther); !ok || !hfw.HasHeightForWidth() { if is, ok := item.(IdealSizer); ok { idealSize = is.IdealSize() } } if idealSize.Width == 0 && idealSize.Height == 0 && item.LayoutFlags() == 0 { continue } filteredItems = append(filteredItems, item) } return filteredItems } func anyVisibleItemInHierarchy(item LayoutItem) bool { if item == nil || !item.Visible() { return false } if cli, ok := item.(ContainerLayoutItem); ok { for _, child := range cli.AsContainerLayoutItemBase().children { if anyVisibleItemInHierarchy(child) { return true } } } else if _, ok := item.(*spacerLayoutItem); !ok { return true } return false } // minSizeEffective returns minimum effective size in native pixels func minSizeEffective(item LayoutItem) Size { geometry := item.Geometry() var s Size if msh, ok := item.(MinSizer); ok { s = msh.MinSize() } else if is, ok := item.(IdealSizer); ok { s = is.IdealSize() } size := maxSize(geometry.MinSize, s) max := geometry.MaxSize if max.Width > 0 && size.Width > max.Width { size.Width = max.Width } if max.Height > 0 && size.Height > max.Height { size.Height = max.Height } return size } ================================================ FILE: lineedit.go ================================================ // 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 } ================================================ FILE: linklabel.go ================================================ // Copyright 2017 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" "github.com/lxn/win" ) type LinkLabel struct { WidgetBase textChangedPublisher EventPublisher linkActivatedPublisher LinkLabelLinkEventPublisher } func NewLinkLabel(parent Container) (*LinkLabel, error) { ll := new(LinkLabel) if err := InitWidget( ll, parent, "SysLink", win.WS_TABSTOP|win.WS_VISIBLE, 0); err != nil { return nil, err } ll.SetBackground(nullBrushSingleton) ll.MustRegisterProperty("Text", NewProperty( func() interface{} { return ll.Text() }, func(v interface{}) error { return ll.SetText(assertStringOr(v, "")) }, ll.textChangedPublisher.Event())) return ll, nil } func (ll *LinkLabel) Text() string { return ll.text() } func (ll *LinkLabel) SetText(value string) error { if value == ll.Text() { return nil } if err := ll.setText(value); err != nil { return err } ll.RequestLayout() return nil } func (ll *LinkLabel) LinkActivated() *LinkLabelLinkEvent { return ll.linkActivatedPublisher.Event() } func (ll *LinkLabel) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_NOTIFY: nml := (*win.NMLINK)(unsafe.Pointer(lParam)) switch nml.Hdr.Code { case win.NM_CLICK, win.NM_RETURN: link := &LinkLabelLink{ ll: ll, index: int(nml.Item.ILink), id: syscall.UTF16ToString(nml.Item.SzID[:]), url: syscall.UTF16ToString(nml.Item.SzUrl[:]), } ll.linkActivatedPublisher.Publish(link) } case win.WM_KILLFOCUS: ll.ensureStyleBits(win.WS_TABSTOP, true) case win.WM_SETTEXT: ll.textChangedPublisher.Publish() case win.WM_WINDOWPOSCHANGED: wp := (*win.WINDOWPOS)(unsafe.Pointer(lParam)) if wp.Flags&win.SWP_NOSIZE != 0 { break } ll.Invalidate() } return ll.WidgetBase.WndProc(hwnd, msg, wParam, lParam) } type LinkLabelLinkEventHandler func(link *LinkLabelLink) type LinkLabelLinkEvent struct { handlers []LinkLabelLinkEventHandler } func (e *LinkLabelLinkEvent) Attach(handler LinkLabelLinkEventHandler) int { for i, h := range e.handlers { if h == nil { e.handlers[i] = handler return i } } e.handlers = append(e.handlers, handler) return len(e.handlers) - 1 } func (e *LinkLabelLinkEvent) Detach(handle int) { e.handlers[handle] = nil } type LinkLabelLinkEventPublisher struct { event LinkLabelLinkEvent } func (p *LinkLabelLinkEventPublisher) Event() *LinkLabelLinkEvent { return &p.event } func (p *LinkLabelLinkEventPublisher) Publish(link *LinkLabelLink) { for _, handler := range p.event.handlers { if handler != nil { handler(link) } } } type LinkLabelLink struct { ll *LinkLabel index int id string url string } func (lll *LinkLabelLink) Index() int { return lll.index } func (lll *LinkLabelLink) Id() string { return lll.id } func (lll *LinkLabelLink) URL() string { return lll.url } func (lll *LinkLabelLink) Enabled() (bool, error) { return lll.hasState(win.LIS_ENABLED) } func (lll *LinkLabelLink) SetEnabled(enabled bool) error { return lll.setState(win.LIS_ENABLED, enabled) } func (lll *LinkLabelLink) Focused() (bool, error) { return lll.hasState(win.LIS_FOCUSED) } func (lll *LinkLabelLink) SetFocused(focused bool) error { return lll.setState(win.LIS_FOCUSED, focused) } func (lll *LinkLabelLink) Visited() (bool, error) { return lll.hasState(win.LIS_VISITED) } func (lll *LinkLabelLink) SetVisited(visited bool) error { return lll.setState(win.LIS_VISITED, visited) } func (lll *LinkLabelLink) hasState(state uint32) (bool, error) { li := win.LITEM{ ILink: int32(lll.index), Mask: win.LIF_ITEMINDEX | win.LIF_STATE, StateMask: state, } if win.TRUE != lll.ll.SendMessage(win.LM_GETITEM, 0, uintptr(unsafe.Pointer(&li))) { return false, newError("LM_GETITEM") } return li.State&state == state, nil } func (lll *LinkLabelLink) setState(state uint32, set bool) error { li := win.LITEM{ Mask: win.LIF_STATE, StateMask: state, } if set { li.State = state } li.Mask |= win.LIF_ITEMINDEX li.ILink = int32(lll.index) if win.TRUE != lll.ll.SendMessage(win.LM_SETITEM, 0, uintptr(unsafe.Pointer(&li))) { return newError("LM_SETITEM") } return nil } func (ll *LinkLabel) CreateLayoutItem(ctx *LayoutContext) LayoutItem { var s win.SIZE ll.SendMessage(win.LM_GETIDEALSIZE, uintptr(ll.IntFrom96DPI(ll.maxSize96dpi.Width)), uintptr(unsafe.Pointer(&s))) return &linkLabelLayoutItem{ idealSize: sizeFromSIZE(s), } } type linkLabelLayoutItem struct { LayoutItemBase idealSize Size // in native pixels } func (*linkLabelLayoutItem) LayoutFlags() LayoutFlags { return 0 } func (li *linkLabelLayoutItem) IdealSize() Size { return li.idealSize } func (li *linkLabelLayoutItem) MinSize() Size { return li.idealSize } ================================================ FILE: listbox.go ================================================ // Copyright 2012 The Walk Authors. All rights reserved. // Use of lb source code is governed by a BSD-style // license that can be found in the LICENSE file. // +build windows package walk import ( "fmt" "math/big" "reflect" "syscall" "time" "unsafe" "github.com/lxn/win" ) type ListBox struct { WidgetBase bindingValueProvider BindingValueProvider model ListModel providedModel interface{} styler ListItemStyler style ListItemStyle bindingMember string displayMember string format string precision int prevCurIndex int currentValue interface{} itemsResetHandlerHandle int itemChangedHandlerHandle int itemsInsertedHandlerHandle int itemsRemovedHandlerHandle int maxItemTextWidth int // in native pixels lastWidth int // in native pixels lastWidthsMeasuredFor []int // in native pixels currentIndexChangedPublisher EventPublisher selectedIndexesChangedPublisher EventPublisher itemActivatedPublisher EventPublisher themeNormalBGColor Color themeNormalTextColor Color themeSelectedBGColor Color themeSelectedTextColor Color themeSelectedNotFocusedBGColor Color trackingMouseEvent bool } func NewListBox(parent Container) (*ListBox, error) { return NewListBoxWithStyle(parent, 0) } func NewListBoxWithStyle(parent Container, style uint32) (*ListBox, error) { lb := new(ListBox) err := InitWidget( lb, parent, "LISTBOX", win.WS_BORDER|win.WS_TABSTOP|win.WS_VISIBLE|win.WS_VSCROLL|win.WS_HSCROLL|win.LBS_NOINTEGRALHEIGHT|win.LBS_NOTIFY|style, 0) if err != nil { return nil, err } succeeded := false defer func() { if !succeeded { lb.Dispose() } }() lb.setTheme("Explorer") lb.style.dpi = lb.DPI() lb.ApplySysColors() lb.GraphicsEffects().Add(InteractionEffect) lb.GraphicsEffects().Add(FocusEffect) lb.MustRegisterProperty("CurrentIndex", NewProperty( func() interface{} { return lb.CurrentIndex() }, func(v interface{}) error { return lb.SetCurrentIndex(assertIntOr(v, -1)) }, lb.CurrentIndexChanged())) lb.MustRegisterProperty("CurrentItem", NewReadOnlyProperty( func() interface{} { if i := lb.CurrentIndex(); i > -1 { if rm, ok := lb.providedModel.(reflectModel); ok { return reflect.ValueOf(rm.Items()).Index(i).Interface() } } return nil }, lb.CurrentIndexChanged())) lb.MustRegisterProperty("HasCurrentItem", NewReadOnlyBoolProperty( func() bool { return lb.CurrentIndex() != -1 }, lb.CurrentIndexChanged())) lb.MustRegisterProperty("Value", NewProperty( func() interface{} { index := lb.CurrentIndex() if lb.bindingValueProvider == nil || index == -1 { return nil } return lb.bindingValueProvider.BindingValue(index) }, func(v interface{}) error { if lb.bindingValueProvider == nil { if lb.model == nil { return nil } else { return newError("Data binding is only supported using a model that implements BindingValueProvider.") } } index := -1 count := lb.model.ItemCount() for i := 0; i < count; i++ { if lb.bindingValueProvider.BindingValue(i) == v { index = i break } } return lb.SetCurrentIndex(index) }, lb.CurrentIndexChanged())) succeeded = true return lb, nil } func (*ListBox) LayoutFlags() LayoutFlags { return ShrinkableHorz | ShrinkableVert | GrowableHorz | GrowableVert | GreedyHorz | GreedyVert } func (lb *ListBox) ItemStyler() ListItemStyler { return lb.styler } func (lb *ListBox) SetItemStyler(styler ListItemStyler) { lb.styler = styler } func (lb *ListBox) ApplySysColors() { lb.WidgetBase.ApplySysColors() var hc win.HIGHCONTRAST hc.CbSize = uint32(unsafe.Sizeof(hc)) if win.SystemParametersInfo(win.SPI_GETHIGHCONTRAST, hc.CbSize, unsafe.Pointer(&hc), 0) { lb.style.highContrastActive = hc.DwFlags&win.HCF_HIGHCONTRASTON != 0 } lb.themeNormalBGColor = Color(win.GetSysColor(win.COLOR_WINDOW)) lb.themeNormalTextColor = Color(win.GetSysColor(win.COLOR_WINDOWTEXT)) lb.themeSelectedBGColor = Color(win.GetSysColor(win.COLOR_HIGHLIGHT)) lb.themeSelectedTextColor = Color(win.GetSysColor(win.COLOR_HIGHLIGHTTEXT)) lb.themeSelectedNotFocusedBGColor = Color(win.GetSysColor(win.COLOR_BTNFACE)) } func (lb *ListBox) ApplyDPI(dpi int) { lb.style.dpi = dpi lb.WidgetBase.ApplyDPI(dpi) } func (lb *ListBox) applyFont(font *Font) { lb.WidgetBase.applyFont(font) for i := range lb.lastWidthsMeasuredFor { lb.lastWidthsMeasuredFor[i] = 0 } } func (lb *ListBox) itemString(index int) string { switch val := lb.model.Value(index).(type) { case string: return val case time.Time: return val.Format(lb.format) case *big.Rat: return val.FloatString(lb.precision) default: return fmt.Sprintf(lb.format, val) } } //insert one item from list model func (lb *ListBox) insertItemAt(index int) error { str := lb.itemString(index) lp := uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(str))) ret := int(lb.SendMessage(win.LB_INSERTSTRING, uintptr(index), lp)) if ret == win.LB_ERRSPACE || ret == win.LB_ERR { return newError("SendMessage(LB_INSERTSTRING)") } return nil } func (lb *ListBox) removeItem(index int) error { if win.LB_ERR == int(lb.SendMessage(win.LB_DELETESTRING, uintptr(index), 0)) { return newError("SendMessage(LB_DELETESTRING)") } return nil } // reread all the items from list model func (lb *ListBox) resetItems() error { lb.SetSuspended(true) defer lb.SetSuspended(false) lb.SendMessage(win.LB_RESETCONTENT, 0, 0) lb.maxItemTextWidth = 0 oldValue := lb.currentValue if lb.model == nil { lb.SetCurrentIndex(-1) return nil } count := lb.model.ItemCount() lb.lastWidthsMeasuredFor = make([]int, count) for i := 0; i < count; i++ { if err := lb.insertItemAt(i); err != nil { return err } } if oldValue != nil { lb.Property("Value").Set(oldValue) } else { lb.SetCurrentIndex(-1) } if lb.styler == nil { // Update the listbox width (this sets the correct horizontal scrollbar). sh := lb.idealSize() lb.SendMessage(win.LB_SETHORIZONTALEXTENT, uintptr(sh.Width), 0) } return nil } func (lb *ListBox) ensureVisibleItemsHeightUpToDate() error { if lb.styler == nil { return nil } if !lb.Suspended() { lb.SetSuspended(true) defer lb.SetSuspended(false) } topIndex := int(lb.SendMessage(win.LB_GETTOPINDEX, 0, 0)) offset := maxi(0, topIndex-10) count := lb.model.ItemCount() var rc win.RECT lb.SendMessage(win.LB_GETITEMRECT, uintptr(offset), uintptr(unsafe.Pointer(&rc))) width := int(rc.Right - rc.Left) offsetTop := int(rc.Top) lbHeight := lb.HeightPixels() var pastBottomCount int for i := offset; i >= 0 && i < count; i++ { if lb.lastWidthsMeasuredFor[i] == lb.lastWidth { continue } lb.SendMessage(win.LB_GETITEMRECT, uintptr(i), uintptr(unsafe.Pointer(&rc))) if int(rc.Top)-offsetTop > lbHeight { if pastBottomCount++; pastBottomCount > 10 { break } } height := lb.styler.ItemHeight(i, width) lb.SendMessage(win.LB_SETITEMHEIGHT, uintptr(i), uintptr(height)) lb.lastWidthsMeasuredFor[i] = lb.lastWidth } lb.EnsureItemVisible(topIndex) return nil } func (lb *ListBox) attachModel() { itemsResetHandler := func() { lb.resetItems() } lb.itemsResetHandlerHandle = lb.model.ItemsReset().Attach(itemsResetHandler) itemChangedHandler := func(index int) { if win.CB_ERR == lb.SendMessage(win.LB_DELETESTRING, uintptr(index), 0) { newError("SendMessage(CB_DELETESTRING)") } lb.insertItemAt(index) if lb.styler != nil { var rc win.RECT lb.SendMessage(win.LB_GETITEMRECT, uintptr(index), uintptr(unsafe.Pointer(&rc))) width := int(rc.Right - rc.Left) height := lb.styler.ItemHeight(index, width) lb.SendMessage(win.LB_SETITEMHEIGHT, uintptr(index), uintptr(height)) lb.lastWidthsMeasuredFor[index] = lb.lastWidth } lb.SetCurrentIndex(lb.prevCurIndex) } lb.itemChangedHandlerHandle = lb.model.ItemChanged().Attach(itemChangedHandler) lb.itemsInsertedHandlerHandle = lb.model.ItemsInserted().Attach(func(from, to int) { if !lb.Suspended() { lb.SetSuspended(true) defer lb.SetSuspended(false) } for i := from; i <= to; i++ { lb.insertItemAt(i) } lb.lastWidthsMeasuredFor = append(lb.lastWidthsMeasuredFor[:from], append(make([]int, to-from+1), lb.lastWidthsMeasuredFor[from:]...)...) lb.ensureVisibleItemsHeightUpToDate() }) lb.itemsRemovedHandlerHandle = lb.model.ItemsRemoved().Attach(func(from, to int) { if !lb.Suspended() { lb.SetSuspended(true) defer lb.SetSuspended(false) } for i := to; i >= from; i-- { lb.removeItem(i) } lb.lastWidthsMeasuredFor = append(lb.lastWidthsMeasuredFor[:from], lb.lastWidthsMeasuredFor[to:]...) lb.ensureVisibleItemsHeightUpToDate() }) } func (lb *ListBox) detachModel() { lb.model.ItemsReset().Detach(lb.itemsResetHandlerHandle) lb.model.ItemChanged().Detach(lb.itemChangedHandlerHandle) lb.model.ItemsInserted().Detach(lb.itemsInsertedHandlerHandle) lb.model.ItemsRemoved().Detach(lb.itemsRemovedHandlerHandle) } // Model returns the model of the ListBox. func (lb *ListBox) Model() interface{} { return lb.providedModel } // SetModel sets the model of the ListBox. // // It is required that mdl either implements walk.ListModel or // walk.ReflectListModel or be a slice of pointers to struct or a []string. func (lb *ListBox) SetModel(mdl interface{}) error { model, ok := mdl.(ListModel) if !ok && mdl != nil { var err error if model, err = newReflectListModel(mdl); err != nil { return err } if _, ok := mdl.([]string); !ok { if badms, ok := model.(bindingAndDisplayMemberSetter); ok { badms.setBindingMember(lb.bindingMember) badms.setDisplayMember(lb.displayMember) } } } lb.providedModel = mdl if lb.model != nil { lb.detachModel() } lb.model = model lb.bindingValueProvider, _ = model.(BindingValueProvider) if model != nil { lb.attachModel() } if err := lb.resetItems(); err != nil { return err } return lb.ensureVisibleItemsHeightUpToDate() } // BindingMember returns the member from the model of the ListBox that is bound // to a field of the data source managed by an associated DataBinder. // // This is only applicable to walk.ReflectListModel models and simple slices of // pointers to struct. func (lb *ListBox) BindingMember() string { return lb.bindingMember } // SetBindingMember sets the member from the model of the ListBox that is bound // to a field of the data source managed by an associated DataBinder. // // This is only applicable to walk.ReflectListModel models and simple slices of // pointers to struct. // // For a model consisting of items of type S, data source field of type T and // bindingMember "Foo", this can be one of the following: // // A field Foo T // A method func (s S) Foo() T // A method func (s S) Foo() (T, error) // // If bindingMember is not a simple member name like "Foo", but a path to a // member like "A.B.Foo", members "A" and "B" both must be one of the options // mentioned above, but with T having type pointer to struct. func (lb *ListBox) SetBindingMember(bindingMember string) error { if bindingMember != "" { if _, ok := lb.providedModel.([]string); ok { return newError("invalid for []string model") } } lb.bindingMember = bindingMember if badms, ok := lb.model.(bindingAndDisplayMemberSetter); ok { badms.setBindingMember(bindingMember) } return nil } // DisplayMember returns the member from the model of the ListBox that is // displayed in the ListBox. // // This is only applicable to walk.ReflectListModel models and simple slices of // pointers to struct. func (lb *ListBox) DisplayMember() string { return lb.displayMember } // SetDisplayMember sets the member from the model of the ListBox that is // displayed in the ListBox. // // This is only applicable to walk.ReflectListModel models and simple slices of // pointers to struct. // // For a model consisting of items of type S, the type of the specified member T // and displayMember "Foo", this can be one of the following: // // A field Foo T // A method func (s S) Foo() T // A method func (s S) Foo() (T, error) // // If displayMember is not a simple member name like "Foo", but a path to a // member like "A.B.Foo", members "A" and "B" both must be one of the options // mentioned above, but with T having type pointer to struct. func (lb *ListBox) SetDisplayMember(displayMember string) error { if displayMember != "" { if _, ok := lb.providedModel.([]string); ok { return newError("invalid for []string model") } } lb.displayMember = displayMember if badms, ok := lb.model.(bindingAndDisplayMemberSetter); ok { badms.setDisplayMember(displayMember) } return nil } func (lb *ListBox) Format() string { return lb.format } func (lb *ListBox) SetFormat(value string) { lb.format = value } func (lb *ListBox) Precision() int { return lb.precision } func (lb *ListBox) SetPrecision(value int) { lb.precision = value } // calculateMaxItemTextWidth returns maximum item text width in native pixels. func (lb *ListBox) calculateMaxItemTextWidth() int { hdc := win.GetDC(lb.hWnd) if hdc == 0 { newError("GetDC failed") return -1 } defer win.ReleaseDC(lb.hWnd, hdc) hFontOld := win.SelectObject(hdc, win.HGDIOBJ(lb.Font().handleForDPI(lb.DPI()))) defer win.SelectObject(hdc, hFontOld) var maxWidth int if lb.model == nil { return -1 } count := lb.model.ItemCount() for i := 0; i < count; i++ { item := lb.itemString(i) var s win.SIZE str := syscall.StringToUTF16(item) if !win.GetTextExtentPoint32(hdc, &str[0], int32(len(str)-1), &s) { newError("GetTextExtentPoint32 failed") return -1 } maxWidth = maxi(maxWidth, int(s.CX)) } return maxWidth } // idealSize returns listbox ideal size in native pixels. func (lb *ListBox) idealSize() Size { defaultSize := lb.dialogBaseUnitsToPixels(Size{50, 12}) if lb.maxItemTextWidth <= 0 { lb.maxItemTextWidth = lb.calculateMaxItemTextWidth() } // FIXME: Use GetThemePartSize instead of guessing w := maxi(defaultSize.Width, lb.maxItemTextWidth+IntFrom96DPI(24, lb.DPI())) h := defaultSize.Height + 1 return Size{w, h} } func (lb *ListBox) ItemVisible(index int) bool { topIndex := int(lb.SendMessage(win.LB_GETTOPINDEX, 0, 0)) var rc win.RECT lb.SendMessage(win.LB_GETITEMRECT, uintptr(index), uintptr(unsafe.Pointer(&rc))) return index >= topIndex && int(rc.Top) < lb.HeightPixels() } func (lb *ListBox) EnsureItemVisible(index int) { lb.SendMessage(win.LB_SETTOPINDEX, uintptr(index), 0) } func (lb *ListBox) CurrentIndex() int { return int(int32(lb.SendMessage(win.LB_GETCURSEL, 0, 0))) } func (lb *ListBox) SetCurrentIndex(value int) error { if value > -1 && win.LB_ERR == int(int32(lb.SendMessage(win.LB_SETCURSEL, uintptr(value), 0))) { return newError("Invalid index or ensure lb is single-selection listbox") } if value != lb.prevCurIndex { if value == -1 { lb.currentValue = nil } else { lb.currentValue = lb.Property("Value").Get() } lb.prevCurIndex = value lb.currentIndexChangedPublisher.Publish() } return nil } func (lb *ListBox) SelectedIndexes() []int { count := int(int32(lb.SendMessage(win.LB_GETCOUNT, 0, 0))) if count < 1 { return nil } index32 := make([]int32, count) if n := int(int32(lb.SendMessage(win.LB_GETSELITEMS, uintptr(count), uintptr(unsafe.Pointer(&index32[0]))))); n == win.LB_ERR { return nil } else { indexes := make([]int, n) for i := 0; i < n; i++ { indexes[i] = int(index32[i]) } return indexes } } func (lb *ListBox) SetSelectedIndexes(indexes []int) { var m int32 = -1 lb.SendMessage(win.LB_SETSEL, win.FALSE, uintptr(m)) for _, v := range indexes { lb.SendMessage(win.LB_SETSEL, win.TRUE, uintptr(uint32(v))) } lb.selectedIndexesChangedPublisher.Publish() } func (lb *ListBox) CurrentIndexChanged() *Event { return lb.currentIndexChangedPublisher.Event() } func (lb *ListBox) SelectedIndexesChanged() *Event { return lb.selectedIndexesChangedPublisher.Event() } func (lb *ListBox) ItemActivated() *Event { return lb.itemActivatedPublisher.Event() } func (lb *ListBox) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_MEASUREITEM: if lb.styler == nil { break } mis := (*win.MEASUREITEMSTRUCT)(unsafe.Pointer(lParam)) mis.ItemHeight = uint32(lb.styler.DefaultItemHeight()) return win.TRUE case win.WM_DRAWITEM: dis := (*win.DRAWITEMSTRUCT)(unsafe.Pointer(lParam)) if lb.styler == nil || dis.ItemID < 0 || dis.ItemAction != win.ODA_DRAWENTIRE { return win.TRUE } lb.style.index = int(dis.ItemID) lb.style.rc = dis.RcItem lb.style.bounds = rectangleFromRECT(dis.RcItem) lb.style.dpi = lb.DPI() lb.style.state = dis.ItemState lb.style.hwnd = lb.hWnd lb.style.hdc = dis.HDC lb.style.Font = lb.Font() if dis.ItemAction == win.ODA_FOCUS { return win.TRUE } var hTheme win.HTHEME if !lb.style.highContrastActive { if hTheme = win.OpenThemeData(lb.hWnd, syscall.StringToUTF16Ptr("Listview")); hTheme != 0 { defer win.CloseThemeData(hTheme) } } lb.style.hTheme = hTheme if dis.ItemState&win.ODS_CHECKED != 0 { if lb.style.highContrastActive || lb.Focused() { lb.style.BackgroundColor = lb.themeSelectedBGColor lb.style.TextColor = lb.themeSelectedTextColor } else { lb.style.BackgroundColor = lb.themeSelectedNotFocusedBGColor lb.style.TextColor = lb.themeNormalTextColor } } else if int(dis.ItemID) == lb.style.hoverIndex { if hTheme == 0 { lb.style.BackgroundColor = lb.themeNormalBGColor } else { lb.style.BackgroundColor = lb.themeSelectedBGColor } lb.style.TextColor = lb.themeNormalTextColor } else { lb.style.BackgroundColor = lb.themeNormalBGColor lb.style.TextColor = lb.themeNormalTextColor } if lb.themeNormalTextColor == RGB(0, 0, 0) { lb.style.LineColor = RGB(0, 0, 0) } else { lb.style.LineColor = RGB(255, 255, 255) } lb.style.defaultTextColor = lb.style.TextColor lb.style.DrawBackground() lb.styler.StyleItem(&lb.style) defer func() { lb.style.bounds = Rectangle{} if lb.style.canvas != nil { lb.style.canvas.Dispose() lb.style.canvas = nil } lb.style.hdc = 0 }() return win.TRUE case win.WM_WINDOWPOSCHANGED: wp := (*win.WINDOWPOS)(unsafe.Pointer(lParam)) if wp.Flags&win.SWP_NOSIZE != 0 { break } if lb.styler != nil && lb.styler.ItemHeightDependsOnWidth() { width := lb.WidthPixels() if width != lb.lastWidth { lb.lastWidth = width lb.lastWidthsMeasuredFor = make([]int, lb.model.ItemCount()) } } lb.ensureVisibleItemsHeightUpToDate() return win.CallWindowProc(lb.origWndProcPtr, hwnd, msg, wParam, lParam) case win.WM_VSCROLL: lb.ensureVisibleItemsHeightUpToDate() case win.WM_MOUSEWHEEL: lb.ensureVisibleItemsHeightUpToDate() case win.WM_LBUTTONDOWN: lb.Invalidate() case win.WM_MOUSEMOVE: if lb.styler == nil { break } if !lb.trackingMouseEvent { var tme win.TRACKMOUSEEVENT tme.CbSize = uint32(unsafe.Sizeof(tme)) tme.DwFlags = win.TME_LEAVE tme.HwndTrack = lb.hWnd lb.trackingMouseEvent = win.TrackMouseEvent(&tme) } oldHoverIndex := lb.style.hoverIndex result := uint32(lb.SendMessage(win.LB_ITEMFROMPOINT, 0, lParam)) if win.HIWORD(result) == 0 { index := int(win.LOWORD(result)) var rc win.RECT lb.SendMessage(win.LB_GETITEMRECT, uintptr(index), uintptr(unsafe.Pointer(&rc))) lp := uint32(lParam) x := int32(win.LOWORD(lp)) y := int32(win.HIWORD(lp)) if x >= rc.Left && x <= rc.Right && y >= rc.Top && y <= rc.Bottom { lb.style.hoverIndex = index win.InvalidateRect(lb.hWnd, &rc, true) } } if lb.style.hoverIndex != oldHoverIndex { if wParam&win.MK_LBUTTON != 0 { lb.Invalidate() } else { lb.invalidateItem(oldHoverIndex) lb.invalidateItem(lb.style.hoverIndex) } } case win.WM_MOUSELEAVE: if lb.styler == nil { break } lb.trackingMouseEvent = false index := lb.style.hoverIndex lb.style.hoverIndex = -1 lb.invalidateItem(index) case win.WM_COMMAND: switch win.HIWORD(uint32(wParam)) { case win.LBN_SELCHANGE: lb.ensureVisibleItemsHeightUpToDate() lb.prevCurIndex = lb.CurrentIndex() lb.currentValue = lb.Property("Value").Get() lb.currentIndexChangedPublisher.Publish() lb.selectedIndexesChangedPublisher.Publish() case win.LBN_DBLCLK: lb.itemActivatedPublisher.Publish() } case win.WM_GETDLGCODE: if form := ancestor(lb); form != nil { if dlg, ok := form.(dialogish); ok { if dlg.DefaultButton() != nil { // If the ListBox 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: if uint32(lParam)>>30 == 0 && Key(wParam) == KeyReturn && lb.CurrentIndex() > -1 { lb.itemActivatedPublisher.Publish() } } return lb.WidgetBase.WndProc(hwnd, msg, wParam, lParam) } func (lb *ListBox) invalidateItem(index int) { var rc win.RECT lb.SendMessage(win.LB_GETITEMRECT, uintptr(index), uintptr(unsafe.Pointer(&rc))) win.InvalidateRect(lb.hWnd, &rc, true) } func (lb *ListBox) CreateLayoutItem(ctx *LayoutContext) LayoutItem { return NewGreedyLayoutItem() } ================================================ FILE: mainloop_cgo.go ================================================ // Copyright 2019 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,walk_use_cgo package walk import ( "unsafe" "github.com/lxn/win" ) // #include // // extern void shimRunSynchronized(uintptr_t fb); // extern unsigned char shimHandleKeyDown(uintptr_t fb, uintptr_t m); // // static int mainloop(uintptr_t handle_ptr, uintptr_t fb_ptr) // { // HANDLE *hwnd = (HANDLE *)handle_ptr; // MSG m; // int r; // // while (*hwnd) { // r = GetMessage(&m, NULL, 0, 0); // if (!r) // return m.wParam; // else if (r < 0) // return -1; // if (m.message == WM_KEYDOWN && shimHandleKeyDown(fb_ptr, (uintptr_t)&m)) // continue; // if (!IsDialogMessage(*hwnd, &m)) { // TranslateMessage(&m); // DispatchMessage(&m); // } // shimRunSynchronized(fb_ptr); // } // return 0; // } import "C" //export shimHandleKeyDown func shimHandleKeyDown(fb uintptr, msg uintptr) bool { return (*FormBase)(unsafe.Pointer(fb)).handleKeyDown((*win.MSG)(unsafe.Pointer(msg))) } //export shimRunSynchronized func shimRunSynchronized(fb uintptr) { (*FormBase)(unsafe.Pointer(fb)).group.RunSynchronized() } func (fb *FormBase) mainLoop() int { return int(C.mainloop(C.uintptr_t(uintptr(unsafe.Pointer(&fb.hWnd))), C.uintptr_t(uintptr(unsafe.Pointer(fb))))) } ================================================ FILE: mainloop_default.go ================================================ // Copyright 2019 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,!walk_use_cgo package walk import ( "unsafe" "github.com/lxn/win" ) func (fb *FormBase) mainLoop() int { msg := (*win.MSG)(unsafe.Pointer(win.GlobalAlloc(0, unsafe.Sizeof(win.MSG{})))) defer win.GlobalFree(win.HGLOBAL(unsafe.Pointer(msg))) for fb.hWnd != 0 { switch win.GetMessage(msg, 0, 0, 0) { case 0: return int(msg.WParam) case -1: return -1 } switch msg.Message { case win.WM_KEYDOWN: if fb.handleKeyDown(msg) { continue } } if !win.IsDialogMessage(fb.hWnd, msg) { win.TranslateMessage(msg) win.DispatchMessage(msg) } fb.group.RunSynchronized() } return 0 } ================================================ FILE: mainwindow.go ================================================ // 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 ( "unsafe" "github.com/lxn/win" ) const mainWindowWindowClass = `\o/ Walk_MainWindow_Class \o/` func init() { AppendToWalkInit(func() { MustRegisterWindowClass(mainWindowWindowClass) }) } type MainWindowCfg struct { Name string Bounds Rectangle } type MainWindow struct { FormBase windowPlacement *win.WINDOWPLACEMENT menu *Menu toolBar *ToolBar statusBar *StatusBar } func NewMainWindow() (*MainWindow, error) { return NewMainWindowWithName("") } func NewMainWindowWithName(name string) (*MainWindow, error) { return NewMainWindowWithCfg(&MainWindowCfg{Name: name}) } func NewMainWindowWithCfg(cfg *MainWindowCfg) (*MainWindow, error) { mw := new(MainWindow) mw.SetName(cfg.Name) if err := initWindowWithCfg(&windowCfg{ Window: mw, ClassName: mainWindowWindowClass, Style: win.WS_OVERLAPPEDWINDOW, ExStyle: win.WS_EX_CONTROLPARENT, Bounds: cfg.Bounds, }); err != nil { return nil, err } succeeded := false defer func() { if !succeeded { mw.Dispose() } }() mw.SetPersistent(true) var err error if mw.menu, err = newMenuBar(mw); err != nil { return nil, err } if !win.SetMenu(mw.hWnd, mw.menu.hMenu) { return nil, lastError("SetMenu") } tb, err := NewToolBar(mw) if err != nil { return nil, err } mw.SetToolBar(tb) if mw.statusBar, err = NewStatusBar(mw); err != nil { return nil, err } mw.statusBar.parent = nil mw.Children().Remove(mw.statusBar) mw.statusBar.parent = mw win.SetParent(mw.statusBar.hWnd, mw.hWnd) mw.statusBar.visibleChangedPublisher.event.Attach(func() { mw.SetBoundsPixels(mw.BoundsPixels()) }) succeeded = true return mw, nil } func (mw *MainWindow) Menu() *Menu { return mw.menu } func (mw *MainWindow) ToolBar() *ToolBar { return mw.toolBar } func (mw *MainWindow) SetToolBar(tb *ToolBar) { if mw.toolBar != nil { win.SetParent(mw.toolBar.hWnd, 0) } if tb != nil { parent := tb.parent tb.parent = nil parent.Children().Remove(tb) tb.parent = mw win.SetParent(tb.hWnd, mw.hWnd) } mw.toolBar = tb } func (mw *MainWindow) StatusBar() *StatusBar { return mw.statusBar } func (mw *MainWindow) ClientBoundsPixels() Rectangle { bounds := mw.FormBase.ClientBoundsPixels() if mw.toolBar != nil && mw.toolBar.Actions().Len() > 0 { tlbBounds := mw.toolBar.BoundsPixels() bounds.Y += tlbBounds.Height bounds.Height -= tlbBounds.Height } if mw.statusBar.Visible() { bounds.Height -= mw.statusBar.HeightPixels() } return bounds } func (mw *MainWindow) SetVisible(visible bool) { if visible { win.DrawMenuBar(mw.hWnd) mw.clientComposite.RequestLayout() } mw.FormBase.SetVisible(visible) } func (mw *MainWindow) applyFont(font *Font) { mw.FormBase.applyFont(font) if mw.toolBar != nil { mw.toolBar.applyFont(font) } if mw.statusBar != nil { mw.statusBar.applyFont(font) } } func (mw *MainWindow) Fullscreen() bool { return win.GetWindowLong(mw.hWnd, win.GWL_STYLE)&win.WS_OVERLAPPEDWINDOW == 0 } func (mw *MainWindow) SetFullscreen(fullscreen bool) error { if fullscreen == mw.Fullscreen() { return nil } if fullscreen { var mi win.MONITORINFO mi.CbSize = uint32(unsafe.Sizeof(mi)) if mw.windowPlacement == nil { mw.windowPlacement = new(win.WINDOWPLACEMENT) } if !win.GetWindowPlacement(mw.hWnd, mw.windowPlacement) { return lastError("GetWindowPlacement") } if !win.GetMonitorInfo(win.MonitorFromWindow( mw.hWnd, win.MONITOR_DEFAULTTOPRIMARY), &mi) { return newError("GetMonitorInfo") } if err := mw.ensureStyleBits(win.WS_OVERLAPPEDWINDOW, false); err != nil { return err } if r := mi.RcMonitor; !win.SetWindowPos( mw.hWnd, win.HWND_TOP, r.Left, r.Top, r.Right-r.Left, r.Bottom-r.Top, win.SWP_FRAMECHANGED|win.SWP_NOOWNERZORDER) { return lastError("SetWindowPos") } } else { if err := mw.ensureStyleBits(win.WS_OVERLAPPEDWINDOW, true); err != nil { return err } if !win.SetWindowPlacement(mw.hWnd, mw.windowPlacement) { return lastError("SetWindowPlacement") } if !win.SetWindowPos(mw.hWnd, 0, 0, 0, 0, 0, win.SWP_FRAMECHANGED|win.SWP_NOMOVE| win.SWP_NOOWNERZORDER|win.SWP_NOSIZE|win.SWP_NOZORDER) { return lastError("SetWindowPos") } } return nil } func (mw *MainWindow) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_WINDOWPOSCHANGED, win.WM_SIZE: if win.WM_WINDOWPOSCHANGED == msg { wp := (*win.WINDOWPOS)(unsafe.Pointer(lParam)) if wp.Flags&win.SWP_NOSIZE != 0 { break } } cb := mw.ClientBoundsPixels() if mw.toolBar != nil { bounds := Rectangle{0, 0, cb.Width, mw.toolBar.HeightPixels()} if mw.toolBar.BoundsPixels() != bounds { mw.toolBar.SetBoundsPixels(bounds) } } bounds := Rectangle{0, cb.Y + cb.Height, cb.Width, mw.statusBar.HeightPixels()} if mw.statusBar.BoundsPixels() != bounds { mw.statusBar.SetBoundsPixels(bounds) } case win.WM_INITMENUPOPUP: mw.menu.updateItemsWithImageForWindow(mw) } return mw.FormBase.WndProc(hwnd, msg, wParam, lParam) } ================================================ FILE: maptablemodel.go ================================================ // Copyright 2013 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 ( "sort" ) type mapTableModel struct { TableModelBase SorterBase dataMembers []string dataSource interface{} items []map[string]interface{} } func newMapTableModel(dataSource interface{}) (TableModel, error) { items, ok := dataSource.([]map[string]interface{}) if !ok { return nil, newError("dataSource must be assignable to []map[string]interface{}") } return &mapTableModel{dataSource: dataSource, items: items}, nil } func (m *mapTableModel) setDataMembers(dataMembers []string) { m.dataMembers = dataMembers } func (m *mapTableModel) RowCount() int { return len(m.items) } func (m *mapTableModel) Value(row, col int) interface{} { if m.items[row] == nil { if populator, ok := m.dataSource.(Populator); ok { if err := populator.Populate(row); err != nil { return err } } if m.items[row] == nil { return nil } } return m.items[row][m.dataMembers[col]] } func (m *mapTableModel) Sort(col int, order SortOrder) error { m.col, m.order = col, order sort.Stable(m) m.changedPublisher.Publish() return nil } func (m *mapTableModel) Len() int { return m.RowCount() } func (m *mapTableModel) Less(i, j int) bool { col := m.SortedColumn() return less(m.Value(i, col), m.Value(j, col), m.SortOrder()) } func (m *mapTableModel) Swap(i, j int) { m.items[i], m.items[j] = m.items[j], m.items[i] } ================================================ FILE: menu.go ================================================ // 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 ( "fmt" "syscall" "unsafe" "github.com/lxn/win" ) type Menu struct { hMenu win.HMENU window Window actions *ActionList getDPI func() int } func newMenuBar(window Window) (*Menu, error) { hMenu := win.CreateMenu() if hMenu == 0 { return nil, lastError("CreateMenu") } m := &Menu{ hMenu: hMenu, window: window, } m.actions = newActionList(m) return m, nil } func NewMenu() (*Menu, error) { hMenu := win.CreatePopupMenu() if hMenu == 0 { return nil, lastError("CreatePopupMenu") } var mi win.MENUINFO mi.CbSize = uint32(unsafe.Sizeof(mi)) if !win.GetMenuInfo(hMenu, &mi) { return nil, lastError("GetMenuInfo") } mi.FMask |= win.MIM_STYLE mi.DwStyle = win.MNS_CHECKORBMP if !win.SetMenuInfo(hMenu, &mi) { return nil, lastError("SetMenuInfo") } m := &Menu{ hMenu: hMenu, } m.actions = newActionList(m) return m, nil } func (m *Menu) Dispose() { m.actions.Clear() if m.hMenu != 0 { win.DestroyMenu(m.hMenu) m.hMenu = 0 } } func (m *Menu) IsDisposed() bool { return m.hMenu == 0 } func (m *Menu) Actions() *ActionList { return m.actions } func (m *Menu) updateItemsWithImageForWindow(window Window) { if m.window == nil { m.window = window defer func() { m.window = nil }() } for _, action := range m.actions.actions { if action.image != nil { m.onActionChanged(action) } if action.menu != nil { action.menu.updateItemsWithImageForWindow(window) } } } func (m *Menu) initMenuItemInfoFromAction(mii *win.MENUITEMINFO, action *Action) { mii.CbSize = uint32(unsafe.Sizeof(*mii)) mii.FMask = win.MIIM_FTYPE | win.MIIM_ID | win.MIIM_STATE | win.MIIM_STRING if action.image != nil { mii.FMask |= win.MIIM_BITMAP dpi := 96 if m.getDPI != nil { dpi = m.getDPI() } else if m.window != nil { dpi = m.window.DPI() } else { dpi = screenDPI() } if bmp, err := iconCache.Bitmap(action.image, dpi); err == nil { mii.HbmpItem = bmp.hBmp } } if action.IsSeparator() { mii.FType |= win.MFT_SEPARATOR } else { mii.FType |= win.MFT_STRING var text string if s := action.shortcut; s.Key != 0 { text = fmt.Sprintf("%s\t%s", action.text, s.String()) } else { text = action.text } mii.DwTypeData = syscall.StringToUTF16Ptr(text) mii.Cch = uint32(len([]rune(action.text))) } mii.WID = uint32(action.id) if action.Enabled() { mii.FState &^= win.MFS_DISABLED } else { mii.FState |= win.MFS_DISABLED } if action.Checkable() { mii.FMask |= win.MIIM_CHECKMARKS } if action.Checked() { mii.FState |= win.MFS_CHECKED } if action.Exclusive() { mii.FType |= win.MFT_RADIOCHECK } menu := action.menu if menu != nil { mii.FMask |= win.MIIM_SUBMENU mii.HSubMenu = menu.hMenu } } func (m *Menu) handleDefaultState(action *Action) { if action.Default() { // Unset other default actions before we set this one. Otherwise insertion fails. win.SetMenuDefaultItem(m.hMenu, ^uint32(0), false) for _, otherAction := range m.actions.actions { if otherAction != action { otherAction.SetDefault(false) } } } } func (m *Menu) onActionChanged(action *Action) error { m.handleDefaultState(action) if !action.Visible() { return nil } var mii win.MENUITEMINFO m.initMenuItemInfoFromAction(&mii, action) if !win.SetMenuItemInfo(m.hMenu, uint32(m.actions.indexInObserver(action)), true, &mii) { return newError("SetMenuItemInfo failed") } if action.Default() { win.SetMenuDefaultItem(m.hMenu, uint32(m.actions.indexInObserver(action)), true) } if action.Exclusive() && action.Checked() { var first, last int index := m.actions.Index(action) for i := index; i >= 0; i-- { first = i if !m.actions.At(i).Exclusive() { break } } for i := index; i < m.actions.Len(); i++ { last = i if !m.actions.At(i).Exclusive() { break } } if !win.CheckMenuRadioItem(m.hMenu, uint32(first), uint32(last), uint32(index), win.MF_BYPOSITION) { return newError("CheckMenuRadioItem failed") } } return nil } func (m *Menu) onActionVisibleChanged(action *Action) error { if !action.IsSeparator() { defer m.actions.updateSeparatorVisibility() } if action.Visible() { return m.insertAction(action, true) } return m.removeAction(action, true) } func (m *Menu) insertAction(action *Action, visibleChanged bool) (err error) { m.handleDefaultState(action) if !visibleChanged { action.addChangedHandler(m) defer func() { if err != nil { action.removeChangedHandler(m) } }() } if !action.Visible() { return } index := m.actions.indexInObserver(action) var mii win.MENUITEMINFO m.initMenuItemInfoFromAction(&mii, action) if !win.InsertMenuItem(m.hMenu, uint32(index), true, &mii) { return newError("InsertMenuItem failed") } if action.Default() { win.SetMenuDefaultItem(m.hMenu, uint32(m.actions.indexInObserver(action)), true) } menu := action.menu if menu != nil { menu.window = m.window } m.ensureMenuBarRedrawn() return } func (m *Menu) removeAction(action *Action, visibleChanged bool) error { index := m.actions.indexInObserver(action) if !win.RemoveMenu(m.hMenu, uint32(index), win.MF_BYPOSITION) { return lastError("RemoveMenu") } if !visibleChanged { action.removeChangedHandler(m) } m.ensureMenuBarRedrawn() return nil } func (m *Menu) ensureMenuBarRedrawn() { if m.window != nil { if mw, ok := m.window.(*MainWindow); ok && mw != nil { win.DrawMenuBar(mw.Handle()) } } } func (m *Menu) onInsertedAction(action *Action) error { return m.insertAction(action, false) } func (m *Menu) onRemovingAction(action *Action) error { return m.removeAction(action, false) } func (m *Menu) onClearingActions() error { for i := m.actions.Len() - 1; i >= 0; i-- { if action := m.actions.At(i); action.Visible() { if err := m.onRemovingAction(action); err != nil { return err } } } return nil } ================================================ FILE: messagebox.go ================================================ // 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 ( "strings" "syscall" ) import ( "github.com/lxn/win" ) type MsgBoxStyle uint const ( MsgBoxOK MsgBoxStyle = win.MB_OK MsgBoxOKCancel MsgBoxStyle = win.MB_OKCANCEL MsgBoxAbortRetryIgnore MsgBoxStyle = win.MB_ABORTRETRYIGNORE MsgBoxYesNoCancel MsgBoxStyle = win.MB_YESNOCANCEL MsgBoxYesNo MsgBoxStyle = win.MB_YESNO MsgBoxRetryCancel MsgBoxStyle = win.MB_RETRYCANCEL MsgBoxCancelTryContinue MsgBoxStyle = win.MB_CANCELTRYCONTINUE MsgBoxIconHand MsgBoxStyle = win.MB_ICONHAND MsgBoxIconQuestion MsgBoxStyle = win.MB_ICONQUESTION MsgBoxIconExclamation MsgBoxStyle = win.MB_ICONEXCLAMATION MsgBoxIconAsterisk MsgBoxStyle = win.MB_ICONASTERISK MsgBoxUserIcon MsgBoxStyle = win.MB_USERICON MsgBoxIconWarning MsgBoxStyle = win.MB_ICONWARNING MsgBoxIconError MsgBoxStyle = win.MB_ICONERROR MsgBoxIconInformation MsgBoxStyle = win.MB_ICONINFORMATION MsgBoxIconStop MsgBoxStyle = win.MB_ICONSTOP MsgBoxDefButton1 MsgBoxStyle = win.MB_DEFBUTTON1 MsgBoxDefButton2 MsgBoxStyle = win.MB_DEFBUTTON2 MsgBoxDefButton3 MsgBoxStyle = win.MB_DEFBUTTON3 MsgBoxDefButton4 MsgBoxStyle = win.MB_DEFBUTTON4 MsgBoxApplModal MsgBoxStyle = win.MB_APPLMODAL MsgBoxSystemModal MsgBoxStyle = win.MB_SYSTEMMODAL MsgBoxTaskModal MsgBoxStyle = win.MB_TASKMODAL MsgBoxHelp MsgBoxStyle = win.MB_HELP MsgBoxSetForeground MsgBoxStyle = win.MB_SETFOREGROUND MsgBoxDefaultDesktopOnly MsgBoxStyle = win.MB_DEFAULT_DESKTOP_ONLY MsgBoxTopMost MsgBoxStyle = win.MB_TOPMOST MsgBoxRight MsgBoxStyle = win.MB_RIGHT MsgBoxRTLReading MsgBoxStyle = win.MB_RTLREADING MsgBoxServiceNotification MsgBoxStyle = win.MB_SERVICE_NOTIFICATION ) func MsgBox(owner Form, title, message string, style MsgBoxStyle) int { var ownerHWnd win.HWND if owner != nil { ownerHWnd = owner.Handle() } return int(win.MessageBox( ownerHWnd, syscall.StringToUTF16Ptr(strings.ReplaceAll(message, "\x00", "␀")), syscall.StringToUTF16Ptr(strings.ReplaceAll(title, "\x00", "␀")), uint32(style))) } ================================================ FILE: metafile.go ================================================ // 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 ( "math" "syscall" "unsafe" "github.com/lxn/win" ) const milimeterPerMeter float64 = 1000.0 type Metafile struct { hdc win.HDC hemf win.HENHMETAFILE size Size // in native pixels dpi Size } func NewMetafile(referenceCanvas *Canvas) (*Metafile, error) { hdc := win.CreateEnhMetaFile(referenceCanvas.hdc, nil, nil, nil) if hdc == 0 { return nil, newError("CreateEnhMetaFile failed") } return &Metafile{hdc: hdc}, nil } func NewMetafileFromFile(filePath string) (*Metafile, error) { hemf := win.GetEnhMetaFile(syscall.StringToUTF16Ptr(filePath)) if hemf == 0 { return nil, newError("GetEnhMetaFile failed") } mf := &Metafile{hemf: hemf} err := mf.readSizeFromHeader() if err != nil { return nil, err } return mf, nil } func (mf *Metafile) Dispose() { mf.ensureFinished() if mf.hemf != 0 { win.DeleteEnhMetaFile(mf.hemf) mf.hemf = 0 } } func (mf *Metafile) Save(filePath string) error { hemf := win.CopyEnhMetaFile(mf.hemf, syscall.StringToUTF16Ptr(filePath)) if hemf == 0 { return newError("CopyEnhMetaFile failed") } win.DeleteEnhMetaFile(hemf) return nil } func (mf *Metafile) readSizeFromHeader() error { var hdr win.ENHMETAHEADER if win.GetEnhMetaFileHeader(mf.hemf, uint32(unsafe.Sizeof(hdr)), &hdr) == 0 { return newError("GetEnhMetaFileHeader failed") } mf.size = sizeFromRECT(hdr.RclBounds) scale := milimeterPerMeter / inchesPerMeter mf.dpi = Size{ int(math.Round(float64(hdr.SzlDevice.CX) / float64(hdr.SzlMillimeters.CX) * scale)), int(math.Round(float64(hdr.SzlDevice.CY) / float64(hdr.SzlMillimeters.CY) * scale)), } return nil } func (mf *Metafile) ensureFinished() error { if mf.hdc == 0 { if mf.hemf == 0 { return newError("already disposed") } else { return nil } } mf.hemf = win.CloseEnhMetaFile(mf.hdc) if mf.hemf == 0 { return newError("CloseEnhMetaFile failed") } mf.hdc = 0 return mf.readSizeFromHeader() } // Size returns image size in 1/96" units. func (mf *Metafile) Size() Size { return Size{ Width: scaleInt(mf.size.Width, 96.0/float64(mf.dpi.Width)), Height: scaleInt(mf.size.Height, 96.0/float64(mf.dpi.Height)), } } func (mf *Metafile) draw(hdc win.HDC, location Point) error { return mf.drawStretched(hdc, Rectangle{location.X, location.Y, mf.size.Width, mf.size.Height}) } func (mf *Metafile) drawStretched(hdc win.HDC, bounds Rectangle) error { rc := bounds.toRECT() if !win.PlayEnhMetaFile(hdc, mf.hemf, &rc) { return newError("PlayEnhMetaFile failed") } return nil } ================================================ FILE: models.go ================================================ // Copyright 2012 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" "github.com/lxn/win" ) // BindingValueProvider is the interface that a model must implement to support // data binding with widgets like ComboBox. type BindingValueProvider interface { BindingValue(index int) interface{} } // ListModel is the interface that a model must implement to support widgets // like ComboBox. type ListModel interface { // ItemCount returns the number of items in the model. ItemCount() int // Value returns the value that should be displayed for the given index. Value(index int) interface{} // ItemsReset returns the event that the model should publish when the // number of its items changes. ItemsReset() *Event // ItemChanged returns the event that the model should publish when an item // was changed. ItemChanged() *IntEvent // ItemsInserted returns the event that the model should publish when a // contiguous range of items was inserted. ItemsInserted() *IntRangeEvent // ItemsRemoved returns the event that the model should publish when a // contiguous range of items was removed. ItemsRemoved() *IntRangeEvent } // ListModelBase implements the ItemsReset and ItemChanged methods of the // ListModel interface. type ListModelBase struct { itemsResetPublisher EventPublisher itemChangedPublisher IntEventPublisher itemsInsertedPublisher IntRangeEventPublisher itemsRemovedPublisher IntRangeEventPublisher } func (lmb *ListModelBase) ItemsReset() *Event { return lmb.itemsResetPublisher.Event() } func (lmb *ListModelBase) ItemChanged() *IntEvent { return lmb.itemChangedPublisher.Event() } func (lmb *ListModelBase) ItemsInserted() *IntRangeEvent { return lmb.itemsInsertedPublisher.Event() } func (lmb *ListModelBase) ItemsRemoved() *IntRangeEvent { return lmb.itemsRemovedPublisher.Event() } func (lmb *ListModelBase) PublishItemsReset() { lmb.itemsResetPublisher.Publish() } func (lmb *ListModelBase) PublishItemChanged(index int) { lmb.itemChangedPublisher.Publish(index) } func (lmb *ListModelBase) PublishItemsInserted(from, to int) { lmb.itemsInsertedPublisher.Publish(from, to) } func (lmb *ListModelBase) PublishItemsRemoved(from, to int) { lmb.itemsRemovedPublisher.Publish(from, to) } // ReflectListModel provides an alternative to the ListModel interface. It // uses reflection to obtain data. type ReflectListModel interface { // Items returns the model data, which must be a slice of pointer to struct. Items() interface{} // ItemsReset returns the event that the model should publish when the // number of its items changes. ItemsReset() *Event // ItemChanged returns the event that the model should publish when an item // was changed. ItemChanged() *IntEvent // ItemsInserted returns the event that the model should publish when a // contiguous range of items was inserted. ItemsInserted() *IntRangeEvent // ItemsRemoved returns the event that the model should publish when a // contiguous range of items was removed. ItemsRemoved() *IntRangeEvent setValueFunc(value func(index int) interface{}) } // ReflectListModelBase implements the ItemsReset and ItemChanged methods of // the ReflectListModel interface. type ReflectListModelBase struct { ListModelBase value func(index int) interface{} } func (rlmb *ReflectListModelBase) setValueFunc(value func(index int) interface{}) { rlmb.value = value } func (rlmb *ReflectListModelBase) Value(index int) interface{} { return rlmb.value(index) } // TableModel is the interface that a model must implement to support widgets // like TableView. type TableModel interface { // RowCount returns the number of rows in the model. RowCount() int // Value returns the value that should be displayed for the given cell. Value(row, col int) interface{} // RowsReset returns the event that the model should publish when the number // of its rows changes. RowsReset() *Event // RowChanged returns the event that the model should publish when a row was // changed. RowChanged() *IntEvent // RowsChanged returns the event that the model should publish when a // contiguous range of items was changed. RowsChanged() *IntRangeEvent // RowsInserted returns the event that the model should publish when a // contiguous range of items was inserted. If the model supports sorting, it // is assumed to be sorted before the model publishes the event. RowsInserted() *IntRangeEvent // RowsRemoved returns the event that the model should publish when a // contiguous range of items was removed. RowsRemoved() *IntRangeEvent } // TableModelBase implements the RowsReset and RowChanged methods of the // TableModel interface. type TableModelBase struct { rowsResetPublisher EventPublisher rowChangedPublisher IntEventPublisher rowsChangedPublisher IntRangeEventPublisher rowsInsertedPublisher IntRangeEventPublisher rowsRemovedPublisher IntRangeEventPublisher } func (tmb *TableModelBase) RowsReset() *Event { return tmb.rowsResetPublisher.Event() } func (tmb *TableModelBase) RowChanged() *IntEvent { return tmb.rowChangedPublisher.Event() } func (tmb *TableModelBase) RowsChanged() *IntRangeEvent { return tmb.rowsChangedPublisher.Event() } func (tmb *TableModelBase) RowsInserted() *IntRangeEvent { return tmb.rowsInsertedPublisher.Event() } func (tmb *TableModelBase) RowsRemoved() *IntRangeEvent { return tmb.rowsRemovedPublisher.Event() } func (tmb *TableModelBase) PublishRowsReset() { tmb.rowsResetPublisher.Publish() } func (tmb *TableModelBase) PublishRowChanged(row int) { tmb.rowChangedPublisher.Publish(row) } func (tmb *TableModelBase) PublishRowsChanged(from, to int) { tmb.rowsChangedPublisher.Publish(from, to) } func (tmb *TableModelBase) PublishRowsInserted(from, to int) { tmb.rowsInsertedPublisher.Publish(from, to) } func (tmb *TableModelBase) PublishRowsRemoved(from, to int) { tmb.rowsRemovedPublisher.Publish(from, to) } // ReflectTableModel provides an alternative to the TableModel interface. It // uses reflection to obtain data. type ReflectTableModel interface { // Items returns the model data, which must be a slice of pointer to struct. Items() interface{} // RowsReset returns the event that the model should publish when the // number of its items changes. RowsReset() *Event // RowChanged returns the event that the model should publish when an item // was changed. RowChanged() *IntEvent // RowsChanged returns the event that the model should publish when a // contiguous range of items was changed. RowsChanged() *IntRangeEvent // RowsInserted returns the event that the model should publish when a // contiguous range of items was inserted. If the model supports sorting, it // is assumed to be sorted before the model publishes the event. RowsInserted() *IntRangeEvent // RowsRemoved returns the event that the model should publish when a // contiguous range of items was removed. RowsRemoved() *IntRangeEvent setValueFunc(value func(row, col int) interface{}) } // ReflectTableModelBase implements the ItemsReset and ItemChanged methods of // the ReflectTableModel interface. type ReflectTableModelBase struct { TableModelBase value func(row, col int) interface{} } func (rtmb *ReflectTableModelBase) setValueFunc(value func(row, col int) interface{}) { rtmb.value = value } func (rtmb *ReflectTableModelBase) Value(row, col int) interface{} { return rtmb.value(row, col) } type interceptedSorter interface { sorterBase() *SorterBase setSortFunc(sort func(col int, order SortOrder) error) } // SortedReflectTableModelBase implements the RowsReset and RowChanged methods // of the ReflectTableModel interface as well as the Sorter interface for // pre-implemented in-memory sorting. type SortedReflectTableModelBase struct { ReflectTableModelBase SorterBase sort func(col int, order SortOrder) error } func (srtmb *SortedReflectTableModelBase) setSortFunc(sort func(col int, order SortOrder) error) { srtmb.sort = sort } func (srtmb *SortedReflectTableModelBase) sorterBase() *SorterBase { return &srtmb.SorterBase } func (srtmb *SortedReflectTableModelBase) Sort(col int, order SortOrder) error { if srtmb.sort != nil { return srtmb.sort(col, order) } return srtmb.SorterBase.Sort(col, order) } // Populator is an interface that can be implemented by Reflect*Models and slice // types to populate themselves on demand. // // Widgets like TableView, ListBox and ComboBox support lazy population of a // Reflect*Model or slice, if it implements this interface. type Populator interface { // Populate initializes the slot specified by index. // // For best performance it is probably a good idea to populate more than a // single slot of the slice at once. Populate(index int) error } // ImageProvider is the interface that a model must implement to support // displaying an item image. type ImageProvider interface { // Image returns the image to display for the item at index index. // // Supported types are *walk.Bitmap, *walk.Icon and string. A string will be // interpreted as a file path and the icon associated with the file will be // used. It is not supported to use strings together with the other options // in the same model instance. Image(index int) interface{} } // CellStyler is the interface that must be implemented to provide a tabular // widget like TableView with cell display style information. type CellStyler interface { // StyleCell is called for each cell to pick up cell style information. StyleCell(style *CellStyle) } // CellStyle carries information about the display style of a cell in a tabular widget // like TableView. type CellStyle struct { row int col int bounds Rectangle // in native pixels hdc win.HDC dpi int canvas *Canvas BackgroundColor Color TextColor Color Font *Font // Image is the image to display in the cell. // // Supported types are *walk.Bitmap, *walk.Icon and string. A string will be // interpreted as a file path and the icon associated with the file will be // used. It is not supported to use strings together with the other options // in the same model instance. Image interface{} } func (cs *CellStyle) Row() int { return cs.row } func (cs *CellStyle) Col() int { return cs.col } func (cs *CellStyle) Bounds() Rectangle { return RectangleTo96DPI(cs.bounds, cs.dpi) } func (cs *CellStyle) BoundsPixels() Rectangle { return cs.bounds } func (cs *CellStyle) Canvas() *Canvas { if cs.canvas != nil { cs.canvas.dpi = cs.dpi return cs.canvas } if cs.hdc != 0 { cs.canvas, _ = newCanvasFromHDC(cs.hdc) cs.canvas.dpi = cs.dpi } return cs.canvas } // IDProvider is the interface that must be implemented by models to enable // widgets like TableView to attempt keeping the current item when the model // publishes a reset event. type IDProvider interface { ID(index int) interface{} } // ListItemStyler is the interface that must be implemented to provide a list // widget like ListBox with item display style information. type ListItemStyler interface { // ItemHeightDependsOnWidth returns whether item height depends on width. ItemHeightDependsOnWidth() bool // DefaultItemHeight returns the initial height in native pixels for any item. DefaultItemHeight() int // ItemHeight is called for each item to retrieve the height of the item. width parameter and // return value are specified in native pixels. ItemHeight(index int, width int) int // StyleItem is called for each item to pick up item style information. StyleItem(style *ListItemStyle) } // ListItemStyle carries information about the display style of an item in a list widget // like ListBox. type ListItemStyle struct { BackgroundColor Color TextColor Color defaultTextColor Color LineColor Color Font *Font index int hoverIndex int rc win.RECT bounds Rectangle // in native pixels state uint32 hTheme win.HTHEME hwnd win.HWND hdc win.HDC dpi int canvas *Canvas highContrastActive bool } func (lis *ListItemStyle) Index() int { return lis.index } func (lis *ListItemStyle) Bounds() Rectangle { return RectangleTo96DPI(lis.bounds, lis.dpi) } func (lis *ListItemStyle) BoundsPixels() Rectangle { return lis.bounds } func (lis *ListItemStyle) Canvas() *Canvas { if lis.canvas != nil { lis.canvas.dpi = lis.dpi return lis.canvas } if lis.hdc != 0 { lis.canvas, _ = newCanvasFromHDC(lis.hdc) lis.canvas.dpi = lis.dpi } return lis.canvas } func (lis *ListItemStyle) DrawBackground() error { canvas := lis.Canvas() if canvas == nil { return nil } stateID := lis.stateID() if lis.hTheme != 0 && stateID != win.LISS_NORMAL { if win.FAILED(win.DrawThemeBackground(lis.hTheme, lis.hdc, win.LVP_LISTITEM, stateID, &lis.rc, nil)) { return newError("DrawThemeBackground failed") } } else { brush, err := NewSolidColorBrush(lis.BackgroundColor) if err != nil { return err } defer brush.Dispose() if err := canvas.FillRectanglePixels(brush, lis.bounds); err != nil { return err } if lis.highContrastActive && (lis.index == lis.hoverIndex || stateID != win.LISS_NORMAL) { pen, err := NewCosmeticPen(PenSolid, lis.LineColor) if err != nil { return err } defer pen.Dispose() if err := canvas.DrawRectanglePixels(pen, lis.bounds); err != nil { return err } } } return nil } // DrawText draws text inside given bounds specified in native pixels. func (lis *ListItemStyle) DrawText(text string, bounds Rectangle, format DrawTextFormat) error { if lis.hTheme != 0 && lis.TextColor == lis.defaultTextColor { if lis.Font != nil { hFontOld := win.SelectObject(lis.hdc, win.HGDIOBJ(lis.Font.handleForDPI(lis.dpi))) defer win.SelectObject(lis.hdc, hFontOld) } rc := bounds.toRECT() if win.FAILED(win.DrawThemeTextEx(lis.hTheme, lis.hdc, win.LVP_LISTITEM, lis.stateID(), syscall.StringToUTF16Ptr(text), int32(len(([]rune)(text))), uint32(format), &rc, nil)) { return newError("DrawThemeTextEx failed") } } else { if canvas := lis.Canvas(); canvas != nil { if err := canvas.DrawTextPixels(text, lis.Font, lis.TextColor, bounds, format); err != nil { return err } } } return nil } func (lis *ListItemStyle) stateID() int32 { if lis.state&win.ODS_CHECKED != 0 { if win.GetFocus() == lis.hwnd { if lis.index == lis.hoverIndex { return win.LISS_HOTSELECTED } else { return win.LISS_SELECTED } } else { return win.LISS_SELECTEDNOTFOCUS } } else if lis.index == lis.hoverIndex { return win.LISS_HOT } return win.LISS_NORMAL } // ItemChecker is the interface that a model must implement to support check // boxes in a widget like TableView. type ItemChecker interface { // Checked returns if the specified item is checked. Checked(index int) bool // SetChecked sets if the specified item is checked. SetChecked(index int, checked bool) error } // SortOrder specifies the order by which items are sorted. type SortOrder int const ( // SortAscending specifies ascending sort order. SortAscending SortOrder = iota // SortDescending specifies descending sort order. SortDescending ) // Sorter is the interface that a model must implement to support sorting with a // widget like TableView. type Sorter interface { // ColumnSortable returns whether column col is sortable. ColumnSortable(col int) bool // Sort sorts column col in order order. // // If col is -1 then no column is to be sorted. Sort must publish the event // returned from SortChanged() after sorting. Sort(col int, order SortOrder) error // SortChanged returns an event that is published after sorting. SortChanged() *Event // SortedColumn returns the index of the currently sorted column, or -1 if // no column is currently sorted. SortedColumn() int // SortOrder returns the current sort order. SortOrder() SortOrder } // SorterBase implements the Sorter interface. // // You still need to provide your own implementation of at least the Sort method // to actually sort and reset the model. Your Sort method should call the // SorterBase implementation so the SortChanged event, that e.g. a TableView // widget depends on, is published. type SorterBase struct { changedPublisher EventPublisher col int order SortOrder } func (sb *SorterBase) ColumnSortable(col int) bool { return true } func (sb *SorterBase) Sort(col int, order SortOrder) error { sb.col, sb.order = col, order sb.changedPublisher.Publish() return nil } func (sb *SorterBase) SortChanged() *Event { return sb.changedPublisher.Event() } func (sb *SorterBase) SortedColumn() int { return sb.col } func (sb *SorterBase) SortOrder() SortOrder { return sb.order } // Imager provides access to an image of objects like tree items. type Imager interface { // Image returns the image to display for an item. // // Supported types are *walk.Bitmap, *walk.Icon and string. A string will be // interpreted as a file path and the icon associated with the file will be // used. It is not supported to use strings together with the other options // in the same model instance. Image() interface{} } // TreeItem represents an item in a TreeView widget. type TreeItem interface { // Text returns the text of the item. Text() string // Parent returns the parent of the item. Parent() TreeItem // ChildCount returns the number of children of the item. ChildCount() int // ChildAt returns the child at the specified index. ChildAt(index int) TreeItem } // HasChilder enables widgets like TreeView to determine if an item has any // child, without enforcing to fully count all children. type HasChilder interface { HasChild() bool } // TreeModel provides widgets like TreeView with item data. type TreeModel interface { // LazyPopulation returns if the model prefers on-demand population. // // This is useful for models that potentially contain huge amounts of items, // e.g. a model that represents a file system. LazyPopulation() bool // RootCount returns the number of root items. RootCount() int // RootAt returns the root item at the specified index. RootAt(index int) TreeItem // ItemsReset returns the event that the model should publish when the // descendants of the specified item, or all items if no item is specified, // are reset. ItemsReset() *TreeItemEvent // ItemChanged returns the event that the model should publish when an item // was changed. ItemChanged() *TreeItemEvent // ItemInserted returns the event that the model should publish when an item // was inserted into the model. ItemInserted() *TreeItemEvent // ItemRemoved returns the event that the model should publish when an item // was removed from the model. ItemRemoved() *TreeItemEvent } // TreeModelBase partially implements the TreeModel interface. // // You still need to provide your own implementation of at least the // RootCount and RootAt methods. If your model needs lazy population, // you will also have to implement LazyPopulation. type TreeModelBase struct { itemsResetPublisher TreeItemEventPublisher itemChangedPublisher TreeItemEventPublisher itemInsertedPublisher TreeItemEventPublisher itemRemovedPublisher TreeItemEventPublisher } func (tmb *TreeModelBase) LazyPopulation() bool { return false } func (tmb *TreeModelBase) ItemsReset() *TreeItemEvent { return tmb.itemsResetPublisher.Event() } func (tmb *TreeModelBase) ItemChanged() *TreeItemEvent { return tmb.itemChangedPublisher.Event() } func (tmb *TreeModelBase) ItemInserted() *TreeItemEvent { return tmb.itemInsertedPublisher.Event() } func (tmb *TreeModelBase) ItemRemoved() *TreeItemEvent { return tmb.itemRemovedPublisher.Event() } func (tmb *TreeModelBase) PublishItemsReset(parent TreeItem) { tmb.itemsResetPublisher.Publish(parent) } func (tmb *TreeModelBase) PublishItemChanged(item TreeItem) { tmb.itemChangedPublisher.Publish(item) } func (tmb *TreeModelBase) PublishItemInserted(item TreeItem) { tmb.itemInsertedPublisher.Publish(item) } func (tmb *TreeModelBase) PublishItemRemoved(item TreeItem) { tmb.itemRemovedPublisher.Publish(item) } ================================================ FILE: mouseevent.go ================================================ // Copyright 2011 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 ( "github.com/lxn/win" ) type MouseButton int const ( LeftButton MouseButton = win.MK_LBUTTON RightButton MouseButton = win.MK_RBUTTON MiddleButton MouseButton = win.MK_MBUTTON ) type mouseEventHandlerInfo struct { handler MouseEventHandler once bool } // MouseEventHandler is called for mouse events. x and y are measured in native pixels. type MouseEventHandler func(x, y int, button MouseButton) type MouseEvent struct { handlers []mouseEventHandlerInfo } func (e *MouseEvent) Attach(handler MouseEventHandler) int { handlerInfo := mouseEventHandlerInfo{handler, false} for i, h := range e.handlers { if h.handler == nil { e.handlers[i] = handlerInfo return i } } e.handlers = append(e.handlers, handlerInfo) return len(e.handlers) - 1 } func (e *MouseEvent) Detach(handle int) { e.handlers[handle].handler = nil } func (e *MouseEvent) Once(handler MouseEventHandler) { i := e.Attach(handler) e.handlers[i].once = true } type MouseEventPublisher struct { event MouseEvent } func (p *MouseEventPublisher) Event() *MouseEvent { return &p.event } // Publish publishes mouse event. x and y are measured in native pixels. func (p *MouseEventPublisher) Publish(x, y int, button MouseButton) { for i, h := range p.event.handlers { if h.handler != nil { h.handler(x, y, button) if h.once { p.event.Detach(i) } } } } func MouseWheelEventDelta(button MouseButton) int { return int(int32(button) >> 16) } func MouseWheelEventKeyState(button MouseButton) int { return int(int32(button) & 0xFFFF) } ================================================ FILE: notifyicon.go ================================================ // Copyright 2011 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" "github.com/lxn/win" ) var notifyIcons = make(map[*NotifyIcon]bool) func notifyIconWndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) (result uintptr) { // Retrieve our *NotifyIcon from the message window. ptr := win.GetWindowLongPtr(hwnd, win.GWLP_USERDATA) ni := (*NotifyIcon)(unsafe.Pointer(ptr)) switch lParam { case win.WM_LBUTTONDOWN: ni.publishMouseEvent(&ni.mouseDownPublisher, LeftButton) case win.WM_LBUTTONUP: ni.publishMouseEvent(&ni.mouseUpPublisher, LeftButton) case win.WM_RBUTTONDOWN: ni.publishMouseEvent(&ni.mouseDownPublisher, RightButton) case win.WM_RBUTTONUP: ni.publishMouseEvent(&ni.mouseUpPublisher, RightButton) win.SendMessage(hwnd, msg, wParam, win.WM_CONTEXTMENU) case win.WM_CONTEXTMENU: if ni.contextMenu.Actions().Len() == 0 { break } win.SetForegroundWindow(hwnd) var p win.POINT if !win.GetCursorPos(&p) { lastError("GetCursorPos") } ni.applyDPI() actionId := uint16(win.TrackPopupMenuEx( ni.contextMenu.hMenu, win.TPM_NOANIMATION|win.TPM_RETURNCMD, p.X, p.Y, hwnd, nil)) if actionId != 0 { if action, ok := actionsById[actionId]; ok { action.raiseTriggered() } } return 0 case win.NIN_BALLOONUSERCLICK: ni.messageClickedPublisher.Publish() } return win.DefWindowProc(hwnd, msg, wParam, lParam) } // NotifyIcon represents an icon in the taskbar notification area. type NotifyIcon struct { id uint32 hWnd win.HWND lastDPI int contextMenu *Menu icon Image toolTip string visible bool mouseDownPublisher MouseEventPublisher mouseUpPublisher MouseEventPublisher messageClickedPublisher EventPublisher } // NewNotifyIcon creates and returns a new NotifyIcon. // // The NotifyIcon is initially not visible. func NewNotifyIcon(form Form) (*NotifyIcon, error) { fb := form.AsFormBase() // Add our notify icon to the status area and make sure it is hidden. nid := win.NOTIFYICONDATA{ HWnd: fb.hWnd, UFlags: win.NIF_MESSAGE | win.NIF_STATE, DwState: win.NIS_HIDDEN, DwStateMask: win.NIS_HIDDEN, UCallbackMessage: notifyIconMessageId, } nid.CbSize = uint32(unsafe.Sizeof(nid) - unsafe.Sizeof(win.HICON(0))) if !win.Shell_NotifyIcon(win.NIM_ADD, &nid) { return nil, newError("Shell_NotifyIcon") } // We want XP-compatible message behavior. nid.UVersion = win.NOTIFYICON_VERSION if !win.Shell_NotifyIcon(win.NIM_SETVERSION, &nid) { return nil, newError("Shell_NotifyIcon") } // Create and initialize the NotifyIcon already. menu, err := NewMenu() if err != nil { return nil, err } menu.window = form ni := &NotifyIcon{ id: nid.UID, hWnd: fb.hWnd, contextMenu: menu, } menu.getDPI = ni.DPI // Set our *NotifyIcon as user data for the message window. win.SetWindowLongPtr(fb.hWnd, win.GWLP_USERDATA, uintptr(unsafe.Pointer(ni))) notifyIcons[ni] = true return ni, nil } func (ni *NotifyIcon) DPI() int { fakeWb := WindowBase{hWnd: win.FindWindow(syscall.StringToUTF16Ptr("Shell_TrayWnd"), syscall.StringToUTF16Ptr(""))} return fakeWb.DPI() } func (ni *NotifyIcon) readdToTaskbar() error { nid := win.NOTIFYICONDATA{ HWnd: ni.hWnd, UFlags: win.NIF_MESSAGE | win.NIF_STATE, DwState: win.NIS_HIDDEN, DwStateMask: win.NIS_HIDDEN, UCallbackMessage: notifyIconMessageId, } nid.CbSize = uint32(unsafe.Sizeof(nid) - unsafe.Sizeof(win.HICON(0))) if !win.Shell_NotifyIcon(win.NIM_ADD, &nid) { return newError("Shell_NotifyIcon") } // We want XP-compatible message behavior. nid.UVersion = win.NOTIFYICON_VERSION if !win.Shell_NotifyIcon(win.NIM_SETVERSION, &nid) { return newError("Shell_NotifyIcon") } icon := ni.icon ni.icon = nil err := ni.SetIcon(icon) if err != nil { return err } visible := ni.visible ni.visible = false err = ni.SetVisible(visible) if err != nil { return err } tooltip := ni.toolTip ni.toolTip = "" err = ni.SetToolTip(tooltip) if err != nil { return err } return nil } func (ni *NotifyIcon) applyDPI() { dpi := ni.DPI() if dpi == ni.lastDPI { return } ni.lastDPI = dpi for _, action := range ni.contextMenu.actions.actions { if action.image != nil { ni.contextMenu.onActionChanged(action) } } icon := ni.icon ni.icon = nil if icon != nil { ni.SetIcon(icon) } } func (ni *NotifyIcon) notifyIconData() *win.NOTIFYICONDATA { nid := &win.NOTIFYICONDATA{ UID: ni.id, HWnd: ni.hWnd, } nid.CbSize = uint32(unsafe.Sizeof(*nid) - unsafe.Sizeof(win.HICON(0))) return nid } // Dispose releases the operating system resources associated with the // NotifyIcon. // // The associated Icon is not disposed of. func (ni *NotifyIcon) Dispose() error { if ni.hWnd == 0 { return nil } delete(notifyIcons, ni) nid := ni.notifyIconData() if !win.Shell_NotifyIcon(win.NIM_DELETE, nid) { return newError("Shell_NotifyIcon") } ni.hWnd = 0 return nil } func (ni *NotifyIcon) showMessage(title, info string, iconType uint32, icon Image) error { nid := ni.notifyIconData() nid.UFlags = win.NIF_INFO nid.DwInfoFlags = iconType var oldIcon Image if iconType == win.NIIF_USER && icon != nil { oldIcon = ni.icon if err := ni.setNIDIcon(nid, icon); err != nil { return err } nid.UFlags |= win.NIF_ICON } if title16, err := syscall.UTF16FromString(title); err == nil { copy(nid.SzInfoTitle[:], title16) } if info16, err := syscall.UTF16FromString(info); err == nil { copy(nid.SzInfo[:], info16) } if !win.Shell_NotifyIcon(win.NIM_MODIFY, nid) { return newError("Shell_NotifyIcon") } if oldIcon != nil { ni.icon = nil ni.SetIcon(oldIcon) } return nil } // ShowMessage displays a neutral message balloon above the NotifyIcon. // // The NotifyIcon must be visible before calling this method. func (ni *NotifyIcon) ShowMessage(title, info string) error { return ni.showMessage(title, info, win.NIIF_NONE, nil) } // ShowInfo displays an info message balloon above the NotifyIcon. // // The NotifyIcon must be visible before calling this method. func (ni *NotifyIcon) ShowInfo(title, info string) error { return ni.showMessage(title, info, win.NIIF_INFO, nil) } // ShowWarning displays a warning message balloon above the NotifyIcon. // // The NotifyIcon must be visible before calling this method. func (ni *NotifyIcon) ShowWarning(title, info string) error { return ni.showMessage(title, info, win.NIIF_WARNING, nil) } // ShowError displays an error message balloon above the NotifyIcon. // // The NotifyIcon must be visible before calling this method. func (ni *NotifyIcon) ShowError(title, info string) error { return ni.showMessage(title, info, win.NIIF_ERROR, nil) } // ShowCustom displays a custom icon message balloon above the NotifyIcon. // If icon is nil, the main notification icon is used instead of a custom one. // // The NotifyIcon must be visible before calling this method. func (ni *NotifyIcon) ShowCustom(title, info string, icon Image) error { return ni.showMessage(title, info, win.NIIF_USER, icon) } // ContextMenu returns the context menu of the NotifyIcon. func (ni *NotifyIcon) ContextMenu() *Menu { return ni.contextMenu } // Icon returns the Icon of the NotifyIcon. func (ni *NotifyIcon) Icon() Image { return ni.icon } // SetIcon sets the Icon of the NotifyIcon. func (ni *NotifyIcon) SetIcon(icon Image) error { if icon == ni.icon { return nil } nid := ni.notifyIconData() nid.UFlags = win.NIF_ICON if icon == nil { nid.HIcon = 0 } else { if err := ni.setNIDIcon(nid, icon); err != nil { return err } } if !win.Shell_NotifyIcon(win.NIM_MODIFY, nid) { return newError("Shell_NotifyIcon") } ni.icon = icon return nil } func (ni *NotifyIcon) setNIDIcon(nid *win.NOTIFYICONDATA, icon Image) error { dpi := ni.DPI() ic, err := iconCache.Icon(icon, dpi) if err != nil { return err } nid.HIcon = ic.handleForDPI(dpi) return nil } // ToolTip returns the tool tip text of the NotifyIcon. func (ni *NotifyIcon) ToolTip() string { return ni.toolTip } // SetToolTip sets the tool tip text of the NotifyIcon. func (ni *NotifyIcon) SetToolTip(toolTip string) error { if toolTip == ni.toolTip { return nil } nid := ni.notifyIconData() nid.UFlags = win.NIF_TIP copy(nid.SzTip[:], syscall.StringToUTF16(toolTip)) if !win.Shell_NotifyIcon(win.NIM_MODIFY, nid) { return newError("Shell_NotifyIcon") } ni.toolTip = toolTip return nil } // Visible returns if the NotifyIcon is visible. func (ni *NotifyIcon) Visible() bool { return ni.visible } // SetVisible sets if the NotifyIcon is visible. func (ni *NotifyIcon) SetVisible(visible bool) error { if visible == ni.visible { return nil } nid := ni.notifyIconData() nid.UFlags = win.NIF_STATE nid.DwStateMask = win.NIS_HIDDEN if !visible { nid.DwState = win.NIS_HIDDEN } if !win.Shell_NotifyIcon(win.NIM_MODIFY, nid) { return newError("Shell_NotifyIcon") } ni.visible = visible return nil } func (ni *NotifyIcon) publishMouseEvent(publisher *MouseEventPublisher, button MouseButton) { var p win.POINT if !win.GetCursorPos(&p) { lastError("GetCursorPos") } publisher.Publish(int(p.X), int(p.Y), button) } // MouseDown returns the event that is published when a mouse button is pressed // while the cursor is over the NotifyIcon. func (ni *NotifyIcon) MouseDown() *MouseEvent { return ni.mouseDownPublisher.Event() } // MouseDown returns the event that is published when a mouse button is released // while the cursor is over the NotifyIcon. func (ni *NotifyIcon) MouseUp() *MouseEvent { return ni.mouseUpPublisher.Event() } // MessageClicked occurs when the user clicks a message shown with ShowMessage or // one of its iconed variants. func (ni *NotifyIcon) MessageClicked() *Event { return ni.messageClickedPublisher.Event() } ================================================ FILE: numberedit.go ================================================ // Copyright 2011 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 ( "bytes" "fmt" "math" "strconv" "strings" "syscall" "unsafe" "github.com/lxn/win" ) const numberEditWindowClass = `\o/ Walk_NumberEdit_Class \o/` func init() { AppendToWalkInit(func() { MustRegisterWindowClass(numberEditWindowClass) }) } // NumberEdit is a widget that is suited to edit numeric values. type NumberEdit struct { WidgetBase edit *numberLineEdit hWndUpDown win.HWND maxValueChangedPublisher EventPublisher minValueChangedPublisher EventPublisher prefixChangedPublisher EventPublisher suffixChangedPublisher EventPublisher } // NewNumberEdit returns a new NumberEdit widget as child of parent. func NewNumberEdit(parent Container) (*NumberEdit, error) { ne := new(NumberEdit) if err := InitWidget( ne, parent, numberEditWindowClass, win.WS_VISIBLE, win.WS_EX_CONTROLPARENT); err != nil { return nil, err } var succeeded bool defer func() { if !succeeded { ne.Dispose() } }() var err error if ne.edit, err = newNumberLineEdit(ne); err != nil { return nil, err } ne.edit.applyFont(ne.Font()) ne.SetRange(-math.MaxFloat64, math.MaxFloat64) if err = ne.SetValue(0); err != nil { return nil, err } ne.GraphicsEffects().Add(InteractionEffect) ne.GraphicsEffects().Add(FocusEffect) ne.MustRegisterProperty("MaxValue", NewProperty( func() interface{} { return ne.MaxValue() }, func(v interface{}) error { return ne.SetRange(ne.MinValue(), assertFloat64Or(v, 0.0)) }, ne.minValueChangedPublisher.Event())) ne.MustRegisterProperty("MinValue", NewProperty( func() interface{} { return ne.MinValue() }, func(v interface{}) error { return ne.SetRange(assertFloat64Or(v, 0.0), ne.MaxValue()) }, ne.maxValueChangedPublisher.Event())) ne.MustRegisterProperty("Prefix", NewProperty( func() interface{} { return ne.Prefix() }, func(v interface{}) error { return ne.SetPrefix(assertStringOr(v, "")) }, ne.prefixChangedPublisher.Event())) ne.MustRegisterProperty("ReadOnly", NewProperty( func() interface{} { return ne.ReadOnly() }, func(v interface{}) error { return ne.SetReadOnly(v.(bool)) }, ne.edit.readOnlyChangedPublisher.Event())) ne.MustRegisterProperty("Suffix", NewProperty( func() interface{} { return ne.Suffix() }, func(v interface{}) error { return ne.SetSuffix(assertStringOr(v, "")) }, ne.suffixChangedPublisher.Event())) ne.MustRegisterProperty("Value", NewProperty( func() interface{} { return ne.Value() }, func(v interface{}) error { return ne.SetValue(assertFloat64Or(v, 0.0)) }, ne.edit.valueChangedPublisher.Event())) succeeded = true return ne, nil } func (ne *NumberEdit) applyEnabled(enabled bool) { ne.WidgetBase.applyEnabled(enabled) if ne.edit == nil { return } ne.edit.applyEnabled(enabled) } func (ne *NumberEdit) applyFont(font *Font) { ne.WidgetBase.applyFont(font) if ne.edit == nil { return } ne.edit.applyFont(font) } // Decimals returns the number of decimal places in the NumberEdit. func (ne *NumberEdit) Decimals() int { return ne.edit.decimals } // SetDecimals sets the number of decimal places in the NumberEdit. func (ne *NumberEdit) SetDecimals(decimals int) error { if decimals < 0 || decimals > 8 { return newError("decimals must >= 0 && <= 8") } ne.edit.decimals = decimals return ne.SetValue(ne.edit.value) } // Prefix returns the text that appears in the NumberEdit before the number. func (ne *NumberEdit) Prefix() string { return syscall.UTF16ToString(ne.edit.prefix) } // SetPrefix sets the text that appears in the NumberEdit before the number. func (ne *NumberEdit) SetPrefix(prefix string) error { if prefix == ne.Prefix() { return nil } p, err := syscall.UTF16FromString(prefix) if err != nil { return err } old := ne.edit.prefix ne.edit.prefix = p[:len(p)-1] if err := ne.edit.setTextFromValue(ne.edit.value); err != nil { ne.edit.prefix = old return err } ne.prefixChangedPublisher.Publish() return nil } // PrefixChanged returns the event that is published when the prefix changed. func (ne *NumberEdit) PrefixChanged() *Event { return ne.prefixChangedPublisher.Event() } // Suffix returns the text that appears in the NumberEdit after the number. func (ne *NumberEdit) Suffix() string { return syscall.UTF16ToString(ne.edit.suffix) } // SetSuffix sets the text that appears in the NumberEdit after the number. func (ne *NumberEdit) SetSuffix(suffix string) error { if suffix == ne.Suffix() { return nil } s, err := syscall.UTF16FromString(suffix) if err != nil { return err } old := ne.edit.suffix ne.edit.suffix = s[:len(s)-1] if err := ne.edit.setTextFromValue(ne.edit.value); err != nil { ne.edit.suffix = old return err } ne.suffixChangedPublisher.Publish() return nil } // SuffixChanged returns the event that is published when the suffix changed. func (ne *NumberEdit) SuffixChanged() *Event { return ne.suffixChangedPublisher.Event() } // Increment returns the amount by which the NumberEdit increments or decrements // its value, when the user presses the KeyDown or KeyUp keys, or when the mouse // wheel is rotated. func (ne *NumberEdit) Increment() float64 { return ne.edit.increment } // SetIncrement sets the amount by which the NumberEdit increments or decrements // its value, when the user presses the KeyDown or KeyUp keys, or when the mouse // wheel is rotated. func (ne *NumberEdit) SetIncrement(increment float64) error { ne.edit.increment = increment return nil } // MinValue returns the minimum value the NumberEdit will accept. func (ne *NumberEdit) MinValue() float64 { return ne.edit.minValue } // MinValue returns the maximum value the NumberEdit will accept. func (ne *NumberEdit) MaxValue() float64 { return ne.edit.maxValue } // SetRange sets the minimum and maximum values the NumberEdit will accept. // // If the current value is out of this range, it will be adjusted. func (ne *NumberEdit) SetRange(min, max float64) error { if min > max { return newError(fmt.Sprintf("invalid range - min: %f, max: %f", min, max)) } minChanged := min != ne.edit.minValue maxChanged := max != ne.edit.maxValue ne.edit.minValue = min ne.edit.maxValue = max if min != max { if ne.edit.value < min { if err := ne.edit.setValue(min, true); err != nil { return err } } else if ne.edit.value > max { if err := ne.edit.setValue(max, true); err != nil { return err } } } if minChanged { ne.minValueChangedPublisher.Publish() } if maxChanged { ne.maxValueChangedPublisher.Publish() } return nil } // Value returns the value of the NumberEdit. func (ne *NumberEdit) Value() float64 { return ne.edit.value } // SetValue sets the value of the NumberEdit. func (ne *NumberEdit) SetValue(value float64) error { if ne.edit.minValue != ne.edit.maxValue && (value < ne.edit.minValue || value > ne.edit.maxValue) { return newError("value out of range") } return ne.edit.setValue(value, true) } // ValueChanged returns an Event that can be used to track changes to Value. func (ne *NumberEdit) ValueChanged() *Event { return ne.edit.valueChangedPublisher.Event() } // SetFocus sets the keyboard input focus to the NumberEdit. func (ne *NumberEdit) SetFocus() error { if win.SetFocus(ne.edit.hWnd) == 0 { return lastError("SetFocus") } return nil } // TextSelection returns the range of the current text selection of the // NumberEdit. func (ne *NumberEdit) TextSelection() (start, end int) { return ne.edit.TextSelection() } // SetTextSelection sets the range of the current text selection of the // NumberEdit. func (ne *NumberEdit) SetTextSelection(start, end int) { ne.edit.SetTextSelection(start, end) } // ReadOnly returns whether the NumberEdit is in read-only mode. func (ne *NumberEdit) ReadOnly() bool { return ne.edit.ReadOnly() } // SetReadOnly sets whether the NumberEdit is in read-only mode. func (ne *NumberEdit) SetReadOnly(readOnly bool) error { if readOnly != ne.ReadOnly() { ne.invalidateBorderInParent() } return ne.edit.SetReadOnly(readOnly) } // SpinButtonsVisible returns whether the NumberEdit appears with spin buttons. func (ne *NumberEdit) SpinButtonsVisible() bool { return ne.hWndUpDown != 0 } // SetSpinButtonsVisible sets whether the NumberEdit appears with spin buttons. func (ne *NumberEdit) SetSpinButtonsVisible(visible bool) error { if visible == ne.SpinButtonsVisible() { return nil } if visible { ne.hWndUpDown = win.CreateWindowEx( 0, syscall.StringToUTF16Ptr("msctls_updown32"), nil, win.WS_CHILD|win.WS_VISIBLE|win.UDS_ALIGNRIGHT|win.UDS_ARROWKEYS|win.UDS_HOTTRACK, 0, 0, 16, 20, ne.hWnd, 0, 0, nil) if ne.hWndUpDown == 0 { return lastError("CreateWindowEx") } win.SendMessage(ne.hWndUpDown, win.UDM_SETBUDDY, uintptr(ne.edit.hWnd), 0) } else { if !win.DestroyWindow(ne.hWndUpDown) { return lastError("DestroyWindow") } ne.hWndUpDown = 0 } return nil } // Background returns the background Brush of the NumberEdit. // // By default this is nil. func (ne *NumberEdit) Background() Brush { return ne.edit.Background() } // SetBackground sets the background Brush of the NumberEdit. func (ne *NumberEdit) SetBackground(bg Brush) { ne.edit.SetBackground(bg) } // TextColor returns the Color used to draw the text of the NumberEdit. func (ne *NumberEdit) TextColor() Color { return ne.edit.TextColor() } // TextColor sets the Color used to draw the text of the NumberEdit. func (ne *NumberEdit) SetTextColor(c Color) { ne.edit.SetTextColor(c) } func (*NumberEdit) NeedsWmSize() bool { return true } // WndProc is the window procedure of the NumberEdit. // // When implementing your own WndProc to add or modify behavior, call the // WndProc of the embedded NumberEdit for messages you don't handle yourself. func (ne *NumberEdit) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_NOTIFY: switch ((*win.NMHDR)(unsafe.Pointer(lParam))).Code { case win.UDN_DELTAPOS: nmud := (*win.NMUPDOWN)(unsafe.Pointer(lParam)) ne.edit.incrementValue(-float64(nmud.IDelta) * ne.edit.increment) } case win.WM_CTLCOLOREDIT, win.WM_CTLCOLORSTATIC: if hBrush := ne.handleWMCTLCOLOR(wParam, lParam); hBrush != 0 { return hBrush } case win.WM_WINDOWPOSCHANGED: wp := (*win.WINDOWPOS)(unsafe.Pointer(lParam)) if wp.Flags&win.SWP_NOSIZE != 0 { break } if ne.edit == nil { break } cb := ne.ClientBoundsPixels() if err := ne.edit.SetBoundsPixels(cb); err != nil { break } if ne.hWndUpDown != 0 { win.SendMessage(ne.hWndUpDown, win.UDM_SETBUDDY, uintptr(ne.edit.hWnd), 0) } } return ne.WidgetBase.WndProc(hwnd, msg, wParam, lParam) } func (ne *NumberEdit) CreateLayoutItem(ctx *LayoutContext) LayoutItem { return &numberEditLayoutItem{ idealSize: ne.dialogBaseUnitsToPixels(Size{50, 12}), minSize: ne.dialogBaseUnitsToPixels(Size{20, 12}), } } type numberEditLayoutItem struct { LayoutItemBase idealSize Size // in native pixels minSize Size // in native pixels } func (*numberEditLayoutItem) LayoutFlags() LayoutFlags { return ShrinkableHorz | GrowableHorz } func (li *numberEditLayoutItem) IdealSize() Size { return li.idealSize } func (li *numberEditLayoutItem) MinSize() Size { return li.minSize } type numberLineEdit struct { *LineEdit buf *bytes.Buffer prefix []uint16 suffix []uint16 value float64 minValue float64 maxValue float64 increment float64 decimals int valueChangedPublisher EventPublisher inEditMode bool } func newNumberLineEdit(parent Widget) (*numberLineEdit, error) { nle := &numberLineEdit{ buf: new(bytes.Buffer), increment: 1, } var err error if nle.LineEdit, err = newLineEdit(parent); err != nil { return nil, err } succeeded := false defer func() { if !succeeded { nle.Dispose() } }() if err := nle.LineEdit.setAndClearStyleBits(win.ES_RIGHT, win.ES_LEFT|win.ES_CENTER); err != nil { return nil, err } if err := InitWrapperWindow(nle); err != nil { return nil, err } succeeded = true return nle, nil } func (nle *numberLineEdit) TextColor() Color { return nle.LineEdit.TextColor() } func (nle *numberLineEdit) SetTextColor(c Color) { nle.LineEdit.SetTextColor(c) } func (nle *numberLineEdit) setValue(value float64, setText bool) error { if setText { if err := nle.setTextFromValue(value); err != nil { return err } } if value == nle.value { return nil } nle.value = value nle.valueChangedPublisher.Publish() return nil } func (nle *numberLineEdit) setTextFromValue(value float64) error { nle.buf.Reset() nle.buf.WriteString(syscall.UTF16ToString(nle.prefix)) if nle.decimals > 0 { nle.buf.WriteString(FormatFloatGrouped(value, nle.decimals)) } else { nle.buf.WriteString(FormatFloat(value, nle.decimals)) } nle.buf.WriteString(syscall.UTF16ToString(nle.suffix)) return nle.SetText(nle.buf.String()) } func (nle *numberLineEdit) endEdit() error { if err := nle.setTextFromValue(nle.value); err != nil { return err } nle.inEditMode = false return nil } func (nle *numberLineEdit) processChar(text []uint16, start, end int, key Key, char uint16) { hadSelection := start != end if !nle.inEditMode { var groupSepsBeforeStart int if nle.decimals > 0 { groupSepsBeforeStart = uint16CountUint16(text[:start], groupSepUint16) } if hadSelection { text = append(text[:start], text[end:]...) } if nle.decimals > 0 { text = uint16RemoveUint16(text, groupSepUint16) start -= groupSepsBeforeStart } nle.inEditMode = true } else { if hadSelection { text = append(text[:start], text[end:]...) } } end = start switch key { case KeyBack: if !hadSelection && start > 0 { start -= 1 text = append(text[:start], text[start+1:]...) } case KeyDelete: if !hadSelection && start < len(text) { text = append(text[:start], text[start+1:]...) } default: t := make([]uint16, len(text[:start]), len(text)+1) copy(t, text[:start]) t = append(t, char) text = append(t, text[start:]...) start += 1 } nle.buf.Reset() str := syscall.UTF16ToString(text) nle.buf.WriteString(syscall.UTF16ToString(nle.prefix)) nle.buf.WriteString(str) nle.buf.WriteString(syscall.UTF16ToString(nle.suffix)) nle.SetText(nle.buf.String()) start += len(nle.prefix) nle.SetTextSelection(start, start) nle.tryUpdateValue(false) } func (nle *numberLineEdit) tryUpdateValue(setText bool) bool { t := nle.textUTF16() t = t[len(nle.prefix) : len(t)-len(nle.suffix)] text := strings.Replace(syscall.UTF16ToString(t), decimalSepS, ".", 1) switch text { case "", ".": text = "0" } if value, err := strconv.ParseFloat(text, 64); err == nil { if nle.minValue == nle.maxValue || value >= nle.minValue && value <= nle.maxValue { return nle.setValue(value, setText) == nil } } return false } func (nle *numberLineEdit) selectNumber() { nle.SetTextSelection(len(nle.prefix), len(nle.textUTF16())-len(nle.suffix)) } func (nle *numberLineEdit) textUTF16() []uint16 { textLength := nle.SendMessage(win.WM_GETTEXTLENGTH, 0, 0) buf := make([]uint16, textLength+1) nle.SendMessage(win.WM_GETTEXT, uintptr(textLength+1), uintptr(unsafe.Pointer(&buf[0]))) return buf[:len(buf)-1] } func (nle *numberLineEdit) incrementValue(delta float64) { value := nle.value + delta if nle.minValue != nle.maxValue { if value < nle.minValue { value = nle.minValue } else if value > nle.maxValue { value = nle.maxValue } } nle.setValue(value, true) nle.selectNumber() } func (nle *numberLineEdit) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_CHAR: if nle.ReadOnly() { break } if AltDown() { return 0 } if ControlDown() { if wParam == 1 { // Ctrl+A return 0 } break } char := uint16(wParam) text := nle.textUTF16() text = text[len(nle.prefix) : len(text)-len(nle.suffix)] start, end := nle.TextSelection() start -= len(nle.prefix) end -= len(nle.prefix) if Key(wParam) == KeyBack { nle.processChar(text, start, end, KeyBack, 0) return 0 } switch char { case uint16('0'), uint16('1'), uint16('2'), uint16('3'), uint16('4'), uint16('5'), uint16('6'), uint16('7'), uint16('8'), uint16('9'): if start == end && nle.decimals > 0 { if i := uint16IndexUint16(text, decimalSepUint16); i > -1 && i < len(text)-nle.decimals && start > i { return 0 } } nle.processChar(text, start, end, 0, char) return 0 case uint16('-'): if nle.minValue != nle.maxValue && nle.minValue >= 0 { return 0 } if start > 0 || uint16ContainsUint16(text, uint16('-')) && end == 0 { return 0 } nle.processChar(text, start, end, 0, char) return 0 case decimalSepUint16: if nle.decimals == 0 { return 0 } if start == 0 && end == 0 && len(text) > 0 && text[0] == '-' { return 0 } if end < len(text)-nle.decimals { return 0 } if i := uint16IndexUint16(text, decimalSepUint16); i > -1 && i <= start || i > end { return 0 } nle.processChar(text, start, end, 0, char) return 0 default: return 0 } case win.WM_KEYDOWN: switch Key(wParam) { case KeyA: if ControlDown() { nle.selectNumber() return 0 } case KeyDelete: if nle.ReadOnly() { break } text := nle.textUTF16() text = text[len(nle.prefix) : len(text)-len(nle.suffix)] start, end := nle.TextSelection() start -= len(nle.prefix) end -= len(nle.prefix) nle.processChar(text, start, end, KeyDelete, 0) return 0 case KeyDown: if nle.ReadOnly() || nle.increment <= 0 { return 0 } nle.incrementValue(-nle.increment) return 0 case KeyEnd: start, end := nle.TextSelection() end = len(nle.textUTF16()) - len(nle.suffix) if !ShiftDown() { start = end } nle.SetTextSelection(start, end) return 0 case KeyHome: start, end := nle.TextSelection() start = len(nle.prefix) if !ShiftDown() { end = start } nle.SetTextSelection(start, end) return 0 case KeyLeft: var pos win.POINT win.GetCaretPos(&pos) lParam := uintptr(win.MAKELONG(uint16(pos.X), uint16(pos.Y))) i := int(win.LOWORD(uint32(nle.SendMessage(win.EM_CHARFROMPOS, 0, lParam)))) if min := len(nle.prefix); i <= min { if !ShiftDown() { nle.SetTextSelection(min, min) } return 0 } case KeyReturn: if nle.ReadOnly() { break } if nle.inEditMode { nle.endEdit() nle.selectNumber() return 0 } case KeyRight: var pos win.POINT win.GetCaretPos(&pos) lParam := uintptr(win.MAKELONG(uint16(pos.X), uint16(pos.Y))) i := int(win.LOWORD(uint32(nle.SendMessage(win.EM_CHARFROMPOS, 0, lParam)))) if max := len(nle.textUTF16()) - len(nle.suffix); i >= max { if !ShiftDown() { nle.SetTextSelection(max, max) } return 0 } case KeyUp: if nle.ReadOnly() || nle.increment <= 0 { return 0 } nle.incrementValue(nle.increment) return 0 } case win.WM_GETDLGCODE: if !nle.inEditMode { if form := ancestor(nle); form != nil { if dlg, ok := form.(dialogish); ok { if dlg.DefaultButton() != nil { // If the NumberEdit 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_KILLFOCUS: nle.onFocusChanged() nle.endEdit() case win.WM_LBUTTONDOWN: i := int(win.LOWORD(uint32(nle.SendMessage(win.EM_CHARFROMPOS, 0, lParam)))) if min := len(nle.prefix); i < min { nle.SetFocus() nle.SetTextSelection(min, min) return 0 } if max := len(nle.textUTF16()) - len(nle.suffix); i > max { nle.SetFocus() nle.SetTextSelection(max, max) return 0 } case win.WM_LBUTTONDBLCLK: nle.selectNumber() return 0 case win.WM_MOUSEMOVE: i := int(win.LOWORD(uint32(nle.SendMessage(win.EM_CHARFROMPOS, 0, lParam)))) if min := len(nle.prefix); i < min { return 0 } if max := len(nle.textUTF16()) - len(nle.suffix); i > max { return 0 } case win.WM_MOUSEWHEEL: if nle.ReadOnly() || nle.increment <= 0 { break } delta := float64(int16(win.HIWORD(uint32(wParam)))) nle.incrementValue(delta / 120 * nle.increment) return 0 case win.WM_PASTE: if nle.ReadOnly() { break } ret := nle.LineEdit.WndProc(hwnd, msg, wParam, lParam) if !nle.tryUpdateValue(true) { nle.setTextFromValue(nle.value) } nle.selectNumber() return ret case win.WM_SETFOCUS: nle.onFocusChanged() nle.selectNumber() case win.EM_SETSEL: start := int(wParam) end := int(lParam) adjusted := false if min := len(nle.prefix); start < min { start = min adjusted = true } if max := len(nle.textUTF16()) - len(nle.suffix); end < 0 || end > max { end = max adjusted = true } if adjusted { nle.SetTextSelection(start, end) return 0 } } return nle.LineEdit.WndProc(hwnd, msg, wParam, lParam) } func (nle *numberLineEdit) onFocusChanged() { if ne := windowFromHandle(win.GetParent(nle.hWnd)); ne != nil { if wnd := windowFromHandle(win.GetParent(ne.Handle())); wnd != nil { if _, ok := wnd.(Container); ok { ne.(Widget).AsWidgetBase().invalidateBorderInParent() } } } } func (ne *NumberEdit) SetToolTipText(s string) error { return ne.edit.SetToolTipText(s) } ================================================ FILE: numberlabel.go ================================================ // Copyright 2018 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 ( "strings" ) type NumberLabel struct { static decimals int decimalsChangedPublisher EventPublisher suffix string suffixChangedPublisher EventPublisher value float64 valueChangedPublisher EventPublisher } func NewNumberLabel(parent Container) (*NumberLabel, error) { nl := new(NumberLabel) if err := nl.init(nl, parent, 0); err != nil { return nil, err } nl.SetTextAlignment(AlignFar) if _, err := nl.updateText(); err != nil { return nil, err } nl.MustRegisterProperty("Decimals", NewProperty( func() interface{} { return nl.Decimals() }, func(v interface{}) error { return nl.SetDecimals(assertIntOr(v, 0)) }, nl.decimalsChangedPublisher.Event())) nl.MustRegisterProperty("Suffix", NewProperty( func() interface{} { return nl.Suffix() }, func(v interface{}) error { return nl.SetSuffix(assertStringOr(v, "")) }, nl.suffixChangedPublisher.Event())) nl.MustRegisterProperty("Value", NewProperty( func() interface{} { return nl.Value() }, func(v interface{}) error { return nl.SetValue(assertFloat64Or(v, 0.0)) }, nl.valueChangedPublisher.Event())) return nl, nil } func (nl *NumberLabel) asStatic() *static { return &nl.static } func (nl *NumberLabel) TextAlignment() Alignment1D { return nl.textAlignment1D() } func (nl *NumberLabel) SetTextAlignment(alignment Alignment1D) error { if alignment == AlignDefault { alignment = AlignFar } return nl.setTextAlignment1D(alignment) } func (nl *NumberLabel) Decimals() int { return nl.decimals } func (nl *NumberLabel) SetDecimals(decimals int) error { if decimals == nl.decimals { return nil } old := nl.decimals nl.decimals = decimals if _, err := nl.updateText(); err != nil { nl.decimals = old return err } nl.decimalsChangedPublisher.Publish() return nil } func (nl *NumberLabel) Suffix() string { return nl.suffix } func (nl *NumberLabel) SetSuffix(suffix string) error { if suffix == nl.suffix { return nil } old := nl.suffix nl.suffix = suffix if _, err := nl.updateText(); err != nil { nl.suffix = old return err } nl.suffixChangedPublisher.Publish() return nil } func (nl *NumberLabel) Value() float64 { return nl.value } func (nl *NumberLabel) SetValue(value float64) error { if value == nl.value { return nil } old := nl.value nl.value = value if _, err := nl.updateText(); err != nil { nl.value = old return err } nl.valueChangedPublisher.Publish() return nil } func (nl *NumberLabel) updateText() (changed bool, err error) { var sb strings.Builder sb.WriteString(FormatFloatGrouped(nl.value, nl.decimals)) if nl.suffix != "" { sb.WriteString(nl.suffix) } return nl.setText(sb.String()) } ================================================ FILE: path.go ================================================ // 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" "github.com/lxn/win" ) func knownFolderPath(id win.CSIDL) (string, error) { var buf [win.MAX_PATH]uint16 if !win.SHGetSpecialFolderPath(0, &buf[0], id, false) { return "", newError("SHGetSpecialFolderPath failed") } return syscall.UTF16ToString(buf[0:]), nil } func AppDataPath() (string, error) { return knownFolderPath(win.CSIDL_APPDATA) } func CommonAppDataPath() (string, error) { return knownFolderPath(win.CSIDL_COMMON_APPDATA) } func LocalAppDataPath() (string, error) { return knownFolderPath(win.CSIDL_LOCAL_APPDATA) } func PersonalPath() (string, error) { return knownFolderPath(win.CSIDL_PERSONAL) } func SystemPath() (string, error) { return knownFolderPath(win.CSIDL_SYSTEM) } func DriveNames() ([]string, error) { bufLen := win.GetLogicalDriveStrings(0, nil) if bufLen == 0 { return nil, lastError("GetLogicalDriveStrings") } buf := make([]uint16, bufLen+1) bufLen = win.GetLogicalDriveStrings(bufLen+1, &buf[0]) if bufLen == 0 { return nil, lastError("GetLogicalDriveStrings") } var names []string for i := 0; i < len(buf)-2; { name := syscall.UTF16ToString(buf[i:]) names = append(names, name) i += len(name) + 1 } return names, nil } ================================================ FILE: pen.go ================================================ // 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 ( "github.com/lxn/win" ) type PenStyle int // Pen styles const ( PenSolid PenStyle = win.PS_SOLID PenDash PenStyle = win.PS_DASH PenDot PenStyle = win.PS_DOT PenDashDot PenStyle = win.PS_DASHDOT PenDashDotDot PenStyle = win.PS_DASHDOTDOT PenNull PenStyle = win.PS_NULL PenInsideFrame PenStyle = win.PS_INSIDEFRAME PenUserStyle PenStyle = win.PS_USERSTYLE PenAlternate PenStyle = win.PS_ALTERNATE ) // Pen cap styles (geometric pens only) const ( PenCapRound PenStyle = win.PS_ENDCAP_ROUND PenCapSquare PenStyle = win.PS_ENDCAP_SQUARE PenCapFlat PenStyle = win.PS_ENDCAP_FLAT ) // Pen join styles (geometric pens only) const ( PenJoinBevel PenStyle = win.PS_JOIN_BEVEL PenJoinMiter PenStyle = win.PS_JOIN_MITER PenJoinRound PenStyle = win.PS_JOIN_ROUND ) type Pen interface { handleForDPI(dpi int) win.HPEN Dispose() Style() PenStyle // Width returns pen width in 1/96" units. Width() int } type nullPen struct { hPen win.HPEN } func newNullPen() *nullPen { lb := &win.LOGBRUSH{LbStyle: win.BS_NULL} hPen := win.ExtCreatePen(win.PS_COSMETIC|win.PS_NULL, 1, lb, 0, nil) if hPen == 0 { panic("failed to create null brush") } return &nullPen{hPen: hPen} } func (p *nullPen) Dispose() { if p.hPen != 0 { win.DeleteObject(win.HGDIOBJ(p.hPen)) p.hPen = 0 } } func (p *nullPen) handleForDPI(dpi int) win.HPEN { return p.hPen } func (p *nullPen) Style() PenStyle { return PenNull } func (p *nullPen) Width() int { return 0 } var nullPenSingleton Pen func init() { AppendToWalkInit(func() { nullPenSingleton = newNullPen() }) } func NullPen() Pen { return nullPenSingleton } type CosmeticPen struct { hPen win.HPEN style PenStyle color Color } func NewCosmeticPen(style PenStyle, color Color) (*CosmeticPen, error) { lb := &win.LOGBRUSH{LbStyle: win.BS_SOLID, LbColor: win.COLORREF(color)} style |= win.PS_COSMETIC hPen := win.ExtCreatePen(uint32(style), 1, lb, 0, nil) if hPen == 0 { return nil, newError("ExtCreatePen failed") } return &CosmeticPen{hPen: hPen, style: style, color: color}, nil } func (p *CosmeticPen) Dispose() { if p.hPen != 0 { win.DeleteObject(win.HGDIOBJ(p.hPen)) p.hPen = 0 } } func (p *CosmeticPen) handleForDPI(dpi int) win.HPEN { return p.hPen } func (p *CosmeticPen) Style() PenStyle { return p.style } func (p *CosmeticPen) Color() Color { return p.color } func (p *CosmeticPen) Width() int { return 1 } type GeometricPen struct { dpi2hPen map[int]win.HPEN style PenStyle brush Brush width96dpi int } // NewGeometricPen prepares new geometric pen. width parameter is specified in 1/96" units. func NewGeometricPen(style PenStyle, width int, brush Brush) (*GeometricPen, error) { if brush == nil { return nil, newError("brush cannot be nil") } style |= win.PS_GEOMETRIC return &GeometricPen{ style: style, width96dpi: width, brush: brush, }, nil } func (p *GeometricPen) Dispose() { if len(p.dpi2hPen) == 0 { return } for dpi, hPen := range p.dpi2hPen { win.DeleteObject(win.HGDIOBJ(hPen)) delete(p.dpi2hPen, dpi) } } func (p *GeometricPen) handleForDPI(dpi int) win.HPEN { hPen, _ := p.handleForDPIWithError(dpi) return hPen } func (p *GeometricPen) handleForDPIWithError(dpi int) (win.HPEN, error) { if p.dpi2hPen == nil { p.dpi2hPen = make(map[int]win.HPEN) } else if handle, ok := p.dpi2hPen[dpi]; ok { return handle, nil } hPen := win.ExtCreatePen( uint32(p.style), uint32(IntFrom96DPI(p.width96dpi, dpi)), p.brush.logbrush(), 0, nil) if hPen == 0 { return 0, newError("ExtCreatePen failed") } p.dpi2hPen[dpi] = hPen return hPen, nil } func (p *GeometricPen) Style() PenStyle { return p.style } // Width returns pen width in 1/96" units. func (p *GeometricPen) Width() int { return p.width96dpi } func (p *GeometricPen) Brush() Brush { return p.brush } ================================================ FILE: point.go ================================================ // 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 "github.com/lxn/win" // Point defines 2D coordinate in 1/96" units ot native pixels. type Point struct { X, Y int } func (p Point) toPOINT() win.POINT { return win.POINT{ X: int32(p.X), Y: int32(p.Y), } } func pointPixelsFromPOINT(p win.POINT) Point { return Point{ X: int(p.X), Y: int(p.Y), } } ================================================ FILE: progressbar.go ================================================ // 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 ( "github.com/lxn/win" ) type ProgressBar struct { WidgetBase } func NewProgressBar(parent Container) (*ProgressBar, error) { pb := new(ProgressBar) if err := InitWidget( pb, parent, "msctls_progress32", win.WS_VISIBLE, 0); err != nil { return nil, err } return pb, nil } func (pb *ProgressBar) MinValue() int { return int(pb.SendMessage(win.PBM_GETRANGE, 1, 0)) } func (pb *ProgressBar) MaxValue() int { return int(pb.SendMessage(win.PBM_GETRANGE, 0, 0)) } func (pb *ProgressBar) SetRange(min, max int) { pb.SendMessage(win.PBM_SETRANGE32, uintptr(min), uintptr(max)) } func (pb *ProgressBar) Value() int { return int(pb.SendMessage(win.PBM_GETPOS, 0, 0)) } func (pb *ProgressBar) SetValue(value int) { pb.SendMessage(win.PBM_SETPOS, uintptr(value), 0) } func (pb *ProgressBar) MarqueeMode() bool { return pb.hasStyleBits(win.PBS_MARQUEE) } func (pb *ProgressBar) SetMarqueeMode(marqueeMode bool) error { if err := pb.ensureStyleBits(win.PBS_MARQUEE, marqueeMode); err != nil { return err } pb.SendMessage(win.PBM_SETMARQUEE, uintptr(win.BoolToBOOL(marqueeMode)), 0) return nil } func (pb *ProgressBar) CreateLayoutItem(ctx *LayoutContext) LayoutItem { return &progressBarLayoutItem{ idealSize: pb.dialogBaseUnitsToPixels(Size{50, 14}), minSize: pb.dialogBaseUnitsToPixels(Size{10, 14}), } } type progressBarLayoutItem struct { LayoutItemBase idealSize Size // in native pixels minSize Size // in native pixels } func (*progressBarLayoutItem) LayoutFlags() LayoutFlags { return ShrinkableHorz | GrowableHorz | GreedyHorz } func (li *progressBarLayoutItem) IdealSize() Size { return li.idealSize } func (li *progressBarLayoutItem) MinSize() Size { return li.minSize } ================================================ FILE: progressindicator.go ================================================ // Copyright 2012 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 ( "unsafe" ) import ( "github.com/lxn/win" "syscall" ) type ProgressIndicator struct { hwnd win.HWND taskbarList3 *win.ITaskbarList3 completed uint32 total uint32 state PIState overlayIcon *Icon overlayIconDescription string } type PIState int const ( PINoProgress PIState = win.TBPF_NOPROGRESS PIIndeterminate PIState = win.TBPF_INDETERMINATE PINormal PIState = win.TBPF_NORMAL PIError PIState = win.TBPF_ERROR PIPaused PIState = win.TBPF_PAUSED ) //newTaskbarList3 precondition: Windows version is at least 6.1 (yes, Win 7 is version 6.1). func newTaskbarList3(hwnd win.HWND) (*ProgressIndicator, error) { var classFactoryPtr unsafe.Pointer if hr := win.CoGetClassObject(&win.CLSID_TaskbarList, win.CLSCTX_ALL, nil, &win.IID_IClassFactory, &classFactoryPtr); win.FAILED(hr) { return nil, errorFromHRESULT("CoGetClassObject", hr) } var taskbarList3ObjectPtr unsafe.Pointer classFactory := (*win.IClassFactory)(classFactoryPtr) defer classFactory.Release() if hr := classFactory.CreateInstance(nil, &win.IID_ITaskbarList3, &taskbarList3ObjectPtr); win.FAILED(hr) { return nil, errorFromHRESULT("IClassFactory.CreateInstance", hr) } return &ProgressIndicator{taskbarList3: (*win.ITaskbarList3)(taskbarList3ObjectPtr), hwnd: hwnd}, nil } func (pi *ProgressIndicator) SetState(state PIState) error { if hr := pi.taskbarList3.SetProgressState(pi.hwnd, (int)(state)); win.FAILED(hr) { return errorFromHRESULT("ITaskbarList3.setprogressState", hr) } pi.state = state return nil } func (pi *ProgressIndicator) State() PIState { return pi.state } func (pi *ProgressIndicator) SetTotal(total uint32) { pi.total = total } func (pi *ProgressIndicator) Total() uint32 { return pi.total } func (pi *ProgressIndicator) SetCompleted(completed uint32) error { if hr := pi.taskbarList3.SetProgressValue(pi.hwnd, completed, pi.total); win.FAILED(hr) { return errorFromHRESULT("ITaskbarList3.SetProgressValue", hr) } pi.completed = completed return nil } func (pi *ProgressIndicator) Completed() uint32 { return pi.completed } func (pi *ProgressIndicator) SetOverlayIcon(icon *Icon, description string) error { handle := win.HICON(0) if icon != nil { handle = icon.handleForDPI(int(win.GetDpiForWindow(pi.hwnd))) } description16, err := syscall.UTF16PtrFromString(description) if err != nil { description16 = &[]uint16{0}[0] } if hr := pi.taskbarList3.SetOverlayIcon(pi.hwnd, handle, description16); win.FAILED(hr) { return errorFromHRESULT("ITaskbarList3.SetOverlayIcon", hr) } pi.overlayIcon = icon pi.overlayIconDescription = description return nil } ================================================ FILE: property.go ================================================ // Copyright 2012 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 ( "errors" "fmt" ) var ( ErrPropertyReadOnly = errors.New("read-only property") ErrPropertyNotValidatable = errors.New("property not validatable") ) type Property interface { Expression ReadOnly() bool Get() interface{} Set(value interface{}) error Source() interface{} SetSource(source interface{}) error Validatable() bool Validator() Validator SetValidator(validator Validator) error } type property struct { get func() interface{} set func(v interface{}) error changed *Event source interface{} sourceChangedHandle int validator Validator } func NewProperty(get func() interface{}, set func(v interface{}) error, changed *Event) Property { return &property{get: get, set: set, changed: changed} } func (p *property) ReadOnly() bool { return p.set == nil } func (p *property) Value() interface{} { return p.get() } func (p *property) Get() interface{} { return p.get() } func (p *property) Set(value interface{}) error { if p.ReadOnly() { return ErrPropertyReadOnly } if oldValue := p.get(); value == oldValue { return nil } return p.set(value) } func (p *property) Changed() *Event { return p.changed } func (p *property) Source() interface{} { return p.source } func (p *property) SetSource(source interface{}) error { if p.ReadOnly() { return ErrPropertyReadOnly } if source != nil { switch source := source.(type) { case string: // nop case Property: if err := checkPropertySource(p, source); err != nil { return err } if source != nil { p.Set(source.Get()) p.sourceChangedHandle = source.Changed().Attach(func() { p.Set(source.Get()) }) } case Expression: p.Set(source.Value()) p.sourceChangedHandle = source.Changed().Attach(func() { p.Set(source.Value()) }) default: return newError("invalid source type") } } if oldProp, ok := p.source.(Property); ok { oldProp.Changed().Detach(p.sourceChangedHandle) } p.source = source return nil } func (p *property) Validatable() bool { return true } func (p *property) Validator() Validator { return p.validator } func (p *property) SetValidator(validator Validator) error { if p.ReadOnly() { return ErrPropertyReadOnly } p.validator = validator return nil } type readOnlyProperty struct { get func() interface{} changed *Event } func NewReadOnlyProperty(get func() interface{}, changed *Event) Property { return &readOnlyProperty{get: get, changed: changed} } func (*readOnlyProperty) ReadOnly() bool { return true } func (rop *readOnlyProperty) Value() interface{} { return rop.get() } func (rop *readOnlyProperty) Get() interface{} { return rop.get() } func (*readOnlyProperty) Set(value interface{}) error { return ErrPropertyReadOnly } func (rop *readOnlyProperty) Changed() *Event { return rop.changed } func (*readOnlyProperty) Source() interface{} { return nil } func (*readOnlyProperty) SetSource(source interface{}) error { return ErrPropertyReadOnly } func (*readOnlyProperty) Validatable() bool { return false } func (*readOnlyProperty) Validator() Validator { return nil } func (*readOnlyProperty) SetValidator(validator Validator) error { return ErrPropertyReadOnly } type boolProperty struct { get func() bool set func(v bool) error changed *Event source interface{} sourceChangedHandle int } func NewBoolProperty(get func() bool, set func(b bool) error, changed *Event) Property { return &boolProperty{get: get, set: set, changed: changed} } func (bp *boolProperty) ReadOnly() bool { return bp.set == nil } func (bp *boolProperty) Value() interface{} { return bp.get() } func (bp *boolProperty) Get() interface{} { return bp.get() } func (bp *boolProperty) Set(value interface{}) error { if bp.ReadOnly() { return ErrPropertyReadOnly } /* FIXME: Visible property doesn't like this. if oldValue := bp.get(); value == oldValue { return nil }*/ return bp.set(value.(bool)) } func (bp *boolProperty) Changed() *Event { return bp.changed } func (bp *boolProperty) Source() interface{} { return bp.source } func (bp *boolProperty) SetSource(source interface{}) error { if bp.ReadOnly() { return ErrPropertyReadOnly } if source != nil { switch source := source.(type) { case string: // nop case Condition: if err := checkPropertySource(bp, source); err != nil { return err } if err := bp.Set(source.Satisfied()); err != nil { return err } bp.sourceChangedHandle = source.Changed().Attach(func() { bp.Set(source.Satisfied()) }) case Expression: if err := checkPropertySource(bp, source); err != nil { return err } if satisfied, ok := source.Value().(bool); ok { if err := bp.Set(satisfied); err != nil { return err } } bp.sourceChangedHandle = source.Changed().Attach(func() { if satisfied, ok := source.Value().(bool); ok { bp.Set(satisfied) } }) default: return newError(fmt.Sprintf(`invalid source: "%s" of type %T`, source, source)) } } if oldCond, ok := bp.source.(Condition); ok { oldCond.Changed().Detach(bp.sourceChangedHandle) } bp.source = source return nil } func (bp *boolProperty) Validatable() bool { return false } func (*boolProperty) Validator() Validator { return nil } func (*boolProperty) SetValidator(validator Validator) error { return ErrPropertyNotValidatable } func (bp *boolProperty) Satisfied() bool { return bp.get() } type readOnlyBoolProperty struct { get func() bool changed *Event } func NewReadOnlyBoolProperty(get func() bool, changed *Event) Property { return &readOnlyBoolProperty{get: get, changed: changed} } func (*readOnlyBoolProperty) ReadOnly() bool { return true } func (robp *readOnlyBoolProperty) Value() interface{} { return robp.get() } func (robp *readOnlyBoolProperty) Get() interface{} { return robp.get() } func (*readOnlyBoolProperty) Set(value interface{}) error { return ErrPropertyReadOnly } func (robp *readOnlyBoolProperty) Changed() *Event { return robp.changed } func (*readOnlyBoolProperty) Source() interface{} { return nil } func (*readOnlyBoolProperty) SetSource(source interface{}) error { return ErrPropertyReadOnly } func (*readOnlyBoolProperty) Validatable() bool { return false } func (*readOnlyBoolProperty) Validator() Validator { return nil } func (*readOnlyBoolProperty) SetValidator(validator Validator) error { return ErrPropertyNotValidatable } func (robp *readOnlyBoolProperty) Satisfied() bool { return robp.get() } func checkPropertySource(prop Property, source interface{}) error { switch source := source.(type) { case Property: for cur := source; cur != nil; cur, _ = cur.Source().(Property) { if cur == prop { return newError("source cycle") } } } return nil } ================================================ FILE: pushbutton.go ================================================ // 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 ( "github.com/lxn/win" ) type PushButton struct { Button } func NewPushButton(parent Container) (*PushButton, error) { pb := new(PushButton) if err := InitWidget( pb, parent, "BUTTON", win.WS_TABSTOP|win.WS_VISIBLE|win.BS_PUSHBUTTON, 0); err != nil { return nil, err } pb.Button.init() pb.GraphicsEffects().Add(InteractionEffect) pb.GraphicsEffects().Add(FocusEffect) return pb, nil } func (pb *PushButton) ImageAboveText() bool { return pb.hasStyleBits(win.BS_TOP) } func (pb *PushButton) SetImageAboveText(value bool) error { if err := pb.ensureStyleBits(win.BS_TOP, value); err != nil { return err } // We need to set the image again, or Windows will fail to calculate the // button control size correctly. return pb.SetImage(pb.image) } func (pb *PushButton) ensureProperDialogDefaultButton(hwndFocus win.HWND) { widget := windowFromHandle(hwndFocus) if widget == nil { return } if _, ok := widget.(*PushButton); ok { return } form := ancestor(pb) if form == nil { return } dlg, ok := form.(dialogish) if !ok { return } defBtn := dlg.DefaultButton() if defBtn == nil { return } if err := defBtn.setAndClearStyleBits(win.BS_DEFPUSHBUTTON, win.BS_PUSHBUTTON); err != nil { return } if err := defBtn.Invalidate(); err != nil { return } } func (pb *PushButton) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_GETDLGCODE: hwndFocus := win.GetFocus() if hwndFocus == pb.hWnd { form := ancestor(pb) if form == nil { break } dlg, ok := form.(dialogish) if !ok { break } defBtn := dlg.DefaultButton() if defBtn == pb { pb.setAndClearStyleBits(win.BS_DEFPUSHBUTTON, win.BS_PUSHBUTTON) return win.DLGC_BUTTON | win.DLGC_DEFPUSHBUTTON } break } pb.ensureProperDialogDefaultButton(hwndFocus) case win.WM_KILLFOCUS: pb.ensureProperDialogDefaultButton(win.HWND(wParam)) } return pb.Button.WndProc(hwnd, msg, wParam, lParam) } func (pb *PushButton) CreateLayoutItem(ctx *LayoutContext) LayoutItem { return &pushButtonLayoutItem{ buttonLayoutItem: buttonLayoutItem{ idealSize: pb.idealSize(), }, } } type pushButtonLayoutItem struct { buttonLayoutItem } func (*pushButtonLayoutItem) LayoutFlags() LayoutFlags { return GrowableHorz } ================================================ FILE: radiobutton.go ================================================ // 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 ( "github.com/lxn/win" ) type RadioButtonGroup struct { buttons []*RadioButton checkedButton *RadioButton } func (rbg *RadioButtonGroup) Buttons() []*RadioButton { buttons := make([]*RadioButton, len(rbg.buttons)) copy(buttons, rbg.buttons) return buttons } func (rbg *RadioButtonGroup) CheckedButton() *RadioButton { return rbg.checkedButton } type radioButtonish interface { radioButton() *RadioButton } type RadioButton struct { Button group *RadioButtonGroup value interface{} } func NewRadioButton(parent Container) (*RadioButton, error) { rb := new(RadioButton) if count := parent.Children().Len(); count > 0 { if prevRB, ok := parent.Children().At(count - 1).(radioButtonish); ok { rb.group = prevRB.radioButton().group } } var groupBit uint32 if rb.group == nil { groupBit = win.WS_GROUP rb.group = new(RadioButtonGroup) } if err := InitWidget( rb, parent, "BUTTON", groupBit|win.WS_TABSTOP|win.WS_VISIBLE|win.BS_AUTORADIOBUTTON, 0); err != nil { return nil, err } rb.Button.init() rb.SetBackground(nullBrushSingleton) rb.GraphicsEffects().Add(InteractionEffect) rb.GraphicsEffects().Add(FocusEffect) rb.MustRegisterProperty("CheckedValue", NewProperty( func() interface{} { if rb.Checked() { return rb.value } return nil }, func(v interface{}) error { checked := v == rb.value if checked { rb.group.checkedButton = rb } rb.SetChecked(checked) return nil }, rb.CheckedChanged())) rb.group.buttons = append(rb.group.buttons, rb) return rb, nil } func (rb *RadioButton) radioButton() *RadioButton { return rb } func (rb *RadioButton) TextOnLeftSide() bool { return rb.hasStyleBits(win.BS_LEFTTEXT) } func (rb *RadioButton) SetTextOnLeftSide(textLeft bool) error { return rb.ensureStyleBits(win.BS_LEFTTEXT, textLeft) } func (rb *RadioButton) Group() *RadioButtonGroup { return rb.group } func (rb *RadioButton) Value() interface{} { return rb.value } func (rb *RadioButton) SetValue(value interface{}) { rb.value = value } func (rb *RadioButton) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_COMMAND: switch win.HIWORD(uint32(wParam)) { case win.BN_CLICKED: prevChecked := rb.group.checkedButton rb.group.checkedButton = rb if prevChecked != rb { if prevChecked != nil { prevChecked.setChecked(false) } rb.setChecked(true) } } } return rb.Button.WndProc(hwnd, msg, wParam, lParam) } ================================================ FILE: rectangle.go ================================================ // 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 ( "github.com/lxn/win" ) // Rectangle defines upper left corner with width and height region in 1/96" units, or native // pixels, or grid rows and columns. type Rectangle struct { X, Y, Width, Height int } func (r Rectangle) IsZero() bool { return r.X == 0 && r.Y == 0 && r.Width == 0 && r.Height == 0 } func rectangleFromRECT(r win.RECT) Rectangle { return Rectangle{ X: int(r.Left), Y: int(r.Top), Width: int(r.Right - r.Left), Height: int(r.Bottom - r.Top), } } func (r Rectangle) Left() int { return r.X } func (r Rectangle) Top() int { return r.Y } func (r Rectangle) Right() int { return r.X + r.Width - 1 } func (r Rectangle) Bottom() int { return r.Y + r.Height - 1 } func (r Rectangle) Location() Point { return Point{r.X, r.Y} } func (r *Rectangle) SetLocation(p Point) Rectangle { r.X = p.X r.Y = p.Y return *r } func (r Rectangle) Size() Size { return Size{r.Width, r.Height} } func (r *Rectangle) SetSize(s Size) Rectangle { r.Width = s.Width r.Height = s.Height return *r } func (r Rectangle) toRECT() win.RECT { return win.RECT{ int32(r.X), int32(r.Y), int32(r.X + r.Width), int32(r.Y + r.Height), } } ================================================ FILE: reflectmodels.go ================================================ // Copyright 2013 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 ( "fmt" "reflect" "sort" ) type reflectModel interface { Items() interface{} } type bindingAndDisplayMemberSetter interface { setBindingMember(member string) setDisplayMember(member string) } type reflectListModel struct { ListModelBase bindingMember string displayMember string dataSource interface{} items interface{} value reflect.Value } func newReflectListModel(dataSource interface{}) (ListModel, error) { items, err := itemsFromReflectModelDataSource(dataSource, "ReflectListModel") if err != nil { return nil, err } m := &reflectListModel{ dataSource: dataSource, items: items, value: reflect.ValueOf(items), } if rlm, ok := dataSource.(ReflectListModel); ok { rlm.setValueFunc(func(index int) interface{} { return m.Value(index) }) rlm.ItemChanged().Attach(func(index int) { m.PublishItemChanged(index) }) rlm.ItemsReset().Attach(func() { m.items = rlm.Items() m.value = reflect.ValueOf(m.items) m.PublishItemsReset() }) rlm.ItemsInserted().Attach(func(from, to int) { m.items = rlm.Items() m.value = reflect.ValueOf(m.items) m.PublishItemsInserted(from, to) }) rlm.ItemsRemoved().Attach(func(from, to int) { m.items = rlm.Items() m.value = reflect.ValueOf(m.items) m.PublishItemsRemoved(from, to) }) } return m, nil } func (m *reflectListModel) setBindingMember(member string) { m.bindingMember = member } func (m *reflectListModel) setDisplayMember(member string) { m.displayMember = member } func (m *reflectListModel) ItemCount() int { return m.value.Len() } func (m *reflectListModel) BindingValue(index int) interface{} { return valueFromSlice(m.dataSource, m.value, m.bindingMember, index) } func (m *reflectListModel) Value(index int) interface{} { return valueFromSlice(m.dataSource, m.value, m.displayMember, index) } type lessFuncsSetter interface { setLessFuncs(lessFuncs []func(i, j int) bool) } type dataMembersSetter interface { setDataMembers(dataMembers []string) } type reflectTableModel struct { TableModelBase sorterBase *SorterBase lessFuncs []func(i, j int) bool dataMembers []string dataSource interface{} items interface{} value reflect.Value } func newReflectTableModel(dataSource interface{}) (TableModel, error) { items, err := itemsFromReflectModelDataSource(dataSource, "ReflectTableModel") if err != nil { return nil, err } m := &reflectTableModel{ dataSource: dataSource, items: items, value: reflect.ValueOf(items), } if rtm, ok := dataSource.(ReflectTableModel); ok { rtm.setValueFunc(func(row, col int) interface{} { return m.Value(row, col) }) rtm.RowChanged().Attach(func(index int) { m.PublishRowChanged(index) }) rtm.RowsReset().Attach(func() { m.items = rtm.Items() m.value = reflect.ValueOf(m.items) m.PublishRowsReset() if is, ok := dataSource.(interceptedSorter); ok { sb := is.sorterBase() m.sort(sb.SortedColumn(), sb.SortOrder()) } }) rtm.RowsChanged().Attach(func(from, to int) { m.PublishRowsChanged(from, to) }) rtm.RowsInserted().Attach(func(from, to int) { m.items = rtm.Items() m.value = reflect.ValueOf(m.items) m.PublishRowsInserted(from, to) }) rtm.RowsRemoved().Attach(func(from, to int) { m.items = rtm.Items() m.value = reflect.ValueOf(m.items) m.PublishRowsRemoved(from, to) }) } else { m.sorterBase = new(SorterBase) } if is, ok := dataSource.(interceptedSorter); ok { m.sorterBase = is.sorterBase() is.setSortFunc(func(col int, order SortOrder) error { return m.sort(col, order) }) } _, isImageProvider := dataSource.(ImageProvider) _, isSortable := dataSource.(Sorter) if !isSortable { isSortable = m.sorterBase != nil } if isImageProvider { if isSortable { return &sortedImageReflectTableModel{reflectTableModel: m}, nil } else { return &imageReflectTableModel{reflectTableModel: m}, nil } } else if isSortable { return &sortedReflectTableModel{reflectTableModel: m}, nil } return m, nil } func (m *reflectTableModel) setLessFuncs(lessFuncs []func(i, j int) bool) { m.lessFuncs = lessFuncs } func (m *reflectTableModel) setDataMembers(dataMembers []string) { m.dataMembers = dataMembers } func (m *reflectTableModel) RowCount() int { return m.value.Len() } func (m *reflectTableModel) Value(row, col int) interface{} { return valueFromSlice(m.dataSource, m.value, m.dataMembers[col], row) } func (m *reflectTableModel) Checked(row int) bool { if m.value.Index(row).IsNil() { return false } if checker, ok := m.dataSource.(ItemChecker); ok { return checker.Checked(row) } return false } func (m *reflectTableModel) SetChecked(row int, checked bool) error { if m.value.Index(row).IsNil() { return nil } if checker, ok := m.dataSource.(ItemChecker); ok { return checker.SetChecked(row, checked) } return nil } func (m *reflectTableModel) ColumnSortable(col int) bool { if sorter, ok := m.dataSource.(Sorter); ok { return sorter.ColumnSortable(col) } return true } func (m *reflectTableModel) SortChanged() *Event { if sorter, ok := m.dataSource.(Sorter); ok { return sorter.SortChanged() } if m.sorterBase != nil { return m.sorterBase.SortChanged() } return nil } func (m *reflectTableModel) SortedColumn() int { if sorter, ok := m.dataSource.(Sorter); ok { return sorter.SortedColumn() } if m.sorterBase != nil { return m.sorterBase.SortedColumn() } return -1 } func (m *reflectTableModel) SortOrder() SortOrder { if sorter, ok := m.dataSource.(Sorter); ok { return sorter.SortOrder() } if m.sorterBase != nil { return m.sorterBase.SortOrder() } return SortAscending } func (m *reflectTableModel) sort(col int, order SortOrder) error { if sb := m.sorterBase; sb != nil { sb.col, sb.order = col, order sort.Stable(m) sb.changedPublisher.Publish() return nil } if sorter, ok := m.dataSource.(Sorter); ok { return sorter.Sort(col, order) } return nil } func (m *reflectTableModel) Len() int { return m.RowCount() } func (m *reflectTableModel) Less(i, j int) bool { col := m.SortedColumn() if lt := m.lessFuncs[col]; lt != nil { ls := lt(i, j) if m.SortOrder() == SortAscending { return ls } else { return !ls } } return less(m.Value(i, col), m.Value(j, col), m.SortOrder()) } func (m *reflectTableModel) Swap(i, j int) { vi := m.value.Index(i) vj := m.value.Index(j) viv := vi.Interface() vjv := vj.Interface() vi.Set(reflect.ValueOf(vjv)) vj.Set(reflect.ValueOf(viv)) } type imageReflectTableModel struct { *reflectTableModel } func (m *imageReflectTableModel) Image(index int) interface{} { if m.value.Index(index).IsNil() { return nil } return m.dataSource.(ImageProvider).Image(index) } type sortedReflectTableModel struct { *reflectTableModel } func (m *sortedReflectTableModel) Sort(col int, order SortOrder) error { return m.reflectTableModel.sort(col, order) } type sortedImageReflectTableModel struct { *reflectTableModel } func (m *sortedImageReflectTableModel) Sort(col int, order SortOrder) error { return m.reflectTableModel.sort(col, order) } func (m *sortedImageReflectTableModel) Image(index int) interface{} { if m.value.Index(index).IsNil() { return nil } return m.dataSource.(ImageProvider).Image(index) } func itemsFromReflectModelDataSource(dataSource interface{}, requiredInterfaceName string) (interface{}, error) { var items interface{} if rm, ok := dataSource.(reflectModel); ok { items = rm.Items() } else { items = dataSource } if requiredInterfaceName == "ReflectListModel" { if _, ok := dataSource.([]string); ok { return items, nil } } if t := reflect.TypeOf(items); t != nil && t.Kind() == reflect.Slice && (t.Elem().Kind() == reflect.Struct || (t.Elem().Kind() == reflect.Interface || t.Elem().Kind() == reflect.Ptr) && t.Elem().Elem().Kind() == reflect.Struct) { return items, nil } return nil, newError(fmt.Sprintf("dataSource must be a slice of struct or interface or pointer to struct or must implement %s.", requiredInterfaceName)) } func valueFromSlice(dataSource interface{}, itemsValue reflect.Value, member string, index int) interface{} { if member == "" { if strs, ok := dataSource.([]string); ok { return strs[index] } return "" } v := itemsValue.Index(index) if v.Kind() == reflect.Ptr && v.IsNil() { if populator, ok := dataSource.(Populator); ok { if err := populator.Populate(index); err != nil { return err } } if v.IsNil() { return nil } } _, vv, err := reflectValueFromPath(v, member) if err != nil { return err } return vv.Interface() } ================================================ FILE: registry.go ================================================ // 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 RegistryKey struct { hKey win.HKEY } func ClassesRootKey() *RegistryKey { return &RegistryKey{win.HKEY_CLASSES_ROOT} } func CurrentUserKey() *RegistryKey { return &RegistryKey{win.HKEY_CURRENT_USER} } func LocalMachineKey() *RegistryKey { return &RegistryKey{win.HKEY_LOCAL_MACHINE} } func RegistryKeyString(rootKey *RegistryKey, subKeyPath, valueName string) (value string, err error) { var hKey win.HKEY if win.RegOpenKeyEx( rootKey.hKey, syscall.StringToUTF16Ptr(subKeyPath), 0, win.KEY_READ, &hKey) != win.ERROR_SUCCESS { return "", newError("RegistryKeyString: Failed to open subkey.") } defer win.RegCloseKey(hKey) var typ uint32 var data []uint16 var bufSize uint32 if win.ERROR_SUCCESS != win.RegQueryValueEx( hKey, syscall.StringToUTF16Ptr(valueName), nil, &typ, nil, &bufSize) { return "", newError("RegQueryValueEx #1") } data = make([]uint16, bufSize/2+1) if win.ERROR_SUCCESS != win.RegQueryValueEx( hKey, syscall.StringToUTF16Ptr(valueName), nil, &typ, (*byte)(unsafe.Pointer(&data[0])), &bufSize) { return "", newError("RegQueryValueEx #2") } return syscall.UTF16ToString(data), nil } func RegistryKeyUint32(rootKey *RegistryKey, subKeyPath, valueName string) (value uint32, err error) { var hKey win.HKEY if win.RegOpenKeyEx( rootKey.hKey, syscall.StringToUTF16Ptr(subKeyPath), 0, win.KEY_READ, &hKey) != win.ERROR_SUCCESS { return 0, newError("RegistryKeyUint32: Failed to open subkey.") } defer win.RegCloseKey(hKey) bufSize := uint32(4) if win.ERROR_SUCCESS != win.RegQueryValueEx( hKey, syscall.StringToUTF16Ptr(valueName), nil, nil, (*byte)(unsafe.Pointer(&value)), &bufSize) { return 0, newError("RegQueryValueEx") } return } ================================================ FILE: resourcemanager.go ================================================ // Copyright 2017 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 ( "fmt" "os" "path/filepath" "strconv" ) func init() { Resources.rootDirPath, _ = os.Getwd() Resources.bitmaps = make(map[string]*Bitmap) Resources.icons = make(map[string]*Icon) } // Resources is the singleton instance of ResourceManager. var Resources ResourceManager // ResourceManager is a cache for sharing resources like bitmaps and icons. // The resources can be either embedded in the running executable // file or located below a specified root directory in the file system. type ResourceManager struct { rootDirPath string bitmaps map[string]*Bitmap icons map[string]*Icon } // RootDirPath returns the root directory path where resources are to be loaded from. func (rm *ResourceManager) RootDirPath() string { return rm.rootDirPath } // SetRootDirPath sets the root directory path where resources are to be loaded from. func (rm *ResourceManager) SetRootDirPath(rootDirPath string) error { path, err := filepath.Abs(rootDirPath) if err != nil { return err } rm.rootDirPath = path return nil } // Bitmap loads a bitmap from file or resource identified by name, or an error if it could not be // found. When bitmap is loaded, 96dpi is assumed. // // Deprecated: Newer applications should use BitmapForDPI. func (rm *ResourceManager) Bitmap(name string) (*Bitmap, error) { return rm.BitmapForDPI(name, 96) } // BitmapForDPI loads a bitmap from file or resource identified by name, or an error if it could // not be found. When bitmap is loaded, given DPI is assumed. func (rm *ResourceManager) BitmapForDPI(name string, dpi int) (*Bitmap, error) { if bm := rm.bitmaps[name]; bm != nil { return bm, nil } if bm, err := NewBitmapFromFileForDPI(filepath.Join(rm.rootDirPath, name), dpi); err == nil { rm.bitmaps[name] = bm return bm, nil } if bm, err := NewBitmapFromResourceForDPI(name, dpi); err == nil { rm.bitmaps[name] = bm return bm, nil } if id, err := strconv.Atoi(name); err == nil { if bm, err := NewBitmapFromResourceIdForDPI(id, dpi); err == nil { rm.bitmaps[name] = bm return bm, nil } } return nil, rm.notFoundErr("bitmap", name) } // Icon returns the Icon identified by name, or an error if it could not be found. func (rm *ResourceManager) Icon(name string) (*Icon, error) { if icon := rm.icons[name]; icon != nil { return icon, nil } if icon, err := NewIconFromFile(filepath.Join(rm.rootDirPath, name)); err == nil { rm.icons[name] = icon return icon, nil } if icon, err := NewIconFromResource(name); err == nil { rm.icons[name] = icon return icon, nil } if id, err := strconv.Atoi(name); err == nil { if icon, err := NewIconFromResourceId(id); err == nil { rm.icons[name] = icon return icon, nil } } return nil, rm.notFoundErr("icon", name) } // Image returns the Image identified by name, or an error if it could not be found. func (rm *ResourceManager) Image(name string) (Image, error) { if icon, err := rm.Icon(name); err == nil { return icon, nil } if bm, err := rm.Bitmap(name); err == nil { return bm, nil } return nil, rm.notFoundErr("image", name) } func (rm *ResourceManager) notFoundErr(typ, name string) error { path := filepath.Clean(filepath.Join(rm.rootDirPath, name)) return newError(fmt.Sprintf("neither %s resource '%s' nor file '%s' could be found or the image format is not supported", typ, name, path)) } ================================================ FILE: scrollview.go ================================================ // Copyright 2014 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 ( "unsafe" "github.com/lxn/win" ) const scrollViewWindowClass = `\o/ Walk_ScrollView_Class \o/` func init() { AppendToWalkInit(func() { MustRegisterWindowClass(scrollViewWindowClass) }) } type ScrollView struct { WidgetBase composite *Composite horizontal bool vertical bool } func NewScrollView(parent Container) (*ScrollView, error) { sv := &ScrollView{horizontal: true, vertical: true} if err := InitWidget( sv, parent, scrollViewWindowClass, win.WS_CHILD|win.WS_HSCROLL|win.WS_VISIBLE|win.WS_VSCROLL, win.WS_EX_CONTROLPARENT); err != nil { return nil, err } succeeded := false defer func() { if !succeeded { sv.Dispose() } }() var err error if sv.composite, err = NewComposite(sv); err != nil { return nil, err } sv.composite.SizeChanged().Attach(func() { sv.updateScrollBars() }) sv.SetBackground(NullBrush()) succeeded = true return sv, nil } func (sv *ScrollView) AsContainerBase() *ContainerBase { if sv.composite == nil { return nil } return sv.composite.AsContainerBase() } func (sv *ScrollView) ApplyDPI(dpi int) { sv.WidgetBase.ApplyDPI(dpi) sv.composite.ApplyDPI(dpi) } func (sv *ScrollView) Scrollbars() (horizontal, vertical bool) { horizontal = sv.horizontal vertical = sv.vertical return } func (sv *ScrollView) SetScrollbars(horizontal, vertical bool) { sv.horizontal = horizontal sv.vertical = vertical sv.ensureStyleBits(win.WS_HSCROLL, horizontal) sv.ensureStyleBits(win.WS_VSCROLL, vertical) } func (sv *ScrollView) SetSuspended(suspend bool) { sv.composite.SetSuspended(suspend) sv.WidgetBase.SetSuspended(suspend) sv.Invalidate() } func (sv *ScrollView) DataBinder() *DataBinder { return sv.composite.dataBinder } func (sv *ScrollView) SetDataBinder(dataBinder *DataBinder) { sv.composite.SetDataBinder(dataBinder) } func (sv *ScrollView) Children() *WidgetList { if sv.composite == nil { // Without this we would get into trouble in NewComposite. return nil } return sv.composite.Children() } func (sv *ScrollView) Layout() Layout { if sv.composite == nil { return nil } return sv.composite.Layout() } func (sv *ScrollView) SetLayout(value Layout) error { return sv.composite.SetLayout(value) } func (sv *ScrollView) Name() string { if sv.composite == nil { return "" } return sv.composite.Name() } func (sv *ScrollView) SetName(name string) { sv.composite.SetName(name) } func (sv *ScrollView) Persistent() bool { return sv.composite.Persistent() } func (sv *ScrollView) SetPersistent(value bool) { sv.composite.SetPersistent(value) } func (sv *ScrollView) SaveState() error { return sv.composite.SaveState() } func (sv *ScrollView) RestoreState() error { return sv.composite.RestoreState() } func (sv *ScrollView) MouseDown() *MouseEvent { return sv.composite.MouseDown() } func (sv *ScrollView) MouseMove() *MouseEvent { return sv.composite.MouseMove() } func (sv *ScrollView) MouseUp() *MouseEvent { return sv.composite.MouseUp() } func (sv *ScrollView) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { if sv.composite != nil { avoidBGArtifacts := func() { if sv.hasComplexBackground() { sv.composite.Invalidate() } } switch msg { case win.WM_HSCROLL: sv.composite.SetXPixels(sv.scroll(win.SB_HORZ, win.LOWORD(uint32(wParam)))) if wParam == win.SB_ENDSCROLL { avoidBGArtifacts() } case win.WM_VSCROLL: sv.composite.SetYPixels(sv.scroll(win.SB_VERT, win.LOWORD(uint32(wParam)))) if wParam == win.SB_ENDSCROLL { avoidBGArtifacts() } case win.WM_MOUSEWHEEL: if win.GetWindowLong(sv.hWnd, win.GWL_STYLE)&win.WS_VSCROLL == 0 { break } var cmd uint16 if delta := int16(win.HIWORD(uint32(wParam))); delta < 0 { cmd = win.SB_LINEDOWN } else { cmd = win.SB_LINEUP } sv.composite.SetYPixels(sv.scroll(win.SB_VERT, cmd)) avoidBGArtifacts() return 0 case win.WM_COMMAND, win.WM_NOTIFY: sv.composite.WndProc(hwnd, msg, wParam, lParam) case win.WM_WINDOWPOSCHANGED: wp := (*win.WINDOWPOS)(unsafe.Pointer(lParam)) if wp.Flags&win.SWP_NOSIZE != 0 { break } sv.updateScrollBars() if h, v := sv.Scrollbars(); !h || !v { sv.RequestLayout() } } } return sv.WidgetBase.WndProc(hwnd, msg, wParam, lParam) } func (sv *ScrollView) updateScrollBars() { size := sv.SizePixels() compositeSize := sv.composite.SizePixels() var si win.SCROLLINFO si.CbSize = uint32(unsafe.Sizeof(si)) si.FMask = win.SIF_PAGE | win.SIF_RANGE newCompositeBounds := Rectangle{Width: compositeSize.Width, Height: compositeSize.Height} if size != compositeSize { dpi := uint32(sv.DPI()) vsbw := int(win.GetSystemMetricsForDpi(win.SM_CXVSCROLL, dpi)) hsbh := int(win.GetSystemMetricsForDpi(win.SM_CYHSCROLL, dpi)) if size.Width < compositeSize.Width && size.Height < compositeSize.Height { size.Width -= vsbw size.Height -= hsbh } } si.NMax = int32(compositeSize.Width - 1) si.NPage = uint32(size.Width) win.SetScrollInfo(sv.hWnd, win.SB_HORZ, &si, false) newCompositeBounds.X = sv.scroll(win.SB_HORZ, win.SB_THUMBPOSITION) si.NMax = int32(compositeSize.Height - 1) si.NPage = uint32(size.Height) win.SetScrollInfo(sv.hWnd, win.SB_VERT, &si, false) newCompositeBounds.Y = sv.scroll(win.SB_VERT, win.SB_THUMBPOSITION) sv.composite.SetBoundsPixels(newCompositeBounds) } // scroll scrolls and returns new position in native pixels. func (sv *ScrollView) scroll(sb int32, cmd uint16) int { var pos int32 var si win.SCROLLINFO si.CbSize = uint32(unsafe.Sizeof(si)) si.FMask = win.SIF_PAGE | win.SIF_POS | win.SIF_RANGE | win.SIF_TRACKPOS win.GetScrollInfo(sv.hWnd, sb, &si) pos = si.NPos switch cmd { case win.SB_LINELEFT: // == win.SB_LINEUP pos -= int32(sv.IntFrom96DPI(20)) case win.SB_LINERIGHT: // == win.SB_LINEDOWN pos += int32(sv.IntFrom96DPI(20)) case win.SB_PAGELEFT: // == win.SB_PAGEUP pos -= int32(si.NPage) case win.SB_PAGERIGHT: // == win.SB_PAGEDOWN pos += int32(si.NPage) case win.SB_THUMBTRACK: pos = si.NTrackPos } if pos < 0 { pos = 0 } if pos > si.NMax+1-int32(si.NPage) { pos = si.NMax + 1 - int32(si.NPage) } si.FMask = win.SIF_POS si.NPos = pos win.SetScrollInfo(sv.hWnd, sb, &si, true) return -int(pos) } func (sv *ScrollView) CreateLayoutItem(ctx *LayoutContext) LayoutItem { svli := new(scrollViewLayoutItem) svli.ctx = ctx cli := CreateLayoutItemsForContainerWithContext(sv.composite, ctx) cli.AsLayoutItemBase().parent = svli svli.children = append(svli.children, cli) if box, ok := cli.(*boxLayoutItem); ok { if len(box.children) > 0 { if _, ok := box.children[len(box.children)-1].(*spacerLayoutItem); !ok { // To retain the previous behavior with box layouts, we add a fake spacer at the end. // Maybe this should just be an option. box.children = append(box.children, &spacerLayoutItem{ LayoutItemBase: LayoutItemBase{ctx: ctx}, layoutFlags: ShrinkableHorz | ShrinkableVert | GrowableVert | GreedyVert, }) } } } svli.idealSize = cli.MinSize() h, v := sv.Scrollbars() if h { svli.layoutFlags |= ShrinkableHorz | GrowableHorz | GreedyHorz if !v { maxSize := SizeFrom96DPI(sv.maxSize96dpi, ctx.dpi) if svli.idealSize.Width > sv.geometry.ClientSize.Width && sv.geometry.ClientSize.Width > 0 && maxSize.Width == 0 || svli.idealSize.Width > maxSize.Width && maxSize.Width > 0 { svli.sbSize.Height = int(win.GetSystemMetricsForDpi(win.SM_CYHSCROLL, uint32(ctx.dpi))) svli.idealSize.Height += svli.sbSize.Height } svli.minSize.Height = svli.idealSize.Height } } if v { svli.layoutFlags |= GreedyVert | GrowableVert | ShrinkableVert if !h { maxSize := SizeFrom96DPI(sv.maxSize96dpi, ctx.dpi) if svli.idealSize.Height > sv.geometry.ClientSize.Height && sv.geometry.ClientSize.Height > 0 && maxSize.Height == 0 || svli.idealSize.Height > maxSize.Height && maxSize.Height > 0 { svli.sbSize.Width = int(win.GetSystemMetricsForDpi(win.SM_CXVSCROLL, uint32(ctx.dpi))) svli.idealSize.Width += svli.sbSize.Width } svli.minSize.Width = svli.idealSize.Width } } var si win.SCROLLINFO si.CbSize = uint32(unsafe.Sizeof(si)) si.FMask = win.SIF_POS | win.SIF_RANGE win.GetScrollInfo(sv.hWnd, win.SB_HORZ, &si) svli.scrollX = float64(si.NPos) / float64(si.NMax) win.GetScrollInfo(sv.hWnd, win.SB_VERT, &si) svli.scrollY = float64(si.NPos) / float64(si.NMax) return svli } type scrollViewLayoutItem struct { ContainerLayoutItemBase idealSize Size // in native pixels minSize Size // in native pixels sbSize Size // in native pixels layoutFlags LayoutFlags scrollX float64 scrollY float64 } func (li *scrollViewLayoutItem) LayoutFlags() LayoutFlags { return li.layoutFlags } func (li *scrollViewLayoutItem) IdealSize() Size { return li.idealSize } func (li *scrollViewLayoutItem) MinSize() Size { return li.minSize } func (li *scrollViewLayoutItem) MinSizeForSize(size Size) Size { return li.MinSize() } func (li *scrollViewLayoutItem) HasHeightForWidth() bool { return false } func (li *scrollViewLayoutItem) HeightForWidth(width int) int { return 0 } func (li *scrollViewLayoutItem) PerformLayout() []LayoutResultItem { composite := li.children[0] clientSize := li.geometry.Size clientSize.Width -= li.sbSize.Width clientSize.Height -= li.sbSize.Height minSize := composite.(MinSizeForSizer).MinSizeForSize(clientSize) if hfw, ok := composite.(HeightForWidther); ok && hfw.HasHeightForWidth() { if minSize.Height > clientSize.Height { if minSize.Width > clientSize.Width { clientSize.Width = minSize.Width minSize = composite.(MinSizeForSizer).MinSizeForSize(clientSize) } else { clientSize.Width -= int(win.GetSystemMetricsForDpi(win.SM_CXVSCROLL, uint32(li.ctx.dpi))) minSize = composite.(MinSizeForSizer).MinSizeForSize(clientSize) if minSize.Width > clientSize.Width { clientSize.Width = minSize.Width minSize = composite.(MinSizeForSizer).MinSizeForSize(clientSize) } } } } s := maxSize(minSize, clientSize) var x, y int if clientSize.Width < minSize.Width { x = -int(float64(minSize.Width) * li.scrollX) } if clientSize.Height < minSize.Height { y = -int(float64(minSize.Height) * li.scrollY) } return []LayoutResultItem{ { Item: composite, Bounds: Rectangle{x, y, s.Width, s.Height}, }, } } ================================================ FILE: separator.go ================================================ // Copyright 2017 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 ( "github.com/lxn/win" ) type Separator struct { WidgetBase vertical bool } func NewHSeparator(parent Container) (*Separator, error) { return newSeparator(parent, false) } func NewVSeparator(parent Container) (*Separator, error) { return newSeparator(parent, true) } func newSeparator(parent Container, vertical bool) (*Separator, error) { s := &Separator{vertical: vertical} if err := InitWidget( s, parent, "STATIC", win.WS_VISIBLE|win.SS_ETCHEDHORZ, 0); err != nil { return nil, err } return s, nil } func (s *Separator) CreateLayoutItem(ctx *LayoutContext) LayoutItem { var layoutFlags LayoutFlags if s.vertical { layoutFlags = GrowableHorz | GreedyHorz } else { layoutFlags = GrowableVert | GreedyVert } return &separatorLayoutItem{ layoutFlags: layoutFlags, } } type separatorLayoutItem struct { LayoutItemBase layoutFlags LayoutFlags } func (li *separatorLayoutItem) LayoutFlags() LayoutFlags { return li.layoutFlags } func (li *separatorLayoutItem) IdealSize() Size { return li.MinSize() } func (li *separatorLayoutItem) MinSize() Size { return SizeFrom96DPI(Size{2, 2}, li.ctx.dpi) } ================================================ FILE: simpletypes.go ================================================ // 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 type Alignment1D uint const ( AlignDefault Alignment1D = iota AlignNear AlignCenter AlignFar ) type Alignment2D uint const ( AlignHVDefault Alignment2D = iota AlignHNearVNear AlignHCenterVNear AlignHFarVNear AlignHNearVCenter AlignHCenterVCenter AlignHFarVCenter AlignHNearVFar AlignHCenterVFar AlignHFarVFar ) ================================================ FILE: size.go ================================================ // 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 "github.com/lxn/win" // Size defines width and height in 1/96" units or native pixels, or dialog base units. // // When Size is used for DPI metrics, it defines a 1"x1" rectangle in native pixels. type Size struct { Width, Height int } func (s Size) IsZero() bool { return s.Width == 0 && s.Height == 0 } func (s Size) toSIZE() win.SIZE { return win.SIZE{ CX: int32(s.Width), CY: int32(s.Height), } } func minSize(a, b Size) Size { var s Size if a.Width < b.Width { s.Width = a.Width } else { s.Width = b.Width } if a.Height < b.Height { s.Height = a.Height } else { s.Height = b.Height } return s } func maxSize(a, b Size) Size { var s Size if a.Width > b.Width { s.Width = a.Width } else { s.Width = b.Width } if a.Height > b.Height { s.Height = a.Height } else { s.Height = b.Height } return s } func sizeFromSIZE(s win.SIZE) Size { return Size{ Width: int(s.CX), Height: int(s.CY), } } func sizeFromRECT(r win.RECT) Size { return Size{ Width: int(r.Right - r.Left), Height: int(r.Bottom - r.Top), } } ================================================ FILE: slider.go ================================================ // Copyright 2016 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 ( "strconv" "github.com/lxn/win" ) type Slider struct { WidgetBase valueChangedPublisher EventPublisher layoutFlags LayoutFlags tracking bool persistent bool } type SliderCfg struct { Orientation Orientation ToolTipsHidden bool } func NewSlider(parent Container) (*Slider, error) { return NewSliderWithOrientation(parent, Horizontal) } func NewSliderWithOrientation(parent Container, orientation Orientation) (*Slider, error) { return NewSliderWithCfg(parent, &SliderCfg{Orientation: orientation}) } func NewSliderWithCfg(parent Container, cfg *SliderCfg) (*Slider, error) { sl := new(Slider) var style uint32 = win.WS_TABSTOP | win.WS_VISIBLE if cfg.Orientation == Vertical { style |= win.TBS_VERT sl.layoutFlags = ShrinkableVert | GrowableVert } else { sl.layoutFlags = ShrinkableHorz | GrowableHorz } if !cfg.ToolTipsHidden { style |= win.TBS_TOOLTIPS } if err := InitWidget( sl, parent, "msctls_trackbar32", style, 0); err != nil { return nil, err } sl.SetBackground(nullBrushSingleton) sl.GraphicsEffects().Add(InteractionEffect) sl.GraphicsEffects().Add(FocusEffect) sl.MustRegisterProperty("Value", NewProperty( func() interface{} { return sl.Value() }, func(v interface{}) error { sl.SetValue(assertIntOr(v, 0)) return nil }, sl.valueChangedPublisher.Event())) return sl, nil } func (sl *Slider) MinValue() int { return int(sl.SendMessage(win.TBM_GETRANGEMIN, 0, 0)) } func (sl *Slider) MaxValue() int { return int(sl.SendMessage(win.TBM_GETRANGEMAX, 0, 0)) } func (sl *Slider) SetRange(min, max int) { sl.SendMessage(win.TBM_SETRANGEMIN, 0, uintptr(min)) sl.SendMessage(win.TBM_SETRANGEMAX, 1, uintptr(max)) } func (sl *Slider) Value() int { return int(sl.SendMessage(win.TBM_GETPOS, 0, 0)) } func (sl *Slider) SetValue(value int) { sl.SendMessage(win.TBM_SETPOS, 1, uintptr(value)) sl.valueChangedPublisher.Publish() } // ValueChanged returns an Event that can be used to track changes to Value. func (sl *Slider) ValueChanged() *Event { return sl.valueChangedPublisher.Event() } func (sl *Slider) Persistent() bool { return sl.persistent } func (sl *Slider) SetPersistent(value bool) { sl.persistent = value } func (sl *Slider) SaveState() error { return sl.WriteState(strconv.Itoa(sl.Value())) } func (sl *Slider) RestoreState() error { s, err := sl.ReadState() if err != nil { return err } value, err := strconv.Atoi(s) if err != nil { return err } sl.SetValue(value) return nil } func (sl *Slider) LineSize() int { return int(sl.SendMessage(win.TBM_GETLINESIZE, 0, 0)) } func (sl *Slider) SetLineSize(lineSize int) { sl.SendMessage(win.TBM_SETLINESIZE, 0, uintptr(lineSize)) } func (sl *Slider) PageSize() int { return int(sl.SendMessage(win.TBM_GETPAGESIZE, 0, 0)) } func (sl *Slider) SetPageSize(pageSize int) { sl.SendMessage(win.TBM_SETPAGESIZE, 0, uintptr(pageSize)) } func (sl *Slider) Tracking() bool { return sl.tracking } func (sl *Slider) SetTracking(tracking bool) { sl.tracking = tracking } func (sl *Slider) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_HSCROLL, win.WM_VSCROLL: switch win.LOWORD(uint32(wParam)) { case win.TB_THUMBPOSITION, win.TB_ENDTRACK: sl.valueChangedPublisher.Publish() case win.TB_THUMBTRACK: if sl.tracking { sl.valueChangedPublisher.Publish() } } return 0 } return sl.WidgetBase.WndProc(hwnd, msg, wParam, lParam) } func (*Slider) NeedsWmSize() bool { return true } func (sl *Slider) CreateLayoutItem(ctx *LayoutContext) LayoutItem { return &sliderLayoutItem{ layoutFlags: sl.layoutFlags, idealSize: sl.dialogBaseUnitsToPixels(Size{15, 15}), } } type sliderLayoutItem struct { LayoutItemBase layoutFlags LayoutFlags idealSize Size // in native pixels } func (li *sliderLayoutItem) LayoutFlags() LayoutFlags { return li.layoutFlags } func (li *sliderLayoutItem) IdealSize() Size { return li.idealSize } func (li *sliderLayoutItem) MinSize() Size { return li.idealSize } ================================================ FILE: spacer.go ================================================ // Copyright 2011 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 const spacerWindowClass = `\o/ Walk_Spacer_Class \o/` func init() { AppendToWalkInit(func() { MustRegisterWindowClass(spacerWindowClass) }) } type Spacer struct { WidgetBase sizeHint96dpi Size layoutFlags LayoutFlags greedyLocallyOnly bool } type SpacerCfg struct { LayoutFlags LayoutFlags SizeHint Size // in 1/96" units GreedyLocallyOnly bool } func NewSpacerWithCfg(parent Container, cfg *SpacerCfg) (*Spacer, error) { return newSpacer(parent, cfg.LayoutFlags, cfg.SizeHint, cfg.GreedyLocallyOnly) } func newSpacer(parent Container, layoutFlags LayoutFlags, sizeHint96dpi Size, greedyLocallyOnly bool) (*Spacer, error) { s := &Spacer{ layoutFlags: layoutFlags, sizeHint96dpi: sizeHint96dpi, greedyLocallyOnly: greedyLocallyOnly, } if err := InitWidget( s, parent, spacerWindowClass, 0, 0); err != nil { return nil, err } return s, nil } func NewHSpacer(parent Container) (*Spacer, error) { return newSpacer(parent, ShrinkableHorz|ShrinkableVert|GrowableHorz|GreedyHorz, Size{}, false) } func NewHSpacerFixed(parent Container, width int) (*Spacer, error) { return newSpacer(parent, 0, Size{width, 0}, false) } func NewVSpacer(parent Container) (*Spacer, error) { return newSpacer(parent, ShrinkableHorz|ShrinkableVert|GrowableVert|GreedyVert, Size{}, false) } func NewVSpacerFixed(parent Container, height int) (*Spacer, error) { return newSpacer(parent, 0, Size{0, height}, false) } func (s *Spacer) CreateLayoutItem(ctx *LayoutContext) LayoutItem { return &spacerLayoutItem{ idealSize96dpi: s.sizeHint96dpi, layoutFlags: s.layoutFlags, greedyLocallyOnly: s.greedyLocallyOnly, } } type spacerLayoutItem struct { LayoutItemBase idealSize96dpi Size layoutFlags LayoutFlags greedyLocallyOnly bool } func (li *spacerLayoutItem) LayoutFlags() LayoutFlags { return li.layoutFlags } func (li *spacerLayoutItem) IdealSize() Size { return SizeFrom96DPI(li.idealSize96dpi, li.ctx.dpi) } func (li *spacerLayoutItem) MinSize() Size { return SizeFrom96DPI(li.idealSize96dpi, li.ctx.dpi) } ================================================ FILE: splitbutton.go ================================================ // Copyright 2016 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 ( "unsafe" ) import ( "github.com/lxn/win" ) type SplitButton struct { Button menu *Menu } func NewSplitButton(parent Container) (*SplitButton, error) { sb := new(SplitButton) var disposables Disposables defer disposables.Treat() if err := InitWidget( sb, parent, "BUTTON", win.WS_TABSTOP|win.WS_VISIBLE|win.BS_SPLITBUTTON, 0); err != nil { return nil, err } disposables.Add(sb) sb.Button.init() menu, err := NewMenu() if err != nil { return nil, err } disposables.Add(menu) menu.window = sb sb.menu = menu sb.GraphicsEffects().Add(InteractionEffect) sb.GraphicsEffects().Add(FocusEffect) disposables.Spare() return sb, nil } func (sb *SplitButton) Dispose() { sb.Button.Dispose() sb.menu.Dispose() } func (sb *SplitButton) ImageAboveText() bool { return sb.hasStyleBits(win.BS_TOP) } func (sb *SplitButton) SetImageAboveText(value bool) error { if err := sb.ensureStyleBits(win.BS_TOP, value); err != nil { return err } // We need to set the image again, or Windows will fail to calculate the // button control size correctly. return sb.SetImage(sb.image) } func (sb *SplitButton) Menu() *Menu { return sb.menu } func (sb *SplitButton) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_NOTIFY: switch ((*win.NMHDR)(unsafe.Pointer(lParam))).Code { case win.BCN_DROPDOWN: dd := (*win.NMBCDROPDOWN)(unsafe.Pointer(lParam)) p := win.POINT{dd.RcButton.Left, dd.RcButton.Bottom} win.ClientToScreen(sb.hWnd, &p) win.TrackPopupMenuEx( sb.menu.hMenu, win.TPM_NOANIMATION, p.X, p.Y, sb.hWnd, nil) return 0 } } return sb.Button.WndProc(hwnd, msg, wParam, lParam) } ================================================ FILE: splitter.go ================================================ // 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 ( "bytes" "log" "strconv" "strings" "unsafe" "github.com/lxn/win" ) const splitterWindowClass = `\o/ Walk_Splitter_Class \o/` var splitterHandleDraggingBrush *SolidColorBrush func init() { AppendToWalkInit(func() { MustRegisterWindowClass(splitterWindowClass) splitterHandleDraggingBrush, _ = NewSolidColorBrush(Color(win.GetSysColor(win.COLOR_BTNSHADOW))) splitterHandleDraggingBrush.wb2info = map[*WindowBase]*windowBrushInfo{nil: nil} }) } type Splitter struct { ContainerBase handleWidth int mouseDownPos Point // in native pixels draggedHandle *splitterHandle persistent bool removing bool } func newSplitter(parent Container, orientation Orientation) (*Splitter, error) { layout := newSplitterLayout(Horizontal) s := &Splitter{ ContainerBase: ContainerBase{ layout: layout, }, handleWidth: 5, } s.children = newWidgetList(s) layout.container = s if err := InitWidget( s, parent, splitterWindowClass, win.WS_VISIBLE, win.WS_EX_CONTROLPARENT); err != nil { return nil, err } var succeeded bool defer func() { if !succeeded { s.Dispose() } }() s.SetBackground(NullBrush()) if err := s.setOrientation(orientation); err != nil { return nil, err } s.SetPersistent(true) succeeded = true return s, nil } func NewHSplitter(parent Container) (*Splitter, error) { return newSplitter(parent, Horizontal) } func NewVSplitter(parent Container) (*Splitter, error) { return newSplitter(parent, Vertical) } func (s *Splitter) SetLayout(value Layout) error { return newError("not supported") } func (s *Splitter) HandleWidth() int { return s.handleWidth } func (s *Splitter) SetHandleWidth(value int) error { if value == s.handleWidth { return nil } if value < 1 { return newError("invalid handle width") } s.handleWidth = value s.RequestLayout() return nil } func (s *Splitter) Orientation() Orientation { layout := s.layout.(*splitterLayout) return layout.Orientation() } func (s *Splitter) setOrientation(value Orientation) error { var cursor Cursor if value == Horizontal { cursor = CursorSizeWE() } else { cursor = CursorSizeNS() } for i, wb := range s.Children().items { if i%2 == 1 { wb.window.SetCursor(cursor) } } layout := s.layout.(*splitterLayout) return layout.SetOrientation(value) } func (s *Splitter) updateMarginsForFocusEffect() { var margins Margins var parentLayout Layout if s.parent != nil { if parentLayout = s.parent.Layout(); parentLayout != nil { if m := parentLayout.Margins(); m.HNear < 9 || m.HFar < 9 || m.VNear < 9 || m.VFar < 9 { parentLayout = nil } } } var affected bool if FocusEffect != nil { for _, wb := range s.children.items { if wb.window.(Widget).GraphicsEffects().Contains(FocusEffect) { affected = true break } } } if affected { var marginsNeeded bool for _, wb := range s.children.items { switch wb.window.(type) { case *splitterHandle, *TabWidget, Container: default: marginsNeeded = true break } } if marginsNeeded { margins = Margins{5, 5, 5, 5} } } if parentLayout != nil { parentLayout.SetMargins(Margins{9 - margins.HNear, 9 - margins.VNear, 9 - margins.HFar, 9 - margins.VFar}) } s.layout.SetMargins(margins) } func (s *Splitter) Persistent() bool { return s.persistent } func (s *Splitter) SetPersistent(value bool) { s.persistent = value } func (s *Splitter) SaveState() error { buf := bytes.NewBuffer(nil) count := s.children.Len() layout := s.Layout().(*splitterLayout) for i := 0; i < count; i += 2 { if i > 0 { buf.WriteString(" ") } item := layout.hwnd2Item[s.children.At(i).Handle()] size := item.oldExplicitSize if size == 0 { size = item.size } buf.WriteString(strconv.FormatInt(int64(size), 10)) } s.WriteState(buf.String()) for _, wb := range s.children.items { if persistable, ok := wb.window.(Persistable); ok { if err := persistable.SaveState(); err != nil { return err } } } return nil } func (s *Splitter) RestoreState() error { childCount := s.children.Len()/2 + 1 if childCount == 0 { return nil } state, err := s.ReadState() if err != nil { return err } if state == "" { return nil } sizeStrs := strings.Split(state, " ") // FIXME: Solve this in a better way. if len(sizeStrs) != childCount { log.Print("*Splitter.RestoreState: failed due to unexpected child count (FIXME!)") return nil } layout := s.layout.(*splitterLayout) s.SetSuspended(true) layout.suspended = true defer func() { layout.suspended = false s.SetSuspended(false) }() var space int size := s.ClientBoundsPixels().Size() if s.Orientation() == Horizontal { space = size.Width } else { space = size.Height } regularSpace := space - layout.spaceUnavailableToRegularWidgets() for i, wb := range s.children.items { widget := wb.window.(Widget) if i%2 == 0 { j := i/2 + i%2 s := sizeStrs[j] size, err := strconv.Atoi(s) if err != nil { // OK, we probably got old style settings which were stored as fractions. fraction, err := strconv.ParseFloat(s, 64) if err != nil { return err } size = int(float64(regularSpace) * fraction) } item := layout.hwnd2Item[widget.Handle()] item.size = size item.oldExplicitSize = size } } for _, wb := range s.children.items { if persistable, ok := wb.window.(Persistable); ok { if err := persistable.RestoreState(); err != nil { return err } } } return nil } func (s *Splitter) Fixed(widget Widget) bool { return s.layout.(*splitterLayout).Fixed(widget) } func (s *Splitter) SetFixed(widget Widget, fixed bool) error { item := s.layout.(*splitterLayout).hwnd2Item[widget.Handle()] if item == nil { return newError("unknown widget") } item.fixed = fixed if b := widget.BoundsPixels(); fixed && item.size == 0 && (b.Width == 0 || b.Height == 0) { b.Width, b.Height = 100, 100 widget.SetBoundsPixels(b) item.size = 100 } return nil } func (s *Splitter) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_WINDOWPOSCHANGED: wp := (*win.WINDOWPOS)(unsafe.Pointer(lParam)) if wp.Flags&win.SWP_NOSIZE != 0 { break } layout := s.layout.(*splitterLayout) layout.resetNeeded = false for _, item := range layout.hwnd2Item { item.oldExplicitSize = 0 } } return s.ContainerBase.WndProc(hwnd, msg, wParam, lParam) } func (s *Splitter) onInsertingWidget(index int, widget Widget) (err error) { return s.ContainerBase.onInsertingWidget(index, widget) } func (s *Splitter) onInsertedWidget(index int, widget Widget) (err error) { defer func() { if err != nil { return } s.updateMarginsForFocusEffect() }() _, isHandle := widget.(*splitterHandle) if isHandle { if s.Orientation() == Horizontal { widget.SetCursor(CursorSizeWE()) } else { widget.SetCursor(CursorSizeNS()) } } else { layout := s.Layout().(*splitterLayout) item := &splitterLayoutItem{stretchFactor: 1, wasVisible: true} layout.hwnd2Item[widget.Handle()] = item layout.resetNeeded = true if !layout.suspended && widget.AsWidgetBase().visible { s.RequestLayout() } item.visibleChangedHandle = widget.VisibleChanged().Attach(func() { if !layout.suspended && widget.AsWidgetBase().visible != item.wasVisible { layout.resetNeeded = true s.RequestLayout() } }) if s.children.Len()%2 == 0 { defer func() { if err != nil { return } var handle *splitterHandle handle, err = newSplitterHandle(s) if err != nil { return } closestVisibleWidget := func(offset, direction int) Widget { index := offset + direction for index >= 0 && index < len(s.children.items) { if wb := s.children.items[index]; wb.visible { return wb.window.(Widget) } index += direction } return nil } handleIndex := index + 1 - index%2 err = s.children.Insert(handleIndex, handle) if err == nil { // FIXME: These handlers will be leaked, if widgets get removed. handle.MouseDown().Attach(func(x, y int, button MouseButton) { if button != LeftButton { return } s.draggedHandle = handle s.mouseDownPos = Point{x, y} handle.SetBackground(splitterHandleDraggingBrush) }) handle.MouseMove().Attach(func(x, y int, button MouseButton) { if s.draggedHandle == nil { return } handleIndex := s.children.Index(s.draggedHandle) bh := s.draggedHandle.BoundsPixels() prev := closestVisibleWidget(handleIndex, -1) bp := prev.BoundsPixels() msep := minSizeEffective(createLayoutItemForWidget(prev)) next := closestVisibleWidget(handleIndex, 1) bn := next.BoundsPixels() msen := minSizeEffective(createLayoutItemForWidget(next)) dpi := s.draggedHandle.DPI() handleWidth := IntFrom96DPI(s.handleWidth, dpi) if s.Orientation() == Horizontal { xh := s.draggedHandle.XPixels() xnew := xh + x - s.mouseDownPos.X if xnew < bp.X+msep.Width { xnew = bp.X + msep.Width } else if xnew >= bn.X+bn.Width-msen.Width-handleWidth { xnew = bn.X + bn.Width - msen.Width - handleWidth } if e := s.draggedHandle.SetXPixels(xnew); e != nil { return } } else { yh := s.draggedHandle.YPixels() ynew := yh + y - s.mouseDownPos.Y if ynew < bp.Y+msep.Height { ynew = bp.Y + msep.Height } else if ynew >= bn.Y+bn.Height-msen.Height-handleWidth { ynew = bn.Y + bn.Height - msen.Height - handleWidth } if e := s.draggedHandle.SetYPixels(ynew); e != nil { return } } rc := bh.toRECT() if s.Orientation() == Horizontal { rc.Left -= int32(bp.X) rc.Right -= int32(bp.X) } else { rc.Top -= int32(bp.Y) rc.Bottom -= int32(bp.Y) } win.InvalidateRect(prev.Handle(), &rc, true) rc = bh.toRECT() if s.Orientation() == Horizontal { rc.Left -= int32(bn.X) rc.Right -= int32(bn.X) } else { rc.Top -= int32(bn.Y) rc.Bottom -= int32(bn.Y) } win.InvalidateRect(next.Handle(), &rc, true) s.draggedHandle.Invalidate() }) handle.MouseUp().Attach(func(x, y int, button MouseButton) { if s.draggedHandle == nil { return } defer s.RequestLayout() dragHandle := s.draggedHandle handleIndex := s.children.Index(dragHandle) prev := closestVisibleWidget(handleIndex, -1) next := closestVisibleWidget(handleIndex, 1) s.draggedHandle = nil dragHandle.SetBackground(NullBrush()) prev.AsWidgetBase().invalidateBorderInParent() next.AsWidgetBase().invalidateBorderInParent() prev.SetSuspended(true) defer prev.Invalidate() defer prev.SetSuspended(false) next.SetSuspended(true) defer next.Invalidate() defer next.SetSuspended(false) bh := dragHandle.BoundsPixels() bp := prev.BoundsPixels() bn := next.BoundsPixels() var sizePrev int var sizeNext int if s.Orientation() == Horizontal { bp.Width = bh.X - bp.X bn.Width -= (bh.X + bh.Width) - bn.X bn.X = bh.X + bh.Width sizePrev = bp.Width sizeNext = bn.Width } else { bp.Height = bh.Y - bp.Y bn.Height -= (bh.Y + bh.Height) - bn.Y bn.Y = bh.Y + bh.Height sizePrev = bp.Height sizeNext = bn.Height } layout := s.Layout().(*splitterLayout) prevItem := layout.hwnd2Item[prev.Handle()] prevItem.size = sizePrev prevItem.oldExplicitSize = sizePrev nextItem := layout.hwnd2Item[next.Handle()] nextItem.size = sizeNext nextItem.oldExplicitSize = sizeNext }) } }() } } return s.ContainerBase.onInsertedWidget(index, widget) } func (s *Splitter) onRemovingWidget(index int, widget Widget) (err error) { return s.ContainerBase.onRemovingWidget(index, widget) } func (s *Splitter) onRemovedWidget(index int, widget Widget) (err error) { defer func() { if err != nil { return } s.updateMarginsForFocusEffect() }() _, isHandle := widget.(*splitterHandle) if !s.removing && isHandle && s.children.Len()%2 == 1 { return newError("cannot remove splitter handle") } if !isHandle { sl := s.layout.(*splitterLayout) widget.AsWidgetBase().Property("Visible").Changed().Detach(sl.hwnd2Item[widget.Handle()].visibleChangedHandle) } if !isHandle && s.children.Len() > 1 { defer func() { if err != nil { return } var handleIndex int if index == 0 { handleIndex = 0 } else { handleIndex = index - 1 } s.removing = true handle := s.children.items[handleIndex].window.(*splitterHandle) if err = handle.SetParent(nil); err == nil { sl := s.layout.(*splitterLayout) for _, item := range sl.hwnd2Item { item.oldExplicitSize = 0 item.keepSize = false } sl.resetNeeded = true s.RequestLayout() handle.Dispose() } s.removing = false }() } err = s.ContainerBase.onRemovedWidget(index, widget) return } func (s *Splitter) onClearingWidgets() (err error) { panic("not implemented") } func (s *Splitter) onClearedWidgets() (err error) { panic("not implemented") } func (s *Splitter) CreateLayoutItem(ctx *LayoutContext) LayoutItem { return s.layout.CreateLayoutItem(ctx) } ================================================ FILE: splitterhandle.go ================================================ // Copyright 2011 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 ( "github.com/lxn/win" ) const splitterHandleWindowClass = `\o/ Walk_SplitterHandle_Class \o/` func init() { AppendToWalkInit(func() { MustRegisterWindowClass(splitterHandleWindowClass) }) } type splitterHandle struct { WidgetBase } func newSplitterHandle(splitter *Splitter) (*splitterHandle, error) { if splitter == nil { return nil, newError("splitter cannot be nil") } sh := new(splitterHandle) sh.parent = splitter if err := InitWindow( sh, splitter, splitterHandleWindowClass, win.WS_CHILD|win.WS_VISIBLE, 0); err != nil { return nil, err } sh.SetBackground(NullBrush()) if err := sh.setAndClearStyleBits(0, win.WS_CLIPSIBLINGS); err != nil { return nil, err } return sh, nil } func (sh *splitterHandle) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_ERASEBKGND: if sh.Background() == nullBrushSingleton { return 1 } case win.WM_PAINT: if sh.Background() == nullBrushSingleton { var ps win.PAINTSTRUCT win.BeginPaint(hwnd, &ps) defer win.EndPaint(hwnd, &ps) return 0 } } return sh.WidgetBase.WndProc(hwnd, msg, wParam, lParam) } func (sh *splitterHandle) CreateLayoutItem(ctx *LayoutContext) LayoutItem { var orientation Orientation var handleWidth int if splitter, ok := sh.Parent().(*Splitter); ok { orientation = splitter.Orientation() handleWidth = splitter.HandleWidth() } return &splitterHandleLayoutItem{ orientation: orientation, handleWidth: handleWidth, } } type splitterHandleLayoutItem struct { LayoutItemBase orientation Orientation handleWidth int } func (li *splitterHandleLayoutItem) LayoutFlags() LayoutFlags { if li.orientation == Horizontal { return ShrinkableVert | GrowableVert | GreedyVert } return ShrinkableHorz | GrowableHorz | GreedyHorz } func (li *splitterHandleLayoutItem) IdealSize() Size { var size Size dpi := int(win.GetDpiForWindow(li.handle)) if li.orientation == Horizontal { size.Width = IntFrom96DPI(li.handleWidth, dpi) } else { size.Height = IntFrom96DPI(li.handleWidth, dpi) } return size } func (li *splitterHandleLayoutItem) MinSize() Size { return li.IdealSize() } ================================================ FILE: splitterlayout.go ================================================ // 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 ( "sort" "github.com/lxn/win" ) type splitterLayout struct { container Container orientation Orientation margins96dpi Margins hwnd2Item map[win.HWND]*splitterLayoutItem resetNeeded bool suspended bool } type splitterLayoutItem struct { size int // in native pixels oldExplicitSize int // in native pixels stretchFactor int growth int visibleChangedHandle int fixed bool keepSize bool wasVisible bool } func newSplitterLayout(orientation Orientation) *splitterLayout { return &splitterLayout{ orientation: orientation, hwnd2Item: make(map[win.HWND]*splitterLayoutItem), } } func (l *splitterLayout) asLayoutBase() *LayoutBase { return nil } func (l *splitterLayout) Container() Container { return l.container } func (l *splitterLayout) SetContainer(value Container) { if value != l.container { if l.container != nil { l.container.SetLayout(nil) } l.container = value if value != nil && value.Layout() != Layout(l) { value.SetLayout(l) l.container.RequestLayout() } } } func (l *splitterLayout) Margins() Margins { return l.margins96dpi } func (l *splitterLayout) SetMargins(value Margins) error { l.margins96dpi = value l.container.RequestLayout() return nil } func (l *splitterLayout) Spacing() int { return l.container.(*Splitter).handleWidth } func (l *splitterLayout) SetSpacing(value int) error { return newError("not supported") } func (l *splitterLayout) Orientation() Orientation { return l.orientation } func (l *splitterLayout) SetOrientation(value Orientation) error { if value != l.orientation { switch value { case Horizontal, Vertical: default: return newError("invalid Orientation value") } l.orientation = value l.container.RequestLayout() } return nil } func (l *splitterLayout) Fixed(widget Widget) bool { item := l.hwnd2Item[widget.Handle()] return item != nil && item.fixed } func (l *splitterLayout) StretchFactor(widget Widget) int { item := l.hwnd2Item[widget.Handle()] if item == nil || item.stretchFactor == 0 { return 1 } return item.stretchFactor } func (l *splitterLayout) SetStretchFactor(widget Widget, factor int) error { if factor != l.StretchFactor(widget) { if factor < 1 { return newError("factor must be >= 1") } if l.container == nil { return newError("container required") } item := l.hwnd2Item[widget.Handle()] if item == nil { item = new(splitterLayoutItem) l.hwnd2Item[widget.Handle()] = item } item.stretchFactor = factor l.container.RequestLayout() } return nil } func (l *splitterLayout) anyNonFixed() bool { for i, widget := range l.container.Children().items { if i%2 == 0 && widget.visible && !l.Fixed(widget.window.(Widget)) { return true } } return false } // spaceUnavailableToRegularWidgets returns amount of space unavailable to regular widgets in native pixels. func (l *splitterLayout) spaceUnavailableToRegularWidgets() int { splitter := l.container.(*Splitter) var space int for _, widget := range l.container.Children().items { if _, isHandle := widget.window.(*splitterHandle); isHandle && widget.visible { space += splitter.handleWidth } } return IntFrom96DPI(space, splitter.DPI()) } func (l *splitterLayout) CreateLayoutItem(ctx *LayoutContext) ContainerLayoutItem { splitter := l.container.(*Splitter) hwnd2Item := make(map[win.HWND]*splitterLayoutItem, len(l.hwnd2Item)) for hwnd, sli := range l.hwnd2Item { hwnd2Item[hwnd] = sli } li := &splitterContainerLayoutItem{ orientation: l.orientation, hwnd2Item: hwnd2Item, spaceUnavailableToRegularItems: l.spaceUnavailableToRegularWidgets(), handleWidth96dpi: splitter.HandleWidth(), anyNonFixed: l.anyNonFixed(), resetNeeded: l.resetNeeded, } li.margins96dpi = l.margins96dpi return li } type splitterContainerLayoutItem struct { ContainerLayoutItemBase orientation Orientation hwnd2Item map[win.HWND]*splitterLayoutItem spaceUnavailableToRegularItems int // in native pixels handleWidth96dpi int anyNonFixed bool resetNeeded bool } func (li *splitterContainerLayoutItem) StretchFactor(item LayoutItem) int { sli := li.hwnd2Item[item.Handle()] if sli == nil || sli.stretchFactor == 0 { return 1 } return sli.stretchFactor } func (li *splitterContainerLayoutItem) LayoutFlags() LayoutFlags { return boxLayoutFlags(li.orientation, li.children) } func (li *splitterContainerLayoutItem) MinSize() Size { return li.MinSizeForSize(li.geometry.ClientSize) } func (li *splitterContainerLayoutItem) HeightForWidth(width int) int { return li.MinSizeForSize(Size{width, li.geometry.ClientSize.Height}).Height } func (li *splitterContainerLayoutItem) MinSizeForSize(size Size) Size { marginsPixels := MarginsFrom96DPI(li.margins96dpi, li.ctx.dpi) margins := Size{marginsPixels.HNear + marginsPixels.HFar, marginsPixels.VNear + marginsPixels.VFar} s := margins for _, item := range li.children { if !anyVisibleItemInHierarchy(item) { continue } var cur Size if sli, ok := li.hwnd2Item[item.Handle()]; ok && li.anyNonFixed && sli.fixed { cur = item.Geometry().Size if li.orientation == Horizontal { cur.Height = 0 } else { cur.Width = 0 } } else { cur = li.MinSizeEffectiveForChild(item) } if li.orientation == Horizontal { s.Width += cur.Width s.Height = maxi(s.Height, margins.Height+cur.Height) } else { s.Height += cur.Height s.Width = maxi(s.Width, margins.Width+cur.Width) } } return s } func (li *splitterContainerLayoutItem) PerformLayout() []LayoutResultItem { if li.resetNeeded { li.reset() } margins := MarginsFrom96DPI(li.margins96dpi, li.ctx.dpi) handleWidthPixels := IntFrom96DPI(li.handleWidth96dpi, li.ctx.dpi) sizes := make([]int, len(li.children)) cb := Rectangle{Width: li.geometry.ClientSize.Width, Height: li.geometry.ClientSize.Height} cb.X += margins.HNear cb.Y += margins.HFar cb.Width -= margins.HNear + margins.HFar cb.Height -= margins.VNear + margins.VFar var space1, space2 int if li.orientation == Horizontal { space1 = cb.Width - li.spaceUnavailableToRegularItems space2 = cb.Height } else { space1 = cb.Height - li.spaceUnavailableToRegularItems space2 = cb.Width } type WidgetItem struct { item *splitterLayoutItem index int min int // in native pixels max int // in native pixels shrinkable bool growable bool } var wis []WidgetItem anyNonFixed := li.anyNonFixed var totalRegularSize int for i, item := range li.children { if !anyVisibleItemInHierarchy(item) { continue } if i%2 == 0 { slItem := li.hwnd2Item[item.Handle()] var wi *WidgetItem if !anyNonFixed || !slItem.fixed { var min, max int minSize := li.MinSizeEffectiveForChild(item) maxSize := item.Geometry().MaxSize if li.orientation == Horizontal { min = minSize.Width max = maxSize.Width } else { min = minSize.Height max = maxSize.Height } wis = append(wis, WidgetItem{item: slItem, index: i, min: min, max: max}) wi = &wis[len(wis)-1] } size := slItem.size var idealSize Size if hfw, ok := item.(HeightForWidther); ok && li.orientation == Vertical && hfw.HasHeightForWidth() { idealSize.Height = hfw.HeightForWidth(space2) } else { switch sizer := item.(type) { case IdealSizer: idealSize = sizer.IdealSize() case MinSizer: idealSize = sizer.MinSize() } } if flags := item.LayoutFlags(); li.orientation == Horizontal { if flags&ShrinkableHorz == 0 { size = maxi(size, idealSize.Width) if wi != nil { wi.min = maxi(wi.min, size) } } else if wi != nil { wi.shrinkable = true } if flags&GrowableHorz == 0 { size = mini(size, idealSize.Width) if wi != nil { wi.max = mini(wi.max, size) } } else if wi != nil { wi.growable = true } } else { if flags&ShrinkableVert == 0 { size = maxi(size, idealSize.Height) if wi != nil { wi.min = maxi(wi.min, size) } } else if wi != nil { wi.shrinkable = true } if flags&GrowableVert == 0 { size = mini(size, idealSize.Height) if wi != nil { wi.max = mini(wi.max, size) } } else if wi != nil { wi.growable = true } } totalRegularSize += size sizes[i] = size } else { sizes[i] = handleWidthPixels } } var resultItems []LayoutResultItem diff := space1 - totalRegularSize if diff != 0 && len(sizes) > 1 { for diff != 0 { sort.SliceStable(wis, func(i, j int) bool { a := wis[i] b := wis[j] x := float64(a.item.growth) / float64(a.item.stretchFactor) y := float64(b.item.growth) / float64(b.item.stretchFactor) if diff > 0 { return x < y && (a.max == 0 || a.max > a.item.size) } else { return x > y && a.min < a.item.size } }) var wi *WidgetItem for _, wItem := range wis { if !wItem.item.keepSize && (diff < 0 && wItem.item.size > wItem.min || diff > 0 && (wItem.item.size < wItem.max || wItem.max == 0)) { wi = &wItem break } } if wi == nil { break } if diff > 0 { sizes[wi.index]++ wi.item.size++ wi.item.growth++ diff-- } else { sizes[wi.index]-- wi.item.size-- wi.item.growth-- diff++ } } } var p1 int if li.orientation == Horizontal { p1 = margins.HNear } else { p1 = margins.VNear } for i, item := range li.children { if !anyVisibleItemInHierarchy(item) { continue } s1 := sizes[i] var x, y, w, h int if li.orientation == Horizontal { x, y, w, h = p1, margins.VNear, s1, space2 } else { x, y, w, h = margins.HNear, p1, space2, s1 } resultItems = append(resultItems, LayoutResultItem{Item: item, Bounds: Rectangle{x, y, w, h}}) p1 += s1 } return resultItems } func (li *splitterContainerLayoutItem) reset() { var anyVisible bool for i, item := range li.children { sli := li.hwnd2Item[item.Handle()] visible := anyVisibleItemInHierarchy(item) if !anyVisible && visible { anyVisible = true } if sli == nil || visible == sli.wasVisible { continue } sli.wasVisible = visible if _, isHandle := item.(*splitterHandleLayoutItem); !isHandle { var handleIndex int if i == 0 { if len(li.children) > 1 { handleIndex = 1 } else { handleIndex = -1 } } else { handleIndex = i - 1 } if handleIndex > -1 { li.children[handleIndex].AsLayoutItemBase().visible = visible } } } if li.Visible() != anyVisible { li.AsLayoutItemBase().visible = anyVisible } minSizes := make([]int, len(li.children)) var minSizesTotal int for i, item := range li.children { if i%2 == 1 || !anyVisibleItemInHierarchy(item) { continue } min := li.MinSizeEffectiveForChild(item) if li.orientation == Horizontal { minSizes[i] = min.Width minSizesTotal += min.Width } else { minSizes[i] = min.Height minSizesTotal += min.Height } } var regularSpace int if li.orientation == Horizontal { regularSpace = li.Geometry().ClientSize.Width - li.spaceUnavailableToRegularItems } else { regularSpace = li.Geometry().ClientSize.Height - li.spaceUnavailableToRegularItems } stretchTotal := 0 for i, item := range li.children { if i%2 == 1 || !anyVisibleItemInHierarchy(item) { continue } if sli := li.hwnd2Item[item.Handle()]; sli == nil { li.hwnd2Item[item.Handle()] = &splitterLayoutItem{stretchFactor: 1} } stretchTotal += li.StretchFactor(item) } for i, item := range li.children { if i%2 == 1 || !anyVisibleItemInHierarchy(item) { continue } sli := li.hwnd2Item[item.Handle()] sli.growth = 0 sli.keepSize = false if sli.oldExplicitSize > 0 { sli.size = sli.oldExplicitSize } else { sli.size = int(float64(li.StretchFactor(item)) / float64(stretchTotal) * float64(regularSpace)) } min := minSizes[i] if minSizesTotal <= regularSpace { if sli.size < min { sli.size = min } } if sli.size >= min { flags := item.LayoutFlags() if li.orientation == Horizontal && flags&GrowableHorz == 0 || li.orientation == Vertical && flags&GrowableVert == 0 { sli.size = min sli.keepSize = true } } } } ================================================ FILE: static.go ================================================ // Copyright 2018 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" "github.com/lxn/win" ) const staticWindowClass = `\o/ Walk_Static_Class \o/` var staticWndProcPtr uintptr func init() { AppendToWalkInit(func() { MustRegisterWindowClass(staticWindowClass) staticWndProcPtr = syscall.NewCallback(staticWndProc) }) } type static struct { WidgetBase hwndStatic win.HWND origStaticWndProcPtr uintptr textAlignment Alignment2D textColor Color } func (s *static) init(widget Widget, parent Container, style uint32) error { if err := InitWidget( widget, parent, staticWindowClass, win.WS_VISIBLE|(style&win.WS_BORDER), win.WS_EX_CONTROLPARENT); err != nil { return err } if s.hwndStatic = win.CreateWindowEx( 0, syscall.StringToUTF16Ptr("static"), nil, win.WS_CHILD|win.WS_CLIPSIBLINGS|win.WS_VISIBLE|win.SS_LEFT|win.SS_NOTIFY|(style&^win.WS_BORDER), win.CW_USEDEFAULT, win.CW_USEDEFAULT, win.CW_USEDEFAULT, win.CW_USEDEFAULT, s.hWnd, 0, 0, nil, ); s.hwndStatic == 0 { return newError("creating static failed") } if err := s.group.toolTip.AddTool(s); err != nil { return err } s.origStaticWndProcPtr = win.SetWindowLongPtr(s.hwndStatic, win.GWLP_WNDPROC, staticWndProcPtr) if s.origStaticWndProcPtr == 0 { return lastError("SetWindowLongPtr") } s.applyFont(s.Font()) s.SetBackground(nullBrushSingleton) s.SetAlignment(AlignHNearVCenter) return nil } func (s *static) Dispose() { if s.hwndStatic != 0 { win.DestroyWindow(s.hwndStatic) s.hwndStatic = 0 } s.WidgetBase.Dispose() } func (s *static) handleForToolTip() win.HWND { return s.hwndStatic } func (s *static) applyEnabled(enabled bool) { s.WidgetBase.applyEnabled(enabled) setWindowEnabled(s.hwndStatic, enabled) } func (s *static) applyFont(font *Font) { s.WidgetBase.applyFont(font) SetWindowFont(s.hwndStatic, font) } func (s *static) textAlignment1D() Alignment1D { switch s.textAlignment { case AlignHCenterVNear, AlignHCenterVCenter, AlignHCenterVFar: return AlignCenter case AlignHFarVNear, AlignHFarVCenter, AlignHFarVFar: return AlignFar default: return AlignNear } } func (s *static) setTextAlignment1D(alignment Alignment1D) error { var align Alignment2D switch alignment { case AlignCenter: align = AlignHCenterVCenter case AlignFar: align = AlignHFarVCenter default: align = AlignHNearVCenter } return s.setTextAlignment(align) } func (s *static) setTextAlignment(alignment Alignment2D) error { if alignment == s.textAlignment { return nil } var styleBit uint32 switch alignment { case AlignHNearVNear, AlignHNearVCenter, AlignHNearVFar: styleBit |= win.SS_LEFT case AlignHCenterVNear, AlignHCenterVCenter, AlignHCenterVFar: styleBit |= win.SS_CENTER case AlignHFarVNear, AlignHFarVCenter, AlignHFarVFar: styleBit |= win.SS_RIGHT } if err := setAndClearWindowLongBits(s.hwndStatic, win.GWL_STYLE, styleBit, win.SS_LEFT|win.SS_CENTER|win.SS_RIGHT); err != nil { return err } s.textAlignment = alignment s.Invalidate() return nil } func (s *static) setText(text string) (changed bool, err error) { if text == s.text() { return false, nil } if err := s.WidgetBase.setText(text); err != nil { return false, err } if err := setWindowText(s.hwndStatic, text); err != nil { return false, err } s.RequestLayout() return true, nil } func (s *static) TextColor() Color { return s.textColor } func (s *static) SetTextColor(c Color) { s.textColor = c s.Invalidate() } func (s *static) shrinkable() bool { if em, ok := s.window.(interface{ EllipsisMode() EllipsisMode }); ok { return em.EllipsisMode() != EllipsisNone } return false } func (s *static) updateStaticBounds() { var format DrawTextFormat switch s.textAlignment { case AlignHNearVNear, AlignHNearVCenter, AlignHNearVFar: format |= TextLeft case AlignHCenterVNear, AlignHCenterVCenter, AlignHCenterVFar: format |= TextCenter case AlignHFarVNear, AlignHFarVCenter, AlignHFarVFar: format |= TextRight } switch s.textAlignment { case AlignHNearVNear, AlignHCenterVNear, AlignHFarVNear: format |= TextTop case AlignHNearVCenter, AlignHCenterVCenter, AlignHFarVCenter: format |= TextVCenter case AlignHNearVFar, AlignHCenterVFar, AlignHFarVFar: format |= TextBottom } cb := s.ClientBoundsPixels() if shrinkable := s.shrinkable(); shrinkable || format&TextVCenter != 0 || format&TextBottom != 0 { var size Size if _, ok := s.window.(HeightForWidther); ok { size = s.calculateTextSizeForWidth(cb.Width) } else { size = s.calculateTextSize() } if shrinkable { var text string if size.Width > cb.Width { text = s.text() } s.SetToolTipText(text) } if format&TextVCenter != 0 || format&TextBottom != 0 { if format&TextVCenter != 0 { cb.Y += (cb.Height - size.Height) / 2 } else { cb.Y += cb.Height - size.Height } cb.Height = size.Height } } win.MoveWindow(s.hwndStatic, int32(cb.X), int32(cb.Y), int32(cb.Width), int32(cb.Height), true) s.Invalidate() } func (s *static) WndProc(hwnd win.HWND, msg uint32, wp, lp uintptr) uintptr { switch msg { case win.WM_CTLCOLORSTATIC: if hBrush := s.handleWMCTLCOLOR(wp, uintptr(s.hWnd)); hBrush != 0 { return hBrush } case win.WM_WINDOWPOSCHANGED: wp := (*win.WINDOWPOS)(unsafe.Pointer(lp)) if wp.Flags&win.SWP_NOSIZE != 0 { break } s.updateStaticBounds() } return s.WidgetBase.WndProc(hwnd, msg, wp, lp) } func staticWndProc(hwnd win.HWND, msg uint32, wp, lp uintptr) uintptr { as, ok := windowFromHandle(win.GetParent(hwnd)).(interface{ asStatic() *static }) if !ok { return 0 } s := as.asStatic() switch msg { case win.WM_NCHITTEST: return win.HTCLIENT case win.WM_MOUSEMOVE, win.WM_LBUTTONDOWN, win.WM_LBUTTONUP, win.WM_MBUTTONDOWN, win.WM_MBUTTONUP, win.WM_RBUTTONDOWN, win.WM_RBUTTONUP: m := win.MSG{ HWnd: hwnd, Message: msg, WParam: wp, LParam: lp, Pt: win.POINT{int32(win.GET_X_LPARAM(lp)), int32(win.GET_Y_LPARAM(lp))}, } return s.group.toolTip.SendMessage(win.TTM_RELAYEVENT, 0, uintptr(unsafe.Pointer(&m))) } return win.CallWindowProc(s.origStaticWndProcPtr, hwnd, msg, wp, lp) } func (s *static) CreateLayoutItem(ctx *LayoutContext) LayoutItem { var layoutFlags LayoutFlags if s.textAlignment1D() != AlignNear { layoutFlags = GrowableHorz } else if s.shrinkable() { layoutFlags = ShrinkableHorz } idealSize := s.calculateTextSize() if s.hasStyleBits(win.WS_BORDER) { border := s.IntFrom96DPI(1) * 2 idealSize.Width += border idealSize.Height += border * 2 } return &staticLayoutItem{ layoutFlags: layoutFlags, idealSize: idealSize, } } type staticLayoutItem struct { LayoutItemBase layoutFlags LayoutFlags idealSize Size // in native pixels } func (li *staticLayoutItem) LayoutFlags() LayoutFlags { return li.layoutFlags } func (li *staticLayoutItem) IdealSize() Size { return li.idealSize } func (li *staticLayoutItem) MinSize() Size { if li.layoutFlags&ShrinkableHorz != 0 { return Size{Height: li.idealSize.Height} } return li.idealSize } ================================================ FILE: statusbar.go ================================================ // Copyright 2013 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" ) // StatusBar is a widget that displays status messages. type StatusBar struct { WidgetBase items *StatusBarItemList } // NewStatusBar returns a new StatusBar as child of container parent. func NewStatusBar(parent Container) (*StatusBar, error) { sb := new(StatusBar) if err := InitWidget( sb, parent, "msctls_statusbar32", win.SBARS_SIZEGRIP|win.SBARS_TOOLTIPS, 0); err != nil { return nil, err } sb.items = newStatusBarItemList(sb) return sb, nil } // Items returns the list of items in the StatusBar. func (sb *StatusBar) Items() *StatusBarItemList { return sb.items } // SetVisible sets whether the StatusBar is visible. func (sb *StatusBar) SetVisible(visible bool) { sb.WidgetBase.SetVisible(visible) sb.RequestLayout() } func (sb *StatusBar) ApplyDPI(dpi int) { sb.WidgetBase.ApplyDPI(dpi) sb.update() } func (sb *StatusBar) update() error { if err := sb.updateParts(); err != nil { return err } for i, item := range sb.items.items { if err := item.update(i); err != nil { return err } } sb.SetVisible(sb.items.Len() > 0) return nil } func (sb *StatusBar) updateParts() error { items := sb.items.items dpi := sb.DPI() rightEdges := make([]int32, len(items)) var right int32 for i, item := range items { right += int32(IntFrom96DPI(item.width, dpi)) rightEdges[i] = right } var rep *int32 if len(rightEdges) > 0 { rep = &rightEdges[0] } if len(rightEdges) == 1 { rightEdges[0] = -1 } if 0 == sb.SendMessage( win.SB_SETPARTS, uintptr(len(items)), uintptr(unsafe.Pointer(rep))) { return newError("SB_SETPARTS") } return nil } func (sb *StatusBar) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_NOTIFY: nmhdr := (*win.NMHDR)(unsafe.Pointer(lParam)) switch nmhdr.Code { case win.NM_CLICK: lpnm := (*win.NMMOUSE)(unsafe.Pointer(lParam)) if n := int(lpnm.DwItemSpec); n >= 0 && n < sb.items.Len() { sb.items.At(n).raiseClicked() } } } return sb.WidgetBase.WndProc(hwnd, msg, wParam, lParam) } func (*StatusBar) CreateLayoutItem(ctx *LayoutContext) LayoutItem { return new(statusBarLayoutItem) } type statusBarLayoutItem struct { LayoutItemBase } func (*statusBarLayoutItem) LayoutFlags() LayoutFlags { return 0 } func (*statusBarLayoutItem) IdealSize() Size { return Size{} } // StatusBarItem represents a section of a StatusBar that can have its own icon, // text, tool tip text and width. type StatusBarItem struct { sb *StatusBar icon *Icon text string toolTipText string width int clickedPublisher EventPublisher } // NewStatusBarItem returns a new StatusBarItem. func NewStatusBarItem() *StatusBarItem { return &StatusBarItem{width: 100} } // Icon returns the Icon of the StatusBarItem. func (sbi *StatusBarItem) Icon() *Icon { return sbi.icon } // SetIcon sets the Icon of the StatusBarItem. func (sbi *StatusBarItem) SetIcon(icon *Icon) error { if icon == sbi.icon { return nil } old := sbi.icon sbi.icon = icon return sbi.maybeTry(sbi.updateIcon, func() { sbi.icon = old }) } // Text returns the text of the StatusBarItem. func (sbi *StatusBarItem) Text() string { return sbi.text } // SetText sets the text of the StatusBarItem. func (sbi *StatusBarItem) SetText(text string) error { if text == sbi.text { return nil } old := sbi.text sbi.text = text return sbi.maybeTry(sbi.updateText, func() { sbi.text = old }) } // ToolTipText returns the tool tip text of the StatusBarItem. func (sbi *StatusBarItem) ToolTipText() string { return sbi.toolTipText } // SetToolTipText sets the tool tip text of the StatusBarItem. func (sbi *StatusBarItem) SetToolTipText(toolTipText string) error { if toolTipText == sbi.toolTipText { return nil } old := sbi.toolTipText sbi.toolTipText = toolTipText return sbi.maybeTry(sbi.updateToolTipText, func() { sbi.toolTipText = old }) } // Width returns the width of the StatusBarItem. func (sbi *StatusBarItem) Width() int { return sbi.width } // SetWidth sets the width of the StatusBarItem. func (sbi *StatusBarItem) SetWidth(width int) error { if width == sbi.width { return nil } old := sbi.width sbi.width = width if sbi.sb != nil { succeeded := false defer func() { if !succeeded { sbi.width = old } }() if err := sbi.sb.updateParts(); err != nil { return err } succeeded = true } return nil } func (sbi *StatusBarItem) Clicked() *Event { return sbi.clickedPublisher.Event() } func (sbi *StatusBarItem) raiseClicked() { sbi.clickedPublisher.Publish() } func (sbi *StatusBarItem) maybeTry(f func(index int) error, rollback func()) error { if sbi.sb != nil { succeeded := false defer func() { if !succeeded { rollback() } }() if err := f(sbi.sb.items.Index(sbi)); err != nil { return err } succeeded = true } return nil } func (sbi *StatusBarItem) update(index int) error { if err := sbi.updateIcon(index); err != nil { return err } if err := sbi.updateText(index); err != nil { return err } if err := sbi.updateToolTipText(index); err != nil { return err } return nil } func (sbi *StatusBarItem) updateIcon(index int) error { var hIcon win.HICON if sbi.icon != nil { hIcon = sbi.icon.handleForDPI(sbi.sb.DPI()) } if 0 == sbi.sb.SendMessage( win.SB_SETICON, uintptr(index), uintptr(hIcon)) { return newError("SB_SETICON") } return nil } func (sbi *StatusBarItem) updateText(index int) error { utf16, err := syscall.UTF16PtrFromString(sbi.text) if err != nil { return err } if 0 == sbi.sb.SendMessage( win.SB_SETTEXT, uintptr(win.MAKEWORD(byte(index), 0)), uintptr(unsafe.Pointer(utf16))) { return newError("SB_SETTEXT") } return nil } func (sbi *StatusBarItem) updateToolTipText(index int) error { utf16, err := syscall.UTF16PtrFromString(sbi.toolTipText) if err != nil { return err } sbi.sb.SendMessage( win.SB_SETTIPTEXT, uintptr(index), uintptr(unsafe.Pointer(utf16))) return nil } type StatusBarItemList struct { sb *StatusBar items []*StatusBarItem } func newStatusBarItemList(statusBar *StatusBar) *StatusBarItemList { return &StatusBarItemList{sb: statusBar} } func (l *StatusBarItemList) Add(item *StatusBarItem) error { return l.Insert(len(l.items), item) } func (l *StatusBarItemList) At(index int) *StatusBarItem { return l.items[index] } func (l *StatusBarItemList) Clear() error { old := l.items l.items = l.items[:0] succeeded := false defer func() { if !succeeded { l.items = old l.sb.update() } }() if err := l.sb.update(); err != nil { return err } succeeded = true return nil } func (l *StatusBarItemList) Index(item *StatusBarItem) int { for i, it := range l.items { if it == item { return i } } return -1 } func (l *StatusBarItemList) Contains(item *StatusBarItem) bool { return l.Index(item) > -1 } func (l *StatusBarItemList) Insert(index int, item *StatusBarItem) error { if item.sb != nil { return newError("item already contained in a StatusBar") } l.items = append(l.items, nil) copy(l.items[index+1:], l.items[index:]) l.items[index] = item item.sb = l.sb succeeded := false defer func() { if !succeeded { item.sb = nil l.items = append(l.items[:index], l.items[index+1:]...) l.sb.update() } }() if err := l.sb.update(); err != nil { return err } succeeded = true return nil } func (l *StatusBarItemList) Len() int { return len(l.items) } func (l *StatusBarItemList) Remove(item *StatusBarItem) error { index := l.Index(item) if index == -1 { return nil } return l.RemoveAt(index) } func (l *StatusBarItemList) RemoveAt(index int) error { item := l.items[index] item.sb = nil l.items = append(l.items[:index], l.items[index+1:]...) succeeded := false defer func() { if !succeeded { l.items = append(l.items, nil) copy(l.items[index+1:], l.items[index:]) l.items[index] = item item.sb = l.sb l.sb.update() } }() if err := l.sb.update(); err != nil { return err } succeeded = true return nil } ================================================ FILE: stopwatch.go ================================================ // Copyright 2019 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 ( "bytes" "fmt" "sort" "sync" "text/tabwriter" "time" ) type stopwatchItem struct { stopwatchStats subject string startedTime time.Time } type stopwatchStats struct { count int64 min time.Duration max time.Duration total time.Duration } func (sws *stopwatchStats) Average() time.Duration { if sws.count == 0 { return 0 } return time.Nanosecond * time.Duration(sws.total.Nanoseconds()/sws.count) } type stopwatch struct { mutex sync.Mutex subject2item map[string]*stopwatchItem } func newStopwatch() *stopwatch { return &stopwatch{ subject2item: make(map[string]*stopwatchItem), } } func (sw *stopwatch) Start(subject string) time.Time { sw.mutex.Lock() defer sw.mutex.Unlock() item, ok := sw.subject2item[subject] if !ok { item = &stopwatchItem{subject: subject} sw.subject2item[subject] = item } item.startedTime = time.Now() return item.startedTime } func (sw *stopwatch) Stop(subject string) time.Duration { sw.mutex.Lock() defer sw.mutex.Unlock() item, ok := sw.subject2item[subject] if !ok || item.startedTime.IsZero() { return 0 } duration := time.Now().Sub(item.startedTime) item.count++ if duration < item.min || item.min == 0 { item.min = duration } if duration > item.max { item.max = duration } item.total += duration item.startedTime = time.Time{} return duration } func (sw *stopwatch) Cancel(subject string) { sw.mutex.Lock() defer sw.mutex.Unlock() item, ok := sw.subject2item[subject] if !ok { return } item.startedTime = time.Time{} } func (sw *stopwatch) Clear() { sw.mutex.Lock() defer sw.mutex.Unlock() for key := range sw.subject2item { delete(sw.subject2item, key) } } func (sw *stopwatch) Print() { sw.mutex.Lock() items := make([]*stopwatchItem, 0, len(sw.subject2item)) for _, item := range sw.subject2item { items = append(items, item) } sw.mutex.Unlock() sort.Slice(items, func(i, j int) bool { return items[i].total > items[j].total }) var buf bytes.Buffer writer := tabwriter.NewWriter(&buf, 0, 8, 2, ' ', tabwriter.AlignRight) fmt.Fprintln(writer, "#\tSubject\tAverage\tTotal\tMin\tMax\t\tCount") for i, item := range items { fmt.Fprintf(writer, "%d\t%s\t%s\t%s\t%s\t%s\t\t%d\n", i+1, item.subject, item.Average(), item.total, item.min, item.max, item.count) } writer.Flush() fmt.Print(buf.String()) } ================================================ FILE: stringevent.go ================================================ // Copyright 2011 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 type stringEventHandlerInfo struct { handler StringEventHandler once bool } type StringEventHandler func(s string) type StringEvent struct { handlers []stringEventHandlerInfo } func (e *StringEvent) Attach(handler StringEventHandler) int { handlerInfo := stringEventHandlerInfo{handler, false} for i, h := range e.handlers { if h.handler == nil { e.handlers[i] = handlerInfo return i } } e.handlers = append(e.handlers, handlerInfo) return len(e.handlers) - 1 } func (e *StringEvent) Detach(handle int) { e.handlers[handle].handler = nil } func (e *StringEvent) Once(handler StringEventHandler) { i := e.Attach(handler) e.handlers[i].once = true } type StringEventPublisher struct { event StringEvent } func (p *StringEventPublisher) Event() *StringEvent { return &p.event } func (p *StringEventPublisher) Publish(s string) { for i, h := range p.event.handlers { if h.handler != nil { h.handler(s) if h.once { p.event.Detach(i) } } } } ================================================ FILE: tableview.go ================================================ // Copyright 2011 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 ( "encoding/json" "fmt" "math/big" "reflect" "syscall" "time" "unsafe" "github.com/lxn/win" ) const tableViewWindowClass = `\o/ Walk_TableView_Class \o/` var ( white = win.COLORREF(RGB(255, 255, 255)) checkmark = string([]byte{0xE2, 0x9C, 0x94}) tableViewFrozenLVWndProcPtr uintptr tableViewNormalLVWndProcPtr uintptr tableViewHdrWndProcPtr uintptr ) func init() { AppendToWalkInit(func() { MustRegisterWindowClass(tableViewWindowClass) tableViewFrozenLVWndProcPtr = syscall.NewCallback(tableViewFrozenLVWndProc) tableViewNormalLVWndProcPtr = syscall.NewCallback(tableViewNormalLVWndProc) tableViewHdrWndProcPtr = syscall.NewCallback(tableViewHdrWndProc) }) } const ( tableViewCurrentIndexChangedTimerId = 1 + iota tableViewSelectedIndexesChangedTimerId ) type TableViewCfg struct { Style uint32 CustomHeaderHeight int // in native pixels? CustomRowHeight int // in native pixels? } // TableView is a model based widget for record centric, tabular data. // // TableView is implemented as a virtual mode list view to support quite large // amounts of data. type TableView struct { WidgetBase hwndFrozenLV win.HWND hwndFrozenHdr win.HWND frozenLVOrigWndProcPtr uintptr frozenHdrOrigWndProcPtr uintptr hwndNormalLV win.HWND hwndNormalHdr win.HWND normalLVOrigWndProcPtr uintptr normalHdrOrigWndProcPtr uintptr state *tableViewState columns *TableViewColumnList model TableModel providedModel interface{} itemChecker ItemChecker imageProvider ImageProvider styler CellStyler style CellStyle itemFont *Font hIml win.HIMAGELIST usingSysIml bool imageUintptr2Index map[uintptr]int32 filePath2IconIndex map[string]int32 rowsResetHandlerHandle int rowChangedHandlerHandle int rowsChangedHandlerHandle int rowsInsertedHandlerHandle int rowsRemovedHandlerHandle int sortChangedHandlerHandle int selectedIndexes []int prevIndex int currentIndex int itemIndexOfLastMouseButtonDown int hwndItemChanged win.HWND currentIndexChangedPublisher EventPublisher selectedIndexesChangedPublisher EventPublisher itemActivatedPublisher EventPublisher columnClickedPublisher IntEventPublisher columnsOrderableChangedPublisher EventPublisher columnsSizableChangedPublisher EventPublisher itemCountChangedPublisher EventPublisher publishNextSelClear bool inSetSelectedIndexes bool lastColumnStretched bool persistent bool itemStateChangedEventDelay int themeNormalBGColor Color themeNormalTextColor Color themeSelectedBGColor Color themeSelectedTextColor Color themeSelectedNotFocusedBGColor Color itemBGColor Color itemTextColor Color alternatingRowBGColor Color alternatingRowTextColor Color alternatingRowBG bool delayedCurrentIndexChangedCanceled bool sortedColumnIndex int sortOrder SortOrder formActivatingHandle int customHeaderHeight int // in native pixels? customRowHeight int // in native pixels? dpiOfPrevStretchLastColumn int scrolling bool inSetCurrentIndex bool inMouseEvent bool hasFrozenColumn bool busyStretchingLastColumn bool focused bool ignoreNowhere bool updateLVSizesNeedsSpecialCare bool scrollbarOrientation Orientation currentItemChangedPublisher EventPublisher currentItemID interface{} restoringCurrentItemOnReset bool } // NewTableView creates and returns a *TableView as child of the specified // Container. func NewTableView(parent Container) (*TableView, error) { return NewTableViewWithStyle(parent, win.LVS_SHOWSELALWAYS) } // NewTableViewWithStyle creates and returns a *TableView as child of the specified // Container and with the provided additional style bits set. func NewTableViewWithStyle(parent Container, style uint32) (*TableView, error) { return NewTableViewWithCfg(parent, &TableViewCfg{Style: style}) } // NewTableViewWithCfg creates and returns a *TableView as child of the specified // Container and with the provided additional configuration. func NewTableViewWithCfg(parent Container, cfg *TableViewCfg) (*TableView, error) { tv := &TableView{ imageUintptr2Index: make(map[uintptr]int32), filePath2IconIndex: make(map[string]int32), formActivatingHandle: -1, customHeaderHeight: cfg.CustomHeaderHeight, customRowHeight: cfg.CustomRowHeight, scrollbarOrientation: Horizontal | Vertical, restoringCurrentItemOnReset: true, } tv.columns = newTableViewColumnList(tv) if err := InitWidget( tv, parent, tableViewWindowClass, win.WS_BORDER|win.WS_VISIBLE, win.WS_EX_CONTROLPARENT); err != nil { return nil, err } succeeded := false defer func() { if !succeeded { tv.Dispose() } }() var rowHeightStyle uint32 if cfg.CustomRowHeight > 0 { rowHeightStyle = win.LVS_OWNERDRAWFIXED } if tv.hwndFrozenLV = win.CreateWindowEx( 0, syscall.StringToUTF16Ptr("SysListView32"), nil, win.WS_CHILD|win.WS_CLIPSIBLINGS|win.WS_TABSTOP|win.WS_VISIBLE|win.LVS_OWNERDATA|win.LVS_REPORT|cfg.Style|rowHeightStyle, win.CW_USEDEFAULT, win.CW_USEDEFAULT, win.CW_USEDEFAULT, win.CW_USEDEFAULT, tv.hWnd, 0, 0, nil, ); tv.hwndFrozenLV == 0 { return nil, newError("creating frozen lv failed") } tv.frozenLVOrigWndProcPtr = win.SetWindowLongPtr(tv.hwndFrozenLV, win.GWLP_WNDPROC, tableViewFrozenLVWndProcPtr) if tv.frozenLVOrigWndProcPtr == 0 { return nil, lastError("SetWindowLongPtr") } tv.hwndFrozenHdr = win.HWND(win.SendMessage(tv.hwndFrozenLV, win.LVM_GETHEADER, 0, 0)) tv.frozenHdrOrigWndProcPtr = win.SetWindowLongPtr(tv.hwndFrozenHdr, win.GWLP_WNDPROC, tableViewHdrWndProcPtr) if tv.frozenHdrOrigWndProcPtr == 0 { return nil, lastError("SetWindowLongPtr") } if tv.hwndNormalLV = win.CreateWindowEx( 0, syscall.StringToUTF16Ptr("SysListView32"), nil, win.WS_CHILD|win.WS_CLIPSIBLINGS|win.WS_TABSTOP|win.WS_VISIBLE|win.LVS_OWNERDATA|win.LVS_REPORT|cfg.Style|rowHeightStyle, win.CW_USEDEFAULT, win.CW_USEDEFAULT, win.CW_USEDEFAULT, win.CW_USEDEFAULT, tv.hWnd, 0, 0, nil, ); tv.hwndNormalLV == 0 { return nil, newError("creating normal lv failed") } tv.normalLVOrigWndProcPtr = win.SetWindowLongPtr(tv.hwndNormalLV, win.GWLP_WNDPROC, tableViewNormalLVWndProcPtr) if tv.normalLVOrigWndProcPtr == 0 { return nil, lastError("SetWindowLongPtr") } tv.hwndNormalHdr = win.HWND(win.SendMessage(tv.hwndNormalLV, win.LVM_GETHEADER, 0, 0)) tv.normalHdrOrigWndProcPtr = win.SetWindowLongPtr(tv.hwndNormalHdr, win.GWLP_WNDPROC, tableViewHdrWndProcPtr) if tv.normalHdrOrigWndProcPtr == 0 { return nil, lastError("SetWindowLongPtr") } tv.SetPersistent(true) exStyle := win.SendMessage(tv.hwndFrozenLV, win.LVM_GETEXTENDEDLISTVIEWSTYLE, 0, 0) exStyle |= win.LVS_EX_DOUBLEBUFFER | win.LVS_EX_FULLROWSELECT | win.LVS_EX_HEADERDRAGDROP | win.LVS_EX_LABELTIP | win.LVS_EX_SUBITEMIMAGES win.SendMessage(tv.hwndFrozenLV, win.LVM_SETEXTENDEDLISTVIEWSTYLE, 0, exStyle) win.SendMessage(tv.hwndNormalLV, win.LVM_SETEXTENDEDLISTVIEWSTYLE, 0, exStyle) if hr := win.SetWindowTheme(tv.hwndFrozenLV, syscall.StringToUTF16Ptr("Explorer"), nil); win.FAILED(hr) { return nil, errorFromHRESULT("SetWindowTheme", hr) } if hr := win.SetWindowTheme(tv.hwndNormalLV, syscall.StringToUTF16Ptr("Explorer"), nil); win.FAILED(hr) { return nil, errorFromHRESULT("SetWindowTheme", hr) } win.SendMessage(tv.hwndFrozenLV, win.WM_CHANGEUISTATE, uintptr(win.MAKELONG(win.UIS_SET, win.UISF_HIDEFOCUS)), 0) win.SendMessage(tv.hwndNormalLV, win.WM_CHANGEUISTATE, uintptr(win.MAKELONG(win.UIS_SET, win.UISF_HIDEFOCUS)), 0) tv.group.toolTip.addTool(tv.hwndFrozenHdr, false) tv.group.toolTip.addTool(tv.hwndNormalHdr, false) tv.applyFont(parent.Font()) tv.style.dpi = tv.DPI() tv.ApplySysColors() tv.currentIndex = -1 tv.GraphicsEffects().Add(InteractionEffect) tv.GraphicsEffects().Add(FocusEffect) tv.MustRegisterProperty("ColumnsOrderable", NewBoolProperty( func() bool { return tv.ColumnsOrderable() }, func(b bool) error { tv.SetColumnsOrderable(b) return nil }, tv.columnsOrderableChangedPublisher.Event())) tv.MustRegisterProperty("ColumnsSizable", NewBoolProperty( func() bool { return tv.ColumnsSizable() }, func(b bool) error { return tv.SetColumnsSizable(b) }, tv.columnsSizableChangedPublisher.Event())) tv.MustRegisterProperty("CurrentIndex", NewProperty( func() interface{} { return tv.CurrentIndex() }, func(v interface{}) error { return tv.SetCurrentIndex(assertIntOr(v, -1)) }, tv.CurrentIndexChanged())) tv.MustRegisterProperty("CurrentItem", NewReadOnlyProperty( func() interface{} { if i := tv.CurrentIndex(); i > -1 { if rm, ok := tv.providedModel.(reflectModel); ok { return reflect.ValueOf(rm.Items()).Index(i).Interface() } } return nil }, tv.CurrentIndexChanged())) tv.MustRegisterProperty("HasCurrentItem", NewReadOnlyBoolProperty( func() bool { return tv.CurrentIndex() != -1 }, tv.CurrentIndexChanged())) tv.MustRegisterProperty("ItemCount", NewReadOnlyProperty( func() interface{} { if tv.model == nil { return 0 } return tv.model.RowCount() }, tv.itemCountChangedPublisher.Event())) tv.MustRegisterProperty("SelectedCount", NewReadOnlyProperty( func() interface{} { return len(tv.selectedIndexes) }, tv.SelectedIndexesChanged())) succeeded = true return tv, nil } func (tv *TableView) asTableView() *TableView { return tv } // Dispose releases the operating system resources, associated with the // *TableView. func (tv *TableView) Dispose() { tv.columns.unsetColumnsTV() tv.disposeImageListAndCaches() if tv.hWnd != 0 { if !win.KillTimer(tv.hWnd, tableViewCurrentIndexChangedTimerId) { lastError("KillTimer") } if !win.KillTimer(tv.hWnd, tableViewSelectedIndexesChangedTimerId) { lastError("KillTimer") } } if tv.hwndFrozenLV != 0 { tv.group.toolTip.removeTool(tv.hwndFrozenHdr) win.DestroyWindow(tv.hwndFrozenLV) tv.hwndFrozenLV = 0 } if tv.hwndNormalLV != 0 { tv.group.toolTip.removeTool(tv.hwndNormalHdr) win.DestroyWindow(tv.hwndNormalLV) tv.hwndNormalLV = 0 } if tv.formActivatingHandle > -1 { if form := tv.Form(); form != nil { form.Activating().Detach(tv.formActivatingHandle) } tv.formActivatingHandle = -1 } tv.WidgetBase.Dispose() } func (tv *TableView) applyEnabled(enabled bool) { tv.WidgetBase.applyEnabled(enabled) win.EnableWindow(tv.hwndFrozenLV, enabled) win.EnableWindow(tv.hwndNormalLV, enabled) } func (tv *TableView) applyFont(font *Font) { if tv.customHeaderHeight > 0 || tv.customRowHeight > 0 { return } tv.WidgetBase.applyFont(font) hFont := uintptr(font.handleForDPI(tv.DPI())) win.SendMessage(tv.hwndFrozenLV, win.WM_SETFONT, hFont, 0) win.SendMessage(tv.hwndNormalLV, win.WM_SETFONT, hFont, 0) } func (tv *TableView) ApplyDPI(dpi int) { tv.style.dpi = dpi if tv.style.canvas != nil { tv.style.canvas.dpi = dpi } tv.WidgetBase.ApplyDPI(dpi) for _, column := range tv.columns.items { column.update() } if tv.hIml != 0 { tv.disposeImageListAndCaches() if bmp, err := NewBitmapForDPI(SizeFrom96DPI(Size{16, 16}, dpi), dpi); err == nil { tv.applyImageListForImage(bmp) bmp.Dispose() } } } func (tv *TableView) ApplySysColors() { tv.WidgetBase.ApplySysColors() // As some combinations of property and state may be invalid for any theme, // we set some defaults here. tv.themeNormalBGColor = Color(win.GetSysColor(win.COLOR_WINDOW)) tv.themeNormalTextColor = Color(win.GetSysColor(win.COLOR_WINDOWTEXT)) tv.themeSelectedBGColor = tv.themeNormalBGColor tv.themeSelectedTextColor = tv.themeNormalTextColor tv.themeSelectedNotFocusedBGColor = tv.themeNormalBGColor tv.alternatingRowBGColor = Color(win.GetSysColor(win.COLOR_BTNFACE)) tv.alternatingRowTextColor = Color(win.GetSysColor(win.COLOR_BTNTEXT)) type item struct { stateID int32 propertyID int32 color *Color } getThemeColor := func(theme win.HTHEME, partId int32, items []item) { for _, item := range items { var c win.COLORREF if result := win.GetThemeColor(theme, partId, item.stateID, item.propertyID, &c); !win.FAILED(result) { (*item.color) = Color(c) } } } if hThemeListView := win.OpenThemeData(tv.hwndNormalLV, syscall.StringToUTF16Ptr("Listview")); hThemeListView != 0 { defer win.CloseThemeData(hThemeListView) getThemeColor(hThemeListView, win.LVP_LISTITEM, []item{ {win.LISS_NORMAL, win.TMT_FILLCOLOR, &tv.themeNormalBGColor}, {win.LISS_NORMAL, win.TMT_TEXTCOLOR, &tv.themeNormalTextColor}, {win.LISS_SELECTED, win.TMT_FILLCOLOR, &tv.themeSelectedBGColor}, {win.LISS_SELECTED, win.TMT_TEXTCOLOR, &tv.themeSelectedTextColor}, {win.LISS_SELECTEDNOTFOCUS, win.TMT_FILLCOLOR, &tv.themeSelectedNotFocusedBGColor}, }) } else { // The others already have been retrieved above. tv.themeSelectedBGColor = Color(win.GetSysColor(win.COLOR_HIGHLIGHT)) tv.themeSelectedTextColor = Color(win.GetSysColor(win.COLOR_HIGHLIGHTTEXT)) tv.themeSelectedNotFocusedBGColor = Color(win.GetSysColor(win.COLOR_BTNFACE)) } if hThemeButton := win.OpenThemeData(tv.hwndNormalLV, syscall.StringToUTF16Ptr("BUTTON")); hThemeButton != 0 { defer win.CloseThemeData(hThemeButton) getThemeColor(hThemeButton, win.BP_PUSHBUTTON, []item{ {win.PBS_NORMAL, win.TMT_FILLCOLOR, &tv.alternatingRowBGColor}, {win.PBS_NORMAL, win.TMT_TEXTCOLOR, &tv.alternatingRowTextColor}, }) } win.SendMessage(tv.hwndNormalLV, win.LVM_SETBKCOLOR, 0, uintptr(tv.themeNormalBGColor)) win.SendMessage(tv.hwndFrozenLV, win.LVM_SETBKCOLOR, 0, uintptr(tv.themeNormalBGColor)) } // ColumnsOrderable returns if the user can reorder columns by dragging and // dropping column headers. func (tv *TableView) ColumnsOrderable() bool { exStyle := win.SendMessage(tv.hwndNormalLV, win.LVM_GETEXTENDEDLISTVIEWSTYLE, 0, 0) return exStyle&win.LVS_EX_HEADERDRAGDROP > 0 } // SetColumnsOrderable sets if the user can reorder columns by dragging and // dropping column headers. func (tv *TableView) SetColumnsOrderable(enabled bool) { var hwnd win.HWND if tv.hasFrozenColumn { hwnd = tv.hwndFrozenLV } else { hwnd = tv.hwndNormalLV } exStyle := win.SendMessage(hwnd, win.LVM_GETEXTENDEDLISTVIEWSTYLE, 0, 0) if enabled { exStyle |= win.LVS_EX_HEADERDRAGDROP } else { exStyle &^= win.LVS_EX_HEADERDRAGDROP } win.SendMessage(tv.hwndFrozenLV, win.LVM_SETEXTENDEDLISTVIEWSTYLE, 0, exStyle) win.SendMessage(tv.hwndNormalLV, win.LVM_SETEXTENDEDLISTVIEWSTYLE, 0, exStyle) tv.columnsOrderableChangedPublisher.Publish() } // ColumnsSizable returns if the user can change column widths by dragging // dividers in the header. func (tv *TableView) ColumnsSizable() bool { style := win.GetWindowLong(tv.hwndNormalHdr, win.GWL_STYLE) return style&win.HDS_NOSIZING == 0 } // SetColumnsSizable sets if the user can change column widths by dragging // dividers in the header. func (tv *TableView) SetColumnsSizable(b bool) error { updateStyle := func(headerHWnd win.HWND) error { style := win.GetWindowLong(headerHWnd, win.GWL_STYLE) if b { style &^= win.HDS_NOSIZING } else { style |= win.HDS_NOSIZING } if 0 == win.SetWindowLong(headerHWnd, win.GWL_STYLE, style) { return lastError("SetWindowLong(GWL_STYLE)") } return nil } if err := updateStyle(tv.hwndFrozenHdr); err != nil { return err } if err := updateStyle(tv.hwndNormalHdr); err != nil { return err } tv.columnsSizableChangedPublisher.Publish() return nil } // ContextMenuLocation returns selected item position in screen coordinates in native pixels. func (tv *TableView) ContextMenuLocation() Point { idx := win.SendMessage(tv.hwndNormalLV, win.LVM_GETSELECTIONMARK, 0, 0) rc := win.RECT{Left: win.LVIR_BOUNDS} if 0 == win.SendMessage(tv.hwndNormalLV, win.LVM_GETITEMRECT, idx, uintptr(unsafe.Pointer(&rc))) { return tv.WidgetBase.ContextMenuLocation() } var pt win.POINT if tv.RightToLeftReading() { pt.X = rc.Right } else { pt.X = rc.Left } pt.X = rc.Bottom windowTrimToClientBounds(tv.hwndNormalLV, &pt) win.ClientToScreen(tv.hwndNormalLV, &pt) return pointPixelsFromPOINT(pt) } // SortableByHeaderClick returns if the user can change sorting by clicking the header. func (tv *TableView) SortableByHeaderClick() bool { return !hasWindowLongBits(tv.hwndFrozenLV, win.GWL_STYLE, win.LVS_NOSORTHEADER) || !hasWindowLongBits(tv.hwndNormalLV, win.GWL_STYLE, win.LVS_NOSORTHEADER) } // HeaderHidden returns whether the column header is hidden. func (tv *TableView) HeaderHidden() bool { style := win.GetWindowLong(tv.hwndNormalLV, win.GWL_STYLE) return style&win.LVS_NOCOLUMNHEADER != 0 } // SetHeaderHidden sets whether the column header is hidden. func (tv *TableView) SetHeaderHidden(hidden bool) error { if err := ensureWindowLongBits(tv.hwndFrozenLV, win.GWL_STYLE, win.LVS_NOCOLUMNHEADER, hidden); err != nil { return err } return ensureWindowLongBits(tv.hwndNormalLV, win.GWL_STYLE, win.LVS_NOCOLUMNHEADER, hidden) } // AlternatingRowBG returns the alternating row background. func (tv *TableView) AlternatingRowBG() bool { return tv.alternatingRowBG } // SetAlternatingRowBG sets the alternating row background. func (tv *TableView) SetAlternatingRowBG(enabled bool) { tv.alternatingRowBG = enabled tv.Invalidate() } // Gridlines returns if the rows are separated by grid lines. func (tv *TableView) Gridlines() bool { exStyle := win.SendMessage(tv.hwndNormalLV, win.LVM_GETEXTENDEDLISTVIEWSTYLE, 0, 0) return exStyle&win.LVS_EX_GRIDLINES > 0 } // SetGridlines sets if the rows are separated by grid lines. func (tv *TableView) SetGridlines(enabled bool) { var hwnd win.HWND if tv.hasFrozenColumn { hwnd = tv.hwndFrozenLV } else { hwnd = tv.hwndNormalLV } exStyle := win.SendMessage(hwnd, win.LVM_GETEXTENDEDLISTVIEWSTYLE, 0, 0) if enabled { exStyle |= win.LVS_EX_GRIDLINES } else { exStyle &^= win.LVS_EX_GRIDLINES } win.SendMessage(tv.hwndFrozenLV, win.LVM_SETEXTENDEDLISTVIEWSTYLE, 0, exStyle) win.SendMessage(tv.hwndNormalLV, win.LVM_SETEXTENDEDLISTVIEWSTYLE, 0, exStyle) } // Columns returns the list of columns. func (tv *TableView) Columns() *TableViewColumnList { return tv.columns } // VisibleColumnsInDisplayOrder returns a slice of visible columns in display // order. func (tv *TableView) VisibleColumnsInDisplayOrder() []*TableViewColumn { visibleCols := tv.visibleColumns() indices := make([]int32, len(visibleCols)) frozenCount := tv.visibleFrozenColumnCount() normalCount := len(visibleCols) - frozenCount if frozenCount > 0 { if win.FALSE == win.SendMessage(tv.hwndFrozenLV, win.LVM_GETCOLUMNORDERARRAY, uintptr(frozenCount), uintptr(unsafe.Pointer(&indices[0]))) { newError("LVM_GETCOLUMNORDERARRAY") return nil } } if normalCount > 0 { if win.FALSE == win.SendMessage(tv.hwndNormalLV, win.LVM_GETCOLUMNORDERARRAY, uintptr(normalCount), uintptr(unsafe.Pointer(&indices[frozenCount]))) { newError("LVM_GETCOLUMNORDERARRAY") return nil } } orderedCols := make([]*TableViewColumn, len(visibleCols)) for i, j := range indices { if i >= frozenCount { j += int32(frozenCount) } orderedCols[i] = visibleCols[j] } return orderedCols } // RowsPerPage returns the number of fully visible rows. func (tv *TableView) RowsPerPage() int { return int(win.SendMessage(tv.hwndNormalLV, win.LVM_GETCOUNTPERPAGE, 0, 0)) } func (tv *TableView) Invalidate() error { win.InvalidateRect(tv.hwndFrozenLV, nil, true) win.InvalidateRect(tv.hwndNormalLV, nil, true) return tv.WidgetBase.Invalidate() } func (tv *TableView) redrawItems() { first := win.SendMessage(tv.hwndNormalLV, win.LVM_GETTOPINDEX, 0, 0) last := first + win.SendMessage(tv.hwndNormalLV, win.LVM_GETCOUNTPERPAGE, 0, 0) + 1 win.SendMessage(tv.hwndFrozenLV, win.LVM_REDRAWITEMS, first, last) win.SendMessage(tv.hwndNormalLV, win.LVM_REDRAWITEMS, first, last) } // UpdateItem ensures the item at index will be redrawn. // // If the model supports sorting, it will be resorted. func (tv *TableView) UpdateItem(index int) error { if s, ok := tv.model.(Sorter); ok { if err := s.Sort(s.SortedColumn(), s.SortOrder()); err != nil { return err } } else { if win.FALSE == win.SendMessage(tv.hwndFrozenLV, win.LVM_UPDATE, uintptr(index), 0) { return newError("LVM_UPDATE") } if win.FALSE == win.SendMessage(tv.hwndNormalLV, win.LVM_UPDATE, uintptr(index), 0) { return newError("LVM_UPDATE") } } return nil } func (tv *TableView) attachModel() { restoreCurrentItemOrFallbackToFirst := func(ip IDProvider) { if tv.itemStateChangedEventDelay == 0 { defer tv.currentItemChangedPublisher.Publish() } else { if 0 == win.SetTimer( tv.hWnd, tableViewCurrentIndexChangedTimerId, uint32(tv.itemStateChangedEventDelay), 0, ) { lastError("SetTimer") } } count := tv.model.RowCount() for i := 0; i < count; i++ { if ip.ID(i) == tv.currentItemID { tv.SetCurrentIndex(i) return } } tv.SetCurrentIndex(0) } tv.rowsResetHandlerHandle = tv.model.RowsReset().Attach(func() { tv.setItemCount() if ip, ok := tv.providedModel.(IDProvider); ok && tv.restoringCurrentItemOnReset { if _, ok := tv.model.(Sorter); !ok { restoreCurrentItemOrFallbackToFirst(ip) } } else { tv.SetCurrentIndex(-1) } tv.itemCountChangedPublisher.Publish() }) tv.rowChangedHandlerHandle = tv.model.RowChanged().Attach(func(row int) { tv.UpdateItem(row) }) tv.rowsChangedHandlerHandle = tv.model.RowsChanged().Attach(func(from, to int) { if s, ok := tv.model.(Sorter); ok { s.Sort(s.SortedColumn(), s.SortOrder()) } else { first, last := uintptr(from), uintptr(to) win.SendMessage(tv.hwndFrozenLV, win.LVM_REDRAWITEMS, first, last) win.SendMessage(tv.hwndNormalLV, win.LVM_REDRAWITEMS, first, last) } }) tv.rowsInsertedHandlerHandle = tv.model.RowsInserted().Attach(func(from, to int) { i := tv.currentIndex tv.setItemCount() if from <= i { i += 1 + to - from tv.SetCurrentIndex(i) } tv.itemCountChangedPublisher.Publish() }) tv.rowsRemovedHandlerHandle = tv.model.RowsRemoved().Attach(func(from, to int) { i := tv.currentIndex tv.setItemCount() index := i if from <= i && i <= to { index = -1 } else if from < i { index -= 1 + to - from } if index != i { tv.SetCurrentIndex(index) } tv.itemCountChangedPublisher.Publish() }) if sorter, ok := tv.model.(Sorter); ok { tv.sortChangedHandlerHandle = sorter.SortChanged().Attach(func() { if ip, ok := tv.providedModel.(IDProvider); ok && tv.restoringCurrentItemOnReset { restoreCurrentItemOrFallbackToFirst(ip) } col := sorter.SortedColumn() tv.setSortIcon(col, sorter.SortOrder()) tv.redrawItems() }) } } func (tv *TableView) detachModel() { tv.model.RowsReset().Detach(tv.rowsResetHandlerHandle) tv.model.RowChanged().Detach(tv.rowChangedHandlerHandle) tv.model.RowsInserted().Detach(tv.rowsInsertedHandlerHandle) tv.model.RowsRemoved().Detach(tv.rowsRemovedHandlerHandle) if sorter, ok := tv.model.(Sorter); ok { sorter.SortChanged().Detach(tv.sortChangedHandlerHandle) } } // ItemCountChanged returns the event that is published when the number of items // in the model of the TableView changed. func (tv *TableView) ItemCountChanged() *Event { return tv.itemCountChangedPublisher.Event() } // Model returns the model of the TableView. func (tv *TableView) Model() interface{} { return tv.providedModel } // SetModel sets the model of the TableView. // // It is required that mdl either implements walk.TableModel, // walk.ReflectTableModel or be a slice of pointers to struct or a // []map[string]interface{}. A walk.TableModel implementation must also // implement walk.Sorter to support sorting, all other options get sorting for // free. To support item check boxes and icons, mdl must implement // walk.ItemChecker and walk.ImageProvider, respectively. On-demand model // population for a walk.ReflectTableModel or slice requires mdl to implement // walk.Populator. func (tv *TableView) SetModel(mdl interface{}) error { model, ok := mdl.(TableModel) if !ok && mdl != nil { var err error if model, err = newReflectTableModel(mdl); err != nil { if model, err = newMapTableModel(mdl); err != nil { return err } } } tv.SetSuspended(true) defer tv.SetSuspended(false) if tv.model != nil { tv.detachModel() tv.disposeImageListAndCaches() } oldProvidedModelStyler, _ := tv.providedModel.(CellStyler) if styler, ok := mdl.(CellStyler); ok || tv.styler == oldProvidedModelStyler { tv.styler = styler } tv.providedModel = mdl tv.model = model tv.itemChecker, _ = model.(ItemChecker) tv.imageProvider, _ = model.(ImageProvider) if model != nil { tv.attachModel() if dms, ok := model.(dataMembersSetter); ok { // FIXME: This depends on columns to be initialized before // calling this method. dataMembers := make([]string, len(tv.columns.items)) for i, col := range tv.columns.items { dataMembers[i] = col.DataMemberEffective() } dms.setDataMembers(dataMembers) } if lfs, ok := model.(lessFuncsSetter); ok { lessFuncs := make([]func(i, j int) bool, tv.columns.Len()) for i, c := range tv.columns.items { lessFuncs[i] = c.lessFunc } lfs.setLessFuncs(lessFuncs) } if sorter, ok := tv.model.(Sorter); ok { if tv.sortedColumnIndex >= tv.visibleColumnCount() { tv.sortedColumnIndex = maxi(-1, mini(0, tv.visibleColumnCount()-1)) tv.sortOrder = SortAscending } sorter.Sort(tv.sortedColumnIndex, tv.sortOrder) } } tv.SetCurrentIndex(-1) tv.setItemCount() tv.itemCountChangedPublisher.Publish() return nil } // TableModel returns the TableModel of the TableView. func (tv *TableView) TableModel() TableModel { return tv.model } // ItemChecker returns the ItemChecker of the TableView. func (tv *TableView) ItemChecker() ItemChecker { return tv.itemChecker } // SetItemChecker sets the ItemChecker of the TableView. func (tv *TableView) SetItemChecker(itemChecker ItemChecker) { tv.itemChecker = itemChecker } // CellStyler returns the CellStyler of the TableView. func (tv *TableView) CellStyler() CellStyler { return tv.styler } // SetCellStyler sets the CellStyler of the TableView. func (tv *TableView) SetCellStyler(styler CellStyler) { tv.styler = styler } func (tv *TableView) setItemCount() error { var count int if tv.model != nil { count = tv.model.RowCount() } if 0 == win.SendMessage(tv.hwndFrozenLV, win.LVM_SETITEMCOUNT, uintptr(count), win.LVSICF_NOINVALIDATEALL|win.LVSICF_NOSCROLL) { return newError("SendMessage(LVM_SETITEMCOUNT)") } if 0 == win.SendMessage(tv.hwndNormalLV, win.LVM_SETITEMCOUNT, uintptr(count), win.LVSICF_NOINVALIDATEALL|win.LVSICF_NOSCROLL) { return newError("SendMessage(LVM_SETITEMCOUNT)") } return nil } // CheckBoxes returns if the *TableView has check boxes. func (tv *TableView) CheckBoxes() bool { var hwnd win.HWND if tv.hasFrozenColumn { hwnd = tv.hwndFrozenLV } else { hwnd = tv.hwndNormalLV } return win.SendMessage(hwnd, win.LVM_GETEXTENDEDLISTVIEWSTYLE, 0, 0)&win.LVS_EX_CHECKBOXES > 0 } // SetCheckBoxes sets if the *TableView has check boxes. func (tv *TableView) SetCheckBoxes(checkBoxes bool) { var hwnd, hwndOther win.HWND if tv.hasFrozenColumn { hwnd, hwndOther = tv.hwndFrozenLV, tv.hwndNormalLV } else { hwnd, hwndOther = tv.hwndNormalLV, tv.hwndFrozenLV } exStyle := win.SendMessage(hwnd, win.LVM_GETEXTENDEDLISTVIEWSTYLE, 0, 0) oldStyle := exStyle if checkBoxes { exStyle |= win.LVS_EX_CHECKBOXES } else { exStyle &^= win.LVS_EX_CHECKBOXES } if exStyle != oldStyle { win.SendMessage(hwnd, win.LVM_SETEXTENDEDLISTVIEWSTYLE, 0, exStyle) } win.SendMessage(hwndOther, win.LVM_SETEXTENDEDLISTVIEWSTYLE, 0, exStyle&^win.LVS_EX_CHECKBOXES) mask := win.SendMessage(hwnd, win.LVM_GETCALLBACKMASK, 0, 0) if checkBoxes { mask |= win.LVIS_STATEIMAGEMASK } else { mask &^= win.LVIS_STATEIMAGEMASK } if win.FALSE == win.SendMessage(hwnd, win.LVM_SETCALLBACKMASK, mask, 0) { newError("SendMessage(LVM_SETCALLBACKMASK)") } } func (tv *TableView) fromLVColIdx(frozen bool, index int32) int { var idx int32 for i, tvc := range tv.columns.items { if frozen == tvc.frozen && tvc.visible { if idx == index { return i } idx++ } } return -1 } func (tv *TableView) toLVColIdx(index int) int32 { var idx int32 for i, tvc := range tv.columns.items { if tvc.visible { if i == index { return idx } idx++ } } return -1 } func (tv *TableView) visibleFrozenColumnCount() int { var count int for _, tvc := range tv.columns.items { if tvc.frozen && tvc.visible { count++ } } return count } func (tv *TableView) visibleColumnCount() int { var count int for _, tvc := range tv.columns.items { if tvc.visible { count++ } } return count } func (tv *TableView) visibleColumns() []*TableViewColumn { var cols []*TableViewColumn for _, tvc := range tv.columns.items { if tvc.visible { cols = append(cols, tvc) } } return cols } /*func (tv *TableView) selectedColumnIndex() int { return tv.fromLVColIdx(tv.SendMessage(LVM_GETSELECTEDCOLUMN, 0, 0)) }*/ // func (tv *TableView) setSelectedColumnIndex(index int) { // tv.SendMessage(win.LVM_SETSELECTEDCOLUMN, uintptr(tv.toLVColIdx(index)), 0) // } func (tv *TableView) setSortIcon(index int, order SortOrder) error { idx := int(tv.toLVColIdx(index)) frozenCount := tv.visibleFrozenColumnCount() for i, col := range tv.visibleColumns() { item := win.HDITEM{ Mask: win.HDI_FORMAT, } var headerHwnd win.HWND var offset int if col.frozen { headerHwnd = tv.hwndFrozenHdr } else { headerHwnd = tv.hwndNormalHdr offset = -frozenCount } iPtr := uintptr(offset + i) itemPtr := uintptr(unsafe.Pointer(&item)) if win.SendMessage(headerHwnd, win.HDM_GETITEM, iPtr, itemPtr) == 0 { return newError("SendMessage(HDM_GETITEM)") } if i == idx { switch order { case SortAscending: item.Fmt &^= win.HDF_SORTDOWN item.Fmt |= win.HDF_SORTUP case SortDescending: item.Fmt &^= win.HDF_SORTUP item.Fmt |= win.HDF_SORTDOWN } } else { item.Fmt &^= win.HDF_SORTDOWN | win.HDF_SORTUP } if win.SendMessage(headerHwnd, win.HDM_SETITEM, iPtr, itemPtr) == 0 { return newError("SendMessage(HDM_SETITEM)") } } return nil } // ColumnClicked returns the event that is published after a column header was // clicked. func (tv *TableView) ColumnClicked() *IntEvent { return tv.columnClickedPublisher.Event() } // ItemActivated returns the event that is published after an item was // activated. // // An item is activated when it is double clicked or the enter key is pressed // when the item is selected. func (tv *TableView) ItemActivated() *Event { return tv.itemActivatedPublisher.Event() } // RestoringCurrentItemOnReset returns whether the TableView after its model // has been reset should attempt to restore CurrentIndex to the item that was // current before the reset. // // For this to work, the model must implement the IDProvider interface. func (tv *TableView) RestoringCurrentItemOnReset() bool { return tv.restoringCurrentItemOnReset } // SetRestoringCurrentItemOnReset sets whether the TableView after its model // has been reset should attempt to restore CurrentIndex to the item that was // current before the reset. // // For this to work, the model must implement the IDProvider interface. func (tv *TableView) SetRestoringCurrentItemOnReset(restoring bool) { tv.restoringCurrentItemOnReset = restoring } // CurrentItemChanged returns the event that is published after the current // item has changed. // // For this to work, the model must implement the IDProvider interface. func (tv *TableView) CurrentItemChanged() *Event { return tv.currentItemChangedPublisher.Event() } // CurrentIndex returns the index of the current item, or -1 if there is no // current item. func (tv *TableView) CurrentIndex() int { return tv.currentIndex } // SetCurrentIndex sets the index of the current item. // // Call this with a value of -1 to have no current item. func (tv *TableView) SetCurrentIndex(index int) error { if tv.inSetCurrentIndex { return nil } tv.inSetCurrentIndex = true defer func() { tv.inSetCurrentIndex = false }() var lvi win.LVITEM lvi.StateMask = win.LVIS_FOCUSED | win.LVIS_SELECTED if tv.MultiSelection() { if win.FALSE == win.SendMessage(tv.hwndFrozenLV, win.LVM_SETITEMSTATE, ^uintptr(0), uintptr(unsafe.Pointer(&lvi))) { return newError("SendMessage(LVM_SETITEMSTATE)") } if win.FALSE == win.SendMessage(tv.hwndNormalLV, win.LVM_SETITEMSTATE, ^uintptr(0), uintptr(unsafe.Pointer(&lvi))) { return newError("SendMessage(LVM_SETITEMSTATE)") } } if index > -1 { lvi.State = win.LVIS_FOCUSED | win.LVIS_SELECTED } if win.FALSE == win.SendMessage(tv.hwndFrozenLV, win.LVM_SETITEMSTATE, uintptr(index), uintptr(unsafe.Pointer(&lvi))) { return newError("SendMessage(LVM_SETITEMSTATE)") } if win.FALSE == win.SendMessage(tv.hwndNormalLV, win.LVM_SETITEMSTATE, uintptr(index), uintptr(unsafe.Pointer(&lvi))) { return newError("SendMessage(LVM_SETITEMSTATE)") } if index > -1 { if win.FALSE == win.SendMessage(tv.hwndFrozenLV, win.LVM_ENSUREVISIBLE, uintptr(index), uintptr(0)) { return newError("SendMessage(LVM_ENSUREVISIBLE)") } // Windows bug? Sometimes a second LVM_ENSUREVISIBLE is required. if win.FALSE == win.SendMessage(tv.hwndFrozenLV, win.LVM_ENSUREVISIBLE, uintptr(index), uintptr(0)) { return newError("SendMessage(LVM_ENSUREVISIBLE)") } if win.FALSE == win.SendMessage(tv.hwndNormalLV, win.LVM_ENSUREVISIBLE, uintptr(index), uintptr(0)) { return newError("SendMessage(LVM_ENSUREVISIBLE)") } // Windows bug? Sometimes a second LVM_ENSUREVISIBLE is required. if win.FALSE == win.SendMessage(tv.hwndNormalLV, win.LVM_ENSUREVISIBLE, uintptr(index), uintptr(0)) { return newError("SendMessage(LVM_ENSUREVISIBLE)") } if ip, ok := tv.providedModel.(IDProvider); ok && tv.restoringCurrentItemOnReset { if id := ip.ID(index); id != tv.currentItemID { tv.currentItemID = id if tv.itemStateChangedEventDelay == 0 { defer tv.currentItemChangedPublisher.Publish() } } } } else { tv.currentItemID = nil if tv.itemStateChangedEventDelay == 0 { defer tv.currentItemChangedPublisher.Publish() } } tv.currentIndex = index if index == -1 || tv.itemStateChangedEventDelay == 0 { tv.currentIndexChangedPublisher.Publish() } if tv.MultiSelection() { tv.updateSelectedIndexes() } return nil } // CurrentIndexChanged is the event that is published after CurrentIndex has // changed. func (tv *TableView) CurrentIndexChanged() *Event { return tv.currentIndexChangedPublisher.Event() } // IndexAt returns the item index at coordinates x, y of the // TableView or -1, if that point is not inside any item. func (tv *TableView) IndexAt(x, y int) int { var hti win.LVHITTESTINFO var rc win.RECT if !win.GetWindowRect(tv.hwndFrozenLV, &rc) { return -1 } var hwnd win.HWND if x < int(rc.Right-rc.Left) { hwnd = tv.hwndFrozenLV } else { hwnd = tv.hwndNormalLV } hti.Pt.X = int32(x) hti.Pt.Y = int32(y) win.SendMessage(hwnd, win.LVM_HITTEST, 0, uintptr(unsafe.Pointer(&hti))) return int(hti.IItem) } // ItemVisible returns whether the item at position index is visible. func (tv *TableView) ItemVisible(index int) bool { return 0 != win.SendMessage(tv.hwndNormalLV, win.LVM_ISITEMVISIBLE, uintptr(index), 0) } // EnsureItemVisible ensures the item at position index is visible, scrolling if necessary. func (tv *TableView) EnsureItemVisible(index int) { win.SendMessage(tv.hwndNormalLV, win.LVM_ENSUREVISIBLE, uintptr(index), 0) } // SelectionHiddenWithoutFocus returns whether selection indicators are hidden // when the TableView does not have the keyboard input focus. func (tv *TableView) SelectionHiddenWithoutFocus() bool { style := uint(win.GetWindowLong(tv.hwndNormalLV, win.GWL_STYLE)) if style == 0 { lastError("GetWindowLong") return false } return style&win.LVS_SHOWSELALWAYS == 0 } // SetSelectionHiddenWithoutFocus sets whether selection indicators are visible when the TableView does not have the keyboard input focus. func (tv *TableView) SetSelectionHiddenWithoutFocus(hidden bool) error { if err := ensureWindowLongBits(tv.hwndFrozenLV, win.GWL_STYLE, win.LVS_SHOWSELALWAYS, !hidden); err != nil { return err } return ensureWindowLongBits(tv.hwndNormalLV, win.GWL_STYLE, win.LVS_SHOWSELALWAYS, !hidden) } // MultiSelection returns whether multiple items can be selected at once. // // By default only a single item can be selected at once. func (tv *TableView) MultiSelection() bool { style := uint(win.GetWindowLong(tv.hwndNormalLV, win.GWL_STYLE)) if style == 0 { lastError("GetWindowLong") return false } return style&win.LVS_SINGLESEL == 0 } // SetMultiSelection sets whether multiple items can be selected at once. func (tv *TableView) SetMultiSelection(multiSel bool) error { if err := ensureWindowLongBits(tv.hwndFrozenLV, win.GWL_STYLE, win.LVS_SINGLESEL, !multiSel); err != nil { return err } return ensureWindowLongBits(tv.hwndNormalLV, win.GWL_STYLE, win.LVS_SINGLESEL, !multiSel) } // SelectedIndexes returns the indexes of the currently selected items. func (tv *TableView) SelectedIndexes() []int { indexes := make([]int, len(tv.selectedIndexes)) for i, j := range tv.selectedIndexes { indexes[i] = j } return indexes } // SetSelectedIndexes sets the indexes of the currently selected items. func (tv *TableView) SetSelectedIndexes(indexes []int) error { tv.inSetSelectedIndexes = true defer func() { tv.inSetSelectedIndexes = false tv.publishSelectedIndexesChanged() }() lvi := &win.LVITEM{StateMask: win.LVIS_FOCUSED | win.LVIS_SELECTED} lp := uintptr(unsafe.Pointer(lvi)) if win.FALSE == win.SendMessage(tv.hwndFrozenLV, win.LVM_SETITEMSTATE, ^uintptr(0), lp) { return newError("SendMessage(LVM_SETITEMSTATE)") } if win.FALSE == win.SendMessage(tv.hwndNormalLV, win.LVM_SETITEMSTATE, ^uintptr(0), lp) { return newError("SendMessage(LVM_SETITEMSTATE)") } selectAll := false lvi.State = win.LVIS_FOCUSED | win.LVIS_SELECTED for _, i := range indexes { val := uintptr(i) if i == -1 { selectAll = true val = ^uintptr(0) } if win.FALSE == win.SendMessage(tv.hwndFrozenLV, win.LVM_SETITEMSTATE, val, lp) && i != -1 { return newError("SendMessage(LVM_SETITEMSTATE)") } if win.FALSE == win.SendMessage(tv.hwndNormalLV, win.LVM_SETITEMSTATE, val, lp) && i != -1 { return newError("SendMessage(LVM_SETITEMSTATE)") } } if !selectAll { idxs := make([]int, len(indexes)) for i, j := range indexes { idxs[i] = j } tv.selectedIndexes = idxs } else { count := int(win.SendMessage(tv.hwndNormalLV, win.LVM_GETSELECTEDCOUNT, 0, 0)) idxs := make([]int, count) for i := range idxs { idxs[i] = i } tv.selectedIndexes = idxs } return nil } func (tv *TableView) updateSelectedIndexes() { count := int(win.SendMessage(tv.hwndNormalLV, win.LVM_GETSELECTEDCOUNT, 0, 0)) indexes := make([]int, count) j := -1 for i := 0; i < count; i++ { j = int(win.SendMessage(tv.hwndNormalLV, win.LVM_GETNEXTITEM, uintptr(j), win.LVNI_SELECTED)) indexes[i] = j } changed := len(indexes) != len(tv.selectedIndexes) if !changed { for i := 0; i < len(indexes); i++ { if indexes[i] != tv.selectedIndexes[i] { changed = true break } } } if changed { tv.selectedIndexes = indexes tv.publishSelectedIndexesChanged() } } func (tv *TableView) copySelectedIndexes(hwndTo, hwndFrom win.HWND) error { count := int(win.SendMessage(hwndFrom, win.LVM_GETSELECTEDCOUNT, 0, 0)) lvi := &win.LVITEM{StateMask: win.LVIS_FOCUSED | win.LVIS_SELECTED} lp := uintptr(unsafe.Pointer(lvi)) if win.FALSE == win.SendMessage(hwndTo, win.LVM_SETITEMSTATE, ^uintptr(0), lp) { return newError("SendMessage(LVM_SETITEMSTATE)") } lvi.StateMask = win.LVIS_SELECTED lvi.State = win.LVIS_SELECTED j := -1 for i := 0; i < count; i++ { j = int(win.SendMessage(hwndFrom, win.LVM_GETNEXTITEM, uintptr(j), win.LVNI_SELECTED)) if win.FALSE == win.SendMessage(hwndTo, win.LVM_SETITEMSTATE, uintptr(j), lp) { return newError("SendMessage(LVM_SETITEMSTATE)") } } return nil } // ItemStateChangedEventDelay returns the delay in milliseconds, between the // moment the state of an item in the *TableView changes and the moment the // associated event is published. // // By default there is no delay. func (tv *TableView) ItemStateChangedEventDelay() int { return tv.itemStateChangedEventDelay } // SetItemStateChangedEventDelay sets the delay in milliseconds, between the // moment the state of an item in the *TableView changes and the moment the // associated event is published. // // An example where this may be useful is a master-details scenario. If the // master TableView is configured to delay the event, you can avoid pointless // updates of the details TableView, if the user uses arrow keys to rapidly // navigate the master view. func (tv *TableView) SetItemStateChangedEventDelay(delay int) { tv.itemStateChangedEventDelay = delay } // SelectedIndexesChanged returns the event that is published when the list of // selected item indexes changed. func (tv *TableView) SelectedIndexesChanged() *Event { return tv.selectedIndexesChangedPublisher.Event() } func (tv *TableView) publishSelectedIndexesChanged() { if tv.itemStateChangedEventDelay > 0 { if 0 == win.SetTimer( tv.hWnd, tableViewSelectedIndexesChangedTimerId, uint32(tv.itemStateChangedEventDelay), 0) { lastError("SetTimer") } } else { tv.selectedIndexesChangedPublisher.Publish() } } // LastColumnStretched returns if the last column should take up all remaining // horizontal space of the *TableView. func (tv *TableView) LastColumnStretched() bool { return tv.lastColumnStretched } // SetLastColumnStretched sets if the last column should take up all remaining // horizontal space of the *TableView. // // The effect of setting this is persistent. func (tv *TableView) SetLastColumnStretched(value bool) error { if value { if err := tv.StretchLastColumn(); err != nil { return err } } tv.lastColumnStretched = value return nil } // StretchLastColumn makes the last column take up all remaining horizontal // space of the *TableView. // // The effect of this is not persistent. func (tv *TableView) StretchLastColumn() error { colCount := tv.visibleColumnCount() if colCount == 0 { return nil } var hwnd win.HWND frozenColCount := tv.visibleFrozenColumnCount() if colCount-frozenColCount == 0 { hwnd = tv.hwndFrozenLV colCount = frozenColCount } else { hwnd = tv.hwndNormalLV colCount -= frozenColCount } var lp uintptr if tv.scrollbarOrientation&Horizontal != 0 { lp = win.LVSCW_AUTOSIZE_USEHEADER } else { width := tv.ClientBoundsPixels().Width lastIndexInLV := -1 var lastIndexInLVWidth int for _, tvc := range tv.columns.items { var offset int if !tvc.Frozen() { offset = frozenColCount } colWidth := tv.IntFrom96DPI(tvc.Width()) width -= colWidth if index := int32(offset) + tvc.indexInListView(); int(index) > lastIndexInLV { lastIndexInLV = int(index) lastIndexInLVWidth = colWidth } } width += lastIndexInLVWidth if hasWindowLongBits(tv.hwndNormalLV, win.GWL_STYLE, win.WS_VSCROLL) { width -= int(win.GetSystemMetricsForDpi(win.SM_CXVSCROLL, uint32(tv.DPI()))) } lp = uintptr(maxi(0, width)) } if lp > 0 { if 0 == win.SendMessage(hwnd, win.LVM_SETCOLUMNWIDTH, uintptr(colCount-1), lp) { return newError("LVM_SETCOLUMNWIDTH failed") } if dpi := tv.DPI(); dpi != tv.dpiOfPrevStretchLastColumn { tv.dpiOfPrevStretchLastColumn = dpi tv.Invalidate() } } return nil } // Persistent returns if the *TableView should persist its UI state, like column // widths. See *App.Settings for details. func (tv *TableView) Persistent() bool { return tv.persistent } // SetPersistent sets if the *TableView should persist its UI state, like column // widths. See *App.Settings for details. func (tv *TableView) SetPersistent(value bool) { tv.persistent = value } // IgnoreNowhere returns if the *TableView should ignore left mouse clicks in the // empty space. It forbids the user from unselecting the current index, or when // multi selection is enabled, disables click drag selection. func (tv *TableView) IgnoreNowhere() bool { return tv.ignoreNowhere } // IgnoreNowhere sets if the *TableView should ignore left mouse clicks in the // empty space. It forbids the user from unselecting the current index, or when // multi selection is enabled, disables click drag selection. func (tv *TableView) SetIgnoreNowhere(value bool) { tv.ignoreNowhere = value } type tableViewState struct { SortColumnName string SortOrder SortOrder ColumnDisplayOrder []string Columns []*tableViewColumnState } type tableViewColumnState struct { Name string Title string Width int Visible bool Frozen bool LastSeenDate string } // SaveState writes the UI state of the *TableView to the settings. func (tv *TableView) SaveState() error { if tv.columns.Len() == 0 { return nil } if tv.state == nil { tv.state = new(tableViewState) } tvs := tv.state tvs.SortColumnName = tv.columns.items[tv.sortedColumnIndex].name tvs.SortOrder = tv.sortOrder // tvs.Columns = make([]tableViewColumnState, tv.columns.Len()) for _, tvc := range tv.columns.items { var tvcs *tableViewColumnState for _, cur := range tvs.Columns { if cur.Name == tvc.name { tvcs = cur break } } // tvcs := &tvs.Columns[i] if tvcs == nil { tvs.Columns = append(tvs.Columns, new(tableViewColumnState)) tvcs = tvs.Columns[len(tvs.Columns)-1] } tvcs.Name = tvc.name tvcs.Title = tvc.titleOverride tvcs.Width = tvc.Width() tvcs.Visible = tvc.Visible() tvcs.Frozen = tvc.Frozen() tvcs.LastSeenDate = time.Now().Format("2006-01-02") } visibleCols := tv.visibleColumns() frozenCount := tv.visibleFrozenColumnCount() normalCount := len(visibleCols) - frozenCount indices := make([]int32, len(visibleCols)) var lp uintptr if frozenCount > 0 { lp = uintptr(unsafe.Pointer(&indices[0])) if 0 == win.SendMessage(tv.hwndFrozenLV, win.LVM_GETCOLUMNORDERARRAY, uintptr(frozenCount), lp) { return newError("LVM_GETCOLUMNORDERARRAY") } } if normalCount > 0 { lp = uintptr(unsafe.Pointer(&indices[frozenCount])) if 0 == win.SendMessage(tv.hwndNormalLV, win.LVM_GETCOLUMNORDERARRAY, uintptr(normalCount), lp) { return newError("LVM_GETCOLUMNORDERARRAY") } } tvs.ColumnDisplayOrder = make([]string, len(visibleCols)) for i, j := range indices { if i >= frozenCount { j += int32(frozenCount) } tvs.ColumnDisplayOrder[i] = visibleCols[j].name } state, err := json.Marshal(tvs) if err != nil { return err } return tv.WriteState(string(state)) } // RestoreState restores the UI state of the *TableView from the settings. func (tv *TableView) RestoreState() error { state, err := tv.ReadState() if err != nil { return err } if state == "" { return nil } tv.SetSuspended(true) defer tv.SetSuspended(false) if tv.state == nil { tv.state = new(tableViewState) } tvs := tv.state if err := json.Unmarshal(([]byte)(state), tvs); err != nil { return err } name2tvc := make(map[string]*TableViewColumn) for _, tvc := range tv.columns.items { name2tvc[tvc.name] = tvc } name2tvcs := make(map[string]*tableViewColumnState) tvcsRetained := make([]*tableViewColumnState, 0, len(tvs.Columns)) for _, tvcs := range tvs.Columns { if tvcs.LastSeenDate != "" { if lastSeen, err := time.Parse("2006-02-01", tvcs.LastSeenDate); err != nil { tvcs.LastSeenDate = "" } else if name2tvc[tvcs.Name] == nil && lastSeen.Add(time.Hour*24*90).Before(time.Now()) { continue } } tvcsRetained = append(tvcsRetained, tvcs) name2tvcs[tvcs.Name] = tvcsRetained[len(tvcsRetained)-1] if tvc := name2tvc[tvcs.Name]; tvc != nil { if err := tvc.SetFrozen(tvcs.Frozen); err != nil { return err } var visible bool for _, name := range tvs.ColumnDisplayOrder { if name == tvc.name { visible = true break } } if err := tvc.SetVisible(tvc.visible && (visible || tvcs.Visible)); err != nil { return err } if err := tvc.SetTitleOverride(tvcs.Title); err != nil { return err } if err := tvc.SetWidth(tvcs.Width); err != nil { return err } } } tvs.Columns = tvcsRetained visibleCount := tv.visibleColumnCount() frozenCount := tv.visibleFrozenColumnCount() normalCount := visibleCount - frozenCount indices := make([]int32, visibleCount) knownNames := make(map[string]struct{}) displayOrder := make([]string, 0, visibleCount) for _, name := range tvs.ColumnDisplayOrder { knownNames[name] = struct{}{} if tvc, ok := name2tvc[name]; ok && tvc.visible { displayOrder = append(displayOrder, name) } } for _, tvc := range tv.visibleColumns() { if _, ok := knownNames[tvc.name]; !ok { displayOrder = append(displayOrder, tvc.name) } } for i, tvc := range tv.visibleColumns() { for j, name := range displayOrder { if tvc.name == name && j < visibleCount { idx := i if j >= frozenCount { idx -= frozenCount } indices[j] = int32(idx) break } } } var lp uintptr if frozenCount > 0 { lp = uintptr(unsafe.Pointer(&indices[0])) if 0 == win.SendMessage(tv.hwndFrozenLV, win.LVM_SETCOLUMNORDERARRAY, uintptr(frozenCount), lp) { return newError("LVM_SETCOLUMNORDERARRAY") } } if normalCount > 0 { lp = uintptr(unsafe.Pointer(&indices[frozenCount])) if 0 == win.SendMessage(tv.hwndNormalLV, win.LVM_SETCOLUMNORDERARRAY, uintptr(normalCount), lp) { return newError("LVM_SETCOLUMNORDERARRAY") } } for i, c := range tvs.Columns { if c.Name == tvs.SortColumnName && i < visibleCount { tv.sortedColumnIndex = i tv.sortOrder = tvs.SortOrder break } } if sorter, ok := tv.model.(Sorter); ok { if !sorter.ColumnSortable(tv.sortedColumnIndex) { for i := range tvs.Columns { if sorter.ColumnSortable(i) { tv.sortedColumnIndex = i break } } } sorter.Sort(tv.sortedColumnIndex, tvs.SortOrder) } return nil } func (tv *TableView) toggleItemChecked(index int) error { checked := tv.itemChecker.Checked(index) if err := tv.itemChecker.SetChecked(index, !checked); err != nil { return wrapError(err) } if win.FALSE == win.SendMessage(tv.hwndFrozenLV, win.LVM_UPDATE, uintptr(index), 0) { return newError("SendMessage(LVM_UPDATE)") } if win.FALSE == win.SendMessage(tv.hwndNormalLV, win.LVM_UPDATE, uintptr(index), 0) { return newError("SendMessage(LVM_UPDATE)") } return nil } func (tv *TableView) applyImageListForImage(image interface{}) { tv.hIml, tv.usingSysIml, _ = imageListForImage(image, tv.DPI()) tv.applyImageList() tv.imageUintptr2Index = make(map[uintptr]int32) tv.filePath2IconIndex = make(map[string]int32) } func (tv *TableView) applyImageList() { win.SendMessage(tv.hwndFrozenLV, win.LVM_SETIMAGELIST, win.LVSIL_SMALL, uintptr(tv.hIml)) win.SendMessage(tv.hwndNormalLV, win.LVM_SETIMAGELIST, win.LVSIL_SMALL, uintptr(tv.hIml)) } func (tv *TableView) disposeImageListAndCaches() { if tv.hIml != 0 && !tv.usingSysIml { win.SendMessage(tv.hwndFrozenLV, win.LVM_SETIMAGELIST, win.LVSIL_SMALL, 0) win.SendMessage(tv.hwndNormalLV, win.LVM_SETIMAGELIST, win.LVSIL_SMALL, 0) win.ImageList_Destroy(tv.hIml) } tv.hIml = 0 tv.imageUintptr2Index = nil tv.filePath2IconIndex = nil } func (tv *TableView) Focused() bool { focused := win.GetFocus() return focused == tv.hwndFrozenLV || focused == tv.hwndNormalLV } func (tv *TableView) maybePublishFocusChanged(hwnd win.HWND, msg uint32, wp uintptr) { focused := msg == win.WM_SETFOCUS if focused != tv.focused && wp != uintptr(tv.hwndFrozenLV) && wp != uintptr(tv.hwndNormalLV) { tv.focused = focused tv.focusedChangedPublisher.Publish() } } func tableViewFrozenLVWndProc(hwnd win.HWND, msg uint32, wp, lp uintptr) uintptr { tv := (*TableView)(unsafe.Pointer(windowFromHandle(win.GetParent(hwnd)).AsWindowBase())) switch msg { case win.WM_NCCALCSIZE: ensureWindowLongBits(hwnd, win.GWL_STYLE, win.WS_HSCROLL|win.WS_VSCROLL, false) case win.WM_SETFOCUS: win.SetFocus(tv.hwndNormalLV) tv.maybePublishFocusChanged(hwnd, msg, wp) case win.WM_KILLFOCUS: tv.maybePublishFocusChanged(hwnd, msg, wp) case win.WM_MOUSEWHEEL: tableViewNormalLVWndProc(tv.hwndNormalLV, msg, wp, lp) } return tv.lvWndProc(tv.frozenLVOrigWndProcPtr, hwnd, msg, wp, lp) } func tableViewNormalLVWndProc(hwnd win.HWND, msg uint32, wp, lp uintptr) uintptr { tv := (*TableView)(unsafe.Pointer(windowFromHandle(win.GetParent(hwnd)).AsWindowBase())) switch msg { case win.WM_LBUTTONDOWN, win.WM_RBUTTONDOWN: win.SetFocus(tv.hwndFrozenLV) case win.WM_SETFOCUS: tv.invalidateBorderInParent() tv.maybePublishFocusChanged(hwnd, msg, wp) case win.WM_KILLFOCUS: win.SendMessage(tv.hwndFrozenLV, msg, wp, lp) tv.WndProc(tv.hWnd, msg, wp, lp) tv.maybePublishFocusChanged(hwnd, msg, wp) } result := tv.lvWndProc(tv.normalLVOrigWndProcPtr, hwnd, msg, wp, lp) var off uint32 = win.WS_HSCROLL | win.WS_VSCROLL if tv.scrollbarOrientation&Horizontal != 0 { off &^= win.WS_HSCROLL } if tv.scrollbarOrientation&Vertical != 0 { off &^= win.WS_VSCROLL } if off != 0 { ensureWindowLongBits(hwnd, win.GWL_STYLE, off, false) } return result } func (tv *TableView) lvWndProc(origWndProcPtr uintptr, hwnd win.HWND, msg uint32, wp, lp uintptr) uintptr { var hwndOther win.HWND if hwnd == tv.hwndFrozenLV { hwndOther = tv.hwndNormalLV } else { hwndOther = tv.hwndFrozenLV } var maybeStretchLastColumn bool switch msg { case win.WM_ERASEBKGND: maybeStretchLastColumn = true case win.WM_WINDOWPOSCHANGED: wp := (*win.WINDOWPOS)(unsafe.Pointer(lp)) if wp.Flags&win.SWP_NOSIZE != 0 { break } maybeStretchLastColumn = int(wp.Cx) < tv.WidthPixels() case win.WM_GETDLGCODE: if wp == win.VK_RETURN { return win.DLGC_WANTALLKEYS } case win.WM_LBUTTONDOWN, win.WM_RBUTTONDOWN, win.WM_LBUTTONDBLCLK, win.WM_RBUTTONDBLCLK: var hti win.LVHITTESTINFO hti.Pt = win.POINT{win.GET_X_LPARAM(lp), win.GET_Y_LPARAM(lp)} win.SendMessage(hwnd, win.LVM_HITTEST, 0, uintptr(unsafe.Pointer(&hti))) tv.itemIndexOfLastMouseButtonDown = int(hti.IItem) if hti.Flags == win.LVHT_NOWHERE { if tv.MultiSelection() { tv.publishNextSelClear = true } else { if tv.CheckBoxes() { if tv.currentIndex > -1 { tv.SetCurrentIndex(-1) } } else { // We keep the current item, if in single item selection mode without check boxes. win.SetFocus(tv.hwndFrozenLV) return 0 } } if tv.IgnoreNowhere() { return 0 } } switch msg { case win.WM_LBUTTONDOWN, win.WM_RBUTTONDOWN: if hti.Flags == win.LVHT_ONITEMSTATEICON && tv.itemChecker != nil && tv.CheckBoxes() { tv.toggleItemChecked(int(hti.IItem)) } case win.WM_LBUTTONDBLCLK, win.WM_RBUTTONDBLCLK: if tv.currentIndex != tv.prevIndex && tv.itemStateChangedEventDelay > 0 { tv.prevIndex = tv.currentIndex tv.currentIndexChangedPublisher.Publish() tv.currentItemChangedPublisher.Publish() } } case win.WM_LBUTTONUP, win.WM_RBUTTONUP: tv.itemIndexOfLastMouseButtonDown = -1 case win.WM_MOUSEMOVE, win.WM_MOUSELEAVE: if tv.inMouseEvent { break } tv.inMouseEvent = true defer func() { tv.inMouseEvent = false }() if msg == win.WM_MOUSEMOVE { y := int(win.GET_Y_LPARAM(lp)) lp = uintptr(win.MAKELONG(0, uint16(y))) } win.SendMessage(hwndOther, msg, wp, lp) case win.WM_KEYDOWN: if wp == win.VK_SPACE && tv.currentIndex > -1 && tv.itemChecker != nil && tv.CheckBoxes() { tv.toggleItemChecked(tv.currentIndex) } tv.handleKeyDown(wp, lp) case win.WM_KEYUP: tv.handleKeyUp(wp, lp) case win.WM_NOTIFY: nmh := ((*win.NMHDR)(unsafe.Pointer(lp))) switch nmh.HwndFrom { case tv.hwndFrozenHdr, tv.hwndNormalHdr: if nmh.Code == win.NM_CUSTOMDRAW { return tableViewHdrWndProc(nmh.HwndFrom, msg, wp, lp) } } switch nmh.Code { case win.LVN_GETDISPINFO: di := (*win.NMLVDISPINFO)(unsafe.Pointer(lp)) row := int(di.Item.IItem) col := tv.fromLVColIdx(hwnd == tv.hwndFrozenLV, di.Item.ISubItem) if col == -1 { break } if di.Item.Mask&win.LVIF_TEXT > 0 { value := tv.model.Value(row, col) var text string if format := tv.columns.items[col].formatFunc; format != nil { text = format(value) } else { switch val := value.(type) { case string: text = val case float32: prec := tv.columns.items[col].precision if prec == 0 { prec = 2 } text = FormatFloatGrouped(float64(val), prec) case float64: prec := tv.columns.items[col].precision if prec == 0 { prec = 2 } text = FormatFloatGrouped(val, prec) case time.Time: if val.Year() > 1601 { text = val.Format(tv.columns.items[col].format) } case bool: if val { text = checkmark } case *big.Rat: prec := tv.columns.items[col].precision if prec == 0 { prec = 2 } text = formatBigRatGrouped(val, prec) default: text = fmt.Sprintf(tv.columns.items[col].format, val) } } utf16 := syscall.StringToUTF16(text) buf := (*[264]uint16)(unsafe.Pointer(di.Item.PszText)) max := mini(len(utf16), int(di.Item.CchTextMax)) copy((*buf)[:], utf16[:max]) (*buf)[max-1] = 0 } if (tv.imageProvider != nil || tv.styler != nil) && di.Item.Mask&win.LVIF_IMAGE > 0 { var image interface{} if di.Item.ISubItem == 0 { if ip := tv.imageProvider; ip != nil && image == nil { image = ip.Image(row) } } if styler := tv.styler; styler != nil && image == nil { tv.style.row = row tv.style.col = col tv.style.bounds = Rectangle{} tv.style.dpi = tv.DPI() tv.style.Image = nil styler.StyleCell(&tv.style) image = tv.style.Image } if image != nil { if tv.hIml == 0 { tv.applyImageListForImage(image) } di.Item.IImage = imageIndexMaybeAdd( image, tv.hIml, tv.usingSysIml, tv.imageUintptr2Index, tv.filePath2IconIndex, tv.DPI()) } } if di.Item.ISubItem == 0 && di.Item.StateMask&win.LVIS_STATEIMAGEMASK > 0 && tv.itemChecker != nil { checked := tv.itemChecker.Checked(row) if checked { di.Item.State = 0x2000 } else { di.Item.State = 0x1000 } } case win.NM_CUSTOMDRAW: nmlvcd := (*win.NMLVCUSTOMDRAW)(unsafe.Pointer(lp)) if nmlvcd.IIconPhase == 0 { row := int(nmlvcd.Nmcd.DwItemSpec) col := tv.fromLVColIdx(hwnd == tv.hwndFrozenLV, nmlvcd.ISubItem) if col == -1 { break } applyCellStyle := func() int { if tv.styler != nil { dpi := tv.DPI() tv.style.row = row tv.style.col = col tv.style.bounds = rectangleFromRECT(nmlvcd.Nmcd.Rc) tv.style.dpi = dpi tv.style.hdc = nmlvcd.Nmcd.Hdc tv.style.BackgroundColor = tv.itemBGColor tv.style.TextColor = tv.itemTextColor tv.style.Font = nil tv.style.Image = nil tv.styler.StyleCell(&tv.style) defer func() { tv.style.bounds = Rectangle{} if tv.style.canvas != nil { tv.style.canvas.Dispose() tv.style.canvas = nil } tv.style.hdc = 0 }() if tv.style.canvas != nil { return win.CDRF_SKIPDEFAULT } nmlvcd.ClrTextBk = win.COLORREF(tv.style.BackgroundColor) nmlvcd.ClrText = win.COLORREF(tv.style.TextColor) font := tv.style.Font if font == nil { font = tv.Font() } win.SelectObject(nmlvcd.Nmcd.Hdc, win.HGDIOBJ(font.handleForDPI(dpi))) } return 0 } switch nmlvcd.Nmcd.DwDrawStage { case win.CDDS_PREPAINT: return win.CDRF_NOTIFYITEMDRAW case win.CDDS_ITEMPREPAINT: var selected bool if itemState := win.SendMessage(hwnd, win.LVM_GETITEMSTATE, nmlvcd.Nmcd.DwItemSpec, win.LVIS_SELECTED); itemState&win.LVIS_SELECTED != 0 { selected = true tv.itemBGColor = tv.themeSelectedBGColor tv.itemTextColor = tv.themeSelectedTextColor } else { tv.itemBGColor = tv.themeNormalBGColor tv.itemTextColor = tv.themeNormalTextColor } if !selected && tv.alternatingRowBG && row%2 == 1 { tv.itemBGColor = tv.alternatingRowBGColor tv.itemTextColor = tv.alternatingRowTextColor } tv.style.BackgroundColor = tv.itemBGColor tv.style.TextColor = tv.itemTextColor if tv.styler != nil { tv.style.row = row tv.style.col = -1 tv.style.bounds = rectangleFromRECT(nmlvcd.Nmcd.Rc) tv.style.dpi = tv.DPI() tv.style.hdc = 0 tv.style.Font = nil tv.style.Image = nil tv.styler.StyleCell(&tv.style) tv.itemFont = tv.style.Font } if selected { tv.style.BackgroundColor = tv.itemBGColor tv.style.TextColor = tv.itemTextColor } else { tv.itemBGColor = tv.style.BackgroundColor tv.itemTextColor = tv.style.TextColor } if tv.style.BackgroundColor != tv.themeNormalBGColor { var color Color if selected && !tv.Focused() { color = tv.themeSelectedNotFocusedBGColor } else { color = tv.style.BackgroundColor } if brush, _ := NewSolidColorBrush(color); brush != nil { defer brush.Dispose() canvas, _ := newCanvasFromHDC(nmlvcd.Nmcd.Hdc) canvas.FillRectanglePixels(brush, rectangleFromRECT(nmlvcd.Nmcd.Rc)) } } nmlvcd.ClrText = win.COLORREF(tv.style.TextColor) nmlvcd.ClrTextBk = win.COLORREF(tv.style.BackgroundColor) return win.CDRF_NOTIFYSUBITEMDRAW case win.CDDS_ITEMPREPAINT | win.CDDS_SUBITEM: if tv.itemFont != nil { win.SelectObject(nmlvcd.Nmcd.Hdc, win.HGDIOBJ(tv.itemFont.handleForDPI(tv.DPI()))) } if applyCellStyle() == win.CDRF_SKIPDEFAULT && win.IsAppThemed() { return win.CDRF_SKIPDEFAULT } return win.CDRF_NEWFONT | win.CDRF_SKIPPOSTPAINT | win.CDRF_NOTIFYPOSTPAINT case win.CDDS_ITEMPOSTPAINT | win.CDDS_SUBITEM: if applyCellStyle() == win.CDRF_SKIPDEFAULT { return win.CDRF_SKIPDEFAULT } return win.CDRF_NEWFONT | win.CDRF_SKIPPOSTPAINT } return win.CDRF_SKIPPOSTPAINT } return win.CDRF_SKIPPOSTPAINT case win.LVN_BEGINSCROLL: if tv.scrolling { break } tv.scrolling = true defer func() { tv.scrolling = false }() var rc win.RECT win.SendMessage(hwnd, win.LVM_GETITEMRECT, 0, uintptr(unsafe.Pointer(&rc))) nmlvs := (*win.NMLVSCROLL)(unsafe.Pointer(lp)) win.SendMessage(hwndOther, win.LVM_SCROLL, 0, uintptr(nmlvs.Dy*(rc.Bottom-rc.Top))) case win.LVN_COLUMNCLICK: nmlv := (*win.NMLISTVIEW)(unsafe.Pointer(lp)) col := tv.fromLVColIdx(hwnd == tv.hwndFrozenLV, nmlv.ISubItem) if sorter, ok := tv.model.(Sorter); ok && sorter.ColumnSortable(col) { prevCol := sorter.SortedColumn() var order SortOrder if col != prevCol || sorter.SortOrder() == SortDescending { order = SortAscending } else { order = SortDescending } tv.sortedColumnIndex = col tv.sortOrder = order sorter.Sort(col, order) } tv.columnClickedPublisher.Publish(col) case win.LVN_ITEMCHANGED: nmlv := (*win.NMLISTVIEW)(unsafe.Pointer(lp)) if tv.hwndItemChanged != 0 && tv.hwndItemChanged != hwnd { break } tv.hwndItemChanged = hwnd defer func() { tv.hwndItemChanged = 0 }() tv.copySelectedIndexes(hwndOther, hwnd) if nmlv.IItem == -1 && !tv.publishNextSelClear { break } tv.publishNextSelClear = false selectedNow := nmlv.UNewState&win.LVIS_SELECTED > 0 selectedBefore := nmlv.UOldState&win.LVIS_SELECTED > 0 if tv.itemIndexOfLastMouseButtonDown != -1 && selectedNow && !selectedBefore && ModifiersDown()&(ModControl|ModShift) == 0 { tv.prevIndex = tv.currentIndex tv.currentIndex = int(nmlv.IItem) if tv.itemStateChangedEventDelay > 0 { tv.delayedCurrentIndexChangedCanceled = false if 0 == win.SetTimer( tv.hWnd, tableViewCurrentIndexChangedTimerId, uint32(tv.itemStateChangedEventDelay), 0) { lastError("SetTimer") } tv.SetCurrentIndex(int(nmlv.IItem)) } else { tv.SetCurrentIndex(int(nmlv.IItem)) } } if selectedNow != selectedBefore { if !tv.inSetSelectedIndexes && tv.MultiSelection() { tv.updateSelectedIndexes() } } case win.LVN_ODSTATECHANGED: if tv.hwndItemChanged != 0 && tv.hwndItemChanged != hwnd { break } tv.hwndItemChanged = hwnd defer func() { tv.hwndItemChanged = 0 }() tv.copySelectedIndexes(hwndOther, hwnd) tv.updateSelectedIndexes() case win.LVN_ITEMACTIVATE: nmia := (*win.NMITEMACTIVATE)(unsafe.Pointer(lp)) if tv.itemStateChangedEventDelay > 0 { tv.delayedCurrentIndexChangedCanceled = true } if int(nmia.IItem) != tv.currentIndex { tv.SetCurrentIndex(int(nmia.IItem)) tv.currentIndexChangedPublisher.Publish() tv.currentItemChangedPublisher.Publish() } tv.itemActivatedPublisher.Publish() case win.HDN_ITEMCHANGING: tv.updateLVSizes() } case win.WM_UPDATEUISTATE: switch win.LOWORD(uint32(wp)) { case win.UIS_SET: wp |= win.UISF_HIDEFOCUS << 16 case win.UIS_CLEAR, win.UIS_INITIALIZE: wp &^= ^uintptr(win.UISF_HIDEFOCUS << 16) } } lpFixed := lp fixXInLP := func() { // fmt.Printf("hwnd == tv.hwndNormalLV: %t, tv.hasFrozenColumn: %t\n", hwnd == tv.hwndNormalLV, tv.hasFrozenColumn) if hwnd == tv.hwndNormalLV && tv.hasFrozenColumn { var rc win.RECT if win.GetWindowRect(tv.hwndFrozenLV, &rc) { x := int(win.GET_X_LPARAM(lp)) + int(rc.Right-rc.Left) y := int(win.GET_Y_LPARAM(lp)) lpFixed = uintptr(win.MAKELONG(uint16(x), uint16(y))) } } } switch msg { case win.WM_LBUTTONDOWN, win.WM_MBUTTONDOWN, win.WM_RBUTTONDOWN: fixXInLP() tv.publishMouseEvent(&tv.mouseDownPublisher, msg, wp, lpFixed) case win.WM_LBUTTONUP, win.WM_MBUTTONUP, win.WM_RBUTTONUP: fixXInLP() tv.publishMouseEvent(&tv.mouseUpPublisher, msg, wp, lpFixed) case win.WM_MOUSEMOVE: fixXInLP() tv.publishMouseEvent(&tv.mouseMovePublisher, msg, wp, lpFixed) case win.WM_MOUSEWHEEL: fixXInLP() tv.publishMouseWheelEvent(&tv.mouseWheelPublisher, wp, lpFixed) } if maybeStretchLastColumn { if tv.lastColumnStretched && !tv.busyStretchingLastColumn { if normalVisColCount := tv.visibleColumnCount() - tv.visibleFrozenColumnCount(); normalVisColCount == 0 || normalVisColCount > 0 == (hwnd == tv.hwndNormalLV) { tv.busyStretchingLastColumn = true defer func() { tv.busyStretchingLastColumn = false }() tv.StretchLastColumn() } } if msg == win.WM_ERASEBKGND { return 1 } } return win.CallWindowProc(origWndProcPtr, hwnd, msg, wp, lp) } func tableViewHdrWndProc(hwnd win.HWND, msg uint32, wp, lp uintptr) uintptr { tv := (*TableView)(unsafe.Pointer(windowFromHandle(win.GetParent(win.GetParent(hwnd))).AsWindowBase())) var origWndProcPtr uintptr if hwnd == tv.hwndFrozenHdr { origWndProcPtr = tv.frozenHdrOrigWndProcPtr } else { origWndProcPtr = tv.normalHdrOrigWndProcPtr } switch msg { case win.WM_NOTIFY: switch ((*win.NMHDR)(unsafe.Pointer(lp))).Code { case win.NM_CUSTOMDRAW: if tv.customHeaderHeight == 0 { break } nmcd := (*win.NMCUSTOMDRAW)(unsafe.Pointer(lp)) switch nmcd.DwDrawStage { case win.CDDS_PREPAINT: return win.CDRF_NOTIFYITEMDRAW case win.CDDS_ITEMPREPAINT: return win.CDRF_NOTIFYPOSTPAINT case win.CDDS_ITEMPOSTPAINT: col := tv.fromLVColIdx(hwnd == tv.hwndFrozenHdr, int32(nmcd.DwItemSpec)) if tv.styler != nil && col > -1 { tv.style.row = -1 tv.style.col = col tv.style.bounds = rectangleFromRECT(nmcd.Rc) tv.style.dpi = tv.DPI() tv.style.hdc = nmcd.Hdc tv.style.TextColor = tv.themeNormalTextColor tv.style.Font = nil tv.styler.StyleCell(&tv.style) defer func() { tv.style.bounds = Rectangle{} if tv.style.canvas != nil { tv.style.canvas.Dispose() tv.style.canvas = nil } tv.style.hdc = 0 }() } return win.CDRF_DODEFAULT } return win.CDRF_DODEFAULT } case win.HDM_LAYOUT: if tv.customHeaderHeight == 0 { break } result := win.CallWindowProc(origWndProcPtr, hwnd, msg, wp, lp) hdl := (*win.HDLAYOUT)(unsafe.Pointer(lp)) hdl.Prc.Top = int32(tv.customHeaderHeight) hdl.Pwpos.Cy = int32(tv.customHeaderHeight) return result case win.WM_MOUSEMOVE, win.WM_LBUTTONDOWN, win.WM_LBUTTONUP, win.WM_MBUTTONDOWN, win.WM_MBUTTONUP, win.WM_RBUTTONDOWN, win.WM_RBUTTONUP: hti := win.HDHITTESTINFO{Pt: win.POINT{int32(win.GET_X_LPARAM(lp)), int32(win.GET_Y_LPARAM(lp))}} win.SendMessage(hwnd, win.HDM_HITTEST, 0, uintptr(unsafe.Pointer(&hti))) if hti.IItem == -1 { tv.group.toolTip.setText(hwnd, "") break } col := tv.fromLVColIdx(hwnd == tv.hwndFrozenHdr, hti.IItem) text := tv.columns.At(col).TitleEffective() var rc win.RECT if 0 == win.SendMessage(hwnd, win.HDM_GETITEMRECT, uintptr(hti.IItem), uintptr(unsafe.Pointer(&rc))) { tv.group.toolTip.setText(hwnd, "") break } size := calculateTextSize(text, tv.Font(), tv.DPI(), 0, hwnd) if size.Width <= rectangleFromRECT(rc).Width-int(win.SendMessage(hwnd, win.HDM_GETBITMAPMARGIN, 0, 0)) { tv.group.toolTip.setText(hwnd, "") break } if tv.group.toolTip.text(hwnd) == text { break } tv.group.toolTip.setText(hwnd, text) m := win.MSG{ HWnd: hwnd, Message: msg, WParam: wp, LParam: lp, Pt: hti.Pt, } tv.group.toolTip.SendMessage(win.TTM_RELAYEVENT, 0, uintptr(unsafe.Pointer(&m))) } return win.CallWindowProc(origWndProcPtr, hwnd, msg, wp, lp) } func (tv *TableView) WndProc(hwnd win.HWND, msg uint32, wp, lp uintptr) uintptr { switch msg { case win.WM_NOTIFY: nmh := (*win.NMHDR)(unsafe.Pointer(lp)) switch nmh.HwndFrom { case tv.hwndFrozenLV: return tableViewFrozenLVWndProc(nmh.HwndFrom, msg, wp, lp) case tv.hwndNormalLV: return tableViewNormalLVWndProc(nmh.HwndFrom, msg, wp, lp) } case win.WM_WINDOWPOSCHANGED: wp := (*win.WINDOWPOS)(unsafe.Pointer(lp)) if wp.Flags&win.SWP_NOSIZE != 0 { break } if tv.formActivatingHandle == -1 { if form := tv.Form(); form != nil { tv.formActivatingHandle = form.Activating().Attach(func() { if tv.hwndNormalLV == win.GetFocus() { win.SetFocus(tv.hwndFrozenLV) } }) } } tv.updateLVSizes() // FIXME: The InvalidateRect and redrawItems calls below prevent // painting glitches on resize. Though this seems to work reasonably // well, in the long run we would like to find the root cause of this // issue and come up with a better fix. dpi := uint32(tv.DPI()) var rc win.RECT vsbWidth := win.GetSystemMetricsForDpi(win.SM_CXVSCROLL, dpi) rc = win.RECT{wp.Cx - vsbWidth - 1, 0, wp.Cx, wp.Cy} win.InvalidateRect(tv.hWnd, &rc, true) hsbHeight := win.GetSystemMetricsForDpi(win.SM_CYHSCROLL, dpi) rc = win.RECT{0, wp.Cy - hsbHeight - 1, wp.Cx, wp.Cy} win.InvalidateRect(tv.hWnd, &rc, true) tv.redrawItems() case win.WM_TIMER: if !win.KillTimer(tv.hWnd, wp) { lastError("KillTimer") } switch wp { case tableViewCurrentIndexChangedTimerId: if !tv.delayedCurrentIndexChangedCanceled { tv.currentIndexChangedPublisher.Publish() tv.currentItemChangedPublisher.Publish() } case tableViewSelectedIndexesChangedTimerId: tv.selectedIndexesChangedPublisher.Publish() } case win.WM_MEASUREITEM: mis := (*win.MEASUREITEMSTRUCT)(unsafe.Pointer(lp)) mis.ItemHeight = uint32(tv.customRowHeight) ensureWindowLongBits(tv.hwndFrozenLV, win.GWL_STYLE, win.LVS_OWNERDRAWFIXED, false) ensureWindowLongBits(tv.hwndNormalLV, win.GWL_STYLE, win.LVS_OWNERDRAWFIXED, false) case win.WM_SETFOCUS: win.SetFocus(tv.hwndFrozenLV) case win.WM_DESTROY: // As we subclass all windows of system classes, we prevented the // clean-up code in the WM_NCDESTROY handlers of some windows from // being called. To fix this, we restore the original window // procedures here. if tv.frozenHdrOrigWndProcPtr != 0 { win.SetWindowLongPtr(tv.hwndFrozenHdr, win.GWLP_WNDPROC, tv.frozenHdrOrigWndProcPtr) } if tv.frozenLVOrigWndProcPtr != 0 { win.SetWindowLongPtr(tv.hwndFrozenLV, win.GWLP_WNDPROC, tv.frozenLVOrigWndProcPtr) } if tv.normalHdrOrigWndProcPtr != 0 { win.SetWindowLongPtr(tv.hwndNormalHdr, win.GWLP_WNDPROC, tv.normalHdrOrigWndProcPtr) } if tv.normalLVOrigWndProcPtr != 0 { win.SetWindowLongPtr(tv.hwndNormalLV, win.GWLP_WNDPROC, tv.normalLVOrigWndProcPtr) } } return tv.WidgetBase.WndProc(hwnd, msg, wp, lp) } func (tv *TableView) updateLVSizes() { tv.updateLVSizesWithSpecialCare(false) } func (tv *TableView) updateLVSizesWithSpecialCare(needSpecialCare bool) { var width int for i := tv.columns.Len() - 1; i >= 0; i-- { if col := tv.columns.At(i); col.frozen && col.visible { width += col.Width() } } dpi := tv.DPI() widthPixels := IntFrom96DPI(width, dpi) cb := tv.ClientBoundsPixels() win.MoveWindow(tv.hwndNormalLV, int32(widthPixels), 0, int32(cb.Width-widthPixels), int32(cb.Height), true) var sbh int if hasWindowLongBits(tv.hwndNormalLV, win.GWL_STYLE, win.WS_HSCROLL) { sbh = int(win.GetSystemMetricsForDpi(win.SM_CYHSCROLL, uint32(dpi))) } win.MoveWindow(tv.hwndFrozenLV, 0, 0, int32(widthPixels), int32(cb.Height-sbh), true) if needSpecialCare { tv.updateLVSizesNeedsSpecialCare = true } if tv.updateLVSizesNeedsSpecialCare { win.ShowWindow(tv.hwndNormalLV, win.SW_HIDE) win.ShowWindow(tv.hwndNormalLV, win.SW_SHOW) } if !needSpecialCare { tv.updateLVSizesNeedsSpecialCare = false } } func (*TableView) CreateLayoutItem(ctx *LayoutContext) LayoutItem { return NewGreedyLayoutItem() } func (tv *TableView) SetScrollbarOrientation(orientation Orientation) { tv.scrollbarOrientation = orientation } func (tv *TableView) ScrollbarOrientation() Orientation { return tv.scrollbarOrientation } ================================================ FILE: tableviewcolumn.go ================================================ // Copyright 2013 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" "github.com/lxn/win" ) // TableViewColumn represents a column in a TableView. type TableViewColumn struct { tv *TableView name string dataMember string alignment Alignment1D format string precision int title string titleOverride string width int lessFunc func(i, j int) bool formatFunc func(value interface{}) string visible bool frozen bool } // NewTableViewColumn returns a new TableViewColumn. func NewTableViewColumn() *TableViewColumn { return &TableViewColumn{ format: "%v", visible: true, width: 50, } } // Alignment returns the alignment of the TableViewColumn. func (tvc *TableViewColumn) Alignment() Alignment1D { return tvc.alignment } // SetAlignment sets the alignment of the TableViewColumn. func (tvc *TableViewColumn) SetAlignment(alignment Alignment1D) (err error) { if alignment == AlignDefault { alignment = AlignNear } if alignment == tvc.alignment { return nil } old := tvc.alignment defer func() { if err != nil { tvc.alignment = old } }() tvc.alignment = alignment return tvc.update() } // DataMember returns the data member this TableViewColumn is bound against. func (tvc *TableViewColumn) DataMember() string { return tvc.dataMember } // DataMemberEffective returns the effective data member this TableViewColumn is // bound against. func (tvc *TableViewColumn) DataMemberEffective() string { if tvc.dataMember != "" { return tvc.dataMember } return tvc.name } // SetDataMember sets the data member this TableViewColumn is bound against. func (tvc *TableViewColumn) SetDataMember(dataMember string) { tvc.dataMember = dataMember } // Format returns the format string for converting a value into a string. func (tvc *TableViewColumn) Format() string { return tvc.format } // SetFormat sets the format string for converting a value into a string. func (tvc *TableViewColumn) SetFormat(format string) (err error) { if format == tvc.format { return nil } old := tvc.format defer func() { if err != nil { tvc.format = old } }() tvc.format = format if tvc.tv == nil { return nil } return tvc.tv.Invalidate() } // Name returns the name of this TableViewColumn. func (tvc *TableViewColumn) Name() string { return tvc.name } // SetName sets the name of this TableViewColumn. func (tvc *TableViewColumn) SetName(name string) { tvc.name = name } // Precision returns the number of decimal places for formatting float32, // float64 or big.Rat values. func (tvc *TableViewColumn) Precision() int { return tvc.precision } // SetPrecision sets the number of decimal places for formatting float32, // float64 or big.Rat values. func (tvc *TableViewColumn) SetPrecision(precision int) (err error) { if precision == tvc.precision { return nil } old := tvc.precision defer func() { if err != nil { tvc.precision = old } }() tvc.precision = precision if tvc.tv == nil { return nil } return tvc.tv.Invalidate() } // Title returns the (default) text to display in the column header. func (tvc *TableViewColumn) Title() string { return tvc.title } // SetTitle sets the (default) text to display in the column header. func (tvc *TableViewColumn) SetTitle(title string) (err error) { if title == tvc.title { return nil } old := tvc.title defer func() { if err != nil { tvc.title = old } }() tvc.title = title return tvc.update() } // TitleOverride returns the (overridden by user) text to display in the column // header. func (tvc *TableViewColumn) TitleOverride() string { return tvc.titleOverride } // SetTitleOverride sets the (overridden by user) text to display in the column // header. func (tvc *TableViewColumn) SetTitleOverride(titleOverride string) (err error) { if titleOverride == tvc.titleOverride { return nil } old := tvc.titleOverride defer func() { if err != nil { tvc.titleOverride = old } }() tvc.titleOverride = titleOverride return tvc.update() } // TitleEffective returns the effective text to display in the column header. func (tvc *TableViewColumn) TitleEffective() string { if tvc.titleOverride != "" { return tvc.titleOverride } if tvc.title != "" { return tvc.title } return tvc.DataMemberEffective() } // Visible returns if the column is visible. func (tvc *TableViewColumn) Visible() bool { return tvc.visible } // SetVisible sets if the column is visible. func (tvc *TableViewColumn) SetVisible(visible bool) (err error) { if visible == tvc.visible { return nil } old := tvc.visible defer func() { if err != nil { tvc.visible = old } }() tvc.visible = visible if tvc.tv == nil { return nil } if visible { return tvc.create() } return tvc.destroy() } // Frozen returns if the column is frozen. func (tvc *TableViewColumn) Frozen() bool { return tvc.frozen } // SetFrozen sets if the column is frozen. func (tvc *TableViewColumn) SetFrozen(frozen bool) (err error) { if frozen == tvc.frozen { return nil } var checkBoxes bool if tvc.tv != nil { checkBoxes = tvc.tv.CheckBoxes() } old := tvc.frozen defer func() { if err != nil { tvc.frozen = old if tvc.tv != nil { tvc.create() } } if tvc.tv != nil { tvc.tv.hasFrozenColumn = tvc.tv.visibleFrozenColumnCount() > 0 tvc.tv.SetCheckBoxes(checkBoxes) tvc.tv.applyImageList() } }() if tvc.tv != nil && tvc.visible { if err = tvc.destroy(); err != nil { return } } tvc.frozen = frozen if tvc.tv != nil && tvc.visible { return tvc.create() } return nil } // Width returns the width of the column in pixels. func (tvc *TableViewColumn) Width() int { if tvc.tv == nil || !tvc.visible { return tvc.width } // We call win.SendMessage instead of tvc.sendMessage here, because some // call inside the latter interferes with scrolling via scroll bar button // when *TableViewColumn.Width is called from *TableView.StretchLastColumn. var hwnd win.HWND if tvc.frozen { hwnd = tvc.tv.hwndFrozenLV } else { hwnd = tvc.tv.hwndNormalLV } return tvc.tv.IntTo96DPI(int(win.SendMessage(hwnd, win.LVM_GETCOLUMNWIDTH, uintptr(tvc.indexInListView()), 0))) } // SetWidth sets the width of the column in pixels. func (tvc *TableViewColumn) SetWidth(width int) (err error) { if width == tvc.width { return nil } old := tvc.width defer func() { if err != nil { tvc.width = old } }() tvc.width = width return tvc.update() } // LessFunc returns the less func of this TableViewColumn. // // This function is used to provide custom sorting for models based on ReflectTableModel only. func (tvc *TableViewColumn) LessFunc() func(i, j int) bool { return tvc.lessFunc } // SetLessFunc sets the less func of this TableViewColumn. // // This function is used to provide custom sorting for models based on ReflectTableModel only. func (tvc *TableViewColumn) SetLessFunc(lessFunc func(i, j int) bool) { tvc.lessFunc = lessFunc } // FormatFunc returns the custom format func of this TableViewColumn. func (tvc *TableViewColumn) FormatFunc() func(value interface{}) string { return tvc.formatFunc } // FormatFunc sets the custom format func of this TableViewColumn. func (tvc *TableViewColumn) SetFormatFunc(formatFunc func(value interface{}) string) { tvc.formatFunc = formatFunc } func (tvc *TableViewColumn) indexInListView() int32 { if tvc.tv == nil { return -1 } var idx int32 for _, c := range tvc.tv.columns.items { if c.frozen != tvc.frozen { continue } if c == tvc { break } if c.visible { idx++ } } return idx } func (tvc *TableViewColumn) create() error { var lvc win.LVCOLUMN index := tvc.indexInListView() dpi := tvc.tv.DPI() lvc.Mask = win.LVCF_FMT | win.LVCF_WIDTH | win.LVCF_TEXT | win.LVCF_SUBITEM lvc.ISubItem = index lvc.PszText = syscall.StringToUTF16Ptr(tvc.TitleEffective()) if tvc.width > 0 { lvc.Cx = int32(IntFrom96DPI(tvc.width, dpi)) } else { lvc.Cx = int32(IntFrom96DPI(100, dpi)) } switch tvc.alignment { case AlignCenter: lvc.Fmt = 2 case AlignFar: lvc.Fmt = 1 } if -1 == int(tvc.sendMessage(win.LVM_INSERTCOLUMN, uintptr(index), uintptr(unsafe.Pointer(&lvc)))) { return newError("LVM_INSERTCOLUMN") } tvc.tv.updateLVSizes() return nil } func (tvc *TableViewColumn) destroy() error { width := tvc.Width() if win.FALSE == tvc.sendMessage(win.LVM_DELETECOLUMN, uintptr(tvc.indexInListView()), 0) { return newError("LVM_DELETECOLUMN") } tvc.width = width tvc.tv.updateLVSizes() return nil } func (tvc *TableViewColumn) update() error { if tvc.tv == nil || !tvc.visible { return nil } lvc := tvc.getLVCOLUMN() if win.FALSE == tvc.sendMessage(win.LVM_SETCOLUMN, uintptr(tvc.indexInListView()), uintptr(unsafe.Pointer(lvc))) { return newError("LVM_SETCOLUMN") } tvc.tv.updateLVSizes() return nil } func (tvc *TableViewColumn) getLVCOLUMN() *win.LVCOLUMN { var lvc win.LVCOLUMN dpi := 96 if tvc.tv != nil { dpi = tvc.tv.DPI() } else { dpi = screenDPI() } width := IntFrom96DPI(tvc.width, dpi) lvc.Mask = win.LVCF_FMT | win.LVCF_WIDTH | win.LVCF_TEXT | win.LVCF_SUBITEM lvc.ISubItem = int32(tvc.indexInListView()) lvc.PszText = syscall.StringToUTF16Ptr(tvc.TitleEffective()) lvc.Cx = int32(width) switch tvc.alignment { case AlignCenter: lvc.Fmt = 2 case AlignFar: lvc.Fmt = 1 } return &lvc } func (tvc *TableViewColumn) sendMessage(msg uint32, wp, lp uintptr) uintptr { if tvc.tv == nil { return 0 } tvc.tv.hasFrozenColumn = tvc.tv.visibleFrozenColumnCount() > 0 tvc.tv.SetCheckBoxes(tvc.tv.CheckBoxes()) tvc.tv.applyImageList() var hwnd win.HWND if tvc.frozen { hwnd = tvc.tv.hwndFrozenLV } else { hwnd = tvc.tv.hwndNormalLV } return win.SendMessage(hwnd, msg, wp, lp) } ================================================ FILE: tableviewcolumnlist.go ================================================ // Copyright 2013 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 type TableViewColumnList struct { tv *TableView items []*TableViewColumn } func newTableViewColumnList(tv *TableView) *TableViewColumnList { return &TableViewColumnList{tv: tv} } // Add adds a TableViewColumn to the end of the list. func (l *TableViewColumnList) Add(item *TableViewColumn) error { return l.Insert(len(l.items), item) } // At returns the TableViewColumn as the specified index. // // Bounds are not checked. func (l *TableViewColumnList) At(index int) *TableViewColumn { return l.items[index] } // ByName returns the TableViewColumn identified by name, or nil, if no column // of that name is contained in the TableViewColumnList. func (l *TableViewColumnList) ByName(name string) *TableViewColumn { for _, tvc := range l.items { if tvc.name == name { return tvc } } return nil } // Clear removes all TableViewColumns from the list. func (l *TableViewColumnList) Clear() error { for _ = range l.items { if err := l.RemoveAt(0); err != nil { return err } } return nil } // Index returns the index of the specified TableViewColumn or -1 if it is not // found. func (l *TableViewColumnList) Index(item *TableViewColumn) int { for i, lvi := range l.items { if lvi == item { return i } } return -1 } // Contains returns whether the specified TableViewColumn is found in the list. func (l *TableViewColumnList) Contains(item *TableViewColumn) bool { return l.Index(item) > -1 } // Insert inserts TableViewColumn item at position index. // // A TableViewColumn cannot be contained in multiple TableViewColumnLists at the // same time. func (l *TableViewColumnList) Insert(index int, item *TableViewColumn) error { if item.tv != nil { return newError("duplicate insert") } item.tv = l.tv if item.visible { if err := item.create(); err != nil { item.tv = nil return err } } l.items = append(l.items, nil) copy(l.items[index+1:], l.items[index:]) l.items[index] = item return nil } // Len returns the number of TableViewColumns in the list. func (l *TableViewColumnList) Len() int { return len(l.items) } // Remove removes the specified TableViewColumn from the list. func (l *TableViewColumnList) Remove(item *TableViewColumn) error { index := l.Index(item) if index == -1 { return nil } return l.RemoveAt(index) } // RemoveAt removes the TableViewColumn at position index. func (l *TableViewColumnList) RemoveAt(index int) error { tvc := l.items[index] if err := tvc.destroy(); err != nil { return err } tvc.tv = nil l.items = append(l.items[:index], l.items[index+1:]...) return nil } func (l *TableViewColumnList) unsetColumnsTV() { for _, tvc := range l.items { tvc.tv = nil } } ================================================ FILE: tabpage.go ================================================ // Copyright 2011 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 ( "github.com/lxn/win" ) const tabPageWindowClass = `\o/ Walk_TabPage_Class \o/` var tabPageBackgroundBrush Brush func init() { AppendToWalkInit(func() { MustRegisterWindowClass(tabPageWindowClass) tabPageBackgroundBrush, _ = NewSystemColorBrush(win.COLOR_WINDOW) }) } type TabPage struct { ContainerBase image Image title string tabWidget *TabWidget titleChangedPublisher EventPublisher imageChangedPublisher EventPublisher } func NewTabPage() (*TabPage, error) { tp := new(TabPage) if err := InitWindow( tp, nil, tabPageWindowClass, win.WS_POPUP, win.WS_EX_CONTROLPARENT); err != nil { return nil, err } tp.children = newWidgetList(tp) tp.MustRegisterProperty("Title", NewProperty( func() interface{} { return tp.Title() }, func(v interface{}) error { return tp.SetTitle(assertStringOr(v, "")) }, tp.titleChangedPublisher.Event())) tp.MustRegisterProperty("Image", NewProperty( func() interface{} { return tp.Image() }, func(v interface{}) error { img, err := ImageFrom(v) if err != nil { return err } return tp.SetImage(img) }, tp.imageChangedPublisher.Event())) return tp, nil } func (tp *TabPage) Enabled() bool { if tp.tabWidget != nil { return tp.tabWidget.Enabled() && tp.enabled } return tp.enabled } func (tp *TabPage) Background() Brush { if tp.background != nil { return tp.background } else if tp.tabWidget != nil && tp.tabWidget.background == nullBrushSingleton { return nullBrushSingleton } if win.IsAppThemed() { return tabPageBackgroundBrush } return nil } func (tp *TabPage) Font() *Font { if tp.font != nil { return tp.font } else if tp.tabWidget != nil { return tp.tabWidget.Font() } return defaultFont } func (tp *TabPage) Image() Image { return tp.image } func (tp *TabPage) SetImage(value Image) error { tp.image = value if tp.tabWidget == nil { return nil } return tp.tabWidget.onPageChanged(tp) } func (tp *TabPage) Title() string { return tp.title } func (tp *TabPage) SetTitle(value string) error { tp.title = value tp.titleChangedPublisher.Publish() if tp.tabWidget == nil { return nil } return tp.tabWidget.onPageChanged(tp) } ================================================ FILE: tabpagelist.go ================================================ // Copyright 2011 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 ( "github.com/lxn/win" ) type tabPageListObserver interface { onInsertingPage(index int, page *TabPage) error onInsertedPage(index int, page *TabPage) error onRemovingPage(index int, page *TabPage) error onRemovedPage(index int, page *TabPage) error onClearingPages(pages []*TabPage) error onClearedPages(pages []*TabPage) error } type TabPageList struct { items []*TabPage observer tabPageListObserver } func newTabPageList(observer tabPageListObserver) *TabPageList { return &TabPageList{observer: observer} } func (l *TabPageList) Add(item *TabPage) error { return l.Insert(len(l.items), item) } func (l *TabPageList) At(index int) *TabPage { return l.items[index] } func (l *TabPageList) Clear() error { observer := l.observer if observer != nil { if err := observer.onClearingPages(l.items); err != nil { return err } } oldItems := l.items l.items = l.items[:0] if observer != nil { if err := observer.onClearedPages(oldItems); err != nil { l.items = oldItems return err } } return nil } func (l *TabPageList) Index(item *TabPage) int { for i, lvi := range l.items { if lvi == item { return i } } return -1 } func (l *TabPageList) Contains(item *TabPage) bool { return l.Index(item) > -1 } func (l *TabPageList) indexHandle(handle win.HWND) int { for i, page := range l.items { if page.Handle() == handle { return i } } return -1 } func (l *TabPageList) containsHandle(handle win.HWND) bool { return l.indexHandle(handle) > -1 } func (l *TabPageList) insertIntoSlice(index int, item *TabPage) { l.items = append(l.items, nil) copy(l.items[index+1:], l.items[index:]) l.items[index] = item } func (l *TabPageList) Insert(index int, item *TabPage) error { observer := l.observer if observer != nil { if err := observer.onInsertingPage(index, item); err != nil { return err } } l.insertIntoSlice(index, item) if observer != nil { if err := observer.onInsertedPage(index, item); err != nil { l.items = append(l.items[:index], l.items[index+1:]...) return err } } item.RequestLayout() return nil } func (l *TabPageList) Len() int { return len(l.items) } func (l *TabPageList) Remove(item *TabPage) error { index := l.Index(item) if index == -1 { return nil } return l.RemoveAt(index) } func (l *TabPageList) RemoveAt(index int) error { observer := l.observer item := l.items[index] if observer != nil { if err := observer.onRemovingPage(index, item); err != nil { return err } } l.items = append(l.items[:index], l.items[index+1:]...) if observer != nil { if err := observer.onRemovedPage(index, item); err != nil { l.insertIntoSlice(index, item) return err } } return nil } ================================================ FILE: tabwidget.go ================================================ // Copyright 2011 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 ( "strconv" "syscall" "unsafe" "github.com/lxn/win" ) const tabWidgetWindowClass = `\o/ Walk_TabWidget_Class \o/` func init() { AppendToWalkInit(func() { MustRegisterWindowClass(tabWidgetWindowClass) tabWidgetTabWndProcPtr = syscall.NewCallback(tabWidgetTabWndProc) }) } type TabWidget struct { WidgetBase hWndTab win.HWND tabOrigWndProcPtr uintptr imageList *ImageList pages *TabPageList currentIndex int currentIndexChangedPublisher EventPublisher nonClientSizePixels Size persistent bool } func NewTabWidget(parent Container) (*TabWidget, error) { tw := &TabWidget{currentIndex: -1} tw.pages = newTabPageList(tw) if err := InitWidget( tw, parent, tabWidgetWindowClass, win.WS_VISIBLE, win.WS_EX_CONTROLPARENT); err != nil { return nil, err } succeeded := false defer func() { if !succeeded { tw.Dispose() } }() tw.SetPersistent(true) tw.hWndTab = win.CreateWindowEx( 0, syscall.StringToUTF16Ptr("SysTabControl32"), nil, win.WS_CHILD|win.WS_CLIPSIBLINGS|win.WS_TABSTOP|win.WS_VISIBLE, 0, 0, 0, 0, tw.hWnd, 0, 0, nil) if tw.hWndTab == 0 { return nil, lastError("CreateWindowEx") } win.SetWindowLongPtr(tw.hWndTab, win.GWLP_USERDATA, uintptr(unsafe.Pointer(tw))) tw.tabOrigWndProcPtr = win.SetWindowLongPtr(tw.hWndTab, win.GWLP_WNDPROC, tabWidgetTabWndProcPtr) dpi := int(win.GetDpiForWindow(tw.hWndTab)) win.SendMessage(tw.hWndTab, win.WM_SETFONT, uintptr(defaultFont.handleForDPI(dpi)), 1) tw.applyFont(tw.Font()) tw.MustRegisterProperty("HasCurrentPage", NewReadOnlyBoolProperty( func() bool { return tw.CurrentIndex() != -1 }, tw.CurrentIndexChanged())) tw.MustRegisterProperty("CurrentIndex", NewProperty( func() interface{} { return tw.CurrentIndex() }, func(v interface{}) error { return tw.SetCurrentIndex(assertIntOr(v, -1)) }, tw.CurrentIndexChanged())) succeeded = true return tw, nil } func (tw *TabWidget) Dispose() { tw.WidgetBase.Dispose() if tw.imageList != nil { tw.imageList.Dispose() tw.imageList = nil } } func (tw *TabWidget) applyEnabled(enabled bool) { tw.WidgetBase.applyEnabled(enabled) setWindowEnabled(tw.hWndTab, enabled) applyEnabledToDescendants(tw, enabled) } func (tw *TabWidget) applyFont(font *Font) { tw.WidgetBase.applyFont(font) SetWindowFont(tw.hWndTab, font) // FIXME: won't work with ApplyDPI // applyFontToDescendants(tw, font) } func (tw *TabWidget) ApplyDPI(dpi int) { tw.WidgetBase.ApplyDPI(dpi) var maskColor Color var size Size if tw.imageList != nil { maskColor = tw.imageList.maskColor size = SizeFrom96DPI(tw.imageList.imageSize96dpi, dpi) } else { size = SizeFrom96DPI(Size{16, 16}, dpi) } iml, err := NewImageListForDPI(size, maskColor, dpi) if err != nil { return } win.SendMessage(tw.hWndTab, win.TCM_SETIMAGELIST, 0, uintptr(iml.hIml)) if tw.imageList != nil { tw.imageList.Dispose() } tw.imageList = iml for _, page := range tw.pages.items { tw.onPageChanged(page) } } func (tw *TabWidget) CurrentIndex() int { return tw.currentIndex } func (tw *TabWidget) SetCurrentIndex(index int) error { if index == tw.currentIndex { return nil } if index < 0 || index >= tw.pages.Len() { return newError("invalid index") } ret := int(win.SendMessage(tw.hWndTab, win.TCM_SETCURSEL, uintptr(index), 0)) if ret == -1 { return newError("SendMessage(TCM_SETCURSEL) failed") } // FIXME: The SendMessage(TCM_SETCURSEL) call above doesn't cause a // TCN_SELCHANGE notification, so we use this workaround. tw.onSelChange() return nil } func (tw *TabWidget) CurrentIndexChanged() *Event { return tw.currentIndexChangedPublisher.Event() } func (tw *TabWidget) Pages() *TabPageList { return tw.pages } func (tw *TabWidget) Persistent() bool { return tw.persistent } func (tw *TabWidget) SetPersistent(value bool) { tw.persistent = value } func (tw *TabWidget) SaveState() error { tw.WriteState(strconv.Itoa(tw.CurrentIndex())) for _, page := range tw.pages.items { if err := page.SaveState(); err != nil { return err } } return nil } func (tw *TabWidget) RestoreState() error { state, err := tw.ReadState() if err != nil { return err } if state == "" { return nil } index, err := strconv.Atoi(state) if err != nil { return err } if index >= 0 && index < tw.pages.Len() { if err := tw.SetCurrentIndex(index); err != nil { return err } } for _, page := range tw.pages.items { if err := page.RestoreState(); err != nil { return err } } return nil } func (tw *TabWidget) resizePages() { bounds := tw.pageBounds() for _, page := range tw.pages.items { page.SetBoundsPixels(bounds) } } // pageBounds returns page bounds in native pixels. func (tw *TabWidget) pageBounds() Rectangle { var r win.RECT if !win.GetWindowRect(tw.hWndTab, &r) { lastError("GetWindowRect") return Rectangle{} } p := win.POINT{ r.Left, r.Top, } if !win.ScreenToClient(tw.hWnd, &p) { newError("ScreenToClient failed") return Rectangle{} } r = win.RECT{ p.X, p.Y, r.Right - r.Left + p.X, r.Bottom - r.Top + p.Y, } win.SendMessage(tw.hWndTab, win.TCM_ADJUSTRECT, 0, uintptr(unsafe.Pointer(&r))) adjustment := 2 * int32(tw.IntFrom96DPI(1)) return Rectangle{ int(r.Left - adjustment), int(r.Top), int(r.Right - r.Left + adjustment), int(r.Bottom - r.Top), } } func (tw *TabWidget) onResize(width, height int32) { if !win.MoveWindow(tw.hWndTab, 0, 0, width, height, true) { lastError("MoveWindow") return } tw.resizePages() } func (tw *TabWidget) onSelChange() { pageCount := tw.pages.Len() if tw.currentIndex > -1 && tw.currentIndex < pageCount { page := tw.pages.At(tw.currentIndex) page.SetVisible(false) } tw.currentIndex = int(int32(win.SendMessage(tw.hWndTab, win.TCM_GETCURSEL, 0, 0))) if tw.currentIndex > -1 && tw.currentIndex < pageCount { page := tw.pages.At(tw.currentIndex) page.SetVisible(true) tw.RequestLayout() page.Invalidate() var containsFocus bool tw.forEachDescendantRaw(uintptr(win.GetFocus()), func(hwnd win.HWND, lParam uintptr) bool { if hwnd == win.HWND(lParam) { containsFocus = true } return !containsFocus }) if containsFocus { tw.pages.At(tw.currentIndex).focusFirstCandidateDescendant() } } tw.Invalidate() tw.currentIndexChangedPublisher.Publish() } func (tw *TabWidget) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { if tw.hWndTab != 0 { switch msg { case win.WM_ERASEBKGND: return 1 case win.WM_WINDOWPOSCHANGED: wp := (*win.WINDOWPOS)(unsafe.Pointer(lParam)) if wp.Flags&win.SWP_NOSIZE != 0 { break } tw.onResize(wp.Cx, wp.Cy) case win.WM_NOTIFY: nmhdr := (*win.NMHDR)(unsafe.Pointer(lParam)) switch int32(nmhdr.Code) { case win.TCN_SELCHANGE: tw.onSelChange() } } } return tw.WidgetBase.WndProc(hwnd, msg, wParam, lParam) } var tabWidgetTabWndProcPtr uintptr func tabWidgetTabWndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { tw := (*TabWidget)(unsafe.Pointer(win.GetWindowLongPtr(hwnd, win.GWLP_USERDATA))) switch msg { case win.WM_MOUSEMOVE: win.InvalidateRect(hwnd, nil, true) case win.WM_ERASEBKGND: return 1 case win.WM_PAINT: var ps win.PAINTSTRUCT hdc := win.BeginPaint(hwnd, &ps) defer win.EndPaint(hwnd, &ps) cb := tw.ClientBoundsPixels() dpi := tw.DPI() bitmap, err := NewBitmapForDPI(cb.Size(), dpi) if err != nil { break } defer bitmap.Dispose() canvas, err := NewCanvasFromImage(bitmap) if err != nil { break } defer canvas.Dispose() themed := win.IsAppThemed() if !themed { if err := canvas.FillRectanglePixels(sysColorBtnFaceBrush, cb); err != nil { break } } win.SendMessage(hwnd, win.WM_PRINTCLIENT, uintptr(canvas.hdc), uintptr(win.PRF_CLIENT|win.PRF_CHILDREN|win.PRF_ERASEBKGND)) parent := tw.Parent() if parent == nil { break } // Draw background of free area not occupied by tab items. if bg, wnd := parent.AsWindowBase().backgroundEffective(); bg != nil { tw.prepareDCForBackground(canvas.hdc, hwnd, wnd) hRgn := win.CreateRectRgn(0, 0, 0, 0) defer win.DeleteObject(win.HGDIOBJ(hRgn)) var rc win.RECT adjustment := SizeFrom96DPI(Size{1, 1}, dpi).toSIZE() count := tw.pages.Len() for i := 0; i < count; i++ { if 0 == win.SendMessage(hwnd, win.TCM_GETITEMRECT, uintptr(i), uintptr(unsafe.Pointer(&rc))) { break } if i == tw.currentIndex { rc.Left -= 2 * adjustment.CX rc.Top -= 2 * adjustment.CY rc.Right += 2 * adjustment.CX } else { if i == count-1 && themed { rc.Right -= 2 * adjustment.CX } } hRgnTab := win.CreateRectRgn(rc.Left, rc.Top, rc.Right, rc.Bottom) win.CombineRgn(hRgn, hRgn, hRgnTab, win.RGN_OR) win.DeleteObject(win.HGDIOBJ(hRgnTab)) } hRgnRC := win.CreateRectRgn(0, 0, int32(cb.Width), rc.Bottom) win.CombineRgn(hRgn, hRgnRC, hRgn, win.RGN_DIFF) win.DeleteObject(win.HGDIOBJ(hRgnRC)) if !win.FillRgn(canvas.hdc, hRgn, bg.handle()) { break } } // Draw current tab item. if tw.currentIndex != -1 { page := tw.pages.At(tw.CurrentIndex()) if bg, wnd := page.AsWindowBase().backgroundEffective(); bg != nil && bg != tabPageBackgroundBrush && (page.layout == nil || !page.layout.Margins().isZero()) { tw.prepareDCForBackground(canvas.hdc, hwnd, wnd) var rc win.RECT if 0 == win.SendMessage(hwnd, win.TCM_GETITEMRECT, uintptr(tw.currentIndex), uintptr(unsafe.Pointer(&rc))) { break } adjustment := SizeFrom96DPI(Size{6, 1}, dpi).toSIZE() hRgn := win.CreateRectRgn(rc.Left, rc.Top, rc.Right, rc.Bottom+2*adjustment.CY) defer win.DeleteObject(win.HGDIOBJ(hRgn)) if !win.FillRgn(canvas.hdc, hRgn, bg.handle()) { break } if page.image != nil { x := rc.Left + adjustment.CX y := rc.Top s := int32(IntFrom96DPI(16, dpi)) bmp, err := iconCache.Bitmap(page.image, dpi) if err == nil { if imageCanvas, err := NewCanvasFromImage(bmp); err == nil { defer imageCanvas.Dispose() if !win.TransparentBlt( canvas.hdc, x, y, s, s, imageCanvas.hdc, 0, 0, int32(bmp.size.Width), int32(bmp.size.Height), 0) { break } } rc.Left += s + adjustment.CX } } rc.Left += adjustment.CX rc.Top += adjustment.CY title := syscall.StringToUTF16(page.title) if themed { hTheme := win.OpenThemeData(hwnd, syscall.StringToUTF16Ptr("tab")) defer win.CloseThemeData(hTheme) options := win.DTTOPTS{DwFlags: win.DTT_GLOWSIZE, IGlowSize: int32(IntFrom96DPI(3, dpi))} options.DwSize = uint32(unsafe.Sizeof(options)) if hr := win.DrawThemeTextEx(hTheme, canvas.hdc, 0, win.TIS_SELECTED, &title[0], int32(len(title)), 0, &rc, &options); !win.SUCCEEDED(hr) { break } } else { if 0 == win.DrawTextEx(canvas.hdc, &title[0], int32(len(title)), &rc, 0, nil) { break } } } } if !win.BitBlt(hdc, 0, 0, int32(cb.Width), int32(cb.Height), canvas.hdc, 0, 0, win.SRCCOPY) { break } return 0 } return win.CallWindowProc(tw.tabOrigWndProcPtr, hwnd, msg, wParam, lParam) } func (tw *TabWidget) onPageChanged(page *TabPage) (err error) { index := tw.pages.Index(page) item := tw.tcitemFromPage(page) if 0 == win.SendMessage(tw.hWndTab, win.TCM_SETITEM, uintptr(index), uintptr(unsafe.Pointer(item))) { return newError("SendMessage(TCM_SETITEM) failed") } tw.updateNonClientSize() return nil } func (tw *TabWidget) onInsertingPage(index int, page *TabPage) (err error) { return nil } func (tw *TabWidget) onInsertedPage(index int, page *TabPage) (err error) { item := tw.tcitemFromPage(page) if idx := int(win.SendMessage(tw.hWndTab, win.TCM_INSERTITEM, uintptr(index), uintptr(unsafe.Pointer(item)))); idx == -1 { return newError("SendMessage(TCM_INSERTITEM) failed") } page.SetVisible(false) style := uint32(win.GetWindowLong(page.hWnd, win.GWL_STYLE)) if style == 0 { return lastError("GetWindowLong") } style |= win.WS_CHILD style &^= win.WS_POPUP win.SetLastError(0) if win.SetWindowLong(page.hWnd, win.GWL_STYLE, int32(style)) == 0 { return lastError("SetWindowLong") } if win.SetParent(page.hWnd, tw.hWnd) == 0 { return lastError("SetParent") } if tw.pages.Len() == 1 { page.SetVisible(true) tw.SetCurrentIndex(0) } tw.resizePages() page.tabWidget = tw page.applyFont(tw.Font()) tw.Invalidate() return } func (tw *TabWidget) removePage(page *TabPage) (err error) { page.SetVisible(false) style := uint32(win.GetWindowLong(page.hWnd, win.GWL_STYLE)) if style == 0 { return lastError("GetWindowLong") } style &^= win.WS_CHILD style |= win.WS_POPUP win.SetLastError(0) if win.SetWindowLong(page.hWnd, win.GWL_STYLE, int32(style)) == 0 { return lastError("SetWindowLong") } page.tabWidget = nil return page.SetParent(nil) } func (tw *TabWidget) onRemovingPage(index int, page *TabPage) (err error) { return nil } func (tw *TabWidget) onRemovedPage(index int, page *TabPage) (err error) { err = tw.removePage(page) if err != nil { return } win.SendMessage(tw.hWndTab, win.TCM_DELETEITEM, uintptr(index), 0) if tw.pages.Len() > 0 { tw.currentIndex = 0 win.SendMessage(tw.hWndTab, win.TCM_SETCURSEL, uintptr(tw.currentIndex), 0) } else { tw.currentIndex = -1 } tw.onSelChange() return // FIXME: Either make use of this unreachable code or remove it. if index == tw.currentIndex { // removal of current visible tabpage... tw.currentIndex = -1 // select new tabpage if any : if tw.pages.Len() > 0 { // are we removing the rightmost page ? if index == tw.pages.Len()-1 { // If so, select the page on the left index -= 1 } } } tw.SetCurrentIndex(index) tw.Invalidate() return } func (tw *TabWidget) onClearingPages(pages []*TabPage) (err error) { return nil } func (tw *TabWidget) onClearedPages(pages []*TabPage) (err error) { win.SendMessage(tw.hWndTab, win.TCM_DELETEALLITEMS, 0, 0) for _, page := range pages { tw.removePage(page) } tw.currentIndex = -1 tw.Invalidate() return nil } func (tw *TabWidget) tcitemFromPage(page *TabPage) *win.TCITEM { var imageIndex int32 = -1 if page.image != nil { if bmp, err := iconCache.Bitmap(page.image, tw.DPI()); err == nil { imageIndex, _ = tw.imageIndex(bmp) } } text := syscall.StringToUTF16(page.title) item := &win.TCITEM{ Mask: win.TCIF_IMAGE | win.TCIF_TEXT, IImage: imageIndex, PszText: &text[0], CchTextMax: int32(len(text)), } return item } func (tw *TabWidget) imageIndex(image *Bitmap) (index int32, err error) { index = -1 if image != nil { if tw.imageList == nil { dpi := tw.DPI() if tw.imageList, err = NewImageListForDPI(SizeFrom96DPI(Size{16, 16}, dpi), 0, dpi); err != nil { return } win.SendMessage(tw.hWndTab, win.TCM_SETIMAGELIST, 0, uintptr(tw.imageList.hIml)) } if index, err = tw.imageList.AddMasked(image); err != nil { return } } return } func (tw *TabWidget) updateNonClientSize() { rc := win.RECT{Right: 1000, Bottom: 1000} win.SendMessage(tw.hWndTab, win.TCM_ADJUSTRECT, 1, uintptr(unsafe.Pointer(&rc))) tw.nonClientSizePixels.Width = int(rc.Right-rc.Left) - 1000 tw.nonClientSizePixels.Height = int(rc.Bottom-rc.Top) - 1000 } func (tw *TabWidget) CreateLayoutItem(ctx *LayoutContext) LayoutItem { pages := make([]LayoutItem, tw.pages.Len()) bounds := tw.pageBounds() li := &tabWidgetLayoutItem{ pagePos: bounds.Location(), currentIndex: tw.CurrentIndex(), nonClientSizePixels: tw.nonClientSizePixels, } for i := tw.pages.Len() - 1; i >= 0; i-- { var page LayoutItem if p := tw.pages.At(i); p.Layout() != nil { page = CreateLayoutItemsForContainerWithContext(p, ctx) } else { page = NewGreedyLayoutItem() } lib := page.AsLayoutItemBase() lib.ctx = ctx lib.parent = li pages[i] = page } li.children = pages return li } type tabWidgetLayoutItem struct { ContainerLayoutItemBase nonClientSizePixels Size pagePos Point // in native pixels currentIndex int } func (li *tabWidgetLayoutItem) LayoutFlags() LayoutFlags { if len(li.children) == 0 { return ShrinkableHorz | ShrinkableVert | GrowableHorz | GrowableVert | GreedyHorz | GreedyVert } var flags LayoutFlags for _, page := range li.children { flags |= page.LayoutFlags() } return flags } func (li *tabWidgetLayoutItem) MinSize() Size { if len(li.children) == 0 { return Size{} } var min Size for _, page := range li.children { if ms, ok := page.(MinSizer); ok { s := ms.MinSize() min.Width = maxi(min.Width, s.Width) min.Height = maxi(min.Height, s.Height) } } return Size{min.Width + li.nonClientSizePixels.Width, min.Height + li.nonClientSizePixels.Height} } func (li *tabWidgetLayoutItem) MinSizeForSize(size Size) Size { return li.MinSize() } func (li *tabWidgetLayoutItem) HasHeightForWidth() bool { if len(li.children) == 0 { return false } for _, page := range li.children { if hfw, ok := page.(HeightForWidther); ok && hfw.HasHeightForWidth() { return true } } return false } func (li *tabWidgetLayoutItem) HeightForWidth(width int) int { if len(li.children) == 0 { return 0 } var height int margin := li.geometry.Size pageSize := li.children[0].Geometry().Size margin.Width -= pageSize.Width margin.Height -= pageSize.Height for _, page := range li.children { if hfw, ok := page.(HeightForWidther); ok && hfw.HasHeightForWidth() { h := hfw.HeightForWidth(width + margin.Width) height = maxi(height, h) } } return height + margin.Height } func (li *tabWidgetLayoutItem) IdealSize() Size { return li.MinSize() } func (li *tabWidgetLayoutItem) PerformLayout() []LayoutResultItem { if li.currentIndex > -1 { page := li.children[li.currentIndex] adjustment := IntFrom96DPI(1, li.ctx.dpi) return []LayoutResultItem{ { Item: page, Bounds: Rectangle{ X: li.pagePos.X, Y: li.pagePos.Y, Width: li.geometry.Size.Width - li.pagePos.X*2 - adjustment, Height: li.geometry.Size.Height - li.pagePos.Y - 2*adjustment, }, }, } } return nil } ================================================ FILE: textedit.go ================================================ // 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 ( "sync" "syscall" "unsafe" "github.com/lxn/win" ) type TextEdit struct { WidgetBase readOnlyChangedPublisher EventPublisher textChangedPublisher EventPublisher textColor Color compactHeight bool margins Size // in native pixels lastHeight int origWordbreakProcPtr uintptr } func NewTextEdit(parent Container) (*TextEdit, error) { return NewTextEditWithStyle(parent, 0) } func NewTextEditWithStyle(parent Container, style uint32) (*TextEdit, error) { te := new(TextEdit) if err := InitWidget( te, parent, "EDIT", win.WS_TABSTOP|win.WS_VISIBLE|win.ES_MULTILINE|win.ES_WANTRETURN|style, win.WS_EX_CLIENTEDGE); err != nil { return nil, err } te.origWordbreakProcPtr = te.SendMessage(win.EM_GETWORDBREAKPROC, 0, 0) te.GraphicsEffects().Add(InteractionEffect) te.GraphicsEffects().Add(FocusEffect) te.MustRegisterProperty("ReadOnly", NewProperty( func() interface{} { return te.ReadOnly() }, func(v interface{}) error { return te.SetReadOnly(v.(bool)) }, te.readOnlyChangedPublisher.Event())) te.MustRegisterProperty("Text", NewProperty( func() interface{} { return te.Text() }, func(v interface{}) error { return te.SetText(assertStringOr(v, "")) }, te.textChangedPublisher.Event())) return te, nil } func (te *TextEdit) applyFont(font *Font) { te.WidgetBase.applyFont(font) te.updateMargins() } func (te *TextEdit) updateMargins() { // 56 works at least from 96 to 192 DPI, so until a better solution comes up, this is it. defaultSize := te.dialogBaseUnitsToPixels(Size{56, 12}) var rc win.RECT te.SendMessage(win.EM_GETRECT, 0, uintptr(unsafe.Pointer(&rc))) if te.hasExtendedStyleBits(win.WS_EX_CLIENTEDGE) { width := te.WidthPixels() if width == 0 { width = defaultSize.Width } te.margins.Width = width - int(rc.Right-rc.Left) } else { te.margins.Width = int(rc.Left) * 2 } lineHeight := te.calculateTextSizeImpl("gM").Height te.margins.Height = defaultSize.Height - lineHeight } var drawTextCompatibleEditWordbreakProcPtr uintptr func init() { AppendToWalkInit(func() { drawTextCompatibleEditWordbreakProcPtr = syscall.NewCallback(drawTextCompatibleEditWordbreakProc) }) } func drawTextCompatibleEditWordbreakProc(lpch *uint16, ichCurrent, cch, code uintptr) uintptr { switch code { case win.WB_LEFT: for i := int(ichCurrent); i >= 0; i-- { if *(*uint16)(unsafe.Pointer(uintptr(unsafe.Pointer(lpch)) + uintptr(i)*2)) == 32 { return uintptr(i) } } case win.WB_RIGHT: for i := int(ichCurrent); i < int(cch); i++ { if *(*uint16)(unsafe.Pointer(uintptr(unsafe.Pointer(lpch)) + uintptr(i)*2)) == 32 { return uintptr(i) } } case win.WB_ISDELIMITER: if *(*uint16)(unsafe.Pointer(uintptr(unsafe.Pointer(lpch)) + ichCurrent*2)) == 32 { return 1 } } return 0 } func (te *TextEdit) Text() string { return te.text() } func (te *TextEdit) TextLength() int { return int(te.SendMessage(win.WM_GETTEXTLENGTH, 0, 0)) } func (te *TextEdit) SetText(text string) (err error) { if text == te.Text() { return nil } var oldLineCount int if te.compactHeight { oldLineCount = int(te.SendMessage(win.EM_GETLINECOUNT, 0, 0)) } err = te.setText(text) if te.compactHeight { if newLineCount := int(te.SendMessage(win.EM_GETLINECOUNT, 0, 0)); newLineCount != oldLineCount { te.RequestLayout() } } te.textChangedPublisher.Publish() return } func (te *TextEdit) CompactHeight() bool { return te.compactHeight } func (te *TextEdit) SetCompactHeight(enabled bool) { if enabled == te.compactHeight { return } te.compactHeight = enabled var ptr uintptr if enabled { te.updateMargins() ptr = drawTextCompatibleEditWordbreakProcPtr } else { ptr = te.origWordbreakProcPtr } te.SendMessage(win.EM_SETWORDBREAKPROC, 0, ptr) te.RequestLayout() } func (te *TextEdit) TextAlignment() Alignment1D { switch win.GetWindowLong(te.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 (te *TextEdit) 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 te.setAndClearStyleBits(bit, win.ES_LEFT|win.ES_CENTER|win.ES_RIGHT) } func (te *TextEdit) MaxLength() int { return int(te.SendMessage(win.EM_GETLIMITTEXT, 0, 0)) } func (te *TextEdit) SetMaxLength(value int) { te.SendMessage(win.EM_SETLIMITTEXT, uintptr(value), 0) } func (te *TextEdit) ScrollToCaret() { te.SendMessage(win.EM_SCROLLCARET, 0, 0) } func (te *TextEdit) TextSelection() (start, end int) { te.SendMessage(win.EM_GETSEL, uintptr(unsafe.Pointer(&start)), uintptr(unsafe.Pointer(&end))) return } func (te *TextEdit) SetTextSelection(start, end int) { te.SendMessage(win.EM_SETSEL, uintptr(start), uintptr(end)) } func (te *TextEdit) ReplaceSelectedText(text string, canUndo bool) { te.SendMessage(win.EM_REPLACESEL, uintptr(win.BoolToBOOL(canUndo)), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(text)))) } func (te *TextEdit) AppendText(value string) { s, e := te.TextSelection() l := te.TextLength() te.SetTextSelection(l, l) te.ReplaceSelectedText(value, false) te.SetTextSelection(s, e) } func (te *TextEdit) ReadOnly() bool { return te.hasStyleBits(win.ES_READONLY) } func (te *TextEdit) SetReadOnly(readOnly bool) error { if 0 == te.SendMessage(win.EM_SETREADONLY, uintptr(win.BoolToBOOL(readOnly)), 0) { return newError("SendMessage(EM_SETREADONLY)") } te.readOnlyChangedPublisher.Publish() return nil } func (te *TextEdit) TextChanged() *Event { return te.textChangedPublisher.Event() } func (te *TextEdit) TextColor() Color { return te.textColor } func (te *TextEdit) SetTextColor(c Color) { te.textColor = c te.Invalidate() } // ContextMenuLocation returns carret position in screen coordinates in native pixels. func (te *TextEdit) ContextMenuLocation() Point { idx := int(te.SendMessage(win.EM_GETCARETINDEX, 0, 0)) if idx < 0 { start, end := te.TextSelection() idx = (start + end) / 2 } res := uint32(te.SendMessage(win.EM_POSFROMCHAR, uintptr(idx), 0)) pt := win.POINT{int32(win.LOWORD(res)), int32(win.HIWORD(res))} windowTrimToClientBounds(te.hWnd, &pt) return pointPixelsFromPOINT(pt) } func (*TextEdit) NeedsWmSize() bool { return true } func (te *TextEdit) 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: if te.compactHeight { if createLayoutItemForWidget(te).(MinSizer).MinSize().Height != te.HeightPixels() { te.RequestLayout() } } te.textChangedPublisher.Publish() } case win.WM_GETDLGCODE: if wParam == win.VK_RETURN { return win.DLGC_WANTALLKEYS } return win.DLGC_HASSETSEL | win.DLGC_WANTARROWS | win.DLGC_WANTCHARS case win.WM_KEYDOWN: if Key(wParam) == KeyA && ControlDown() { te.SetTextSelection(0, -1) } } return te.WidgetBase.WndProc(hwnd, msg, wParam, lParam) } func (te *TextEdit) CreateLayoutItem(ctx *LayoutContext) LayoutItem { if te.margins.Width <= 0 { te.updateMargins() } return &textEditLayoutItem{ width2Height: make(map[int]int), compactHeight: te.compactHeight, margins: te.margins, text: te.Text(), font: te.Font(), minWidth: te.calculateTextSizeImpl("W").Width, nonCompactHeightMinSize: te.dialogBaseUnitsToPixels(Size{20, 12}), } } type textEditLayoutItem struct { LayoutItemBase mutex sync.Mutex width2Height map[int]int // in native pixels nonCompactHeightMinSize Size // in native pixels margins Size // in native pixels text string font *Font minWidth int // in native pixels compactHeight bool } func (li *textEditLayoutItem) LayoutFlags() LayoutFlags { flags := ShrinkableHorz | GrowableHorz | GreedyHorz if !li.compactHeight { flags |= GreedyVert | GrowableVert | ShrinkableVert } return flags } func (li *textEditLayoutItem) IdealSize() Size { if li.compactHeight { return li.MinSize() } else { return SizeFrom96DPI(Size{100, 100}, li.ctx.dpi) } } func (li *textEditLayoutItem) MinSize() Size { if li.compactHeight { width := IntFrom96DPI(100, li.ctx.dpi) return Size{width, li.HeightForWidth(width)} } else { return li.nonCompactHeightMinSize } } func (li *textEditLayoutItem) HasHeightForWidth() bool { return li.compactHeight } func (li *textEditLayoutItem) HeightForWidth(width int) int { li.mutex.Lock() defer li.mutex.Unlock() if height, ok := li.width2Height[width]; ok { return height } size := calculateTextSize(li.text, li.font, li.ctx.dpi, width-li.margins.Width, li.handle) size.Height += li.margins.Height size.Height = maxi(size.Height, li.nonCompactHeightMinSize.Height) li.width2Height[width] = size.Height return size.Height } ================================================ FILE: textlabel.go ================================================ // Copyright 2018 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 ( "sync" ) type TextLabel struct { static textChangedPublisher EventPublisher } func NewTextLabel(parent Container) (*TextLabel, error) { return NewTextLabelWithStyle(parent, 0) } func NewTextLabelWithStyle(parent Container, style uint32) (*TextLabel, error) { tl := new(TextLabel) if err := tl.init(tl, parent, style); err != nil { return nil, err } tl.textAlignment = AlignHNearVNear tl.MustRegisterProperty("Text", NewProperty( func() interface{} { return tl.Text() }, func(v interface{}) error { return tl.SetText(assertStringOr(v, "")) }, tl.textChangedPublisher.Event())) return tl, nil } func (tl *TextLabel) asStatic() *static { return &tl.static } func (tl *TextLabel) TextAlignment() Alignment2D { return tl.textAlignment } func (tl *TextLabel) SetTextAlignment(alignment Alignment2D) error { if alignment == AlignHVDefault { alignment = AlignHNearVNear } return tl.setTextAlignment(alignment) } func (tl *TextLabel) Text() string { return tl.text() } func (tl *TextLabel) SetText(text string) error { if changed, err := tl.setText(text); err != nil { return err } else if !changed { return nil } tl.textChangedPublisher.Publish() return nil } func (tl *TextLabel) CreateLayoutItem(ctx *LayoutContext) LayoutItem { return &textLabelLayoutItem{ width2Height: make(map[int]int), text: tl.Text(), font: tl.Font(), minWidth: tl.MinSizePixels().Width, } } type textLabelLayoutItem struct { LayoutItemBase mutex sync.Mutex width2Height map[int]int // in native pixels text string font *Font minWidth int // in native pixels } func (*textLabelLayoutItem) LayoutFlags() LayoutFlags { return GrowableHorz | GrowableVert } func (li *textLabelLayoutItem) IdealSize() Size { return li.MinSize() } func (li *textLabelLayoutItem) MinSize() Size { return calculateTextSize(li.text, li.font, li.ctx.dpi, li.minWidth, li.handle) } func (li *textLabelLayoutItem) HasHeightForWidth() bool { return true } func (li *textLabelLayoutItem) HeightForWidth(width int) int { li.mutex.Lock() defer li.mutex.Unlock() if height, ok := li.width2Height[width]; ok { return height } size := calculateTextSize(li.text, li.font, li.ctx.dpi, width, li.handle) li.width2Height[width] = size.Height return size.Height } ================================================ FILE: toolbar.go ================================================ // 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 ( "fmt" "syscall" "unsafe" "github.com/lxn/win" ) type ToolBarButtonStyle int const ( ToolBarButtonImageOnly ToolBarButtonStyle = iota ToolBarButtonTextOnly ToolBarButtonImageBeforeText ToolBarButtonImageAboveText ) type ToolBar struct { WidgetBase imageList *ImageList actions *ActionList defaultButtonWidth int maxTextRows int buttonStyle ToolBarButtonStyle } func NewToolBarWithOrientationAndButtonStyle(parent Container, orientation Orientation, buttonStyle ToolBarButtonStyle) (*ToolBar, error) { var style uint32 if orientation == Vertical { style = win.CCS_VERT | win.CCS_NORESIZE } else { style = win.TBSTYLE_WRAPABLE } if buttonStyle != ToolBarButtonImageAboveText { style |= win.TBSTYLE_LIST } tb := &ToolBar{ buttonStyle: buttonStyle, } tb.actions = newActionList(tb) if orientation == Vertical { tb.defaultButtonWidth = 100 } if err := InitWidget( tb, parent, "ToolbarWindow32", win.CCS_NODIVIDER|win.TBSTYLE_FLAT|win.TBSTYLE_TOOLTIPS|style, 0); err != nil { return nil, err } exStyle := tb.SendMessage(win.TB_GETEXTENDEDSTYLE, 0, 0) exStyle |= win.TBSTYLE_EX_DRAWDDARROWS | win.TBSTYLE_EX_MIXEDBUTTONS tb.SendMessage(win.TB_SETEXTENDEDSTYLE, 0, exStyle) return tb, nil } func NewToolBar(parent Container) (*ToolBar, error) { return NewToolBarWithOrientationAndButtonStyle(parent, Horizontal, ToolBarButtonImageOnly) } func NewVerticalToolBar(parent Container) (*ToolBar, error) { return NewToolBarWithOrientationAndButtonStyle(parent, Vertical, ToolBarButtonImageAboveText) } func (tb *ToolBar) Dispose() { tb.WidgetBase.Dispose() tb.actions.Clear() if tb.imageList != nil { tb.imageList.Dispose() tb.imageList = nil } } func (tb *ToolBar) applyFont(font *Font) { tb.WidgetBase.applyFont(font) tb.applyDefaultButtonWidth() tb.RequestLayout() } func (tb *ToolBar) ApplyDPI(dpi int) { tb.WidgetBase.ApplyDPI(dpi) var maskColor Color var size Size if tb.imageList != nil { maskColor = tb.imageList.maskColor size = SizeFrom96DPI(tb.imageList.imageSize96dpi, dpi) } else { size = SizeFrom96DPI(Size{16, 16}, dpi) } iml, err := NewImageListForDPI(size, maskColor, dpi) if err != nil { return } tb.SendMessage(win.TB_SETIMAGELIST, 0, uintptr(iml.hIml)) if tb.imageList != nil { tb.imageList.Dispose() } tb.imageList = iml for _, action := range tb.actions.actions { if action.image != nil { tb.onActionChanged(action) } } tb.hFont = tb.Font().handleForDPI(tb.DPI()) setWindowFont(tb.hWnd, tb.hFont) } func (tb *ToolBar) Orientation() Orientation { style := win.GetWindowLong(tb.hWnd, win.GWL_STYLE) if style&win.CCS_VERT > 0 { return Vertical } return Horizontal } func (tb *ToolBar) ButtonStyle() ToolBarButtonStyle { return tb.buttonStyle } func (tb *ToolBar) applyDefaultButtonWidth() error { if tb.defaultButtonWidth == 0 { return nil } dpi := tb.DPI() width := IntFrom96DPI(tb.defaultButtonWidth, dpi) lParam := uintptr(win.MAKELONG(uint16(width), uint16(width))) if 0 == tb.SendMessage(win.TB_SETBUTTONWIDTH, 0, lParam) { return newError("SendMessage(TB_SETBUTTONWIDTH)") } size := uint32(tb.SendMessage(win.TB_GETBUTTONSIZE, 0, 0)) height := win.HIWORD(size) lParam = uintptr(win.MAKELONG(uint16(width), height)) if win.FALSE == tb.SendMessage(win.TB_SETBUTTONSIZE, 0, lParam) { return newError("SendMessage(TB_SETBUTTONSIZE)") } return nil } // DefaultButtonWidth returns the default button width of the ToolBar. // // The default value for a horizontal ToolBar is 0, resulting in automatic // sizing behavior. For a vertical ToolBar, the default is 100 pixels. func (tb *ToolBar) DefaultButtonWidth() int { return tb.defaultButtonWidth } // SetDefaultButtonWidth sets the default button width of the ToolBar. // // Calling this method affects all buttons in the ToolBar, no matter if they are // added before or after the call. A width of 0 results in automatic sizing // behavior. Negative values are not allowed. func (tb *ToolBar) SetDefaultButtonWidth(width int) error { if width == tb.defaultButtonWidth { return nil } if width < 0 { return newError("width must be >= 0") } old := tb.defaultButtonWidth tb.defaultButtonWidth = width for _, action := range tb.actions.actions { if err := tb.onActionChanged(action); err != nil { tb.defaultButtonWidth = old return err } } return tb.applyDefaultButtonWidth() } func (tb *ToolBar) MaxTextRows() int { return tb.maxTextRows } func (tb *ToolBar) SetMaxTextRows(maxTextRows int) error { if 0 == tb.SendMessage(win.TB_SETMAXTEXTROWS, uintptr(maxTextRows), 0) { return newError("SendMessage(TB_SETMAXTEXTROWS)") } tb.maxTextRows = maxTextRows return nil } func (tb *ToolBar) Actions() *ActionList { return tb.actions } func (tb *ToolBar) ImageList() *ImageList { return tb.imageList } func (tb *ToolBar) SetImageList(value *ImageList) { var hIml win.HIMAGELIST if tb.buttonStyle != ToolBarButtonTextOnly && value != nil { hIml = value.hIml } tb.SendMessage(win.TB_SETIMAGELIST, 0, uintptr(hIml)) tb.imageList = value } func (tb *ToolBar) imageIndex(image Image) (imageIndex int32, err error) { if tb.imageList == nil { dpi := tb.DPI() iml, err := NewImageListForDPI(SizeFrom96DPI(Size{16, 16}, dpi), 0, dpi) if err != nil { return 0, err } tb.SetImageList(iml) } imageIndex = -1 if image != nil { if imageIndex, err = tb.imageList.AddImage(image); err != nil { return } } return } func (tb *ToolBar) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_MOUSEMOVE, win.WM_MOUSELEAVE, win.WM_LBUTTONDOWN: tb.Invalidate() case win.WM_COMMAND: switch win.HIWORD(uint32(wParam)) { case win.BN_CLICKED: actionId := uint16(win.LOWORD(uint32(wParam))) if action, ok := actionsById[actionId]; ok { action.raiseTriggered() return 0 } } case win.WM_NOTIFY: nmhdr := (*win.NMHDR)(unsafe.Pointer(lParam)) switch int32(nmhdr.Code) { case win.TBN_DROPDOWN: nmtb := (*win.NMTOOLBAR)(unsafe.Pointer(lParam)) actionId := uint16(nmtb.IItem) if action := actionsById[actionId]; action != nil { var r win.RECT if 0 == tb.SendMessage(win.TB_GETRECT, uintptr(actionId), uintptr(unsafe.Pointer(&r))) { break } p := win.POINT{r.Left, r.Bottom} if !win.ClientToScreen(tb.hWnd, &p) { break } action.menu.updateItemsWithImageForWindow(tb) win.TrackPopupMenuEx( action.menu.hMenu, win.TPM_NOANIMATION, p.X, p.Y, tb.hWnd, nil) return win.TBDDRET_DEFAULT } } case win.WM_WINDOWPOSCHANGED: wp := (*win.WINDOWPOS)(unsafe.Pointer(lParam)) if wp.Flags&win.SWP_NOSIZE != 0 { break } tb.SendMessage(win.TB_AUTOSIZE, 0, 0) } return tb.WidgetBase.WndProc(hwnd, msg, wParam, lParam) } func (tb *ToolBar) initButtonForAction(action *Action, state, style *byte, image *int32, text *uintptr) (err error) { if tb.hasStyleBits(win.CCS_VERT) { *state |= win.TBSTATE_WRAP } else if tb.defaultButtonWidth == 0 { *style |= win.BTNS_AUTOSIZE } if action.checked { *state |= win.TBSTATE_CHECKED } if action.enabled { *state |= win.TBSTATE_ENABLED } if action.checkable { *style |= win.BTNS_CHECK } if action.exclusive { *style |= win.BTNS_GROUP } if tb.buttonStyle != ToolBarButtonImageOnly && len(action.text) > 0 { *style |= win.BTNS_SHOWTEXT } if action.menu != nil { if len(action.Triggered().handlers) > 0 { *style |= win.BTNS_DROPDOWN } else { *style |= win.BTNS_WHOLEDROPDOWN } } if action.IsSeparator() { *style = win.BTNS_SEP } if tb.buttonStyle != ToolBarButtonTextOnly { if *image, err = tb.imageIndex(action.image); err != nil { return err } } var actionText string if s := action.shortcut; tb.buttonStyle == ToolBarButtonImageOnly && s.Key != 0 { actionText = fmt.Sprintf("%s (%s)", action.Text(), s.String()) } else { actionText = action.Text() } if len(actionText) != 0 { *text = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(actionText))) } else if len(action.toolTip) != 0 { *text = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(action.toolTip))) } return } func (tb *ToolBar) onActionChanged(action *Action) error { tbbi := win.TBBUTTONINFO{ DwMask: win.TBIF_IMAGE | win.TBIF_STATE | win.TBIF_STYLE | win.TBIF_TEXT, IImage: win.I_IMAGENONE, } tbbi.CbSize = uint32(unsafe.Sizeof(tbbi)) if err := tb.initButtonForAction( action, &tbbi.FsState, &tbbi.FsStyle, &tbbi.IImage, &tbbi.PszText); err != nil { return err } if 0 == tb.SendMessage( win.TB_SETBUTTONINFO, uintptr(action.id), uintptr(unsafe.Pointer(&tbbi))) { return newError("SendMessage(TB_SETBUTTONINFO) failed") } tb.RequestLayout() return nil } func (tb *ToolBar) onActionVisibleChanged(action *Action) error { if !action.IsSeparator() { defer tb.actions.updateSeparatorVisibility() } if action.Visible() { return tb.insertAction(action, true) } return tb.removeAction(action, true) } func (tb *ToolBar) insertAction(action *Action, visibleChanged bool) (err error) { if !visibleChanged { action.addChangedHandler(tb) defer func() { if err != nil { action.removeChangedHandler(tb) } }() } if !action.Visible() { return } index := tb.actions.indexInObserver(action) tbb := win.TBBUTTON{ IdCommand: int32(action.id), } if err = tb.initButtonForAction( action, &tbb.FsState, &tbb.FsStyle, &tbb.IBitmap, &tbb.IString); err != nil { return } tb.SetVisible(true) tb.SendMessage(win.TB_BUTTONSTRUCTSIZE, uintptr(unsafe.Sizeof(tbb)), 0) if win.FALSE == tb.SendMessage(win.TB_INSERTBUTTON, uintptr(index), uintptr(unsafe.Pointer(&tbb))) { return newError("SendMessage(TB_ADDBUTTONS)") } if err = tb.applyDefaultButtonWidth(); err != nil { return } tb.SendMessage(win.TB_AUTOSIZE, 0, 0) tb.RequestLayout() return } func (tb *ToolBar) removeAction(action *Action, visibleChanged bool) error { index := tb.actions.indexInObserver(action) if !visibleChanged { action.removeChangedHandler(tb) } if 0 == tb.SendMessage(win.TB_DELETEBUTTON, uintptr(index), 0) { return newError("SendMessage(TB_DELETEBUTTON) failed") } tb.RequestLayout() return nil } func (tb *ToolBar) onInsertedAction(action *Action) error { return tb.insertAction(action, false) } func (tb *ToolBar) onRemovingAction(action *Action) error { return tb.removeAction(action, false) } func (tb *ToolBar) onClearingActions() error { for i := tb.actions.Len() - 1; i >= 0; i-- { if action := tb.actions.At(i); action.Visible() { if err := tb.onRemovingAction(action); err != nil { return err } } } return nil } func (tb *ToolBar) CreateLayoutItem(ctx *LayoutContext) LayoutItem { buttonSize := uint32(tb.SendMessage(win.TB_GETBUTTONSIZE, 0, 0)) dpi := tb.DPI() width := IntFrom96DPI(tb.defaultButtonWidth, dpi) if width == 0 { width = int(win.LOWORD(buttonSize)) } height := int(win.HIWORD(buttonSize)) var size win.SIZE var wp uintptr var layoutFlags LayoutFlags if tb.Orientation() == Vertical { wp = win.TRUE layoutFlags = ShrinkableVert | GrowableVert | GreedyVert } else { wp = win.FALSE // FIXME: Since reimplementation of BoxLayout we must use 0 here, // otherwise the ToolBar contained in MainWindow will eat half the space. //layoutFlags = ShrinkableHorz | GrowableHorz } if win.FALSE != tb.SendMessage(win.TB_GETIDEALSIZE, wp, uintptr(unsafe.Pointer(&size))) { if wp == win.TRUE { height = int(size.CY) } else { width = int(size.CX) } } return &toolBarLayoutItem{ layoutFlags: layoutFlags, idealSize: Size{width, height}, } } type toolBarLayoutItem struct { LayoutItemBase layoutFlags LayoutFlags idealSize Size // in native pixels } func (li *toolBarLayoutItem) LayoutFlags() LayoutFlags { return li.layoutFlags } func (li *toolBarLayoutItem) IdealSize() Size { return li.idealSize } func (li *toolBarLayoutItem) MinSize() Size { return li.idealSize } ================================================ FILE: toolbutton.go ================================================ // Copyright 2012 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 ( "github.com/lxn/win" ) type ToolButton struct { Button } func NewToolButton(parent Container) (*ToolButton, error) { tb := new(ToolButton) if err := InitWidget( tb, parent, "BUTTON", win.WS_TABSTOP|win.WS_VISIBLE|win.BS_BITMAP|win.BS_PUSHBUTTON, 0); err != nil { return nil, err } tb.Button.init() tb.GraphicsEffects().Add(InteractionEffect) tb.GraphicsEffects().Add(FocusEffect) return tb, nil } func (tb *ToolButton) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_GETDLGCODE: return win.DLGC_BUTTON } return tb.Button.WndProc(hwnd, msg, wParam, lParam) } func (tb *ToolButton) CreateLayoutItem(ctx *LayoutContext) LayoutItem { return &toolButtonLayoutItem{ idealSize: tb.dialogBaseUnitsToPixels(Size{16, 12}), } } type toolButtonLayoutItem struct { LayoutItemBase idealSize Size // in native pixels } func (*toolButtonLayoutItem) LayoutFlags() LayoutFlags { return 0 } func (tb *toolButtonLayoutItem) IdealSize() Size { return tb.idealSize } func (tb *toolButtonLayoutItem) MinSize() Size { return tb.idealSize } ================================================ FILE: tools/ui2walk/ui2walk.go ================================================ // Copyright 2011 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. package main import ( "bufio" "bytes" "encoding/xml" "errors" "flag" "fmt" "io" "log" "os" "path" "path/filepath" "runtime" "strings" ) var forceUpdate *bool = flag.Bool("force", false, "forces code generation for up-to-date files") var translatable *bool = flag.Bool("tr", false, "adds calls to a user provided 'func tr(source string, context ...string) string' that returns a translation of the source argument, using provided context args for disambiguation") type String struct { Text string `xml:"string"` Comment string `xml:"comment,attr"` ExtraComment string `xml:"extracomment,attr"` } type UI struct { Class string `xml:"class"` Widget Widget `xml:"widget"` CustomWidgets CustomWidgets `xml:"customwidgets"` TabStops []string `xml:"tabstops>tabstop"` } type Widget struct { Class string `xml:"class,attr"` Name string `xml:"name,attr"` Attribute []*Attribute `xml:"attribute"` Property []*Property `xml:"property"` Layout *Layout `xml:"layout"` Widget []*Widget `xml:"widget"` AddAction []*AddAction `xml:"addaction"` Action []*Action `xml:"action"` ignored bool } type Layout struct { Class string `xml:"class,attr"` Name string `xml:"name,attr"` Stretch string `xml:"stretch,attr"` Property []*Property `xml:"property"` Item []*Item `xml:"item"` ignored bool } type Item struct { Row string `xml:"row,attr"` Column string `xml:"column,attr"` RowSpan string `xml:"rowspan,attr"` ColSpan string `xml:"colspan,attr"` Widget *Widget `xml:"widget"` Spacer *Spacer `xml:"spacer"` } type Spacer struct { Name string `xml:"name,attr"` Property []*Property `xml:"property"` } type AddAction struct { Name string `xml:"name,attr"` } type Action struct { Name string `xml:"name,attr"` Property []*Property `xml:"property"` } type Attribute struct { Name string `xml:"name,attr"` String } type Property struct { Name string `xml:"name,attr"` Bool bool `xml:"bool"` Enum string `xml:"enum"` Font *Font `xml:"font"` Number float64 `xml:"number"` Rect Rectangle `xml:"rect"` Set string `xml:"set"` Size Size `xml:"size"` String } type Font struct { Family string `xml:"family"` PointSize int `xml:"pointsize"` Italic bool `xml:"italic"` Bold bool `xml:"bold"` Underline bool `xml:"underline"` StrikeOut bool `xml:"strikeout"` } type Rectangle struct { X int `xml:"x"` Y int `xml:"y"` Width int `xml:"width"` Height int `xml:"height"` } type Size struct { Width int `xml:"width"` Height int `xml:"height"` } type CustomWidgets struct { CustomWidget []*CustomWidget `xml:"customwidget"` } type CustomWidget struct { Class string `xml:"class"` Extends string `xml:"extends"` } func trString(str *String) string { if str == nil { return "" } if !*translatable { return fmt.Sprintf("`%s`", str.Text) } buf := new(bytes.Buffer) buf.WriteString("tr(`") buf.WriteString(str.Text) buf.WriteString("`") if str.Comment != "" { buf.WriteString(", `") buf.WriteString(str.Comment) buf.WriteString("`") } if str.ExtraComment != "" { buf.WriteString(", `") buf.WriteString(str.ExtraComment) buf.WriteString("`") } buf.WriteString(")") return buf.String() } func logFatal(err error) { if err != nil { log.Fatal(err) } } func parseUI(reader io.Reader) (*UI, error) { ui := &UI{} if err := xml.NewDecoder(reader).Decode(ui); err != nil { return nil, err } return ui, nil } func writeAttribute(buf *bytes.Buffer, attr *Attribute, qualifiedReceiver string) (err error) { switch attr.Name { case "title": buf.WriteString(fmt.Sprintf( "if err := %s.SetTitle(%s); err != nil {\nreturn err\n}\n", qualifiedReceiver, trString(&attr.String))) default: fmt.Printf("Ignoring unsupported attribute: '%s'\n", attr.Name) return nil } return nil } func writeAttributes(buf *bytes.Buffer, attrs []*Attribute, qualifiedReceiver string) error { for _, attr := range attrs { if err := writeAttribute(buf, attr, qualifiedReceiver); err != nil { return err } } return nil } func writeProperty(buf *bytes.Buffer, prop *Property, qualifiedReceiver string, widget *Widget) (err error) { if prop.Name == "windowTitle" && widget != nil && widget.Class == "QWidget" { return } switch prop.Name { case "decimals": buf.WriteString(fmt.Sprintf("if err := %s.SetDecimals(%d); err != nil {\nreturn err\n}\n", qualifiedReceiver, int(prop.Number))) case "echoMode": switch prop.Enum { case "QLineEdit::Normal": // nop case "QLineEdit::Password": buf.WriteString(fmt.Sprintf("%s.SetPasswordMode(true)\n", qualifiedReceiver)) default: fmt.Printf("Ignoring unsupported echoMode: '%s'\n", prop.Enum) return nil } case "enabled": buf.WriteString(fmt.Sprintf("%s.SetEnabled(%t)\n", qualifiedReceiver, prop.Bool)) case "font": f := prop.Font family := f.Family if family == "" { family = "MS Shell Dlg 2" } pointSize := f.PointSize if pointSize == 0 { pointSize = 8 } buf.WriteString(fmt.Sprintf("if font, err = walk.NewFont(\"%s\", %d, ", family, pointSize)) included := []bool{f.Bold, f.Italic, f.StrikeOut, f.Underline} flags := []string{"walk.FontBold", "walk.FontItalic", "walk.FontStrikeOut", "walk.FontUnderline"} var includedFlags []string for i := 0; i < len(included); i++ { if included[i] { includedFlags = append(includedFlags, flags[i]) } } if len(includedFlags) == 0 { buf.WriteString("0") } else { buf.WriteString(strings.Join(includedFlags, "|")) } buf.WriteString(`); err != nil { return err } `) buf.WriteString(fmt.Sprintf("%s.SetFont(font)\n", qualifiedReceiver)) case "geometry": if qualifiedReceiver == "w" { // Only set client size for top level buf.WriteString(fmt.Sprintf( `if err := %s.SetClientSize(walk.Size{%d, %d}); err != nil { return err } `, qualifiedReceiver, prop.Rect.Width, prop.Rect.Height)) } else { buf.WriteString(fmt.Sprintf( `if err := %s.SetBounds(walk.Rectangle{%d, %d, %d, %d}); err != nil { return err } `, qualifiedReceiver, prop.Rect.X, prop.Rect.Y, prop.Rect.Width, prop.Rect.Height)) } case "maximumSize", "minimumSize": // We do these two guys in writeProperties, because we want to map them // to a single method call, if both are present. case "maxLength": buf.WriteString(fmt.Sprintf("%s.SetMaxLength(%d)\n", qualifiedReceiver, int(prop.Number))) case "readOnly": buf.WriteString(fmt.Sprintf("%s.SetReadOnly(%t)\n", qualifiedReceiver, prop.Bool)) case "text": buf.WriteString(fmt.Sprintf( "if err := %s.SetText(%s); err != nil {\nreturn err\n}\n", qualifiedReceiver, trString(&prop.String))) case "title", "windowTitle": buf.WriteString(fmt.Sprintf( "if err := %s.SetTitle(%s); err != nil {\nreturn err\n}\n", qualifiedReceiver, trString(&prop.String))) case "orientation": var orientation string switch prop.Enum { case "Qt::Horizontal": orientation = "walk.Horizontal" case "Qt::Vertical": orientation = "walk.Vertical" default: return errors.New(fmt.Sprintf("unknown orientation: '%s'", prop.Enum)) } buf.WriteString(fmt.Sprintf( `if err := %s.SetOrientation(%s); err != nil { return err } `, qualifiedReceiver, orientation)) default: fmt.Printf("Ignoring unsupported property: '%s'\n", prop.Name) return nil } return } func writeProperties(buf *bytes.Buffer, props []*Property, qualifiedReceiver string, widget *Widget) error { var minSize, maxSize Size var hasMinOrMaxSize bool for _, prop := range props { if err := writeProperty(buf, prop, qualifiedReceiver, widget); err != nil { return err } if prop.Name == "minimumSize" { minSize = prop.Size hasMinOrMaxSize = true } if prop.Name == "maximumSize" { maxSize = prop.Size hasMinOrMaxSize = true } } if hasMinOrMaxSize { buf.WriteString(fmt.Sprintf( `if err := %s.SetMinMaxSize(walk.Size{%d, %d}, walk.Size{%d, %d}); err != nil { return err } `, qualifiedReceiver, minSize.Width, minSize.Height, maxSize.Width, maxSize.Height)) } return nil } func writeItemInitializations(buf *bytes.Buffer, items []*Item, parent *Widget, qualifiedParent string, layout string) error { for _, item := range items { var itemName string if item.Spacer != nil { itemName = item.Spacer.Name name2Prop := make(map[string]*Property) for _, prop := range item.Spacer.Property { name2Prop[prop.Name] = prop } orientation := name2Prop["orientation"] sizeType := name2Prop["sizeType"] sizeHint := name2Prop["sizeHint"] var orientStr string var fixedStr string var secondParamStr string if orientation.Enum == "Qt::Horizontal" { orientStr = "H" if sizeType != nil && sizeType.Enum == "QSizePolicy::Fixed" { fixedStr = "Fixed" secondParamStr = fmt.Sprintf(", %d", sizeHint.Size.Width) } } else { orientStr = "V" if sizeType != nil && sizeType.Enum == "QSizePolicy::Fixed" { fixedStr = "Fixed" secondParamStr = fmt.Sprintf(", %d", sizeHint.Size.Height) } } if layout == "" { buf.WriteString(fmt.Sprintf( ` // anonymous spacer if _, err := walk.New%sSpacer%s(%s%s); err != nil { return err } `, orientStr, fixedStr, qualifiedParent, secondParamStr)) } else { buf.WriteString(fmt.Sprintf( ` // %s %s, err := walk.New%sSpacer%s(%s%s) if err != nil { return err } `, itemName, itemName, orientStr, fixedStr, qualifiedParent, secondParamStr)) } } if item.Widget != nil && !item.Widget.ignored { itemName = fmt.Sprintf("w.ui.%s", item.Widget.Name) if err := writeWidgetInitialization(buf, item.Widget, parent, qualifiedParent); err != nil { return err } } if layout != "" && itemName != "" && item.Row != "" && item.Column != "" { if item.ColSpan == "" { item.ColSpan = "1" } if item.RowSpan == "" { item.RowSpan = "1" } buf.WriteString(fmt.Sprintf( ` if err := %s.SetRange(%s, walk.Rectangle{%s, %s, %s, %s}); err != nil { return err } `, layout, itemName, item.Column, item.Row, item.ColSpan, item.RowSpan)) } } return nil } func writeLayoutInitialization(buf *bytes.Buffer, layout *Layout, parent *Widget, qualifiedParent string) error { var typ string switch layout.Class { case "QGridLayout": typ = "GridLayout" case "QHBoxLayout": typ = "HBoxLayout" case "QVBoxLayout": typ = "VBoxLayout" default: return errors.New(fmt.Sprintf("unsupported layout type: '%s'", layout.Class)) } buf.WriteString(fmt.Sprintf("%s := walk.New%s()\n", layout.Name, typ)) buf.WriteString(fmt.Sprintf( `if err := %s.SetLayout(%s); err != nil { return err } `, qualifiedParent, layout.Name)) spacing := 6 margL, margT, margR, margB := 9, 9, 9, 9 for _, prop := range layout.Property { switch prop.Name { case "spacing": spacing = int(prop.Number) case "leftMargin": margL = int(prop.Number) case "topMargin": margT = int(prop.Number) case "rightMargin": margR = int(prop.Number) case "bottomMargin": margB = int(prop.Number) case "margin": m := int(prop.Number) margL, margT, margR, margB = m, m, m, m } } if margL != 0 || margT != 0 || margR != 0 || margB != 0 { buf.WriteString(fmt.Sprintf( `if err := %s.SetMargins(walk.Margins{%d, %d, %d, %d}); err != nil { return err } `, layout.Name, margL, margT, margR, margB)) } if spacing != 0 { buf.WriteString(fmt.Sprintf( `if err := %s.SetSpacing(%d); err != nil { return err } `, layout.Name, spacing)) } var layoutName string if typ == "GridLayout" { layoutName = layout.Name } if err := writeItemInitializations(buf, layout.Item, parent, qualifiedParent, layoutName); err != nil { return err } return nil } func writeWidgetInitialization(buf *bytes.Buffer, widget *Widget, parent *Widget, qualifiedParent string) error { receiver := fmt.Sprintf("w.ui.%s", widget.Name) var typ string var custom bool switch widget.Class { case "QCheckBox": typ = "CheckBox" case "QComboBox": typ = "ComboBox" case "QDateEdit": typ = "DateEdit" case "QDoubleSpinBox", "QSpinBox": typ = "NumberEdit" case "QFrame": typ = "Composite" case "QGroupBox": typ = "GroupBox" case "QLabel": typ = "Label" case "QLineEdit": typ = "LineEdit" case "QPlainTextEdit", "QTextEdit": typ = "TextEdit" case "QProgressBar": typ = "ProgressBar" case "QPushButton": typ = "PushButton" case "QRadioButton": typ = "RadioButton" case "QSplitter": typ = "Splitter" case "QTabWidget": typ = "TabWidget" case "QTableView", "QTableWidget": typ = "TableView" case "QToolButton": typ = "ToolButton" case "QTreeView", "QTreeWidget": typ = "TreeView" case "QWebView": typ = "WebView" case "QWidget": if parent != nil && parent.Class == "QTabWidget" { typ = "TabPage" } else { typ = "Composite" } default: // FIXME: We assume this is a custom widget in the same package. // We also require a func NewFoo(parent) (*Foo, error). typ = widget.Class custom = true } if custom { buf.WriteString(fmt.Sprintf( ` // %s if %s, err = New%s(%s); err != nil { return err } `, widget.Name, receiver, typ, qualifiedParent)) } else { if typ == "TabPage" { buf.WriteString(fmt.Sprintf( ` // %s if %s, err = walk.NewTabPage(); err != nil { return err } `, widget.Name, receiver)) } else { buf.WriteString(fmt.Sprintf( ` // %s if %s, err = walk.New%s(%s); err != nil { return err } `, widget.Name, receiver, typ, qualifiedParent)) } } buf.WriteString(fmt.Sprintf("%s.SetName(\"%s\")\n", receiver, widget.Name)) if err := writeAttributes(buf, widget.Attribute, receiver); err != nil { return err } if err := writeProperties(buf, widget.Property, receiver, widget); err != nil { return err } if widget.Layout != nil && !widget.Layout.ignored { if err := writeLayoutInitialization(buf, widget.Layout, widget, receiver); err != nil { return err } } if typ == "TabPage" { buf.WriteString(fmt.Sprintf( `if err := %s.Pages().Add(%s); err != nil { return err } `, qualifiedParent, receiver)) } return writeWidgetInitializations(buf, widget.Widget, widget, receiver) } func writeWidgetInitializations(buf *bytes.Buffer, widgets []*Widget, parent *Widget, qualifiedParent string) error { for _, widget := range widgets { if widget.ignored || widget.Class == "QMenuBar" || widget.Class == "QStatusBar" { continue } if err := writeWidgetInitialization(buf, widget, parent, qualifiedParent); err != nil { return err } } return nil } func writeWidgetDecl(buf *bytes.Buffer, widget *Widget, parent *Widget) error { var typ string switch widget.Class { case "QCheckBox": typ = "walk.CheckBox" case "QComboBox": typ = "walk.ComboBox" case "QDateEdit": typ = "walk.DateEdit" case "QDoubleSpinBox", "QSpinBox": typ = "walk.NumberEdit" case "QFrame": typ = "walk.Composite" case "QGroupBox": typ = "walk.GroupBox" case "QLabel": typ = "walk.Label" case "QLineEdit": typ = "walk.LineEdit" case "QPlainTextEdit", "QTextEdit": typ = "walk.TextEdit" case "QProgressBar": typ = "walk.ProgressBar" case "QPushButton": typ = "walk.PushButton" case "QRadioButton": typ = "walk.RadioButton" case "QSplitter": typ = "walk.Splitter" case "QTabWidget": typ = "walk.TabWidget" case "QTableView", "QTableWidget": typ = "walk.TableView" case "QToolButton": typ = "walk.ToolButton" case "QTreeView", "QTreeWidget": typ = "walk.TreeView" case "QWebView": typ = "walk.WebView" case "QWidget": if parent != nil && parent.Class == "QTabWidget" { typ = "walk.TabPage" } else { typ = "walk.Composite" } default: // FIXME: For now, we assume this is a custom widget in the same package typ = widget.Class } buf.WriteString(fmt.Sprintf("%s *%s\n", widget.Name, typ)) if widget.Layout != nil { return writeItemDecls(buf, widget.Layout.Item, widget) } return writeWidgetDecls(buf, widget.Widget, widget) } func writeWidgetDecls(buf *bytes.Buffer, widgets []*Widget, parent *Widget) error { for _, widget := range widgets { switch widget.Class { case "QMenuBar", "QStatusBar": continue } if err := writeWidgetDecl(buf, widget, parent); err != nil { return err } } return nil } func writeItemDecls(buf *bytes.Buffer, items []*Item, parent *Widget) error { for _, item := range items { if item.Widget == nil { continue } if err := writeWidgetDecl(buf, item.Widget, parent); err != nil { return err } } return nil } func writeActionDecl(buf *bytes.Buffer, action *Action) error { buf.WriteString(action.Name) buf.WriteString(" *walk.Action\n") return nil } func writeActionDecls(buf *bytes.Buffer, actions []*Action) error { for _, action := range actions { if err := writeActionDecl(buf, action); err != nil { return err } } return nil } func writeMenuInitialization(buf *bytes.Buffer, menu *Widget, realActions map[string]bool) error { var qualifiedParentMenu string if menu.Class == "QMenuBar" { buf.WriteString("// Menus\n\n") qualifiedParentMenu = "w.Menu()" } else { qualifiedParentMenu = menu.Name } for _, addAction := range menu.AddAction { if realActions[addAction.Name] { buf.WriteString("if err := ") buf.WriteString(qualifiedParentMenu) buf.WriteString(".Actions().Add(w.ui.actions.") buf.WriteString(addAction.Name) buf.WriteString(`); err != nil { return err } `) } else { for _, submenu := range menu.Widget { if submenu.Name != addAction.Name { continue } buf.WriteString("// ") buf.WriteString(submenu.Name) buf.WriteString("\n") buf.WriteString(submenu.Name) buf.WriteString(`, err := walk.NewMenu() if err != nil { return err } `) submenuActionName := submenu.Name + "Action" buf.WriteString(submenuActionName) buf.WriteString(", err := ") buf.WriteString(qualifiedParentMenu) buf.WriteString(".Actions().AddMenu(") buf.WriteString(submenu.Name) buf.WriteString(`) if err != nil { return err } `) for _, prop := range submenu.Property { if prop.Name == "title" { buf.WriteString("if err := ") buf.WriteString(submenuActionName) buf.WriteString(".SetText(") buf.WriteString(trString(&prop.String)) buf.WriteString(`); err != nil { return err } `) break } } if err := writeMenuInitialization(buf, submenu, realActions); err != nil { return err } } } } return nil } func writeActionInitializations(buf *bytes.Buffer, actions []*Action) error { buf.WriteString("\n// Actions\n\n") for _, action := range actions { qualifiedReceiver := "w.ui.actions." + action.Name buf.WriteString("// ") buf.WriteString(qualifiedReceiver) buf.WriteString("\n") buf.WriteString(qualifiedReceiver) buf.WriteString(" = walk.NewAction()\n") if err := writeProperties(buf, action.Property, qualifiedReceiver, nil); err != nil { return err } buf.WriteString("\n") } return nil } func generateUICode(buf *bytes.Buffer, ui *UI) error { // Comment, package decl, imports buf.WriteString( `// This file was created by ui2walk and may be regenerated. // DO NOT EDIT OR YOUR MODIFICATIONS WILL BE LOST! package main import ( "github.com/lxn/walk" ) `) // Embed the corresponding Walk type. var embeddedType string switch ui.Widget.Class { case "QMainWindow": embeddedType = "MainWindow" case "QDialog": embeddedType = "Dialog" case "QWidget": embeddedType = "Composite" default: return errors.New(fmt.Sprintf("Top level '%s' currently not supported.", ui.Widget.Class)) } genTypeBaseName := strings.ToLower(ui.Class[:1]) + ui.Class[1:] if len(ui.Widget.Action) > 0 { // This struct will contain actions. buf.WriteString(fmt.Sprintf("type %sActions struct {\n", genTypeBaseName)) writeActionDecls(buf, ui.Widget.Action) buf.WriteString("}\n\n") } // Struct containing all descendant widgets. buf.WriteString(fmt.Sprintf("type %sUI struct {\n", genTypeBaseName)) if len(ui.Widget.Action) > 0 { buf.WriteString(fmt.Sprintf("actions %sActions\n", genTypeBaseName)) } // Descendant widget decls if ui.Widget.Widget != nil { if err := writeWidgetDecls(buf, ui.Widget.Widget, &ui.Widget); err != nil { return err } } if ui.Widget.Layout != nil { if err := writeItemDecls(buf, ui.Widget.Layout.Item, &ui.Widget); err != nil { return err } } // end struct buf.WriteString("}\n\n") // init func switch embeddedType { case "MainWindow": buf.WriteString(fmt.Sprintf( `func (w *%s) init() (err error) { if w.MainWindow, err = walk.NewMainWindow()`, ui.Widget.Name)) case "Dialog": buf.WriteString(fmt.Sprintf( `func (w *%s) init(owner walk.Form) (err error) { if w.Dialog, err = walk.NewDialog(owner)`, ui.Widget.Name)) case "Composite": buf.WriteString(fmt.Sprintf( `func (w *%s) init(parent walk.Container) (err error) { if w.Composite, err = walk.NewComposite(parent)`, ui.Widget.Name)) } buf.WriteString(fmt.Sprintf(`; err != nil { return err } succeeded := false defer func(){ if !succeeded { w.Dispose() } }() var font *walk.Font if font == nil { font = nil } w.SetName("%s") `, ui.Widget.Name)) if embeddedType == "MainWindow" { buf.WriteString(fmt.Sprintf( `l := walk.NewVBoxLayout() if err := l.SetMargins(walk.Margins{0, 0, 0, 0}); err != nil { return err } if err := w.SetLayout(l); err != nil { return err } `)) } if err := writeProperties(buf, ui.Widget.Property, "w", &ui.Widget); err != nil { return err } // Let's see if we find a QMenuBar widget. var menuBar *Widget for _, widget := range ui.Widget.Widget { if widget.Class == "QMenuBar" { menuBar = widget break } } if len(ui.Widget.Action) > 0 { writeActionInitializations(buf, ui.Widget.Action) if menuBar != nil { realActions := make(map[string]bool) for _, action := range ui.Widget.Action { realActions[action.Name] = true } writeMenuInitialization(buf, menuBar, realActions) } } if ui.Widget.Widget != nil { if err := writeWidgetInitializations(buf, ui.Widget.Widget, &ui.Widget, "w"); err != nil { return err } } if ui.Widget.Layout != nil { if err := writeLayoutInitialization(buf, ui.Widget.Layout, &ui.Widget, "w"); err != nil { return err } } buf.WriteString("\n// Tab order\n") for i := len(ui.TabStops) - 1; i >= 0; i-- { buf.WriteString(fmt.Sprintf(`if err = w.ui.%s.BringToTop(); err != nil { return err } `, ui.TabStops[i])) } // end func buf.WriteString(` succeeded = true return nil }`) return nil } func generateLogicCode(buf *bytes.Buffer, ui *UI) error { // Comment, package decl, imports buf.WriteString( `package main import ( "github.com/lxn/walk" ) `) // Embed the corresponding Walk type. var embeddedType string switch ui.Widget.Class { case "QMainWindow": embeddedType = "MainWindow" case "QDialog": embeddedType = "Dialog" case "QWidget": embeddedType = "Composite" default: return errors.New(fmt.Sprintf("Top level '%s' currently not supported.", ui.Widget.Class)) } buf.WriteString("type ") buf.WriteString(ui.Widget.Name) buf.WriteString(" struct {\n*walk.") buf.WriteString(embeddedType) buf.WriteString("\nui ") buf.WriteString(strings.ToLower(ui.Class[:1]) + ui.Class[1:]) buf.WriteString(`UI } `) switch embeddedType { case "MainWindow": buf.WriteString("func run") buf.WriteString(ui.Widget.Name) buf.WriteString(`() (int, error) { mw := new(`) buf.WriteString(ui.Widget.Name) buf.WriteString(`) if err := mw.init(); err != nil { return 0, err } defer mw.Dispose() // TODO: Do further required setup, e.g. for event handling, here. mw.Show() return mw.Run(), nil } `) case "Dialog": buf.WriteString("func run") buf.WriteString(ui.Widget.Name) buf.WriteString(`(owner walk.Form) (int, error) { dlg := new(`) buf.WriteString(ui.Widget.Name) buf.WriteString(`) if err := dlg.init(owner); err != nil { return 0, err } `) if b := findWidget(&ui.Widget, "QPushButton", []string{"accept", "ok"}); b != nil { buf.WriteString("if err := dlg.SetDefaultButton(dlg.ui.") buf.WriteString(b.Name) buf.WriteString(`); err != nil { return 0, err } dlg.ui.`) buf.WriteString(b.Name) buf.WriteString(`.Clicked().Attach(func(){ dlg.Accept() }) `) } if b := findWidget(&ui.Widget, "QPushButton", []string{"cancel"}); b != nil { buf.WriteString("if err := dlg.SetCancelButton(dlg.ui.") buf.WriteString(b.Name) buf.WriteString(`); err != nil { return 0, err } dlg.ui.`) buf.WriteString(b.Name) buf.WriteString(`.Clicked().Attach(func(){ dlg.Cancel() }) `) } buf.WriteString(`// TODO: Do further required setup, e.g. for event handling, here. return dlg.Run(), nil } `) case "Composite": buf.WriteString("func new") buf.WriteString(ui.Widget.Name) buf.WriteString("(parent walk.Container) (*") buf.WriteString(ui.Widget.Name) buf.WriteString(`, error) { c := new(`) buf.WriteString(ui.Widget.Name) buf.WriteString(`) if err := c.init(parent); err != nil { return nil, err } // TODO: Do further required setup, e.g. for event handling, here. return c, nil } `) } return nil } func findWidget(parent *Widget, class string, nameSubstrs []string) *Widget { find := func(widget *Widget) *Widget { if widget.Class == class { for _, substr := range nameSubstrs { if strings.Contains(widget.Name, substr) { return widget } } } if w := findWidget(widget, class, nameSubstrs); w != nil { return w } return nil } for _, widget := range parent.Widget { if w := find(widget); w != nil { return w } } if parent.Layout != nil { for _, item := range parent.Layout.Item { if item.Widget != nil { if w := find(item.Widget); w != nil { return w } } } } return nil } func processFile(uiFilePath string) error { goLogicFilePath := uiFilePath[:len(uiFilePath)-3] + ".go" goUIFilePath := uiFilePath[:len(uiFilePath)-3] + "_ui.go" uiFileInfo, err := os.Stat(uiFilePath) if err != nil { return err } goUIFileInfo, err := os.Stat(goUIFilePath) if !*forceUpdate && err == nil && !uiFileInfo.ModTime().After(goUIFileInfo.ModTime()) { // The go file should be up-to-date return nil } fmt.Printf("Processing '%s'\n", uiFilePath) defer fmt.Println("") uiFile, err := os.Open(uiFilePath) if err != nil { return err } defer uiFile.Close() reader := bufio.NewReader(uiFile) ui, err := parseUI(reader) if err != nil { return err } goLogicFile, err := os.OpenFile(goLogicFilePath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0666) if err == nil { defer goLogicFile.Close() buf := new(bytes.Buffer) if err := generateLogicCode(buf, ui); err != nil { return err } if _, err := io.Copy(goLogicFile, buf); err != nil { return err } if err := goLogicFile.Close(); err != nil { return err } } goUIFile, err := os.Create(goUIFilePath) if err != nil { return err } defer goUIFile.Close() buf := new(bytes.Buffer) if err := generateUICode(buf, ui); err != nil { return err } if _, err := io.Copy(goUIFile, buf); err != nil { return err } if err := goUIFile.Close(); err != nil { return err } dirPath := os.Getenv("GOBIN") if dirPath == "" { dirPath = filepath.Join(runtime.GOROOT(), "bin") } gofmtPath := filepath.Join(dirPath, "gofmt.exe") args := []string{gofmtPath, "-w", goUIFilePath} if goLogicFile != nil { args = append(args, goLogicFilePath) } gofmt, err := os.StartProcess(gofmtPath, args, &os.ProcAttr{Files: []*os.File{nil, nil, os.Stderr}}) if err != nil { return err } defer gofmt.Release() return nil } func processDirectory(dirPath string) error { dir, err := os.Open(dirPath) if err != nil { return err } defer dir.Close() names, err := dir.Readdirnames(-1) if err != nil { return err } for _, name := range names { fullPath := path.Join(dirPath, name) fi, err := os.Stat(fullPath) if err != nil { return err } if fi.IsDir() { if err := processDirectory(fullPath); err != nil { return err } } else if !fi.IsDir() && strings.HasSuffix(name, ".ui") { if err := processFile(fullPath); err != nil { return err } } } return nil } func main() { flag.Parse() cwd, err := os.Getwd() logFatal(err) logFatal(processDirectory(cwd)) } ================================================ FILE: tooltip.go ================================================ // 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" "github.com/lxn/win" ) // https://msdn.microsoft.com/en-us/library/windows/desktop/bb760416(v=vs.85).aspx says 80, // but in reality, that hasn't been enforced for many many Windows versions. So we give it // 1024 instead. const maxToolTipTextLen = 1024 // including NUL terminator type ToolTip struct { WindowBase } func NewToolTip() (*ToolTip, error) { tt, err := newToolTip(0) if err != nil { return nil, err } win.SetWindowPos(tt.hWnd, win.HWND_TOPMOST, 0, 0, 0, 0, win.SWP_NOMOVE|win.SWP_NOSIZE|win.SWP_NOACTIVATE) return tt, nil } func newToolTip(style uint32) (*ToolTip, error) { tt := new(ToolTip) if err := InitWindow( tt, nil, "tooltips_class32", win.WS_DISABLED|win.WS_POPUP|win.TTS_ALWAYSTIP|win.TTS_NOPREFIX|style, 0); err != nil { return nil, err } succeeded := false defer func() { if !succeeded { tt.Dispose() } }() tt.SendMessage(win.TTM_SETMAXTIPWIDTH, 0, 300) succeeded = true return tt, nil } func (tt *ToolTip) Title() string { var gt win.TTGETTITLE buf := make([]uint16, 100) gt.DwSize = uint32(unsafe.Sizeof(gt)) gt.Cch = uint32(len(buf)) gt.PszTitle = &buf[0] tt.SendMessage(win.TTM_GETTITLE, 0, uintptr(unsafe.Pointer(>))) return syscall.UTF16ToString(buf) } func (tt *ToolTip) SetTitle(title string) error { return tt.setTitle(title, win.TTI_NONE) } func (tt *ToolTip) SetInfoTitle(title string) error { return tt.setTitle(title, win.TTI_INFO) } func (tt *ToolTip) SetWarningTitle(title string) error { return tt.setTitle(title, win.TTI_WARNING) } func (tt *ToolTip) SetErrorTitle(title string) error { return tt.setTitle(title, win.TTI_ERROR) } func (tt *ToolTip) setTitle(title string, icon uintptr) error { if len(title) > 99 { title = title[:99] } if win.FALSE == tt.SendMessage(win.TTM_SETTITLE, icon, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(title)))) { return newError("TTM_SETTITLE failed") } return nil } func (tt *ToolTip) track(tool Widget) error { form := tool.Form() if form == nil { return nil } // HACK: We may have to delay this until the form is fully up to avoid glitches. if !form.AsFormBase().started { form.Starting().Once(func() { tt.track(tool) }) return nil } ti := tt.toolInfo(tool.Handle()) if ti == nil { return newError("unknown tool") } tt.SendMessage(win.TTM_TRACKACTIVATE, 1, uintptr(unsafe.Pointer(ti))) b := tool.BoundsPixels() p := Point{0, b.Y + b.Height}.toPOINT() if form.RightToLeftLayout() { p.X = int32(b.X - b.Width/2) } else { p.X = int32(b.X + b.Width/2) } win.ClientToScreen(tool.Parent().Handle(), &p) tt.SendMessage(win.TTM_TRACKPOSITION, 0, uintptr(win.MAKELONG(uint16(p.X), uint16(p.Y)))) var insertAfterHWND win.HWND if form := tool.Form(); form != nil && win.GetForegroundWindow() == form.Handle() { insertAfterHWND = win.HWND_TOP } else { insertAfterHWND = tool.Handle() } win.SetWindowPos(tt.hWnd, insertAfterHWND, 0, 0, 0, 0, win.SWP_NOMOVE|win.SWP_NOSIZE|win.SWP_NOACTIVATE) return nil } func (tt *ToolTip) untrack(tool Widget) error { ti := tt.toolInfo(tool.Handle()) if ti == nil { return newError("unknown tool") } tt.SendMessage(win.TTM_TRACKACTIVATE, 0, uintptr(unsafe.Pointer(ti))) return nil } func (tt *ToolTip) AddTool(tool Widget) error { return tt.addTool(tt.hwndForTool(tool), false) } func (tt *ToolTip) addTrackedTool(tool Widget) error { return tt.addTool(tt.hwndForTool(tool), true) } func (tt *ToolTip) addTool(hwnd win.HWND, track bool) error { if hwnd == 0 { return nil } var ti win.TOOLINFO ti.CbSize = uint32(unsafe.Sizeof(ti)) ti.Hwnd = hwnd ti.UFlags = win.TTF_IDISHWND if track { ti.UFlags |= win.TTF_TRACK } else { ti.UFlags |= win.TTF_SUBCLASS } ti.UId = uintptr(hwnd) if win.FALSE == tt.SendMessage(win.TTM_ADDTOOL, 0, uintptr(unsafe.Pointer(&ti))) { return newError("TTM_ADDTOOL failed") } return nil } func (tt *ToolTip) RemoveTool(tool Widget) error { return tt.removeTool(tt.hwndForTool(tool)) } func (tt *ToolTip) removeTool(hwnd win.HWND) error { var ti win.TOOLINFO ti.CbSize = uint32(unsafe.Sizeof(ti)) ti.Hwnd = hwnd ti.UId = uintptr(hwnd) tt.SendMessage(win.TTM_DELTOOL, 0, uintptr(unsafe.Pointer(&ti))) return nil } func (tt *ToolTip) Text(tool Widget) string { return tt.text(tt.hwndForTool(tool)) } func (tt *ToolTip) text(hwnd win.HWND) string { ti := tt.toolInfo(hwnd) if ti == nil { return "" } return win.UTF16PtrToString(ti.LpszText) } func (tt *ToolTip) SetText(tool Widget, text string) error { return tt.setText(tt.hwndForTool(tool), text) } func (tt *ToolTip) setText(hwnd win.HWND, text string) error { ti := tt.toolInfo(hwnd) if ti == nil { return newError("unknown tool") } n := 0 for i, r := range text { if r < 0x10000 { n++ } else { n += 2 // surrogate pair } if n >= maxToolTipTextLen { text = text[:i] break } } ti.LpszText = syscall.StringToUTF16Ptr(text) tt.SendMessage(win.TTM_SETTOOLINFO, 0, uintptr(unsafe.Pointer(ti))) return nil } func (tt *ToolTip) toolInfo(hwnd win.HWND) *win.TOOLINFO { var ti win.TOOLINFO var buf [maxToolTipTextLen]uint16 ti.CbSize = uint32(unsafe.Sizeof(ti)) ti.Hwnd = hwnd ti.UId = uintptr(hwnd) ti.LpszText = &buf[0] if win.FALSE == tt.SendMessage(win.TTM_GETTOOLINFO, 0, uintptr(unsafe.Pointer(&ti))) { return nil } return &ti } func (*ToolTip) hwndForTool(tool Widget) win.HWND { if hftt, ok := tool.(interface{ handleForToolTip() win.HWND }); ok { return hftt.handleForToolTip() } return tool.Handle() } ================================================ FILE: tooltiperrorpresenter.go ================================================ // Copyright 2017 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 ( "github.com/lxn/win" ) var ValidationErrorEffect WidgetGraphicsEffect type ToolTipErrorPresenter struct { toolTip *ToolTip curWidget Widget widget2error map[Widget]error trackedBoundsChangedHandles map[Window]int form Form formActivatingHandle int formDeactivatingHandle int } func NewToolTipErrorPresenter() (*ToolTipErrorPresenter, error) { tt, err := newToolTip(win.TTS_BALLOON) if err != nil { return nil, err } succeeded := false defer func() { if !succeeded { tt.Dispose() } }() succeeded = true return &ToolTipErrorPresenter{ toolTip: tt, widget2error: make(map[Widget]error), trackedBoundsChangedHandles: make(map[Window]int), formActivatingHandle: -1, formDeactivatingHandle: -1, }, nil } func (ttep *ToolTipErrorPresenter) Dispose() { if ttep.toolTip != nil { ttep.untrack() ttep.toolTip.Dispose() ttep.toolTip = nil if ttep.form != nil { ttep.form.AsFormBase().activatingPublisher.event.Detach(ttep.formActivatingHandle) ttep.form.AsFormBase().deactivatingPublisher.event.Detach(ttep.formDeactivatingHandle) ttep.form = nil } } } func (ttep *ToolTipErrorPresenter) PresentError(err error, widget Widget) { if ttep.toolTip == nil { return } if err == nil && widget == ttep.curWidget { ttep.untrack() } if err == nil { ttep.toolTip.RemoveTool(widget) delete(ttep.widget2error, widget) } else { ttep.toolTip.addTrackedTool(widget) ttep.widget2error[widget] = err } var found bool if widget != nil { walkDescendants(widget.Form().AsFormBase().clientComposite, func(w Window) bool { wt := w.(Widget) if !found { if e, ok := ttep.widget2error[wt]; ok { err, widget, found = e, wt, true } } if !found && wt == ttep.curWidget || wt != widget || err == nil { wt.GraphicsEffects().Remove(ValidationErrorEffect) } return true }) } if found { if widget != ttep.curWidget { ttep.untrack() } if ve, ok := err.(*ValidationError); ok { ttep.toolTip.SetErrorTitle(ve.title) ttep.toolTip.SetText(widget, ve.message) } else { ttep.toolTip.SetErrorTitle(tr("Invalid Input")) ttep.toolTip.SetText(widget, err.Error()) } if widget != ttep.curWidget { ttep.track(widget) if effects := widget.GraphicsEffects(); !effects.Contains(ValidationErrorEffect) { effects.Add(ValidationErrorEffect) } } } } func (ttep *ToolTipErrorPresenter) track(widget Widget) { var wnd Window wnd = widget for wnd != nil { handle := wnd.AsWindowBase().boundsChangedPublisher.event.Attach(func() { ttep.toolTip.track(widget) }) ttep.trackedBoundsChangedHandles[wnd] = handle if ttep.form == nil { ttep.form = widget.Form() ttep.formActivatingHandle = ttep.form.AsFormBase().activatingPublisher.event.Attach(func() { ttep.toolTip.track(widget) }) ttep.formDeactivatingHandle = ttep.form.AsFormBase().deactivatingPublisher.event.Attach(func() { ttep.toolTip.track(widget) }) } if w, ok := wnd.(Widget); ok { if parent := w.Parent(); parent != nil { wnd = parent } } else { break } } ttep.toolTip.track(widget) ttep.curWidget = widget } func (ttep *ToolTipErrorPresenter) untrack() { if ttep.curWidget == nil { return } ttep.toolTip.untrack(ttep.curWidget) for wnd, handle := range ttep.trackedBoundsChangedHandles { wnd.AsWindowBase().boundsChangedPublisher.event.Detach(handle) delete(ttep.trackedBoundsChangedHandles, wnd) } ttep.curWidget = nil } ================================================ FILE: treeitemevent.go ================================================ // Copyright 2011 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 type treeItemEventHandlerInfo struct { handler TreeItemEventHandler once bool } type TreeItemEventHandler func(item TreeItem) type TreeItemEvent struct { handlers []treeItemEventHandlerInfo } func (e *TreeItemEvent) Attach(handler TreeItemEventHandler) int { handlerInfo := treeItemEventHandlerInfo{handler, false} for i, h := range e.handlers { if h.handler == nil { e.handlers[i] = handlerInfo return i } } e.handlers = append(e.handlers, handlerInfo) return len(e.handlers) - 1 } func (e *TreeItemEvent) Detach(handle int) { e.handlers[handle].handler = nil } func (e *TreeItemEvent) Once(handler TreeItemEventHandler) { i := e.Attach(handler) e.handlers[i].once = true } type TreeItemEventPublisher struct { event TreeItemEvent } func (p *TreeItemEventPublisher) Event() *TreeItemEvent { return &p.event } func (p *TreeItemEventPublisher) Publish(item TreeItem) { for i, h := range p.event.handlers { if h.handler != nil { h.handler(item) if h.once { p.event.Detach(i) } } } } ================================================ FILE: treeview.go ================================================ // 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" "github.com/lxn/win" ) type treeViewItemInfo struct { handle win.HTREEITEM child2Handle map[TreeItem]win.HTREEITEM } type TreeView struct { WidgetBase model TreeModel lazyPopulation bool itemsResetEventHandlerHandle int itemChangedEventHandlerHandle int itemInsertedEventHandlerHandle int itemRemovedEventHandlerHandle int item2Info map[TreeItem]*treeViewItemInfo handle2Item map[win.HTREEITEM]TreeItem currItem TreeItem hIml win.HIMAGELIST usingSysIml bool imageUintptr2Index map[uintptr]int32 filePath2IconIndex map[string]int32 expandedChangedPublisher TreeItemEventPublisher currentItemChangedPublisher EventPublisher itemActivatedPublisher EventPublisher } func NewTreeView(parent Container) (*TreeView, error) { tv := new(TreeView) if err := InitWidget( tv, parent, "SysTreeView32", win.WS_TABSTOP|win.WS_VISIBLE|win.TVS_HASBUTTONS|win.TVS_LINESATROOT|win.TVS_SHOWSELALWAYS|win.TVS_TRACKSELECT, win.WS_EX_CLIENTEDGE); err != nil { return nil, err } succeeded := false defer func() { if !succeeded { tv.Dispose() } }() if hr := win.HRESULT(tv.SendMessage(win.TVM_SETEXTENDEDSTYLE, win.TVS_EX_DOUBLEBUFFER, win.TVS_EX_DOUBLEBUFFER)); win.FAILED(hr) { return nil, errorFromHRESULT("TVM_SETEXTENDEDSTYLE", hr) } if err := tv.setTheme("Explorer"); err != nil { return nil, err } tv.GraphicsEffects().Add(InteractionEffect) tv.GraphicsEffects().Add(FocusEffect) tv.MustRegisterProperty("CurrentItem", NewReadOnlyProperty( func() interface{} { return tv.CurrentItem() }, tv.CurrentItemChanged())) tv.MustRegisterProperty("CurrentItemLevel", NewReadOnlyProperty( func() interface{} { level := -1 item := tv.CurrentItem() for item != nil { level++ item = item.Parent() } return level }, tv.CurrentItemChanged())) tv.MustRegisterProperty("HasCurrentItem", NewReadOnlyBoolProperty( func() bool { return tv.CurrentItem() != nil }, tv.CurrentItemChanged())) succeeded = true return tv, nil } func (tv *TreeView) Dispose() { tv.WidgetBase.Dispose() tv.disposeImageListAndCaches() } func (tv *TreeView) SetBackground(bg Brush) { tv.WidgetBase.SetBackground(bg) color := Color(win.GetSysColor(win.COLOR_WINDOW)) if bg != nil { type Colorer interface { Color() Color } if c, ok := bg.(Colorer); ok { color = c.Color() } } tv.SendMessage(win.TVM_SETBKCOLOR, 0, uintptr(color)) } func (tv *TreeView) Model() TreeModel { return tv.model } func (tv *TreeView) SetModel(model TreeModel) error { if tv.model != nil { tv.model.ItemsReset().Detach(tv.itemsResetEventHandlerHandle) tv.model.ItemChanged().Detach(tv.itemChangedEventHandlerHandle) tv.model.ItemInserted().Detach(tv.itemInsertedEventHandlerHandle) tv.model.ItemRemoved().Detach(tv.itemRemovedEventHandlerHandle) tv.disposeImageListAndCaches() } tv.model = model if model != nil { tv.lazyPopulation = model.LazyPopulation() tv.itemsResetEventHandlerHandle = model.ItemsReset().Attach(func(parent TreeItem) { if parent == nil { tv.resetItems() } else if tv.item2Info[parent] != nil { tv.SetSuspended(true) defer tv.SetSuspended(false) if err := tv.removeDescendants(parent); err != nil { return } if err := tv.insertChildren(parent); err != nil { return } } }) tv.itemChangedEventHandlerHandle = model.ItemChanged().Attach(func(item TreeItem) { if item == nil || tv.item2Info[item] == nil { return } if err := tv.updateItem(item); err != nil { return } }) tv.itemInsertedEventHandlerHandle = model.ItemInserted().Attach(func(item TreeItem) { tv.SetSuspended(true) defer tv.SetSuspended(false) var hInsertAfter win.HTREEITEM parent := item.Parent() for i := parent.ChildCount() - 1; i >= 0; i-- { if parent.ChildAt(i) == item { if i > 0 { hInsertAfter = tv.item2Info[parent.ChildAt(i-1)].handle } else { hInsertAfter = win.TVI_FIRST } } } if _, err := tv.insertItemAfter(item, hInsertAfter); err != nil { return } }) tv.itemRemovedEventHandlerHandle = model.ItemRemoved().Attach(func(item TreeItem) { if err := tv.removeItem(item); err != nil { return } }) } return tv.resetItems() } func (tv *TreeView) CurrentItem() TreeItem { return tv.currItem } func (tv *TreeView) SetCurrentItem(item TreeItem) error { if item == tv.currItem { return nil } if item != nil { if err := tv.ensureItemAndAncestorsInserted(item); err != nil { return err } } handle, err := tv.handleForItem(item) if err != nil { return err } if 0 == tv.SendMessage(win.TVM_SELECTITEM, win.TVGN_CARET, uintptr(handle)) { return newError("SendMessage(TVM_SELECTITEM) failed") } tv.currItem = item return nil } func (tv *TreeView) EnsureVisible(item TreeItem) error { handle, err := tv.handleForItem(item) if err != nil { return err } tv.SendMessage(win.TVM_ENSUREVISIBLE, 0, uintptr(handle)) return nil } func (tv *TreeView) handleForItem(item TreeItem) (win.HTREEITEM, error) { if item != nil { if info := tv.item2Info[item]; info == nil { return 0, newError("invalid item") } else { return info.handle, nil } } return 0, newError("invalid item") } // ItemAt determines the location of the specified point in native pixels relative to the client area of a tree-view control. func (tv *TreeView) ItemAt(x, y int) TreeItem { hti := win.TVHITTESTINFO{Pt: Point{x, y}.toPOINT()} tv.SendMessage(win.TVM_HITTEST, 0, uintptr(unsafe.Pointer(&hti))) if item, ok := tv.handle2Item[hti.HItem]; ok { return item } return nil } // ItemHeight returns the height of each item in native pixels. func (tv *TreeView) ItemHeight() int { return int(tv.SendMessage(win.TVM_GETITEMHEIGHT, 0, 0)) } // SetItemHeight sets the height of the tree-view items in native pixels. func (tv *TreeView) SetItemHeight(height int) { tv.SendMessage(win.TVM_SETITEMHEIGHT, uintptr(height), 0) } func (tv *TreeView) resetItems() error { tv.SetSuspended(true) defer tv.SetSuspended(false) if err := tv.clearItems(); err != nil { return err } if tv.model == nil { return nil } if err := tv.insertRoots(); err != nil { return err } return nil } func (tv *TreeView) clearItems() error { if 0 == tv.SendMessage(win.TVM_DELETEITEM, 0, 0) { return newError("SendMessage(TVM_DELETEITEM) failed") } tv.item2Info = make(map[TreeItem]*treeViewItemInfo) tv.handle2Item = make(map[win.HTREEITEM]TreeItem) return nil } func (tv *TreeView) insertRoots() error { for i := tv.model.RootCount() - 1; i >= 0; i-- { if _, err := tv.insertItem(tv.model.RootAt(i)); err != nil { return err } } return nil } func (tv *TreeView) ApplyDPI(dpi int) { tv.WidgetBase.ApplyDPI(dpi) tv.disposeImageListAndCaches() } func (tv *TreeView) applyImageListForImage(image interface{}) { tv.hIml, tv.usingSysIml, _ = imageListForImage(image, tv.DPI()) tv.SendMessage(win.TVM_SETIMAGELIST, 0, uintptr(tv.hIml)) tv.imageUintptr2Index = make(map[uintptr]int32) tv.filePath2IconIndex = make(map[string]int32) } func (tv *TreeView) disposeImageListAndCaches() { if tv.hIml != 0 && !tv.usingSysIml { win.ImageList_Destroy(tv.hIml) } tv.hIml = 0 tv.imageUintptr2Index = nil tv.filePath2IconIndex = nil } func (tv *TreeView) setTVITEMImageInfo(tvi *win.TVITEM, item TreeItem) { if imager, ok := item.(Imager); ok { if tv.hIml == 0 { tv.applyImageListForImage(imager.Image()) } // FIXME: If not setting TVIF_SELECTEDIMAGE and tvi.ISelectedImage, // some default icon will show up, even though we have not asked for it. tvi.Mask |= win.TVIF_IMAGE | win.TVIF_SELECTEDIMAGE tvi.IImage = imageIndexMaybeAdd( imager.Image(), tv.hIml, tv.usingSysIml, tv.imageUintptr2Index, tv.filePath2IconIndex, tv.DPI()) tvi.ISelectedImage = tvi.IImage } } func (tv *TreeView) insertItem(item TreeItem) (win.HTREEITEM, error) { return tv.insertItemAfter(item, win.TVI_FIRST) } func (tv *TreeView) insertItemAfter(item TreeItem, hInsertAfter win.HTREEITEM) (win.HTREEITEM, error) { var tvins win.TVINSERTSTRUCT tvi := &tvins.Item tvi.Mask = win.TVIF_CHILDREN | win.TVIF_TEXT tvi.PszText = win.LPSTR_TEXTCALLBACK tvi.CChildren = win.I_CHILDRENCALLBACK tv.setTVITEMImageInfo(tvi, item) parent := item.Parent() if parent == nil { tvins.HParent = win.TVI_ROOT } else { info := tv.item2Info[parent] if info == nil { return 0, newError("invalid parent") } tvins.HParent = info.handle } tvins.HInsertAfter = hInsertAfter hItem := win.HTREEITEM(tv.SendMessage(win.TVM_INSERTITEM, 0, uintptr(unsafe.Pointer(&tvins)))) if hItem == 0 { return 0, newError("TVM_INSERTITEM failed") } tv.item2Info[item] = &treeViewItemInfo{hItem, make(map[TreeItem]win.HTREEITEM)} tv.handle2Item[hItem] = item if !tv.lazyPopulation { if err := tv.insertChildren(item); err != nil { return 0, err } } return hItem, nil } func (tv *TreeView) insertChildren(parent TreeItem) error { info := tv.item2Info[parent] for i := parent.ChildCount() - 1; i >= 0; i-- { child := parent.ChildAt(i) if handle, err := tv.insertItem(child); err != nil { return err } else { info.child2Handle[child] = handle } } return nil } func (tv *TreeView) updateItem(item TreeItem) error { tvi := &win.TVITEM{ Mask: win.TVIF_TEXT, HItem: tv.item2Info[item].handle, PszText: win.LPSTR_TEXTCALLBACK, } tv.setTVITEMImageInfo(tvi, item) if 0 == tv.SendMessage(win.TVM_SETITEM, 0, uintptr(unsafe.Pointer(tvi))) { return newError("SendMessage(TVM_SETITEM) failed") } return nil } func (tv *TreeView) removeItem(item TreeItem) error { if err := tv.removeDescendants(item); err != nil { return err } info := tv.item2Info[item] if info == nil { return newError("invalid item") } if 0 == tv.SendMessage(win.TVM_DELETEITEM, 0, uintptr(info.handle)) { return newError("SendMessage(TVM_DELETEITEM) failed") } if parentInfo := tv.item2Info[item.Parent()]; parentInfo != nil { delete(parentInfo.child2Handle, item) } delete(tv.item2Info, item) delete(tv.handle2Item, info.handle) return nil } func (tv *TreeView) removeDescendants(parent TreeItem) error { for item, _ := range tv.item2Info[parent].child2Handle { if err := tv.removeItem(item); err != nil { return err } } return nil } func (tv *TreeView) ensureItemAndAncestorsInserted(item TreeItem) error { if item == nil { return newError("invalid item") } tv.SetSuspended(true) defer tv.SetSuspended(false) var hierarchy []TreeItem for item != nil && tv.item2Info[item] == nil { item = item.Parent() if item != nil { hierarchy = append(hierarchy, item) } else { return newError("invalid item") } } for i := len(hierarchy) - 1; i >= 0; i-- { if err := tv.insertChildren(hierarchy[i]); err != nil { return err } } return nil } func (tv *TreeView) Expanded(item TreeItem) bool { if tv.item2Info[item] == nil { return false } tvi := &win.TVITEM{ HItem: tv.item2Info[item].handle, Mask: win.TVIF_STATE, StateMask: win.TVIS_EXPANDED, } if 0 == tv.SendMessage(win.TVM_GETITEM, 0, uintptr(unsafe.Pointer(tvi))) { newError("SendMessage(TVM_GETITEM) failed") } return tvi.State&win.TVIS_EXPANDED != 0 } func (tv *TreeView) SetExpanded(item TreeItem, expanded bool) error { if expanded { if err := tv.ensureItemAndAncestorsInserted(item); err != nil { return err } } info := tv.item2Info[item] if info == nil { return newError("invalid item") } var action uintptr if expanded { action = win.TVE_EXPAND } else { action = win.TVE_COLLAPSE } if 0 == tv.SendMessage(win.TVM_EXPAND, action, uintptr(info.handle)) { return newError("SendMessage(TVM_EXPAND) failed") } return nil } func (tv *TreeView) ExpandedChanged() *TreeItemEvent { return tv.expandedChangedPublisher.Event() } func (tv *TreeView) CurrentItemChanged() *Event { return tv.currentItemChangedPublisher.Event() } func (tv *TreeView) ItemActivated() *Event { return tv.itemActivatedPublisher.Event() } func (tv *TreeView) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_GETDLGCODE: if wParam == win.VK_RETURN { return win.DLGC_WANTALLKEYS } case win.WM_NOTIFY: nmhdr := (*win.NMHDR)(unsafe.Pointer(lParam)) switch nmhdr.Code { case win.TVN_GETDISPINFO: nmtvdi := (*win.NMTVDISPINFO)(unsafe.Pointer(lParam)) item := tv.handle2Item[nmtvdi.Item.HItem] if nmtvdi.Item.Mask&win.TVIF_TEXT != 0 { text := item.Text() utf16 := syscall.StringToUTF16(text) buf := (*[264]uint16)(unsafe.Pointer(nmtvdi.Item.PszText)) max := mini(len(utf16), int(nmtvdi.Item.CchTextMax)) copy((*buf)[:], utf16[:max]) (*buf)[max-1] = 0 } if nmtvdi.Item.Mask&win.TVIF_CHILDREN != 0 { if hc, ok := item.(HasChilder); ok { if hc.HasChild() { nmtvdi.Item.CChildren = 1 } else { nmtvdi.Item.CChildren = 0 } } else { nmtvdi.Item.CChildren = int32(item.ChildCount()) } } case win.TVN_ITEMEXPANDING: nmtv := (*win.NMTREEVIEW)(unsafe.Pointer(lParam)) item := tv.handle2Item[nmtv.ItemNew.HItem] if nmtv.Action == win.TVE_EXPAND && tv.lazyPopulation { info := tv.item2Info[item] if len(info.child2Handle) == 0 { tv.insertChildren(item) } } case win.TVN_ITEMEXPANDED: nmtv := (*win.NMTREEVIEW)(unsafe.Pointer(lParam)) item := tv.handle2Item[nmtv.ItemNew.HItem] switch nmtv.Action { case win.TVE_COLLAPSE: tv.expandedChangedPublisher.Publish(item) case win.TVE_COLLAPSERESET: case win.TVE_EXPAND: tv.expandedChangedPublisher.Publish(item) case win.TVE_EXPANDPARTIAL: case win.TVE_TOGGLE: } case win.NM_DBLCLK: tv.itemActivatedPublisher.Publish() case win.TVN_KEYDOWN: nmtvkd := (*win.NMTVKEYDOWN)(unsafe.Pointer(lParam)) if nmtvkd.WVKey == uint16(KeyReturn) { tv.itemActivatedPublisher.Publish() } case win.TVN_SELCHANGED: nmtv := (*win.NMTREEVIEW)(unsafe.Pointer(lParam)) tv.currItem = tv.handle2Item[nmtv.ItemNew.HItem] tv.currentItemChangedPublisher.Publish() } } return tv.WidgetBase.WndProc(hwnd, msg, wParam, lParam) } func (*TreeView) NeedsWmSize() bool { return true } func (tv *TreeView) CreateLayoutItem(ctx *LayoutContext) LayoutItem { return NewGreedyLayoutItem() } ================================================ FILE: util.go ================================================ // 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 ( "bytes" "math" "math/big" "strconv" "strings" "syscall" "time" "github.com/lxn/win" ) var ( decimalSepB byte decimalSepUint16 uint16 decimalSepS string groupSepB byte groupSepUint16 uint16 groupSepS string ) func init() { AppendToWalkInit(func() { var buf [4]uint16 win.GetLocaleInfo(win.LOCALE_USER_DEFAULT, win.LOCALE_SDECIMAL, &buf[0], int32(len(buf))) decimalSepB = byte(buf[0]) decimalSepS = syscall.UTF16ToString(buf[0:1]) decimalSepUint16 = buf[0] win.GetLocaleInfo(win.LOCALE_USER_DEFAULT, win.LOCALE_STHOUSAND, &buf[0], int32(len(buf))) groupSepB = byte(buf[0]) groupSepS = syscall.UTF16ToString(buf[0:1]) groupSepUint16 = buf[0] }) } func maxi(a, b int) int { if a > b { return a } return b } func mini(a, b int) int { if a < b { return a } return b } func boolToInt(value bool) int { if value { return 1 } return 0 } func uint16IndexUint16(s []uint16, v uint16) int { for i, u := range s { if u == v { return i } } return -1 } func uint16ContainsUint16(s []uint16, v uint16) bool { return uint16IndexUint16(s, v) != -1 } func uint16CountUint16(s []uint16, v uint16) int { var count int for _, u := range s { if u == v { count++ } } return count } func uint16RemoveUint16(s []uint16, v uint16) []uint16 { count := uint16CountUint16(s, v) if count == 0 { return s } ret := make([]uint16, 0, len(s)-count) for _, u := range s { if u != v { ret = append(ret, u) } } return ret } func assertFloat64Or(value interface{}, defaultValue float64) float64 { if f, ok := value.(float64); ok { return f } return defaultValue } func assertIntOr(value interface{}, defaultValue int) int { if n, ok := value.(int); ok { return n } return defaultValue } func assertStringOr(value interface{}, defaultValue string) string { if s, ok := value.(string); ok { return s } return defaultValue } func assertTimeOr(value interface{}, defaultValue time.Time) time.Time { if t, ok := value.(time.Time); ok { return t } return defaultValue } func ParseFloat(s string) (float64, error) { s = strings.TrimSpace(s) t := FormatFloatGrouped(1000, 2) replaceSep := func(new string, index func(string, func(rune) bool) int) { i := index(t, func(r rune) bool { return r < '0' || r > '9' }) var sep string if i > -1 { sep = string(t[i]) } if sep != "" { s = strings.Replace(s, string(sep), new, -1) } } replaceSep("", strings.IndexFunc) replaceSep(".", strings.LastIndexFunc) return strconv.ParseFloat(s, 64) } func FormatFloat(f float64, prec int) string { return formatFloatString(strconv.FormatFloat(f, 'f', prec, 64), prec, false) } func FormatFloatGrouped(f float64, prec int) string { return formatFloatString(strconv.FormatFloat(f, 'f', maxi(1, prec), 64), prec, true) } func formatBigRat(r *big.Rat, prec int) string { return formatFloatString(r.FloatString(prec), prec, false) } func formatBigRatGrouped(r *big.Rat, prec int) string { return formatFloatString(r.FloatString(prec), prec, true) } func formatFloatString(s string, prec int, grouped bool) string { switch s { case "NaN", "-Inf", "+Inf": return s } s = strings.Replace(s, ".", decimalSepS, 1) if !grouped { return s } b := new(bytes.Buffer) var firstDigit int if len(s) > 0 && s[0] == '-' { firstDigit = 1 b.WriteByte('-') s = s[1:] } intLen := len(s) - maxi(1, prec) - 1 n := intLen % 3 if n != 0 { b.WriteString(s[:n]) } for i := n; i < intLen; i += 3 { if b.Len() > firstDigit { b.WriteByte(groupSepB) } b.WriteString(s[i : i+3]) } b.WriteString(s[intLen:]) s = b.String() if prec == 0 { s = s[:len(s)-2] } return s } func applyEnabledToDescendants(window Window, enabled bool) { wb := window.AsWindowBase() wb.applyEnabled(enabled) walkDescendants(window, func(w Window) bool { if w.Handle() == wb.hWnd { return true } if enabled && !w.AsWindowBase().enabled { return false } w.(applyEnableder).applyEnabled(enabled) return true }) } var seenInApplyFontToDescendantsDuringDPIChange map[*WindowBase]bool func applyFontToDescendants(window Window, font *Font) { wb := window.AsWindowBase() wb.applyFont(font) walkDescendants(window, func(w Window) bool { if w.Handle() == wb.hWnd { return true } if w.AsWindowBase().font != nil { return false } if seenInApplyFontToDescendantsDuringDPIChange != nil { wb := w.AsWindowBase() if seenInApplyFontToDescendantsDuringDPIChange[wb] { return true } seenInApplyFontToDescendantsDuringDPIChange[wb] = true } w.(applyFonter).applyFont(font) return true }) } func applySysColorsToDescendants(window Window) { wb := window.AsWindowBase() wb.ApplySysColors() walkDescendants(window, func(w Window) bool { if w.Handle() == wb.hWnd { return true } w.(ApplySysColorser).ApplySysColors() return true }) } var seenInApplyDPIToDescendantsDuringDPIChange map[*WindowBase]bool func applyDPIToDescendants(window Window, dpi int) { wb := window.AsWindowBase() wb.ApplyDPI(dpi) walkDescendants(window, func(w Window) bool { if w.Handle() == wb.hWnd { return true } if seenInApplyDPIToDescendantsDuringDPIChange != nil { wb := w.AsWindowBase() if seenInApplyDPIToDescendantsDuringDPIChange[wb] { return true } seenInApplyDPIToDescendantsDuringDPIChange[wb] = true } w.(ApplyDPIer).ApplyDPI(dpi) return true }) } func walkDescendants(window Window, f func(w Window) bool) { window = window.AsWindowBase().window if window == nil || !f(window) { return } var children []*WidgetBase switch w := window.(type) { case *NumberEdit: if w.edit != nil { children = append(children, w.edit.AsWidgetBase()) } case *TabWidget: for _, p := range w.Pages().items { children = append(children, p.AsWidgetBase()) } case Container: if c := w.Children(); c != nil { children = c.items } else { children = nil } } for _, wb := range children { walkDescendants(wb.window.(Widget), f) } } func less(a, b interface{}, order SortOrder) bool { if _, ok := a.(error); ok { _, bIsErr := b.(error) return order == SortAscending == !bIsErr } if _, ok := b.(error); ok { return order == SortDescending } if a == nil { return order == SortAscending == (b != nil) } if b == nil { return order == SortDescending } switch av := a.(type) { case string: if bv, ok := b.(string); ok { if order == SortAscending { return av < bv } else { return bv < av } } case int: if bv, ok := b.(int); ok { if order == SortAscending { return av < bv } else { return bv < av } } case float64: if bv, ok := b.(float64); ok { if order == SortAscending { return av < bv } else { return bv < av } } case float32: if bv, ok := b.(float32); ok { if order == SortAscending { return av < bv } else { return bv < av } } case int64: if bv, ok := b.(int64); ok { if order == SortAscending { return av < bv } else { return bv < av } } case int32: if bv, ok := b.(int32); ok { if order == SortAscending { return av < bv } else { return bv < av } } case int16: if bv, ok := b.(int16); ok { if order == SortAscending { return av < bv } else { return bv < av } } case int8: if bv, ok := b.(int8); ok { if order == SortAscending { return av < bv } else { return bv < av } } case uint: if bv, ok := b.(uint); ok { if order == SortAscending { return av < bv } else { return bv < av } } case uint64: if bv, ok := b.(uint64); ok { if order == SortAscending { return av < bv } else { return bv < av } } case uint32: if bv, ok := b.(uint32); ok { if order == SortAscending { return av < bv } else { return bv < av } } case uint16: if bv, ok := b.(uint16); ok { if order == SortAscending { return av < bv } else { return bv < av } } case uint8: if bv, ok := b.(uint8); ok { if order == SortAscending { return av < bv } else { return bv < av } } case time.Time: if bv, ok := b.(time.Time); ok { if order == SortAscending { return av.Before(bv) } else { return bv.Before(av) } } case bool: if bv, ok := b.(bool); ok { if order == SortAscending { return !av && bv } else { return !bv && av } } } return false } func dpiForHDC(hdc win.HDC) int { if hwnd := win.WindowFromDC(hdc); hwnd != 0 { return int(win.GetDpiForWindow(hwnd)) } return int(win.GetDeviceCaps(hdc, win.LOGPIXELSX)) } // IntFrom96DPI converts from 1/96" units to native pixels. func IntFrom96DPI(value, dpi int) int { return scaleInt(value, float64(dpi)/96.0) } // IntTo96DPI converts from native pixels to 1/96" units. func IntTo96DPI(value, dpi int) int { return scaleInt(value, 96.0/float64(dpi)) } func scaleInt(value int, scale float64) int { return int(math.Round(float64(value) * scale)) } // MarginsFrom96DPI converts from 1/96" units to native pixels. func MarginsFrom96DPI(value Margins, dpi int) Margins { return scaleMargins(value, float64(dpi)/96.0) } // MarginsTo96DPI converts from native pixels to 1/96" units. func MarginsTo96DPI(value Margins, dpi int) Margins { return scaleMargins(value, 96.0/float64(dpi)) } func scaleMargins(value Margins, scale float64) Margins { return Margins{ HNear: scaleInt(value.HNear, scale), VNear: scaleInt(value.VNear, scale), HFar: scaleInt(value.HFar, scale), VFar: scaleInt(value.VFar, scale), } } // PointFrom96DPI converts from 1/96" units to native pixels. func PointFrom96DPI(value Point, dpi int) Point { return scalePoint(value, float64(dpi)/96.0) } // PointTo96DPI converts from native pixels to 1/96" units. func PointTo96DPI(value Point, dpi int) Point { return scalePoint(value, 96.0/float64(dpi)) } func scalePoint(value Point, scale float64) Point { return Point{ X: scaleInt(value.X, scale), Y: scaleInt(value.Y, scale), } } // RectangleFrom96DPI converts from 1/96" units to native pixels. func RectangleFrom96DPI(value Rectangle, dpi int) Rectangle { return scaleRectangle(value, float64(dpi)/96.0) } // RectangleTo96DPI converts from native pixels to 1/96" units. func RectangleTo96DPI(value Rectangle, dpi int) Rectangle { return scaleRectangle(value, 96.0/float64(dpi)) } func scaleRectangle(value Rectangle, scale float64) Rectangle { return Rectangle{ X: scaleInt(value.X, scale), Y: scaleInt(value.Y, scale), Width: scaleInt(value.Width, scale), Height: scaleInt(value.Height, scale), } } // SizeFrom96DPI converts from 1/96" units to native pixels. func SizeFrom96DPI(value Size, dpi int) Size { return scaleSize(value, float64(dpi)/96.0) } // SizeTo96DPI converts from native pixels to 1/96" units. func SizeTo96DPI(value Size, dpi int) Size { return scaleSize(value, 96.0/float64(dpi)) } func scaleSize(value Size, scale float64) Size { return Size{ Width: scaleInt(value.Width, scale), Height: scaleInt(value.Height, scale), } } ================================================ FILE: validators.go ================================================ // Copyright 2011 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 ( "errors" "fmt" "math" "regexp" ) type Validator interface { Validate(v interface{}) error } type ValidationError struct { title string message string } func NewValidationError(title, message string) *ValidationError { return &ValidationError{title: title, message: message} } func (ve *ValidationError) Title() string { return ve.title } func (ve *ValidationError) Message() string { return ve.message } func (ve *ValidationError) Error() string { return fmt.Sprintf("%s - %s", ve.title, ve.message) } type RangeValidator struct { min float64 max float64 } func NewRangeValidator(min, max float64) (*RangeValidator, error) { if max < min { return nil, errors.New("max < min") } return &RangeValidator{min: min, max: max}, nil } func (rv *RangeValidator) Min() float64 { return rv.min } func (rv *RangeValidator) Max() float64 { return rv.max } func (rv *RangeValidator) Reset(min, max float64) error { if max < min { return errors.New("max < min") } rv.min, rv.max = min, max return nil } func (rv *RangeValidator) Validate(v interface{}) error { f64 := v.(float64) if f64 < rv.min || f64 > rv.max { var msg string if math.Abs(rv.min-math.Floor(rv.min)) < math.SmallestNonzeroFloat64 && math.Abs(rv.max-math.Floor(rv.max)) < math.SmallestNonzeroFloat64 { msg = fmt.Sprintf(tr("Please enter a number from %.f to %.f.", "walk"), rv.min, rv.max) } else { msg = fmt.Sprintf(tr("Please enter a number from %s to %s.", "walk"), FormatFloatGrouped(rv.min, 2), FormatFloatGrouped(rv.max, 2)) } return NewValidationError(tr("Number out of allowed range", "walk"), msg) } return nil } type RegexpValidator struct { re *regexp.Regexp } func NewRegexpValidator(pattern string) (*RegexpValidator, error) { re, err := regexp.Compile(pattern) if err != nil { return nil, err } return &RegexpValidator{re}, nil } func (rv *RegexpValidator) Pattern() string { return rv.re.String() } func (rv *RegexpValidator) Validate(v interface{}) error { var matched bool switch val := v.(type) { case string: matched = rv.re.MatchString(val) case []byte: matched = rv.re.Match(val) case fmt.Stringer: matched = rv.re.MatchString(val.String()) default: panic("Unsupported type") } if !matched { return errors.New(tr("The text does not match the required pattern.", "walk")) } return nil } type selectionRequiredValidator struct { } var selectionRequiredValidatorSingleton Validator = selectionRequiredValidator{} func SelectionRequiredValidator() Validator { return selectionRequiredValidatorSingleton } func (selectionRequiredValidator) Validate(v interface{}) error { if v == nil { // For Widgets like ComboBox nil is passed to indicate "no selection". return NewValidationError( tr("Selection Required", "walk"), tr("Please select one of the provided options.", "walk")) } return nil } ================================================ FILE: walk.go ================================================ // Copyright 2011 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 ( "errors" ) var ( ErrInvalidType = errors.New("invalid type") ) func LogErrors() bool { return logErrors } func SetLogErrors(v bool) { logErrors = v } func PanicOnError() bool { return panicOnError } func SetPanicOnError(v bool) { panicOnError = v } func TranslationFunc() TranslationFunction { return translation } func SetTranslationFunc(f TranslationFunction) { translation = f } type TranslationFunction func(source string, context ...string) string var translation TranslationFunction func tr(source string, context ...string) string { if translation == nil { return source } return translation(source, context...) } type Disposable interface { Dispose() } type Disposables struct { items []Disposable done bool } func (d *Disposables) Add(item Disposable) { d.items = append(d.items, item) } func (d *Disposables) Spare() { d.done = true } func (d *Disposables) Treat() { if d.done { return } for _, item := range d.items { item.Dispose() } d.done = true } ================================================ FILE: webview.go ================================================ // 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 ( "fmt" "syscall" "unsafe" ) import ( "github.com/lxn/win" ) const webViewWindowClass = `\o/ Walk_WebView_Class \o/` func init() { AppendToWalkInit(func() { MustRegisterWindowClass(webViewWindowClass) }) } type WebView struct { WidgetBase clientSite webViewIOleClientSite // IMPORTANT: Must remain first member after WidgetBase browserObject *win.IOleObject urlChangedPublisher EventPublisher shortcutsEnabled bool shortcutsEnabledChangedPublisher EventPublisher nativeContextMenuEnabled bool nativeContextMenuEnabledChangedPublisher EventPublisher navigatingPublisher WebViewNavigatingEventPublisher navigatedPublisher StringEventPublisher downloadingPublisher EventPublisher downloadedPublisher EventPublisher documentCompletedPublisher StringEventPublisher navigatedErrorPublisher WebViewNavigatedErrorEventPublisher newWindowPublisher WebViewNewWindowEventPublisher quittingPublisher EventPublisher windowClosingPublisher WebViewWindowClosingEventPublisher statusBarVisible bool statusBarVisibleChangedPublisher EventPublisher isTheaterMode bool theaterModeChangedPublisher EventPublisher toolBarVisible bool toolBarVisibleChangedPublisher EventPublisher browserVisible bool browserVisibleChangedPublisher EventPublisher toolBarEnabled bool toolBarEnabledChangedPublisher EventPublisher canGoBack bool canGoBackChangedPublisher EventPublisher canGoForward bool canGoForwardChangedPublisher EventPublisher progressValue int32 progressMax int32 progressChangedPublisher EventPublisher statusText string statusTextChangedPublisher EventPublisher documentTitle string documentTitleChangedPublisher EventPublisher } func NewWebView(parent Container) (*WebView, error) { if hr := win.OleInitialize(); hr != win.S_OK && hr != win.S_FALSE { return nil, newError(fmt.Sprint("OleInitialize Error: ", hr)) } wv := &WebView{ clientSite: webViewIOleClientSite{ IOleClientSite: win.IOleClientSite{ LpVtbl: webViewIOleClientSiteVtbl, }, inPlaceSite: webViewIOleInPlaceSite{ IOleInPlaceSite: win.IOleInPlaceSite{ LpVtbl: webViewIOleInPlaceSiteVtbl, }, inPlaceFrame: webViewIOleInPlaceFrame{ IOleInPlaceFrame: win.IOleInPlaceFrame{ LpVtbl: webViewIOleInPlaceFrameVtbl, }, }, }, docHostUIHandler: webViewIDocHostUIHandler{ IDocHostUIHandler: win.IDocHostUIHandler{ LpVtbl: webViewIDocHostUIHandlerVtbl, }, }, webBrowserEvents2: webViewDWebBrowserEvents2{ DWebBrowserEvents2: win.DWebBrowserEvents2{ LpVtbl: webViewDWebBrowserEvents2Vtbl, }, }, }, shortcutsEnabled: false, nativeContextMenuEnabled: false, } if err := InitWidget( wv, parent, webViewWindowClass, win.WS_CLIPCHILDREN|win.WS_VISIBLE, 0); err != nil { return nil, err } wv.clientSite.inPlaceSite.inPlaceFrame.webView = wv succeeded := false defer func() { if !succeeded { wv.Dispose() } }() var classFactoryPtr unsafe.Pointer if hr := win.CoGetClassObject(&win.CLSID_WebBrowser, win.CLSCTX_INPROC_HANDLER|win.CLSCTX_INPROC_SERVER, nil, &win.IID_IClassFactory, &classFactoryPtr); win.FAILED(hr) { return nil, errorFromHRESULT("CoGetClassObject", hr) } classFactory := (*win.IClassFactory)(classFactoryPtr) defer classFactory.Release() var browserObjectPtr unsafe.Pointer if hr := classFactory.CreateInstance(nil, &win.IID_IOleObject, &browserObjectPtr); win.FAILED(hr) { return nil, errorFromHRESULT("IClassFactory.CreateInstance", hr) } browserObject := (*win.IOleObject)(browserObjectPtr) wv.browserObject = browserObject if hr := browserObject.SetClientSite((*win.IOleClientSite)(unsafe.Pointer(&wv.clientSite))); win.FAILED(hr) { return nil, errorFromHRESULT("IOleObject.SetClientSite", hr) } if hr := browserObject.SetHostNames(syscall.StringToUTF16Ptr("Walk.WebView"), nil); win.FAILED(hr) { return nil, errorFromHRESULT("IOleObject.SetHostNames", hr) } if hr := win.OleSetContainedObject((*win.IUnknown)(unsafe.Pointer(browserObject)), true); win.FAILED(hr) { return nil, errorFromHRESULT("OleSetContainedObject", hr) } var rect win.RECT win.GetClientRect(wv.hWnd, &rect) if hr := browserObject.DoVerb(win.OLEIVERB_SHOW, nil, (*win.IOleClientSite)(unsafe.Pointer(&wv.clientSite)), 0, wv.hWnd, &rect); win.FAILED(hr) { return nil, errorFromHRESULT("IOleObject.DoVerb", hr) } var cpcPtr unsafe.Pointer if hr := browserObject.QueryInterface(&win.IID_IConnectionPointContainer, &cpcPtr); win.FAILED(hr) { return nil, errorFromHRESULT("IOleObject.QueryInterface(IID_IConnectionPointContainer)", hr) } cpc := (*win.IConnectionPointContainer)(cpcPtr) defer cpc.Release() var cp *win.IConnectionPoint if hr := cpc.FindConnectionPoint(&win.DIID_DWebBrowserEvents2, &cp); win.FAILED(hr) { return nil, errorFromHRESULT("IConnectionPointContainer.FindConnectionPoint(DIID_DWebBrowserEvents2)", hr) } defer cp.Release() var cookie uint32 if hr := cp.Advise(unsafe.Pointer(&wv.clientSite.webBrowserEvents2), &cookie); win.FAILED(hr) { return nil, errorFromHRESULT("IConnectionPoint.Advise", hr) } wv.onResize() wv.MustRegisterProperty("URL", NewProperty( func() interface{} { url, _ := wv.URL() return url }, func(v interface{}) error { return wv.SetURL(assertStringOr(v, "")) }, wv.urlChangedPublisher.Event())) wv.MustRegisterProperty("ShortcutsEnabled", NewProperty( func() interface{} { return wv.ShortcutsEnabled() }, func(v interface{}) error { wv.SetShortcutsEnabled(v.(bool)) return nil }, wv.shortcutsEnabledChangedPublisher.Event())) wv.MustRegisterProperty("NativeContextMenuEnabled", NewProperty( func() interface{} { return wv.NativeContextMenuEnabled() }, func(v interface{}) error { wv.SetNativeContextMenuEnabled(v.(bool)) return nil }, wv.nativeContextMenuEnabledChangedPublisher.Event())) succeeded = true return wv, nil } func (wv *WebView) Dispose() { if wv.browserObject != nil { wv.browserObject.Close(win.OLECLOSE_NOSAVE) wv.browserObject.Release() wv.browserObject = nil win.OleUninitialize() } wv.WidgetBase.Dispose() } func (wv *WebView) URL() (url string, err error) { err = wv.withWebBrowser2(func(webBrowser2 *win.IWebBrowser2) error { var urlBstr *uint16 /*BSTR*/ if hr := webBrowser2.Get_LocationURL(&urlBstr); win.FAILED(hr) { return errorFromHRESULT("IWebBrowser2.Get_LocationURL", hr) } defer win.SysFreeString(urlBstr) url = win.BSTRToString(urlBstr) return nil }) return } func (wv *WebView) SetURL(url string) error { return wv.withWebBrowser2(func(webBrowser2 *win.IWebBrowser2) error { urlBstr := win.StringToVariantBSTR(url) flags := win.IntToVariantI4(0) targetFrameName := win.StringToVariantBSTR("_self") if hr := webBrowser2.Navigate2(urlBstr, flags, targetFrameName, nil, nil); win.FAILED(hr) { return errorFromHRESULT("IWebBrowser2.Navigate2", hr) } return nil }) } func (wv *WebView) URLChanged() *Event { return wv.urlChangedPublisher.Event() } func (wv *WebView) ShortcutsEnabled() bool { return wv.shortcutsEnabled } func (wv *WebView) SetShortcutsEnabled(value bool) { wv.shortcutsEnabled = value wv.shortcutsEnabledChangedPublisher.Publish() } func (wv *WebView) ShortcutsEnabledChanged() *Event { return wv.shortcutsEnabledChangedPublisher.Event() } func (wv *WebView) NativeContextMenuEnabled() bool { return wv.nativeContextMenuEnabled } func (wv *WebView) SetNativeContextMenuEnabled(value bool) { wv.nativeContextMenuEnabled = value wv.nativeContextMenuEnabledChangedPublisher.Publish() } func (wv *WebView) NativeContextMenuEnabledChanged() *Event { return wv.nativeContextMenuEnabledChangedPublisher.Event() } func (wv *WebView) Navigating() *WebViewNavigatingEvent { return wv.navigatingPublisher.Event() } func (wv *WebView) Navigated() *StringEvent { return wv.navigatedPublisher.Event() } func (wv *WebView) Downloading() *Event { return wv.downloadingPublisher.Event() } func (wv *WebView) Downloaded() *Event { return wv.downloadedPublisher.Event() } func (wv *WebView) DocumentCompleted() *StringEvent { return wv.documentCompletedPublisher.Event() } func (wv *WebView) NavigatedError() *WebViewNavigatedErrorEvent { return wv.navigatedErrorPublisher.Event() } func (wv *WebView) NewWindow() *WebViewNewWindowEvent { return wv.newWindowPublisher.Event() } func (wv *WebView) Quitting() *Event { return wv.quittingPublisher.Event() } func (wv *WebView) WindowClosing() *WebViewWindowClosingEvent { return wv.windowClosingPublisher.Event() } func (wv *WebView) StatusBarVisible() bool { return wv.statusBarVisible } func (wv *WebView) StatusBarVisibleChanged() *Event { return wv.statusBarVisibleChangedPublisher.Event() } func (wv *WebView) IsTheaterMode() bool { return wv.isTheaterMode } func (wv *WebView) TheaterModeChanged() *Event { return wv.theaterModeChangedPublisher.Event() } func (wv *WebView) ToolBarVisible() bool { return wv.toolBarVisible } func (wv *WebView) ToolBarVisibleChanged() *Event { return wv.toolBarVisibleChangedPublisher.Event() } func (wv *WebView) BrowserVisible() bool { return wv.browserVisible } func (wv *WebView) BrowserVisibleChanged() *Event { return wv.browserVisibleChangedPublisher.Event() } func (wv *WebView) ToolBarEnabled() bool { return wv.toolBarEnabled } func (wv *WebView) ToolBarEnabledChanged() *Event { return wv.toolBarEnabledChangedPublisher.Event() } func (wv *WebView) CanGoBack() bool { return wv.canGoBack } func (wv *WebView) CanGoBackChanged() *Event { return wv.canGoBackChangedPublisher.Event() } func (wv *WebView) CanGoForward() bool { return wv.canGoForward } func (wv *WebView) CanGoForwardChanged() *Event { return wv.canGoForwardChangedPublisher.Event() } func (wv *WebView) ProgressValue() int32 { return wv.progressValue } func (wv *WebView) ProgressMax() int32 { return wv.progressMax } func (wv *WebView) ProgressChanged() *Event { return wv.progressChangedPublisher.Event() } func (wv *WebView) StatusText() string { return wv.statusText } func (wv *WebView) StatusTextChanged() *Event { return wv.statusTextChangedPublisher.Event() } func (wv *WebView) DocumentTitle() string { return wv.documentTitle } func (wv *WebView) DocumentTitleChanged() *Event { return wv.documentTitleChangedPublisher.Event() } func (wv *WebView) Refresh() error { return wv.withWebBrowser2(func(webBrowser2 *win.IWebBrowser2) error { if hr := webBrowser2.Refresh(); win.FAILED(hr) { return errorFromHRESULT("IWebBrowser2.Refresh", hr) } return nil }) } func (wv *WebView) withWebBrowser2(f func(webBrowser2 *win.IWebBrowser2) error) error { var webBrowser2Ptr unsafe.Pointer if hr := wv.browserObject.QueryInterface(&win.IID_IWebBrowser2, &webBrowser2Ptr); win.FAILED(hr) { return errorFromHRESULT("IOleObject.QueryInterface", hr) } webBrowser2 := (*win.IWebBrowser2)(webBrowser2Ptr) defer webBrowser2.Release() return f(webBrowser2) } func (wv *WebView) onResize() { // FIXME: handle error? wv.withWebBrowser2(func(webBrowser2 *win.IWebBrowser2) error { bounds := wv.ClientBoundsPixels() webBrowser2.Put_Left(0) webBrowser2.Put_Top(0) webBrowser2.Put_Width(int32(bounds.Width)) webBrowser2.Put_Height(int32(bounds.Height)) return nil }) } func (wv *WebView) withInPlaceActiveObject(f func(activeObject *win.IOleInPlaceActiveObject) error) error { if wv.browserObject == nil { return nil } wv.withWebBrowser2(func(webBrowser2 *win.IWebBrowser2) error { var activeObjectPtr unsafe.Pointer if hr := webBrowser2.QueryInterface(&win.IID_IOleInPlaceActiveObject, &activeObjectPtr); win.FAILED(hr) { return errorFromHRESULT("WebBowser2.QueryInterface", hr) } activeObject := (*win.IOleInPlaceActiveObject)(activeObjectPtr) defer activeObject.Release() return f(activeObject) }) return nil } func (wv *WebView) translateAccelerator(msg *win.MSG) bool { if wv.shortcutsEnabled { hr := wv.inPlaceActiveObjectTranslateAccelerator(msg) return hr == win.S_OK } return false } func (wv *WebView) inPlaceActiveObjectTranslateAccelerator(msg *win.MSG) win.HRESULT { var ret win.HRESULT ret = win.S_FALSE wv.withInPlaceActiveObject(func(activeObject *win.IOleInPlaceActiveObject) error { hr := activeObject.TranslateAccelerator(msg) if hr == win.S_OK { ret = win.S_OK } return nil }) return ret } func (wv *WebView) inPlaceActiveObjectSetFocus() win.HRESULT { var ret win.HRESULT ret = win.S_FALSE wv.withInPlaceActiveObject(func(activeObject *win.IOleInPlaceActiveObject) error { var hWndActive win.HWND hr := activeObject.GetWindow(&hWndActive) if hr != win.S_OK { return nil } win.SetFocus(hWndActive) ret = win.S_OK return nil }) return ret } func (wv *WebView) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_WINDOWPOSCHANGED: wp := (*win.WINDOWPOS)(unsafe.Pointer(lParam)) if wp.Flags&win.SWP_NOSIZE != 0 { break } if wv.clientSite.inPlaceSite.inPlaceFrame.webView == nil { break } wv.onResize() case win.WM_MOUSEACTIVATE: wv.invalidateBorderInParent() } return wv.WidgetBase.WndProc(hwnd, msg, wParam, lParam) } func (wv *WebView) CreateLayoutItem(ctx *LayoutContext) LayoutItem { return NewGreedyLayoutItem() } ================================================ FILE: webview_dwebbrowserevents2.go ================================================ // 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 ( "time" "github.com/lxn/win" ) var webViewDWebBrowserEvents2Vtbl *win.DWebBrowserEvents2Vtbl func init() { AppendToWalkInit(func() { webViewDWebBrowserEvents2Vtbl = &win.DWebBrowserEvents2Vtbl{ syscall.NewCallback(webView_DWebBrowserEvents2_QueryInterface), syscall.NewCallback(webView_DWebBrowserEvents2_AddRef), syscall.NewCallback(webView_DWebBrowserEvents2_Release), syscall.NewCallback(webView_DWebBrowserEvents2_GetTypeInfoCount), syscall.NewCallback(webView_DWebBrowserEvents2_GetTypeInfo), syscall.NewCallback(webView_DWebBrowserEvents2_GetIDsOfNames), syscall.NewCallback(webView_DWebBrowserEvents2_Invoke), } }) } type webViewDWebBrowserEvents2 struct { win.DWebBrowserEvents2 } func webView_DWebBrowserEvents2_QueryInterface(wbe2 *webViewDWebBrowserEvents2, riid win.REFIID, ppvObject *unsafe.Pointer) uintptr { // Just reuse the QueryInterface implementation we have for IOleClientSite. // We need to adjust object, which initially points at our // webViewDWebBrowserEvents2, so it refers to the containing // webViewIOleClientSite for the call. var clientSite win.IOleClientSite var webViewInPlaceSite webViewIOleInPlaceSite var docHostUIHandler webViewIDocHostUIHandler ptr := uintptr(unsafe.Pointer(wbe2)) - uintptr(unsafe.Sizeof(clientSite)) - uintptr(unsafe.Sizeof(webViewInPlaceSite)) - uintptr(unsafe.Sizeof(docHostUIHandler)) return webView_IOleClientSite_QueryInterface((*webViewIOleClientSite)(unsafe.Pointer(ptr)), riid, ppvObject) } func webView_DWebBrowserEvents2_AddRef(args *uintptr) uintptr { return 1 } func webView_DWebBrowserEvents2_Release(args *uintptr) uintptr { return 1 } func webView_DWebBrowserEvents2_GetTypeInfoCount(args *uintptr) uintptr { /* p := (*struct { wbe2 *webViewDWebBrowserEvents2 pctinfo *uint })(unsafe.Pointer(args)) *p.pctinfo = 0 return S_OK*/ return win.E_NOTIMPL } func webView_DWebBrowserEvents2_GetTypeInfo(args *uintptr) uintptr { /* p := (*struct { wbe2 *webViewDWebBrowserEvents2 })(unsafe.Pointer(args)) unsigned int iTInfo, LCID lcid, ITypeInfo FAR* FAR* ppTInfo*/ return win.E_NOTIMPL } func webView_DWebBrowserEvents2_GetIDsOfNames(args *uintptr) uintptr { /* p := (*struct { wbe2 *webViewDWebBrowserEvents2 riid REFIID rgszNames **uint16 cNames uint32 lcid LCID rgDispId *DISPID })(unsafe.Pointer(args))*/ return win.E_NOTIMPL } /* func webView_DWebBrowserEvents2_Invoke( wbe2 *webViewDWebBrowserEvents2, dispIdMember win.DISPID, riid win.REFIID, lcid uint32, // LCID wFlags uint16, pDispParams *win.DISPPARAMS, pVarResult *win.VARIANT, pExcepInfo unsafe.Pointer, // *EXCEPINFO puArgErr *uint32) uintptr { */ func webView_DWebBrowserEvents2_Invoke( arg0 uintptr, arg1 uintptr, arg2 uintptr, arg3 uintptr, arg4 uintptr, arg5 uintptr, arg6 uintptr, arg7 uintptr, arg8 uintptr) uintptr { wbe2 := (*webViewDWebBrowserEvents2)(unsafe.Pointer(arg0)) dispIdMember := *(*win.DISPID)(unsafe.Pointer(&arg1)) //riid := *(*win.REFIID)(unsafe.Pointer(&arg2)) //lcid := *(*uint32)(unsafe.Pointer(&arg3)) //wFlags := *(*uint16)(unsafe.Pointer(&arg4)) pDispParams := (*win.DISPPARAMS)(unsafe.Pointer(arg5)) //pVarResult := (*win.VARIANT)(unsafe.Pointer(arg6)) //pExcepInfo := unsafe.Pointer(arg7) //puArgErr := (*uint32)(unsafe.Pointer(arg8)) var wb WidgetBase var wvcs webViewIOleClientSite wv := (*WebView)(unsafe.Pointer(uintptr(unsafe.Pointer(wbe2)) + uintptr(unsafe.Sizeof(*wbe2)) - uintptr(unsafe.Sizeof(wvcs)) - uintptr(unsafe.Sizeof(wb)))) switch dispIdMember { case win.DISPID_BEFORENAVIGATE2: rgvargPtr := (*[7]win.VARIANTARG)(unsafe.Pointer(pDispParams.Rgvarg)) eventData := &WebViewNavigatingEventData{ pDisp: (*rgvargPtr)[6].MustPDispatch(), url: (*rgvargPtr)[5].MustPVariant(), flags: (*rgvargPtr)[4].MustPVariant(), targetFrameName: (*rgvargPtr)[3].MustPVariant(), postData: (*rgvargPtr)[2].MustPVariant(), headers: (*rgvargPtr)[1].MustPVariant(), cancel: (*rgvargPtr)[0].MustPBool(), } wv.navigatingPublisher.Publish(eventData) case win.DISPID_NAVIGATECOMPLETE2: rgvargPtr := (*[2]win.VARIANTARG)(unsafe.Pointer(pDispParams.Rgvarg)) url := (*rgvargPtr)[0].MustPVariant() urlStr := "" if url != nil && url.MustBSTR() != nil { urlStr = win.BSTRToString(url.MustBSTR()) } wv.navigatedPublisher.Publish(urlStr) wv.urlChangedPublisher.Publish() case win.DISPID_DOWNLOADBEGIN: wv.downloadingPublisher.Publish() case win.DISPID_DOWNLOADCOMPLETE: wv.downloadedPublisher.Publish() case win.DISPID_DOCUMENTCOMPLETE: rgvargPtr := (*[2]win.VARIANTARG)(unsafe.Pointer(pDispParams.Rgvarg)) url := (*rgvargPtr)[0].MustPVariant() urlStr := "" if url != nil && url.MustBSTR() != nil { urlStr = win.BSTRToString(url.MustBSTR()) } // FIXME: Horrible hack to avoid glitch where the document is not displayed. time.AfterFunc(time.Millisecond*100, func() { wv.Synchronize(func() { b := wv.BoundsPixels() b.Width++ wv.SetBoundsPixels(b) b.Width-- wv.SetBoundsPixels(b) }) }) wv.documentCompletedPublisher.Publish(urlStr) case win.DISPID_NAVIGATEERROR: rgvargPtr := (*[5]win.VARIANTARG)(unsafe.Pointer(pDispParams.Rgvarg)) eventData := &WebViewNavigatedErrorEventData{ pDisp: (*rgvargPtr)[4].MustPDispatch(), url: (*rgvargPtr)[3].MustPVariant(), targetFrameName: (*rgvargPtr)[2].MustPVariant(), statusCode: (*rgvargPtr)[1].MustPVariant(), cancel: (*rgvargPtr)[0].MustPBool(), } wv.navigatedErrorPublisher.Publish(eventData) case win.DISPID_NEWWINDOW3: rgvargPtr := (*[5]win.VARIANTARG)(unsafe.Pointer(pDispParams.Rgvarg)) eventData := &WebViewNewWindowEventData{ ppDisp: (*rgvargPtr)[4].MustPPDispatch(), cancel: (*rgvargPtr)[3].MustPBool(), dwFlags: (*rgvargPtr)[2].MustULong(), bstrUrlContext: (*rgvargPtr)[1].MustBSTR(), bstrUrl: (*rgvargPtr)[0].MustBSTR(), } wv.newWindowPublisher.Publish(eventData) case win.DISPID_ONQUIT: wv.quittingPublisher.Publish() case win.DISPID_WINDOWCLOSING: rgvargPtr := (*[2]win.VARIANTARG)(unsafe.Pointer(pDispParams.Rgvarg)) eventData := &WebViewWindowClosingEventData{ bIsChildWindow: (*rgvargPtr)[1].MustBool(), cancel: (*rgvargPtr)[0].MustPBool(), } wv.windowClosingPublisher.Publish(eventData) case win.DISPID_ONSTATUSBAR: rgvargPtr := (*[1]win.VARIANTARG)(unsafe.Pointer(pDispParams.Rgvarg)) statusBar := (*rgvargPtr)[0].MustBool() if statusBar != win.VARIANT_FALSE { wv.statusBarVisible = true } else { wv.statusBarVisible = false } wv.statusBarVisibleChangedPublisher.Publish() case win.DISPID_ONTHEATERMODE: rgvargPtr := (*[1]win.VARIANTARG)(unsafe.Pointer(pDispParams.Rgvarg)) theaterMode := (*rgvargPtr)[0].MustBool() if theaterMode != win.VARIANT_FALSE { wv.isTheaterMode = true } else { wv.isTheaterMode = false } wv.theaterModeChangedPublisher.Publish() case win.DISPID_ONTOOLBAR: rgvargPtr := (*[1]win.VARIANTARG)(unsafe.Pointer(pDispParams.Rgvarg)) toolBar := (*rgvargPtr)[0].MustBool() if toolBar != win.VARIANT_FALSE { wv.toolBarVisible = true } else { wv.toolBarVisible = false } wv.toolBarVisibleChangedPublisher.Publish() case win.DISPID_ONVISIBLE: rgvargPtr := (*[1]win.VARIANTARG)(unsafe.Pointer(pDispParams.Rgvarg)) vVisible := (*rgvargPtr)[0].MustBool() if vVisible != win.VARIANT_FALSE { wv.browserVisible = true } else { wv.browserVisible = false } wv.browserVisibleChangedPublisher.Publish() case win.DISPID_COMMANDSTATECHANGE: rgvargPtr := (*[2]win.VARIANTARG)(unsafe.Pointer(pDispParams.Rgvarg)) command := (*rgvargPtr)[1].MustLong() enable := (*rgvargPtr)[0].MustBool() enableBool := (enable != win.VARIANT_FALSE) switch command { case win.CSC_UPDATECOMMANDS: wv.toolBarEnabled = enableBool wv.toolBarEnabledChangedPublisher.Publish() case win.CSC_NAVIGATEFORWARD: wv.canGoForward = enableBool wv.canGoForwardChangedPublisher.Publish() case win.CSC_NAVIGATEBACK: wv.canGoBack = enableBool wv.canGoBackChangedPublisher.Publish() } case win.DISPID_PROGRESSCHANGE: rgvargPtr := (*[2]win.VARIANTARG)(unsafe.Pointer(pDispParams.Rgvarg)) wv.progressValue = (*rgvargPtr)[1].MustLong() wv.progressMax = (*rgvargPtr)[0].MustLong() wv.progressChangedPublisher.Publish() case win.DISPID_STATUSTEXTCHANGE: rgvargPtr := (*[1]win.VARIANTARG)(unsafe.Pointer(pDispParams.Rgvarg)) sText := (*rgvargPtr)[0].MustBSTR() if sText != nil { wv.statusText = win.BSTRToString(sText) } else { wv.statusText = "" } wv.statusTextChangedPublisher.Publish() case win.DISPID_TITLECHANGE: rgvargPtr := (*[1]win.VARIANTARG)(unsafe.Pointer(pDispParams.Rgvarg)) sText := (*rgvargPtr)[0].MustBSTR() if sText != nil { wv.documentTitle = win.BSTRToString(sText) } else { wv.documentTitle = "" } wv.documentTitleChangedPublisher.Publish() } return win.DISP_E_MEMBERNOTFOUND } ================================================ FILE: webview_events.go ================================================ // Copyright 2011 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 ( "unsafe" ) import ( "github.com/lxn/win" ) type WebViewNavigatingEventData struct { pDisp *win.IDispatch url *win.VARIANT flags *win.VARIANT targetFrameName *win.VARIANT postData *win.VARIANT headers *win.VARIANT cancel *win.VARIANT_BOOL } func (eventData *WebViewNavigatingEventData) Url() string { url := eventData.url if url != nil && url.MustBSTR() != nil { return win.BSTRToString(url.MustBSTR()) } return "" } func (eventData *WebViewNavigatingEventData) Flags() int32 { flags := eventData.flags if flags != nil { return flags.MustLong() } return 0 } func (eventData *WebViewNavigatingEventData) PostData() string { postData := eventData.postData if postData != nil { pvar := postData.MustPVariant() if pvar != nil && pvar.Vt == win.VT_ARRAY|win.VT_UI1 { psa := pvar.MustPSafeArray() if psa != nil && psa.CDims == 1 && psa.CbElements == 1 { postDataSize := psa.Rgsabound[0].CElements * psa.CbElements byteAryPtr := (*[200000000]byte)(unsafe.Pointer(psa.PvData)) byteArySlice := (*byteAryPtr)[0 : postDataSize-1] return string(byteArySlice) } } } return "" } func (eventData *WebViewNavigatingEventData) Headers() string { headers := eventData.headers if headers != nil && headers.MustBSTR() != nil { return win.BSTRToString(headers.MustBSTR()) } return "" } func (eventData *WebViewNavigatingEventData) TargetFrameName() string { targetFrameName := eventData.targetFrameName if targetFrameName != nil && targetFrameName.MustBSTR() != nil { return win.BSTRToString(targetFrameName.MustBSTR()) } return "" } func (eventData *WebViewNavigatingEventData) Canceled() bool { cancel := eventData.cancel if cancel != nil { if *cancel != win.VARIANT_FALSE { return true } else { return false } } return false } func (eventData *WebViewNavigatingEventData) SetCanceled(value bool) { cancel := eventData.cancel if cancel != nil { if value { *cancel = win.VARIANT_TRUE } else { *cancel = win.VARIANT_FALSE } } } type WebViewNavigatingEventHandler func(eventData *WebViewNavigatingEventData) type WebViewNavigatingEvent struct { handlers []WebViewNavigatingEventHandler } func (e *WebViewNavigatingEvent) Attach(handler WebViewNavigatingEventHandler) int { for i, h := range e.handlers { if h == nil { e.handlers[i] = handler return i } } e.handlers = append(e.handlers, handler) return len(e.handlers) - 1 } func (e *WebViewNavigatingEvent) Detach(handle int) { e.handlers[handle] = nil } type WebViewNavigatingEventPublisher struct { event WebViewNavigatingEvent } func (p *WebViewNavigatingEventPublisher) Event() *WebViewNavigatingEvent { return &p.event } func (p *WebViewNavigatingEventPublisher) Publish(eventData *WebViewNavigatingEventData) { for _, handler := range p.event.handlers { if handler != nil { handler(eventData) } } } type WebViewNavigatedErrorEventData struct { pDisp *win.IDispatch url *win.VARIANT targetFrameName *win.VARIANT statusCode *win.VARIANT cancel *win.VARIANT_BOOL } func (eventData *WebViewNavigatedErrorEventData) Url() string { url := eventData.url if url != nil && url.MustBSTR() != nil { return win.BSTRToString(url.MustBSTR()) } return "" } func (eventData *WebViewNavigatedErrorEventData) TargetFrameName() string { targetFrameName := eventData.targetFrameName if targetFrameName != nil && targetFrameName.MustBSTR() != nil { return win.BSTRToString(targetFrameName.MustBSTR()) } return "" } func (eventData *WebViewNavigatedErrorEventData) StatusCode() int32 { statusCode := eventData.statusCode if statusCode != nil { return statusCode.MustLong() } return 0 } func (eventData *WebViewNavigatedErrorEventData) Canceled() bool { cancel := eventData.cancel if cancel != nil { if *cancel != win.VARIANT_FALSE { return true } else { return false } } return false } func (eventData *WebViewNavigatedErrorEventData) SetCanceled(value bool) { cancel := eventData.cancel if cancel != nil { if value { *cancel = win.VARIANT_TRUE } else { *cancel = win.VARIANT_FALSE } } } type WebViewNavigatedErrorEventHandler func(eventData *WebViewNavigatedErrorEventData) type WebViewNavigatedErrorEvent struct { handlers []WebViewNavigatedErrorEventHandler } func (e *WebViewNavigatedErrorEvent) Attach(handler WebViewNavigatedErrorEventHandler) int { for i, h := range e.handlers { if h == nil { e.handlers[i] = handler return i } } e.handlers = append(e.handlers, handler) return len(e.handlers) - 1 } func (e *WebViewNavigatedErrorEvent) Detach(handle int) { e.handlers[handle] = nil } type WebViewNavigatedErrorEventPublisher struct { event WebViewNavigatedErrorEvent } func (p *WebViewNavigatedErrorEventPublisher) Event() *WebViewNavigatedErrorEvent { return &p.event } func (p *WebViewNavigatedErrorEventPublisher) Publish(eventData *WebViewNavigatedErrorEventData) { for _, handler := range p.event.handlers { if handler != nil { handler(eventData) } } } type WebViewNewWindowEventData struct { ppDisp **win.IDispatch cancel *win.VARIANT_BOOL dwFlags uint32 bstrUrlContext *uint16 bstrUrl *uint16 } func (eventData *WebViewNewWindowEventData) Canceled() bool { cancel := eventData.cancel if cancel != nil { if *cancel != win.VARIANT_FALSE { return true } else { return false } } return false } func (eventData *WebViewNewWindowEventData) SetCanceled(value bool) { cancel := eventData.cancel if cancel != nil { if value { *cancel = win.VARIANT_TRUE } else { *cancel = win.VARIANT_FALSE } } } func (eventData *WebViewNewWindowEventData) Flags() uint32 { return eventData.dwFlags } func (eventData *WebViewNewWindowEventData) UrlContext() string { bstrUrlContext := eventData.bstrUrlContext if bstrUrlContext != nil { return win.BSTRToString(bstrUrlContext) } return "" } func (eventData *WebViewNewWindowEventData) Url() string { bstrUrl := eventData.bstrUrl if bstrUrl != nil { return win.BSTRToString(bstrUrl) } return "" } type WebViewNewWindowEventHandler func(eventData *WebViewNewWindowEventData) type WebViewNewWindowEvent struct { handlers []WebViewNewWindowEventHandler } func (e *WebViewNewWindowEvent) Attach(handler WebViewNewWindowEventHandler) int { for i, h := range e.handlers { if h == nil { e.handlers[i] = handler return i } } e.handlers = append(e.handlers, handler) return len(e.handlers) - 1 } func (e *WebViewNewWindowEvent) Detach(handle int) { e.handlers[handle] = nil } type WebViewNewWindowEventPublisher struct { event WebViewNewWindowEvent } func (p *WebViewNewWindowEventPublisher) Event() *WebViewNewWindowEvent { return &p.event } func (p *WebViewNewWindowEventPublisher) Publish(eventData *WebViewNewWindowEventData) { for _, handler := range p.event.handlers { if handler != nil { handler(eventData) } } } type WebViewWindowClosingEventData struct { bIsChildWindow win.VARIANT_BOOL cancel *win.VARIANT_BOOL } func (eventData *WebViewWindowClosingEventData) IsChildWindow() bool { bIsChildWindow := eventData.bIsChildWindow if bIsChildWindow != win.VARIANT_FALSE { return true } else { return false } return false } func (eventData *WebViewWindowClosingEventData) Canceled() bool { cancel := eventData.cancel if cancel != nil { if *cancel != win.VARIANT_FALSE { return true } else { return false } } return false } func (eventData *WebViewWindowClosingEventData) SetCanceled(value bool) { cancel := eventData.cancel if cancel != nil { if value { *cancel = win.VARIANT_TRUE } else { *cancel = win.VARIANT_FALSE } } } type WebViewWindowClosingEventHandler func(eventData *WebViewWindowClosingEventData) type WebViewWindowClosingEvent struct { handlers []WebViewWindowClosingEventHandler } func (e *WebViewWindowClosingEvent) Attach(handler WebViewWindowClosingEventHandler) int { for i, h := range e.handlers { if h == nil { e.handlers[i] = handler return i } } e.handlers = append(e.handlers, handler) return len(e.handlers) - 1 } func (e *WebViewWindowClosingEvent) Detach(handle int) { e.handlers[handle] = nil } type WebViewWindowClosingEventPublisher struct { event WebViewWindowClosingEvent } func (p *WebViewWindowClosingEventPublisher) Event() *WebViewWindowClosingEvent { return &p.event } func (p *WebViewWindowClosingEventPublisher) Publish(eventData *WebViewWindowClosingEventData) { for _, handler := range p.event.handlers { if handler != nil { handler(eventData) } } } ================================================ FILE: webview_idochostuihandler.go ================================================ // 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" ) var webViewIDocHostUIHandlerVtbl *win.IDocHostUIHandlerVtbl func init() { AppendToWalkInit(func() { webViewIDocHostUIHandlerVtbl = &win.IDocHostUIHandlerVtbl{ syscall.NewCallback(webView_IDocHostUIHandler_QueryInterface), syscall.NewCallback(webView_IDocHostUIHandler_AddRef), syscall.NewCallback(webView_IDocHostUIHandler_Release), syscall.NewCallback(webView_IDocHostUIHandler_ShowContextMenu), syscall.NewCallback(webView_IDocHostUIHandler_GetHostInfo), syscall.NewCallback(webView_IDocHostUIHandler_ShowUI), syscall.NewCallback(webView_IDocHostUIHandler_HideUI), syscall.NewCallback(webView_IDocHostUIHandler_UpdateUI), syscall.NewCallback(webView_IDocHostUIHandler_EnableModeless), syscall.NewCallback(webView_IDocHostUIHandler_OnDocWindowActivate), syscall.NewCallback(webView_IDocHostUIHandler_OnFrameWindowActivate), syscall.NewCallback(webView_IDocHostUIHandler_ResizeBorder), syscall.NewCallback(webView_IDocHostUIHandler_TranslateAccelerator), syscall.NewCallback(webView_IDocHostUIHandler_GetOptionKeyPath), syscall.NewCallback(webView_IDocHostUIHandler_GetDropTarget), syscall.NewCallback(webView_IDocHostUIHandler_GetExternal), syscall.NewCallback(webView_IDocHostUIHandler_TranslateUrl), syscall.NewCallback(webView_IDocHostUIHandler_FilterDataObject), } }) } type webViewIDocHostUIHandler struct { win.IDocHostUIHandler } func webView_IDocHostUIHandler_QueryInterface(docHostUIHandler *webViewIDocHostUIHandler, riid win.REFIID, ppvObject *unsafe.Pointer) uintptr { // Just reuse the QueryInterface implementation we have for IOleClientSite. // We need to adjust object, which initially points at our // webViewIDocHostUIHandler, so it refers to the containing // webViewIOleClientSite for the call. var clientSite win.IOleClientSite var webViewInPlaceSite webViewIOleInPlaceSite ptr := uintptr(unsafe.Pointer(docHostUIHandler)) - uintptr(unsafe.Sizeof(clientSite)) - uintptr(unsafe.Sizeof(webViewInPlaceSite)) return webView_IOleClientSite_QueryInterface((*webViewIOleClientSite)(unsafe.Pointer(ptr)), riid, ppvObject) } func webView_IDocHostUIHandler_AddRef(docHostUIHandler *webViewIDocHostUIHandler) uintptr { return 1 } func webView_IDocHostUIHandler_Release(docHostUIHandler *webViewIDocHostUIHandler) uintptr { return 1 } func webView_IDocHostUIHandler_ShowContextMenu(docHostUIHandler *webViewIDocHostUIHandler, dwID uint32, ppt *win.POINT, pcmdtReserved *win.IUnknown, pdispReserved uintptr) uintptr { var webViewInPlaceSite webViewIOleInPlaceSite var iOleClientSite win.IOleClientSite var wb WidgetBase ptr := uintptr(unsafe.Pointer(docHostUIHandler)) - uintptr(unsafe.Sizeof(webViewInPlaceSite)) - uintptr(unsafe.Sizeof(iOleClientSite)) - uintptr(unsafe.Sizeof(wb)) webView := (*WebView)(unsafe.Pointer(ptr)) // show context menu if webView.NativeContextMenuEnabled() { return win.S_FALSE } return win.S_OK } func webView_IDocHostUIHandler_GetHostInfo(docHostUIHandler *webViewIDocHostUIHandler, pInfo *win.DOCHOSTUIINFO) uintptr { pInfo.CbSize = uint32(unsafe.Sizeof(*pInfo)) pInfo.DwFlags = win.DOCHOSTUIFLAG_NO3DBORDER pInfo.DwDoubleClick = win.DOCHOSTUIDBLCLK_DEFAULT return win.S_OK } func webView_IDocHostUIHandler_ShowUI(docHostUIHandler *webViewIDocHostUIHandler, dwID uint32, pActiveObject uintptr, pCommandTarget uintptr, pFrame *win.IOleInPlaceFrame, pDoc uintptr) uintptr { return win.S_OK } func webView_IDocHostUIHandler_HideUI(docHostUIHandler *webViewIDocHostUIHandler) uintptr { return win.S_OK } func webView_IDocHostUIHandler_UpdateUI(docHostUIHandler *webViewIDocHostUIHandler) uintptr { return win.S_OK } func webView_IDocHostUIHandler_EnableModeless(docHostUIHandler *webViewIDocHostUIHandler, fEnable win.BOOL) uintptr { return win.S_OK } func webView_IDocHostUIHandler_OnDocWindowActivate(docHostUIHandler *webViewIDocHostUIHandler, fActivate win.BOOL) uintptr { return win.S_OK } func webView_IDocHostUIHandler_OnFrameWindowActivate(docHostUIHandler *webViewIDocHostUIHandler, fActivate win.BOOL) uintptr { return win.S_OK } func webView_IDocHostUIHandler_ResizeBorder(docHostUIHandler *webViewIDocHostUIHandler, prcBorder *win.RECT, pUIWindow uintptr, fRameWindow win.BOOL) uintptr { return win.S_OK } func webView_IDocHostUIHandler_TranslateAccelerator(docHostUIHandler *webViewIDocHostUIHandler, lpMsg *win.MSG, pguidCmdGroup *syscall.GUID, nCmdID uint) uintptr { return win.S_FALSE } func webView_IDocHostUIHandler_GetOptionKeyPath(docHostUIHandler *webViewIDocHostUIHandler, pchKey *uint16, dw uint) uintptr { return win.S_FALSE } func webView_IDocHostUIHandler_GetDropTarget(docHostUIHandler *webViewIDocHostUIHandler, pDropTarget uintptr, ppDropTarget *uintptr) uintptr { return win.S_FALSE } func webView_IDocHostUIHandler_GetExternal(docHostUIHandler *webViewIDocHostUIHandler, ppDispatch *uintptr) uintptr { *ppDispatch = 0 return win.S_FALSE } func webView_IDocHostUIHandler_TranslateUrl(docHostUIHandler *webViewIDocHostUIHandler, dwTranslate uint32, pchURLIn *uint16, ppchURLOut **uint16) uintptr { *ppchURLOut = nil return win.S_FALSE } func webView_IDocHostUIHandler_FilterDataObject(docHostUIHandler *webViewIDocHostUIHandler, pDO uintptr, ppDORet *uintptr) uintptr { *ppDORet = 0 return win.S_FALSE } ================================================ FILE: webview_ioleclientsite.go ================================================ // 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" ) var webViewIOleClientSiteVtbl *win.IOleClientSiteVtbl func init() { AppendToWalkInit(func() { webViewIOleClientSiteVtbl = &win.IOleClientSiteVtbl{ syscall.NewCallback(webView_IOleClientSite_QueryInterface), syscall.NewCallback(webView_IOleClientSite_AddRef), syscall.NewCallback(webView_IOleClientSite_Release), syscall.NewCallback(webView_IOleClientSite_SaveObject), syscall.NewCallback(webView_IOleClientSite_GetMoniker), syscall.NewCallback(webView_IOleClientSite_GetContainer), syscall.NewCallback(webView_IOleClientSite_ShowObject), syscall.NewCallback(webView_IOleClientSite_OnShowWindow), syscall.NewCallback(webView_IOleClientSite_RequestNewObjectLayout), } }) } type webViewIOleClientSite struct { win.IOleClientSite inPlaceSite webViewIOleInPlaceSite docHostUIHandler webViewIDocHostUIHandler webBrowserEvents2 webViewDWebBrowserEvents2 } func webView_IOleClientSite_QueryInterface(clientSite *webViewIOleClientSite, riid win.REFIID, ppvObject *unsafe.Pointer) uintptr { if win.EqualREFIID(riid, &win.IID_IUnknown) { *ppvObject = unsafe.Pointer(clientSite) } else if win.EqualREFIID(riid, &win.IID_IOleClientSite) { *ppvObject = unsafe.Pointer(clientSite) } else if win.EqualREFIID(riid, &win.IID_IOleInPlaceSite) { *ppvObject = unsafe.Pointer(&clientSite.inPlaceSite) } else if win.EqualREFIID(riid, &win.IID_IDocHostUIHandler) { *ppvObject = unsafe.Pointer(&clientSite.docHostUIHandler) } else if win.EqualREFIID(riid, &win.DIID_DWebBrowserEvents2) { *ppvObject = unsafe.Pointer(&clientSite.webBrowserEvents2) } else { *ppvObject = nil return win.E_NOINTERFACE } return win.S_OK } func webView_IOleClientSite_AddRef(clientSite *webViewIOleClientSite) uintptr { return 1 } func webView_IOleClientSite_Release(clientSite *webViewIOleClientSite) uintptr { return 1 } func webView_IOleClientSite_SaveObject(clientSite *webViewIOleClientSite) uintptr { return win.E_NOTIMPL } func webView_IOleClientSite_GetMoniker(clientSite *webViewIOleClientSite, dwAssign, dwWhichMoniker uint32, ppmk *unsafe.Pointer) uintptr { return win.E_NOTIMPL } func webView_IOleClientSite_GetContainer(clientSite *webViewIOleClientSite, ppContainer *unsafe.Pointer) uintptr { *ppContainer = nil return win.E_NOINTERFACE } func webView_IOleClientSite_ShowObject(clientSite *webViewIOleClientSite) uintptr { return win.S_OK } func webView_IOleClientSite_OnShowWindow(clientSite *webViewIOleClientSite, fShow win.BOOL) uintptr { return win.E_NOTIMPL } func webView_IOleClientSite_RequestNewObjectLayout(clientSite *webViewIOleClientSite) uintptr { return win.E_NOTIMPL } ================================================ FILE: webview_ioleinplaceframe.go ================================================ // 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" ) import ( "github.com/lxn/win" ) var webViewIOleInPlaceFrameVtbl *win.IOleInPlaceFrameVtbl func init() { AppendToWalkInit(func() { webViewIOleInPlaceFrameVtbl = &win.IOleInPlaceFrameVtbl{ syscall.NewCallback(webView_IOleInPlaceFrame_QueryInterface), syscall.NewCallback(webView_IOleInPlaceFrame_AddRef), syscall.NewCallback(webView_IOleInPlaceFrame_Release), syscall.NewCallback(webView_IOleInPlaceFrame_GetWindow), syscall.NewCallback(webView_IOleInPlaceFrame_ContextSensitiveHelp), syscall.NewCallback(webView_IOleInPlaceFrame_GetBorder), syscall.NewCallback(webView_IOleInPlaceFrame_RequestBorderSpace), syscall.NewCallback(webView_IOleInPlaceFrame_SetBorderSpace), syscall.NewCallback(webView_IOleInPlaceFrame_SetActiveObject), syscall.NewCallback(webView_IOleInPlaceFrame_InsertMenus), syscall.NewCallback(webView_IOleInPlaceFrame_SetMenu), syscall.NewCallback(webView_IOleInPlaceFrame_RemoveMenus), syscall.NewCallback(webView_IOleInPlaceFrame_SetStatusText), syscall.NewCallback(webView_IOleInPlaceFrame_EnableModeless), syscall.NewCallback(webView_IOleInPlaceFrame_TranslateAccelerator), } }) } type webViewIOleInPlaceFrame struct { win.IOleInPlaceFrame webView *WebView } func webView_IOleInPlaceFrame_QueryInterface(inPlaceFrame *webViewIOleInPlaceFrame, riid win.REFIID, ppvObj *uintptr) uintptr { return win.E_NOTIMPL } func webView_IOleInPlaceFrame_AddRef(inPlaceFrame *webViewIOleInPlaceFrame) uintptr { return 1 } func webView_IOleInPlaceFrame_Release(inPlaceFrame *webViewIOleInPlaceFrame) uintptr { return 1 } func webView_IOleInPlaceFrame_GetWindow(inPlaceFrame *webViewIOleInPlaceFrame, lphwnd *win.HWND) uintptr { *lphwnd = inPlaceFrame.webView.hWnd return win.S_OK } func webView_IOleInPlaceFrame_ContextSensitiveHelp(inPlaceFrame *webViewIOleInPlaceFrame, fEnterMode win.BOOL) uintptr { return win.E_NOTIMPL } func webView_IOleInPlaceFrame_GetBorder(inPlaceFrame *webViewIOleInPlaceFrame, lprectBorder *win.RECT) uintptr { return win.E_NOTIMPL } func webView_IOleInPlaceFrame_RequestBorderSpace(inPlaceFrame *webViewIOleInPlaceFrame, pborderwidths uintptr) uintptr { return win.E_NOTIMPL } func webView_IOleInPlaceFrame_SetBorderSpace(inPlaceFrame *webViewIOleInPlaceFrame, pborderwidths uintptr) uintptr { return win.E_NOTIMPL } func webView_IOleInPlaceFrame_SetActiveObject(inPlaceFrame *webViewIOleInPlaceFrame, pActiveObject uintptr, pszObjName *uint16) uintptr { return win.S_OK } func webView_IOleInPlaceFrame_InsertMenus(inPlaceFrame *webViewIOleInPlaceFrame, hmenuShared win.HMENU, lpMenuWidths uintptr) uintptr { return win.E_NOTIMPL } func webView_IOleInPlaceFrame_SetMenu(inPlaceFrame *webViewIOleInPlaceFrame, hmenuShared win.HMENU, holemenu win.HMENU, hwndActiveObject win.HWND) uintptr { return win.S_OK } func webView_IOleInPlaceFrame_RemoveMenus(inPlaceFrame *webViewIOleInPlaceFrame, hmenuShared win.HMENU) uintptr { return win.E_NOTIMPL } func webView_IOleInPlaceFrame_SetStatusText(inPlaceFrame *webViewIOleInPlaceFrame, pszStatusText *uint16) uintptr { return win.S_OK } func webView_IOleInPlaceFrame_EnableModeless(inPlaceFrame *webViewIOleInPlaceFrame, fEnable win.BOOL) uintptr { return win.S_OK } func webView_IOleInPlaceFrame_TranslateAccelerator(inPlaceFrame *webViewIOleInPlaceFrame, lpmsg *win.MSG, wID uint32) uintptr { return win.E_NOTIMPL } ================================================ FILE: webview_ioleinplacesite.go ================================================ // 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" ) var webViewIOleInPlaceSiteVtbl *win.IOleInPlaceSiteVtbl func init() { AppendToWalkInit(func() { webViewIOleInPlaceSiteVtbl = &win.IOleInPlaceSiteVtbl{ syscall.NewCallback(webView_IOleInPlaceSite_QueryInterface), syscall.NewCallback(webView_IOleInPlaceSite_AddRef), syscall.NewCallback(webView_IOleInPlaceSite_Release), syscall.NewCallback(webView_IOleInPlaceSite_GetWindow), syscall.NewCallback(webView_IOleInPlaceSite_ContextSensitiveHelp), syscall.NewCallback(webView_IOleInPlaceSite_CanInPlaceActivate), syscall.NewCallback(webView_IOleInPlaceSite_OnInPlaceActivate), syscall.NewCallback(webView_IOleInPlaceSite_OnUIActivate), syscall.NewCallback(webView_IOleInPlaceSite_GetWindowContext), syscall.NewCallback(webView_IOleInPlaceSite_Scroll), syscall.NewCallback(webView_IOleInPlaceSite_OnUIDeactivate), syscall.NewCallback(webView_IOleInPlaceSite_OnInPlaceDeactivate), syscall.NewCallback(webView_IOleInPlaceSite_DiscardUndoState), syscall.NewCallback(webView_IOleInPlaceSite_DeactivateAndUndo), syscall.NewCallback(webView_IOleInPlaceSite_OnPosRectChange), } }) } type webViewIOleInPlaceSite struct { win.IOleInPlaceSite inPlaceFrame webViewIOleInPlaceFrame } func webView_IOleInPlaceSite_QueryInterface(inPlaceSite *webViewIOleInPlaceSite, riid win.REFIID, ppvObject *unsafe.Pointer) uintptr { // Just reuse the QueryInterface implementation we have for IOleClientSite. // We need to adjust object from the webViewIDocHostUIHandler to the // containing webViewIOleInPlaceSite. var clientSite win.IOleClientSite ptr := uintptr(unsafe.Pointer(inPlaceSite)) - uintptr(unsafe.Sizeof(clientSite)) return webView_IOleClientSite_QueryInterface((*webViewIOleClientSite)(unsafe.Pointer(ptr)), riid, ppvObject) } func webView_IOleInPlaceSite_AddRef(inPlaceSite *webViewIOleInPlaceSite) uintptr { return 1 } func webView_IOleInPlaceSite_Release(inPlaceSite *webViewIOleInPlaceSite) uintptr { return 1 } func webView_IOleInPlaceSite_GetWindow(inPlaceSite *webViewIOleInPlaceSite, lphwnd *win.HWND) uintptr { *lphwnd = inPlaceSite.inPlaceFrame.webView.hWnd return win.S_OK } func webView_IOleInPlaceSite_ContextSensitiveHelp(inPlaceSite *webViewIOleInPlaceSite, fEnterMode win.BOOL) uintptr { return win.E_NOTIMPL } func webView_IOleInPlaceSite_CanInPlaceActivate(inPlaceSite *webViewIOleInPlaceSite) uintptr { return win.S_OK } func webView_IOleInPlaceSite_OnInPlaceActivate(inPlaceSite *webViewIOleInPlaceSite) uintptr { return win.S_OK } func webView_IOleInPlaceSite_OnUIActivate(inPlaceSite *webViewIOleInPlaceSite) uintptr { return win.S_OK } func webView_IOleInPlaceSite_GetWindowContext(inPlaceSite *webViewIOleInPlaceSite, lplpFrame **webViewIOleInPlaceFrame, lplpDoc *uintptr, lprcPosRect, lprcClipRect *win.RECT, lpFrameInfo *win.OLEINPLACEFRAMEINFO) uintptr { *lplpFrame = &inPlaceSite.inPlaceFrame *lplpDoc = 0 lpFrameInfo.FMDIApp = win.FALSE lpFrameInfo.HwndFrame = inPlaceSite.inPlaceFrame.webView.hWnd lpFrameInfo.Haccel = 0 lpFrameInfo.CAccelEntries = 0 return win.S_OK } func webView_IOleInPlaceSite_Scroll(inPlaceSite *webViewIOleInPlaceSite, scrollExtentX, scrollExtentY int32) uintptr { return win.E_NOTIMPL } func webView_IOleInPlaceSite_OnUIDeactivate(inPlaceSite *webViewIOleInPlaceSite, fUndoable win.BOOL) uintptr { return win.S_OK } func webView_IOleInPlaceSite_OnInPlaceDeactivate(inPlaceSite *webViewIOleInPlaceSite) uintptr { return win.S_OK } func webView_IOleInPlaceSite_DiscardUndoState(inPlaceSite *webViewIOleInPlaceSite) uintptr { return win.E_NOTIMPL } func webView_IOleInPlaceSite_DeactivateAndUndo(inPlaceSite *webViewIOleInPlaceSite) uintptr { return win.E_NOTIMPL } func webView_IOleInPlaceSite_OnPosRectChange(inPlaceSite *webViewIOleInPlaceSite, lprcPosRect *win.RECT) uintptr { browserObject := inPlaceSite.inPlaceFrame.webView.browserObject var inPlaceObjectPtr unsafe.Pointer if hr := browserObject.QueryInterface(&win.IID_IOleInPlaceObject, &inPlaceObjectPtr); win.FAILED(hr) { return uintptr(hr) } inPlaceObject := (*win.IOleInPlaceObject)(inPlaceObjectPtr) defer inPlaceObject.Release() return uintptr(inPlaceObject.SetObjectRects(lprcPosRect, lprcPosRect)) } ================================================ FILE: widget.go ================================================ // 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 ( "github.com/lxn/win" ) // LayoutFlags specify how a Widget wants to be treated when used with a Layout. // // These flags are interpreted in respect to Widget.SizeHint. type LayoutFlags byte const ( // ShrinkableHorz allows a Widget to be shrunk horizontally. ShrinkableHorz LayoutFlags = 1 << iota // ShrinkableVert allows a Widget to be shrunk vertically. ShrinkableVert // GrowableHorz allows a Widget to be enlarged horizontally. GrowableHorz // GrowableVert allows a Widget to be enlarged vertically. GrowableVert // GreedyHorz specifies that the widget prefers to take up as much space as // possible, horizontally. GreedyHorz // GreedyVert specifies that the widget prefers to take up as much space as // possible, vertically. GreedyVert ) type Widget interface { Window // Alignment returns the alignment of the Widget. Alignment() Alignment2D // AlwaysConsumeSpace returns if the Widget should consume space even if it // is not visible. AlwaysConsumeSpace() bool // AsWidgetBase returns a *WidgetBase that implements Widget. AsWidgetBase() *WidgetBase // CreateLayoutItem creates and returns a new LayoutItem specific to the // concrete Widget type, that carries all data and logic required to layout // the Widget. CreateLayoutItem(ctx *LayoutContext) LayoutItem // GraphicsEffects returns a list of WidgetGraphicsEffects that are applied to the Widget. GraphicsEffects() *WidgetGraphicsEffectList // LayoutFlags returns a combination of LayoutFlags that specify how the // Widget wants to be treated by Layout implementations. LayoutFlags() LayoutFlags // MinSizeHint returns the minimum outer size in native pixels, including decorations, that // makes sense for the respective type of Widget. MinSizeHint() Size // Parent returns the Container of the Widget. Parent() Container // SetAlignment sets the alignment of the widget. SetAlignment(alignment Alignment2D) error // SetAlwaysConsumeSpace sets if the Widget should consume space even if it // is not visible. SetAlwaysConsumeSpace(b bool) error // SetParent sets the parent of the Widget and adds the Widget to the // Children list of the Container. SetParent(value Container) error // SetToolTipText sets the tool tip text of the Widget. SetToolTipText(s string) error // SizeHint returns the preferred size in native pixels for the respective type of Widget. SizeHint() Size // ToolTipText returns the tool tip text of the Widget. ToolTipText() string } type WidgetBase struct { WindowBase geometry Geometry parent Container toolTipTextProperty Property toolTipTextChangedPublisher EventPublisher graphicsEffects *WidgetGraphicsEffectList alignment Alignment2D alwaysConsumeSpace bool } // InitWidget initializes a Widget. func InitWidget(widget Widget, parent Window, className string, style, exStyle uint32) error { if parent == nil { return newError("parent cannot be nil") } if err := InitWindow(widget, parent, className, style|win.WS_CHILD, exStyle); err != nil { return err } if container, ok := parent.(Container); ok { if container.Children() == nil { // Required by parents like MainWindow and GroupBox. if win.SetParent(widget.Handle(), container.Handle()) == 0 { return lastError("SetParent") } } else { if err := container.Children().Add(widget); err != nil { return err } } } return nil } func (wb *WidgetBase) init(widget Widget) error { wb.graphicsEffects = newWidgetGraphicsEffectList(wb) tt, err := wb.group.CreateToolTip() if err != nil { return err } if err := tt.AddTool(wb.window.(Widget)); err != nil { return err } wb.toolTipTextProperty = NewProperty( func() interface{} { return wb.window.(Widget).ToolTipText() }, func(v interface{}) error { wb.window.(Widget).SetToolTipText(assertStringOr(v, "")) return nil }, wb.toolTipTextChangedPublisher.Event()) wb.MustRegisterProperty("ToolTipText", wb.toolTipTextProperty) return nil } func (wb *WidgetBase) Dispose() { if wb.hWnd == 0 { return } if wb.parent != nil && win.GetParent(wb.hWnd) == wb.parent.Handle() { wb.SetParent(nil) } if tt := wb.group.ToolTip(); tt != nil { tt.RemoveTool(wb.window.(Widget)) } wb.WindowBase.Dispose() } // AsWidgetBase just returns the receiver. func (wb *WidgetBase) AsWidgetBase() *WidgetBase { return wb } // Bounds returns the outer bounding box rectangle of the WidgetBase, including // decorations. // // The coordinates are relative to the parent of the Widget. func (wb *WidgetBase) Bounds() Rectangle { return wb.RectangleTo96DPI(wb.BoundsPixels()) } // BoundsPixels returns the outer bounding box rectangle of the WidgetBase, including // decorations. // // The coordinates are relative to the parent of the Widget. func (wb *WidgetBase) BoundsPixels() Rectangle { b := wb.WindowBase.BoundsPixels() if wb.parent != nil { p := b.Location().toPOINT() if !win.ScreenToClient(wb.parent.Handle(), &p) { newError("ScreenToClient failed") return Rectangle{} } b.X = int(p.X) b.Y = int(p.Y) } return b } // BringToTop moves the WidgetBase to the top of the keyboard focus order. func (wb *WidgetBase) BringToTop() error { if wb.parent != nil { if err := wb.parent.BringToTop(); err != nil { return err } } return wb.WindowBase.BringToTop() } // Enabled returns if the WidgetBase is enabled for user interaction. func (wb *WidgetBase) Enabled() bool { if wb.parent != nil { return wb.enabled && wb.parent.Enabled() } return wb.enabled } // Font returns the Font of the WidgetBase. // // By default this is a MS Shell Dlg 2, 8 point font. func (wb *WidgetBase) Font() *Font { if wb.font != nil { return wb.font } else if wb.parent != nil { return wb.parent.Font() } return defaultFont } func (wb *WidgetBase) applyFont(font *Font) { wb.WindowBase.applyFont(font) wb.RequestLayout() } // Alignment return the alignment ot the *WidgetBase. func (wb *WidgetBase) Alignment() Alignment2D { return wb.alignment } // SetAlignment sets the alignment of the *WidgetBase. func (wb *WidgetBase) SetAlignment(alignment Alignment2D) error { if alignment != wb.alignment { if alignment < AlignHVDefault || alignment > AlignHFarVFar { return newError("invalid Alignment value") } wb.alignment = alignment wb.RequestLayout() } return nil } // SetMinMaxSize sets the minimum and maximum outer size of the *WidgetBase, // including decorations. // // Use walk.Size{} to make the respective limit be ignored. func (wb *WidgetBase) SetMinMaxSize(min, max Size) (err error) { err = wb.WindowBase.SetMinMaxSize(min, max) wb.RequestLayout() return } // AlwaysConsumeSpace returns if the Widget should consume space even if it is // not visible. func (wb *WidgetBase) AlwaysConsumeSpace() bool { return wb.alwaysConsumeSpace } // SetAlwaysConsumeSpace sets if the Widget should consume space even if it is // not visible. func (wb *WidgetBase) SetAlwaysConsumeSpace(b bool) error { wb.alwaysConsumeSpace = b wb.RequestLayout() return nil } // Parent returns the Container of the WidgetBase. func (wb *WidgetBase) Parent() Container { return wb.parent } // SetParent sets the parent of the WidgetBase and adds the WidgetBase to the // Children list of the Container. func (wb *WidgetBase) SetParent(parent Container) (err error) { if parent == wb.parent { return nil } style := uint32(win.GetWindowLong(wb.hWnd, win.GWL_STYLE)) if style == 0 { return lastError("GetWindowLong") } if parent == nil { wb.SetVisible(false) style &^= win.WS_CHILD style |= win.WS_POPUP if win.SetParent(wb.hWnd, 0) == 0 { return lastError("SetParent") } win.SetLastError(0) if win.SetWindowLong(wb.hWnd, win.GWL_STYLE, int32(style)) == 0 { return lastError("SetWindowLong") } } else { style |= win.WS_CHILD style &^= win.WS_POPUP win.SetLastError(0) if win.SetWindowLong(wb.hWnd, win.GWL_STYLE, int32(style)) == 0 { return lastError("SetWindowLong") } if win.SetParent(wb.hWnd, parent.Handle()) == 0 { return lastError("SetParent") } if cb := parent.AsContainerBase(); cb != nil { win.SetWindowLong(wb.hWnd, win.GWL_ID, cb.NextChildID()) } } b := wb.BoundsPixels() if !win.SetWindowPos( wb.hWnd, win.HWND_BOTTOM, int32(b.X), int32(b.Y), int32(b.Width), int32(b.Height), win.SWP_FRAMECHANGED) { return lastError("SetWindowPos") } oldParent := wb.parent wb.parent = parent var oldChildren, newChildren *WidgetList if oldParent != nil { oldChildren = oldParent.Children() } if parent != nil { newChildren = parent.Children() } if newChildren == oldChildren { return nil } widget := wb.window.(Widget) if oldChildren != nil { oldChildren.Remove(widget) } if newChildren != nil && !newChildren.containsHandle(wb.hWnd) { newChildren.Add(widget) } return nil } func (wb *WidgetBase) ForEachAncestor(f func(window Window) bool) { hwnd := win.GetParent(wb.hWnd) for hwnd != 0 { if window := windowFromHandle(hwnd); window != nil { if !f(window) { return } } hwnd = win.GetParent(hwnd) } } // ToolTipText returns the tool tip text of the WidgetBase. func (wb *WidgetBase) ToolTipText() string { if tt := wb.group.ToolTip(); tt != nil { return tt.Text(wb.window.(Widget)) } return "" } // SetToolTipText sets the tool tip text of the WidgetBase. func (wb *WidgetBase) SetToolTipText(s string) error { if tt := wb.group.ToolTip(); tt != nil { if err := tt.SetText(wb.window.(Widget), s); err != nil { return err } } wb.toolTipTextChangedPublisher.Publish() return nil } // GraphicsEffects returns a list of WidgetGraphicsEffects that are applied to the WidgetBase. func (wb *WidgetBase) GraphicsEffects() *WidgetGraphicsEffectList { return wb.graphicsEffects } func (wb *WidgetBase) onInsertedGraphicsEffect(index int, effect WidgetGraphicsEffect) error { wb.invalidateBorderInParent() return nil } func (wb *WidgetBase) onRemovedGraphicsEffect(index int, effect WidgetGraphicsEffect) error { wb.invalidateBorderInParent() return nil } func (wb *WidgetBase) onClearedGraphicsEffects() error { wb.invalidateBorderInParent() return nil } func (wb *WidgetBase) invalidateBorderInParent() { if !wb.hasActiveGraphicsEffects() { return } if wb.parent != nil && wb.parent.Layout() != nil { b := wb.BoundsPixels().toRECT() s := int32(wb.parent.Layout().Spacing()) hwnd := wb.parent.Handle() rc := win.RECT{Left: b.Left - s, Top: b.Top - s, Right: b.Left, Bottom: b.Bottom + s} win.InvalidateRect(hwnd, &rc, true) rc = win.RECT{Left: b.Right, Top: b.Top - s, Right: b.Right + s, Bottom: b.Bottom + s} win.InvalidateRect(hwnd, &rc, true) rc = win.RECT{Left: b.Left, Top: b.Top - s, Right: b.Right, Bottom: b.Top} win.InvalidateRect(hwnd, &rc, true) rc = win.RECT{Left: b.Left, Top: b.Bottom, Right: b.Right, Bottom: b.Bottom + s} win.InvalidateRect(hwnd, &rc, true) } } func (wb *WidgetBase) hasActiveGraphicsEffects() bool { if wb.graphicsEffects == nil { return false } count := wb.graphicsEffects.Len() for _, gfx := range [...]WidgetGraphicsEffect{FocusEffect, InteractionEffect, ValidationErrorEffect} { if wb.graphicsEffects.Contains(gfx) { if gfx == nil { count-- } } } return count > 0 } func (wb *WidgetBase) hasComplexBackground() bool { if bg := wb.window.Background(); bg != nil && bg != nullBrushSingleton { return !bg.simple() } var complex bool wb.ForEachAncestor(func(window Window) bool { if bg := window.Background(); bg != nil && !bg.simple() { complex = true return false } return true }) return complex } func ancestor(w Widget) Form { if w == nil { return nil } hWndRoot := win.GetAncestor(w.Handle(), win.GA_ROOT) rw, _ := windowFromHandle(hWndRoot).(Form) return rw } func (wb *WidgetBase) LayoutFlags() LayoutFlags { return createLayoutItemForWidget(wb.window.(Widget)).LayoutFlags() } func (wb *WidgetBase) SizeHint() Size { if is, ok := createLayoutItemForWidget(wb.window.(Widget)).(IdealSizer); ok { return is.IdealSize() } return Size{} } func (wb *WidgetBase) MinSizeHint() Size { if ms, ok := createLayoutItemForWidget(wb.window.(Widget)).(MinSizer); ok { return ms.MinSize() } return Size{} } ================================================ FILE: widgetlist.go ================================================ // 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 ( "github.com/lxn/win" ) type widgetListObserver interface { onInsertingWidget(index int, widget Widget) error onInsertedWidget(index int, widget Widget) error onRemovingWidget(index int, widget Widget) error onRemovedWidget(index int, widget Widget) error onClearingWidgets() error onClearedWidgets() error } type WidgetList struct { items []*WidgetBase observer widgetListObserver widgetInRemoval *WidgetBase } func newWidgetList(observer widgetListObserver) *WidgetList { return &WidgetList{observer: observer} } func (l *WidgetList) Add(item Widget) error { return l.Insert(len(l.items), item) } func (l *WidgetList) At(index int) Widget { return l.items[index].window.(Widget) } func (l *WidgetList) Clear() error { observer := l.observer if observer != nil { if err := observer.onClearingWidgets(); err != nil { return err } } oldItems := l.items l.items = l.items[:0] if observer != nil { if err := observer.onClearedWidgets(); err != nil { l.items = oldItems return err } } for _, item := range oldItems { item.form = nil } return nil } func (l *WidgetList) Index(item Widget) int { wb := item.AsWidgetBase() for i, widget := range l.items { if widget == wb { return i } } return -1 } func (l *WidgetList) Contains(item Widget) bool { return l.Index(item) > -1 } func (l *WidgetList) indexHandle(handle win.HWND) int { for i, widget := range l.items { if widget.Handle() == handle { return i } } return -1 } func (l *WidgetList) containsHandle(handle win.HWND) bool { return l.indexHandle(handle) > -1 } func (l *WidgetList) insertIntoSlice(index int, item Widget) { l.items = append(l.items, nil) copy(l.items[index+1:], l.items[index:]) l.items[index] = item.AsWidgetBase() } func (l *WidgetList) Insert(index int, item Widget) error { if l.Contains(item) { return newError("cannot insert same widget multiple times") } observer := l.observer if observer != nil { if err := observer.onInsertingWidget(index, item); err != nil { return err } } l.insertIntoSlice(index, item) if observer != nil { if err := observer.onInsertedWidget(index, item); err != nil { l.items = append(l.items[:index], l.items[index+1:]...) return err } } return nil } func (l *WidgetList) Len() int { return len(l.items) } func (l *WidgetList) Remove(item Widget) error { index := l.Index(item) if index == -1 { return nil } return l.RemoveAt(index) } func (l *WidgetList) RemoveAt(index int) error { item := l.items[index] if item == l.widgetInRemoval { return nil } observer := l.observer widget := item.window.(Widget) if observer != nil { l.widgetInRemoval = item defer func() { l.widgetInRemoval = nil }() if err := observer.onRemovingWidget(index, widget); err != nil { return err } } l.items = append(l.items[:index], l.items[index+1:]...) if observer != nil { if err := observer.onRemovedWidget(index, widget); err != nil { l.insertIntoSlice(index, widget) return err } } item.form = nil return nil } ================================================ FILE: window.go ================================================ // 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 ( "bytes" "fmt" "image" "runtime" "strings" "sync/atomic" "syscall" "unsafe" "github.com/lxn/win" ) // App-specific message ids for internal use in Walk. // TODO: Document reserved range somewhere (when we have an idea how many we need). const ( notifyIconMessageId = win.WM_APP + iota ) // Window is an interface that provides operations common to all windows. type Window interface { // AddDisposable adds a Disposable resource that should be disposed of // together with this Window. AddDisposable(d Disposable) // AsWindowBase returns a *WindowBase, a pointer to an instance of the // struct that implements most operations common to all windows. AsWindowBase() *WindowBase // Accessibility returns the accessibility object used to set Dynamic Annotation properties of the // window. Accessibility() *Accessibility // Background returns the background Brush of the Window. // // By default this is nil. Background() Brush // Bounds returns the outer bounding box rectangle of the Window, including // decorations. // // For a Form, like *MainWindow or *Dialog, the rectangle is in screen // coordinates, for a child Window the coordinates are relative to its // parent. Bounds() Rectangle // BoundsPixels returns the outer bounding box rectangle of the Window, including // decorations. // // For a Form, like *MainWindow or *Dialog, the rectangle is in screen // coordinates, for a child Window the coordinates are relative to its // parent. BoundsPixels() Rectangle // BoundsChanged returns an *Event that you can attach to for handling bounds // changed events for the Window. BoundsChanged() *Event // BringToTop moves the Window to the top of the keyboard focus order. BringToTop() error // ClientBounds returns the inner bounding box rectangle of the Window, // excluding decorations. ClientBounds() Rectangle // ClientBoundsPixels returns the inner bounding box rectangle of the Window, // excluding decorations. ClientBoundsPixels() Rectangle // ContextMenu returns the context menu of the Window. // // By default this is nil. ContextMenu() *Menu // ContextMenuLocation returns the context menu suggested location in screen coordinates in // native pixels. This method is called when context menu is invoked using keyboard and mouse // coordinates are not available. ContextMenuLocation() Point // CreateCanvas creates and returns a *Canvas that can be used to draw // inside the ClientBoundsPixels of the Window. // // Remember to call the Dispose method on the canvas to release resources, // when you no longer need it. CreateCanvas() (*Canvas, error) // Cursor returns the Cursor of the Window. // // By default this is nil. Cursor() Cursor // Dispose releases the operating system resources, associated with the // Window. // // If a user closes a *MainWindow or *Dialog, it is automatically released. // Also, if a Container is disposed of, all its descendants will be released // as well. Dispose() // Disposing returns an Event that is published when the Window is disposed // of. Disposing() *Event // DoubleBuffering returns whether double buffering of the // drawing is enabled, which may help reduce flicker. DoubleBuffering() bool // DPI returns the current DPI value of the Window. DPI() int // Enabled returns if the Window is enabled for user interaction. Enabled() bool // Focused returns whether the Window has the keyboard input focus. Focused() bool // FocusedChanged returns an Event that you can attach to for handling focus // changed events for the Window. FocusedChanged() *Event // Font returns the *Font of the Window. // // By default this is a MS Shell Dlg 2, 8 point font. Font() *Font // Form returns the Form of the Window. Form() Form // Handle returns the window handle of the Window. Handle() win.HWND // Height returns the outer height of the Window, including decorations. Height() int // HeightPixels returns the outer height of the Window, including decorations. HeightPixels() int // Invalidate schedules a full repaint of the Window. Invalidate() error // IsDisposed returns if the Window has been disposed of. IsDisposed() bool // KeyDown returns a *KeyEvent that you can attach to for handling key down // events for the Window. KeyDown() *KeyEvent // KeyPress returns a *KeyEvent that you can attach to for handling key // press events for the Window. KeyPress() *KeyEvent // KeyUp returns a *KeyEvent that you can attach to for handling key up // events for the Window. KeyUp() *KeyEvent // MaxSize returns the maximum allowed outer size for the Window, including // decorations. // // For child windows, this is only relevant when the parent of the Window // has a Layout. RootWidgets, like *MainWindow and *Dialog, also honor this. MaxSize() Size // MaxSizePixels returns the maximum allowed outer size for the Window, including // decorations. // // For child windows, this is only relevant when the parent of the Window // has a Layout. RootWidgets, like *MainWindow and *Dialog, also honor this. MaxSizePixels() Size // MinSize returns the minimum allowed outer size for the Window, including // decorations. // // For child windows, this is only relevant when the parent of the Window // has a Layout. RootWidgets, like *MainWindow and *Dialog, also honor this. MinSize() Size // MinSizePixels returns the minimum allowed outer size for the Window, including // decorations. // // For child windows, this is only relevant when the parent of the Window // has a Layout. RootWidgets, like *MainWindow and *Dialog, also honor this. MinSizePixels() Size // MouseDown returns a *MouseEvent that you can attach to for handling // mouse down events for the Window. MouseDown() *MouseEvent // MouseMove returns a *MouseEvent that you can attach to for handling // mouse move events for the Window. MouseMove() *MouseEvent // MouseUp returns a *MouseEvent that you can attach to for handling // mouse up events for the Window. MouseUp() *MouseEvent // Name returns the name of the Window. Name() string // RequestLayout either schedules or immediately starts performing layout. RequestLayout() // RightToLeftReading returns whether the reading order of the Window // is from right to left. RightToLeftReading() bool // Screenshot returns an image of the window. Screenshot() (*image.RGBA, error) // SendMessage sends a message to the window and returns the result. SendMessage(msg uint32, wParam, lParam uintptr) uintptr // SetBackground sets the background Brush of the Window. SetBackground(value Brush) // SetBounds sets the outer bounding box rectangle of the Window, including // decorations. // // For a Form, like *MainWindow or *Dialog, the rectangle is in screen // coordinates, for a child Window the coordinates are relative to its // parent. SetBounds(value Rectangle) error // SetBoundsPixels sets the outer bounding box rectangle of the Window, including // decorations. // // For a Form, like *MainWindow or *Dialog, the rectangle is in screen // coordinates, for a child Window the coordinates are relative to its // parent. SetBoundsPixels(value Rectangle) error // SetClientSize sets the size of the inner bounding box of the Window, // excluding decorations. SetClientSize(value Size) error // SetClientSizePixels sets the size of the inner bounding box of the Window, // excluding decorations. SetClientSizePixels(value Size) error // SetContextMenu sets the context menu of the Window. SetContextMenu(value *Menu) // SetCursor sets the Cursor of the Window. SetCursor(value Cursor) // SetDoubleBuffering enables or disables double buffering of the // drawing, which may help reduce flicker. SetDoubleBuffering(value bool) error // SetEnabled sets if the Window is enabled for user interaction. SetEnabled(value bool) // SetFocus sets the keyboard input focus to the Window. SetFocus() error // SetFont sets the *Font of the Window. SetFont(value *Font) // SetHeight sets the outer height of the Window, including decorations. SetHeight(value int) error // SetHeightPixels sets the outer height of the Window, including decorations. SetHeightPixels(value int) error // SetMinMaxSize sets the minimum and maximum outer size of the Window, // including decorations. // // Use walk.Size{} to make the respective limit be ignored. SetMinMaxSize(min, max Size) error // SetMinMaxSizePixels sets the minimum and maximum outer size of the Window, // including decorations. // // Use walk.Size{} to make the respective limit be ignored. SetMinMaxSizePixels(min, max Size) error // SetName sets the name of the Window. // // This is important if you want to make use of the built-in UI persistence. // Some windows support automatic state persistence. See Settings for // details. SetName(name string) // SetRightToLeftReading sets whether the reading order of the Window // is from right to left. SetRightToLeftReading(rtl bool) error // SetSize sets the outer size of the Window, including decorations. SetSize(value Size) error // SetSizePixels sets the outer size of the Window, including decorations. SetSizePixels(value Size) error // SetSuspended sets if the Window is suspended for layout and repainting // purposes. // // You should call SetSuspended(true), before doing a batch of modifications // that would cause multiple layout or drawing updates. Remember to call // SetSuspended(false) afterwards, which will update the Window accordingly. SetSuspended(suspend bool) // SetVisible sets if the Window is visible. SetVisible(value bool) // SetWidth sets the outer width of the Window, including decorations. SetWidth(value int) error // SetWidthPixels sets the outer width of the Window, including decorations. SetWidthPixels(value int) error // SetX sets the x coordinate of the Window, relative to the screen for // RootWidgets like *MainWindow or *Dialog and relative to the parent for // child Windows. SetX(value int) error // SetXPixels sets the x coordinate of the Window, relative to the screen for // RootWidgets like *MainWindow or *Dialog and relative to the parent for // child Windows. SetXPixels(value int) error // SetY sets the y coordinate of the Window, relative to the screen for // RootWidgets like *MainWindow or *Dialog and relative to the parent for // child Windows. SetY(value int) error // SetYPixels sets the y coordinate of the Window, relative to the screen for // RootWidgets like *MainWindow or *Dialog and relative to the parent for // child Windows. SetYPixels(value int) error // Size returns the outer size of the Window, including decorations. Size() Size // SizePixels returns the outer size of the Window, including decorations. SizePixels() Size // SizeChanged returns an *Event that you can attach to for handling size // changed events for the Window. SizeChanged() *Event // Suspended returns if the Window is suspended for layout and repainting // purposes. Suspended() bool // Synchronize enqueues func f to be called some time later by the main // goroutine from inside a message loop. Synchronize(f func()) // Visible returns if the Window is visible. Visible() bool // VisibleChanged returns an Event that you can attach to for handling // visible changed events for the Window. VisibleChanged() *Event // Width returns the outer width of the Window, including decorations. Width() int // WidthPixels returns the outer width of the Window, including decorations. WidthPixels() int // WndProc is the window procedure of the window. // // When implementing your own WndProc to add or modify behavior, call the // WndProc of the embedded window for messages you don't handle yourself. WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr // X returns the x coordinate of the Window, relative to the screen for // RootWidgets like *MainWindow or *Dialog and relative to the parent for // child Windows. X() int // XPixels returns the x coordinate of the Window, relative to the screen for // RootWidgets like *MainWindow or *Dialog and relative to the parent for // child Windows. XPixels() int // Y returns the y coordinate of the Window, relative to the screen for // RootWidgets like *MainWindow or *Dialog and relative to the parent for // child Windows. Y() int // YPixels returns the y coordinate of the Window, relative to the screen for // RootWidgets like *MainWindow or *Dialog and relative to the parent for // child Windows. YPixels() int } type calcTextSizeInfo struct { width int // in native pixels font fontInfo text string dpi int } // WindowBase implements many operations common to all Windows. type WindowBase struct { nopActionListObserver group *WindowGroup window Window form Form hWnd win.HWND origWndProcPtr uintptr name string font *Font hFont win.HFONT contextMenu *Menu shortcutActions *ActionList disposables []Disposable disposingPublisher EventPublisher dropFilesPublisher DropFilesEventPublisher keyDownPublisher KeyEventPublisher keyPressPublisher KeyEventPublisher keyUpPublisher KeyEventPublisher mouseDownPublisher MouseEventPublisher mouseUpPublisher MouseEventPublisher mouseMovePublisher MouseEventPublisher mouseWheelPublisher MouseEventPublisher boundsChangedPublisher EventPublisher sizeChangedPublisher EventPublisher maxSize96dpi Size minSize96dpi Size background Brush cursor Cursor name2Property map[string]Property enabledProperty Property enabledChangedPublisher EventPublisher visibleProperty Property visibleChangedPublisher EventPublisher focusedProperty Property focusedChangedPublisher EventPublisher calcTextSizeInfo2TextSize map[calcTextSizeInfo]Size // in native pixels suspended bool visible bool enabled bool acc *Accessibility } var ( registeredWindowClasses = make(map[string]bool) defaultWndProcPtr uintptr hwnd2WindowBase = make(map[win.HWND]*WindowBase) ) func init() { AppendToWalkInit(func() { forEachDescendantCallbackPtr = syscall.NewCallback(forEachDescendant) forEachDescendantRawCallbackPtr = syscall.NewCallback(forEachDescendantRaw) dialogBaseUnitsUTF16StringPtr = syscall.StringToUTF16Ptr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") }) } // MustRegisterWindowClass registers the specified window class. // // MustRegisterWindowClass must be called once for every window type that is not // based on any system provided control, before calling InitChildWidget or // InitWidget. Calling MustRegisterWindowClass twice with the same className // results in a panic. func MustRegisterWindowClass(className string) { MustRegisterWindowClassWithWndProcPtr(className, defaultWndProcPtr) } func MustRegisterWindowClassWithStyle(className string, style uint32) { MustRegisterWindowClassWithWndProcPtrAndStyle(className, defaultWndProcPtr, style) } func MustRegisterWindowClassWithWndProcPtr(className string, wndProcPtr uintptr) { MustRegisterWindowClassWithWndProcPtrAndStyle(className, wndProcPtr, 0) } func MustRegisterWindowClassWithWndProcPtrAndStyle(className string, wndProcPtr uintptr, style uint32) { if registeredWindowClasses[className] { panic("window class already registered") } hInst := win.GetModuleHandle(nil) if hInst == 0 { panic("GetModuleHandle") } hIcon := win.LoadIcon(hInst, win.MAKEINTRESOURCE(7)) // rsrc uses 7 for app icon if hIcon == 0 { hIcon = win.LoadIcon(0, win.MAKEINTRESOURCE(win.IDI_APPLICATION)) } if hIcon == 0 { panic("LoadIcon") } hCursor := win.LoadCursor(0, win.MAKEINTRESOURCE(win.IDC_ARROW)) if hCursor == 0 { panic("LoadCursor") } var wc win.WNDCLASSEX wc.CbSize = uint32(unsafe.Sizeof(wc)) wc.LpfnWndProc = wndProcPtr wc.HInstance = hInst wc.HIcon = hIcon wc.HCursor = hCursor wc.HbrBackground = win.COLOR_BTNFACE + 1 wc.LpszClassName = syscall.StringToUTF16Ptr(className) wc.Style = style if atom := win.RegisterClassEx(&wc); atom == 0 { panic("RegisterClassEx") } registeredWindowClasses[className] = true } var initedWalk uint32 var walkInit []func() func AppendToWalkInit(fn func()) { walkInit = append(walkInit, fn) } type windowCfg struct { Window Window Parent Window ClassName string Style uint32 ExStyle uint32 Bounds Rectangle } // InitWindow initializes a window. // // Widgets should be initialized using InitWidget instead. func InitWindow(window, parent Window, className string, style, exStyle uint32) error { return initWindowWithCfg(&windowCfg{ Window: window, Parent: parent, ClassName: className, Style: style, ExStyle: exStyle, }) } func initWindowWithCfg(cfg *windowCfg) error { // We can't use sync.Once, because tooltip.go's init also calls InitWindow, so we deadlock. if atomic.CompareAndSwapUint32(&initedWalk, 0, 1) { runtime.LockOSThread() var initCtrls win.INITCOMMONCONTROLSEX initCtrls.DwSize = uint32(unsafe.Sizeof(initCtrls)) initCtrls.DwICC = win.ICC_LINK_CLASS | win.ICC_LISTVIEW_CLASSES | win.ICC_PROGRESS_CLASS | win.ICC_TAB_CLASSES | win.ICC_TREEVIEW_CLASSES win.InitCommonControlsEx(&initCtrls) defaultWndProcPtr = syscall.NewCallback(defaultWndProc) for _, fn := range walkInit { fn() } } wb := cfg.Window.AsWindowBase() wb.window = cfg.Window wb.enabled = true wb.visible = cfg.Style&win.WS_VISIBLE != 0 wb.calcTextSizeInfo2TextSize = make(map[calcTextSizeInfo]Size) wb.name2Property = make(map[string]Property) var hwndParent win.HWND var hMenu win.HMENU if cfg.Parent != nil { hwndParent = cfg.Parent.Handle() if widget, ok := cfg.Window.(Widget); ok { if container, ok := cfg.Parent.(Container); ok { if cb := container.AsContainerBase(); cb != nil { hMenu = win.HMENU(cb.NextChildID()) } widget.AsWidgetBase().parent = container } } } var windowName *uint16 if len(wb.name) != 0 { windowName = syscall.StringToUTF16Ptr(wb.name) } if hwnd := cfg.Window.Handle(); hwnd == 0 { var x, y, w, h int32 if cfg.Bounds.IsZero() { x = win.CW_USEDEFAULT y = win.CW_USEDEFAULT w = win.CW_USEDEFAULT h = win.CW_USEDEFAULT } else { x = int32(cfg.Bounds.X) y = int32(cfg.Bounds.Y) w = int32(cfg.Bounds.Width) h = int32(cfg.Bounds.Height) } wb.hWnd = win.CreateWindowEx( cfg.ExStyle, syscall.StringToUTF16Ptr(cfg.ClassName), windowName, cfg.Style|win.WS_CLIPSIBLINGS, x, y, w, h, hwndParent, hMenu, 0, nil) if wb.hWnd == 0 { return lastError("CreateWindowEx") } } else { wb.hWnd = hwnd } // Handles returned by CreateWindowEx can only be used by the calling // thread. As a result, InitWindow *must* be called from a goroutine that // has been locked to an OS thread via runtime.LockOSThread(). // // This means we can ask the OS for the ID of the current thread and we // don't have to worry about the scheduler moving us onto another thread // later. tid := win.GetCurrentThreadId() // Use the thread ID to look up our window group, which stores data that // is common to all windows on a common thread. A group will be created // if one doesn't already exist for the thread ID. // // CreateGroup automatically increments the reference counter for the // group. The counter will be decremented later in WindowBase.Dispose. wb.group = wgm.CreateGroup(tid) succeeded := false defer func() { if !succeeded { wb.Dispose() } }() hwnd2WindowBase[wb.hWnd] = wb if !registeredWindowClasses[cfg.ClassName] { // We subclass all windows of system classes. wb.origWndProcPtr = win.SetWindowLongPtr(wb.hWnd, win.GWLP_WNDPROC, defaultWndProcPtr) if wb.origWndProcPtr == 0 { return lastError("SetWindowLongPtr") } } SetWindowFont(wb.hWnd, defaultFont) if form, ok := cfg.Window.(Form); ok { if fb := form.AsFormBase(); fb != nil { if err := fb.init(form); err != nil { return err } } } if widget, ok := cfg.Window.(Widget); ok { if wb := widget.AsWidgetBase(); wb != nil { if err := wb.init(widget); err != nil { return err } } } wb.enabledProperty = NewBoolProperty( func() bool { return wb.window.Enabled() }, func(b bool) error { wb.window.SetEnabled(b) return nil }, wb.enabledChangedPublisher.Event()) wb.visibleProperty = NewBoolProperty( func() bool { return wb.visible }, func(b bool) error { wb.window.SetVisible(b) return nil }, wb.visibleChangedPublisher.Event()) wb.focusedProperty = NewReadOnlyBoolProperty( func() bool { return wb.window.Focused() }, wb.focusedChangedPublisher.Event()) wb.MustRegisterProperty("Enabled", wb.enabledProperty) wb.MustRegisterProperty("Visible", wb.visibleProperty) wb.MustRegisterProperty("Focused", wb.focusedProperty) succeeded = true return nil } // InitWrapperWindow initializes a window that wraps (embeds) another window. // // Calling this method is necessary, if you want to be able to override the // WndProc method of the embedded window. The embedded window should only be // used as inseparable part of the wrapper window to avoid undefined behavior. func InitWrapperWindow(window Window) error { wb := window.AsWindowBase() wb.window = window if container, ok := window.(Container); ok { children := container.Children() if wlo, ok := window.(widgetListObserver); ok { children.observer = wlo } for _, child := range children.items { child.parent = container } } return nil } func (wb *WindowBase) MustRegisterProperty(name string, property Property) { if property == nil { panic("property must not be nil") } if wb.name2Property[name] != nil { panic("property already registered") } wb.name2Property[name] = property } func (wb *WindowBase) Property(name string) Property { return wb.name2Property[name] } func (wb *WindowBase) hasStyleBits(bits uint32) bool { return hasWindowLongBits(wb.hWnd, win.GWL_STYLE, bits) } func (wb *WindowBase) hasExtendedStyleBits(bits uint32) bool { return hasWindowLongBits(wb.hWnd, win.GWL_EXSTYLE, bits) } func hasWindowLongBits(hwnd win.HWND, index int32, bits uint32) bool { value := uint32(win.GetWindowLong(hwnd, index)) return value&bits == bits } func (wb *WindowBase) setAndClearStyleBits(set, clear uint32) error { return setAndClearWindowLongBits(wb.hWnd, win.GWL_STYLE, set, clear) } func (wb *WindowBase) setAndClearExtendedStyleBits(set, clear uint32) error { return setAndClearWindowLongBits(wb.hWnd, win.GWL_EXSTYLE, set, clear) } func setAndClearWindowLongBits(hwnd win.HWND, index int32, set, clear uint32) error { value := uint32(win.GetWindowLong(hwnd, index)) if value == 0 { return lastError("GetWindowLong") } if newValue := value&^clear | set; newValue != value { win.SetLastError(0) if win.SetWindowLong(hwnd, index, int32(newValue)) == 0 { return lastError("SetWindowLong") } } return nil } func (wb *WindowBase) ensureStyleBits(bits uint32, set bool) error { return ensureWindowLongBits(wb.hWnd, win.GWL_STYLE, bits, set) } func (wb *WindowBase) ensureExtendedStyleBits(bits uint32, set bool) error { return ensureWindowLongBits(wb.hWnd, win.GWL_EXSTYLE, bits, set) } func ensureWindowLongBits(hwnd win.HWND, index int32, bits uint32, set bool) error { var setBits uint32 var clearBits uint32 if set { setBits = bits } else { clearBits = bits } return setAndClearWindowLongBits(hwnd, index, setBits, clearBits) } // Accessibility returns the accessibility object used to set Dynamic Annotation properties of the // window. func (wb *WindowBase) Accessibility() *Accessibility { if wb.acc == nil { wb.acc = &Accessibility{wb: wb} } return wb.acc } // Handle returns the window handle of the Window. func (wb *WindowBase) Handle() win.HWND { return wb.hWnd } // SendMessage sends a message to the window and returns the result. func (wb *WindowBase) SendMessage(msg uint32, wParam, lParam uintptr) uintptr { return win.SendMessage(wb.hWnd, msg, wParam, lParam) } // Name returns the name of the *WindowBase. func (wb *WindowBase) Name() string { return wb.name } // SetName sets the name of the *WindowBase. func (wb *WindowBase) SetName(name string) { wb.name = name } func (wb *WindowBase) writePath(buf *bytes.Buffer) { hWndParent := win.GetAncestor(wb.hWnd, win.GA_PARENT) if pwi := windowFromHandle(hWndParent); pwi != nil { if sv, ok := pwi.(*ScrollView); ok { pwi = sv.Parent() } pwi.AsWindowBase().writePath(buf) buf.WriteByte('/') } buf.WriteString(wb.name) } func (wb *WindowBase) path() string { buf := bytes.NewBuffer(nil) wb.writePath(buf) return buf.String() } // WindowBase simply returns the receiver. func (wb *WindowBase) AsWindowBase() *WindowBase { return wb } // AddDisposable adds a Disposable resource that should be disposed of // together with this Window. func (wb *WindowBase) AddDisposable(d Disposable) { wb.disposables = append(wb.disposables, d) } // Dispose releases the operating system resources, associated with the // *WindowBase. // // If a user closes a *MainWindow or *Dialog, it is automatically released. // Also, if a Container is disposed of, all its descendants will be released // as well. func (wb *WindowBase) Dispose() { for _, d := range wb.disposables { d.Dispose() } if wb.background != nil { wb.background.detachWindow(wb) } hWnd := wb.hWnd if hWnd != 0 { wb.disposingPublisher.Publish() wb.hWnd = 0 if _, ok := hwnd2WindowBase[hWnd]; ok { win.DestroyWindow(hWnd) } } if cm := wb.contextMenu; cm != nil { cm.actions.Clear() cm.Dispose() } if wb.shortcutActions != nil { wb.shortcutActions.Clear() } for _, p := range wb.name2Property { p.SetSource(nil) } if hWnd != 0 { wb.group.accClearHwndProps(wb.hWnd) wb.group.Done() } } // Disposing returns an Event that is published when the Window is disposed // of. func (wb *WindowBase) Disposing() *Event { return wb.disposingPublisher.Event() } // IsDisposed returns if the *WindowBase has been disposed of. func (wb *WindowBase) IsDisposed() bool { return wb.hWnd == 0 } // ContextMenu returns the context menu of the *WindowBase. // // By default this is nil. func (wb *WindowBase) ContextMenu() *Menu { return wb.contextMenu } // SetContextMenu sets the context menu of the *WindowBase. func (wb *WindowBase) SetContextMenu(value *Menu) { wb.contextMenu = value } // ContextMenuLocation returns the the *WindowBase center in screen coordinates in native pixels. func (wb *WindowBase) ContextMenuLocation() Point { var rc win.RECT if !win.GetWindowRect(wb.hWnd, &rc) { return Point{} } return Point{int(rc.Left+rc.Right) / 2, int(rc.Top+rc.Bottom) / 2} } // ShortcutActions returns the list of actions that will be triggered if their // shortcut is pressed when this window or one of its descendants has the // keyboard focus. func (wb *WindowBase) ShortcutActions() *ActionList { if wb.shortcutActions == nil { wb.shortcutActions = newActionList(wb) } return wb.shortcutActions } // Background returns the background Brush of the *WindowBase. // // By default this is nil. func (wb *WindowBase) Background() Brush { return wb.background } // SetBackground sets the background Brush of the *WindowBase. func (wb *WindowBase) SetBackground(background Brush) { if wb.background != nil { wb.background.detachWindow(wb) } wb.background = background if background != nil { background.attachWindow(wb) } wb.Invalidate() // Sliders need some extra encouragement... walkDescendants(wb, func(w Window) bool { if s, ok := w.(*Slider); ok { s.SetRange(s.MinValue(), s.MaxValue()+1) s.SetRange(s.MinValue(), s.MaxValue()-1) } return true }) } // Cursor returns the Cursor of the *WindowBase. // // By default this is nil. func (wb *WindowBase) Cursor() Cursor { return wb.cursor } // SetCursor sets the Cursor of the *WindowBase. func (wb *WindowBase) SetCursor(value Cursor) { wb.cursor = value } // DoubleBuffering returns whether double buffering of the // drawing is enabled, which may help reduce flicker. func (wb *WindowBase) DoubleBuffering() bool { return wb.hasExtendedStyleBits(win.WS_EX_COMPOSITED) } // SetDoubleBuffering enables or disables double buffering of the // drawing, which may help reduce flicker. func (wb *WindowBase) SetDoubleBuffering(enabled bool) error { return wb.ensureExtendedStyleBits(win.WS_EX_COMPOSITED, enabled) } type ApplySysColorser interface { ApplySysColors() } func (wb *WindowBase) ApplySysColors() { wb.Invalidate() } // DPI returns the current DPI value of the WindowBase. func (wb *WindowBase) DPI() int { return int(win.GetDpiForWindow(wb.hWnd)) } type ApplyDPIer interface { ApplyDPI(dpi int) } func (wb *WindowBase) ApplyDPI(dpi int) { if af, ok := wb.window.(applyFonter); ok { af.applyFont(wb.window.Font()) } } // IntFrom96DPI converts from 1/96" units to native pixels. func (wb *WindowBase) IntFrom96DPI(value int) int { return IntFrom96DPI(value, wb.DPI()) } // IntTo96DPI converts from native pixels to 1/96" units. func (wb *WindowBase) IntTo96DPI(value int) int { return IntTo96DPI(value, wb.DPI()) } // MarginsFrom96DPI converts from 1/96" units to native pixels. func (wb *WindowBase) MarginsFrom96DPI(value Margins) Margins { return MarginsFrom96DPI(value, wb.DPI()) } // MarginsTo96DPI converts from native pixels to 1/96" units. func (wb *WindowBase) MarginsTo96DPI(value Margins) Margins { return MarginsTo96DPI(value, wb.DPI()) } // PointFrom96DPI converts from 1/96" units to native pixels. func (wb *WindowBase) PointFrom96DPI(value Point) Point { return PointFrom96DPI(value, wb.DPI()) } // PointTo96DPI converts from native pixels to 1/96" units. func (wb *WindowBase) PointTo96DPI(value Point) Point { return PointTo96DPI(value, wb.DPI()) } // RectangleFrom96DPI converts from 1/96" units to native pixels. func (wb *WindowBase) RectangleFrom96DPI(value Rectangle) Rectangle { return RectangleFrom96DPI(value, wb.DPI()) } // RectangleTo96DPI converts from native pixels to 1/96" units. func (wb *WindowBase) RectangleTo96DPI(value Rectangle) Rectangle { return RectangleTo96DPI(value, wb.DPI()) } // SizeFrom96DPI converts from 1/96" units to native pixels. func (wb *WindowBase) SizeFrom96DPI(value Size) Size { return SizeFrom96DPI(value, wb.DPI()) } // SizeTo96DPI converts from native pixels to 1/96" units. func (wb *WindowBase) SizeTo96DPI(value Size) Size { return SizeTo96DPI(value, wb.DPI()) } // Enabled returns if the *WindowBase is enabled for user interaction. func (wb *WindowBase) Enabled() bool { return wb.enabled } // SetEnabled sets if the *WindowBase is enabled for user interaction. func (wb *WindowBase) SetEnabled(enabled bool) { wb.enabled = enabled wb.window.(applyEnableder).applyEnabled(wb.window.Enabled()) if widget, ok := wb.window.(Widget); ok { widget.AsWidgetBase().invalidateBorderInParent() } wb.enabledChangedPublisher.Publish() } type applyEnableder interface { applyEnabled(enabled bool) } func (wb *WindowBase) applyEnabled(enabled bool) { setWindowEnabled(wb.hWnd, enabled) } func setWindowEnabled(hwnd win.HWND, enabled bool) { win.EnableWindow(hwnd, enabled) win.UpdateWindow(hwnd) } // Font returns the *Font of the *WindowBase. // // By default this is a MS Shell Dlg 2, 8 point font. func (wb *WindowBase) Font() *Font { if wb.font != nil { return wb.font } return defaultFont } // SetFont sets the *Font of the *WindowBase. func (wb *WindowBase) SetFont(font *Font) { if font != wb.font { wb.font = font wb.window.(applyFonter).applyFont(font) } } type applyFonter interface { applyFont(font *Font) } type ApplyFonter interface { ApplyFont(font *Font) } func (wb *WindowBase) applyFont(font *Font) { if hFont := font.handleForDPI(wb.DPI()); hFont != wb.hFont { wb.hFont = hFont setWindowFont(wb.hWnd, hFont) } if af, ok := wb.window.(ApplyFonter); ok { af.ApplyFont(font) } } func SetWindowFont(hwnd win.HWND, font *Font) { dpi := int(win.GetDpiForWindow(hwnd)) setWindowFont(hwnd, font.handleForDPI(dpi)) } func setWindowFont(hwnd win.HWND, hFont win.HFONT) { win.SendMessage(hwnd, win.WM_SETFONT, uintptr(hFont), 1) if window := windowFromHandle(hwnd); window != nil { if widget, ok := window.(Widget); ok { widget.AsWidgetBase().RequestLayout() } } } // Suspended returns if the *WindowBase is suspended for layout and repainting // purposes. func (wb *WindowBase) Suspended() bool { return wb.suspended } // SetSuspended sets if the *WindowBase is suspended for layout and repainting // purposes. // // You should call SetSuspended(true), before doing a batch of modifications // that would cause multiple layout or drawing updates. Remember to call // SetSuspended(false) afterwards, which will update the *WindowBase // accordingly. func (wb *WindowBase) SetSuspended(suspend bool) { if suspend == wb.suspended { return } var wParam int if suspend { wParam = 0 } else { wParam = 1 } if wb.visible { wb.SendMessage(win.WM_SETREDRAW, uintptr(wParam), 0) } wb.suspended = suspend if !suspend && wb.visible { wb.Invalidate() wb.RequestLayout() } } // Invalidate schedules a full repaint of the *WindowBase. func (wb *WindowBase) Invalidate() error { if !win.InvalidateRect(wb.hWnd, nil, true) { return newError("InvalidateRect failed") } return nil } func (wb *WindowBase) text() string { return windowText(wb.hWnd) } func (wb *WindowBase) setText(text string) error { if err := setWindowText(wb.hWnd, text); err != nil { return err } return nil } func windowText(hwnd win.HWND) string { textLength := win.SendMessage(hwnd, win.WM_GETTEXTLENGTH, 0, 0) buf := make([]uint16, textLength+1) win.SendMessage(hwnd, win.WM_GETTEXT, uintptr(textLength+1), uintptr(unsafe.Pointer(&buf[0]))) return syscall.UTF16ToString(buf) } func setWindowText(hwnd win.HWND, text string) error { if win.TRUE != win.SendMessage(hwnd, win.WM_SETTEXT, 0, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(text)))) { return newError("WM_SETTEXT failed") } return nil } func (wb *WindowBase) RestoreState() (err error) { wb.ForEachDescendant(func(widget Widget) bool { if persistable, ok := widget.(Persistable); ok && persistable.Persistent() { if err = persistable.RestoreState(); err != nil { return false } } if _, ok := widget.(Container); ok { return false } return true }) return } func (wb *WindowBase) SaveState() (err error) { wb.ForEachDescendant(func(widget Widget) bool { if persistable, ok := widget.(Persistable); ok && persistable.Persistent() { if err = persistable.SaveState(); err != nil { return false } } if _, ok := widget.(Container); ok { return false } return true }) return } // Form returns the Form of the Window. func (wb *WindowBase) Form() Form { if wb.form == nil { if form, ok := wb.window.(Form); ok { wb.form = form } else { wb.form = ancestor(wb.window.(Widget)) } } return wb.form } func forEachDescendant(hwnd win.HWND, lParam uintptr) uintptr { if window := windowFromHandle(hwnd); window == nil || forEachDescendantCallback(window.(Widget)) { return 1 } return 0 } var ( forEachDescendantCallbackPtr uintptr forEachDescendantCallback func(widget Widget) bool ) func (wb *WindowBase) ForEachDescendant(f func(widget Widget) bool) { forEachDescendantCallback = f defer func() { forEachDescendantCallback = nil }() win.EnumChildWindows(wb.hWnd, forEachDescendantCallbackPtr, 0) } func forEachDescendantRaw(hwnd win.HWND, lParam uintptr) uintptr { if forEachDescendantRawCallback(hwnd, lParam) { return 1 } return 0 } var ( forEachDescendantRawCallbackPtr uintptr forEachDescendantRawCallback func(hwnd win.HWND, lParam uintptr) bool ) func (wb *WindowBase) forEachDescendantRaw(lParam uintptr, f func(hwnd win.HWND, lParam uintptr) bool) { forEachDescendantRawCallback = f defer func() { forEachDescendantRawCallback = nil }() win.EnumChildWindows(wb.hWnd, forEachDescendantRawCallbackPtr, lParam) } // Visible returns if the *WindowBase is visible. func (wb *WindowBase) Visible() bool { return win.IsWindowVisible(wb.hWnd) } // SetVisible sets if the *WindowBase is visible. func (wb *WindowBase) SetVisible(visible bool) { old := wb.Visible() setWindowVisible(wb.hWnd, visible) wb.visible = visible walkDescendants(wb.window, func(w Window) bool { w.AsWindowBase().visibleChangedPublisher.Publish() return true }) if visible == old { return } if widget, ok := wb.window.(Widget); ok { wb := widget.AsWidgetBase() wb.invalidateBorderInParent() wb.RequestLayout() } wb.visibleChangedPublisher.Publish() } // VisibleChanged returns an Event that you can attach to for handling // visible changed events for the Window. func (wb *WindowBase) VisibleChanged() *Event { return wb.visibleChangedPublisher.Event() } func setWindowVisible(hwnd win.HWND, visible bool) { var cmd int32 if visible { cmd = win.SW_SHOWNA } else { cmd = win.SW_HIDE } win.ShowWindow(hwnd, cmd) } // BringToTop moves the *WindowBase to the top of the keyboard focus order. func (wb *WindowBase) BringToTop() error { if !win.SetWindowPos(wb.hWnd, win.HWND_TOP, 0, 0, 0, 0, win.SWP_NOACTIVATE|win.SWP_NOMOVE|win.SWP_NOSIZE) { return lastError("SetWindowPos") } return nil } // Bounds returns the outer bounding box rectangle of the *WindowBase, including // decorations. // // The coordinates are relative to the screen. func (wb *WindowBase) Bounds() Rectangle { return wb.RectangleTo96DPI(wb.BoundsPixels()) } // SetBounds sets the outer bounding box rectangle of the *WindowBase, // including decorations. // // For a Form, like *MainWindow or *Dialog, the rectangle is in screen // coordinates, for a child Window the coordinates are relative to its parent. func (wb *WindowBase) SetBounds(bounds Rectangle) error { return wb.SetBoundsPixels(wb.RectangleFrom96DPI(bounds)) } // BoundsPixels returns the outer bounding box rectangle of the *WindowBase, including // decorations. // // The coordinates are relative to the screen. func (wb *WindowBase) BoundsPixels() Rectangle { var r win.RECT if !win.GetWindowRect(wb.hWnd, &r) { lastError("GetWindowRect") return Rectangle{} } return rectangleFromRECT(r) } // SetBoundsPixels sets the outer bounding box rectangle of the *WindowBase, // including decorations. // // For a Form, like *MainWindow or *Dialog, the rectangle is in screen // coordinates, for a child Window the coordinates are relative to its parent. func (wb *WindowBase) SetBoundsPixels(bounds Rectangle) error { if !win.MoveWindow( wb.hWnd, int32(bounds.X), int32(bounds.Y), int32(bounds.Width), int32(bounds.Height), true) { return lastError("MoveWindow") } return nil } // MinSize returns the minimum allowed outer size for the *WindowBase, including // decorations. // // For child windows, this is only relevant when the parent of the *WindowBase // has a Layout. RootWidgets, like *MainWindow and *Dialog, also honor this. func (wb *WindowBase) MinSize() Size { return wb.minSize96dpi } // MinSizePixels returns the minimum allowed outer size for the *WindowBase, including // decorations. // // For child windows, this is only relevant when the parent of the *WindowBase // has a Layout. RootWidgets, like *MainWindow and *Dialog, also honor this. func (wb *WindowBase) MinSizePixels() Size { return wb.SizeFrom96DPI(wb.minSize96dpi) } // MaxSize returns the maximum allowed outer size for the *WindowBase, including // decorations. // // For child windows, this is only relevant when the parent of the *WindowBase // has a Layout. RootWidgets, like *MainWindow and *Dialog, also honor this. func (wb *WindowBase) MaxSize() Size { return wb.maxSize96dpi } // MaxSizePixels returns the maximum allowed outer size for the *WindowBase, including // decorations. // // For child windows, this is only relevant when the parent of the *WindowBase // has a Layout. RootWidgets, like *MainWindow and *Dialog, also honor this. func (wb *WindowBase) MaxSizePixels() Size { return wb.SizeFrom96DPI(wb.maxSize96dpi) } // SetMinMaxSize sets the minimum and maximum outer size of the *WindowBase, // including decorations. // // Use walk.Size{} to make the respective limit be ignored. func (wb *WindowBase) SetMinMaxSize(min, max Size) error { if min.Width < 0 || min.Height < 0 { return newError("min must be positive") } if max.Width > 0 && max.Width < min.Width || max.Height > 0 && max.Height < min.Height { return newError("max must be greater as or equal to min") } wb.minSize96dpi = min wb.maxSize96dpi = max return nil } // SetMinMaxSizePixels sets the minimum and maximum outer size of the *WindowBase, // including decorations. // // Use walk.Size{} to make the respective limit be ignored. func (wb *WindowBase) SetMinMaxSizePixels(min, max Size) error { dpi := wb.DPI() return wb.SetMinMaxSize(SizeTo96DPI(min, dpi), SizeTo96DPI(max, dpi)) } type fontInfoAndDPI struct { fontInfo dpi int } var ( dialogBaseUnitsUTF16StringPtr *uint16 fontInfoAndDPI2DialogBaseUnits = make(map[fontInfoAndDPI]Size) ) // dialogBaseUnits returns dialog unit base size in native pixels. func (wb *WindowBase) dialogBaseUnits() Size { // The window may use a font different from that in WindowBase, // like e.g. NumberEdit does, so we try to use the right one. font := wb.window.Font() fi := fontInfoAndDPI{ fontInfo: fontInfo{ family: font.Family(), pointSize: font.PointSize(), style: font.Style(), }, dpi: wb.DPI()} if s, ok := fontInfoAndDPI2DialogBaseUnits[fi]; ok { return s } hdc := win.GetDC(wb.hWnd) defer win.ReleaseDC(wb.hWnd, hdc) hFont := font.handleForDPI(wb.DPI()) hFontOld := win.SelectObject(hdc, win.HGDIOBJ(hFont)) defer win.SelectObject(hdc, win.HGDIOBJ(hFontOld)) var tm win.TEXTMETRIC if !win.GetTextMetrics(hdc, &tm) { newError("GetTextMetrics failed") } var size win.SIZE if !win.GetTextExtentPoint32( hdc, dialogBaseUnitsUTF16StringPtr, 52, &size) { newError("GetTextExtentPoint32 failed") } s := Size{int((size.CX/26 + 1) / 2), int(tm.TmHeight)} fontInfoAndDPI2DialogBaseUnits[fi] = s return s } // dialogBaseUnitsToPixels returns size in dialog based units in native pixels. func (wb *WindowBase) dialogBaseUnitsToPixels(dlus Size) (pixels Size) { base := wb.dialogBaseUnits() return Size{ int(win.MulDiv(int32(dlus.Width), int32(base.Width), 4)), int(win.MulDiv(int32(dlus.Height), int32(base.Height), 8)), } } // calculateTextSizeImpl returns text size in native pixels. func (wb *WindowBase) calculateTextSizeImpl(text string) Size { return wb.calculateTextSizeImplForWidth(text, 0) } // calculateTextSizeImplForWidth calculates text size for specified width in native pixels. func (wb *WindowBase) calculateTextSizeImplForWidth(text string, width int) Size { font := wb.window.Font() dpi := wb.DPI() w := width if w == 0 { w = wb.WidthPixels() } key := calcTextSizeInfo{ width: w, font: fontInfo{ family: font.family, pointSize: font.pointSize, style: font.style, }, text: text, dpi: dpi, } if size, ok := wb.calcTextSizeInfo2TextSize[key]; ok { return size } size := calculateTextSize(text, font, dpi, width, wb.hWnd) wb.calcTextSizeInfo2TextSize[key] = size return size } // calculateTextSize calculates text size in native pixels. func (wb *WindowBase) calculateTextSize() Size { return wb.calculateTextSizeForWidth(0) } // calculateTextSizeForWidth calculates text size for specified width in native pixels. func (wb *WindowBase) calculateTextSizeForWidth(width int) Size { return wb.calculateTextSizeImplForWidth(wb.text(), width) } // calculateTextSize calculates text size at specified DPI and for width in native pixels. func calculateTextSize(text string, font *Font, dpi int, width int, hwnd win.HWND) Size { hdc := win.GetDC(hwnd) if hdc == 0 { newError("GetDC failed") return Size{} } defer win.ReleaseDC(hwnd, hdc) var size Size if width > 0 { canvas, err := newCanvasFromHDC(hdc) if err != nil { return size } defer canvas.Dispose() bounds, err := canvas.measureTextForDPI(text, font, Rectangle{Width: width, Height: 9999999}, 0, dpi) if err != nil { return size } size = bounds.Size() } else { hFontOld := win.SelectObject(hdc, win.HGDIOBJ(font.handleForDPI(dpi))) defer win.SelectObject(hdc, hFontOld) lines := strings.Split(text, "\n") for _, line := range lines { var s win.SIZE str := syscall.StringToUTF16(strings.TrimRight(line, "\r ")) if !win.GetTextExtentPoint32(hdc, &str[0], int32(len(str)-1), &s) { newError("GetTextExtentPoint32 failed") return Size{} } size.Width = maxi(size.Width, int(s.CX)) size.Height += int(s.CY) } } return size } // Size returns the outer size of the *WindowBase, including decorations. func (wb *WindowBase) Size() Size { return wb.SizeTo96DPI(wb.SizePixels()) } // SizePixels returns the outer size of the *WindowBase, including decorations. func (wb *WindowBase) SizePixels() Size { return wb.window.BoundsPixels().Size() } // SetSize sets the outer size of the *WindowBase, including decorations. func (wb *WindowBase) SetSize(size Size) error { return wb.SetSizePixels(wb.SizeFrom96DPI(size)) } // SetSizePixels sets the outer size of the *WindowBase, including decorations. func (wb *WindowBase) SetSizePixels(size Size) error { bounds := wb.window.BoundsPixels() return wb.SetBoundsPixels(bounds.SetSize(size)) } // X returns the x coordinate of the *WindowBase, relative to the screen for // RootWidgets like *MainWindow or *Dialog and relative to the parent for // child Windows. func (wb *WindowBase) X() int { return wb.IntTo96DPI(wb.XPixels()) } // XPixels returns the x coordinate of the *WindowBase, relative to the screen for // RootWidgets like *MainWindow or *Dialog and relative to the parent for // child Windows. func (wb *WindowBase) XPixels() int { return wb.window.BoundsPixels().X } // SetX sets the x coordinate of the *WindowBase, relative to the screen for // RootWidgets like *MainWindow or *Dialog and relative to the parent for // child Windows. func (wb *WindowBase) SetX(value int) error { return wb.SetXPixels(wb.IntFrom96DPI(value)) } // SetXPixels sets the x coordinate of the *WindowBase, relative to the screen for // RootWidgets like *MainWindow or *Dialog and relative to the parent for // child Windows. func (wb *WindowBase) SetXPixels(value int) error { bounds := wb.window.BoundsPixels() bounds.X = value return wb.SetBoundsPixels(bounds) } // Y returns the y coordinate of the *WindowBase, relative to the screen for // RootWidgets like *MainWindow or *Dialog and relative to the parent for // child Windows. func (wb *WindowBase) Y() int { return wb.IntTo96DPI(wb.YPixels()) } // YPixels returns the y coordinate of the *WindowBase, relative to the screen for // RootWidgets like *MainWindow or *Dialog and relative to the parent for // child Windows. func (wb *WindowBase) YPixels() int { return wb.window.BoundsPixels().Y } // SetY sets the y coordinate of the *WindowBase, relative to the screen for // RootWidgets like *MainWindow or *Dialog and relative to the parent for // child Windows. func (wb *WindowBase) SetY(value int) error { return wb.SetYPixels(wb.IntFrom96DPI(value)) } // SetYPixels sets the y coordinate of the *WindowBase, relative to the screen for // RootWidgets like *MainWindow or *Dialog and relative to the parent for // child Windows. func (wb *WindowBase) SetYPixels(value int) error { bounds := wb.window.BoundsPixels() bounds.Y = value return wb.SetBoundsPixels(bounds) } // Width returns the outer width of the *WindowBase, including decorations. func (wb *WindowBase) Width() int { return wb.IntTo96DPI(wb.WidthPixels()) } // WidthPixels returns the outer width of the *WindowBase, including decorations. func (wb *WindowBase) WidthPixels() int { return wb.window.BoundsPixels().Width } // SetWidth sets the outer width of the *WindowBase, including decorations. func (wb *WindowBase) SetWidth(value int) error { return wb.SetWidthPixels(wb.IntFrom96DPI(value)) } // SetWidthPixels sets the outer width of the *WindowBase, including decorations. func (wb *WindowBase) SetWidthPixels(value int) error { bounds := wb.window.BoundsPixels() bounds.Width = value return wb.SetBoundsPixels(bounds) } // Height returns the outer height of the *WindowBase, including decorations. func (wb *WindowBase) Height() int { return wb.IntTo96DPI(wb.HeightPixels()) } // HeightPixels returns the outer height of the *WindowBase, including decorations. func (wb *WindowBase) HeightPixels() int { return wb.window.BoundsPixels().Height } // SetHeight sets the outer height of the *WindowBase, including decorations. func (wb *WindowBase) SetHeight(value int) error { return wb.SetHeightPixels(wb.IntFrom96DPI(value)) } // SetHeightPixels sets the outer height of the *WindowBase, including decorations. func (wb *WindowBase) SetHeightPixels(value int) error { bounds := wb.window.BoundsPixels() bounds.Height = value return wb.SetBoundsPixels(bounds) } func windowTrimToClientBounds(hwnd win.HWND, pt *win.POINT) { var r win.RECT if !win.GetClientRect(hwnd, &r) { lastError("GetClientRect") return } if pt.X < r.Left { pt.X = r.Left } if pt.X > r.Right { pt.X = r.Right } if pt.Y < r.Top { pt.Y = r.Top } if pt.Y > r.Bottom { pt.Y = r.Bottom } } // windowClientBounds returns window client bounds in native pixels. func windowClientBounds(hwnd win.HWND) Rectangle { var r win.RECT if !win.GetClientRect(hwnd, &r) { lastError("GetClientRect") return Rectangle{} } return rectangleFromRECT(r) } // ClientBounds returns the inner bounding box rectangle of the *WindowBase, // excluding decorations. func (wb *WindowBase) ClientBounds() Rectangle { return wb.RectangleTo96DPI(wb.ClientBoundsPixels()) } // ClientBoundsPixels returns the inner bounding box rectangle of the *WindowBase, // excluding decorations. func (wb *WindowBase) ClientBoundsPixels() Rectangle { return windowClientBounds(wb.hWnd) } // sizeFromClientSizePixels calculates size from client size in native pixels. func (wb *WindowBase) sizeFromClientSizePixels(clientSize Size) Size { window := wb.window s := window.SizePixels() cs := window.ClientBoundsPixels().Size() ncs := Size{s.Width - cs.Width, s.Height - cs.Height} return Size{clientSize.Width + ncs.Width, clientSize.Height + ncs.Height} } // clientSizeFromSizePixels calculates client size from size in native pixels. func (wb *WindowBase) clientSizeFromSizePixels(size Size) Size { window := wb.window s := window.SizePixels() cs := window.ClientBoundsPixels().Size() ncs := Size{s.Width - cs.Width, s.Height - cs.Height} return Size{size.Width - ncs.Width, size.Height - ncs.Height} } // SetClientSize sets the size of the inner bounding box of the *WindowBase, // excluding decorations. func (wb *WindowBase) SetClientSize(value Size) error { return wb.SetClientSizePixels(wb.SizeFrom96DPI(value)) } // SetClientSizePixels sets the size of the inner bounding box of the *WindowBase, // excluding decorations. func (wb *WindowBase) SetClientSizePixels(value Size) error { return wb.SetSizePixels(wb.sizeFromClientSizePixels(value)) } // RequestLayout either schedules or immediately starts performing layout. func (wb *WindowBase) RequestLayout() { var form Form hwnd := wb.hWnd window := wb.window for hwnd != 0 { if window != nil { var ok, visible bool if form, ok = window.(Form); ok { visible = form.Visible() } else { visible = window.AsWindowBase().visible } if !visible && window != wb.window || window.Suspended() { return } if container, ok := window.(Container); ok && container.Layout() == nil { return } if widget, ok := window.(Widget); ok { if window = widget.Parent(); window != nil { hwnd = window.Handle() continue } } } else if !win.IsWindowVisible(hwnd) { return } hwnd = win.GetParent(hwnd) window = windowFromHandle(hwnd) } if form == nil { return } if fb := form.AsFormBase(); fb.group.ActiveForm() != form || fb.inProgressEventCount == 0 { fb.startLayout() } else { fb.layoutScheduled = true } } // RightToLeftReading returns whether the reading order of the Window // is from right to left. func (wb *WindowBase) RightToLeftReading() bool { return wb.hasExtendedStyleBits(win.WS_EX_RTLREADING) } // SetRightToLeftReading sets whether the reading order of the Window // is from right to left. func (wb *WindowBase) SetRightToLeftReading(rtl bool) error { return wb.ensureExtendedStyleBits(win.WS_EX_RTLREADING, rtl) } // Screenshot returns an image of the window. func (wb *WindowBase) Screenshot() (*image.RGBA, error) { bmp, err := NewBitmapFromWindow(wb) if err != nil { return nil, err } defer bmp.Dispose() return bmp.ToImage() } // FocusedWindow returns the Window that has the keyboard input focus. func FocusedWindow() Window { return windowFromHandle(win.GetFocus()) } // Focused returns whether the Window has the keyboard input focus. func (wb *WindowBase) Focused() bool { return wb.hWnd == win.GetFocus() } // SetFocus sets the keyboard input focus to the *WindowBase. func (wb *WindowBase) SetFocus() error { if win.SetFocus(wb.hWnd) == 0 { return lastError("SetFocus") } return nil } // FocusedChanged returns an Event that you can attach to for handling focus // change events for the WindowBase. func (wb *WindowBase) FocusedChanged() *Event { return wb.focusedChangedPublisher.Event() } // CreateCanvas creates and returns a *Canvas that can be used to draw // inside the ClientBoundsPixels of the *WindowBase. // // Remember to call the Dispose method on the canvas to release resources, // when you no longer need it. func (wb *WindowBase) CreateCanvas() (*Canvas, error) { return newCanvasFromWindow(wb.window) } func (wb *WindowBase) setTheme(appName string) error { if hr := win.SetWindowTheme(wb.hWnd, syscall.StringToUTF16Ptr(appName), nil); win.FAILED(hr) { return errorFromHRESULT("SetWindowTheme", hr) } return nil } // KeyDown returns a *KeyEvent that you can attach to for handling key down // events for the *WindowBase. func (wb *WindowBase) KeyDown() *KeyEvent { return wb.keyDownPublisher.Event() } // KeyPress returns a *KeyEvent that you can attach to for handling key press // events for the *WindowBase. func (wb *WindowBase) KeyPress() *KeyEvent { return wb.keyPressPublisher.Event() } // KeyUp returns a *KeyEvent that you can attach to for handling key up // events for the *WindowBase. func (wb *WindowBase) KeyUp() *KeyEvent { return wb.keyUpPublisher.Event() } // DropFiles returns a *DropFilesEvent that you can attach to for handling // drop file events for the *WindowBase. func (wb *WindowBase) DropFiles() *DropFilesEvent { return wb.dropFilesPublisher.Event(wb.hWnd) } // MouseDown returns a *MouseEvent that you can attach to for handling // mouse down events for the *WindowBase. func (wb *WindowBase) MouseDown() *MouseEvent { return wb.mouseDownPublisher.Event() } // MouseMove returns a *MouseEvent that you can attach to for handling // mouse move events for the *WindowBase. func (wb *WindowBase) MouseMove() *MouseEvent { return wb.mouseMovePublisher.Event() } // MouseUp returns a *MouseEvent that you can attach to for handling // mouse up events for the *WindowBase. func (wb *WindowBase) MouseUp() *MouseEvent { return wb.mouseUpPublisher.Event() } func (wb *WindowBase) MouseWheel() *MouseEvent { return wb.mouseWheelPublisher.Event() } func (wb *WindowBase) publishMouseEvent(publisher *MouseEventPublisher, msg uint32, wParam, lParam uintptr) { x := int(win.GET_X_LPARAM(lParam)) y := int(win.GET_Y_LPARAM(lParam)) var button MouseButton switch msg { case win.WM_LBUTTONUP: button = LeftButton case win.WM_RBUTTONUP: button = RightButton case win.WM_MBUTTONUP: button = MiddleButton default: button = MouseButton(wParam&win.MK_LBUTTON | wParam&win.MK_RBUTTON | wParam&win.MK_MBUTTON) } publisher.Publish(x, y, button) } func (wb *WindowBase) publishMouseWheelEvent(publisher *MouseEventPublisher, wParam, lParam uintptr) { x := int(win.GET_X_LPARAM(lParam)) y := int(win.GET_Y_LPARAM(lParam)) button := MouseButton(uint32(wParam)) publisher.Publish(x, y, button) } // SizeChanged returns an *Event that you can attach to for handling size // changed events for the *WindowBase. func (wb *WindowBase) SizeChanged() *Event { return wb.sizeChangedPublisher.Event() } // BoundsChanged returns an *Event that you can attach to for handling bounds // changed events for the *WindowBase. func (wb *WindowBase) BoundsChanged() *Event { return wb.boundsChangedPublisher.Event() } // Synchronize enqueues func f to be called some time later by the main // goroutine from inside a message loop. func (wb *WindowBase) Synchronize(f func()) { wb.group.Synchronize(f) win.PostMessage(wb.hWnd, syncMsgId, 0, 0) } // synchronizeLayout causes the given layout computations to be applied // later by the message loop running on the group's thread. // // Any previously queued layout computations that have not yet been applied // will be replaced. func (wb *WindowBase) synchronizeLayout(result *formLayoutResult) { wb.group.synchronizeLayout(result) win.PostMessage(wb.hWnd, syncMsgId, 0, 0) } func (wb *WindowBase) ReadState() (string, error) { settings := App().Settings() if settings == nil { return "", newError("App().Settings() must not be nil") } state, _ := settings.Get(wb.path()) return state, nil } func (wb *WindowBase) WriteState(state string) error { settings := App().Settings() if settings == nil { return newError("App().Settings() must not be nil") } p := wb.path() if strings.HasPrefix(p, "/") || strings.HasSuffix(p, "/") || strings.Contains(p, "//") { return nil } return settings.PutExpiring(p, state) } func windowFromHandle(hwnd win.HWND) Window { if wb := hwnd2WindowBase[hwnd]; wb != nil { return wb.window } return nil } func defaultWndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) (result uintptr) { defer func() { // FIXME: Rework the panicking publisher so that we don't have to // access a private member here. if len(App().panickingPublisher.event.handlers) > 0 { var err error if x := recover(); x != nil { if e, ok := x.(error); ok { err = wrapErrorNoPanic(e) } else { err = newErrorNoPanic(fmt.Sprint(x)) } } if err != nil { App().panickingPublisher.Publish(err) } } }() if msg == notifyIconMessageId { return notifyIconWndProc(hwnd, msg, wParam, lParam) } wi := windowFromHandle(hwnd) if wi == nil { return win.DefWindowProc(hwnd, msg, wParam, lParam) } result = wi.WndProc(hwnd, msg, wParam, lParam) return } type menuer interface { Menu() *Menu } func menuContainsAction(menu *Menu, action *Action) bool { if menu.Actions().Contains(action) { return true } for _, a := range menu.actions.actions { if a.menu != nil && menuContainsAction(a.menu, action) { return true } } return false } func (wb *WindowBase) handleKeyDown(wParam, lParam uintptr) { key := Key(wParam) if uint32(lParam)>>30 == 0 { wb.keyDownPublisher.Publish(key) // Using TranslateAccelerators refused to work, so we handle them // ourselves, at least for now. shortcut := Shortcut{ModifiersDown(), key} if action, ok := shortcut2Action[shortcut]; ok { if action.Visible() && action.Enabled() { window := wb.window if w, ok := window.(Widget); ok { window = ancestor(w) } if m, ok := window.(menuer); ok && menuContainsAction(m.Menu(), action) { action.raiseTriggered() } } } } switch key { case KeyAlt, KeyControl, KeyShift: // nop default: wb.keyPressPublisher.Publish(key) } } func (wb *WindowBase) handleKeyUp(wParam, lParam uintptr) { wb.keyUpPublisher.Publish(Key(wParam)) } func (wb *WindowBase) backgroundEffective() (Brush, Window) { wnd := wb.window bg := wnd.Background() if widget, ok := wb.window.(Widget); ok { for bg == nullBrushSingleton && widget != nil { if hwndParent := win.GetParent(widget.Handle()); hwndParent != 0 { if parent := windowFromHandle(hwndParent); parent != nil { wnd = parent bg = parent.Background() widget, _ = parent.(Widget) } else { break } } else { break } } } if bg != nil { if pwb, ok := bg.(perWindowBrush); ok { bg = pwb.delegateForWindow(wnd.AsWindowBase()) } } return bg, wnd } func (wb *WindowBase) prepareDCForBackground(hdc win.HDC, hwnd win.HWND, brushWnd Window) { if _, ok := brushWnd.(Container); ok { win.SetBkMode(hdc, win.TRANSPARENT) } var bgRC win.RECT win.GetWindowRect(brushWnd.Handle(), &bgRC) var rc win.RECT win.GetWindowRect(hwnd, &rc) win.SetBrushOrgEx(hdc, bgRC.Left-rc.Left, bgRC.Top-rc.Top, nil) } func (wb *WindowBase) handleWMCTLCOLOR(wParam, lParam uintptr) uintptr { hwnd := win.HWND(lParam) hdc := win.HDC(wParam) type TextColorer interface { TextColor() Color } wnd := windowFromHandle(hwnd) if wnd == nil { switch windowFromHandle(win.GetParent(hwnd)).(type) { case *ComboBox: // nop return 0 } wnd = wb } else if tc, ok := wnd.(TextColorer); ok { color := tc.TextColor() if color == 0 { color = Color(win.GetSysColor(win.COLOR_WINDOWTEXT)) } win.SetTextColor(hdc, win.COLORREF(color)) } if bg, wnd := wnd.AsWindowBase().backgroundEffective(); bg != nil { wb.prepareDCForBackground(hdc, hwnd, wnd) type Colorer interface { Color() Color } if c, ok := bg.(Colorer); ok { win.SetBkColor(hdc, win.COLORREF(c.Color())) } return uintptr(bg.handle()) } switch wnd.(type) { case *LineEdit, *numberLineEdit, *TextEdit: type ReadOnlyer interface { ReadOnly() bool } var sysColor int if ro, ok := wnd.(ReadOnlyer); ok && ro.ReadOnly() { sysColor = win.COLOR_BTNFACE } else { sysColor = win.COLOR_WINDOW } win.SetBkColor(hdc, win.COLORREF(win.GetSysColor(sysColor))) return uintptr(win.GetSysColorBrush(sysColor)) } return 0 } // WndProc is the window procedure of the window. // // When implementing your own WndProc to add or modify behavior, call the // WndProc of the embedded window for messages you don't handle yourself. func (wb *WindowBase) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { window := windowFromHandle(hwnd) switch msg { case win.WM_ERASEBKGND: if _, ok := window.(Widget); !ok { return 0 } bg, wnd := wb.backgroundEffective() if bg == nil { break } hdc := win.HDC(wParam) canvas, err := newCanvasFromHDC(hdc) if err != nil { break } defer canvas.Dispose() wb.prepareDCForBackground(hdc, hwnd, wnd) if err := canvas.FillRectanglePixels(bg, wb.ClientBoundsPixels()); err != nil { break } return 1 case win.WM_HSCROLL, win.WM_VSCROLL: if window := windowFromHandle(win.HWND(lParam)); window != nil { // The window that sent the notification shall handle it itself. return window.WndProc(hwnd, msg, wParam, lParam) } case win.WM_LBUTTONDOWN, win.WM_MBUTTONDOWN, win.WM_RBUTTONDOWN: if msg == win.WM_LBUTTONDOWN && wb.origWndProcPtr == 0 { // Only call SetCapture if this is no subclassed control. // (Otherwise e.g. WM_COMMAND(BN_CLICKED) would no longer // be generated for PushButton.) win.SetCapture(wb.hWnd) } wb.publishMouseEvent(&wb.mouseDownPublisher, msg, wParam, lParam) case win.WM_LBUTTONUP, win.WM_MBUTTONUP, win.WM_RBUTTONUP: if msg == win.WM_LBUTTONUP && wb.origWndProcPtr == 0 { // See WM_LBUTTONDOWN for why we require origWndProcPtr == 0 here. if !win.ReleaseCapture() { lastError("ReleaseCapture") } } wb.publishMouseEvent(&wb.mouseUpPublisher, msg, wParam, lParam) case win.WM_MOUSEMOVE: wb.publishMouseEvent(&wb.mouseMovePublisher, msg, wParam, lParam) case win.WM_MOUSEWHEEL: wb.publishMouseWheelEvent(&wb.mouseWheelPublisher, wParam, lParam) case win.WM_SETFOCUS, win.WM_KILLFOCUS: switch wnd := wb.window.(type) { // case *splitterHandle: // nop case Widget: parent := wnd.Parent() if parent == nil { hwndParent := win.GetParent(wnd.Handle()) for parent == nil && hwndParent != 0 { hwndParent = win.GetParent(hwndParent) if wnd := windowFromHandle(hwndParent); wnd != nil { parent, _ = wnd.(Container) } } } if wb.Form() == wb.group.ActiveForm() { wnd.AsWidgetBase().invalidateBorderInParent() } } wb.focusedChangedPublisher.Publish() case win.WM_SETCURSOR: if wb.cursor != nil { win.SetCursor(wb.cursor.handle()) return 0 } case win.WM_CONTEXTMENU: sourceWindow := windowFromHandle(win.HWND(wParam)) if sourceWindow == nil { break } contextMenu := sourceWindow.ContextMenu() var handle win.HWND if widget, ok := sourceWindow.(Widget); ok { if form := ancestor(widget); form != nil { handle = form.Handle() } } if handle == 0 { handle = sourceWindow.Handle() } if contextMenu != nil { x := win.GET_X_LPARAM(lParam) y := win.GET_Y_LPARAM(lParam) if x == -1 && y == -1 { pt := sourceWindow.ContextMenuLocation() x = int32(pt.X) y = int32(pt.Y) } contextMenu.updateItemsWithImageForWindow(wb.window) win.TrackPopupMenuEx( contextMenu.hMenu, win.TPM_NOANIMATION, x, y, handle, nil) return 0 } case win.WM_KEYDOWN: wb.handleKeyDown(wParam, lParam) case win.WM_KEYUP: wb.handleKeyUp(wParam, lParam) case win.WM_DROPFILES: wb.dropFilesPublisher.Publish(win.HDROP(wParam)) case win.WM_WINDOWPOSCHANGED: wp := (*win.WINDOWPOS)(unsafe.Pointer(lParam)) if wp.Flags&win.SWP_NOMOVE != 0 && wp.Flags&win.SWP_NOSIZE != 0 { break } if wp.Flags&win.SWP_NOSIZE == 0 { if widget, ok := wb.window.(Widget); ok { wb := widget.AsWidgetBase() wb.geometry.Size = wb.window.SizePixels() wb.geometry.ClientSize = Size{int(wp.Cx), int(wp.Cy)} wb.invalidateBorderInParent() } wb.sizeChangedPublisher.Publish() } wb.boundsChangedPublisher.Publish() if nws, ok := wb.window.(interface{ NeedsWmSize() bool }); !ok || !nws.NeedsWmSize() { return 0 } case win.WM_THEMECHANGED: wb.window.(ApplySysColorser).ApplySysColors() case win.WM_DESTROY: if wb.origWndProcPtr != 0 { // As we subclass all windows of system classes, we prevented the // clean-up code in the WM_NCDESTROY handlers of some windows from // being called. To fix this, we restore the original window // procedure here. win.SetWindowLongPtr(wb.hWnd, win.GWLP_WNDPROC, wb.origWndProcPtr) } delete(hwnd2WindowBase, hwnd) wb.window.Dispose() wb.hWnd = 0 } if window != nil { if wndProc := window.AsWindowBase().origWndProcPtr; wndProc != 0 { return win.CallWindowProc(wndProc, hwnd, msg, wParam, lParam) } } return win.DefWindowProc(hwnd, msg, wParam, lParam) } ================================================ FILE: windowgroup.go ================================================ // Copyright 2019 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 ( "sync" "unsafe" "github.com/lxn/win" ) // The global window group manager instance. var wgm windowGroupManager // windowGroupManager manages window groups for each thread with one or // more windows. type windowGroupManager struct { mutex sync.RWMutex groups map[uint32]*WindowGroup } // Group returns a window group for the given thread ID, if one exists. // If a group does not already exist it returns nil. func (m *windowGroupManager) Group(threadID uint32) *WindowGroup { m.mutex.RLock() defer m.mutex.RUnlock() if m.groups == nil { return nil } return m.groups[threadID] } // CreateGroup returns a window group for the given thread ID. If one does // not already exist, it will be created. // // The group will have its counter incremented as a result of this call. // It is the caller's responsibility to call Done when finished with the // group. func (m *windowGroupManager) CreateGroup(threadID uint32) *WindowGroup { // Fast path with read lock m.mutex.RLock() if m.groups != nil { if group := m.groups[threadID]; group != nil { m.mutex.RUnlock() group.Add(1) return group } } m.mutex.RUnlock() // Slow path with write lock m.mutex.Lock() if m.groups == nil { m.groups = make(map[uint32]*WindowGroup) } else { if group := m.groups[threadID]; group != nil { // Another caller raced with our lock and beat us m.mutex.Unlock() group.Add(1) return group } } group := newWindowGroup(threadID, m.removeGroup) group.Add(1) m.groups[threadID] = group m.mutex.Unlock() return group } // removeGroup is called by window groups to remove themselves from // the manager. func (m *windowGroupManager) removeGroup(threadID uint32) { m.mutex.Lock() delete(m.groups, threadID) m.mutex.Unlock() } // WindowGroup holds data common to windows that share a thread. // // Each WindowGroup keeps track of the number of references to // the group. When the number of references reaches zero, the // group is disposed of. type WindowGroup struct { refs int // Tracks the number of windows that rely on this group ignored int // Tracks the number of refs created by the group itself threadID uint32 completion func(uint32) // Used to tell the window group manager to remove this group removed bool // Has this group been removed from its manager? (used for race detection) toolTip *ToolTip activeForm Form oleInit bool accPropServices *win.IAccPropServices syncMutex sync.Mutex syncFuncs []func() // Functions queued to run on the group's thread layoutResultsByForm map[Form]*formLayoutResult // Layout computations queued for application on the group's thread } // newWindowGroup returns a new window group for the given thread ID. // // The completion function will be called when the group is disposed of. func newWindowGroup(threadID uint32, completion func(uint32)) *WindowGroup { hr := win.OleInitialize() return &WindowGroup{ threadID: threadID, completion: completion, oleInit: hr == win.S_OK || hr == win.S_FALSE, layoutResultsByForm: make(map[Form]*formLayoutResult), } } // ThreadID identifies the thread that the group is affiliated with. func (g *WindowGroup) ThreadID() uint32 { return g.threadID } // Refs returns the current number of references to the group. func (g *WindowGroup) Refs() int { return g.refs } // AccessibilityServices returns an instance of CLSID_AccPropServices class. func (g *WindowGroup) accessibilityServices() *win.IAccPropServices { if g.accPropServices != nil { return g.accPropServices } var accPropServices *win.IAccPropServices hr := win.CoCreateInstance(&win.CLSID_AccPropServices, nil, win.CLSCTX_ALL, &win.IID_IAccPropServices, (*unsafe.Pointer)(unsafe.Pointer(&accPropServices))) if win.FAILED(hr) { return nil } g.accPropServices = accPropServices return accPropServices } // accPropIds is a static list of accessibility properties user (may) set for a window // and we should clear when the window is disposed. var accPropIds = []win.MSAAPROPID{ win.PROPID_ACC_DEFAULTACTION, win.PROPID_ACC_DESCRIPTION, win.PROPID_ACC_HELP, win.PROPID_ACC_KEYBOARDSHORTCUT, win.PROPID_ACC_NAME, win.PROPID_ACC_ROLE, win.PROPID_ACC_ROLEMAP, win.PROPID_ACC_STATE, win.PROPID_ACC_STATEMAP, win.PROPID_ACC_VALUEMAP, } // accClearHwndProps clears all window properties for Dynamic Annotation to release resources. func (g *WindowGroup) accClearHwndProps(hwnd win.HWND) { if g.accPropServices != nil { g.accPropServices.ClearHwndProps(hwnd, win.OBJID_CLIENT, win.CHILDID_SELF, accPropIds) } } // Add changes the group's reference counter by delta, which may be negative. // // If the reference counter becomes zero the group will be disposed of. // // If the reference counter goes negative Add will panic. func (g *WindowGroup) Add(delta int) { if g.removed { panic("walk: add() called on a WindowGroup that has been removed from its manager") } g.refs += delta if g.refs < 0 { panic("walk: negative WindowGroup refs counter") } if g.refs-g.ignored == 0 { g.dispose() } } // Done decrements the group's reference counter by one. func (g *WindowGroup) Done() { g.Add(-1) } // Synchronize adds f to the group's function queue, to be executed // by the message loop running on the the group's thread. // // Synchronize can be called from any thread. func (g *WindowGroup) Synchronize(f func()) { g.syncMutex.Lock() defer g.syncMutex.Unlock() g.syncFuncs = append(g.syncFuncs, f) } // synchronizeLayout causes the given layout computations to be applied // later by the message loop running on the group's thread. // // Any previously queued layout computations for the affected form that // have not yet been applied will be replaced. // // synchronizeLayout can be called from any thread. func (g *WindowGroup) synchronizeLayout(result *formLayoutResult) { g.syncMutex.Lock() g.layoutResultsByForm[result.form] = result g.syncMutex.Unlock() } // RunSynchronized runs all of the function calls queued by Synchronize // and applies any layout changes queued by synchronizeLayout. // // RunSynchronized must be called by the group's thread. func (g *WindowGroup) RunSynchronized() { // Clear the list of callbacks first to avoid deadlock // if a callback itself calls Synchronize()... g.syncMutex.Lock() funcs := g.syncFuncs var results []*formLayoutResult for _, result := range g.layoutResultsByForm { results = append(results, result) delete(g.layoutResultsByForm, result.form) } g.syncFuncs = nil g.syncMutex.Unlock() for _, result := range results { applyLayoutResults(result.results, result.stopwatch) } for _, f := range funcs { f() } } // ToolTip returns the tool tip control for the group, if one exists. func (g *WindowGroup) ToolTip() *ToolTip { return g.toolTip } // CreateToolTip returns a tool tip control for the group. // // If a control has not already been prepared for the group one will be // created. func (g *WindowGroup) CreateToolTip() (*ToolTip, error) { if g.toolTip != nil { return g.toolTip, nil } tt, err := NewToolTip() // This must not call group.ToolTip() if err != nil { return nil, err } g.toolTip = tt // At this point the ToolTip has already added a reference for itself // to the group as part of the ToolTip's InitWindow process. We don't // want it to count toward the group's liveness, however, because it // would keep the group from cleaning up after itself. // // To solve this problem we also keep track of the number of // references that each group should ignore. The ignored references // are subtracted from the total number of references when evaluating // liveness. The expectation is that ignored references will be // removed as part of the group's disposal process. g.ignore(1) return tt, nil } // ActiveForm returns the currently active form for the group. If no // form is active it returns nil. func (g *WindowGroup) ActiveForm() Form { return g.activeForm } // SetActiveForm updates the currently active form for the group. func (g *WindowGroup) SetActiveForm(form Form) { g.activeForm = form } // ignore changes the number of references that the group will ignore. // // ignore is used internally by WindowGroup to keep track of the number // of references created by the group itself. When finished with a group, // call Done() instead. func (g *WindowGroup) ignore(delta int) { if g.removed { panic("walk: ignore() called on a WindowGroup that has been removed from its manager") } g.ignored += delta if g.ignored < 0 { panic("walk: negative WindowGroup ignored counter") } if g.refs-g.ignored == 0 { g.dispose() } } // dispose releases any resources consumed by the group. func (g *WindowGroup) dispose() { if g.accPropServices != nil { g.accPropServices.Release() g.accPropServices = nil } if g.oleInit { win.OleUninitialize() g.oleInit = false } if g.toolTip != nil { g.toolTip.Dispose() g.toolTip = nil } g.removed = true // race detection only g.completion(g.threadID) }