summaryrefslogtreecommitdiff
path: root/prefsize_windows.go
blob: 9598cb0ca677d19ab0563afa4fe84af56f5cb222 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// 24 february 2014
package ui

import (
//	"syscall"
	"unsafe"
)

// For Windows, Microsoft just hands you a list of preferred control sizes as part of the MSDN documentation and tells you to roll with it.
// These sizes are given in "dialog units", which are independent of the font in use.
// We need to convert these into standard pixels, which requires we get the device context of the OS window.
// References:
// - http://msdn.microsoft.com/en-us/library/windows/desktop/aa511279.aspx#controlsizing for control sizes
// - http://msdn.microsoft.com/en-us/library/ms645502%28VS.85%29.aspx - the calculation needed
// - http://support.microsoft.com/kb/125681 - to get the base X and Y

// As we are left with incomplete data, an arbitrary size will be chosen
const (
	defaultWidth = 100		// 2 * preferred width of buttons
)

type dlgunits struct {
	width	int
	height	int
	longest	bool		// TODO actually use this
}

var stdDlgSizes = [nctypes]dlgunits{
	c_button:		dlgunits{
		width:	50,
		height:	14,
	},
	c_checkbox:	dlgunits{
		// widtdh is not defined here so assume longest
		longest:	true,
		height:	10,
	},
	c_combobox:	dlgunits{
		longest:	true,
		height:	14,
	},
	c_lineedit:	dlgunits{
		longest:	true,
		height:	14,
	},
	c_label:		dlgunits{
		longest:	true,
		height:	8,
	},
	c_listbox:		dlgunits{
		longest:	true,
		// height is not clearly defined here ("an integral number of items (3 items minimum)") so just use a three-line edit control
		height:	14 + 10 + 10,
	},
}

var (
	_getTextMetrics = gdi32.NewProc("GetTextMetricsW")
	_getWindowDC = user32.NewProc("GetWindowDC")
	_releaseDC = user32.NewProc("ReleaseDC")
)

func (s *sysData) preferredSize() (width int, height int) {
	var dc _HANDLE
	var tm _TEXTMETRICS
	var baseX, baseY int

	r1, _, err := _getWindowDC.Call(uintptr(s.hwnd))
	if r1 == 0 {		// failure
		panic(err)		// TODO return it instead
	}
	dc = _HANDLE(r1)
	r1, _, err = _getTextMetrics.Call(
		uintptr(dc),
		uintptr(unsafe.Pointer(&tm)))
	if r1 == 0 {		// failure
		panic(err)		// TODO return it instead
	}
	baseX = int(tm.tmAveCharWidth)		// TODO not optimal; third reference has better way
	baseY = int(tm.tmHeight)
	r1, _, err = _releaseDC.Call(
		uintptr(s.hwnd),
		uintptr(dc))
	if r1 == 0 {		// failure
		panic(err)		// TODO return it instead
	}

	// now that we have the conversion factors...
	width = stdDlgSizes[s.ctype].width
	if width == 0 {
		width = defaultWidth
	}
	height = stdDlgSizes[s.ctype].height
	width = muldiv(width, baseX, 4)		// equivalent to right of rect
	height = muldiv(height, baseY, 8)		// equivalent to bottom of rect
	return width, height
}

// attempts to mimic the behavior of kernel32.MulDiv()
// caling it directly would be better (TODO)
// alternatively TODO make sure the rounding is correct
func muldiv(ma int, mb int, div int) int {
	xa := int64(ma) * int64(mb)
	xa /= int64(div)
	return int(xa)
}

type _TEXTMETRICS struct {
	tmHeight				int32
	tmAscent				int32
	tmDescent			int32
	tmInternalLeading		int32
	tmExternalLeading		int32
	tmAveCharWidth		int32
	tmMaxCharWidth		int32
	tmWeight				int32
	tmOverhang			int32
	tmDigitizedAspectX		int32
	tmDigitizedAspectY		int32
	tmFirstChar			uint16
	tmLastChar			uint16
	tmDefaultChar			uint16
	tmBreakChar			uint16
	tmItalic				byte
	tmUnderlined			byte
	tmStruckOut			byte
	tmPitchAndFamily		byte
	tmCharSet			byte
}