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