msi: Release record instead of free.
[wine.git] / dlls / msi / dialog.c
blob1374f3b6274c9721d71fd90db225077d506a4cb3
1 /*
2 * Implementation of the Microsoft Installer (msi.dll)
4 * Copyright 2005 Mike McCormack for CodeWeavers
5 * Copyright 2005 Aric Stewart for CodeWeavers
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22 #define COBJMACROS
23 #define NONAMELESSUNION
25 #include <stdarg.h>
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 "msidefs.h"
34 #include "ocidl.h"
35 #include "olectl.h"
36 #include "richedit.h"
37 #include "commctrl.h"
38 #include "winreg.h"
39 #include "shlwapi.h"
40 #include "shellapi.h"
42 #include "wine/debug.h"
44 #include "msipriv.h"
45 #include "msiserver.h"
46 #include "resource.h"
48 WINE_DEFAULT_DEBUG_CHANNEL(msi);
50 extern HINSTANCE msi_hInstance;
52 struct control
54 struct list entry;
55 HWND hwnd;
56 UINT (*handler)( msi_dialog *, struct control *, WPARAM );
57 void (*update)( msi_dialog *, struct control * );
58 LPWSTR property;
59 LPWSTR value;
60 HBITMAP hBitmap;
61 HICON hIcon;
62 HIMAGELIST hImageList;
63 LPWSTR tabnext;
64 LPWSTR type;
65 HMODULE hDll;
66 float progress_current;
67 float progress_max;
68 BOOL progress_backwards;
69 DWORD attributes;
70 WCHAR name[1];
73 struct font
75 struct list entry;
76 HFONT hfont;
77 COLORREF color;
78 WCHAR name[1];
81 struct msi_dialog_tag
83 MSIPACKAGE *package;
84 msi_dialog *parent;
85 UINT (*event_handler)( msi_dialog *, const WCHAR *, const WCHAR * );
86 BOOL finished;
87 INT scale;
88 DWORD attributes;
89 SIZE size;
90 HWND hwnd;
91 LPWSTR default_font;
92 struct list fonts;
93 struct list controls;
94 HWND hWndFocus;
95 LPWSTR control_default;
96 LPWSTR control_cancel;
97 UINT (*pending_event)( msi_dialog *, const WCHAR * );
98 LPWSTR pending_argument;
99 INT retval;
100 WCHAR name[1];
103 struct subscriber
105 struct list entry;
106 msi_dialog *dialog;
107 WCHAR *event;
108 WCHAR *control;
109 WCHAR *attribute;
112 struct control_handler
114 LPCWSTR control_type;
115 UINT (*func)( msi_dialog *dialog, MSIRECORD *rec );
118 struct radio_button_group_descr
120 msi_dialog *dialog;
121 struct control *parent;
122 WCHAR *propval;
125 /* dialog sequencing */
127 #define WM_MSI_DIALOG_CREATE (WM_USER+0x100)
128 #define WM_MSI_DIALOG_DESTROY (WM_USER+0x101)
130 #define USER_INSTALLSTATE_ALL 0x1000
132 static DWORD uiThreadId;
133 static HWND hMsiHiddenWindow;
135 static WCHAR *get_window_text( HWND hwnd )
137 UINT sz, r;
138 WCHAR *buf, *new_buf;
140 sz = 0x20;
141 buf = malloc( sz * sizeof(WCHAR) );
142 while ( buf )
144 r = GetWindowTextW( hwnd, buf, sz );
145 if ( r < (sz - 1) )
146 break;
147 sz *= 2;
148 new_buf = realloc( buf, sz * sizeof(WCHAR) );
149 if ( !new_buf )
150 free( buf );
151 buf = new_buf;
154 return buf;
157 static INT dialog_scale_unit( msi_dialog *dialog, INT val )
159 return MulDiv( val, dialog->scale, 12 );
162 static struct control *dialog_find_control( msi_dialog *dialog, const WCHAR *name )
164 struct control *control;
166 if( !name )
167 return NULL;
168 if( !dialog->hwnd )
169 return NULL;
170 LIST_FOR_EACH_ENTRY( control, &dialog->controls, struct control, entry )
171 if( !wcscmp( control->name, name ) ) /* FIXME: case sensitive? */
172 return control;
173 return NULL;
176 static struct control *dialog_find_control_by_type( msi_dialog *dialog, const WCHAR *type )
178 struct control *control;
180 if( !type )
181 return NULL;
182 if( !dialog->hwnd )
183 return NULL;
184 LIST_FOR_EACH_ENTRY( control, &dialog->controls, struct control, entry )
185 if( !wcscmp( control->type, type ) ) /* FIXME: case sensitive? */
186 return control;
187 return NULL;
190 static struct control *dialog_find_control_by_hwnd( msi_dialog *dialog, HWND hwnd )
192 struct control *control;
194 if( !dialog->hwnd )
195 return NULL;
196 LIST_FOR_EACH_ENTRY( control, &dialog->controls, struct control, entry )
197 if( hwnd == control->hwnd )
198 return control;
199 return NULL;
202 static WCHAR *get_deformatted_field( MSIPACKAGE *package, MSIRECORD *rec, int field )
204 LPCWSTR str = MSI_RecordGetString( rec, field );
205 LPWSTR ret = NULL;
207 if (str)
208 deformat_string( package, str, &ret );
209 return ret;
212 static WCHAR *dialog_dup_property( msi_dialog *dialog, const WCHAR *property, BOOL indirect )
214 LPWSTR prop = NULL;
216 if (!property)
217 return NULL;
219 if (indirect)
220 prop = msi_dup_property( dialog->package->db, property );
222 if (!prop)
223 prop = wcsdup( property );
225 return prop;
229 * dialog_get_style
231 * Extract the {\style} string from the front of the text to display and
232 * update the pointer. Only the last style in a list is applied.
234 static WCHAR *dialog_get_style( const WCHAR *p, const WCHAR **rest )
236 LPWSTR ret;
237 LPCWSTR q, i, first;
238 DWORD len;
240 q = NULL;
241 *rest = p;
242 if( !p )
243 return NULL;
245 while ((first = wcschr( p, '{' )) && (q = wcschr( first + 1, '}' )))
247 p = first + 1;
248 if( *p != '\\' && *p != '&' )
249 return NULL;
251 /* little bit of sanity checking to stop us getting confused with RTF */
252 for( i=++p; i<q; i++ )
253 if( *i == '}' || *i == '\\' )
254 return NULL;
257 if (!q)
258 return NULL;
260 *rest = ++q;
261 len = q - p;
263 ret = malloc( len * sizeof(WCHAR) );
264 if( !ret )
265 return ret;
266 memcpy( ret, p, len*sizeof(WCHAR) );
267 ret[len-1] = 0;
268 return ret;
271 static UINT dialog_add_font( MSIRECORD *rec, void *param )
273 msi_dialog *dialog = param;
274 struct font *font;
275 LPCWSTR face, name;
276 LOGFONTW lf;
277 INT style;
278 HDC hdc;
280 /* create a font and add it to the list */
281 name = MSI_RecordGetString( rec, 1 );
282 font = malloc( offsetof( struct font, name[wcslen( name ) + 1] ) );
283 lstrcpyW( font->name, name );
284 list_add_head( &dialog->fonts, &font->entry );
286 font->color = MSI_RecordGetInteger( rec, 4 );
288 memset( &lf, 0, sizeof lf );
289 face = MSI_RecordGetString( rec, 2 );
290 lf.lfHeight = MSI_RecordGetInteger( rec, 3 );
291 style = MSI_RecordGetInteger( rec, 5 );
292 if( style & msidbTextStyleStyleBitsBold )
293 lf.lfWeight = FW_BOLD;
294 if( style & msidbTextStyleStyleBitsItalic )
295 lf.lfItalic = TRUE;
296 if( style & msidbTextStyleStyleBitsUnderline )
297 lf.lfUnderline = TRUE;
298 if( style & msidbTextStyleStyleBitsStrike )
299 lf.lfStrikeOut = TRUE;
300 lstrcpynW( lf.lfFaceName, face, LF_FACESIZE );
302 /* adjust the height */
303 hdc = GetDC( dialog->hwnd );
304 if (hdc)
306 lf.lfHeight = -MulDiv(lf.lfHeight, GetDeviceCaps(hdc, LOGPIXELSY), 72);
307 ReleaseDC( dialog->hwnd, hdc );
310 font->hfont = CreateFontIndirectW( &lf );
312 TRACE("Adding font style %s\n", debugstr_w(font->name) );
314 return ERROR_SUCCESS;
317 static struct font *dialog_find_font( msi_dialog *dialog, const WCHAR *name )
319 struct font *font = NULL;
321 LIST_FOR_EACH_ENTRY( font, &dialog->fonts, struct font, entry )
322 if( !wcscmp( font->name, name ) ) /* FIXME: case sensitive? */
323 break;
325 return font;
328 static UINT dialog_set_font( msi_dialog *dialog, HWND hwnd, const WCHAR *name )
330 struct font *font;
332 font = dialog_find_font( dialog, name );
333 if( font )
334 SendMessageW( hwnd, WM_SETFONT, (WPARAM) font->hfont, TRUE );
335 else
336 ERR("No font entry for %s\n", debugstr_w(name));
337 return ERROR_SUCCESS;
340 static UINT dialog_build_font_list( msi_dialog *dialog )
342 MSIQUERY *view;
343 UINT r;
345 TRACE("dialog %p\n", dialog );
347 r = MSI_OpenQuery( dialog->package->db, &view, L"SELECT * FROM `TextStyle`" );
348 if( r != ERROR_SUCCESS )
349 return r;
351 r = MSI_IterateRecords( view, NULL, dialog_add_font, dialog );
352 msiobj_release( &view->hdr );
353 return r;
356 static void destroy_control( struct control *t )
358 list_remove( &t->entry );
359 /* leave dialog->hwnd - destroying parent destroys child windows */
360 free( t->property );
361 free( t->value );
362 if( t->hBitmap )
363 DeleteObject( t->hBitmap );
364 if( t->hIcon )
365 DestroyIcon( t->hIcon );
366 if ( t->hImageList )
367 ImageList_Destroy( t->hImageList );
368 free( t->tabnext );
369 free( t->type );
370 if (t->hDll)
371 FreeLibrary( t->hDll );
372 free( t );
375 static struct control *dialog_create_window( msi_dialog *dialog, MSIRECORD *rec, DWORD exstyle,
376 const WCHAR *szCls, const WCHAR *name, const WCHAR *text,
377 DWORD style, HWND parent )
379 DWORD x, y, width, height;
380 LPWSTR font = NULL, title_font = NULL;
381 LPCWSTR title = NULL;
382 struct control *control;
384 style |= WS_CHILD;
386 control = malloc( offsetof( struct control, name[wcslen( name ) + 1] ) );
387 if (!control)
388 return NULL;
390 lstrcpyW( control->name, name );
391 list_add_tail( &dialog->controls, &control->entry );
392 control->handler = NULL;
393 control->update = NULL;
394 control->property = NULL;
395 control->value = NULL;
396 control->hBitmap = NULL;
397 control->hIcon = NULL;
398 control->hImageList = NULL;
399 control->hDll = NULL;
400 control->tabnext = wcsdup( MSI_RecordGetString( rec, 11 ) );
401 control->type = wcsdup( MSI_RecordGetString( rec, 3 ) );
402 control->progress_current = 0;
403 control->progress_max = 100;
404 control->progress_backwards = FALSE;
406 x = MSI_RecordGetInteger( rec, 4 );
407 y = MSI_RecordGetInteger( rec, 5 );
408 width = MSI_RecordGetInteger( rec, 6 );
409 height = MSI_RecordGetInteger( rec, 7 );
411 x = dialog_scale_unit( dialog, x );
412 y = dialog_scale_unit( dialog, y );
413 width = dialog_scale_unit( dialog, width );
414 height = dialog_scale_unit( dialog, height );
416 if( text )
418 deformat_string( dialog->package, text, &title_font );
419 font = dialog_get_style( title_font, &title );
422 control->hwnd = CreateWindowExW( exstyle, szCls, title, style,
423 x, y, width, height, parent, NULL, NULL, NULL );
425 TRACE("Dialog %s control %s hwnd %p\n",
426 debugstr_w(dialog->name), debugstr_w(text), control->hwnd );
428 dialog_set_font( dialog, control->hwnd, font ? font : dialog->default_font );
430 free( title_font );
431 free( font );
433 return control;
436 static WCHAR *dialog_get_uitext( msi_dialog *dialog, const WCHAR *key )
438 MSIRECORD *rec;
439 LPWSTR text;
441 rec = MSI_QueryGetRecord( dialog->package->db, L"SELECT * FROM `UIText` WHERE `Key` = '%s'", key );
442 if (!rec) return NULL;
443 text = wcsdup( MSI_RecordGetString( rec, 2 ) );
444 msiobj_release( &rec->hdr );
445 return text;
448 static HANDLE load_image( MSIDATABASE *db, const WCHAR *name, UINT type, UINT cx, UINT cy, UINT flags )
450 MSIRECORD *rec;
451 HANDLE himage = NULL;
452 LPWSTR tmp;
453 UINT r;
455 TRACE("%p %s %u %u %08x\n", db, debugstr_w(name), cx, cy, flags);
457 if (!(tmp = msi_create_temp_file( db ))) return NULL;
459 rec = MSI_QueryGetRecord( db, L"SELECT * FROM `Binary` WHERE `Name` = '%s'", name );
460 if( rec )
462 r = MSI_RecordStreamToFile( rec, 2, tmp );
463 if( r == ERROR_SUCCESS )
465 himage = LoadImageW( 0, tmp, type, cx, cy, flags );
467 msiobj_release( &rec->hdr );
469 DeleteFileW( tmp );
471 free( tmp );
472 return himage;
475 static HICON load_icon( MSIDATABASE *db, const WCHAR *text, UINT attributes )
477 DWORD cx = 0, cy = 0, flags;
479 flags = LR_LOADFROMFILE | LR_DEFAULTSIZE;
480 if( attributes & msidbControlAttributesFixedSize )
482 flags &= ~LR_DEFAULTSIZE;
483 if( attributes & msidbControlAttributesIconSize16 )
485 cx += 16;
486 cy += 16;
488 if( attributes & msidbControlAttributesIconSize32 )
490 cx += 32;
491 cy += 32;
493 /* msidbControlAttributesIconSize48 handled by above logic */
495 return load_image( db, text, IMAGE_ICON, cx, cy, flags );
498 static void dialog_update_controls( msi_dialog *dialog, const WCHAR *property )
500 struct control *control;
502 LIST_FOR_EACH_ENTRY( control, &dialog->controls, struct control, entry )
504 if ( control->property && !wcscmp( control->property, property ) && control->update )
505 control->update( dialog, control );
509 static void dialog_update_all_controls( msi_dialog *dialog )
511 struct control *control;
513 LIST_FOR_EACH_ENTRY( control, &dialog->controls, struct control, entry )
515 if ( control->property && control->update )
516 control->update( dialog, control );
520 static void dialog_set_property( MSIPACKAGE *package, const WCHAR *property, const WCHAR *value )
522 UINT r = msi_set_property( package->db, property, value, -1 );
523 if (r == ERROR_SUCCESS && !wcscmp( property, L"SourceDir" ))
524 msi_reset_source_folders( package );
527 static MSIFEATURE *seltree_feature_from_item( HWND hwnd, HTREEITEM hItem )
529 TVITEMW tvi;
531 /* get the feature from the item */
532 memset( &tvi, 0, sizeof tvi );
533 tvi.hItem = hItem;
534 tvi.mask = TVIF_PARAM | TVIF_HANDLE;
535 SendMessageW( hwnd, TVM_GETITEMW, 0, (LPARAM)&tvi );
536 return (MSIFEATURE *)tvi.lParam;
539 struct msi_selection_tree_info
541 msi_dialog *dialog;
542 HWND hwnd;
543 WNDPROC oldproc;
544 HTREEITEM selected;
547 static MSIFEATURE *seltree_get_selected_feature( struct control *control )
549 struct msi_selection_tree_info *info = GetPropW( control->hwnd, L"MSIDATA" );
550 return seltree_feature_from_item( control->hwnd, info->selected );
553 static void dialog_handle_event( msi_dialog *dialog, const WCHAR *control, const WCHAR *attribute, MSIRECORD *rec )
555 struct control* ctrl;
557 ctrl = dialog_find_control( dialog, control );
558 if (!ctrl)
559 return;
560 if( !wcscmp( attribute, L"Text" ) )
562 const WCHAR *font_text, *text = NULL;
563 WCHAR *font, *text_fmt = NULL;
565 font_text = MSI_RecordGetString( rec , 1 );
566 font = dialog_get_style( font_text, &text );
567 deformat_string( dialog->package, text, &text_fmt );
568 if (text_fmt) text = text_fmt;
569 else text = L"";
571 SetWindowTextW( ctrl->hwnd, text );
573 free( font );
574 free( text_fmt );
575 msi_dialog_check_messages( NULL );
577 else if( !wcscmp( attribute, L"Progress" ) )
579 DWORD func, val1, val2, units;
581 func = MSI_RecordGetInteger( rec, 1 );
582 val1 = MSI_RecordGetInteger( rec, 2 );
583 val2 = MSI_RecordGetInteger( rec, 3 );
585 TRACE( "progress: func %lu val1 %lu val2 %lu\n", func, val1, val2 );
587 units = val1 / 512;
588 switch (func)
590 case 0: /* init */
591 SendMessageW( ctrl->hwnd, PBM_SETRANGE, 0, MAKELPARAM(0,100) );
592 if (val2)
594 ctrl->progress_max = units ? units : 100;
595 ctrl->progress_current = units;
596 ctrl->progress_backwards = TRUE;
597 SendMessageW( ctrl->hwnd, PBM_SETPOS, 100, 0 );
599 else
601 ctrl->progress_max = units ? units : 100;
602 ctrl->progress_current = 0;
603 ctrl->progress_backwards = FALSE;
604 SendMessageW( ctrl->hwnd, PBM_SETPOS, 0, 0 );
606 break;
607 case 1: /* action data increment */
608 if (val2) dialog->package->action_progress_increment = val1;
609 else dialog->package->action_progress_increment = 0;
610 break;
611 case 2: /* move */
612 if (ctrl->progress_backwards)
614 if (units >= ctrl->progress_current) ctrl->progress_current -= units;
615 else ctrl->progress_current = 0;
617 else
619 if (ctrl->progress_current + units < ctrl->progress_max) ctrl->progress_current += units;
620 else ctrl->progress_current = ctrl->progress_max;
622 SendMessageW( ctrl->hwnd, PBM_SETPOS, MulDiv(100, ctrl->progress_current, ctrl->progress_max), 0 );
623 break;
624 case 3: /* add */
625 ctrl->progress_max += units;
626 break;
627 default:
628 FIXME( "unknown progress message %lu\n", func );
629 break;
632 else if ( !wcscmp( attribute, L"Property" ) )
634 MSIFEATURE *feature = seltree_get_selected_feature( ctrl );
635 if (feature) dialog_set_property( dialog->package, ctrl->property, feature->Directory );
637 else if ( !wcscmp( attribute, L"SelectionPath" ) )
639 BOOL indirect = ctrl->attributes & msidbControlAttributesIndirect;
640 WCHAR *path = dialog_dup_property( dialog, ctrl->property, indirect );
641 if (!path) return;
642 SetWindowTextW( ctrl->hwnd, path );
643 free( path );
645 else
647 FIXME("Attribute %s not being set\n", debugstr_w(attribute));
648 return;
652 static void event_subscribe( msi_dialog *dialog, const WCHAR *event, const WCHAR *control, const WCHAR *attribute )
654 struct subscriber *sub;
656 TRACE("dialog %s event %s control %s attribute %s\n", debugstr_w(dialog->name), debugstr_w(event),
657 debugstr_w(control), debugstr_w(attribute));
659 LIST_FOR_EACH_ENTRY( sub, &dialog->package->subscriptions, struct subscriber, entry )
661 if (sub->dialog == dialog &&
662 !wcsicmp( sub->event, event ) &&
663 !wcsicmp( sub->control, control ) &&
664 !wcsicmp( sub->attribute, attribute ))
666 TRACE("already subscribed\n");
667 return;
670 if (!(sub = malloc( sizeof(*sub) ))) return;
671 sub->dialog = dialog;
672 sub->event = wcsdup( event );
673 sub->control = wcsdup( control );
674 sub->attribute = wcsdup( attribute );
675 list_add_tail( &dialog->package->subscriptions, &sub->entry );
678 struct dialog_control
680 msi_dialog *dialog;
681 const WCHAR *control;
684 static UINT map_event( MSIRECORD *row, void *param )
686 struct dialog_control *dc = param;
687 const WCHAR *event = MSI_RecordGetString( row, 3 );
688 const WCHAR *attribute = MSI_RecordGetString( row, 4 );
690 event_subscribe( dc->dialog, event, dc->control, attribute );
691 return ERROR_SUCCESS;
694 static void dialog_map_events( msi_dialog *dialog, const WCHAR *control )
696 MSIQUERY *view;
697 struct dialog_control dialog_control =
699 dialog,
700 control
703 if (!MSI_OpenQuery( dialog->package->db, &view,
704 L"SELECT * FROM `EventMapping` WHERE `Dialog_` = '%s' AND `Control_` = '%s'",
705 dialog->name, control ))
707 MSI_IterateRecords( view, NULL, map_event, &dialog_control );
708 msiobj_release( &view->hdr );
712 /* everything except radio buttons */
713 static struct control *dialog_add_control( msi_dialog *dialog, MSIRECORD *rec, const WCHAR *szCls, DWORD style )
715 DWORD attributes;
716 const WCHAR *text = NULL, *name, *control_type;
717 DWORD exstyle = 0;
719 name = MSI_RecordGetString( rec, 2 );
720 control_type = MSI_RecordGetString( rec, 3 );
721 attributes = MSI_RecordGetInteger( rec, 8 );
722 if (wcscmp( control_type, L"ScrollableText" )) text = MSI_RecordGetString( rec, 10 );
724 TRACE( "%s, %s, %#lx, %s, %#lx\n", debugstr_w(szCls), debugstr_w(name), attributes, debugstr_w(text), style );
726 if( attributes & msidbControlAttributesVisible )
727 style |= WS_VISIBLE;
728 if( ~attributes & msidbControlAttributesEnabled )
729 style |= WS_DISABLED;
730 if( attributes & msidbControlAttributesSunken )
731 exstyle |= WS_EX_CLIENTEDGE;
733 dialog_map_events( dialog, name );
735 return dialog_create_window( dialog, rec, exstyle, szCls, name, text, style, dialog->hwnd );
738 struct msi_text_info
740 struct font *font;
741 WNDPROC oldproc;
742 DWORD attributes;
746 * we don't erase our own background,
747 * so we have to make sure that the parent window redraws first
749 static void text_on_settext( HWND hWnd )
751 HWND hParent;
752 RECT rc;
754 hParent = GetParent( hWnd );
755 GetWindowRect( hWnd, &rc );
756 MapWindowPoints( NULL, hParent, (LPPOINT) &rc, 2 );
757 InvalidateRect( hParent, &rc, TRUE );
760 static LRESULT WINAPI MSIText_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
762 struct msi_text_info *info;
763 LRESULT r = 0;
765 TRACE( "%p %04x %#Ix %#Ix\n", hWnd, msg, wParam, lParam );
767 info = GetPropW(hWnd, L"MSIDATA");
769 if( msg == WM_CTLCOLORSTATIC &&
770 ( info->attributes & msidbControlAttributesTransparent ) )
772 SetBkMode( (HDC)wParam, TRANSPARENT );
773 return (LRESULT) GetStockObject(NULL_BRUSH);
776 r = CallWindowProcW(info->oldproc, hWnd, msg, wParam, lParam);
777 if ( info->font )
778 SetTextColor( (HDC)wParam, info->font->color );
780 switch( msg )
782 case WM_SETTEXT:
783 text_on_settext( hWnd );
784 break;
785 case WM_NCDESTROY:
786 free( info );
787 RemovePropW( hWnd, L"MSIDATA" );
788 break;
791 return r;
794 static UINT dialog_text_control( msi_dialog *dialog, MSIRECORD *rec )
796 struct control *control;
797 struct msi_text_info *info;
798 LPCWSTR text, ptr, prop, control_name;
799 LPWSTR font_name;
801 TRACE("%p %p\n", dialog, rec);
803 control = dialog_add_control( dialog, rec, L"Static", SS_LEFT | WS_GROUP );
804 if( !control )
805 return ERROR_FUNCTION_FAILED;
807 info = malloc( sizeof *info );
808 if( !info )
809 return ERROR_SUCCESS;
811 control_name = MSI_RecordGetString( rec, 2 );
812 control->attributes = MSI_RecordGetInteger( rec, 8 );
813 prop = MSI_RecordGetString( rec, 9 );
814 control->property = dialog_dup_property( dialog, prop, FALSE );
816 text = MSI_RecordGetString( rec, 10 );
817 font_name = dialog_get_style( text, &ptr );
818 info->font = ( font_name ) ? dialog_find_font( dialog, font_name ) : NULL;
819 free( font_name );
821 info->attributes = MSI_RecordGetInteger( rec, 8 );
822 if( info->attributes & msidbControlAttributesTransparent )
823 SetWindowLongPtrW( control->hwnd, GWL_EXSTYLE, WS_EX_TRANSPARENT );
825 info->oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
826 (LONG_PTR)MSIText_WndProc );
827 SetPropW( control->hwnd, L"MSIDATA", info );
829 event_subscribe( dialog, L"SelectionPath", control_name, L"SelectionPath" );
830 return ERROR_SUCCESS;
833 /* strip any leading text style label from text field */
834 static WCHAR *get_binary_name( MSIPACKAGE *package, MSIRECORD *rec )
836 WCHAR *p, *text;
838 text = get_deformatted_field( package, rec, 10 );
839 if (!text)
840 return NULL;
842 p = text;
843 while (*p && *p != '{') p++;
844 if (!*p++) return text;
846 while (*p && *p != '}') p++;
847 if (!*p++) return text;
849 p = wcsdup( p );
850 free( text );
851 return p;
854 static UINT dialog_set_property_event( msi_dialog *dialog, const WCHAR *event, const WCHAR *arg )
856 LPWSTR p, prop, arg_fmt = NULL;
857 UINT len;
859 len = lstrlenW( event );
860 prop = malloc( len * sizeof(WCHAR) );
861 lstrcpyW( prop, &event[1] );
862 p = wcschr( prop, ']' );
863 if (p && (p[1] == 0 || p[1] == ' '))
865 *p = 0;
866 if (wcscmp( L"{}", arg )) deformat_string( dialog->package, arg, &arg_fmt );
867 dialog_set_property( dialog->package, prop, arg_fmt );
868 dialog_update_controls( dialog, prop );
869 free( arg_fmt );
871 else ERR("Badly formatted property string - what happens?\n");
872 free( prop );
873 return ERROR_SUCCESS;
876 static UINT dialog_send_event( msi_dialog *dialog, const WCHAR *event, const WCHAR *arg )
878 LPWSTR event_fmt = NULL, arg_fmt = NULL;
880 TRACE("Sending control event %s %s\n", debugstr_w(event), debugstr_w(arg));
882 deformat_string( dialog->package, event, &event_fmt );
883 deformat_string( dialog->package, arg, &arg_fmt );
885 dialog->event_handler( dialog, event_fmt, arg_fmt );
887 free( event_fmt );
888 free( arg_fmt );
890 return ERROR_SUCCESS;
893 static UINT dialog_control_event( MSIRECORD *rec, void *param )
895 msi_dialog *dialog = param;
896 LPCWSTR condition, event, arg;
897 UINT r;
899 condition = MSI_RecordGetString( rec, 5 );
900 r = MSI_EvaluateConditionW( dialog->package, condition );
901 if (r == MSICONDITION_TRUE || r == MSICONDITION_NONE)
903 event = MSI_RecordGetString( rec, 3 );
904 arg = MSI_RecordGetString( rec, 4 );
905 if (event[0] == '[')
906 dialog_set_property_event( dialog, event, arg );
907 else
908 dialog_send_event( dialog, event, arg );
910 return ERROR_SUCCESS;
913 static UINT dialog_button_handler( msi_dialog *dialog, struct control *control, WPARAM param )
915 MSIQUERY *view;
916 UINT r;
918 if (HIWORD(param) != BN_CLICKED)
919 return ERROR_SUCCESS;
921 r = MSI_OpenQuery( dialog->package->db, &view,
922 L"SELECT * FROM `ControlEvent` WHERE `Dialog_` = '%s' AND `Control_` = '%s' ORDER BY `Ordering`",
923 dialog->name, control->name );
924 if (r != ERROR_SUCCESS)
926 ERR("query failed\n");
927 return ERROR_SUCCESS;
929 r = MSI_IterateRecords( view, 0, dialog_control_event, dialog );
930 msiobj_release( &view->hdr );
932 /* dialog control events must be processed last regardless of ordering */
933 if (dialog->pending_event)
935 r = dialog->pending_event( dialog, dialog->pending_argument );
937 free( dialog->pending_argument );
938 dialog->pending_event = NULL;
939 dialog->pending_argument = NULL;
941 return r;
944 static HBITMAP load_picture( MSIDATABASE *db, const WCHAR *name, INT cx, INT cy, DWORD flags )
946 HBITMAP hOleBitmap = 0, hBitmap = 0, hOldSrcBitmap, hOldDestBitmap;
947 MSIRECORD *rec = NULL;
948 IStream *stm = NULL;
949 IPicture *pic = NULL;
950 HDC srcdc, destdc;
951 BITMAP bm;
952 UINT r;
954 rec = MSI_QueryGetRecord( db, L"SELECT * FROM `Binary` WHERE `Name` = '%s'", name );
955 if (!rec)
956 goto end;
958 r = MSI_RecordGetIStream( rec, 2, &stm );
959 msiobj_release( &rec->hdr );
960 if (r != ERROR_SUCCESS)
961 goto end;
963 r = OleLoadPicture( stm, 0, TRUE, &IID_IPicture, (void **)&pic );
964 IStream_Release( stm );
965 if (FAILED( r ))
967 ERR("failed to load picture\n");
968 goto end;
971 r = IPicture_get_Handle( pic, (OLE_HANDLE *)&hOleBitmap );
972 if (FAILED( r ))
974 ERR("failed to get bitmap handle\n");
975 goto end;
978 /* make the bitmap the desired size */
979 r = GetObjectW( hOleBitmap, sizeof(bm), &bm );
980 if (r != sizeof(bm))
982 ERR("failed to get bitmap size\n");
983 goto end;
986 if (flags & LR_DEFAULTSIZE)
988 cx = bm.bmWidth;
989 cy = bm.bmHeight;
992 srcdc = CreateCompatibleDC( NULL );
993 hOldSrcBitmap = SelectObject( srcdc, hOleBitmap );
994 destdc = CreateCompatibleDC( NULL );
995 hBitmap = CreateCompatibleBitmap( srcdc, cx, cy );
996 hOldDestBitmap = SelectObject( destdc, hBitmap );
997 StretchBlt( destdc, 0, 0, cx, cy, srcdc, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY );
998 SelectObject( srcdc, hOldSrcBitmap );
999 SelectObject( destdc, hOldDestBitmap );
1000 DeleteDC( srcdc );
1001 DeleteDC( destdc );
1003 end:
1004 if (pic) IPicture_Release( pic );
1005 return hBitmap;
1008 static UINT dialog_button_control( msi_dialog *dialog, MSIRECORD *rec )
1010 struct control *control;
1011 UINT attributes, style, cx = 0, cy = 0, flags = 0;
1012 WCHAR *name = NULL;
1014 TRACE("%p %p\n", dialog, rec);
1016 style = WS_TABSTOP;
1017 attributes = MSI_RecordGetInteger( rec, 8 );
1018 if (attributes & msidbControlAttributesIcon) style |= BS_ICON;
1019 else if (attributes & msidbControlAttributesBitmap)
1021 style |= BS_BITMAP;
1022 if (attributes & msidbControlAttributesFixedSize) flags |= LR_DEFAULTSIZE;
1023 else
1025 cx = dialog_scale_unit( dialog, MSI_RecordGetInteger(rec, 6) );
1026 cy = dialog_scale_unit( dialog, MSI_RecordGetInteger(rec, 7) );
1030 control = dialog_add_control( dialog, rec, L"BUTTON", style );
1031 if (!control)
1032 return ERROR_FUNCTION_FAILED;
1034 control->handler = dialog_button_handler;
1036 if (attributes & msidbControlAttributesIcon)
1038 name = get_binary_name( dialog->package, rec );
1039 control->hIcon = load_icon( dialog->package->db, name, attributes );
1040 if (control->hIcon)
1042 SendMessageW( control->hwnd, BM_SETIMAGE, IMAGE_ICON, (LPARAM) control->hIcon );
1044 else ERR("Failed to load icon %s\n", debugstr_w(name));
1046 else if (attributes & msidbControlAttributesBitmap)
1048 name = get_binary_name( dialog->package, rec );
1049 control->hBitmap = load_picture( dialog->package->db, name, cx, cy, flags );
1050 if (control->hBitmap)
1052 SendMessageW( control->hwnd, BM_SETIMAGE, IMAGE_BITMAP, (LPARAM) control->hBitmap );
1054 else ERR("Failed to load bitmap %s\n", debugstr_w(name));
1057 free( name );
1058 return ERROR_SUCCESS;
1061 static WCHAR *get_checkbox_value( msi_dialog *dialog, const WCHAR *prop )
1063 MSIRECORD *rec = NULL;
1064 LPWSTR ret = NULL;
1066 /* find if there is a value associated with the checkbox */
1067 rec = MSI_QueryGetRecord( dialog->package->db, L"SELECT * FROM `CheckBox` WHERE `Property` = '%s'", prop );
1068 if (!rec)
1069 return ret;
1071 ret = get_deformatted_field( dialog->package, rec, 2 );
1072 if( ret && !ret[0] )
1074 free( ret );
1075 ret = NULL;
1077 msiobj_release( &rec->hdr );
1078 if (ret)
1079 return ret;
1081 ret = msi_dup_property( dialog->package->db, prop );
1082 if( ret && !ret[0] )
1084 free( ret );
1085 ret = NULL;
1088 return ret;
1091 static UINT dialog_get_checkbox_state( msi_dialog *dialog, struct control *control )
1093 WCHAR state[2] = {0};
1094 DWORD sz = 2;
1096 msi_get_property( dialog->package->db, control->property, state, &sz );
1097 return state[0] ? 1 : 0;
1100 static void dialog_set_checkbox_state( msi_dialog *dialog, struct control *control, UINT state )
1102 LPCWSTR val;
1104 /* if uncheck then the property is set to NULL */
1105 if (!state)
1107 dialog_set_property( dialog->package, control->property, NULL );
1108 return;
1111 /* check for a custom state */
1112 if (control->value && control->value[0])
1113 val = control->value;
1114 else
1115 val = L"1";
1117 dialog_set_property( dialog->package, control->property, val );
1120 static void dialog_checkbox_sync_state( msi_dialog *dialog, struct control *control )
1122 UINT state = dialog_get_checkbox_state( dialog, control );
1123 SendMessageW( control->hwnd, BM_SETCHECK, state ? BST_CHECKED : BST_UNCHECKED, 0 );
1126 static UINT dialog_checkbox_handler( msi_dialog *dialog, struct control *control, WPARAM param )
1128 UINT state;
1130 if (HIWORD(param) != BN_CLICKED)
1131 return ERROR_SUCCESS;
1133 TRACE("clicked checkbox %s, set %s\n", debugstr_w(control->name), debugstr_w(control->property));
1135 state = dialog_get_checkbox_state( dialog, control );
1136 state = state ? 0 : 1;
1137 dialog_set_checkbox_state( dialog, control, state );
1138 dialog_checkbox_sync_state( dialog, control );
1140 return dialog_button_handler( dialog, control, param );
1143 static UINT dialog_checkbox_control( msi_dialog *dialog, MSIRECORD *rec )
1145 struct control *control;
1146 LPCWSTR prop;
1148 TRACE("%p %p\n", dialog, rec);
1150 control = dialog_add_control( dialog, rec, L"BUTTON", BS_CHECKBOX | BS_MULTILINE | WS_TABSTOP );
1151 control->handler = dialog_checkbox_handler;
1152 control->update = dialog_checkbox_sync_state;
1153 prop = MSI_RecordGetString( rec, 9 );
1154 if (prop)
1156 control->property = wcsdup( prop );
1157 control->value = get_checkbox_value( dialog, prop );
1158 TRACE("control %s value %s\n", debugstr_w(control->property), debugstr_w(control->value));
1160 dialog_checkbox_sync_state( dialog, control );
1161 return ERROR_SUCCESS;
1164 static UINT dialog_line_control( msi_dialog *dialog, MSIRECORD *rec )
1166 DWORD attributes;
1167 LPCWSTR name;
1168 DWORD style, exstyle = 0;
1169 DWORD x, y, width, height;
1170 struct control *control;
1172 TRACE("%p %p\n", dialog, rec);
1174 style = WS_CHILD | SS_ETCHEDHORZ | SS_SUNKEN;
1176 name = MSI_RecordGetString( rec, 2 );
1177 attributes = MSI_RecordGetInteger( rec, 8 );
1179 if( attributes & msidbControlAttributesVisible )
1180 style |= WS_VISIBLE;
1181 if( ~attributes & msidbControlAttributesEnabled )
1182 style |= WS_DISABLED;
1183 if( attributes & msidbControlAttributesSunken )
1184 exstyle |= WS_EX_CLIENTEDGE;
1186 dialog_map_events( dialog, name );
1188 control = malloc( offsetof( struct control, name[wcslen( name ) + 1] ) );
1189 if (!control)
1190 return ERROR_OUTOFMEMORY;
1192 lstrcpyW( control->name, name );
1193 list_add_head( &dialog->controls, &control->entry );
1194 control->handler = NULL;
1195 control->property = NULL;
1196 control->value = NULL;
1197 control->hBitmap = NULL;
1198 control->hIcon = NULL;
1199 control->hDll = NULL;
1200 control->tabnext = wcsdup( MSI_RecordGetString( rec, 11 ) );
1201 control->type = wcsdup( MSI_RecordGetString( rec, 3 ) );
1202 control->progress_current = 0;
1203 control->progress_max = 100;
1204 control->progress_backwards = FALSE;
1206 x = MSI_RecordGetInteger( rec, 4 );
1207 y = MSI_RecordGetInteger( rec, 5 );
1208 width = MSI_RecordGetInteger( rec, 6 );
1210 x = dialog_scale_unit( dialog, x );
1211 y = dialog_scale_unit( dialog, y );
1212 width = dialog_scale_unit( dialog, width );
1213 height = 2; /* line is exactly 2 units in height */
1215 control->hwnd = CreateWindowExW( exstyle, L"Static", NULL, style,
1216 x, y, width, height, dialog->hwnd, NULL, NULL, NULL );
1218 TRACE("Dialog %s control %s hwnd %p\n",
1219 debugstr_w(dialog->name), debugstr_w(name), control->hwnd );
1221 return ERROR_SUCCESS;
1224 /******************** Scroll Text ********************************************/
1226 struct msi_scrolltext_info
1228 msi_dialog *dialog;
1229 struct control *control;
1230 WNDPROC oldproc;
1233 static LRESULT WINAPI MSIScrollText_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
1235 struct msi_scrolltext_info *info;
1236 HRESULT r;
1238 TRACE( "%p %04x %#Ix %#Ix\n", hWnd, msg, wParam, lParam );
1240 info = GetPropW( hWnd, L"MSIDATA" );
1242 r = CallWindowProcW( info->oldproc, hWnd, msg, wParam, lParam );
1244 switch( msg )
1246 case WM_GETDLGCODE:
1247 return DLGC_WANTARROWS;
1248 case WM_NCDESTROY:
1249 free( info );
1250 RemovePropW( hWnd, L"MSIDATA" );
1251 break;
1252 case WM_PAINT:
1253 /* native MSI sets a wait cursor here */
1254 dialog_button_handler( info->dialog, info->control, BN_CLICKED );
1255 break;
1257 return r;
1260 struct msi_streamin_info
1262 LPSTR string;
1263 DWORD offset;
1264 DWORD length;
1267 static DWORD CALLBACK richedit_stream_in( DWORD_PTR arg, BYTE *buffer, LONG count, LONG *pcb )
1269 struct msi_streamin_info *info = (struct msi_streamin_info*) arg;
1271 if( (count + info->offset) > info->length )
1272 count = info->length - info->offset;
1273 memcpy( buffer, &info->string[ info->offset ], count );
1274 *pcb = count;
1275 info->offset += count;
1277 TRACE( "%lu/%lu\n", info->offset, info->length );
1279 return 0;
1282 static void scrolltext_add_text( struct control *control, const WCHAR *text )
1284 struct msi_streamin_info info;
1285 EDITSTREAM es;
1287 info.string = strdupWtoA( text );
1288 info.offset = 0;
1289 info.length = lstrlenA( info.string ) + 1;
1291 es.dwCookie = (DWORD_PTR) &info;
1292 es.dwError = 0;
1293 es.pfnCallback = richedit_stream_in;
1295 SendMessageW( control->hwnd, EM_STREAMIN, SF_RTF, (LPARAM) &es );
1297 free( info.string );
1300 static UINT dialog_scrolltext_control( msi_dialog *dialog, MSIRECORD *rec )
1302 struct msi_scrolltext_info *info;
1303 struct control *control;
1304 HMODULE hRichedit;
1305 LPCWSTR text;
1306 DWORD style;
1308 info = malloc( sizeof *info );
1309 if (!info)
1310 return ERROR_FUNCTION_FAILED;
1312 hRichedit = LoadLibraryA("riched20");
1314 style = WS_BORDER | ES_MULTILINE | WS_VSCROLL |
1315 ES_READONLY | ES_AUTOVSCROLL | WS_TABSTOP;
1316 control = dialog_add_control( dialog, rec, L"RichEdit20W", style );
1317 if (!control)
1319 FreeLibrary( hRichedit );
1320 free( info );
1321 return ERROR_FUNCTION_FAILED;
1324 control->hDll = hRichedit;
1326 info->dialog = dialog;
1327 info->control = control;
1329 /* subclass the static control */
1330 info->oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
1331 (LONG_PTR)MSIScrollText_WndProc );
1332 SetPropW( control->hwnd, L"MSIDATA", info );
1334 /* add the text into the richedit */
1335 text = MSI_RecordGetString( rec, 10 );
1336 if (text)
1337 scrolltext_add_text( control, text );
1339 return ERROR_SUCCESS;
1343 static UINT dialog_bitmap_control( msi_dialog *dialog, MSIRECORD *rec )
1345 UINT cx, cy, flags, style, attributes;
1346 struct control *control;
1347 LPWSTR name;
1349 flags = LR_LOADFROMFILE;
1350 style = SS_BITMAP | SS_LEFT | WS_GROUP;
1352 attributes = MSI_RecordGetInteger( rec, 8 );
1353 if( attributes & msidbControlAttributesFixedSize )
1355 flags |= LR_DEFAULTSIZE;
1356 style |= SS_CENTERIMAGE;
1359 control = dialog_add_control( dialog, rec, L"Static", style );
1360 cx = MSI_RecordGetInteger( rec, 6 );
1361 cy = MSI_RecordGetInteger( rec, 7 );
1362 cx = dialog_scale_unit( dialog, cx );
1363 cy = dialog_scale_unit( dialog, cy );
1365 name = get_binary_name( dialog->package, rec );
1366 control->hBitmap = load_picture( dialog->package->db, name, cx, cy, flags );
1367 if( control->hBitmap )
1368 SendMessageW( control->hwnd, STM_SETIMAGE,
1369 IMAGE_BITMAP, (LPARAM) control->hBitmap );
1370 else
1371 ERR("Failed to load bitmap %s\n", debugstr_w(name));
1373 free( name );
1375 return ERROR_SUCCESS;
1378 static UINT dialog_icon_control( msi_dialog *dialog, MSIRECORD *rec )
1380 struct control *control;
1381 DWORD attributes;
1382 LPWSTR name;
1384 TRACE("\n");
1386 control = dialog_add_control( dialog, rec, L"Static", SS_ICON | SS_CENTERIMAGE | WS_GROUP );
1388 attributes = MSI_RecordGetInteger( rec, 8 );
1389 name = get_binary_name( dialog->package, rec );
1390 control->hIcon = load_icon( dialog->package->db, name, attributes );
1391 if( control->hIcon )
1392 SendMessageW( control->hwnd, STM_SETICON, (WPARAM) control->hIcon, 0 );
1393 else
1394 ERR("Failed to load bitmap %s\n", debugstr_w(name));
1395 free( name );
1396 return ERROR_SUCCESS;
1399 /******************** Combo Box ***************************************/
1401 struct msi_combobox_info
1403 msi_dialog *dialog;
1404 HWND hwnd;
1405 WNDPROC oldproc;
1406 DWORD num_items;
1407 DWORD addpos_items;
1408 LPWSTR *items;
1411 static LRESULT WINAPI MSIComboBox_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
1413 struct msi_combobox_info *info;
1414 LRESULT r;
1415 DWORD j;
1417 TRACE( "%p %04x %#Ix %#Ix\n", hWnd, msg, wParam, lParam );
1419 info = GetPropW( hWnd, L"MSIDATA" );
1420 if (!info)
1421 return 0;
1423 r = CallWindowProcW( info->oldproc, hWnd, msg, wParam, lParam );
1425 switch (msg)
1427 case WM_NCDESTROY:
1428 for (j = 0; j < info->num_items; j++)
1429 free( info->items[j] );
1430 free( info->items );
1431 free( info );
1432 RemovePropW( hWnd, L"MSIDATA" );
1433 break;
1436 return r;
1439 static UINT combobox_add_item( MSIRECORD *rec, void *param )
1441 struct msi_combobox_info *info = param;
1442 LPCWSTR value, text;
1443 int pos;
1445 value = MSI_RecordGetString( rec, 3 );
1446 text = MSI_RecordGetString( rec, 4 );
1448 info->items[info->addpos_items] = wcsdup( value );
1450 pos = SendMessageW( info->hwnd, CB_ADDSTRING, 0, (LPARAM)text );
1451 SendMessageW( info->hwnd, CB_SETITEMDATA, pos, (LPARAM)info->items[info->addpos_items] );
1452 info->addpos_items++;
1454 return ERROR_SUCCESS;
1457 static UINT combobox_add_items( struct msi_combobox_info *info, const WCHAR *property )
1459 MSIQUERY *view;
1460 DWORD count;
1461 UINT r;
1463 r = MSI_OpenQuery( info->dialog->package->db, &view,
1464 L"SELECT * FROM `ComboBox` WHERE `Property` = '%s' ORDER BY `Order`", property );
1465 if (r != ERROR_SUCCESS)
1466 return r;
1468 /* just get the number of records */
1469 count = 0;
1470 r = MSI_IterateRecords( view, &count, NULL, NULL );
1471 if (r != ERROR_SUCCESS)
1473 msiobj_release( &view->hdr );
1474 return r;
1476 info->num_items = count;
1477 info->items = malloc( sizeof(*info->items) * count );
1479 r = MSI_IterateRecords( view, NULL, combobox_add_item, info );
1480 msiobj_release( &view->hdr );
1481 return r;
1484 static UINT dialog_set_control_condition( MSIRECORD *rec, void *param )
1486 msi_dialog *dialog = param;
1487 struct control *control;
1488 LPCWSTR name, action, condition;
1489 UINT r;
1491 name = MSI_RecordGetString( rec, 2 );
1492 action = MSI_RecordGetString( rec, 3 );
1493 condition = MSI_RecordGetString( rec, 4 );
1494 r = MSI_EvaluateConditionW( dialog->package, condition );
1495 control = dialog_find_control( dialog, name );
1496 if (r == MSICONDITION_TRUE && control)
1498 TRACE("%s control %s\n", debugstr_w(action), debugstr_w(name));
1500 /* FIXME: case sensitive? */
1501 if (!wcscmp( action, L"Hide" ))
1502 ShowWindow(control->hwnd, SW_HIDE);
1503 else if (!wcscmp( action, L"Show" ))
1504 ShowWindow(control->hwnd, SW_SHOW);
1505 else if (!wcscmp( action, L"Disable" ))
1506 EnableWindow(control->hwnd, FALSE);
1507 else if (!wcscmp( action, L"Enable" ))
1508 EnableWindow(control->hwnd, TRUE);
1509 else if (!wcscmp( action, L"Default" ))
1510 SetFocus(control->hwnd);
1511 else
1512 FIXME("Unhandled action %s\n", debugstr_w(action));
1514 return ERROR_SUCCESS;
1517 static UINT dialog_evaluate_control_conditions( msi_dialog *dialog )
1519 UINT r;
1520 MSIQUERY *view;
1521 MSIPACKAGE *package = dialog->package;
1523 TRACE("%p %s\n", dialog, debugstr_w(dialog->name));
1525 /* query the Control table for all the elements of the control */
1526 r = MSI_OpenQuery( package->db, &view, L"SELECT * FROM `ControlCondition` WHERE `Dialog_` = '%s'", dialog->name );
1527 if (r != ERROR_SUCCESS)
1528 return ERROR_SUCCESS;
1530 r = MSI_IterateRecords( view, 0, dialog_set_control_condition, dialog );
1531 msiobj_release( &view->hdr );
1532 return r;
1535 static UINT dialog_combobox_handler( msi_dialog *dialog, struct control *control, WPARAM param )
1537 struct msi_combobox_info *info;
1538 int index;
1539 LPWSTR value;
1541 if (HIWORD(param) != CBN_SELCHANGE && HIWORD(param) != CBN_EDITCHANGE)
1542 return ERROR_SUCCESS;
1544 info = GetPropW( control->hwnd, L"MSIDATA" );
1545 index = SendMessageW( control->hwnd, CB_GETCURSEL, 0, 0 );
1546 if (index == CB_ERR)
1547 value = get_window_text( control->hwnd );
1548 else
1549 value = (LPWSTR) SendMessageW( control->hwnd, CB_GETITEMDATA, index, 0 );
1551 dialog_set_property( info->dialog->package, control->property, value );
1552 dialog_evaluate_control_conditions( info->dialog );
1554 if (index == CB_ERR)
1555 free( value );
1557 return ERROR_SUCCESS;
1560 static void dialog_combobox_update( msi_dialog *dialog, struct control *control )
1562 struct msi_combobox_info *info;
1563 LPWSTR value, tmp;
1564 DWORD j;
1566 info = GetPropW( control->hwnd, L"MSIDATA" );
1568 value = msi_dup_property( dialog->package->db, control->property );
1569 if (!value)
1571 SendMessageW( control->hwnd, CB_SETCURSEL, -1, 0 );
1572 return;
1575 for (j = 0; j < info->num_items; j++)
1577 tmp = (LPWSTR) SendMessageW( control->hwnd, CB_GETITEMDATA, j, 0 );
1578 if (!wcscmp( value, tmp ))
1579 break;
1582 if (j < info->num_items)
1584 SendMessageW( control->hwnd, CB_SETCURSEL, j, 0 );
1586 else
1588 SendMessageW( control->hwnd, CB_SETCURSEL, -1, 0 );
1589 SetWindowTextW( control->hwnd, value );
1592 free( value );
1595 static UINT dialog_combo_control( msi_dialog *dialog, MSIRECORD *rec )
1597 struct msi_combobox_info *info;
1598 struct control *control;
1599 DWORD attributes, style;
1600 LPCWSTR prop;
1602 info = malloc( sizeof *info );
1603 if (!info)
1604 return ERROR_FUNCTION_FAILED;
1606 style = CBS_AUTOHSCROLL | WS_TABSTOP | WS_GROUP | WS_CHILD;
1607 attributes = MSI_RecordGetInteger( rec, 8 );
1608 if ( ~attributes & msidbControlAttributesSorted)
1609 style |= CBS_SORT;
1610 if ( attributes & msidbControlAttributesComboList)
1611 style |= CBS_DROPDOWNLIST;
1612 else
1613 style |= CBS_DROPDOWN;
1615 control = dialog_add_control( dialog, rec, WC_COMBOBOXW, style );
1616 if (!control)
1618 free( info );
1619 return ERROR_FUNCTION_FAILED;
1622 control->handler = dialog_combobox_handler;
1623 control->update = dialog_combobox_update;
1625 prop = MSI_RecordGetString( rec, 9 );
1626 control->property = dialog_dup_property( dialog, prop, FALSE );
1628 /* subclass */
1629 info->dialog = dialog;
1630 info->hwnd = control->hwnd;
1631 info->items = NULL;
1632 info->addpos_items = 0;
1633 info->oldproc = (WNDPROC)SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
1634 (LONG_PTR)MSIComboBox_WndProc );
1635 SetPropW( control->hwnd, L"MSIDATA", info );
1637 if (control->property)
1638 combobox_add_items( info, control->property );
1640 dialog_combobox_update( dialog, control );
1642 return ERROR_SUCCESS;
1645 static UINT dialog_edit_handler( msi_dialog *dialog, struct control *control, WPARAM param )
1647 LPWSTR buf;
1649 if (HIWORD(param) != EN_CHANGE)
1650 return ERROR_SUCCESS;
1652 TRACE("edit %s contents changed, set %s\n", debugstr_w(control->name), debugstr_w(control->property));
1654 buf = get_window_text( control->hwnd );
1655 dialog_set_property( dialog->package, control->property, buf );
1656 free( buf );
1658 return ERROR_SUCCESS;
1661 /* length of 2^32 + 1 */
1662 #define MAX_NUM_DIGITS 11
1664 static UINT dialog_edit_control( msi_dialog *dialog, MSIRECORD *rec )
1666 struct control *control;
1667 LPCWSTR prop, text;
1668 LPWSTR val, begin, end;
1669 WCHAR num[MAX_NUM_DIGITS];
1670 DWORD limit;
1672 control = dialog_add_control( dialog, rec, L"Edit", WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL );
1673 control->handler = dialog_edit_handler;
1675 text = MSI_RecordGetString( rec, 10 );
1676 if ( text )
1678 begin = wcschr( text, '{' );
1679 end = wcschr( text, '}' );
1681 if ( begin && end && end > begin &&
1682 begin[0] >= '0' && begin[0] <= '9' &&
1683 end - begin < MAX_NUM_DIGITS)
1685 lstrcpynW( num, begin + 1, end - begin );
1686 limit = wcstol( num, NULL, 10 );
1688 SendMessageW( control->hwnd, EM_SETLIMITTEXT, limit, 0 );
1692 prop = MSI_RecordGetString( rec, 9 );
1693 if( prop )
1694 control->property = wcsdup( prop );
1696 val = msi_dup_property( dialog->package->db, control->property );
1697 SetWindowTextW( control->hwnd, val );
1698 free( val );
1699 return ERROR_SUCCESS;
1702 /******************** Masked Edit ********************************************/
1704 #define MASK_MAX_GROUPS 20
1706 struct msi_mask_group
1708 UINT len;
1709 UINT ofs;
1710 WCHAR type;
1711 HWND hwnd;
1714 struct msi_maskedit_info
1716 msi_dialog *dialog;
1717 WNDPROC oldproc;
1718 HWND hwnd;
1719 LPWSTR prop;
1720 UINT num_chars;
1721 UINT num_groups;
1722 struct msi_mask_group group[MASK_MAX_GROUPS];
1725 static BOOL mask_editable( WCHAR type )
1727 switch (type)
1729 case '%':
1730 case '#':
1731 case '&':
1732 case '`':
1733 case '?':
1734 case '^':
1735 return TRUE;
1737 return FALSE;
1740 static void mask_control_change( struct msi_maskedit_info *info )
1742 LPWSTR val;
1743 UINT i, n, r;
1745 val = malloc( (info->num_chars + 1) * sizeof(WCHAR) );
1746 for( i=0, n=0; i<info->num_groups; i++ )
1748 if (info->group[i].len == ~0u)
1750 UINT len = SendMessageW( info->group[i].hwnd, WM_GETTEXTLENGTH, 0, 0 );
1751 val = realloc( val, (len + 1) * sizeof(WCHAR) );
1752 GetWindowTextW( info->group[i].hwnd, val, len + 1 );
1754 else
1756 if (info->group[i].len + n > info->num_chars)
1758 ERR("can't fit control %d text into template\n",i);
1759 break;
1761 if (!mask_editable(info->group[i].type))
1763 for(r=0; r<info->group[i].len; r++)
1764 val[n+r] = info->group[i].type;
1765 val[n+r] = 0;
1767 else
1769 r = GetWindowTextW( info->group[i].hwnd, &val[n], info->group[i].len+1 );
1770 if( r != info->group[i].len )
1771 break;
1773 n += r;
1777 TRACE("%d/%d controls were good\n", i, info->num_groups);
1779 if( i == info->num_groups )
1781 TRACE("Set property %s to %s\n", debugstr_w(info->prop), debugstr_w(val));
1782 dialog_set_property( info->dialog->package, info->prop, val );
1783 dialog_evaluate_control_conditions( info->dialog );
1785 free( val );
1788 /* now move to the next control if necessary */
1789 static void mask_next_control( struct msi_maskedit_info *info, HWND hWnd )
1791 HWND hWndNext;
1792 UINT len, i;
1794 for( i=0; i<info->num_groups; i++ )
1795 if( info->group[i].hwnd == hWnd )
1796 break;
1798 /* don't move from the last control */
1799 if( i >= (info->num_groups-1) )
1800 return;
1802 len = SendMessageW( hWnd, WM_GETTEXTLENGTH, 0, 0 );
1803 if( len < info->group[i].len )
1804 return;
1806 hWndNext = GetNextDlgTabItem( GetParent( hWnd ), hWnd, FALSE );
1807 SetFocus( hWndNext );
1810 static LRESULT WINAPI MSIMaskedEdit_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
1812 struct msi_maskedit_info *info;
1813 HRESULT r;
1815 TRACE("%p %04x %#Ix %#Ix\n", hWnd, msg, wParam, lParam);
1817 info = GetPropW(hWnd, L"MSIDATA");
1819 r = CallWindowProcW(info->oldproc, hWnd, msg, wParam, lParam);
1821 switch( msg )
1823 case WM_COMMAND:
1824 if (HIWORD(wParam) == EN_CHANGE)
1826 mask_control_change( info );
1827 mask_next_control( info, (HWND) lParam );
1829 break;
1830 case WM_NCDESTROY:
1831 free( info->prop );
1832 free( info );
1833 RemovePropW( hWnd, L"MSIDATA" );
1834 break;
1837 return r;
1840 /* fish the various bits of the property out and put them in the control */
1841 static void maskedit_set_text( struct msi_maskedit_info *info, const WCHAR *text )
1843 LPCWSTR p;
1844 UINT i;
1846 p = text;
1847 for( i = 0; i < info->num_groups; i++ )
1849 if( info->group[i].len < lstrlenW( p ) )
1851 WCHAR *chunk = wcsdup( p );
1852 chunk[ info->group[i].len ] = 0;
1853 SetWindowTextW( info->group[i].hwnd, chunk );
1854 free( chunk );
1856 else
1858 SetWindowTextW( info->group[i].hwnd, p );
1859 break;
1861 p += info->group[i].len;
1865 static struct msi_maskedit_info *dialog_parse_groups( const WCHAR *mask )
1867 struct msi_maskedit_info *info;
1868 int i = 0, n = 0, total = 0;
1869 LPCWSTR p;
1871 TRACE("masked control, template %s\n", debugstr_w(mask));
1873 if( !mask )
1874 return NULL;
1876 info = calloc( 1, sizeof *info );
1877 if( !info )
1878 return info;
1880 p = wcschr(mask, '<');
1881 if( p )
1882 p++;
1883 else
1884 p = mask;
1886 for( i=0; i<MASK_MAX_GROUPS; i++ )
1888 /* stop at the end of the string */
1889 if( p[0] == 0 || p[0] == '>' )
1891 if (!total)
1893 /* create a group for the empty mask */
1894 info->group[0].type = '&';
1895 info->group[0].len = ~0u;
1896 i = 1;
1898 break;
1901 /* count the number of the same identifier */
1902 for( n=0; p[n] == p[0]; n++ )
1904 info->group[i].ofs = total;
1905 info->group[i].type = p[0];
1906 if( p[n] == '=' )
1908 n++;
1909 total++; /* an extra not part of the group */
1911 info->group[i].len = n;
1912 total += n;
1913 p += n;
1916 TRACE("%d characters in %d groups\n", total, i );
1917 if( i == MASK_MAX_GROUPS )
1918 ERR("too many groups in PIDTemplate %s\n", debugstr_w(mask));
1920 info->num_chars = total;
1921 info->num_groups = i;
1923 return info;
1926 static void maskedit_create_children( struct msi_maskedit_info *info, const WCHAR *font )
1928 DWORD width, height, style, wx, ww;
1929 RECT rect;
1930 HWND hwnd;
1931 UINT i;
1933 style = WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL;
1935 GetClientRect( info->hwnd, &rect );
1937 width = rect.right - rect.left;
1938 height = rect.bottom - rect.top;
1940 for( i = 0; i < info->num_groups; i++ )
1942 if (!mask_editable( info->group[i].type ))
1943 continue;
1944 if (info->num_chars)
1946 wx = (info->group[i].ofs * width) / info->num_chars;
1947 ww = (info->group[i].len * width) / info->num_chars;
1949 else
1951 wx = 0;
1952 ww = width;
1954 hwnd = CreateWindowW( L"Edit", NULL, style, wx, 0, ww, height,
1955 info->hwnd, NULL, NULL, NULL );
1956 if( !hwnd )
1958 ERR("failed to create mask edit sub window\n");
1959 break;
1962 SendMessageW( hwnd, EM_LIMITTEXT, info->group[i].len, 0 );
1964 dialog_set_font( info->dialog, hwnd, font?font:info->dialog->default_font );
1965 info->group[i].hwnd = hwnd;
1970 * office 2003 uses "73931<````=````=````=````=`````>@@@@@"
1971 * delphi 7 uses "<????-??????-??????-????>" and "<???-???>"
1972 * filemaker pro 7 uses "<^^^^=^^^^=^^^^=^^^^=^^^^=^^^^=^^^^^>"
1974 static UINT dialog_maskedit_control( msi_dialog *dialog, MSIRECORD *rec )
1976 LPWSTR font_mask, val = NULL, font;
1977 struct msi_maskedit_info *info = NULL;
1978 UINT ret = ERROR_SUCCESS;
1979 struct control *control;
1980 LPCWSTR prop, mask;
1982 TRACE("\n");
1984 font_mask = get_deformatted_field( dialog->package, rec, 10 );
1985 font = dialog_get_style( font_mask, &mask );
1986 if( !mask )
1988 WARN("mask template is empty\n");
1989 goto end;
1992 info = dialog_parse_groups( mask );
1993 if( !info )
1995 ERR("template %s is invalid\n", debugstr_w(mask));
1996 goto end;
1999 info->dialog = dialog;
2001 control = dialog_add_control( dialog, rec, L"Static", SS_OWNERDRAW | WS_GROUP | WS_VISIBLE );
2002 if( !control )
2004 ERR("Failed to create maskedit container\n");
2005 ret = ERROR_FUNCTION_FAILED;
2006 goto end;
2008 SetWindowLongPtrW( control->hwnd, GWL_EXSTYLE, WS_EX_CONTROLPARENT );
2010 info->hwnd = control->hwnd;
2012 /* subclass the static control */
2013 info->oldproc = (WNDPROC) SetWindowLongPtrW( info->hwnd, GWLP_WNDPROC,
2014 (LONG_PTR)MSIMaskedEdit_WndProc );
2015 SetPropW( control->hwnd, L"MSIDATA", info );
2017 prop = MSI_RecordGetString( rec, 9 );
2018 if( prop )
2019 info->prop = wcsdup( prop );
2021 maskedit_create_children( info, font );
2023 if( prop )
2025 val = msi_dup_property( dialog->package->db, prop );
2026 if( val )
2028 maskedit_set_text( info, val );
2029 free( val );
2033 end:
2034 if( ret != ERROR_SUCCESS )
2035 free( info );
2036 free( font_mask );
2037 free( font );
2038 return ret;
2041 /******************** Progress Bar *****************************************/
2043 static UINT dialog_progress_bar( msi_dialog *dialog, MSIRECORD *rec )
2045 struct control *control;
2046 DWORD attributes, style;
2048 style = WS_VISIBLE;
2049 attributes = MSI_RecordGetInteger( rec, 8 );
2050 if( !(attributes & msidbControlAttributesProgress95) )
2051 style |= PBS_SMOOTH;
2053 control = dialog_add_control( dialog, rec, PROGRESS_CLASSW, style );
2054 if( !control )
2055 return ERROR_FUNCTION_FAILED;
2057 event_subscribe( dialog, L"SetProgress", control->name, L"Progress" );
2058 return ERROR_SUCCESS;
2061 /******************** Path Edit ********************************************/
2063 struct msi_pathedit_info
2065 msi_dialog *dialog;
2066 struct control *control;
2067 WNDPROC oldproc;
2070 static WCHAR *get_path_property( msi_dialog *dialog, struct control *control )
2072 WCHAR *prop, *path;
2073 BOOL indirect = control->attributes & msidbControlAttributesIndirect;
2074 if (!(prop = dialog_dup_property( dialog, control->property, indirect ))) return NULL;
2075 path = dialog_dup_property( dialog, prop, TRUE );
2076 free( prop );
2077 return path;
2080 static void dialog_update_pathedit( msi_dialog *dialog, struct control *control )
2082 WCHAR *path;
2084 if (!control && !(control = dialog_find_control_by_type( dialog, L"PathEdit" )))
2085 return;
2087 if (!(path = get_path_property( dialog, control ))) return;
2088 SetWindowTextW( control->hwnd, path );
2089 SendMessageW( control->hwnd, EM_SETSEL, 0, -1 );
2090 free( path );
2093 /* FIXME: test when this should fail */
2094 static BOOL dialog_verify_path( const WCHAR *path )
2096 if ( !path[0] )
2097 return FALSE;
2099 if ( PathIsRelativeW( path ) )
2100 return FALSE;
2102 return TRUE;
2105 /* returns TRUE if the path is valid, FALSE otherwise */
2106 static BOOL dialog_onkillfocus( msi_dialog *dialog, struct control *control )
2108 LPWSTR buf, prop;
2109 BOOL indirect;
2110 BOOL valid;
2112 indirect = control->attributes & msidbControlAttributesIndirect;
2113 prop = dialog_dup_property( dialog, control->property, indirect );
2115 buf = get_window_text( control->hwnd );
2117 if ( !dialog_verify_path( buf ) )
2119 /* FIXME: display an error message box */
2120 ERR("Invalid path %s\n", debugstr_w( buf ));
2121 valid = FALSE;
2122 SetFocus( control->hwnd );
2124 else
2126 valid = TRUE;
2127 dialog_set_property( dialog->package, prop, buf );
2130 dialog_update_pathedit( dialog, control );
2132 TRACE("edit %s contents changed, set %s\n", debugstr_w(control->name),
2133 debugstr_w(prop));
2135 free( buf );
2136 free( prop );
2138 return valid;
2141 static LRESULT WINAPI MSIPathEdit_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
2143 struct msi_pathedit_info *info = GetPropW(hWnd, L"MSIDATA");
2144 LRESULT r = 0;
2146 TRACE("%p %04x %#Ix %#Ix\n", hWnd, msg, wParam, lParam);
2148 if ( msg == WM_KILLFOCUS )
2150 /* if the path is invalid, don't handle this message */
2151 if ( !dialog_onkillfocus( info->dialog, info->control ) )
2152 return 0;
2155 r = CallWindowProcW(info->oldproc, hWnd, msg, wParam, lParam);
2157 if ( msg == WM_NCDESTROY )
2159 free( info );
2160 RemovePropW( hWnd, L"MSIDATA" );
2163 return r;
2166 static UINT dialog_pathedit_control( msi_dialog *dialog, MSIRECORD *rec )
2168 struct msi_pathedit_info *info;
2169 struct control *control;
2170 LPCWSTR prop;
2172 info = malloc( sizeof *info );
2173 if (!info)
2174 return ERROR_FUNCTION_FAILED;
2176 control = dialog_add_control( dialog, rec, L"Edit", WS_BORDER | WS_TABSTOP );
2177 control->attributes = MSI_RecordGetInteger( rec, 8 );
2178 prop = MSI_RecordGetString( rec, 9 );
2179 control->property = dialog_dup_property( dialog, prop, FALSE );
2180 control->update = dialog_update_pathedit;
2182 info->dialog = dialog;
2183 info->control = control;
2184 info->oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
2185 (LONG_PTR)MSIPathEdit_WndProc );
2186 SetPropW( control->hwnd, L"MSIDATA", info );
2188 dialog_update_pathedit( dialog, control );
2190 return ERROR_SUCCESS;
2193 static UINT dialog_radiogroup_handler( msi_dialog *dialog, struct control *control, WPARAM param )
2195 if (HIWORD(param) != BN_CLICKED)
2196 return ERROR_SUCCESS;
2198 TRACE("clicked radio button %s, set %s\n", debugstr_w(control->name), debugstr_w(control->property));
2200 dialog_set_property( dialog->package, control->property, control->name );
2202 return dialog_button_handler( dialog, control, param );
2205 /* radio buttons are a bit different from normal controls */
2206 static UINT dialog_create_radiobutton( MSIRECORD *rec, void *param )
2208 struct radio_button_group_descr *group = param;
2209 msi_dialog *dialog = group->dialog;
2210 struct control *control;
2211 LPCWSTR prop, text, name;
2212 DWORD style = WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTORADIOBUTTON | BS_MULTILINE;
2214 name = MSI_RecordGetString( rec, 3 );
2215 text = MSI_RecordGetString( rec, 8 );
2217 control = dialog_create_window( dialog, rec, 0, L"BUTTON", name, text, style,
2218 group->parent->hwnd );
2219 if (!control)
2220 return ERROR_FUNCTION_FAILED;
2221 control->handler = dialog_radiogroup_handler;
2223 if (group->propval && !wcscmp( control->name, group->propval ))
2224 SendMessageW(control->hwnd, BM_SETCHECK, BST_CHECKED, 0);
2226 prop = MSI_RecordGetString( rec, 1 );
2227 if( prop )
2228 control->property = wcsdup( prop );
2230 return ERROR_SUCCESS;
2233 static BOOL CALLBACK radioground_child_enum( HWND hWnd, LPARAM lParam )
2235 EnableWindow( hWnd, lParam );
2236 return TRUE;
2239 static LRESULT WINAPI MSIRadioGroup_WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
2241 WNDPROC oldproc = (WNDPROC)GetPropW( hWnd, L"MSIDATA" );
2242 LRESULT r;
2244 TRACE( "hWnd %p msg %04x wParam %#Ix lParam %#Ix\n", hWnd, msg, wParam, lParam );
2246 if (msg == WM_COMMAND) /* Forward notifications to dialog */
2247 SendMessageW( GetParent( hWnd ), msg, wParam, lParam );
2249 r = CallWindowProcW( oldproc, hWnd, msg, wParam, lParam );
2251 /* make sure the radio buttons show as disabled if the parent is disabled */
2252 if (msg == WM_ENABLE)
2253 EnumChildWindows( hWnd, radioground_child_enum, wParam );
2255 return r;
2258 static UINT dialog_radiogroup_control( msi_dialog *dialog, MSIRECORD *rec )
2260 UINT r;
2261 LPCWSTR prop;
2262 struct control *control;
2263 MSIQUERY *view;
2264 struct radio_button_group_descr group;
2265 MSIPACKAGE *package = dialog->package;
2266 WNDPROC oldproc;
2267 DWORD attr, style = WS_GROUP;
2269 prop = MSI_RecordGetString( rec, 9 );
2271 TRACE("%p %p %s\n", dialog, rec, debugstr_w( prop ));
2273 attr = MSI_RecordGetInteger( rec, 8 );
2274 if (attr & msidbControlAttributesVisible)
2275 style |= WS_VISIBLE;
2276 if (~attr & msidbControlAttributesEnabled)
2277 style |= WS_DISABLED;
2278 if (attr & msidbControlAttributesHasBorder)
2279 style |= BS_GROUPBOX;
2280 else
2281 style |= BS_OWNERDRAW;
2283 /* Create parent group box to hold radio buttons */
2284 control = dialog_add_control( dialog, rec, L"BUTTON", style );
2285 if( !control )
2286 return ERROR_FUNCTION_FAILED;
2288 oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
2289 (LONG_PTR)MSIRadioGroup_WndProc );
2290 SetPropW(control->hwnd, L"MSIDATA", oldproc);
2291 SetWindowLongPtrW( control->hwnd, GWL_EXSTYLE, WS_EX_CONTROLPARENT );
2293 if( prop )
2294 control->property = wcsdup( prop );
2296 /* query the Radio Button table for all control in this group */
2297 r = MSI_OpenQuery( package->db, &view, L"SELECT * FROM `RadioButton` WHERE `Property` = '%s'", prop );
2298 if( r != ERROR_SUCCESS )
2300 ERR("query failed for dialog %s radio group %s\n",
2301 debugstr_w(dialog->name), debugstr_w(prop));
2302 return ERROR_INVALID_PARAMETER;
2305 group.dialog = dialog;
2306 group.parent = control;
2307 group.propval = msi_dup_property( dialog->package->db, control->property );
2309 r = MSI_IterateRecords( view, 0, dialog_create_radiobutton, &group );
2310 msiobj_release( &view->hdr );
2311 free( group.propval );
2312 return r;
2315 static void seltree_sync_item_state( HWND hwnd, MSIFEATURE *feature, HTREEITEM hItem )
2317 TVITEMW tvi;
2318 DWORD index = feature->ActionRequest;
2320 TRACE("Feature %s -> %d %d %d\n", debugstr_w(feature->Title),
2321 feature->Installed, feature->Action, feature->ActionRequest);
2323 if (index == INSTALLSTATE_UNKNOWN)
2324 index = INSTALLSTATE_ABSENT;
2326 tvi.mask = TVIF_STATE;
2327 tvi.hItem = hItem;
2328 tvi.state = INDEXTOSTATEIMAGEMASK( index );
2329 tvi.stateMask = TVIS_STATEIMAGEMASK;
2331 SendMessageW( hwnd, TVM_SETITEMW, 0, (LPARAM) &tvi );
2334 static UINT seltree_popup_menu( HWND hwnd, INT x, INT y )
2336 HMENU hMenu;
2337 INT r;
2339 /* create a menu to display */
2340 hMenu = CreatePopupMenu();
2342 /* FIXME: load strings from resources */
2343 AppendMenuA( hMenu, MF_ENABLED, INSTALLSTATE_LOCAL, "Install feature locally");
2344 AppendMenuA( hMenu, MF_ENABLED, USER_INSTALLSTATE_ALL, "Install entire feature");
2345 AppendMenuA( hMenu, MF_ENABLED, INSTALLSTATE_ADVERTISED, "Install on demand");
2346 AppendMenuA( hMenu, MF_ENABLED, INSTALLSTATE_ABSENT, "Don't install");
2347 r = TrackPopupMenu( hMenu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RETURNCMD,
2348 x, y, 0, hwnd, NULL );
2349 DestroyMenu( hMenu );
2350 return r;
2353 static void seltree_update_feature_installstate( HWND hwnd, HTREEITEM hItem, MSIPACKAGE *package,
2354 MSIFEATURE *feature, INSTALLSTATE state )
2356 feature->ActionRequest = state;
2357 seltree_sync_item_state( hwnd, feature, hItem );
2358 ACTION_UpdateComponentStates( package, feature );
2361 static void seltree_update_siblings_and_children_installstate( HWND hwnd, HTREEITEM curr, MSIPACKAGE *package,
2362 INSTALLSTATE state )
2364 /* update all siblings */
2367 MSIFEATURE *feature;
2368 HTREEITEM child;
2370 feature = seltree_feature_from_item( hwnd, curr );
2371 seltree_update_feature_installstate( hwnd, curr, package, feature, state );
2373 /* update this sibling's children */
2374 child = (HTREEITEM)SendMessageW( hwnd, TVM_GETNEXTITEM, (WPARAM)TVGN_CHILD, (LPARAM)curr );
2375 if (child)
2376 seltree_update_siblings_and_children_installstate( hwnd, child, package, state );
2378 while ((curr = (HTREEITEM)SendMessageW( hwnd, TVM_GETNEXTITEM, (WPARAM)TVGN_NEXT, (LPARAM)curr )));
2381 static LRESULT seltree_menu( HWND hwnd, HTREEITEM hItem )
2383 struct msi_selection_tree_info *info;
2384 MSIFEATURE *feature;
2385 MSIPACKAGE *package;
2386 union {
2387 RECT rc;
2388 POINT pt[2];
2389 HTREEITEM hItem;
2390 } u;
2391 UINT r;
2393 info = GetPropW(hwnd, L"MSIDATA");
2394 package = info->dialog->package;
2396 feature = seltree_feature_from_item( hwnd, hItem );
2397 if (!feature)
2399 ERR("item %p feature was NULL\n", hItem);
2400 return 0;
2403 /* get the item's rectangle to put the menu just below it */
2404 u.hItem = hItem;
2405 SendMessageW( hwnd, TVM_GETITEMRECT, 0, (LPARAM) &u.rc );
2406 MapWindowPoints( hwnd, NULL, u.pt, 2 );
2408 r = seltree_popup_menu( hwnd, u.rc.left, u.rc.top );
2410 switch (r)
2412 case USER_INSTALLSTATE_ALL:
2413 r = INSTALLSTATE_LOCAL;
2414 /* fall-through */
2415 case INSTALLSTATE_ADVERTISED:
2416 case INSTALLSTATE_ABSENT:
2418 HTREEITEM child;
2419 child = (HTREEITEM)SendMessageW( hwnd, TVM_GETNEXTITEM, (WPARAM)TVGN_CHILD, (LPARAM)hItem );
2420 if (child)
2421 seltree_update_siblings_and_children_installstate( hwnd, child, package, r );
2423 /* fall-through */
2424 case INSTALLSTATE_LOCAL:
2425 seltree_update_feature_installstate( hwnd, hItem, package, feature, r );
2426 break;
2429 return 0;
2432 static LRESULT WINAPI MSISelectionTree_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
2434 struct msi_selection_tree_info *info;
2435 TVHITTESTINFO tvhti;
2436 HRESULT r;
2438 TRACE("%p %04x %#Ix %#Ix\n", hWnd, msg, wParam, lParam);
2440 info = GetPropW(hWnd, L"MSIDATA");
2442 switch( msg )
2444 case WM_LBUTTONDOWN:
2445 tvhti.pt.x = (short)LOWORD( lParam );
2446 tvhti.pt.y = (short)HIWORD( lParam );
2447 tvhti.flags = 0;
2448 tvhti.hItem = 0;
2449 CallWindowProcW(info->oldproc, hWnd, TVM_HITTEST, 0, (LPARAM) &tvhti );
2450 if (tvhti.flags & TVHT_ONITEMSTATEICON)
2451 return seltree_menu( hWnd, tvhti.hItem );
2452 break;
2454 r = CallWindowProcW(info->oldproc, hWnd, msg, wParam, lParam);
2456 switch( msg )
2458 case WM_NCDESTROY:
2459 free( info );
2460 RemovePropW( hWnd, L"MSIDATA" );
2461 break;
2463 return r;
2466 static void seltree_add_child_features( MSIPACKAGE *package, HWND hwnd, const WCHAR *parent, HTREEITEM hParent )
2468 struct msi_selection_tree_info *info = GetPropW( hwnd, L"MSIDATA" );
2469 MSIFEATURE *feature;
2470 TVINSERTSTRUCTW tvis;
2471 HTREEITEM hitem, hfirst = NULL;
2473 LIST_FOR_EACH_ENTRY( feature, &package->features, MSIFEATURE, entry )
2475 if ( parent && feature->Feature_Parent && wcscmp( parent, feature->Feature_Parent ))
2476 continue;
2477 else if ( parent && !feature->Feature_Parent )
2478 continue;
2479 else if ( !parent && feature->Feature_Parent )
2480 continue;
2482 if ( !feature->Title )
2483 continue;
2485 if ( !feature->Display )
2486 continue;
2488 memset( &tvis, 0, sizeof tvis );
2489 tvis.hParent = hParent;
2490 tvis.hInsertAfter = TVI_LAST;
2491 tvis.u.item.mask = TVIF_TEXT | TVIF_PARAM;
2492 tvis.u.item.pszText = feature->Title;
2493 tvis.u.item.lParam = (LPARAM) feature;
2495 hitem = (HTREEITEM) SendMessageW( hwnd, TVM_INSERTITEMW, 0, (LPARAM) &tvis );
2496 if (!hitem)
2497 continue;
2499 if (!hfirst)
2500 hfirst = hitem;
2502 seltree_sync_item_state( hwnd, feature, hitem );
2503 seltree_add_child_features( package, hwnd,
2504 feature->Feature, hitem );
2506 /* the node is expanded if Display is odd */
2507 if ( feature->Display % 2 != 0 )
2508 SendMessageW( hwnd, TVM_EXPAND, TVE_EXPAND, (LPARAM) hitem );
2511 /* select the first item */
2512 SendMessageW( hwnd, TVM_SELECTITEM, TVGN_CARET | TVGN_DROPHILITE, (LPARAM) hfirst );
2513 info->selected = hfirst;
2516 static void seltree_create_imagelist( HWND hwnd )
2518 const int bm_width = 32, bm_height = 16, bm_count = 3;
2519 const int bm_resource = 0x1001;
2520 HIMAGELIST himl;
2521 int i;
2522 HBITMAP hbmp;
2524 himl = ImageList_Create( bm_width, bm_height, FALSE, 4, 0 );
2525 if (!himl)
2527 ERR("failed to create image list\n");
2528 return;
2531 for (i=0; i<bm_count; i++)
2533 hbmp = LoadBitmapW( msi_hInstance, MAKEINTRESOURCEW(i+bm_resource) );
2534 if (!hbmp)
2536 ERR("failed to load bitmap %d\n", i);
2537 break;
2541 * Add a dummy bitmap at offset zero because the treeview
2542 * can't use it as a state mask (zero means no user state).
2544 if (!i)
2545 ImageList_Add( himl, hbmp, NULL );
2547 ImageList_Add( himl, hbmp, NULL );
2550 SendMessageW( hwnd, TVM_SETIMAGELIST, TVSIL_STATE, (LPARAM)himl );
2553 static UINT dialog_seltree_handler( msi_dialog *dialog, struct control *control, WPARAM param )
2555 struct msi_selection_tree_info *info = GetPropW( control->hwnd, L"MSIDATA" );
2556 LPNMTREEVIEWW tv = (LPNMTREEVIEWW)param;
2557 MSIRECORD *row, *rec;
2558 MSIFOLDER *folder;
2559 MSIFEATURE *feature;
2560 LPCWSTR dir, title = NULL;
2561 UINT r = ERROR_SUCCESS;
2563 if (tv->hdr.code != TVN_SELCHANGINGW)
2564 return ERROR_SUCCESS;
2566 info->selected = tv->itemNew.hItem;
2568 if (!(tv->itemNew.mask & TVIF_TEXT))
2570 feature = seltree_feature_from_item( control->hwnd, tv->itemNew.hItem );
2571 if (feature)
2572 title = feature->Title;
2574 else
2575 title = tv->itemNew.pszText;
2577 row = MSI_QueryGetRecord( dialog->package->db, L"SELECT * FROM `Feature` WHERE `Title` = '%s'", title );
2578 if (!row)
2579 return ERROR_FUNCTION_FAILED;
2581 rec = MSI_CreateRecord( 1 );
2583 MSI_RecordSetStringW( rec, 1, MSI_RecordGetString( row, 4 ) );
2584 msi_event_fire( dialog->package, L"SelectionDescription", rec );
2586 dir = MSI_RecordGetString( row, 7 );
2587 if (dir)
2589 folder = msi_get_loaded_folder( dialog->package, dir );
2590 if (!folder)
2592 r = ERROR_FUNCTION_FAILED;
2593 goto done;
2595 MSI_RecordSetStringW( rec, 1, folder->ResolvedTarget );
2597 else
2598 MSI_RecordSetStringW( rec, 1, NULL );
2600 msi_event_fire( dialog->package, L"SelectionPath", rec );
2602 done:
2603 msiobj_release(&row->hdr);
2604 msiobj_release(&rec->hdr);
2606 return r;
2609 static UINT dialog_selection_tree( msi_dialog *dialog, MSIRECORD *rec )
2611 struct control *control;
2612 LPCWSTR prop, control_name;
2613 MSIPACKAGE *package = dialog->package;
2614 DWORD style;
2615 struct msi_selection_tree_info *info;
2617 info = malloc( sizeof *info );
2618 if (!info)
2619 return ERROR_FUNCTION_FAILED;
2621 /* create the treeview control */
2622 style = TVS_HASLINES | TVS_HASBUTTONS | TVS_LINESATROOT;
2623 style |= WS_GROUP | WS_VSCROLL | WS_TABSTOP;
2624 control = dialog_add_control( dialog, rec, WC_TREEVIEWW, style );
2625 if (!control)
2627 free(info);
2628 return ERROR_FUNCTION_FAILED;
2631 control->handler = dialog_seltree_handler;
2632 control_name = MSI_RecordGetString( rec, 2 );
2633 control->attributes = MSI_RecordGetInteger( rec, 8 );
2634 prop = MSI_RecordGetString( rec, 9 );
2635 control->property = dialog_dup_property( dialog, prop, FALSE );
2637 /* subclass */
2638 info->dialog = dialog;
2639 info->hwnd = control->hwnd;
2640 info->oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
2641 (LONG_PTR)MSISelectionTree_WndProc );
2642 SetPropW( control->hwnd, L"MSIDATA", info );
2644 event_subscribe( dialog, L"SelectionPath", control_name, L"Property" );
2646 /* initialize it */
2647 seltree_create_imagelist( control->hwnd );
2648 seltree_add_child_features( package, control->hwnd, NULL, NULL );
2650 return ERROR_SUCCESS;
2653 /******************** Group Box ***************************************/
2655 static UINT dialog_group_box( msi_dialog *dialog, MSIRECORD *rec )
2657 struct control *control;
2658 DWORD style;
2660 style = BS_GROUPBOX | WS_CHILD | WS_GROUP;
2661 control = dialog_add_control( dialog, rec, WC_BUTTONW, style );
2662 if (!control)
2663 return ERROR_FUNCTION_FAILED;
2665 return ERROR_SUCCESS;
2668 /******************** List Box ***************************************/
2670 struct msi_listbox_info
2672 msi_dialog *dialog;
2673 HWND hwnd;
2674 WNDPROC oldproc;
2675 DWORD num_items;
2676 DWORD addpos_items;
2677 LPWSTR *items;
2680 static LRESULT WINAPI MSIListBox_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
2682 struct msi_listbox_info *info;
2683 LRESULT r;
2684 DWORD j;
2686 TRACE("%p %04x %#Ix %#Ix\n", hWnd, msg, wParam, lParam);
2688 info = GetPropW( hWnd, L"MSIDATA" );
2689 if (!info)
2690 return 0;
2692 r = CallWindowProcW( info->oldproc, hWnd, msg, wParam, lParam );
2694 switch( msg )
2696 case WM_NCDESTROY:
2697 for (j = 0; j < info->num_items; j++)
2698 free( info->items[j] );
2699 free( info->items );
2700 free( info );
2701 RemovePropW( hWnd, L"MSIDATA" );
2702 break;
2705 return r;
2708 static UINT listbox_add_item( MSIRECORD *rec, void *param )
2710 struct msi_listbox_info *info = param;
2711 LPCWSTR value, text;
2712 int pos;
2714 value = MSI_RecordGetString( rec, 3 );
2715 text = MSI_RecordGetString( rec, 4 );
2717 info->items[info->addpos_items] = wcsdup( value );
2719 pos = SendMessageW( info->hwnd, LB_ADDSTRING, 0, (LPARAM)text );
2720 SendMessageW( info->hwnd, LB_SETITEMDATA, pos, (LPARAM)info->items[info->addpos_items] );
2721 info->addpos_items++;
2722 return ERROR_SUCCESS;
2725 static UINT listbox_add_items( struct msi_listbox_info *info, const WCHAR *property )
2727 MSIQUERY *view;
2728 DWORD count;
2729 UINT r;
2731 r = MSI_OpenQuery( info->dialog->package->db, &view,
2732 L"SELECT * FROM `ListBox` WHERE `Property` = '%s' ORDER BY `Order`", property );
2733 if ( r != ERROR_SUCCESS )
2734 return r;
2736 /* just get the number of records */
2737 count = 0;
2738 r = MSI_IterateRecords( view, &count, NULL, NULL );
2739 if (r != ERROR_SUCCESS)
2741 msiobj_release( &view->hdr );
2742 return r;
2744 info->num_items = count;
2745 info->items = malloc( sizeof(*info->items) * count );
2747 r = MSI_IterateRecords( view, NULL, listbox_add_item, info );
2748 msiobj_release( &view->hdr );
2749 return r;
2752 static UINT dialog_listbox_handler( msi_dialog *dialog, struct control *control, WPARAM param )
2754 struct msi_listbox_info *info;
2755 int index;
2756 LPCWSTR value;
2758 if( HIWORD(param) != LBN_SELCHANGE )
2759 return ERROR_SUCCESS;
2761 info = GetPropW( control->hwnd, L"MSIDATA" );
2762 index = SendMessageW( control->hwnd, LB_GETCURSEL, 0, 0 );
2763 value = (LPCWSTR) SendMessageW( control->hwnd, LB_GETITEMDATA, index, 0 );
2765 dialog_set_property( info->dialog->package, control->property, value );
2766 dialog_evaluate_control_conditions( info->dialog );
2768 return ERROR_SUCCESS;
2771 static UINT dialog_list_box( msi_dialog *dialog, MSIRECORD *rec )
2773 struct msi_listbox_info *info;
2774 struct control *control;
2775 DWORD attributes, style;
2776 LPCWSTR prop;
2778 info = malloc( sizeof *info );
2779 if (!info)
2780 return ERROR_FUNCTION_FAILED;
2782 style = WS_TABSTOP | WS_GROUP | WS_CHILD | LBS_NOTIFY | WS_VSCROLL | WS_BORDER;
2783 attributes = MSI_RecordGetInteger( rec, 8 );
2784 if (~attributes & msidbControlAttributesSorted)
2785 style |= LBS_SORT;
2787 control = dialog_add_control( dialog, rec, WC_LISTBOXW, style );
2788 if (!control)
2790 free(info);
2791 return ERROR_FUNCTION_FAILED;
2794 control->handler = dialog_listbox_handler;
2796 prop = MSI_RecordGetString( rec, 9 );
2797 control->property = dialog_dup_property( dialog, prop, FALSE );
2799 /* subclass */
2800 info->dialog = dialog;
2801 info->hwnd = control->hwnd;
2802 info->items = NULL;
2803 info->addpos_items = 0;
2804 info->oldproc = (WNDPROC)SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
2805 (LONG_PTR)MSIListBox_WndProc );
2806 SetPropW( control->hwnd, L"MSIDATA", info );
2808 if ( control->property )
2809 listbox_add_items( info, control->property );
2811 return ERROR_SUCCESS;
2814 /******************** Directory Combo ***************************************/
2816 static void dialog_update_directory_combo( msi_dialog *dialog, struct control *control )
2818 WCHAR *path;
2820 if (!control && !(control = dialog_find_control_by_type( dialog, L"DirectoryCombo" )))
2821 return;
2823 if (!(path = get_path_property( dialog, control ))) return;
2824 PathStripPathW( path );
2825 PathRemoveBackslashW( path );
2827 SendMessageW( control->hwnd, CB_INSERTSTRING, 0, (LPARAM)path );
2828 SendMessageW( control->hwnd, CB_SETCURSEL, 0, 0 );
2830 free( path );
2833 static UINT dialog_directory_combo( msi_dialog *dialog, MSIRECORD *rec )
2835 struct control *control;
2836 LPCWSTR prop;
2837 DWORD style;
2839 /* FIXME: use CBS_OWNERDRAWFIXED and add owner draw code */
2840 style = CBS_DROPDOWNLIST | CBS_HASSTRINGS | WS_CHILD |
2841 WS_GROUP | WS_TABSTOP | WS_VSCROLL;
2842 control = dialog_add_control( dialog, rec, WC_COMBOBOXW, style );
2843 if (!control)
2844 return ERROR_FUNCTION_FAILED;
2846 control->attributes = MSI_RecordGetInteger( rec, 8 );
2847 prop = MSI_RecordGetString( rec, 9 );
2848 control->property = dialog_dup_property( dialog, prop, FALSE );
2850 dialog_update_directory_combo( dialog, control );
2852 return ERROR_SUCCESS;
2855 /******************** Directory List ***************************************/
2857 static void dialog_update_directory_list( msi_dialog *dialog, struct control *control )
2859 WCHAR dir_spec[MAX_PATH], *path;
2860 WIN32_FIND_DATAW wfd;
2861 LVITEMW item;
2862 HANDLE file;
2864 if (!control && !(control = dialog_find_control_by_type( dialog, L"DirectoryList" )))
2865 return;
2867 /* clear the list-view */
2868 SendMessageW( control->hwnd, LVM_DELETEALLITEMS, 0, 0 );
2870 if (!(path = get_path_property( dialog, control ))) return;
2871 lstrcpyW( dir_spec, path );
2872 lstrcatW( dir_spec, L"*" );
2874 file = FindFirstFileW( dir_spec, &wfd );
2875 if (file == INVALID_HANDLE_VALUE)
2877 free( path );
2878 return;
2883 if ( wfd.dwFileAttributes != FILE_ATTRIBUTE_DIRECTORY )
2884 continue;
2886 if ( !wcscmp( wfd.cFileName, L"." ) || !wcscmp( wfd.cFileName, L".." ) )
2887 continue;
2889 item.mask = LVIF_TEXT;
2890 item.cchTextMax = MAX_PATH;
2891 item.iItem = 0;
2892 item.iSubItem = 0;
2893 item.pszText = wfd.cFileName;
2895 SendMessageW( control->hwnd, LVM_INSERTITEMW, 0, (LPARAM)&item );
2896 } while ( FindNextFileW( file, &wfd ) );
2898 free( path );
2899 FindClose( file );
2902 static UINT dialog_directorylist_up( msi_dialog *dialog )
2904 struct control *control;
2905 LPWSTR prop, path, ptr;
2906 BOOL indirect;
2908 control = dialog_find_control_by_type( dialog, L"DirectoryList" );
2909 indirect = control->attributes & msidbControlAttributesIndirect;
2910 prop = dialog_dup_property( dialog, control->property, indirect );
2911 path = dialog_dup_property( dialog, prop, TRUE );
2913 /* strip off the last directory */
2914 ptr = PathFindFileNameW( path );
2915 if (ptr != path) *(ptr - 1) = '\0';
2916 PathAddBackslashW( path );
2918 dialog_set_property( dialog->package, prop, path );
2920 dialog_update_directory_list( dialog, NULL );
2921 dialog_update_directory_combo( dialog, NULL );
2922 dialog_update_pathedit( dialog, NULL );
2924 free( path );
2925 free( prop );
2927 return ERROR_SUCCESS;
2930 static WCHAR *get_unique_folder_name( const WCHAR *root, int *ret_len )
2932 WCHAR newfolder[MAX_PATH], *path, *ptr;
2933 int len, count = 2;
2935 len = LoadStringW( msi_hInstance, IDS_NEWFOLDER, newfolder, ARRAY_SIZE(newfolder) );
2936 len += lstrlenW(root) + 1;
2937 if (!(path = malloc( (len + 4) * sizeof(WCHAR) ))) return NULL;
2938 lstrcpyW( path, root );
2939 lstrcatW( path, newfolder );
2941 for (;;)
2943 if (GetFileAttributesW( path ) == INVALID_FILE_ATTRIBUTES) break;
2944 if (count > 99)
2946 free( path );
2947 return NULL;
2949 swprintf( path, len + 4, L"%s%s %u", root, newfolder, count++ );
2952 ptr = wcsrchr( path, '\\' ) + 1;
2953 *ret_len = lstrlenW(ptr);
2954 memmove( path, ptr, *ret_len * sizeof(WCHAR) );
2955 return path;
2958 static UINT dialog_directorylist_new( msi_dialog *dialog )
2960 struct control *control;
2961 WCHAR *path;
2962 LVITEMW item;
2963 int index;
2965 control = dialog_find_control_by_type( dialog, L"DirectoryList" );
2967 if (!(path = get_path_property( dialog, control ))) return ERROR_OUTOFMEMORY;
2969 item.mask = LVIF_TEXT;
2970 item.iItem = 0;
2971 item.iSubItem = 0;
2972 item.pszText = get_unique_folder_name( path, &item.cchTextMax );
2974 index = SendMessageW( control->hwnd, LVM_INSERTITEMW, 0, (LPARAM)&item );
2975 SendMessageW( control->hwnd, LVM_ENSUREVISIBLE, index, 0 );
2976 SendMessageW( control->hwnd, LVM_EDITLABELW, index, -1 );
2978 free( path );
2979 free( item.pszText );
2980 return ERROR_SUCCESS;
2983 static UINT dialog_dirlist_handler( msi_dialog *dialog, struct control *control, WPARAM param )
2985 NMHDR *nmhdr = (NMHDR *)param;
2986 WCHAR text[MAX_PATH], *new_path, *path, *prop;
2987 BOOL indirect;
2989 switch (nmhdr->code)
2991 case LVN_ENDLABELEDITW:
2993 NMLVDISPINFOW *info = (NMLVDISPINFOW *)param;
2994 if (!info->item.pszText) return ERROR_SUCCESS;
2995 lstrcpynW( text, info->item.pszText, ARRAY_SIZE(text) );
2996 text[ARRAY_SIZE(text) - 1] = 0;
2997 break;
2999 case LVN_ITEMACTIVATE:
3001 LVITEMW item;
3002 int index = SendMessageW( control->hwnd, LVM_GETNEXTITEM, -1, LVNI_SELECTED );
3003 if (index < 0)
3005 ERR("no list-view item selected\n");
3006 return ERROR_FUNCTION_FAILED;
3009 item.iSubItem = 0;
3010 item.pszText = text;
3011 item.cchTextMax = MAX_PATH;
3012 SendMessageW( control->hwnd, LVM_GETITEMTEXTW, index, (LPARAM)&item );
3013 text[ARRAY_SIZE(text) - 1] = 0;
3014 break;
3016 default:
3017 return ERROR_SUCCESS;
3020 indirect = control->attributes & msidbControlAttributesIndirect;
3021 prop = dialog_dup_property( dialog, control->property, indirect );
3022 path = dialog_dup_property( dialog, prop, TRUE );
3024 if (!(new_path = malloc( (wcslen(path) + wcslen(text) + 2) * sizeof(WCHAR) )))
3026 free( prop );
3027 free( path );
3028 return ERROR_OUTOFMEMORY;
3030 lstrcpyW( new_path, path );
3031 lstrcatW( new_path, text );
3032 if (nmhdr->code == LVN_ENDLABELEDITW) CreateDirectoryW( new_path, NULL );
3033 lstrcatW( new_path, L"\\" );
3035 dialog_set_property( dialog->package, prop, new_path );
3037 dialog_update_directory_list( dialog, NULL );
3038 dialog_update_directory_combo( dialog, NULL );
3039 dialog_update_pathedit( dialog, NULL );
3041 free( prop );
3042 free( path );
3043 free( new_path );
3045 return ERROR_SUCCESS;
3048 static UINT dialog_directory_list( msi_dialog *dialog, MSIRECORD *rec )
3050 struct control *control;
3051 LPCWSTR prop;
3052 DWORD style;
3054 style = LVS_LIST | WS_VSCROLL | LVS_SHAREIMAGELISTS | LVS_EDITLABELS |
3055 LVS_AUTOARRANGE | LVS_SINGLESEL | WS_BORDER |
3056 LVS_SORTASCENDING | WS_CHILD | WS_GROUP | WS_TABSTOP;
3057 control = dialog_add_control( dialog, rec, WC_LISTVIEWW, style );
3058 if (!control)
3059 return ERROR_FUNCTION_FAILED;
3061 control->attributes = MSI_RecordGetInteger( rec, 8 );
3062 control->handler = dialog_dirlist_handler;
3063 prop = MSI_RecordGetString( rec, 9 );
3064 control->property = dialog_dup_property( dialog, prop, FALSE );
3066 /* double click to activate an item in the list */
3067 SendMessageW( control->hwnd, LVM_SETEXTENDEDLISTVIEWSTYLE,
3068 0, LVS_EX_TWOCLICKACTIVATE );
3070 dialog_update_directory_list( dialog, control );
3072 return ERROR_SUCCESS;
3075 /******************** VolumeCost List ***************************************/
3077 static BOOL str_is_number( LPCWSTR str )
3079 int i;
3081 for (i = 0; i < lstrlenW( str ); i++)
3082 if (!iswdigit(str[i]))
3083 return FALSE;
3085 return TRUE;
3088 static const WCHAR column_keys[][80] =
3090 L"VolumeCostVolume",
3091 L"VolumeCostSize",
3092 L"VolumeCostAvailable",
3093 L"VolumeCostRequired",
3094 L"VolumeCostDifference",
3097 static void dialog_vcl_add_columns( msi_dialog *dialog, struct control *control, MSIRECORD *rec )
3099 LPCWSTR text = MSI_RecordGetString( rec, 10 );
3100 LPCWSTR begin = text, end;
3101 WCHAR *num;
3102 LVCOLUMNW lvc;
3103 DWORD count = 0;
3105 if (!text) return;
3107 while ((begin = wcschr( begin, '{' )) && count < 5)
3109 if (!(end = wcschr( begin, '}' )))
3110 return;
3112 num = malloc( (end - begin + 1) * sizeof(WCHAR) );
3113 if (!num)
3114 return;
3116 lstrcpynW( num, begin + 1, end - begin );
3117 begin += end - begin + 1;
3119 /* empty braces or '0' hides the column */
3120 if ( !num[0] || !wcscmp( num, L"0" ) )
3122 count++;
3123 free( num );
3124 continue;
3127 /* the width must be a positive number
3128 * if a width is invalid, all remaining columns are hidden
3130 if ( !wcsncmp( num, L"-", 1 ) || !str_is_number( num ) ) {
3131 free( num );
3132 return;
3135 ZeroMemory( &lvc, sizeof(lvc) );
3136 lvc.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM;
3137 lvc.cx = wcstol( num, NULL, 10 );
3138 lvc.pszText = dialog_get_uitext( dialog, column_keys[count] );
3140 SendMessageW( control->hwnd, LVM_INSERTCOLUMNW, count++, (LPARAM)&lvc );
3141 free( lvc.pszText );
3142 free( num );
3146 static LONGLONG vcl_get_cost( msi_dialog *dialog )
3148 MSIFEATURE *feature;
3149 INT each_cost;
3150 LONGLONG total_cost = 0;
3152 LIST_FOR_EACH_ENTRY( feature, &dialog->package->features, MSIFEATURE, entry )
3154 if (ERROR_SUCCESS == (MSI_GetFeatureCost(dialog->package, feature,
3155 MSICOSTTREE_SELFONLY, INSTALLSTATE_LOCAL, &each_cost)))
3157 /* each_cost is in 512-byte units */
3158 total_cost += each_cost * 512;
3160 if (ERROR_SUCCESS == (MSI_GetFeatureCost(dialog->package, feature,
3161 MSICOSTTREE_SELFONLY, INSTALLSTATE_ABSENT, &each_cost)))
3163 /* each_cost is in 512-byte units */
3164 total_cost -= each_cost * 512;
3167 return total_cost;
3170 static void dialog_vcl_add_drives( msi_dialog *dialog, struct control *control )
3172 ULARGE_INTEGER total, unused;
3173 LONGLONG difference, cost;
3174 WCHAR size_text[MAX_PATH];
3175 WCHAR cost_text[MAX_PATH];
3176 LPWSTR drives, ptr;
3177 LVITEMW lvitem;
3178 DWORD size, flags;
3179 int i = 0;
3181 cost = vcl_get_cost(dialog);
3182 StrFormatByteSizeW(cost, cost_text, MAX_PATH);
3184 size = GetLogicalDriveStringsW( 0, NULL );
3185 if ( !size ) return;
3187 drives = malloc( (size + 1) * sizeof(WCHAR) );
3188 if ( !drives ) return;
3190 GetLogicalDriveStringsW( size, drives );
3192 ptr = drives;
3193 while (*ptr)
3195 if (GetVolumeInformationW(ptr, NULL, 0, NULL, 0, &flags, NULL, 0) &&
3196 flags & FILE_READ_ONLY_VOLUME)
3198 ptr += lstrlenW(ptr) + 1;
3199 continue;
3202 lvitem.mask = LVIF_TEXT;
3203 lvitem.iItem = i;
3204 lvitem.iSubItem = 0;
3205 lvitem.pszText = ptr;
3206 lvitem.cchTextMax = lstrlenW(ptr) + 1;
3207 SendMessageW( control->hwnd, LVM_INSERTITEMW, 0, (LPARAM)&lvitem );
3209 GetDiskFreeSpaceExW(ptr, &unused, &total, NULL);
3210 difference = unused.QuadPart - cost;
3212 StrFormatByteSizeW(total.QuadPart, size_text, MAX_PATH);
3213 lvitem.iSubItem = 1;
3214 lvitem.pszText = size_text;
3215 lvitem.cchTextMax = lstrlenW(size_text) + 1;
3216 SendMessageW( control->hwnd, LVM_SETITEMW, 0, (LPARAM)&lvitem );
3218 StrFormatByteSizeW(unused.QuadPart, size_text, MAX_PATH);
3219 lvitem.iSubItem = 2;
3220 lvitem.pszText = size_text;
3221 lvitem.cchTextMax = lstrlenW(size_text) + 1;
3222 SendMessageW( control->hwnd, LVM_SETITEMW, 0, (LPARAM)&lvitem );
3224 lvitem.iSubItem = 3;
3225 lvitem.pszText = cost_text;
3226 lvitem.cchTextMax = lstrlenW(cost_text) + 1;
3227 SendMessageW( control->hwnd, LVM_SETITEMW, 0, (LPARAM)&lvitem );
3229 StrFormatByteSizeW(difference, size_text, MAX_PATH);
3230 lvitem.iSubItem = 4;
3231 lvitem.pszText = size_text;
3232 lvitem.cchTextMax = lstrlenW(size_text) + 1;
3233 SendMessageW( control->hwnd, LVM_SETITEMW, 0, (LPARAM)&lvitem );
3235 ptr += lstrlenW(ptr) + 1;
3236 i++;
3239 free( drives );
3242 static UINT dialog_volumecost_list( msi_dialog *dialog, MSIRECORD *rec )
3244 struct control *control;
3245 DWORD style;
3247 style = LVS_REPORT | WS_VSCROLL | WS_HSCROLL | LVS_SHAREIMAGELISTS |
3248 LVS_AUTOARRANGE | LVS_SINGLESEL | WS_BORDER |
3249 WS_CHILD | WS_TABSTOP | WS_GROUP;
3250 control = dialog_add_control( dialog, rec, WC_LISTVIEWW, style );
3251 if (!control)
3252 return ERROR_FUNCTION_FAILED;
3254 dialog_vcl_add_columns( dialog, control, rec );
3255 dialog_vcl_add_drives( dialog, control );
3257 return ERROR_SUCCESS;
3260 /******************** VolumeSelect Combo ***************************************/
3262 static UINT dialog_volsel_handler( msi_dialog *dialog, struct control *control, WPARAM param )
3264 WCHAR text[MAX_PATH];
3265 LPWSTR prop;
3266 BOOL indirect;
3267 int index;
3269 if (HIWORD(param) != CBN_SELCHANGE)
3270 return ERROR_SUCCESS;
3272 index = SendMessageW( control->hwnd, CB_GETCURSEL, 0, 0 );
3273 if ( index == CB_ERR )
3275 ERR("No ComboBox item selected!\n");
3276 return ERROR_FUNCTION_FAILED;
3279 SendMessageW( control->hwnd, CB_GETLBTEXT, index, (LPARAM)text );
3281 indirect = control->attributes & msidbControlAttributesIndirect;
3282 prop = dialog_dup_property( dialog, control->property, indirect );
3284 dialog_set_property( dialog->package, prop, text );
3286 free( prop );
3287 return ERROR_SUCCESS;
3290 static void dialog_vsc_add_drives( msi_dialog *dialog, struct control *control )
3292 LPWSTR drives, ptr;
3293 DWORD size;
3295 size = GetLogicalDriveStringsW( 0, NULL );
3296 if ( !size ) return;
3298 drives = malloc( (size + 1) * sizeof(WCHAR) );
3299 if ( !drives ) return;
3301 GetLogicalDriveStringsW( size, drives );
3303 ptr = drives;
3304 while (*ptr)
3306 SendMessageW( control->hwnd, CB_ADDSTRING, 0, (LPARAM)ptr );
3307 ptr += lstrlenW(ptr) + 1;
3310 free( drives );
3313 static UINT dialog_volumeselect_combo( msi_dialog *dialog, MSIRECORD *rec )
3315 struct control *control;
3316 LPCWSTR prop;
3317 DWORD style;
3319 /* FIXME: CBS_OWNERDRAWFIXED */
3320 style = WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP |
3321 CBS_DROPDOWNLIST | CBS_SORT | CBS_HASSTRINGS |
3322 WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR;
3323 control = dialog_add_control( dialog, rec, WC_COMBOBOXW, style );
3324 if (!control)
3325 return ERROR_FUNCTION_FAILED;
3327 control->attributes = MSI_RecordGetInteger( rec, 8 );
3328 control->handler = dialog_volsel_handler;
3329 prop = MSI_RecordGetString( rec, 9 );
3330 control->property = dialog_dup_property( dialog, prop, FALSE );
3332 dialog_vsc_add_drives( dialog, control );
3334 return ERROR_SUCCESS;
3337 static UINT dialog_hyperlink_handler( msi_dialog *dialog, struct control *control, WPARAM param )
3339 int len, len_href = ARRAY_SIZE( L"href" ) - 1;
3340 const WCHAR *p, *q;
3341 WCHAR quote = 0;
3342 LITEM item;
3344 item.mask = LIF_ITEMINDEX | LIF_URL;
3345 item.iLink = 0;
3346 item.szUrl[0] = 0;
3348 SendMessageW( control->hwnd, LM_GETITEM, 0, (LPARAM)&item );
3350 p = item.szUrl;
3351 while (*p && *p != '<') p++;
3352 if (!*p++) return ERROR_SUCCESS;
3353 if (towupper( *p++ ) != 'A' || !iswspace( *p++ )) return ERROR_SUCCESS;
3354 while (*p && iswspace( *p )) p++;
3356 len = lstrlenW( p );
3357 if (len > len_href && !wcsnicmp( p, L"href", len_href ))
3359 p += len_href;
3360 while (*p && iswspace( *p )) p++;
3361 if (!*p || *p++ != '=') return ERROR_SUCCESS;
3362 while (*p && iswspace( *p )) p++;
3364 if (*p == '\"' || *p == '\'') quote = *p++;
3365 q = p;
3366 if (quote)
3368 while (*q && *q != quote) q++;
3369 if (*q != quote) return ERROR_SUCCESS;
3371 else
3373 while (*q && *q != '>' && !iswspace( *q )) q++;
3374 if (!*q) return ERROR_SUCCESS;
3376 item.szUrl[q - item.szUrl] = 0;
3377 ShellExecuteW( NULL, L"open", p, NULL, NULL, SW_SHOWNORMAL );
3379 return ERROR_SUCCESS;
3382 static UINT dialog_hyperlink( msi_dialog *dialog, MSIRECORD *rec )
3384 struct control *control;
3385 DWORD style = WS_CHILD | WS_TABSTOP | WS_GROUP;
3386 const WCHAR *text = MSI_RecordGetString( rec, 10 );
3387 int len = lstrlenW( text );
3388 LITEM item;
3390 control = dialog_add_control( dialog, rec, WC_LINK, style );
3391 if (!control)
3392 return ERROR_FUNCTION_FAILED;
3394 control->attributes = MSI_RecordGetInteger( rec, 8 );
3395 control->handler = dialog_hyperlink_handler;
3397 item.mask = LIF_ITEMINDEX | LIF_STATE | LIF_URL;
3398 item.iLink = 0;
3399 item.state = LIS_ENABLED;
3400 item.stateMask = LIS_ENABLED;
3401 if (len < L_MAX_URL_LENGTH) lstrcpyW( item.szUrl, text );
3402 else item.szUrl[0] = 0;
3404 SendMessageW( control->hwnd, LM_SETITEM, 0, (LPARAM)&item );
3406 return ERROR_SUCCESS;
3409 /******************** ListView *****************************************/
3411 struct listview_param
3413 msi_dialog *dialog;
3414 struct control *control;
3417 static UINT dialog_listview_handler( msi_dialog *dialog, struct control *control, WPARAM param )
3419 NMHDR *nmhdr = (NMHDR *)param;
3421 FIXME("code %#x (%d)\n", nmhdr->code, nmhdr->code);
3423 return ERROR_SUCCESS;
3426 static UINT listview_add_item( MSIRECORD *rec, void *param )
3428 struct listview_param *lv_param = (struct listview_param *)param;
3429 LPCWSTR text, binary;
3430 LVITEMW item;
3431 HICON hIcon;
3433 text = MSI_RecordGetString( rec, 4 );
3434 binary = MSI_RecordGetString( rec, 5 );
3435 hIcon = load_icon( lv_param->dialog->package->db, binary, 0 );
3437 TRACE("Adding: text %s, binary %s, icon %p\n", debugstr_w(text), debugstr_w(binary), hIcon);
3439 memset( &item, 0, sizeof(item) );
3440 item.mask = LVIF_TEXT | LVIF_IMAGE;
3441 deformat_string( lv_param->dialog->package, text, &item.pszText );
3442 item.iImage = ImageList_AddIcon( lv_param->control->hImageList, hIcon );
3443 item.iItem = item.iImage;
3444 SendMessageW( lv_param->control->hwnd, LVM_INSERTITEMW, 0, (LPARAM)&item );
3446 DestroyIcon( hIcon );
3448 return ERROR_SUCCESS;
3451 static UINT listview_add_items( msi_dialog *dialog, struct control *control )
3453 MSIQUERY *view;
3454 struct listview_param lv_param = { dialog, control };
3456 if (MSI_OpenQuery( dialog->package->db, &view, L"SELECT * FROM `ListView` WHERE `Property` = '%s' ORDER BY `Order`",
3457 control->property ) == ERROR_SUCCESS)
3459 MSI_IterateRecords( view, NULL, listview_add_item, &lv_param );
3460 msiobj_release( &view->hdr );
3463 return ERROR_SUCCESS;
3466 static UINT dialog_listview( msi_dialog *dialog, MSIRECORD *rec )
3468 struct control *control;
3469 LPCWSTR prop;
3470 DWORD style, attributes;
3471 LVCOLUMNW col;
3472 RECT rc;
3474 style = LVS_REPORT | LVS_NOCOLUMNHEADER | LVS_SHAREIMAGELISTS | LVS_SINGLESEL |
3475 LVS_SHOWSELALWAYS | WS_VSCROLL | WS_HSCROLL | WS_BORDER | WS_TABSTOP | WS_CHILD;
3476 attributes = MSI_RecordGetInteger( rec, 8 );
3477 if ( ~attributes & msidbControlAttributesSorted )
3478 style |= LVS_SORTASCENDING;
3479 control = dialog_add_control( dialog, rec, WC_LISTVIEWW, style );
3480 if (!control)
3481 return ERROR_FUNCTION_FAILED;
3483 prop = MSI_RecordGetString( rec, 9 );
3484 control->property = dialog_dup_property( dialog, prop, FALSE );
3486 control->hImageList = ImageList_Create( 16, 16, ILC_COLOR32, 0, 1);
3487 SendMessageW( control->hwnd, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM)control->hImageList );
3489 col.mask = LVCF_FMT | LVCF_WIDTH;
3490 col.fmt = LVCFMT_LEFT;
3491 col.cx = 16;
3492 SendMessageW( control->hwnd, LVM_INSERTCOLUMNW, 0, (LPARAM)&col );
3494 GetClientRect( control->hwnd, &rc );
3495 col.cx = rc.right - 16;
3496 SendMessageW( control->hwnd, LVM_INSERTCOLUMNW, 0, (LPARAM)&col );
3498 if (control->property)
3499 listview_add_items( dialog, control );
3501 control->handler = dialog_listview_handler;
3503 return ERROR_SUCCESS;
3506 static const struct control_handler msi_dialog_handler[] =
3508 { L"Text", dialog_text_control },
3509 { L"PushButton", dialog_button_control },
3510 { L"Line", dialog_line_control },
3511 { L"Bitmap", dialog_bitmap_control },
3512 { L"CheckBox", dialog_checkbox_control },
3513 { L"ScrollableText", dialog_scrolltext_control },
3514 { L"ComboBox", dialog_combo_control },
3515 { L"Edit", dialog_edit_control },
3516 { L"MaskedEdit", dialog_maskedit_control },
3517 { L"PathEdit", dialog_pathedit_control },
3518 { L"ProgressBar", dialog_progress_bar },
3519 { L"RadioButtonGroup", dialog_radiogroup_control },
3520 { L"Icon", dialog_icon_control },
3521 { L"SelectionTree", dialog_selection_tree },
3522 { L"GroupBox", dialog_group_box },
3523 { L"ListBox", dialog_list_box },
3524 { L"DirectoryCombo", dialog_directory_combo },
3525 { L"DirectoryList", dialog_directory_list },
3526 { L"VolumeCostList", dialog_volumecost_list },
3527 { L"VolumeSelectCombo", dialog_volumeselect_combo },
3528 { L"HyperLink", dialog_hyperlink },
3529 { L"ListView", dialog_listview }
3532 static UINT dialog_create_controls( MSIRECORD *rec, void *param )
3534 msi_dialog *dialog = param;
3535 LPCWSTR control_type;
3536 UINT i;
3538 /* find and call the function that can create this type of control */
3539 control_type = MSI_RecordGetString( rec, 3 );
3540 for( i = 0; i < ARRAY_SIZE( msi_dialog_handler ); i++ )
3541 if (!wcsicmp( msi_dialog_handler[i].control_type, control_type ))
3542 break;
3543 if( i != ARRAY_SIZE( msi_dialog_handler ))
3544 msi_dialog_handler[i].func( dialog, rec );
3545 else
3546 ERR("no handler for element type %s\n", debugstr_w(control_type));
3548 return ERROR_SUCCESS;
3551 static UINT dialog_fill_controls( msi_dialog *dialog )
3553 UINT r;
3554 MSIQUERY *view;
3555 MSIPACKAGE *package = dialog->package;
3557 TRACE("%p %s\n", dialog, debugstr_w(dialog->name) );
3559 /* query the Control table for all the elements of the control */
3560 r = MSI_OpenQuery( package->db, &view, L"SELECT * FROM `Control` WHERE `Dialog_` = '%s'", dialog->name );
3561 if( r != ERROR_SUCCESS )
3563 ERR("query failed for dialog %s\n", debugstr_w(dialog->name));
3564 return ERROR_INVALID_PARAMETER;
3567 r = MSI_IterateRecords( view, 0, dialog_create_controls, dialog );
3568 msiobj_release( &view->hdr );
3569 return r;
3572 static UINT dialog_reset( msi_dialog *dialog )
3574 /* FIXME: should restore the original values of any properties we changed */
3575 return dialog_evaluate_control_conditions( dialog );
3578 /* figure out the height of 10 point MS Sans Serif */
3579 static INT dialog_get_sans_serif_height( HWND hwnd )
3581 LOGFONTW lf;
3582 TEXTMETRICW tm;
3583 BOOL r;
3584 LONG height = 0;
3585 HFONT hFont, hOldFont;
3586 HDC hdc;
3588 hdc = GetDC( hwnd );
3589 if (hdc)
3591 memset( &lf, 0, sizeof lf );
3592 lf.lfHeight = MulDiv(12, GetDeviceCaps(hdc, LOGPIXELSY), 72);
3593 lstrcpyW( lf.lfFaceName, L"MS Sans Serif" );
3594 hFont = CreateFontIndirectW(&lf);
3595 if (hFont)
3597 hOldFont = SelectObject( hdc, hFont );
3598 r = GetTextMetricsW( hdc, &tm );
3599 if (r)
3600 height = tm.tmHeight;
3601 SelectObject( hdc, hOldFont );
3602 DeleteObject( hFont );
3604 ReleaseDC( hwnd, hdc );
3606 return height;
3609 /* fetch the associated record from the Dialog table */
3610 static MSIRECORD *get_dialog_record( msi_dialog *dialog )
3612 MSIPACKAGE *package = dialog->package;
3613 MSIRECORD *rec = NULL;
3615 TRACE("%p %s\n", dialog, debugstr_w(dialog->name) );
3617 rec = MSI_QueryGetRecord( package->db, L"SELECT * FROM `Dialog` WHERE `Dialog` = '%s'", dialog->name );
3618 if( !rec )
3619 WARN("query failed for dialog %s\n", debugstr_w(dialog->name));
3621 return rec;
3624 static void dialog_adjust_dialog_pos( msi_dialog *dialog, MSIRECORD *rec, RECT *pos )
3626 UINT xres, yres;
3627 POINT center;
3628 SIZE sz;
3629 LONG style;
3631 center.x = MSI_RecordGetInteger( rec, 2 );
3632 center.y = MSI_RecordGetInteger( rec, 3 );
3634 sz.cx = MSI_RecordGetInteger( rec, 4 );
3635 sz.cy = MSI_RecordGetInteger( rec, 5 );
3637 sz.cx = dialog_scale_unit( dialog, sz.cx );
3638 sz.cy = dialog_scale_unit( dialog, sz.cy );
3640 xres = msi_get_property_int( dialog->package->db, L"ScreenX", 0 );
3641 yres = msi_get_property_int( dialog->package->db, L"ScreenY", 0 );
3643 center.x = MulDiv( center.x, xres, 100 );
3644 center.y = MulDiv( center.y, yres, 100 );
3646 /* turn the client pos into the window rectangle */
3647 if (dialog->package->center_x && dialog->package->center_y)
3649 pos->left = dialog->package->center_x - sz.cx / 2.0;
3650 pos->right = pos->left + sz.cx;
3651 pos->top = dialog->package->center_y - sz.cy / 2.0;
3652 pos->bottom = pos->top + sz.cy;
3654 else
3656 pos->left = center.x - sz.cx/2;
3657 pos->right = pos->left + sz.cx;
3658 pos->top = center.y - sz.cy/2;
3659 pos->bottom = pos->top + sz.cy;
3661 /* save the center */
3662 dialog->package->center_x = center.x;
3663 dialog->package->center_y = center.y;
3666 dialog->size.cx = sz.cx;
3667 dialog->size.cy = sz.cy;
3669 TRACE("%s\n", wine_dbgstr_rect(pos));
3671 style = GetWindowLongPtrW( dialog->hwnd, GWL_STYLE );
3672 AdjustWindowRect( pos, style, FALSE );
3675 static void dialog_set_tab_order( msi_dialog *dialog, const WCHAR *first )
3677 struct list tab_chain;
3678 struct control *control;
3679 HWND prev = HWND_TOP;
3681 list_init( &tab_chain );
3682 if (!(control = dialog_find_control( dialog, first ))) return;
3684 dialog->hWndFocus = control->hwnd;
3685 while (control)
3687 list_remove( &control->entry );
3688 list_add_tail( &tab_chain, &control->entry );
3689 if (!control->tabnext) break;
3690 control = dialog_find_control( dialog, control->tabnext );
3693 LIST_FOR_EACH_ENTRY( control, &tab_chain, struct control, entry )
3695 SetWindowPos( control->hwnd, prev, 0, 0, 0, 0,
3696 SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOREDRAW |
3697 SWP_NOREPOSITION | SWP_NOSENDCHANGING | SWP_NOSIZE );
3698 prev = control->hwnd;
3701 /* put them back on the main list */
3702 list_move_head( &dialog->controls, &tab_chain );
3705 static LRESULT dialog_oncreate( HWND hwnd, CREATESTRUCTW *cs )
3707 msi_dialog *dialog = cs->lpCreateParams;
3708 MSIRECORD *rec = NULL;
3709 LPWSTR title = NULL;
3710 RECT pos;
3712 TRACE("%p %p\n", dialog, dialog->package);
3714 dialog->hwnd = hwnd;
3715 SetWindowLongPtrW( hwnd, GWLP_USERDATA, (LONG_PTR) dialog );
3717 rec = get_dialog_record( dialog );
3718 if( !rec )
3720 TRACE("No record found for dialog %s\n", debugstr_w(dialog->name));
3721 return -1;
3724 dialog->scale = dialog_get_sans_serif_height(dialog->hwnd);
3726 dialog_adjust_dialog_pos( dialog, rec, &pos );
3728 dialog->attributes = MSI_RecordGetInteger( rec, 6 );
3730 dialog->default_font = msi_dup_property( dialog->package->db, L"DefaultUIFont" );
3731 if (!dialog->default_font)
3733 dialog->default_font = wcsdup( L"MS Shell Dlg" );
3734 if (!dialog->default_font)
3736 msiobj_release( &rec->hdr );
3737 return -1;
3741 title = get_deformatted_field( dialog->package, rec, 7 );
3742 SetWindowTextW( hwnd, title );
3743 free( title );
3745 SetWindowPos( hwnd, 0, pos.left, pos.top,
3746 pos.right - pos.left, pos.bottom - pos.top,
3747 SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOREDRAW );
3749 dialog_build_font_list( dialog );
3750 dialog_fill_controls( dialog );
3751 dialog_evaluate_control_conditions( dialog );
3752 dialog_set_tab_order( dialog, MSI_RecordGetString( rec, 8 ) );
3753 msiobj_release( &rec->hdr );
3755 return 0;
3758 static LRESULT dialog_oncommand( msi_dialog *dialog, WPARAM param, HWND hwnd )
3760 struct control *control = NULL;
3762 TRACE( "%p, %#Ix, %p\n", dialog, param, hwnd );
3764 switch (param)
3766 case 1: /* enter */
3767 control = dialog_find_control( dialog, dialog->control_default );
3768 break;
3769 case 2: /* escape */
3770 control = dialog_find_control( dialog, dialog->control_cancel );
3771 break;
3772 default:
3773 control = dialog_find_control_by_hwnd( dialog, hwnd );
3776 if( control )
3778 if( control->handler )
3780 control->handler( dialog, control, param );
3781 dialog_evaluate_control_conditions( dialog );
3785 return 0;
3788 static LRESULT dialog_onnotify( msi_dialog *dialog, LPARAM param )
3790 LPNMHDR nmhdr = (LPNMHDR) param;
3791 struct control *control = dialog_find_control_by_hwnd( dialog, nmhdr->hwndFrom );
3793 TRACE("%p %p\n", dialog, nmhdr->hwndFrom);
3795 if ( control && control->handler )
3796 control->handler( dialog, control, param );
3798 return 0;
3801 static void dialog_setfocus( msi_dialog *dialog )
3803 HWND hwnd = dialog->hWndFocus;
3805 hwnd = GetNextDlgTabItem( dialog->hwnd, hwnd, TRUE);
3806 hwnd = GetNextDlgTabItem( dialog->hwnd, hwnd, FALSE);
3807 SetFocus( hwnd );
3808 dialog->hWndFocus = hwnd;
3811 static LRESULT WINAPI MSIDialog_WndProc( HWND hwnd, UINT msg,
3812 WPARAM wParam, LPARAM lParam )
3814 msi_dialog *dialog = (LPVOID) GetWindowLongPtrW( hwnd, GWLP_USERDATA );
3816 TRACE("0x%04x\n", msg);
3818 switch (msg)
3820 case WM_MOVE:
3821 dialog->package->center_x = LOWORD(lParam) + dialog->size.cx / 2.0;
3822 dialog->package->center_y = HIWORD(lParam) + dialog->size.cy / 2.0;
3823 break;
3825 case WM_CREATE:
3826 return dialog_oncreate( hwnd, (LPCREATESTRUCTW)lParam );
3828 case WM_COMMAND:
3829 return dialog_oncommand( dialog, wParam, (HWND)lParam );
3831 case WM_CLOSE:
3832 /* Simulate escape press */
3833 return dialog_oncommand(dialog, 2, NULL);
3835 case WM_ACTIVATE:
3836 if( LOWORD(wParam) == WA_INACTIVE )
3837 dialog->hWndFocus = GetFocus();
3838 else
3839 dialog_setfocus( dialog );
3840 return 0;
3842 case WM_SETFOCUS:
3843 dialog_setfocus( dialog );
3844 return 0;
3846 /* bounce back to our subclassed static control */
3847 case WM_CTLCOLORSTATIC:
3848 return SendMessageW( (HWND) lParam, WM_CTLCOLORSTATIC, wParam, lParam );
3850 case WM_DESTROY:
3851 dialog->hwnd = NULL;
3852 return 0;
3853 case WM_NOTIFY:
3854 return dialog_onnotify( dialog, lParam );
3856 return DefWindowProcW(hwnd, msg, wParam, lParam);
3859 static void process_pending_messages( HWND hdlg )
3861 MSG msg;
3863 while (PeekMessageW( &msg, 0, 0, 0, PM_REMOVE ))
3865 if (hdlg && IsDialogMessageW( hdlg, &msg )) continue;
3866 TranslateMessage( &msg );
3867 DispatchMessageW( &msg );
3871 static UINT dialog_run_message_loop( msi_dialog *dialog )
3873 DWORD style;
3874 HWND hwnd, parent;
3876 if( uiThreadId != GetCurrentThreadId() )
3877 return SendMessageW( hMsiHiddenWindow, WM_MSI_DIALOG_CREATE, 0, (LPARAM) dialog );
3879 /* create the dialog window, don't show it yet */
3880 style = WS_OVERLAPPED | WS_SYSMENU;
3881 if( dialog->attributes & msidbDialogAttributesVisible )
3882 style |= WS_VISIBLE;
3884 if (dialog->parent == NULL && (dialog->attributes & msidbDialogAttributesMinimize))
3885 style |= WS_MINIMIZEBOX;
3887 parent = dialog->parent ? dialog->parent->hwnd : 0;
3889 hwnd = CreateWindowW( L"MsiDialogCloseClass", dialog->name, style,
3890 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
3891 parent, NULL, NULL, dialog );
3892 if( !hwnd )
3894 ERR("Failed to create dialog %s\n", debugstr_w( dialog->name ));
3895 return ERROR_FUNCTION_FAILED;
3898 ShowWindow( hwnd, SW_SHOW );
3899 /* UpdateWindow( hwnd ); - and causes the transparent static controls not to paint */
3901 if( dialog->attributes & msidbDialogAttributesModal )
3903 while( !dialog->finished )
3905 MsgWaitForMultipleObjects( 0, NULL, 0, INFINITE, QS_ALLINPUT );
3906 process_pending_messages( dialog->hwnd );
3909 else
3910 return ERROR_IO_PENDING;
3912 return ERROR_SUCCESS;
3915 static LRESULT WINAPI MSIHiddenWindowProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3917 msi_dialog *dialog = (msi_dialog*) lParam;
3919 TRACE("%d %p\n", msg, dialog);
3921 switch (msg)
3923 case WM_MSI_DIALOG_CREATE:
3924 return dialog_run_message_loop( dialog );
3925 case WM_MSI_DIALOG_DESTROY:
3926 msi_dialog_destroy( dialog );
3927 return 0;
3929 return DefWindowProcW( hwnd, msg, wParam, lParam );
3932 static BOOL dialog_register_class( void )
3934 WNDCLASSW cls;
3936 ZeroMemory( &cls, sizeof cls );
3937 cls.lpfnWndProc = MSIDialog_WndProc;
3938 cls.hInstance = NULL;
3939 cls.hIcon = LoadIconW(0, (LPWSTR)IDI_APPLICATION);
3940 cls.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
3941 cls.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
3942 cls.lpszMenuName = NULL;
3943 cls.lpszClassName = L"MsiDialogCloseClass";
3945 if( !RegisterClassW( &cls ) )
3946 return FALSE;
3948 cls.lpfnWndProc = MSIHiddenWindowProc;
3949 cls.lpszClassName = L"MsiHiddenWindow";
3951 if( !RegisterClassW( &cls ) )
3952 return FALSE;
3954 uiThreadId = GetCurrentThreadId();
3956 hMsiHiddenWindow = CreateWindowW( L"MsiHiddenWindow", NULL, WS_OVERLAPPED,
3957 0, 0, 100, 100, NULL, NULL, NULL, NULL );
3958 if( !hMsiHiddenWindow )
3959 return FALSE;
3961 return TRUE;
3964 static msi_dialog *dialog_create( MSIPACKAGE *package, const WCHAR *name, msi_dialog *parent,
3965 UINT (*event_handler)(msi_dialog *, const WCHAR *, const WCHAR *) )
3967 MSIRECORD *rec = NULL;
3968 msi_dialog *dialog;
3970 TRACE("%s\n", debugstr_w(name));
3972 if (!hMsiHiddenWindow) dialog_register_class();
3974 /* allocate the structure for the dialog to use */
3975 dialog = calloc( 1, offsetof( msi_dialog, name[wcslen( name ) + 1] ) );
3976 if( !dialog )
3977 return NULL;
3978 lstrcpyW( dialog->name, name );
3979 dialog->parent = parent;
3980 dialog->package = package;
3981 dialog->event_handler = event_handler;
3982 dialog->finished = 0;
3983 list_init( &dialog->controls );
3984 list_init( &dialog->fonts );
3986 /* verify that the dialog exists */
3987 rec = get_dialog_record( dialog );
3988 if( !rec )
3990 free( dialog );
3991 return NULL;
3993 dialog->attributes = MSI_RecordGetInteger( rec, 6 );
3994 dialog->control_default = wcsdup( MSI_RecordGetString( rec, 9 ) );
3995 dialog->control_cancel = wcsdup( MSI_RecordGetString( rec, 10 ) );
3996 msiobj_release( &rec->hdr );
3998 rec = MSI_CreateRecord(2);
3999 if (!rec)
4001 msi_dialog_destroy(dialog);
4002 return NULL;
4004 MSI_RecordSetStringW(rec, 1, name);
4005 MSI_RecordSetStringW(rec, 2, L"Dialog created");
4006 MSI_ProcessMessage(package, INSTALLMESSAGE_ACTIONSTART, rec);
4007 msiobj_release(&rec->hdr);
4009 return dialog;
4012 static void dialog_end_dialog( msi_dialog *dialog )
4014 TRACE("%p\n", dialog);
4015 dialog->finished = 1;
4016 PostMessageW(dialog->hwnd, WM_NULL, 0, 0);
4019 void msi_dialog_check_messages( HANDLE handle )
4021 DWORD r;
4023 /* in threads other than the UI thread, block */
4024 if( uiThreadId != GetCurrentThreadId() )
4026 if (!handle) return;
4027 while (MsgWaitForMultipleObjectsEx( 1, &handle, INFINITE, QS_ALLINPUT, 0 ) == WAIT_OBJECT_0 + 1)
4029 MSG msg;
4030 while (PeekMessageW( &msg, NULL, 0, 0, PM_REMOVE ))
4032 TranslateMessage( &msg );
4033 DispatchMessageW( &msg );
4036 return;
4039 /* there are two choices for the UI thread */
4040 while (1)
4042 process_pending_messages( NULL );
4044 if( !handle )
4045 break;
4048 * block here until somebody creates a new dialog or
4049 * the handle we're waiting on becomes ready
4051 r = MsgWaitForMultipleObjects( 1, &handle, 0, INFINITE, QS_ALLINPUT );
4052 if( r == WAIT_OBJECT_0 )
4053 break;
4057 static void dialog_do_preview( msi_dialog *dialog )
4059 TRACE("\n");
4060 dialog->attributes |= msidbDialogAttributesVisible;
4061 dialog->attributes &= ~msidbDialogAttributesModal;
4062 dialog_run_message_loop( dialog );
4065 static void free_subscriber( struct subscriber *sub )
4067 free( sub->event );
4068 free( sub->control );
4069 free( sub->attribute );
4070 free( sub );
4073 static void event_cleanup_subscriptions( MSIPACKAGE *package, const WCHAR *dialog )
4075 struct list *item, *next;
4077 LIST_FOR_EACH_SAFE( item, next, &package->subscriptions )
4079 struct subscriber *sub = LIST_ENTRY( item, struct subscriber, entry );
4081 if (wcscmp( sub->dialog->name, dialog )) continue;
4082 list_remove( &sub->entry );
4083 free_subscriber( sub );
4087 void msi_dialog_destroy( msi_dialog *dialog )
4089 struct font *font, *next;
4091 if( uiThreadId != GetCurrentThreadId() )
4093 SendMessageW( hMsiHiddenWindow, WM_MSI_DIALOG_DESTROY, 0, (LPARAM) dialog );
4094 return;
4097 if( dialog->hwnd )
4099 ShowWindow( dialog->hwnd, SW_HIDE );
4100 DestroyWindow( dialog->hwnd );
4103 /* unsubscribe events */
4104 event_cleanup_subscriptions( dialog->package, dialog->name );
4106 /* destroy the list of controls */
4107 while( !list_empty( &dialog->controls ) )
4109 struct control *t;
4111 t = LIST_ENTRY( list_head( &dialog->controls ), struct control, entry );
4112 destroy_control( t );
4115 /* destroy the list of fonts */
4116 LIST_FOR_EACH_ENTRY_SAFE( font, next, &dialog->fonts, struct font, entry )
4118 list_remove( &font->entry );
4119 DeleteObject( font->hfont );
4120 free( font );
4122 free( dialog->default_font );
4124 free( dialog->control_default );
4125 free( dialog->control_cancel );
4126 dialog->package = NULL;
4127 free( dialog );
4130 void msi_dialog_unregister_class( void )
4132 DestroyWindow( hMsiHiddenWindow );
4133 hMsiHiddenWindow = NULL;
4134 UnregisterClassW( L"MsiDialogCloseClass", NULL );
4135 UnregisterClassW( L"MsiHiddenWindow", NULL );
4136 uiThreadId = 0;
4139 void msi_event_cleanup_all_subscriptions( MSIPACKAGE *package )
4141 struct list *item, *next;
4143 LIST_FOR_EACH_SAFE( item, next, &package->subscriptions )
4145 struct subscriber *sub = LIST_ENTRY( item, struct subscriber, entry );
4146 list_remove( &sub->entry );
4147 free_subscriber( sub );
4151 static void MSI_ClosePreview( MSIOBJECTHDR *arg )
4153 MSIPREVIEW *preview = (MSIPREVIEW *)arg;
4154 msiobj_release( &preview->package->hdr );
4157 static MSIPREVIEW *MSI_EnableUIPreview( MSIDATABASE *db )
4159 MSIPREVIEW *preview = NULL;
4160 MSIPACKAGE *package;
4162 package = MSI_CreatePackage( db );
4163 if (package)
4165 preview = alloc_msiobject( MSIHANDLETYPE_PREVIEW, sizeof(MSIPREVIEW), MSI_ClosePreview );
4166 if (preview)
4168 preview->package = package;
4169 msiobj_addref( &package->hdr );
4171 msiobj_release( &package->hdr );
4173 return preview;
4176 UINT WINAPI MsiEnableUIPreview( MSIHANDLE hdb, MSIHANDLE *phPreview )
4178 MSIDATABASE *db;
4179 MSIPREVIEW *preview;
4180 UINT r = ERROR_FUNCTION_FAILED;
4182 TRACE( "%lu %p\n", hdb, phPreview );
4184 if (!(db = msihandle2msiinfo(hdb, MSIHANDLETYPE_DATABASE)))
4185 return ERROR_INVALID_HANDLE;
4187 preview = MSI_EnableUIPreview( db );
4188 if (preview)
4190 *phPreview = alloc_msihandle( &preview->hdr );
4191 msiobj_release( &preview->hdr );
4192 r = ERROR_SUCCESS;
4193 if (!*phPreview)
4194 r = ERROR_NOT_ENOUGH_MEMORY;
4196 msiobj_release( &db->hdr );
4197 return r;
4200 static UINT preview_event_handler( msi_dialog *dialog, const WCHAR *event, const WCHAR *argument )
4202 MESSAGE("Preview dialog event '%s' (arg='%s')\n", debugstr_w(event), debugstr_w(argument));
4203 return ERROR_SUCCESS;
4206 static UINT MSI_PreviewDialogW( MSIPREVIEW *preview, LPCWSTR szDialogName )
4208 msi_dialog *dialog = NULL;
4209 UINT r = ERROR_SUCCESS;
4211 if (preview->dialog)
4212 msi_dialog_destroy( preview->dialog );
4214 /* an empty name means we should just destroy the current preview dialog */
4215 if (szDialogName)
4217 dialog = dialog_create( preview->package, szDialogName, NULL, preview_event_handler );
4218 if (dialog)
4219 dialog_do_preview( dialog );
4220 else
4221 r = ERROR_FUNCTION_FAILED;
4223 preview->dialog = dialog;
4224 return r;
4227 UINT WINAPI MsiPreviewDialogW( MSIHANDLE hPreview, LPCWSTR szDialogName )
4229 MSIPREVIEW *preview;
4230 UINT r;
4232 TRACE( "%lu %s\n", hPreview, debugstr_w(szDialogName) );
4234 preview = msihandle2msiinfo( hPreview, MSIHANDLETYPE_PREVIEW );
4235 if (!preview)
4236 return ERROR_INVALID_HANDLE;
4238 r = MSI_PreviewDialogW( preview, szDialogName );
4239 msiobj_release( &preview->hdr );
4240 return r;
4243 UINT WINAPI MsiPreviewDialogA( MSIHANDLE hPreview, LPCSTR szDialogName )
4245 UINT r;
4246 LPWSTR strW = NULL;
4248 TRACE( "%lu %s\n", hPreview, debugstr_a(szDialogName) );
4250 if (szDialogName)
4252 strW = strdupAtoW( szDialogName );
4253 if (!strW)
4254 return ERROR_OUTOFMEMORY;
4256 r = MsiPreviewDialogW( hPreview, strW );
4257 free( strW );
4258 return r;
4261 UINT WINAPI MsiPreviewBillboardW( MSIHANDLE hPreview, const WCHAR *szControlName, const WCHAR *szBillboard )
4263 FIXME( "%lu %s %s\n", hPreview, debugstr_w(szControlName), debugstr_w(szBillboard) );
4264 return ERROR_CALL_NOT_IMPLEMENTED;
4267 UINT WINAPI MsiPreviewBillboardA( MSIHANDLE hPreview, const char *szControlName, const char *szBillboard )
4269 FIXME( "%lu %s %s\n", hPreview, debugstr_a(szControlName), debugstr_a(szBillboard) );
4270 return ERROR_CALL_NOT_IMPLEMENTED;
4273 struct control_event
4275 const WCHAR *event;
4276 UINT (*handler)( msi_dialog *, const WCHAR * );
4279 static UINT dialog_event_handler( msi_dialog *, const WCHAR *, const WCHAR * );
4281 /* create a dialog box and run it if it's modal */
4282 static INT event_do_dialog( MSIPACKAGE *package, const WCHAR *name, msi_dialog *parent, BOOL destroy_modeless )
4284 msi_dialog *dialog;
4285 UINT r;
4286 INT retval;
4288 /* create a new dialog */
4289 dialog = dialog_create( package, name, parent, dialog_event_handler );
4290 if (dialog)
4292 /* kill the current modeless dialog */
4293 if (destroy_modeless && package->dialog)
4295 msi_dialog_destroy( package->dialog );
4296 package->dialog = NULL;
4299 /* modeless dialogs return an error message */
4300 r = dialog_run_message_loop( dialog );
4301 if (r == ERROR_SUCCESS)
4303 retval = dialog->retval;
4304 msi_dialog_destroy( dialog );
4305 return retval;
4307 else
4309 package->dialog = dialog;
4310 return IDOK;
4313 else return 0;
4316 /* end a modal dialog box */
4317 static UINT event_end_dialog( msi_dialog *dialog, const WCHAR *argument )
4319 if (!wcscmp( argument, L"Exit" ))
4320 dialog->retval = IDCANCEL;
4321 else if (!wcscmp( argument, L"Retry" ))
4322 dialog->retval = IDRETRY;
4323 else if (!wcscmp( argument, L"Ignore" ))
4324 dialog->retval = IDOK;
4325 else if (!wcscmp( argument, L"Return" ))
4326 dialog->retval = 0;
4327 else
4329 ERR("Unknown argument string %s\n", debugstr_w(argument));
4330 dialog->retval = IDABORT;
4332 event_cleanup_subscriptions( dialog->package, dialog->name );
4333 dialog_end_dialog( dialog );
4334 return ERROR_SUCCESS;
4337 static UINT pending_event_end_dialog( msi_dialog *dialog, const WCHAR *argument )
4339 dialog->pending_event = event_end_dialog;
4340 free( dialog->pending_argument );
4341 dialog->pending_argument = wcsdup( argument );
4342 return ERROR_SUCCESS;
4345 /* transition from one modal dialog to another modal dialog */
4346 static UINT event_new_dialog( msi_dialog *dialog, const WCHAR *argument )
4348 /* store the name of the next dialog, and signal this one to end */
4349 dialog->package->next_dialog = wcsdup( argument );
4350 msi_event_cleanup_all_subscriptions( dialog->package );
4351 dialog_end_dialog( dialog );
4352 return ERROR_SUCCESS;
4355 static UINT pending_event_new_dialog( msi_dialog *dialog, const WCHAR *argument )
4357 dialog->pending_event = event_new_dialog;
4358 free( dialog->pending_argument );
4359 dialog->pending_argument = wcsdup( argument );
4360 return ERROR_SUCCESS;
4363 /* create a new child dialog of an existing modal dialog */
4364 static UINT event_spawn_dialog( msi_dialog *dialog, const WCHAR *argument )
4366 INT r;
4367 /* don't destroy a modeless dialogs that might be our parent */
4368 r = event_do_dialog( dialog->package, argument, dialog, FALSE );
4369 if (r != 0)
4371 dialog->retval = r;
4372 dialog_end_dialog( dialog );
4374 else
4375 dialog_update_all_controls(dialog);
4377 return ERROR_SUCCESS;
4380 static UINT pending_event_spawn_dialog( msi_dialog *dialog, const WCHAR *argument )
4382 dialog->pending_event = event_spawn_dialog;
4383 free( dialog->pending_argument );
4384 dialog->pending_argument = wcsdup( argument );
4385 return ERROR_SUCCESS;
4388 /* creates a dialog that remains up for a period of time based on a condition */
4389 static UINT event_spawn_wait_dialog( msi_dialog *dialog, const WCHAR *argument )
4391 FIXME("doing nothing\n");
4392 return ERROR_SUCCESS;
4395 static UINT event_do_action( msi_dialog *dialog, const WCHAR *argument )
4397 ACTION_PerformAction(dialog->package, argument);
4398 return ERROR_SUCCESS;
4401 static UINT event_add_local( msi_dialog *dialog, const WCHAR *argument )
4403 MSIFEATURE *feature;
4405 LIST_FOR_EACH_ENTRY( feature, &dialog->package->features, MSIFEATURE, entry )
4407 if (!wcscmp( argument, feature->Feature ) || !wcscmp( argument, L"ALL" ))
4409 if (feature->ActionRequest != INSTALLSTATE_LOCAL)
4410 msi_set_property( dialog->package->db, L"Preselected", L"1", -1 );
4411 MSI_SetFeatureStateW( dialog->package, feature->Feature, INSTALLSTATE_LOCAL );
4414 return ERROR_SUCCESS;
4417 static UINT event_remove( msi_dialog *dialog, const WCHAR *argument )
4419 MSIFEATURE *feature;
4421 LIST_FOR_EACH_ENTRY( feature, &dialog->package->features, MSIFEATURE, entry )
4423 if (!wcscmp( argument, feature->Feature ) || !wcscmp( argument, L"ALL" ))
4425 if (feature->ActionRequest != INSTALLSTATE_ABSENT)
4426 msi_set_property( dialog->package->db, L"Preselected", L"1", -1 );
4427 MSI_SetFeatureStateW( dialog->package, feature->Feature, INSTALLSTATE_ABSENT );
4430 return ERROR_SUCCESS;
4433 static UINT event_add_source( msi_dialog *dialog, const WCHAR *argument )
4435 MSIFEATURE *feature;
4437 LIST_FOR_EACH_ENTRY( feature, &dialog->package->features, MSIFEATURE, entry )
4439 if (!wcscmp( argument, feature->Feature ) || !wcscmp( argument, L"ALL" ))
4441 if (feature->ActionRequest != INSTALLSTATE_SOURCE)
4442 msi_set_property( dialog->package->db, L"Preselected", L"1", -1 );
4443 MSI_SetFeatureStateW( dialog->package, feature->Feature, INSTALLSTATE_SOURCE );
4446 return ERROR_SUCCESS;
4449 void msi_event_fire( MSIPACKAGE *package, const WCHAR *event, MSIRECORD *rec )
4451 struct subscriber *sub;
4453 TRACE("firing event %s\n", debugstr_w(event));
4455 LIST_FOR_EACH_ENTRY( sub, &package->subscriptions, struct subscriber, entry )
4457 if (wcsicmp( sub->event, event )) continue;
4458 dialog_handle_event( sub->dialog, sub->control, sub->attribute, rec );
4462 static UINT event_set_target_path( msi_dialog *dialog, const WCHAR *argument )
4464 WCHAR *path = msi_dup_property( dialog->package->db, argument );
4465 MSIRECORD *rec = MSI_CreateRecord( 1 );
4466 UINT r = ERROR_SUCCESS;
4468 MSI_RecordSetStringW( rec, 1, path );
4469 msi_event_fire( dialog->package, L"SelectionPath", rec );
4470 if (path)
4472 /* failure to set the path halts the executing of control events */
4473 r = MSI_SetTargetPathW( dialog->package, argument, path );
4474 free( path );
4476 msiobj_release( &rec->hdr );
4477 return r;
4480 static UINT event_reset( msi_dialog *dialog, const WCHAR *argument )
4482 dialog_reset( dialog );
4483 return ERROR_SUCCESS;
4486 INT ACTION_ShowDialog( MSIPACKAGE *package, const WCHAR *dialog )
4488 MSIRECORD *row;
4489 INT rc;
4491 if (!TABLE_Exists(package->db, L"Dialog")) return 0;
4493 row = MSI_CreateRecord(0);
4494 if (!row) return -1;
4495 MSI_RecordSetStringW(row, 0, dialog);
4496 rc = MSI_ProcessMessage(package, INSTALLMESSAGE_SHOWDIALOG, row);
4497 msiobj_release(&row->hdr);
4499 if (rc == -2) rc = 0;
4501 if (!rc)
4503 MSIRECORD *row = MSI_CreateRecord(2);
4504 if (!row) return -1;
4505 MSI_RecordSetInteger(row, 1, 2726);
4506 MSI_RecordSetStringW(row, 2, dialog);
4507 MSI_ProcessMessage(package, INSTALLMESSAGE_INFO, row);
4509 msiobj_release(&row->hdr);
4511 return rc;
4514 INT ACTION_DialogBox( MSIPACKAGE *package, const WCHAR *dialog )
4516 INT r;
4518 if (package->next_dialog) ERR("Already got next dialog... ignoring it\n");
4519 package->next_dialog = NULL;
4521 /* Dialogs are chained through NewDialog, which sets the next_dialog member.
4522 * We fall out of the loop if we reach a modeless dialog, which immediately
4523 * returns IDOK, or an EndDialog event, which returns the value corresponding
4524 * to its argument.
4526 r = event_do_dialog( package, dialog, NULL, TRUE );
4527 while (package->next_dialog)
4529 WCHAR *name = package->next_dialog;
4531 package->next_dialog = NULL;
4532 r = event_do_dialog( package, name, NULL, TRUE );
4533 free( name );
4535 return r;
4538 static UINT event_set_install_level( msi_dialog *dialog, const WCHAR *argument )
4540 int level = wcstol( argument, NULL, 10 );
4542 TRACE("setting install level to %d\n", level);
4543 return MSI_SetInstallLevel( dialog->package, level );
4546 static UINT event_directory_list_up( msi_dialog *dialog, const WCHAR *argument )
4548 return dialog_directorylist_up( dialog );
4551 static UINT event_directory_list_new( msi_dialog *dialog, const WCHAR *argument )
4553 return dialog_directorylist_new( dialog );
4556 static UINT event_reinstall_mode( msi_dialog *dialog, const WCHAR *argument )
4558 return msi_set_property( dialog->package->db, L"REINSTALLMODE", argument, -1 );
4561 static UINT event_reinstall( msi_dialog *dialog, const WCHAR *argument )
4563 return msi_set_property( dialog->package->db, L"REINSTALL", argument, -1 );
4566 static UINT event_validate_product_id( msi_dialog *dialog, const WCHAR *argument )
4568 return msi_validate_product_id( dialog->package );
4571 static const struct control_event control_events[] =
4573 { L"EndDialog", pending_event_end_dialog },
4574 { L"NewDialog", pending_event_new_dialog },
4575 { L"SpawnDialog", pending_event_spawn_dialog },
4576 { L"SpawnWaitDialog", event_spawn_wait_dialog },
4577 { L"DoAction", event_do_action },
4578 { L"AddLocal", event_add_local },
4579 { L"Remove", event_remove },
4580 { L"AddSource", event_add_source },
4581 { L"SetTargetPath", event_set_target_path },
4582 { L"Reset", event_reset },
4583 { L"SetInstallLevel", event_set_install_level },
4584 { L"DirectoryListUp", event_directory_list_up },
4585 { L"DirectoryListNew", event_directory_list_new },
4586 { L"SelectionBrowse", event_spawn_dialog },
4587 { L"ReinstallMode", event_reinstall_mode },
4588 { L"Reinstall", event_reinstall },
4589 { L"ValidateProductID", event_validate_product_id },
4590 { NULL, NULL }
4593 static UINT dialog_event_handler( msi_dialog *dialog, const WCHAR *event, const WCHAR *argument )
4595 unsigned int i;
4597 TRACE("handling event %s\n", debugstr_w(event));
4599 if (!event) return ERROR_SUCCESS;
4601 for (i = 0; control_events[i].event; i++)
4603 if (!wcscmp( control_events[i].event, event ))
4604 return control_events[i].handler( dialog, argument );
4606 FIXME("unhandled event %s arg(%s)\n", debugstr_w(event), debugstr_w(argument));
4607 return ERROR_SUCCESS;