]>
Commit | Line | Data |
---|---|---|
df62e023 | 1 | /* |
0a539fe4 | 2 | * Copyright (c) 2001, 2002, 2003 Gary R. Van Sickle. |
df62e023 RC |
3 | * |
4 | * This program is free software; you can redistribute it and/or modify | |
5 | * it under the terms of the GNU General Public License as published by | |
6 | * the Free Software Foundation; either version 2 of the License, or | |
7 | * (at your option) any later version. | |
8 | * | |
9 | * A copy of the GNU General Public License can be found at | |
10 | * http://www.gnu.org/ | |
11 | * | |
12 | * Written by Gary R. Van Sickle <g.r.vansickle@worldnet.att.net> | |
13 | * | |
14 | */ | |
15 | ||
16 | // This is the implementation of the Window class. It serves both as a window class | |
17 | // in its own right and as a base class for other window-like classes (e.g. PropertyPage, | |
18 | // PropSheet). | |
19 | ||
46d04e97 | 20 | #include "win32.h" |
df62e023 | 21 | #include "window.h" |
0a539fe4 | 22 | #include "RECTWrapper.h" |
d943aa72 MB |
23 | #include "msg.h" |
24 | #include "resource.h" | |
df62e023 RC |
25 | |
26 | ATOM Window::WindowClassAtom = 0; | |
27 | HINSTANCE Window::AppInstance = NULL; | |
28 | ||
df62e023 RC |
29 | Window::Window () |
30 | { | |
31 | WindowHandle = NULL; | |
32 | Parent = NULL; | |
37f49caf | 33 | TooltipHandle = NULL; |
df62e023 RC |
34 | } |
35 | ||
36 | Window::~Window () | |
37 | { | |
b7301c43 | 38 | // Delete any fonts we created. |
daab12c7 BD |
39 | for (unsigned int i = 0; i < Fonts.size (); i++) |
40 | DeleteObject (Fonts[i]); | |
b7301c43 | 41 | |
37f49caf BD |
42 | // shut down the tooltip control, if activated |
43 | if (TooltipHandle) | |
44 | DestroyWindow (TooltipHandle); | |
45 | ||
df62e023 RC |
46 | // FIXME: Maybe do some reference counting and do this Unregister |
47 | // when there are no more of us left. Not real critical unless | |
48 | // we're in a DLL which we're not right now. | |
49 | //UnregisterClass(WindowClassAtom, InstanceHandle); | |
50 | } | |
51 | ||
52 | LRESULT CALLBACK | |
53 | Window::FirstWindowProcReflector (HWND hwnd, UINT uMsg, WPARAM wParam, | |
54 | LPARAM lParam) | |
55 | { | |
63c82708 | 56 | Window *wnd = NULL; |
df62e023 | 57 | |
63c82708 | 58 | if(uMsg == WM_NCCREATE) |
df62e023 | 59 | { |
63c82708 RC |
60 | // This is the first message a window gets (so MSDN says anyway). |
61 | // Take this opportunity to "link" the HWND to the 'this' ptr, steering | |
62 | // messages to the class instance's WindowProc(). | |
63 | wnd = reinterpret_cast<Window *>(((LPCREATESTRUCT)lParam)->lpCreateParams); | |
df62e023 RC |
64 | |
65 | // Set a backreference to this class instance in the HWND. | |
0a539fe4 | 66 | SetWindowLongPtr (hwnd, GWL_USERDATA, reinterpret_cast<LONG_PTR>(wnd)); |
df62e023 RC |
67 | |
68 | // Set a new WindowProc now that we have the peliminaries done. | |
63c82708 RC |
69 | // We could instead simply do the contents of Window::WindowProcReflector |
70 | // in the 'else' clause below, but this way we eliminate an unnecessary 'if/else' on | |
71 | // every message. Yeah, it's probably not worth the trouble. | |
72 | SetWindowLongPtr (hwnd, GWL_WNDPROC, (LONG_PTR) & Window::WindowProcReflector); | |
693916f8 RC |
73 | // Finally, store the window handle in the class. |
74 | wnd->WindowHandle = hwnd; | |
63c82708 RC |
75 | } |
76 | else | |
d943aa72 MB |
77 | { |
78 | // Should never get here. | |
79 | fatal(NULL, IDS_WINDOW_INIT_BADMSG, uMsg); | |
df62e023 RC |
80 | } |
81 | ||
63c82708 | 82 | return wnd->WindowProc (uMsg, wParam, lParam); |
df62e023 RC |
83 | } |
84 | ||
85 | LRESULT CALLBACK | |
86 | Window::WindowProcReflector (HWND hwnd, UINT uMsg, WPARAM wParam, | |
87 | LPARAM lParam) | |
88 | { | |
89 | Window *This; | |
90 | ||
91 | // Get our this pointer | |
0a539fe4 | 92 | This = reinterpret_cast<Window *>(GetWindowLongPtr (hwnd, GWL_USERDATA)); |
df62e023 RC |
93 | |
94 | return This->WindowProc (uMsg, wParam, lParam); | |
95 | } | |
96 | ||
2f3a74d1 MB |
97 | bool |
98 | Window::Create (Window * parent, DWORD Style) | |
df62e023 RC |
99 | { |
100 | // First register the window class, if we haven't already | |
693916f8 | 101 | if (registerWindowClass () == false) |
df62e023 RC |
102 | { |
103 | // Registration failed | |
104 | return false; | |
105 | } | |
106 | ||
0a539fe4 | 107 | // Save our parent, we'll probably need it eventually. |
df62e023 RC |
108 | Parent = parent; |
109 | ||
110 | // Create the window instance | |
63c82708 RC |
111 | WindowHandle = CreateWindowEx ( |
112 | // Extended Style | |
113 | 0, | |
114 | "MainWindowClass", //MAKEINTATOM(WindowClassAtom), // window class atom (name) | |
df62e023 RC |
115 | "Hello", // no title-bar string yet |
116 | // Style bits | |
117 | Style, | |
118 | // Default positions and size | |
119 | CW_USEDEFAULT, | |
120 | CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, | |
121 | // Parent Window | |
122 | parent == | |
123 | NULL ? (HWND) NULL : parent->GetHWND (), | |
124 | // use class menu | |
125 | (HMENU) NULL, | |
126 | // The application instance | |
63c82708 RC |
127 | GetInstance (), |
128 | // The this ptr, which we'll use to set up the WindowProc reflection. | |
129 | (LPVOID) this); | |
df62e023 RC |
130 | |
131 | if (WindowHandle == NULL) | |
132 | { | |
133 | // Failed | |
134 | return false; | |
135 | } | |
136 | ||
137 | return true; | |
138 | } | |
139 | ||
2f3a74d1 MB |
140 | bool |
141 | Window::registerWindowClass () | |
df62e023 RC |
142 | { |
143 | if (WindowClassAtom == 0) | |
144 | { | |
145 | // We're not registered yet | |
146 | WNDCLASSEX | |
147 | wc; | |
148 | ||
149 | wc.cbSize = sizeof (wc); | |
150 | // Some sensible style defaults | |
151 | wc.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW; | |
152 | // Our default window procedure. This replaces itself | |
153 | // on the first call with the simpler Window::WindowProcReflector(). | |
154 | wc.lpfnWndProc = Window::FirstWindowProcReflector; | |
155 | // No class bytes | |
156 | wc.cbClsExtra = 0; | |
157 | // One pointer to REFLECTION_INFO in the extra window instance bytes | |
158 | wc.cbWndExtra = 4; | |
159 | // The app instance | |
160 | wc.hInstance = GetInstance (); | |
161 | // Use a bunch of system defaults for the GUI elements | |
162 | wc.hIcon = NULL; | |
163 | wc.hIconSm = NULL; | |
164 | wc.hCursor = NULL; | |
165 | wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND + 1); | |
166 | // No menu | |
167 | wc.lpszMenuName = NULL; | |
168 | // We'll get a little crazy here with the class name | |
169 | wc.lpszClassName = "MainWindowClass"; | |
170 | ||
171 | // All set, try to register | |
172 | WindowClassAtom = RegisterClassEx (&wc); | |
173 | ||
174 | if (WindowClassAtom == 0) | |
175 | { | |
176 | // Failed | |
177 | return false; | |
178 | } | |
179 | } | |
180 | ||
181 | // We're registered, or already were before the call, | |
182 | // return success in either case. | |
183 | return true; | |
184 | } | |
185 | ||
186 | void | |
187 | Window::Show (int State) | |
188 | { | |
189 | ::ShowWindow (WindowHandle, State); | |
190 | } | |
191 | ||
63c82708 RC |
192 | RECT |
193 | Window::GetWindowRect() const | |
194 | { | |
195 | RECT retval; | |
196 | ::GetWindowRect(WindowHandle, &retval); | |
197 | return retval; | |
198 | } | |
199 | ||
200 | RECT | |
201 | Window::GetClientRect() const | |
202 | { | |
203 | RECT retval; | |
204 | ::GetClientRect(WindowHandle, &retval); | |
205 | return retval; | |
206 | } | |
207 | ||
208 | bool | |
209 | Window::MoveWindow(long x, long y, long w, long h, bool Repaint) | |
210 | { | |
211 | return ::MoveWindow (WindowHandle, x, y, w, h, Repaint); | |
212 | } | |
213 | ||
0a539fe4 MB |
214 | bool |
215 | Window::MoveWindow(const RECTWrapper &r, bool Repaint) | |
216 | { | |
217 | return ::MoveWindow (WindowHandle, r.left, r.top, r.width(), r.height(), Repaint); | |
218 | } | |
219 | ||
df62e023 RC |
220 | void |
221 | Window::CenterWindow () | |
222 | { | |
223 | RECT WindowRect, ParentRect; | |
224 | int WindowWidth, WindowHeight; | |
225 | POINT p; | |
226 | ||
227 | // Get the window rectangle | |
63c82708 | 228 | WindowRect = GetWindowRect (); |
df62e023 RC |
229 | |
230 | if (GetParent () == NULL) | |
231 | { | |
232 | // Center on desktop window | |
63c82708 | 233 | ::GetWindowRect (GetDesktopWindow (), &ParentRect); |
df62e023 RC |
234 | } |
235 | else | |
236 | { | |
237 | // Center on client area of parent | |
63c82708 | 238 | ::GetClientRect (GetParent ()->GetHWND (), &ParentRect); |
df62e023 RC |
239 | } |
240 | ||
241 | WindowWidth = WindowRect.right - WindowRect.left; | |
242 | WindowHeight = WindowRect.bottom - WindowRect.top; | |
243 | ||
244 | // Find center of area we're centering on | |
245 | p.x = (ParentRect.right - ParentRect.left) / 2; | |
246 | p.y = (ParentRect.bottom - ParentRect.top) / 2; | |
247 | ||
248 | // Convert that to screen coords | |
249 | if (GetParent () == NULL) | |
250 | { | |
251 | ClientToScreen (GetDesktopWindow (), &p); | |
252 | } | |
253 | else | |
254 | { | |
255 | ClientToScreen (GetParent ()->GetHWND (), &p); | |
256 | } | |
257 | ||
258 | // Calculate new top left corner for window | |
259 | p.x -= WindowWidth / 2; | |
260 | p.y -= WindowHeight / 2; | |
261 | ||
262 | // And finally move the window | |
63c82708 | 263 | MoveWindow (p.x, p.y, WindowWidth, WindowHeight); |
df62e023 RC |
264 | } |
265 | ||
2f3a74d1 MB |
266 | LRESULT |
267 | Window::WindowProc (UINT uMsg, WPARAM wParam, LPARAM lParam) | |
df62e023 | 268 | { |
693916f8 | 269 | return DefWindowProc (WindowHandle, uMsg, wParam, lParam); |
df62e023 RC |
270 | } |
271 | ||
2f3a74d1 MB |
272 | bool |
273 | Window::MessageLoop () | |
df62e023 RC |
274 | { |
275 | MSG | |
276 | msg; | |
277 | ||
278 | while (GetMessage (&msg, NULL, 0, 0) != 0 | |
279 | && GetMessage (&msg, (HWND) NULL, 0, 0) != -1) | |
280 | { | |
281 | if (!IsWindow (WindowHandle) || !IsDialogMessage (WindowHandle, &msg)) | |
282 | { | |
283 | TranslateMessage (&msg); | |
284 | DispatchMessage (&msg); | |
285 | } | |
286 | } | |
287 | ||
288 | return true; | |
289 | } | |
290 | ||
291 | void | |
292 | Window::PostMessage (UINT uMsg, WPARAM wParam, LPARAM lParam) | |
293 | { | |
294 | ::PostMessage (GetHWND (), uMsg, wParam, lParam); | |
295 | } | |
b7301c43 | 296 | |
2f3a74d1 MB |
297 | UINT |
298 | Window::IsButtonChecked (int nIDButton) const | |
b7301c43 | 299 | { |
0a539fe4 | 300 | return ::IsDlgButtonChecked (GetHWND (), nIDButton); |
b7301c43 RC |
301 | } |
302 | ||
303 | bool | |
2f3a74d1 | 304 | Window::SetDlgItemFont (int id, const TCHAR * fontname, int Pointsize, |
b7301c43 RC |
305 | int Weight, bool Italic, bool Underline, |
306 | bool Strikeout) | |
307 | { | |
308 | HWND ctrl; | |
63c82708 | 309 | |
b7301c43 RC |
310 | ctrl = GetDlgItem (id); |
311 | if (ctrl == NULL) | |
312 | { | |
313 | // Couldn't get that ID | |
314 | return false; | |
315 | } | |
316 | ||
317 | // We need the DC for the point size calculation. | |
318 | HDC hdc = GetDC (ctrl); | |
319 | ||
320 | // Create the font. We have to keep it around until the dialog item | |
321 | // goes away - basically until we're destroyed. | |
322 | HFONT hfnt; | |
323 | hfnt = | |
324 | CreateFont (-MulDiv (Pointsize, GetDeviceCaps (hdc, LOGPIXELSY), 72), 0, | |
325 | 0, 0, Weight, Italic ? TRUE : FALSE, | |
326 | Underline ? TRUE : FALSE, Strikeout ? TRUE : FALSE, | |
327 | ANSI_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, | |
328 | PROOF_QUALITY, DEFAULT_PITCH | FF_DONTCARE, fontname); | |
329 | if (hfnt == NULL) | |
330 | { | |
331 | // Font creation failed | |
332 | return false; | |
333 | } | |
334 | ||
0a539fe4 | 335 | // Set the new font, and redraw any text which was already in the item. |
b7301c43 RC |
336 | SendMessage (ctrl, WM_SETFONT, (WPARAM) hfnt, TRUE); |
337 | ||
daab12c7 BD |
338 | // Store the handle so that we can DeleteObject() it in dtor |
339 | Fonts.push_back (hfnt); | |
b7301c43 RC |
340 | |
341 | return true; | |
342 | } | |
58db1046 RC |
343 | |
344 | void | |
08678720 | 345 | Window::SetWindowText (const std::string& s) |
58db1046 | 346 | { |
d2a3615c | 347 | ::SetWindowText (WindowHandle, s.c_str ()); |
58db1046 | 348 | } |
63c82708 RC |
349 | |
350 | RECT | |
351 | Window::ScreenToClient(const RECT &r) const | |
352 | { | |
353 | POINT tl; | |
354 | POINT br; | |
355 | ||
356 | tl.y = r.top; | |
357 | tl.x = r.left; | |
358 | ::ScreenToClient(GetHWND(), &tl); | |
359 | br.y = r.bottom; | |
360 | br.x = r.right; | |
361 | ::ScreenToClient(GetHWND(), &br); | |
362 | ||
363 | RECT ret; | |
364 | ||
365 | ret.top = tl.y; | |
366 | ret.left = tl.x; | |
367 | ret.bottom = br.y; | |
368 | ret.right = br.x; | |
369 | ||
370 | return ret; | |
371 | } | |
372 | ||
37f49caf BD |
373 | void |
374 | Window::ActivateTooltips () | |
375 | // initialization of the tooltip capability | |
376 | { | |
377 | if (TooltipHandle != NULL) | |
378 | return; // already initialized | |
379 | ||
380 | // create a window for the tool tips - will be invisible most of the time | |
3f6926b1 | 381 | if ((TooltipHandle = CreateWindowEx (0, (LPCTSTR) TOOLTIPS_CLASS, NULL, |
37f49caf BD |
382 | WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP, CW_USEDEFAULT, |
383 | CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, GetHWND (), | |
3f6926b1 | 384 | (HMENU) 0, GetInstance (), (LPVOID) 0)) == (HWND) NULL) |
37f49caf | 385 | { |
3f6926b1 | 386 | log (LOG_PLAIN) << "Warning: call to CreateWindowEx failed when " |
37f49caf BD |
387 | "initializing tooltips. Error = %8.8x" << GetLastError () |
388 | << endLog; | |
389 | return; | |
390 | } | |
391 | ||
392 | // must be topmost so that tooltips will display on top | |
3f6926b1 BD |
393 | SetWindowPos (TooltipHandle, HWND_TOPMOST, 0, 0, 0, 0, |
394 | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); | |
395 | ||
396 | // some of our tooltips are lengthy, and will disappear before they can be | |
397 | // read with the default windows delay, so we set a long (30s) delay here. | |
398 | SendMessage (TooltipHandle, TTM_SETDELAYTIME, TTDT_AUTOPOP, | |
399 | (LPARAM) MAKELONG (30000, 0)); | |
37f49caf BD |
400 | } |
401 | ||
402 | void | |
403 | Window::SetTooltipState (bool b) | |
404 | // enable/disable tooltips | |
405 | { | |
406 | SendMessage (TooltipHandle, (UINT)TTM_ACTIVATE, (WPARAM)(BOOL)b, 0); | |
407 | } | |
408 | ||
409 | void | |
410 | Window::AddTooltip (HWND target, HWND win, const char *text) | |
411 | // adds a tooltip to element 'target' in window 'win' | |
412 | // note: text is limited to 80 chars (grumble) | |
413 | { | |
414 | if (!TooltipHandle) | |
415 | ActivateTooltips (); | |
416 | ||
417 | TOOLINFO ti; | |
418 | ||
419 | memset ((void *)&ti, 0, sizeof(ti)); | |
420 | ti.cbSize = sizeof(ti); | |
421 | ||
422 | ti.uFlags = TTF_IDISHWND // add tool based on handle not ID | |
423 | | TTF_SUBCLASS; // tool is to subclass the window in order | |
424 | // to automatically get mouse events | |
425 | ti.hwnd = win; | |
426 | ti.uId = reinterpret_cast<UINT_PTR>(target); | |
427 | ti.lpszText = (LPTSTR)text; // pointer to text or string resource | |
428 | ||
429 | SendMessage (TooltipHandle, (UINT)TTM_ADDTOOL, 0, | |
430 | (LPARAM)(LPTOOLINFO)&ti); | |
431 | } | |
432 | ||
433 | void | |
434 | Window::AddTooltip (int id, const char *text) | |
435 | // adds a tooltip to a control identified by its ID | |
436 | { | |
437 | HWND target, parent; | |
438 | ||
439 | if ((target = GetDlgItem (id)) != NULL && | |
440 | (parent = ::GetParent (target)) != NULL) | |
441 | AddTooltip (target, parent, text); | |
442 | } | |
443 | ||
444 | void | |
445 | Window::AddTooltip (int id, int string_resource) | |
446 | // adds a tooltip that's represented by a string resource | |
447 | // this also allows for tooltips greater than 80 characters | |
448 | // we do this by setting the lpszText to LPSTR_TEXTCALLBACK | |
449 | // and then responding to the TTN_GETDISPINFO notification | |
450 | // in order to do this we store a list of (control ID, string ID) pairs | |
451 | { | |
452 | AddTooltip (id, (const char *)LPSTR_TEXTCALLBACK); | |
453 | TooltipStrings[id] = string_resource; | |
454 | } | |
455 | ||
456 | BOOL | |
457 | Window::TooltipNotificationHandler (LPARAM lParam) | |
458 | // this is the handler for TTN_GETDISPINFO notifications | |
459 | { | |
460 | NMTTDISPINFO *dispinfo = (NMTTDISPINFO *)lParam; | |
461 | int ctrlID; | |
462 | std::map<int, int>::iterator findID; | |
463 | ||
464 | if ((dispinfo->uFlags & TTF_IDISHWND) && | |
465 | ((ctrlID = GetDlgCtrlID ((HWND)dispinfo->hdr.idFrom)) != 0) && | |
466 | ((findID = TooltipStrings.find (ctrlID)) != TooltipStrings.end ())) { | |
467 | ||
468 | // enable multiple lines | |
469 | SendMessage(dispinfo->hdr.hwndFrom, TTM_SETMAXTIPWIDTH, 0, 450); | |
470 | ||
471 | // this is quite ugly. Apparently even when using string resources | |
472 | // the tooltip length still can't exceed 80 chars. So, we fetch the | |
473 | // resource into our own buffer and use that | |
474 | ||
475 | TCHAR buf[2048]; | |
476 | LoadString (GetInstance (), findID->second, (LPTSTR)buf, | |
477 | (sizeof (buf) / sizeof (TCHAR))); | |
478 | ||
479 | dispinfo->lpszText = buf; | |
480 | ||
481 | // set this flag so that the control will not ask for this again | |
482 | dispinfo->uFlags |= TTF_DI_SETITEM; | |
483 | dispinfo->hinst = NULL; | |
484 | return TRUE; | |
485 | } | |
486 | ||
487 | return FALSE; | |
488 | } | |
489 |