1 // Written by Christopher E. Miller
2 // See the included license.txt for copyright and license details.
3 
4 
5 ///
6 module dfl.tooltip;
7 
8 
9 private import dfl.internal.dlib, dfl.internal.clib;
10 
11 private import dfl.control, dfl.base, dfl.application, dfl.internal.winapi,
12 	dfl.internal.utf;
13 
14 
15 ///
16 class ToolTip // docmain
17 {
18 	package this(DWORD style)
19 	{
20 		_initCommonControls(ICC_TREEVIEW_CLASSES); // Includes tooltip.
21 		
22 		hwtt = CreateWindowExA(WS_EX_TOPMOST | WS_EX_TOOLWINDOW, _TOOLTIPS_CLASSA.ptr,
23 			"", style, 0, 0, 50, 50, null, null, null, null);
24 		if(!hwtt)
25 			throw new DflException("Unable to create tooltip");
26 	}
27 	
28 	
29 	this()
30 	{
31 		this(cast(DWORD)WS_POPUP);
32 	}
33 	
34 	
35 	~this()
36 	{
37 		removeAll(); // Fixes ref count.
38 		DestroyWindow(hwtt);
39 	}
40 	
41 	
42 	///
43 	final @property HWND handle() // getter
44 	{
45 		return hwtt;
46 	}
47 	
48 	
49 	///
50 	final @property void active(bool byes) // setter
51 	{
52 		SendMessageA(hwtt, TTM_ACTIVATE, byes, 0); // ?
53 		_active = byes;
54 	}
55 	
56 	/// ditto
57 	final @property bool active() // getter
58 	{
59 		return _active;
60 	}
61 	
62 	
63 	///
64 	// Sets autoPopDelay, initialDelay and reshowDelay.
65 	final @property void automaticDelay(DWORD ms) // setter
66 	{
67 		SendMessageA(hwtt, TTM_SETDELAYTIME, TTDT_AUTOMATIC, ms);
68 	}
69 	
70 	/+
71 	/// ditto
72 	final @property DWORD automaticDelay() // getter
73 	{
74 	}
75 	+/
76 	
77 	
78 	///
79 	final @property void autoPopDelay(DWORD ms) // setter
80 	{
81 		SendMessageA(hwtt, TTM_SETDELAYTIME, TTDT_AUTOPOP, ms);
82 	}
83 	
84 	/+
85 	/// ditto
86 	final @property DWORD autoPopDelay() // getter
87 	{
88 	}
89 	+/
90 	
91 	
92 	///
93 	final @property void initialDelay(DWORD ms) // setter
94 	{
95 		SendMessageA(hwtt, TTM_SETDELAYTIME, TTDT_INITIAL, ms);
96 	}
97 	
98 	/+
99 	/// ditto
100 	final @property DWORD initialDelay() // getter
101 	{
102 	}
103 	+/
104 	
105 	
106 	///
107 	final @property void reshowDelay(DWORD ms) // setter
108 	{
109 		SendMessageA(hwtt, TTM_SETDELAYTIME, TTDT_RESHOW, ms);
110 	}
111 	
112 	/+
113 	/// ditto
114 	final @property DWORD reshowDelay() // getter
115 	{
116 	}
117 	+/
118 	
119 	
120 	///
121 	final @property void showAlways(bool byes) // setter
122 	{
123 		LONG wl;
124 		wl = GetWindowLongA(hwtt, GWL_STYLE);
125 		if(byes)
126 		{
127 			if(wl & TTS_ALWAYSTIP)
128 				return;
129 			wl |= TTS_ALWAYSTIP;
130 		}
131 		else
132 		{
133 			if(!(wl & TTS_ALWAYSTIP))
134 				return;
135 			wl &= ~TTS_ALWAYSTIP;
136 		}
137 		SetWindowLongA(hwtt, GWL_STYLE, wl);
138 	}
139 	
140 	/// ditto
141 	final @property bool showAlways() // getter
142 	{
143 		return (GetWindowLongA(hwtt, GWL_STYLE) & TTS_ALWAYSTIP) != 0;
144 	}
145 	
146 	
147 	///
148 	// Remove all tooltip text associated with this instance.
149 	final void removeAll()
150 	{
151 		TOOLINFOA tool;
152 		tool.cbSize = TOOLINFOA.sizeof;
153 		while(SendMessageA(hwtt, TTM_ENUMTOOLSA, 0, cast(LPARAM)&tool))
154 		{
155 			SendMessageA(hwtt, TTM_DELTOOLA, 0, cast(LPARAM)&tool);
156 			Application.refCountDec(cast(void*)this);
157 		}
158 	}
159 	
160 	
161 	///
162 	// WARNING: possible buffer overflow.
163 	final Dstring getToolTip(Control ctrl)
164 	{
165 		Dstring result;
166 		TOOLINFOA tool;
167 		tool.cbSize = TOOLINFOA.sizeof;
168 		tool.uFlags = TTF_IDISHWND;
169 		tool.hwnd = ctrl.handle;
170 		tool.uId = cast(UINT)ctrl.handle;
171 		
172 		if(dfl.internal.utf.useUnicode)
173 		{
174 			tool.lpszText = cast(typeof(tool.lpszText))dfl.internal.clib.malloc((MAX_TIP_TEXT_LENGTH + 1) * wchar.sizeof);
175 			if(!tool.lpszText)
176 				throw new OomException;
177 			scope(exit)
178 				dfl.internal.clib.free(tool.lpszText);
179 			tool.lpszText[0 .. 2] = 0;
180 			SendMessageA(hwtt, TTM_GETTEXTW, 0, cast(LPARAM)&tool);
181 			if(!(cast(wchar*)tool.lpszText)[0])
182 				result = null;
183 			else
184 				result = fromUnicodez(cast(wchar*)tool.lpszText);
185 		}
186 		else
187 		{
188 			tool.lpszText = cast(typeof(tool.lpszText))dfl.internal.clib.malloc(MAX_TIP_TEXT_LENGTH + 1);
189 			if(!tool.lpszText)
190 				throw new OomException;
191 			scope(exit)
192 				dfl.internal.clib.free(tool.lpszText);
193 			tool.lpszText[0] = 0;
194 			SendMessageA(hwtt, TTM_GETTEXTA, 0, cast(LPARAM)&tool);
195 			if(!tool.lpszText[0])
196 				result = null;
197 			else
198 				result = fromAnsiz(tool.lpszText); // Assumes fromAnsiz() copies.
199 		}
200 		return result;
201 	}
202 	
203 	/// ditto
204 	final void setToolTip(Control ctrl, Dstring text)
205 	in
206 	{
207 		try
208 		{
209 			ctrl.createControl();
210 		}
211 		catch(DThrowable o)
212 		{
213 			assert(0); // If -ctrl- is a child, make sure the parent is set before setting tool tip text.
214 			//throw o;
215 		}
216 	}
217 	body
218 	{
219 		TOOLINFOA tool;
220 		tool.cbSize = TOOLINFOA.sizeof;
221 		tool.uFlags = TTF_IDISHWND;
222 		tool.hwnd = ctrl.handle;
223 		tool.uId = cast(UINT)ctrl.handle;
224 		
225 		if(!text.length)
226 		{
227 			if(SendMessageA(hwtt, TTM_GETTOOLINFOA, 0, cast(LPARAM)&tool))
228 			{
229 				// Remove.
230 				
231 				SendMessageA(hwtt, TTM_DELTOOLA, 0, cast(LPARAM)&tool);
232 				
233 				Application.refCountDec(cast(void*)this);
234 			}
235 			return;
236 		}
237 		
238 		// Hack to help prevent getToolTip() overflow.
239 		if(text.length > MAX_TIP_TEXT_LENGTH)
240 			text = text[0 .. MAX_TIP_TEXT_LENGTH];
241 		
242 		if(SendMessageA(hwtt, TTM_GETTOOLINFOA, 0, cast(LPARAM)&tool))
243 		{
244 			// Update.
245 			
246 			if(dfl.internal.utf.useUnicode)
247 			{
248 				tool.lpszText = cast(typeof(tool.lpszText))toUnicodez(text);
249 				SendMessageA(hwtt, TTM_UPDATETIPTEXTW, 0, cast(LPARAM)&tool);
250 			}
251 			else
252 			{
253 				tool.lpszText = cast(typeof(tool.lpszText))unsafeAnsiz(text);
254 				SendMessageA(hwtt, TTM_UPDATETIPTEXTA, 0, cast(LPARAM)&tool);
255 			}
256 		}
257 		else
258 		{
259 			// Add.
260 			
261 			/+
262 			// TOOLINFOA.rect is ignored if TTF_IDISHWND.
263 			tool.rect.left = 0;
264 			tool.rect.top = 0;
265 			tool.rect.right = ctrl.clientSize.width;
266 			tool.rect.bottom = ctrl.clientSize.height;
267 			+/
268 			tool.uFlags |= TTF_SUBCLASS; // Not a good idea ?
269 			LRESULT lr;
270 			if(dfl.internal.utf.useUnicode)
271 			{
272 				tool.lpszText = cast(typeof(tool.lpszText))toUnicodez(text);
273 				lr = SendMessageA(hwtt, TTM_ADDTOOLW, 0, cast(LPARAM)&tool);
274 			}
275 			else
276 			{
277 				tool.lpszText = cast(typeof(tool.lpszText))unsafeAnsiz(text);
278 				lr = SendMessageA(hwtt, TTM_ADDTOOLA, 0, cast(LPARAM)&tool);
279 			}
280 			
281 			if(lr)
282 				Application.refCountInc(cast(void*)this);
283 		}
284 	}
285 	
286 	
287 	private:
288 	enum _TOOLTIPS_CLASSA = "tooltips_class32";
289 	enum size_t MAX_TIP_TEXT_LENGTH = 2045;
290 	
291 	HWND hwtt; // Tooltip control handle.
292 	bool _active = true;
293 }
294