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