Create a helper function to fetch a single record from a query.
[wine/multimedia.git] / dlls / msi / dialog.c
blob92e05f87800beb336d352d22b035482cb3ec68ed
1 /*
2 * Implementation of the Microsoft Installer (msi.dll)
4 * Copyright 2005 Mike McCormack for CodeWeavers
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 #define COBJMACROS
23 #include <stdarg.h>
25 #include "windef.h"
26 #include "winbase.h"
27 #include "winuser.h"
28 #include "winnls.h"
29 #include "wingdi.h"
30 #include "msi.h"
31 #include "msipriv.h"
32 #include "msidefs.h"
33 #include "ocidl.h"
34 #include "olectl.h"
36 #include "wine/debug.h"
37 #include "wine/unicode.h"
39 #include "action.h"
41 WINE_DEFAULT_DEBUG_CHANNEL(msi);
44 const WCHAR szMsiDialogClass[] = {
45 'M','s','i','D','i','a','l','o','g','C','l','o','s','e','C','l','a','s','s',0
47 const WCHAR szMsiHiddenWindow[] = {
48 'M','s','i','H','i','d','d','e','n','W','i','n','d','o','w',0
50 const static WCHAR szStatic[] = { 'S','t','a','t','i','c',0 };
51 const static WCHAR szButton[] = { 'B','U','T','T','O','N', 0 };
53 const static WCHAR szButtonData[] = { 'M','S','I','D','A','T','A',0 };
55 struct msi_control_tag;
56 typedef struct msi_control_tag msi_control;
57 typedef UINT (*msi_handler)( msi_dialog *, msi_control *, WPARAM );
59 struct msi_control_tag
61 struct msi_control_tag *next;
62 HWND hwnd;
63 msi_handler handler;
64 LPWSTR property;
65 LPWSTR value;
66 IPicture *pic;
67 WCHAR name[1];
70 typedef struct msi_font_tag
72 struct msi_font_tag *next;
73 HFONT hfont;
74 WCHAR name[1];
75 } msi_font;
77 struct msi_dialog_tag
79 MSIPACKAGE *package;
80 msi_dialog_event_handler event_handler;
81 BOOL finished;
82 INT scale;
83 DWORD attributes;
84 HWND hwnd;
85 LPWSTR default_font;
86 msi_font *font_list;
87 msi_control *control_list;
88 WCHAR name[1];
91 typedef UINT (*msi_dialog_control_func)( msi_dialog *dialog, MSIRECORD *rec );
92 struct control_handler
94 LPCWSTR control_type;
95 msi_dialog_control_func func;
98 typedef struct
100 msi_dialog* dialog;
101 msi_control *parent;
102 DWORD attributes;
103 } radio_button_group_descr;
105 static UINT msi_dialog_checkbox_handler( msi_dialog *, msi_control *, WPARAM );
106 static void msi_dialog_checkbox_sync_state( msi_dialog *, msi_control * );
107 static UINT msi_dialog_button_handler( msi_dialog *, msi_control *, WPARAM );
108 static UINT msi_dialog_edit_handler( msi_dialog *, msi_control *, WPARAM );
109 static UINT msi_dialog_radiogroup_handler( msi_dialog *, msi_control *, WPARAM param );
110 static LRESULT WINAPI MSIRadioGroup_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
113 /* dialog sequencing */
115 #define WM_MSI_DIALOG_CREATE (WM_USER+0x100)
116 #define WM_MSI_DIALOG_DESTROY (WM_USER+0x101)
118 static DWORD uiThreadId;
119 static HWND hMsiHiddenWindow;
121 static INT msi_dialog_scale_unit( msi_dialog *dialog, INT val )
123 return (dialog->scale * val + 5) / 10;
127 * msi_dialog_get_style
129 * Extract the {\style} string from the front of the text to display and
130 * update the pointer.
132 static LPWSTR msi_dialog_get_style( LPCWSTR *text )
134 LPWSTR ret = NULL;
135 LPCWSTR p = *text, q;
136 DWORD len;
138 if( *p++ != '{' )
139 return ret;
140 q = strchrW( p, '}' );
141 if( !q )
142 return ret;
143 *text = ++q;
144 if( *p++ != '\\' )
145 return ret;
146 len = q - p;
148 ret = HeapAlloc( GetProcessHeap(), 0, len*sizeof(WCHAR) );
149 if( !ret )
150 return ret;
151 memcpy( ret, p, len*sizeof(WCHAR) );
152 ret[len-1] = 0;
153 return ret;
156 static UINT msi_dialog_add_font( MSIRECORD *rec, LPVOID param )
158 msi_dialog *dialog = param;
159 msi_font *font;
160 LPCWSTR face, name;
161 LOGFONTW lf;
162 INT style;
163 HDC hdc;
165 /* create a font and add it to the list */
166 name = MSI_RecordGetString( rec, 1 );
167 font = HeapAlloc( GetProcessHeap(), 0,
168 sizeof *font + strlenW( name )*sizeof (WCHAR) );
169 strcpyW( font->name, name );
170 font->next = dialog->font_list;
171 dialog->font_list = font;
173 memset( &lf, 0, sizeof lf );
174 face = MSI_RecordGetString( rec, 2 );
175 lf.lfHeight = MSI_RecordGetInteger( rec, 3 );
176 style = MSI_RecordGetInteger( rec, 5 );
177 if( style & msidbTextStyleStyleBitsBold )
178 lf.lfWeight = FW_BOLD;
179 if( style & msidbTextStyleStyleBitsItalic )
180 lf.lfItalic = TRUE;
181 if( style & msidbTextStyleStyleBitsUnderline )
182 lf.lfUnderline = TRUE;
183 if( style & msidbTextStyleStyleBitsStrike )
184 lf.lfStrikeOut = TRUE;
185 lstrcpynW( lf.lfFaceName, face, LF_FACESIZE );
187 /* adjust the height */
188 hdc = GetDC( dialog->hwnd );
189 if (hdc)
191 lf.lfHeight = -MulDiv(lf.lfHeight, GetDeviceCaps(hdc, LOGPIXELSY), 72);
192 ReleaseDC( dialog->hwnd, hdc );
195 font->hfont = CreateFontIndirectW( &lf );
197 TRACE("Adding font style %s\n", debugstr_w(font->name) );
199 return ERROR_SUCCESS;
202 static msi_font *msi_dialog_find_font( msi_dialog *dialog, LPCWSTR name )
204 msi_font *font;
206 for( font = dialog->font_list; font; font = font->next )
207 if( !strcmpW( font->name, name ) ) /* FIXME: case sensitive? */
208 break;
210 return font;
213 static UINT msi_dialog_set_font( msi_dialog *dialog, HWND hwnd, LPCWSTR name )
215 msi_font *font;
217 font = msi_dialog_find_font( dialog, name );
218 if( font )
219 SendMessageW( hwnd, WM_SETFONT, (WPARAM) font->hfont, TRUE );
220 else
221 ERR("No font entry for %s\n", debugstr_w(name));
222 return ERROR_SUCCESS;
225 static UINT msi_dialog_build_font_list( msi_dialog *dialog )
227 static const WCHAR query[] = {
228 'S','E','L','E','C','T',' ','*',' ',
229 'F','R','O','M',' ','`','T','e','x','t','S','t','y','l','e','`',' ',0
231 UINT r;
232 MSIQUERY *view = NULL;
234 TRACE("dialog %p\n", dialog );
236 r = MSI_OpenQuery( dialog->package->db, &view, query );
237 if( r != ERROR_SUCCESS )
238 return r;
240 r = MSI_IterateRecords( view, NULL, msi_dialog_add_font, dialog );
241 msiobj_release( &view->hdr );
243 return r;
246 static msi_control *msi_dialog_create_window( msi_dialog *dialog,
247 MSIRECORD *rec, LPCWSTR szCls, LPCWSTR name, LPCWSTR text,
248 DWORD style, HWND parent )
250 DWORD x, y, width, height;
251 LPWSTR font = NULL, title = NULL;
252 msi_control *control;
254 style |= WS_CHILD | WS_GROUP;
256 control = HeapAlloc( GetProcessHeap(), 0,
257 sizeof *control + strlenW(name)*sizeof(WCHAR) );
258 strcpyW( control->name, name );
259 control->next = dialog->control_list;
260 dialog->control_list = control;
261 control->handler = NULL;
262 control->property = NULL;
263 control->value = NULL;
264 control->pic = NULL;
266 x = MSI_RecordGetInteger( rec, 4 );
267 y = MSI_RecordGetInteger( rec, 5 );
268 width = MSI_RecordGetInteger( rec, 6 );
269 height = MSI_RecordGetInteger( rec, 7 );
271 x = msi_dialog_scale_unit( dialog, x );
272 y = msi_dialog_scale_unit( dialog, y );
273 width = msi_dialog_scale_unit( dialog, width );
274 height = msi_dialog_scale_unit( dialog, height );
276 if( text )
278 font = msi_dialog_get_style( &text );
279 deformat_string( dialog->package, text, &title );
282 control->hwnd = CreateWindowW( szCls, title, style,
283 x, y, width, height, parent, NULL, NULL, NULL );
285 TRACE("Dialog %s control %s hwnd %p\n",
286 debugstr_w(dialog->name), debugstr_w(text), control->hwnd );
288 msi_dialog_set_font( dialog, control->hwnd,
289 font ? font : dialog->default_font );
291 HeapFree( GetProcessHeap(), 0, font );
292 HeapFree( GetProcessHeap(), 0, title );
294 return control;
297 /* everything except radio buttons */
298 static msi_control *msi_dialog_add_control( msi_dialog *dialog,
299 MSIRECORD *rec, LPCWSTR szCls, DWORD style )
301 DWORD attributes;
302 LPCWSTR text, name;
304 name = MSI_RecordGetString( rec, 2 );
305 attributes = MSI_RecordGetInteger( rec, 8 );
306 text = MSI_RecordGetString( rec, 10 );
307 if( attributes & 1 )
308 style |= WS_VISIBLE;
309 if( ~attributes & 2 )
310 style |= WS_DISABLED;
311 return msi_dialog_create_window( dialog, rec, szCls, name, text,
312 style, dialog->hwnd );
315 static UINT msi_dialog_text_control( msi_dialog *dialog, MSIRECORD *rec )
317 TRACE("%p %p\n", dialog, rec);
319 msi_dialog_add_control( dialog, rec, szStatic, 0 );
320 return ERROR_SUCCESS;
323 static UINT msi_dialog_button_control( msi_dialog *dialog, MSIRECORD *rec )
325 msi_control *control;
327 TRACE("%p %p\n", dialog, rec);
329 control = msi_dialog_add_control( dialog, rec, szButton, 0 );
330 control->handler = msi_dialog_button_handler;
332 return ERROR_SUCCESS;
335 static LPWSTR msi_get_checkbox_value( msi_dialog *dialog, LPCWSTR prop )
337 const static WCHAR query[] = {
338 'S','E','L','E','C','T',' ','*',' ',
339 'F','R','O','M',' ','`','C','h','e','c','k','B','o','x',' ','`',
340 'W','H','E','R','E',' ',
341 '`','P','r','o','p','e','r','t','y','`',' ','=',' ',
342 '\'','%','s','\'',0
344 MSIRECORD *rec = NULL;
345 LPCWSTR val = NULL;
346 LPWSTR ret = NULL;
348 /* find if there is a value associated with the checkbox */
349 rec = MSI_QueryGetRecord( dialog->package->db, query, prop );
350 if (!rec)
351 return ret;
353 val = MSI_RecordGetString( rec, 2 );
354 if (val)
356 deformat_string( dialog->package, val, &ret );
357 if( ret && !ret[0] )
359 HeapFree( GetProcessHeap(), 0, ret );
360 ret = NULL;
363 msiobj_release( &rec->hdr );
364 if (ret)
365 return ret;
367 ret = load_dynamic_property(dialog->package, prop, NULL);
368 if( ret && !ret[0] )
370 HeapFree( GetProcessHeap(), 0, ret );
371 ret = NULL;
374 return ret;
377 static UINT msi_dialog_checkbox_control( msi_dialog *dialog, MSIRECORD *rec )
379 msi_control *control;
380 LPCWSTR prop;
382 TRACE("%p %p\n", dialog, rec);
384 control = msi_dialog_add_control( dialog, rec, szButton,
385 BS_CHECKBOX | BS_MULTILINE );
386 control->handler = msi_dialog_checkbox_handler;
387 prop = MSI_RecordGetString( rec, 9 );
388 if( prop )
390 control->property = strdupW( prop );
391 control->value = msi_get_checkbox_value( dialog, prop );
392 TRACE("control %s value %s\n", debugstr_w(control->property),
393 debugstr_w(control->value));
395 msi_dialog_checkbox_sync_state( dialog, control );
397 return ERROR_SUCCESS;
400 static UINT msi_dialog_line_control( msi_dialog *dialog, MSIRECORD *rec )
402 TRACE("%p %p\n", dialog, rec);
404 msi_dialog_add_control( dialog, rec, szStatic, SS_ETCHEDHORZ | SS_SUNKEN );
405 return ERROR_SUCCESS;
408 static UINT msi_dialog_scrolltext_control( msi_dialog *dialog, MSIRECORD *rec )
410 const static WCHAR szEdit[] = { 'E','D','I','T',0 };
412 FIXME("%p %p\n", dialog, rec);
414 msi_dialog_add_control( dialog, rec, szEdit, WS_BORDER |
415 ES_MULTILINE | WS_VSCROLL | ES_READONLY | ES_AUTOVSCROLL );
417 return ERROR_SUCCESS;
420 static UINT msi_load_bitmap( MSIDATABASE *db, LPCWSTR name, IPicture **pic )
422 const static WCHAR query[] = {
423 's','e','l','e','c','t',' ','*',' ',
424 'f','r','o','m',' ','B','i','n','a','r','y',' ',
425 'w','h','e','r','e',' ',
426 '`','N','a','m','e','`',' ','=',' ','\'','%','s','\'',0
428 MSIQUERY *view = NULL;
429 MSIRECORD *rec = NULL;
430 IStream *stm = NULL;
431 UINT r;
433 r = MSI_OpenQuery( db, &view, query, name );
434 if( r != ERROR_SUCCESS )
435 return r;
437 MSI_ViewExecute( view, NULL );
438 MSI_ViewFetch( view, &rec );
439 MSI_ViewClose( view );
440 msiobj_release( &view->hdr );
442 if( !rec )
443 return ERROR_FUNCTION_FAILED;
445 r = MSI_RecordGetIStream( rec, 2, &stm );
446 msiobj_release( &rec->hdr );
447 if( r != ERROR_SUCCESS )
448 return r;
450 r = OleLoadPicture( stm, 0, TRUE, &IID_IPicture, (LPVOID*) pic );
451 IStream_Release( stm );
452 if( FAILED( r ) )
453 return ERROR_FUNCTION_FAILED;
455 return ERROR_SUCCESS;
458 static UINT msi_dialog_bitmap_control( msi_dialog *dialog, MSIRECORD *rec )
460 IPicture *pic = NULL;
461 msi_control *control;
462 OLE_HANDLE hBitmap = 0;
463 LPCWSTR text;
464 UINT r;
466 control = msi_dialog_add_control( dialog, rec, szStatic,
467 SS_BITMAP | SS_LEFT | SS_CENTERIMAGE );
468 text = MSI_RecordGetString( rec, 10 );
469 r = msi_load_bitmap( dialog->package->db, text, &pic );
470 if( r == ERROR_SUCCESS )
472 r = IPicture_get_Handle( pic, &hBitmap );
473 if( SUCCEEDED( r ) )
474 SendMessageW( control->hwnd, STM_SETIMAGE, IMAGE_BITMAP, hBitmap );
475 control->pic = pic;
478 return ERROR_SUCCESS;
481 static UINT msi_dialog_combo_control( msi_dialog *dialog, MSIRECORD *rec )
483 static const WCHAR szCombo[] = { 'C','O','M','B','O','B','O','X',0 };
485 msi_dialog_add_control( dialog, rec, szCombo,
486 SS_BITMAP | SS_LEFT | SS_CENTERIMAGE );
487 return ERROR_SUCCESS;
490 static UINT msi_dialog_edit_control( msi_dialog *dialog, MSIRECORD *rec )
492 const static WCHAR szEdit[] = { 'E','D','I','T',0 };
493 msi_control *control;
494 LPCWSTR prop;
495 LPWSTR val;
497 control = msi_dialog_add_control( dialog, rec, szEdit, WS_BORDER );
498 control->handler = msi_dialog_edit_handler;
499 prop = MSI_RecordGetString( rec, 9 );
500 if( prop )
501 control->property = strdupW( prop );
502 val = load_dynamic_property( dialog->package, control->property, NULL );
503 SetWindowTextW( control->hwnd, val );
504 HeapFree( GetProcessHeap(), 0, val );
505 return ERROR_SUCCESS;
508 static UINT msi_dialog_pathedit_control( msi_dialog *dialog, MSIRECORD *rec )
510 FIXME("not implemented properly\n");
511 return msi_dialog_edit_control( dialog, rec );
514 /* radio buttons are a bit different from normal controls */
515 static UINT msi_dialog_create_radiobutton( MSIRECORD *rec, LPVOID param )
517 radio_button_group_descr *group = (radio_button_group_descr *)param;
518 msi_dialog *dialog = group->dialog;
519 msi_control *control;
520 LPCWSTR prop, text, name;
521 DWORD style;
522 DWORD attributes = group->attributes;
524 style = WS_CHILD | BS_AUTORADIOBUTTON | BS_MULTILINE;
525 name = MSI_RecordGetString( rec, 3 );
526 text = MSI_RecordGetString( rec, 8 );
527 if( attributes & 1 )
528 style |= WS_VISIBLE;
529 if( ~attributes & 2 )
530 style |= WS_DISABLED;
532 control = msi_dialog_create_window( dialog, rec, szButton, name, text,
533 style, group->parent->hwnd );
534 control->handler = msi_dialog_radiogroup_handler;
536 prop = MSI_RecordGetString( rec, 1 );
537 if( prop )
538 control->property = strdupW( prop );
540 return ERROR_SUCCESS;
543 static UINT msi_dialog_radiogroup_control( msi_dialog *dialog, MSIRECORD *rec )
545 static const WCHAR query[] = {
546 'S','E','L','E','C','T',' ','*',' ',
547 'F','R','O','M',' ','R','a','d','i','o','B','u','t','t','o','n',' ',
548 'W','H','E','R','E',' ',
549 '`','P','r','o','p','e','r','t','y','`',' ','=',' ','\'','%','s','\'',0};
550 UINT r;
551 LPCWSTR prop;
552 msi_control *control;
553 MSIQUERY *view = NULL;
554 radio_button_group_descr group;
555 MSIPACKAGE *package = dialog->package;
557 prop = MSI_RecordGetString( rec, 9 );
559 TRACE("%p %p %s\n", dialog, rec, debugstr_w( prop ));
561 /* Create parent group box to hold radio buttons */
562 control = msi_dialog_add_control( dialog, rec, szButton, BS_OWNERDRAW );
564 if (control->hwnd)
566 WNDPROC oldproc = (WNDPROC) SetWindowLongPtrW(control->hwnd, GWLP_WNDPROC,
567 (LONG_PTR)MSIRadioGroup_WndProc);
568 SetPropW(control->hwnd, szButtonData, oldproc);
571 if( prop )
572 control->property = strdupW( prop );
574 /* query the Radio Button table for all control in this group */
575 r = MSI_OpenQuery( package->db, &view, query, prop );
576 if( r != ERROR_SUCCESS )
578 ERR("query failed for dialog %s radio group %s\n",
579 debugstr_w(dialog->name), debugstr_w(prop));
580 return ERROR_INVALID_PARAMETER;
583 group.dialog = dialog;
584 group.parent = control;
585 group.attributes = MSI_RecordGetInteger( rec, 8 );
587 r = MSI_IterateRecords( view, 0, msi_dialog_create_radiobutton, &group );
588 msiobj_release( &view->hdr );
590 return r;
593 static const WCHAR szText[] = { 'T','e','x','t',0 };
594 static const WCHAR szPushButton[] = { 'P','u','s','h','B','u','t','t','o','n',0 };
595 static const WCHAR szLine[] = { 'L','i','n','e',0 };
596 static const WCHAR szBitmap[] = { 'B','i','t','m','a','p',0 };
597 static const WCHAR szCheckBox[] = { 'C','h','e','c','k','B','o','x',0 };
598 static const WCHAR szScrollableText[] = {
599 'S','c','r','o','l','l','a','b','l','e','T','e','x','t',0 };
600 static const WCHAR szComboBox[] = { 'C','o','m','b','o','B','o','x',0 };
601 static const WCHAR szEdit[] = { 'E','d','i','t',0 };
602 static const WCHAR szMaskedEdit[] = { 'M','a','s','k','e','d','E','d','i','t',0 };
603 static const WCHAR szPathEdit[] = { 'P','a','t','h','E','d','i','t',0 };
604 static const WCHAR szRadioButtonGroup[] = {
605 'R','a','d','i','o','B','u','t','t','o','n','G','r','o','u','p',0 };
607 struct control_handler msi_dialog_handler[] =
609 { szText, msi_dialog_text_control },
610 { szPushButton, msi_dialog_button_control },
611 { szLine, msi_dialog_line_control },
612 { szBitmap, msi_dialog_bitmap_control },
613 { szCheckBox, msi_dialog_checkbox_control },
614 { szScrollableText, msi_dialog_scrolltext_control },
615 { szComboBox, msi_dialog_combo_control },
616 { szEdit, msi_dialog_edit_control },
617 { szMaskedEdit, msi_dialog_edit_control },
618 { szPathEdit, msi_dialog_pathedit_control },
619 { szRadioButtonGroup, msi_dialog_radiogroup_control },
622 #define NUM_CONTROL_TYPES (sizeof msi_dialog_handler/sizeof msi_dialog_handler[0])
624 static UINT msi_dialog_create_controls( MSIRECORD *rec, LPVOID param )
626 msi_dialog *dialog = param;
627 LPCWSTR control_type;
628 UINT i;
630 /* find and call the function that can create this type of control */
631 control_type = MSI_RecordGetString( rec, 3 );
632 for( i=0; i<NUM_CONTROL_TYPES; i++ )
633 if (!strcmpiW( msi_dialog_handler[i].control_type, control_type ))
634 break;
635 if( i != NUM_CONTROL_TYPES )
636 msi_dialog_handler[i].func( dialog, rec );
637 else
638 ERR("no handler for element type %s\n", debugstr_w(control_type));
640 return ERROR_SUCCESS;
643 static UINT msi_dialog_fill_controls( msi_dialog *dialog )
645 static const WCHAR query[] = {
646 'S','E','L','E','C','T',' ','*',' ',
647 'F','R','O','M',' ','C','o','n','t','r','o','l',' ',
648 'W','H','E','R','E',' ',
649 '`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',0};
650 UINT r;
651 MSIQUERY *view = NULL;
652 MSIPACKAGE *package = dialog->package;
654 TRACE("%p %s\n", dialog, debugstr_w(dialog->name) );
656 /* query the Control table for all the elements of the control */
657 r = MSI_OpenQuery( package->db, &view, query, dialog->name );
658 if( r != ERROR_SUCCESS )
660 ERR("query failed for dialog %s\n", debugstr_w(dialog->name));
661 return ERROR_INVALID_PARAMETER;
664 r = MSI_IterateRecords( view, 0, msi_dialog_create_controls, dialog );
665 msiobj_release( &view->hdr );
667 return r;
670 static msi_control *msi_dialog_find_control( msi_dialog *dialog, LPCWSTR name )
672 msi_control *control;
674 for( control = dialog->control_list; control; control = control->next )
675 if( !strcmpW( control->name, name ) ) /* FIXME: case sensitive? */
676 break;
677 return control;
680 static msi_control *msi_dialog_find_control_by_hwnd( msi_dialog *dialog, HWND hwnd )
682 msi_control *control;
684 for( control = dialog->control_list; control; control = control->next )
685 if( hwnd == control->hwnd )
686 break;
687 return control;
690 static UINT msi_dialog_set_control_condition( MSIRECORD *rec, LPVOID param )
692 static const WCHAR szHide[] = { 'H','i','d','e',0 };
693 static const WCHAR szShow[] = { 'S','h','o','w',0 };
694 static const WCHAR szDisable[] = { 'D','i','s','a','b','l','e',0 };
695 static const WCHAR szEnable[] = { 'E','n','a','b','l','e',0 };
696 msi_dialog *dialog = param;
697 msi_control *control;
698 LPCWSTR name, action, condition;
699 UINT r;
701 name = MSI_RecordGetString( rec, 2 );
702 action = MSI_RecordGetString( rec, 3 );
703 condition = MSI_RecordGetString( rec, 4 );
704 r = MSI_EvaluateConditionW( dialog->package, condition );
705 control = msi_dialog_find_control( dialog, name );
706 if( r && control )
708 TRACE("%s control %s\n", debugstr_w(action), debugstr_w(name));
710 /* FIXME: case sensitive? */
711 if(!strcmpW(action, szHide))
712 ShowWindow(control->hwnd, SW_HIDE);
713 else if(!strcmpW(action, szShow))
714 ShowWindow(control->hwnd, SW_SHOW);
715 else if(!strcmpW(action, szDisable))
716 EnableWindow(control->hwnd, FALSE);
717 else if(!strcmpW(action, szEnable))
718 EnableWindow(control->hwnd, TRUE);
719 else
720 FIXME("Unhandled action %s\n", debugstr_w(action));
723 return ERROR_SUCCESS;
726 static UINT msi_dialog_evaluate_control_conditions( msi_dialog *dialog )
728 static const WCHAR query[] = {
729 'S','E','L','E','C','T',' ','*',' ',
730 'F','R','O','M',' ',
731 'C','o','n','t','r','o','l','C','o','n','d','i','t','i','o','n',' ',
732 'W','H','E','R','E',' ',
733 '`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',0
735 UINT r;
736 MSIQUERY *view = NULL;
737 MSIPACKAGE *package = dialog->package;
739 TRACE("%p %s\n", dialog, debugstr_w(dialog->name) );
741 /* query the Control table for all the elements of the control */
742 r = MSI_OpenQuery( package->db, &view, query, dialog->name );
743 if( r != ERROR_SUCCESS )
745 ERR("query failed for dialog %s\n", debugstr_w(dialog->name));
746 return ERROR_INVALID_PARAMETER;
749 r = MSI_IterateRecords( view, 0, msi_dialog_set_control_condition, dialog );
750 msiobj_release( &view->hdr );
752 return r;
755 /* figure out the height of 10 point MS Sans Serif */
756 static INT msi_dialog_get_sans_serif_height( HWND hwnd )
758 static const WCHAR szSansSerif[] = {
759 'M','S',' ','S','a','n','s',' ','S','e','r','i','f',0 };
760 LOGFONTW lf;
761 TEXTMETRICW tm;
762 BOOL r;
763 LONG height = 0;
764 HFONT hFont, hOldFont;
765 HDC hdc;
767 hdc = GetDC( hwnd );
768 if (hdc)
770 memset( &lf, 0, sizeof lf );
771 lf.lfHeight = MulDiv(10, GetDeviceCaps(hdc, LOGPIXELSY), 72);
772 strcpyW( lf.lfFaceName, szSansSerif );
773 hFont = CreateFontIndirectW(&lf);
774 if (hFont)
776 hOldFont = SelectObject( hdc, hFont );
777 r = GetTextMetricsW( hdc, &tm );
778 if (r)
779 height = tm.tmHeight;
780 SelectObject( hdc, hOldFont );
781 DeleteObject( hFont );
783 ReleaseDC( hwnd, hdc );
785 return height;
788 /* fetch the associated record from the Dialog table */
789 static MSIRECORD *msi_get_dialog_record( msi_dialog *dialog )
791 static const WCHAR query[] = {
792 'S','E','L','E','C','T',' ','*',' ',
793 'F','R','O','M',' ','D','i','a','l','o','g',' ',
794 'W','H','E','R','E',' ',
795 '`','D','i','a','l','o','g','`',' ','=',' ','\'','%','s','\'',0};
796 MSIPACKAGE *package = dialog->package;
797 MSIRECORD *rec = NULL;
799 TRACE("%p %s\n", dialog, debugstr_w(dialog->name) );
801 rec = MSI_QueryGetRecord( package->db, query, dialog->name );
802 if( !rec )
803 ERR("query failed for dialog %s\n", debugstr_w(dialog->name));
805 return rec;
808 static LRESULT msi_dialog_oncreate( HWND hwnd, LPCREATESTRUCTW cs )
810 static const WCHAR df[] = {
811 'D','e','f','a','u','l','t','U','I','F','o','n','t',0 };
812 msi_dialog *dialog = (msi_dialog*) cs->lpCreateParams;
813 MSIRECORD *rec = NULL;
814 DWORD width, height;
815 LPCWSTR text;
816 LPWSTR title = NULL;
818 TRACE("%p %p\n", dialog, dialog->package);
820 dialog->hwnd = hwnd;
821 SetWindowLongPtrW( hwnd, GWLP_USERDATA, (LONG_PTR) dialog );
823 rec = msi_get_dialog_record( dialog );
824 if( !rec )
826 TRACE("No record found for dialog %s\n", debugstr_w(dialog->name));
827 return -1;
830 dialog->scale = msi_dialog_get_sans_serif_height(dialog->hwnd);
832 width = MSI_RecordGetInteger( rec, 4 );
833 height = MSI_RecordGetInteger( rec, 5 );
834 dialog->attributes = MSI_RecordGetInteger( rec, 6 );
835 text = MSI_RecordGetString( rec, 7 );
837 width = msi_dialog_scale_unit( dialog, width );
838 height = msi_dialog_scale_unit( dialog, height ) + 25; /* FIXME */
840 dialog->default_font = load_dynamic_property( dialog->package, df, NULL );
842 deformat_string( dialog->package, text, &title );
843 SetWindowTextW( hwnd, title );
844 SetWindowPos( hwnd, 0, 0, 0, width, height,
845 SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOREDRAW );
847 HeapFree( GetProcessHeap(), 0, title );
848 msiobj_release( &rec->hdr );
850 msi_dialog_build_font_list( dialog );
851 msi_dialog_fill_controls( dialog );
852 msi_dialog_evaluate_control_conditions( dialog );
854 return 0;
857 static UINT msi_dialog_send_event( msi_dialog *dialog, LPCWSTR event, LPCWSTR arg )
859 LPWSTR event_fmt = NULL, arg_fmt = NULL;
861 TRACE("Sending control event %s %s\n", debugstr_w(event), debugstr_w(arg));
863 deformat_string( dialog->package, event, &event_fmt );
864 deformat_string( dialog->package, arg, &arg_fmt );
866 dialog->event_handler( dialog->package, event_fmt, arg_fmt, dialog );
868 HeapFree( GetProcessHeap(), 0, event_fmt );
869 HeapFree( GetProcessHeap(), 0, arg_fmt );
871 return ERROR_SUCCESS;
874 static UINT msi_dialog_set_property( msi_dialog *dialog, LPCWSTR event, LPCWSTR arg )
876 static const WCHAR szNullArg[] = { '{','}',0 };
877 LPWSTR p, prop, arg_fmt = NULL;
878 UINT len;
880 len = strlenW(event);
881 prop = HeapAlloc( GetProcessHeap(), 0, len*sizeof(WCHAR));
882 strcpyW( prop, &event[1] );
883 p = strchrW( prop, ']' );
884 if( p && p[1] == 0 )
886 *p = 0;
887 if( strcmpW( szNullArg, arg ) )
888 deformat_string( dialog->package, arg, &arg_fmt );
889 MSI_SetPropertyW( dialog->package, prop, arg_fmt );
891 else
892 ERR("Badly formatted property string - what happens?\n");
893 HeapFree( GetProcessHeap(), 0, prop );
894 return ERROR_SUCCESS;
897 static UINT msi_dialog_control_event( MSIRECORD *rec, LPVOID param )
899 msi_dialog *dialog = param;
900 LPCWSTR condition, event, arg;
901 UINT r;
903 condition = MSI_RecordGetString( rec, 5 );
904 r = MSI_EvaluateConditionW( dialog->package, condition );
905 if( r )
907 event = MSI_RecordGetString( rec, 3 );
908 arg = MSI_RecordGetString( rec, 4 );
909 if( event[0] == '[' )
910 msi_dialog_set_property( dialog, event, arg );
911 else
912 msi_dialog_send_event( dialog, event, arg );
915 return ERROR_SUCCESS;
918 static UINT msi_dialog_button_handler( msi_dialog *dialog,
919 msi_control *control, WPARAM param )
921 static const WCHAR query[] = {
922 'S','E','L','E','C','T',' ','*',' ',
923 'F','R','O','M',' ','C','o','n','t','r','o','l','E','v','e','n','t',' ',
924 'W','H','E','R','E',' ',
925 '`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',' ',
926 'A','N','D',' ',
927 '`','C','o','n','t','r','o','l','_','`',' ','=',' ','\'','%','s','\'',' ',
928 'O','R','D','E','R',' ','B','Y',' ','`','O','r','d','e','r','i','n','g','`',0
930 MSIQUERY *view = NULL;
931 UINT r;
933 if( HIWORD(param) != BN_CLICKED )
934 return ERROR_SUCCESS;
936 r = MSI_OpenQuery( dialog->package->db, &view, query,
937 dialog->name, control->name );
938 if( r != ERROR_SUCCESS )
940 ERR("query failed\n");
941 return 0;
944 r = MSI_IterateRecords( view, 0, msi_dialog_control_event, dialog );
945 msiobj_release( &view->hdr );
947 return r;
950 static UINT msi_dialog_get_checkbox_state( msi_dialog *dialog,
951 msi_control *control )
953 WCHAR state[2] = { 0 };
954 DWORD sz = 2;
956 MSI_GetPropertyW( dialog->package, control->property, state, &sz );
957 return state[0] ? 1 : 0;
960 static void msi_dialog_set_checkbox_state( msi_dialog *dialog,
961 msi_control *control, UINT state )
963 static const WCHAR szState[] = { '1', 0 };
964 LPCWSTR val;
966 /* if uncheck then the property is set to NULL */
967 if (!state)
969 MSI_SetPropertyW( dialog->package, control->property, NULL );
970 return;
973 /* check for a custom state */
974 if (control->value && control->value[0])
975 val = control->value;
976 else
977 val = szState;
979 MSI_SetPropertyW( dialog->package, control->property, val );
982 static void msi_dialog_checkbox_sync_state( msi_dialog *dialog,
983 msi_control *control )
985 UINT state;
987 state = msi_dialog_get_checkbox_state( dialog, control );
988 SendMessageW( control->hwnd, BM_SETCHECK,
989 state ? BST_CHECKED : BST_UNCHECKED, 0 );
992 static UINT msi_dialog_checkbox_handler( msi_dialog *dialog,
993 msi_control *control, WPARAM param )
995 UINT state;
997 if( HIWORD(param) != BN_CLICKED )
998 return ERROR_SUCCESS;
1000 TRACE("clicked checkbox %s, set %s\n", debugstr_w(control->name),
1001 debugstr_w(control->property));
1003 state = msi_dialog_get_checkbox_state( dialog, control );
1004 state = state ? 0 : 1;
1005 msi_dialog_set_checkbox_state( dialog, control, state );
1006 msi_dialog_checkbox_sync_state( dialog, control );
1008 return msi_dialog_button_handler( dialog, control, param );
1011 static UINT msi_dialog_edit_handler( msi_dialog *dialog,
1012 msi_control *control, WPARAM param )
1014 UINT sz, r;
1015 LPWSTR buf;
1017 if( HIWORD(param) != EN_CHANGE )
1018 return ERROR_SUCCESS;
1020 TRACE("edit %s contents changed, set %s\n", debugstr_w(control->name),
1021 debugstr_w(control->property));
1023 sz = 0x20;
1024 buf = HeapAlloc( GetProcessHeap(), 0, sz*sizeof(WCHAR) );
1025 while( buf )
1027 r = GetWindowTextW( control->hwnd, buf, sz );
1028 if( r < (sz-1) )
1029 break;
1030 sz *= 2;
1031 buf = HeapReAlloc( GetProcessHeap(), 0, buf, sz*sizeof(WCHAR) );
1034 MSI_SetPropertyW( dialog->package, control->property, buf );
1036 HeapFree( GetProcessHeap(), 0, buf );
1038 return ERROR_SUCCESS;
1041 static UINT msi_dialog_radiogroup_handler( msi_dialog *dialog,
1042 msi_control *control, WPARAM param )
1044 if( HIWORD(param) != BN_CLICKED )
1045 return ERROR_SUCCESS;
1047 TRACE("clicked radio button %s, set %s\n", debugstr_w(control->name),
1048 debugstr_w(control->property));
1050 MSI_SetPropertyW( dialog->package, control->property, control->name );
1052 return msi_dialog_button_handler( dialog, control, param );
1055 static LRESULT msi_dialog_oncommand( msi_dialog *dialog, WPARAM param, HWND hwnd )
1057 msi_control *control;
1059 TRACE("%p %p %08x\n", dialog, hwnd, param);
1061 control = msi_dialog_find_control_by_hwnd( dialog, hwnd );
1062 if( control )
1064 if( control->handler )
1066 control->handler( dialog, control, param );
1067 msi_dialog_evaluate_control_conditions( dialog );
1070 else
1071 ERR("button click from nowhere\n");
1072 return 0;
1075 static LRESULT WINAPI MSIDialog_WndProc( HWND hwnd, UINT msg,
1076 WPARAM wParam, LPARAM lParam )
1078 msi_dialog *dialog = (LPVOID) GetWindowLongPtrW( hwnd, GWLP_USERDATA );
1080 TRACE("0x%04x\n", msg);
1082 switch (msg)
1084 case WM_CREATE:
1085 return msi_dialog_oncreate( hwnd, (LPCREATESTRUCTW)lParam );
1087 case WM_COMMAND:
1088 return msi_dialog_oncommand( dialog, wParam, (HWND)lParam );
1090 case WM_DESTROY:
1091 dialog->hwnd = NULL;
1092 return 0;
1094 return DefWindowProcW(hwnd, msg, wParam, lParam);
1097 static LRESULT WINAPI MSIRadioGroup_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
1099 WNDPROC oldproc = (WNDPROC) GetPropW(hWnd, szButtonData);
1101 TRACE("hWnd %p msg %04x wParam 0x%08x lParam 0x%08lx\n", hWnd, msg, wParam, lParam);
1103 if (msg == WM_COMMAND) /* Forward notifications to dialog */
1104 SendMessageW(GetParent(hWnd), msg, wParam, lParam);
1106 return CallWindowProcW(oldproc, hWnd, msg, wParam, lParam);
1109 static LRESULT WINAPI MSIHiddenWindowProc( HWND hwnd, UINT msg,
1110 WPARAM wParam, LPARAM lParam )
1112 msi_dialog *dialog = (msi_dialog*) lParam;
1114 TRACE("%d %p\n", msg, dialog);
1116 switch (msg)
1118 case WM_MSI_DIALOG_CREATE:
1119 return msi_dialog_run_message_loop( dialog );
1120 case WM_MSI_DIALOG_DESTROY:
1121 msi_dialog_destroy( dialog );
1122 return 0;
1124 return DefWindowProcW( hwnd, msg, wParam, lParam );
1127 /* functions that interface to other modules within MSI */
1129 msi_dialog *msi_dialog_create( MSIPACKAGE* package, LPCWSTR szDialogName,
1130 msi_dialog_event_handler event_handler )
1132 MSIRECORD *rec = NULL;
1133 msi_dialog *dialog;
1135 TRACE("%p %s\n", package, debugstr_w(szDialogName));
1137 /* allocate the structure for the dialog to use */
1138 dialog = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY,
1139 sizeof *dialog + sizeof(WCHAR)*strlenW(szDialogName) );
1140 if( !dialog )
1141 return NULL;
1142 strcpyW( dialog->name, szDialogName );
1143 msiobj_addref( &package->hdr );
1144 dialog->package = package;
1145 dialog->event_handler = event_handler;
1146 dialog->finished = 0;
1148 /* verify that the dialog exists */
1149 rec = msi_get_dialog_record( dialog );
1150 if( !rec )
1152 HeapFree( GetProcessHeap(), 0, dialog );
1153 return NULL;
1155 dialog->attributes = MSI_RecordGetInteger( rec, 6 );
1156 msiobj_release( &rec->hdr );
1158 return dialog;
1161 static void msi_process_pending_messages(void)
1163 MSG msg;
1165 while( PeekMessageW( &msg, 0, 0, 0, PM_REMOVE ) )
1167 TranslateMessage( &msg );
1168 DispatchMessageW( &msg );
1172 void msi_dialog_end_dialog( msi_dialog *dialog )
1174 TRACE("%p\n", dialog);
1175 dialog->finished = 1;
1176 PostMessageW(dialog->hwnd, WM_NULL, 0, 0);
1179 void msi_dialog_check_messages( HANDLE handle )
1181 DWORD r;
1183 /* in threads other than the UI thread, block */
1184 if( uiThreadId != GetCurrentThreadId() )
1186 if( handle )
1187 WaitForSingleObject( handle, INFINITE );
1188 return;
1191 /* there's two choices for the UI thread */
1192 while (1)
1194 msi_process_pending_messages();
1196 if( !handle )
1197 break;
1200 * block here until somebody creates a new dialog or
1201 * the handle we're waiting on becomes ready
1203 r = MsgWaitForMultipleObjects( 1, &handle, 0, INFINITE, QS_ALLINPUT );
1204 if( r == WAIT_OBJECT_0 )
1205 break;
1209 UINT msi_dialog_run_message_loop( msi_dialog *dialog )
1211 HWND hwnd;
1213 if( !(dialog->attributes & msidbDialogAttributesVisible) )
1214 return ERROR_SUCCESS;
1216 if( uiThreadId != GetCurrentThreadId() )
1217 return SendMessageW( hMsiHiddenWindow, WM_MSI_DIALOG_CREATE, 0, (LPARAM) dialog );
1219 /* create the dialog window, don't show it yet */
1220 hwnd = CreateWindowW( szMsiDialogClass, dialog->name, WS_OVERLAPPEDWINDOW,
1221 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
1222 NULL, NULL, NULL, dialog );
1223 if( !hwnd )
1225 ERR("Failed to create dialog %s\n", debugstr_w( dialog->name ));
1226 return ERROR_FUNCTION_FAILED;
1229 ShowWindow( hwnd, SW_SHOW );
1230 UpdateWindow( hwnd );
1232 if( dialog->attributes & msidbDialogAttributesModal )
1234 while( !dialog->finished )
1236 MsgWaitForMultipleObjects( 0, NULL, 0, INFINITE, QS_ALLEVENTS );
1237 msi_process_pending_messages();
1240 else
1241 return ERROR_IO_PENDING;
1243 return ERROR_SUCCESS;
1246 void msi_dialog_do_preview( msi_dialog *dialog )
1248 TRACE("\n");
1249 dialog->attributes |= msidbDialogAttributesVisible;
1250 dialog->attributes &= ~msidbDialogAttributesModal;
1251 msi_dialog_run_message_loop( dialog );
1254 void msi_dialog_destroy( msi_dialog *dialog )
1256 if( uiThreadId != GetCurrentThreadId() )
1258 SendMessageW( hMsiHiddenWindow, WM_MSI_DIALOG_DESTROY, 0, (LPARAM) dialog );
1259 return;
1262 if( dialog->hwnd )
1263 ShowWindow( dialog->hwnd, SW_HIDE );
1265 /* destroy the list of controls */
1266 while( dialog->control_list )
1268 msi_control *t = dialog->control_list;
1269 dialog->control_list = t->next;
1270 /* leave dialog->hwnd - destroying parent destroys child windows */
1271 HeapFree( GetProcessHeap(), 0, t->property );
1272 HeapFree( GetProcessHeap(), 0, t->value );
1273 if( t->pic )
1274 IPicture_Release( t->pic );
1275 HeapFree( GetProcessHeap(), 0, t );
1278 /* destroy the list of fonts */
1279 while( dialog->font_list )
1281 msi_font *t = dialog->font_list;
1282 dialog->font_list = t->next;
1283 DeleteObject( t->hfont );
1284 HeapFree( GetProcessHeap(), 0, t );
1286 HeapFree( GetProcessHeap(), 0, dialog->default_font );
1288 if( dialog->hwnd )
1289 DestroyWindow( dialog->hwnd );
1291 msiobj_release( &dialog->package->hdr );
1292 dialog->package = NULL;
1293 HeapFree( GetProcessHeap(), 0, dialog );
1296 BOOL msi_dialog_register_class( void )
1298 WNDCLASSW cls;
1300 ZeroMemory( &cls, sizeof cls );
1301 cls.lpfnWndProc = MSIDialog_WndProc;
1302 cls.hInstance = NULL;
1303 cls.hIcon = LoadIconW(0, (LPWSTR)IDI_APPLICATION);
1304 cls.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
1305 cls.hbrBackground = (HBRUSH)(COLOR_WINDOW);
1306 cls.lpszMenuName = NULL;
1307 cls.lpszClassName = szMsiDialogClass;
1309 if( !RegisterClassW( &cls ) )
1310 return FALSE;
1312 cls.lpfnWndProc = MSIHiddenWindowProc;
1313 cls.lpszClassName = szMsiHiddenWindow;
1315 if( !RegisterClassW( &cls ) )
1316 return FALSE;
1318 uiThreadId = GetCurrentThreadId();
1320 hMsiHiddenWindow = CreateWindowW( szMsiHiddenWindow, NULL, WS_OVERLAPPED,
1321 0, 0, 100, 100, NULL, NULL, NULL, NULL );
1322 if( !hMsiHiddenWindow )
1323 return FALSE;
1325 return TRUE;
1328 void msi_dialog_unregister_class( void )
1330 DestroyWindow( hMsiHiddenWindow );
1331 UnregisterClassW( szMsiDialogClass, NULL );
1332 uiThreadId = 0;