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