shlwapi: Implement SHSendMessageBroadcastA/W.
[wine] / dlls / msi / dialog.c
index ebdfb7d..2dcb868 100644 (file)
@@ -15,7 +15,7 @@
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
  */
 
 #define COBJMACROS
@@ -26,9 +26,9 @@
 
 #include "windef.h"
 #include "winbase.h"
+#include "wingdi.h"
 #include "winuser.h"
 #include "winnls.h"
-#include "wingdi.h"
 #include "msi.h"
 #include "msipriv.h"
 #include "msidefs.h"
 #include "olectl.h"
 #include "richedit.h"
 #include "commctrl.h"
+#include "winreg.h"
+#include "shlwapi.h"
 
 #include "wine/debug.h"
 #include "wine/unicode.h"
 
-#include "action.h"
-
 WINE_DEFAULT_DEBUG_CHANNEL(msi);
 
 
@@ -50,18 +50,24 @@ extern HINSTANCE msi_hInstance;
 struct msi_control_tag;
 typedef struct msi_control_tag msi_control;
 typedef UINT (*msi_handler)( msi_dialog *, msi_control *, WPARAM );
+typedef void (*msi_update)( msi_dialog *, msi_control * );
 
 struct msi_control_tag
 {
     struct list entry;
     HWND hwnd;
     msi_handler handler;
+    msi_update update;
     LPWSTR property;
     LPWSTR value;
     HBITMAP hBitmap;
     HICON hIcon;
     LPWSTR tabnext;
+    LPWSTR type;
     HMODULE hDll;
+    float progress_current;
+    float progress_max;
+    DWORD attributes;
     WCHAR name[1];
 };
 
@@ -69,16 +75,19 @@ typedef struct msi_font_tag
 {
     struct msi_font_tag *next;
     HFONT hfont;
+    COLORREF color;
     WCHAR name[1];
 } msi_font;
 
 struct msi_dialog_tag
 {
     MSIPACKAGE *package;
+    msi_dialog *parent;
     msi_dialog_event_handler event_handler;
     BOOL finished;
     INT scale;
     DWORD attributes;
+    SIZE size;
     HWND hwnd;
     LPWSTR default_font;
     msi_font *font_list;
@@ -101,12 +110,13 @@ typedef struct
     msi_dialog* dialog;
     msi_control *parent;
     DWORD       attributes;
+    LPWSTR      propval;
 } radio_button_group_descr;
 
-const WCHAR szMsiDialogClass[] = {
+static const WCHAR szMsiDialogClass[] = {
     'M','s','i','D','i','a','l','o','g','C','l','o','s','e','C','l','a','s','s',0
 };
-const WCHAR szMsiHiddenWindow[] = {
+static const WCHAR szMsiHiddenWindow[] = {
     'M','s','i','H','i','d','d','e','n','W','i','n','d','o','w',0 };
 static const WCHAR szStatic[] = { 'S','t','a','t','i','c',0 };
 static const WCHAR szButton[] = { 'B','U','T','T','O','N', 0 };
@@ -130,15 +140,24 @@ static const WCHAR szRadioButtonGroup[] = {
 static const WCHAR szIcon[] = { 'I','c','o','n',0 };
 static const WCHAR szSelectionTree[] = {
     'S','e','l','e','c','t','i','o','n','T','r','e','e',0 };
+static const WCHAR szGroupBox[] = { 'G','r','o','u','p','B','o','x',0 };
+static const WCHAR szListBox[] = { 'L','i','s','t','B','o','x',0 };
+static const WCHAR szDirectoryCombo[] = { 'D','i','r','e','c','t','o','r','y','C','o','m','b','o',0 };
+static const WCHAR szDirectoryList[] = { 'D','i','r','e','c','t','o','r','y','L','i','s','t',0 };
+static const WCHAR szVolumeCostList[] = { 'V','o','l','u','m','e','C','o','s','t','L','i','s','t',0 };
+static const WCHAR szVolumeSelectCombo[] = { 'V','o','l','u','m','e','S','e','l','e','c','t','C','o','m','b','o',0 };
+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};
+static const WCHAR szSelectionPath[] = {'S','e','l','e','c','t','i','o','n','P','a','t','h',0};
+static const WCHAR szProperty[] = {'P','r','o','p','e','r','t','y',0};
 
 static UINT msi_dialog_checkbox_handler( msi_dialog *, msi_control *, WPARAM );
 static void msi_dialog_checkbox_sync_state( msi_dialog *, msi_control * );
 static UINT msi_dialog_button_handler( msi_dialog *, msi_control *, WPARAM );
 static UINT msi_dialog_edit_handler( msi_dialog *, msi_control *, WPARAM );
-static UINT msi_dialog_radiogroup_handler( msi_dialog *, msi_control *, WPARAM param );
+static UINT msi_dialog_radiogroup_handler( msi_dialog *, msi_control *, WPARAM );
 static UINT msi_dialog_evaluate_control_conditions( msi_dialog *dialog );
 static LRESULT WINAPI MSIRadioGroup_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
-
+static MSIFEATURE *msi_seltree_get_selected_feature( msi_control *control );
 
 /* dialog sequencing */
 
@@ -159,16 +178,34 @@ static msi_control *msi_dialog_find_control( msi_dialog *dialog, LPCWSTR name )
 
     if( !name )
         return NULL;
+    if( !dialog->hwnd )
+        return NULL;
     LIST_FOR_EACH_ENTRY( control, &dialog->controls, msi_control, entry )
         if( !strcmpW( control->name, name ) ) /* FIXME: case sensitive? */
             return control;
     return NULL;
 }
 
+static msi_control *msi_dialog_find_control_by_type( msi_dialog *dialog, LPCWSTR type )
+{
+    msi_control *control;
+
+    if( !type )
+        return NULL;
+    if( !dialog->hwnd )
+        return NULL;
+    LIST_FOR_EACH_ENTRY( control, &dialog->controls, msi_control, entry )
+        if( !strcmpW( control->type, type ) ) /* FIXME: case sensitive? */
+            return control;
+    return NULL;
+}
+
 static msi_control *msi_dialog_find_control_by_hwnd( msi_dialog *dialog, HWND hwnd )
 {
     msi_control *control;
 
+    if( !dialog->hwnd )
+        return NULL;
     LIST_FOR_EACH_ENTRY( control, &dialog->controls, msi_control, entry )
         if( hwnd == control->hwnd )
             return control;
@@ -185,34 +222,64 @@ static LPWSTR msi_get_deformatted_field( MSIPACKAGE *package, MSIRECORD *rec, in
     return ret;
 }
 
+static LPWSTR msi_dialog_dup_property( msi_dialog *dialog, LPCWSTR property, BOOL indirect )
+{
+    LPWSTR prop = NULL;
+
+    if (!property)
+        return NULL;
+
+    if (indirect)
+        prop = msi_dup_property( dialog->package, property );
+
+    if (!prop)
+        prop = strdupW( property );
+
+    return prop;
+}
+
+msi_dialog *msi_dialog_get_parent( msi_dialog *dialog )
+{
+    return dialog->parent;
+}
+
+LPWSTR msi_dialog_get_name( msi_dialog *dialog )
+{
+    return dialog->name;
+}
+
 /*
  * msi_dialog_get_style
  *
  * Extract the {\style} string from the front of the text to display and
- *  update the pointer.
+ * update the pointer.  Only the last style in a list is applied.
  */
 static LPWSTR msi_dialog_get_style( LPCWSTR p, LPCWSTR *rest )
 {
-    LPWSTR ret = NULL;
-    LPCWSTR q, i;
+    LPWSTR ret;
+    LPCWSTR q, i, first;
     DWORD len;
 
+    q = NULL;
     *rest = p;
     if( !p )
-        return ret;
-    if( *p++ != '{' )
-        return ret;
-    q = strchrW( p, '}' );
-    if( !q )
-        return ret;
-    if( *p == '\\' || *p == '&' )
-        p++;
+        return NULL;
+
+    while ((first = strchrW( p, '{' )) && (q = strchrW( first + 1, '}' )))
+    {
+        p = first + 1;
+        if( *p != '\\' && *p != '&' )
+            return NULL;
+
+        /* little bit of sanity checking to stop us getting confused with RTF */
+        for( i=++p; i<q; i++ )
+            if( *i == '}' || *i == '\\' )
+                return NULL;
+    }
+
+    if (!p || !q)
+        return NULL;
 
-    /* little bit of sanity checking to stop us getting confused with RTF */
-    for( i=p; i<q; i++ )
-        if( *i == '}' || *i == '\\' )
-            return ret;
-    
     *rest = ++q;
     len = q - p;
 
@@ -240,6 +307,8 @@ static UINT msi_dialog_add_font( MSIRECORD *rec, LPVOID param )
     font->next = dialog->font_list;
     dialog->font_list = font;
 
+    font->color = MSI_RecordGetInteger( rec, 4 );
+
     memset( &lf, 0, sizeof lf );
     face = MSI_RecordGetString( rec, 2 );
     lf.lfHeight = MSI_RecordGetInteger( rec, 3 );
@@ -313,6 +382,23 @@ static UINT msi_dialog_build_font_list( msi_dialog *dialog )
     return r;
 }
 
+static void msi_destroy_control( msi_control *t )
+{
+    list_remove( &t->entry );
+    /* leave dialog->hwnd - destroying parent destroys child windows */
+    msi_free( t->property );
+    msi_free( t->value );
+    if( t->hBitmap )
+        DeleteObject( t->hBitmap );
+    if( t->hIcon )
+        DestroyIcon( t->hIcon );
+    msi_free( t->tabnext );
+    msi_free( t->type );
+    if (t->hDll)
+        FreeLibrary( t->hDll );
+    msi_free( t );
+}
+
 static msi_control *msi_dialog_create_window( msi_dialog *dialog,
                 MSIRECORD *rec, DWORD exstyle, LPCWSTR szCls, LPCWSTR name, LPCWSTR text,
                 DWORD style, HWND parent )
@@ -325,15 +411,22 @@ static msi_control *msi_dialog_create_window( msi_dialog *dialog,
     style |= WS_CHILD;
 
     control = msi_alloc( sizeof *control + strlenW(name)*sizeof(WCHAR) );
+    if (!control)
+        return NULL;
+
     strcpyW( control->name, name );
     list_add_head( &dialog->controls, &control->entry );
     control->handler = NULL;
+    control->update = NULL;
     control->property = NULL;
     control->value = NULL;
     control->hBitmap = NULL;
     control->hIcon = NULL;
     control->hDll = NULL;
     control->tabnext = strdupW( MSI_RecordGetString( rec, 11) );
+    control->type = strdupW( MSI_RecordGetString( rec, 3 ) );
+    control->progress_current = 0;
+    control->progress_max = 100;
 
     x = MSI_RecordGetInteger( rec, 4 );
     y = MSI_RecordGetInteger( rec, 5 );
@@ -366,6 +459,24 @@ static msi_control *msi_dialog_create_window( msi_dialog *dialog,
     return control;
 }
 
+static LPWSTR msi_dialog_get_uitext( msi_dialog *dialog, LPCWSTR key )
+{
+    MSIRECORD *rec;
+    LPWSTR text;
+
+    static const WCHAR query[] = {
+        's','e','l','e','c','t',' ','*',' ',
+        'f','r','o','m',' ','`','U','I','T','e','x','t','`',' ',
+        'w','h','e','r','e',' ','`','K','e','y','`',' ','=',' ','\'','%','s','\'',0
+    };
+
+    rec = MSI_QueryGetRecord( dialog->package->db, query, key );
+    if (!rec) return NULL;
+    text = strdupW( MSI_RecordGetString( rec, 2 ) );
+    msiobj_release( &rec->hdr );
+    return text;
+}
+
 static MSIRECORD *msi_get_binary_record( MSIDATABASE *db, LPCWSTR name )
 {
     static const WCHAR query[] = {
@@ -424,10 +535,10 @@ static HANDLE msi_load_image( MSIDATABASE *db, LPCWSTR name, UINT type,
         if( r == ERROR_SUCCESS )
         {
             himage = LoadImageW( 0, tmp, type, cx, cy, flags );
-            DeleteFileW( tmp );
         }
         msiobj_release( &rec->hdr );
     }
+    DeleteFileW( tmp );
 
     msi_free( tmp );
     return himage;
@@ -456,6 +567,16 @@ static HICON msi_load_icon( MSIDATABASE *db, LPCWSTR text, UINT attributes )
     return msi_load_image( db, text, IMAGE_ICON, cx, cy, flags );
 }
 
+static void msi_dialog_update_controls( msi_dialog *dialog, LPCWSTR property )
+{
+    msi_control *control;
+
+    LIST_FOR_EACH_ENTRY( control, &dialog->controls, msi_control, entry )
+    {
+        if ( !lstrcmpW( control->property, property ) && control->update )
+            control->update( dialog, control );
+    }
+}
 
 /* called from the Control Event subscription code */
 void msi_dialog_handle_event( msi_dialog* dialog, LPCWSTR control, 
@@ -465,6 +586,8 @@ void msi_dialog_handle_event( msi_dialog* dialog, LPCWSTR control,
     LPCWSTR font_text, text = NULL;
     LPWSTR font;
 
+    static const WCHAR empty[] = {0};
+
     ctrl = msi_dialog_find_control( dialog, control );
     if (!ctrl)
         return;
@@ -472,17 +595,51 @@ void msi_dialog_handle_event( msi_dialog* dialog, LPCWSTR control,
     {
         font_text = MSI_RecordGetString( rec , 1 );
         font = msi_dialog_get_style( font_text, &text );
+        if (!text) text = empty;
         SetWindowTextW( ctrl->hwnd, text );
         msi_free( font );
         msi_dialog_check_messages( NULL );
     }
     else if( !lstrcmpW(attribute, szProgress) )
     {
-        /* FIXME: should forward to progress bar */
-        static int display_fixme = 1;
-        if (display_fixme)
-            FIXME("Attribute %s not being set\n", debugstr_w(attribute));
-        display_fixme = 0;
+        DWORD func, val;
+
+        func = MSI_RecordGetInteger( rec , 1 );
+        val = MSI_RecordGetInteger( rec , 2 );
+
+        switch (func)
+        {
+        case 0: /* init */
+            ctrl->progress_max = val;
+            ctrl->progress_current = 0;
+            SendMessageW(ctrl->hwnd, PBM_SETRANGE, 0, MAKELPARAM(0,100));
+            SendMessageW(ctrl->hwnd, PBM_SETPOS, 0, 0);
+            break;
+        case 1: /* FIXME: not sure what this is supposed to do */
+            break;
+        case 2: /* move */
+            ctrl->progress_current += val;
+            SendMessageW(ctrl->hwnd, PBM_SETPOS, 100*(ctrl->progress_current/ctrl->progress_max), 0);
+            break;
+        default:
+            ERR("Unknown progress message %d\n", func);
+            break;
+        }
+    }
+    else if ( !lstrcmpW(attribute, szProperty) )
+    {
+        MSIFEATURE *feature = msi_seltree_get_selected_feature( ctrl );
+        MSI_SetPropertyW( dialog->package, ctrl->property, feature->Directory );
+    }
+    else if ( !lstrcmpW(attribute, szSelectionPath) )
+    {
+        LPWSTR prop = msi_dialog_dup_property( dialog, ctrl->property, TRUE );
+        LPWSTR path;
+        if (!prop) return;
+        path = msi_dup_property( dialog->package, prop );
+        SetWindowTextW( ctrl->hwnd, path );
+        msi_free(prop);
+        msi_free(path);
     }
     else
     {
@@ -493,7 +650,7 @@ void msi_dialog_handle_event( msi_dialog* dialog, LPCWSTR control,
 
 static void msi_dialog_map_events(msi_dialog* dialog, LPCWSTR control)
 {
-    static WCHAR Query[] = {
+    static const WCHAR Query[] = {
         'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
          '`','E','v','e','n','t','M','a','p','p','i','n','g','`',' ',
         'W','H','E','R','E',' ',
@@ -510,7 +667,7 @@ static void msi_dialog_map_events(msi_dialog* dialog, LPCWSTR control)
 
     event = MSI_RecordGetString( row, 3 );
     attribute = MSI_RecordGetString( row, 4 );
-    ControlEvent_SubscribeToEvent( dialog->package, event, control, attribute );
+    ControlEvent_SubscribeToEvent( dialog->package, dialog, event, control, attribute );
     msiobj_release( &row->hdr );
 }
 
@@ -540,6 +697,7 @@ static msi_control *msi_dialog_add_control( msi_dialog *dialog,
 
 struct msi_text_info
 {
+    msi_font *font;
     WNDPROC oldproc;
     DWORD attributes;
 };
@@ -565,7 +723,7 @@ MSIText_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
     struct msi_text_info *info;
     LRESULT r = 0;
 
-    TRACE("%p %04x %08x %08lx\n", hWnd, msg, wParam, lParam);
+    TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam);
 
     info = GetPropW(hWnd, szButtonData);
 
@@ -577,6 +735,8 @@ MSIText_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
     }
 
     r = CallWindowProcW(info->oldproc, hWnd, msg, wParam, lParam);
+    if ( info->font )
+        SetTextColor( (HDC)wParam, info->font->color );
 
     switch( msg )
     {
@@ -596,6 +756,8 @@ static UINT msi_dialog_text_control( msi_dialog *dialog, MSIRECORD *rec )
 {
     msi_control *control;
     struct msi_text_info *info;
+    LPCWSTR text, ptr, prop;
+    LPWSTR font_name;
 
     TRACE("%p %p\n", dialog, rec);
 
@@ -607,6 +769,15 @@ static UINT msi_dialog_text_control( msi_dialog *dialog, MSIRECORD *rec )
     if( !info )
         return ERROR_SUCCESS;
 
+    control->attributes = MSI_RecordGetInteger( rec, 8 );
+    prop = MSI_RecordGetString( rec, 9 );
+    control->property = msi_dialog_dup_property( dialog, prop, FALSE );
+
+    text = MSI_RecordGetString( rec, 10 );
+    font_name = msi_dialog_get_style( text, &ptr );
+    info->font = ( font_name ) ? msi_dialog_find_font( dialog, font_name ) : NULL;
+    msi_free( font_name );
+
     info->attributes = MSI_RecordGetInteger( rec, 8 );
     if( info->attributes & msidbControlAttributesTransparent )
         SetWindowLongPtrW( control->hwnd, GWL_EXSTYLE, WS_EX_TRANSPARENT );
@@ -615,6 +786,9 @@ static UINT msi_dialog_text_control( msi_dialog *dialog, MSIRECORD *rec )
                                           (LONG_PTR)MSIText_WndProc );
     SetPropW( control->hwnd, szButtonData, info );
 
+    ControlEvent_SubscribeToEvent( dialog->package, dialog,
+                                   szSelectionPath, control->name, szSelectionPath );
+
     return ERROR_SUCCESS;
 }
 
@@ -651,7 +825,7 @@ static LPWSTR msi_get_checkbox_value( msi_dialog *dialog, LPCWSTR prop )
 {
     static const WCHAR query[] = {
         'S','E','L','E','C','T',' ','*',' ',
-        'F','R','O','M',' ','`','C','h','e','c','k','B','o','x',' ','`',
+        'F','R','O','M',' ','`','C','h','e','c','k','B','o','x','`',' ',
         'W','H','E','R','E',' ',
         '`','P','r','o','p','e','r','t','y','`',' ','=',' ',
         '\'','%','s','\'',0
@@ -694,6 +868,7 @@ static UINT msi_dialog_checkbox_control( msi_dialog *dialog, MSIRECORD *rec )
     control = msi_dialog_add_control( dialog, rec, szButton,
                                 BS_CHECKBOX | BS_MULTILINE | WS_TABSTOP );
     control->handler = msi_dialog_checkbox_handler;
+    control->update = msi_dialog_checkbox_sync_state;
     prop = MSI_RecordGetString( rec, 9 );
     if( prop )
     {
@@ -709,9 +884,60 @@ static UINT msi_dialog_checkbox_control( msi_dialog *dialog, MSIRECORD *rec )
 
 static UINT msi_dialog_line_control( msi_dialog *dialog, MSIRECORD *rec )
 {
+    DWORD attributes;
+    LPCWSTR name;
+    DWORD style, exstyle = 0;
+    DWORD x, y, width, height;
+    msi_control *control;
+
     TRACE("%p %p\n", dialog, rec);
 
-    msi_dialog_add_control( dialog, rec, szStatic, SS_ETCHEDHORZ | SS_SUNKEN );
+    style = WS_CHILD | SS_ETCHEDHORZ | SS_SUNKEN;
+
+    name = MSI_RecordGetString( rec, 2 );
+    attributes = MSI_RecordGetInteger( rec, 8 );
+
+    if( attributes & msidbControlAttributesVisible )
+        style |= WS_VISIBLE;
+    if( ~attributes & msidbControlAttributesEnabled )
+        style |= WS_DISABLED;
+    if( attributes & msidbControlAttributesSunken )
+        exstyle |= WS_EX_CLIENTEDGE;
+
+    msi_dialog_map_events(dialog, name);
+
+    control = msi_alloc( sizeof(*control) + strlenW(name) * sizeof(WCHAR) );
+    if (!control)
+        return ERROR_OUTOFMEMORY;
+
+    strcpyW( control->name, name );
+    list_add_head( &dialog->controls, &control->entry );
+    control->handler = NULL;
+    control->property = NULL;
+    control->value = NULL;
+    control->hBitmap = NULL;
+    control->hIcon = NULL;
+    control->hDll = NULL;
+    control->tabnext = strdupW( MSI_RecordGetString( rec, 11) );
+    control->type = strdupW( MSI_RecordGetString( rec, 3 ) );
+    control->progress_current = 0;
+    control->progress_max = 100;
+
+    x = MSI_RecordGetInteger( rec, 4 );
+    y = MSI_RecordGetInteger( rec, 5 );
+    width = MSI_RecordGetInteger( rec, 6 );
+
+    x = msi_dialog_scale_unit( dialog, x );
+    y = msi_dialog_scale_unit( dialog, y );
+    width = msi_dialog_scale_unit( dialog, width );
+    height = 2; /* line is exactly 2 units in height */
+
+    control->hwnd = CreateWindowExW( exstyle, szStatic, NULL, style,
+                          x, y, width, height, dialog->hwnd, NULL, NULL, NULL );
+
+    TRACE("Dialog %s control %s hwnd %p\n",
+           debugstr_w(dialog->name), debugstr_w(name), control->hwnd );
+
     return ERROR_SUCCESS;
 }
 
@@ -730,7 +956,7 @@ MSIScrollText_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
     struct msi_scrolltext_info *info;
     HRESULT r;
 
-    TRACE("%p %04x %08x %08lx\n", hWnd, msg, wParam, lParam);
+    TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam);
 
     info = GetPropW( hWnd, szButtonData );
 
@@ -768,7 +994,7 @@ msi_richedit_stream_in( DWORD_PTR arg, LPBYTE buffer, LONG count, LONG *pcb )
     *pcb = count;
     info->offset += count;
 
-    TRACE("%ld/%ld\n", info->offset, info->length);
+    TRACE("%d/%d\n", info->offset, info->length);
 
     return 0;
 }
@@ -799,6 +1025,7 @@ static UINT msi_dialog_scrolltext_control( msi_dialog *dialog, MSIRECORD *rec )
     struct msi_scrolltext_info *info;
     msi_control *control;
     HMODULE hRichedit;
+    LPCWSTR text;
     DWORD style;
 
     info = msi_alloc( sizeof *info );
@@ -828,7 +1055,9 @@ static UINT msi_dialog_scrolltext_control( msi_dialog *dialog, MSIRECORD *rec )
     SetPropW( control->hwnd, szButtonData, info );
 
     /* add the text into the richedit */
-    msi_scrolltext_add_text( control, MSI_RecordGetString( rec, 10 ) );
+    text = MSI_RecordGetString( rec, 10 );
+    if (text)
+        msi_scrolltext_add_text( control, text );
 
     return ERROR_SUCCESS;
 }
@@ -960,24 +1189,57 @@ static UINT msi_dialog_icon_control( msi_dialog *dialog, MSIRECORD *rec )
 static UINT msi_dialog_combo_control( msi_dialog *dialog, MSIRECORD *rec )
 {
     static const WCHAR szCombo[] = { 'C','O','M','B','O','B','O','X',0 };
+    DWORD attributes, style;
+
+    style = CBS_AUTOHSCROLL | WS_TABSTOP | WS_GROUP | WS_CHILD;
+    attributes = MSI_RecordGetInteger( rec, 8 );
+    if ( ~attributes & msidbControlAttributesSorted)
+        style |= CBS_SORT;
+    if ( attributes & msidbControlAttributesComboList)
+        style |= CBS_DROPDOWNLIST;
+    else
+        style |= CBS_DROPDOWN;
 
-    msi_dialog_add_control( dialog, rec, szCombo,
-                            SS_BITMAP | SS_LEFT | SS_CENTERIMAGE );
+    msi_dialog_add_control( dialog, rec, szCombo, style );
     return ERROR_SUCCESS;
 }
 
+/* length of 2^32 + 1 */
+#define MAX_NUM_DIGITS 11
+
 static UINT msi_dialog_edit_control( msi_dialog *dialog, MSIRECORD *rec )
 {
     msi_control *control;
-    LPCWSTR prop;
-    LPWSTR val;
+    LPCWSTR prop, text;
+    LPWSTR val, begin, end;
+    WCHAR num[MAX_NUM_DIGITS];
+    DWORD limit;
 
     control = msi_dialog_add_control( dialog, rec, szEdit,
-                                      WS_BORDER | WS_TABSTOP );
+                                      WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL );
     control->handler = msi_dialog_edit_handler;
+
+    text = MSI_RecordGetString( rec, 10 );
+    if ( text )
+    {
+        begin = strchrW( text, '{' );
+        end = strchrW( text, '}' );
+
+        if ( begin && end && end > begin &&
+             begin[0] >= '0' && begin[0] <= '9' &&
+             end - begin < MAX_NUM_DIGITS)
+        {
+            lstrcpynW( num, begin + 1, end - begin );
+            limit = atolW( num );
+
+            SendMessageW( control->hwnd, EM_SETLIMITTEXT, limit, 0 );
+        }
+    }
+
     prop = MSI_RecordGetString( rec, 9 );
     if( prop )
         control->property = strdupW( prop );
+
     val = msi_dup_property( dialog->package, control->property );
     SetWindowTextW( control->hwnd, val );
     msi_free( val );
@@ -986,7 +1248,7 @@ static UINT msi_dialog_edit_control( msi_dialog *dialog, MSIRECORD *rec )
 
 /******************** Masked Edit ********************************************/
 
-#define MASK_MAX_GROUPS 10
+#define MASK_MAX_GROUPS 20
 
 struct msi_mask_group
 {
@@ -1091,7 +1353,7 @@ MSIMaskedEdit_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
     struct msi_maskedit_info *info;
     HRESULT r;
 
-    TRACE("%p %04x %08x %08lx\n", hWnd, msg, wParam, lParam);
+    TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam);
 
     info = GetPropW(hWnd, szButtonData);
 
@@ -1315,10 +1577,149 @@ static UINT msi_dialog_progress_bar( msi_dialog *dialog, MSIRECORD *rec )
 
 /******************** Path Edit ********************************************/
 
+struct msi_pathedit_info
+{
+    msi_dialog *dialog;
+    msi_control *control;
+    WNDPROC oldproc;
+};
+
+static LPWSTR msi_get_window_text( HWND hwnd )
+{
+    UINT sz, r;
+    LPWSTR buf;
+
+    sz = 0x20;
+    buf = msi_alloc( sz*sizeof(WCHAR) );
+    while ( buf )
+    {
+        r = GetWindowTextW( hwnd, buf, sz );
+        if ( r < (sz - 1) )
+            break;
+        sz *= 2;
+        buf = msi_realloc( buf, sz*sizeof(WCHAR) );
+    }
+
+    return buf;
+}
+
+static void msi_dialog_update_pathedit( msi_dialog *dialog, msi_control *control )
+{
+    LPWSTR prop, path;
+    BOOL indirect;
+
+    if (!control && !(control = msi_dialog_find_control_by_type( dialog, szPathEdit )))
+       return;
+
+    indirect = control->attributes & msidbControlAttributesIndirect;
+    prop = msi_dialog_dup_property( dialog, control->property, indirect );
+    path = msi_dialog_dup_property( dialog, prop, TRUE );
+
+    SetWindowTextW( control->hwnd, path );
+    SendMessageW( control->hwnd, EM_SETSEL, 0, -1 );
+
+    msi_free( path );
+    msi_free( prop );
+}
+
+/* FIXME: test when this should fail */
+static BOOL msi_dialog_verify_path( LPWSTR path )
+{
+    if ( !lstrlenW( path ) )
+        return FALSE;
+
+    if ( PathIsRelativeW( path ) )
+        return FALSE;
+
+    return TRUE;
+}
+
+/* returns TRUE if the path is valid, FALSE otherwise */
+static BOOL msi_dialog_onkillfocus( msi_dialog *dialog, msi_control *control )
+{
+    LPWSTR buf, prop;
+    BOOL indirect;
+    BOOL valid;
+
+    indirect = control->attributes & msidbControlAttributesIndirect;
+    prop = msi_dialog_dup_property( dialog, control->property, indirect );
+
+    buf = msi_get_window_text( control->hwnd );
+
+    if ( !msi_dialog_verify_path( buf ) )
+    {
+        /* FIXME: display an error message box */
+        ERR("Invalid path %s\n", debugstr_w( buf ));
+        valid = FALSE;
+        SetFocus( control->hwnd );
+    }
+    else
+    {
+        valid = TRUE;
+        MSI_SetPropertyW( dialog->package, prop, buf );
+    }
+
+    msi_dialog_update_pathedit( dialog, control );
+
+    TRACE("edit %s contents changed, set %s\n", debugstr_w(control->name),
+          debugstr_w(prop));
+
+    msi_free( buf );
+    msi_free( prop );
+
+    return valid;
+}
+
+static LRESULT WINAPI MSIPathEdit_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+    struct msi_pathedit_info *info = GetPropW(hWnd, szButtonData);
+    LRESULT r = 0;
+
+    TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam);
+
+    if ( msg == WM_KILLFOCUS )
+    {
+        /* if the path is invalid, don't handle this message */
+        if ( !msi_dialog_onkillfocus( info->dialog, info->control ) )
+            return 0;
+    }
+
+    r = CallWindowProcW(info->oldproc, hWnd, msg, wParam, lParam);
+
+    if ( msg == WM_NCDESTROY )
+    {
+        msi_free( info );
+        RemovePropW( hWnd, szButtonData );
+    }
+
+    return r;
+}
+
 static UINT msi_dialog_pathedit_control( msi_dialog *dialog, MSIRECORD *rec )
 {
-    FIXME("not implemented properly\n");
-    return msi_dialog_edit_control( dialog, rec );
+    struct msi_pathedit_info *info;
+    msi_control *control;
+    LPCWSTR prop;
+
+    info = msi_alloc( sizeof *info );
+    if (!info)
+        return ERROR_FUNCTION_FAILED;
+
+    control = msi_dialog_add_control( dialog, rec, szEdit,
+                                      WS_BORDER | WS_TABSTOP );
+    control->attributes = MSI_RecordGetInteger( rec, 8 );
+    prop = MSI_RecordGetString( rec, 9 );
+    control->property = msi_dialog_dup_property( dialog, prop, FALSE );
+
+    info->dialog = dialog;
+    info->control = control;
+    info->oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
+                                                 (LONG_PTR)MSIPathEdit_WndProc );
+    SetPropW( control->hwnd, szButtonData, info );
+
+    msi_dialog_update_pathedit( dialog, control );
+
+    return ERROR_SUCCESS;
 }
 
 /* radio buttons are a bit different from normal controls */
@@ -1333,9 +1734,9 @@ static UINT msi_dialog_create_radiobutton( MSIRECORD *rec, LPVOID param )
     style = WS_CHILD | BS_AUTORADIOBUTTON | BS_MULTILINE | WS_TABSTOP;
     name = MSI_RecordGetString( rec, 3 );
     text = MSI_RecordGetString( rec, 8 );
-    if( attributes & 1 )
+    if( attributes & msidbControlAttributesVisible )
         style |= WS_VISIBLE;
-    if( ~attributes & 2 )
+    if( ~attributes & msidbControlAttributesEnabled )
         style |= WS_DISABLED;
 
     control = msi_dialog_create_window( dialog, rec, 0, szButton, name, text,
@@ -1344,6 +1745,9 @@ static UINT msi_dialog_create_radiobutton( MSIRECORD *rec, LPVOID param )
         return ERROR_FUNCTION_FAILED;
     control->handler = msi_dialog_radiogroup_handler;
 
+    if (!lstrcmpW(control->name, group->propval))
+        SendMessageW(control->hwnd, BM_SETCHECK, BST_CHECKED, 0);
+
     prop = MSI_RecordGetString( rec, 1 );
     if( prop )
         control->property = strdupW( prop );
@@ -1365,13 +1769,20 @@ static UINT msi_dialog_radiogroup_control( msi_dialog *dialog, MSIRECORD *rec )
     radio_button_group_descr group;
     MSIPACKAGE *package = dialog->package;
     WNDPROC oldproc;
+    DWORD attr, style = WS_GROUP;
 
     prop = MSI_RecordGetString( rec, 9 );
 
     TRACE("%p %p %s\n", dialog, rec, debugstr_w( prop ));
 
+    attr = MSI_RecordGetInteger( rec, 8 );
+    if (attr & msidbControlAttributesHasBorder)
+        style |= BS_GROUPBOX;
+    else
+        style |= BS_OWNERDRAW;
+
     /* Create parent group box to hold radio buttons */
-    control = msi_dialog_add_control( dialog, rec, szButton, BS_OWNERDRAW|WS_GROUP );
+    control = msi_dialog_add_control( dialog, rec, szButton, style );
     if( !control )
         return ERROR_FUNCTION_FAILED;
 
@@ -1395,9 +1806,11 @@ static UINT msi_dialog_radiogroup_control( msi_dialog *dialog, MSIRECORD *rec )
     group.dialog = dialog;
     group.parent = control;
     group.attributes = MSI_RecordGetInteger( rec, 8 );
+    group.propval = msi_dup_property( dialog->package, control->property );
 
     r = MSI_IterateRecords( view, 0, msi_dialog_create_radiobutton, &group );
     msiobj_release( &view->hdr );
+    msi_free( group.propval );
 
     return r;
 }
@@ -1409,19 +1822,24 @@ struct msi_selection_tree_info
     msi_dialog *dialog;
     HWND hwnd;
     WNDPROC oldproc;
+    HTREEITEM selected;
 };
 
 static void
 msi_seltree_sync_item_state( HWND hwnd, MSIFEATURE *feature, HTREEITEM hItem )
 {
     TVITEMW tvi;
+    DWORD index = feature->Action;
 
     TRACE("Feature %s -> %d %d %d\n", debugstr_w(feature->Title),
         feature->Installed, feature->Action, feature->ActionRequest);
 
+    if (index == INSTALLSTATE_UNKNOWN)
+        index = INSTALLSTATE_ABSENT;
+
     tvi.mask = TVIF_STATE;
     tvi.hItem = hItem;
-    tvi.state = INDEXTOSTATEIMAGEMASK( feature->Action );
+    tvi.state = INDEXTOSTATEIMAGEMASK( index );
     tvi.stateMask = TVIS_STATEIMAGEMASK;
 
     SendMessageW( hwnd, TVM_SETITEMW, 0, (LPARAM) &tvi );
@@ -1464,7 +1882,9 @@ msi_seltree_feature_from_item( HWND hwnd, HTREEITEM hItem )
 static LRESULT
 msi_seltree_menu( HWND hwnd, HTREEITEM hItem )
 {
+    struct msi_selection_tree_info *info;
     MSIFEATURE *feature;
+    MSIPACKAGE *package;
     union {
         RECT rc;
         POINT pt[2];
@@ -1472,6 +1892,9 @@ msi_seltree_menu( HWND hwnd, HTREEITEM hItem )
     } u;
     UINT r;
 
+    info = GetPropW(hwnd, szButtonData);
+    package = info->dialog->package;
+
     feature = msi_seltree_feature_from_item( hwnd, hItem );
     if (!feature)
     {
@@ -1491,8 +1914,7 @@ msi_seltree_menu( HWND hwnd, HTREEITEM hItem )
     case INSTALLSTATE_LOCAL:
     case INSTALLSTATE_ADVERTISED:
     case INSTALLSTATE_ABSENT:
-        feature->ActionRequest = r;
-        feature->Action = r;
+        msi_feature_set_state(package, feature, r);
         break;
     default:
         FIXME("select feature and all children\n");
@@ -1500,10 +1922,17 @@ msi_seltree_menu( HWND hwnd, HTREEITEM hItem )
 
     /* update */
     msi_seltree_sync_item_state( hwnd, feature, hItem );
+    ACTION_UpdateComponentStates( package, feature->Feature );
 
     return 0;
 }
 
+static MSIFEATURE *msi_seltree_get_selected_feature( msi_control *control )
+{
+    struct msi_selection_tree_info *info = GetPropW(control->hwnd, szButtonData);
+    return msi_seltree_feature_from_item( control->hwnd, info->selected );
+}
+
 static LRESULT WINAPI
 MSISelectionTree_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
 {
@@ -1511,15 +1940,15 @@ MSISelectionTree_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
     TVHITTESTINFO tvhti;
     HRESULT r;
 
-    TRACE("%p %04x %08x %08lx\n", hWnd, msg, wParam, lParam);
+    TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam);
 
     info = GetPropW(hWnd, szButtonData);
 
     switch( msg )
     {
     case WM_LBUTTONDOWN:
-        tvhti.pt.x = LOWORD( lParam );
-        tvhti.pt.y = HIWORD( lParam );
+        tvhti.pt.x = (short)LOWORD( lParam );
+        tvhti.pt.y = (short)HIWORD( lParam );
         tvhti.flags = 0;
         tvhti.hItem = 0;
         r = CallWindowProcW(info->oldproc, hWnd, TVM_HITTEST, 0, (LPARAM) &tvhti );
@@ -1544,9 +1973,10 @@ static void
 msi_seltree_add_child_features( MSIPACKAGE *package, HWND hwnd,
                                 LPCWSTR parent, HTREEITEM hParent )
 {
+    struct msi_selection_tree_info *info = GetPropW( hwnd, szButtonData );
     MSIFEATURE *feature;
     TVINSERTSTRUCTW tvis;
-    HTREEITEM hitem;
+    HTREEITEM hitem, hfirst = NULL;
 
     LIST_FOR_EACH_ENTRY( feature, &package->features, MSIFEATURE, entry )
     {
@@ -1556,9 +1986,12 @@ msi_seltree_add_child_features( MSIPACKAGE *package, HWND hwnd,
         if ( !feature->Title )
             continue;
 
+        if ( !feature->Display )
+            continue;
+
         memset( &tvis, 0, sizeof tvis );
         tvis.hParent = hParent;
-        tvis.hInsertAfter = TVI_SORT;
+        tvis.hInsertAfter = TVI_LAST;
         tvis.u.item.mask = TVIF_TEXT | TVIF_PARAM;
         tvis.u.item.pszText = feature->Title;
         tvis.u.item.lParam = (LPARAM) feature;
@@ -1567,10 +2000,21 @@ msi_seltree_add_child_features( MSIPACKAGE *package, HWND hwnd,
         if (!hitem)
             continue;
 
+        if (!hfirst)
+            hfirst = hitem;
+
         msi_seltree_sync_item_state( hwnd, feature, hitem );
         msi_seltree_add_child_features( package, hwnd,
                                         feature->Feature, hitem );
+
+        /* the node is expanded if Display is odd */
+        if ( feature->Display % 2 != 0 )
+            SendMessageW( hwnd, TVM_EXPAND, TVE_EXPAND, (LPARAM) hitem );
     }
+
+    /* select the first item */
+    SendMessageW( hwnd, TVM_SELECTITEM, TVGN_CARET | TVGN_DROPHILITE, (LPARAM) hfirst );
+    info->selected = hfirst;
 }
 
 static void msi_seltree_create_imagelist( HWND hwnd )
@@ -1610,10 +2054,68 @@ static void msi_seltree_create_imagelist( HWND hwnd )
     SendMessageW( hwnd, TVM_SETIMAGELIST, TVSIL_STATE, (LPARAM)himl );
 }
 
-static UINT msi_dialog_selection_tree( msi_dialog *dialog, MSIRECORD *rec )
+static UINT msi_dialog_seltree_handler( msi_dialog *dialog,
+                                        msi_control *control, WPARAM param )
 {
-    msi_control *control;
-    LPCWSTR prop;
+    struct msi_selection_tree_info *info = GetPropW( control->hwnd, szButtonData );
+    LPNMTREEVIEWW tv = (LPNMTREEVIEWW)param;
+    MSIRECORD *row, *rec;
+    MSIFOLDER *folder;
+    MSIFEATURE *feature;
+    LPCWSTR dir, title = NULL;
+    UINT r = ERROR_SUCCESS;
+
+    static const WCHAR select[] = {
+        'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
+        '`','F','e','a','t','u','r','e','`',' ','W','H','E','R','E',' ',
+        '`','T','i','t','l','e','`',' ','=',' ','\'','%','s','\'',0
+    };
+
+    if (tv->hdr.code != TVN_SELCHANGINGW)
+        return ERROR_SUCCESS;
+
+    info->selected = tv->itemNew.hItem;
+
+    if (!(tv->itemNew.mask & TVIF_TEXT))
+    {
+        feature = msi_seltree_feature_from_item( control->hwnd, tv->itemNew.hItem );
+        if (feature)
+            title = feature->Title;
+    }
+    else
+        title = tv->itemNew.pszText;
+
+    row = MSI_QueryGetRecord( dialog->package->db, select, title );
+    if (!row)
+        return ERROR_FUNCTION_FAILED;
+
+    rec = MSI_CreateRecord( 1 );
+
+    MSI_RecordSetStringW( rec, 1, MSI_RecordGetString( row, 4 ) );
+    ControlEvent_FireSubscribedEvent( dialog->package, szSelectionDescription, rec );
+
+    dir = MSI_RecordGetString( row, 7 );
+    folder = get_loaded_folder( dialog->package, dir );
+    if (!folder)
+    {
+        r = ERROR_FUNCTION_FAILED;
+        goto done;
+    }
+
+    MSI_RecordSetStringW( rec, 1, folder->ResolvedTarget );
+    ControlEvent_FireSubscribedEvent( dialog->package, szSelectionPath, rec );
+
+done:
+    msiobj_release(&row->hdr);
+    msiobj_release(&rec->hdr);
+
+    return r;
+}
+
+static UINT msi_dialog_selection_tree( msi_dialog *dialog, MSIRECORD *rec )
+{
+    msi_control *control;
+    LPCWSTR prop;
     MSIPACKAGE *package = dialog->package;
     DWORD style;
     struct msi_selection_tree_info *info;
@@ -1623,7 +2125,6 @@ static UINT msi_dialog_selection_tree( msi_dialog *dialog, MSIRECORD *rec )
         return ERROR_FUNCTION_FAILED;
 
     /* create the treeview control */
-    prop = MSI_RecordGetString( rec, 9 );
     style = TVS_HASLINES | TVS_HASBUTTONS | TVS_LINESATROOT;
     style |= WS_GROUP | WS_VSCROLL;
     control = msi_dialog_add_control( dialog, rec, WC_TREEVIEWW, style );
@@ -1633,6 +2134,11 @@ static UINT msi_dialog_selection_tree( msi_dialog *dialog, MSIRECORD *rec )
         return ERROR_FUNCTION_FAILED;
     }
 
+    control->handler = msi_dialog_seltree_handler;
+    control->attributes = MSI_RecordGetInteger( rec, 8 );
+    prop = MSI_RecordGetString( rec, 9 );
+    control->property = msi_dialog_dup_property( dialog, prop, FALSE );
+
     /* subclass */
     info->dialog = dialog;
     info->hwnd = control->hwnd;
@@ -1640,6 +2146,9 @@ static UINT msi_dialog_selection_tree( msi_dialog *dialog, MSIRECORD *rec )
                                           (LONG_PTR)MSISelectionTree_WndProc );
     SetPropW( control->hwnd, szButtonData, info );
 
+    ControlEvent_SubscribeToEvent( dialog->package, dialog,
+                                   szSelectionPath, control->name, szProperty );
+
     /* initialize it */
     msi_seltree_create_imagelist( control->hwnd );
     msi_seltree_add_child_features( package, control->hwnd, NULL, NULL );
@@ -1647,7 +2156,594 @@ static UINT msi_dialog_selection_tree( msi_dialog *dialog, MSIRECORD *rec )
     return ERROR_SUCCESS;
 }
 
-struct control_handler msi_dialog_handler[] =
+/******************** Group Box ***************************************/
+
+static UINT msi_dialog_group_box( msi_dialog *dialog, MSIRECORD *rec )
+{
+    msi_control *control;
+    DWORD style;
+
+    style = BS_GROUPBOX | WS_CHILD | WS_GROUP;
+    control = msi_dialog_add_control( dialog, rec, WC_BUTTONW, style );
+    if (!control)
+        return ERROR_FUNCTION_FAILED;
+
+    return ERROR_SUCCESS;
+}
+
+/******************** List Box ***************************************/
+
+struct msi_listbox_info
+{
+    msi_dialog *dialog;
+    HWND hwnd;
+    WNDPROC oldproc;
+    DWORD num_items;
+    DWORD addpos_items;
+    LPWSTR *items;
+};
+
+static LRESULT WINAPI MSIListBox_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+    struct msi_listbox_info *info;
+    LRESULT r;
+    DWORD j;
+
+    TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam);
+
+    info = GetPropW( hWnd, szButtonData );
+    if (!info)
+        return 0;
+
+    r = CallWindowProcW( info->oldproc, hWnd, msg, wParam, lParam );
+
+    switch( msg )
+    {
+    case WM_NCDESTROY:
+        for (j = 0; j < info->num_items; j++)
+            msi_free( info->items[j] );
+        msi_free( info->items );
+        msi_free( info );
+        RemovePropW( hWnd, szButtonData );
+        break;
+    }
+
+    return r;
+}
+
+static UINT msi_listbox_add_item( MSIRECORD *rec, LPVOID param )
+{
+    struct msi_listbox_info *info = param;
+    LPCWSTR value, text;
+    int pos;
+
+    value = MSI_RecordGetString( rec, 3 );
+    text = MSI_RecordGetString( rec, 4 );
+
+    info->items[info->addpos_items] = strdupW( value );
+
+    pos = SendMessageW( info->hwnd, LB_ADDSTRING, 0, (LPARAM)text );
+    SendMessageW( info->hwnd, LB_SETITEMDATA, pos, (LPARAM)info->items[info->addpos_items] );
+    info->addpos_items++;
+    return ERROR_SUCCESS;
+}
+
+static UINT msi_listbox_add_items( struct msi_listbox_info *info, LPCWSTR property )
+{
+    UINT r;
+    MSIQUERY *view = NULL;
+    DWORD count;
+
+    static const WCHAR query[] = {
+        'S','E','L','E','C','T',' ','*',' ',
+        'F','R','O','M',' ','`','L','i','s','t','B','o','x','`',' ',
+        'W','H','E','R','E',' ',
+        '`','P','r','o','p','e','r','t','y','`',' ','=',' ','\'','%','s','\'',' ',
+        'O','R','D','E','R',' ','B','Y',' ','`','O','r','d','e','r','`',0
+    };
+
+    r = MSI_OpenQuery( info->dialog->package->db, &view, query, property );
+    if ( r != ERROR_SUCCESS )
+        return r;
+
+    /* just get the number of records */
+    count = 0;
+    r = MSI_IterateRecords( view, &count, NULL, NULL );
+
+    info->num_items = count;
+    info->items = msi_alloc( sizeof(*info->items) * count );
+
+    r = MSI_IterateRecords( view, NULL, msi_listbox_add_item, info );
+    msiobj_release( &view->hdr );
+
+    return r;
+}
+
+static UINT msi_dialog_listbox_handler( msi_dialog *dialog,
+                                        msi_control *control, WPARAM param )
+{
+    struct msi_listbox_info *info;
+    int index;
+    LPCWSTR value;
+
+    if( HIWORD(param) != LBN_SELCHANGE )
+        return ERROR_SUCCESS;
+
+    info = GetPropW( control->hwnd, szButtonData );
+    index = SendMessageW( control->hwnd, LB_GETCURSEL, 0, 0 );
+    value = (LPCWSTR) SendMessageW( control->hwnd, LB_GETITEMDATA, index, 0 );
+
+    MSI_SetPropertyW( info->dialog->package,
+                      control->property, value );
+    msi_dialog_evaluate_control_conditions( info->dialog );
+
+    return ERROR_SUCCESS;
+}
+
+static UINT msi_dialog_list_box( msi_dialog *dialog, MSIRECORD *rec )
+{
+    struct msi_listbox_info *info;
+    msi_control *control;
+    DWORD attributes, style;
+    LPCWSTR prop;
+
+    info = msi_alloc( sizeof *info );
+    if (!info)
+        return ERROR_FUNCTION_FAILED;
+
+    style = WS_TABSTOP | WS_GROUP | WS_CHILD | LBS_NOTIFY | WS_VSCROLL | WS_BORDER;
+    attributes = MSI_RecordGetInteger( rec, 8 );
+    if (~attributes & msidbControlAttributesSorted)
+        style |= LBS_SORT;
+
+    control = msi_dialog_add_control( dialog, rec, WC_LISTBOXW, style );
+    if (!control)
+    {
+        msi_free(info);
+        return ERROR_FUNCTION_FAILED;
+    }
+
+    control->handler = msi_dialog_listbox_handler;
+
+    prop = MSI_RecordGetString( rec, 9 );
+    control->property = msi_dialog_dup_property( dialog, prop, FALSE );
+
+    /* subclass */
+    info->dialog = dialog;
+    info->hwnd = control->hwnd;
+    info->items = NULL;
+    info->addpos_items = 0;
+    info->oldproc = (WNDPROC)SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
+                                                (LONG_PTR)MSIListBox_WndProc );
+    SetPropW( control->hwnd, szButtonData, info );
+
+    if ( control->property )
+        msi_listbox_add_items( info, control->property );
+
+    return ERROR_SUCCESS;
+}
+
+/******************** Directory Combo ***************************************/
+
+static void msi_dialog_update_directory_combo( msi_dialog *dialog, msi_control *control )
+{
+    LPWSTR prop, path;
+    BOOL indirect;
+
+    if (!control && !(control = msi_dialog_find_control_by_type( dialog, szDirectoryCombo )))
+        return;
+
+    indirect = control->attributes & msidbControlAttributesIndirect;
+    prop = msi_dialog_dup_property( dialog, control->property, indirect );
+    path = msi_dialog_dup_property( dialog, prop, TRUE );
+
+    PathStripPathW( path );
+    PathRemoveBackslashW( path );
+
+    SendMessageW( control->hwnd, CB_INSERTSTRING, 0, (LPARAM)path );
+    SendMessageW( control->hwnd, CB_SETCURSEL, 0, 0 );
+
+    msi_free( path );
+    msi_free( prop );
+}
+
+static UINT msi_dialog_directory_combo( msi_dialog *dialog, MSIRECORD *rec )
+{
+    msi_control *control;
+    LPCWSTR prop;
+    DWORD style;
+
+    /* FIXME: use CBS_OWNERDRAWFIXED and add owner draw code */
+    style = CBS_DROPDOWNLIST | CBS_HASSTRINGS | WS_CHILD |
+            WS_GROUP | WS_TABSTOP | WS_VSCROLL;
+    control = msi_dialog_add_control( dialog, rec, WC_COMBOBOXW, style );
+    if (!control)
+        return ERROR_FUNCTION_FAILED;
+
+    control->attributes = MSI_RecordGetInteger( rec, 8 );
+    prop = MSI_RecordGetString( rec, 9 );
+    control->property = msi_dialog_dup_property( dialog, prop, FALSE );
+
+    msi_dialog_update_directory_combo( dialog, control );
+
+    return ERROR_SUCCESS;
+}
+
+/******************** Directory List ***************************************/
+
+static void msi_dialog_update_directory_list( msi_dialog *dialog, msi_control *control )
+{
+    WCHAR dir_spec[MAX_PATH];
+    WIN32_FIND_DATAW wfd;
+    LPWSTR prop, path;
+    BOOL indirect;
+    LVITEMW item;
+    HANDLE file;
+
+    static const WCHAR asterisk[] = {'*',0};
+    static const WCHAR dot[] = {'.',0};
+    static const WCHAR dotdot[] = {'.','.',0};
+
+    if (!control && !(control = msi_dialog_find_control_by_type( dialog, szDirectoryList )))
+        return;
+
+    /* clear the list-view */
+    SendMessageW( control->hwnd, LVM_DELETEALLITEMS, 0, 0 );
+
+    indirect = control->attributes & msidbControlAttributesIndirect;
+    prop = msi_dialog_dup_property( dialog, control->property, indirect );
+    path = msi_dialog_dup_property( dialog, prop, TRUE );
+
+    lstrcpyW( dir_spec, path );
+    lstrcatW( dir_spec, asterisk );
+
+    file = FindFirstFileW( dir_spec, &wfd );
+    if ( file == INVALID_HANDLE_VALUE )
+        return;
+
+    do
+    {
+        if ( wfd.dwFileAttributes != FILE_ATTRIBUTE_DIRECTORY )
+            continue;
+
+        if ( !lstrcmpW( wfd.cFileName, dot ) || !lstrcmpW( wfd.cFileName, dotdot ) )
+            continue;
+
+        item.mask = LVIF_TEXT;
+        item.cchTextMax = MAX_PATH;
+        item.iItem = 0;
+        item.iSubItem = 0;
+        item.pszText = wfd.cFileName;
+
+        SendMessageW( control->hwnd, LVM_INSERTITEMW, 0, (LPARAM)&item );
+    } while ( FindNextFileW( file, &wfd ) );
+
+    msi_free( prop );
+    msi_free( path );
+    FindClose( file );
+}
+
+UINT msi_dialog_directorylist_up( msi_dialog *dialog )
+{
+    msi_control *control;
+    LPWSTR prop, path, ptr;
+    BOOL indirect;
+
+    control = msi_dialog_find_control_by_type( dialog, szDirectoryList );
+    indirect = control->attributes & msidbControlAttributesIndirect;
+    prop = msi_dialog_dup_property( dialog, control->property, indirect );
+    path = msi_dialog_dup_property( dialog, prop, TRUE );
+
+    /* strip off the last directory */
+    ptr = PathFindFileNameW( path );
+    if (ptr != path) *(ptr - 1) = '\0';
+    PathAddBackslashW( path );
+
+    MSI_SetPropertyW( dialog->package, prop, path );
+
+    msi_dialog_update_directory_list( dialog, NULL );
+    msi_dialog_update_directory_combo( dialog, NULL );
+    msi_dialog_update_pathedit( dialog, NULL );
+
+    msi_free( path );
+    msi_free( prop );
+
+    return ERROR_SUCCESS;
+}
+
+static UINT msi_dialog_dirlist_handler( msi_dialog *dialog,
+                                        msi_control *control, WPARAM param )
+{
+    LPNMHDR nmhdr = (LPNMHDR)param;
+    WCHAR new_path[MAX_PATH];
+    WCHAR text[MAX_PATH];
+    LPWSTR path, prop;
+    BOOL indirect;
+    LVITEMW item;
+    int index;
+
+    static const WCHAR backslash[] = {'\\',0};
+
+    if (nmhdr->code != LVN_ITEMACTIVATE)
+        return ERROR_SUCCESS;
+
+    index = SendMessageW( control->hwnd, LVM_GETNEXTITEM, -1, LVNI_SELECTED );
+    if ( index < 0 )
+    {
+        ERR("No list-view item selected!\n");
+        return ERROR_FUNCTION_FAILED;
+    }
+
+    item.iSubItem = 0;
+    item.pszText = text;
+    item.cchTextMax = MAX_PATH;
+    SendMessageW( control->hwnd, LVM_GETITEMTEXTW, index, (LPARAM)&item );
+
+    indirect = control->attributes & msidbControlAttributesIndirect;
+    prop = msi_dialog_dup_property( dialog, control->property, indirect );
+    path = msi_dialog_dup_property( dialog, prop, TRUE );
+
+    lstrcpyW( new_path, path );
+    lstrcatW( new_path, text );
+    lstrcatW( new_path, backslash );
+
+    MSI_SetPropertyW( dialog->package, prop, new_path );
+
+    msi_dialog_update_directory_list( dialog, NULL );
+    msi_dialog_update_directory_combo( dialog, NULL );
+    msi_dialog_update_pathedit( dialog, NULL );
+
+    msi_free( prop );
+    msi_free( path );
+    return ERROR_SUCCESS;
+}
+
+static UINT msi_dialog_directory_list( msi_dialog *dialog, MSIRECORD *rec )
+{
+    msi_control *control;
+    LPCWSTR prop;
+    DWORD style;
+
+    style = LVS_LIST | WS_VSCROLL | LVS_SHAREIMAGELISTS |
+            LVS_AUTOARRANGE | LVS_SINGLESEL | WS_BORDER |
+            LVS_SORTASCENDING | WS_CHILD | WS_GROUP | WS_TABSTOP;
+    control = msi_dialog_add_control( dialog, rec, WC_LISTVIEWW, style );
+    if (!control)
+        return ERROR_FUNCTION_FAILED;
+
+    control->attributes = MSI_RecordGetInteger( rec, 8 );
+    control->handler = msi_dialog_dirlist_handler;
+    prop = MSI_RecordGetString( rec, 9 );
+    control->property = msi_dialog_dup_property( dialog, prop, FALSE );
+
+    /* double click to activate an item in the list */
+    SendMessageW( control->hwnd, LVM_SETEXTENDEDLISTVIEWSTYLE,
+                  0, LVS_EX_TWOCLICKACTIVATE );
+
+    msi_dialog_update_directory_list( dialog, control );
+
+    return ERROR_SUCCESS;
+}
+
+/******************** VolumeCost List ***************************************/
+
+static BOOL str_is_number( LPCWSTR str )
+{
+    int i;
+
+    for (i = 0; i < lstrlenW( str ); i++)
+        if (!isdigitW(str[i]))
+            return FALSE;
+
+    return TRUE;
+}
+
+static const WCHAR column_keys[][80] =
+{
+    {'V','o','l','u','m','e','C','o','s','t','V','o','l','u','m','e',0},
+    {'V','o','l','u','m','e','C','o','s','t','S','i','z','e',0},
+    {'V','o','l','u','m','e','C','o','s','t','A','v','a','i','l','a','b','l','e',0},
+    {'V','o','l','u','m','e','C','o','s','t','R','e','q','u','i','r','e','d',0},
+    {'V','o','l','u','m','e','C','o','s','t','D','i','f','f','e','r','e','n','c','e',0}
+};
+
+static void msi_dialog_vcl_add_columns( msi_dialog *dialog, msi_control *control, MSIRECORD *rec )
+{
+    LPCWSTR text = MSI_RecordGetString( rec, 10 );
+    LPCWSTR begin = text, end;
+    WCHAR *num;
+    LVCOLUMNW lvc;
+    DWORD count = 0;
+
+    static const WCHAR zero[] = {'0',0};
+    static const WCHAR negative[] = {'-',0};
+
+    if (!text) return;
+
+    while ((begin = strchrW( begin, '{' )) && count < 5)
+    {
+        if (!(end = strchrW( begin, '}' )))
+            return;
+
+        num = msi_alloc( (end-begin+1)*sizeof(WCHAR) );
+        if (!num)
+            return;
+
+        lstrcpynW( num, begin + 1, end - begin );
+        begin += end - begin + 1;
+
+        /* empty braces or '0' hides the column */ 
+        if ( !num[0] || !lstrcmpW( num, zero ) )
+        {
+            count++;
+            msi_free( num );
+            continue;
+        }
+
+        /* the width must be a positive number
+         * if a width is invalid, all remaining columns are hidden
+         */
+        if ( !strncmpW( num, negative, 1 ) || !str_is_number( num ) ) {
+            msi_free( num );
+            return;
+        }
+
+        ZeroMemory( &lvc, sizeof(lvc) );
+        lvc.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM;
+        lvc.cx = atolW( num );
+        lvc.pszText = msi_dialog_get_uitext( dialog, column_keys[count] );
+
+        SendMessageW( control->hwnd,  LVM_INSERTCOLUMNW, count++, (LPARAM)&lvc );
+        msi_free( lvc.pszText );
+        msi_free( num );
+    }
+}
+
+static void msi_dialog_vcl_add_drives( msi_dialog *dialog, msi_control *control )
+{
+    ULARGE_INTEGER total, free;
+    WCHAR size_text[MAX_PATH];
+    LPWSTR drives, ptr;
+    LVITEMW lvitem;
+    DWORD size;
+    int i = 0;
+
+    size = GetLogicalDriveStringsW( 0, NULL );
+    if ( !size ) return;
+
+    drives = msi_alloc( (size + 1) * sizeof(WCHAR) );
+    if ( !drives ) return;
+
+    GetLogicalDriveStringsW( size, drives );
+
+    ptr = drives;
+    while (*ptr)
+    {
+        lvitem.mask = LVIF_TEXT;
+        lvitem.iItem = i;
+        lvitem.iSubItem = 0;
+        lvitem.pszText = ptr;
+        lvitem.cchTextMax = lstrlenW(ptr) + 1;
+        SendMessageW( control->hwnd, LVM_INSERTITEMW, 0, (LPARAM)&lvitem );
+
+        GetDiskFreeSpaceExW(ptr, &free, &total, NULL);
+
+        StrFormatByteSizeW(total.QuadPart, size_text, MAX_PATH);
+        lvitem.iSubItem = 1;
+        lvitem.pszText = size_text;
+        lvitem.cchTextMax = lstrlenW(size_text) + 1;
+        SendMessageW( control->hwnd, LVM_SETITEMW, 0, (LPARAM)&lvitem );
+
+        StrFormatByteSizeW(free.QuadPart, size_text, MAX_PATH);
+        lvitem.iSubItem = 2;
+        lvitem.pszText = size_text;
+        lvitem.cchTextMax = lstrlenW(size_text) + 1;
+        SendMessageW( control->hwnd, LVM_SETITEMW, 0, (LPARAM)&lvitem );
+
+        ptr += lstrlenW(ptr) + 1;
+        i++;
+    }
+
+    msi_free( drives );
+}
+
+static UINT msi_dialog_volumecost_list( msi_dialog *dialog, MSIRECORD *rec )
+{
+    msi_control *control;
+    DWORD style;
+
+    style = LVS_REPORT | WS_VSCROLL | WS_HSCROLL | LVS_SHAREIMAGELISTS |
+            LVS_AUTOARRANGE | LVS_SINGLESEL | WS_BORDER |
+            WS_CHILD | WS_TABSTOP | WS_GROUP;
+    control = msi_dialog_add_control( dialog, rec, WC_LISTVIEWW, style );
+    if (!control)
+        return ERROR_FUNCTION_FAILED;
+
+    msi_dialog_vcl_add_columns( dialog, control, rec );
+    msi_dialog_vcl_add_drives( dialog, control );
+
+    return ERROR_SUCCESS;
+}
+
+/******************** VolumeSelect Combo ***************************************/
+
+static UINT msi_dialog_volsel_handler( msi_dialog *dialog,
+                                       msi_control *control, WPARAM param )
+{
+    WCHAR text[MAX_PATH];
+    LPWSTR prop;
+    BOOL indirect;
+    int index;
+
+    if (HIWORD(param) != CBN_SELCHANGE)
+        return ERROR_SUCCESS;
+
+    index = SendMessageW( control->hwnd, CB_GETCURSEL, 0, 0 );
+    if ( index == CB_ERR )
+    {
+        ERR("No ComboBox item selected!\n");
+        return ERROR_FUNCTION_FAILED;
+    }
+
+    SendMessageW( control->hwnd, CB_GETLBTEXT, index, (LPARAM)text );
+
+    indirect = control->attributes & msidbControlAttributesIndirect;
+    prop = msi_dialog_dup_property( dialog, control->property, indirect );
+
+    MSI_SetPropertyW( dialog->package, prop, text );
+
+    msi_free( prop );
+    return ERROR_SUCCESS;
+}
+
+static void msi_dialog_vsc_add_drives( msi_dialog *dialog, msi_control *control )
+{
+    LPWSTR drives, ptr;
+    DWORD size;
+
+    size = GetLogicalDriveStringsW( 0, NULL );
+    if ( !size ) return;
+
+    drives = msi_alloc( (size + 1) * sizeof(WCHAR) );
+    if ( !drives ) return;
+
+    GetLogicalDriveStringsW( size, drives );
+
+    ptr = drives;
+    while (*ptr)
+    {
+        SendMessageW( control->hwnd, CB_ADDSTRING, 0, (LPARAM)ptr );
+        ptr += lstrlenW(ptr) + 1;
+    }
+
+    msi_free( drives );
+}
+
+static UINT msi_dialog_volumeselect_combo( msi_dialog *dialog, MSIRECORD *rec )
+{
+    msi_control *control;
+    LPCWSTR prop;
+    DWORD style;
+
+    /* FIXME: CBS_OWNERDRAWFIXED */
+    style = WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP |
+            CBS_DROPDOWNLIST | CBS_SORT | CBS_HASSTRINGS |
+            WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR;
+    control = msi_dialog_add_control( dialog, rec, WC_COMBOBOXW, style );
+    if (!control)
+        return ERROR_FUNCTION_FAILED;
+
+    control->attributes = MSI_RecordGetInteger( rec, 8 );
+    control->handler = msi_dialog_volsel_handler;
+    prop = MSI_RecordGetString( rec, 9 );
+    control->property = msi_dialog_dup_property( dialog, prop, FALSE );
+
+    msi_dialog_vsc_add_drives( dialog, control );
+
+    return ERROR_SUCCESS;
+}
+
+static const struct control_handler msi_dialog_handler[] =
 {
     { szText, msi_dialog_text_control },
     { szPushButton, msi_dialog_button_control },
@@ -1663,6 +2759,12 @@ struct control_handler msi_dialog_handler[] =
     { szRadioButtonGroup, msi_dialog_radiogroup_control },
     { szIcon, msi_dialog_icon_control },
     { szSelectionTree, msi_dialog_selection_tree },
+    { szGroupBox, msi_dialog_group_box },
+    { szListBox, msi_dialog_list_box },
+    { szDirectoryCombo, msi_dialog_directory_combo },
+    { szDirectoryList, msi_dialog_directory_list },
+    { szVolumeCostList, msi_dialog_volumecost_list },
+    { szVolumeSelectCombo, msi_dialog_volumeselect_combo },
 };
 
 #define NUM_CONTROL_TYPES (sizeof msi_dialog_handler/sizeof msi_dialog_handler[0])
@@ -1719,6 +2821,7 @@ static UINT msi_dialog_set_control_condition( MSIRECORD *rec, LPVOID param )
     static const WCHAR szShow[] = { 'S','h','o','w',0 };
     static const WCHAR szDisable[] = { 'D','i','s','a','b','l','e',0 };
     static const WCHAR szEnable[] = { 'E','n','a','b','l','e',0 };
+    static const WCHAR szDefault[] = { 'D','e','f','a','u','l','t',0 };
     msi_dialog *dialog = param;
     msi_control *control;
     LPCWSTR name, action, condition;
@@ -1742,6 +2845,8 @@ static UINT msi_dialog_set_control_condition( MSIRECORD *rec, LPVOID param )
             EnableWindow(control->hwnd, FALSE);
         else if(!strcmpW(action, szEnable))
             EnableWindow(control->hwnd, TRUE);
+        else if(!strcmpW(action, szDefault))
+            SetFocus(control->hwnd);
         else
             FIXME("Unhandled action %s\n", debugstr_w(action));
     }
@@ -1767,10 +2872,7 @@ static UINT msi_dialog_evaluate_control_conditions( msi_dialog *dialog )
     /* query the Control table for all the elements of the control */
     r = MSI_OpenQuery( package->db, &view, query, dialog->name );
     if( r != ERROR_SUCCESS )
-    {
-        ERR("query failed for dialog %s\n", debugstr_w(dialog->name));
-        return ERROR_INVALID_PARAMETER;
-    }
+        return ERROR_SUCCESS;
 
     r = MSI_IterateRecords( view, 0, msi_dialog_set_control_condition, dialog );
     msiobj_release( &view->hdr );
@@ -1832,25 +2934,63 @@ static MSIRECORD *msi_get_dialog_record( msi_dialog *dialog )
 
     rec = MSI_QueryGetRecord( package->db, query, dialog->name );
     if( !rec )
-        ERR("query failed for dialog %s\n", debugstr_w(dialog->name));
+        WARN("query failed for dialog %s\n", debugstr_w(dialog->name));
 
     return rec;
 }
 
-static void msi_dialog_adjust_dialog_size( msi_dialog *dialog, LPSIZE sz )
+static void msi_dialog_adjust_dialog_pos( msi_dialog *dialog, MSIRECORD *rec, LPRECT pos )
 {
-    RECT rect;
+    static const WCHAR szScreenX[] = {'S','c','r','e','e','n','X',0};
+    static const WCHAR szScreenY[] = {'S','c','r','e','e','n','Y',0};
+
+    UINT xres, yres;
+    POINT center;
+    SIZE sz;
     LONG style;
 
-    /* turn the client size into the window rectangle */
-    rect.left = 0;
-    rect.top = 0;
-    rect.right = msi_dialog_scale_unit( dialog, sz->cx );
-    rect.bottom = msi_dialog_scale_unit( dialog, sz->cy );
+    center.x = MSI_RecordGetInteger( rec, 2 );
+    center.y = MSI_RecordGetInteger( rec, 3 );
+
+    sz.cx = MSI_RecordGetInteger( rec, 4 );
+    sz.cy = MSI_RecordGetInteger( rec, 5 );
+
+    sz.cx = msi_dialog_scale_unit( dialog, sz.cx );
+    sz.cy = msi_dialog_scale_unit( dialog, sz.cy );
+
+    xres = msi_get_property_int( dialog->package, szScreenX, 0 );
+    yres = msi_get_property_int( dialog->package, szScreenY, 0 );
+
+    center.x = MulDiv( center.x, xres, 100 );
+    center.y = MulDiv( center.y, yres, 100 );
+
+    /* turn the client pos into the window rectangle */
+    if (dialog->package->center_x && dialog->package->center_y)
+    {
+        pos->left = dialog->package->center_x - sz.cx / 2.0;
+        pos->right = pos->left + sz.cx;
+        pos->top = dialog->package->center_y - sz.cy / 2.0;
+        pos->bottom = pos->top + sz.cy;
+    }
+    else
+    {
+        pos->left = center.x - sz.cx/2;
+        pos->right = pos->left + sz.cx;
+        pos->top = center.y - sz.cy/2;
+        pos->bottom = pos->top + sz.cy;
+
+        /* save the center */
+        dialog->package->center_x = center.x;
+        dialog->package->center_y = center.y;
+    }
+
+    dialog->size.cx = sz.cx;
+    dialog->size.cy = sz.cy;
+
+    TRACE("%u %u %u %u\n", pos->left, pos->top, pos->right, pos->bottom);
+
     style = GetWindowLongPtrW( dialog->hwnd, GWL_STYLE );
-    AdjustWindowRect( &rect, style, FALSE );
-    sz->cx = rect.right - rect.left;
-    sz->cy = rect.bottom - rect.top;
+    AdjustWindowRect( pos, style, FALSE );
 }
 
 static BOOL msi_control_set_next( msi_control *control, msi_control *next )
@@ -1895,7 +3035,7 @@ static LRESULT msi_dialog_oncreate( HWND hwnd, LPCREATESTRUCTW cs )
     msi_dialog *dialog = (msi_dialog*) cs->lpCreateParams;
     MSIRECORD *rec = NULL;
     LPWSTR title = NULL;
-    SIZE size;
+    RECT pos;
 
     TRACE("%p %p\n", dialog, dialog->package);
 
@@ -1911,9 +3051,7 @@ static LRESULT msi_dialog_oncreate( HWND hwnd, LPCREATESTRUCTW cs )
 
     dialog->scale = msi_dialog_get_sans_serif_height(dialog->hwnd);
 
-    size.cx = MSI_RecordGetInteger( rec, 4 );
-    size.cy = MSI_RecordGetInteger( rec, 5 );
-    msi_dialog_adjust_dialog_size( dialog, &size );
+    msi_dialog_adjust_dialog_pos( dialog, rec, &pos );
 
     dialog->attributes = MSI_RecordGetInteger( rec, 6 );
 
@@ -1928,9 +3066,9 @@ static LRESULT msi_dialog_oncreate( HWND hwnd, LPCREATESTRUCTW cs )
     SetWindowTextW( hwnd, title );
     msi_free( title );
 
-    SetWindowPos( hwnd, 0, 0, 0, size.cx, size.cy,
-                  SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOREDRAW );
-
+    SetWindowPos( hwnd, 0, pos.left, pos.top,
+                  pos.right - pos.left, pos.bottom - pos.top,
+                  SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOREDRAW );
 
     msi_dialog_build_font_list( dialog );
     msi_dialog_fill_controls( dialog );
@@ -1969,12 +3107,13 @@ static UINT msi_dialog_set_property( msi_dialog *dialog, LPCWSTR event, LPCWSTR
     prop = msi_alloc( len*sizeof(WCHAR));
     strcpyW( prop, &event[1] );
     p = strchrW( prop, ']' );
-    if( p && p[1] == 0 )
+    if( p && (p[1] == 0 || p[1] == ' ') )
     {
         *p = 0;
         if( strcmpW( szNullArg, arg ) )
             deformat_string( dialog->package, arg, &arg_fmt );
         MSI_SetPropertyW( dialog->package, prop, arg_fmt );
+        msi_dialog_update_controls( dialog, prop );
         msi_free( arg_fmt );
     }
     else
@@ -1991,7 +3130,7 @@ static UINT msi_dialog_control_event( MSIRECORD *rec, LPVOID param )
 
     condition = MSI_RecordGetString( rec, 5 );
     r = MSI_EvaluateConditionW( dialog->package, condition );
-    if( r == MSICONDITION_TRUE )
+    if( r == MSICONDITION_TRUE || r == MSICONDITION_NONE )
     {
         event = MSI_RecordGetString( rec, 3 );
         arg = MSI_RecordGetString( rec, 4 );
@@ -2004,6 +3143,38 @@ static UINT msi_dialog_control_event( MSIRECORD *rec, LPVOID param )
     return ERROR_SUCCESS;
 }
 
+struct rec_list
+{
+    struct list entry;
+    MSIRECORD *rec;
+};
+
+static UINT add_rec_to_list( MSIRECORD *rec, LPVOID param )
+{
+    struct rec_list *add_rec;
+    struct list *records = (struct list *)param;
+
+    msiobj_addref( &rec->hdr );
+
+    add_rec = msi_alloc( sizeof( *add_rec ) );
+    if (!add_rec)
+    {
+        msiobj_release( &rec->hdr );
+        return ERROR_OUTOFMEMORY;
+    }
+
+    add_rec->rec = rec;
+    list_add_tail( records, &add_rec->entry );
+    return ERROR_SUCCESS;
+}
+
+static inline void remove_rec_from_list( struct rec_list *rec_entry )
+{
+    msiobj_release( &rec_entry->rec->hdr );
+    list_remove( &rec_entry->entry );
+    msi_free( rec_entry );
+}
+
 static UINT msi_dialog_button_handler( msi_dialog *dialog,
                                        msi_control *control, WPARAM param )
 {
@@ -2017,11 +3188,15 @@ static UINT msi_dialog_button_handler( msi_dialog *dialog,
       'O','R','D','E','R',' ','B','Y',' ','`','O','r','d','e','r','i','n','g','`',0
     };
     MSIQUERY *view = NULL;
+    struct rec_list *rec_entry, *next;
+    struct list events;
     UINT r;
 
     if( HIWORD(param) != BN_CLICKED )
         return ERROR_SUCCESS;
 
+    list_init( &events );
+
     r = MSI_OpenQuery( dialog->package->db, &view, query,
                        dialog->name, control->name );
     if( r != ERROR_SUCCESS )
@@ -2030,8 +3205,41 @@ static UINT msi_dialog_button_handler( msi_dialog *dialog,
         return 0;
     }
 
-    r = MSI_IterateRecords( view, 0, msi_dialog_control_event, dialog );
+    r = MSI_IterateRecords( view, 0, add_rec_to_list, &events );
     msiobj_release( &view->hdr );
+    if (r != ERROR_SUCCESS)
+        goto done;
+
+    /* handle all SetProperty events first */
+    LIST_FOR_EACH_ENTRY_SAFE( rec_entry, next, &events, struct rec_list, entry )
+    {
+        LPCWSTR event = MSI_RecordGetString( rec_entry->rec, 3 );
+
+        if ( event[0] != '[' )
+            continue;
+
+        r = msi_dialog_control_event( rec_entry->rec, dialog );
+        remove_rec_from_list( rec_entry );
+
+        if ( r != ERROR_SUCCESS )
+            goto done;
+    }    
+
+    /* handle all other events */
+    LIST_FOR_EACH_ENTRY_SAFE( rec_entry, next, &events, struct rec_list, entry )
+    {
+        r = msi_dialog_control_event( rec_entry->rec, dialog );
+        remove_rec_from_list( rec_entry );
+
+        if ( r != ERROR_SUCCESS )
+            goto done;
+    }
+
+done:
+    LIST_FOR_EACH_ENTRY_SAFE( rec_entry, next, &events, struct rec_list, entry )
+    {
+        remove_rec_from_list( rec_entry );
+    }
 
     return r;
 }
@@ -2100,7 +3308,6 @@ static UINT msi_dialog_checkbox_handler( msi_dialog *dialog,
 static UINT msi_dialog_edit_handler( msi_dialog *dialog,
                 msi_control *control, WPARAM param )
 {
-    UINT sz, r;
     LPWSTR buf;
 
     if( HIWORD(param) != EN_CHANGE )
@@ -2109,17 +3316,7 @@ static UINT msi_dialog_edit_handler( msi_dialog *dialog,
     TRACE("edit %s contents changed, set %s\n", debugstr_w(control->name),
           debugstr_w(control->property));
 
-    sz = 0x20;
-    buf = msi_alloc( sz*sizeof(WCHAR) );
-    while( buf )
-    {
-        r = GetWindowTextW( control->hwnd, buf, sz );
-        if( r < (sz-1) )
-            break;
-        sz *= 2;
-        buf = msi_realloc( buf, sz*sizeof(WCHAR) );
-    }
-
+    buf = msi_get_window_text( control->hwnd );
     MSI_SetPropertyW( dialog->package, control->property, buf );
 
     msi_free( buf );
@@ -2145,7 +3342,7 @@ static LRESULT msi_dialog_oncommand( msi_dialog *dialog, WPARAM param, HWND hwnd
 {
     msi_control *control = NULL;
 
-    TRACE("%p %p %08x\n", dialog, hwnd, param);
+    TRACE("%p %p %08lx\n", dialog, hwnd, param);
 
     switch (param)
     {
@@ -2167,8 +3364,20 @@ static LRESULT msi_dialog_oncommand( msi_dialog *dialog, WPARAM param, HWND hwnd
             msi_dialog_evaluate_control_conditions( dialog );
         }
     }
-    else
-        ERR("button click from nowhere %p %d %p\n", dialog, param, hwnd);
+
+    return 0;
+}
+
+static LRESULT msi_dialog_onnotify( msi_dialog *dialog, LPARAM param )
+{
+    LPNMHDR nmhdr = (LPNMHDR) param;
+    msi_control *control = msi_dialog_find_control_by_hwnd( dialog, nmhdr->hwndFrom );
+
+    TRACE("%p %p\n", dialog, nmhdr->hwndFrom);
+
+    if ( control && control->handler )
+        control->handler( dialog, control, param );
+
     return 0;
 }
 
@@ -2191,6 +3400,11 @@ static LRESULT WINAPI MSIDialog_WndProc( HWND hwnd, UINT msg,
 
     switch (msg)
     {
+    case WM_MOVE:
+        dialog->package->center_x = LOWORD(lParam) + dialog->size.cx / 2.0;
+        dialog->package->center_y = HIWORD(lParam) + dialog->size.cy / 2.0;
+        break;
+
     case WM_CREATE:
         return msi_dialog_oncreate( hwnd, (LPCREATESTRUCTW)lParam );
 
@@ -2215,6 +3429,8 @@ static LRESULT WINAPI MSIDialog_WndProc( HWND hwnd, UINT msg,
     case WM_DESTROY:
         dialog->hwnd = NULL;
         return 0;
+    case WM_NOTIFY:
+        return msi_dialog_onnotify( dialog, lParam );
     }
     return DefWindowProcW(hwnd, msg, wParam, lParam);
 }
@@ -2230,7 +3446,7 @@ static LRESULT WINAPI MSIRadioGroup_WndProc(HWND hWnd, UINT msg, WPARAM wParam,
     WNDPROC oldproc = (WNDPROC) GetPropW(hWnd, szButtonData);
     LRESULT r;
 
-    TRACE("hWnd %p msg %04x wParam 0x%08x lParam 0x%08lx\n", hWnd, msg, wParam, lParam);
+    TRACE("hWnd %p msg %04x wParam 0x%08lx lParam 0x%08lx\n", hWnd, msg, wParam, lParam);
 
     if (msg == WM_COMMAND) /* Forward notifications to dialog */
         SendMessageW(GetParent(hWnd), msg, wParam, lParam);
@@ -2264,19 +3480,24 @@ static LRESULT WINAPI MSIHiddenWindowProc( HWND hwnd, UINT msg,
 
 /* functions that interface to other modules within MSI */
 
-msi_dialog *msi_dialog_create( MSIPACKAGE* package, LPCWSTR szDialogName,
-                                msi_dialog_event_handler event_handler )
+msi_dialog *msi_dialog_create( MSIPACKAGE* package,
+                               LPCWSTR szDialogName, msi_dialog *parent,
+                               msi_dialog_event_handler event_handler )
 {
     MSIRECORD *rec = NULL;
     msi_dialog *dialog;
 
     TRACE("%p %s\n", package, debugstr_w(szDialogName));
 
+    if (!hMsiHiddenWindow)
+        msi_dialog_register_class();
+
     /* allocate the structure for the dialog to use */
     dialog = msi_alloc_zero( sizeof *dialog + sizeof(WCHAR)*strlenW(szDialogName) );
     if( !dialog )
         return NULL;
     strcpyW( dialog->name, szDialogName );
+    dialog->parent = parent;
     msiobj_addref( &package->hdr );
     dialog->package = package;
     dialog->event_handler = event_handler;
@@ -2351,16 +3572,18 @@ void msi_dialog_check_messages( HANDLE handle )
 
 UINT msi_dialog_run_message_loop( msi_dialog *dialog )
 {
+    DWORD style;
     HWND hwnd;
 
-    if( !(dialog->attributes & msidbDialogAttributesVisible) )
-        return ERROR_SUCCESS;
-
     if( uiThreadId != GetCurrentThreadId() )
         return SendMessageW( hMsiHiddenWindow, WM_MSI_DIALOG_CREATE, 0, (LPARAM) dialog );
 
     /* create the dialog window, don't show it yet */
-    hwnd = CreateWindowW( szMsiDialogClass, dialog->name, WS_OVERLAPPEDWINDOW,
+    style = WS_OVERLAPPED;
+    if( dialog->attributes & msidbDialogAttributesVisible )
+        style |= WS_VISIBLE;
+
+    hwnd = CreateWindowW( szMsiDialogClass, dialog->name, style,
                      CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
                      NULL, NULL, NULL, dialog );
     if( !hwnd )
@@ -2376,7 +3599,7 @@ UINT msi_dialog_run_message_loop( msi_dialog *dialog )
     {
         while( !dialog->finished )
         {
-            MsgWaitForMultipleObjects( 0, NULL, 0, INFINITE, QS_ALLEVENTS );
+            MsgWaitForMultipleObjects( 0, NULL, 0, INFINITE, QS_ALLINPUT );
             msi_process_pending_messages( dialog->hwnd );
         }
     }
@@ -2404,27 +3627,21 @@ void msi_dialog_destroy( msi_dialog *dialog )
 
     if( dialog->hwnd )
         ShowWindow( dialog->hwnd, SW_HIDE );
-    
+
     if( dialog->hwnd )
         DestroyWindow( dialog->hwnd );
 
+    /* unsubscribe events */
+    ControlEvent_CleanupDialogSubscriptions(dialog->package, dialog->name);
+
     /* destroy the list of controls */
     while( !list_empty( &dialog->controls ) )
     {
-        msi_control *t = LIST_ENTRY( list_head( &dialog->controls ),
-                                     msi_control, entry );
-        list_remove( &t->entry );
-        /* leave dialog->hwnd - destroying parent destroys child windows */
-        msi_free( t->property );
-        msi_free( t->value );
-        if( t->hBitmap )
-            DeleteObject( t->hBitmap );
-        if( t->hIcon )
-            DestroyIcon( t->hIcon );
-        msi_free( t->tabnext );
-        msi_free( t );
-        if (t->hDll)
-            FreeLibrary( t->hDll );
+        msi_control *t;
+
+        t = LIST_ENTRY( list_head( &dialog->controls ),
+                        msi_control, entry );
+        msi_destroy_control( t );
     }
 
     /* destroy the list of fonts */
@@ -2484,3 +3701,110 @@ void msi_dialog_unregister_class( void )
     UnregisterClassW( szMsiHiddenWindow, NULL );
     uiThreadId = 0;
 }
+
+static UINT error_dialog_handler(MSIPACKAGE *package, LPCWSTR event,
+                                 LPCWSTR argument, msi_dialog* dialog)
+{
+    static const WCHAR end_dialog[] = {'E','n','d','D','i','a','l','o','g',0};
+    static const WCHAR error_abort[] = {'E','r','r','o','r','A','b','o','r','t',0};
+    static const WCHAR error_cancel[] = {'E','r','r','o','r','C','a','n','c','e','l',0};
+    static const WCHAR error_no[] = {'E','r','r','o','r','N','o',0};
+    static const WCHAR result_prop[] = {
+        'M','S','I','E','r','r','o','r','D','i','a','l','o','g','R','e','s','u','l','t',0
+    };
+
+    if ( lstrcmpW( event, end_dialog ) )
+        return ERROR_SUCCESS;
+
+    if ( !lstrcmpW( argument, error_abort ) || !lstrcmpW( argument, error_cancel ) ||
+         !lstrcmpW( argument, error_no ) )
+    {
+         MSI_SetPropertyW( package, result_prop, error_abort );
+    }
+
+    ControlEvent_CleanupSubscriptions(package);
+    msi_dialog_end_dialog( dialog );
+
+    return ERROR_SUCCESS;
+}
+
+static UINT msi_error_dialog_set_error( MSIPACKAGE *package, LPWSTR error_dialog, LPWSTR error )
+{
+    MSIRECORD * row;
+
+    static const WCHAR update[] = 
+        {'U','P','D','A','T','E',' ','`','C','o','n','t','r','o','l','`',' ',
+         'S','E','T',' ','`','T','e','x','t','`',' ','=',' ','\'','%','s','\'',' ',
+         'W','H','E','R','E', ' ','`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',' ',
+         'A','N','D',' ','`','C','o','n','t','r','o','l','`',' ','=',' ',
+         '\'','E','r','r','o','r','T','e','x','t','\'',0};
+
+    row = MSI_QueryGetRecord( package->db, update, error, error_dialog );
+    if (!row)
+        return ERROR_FUNCTION_FAILED;
+
+    msiobj_release(&row->hdr);
+    return ERROR_SUCCESS;
+}
+
+UINT msi_spawn_error_dialog( MSIPACKAGE *package, LPWSTR error_dialog, LPWSTR error )
+{
+    msi_dialog *dialog;
+    WCHAR result[MAX_PATH];
+    UINT r = ERROR_SUCCESS;
+    DWORD size = MAX_PATH;
+    int res;
+
+    static const WCHAR szUILevel[] = {'U','I','L','e','v','e','l',0};
+    static const WCHAR pn_prop[] = {'P','r','o','d','u','c','t','N','a','m','e',0};
+    static const WCHAR title_fmt[] = {'%','s',' ','W','a','r','n','i','n','g',0};
+    static const WCHAR error_abort[] = {'E','r','r','o','r','A','b','o','r','t',0};
+    static const WCHAR result_prop[] = {
+        'M','S','I','E','r','r','o','r','D','i','a','l','o','g','R','e','s','u','l','t',0
+    };
+
+    if ( (msi_get_property_int(package, szUILevel, 0) & INSTALLUILEVEL_MASK) == INSTALLUILEVEL_NONE )
+        return ERROR_SUCCESS;
+
+    if ( !error_dialog )
+    {
+        LPWSTR product_name = msi_dup_property( package, pn_prop );
+        WCHAR title[MAX_PATH];
+
+        sprintfW( title, title_fmt, product_name );
+        res = MessageBoxW( NULL, error, title, MB_OKCANCEL | MB_ICONWARNING );
+
+        msi_free( product_name );
+
+        if ( res == IDOK )
+            return ERROR_SUCCESS;
+        else
+            return ERROR_FUNCTION_FAILED;
+    }
+
+    r = msi_error_dialog_set_error( package, error_dialog, error );
+    if ( r != ERROR_SUCCESS )
+        return r;
+
+    dialog = msi_dialog_create( package, error_dialog, package->dialog,
+                                error_dialog_handler );
+    if ( !dialog )
+        return ERROR_FUNCTION_FAILED;
+
+    dialog->finished = FALSE;
+    r = msi_dialog_run_message_loop( dialog );
+    if ( r != ERROR_SUCCESS )
+        goto done;
+
+    r = MSI_GetPropertyW( package, result_prop, result, &size );
+    if ( r != ERROR_SUCCESS)
+        r = ERROR_SUCCESS;
+
+    if ( !lstrcmpW( result, error_abort ) )
+        r = ERROR_FUNCTION_FAILED;
+
+done:
+    msi_dialog_destroy( dialog );
+
+    return r;
+}