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