Handle wParam in WM_PAINT properly: if non-null, it is the hdc we are
[wine] / dlls / msi / dialog.c
1 /*
2  * Implementation of the Microsoft Installer (msi.dll)
3  *
4  * Copyright 2005 Mike McCormack for CodeWeavers
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20
21 #include <stdarg.h>
22
23 #include "windef.h"
24 #include "winbase.h"
25 #include "winuser.h"
26 #include "winnls.h"
27 #include "wingdi.h"
28 #include "msi.h"
29 #include "msipriv.h"
30 #include "msidefs.h"
31
32 #include "wine/debug.h"
33 #include "wine/unicode.h"
34
35 #include "action.h"
36
37 WINE_DEFAULT_DEBUG_CHANNEL(msi);
38
39
40 const WCHAR szMsiDialogClass[] = {
41     'M','s','i','D','i','a','l','o','g','C','l','o','s','e','C','l','a','s','s',0
42 };
43 const static WCHAR szStatic[] = { 'S','t','a','t','i','c',0 };
44 const static WCHAR szButton[] = { 'B','U','T','T','O','N', 0 };
45
46 const static WCHAR szButtonData[] = { 'M','S','I','D','A','T','A',0 };
47
48 struct msi_control_tag;
49 typedef struct msi_control_tag msi_control;
50 typedef UINT (*msi_handler)( msi_dialog *, msi_control *, WPARAM );
51
52 struct msi_control_tag
53 {
54     struct msi_control_tag *next;
55     HWND hwnd;
56     msi_handler handler;
57     LPWSTR property;
58     WCHAR name[1];
59 };
60
61 typedef struct msi_font_tag
62 {
63     struct msi_font_tag *next;
64     HFONT hfont;
65     WCHAR name[1];
66 } msi_font;
67
68 struct msi_dialog_tag
69 {
70     MSIPACKAGE *package;
71     msi_dialog_event_handler event_handler;
72     BOOL finished;
73     INT scale;
74     DWORD attributes;
75     HWND hwnd;
76     LPWSTR default_font;
77     msi_font *font_list;
78     msi_control *control_list;
79     WCHAR name[1];
80 };
81
82 typedef UINT (*msi_dialog_control_func)( msi_dialog *dialog, MSIRECORD *rec );
83 struct control_handler 
84 {
85     LPCWSTR control_type;
86     msi_dialog_control_func func;
87 };
88
89 typedef struct
90 {
91     msi_dialog* dialog;
92     msi_control *parent;
93     DWORD       attributes;
94 } radio_button_group_descr;
95
96 static UINT msi_dialog_checkbox_handler( msi_dialog *, msi_control *, WPARAM );
97 static void msi_dialog_checkbox_sync_state( msi_dialog *, msi_control * );
98 static UINT msi_dialog_button_handler( msi_dialog *, msi_control *, WPARAM );
99 static UINT msi_dialog_edit_handler( msi_dialog *, msi_control *, WPARAM );
100 static UINT msi_dialog_radiogroup_handler( msi_dialog *, msi_control *, WPARAM param );
101 static LRESULT WINAPI MSIRadioGroup_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
102
103
104 INT msi_dialog_scale_unit( msi_dialog *dialog, INT val )
105 {
106     return (dialog->scale * val + 5) / 10;
107 }
108
109 /*
110  * msi_dialog_get_style
111  *
112  * Extract the {\style} string from the front of the text to display and
113  *  update the pointer.
114  */
115 static LPWSTR msi_dialog_get_style( LPCWSTR *text )
116 {
117     LPWSTR ret = NULL;
118     LPCWSTR p = *text, q;
119     DWORD len;
120
121     if( *p++ != '{' )
122         return ret;
123     q = strchrW( p, '}' );
124     if( !q )
125         return ret;
126     *text = ++q;
127     if( *p++ != '\\' )
128         return ret;
129     len = q - p;
130     
131     ret = HeapAlloc( GetProcessHeap(), 0, len*sizeof(WCHAR) );
132     if( !ret )
133         return ret;
134     strncpyW( ret, p, len );
135     ret[len-1] = 0;
136     return ret;
137 }
138
139 static UINT msi_dialog_add_font( MSIRECORD *rec, LPVOID param )
140 {
141     msi_dialog *dialog = param;
142     msi_font *font;
143     LPCWSTR face, name;
144     LOGFONTW lf;
145     INT style;
146     HDC hdc;
147
148     /* create a font and add it to the list */
149     name = MSI_RecordGetString( rec, 1 );
150     font = HeapAlloc( GetProcessHeap(), 0,
151                       sizeof *font + strlenW( name )*sizeof (WCHAR) );
152     strcpyW( font->name, name );
153     font->next = dialog->font_list;
154     dialog->font_list = font;
155
156     memset( &lf, 0, sizeof lf );
157     face = MSI_RecordGetString( rec, 2 );
158     lf.lfHeight = MSI_RecordGetInteger( rec, 3 );
159     style = MSI_RecordGetInteger( rec, 5 );
160     if( style & msidbTextStyleStyleBitsBold )
161         lf.lfWeight = FW_BOLD;
162     if( style & msidbTextStyleStyleBitsItalic )
163         lf.lfItalic = TRUE;
164     if( style & msidbTextStyleStyleBitsUnderline )
165         lf.lfUnderline = TRUE;
166     if( style & msidbTextStyleStyleBitsStrike )
167         lf.lfStrikeOut = TRUE;
168     lstrcpynW( lf.lfFaceName, face, LF_FACESIZE );
169
170     /* adjust the height */
171     hdc = GetDC( dialog->hwnd );
172     if (hdc)
173     {
174         lf.lfHeight = -MulDiv(lf.lfHeight, GetDeviceCaps(hdc, LOGPIXELSY), 72);
175         ReleaseDC( dialog->hwnd, hdc );
176     }
177
178     font->hfont = CreateFontIndirectW( &lf );
179
180     TRACE("Adding font style %s\n", debugstr_w(font->name) );
181
182     return ERROR_SUCCESS;
183 }
184
185 static msi_font *msi_dialog_find_font( msi_dialog *dialog, LPCWSTR name )
186 {
187     msi_font *font;
188
189     for( font = dialog->font_list; font; font = font->next )
190         if( !strcmpW( font->name, name ) )  /* FIXME: case sensitive? */
191             break;
192
193     return font;
194 }
195
196 static UINT msi_dialog_set_font( msi_dialog *dialog, HWND hwnd, LPCWSTR name )
197 {
198     msi_font *font;
199
200     font = msi_dialog_find_font( dialog, name );
201     if( font )
202         SendMessageW( hwnd, WM_SETFONT, (WPARAM) font->hfont, TRUE );
203     else
204         ERR("No font entry for %s\n", debugstr_w(name));
205     return ERROR_SUCCESS;
206 }
207
208 static UINT msi_dialog_build_font_list( msi_dialog *dialog )
209 {
210     static const WCHAR query[] = {
211       'S','E','L','E','C','T',' ','*',' ',
212       'F','R','O','M',' ','`','T','e','x','t','S','t','y','l','e','`',' ',0
213     };
214     UINT r;
215     MSIQUERY *view = NULL;
216
217     TRACE("dialog %p\n", dialog );
218
219     r = MSI_OpenQuery( dialog->package->db, &view, query );
220     if( r != ERROR_SUCCESS )
221         return r;
222
223     r = MSI_IterateRecords( view, NULL, msi_dialog_add_font, dialog );
224     msiobj_release( &view->hdr );
225
226     return r;
227 }
228
229 static msi_control *msi_dialog_create_window( msi_dialog *dialog,
230                 MSIRECORD *rec, LPCWSTR szCls, LPCWSTR name, LPCWSTR text,
231                 DWORD style, HWND parent )
232 {
233     DWORD x, y, width, height;
234     LPWSTR font = NULL, title = NULL;
235     msi_control *control;
236
237     style |= WS_CHILD | WS_GROUP;
238
239     control = HeapAlloc( GetProcessHeap(), 0,
240                          sizeof *control + strlenW(name)*sizeof(WCHAR) );
241     strcpyW( control->name, name );
242     control->next = dialog->control_list;
243     dialog->control_list = control;
244     control->handler = NULL;
245     control->property = NULL;
246
247     x = MSI_RecordGetInteger( rec, 4 );
248     y = MSI_RecordGetInteger( rec, 5 );
249     width = MSI_RecordGetInteger( rec, 6 );
250     height = MSI_RecordGetInteger( rec, 7 );
251
252     x = msi_dialog_scale_unit( dialog, x );
253     y = msi_dialog_scale_unit( dialog, y );
254     width = msi_dialog_scale_unit( dialog, width );
255     height = msi_dialog_scale_unit( dialog, height );
256
257     if( text )
258     {
259         font = msi_dialog_get_style( &text );
260         deformat_string( dialog->package, text, &title );
261     }
262
263     control->hwnd = CreateWindowW( szCls, title, style,
264                           x, y, width, height, parent, NULL, NULL, NULL );
265
266     TRACE("Dialog %s control %s hwnd %p\n",
267            debugstr_w(dialog->name), debugstr_w(text), control->hwnd );
268
269     msi_dialog_set_font( dialog, control->hwnd,
270                          font ? font : dialog->default_font );
271
272     HeapFree( GetProcessHeap(), 0, font );
273     HeapFree( GetProcessHeap(), 0, title );
274
275     return control;
276 }
277
278 /* everything except radio buttons */
279 static msi_control *msi_dialog_add_control( msi_dialog *dialog,
280                 MSIRECORD *rec, LPCWSTR szCls, DWORD style )
281 {
282     DWORD attributes;
283     LPCWSTR text, name;
284
285     name = MSI_RecordGetString( rec, 2 );
286     attributes = MSI_RecordGetInteger( rec, 8 );
287     text = MSI_RecordGetString( rec, 10 );
288     if( attributes & 1 )
289         style |= WS_VISIBLE;
290     if( ~attributes & 2 )
291         style |= WS_DISABLED;
292     return msi_dialog_create_window( dialog, rec, szCls, name, text,
293                                      style, dialog->hwnd );
294 }
295
296 static UINT msi_dialog_text_control( msi_dialog *dialog, MSIRECORD *rec )
297 {
298     TRACE("%p %p\n", dialog, rec);
299
300     msi_dialog_add_control( dialog, rec, szStatic, 0 );
301     return ERROR_SUCCESS;
302 }
303
304 static UINT msi_dialog_button_control( msi_dialog *dialog, MSIRECORD *rec )
305 {
306     msi_control *control;
307
308     TRACE("%p %p\n", dialog, rec);
309
310     control = msi_dialog_add_control( dialog, rec, szButton, 0 );
311     control->handler = msi_dialog_button_handler;
312
313     return ERROR_SUCCESS;
314 }
315
316 static UINT msi_dialog_checkbox_control( msi_dialog *dialog, MSIRECORD *rec )
317 {
318     msi_control *control;
319     LPCWSTR prop;
320
321     TRACE("%p %p\n", dialog, rec);
322
323     control = msi_dialog_add_control( dialog, rec, szButton,
324                                       BS_CHECKBOX | BS_MULTILINE );
325     control->handler = msi_dialog_checkbox_handler;
326     prop = MSI_RecordGetString( rec, 9 );
327     if( prop )
328         control->property = strdupW( prop );
329     msi_dialog_checkbox_sync_state( dialog, control );
330
331     return ERROR_SUCCESS;
332 }
333
334 static UINT msi_dialog_line_control( msi_dialog *dialog, MSIRECORD *rec )
335 {
336     TRACE("%p %p\n", dialog, rec);
337
338     msi_dialog_add_control( dialog, rec, szStatic, SS_ETCHEDHORZ | SS_SUNKEN );
339     return ERROR_SUCCESS;
340 }
341
342 static UINT msi_dialog_scrolltext_control( msi_dialog *dialog, MSIRECORD *rec )
343 {
344     const static WCHAR szEdit[] = { 'E','D','I','T',0 };
345
346     FIXME("%p %p\n", dialog, rec);
347
348     msi_dialog_add_control( dialog, rec, szEdit, WS_BORDER |
349                  ES_MULTILINE | WS_VSCROLL | ES_READONLY | ES_AUTOVSCROLL );
350
351     return ERROR_SUCCESS;
352 }
353
354 static UINT msi_dialog_bitmap_control( msi_dialog *dialog, MSIRECORD *rec )
355 {
356     TRACE("%p %p\n", dialog, rec);
357
358     msi_dialog_add_control( dialog, rec, szStatic,
359                             SS_BITMAP | SS_LEFT | SS_CENTERIMAGE );
360     return ERROR_SUCCESS;
361 }
362
363 static UINT msi_dialog_combo_control( msi_dialog *dialog, MSIRECORD *rec )
364 {
365     static const WCHAR szCombo[] = { 'C','O','M','B','O','B','O','X',0 };
366
367     msi_dialog_add_control( dialog, rec, szCombo,
368                             SS_BITMAP | SS_LEFT | SS_CENTERIMAGE );
369     return ERROR_SUCCESS;
370 }
371
372 static UINT msi_dialog_edit_control( msi_dialog *dialog, MSIRECORD *rec )
373 {
374     const static WCHAR szEdit[] = { 'E','D','I','T',0 };
375     msi_control *control;
376     LPCWSTR prop;
377     LPWSTR val;
378
379     control = msi_dialog_add_control( dialog, rec, szEdit, WS_BORDER );
380     control->handler = msi_dialog_edit_handler;
381     prop = MSI_RecordGetString( rec, 9 );
382     if( prop )
383         control->property = strdupW( prop );
384     val = load_dynamic_property( dialog->package, control->property, NULL );
385     SetWindowTextW( control->hwnd, val );
386     HeapFree( GetProcessHeap(), 0, val );
387     return ERROR_SUCCESS;
388 }
389
390 static UINT msi_dialog_pathedit_control( msi_dialog *dialog, MSIRECORD *rec )
391 {
392     FIXME("not implemented properly\n");
393     return msi_dialog_edit_control( dialog, rec );
394 }
395
396 /* radio buttons are a bit different to a normal control */
397 static UINT msi_dialog_create_radiobutton( MSIRECORD *rec, LPVOID param )
398 {
399     radio_button_group_descr *group = (radio_button_group_descr *)param;
400     msi_dialog *dialog = group->dialog;
401     msi_control *control;
402     LPCWSTR prop, text, name;
403     DWORD style;
404     DWORD attributes = group->attributes;
405
406     style = WS_CHILD | BS_AUTORADIOBUTTON | BS_MULTILINE;
407     name = MSI_RecordGetString( rec, 3 );
408     text = MSI_RecordGetString( rec, 8 );
409     if( attributes & 1 )
410         style |= WS_VISIBLE;
411     if( ~attributes & 2 )
412         style |= WS_DISABLED;
413
414     control = msi_dialog_create_window( dialog, rec, szButton, name, text,
415                                         style, group->parent->hwnd );
416     control->handler = msi_dialog_radiogroup_handler;
417
418     prop = MSI_RecordGetString( rec, 1 );
419     if( prop )
420         control->property = strdupW( prop );
421
422     return ERROR_SUCCESS;
423 }
424
425 static UINT msi_dialog_radiogroup_control( msi_dialog *dialog, MSIRECORD *rec )
426 {
427     static const WCHAR query[] = {
428         'S','E','L','E','C','T',' ','*',' ',
429         'F','R','O','M',' ','R','a','d','i','o','B','u','t','t','o','n',' ',
430         'W','H','E','R','E',' ',
431            '`','P','r','o','p','e','r','t','y','`',' ','=',' ','\'','%','s','\'',0};
432     UINT r;
433     LPCWSTR prop;
434     msi_control *control;
435     MSIQUERY *view = NULL;
436     radio_button_group_descr group;
437     MSIPACKAGE *package = dialog->package;
438
439     prop = MSI_RecordGetString( rec, 9 );
440
441     TRACE("%p %p %s\n", dialog, rec, debugstr_w( prop ));
442
443     /* Create parent group box to hold radio buttons */
444     control = msi_dialog_add_control( dialog, rec, szButton, BS_OWNERDRAW );
445
446     if (control->hwnd)
447     {
448         WNDPROC oldproc = (WNDPROC) SetWindowLongPtrW(control->hwnd, GWLP_WNDPROC,
449             (LONG_PTR)MSIRadioGroup_WndProc);
450         SetPropW(control->hwnd, szButtonData, oldproc);
451     }
452
453     if( prop )
454         control->property = strdupW( prop );
455
456     /* query the Radio Button table for all control in this group */
457     r = MSI_OpenQuery( package->db, &view, query, prop );
458     if( r != ERROR_SUCCESS )
459     {
460         ERR("query failed for dialog %s radio group %s\n", 
461             debugstr_w(dialog->name), debugstr_w(prop));
462         return ERROR_INVALID_PARAMETER;
463     }
464
465     group.dialog = dialog;
466     group.parent = control;
467     group.attributes = MSI_RecordGetInteger( rec, 8 );
468
469     r = MSI_IterateRecords( view, 0, msi_dialog_create_radiobutton, &group );
470     msiobj_release( &view->hdr );
471
472     return r;
473 }
474
475 static const WCHAR szText[] = { 'T','e','x','t',0 };
476 static const WCHAR szPushButton[] = { 'P','u','s','h','B','u','t','t','o','n',0 };
477 static const WCHAR szLine[] = { 'L','i','n','e',0 };
478 static const WCHAR szBitmap[] = { 'B','i','t','m','a','p',0 };
479 static const WCHAR szCheckBox[] = { 'C','h','e','c','k','B','o','x',0 };
480 static const WCHAR szScrollableText[] = {
481     'S','c','r','o','l','l','a','b','l','e','T','e','x','t',0 };
482 static const WCHAR szComboBox[] = { 'C','o','m','b','o','B','o','x',0 };
483 static const WCHAR szEdit[] = { 'E','d','i','t',0 };
484 static const WCHAR szMaskedEdit[] = { 'M','a','s','k','e','d','E','d','i','t',0 };
485 static const WCHAR szPathEdit[] = { 'P','a','t','h','E','d','i','t',0 };
486 static const WCHAR szRadioButtonGroup[] = { 
487     'R','a','d','i','o','B','u','t','t','o','n','G','r','o','u','p',0 };
488
489 struct control_handler msi_dialog_handler[] =
490 {
491     { szText, msi_dialog_text_control },
492     { szPushButton, msi_dialog_button_control },
493     { szLine, msi_dialog_line_control },
494     { szBitmap, msi_dialog_bitmap_control },
495     { szCheckBox, msi_dialog_checkbox_control },
496     { szScrollableText, msi_dialog_scrolltext_control },
497     { szComboBox, msi_dialog_combo_control },
498     { szEdit, msi_dialog_edit_control },
499     { szMaskedEdit, msi_dialog_edit_control },
500     { szPathEdit, msi_dialog_pathedit_control },
501     { szRadioButtonGroup, msi_dialog_radiogroup_control },
502 };
503
504 #define NUM_CONTROL_TYPES (sizeof msi_dialog_handler/sizeof msi_dialog_handler[0])
505
506 static UINT msi_dialog_create_controls( MSIRECORD *rec, LPVOID param )
507 {
508     msi_dialog *dialog = param;
509     LPCWSTR control_type;
510     UINT i;
511
512     /* find and call the function that can create this type of control */
513     control_type = MSI_RecordGetString( rec, 3 );
514     for( i=0; i<NUM_CONTROL_TYPES; i++ )
515         if (!strcmpiW( msi_dialog_handler[i].control_type, control_type ))
516             break;
517     if( i != NUM_CONTROL_TYPES )
518         msi_dialog_handler[i].func( dialog, rec );
519     else
520         ERR("no handler for element type %s\n", debugstr_w(control_type));
521
522     return ERROR_SUCCESS;
523 }
524
525 static UINT msi_dialog_fill_controls( msi_dialog *dialog )
526 {
527     static const WCHAR query[] = {
528         'S','E','L','E','C','T',' ','*',' ',
529         'F','R','O','M',' ','C','o','n','t','r','o','l',' ',
530         'W','H','E','R','E',' ',
531            '`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',0};
532     UINT r;
533     MSIQUERY *view = NULL;
534     MSIPACKAGE *package = dialog->package;
535
536     TRACE("%p %s\n", dialog, debugstr_w(dialog->name) );
537
538     /* query the Control table for all the elements of the control */
539     r = MSI_OpenQuery( package->db, &view, query, dialog->name );
540     if( r != ERROR_SUCCESS )
541     {
542         ERR("query failed for dialog %s\n", debugstr_w(dialog->name));
543         return ERROR_INVALID_PARAMETER;
544     }
545
546     r = MSI_IterateRecords( view, 0, msi_dialog_create_controls, dialog );
547     msiobj_release( &view->hdr );
548
549     return r;
550 }
551
552 static msi_control *msi_dialog_find_control( msi_dialog *dialog, LPCWSTR name )
553 {
554     msi_control *control;
555
556     for( control = dialog->control_list; control; control = control->next )
557         if( !strcmpW( control->name, name ) ) /* FIXME: case sensitive? */
558             break;
559     return control;
560 }
561
562 static msi_control *msi_dialog_find_control_by_hwnd( msi_dialog *dialog, HWND hwnd )
563 {
564     msi_control *control;
565
566     for( control = dialog->control_list; control; control = control->next )
567         if( hwnd == control->hwnd )
568             break;
569     return control;
570 }
571
572 static UINT msi_dialog_set_control_condition( MSIRECORD *rec, LPVOID param )
573 {
574     static const WCHAR szHide[] = { 'H','i','d','e',0 };
575     static const WCHAR szShow[] = { 'S','h','o','w',0 };
576     static const WCHAR szDisable[] = { 'D','i','s','a','b','l','e',0 };
577     static const WCHAR szEnable[] = { 'E','n','a','b','l','e',0 };
578     msi_dialog *dialog = param;
579     msi_control *control;
580     LPCWSTR name, action, condition;
581     UINT r;
582
583     name = MSI_RecordGetString( rec, 2 );
584     action = MSI_RecordGetString( rec, 3 );
585     condition = MSI_RecordGetString( rec, 4 );
586     r = MSI_EvaluateConditionW( dialog->package, condition );
587     control = msi_dialog_find_control( dialog, name );
588     if( r && control )
589     {
590         TRACE("%s control %s\n", debugstr_w(action), debugstr_w(name));
591
592         /* FIXME: case sensitive? */
593         if(!strcmpW(action, szHide))
594             ShowWindow(control->hwnd, SW_HIDE);
595         else if(!strcmpW(action, szShow))
596             ShowWindow(control->hwnd, SW_SHOW);
597         else if(!strcmpW(action, szDisable))
598             EnableWindow(control->hwnd, FALSE);
599         else if(!strcmpW(action, szEnable))
600             EnableWindow(control->hwnd, TRUE);
601         else
602             FIXME("Unhandled action %s\n", debugstr_w(action));
603     }
604
605     return ERROR_SUCCESS;
606 }
607
608 static UINT msi_dialog_evaluate_control_conditions( msi_dialog *dialog )
609 {
610     static const WCHAR query[] = {
611       'S','E','L','E','C','T',' ','*',' ',
612       'F','R','O','M',' ',
613         'C','o','n','t','r','o','l','C','o','n','d','i','t','i','o','n',' ',
614       'W','H','E','R','E',' ',
615         '`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',0
616     };
617     UINT r;
618     MSIQUERY *view = NULL;
619     MSIPACKAGE *package = dialog->package;
620
621     TRACE("%p %s\n", dialog, debugstr_w(dialog->name) );
622
623     /* query the Control table for all the elements of the control */
624     r = MSI_OpenQuery( package->db, &view, query, dialog->name );
625     if( r != ERROR_SUCCESS )
626     {
627         ERR("query failed for dialog %s\n", debugstr_w(dialog->name));
628         return ERROR_INVALID_PARAMETER;
629     }
630
631     r = MSI_IterateRecords( view, 0, msi_dialog_set_control_condition, dialog );
632     msiobj_release( &view->hdr );
633
634     return r;
635 }
636
637 /* figure out the height of 10 point MS Sans Serif */
638 static INT msi_dialog_get_sans_serif_height( HWND hwnd )
639 {
640     static const WCHAR szSansSerif[] = {
641         'M','S',' ','S','a','n','s',' ','S','e','r','i','f',0 };
642     LOGFONTW lf;
643     TEXTMETRICW tm;
644     BOOL r;
645     LONG height = 0;
646     HFONT hFont, hOldFont;
647     HDC hdc;
648
649     hdc = GetDC( hwnd );
650     if (hdc)
651     {
652         memset( &lf, 0, sizeof lf );
653         lf.lfHeight = MulDiv(10, GetDeviceCaps(hdc, LOGPIXELSY), 72);
654         strcpyW( lf.lfFaceName, szSansSerif );
655         hFont = CreateFontIndirectW(&lf);
656         if (hFont)
657         {
658             hOldFont = SelectObject( hdc, hFont );
659             r = GetTextMetricsW( hdc, &tm );
660             if (r)
661                 height = tm.tmHeight;
662             SelectObject( hdc, hOldFont );
663             DeleteObject( hFont );
664         }
665         ReleaseDC( hwnd, hdc );
666     }
667     return height;
668 }
669
670 static LRESULT msi_dialog_oncreate( HWND hwnd, LPCREATESTRUCTW cs )
671 {
672     static const WCHAR query[] = {
673         'S','E','L','E','C','T',' ','*',' ',
674         'F','R','O','M',' ','D','i','a','l','o','g',' ',
675         'W','H','E','R','E',' ',
676            '`','D','i','a','l','o','g','`',' ','=',' ','\'','%','s','\'',0};
677     static const WCHAR df[] = {
678         'D','e','f','a','u','l','t','U','I','F','o','n','t',0 };
679     msi_dialog *dialog = (msi_dialog*) cs->lpCreateParams;
680     MSIPACKAGE *package = dialog->package;
681     MSIQUERY *view = NULL;
682     MSIRECORD *rec = NULL;
683     DWORD width, height;
684     LPCWSTR text;
685     LPWSTR title = NULL;
686     UINT r;
687
688     TRACE("%p %p\n", dialog, package);
689
690     dialog->hwnd = hwnd;
691     SetWindowLongPtrW( hwnd, GWLP_USERDATA, (LONG_PTR) dialog );
692
693     /* fetch the associated record from the Dialog table */
694     r = MSI_OpenQuery( package->db, &view, query, dialog->name );
695     if( r != ERROR_SUCCESS )
696     {
697         ERR("query failed for dialog %s\n", debugstr_w(dialog->name));
698         return -1;
699     }
700     MSI_ViewExecute( view, NULL );
701     MSI_ViewFetch( view, &rec );
702     MSI_ViewClose( view );
703     msiobj_release( &view->hdr );
704
705     if( !rec )
706     {
707         TRACE("No record found for dialog %s\n", debugstr_w(dialog->name));
708         return -1;
709     }
710
711     dialog->scale = msi_dialog_get_sans_serif_height(dialog->hwnd);
712
713     width = MSI_RecordGetInteger( rec, 4 );
714     height = MSI_RecordGetInteger( rec, 5 );
715     dialog->attributes = MSI_RecordGetInteger( rec, 6 );
716     text = MSI_RecordGetString( rec, 7 );
717
718     width = msi_dialog_scale_unit( dialog, width );
719     height = msi_dialog_scale_unit( dialog, height ) + 25; /* FIXME */
720
721     dialog->default_font = load_dynamic_property( dialog->package, df, NULL );
722
723     deformat_string( dialog->package, text, &title );
724     SetWindowTextW( hwnd, title );
725     SetWindowPos( hwnd, 0, 0, 0, width, height,
726                   SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOREDRAW );
727
728     HeapFree( GetProcessHeap(), 0, title );
729     msiobj_release( &rec->hdr );
730
731     msi_dialog_build_font_list( dialog );
732     msi_dialog_fill_controls( dialog );
733     msi_dialog_evaluate_control_conditions( dialog );
734
735     return 0;
736 }
737
738 static UINT msi_dialog_send_event( msi_dialog *dialog, LPCWSTR event, LPCWSTR arg )
739 {
740     LPWSTR event_fmt = NULL, arg_fmt = NULL;
741
742     TRACE("Sending control event %s %s\n", debugstr_w(event), debugstr_w(arg));
743
744     deformat_string( dialog->package, event, &event_fmt );
745     deformat_string( dialog->package, arg, &arg_fmt );
746
747     dialog->event_handler( dialog->package, event_fmt, arg_fmt, dialog );
748
749     HeapFree( GetProcessHeap(), 0, event_fmt );
750     HeapFree( GetProcessHeap(), 0, arg_fmt );
751
752     return ERROR_SUCCESS;
753 }
754
755 static UINT msi_dialog_set_property( msi_dialog *dialog, LPCWSTR event, LPCWSTR arg )
756 {
757     static const WCHAR szNullArg[] = { '{','}',0 };
758     LPWSTR p, prop, arg_fmt = NULL;
759     UINT len;
760
761     len = strlenW(event);
762     prop = HeapAlloc( GetProcessHeap(), 0, len*sizeof(WCHAR));
763     strcpyW( prop, &event[1] );
764     p = strchrW( prop, ']' );
765     if( p && p[1] == 0 )
766     {
767         *p = 0;
768         if( strcmpW( szNullArg, arg ) )
769             deformat_string( dialog->package, arg, &arg_fmt );
770         MSI_SetPropertyW( dialog->package, prop, arg_fmt );
771     }
772     else
773         ERR("Badly formatted property string - what happens?\n");
774     HeapFree( GetProcessHeap(), 0, prop );
775     return ERROR_SUCCESS;
776 }
777
778 static UINT msi_dialog_control_event( MSIRECORD *rec, LPVOID param )
779 {
780     msi_dialog *dialog = param;
781     LPCWSTR condition, event, arg;
782     UINT r;
783
784     condition = MSI_RecordGetString( rec, 5 );
785     r = MSI_EvaluateConditionW( dialog->package, condition );
786     if( r )
787     {
788         event = MSI_RecordGetString( rec, 3 );
789         arg = MSI_RecordGetString( rec, 4 );
790         if( event[0] == '[' )
791             msi_dialog_set_property( dialog, event, arg );
792         else
793             msi_dialog_send_event( dialog, event, arg );
794     }
795
796     return ERROR_SUCCESS;
797 }
798
799 static UINT msi_dialog_button_handler( msi_dialog *dialog,
800                                        msi_control *control, WPARAM param )
801 {
802     static const WCHAR query[] = {
803       'S','E','L','E','C','T',' ','*',' ',
804       'F','R','O','M',' ','C','o','n','t','r','o','l','E','v','e','n','t',' ',
805       'W','H','E','R','E',' ',
806          '`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',' ',
807       'A','N','D',' ',
808          '`','C','o','n','t','r','o','l','_','`',' ','=',' ','\'','%','s','\'',' ',
809       'O','R','D','E','R',' ','B','Y',' ','`','O','r','d','e','r','i','n','g','`',0
810     };
811     MSIQUERY *view = NULL;
812     UINT r;
813
814     if( HIWORD(param) != BN_CLICKED )
815         return ERROR_SUCCESS;
816
817     r = MSI_OpenQuery( dialog->package->db, &view, query,
818                        dialog->name, control->name );
819     if( r != ERROR_SUCCESS )
820     {
821         ERR("query failed\n");
822         return 0;
823     }
824
825     r = MSI_IterateRecords( view, 0, msi_dialog_control_event, dialog );
826     msiobj_release( &view->hdr );
827
828     return r;
829 }
830
831 static UINT msi_dialog_get_checkbox_state( msi_dialog *dialog,
832                 msi_control *control )
833 {
834     WCHAR state[2] = { 0 };
835     DWORD sz = 2;
836
837     MSI_GetPropertyW( dialog->package, control->property, state, &sz );
838     return atoiW( state ) ? 1 : 0;
839 }
840
841 static void msi_dialog_set_checkbox_state( msi_dialog *dialog,
842                 msi_control *control, UINT state )
843 {
844     WCHAR szState[2] = { '0', 0 };
845
846     if( state )
847         szState[0]++;
848     MSI_SetPropertyW( dialog->package, control->property, szState );
849 }
850
851 static void msi_dialog_checkbox_sync_state( msi_dialog *dialog,
852                 msi_control *control )
853 {
854     UINT state;
855
856     state = msi_dialog_get_checkbox_state( dialog, control );
857     SendMessageW( control->hwnd, BM_SETCHECK,
858                   state ? BST_CHECKED : BST_UNCHECKED, 0 );
859 }
860
861 static UINT msi_dialog_checkbox_handler( msi_dialog *dialog,
862                 msi_control *control, WPARAM param )
863 {
864     UINT state;
865
866     if( HIWORD(param) != BN_CLICKED )
867         return ERROR_SUCCESS;
868
869     TRACE("clicked checkbox %s, set %s\n", debugstr_w(control->name),
870           debugstr_w(control->property));
871
872     state = msi_dialog_get_checkbox_state( dialog, control );
873     state = state ? 0 : 1;
874     msi_dialog_set_checkbox_state( dialog, control, state );
875     msi_dialog_checkbox_sync_state( dialog, control );
876
877     return msi_dialog_button_handler( dialog, control, param );
878 }
879
880 static UINT msi_dialog_edit_handler( msi_dialog *dialog,
881                 msi_control *control, WPARAM param )
882 {
883     UINT sz, r;
884     LPWSTR buf;
885
886     if( HIWORD(param) != EN_CHANGE )
887         return ERROR_SUCCESS;
888
889     TRACE("edit %s contents changed, set %s\n", debugstr_w(control->name),
890           debugstr_w(control->property));
891
892     sz = 0x20;
893     buf = HeapAlloc( GetProcessHeap(), 0, sz*sizeof(WCHAR) );
894     while( buf )
895     {
896         r = GetWindowTextW( control->hwnd, buf, sz );
897         if( r < (sz-1) )
898             break;
899             sz *= 2;
900         buf = HeapReAlloc( GetProcessHeap(), 0, buf, sz*sizeof(WCHAR) );
901     }
902
903     MSI_SetPropertyW( dialog->package, control->property, buf );
904
905     HeapFree( GetProcessHeap(), 0, buf );
906
907     return ERROR_SUCCESS;
908 }
909
910 static UINT msi_dialog_radiogroup_handler( msi_dialog *dialog,
911                 msi_control *control, WPARAM param )
912 {
913     if( HIWORD(param) != BN_CLICKED )
914         return ERROR_SUCCESS;
915
916     TRACE("clicked radio button %s, set %s\n", debugstr_w(control->name),
917           debugstr_w(control->property));
918
919     MSI_SetPropertyW( dialog->package, control->property, control->name );
920
921     return msi_dialog_button_handler( dialog, control, param );
922 }
923
924 static LRESULT msi_dialog_oncommand( msi_dialog *dialog, WPARAM param, HWND hwnd )
925 {
926     msi_control *control;
927
928     TRACE("%p %p %08x\n", dialog, hwnd, param);
929
930     control = msi_dialog_find_control_by_hwnd( dialog, hwnd );
931     if( control )
932     {
933         if( control->handler )
934         {
935             control->handler( dialog, control, param );
936             msi_dialog_evaluate_control_conditions( dialog );
937         }
938     }
939     else
940         ERR("button click from nowhere\n");
941     return 0;
942 }
943
944 static LRESULT WINAPI MSIDialog_WndProc( HWND hwnd, UINT msg,
945                 WPARAM wParam, LPARAM lParam )
946 {
947     msi_dialog *dialog = (LPVOID) GetWindowLongPtrW( hwnd, GWLP_USERDATA );
948
949     TRACE(" 0x%04x\n", msg);
950     switch (msg)
951     {
952     case WM_CREATE:
953         return msi_dialog_oncreate( hwnd, (LPCREATESTRUCTW)lParam );
954
955     case WM_COMMAND:
956         return msi_dialog_oncommand( dialog, wParam, (HWND)lParam );
957
958     case WM_DESTROY:
959         dialog->hwnd = NULL;
960         return 0;
961     }
962     return DefWindowProcW(hwnd, msg, wParam, lParam);
963 }
964
965 /* functions that interface to other modules within MSI */
966
967 msi_dialog *msi_dialog_create( MSIPACKAGE* package, LPCWSTR szDialogName,
968                                 msi_dialog_event_handler event_handler )
969 {
970     msi_dialog *dialog;
971     HWND hwnd;
972
973     TRACE("%p %s\n", package, debugstr_w(szDialogName));
974
975     /* allocate the structure for the dialog to use */
976     dialog = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY,
977                         sizeof *dialog + sizeof(WCHAR)*strlenW(szDialogName) );
978     if( !dialog )
979         return NULL;
980     strcpyW( dialog->name, szDialogName );
981     dialog->package = package;
982     dialog->event_handler = event_handler;
983
984     /* create the dialog window, don't show it yet */
985     hwnd = CreateWindowW( szMsiDialogClass, szDialogName, WS_OVERLAPPEDWINDOW,
986                      CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
987                      NULL, NULL, NULL, dialog );
988     if( !hwnd )
989     {
990         ERR("Failed to create dialog %s\n", debugstr_w( szDialogName ));
991         msi_dialog_destroy( dialog );
992         return NULL;
993     }
994
995     return dialog;
996 }
997
998 void msi_dialog_end_dialog( msi_dialog *dialog )
999 {
1000     dialog->finished = 1;
1001 }
1002
1003 UINT msi_dialog_run_message_loop( msi_dialog *dialog )
1004 {
1005     MSG msg;
1006
1007     if( dialog->attributes & msidbDialogAttributesVisible )
1008     {
1009         ShowWindow( dialog->hwnd, SW_SHOW );
1010         UpdateWindow( dialog->hwnd );
1011     }
1012
1013     if( dialog->attributes & msidbDialogAttributesModal )
1014     {
1015         while( !dialog->finished && GetMessageW( &msg, 0, 0, 0 ) )
1016         {
1017             TranslateMessage( &msg );
1018             DispatchMessageW( &msg );
1019         }
1020     }
1021     else
1022         return ERROR_IO_PENDING;
1023
1024     return ERROR_SUCCESS;
1025 }
1026
1027 void msi_dialog_check_messages( msi_dialog *dialog, HANDLE handle )
1028 {
1029     MSG msg;
1030     DWORD r;
1031
1032     do
1033     {
1034         while( PeekMessageW( &msg, 0, 0, 0, PM_REMOVE ) )
1035         {
1036             TranslateMessage( &msg );
1037             DispatchMessageW( &msg );
1038         }
1039         if( !handle )
1040             break;
1041         r = MsgWaitForMultipleObjects( 1, &handle, 0, INFINITE, QS_ALLEVENTS );
1042     }
1043     while( WAIT_OBJECT_0 != r );
1044 }
1045
1046 void msi_dialog_do_preview( msi_dialog *dialog )
1047 {
1048     dialog->attributes |= msidbDialogAttributesVisible;
1049     dialog->attributes &= ~msidbDialogAttributesModal;
1050     msi_dialog_run_message_loop( dialog );
1051 }
1052
1053 void msi_dialog_destroy( msi_dialog *dialog )
1054 {
1055     if( dialog->hwnd )
1056         ShowWindow( dialog->hwnd, SW_HIDE );
1057     
1058     /* destroy the list of controls */
1059     while( dialog->control_list )
1060     {
1061         msi_control *t = dialog->control_list;
1062         dialog->control_list = t->next;
1063         /* leave dialog->hwnd - destroying parent destroys child windows */
1064         HeapFree( GetProcessHeap(), 0, t->property );
1065         HeapFree( GetProcessHeap(), 0, t );
1066     }
1067
1068     /* destroy the list of fonts */
1069     while( dialog->font_list )
1070     {
1071         msi_font *t = dialog->font_list;
1072         dialog->font_list = t->next;
1073         DeleteObject( t->hfont );
1074         HeapFree( GetProcessHeap(), 0, t );
1075     }
1076     HeapFree( GetProcessHeap(), 0, dialog->default_font );
1077
1078     if( dialog->hwnd )
1079         DestroyWindow( dialog->hwnd );
1080
1081     dialog->package = NULL;
1082     HeapFree( GetProcessHeap(), 0, dialog );
1083 }
1084
1085 void msi_dialog_register_class( void )
1086 {
1087     WNDCLASSW cls;
1088
1089     ZeroMemory( &cls, sizeof cls );
1090     cls.lpfnWndProc   = MSIDialog_WndProc;
1091     cls.hInstance     = NULL;
1092     cls.hIcon         = LoadIconW(0, (LPWSTR)IDI_APPLICATION);
1093     cls.hCursor       = LoadCursorW(0, (LPWSTR)IDC_ARROW);
1094     cls.hbrBackground = (HBRUSH)(COLOR_WINDOW);
1095     cls.lpszMenuName  = NULL;
1096     cls.lpszClassName = szMsiDialogClass;
1097
1098     RegisterClassW( &cls );
1099 }
1100
1101 void msi_dialog_unregister_class( void )
1102 {
1103     UnregisterClassW( szMsiDialogClass, NULL );
1104 }
1105
1106 static LRESULT WINAPI MSIRadioGroup_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
1107 {
1108     WNDPROC oldproc = (WNDPROC) GetPropW(hWnd, szButtonData);
1109
1110     TRACE("hWnd %p msg %04x wParam 0x%08x lParam 0x%08lx\n", hWnd, msg, wParam, lParam);
1111
1112     if (msg == WM_COMMAND) /* Forward notifications to dialog */
1113         SendMessageW(GetParent(hWnd), msg, wParam, lParam);
1114
1115     return CallWindowProcW(oldproc, hWnd, msg, wParam, lParam);
1116 }