]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * Copyright (c) 2001, 2002, 2003 Gary R. Van Sickle. | |
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 PropertyPage class. It works closely with the | |
17 | // PropSheet class to implement a single page of the property sheet. | |
18 | ||
19 | #include "proppage.h" | |
20 | #include "propsheet.h" | |
21 | #include "win32.h" | |
22 | #include <shellapi.h> | |
23 | #include "resource.h" | |
24 | #include "state.h" | |
25 | ||
26 | #include "getopt++/BoolOption.h" | |
27 | #include "Exception.h" | |
28 | #include "LogSingleton.h" | |
29 | ||
30 | bool PropertyPage::DoOnceForSheet = true; | |
31 | ||
32 | /* | |
33 | Sizing information for some controls that are common to all pages. | |
34 | */ | |
35 | static ControlAdjuster::ControlInfo DefaultControlsInfo[] = { | |
36 | {IDC_HEADICON, CP_RIGHT, CP_TOP}, | |
37 | {IDC_HEADSEPARATOR, CP_STRETCH, CP_TOP}, | |
38 | {0, CP_LEFT, CP_TOP} | |
39 | }; | |
40 | ||
41 | PropertyPage::PropertyPage () | |
42 | { | |
43 | proc = NULL; | |
44 | cmdproc = NULL; | |
45 | IsFirst = false; | |
46 | IsLast = false; | |
47 | ||
48 | sizeProcessor.AddControlInfo (DefaultControlsInfo); | |
49 | } | |
50 | ||
51 | PropertyPage::~PropertyPage () | |
52 | { | |
53 | } | |
54 | ||
55 | bool PropertyPage::Create (int TemplateID) | |
56 | { | |
57 | return Create (NULL, NULL, TemplateID); | |
58 | } | |
59 | ||
60 | bool PropertyPage::Create (DLGPROC dlgproc, int TemplateID) | |
61 | { | |
62 | return Create (dlgproc, NULL, TemplateID); | |
63 | } | |
64 | ||
65 | bool | |
66 | PropertyPage::Create (DLGPROC dlgproc, | |
67 | BOOL (*cproc) (HWND h, int id, HWND hwndctl, | |
68 | UINT code), int TemplateID) | |
69 | { | |
70 | memset(&psp, 0, sizeof (PROPSHEETPAGE)); | |
71 | psp.dwSize = sizeof (PROPSHEETPAGE); | |
72 | psp.dwFlags = 0; | |
73 | psp.hInstance = GetInstance (); | |
74 | psp.pfnDlgProc = FirstDialogProcReflector; | |
75 | psp.pszTemplate = MAKEINTRESOURCE(TemplateID); | |
76 | psp.lParam = (LPARAM) this; | |
77 | psp.pfnCallback = NULL; | |
78 | ||
79 | proc = dlgproc; | |
80 | cmdproc = cproc; | |
81 | ||
82 | return true; | |
83 | } | |
84 | ||
85 | INT_PTR CALLBACK | |
86 | PropertyPage::FirstDialogProcReflector (HWND hwnd, UINT message, | |
87 | WPARAM wParam, LPARAM lParam) | |
88 | { | |
89 | PropertyPage *This; | |
90 | ||
91 | if (message != WM_INITDIALOG) | |
92 | { | |
93 | // Don't handle anything until we get a WM_INITDIALOG message, which | |
94 | // will have our 'this' pointer with it. | |
95 | return FALSE; | |
96 | } | |
97 | ||
98 | This = (PropertyPage *) (((PROPSHEETPAGE *) lParam)->lParam); | |
99 | ||
100 | SetWindowLongPtr (hwnd, DWLP_USER, (LONG_PTR) This); | |
101 | SetWindowLongPtr (hwnd, DWLP_DLGPROC, (LONG_PTR) DialogProcReflector); | |
102 | ||
103 | This->SetHWND (hwnd); | |
104 | return This->DialogProc (message, wParam, lParam); | |
105 | } | |
106 | ||
107 | INT_PTR CALLBACK | |
108 | PropertyPage::DialogProcReflector (HWND hwnd, UINT message, WPARAM wParam, | |
109 | LPARAM lParam) | |
110 | { | |
111 | PropertyPage *This; | |
112 | ||
113 | This = (PropertyPage *) GetWindowLongPtr (hwnd, DWLP_USER); | |
114 | ||
115 | return This->DialogProc (message, wParam, lParam); | |
116 | } | |
117 | ||
118 | INT_PTR CALLBACK | |
119 | PropertyPage::DialogProc (UINT message, WPARAM wParam, LPARAM lParam) | |
120 | { | |
121 | try | |
122 | { | |
123 | if (proc != NULL) | |
124 | { | |
125 | proc (GetHWND (), message, wParam, lParam); | |
126 | } | |
127 | ||
128 | switch (message) | |
129 | { | |
130 | case WM_INITDIALOG: | |
131 | { | |
132 | OnInit (); | |
133 | ||
134 | setTitleFont (); | |
135 | ||
136 | // Call it here so it stores the initial client rect. | |
137 | sizeProcessor.UpdateSize (GetHWND ()); | |
138 | ||
139 | // TRUE = Set focus to default control (in wParam). | |
140 | return TRUE; | |
141 | } | |
142 | case WM_NOTIFY: | |
143 | switch (((NMHDR FAR *) lParam)->code) | |
144 | { | |
145 | case PSN_APPLY: | |
146 | { | |
147 | SetWindowLongPtr (GetHWND (), DWLP_MSGRESULT, PSNRET_NOERROR); | |
148 | return TRUE; | |
149 | } | |
150 | case PSN_SETACTIVE: | |
151 | { | |
152 | if (DoOnceForSheet) | |
153 | { | |
154 | // Tell our parent PropSheet what its own HWND is. | |
155 | GetOwner ()->SetHWNDFromPage (((NMHDR FAR *) lParam)-> | |
156 | hwndFrom); | |
157 | GetOwner ()->CenterWindow (); | |
158 | DoOnceForSheet = false; | |
159 | } | |
160 | ||
161 | GetOwner ()->AdjustPageSize (GetHWND ()); | |
162 | ||
163 | // Set the wizard buttons apropriately | |
164 | if (IsFirst) | |
165 | { | |
166 | // Disable "Back" on first page. | |
167 | GetOwner ()->SetButtons (PSWIZB_NEXT); | |
168 | } | |
169 | else if (IsLast) | |
170 | { | |
171 | // Disable "Next", enable "Finish" on last page | |
172 | GetOwner ()->SetButtons (PSWIZB_BACK | PSWIZB_FINISH); | |
173 | } | |
174 | else | |
175 | { | |
176 | // Middle page, enable both "Next" and "Back" buttons | |
177 | GetOwner ()->SetButtons (PSWIZB_BACK | PSWIZB_NEXT); | |
178 | } | |
179 | ||
180 | if(!wantsActivation()) | |
181 | { | |
182 | ::SetWindowLongPtr (GetHWND (), DWLP_MSGRESULT, -1); | |
183 | return TRUE; | |
184 | } | |
185 | ||
186 | OnActivate (); | |
187 | ||
188 | if (unattended_mode) | |
189 | { | |
190 | // -2 == disable unattended mode, display page | |
191 | // -1 == display page but stay in unattended mode (progress bars) | |
192 | // 0 == skip to next page (in propsheet sequence) | |
193 | // IDD_* == skip to specified page | |
194 | long nextwindow = OnUnattended(); | |
195 | if (nextwindow == -2) | |
196 | { | |
197 | unattended_mode = attended; | |
198 | SetWindowLongPtr (GetHWND (), DWLP_MSGRESULT, 0); | |
199 | return TRUE; | |
200 | } | |
201 | else if (nextwindow == -1) | |
202 | { | |
203 | SetWindowLongPtr (GetHWND (), DWLP_MSGRESULT, 0); | |
204 | return TRUE; | |
205 | } | |
206 | else if (nextwindow == 0) | |
207 | { | |
208 | SetWindowLongPtr (GetHWND (), DWLP_MSGRESULT, -1); | |
209 | return TRUE; | |
210 | } | |
211 | else | |
212 | { | |
213 | SetWindowLongPtr (GetHWND (), DWLP_MSGRESULT, nextwindow); | |
214 | return TRUE; | |
215 | } | |
216 | } | |
217 | else | |
218 | { | |
219 | // 0 == Accept activation, -1 = Don't accept | |
220 | ::SetWindowLongPtr (GetHWND (), DWLP_MSGRESULT, 0); | |
221 | return TRUE; | |
222 | } | |
223 | ||
224 | } | |
225 | break; | |
226 | case PSN_KILLACTIVE: | |
227 | { | |
228 | OnDeactivate (); | |
229 | // FALSE = Allow deactivation | |
230 | SetWindowLongPtr (GetHWND (), DWLP_MSGRESULT, FALSE); | |
231 | return TRUE; | |
232 | } | |
233 | case PSN_WIZNEXT: | |
234 | { | |
235 | LONG retval; | |
236 | retval = OnNext (); | |
237 | SetWindowLongPtr (GetHWND (), DWLP_MSGRESULT, retval); | |
238 | return TRUE; | |
239 | } | |
240 | case PSN_WIZBACK: | |
241 | { | |
242 | LONG retval; | |
243 | retval = OnBack (); | |
244 | SetWindowLongPtr (GetHWND (), DWLP_MSGRESULT, retval); | |
245 | return TRUE; | |
246 | } | |
247 | case PSN_WIZFINISH: | |
248 | { | |
249 | OnFinish (); | |
250 | // False = Allow the wizard to finish | |
251 | SetWindowLongPtr (GetHWND (), DWLP_MSGRESULT, FALSE); | |
252 | return TRUE; | |
253 | } | |
254 | case TTN_GETDISPINFO: | |
255 | { | |
256 | return TooltipNotificationHandler (lParam); | |
257 | } | |
258 | default: | |
259 | { | |
260 | // Unrecognized notification | |
261 | return FALSE; | |
262 | } | |
263 | } | |
264 | break; | |
265 | case WM_COMMAND: | |
266 | { | |
267 | bool retval; | |
268 | ||
269 | retval = | |
270 | OnMessageCmd (LOWORD (wParam), (HWND) lParam, HIWORD (wParam)); | |
271 | if (retval == true) | |
272 | { | |
273 | // Handled, return 0 | |
274 | SetWindowLongPtr (GetHWND (), DWLP_MSGRESULT, 0); | |
275 | return TRUE; | |
276 | } | |
277 | else if (cmdproc != NULL) | |
278 | { | |
279 | cmdproc (GetHWND(), LOWORD(wParam), (HWND)lParam, HIWORD(wParam)); | |
280 | return 0; | |
281 | } | |
282 | break; | |
283 | } | |
284 | case WM_SIZE: | |
285 | { | |
286 | sizeProcessor.UpdateSize (GetHWND ()); | |
287 | break; | |
288 | } | |
289 | case WM_CTLCOLORSTATIC: | |
290 | { | |
291 | // check for text controls that we've url-ified that are initializing | |
292 | int id; | |
293 | std::map <int, ClickableURL>::iterator theURL; | |
294 | ||
295 | // get the ID of the control, and look it up in our list | |
296 | if ((id = GetDlgCtrlID ((HWND)lParam)) == 0 || | |
297 | (theURL = urls.find (id)) == urls.end ()) | |
298 | ||
299 | // nope sorry, don't know nothing about this control | |
300 | return FALSE; | |
301 | ||
302 | // set FG = blue, BG = default background for a dialog | |
303 | SetTextColor ((HDC)wParam, RGB (0, 0, 255)); | |
304 | SetBkColor ((HDC)wParam, GetSysColor (COLOR_BTNFACE)); | |
305 | ||
306 | // get the current font, add underline, and set it back | |
307 | if (theURL->second.font == 0) | |
308 | { | |
309 | TEXTMETRIC tm; | |
310 | ||
311 | GetTextMetrics ((HDC)wParam, &tm); | |
312 | LOGFONT lf; | |
313 | memset ((void *)&lf, 0, sizeof(LOGFONT)); | |
314 | lf.lfUnderline = TRUE; | |
315 | lf.lfHeight = tm.tmHeight; | |
316 | lf.lfWeight = tm.tmWeight; | |
317 | lf.lfItalic = tm.tmItalic; | |
318 | lf.lfStrikeOut = tm.tmStruckOut; | |
319 | lf.lfCharSet = tm.tmCharSet; | |
320 | lf.lfOutPrecision = OUT_DEFAULT_PRECIS; | |
321 | lf.lfClipPrecision = CLIP_DEFAULT_PRECIS; | |
322 | lf.lfQuality = DEFAULT_QUALITY; | |
323 | lf.lfPitchAndFamily = tm.tmPitchAndFamily; | |
324 | GetTextFace ((HDC)wParam, LF_FACESIZE, lf.lfFaceName); | |
325 | if ((theURL->second.font = CreateFontIndirect (&lf)) == NULL) | |
326 | Log (LOG_PLAIN) << "Warning: unable to set font for url " | |
327 | << theURL->second.url << endLog; | |
328 | } | |
329 | ||
330 | // apply the font | |
331 | SelectObject ((HDC)wParam, theURL->second.font); | |
332 | ||
333 | // make a brush if we have not yet | |
334 | if (theURL->second.brush == NULL) | |
335 | theURL->second.brush = CreateSolidBrush | |
336 | (GetSysColor (COLOR_BTNFACE)); | |
337 | ||
338 | return (INT_PTR) theURL->second.brush; | |
339 | } | |
340 | case WM_MOUSEWHEEL: | |
341 | // we do this so that derived classes that wish to process this message | |
342 | // do not need to reimplement the entire WinProc, they can just | |
343 | // provice an OnMouseWheel. (Note that mousewheel events are delivered | |
344 | // to the parent of the window that received the scroll, so it would | |
345 | // not work to just process this message there.) | |
346 | return OnMouseWheel (message, wParam, lParam); | |
347 | ||
348 | case WM_TIMER: | |
349 | // similar delegation as with WM_MOUSEWHEEL | |
350 | return OnTimerMessage (message, wParam, lParam); | |
351 | ||
352 | default: | |
353 | break; | |
354 | } | |
355 | ||
356 | if ((message >= WM_APP) && (message < 0xC000)) | |
357 | { | |
358 | // It's a private app message | |
359 | return OnMessageApp (message, wParam, lParam); | |
360 | } | |
361 | } | |
362 | TOPLEVEL_CATCH(GetHWND (), "DialogProc"); | |
363 | ||
364 | // Wasn't handled | |
365 | return FALSE; | |
366 | } | |
367 | ||
368 | INT_PTR CALLBACK | |
369 | PropertyPage::OnMouseWheel (UINT message, WPARAM wParam, LPARAM lParam) | |
370 | { | |
371 | return 1; // not handled; define in a derived class to support this | |
372 | } | |
373 | ||
374 | INT_PTR CALLBACK | |
375 | PropertyPage::OnTimerMessage (UINT message, WPARAM wParam, LPARAM lParam) | |
376 | { | |
377 | return 1; // not handled; define in a derived class to support this | |
378 | } | |
379 | ||
380 | void | |
381 | PropertyPage::setTitleFont () | |
382 | { | |
383 | // These font settings will just silently fail when the resource id | |
384 | // is not present on a page. | |
385 | // Set header title font of each internal page | |
386 | SetDlgItemFont(IDC_STATIC_HEADER_TITLE, "MS Shell Dlg", 8, FW_BOLD); | |
387 | // Set the font for the IDC_STATIC_WELCOME_TITLE | |
388 | SetDlgItemFont(IDC_STATIC_WELCOME_TITLE, "Arial", 12, FW_BOLD); | |
389 | } | |
390 | ||
391 | std::map <int, PropertyPage::ClickableURL> PropertyPage::urls; | |
392 | ||
393 | void | |
394 | PropertyPage::makeClickable (int id, std::string link) | |
395 | // turns a static text control in this dialog into a hyperlink | |
396 | { | |
397 | // get the handle of the specified control | |
398 | HWND hctl = ::GetDlgItem (GetHWND (), id); | |
399 | if (hctl == NULL) | |
400 | return; // invalid ID | |
401 | ||
402 | if (urls.find (id) != urls.end ()) | |
403 | return; // already done this one | |
404 | ||
405 | ClickableURL c; | |
406 | c.url = link; | |
407 | c.font = NULL; // these will be created as needed | |
408 | c.brush = NULL; | |
409 | if ((c.origWinProc = reinterpret_cast<WNDPROC>(SetWindowLongPtr (hctl, | |
410 | GWLP_WNDPROC, (LONG_PTR) & PropertyPage::urlWinProc))) == 0) | |
411 | return; // failure | |
412 | ||
413 | // add this to 'urls' so that the dialog and control winprocs know about it | |
414 | urls[id] = c; | |
415 | ||
416 | // set a tooltip for the link | |
417 | AddTooltip (id, link.c_str()); | |
418 | } | |
419 | ||
420 | LRESULT CALLBACK | |
421 | PropertyPage::urlWinProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) | |
422 | // a winproc that we use to subclass a static text control to make a URL | |
423 | { | |
424 | int id; | |
425 | std::map <int, ClickableURL>::iterator theURL; | |
426 | ||
427 | // get the ID of the control, and look it up in our list | |
428 | if ((id = GetDlgCtrlID (hwnd)) == 0 || | |
429 | (theURL = urls.find (id)) == urls.end ()) | |
430 | ||
431 | // we were called for a control that we weren't installed on | |
432 | // punt to default winproc | |
433 | return DefWindowProc (hwnd, uMsg, wParam, lParam); | |
434 | ||
435 | switch (uMsg) | |
436 | { | |
437 | case WM_LBUTTONDOWN: | |
438 | { | |
439 | // they clicked our URL! yay! | |
440 | intptr_t rc = (intptr_t) ShellExecute (hwnd, "open", | |
441 | theURL->second.url.c_str (), NULL, NULL, SW_SHOWNORMAL); | |
442 | ||
443 | if (rc <= 32) | |
444 | Log (LOG_PLAIN) << "Unable to launch browser for URL " << | |
445 | theURL->second.url << " (rc = " << rc << ")" << endLog; | |
446 | break; | |
447 | } | |
448 | case WM_SETCURSOR: | |
449 | { | |
450 | // show the hand cursor when they hover | |
451 | // note: apparently the hand cursor isn't available | |
452 | // on very old versions of win95? So, check return of LoadCursor | |
453 | // and don't attempt SetCursor if it failed | |
454 | HCURSOR c = LoadCursor (NULL, reinterpret_cast<LPCSTR>(IDC_HAND)); | |
455 | if (c) | |
456 | SetCursor (c); | |
457 | return TRUE; | |
458 | } | |
459 | case WM_NCHITTEST: | |
460 | { | |
461 | // normally, a static control returns HTTRANSPARENT for this | |
462 | // which means that we would never receive the SETCURSOR message | |
463 | return HTCLIENT; | |
464 | } | |
465 | case WM_DESTROY: | |
466 | { | |
467 | // clean up | |
468 | WNDPROC saveWinProc = theURL->second.origWinProc; | |
469 | DeleteObject (theURL->second.font); | |
470 | DeleteObject (theURL->second.brush); | |
471 | urls.erase (id); | |
472 | return CallWindowProc (saveWinProc, hwnd, uMsg, wParam, lParam); | |
473 | } | |
474 | } | |
475 | ||
476 | // pass on control to the previous winproc | |
477 | return CallWindowProc (theURL->second.origWinProc, hwnd, uMsg, wParam, lParam); | |
478 | } |