opengl32: Fix some style issues.
[wine.git] / dlls / msi / dialog.c
blob92374d4f0f57fa7f7cc420633d63014730fe9440
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
24 #define NONAMELESSSTRUCT
26 #include <stdarg.h>
28 #include "windef.h"
29 #include "winbase.h"
30 #include "wingdi.h"
31 #include "winuser.h"
32 #include "winnls.h"
33 #include "msi.h"
34 #include "msipriv.h"
35 #include "msidefs.h"
36 #include "ocidl.h"
37 #include "olectl.h"
38 #include "richedit.h"
39 #include "commctrl.h"
40 #include "winreg.h"
41 #include "shlwapi.h"
42 #include "msiserver.h"
43 #include "shellapi.h"
45 #include "wine/debug.h"
46 #include "wine/unicode.h"
48 WINE_DEFAULT_DEBUG_CHANNEL(msi);
50 extern HINSTANCE msi_hInstance;
52 struct msi_control_tag;
53 typedef struct msi_control_tag msi_control;
54 typedef UINT (*msi_handler)( msi_dialog *, msi_control *, WPARAM );
55 typedef void (*msi_update)( msi_dialog *, msi_control * );
56 typedef UINT (*control_event_handler)( msi_dialog *, const WCHAR *, const WCHAR * );
58 struct msi_control_tag
60 struct list entry;
61 HWND hwnd;
62 msi_handler handler;
63 msi_update update;
64 LPWSTR property;
65 LPWSTR value;
66 HBITMAP hBitmap;
67 HICON hIcon;
68 LPWSTR tabnext;
69 LPWSTR type;
70 HMODULE hDll;
71 float progress_current;
72 float progress_max;
73 BOOL progress_backwards;
74 DWORD attributes;
75 WCHAR name[1];
78 typedef struct msi_font_tag
80 struct list entry;
81 HFONT hfont;
82 COLORREF color;
83 WCHAR name[1];
84 } msi_font;
86 struct msi_dialog_tag
88 MSIPACKAGE *package;
89 msi_dialog *parent;
90 control_event_handler event_handler;
91 BOOL finished;
92 INT scale;
93 DWORD attributes;
94 SIZE size;
95 HWND hwnd;
96 LPWSTR default_font;
97 struct list fonts;
98 struct list controls;
99 HWND hWndFocus;
100 LPWSTR control_default;
101 LPWSTR control_cancel;
102 WCHAR name[1];
105 struct subscriber
107 struct list entry;
108 msi_dialog *dialog;
109 WCHAR *event;
110 WCHAR *control;
111 WCHAR *attribute;
114 typedef UINT (*msi_dialog_control_func)( msi_dialog *dialog, MSIRECORD *rec );
115 struct control_handler
117 LPCWSTR control_type;
118 msi_dialog_control_func func;
121 typedef struct
123 msi_dialog* dialog;
124 msi_control *parent;
125 DWORD attributes;
126 LPWSTR propval;
127 } radio_button_group_descr;
129 static const WCHAR szMsiDialogClass[] = { 'M','s','i','D','i','a','l','o','g','C','l','o','s','e','C','l','a','s','s',0 };
130 static const WCHAR szMsiHiddenWindow[] = { 'M','s','i','H','i','d','d','e','n','W','i','n','d','o','w',0 };
131 static const WCHAR szStatic[] = { 'S','t','a','t','i','c',0 };
132 static const WCHAR szButton[] = { 'B','U','T','T','O','N', 0 };
133 static const WCHAR szButtonData[] = { 'M','S','I','D','A','T','A',0 };
134 static const WCHAR szProgress[] = { 'P','r','o','g','r','e','s','s',0 };
135 static const WCHAR szText[] = { 'T','e','x','t',0 };
136 static const WCHAR szPushButton[] = { 'P','u','s','h','B','u','t','t','o','n',0 };
137 static const WCHAR szLine[] = { 'L','i','n','e',0 };
138 static const WCHAR szBitmap[] = { 'B','i','t','m','a','p',0 };
139 static const WCHAR szCheckBox[] = { 'C','h','e','c','k','B','o','x',0 };
140 static const WCHAR szScrollableText[] = { 'S','c','r','o','l','l','a','b','l','e','T','e','x','t',0 };
141 static const WCHAR szComboBox[] = { 'C','o','m','b','o','B','o','x',0 };
142 static const WCHAR szEdit[] = { 'E','d','i','t',0 };
143 static const WCHAR szMaskedEdit[] = { 'M','a','s','k','e','d','E','d','i','t',0 };
144 static const WCHAR szPathEdit[] = { 'P','a','t','h','E','d','i','t',0 };
145 static const WCHAR szProgressBar[] = { 'P','r','o','g','r','e','s','s','B','a','r',0 };
146 static const WCHAR szSetProgress[] = { 'S','e','t','P','r','o','g','r','e','s','s',0 };
147 static const WCHAR szRadioButtonGroup[] = { 'R','a','d','i','o','B','u','t','t','o','n','G','r','o','u','p',0 };
148 static const WCHAR szIcon[] = { 'I','c','o','n',0 };
149 static const WCHAR szSelectionTree[] = { 'S','e','l','e','c','t','i','o','n','T','r','e','e',0 };
150 static const WCHAR szGroupBox[] = { 'G','r','o','u','p','B','o','x',0 };
151 static const WCHAR szListBox[] = { 'L','i','s','t','B','o','x',0 };
152 static const WCHAR szDirectoryCombo[] = { 'D','i','r','e','c','t','o','r','y','C','o','m','b','o',0 };
153 static const WCHAR szDirectoryList[] = { 'D','i','r','e','c','t','o','r','y','L','i','s','t',0 };
154 static const WCHAR szVolumeCostList[] = { 'V','o','l','u','m','e','C','o','s','t','L','i','s','t',0 };
155 static const WCHAR szVolumeSelectCombo[] = { 'V','o','l','u','m','e','S','e','l','e','c','t','C','o','m','b','o',0 };
156 static const WCHAR szSelectionDescription[] = {'S','e','l','e','c','t','i','o','n','D','e','s','c','r','i','p','t','i','o','n',0};
157 static const WCHAR szSelectionPath[] = {'S','e','l','e','c','t','i','o','n','P','a','t','h',0};
158 static const WCHAR szProperty[] = {'P','r','o','p','e','r','t','y',0};
159 static const WCHAR szHyperLink[] = {'H','y','p','e','r','L','i','n','k',0};
161 /* dialog sequencing */
163 #define WM_MSI_DIALOG_CREATE (WM_USER+0x100)
164 #define WM_MSI_DIALOG_DESTROY (WM_USER+0x101)
166 #define USER_INSTALLSTATE_ALL 0x1000
168 static DWORD uiThreadId;
169 static HWND hMsiHiddenWindow;
171 static LPWSTR msi_get_window_text( HWND hwnd )
173 UINT sz, r;
174 LPWSTR buf;
176 sz = 0x20;
177 buf = msi_alloc( sz*sizeof(WCHAR) );
178 while ( buf )
180 r = GetWindowTextW( hwnd, buf, sz );
181 if ( r < (sz - 1) )
182 break;
183 sz *= 2;
184 buf = msi_realloc( buf, sz*sizeof(WCHAR) );
187 return buf;
190 static INT msi_dialog_scale_unit( msi_dialog *dialog, INT val )
192 return MulDiv( val, dialog->scale, 12 );
195 static msi_control *msi_dialog_find_control( msi_dialog *dialog, LPCWSTR name )
197 msi_control *control;
199 if( !name )
200 return NULL;
201 if( !dialog->hwnd )
202 return NULL;
203 LIST_FOR_EACH_ENTRY( control, &dialog->controls, msi_control, entry )
204 if( !strcmpW( control->name, name ) ) /* FIXME: case sensitive? */
205 return control;
206 return NULL;
209 static msi_control *msi_dialog_find_control_by_type( msi_dialog *dialog, LPCWSTR type )
211 msi_control *control;
213 if( !type )
214 return NULL;
215 if( !dialog->hwnd )
216 return NULL;
217 LIST_FOR_EACH_ENTRY( control, &dialog->controls, msi_control, entry )
218 if( !strcmpW( control->type, type ) ) /* FIXME: case sensitive? */
219 return control;
220 return NULL;
223 static msi_control *msi_dialog_find_control_by_hwnd( msi_dialog *dialog, HWND hwnd )
225 msi_control *control;
227 if( !dialog->hwnd )
228 return NULL;
229 LIST_FOR_EACH_ENTRY( control, &dialog->controls, msi_control, entry )
230 if( hwnd == control->hwnd )
231 return control;
232 return NULL;
235 static LPWSTR msi_get_deformatted_field( MSIPACKAGE *package, MSIRECORD *rec, int field )
237 LPCWSTR str = MSI_RecordGetString( rec, field );
238 LPWSTR ret = NULL;
240 if (str)
241 deformat_string( package, str, &ret );
242 return ret;
245 static LPWSTR msi_dialog_dup_property( msi_dialog *dialog, LPCWSTR property, BOOL indirect )
247 LPWSTR prop = NULL;
249 if (!property)
250 return NULL;
252 if (indirect)
253 prop = msi_dup_property( dialog->package->db, property );
255 if (!prop)
256 prop = strdupW( property );
258 return prop;
262 * msi_dialog_get_style
264 * Extract the {\style} string from the front of the text to display and
265 * update the pointer. Only the last style in a list is applied.
267 static LPWSTR msi_dialog_get_style( LPCWSTR p, LPCWSTR *rest )
269 LPWSTR ret;
270 LPCWSTR q, i, first;
271 DWORD len;
273 q = NULL;
274 *rest = p;
275 if( !p )
276 return NULL;
278 while ((first = strchrW( p, '{' )) && (q = strchrW( first + 1, '}' )))
280 p = first + 1;
281 if( *p != '\\' && *p != '&' )
282 return NULL;
284 /* little bit of sanity checking to stop us getting confused with RTF */
285 for( i=++p; i<q; i++ )
286 if( *i == '}' || *i == '\\' )
287 return NULL;
290 if (!q)
291 return NULL;
293 *rest = ++q;
294 len = q - p;
296 ret = msi_alloc( len*sizeof(WCHAR) );
297 if( !ret )
298 return ret;
299 memcpy( ret, p, len*sizeof(WCHAR) );
300 ret[len-1] = 0;
301 return ret;
304 static UINT msi_dialog_add_font( MSIRECORD *rec, LPVOID param )
306 msi_dialog *dialog = param;
307 msi_font *font;
308 LPCWSTR face, name;
309 LOGFONTW lf;
310 INT style;
311 HDC hdc;
313 /* create a font and add it to the list */
314 name = MSI_RecordGetString( rec, 1 );
315 font = msi_alloc( FIELD_OFFSET( msi_font, name[strlenW( name ) + 1] ));
316 strcpyW( font->name, name );
317 list_add_head( &dialog->fonts, &font->entry );
319 font->color = MSI_RecordGetInteger( rec, 4 );
321 memset( &lf, 0, sizeof lf );
322 face = MSI_RecordGetString( rec, 2 );
323 lf.lfHeight = MSI_RecordGetInteger( rec, 3 );
324 style = MSI_RecordGetInteger( rec, 5 );
325 if( style & msidbTextStyleStyleBitsBold )
326 lf.lfWeight = FW_BOLD;
327 if( style & msidbTextStyleStyleBitsItalic )
328 lf.lfItalic = TRUE;
329 if( style & msidbTextStyleStyleBitsUnderline )
330 lf.lfUnderline = TRUE;
331 if( style & msidbTextStyleStyleBitsStrike )
332 lf.lfStrikeOut = TRUE;
333 lstrcpynW( lf.lfFaceName, face, LF_FACESIZE );
335 /* adjust the height */
336 hdc = GetDC( dialog->hwnd );
337 if (hdc)
339 lf.lfHeight = -MulDiv(lf.lfHeight, GetDeviceCaps(hdc, LOGPIXELSY), 72);
340 ReleaseDC( dialog->hwnd, hdc );
343 font->hfont = CreateFontIndirectW( &lf );
345 TRACE("Adding font style %s\n", debugstr_w(font->name) );
347 return ERROR_SUCCESS;
350 static msi_font *msi_dialog_find_font( msi_dialog *dialog, LPCWSTR name )
352 msi_font *font = NULL;
354 LIST_FOR_EACH_ENTRY( font, &dialog->fonts, msi_font, entry )
355 if( !strcmpW( font->name, name ) ) /* FIXME: case sensitive? */
356 break;
358 return font;
361 static UINT msi_dialog_set_font( msi_dialog *dialog, HWND hwnd, LPCWSTR name )
363 msi_font *font;
365 font = msi_dialog_find_font( dialog, name );
366 if( font )
367 SendMessageW( hwnd, WM_SETFONT, (WPARAM) font->hfont, TRUE );
368 else
369 ERR("No font entry for %s\n", debugstr_w(name));
370 return ERROR_SUCCESS;
373 static UINT msi_dialog_build_font_list( msi_dialog *dialog )
375 static const WCHAR query[] = {
376 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
377 '`','T','e','x','t','S','t','y','l','e','`',0};
378 MSIQUERY *view;
379 UINT r;
381 TRACE("dialog %p\n", dialog );
383 r = MSI_OpenQuery( dialog->package->db, &view, query );
384 if( r != ERROR_SUCCESS )
385 return r;
387 r = MSI_IterateRecords( view, NULL, msi_dialog_add_font, dialog );
388 msiobj_release( &view->hdr );
389 return r;
392 static void msi_destroy_control( msi_control *t )
394 list_remove( &t->entry );
395 /* leave dialog->hwnd - destroying parent destroys child windows */
396 msi_free( t->property );
397 msi_free( t->value );
398 if( t->hBitmap )
399 DeleteObject( t->hBitmap );
400 if( t->hIcon )
401 DestroyIcon( t->hIcon );
402 msi_free( t->tabnext );
403 msi_free( t->type );
404 if (t->hDll)
405 FreeLibrary( t->hDll );
406 msi_free( t );
409 static msi_control *dialog_create_window( msi_dialog *dialog, MSIRECORD *rec, DWORD exstyle,
410 const WCHAR *szCls, const WCHAR *name, const WCHAR *text,
411 DWORD style, HWND parent )
413 DWORD x, y, width, height;
414 LPWSTR font = NULL, title_font = NULL;
415 LPCWSTR title = NULL;
416 msi_control *control;
418 style |= WS_CHILD;
420 control = msi_alloc( FIELD_OFFSET( msi_control, name[strlenW( name ) + 1] ));
421 if (!control)
422 return NULL;
424 strcpyW( control->name, name );
425 list_add_tail( &dialog->controls, &control->entry );
426 control->handler = NULL;
427 control->update = NULL;
428 control->property = NULL;
429 control->value = NULL;
430 control->hBitmap = NULL;
431 control->hIcon = NULL;
432 control->hDll = NULL;
433 control->tabnext = strdupW( MSI_RecordGetString( rec, 11) );
434 control->type = strdupW( MSI_RecordGetString( rec, 3 ) );
435 control->progress_current = 0;
436 control->progress_max = 100;
437 control->progress_backwards = FALSE;
439 x = MSI_RecordGetInteger( rec, 4 );
440 y = MSI_RecordGetInteger( rec, 5 );
441 width = MSI_RecordGetInteger( rec, 6 );
442 height = MSI_RecordGetInteger( rec, 7 );
444 x = msi_dialog_scale_unit( dialog, x );
445 y = msi_dialog_scale_unit( dialog, y );
446 width = msi_dialog_scale_unit( dialog, width );
447 height = msi_dialog_scale_unit( dialog, height );
449 if( text )
451 deformat_string( dialog->package, text, &title_font );
452 font = msi_dialog_get_style( title_font, &title );
455 control->hwnd = CreateWindowExW( exstyle, szCls, title, style,
456 x, y, width, height, parent, NULL, NULL, NULL );
458 TRACE("Dialog %s control %s hwnd %p\n",
459 debugstr_w(dialog->name), debugstr_w(text), control->hwnd );
461 msi_dialog_set_font( dialog, control->hwnd,
462 font ? font : dialog->default_font );
464 msi_free( title_font );
465 msi_free( font );
467 return control;
470 static LPWSTR msi_dialog_get_uitext( msi_dialog *dialog, LPCWSTR key )
472 MSIRECORD *rec;
473 LPWSTR text;
475 static const WCHAR query[] = {
476 's','e','l','e','c','t',' ','*',' ',
477 'f','r','o','m',' ','`','U','I','T','e','x','t','`',' ',
478 'w','h','e','r','e',' ','`','K','e','y','`',' ','=',' ','\'','%','s','\'',0
481 rec = MSI_QueryGetRecord( dialog->package->db, query, key );
482 if (!rec) return NULL;
483 text = strdupW( MSI_RecordGetString( rec, 2 ) );
484 msiobj_release( &rec->hdr );
485 return text;
488 static MSIRECORD *msi_get_binary_record( MSIDATABASE *db, LPCWSTR name )
490 static const WCHAR query[] = {
491 's','e','l','e','c','t',' ','*',' ',
492 'f','r','o','m',' ','B','i','n','a','r','y',' ',
493 'w','h','e','r','e',' ',
494 '`','N','a','m','e','`',' ','=',' ','\'','%','s','\'',0
497 return MSI_QueryGetRecord( db, query, name );
500 static LPWSTR msi_create_tmp_path(void)
502 WCHAR tmp[MAX_PATH];
503 LPWSTR path = NULL;
504 DWORD len, r;
506 r = GetTempPathW( MAX_PATH, tmp );
507 if( !r )
508 return path;
509 len = lstrlenW( tmp ) + 20;
510 path = msi_alloc( len * sizeof (WCHAR) );
511 if( path )
513 r = GetTempFileNameW( tmp, szMsi, 0, path );
514 if (!r)
516 msi_free( path );
517 path = NULL;
520 return path;
523 static HANDLE msi_load_image( MSIDATABASE *db, LPCWSTR name, UINT type,
524 UINT cx, UINT cy, UINT flags )
526 MSIRECORD *rec = NULL;
527 HANDLE himage = NULL;
528 LPWSTR tmp;
529 UINT r;
531 TRACE("%p %s %u %u %08x\n", db, debugstr_w(name), cx, cy, flags);
533 tmp = msi_create_tmp_path();
534 if( !tmp )
535 return himage;
537 rec = msi_get_binary_record( db, name );
538 if( rec )
540 r = MSI_RecordStreamToFile( rec, 2, tmp );
541 if( r == ERROR_SUCCESS )
543 himage = LoadImageW( 0, tmp, type, cx, cy, flags );
545 msiobj_release( &rec->hdr );
547 DeleteFileW( tmp );
549 msi_free( tmp );
550 return himage;
553 static HICON msi_load_icon( MSIDATABASE *db, LPCWSTR text, UINT attributes )
555 DWORD cx = 0, cy = 0, flags;
557 flags = LR_LOADFROMFILE | LR_DEFAULTSIZE;
558 if( attributes & msidbControlAttributesFixedSize )
560 flags &= ~LR_DEFAULTSIZE;
561 if( attributes & msidbControlAttributesIconSize16 )
563 cx += 16;
564 cy += 16;
566 if( attributes & msidbControlAttributesIconSize32 )
568 cx += 32;
569 cy += 32;
571 /* msidbControlAttributesIconSize48 handled by above logic */
573 return msi_load_image( db, text, IMAGE_ICON, cx, cy, flags );
576 static void msi_dialog_update_controls( msi_dialog *dialog, LPCWSTR property )
578 msi_control *control;
580 LIST_FOR_EACH_ENTRY( control, &dialog->controls, msi_control, entry )
582 if ( control->property && !strcmpW( control->property, property ) && control->update )
583 control->update( dialog, control );
587 static void msi_dialog_set_property( MSIPACKAGE *package, LPCWSTR property, LPCWSTR value )
589 UINT r = msi_set_property( package->db, property, value, -1 );
590 if (r == ERROR_SUCCESS && !strcmpW( property, szSourceDir ))
591 msi_reset_folders( package, TRUE );
594 static MSIFEATURE *msi_seltree_feature_from_item( HWND hwnd, HTREEITEM hItem )
596 TVITEMW tvi;
598 /* get the feature from the item */
599 memset( &tvi, 0, sizeof tvi );
600 tvi.hItem = hItem;
601 tvi.mask = TVIF_PARAM | TVIF_HANDLE;
602 SendMessageW( hwnd, TVM_GETITEMW, 0, (LPARAM)&tvi );
603 return (MSIFEATURE *)tvi.lParam;
606 struct msi_selection_tree_info
608 msi_dialog *dialog;
609 HWND hwnd;
610 WNDPROC oldproc;
611 HTREEITEM selected;
614 static MSIFEATURE *msi_seltree_get_selected_feature( msi_control *control )
616 struct msi_selection_tree_info *info = GetPropW( control->hwnd, szButtonData );
617 return msi_seltree_feature_from_item( control->hwnd, info->selected );
620 static void dialog_handle_event( msi_dialog *dialog, const WCHAR *control,
621 const WCHAR *attribute, MSIRECORD *rec )
623 msi_control* ctrl;
625 ctrl = msi_dialog_find_control( dialog, control );
626 if (!ctrl)
627 return;
628 if( !strcmpW( attribute, szText ) )
630 const WCHAR *font_text, *text = NULL;
631 WCHAR *font, *text_fmt = NULL;
633 font_text = MSI_RecordGetString( rec , 1 );
634 font = msi_dialog_get_style( font_text, &text );
635 deformat_string( dialog->package, text, &text_fmt );
636 if (text_fmt) text = text_fmt;
637 else text = szEmpty;
639 SetWindowTextW( ctrl->hwnd, text );
641 msi_free( font );
642 msi_free( text_fmt );
643 msi_dialog_check_messages( NULL );
645 else if( !strcmpW( attribute, szProgress ) )
647 DWORD func, val1, val2, units;
649 func = MSI_RecordGetInteger( rec, 1 );
650 val1 = MSI_RecordGetInteger( rec, 2 );
651 val2 = MSI_RecordGetInteger( rec, 3 );
653 TRACE("progress: func %u val1 %u val2 %u\n", func, val1, val2);
655 units = val1 / 512;
656 switch (func)
658 case 0: /* init */
659 SendMessageW( ctrl->hwnd, PBM_SETRANGE, 0, MAKELPARAM(0,100) );
660 if (val2)
662 ctrl->progress_max = units ? units : 100;
663 ctrl->progress_current = units;
664 ctrl->progress_backwards = TRUE;
665 SendMessageW( ctrl->hwnd, PBM_SETPOS, 100, 0 );
667 else
669 ctrl->progress_max = units ? units : 100;
670 ctrl->progress_current = 0;
671 ctrl->progress_backwards = FALSE;
672 SendMessageW( ctrl->hwnd, PBM_SETPOS, 0, 0 );
674 break;
675 case 1: /* action data increment */
676 if (val2) dialog->package->action_progress_increment = val1;
677 else dialog->package->action_progress_increment = 0;
678 break;
679 case 2: /* move */
680 if (ctrl->progress_backwards)
682 if (units >= ctrl->progress_current) ctrl->progress_current -= units;
683 else ctrl->progress_current = 0;
685 else
687 if (ctrl->progress_current + units < ctrl->progress_max) ctrl->progress_current += units;
688 else ctrl->progress_current = ctrl->progress_max;
690 SendMessageW( ctrl->hwnd, PBM_SETPOS, MulDiv(100, ctrl->progress_current, ctrl->progress_max), 0 );
691 break;
692 case 3: /* add */
693 ctrl->progress_max += units;
694 break;
695 default:
696 FIXME("Unknown progress message %u\n", func);
697 break;
700 else if ( !strcmpW( attribute, szProperty ) )
702 MSIFEATURE *feature = msi_seltree_get_selected_feature( ctrl );
703 if (feature) msi_dialog_set_property( dialog->package, ctrl->property, feature->Directory );
705 else if ( !strcmpW( attribute, szSelectionPath ) )
707 BOOL indirect = ctrl->attributes & msidbControlAttributesIndirect;
708 LPWSTR path = msi_dialog_dup_property( dialog, ctrl->property, indirect );
709 if (!path) return;
710 SetWindowTextW( ctrl->hwnd, path );
711 msi_free(path);
713 else
715 FIXME("Attribute %s not being set\n", debugstr_w(attribute));
716 return;
720 static void event_subscribe( msi_dialog *dialog, const WCHAR *event, const WCHAR *control, const WCHAR *attribute )
722 struct subscriber *sub;
724 TRACE("event %s control %s attribute %s\n", debugstr_w(event), debugstr_w(control), debugstr_w(attribute));
726 LIST_FOR_EACH_ENTRY( sub, &dialog->package->subscriptions, struct subscriber, entry )
728 if (!strcmpiW( sub->event, event ) &&
729 !strcmpiW( sub->control, control ) &&
730 !strcmpiW( sub->attribute, attribute ))
732 TRACE("already subscribed\n");
733 return;
736 if (!(sub = msi_alloc( sizeof(*sub) ))) return;
737 sub->dialog = dialog;
738 sub->event = strdupW( event );
739 sub->control = strdupW( control );
740 sub->attribute = strdupW( attribute );
741 list_add_tail( &dialog->package->subscriptions, &sub->entry );
744 struct dialog_control
746 msi_dialog *dialog;
747 const WCHAR *control;
750 static UINT map_event( MSIRECORD *row, void *param )
752 struct dialog_control *dc = param;
753 const WCHAR *event = MSI_RecordGetString( row, 3 );
754 const WCHAR *attribute = MSI_RecordGetString( row, 4 );
756 event_subscribe( dc->dialog, event, dc->control, attribute );
757 return ERROR_SUCCESS;
760 static void dialog_map_events( msi_dialog *dialog, const WCHAR *control )
762 static const WCHAR queryW[] =
763 {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
764 '`','E','v','e','n','t','M','a','p','p','i','n','g','`',' ',
765 'W','H','E','R','E',' ','`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',' ',
766 'A','N','D',' ','`','C','o','n','t','r','o','l','_','`',' ','=',' ','\'','%','s','\'',0};
767 MSIQUERY *view;
768 struct dialog_control dialog_control =
770 dialog,
771 control
774 if (!MSI_OpenQuery( dialog->package->db, &view, queryW, dialog->name, control ))
776 MSI_IterateRecords( view, NULL, map_event, &dialog_control );
777 msiobj_release( &view->hdr );
781 /* everything except radio buttons */
782 static msi_control *msi_dialog_add_control( msi_dialog *dialog,
783 MSIRECORD *rec, LPCWSTR szCls, DWORD style )
785 DWORD attributes;
786 LPCWSTR text, name;
787 DWORD exstyle = 0;
789 name = MSI_RecordGetString( rec, 2 );
790 attributes = MSI_RecordGetInteger( rec, 8 );
791 text = MSI_RecordGetString( rec, 10 );
793 TRACE("%s, %s, %08x, %s, %08x\n", debugstr_w(szCls), debugstr_w(name),
794 attributes, debugstr_w(text), style);
796 if( attributes & msidbControlAttributesVisible )
797 style |= WS_VISIBLE;
798 if( ~attributes & msidbControlAttributesEnabled )
799 style |= WS_DISABLED;
800 if( attributes & msidbControlAttributesSunken )
801 exstyle |= WS_EX_CLIENTEDGE;
803 dialog_map_events( dialog, name );
805 return dialog_create_window( dialog, rec, exstyle, szCls, name, text, style, dialog->hwnd );
808 struct msi_text_info
810 msi_font *font;
811 WNDPROC oldproc;
812 DWORD attributes;
816 * we don't erase our own background,
817 * so we have to make sure that the parent window redraws first
819 static void msi_text_on_settext( HWND hWnd )
821 HWND hParent;
822 RECT rc;
824 hParent = GetParent( hWnd );
825 GetWindowRect( hWnd, &rc );
826 MapWindowPoints( NULL, hParent, (LPPOINT) &rc, 2 );
827 InvalidateRect( hParent, &rc, TRUE );
830 static LRESULT WINAPI
831 MSIText_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
833 struct msi_text_info *info;
834 LRESULT r = 0;
836 TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam);
838 info = GetPropW(hWnd, szButtonData);
840 if( msg == WM_CTLCOLORSTATIC &&
841 ( info->attributes & msidbControlAttributesTransparent ) )
843 SetBkMode( (HDC)wParam, TRANSPARENT );
844 return (LRESULT) GetStockObject(NULL_BRUSH);
847 r = CallWindowProcW(info->oldproc, hWnd, msg, wParam, lParam);
848 if ( info->font )
849 SetTextColor( (HDC)wParam, info->font->color );
851 switch( msg )
853 case WM_SETTEXT:
854 msi_text_on_settext( hWnd );
855 break;
856 case WM_NCDESTROY:
857 msi_free( info );
858 RemovePropW( hWnd, szButtonData );
859 break;
862 return r;
865 static UINT msi_dialog_text_control( msi_dialog *dialog, MSIRECORD *rec )
867 msi_control *control;
868 struct msi_text_info *info;
869 LPCWSTR text, ptr, prop, control_name;
870 LPWSTR font_name;
872 TRACE("%p %p\n", dialog, rec);
874 control = msi_dialog_add_control( dialog, rec, szStatic, SS_LEFT | WS_GROUP );
875 if( !control )
876 return ERROR_FUNCTION_FAILED;
878 info = msi_alloc( sizeof *info );
879 if( !info )
880 return ERROR_SUCCESS;
882 control_name = MSI_RecordGetString( rec, 2 );
883 control->attributes = MSI_RecordGetInteger( rec, 8 );
884 prop = MSI_RecordGetString( rec, 9 );
885 control->property = msi_dialog_dup_property( dialog, prop, FALSE );
887 text = MSI_RecordGetString( rec, 10 );
888 font_name = msi_dialog_get_style( text, &ptr );
889 info->font = ( font_name ) ? msi_dialog_find_font( dialog, font_name ) : NULL;
890 msi_free( font_name );
892 info->attributes = MSI_RecordGetInteger( rec, 8 );
893 if( info->attributes & msidbControlAttributesTransparent )
894 SetWindowLongPtrW( control->hwnd, GWL_EXSTYLE, WS_EX_TRANSPARENT );
896 info->oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
897 (LONG_PTR)MSIText_WndProc );
898 SetPropW( control->hwnd, szButtonData, info );
900 event_subscribe( dialog, szSelectionPath, control_name, szSelectionPath );
901 return ERROR_SUCCESS;
904 /* strip any leading text style label from text field */
905 static WCHAR *msi_get_binary_name( MSIPACKAGE *package, MSIRECORD *rec )
907 WCHAR *p, *text;
909 text = msi_get_deformatted_field( package, rec, 10 );
910 if (!text)
911 return NULL;
913 p = text;
914 while (*p && *p != '{') p++;
915 if (!*p++) return text;
917 while (*p && *p != '}') p++;
918 if (!*p++) return text;
920 p = strdupW( p );
921 msi_free( text );
922 return p;
925 static UINT msi_dialog_set_property_event( msi_dialog *dialog, LPCWSTR event, LPCWSTR arg )
927 static const WCHAR szNullArg[] = {'{','}',0};
928 LPWSTR p, prop, arg_fmt = NULL;
929 UINT len;
931 len = strlenW( event );
932 prop = msi_alloc( len * sizeof(WCHAR) );
933 strcpyW( prop, &event[1] );
934 p = strchrW( prop, ']' );
935 if (p && (p[1] == 0 || p[1] == ' '))
937 *p = 0;
938 if (strcmpW( szNullArg, arg ))
939 deformat_string( dialog->package, arg, &arg_fmt );
940 msi_dialog_set_property( dialog->package, prop, arg_fmt );
941 msi_dialog_update_controls( dialog, prop );
942 msi_free( arg_fmt );
944 else ERR("Badly formatted property string - what happens?\n");
945 msi_free( prop );
946 return ERROR_SUCCESS;
949 static UINT msi_dialog_send_event( msi_dialog *dialog, LPCWSTR event, LPCWSTR arg )
951 LPWSTR event_fmt = NULL, arg_fmt = NULL;
953 TRACE("Sending control event %s %s\n", debugstr_w(event), debugstr_w(arg));
955 deformat_string( dialog->package, event, &event_fmt );
956 deformat_string( dialog->package, arg, &arg_fmt );
958 dialog->event_handler( dialog, event_fmt, arg_fmt );
960 msi_free( event_fmt );
961 msi_free( arg_fmt );
963 return ERROR_SUCCESS;
966 static UINT msi_dialog_control_event( MSIRECORD *rec, LPVOID param )
968 msi_dialog *dialog = param;
969 LPCWSTR condition, event, arg;
970 UINT r;
972 condition = MSI_RecordGetString( rec, 5 );
973 r = MSI_EvaluateConditionW( dialog->package, condition );
974 if (r == MSICONDITION_TRUE || r == MSICONDITION_NONE)
976 event = MSI_RecordGetString( rec, 3 );
977 arg = MSI_RecordGetString( rec, 4 );
978 if (event[0] == '[')
979 msi_dialog_set_property_event( dialog, event, arg );
980 else
981 msi_dialog_send_event( dialog, event, arg );
983 return ERROR_SUCCESS;
986 static UINT msi_dialog_button_handler( msi_dialog *dialog, msi_control *control, WPARAM param )
988 static const WCHAR query[] = {
989 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
990 'C','o','n','t','r','o','l','E','v','e','n','t',' ','W','H','E','R','E',' ',
991 '`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',' ','A','N','D',' ',
992 '`','C','o','n','t','r','o','l','_','`',' ','=',' ','\'','%','s','\'',' ',
993 'O','R','D','E','R',' ','B','Y',' ','`','O','r','d','e','r','i','n','g','`',0};
994 MSIQUERY *view;
995 UINT r;
997 if (HIWORD(param) != BN_CLICKED)
998 return ERROR_SUCCESS;
1000 r = MSI_OpenQuery( dialog->package->db, &view, query, dialog->name, control->name );
1001 if (r != ERROR_SUCCESS)
1003 ERR("query failed\n");
1004 return ERROR_SUCCESS;
1006 r = MSI_IterateRecords( view, 0, msi_dialog_control_event, dialog );
1007 msiobj_release( &view->hdr );
1008 return r;
1011 static UINT msi_dialog_button_control( msi_dialog *dialog, MSIRECORD *rec )
1013 msi_control *control;
1014 UINT attributes, style;
1016 TRACE("%p %p\n", dialog, rec);
1018 style = WS_TABSTOP;
1019 attributes = MSI_RecordGetInteger( rec, 8 );
1020 if( attributes & msidbControlAttributesIcon )
1021 style |= BS_ICON;
1023 control = msi_dialog_add_control( dialog, rec, szButton, style );
1024 if( !control )
1025 return ERROR_FUNCTION_FAILED;
1027 control->handler = msi_dialog_button_handler;
1029 if (attributes & msidbControlAttributesIcon)
1031 /* set the icon */
1032 LPWSTR name = msi_get_binary_name( dialog->package, rec );
1033 control->hIcon = msi_load_icon( dialog->package->db, name, attributes );
1034 if (control->hIcon)
1036 SendMessageW( control->hwnd, BM_SETIMAGE, IMAGE_ICON, (LPARAM) control->hIcon );
1038 else
1039 ERR("Failed to load icon %s\n", debugstr_w(name));
1040 msi_free( name );
1043 return ERROR_SUCCESS;
1046 static LPWSTR msi_get_checkbox_value( msi_dialog *dialog, LPCWSTR prop )
1048 static const WCHAR query[] = {
1049 'S','E','L','E','C','T',' ','*',' ',
1050 'F','R','O','M',' ','`','C','h','e','c','k','B','o','x','`',' ',
1051 'W','H','E','R','E',' ',
1052 '`','P','r','o','p','e','r','t','y','`',' ','=',' ',
1053 '\'','%','s','\'',0
1055 MSIRECORD *rec = NULL;
1056 LPWSTR ret = NULL;
1058 /* find if there is a value associated with the checkbox */
1059 rec = MSI_QueryGetRecord( dialog->package->db, query, prop );
1060 if (!rec)
1061 return ret;
1063 ret = msi_get_deformatted_field( dialog->package, rec, 2 );
1064 if( ret && !ret[0] )
1066 msi_free( ret );
1067 ret = NULL;
1069 msiobj_release( &rec->hdr );
1070 if (ret)
1071 return ret;
1073 ret = msi_dup_property( dialog->package->db, prop );
1074 if( ret && !ret[0] )
1076 msi_free( ret );
1077 ret = NULL;
1080 return ret;
1083 static UINT msi_dialog_get_checkbox_state( msi_dialog *dialog, msi_control *control )
1085 WCHAR state[2] = {0};
1086 DWORD sz = 2;
1088 msi_get_property( dialog->package->db, control->property, state, &sz );
1089 return state[0] ? 1 : 0;
1092 static void msi_dialog_set_checkbox_state( msi_dialog *dialog, msi_control *control, UINT state )
1094 static const WCHAR szState[] = {'1',0};
1095 LPCWSTR val;
1097 /* if uncheck then the property is set to NULL */
1098 if (!state)
1100 msi_dialog_set_property( dialog->package, control->property, NULL );
1101 return;
1104 /* check for a custom state */
1105 if (control->value && control->value[0])
1106 val = control->value;
1107 else
1108 val = szState;
1110 msi_dialog_set_property( dialog->package, control->property, val );
1113 static void msi_dialog_checkbox_sync_state( msi_dialog *dialog, msi_control *control )
1115 UINT state = msi_dialog_get_checkbox_state( dialog, control );
1116 SendMessageW( control->hwnd, BM_SETCHECK, state ? BST_CHECKED : BST_UNCHECKED, 0 );
1119 static UINT msi_dialog_checkbox_handler( msi_dialog *dialog, msi_control *control, WPARAM param )
1121 UINT state;
1123 if (HIWORD(param) != BN_CLICKED)
1124 return ERROR_SUCCESS;
1126 TRACE("clicked checkbox %s, set %s\n", debugstr_w(control->name), debugstr_w(control->property));
1128 state = msi_dialog_get_checkbox_state( dialog, control );
1129 state = state ? 0 : 1;
1130 msi_dialog_set_checkbox_state( dialog, control, state );
1131 msi_dialog_checkbox_sync_state( dialog, control );
1133 return msi_dialog_button_handler( dialog, control, param );
1136 static UINT msi_dialog_checkbox_control( msi_dialog *dialog, MSIRECORD *rec )
1138 msi_control *control;
1139 LPCWSTR prop;
1141 TRACE("%p %p\n", dialog, rec);
1143 control = msi_dialog_add_control( dialog, rec, szButton, BS_CHECKBOX | BS_MULTILINE | WS_TABSTOP );
1144 control->handler = msi_dialog_checkbox_handler;
1145 control->update = msi_dialog_checkbox_sync_state;
1146 prop = MSI_RecordGetString( rec, 9 );
1147 if (prop)
1149 control->property = strdupW( prop );
1150 control->value = msi_get_checkbox_value( dialog, prop );
1151 TRACE("control %s value %s\n", debugstr_w(control->property), debugstr_w(control->value));
1153 msi_dialog_checkbox_sync_state( dialog, control );
1154 return ERROR_SUCCESS;
1157 static UINT msi_dialog_line_control( msi_dialog *dialog, MSIRECORD *rec )
1159 DWORD attributes;
1160 LPCWSTR name;
1161 DWORD style, exstyle = 0;
1162 DWORD x, y, width, height;
1163 msi_control *control;
1165 TRACE("%p %p\n", dialog, rec);
1167 style = WS_CHILD | SS_ETCHEDHORZ | SS_SUNKEN;
1169 name = MSI_RecordGetString( rec, 2 );
1170 attributes = MSI_RecordGetInteger( rec, 8 );
1172 if( attributes & msidbControlAttributesVisible )
1173 style |= WS_VISIBLE;
1174 if( ~attributes & msidbControlAttributesEnabled )
1175 style |= WS_DISABLED;
1176 if( attributes & msidbControlAttributesSunken )
1177 exstyle |= WS_EX_CLIENTEDGE;
1179 dialog_map_events( dialog, name );
1181 control = msi_alloc( FIELD_OFFSET(msi_control, name[strlenW( name ) + 1] ));
1182 if (!control)
1183 return ERROR_OUTOFMEMORY;
1185 strcpyW( control->name, name );
1186 list_add_head( &dialog->controls, &control->entry );
1187 control->handler = NULL;
1188 control->property = NULL;
1189 control->value = NULL;
1190 control->hBitmap = NULL;
1191 control->hIcon = NULL;
1192 control->hDll = NULL;
1193 control->tabnext = strdupW( MSI_RecordGetString( rec, 11) );
1194 control->type = strdupW( MSI_RecordGetString( rec, 3 ) );
1195 control->progress_current = 0;
1196 control->progress_max = 100;
1197 control->progress_backwards = FALSE;
1199 x = MSI_RecordGetInteger( rec, 4 );
1200 y = MSI_RecordGetInteger( rec, 5 );
1201 width = MSI_RecordGetInteger( rec, 6 );
1203 x = msi_dialog_scale_unit( dialog, x );
1204 y = msi_dialog_scale_unit( dialog, y );
1205 width = msi_dialog_scale_unit( dialog, width );
1206 height = 2; /* line is exactly 2 units in height */
1208 control->hwnd = CreateWindowExW( exstyle, szStatic, NULL, style,
1209 x, y, width, height, dialog->hwnd, NULL, NULL, NULL );
1211 TRACE("Dialog %s control %s hwnd %p\n",
1212 debugstr_w(dialog->name), debugstr_w(name), control->hwnd );
1214 return ERROR_SUCCESS;
1217 /******************** Scroll Text ********************************************/
1219 struct msi_scrolltext_info
1221 msi_dialog *dialog;
1222 msi_control *control;
1223 WNDPROC oldproc;
1226 static LRESULT WINAPI
1227 MSIScrollText_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
1229 struct msi_scrolltext_info *info;
1230 HRESULT r;
1232 TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam);
1234 info = GetPropW( hWnd, szButtonData );
1236 r = CallWindowProcW( info->oldproc, hWnd, msg, wParam, lParam );
1238 switch( msg )
1240 case WM_GETDLGCODE:
1241 return DLGC_WANTARROWS;
1242 case WM_NCDESTROY:
1243 msi_free( info );
1244 RemovePropW( hWnd, szButtonData );
1245 break;
1246 case WM_PAINT:
1247 /* native MSI sets a wait cursor here */
1248 msi_dialog_button_handler( info->dialog, info->control, BN_CLICKED );
1249 break;
1251 return r;
1254 struct msi_streamin_info
1256 LPSTR string;
1257 DWORD offset;
1258 DWORD length;
1261 static DWORD CALLBACK
1262 msi_richedit_stream_in( DWORD_PTR arg, LPBYTE buffer, LONG count, LONG *pcb )
1264 struct msi_streamin_info *info = (struct msi_streamin_info*) arg;
1266 if( (count + info->offset) > info->length )
1267 count = info->length - info->offset;
1268 memcpy( buffer, &info->string[ info->offset ], count );
1269 *pcb = count;
1270 info->offset += count;
1272 TRACE("%d/%d\n", info->offset, info->length);
1274 return 0;
1277 static void msi_scrolltext_add_text( msi_control *control, LPCWSTR text )
1279 struct msi_streamin_info info;
1280 EDITSTREAM es;
1282 info.string = strdupWtoA( text );
1283 info.offset = 0;
1284 info.length = lstrlenA( info.string ) + 1;
1286 es.dwCookie = (DWORD_PTR) &info;
1287 es.dwError = 0;
1288 es.pfnCallback = msi_richedit_stream_in;
1290 SendMessageW( control->hwnd, EM_STREAMIN, SF_RTF, (LPARAM) &es );
1292 msi_free( info.string );
1295 static UINT msi_dialog_scrolltext_control( msi_dialog *dialog, MSIRECORD *rec )
1297 static const WCHAR szRichEdit20W[] = {'R','i','c','h','E','d','i','t','2','0','W',0};
1298 struct msi_scrolltext_info *info;
1299 msi_control *control;
1300 HMODULE hRichedit;
1301 LPCWSTR text;
1302 DWORD style;
1304 info = msi_alloc( sizeof *info );
1305 if (!info)
1306 return ERROR_FUNCTION_FAILED;
1308 hRichedit = LoadLibraryA("riched20");
1310 style = WS_BORDER | ES_MULTILINE | WS_VSCROLL |
1311 ES_READONLY | ES_AUTOVSCROLL | WS_TABSTOP;
1312 control = msi_dialog_add_control( dialog, rec, szRichEdit20W, style );
1313 if (!control)
1315 FreeLibrary( hRichedit );
1316 msi_free( info );
1317 return ERROR_FUNCTION_FAILED;
1320 control->hDll = hRichedit;
1322 info->dialog = dialog;
1323 info->control = control;
1325 /* subclass the static control */
1326 info->oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
1327 (LONG_PTR)MSIScrollText_WndProc );
1328 SetPropW( control->hwnd, szButtonData, info );
1330 /* add the text into the richedit */
1331 text = MSI_RecordGetString( rec, 10 );
1332 if (text)
1333 msi_scrolltext_add_text( control, text );
1335 return ERROR_SUCCESS;
1338 static HBITMAP msi_load_picture( MSIDATABASE *db, LPCWSTR name,
1339 INT cx, INT cy, DWORD flags )
1341 HBITMAP hOleBitmap = 0, hBitmap = 0, hOldSrcBitmap, hOldDestBitmap;
1342 MSIRECORD *rec = NULL;
1343 IStream *stm = NULL;
1344 IPicture *pic = NULL;
1345 HDC srcdc, destdc;
1346 BITMAP bm;
1347 UINT r;
1349 rec = msi_get_binary_record( db, name );
1350 if( !rec )
1351 goto end;
1353 r = MSI_RecordGetIStream( rec, 2, &stm );
1354 msiobj_release( &rec->hdr );
1355 if( r != ERROR_SUCCESS )
1356 goto end;
1358 r = OleLoadPicture( stm, 0, TRUE, &IID_IPicture, (LPVOID*) &pic );
1359 IStream_Release( stm );
1360 if( FAILED( r ) )
1362 ERR("failed to load picture\n");
1363 goto end;
1366 r = IPicture_get_Handle( pic, (OLE_HANDLE*) &hOleBitmap );
1367 if( FAILED( r ) )
1369 ERR("failed to get bitmap handle\n");
1370 goto end;
1373 /* make the bitmap the desired size */
1374 r = GetObjectW( hOleBitmap, sizeof bm, &bm );
1375 if (r != sizeof bm )
1377 ERR("failed to get bitmap size\n");
1378 goto end;
1381 if (flags & LR_DEFAULTSIZE)
1383 cx = bm.bmWidth;
1384 cy = bm.bmHeight;
1387 srcdc = CreateCompatibleDC( NULL );
1388 hOldSrcBitmap = SelectObject( srcdc, hOleBitmap );
1389 destdc = CreateCompatibleDC( NULL );
1390 hBitmap = CreateCompatibleBitmap( srcdc, cx, cy );
1391 hOldDestBitmap = SelectObject( destdc, hBitmap );
1392 StretchBlt( destdc, 0, 0, cx, cy,
1393 srcdc, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
1394 SelectObject( srcdc, hOldSrcBitmap );
1395 SelectObject( destdc, hOldDestBitmap );
1396 DeleteDC( srcdc );
1397 DeleteDC( destdc );
1399 end:
1400 if ( pic )
1401 IPicture_Release( pic );
1402 return hBitmap;
1405 static UINT msi_dialog_bitmap_control( msi_dialog *dialog, MSIRECORD *rec )
1407 UINT cx, cy, flags, style, attributes;
1408 msi_control *control;
1409 LPWSTR name;
1411 flags = LR_LOADFROMFILE;
1412 style = SS_BITMAP | SS_LEFT | WS_GROUP;
1414 attributes = MSI_RecordGetInteger( rec, 8 );
1415 if( attributes & msidbControlAttributesFixedSize )
1417 flags |= LR_DEFAULTSIZE;
1418 style |= SS_CENTERIMAGE;
1421 control = msi_dialog_add_control( dialog, rec, szStatic, style );
1422 cx = MSI_RecordGetInteger( rec, 6 );
1423 cy = MSI_RecordGetInteger( rec, 7 );
1424 cx = msi_dialog_scale_unit( dialog, cx );
1425 cy = msi_dialog_scale_unit( dialog, cy );
1427 name = msi_get_binary_name( dialog->package, rec );
1428 control->hBitmap = msi_load_picture( dialog->package->db, name, cx, cy, flags );
1429 if( control->hBitmap )
1430 SendMessageW( control->hwnd, STM_SETIMAGE,
1431 IMAGE_BITMAP, (LPARAM) control->hBitmap );
1432 else
1433 ERR("Failed to load bitmap %s\n", debugstr_w(name));
1435 msi_free( name );
1437 return ERROR_SUCCESS;
1440 static UINT msi_dialog_icon_control( msi_dialog *dialog, MSIRECORD *rec )
1442 msi_control *control;
1443 DWORD attributes;
1444 LPWSTR name;
1446 TRACE("\n");
1448 control = msi_dialog_add_control( dialog, rec, szStatic,
1449 SS_ICON | SS_CENTERIMAGE | WS_GROUP );
1451 attributes = MSI_RecordGetInteger( rec, 8 );
1452 name = msi_get_binary_name( dialog->package, rec );
1453 control->hIcon = msi_load_icon( dialog->package->db, name, attributes );
1454 if( control->hIcon )
1455 SendMessageW( control->hwnd, STM_SETICON, (WPARAM) control->hIcon, 0 );
1456 else
1457 ERR("Failed to load bitmap %s\n", debugstr_w(name));
1458 msi_free( name );
1459 return ERROR_SUCCESS;
1462 /******************** Combo Box ***************************************/
1464 struct msi_combobox_info
1466 msi_dialog *dialog;
1467 HWND hwnd;
1468 WNDPROC oldproc;
1469 DWORD num_items;
1470 DWORD addpos_items;
1471 LPWSTR *items;
1474 static LRESULT WINAPI MSIComboBox_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
1476 struct msi_combobox_info *info;
1477 LRESULT r;
1478 DWORD j;
1480 TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam);
1482 info = GetPropW( hWnd, szButtonData );
1483 if (!info)
1484 return 0;
1486 r = CallWindowProcW( info->oldproc, hWnd, msg, wParam, lParam );
1488 switch (msg)
1490 case WM_NCDESTROY:
1491 for (j = 0; j < info->num_items; j++)
1492 msi_free( info->items[j] );
1493 msi_free( info->items );
1494 msi_free( info );
1495 RemovePropW( hWnd, szButtonData );
1496 break;
1499 return r;
1502 static UINT msi_combobox_add_item( MSIRECORD *rec, LPVOID param )
1504 struct msi_combobox_info *info = param;
1505 LPCWSTR value, text;
1506 int pos;
1508 value = MSI_RecordGetString( rec, 3 );
1509 text = MSI_RecordGetString( rec, 4 );
1511 info->items[info->addpos_items] = strdupW( value );
1513 pos = SendMessageW( info->hwnd, CB_ADDSTRING, 0, (LPARAM)text );
1514 SendMessageW( info->hwnd, CB_SETITEMDATA, pos, (LPARAM)info->items[info->addpos_items] );
1515 info->addpos_items++;
1517 return ERROR_SUCCESS;
1520 static UINT msi_combobox_add_items( struct msi_combobox_info *info, LPCWSTR property )
1522 static const WCHAR query[] = {
1523 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
1524 '`','C','o','m','b','o','B','o','x','`',' ','W','H','E','R','E',' ',
1525 '`','P','r','o','p','e','r','t','y','`',' ','=',' ','\'','%','s','\'',' ',
1526 'O','R','D','E','R',' ','B','Y',' ','`','O','r','d','e','r','`',0};
1527 MSIQUERY *view;
1528 DWORD count;
1529 UINT r;
1531 r = MSI_OpenQuery( info->dialog->package->db, &view, query, property );
1532 if (r != ERROR_SUCCESS)
1533 return r;
1535 /* just get the number of records */
1536 count = 0;
1537 r = MSI_IterateRecords( view, &count, NULL, NULL );
1538 if (r != ERROR_SUCCESS)
1540 msiobj_release( &view->hdr );
1541 return r;
1543 info->num_items = count;
1544 info->items = msi_alloc( sizeof(*info->items) * count );
1546 r = MSI_IterateRecords( view, NULL, msi_combobox_add_item, info );
1547 msiobj_release( &view->hdr );
1548 return r;
1551 static UINT msi_dialog_set_control_condition( MSIRECORD *rec, LPVOID param )
1553 static const WCHAR szHide[] = {'H','i','d','e',0};
1554 static const WCHAR szShow[] = {'S','h','o','w',0};
1555 static const WCHAR szDisable[] = {'D','i','s','a','b','l','e',0};
1556 static const WCHAR szEnable[] = {'E','n','a','b','l','e',0};
1557 static const WCHAR szDefault[] = {'D','e','f','a','u','l','t',0};
1558 msi_dialog *dialog = param;
1559 msi_control *control;
1560 LPCWSTR name, action, condition;
1561 UINT r;
1563 name = MSI_RecordGetString( rec, 2 );
1564 action = MSI_RecordGetString( rec, 3 );
1565 condition = MSI_RecordGetString( rec, 4 );
1566 r = MSI_EvaluateConditionW( dialog->package, condition );
1567 control = msi_dialog_find_control( dialog, name );
1568 if (r == MSICONDITION_TRUE && control)
1570 TRACE("%s control %s\n", debugstr_w(action), debugstr_w(name));
1572 /* FIXME: case sensitive? */
1573 if (!strcmpW( action, szHide ))
1574 ShowWindow(control->hwnd, SW_HIDE);
1575 else if (!strcmpW( action, szShow ))
1576 ShowWindow(control->hwnd, SW_SHOW);
1577 else if (!strcmpW( action, szDisable ))
1578 EnableWindow(control->hwnd, FALSE);
1579 else if (!strcmpW( action, szEnable ))
1580 EnableWindow(control->hwnd, TRUE);
1581 else if (!strcmpW( action, szDefault ))
1582 SetFocus(control->hwnd);
1583 else
1584 FIXME("Unhandled action %s\n", debugstr_w(action));
1586 return ERROR_SUCCESS;
1589 static UINT msi_dialog_evaluate_control_conditions( msi_dialog *dialog )
1591 static const WCHAR query[] = {
1592 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
1593 'C','o','n','t','r','o','l','C','o','n','d','i','t','i','o','n',' ',
1594 'W','H','E','R','E',' ','`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',0};
1595 UINT r;
1596 MSIQUERY *view;
1597 MSIPACKAGE *package = dialog->package;
1599 TRACE("%p %s\n", dialog, debugstr_w(dialog->name));
1601 /* query the Control table for all the elements of the control */
1602 r = MSI_OpenQuery( package->db, &view, query, dialog->name );
1603 if (r != ERROR_SUCCESS)
1604 return ERROR_SUCCESS;
1606 r = MSI_IterateRecords( view, 0, msi_dialog_set_control_condition, dialog );
1607 msiobj_release( &view->hdr );
1608 return r;
1611 static UINT msi_dialog_combobox_handler( msi_dialog *dialog, msi_control *control, WPARAM param )
1613 struct msi_combobox_info *info;
1614 int index;
1615 LPWSTR value;
1617 if (HIWORD(param) != CBN_SELCHANGE && HIWORD(param) != CBN_EDITCHANGE)
1618 return ERROR_SUCCESS;
1620 info = GetPropW( control->hwnd, szButtonData );
1621 index = SendMessageW( control->hwnd, CB_GETCURSEL, 0, 0 );
1622 if (index == CB_ERR)
1623 value = msi_get_window_text( control->hwnd );
1624 else
1625 value = (LPWSTR) SendMessageW( control->hwnd, CB_GETITEMDATA, index, 0 );
1627 msi_dialog_set_property( info->dialog->package, control->property, value );
1628 msi_dialog_evaluate_control_conditions( info->dialog );
1630 if (index == CB_ERR)
1631 msi_free( value );
1633 return ERROR_SUCCESS;
1636 static void msi_dialog_combobox_update( msi_dialog *dialog, msi_control *control )
1638 struct msi_combobox_info *info;
1639 LPWSTR value, tmp;
1640 DWORD j;
1642 info = GetPropW( control->hwnd, szButtonData );
1644 value = msi_dup_property( dialog->package->db, control->property );
1645 if (!value)
1647 SendMessageW( control->hwnd, CB_SETCURSEL, -1, 0 );
1648 return;
1651 for (j = 0; j < info->num_items; j++)
1653 tmp = (LPWSTR) SendMessageW( control->hwnd, CB_GETITEMDATA, j, 0 );
1654 if (!strcmpW( value, tmp ))
1655 break;
1658 if (j < info->num_items)
1660 SendMessageW( control->hwnd, CB_SETCURSEL, j, 0 );
1662 else
1664 SendMessageW( control->hwnd, CB_SETCURSEL, -1, 0 );
1665 SetWindowTextW( control->hwnd, value );
1668 msi_free(value);
1671 static UINT msi_dialog_combo_control( msi_dialog *dialog, MSIRECORD *rec )
1673 struct msi_combobox_info *info;
1674 msi_control *control;
1675 DWORD attributes, style;
1676 LPCWSTR prop;
1678 info = msi_alloc( sizeof *info );
1679 if (!info)
1680 return ERROR_FUNCTION_FAILED;
1682 style = CBS_AUTOHSCROLL | WS_TABSTOP | WS_GROUP | WS_CHILD;
1683 attributes = MSI_RecordGetInteger( rec, 8 );
1684 if ( ~attributes & msidbControlAttributesSorted)
1685 style |= CBS_SORT;
1686 if ( attributes & msidbControlAttributesComboList)
1687 style |= CBS_DROPDOWNLIST;
1688 else
1689 style |= CBS_DROPDOWN;
1691 control = msi_dialog_add_control( dialog, rec, WC_COMBOBOXW, style );
1692 if (!control)
1694 msi_free( info );
1695 return ERROR_FUNCTION_FAILED;
1698 control->handler = msi_dialog_combobox_handler;
1699 control->update = msi_dialog_combobox_update;
1701 prop = MSI_RecordGetString( rec, 9 );
1702 control->property = msi_dialog_dup_property( dialog, prop, FALSE );
1704 /* subclass */
1705 info->dialog = dialog;
1706 info->hwnd = control->hwnd;
1707 info->items = NULL;
1708 info->addpos_items = 0;
1709 info->oldproc = (WNDPROC)SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
1710 (LONG_PTR)MSIComboBox_WndProc );
1711 SetPropW( control->hwnd, szButtonData, info );
1713 if (control->property)
1714 msi_combobox_add_items( info, control->property );
1716 msi_dialog_combobox_update( dialog, control );
1718 return ERROR_SUCCESS;
1721 static UINT msi_dialog_edit_handler( msi_dialog *dialog, msi_control *control, WPARAM param )
1723 LPWSTR buf;
1725 if (HIWORD(param) != EN_CHANGE)
1726 return ERROR_SUCCESS;
1728 TRACE("edit %s contents changed, set %s\n", debugstr_w(control->name), debugstr_w(control->property));
1730 buf = msi_get_window_text( control->hwnd );
1731 msi_dialog_set_property( dialog->package, control->property, buf );
1732 msi_free( buf );
1734 return ERROR_SUCCESS;
1737 /* length of 2^32 + 1 */
1738 #define MAX_NUM_DIGITS 11
1740 static UINT msi_dialog_edit_control( msi_dialog *dialog, MSIRECORD *rec )
1742 msi_control *control;
1743 LPCWSTR prop, text;
1744 LPWSTR val, begin, end;
1745 WCHAR num[MAX_NUM_DIGITS];
1746 DWORD limit;
1748 control = msi_dialog_add_control( dialog, rec, szEdit,
1749 WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL );
1750 control->handler = msi_dialog_edit_handler;
1752 text = MSI_RecordGetString( rec, 10 );
1753 if ( text )
1755 begin = strchrW( text, '{' );
1756 end = strchrW( text, '}' );
1758 if ( begin && end && end > begin &&
1759 begin[0] >= '0' && begin[0] <= '9' &&
1760 end - begin < MAX_NUM_DIGITS)
1762 lstrcpynW( num, begin + 1, end - begin );
1763 limit = atolW( num );
1765 SendMessageW( control->hwnd, EM_SETLIMITTEXT, limit, 0 );
1769 prop = MSI_RecordGetString( rec, 9 );
1770 if( prop )
1771 control->property = strdupW( prop );
1773 val = msi_dup_property( dialog->package->db, control->property );
1774 SetWindowTextW( control->hwnd, val );
1775 msi_free( val );
1776 return ERROR_SUCCESS;
1779 /******************** Masked Edit ********************************************/
1781 #define MASK_MAX_GROUPS 20
1783 struct msi_mask_group
1785 UINT len;
1786 UINT ofs;
1787 WCHAR type;
1788 HWND hwnd;
1791 struct msi_maskedit_info
1793 msi_dialog *dialog;
1794 WNDPROC oldproc;
1795 HWND hwnd;
1796 LPWSTR prop;
1797 UINT num_chars;
1798 UINT num_groups;
1799 struct msi_mask_group group[MASK_MAX_GROUPS];
1802 static BOOL msi_mask_editable( WCHAR type )
1804 switch (type)
1806 case '%':
1807 case '#':
1808 case '&':
1809 case '`':
1810 case '?':
1811 case '^':
1812 return TRUE;
1814 return FALSE;
1817 static void msi_mask_control_change( struct msi_maskedit_info *info )
1819 LPWSTR val;
1820 UINT i, n, r;
1822 val = msi_alloc( (info->num_chars+1)*sizeof(WCHAR) );
1823 for( i=0, n=0; i<info->num_groups; i++ )
1825 if( (info->group[i].len + n) > info->num_chars )
1827 ERR("can't fit control %d text into template\n",i);
1828 break;
1830 if (!msi_mask_editable(info->group[i].type))
1832 for(r=0; r<info->group[i].len; r++)
1833 val[n+r] = info->group[i].type;
1834 val[n+r] = 0;
1836 else
1838 r = GetWindowTextW( info->group[i].hwnd, &val[n], info->group[i].len+1 );
1839 if( r != info->group[i].len )
1840 break;
1842 n += r;
1845 TRACE("%d/%d controls were good\n", i, info->num_groups);
1847 if( i == info->num_groups )
1849 TRACE("Set property %s to %s\n", debugstr_w(info->prop), debugstr_w(val));
1850 msi_dialog_set_property( info->dialog->package, info->prop, val );
1851 msi_dialog_evaluate_control_conditions( info->dialog );
1853 msi_free( val );
1856 /* now move to the next control if necessary */
1857 static VOID msi_mask_next_control( struct msi_maskedit_info *info, HWND hWnd )
1859 HWND hWndNext;
1860 UINT len, i;
1862 for( i=0; i<info->num_groups; i++ )
1863 if( info->group[i].hwnd == hWnd )
1864 break;
1866 /* don't move from the last control */
1867 if( i >= (info->num_groups-1) )
1868 return;
1870 len = SendMessageW( hWnd, WM_GETTEXTLENGTH, 0, 0 );
1871 if( len < info->group[i].len )
1872 return;
1874 hWndNext = GetNextDlgTabItem( GetParent( hWnd ), hWnd, FALSE );
1875 SetFocus( hWndNext );
1878 static LRESULT WINAPI
1879 MSIMaskedEdit_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
1881 struct msi_maskedit_info *info;
1882 HRESULT r;
1884 TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam);
1886 info = GetPropW(hWnd, szButtonData);
1888 r = CallWindowProcW(info->oldproc, hWnd, msg, wParam, lParam);
1890 switch( msg )
1892 case WM_COMMAND:
1893 if (HIWORD(wParam) == EN_CHANGE)
1895 msi_mask_control_change( info );
1896 msi_mask_next_control( info, (HWND) lParam );
1898 break;
1899 case WM_NCDESTROY:
1900 msi_free( info->prop );
1901 msi_free( info );
1902 RemovePropW( hWnd, szButtonData );
1903 break;
1906 return r;
1909 /* fish the various bits of the property out and put them in the control */
1910 static void
1911 msi_maskedit_set_text( struct msi_maskedit_info *info, LPCWSTR text )
1913 LPCWSTR p;
1914 UINT i;
1916 p = text;
1917 for( i = 0; i < info->num_groups; i++ )
1919 if( info->group[i].len < strlenW( p ) )
1921 LPWSTR chunk = strdupW( p );
1922 chunk[ info->group[i].len ] = 0;
1923 SetWindowTextW( info->group[i].hwnd, chunk );
1924 msi_free( chunk );
1926 else
1928 SetWindowTextW( info->group[i].hwnd, p );
1929 break;
1931 p += info->group[i].len;
1935 static struct msi_maskedit_info * msi_dialog_parse_groups( LPCWSTR mask )
1937 struct msi_maskedit_info * info = NULL;
1938 int i = 0, n = 0, total = 0;
1939 LPCWSTR p;
1941 TRACE("masked control, template %s\n", debugstr_w(mask));
1943 if( !mask )
1944 return info;
1946 info = msi_alloc_zero( sizeof *info );
1947 if( !info )
1948 return info;
1950 p = strchrW(mask, '<');
1951 if( p )
1952 p++;
1953 else
1954 p = mask;
1956 for( i=0; i<MASK_MAX_GROUPS; i++ )
1958 /* stop at the end of the string */
1959 if( p[0] == 0 || p[0] == '>' )
1960 break;
1962 /* count the number of the same identifier */
1963 for( n=0; p[n] == p[0]; n++ )
1965 info->group[i].ofs = total;
1966 info->group[i].type = p[0];
1967 if( p[n] == '=' )
1969 n++;
1970 total++; /* an extra not part of the group */
1972 info->group[i].len = n;
1973 total += n;
1974 p += n;
1977 TRACE("%d characters in %d groups\n", total, i );
1978 if( i == MASK_MAX_GROUPS )
1979 ERR("too many groups in PIDTemplate %s\n", debugstr_w(mask));
1981 info->num_chars = total;
1982 info->num_groups = i;
1984 return info;
1987 static void
1988 msi_maskedit_create_children( struct msi_maskedit_info *info, LPCWSTR font )
1990 DWORD width, height, style, wx, ww;
1991 RECT rect;
1992 HWND hwnd;
1993 UINT i;
1995 style = WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL;
1997 GetClientRect( info->hwnd, &rect );
1999 width = rect.right - rect.left;
2000 height = rect.bottom - rect.top;
2002 for( i = 0; i < info->num_groups; i++ )
2004 if (!msi_mask_editable( info->group[i].type ))
2005 continue;
2006 wx = (info->group[i].ofs * width) / info->num_chars;
2007 ww = (info->group[i].len * width) / info->num_chars;
2009 hwnd = CreateWindowW( szEdit, NULL, style, wx, 0, ww, height,
2010 info->hwnd, NULL, NULL, NULL );
2011 if( !hwnd )
2013 ERR("failed to create mask edit sub window\n");
2014 break;
2017 SendMessageW( hwnd, EM_LIMITTEXT, info->group[i].len, 0 );
2019 msi_dialog_set_font( info->dialog, hwnd,
2020 font?font:info->dialog->default_font );
2021 info->group[i].hwnd = hwnd;
2026 * office 2003 uses "73931<````=````=````=````=`````>@@@@@"
2027 * delphi 7 uses "<????-??????-??????-????>" and "<???-???>"
2028 * filemaker pro 7 uses "<^^^^=^^^^=^^^^=^^^^=^^^^=^^^^=^^^^^>"
2030 static UINT msi_dialog_maskedit_control( msi_dialog *dialog, MSIRECORD *rec )
2032 LPWSTR font_mask, val = NULL, font;
2033 struct msi_maskedit_info *info = NULL;
2034 UINT ret = ERROR_SUCCESS;
2035 msi_control *control;
2036 LPCWSTR prop, mask;
2038 TRACE("\n");
2040 font_mask = msi_get_deformatted_field( dialog->package, rec, 10 );
2041 font = msi_dialog_get_style( font_mask, &mask );
2042 if( !mask )
2044 WARN("mask template is empty\n");
2045 goto end;
2048 info = msi_dialog_parse_groups( mask );
2049 if( !info )
2051 ERR("template %s is invalid\n", debugstr_w(mask));
2052 goto end;
2055 info->dialog = dialog;
2057 control = msi_dialog_add_control( dialog, rec, szStatic,
2058 SS_OWNERDRAW | WS_GROUP | WS_VISIBLE );
2059 if( !control )
2061 ERR("Failed to create maskedit container\n");
2062 ret = ERROR_FUNCTION_FAILED;
2063 goto end;
2065 SetWindowLongPtrW( control->hwnd, GWL_EXSTYLE, WS_EX_CONTROLPARENT );
2067 info->hwnd = control->hwnd;
2069 /* subclass the static control */
2070 info->oldproc = (WNDPROC) SetWindowLongPtrW( info->hwnd, GWLP_WNDPROC,
2071 (LONG_PTR)MSIMaskedEdit_WndProc );
2072 SetPropW( control->hwnd, szButtonData, info );
2074 prop = MSI_RecordGetString( rec, 9 );
2075 if( prop )
2076 info->prop = strdupW( prop );
2078 msi_maskedit_create_children( info, font );
2080 if( prop )
2082 val = msi_dup_property( dialog->package->db, prop );
2083 if( val )
2085 msi_maskedit_set_text( info, val );
2086 msi_free( val );
2090 end:
2091 if( ret != ERROR_SUCCESS )
2092 msi_free( info );
2093 msi_free( font_mask );
2094 msi_free( font );
2095 return ret;
2098 /******************** Progress Bar *****************************************/
2100 static UINT msi_dialog_progress_bar( msi_dialog *dialog, MSIRECORD *rec )
2102 msi_control *control;
2103 DWORD attributes, style;
2105 style = WS_VISIBLE;
2106 attributes = MSI_RecordGetInteger( rec, 8 );
2107 if( !(attributes & msidbControlAttributesProgress95) )
2108 style |= PBS_SMOOTH;
2110 control = msi_dialog_add_control( dialog, rec, PROGRESS_CLASSW, style );
2111 if( !control )
2112 return ERROR_FUNCTION_FAILED;
2114 event_subscribe( dialog, szSetProgress, control->name, szProgress );
2115 return ERROR_SUCCESS;
2118 /******************** Path Edit ********************************************/
2120 struct msi_pathedit_info
2122 msi_dialog *dialog;
2123 msi_control *control;
2124 WNDPROC oldproc;
2127 static void msi_dialog_update_pathedit( msi_dialog *dialog, msi_control *control )
2129 LPWSTR prop, path;
2130 BOOL indirect;
2132 if (!control && !(control = msi_dialog_find_control_by_type( dialog, szPathEdit )))
2133 return;
2135 indirect = control->attributes & msidbControlAttributesIndirect;
2136 prop = msi_dialog_dup_property( dialog, control->property, indirect );
2137 path = msi_dialog_dup_property( dialog, prop, TRUE );
2139 SetWindowTextW( control->hwnd, path );
2140 SendMessageW( control->hwnd, EM_SETSEL, 0, -1 );
2142 msi_free( path );
2143 msi_free( prop );
2146 /* FIXME: test when this should fail */
2147 static BOOL msi_dialog_verify_path( LPWSTR path )
2149 if ( !lstrlenW( path ) )
2150 return FALSE;
2152 if ( PathIsRelativeW( path ) )
2153 return FALSE;
2155 return TRUE;
2158 /* returns TRUE if the path is valid, FALSE otherwise */
2159 static BOOL msi_dialog_onkillfocus( msi_dialog *dialog, msi_control *control )
2161 LPWSTR buf, prop;
2162 BOOL indirect;
2163 BOOL valid;
2165 indirect = control->attributes & msidbControlAttributesIndirect;
2166 prop = msi_dialog_dup_property( dialog, control->property, indirect );
2168 buf = msi_get_window_text( control->hwnd );
2170 if ( !msi_dialog_verify_path( buf ) )
2172 /* FIXME: display an error message box */
2173 ERR("Invalid path %s\n", debugstr_w( buf ));
2174 valid = FALSE;
2175 SetFocus( control->hwnd );
2177 else
2179 valid = TRUE;
2180 msi_dialog_set_property( dialog->package, prop, buf );
2183 msi_dialog_update_pathedit( dialog, control );
2185 TRACE("edit %s contents changed, set %s\n", debugstr_w(control->name),
2186 debugstr_w(prop));
2188 msi_free( buf );
2189 msi_free( prop );
2191 return valid;
2194 static LRESULT WINAPI MSIPathEdit_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
2196 struct msi_pathedit_info *info = GetPropW(hWnd, szButtonData);
2197 LRESULT r = 0;
2199 TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam);
2201 if ( msg == WM_KILLFOCUS )
2203 /* if the path is invalid, don't handle this message */
2204 if ( !msi_dialog_onkillfocus( info->dialog, info->control ) )
2205 return 0;
2208 r = CallWindowProcW(info->oldproc, hWnd, msg, wParam, lParam);
2210 if ( msg == WM_NCDESTROY )
2212 msi_free( info );
2213 RemovePropW( hWnd, szButtonData );
2216 return r;
2219 static UINT msi_dialog_pathedit_control( msi_dialog *dialog, MSIRECORD *rec )
2221 struct msi_pathedit_info *info;
2222 msi_control *control;
2223 LPCWSTR prop;
2225 info = msi_alloc( sizeof *info );
2226 if (!info)
2227 return ERROR_FUNCTION_FAILED;
2229 control = msi_dialog_add_control( dialog, rec, szEdit,
2230 WS_BORDER | WS_TABSTOP );
2231 control->attributes = MSI_RecordGetInteger( rec, 8 );
2232 prop = MSI_RecordGetString( rec, 9 );
2233 control->property = msi_dialog_dup_property( dialog, prop, FALSE );
2235 info->dialog = dialog;
2236 info->control = control;
2237 info->oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
2238 (LONG_PTR)MSIPathEdit_WndProc );
2239 SetPropW( control->hwnd, szButtonData, info );
2241 msi_dialog_update_pathedit( dialog, control );
2243 return ERROR_SUCCESS;
2246 static UINT msi_dialog_radiogroup_handler( msi_dialog *dialog, msi_control *control, WPARAM param )
2248 if (HIWORD(param) != BN_CLICKED)
2249 return ERROR_SUCCESS;
2251 TRACE("clicked radio button %s, set %s\n", debugstr_w(control->name), debugstr_w(control->property));
2253 msi_dialog_set_property( dialog->package, control->property, control->name );
2255 return msi_dialog_button_handler( dialog, control, param );
2258 /* radio buttons are a bit different from normal controls */
2259 static UINT msi_dialog_create_radiobutton( MSIRECORD *rec, LPVOID param )
2261 radio_button_group_descr *group = param;
2262 msi_dialog *dialog = group->dialog;
2263 msi_control *control;
2264 LPCWSTR prop, text, name;
2265 DWORD style, attributes = group->attributes;
2267 style = WS_CHILD | BS_AUTORADIOBUTTON | BS_MULTILINE | WS_TABSTOP;
2268 name = MSI_RecordGetString( rec, 3 );
2269 text = MSI_RecordGetString( rec, 8 );
2270 if( attributes & msidbControlAttributesVisible )
2271 style |= WS_VISIBLE;
2272 if( ~attributes & msidbControlAttributesEnabled )
2273 style |= WS_DISABLED;
2275 control = dialog_create_window( dialog, rec, 0, szButton, name, text, style,
2276 group->parent->hwnd );
2277 if (!control)
2278 return ERROR_FUNCTION_FAILED;
2279 control->handler = msi_dialog_radiogroup_handler;
2281 if (group->propval && !strcmpW( control->name, group->propval ))
2282 SendMessageW(control->hwnd, BM_SETCHECK, BST_CHECKED, 0);
2284 prop = MSI_RecordGetString( rec, 1 );
2285 if( prop )
2286 control->property = strdupW( prop );
2288 return ERROR_SUCCESS;
2291 static BOOL CALLBACK msi_radioground_child_enum( HWND hWnd, LPARAM lParam )
2293 EnableWindow( hWnd, lParam );
2294 return TRUE;
2297 static LRESULT WINAPI MSIRadioGroup_WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
2299 WNDPROC oldproc = (WNDPROC)GetPropW( hWnd, szButtonData );
2300 LRESULT r;
2302 TRACE("hWnd %p msg %04x wParam 0x%08lx lParam 0x%08lx\n", hWnd, msg, wParam, lParam);
2304 if (msg == WM_COMMAND) /* Forward notifications to dialog */
2305 SendMessageW( GetParent( hWnd ), msg, wParam, lParam );
2307 r = CallWindowProcW( oldproc, hWnd, msg, wParam, lParam );
2309 /* make sure the radio buttons show as disabled if the parent is disabled */
2310 if (msg == WM_ENABLE)
2311 EnumChildWindows( hWnd, msi_radioground_child_enum, wParam );
2313 return r;
2316 static UINT msi_dialog_radiogroup_control( msi_dialog *dialog, MSIRECORD *rec )
2318 static const WCHAR query[] = {
2319 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
2320 'R','a','d','i','o','B','u','t','t','o','n',' ','W','H','E','R','E',' ',
2321 '`','P','r','o','p','e','r','t','y','`',' ','=',' ','\'','%','s','\'',0};
2322 UINT r;
2323 LPCWSTR prop;
2324 msi_control *control;
2325 MSIQUERY *view;
2326 radio_button_group_descr group;
2327 MSIPACKAGE *package = dialog->package;
2328 WNDPROC oldproc;
2329 DWORD attr, style = WS_GROUP;
2331 prop = MSI_RecordGetString( rec, 9 );
2333 TRACE("%p %p %s\n", dialog, rec, debugstr_w( prop ));
2335 attr = MSI_RecordGetInteger( rec, 8 );
2336 if (attr & msidbControlAttributesHasBorder)
2337 style |= BS_GROUPBOX;
2338 else
2339 style |= BS_OWNERDRAW;
2341 /* Create parent group box to hold radio buttons */
2342 control = msi_dialog_add_control( dialog, rec, szButton, style );
2343 if( !control )
2344 return ERROR_FUNCTION_FAILED;
2346 oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
2347 (LONG_PTR)MSIRadioGroup_WndProc );
2348 SetPropW(control->hwnd, szButtonData, oldproc);
2349 SetWindowLongPtrW( control->hwnd, GWL_EXSTYLE, WS_EX_CONTROLPARENT );
2351 if( prop )
2352 control->property = strdupW( prop );
2354 /* query the Radio Button table for all control in this group */
2355 r = MSI_OpenQuery( package->db, &view, query, prop );
2356 if( r != ERROR_SUCCESS )
2358 ERR("query failed for dialog %s radio group %s\n",
2359 debugstr_w(dialog->name), debugstr_w(prop));
2360 return ERROR_INVALID_PARAMETER;
2363 group.dialog = dialog;
2364 group.parent = control;
2365 group.attributes = MSI_RecordGetInteger( rec, 8 );
2366 group.propval = msi_dup_property( dialog->package->db, control->property );
2368 r = MSI_IterateRecords( view, 0, msi_dialog_create_radiobutton, &group );
2369 msiobj_release( &view->hdr );
2370 msi_free( group.propval );
2371 return r;
2374 static void
2375 msi_seltree_sync_item_state( HWND hwnd, MSIFEATURE *feature, HTREEITEM hItem )
2377 TVITEMW tvi;
2378 DWORD index = feature->ActionRequest;
2380 TRACE("Feature %s -> %d %d %d\n", debugstr_w(feature->Title),
2381 feature->Installed, feature->Action, feature->ActionRequest);
2383 if (index == INSTALLSTATE_UNKNOWN)
2384 index = INSTALLSTATE_ABSENT;
2386 tvi.mask = TVIF_STATE;
2387 tvi.hItem = hItem;
2388 tvi.state = INDEXTOSTATEIMAGEMASK( index );
2389 tvi.stateMask = TVIS_STATEIMAGEMASK;
2391 SendMessageW( hwnd, TVM_SETITEMW, 0, (LPARAM) &tvi );
2394 static UINT
2395 msi_seltree_popup_menu( HWND hwnd, INT x, INT y )
2397 HMENU hMenu;
2398 INT r;
2400 /* create a menu to display */
2401 hMenu = CreatePopupMenu();
2403 /* FIXME: load strings from resources */
2404 AppendMenuA( hMenu, MF_ENABLED, INSTALLSTATE_LOCAL, "Install feature locally");
2405 AppendMenuA( hMenu, MF_ENABLED, USER_INSTALLSTATE_ALL, "Install entire feature");
2406 AppendMenuA( hMenu, MF_ENABLED, INSTALLSTATE_ADVERTISED, "Install on demand");
2407 AppendMenuA( hMenu, MF_ENABLED, INSTALLSTATE_ABSENT, "Don't install");
2408 r = TrackPopupMenu( hMenu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RETURNCMD,
2409 x, y, 0, hwnd, NULL );
2410 DestroyMenu( hMenu );
2411 return r;
2414 static void
2415 msi_seltree_update_feature_installstate( HWND hwnd, HTREEITEM hItem,
2416 MSIPACKAGE *package, MSIFEATURE *feature, INSTALLSTATE state )
2418 feature->ActionRequest = state;
2419 msi_seltree_sync_item_state( hwnd, feature, hItem );
2420 ACTION_UpdateComponentStates( package, feature );
2423 static void
2424 msi_seltree_update_siblings_and_children_installstate( HWND hwnd, HTREEITEM curr,
2425 MSIPACKAGE *package, INSTALLSTATE state)
2427 /* update all siblings */
2430 MSIFEATURE *feature;
2431 HTREEITEM child;
2433 feature = msi_seltree_feature_from_item( hwnd, curr );
2434 msi_seltree_update_feature_installstate( hwnd, curr, package, feature, state );
2436 /* update this sibling's children */
2437 child = (HTREEITEM)SendMessageW( hwnd, TVM_GETNEXTITEM, (WPARAM)TVGN_CHILD, (LPARAM)curr );
2438 if (child)
2439 msi_seltree_update_siblings_and_children_installstate( hwnd, child,
2440 package, state );
2442 while ((curr = (HTREEITEM)SendMessageW( hwnd, TVM_GETNEXTITEM, (WPARAM)TVGN_NEXT, (LPARAM)curr )));
2445 static LRESULT
2446 msi_seltree_menu( HWND hwnd, HTREEITEM hItem )
2448 struct msi_selection_tree_info *info;
2449 MSIFEATURE *feature;
2450 MSIPACKAGE *package;
2451 union {
2452 RECT rc;
2453 POINT pt[2];
2454 HTREEITEM hItem;
2455 } u;
2456 UINT r;
2458 info = GetPropW(hwnd, szButtonData);
2459 package = info->dialog->package;
2461 feature = msi_seltree_feature_from_item( hwnd, hItem );
2462 if (!feature)
2464 ERR("item %p feature was NULL\n", hItem);
2465 return 0;
2468 /* get the item's rectangle to put the menu just below it */
2469 u.hItem = hItem;
2470 SendMessageW( hwnd, TVM_GETITEMRECT, 0, (LPARAM) &u.rc );
2471 MapWindowPoints( hwnd, NULL, u.pt, 2 );
2473 r = msi_seltree_popup_menu( hwnd, u.rc.left, u.rc.top );
2475 switch (r)
2477 case USER_INSTALLSTATE_ALL:
2478 r = INSTALLSTATE_LOCAL;
2479 /* fall-through */
2480 case INSTALLSTATE_ADVERTISED:
2481 case INSTALLSTATE_ABSENT:
2483 HTREEITEM child;
2484 child = (HTREEITEM)SendMessageW( hwnd, TVM_GETNEXTITEM, (WPARAM)TVGN_CHILD, (LPARAM)hItem );
2485 if (child)
2486 msi_seltree_update_siblings_and_children_installstate( hwnd, child, package, r );
2488 /* fall-through */
2489 case INSTALLSTATE_LOCAL:
2490 msi_seltree_update_feature_installstate( hwnd, hItem, package, feature, r );
2491 break;
2494 return 0;
2497 static LRESULT WINAPI
2498 MSISelectionTree_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
2500 struct msi_selection_tree_info *info;
2501 TVHITTESTINFO tvhti;
2502 HRESULT r;
2504 TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam);
2506 info = GetPropW(hWnd, szButtonData);
2508 switch( msg )
2510 case WM_LBUTTONDOWN:
2511 tvhti.pt.x = (short)LOWORD( lParam );
2512 tvhti.pt.y = (short)HIWORD( lParam );
2513 tvhti.flags = 0;
2514 tvhti.hItem = 0;
2515 CallWindowProcW(info->oldproc, hWnd, TVM_HITTEST, 0, (LPARAM) &tvhti );
2516 if (tvhti.flags & TVHT_ONITEMSTATEICON)
2517 return msi_seltree_menu( hWnd, tvhti.hItem );
2518 break;
2520 r = CallWindowProcW(info->oldproc, hWnd, msg, wParam, lParam);
2522 switch( msg )
2524 case WM_NCDESTROY:
2525 msi_free( info );
2526 RemovePropW( hWnd, szButtonData );
2527 break;
2529 return r;
2532 static void
2533 msi_seltree_add_child_features( MSIPACKAGE *package, HWND hwnd,
2534 LPCWSTR parent, HTREEITEM hParent )
2536 struct msi_selection_tree_info *info = GetPropW( hwnd, szButtonData );
2537 MSIFEATURE *feature;
2538 TVINSERTSTRUCTW tvis;
2539 HTREEITEM hitem, hfirst = NULL;
2541 LIST_FOR_EACH_ENTRY( feature, &package->features, MSIFEATURE, entry )
2543 if ( parent && feature->Feature_Parent && strcmpW( parent, feature->Feature_Parent ))
2544 continue;
2545 else if ( parent && !feature->Feature_Parent )
2546 continue;
2547 else if ( !parent && feature->Feature_Parent )
2548 continue;
2550 if ( !feature->Title )
2551 continue;
2553 if ( !feature->Display )
2554 continue;
2556 memset( &tvis, 0, sizeof tvis );
2557 tvis.hParent = hParent;
2558 tvis.hInsertAfter = TVI_LAST;
2559 tvis.u.item.mask = TVIF_TEXT | TVIF_PARAM;
2560 tvis.u.item.pszText = feature->Title;
2561 tvis.u.item.lParam = (LPARAM) feature;
2563 hitem = (HTREEITEM) SendMessageW( hwnd, TVM_INSERTITEMW, 0, (LPARAM) &tvis );
2564 if (!hitem)
2565 continue;
2567 if (!hfirst)
2568 hfirst = hitem;
2570 msi_seltree_sync_item_state( hwnd, feature, hitem );
2571 msi_seltree_add_child_features( package, hwnd,
2572 feature->Feature, hitem );
2574 /* the node is expanded if Display is odd */
2575 if ( feature->Display % 2 != 0 )
2576 SendMessageW( hwnd, TVM_EXPAND, TVE_EXPAND, (LPARAM) hitem );
2579 /* select the first item */
2580 SendMessageW( hwnd, TVM_SELECTITEM, TVGN_CARET | TVGN_DROPHILITE, (LPARAM) hfirst );
2581 info->selected = hfirst;
2584 static void msi_seltree_create_imagelist( HWND hwnd )
2586 const int bm_width = 32, bm_height = 16, bm_count = 3;
2587 const int bm_resource = 0x1001;
2588 HIMAGELIST himl;
2589 int i;
2590 HBITMAP hbmp;
2592 himl = ImageList_Create( bm_width, bm_height, FALSE, 4, 0 );
2593 if (!himl)
2595 ERR("failed to create image list\n");
2596 return;
2599 for (i=0; i<bm_count; i++)
2601 hbmp = LoadBitmapW( msi_hInstance, MAKEINTRESOURCEW(i+bm_resource) );
2602 if (!hbmp)
2604 ERR("failed to load bitmap %d\n", i);
2605 break;
2609 * Add a dummy bitmap at offset zero because the treeview
2610 * can't use it as a state mask (zero means no user state).
2612 if (!i)
2613 ImageList_Add( himl, hbmp, NULL );
2615 ImageList_Add( himl, hbmp, NULL );
2618 SendMessageW( hwnd, TVM_SETIMAGELIST, TVSIL_STATE, (LPARAM)himl );
2621 static UINT msi_dialog_seltree_handler( msi_dialog *dialog,
2622 msi_control *control, WPARAM param )
2624 struct msi_selection_tree_info *info = GetPropW( control->hwnd, szButtonData );
2625 LPNMTREEVIEWW tv = (LPNMTREEVIEWW)param;
2626 MSIRECORD *row, *rec;
2627 MSIFOLDER *folder;
2628 MSIFEATURE *feature;
2629 LPCWSTR dir, title = NULL;
2630 UINT r = ERROR_SUCCESS;
2632 static const WCHAR select[] = {
2633 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
2634 '`','F','e','a','t','u','r','e','`',' ','W','H','E','R','E',' ',
2635 '`','T','i','t','l','e','`',' ','=',' ','\'','%','s','\'',0
2638 if (tv->hdr.code != TVN_SELCHANGINGW)
2639 return ERROR_SUCCESS;
2641 info->selected = tv->itemNew.hItem;
2643 if (!(tv->itemNew.mask & TVIF_TEXT))
2645 feature = msi_seltree_feature_from_item( control->hwnd, tv->itemNew.hItem );
2646 if (feature)
2647 title = feature->Title;
2649 else
2650 title = tv->itemNew.pszText;
2652 row = MSI_QueryGetRecord( dialog->package->db, select, title );
2653 if (!row)
2654 return ERROR_FUNCTION_FAILED;
2656 rec = MSI_CreateRecord( 1 );
2658 MSI_RecordSetStringW( rec, 1, MSI_RecordGetString( row, 4 ) );
2659 msi_event_fire( dialog->package, szSelectionDescription, rec );
2661 dir = MSI_RecordGetString( row, 7 );
2662 if (dir)
2664 folder = msi_get_loaded_folder( dialog->package, dir );
2665 if (!folder)
2667 r = ERROR_FUNCTION_FAILED;
2668 goto done;
2670 MSI_RecordSetStringW( rec, 1, folder->ResolvedTarget );
2672 else
2673 MSI_RecordSetStringW( rec, 1, NULL );
2675 msi_event_fire( dialog->package, szSelectionPath, rec );
2677 done:
2678 msiobj_release(&row->hdr);
2679 msiobj_release(&rec->hdr);
2681 return r;
2684 static UINT msi_dialog_selection_tree( msi_dialog *dialog, MSIRECORD *rec )
2686 msi_control *control;
2687 LPCWSTR prop, control_name;
2688 MSIPACKAGE *package = dialog->package;
2689 DWORD style;
2690 struct msi_selection_tree_info *info;
2692 info = msi_alloc( sizeof *info );
2693 if (!info)
2694 return ERROR_FUNCTION_FAILED;
2696 /* create the treeview control */
2697 style = TVS_HASLINES | TVS_HASBUTTONS | TVS_LINESATROOT;
2698 style |= WS_GROUP | WS_VSCROLL;
2699 control = msi_dialog_add_control( dialog, rec, WC_TREEVIEWW, style );
2700 if (!control)
2702 msi_free(info);
2703 return ERROR_FUNCTION_FAILED;
2706 control->handler = msi_dialog_seltree_handler;
2707 control_name = MSI_RecordGetString( rec, 2 );
2708 control->attributes = MSI_RecordGetInteger( rec, 8 );
2709 prop = MSI_RecordGetString( rec, 9 );
2710 control->property = msi_dialog_dup_property( dialog, prop, FALSE );
2712 /* subclass */
2713 info->dialog = dialog;
2714 info->hwnd = control->hwnd;
2715 info->oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
2716 (LONG_PTR)MSISelectionTree_WndProc );
2717 SetPropW( control->hwnd, szButtonData, info );
2719 event_subscribe( dialog, szSelectionPath, control_name, szProperty );
2721 /* initialize it */
2722 msi_seltree_create_imagelist( control->hwnd );
2723 msi_seltree_add_child_features( package, control->hwnd, NULL, NULL );
2725 return ERROR_SUCCESS;
2728 /******************** Group Box ***************************************/
2730 static UINT msi_dialog_group_box( msi_dialog *dialog, MSIRECORD *rec )
2732 msi_control *control;
2733 DWORD style;
2735 style = BS_GROUPBOX | WS_CHILD | WS_GROUP;
2736 control = msi_dialog_add_control( dialog, rec, WC_BUTTONW, style );
2737 if (!control)
2738 return ERROR_FUNCTION_FAILED;
2740 return ERROR_SUCCESS;
2743 /******************** List Box ***************************************/
2745 struct msi_listbox_info
2747 msi_dialog *dialog;
2748 HWND hwnd;
2749 WNDPROC oldproc;
2750 DWORD num_items;
2751 DWORD addpos_items;
2752 LPWSTR *items;
2755 static LRESULT WINAPI MSIListBox_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
2757 struct msi_listbox_info *info;
2758 LRESULT r;
2759 DWORD j;
2761 TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam);
2763 info = GetPropW( hWnd, szButtonData );
2764 if (!info)
2765 return 0;
2767 r = CallWindowProcW( info->oldproc, hWnd, msg, wParam, lParam );
2769 switch( msg )
2771 case WM_NCDESTROY:
2772 for (j = 0; j < info->num_items; j++)
2773 msi_free( info->items[j] );
2774 msi_free( info->items );
2775 msi_free( info );
2776 RemovePropW( hWnd, szButtonData );
2777 break;
2780 return r;
2783 static UINT msi_listbox_add_item( MSIRECORD *rec, LPVOID param )
2785 struct msi_listbox_info *info = param;
2786 LPCWSTR value, text;
2787 int pos;
2789 value = MSI_RecordGetString( rec, 3 );
2790 text = MSI_RecordGetString( rec, 4 );
2792 info->items[info->addpos_items] = strdupW( value );
2794 pos = SendMessageW( info->hwnd, LB_ADDSTRING, 0, (LPARAM)text );
2795 SendMessageW( info->hwnd, LB_SETITEMDATA, pos, (LPARAM)info->items[info->addpos_items] );
2796 info->addpos_items++;
2797 return ERROR_SUCCESS;
2800 static UINT msi_listbox_add_items( struct msi_listbox_info *info, LPCWSTR property )
2802 static const WCHAR query[] = {
2803 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
2804 '`','L','i','s','t','B','o','x','`',' ','W','H','E','R','E',' ',
2805 '`','P','r','o','p','e','r','t','y','`',' ','=',' ','\'','%','s','\'',' ',
2806 'O','R','D','E','R',' ','B','Y',' ','`','O','r','d','e','r','`',0};
2807 MSIQUERY *view;
2808 DWORD count;
2809 UINT r;
2811 r = MSI_OpenQuery( info->dialog->package->db, &view, query, property );
2812 if ( r != ERROR_SUCCESS )
2813 return r;
2815 /* just get the number of records */
2816 count = 0;
2817 r = MSI_IterateRecords( view, &count, NULL, NULL );
2818 if (r != ERROR_SUCCESS)
2820 msiobj_release( &view->hdr );
2821 return r;
2823 info->num_items = count;
2824 info->items = msi_alloc( sizeof(*info->items) * count );
2826 r = MSI_IterateRecords( view, NULL, msi_listbox_add_item, info );
2827 msiobj_release( &view->hdr );
2828 return r;
2831 static UINT msi_dialog_listbox_handler( msi_dialog *dialog,
2832 msi_control *control, WPARAM param )
2834 struct msi_listbox_info *info;
2835 int index;
2836 LPCWSTR value;
2838 if( HIWORD(param) != LBN_SELCHANGE )
2839 return ERROR_SUCCESS;
2841 info = GetPropW( control->hwnd, szButtonData );
2842 index = SendMessageW( control->hwnd, LB_GETCURSEL, 0, 0 );
2843 value = (LPCWSTR) SendMessageW( control->hwnd, LB_GETITEMDATA, index, 0 );
2845 msi_dialog_set_property( info->dialog->package, control->property, value );
2846 msi_dialog_evaluate_control_conditions( info->dialog );
2848 return ERROR_SUCCESS;
2851 static UINT msi_dialog_list_box( msi_dialog *dialog, MSIRECORD *rec )
2853 struct msi_listbox_info *info;
2854 msi_control *control;
2855 DWORD attributes, style;
2856 LPCWSTR prop;
2858 info = msi_alloc( sizeof *info );
2859 if (!info)
2860 return ERROR_FUNCTION_FAILED;
2862 style = WS_TABSTOP | WS_GROUP | WS_CHILD | LBS_NOTIFY | WS_VSCROLL | WS_BORDER;
2863 attributes = MSI_RecordGetInteger( rec, 8 );
2864 if (~attributes & msidbControlAttributesSorted)
2865 style |= LBS_SORT;
2867 control = msi_dialog_add_control( dialog, rec, WC_LISTBOXW, style );
2868 if (!control)
2870 msi_free(info);
2871 return ERROR_FUNCTION_FAILED;
2874 control->handler = msi_dialog_listbox_handler;
2876 prop = MSI_RecordGetString( rec, 9 );
2877 control->property = msi_dialog_dup_property( dialog, prop, FALSE );
2879 /* subclass */
2880 info->dialog = dialog;
2881 info->hwnd = control->hwnd;
2882 info->items = NULL;
2883 info->addpos_items = 0;
2884 info->oldproc = (WNDPROC)SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
2885 (LONG_PTR)MSIListBox_WndProc );
2886 SetPropW( control->hwnd, szButtonData, info );
2888 if ( control->property )
2889 msi_listbox_add_items( info, control->property );
2891 return ERROR_SUCCESS;
2894 /******************** Directory Combo ***************************************/
2896 static void msi_dialog_update_directory_combo( msi_dialog *dialog, msi_control *control )
2898 LPWSTR prop, path;
2899 BOOL indirect;
2901 if (!control && !(control = msi_dialog_find_control_by_type( dialog, szDirectoryCombo )))
2902 return;
2904 indirect = control->attributes & msidbControlAttributesIndirect;
2905 prop = msi_dialog_dup_property( dialog, control->property, indirect );
2906 path = msi_dialog_dup_property( dialog, prop, TRUE );
2908 PathStripPathW( path );
2909 PathRemoveBackslashW( path );
2911 SendMessageW( control->hwnd, CB_INSERTSTRING, 0, (LPARAM)path );
2912 SendMessageW( control->hwnd, CB_SETCURSEL, 0, 0 );
2914 msi_free( path );
2915 msi_free( prop );
2918 static UINT msi_dialog_directory_combo( msi_dialog *dialog, MSIRECORD *rec )
2920 msi_control *control;
2921 LPCWSTR prop;
2922 DWORD style;
2924 /* FIXME: use CBS_OWNERDRAWFIXED and add owner draw code */
2925 style = CBS_DROPDOWNLIST | CBS_HASSTRINGS | WS_CHILD |
2926 WS_GROUP | WS_TABSTOP | WS_VSCROLL;
2927 control = msi_dialog_add_control( dialog, rec, WC_COMBOBOXW, style );
2928 if (!control)
2929 return ERROR_FUNCTION_FAILED;
2931 control->attributes = MSI_RecordGetInteger( rec, 8 );
2932 prop = MSI_RecordGetString( rec, 9 );
2933 control->property = msi_dialog_dup_property( dialog, prop, FALSE );
2935 msi_dialog_update_directory_combo( dialog, control );
2937 return ERROR_SUCCESS;
2940 /******************** Directory List ***************************************/
2942 static void msi_dialog_update_directory_list( msi_dialog *dialog, msi_control *control )
2944 WCHAR dir_spec[MAX_PATH];
2945 WIN32_FIND_DATAW wfd;
2946 LPWSTR prop, path;
2947 BOOL indirect;
2948 LVITEMW item;
2949 HANDLE file;
2951 static const WCHAR asterisk[] = {'*',0};
2953 if (!control && !(control = msi_dialog_find_control_by_type( dialog, szDirectoryList )))
2954 return;
2956 /* clear the list-view */
2957 SendMessageW( control->hwnd, LVM_DELETEALLITEMS, 0, 0 );
2959 indirect = control->attributes & msidbControlAttributesIndirect;
2960 prop = msi_dialog_dup_property( dialog, control->property, indirect );
2961 path = msi_dialog_dup_property( dialog, prop, TRUE );
2963 lstrcpyW( dir_spec, path );
2964 lstrcatW( dir_spec, asterisk );
2966 file = FindFirstFileW( dir_spec, &wfd );
2967 if ( file == INVALID_HANDLE_VALUE )
2968 return;
2972 if ( wfd.dwFileAttributes != FILE_ATTRIBUTE_DIRECTORY )
2973 continue;
2975 if ( !strcmpW( wfd.cFileName, szDot ) || !strcmpW( wfd.cFileName, szDotDot ) )
2976 continue;
2978 item.mask = LVIF_TEXT;
2979 item.cchTextMax = MAX_PATH;
2980 item.iItem = 0;
2981 item.iSubItem = 0;
2982 item.pszText = wfd.cFileName;
2984 SendMessageW( control->hwnd, LVM_INSERTITEMW, 0, (LPARAM)&item );
2985 } while ( FindNextFileW( file, &wfd ) );
2987 msi_free( prop );
2988 msi_free( path );
2989 FindClose( file );
2992 static UINT msi_dialog_directorylist_up( msi_dialog *dialog )
2994 msi_control *control;
2995 LPWSTR prop, path, ptr;
2996 BOOL indirect;
2998 control = msi_dialog_find_control_by_type( dialog, szDirectoryList );
2999 indirect = control->attributes & msidbControlAttributesIndirect;
3000 prop = msi_dialog_dup_property( dialog, control->property, indirect );
3001 path = msi_dialog_dup_property( dialog, prop, TRUE );
3003 /* strip off the last directory */
3004 ptr = PathFindFileNameW( path );
3005 if (ptr != path) *(ptr - 1) = '\0';
3006 PathAddBackslashW( path );
3008 msi_dialog_set_property( dialog->package, prop, path );
3010 msi_dialog_update_directory_list( dialog, NULL );
3011 msi_dialog_update_directory_combo( dialog, NULL );
3012 msi_dialog_update_pathedit( dialog, NULL );
3014 msi_free( path );
3015 msi_free( prop );
3017 return ERROR_SUCCESS;
3020 static UINT msi_dialog_dirlist_handler( msi_dialog *dialog,
3021 msi_control *control, WPARAM param )
3023 LPNMHDR nmhdr = (LPNMHDR)param;
3024 WCHAR new_path[MAX_PATH];
3025 WCHAR text[MAX_PATH];
3026 LPWSTR path, prop;
3027 BOOL indirect;
3028 LVITEMW item;
3029 int index;
3031 if (nmhdr->code != LVN_ITEMACTIVATE)
3032 return ERROR_SUCCESS;
3034 index = SendMessageW( control->hwnd, LVM_GETNEXTITEM, -1, LVNI_SELECTED );
3035 if ( index < 0 )
3037 ERR("No list-view item selected!\n");
3038 return ERROR_FUNCTION_FAILED;
3041 item.iSubItem = 0;
3042 item.pszText = text;
3043 item.cchTextMax = MAX_PATH;
3044 SendMessageW( control->hwnd, LVM_GETITEMTEXTW, index, (LPARAM)&item );
3046 indirect = control->attributes & msidbControlAttributesIndirect;
3047 prop = msi_dialog_dup_property( dialog, control->property, indirect );
3048 path = msi_dialog_dup_property( dialog, prop, TRUE );
3050 lstrcpyW( new_path, path );
3051 lstrcatW( new_path, text );
3052 lstrcatW( new_path, szBackSlash );
3054 msi_dialog_set_property( dialog->package, prop, new_path );
3056 msi_dialog_update_directory_list( dialog, NULL );
3057 msi_dialog_update_directory_combo( dialog, NULL );
3058 msi_dialog_update_pathedit( dialog, NULL );
3060 msi_free( prop );
3061 msi_free( path );
3062 return ERROR_SUCCESS;
3065 static UINT msi_dialog_directory_list( msi_dialog *dialog, MSIRECORD *rec )
3067 msi_control *control;
3068 LPCWSTR prop;
3069 DWORD style;
3071 style = LVS_LIST | WS_VSCROLL | LVS_SHAREIMAGELISTS |
3072 LVS_AUTOARRANGE | LVS_SINGLESEL | WS_BORDER |
3073 LVS_SORTASCENDING | WS_CHILD | WS_GROUP | WS_TABSTOP;
3074 control = msi_dialog_add_control( dialog, rec, WC_LISTVIEWW, style );
3075 if (!control)
3076 return ERROR_FUNCTION_FAILED;
3078 control->attributes = MSI_RecordGetInteger( rec, 8 );
3079 control->handler = msi_dialog_dirlist_handler;
3080 prop = MSI_RecordGetString( rec, 9 );
3081 control->property = msi_dialog_dup_property( dialog, prop, FALSE );
3083 /* double click to activate an item in the list */
3084 SendMessageW( control->hwnd, LVM_SETEXTENDEDLISTVIEWSTYLE,
3085 0, LVS_EX_TWOCLICKACTIVATE );
3087 msi_dialog_update_directory_list( dialog, control );
3089 return ERROR_SUCCESS;
3092 /******************** VolumeCost List ***************************************/
3094 static BOOL str_is_number( LPCWSTR str )
3096 int i;
3098 for (i = 0; i < lstrlenW( str ); i++)
3099 if (!isdigitW(str[i]))
3100 return FALSE;
3102 return TRUE;
3105 static const WCHAR column_keys[][80] =
3107 {'V','o','l','u','m','e','C','o','s','t','V','o','l','u','m','e',0},
3108 {'V','o','l','u','m','e','C','o','s','t','S','i','z','e',0},
3109 {'V','o','l','u','m','e','C','o','s','t','A','v','a','i','l','a','b','l','e',0},
3110 {'V','o','l','u','m','e','C','o','s','t','R','e','q','u','i','r','e','d',0},
3111 {'V','o','l','u','m','e','C','o','s','t','D','i','f','f','e','r','e','n','c','e',0}
3114 static void msi_dialog_vcl_add_columns( msi_dialog *dialog, msi_control *control, MSIRECORD *rec )
3116 LPCWSTR text = MSI_RecordGetString( rec, 10 );
3117 LPCWSTR begin = text, end;
3118 WCHAR *num;
3119 LVCOLUMNW lvc;
3120 DWORD count = 0;
3122 static const WCHAR negative[] = {'-',0};
3124 if (!text) return;
3126 while ((begin = strchrW( begin, '{' )) && count < 5)
3128 if (!(end = strchrW( begin, '}' )))
3129 return;
3131 num = msi_alloc( (end-begin+1)*sizeof(WCHAR) );
3132 if (!num)
3133 return;
3135 lstrcpynW( num, begin + 1, end - begin );
3136 begin += end - begin + 1;
3138 /* empty braces or '0' hides the column */
3139 if ( !num[0] || !strcmpW( num, szZero ) )
3141 count++;
3142 msi_free( num );
3143 continue;
3146 /* the width must be a positive number
3147 * if a width is invalid, all remaining columns are hidden
3149 if ( !strncmpW( num, negative, 1 ) || !str_is_number( num ) ) {
3150 msi_free( num );
3151 return;
3154 ZeroMemory( &lvc, sizeof(lvc) );
3155 lvc.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM;
3156 lvc.cx = atolW( num );
3157 lvc.pszText = msi_dialog_get_uitext( dialog, column_keys[count] );
3159 SendMessageW( control->hwnd, LVM_INSERTCOLUMNW, count++, (LPARAM)&lvc );
3160 msi_free( lvc.pszText );
3161 msi_free( num );
3165 static LONGLONG msi_vcl_get_cost( msi_dialog *dialog )
3167 MSIFEATURE *feature;
3168 INT each_cost;
3169 LONGLONG total_cost = 0;
3171 LIST_FOR_EACH_ENTRY( feature, &dialog->package->features, MSIFEATURE, entry )
3173 if (ERROR_SUCCESS == (MSI_GetFeatureCost(dialog->package, feature,
3174 MSICOSTTREE_SELFONLY, INSTALLSTATE_LOCAL, &each_cost)))
3176 /* each_cost is in 512-byte units */
3177 total_cost += each_cost * 512;
3179 if (ERROR_SUCCESS == (MSI_GetFeatureCost(dialog->package, feature,
3180 MSICOSTTREE_SELFONLY, INSTALLSTATE_ABSENT, &each_cost)))
3182 /* each_cost is in 512-byte units */
3183 total_cost -= each_cost * 512;
3186 return total_cost;
3189 static void msi_dialog_vcl_add_drives( msi_dialog *dialog, msi_control *control )
3191 ULARGE_INTEGER total, free;
3192 LONGLONG difference, cost;
3193 WCHAR size_text[MAX_PATH];
3194 WCHAR cost_text[MAX_PATH];
3195 LPWSTR drives, ptr;
3196 LVITEMW lvitem;
3197 DWORD size;
3198 int i = 0;
3200 cost = msi_vcl_get_cost(dialog);
3201 StrFormatByteSizeW(cost, cost_text, MAX_PATH);
3203 size = GetLogicalDriveStringsW( 0, NULL );
3204 if ( !size ) return;
3206 drives = msi_alloc( (size + 1) * sizeof(WCHAR) );
3207 if ( !drives ) return;
3209 GetLogicalDriveStringsW( size, drives );
3211 ptr = drives;
3212 while (*ptr)
3214 lvitem.mask = LVIF_TEXT;
3215 lvitem.iItem = i;
3216 lvitem.iSubItem = 0;
3217 lvitem.pszText = ptr;
3218 lvitem.cchTextMax = lstrlenW(ptr) + 1;
3219 SendMessageW( control->hwnd, LVM_INSERTITEMW, 0, (LPARAM)&lvitem );
3221 GetDiskFreeSpaceExW(ptr, &free, &total, NULL);
3222 difference = free.QuadPart - cost;
3224 StrFormatByteSizeW(total.QuadPart, size_text, MAX_PATH);
3225 lvitem.iSubItem = 1;
3226 lvitem.pszText = size_text;
3227 lvitem.cchTextMax = lstrlenW(size_text) + 1;
3228 SendMessageW( control->hwnd, LVM_SETITEMW, 0, (LPARAM)&lvitem );
3230 StrFormatByteSizeW(free.QuadPart, size_text, MAX_PATH);
3231 lvitem.iSubItem = 2;
3232 lvitem.pszText = size_text;
3233 lvitem.cchTextMax = lstrlenW(size_text) + 1;
3234 SendMessageW( control->hwnd, LVM_SETITEMW, 0, (LPARAM)&lvitem );
3236 lvitem.iSubItem = 3;
3237 lvitem.pszText = cost_text;
3238 lvitem.cchTextMax = lstrlenW(cost_text) + 1;
3239 SendMessageW( control->hwnd, LVM_SETITEMW, 0, (LPARAM)&lvitem );
3241 StrFormatByteSizeW(difference, size_text, MAX_PATH);
3242 lvitem.iSubItem = 4;
3243 lvitem.pszText = size_text;
3244 lvitem.cchTextMax = lstrlenW(size_text) + 1;
3245 SendMessageW( control->hwnd, LVM_SETITEMW, 0, (LPARAM)&lvitem );
3247 ptr += lstrlenW(ptr) + 1;
3248 i++;
3251 msi_free( drives );
3254 static UINT msi_dialog_volumecost_list( msi_dialog *dialog, MSIRECORD *rec )
3256 msi_control *control;
3257 DWORD style;
3259 style = LVS_REPORT | WS_VSCROLL | WS_HSCROLL | LVS_SHAREIMAGELISTS |
3260 LVS_AUTOARRANGE | LVS_SINGLESEL | WS_BORDER |
3261 WS_CHILD | WS_TABSTOP | WS_GROUP;
3262 control = msi_dialog_add_control( dialog, rec, WC_LISTVIEWW, style );
3263 if (!control)
3264 return ERROR_FUNCTION_FAILED;
3266 msi_dialog_vcl_add_columns( dialog, control, rec );
3267 msi_dialog_vcl_add_drives( dialog, control );
3269 return ERROR_SUCCESS;
3272 /******************** VolumeSelect Combo ***************************************/
3274 static UINT msi_dialog_volsel_handler( msi_dialog *dialog,
3275 msi_control *control, WPARAM param )
3277 WCHAR text[MAX_PATH];
3278 LPWSTR prop;
3279 BOOL indirect;
3280 int index;
3282 if (HIWORD(param) != CBN_SELCHANGE)
3283 return ERROR_SUCCESS;
3285 index = SendMessageW( control->hwnd, CB_GETCURSEL, 0, 0 );
3286 if ( index == CB_ERR )
3288 ERR("No ComboBox item selected!\n");
3289 return ERROR_FUNCTION_FAILED;
3292 SendMessageW( control->hwnd, CB_GETLBTEXT, index, (LPARAM)text );
3294 indirect = control->attributes & msidbControlAttributesIndirect;
3295 prop = msi_dialog_dup_property( dialog, control->property, indirect );
3297 msi_dialog_set_property( dialog->package, prop, text );
3299 msi_free( prop );
3300 return ERROR_SUCCESS;
3303 static void msi_dialog_vsc_add_drives( msi_dialog *dialog, msi_control *control )
3305 LPWSTR drives, ptr;
3306 DWORD size;
3308 size = GetLogicalDriveStringsW( 0, NULL );
3309 if ( !size ) return;
3311 drives = msi_alloc( (size + 1) * sizeof(WCHAR) );
3312 if ( !drives ) return;
3314 GetLogicalDriveStringsW( size, drives );
3316 ptr = drives;
3317 while (*ptr)
3319 SendMessageW( control->hwnd, CB_ADDSTRING, 0, (LPARAM)ptr );
3320 ptr += lstrlenW(ptr) + 1;
3323 msi_free( drives );
3326 static UINT msi_dialog_volumeselect_combo( msi_dialog *dialog, MSIRECORD *rec )
3328 msi_control *control;
3329 LPCWSTR prop;
3330 DWORD style;
3332 /* FIXME: CBS_OWNERDRAWFIXED */
3333 style = WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP |
3334 CBS_DROPDOWNLIST | CBS_SORT | CBS_HASSTRINGS |
3335 WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR;
3336 control = msi_dialog_add_control( dialog, rec, WC_COMBOBOXW, style );
3337 if (!control)
3338 return ERROR_FUNCTION_FAILED;
3340 control->attributes = MSI_RecordGetInteger( rec, 8 );
3341 control->handler = msi_dialog_volsel_handler;
3342 prop = MSI_RecordGetString( rec, 9 );
3343 control->property = msi_dialog_dup_property( dialog, prop, FALSE );
3345 msi_dialog_vsc_add_drives( dialog, control );
3347 return ERROR_SUCCESS;
3350 static UINT msi_dialog_hyperlink_handler( msi_dialog *dialog, msi_control *control, WPARAM param )
3352 static const WCHAR hrefW[] = {'h','r','e','f'};
3353 static const WCHAR openW[] = {'o','p','e','n',0};
3354 int len, len_href = sizeof(hrefW) / sizeof(hrefW[0]);
3355 const WCHAR *p, *q;
3356 WCHAR quote = 0;
3357 LITEM item;
3359 item.mask = LIF_ITEMINDEX | LIF_URL;
3360 item.iLink = 0;
3361 item.szUrl[0] = 0;
3363 SendMessageW( control->hwnd, LM_GETITEM, 0, (LPARAM)&item );
3365 p = item.szUrl;
3366 while (*p && *p != '<') p++;
3367 if (!*p++) return ERROR_SUCCESS;
3368 if (toupperW( *p++ ) != 'A' || !isspaceW( *p++ )) return ERROR_SUCCESS;
3369 while (*p && isspaceW( *p )) p++;
3371 len = strlenW( p );
3372 if (len > len_href && !memicmpW( p, hrefW, len_href ))
3374 p += len_href;
3375 while (*p && isspaceW( *p )) p++;
3376 if (!*p || *p++ != '=') return ERROR_SUCCESS;
3377 while (*p && isspaceW( *p )) p++;
3379 if (*p == '\"' || *p == '\'') quote = *p++;
3380 q = p;
3381 if (quote)
3383 while (*q && *q != quote) q++;
3384 if (*q != quote) return ERROR_SUCCESS;
3386 else
3388 while (*q && *q != '>' && !isspaceW( *q )) q++;
3389 if (!*q) return ERROR_SUCCESS;
3391 item.szUrl[q - item.szUrl] = 0;
3392 ShellExecuteW( NULL, openW, p, NULL, NULL, SW_SHOWNORMAL );
3394 return ERROR_SUCCESS;
3397 static UINT msi_dialog_hyperlink( msi_dialog *dialog, MSIRECORD *rec )
3399 msi_control *control;
3400 DWORD style = WS_CHILD | WS_TABSTOP | WS_GROUP;
3401 const WCHAR *text = MSI_RecordGetString( rec, 10 );
3402 int len = strlenW( text );
3403 LITEM item;
3405 control = msi_dialog_add_control( dialog, rec, WC_LINK, style );
3406 if (!control)
3407 return ERROR_FUNCTION_FAILED;
3409 control->attributes = MSI_RecordGetInteger( rec, 8 );
3410 control->handler = msi_dialog_hyperlink_handler;
3412 item.mask = LIF_ITEMINDEX | LIF_STATE | LIF_URL;
3413 item.iLink = 0;
3414 item.state = LIS_ENABLED;
3415 item.stateMask = LIS_ENABLED;
3416 if (len < L_MAX_URL_LENGTH) strcpyW( item.szUrl, text );
3417 else item.szUrl[0] = 0;
3419 SendMessageW( control->hwnd, LM_SETITEM, 0, (LPARAM)&item );
3421 return ERROR_SUCCESS;
3424 static const struct control_handler msi_dialog_handler[] =
3426 { szText, msi_dialog_text_control },
3427 { szPushButton, msi_dialog_button_control },
3428 { szLine, msi_dialog_line_control },
3429 { szBitmap, msi_dialog_bitmap_control },
3430 { szCheckBox, msi_dialog_checkbox_control },
3431 { szScrollableText, msi_dialog_scrolltext_control },
3432 { szComboBox, msi_dialog_combo_control },
3433 { szEdit, msi_dialog_edit_control },
3434 { szMaskedEdit, msi_dialog_maskedit_control },
3435 { szPathEdit, msi_dialog_pathedit_control },
3436 { szProgressBar, msi_dialog_progress_bar },
3437 { szRadioButtonGroup, msi_dialog_radiogroup_control },
3438 { szIcon, msi_dialog_icon_control },
3439 { szSelectionTree, msi_dialog_selection_tree },
3440 { szGroupBox, msi_dialog_group_box },
3441 { szListBox, msi_dialog_list_box },
3442 { szDirectoryCombo, msi_dialog_directory_combo },
3443 { szDirectoryList, msi_dialog_directory_list },
3444 { szVolumeCostList, msi_dialog_volumecost_list },
3445 { szVolumeSelectCombo, msi_dialog_volumeselect_combo },
3446 { szHyperLink, msi_dialog_hyperlink }
3449 #define NUM_CONTROL_TYPES (sizeof msi_dialog_handler/sizeof msi_dialog_handler[0])
3451 static UINT msi_dialog_create_controls( MSIRECORD *rec, LPVOID param )
3453 msi_dialog *dialog = param;
3454 LPCWSTR control_type;
3455 UINT i;
3457 /* find and call the function that can create this type of control */
3458 control_type = MSI_RecordGetString( rec, 3 );
3459 for( i=0; i<NUM_CONTROL_TYPES; i++ )
3460 if (!strcmpiW( msi_dialog_handler[i].control_type, control_type ))
3461 break;
3462 if( i != NUM_CONTROL_TYPES )
3463 msi_dialog_handler[i].func( dialog, rec );
3464 else
3465 ERR("no handler for element type %s\n", debugstr_w(control_type));
3467 return ERROR_SUCCESS;
3470 static UINT msi_dialog_fill_controls( msi_dialog *dialog )
3472 static const WCHAR query[] = {
3473 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
3474 'C','o','n','t','r','o','l',' ','W','H','E','R','E',' ',
3475 '`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',0};
3476 UINT r;
3477 MSIQUERY *view;
3478 MSIPACKAGE *package = dialog->package;
3480 TRACE("%p %s\n", dialog, debugstr_w(dialog->name) );
3482 /* query the Control table for all the elements of the control */
3483 r = MSI_OpenQuery( package->db, &view, query, dialog->name );
3484 if( r != ERROR_SUCCESS )
3486 ERR("query failed for dialog %s\n", debugstr_w(dialog->name));
3487 return ERROR_INVALID_PARAMETER;
3490 r = MSI_IterateRecords( view, 0, msi_dialog_create_controls, dialog );
3491 msiobj_release( &view->hdr );
3492 return r;
3495 static UINT msi_dialog_reset( msi_dialog *dialog )
3497 /* FIXME: should restore the original values of any properties we changed */
3498 return msi_dialog_evaluate_control_conditions( dialog );
3501 /* figure out the height of 10 point MS Sans Serif */
3502 static INT msi_dialog_get_sans_serif_height( HWND hwnd )
3504 static const WCHAR szSansSerif[] = {
3505 'M','S',' ','S','a','n','s',' ','S','e','r','i','f',0 };
3506 LOGFONTW lf;
3507 TEXTMETRICW tm;
3508 BOOL r;
3509 LONG height = 0;
3510 HFONT hFont, hOldFont;
3511 HDC hdc;
3513 hdc = GetDC( hwnd );
3514 if (hdc)
3516 memset( &lf, 0, sizeof lf );
3517 lf.lfHeight = MulDiv(12, GetDeviceCaps(hdc, LOGPIXELSY), 72);
3518 strcpyW( lf.lfFaceName, szSansSerif );
3519 hFont = CreateFontIndirectW(&lf);
3520 if (hFont)
3522 hOldFont = SelectObject( hdc, hFont );
3523 r = GetTextMetricsW( hdc, &tm );
3524 if (r)
3525 height = tm.tmHeight;
3526 SelectObject( hdc, hOldFont );
3527 DeleteObject( hFont );
3529 ReleaseDC( hwnd, hdc );
3531 return height;
3534 /* fetch the associated record from the Dialog table */
3535 static MSIRECORD *msi_get_dialog_record( msi_dialog *dialog )
3537 static const WCHAR query[] = {
3538 'S','E','L','E','C','T',' ','*',' ',
3539 'F','R','O','M',' ','D','i','a','l','o','g',' ',
3540 'W','H','E','R','E',' ',
3541 '`','D','i','a','l','o','g','`',' ','=',' ','\'','%','s','\'',0};
3542 MSIPACKAGE *package = dialog->package;
3543 MSIRECORD *rec = NULL;
3545 TRACE("%p %s\n", dialog, debugstr_w(dialog->name) );
3547 rec = MSI_QueryGetRecord( package->db, query, dialog->name );
3548 if( !rec )
3549 WARN("query failed for dialog %s\n", debugstr_w(dialog->name));
3551 return rec;
3554 static void msi_dialog_adjust_dialog_pos( msi_dialog *dialog, MSIRECORD *rec, LPRECT pos )
3556 static const WCHAR szScreenX[] = {'S','c','r','e','e','n','X',0};
3557 static const WCHAR szScreenY[] = {'S','c','r','e','e','n','Y',0};
3559 UINT xres, yres;
3560 POINT center;
3561 SIZE sz;
3562 LONG style;
3564 center.x = MSI_RecordGetInteger( rec, 2 );
3565 center.y = MSI_RecordGetInteger( rec, 3 );
3567 sz.cx = MSI_RecordGetInteger( rec, 4 );
3568 sz.cy = MSI_RecordGetInteger( rec, 5 );
3570 sz.cx = msi_dialog_scale_unit( dialog, sz.cx );
3571 sz.cy = msi_dialog_scale_unit( dialog, sz.cy );
3573 xres = msi_get_property_int( dialog->package->db, szScreenX, 0 );
3574 yres = msi_get_property_int( dialog->package->db, szScreenY, 0 );
3576 center.x = MulDiv( center.x, xres, 100 );
3577 center.y = MulDiv( center.y, yres, 100 );
3579 /* turn the client pos into the window rectangle */
3580 if (dialog->package->center_x && dialog->package->center_y)
3582 pos->left = dialog->package->center_x - sz.cx / 2.0;
3583 pos->right = pos->left + sz.cx;
3584 pos->top = dialog->package->center_y - sz.cy / 2.0;
3585 pos->bottom = pos->top + sz.cy;
3587 else
3589 pos->left = center.x - sz.cx/2;
3590 pos->right = pos->left + sz.cx;
3591 pos->top = center.y - sz.cy/2;
3592 pos->bottom = pos->top + sz.cy;
3594 /* save the center */
3595 dialog->package->center_x = center.x;
3596 dialog->package->center_y = center.y;
3599 dialog->size.cx = sz.cx;
3600 dialog->size.cy = sz.cy;
3602 TRACE("%u %u %u %u\n", pos->left, pos->top, pos->right, pos->bottom);
3604 style = GetWindowLongPtrW( dialog->hwnd, GWL_STYLE );
3605 AdjustWindowRect( pos, style, FALSE );
3608 static void msi_dialog_set_tab_order( msi_dialog *dialog, LPCWSTR first )
3610 struct list tab_chain;
3611 msi_control *control;
3612 HWND prev = HWND_TOP;
3614 list_init( &tab_chain );
3615 if (!(control = msi_dialog_find_control( dialog, first ))) return;
3617 dialog->hWndFocus = control->hwnd;
3618 while (control)
3620 list_remove( &control->entry );
3621 list_add_tail( &tab_chain, &control->entry );
3622 if (!control->tabnext) break;
3623 control = msi_dialog_find_control( dialog, control->tabnext );
3626 LIST_FOR_EACH_ENTRY( control, &tab_chain, msi_control, entry )
3628 SetWindowPos( control->hwnd, prev, 0, 0, 0, 0,
3629 SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOREDRAW |
3630 SWP_NOREPOSITION | SWP_NOSENDCHANGING | SWP_NOSIZE );
3631 prev = control->hwnd;
3634 /* put them back on the main list */
3635 list_move_head( &dialog->controls, &tab_chain );
3638 static LRESULT msi_dialog_oncreate( HWND hwnd, LPCREATESTRUCTW cs )
3640 static const WCHAR df[] = {
3641 'D','e','f','a','u','l','t','U','I','F','o','n','t',0 };
3642 static const WCHAR dfv[] = {
3643 'M','S',' ','S','h','e','l','l',' ','D','l','g',0 };
3644 msi_dialog *dialog = cs->lpCreateParams;
3645 MSIRECORD *rec = NULL;
3646 LPWSTR title = NULL;
3647 RECT pos;
3649 TRACE("%p %p\n", dialog, dialog->package);
3651 dialog->hwnd = hwnd;
3652 SetWindowLongPtrW( hwnd, GWLP_USERDATA, (LONG_PTR) dialog );
3654 rec = msi_get_dialog_record( dialog );
3655 if( !rec )
3657 TRACE("No record found for dialog %s\n", debugstr_w(dialog->name));
3658 return -1;
3661 dialog->scale = msi_dialog_get_sans_serif_height(dialog->hwnd);
3663 msi_dialog_adjust_dialog_pos( dialog, rec, &pos );
3665 dialog->attributes = MSI_RecordGetInteger( rec, 6 );
3667 dialog->default_font = msi_dup_property( dialog->package->db, df );
3668 if (!dialog->default_font)
3670 dialog->default_font = strdupW(dfv);
3671 msiobj_release( &rec->hdr );
3672 if (!dialog->default_font) return -1;
3675 title = msi_get_deformatted_field( dialog->package, rec, 7 );
3676 SetWindowTextW( hwnd, title );
3677 msi_free( title );
3679 SetWindowPos( hwnd, 0, pos.left, pos.top,
3680 pos.right - pos.left, pos.bottom - pos.top,
3681 SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOREDRAW );
3683 msi_dialog_build_font_list( dialog );
3684 msi_dialog_fill_controls( dialog );
3685 msi_dialog_evaluate_control_conditions( dialog );
3686 msi_dialog_set_tab_order( dialog, MSI_RecordGetString( rec, 8 ) );
3687 msiobj_release( &rec->hdr );
3689 return 0;
3692 static LRESULT msi_dialog_oncommand( msi_dialog *dialog, WPARAM param, HWND hwnd )
3694 msi_control *control = NULL;
3696 TRACE("%p %p %08lx\n", dialog, hwnd, param);
3698 switch (param)
3700 case 1: /* enter */
3701 control = msi_dialog_find_control( dialog, dialog->control_default );
3702 break;
3703 case 2: /* escape */
3704 control = msi_dialog_find_control( dialog, dialog->control_cancel );
3705 break;
3706 default:
3707 control = msi_dialog_find_control_by_hwnd( dialog, hwnd );
3710 if( control )
3712 if( control->handler )
3714 control->handler( dialog, control, param );
3715 msi_dialog_evaluate_control_conditions( dialog );
3719 return 0;
3722 static LRESULT msi_dialog_onnotify( msi_dialog *dialog, LPARAM param )
3724 LPNMHDR nmhdr = (LPNMHDR) param;
3725 msi_control *control = msi_dialog_find_control_by_hwnd( dialog, nmhdr->hwndFrom );
3727 TRACE("%p %p\n", dialog, nmhdr->hwndFrom);
3729 if ( control && control->handler )
3730 control->handler( dialog, control, param );
3732 return 0;
3735 static void dialog_setfocus( msi_dialog *dialog )
3737 HWND hwnd = dialog->hWndFocus;
3739 hwnd = GetNextDlgTabItem( dialog->hwnd, hwnd, TRUE);
3740 hwnd = GetNextDlgTabItem( dialog->hwnd, hwnd, FALSE);
3741 SetFocus( hwnd );
3742 dialog->hWndFocus = hwnd;
3745 static LRESULT WINAPI MSIDialog_WndProc( HWND hwnd, UINT msg,
3746 WPARAM wParam, LPARAM lParam )
3748 msi_dialog *dialog = (LPVOID) GetWindowLongPtrW( hwnd, GWLP_USERDATA );
3750 TRACE("0x%04x\n", msg);
3752 switch (msg)
3754 case WM_MOVE:
3755 dialog->package->center_x = LOWORD(lParam) + dialog->size.cx / 2.0;
3756 dialog->package->center_y = HIWORD(lParam) + dialog->size.cy / 2.0;
3757 break;
3759 case WM_CREATE:
3760 return msi_dialog_oncreate( hwnd, (LPCREATESTRUCTW)lParam );
3762 case WM_COMMAND:
3763 return msi_dialog_oncommand( dialog, wParam, (HWND)lParam );
3765 case WM_ACTIVATE:
3766 if( LOWORD(wParam) == WA_INACTIVE )
3767 dialog->hWndFocus = GetFocus();
3768 else
3769 dialog_setfocus( dialog );
3770 return 0;
3772 case WM_SETFOCUS:
3773 dialog_setfocus( dialog );
3774 return 0;
3776 /* bounce back to our subclassed static control */
3777 case WM_CTLCOLORSTATIC:
3778 return SendMessageW( (HWND) lParam, WM_CTLCOLORSTATIC, wParam, lParam );
3780 case WM_DESTROY:
3781 dialog->hwnd = NULL;
3782 return 0;
3783 case WM_NOTIFY:
3784 return msi_dialog_onnotify( dialog, lParam );
3786 return DefWindowProcW(hwnd, msg, wParam, lParam);
3789 static void process_pending_messages( HWND hdlg )
3791 MSG msg;
3793 while (PeekMessageW( &msg, 0, 0, 0, PM_REMOVE ))
3795 if (hdlg && IsDialogMessageW( hdlg, &msg )) continue;
3796 TranslateMessage( &msg );
3797 DispatchMessageW( &msg );
3801 static UINT dialog_run_message_loop( msi_dialog *dialog )
3803 DWORD style;
3804 HWND hwnd;
3806 if( uiThreadId != GetCurrentThreadId() )
3807 return SendMessageW( hMsiHiddenWindow, WM_MSI_DIALOG_CREATE, 0, (LPARAM) dialog );
3809 /* create the dialog window, don't show it yet */
3810 style = WS_OVERLAPPED;
3811 if( dialog->attributes & msidbDialogAttributesVisible )
3812 style |= WS_VISIBLE;
3814 hwnd = CreateWindowW( szMsiDialogClass, dialog->name, style,
3815 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
3816 NULL, NULL, NULL, dialog );
3817 if( !hwnd )
3819 ERR("Failed to create dialog %s\n", debugstr_w( dialog->name ));
3820 return ERROR_FUNCTION_FAILED;
3823 ShowWindow( hwnd, SW_SHOW );
3824 /* UpdateWindow( hwnd ); - and causes the transparent static controls not to paint */
3826 if( dialog->attributes & msidbDialogAttributesModal )
3828 while( !dialog->finished )
3830 MsgWaitForMultipleObjects( 0, NULL, 0, INFINITE, QS_ALLINPUT );
3831 process_pending_messages( dialog->hwnd );
3834 else
3835 return ERROR_IO_PENDING;
3837 return ERROR_SUCCESS;
3840 static LRESULT WINAPI MSIHiddenWindowProc( HWND hwnd, UINT msg,
3841 WPARAM wParam, LPARAM lParam )
3843 msi_dialog *dialog = (msi_dialog*) lParam;
3845 TRACE("%d %p\n", msg, dialog);
3847 switch (msg)
3849 case WM_MSI_DIALOG_CREATE:
3850 return dialog_run_message_loop( dialog );
3851 case WM_MSI_DIALOG_DESTROY:
3852 msi_dialog_destroy( dialog );
3853 return 0;
3855 return DefWindowProcW( hwnd, msg, wParam, lParam );
3858 static BOOL dialog_register_class( void )
3860 WNDCLASSW cls;
3862 ZeroMemory( &cls, sizeof cls );
3863 cls.lpfnWndProc = MSIDialog_WndProc;
3864 cls.hInstance = NULL;
3865 cls.hIcon = LoadIconW(0, (LPWSTR)IDI_APPLICATION);
3866 cls.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
3867 cls.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
3868 cls.lpszMenuName = NULL;
3869 cls.lpszClassName = szMsiDialogClass;
3871 if( !RegisterClassW( &cls ) )
3872 return FALSE;
3874 cls.lpfnWndProc = MSIHiddenWindowProc;
3875 cls.lpszClassName = szMsiHiddenWindow;
3877 if( !RegisterClassW( &cls ) )
3878 return FALSE;
3880 uiThreadId = GetCurrentThreadId();
3882 hMsiHiddenWindow = CreateWindowW( szMsiHiddenWindow, NULL, WS_OVERLAPPED,
3883 0, 0, 100, 100, NULL, NULL, NULL, NULL );
3884 if( !hMsiHiddenWindow )
3885 return FALSE;
3887 return TRUE;
3890 static msi_dialog *dialog_create( MSIPACKAGE *package, const WCHAR *name, msi_dialog *parent,
3891 control_event_handler event_handler )
3893 MSIRECORD *rec = NULL;
3894 msi_dialog *dialog;
3896 TRACE("%s\n", debugstr_w(name));
3898 if (!hMsiHiddenWindow) dialog_register_class();
3900 /* allocate the structure for the dialog to use */
3901 dialog = msi_alloc_zero( FIELD_OFFSET( msi_dialog, name[strlenW( name ) + 1] ));
3902 if( !dialog )
3903 return NULL;
3904 strcpyW( dialog->name, name );
3905 dialog->parent = parent;
3906 msiobj_addref( &package->hdr );
3907 dialog->package = package;
3908 dialog->event_handler = event_handler;
3909 dialog->finished = 0;
3910 list_init( &dialog->controls );
3911 list_init( &dialog->fonts );
3913 /* verify that the dialog exists */
3914 rec = msi_get_dialog_record( dialog );
3915 if( !rec )
3917 msiobj_release( &package->hdr );
3918 msi_free( dialog );
3919 return NULL;
3921 dialog->attributes = MSI_RecordGetInteger( rec, 6 );
3922 dialog->control_default = strdupW( MSI_RecordGetString( rec, 9 ) );
3923 dialog->control_cancel = strdupW( MSI_RecordGetString( rec, 10 ) );
3924 msiobj_release( &rec->hdr );
3926 return dialog;
3929 static void msi_dialog_end_dialog( msi_dialog *dialog )
3931 TRACE("%p\n", dialog);
3932 dialog->finished = 1;
3933 PostMessageW(dialog->hwnd, WM_NULL, 0, 0);
3936 void msi_dialog_check_messages( HANDLE handle )
3938 DWORD r;
3940 /* in threads other than the UI thread, block */
3941 if( uiThreadId != GetCurrentThreadId() )
3943 if (!handle) return;
3944 while (MsgWaitForMultipleObjectsEx( 1, &handle, INFINITE, QS_ALLINPUT, 0 ) == WAIT_OBJECT_0 + 1)
3946 MSG msg;
3947 while (PeekMessageW( &msg, NULL, 0, 0, PM_REMOVE ))
3949 TranslateMessage( &msg );
3950 DispatchMessageW( &msg );
3953 return;
3956 /* there's two choices for the UI thread */
3957 while (1)
3959 process_pending_messages( NULL );
3961 if( !handle )
3962 break;
3965 * block here until somebody creates a new dialog or
3966 * the handle we're waiting on becomes ready
3968 r = MsgWaitForMultipleObjects( 1, &handle, 0, INFINITE, QS_ALLINPUT );
3969 if( r == WAIT_OBJECT_0 )
3970 break;
3974 static void dialog_do_preview( msi_dialog *dialog )
3976 TRACE("\n");
3977 dialog->attributes |= msidbDialogAttributesVisible;
3978 dialog->attributes &= ~msidbDialogAttributesModal;
3979 dialog_run_message_loop( dialog );
3982 static void free_subscriber( struct subscriber *sub )
3984 msi_free( sub->event );
3985 msi_free( sub->control );
3986 msi_free( sub->attribute );
3987 msi_free( sub );
3990 static void event_cleanup_subscriptions( MSIPACKAGE *package, const WCHAR *dialog )
3992 struct list *item, *next;
3994 LIST_FOR_EACH_SAFE( item, next, &package->subscriptions )
3996 struct subscriber *sub = LIST_ENTRY( item, struct subscriber, entry );
3998 if (strcmpW( sub->dialog->name, dialog )) continue;
3999 list_remove( &sub->entry );
4000 free_subscriber( sub );
4004 void msi_dialog_destroy( msi_dialog *dialog )
4006 msi_font *font, *next;
4008 if( uiThreadId != GetCurrentThreadId() )
4010 SendMessageW( hMsiHiddenWindow, WM_MSI_DIALOG_DESTROY, 0, (LPARAM) dialog );
4011 return;
4014 if( dialog->hwnd )
4015 ShowWindow( dialog->hwnd, SW_HIDE );
4017 if( dialog->hwnd )
4018 DestroyWindow( dialog->hwnd );
4020 /* unsubscribe events */
4021 event_cleanup_subscriptions( dialog->package, dialog->name );
4023 /* destroy the list of controls */
4024 while( !list_empty( &dialog->controls ) )
4026 msi_control *t;
4028 t = LIST_ENTRY( list_head( &dialog->controls ),
4029 msi_control, entry );
4030 msi_destroy_control( t );
4033 /* destroy the list of fonts */
4034 LIST_FOR_EACH_ENTRY_SAFE( font, next, &dialog->fonts, msi_font, entry )
4036 list_remove( &font->entry );
4037 DeleteObject( font->hfont );
4038 msi_free( font );
4040 msi_free( dialog->default_font );
4042 msi_free( dialog->control_default );
4043 msi_free( dialog->control_cancel );
4044 msiobj_release( &dialog->package->hdr );
4045 dialog->package = NULL;
4046 msi_free( dialog );
4049 void msi_dialog_unregister_class( void )
4051 DestroyWindow( hMsiHiddenWindow );
4052 hMsiHiddenWindow = NULL;
4053 UnregisterClassW( szMsiDialogClass, NULL );
4054 UnregisterClassW( szMsiHiddenWindow, NULL );
4055 uiThreadId = 0;
4058 void msi_event_cleanup_all_subscriptions( MSIPACKAGE *package )
4060 struct list *item, *next;
4062 LIST_FOR_EACH_SAFE( item, next, &package->subscriptions )
4064 struct subscriber *sub = LIST_ENTRY( item, struct subscriber, entry );
4065 list_remove( &sub->entry );
4066 free_subscriber( sub );
4070 static UINT error_dialog_handler( msi_dialog *dialog, const WCHAR *event, const WCHAR *argument )
4072 static const WCHAR end_dialog[] = {'E','n','d','D','i','a','l','o','g',0};
4073 static const WCHAR error_abort[] = {'E','r','r','o','r','A','b','o','r','t',0};
4074 static const WCHAR error_cancel[] = {'E','r','r','o','r','C','a','n','c','e','l',0};
4075 static const WCHAR error_no[] = {'E','r','r','o','r','N','o',0};
4076 static const WCHAR result_prop[] = {
4077 'M','S','I','E','r','r','o','r','D','i','a','l','o','g','R','e','s','u','l','t',0
4080 if ( strcmpW( event, end_dialog ) )
4081 return ERROR_SUCCESS;
4083 if ( !strcmpW( argument, error_abort ) || !strcmpW( argument, error_cancel ) ||
4084 !strcmpW( argument, error_no ) )
4086 msi_set_property( dialog->package->db, result_prop, error_abort, -1 );
4089 msi_event_cleanup_all_subscriptions( dialog->package );
4090 msi_dialog_end_dialog( dialog );
4092 return ERROR_SUCCESS;
4095 static UINT msi_error_dialog_set_error( MSIPACKAGE *package, LPWSTR error_dialog, LPWSTR error )
4097 MSIRECORD * row;
4099 static const WCHAR update[] =
4100 {'U','P','D','A','T','E',' ','`','C','o','n','t','r','o','l','`',' ',
4101 'S','E','T',' ','`','T','e','x','t','`',' ','=',' ','\'','%','s','\'',' ',
4102 'W','H','E','R','E', ' ','`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',' ',
4103 'A','N','D',' ','`','C','o','n','t','r','o','l','`',' ','=',' ',
4104 '\'','E','r','r','o','r','T','e','x','t','\'',0};
4106 row = MSI_QueryGetRecord( package->db, update, error, error_dialog );
4107 if (!row)
4108 return ERROR_FUNCTION_FAILED;
4110 msiobj_release(&row->hdr);
4111 return ERROR_SUCCESS;
4114 UINT msi_spawn_error_dialog( MSIPACKAGE *package, LPWSTR error_dialog, LPWSTR error )
4116 msi_dialog *dialog;
4117 WCHAR result[MAX_PATH];
4118 UINT r = ERROR_SUCCESS;
4119 DWORD size = MAX_PATH;
4120 int res;
4122 static const WCHAR pn_prop[] = {'P','r','o','d','u','c','t','N','a','m','e',0};
4123 static const WCHAR title_fmt[] = {'%','s',' ','W','a','r','n','i','n','g',0};
4124 static const WCHAR error_abort[] = {'E','r','r','o','r','A','b','o','r','t',0};
4125 static const WCHAR result_prop[] = {
4126 'M','S','I','E','r','r','o','r','D','i','a','l','o','g','R','e','s','u','l','t',0
4129 if ((package->ui_level & INSTALLUILEVEL_MASK) == INSTALLUILEVEL_NONE) return ERROR_SUCCESS;
4131 if ( !error_dialog )
4133 LPWSTR product_name = msi_dup_property( package->db, pn_prop );
4134 WCHAR title[MAX_PATH];
4136 sprintfW( title, title_fmt, product_name );
4137 res = MessageBoxW( NULL, error, title, MB_OKCANCEL | MB_ICONWARNING );
4139 msi_free( product_name );
4141 if ( res == IDOK )
4142 return ERROR_SUCCESS;
4143 else
4144 return ERROR_FUNCTION_FAILED;
4147 r = msi_error_dialog_set_error( package, error_dialog, error );
4148 if ( r != ERROR_SUCCESS )
4149 return r;
4151 dialog = dialog_create( package, error_dialog, package->dialog, error_dialog_handler );
4152 if ( !dialog )
4153 return ERROR_FUNCTION_FAILED;
4155 dialog->finished = FALSE;
4156 r = dialog_run_message_loop( dialog );
4157 if ( r != ERROR_SUCCESS )
4158 goto done;
4160 r = msi_get_property( package->db, result_prop, result, &size );
4161 if ( r != ERROR_SUCCESS)
4162 r = ERROR_SUCCESS;
4164 if ( !strcmpW( result, error_abort ) )
4165 r = ERROR_FUNCTION_FAILED;
4167 done:
4168 msi_dialog_destroy( dialog );
4170 return r;
4173 static void MSI_ClosePreview( MSIOBJECTHDR *arg )
4175 MSIPREVIEW *preview = (MSIPREVIEW *)arg;
4176 msiobj_release( &preview->package->hdr );
4179 static MSIPREVIEW *MSI_EnableUIPreview( MSIDATABASE *db )
4181 MSIPREVIEW *preview = NULL;
4182 MSIPACKAGE *package;
4184 package = MSI_CreatePackage( db, NULL );
4185 if (package)
4187 preview = alloc_msiobject( MSIHANDLETYPE_PREVIEW, sizeof(MSIPREVIEW), MSI_ClosePreview );
4188 if (preview)
4190 preview->package = package;
4191 msiobj_addref( &package->hdr );
4193 msiobj_release( &package->hdr );
4195 return preview;
4198 UINT WINAPI MsiEnableUIPreview( MSIHANDLE hdb, MSIHANDLE *phPreview )
4200 MSIDATABASE *db;
4201 MSIPREVIEW *preview;
4202 UINT r = ERROR_FUNCTION_FAILED;
4204 TRACE("%d %p\n", hdb, phPreview);
4206 db = msihandle2msiinfo( hdb, MSIHANDLETYPE_DATABASE );
4207 if (!db)
4209 IWineMsiRemoteDatabase *remote_database;
4211 remote_database = (IWineMsiRemoteDatabase *)msi_get_remote( hdb );
4212 if (!remote_database)
4213 return ERROR_INVALID_HANDLE;
4215 *phPreview = 0;
4217 IWineMsiRemoteDatabase_Release( remote_database );
4218 WARN("MsiEnableUIPreview not allowed during a custom action!\n");
4220 return ERROR_FUNCTION_FAILED;
4222 preview = MSI_EnableUIPreview( db );
4223 if (preview)
4225 *phPreview = alloc_msihandle( &preview->hdr );
4226 msiobj_release( &preview->hdr );
4227 r = ERROR_SUCCESS;
4228 if (!*phPreview)
4229 r = ERROR_NOT_ENOUGH_MEMORY;
4231 msiobj_release( &db->hdr );
4232 return r;
4235 static UINT preview_event_handler( msi_dialog *dialog, const WCHAR *event, const WCHAR *argument )
4237 MESSAGE("Preview dialog event '%s' (arg='%s')\n", debugstr_w(event), debugstr_w(argument));
4238 return ERROR_SUCCESS;
4241 static UINT MSI_PreviewDialogW( MSIPREVIEW *preview, LPCWSTR szDialogName )
4243 msi_dialog *dialog = NULL;
4244 UINT r = ERROR_SUCCESS;
4246 if (preview->dialog)
4247 msi_dialog_destroy( preview->dialog );
4249 /* an empty name means we should just destroy the current preview dialog */
4250 if (szDialogName)
4252 dialog = dialog_create( preview->package, szDialogName, NULL, preview_event_handler );
4253 if (dialog)
4254 dialog_do_preview( dialog );
4255 else
4256 r = ERROR_FUNCTION_FAILED;
4258 preview->dialog = dialog;
4259 return r;
4262 UINT WINAPI MsiPreviewDialogW( MSIHANDLE hPreview, LPCWSTR szDialogName )
4264 MSIPREVIEW *preview;
4265 UINT r;
4267 TRACE("%d %s\n", hPreview, debugstr_w(szDialogName));
4269 preview = msihandle2msiinfo( hPreview, MSIHANDLETYPE_PREVIEW );
4270 if (!preview)
4271 return ERROR_INVALID_HANDLE;
4273 r = MSI_PreviewDialogW( preview, szDialogName );
4274 msiobj_release( &preview->hdr );
4275 return r;
4278 UINT WINAPI MsiPreviewDialogA( MSIHANDLE hPreview, LPCSTR szDialogName )
4280 UINT r;
4281 LPWSTR strW = NULL;
4283 TRACE("%d %s\n", hPreview, debugstr_a(szDialogName));
4285 if (szDialogName)
4287 strW = strdupAtoW( szDialogName );
4288 if (!strW)
4289 return ERROR_OUTOFMEMORY;
4291 r = MsiPreviewDialogW( hPreview, strW );
4292 msi_free( strW );
4293 return r;
4296 UINT WINAPI MsiPreviewBillboardW( MSIHANDLE hPreview, LPCWSTR szControlName, LPCWSTR szBillboard )
4298 FIXME("%d %s %s\n", hPreview, debugstr_w(szControlName), debugstr_w(szBillboard));
4299 return ERROR_CALL_NOT_IMPLEMENTED;
4302 UINT WINAPI MsiPreviewBillboardA( MSIHANDLE hPreview, LPCSTR szControlName, LPCSTR szBillboard )
4304 FIXME("%d %s %s\n", hPreview, debugstr_a(szControlName), debugstr_a(szBillboard));
4305 return ERROR_CALL_NOT_IMPLEMENTED;
4308 typedef UINT (*event_handler)( msi_dialog *, const WCHAR * );
4310 struct control_event
4312 const WCHAR *event;
4313 event_handler handler;
4316 static UINT dialog_event_handler( msi_dialog *, const WCHAR *, const WCHAR * );
4318 /* create a dialog box and run it if it's modal */
4319 static UINT event_do_dialog( MSIPACKAGE *package, const WCHAR *name, msi_dialog *parent, BOOL destroy_modeless )
4321 msi_dialog *dialog;
4322 UINT r;
4324 /* create a new dialog */
4325 dialog = dialog_create( package, name, parent, dialog_event_handler );
4326 if (dialog)
4328 /* kill the current modeless dialog */
4329 if (destroy_modeless && package->dialog)
4331 msi_dialog_destroy( package->dialog );
4332 package->dialog = NULL;
4335 /* modeless dialogs return an error message */
4336 r = dialog_run_message_loop( dialog );
4337 if (r == ERROR_SUCCESS)
4338 msi_dialog_destroy( dialog );
4339 else
4340 package->dialog = dialog;
4342 else r = ERROR_FUNCTION_FAILED;
4343 return r;
4346 /* end a modal dialog box */
4347 static UINT event_end_dialog( msi_dialog *dialog, const WCHAR *argument )
4349 static const WCHAR exitW[] = {'E','x','i','t',0};
4350 static const WCHAR retryW[] = {'R','e','t','r','y',0};
4351 static const WCHAR ignoreW[] = {'I','g','n','o','r','e',0};
4352 static const WCHAR returnW[] = {'R','e','t','u','r','n',0};
4354 if (!strcmpW( argument, exitW ))
4355 dialog->package->CurrentInstallState = ERROR_INSTALL_USEREXIT;
4356 else if (!strcmpW( argument, retryW ))
4357 dialog->package->CurrentInstallState = ERROR_INSTALL_SUSPEND;
4358 else if (!strcmpW( argument, ignoreW ))
4359 dialog->package->CurrentInstallState = ERROR_SUCCESS;
4360 else if (!strcmpW( argument, returnW ))
4362 msi_dialog *parent = dialog->parent;
4363 msi_free( dialog->package->next_dialog );
4364 dialog->package->next_dialog = (parent) ? strdupW( parent->name ) : NULL;
4365 dialog->package->CurrentInstallState = ERROR_SUCCESS;
4367 else
4369 ERR("Unknown argument string %s\n", debugstr_w(argument));
4370 dialog->package->CurrentInstallState = ERROR_FUNCTION_FAILED;
4372 event_cleanup_subscriptions( dialog->package, dialog->name );
4373 msi_dialog_end_dialog( dialog );
4374 return ERROR_SUCCESS;
4377 /* transition from one modal dialog to another modal dialog */
4378 static UINT event_new_dialog( msi_dialog *dialog, const WCHAR *argument )
4380 /* store the name of the next dialog, and signal this one to end */
4381 dialog->package->next_dialog = strdupW( argument );
4382 msi_event_cleanup_all_subscriptions( dialog->package );
4383 msi_dialog_end_dialog( dialog );
4384 return ERROR_SUCCESS;
4387 /* create a new child dialog of an existing modal dialog */
4388 static UINT event_spawn_dialog( msi_dialog *dialog, const WCHAR *argument )
4390 /* don't destroy a modeless dialogs that might be our parent */
4391 event_do_dialog( dialog->package, argument, dialog, FALSE );
4392 if (dialog->package->CurrentInstallState != ERROR_SUCCESS) msi_dialog_end_dialog( dialog );
4393 return ERROR_SUCCESS;
4396 /* creates a dialog that remains up for a period of time based on a condition */
4397 static UINT event_spawn_wait_dialog( msi_dialog *dialog, const WCHAR *argument )
4399 FIXME("doing nothing\n");
4400 return ERROR_SUCCESS;
4403 static UINT event_do_action( msi_dialog *dialog, const WCHAR *argument )
4405 ACTION_PerformAction( dialog->package, argument, SCRIPT_NONE );
4406 return ERROR_SUCCESS;
4409 static UINT event_add_local( msi_dialog *dialog, const WCHAR *argument )
4411 MSIFEATURE *feature;
4413 LIST_FOR_EACH_ENTRY( feature, &dialog->package->features, MSIFEATURE, entry )
4415 if (!strcmpW( argument, feature->Feature ) || !strcmpW( argument, szAll ))
4417 if (feature->ActionRequest != INSTALLSTATE_LOCAL)
4418 msi_set_property( dialog->package->db, szPreselected, szOne, -1 );
4419 MSI_SetFeatureStateW( dialog->package, feature->Feature, INSTALLSTATE_LOCAL );
4422 return ERROR_SUCCESS;
4425 static UINT event_remove( msi_dialog *dialog, const WCHAR *argument )
4427 MSIFEATURE *feature;
4429 LIST_FOR_EACH_ENTRY( feature, &dialog->package->features, MSIFEATURE, entry )
4431 if (!strcmpW( argument, feature->Feature ) || !strcmpW( argument, szAll ))
4433 if (feature->ActionRequest != INSTALLSTATE_ABSENT)
4434 msi_set_property( dialog->package->db, szPreselected, szOne, -1 );
4435 MSI_SetFeatureStateW( dialog->package, feature->Feature, INSTALLSTATE_ABSENT );
4438 return ERROR_SUCCESS;
4441 static UINT event_add_source( msi_dialog *dialog, const WCHAR *argument )
4443 MSIFEATURE *feature;
4445 LIST_FOR_EACH_ENTRY( feature, &dialog->package->features, MSIFEATURE, entry )
4447 if (!strcmpW( argument, feature->Feature ) || !strcmpW( argument, szAll ))
4449 if (feature->ActionRequest != INSTALLSTATE_SOURCE)
4450 msi_set_property( dialog->package->db, szPreselected, szOne, -1 );
4451 MSI_SetFeatureStateW( dialog->package, feature->Feature, INSTALLSTATE_SOURCE );
4454 return ERROR_SUCCESS;
4457 void msi_event_fire( MSIPACKAGE *package, const WCHAR *event, MSIRECORD *rec )
4459 struct subscriber *sub;
4461 TRACE("firing event %s\n", debugstr_w(event));
4463 LIST_FOR_EACH_ENTRY( sub, &package->subscriptions, struct subscriber, entry )
4465 if (strcmpiW( sub->event, event )) continue;
4466 dialog_handle_event( sub->dialog, sub->control, sub->attribute, rec );
4470 static UINT event_set_target_path( msi_dialog *dialog, const WCHAR *argument )
4472 WCHAR *path = msi_dup_property( dialog->package->db, argument );
4473 MSIRECORD *rec = MSI_CreateRecord( 1 );
4474 UINT r = ERROR_SUCCESS;
4476 MSI_RecordSetStringW( rec, 1, path );
4477 msi_event_fire( dialog->package, szSelectionPath, rec );
4478 if (path)
4480 /* failure to set the path halts the executing of control events */
4481 r = MSI_SetTargetPathW( dialog->package, argument, path );
4482 msi_free( path );
4484 msi_free( &rec->hdr );
4485 return r;
4488 static UINT event_reset( msi_dialog *dialog, const WCHAR *argument )
4490 msi_dialog_reset( dialog );
4491 return ERROR_SUCCESS;
4494 /* Return ERROR_SUCCESS if dialog is process and ERROR_FUNCTION_FAILED
4495 * if the given parameter is not a dialog box
4497 UINT ACTION_DialogBox( MSIPACKAGE *package, const WCHAR *dialog )
4499 UINT r;
4501 if (package->next_dialog) ERR("Already got next dialog... ignoring it\n");
4502 package->next_dialog = NULL;
4504 /* Dialogs are chained by filling in the next_dialog member
4505 * of the package structure, then terminating the current dialog.
4506 * The code below sees the next_dialog member set, and runs the
4507 * next dialog.
4508 * We fall out of the loop below if we come across a modeless
4509 * dialog, as it returns ERROR_IO_PENDING when we try to run
4510 * its message loop.
4512 r = event_do_dialog( package, dialog, NULL, TRUE );
4513 while (r == ERROR_SUCCESS && package->next_dialog)
4515 WCHAR *name = package->next_dialog;
4517 package->next_dialog = NULL;
4518 r = event_do_dialog( package, name, NULL, TRUE );
4519 msi_free( name );
4521 if (r == ERROR_IO_PENDING) r = ERROR_SUCCESS;
4522 return r;
4525 static UINT event_set_install_level( msi_dialog *dialog, const WCHAR *argument )
4527 int level = atolW( argument );
4529 TRACE("setting install level to %d\n", level);
4530 return MSI_SetInstallLevel( dialog->package, level );
4533 static UINT event_directory_list_up( msi_dialog *dialog, const WCHAR *argument )
4535 return msi_dialog_directorylist_up( dialog );
4538 static UINT event_reinstall_mode( msi_dialog *dialog, const WCHAR *argument )
4540 return msi_set_property( dialog->package->db, szReinstallMode, argument, -1 );
4543 static UINT event_reinstall( msi_dialog *dialog, const WCHAR *argument )
4545 return msi_set_property( dialog->package->db, szReinstall, argument, -1 );
4548 static UINT event_validate_product_id( msi_dialog *dialog, const WCHAR *argument )
4550 return msi_validate_product_id( dialog->package );
4553 static const WCHAR end_dialogW[] = {'E','n','d','D','i','a','l','o','g',0};
4554 static const WCHAR new_dialogW[] = {'N','e','w','D','i','a','l','o','g',0};
4555 static const WCHAR spawn_dialogW[] = {'S','p','a','w','n','D','i','a','l','o','g',0};
4556 static const WCHAR spawn_wait_dialogW[] = {'S','p','a','w','n','W','a','i','t','D','i','a','l','o','g',0};
4557 static const WCHAR do_actionW[] = {'D','o','A','c','t','i','o','n',0};
4558 static const WCHAR add_localW[] = {'A','d','d','L','o','c','a','l',0};
4559 static const WCHAR removeW[] = {'R','e','m','o','v','e',0};
4560 static const WCHAR add_sourceW[] = {'A','d','d','S','o','u','r','c','e',0};
4561 static const WCHAR set_target_pathW[] = {'S','e','t','T','a','r','g','e','t','P','a','t','h',0};
4562 static const WCHAR resetW[] = {'R','e','s','e','t',0};
4563 static const WCHAR set_install_levelW[] = {'S','e','t','I','n','s','t','a','l','l','L','e','v','e','l',0};
4564 static const WCHAR directory_list_upW[] = {'D','i','r','e','c','t','o','r','y','L','i','s','t','U','p',0};
4565 static const WCHAR selection_browseW[] = {'S','e','l','e','c','t','i','o','n','B','r','o','w','s','e',0};
4566 static const WCHAR reinstall_modeW[] = {'R','e','i','n','s','t','a','l','l','M','o','d','e',0};
4567 static const WCHAR reinstallW[] = {'R','e','i','n','s','t','a','l','l',0};
4568 static const WCHAR validate_product_idW[] = {'V','a','l','i','d','a','t','e','P','r','o','d','u','c','t','I','D',0};
4570 static const struct control_event control_events[] =
4572 { end_dialogW, event_end_dialog },
4573 { new_dialogW, event_new_dialog },
4574 { spawn_dialogW, event_spawn_dialog },
4575 { spawn_wait_dialogW, event_spawn_wait_dialog },
4576 { do_actionW, event_do_action },
4577 { add_localW, event_add_local },
4578 { removeW, event_remove },
4579 { add_sourceW, event_add_source },
4580 { set_target_pathW, event_set_target_path },
4581 { resetW, event_reset },
4582 { set_install_levelW, event_set_install_level },
4583 { directory_list_upW, event_directory_list_up },
4584 { selection_browseW, event_spawn_dialog },
4585 { reinstall_modeW, event_reinstall_mode },
4586 { reinstallW, event_reinstall },
4587 { validate_product_idW, event_validate_product_id },
4588 { NULL, NULL }
4591 static UINT dialog_event_handler( msi_dialog *dialog, const WCHAR *event, const WCHAR *argument )
4593 unsigned int i;
4595 TRACE("handling event %s\n", debugstr_w(event));
4597 if (!event) return ERROR_SUCCESS;
4599 for (i = 0; control_events[i].event; i++)
4601 if (!strcmpW( control_events[i].event, event ))
4602 return control_events[i].handler( dialog, argument );
4604 FIXME("unhandled event %s arg(%s)\n", debugstr_w(event), debugstr_w(argument));
4605 return ERROR_SUCCESS;