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