Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add combobox to Windows #61

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 93 additions & 1 deletion src/nigui.nim
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,11 @@ type
TextBox* = ref object of ControlImpl
fEditable: bool

ComboBox* = ref object of ControlImpl
fOptions: seq[string]
fStyle: string
fEnabled: bool

# Platform-specific extension:
when useWindows(): include "nigui/private/windows/platform_types2"
when useGtk(): include "nigui/private/gtk3/platform_types2"
Expand Down Expand Up @@ -937,6 +942,40 @@ method wrap*(textArea: TextArea): bool
method `wrap=`*(textArea: TextArea, wrap: bool)



# ----------------------------------------------------------------------------------------
# ComboBox
# ----------------------------------------------------------------------------------------

proc newComboBox*(options = @[""], style = "simple"): ComboBox

proc init*(comboBox: ComboBox)
proc init*(comboBox: NativeComboBox)

method options*(comboBox: ComboBox): seq[string]
method `options=`*(comboBox: ComboBox, options: seq[string])

method style*(comboBox: ComboBox): string
method `style=`*(comboBox: ComboBox, style: string)

method enabled*(comboBox: ComboBox): bool
method `enabled=`*(comboBox: ComboBox, enabled: bool)

method getOptionCount*(comboBox: ComboBox): int

method addOption*(comboBox: ComboBox, option: string)

method deleteOption*(comboBox: ComboBox, index: uint)

method selectOption*(comboBox: ComboBox, index: uint)

method findOptionIndex*(comboBox: ComboBox, search: string): int

method getSelectedIndex*(comboBox: ComboBox): int

method getSelectedValue*(comboBox: ComboBox): string


# ----------------------------------------------------------------------------------------
# Private Procedures Predeclaration
# ----------------------------------------------------------------------------------------
Expand Down Expand Up @@ -2485,9 +2524,62 @@ method `wrap=`(textArea: TextArea, wrap: bool) =
# should be extended by NativeTextArea


# ----------------------------------------------------------------------------------------
# ComboBox
# ----------------------------------------------------------------------------------------

proc newComboBox(options = @[""], style = "simple"): ComboBox =
result = new NativeComboBox
# need to pass desired style (type) of combobox to NativeComboBox... is there a better way?
result.style = style
result.NativeComboBox.init()
result.options = options

proc init(comboBox: ComboBox) =
comboBox.ControlImpl.init()
comboBox.fOnClick = nil
comboBox.fWidthMode = WidthMode_Expand
comboBox.fHeightMode = HeightMode_Auto
comboBox.minWidth = 20.scaleToDpi
comboBox.minHeight = 20.scaleToDpi
comboBox.enabled = true

method options(comboBox: ComboBox): seq[string] = comboBox.fOptions
method `options=`(comboBox: ComboBox, options: seq[string]) = comboBox.fOptions = options

method style(comboBox: ComboBox): string = comboBox.fStyle
method `style=`(comboBox: ComboBox, style: string) = comboBox.fStyle = style

method getOptionCount*(comboBox: ComboBox): int = discard
# has to be implemented by NativeComboBox

method addOption(comboBox: ComboBox, option: string) = discard
# has to be implemented by NativeComboBox

method deleteOption(comboBox: ComboBox, index: uint) = discard
# has to be implemented by NativeComboBox

method selectOption*(comboBox: ComboBox, index: uint) = discard
# has to be implemented by NativeComboBox

method findOptionIndex(comboBox: ComboBox, search: string): int = discard
# has to be implemented by NativeComboBox

method getSelectedIndex(comboBox: ComboBox): int = discard
# has to be implemented by NativeComboBox

method getSelectedValue(comboBox: ComboBox): string = discard
# has to be implemented by NativeComboBox

method enabled(comboBox: ComboBox): bool = comboBox.fEnabled

method `enabled=`(comboBox: ComboBox, enabled: bool) = discard
# has to be implemented by NativeComboBox


# ----------------------------------------------------------------------------------------
# Platform-specific implementation
# ----------------------------------------------------------------------------------------

when useWindows(): include "nigui/private/windows/platform_impl"
when useGtk(): include "nigui/private/gtk3/platform_impl"
when useGtk(): include "nigui/private/gtk3/platform_impl"
69 changes: 66 additions & 3 deletions src/nigui/private/windows/platform_impl.nim
Original file line number Diff line number Diff line change
Expand Up @@ -193,9 +193,11 @@ proc pCommonWndProc(hWnd: pointer, uMsg: int32, wParam, lParam: pointer): pointe
control.handleTextChangeEvent(evt)
of WM_CTLCOLORSTATIC, WM_CTLCOLOREDIT:
let control = cast[Control](pGetWindowLongPtr(lParam, GWLP_USERDATA))
discard SetTextColor(wParam, control.textColor.pColorToRGB32())
discard SetBkColor(wParam, control.backgroundColor.pColorToRGB32())
return CreateSolidBrush(control.backgroundColor.pColorToRGB32)
# Without nil check, this cannot compile due to combobox addition? Why?
if control != nil:
discard SetTextColor(wParam, control.textColor.pColorToRGB32())
discard SetBkColor(wParam, control.backgroundColor.pColorToRGB32())
return CreateSolidBrush(control.backgroundColor.pColorToRGB32)
else:
discard
result = DefWindowProcA(hWnd, uMsg, wParam, lParam)
Expand Down Expand Up @@ -1529,3 +1531,64 @@ method `wrap=`(textArea: NativeTextArea, wrap: bool) =
# It seems that this is not possible.
# Word wrap depends on whether dwStyle contains WS_HSCROLL at window creation.
# Changing the style later has not the wanted effect.


# ----------------------------------------------------------------------------------------
# ComboBox
# ----------------------------------------------------------------------------------------

proc init(comboBox: NativeComboBox) =
var dwStyle: int32
# determine which "style" of combobox to display.. is there a better way to do this?
case comboBox.style:
of "dropdown":
dwStyle = WS_CHILD or CBS_DROPDOWN or WS_VSCROLL
of "dropdownlist":
dwStyle = WS_CHILD or CBS_DROPDOWNLIST or WS_VSCROLL
else:
dwStyle = WS_CHILD or CBS_SIMPLE or WS_VSCROLL
comboBox.fHandle = pCreateWindowExWithUserdata("COMBOBOX", dwStyle, 0, pDefaultParentWindow, cast[pointer](comboBox))
comboBox.ComboBox.init()

# TODO: Find correct(?) way to calculate optimum height on combobox
method naturalHeight(comboBox: ComboBox): int =
case comboBox.style:
of "dropdown", "dropdownlist":
# just size of dropdown window
# (line height + 8 padding/margin)
return comboBox.getTextLineHeight() + 8
else:
# size of combolist and dropdown section - not sure if this is calculated correctly.
# (line height + 8 padding/margin) + (number of options * (line height + 2 margin)) ??
return (comboBox.getTextLineHeight() + 8) + (comboBox.getOptionCount() * (comboBox.getTextLineHeight() + 2))

method `options=`(comboBox: NativeComboBox, options: seq[string]) =
for option in options:
discard SendMessageW(comboBox.fHandle, CB_ADDSTRING, nil, cast[pointer](newWideCString(option)))

method getOptionCount(comboBox: NativeComboBox): int =
result = cast[int](SendMessageW(comboBox.fHandle, CB_GETCOUNT, nil, nil))

method addOption(comboBox: NativeComboBox, option: string) =
discard SendMessageW(comboBox.fHandle, CB_ADDSTRING, nil, cast[pointer](newWideCString(option)))

method deleteOption(comboBox: NativeComboBox, index: uint) =
discard SendMessageA(comboBox.fHandle, CB_DELETESTRING, cast[pointer](index), nil)

method selectOption(comboBox: NativeComboBox, index: uint) =
discard SendMessageA(comboBox.fHandle, CB_SETCURSEL, cast[pointer](index), nil)

method findOptionIndex(comboBox: NativeComboBox, search: string): int =
# wParam value -1 searches entire contents of ComboBox, result of -1 means option doesn't exist (not found)
var searchString: cstring = search
result = cast[int](SendMessageA(comboBox.fHandle, CB_FINDSTRINGEXACT, cast[pointer](-1), cast[pointer](searchString)))

method getSelectedIndex(comboBox: NativeComboBox): int =
result = cast[int]((SendMessageA(comboBox.fHandle, CB_GETCURSEL, cast[pointer](0), cast[pointer](0))))

method getSelectedValue(comboBox: NativeComboBox): string =
result = pGetWindowText(comboBox.fHandle)

method `enabled=`(comboBox: NativeComboBox, enabled: bool) =
comboBox.fEnabled = enabled
discard EnableWindow(comboBox.fHandle, enabled)
2 changes: 2 additions & 0 deletions src/nigui/private/windows/platform_types2.nim
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ type
NativeLabel* = ref object of Label

NativeTextBox* = ref object of TextBox

NativeComboBox* = ref object of ComboBox
17 changes: 15 additions & 2 deletions src/nigui/private/windows/windows.nim
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,20 @@ const
BN_CLICKED* = 0
BM_SETSTYLE* = 244
BM_SETIMAGE* = 247
BM_GETCHECK* = 0x00F0
BS_DEFPUSHBUTTON* = 0x00000001
BS_GROUPBOX* = 0x00000007
CB_ERR* = -1
CB_ADDSTRING* = 0x0143
CB_DELETESTRING* = 0x0144
CB_FINDSTRINGEXACT* = 0x0158
CB_GETCOUNT* = 0x0146
CB_GETCURSEL* = 0x0147
CB_SETCURSEL* = 0x014E
CB_SETITEMHEIGHT* = 0x0153
CBS_SIMPLE* = 0x0001
CBS_DROPDOWN* = 0x0002
CBS_DROPDOWNLIST* = 0x0003
CF_TEXT* = 1
COLOR_BTNFACE* = 15
COLOR_WINDOW* = 5
Expand Down Expand Up @@ -166,6 +178,7 @@ const
WS_THICKFRAME* = 0x00040000
WS_VSCROLL* = 0x00200000
WS_EX_CONTROLPARENT* = 0x00010000
WS_VISIBLE* = 0x10000000
# DT_CALCRECT* = 1024
# OBJ_FONT* = 6
# SM_XVIRTUALSCREEN* = 76
Expand Down Expand Up @@ -406,7 +419,7 @@ proc GetClientRect*(wnd: pointer, lpRect: var Rect): bool {.importc: "GetClientR
proc BeginPaint*(hWnd: pointer, lpPaint: var PaintStruct): pointer {.importc: "BeginPaint", libUser32.}
proc EndPaint*(hWnd: pointer, lpPaint: var PaintStruct): bool {.importc: "EndPaint", libUser32.}
proc SendMessageA*(hWnd: pointer, msg: int32, wParam, lParam: pointer): pointer {.importc: "SendMessageA", libUser32.}
# proc SendMessageW*(hWnd: pointer, msg: int32, wParam, lParam: pointer): pointer {.importc: "SendMessageW", libUser32.}
proc SendMessageW*(hWnd: pointer, msg: int32, wParam, lParam: pointer): pointer {.importc: "SendMessageW", libUser32.}
proc PostMessageA*(hWnd: pointer, msg: int32, wParam, lParam: pointer): pointer {.importc: "PostMessageA", libUser32.}
proc GetSysColor*(nIndex: int32): RGB32 {.importc: "GetSysColor", libUser32.}
proc InvalidateRect*(hWnd: pointer, lpRect: ref Rect, bErase: bool): bool {.importc: "InvalidateRect", libUser32.}
Expand Down Expand Up @@ -543,4 +556,4 @@ proc GetSaveFileNameW*(lpofn: var OpenFileName): bool {.importc: "GetSaveFileNam
# Shcore Procs
# ----------------------------------------------------------------------------------------

type SetProcessDpiAwarenessType* = proc(value: int32): int32 {.gcsafe, stdcall.} # not available on Windows 7
type SetProcessDpiAwarenessType* = proc(value: int32): int32 {.gcsafe, stdcall.} # not available on Windows 7