69e2b1e6fd7bc64ddfd7c7e8c2cd86b9ae1a16c6
[reactos.git] / reactos / lib / msi / dialog.c
1 /*
2 * Implementation of the Microsoft Installer (msi.dll)
3 *
4 * Copyright 2005 Mike McCormack for CodeWeavers
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21 #define COBJMACROS
22
23 #include <stdarg.h>
24
25 #include "windef.h"
26 #include "winbase.h"
27 #include "winuser.h"
28 #include "winnls.h"
29 #include "wingdi.h"
30 #include "msi.h"
31 #include "msipriv.h"
32 #include "msidefs.h"
33 #include "ocidl.h"
34 #include "olectl.h"
35 #include "richedit.h"
36
37 #include "wine/debug.h"
38 #include "wine/unicode.h"
39
40 #include "action.h"
41
42 WINE_DEFAULT_DEBUG_CHANNEL(msi);
43
44
45 struct msi_control_tag;
46 typedef struct msi_control_tag msi_control;
47 typedef UINT (*msi_handler)( msi_dialog *, msi_control *, WPARAM );
48
49 struct msi_control_tag
50 {
51 struct msi_control_tag *next;
52 HWND hwnd;
53 msi_handler handler;
54 LPWSTR property;
55 LPWSTR value;
56 IPicture *pic;
57 HICON hIcon;
58 WCHAR name[1];
59 };
60
61 typedef struct msi_font_tag
62 {
63 struct msi_font_tag *next;
64 HFONT hfont;
65 WCHAR name[1];
66 } msi_font;
67
68 struct msi_dialog_tag
69 {
70 MSIPACKAGE *package;
71 msi_dialog_event_handler event_handler;
72 BOOL finished;
73 INT scale;
74 DWORD attributes;
75 HWND hwnd;
76 LPWSTR default_font;
77 msi_font *font_list;
78 msi_control *control_list;
79 WCHAR name[1];
80 };
81
82 typedef UINT (*msi_dialog_control_func)( msi_dialog *dialog, MSIRECORD *rec );
83 struct control_handler
84 {
85 LPCWSTR control_type;
86 msi_dialog_control_func func;
87 };
88
89 typedef struct
90 {
91 msi_dialog* dialog;
92 msi_control *parent;
93 DWORD attributes;
94 } radio_button_group_descr;
95
96 const WCHAR szMsiDialogClass[] = {
97 'M','s','i','D','i','a','l','o','g','C','l','o','s','e','C','l','a','s','s',0
98 };
99 const WCHAR szMsiHiddenWindow[] = {
100 'M','s','i','H','i','d','d','e','n','W','i','n','d','o','w',0 };
101 const static WCHAR szStatic[] = { 'S','t','a','t','i','c',0 };
102 const static WCHAR szButton[] = { 'B','U','T','T','O','N', 0 };
103 const static WCHAR szButtonData[] = { 'M','S','I','D','A','T','A',0 };
104 static const WCHAR szText[] = { 'T','e','x','t',0 };
105 static const WCHAR szPushButton[] = { 'P','u','s','h','B','u','t','t','o','n',0 };
106 static const WCHAR szLine[] = { 'L','i','n','e',0 };
107 static const WCHAR szBitmap[] = { 'B','i','t','m','a','p',0 };
108 static const WCHAR szCheckBox[] = { 'C','h','e','c','k','B','o','x',0 };
109 static const WCHAR szScrollableText[] = {
110 'S','c','r','o','l','l','a','b','l','e','T','e','x','t',0 };
111 static const WCHAR szComboBox[] = { 'C','o','m','b','o','B','o','x',0 };
112 static const WCHAR szEdit[] = { 'E','d','i','t',0 };
113 static const WCHAR szMaskedEdit[] = { 'M','a','s','k','e','d','E','d','i','t',0 };
114 static const WCHAR szPathEdit[] = { 'P','a','t','h','E','d','i','t',0 };
115 static const WCHAR szRadioButtonGroup[] = {
116 'R','a','d','i','o','B','u','t','t','o','n','G','r','o','u','p',0 };
117 static const WCHAR szIcon[] = { 'I','c','o','n',0 };
118
119 static UINT msi_dialog_checkbox_handler( msi_dialog *, msi_control *, WPARAM );
120 static void msi_dialog_checkbox_sync_state( msi_dialog *, msi_control * );
121 static UINT msi_dialog_button_handler( msi_dialog *, msi_control *, WPARAM );
122 static UINT msi_dialog_edit_handler( msi_dialog *, msi_control *, WPARAM );
123 static UINT msi_dialog_radiogroup_handler( msi_dialog *, msi_control *, WPARAM param );
124 static UINT msi_dialog_evaluate_control_conditions( msi_dialog *dialog );
125 static LRESULT WINAPI MSIRadioGroup_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
126
127
128 /* dialog sequencing */
129
130 #define WM_MSI_DIALOG_CREATE (WM_USER+0x100)
131 #define WM_MSI_DIALOG_DESTROY (WM_USER+0x101)
132
133 static DWORD uiThreadId;
134 static HWND hMsiHiddenWindow;
135 static HMODULE hRichedit;
136
137 static INT msi_dialog_scale_unit( msi_dialog *dialog, INT val )
138 {
139 return (dialog->scale * val + 5) / 10;
140 }
141
142 static msi_control *msi_dialog_find_control( msi_dialog *dialog, LPCWSTR name )
143 {
144 msi_control *control;
145
146 for( control = dialog->control_list; control; control = control->next )
147 if( !strcmpW( control->name, name ) ) /* FIXME: case sensitive? */
148 break;
149 return control;
150 }
151
152 static msi_control *msi_dialog_find_control_by_hwnd( msi_dialog *dialog, HWND hwnd )
153 {
154 msi_control *control;
155
156 for( control = dialog->control_list; control; control = control->next )
157 if( hwnd == control->hwnd )
158 break;
159 return control;
160 }
161
162 /*
163 * msi_dialog_get_style
164 *
165 * Extract the {\style} string from the front of the text to display and
166 * update the pointer.
167 */
168 static LPWSTR msi_dialog_get_style( LPCWSTR *text )
169 {
170 LPWSTR ret = NULL;
171 LPCWSTR p = *text, q;
172 DWORD len;
173
174 if( *p++ != '{' )
175 return ret;
176 q = strchrW( p, '}' );
177 if( !q )
178 return ret;
179 *text = ++q;
180 if( *p++ != '\\' )
181 return ret;
182 len = q - p;
183
184 ret = HeapAlloc( GetProcessHeap(), 0, len*sizeof(WCHAR) );
185 if( !ret )
186 return ret;
187 memcpy( ret, p, len*sizeof(WCHAR) );
188 ret[len-1] = 0;
189 return ret;
190 }
191
192 static UINT msi_dialog_add_font( MSIRECORD *rec, LPVOID param )
193 {
194 msi_dialog *dialog = param;
195 msi_font *font;
196 LPCWSTR face, name;
197 LOGFONTW lf;
198 INT style;
199 HDC hdc;
200
201 /* create a font and add it to the list */
202 name = MSI_RecordGetString( rec, 1 );
203 font = HeapAlloc( GetProcessHeap(), 0,
204 sizeof *font + strlenW( name )*sizeof (WCHAR) );
205 strcpyW( font->name, name );
206 font->next = dialog->font_list;
207 dialog->font_list = font;
208
209 memset( &lf, 0, sizeof lf );
210 face = MSI_RecordGetString( rec, 2 );
211 lf.lfHeight = MSI_RecordGetInteger( rec, 3 );
212 style = MSI_RecordGetInteger( rec, 5 );
213 if( style & msidbTextStyleStyleBitsBold )
214 lf.lfWeight = FW_BOLD;
215 if( style & msidbTextStyleStyleBitsItalic )
216 lf.lfItalic = TRUE;
217 if( style & msidbTextStyleStyleBitsUnderline )
218 lf.lfUnderline = TRUE;
219 if( style & msidbTextStyleStyleBitsStrike )
220 lf.lfStrikeOut = TRUE;
221 lstrcpynW( lf.lfFaceName, face, LF_FACESIZE );
222
223 /* adjust the height */
224 hdc = GetDC( dialog->hwnd );
225 if (hdc)
226 {
227 lf.lfHeight = -MulDiv(lf.lfHeight, GetDeviceCaps(hdc, LOGPIXELSY), 72);
228 ReleaseDC( dialog->hwnd, hdc );
229 }
230
231 font->hfont = CreateFontIndirectW( &lf );
232
233 TRACE("Adding font style %s\n", debugstr_w(font->name) );
234
235 return ERROR_SUCCESS;
236 }
237
238 static msi_font *msi_dialog_find_font( msi_dialog *dialog, LPCWSTR name )
239 {
240 msi_font *font;
241
242 for( font = dialog->font_list; font; font = font->next )
243 if( !strcmpW( font->name, name ) ) /* FIXME: case sensitive? */
244 break;
245
246 return font;
247 }
248
249 static UINT msi_dialog_set_font( msi_dialog *dialog, HWND hwnd, LPCWSTR name )
250 {
251 msi_font *font;
252
253 font = msi_dialog_find_font( dialog, name );
254 if( font )
255 SendMessageW( hwnd, WM_SETFONT, (WPARAM) font->hfont, TRUE );
256 else
257 ERR("No font entry for %s\n", debugstr_w(name));
258 return ERROR_SUCCESS;
259 }
260
261 static UINT msi_dialog_build_font_list( msi_dialog *dialog )
262 {
263 static const WCHAR query[] = {
264 'S','E','L','E','C','T',' ','*',' ',
265 'F','R','O','M',' ','`','T','e','x','t','S','t','y','l','e','`',' ',0
266 };
267 UINT r;
268 MSIQUERY *view = NULL;
269
270 TRACE("dialog %p\n", dialog );
271
272 r = MSI_OpenQuery( dialog->package->db, &view, query );
273 if( r != ERROR_SUCCESS )
274 return r;
275
276 r = MSI_IterateRecords( view, NULL, msi_dialog_add_font, dialog );
277 msiobj_release( &view->hdr );
278
279 return r;
280 }
281
282 static msi_control *msi_dialog_create_window( msi_dialog *dialog,
283 MSIRECORD *rec, LPCWSTR szCls, LPCWSTR name, LPCWSTR text,
284 DWORD style, HWND parent )
285 {
286 DWORD x, y, width, height;
287 LPWSTR font = NULL, title = NULL;
288 msi_control *control;
289
290 style |= WS_CHILD;
291
292 control = HeapAlloc( GetProcessHeap(), 0,
293 sizeof *control + strlenW(name)*sizeof(WCHAR) );
294 strcpyW( control->name, name );
295 control->next = dialog->control_list;
296 dialog->control_list = control;
297 control->handler = NULL;
298 control->property = NULL;
299 control->value = NULL;
300 control->pic = NULL;
301 control->hIcon = NULL;
302
303 x = MSI_RecordGetInteger( rec, 4 );
304 y = MSI_RecordGetInteger( rec, 5 );
305 width = MSI_RecordGetInteger( rec, 6 );
306 height = MSI_RecordGetInteger( rec, 7 );
307
308 x = msi_dialog_scale_unit( dialog, x );
309 y = msi_dialog_scale_unit( dialog, y );
310 width = msi_dialog_scale_unit( dialog, width );
311 height = msi_dialog_scale_unit( dialog, height );
312
313 if( text )
314 {
315 font = msi_dialog_get_style( &text );
316 deformat_string( dialog->package, text, &title );
317 }
318
319 control->hwnd = CreateWindowW( szCls, title, style,
320 x, y, width, height, parent, NULL, NULL, NULL );
321
322 TRACE("Dialog %s control %s hwnd %p\n",
323 debugstr_w(dialog->name), debugstr_w(text), control->hwnd );
324
325 msi_dialog_set_font( dialog, control->hwnd,
326 font ? font : dialog->default_font );
327
328 HeapFree( GetProcessHeap(), 0, font );
329 HeapFree( GetProcessHeap(), 0, title );
330
331 return control;
332 }
333
334 /* called from the Control Event subscription code */
335 void msi_dialog_handle_event( msi_dialog* dialog, LPCWSTR control,
336 LPCWSTR attribute, MSIRECORD *rec )
337 {
338 msi_control* ctrl;
339 LPCWSTR text;
340
341 ctrl = msi_dialog_find_control( dialog, control );
342 if (!ctrl)
343 return;
344 if( lstrcmpW(attribute, szText) )
345 return;
346 text = MSI_RecordGetString( rec , 1 );
347 SetWindowTextW( ctrl->hwnd, text );
348 msi_dialog_check_messages( NULL );
349 }
350
351 static void msi_dialog_map_events(msi_dialog* dialog, LPCWSTR control)
352 {
353 static WCHAR Query[] = {
354 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
355 '`','E','v','e','n','t','M','a','p','p','i','n','g','`',' ',
356 'W','H','E','R','E',' ',
357 '`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',' ',
358 'A','N','D',' ',
359 '`','C','o','n','t','r','o','l','_','`',' ','=',' ','\'','%','s','\'',0
360 };
361 MSIRECORD *row;
362 LPCWSTR event, attribute;
363
364 row = MSI_QueryGetRecord( dialog->package->db, Query, dialog->name, control );
365 if (!row)
366 return;
367
368 event = MSI_RecordGetString( row, 3 );
369 attribute = MSI_RecordGetString( row, 4 );
370 ControlEvent_SubscribeToEvent( dialog->package, event, control, attribute );
371 msiobj_release( &row->hdr );
372 }
373
374 /* everything except radio buttons */
375 static msi_control *msi_dialog_add_control( msi_dialog *dialog,
376 MSIRECORD *rec, LPCWSTR szCls, DWORD style )
377 {
378 DWORD attributes;
379 LPCWSTR text, name;
380
381 name = MSI_RecordGetString( rec, 2 );
382 attributes = MSI_RecordGetInteger( rec, 8 );
383 text = MSI_RecordGetString( rec, 10 );
384 if( attributes & msidbControlAttributesVisible )
385 style |= WS_VISIBLE;
386 if( ~attributes & msidbControlAttributesEnabled )
387 style |= WS_DISABLED;
388
389 msi_dialog_map_events(dialog, name);
390
391 return msi_dialog_create_window( dialog, rec, szCls, name, text,
392 style, dialog->hwnd );
393 }
394
395 struct msi_text_info
396 {
397 WNDPROC oldproc;
398 DWORD attributes;
399 };
400
401 /*
402 * we don't erase our own background,
403 * so we have to make sure that the parent window redraws first
404 */
405 static void msi_text_on_settext( HWND hWnd )
406 {
407 HWND hParent;
408 RECT rc;
409
410 hParent = GetParent( hWnd );
411 GetWindowRect( hWnd, &rc );
412 MapWindowPoints( NULL, hParent, (LPPOINT) &rc, 2 );
413 InvalidateRect( hParent, &rc, TRUE );
414 }
415
416 static LRESULT WINAPI
417 MSIText_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
418 {
419 struct msi_text_info *info;
420 LRESULT r = 0;
421
422 TRACE("%p %04x %08x %08lx\n", hWnd, msg, wParam, lParam);
423
424 info = GetPropW(hWnd, szButtonData);
425
426 if( msg == WM_CTLCOLORSTATIC &&
427 ( info->attributes & msidbControlAttributesTransparent ) )
428 {
429 SetBkMode( (HDC)wParam, TRANSPARENT );
430 return (LRESULT) GetStockObject(NULL_BRUSH);
431 }
432
433 r = CallWindowProcW(info->oldproc, hWnd, msg, wParam, lParam);
434
435 switch( msg )
436 {
437 case WM_SETTEXT:
438 msi_text_on_settext( hWnd );
439 break;
440 case WM_NCDESTROY:
441 HeapFree( GetProcessHeap(), 0, info );
442 RemovePropW( hWnd, szButtonData );
443 break;
444 }
445
446 return r;
447 }
448
449 static UINT msi_dialog_text_control( msi_dialog *dialog, MSIRECORD *rec )
450 {
451 msi_control *control;
452 struct msi_text_info *info;
453
454 TRACE("%p %p\n", dialog, rec);
455
456 control = msi_dialog_add_control( dialog, rec, szStatic, SS_LEFT | WS_GROUP );
457 if( !control )
458 return ERROR_FUNCTION_FAILED;
459
460 info = HeapAlloc( GetProcessHeap(), 0, sizeof *info );
461 if( !info )
462 return ERROR_SUCCESS;
463
464 info->attributes = MSI_RecordGetInteger( rec, 8 );
465 if( info->attributes & msidbControlAttributesTransparent )
466 SetWindowLongPtrW( control->hwnd, GWL_EXSTYLE, WS_EX_TRANSPARENT );
467
468 info->oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
469 (LONG_PTR)MSIText_WndProc );
470 SetPropW( control->hwnd, szButtonData, info );
471
472 return ERROR_SUCCESS;
473 }
474
475 static UINT msi_dialog_button_control( msi_dialog *dialog, MSIRECORD *rec )
476 {
477 msi_control *control;
478
479 TRACE("%p %p\n", dialog, rec);
480
481 control = msi_dialog_add_control( dialog, rec, szButton, WS_TABSTOP );
482 control->handler = msi_dialog_button_handler;
483
484 return ERROR_SUCCESS;
485 }
486
487 static LPWSTR msi_get_checkbox_value( msi_dialog *dialog, LPCWSTR prop )
488 {
489 const static WCHAR query[] = {
490 'S','E','L','E','C','T',' ','*',' ',
491 'F','R','O','M',' ','`','C','h','e','c','k','B','o','x',' ','`',
492 'W','H','E','R','E',' ',
493 '`','P','r','o','p','e','r','t','y','`',' ','=',' ',
494 '\'','%','s','\'',0
495 };
496 MSIRECORD *rec = NULL;
497 LPCWSTR val = NULL;
498 LPWSTR ret = NULL;
499
500 /* find if there is a value associated with the checkbox */
501 rec = MSI_QueryGetRecord( dialog->package->db, query, prop );
502 if (!rec)
503 return ret;
504
505 val = MSI_RecordGetString( rec, 2 );
506 if (val)
507 {
508 deformat_string( dialog->package, val, &ret );
509 if( ret && !ret[0] )
510 {
511 HeapFree( GetProcessHeap(), 0, ret );
512 ret = NULL;
513 }
514 }
515 msiobj_release( &rec->hdr );
516 if (ret)
517 return ret;
518
519 ret = load_dynamic_property(dialog->package, prop, NULL);
520 if( ret && !ret[0] )
521 {
522 HeapFree( GetProcessHeap(), 0, ret );
523 ret = NULL;
524 }
525
526 return ret;
527 }
528
529 static UINT msi_dialog_checkbox_control( msi_dialog *dialog, MSIRECORD *rec )
530 {
531 msi_control *control;
532 LPCWSTR prop;
533
534 TRACE("%p %p\n", dialog, rec);
535
536 control = msi_dialog_add_control( dialog, rec, szButton,
537 BS_CHECKBOX | BS_MULTILINE | WS_TABSTOP );
538 control->handler = msi_dialog_checkbox_handler;
539 prop = MSI_RecordGetString( rec, 9 );
540 if( prop )
541 {
542 control->property = strdupW( prop );
543 control->value = msi_get_checkbox_value( dialog, prop );
544 TRACE("control %s value %s\n", debugstr_w(control->property),
545 debugstr_w(control->value));
546 }
547 msi_dialog_checkbox_sync_state( dialog, control );
548
549 return ERROR_SUCCESS;
550 }
551
552 static UINT msi_dialog_line_control( msi_dialog *dialog, MSIRECORD *rec )
553 {
554 TRACE("%p %p\n", dialog, rec);
555
556 msi_dialog_add_control( dialog, rec, szStatic, SS_ETCHEDHORZ | SS_SUNKEN );
557 return ERROR_SUCCESS;
558 }
559
560 struct msi_streamin_info
561 {
562 LPSTR string;
563 DWORD offset;
564 DWORD length;
565 };
566
567 static DWORD CALLBACK
568 msi_richedit_stream_in( DWORD_PTR arg, LPBYTE buffer, LONG count, LONG *pcb )
569 {
570 struct msi_streamin_info *info = (struct msi_streamin_info*) arg;
571
572 if( (count + info->offset) > info->length )
573 count = info->length - info->offset;
574 memcpy( buffer, &info->string[ info->offset ], count );
575 *pcb = count;
576 info->offset += count;
577
578 TRACE("%ld/%ld\n", info->offset, info->length);
579
580 return 0;
581 }
582
583 static UINT msi_dialog_scrolltext_control( msi_dialog *dialog, MSIRECORD *rec )
584 {
585 const static WCHAR szRichEdit20W[] = {
586 'R','i','c','h','E','d','i','t','2','0','W',0
587 };
588 struct msi_streamin_info info;
589 msi_control *control;
590 LPCWSTR text;
591 EDITSTREAM es;
592 DWORD style;
593
594 style = WS_BORDER | ES_MULTILINE | WS_VSCROLL |
595 ES_READONLY | ES_AUTOVSCROLL | WS_TABSTOP;
596 control = msi_dialog_add_control( dialog, rec, szRichEdit20W, style );
597
598 text = MSI_RecordGetString( rec, 10 );
599 info.string = strdupWtoA( text );
600 info.offset = 0;
601 info.length = lstrlenA( info.string ) + 1;
602
603 es.dwCookie = (DWORD_PTR) &info;
604 es.dwError = 0;
605 es.pfnCallback = msi_richedit_stream_in;
606
607 SendMessageW( control->hwnd, EM_STREAMIN, SF_RTF, (LPARAM) &es );
608
609 HeapFree( GetProcessHeap(), 0, info.string );
610
611 return ERROR_SUCCESS;
612 }
613
614 static MSIRECORD *msi_get_binary_record( MSIDATABASE *db, LPCWSTR name )
615 {
616 const static WCHAR query[] = {
617 's','e','l','e','c','t',' ','*',' ',
618 'f','r','o','m',' ','B','i','n','a','r','y',' ',
619 'w','h','e','r','e',' ',
620 '`','N','a','m','e','`',' ','=',' ','\'','%','s','\'',0
621 };
622
623 return MSI_QueryGetRecord( db, query, name );
624 }
625
626 static UINT msi_load_bitmap( MSIDATABASE *db, LPCWSTR name, IPicture **pic )
627 {
628 MSIRECORD *rec = NULL;
629 IStream *stm = NULL;
630 UINT r;
631
632 rec = msi_get_binary_record( db, name );
633 if( !rec )
634 return ERROR_FUNCTION_FAILED;
635
636 r = MSI_RecordGetIStream( rec, 2, &stm );
637 msiobj_release( &rec->hdr );
638 if( r != ERROR_SUCCESS )
639 return r;
640
641 r = OleLoadPicture( stm, 0, TRUE, &IID_IPicture, (LPVOID*) pic );
642 IStream_Release( stm );
643 if( FAILED( r ) )
644 return ERROR_FUNCTION_FAILED;
645
646 return ERROR_SUCCESS;
647 }
648
649 static UINT msi_dialog_bitmap_control( msi_dialog *dialog, MSIRECORD *rec )
650 {
651 IPicture *pic = NULL;
652 msi_control *control;
653 OLE_HANDLE hBitmap = 0;
654 LPCWSTR text;
655 UINT r;
656
657 control = msi_dialog_add_control( dialog, rec, szStatic,
658 SS_BITMAP | SS_LEFT | SS_CENTERIMAGE );
659 text = MSI_RecordGetString( rec, 10 );
660 r = msi_load_bitmap( dialog->package->db, text, &pic );
661 if( r == ERROR_SUCCESS )
662 {
663 r = IPicture_get_Handle( pic, &hBitmap );
664 if( SUCCEEDED( r ) )
665 SendMessageW( control->hwnd, STM_SETIMAGE, IMAGE_BITMAP, hBitmap );
666 control->pic = pic;
667 }
668
669 return ERROR_SUCCESS;
670 }
671
672 static LPWSTR msi_create_tmp_path(void)
673 {
674 WCHAR tmp[MAX_PATH];
675 LPWSTR path = NULL;
676 static const WCHAR prefix[] = { 'm','s','i',0 };
677 DWORD len, r;
678
679 r = GetTempPathW( MAX_PATH, tmp );
680 if( !r )
681 return path;
682 len = lstrlenW( tmp ) + 20;
683 path = HeapAlloc( GetProcessHeap(), 0, len * sizeof (WCHAR) );
684 if( path )
685 {
686 r = GetTempFileNameW( tmp, prefix, 0, path );
687 if (!r)
688 {
689 HeapFree( GetProcessHeap(), 0, path );
690 path = NULL;
691 }
692 }
693 return path;
694 }
695
696 static UINT
697 msi_load_icon( MSIDATABASE *db, LPCWSTR name, DWORD attributes, HICON *picon )
698 {
699 UINT r = ERROR_FUNCTION_FAILED;
700 LPWSTR tmp;
701 MSIRECORD *rec;
702 HICON hicon = 0;
703
704 TRACE("loading %s\n", debugstr_w( name ) );
705
706 tmp = msi_create_tmp_path();
707 if( !tmp )
708 return r;
709
710 rec = msi_get_binary_record( db, name );
711 if( rec )
712 {
713 r = MSI_RecordStreamToFile( rec, 2, tmp );
714 if( r == ERROR_SUCCESS )
715 {
716 DWORD cx = 0, cy = 0, flags = LR_LOADFROMFILE | LR_DEFAULTSIZE;
717
718 if( attributes & msidbControlAttributesFixedSize )
719 {
720 flags &= ~LR_DEFAULTSIZE;
721 if( attributes & msidbControlAttributesIconSize16 )
722 {
723 cx += 16;
724 cy += 16;
725 }
726 if( attributes & msidbControlAttributesIconSize32 )
727 {
728 cx += 32;
729 cy += 32;
730 }
731 /* msidbControlAttributesIconSize48 handled by above logic */
732 }
733
734 hicon = LoadImageW( 0, tmp, IMAGE_ICON, cx, cy, flags );
735 if( hicon )
736 *picon = hicon;
737 else
738 ERR("failed to load icon from %s\n", debugstr_w( tmp ));
739 DeleteFileW( tmp );
740 }
741 msiobj_release( &rec->hdr );
742 }
743
744 HeapFree( GetProcessHeap(), 0, tmp );
745
746 return r;
747 }
748
749 static UINT msi_dialog_icon_control( msi_dialog *dialog, MSIRECORD *rec )
750 {
751 msi_control *control;
752 DWORD attributes;
753 HICON hIcon = 0;
754 LPCWSTR text;
755 UINT r;
756
757 TRACE("\n");
758
759 control = msi_dialog_add_control( dialog, rec, szStatic,
760 SS_ICON | SS_CENTERIMAGE | WS_GROUP );
761 text = MSI_RecordGetString( rec, 10 );
762 attributes = MSI_RecordGetInteger( rec, 8 );
763 r = msi_load_icon( dialog->package->db, text, attributes, &hIcon );
764 if( r == ERROR_SUCCESS )
765 {
766 r = SendMessageW( control->hwnd, STM_SETICON, (WPARAM) hIcon, 0 );
767 control->hIcon = hIcon;
768 }
769 else
770 ERR("Failed to load bitmap %s\n", debugstr_w(text));
771 return ERROR_SUCCESS;
772 }
773
774 static UINT msi_dialog_combo_control( msi_dialog *dialog, MSIRECORD *rec )
775 {
776 static const WCHAR szCombo[] = { 'C','O','M','B','O','B','O','X',0 };
777
778 msi_dialog_add_control( dialog, rec, szCombo,
779 SS_BITMAP | SS_LEFT | SS_CENTERIMAGE );
780 return ERROR_SUCCESS;
781 }
782
783 static UINT msi_dialog_edit_control( msi_dialog *dialog, MSIRECORD *rec )
784 {
785 msi_control *control;
786 LPCWSTR prop;
787 LPWSTR val;
788
789 control = msi_dialog_add_control( dialog, rec, szEdit,
790 WS_BORDER | WS_TABSTOP );
791 control->handler = msi_dialog_edit_handler;
792 prop = MSI_RecordGetString( rec, 9 );
793 if( prop )
794 control->property = strdupW( prop );
795 val = load_dynamic_property( dialog->package, control->property, NULL );
796 SetWindowTextW( control->hwnd, val );
797 HeapFree( GetProcessHeap(), 0, val );
798 return ERROR_SUCCESS;
799 }
800
801 /******************** Masked Edit ********************************************/
802
803 #define MASK_MAX_GROUPS 10
804
805 struct msi_mask_group
806 {
807 UINT len;
808 UINT ofs;
809 WCHAR type;
810 HWND hwnd;
811 };
812
813 struct msi_maskedit_info
814 {
815 msi_dialog *dialog;
816 WNDPROC oldproc;
817 HWND hwnd;
818 LPWSTR prop;
819 UINT num_chars;
820 UINT num_groups;
821 struct msi_mask_group group[MASK_MAX_GROUPS];
822 };
823
824 static void msi_mask_control_change( struct msi_maskedit_info *info )
825 {
826 LPWSTR val;
827 UINT i, n, r;
828
829 val = HeapAlloc( GetProcessHeap(), 0, (info->num_chars+1)*sizeof(WCHAR) );
830 for( i=0, n=0; i<info->num_groups; i++ )
831 {
832 if( (info->group[i].len + n) > info->num_chars )
833 {
834 ERR("can't fit control %d text into template\n",i);
835 break;
836 }
837 r = GetWindowTextW( info->group[i].hwnd, &val[n], info->group[i].len+1 );
838 if( r != info->group[i].len )
839 break;
840 n += r;
841 }
842
843 TRACE("%d/%d controls were good\n", i, info->num_groups);
844
845 if( i == info->num_groups )
846 {
847 TRACE("Set property %s to %s\n",
848 debugstr_w(info->prop), debugstr_w(val) );
849 CharUpperBuffW( val, info->num_chars );
850 MSI_SetPropertyW( info->dialog->package, info->prop, val );
851 msi_dialog_evaluate_control_conditions( info->dialog );
852 }
853 HeapFree( GetProcessHeap(), 0, val );
854 }
855
856 static LRESULT WINAPI
857 MSIMaskedEdit_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
858 {
859 struct msi_maskedit_info *info;
860 HRESULT r;
861
862 TRACE("%p %04x %08x %08lx\n", hWnd, msg, wParam, lParam);
863
864 info = GetPropW(hWnd, szButtonData);
865
866 r = CallWindowProcW(info->oldproc, hWnd, msg, wParam, lParam);
867
868 switch( msg )
869 {
870 case WM_COMMAND:
871 if (HIWORD(wParam) == EN_CHANGE)
872 msi_mask_control_change( info );
873 break;
874 case WM_NCDESTROY:
875 HeapFree( GetProcessHeap(), 0, info->prop );
876 HeapFree( GetProcessHeap(), 0, info );
877 RemovePropW( hWnd, szButtonData );
878 break;
879 }
880
881 return r;
882 }
883
884 /* fish the various bits of the property out and put them in the control */
885 static void
886 msi_maskedit_set_text( struct msi_maskedit_info *info, LPCWSTR text )
887 {
888 LPCWSTR p;
889 UINT i;
890
891 p = text;
892 for( i = 0; i < info->num_groups; i++ )
893 {
894 if( info->group[i].len < lstrlenW( p ) )
895 {
896 LPWSTR chunk = strdupW( p );
897 chunk[ info->group[i].len ] = 0;
898 SetWindowTextW( info->group[i].hwnd, chunk );
899 HeapFree( GetProcessHeap(), 0, chunk );
900 }
901 else
902 {
903 SetWindowTextW( info->group[i].hwnd, p );
904 break;
905 }
906 p += info->group[i].len;
907 }
908 }
909
910 static struct msi_maskedit_info * msi_dialog_parse_groups( LPCWSTR mask )
911 {
912 struct msi_maskedit_info * info = NULL;
913 int i = 0, n = 0, total = 0;
914 LPCWSTR p;
915
916 TRACE("masked control, template %s\n", debugstr_w(mask));
917
918 if( !mask )
919 return info;
920
921 p = strchrW(mask, '<');
922 if( !p )
923 return info;
924
925 info = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof *info );
926 if( !info )
927 return info;
928
929 p++;
930 for( i=0; i<MASK_MAX_GROUPS; i++ )
931 {
932 /* stop at the end of the string */
933 if( p[0] == 0 || p[0] == '>' )
934 break;
935
936 /* count the number of the same identifier */
937 for( n=0; p[n] == p[0]; n++ )
938 ;
939 info->group[i].ofs = total;
940 info->group[i].type = p[0];
941 if( p[n] == '=' )
942 {
943 n++;
944 total++; /* an extra not part of the group */
945 }
946 info->group[i].len = n;
947 total += n;
948 p += n;
949 }
950
951 TRACE("%d characters in %d groups\n", total, info->num_groups );
952 if( i == MASK_MAX_GROUPS )
953 ERR("too many groups in PIDTemplate %s\n", debugstr_w(mask));
954
955 info->num_chars = total;
956 info->num_groups = i;
957
958 return info;
959 }
960
961 static void
962 msi_maskedit_create_children( struct msi_maskedit_info *info )
963 {
964 DWORD width, height, style, wx, ww;
965 LPCWSTR text, font = NULL;
966 RECT rect;
967 HWND hwnd;
968 UINT i;
969
970 style = WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP;
971
972 GetClientRect( info->hwnd, &rect );
973
974 width = rect.right - rect.left;
975 height = rect.bottom - rect.top;
976
977 if( text )
978 font = msi_dialog_get_style( &text );
979
980 for( i = 0; i < info->num_groups; i++ )
981 {
982 wx = (info->group[i].ofs * width) / info->num_chars;
983 ww = (info->group[i].len * width) / info->num_chars;
984
985 hwnd = CreateWindowW( szEdit, NULL, style, wx, 0, ww, height,
986 info->hwnd, NULL, NULL, NULL );
987 if( !hwnd )
988 {
989 ERR("failed to create mask edit sub window\n");
990 break;
991 }
992
993 SendMessageW( hwnd, EM_LIMITTEXT, info->group[i].len, 0 );
994
995 msi_dialog_set_font( info->dialog, hwnd,
996 font ? font : info->dialog->default_font );
997 info->group[i].hwnd = hwnd;
998 }
999 }
1000
1001 /* office 2003 uses "73931<````=````=````=````=`````>@@@@@" */
1002 static UINT msi_dialog_maskedit_control( msi_dialog *dialog, MSIRECORD *rec )
1003 {
1004 const static WCHAR pidt[] = {'P','I','D','T','e','m','p','l','a','t','e',0};
1005 LPWSTR mask = NULL, title = NULL, val = NULL;
1006 struct msi_maskedit_info *info = NULL;
1007 UINT ret = ERROR_SUCCESS;
1008 msi_control *control;
1009 LPCWSTR prop;
1010
1011 mask = load_dynamic_property( dialog->package, pidt, NULL );
1012 if( !mask )
1013 {
1014 ERR("PIDTemplate is empty\n");
1015 goto end;
1016 }
1017
1018 info = msi_dialog_parse_groups( mask );
1019 if( !info )
1020 {
1021 ERR("template %s is invalid\n", debugstr_w(mask));
1022 goto end;
1023 }
1024
1025 info->dialog = dialog;
1026
1027 control = msi_dialog_add_control( dialog, rec, szStatic,
1028 SS_OWNERDRAW | WS_GROUP | WS_VISIBLE );
1029 if( !control )
1030 {
1031 ERR("Failed to create maskedit container\n");
1032 ret = ERROR_FUNCTION_FAILED;
1033 goto end;
1034 }
1035 SetWindowLongPtrW( control->hwnd, GWL_EXSTYLE, WS_EX_CONTROLPARENT );
1036
1037 info->hwnd = control->hwnd;
1038
1039 /* subclass the static control */
1040 info->oldproc = (WNDPROC) SetWindowLongPtrW( info->hwnd, GWLP_WNDPROC,
1041 (LONG_PTR)MSIMaskedEdit_WndProc );
1042 SetPropW( control->hwnd, szButtonData, info );
1043
1044 prop = MSI_RecordGetString( rec, 9 );
1045 if( prop )
1046 info->prop = strdupW( prop );
1047
1048 msi_maskedit_create_children( info );
1049
1050 if( prop )
1051 {
1052 val = load_dynamic_property( dialog->package, prop, NULL );
1053 if( val )
1054 {
1055 msi_maskedit_set_text( info, val );
1056 HeapFree( GetProcessHeap(), 0, val );
1057 }
1058 }
1059
1060 end:
1061 if( ret != ERROR_SUCCESS )
1062 HeapFree( GetProcessHeap(), 0, info );
1063 HeapFree( GetProcessHeap(), 0, title );
1064 HeapFree( GetProcessHeap(), 0, mask );
1065 return ret;
1066 }
1067
1068 /******************** Path Edit ********************************************/
1069
1070 static UINT msi_dialog_pathedit_control( msi_dialog *dialog, MSIRECORD *rec )
1071 {
1072 FIXME("not implemented properly\n");
1073 return msi_dialog_edit_control( dialog, rec );
1074 }
1075
1076 /* radio buttons are a bit different from normal controls */
1077 static UINT msi_dialog_create_radiobutton( MSIRECORD *rec, LPVOID param )
1078 {
1079 radio_button_group_descr *group = (radio_button_group_descr *)param;
1080 msi_dialog *dialog = group->dialog;
1081 msi_control *control;
1082 LPCWSTR prop, text, name;
1083 DWORD style;
1084 DWORD attributes = group->attributes;
1085
1086 style = WS_CHILD | BS_AUTORADIOBUTTON | BS_MULTILINE | WS_TABSTOP;
1087 name = MSI_RecordGetString( rec, 3 );
1088 text = MSI_RecordGetString( rec, 8 );
1089 if( attributes & 1 )
1090 style |= WS_VISIBLE;
1091 if( ~attributes & 2 )
1092 style |= WS_DISABLED;
1093
1094 control = msi_dialog_create_window( dialog, rec, szButton, name, text,
1095 style, group->parent->hwnd );
1096 control->handler = msi_dialog_radiogroup_handler;
1097
1098 prop = MSI_RecordGetString( rec, 1 );
1099 if( prop )
1100 control->property = strdupW( prop );
1101
1102 return ERROR_SUCCESS;
1103 }
1104
1105 static UINT msi_dialog_radiogroup_control( msi_dialog *dialog, MSIRECORD *rec )
1106 {
1107 static const WCHAR query[] = {
1108 'S','E','L','E','C','T',' ','*',' ',
1109 'F','R','O','M',' ','R','a','d','i','o','B','u','t','t','o','n',' ',
1110 'W','H','E','R','E',' ',
1111 '`','P','r','o','p','e','r','t','y','`',' ','=',' ','\'','%','s','\'',0};
1112 UINT r;
1113 LPCWSTR prop;
1114 msi_control *control;
1115 MSIQUERY *view = NULL;
1116 radio_button_group_descr group;
1117 MSIPACKAGE *package = dialog->package;
1118 WNDPROC oldproc;
1119
1120 prop = MSI_RecordGetString( rec, 9 );
1121
1122 TRACE("%p %p %s\n", dialog, rec, debugstr_w( prop ));
1123
1124 /* Create parent group box to hold radio buttons */
1125 control = msi_dialog_add_control( dialog, rec, szButton, BS_OWNERDRAW|WS_GROUP );
1126 if( !control )
1127 return ERROR_FUNCTION_FAILED;
1128
1129 oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
1130 (LONG_PTR)MSIRadioGroup_WndProc );
1131 SetPropW(control->hwnd, szButtonData, oldproc);
1132 SetWindowLongPtrW( control->hwnd, GWL_EXSTYLE, WS_EX_CONTROLPARENT );
1133
1134 if( prop )
1135 control->property = strdupW( prop );
1136
1137 /* query the Radio Button table for all control in this group */
1138 r = MSI_OpenQuery( package->db, &view, query, prop );
1139 if( r != ERROR_SUCCESS )
1140 {
1141 ERR("query failed for dialog %s radio group %s\n",
1142 debugstr_w(dialog->name), debugstr_w(prop));
1143 return ERROR_INVALID_PARAMETER;
1144 }
1145
1146 group.dialog = dialog;
1147 group.parent = control;
1148 group.attributes = MSI_RecordGetInteger( rec, 8 );
1149
1150 r = MSI_IterateRecords( view, 0, msi_dialog_create_radiobutton, &group );
1151 msiobj_release( &view->hdr );
1152
1153 return r;
1154 }
1155
1156 struct control_handler msi_dialog_handler[] =
1157 {
1158 { szText, msi_dialog_text_control },
1159 { szPushButton, msi_dialog_button_control },
1160 { szLine, msi_dialog_line_control },
1161 { szBitmap, msi_dialog_bitmap_control },
1162 { szCheckBox, msi_dialog_checkbox_control },
1163 { szScrollableText, msi_dialog_scrolltext_control },
1164 { szComboBox, msi_dialog_combo_control },
1165 { szEdit, msi_dialog_edit_control },
1166 { szMaskedEdit, msi_dialog_maskedit_control },
1167 { szPathEdit, msi_dialog_pathedit_control },
1168 { szRadioButtonGroup, msi_dialog_radiogroup_control },
1169 { szIcon, msi_dialog_icon_control },
1170 };
1171
1172 #define NUM_CONTROL_TYPES (sizeof msi_dialog_handler/sizeof msi_dialog_handler[0])
1173
1174 static UINT msi_dialog_create_controls( MSIRECORD *rec, LPVOID param )
1175 {
1176 msi_dialog *dialog = param;
1177 LPCWSTR control_type;
1178 UINT i;
1179
1180 /* find and call the function that can create this type of control */
1181 control_type = MSI_RecordGetString( rec, 3 );
1182 for( i=0; i<NUM_CONTROL_TYPES; i++ )
1183 if (!strcmpiW( msi_dialog_handler[i].control_type, control_type ))
1184 break;
1185 if( i != NUM_CONTROL_TYPES )
1186 msi_dialog_handler[i].func( dialog, rec );
1187 else
1188 ERR("no handler for element type %s\n", debugstr_w(control_type));
1189
1190 return ERROR_SUCCESS;
1191 }
1192
1193 static UINT msi_dialog_fill_controls( msi_dialog *dialog )
1194 {
1195 static const WCHAR query[] = {
1196 'S','E','L','E','C','T',' ','*',' ',
1197 'F','R','O','M',' ','C','o','n','t','r','o','l',' ',
1198 'W','H','E','R','E',' ',
1199 '`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',0};
1200 UINT r;
1201 MSIQUERY *view = NULL;
1202 MSIPACKAGE *package = dialog->package;
1203
1204 TRACE("%p %s\n", dialog, debugstr_w(dialog->name) );
1205
1206 /* query the Control table for all the elements of the control */
1207 r = MSI_OpenQuery( package->db, &view, query, dialog->name );
1208 if( r != ERROR_SUCCESS )
1209 {
1210 ERR("query failed for dialog %s\n", debugstr_w(dialog->name));
1211 return ERROR_INVALID_PARAMETER;
1212 }
1213
1214 r = MSI_IterateRecords( view, 0, msi_dialog_create_controls, dialog );
1215 msiobj_release( &view->hdr );
1216
1217 return r;
1218 }
1219
1220 static UINT msi_dialog_set_control_condition( MSIRECORD *rec, LPVOID param )
1221 {
1222 static const WCHAR szHide[] = { 'H','i','d','e',0 };
1223 static const WCHAR szShow[] = { 'S','h','o','w',0 };
1224 static const WCHAR szDisable[] = { 'D','i','s','a','b','l','e',0 };
1225 static const WCHAR szEnable[] = { 'E','n','a','b','l','e',0 };
1226 msi_dialog *dialog = param;
1227 msi_control *control;
1228 LPCWSTR name, action, condition;
1229 UINT r;
1230
1231 name = MSI_RecordGetString( rec, 2 );
1232 action = MSI_RecordGetString( rec, 3 );
1233 condition = MSI_RecordGetString( rec, 4 );
1234 r = MSI_EvaluateConditionW( dialog->package, condition );
1235 control = msi_dialog_find_control( dialog, name );
1236 if( r && control )
1237 {
1238 TRACE("%s control %s\n", debugstr_w(action), debugstr_w(name));
1239
1240 /* FIXME: case sensitive? */
1241 if(!lstrcmpW(action, szHide))
1242 ShowWindow(control->hwnd, SW_HIDE);
1243 else if(!strcmpW(action, szShow))
1244 ShowWindow(control->hwnd, SW_SHOW);
1245 else if(!strcmpW(action, szDisable))
1246 EnableWindow(control->hwnd, FALSE);
1247 else if(!strcmpW(action, szEnable))
1248 EnableWindow(control->hwnd, TRUE);
1249 else
1250 FIXME("Unhandled action %s\n", debugstr_w(action));
1251 }
1252
1253 return ERROR_SUCCESS;
1254 }
1255
1256 static UINT msi_dialog_evaluate_control_conditions( msi_dialog *dialog )
1257 {
1258 static const WCHAR query[] = {
1259 'S','E','L','E','C','T',' ','*',' ',
1260 'F','R','O','M',' ',
1261 'C','o','n','t','r','o','l','C','o','n','d','i','t','i','o','n',' ',
1262 'W','H','E','R','E',' ',
1263 '`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',0
1264 };
1265 UINT r;
1266 MSIQUERY *view = NULL;
1267 MSIPACKAGE *package = dialog->package;
1268
1269 TRACE("%p %s\n", dialog, debugstr_w(dialog->name) );
1270
1271 /* query the Control table for all the elements of the control */
1272 r = MSI_OpenQuery( package->db, &view, query, dialog->name );
1273 if( r != ERROR_SUCCESS )
1274 {
1275 ERR("query failed for dialog %s\n", debugstr_w(dialog->name));
1276 return ERROR_INVALID_PARAMETER;
1277 }
1278
1279 r = MSI_IterateRecords( view, 0, msi_dialog_set_control_condition, dialog );
1280 msiobj_release( &view->hdr );
1281
1282 return r;
1283 }
1284
1285 /* figure out the height of 10 point MS Sans Serif */
1286 static INT msi_dialog_get_sans_serif_height( HWND hwnd )
1287 {
1288 static const WCHAR szSansSerif[] = {
1289 'M','S',' ','S','a','n','s',' ','S','e','r','i','f',0 };
1290 LOGFONTW lf;
1291 TEXTMETRICW tm;
1292 BOOL r;
1293 LONG height = 0;
1294 HFONT hFont, hOldFont;
1295 HDC hdc;
1296
1297 hdc = GetDC( hwnd );
1298 if (hdc)
1299 {
1300 memset( &lf, 0, sizeof lf );
1301 lf.lfHeight = MulDiv(10, GetDeviceCaps(hdc, LOGPIXELSY), 72);
1302 strcpyW( lf.lfFaceName, szSansSerif );
1303 hFont = CreateFontIndirectW(&lf);
1304 if (hFont)
1305 {
1306 hOldFont = SelectObject( hdc, hFont );
1307 r = GetTextMetricsW( hdc, &tm );
1308 if (r)
1309 height = tm.tmHeight;
1310 SelectObject( hdc, hOldFont );
1311 DeleteObject( hFont );
1312 }
1313 ReleaseDC( hwnd, hdc );
1314 }
1315 return height;
1316 }
1317
1318 /* fetch the associated record from the Dialog table */
1319 static MSIRECORD *msi_get_dialog_record( msi_dialog *dialog )
1320 {
1321 static const WCHAR query[] = {
1322 'S','E','L','E','C','T',' ','*',' ',
1323 'F','R','O','M',' ','D','i','a','l','o','g',' ',
1324 'W','H','E','R','E',' ',
1325 '`','D','i','a','l','o','g','`',' ','=',' ','\'','%','s','\'',0};
1326 MSIPACKAGE *package = dialog->package;
1327 MSIRECORD *rec = NULL;
1328
1329 TRACE("%p %s\n", dialog, debugstr_w(dialog->name) );
1330
1331 rec = MSI_QueryGetRecord( package->db, query, dialog->name );
1332 if( !rec )
1333 ERR("query failed for dialog %s\n", debugstr_w(dialog->name));
1334
1335 return rec;
1336 }
1337
1338 static void msi_dialog_adjust_dialog_size( msi_dialog *dialog, LPSIZE sz )
1339 {
1340 RECT rect;
1341 LONG style;
1342
1343 /* turn the client size into the window rectangle */
1344 rect.left = 0;
1345 rect.top = 0;
1346 rect.right = msi_dialog_scale_unit( dialog, sz->cx );
1347 rect.bottom = msi_dialog_scale_unit( dialog, sz->cy );
1348 style = GetWindowLongPtrW( dialog->hwnd, GWL_STYLE );
1349 AdjustWindowRect( &rect, style, FALSE );
1350 sz->cx = rect.right - rect.left;
1351 sz->cy = rect.bottom - rect.top;
1352 }
1353
1354 static LRESULT msi_dialog_oncreate( HWND hwnd, LPCREATESTRUCTW cs )
1355 {
1356 static const WCHAR df[] = {
1357 'D','e','f','a','u','l','t','U','I','F','o','n','t',0 };
1358 msi_dialog *dialog = (msi_dialog*) cs->lpCreateParams;
1359 MSIRECORD *rec = NULL;
1360 LPCWSTR text;
1361 LPWSTR title = NULL;
1362 SIZE size;
1363
1364 TRACE("%p %p\n", dialog, dialog->package);
1365
1366 dialog->hwnd = hwnd;
1367 SetWindowLongPtrW( hwnd, GWLP_USERDATA, (LONG_PTR) dialog );
1368
1369 rec = msi_get_dialog_record( dialog );
1370 if( !rec )
1371 {
1372 TRACE("No record found for dialog %s\n", debugstr_w(dialog->name));
1373 return -1;
1374 }
1375
1376 dialog->scale = msi_dialog_get_sans_serif_height(dialog->hwnd);
1377
1378 size.cx = MSI_RecordGetInteger( rec, 4 );
1379 size.cy = MSI_RecordGetInteger( rec, 5 );
1380 msi_dialog_adjust_dialog_size( dialog, &size );
1381
1382 dialog->attributes = MSI_RecordGetInteger( rec, 6 );
1383 text = MSI_RecordGetString( rec, 7 );
1384
1385 dialog->default_font = load_dynamic_property( dialog->package, df, NULL );
1386
1387 deformat_string( dialog->package, text, &title );
1388 SetWindowTextW( hwnd, title );
1389 SetWindowPos( hwnd, 0, 0, 0, size.cx, size.cy,
1390 SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOREDRAW );
1391
1392 HeapFree( GetProcessHeap(), 0, title );
1393 msiobj_release( &rec->hdr );
1394
1395 msi_dialog_build_font_list( dialog );
1396 msi_dialog_fill_controls( dialog );
1397 msi_dialog_evaluate_control_conditions( dialog );
1398
1399 return 0;
1400 }
1401
1402 static UINT msi_dialog_send_event( msi_dialog *dialog, LPCWSTR event, LPCWSTR arg )
1403 {
1404 LPWSTR event_fmt = NULL, arg_fmt = NULL;
1405
1406 TRACE("Sending control event %s %s\n", debugstr_w(event), debugstr_w(arg));
1407
1408 deformat_string( dialog->package, event, &event_fmt );
1409 deformat_string( dialog->package, arg, &arg_fmt );
1410
1411 dialog->event_handler( dialog->package, event_fmt, arg_fmt, dialog );
1412
1413 HeapFree( GetProcessHeap(), 0, event_fmt );
1414 HeapFree( GetProcessHeap(), 0, arg_fmt );
1415
1416 return ERROR_SUCCESS;
1417 }
1418
1419 static UINT msi_dialog_set_property( msi_dialog *dialog, LPCWSTR event, LPCWSTR arg )
1420 {
1421 static const WCHAR szNullArg[] = { '{','}',0 };
1422 LPWSTR p, prop, arg_fmt = NULL;
1423 UINT len;
1424
1425 len = strlenW(event);
1426 prop = HeapAlloc( GetProcessHeap(), 0, len*sizeof(WCHAR));
1427 strcpyW( prop, &event[1] );
1428 p = strchrW( prop, ']' );
1429 if( p && p[1] == 0 )
1430 {
1431 *p = 0;
1432 if( strcmpW( szNullArg, arg ) )
1433 deformat_string( dialog->package, arg, &arg_fmt );
1434 MSI_SetPropertyW( dialog->package, prop, arg_fmt );
1435 }
1436 else
1437 ERR("Badly formatted property string - what happens?\n");
1438 HeapFree( GetProcessHeap(), 0, prop );
1439 return ERROR_SUCCESS;
1440 }
1441
1442 static UINT msi_dialog_control_event( MSIRECORD *rec, LPVOID param )
1443 {
1444 msi_dialog *dialog = param;
1445 LPCWSTR condition, event, arg;
1446 UINT r;
1447
1448 condition = MSI_RecordGetString( rec, 5 );
1449 r = MSI_EvaluateConditionW( dialog->package, condition );
1450 if( r )
1451 {
1452 event = MSI_RecordGetString( rec, 3 );
1453 arg = MSI_RecordGetString( rec, 4 );
1454 if( event[0] == '[' )
1455 msi_dialog_set_property( dialog, event, arg );
1456 else
1457 msi_dialog_send_event( dialog, event, arg );
1458 }
1459
1460 return ERROR_SUCCESS;
1461 }
1462
1463 static UINT msi_dialog_button_handler( msi_dialog *dialog,
1464 msi_control *control, WPARAM param )
1465 {
1466 static const WCHAR query[] = {
1467 'S','E','L','E','C','T',' ','*',' ',
1468 'F','R','O','M',' ','C','o','n','t','r','o','l','E','v','e','n','t',' ',
1469 'W','H','E','R','E',' ',
1470 '`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',' ',
1471 'A','N','D',' ',
1472 '`','C','o','n','t','r','o','l','_','`',' ','=',' ','\'','%','s','\'',' ',
1473 'O','R','D','E','R',' ','B','Y',' ','`','O','r','d','e','r','i','n','g','`',0
1474 };
1475 MSIQUERY *view = NULL;
1476 UINT r;
1477
1478 if( HIWORD(param) != BN_CLICKED )
1479 return ERROR_SUCCESS;
1480
1481 r = MSI_OpenQuery( dialog->package->db, &view, query,
1482 dialog->name, control->name );
1483 if( r != ERROR_SUCCESS )
1484 {
1485 ERR("query failed\n");
1486 return 0;
1487 }
1488
1489 r = MSI_IterateRecords( view, 0, msi_dialog_control_event, dialog );
1490 msiobj_release( &view->hdr );
1491
1492 return r;
1493 }
1494
1495 static UINT msi_dialog_get_checkbox_state( msi_dialog *dialog,
1496 msi_control *control )
1497 {
1498 WCHAR state[2] = { 0 };
1499 DWORD sz = 2;
1500
1501 MSI_GetPropertyW( dialog->package, control->property, state, &sz );
1502 return state[0] ? 1 : 0;
1503 }
1504
1505 static void msi_dialog_set_checkbox_state( msi_dialog *dialog,
1506 msi_control *control, UINT state )
1507 {
1508 static const WCHAR szState[] = { '1', 0 };
1509 LPCWSTR val;
1510
1511 /* if uncheck then the property is set to NULL */
1512 if (!state)
1513 {
1514 MSI_SetPropertyW( dialog->package, control->property, NULL );
1515 return;
1516 }
1517
1518 /* check for a custom state */
1519 if (control->value && control->value[0])
1520 val = control->value;
1521 else
1522 val = szState;
1523
1524 MSI_SetPropertyW( dialog->package, control->property, val );
1525 }
1526
1527 static void msi_dialog_checkbox_sync_state( msi_dialog *dialog,
1528 msi_control *control )
1529 {
1530 UINT state;
1531
1532 state = msi_dialog_get_checkbox_state( dialog, control );
1533 SendMessageW( control->hwnd, BM_SETCHECK,
1534 state ? BST_CHECKED : BST_UNCHECKED, 0 );
1535 }
1536
1537 static UINT msi_dialog_checkbox_handler( msi_dialog *dialog,
1538 msi_control *control, WPARAM param )
1539 {
1540 UINT state;
1541
1542 if( HIWORD(param) != BN_CLICKED )
1543 return ERROR_SUCCESS;
1544
1545 TRACE("clicked checkbox %s, set %s\n", debugstr_w(control->name),
1546 debugstr_w(control->property));
1547
1548 state = msi_dialog_get_checkbox_state( dialog, control );
1549 state = state ? 0 : 1;
1550 msi_dialog_set_checkbox_state( dialog, control, state );
1551 msi_dialog_checkbox_sync_state( dialog, control );
1552
1553 return msi_dialog_button_handler( dialog, control, param );
1554 }
1555
1556 static UINT msi_dialog_edit_handler( msi_dialog *dialog,
1557 msi_control *control, WPARAM param )
1558 {
1559 UINT sz, r;
1560 LPWSTR buf;
1561
1562 if( HIWORD(param) != EN_CHANGE )
1563 return ERROR_SUCCESS;
1564
1565 TRACE("edit %s contents changed, set %s\n", debugstr_w(control->name),
1566 debugstr_w(control->property));
1567
1568 sz = 0x20;
1569 buf = HeapAlloc( GetProcessHeap(), 0, sz*sizeof(WCHAR) );
1570 while( buf )
1571 {
1572 r = GetWindowTextW( control->hwnd, buf, sz );
1573 if( r < (sz-1) )
1574 break;
1575 sz *= 2;
1576 buf = HeapReAlloc( GetProcessHeap(), 0, buf, sz*sizeof(WCHAR) );
1577 }
1578
1579 MSI_SetPropertyW( dialog->package, control->property, buf );
1580
1581 HeapFree( GetProcessHeap(), 0, buf );
1582
1583 return ERROR_SUCCESS;
1584 }
1585
1586 static UINT msi_dialog_radiogroup_handler( msi_dialog *dialog,
1587 msi_control *control, WPARAM param )
1588 {
1589 if( HIWORD(param) != BN_CLICKED )
1590 return ERROR_SUCCESS;
1591
1592 TRACE("clicked radio button %s, set %s\n", debugstr_w(control->name),
1593 debugstr_w(control->property));
1594
1595 MSI_SetPropertyW( dialog->package, control->property, control->name );
1596
1597 return msi_dialog_button_handler( dialog, control, param );
1598 }
1599
1600 static LRESULT msi_dialog_oncommand( msi_dialog *dialog, WPARAM param, HWND hwnd )
1601 {
1602 msi_control *control;
1603
1604 TRACE("%p %p %08x\n", dialog, hwnd, param);
1605
1606 control = msi_dialog_find_control_by_hwnd( dialog, hwnd );
1607 if( control )
1608 {
1609 if( control->handler )
1610 {
1611 control->handler( dialog, control, param );
1612 msi_dialog_evaluate_control_conditions( dialog );
1613 }
1614 }
1615 else
1616 ERR("button click from nowhere\n");
1617 return 0;
1618 }
1619
1620 static LRESULT WINAPI MSIDialog_WndProc( HWND hwnd, UINT msg,
1621 WPARAM wParam, LPARAM lParam )
1622 {
1623 msi_dialog *dialog = (LPVOID) GetWindowLongPtrW( hwnd, GWLP_USERDATA );
1624
1625 TRACE("0x%04x\n", msg);
1626
1627 switch (msg)
1628 {
1629 case WM_CREATE:
1630 return msi_dialog_oncreate( hwnd, (LPCREATESTRUCTW)lParam );
1631
1632 case WM_COMMAND:
1633 return msi_dialog_oncommand( dialog, wParam, (HWND)lParam );
1634
1635 /* bounce back to our subclassed static control */
1636 case WM_CTLCOLORSTATIC:
1637 return SendMessageW( (HWND) lParam, WM_CTLCOLORSTATIC, wParam, lParam );
1638
1639 case WM_DESTROY:
1640 dialog->hwnd = NULL;
1641 return 0;
1642 }
1643 return DefWindowProcW(hwnd, msg, wParam, lParam);
1644 }
1645
1646 static LRESULT WINAPI MSIRadioGroup_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
1647 {
1648 WNDPROC oldproc = (WNDPROC) GetPropW(hWnd, szButtonData);
1649
1650 TRACE("hWnd %p msg %04x wParam 0x%08x lParam 0x%08lx\n", hWnd, msg, wParam, lParam);
1651
1652 if (msg == WM_COMMAND) /* Forward notifications to dialog */
1653 SendMessageW(GetParent(hWnd), msg, wParam, lParam);
1654
1655 return CallWindowProcW(oldproc, hWnd, msg, wParam, lParam);
1656 }
1657
1658 static LRESULT WINAPI MSIHiddenWindowProc( HWND hwnd, UINT msg,
1659 WPARAM wParam, LPARAM lParam )
1660 {
1661 msi_dialog *dialog = (msi_dialog*) lParam;
1662
1663 TRACE("%d %p\n", msg, dialog);
1664
1665 switch (msg)
1666 {
1667 case WM_MSI_DIALOG_CREATE:
1668 return msi_dialog_run_message_loop( dialog );
1669 case WM_MSI_DIALOG_DESTROY:
1670 msi_dialog_destroy( dialog );
1671 return 0;
1672 }
1673 return DefWindowProcW( hwnd, msg, wParam, lParam );
1674 }
1675
1676 /* functions that interface to other modules within MSI */
1677
1678 msi_dialog *msi_dialog_create( MSIPACKAGE* package, LPCWSTR szDialogName,
1679 msi_dialog_event_handler event_handler )
1680 {
1681 MSIRECORD *rec = NULL;
1682 msi_dialog *dialog;
1683
1684 TRACE("%p %s\n", package, debugstr_w(szDialogName));
1685
1686 /* allocate the structure for the dialog to use */
1687 dialog = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY,
1688 sizeof *dialog + sizeof(WCHAR)*strlenW(szDialogName) );
1689 if( !dialog )
1690 return NULL;
1691 strcpyW( dialog->name, szDialogName );
1692 msiobj_addref( &package->hdr );
1693 dialog->package = package;
1694 dialog->event_handler = event_handler;
1695 dialog->finished = 0;
1696
1697 /* verify that the dialog exists */
1698 rec = msi_get_dialog_record( dialog );
1699 if( !rec )
1700 {
1701 HeapFree( GetProcessHeap(), 0, dialog );
1702 return NULL;
1703 }
1704 dialog->attributes = MSI_RecordGetInteger( rec, 6 );
1705 msiobj_release( &rec->hdr );
1706
1707 return dialog;
1708 }
1709
1710 static void msi_process_pending_messages( HWND hdlg )
1711 {
1712 MSG msg;
1713
1714 while( PeekMessageW( &msg, 0, 0, 0, PM_REMOVE ) )
1715 {
1716 if( hdlg && IsDialogMessageW( hdlg, &msg ))
1717 continue;
1718 TranslateMessage( &msg );
1719 DispatchMessageW( &msg );
1720 }
1721 }
1722
1723 void msi_dialog_end_dialog( msi_dialog *dialog )
1724 {
1725 TRACE("%p\n", dialog);
1726 dialog->finished = 1;
1727 PostMessageW(dialog->hwnd, WM_NULL, 0, 0);
1728 }
1729
1730 void msi_dialog_check_messages( HANDLE handle )
1731 {
1732 DWORD r;
1733
1734 /* in threads other than the UI thread, block */
1735 if( uiThreadId != GetCurrentThreadId() )
1736 {
1737 if( handle )
1738 WaitForSingleObject( handle, INFINITE );
1739 return;
1740 }
1741
1742 /* there's two choices for the UI thread */
1743 while (1)
1744 {
1745 msi_process_pending_messages( NULL );
1746
1747 if( !handle )
1748 break;
1749
1750 /*
1751 * block here until somebody creates a new dialog or
1752 * the handle we're waiting on becomes ready
1753 */
1754 r = MsgWaitForMultipleObjects( 1, &handle, 0, INFINITE, QS_ALLINPUT );
1755 if( r == WAIT_OBJECT_0 )
1756 break;
1757 }
1758 }
1759
1760 UINT msi_dialog_run_message_loop( msi_dialog *dialog )
1761 {
1762 HWND hwnd;
1763
1764 if( !(dialog->attributes & msidbDialogAttributesVisible) )
1765 return ERROR_SUCCESS;
1766
1767 if( uiThreadId != GetCurrentThreadId() )
1768 return SendMessageW( hMsiHiddenWindow, WM_MSI_DIALOG_CREATE, 0, (LPARAM) dialog );
1769
1770 /* create the dialog window, don't show it yet */
1771 hwnd = CreateWindowW( szMsiDialogClass, dialog->name, WS_OVERLAPPEDWINDOW,
1772 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
1773 NULL, NULL, NULL, dialog );
1774 if( !hwnd )
1775 {
1776 ERR("Failed to create dialog %s\n", debugstr_w( dialog->name ));
1777 return ERROR_FUNCTION_FAILED;
1778 }
1779
1780 ShowWindow( hwnd, SW_SHOW );
1781 UpdateWindow( hwnd );
1782
1783 if( dialog->attributes & msidbDialogAttributesModal )
1784 {
1785 while( !dialog->finished )
1786 {
1787 MsgWaitForMultipleObjects( 0, NULL, 0, INFINITE, QS_ALLEVENTS );
1788 msi_process_pending_messages( dialog->hwnd );
1789 }
1790 }
1791 else
1792 return ERROR_IO_PENDING;
1793
1794 return ERROR_SUCCESS;
1795 }
1796
1797 void msi_dialog_do_preview( msi_dialog *dialog )
1798 {
1799 TRACE("\n");
1800 dialog->attributes |= msidbDialogAttributesVisible;
1801 dialog->attributes &= ~msidbDialogAttributesModal;
1802 msi_dialog_run_message_loop( dialog );
1803 }
1804
1805 void msi_dialog_destroy( msi_dialog *dialog )
1806 {
1807 if( uiThreadId != GetCurrentThreadId() )
1808 {
1809 SendMessageW( hMsiHiddenWindow, WM_MSI_DIALOG_DESTROY, 0, (LPARAM) dialog );
1810 return;
1811 }
1812
1813 if( dialog->hwnd )
1814 ShowWindow( dialog->hwnd, SW_HIDE );
1815
1816 /* destroy the list of controls */
1817 while( dialog->control_list )
1818 {
1819 msi_control *t = dialog->control_list;
1820 dialog->control_list = t->next;
1821 /* leave dialog->hwnd - destroying parent destroys child windows */
1822 HeapFree( GetProcessHeap(), 0, t->property );
1823 HeapFree( GetProcessHeap(), 0, t->value );
1824 if( t->pic )
1825 IPicture_Release( t->pic );
1826 if( t->hIcon )
1827 DestroyIcon( t->hIcon );
1828 HeapFree( GetProcessHeap(), 0, t );
1829 }
1830
1831 /* destroy the list of fonts */
1832 while( dialog->font_list )
1833 {
1834 msi_font *t = dialog->font_list;
1835 dialog->font_list = t->next;
1836 DeleteObject( t->hfont );
1837 HeapFree( GetProcessHeap(), 0, t );
1838 }
1839 HeapFree( GetProcessHeap(), 0, dialog->default_font );
1840
1841 if( dialog->hwnd )
1842 DestroyWindow( dialog->hwnd );
1843
1844 msiobj_release( &dialog->package->hdr );
1845 dialog->package = NULL;
1846 HeapFree( GetProcessHeap(), 0, dialog );
1847 }
1848
1849 BOOL msi_dialog_register_class( void )
1850 {
1851 WNDCLASSW cls;
1852
1853 ZeroMemory( &cls, sizeof cls );
1854 cls.lpfnWndProc = MSIDialog_WndProc;
1855 cls.hInstance = NULL;
1856 cls.hIcon = LoadIconW(0, (LPWSTR)IDI_APPLICATION);
1857 cls.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
1858 cls.hbrBackground = (HBRUSH)(COLOR_WINDOW);
1859 cls.lpszMenuName = NULL;
1860 cls.lpszClassName = szMsiDialogClass;
1861
1862 if( !RegisterClassW( &cls ) )
1863 return FALSE;
1864
1865 cls.lpfnWndProc = MSIHiddenWindowProc;
1866 cls.lpszClassName = szMsiHiddenWindow;
1867
1868 if( !RegisterClassW( &cls ) )
1869 return FALSE;
1870
1871 uiThreadId = GetCurrentThreadId();
1872
1873 hMsiHiddenWindow = CreateWindowW( szMsiHiddenWindow, NULL, WS_OVERLAPPED,
1874 0, 0, 100, 100, NULL, NULL, NULL, NULL );
1875 if( !hMsiHiddenWindow )
1876 return FALSE;
1877
1878 hRichedit = LoadLibraryA("riched20");
1879
1880 return TRUE;
1881 }
1882
1883 void msi_dialog_unregister_class( void )
1884 {
1885 DestroyWindow( hMsiHiddenWindow );
1886 UnregisterClassW( szMsiDialogClass, NULL );
1887 uiThreadId = 0;
1888 FreeLibrary( hRichedit );
1889 }