mshtml: HTMLWindow_item code clean up.
[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 "wingdi.h"
30 #include "winuser.h"
31 #include "winnls.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 #include "winreg.h"
40 #include "shlwapi.h"
41 #include "msiserver.h"
42
43 #include "wine/debug.h"
44 #include "wine/unicode.h"
45
46 WINE_DEFAULT_DEBUG_CHANNEL(msi);
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 typedef void (*msi_update)( msi_dialog *, msi_control * );
54
55 struct msi_control_tag
56 {
57     struct list entry;
58     HWND hwnd;
59     msi_handler handler;
60     msi_update update;
61     LPWSTR property;
62     LPWSTR value;
63     HBITMAP hBitmap;
64     HICON hIcon;
65     LPWSTR tabnext;
66     LPWSTR type;
67     HMODULE hDll;
68     float progress_current;
69     float progress_max;
70     BOOL  progress_backwards;
71     DWORD attributes;
72     WCHAR name[1];
73 };
74
75 typedef struct msi_font_tag
76 {
77     struct list entry;
78     HFONT hfont;
79     COLORREF color;
80     WCHAR name[1];
81 } msi_font;
82
83 struct msi_dialog_tag
84 {
85     MSIPACKAGE *package;
86     msi_dialog *parent;
87     msi_dialog_event_handler event_handler;
88     BOOL finished;
89     INT scale;
90     DWORD attributes;
91     SIZE size;
92     HWND hwnd;
93     LPWSTR default_font;
94     struct list fonts;
95     struct list controls;
96     HWND hWndFocus;
97     LPWSTR control_default;
98     LPWSTR control_cancel;
99     WCHAR name[1];
100 };
101
102 typedef UINT (*msi_dialog_control_func)( msi_dialog *dialog, MSIRECORD *rec );
103 struct control_handler 
104 {
105     LPCWSTR control_type;
106     msi_dialog_control_func func;
107 };
108
109 typedef struct
110 {
111     msi_dialog* dialog;
112     msi_control *parent;
113     DWORD       attributes;
114     LPWSTR      propval;
115 } radio_button_group_descr;
116
117 static const WCHAR szMsiDialogClass[] = { 'M','s','i','D','i','a','l','o','g','C','l','o','s','e','C','l','a','s','s',0 };
118 static const WCHAR szMsiHiddenWindow[] = { 'M','s','i','H','i','d','d','e','n','W','i','n','d','o','w',0 };
119 static const WCHAR szStatic[] = { 'S','t','a','t','i','c',0 };
120 static const WCHAR szButton[] = { 'B','U','T','T','O','N', 0 };
121 static const WCHAR szButtonData[] = { 'M','S','I','D','A','T','A',0 };
122 static const WCHAR szProgress[] = { 'P','r','o','g','r','e','s','s',0 };
123 static const WCHAR szText[] = { 'T','e','x','t',0 };
124 static const WCHAR szPushButton[] = { 'P','u','s','h','B','u','t','t','o','n',0 };
125 static const WCHAR szLine[] = { 'L','i','n','e',0 };
126 static const WCHAR szBitmap[] = { 'B','i','t','m','a','p',0 };
127 static const WCHAR szCheckBox[] = { 'C','h','e','c','k','B','o','x',0 };
128 static const WCHAR szScrollableText[] = { 'S','c','r','o','l','l','a','b','l','e','T','e','x','t',0 };
129 static const WCHAR szComboBox[] = { 'C','o','m','b','o','B','o','x',0 };
130 static const WCHAR szEdit[] = { 'E','d','i','t',0 };
131 static const WCHAR szMaskedEdit[] = { 'M','a','s','k','e','d','E','d','i','t',0 };
132 static const WCHAR szPathEdit[] = { 'P','a','t','h','E','d','i','t',0 };
133 static const WCHAR szProgressBar[] = { 'P','r','o','g','r','e','s','s','B','a','r',0 };
134 static const WCHAR szSetProgress[] = { 'S','e','t','P','r','o','g','r','e','s','s',0 };
135 static const WCHAR szRadioButtonGroup[] = { 'R','a','d','i','o','B','u','t','t','o','n','G','r','o','u','p',0 };
136 static const WCHAR szIcon[] = { 'I','c','o','n',0 };
137 static const WCHAR szSelectionTree[] = { 'S','e','l','e','c','t','i','o','n','T','r','e','e',0 };
138 static const WCHAR szGroupBox[] = { 'G','r','o','u','p','B','o','x',0 };
139 static const WCHAR szListBox[] = { 'L','i','s','t','B','o','x',0 };
140 static const WCHAR szDirectoryCombo[] = { 'D','i','r','e','c','t','o','r','y','C','o','m','b','o',0 };
141 static const WCHAR szDirectoryList[] = { 'D','i','r','e','c','t','o','r','y','L','i','s','t',0 };
142 static const WCHAR szVolumeCostList[] = { 'V','o','l','u','m','e','C','o','s','t','L','i','s','t',0 };
143 static const WCHAR szVolumeSelectCombo[] = { 'V','o','l','u','m','e','S','e','l','e','c','t','C','o','m','b','o',0 };
144 static const WCHAR szSelectionDescription[] = {'S','e','l','e','c','t','i','o','n','D','e','s','c','r','i','p','t','i','o','n',0};
145 static const WCHAR szSelectionPath[] = {'S','e','l','e','c','t','i','o','n','P','a','t','h',0};
146 static const WCHAR szProperty[] = {'P','r','o','p','e','r','t','y',0};
147
148 /* dialog sequencing */
149
150 #define WM_MSI_DIALOG_CREATE  (WM_USER+0x100)
151 #define WM_MSI_DIALOG_DESTROY (WM_USER+0x101)
152
153 #define USER_INSTALLSTATE_ALL 0x1000
154
155 static DWORD uiThreadId;
156 static HWND hMsiHiddenWindow;
157
158 static LPWSTR msi_get_window_text( HWND hwnd )
159 {
160     UINT sz, r;
161     LPWSTR buf;
162
163     sz = 0x20;
164     buf = msi_alloc( sz*sizeof(WCHAR) );
165     while ( buf )
166     {
167         r = GetWindowTextW( hwnd, buf, sz );
168         if ( r < (sz - 1) )
169             break;
170         sz *= 2;
171         buf = msi_realloc( buf, sz*sizeof(WCHAR) );
172     }
173
174     return buf;
175 }
176
177 static INT msi_dialog_scale_unit( msi_dialog *dialog, INT val )
178 {
179     return MulDiv( val, dialog->scale, 12 );
180 }
181
182 static msi_control *msi_dialog_find_control( msi_dialog *dialog, LPCWSTR name )
183 {
184     msi_control *control;
185
186     if( !name )
187         return NULL;
188     if( !dialog->hwnd )
189         return NULL;
190     LIST_FOR_EACH_ENTRY( control, &dialog->controls, msi_control, entry )
191         if( !strcmpW( control->name, name ) ) /* FIXME: case sensitive? */
192             return control;
193     return NULL;
194 }
195
196 static msi_control *msi_dialog_find_control_by_type( msi_dialog *dialog, LPCWSTR type )
197 {
198     msi_control *control;
199
200     if( !type )
201         return NULL;
202     if( !dialog->hwnd )
203         return NULL;
204     LIST_FOR_EACH_ENTRY( control, &dialog->controls, msi_control, entry )
205         if( !strcmpW( control->type, type ) ) /* FIXME: case sensitive? */
206             return control;
207     return NULL;
208 }
209
210 static msi_control *msi_dialog_find_control_by_hwnd( msi_dialog *dialog, HWND hwnd )
211 {
212     msi_control *control;
213
214     if( !dialog->hwnd )
215         return NULL;
216     LIST_FOR_EACH_ENTRY( control, &dialog->controls, msi_control, entry )
217         if( hwnd == control->hwnd )
218             return control;
219     return NULL;
220 }
221
222 static LPWSTR msi_get_deformatted_field( MSIPACKAGE *package, MSIRECORD *rec, int field )
223 {
224     LPCWSTR str = MSI_RecordGetString( rec, field );
225     LPWSTR ret = NULL;
226
227     if (str)
228         deformat_string( package, str, &ret );
229     return ret;
230 }
231
232 static LPWSTR msi_dialog_dup_property( msi_dialog *dialog, LPCWSTR property, BOOL indirect )
233 {
234     LPWSTR prop = NULL;
235
236     if (!property)
237         return NULL;
238
239     if (indirect)
240         prop = msi_dup_property( dialog->package->db, property );
241
242     if (!prop)
243         prop = strdupW( property );
244
245     return prop;
246 }
247
248 msi_dialog *msi_dialog_get_parent( msi_dialog *dialog )
249 {
250     return dialog->parent;
251 }
252
253 LPWSTR msi_dialog_get_name( msi_dialog *dialog )
254 {
255     return dialog->name;
256 }
257
258 /*
259  * msi_dialog_get_style
260  *
261  * Extract the {\style} string from the front of the text to display and
262  * update the pointer.  Only the last style in a list is applied.
263  */
264 static LPWSTR msi_dialog_get_style( LPCWSTR p, LPCWSTR *rest )
265 {
266     LPWSTR ret;
267     LPCWSTR q, i, first;
268     DWORD len;
269
270     q = NULL;
271     *rest = p;
272     if( !p )
273         return NULL;
274
275     while ((first = strchrW( p, '{' )) && (q = strchrW( first + 1, '}' )))
276     {
277         p = first + 1;
278         if( *p != '\\' && *p != '&' )
279             return NULL;
280
281         /* little bit of sanity checking to stop us getting confused with RTF */
282         for( i=++p; i<q; i++ )
283             if( *i == '}' || *i == '\\' )
284                 return NULL;
285     }
286
287     if (!q)
288         return NULL;
289
290     *rest = ++q;
291     len = q - p;
292
293     ret = msi_alloc( len*sizeof(WCHAR) );
294     if( !ret )
295         return ret;
296     memcpy( ret, p, len*sizeof(WCHAR) );
297     ret[len-1] = 0;
298     return ret;
299 }
300
301 static UINT msi_dialog_add_font( MSIRECORD *rec, LPVOID param )
302 {
303     msi_dialog *dialog = param;
304     msi_font *font;
305     LPCWSTR face, name;
306     LOGFONTW lf;
307     INT style;
308     HDC hdc;
309
310     /* create a font and add it to the list */
311     name = MSI_RecordGetString( rec, 1 );
312     font = msi_alloc( sizeof *font + strlenW( name )*sizeof (WCHAR) );
313     strcpyW( font->name, name );
314     list_add_head( &dialog->fonts, &font->entry );
315
316     font->color = MSI_RecordGetInteger( rec, 4 );
317
318     memset( &lf, 0, sizeof lf );
319     face = MSI_RecordGetString( rec, 2 );
320     lf.lfHeight = MSI_RecordGetInteger( rec, 3 );
321     style = MSI_RecordGetInteger( rec, 5 );
322     if( style & msidbTextStyleStyleBitsBold )
323         lf.lfWeight = FW_BOLD;
324     if( style & msidbTextStyleStyleBitsItalic )
325         lf.lfItalic = TRUE;
326     if( style & msidbTextStyleStyleBitsUnderline )
327         lf.lfUnderline = TRUE;
328     if( style & msidbTextStyleStyleBitsStrike )
329         lf.lfStrikeOut = TRUE;
330     lstrcpynW( lf.lfFaceName, face, LF_FACESIZE );
331
332     /* adjust the height */
333     hdc = GetDC( dialog->hwnd );
334     if (hdc)
335     {
336         lf.lfHeight = -MulDiv(lf.lfHeight, GetDeviceCaps(hdc, LOGPIXELSY), 72);
337         ReleaseDC( dialog->hwnd, hdc );
338     }
339
340     font->hfont = CreateFontIndirectW( &lf );
341
342     TRACE("Adding font style %s\n", debugstr_w(font->name) );
343
344     return ERROR_SUCCESS;
345 }
346
347 static msi_font *msi_dialog_find_font( msi_dialog *dialog, LPCWSTR name )
348 {
349     msi_font *font = NULL;
350
351     LIST_FOR_EACH_ENTRY( font, &dialog->fonts, msi_font, entry )
352         if( !strcmpW( font->name, name ) )  /* FIXME: case sensitive? */
353             break;
354
355     return font;
356 }
357
358 static UINT msi_dialog_set_font( msi_dialog *dialog, HWND hwnd, LPCWSTR name )
359 {
360     msi_font *font;
361
362     font = msi_dialog_find_font( dialog, name );
363     if( font )
364         SendMessageW( hwnd, WM_SETFONT, (WPARAM) font->hfont, TRUE );
365     else
366         ERR("No font entry for %s\n", debugstr_w(name));
367     return ERROR_SUCCESS;
368 }
369
370 static UINT msi_dialog_build_font_list( msi_dialog *dialog )
371 {
372     static const WCHAR query[] = {
373         'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
374         '`','T','e','x','t','S','t','y','l','e','`',0};
375     MSIQUERY *view;
376     UINT r;
377
378     TRACE("dialog %p\n", dialog );
379
380     r = MSI_OpenQuery( dialog->package->db, &view, query );
381     if( r != ERROR_SUCCESS )
382         return r;
383
384     r = MSI_IterateRecords( view, NULL, msi_dialog_add_font, dialog );
385     msiobj_release( &view->hdr );
386     return r;
387 }
388
389 static void msi_destroy_control( msi_control *t )
390 {
391     list_remove( &t->entry );
392     /* leave dialog->hwnd - destroying parent destroys child windows */
393     msi_free( t->property );
394     msi_free( t->value );
395     if( t->hBitmap )
396         DeleteObject( t->hBitmap );
397     if( t->hIcon )
398         DestroyIcon( t->hIcon );
399     msi_free( t->tabnext );
400     msi_free( t->type );
401     if (t->hDll)
402         FreeLibrary( t->hDll );
403     msi_free( t );
404 }
405
406 static msi_control *msi_dialog_create_window( msi_dialog *dialog,
407                 MSIRECORD *rec, DWORD exstyle, LPCWSTR szCls, LPCWSTR name, LPCWSTR text,
408                 DWORD style, HWND parent )
409 {
410     DWORD x, y, width, height;
411     LPWSTR font = NULL, title_font = NULL;
412     LPCWSTR title = NULL;
413     msi_control *control;
414
415     style |= WS_CHILD;
416
417     control = msi_alloc( sizeof *control + strlenW(name)*sizeof(WCHAR) );
418     if (!control)
419         return NULL;
420
421     strcpyW( control->name, name );
422     list_add_tail( &dialog->controls, &control->entry );
423     control->handler = NULL;
424     control->update = NULL;
425     control->property = NULL;
426     control->value = NULL;
427     control->hBitmap = NULL;
428     control->hIcon = NULL;
429     control->hDll = NULL;
430     control->tabnext = strdupW( MSI_RecordGetString( rec, 11) );
431     control->type = strdupW( MSI_RecordGetString( rec, 3 ) );
432     control->progress_current = 0;
433     control->progress_max = 100;
434     control->progress_backwards = FALSE;
435
436     x = MSI_RecordGetInteger( rec, 4 );
437     y = MSI_RecordGetInteger( rec, 5 );
438     width = MSI_RecordGetInteger( rec, 6 );
439     height = MSI_RecordGetInteger( rec, 7 );
440
441     x = msi_dialog_scale_unit( dialog, x );
442     y = msi_dialog_scale_unit( dialog, y );
443     width = msi_dialog_scale_unit( dialog, width );
444     height = msi_dialog_scale_unit( dialog, height );
445
446     if( text )
447     {
448         deformat_string( dialog->package, text, &title_font );
449         font = msi_dialog_get_style( title_font, &title );
450     }
451
452     control->hwnd = CreateWindowExW( exstyle, szCls, title, style,
453                           x, y, width, height, parent, NULL, NULL, NULL );
454
455     TRACE("Dialog %s control %s hwnd %p\n",
456            debugstr_w(dialog->name), debugstr_w(text), control->hwnd );
457
458     msi_dialog_set_font( dialog, control->hwnd,
459                          font ? font : dialog->default_font );
460
461     msi_free( title_font );
462     msi_free( font );
463
464     return control;
465 }
466
467 static LPWSTR msi_dialog_get_uitext( msi_dialog *dialog, LPCWSTR key )
468 {
469     MSIRECORD *rec;
470     LPWSTR text;
471
472     static const WCHAR query[] = {
473         's','e','l','e','c','t',' ','*',' ',
474         'f','r','o','m',' ','`','U','I','T','e','x','t','`',' ',
475         'w','h','e','r','e',' ','`','K','e','y','`',' ','=',' ','\'','%','s','\'',0
476     };
477
478     rec = MSI_QueryGetRecord( dialog->package->db, query, key );
479     if (!rec) return NULL;
480     text = strdupW( MSI_RecordGetString( rec, 2 ) );
481     msiobj_release( &rec->hdr );
482     return text;
483 }
484
485 static MSIRECORD *msi_get_binary_record( MSIDATABASE *db, LPCWSTR name )
486 {
487     static const WCHAR query[] = {
488         's','e','l','e','c','t',' ','*',' ',
489         'f','r','o','m',' ','B','i','n','a','r','y',' ',
490         'w','h','e','r','e',' ',
491             '`','N','a','m','e','`',' ','=',' ','\'','%','s','\'',0
492     };
493
494     return MSI_QueryGetRecord( db, query, name );
495 }
496
497 static LPWSTR msi_create_tmp_path(void)
498 {
499     WCHAR tmp[MAX_PATH];
500     LPWSTR path = NULL;
501     DWORD len, r;
502
503     r = GetTempPathW( MAX_PATH, tmp );
504     if( !r )
505         return path;
506     len = lstrlenW( tmp ) + 20;
507     path = msi_alloc( len * sizeof (WCHAR) );
508     if( path )
509     {
510         r = GetTempFileNameW( tmp, szMsi, 0, path );
511         if (!r)
512         {
513             msi_free( path );
514             path = NULL;
515         }
516     }
517     return path;
518 }
519
520 static HANDLE msi_load_image( MSIDATABASE *db, LPCWSTR name, UINT type,
521                               UINT cx, UINT cy, UINT flags )
522 {
523     MSIRECORD *rec = NULL;
524     HANDLE himage = NULL;
525     LPWSTR tmp;
526     UINT r;
527
528     TRACE("%p %s %u %u %08x\n", db, debugstr_w(name), cx, cy, flags);
529
530     tmp = msi_create_tmp_path();
531     if( !tmp )
532         return himage;
533
534     rec = msi_get_binary_record( db, name );
535     if( rec )
536     {
537         r = MSI_RecordStreamToFile( rec, 2, tmp );
538         if( r == ERROR_SUCCESS )
539         {
540             himage = LoadImageW( 0, tmp, type, cx, cy, flags );
541         }
542         msiobj_release( &rec->hdr );
543     }
544     DeleteFileW( tmp );
545
546     msi_free( tmp );
547     return himage;
548 }
549
550 static HICON msi_load_icon( MSIDATABASE *db, LPCWSTR text, UINT attributes )
551 {
552     DWORD cx = 0, cy = 0, flags;
553
554     flags = LR_LOADFROMFILE | LR_DEFAULTSIZE;
555     if( attributes & msidbControlAttributesFixedSize )
556     {
557         flags &= ~LR_DEFAULTSIZE;
558         if( attributes & msidbControlAttributesIconSize16 )
559         {
560             cx += 16;
561             cy += 16;
562         }
563         if( attributes & msidbControlAttributesIconSize32 )
564         {
565             cx += 32;
566             cy += 32;
567         }
568         /* msidbControlAttributesIconSize48 handled by above logic */
569     }
570     return msi_load_image( db, text, IMAGE_ICON, cx, cy, flags );
571 }
572
573 static void msi_dialog_update_controls( msi_dialog *dialog, LPCWSTR property )
574 {
575     msi_control *control;
576
577     LIST_FOR_EACH_ENTRY( control, &dialog->controls, msi_control, entry )
578     {
579         if ( control->property && !strcmpW( control->property, property ) && control->update )
580             control->update( dialog, control );
581     }
582 }
583
584 static void msi_dialog_set_property( MSIPACKAGE *package, LPCWSTR property, LPCWSTR value )
585 {
586     UINT r = msi_set_property( package->db, property, value );
587     if (r == ERROR_SUCCESS && !strcmpW( property, szSourceDir ))
588         msi_reset_folders( package, TRUE );
589 }
590
591 static MSIFEATURE *msi_seltree_feature_from_item( HWND hwnd, HTREEITEM hItem )
592 {
593     TVITEMW tvi;
594
595     /* get the feature from the item */
596     memset( &tvi, 0, sizeof tvi );
597     tvi.hItem = hItem;
598     tvi.mask = TVIF_PARAM | TVIF_HANDLE;
599     SendMessageW( hwnd, TVM_GETITEMW, 0, (LPARAM)&tvi );
600     return (MSIFEATURE *)tvi.lParam;
601 }
602
603 struct msi_selection_tree_info
604 {
605     msi_dialog *dialog;
606     HWND hwnd;
607     WNDPROC oldproc;
608     HTREEITEM selected;
609 };
610
611 static MSIFEATURE *msi_seltree_get_selected_feature( msi_control *control )
612 {
613     struct msi_selection_tree_info *info = GetPropW( control->hwnd, szButtonData );
614     return msi_seltree_feature_from_item( control->hwnd, info->selected );
615 }
616
617 /* called from the Control Event subscription code */
618 void msi_dialog_handle_event( msi_dialog* dialog, LPCWSTR control, 
619                               LPCWSTR attribute, MSIRECORD *rec )
620 {
621     msi_control* ctrl;
622     LPCWSTR font_text, text = NULL;
623     LPWSTR font;
624
625     ctrl = msi_dialog_find_control( dialog, control );
626     if (!ctrl)
627         return;
628     if( !strcmpW( attribute, szText ) )
629     {
630         font_text = MSI_RecordGetString( rec , 1 );
631         font = msi_dialog_get_style( font_text, &text );
632         if (!text) text = szEmpty;
633         SetWindowTextW( ctrl->hwnd, text );
634         msi_free( font );
635         msi_dialog_check_messages( NULL );
636     }
637     else if( !strcmpW( attribute, szProgress ) )
638     {
639         DWORD func, val1, val2, units;
640
641         func = MSI_RecordGetInteger( rec, 1 );
642         val1 = MSI_RecordGetInteger( rec, 2 );
643         val2 = MSI_RecordGetInteger( rec, 3 );
644
645         TRACE("progress: func %u val1 %u val2 %u\n", func, val1, val2);
646
647         switch (func)
648         {
649         case 0: /* init */
650             SendMessageW( ctrl->hwnd, PBM_SETRANGE, 0, MAKELPARAM(0,100) );
651             units = val1 / 512;
652             if (val2)
653             {
654                 ctrl->progress_max = units ? units : 100;
655                 ctrl->progress_current = units;
656                 ctrl->progress_backwards = TRUE;
657                 SendMessageW( ctrl->hwnd, PBM_SETPOS, 100, 0 );
658             }
659             else
660             {
661                 ctrl->progress_max = units ? units : 100;
662                 ctrl->progress_current = 0;
663                 ctrl->progress_backwards = FALSE;
664                 SendMessageW( ctrl->hwnd, PBM_SETPOS, 0, 0 );
665             }
666             break;
667         case 1: /* FIXME: not sure what this is supposed to do */
668             break;
669         case 2: /* move */
670             units = val1 / 512;
671             if (ctrl->progress_backwards)
672             {
673                 if (units >= ctrl->progress_current) ctrl->progress_current -= units;
674                 else ctrl->progress_current = 0;
675             }
676             else
677             {
678                 if (ctrl->progress_current + units < ctrl->progress_max) ctrl->progress_current += units;
679                 else ctrl->progress_current = ctrl->progress_max;
680             }
681             SendMessageW( ctrl->hwnd, PBM_SETPOS, MulDiv(100, ctrl->progress_current, ctrl->progress_max), 0 );
682             break;
683         default:
684             FIXME("Unknown progress message %u\n", func);
685             break;
686         }
687     }
688     else if ( !strcmpW( attribute, szProperty ) )
689     {
690         MSIFEATURE *feature = msi_seltree_get_selected_feature( ctrl );
691         msi_dialog_set_property( dialog->package, ctrl->property, feature->Directory );
692     }
693     else if ( !strcmpW( attribute, szSelectionPath ) )
694     {
695         BOOL indirect = ctrl->attributes & msidbControlAttributesIndirect;
696         LPWSTR path = msi_dialog_dup_property( dialog, ctrl->property, indirect );
697         if (!path) return;
698         SetWindowTextW( ctrl->hwnd, path );
699         msi_free(path);
700     }
701     else
702     {
703         FIXME("Attribute %s not being set\n", debugstr_w(attribute));
704         return;
705     }
706 }
707
708 static void msi_dialog_map_events(msi_dialog* dialog, LPCWSTR control)
709 {
710     static const WCHAR Query[] = {
711         'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
712          '`','E','v','e','n','t','M','a','p','p','i','n','g','`',' ',
713         'W','H','E','R','E',' ',
714          '`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',' ',
715         'A','N','D',' ',
716          '`','C','o','n','t','r','o','l','_','`',' ','=',' ','\'','%','s','\'',0
717     };
718     MSIRECORD *row;
719     LPCWSTR event, attribute;
720
721     row = MSI_QueryGetRecord( dialog->package->db, Query, dialog->name, control );
722     if (!row)
723         return;
724
725     event = MSI_RecordGetString( row, 3 );
726     attribute = MSI_RecordGetString( row, 4 );
727     ControlEvent_SubscribeToEvent( dialog->package, dialog, event, control, attribute );
728     msiobj_release( &row->hdr );
729 }
730
731 /* everything except radio buttons */
732 static msi_control *msi_dialog_add_control( msi_dialog *dialog,
733                 MSIRECORD *rec, LPCWSTR szCls, DWORD style )
734 {
735     DWORD attributes;
736     LPCWSTR text, name;
737     DWORD exstyle = 0;
738
739     name = MSI_RecordGetString( rec, 2 );
740     attributes = MSI_RecordGetInteger( rec, 8 );
741     text = MSI_RecordGetString( rec, 10 );
742
743     TRACE("%s, %s, %08x, %s, %08x\n", debugstr_w(szCls), debugstr_w(name),
744           attributes, debugstr_w(text), style);
745
746     if( attributes & msidbControlAttributesVisible )
747         style |= WS_VISIBLE;
748     if( ~attributes & msidbControlAttributesEnabled )
749         style |= WS_DISABLED;
750     if( attributes & msidbControlAttributesSunken )
751         exstyle |= WS_EX_CLIENTEDGE;
752
753     msi_dialog_map_events(dialog, name);
754
755     return msi_dialog_create_window( dialog, rec, exstyle, szCls, name,
756                                      text, style, dialog->hwnd );
757 }
758
759 struct msi_text_info
760 {
761     msi_font *font;
762     WNDPROC oldproc;
763     DWORD attributes;
764 };
765
766 /*
767  * we don't erase our own background,
768  * so we have to make sure that the parent window redraws first
769  */
770 static void msi_text_on_settext( HWND hWnd )
771 {
772     HWND hParent;
773     RECT rc;
774
775     hParent = GetParent( hWnd );
776     GetWindowRect( hWnd, &rc );
777     MapWindowPoints( NULL, hParent, (LPPOINT) &rc, 2 );
778     InvalidateRect( hParent, &rc, TRUE );
779 }
780
781 static LRESULT WINAPI
782 MSIText_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
783 {
784     struct msi_text_info *info;
785     LRESULT r = 0;
786
787     TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam);
788
789     info = GetPropW(hWnd, szButtonData);
790
791     if( msg == WM_CTLCOLORSTATIC &&
792        ( info->attributes & msidbControlAttributesTransparent ) )
793     {
794         SetBkMode( (HDC)wParam, TRANSPARENT );
795         return (LRESULT) GetStockObject(NULL_BRUSH);
796     }
797
798     r = CallWindowProcW(info->oldproc, hWnd, msg, wParam, lParam);
799     if ( info->font )
800         SetTextColor( (HDC)wParam, info->font->color );
801
802     switch( msg )
803     {
804     case WM_SETTEXT:
805         msi_text_on_settext( hWnd );
806         break;
807     case WM_NCDESTROY:
808         msi_free( info );
809         RemovePropW( hWnd, szButtonData );
810         break;
811     }
812
813     return r;
814 }
815
816 static UINT msi_dialog_text_control( msi_dialog *dialog, MSIRECORD *rec )
817 {
818     msi_control *control;
819     struct msi_text_info *info;
820     LPCWSTR text, ptr, prop, control_name;
821     LPWSTR font_name;
822
823     TRACE("%p %p\n", dialog, rec);
824
825     control = msi_dialog_add_control( dialog, rec, szStatic, SS_LEFT | WS_GROUP );
826     if( !control )
827         return ERROR_FUNCTION_FAILED;
828
829     info = msi_alloc( sizeof *info );
830     if( !info )
831         return ERROR_SUCCESS;
832
833     control_name = MSI_RecordGetString( rec, 2 );
834     control->attributes = MSI_RecordGetInteger( rec, 8 );
835     prop = MSI_RecordGetString( rec, 9 );
836     control->property = msi_dialog_dup_property( dialog, prop, FALSE );
837
838     text = MSI_RecordGetString( rec, 10 );
839     font_name = msi_dialog_get_style( text, &ptr );
840     info->font = ( font_name ) ? msi_dialog_find_font( dialog, font_name ) : NULL;
841     msi_free( font_name );
842
843     info->attributes = MSI_RecordGetInteger( rec, 8 );
844     if( info->attributes & msidbControlAttributesTransparent )
845         SetWindowLongPtrW( control->hwnd, GWL_EXSTYLE, WS_EX_TRANSPARENT );
846
847     info->oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
848                                           (LONG_PTR)MSIText_WndProc );
849     SetPropW( control->hwnd, szButtonData, info );
850
851     ControlEvent_SubscribeToEvent( dialog->package, dialog,
852                                    szSelectionPath, control_name, szSelectionPath );
853
854     return ERROR_SUCCESS;
855 }
856
857 /* strip any leading text style label from text field */
858 static WCHAR *msi_get_binary_name( MSIPACKAGE *package, MSIRECORD *rec )
859 {
860     WCHAR *p, *text;
861
862     text = msi_get_deformatted_field( package, rec, 10 );
863     if (!text)
864         return NULL;
865
866     p = text;
867     while (*p && *p != '{') p++;
868     if (!*p++) return text;
869
870     while (*p && *p != '}') p++;
871     if (!*p++) return text;
872
873     p = strdupW( p );
874     msi_free( text );
875     return p;
876 }
877
878 static UINT msi_dialog_set_property_event( msi_dialog *dialog, LPCWSTR event, LPCWSTR arg )
879 {
880     static const WCHAR szNullArg[] = {'{','}',0};
881     LPWSTR p, prop, arg_fmt = NULL;
882     UINT len;
883
884     len = strlenW( event );
885     prop = msi_alloc( len * sizeof(WCHAR) );
886     strcpyW( prop, &event[1] );
887     p = strchrW( prop, ']' );
888     if (p && (p[1] == 0 || p[1] == ' '))
889     {
890         *p = 0;
891         if (strcmpW( szNullArg, arg ))
892             deformat_string( dialog->package, arg, &arg_fmt );
893         msi_dialog_set_property( dialog->package, prop, arg_fmt );
894         msi_dialog_update_controls( dialog, prop );
895         msi_free( arg_fmt );
896     }
897     else ERR("Badly formatted property string - what happens?\n");
898     msi_free( prop );
899     return ERROR_SUCCESS;
900 }
901
902 static UINT msi_dialog_send_event( msi_dialog *dialog, LPCWSTR event, LPCWSTR arg )
903 {
904     LPWSTR event_fmt = NULL, arg_fmt = NULL;
905
906     TRACE("Sending control event %s %s\n", debugstr_w(event), debugstr_w(arg));
907
908     deformat_string( dialog->package, event, &event_fmt );
909     deformat_string( dialog->package, arg, &arg_fmt );
910
911     dialog->event_handler( dialog->package, event_fmt, arg_fmt, dialog );
912
913     msi_free( event_fmt );
914     msi_free( arg_fmt );
915
916     return ERROR_SUCCESS;
917 }
918
919 static UINT msi_dialog_control_event( MSIRECORD *rec, LPVOID param )
920 {
921     msi_dialog *dialog = param;
922     LPCWSTR condition, event, arg;
923     UINT r;
924
925     condition = MSI_RecordGetString( rec, 5 );
926     r = MSI_EvaluateConditionW( dialog->package, condition );
927     if (r == MSICONDITION_TRUE || r == MSICONDITION_NONE)
928     {
929         event = MSI_RecordGetString( rec, 3 );
930         arg = MSI_RecordGetString( rec, 4 );
931         if (event[0] == '[')
932             msi_dialog_set_property_event( dialog, event, arg );
933         else
934             msi_dialog_send_event( dialog, event, arg );
935     }
936     return ERROR_SUCCESS;
937 }
938
939 static UINT msi_dialog_button_handler( msi_dialog *dialog, msi_control *control, WPARAM param )
940 {
941     static const WCHAR query[] = {
942         'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
943         'C','o','n','t','r','o','l','E','v','e','n','t',' ','W','H','E','R','E',' ',
944         '`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',' ','A','N','D',' ',
945         '`','C','o','n','t','r','o','l','_','`',' ','=',' ','\'','%','s','\'',' ',
946         'O','R','D','E','R',' ','B','Y',' ','`','O','r','d','e','r','i','n','g','`',0};
947     MSIQUERY *view;
948     UINT r;
949
950     if (HIWORD(param) != BN_CLICKED)
951         return ERROR_SUCCESS;
952
953     r = MSI_OpenQuery( dialog->package->db, &view, query, dialog->name, control->name );
954     if (r != ERROR_SUCCESS)
955     {
956         ERR("query failed\n");
957         return ERROR_SUCCESS;
958     }
959     r = MSI_IterateRecords( view, 0, msi_dialog_control_event, dialog );
960     msiobj_release( &view->hdr );
961     return r;
962 }
963
964 static UINT msi_dialog_button_control( msi_dialog *dialog, MSIRECORD *rec )
965 {
966     msi_control *control;
967     UINT attributes, style;
968
969     TRACE("%p %p\n", dialog, rec);
970
971     style = WS_TABSTOP;
972     attributes = MSI_RecordGetInteger( rec, 8 );
973     if( attributes & msidbControlAttributesIcon )
974         style |= BS_ICON;
975
976     control = msi_dialog_add_control( dialog, rec, szButton, style );
977     if( !control )
978         return ERROR_FUNCTION_FAILED;
979
980     control->handler = msi_dialog_button_handler;
981
982     if (attributes & msidbControlAttributesIcon)
983     {
984         /* set the icon */
985         LPWSTR name = msi_get_binary_name( dialog->package, rec );
986         control->hIcon = msi_load_icon( dialog->package->db, name, attributes );
987         if (control->hIcon)
988         {
989             SendMessageW( control->hwnd, BM_SETIMAGE, IMAGE_ICON, (LPARAM) control->hIcon );
990         }
991         else
992             ERR("Failed to load icon %s\n", debugstr_w(name));
993         msi_free( name );
994     }
995
996     return ERROR_SUCCESS;
997 }
998
999 static LPWSTR msi_get_checkbox_value( msi_dialog *dialog, LPCWSTR prop )
1000 {
1001     static const WCHAR query[] = {
1002         'S','E','L','E','C','T',' ','*',' ',
1003         'F','R','O','M',' ','`','C','h','e','c','k','B','o','x','`',' ',
1004         'W','H','E','R','E',' ',
1005         '`','P','r','o','p','e','r','t','y','`',' ','=',' ',
1006         '\'','%','s','\'',0
1007     };
1008     MSIRECORD *rec = NULL;
1009     LPWSTR ret = NULL;
1010
1011     /* find if there is a value associated with the checkbox */
1012     rec = MSI_QueryGetRecord( dialog->package->db, query, prop );
1013     if (!rec)
1014         return ret;
1015
1016     ret = msi_get_deformatted_field( dialog->package, rec, 2 );
1017     if( ret && !ret[0] )
1018     {
1019         msi_free( ret );
1020         ret = NULL;
1021     }
1022     msiobj_release( &rec->hdr );
1023     if (ret)
1024         return ret;
1025
1026     ret = msi_dup_property( dialog->package->db, prop );
1027     if( ret && !ret[0] )
1028     {
1029         msi_free( ret );
1030         ret = NULL;
1031     }
1032
1033     return ret;
1034 }
1035
1036 static UINT msi_dialog_get_checkbox_state( msi_dialog *dialog, msi_control *control )
1037 {
1038     WCHAR state[2] = {0};
1039     DWORD sz = 2;
1040
1041     msi_get_property( dialog->package->db, control->property, state, &sz );
1042     return state[0] ? 1 : 0;
1043 }
1044
1045 static void msi_dialog_set_checkbox_state( msi_dialog *dialog, msi_control *control, UINT state )
1046 {
1047     static const WCHAR szState[] = {'1',0};
1048     LPCWSTR val;
1049
1050     /* if uncheck then the property is set to NULL */
1051     if (!state)
1052     {
1053         msi_dialog_set_property( dialog->package, control->property, NULL );
1054         return;
1055     }
1056
1057     /* check for a custom state */
1058     if (control->value && control->value[0])
1059         val = control->value;
1060     else
1061         val = szState;
1062
1063     msi_dialog_set_property( dialog->package, control->property, val );
1064 }
1065
1066 static void msi_dialog_checkbox_sync_state( msi_dialog *dialog, msi_control *control )
1067 {
1068     UINT state = msi_dialog_get_checkbox_state( dialog, control );
1069     SendMessageW( control->hwnd, BM_SETCHECK, state ? BST_CHECKED : BST_UNCHECKED, 0 );
1070 }
1071
1072 static UINT msi_dialog_checkbox_handler( msi_dialog *dialog, msi_control *control, WPARAM param )
1073 {
1074     UINT state;
1075
1076     if (HIWORD(param) != BN_CLICKED)
1077         return ERROR_SUCCESS;
1078
1079     TRACE("clicked checkbox %s, set %s\n", debugstr_w(control->name), debugstr_w(control->property));
1080
1081     state = msi_dialog_get_checkbox_state( dialog, control );
1082     state = state ? 0 : 1;
1083     msi_dialog_set_checkbox_state( dialog, control, state );
1084     msi_dialog_checkbox_sync_state( dialog, control );
1085
1086     return msi_dialog_button_handler( dialog, control, param );
1087 }
1088
1089 static UINT msi_dialog_checkbox_control( msi_dialog *dialog, MSIRECORD *rec )
1090 {
1091     msi_control *control;
1092     LPCWSTR prop;
1093
1094     TRACE("%p %p\n", dialog, rec);
1095
1096     control = msi_dialog_add_control( dialog, rec, szButton, BS_CHECKBOX | BS_MULTILINE | WS_TABSTOP );
1097     control->handler = msi_dialog_checkbox_handler;
1098     control->update = msi_dialog_checkbox_sync_state;
1099     prop = MSI_RecordGetString( rec, 9 );
1100     if (prop)
1101     {
1102         control->property = strdupW( prop );
1103         control->value = msi_get_checkbox_value( dialog, prop );
1104         TRACE("control %s value %s\n", debugstr_w(control->property), debugstr_w(control->value));
1105     }
1106     msi_dialog_checkbox_sync_state( dialog, control );
1107     return ERROR_SUCCESS;
1108 }
1109
1110 static UINT msi_dialog_line_control( msi_dialog *dialog, MSIRECORD *rec )
1111 {
1112     DWORD attributes;
1113     LPCWSTR name;
1114     DWORD style, exstyle = 0;
1115     DWORD x, y, width, height;
1116     msi_control *control;
1117
1118     TRACE("%p %p\n", dialog, rec);
1119
1120     style = WS_CHILD | SS_ETCHEDHORZ | SS_SUNKEN;
1121
1122     name = MSI_RecordGetString( rec, 2 );
1123     attributes = MSI_RecordGetInteger( rec, 8 );
1124
1125     if( attributes & msidbControlAttributesVisible )
1126         style |= WS_VISIBLE;
1127     if( ~attributes & msidbControlAttributesEnabled )
1128         style |= WS_DISABLED;
1129     if( attributes & msidbControlAttributesSunken )
1130         exstyle |= WS_EX_CLIENTEDGE;
1131
1132     msi_dialog_map_events(dialog, name);
1133
1134     control = msi_alloc( sizeof(*control) + strlenW(name) * sizeof(WCHAR) );
1135     if (!control)
1136         return ERROR_OUTOFMEMORY;
1137
1138     strcpyW( control->name, name );
1139     list_add_head( &dialog->controls, &control->entry );
1140     control->handler = NULL;
1141     control->property = NULL;
1142     control->value = NULL;
1143     control->hBitmap = NULL;
1144     control->hIcon = NULL;
1145     control->hDll = NULL;
1146     control->tabnext = strdupW( MSI_RecordGetString( rec, 11) );
1147     control->type = strdupW( MSI_RecordGetString( rec, 3 ) );
1148     control->progress_current = 0;
1149     control->progress_max = 100;
1150     control->progress_backwards = FALSE;
1151
1152     x = MSI_RecordGetInteger( rec, 4 );
1153     y = MSI_RecordGetInteger( rec, 5 );
1154     width = MSI_RecordGetInteger( rec, 6 );
1155
1156     x = msi_dialog_scale_unit( dialog, x );
1157     y = msi_dialog_scale_unit( dialog, y );
1158     width = msi_dialog_scale_unit( dialog, width );
1159     height = 2; /* line is exactly 2 units in height */
1160
1161     control->hwnd = CreateWindowExW( exstyle, szStatic, NULL, style,
1162                           x, y, width, height, dialog->hwnd, NULL, NULL, NULL );
1163
1164     TRACE("Dialog %s control %s hwnd %p\n",
1165            debugstr_w(dialog->name), debugstr_w(name), control->hwnd );
1166
1167     return ERROR_SUCCESS;
1168 }
1169
1170 /******************** Scroll Text ********************************************/
1171
1172 struct msi_scrolltext_info
1173 {
1174     msi_dialog *dialog;
1175     msi_control *control;
1176     WNDPROC oldproc;
1177 };
1178
1179 static LRESULT WINAPI
1180 MSIScrollText_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
1181 {
1182     struct msi_scrolltext_info *info;
1183     HRESULT r;
1184
1185     TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam);
1186
1187     info = GetPropW( hWnd, szButtonData );
1188
1189     r = CallWindowProcW( info->oldproc, hWnd, msg, wParam, lParam );
1190
1191     switch( msg )
1192     {
1193     case WM_GETDLGCODE:
1194         return DLGC_WANTARROWS;
1195     case WM_NCDESTROY:
1196         msi_free( info );
1197         RemovePropW( hWnd, szButtonData );
1198         break;
1199     case WM_PAINT:
1200         /* native MSI sets a wait cursor here */
1201         msi_dialog_button_handler( info->dialog, info->control, BN_CLICKED );
1202         break;
1203     }
1204     return r;
1205 }
1206
1207 struct msi_streamin_info
1208 {
1209     LPSTR string;
1210     DWORD offset;
1211     DWORD length;
1212 };
1213
1214 static DWORD CALLBACK
1215 msi_richedit_stream_in( DWORD_PTR arg, LPBYTE buffer, LONG count, LONG *pcb )
1216 {
1217     struct msi_streamin_info *info = (struct msi_streamin_info*) arg;
1218
1219     if( (count + info->offset) > info->length )
1220         count = info->length - info->offset;
1221     memcpy( buffer, &info->string[ info->offset ], count );
1222     *pcb = count;
1223     info->offset += count;
1224
1225     TRACE("%d/%d\n", info->offset, info->length);
1226
1227     return 0;
1228 }
1229
1230 static void msi_scrolltext_add_text( msi_control *control, LPCWSTR text )
1231 {
1232     struct msi_streamin_info info;
1233     EDITSTREAM es;
1234
1235     info.string = strdupWtoA( text );
1236     info.offset = 0;
1237     info.length = lstrlenA( info.string ) + 1;
1238
1239     es.dwCookie = (DWORD_PTR) &info;
1240     es.dwError = 0;
1241     es.pfnCallback = msi_richedit_stream_in;
1242
1243     SendMessageW( control->hwnd, EM_STREAMIN, SF_RTF, (LPARAM) &es );
1244
1245     msi_free( info.string );
1246 }
1247
1248 static UINT msi_dialog_scrolltext_control( msi_dialog *dialog, MSIRECORD *rec )
1249 {
1250     static const WCHAR szRichEdit20W[] = {'R','i','c','h','E','d','i','t','2','0','W',0};
1251     struct msi_scrolltext_info *info;
1252     msi_control *control;
1253     HMODULE hRichedit;
1254     LPCWSTR text;
1255     DWORD style;
1256
1257     info = msi_alloc( sizeof *info );
1258     if (!info)
1259         return ERROR_FUNCTION_FAILED;
1260
1261     hRichedit = LoadLibraryA("riched20");
1262
1263     style = WS_BORDER | ES_MULTILINE | WS_VSCROLL |
1264             ES_READONLY | ES_AUTOVSCROLL | WS_TABSTOP;
1265     control = msi_dialog_add_control( dialog, rec, szRichEdit20W, style );
1266     if (!control)
1267     {
1268         FreeLibrary( hRichedit );
1269         msi_free( info );
1270         return ERROR_FUNCTION_FAILED;
1271     }
1272
1273     control->hDll = hRichedit;
1274
1275     info->dialog = dialog;
1276     info->control = control;
1277
1278     /* subclass the static control */
1279     info->oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
1280                                           (LONG_PTR)MSIScrollText_WndProc );
1281     SetPropW( control->hwnd, szButtonData, info );
1282
1283     /* add the text into the richedit */
1284     text = MSI_RecordGetString( rec, 10 );
1285     if (text)
1286         msi_scrolltext_add_text( control, text );
1287
1288     return ERROR_SUCCESS;
1289 }
1290
1291 static HBITMAP msi_load_picture( MSIDATABASE *db, LPCWSTR name,
1292                                  INT cx, INT cy, DWORD flags )
1293 {
1294     HBITMAP hOleBitmap = 0, hBitmap = 0, hOldSrcBitmap, hOldDestBitmap;
1295     MSIRECORD *rec = NULL;
1296     IStream *stm = NULL;
1297     IPicture *pic = NULL;
1298     HDC srcdc, destdc;
1299     BITMAP bm;
1300     UINT r;
1301
1302     rec = msi_get_binary_record( db, name );
1303     if( !rec )
1304         goto end;
1305
1306     r = MSI_RecordGetIStream( rec, 2, &stm );
1307     msiobj_release( &rec->hdr );
1308     if( r != ERROR_SUCCESS )
1309         goto end;
1310
1311     r = OleLoadPicture( stm, 0, TRUE, &IID_IPicture, (LPVOID*) &pic );
1312     IStream_Release( stm );
1313     if( FAILED( r ) )
1314     {
1315         ERR("failed to load picture\n");
1316         goto end;
1317     }
1318
1319     r = IPicture_get_Handle( pic, (OLE_HANDLE*) &hOleBitmap );
1320     if( FAILED( r ) )
1321     {
1322         ERR("failed to get bitmap handle\n");
1323         goto end;
1324     }
1325  
1326     /* make the bitmap the desired size */
1327     r = GetObjectW( hOleBitmap, sizeof bm, &bm );
1328     if (r != sizeof bm )
1329     {
1330         ERR("failed to get bitmap size\n");
1331         goto end;
1332     }
1333
1334     if (flags & LR_DEFAULTSIZE)
1335     {
1336         cx = bm.bmWidth;
1337         cy = bm.bmHeight;
1338     }
1339
1340     srcdc = CreateCompatibleDC( NULL );
1341     hOldSrcBitmap = SelectObject( srcdc, hOleBitmap );
1342     destdc = CreateCompatibleDC( NULL );
1343     hBitmap = CreateCompatibleBitmap( srcdc, cx, cy );
1344     hOldDestBitmap = SelectObject( destdc, hBitmap );
1345     StretchBlt( destdc, 0, 0, cx, cy,
1346                 srcdc, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
1347     SelectObject( srcdc, hOldSrcBitmap );
1348     SelectObject( destdc, hOldDestBitmap );
1349     DeleteDC( srcdc );
1350     DeleteDC( destdc );
1351
1352 end:
1353     if ( pic )
1354         IPicture_Release( pic );
1355     return hBitmap;
1356 }
1357
1358 static UINT msi_dialog_bitmap_control( msi_dialog *dialog, MSIRECORD *rec )
1359 {
1360     UINT cx, cy, flags, style, attributes;
1361     msi_control *control;
1362     LPWSTR name;
1363
1364     flags = LR_LOADFROMFILE;
1365     style = SS_BITMAP | SS_LEFT | WS_GROUP;
1366
1367     attributes = MSI_RecordGetInteger( rec, 8 );
1368     if( attributes & msidbControlAttributesFixedSize )
1369     {
1370         flags |= LR_DEFAULTSIZE;
1371         style |= SS_CENTERIMAGE;
1372     }
1373
1374     control = msi_dialog_add_control( dialog, rec, szStatic, style );
1375     cx = MSI_RecordGetInteger( rec, 6 );
1376     cy = MSI_RecordGetInteger( rec, 7 );
1377     cx = msi_dialog_scale_unit( dialog, cx );
1378     cy = msi_dialog_scale_unit( dialog, cy );
1379
1380     name = msi_get_binary_name( dialog->package, rec );
1381     control->hBitmap = msi_load_picture( dialog->package->db, name, cx, cy, flags );
1382     if( control->hBitmap )
1383         SendMessageW( control->hwnd, STM_SETIMAGE,
1384                       IMAGE_BITMAP, (LPARAM) control->hBitmap );
1385     else
1386         ERR("Failed to load bitmap %s\n", debugstr_w(name));
1387
1388     msi_free( name );
1389     
1390     return ERROR_SUCCESS;
1391 }
1392
1393 static UINT msi_dialog_icon_control( msi_dialog *dialog, MSIRECORD *rec )
1394 {
1395     msi_control *control;
1396     DWORD attributes;
1397     LPWSTR name;
1398
1399     TRACE("\n");
1400
1401     control = msi_dialog_add_control( dialog, rec, szStatic,
1402                             SS_ICON | SS_CENTERIMAGE | WS_GROUP );
1403             
1404     attributes = MSI_RecordGetInteger( rec, 8 );
1405     name = msi_get_binary_name( dialog->package, rec );
1406     control->hIcon = msi_load_icon( dialog->package->db, name, attributes );
1407     if( control->hIcon )
1408         SendMessageW( control->hwnd, STM_SETICON, (WPARAM) control->hIcon, 0 );
1409     else
1410         ERR("Failed to load bitmap %s\n", debugstr_w(name));
1411     msi_free( name );
1412     return ERROR_SUCCESS;
1413 }
1414
1415 /******************** Combo Box ***************************************/
1416
1417 struct msi_combobox_info
1418 {
1419     msi_dialog *dialog;
1420     HWND hwnd;
1421     WNDPROC oldproc;
1422     DWORD num_items;
1423     DWORD addpos_items;
1424     LPWSTR *items;
1425 };
1426
1427 static LRESULT WINAPI MSIComboBox_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
1428 {
1429     struct msi_combobox_info *info;
1430     LRESULT r;
1431     DWORD j;
1432
1433     TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam);
1434
1435     info = GetPropW( hWnd, szButtonData );
1436     if (!info)
1437         return 0;
1438
1439     r = CallWindowProcW( info->oldproc, hWnd, msg, wParam, lParam );
1440
1441     switch (msg)
1442     {
1443     case WM_NCDESTROY:
1444         for (j = 0; j < info->num_items; j++)
1445             msi_free( info->items[j] );
1446         msi_free( info->items );
1447         msi_free( info );
1448         RemovePropW( hWnd, szButtonData );
1449         break;
1450     }
1451
1452     return r;
1453 }
1454
1455 static UINT msi_combobox_add_item( MSIRECORD *rec, LPVOID param )
1456 {
1457     struct msi_combobox_info *info = param;
1458     LPCWSTR value, text;
1459     int pos;
1460
1461     value = MSI_RecordGetString( rec, 3 );
1462     text = MSI_RecordGetString( rec, 4 );
1463
1464     info->items[info->addpos_items] = strdupW( value );
1465
1466     pos = SendMessageW( info->hwnd, CB_ADDSTRING, 0, (LPARAM)text );
1467     SendMessageW( info->hwnd, CB_SETITEMDATA, pos, (LPARAM)info->items[info->addpos_items] );
1468     info->addpos_items++;
1469
1470     return ERROR_SUCCESS;
1471 }
1472
1473 static UINT msi_combobox_add_items( struct msi_combobox_info *info, LPCWSTR property )
1474 {
1475     static const WCHAR query[] = {
1476         'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
1477         '`','C','o','m','b','o','B','o','x','`',' ','W','H','E','R','E',' ',
1478         '`','P','r','o','p','e','r','t','y','`',' ','=',' ','\'','%','s','\'',' ',
1479         'O','R','D','E','R',' ','B','Y',' ','`','O','r','d','e','r','`',0};
1480     MSIQUERY *view;
1481     DWORD count;
1482     UINT r;
1483
1484     r = MSI_OpenQuery( info->dialog->package->db, &view, query, property );
1485     if (r != ERROR_SUCCESS)
1486         return r;
1487
1488     /* just get the number of records */
1489     count = 0;
1490     r = MSI_IterateRecords( view, &count, NULL, NULL );
1491     if (r != ERROR_SUCCESS)
1492     {
1493         msiobj_release( &view->hdr );
1494         return r;
1495     }
1496     info->num_items = count;
1497     info->items = msi_alloc( sizeof(*info->items) * count );
1498
1499     r = MSI_IterateRecords( view, NULL, msi_combobox_add_item, info );
1500     msiobj_release( &view->hdr );
1501     return r;
1502 }
1503
1504 static UINT msi_dialog_set_control_condition( MSIRECORD *rec, LPVOID param )
1505 {
1506     static const WCHAR szHide[] = {'H','i','d','e',0};
1507     static const WCHAR szShow[] = {'S','h','o','w',0};
1508     static const WCHAR szDisable[] = {'D','i','s','a','b','l','e',0};
1509     static const WCHAR szEnable[] = {'E','n','a','b','l','e',0};
1510     static const WCHAR szDefault[] = {'D','e','f','a','u','l','t',0};
1511     msi_dialog *dialog = param;
1512     msi_control *control;
1513     LPCWSTR name, action, condition;
1514     UINT r;
1515
1516     name = MSI_RecordGetString( rec, 2 );
1517     action = MSI_RecordGetString( rec, 3 );
1518     condition = MSI_RecordGetString( rec, 4 );
1519     r = MSI_EvaluateConditionW( dialog->package, condition );
1520     control = msi_dialog_find_control( dialog, name );
1521     if (r == MSICONDITION_TRUE && control)
1522     {
1523         TRACE("%s control %s\n", debugstr_w(action), debugstr_w(name));
1524
1525         /* FIXME: case sensitive? */
1526         if (!strcmpW( action, szHide ))
1527             ShowWindow(control->hwnd, SW_HIDE);
1528         else if (!strcmpW( action, szShow ))
1529             ShowWindow(control->hwnd, SW_SHOW);
1530         else if (!strcmpW( action, szDisable ))
1531             EnableWindow(control->hwnd, FALSE);
1532         else if (!strcmpW( action, szEnable ))
1533             EnableWindow(control->hwnd, TRUE);
1534         else if (!strcmpW( action, szDefault ))
1535             SetFocus(control->hwnd);
1536         else
1537             FIXME("Unhandled action %s\n", debugstr_w(action));
1538     }
1539     return ERROR_SUCCESS;
1540 }
1541
1542 static UINT msi_dialog_evaluate_control_conditions( msi_dialog *dialog )
1543 {
1544     static const WCHAR query[] = {
1545         'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
1546         'C','o','n','t','r','o','l','C','o','n','d','i','t','i','o','n',' ',
1547         'W','H','E','R','E',' ','`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',0};
1548     UINT r;
1549     MSIQUERY *view;
1550     MSIPACKAGE *package = dialog->package;
1551
1552     TRACE("%p %s\n", dialog, debugstr_w(dialog->name));
1553
1554     /* query the Control table for all the elements of the control */
1555     r = MSI_OpenQuery( package->db, &view, query, dialog->name );
1556     if (r != ERROR_SUCCESS)
1557         return ERROR_SUCCESS;
1558
1559     r = MSI_IterateRecords( view, 0, msi_dialog_set_control_condition, dialog );
1560     msiobj_release( &view->hdr );
1561     return r;
1562 }
1563
1564 static UINT msi_dialog_combobox_handler( msi_dialog *dialog, msi_control *control, WPARAM param )
1565 {
1566     struct msi_combobox_info *info;
1567     int index;
1568     LPWSTR value;
1569
1570     if (HIWORD(param) != CBN_SELCHANGE && HIWORD(param) != CBN_EDITCHANGE)
1571         return ERROR_SUCCESS;
1572
1573     info = GetPropW( control->hwnd, szButtonData );
1574     index = SendMessageW( control->hwnd, CB_GETCURSEL, 0, 0 );
1575     if (index == CB_ERR)
1576         value = msi_get_window_text( control->hwnd );
1577     else
1578         value = (LPWSTR) SendMessageW( control->hwnd, CB_GETITEMDATA, index, 0 );
1579
1580     msi_dialog_set_property( info->dialog->package, control->property, value );
1581     msi_dialog_evaluate_control_conditions( info->dialog );
1582
1583     if (index == CB_ERR)
1584         msi_free( value );
1585
1586     return ERROR_SUCCESS;
1587 }
1588
1589 static void msi_dialog_combobox_update( msi_dialog *dialog, msi_control *control )
1590 {
1591     struct msi_combobox_info *info;
1592     LPWSTR value, tmp;
1593     DWORD j;
1594
1595     info = GetPropW( control->hwnd, szButtonData );
1596
1597     value = msi_dup_property( dialog->package->db, control->property );
1598     if (!value)
1599     {
1600         SendMessageW( control->hwnd, CB_SETCURSEL, -1, 0 );
1601         return;
1602     }
1603
1604     for (j = 0; j < info->num_items; j++)
1605     {
1606         tmp = (LPWSTR) SendMessageW( control->hwnd, CB_GETITEMDATA, j, 0 );
1607         if (!strcmpW( value, tmp ))
1608             break;
1609     }
1610
1611     if (j < info->num_items)
1612     {
1613         SendMessageW( control->hwnd, CB_SETCURSEL, j, 0 );
1614     }
1615     else
1616     {
1617         SendMessageW( control->hwnd, CB_SETCURSEL, -1, 0 );
1618         SetWindowTextW( control->hwnd, value );
1619     }
1620
1621     msi_free(value);
1622 }
1623
1624 static UINT msi_dialog_combo_control( msi_dialog *dialog, MSIRECORD *rec )
1625 {
1626     struct msi_combobox_info *info;
1627     msi_control *control;
1628     DWORD attributes, style;
1629     LPCWSTR prop;
1630
1631     info = msi_alloc( sizeof *info );
1632     if (!info)
1633         return ERROR_FUNCTION_FAILED;
1634
1635     style = CBS_AUTOHSCROLL | WS_TABSTOP | WS_GROUP | WS_CHILD;
1636     attributes = MSI_RecordGetInteger( rec, 8 );
1637     if ( ~attributes & msidbControlAttributesSorted)
1638         style |= CBS_SORT;
1639     if ( attributes & msidbControlAttributesComboList)
1640         style |= CBS_DROPDOWNLIST;
1641     else
1642         style |= CBS_DROPDOWN;
1643
1644     control = msi_dialog_add_control( dialog, rec, WC_COMBOBOXW, style );
1645     if (!control)
1646     {
1647         msi_free( info );
1648         return ERROR_FUNCTION_FAILED;
1649     }
1650
1651     control->handler = msi_dialog_combobox_handler;
1652     control->update = msi_dialog_combobox_update;
1653
1654     prop = MSI_RecordGetString( rec, 9 );
1655     control->property = msi_dialog_dup_property( dialog, prop, FALSE );
1656
1657     /* subclass */
1658     info->dialog = dialog;
1659     info->hwnd = control->hwnd;
1660     info->items = NULL;
1661     info->addpos_items = 0;
1662     info->oldproc = (WNDPROC)SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
1663                                                 (LONG_PTR)MSIComboBox_WndProc );
1664     SetPropW( control->hwnd, szButtonData, info );
1665
1666     if (control->property)
1667         msi_combobox_add_items( info, control->property );
1668
1669     msi_dialog_combobox_update( dialog, control );
1670
1671     return ERROR_SUCCESS;
1672 }
1673
1674 static UINT msi_dialog_edit_handler( msi_dialog *dialog, msi_control *control, WPARAM param )
1675 {
1676     LPWSTR buf;
1677
1678     if (HIWORD(param) != EN_CHANGE)
1679         return ERROR_SUCCESS;
1680
1681     TRACE("edit %s contents changed, set %s\n", debugstr_w(control->name), debugstr_w(control->property));
1682
1683     buf = msi_get_window_text( control->hwnd );
1684     msi_dialog_set_property( dialog->package, control->property, buf );
1685     msi_free( buf );
1686
1687     return ERROR_SUCCESS;
1688 }
1689
1690 /* length of 2^32 + 1 */
1691 #define MAX_NUM_DIGITS 11
1692
1693 static UINT msi_dialog_edit_control( msi_dialog *dialog, MSIRECORD *rec )
1694 {
1695     msi_control *control;
1696     LPCWSTR prop, text;
1697     LPWSTR val, begin, end;
1698     WCHAR num[MAX_NUM_DIGITS];
1699     DWORD limit;
1700
1701     control = msi_dialog_add_control( dialog, rec, szEdit,
1702                                       WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL );
1703     control->handler = msi_dialog_edit_handler;
1704
1705     text = MSI_RecordGetString( rec, 10 );
1706     if ( text )
1707     {
1708         begin = strchrW( text, '{' );
1709         end = strchrW( text, '}' );
1710
1711         if ( begin && end && end > begin &&
1712              begin[0] >= '0' && begin[0] <= '9' &&
1713              end - begin < MAX_NUM_DIGITS)
1714         {
1715             lstrcpynW( num, begin + 1, end - begin );
1716             limit = atolW( num );
1717
1718             SendMessageW( control->hwnd, EM_SETLIMITTEXT, limit, 0 );
1719         }
1720     }
1721
1722     prop = MSI_RecordGetString( rec, 9 );
1723     if( prop )
1724         control->property = strdupW( prop );
1725
1726     val = msi_dup_property( dialog->package->db, control->property );
1727     SetWindowTextW( control->hwnd, val );
1728     msi_free( val );
1729     return ERROR_SUCCESS;
1730 }
1731
1732 /******************** Masked Edit ********************************************/
1733
1734 #define MASK_MAX_GROUPS 20
1735
1736 struct msi_mask_group
1737 {
1738     UINT len;
1739     UINT ofs;
1740     WCHAR type;
1741     HWND hwnd;
1742 };
1743
1744 struct msi_maskedit_info
1745 {
1746     msi_dialog *dialog;
1747     WNDPROC oldproc;
1748     HWND hwnd;
1749     LPWSTR prop;
1750     UINT num_chars;
1751     UINT num_groups;
1752     struct msi_mask_group group[MASK_MAX_GROUPS];
1753 };
1754
1755 static BOOL msi_mask_editable( WCHAR type )
1756 {
1757     switch (type)
1758     {
1759     case '%':
1760     case '#':
1761     case '&':
1762     case '`':
1763     case '?':
1764     case '^':
1765         return TRUE;
1766     }
1767     return FALSE;
1768 }
1769
1770 static void msi_mask_control_change( struct msi_maskedit_info *info )
1771 {
1772     LPWSTR val;
1773     UINT i, n, r;
1774
1775     val = msi_alloc( (info->num_chars+1)*sizeof(WCHAR) );
1776     for( i=0, n=0; i<info->num_groups; i++ )
1777     {
1778         if( (info->group[i].len + n) > info->num_chars )
1779         {
1780             ERR("can't fit control %d text into template\n",i);
1781             break;
1782         }
1783         if (!msi_mask_editable(info->group[i].type))
1784         {
1785             for(r=0; r<info->group[i].len; r++)
1786                 val[n+r] = info->group[i].type;
1787             val[n+r] = 0;
1788         }
1789         else
1790         {
1791             r = GetWindowTextW( info->group[i].hwnd, &val[n], info->group[i].len+1 );
1792             if( r != info->group[i].len )
1793                 break;
1794         }
1795         n += r;
1796     }
1797
1798     TRACE("%d/%d controls were good\n", i, info->num_groups);
1799
1800     if( i == info->num_groups )
1801     {
1802         TRACE("Set property %s to %s\n", debugstr_w(info->prop), debugstr_w(val));
1803         CharUpperBuffW( val, info->num_chars );
1804         msi_dialog_set_property( info->dialog->package, info->prop, val );
1805         msi_dialog_evaluate_control_conditions( info->dialog );
1806     }
1807     msi_free( val );
1808 }
1809
1810 /* now move to the next control if necessary */
1811 static VOID msi_mask_next_control( struct msi_maskedit_info *info, HWND hWnd )
1812 {
1813     HWND hWndNext;
1814     UINT len, i;
1815
1816     for( i=0; i<info->num_groups; i++ )
1817         if( info->group[i].hwnd == hWnd )
1818             break;
1819
1820     /* don't move from the last control */
1821     if( i >= (info->num_groups-1) )
1822         return;
1823
1824     len = SendMessageW( hWnd, WM_GETTEXTLENGTH, 0, 0 );
1825     if( len < info->group[i].len )
1826         return;
1827
1828     hWndNext = GetNextDlgTabItem( GetParent( hWnd ), hWnd, FALSE );
1829     SetFocus( hWndNext );
1830 }
1831
1832 static LRESULT WINAPI
1833 MSIMaskedEdit_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
1834 {
1835     struct msi_maskedit_info *info;
1836     HRESULT r;
1837
1838     TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam);
1839
1840     info = GetPropW(hWnd, szButtonData);
1841
1842     r = CallWindowProcW(info->oldproc, hWnd, msg, wParam, lParam);
1843
1844     switch( msg )
1845     {
1846     case WM_COMMAND:
1847         if (HIWORD(wParam) == EN_CHANGE)
1848         {
1849             msi_mask_control_change( info );
1850             msi_mask_next_control( info, (HWND) lParam );
1851         }
1852         break;
1853     case WM_NCDESTROY:
1854         msi_free( info->prop );
1855         msi_free( info );
1856         RemovePropW( hWnd, szButtonData );
1857         break;
1858     }
1859
1860     return r;
1861 }
1862
1863 /* fish the various bits of the property out and put them in the control */
1864 static void
1865 msi_maskedit_set_text( struct msi_maskedit_info *info, LPCWSTR text )
1866 {
1867     LPCWSTR p;
1868     UINT i;
1869
1870     p = text;
1871     for( i = 0; i < info->num_groups; i++ )
1872     {
1873         if( info->group[i].len < strlenW( p ) )
1874         {
1875             LPWSTR chunk = strdupW( p );
1876             chunk[ info->group[i].len ] = 0;
1877             SetWindowTextW( info->group[i].hwnd, chunk );
1878             msi_free( chunk );
1879         }
1880         else
1881         {
1882             SetWindowTextW( info->group[i].hwnd, p );
1883             break;
1884         }
1885         p += info->group[i].len;
1886     }
1887 }
1888
1889 static struct msi_maskedit_info * msi_dialog_parse_groups( LPCWSTR mask )
1890 {
1891     struct msi_maskedit_info * info = NULL;
1892     int i = 0, n = 0, total = 0;
1893     LPCWSTR p;
1894
1895     TRACE("masked control, template %s\n", debugstr_w(mask));
1896
1897     if( !mask )
1898         return info;
1899
1900     info = msi_alloc_zero( sizeof *info );
1901     if( !info )
1902         return info;
1903
1904     p = strchrW(mask, '<');
1905     if( p )
1906         p++;
1907     else
1908         p = mask;
1909
1910     for( i=0; i<MASK_MAX_GROUPS; i++ )
1911     {
1912         /* stop at the end of the string */
1913         if( p[0] == 0 || p[0] == '>' )
1914             break;
1915
1916         /* count the number of the same identifier */
1917         for( n=0; p[n] == p[0]; n++ )
1918             ;
1919         info->group[i].ofs = total;
1920         info->group[i].type = p[0];
1921         if( p[n] == '=' )
1922         {
1923             n++;
1924             total++; /* an extra not part of the group */
1925         }
1926         info->group[i].len = n;
1927         total += n;
1928         p += n;
1929     }
1930
1931     TRACE("%d characters in %d groups\n", total, i );
1932     if( i == MASK_MAX_GROUPS )
1933         ERR("too many groups in PIDTemplate %s\n", debugstr_w(mask));
1934
1935     info->num_chars = total;
1936     info->num_groups = i;
1937
1938     return info;
1939 }
1940
1941 static void
1942 msi_maskedit_create_children( struct msi_maskedit_info *info, LPCWSTR font )
1943 {
1944     DWORD width, height, style, wx, ww;
1945     RECT rect;
1946     HWND hwnd;
1947     UINT i;
1948
1949     style = WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL;
1950
1951     GetClientRect( info->hwnd, &rect );
1952
1953     width = rect.right - rect.left;
1954     height = rect.bottom - rect.top;
1955
1956     for( i = 0; i < info->num_groups; i++ )
1957     {
1958         if (!msi_mask_editable( info->group[i].type ))
1959             continue;
1960         wx = (info->group[i].ofs * width) / info->num_chars;
1961         ww = (info->group[i].len * width) / info->num_chars;
1962
1963         hwnd = CreateWindowW( szEdit, NULL, style, wx, 0, ww, height,
1964                               info->hwnd, NULL, NULL, NULL );
1965         if( !hwnd )
1966         {
1967             ERR("failed to create mask edit sub window\n");
1968             break;
1969         }
1970
1971         SendMessageW( hwnd, EM_LIMITTEXT, info->group[i].len, 0 );
1972
1973         msi_dialog_set_font( info->dialog, hwnd,
1974                              font?font:info->dialog->default_font );
1975         info->group[i].hwnd = hwnd;
1976     }
1977 }
1978
1979 /*
1980  * office 2003 uses "73931<````=````=````=````=`````>@@@@@"
1981  * delphi 7 uses "<????-??????-??????-????>" and "<???-???>"
1982  * filemaker pro 7 uses "<^^^^=^^^^=^^^^=^^^^=^^^^=^^^^=^^^^^>"
1983  */
1984 static UINT msi_dialog_maskedit_control( msi_dialog *dialog, MSIRECORD *rec )
1985 {
1986     LPWSTR font_mask, val = NULL, font;
1987     struct msi_maskedit_info *info = NULL;
1988     UINT ret = ERROR_SUCCESS;
1989     msi_control *control;
1990     LPCWSTR prop, mask;
1991
1992     TRACE("\n");
1993
1994     font_mask = msi_get_deformatted_field( dialog->package, rec, 10 );
1995     font = msi_dialog_get_style( font_mask, &mask );
1996     if( !mask )
1997     {
1998         WARN("mask template is empty\n");
1999         goto end;
2000     }
2001
2002     info = msi_dialog_parse_groups( mask );
2003     if( !info )
2004     {
2005         ERR("template %s is invalid\n", debugstr_w(mask));
2006         goto end;
2007     }
2008
2009     info->dialog = dialog;
2010
2011     control = msi_dialog_add_control( dialog, rec, szStatic,
2012                    SS_OWNERDRAW | WS_GROUP | WS_VISIBLE );
2013     if( !control )
2014     {
2015         ERR("Failed to create maskedit container\n");
2016         ret = ERROR_FUNCTION_FAILED;
2017         goto end;
2018     }
2019     SetWindowLongPtrW( control->hwnd, GWL_EXSTYLE, WS_EX_CONTROLPARENT );
2020
2021     info->hwnd = control->hwnd;
2022
2023     /* subclass the static control */
2024     info->oldproc = (WNDPROC) SetWindowLongPtrW( info->hwnd, GWLP_WNDPROC,
2025                                           (LONG_PTR)MSIMaskedEdit_WndProc );
2026     SetPropW( control->hwnd, szButtonData, info );
2027
2028     prop = MSI_RecordGetString( rec, 9 );
2029     if( prop )
2030         info->prop = strdupW( prop );
2031
2032     msi_maskedit_create_children( info, font );
2033
2034     if( prop )
2035     {
2036         val = msi_dup_property( dialog->package->db, prop );
2037         if( val )
2038         {
2039             msi_maskedit_set_text( info, val );
2040             msi_free( val );
2041         }
2042     }
2043
2044 end:
2045     if( ret != ERROR_SUCCESS )
2046         msi_free( info );
2047     msi_free( font_mask );
2048     msi_free( font );
2049     return ret;
2050 }
2051
2052 /******************** Progress Bar *****************************************/
2053
2054 static UINT msi_dialog_progress_bar( msi_dialog *dialog, MSIRECORD *rec )
2055 {
2056     msi_control *control;
2057     DWORD attributes, style;
2058
2059     style = WS_VISIBLE;
2060     attributes = MSI_RecordGetInteger( rec, 8 );
2061     if( !(attributes & msidbControlAttributesProgress95) )
2062         style |= PBS_SMOOTH;
2063
2064     control = msi_dialog_add_control( dialog, rec, PROGRESS_CLASSW, style );
2065     if( !control )
2066         return ERROR_FUNCTION_FAILED;
2067
2068     ControlEvent_SubscribeToEvent( dialog->package, dialog,
2069                                    szSetProgress, control->name, szProgress );
2070     return ERROR_SUCCESS;
2071 }
2072
2073 /******************** Path Edit ********************************************/
2074
2075 struct msi_pathedit_info
2076 {
2077     msi_dialog *dialog;
2078     msi_control *control;
2079     WNDPROC oldproc;
2080 };
2081
2082 static void msi_dialog_update_pathedit( msi_dialog *dialog, msi_control *control )
2083 {
2084     LPWSTR prop, path;
2085     BOOL indirect;
2086
2087     if (!control && !(control = msi_dialog_find_control_by_type( dialog, szPathEdit )))
2088        return;
2089
2090     indirect = control->attributes & msidbControlAttributesIndirect;
2091     prop = msi_dialog_dup_property( dialog, control->property, indirect );
2092     path = msi_dialog_dup_property( dialog, prop, TRUE );
2093
2094     SetWindowTextW( control->hwnd, path );
2095     SendMessageW( control->hwnd, EM_SETSEL, 0, -1 );
2096
2097     msi_free( path );
2098     msi_free( prop );
2099 }
2100
2101 /* FIXME: test when this should fail */
2102 static BOOL msi_dialog_verify_path( LPWSTR path )
2103 {
2104     if ( !lstrlenW( path ) )
2105         return FALSE;
2106
2107     if ( PathIsRelativeW( path ) )
2108         return FALSE;
2109
2110     return TRUE;
2111 }
2112
2113 /* returns TRUE if the path is valid, FALSE otherwise */
2114 static BOOL msi_dialog_onkillfocus( msi_dialog *dialog, msi_control *control )
2115 {
2116     LPWSTR buf, prop;
2117     BOOL indirect;
2118     BOOL valid;
2119
2120     indirect = control->attributes & msidbControlAttributesIndirect;
2121     prop = msi_dialog_dup_property( dialog, control->property, indirect );
2122
2123     buf = msi_get_window_text( control->hwnd );
2124
2125     if ( !msi_dialog_verify_path( buf ) )
2126     {
2127         /* FIXME: display an error message box */
2128         ERR("Invalid path %s\n", debugstr_w( buf ));
2129         valid = FALSE;
2130         SetFocus( control->hwnd );
2131     }
2132     else
2133     {
2134         valid = TRUE;
2135         msi_dialog_set_property( dialog->package, prop, buf );
2136     }
2137
2138     msi_dialog_update_pathedit( dialog, control );
2139
2140     TRACE("edit %s contents changed, set %s\n", debugstr_w(control->name),
2141           debugstr_w(prop));
2142
2143     msi_free( buf );
2144     msi_free( prop );
2145
2146     return valid;
2147 }
2148
2149 static LRESULT WINAPI MSIPathEdit_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
2150 {
2151     struct msi_pathedit_info *info = GetPropW(hWnd, szButtonData);
2152     LRESULT r = 0;
2153
2154     TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam);
2155
2156     if ( msg == WM_KILLFOCUS )
2157     {
2158         /* if the path is invalid, don't handle this message */
2159         if ( !msi_dialog_onkillfocus( info->dialog, info->control ) )
2160             return 0;
2161     }
2162
2163     r = CallWindowProcW(info->oldproc, hWnd, msg, wParam, lParam);
2164
2165     if ( msg == WM_NCDESTROY )
2166     {
2167         msi_free( info );
2168         RemovePropW( hWnd, szButtonData );
2169     }
2170
2171     return r;
2172 }
2173
2174 static UINT msi_dialog_pathedit_control( msi_dialog *dialog, MSIRECORD *rec )
2175 {
2176     struct msi_pathedit_info *info;
2177     msi_control *control;
2178     LPCWSTR prop;
2179
2180     info = msi_alloc( sizeof *info );
2181     if (!info)
2182         return ERROR_FUNCTION_FAILED;
2183
2184     control = msi_dialog_add_control( dialog, rec, szEdit,
2185                                       WS_BORDER | WS_TABSTOP );
2186     control->attributes = MSI_RecordGetInteger( rec, 8 );
2187     prop = MSI_RecordGetString( rec, 9 );
2188     control->property = msi_dialog_dup_property( dialog, prop, FALSE );
2189
2190     info->dialog = dialog;
2191     info->control = control;
2192     info->oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
2193                                                  (LONG_PTR)MSIPathEdit_WndProc );
2194     SetPropW( control->hwnd, szButtonData, info );
2195
2196     msi_dialog_update_pathedit( dialog, control );
2197
2198     return ERROR_SUCCESS;
2199 }
2200
2201 static UINT msi_dialog_radiogroup_handler( msi_dialog *dialog, msi_control *control, WPARAM param )
2202 {
2203     if (HIWORD(param) != BN_CLICKED)
2204         return ERROR_SUCCESS;
2205
2206     TRACE("clicked radio button %s, set %s\n", debugstr_w(control->name), debugstr_w(control->property));
2207
2208     msi_dialog_set_property( dialog->package, control->property, control->name );
2209
2210     return msi_dialog_button_handler( dialog, control, param );
2211 }
2212
2213 /* radio buttons are a bit different from normal controls */
2214 static UINT msi_dialog_create_radiobutton( MSIRECORD *rec, LPVOID param )
2215 {
2216     radio_button_group_descr *group = param;
2217     msi_dialog *dialog = group->dialog;
2218     msi_control *control;
2219     LPCWSTR prop, text, name;
2220     DWORD style, attributes = group->attributes;
2221
2222     style = WS_CHILD | BS_AUTORADIOBUTTON | BS_MULTILINE | WS_TABSTOP;
2223     name = MSI_RecordGetString( rec, 3 );
2224     text = MSI_RecordGetString( rec, 8 );
2225     if( attributes & msidbControlAttributesVisible )
2226         style |= WS_VISIBLE;
2227     if( ~attributes & msidbControlAttributesEnabled )
2228         style |= WS_DISABLED;
2229
2230     control = msi_dialog_create_window( dialog, rec, 0, szButton, name, text,
2231                                         style, group->parent->hwnd );
2232     if (!control)
2233         return ERROR_FUNCTION_FAILED;
2234     control->handler = msi_dialog_radiogroup_handler;
2235
2236     if (group->propval && !strcmpW( control->name, group->propval ))
2237         SendMessageW(control->hwnd, BM_SETCHECK, BST_CHECKED, 0);
2238
2239     prop = MSI_RecordGetString( rec, 1 );
2240     if( prop )
2241         control->property = strdupW( prop );
2242
2243     return ERROR_SUCCESS;
2244 }
2245
2246 static BOOL CALLBACK msi_radioground_child_enum( HWND hWnd, LPARAM lParam )
2247 {
2248     EnableWindow( hWnd, lParam );
2249     return TRUE;
2250 }
2251
2252 static LRESULT WINAPI MSIRadioGroup_WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
2253 {
2254     WNDPROC oldproc = (WNDPROC)GetPropW( hWnd, szButtonData );
2255     LRESULT r;
2256
2257     TRACE("hWnd %p msg %04x wParam 0x%08lx lParam 0x%08lx\n", hWnd, msg, wParam, lParam);
2258
2259     if (msg == WM_COMMAND) /* Forward notifications to dialog */
2260         SendMessageW( GetParent( hWnd ), msg, wParam, lParam );
2261
2262     r = CallWindowProcW( oldproc, hWnd, msg, wParam, lParam );
2263
2264     /* make sure the radio buttons show as disabled if the parent is disabled */
2265     if (msg == WM_ENABLE)
2266         EnumChildWindows( hWnd, msi_radioground_child_enum, wParam );
2267
2268     return r;
2269 }
2270
2271 static UINT msi_dialog_radiogroup_control( msi_dialog *dialog, MSIRECORD *rec )
2272 {
2273     static const WCHAR query[] = {
2274         'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
2275         'R','a','d','i','o','B','u','t','t','o','n',' ','W','H','E','R','E',' ',
2276         '`','P','r','o','p','e','r','t','y','`',' ','=',' ','\'','%','s','\'',0};
2277     UINT r;
2278     LPCWSTR prop;
2279     msi_control *control;
2280     MSIQUERY *view;
2281     radio_button_group_descr group;
2282     MSIPACKAGE *package = dialog->package;
2283     WNDPROC oldproc;
2284     DWORD attr, style = WS_GROUP;
2285
2286     prop = MSI_RecordGetString( rec, 9 );
2287
2288     TRACE("%p %p %s\n", dialog, rec, debugstr_w( prop ));
2289
2290     attr = MSI_RecordGetInteger( rec, 8 );
2291     if (attr & msidbControlAttributesHasBorder)
2292         style |= BS_GROUPBOX;
2293     else
2294         style |= BS_OWNERDRAW;
2295
2296     /* Create parent group box to hold radio buttons */
2297     control = msi_dialog_add_control( dialog, rec, szButton, style );
2298     if( !control )
2299         return ERROR_FUNCTION_FAILED;
2300
2301     oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
2302                                            (LONG_PTR)MSIRadioGroup_WndProc );
2303     SetPropW(control->hwnd, szButtonData, oldproc);
2304     SetWindowLongPtrW( control->hwnd, GWL_EXSTYLE, WS_EX_CONTROLPARENT );
2305
2306     if( prop )
2307         control->property = strdupW( prop );
2308
2309     /* query the Radio Button table for all control in this group */
2310     r = MSI_OpenQuery( package->db, &view, query, prop );
2311     if( r != ERROR_SUCCESS )
2312     {
2313         ERR("query failed for dialog %s radio group %s\n", 
2314             debugstr_w(dialog->name), debugstr_w(prop));
2315         return ERROR_INVALID_PARAMETER;
2316     }
2317
2318     group.dialog = dialog;
2319     group.parent = control;
2320     group.attributes = MSI_RecordGetInteger( rec, 8 );
2321     group.propval = msi_dup_property( dialog->package->db, control->property );
2322
2323     r = MSI_IterateRecords( view, 0, msi_dialog_create_radiobutton, &group );
2324     msiobj_release( &view->hdr );
2325     msi_free( group.propval );
2326     return r;
2327 }
2328
2329 static void
2330 msi_seltree_sync_item_state( HWND hwnd, MSIFEATURE *feature, HTREEITEM hItem )
2331 {
2332     TVITEMW tvi;
2333     DWORD index = feature->ActionRequest;
2334
2335     TRACE("Feature %s -> %d %d %d\n", debugstr_w(feature->Title),
2336         feature->Installed, feature->Action, feature->ActionRequest);
2337
2338     if (index == INSTALLSTATE_UNKNOWN)
2339         index = INSTALLSTATE_ABSENT;
2340
2341     tvi.mask = TVIF_STATE;
2342     tvi.hItem = hItem;
2343     tvi.state = INDEXTOSTATEIMAGEMASK( index );
2344     tvi.stateMask = TVIS_STATEIMAGEMASK;
2345
2346     SendMessageW( hwnd, TVM_SETITEMW, 0, (LPARAM) &tvi );
2347 }
2348
2349 static UINT
2350 msi_seltree_popup_menu( HWND hwnd, INT x, INT y )
2351 {
2352     HMENU hMenu;
2353     INT r;
2354
2355     /* create a menu to display */
2356     hMenu = CreatePopupMenu();
2357
2358     /* FIXME: load strings from resources */
2359     AppendMenuA( hMenu, MF_ENABLED, INSTALLSTATE_LOCAL, "Install feature locally");
2360     AppendMenuA( hMenu, MF_ENABLED, USER_INSTALLSTATE_ALL, "Install entire feature");
2361     AppendMenuA( hMenu, MF_ENABLED, INSTALLSTATE_ADVERTISED, "Install on demand");
2362     AppendMenuA( hMenu, MF_ENABLED, INSTALLSTATE_ABSENT, "Don't install");
2363     r = TrackPopupMenu( hMenu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RETURNCMD,
2364                         x, y, 0, hwnd, NULL );
2365     DestroyMenu( hMenu );
2366     return r;
2367 }
2368
2369 static void
2370 msi_seltree_update_feature_installstate( HWND hwnd, HTREEITEM hItem,
2371         MSIPACKAGE *package, MSIFEATURE *feature, INSTALLSTATE state )
2372 {
2373     feature->ActionRequest = state;
2374     msi_seltree_sync_item_state( hwnd, feature, hItem );
2375     ACTION_UpdateComponentStates( package, feature );
2376 }
2377
2378 static void
2379 msi_seltree_update_siblings_and_children_installstate( HWND hwnd, HTREEITEM curr,
2380         MSIPACKAGE *package, INSTALLSTATE state)
2381 {
2382     /* update all siblings */
2383     do
2384     {
2385         MSIFEATURE *feature;
2386         HTREEITEM child;
2387
2388         feature = msi_seltree_feature_from_item( hwnd, curr );
2389         msi_seltree_update_feature_installstate( hwnd, curr, package, feature, state );
2390
2391         /* update this sibling's children */
2392         child = (HTREEITEM)SendMessageW( hwnd, TVM_GETNEXTITEM, (WPARAM)TVGN_CHILD, (LPARAM)curr );
2393         if (child)
2394             msi_seltree_update_siblings_and_children_installstate( hwnd, child,
2395                     package, state );
2396     }
2397     while ((curr = (HTREEITEM)SendMessageW( hwnd, TVM_GETNEXTITEM, (WPARAM)TVGN_NEXT, (LPARAM)curr )));
2398 }
2399
2400 static LRESULT
2401 msi_seltree_menu( HWND hwnd, HTREEITEM hItem )
2402 {
2403     struct msi_selection_tree_info *info;
2404     MSIFEATURE *feature;
2405     MSIPACKAGE *package;
2406     union {
2407         RECT rc;
2408         POINT pt[2];
2409         HTREEITEM hItem;
2410     } u;
2411     UINT r;
2412
2413     info = GetPropW(hwnd, szButtonData);
2414     package = info->dialog->package;
2415
2416     feature = msi_seltree_feature_from_item( hwnd, hItem );
2417     if (!feature)
2418     {
2419         ERR("item %p feature was NULL\n", hItem);
2420         return 0;
2421     }
2422
2423     /* get the item's rectangle to put the menu just below it */
2424     u.hItem = hItem;
2425     SendMessageW( hwnd, TVM_GETITEMRECT, 0, (LPARAM) &u.rc );
2426     MapWindowPoints( hwnd, NULL, u.pt, 2 );
2427
2428     r = msi_seltree_popup_menu( hwnd, u.rc.left, u.rc.top );
2429
2430     switch (r)
2431     {
2432     case USER_INSTALLSTATE_ALL:
2433         r = INSTALLSTATE_LOCAL;
2434         /* fall-through */
2435     case INSTALLSTATE_ADVERTISED:
2436     case INSTALLSTATE_ABSENT:
2437         {
2438             HTREEITEM child;
2439             child = (HTREEITEM)SendMessageW( hwnd, TVM_GETNEXTITEM, (WPARAM)TVGN_CHILD, (LPARAM)hItem );
2440             if (child)
2441                 msi_seltree_update_siblings_and_children_installstate( hwnd, child, package, r );
2442         }
2443         /* fall-through */
2444     case INSTALLSTATE_LOCAL:
2445         msi_seltree_update_feature_installstate( hwnd, hItem, package, feature, r );
2446         break;
2447     }
2448
2449     return 0;
2450 }
2451
2452 static LRESULT WINAPI
2453 MSISelectionTree_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
2454 {
2455     struct msi_selection_tree_info *info;
2456     TVHITTESTINFO tvhti;
2457     HRESULT r;
2458
2459     TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam);
2460
2461     info = GetPropW(hWnd, szButtonData);
2462
2463     switch( msg )
2464     {
2465     case WM_LBUTTONDOWN:
2466         tvhti.pt.x = (short)LOWORD( lParam );
2467         tvhti.pt.y = (short)HIWORD( lParam );
2468         tvhti.flags = 0;
2469         tvhti.hItem = 0;
2470         CallWindowProcW(info->oldproc, hWnd, TVM_HITTEST, 0, (LPARAM) &tvhti );
2471         if (tvhti.flags & TVHT_ONITEMSTATEICON)
2472             return msi_seltree_menu( hWnd, tvhti.hItem );
2473         break;
2474     }
2475     r = CallWindowProcW(info->oldproc, hWnd, msg, wParam, lParam);
2476
2477     switch( msg )
2478     {
2479     case WM_NCDESTROY:
2480         msi_free( info );
2481         RemovePropW( hWnd, szButtonData );
2482         break;
2483     }
2484     return r;
2485 }
2486
2487 static void
2488 msi_seltree_add_child_features( MSIPACKAGE *package, HWND hwnd,
2489                                 LPCWSTR parent, HTREEITEM hParent )
2490 {
2491     struct msi_selection_tree_info *info = GetPropW( hwnd, szButtonData );
2492     MSIFEATURE *feature;
2493     TVINSERTSTRUCTW tvis;
2494     HTREEITEM hitem, hfirst = NULL;
2495
2496     LIST_FOR_EACH_ENTRY( feature, &package->features, MSIFEATURE, entry )
2497     {
2498         if ( parent && feature->Feature_Parent && strcmpW( parent, feature->Feature_Parent ))
2499             continue;
2500         else if ( parent && !feature->Feature_Parent )
2501             continue;
2502         else if ( !parent && feature->Feature_Parent )
2503             continue;
2504
2505         if ( !feature->Title )
2506             continue;
2507
2508         if ( !feature->Display )
2509             continue;
2510
2511         memset( &tvis, 0, sizeof tvis );
2512         tvis.hParent = hParent;
2513         tvis.hInsertAfter = TVI_LAST;
2514         tvis.u.item.mask = TVIF_TEXT | TVIF_PARAM;
2515         tvis.u.item.pszText = feature->Title;
2516         tvis.u.item.lParam = (LPARAM) feature;
2517
2518         hitem = (HTREEITEM) SendMessageW( hwnd, TVM_INSERTITEMW, 0, (LPARAM) &tvis );
2519         if (!hitem)
2520             continue;
2521
2522         if (!hfirst)
2523             hfirst = hitem;
2524
2525         msi_seltree_sync_item_state( hwnd, feature, hitem );
2526         msi_seltree_add_child_features( package, hwnd,
2527                                         feature->Feature, hitem );
2528
2529         /* the node is expanded if Display is odd */
2530         if ( feature->Display % 2 != 0 )
2531             SendMessageW( hwnd, TVM_EXPAND, TVE_EXPAND, (LPARAM) hitem );
2532     }
2533
2534     /* select the first item */
2535     SendMessageW( hwnd, TVM_SELECTITEM, TVGN_CARET | TVGN_DROPHILITE, (LPARAM) hfirst );
2536     info->selected = hfirst;
2537 }
2538
2539 static void msi_seltree_create_imagelist( HWND hwnd )
2540 {
2541     const int bm_width = 32, bm_height = 16, bm_count = 3;
2542     const int bm_resource = 0x1001;
2543     HIMAGELIST himl;
2544     int i;
2545     HBITMAP hbmp;
2546
2547     himl = ImageList_Create( bm_width, bm_height, FALSE, 4, 0 );
2548     if (!himl)
2549     {
2550         ERR("failed to create image list\n");
2551         return;
2552     }
2553
2554     for (i=0; i<bm_count; i++)
2555     {
2556         hbmp = LoadBitmapW( msi_hInstance, MAKEINTRESOURCEW(i+bm_resource) );
2557         if (!hbmp)
2558         {
2559             ERR("failed to load bitmap %d\n", i);
2560             break;
2561         }
2562
2563         /*
2564          * Add a dummy bitmap at offset zero because the treeview
2565          * can't use it as a state mask (zero means no user state).
2566          */
2567         if (!i)
2568             ImageList_Add( himl, hbmp, NULL );
2569
2570         ImageList_Add( himl, hbmp, NULL );
2571     }
2572
2573     SendMessageW( hwnd, TVM_SETIMAGELIST, TVSIL_STATE, (LPARAM)himl );
2574 }
2575
2576 static UINT msi_dialog_seltree_handler( msi_dialog *dialog,
2577                                         msi_control *control, WPARAM param )
2578 {
2579     struct msi_selection_tree_info *info = GetPropW( control->hwnd, szButtonData );
2580     LPNMTREEVIEWW tv = (LPNMTREEVIEWW)param;
2581     MSIRECORD *row, *rec;
2582     MSIFOLDER *folder;
2583     MSIFEATURE *feature;
2584     LPCWSTR dir, title = NULL;
2585     UINT r = ERROR_SUCCESS;
2586
2587     static const WCHAR select[] = {
2588         'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
2589         '`','F','e','a','t','u','r','e','`',' ','W','H','E','R','E',' ',
2590         '`','T','i','t','l','e','`',' ','=',' ','\'','%','s','\'',0
2591     };
2592
2593     if (tv->hdr.code != TVN_SELCHANGINGW)
2594         return ERROR_SUCCESS;
2595
2596     info->selected = tv->itemNew.hItem;
2597
2598     if (!(tv->itemNew.mask & TVIF_TEXT))
2599     {
2600         feature = msi_seltree_feature_from_item( control->hwnd, tv->itemNew.hItem );
2601         if (feature)
2602             title = feature->Title;
2603     }
2604     else
2605         title = tv->itemNew.pszText;
2606
2607     row = MSI_QueryGetRecord( dialog->package->db, select, title );
2608     if (!row)
2609         return ERROR_FUNCTION_FAILED;
2610
2611     rec = MSI_CreateRecord( 1 );
2612
2613     MSI_RecordSetStringW( rec, 1, MSI_RecordGetString( row, 4 ) );
2614     ControlEvent_FireSubscribedEvent( dialog->package, szSelectionDescription, rec );
2615
2616     dir = MSI_RecordGetString( row, 7 );
2617     if (dir)
2618     {
2619         folder = msi_get_loaded_folder( dialog->package, dir );
2620         if (!folder)
2621         {
2622             r = ERROR_FUNCTION_FAILED;
2623             goto done;
2624         }
2625         MSI_RecordSetStringW( rec, 1, folder->ResolvedTarget );
2626     }
2627     else
2628         MSI_RecordSetStringW( rec, 1, NULL );
2629
2630     ControlEvent_FireSubscribedEvent( dialog->package, szSelectionPath, rec );
2631
2632 done:
2633     msiobj_release(&row->hdr);
2634     msiobj_release(&rec->hdr);
2635
2636     return r;
2637 }
2638
2639 static UINT msi_dialog_selection_tree( msi_dialog *dialog, MSIRECORD *rec )
2640 {
2641     msi_control *control;
2642     LPCWSTR prop, control_name;
2643     MSIPACKAGE *package = dialog->package;
2644     DWORD style;
2645     struct msi_selection_tree_info *info;
2646
2647     info = msi_alloc( sizeof *info );
2648     if (!info)
2649         return ERROR_FUNCTION_FAILED;
2650
2651     /* create the treeview control */
2652     style = TVS_HASLINES | TVS_HASBUTTONS | TVS_LINESATROOT;
2653     style |= WS_GROUP | WS_VSCROLL;
2654     control = msi_dialog_add_control( dialog, rec, WC_TREEVIEWW, style );
2655     if (!control)
2656     {
2657         msi_free(info);
2658         return ERROR_FUNCTION_FAILED;
2659     }
2660
2661     control->handler = msi_dialog_seltree_handler;
2662     control_name = MSI_RecordGetString( rec, 2 );
2663     control->attributes = MSI_RecordGetInteger( rec, 8 );
2664     prop = MSI_RecordGetString( rec, 9 );
2665     control->property = msi_dialog_dup_property( dialog, prop, FALSE );
2666
2667     /* subclass */
2668     info->dialog = dialog;
2669     info->hwnd = control->hwnd;
2670     info->oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
2671                                           (LONG_PTR)MSISelectionTree_WndProc );
2672     SetPropW( control->hwnd, szButtonData, info );
2673
2674     ControlEvent_SubscribeToEvent( dialog->package, dialog,
2675                                    szSelectionPath, control_name, szProperty );
2676
2677     /* initialize it */
2678     msi_seltree_create_imagelist( control->hwnd );
2679     msi_seltree_add_child_features( package, control->hwnd, NULL, NULL );
2680
2681     return ERROR_SUCCESS;
2682 }
2683
2684 /******************** Group Box ***************************************/
2685
2686 static UINT msi_dialog_group_box( msi_dialog *dialog, MSIRECORD *rec )
2687 {
2688     msi_control *control;
2689     DWORD style;
2690
2691     style = BS_GROUPBOX | WS_CHILD | WS_GROUP;
2692     control = msi_dialog_add_control( dialog, rec, WC_BUTTONW, style );
2693     if (!control)
2694         return ERROR_FUNCTION_FAILED;
2695
2696     return ERROR_SUCCESS;
2697 }
2698
2699 /******************** List Box ***************************************/
2700
2701 struct msi_listbox_info
2702 {
2703     msi_dialog *dialog;
2704     HWND hwnd;
2705     WNDPROC oldproc;
2706     DWORD num_items;
2707     DWORD addpos_items;
2708     LPWSTR *items;
2709 };
2710
2711 static LRESULT WINAPI MSIListBox_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
2712 {
2713     struct msi_listbox_info *info;
2714     LRESULT r;
2715     DWORD j;
2716
2717     TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam);
2718
2719     info = GetPropW( hWnd, szButtonData );
2720     if (!info)
2721         return 0;
2722
2723     r = CallWindowProcW( info->oldproc, hWnd, msg, wParam, lParam );
2724
2725     switch( msg )
2726     {
2727     case WM_NCDESTROY:
2728         for (j = 0; j < info->num_items; j++)
2729             msi_free( info->items[j] );
2730         msi_free( info->items );
2731         msi_free( info );
2732         RemovePropW( hWnd, szButtonData );
2733         break;
2734     }
2735
2736     return r;
2737 }
2738
2739 static UINT msi_listbox_add_item( MSIRECORD *rec, LPVOID param )
2740 {
2741     struct msi_listbox_info *info = param;
2742     LPCWSTR value, text;
2743     int pos;
2744
2745     value = MSI_RecordGetString( rec, 3 );
2746     text = MSI_RecordGetString( rec, 4 );
2747
2748     info->items[info->addpos_items] = strdupW( value );
2749
2750     pos = SendMessageW( info->hwnd, LB_ADDSTRING, 0, (LPARAM)text );
2751     SendMessageW( info->hwnd, LB_SETITEMDATA, pos, (LPARAM)info->items[info->addpos_items] );
2752     info->addpos_items++;
2753     return ERROR_SUCCESS;
2754 }
2755
2756 static UINT msi_listbox_add_items( struct msi_listbox_info *info, LPCWSTR property )
2757 {
2758     static const WCHAR query[] = {
2759         'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
2760         '`','L','i','s','t','B','o','x','`',' ','W','H','E','R','E',' ',
2761         '`','P','r','o','p','e','r','t','y','`',' ','=',' ','\'','%','s','\'',' ',
2762         'O','R','D','E','R',' ','B','Y',' ','`','O','r','d','e','r','`',0};
2763     MSIQUERY *view;
2764     DWORD count;
2765     UINT r;
2766
2767     r = MSI_OpenQuery( info->dialog->package->db, &view, query, property );
2768     if ( r != ERROR_SUCCESS )
2769         return r;
2770
2771     /* just get the number of records */
2772     count = 0;
2773     r = MSI_IterateRecords( view, &count, NULL, NULL );
2774     if (r != ERROR_SUCCESS)
2775     {
2776         msiobj_release( &view->hdr );
2777         return r;
2778     }
2779     info->num_items = count;
2780     info->items = msi_alloc( sizeof(*info->items) * count );
2781
2782     r = MSI_IterateRecords( view, NULL, msi_listbox_add_item, info );
2783     msiobj_release( &view->hdr );
2784     return r;
2785 }
2786
2787 static UINT msi_dialog_listbox_handler( msi_dialog *dialog,
2788                                         msi_control *control, WPARAM param )
2789 {
2790     struct msi_listbox_info *info;
2791     int index;
2792     LPCWSTR value;
2793
2794     if( HIWORD(param) != LBN_SELCHANGE )
2795         return ERROR_SUCCESS;
2796
2797     info = GetPropW( control->hwnd, szButtonData );
2798     index = SendMessageW( control->hwnd, LB_GETCURSEL, 0, 0 );
2799     value = (LPCWSTR) SendMessageW( control->hwnd, LB_GETITEMDATA, index, 0 );
2800
2801     msi_dialog_set_property( info->dialog->package, control->property, value );
2802     msi_dialog_evaluate_control_conditions( info->dialog );
2803
2804     return ERROR_SUCCESS;
2805 }
2806
2807 static UINT msi_dialog_list_box( msi_dialog *dialog, MSIRECORD *rec )
2808 {
2809     struct msi_listbox_info *info;
2810     msi_control *control;
2811     DWORD attributes, style;
2812     LPCWSTR prop;
2813
2814     info = msi_alloc( sizeof *info );
2815     if (!info)
2816         return ERROR_FUNCTION_FAILED;
2817
2818     style = WS_TABSTOP | WS_GROUP | WS_CHILD | LBS_NOTIFY | WS_VSCROLL | WS_BORDER;
2819     attributes = MSI_RecordGetInteger( rec, 8 );
2820     if (~attributes & msidbControlAttributesSorted)
2821         style |= LBS_SORT;
2822
2823     control = msi_dialog_add_control( dialog, rec, WC_LISTBOXW, style );
2824     if (!control)
2825     {
2826         msi_free(info);
2827         return ERROR_FUNCTION_FAILED;
2828     }
2829
2830     control->handler = msi_dialog_listbox_handler;
2831
2832     prop = MSI_RecordGetString( rec, 9 );
2833     control->property = msi_dialog_dup_property( dialog, prop, FALSE );
2834
2835     /* subclass */
2836     info->dialog = dialog;
2837     info->hwnd = control->hwnd;
2838     info->items = NULL;
2839     info->addpos_items = 0;
2840     info->oldproc = (WNDPROC)SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
2841                                                 (LONG_PTR)MSIListBox_WndProc );
2842     SetPropW( control->hwnd, szButtonData, info );
2843
2844     if ( control->property )
2845         msi_listbox_add_items( info, control->property );
2846
2847     return ERROR_SUCCESS;
2848 }
2849
2850 /******************** Directory Combo ***************************************/
2851
2852 static void msi_dialog_update_directory_combo( msi_dialog *dialog, msi_control *control )
2853 {
2854     LPWSTR prop, path;
2855     BOOL indirect;
2856
2857     if (!control && !(control = msi_dialog_find_control_by_type( dialog, szDirectoryCombo )))
2858         return;
2859
2860     indirect = control->attributes & msidbControlAttributesIndirect;
2861     prop = msi_dialog_dup_property( dialog, control->property, indirect );
2862     path = msi_dialog_dup_property( dialog, prop, TRUE );
2863
2864     PathStripPathW( path );
2865     PathRemoveBackslashW( path );
2866
2867     SendMessageW( control->hwnd, CB_INSERTSTRING, 0, (LPARAM)path );
2868     SendMessageW( control->hwnd, CB_SETCURSEL, 0, 0 );
2869
2870     msi_free( path );
2871     msi_free( prop );
2872 }
2873
2874 static UINT msi_dialog_directory_combo( msi_dialog *dialog, MSIRECORD *rec )
2875 {
2876     msi_control *control;
2877     LPCWSTR prop;
2878     DWORD style;
2879
2880     /* FIXME: use CBS_OWNERDRAWFIXED and add owner draw code */
2881     style = CBS_DROPDOWNLIST | CBS_HASSTRINGS | WS_CHILD |
2882             WS_GROUP | WS_TABSTOP | WS_VSCROLL;
2883     control = msi_dialog_add_control( dialog, rec, WC_COMBOBOXW, style );
2884     if (!control)
2885         return ERROR_FUNCTION_FAILED;
2886
2887     control->attributes = MSI_RecordGetInteger( rec, 8 );
2888     prop = MSI_RecordGetString( rec, 9 );
2889     control->property = msi_dialog_dup_property( dialog, prop, FALSE );
2890
2891     msi_dialog_update_directory_combo( dialog, control );
2892
2893     return ERROR_SUCCESS;
2894 }
2895
2896 /******************** Directory List ***************************************/
2897
2898 static void msi_dialog_update_directory_list( msi_dialog *dialog, msi_control *control )
2899 {
2900     WCHAR dir_spec[MAX_PATH];
2901     WIN32_FIND_DATAW wfd;
2902     LPWSTR prop, path;
2903     BOOL indirect;
2904     LVITEMW item;
2905     HANDLE file;
2906
2907     static const WCHAR asterisk[] = {'*',0};
2908
2909     if (!control && !(control = msi_dialog_find_control_by_type( dialog, szDirectoryList )))
2910         return;
2911
2912     /* clear the list-view */
2913     SendMessageW( control->hwnd, LVM_DELETEALLITEMS, 0, 0 );
2914
2915     indirect = control->attributes & msidbControlAttributesIndirect;
2916     prop = msi_dialog_dup_property( dialog, control->property, indirect );
2917     path = msi_dialog_dup_property( dialog, prop, TRUE );
2918
2919     lstrcpyW( dir_spec, path );
2920     lstrcatW( dir_spec, asterisk );
2921
2922     file = FindFirstFileW( dir_spec, &wfd );
2923     if ( file == INVALID_HANDLE_VALUE )
2924         return;
2925
2926     do
2927     {
2928         if ( wfd.dwFileAttributes != FILE_ATTRIBUTE_DIRECTORY )
2929             continue;
2930
2931         if ( !strcmpW( wfd.cFileName, szDot ) || !strcmpW( wfd.cFileName, szDotDot ) )
2932             continue;
2933
2934         item.mask = LVIF_TEXT;
2935         item.cchTextMax = MAX_PATH;
2936         item.iItem = 0;
2937         item.iSubItem = 0;
2938         item.pszText = wfd.cFileName;
2939
2940         SendMessageW( control->hwnd, LVM_INSERTITEMW, 0, (LPARAM)&item );
2941     } while ( FindNextFileW( file, &wfd ) );
2942
2943     msi_free( prop );
2944     msi_free( path );
2945     FindClose( file );
2946 }
2947
2948 UINT msi_dialog_directorylist_up( msi_dialog *dialog )
2949 {
2950     msi_control *control;
2951     LPWSTR prop, path, ptr;
2952     BOOL indirect;
2953
2954     control = msi_dialog_find_control_by_type( dialog, szDirectoryList );
2955     indirect = control->attributes & msidbControlAttributesIndirect;
2956     prop = msi_dialog_dup_property( dialog, control->property, indirect );
2957     path = msi_dialog_dup_property( dialog, prop, TRUE );
2958
2959     /* strip off the last directory */
2960     ptr = PathFindFileNameW( path );
2961     if (ptr != path) *(ptr - 1) = '\0';
2962     PathAddBackslashW( path );
2963
2964     msi_dialog_set_property( dialog->package, prop, path );
2965
2966     msi_dialog_update_directory_list( dialog, NULL );
2967     msi_dialog_update_directory_combo( dialog, NULL );
2968     msi_dialog_update_pathedit( dialog, NULL );
2969
2970     msi_free( path );
2971     msi_free( prop );
2972
2973     return ERROR_SUCCESS;
2974 }
2975
2976 static UINT msi_dialog_dirlist_handler( msi_dialog *dialog,
2977                                         msi_control *control, WPARAM param )
2978 {
2979     LPNMHDR nmhdr = (LPNMHDR)param;
2980     WCHAR new_path[MAX_PATH];
2981     WCHAR text[MAX_PATH];
2982     LPWSTR path, prop;
2983     BOOL indirect;
2984     LVITEMW item;
2985     int index;
2986
2987     if (nmhdr->code != LVN_ITEMACTIVATE)
2988         return ERROR_SUCCESS;
2989
2990     index = SendMessageW( control->hwnd, LVM_GETNEXTITEM, -1, LVNI_SELECTED );
2991     if ( index < 0 )
2992     {
2993         ERR("No list-view item selected!\n");
2994         return ERROR_FUNCTION_FAILED;
2995     }
2996
2997     item.iSubItem = 0;
2998     item.pszText = text;
2999     item.cchTextMax = MAX_PATH;
3000     SendMessageW( control->hwnd, LVM_GETITEMTEXTW, index, (LPARAM)&item );
3001
3002     indirect = control->attributes & msidbControlAttributesIndirect;
3003     prop = msi_dialog_dup_property( dialog, control->property, indirect );
3004     path = msi_dialog_dup_property( dialog, prop, TRUE );
3005
3006     lstrcpyW( new_path, path );
3007     lstrcatW( new_path, text );
3008     lstrcatW( new_path, szBackSlash );
3009
3010     msi_dialog_set_property( dialog->package, prop, new_path );
3011
3012     msi_dialog_update_directory_list( dialog, NULL );
3013     msi_dialog_update_directory_combo( dialog, NULL );
3014     msi_dialog_update_pathedit( dialog, NULL );
3015
3016     msi_free( prop );
3017     msi_free( path );
3018     return ERROR_SUCCESS;
3019 }
3020
3021 static UINT msi_dialog_directory_list( msi_dialog *dialog, MSIRECORD *rec )
3022 {
3023     msi_control *control;
3024     LPCWSTR prop;
3025     DWORD style;
3026
3027     style = LVS_LIST | WS_VSCROLL | LVS_SHAREIMAGELISTS |
3028             LVS_AUTOARRANGE | LVS_SINGLESEL | WS_BORDER |
3029             LVS_SORTASCENDING | WS_CHILD | WS_GROUP | WS_TABSTOP;
3030     control = msi_dialog_add_control( dialog, rec, WC_LISTVIEWW, style );
3031     if (!control)
3032         return ERROR_FUNCTION_FAILED;
3033
3034     control->attributes = MSI_RecordGetInteger( rec, 8 );
3035     control->handler = msi_dialog_dirlist_handler;
3036     prop = MSI_RecordGetString( rec, 9 );
3037     control->property = msi_dialog_dup_property( dialog, prop, FALSE );
3038
3039     /* double click to activate an item in the list */
3040     SendMessageW( control->hwnd, LVM_SETEXTENDEDLISTVIEWSTYLE,
3041                   0, LVS_EX_TWOCLICKACTIVATE );
3042
3043     msi_dialog_update_directory_list( dialog, control );
3044
3045     return ERROR_SUCCESS;
3046 }
3047
3048 /******************** VolumeCost List ***************************************/
3049
3050 static BOOL str_is_number( LPCWSTR str )
3051 {
3052     int i;
3053
3054     for (i = 0; i < lstrlenW( str ); i++)
3055         if (!isdigitW(str[i]))
3056             return FALSE;
3057
3058     return TRUE;
3059 }
3060
3061 static const WCHAR column_keys[][80] =
3062 {
3063     {'V','o','l','u','m','e','C','o','s','t','V','o','l','u','m','e',0},
3064     {'V','o','l','u','m','e','C','o','s','t','S','i','z','e',0},
3065     {'V','o','l','u','m','e','C','o','s','t','A','v','a','i','l','a','b','l','e',0},
3066     {'V','o','l','u','m','e','C','o','s','t','R','e','q','u','i','r','e','d',0},
3067     {'V','o','l','u','m','e','C','o','s','t','D','i','f','f','e','r','e','n','c','e',0}
3068 };
3069
3070 static void msi_dialog_vcl_add_columns( msi_dialog *dialog, msi_control *control, MSIRECORD *rec )
3071 {
3072     LPCWSTR text = MSI_RecordGetString( rec, 10 );
3073     LPCWSTR begin = text, end;
3074     WCHAR *num;
3075     LVCOLUMNW lvc;
3076     DWORD count = 0;
3077
3078     static const WCHAR negative[] = {'-',0};
3079
3080     if (!text) return;
3081
3082     while ((begin = strchrW( begin, '{' )) && count < 5)
3083     {
3084         if (!(end = strchrW( begin, '}' )))
3085             return;
3086
3087         num = msi_alloc( (end-begin+1)*sizeof(WCHAR) );
3088         if (!num)
3089             return;
3090
3091         lstrcpynW( num, begin + 1, end - begin );
3092         begin += end - begin + 1;
3093
3094         /* empty braces or '0' hides the column */ 
3095         if ( !num[0] || !strcmpW( num, szZero ) )
3096         {
3097             count++;
3098             msi_free( num );
3099             continue;
3100         }
3101
3102         /* the width must be a positive number
3103          * if a width is invalid, all remaining columns are hidden
3104          */
3105         if ( !strncmpW( num, negative, 1 ) || !str_is_number( num ) ) {
3106             msi_free( num );
3107             return;
3108         }
3109
3110         ZeroMemory( &lvc, sizeof(lvc) );
3111         lvc.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM;
3112         lvc.cx = atolW( num );
3113         lvc.pszText = msi_dialog_get_uitext( dialog, column_keys[count] );
3114
3115         SendMessageW( control->hwnd,  LVM_INSERTCOLUMNW, count++, (LPARAM)&lvc );
3116         msi_free( lvc.pszText );
3117         msi_free( num );
3118     }
3119 }
3120
3121 static LONGLONG msi_vcl_get_cost( msi_dialog *dialog )
3122 {
3123     MSIFEATURE *feature;
3124     INT each_cost;
3125     LONGLONG total_cost = 0;
3126
3127     LIST_FOR_EACH_ENTRY( feature, &dialog->package->features, MSIFEATURE, entry )
3128     {
3129         if (ERROR_SUCCESS == (MSI_GetFeatureCost(dialog->package, feature,
3130                 MSICOSTTREE_SELFONLY, INSTALLSTATE_LOCAL, &each_cost)))
3131         {
3132             /* each_cost is in 512-byte units */
3133             total_cost += each_cost * 512;
3134         }
3135         if (ERROR_SUCCESS == (MSI_GetFeatureCost(dialog->package, feature,
3136                 MSICOSTTREE_SELFONLY, INSTALLSTATE_ABSENT, &each_cost)))
3137         {
3138             /* each_cost is in 512-byte units */
3139             total_cost -= each_cost * 512;
3140         }
3141     }
3142     return total_cost;
3143 }
3144
3145 static void msi_dialog_vcl_add_drives( msi_dialog *dialog, msi_control *control )
3146 {
3147     ULARGE_INTEGER total, free;
3148     LONGLONG difference, cost;
3149     WCHAR size_text[MAX_PATH];
3150     WCHAR cost_text[MAX_PATH];
3151     LPWSTR drives, ptr;
3152     LVITEMW lvitem;
3153     DWORD size;
3154     int i = 0;
3155
3156     cost = msi_vcl_get_cost(dialog);
3157     StrFormatByteSizeW(cost, cost_text, MAX_PATH);
3158
3159     size = GetLogicalDriveStringsW( 0, NULL );
3160     if ( !size ) return;
3161
3162     drives = msi_alloc( (size + 1) * sizeof(WCHAR) );
3163     if ( !drives ) return;
3164
3165     GetLogicalDriveStringsW( size, drives );
3166
3167     ptr = drives;
3168     while (*ptr)
3169     {
3170         lvitem.mask = LVIF_TEXT;
3171         lvitem.iItem = i;
3172         lvitem.iSubItem = 0;
3173         lvitem.pszText = ptr;
3174         lvitem.cchTextMax = lstrlenW(ptr) + 1;
3175         SendMessageW( control->hwnd, LVM_INSERTITEMW, 0, (LPARAM)&lvitem );
3176
3177         GetDiskFreeSpaceExW(ptr, &free, &total, NULL);
3178         difference = free.QuadPart - cost;
3179
3180         StrFormatByteSizeW(total.QuadPart, size_text, MAX_PATH);
3181         lvitem.iSubItem = 1;
3182         lvitem.pszText = size_text;
3183         lvitem.cchTextMax = lstrlenW(size_text) + 1;
3184         SendMessageW( control->hwnd, LVM_SETITEMW, 0, (LPARAM)&lvitem );
3185
3186         StrFormatByteSizeW(free.QuadPart, size_text, MAX_PATH);
3187         lvitem.iSubItem = 2;
3188         lvitem.pszText = size_text;
3189         lvitem.cchTextMax = lstrlenW(size_text) + 1;
3190         SendMessageW( control->hwnd, LVM_SETITEMW, 0, (LPARAM)&lvitem );
3191
3192         lvitem.iSubItem = 3;
3193         lvitem.pszText = cost_text;
3194         lvitem.cchTextMax = lstrlenW(cost_text) + 1;
3195         SendMessageW( control->hwnd, LVM_SETITEMW, 0, (LPARAM)&lvitem );
3196
3197         StrFormatByteSizeW(difference, size_text, MAX_PATH);
3198         lvitem.iSubItem = 4;
3199         lvitem.pszText = size_text;
3200         lvitem.cchTextMax = lstrlenW(size_text) + 1;
3201         SendMessageW( control->hwnd, LVM_SETITEMW, 0, (LPARAM)&lvitem );
3202
3203         ptr += lstrlenW(ptr) + 1;
3204         i++;
3205     }
3206
3207     msi_free( drives );
3208 }
3209
3210 static UINT msi_dialog_volumecost_list( msi_dialog *dialog, MSIRECORD *rec )
3211 {
3212     msi_control *control;
3213     DWORD style;
3214
3215     style = LVS_REPORT | WS_VSCROLL | WS_HSCROLL | LVS_SHAREIMAGELISTS |
3216             LVS_AUTOARRANGE | LVS_SINGLESEL | WS_BORDER |
3217             WS_CHILD | WS_TABSTOP | WS_GROUP;
3218     control = msi_dialog_add_control( dialog, rec, WC_LISTVIEWW, style );
3219     if (!control)
3220         return ERROR_FUNCTION_FAILED;
3221
3222     msi_dialog_vcl_add_columns( dialog, control, rec );
3223     msi_dialog_vcl_add_drives( dialog, control );
3224
3225     return ERROR_SUCCESS;
3226 }
3227
3228 /******************** VolumeSelect Combo ***************************************/
3229
3230 static UINT msi_dialog_volsel_handler( msi_dialog *dialog,
3231                                        msi_control *control, WPARAM param )
3232 {
3233     WCHAR text[MAX_PATH];
3234     LPWSTR prop;
3235     BOOL indirect;
3236     int index;
3237
3238     if (HIWORD(param) != CBN_SELCHANGE)
3239         return ERROR_SUCCESS;
3240
3241     index = SendMessageW( control->hwnd, CB_GETCURSEL, 0, 0 );
3242     if ( index == CB_ERR )
3243     {
3244         ERR("No ComboBox item selected!\n");
3245         return ERROR_FUNCTION_FAILED;
3246     }
3247
3248     SendMessageW( control->hwnd, CB_GETLBTEXT, index, (LPARAM)text );
3249
3250     indirect = control->attributes & msidbControlAttributesIndirect;
3251     prop = msi_dialog_dup_property( dialog, control->property, indirect );
3252
3253     msi_dialog_set_property( dialog->package, prop, text );
3254
3255     msi_free( prop );
3256     return ERROR_SUCCESS;
3257 }
3258
3259 static void msi_dialog_vsc_add_drives( msi_dialog *dialog, msi_control *control )
3260 {
3261     LPWSTR drives, ptr;
3262     DWORD size;
3263
3264     size = GetLogicalDriveStringsW( 0, NULL );
3265     if ( !size ) return;
3266
3267     drives = msi_alloc( (size + 1) * sizeof(WCHAR) );
3268     if ( !drives ) return;
3269
3270     GetLogicalDriveStringsW( size, drives );
3271
3272     ptr = drives;
3273     while (*ptr)
3274     {
3275         SendMessageW( control->hwnd, CB_ADDSTRING, 0, (LPARAM)ptr );
3276         ptr += lstrlenW(ptr) + 1;
3277     }
3278
3279     msi_free( drives );
3280 }
3281
3282 static UINT msi_dialog_volumeselect_combo( msi_dialog *dialog, MSIRECORD *rec )
3283 {
3284     msi_control *control;
3285     LPCWSTR prop;
3286     DWORD style;
3287
3288     /* FIXME: CBS_OWNERDRAWFIXED */
3289     style = WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP |
3290             CBS_DROPDOWNLIST | CBS_SORT | CBS_HASSTRINGS |
3291             WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR;
3292     control = msi_dialog_add_control( dialog, rec, WC_COMBOBOXW, style );
3293     if (!control)
3294         return ERROR_FUNCTION_FAILED;
3295
3296     control->attributes = MSI_RecordGetInteger( rec, 8 );
3297     control->handler = msi_dialog_volsel_handler;
3298     prop = MSI_RecordGetString( rec, 9 );
3299     control->property = msi_dialog_dup_property( dialog, prop, FALSE );
3300
3301     msi_dialog_vsc_add_drives( dialog, control );
3302
3303     return ERROR_SUCCESS;
3304 }
3305
3306 static const struct control_handler msi_dialog_handler[] =
3307 {
3308     { szText, msi_dialog_text_control },
3309     { szPushButton, msi_dialog_button_control },
3310     { szLine, msi_dialog_line_control },
3311     { szBitmap, msi_dialog_bitmap_control },
3312     { szCheckBox, msi_dialog_checkbox_control },
3313     { szScrollableText, msi_dialog_scrolltext_control },
3314     { szComboBox, msi_dialog_combo_control },
3315     { szEdit, msi_dialog_edit_control },
3316     { szMaskedEdit, msi_dialog_maskedit_control },
3317     { szPathEdit, msi_dialog_pathedit_control },
3318     { szProgressBar, msi_dialog_progress_bar },
3319     { szRadioButtonGroup, msi_dialog_radiogroup_control },
3320     { szIcon, msi_dialog_icon_control },
3321     { szSelectionTree, msi_dialog_selection_tree },
3322     { szGroupBox, msi_dialog_group_box },
3323     { szListBox, msi_dialog_list_box },
3324     { szDirectoryCombo, msi_dialog_directory_combo },
3325     { szDirectoryList, msi_dialog_directory_list },
3326     { szVolumeCostList, msi_dialog_volumecost_list },
3327     { szVolumeSelectCombo, msi_dialog_volumeselect_combo },
3328 };
3329
3330 #define NUM_CONTROL_TYPES (sizeof msi_dialog_handler/sizeof msi_dialog_handler[0])
3331
3332 static UINT msi_dialog_create_controls( MSIRECORD *rec, LPVOID param )
3333 {
3334     msi_dialog *dialog = param;
3335     LPCWSTR control_type;
3336     UINT i;
3337
3338     /* find and call the function that can create this type of control */
3339     control_type = MSI_RecordGetString( rec, 3 );
3340     for( i=0; i<NUM_CONTROL_TYPES; i++ )
3341         if (!strcmpiW( msi_dialog_handler[i].control_type, control_type ))
3342             break;
3343     if( i != NUM_CONTROL_TYPES )
3344         msi_dialog_handler[i].func( dialog, rec );
3345     else
3346         ERR("no handler for element type %s\n", debugstr_w(control_type));
3347
3348     return ERROR_SUCCESS;
3349 }
3350
3351 static UINT msi_dialog_fill_controls( msi_dialog *dialog )
3352 {
3353     static const WCHAR query[] = {
3354         'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
3355         'C','o','n','t','r','o','l',' ','W','H','E','R','E',' ',
3356         '`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',0};
3357     UINT r;
3358     MSIQUERY *view;
3359     MSIPACKAGE *package = dialog->package;
3360
3361     TRACE("%p %s\n", dialog, debugstr_w(dialog->name) );
3362
3363     /* query the Control table for all the elements of the control */
3364     r = MSI_OpenQuery( package->db, &view, query, dialog->name );
3365     if( r != ERROR_SUCCESS )
3366     {
3367         ERR("query failed for dialog %s\n", debugstr_w(dialog->name));
3368         return ERROR_INVALID_PARAMETER;
3369     }
3370
3371     r = MSI_IterateRecords( view, 0, msi_dialog_create_controls, dialog );
3372     msiobj_release( &view->hdr );
3373     return r;
3374 }
3375
3376 UINT msi_dialog_reset( msi_dialog *dialog )
3377 {
3378     /* FIXME: should restore the original values of any properties we changed */
3379     return msi_dialog_evaluate_control_conditions( dialog );
3380 }
3381
3382 /* figure out the height of 10 point MS Sans Serif */
3383 static INT msi_dialog_get_sans_serif_height( HWND hwnd )
3384 {
3385     static const WCHAR szSansSerif[] = {
3386         'M','S',' ','S','a','n','s',' ','S','e','r','i','f',0 };
3387     LOGFONTW lf;
3388     TEXTMETRICW tm;
3389     BOOL r;
3390     LONG height = 0;
3391     HFONT hFont, hOldFont;
3392     HDC hdc;
3393
3394     hdc = GetDC( hwnd );
3395     if (hdc)
3396     {
3397         memset( &lf, 0, sizeof lf );
3398         lf.lfHeight = MulDiv(12, GetDeviceCaps(hdc, LOGPIXELSY), 72);
3399         strcpyW( lf.lfFaceName, szSansSerif );
3400         hFont = CreateFontIndirectW(&lf);
3401         if (hFont)
3402         {
3403             hOldFont = SelectObject( hdc, hFont );
3404             r = GetTextMetricsW( hdc, &tm );
3405             if (r)
3406                 height = tm.tmHeight;
3407             SelectObject( hdc, hOldFont );
3408             DeleteObject( hFont );
3409         }
3410         ReleaseDC( hwnd, hdc );
3411     }
3412     return height;
3413 }
3414
3415 /* fetch the associated record from the Dialog table */
3416 static MSIRECORD *msi_get_dialog_record( msi_dialog *dialog )
3417 {
3418     static const WCHAR query[] = {
3419         'S','E','L','E','C','T',' ','*',' ',
3420         'F','R','O','M',' ','D','i','a','l','o','g',' ',
3421         'W','H','E','R','E',' ',
3422            '`','D','i','a','l','o','g','`',' ','=',' ','\'','%','s','\'',0};
3423     MSIPACKAGE *package = dialog->package;
3424     MSIRECORD *rec = NULL;
3425
3426     TRACE("%p %s\n", dialog, debugstr_w(dialog->name) );
3427
3428     rec = MSI_QueryGetRecord( package->db, query, dialog->name );
3429     if( !rec )
3430         WARN("query failed for dialog %s\n", debugstr_w(dialog->name));
3431
3432     return rec;
3433 }
3434
3435 static void msi_dialog_adjust_dialog_pos( msi_dialog *dialog, MSIRECORD *rec, LPRECT pos )
3436 {
3437     static const WCHAR szScreenX[] = {'S','c','r','e','e','n','X',0};
3438     static const WCHAR szScreenY[] = {'S','c','r','e','e','n','Y',0};
3439
3440     UINT xres, yres;
3441     POINT center;
3442     SIZE sz;
3443     LONG style;
3444
3445     center.x = MSI_RecordGetInteger( rec, 2 );
3446     center.y = MSI_RecordGetInteger( rec, 3 );
3447
3448     sz.cx = MSI_RecordGetInteger( rec, 4 );
3449     sz.cy = MSI_RecordGetInteger( rec, 5 );
3450
3451     sz.cx = msi_dialog_scale_unit( dialog, sz.cx );
3452     sz.cy = msi_dialog_scale_unit( dialog, sz.cy );
3453
3454     xres = msi_get_property_int( dialog->package->db, szScreenX, 0 );
3455     yres = msi_get_property_int( dialog->package->db, szScreenY, 0 );
3456
3457     center.x = MulDiv( center.x, xres, 100 );
3458     center.y = MulDiv( center.y, yres, 100 );
3459
3460     /* turn the client pos into the window rectangle */
3461     if (dialog->package->center_x && dialog->package->center_y)
3462     {
3463         pos->left = dialog->package->center_x - sz.cx / 2.0;
3464         pos->right = pos->left + sz.cx;
3465         pos->top = dialog->package->center_y - sz.cy / 2.0;
3466         pos->bottom = pos->top + sz.cy;
3467     }
3468     else
3469     {
3470         pos->left = center.x - sz.cx/2;
3471         pos->right = pos->left + sz.cx;
3472         pos->top = center.y - sz.cy/2;
3473         pos->bottom = pos->top + sz.cy;
3474
3475         /* save the center */
3476         dialog->package->center_x = center.x;
3477         dialog->package->center_y = center.y;
3478     }
3479
3480     dialog->size.cx = sz.cx;
3481     dialog->size.cy = sz.cy;
3482
3483     TRACE("%u %u %u %u\n", pos->left, pos->top, pos->right, pos->bottom);
3484
3485     style = GetWindowLongPtrW( dialog->hwnd, GWL_STYLE );
3486     AdjustWindowRect( pos, style, FALSE );
3487 }
3488
3489 static void msi_dialog_set_tab_order( msi_dialog *dialog, LPCWSTR first )
3490 {
3491     struct list tab_chain;
3492     msi_control *control;
3493     HWND prev = HWND_TOP;
3494
3495     list_init( &tab_chain );
3496     if (!(control = msi_dialog_find_control( dialog, first ))) return;
3497
3498     dialog->hWndFocus = control->hwnd;
3499     while (control)
3500     {
3501         list_remove( &control->entry );
3502         list_add_tail( &tab_chain, &control->entry );
3503         if (!control->tabnext) break;
3504         control = msi_dialog_find_control( dialog, control->tabnext );
3505     }
3506
3507     LIST_FOR_EACH_ENTRY( control, &tab_chain, msi_control, entry )
3508     {
3509         SetWindowPos( control->hwnd, prev, 0, 0, 0, 0,
3510                       SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOREDRAW |
3511                       SWP_NOREPOSITION | SWP_NOSENDCHANGING | SWP_NOSIZE );
3512         prev = control->hwnd;
3513     }
3514
3515     /* put them back on the main list */
3516     list_move_head( &dialog->controls, &tab_chain );
3517 }
3518
3519 static LRESULT msi_dialog_oncreate( HWND hwnd, LPCREATESTRUCTW cs )
3520 {
3521     static const WCHAR df[] = {
3522         'D','e','f','a','u','l','t','U','I','F','o','n','t',0 };
3523     static const WCHAR dfv[] = {
3524         'M','S',' ','S','h','e','l','l',' ','D','l','g',0 };
3525     msi_dialog *dialog = cs->lpCreateParams;
3526     MSIRECORD *rec = NULL;
3527     LPWSTR title = NULL;
3528     RECT pos;
3529
3530     TRACE("%p %p\n", dialog, dialog->package);
3531
3532     dialog->hwnd = hwnd;
3533     SetWindowLongPtrW( hwnd, GWLP_USERDATA, (LONG_PTR) dialog );
3534
3535     rec = msi_get_dialog_record( dialog );
3536     if( !rec )
3537     {
3538         TRACE("No record found for dialog %s\n", debugstr_w(dialog->name));
3539         return -1;
3540     }
3541
3542     dialog->scale = msi_dialog_get_sans_serif_height(dialog->hwnd);
3543
3544     msi_dialog_adjust_dialog_pos( dialog, rec, &pos );
3545
3546     dialog->attributes = MSI_RecordGetInteger( rec, 6 );
3547
3548     dialog->default_font = msi_dup_property( dialog->package->db, df );
3549     if (!dialog->default_font)
3550     {
3551         dialog->default_font = strdupW(dfv);
3552         if (!dialog->default_font) return -1;
3553     }
3554
3555     title = msi_get_deformatted_field( dialog->package, rec, 7 );
3556     SetWindowTextW( hwnd, title );
3557     msi_free( title );
3558
3559     SetWindowPos( hwnd, 0, pos.left, pos.top,
3560                   pos.right - pos.left, pos.bottom - pos.top,
3561                   SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOREDRAW );
3562
3563     msi_dialog_build_font_list( dialog );
3564     msi_dialog_fill_controls( dialog );
3565     msi_dialog_evaluate_control_conditions( dialog );
3566     msi_dialog_set_tab_order( dialog, MSI_RecordGetString( rec, 8 ) );
3567     msiobj_release( &rec->hdr );
3568
3569     return 0;
3570 }
3571
3572 static LRESULT msi_dialog_oncommand( msi_dialog *dialog, WPARAM param, HWND hwnd )
3573 {
3574     msi_control *control = NULL;
3575
3576     TRACE("%p %p %08lx\n", dialog, hwnd, param);
3577
3578     switch (param)
3579     {
3580     case 1: /* enter */
3581         control = msi_dialog_find_control( dialog, dialog->control_default );
3582         break;
3583     case 2: /* escape */
3584         control = msi_dialog_find_control( dialog, dialog->control_cancel );
3585         break;
3586     default: 
3587         control = msi_dialog_find_control_by_hwnd( dialog, hwnd );
3588     }
3589
3590     if( control )
3591     {
3592         if( control->handler )
3593         {
3594             control->handler( dialog, control, param );
3595             msi_dialog_evaluate_control_conditions( dialog );
3596         }
3597     }
3598
3599     return 0;
3600 }
3601
3602 static LRESULT msi_dialog_onnotify( msi_dialog *dialog, LPARAM param )
3603 {
3604     LPNMHDR nmhdr = (LPNMHDR) param;
3605     msi_control *control = msi_dialog_find_control_by_hwnd( dialog, nmhdr->hwndFrom );
3606
3607     TRACE("%p %p\n", dialog, nmhdr->hwndFrom);
3608
3609     if ( control && control->handler )
3610         control->handler( dialog, control, param );
3611
3612     return 0;
3613 }
3614
3615 static void msi_dialog_setfocus( msi_dialog *dialog )
3616 {
3617     HWND hwnd = dialog->hWndFocus;
3618
3619     hwnd = GetNextDlgTabItem( dialog->hwnd, hwnd, TRUE);
3620     hwnd = GetNextDlgTabItem( dialog->hwnd, hwnd, FALSE);
3621     SetFocus( hwnd );
3622     dialog->hWndFocus = hwnd;
3623 }
3624
3625 static LRESULT WINAPI MSIDialog_WndProc( HWND hwnd, UINT msg,
3626                 WPARAM wParam, LPARAM lParam )
3627 {
3628     msi_dialog *dialog = (LPVOID) GetWindowLongPtrW( hwnd, GWLP_USERDATA );
3629
3630     TRACE("0x%04x\n", msg);
3631
3632     switch (msg)
3633     {
3634     case WM_MOVE:
3635         dialog->package->center_x = LOWORD(lParam) + dialog->size.cx / 2.0;
3636         dialog->package->center_y = HIWORD(lParam) + dialog->size.cy / 2.0;
3637         break;
3638
3639     case WM_CREATE:
3640         return msi_dialog_oncreate( hwnd, (LPCREATESTRUCTW)lParam );
3641
3642     case WM_COMMAND:
3643         return msi_dialog_oncommand( dialog, wParam, (HWND)lParam );
3644
3645     case WM_ACTIVATE:
3646         if( LOWORD(wParam) == WA_INACTIVE )
3647             dialog->hWndFocus = GetFocus();
3648         else
3649             msi_dialog_setfocus( dialog );
3650         return 0;
3651
3652     case WM_SETFOCUS:
3653         msi_dialog_setfocus( dialog );
3654         return 0;
3655
3656     /* bounce back to our subclassed static control */
3657     case WM_CTLCOLORSTATIC:
3658         return SendMessageW( (HWND) lParam, WM_CTLCOLORSTATIC, wParam, lParam );
3659
3660     case WM_DESTROY:
3661         dialog->hwnd = NULL;
3662         return 0;
3663     case WM_NOTIFY:
3664         return msi_dialog_onnotify( dialog, lParam );
3665     }
3666     return DefWindowProcW(hwnd, msg, wParam, lParam);
3667 }
3668
3669 static LRESULT WINAPI MSIHiddenWindowProc( HWND hwnd, UINT msg,
3670                 WPARAM wParam, LPARAM lParam )
3671 {
3672     msi_dialog *dialog = (msi_dialog*) lParam;
3673
3674     TRACE("%d %p\n", msg, dialog);
3675
3676     switch (msg)
3677     {
3678     case WM_MSI_DIALOG_CREATE:
3679         return msi_dialog_run_message_loop( dialog );
3680     case WM_MSI_DIALOG_DESTROY:
3681         msi_dialog_destroy( dialog );
3682         return 0;
3683     }
3684     return DefWindowProcW( hwnd, msg, wParam, lParam );
3685 }
3686
3687 static BOOL msi_dialog_register_class( void )
3688 {
3689     WNDCLASSW cls;
3690
3691     ZeroMemory( &cls, sizeof cls );
3692     cls.lpfnWndProc   = MSIDialog_WndProc;
3693     cls.hInstance     = NULL;
3694     cls.hIcon         = LoadIconW(0, (LPWSTR)IDI_APPLICATION);
3695     cls.hCursor       = LoadCursorW(0, (LPWSTR)IDC_ARROW);
3696     cls.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
3697     cls.lpszMenuName  = NULL;
3698     cls.lpszClassName = szMsiDialogClass;
3699
3700     if( !RegisterClassW( &cls ) )
3701         return FALSE;
3702
3703     cls.lpfnWndProc   = MSIHiddenWindowProc;
3704     cls.lpszClassName = szMsiHiddenWindow;
3705
3706     if( !RegisterClassW( &cls ) )
3707         return FALSE;
3708
3709     uiThreadId = GetCurrentThreadId();
3710
3711     hMsiHiddenWindow = CreateWindowW( szMsiHiddenWindow, NULL, WS_OVERLAPPED,
3712                                    0, 0, 100, 100, NULL, NULL, NULL, NULL );
3713     if( !hMsiHiddenWindow )
3714         return FALSE;
3715
3716     return TRUE;
3717 }
3718
3719 /* functions that interface to other modules within MSI */
3720
3721 msi_dialog *msi_dialog_create( MSIPACKAGE* package,
3722                                LPCWSTR szDialogName, msi_dialog *parent,
3723                                msi_dialog_event_handler event_handler )
3724 {
3725     MSIRECORD *rec = NULL;
3726     msi_dialog *dialog;
3727
3728     TRACE("%p %s\n", package, debugstr_w(szDialogName));
3729
3730     if (!hMsiHiddenWindow)
3731         msi_dialog_register_class();
3732
3733     /* allocate the structure for the dialog to use */
3734     dialog = msi_alloc_zero( sizeof *dialog + sizeof(WCHAR)*strlenW(szDialogName) );
3735     if( !dialog )
3736         return NULL;
3737     strcpyW( dialog->name, szDialogName );
3738     dialog->parent = parent;
3739     msiobj_addref( &package->hdr );
3740     dialog->package = package;
3741     dialog->event_handler = event_handler;
3742     dialog->finished = 0;
3743     list_init( &dialog->controls );
3744     list_init( &dialog->fonts );
3745
3746     /* verify that the dialog exists */
3747     rec = msi_get_dialog_record( dialog );
3748     if( !rec )
3749     {
3750         msiobj_release( &package->hdr );
3751         msi_free( dialog );
3752         return NULL;
3753     }
3754     dialog->attributes = MSI_RecordGetInteger( rec, 6 );
3755     dialog->control_default = strdupW( MSI_RecordGetString( rec, 9 ) );
3756     dialog->control_cancel = strdupW( MSI_RecordGetString( rec, 10 ) );
3757     msiobj_release( &rec->hdr );
3758
3759     return dialog;
3760 }
3761
3762 static void msi_process_pending_messages( HWND hdlg )
3763 {
3764     MSG msg;
3765
3766     while( PeekMessageW( &msg, 0, 0, 0, PM_REMOVE ) )
3767     {
3768         if( hdlg && IsDialogMessageW( hdlg, &msg ))
3769             continue;
3770         TranslateMessage( &msg );
3771         DispatchMessageW( &msg );
3772     }
3773 }
3774
3775 void msi_dialog_end_dialog( msi_dialog *dialog )
3776 {
3777     TRACE("%p\n", dialog);
3778     dialog->finished = 1;
3779     PostMessageW(dialog->hwnd, WM_NULL, 0, 0);
3780 }
3781
3782 void msi_dialog_check_messages( HANDLE handle )
3783 {
3784     DWORD r;
3785
3786     /* in threads other than the UI thread, block */
3787     if( uiThreadId != GetCurrentThreadId() )
3788     {
3789         if( handle )
3790             MsgWaitForMultipleObjectsEx( 1, &handle, INFINITE, 0, 0 );
3791         return;
3792     }
3793
3794     /* there's two choices for the UI thread */
3795     while (1)
3796     {
3797         msi_process_pending_messages( NULL );
3798
3799         if( !handle )
3800             break;
3801
3802         /*
3803          * block here until somebody creates a new dialog or
3804          * the handle we're waiting on becomes ready
3805          */
3806         r = MsgWaitForMultipleObjects( 1, &handle, 0, INFINITE, QS_ALLINPUT );
3807         if( r == WAIT_OBJECT_0 )
3808             break;
3809     }
3810 }
3811
3812 UINT msi_dialog_run_message_loop( msi_dialog *dialog )
3813 {
3814     DWORD style;
3815     HWND hwnd;
3816
3817     if( uiThreadId != GetCurrentThreadId() )
3818         return SendMessageW( hMsiHiddenWindow, WM_MSI_DIALOG_CREATE, 0, (LPARAM) dialog );
3819
3820     /* create the dialog window, don't show it yet */
3821     style = WS_OVERLAPPED;
3822     if( dialog->attributes & msidbDialogAttributesVisible )
3823         style |= WS_VISIBLE;
3824
3825     hwnd = CreateWindowW( szMsiDialogClass, dialog->name, style,
3826                      CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
3827                      NULL, NULL, NULL, dialog );
3828     if( !hwnd )
3829     {
3830         ERR("Failed to create dialog %s\n", debugstr_w( dialog->name ));
3831         return ERROR_FUNCTION_FAILED;
3832     }
3833
3834     ShowWindow( hwnd, SW_SHOW );
3835     /* UpdateWindow( hwnd ); - and causes the transparent static controls not to paint */
3836
3837     if( dialog->attributes & msidbDialogAttributesModal )
3838     {
3839         while( !dialog->finished )
3840         {
3841             MsgWaitForMultipleObjects( 0, NULL, 0, INFINITE, QS_ALLINPUT );
3842             msi_process_pending_messages( dialog->hwnd );
3843         }
3844     }
3845     else
3846         return ERROR_IO_PENDING;
3847
3848     return ERROR_SUCCESS;
3849 }
3850
3851 static void msi_dialog_do_preview( msi_dialog *dialog )
3852 {
3853     TRACE("\n");
3854     dialog->attributes |= msidbDialogAttributesVisible;
3855     dialog->attributes &= ~msidbDialogAttributesModal;
3856     msi_dialog_run_message_loop( dialog );
3857 }
3858
3859 void msi_dialog_destroy( msi_dialog *dialog )
3860 {
3861     msi_font *font, *next;
3862
3863     if( uiThreadId != GetCurrentThreadId() )
3864     {
3865         SendMessageW( hMsiHiddenWindow, WM_MSI_DIALOG_DESTROY, 0, (LPARAM) dialog );
3866         return;
3867     }
3868
3869     if( dialog->hwnd )
3870         ShowWindow( dialog->hwnd, SW_HIDE );
3871
3872     if( dialog->hwnd )
3873         DestroyWindow( dialog->hwnd );
3874
3875     /* unsubscribe events */
3876     ControlEvent_CleanupDialogSubscriptions(dialog->package, dialog->name);
3877
3878     /* destroy the list of controls */
3879     while( !list_empty( &dialog->controls ) )
3880     {
3881         msi_control *t;
3882
3883         t = LIST_ENTRY( list_head( &dialog->controls ),
3884                         msi_control, entry );
3885         msi_destroy_control( t );
3886     }
3887
3888     /* destroy the list of fonts */
3889     LIST_FOR_EACH_ENTRY_SAFE( font, next, &dialog->fonts, msi_font, entry )
3890     {
3891         list_remove( &font->entry );
3892         DeleteObject( font->hfont );
3893         msi_free( font );
3894     }
3895     msi_free( dialog->default_font );
3896
3897     msi_free( dialog->control_default );
3898     msi_free( dialog->control_cancel );
3899     msiobj_release( &dialog->package->hdr );
3900     dialog->package = NULL;
3901     msi_free( dialog );
3902 }
3903
3904 void msi_dialog_unregister_class( void )
3905 {
3906     DestroyWindow( hMsiHiddenWindow );
3907     hMsiHiddenWindow = NULL;
3908     UnregisterClassW( szMsiDialogClass, NULL );
3909     UnregisterClassW( szMsiHiddenWindow, NULL );
3910     uiThreadId = 0;
3911 }
3912
3913 static UINT error_dialog_handler(MSIPACKAGE *package, LPCWSTR event,
3914                                  LPCWSTR argument, msi_dialog* dialog)
3915 {
3916     static const WCHAR end_dialog[] = {'E','n','d','D','i','a','l','o','g',0};
3917     static const WCHAR error_abort[] = {'E','r','r','o','r','A','b','o','r','t',0};
3918     static const WCHAR error_cancel[] = {'E','r','r','o','r','C','a','n','c','e','l',0};
3919     static const WCHAR error_no[] = {'E','r','r','o','r','N','o',0};
3920     static const WCHAR result_prop[] = {
3921         'M','S','I','E','r','r','o','r','D','i','a','l','o','g','R','e','s','u','l','t',0
3922     };
3923
3924     if ( strcmpW( event, end_dialog ) )
3925         return ERROR_SUCCESS;
3926
3927     if ( !strcmpW( argument, error_abort ) || !strcmpW( argument, error_cancel ) ||
3928          !strcmpW( argument, error_no ) )
3929     {
3930          msi_set_property( package->db, result_prop, error_abort );
3931     }
3932
3933     ControlEvent_CleanupSubscriptions(package);
3934     msi_dialog_end_dialog( dialog );
3935
3936     return ERROR_SUCCESS;
3937 }
3938
3939 static UINT msi_error_dialog_set_error( MSIPACKAGE *package, LPWSTR error_dialog, LPWSTR error )
3940 {
3941     MSIRECORD * row;
3942
3943     static const WCHAR update[] = 
3944         {'U','P','D','A','T','E',' ','`','C','o','n','t','r','o','l','`',' ',
3945          'S','E','T',' ','`','T','e','x','t','`',' ','=',' ','\'','%','s','\'',' ',
3946          'W','H','E','R','E', ' ','`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',' ',
3947          'A','N','D',' ','`','C','o','n','t','r','o','l','`',' ','=',' ',
3948          '\'','E','r','r','o','r','T','e','x','t','\'',0};
3949
3950     row = MSI_QueryGetRecord( package->db, update, error, error_dialog );
3951     if (!row)
3952         return ERROR_FUNCTION_FAILED;
3953
3954     msiobj_release(&row->hdr);
3955     return ERROR_SUCCESS;
3956 }
3957
3958 UINT msi_spawn_error_dialog( MSIPACKAGE *package, LPWSTR error_dialog, LPWSTR error )
3959 {
3960     msi_dialog *dialog;
3961     WCHAR result[MAX_PATH];
3962     UINT r = ERROR_SUCCESS;
3963     DWORD size = MAX_PATH;
3964     int res;
3965
3966     static const WCHAR pn_prop[] = {'P','r','o','d','u','c','t','N','a','m','e',0};
3967     static const WCHAR title_fmt[] = {'%','s',' ','W','a','r','n','i','n','g',0};
3968     static const WCHAR error_abort[] = {'E','r','r','o','r','A','b','o','r','t',0};
3969     static const WCHAR result_prop[] = {
3970         'M','S','I','E','r','r','o','r','D','i','a','l','o','g','R','e','s','u','l','t',0
3971     };
3972
3973     if ( (msi_get_property_int( package->db, szUILevel, 0 ) & INSTALLUILEVEL_MASK) == INSTALLUILEVEL_NONE )
3974         return ERROR_SUCCESS;
3975
3976     if ( !error_dialog )
3977     {
3978         LPWSTR product_name = msi_dup_property( package->db, pn_prop );
3979         WCHAR title[MAX_PATH];
3980
3981         sprintfW( title, title_fmt, product_name );
3982         res = MessageBoxW( NULL, error, title, MB_OKCANCEL | MB_ICONWARNING );
3983
3984         msi_free( product_name );
3985
3986         if ( res == IDOK )
3987             return ERROR_SUCCESS;
3988         else
3989             return ERROR_FUNCTION_FAILED;
3990     }
3991
3992     r = msi_error_dialog_set_error( package, error_dialog, error );
3993     if ( r != ERROR_SUCCESS )
3994         return r;
3995
3996     dialog = msi_dialog_create( package, error_dialog, package->dialog,
3997                                 error_dialog_handler );
3998     if ( !dialog )
3999         return ERROR_FUNCTION_FAILED;
4000
4001     dialog->finished = FALSE;
4002     r = msi_dialog_run_message_loop( dialog );
4003     if ( r != ERROR_SUCCESS )
4004         goto done;
4005
4006     r = msi_get_property( package->db, result_prop, result, &size );
4007     if ( r != ERROR_SUCCESS)
4008         r = ERROR_SUCCESS;
4009
4010     if ( !strcmpW( result, error_abort ) )
4011         r = ERROR_FUNCTION_FAILED;
4012
4013 done:
4014     msi_dialog_destroy( dialog );
4015
4016     return r;
4017 }
4018
4019 static void MSI_ClosePreview( MSIOBJECTHDR *arg )
4020 {
4021     MSIPREVIEW *preview = (MSIPREVIEW *)arg;
4022     msiobj_release( &preview->package->hdr );
4023 }
4024
4025 static MSIPREVIEW *MSI_EnableUIPreview( MSIDATABASE *db )
4026 {
4027     MSIPREVIEW *preview = NULL;
4028     MSIPACKAGE *package;
4029
4030     package = MSI_CreatePackage( db, NULL );
4031     if (package)
4032     {
4033         preview = alloc_msiobject( MSIHANDLETYPE_PREVIEW, sizeof(MSIPREVIEW), MSI_ClosePreview );
4034         if (preview)
4035         {
4036             preview->package = package;
4037             msiobj_addref( &package->hdr );
4038         }
4039         msiobj_release( &package->hdr );
4040     }
4041     return preview;
4042 }
4043
4044 UINT WINAPI MsiEnableUIPreview( MSIHANDLE hdb, MSIHANDLE *phPreview )
4045 {
4046     MSIDATABASE *db;
4047     MSIPREVIEW *preview;
4048     UINT r = ERROR_FUNCTION_FAILED;
4049
4050     TRACE("%d %p\n", hdb, phPreview);
4051
4052     db = msihandle2msiinfo( hdb, MSIHANDLETYPE_DATABASE );
4053     if (!db)
4054     {
4055         IWineMsiRemoteDatabase *remote_database;
4056
4057         remote_database = (IWineMsiRemoteDatabase *)msi_get_remote( hdb );
4058         if (!remote_database)
4059             return ERROR_INVALID_HANDLE;
4060
4061         *phPreview = 0;
4062
4063         IWineMsiRemoteDatabase_Release( remote_database );
4064         WARN("MsiEnableUIPreview not allowed during a custom action!\n");
4065
4066         return ERROR_FUNCTION_FAILED;
4067     }
4068     preview = MSI_EnableUIPreview( db );
4069     if (preview)
4070     {
4071         *phPreview = alloc_msihandle( &preview->hdr );
4072         msiobj_release( &preview->hdr );
4073         r = ERROR_SUCCESS;
4074         if (!*phPreview)
4075             r = ERROR_NOT_ENOUGH_MEMORY;
4076     }
4077     msiobj_release( &db->hdr );
4078     return r;
4079 }
4080
4081 static UINT preview_event_handler( MSIPACKAGE *package, LPCWSTR event,
4082                                    LPCWSTR argument, msi_dialog *dialog )
4083 {
4084     MESSAGE("Preview dialog event '%s' (arg='%s')\n", debugstr_w(event), debugstr_w(argument));
4085     return ERROR_SUCCESS;
4086 }
4087
4088 static UINT MSI_PreviewDialogW( MSIPREVIEW *preview, LPCWSTR szDialogName )
4089 {
4090     msi_dialog *dialog = NULL;
4091     UINT r = ERROR_SUCCESS;
4092
4093     if (preview->dialog)
4094         msi_dialog_destroy( preview->dialog );
4095
4096     /* an empty name means we should just destroy the current preview dialog */
4097     if (szDialogName)
4098     {
4099         dialog = msi_dialog_create( preview->package, szDialogName, NULL, preview_event_handler );
4100         if (dialog)
4101             msi_dialog_do_preview( dialog );
4102         else
4103             r = ERROR_FUNCTION_FAILED;
4104     }
4105     preview->dialog = dialog;
4106     return r;
4107 }
4108
4109 UINT WINAPI MsiPreviewDialogW( MSIHANDLE hPreview, LPCWSTR szDialogName )
4110 {
4111     MSIPREVIEW *preview;
4112     UINT r;
4113
4114     TRACE("%d %s\n", hPreview, debugstr_w(szDialogName));
4115
4116     preview = msihandle2msiinfo( hPreview, MSIHANDLETYPE_PREVIEW );
4117     if (!preview)
4118         return ERROR_INVALID_HANDLE;
4119
4120     r = MSI_PreviewDialogW( preview, szDialogName );
4121     msiobj_release( &preview->hdr );
4122     return r;
4123 }
4124
4125 UINT WINAPI MsiPreviewDialogA( MSIHANDLE hPreview, LPCSTR szDialogName )
4126 {
4127     UINT r;
4128     LPWSTR strW = NULL;
4129
4130     TRACE("%d %s\n", hPreview, debugstr_a(szDialogName));
4131
4132     if (szDialogName)
4133     {
4134         strW = strdupAtoW( szDialogName );
4135         if (!strW)
4136             return ERROR_OUTOFMEMORY;
4137     }
4138     r = MsiPreviewDialogW( hPreview, strW );
4139     msi_free( strW );
4140     return r;
4141 }
4142
4143 UINT WINAPI MsiPreviewBillboardW( MSIHANDLE hPreview, LPCWSTR szControlName, LPCWSTR szBillboard )
4144 {
4145     FIXME("%d %s %s\n", hPreview, debugstr_w(szControlName), debugstr_w(szBillboard));
4146     return ERROR_CALL_NOT_IMPLEMENTED;
4147 }
4148
4149 UINT WINAPI MsiPreviewBillboardA( MSIHANDLE hPreview, LPCSTR szControlName, LPCSTR szBillboard )
4150 {
4151     FIXME("%d %s %s\n", hPreview, debugstr_a(szControlName), debugstr_a(szBillboard));
4152     return ERROR_CALL_NOT_IMPLEMENTED;
4153 }