]>
Commit | Line | Data |
---|---|---|
df62e023 | 1 | /* |
56a7c49e | 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 PropSheet class. This class encapsulates | |
17 | // a Windows property sheet / wizard and interfaces with the PropertyPage class. | |
18 | // It's named PropSheet instead of PropertySheet because the latter conflicts with | |
19 | // the Windows function of the same name. | |
20 | ||
21 | #include "propsheet.h" | |
22 | #include "proppage.h" | |
62489576 | 23 | #include "resource.h" |
ee91d9be RC |
24 | #include "RECTWrapper.h" |
25 | #include "ControlAdjuster.h" | |
00fa5f6c | 26 | #include "choose.h" |
17d64747 | 27 | #include "msg.h" |
cfc01ee7 | 28 | #include "quiet.h" |
668cc4d7 | 29 | #include "windowsx.h" |
df62e023 | 30 | |
b7301c43 RC |
31 | // Sort of a "hidden" Windows structure. Used in the PropSheetCallback. |
32 | #include <pshpack1.h> | |
33 | typedef struct DLGTEMPLATEEX | |
34 | { | |
35 | WORD dlgVer; | |
36 | WORD signature; | |
37 | DWORD helpID; | |
38 | DWORD exStyle; | |
39 | DWORD style; | |
40 | WORD cDlgItems; | |
41 | short x; | |
42 | short y; | |
43 | short cx; | |
44 | short cy; | |
45 | } | |
46 | DLGTEMPLATEEX, *LPDLGTEMPLATEEX; | |
47 | #include <poppack.h> | |
df62e023 RC |
48 | |
49 | PropSheet::PropSheet () | |
50 | { | |
df62e023 RC |
51 | } |
52 | ||
53 | PropSheet::~PropSheet () | |
54 | { | |
55 | } | |
56 | ||
57 | HPROPSHEETPAGE * | |
58 | PropSheet::CreatePages () | |
59 | { | |
60 | HPROPSHEETPAGE *retarray; | |
61 | ||
62 | // Create the return array | |
56a7c49e | 63 | retarray = new HPROPSHEETPAGE[PropertyPages.size()]; |
df62e023 RC |
64 | |
65 | // Create the pages with CreatePropertySheetPage(). | |
66 | // We do it here rather than in the PropertyPages themselves | |
67 | // because, for reasons known only to Microsoft, these handles will be | |
68 | // destroyed by the property sheet before the PropertySheet() call returns, | |
69 | // at least if it's modal (don't know about modeless). | |
56a7c49e MB |
70 | unsigned int i; |
71 | for (i = 0; i < PropertyPages.size(); i++) | |
df62e023 RC |
72 | { |
73 | retarray[i] = | |
b567162a | 74 | CreatePropertySheetPageW (PropertyPages[i]->GetPROPSHEETPAGEPtr ()); |
df62e023 RC |
75 | |
76 | // Set position info | |
77 | if (i == 0) | |
78 | { | |
79 | PropertyPages[i]->YouAreFirst (); | |
80 | } | |
56a7c49e | 81 | else if (i == PropertyPages.size() - 1) |
df62e023 RC |
82 | { |
83 | PropertyPages[i]->YouAreLast (); | |
84 | } | |
85 | else | |
86 | { | |
87 | PropertyPages[i]->YouAreMiddle (); | |
88 | } | |
89 | } | |
90 | ||
91 | return retarray; | |
92 | } | |
93 | ||
ee91d9be RC |
94 | // Stuff needed by the PropSheet wndproc hook |
95 | struct PropSheetData | |
96 | { | |
97 | WNDPROC oldWndProc; | |
98 | bool clientRectValid; | |
99 | RECTWrapper lastClientRect; | |
100 | bool gotPage; | |
101 | RECTWrapper pageRect; | |
4b6e5406 RC |
102 | bool hasMinRect; |
103 | RECTWrapper minRect; | |
30718d6f | 104 | |
ee91d9be RC |
105 | PropSheetData () |
106 | { | |
107 | oldWndProc = 0; | |
108 | clientRectValid = false; | |
109 | gotPage = false; | |
4b6e5406 | 110 | hasMinRect = false; |
ee91d9be | 111 | } |
30718d6f | 112 | |
ee91d9be RC |
113 | // @@@ Ugly. Really only works because only one PS is used now. |
114 | static PropSheetData& Instance() | |
115 | { | |
116 | static PropSheetData TheInstance; | |
30718d6f | 117 | return TheInstance; |
ee91d9be RC |
118 | } |
119 | }; | |
120 | ||
121 | static ControlAdjuster::ControlInfo PropSheetControlsInfo[] = { | |
a8d753b6 RC |
122 | {0x3023, CP_RIGHT, CP_BOTTOM}, // Back |
123 | {0x3024, CP_RIGHT, CP_BOTTOM}, // Next | |
124 | {0x3025, CP_RIGHT, CP_BOTTOM}, // Finish | |
125 | {0x3026, CP_STRETCH, CP_BOTTOM}, // Line above buttons | |
126 | { 2, CP_RIGHT, CP_BOTTOM}, // Cancel | |
127 | {0, CP_LEFT, CP_TOP} | |
ee91d9be RC |
128 | }; |
129 | ||
130 | static bool IsDialog (HWND hwnd) | |
131 | { | |
132 | char className[7]; | |
133 | GetClassName (hwnd, className, sizeof (className)); | |
30718d6f | 134 | |
ee91d9be RC |
135 | return (strcmp (className, "#32770") == 0); |
136 | } | |
137 | ||
138 | BOOL CALLBACK EnumPages (HWND hwnd, LPARAM lParam) | |
139 | { | |
140 | // Is it really a dialog? | |
141 | if (IsDialog (hwnd)) | |
142 | { | |
143 | PropSheetData& psd = PropSheetData::Instance(); | |
30718d6f CF |
144 | SetWindowPos (hwnd, 0, psd.pageRect.left, psd.pageRect.top, |
145 | psd.pageRect.width (), psd.pageRect.height (), | |
ee91d9be RC |
146 | SWP_NOACTIVATE | SWP_NOZORDER); |
147 | } | |
148 | ||
149 | return TRUE; | |
150 | } | |
151 | ||
668cc4d7 JT |
152 | static void |
153 | GetSizeGripRect(HWND hwnd, LPRECT rect) | |
154 | { | |
155 | GetClientRect(hwnd, rect); | |
156 | rect->left = rect->right - GetSystemMetrics(SM_CXHSCROLL); | |
157 | rect->top = rect->bottom - GetSystemMetrics(SM_CYVSCROLL); | |
158 | } | |
159 | ||
30718d6f | 160 | static LRESULT CALLBACK PropSheetWndProc (HWND hwnd, UINT uMsg, |
ee91d9be RC |
161 | WPARAM wParam, LPARAM lParam) |
162 | { | |
163 | PropSheetData& psd = PropSheetData::Instance(); | |
164 | switch (uMsg) | |
165 | { | |
c8356810 | 166 | case WM_SYSCOMMAND: |
f2558685 CF |
167 | if ((wParam & 0xfff0) == SC_CLOSE) |
168 | goto areyousure; | |
169 | break; | |
c8356810 | 170 | case WM_COMMAND: |
f2558685 | 171 | if (wParam != 2) |
c8356810 | 172 | break; |
f2558685 | 173 | areyousure: |
85789118 JT |
174 | if (unattended_mode == unattended) |
175 | return 0; | |
17d64747 | 176 | if (mbox(hwnd, IDS_CONFIRM_EXIT, MB_YESNO) == IDNO) |
f2558685 CF |
177 | return 0; |
178 | break; | |
ee91d9be RC |
179 | case WM_SIZE: |
180 | { | |
30718d6f CF |
181 | RECTWrapper clientRect; |
182 | GetClientRect (hwnd, &clientRect); | |
183 | ||
ee91d9be | 184 | /* |
30718d6f CF |
185 | When the window is minimized, the client rect is reduced to |
186 | (0,0-0,0), which causes child adjusting to screw slightly up. Work | |
4b6e5406 RC |
187 | around by not adjusting child upon minimization - it isn't really |
188 | needed then, anyway. | |
ee91d9be | 189 | */ |
4b6e5406 | 190 | if (wParam != SIZE_MINIMIZED) |
ee91d9be | 191 | { |
ee91d9be | 192 | /* |
4b6e5406 RC |
193 | The first time we get a WM_SIZE, the client rect will be all zeros. |
194 | */ | |
195 | if (psd.clientRectValid) | |
196 | { | |
197 | const int dX = | |
198 | clientRect.width () - psd.lastClientRect.width (); | |
199 | const int dY = | |
200 | clientRect.height () - psd.lastClientRect.height (); | |
30718d6f CF |
201 | |
202 | ControlAdjuster::AdjustControls (hwnd, PropSheetControlsInfo, | |
4b6e5406 | 203 | dX, dY); |
30718d6f | 204 | |
4b6e5406 RC |
205 | psd.pageRect.right += dX; |
206 | psd.pageRect.bottom += dY; | |
30718d6f | 207 | |
4b6e5406 RC |
208 | /* |
209 | The pages are child windows, but don't have IDs. | |
30718d6f | 210 | So change them by enumerating all childs and adjust all |
4b6e5406 RC |
211 | dialogs among them. |
212 | */ | |
213 | if (psd.gotPage) | |
30718d6f | 214 | EnumChildWindows (hwnd, &EnumPages, 0); |
4b6e5406 RC |
215 | } |
216 | else | |
217 | { | |
218 | psd.clientRectValid = true; | |
219 | } | |
220 | /* | |
221 | Store away the current size and use it as the minmal window size. | |
ee91d9be | 222 | */ |
4b6e5406 RC |
223 | if (!psd.hasMinRect) |
224 | { | |
225 | GetWindowRect (hwnd, &psd.minRect); | |
6bfd5236 | 226 | psd.hasMinRect = true; |
4b6e5406 | 227 | } |
30718d6f | 228 | |
4b6e5406 | 229 | psd.lastClientRect = clientRect; |
ee91d9be | 230 | } |
4b6e5406 RC |
231 | } |
232 | break; | |
233 | case WM_GETMINMAXINFO: | |
234 | { | |
235 | if (psd.hasMinRect) | |
ee91d9be | 236 | { |
4b6e5406 RC |
237 | LPMINMAXINFO mmi = (LPMINMAXINFO)lParam; |
238 | mmi->ptMinTrackSize.x = psd.minRect.width (); | |
239 | mmi->ptMinTrackSize.y = psd.minRect.height (); | |
ee91d9be | 240 | } |
ee91d9be RC |
241 | } |
242 | break; | |
668cc4d7 JT |
243 | case WM_PAINT: |
244 | { | |
245 | RECT rect; | |
246 | GetSizeGripRect(hwnd, &rect); | |
247 | ||
248 | // draw size grip | |
249 | HDC dc = GetDC(hwnd); | |
250 | DrawFrameControl(dc, &rect, DFC_SCROLL, DFCS_SCROLLSIZEGRIP); | |
251 | ReleaseDC(hwnd, dc); | |
252 | } | |
253 | break; | |
254 | case WM_NCHITTEST: | |
255 | { | |
256 | RECT rect; | |
257 | GetSizeGripRect(hwnd, &rect); | |
258 | MapWindowPoints(hwnd, NULL, reinterpret_cast<POINT *>(&rect), 2); | |
259 | ||
260 | // when over the size grip, pretend to be over bottom-right corner of | |
261 | // the sizing border | |
262 | POINT point = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; | |
263 | if (PtInRect(&rect, point)) | |
264 | return HTBOTTOMRIGHT; | |
265 | ||
266 | // otherwise, fall through... | |
267 | } | |
268 | break; | |
269 | case WM_SIZING: | |
270 | { | |
271 | // invalidate the area containing the size grip when the window is | |
272 | // resized, so it's redrawn | |
273 | RECT rect; | |
274 | GetSizeGripRect(hwnd, &rect); | |
275 | InvalidateRect(hwnd, &rect, FALSE); | |
276 | } | |
277 | break; | |
ee91d9be | 278 | } |
30718d6f CF |
279 | |
280 | return CallWindowProc (psd.oldWndProc, | |
ee91d9be RC |
281 | hwnd, uMsg, wParam, lParam); |
282 | } | |
283 | ||
b7301c43 RC |
284 | static int CALLBACK |
285 | PropSheetProc (HWND hwndDlg, UINT uMsg, LPARAM lParam) | |
286 | { | |
287 | switch (uMsg) | |
288 | { | |
289 | case PSCB_PRECREATE: | |
290 | { | |
cfc01ee7 JT |
291 | LONG additionalStyle = (WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_THICKFRAME); |
292 | ||
293 | // 'noinput' quiet mode adds WS_DISABLE to prevent user interaction | |
294 | // with the propsheet | |
295 | if (UnattendedOption == QuietNoInput) | |
296 | additionalStyle |= WS_DISABLED; | |
297 | ||
298 | // 'hidden' quiet mode totally hides the propsheet window | |
299 | LONG forbiddenStyle = 0; | |
300 | if (UnattendedOption == QuietHidden) | |
301 | forbiddenStyle |= WS_VISIBLE; | |
302 | ||
b7301c43 RC |
303 | // Add a minimize box to the sheet/wizard. |
304 | if (((LPDLGTEMPLATEEX) lParam)->signature == 0xFFFF) | |
305 | { | |
ee91d9be | 306 | ((LPDLGTEMPLATEEX) lParam)->style |= additionalStyle; |
cfc01ee7 | 307 | ((LPDLGTEMPLATEEX) lParam)->style &= ~forbiddenStyle; |
b7301c43 RC |
308 | } |
309 | else | |
310 | { | |
ee91d9be | 311 | ((LPDLGTEMPLATE) lParam)->style |= additionalStyle; |
cfc01ee7 | 312 | ((LPDLGTEMPLATE) lParam)->style &= ~forbiddenStyle; |
b7301c43 RC |
313 | } |
314 | } | |
315 | return TRUE; | |
ee91d9be RC |
316 | case PSCB_INITIALIZED: |
317 | { | |
d1b5a517 JT |
318 | /* |
319 | PropSheet() with PSH_USEICONID only sets the small icon, | |
320 | so we must set the big icon ourselves | |
321 | */ | |
322 | SendMessage(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_CYGWIN))); | |
30718d6f | 323 | /* |
ee91d9be RC |
324 | Hook into the window proc. |
325 | We need to catch some messages for resizing. | |
326 | */ | |
75bfcddb JT |
327 | PropSheetData::Instance().oldWndProc = (WNDPROC)GetWindowLongPtrW (hwndDlg, GWLP_WNDPROC); |
328 | SetWindowLongPtrW (hwndDlg, GWLP_WNDPROC, (LONG_PTR)&PropSheetWndProc); | |
329 | /* | |
330 | Store the PropSheet HWND in the Chooser, for use in resizing | |
331 | ||
332 | (XXX: this is just silly: The PropSheet HWND is the parent of the | |
333 | PropPage HWND, so chooser has access to that HWND as GetParent() | |
334 | anyhow... ) | |
335 | */ | |
00fa5f6c | 336 | ChooserPage::SetHwndDialog (hwndDlg); |
ee91d9be RC |
337 | } |
338 | return TRUE; | |
b7301c43 RC |
339 | } |
340 | return TRUE; | |
341 | } | |
342 | ||
b7301c43 RC |
343 | bool |
344 | PropSheet::Create (const Window * Parent, DWORD Style) | |
df62e023 | 345 | { |
b567162a | 346 | PROPSHEETHEADERW p; |
df62e023 RC |
347 | |
348 | PageHandles = CreatePages (); | |
349 | ||
b567162a | 350 | p.dwSize = sizeof (PROPSHEETHEADERW); |
62489576 MB |
351 | p.dwFlags = PSH_NOAPPLYNOW | PSH_WIZARD | PSH_USECALLBACK |
352 | /*| PSH_MODELESS */ | PSH_USEICONID; | |
df62e023 RC |
353 | if (Parent != NULL) |
354 | { | |
355 | p.hwndParent = Parent->GetHWND (); | |
356 | } | |
357 | else | |
358 | { | |
359 | p.hwndParent = NULL; | |
360 | } | |
361 | p.hInstance = GetInstance (); | |
56a7c49e | 362 | p.nPages = PropertyPages.size(); |
b567162a | 363 | p.pszIcon = MAKEINTRESOURCEW(IDI_CYGWIN); |
df62e023 RC |
364 | p.nStartPage = 0; |
365 | p.phpage = PageHandles; | |
b7301c43 | 366 | p.pfnCallback = PropSheetProc; |
df62e023 | 367 | |
58ee6135 | 368 | // The winmain event loop actually resides in here. |
b567162a | 369 | PropertySheetW (&p); |
df62e023 | 370 | |
df62e023 RC |
371 | SetHWND (NULL); |
372 | ||
373 | ||
374 | return true; | |
375 | } | |
376 | ||
377 | void | |
378 | PropSheet::SetHWNDFromPage (HWND h) | |
379 | { | |
380 | // If we're a modal dialog, there's no way for us to know our window handle unless | |
381 | // one of our pages tells us through this function. | |
382 | SetHWND (h); | |
383 | } | |
384 | ||
ee91d9be RC |
385 | /* |
386 | Adjust the size of a page so that it fits nicely into the window. | |
387 | */ | |
388 | void | |
389 | PropSheet::AdjustPageSize (HWND page) | |
390 | { | |
391 | PropSheetData& psd = PropSheetData::Instance(); | |
392 | if (!psd.clientRectValid) return; | |
393 | ||
394 | /* | |
395 | It's probably not obvious what's done here: | |
396 | When this method is called the first time, the first page is already | |
397 | created and sized, but at the coordinates (0,0). The sheet, however, | |
398 | isn't in it's final size. My guess is that the sheet first creates the | |
30718d6f | 399 | page, and then resizes itself to have the right metrics to contain the |
ee91d9be | 400 | page and moves it to it's position. For our purposes, however, we need |
4b6e5406 | 401 | the final metrics of the page. So, the first time this method is called, |
ee91d9be RC |
402 | we basically grab the size of the page, but calculate the top/left coords |
403 | ourselves. | |
404 | */ | |
30718d6f | 405 | |
ee91d9be RC |
406 | if (!psd.gotPage) |
407 | { | |
408 | psd.gotPage = true; | |
409 | ||
410 | RECTWrapper& pageRect = psd.pageRect; | |
411 | ::GetWindowRect (page, &pageRect); | |
412 | // We want client coords. | |
413 | ::ScreenToClient (page, (LPPOINT)&pageRect.left); | |
414 | ::ScreenToClient (page, (LPPOINT)&pageRect.right); | |
415 | ||
416 | LONG dialogBaseUnits = ::GetDialogBaseUnits (); | |
417 | // The margins in DUs are a result of "educated guesses" and T&E. | |
418 | int marginX = MulDiv (5, LOWORD(dialogBaseUnits), 4); | |
419 | int marginY = MulDiv (5, HIWORD(dialogBaseUnits), 8); | |
420 | ||
421 | pageRect.move (marginX, marginY); | |
422 | } | |
423 | ||
30718d6f CF |
424 | SetWindowPos (page, 0, psd.pageRect.left, psd.pageRect.top, |
425 | psd.pageRect.width (), psd.pageRect.height (), | |
ee91d9be RC |
426 | SWP_NOACTIVATE | SWP_NOZORDER); |
427 | } | |
428 | ||
df62e023 RC |
429 | void |
430 | PropSheet::AddPage (PropertyPage * p) | |
431 | { | |
432 | // Add a page to the property sheet. | |
433 | p->YouAreBeingAddedToASheet (this); | |
56a7c49e | 434 | PropertyPages.push_back(p); |
df62e023 RC |
435 | } |
436 | ||
b7301c43 RC |
437 | bool |
438 | PropSheet::SetActivePage (int i) | |
df62e023 RC |
439 | { |
440 | // Posts a message to the message queue, so this won't block | |
db09d718 | 441 | return static_cast < bool > (PropSheet_SetCurSel (GetHWND (), NULL, i)); |
df62e023 RC |
442 | } |
443 | ||
b7301c43 RC |
444 | bool |
445 | PropSheet::SetActivePageByID (int resource_id) | |
df62e023 RC |
446 | { |
447 | // Posts a message to the message queue, so this won't block | |
448 | return static_cast < bool > | |
db09d718 | 449 | (PropSheet_SetCurSelByID (GetHWND (), resource_id)); |
df62e023 RC |
450 | } |
451 | ||
452 | void | |
453 | PropSheet::SetButtons (DWORD flags) | |
454 | { | |
455 | // Posts a message to the message queue, so this won't block | |
db09d718 | 456 | PropSheet_SetWizButtons (GetHWND (), flags); |
df62e023 RC |
457 | } |
458 | ||
459 | void | |
460 | PropSheet::PressButton (int button) | |
461 | { | |
db09d718 | 462 | PropSheet_PressButton (GetHWND (), button); |
df62e023 | 463 | } |