]>
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 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" | |
23 | #include "resource.h" | |
24 | #include "RECTWrapper.h" | |
25 | #include "ControlAdjuster.h" | |
26 | #include "choose.h" | |
27 | #include "msg.h" | |
28 | #include "quiet.h" | |
29 | #include "windowsx.h" | |
30 | ||
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> | |
48 | ||
49 | PropSheet::PropSheet () | |
50 | { | |
51 | } | |
52 | ||
53 | PropSheet::~PropSheet () | |
54 | { | |
55 | } | |
56 | ||
57 | HPROPSHEETPAGE * | |
58 | PropSheet::CreatePages () | |
59 | { | |
60 | HPROPSHEETPAGE *retarray; | |
61 | ||
62 | // Create the return array | |
63 | retarray = new HPROPSHEETPAGE[PropertyPages.size()]; | |
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). | |
70 | unsigned int i; | |
71 | for (i = 0; i < PropertyPages.size(); i++) | |
72 | { | |
73 | retarray[i] = | |
74 | CreatePropertySheetPageW (PropertyPages[i]->GetPROPSHEETPAGEPtr ()); | |
75 | ||
76 | // Set position info | |
77 | if (i == 0) | |
78 | { | |
79 | PropertyPages[i]->YouAreFirst (); | |
80 | } | |
81 | else if (i == PropertyPages.size() - 1) | |
82 | { | |
83 | PropertyPages[i]->YouAreLast (); | |
84 | } | |
85 | else | |
86 | { | |
87 | PropertyPages[i]->YouAreMiddle (); | |
88 | } | |
89 | } | |
90 | ||
91 | return retarray; | |
92 | } | |
93 | ||
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; | |
102 | bool hasMinRect; | |
103 | RECTWrapper minRect; | |
104 | ||
105 | PropSheetData () | |
106 | { | |
107 | oldWndProc = 0; | |
108 | clientRectValid = false; | |
109 | gotPage = false; | |
110 | hasMinRect = false; | |
111 | } | |
112 | ||
113 | // @@@ Ugly. Really only works because only one PS is used now. | |
114 | static PropSheetData& Instance() | |
115 | { | |
116 | static PropSheetData TheInstance; | |
117 | return TheInstance; | |
118 | } | |
119 | }; | |
120 | ||
121 | static ControlAdjuster::ControlInfo PropSheetControlsInfo[] = { | |
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} | |
128 | }; | |
129 | ||
130 | static bool IsDialog (HWND hwnd) | |
131 | { | |
132 | char className[7]; | |
133 | GetClassName (hwnd, className, sizeof (className)); | |
134 | ||
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(); | |
144 | SetWindowPos (hwnd, 0, psd.pageRect.left, psd.pageRect.top, | |
145 | psd.pageRect.width (), psd.pageRect.height (), | |
146 | SWP_NOACTIVATE | SWP_NOZORDER); | |
147 | } | |
148 | ||
149 | return TRUE; | |
150 | } | |
151 | ||
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 | ||
160 | static LRESULT CALLBACK PropSheetWndProc (HWND hwnd, UINT uMsg, | |
161 | WPARAM wParam, LPARAM lParam) | |
162 | { | |
163 | PropSheetData& psd = PropSheetData::Instance(); | |
164 | switch (uMsg) | |
165 | { | |
166 | case WM_SYSCOMMAND: | |
167 | if ((wParam & 0xfff0) == SC_CLOSE) | |
168 | goto areyousure; | |
169 | break; | |
170 | case WM_COMMAND: | |
171 | if (wParam != 2) | |
172 | break; | |
173 | areyousure: | |
174 | if (unattended_mode == unattended) | |
175 | return 0; | |
176 | if (mbox(hwnd, IDS_CONFIRM_EXIT, MB_YESNO) == IDNO) | |
177 | return 0; | |
178 | break; | |
179 | case WM_SIZE: | |
180 | { | |
181 | RECTWrapper clientRect; | |
182 | GetClientRect (hwnd, &clientRect); | |
183 | ||
184 | /* | |
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 | |
187 | around by not adjusting child upon minimization - it isn't really | |
188 | needed then, anyway. | |
189 | */ | |
190 | if (wParam != SIZE_MINIMIZED) | |
191 | { | |
192 | /* | |
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 (); | |
201 | ||
202 | ControlAdjuster::AdjustControls (hwnd, PropSheetControlsInfo, | |
203 | dX, dY); | |
204 | ||
205 | psd.pageRect.right += dX; | |
206 | psd.pageRect.bottom += dY; | |
207 | ||
208 | /* | |
209 | The pages are child windows, but don't have IDs. | |
210 | So change them by enumerating all childs and adjust all | |
211 | dialogs among them. | |
212 | */ | |
213 | if (psd.gotPage) | |
214 | EnumChildWindows (hwnd, &EnumPages, 0); | |
215 | } | |
216 | else | |
217 | { | |
218 | psd.clientRectValid = true; | |
219 | } | |
220 | /* | |
221 | Store away the current size and use it as the minmal window size. | |
222 | */ | |
223 | if (!psd.hasMinRect) | |
224 | { | |
225 | GetWindowRect (hwnd, &psd.minRect); | |
226 | psd.hasMinRect = true; | |
227 | } | |
228 | ||
229 | psd.lastClientRect = clientRect; | |
230 | } | |
231 | } | |
232 | break; | |
233 | case WM_GETMINMAXINFO: | |
234 | { | |
235 | if (psd.hasMinRect) | |
236 | { | |
237 | LPMINMAXINFO mmi = (LPMINMAXINFO)lParam; | |
238 | mmi->ptMinTrackSize.x = psd.minRect.width (); | |
239 | mmi->ptMinTrackSize.y = psd.minRect.height (); | |
240 | } | |
241 | } | |
242 | break; | |
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; | |
278 | } | |
279 | ||
280 | return CallWindowProc (psd.oldWndProc, | |
281 | hwnd, uMsg, wParam, lParam); | |
282 | } | |
283 | ||
284 | static int CALLBACK | |
285 | PropSheetProc (HWND hwndDlg, UINT uMsg, LPARAM lParam) | |
286 | { | |
287 | switch (uMsg) | |
288 | { | |
289 | case PSCB_PRECREATE: | |
290 | { | |
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 | ||
303 | // Add a minimize box to the sheet/wizard. | |
304 | if (((LPDLGTEMPLATEEX) lParam)->signature == 0xFFFF) | |
305 | { | |
306 | ((LPDLGTEMPLATEEX) lParam)->style |= additionalStyle; | |
307 | ((LPDLGTEMPLATEEX) lParam)->style &= ~forbiddenStyle; | |
308 | } | |
309 | else | |
310 | { | |
311 | ((LPDLGTEMPLATE) lParam)->style |= additionalStyle; | |
312 | ((LPDLGTEMPLATE) lParam)->style &= ~forbiddenStyle; | |
313 | } | |
314 | } | |
315 | return TRUE; | |
316 | case PSCB_INITIALIZED: | |
317 | { | |
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))); | |
323 | /* | |
324 | Hook into the window proc. | |
325 | We need to catch some messages for resizing. | |
326 | */ | |
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 | */ | |
336 | ChooserPage::SetHwndDialog (hwndDlg); | |
337 | } | |
338 | return TRUE; | |
339 | } | |
340 | return TRUE; | |
341 | } | |
342 | ||
343 | bool | |
344 | PropSheet::Create (const Window * Parent, DWORD Style) | |
345 | { | |
346 | PROPSHEETHEADERW p; | |
347 | ||
348 | PageHandles = CreatePages (); | |
349 | ||
350 | p.dwSize = sizeof (PROPSHEETHEADERW); | |
351 | p.dwFlags = PSH_NOAPPLYNOW | PSH_WIZARD | PSH_USECALLBACK | |
352 | /*| PSH_MODELESS */ | PSH_USEICONID; | |
353 | if (Parent != NULL) | |
354 | { | |
355 | p.hwndParent = Parent->GetHWND (); | |
356 | } | |
357 | else | |
358 | { | |
359 | p.hwndParent = NULL; | |
360 | } | |
361 | p.hInstance = GetInstance (); | |
362 | p.nPages = PropertyPages.size(); | |
363 | p.pszIcon = MAKEINTRESOURCEW(IDI_CYGWIN); | |
364 | p.nStartPage = 0; | |
365 | p.phpage = PageHandles; | |
366 | p.pfnCallback = PropSheetProc; | |
367 | ||
368 | // The winmain event loop actually resides in here. | |
369 | PropertySheetW (&p); | |
370 | ||
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 | ||
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 | |
399 | page, and then resizes itself to have the right metrics to contain the | |
400 | page and moves it to it's position. For our purposes, however, we need | |
401 | the final metrics of the page. So, the first time this method is called, | |
402 | we basically grab the size of the page, but calculate the top/left coords | |
403 | ourselves. | |
404 | */ | |
405 | ||
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 | ||
424 | SetWindowPos (page, 0, psd.pageRect.left, psd.pageRect.top, | |
425 | psd.pageRect.width (), psd.pageRect.height (), | |
426 | SWP_NOACTIVATE | SWP_NOZORDER); | |
427 | } | |
428 | ||
429 | void | |
430 | PropSheet::AddPage (PropertyPage * p) | |
431 | { | |
432 | // Add a page to the property sheet. | |
433 | p->YouAreBeingAddedToASheet (this); | |
434 | PropertyPages.push_back(p); | |
435 | } | |
436 | ||
437 | bool | |
438 | PropSheet::SetActivePage (int i) | |
439 | { | |
440 | // Posts a message to the message queue, so this won't block | |
441 | return static_cast < bool > (PropSheet_SetCurSel (GetHWND (), NULL, i)); | |
442 | } | |
443 | ||
444 | bool | |
445 | PropSheet::SetActivePageByID (int resource_id) | |
446 | { | |
447 | // Posts a message to the message queue, so this won't block | |
448 | return static_cast < bool > | |
449 | (PropSheet_SetCurSelByID (GetHWND (), resource_id)); | |
450 | } | |
451 | ||
452 | void | |
453 | PropSheet::SetButtons (DWORD flags) | |
454 | { | |
455 | // Posts a message to the message queue, so this won't block | |
456 | PropSheet_SetWizButtons (GetHWND (), flags); | |
457 | } | |
458 | ||
459 | void | |
460 | PropSheet::PressButton (int button) | |
461 | { | |
462 | PropSheet_PressButton (GetHWND (), button); | |
463 | } |