msi: Added tests for MsiSetTargetPath.
[wine] / dlls / 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 #define COBJMACROS
22 #define NONAMELESSUNION
23 #define NONAMELESSSTRUCT
24
25 #include <stdarg.h>
26
27 #include "windef.h"
28 #include "winbase.h"
29 #include "winuser.h"
30 #include "winnls.h"
31 #include "wingdi.h"
32 #include "msi.h"
33 #include "msipriv.h"
34 #include "msidefs.h"
35 #include "ocidl.h"
36 #include "olectl.h"
37 #include "richedit.h"
38 #include "commctrl.h"
39
40 #include "wine/debug.h"
41 #include "wine/unicode.h"
42
43 #include "action.h"
44
45 WINE_DEFAULT_DEBUG_CHANNEL(msi);
46
47
48 extern HINSTANCE msi_hInstance;
49
50 struct msi_control_tag;
51 typedef struct msi_control_tag msi_control;
52 typedef UINT (*msi_handler)( msi_dialog *, msi_control *, WPARAM );
53
54 struct msi_control_tag
55 {
56     struct list entry;
57     HWND hwnd;
58     msi_handler handler;
59     LPWSTR property;
60     LPWSTR value;
61     HBITMAP hBitmap;
62     HICON hIcon;
63     LPWSTR tabnext;
64     HMODULE hDll;
65     float progress_current;
66     float progress_max;
67     WCHAR name[1];
68 };
69
70 typedef struct msi_font_tag
71 {
72     struct msi_font_tag *next;
73     HFONT hfont;
74     WCHAR name[1];
75 } msi_font;
76
77 struct msi_dialog_tag
78 {
79     MSIPACKAGE *package;
80     msi_dialog_event_handler event_handler;
81     BOOL finished;
82     INT scale;
83     DWORD attributes;
84     HWND hwnd;
85     LPWSTR default_font;
86     msi_font *font_list;
87     struct list controls;
88     HWND hWndFocus;
89     LPWSTR control_default;
90     LPWSTR control_cancel;
91     WCHAR name[1];
92 };
93
94 typedef UINT (*msi_dialog_control_func)( msi_dialog *dialog, MSIRECORD *rec );
95 struct control_handler 
96 {
97     LPCWSTR control_type;
98     msi_dialog_control_func func;
99 };
100
101 typedef struct
102 {
103     msi_dialog* dialog;
104     msi_control *parent;
105     DWORD       attributes;
106     LPWSTR      propval;
107 } radio_button_group_descr;
108
109 static const WCHAR szMsiDialogClass[] = {
110     'M','s','i','D','i','a','l','o','g','C','l','o','s','e','C','l','a','s','s',0
111 };
112 static const WCHAR szMsiHiddenWindow[] = {
113     'M','s','i','H','i','d','d','e','n','W','i','n','d','o','w',0 };
114 static const WCHAR szStatic[] = { 'S','t','a','t','i','c',0 };
115 static const WCHAR szButton[] = { 'B','U','T','T','O','N', 0 };
116 static const WCHAR szButtonData[] = { 'M','S','I','D','A','T','A',0 };
117 static const WCHAR szProgress[] = { 'P','r','o','g','r','e','s','s',0 };
118 static const WCHAR szText[] = { 'T','e','x','t',0 };
119 static const WCHAR szPushButton[] = { 'P','u','s','h','B','u','t','t','o','n',0 };
120 static const WCHAR szLine[] = { 'L','i','n','e',0 };
121 static const WCHAR szBitmap[] = { 'B','i','t','m','a','p',0 };
122 static const WCHAR szCheckBox[] = { 'C','h','e','c','k','B','o','x',0 };
123 static const WCHAR szScrollableText[] = {
124     'S','c','r','o','l','l','a','b','l','e','T','e','x','t',0 };
125 static const WCHAR szComboBox[] = { 'C','o','m','b','o','B','o','x',0 };
126 static const WCHAR szEdit[] = { 'E','d','i','t',0 };
127 static const WCHAR szMaskedEdit[] = { 'M','a','s','k','e','d','E','d','i','t',0 };
128 static const WCHAR szPathEdit[] = { 'P','a','t','h','E','d','i','t',0 };
129 static const WCHAR szProgressBar[] = {
130      'P','r','o','g','r','e','s','s','B','a','r',0 };
131 static const WCHAR szRadioButtonGroup[] = { 
132     'R','a','d','i','o','B','u','t','t','o','n','G','r','o','u','p',0 };
133 static const WCHAR szIcon[] = { 'I','c','o','n',0 };
134 static const WCHAR szSelectionTree[] = {
135     'S','e','l','e','c','t','i','o','n','T','r','e','e',0 };
136
137 static UINT msi_dialog_checkbox_handler( msi_dialog *, msi_control *, WPARAM );
138 static void msi_dialog_checkbox_sync_state( msi_dialog *, msi_control * );
139 static UINT msi_dialog_button_handler( msi_dialog *, msi_control *, WPARAM );
140 static UINT msi_dialog_edit_handler( msi_dialog *, msi_control *, WPARAM );
141 static UINT msi_dialog_radiogroup_handler( msi_dialog *, msi_control *, WPARAM param );
142 static UINT msi_dialog_evaluate_control_conditions( msi_dialog *dialog );
143 static LRESULT WINAPI MSIRadioGroup_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
144
145
146 /* dialog sequencing */
147
148 #define WM_MSI_DIALOG_CREATE  (WM_USER+0x100)
149 #define WM_MSI_DIALOG_DESTROY (WM_USER+0x101)
150
151 static DWORD uiThreadId;
152 static HWND hMsiHiddenWindow;
153
154 static INT msi_dialog_scale_unit( msi_dialog *dialog, INT val )
155 {
156     return (dialog->scale * val + 5) / 10;
157 }
158
159 static msi_control *msi_dialog_find_control( msi_dialog *dialog, LPCWSTR name )
160 {
161     msi_control *control;
162
163     if( !name )
164         return NULL;
165     LIST_FOR_EACH_ENTRY( control, &dialog->controls, msi_control, entry )
166         if( !strcmpW( control->name, name ) ) /* FIXME: case sensitive? */
167             return control;
168     return NULL;
169 }
170
171 static msi_control *msi_dialog_find_control_by_hwnd( msi_dialog *dialog, HWND hwnd )
172 {
173     msi_control *control;
174
175     LIST_FOR_EACH_ENTRY( control, &dialog->controls, msi_control, entry )
176         if( hwnd == control->hwnd )
177             return control;
178     return NULL;
179 }
180
181 static LPWSTR msi_get_deformatted_field( MSIPACKAGE *package, MSIRECORD *rec, int field )
182 {
183     LPCWSTR str = MSI_RecordGetString( rec, field );
184     LPWSTR ret = NULL;
185
186     if (str)
187         deformat_string( package, str, &ret );
188     return ret;
189 }
190
191 /*
192  * msi_dialog_get_style
193  *
194  * Extract the {\style} string from the front of the text to display and
195  *  update the pointer.
196  */
197 static LPWSTR msi_dialog_get_style( LPCWSTR p, LPCWSTR *rest )
198 {
199     LPWSTR ret = NULL;
200     LPCWSTR q, i;
201     DWORD len;
202
203     *rest = p;
204     if( !p )
205         return ret;
206     if( *p++ != '{' )
207         return ret;
208     q = strchrW( p, '}' );
209     if( !q )
210         return ret;
211     if( *p == '\\' || *p == '&' )
212         p++;
213
214     /* little bit of sanity checking to stop us getting confused with RTF */
215     for( i=p; i<q; i++ )
216         if( *i == '}' || *i == '\\' )
217             return ret;
218     
219     *rest = ++q;
220     len = q - p;
221
222     ret = msi_alloc( len*sizeof(WCHAR) );
223     if( !ret )
224         return ret;
225     memcpy( ret, p, len*sizeof(WCHAR) );
226     ret[len-1] = 0;
227     return ret;
228 }
229
230 static UINT msi_dialog_add_font( MSIRECORD *rec, LPVOID param )
231 {
232     msi_dialog *dialog = param;
233     msi_font *font;
234     LPCWSTR face, name;
235     LOGFONTW lf;
236     INT style;
237     HDC hdc;
238
239     /* create a font and add it to the list */
240     name = MSI_RecordGetString( rec, 1 );
241     font = msi_alloc( sizeof *font + strlenW( name )*sizeof (WCHAR) );
242     strcpyW( font->name, name );
243     font->next = dialog->font_list;
244     dialog->font_list = font;
245
246     memset( &lf, 0, sizeof lf );
247     face = MSI_RecordGetString( rec, 2 );
248     lf.lfHeight = MSI_RecordGetInteger( rec, 3 );
249     style = MSI_RecordGetInteger( rec, 5 );
250     if( style & msidbTextStyleStyleBitsBold )
251         lf.lfWeight = FW_BOLD;
252     if( style & msidbTextStyleStyleBitsItalic )
253         lf.lfItalic = TRUE;
254     if( style & msidbTextStyleStyleBitsUnderline )
255         lf.lfUnderline = TRUE;
256     if( style & msidbTextStyleStyleBitsStrike )
257         lf.lfStrikeOut = TRUE;
258     lstrcpynW( lf.lfFaceName, face, LF_FACESIZE );
259
260     /* adjust the height */
261     hdc = GetDC( dialog->hwnd );
262     if (hdc)
263     {
264         lf.lfHeight = -MulDiv(lf.lfHeight, GetDeviceCaps(hdc, LOGPIXELSY), 72);
265         ReleaseDC( dialog->hwnd, hdc );
266     }
267
268     font->hfont = CreateFontIndirectW( &lf );
269
270     TRACE("Adding font style %s\n", debugstr_w(font->name) );
271
272     return ERROR_SUCCESS;
273 }
274
275 static msi_font *msi_dialog_find_font( msi_dialog *dialog, LPCWSTR name )
276 {
277     msi_font *font;
278
279     for( font = dialog->font_list; font; font = font->next )
280         if( !strcmpW( font->name, name ) )  /* FIXME: case sensitive? */
281             break;
282
283     return font;
284 }
285
286 static UINT msi_dialog_set_font( msi_dialog *dialog, HWND hwnd, LPCWSTR name )
287 {
288     msi_font *font;
289
290     font = msi_dialog_find_font( dialog, name );
291     if( font )
292         SendMessageW( hwnd, WM_SETFONT, (WPARAM) font->hfont, TRUE );
293     else
294         ERR("No font entry for %s\n", debugstr_w(name));
295     return ERROR_SUCCESS;
296 }
297
298 static UINT msi_dialog_build_font_list( msi_dialog *dialog )
299 {
300     static const WCHAR query[] = {
301       'S','E','L','E','C','T',' ','*',' ',
302       'F','R','O','M',' ','`','T','e','x','t','S','t','y','l','e','`',' ',0
303     };
304     UINT r;
305     MSIQUERY *view = NULL;
306
307     TRACE("dialog %p\n", dialog );
308
309     r = MSI_OpenQuery( dialog->package->db, &view, query );
310     if( r != ERROR_SUCCESS )
311         return r;
312
313     r = MSI_IterateRecords( view, NULL, msi_dialog_add_font, dialog );
314     msiobj_release( &view->hdr );
315
316     return r;
317 }
318
319 static msi_control *msi_dialog_create_window( msi_dialog *dialog,
320                 MSIRECORD *rec, DWORD exstyle, LPCWSTR szCls, LPCWSTR name, LPCWSTR text,
321                 DWORD style, HWND parent )
322 {
323     DWORD x, y, width, height;
324     LPWSTR font = NULL, title_font = NULL;
325     LPCWSTR title = NULL;
326     msi_control *control;
327
328     style |= WS_CHILD;
329
330     control = msi_alloc( sizeof *control + strlenW(name)*sizeof(WCHAR) );
331     strcpyW( control->name, name );
332     list_add_head( &dialog->controls, &control->entry );
333     control->handler = NULL;
334     control->property = NULL;
335     control->value = NULL;
336     control->hBitmap = NULL;
337     control->hIcon = NULL;
338     control->hDll = NULL;
339     control->tabnext = strdupW( MSI_RecordGetString( rec, 11) );
340     control->progress_current = 0;
341     control->progress_max = 100;
342
343     x = MSI_RecordGetInteger( rec, 4 );
344     y = MSI_RecordGetInteger( rec, 5 );
345     width = MSI_RecordGetInteger( rec, 6 );
346     height = MSI_RecordGetInteger( rec, 7 );
347
348     x = msi_dialog_scale_unit( dialog, x );
349     y = msi_dialog_scale_unit( dialog, y );
350     width = msi_dialog_scale_unit( dialog, width );
351     height = msi_dialog_scale_unit( dialog, height );
352
353     if( text )
354     {
355         deformat_string( dialog->package, text, &title_font );
356         font = msi_dialog_get_style( title_font, &title );
357     }
358
359     control->hwnd = CreateWindowExW( exstyle, szCls, title, style,
360                           x, y, width, height, parent, NULL, NULL, NULL );
361
362     TRACE("Dialog %s control %s hwnd %p\n",
363            debugstr_w(dialog->name), debugstr_w(text), control->hwnd );
364
365     msi_dialog_set_font( dialog, control->hwnd,
366                          font ? font : dialog->default_font );
367
368     msi_free( title_font );
369     msi_free( font );
370
371     return control;
372 }
373
374 static MSIRECORD *msi_get_binary_record( MSIDATABASE *db, LPCWSTR name )
375 {
376     static const WCHAR query[] = {
377         's','e','l','e','c','t',' ','*',' ',
378         'f','r','o','m',' ','B','i','n','a','r','y',' ',
379         'w','h','e','r','e',' ',
380             '`','N','a','m','e','`',' ','=',' ','\'','%','s','\'',0
381     };
382
383     return MSI_QueryGetRecord( db, query, name );
384 }
385
386 static LPWSTR msi_create_tmp_path(void)
387 {
388     WCHAR tmp[MAX_PATH];
389     LPWSTR path = NULL;
390     static const WCHAR prefix[] = { 'm','s','i',0 };
391     DWORD len, r;
392
393     r = GetTempPathW( MAX_PATH, tmp );
394     if( !r )
395         return path;
396     len = lstrlenW( tmp ) + 20;
397     path = msi_alloc( len * sizeof (WCHAR) );
398     if( path )
399     {
400         r = GetTempFileNameW( tmp, prefix, 0, path );
401         if (!r)
402         {
403             msi_free( path );
404             path = NULL;
405         }
406     }
407     return path;
408 }
409
410
411 static HANDLE msi_load_image( MSIDATABASE *db, LPCWSTR name, UINT type,
412                               UINT cx, UINT cy, UINT flags )
413 {
414     MSIRECORD *rec = NULL;
415     HANDLE himage = NULL;
416     LPWSTR tmp;
417     UINT r;
418
419     TRACE("%p %s %u %u %08x\n", db, debugstr_w(name), cx, cy, flags);
420
421     tmp = msi_create_tmp_path();
422     if( !tmp )
423         return himage;
424
425     rec = msi_get_binary_record( db, name );
426     if( rec )
427     {
428         r = MSI_RecordStreamToFile( rec, 2, tmp );
429         if( r == ERROR_SUCCESS )
430         {
431             himage = LoadImageW( 0, tmp, type, cx, cy, flags );
432             DeleteFileW( tmp );
433         }
434         msiobj_release( &rec->hdr );
435     }
436
437     msi_free( tmp );
438     return himage;
439 }
440
441 static HICON msi_load_icon( MSIDATABASE *db, LPCWSTR text, UINT attributes )
442 {
443     DWORD cx = 0, cy = 0, flags;
444
445     flags = LR_LOADFROMFILE | LR_DEFAULTSIZE;
446     if( attributes & msidbControlAttributesFixedSize )
447     {
448         flags &= ~LR_DEFAULTSIZE;
449         if( attributes & msidbControlAttributesIconSize16 )
450         {
451             cx += 16;
452             cy += 16;
453         }
454         if( attributes & msidbControlAttributesIconSize32 )
455         {
456             cx += 32;
457             cy += 32;
458         }
459         /* msidbControlAttributesIconSize48 handled by above logic */
460     }
461     return msi_load_image( db, text, IMAGE_ICON, cx, cy, flags );
462 }
463
464
465 /* called from the Control Event subscription code */
466 void msi_dialog_handle_event( msi_dialog* dialog, LPCWSTR control, 
467                               LPCWSTR attribute, MSIRECORD *rec )
468 {
469     msi_control* ctrl;
470     LPCWSTR font_text, text = NULL;
471     LPWSTR font;
472
473     ctrl = msi_dialog_find_control( dialog, control );
474     if (!ctrl)
475         return;
476     if( !lstrcmpW(attribute, szText) )
477     {
478         font_text = MSI_RecordGetString( rec , 1 );
479         font = msi_dialog_get_style( font_text, &text );
480         SetWindowTextW( ctrl->hwnd, text );
481         msi_free( font );
482         msi_dialog_check_messages( NULL );
483     }
484     else if( !lstrcmpW(attribute, szProgress) )
485     {
486         DWORD func, val;
487
488         func = MSI_RecordGetInteger( rec , 1 );
489         val = MSI_RecordGetInteger( rec , 2 );
490
491         switch (func)
492         {
493         case 0: /* init */
494             ctrl->progress_max = val;
495             ctrl->progress_current = 0;
496             SendMessageW(ctrl->hwnd, PBM_SETRANGE, 0, MAKELPARAM(0,100));
497             SendMessageW(ctrl->hwnd, PBM_SETPOS, 0, 0);
498             break;
499         case 1: /* FIXME: not sure what this is supposed to do */
500             break;
501         case 2: /* move */
502             ctrl->progress_current += val;
503             SendMessageW(ctrl->hwnd, PBM_SETPOS, 100*(ctrl->progress_current/ctrl->progress_max), 0);
504             break;
505         default:
506             ERR("Unknown progress message %ld\n", func);
507             break;
508         }
509     }
510     else
511     {
512         FIXME("Attribute %s not being set\n", debugstr_w(attribute));
513         return;
514     }
515 }
516
517 static void msi_dialog_map_events(msi_dialog* dialog, LPCWSTR control)
518 {
519     static const WCHAR Query[] = {
520         'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
521          '`','E','v','e','n','t','M','a','p','p','i','n','g','`',' ',
522         'W','H','E','R','E',' ',
523          '`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',' ',
524         'A','N','D',' ',
525          '`','C','o','n','t','r','o','l','_','`',' ','=',' ','\'','%','s','\'',0
526     };
527     MSIRECORD *row;
528     LPCWSTR event, attribute;
529
530     row = MSI_QueryGetRecord( dialog->package->db, Query, dialog->name, control );
531     if (!row)
532         return;
533
534     event = MSI_RecordGetString( row, 3 );
535     attribute = MSI_RecordGetString( row, 4 );
536     ControlEvent_SubscribeToEvent( dialog->package, event, control, attribute );
537     msiobj_release( &row->hdr );
538 }
539
540 /* everything except radio buttons */
541 static msi_control *msi_dialog_add_control( msi_dialog *dialog,
542                 MSIRECORD *rec, LPCWSTR szCls, DWORD style )
543 {
544     DWORD attributes;
545     LPCWSTR text, name;
546     DWORD exstyle = 0;
547
548     name = MSI_RecordGetString( rec, 2 );
549     attributes = MSI_RecordGetInteger( rec, 8 );
550     text = MSI_RecordGetString( rec, 10 );
551     if( attributes & msidbControlAttributesVisible )
552         style |= WS_VISIBLE;
553     if( ~attributes & msidbControlAttributesEnabled )
554         style |= WS_DISABLED;
555     if( attributes & msidbControlAttributesSunken )
556         exstyle |= WS_EX_CLIENTEDGE;
557
558     msi_dialog_map_events(dialog, name);
559
560     return msi_dialog_create_window( dialog, rec, exstyle, szCls, name,
561                                      text, style, dialog->hwnd );
562 }
563
564 struct msi_text_info
565 {
566     WNDPROC oldproc;
567     DWORD attributes;
568 };
569
570 /*
571  * we don't erase our own background,
572  * so we have to make sure that the parent window redraws first
573  */
574 static void msi_text_on_settext( HWND hWnd )
575 {
576     HWND hParent;
577     RECT rc;
578
579     hParent = GetParent( hWnd );
580     GetWindowRect( hWnd, &rc );
581     MapWindowPoints( NULL, hParent, (LPPOINT) &rc, 2 );
582     InvalidateRect( hParent, &rc, TRUE );
583 }
584
585 static LRESULT WINAPI
586 MSIText_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
587 {
588     struct msi_text_info *info;
589     LRESULT r = 0;
590
591     TRACE("%p %04x %08x %08lx\n", hWnd, msg, wParam, lParam);
592
593     info = GetPropW(hWnd, szButtonData);
594
595     if( msg == WM_CTLCOLORSTATIC &&
596        ( info->attributes & msidbControlAttributesTransparent ) )
597     {
598         SetBkMode( (HDC)wParam, TRANSPARENT );
599         return (LRESULT) GetStockObject(NULL_BRUSH);
600     }
601
602     r = CallWindowProcW(info->oldproc, hWnd, msg, wParam, lParam);
603
604     switch( msg )
605     {
606     case WM_SETTEXT:
607         msi_text_on_settext( hWnd );
608         break;
609     case WM_NCDESTROY:
610         msi_free( info );
611         RemovePropW( hWnd, szButtonData );
612         break;
613     }
614
615     return r;
616 }
617
618 static UINT msi_dialog_text_control( msi_dialog *dialog, MSIRECORD *rec )
619 {
620     msi_control *control;
621     struct msi_text_info *info;
622
623     TRACE("%p %p\n", dialog, rec);
624
625     control = msi_dialog_add_control( dialog, rec, szStatic, SS_LEFT | WS_GROUP );
626     if( !control )
627         return ERROR_FUNCTION_FAILED;
628
629     info = msi_alloc( sizeof *info );
630     if( !info )
631         return ERROR_SUCCESS;
632
633     info->attributes = MSI_RecordGetInteger( rec, 8 );
634     if( info->attributes & msidbControlAttributesTransparent )
635         SetWindowLongPtrW( control->hwnd, GWL_EXSTYLE, WS_EX_TRANSPARENT );
636
637     info->oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
638                                           (LONG_PTR)MSIText_WndProc );
639     SetPropW( control->hwnd, szButtonData, info );
640
641     return ERROR_SUCCESS;
642 }
643
644 static UINT msi_dialog_button_control( msi_dialog *dialog, MSIRECORD *rec )
645 {
646     msi_control *control;
647     UINT attributes, style;
648     LPWSTR text;
649
650     TRACE("%p %p\n", dialog, rec);
651
652     style = WS_TABSTOP;
653     attributes = MSI_RecordGetInteger( rec, 8 );
654     if( attributes & msidbControlAttributesIcon )
655         style |= BS_ICON;
656
657     control = msi_dialog_add_control( dialog, rec, szButton, style );
658     if( !control )
659         return ERROR_FUNCTION_FAILED;
660
661     control->handler = msi_dialog_button_handler;
662
663     /* set the icon */
664     text = msi_get_deformatted_field( dialog->package, rec, 10 );
665     control->hIcon = msi_load_icon( dialog->package->db, text, attributes );
666     if( attributes & msidbControlAttributesIcon )
667         SendMessageW( control->hwnd, BM_SETIMAGE, IMAGE_ICON, (LPARAM) control->hIcon );
668     msi_free( text );
669
670     return ERROR_SUCCESS;
671 }
672
673 static LPWSTR msi_get_checkbox_value( msi_dialog *dialog, LPCWSTR prop )
674 {
675     static const WCHAR query[] = {
676         'S','E','L','E','C','T',' ','*',' ',
677         'F','R','O','M',' ','`','C','h','e','c','k','B','o','x',' ','`',
678         'W','H','E','R','E',' ',
679         '`','P','r','o','p','e','r','t','y','`',' ','=',' ',
680         '\'','%','s','\'',0
681     };
682     MSIRECORD *rec = NULL;
683     LPWSTR ret = NULL;
684
685     /* find if there is a value associated with the checkbox */
686     rec = MSI_QueryGetRecord( dialog->package->db, query, prop );
687     if (!rec)
688         return ret;
689
690     ret = msi_get_deformatted_field( dialog->package, rec, 2 );
691     if( ret && !ret[0] )
692     {
693         msi_free( ret );
694         ret = NULL;
695     }
696     msiobj_release( &rec->hdr );
697     if (ret)
698         return ret;
699
700     ret = msi_dup_property( dialog->package, prop );
701     if( ret && !ret[0] )
702     {
703         msi_free( ret );
704         ret = NULL;
705     }
706
707     return ret;
708 }
709
710 static UINT msi_dialog_checkbox_control( msi_dialog *dialog, MSIRECORD *rec )
711 {
712     msi_control *control;
713     LPCWSTR prop;
714
715     TRACE("%p %p\n", dialog, rec);
716
717     control = msi_dialog_add_control( dialog, rec, szButton,
718                                 BS_CHECKBOX | BS_MULTILINE | WS_TABSTOP );
719     control->handler = msi_dialog_checkbox_handler;
720     prop = MSI_RecordGetString( rec, 9 );
721     if( prop )
722     {
723         control->property = strdupW( prop );
724         control->value = msi_get_checkbox_value( dialog, prop );
725         TRACE("control %s value %s\n", debugstr_w(control->property),
726               debugstr_w(control->value));
727     }
728     msi_dialog_checkbox_sync_state( dialog, control );
729
730     return ERROR_SUCCESS;
731 }
732
733 static UINT msi_dialog_line_control( msi_dialog *dialog, MSIRECORD *rec )
734 {
735     TRACE("%p %p\n", dialog, rec);
736
737     msi_dialog_add_control( dialog, rec, szStatic, SS_ETCHEDHORZ | SS_SUNKEN );
738     return ERROR_SUCCESS;
739 }
740
741 /******************** Scroll Text ********************************************/
742
743 struct msi_scrolltext_info
744 {
745     msi_dialog *dialog;
746     msi_control *control;
747     WNDPROC oldproc;
748 };
749
750 static LRESULT WINAPI
751 MSIScrollText_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
752 {
753     struct msi_scrolltext_info *info;
754     HRESULT r;
755
756     TRACE("%p %04x %08x %08lx\n", hWnd, msg, wParam, lParam);
757
758     info = GetPropW( hWnd, szButtonData );
759
760     r = CallWindowProcW( info->oldproc, hWnd, msg, wParam, lParam );
761
762     switch( msg )
763     {
764     case WM_NCDESTROY:
765         msi_free( info );
766         RemovePropW( hWnd, szButtonData );
767         break;
768     case WM_PAINT:
769         /* native MSI sets a wait cursor here */
770         msi_dialog_button_handler( info->dialog, info->control, BN_CLICKED );
771         break;
772     }
773     return r;
774 }
775
776 struct msi_streamin_info
777 {
778     LPSTR string;
779     DWORD offset;
780     DWORD length;
781 };
782
783 static DWORD CALLBACK
784 msi_richedit_stream_in( DWORD_PTR arg, LPBYTE buffer, LONG count, LONG *pcb )
785 {
786     struct msi_streamin_info *info = (struct msi_streamin_info*) arg;
787
788     if( (count + info->offset) > info->length )
789         count = info->length - info->offset;
790     memcpy( buffer, &info->string[ info->offset ], count );
791     *pcb = count;
792     info->offset += count;
793
794     TRACE("%ld/%ld\n", info->offset, info->length);
795
796     return 0;
797 }
798
799 static void msi_scrolltext_add_text( msi_control *control, LPCWSTR text )
800 {
801     struct msi_streamin_info info;
802     EDITSTREAM es;
803
804     info.string = strdupWtoA( text );
805     info.offset = 0;
806     info.length = lstrlenA( info.string ) + 1;
807
808     es.dwCookie = (DWORD_PTR) &info;
809     es.dwError = 0;
810     es.pfnCallback = msi_richedit_stream_in;
811
812     SendMessageW( control->hwnd, EM_STREAMIN, SF_RTF, (LPARAM) &es );
813
814     msi_free( info.string );
815 }
816
817 static UINT msi_dialog_scrolltext_control( msi_dialog *dialog, MSIRECORD *rec )
818 {
819     static const WCHAR szRichEdit20W[] = {
820         'R','i','c','h','E','d','i','t','2','0','W',0
821     };
822     struct msi_scrolltext_info *info;
823     msi_control *control;
824     HMODULE hRichedit;
825     DWORD style;
826
827     info = msi_alloc( sizeof *info );
828     if (!info)
829         return ERROR_FUNCTION_FAILED;
830
831     hRichedit = LoadLibraryA("riched20");
832
833     style = WS_BORDER | ES_MULTILINE | WS_VSCROLL |
834             ES_READONLY | ES_AUTOVSCROLL | WS_TABSTOP;
835     control = msi_dialog_add_control( dialog, rec, szRichEdit20W, style );
836     if (!control)
837     {
838         FreeLibrary( hRichedit );
839         msi_free( info );
840         return ERROR_FUNCTION_FAILED;
841     }
842
843     control->hDll = hRichedit;
844
845     info->dialog = dialog;
846     info->control = control;
847
848     /* subclass the static control */
849     info->oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
850                                           (LONG_PTR)MSIScrollText_WndProc );
851     SetPropW( control->hwnd, szButtonData, info );
852
853     /* add the text into the richedit */
854     msi_scrolltext_add_text( control, MSI_RecordGetString( rec, 10 ) );
855
856     return ERROR_SUCCESS;
857 }
858
859 static HBITMAP msi_load_picture( MSIDATABASE *db, LPCWSTR name,
860                                  INT cx, INT cy, DWORD flags )
861 {
862     HBITMAP hOleBitmap = 0, hBitmap = 0, hOldSrcBitmap, hOldDestBitmap;
863     MSIRECORD *rec = NULL;
864     IStream *stm = NULL;
865     IPicture *pic = NULL;
866     HDC srcdc, destdc;
867     BITMAP bm;
868     UINT r;
869
870     rec = msi_get_binary_record( db, name );
871     if( !rec )
872         goto end;
873
874     r = MSI_RecordGetIStream( rec, 2, &stm );
875     msiobj_release( &rec->hdr );
876     if( r != ERROR_SUCCESS )
877         goto end;
878
879     r = OleLoadPicture( stm, 0, TRUE, &IID_IPicture, (LPVOID*) &pic );
880     IStream_Release( stm );
881     if( FAILED( r ) )
882     {
883         ERR("failed to load picture\n");
884         goto end;
885     }
886
887     r = IPicture_get_Handle( pic, (OLE_HANDLE*) &hOleBitmap );
888     if( FAILED( r ) )
889     {
890         ERR("failed to get bitmap handle\n");
891         goto end;
892     }
893  
894     /* make the bitmap the desired size */
895     r = GetObjectW( hOleBitmap, sizeof bm, &bm );
896     if (r != sizeof bm )
897     {
898         ERR("failed to get bitmap size\n");
899         goto end;
900     }
901
902     if (flags & LR_DEFAULTSIZE)
903     {
904         cx = bm.bmWidth;
905         cy = bm.bmHeight;
906     }
907
908     srcdc = CreateCompatibleDC( NULL );
909     hOldSrcBitmap = SelectObject( srcdc, hOleBitmap );
910     destdc = CreateCompatibleDC( NULL );
911     hBitmap = CreateCompatibleBitmap( srcdc, cx, cy );
912     hOldDestBitmap = SelectObject( destdc, hBitmap );
913     StretchBlt( destdc, 0, 0, cx, cy,
914                 srcdc, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
915     SelectObject( srcdc, hOldSrcBitmap );
916     SelectObject( destdc, hOldDestBitmap );
917     DeleteDC( srcdc );
918     DeleteDC( destdc );
919
920 end:
921     if ( pic )
922         IPicture_Release( pic );
923     return hBitmap;
924 }
925
926 static UINT msi_dialog_bitmap_control( msi_dialog *dialog, MSIRECORD *rec )
927 {
928     UINT cx, cy, flags, style, attributes;
929     msi_control *control;
930     LPWSTR text;
931
932     flags = LR_LOADFROMFILE;
933     style = SS_BITMAP | SS_LEFT | WS_GROUP;
934
935     attributes = MSI_RecordGetInteger( rec, 8 );
936     if( attributes & msidbControlAttributesFixedSize )
937     {
938         flags |= LR_DEFAULTSIZE;
939         style |= SS_CENTERIMAGE;
940     }
941
942     control = msi_dialog_add_control( dialog, rec, szStatic, style );
943     cx = MSI_RecordGetInteger( rec, 6 );
944     cy = MSI_RecordGetInteger( rec, 7 );
945     cx = msi_dialog_scale_unit( dialog, cx );
946     cy = msi_dialog_scale_unit( dialog, cy );
947
948     text = msi_get_deformatted_field( dialog->package, rec, 10 );
949     control->hBitmap = msi_load_picture( dialog->package->db, text, cx, cy, flags );
950     if( control->hBitmap )
951         SendMessageW( control->hwnd, STM_SETIMAGE,
952                       IMAGE_BITMAP, (LPARAM) control->hBitmap );
953     else
954         ERR("Failed to load bitmap %s\n", debugstr_w(text));
955
956     msi_free( text );
957     
958     return ERROR_SUCCESS;
959 }
960
961 static UINT msi_dialog_icon_control( msi_dialog *dialog, MSIRECORD *rec )
962 {
963     msi_control *control;
964     DWORD attributes;
965     LPWSTR text;
966
967     TRACE("\n");
968
969     control = msi_dialog_add_control( dialog, rec, szStatic,
970                             SS_ICON | SS_CENTERIMAGE | WS_GROUP );
971             
972     attributes = MSI_RecordGetInteger( rec, 8 );
973     text = msi_get_deformatted_field( dialog->package, rec, 10 );
974     control->hIcon = msi_load_icon( dialog->package->db, text, attributes );
975     if( control->hIcon )
976         SendMessageW( control->hwnd, STM_SETICON, (WPARAM) control->hIcon, 0 );
977     else
978         ERR("Failed to load bitmap %s\n", debugstr_w(text));
979     msi_free( text );
980     return ERROR_SUCCESS;
981 }
982
983 static UINT msi_dialog_combo_control( msi_dialog *dialog, MSIRECORD *rec )
984 {
985     static const WCHAR szCombo[] = { 'C','O','M','B','O','B','O','X',0 };
986
987     msi_dialog_add_control( dialog, rec, szCombo,
988                             SS_BITMAP | SS_LEFT | SS_CENTERIMAGE );
989     return ERROR_SUCCESS;
990 }
991
992 static UINT msi_dialog_edit_control( msi_dialog *dialog, MSIRECORD *rec )
993 {
994     msi_control *control;
995     LPCWSTR prop;
996     LPWSTR val;
997
998     control = msi_dialog_add_control( dialog, rec, szEdit,
999                                       WS_BORDER | WS_TABSTOP );
1000     control->handler = msi_dialog_edit_handler;
1001     prop = MSI_RecordGetString( rec, 9 );
1002     if( prop )
1003         control->property = strdupW( prop );
1004     val = msi_dup_property( dialog->package, control->property );
1005     SetWindowTextW( control->hwnd, val );
1006     msi_free( val );
1007     return ERROR_SUCCESS;
1008 }
1009
1010 /******************** Masked Edit ********************************************/
1011
1012 #define MASK_MAX_GROUPS 10
1013
1014 struct msi_mask_group
1015 {
1016     UINT len;
1017     UINT ofs;
1018     WCHAR type;
1019     HWND hwnd;
1020 };
1021
1022 struct msi_maskedit_info
1023 {
1024     msi_dialog *dialog;
1025     WNDPROC oldproc;
1026     HWND hwnd;
1027     LPWSTR prop;
1028     UINT num_chars;
1029     UINT num_groups;
1030     struct msi_mask_group group[MASK_MAX_GROUPS];
1031 };
1032
1033 static BOOL msi_mask_editable( WCHAR type )
1034 {
1035     switch (type)
1036     {
1037     case '%':
1038     case '#':
1039     case '&':
1040     case '`':
1041     case '?':
1042     case '^':
1043         return TRUE;
1044     }
1045     return FALSE;
1046 }
1047
1048 static void msi_mask_control_change( struct msi_maskedit_info *info )
1049 {
1050     LPWSTR val;
1051     UINT i, n, r;
1052
1053     val = msi_alloc( (info->num_chars+1)*sizeof(WCHAR) );
1054     for( i=0, n=0; i<info->num_groups; i++ )
1055     {
1056         if( (info->group[i].len + n) > info->num_chars )
1057         {
1058             ERR("can't fit control %d text into template\n",i);
1059             break;
1060         }
1061         if (!msi_mask_editable(info->group[i].type))
1062         {
1063             for(r=0; r<info->group[i].len; r++)
1064                 val[n+r] = info->group[i].type;
1065             val[n+r] = 0;
1066         }
1067         else
1068         {
1069             r = GetWindowTextW( info->group[i].hwnd, &val[n], info->group[i].len+1 );
1070             if( r != info->group[i].len )
1071                 break;
1072         }
1073         n += r;
1074     }
1075
1076     TRACE("%d/%d controls were good\n", i, info->num_groups);
1077
1078     if( i == info->num_groups )
1079     {
1080         TRACE("Set property %s to %s\n",
1081               debugstr_w(info->prop), debugstr_w(val) );
1082         CharUpperBuffW( val, info->num_chars );
1083         MSI_SetPropertyW( info->dialog->package, info->prop, val );
1084         msi_dialog_evaluate_control_conditions( info->dialog );
1085     }
1086     msi_free( val );
1087 }
1088
1089 /* now move to the next control if necessary */
1090 static VOID msi_mask_next_control( struct msi_maskedit_info *info, HWND hWnd )
1091 {
1092     HWND hWndNext;
1093     UINT len, i;
1094
1095     for( i=0; i<info->num_groups; i++ )
1096         if( info->group[i].hwnd == hWnd )
1097             break;
1098
1099     /* don't move from the last control */
1100     if( i >= (info->num_groups-1) )
1101         return;
1102
1103     len = SendMessageW( hWnd, WM_GETTEXTLENGTH, 0, 0 );
1104     if( len < info->group[i].len )
1105         return;
1106
1107     hWndNext = GetNextDlgTabItem( GetParent( hWnd ), hWnd, FALSE );
1108     SetFocus( hWndNext );
1109 }
1110
1111 static LRESULT WINAPI
1112 MSIMaskedEdit_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
1113 {
1114     struct msi_maskedit_info *info;
1115     HRESULT r;
1116
1117     TRACE("%p %04x %08x %08lx\n", hWnd, msg, wParam, lParam);
1118
1119     info = GetPropW(hWnd, szButtonData);
1120
1121     r = CallWindowProcW(info->oldproc, hWnd, msg, wParam, lParam);
1122
1123     switch( msg )
1124     {
1125     case WM_COMMAND:
1126         if (HIWORD(wParam) == EN_CHANGE)
1127         {
1128             msi_mask_control_change( info );
1129             msi_mask_next_control( info, (HWND) lParam );
1130         }
1131         break;
1132     case WM_NCDESTROY:
1133         msi_free( info->prop );
1134         msi_free( info );
1135         RemovePropW( hWnd, szButtonData );
1136         break;
1137     }
1138
1139     return r;
1140 }
1141
1142 /* fish the various bits of the property out and put them in the control */
1143 static void
1144 msi_maskedit_set_text( struct msi_maskedit_info *info, LPCWSTR text )
1145 {
1146     LPCWSTR p;
1147     UINT i;
1148
1149     p = text;
1150     for( i = 0; i < info->num_groups; i++ )
1151     {
1152         if( info->group[i].len < lstrlenW( p ) )
1153         {
1154             LPWSTR chunk = strdupW( p );
1155             chunk[ info->group[i].len ] = 0;
1156             SetWindowTextW( info->group[i].hwnd, chunk );
1157             msi_free( chunk );
1158         }
1159         else
1160         {
1161             SetWindowTextW( info->group[i].hwnd, p );
1162             break;
1163         }
1164         p += info->group[i].len;
1165     }
1166 }
1167
1168 static struct msi_maskedit_info * msi_dialog_parse_groups( LPCWSTR mask )
1169 {
1170     struct msi_maskedit_info * info = NULL;
1171     int i = 0, n = 0, total = 0;
1172     LPCWSTR p;
1173
1174     TRACE("masked control, template %s\n", debugstr_w(mask));
1175
1176     if( !mask )
1177         return info;
1178
1179     info = msi_alloc_zero( sizeof *info );
1180     if( !info )
1181         return info;
1182
1183     p = strchrW(mask, '<');
1184     if( p )
1185         p++;
1186     else
1187         p = mask;
1188
1189     for( i=0; i<MASK_MAX_GROUPS; i++ )
1190     {
1191         /* stop at the end of the string */
1192         if( p[0] == 0 || p[0] == '>' )
1193             break;
1194
1195         /* count the number of the same identifier */
1196         for( n=0; p[n] == p[0]; n++ )
1197             ;
1198         info->group[i].ofs = total;
1199         info->group[i].type = p[0];
1200         if( p[n] == '=' )
1201         {
1202             n++;
1203             total++; /* an extra not part of the group */
1204         }
1205         info->group[i].len = n;
1206         total += n;
1207         p += n;
1208     }
1209
1210     TRACE("%d characters in %d groups\n", total, i );
1211     if( i == MASK_MAX_GROUPS )
1212         ERR("too many groups in PIDTemplate %s\n", debugstr_w(mask));
1213
1214     info->num_chars = total;
1215     info->num_groups = i;
1216
1217     return info;
1218 }
1219
1220 static void
1221 msi_maskedit_create_children( struct msi_maskedit_info *info, LPCWSTR font )
1222 {
1223     DWORD width, height, style, wx, ww;
1224     RECT rect;
1225     HWND hwnd;
1226     UINT i;
1227
1228     style = WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL;
1229
1230     GetClientRect( info->hwnd, &rect );
1231
1232     width = rect.right - rect.left;
1233     height = rect.bottom - rect.top;
1234
1235     for( i = 0; i < info->num_groups; i++ )
1236     {
1237         if (!msi_mask_editable( info->group[i].type ))
1238             continue;
1239         wx = (info->group[i].ofs * width) / info->num_chars;
1240         ww = (info->group[i].len * width) / info->num_chars;
1241
1242         hwnd = CreateWindowW( szEdit, NULL, style, wx, 0, ww, height,
1243                               info->hwnd, NULL, NULL, NULL );
1244         if( !hwnd )
1245         {
1246             ERR("failed to create mask edit sub window\n");
1247             break;
1248         }
1249
1250         SendMessageW( hwnd, EM_LIMITTEXT, info->group[i].len, 0 );
1251
1252         msi_dialog_set_font( info->dialog, hwnd,
1253                              font?font:info->dialog->default_font );
1254         info->group[i].hwnd = hwnd;
1255     }
1256 }
1257
1258 /*
1259  * office 2003 uses "73931<````=````=````=````=`````>@@@@@"
1260  * delphi 7 uses "<????-??????-??????-????>" and "<???-???>"
1261  * filemaker pro 7 uses "<^^^^=^^^^=^^^^=^^^^=^^^^=^^^^=^^^^^>"
1262  */
1263 static UINT msi_dialog_maskedit_control( msi_dialog *dialog, MSIRECORD *rec )
1264 {
1265     LPWSTR font_mask, val = NULL, font;
1266     struct msi_maskedit_info *info = NULL;
1267     UINT ret = ERROR_SUCCESS;
1268     msi_control *control;
1269     LPCWSTR prop, mask;
1270
1271     TRACE("\n");
1272
1273     font_mask = msi_get_deformatted_field( dialog->package, rec, 10 );
1274     font = msi_dialog_get_style( font_mask, &mask );
1275     if( !mask )
1276     {
1277         ERR("mask template is empty\n");
1278         goto end;
1279     }
1280
1281     info = msi_dialog_parse_groups( mask );
1282     if( !info )
1283     {
1284         ERR("template %s is invalid\n", debugstr_w(mask));
1285         goto end;
1286     }
1287
1288     info->dialog = dialog;
1289
1290     control = msi_dialog_add_control( dialog, rec, szStatic,
1291                    SS_OWNERDRAW | WS_GROUP | WS_VISIBLE );
1292     if( !control )
1293     {
1294         ERR("Failed to create maskedit container\n");
1295         ret = ERROR_FUNCTION_FAILED;
1296         goto end;
1297     }
1298     SetWindowLongPtrW( control->hwnd, GWL_EXSTYLE, WS_EX_CONTROLPARENT );
1299
1300     info->hwnd = control->hwnd;
1301
1302     /* subclass the static control */
1303     info->oldproc = (WNDPROC) SetWindowLongPtrW( info->hwnd, GWLP_WNDPROC,
1304                                           (LONG_PTR)MSIMaskedEdit_WndProc );
1305     SetPropW( control->hwnd, szButtonData, info );
1306
1307     prop = MSI_RecordGetString( rec, 9 );
1308     if( prop )
1309         info->prop = strdupW( prop );
1310
1311     msi_maskedit_create_children( info, font );
1312
1313     if( prop )
1314     {
1315         val = msi_dup_property( dialog->package, prop );
1316         if( val )
1317         {
1318             msi_maskedit_set_text( info, val );
1319             msi_free( val );
1320         }
1321     }
1322
1323 end:
1324     if( ret != ERROR_SUCCESS )
1325         msi_free( info );
1326     msi_free( font_mask );
1327     msi_free( font );
1328     return ret;
1329 }
1330
1331 /******************** Progress Bar *****************************************/
1332
1333 static UINT msi_dialog_progress_bar( msi_dialog *dialog, MSIRECORD *rec )
1334 {
1335     msi_dialog_add_control( dialog, rec, PROGRESS_CLASSW, WS_VISIBLE );
1336     return ERROR_SUCCESS;
1337 }
1338
1339 /******************** Path Edit ********************************************/
1340
1341 static UINT msi_dialog_pathedit_control( msi_dialog *dialog, MSIRECORD *rec )
1342 {
1343     FIXME("not implemented properly\n");
1344     return msi_dialog_edit_control( dialog, rec );
1345 }
1346
1347 /* radio buttons are a bit different from normal controls */
1348 static UINT msi_dialog_create_radiobutton( MSIRECORD *rec, LPVOID param )
1349 {
1350     radio_button_group_descr *group = (radio_button_group_descr *)param;
1351     msi_dialog *dialog = group->dialog;
1352     msi_control *control;
1353     LPCWSTR prop, text, name;
1354     DWORD style, attributes = group->attributes;
1355
1356     style = WS_CHILD | BS_AUTORADIOBUTTON | BS_MULTILINE | WS_TABSTOP;
1357     name = MSI_RecordGetString( rec, 3 );
1358     text = MSI_RecordGetString( rec, 8 );
1359     if( attributes & 1 )
1360         style |= WS_VISIBLE;
1361     if( ~attributes & 2 )
1362         style |= WS_DISABLED;
1363
1364     control = msi_dialog_create_window( dialog, rec, 0, szButton, name, text,
1365                                         style, group->parent->hwnd );
1366     if (!control)
1367         return ERROR_FUNCTION_FAILED;
1368     control->handler = msi_dialog_radiogroup_handler;
1369
1370     if (!lstrcmpW(control->name, group->propval))
1371         SendMessageW(control->hwnd, BM_SETCHECK, BST_CHECKED, 0);
1372
1373     prop = MSI_RecordGetString( rec, 1 );
1374     if( prop )
1375         control->property = strdupW( prop );
1376
1377     return ERROR_SUCCESS;
1378 }
1379
1380 static UINT msi_dialog_radiogroup_control( msi_dialog *dialog, MSIRECORD *rec )
1381 {
1382     static const WCHAR query[] = {
1383         'S','E','L','E','C','T',' ','*',' ',
1384         'F','R','O','M',' ','R','a','d','i','o','B','u','t','t','o','n',' ',
1385         'W','H','E','R','E',' ',
1386            '`','P','r','o','p','e','r','t','y','`',' ','=',' ','\'','%','s','\'',0};
1387     UINT r;
1388     LPCWSTR prop;
1389     msi_control *control;
1390     MSIQUERY *view = NULL;
1391     radio_button_group_descr group;
1392     MSIPACKAGE *package = dialog->package;
1393     WNDPROC oldproc;
1394
1395     prop = MSI_RecordGetString( rec, 9 );
1396
1397     TRACE("%p %p %s\n", dialog, rec, debugstr_w( prop ));
1398
1399     /* Create parent group box to hold radio buttons */
1400     control = msi_dialog_add_control( dialog, rec, szButton, BS_OWNERDRAW|WS_GROUP );
1401     if( !control )
1402         return ERROR_FUNCTION_FAILED;
1403
1404     oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
1405                                            (LONG_PTR)MSIRadioGroup_WndProc );
1406     SetPropW(control->hwnd, szButtonData, oldproc);
1407     SetWindowLongPtrW( control->hwnd, GWL_EXSTYLE, WS_EX_CONTROLPARENT );
1408
1409     if( prop )
1410         control->property = strdupW( prop );
1411
1412     /* query the Radio Button table for all control in this group */
1413     r = MSI_OpenQuery( package->db, &view, query, prop );
1414     if( r != ERROR_SUCCESS )
1415     {
1416         ERR("query failed for dialog %s radio group %s\n", 
1417             debugstr_w(dialog->name), debugstr_w(prop));
1418         return ERROR_INVALID_PARAMETER;
1419     }
1420
1421     group.dialog = dialog;
1422     group.parent = control;
1423     group.attributes = MSI_RecordGetInteger( rec, 8 );
1424     group.propval = msi_dup_property( dialog->package, control->property );
1425
1426     r = MSI_IterateRecords( view, 0, msi_dialog_create_radiobutton, &group );
1427     msiobj_release( &view->hdr );
1428     msi_free( group.propval );
1429
1430     return r;
1431 }
1432
1433 /******************** Selection Tree ***************************************/
1434
1435 struct msi_selection_tree_info
1436 {
1437     msi_dialog *dialog;
1438     HWND hwnd;
1439     WNDPROC oldproc;
1440 };
1441
1442 static void
1443 msi_seltree_sync_item_state( HWND hwnd, MSIFEATURE *feature, HTREEITEM hItem )
1444 {
1445     TVITEMW tvi;
1446
1447     TRACE("Feature %s -> %d %d %d\n", debugstr_w(feature->Title),
1448         feature->Installed, feature->Action, feature->ActionRequest);
1449
1450     tvi.mask = TVIF_STATE;
1451     tvi.hItem = hItem;
1452     tvi.state = INDEXTOSTATEIMAGEMASK( feature->Action );
1453     tvi.stateMask = TVIS_STATEIMAGEMASK;
1454
1455     SendMessageW( hwnd, TVM_SETITEMW, 0, (LPARAM) &tvi );
1456 }
1457
1458 static UINT
1459 msi_seltree_popup_menu( HWND hwnd, INT x, INT y )
1460 {
1461     HMENU hMenu;
1462     INT r;
1463
1464     /* create a menu to display */
1465     hMenu = CreatePopupMenu();
1466
1467     /* FIXME: load strings from resources */
1468     AppendMenuA( hMenu, MF_ENABLED, INSTALLSTATE_LOCAL, "Install feature locally");
1469     AppendMenuA( hMenu, MF_GRAYED, 0x1000, "Install entire feature");
1470     AppendMenuA( hMenu, MF_ENABLED, INSTALLSTATE_ADVERTISED, "Install on demand");
1471     AppendMenuA( hMenu, MF_ENABLED, INSTALLSTATE_ABSENT, "Don't install");
1472     r = TrackPopupMenu( hMenu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RETURNCMD,
1473                         x, y, 0, hwnd, NULL );
1474     DestroyMenu( hMenu );
1475     return r;
1476 }
1477
1478 static MSIFEATURE *
1479 msi_seltree_feature_from_item( HWND hwnd, HTREEITEM hItem )
1480 {
1481     TVITEMW tvi;
1482
1483     /* get the feature from the item */
1484     memset( &tvi, 0, sizeof tvi );
1485     tvi.hItem = hItem;
1486     tvi.mask = TVIF_PARAM | TVIF_HANDLE;
1487     SendMessageW( hwnd, TVM_GETITEMW, 0, (LPARAM) &tvi );
1488
1489     return (MSIFEATURE*) tvi.lParam;
1490 }
1491
1492 static LRESULT
1493 msi_seltree_menu( HWND hwnd, HTREEITEM hItem )
1494 {
1495     MSIFEATURE *feature;
1496     union {
1497         RECT rc;
1498         POINT pt[2];
1499         HTREEITEM hItem;
1500     } u;
1501     UINT r;
1502
1503     feature = msi_seltree_feature_from_item( hwnd, hItem );
1504     if (!feature)
1505     {
1506         ERR("item %p feature was NULL\n", hItem);
1507         return 0;
1508     }
1509
1510     /* get the item's rectangle to put the menu just below it */
1511     u.hItem = hItem;
1512     SendMessageW( hwnd, TVM_GETITEMRECT, 0, (LPARAM) &u.rc );
1513     MapWindowPoints( hwnd, NULL, u.pt, 2 );
1514
1515     r = msi_seltree_popup_menu( hwnd, u.rc.left, u.rc.top );
1516
1517     switch (r)
1518     {
1519     case INSTALLSTATE_LOCAL:
1520     case INSTALLSTATE_ADVERTISED:
1521     case INSTALLSTATE_ABSENT:
1522         feature->ActionRequest = r;
1523         feature->Action = r;
1524         break;
1525     default:
1526         FIXME("select feature and all children\n");
1527     }
1528
1529     /* update */
1530     msi_seltree_sync_item_state( hwnd, feature, hItem );
1531
1532     return 0;
1533 }
1534
1535 static LRESULT WINAPI
1536 MSISelectionTree_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
1537 {
1538     struct msi_selection_tree_info *info;
1539     TVHITTESTINFO tvhti;
1540     HRESULT r;
1541
1542     TRACE("%p %04x %08x %08lx\n", hWnd, msg, wParam, lParam);
1543
1544     info = GetPropW(hWnd, szButtonData);
1545
1546     switch( msg )
1547     {
1548     case WM_LBUTTONDOWN:
1549         tvhti.pt.x = LOWORD( lParam );
1550         tvhti.pt.y = HIWORD( lParam );
1551         tvhti.flags = 0;
1552         tvhti.hItem = 0;
1553         r = CallWindowProcW(info->oldproc, hWnd, TVM_HITTEST, 0, (LPARAM) &tvhti );
1554         if (tvhti.flags & TVHT_ONITEMSTATEICON)
1555             return msi_seltree_menu( hWnd, tvhti.hItem );
1556         break;
1557     }
1558
1559     r = CallWindowProcW(info->oldproc, hWnd, msg, wParam, lParam);
1560
1561     switch( msg )
1562     {
1563     case WM_NCDESTROY:
1564         msi_free( info );
1565         RemovePropW( hWnd, szButtonData );
1566         break;
1567     }
1568     return r;
1569 }
1570
1571 static void
1572 msi_seltree_add_child_features( MSIPACKAGE *package, HWND hwnd,
1573                                 LPCWSTR parent, HTREEITEM hParent )
1574 {
1575     MSIFEATURE *feature;
1576     TVINSERTSTRUCTW tvis;
1577     HTREEITEM hitem;
1578
1579     LIST_FOR_EACH_ENTRY( feature, &package->features, MSIFEATURE, entry )
1580     {
1581         if ( lstrcmpW( parent, feature->Feature_Parent ) )
1582             continue;
1583
1584         if ( !feature->Title )
1585             continue;
1586
1587         memset( &tvis, 0, sizeof tvis );
1588         tvis.hParent = hParent;
1589         tvis.hInsertAfter = TVI_SORT;
1590         tvis.u.item.mask = TVIF_TEXT | TVIF_PARAM;
1591         tvis.u.item.pszText = feature->Title;
1592         tvis.u.item.lParam = (LPARAM) feature;
1593
1594         hitem = (HTREEITEM) SendMessageW( hwnd, TVM_INSERTITEMW, 0, (LPARAM) &tvis );
1595         if (!hitem)
1596             continue;
1597
1598         msi_seltree_sync_item_state( hwnd, feature, hitem );
1599         msi_seltree_add_child_features( package, hwnd,
1600                                         feature->Feature, hitem );
1601     }
1602 }
1603
1604 static void msi_seltree_create_imagelist( HWND hwnd )
1605 {
1606     const int bm_width = 32, bm_height = 16, bm_count = 3;
1607     const int bm_resource = 0x1001;
1608     HIMAGELIST himl;
1609     int i;
1610     HBITMAP hbmp;
1611
1612     himl = ImageList_Create( bm_width, bm_height, FALSE, 4, 0 );
1613     if (!himl)
1614     {
1615         ERR("failed to create image list\n");
1616         return;
1617     }
1618
1619     for (i=0; i<bm_count; i++)
1620     {
1621         hbmp = LoadBitmapW( msi_hInstance, MAKEINTRESOURCEW(i+bm_resource) );
1622         if (!hbmp)
1623         {
1624             ERR("failed to load bitmap %d\n", i);
1625             break;
1626         }
1627
1628         /*
1629          * Add a dummy bitmap at offset zero because the treeview
1630          * can't use it as a state mask (zero means no user state).
1631          */
1632         if (!i)
1633             ImageList_Add( himl, hbmp, NULL );
1634
1635         ImageList_Add( himl, hbmp, NULL );
1636     }
1637
1638     SendMessageW( hwnd, TVM_SETIMAGELIST, TVSIL_STATE, (LPARAM)himl );
1639 }
1640
1641 static UINT msi_dialog_selection_tree( msi_dialog *dialog, MSIRECORD *rec )
1642 {
1643     msi_control *control;
1644     LPCWSTR prop;
1645     MSIPACKAGE *package = dialog->package;
1646     DWORD style;
1647     struct msi_selection_tree_info *info;
1648
1649     info = msi_alloc( sizeof *info );
1650     if (!info)
1651         return ERROR_FUNCTION_FAILED;
1652
1653     /* create the treeview control */
1654     prop = MSI_RecordGetString( rec, 9 );
1655     style = TVS_HASLINES | TVS_HASBUTTONS | TVS_LINESATROOT;
1656     style |= WS_GROUP | WS_VSCROLL;
1657     control = msi_dialog_add_control( dialog, rec, WC_TREEVIEWW, style );
1658     if (!control)
1659     {
1660         msi_free(info);
1661         return ERROR_FUNCTION_FAILED;
1662     }
1663
1664     /* subclass */
1665     info->dialog = dialog;
1666     info->hwnd = control->hwnd;
1667     info->oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
1668                                           (LONG_PTR)MSISelectionTree_WndProc );
1669     SetPropW( control->hwnd, szButtonData, info );
1670
1671     /* initialize it */
1672     msi_seltree_create_imagelist( control->hwnd );
1673     msi_seltree_add_child_features( package, control->hwnd, NULL, NULL );
1674
1675     return ERROR_SUCCESS;
1676 }
1677
1678 static const struct control_handler msi_dialog_handler[] =
1679 {
1680     { szText, msi_dialog_text_control },
1681     { szPushButton, msi_dialog_button_control },
1682     { szLine, msi_dialog_line_control },
1683     { szBitmap, msi_dialog_bitmap_control },
1684     { szCheckBox, msi_dialog_checkbox_control },
1685     { szScrollableText, msi_dialog_scrolltext_control },
1686     { szComboBox, msi_dialog_combo_control },
1687     { szEdit, msi_dialog_edit_control },
1688     { szMaskedEdit, msi_dialog_maskedit_control },
1689     { szPathEdit, msi_dialog_pathedit_control },
1690     { szProgressBar, msi_dialog_progress_bar },
1691     { szRadioButtonGroup, msi_dialog_radiogroup_control },
1692     { szIcon, msi_dialog_icon_control },
1693     { szSelectionTree, msi_dialog_selection_tree },
1694 };
1695
1696 #define NUM_CONTROL_TYPES (sizeof msi_dialog_handler/sizeof msi_dialog_handler[0])
1697
1698 static UINT msi_dialog_create_controls( MSIRECORD *rec, LPVOID param )
1699 {
1700     msi_dialog *dialog = param;
1701     LPCWSTR control_type;
1702     UINT i;
1703
1704     /* find and call the function that can create this type of control */
1705     control_type = MSI_RecordGetString( rec, 3 );
1706     for( i=0; i<NUM_CONTROL_TYPES; i++ )
1707         if (!strcmpiW( msi_dialog_handler[i].control_type, control_type ))
1708             break;
1709     if( i != NUM_CONTROL_TYPES )
1710         msi_dialog_handler[i].func( dialog, rec );
1711     else
1712         ERR("no handler for element type %s\n", debugstr_w(control_type));
1713
1714     return ERROR_SUCCESS;
1715 }
1716
1717 static UINT msi_dialog_fill_controls( msi_dialog *dialog )
1718 {
1719     static const WCHAR query[] = {
1720         'S','E','L','E','C','T',' ','*',' ',
1721         'F','R','O','M',' ','C','o','n','t','r','o','l',' ',
1722         'W','H','E','R','E',' ',
1723            '`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',0};
1724     UINT r;
1725     MSIQUERY *view = NULL;
1726     MSIPACKAGE *package = dialog->package;
1727
1728     TRACE("%p %s\n", dialog, debugstr_w(dialog->name) );
1729
1730     /* query the Control table for all the elements of the control */
1731     r = MSI_OpenQuery( package->db, &view, query, dialog->name );
1732     if( r != ERROR_SUCCESS )
1733     {
1734         ERR("query failed for dialog %s\n", debugstr_w(dialog->name));
1735         return ERROR_INVALID_PARAMETER;
1736     }
1737
1738     r = MSI_IterateRecords( view, 0, msi_dialog_create_controls, dialog );
1739     msiobj_release( &view->hdr );
1740
1741     return r;
1742 }
1743
1744 static UINT msi_dialog_set_control_condition( MSIRECORD *rec, LPVOID param )
1745 {
1746     static const WCHAR szHide[] = { 'H','i','d','e',0 };
1747     static const WCHAR szShow[] = { 'S','h','o','w',0 };
1748     static const WCHAR szDisable[] = { 'D','i','s','a','b','l','e',0 };
1749     static const WCHAR szEnable[] = { 'E','n','a','b','l','e',0 };
1750     msi_dialog *dialog = param;
1751     msi_control *control;
1752     LPCWSTR name, action, condition;
1753     UINT r;
1754
1755     name = MSI_RecordGetString( rec, 2 );
1756     action = MSI_RecordGetString( rec, 3 );
1757     condition = MSI_RecordGetString( rec, 4 );
1758     r = MSI_EvaluateConditionW( dialog->package, condition );
1759     control = msi_dialog_find_control( dialog, name );
1760     if( r == MSICONDITION_TRUE && control )
1761     {
1762         TRACE("%s control %s\n", debugstr_w(action), debugstr_w(name));
1763
1764         /* FIXME: case sensitive? */
1765         if(!lstrcmpW(action, szHide))
1766             ShowWindow(control->hwnd, SW_HIDE);
1767         else if(!strcmpW(action, szShow))
1768             ShowWindow(control->hwnd, SW_SHOW);
1769         else if(!strcmpW(action, szDisable))
1770             EnableWindow(control->hwnd, FALSE);
1771         else if(!strcmpW(action, szEnable))
1772             EnableWindow(control->hwnd, TRUE);
1773         else
1774             FIXME("Unhandled action %s\n", debugstr_w(action));
1775     }
1776
1777     return ERROR_SUCCESS;
1778 }
1779
1780 static UINT msi_dialog_evaluate_control_conditions( msi_dialog *dialog )
1781 {
1782     static const WCHAR query[] = {
1783       'S','E','L','E','C','T',' ','*',' ',
1784       'F','R','O','M',' ',
1785         'C','o','n','t','r','o','l','C','o','n','d','i','t','i','o','n',' ',
1786       'W','H','E','R','E',' ',
1787         '`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',0
1788     };
1789     UINT r;
1790     MSIQUERY *view = NULL;
1791     MSIPACKAGE *package = dialog->package;
1792
1793     TRACE("%p %s\n", dialog, debugstr_w(dialog->name) );
1794
1795     /* query the Control table for all the elements of the control */
1796     r = MSI_OpenQuery( package->db, &view, query, dialog->name );
1797     if( r != ERROR_SUCCESS )
1798     {
1799         ERR("query failed for dialog %s\n", debugstr_w(dialog->name));
1800         return ERROR_INVALID_PARAMETER;
1801     }
1802
1803     r = MSI_IterateRecords( view, 0, msi_dialog_set_control_condition, dialog );
1804     msiobj_release( &view->hdr );
1805
1806     return r;
1807 }
1808
1809 UINT msi_dialog_reset( msi_dialog *dialog )
1810 {
1811     /* FIXME: should restore the original values of any properties we changed */
1812     return msi_dialog_evaluate_control_conditions( dialog );
1813 }
1814
1815 /* figure out the height of 10 point MS Sans Serif */
1816 static INT msi_dialog_get_sans_serif_height( HWND hwnd )
1817 {
1818     static const WCHAR szSansSerif[] = {
1819         'M','S',' ','S','a','n','s',' ','S','e','r','i','f',0 };
1820     LOGFONTW lf;
1821     TEXTMETRICW tm;
1822     BOOL r;
1823     LONG height = 0;
1824     HFONT hFont, hOldFont;
1825     HDC hdc;
1826
1827     hdc = GetDC( hwnd );
1828     if (hdc)
1829     {
1830         memset( &lf, 0, sizeof lf );
1831         lf.lfHeight = MulDiv(10, GetDeviceCaps(hdc, LOGPIXELSY), 72);
1832         strcpyW( lf.lfFaceName, szSansSerif );
1833         hFont = CreateFontIndirectW(&lf);
1834         if (hFont)
1835         {
1836             hOldFont = SelectObject( hdc, hFont );
1837             r = GetTextMetricsW( hdc, &tm );
1838             if (r)
1839                 height = tm.tmHeight;
1840             SelectObject( hdc, hOldFont );
1841             DeleteObject( hFont );
1842         }
1843         ReleaseDC( hwnd, hdc );
1844     }
1845     return height;
1846 }
1847
1848 /* fetch the associated record from the Dialog table */
1849 static MSIRECORD *msi_get_dialog_record( msi_dialog *dialog )
1850 {
1851     static const WCHAR query[] = {
1852         'S','E','L','E','C','T',' ','*',' ',
1853         'F','R','O','M',' ','D','i','a','l','o','g',' ',
1854         'W','H','E','R','E',' ',
1855            '`','D','i','a','l','o','g','`',' ','=',' ','\'','%','s','\'',0};
1856     MSIPACKAGE *package = dialog->package;
1857     MSIRECORD *rec = NULL;
1858
1859     TRACE("%p %s\n", dialog, debugstr_w(dialog->name) );
1860
1861     rec = MSI_QueryGetRecord( package->db, query, dialog->name );
1862     if( !rec )
1863         ERR("query failed for dialog %s\n", debugstr_w(dialog->name));
1864
1865     return rec;
1866 }
1867
1868 static void msi_dialog_adjust_dialog_size( msi_dialog *dialog, LPSIZE sz )
1869 {
1870     RECT rect;
1871     LONG style;
1872
1873     /* turn the client size into the window rectangle */
1874     rect.left = 0;
1875     rect.top = 0;
1876     rect.right = msi_dialog_scale_unit( dialog, sz->cx );
1877     rect.bottom = msi_dialog_scale_unit( dialog, sz->cy );
1878     style = GetWindowLongPtrW( dialog->hwnd, GWL_STYLE );
1879     AdjustWindowRect( &rect, style, FALSE );
1880     sz->cx = rect.right - rect.left;
1881     sz->cy = rect.bottom - rect.top;
1882 }
1883
1884 static BOOL msi_control_set_next( msi_control *control, msi_control *next )
1885 {
1886     return SetWindowPos( next->hwnd, control->hwnd, 0, 0, 0, 0,
1887                          SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOREDRAW |
1888                          SWP_NOREPOSITION | SWP_NOSENDCHANGING | SWP_NOSIZE );
1889 }
1890
1891 static UINT msi_dialog_set_tab_order( msi_dialog *dialog )
1892 {
1893     msi_control *control, *tab_next;
1894
1895     LIST_FOR_EACH_ENTRY( control, &dialog->controls, msi_control, entry )
1896     {
1897         tab_next = msi_dialog_find_control( dialog, control->tabnext );
1898         if( !tab_next )
1899             continue;
1900         msi_control_set_next( control, tab_next );
1901     }
1902
1903     return ERROR_SUCCESS;
1904 }
1905
1906 static void msi_dialog_set_first_control( msi_dialog* dialog, LPCWSTR name )
1907 {
1908     msi_control *control;
1909
1910     control = msi_dialog_find_control( dialog, name );
1911     if( control )
1912         dialog->hWndFocus = control->hwnd;
1913     else
1914         dialog->hWndFocus = NULL;
1915 }
1916
1917 static LRESULT msi_dialog_oncreate( HWND hwnd, LPCREATESTRUCTW cs )
1918 {
1919     static const WCHAR df[] = {
1920         'D','e','f','a','u','l','t','U','I','F','o','n','t',0 };
1921     static const WCHAR dfv[] = {
1922         'M','S',' ','S','h','e','l','l',' ','D','l','g',0 };
1923     msi_dialog *dialog = (msi_dialog*) cs->lpCreateParams;
1924     MSIRECORD *rec = NULL;
1925     LPWSTR title = NULL;
1926     SIZE size;
1927
1928     TRACE("%p %p\n", dialog, dialog->package);
1929
1930     dialog->hwnd = hwnd;
1931     SetWindowLongPtrW( hwnd, GWLP_USERDATA, (LONG_PTR) dialog );
1932
1933     rec = msi_get_dialog_record( dialog );
1934     if( !rec )
1935     {
1936         TRACE("No record found for dialog %s\n", debugstr_w(dialog->name));
1937         return -1;
1938     }
1939
1940     dialog->scale = msi_dialog_get_sans_serif_height(dialog->hwnd);
1941
1942     size.cx = MSI_RecordGetInteger( rec, 4 );
1943     size.cy = MSI_RecordGetInteger( rec, 5 );
1944     msi_dialog_adjust_dialog_size( dialog, &size );
1945
1946     dialog->attributes = MSI_RecordGetInteger( rec, 6 );
1947
1948     dialog->default_font = msi_dup_property( dialog->package, df );
1949     if (!dialog->default_font)
1950     {
1951         dialog->default_font = strdupW(dfv);
1952         if (!dialog->default_font) return -1;
1953     }
1954
1955     title = msi_get_deformatted_field( dialog->package, rec, 7 );
1956     SetWindowTextW( hwnd, title );
1957     msi_free( title );
1958
1959     SetWindowPos( hwnd, 0, 0, 0, size.cx, size.cy,
1960                   SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOREDRAW );
1961
1962     msi_dialog_build_font_list( dialog );
1963     msi_dialog_fill_controls( dialog );
1964     msi_dialog_evaluate_control_conditions( dialog );
1965     msi_dialog_set_tab_order( dialog );
1966     msi_dialog_set_first_control( dialog, MSI_RecordGetString( rec, 8 ) );
1967     msiobj_release( &rec->hdr );
1968
1969     return 0;
1970 }
1971
1972 static UINT msi_dialog_send_event( msi_dialog *dialog, LPCWSTR event, LPCWSTR arg )
1973 {
1974     LPWSTR event_fmt = NULL, arg_fmt = NULL;
1975
1976     TRACE("Sending control event %s %s\n", debugstr_w(event), debugstr_w(arg));
1977
1978     deformat_string( dialog->package, event, &event_fmt );
1979     deformat_string( dialog->package, arg, &arg_fmt );
1980
1981     dialog->event_handler( dialog->package, event_fmt, arg_fmt, dialog );
1982
1983     msi_free( event_fmt );
1984     msi_free( arg_fmt );
1985
1986     return ERROR_SUCCESS;
1987 }
1988
1989 static UINT msi_dialog_set_property( msi_dialog *dialog, LPCWSTR event, LPCWSTR arg )
1990 {
1991     static const WCHAR szNullArg[] = { '{','}',0 };
1992     LPWSTR p, prop, arg_fmt = NULL;
1993     UINT len;
1994
1995     len = strlenW(event);
1996     prop = msi_alloc( len*sizeof(WCHAR));
1997     strcpyW( prop, &event[1] );
1998     p = strchrW( prop, ']' );
1999     if( p && p[1] == 0 )
2000     {
2001         *p = 0;
2002         if( strcmpW( szNullArg, arg ) )
2003             deformat_string( dialog->package, arg, &arg_fmt );
2004         MSI_SetPropertyW( dialog->package, prop, arg_fmt );
2005         msi_free( arg_fmt );
2006     }
2007     else
2008         ERR("Badly formatted property string - what happens?\n");
2009     msi_free( prop );
2010     return ERROR_SUCCESS;
2011 }
2012
2013 static UINT msi_dialog_control_event( MSIRECORD *rec, LPVOID param )
2014 {
2015     msi_dialog *dialog = param;
2016     LPCWSTR condition, event, arg;
2017     UINT r;
2018
2019     condition = MSI_RecordGetString( rec, 5 );
2020     r = MSI_EvaluateConditionW( dialog->package, condition );
2021     if( r == MSICONDITION_TRUE || r == MSICONDITION_NONE )
2022     {
2023         event = MSI_RecordGetString( rec, 3 );
2024         arg = MSI_RecordGetString( rec, 4 );
2025         if( event[0] == '[' )
2026             msi_dialog_set_property( dialog, event, arg );
2027         else
2028             msi_dialog_send_event( dialog, event, arg );
2029     }
2030
2031     return ERROR_SUCCESS;
2032 }
2033
2034 static UINT msi_dialog_button_handler( msi_dialog *dialog,
2035                                        msi_control *control, WPARAM param )
2036 {
2037     static const WCHAR query[] = {
2038       'S','E','L','E','C','T',' ','*',' ',
2039       'F','R','O','M',' ','C','o','n','t','r','o','l','E','v','e','n','t',' ',
2040       'W','H','E','R','E',' ',
2041          '`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',' ',
2042       'A','N','D',' ',
2043          '`','C','o','n','t','r','o','l','_','`',' ','=',' ','\'','%','s','\'',' ',
2044       'O','R','D','E','R',' ','B','Y',' ','`','O','r','d','e','r','i','n','g','`',0
2045     };
2046     MSIQUERY *view = NULL;
2047     UINT r;
2048
2049     if( HIWORD(param) != BN_CLICKED )
2050         return ERROR_SUCCESS;
2051
2052     r = MSI_OpenQuery( dialog->package->db, &view, query,
2053                        dialog->name, control->name );
2054     if( r != ERROR_SUCCESS )
2055     {
2056         ERR("query failed\n");
2057         return 0;
2058     }
2059
2060     r = MSI_IterateRecords( view, 0, msi_dialog_control_event, dialog );
2061     msiobj_release( &view->hdr );
2062
2063     return r;
2064 }
2065
2066 static UINT msi_dialog_get_checkbox_state( msi_dialog *dialog,
2067                 msi_control *control )
2068 {
2069     WCHAR state[2] = { 0 };
2070     DWORD sz = 2;
2071
2072     MSI_GetPropertyW( dialog->package, control->property, state, &sz );
2073     return state[0] ? 1 : 0;
2074 }
2075
2076 static void msi_dialog_set_checkbox_state( msi_dialog *dialog,
2077                 msi_control *control, UINT state )
2078 {
2079     static const WCHAR szState[] = { '1', 0 };
2080     LPCWSTR val;
2081
2082     /* if uncheck then the property is set to NULL */
2083     if (!state)
2084     {
2085         MSI_SetPropertyW( dialog->package, control->property, NULL );
2086         return;
2087     }
2088
2089     /* check for a custom state */
2090     if (control->value && control->value[0])
2091         val = control->value;
2092     else
2093         val = szState;
2094
2095     MSI_SetPropertyW( dialog->package, control->property, val );
2096 }
2097
2098 static void msi_dialog_checkbox_sync_state( msi_dialog *dialog,
2099                 msi_control *control )
2100 {
2101     UINT state;
2102
2103     state = msi_dialog_get_checkbox_state( dialog, control );
2104     SendMessageW( control->hwnd, BM_SETCHECK,
2105                   state ? BST_CHECKED : BST_UNCHECKED, 0 );
2106 }
2107
2108 static UINT msi_dialog_checkbox_handler( msi_dialog *dialog,
2109                 msi_control *control, WPARAM param )
2110 {
2111     UINT state;
2112
2113     if( HIWORD(param) != BN_CLICKED )
2114         return ERROR_SUCCESS;
2115
2116     TRACE("clicked checkbox %s, set %s\n", debugstr_w(control->name),
2117           debugstr_w(control->property));
2118
2119     state = msi_dialog_get_checkbox_state( dialog, control );
2120     state = state ? 0 : 1;
2121     msi_dialog_set_checkbox_state( dialog, control, state );
2122     msi_dialog_checkbox_sync_state( dialog, control );
2123
2124     return msi_dialog_button_handler( dialog, control, param );
2125 }
2126
2127 static UINT msi_dialog_edit_handler( msi_dialog *dialog,
2128                 msi_control *control, WPARAM param )
2129 {
2130     UINT sz, r;
2131     LPWSTR buf;
2132
2133     if( HIWORD(param) != EN_CHANGE )
2134         return ERROR_SUCCESS;
2135
2136     TRACE("edit %s contents changed, set %s\n", debugstr_w(control->name),
2137           debugstr_w(control->property));
2138
2139     sz = 0x20;
2140     buf = msi_alloc( sz*sizeof(WCHAR) );
2141     while( buf )
2142     {
2143         r = GetWindowTextW( control->hwnd, buf, sz );
2144         if( r < (sz-1) )
2145             break;
2146         sz *= 2;
2147         buf = msi_realloc( buf, sz*sizeof(WCHAR) );
2148     }
2149
2150     MSI_SetPropertyW( dialog->package, control->property, buf );
2151
2152     msi_free( buf );
2153
2154     return ERROR_SUCCESS;
2155 }
2156
2157 static UINT msi_dialog_radiogroup_handler( msi_dialog *dialog,
2158                 msi_control *control, WPARAM param )
2159 {
2160     if( HIWORD(param) != BN_CLICKED )
2161         return ERROR_SUCCESS;
2162
2163     TRACE("clicked radio button %s, set %s\n", debugstr_w(control->name),
2164           debugstr_w(control->property));
2165
2166     MSI_SetPropertyW( dialog->package, control->property, control->name );
2167
2168     return msi_dialog_button_handler( dialog, control, param );
2169 }
2170
2171 static LRESULT msi_dialog_oncommand( msi_dialog *dialog, WPARAM param, HWND hwnd )
2172 {
2173     msi_control *control = NULL;
2174
2175     TRACE("%p %p %08x\n", dialog, hwnd, param);
2176
2177     switch (param)
2178     {
2179     case 1: /* enter */
2180         control = msi_dialog_find_control( dialog, dialog->control_default );
2181         break;
2182     case 2: /* escape */
2183         control = msi_dialog_find_control( dialog, dialog->control_cancel );
2184         break;
2185     default: 
2186         control = msi_dialog_find_control_by_hwnd( dialog, hwnd );
2187     }
2188
2189     if( control )
2190     {
2191         if( control->handler )
2192         {
2193             control->handler( dialog, control, param );
2194             msi_dialog_evaluate_control_conditions( dialog );
2195         }
2196     }
2197     else
2198         ERR("button click from nowhere %p %d %p\n", dialog, param, hwnd);
2199     return 0;
2200 }
2201
2202 static void msi_dialog_setfocus( msi_dialog *dialog )
2203 {
2204     HWND hwnd = dialog->hWndFocus;
2205
2206     hwnd = GetNextDlgTabItem( dialog->hwnd, hwnd, TRUE);
2207     hwnd = GetNextDlgTabItem( dialog->hwnd, hwnd, FALSE);
2208     SetFocus( hwnd );
2209     dialog->hWndFocus = hwnd;
2210 }
2211
2212 static LRESULT WINAPI MSIDialog_WndProc( HWND hwnd, UINT msg,
2213                 WPARAM wParam, LPARAM lParam )
2214 {
2215     msi_dialog *dialog = (LPVOID) GetWindowLongPtrW( hwnd, GWLP_USERDATA );
2216
2217     TRACE("0x%04x\n", msg);
2218
2219     switch (msg)
2220     {
2221     case WM_CREATE:
2222         return msi_dialog_oncreate( hwnd, (LPCREATESTRUCTW)lParam );
2223
2224     case WM_COMMAND:
2225         return msi_dialog_oncommand( dialog, wParam, (HWND)lParam );
2226
2227     case WM_ACTIVATE:
2228         if( LOWORD(wParam) == WA_INACTIVE )
2229             dialog->hWndFocus = GetFocus();
2230         else
2231             msi_dialog_setfocus( dialog );
2232         return 0;
2233
2234     case WM_SETFOCUS:
2235         msi_dialog_setfocus( dialog );
2236         return 0;
2237
2238     /* bounce back to our subclassed static control */
2239     case WM_CTLCOLORSTATIC:
2240         return SendMessageW( (HWND) lParam, WM_CTLCOLORSTATIC, wParam, lParam );
2241
2242     case WM_DESTROY:
2243         dialog->hwnd = NULL;
2244         return 0;
2245     }
2246     return DefWindowProcW(hwnd, msg, wParam, lParam);
2247 }
2248
2249 static BOOL CALLBACK msi_radioground_child_enum( HWND hWnd, LPARAM lParam )
2250 {
2251     EnableWindow( hWnd, lParam );
2252     return TRUE;
2253 }
2254
2255 static LRESULT WINAPI MSIRadioGroup_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
2256 {
2257     WNDPROC oldproc = (WNDPROC) GetPropW(hWnd, szButtonData);
2258     LRESULT r;
2259
2260     TRACE("hWnd %p msg %04x wParam 0x%08x lParam 0x%08lx\n", hWnd, msg, wParam, lParam);
2261
2262     if (msg == WM_COMMAND) /* Forward notifications to dialog */
2263         SendMessageW(GetParent(hWnd), msg, wParam, lParam);
2264
2265     r = CallWindowProcW(oldproc, hWnd, msg, wParam, lParam);
2266
2267     /* make sure the radio buttons show as disabled if the parent is disabled */
2268     if (msg == WM_ENABLE)
2269         EnumChildWindows( hWnd, msi_radioground_child_enum, wParam );
2270
2271     return r;
2272 }
2273
2274 static LRESULT WINAPI MSIHiddenWindowProc( HWND hwnd, UINT msg,
2275                 WPARAM wParam, LPARAM lParam )
2276 {
2277     msi_dialog *dialog = (msi_dialog*) lParam;
2278
2279     TRACE("%d %p\n", msg, dialog);
2280
2281     switch (msg)
2282     {
2283     case WM_MSI_DIALOG_CREATE:
2284         return msi_dialog_run_message_loop( dialog );
2285     case WM_MSI_DIALOG_DESTROY:
2286         msi_dialog_destroy( dialog );
2287         return 0;
2288     }
2289     return DefWindowProcW( hwnd, msg, wParam, lParam );
2290 }
2291
2292 /* functions that interface to other modules within MSI */
2293
2294 msi_dialog *msi_dialog_create( MSIPACKAGE* package, LPCWSTR szDialogName,
2295                                 msi_dialog_event_handler event_handler )
2296 {
2297     MSIRECORD *rec = NULL;
2298     msi_dialog *dialog;
2299
2300     TRACE("%p %s\n", package, debugstr_w(szDialogName));
2301
2302     /* allocate the structure for the dialog to use */
2303     dialog = msi_alloc_zero( sizeof *dialog + sizeof(WCHAR)*strlenW(szDialogName) );
2304     if( !dialog )
2305         return NULL;
2306     strcpyW( dialog->name, szDialogName );
2307     msiobj_addref( &package->hdr );
2308     dialog->package = package;
2309     dialog->event_handler = event_handler;
2310     dialog->finished = 0;
2311     list_init( &dialog->controls );
2312
2313     /* verify that the dialog exists */
2314     rec = msi_get_dialog_record( dialog );
2315     if( !rec )
2316     {
2317         msiobj_release( &package->hdr );
2318         msi_free( dialog );
2319         return NULL;
2320     }
2321     dialog->attributes = MSI_RecordGetInteger( rec, 6 );
2322     dialog->control_default = strdupW( MSI_RecordGetString( rec, 9 ) );
2323     dialog->control_cancel = strdupW( MSI_RecordGetString( rec, 10 ) );
2324     msiobj_release( &rec->hdr );
2325
2326     return dialog;
2327 }
2328
2329 static void msi_process_pending_messages( HWND hdlg )
2330 {
2331     MSG msg;
2332
2333     while( PeekMessageW( &msg, 0, 0, 0, PM_REMOVE ) )
2334     {
2335         if( hdlg && IsDialogMessageW( hdlg, &msg ))
2336             continue;
2337         TranslateMessage( &msg );
2338         DispatchMessageW( &msg );
2339     }
2340 }
2341
2342 void msi_dialog_end_dialog( msi_dialog *dialog )
2343 {
2344     TRACE("%p\n", dialog);
2345     dialog->finished = 1;
2346     PostMessageW(dialog->hwnd, WM_NULL, 0, 0);
2347 }
2348
2349 void msi_dialog_check_messages( HANDLE handle )
2350 {
2351     DWORD r;
2352
2353     /* in threads other than the UI thread, block */
2354     if( uiThreadId != GetCurrentThreadId() )
2355     {
2356         if( handle )
2357             WaitForSingleObject( handle, INFINITE );
2358         return;
2359     }
2360
2361     /* there's two choices for the UI thread */
2362     while (1)
2363     {
2364         msi_process_pending_messages( NULL );
2365
2366         if( !handle )
2367             break;
2368
2369         /*
2370          * block here until somebody creates a new dialog or
2371          * the handle we're waiting on becomes ready
2372          */
2373         r = MsgWaitForMultipleObjects( 1, &handle, 0, INFINITE, QS_ALLINPUT );
2374         if( r == WAIT_OBJECT_0 )
2375             break;
2376     }
2377 }
2378
2379 UINT msi_dialog_run_message_loop( msi_dialog *dialog )
2380 {
2381     DWORD style;
2382     HWND hwnd;
2383
2384     if( uiThreadId != GetCurrentThreadId() )
2385         return SendMessageW( hMsiHiddenWindow, WM_MSI_DIALOG_CREATE, 0, (LPARAM) dialog );
2386
2387     /* create the dialog window, don't show it yet */
2388     style = WS_OVERLAPPED;
2389     if( dialog->attributes & msidbDialogAttributesVisible )
2390         style |= WS_VISIBLE;
2391
2392     hwnd = CreateWindowW( szMsiDialogClass, dialog->name, style,
2393                      CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
2394                      NULL, NULL, NULL, dialog );
2395     if( !hwnd )
2396     {
2397         ERR("Failed to create dialog %s\n", debugstr_w( dialog->name ));
2398         return ERROR_FUNCTION_FAILED;
2399     }
2400
2401     ShowWindow( hwnd, SW_SHOW );
2402     /* UpdateWindow( hwnd ); - and causes the transparent static controls not to paint */
2403
2404     if( dialog->attributes & msidbDialogAttributesModal )
2405     {
2406         while( !dialog->finished )
2407         {
2408             MsgWaitForMultipleObjects( 0, NULL, 0, INFINITE, QS_ALLEVENTS );
2409             msi_process_pending_messages( dialog->hwnd );
2410         }
2411     }
2412     else
2413         return ERROR_IO_PENDING;
2414
2415     return ERROR_SUCCESS;
2416 }
2417
2418 void msi_dialog_do_preview( msi_dialog *dialog )
2419 {
2420     TRACE("\n");
2421     dialog->attributes |= msidbDialogAttributesVisible;
2422     dialog->attributes &= ~msidbDialogAttributesModal;
2423     msi_dialog_run_message_loop( dialog );
2424 }
2425
2426 void msi_dialog_destroy( msi_dialog *dialog )
2427 {
2428     if( uiThreadId != GetCurrentThreadId() )
2429     {
2430         SendMessageW( hMsiHiddenWindow, WM_MSI_DIALOG_DESTROY, 0, (LPARAM) dialog );
2431         return;
2432     }
2433
2434     if( dialog->hwnd )
2435         ShowWindow( dialog->hwnd, SW_HIDE );
2436     
2437     if( dialog->hwnd )
2438         DestroyWindow( dialog->hwnd );
2439
2440     /* destroy the list of controls */
2441     while( !list_empty( &dialog->controls ) )
2442     {
2443         msi_control *t = LIST_ENTRY( list_head( &dialog->controls ),
2444                                      msi_control, entry );
2445         list_remove( &t->entry );
2446         /* leave dialog->hwnd - destroying parent destroys child windows */
2447         msi_free( t->property );
2448         msi_free( t->value );
2449         if( t->hBitmap )
2450             DeleteObject( t->hBitmap );
2451         if( t->hIcon )
2452             DestroyIcon( t->hIcon );
2453         msi_free( t->tabnext );
2454         msi_free( t );
2455         if (t->hDll)
2456             FreeLibrary( t->hDll );
2457     }
2458
2459     /* destroy the list of fonts */
2460     while( dialog->font_list )
2461     {
2462         msi_font *t = dialog->font_list;
2463         dialog->font_list = t->next;
2464         DeleteObject( t->hfont );
2465         msi_free( t );
2466     }
2467     msi_free( dialog->default_font );
2468
2469     msi_free( dialog->control_default );
2470     msi_free( dialog->control_cancel );
2471     msiobj_release( &dialog->package->hdr );
2472     dialog->package = NULL;
2473     msi_free( dialog );
2474 }
2475
2476 BOOL msi_dialog_register_class( void )
2477 {
2478     WNDCLASSW cls;
2479
2480     ZeroMemory( &cls, sizeof cls );
2481     cls.lpfnWndProc   = MSIDialog_WndProc;
2482     cls.hInstance     = NULL;
2483     cls.hIcon         = LoadIconW(0, (LPWSTR)IDI_APPLICATION);
2484     cls.hCursor       = LoadCursorW(0, (LPWSTR)IDC_ARROW);
2485     cls.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
2486     cls.lpszMenuName  = NULL;
2487     cls.lpszClassName = szMsiDialogClass;
2488
2489     if( !RegisterClassW( &cls ) )
2490         return FALSE;
2491
2492     cls.lpfnWndProc   = MSIHiddenWindowProc;
2493     cls.lpszClassName = szMsiHiddenWindow;
2494
2495     if( !RegisterClassW( &cls ) )
2496         return FALSE;
2497
2498     uiThreadId = GetCurrentThreadId();
2499
2500     hMsiHiddenWindow = CreateWindowW( szMsiHiddenWindow, NULL, WS_OVERLAPPED,
2501                                    0, 0, 100, 100, NULL, NULL, NULL, NULL );
2502     if( !hMsiHiddenWindow )
2503         return FALSE;
2504
2505     return TRUE;
2506 }
2507
2508 void msi_dialog_unregister_class( void )
2509 {
2510     DestroyWindow( hMsiHiddenWindow );
2511     hMsiHiddenWindow = NULL;
2512     UnregisterClassW( szMsiDialogClass, NULL );
2513     UnregisterClassW( szMsiHiddenWindow, NULL );
2514     uiThreadId = 0;
2515 }