[MSI]
[reactos.git] / reactos / dll / win32 / msi / dialog.c
1 /*
2 * Implementation of the Microsoft Installer (msi.dll)
3 *
4 * Copyright 2005 Mike McCormack for CodeWeavers
5 * Copyright 2005 Aric Stewart for CodeWeavers
6 *
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.
11 *
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.
16 *
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
20 */
21
22 #include "msipriv.h"
23
24 #include <olectl.h>
25 #include <richedit.h>
26 #include <shellapi.h>
27
28 WINE_DEFAULT_DEBUG_CHANNEL(msi);
29
30 extern HINSTANCE msi_hInstance;
31
32 struct msi_control_tag;
33 typedef struct msi_control_tag msi_control;
34 typedef UINT (*msi_handler)( msi_dialog *, msi_control *, WPARAM );
35 typedef void (*msi_update)( msi_dialog *, msi_control * );
36 typedef UINT (*control_event_handler)( msi_dialog *, const WCHAR *, const WCHAR * );
37
38 struct msi_control_tag
39 {
40 struct list entry;
41 HWND hwnd;
42 msi_handler handler;
43 msi_update update;
44 LPWSTR property;
45 LPWSTR value;
46 HBITMAP hBitmap;
47 HICON hIcon;
48 LPWSTR tabnext;
49 LPWSTR type;
50 HMODULE hDll;
51 float progress_current;
52 float progress_max;
53 BOOL progress_backwards;
54 DWORD attributes;
55 WCHAR name[1];
56 };
57
58 typedef struct msi_font_tag
59 {
60 struct list entry;
61 HFONT hfont;
62 COLORREF color;
63 WCHAR name[1];
64 } msi_font;
65
66 struct msi_dialog_tag
67 {
68 MSIPACKAGE *package;
69 msi_dialog *parent;
70 control_event_handler event_handler;
71 BOOL finished;
72 INT scale;
73 DWORD attributes;
74 SIZE size;
75 HWND hwnd;
76 LPWSTR default_font;
77 struct list fonts;
78 struct list controls;
79 HWND hWndFocus;
80 LPWSTR control_default;
81 LPWSTR control_cancel;
82 WCHAR name[1];
83 };
84
85 struct subscriber
86 {
87 struct list entry;
88 msi_dialog *dialog;
89 WCHAR *event;
90 WCHAR *control;
91 WCHAR *attribute;
92 };
93
94 typedef UINT (*msi_dialog_control_func)( msi_dialog *dialog, MSIRECORD *rec );
95 struct control_handler
96 {
97 LPCWSTR control_type;
98 msi_dialog_control_func func;
99 };
100
101 typedef struct
102 {
103 msi_dialog* dialog;
104 msi_control *parent;
105 DWORD attributes;
106 LPWSTR propval;
107 } radio_button_group_descr;
108
109 static const WCHAR szMsiDialogClass[] = { 'M','s','i','D','i','a','l','o','g','C','l','o','s','e','C','l','a','s','s',0 };
110 static const WCHAR szMsiHiddenWindow[] = { 'M','s','i','H','i','d','d','e','n','W','i','n','d','o','w',0 };
111 static const WCHAR szStatic[] = { 'S','t','a','t','i','c',0 };
112 static const WCHAR szButton[] = { 'B','U','T','T','O','N', 0 };
113 static const WCHAR szButtonData[] = { 'M','S','I','D','A','T','A',0 };
114 static const WCHAR szProgress[] = { 'P','r','o','g','r','e','s','s',0 };
115 static const WCHAR szText[] = { 'T','e','x','t',0 };
116 static const WCHAR szPushButton[] = { 'P','u','s','h','B','u','t','t','o','n',0 };
117 static const WCHAR szLine[] = { 'L','i','n','e',0 };
118 static const WCHAR szBitmap[] = { 'B','i','t','m','a','p',0 };
119 static const WCHAR szCheckBox[] = { 'C','h','e','c','k','B','o','x',0 };
120 static const WCHAR szScrollableText[] = { 'S','c','r','o','l','l','a','b','l','e','T','e','x','t',0 };
121 static const WCHAR szComboBox[] = { 'C','o','m','b','o','B','o','x',0 };
122 static const WCHAR szEdit[] = { 'E','d','i','t',0 };
123 static const WCHAR szMaskedEdit[] = { 'M','a','s','k','e','d','E','d','i','t',0 };
124 static const WCHAR szPathEdit[] = { 'P','a','t','h','E','d','i','t',0 };
125 static const WCHAR szProgressBar[] = { 'P','r','o','g','r','e','s','s','B','a','r',0 };
126 static const WCHAR szSetProgress[] = { 'S','e','t','P','r','o','g','r','e','s','s',0 };
127 static const WCHAR szRadioButtonGroup[] = { 'R','a','d','i','o','B','u','t','t','o','n','G','r','o','u','p',0 };
128 static const WCHAR szIcon[] = { 'I','c','o','n',0 };
129 static const WCHAR szSelectionTree[] = { 'S','e','l','e','c','t','i','o','n','T','r','e','e',0 };
130 static const WCHAR szGroupBox[] = { 'G','r','o','u','p','B','o','x',0 };
131 static const WCHAR szListBox[] = { 'L','i','s','t','B','o','x',0 };
132 static const WCHAR szDirectoryCombo[] = { 'D','i','r','e','c','t','o','r','y','C','o','m','b','o',0 };
133 static const WCHAR szDirectoryList[] = { 'D','i','r','e','c','t','o','r','y','L','i','s','t',0 };
134 static const WCHAR szVolumeCostList[] = { 'V','o','l','u','m','e','C','o','s','t','L','i','s','t',0 };
135 static const WCHAR szVolumeSelectCombo[] = { 'V','o','l','u','m','e','S','e','l','e','c','t','C','o','m','b','o',0 };
136 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};
137 static const WCHAR szSelectionPath[] = {'S','e','l','e','c','t','i','o','n','P','a','t','h',0};
138 static const WCHAR szProperty[] = {'P','r','o','p','e','r','t','y',0};
139 static const WCHAR szHyperLink[] = {'H','y','p','e','r','L','i','n','k',0};
140
141 /* dialog sequencing */
142
143 #define WM_MSI_DIALOG_CREATE (WM_USER+0x100)
144 #define WM_MSI_DIALOG_DESTROY (WM_USER+0x101)
145
146 #define USER_INSTALLSTATE_ALL 0x1000
147
148 static DWORD uiThreadId;
149 static HWND hMsiHiddenWindow;
150
151 static LPWSTR msi_get_window_text( HWND hwnd )
152 {
153 UINT sz, r;
154 LPWSTR buf;
155
156 sz = 0x20;
157 buf = msi_alloc( sz*sizeof(WCHAR) );
158 while ( buf )
159 {
160 r = GetWindowTextW( hwnd, buf, sz );
161 if ( r < (sz - 1) )
162 break;
163 sz *= 2;
164 buf = msi_realloc( buf, sz*sizeof(WCHAR) );
165 }
166
167 return buf;
168 }
169
170 static INT msi_dialog_scale_unit( msi_dialog *dialog, INT val )
171 {
172 return MulDiv( val, dialog->scale, 12 );
173 }
174
175 static msi_control *msi_dialog_find_control( msi_dialog *dialog, LPCWSTR name )
176 {
177 msi_control *control;
178
179 if( !name )
180 return NULL;
181 if( !dialog->hwnd )
182 return NULL;
183 LIST_FOR_EACH_ENTRY( control, &dialog->controls, msi_control, entry )
184 if( !strcmpW( control->name, name ) ) /* FIXME: case sensitive? */
185 return control;
186 return NULL;
187 }
188
189 static msi_control *msi_dialog_find_control_by_type( msi_dialog *dialog, LPCWSTR type )
190 {
191 msi_control *control;
192
193 if( !type )
194 return NULL;
195 if( !dialog->hwnd )
196 return NULL;
197 LIST_FOR_EACH_ENTRY( control, &dialog->controls, msi_control, entry )
198 if( !strcmpW( control->type, type ) ) /* FIXME: case sensitive? */
199 return control;
200 return NULL;
201 }
202
203 static msi_control *msi_dialog_find_control_by_hwnd( msi_dialog *dialog, HWND hwnd )
204 {
205 msi_control *control;
206
207 if( !dialog->hwnd )
208 return NULL;
209 LIST_FOR_EACH_ENTRY( control, &dialog->controls, msi_control, entry )
210 if( hwnd == control->hwnd )
211 return control;
212 return NULL;
213 }
214
215 static LPWSTR msi_get_deformatted_field( MSIPACKAGE *package, MSIRECORD *rec, int field )
216 {
217 LPCWSTR str = MSI_RecordGetString( rec, field );
218 LPWSTR ret = NULL;
219
220 if (str)
221 deformat_string( package, str, &ret );
222 return ret;
223 }
224
225 static LPWSTR msi_dialog_dup_property( msi_dialog *dialog, LPCWSTR property, BOOL indirect )
226 {
227 LPWSTR prop = NULL;
228
229 if (!property)
230 return NULL;
231
232 if (indirect)
233 prop = msi_dup_property( dialog->package->db, property );
234
235 if (!prop)
236 prop = strdupW( property );
237
238 return prop;
239 }
240
241 /*
242 * msi_dialog_get_style
243 *
244 * Extract the {\style} string from the front of the text to display and
245 * update the pointer. Only the last style in a list is applied.
246 */
247 static LPWSTR msi_dialog_get_style( LPCWSTR p, LPCWSTR *rest )
248 {
249 LPWSTR ret;
250 LPCWSTR q, i, first;
251 DWORD len;
252
253 q = NULL;
254 *rest = p;
255 if( !p )
256 return NULL;
257
258 while ((first = strchrW( p, '{' )) && (q = strchrW( first + 1, '}' )))
259 {
260 p = first + 1;
261 if( *p != '\\' && *p != '&' )
262 return NULL;
263
264 /* little bit of sanity checking to stop us getting confused with RTF */
265 for( i=++p; i<q; i++ )
266 if( *i == '}' || *i == '\\' )
267 return NULL;
268 }
269
270 if (!q)
271 return NULL;
272
273 *rest = ++q;
274 len = q - p;
275
276 ret = msi_alloc( len*sizeof(WCHAR) );
277 if( !ret )
278 return ret;
279 memcpy( ret, p, len*sizeof(WCHAR) );
280 ret[len-1] = 0;
281 return ret;
282 }
283
284 static UINT msi_dialog_add_font( MSIRECORD *rec, LPVOID param )
285 {
286 msi_dialog *dialog = param;
287 msi_font *font;
288 LPCWSTR face, name;
289 LOGFONTW lf;
290 INT style;
291 HDC hdc;
292
293 /* create a font and add it to the list */
294 name = MSI_RecordGetString( rec, 1 );
295 font = msi_alloc( FIELD_OFFSET( msi_font, name[strlenW( name ) + 1] ));
296 strcpyW( font->name, name );
297 list_add_head( &dialog->fonts, &font->entry );
298
299 font->color = MSI_RecordGetInteger( rec, 4 );
300
301 memset( &lf, 0, sizeof lf );
302 face = MSI_RecordGetString( rec, 2 );
303 lf.lfHeight = MSI_RecordGetInteger( rec, 3 );
304 style = MSI_RecordGetInteger( rec, 5 );
305 if( style & msidbTextStyleStyleBitsBold )
306 lf.lfWeight = FW_BOLD;
307 if( style & msidbTextStyleStyleBitsItalic )
308 lf.lfItalic = TRUE;
309 if( style & msidbTextStyleStyleBitsUnderline )
310 lf.lfUnderline = TRUE;
311 if( style & msidbTextStyleStyleBitsStrike )
312 lf.lfStrikeOut = TRUE;
313 lstrcpynW( lf.lfFaceName, face, LF_FACESIZE );
314
315 /* adjust the height */
316 hdc = GetDC( dialog->hwnd );
317 if (hdc)
318 {
319 lf.lfHeight = -MulDiv(lf.lfHeight, GetDeviceCaps(hdc, LOGPIXELSY), 72);
320 ReleaseDC( dialog->hwnd, hdc );
321 }
322
323 font->hfont = CreateFontIndirectW( &lf );
324
325 TRACE("Adding font style %s\n", debugstr_w(font->name) );
326
327 return ERROR_SUCCESS;
328 }
329
330 static msi_font *msi_dialog_find_font( msi_dialog *dialog, LPCWSTR name )
331 {
332 msi_font *font = NULL;
333
334 LIST_FOR_EACH_ENTRY( font, &dialog->fonts, msi_font, entry )
335 if( !strcmpW( font->name, name ) ) /* FIXME: case sensitive? */
336 break;
337
338 return font;
339 }
340
341 static UINT msi_dialog_set_font( msi_dialog *dialog, HWND hwnd, LPCWSTR name )
342 {
343 msi_font *font;
344
345 font = msi_dialog_find_font( dialog, name );
346 if( font )
347 SendMessageW( hwnd, WM_SETFONT, (WPARAM) font->hfont, TRUE );
348 else
349 ERR("No font entry for %s\n", debugstr_w(name));
350 return ERROR_SUCCESS;
351 }
352
353 static UINT msi_dialog_build_font_list( msi_dialog *dialog )
354 {
355 static const WCHAR query[] = {
356 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
357 '`','T','e','x','t','S','t','y','l','e','`',0};
358 MSIQUERY *view;
359 UINT r;
360
361 TRACE("dialog %p\n", dialog );
362
363 r = MSI_OpenQuery( dialog->package->db, &view, query );
364 if( r != ERROR_SUCCESS )
365 return r;
366
367 r = MSI_IterateRecords( view, NULL, msi_dialog_add_font, dialog );
368 msiobj_release( &view->hdr );
369 return r;
370 }
371
372 static void msi_destroy_control( msi_control *t )
373 {
374 list_remove( &t->entry );
375 /* leave dialog->hwnd - destroying parent destroys child windows */
376 msi_free( t->property );
377 msi_free( t->value );
378 if( t->hBitmap )
379 DeleteObject( t->hBitmap );
380 if( t->hIcon )
381 DestroyIcon( t->hIcon );
382 msi_free( t->tabnext );
383 msi_free( t->type );
384 if (t->hDll)
385 FreeLibrary( t->hDll );
386 msi_free( t );
387 }
388
389 static msi_control *dialog_create_window( msi_dialog *dialog, MSIRECORD *rec, DWORD exstyle,
390 const WCHAR *szCls, const WCHAR *name, const WCHAR *text,
391 DWORD style, HWND parent )
392 {
393 DWORD x, y, width, height;
394 LPWSTR font = NULL, title_font = NULL;
395 LPCWSTR title = NULL;
396 msi_control *control;
397
398 style |= WS_CHILD;
399
400 control = msi_alloc( FIELD_OFFSET( msi_control, name[strlenW( name ) + 1] ));
401 if (!control)
402 return NULL;
403
404 strcpyW( control->name, name );
405 list_add_tail( &dialog->controls, &control->entry );
406 control->handler = NULL;
407 control->update = NULL;
408 control->property = NULL;
409 control->value = NULL;
410 control->hBitmap = NULL;
411 control->hIcon = NULL;
412 control->hDll = NULL;
413 control->tabnext = strdupW( MSI_RecordGetString( rec, 11) );
414 control->type = strdupW( MSI_RecordGetString( rec, 3 ) );
415 control->progress_current = 0;
416 control->progress_max = 100;
417 control->progress_backwards = FALSE;
418
419 x = MSI_RecordGetInteger( rec, 4 );
420 y = MSI_RecordGetInteger( rec, 5 );
421 width = MSI_RecordGetInteger( rec, 6 );
422 height = MSI_RecordGetInteger( rec, 7 );
423
424 x = msi_dialog_scale_unit( dialog, x );
425 y = msi_dialog_scale_unit( dialog, y );
426 width = msi_dialog_scale_unit( dialog, width );
427 height = msi_dialog_scale_unit( dialog, height );
428
429 if( text )
430 {
431 deformat_string( dialog->package, text, &title_font );
432 font = msi_dialog_get_style( title_font, &title );
433 }
434
435 control->hwnd = CreateWindowExW( exstyle, szCls, title, style,
436 x, y, width, height, parent, NULL, NULL, NULL );
437
438 TRACE("Dialog %s control %s hwnd %p\n",
439 debugstr_w(dialog->name), debugstr_w(text), control->hwnd );
440
441 msi_dialog_set_font( dialog, control->hwnd,
442 font ? font : dialog->default_font );
443
444 msi_free( title_font );
445 msi_free( font );
446
447 return control;
448 }
449
450 static LPWSTR msi_dialog_get_uitext( msi_dialog *dialog, LPCWSTR key )
451 {
452 MSIRECORD *rec;
453 LPWSTR text;
454
455 static const WCHAR query[] = {
456 's','e','l','e','c','t',' ','*',' ',
457 'f','r','o','m',' ','`','U','I','T','e','x','t','`',' ',
458 'w','h','e','r','e',' ','`','K','e','y','`',' ','=',' ','\'','%','s','\'',0
459 };
460
461 rec = MSI_QueryGetRecord( dialog->package->db, query, key );
462 if (!rec) return NULL;
463 text = strdupW( MSI_RecordGetString( rec, 2 ) );
464 msiobj_release( &rec->hdr );
465 return text;
466 }
467
468 static MSIRECORD *msi_get_binary_record( MSIDATABASE *db, LPCWSTR name )
469 {
470 static const WCHAR query[] = {
471 's','e','l','e','c','t',' ','*',' ',
472 'f','r','o','m',' ','B','i','n','a','r','y',' ',
473 'w','h','e','r','e',' ',
474 '`','N','a','m','e','`',' ','=',' ','\'','%','s','\'',0
475 };
476
477 return MSI_QueryGetRecord( db, query, name );
478 }
479
480 static LPWSTR msi_create_tmp_path(void)
481 {
482 WCHAR tmp[MAX_PATH];
483 LPWSTR path = NULL;
484 DWORD len, r;
485
486 r = GetTempPathW( MAX_PATH, tmp );
487 if( !r )
488 return path;
489 len = lstrlenW( tmp ) + 20;
490 path = msi_alloc( len * sizeof (WCHAR) );
491 if( path )
492 {
493 r = GetTempFileNameW( tmp, szMsi, 0, path );
494 if (!r)
495 {
496 msi_free( path );
497 path = NULL;
498 }
499 }
500 return path;
501 }
502
503 static HANDLE msi_load_image( MSIDATABASE *db, LPCWSTR name, UINT type,
504 UINT cx, UINT cy, UINT flags )
505 {
506 MSIRECORD *rec = NULL;
507 HANDLE himage = NULL;
508 LPWSTR tmp;
509 UINT r;
510
511 TRACE("%p %s %u %u %08x\n", db, debugstr_w(name), cx, cy, flags);
512
513 tmp = msi_create_tmp_path();
514 if( !tmp )
515 return himage;
516
517 rec = msi_get_binary_record( db, name );
518 if( rec )
519 {
520 r = MSI_RecordStreamToFile( rec, 2, tmp );
521 if( r == ERROR_SUCCESS )
522 {
523 himage = LoadImageW( 0, tmp, type, cx, cy, flags );
524 }
525 msiobj_release( &rec->hdr );
526 }
527 DeleteFileW( tmp );
528
529 msi_free( tmp );
530 return himage;
531 }
532
533 static HICON msi_load_icon( MSIDATABASE *db, LPCWSTR text, UINT attributes )
534 {
535 DWORD cx = 0, cy = 0, flags;
536
537 flags = LR_LOADFROMFILE | LR_DEFAULTSIZE;
538 if( attributes & msidbControlAttributesFixedSize )
539 {
540 flags &= ~LR_DEFAULTSIZE;
541 if( attributes & msidbControlAttributesIconSize16 )
542 {
543 cx += 16;
544 cy += 16;
545 }
546 if( attributes & msidbControlAttributesIconSize32 )
547 {
548 cx += 32;
549 cy += 32;
550 }
551 /* msidbControlAttributesIconSize48 handled by above logic */
552 }
553 return msi_load_image( db, text, IMAGE_ICON, cx, cy, flags );
554 }
555
556 static void msi_dialog_update_controls( msi_dialog *dialog, LPCWSTR property )
557 {
558 msi_control *control;
559
560 LIST_FOR_EACH_ENTRY( control, &dialog->controls, msi_control, entry )
561 {
562 if ( control->property && !strcmpW( control->property, property ) && control->update )
563 control->update( dialog, control );
564 }
565 }
566
567 static void msi_dialog_set_property( MSIPACKAGE *package, LPCWSTR property, LPCWSTR value )
568 {
569 UINT r = msi_set_property( package->db, property, value, -1 );
570 if (r == ERROR_SUCCESS && !strcmpW( property, szSourceDir ))
571 msi_reset_folders( package, TRUE );
572 }
573
574 static MSIFEATURE *msi_seltree_feature_from_item( HWND hwnd, HTREEITEM hItem )
575 {
576 TVITEMW tvi;
577
578 /* get the feature from the item */
579 memset( &tvi, 0, sizeof tvi );
580 tvi.hItem = hItem;
581 tvi.mask = TVIF_PARAM | TVIF_HANDLE;
582 SendMessageW( hwnd, TVM_GETITEMW, 0, (LPARAM)&tvi );
583 return (MSIFEATURE *)tvi.lParam;
584 }
585
586 struct msi_selection_tree_info
587 {
588 msi_dialog *dialog;
589 HWND hwnd;
590 WNDPROC oldproc;
591 HTREEITEM selected;
592 };
593
594 static MSIFEATURE *msi_seltree_get_selected_feature( msi_control *control )
595 {
596 struct msi_selection_tree_info *info = GetPropW( control->hwnd, szButtonData );
597 return msi_seltree_feature_from_item( control->hwnd, info->selected );
598 }
599
600 static void dialog_handle_event( msi_dialog *dialog, const WCHAR *control,
601 const WCHAR *attribute, MSIRECORD *rec )
602 {
603 msi_control* ctrl;
604
605 ctrl = msi_dialog_find_control( dialog, control );
606 if (!ctrl)
607 return;
608 if( !strcmpW( attribute, szText ) )
609 {
610 const WCHAR *font_text, *text = NULL;
611 WCHAR *font, *text_fmt = NULL;
612
613 font_text = MSI_RecordGetString( rec , 1 );
614 font = msi_dialog_get_style( font_text, &text );
615 deformat_string( dialog->package, text, &text_fmt );
616 if (text_fmt) text = text_fmt;
617 else text = szEmpty;
618
619 SetWindowTextW( ctrl->hwnd, text );
620
621 msi_free( font );
622 msi_free( text_fmt );
623 msi_dialog_check_messages( NULL );
624 }
625 else if( !strcmpW( attribute, szProgress ) )
626 {
627 DWORD func, val1, val2, units;
628
629 func = MSI_RecordGetInteger( rec, 1 );
630 val1 = MSI_RecordGetInteger( rec, 2 );
631 val2 = MSI_RecordGetInteger( rec, 3 );
632
633 TRACE("progress: func %u val1 %u val2 %u\n", func, val1, val2);
634
635 units = val1 / 512;
636 switch (func)
637 {
638 case 0: /* init */
639 SendMessageW( ctrl->hwnd, PBM_SETRANGE, 0, MAKELPARAM(0,100) );
640 if (val2)
641 {
642 ctrl->progress_max = units ? units : 100;
643 ctrl->progress_current = units;
644 ctrl->progress_backwards = TRUE;
645 SendMessageW( ctrl->hwnd, PBM_SETPOS, 100, 0 );
646 }
647 else
648 {
649 ctrl->progress_max = units ? units : 100;
650 ctrl->progress_current = 0;
651 ctrl->progress_backwards = FALSE;
652 SendMessageW( ctrl->hwnd, PBM_SETPOS, 0, 0 );
653 }
654 break;
655 case 1: /* action data increment */
656 if (val2) dialog->package->action_progress_increment = val1;
657 else dialog->package->action_progress_increment = 0;
658 break;
659 case 2: /* move */
660 if (ctrl->progress_backwards)
661 {
662 if (units >= ctrl->progress_current) ctrl->progress_current -= units;
663 else ctrl->progress_current = 0;
664 }
665 else
666 {
667 if (ctrl->progress_current + units < ctrl->progress_max) ctrl->progress_current += units;
668 else ctrl->progress_current = ctrl->progress_max;
669 }
670 SendMessageW( ctrl->hwnd, PBM_SETPOS, MulDiv(100, ctrl->progress_current, ctrl->progress_max), 0 );
671 break;
672 case 3: /* add */
673 ctrl->progress_max += units;
674 break;
675 default:
676 FIXME("Unknown progress message %u\n", func);
677 break;
678 }
679 }
680 else if ( !strcmpW( attribute, szProperty ) )
681 {
682 MSIFEATURE *feature = msi_seltree_get_selected_feature( ctrl );
683 if (feature) msi_dialog_set_property( dialog->package, ctrl->property, feature->Directory );
684 }
685 else if ( !strcmpW( attribute, szSelectionPath ) )
686 {
687 BOOL indirect = ctrl->attributes & msidbControlAttributesIndirect;
688 LPWSTR path = msi_dialog_dup_property( dialog, ctrl->property, indirect );
689 if (!path) return;
690 SetWindowTextW( ctrl->hwnd, path );
691 msi_free(path);
692 }
693 else
694 {
695 FIXME("Attribute %s not being set\n", debugstr_w(attribute));
696 return;
697 }
698 }
699
700 static void event_subscribe( msi_dialog *dialog, const WCHAR *event, const WCHAR *control, const WCHAR *attribute )
701 {
702 struct subscriber *sub;
703
704 TRACE("event %s control %s attribute %s\n", debugstr_w(event), debugstr_w(control), debugstr_w(attribute));
705
706 LIST_FOR_EACH_ENTRY( sub, &dialog->package->subscriptions, struct subscriber, entry )
707 {
708 if (!strcmpiW( sub->event, event ) &&
709 !strcmpiW( sub->control, control ) &&
710 !strcmpiW( sub->attribute, attribute ))
711 {
712 TRACE("already subscribed\n");
713 return;
714 };
715 }
716 if (!(sub = msi_alloc( sizeof(*sub) ))) return;
717 sub->dialog = dialog;
718 sub->event = strdupW( event );
719 sub->control = strdupW( control );
720 sub->attribute = strdupW( attribute );
721 list_add_tail( &dialog->package->subscriptions, &sub->entry );
722 }
723
724 struct dialog_control
725 {
726 msi_dialog *dialog;
727 const WCHAR *control;
728 };
729
730 static UINT map_event( MSIRECORD *row, void *param )
731 {
732 struct dialog_control *dc = param;
733 const WCHAR *event = MSI_RecordGetString( row, 3 );
734 const WCHAR *attribute = MSI_RecordGetString( row, 4 );
735
736 event_subscribe( dc->dialog, event, dc->control, attribute );
737 return ERROR_SUCCESS;
738 }
739
740 static void dialog_map_events( msi_dialog *dialog, const WCHAR *control )
741 {
742 static const WCHAR queryW[] =
743 {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
744 '`','E','v','e','n','t','M','a','p','p','i','n','g','`',' ',
745 'W','H','E','R','E',' ','`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',' ',
746 'A','N','D',' ','`','C','o','n','t','r','o','l','_','`',' ','=',' ','\'','%','s','\'',0};
747 MSIQUERY *view;
748 struct dialog_control dialog_control =
749 {
750 dialog,
751 control
752 };
753
754 if (!MSI_OpenQuery( dialog->package->db, &view, queryW, dialog->name, control ))
755 {
756 MSI_IterateRecords( view, NULL, map_event, &dialog_control );
757 msiobj_release( &view->hdr );
758 }
759 }
760
761 /* everything except radio buttons */
762 static msi_control *msi_dialog_add_control( msi_dialog *dialog,
763 MSIRECORD *rec, LPCWSTR szCls, DWORD style )
764 {
765 DWORD attributes;
766 LPCWSTR text, name;
767 DWORD exstyle = 0;
768
769 name = MSI_RecordGetString( rec, 2 );
770 attributes = MSI_RecordGetInteger( rec, 8 );
771 text = MSI_RecordGetString( rec, 10 );
772
773 TRACE("%s, %s, %08x, %s, %08x\n", debugstr_w(szCls), debugstr_w(name),
774 attributes, debugstr_w(text), style);
775
776 if( attributes & msidbControlAttributesVisible )
777 style |= WS_VISIBLE;
778 if( ~attributes & msidbControlAttributesEnabled )
779 style |= WS_DISABLED;
780 if( attributes & msidbControlAttributesSunken )
781 exstyle |= WS_EX_CLIENTEDGE;
782
783 dialog_map_events( dialog, name );
784
785 return dialog_create_window( dialog, rec, exstyle, szCls, name, text, style, dialog->hwnd );
786 }
787
788 struct msi_text_info
789 {
790 msi_font *font;
791 WNDPROC oldproc;
792 DWORD attributes;
793 };
794
795 /*
796 * we don't erase our own background,
797 * so we have to make sure that the parent window redraws first
798 */
799 static void msi_text_on_settext( HWND hWnd )
800 {
801 HWND hParent;
802 RECT rc;
803
804 hParent = GetParent( hWnd );
805 GetWindowRect( hWnd, &rc );
806 MapWindowPoints( NULL, hParent, (LPPOINT) &rc, 2 );
807 InvalidateRect( hParent, &rc, TRUE );
808 }
809
810 static LRESULT WINAPI
811 MSIText_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
812 {
813 struct msi_text_info *info;
814 LRESULT r = 0;
815
816 TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam);
817
818 info = GetPropW(hWnd, szButtonData);
819
820 if( msg == WM_CTLCOLORSTATIC &&
821 ( info->attributes & msidbControlAttributesTransparent ) )
822 {
823 SetBkMode( (HDC)wParam, TRANSPARENT );
824 return (LRESULT) GetStockObject(NULL_BRUSH);
825 }
826
827 r = CallWindowProcW(info->oldproc, hWnd, msg, wParam, lParam);
828 if ( info->font )
829 SetTextColor( (HDC)wParam, info->font->color );
830
831 switch( msg )
832 {
833 case WM_SETTEXT:
834 msi_text_on_settext( hWnd );
835 break;
836 case WM_NCDESTROY:
837 msi_free( info );
838 RemovePropW( hWnd, szButtonData );
839 break;
840 }
841
842 return r;
843 }
844
845 static UINT msi_dialog_text_control( msi_dialog *dialog, MSIRECORD *rec )
846 {
847 msi_control *control;
848 struct msi_text_info *info;
849 LPCWSTR text, ptr, prop, control_name;
850 LPWSTR font_name;
851
852 TRACE("%p %p\n", dialog, rec);
853
854 control = msi_dialog_add_control( dialog, rec, szStatic, SS_LEFT | WS_GROUP );
855 if( !control )
856 return ERROR_FUNCTION_FAILED;
857
858 info = msi_alloc( sizeof *info );
859 if( !info )
860 return ERROR_SUCCESS;
861
862 control_name = MSI_RecordGetString( rec, 2 );
863 control->attributes = MSI_RecordGetInteger( rec, 8 );
864 prop = MSI_RecordGetString( rec, 9 );
865 control->property = msi_dialog_dup_property( dialog, prop, FALSE );
866
867 text = MSI_RecordGetString( rec, 10 );
868 font_name = msi_dialog_get_style( text, &ptr );
869 info->font = ( font_name ) ? msi_dialog_find_font( dialog, font_name ) : NULL;
870 msi_free( font_name );
871
872 info->attributes = MSI_RecordGetInteger( rec, 8 );
873 if( info->attributes & msidbControlAttributesTransparent )
874 SetWindowLongPtrW( control->hwnd, GWL_EXSTYLE, WS_EX_TRANSPARENT );
875
876 info->oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
877 (LONG_PTR)MSIText_WndProc );
878 SetPropW( control->hwnd, szButtonData, info );
879
880 event_subscribe( dialog, szSelectionPath, control_name, szSelectionPath );
881 return ERROR_SUCCESS;
882 }
883
884 /* strip any leading text style label from text field */
885 static WCHAR *msi_get_binary_name( MSIPACKAGE *package, MSIRECORD *rec )
886 {
887 WCHAR *p, *text;
888
889 text = msi_get_deformatted_field( package, rec, 10 );
890 if (!text)
891 return NULL;
892
893 p = text;
894 while (*p && *p != '{') p++;
895 if (!*p++) return text;
896
897 while (*p && *p != '}') p++;
898 if (!*p++) return text;
899
900 p = strdupW( p );
901 msi_free( text );
902 return p;
903 }
904
905 static UINT msi_dialog_set_property_event( msi_dialog *dialog, LPCWSTR event, LPCWSTR arg )
906 {
907 static const WCHAR szNullArg[] = {'{','}',0};
908 LPWSTR p, prop, arg_fmt = NULL;
909 UINT len;
910
911 len = strlenW( event );
912 prop = msi_alloc( len * sizeof(WCHAR) );
913 strcpyW( prop, &event[1] );
914 p = strchrW( prop, ']' );
915 if (p && (p[1] == 0 || p[1] == ' '))
916 {
917 *p = 0;
918 if (strcmpW( szNullArg, arg ))
919 deformat_string( dialog->package, arg, &arg_fmt );
920 msi_dialog_set_property( dialog->package, prop, arg_fmt );
921 msi_dialog_update_controls( dialog, prop );
922 msi_free( arg_fmt );
923 }
924 else ERR("Badly formatted property string - what happens?\n");
925 msi_free( prop );
926 return ERROR_SUCCESS;
927 }
928
929 static UINT msi_dialog_send_event( msi_dialog *dialog, LPCWSTR event, LPCWSTR arg )
930 {
931 LPWSTR event_fmt = NULL, arg_fmt = NULL;
932
933 TRACE("Sending control event %s %s\n", debugstr_w(event), debugstr_w(arg));
934
935 deformat_string( dialog->package, event, &event_fmt );
936 deformat_string( dialog->package, arg, &arg_fmt );
937
938 dialog->event_handler( dialog, event_fmt, arg_fmt );
939
940 msi_free( event_fmt );
941 msi_free( arg_fmt );
942
943 return ERROR_SUCCESS;
944 }
945
946 static UINT msi_dialog_control_event( MSIRECORD *rec, LPVOID param )
947 {
948 msi_dialog *dialog = param;
949 LPCWSTR condition, event, arg;
950 UINT r;
951
952 condition = MSI_RecordGetString( rec, 5 );
953 r = MSI_EvaluateConditionW( dialog->package, condition );
954 if (r == MSICONDITION_TRUE || r == MSICONDITION_NONE)
955 {
956 event = MSI_RecordGetString( rec, 3 );
957 arg = MSI_RecordGetString( rec, 4 );
958 if (event[0] == '[')
959 msi_dialog_set_property_event( dialog, event, arg );
960 else
961 msi_dialog_send_event( dialog, event, arg );
962 }
963 return ERROR_SUCCESS;
964 }
965
966 static UINT msi_dialog_button_handler( msi_dialog *dialog, msi_control *control, WPARAM param )
967 {
968 static const WCHAR query[] = {
969 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
970 'C','o','n','t','r','o','l','E','v','e','n','t',' ','W','H','E','R','E',' ',
971 '`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',' ','A','N','D',' ',
972 '`','C','o','n','t','r','o','l','_','`',' ','=',' ','\'','%','s','\'',' ',
973 'O','R','D','E','R',' ','B','Y',' ','`','O','r','d','e','r','i','n','g','`',0};
974 MSIQUERY *view;
975 UINT r;
976
977 if (HIWORD(param) != BN_CLICKED)
978 return ERROR_SUCCESS;
979
980 r = MSI_OpenQuery( dialog->package->db, &view, query, dialog->name, control->name );
981 if (r != ERROR_SUCCESS)
982 {
983 ERR("query failed\n");
984 return ERROR_SUCCESS;
985 }
986 r = MSI_IterateRecords( view, 0, msi_dialog_control_event, dialog );
987 msiobj_release( &view->hdr );
988 return r;
989 }
990
991 static UINT msi_dialog_button_control( msi_dialog *dialog, MSIRECORD *rec )
992 {
993 msi_control *control;
994 UINT attributes, style;
995
996 TRACE("%p %p\n", dialog, rec);
997
998 style = WS_TABSTOP;
999 attributes = MSI_RecordGetInteger( rec, 8 );
1000 if( attributes & msidbControlAttributesIcon )
1001 style |= BS_ICON;
1002
1003 control = msi_dialog_add_control( dialog, rec, szButton, style );
1004 if( !control )
1005 return ERROR_FUNCTION_FAILED;
1006
1007 control->handler = msi_dialog_button_handler;
1008
1009 if (attributes & msidbControlAttributesIcon)
1010 {
1011 /* set the icon */
1012 LPWSTR name = msi_get_binary_name( dialog->package, rec );
1013 control->hIcon = msi_load_icon( dialog->package->db, name, attributes );
1014 if (control->hIcon)
1015 {
1016 SendMessageW( control->hwnd, BM_SETIMAGE, IMAGE_ICON, (LPARAM) control->hIcon );
1017 }
1018 else
1019 ERR("Failed to load icon %s\n", debugstr_w(name));
1020 msi_free( name );
1021 }
1022
1023 return ERROR_SUCCESS;
1024 }
1025
1026 static LPWSTR msi_get_checkbox_value( msi_dialog *dialog, LPCWSTR prop )
1027 {
1028 static const WCHAR query[] = {
1029 'S','E','L','E','C','T',' ','*',' ',
1030 'F','R','O','M',' ','`','C','h','e','c','k','B','o','x','`',' ',
1031 'W','H','E','R','E',' ',
1032 '`','P','r','o','p','e','r','t','y','`',' ','=',' ',
1033 '\'','%','s','\'',0
1034 };
1035 MSIRECORD *rec = NULL;
1036 LPWSTR ret = NULL;
1037
1038 /* find if there is a value associated with the checkbox */
1039 rec = MSI_QueryGetRecord( dialog->package->db, query, prop );
1040 if (!rec)
1041 return ret;
1042
1043 ret = msi_get_deformatted_field( dialog->package, rec, 2 );
1044 if( ret && !ret[0] )
1045 {
1046 msi_free( ret );
1047 ret = NULL;
1048 }
1049 msiobj_release( &rec->hdr );
1050 if (ret)
1051 return ret;
1052
1053 ret = msi_dup_property( dialog->package->db, prop );
1054 if( ret && !ret[0] )
1055 {
1056 msi_free( ret );
1057 ret = NULL;
1058 }
1059
1060 return ret;
1061 }
1062
1063 static UINT msi_dialog_get_checkbox_state( msi_dialog *dialog, msi_control *control )
1064 {
1065 WCHAR state[2] = {0};
1066 DWORD sz = 2;
1067
1068 msi_get_property( dialog->package->db, control->property, state, &sz );
1069 return state[0] ? 1 : 0;
1070 }
1071
1072 static void msi_dialog_set_checkbox_state( msi_dialog *dialog, msi_control *control, UINT state )
1073 {
1074 static const WCHAR szState[] = {'1',0};
1075 LPCWSTR val;
1076
1077 /* if uncheck then the property is set to NULL */
1078 if (!state)
1079 {
1080 msi_dialog_set_property( dialog->package, control->property, NULL );
1081 return;
1082 }
1083
1084 /* check for a custom state */
1085 if (control->value && control->value[0])
1086 val = control->value;
1087 else
1088 val = szState;
1089
1090 msi_dialog_set_property( dialog->package, control->property, val );
1091 }
1092
1093 static void msi_dialog_checkbox_sync_state( msi_dialog *dialog, msi_control *control )
1094 {
1095 UINT state = msi_dialog_get_checkbox_state( dialog, control );
1096 SendMessageW( control->hwnd, BM_SETCHECK, state ? BST_CHECKED : BST_UNCHECKED, 0 );
1097 }
1098
1099 static UINT msi_dialog_checkbox_handler( msi_dialog *dialog, msi_control *control, WPARAM param )
1100 {
1101 UINT state;
1102
1103 if (HIWORD(param) != BN_CLICKED)
1104 return ERROR_SUCCESS;
1105
1106 TRACE("clicked checkbox %s, set %s\n", debugstr_w(control->name), debugstr_w(control->property));
1107
1108 state = msi_dialog_get_checkbox_state( dialog, control );
1109 state = state ? 0 : 1;
1110 msi_dialog_set_checkbox_state( dialog, control, state );
1111 msi_dialog_checkbox_sync_state( dialog, control );
1112
1113 return msi_dialog_button_handler( dialog, control, param );
1114 }
1115
1116 static UINT msi_dialog_checkbox_control( msi_dialog *dialog, MSIRECORD *rec )
1117 {
1118 msi_control *control;
1119 LPCWSTR prop;
1120
1121 TRACE("%p %p\n", dialog, rec);
1122
1123 control = msi_dialog_add_control( dialog, rec, szButton, BS_CHECKBOX | BS_MULTILINE | WS_TABSTOP );
1124 control->handler = msi_dialog_checkbox_handler;
1125 control->update = msi_dialog_checkbox_sync_state;
1126 prop = MSI_RecordGetString( rec, 9 );
1127 if (prop)
1128 {
1129 control->property = strdupW( prop );
1130 control->value = msi_get_checkbox_value( dialog, prop );
1131 TRACE("control %s value %s\n", debugstr_w(control->property), debugstr_w(control->value));
1132 }
1133 msi_dialog_checkbox_sync_state( dialog, control );
1134 return ERROR_SUCCESS;
1135 }
1136
1137 static UINT msi_dialog_line_control( msi_dialog *dialog, MSIRECORD *rec )
1138 {
1139 DWORD attributes;
1140 LPCWSTR name;
1141 DWORD style, exstyle = 0;
1142 DWORD x, y, width, height;
1143 msi_control *control;
1144
1145 TRACE("%p %p\n", dialog, rec);
1146
1147 style = WS_CHILD | SS_ETCHEDHORZ | SS_SUNKEN;
1148
1149 name = MSI_RecordGetString( rec, 2 );
1150 attributes = MSI_RecordGetInteger( rec, 8 );
1151
1152 if( attributes & msidbControlAttributesVisible )
1153 style |= WS_VISIBLE;
1154 if( ~attributes & msidbControlAttributesEnabled )
1155 style |= WS_DISABLED;
1156 if( attributes & msidbControlAttributesSunken )
1157 exstyle |= WS_EX_CLIENTEDGE;
1158
1159 dialog_map_events( dialog, name );
1160
1161 control = msi_alloc( FIELD_OFFSET(msi_control, name[strlenW( name ) + 1] ));
1162 if (!control)
1163 return ERROR_OUTOFMEMORY;
1164
1165 strcpyW( control->name, name );
1166 list_add_head( &dialog->controls, &control->entry );
1167 control->handler = NULL;
1168 control->property = NULL;
1169 control->value = NULL;
1170 control->hBitmap = NULL;
1171 control->hIcon = NULL;
1172 control->hDll = NULL;
1173 control->tabnext = strdupW( MSI_RecordGetString( rec, 11) );
1174 control->type = strdupW( MSI_RecordGetString( rec, 3 ) );
1175 control->progress_current = 0;
1176 control->progress_max = 100;
1177 control->progress_backwards = FALSE;
1178
1179 x = MSI_RecordGetInteger( rec, 4 );
1180 y = MSI_RecordGetInteger( rec, 5 );
1181 width = MSI_RecordGetInteger( rec, 6 );
1182
1183 x = msi_dialog_scale_unit( dialog, x );
1184 y = msi_dialog_scale_unit( dialog, y );
1185 width = msi_dialog_scale_unit( dialog, width );
1186 height = 2; /* line is exactly 2 units in height */
1187
1188 control->hwnd = CreateWindowExW( exstyle, szStatic, NULL, style,
1189 x, y, width, height, dialog->hwnd, NULL, NULL, NULL );
1190
1191 TRACE("Dialog %s control %s hwnd %p\n",
1192 debugstr_w(dialog->name), debugstr_w(name), control->hwnd );
1193
1194 return ERROR_SUCCESS;
1195 }
1196
1197 /******************** Scroll Text ********************************************/
1198
1199 struct msi_scrolltext_info
1200 {
1201 msi_dialog *dialog;
1202 msi_control *control;
1203 WNDPROC oldproc;
1204 };
1205
1206 static LRESULT WINAPI
1207 MSIScrollText_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
1208 {
1209 struct msi_scrolltext_info *info;
1210 HRESULT r;
1211
1212 TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam);
1213
1214 info = GetPropW( hWnd, szButtonData );
1215
1216 r = CallWindowProcW( info->oldproc, hWnd, msg, wParam, lParam );
1217
1218 switch( msg )
1219 {
1220 case WM_GETDLGCODE:
1221 return DLGC_WANTARROWS;
1222 case WM_NCDESTROY:
1223 msi_free( info );
1224 RemovePropW( hWnd, szButtonData );
1225 break;
1226 case WM_PAINT:
1227 /* native MSI sets a wait cursor here */
1228 msi_dialog_button_handler( info->dialog, info->control, BN_CLICKED );
1229 break;
1230 }
1231 return r;
1232 }
1233
1234 struct msi_streamin_info
1235 {
1236 LPSTR string;
1237 DWORD offset;
1238 DWORD length;
1239 };
1240
1241 static DWORD CALLBACK
1242 msi_richedit_stream_in( DWORD_PTR arg, LPBYTE buffer, LONG count, LONG *pcb )
1243 {
1244 struct msi_streamin_info *info = (struct msi_streamin_info*) arg;
1245
1246 if( (count + info->offset) > info->length )
1247 count = info->length - info->offset;
1248 memcpy( buffer, &info->string[ info->offset ], count );
1249 *pcb = count;
1250 info->offset += count;
1251
1252 TRACE("%d/%d\n", info->offset, info->length);
1253
1254 return 0;
1255 }
1256
1257 static void msi_scrolltext_add_text( msi_control *control, LPCWSTR text )
1258 {
1259 struct msi_streamin_info info;
1260 EDITSTREAM es;
1261
1262 info.string = strdupWtoA( text );
1263 info.offset = 0;
1264 info.length = lstrlenA( info.string ) + 1;
1265
1266 es.dwCookie = (DWORD_PTR) &info;
1267 es.dwError = 0;
1268 es.pfnCallback = msi_richedit_stream_in;
1269
1270 SendMessageW( control->hwnd, EM_STREAMIN, SF_RTF, (LPARAM) &es );
1271
1272 msi_free( info.string );
1273 }
1274
1275 static UINT msi_dialog_scrolltext_control( msi_dialog *dialog, MSIRECORD *rec )
1276 {
1277 static const WCHAR szRichEdit20W[] = {'R','i','c','h','E','d','i','t','2','0','W',0};
1278 struct msi_scrolltext_info *info;
1279 msi_control *control;
1280 HMODULE hRichedit;
1281 LPCWSTR text;
1282 DWORD style;
1283
1284 info = msi_alloc( sizeof *info );
1285 if (!info)
1286 return ERROR_FUNCTION_FAILED;
1287
1288 hRichedit = LoadLibraryA("riched20");
1289
1290 style = WS_BORDER | ES_MULTILINE | WS_VSCROLL |
1291 ES_READONLY | ES_AUTOVSCROLL | WS_TABSTOP;
1292 control = msi_dialog_add_control( dialog, rec, szRichEdit20W, style );
1293 if (!control)
1294 {
1295 FreeLibrary( hRichedit );
1296 msi_free( info );
1297 return ERROR_FUNCTION_FAILED;
1298 }
1299
1300 control->hDll = hRichedit;
1301
1302 info->dialog = dialog;
1303 info->control = control;
1304
1305 /* subclass the static control */
1306 info->oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
1307 (LONG_PTR)MSIScrollText_WndProc );
1308 SetPropW( control->hwnd, szButtonData, info );
1309
1310 /* add the text into the richedit */
1311 text = MSI_RecordGetString( rec, 10 );
1312 if (text)
1313 msi_scrolltext_add_text( control, text );
1314
1315 return ERROR_SUCCESS;
1316 }
1317
1318 static HBITMAP msi_load_picture( MSIDATABASE *db, LPCWSTR name,
1319 INT cx, INT cy, DWORD flags )
1320 {
1321 HBITMAP hOleBitmap = 0, hBitmap = 0, hOldSrcBitmap, hOldDestBitmap;
1322 MSIRECORD *rec = NULL;
1323 IStream *stm = NULL;
1324 IPicture *pic = NULL;
1325 HDC srcdc, destdc;
1326 BITMAP bm;
1327 UINT r;
1328
1329 rec = msi_get_binary_record( db, name );
1330 if( !rec )
1331 goto end;
1332
1333 r = MSI_RecordGetIStream( rec, 2, &stm );
1334 msiobj_release( &rec->hdr );
1335 if( r != ERROR_SUCCESS )
1336 goto end;
1337
1338 r = OleLoadPicture( stm, 0, TRUE, &IID_IPicture, (LPVOID*) &pic );
1339 IStream_Release( stm );
1340 if( FAILED( r ) )
1341 {
1342 ERR("failed to load picture\n");
1343 goto end;
1344 }
1345
1346 r = IPicture_get_Handle( pic, (OLE_HANDLE*) &hOleBitmap );
1347 if( FAILED( r ) )
1348 {
1349 ERR("failed to get bitmap handle\n");
1350 goto end;
1351 }
1352
1353 /* make the bitmap the desired size */
1354 r = GetObjectW( hOleBitmap, sizeof bm, &bm );
1355 if (r != sizeof bm )
1356 {
1357 ERR("failed to get bitmap size\n");
1358 goto end;
1359 }
1360
1361 if (flags & LR_DEFAULTSIZE)
1362 {
1363 cx = bm.bmWidth;
1364 cy = bm.bmHeight;
1365 }
1366
1367 srcdc = CreateCompatibleDC( NULL );
1368 hOldSrcBitmap = SelectObject( srcdc, hOleBitmap );
1369 destdc = CreateCompatibleDC( NULL );
1370 hBitmap = CreateCompatibleBitmap( srcdc, cx, cy );
1371 hOldDestBitmap = SelectObject( destdc, hBitmap );
1372 StretchBlt( destdc, 0, 0, cx, cy,
1373 srcdc, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
1374 SelectObject( srcdc, hOldSrcBitmap );
1375 SelectObject( destdc, hOldDestBitmap );
1376 DeleteDC( srcdc );
1377 DeleteDC( destdc );
1378
1379 end:
1380 if ( pic )
1381 IPicture_Release( pic );
1382 return hBitmap;
1383 }
1384
1385 static UINT msi_dialog_bitmap_control( msi_dialog *dialog, MSIRECORD *rec )
1386 {
1387 UINT cx, cy, flags, style, attributes;
1388 msi_control *control;
1389 LPWSTR name;
1390
1391 flags = LR_LOADFROMFILE;
1392 style = SS_BITMAP | SS_LEFT | WS_GROUP;
1393
1394 attributes = MSI_RecordGetInteger( rec, 8 );
1395 if( attributes & msidbControlAttributesFixedSize )
1396 {
1397 flags |= LR_DEFAULTSIZE;
1398 style |= SS_CENTERIMAGE;
1399 }
1400
1401 control = msi_dialog_add_control( dialog, rec, szStatic, style );
1402 cx = MSI_RecordGetInteger( rec, 6 );
1403 cy = MSI_RecordGetInteger( rec, 7 );
1404 cx = msi_dialog_scale_unit( dialog, cx );
1405 cy = msi_dialog_scale_unit( dialog, cy );
1406
1407 name = msi_get_binary_name( dialog->package, rec );
1408 control->hBitmap = msi_load_picture( dialog->package->db, name, cx, cy, flags );
1409 if( control->hBitmap )
1410 SendMessageW( control->hwnd, STM_SETIMAGE,
1411 IMAGE_BITMAP, (LPARAM) control->hBitmap );
1412 else
1413 ERR("Failed to load bitmap %s\n", debugstr_w(name));
1414
1415 msi_free( name );
1416
1417 return ERROR_SUCCESS;
1418 }
1419
1420 static UINT msi_dialog_icon_control( msi_dialog *dialog, MSIRECORD *rec )
1421 {
1422 msi_control *control;
1423 DWORD attributes;
1424 LPWSTR name;
1425
1426 TRACE("\n");
1427
1428 control = msi_dialog_add_control( dialog, rec, szStatic,
1429 SS_ICON | SS_CENTERIMAGE | WS_GROUP );
1430
1431 attributes = MSI_RecordGetInteger( rec, 8 );
1432 name = msi_get_binary_name( dialog->package, rec );
1433 control->hIcon = msi_load_icon( dialog->package->db, name, attributes );
1434 if( control->hIcon )
1435 SendMessageW( control->hwnd, STM_SETICON, (WPARAM) control->hIcon, 0 );
1436 else
1437 ERR("Failed to load bitmap %s\n", debugstr_w(name));
1438 msi_free( name );
1439 return ERROR_SUCCESS;
1440 }
1441
1442 /******************** Combo Box ***************************************/
1443
1444 struct msi_combobox_info
1445 {
1446 msi_dialog *dialog;
1447 HWND hwnd;
1448 WNDPROC oldproc;
1449 DWORD num_items;
1450 DWORD addpos_items;
1451 LPWSTR *items;
1452 };
1453
1454 static LRESULT WINAPI MSIComboBox_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
1455 {
1456 struct msi_combobox_info *info;
1457 LRESULT r;
1458 DWORD j;
1459
1460 TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam);
1461
1462 info = GetPropW( hWnd, szButtonData );
1463 if (!info)
1464 return 0;
1465
1466 r = CallWindowProcW( info->oldproc, hWnd, msg, wParam, lParam );
1467
1468 switch (msg)
1469 {
1470 case WM_NCDESTROY:
1471 for (j = 0; j < info->num_items; j++)
1472 msi_free( info->items[j] );
1473 msi_free( info->items );
1474 msi_free( info );
1475 RemovePropW( hWnd, szButtonData );
1476 break;
1477 }
1478
1479 return r;
1480 }
1481
1482 static UINT msi_combobox_add_item( MSIRECORD *rec, LPVOID param )
1483 {
1484 struct msi_combobox_info *info = param;
1485 LPCWSTR value, text;
1486 int pos;
1487
1488 value = MSI_RecordGetString( rec, 3 );
1489 text = MSI_RecordGetString( rec, 4 );
1490
1491 info->items[info->addpos_items] = strdupW( value );
1492
1493 pos = SendMessageW( info->hwnd, CB_ADDSTRING, 0, (LPARAM)text );
1494 SendMessageW( info->hwnd, CB_SETITEMDATA, pos, (LPARAM)info->items[info->addpos_items] );
1495 info->addpos_items++;
1496
1497 return ERROR_SUCCESS;
1498 }
1499
1500 static UINT msi_combobox_add_items( struct msi_combobox_info *info, LPCWSTR property )
1501 {
1502 static const WCHAR query[] = {
1503 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
1504 '`','C','o','m','b','o','B','o','x','`',' ','W','H','E','R','E',' ',
1505 '`','P','r','o','p','e','r','t','y','`',' ','=',' ','\'','%','s','\'',' ',
1506 'O','R','D','E','R',' ','B','Y',' ','`','O','r','d','e','r','`',0};
1507 MSIQUERY *view;
1508 DWORD count;
1509 UINT r;
1510
1511 r = MSI_OpenQuery( info->dialog->package->db, &view, query, property );
1512 if (r != ERROR_SUCCESS)
1513 return r;
1514
1515 /* just get the number of records */
1516 count = 0;
1517 r = MSI_IterateRecords( view, &count, NULL, NULL );
1518 if (r != ERROR_SUCCESS)
1519 {
1520 msiobj_release( &view->hdr );
1521 return r;
1522 }
1523 info->num_items = count;
1524 info->items = msi_alloc( sizeof(*info->items) * count );
1525
1526 r = MSI_IterateRecords( view, NULL, msi_combobox_add_item, info );
1527 msiobj_release( &view->hdr );
1528 return r;
1529 }
1530
1531 static UINT msi_dialog_set_control_condition( MSIRECORD *rec, LPVOID param )
1532 {
1533 static const WCHAR szHide[] = {'H','i','d','e',0};
1534 static const WCHAR szShow[] = {'S','h','o','w',0};
1535 static const WCHAR szDisable[] = {'D','i','s','a','b','l','e',0};
1536 static const WCHAR szEnable[] = {'E','n','a','b','l','e',0};
1537 static const WCHAR szDefault[] = {'D','e','f','a','u','l','t',0};
1538 msi_dialog *dialog = param;
1539 msi_control *control;
1540 LPCWSTR name, action, condition;
1541 UINT r;
1542
1543 name = MSI_RecordGetString( rec, 2 );
1544 action = MSI_RecordGetString( rec, 3 );
1545 condition = MSI_RecordGetString( rec, 4 );
1546 r = MSI_EvaluateConditionW( dialog->package, condition );
1547 control = msi_dialog_find_control( dialog, name );
1548 if (r == MSICONDITION_TRUE && control)
1549 {
1550 TRACE("%s control %s\n", debugstr_w(action), debugstr_w(name));
1551
1552 /* FIXME: case sensitive? */
1553 if (!strcmpW( action, szHide ))
1554 ShowWindow(control->hwnd, SW_HIDE);
1555 else if (!strcmpW( action, szShow ))
1556 ShowWindow(control->hwnd, SW_SHOW);
1557 else if (!strcmpW( action, szDisable ))
1558 EnableWindow(control->hwnd, FALSE);
1559 else if (!strcmpW( action, szEnable ))
1560 EnableWindow(control->hwnd, TRUE);
1561 else if (!strcmpW( action, szDefault ))
1562 SetFocus(control->hwnd);
1563 else
1564 FIXME("Unhandled action %s\n", debugstr_w(action));
1565 }
1566 return ERROR_SUCCESS;
1567 }
1568
1569 static UINT msi_dialog_evaluate_control_conditions( msi_dialog *dialog )
1570 {
1571 static const WCHAR query[] = {
1572 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
1573 'C','o','n','t','r','o','l','C','o','n','d','i','t','i','o','n',' ',
1574 'W','H','E','R','E',' ','`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',0};
1575 UINT r;
1576 MSIQUERY *view;
1577 MSIPACKAGE *package = dialog->package;
1578
1579 TRACE("%p %s\n", dialog, debugstr_w(dialog->name));
1580
1581 /* query the Control table for all the elements of the control */
1582 r = MSI_OpenQuery( package->db, &view, query, dialog->name );
1583 if (r != ERROR_SUCCESS)
1584 return ERROR_SUCCESS;
1585
1586 r = MSI_IterateRecords( view, 0, msi_dialog_set_control_condition, dialog );
1587 msiobj_release( &view->hdr );
1588 return r;
1589 }
1590
1591 static UINT msi_dialog_combobox_handler( msi_dialog *dialog, msi_control *control, WPARAM param )
1592 {
1593 struct msi_combobox_info *info;
1594 int index;
1595 LPWSTR value;
1596
1597 if (HIWORD(param) != CBN_SELCHANGE && HIWORD(param) != CBN_EDITCHANGE)
1598 return ERROR_SUCCESS;
1599
1600 info = GetPropW( control->hwnd, szButtonData );
1601 index = SendMessageW( control->hwnd, CB_GETCURSEL, 0, 0 );
1602 if (index == CB_ERR)
1603 value = msi_get_window_text( control->hwnd );
1604 else
1605 value = (LPWSTR) SendMessageW( control->hwnd, CB_GETITEMDATA, index, 0 );
1606
1607 msi_dialog_set_property( info->dialog->package, control->property, value );
1608 msi_dialog_evaluate_control_conditions( info->dialog );
1609
1610 if (index == CB_ERR)
1611 msi_free( value );
1612
1613 return ERROR_SUCCESS;
1614 }
1615
1616 static void msi_dialog_combobox_update( msi_dialog *dialog, msi_control *control )
1617 {
1618 struct msi_combobox_info *info;
1619 LPWSTR value, tmp;
1620 DWORD j;
1621
1622 info = GetPropW( control->hwnd, szButtonData );
1623
1624 value = msi_dup_property( dialog->package->db, control->property );
1625 if (!value)
1626 {
1627 SendMessageW( control->hwnd, CB_SETCURSEL, -1, 0 );
1628 return;
1629 }
1630
1631 for (j = 0; j < info->num_items; j++)
1632 {
1633 tmp = (LPWSTR) SendMessageW( control->hwnd, CB_GETITEMDATA, j, 0 );
1634 if (!strcmpW( value, tmp ))
1635 break;
1636 }
1637
1638 if (j < info->num_items)
1639 {
1640 SendMessageW( control->hwnd, CB_SETCURSEL, j, 0 );
1641 }
1642 else
1643 {
1644 SendMessageW( control->hwnd, CB_SETCURSEL, -1, 0 );
1645 SetWindowTextW( control->hwnd, value );
1646 }
1647
1648 msi_free(value);
1649 }
1650
1651 static UINT msi_dialog_combo_control( msi_dialog *dialog, MSIRECORD *rec )
1652 {
1653 struct msi_combobox_info *info;
1654 msi_control *control;
1655 DWORD attributes, style;
1656 LPCWSTR prop;
1657
1658 info = msi_alloc( sizeof *info );
1659 if (!info)
1660 return ERROR_FUNCTION_FAILED;
1661
1662 style = CBS_AUTOHSCROLL | WS_TABSTOP | WS_GROUP | WS_CHILD;
1663 attributes = MSI_RecordGetInteger( rec, 8 );
1664 if ( ~attributes & msidbControlAttributesSorted)
1665 style |= CBS_SORT;
1666 if ( attributes & msidbControlAttributesComboList)
1667 style |= CBS_DROPDOWNLIST;
1668 else
1669 style |= CBS_DROPDOWN;
1670
1671 control = msi_dialog_add_control( dialog, rec, WC_COMBOBOXW, style );
1672 if (!control)
1673 {
1674 msi_free( info );
1675 return ERROR_FUNCTION_FAILED;
1676 }
1677
1678 control->handler = msi_dialog_combobox_handler;
1679 control->update = msi_dialog_combobox_update;
1680
1681 prop = MSI_RecordGetString( rec, 9 );
1682 control->property = msi_dialog_dup_property( dialog, prop, FALSE );
1683
1684 /* subclass */
1685 info->dialog = dialog;
1686 info->hwnd = control->hwnd;
1687 info->items = NULL;
1688 info->addpos_items = 0;
1689 info->oldproc = (WNDPROC)SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
1690 (LONG_PTR)MSIComboBox_WndProc );
1691 SetPropW( control->hwnd, szButtonData, info );
1692
1693 if (control->property)
1694 msi_combobox_add_items( info, control->property );
1695
1696 msi_dialog_combobox_update( dialog, control );
1697
1698 return ERROR_SUCCESS;
1699 }
1700
1701 static UINT msi_dialog_edit_handler( msi_dialog *dialog, msi_control *control, WPARAM param )
1702 {
1703 LPWSTR buf;
1704
1705 if (HIWORD(param) != EN_CHANGE)
1706 return ERROR_SUCCESS;
1707
1708 TRACE("edit %s contents changed, set %s\n", debugstr_w(control->name), debugstr_w(control->property));
1709
1710 buf = msi_get_window_text( control->hwnd );
1711 msi_dialog_set_property( dialog->package, control->property, buf );
1712 msi_free( buf );
1713
1714 return ERROR_SUCCESS;
1715 }
1716
1717 /* length of 2^32 + 1 */
1718 #define MAX_NUM_DIGITS 11
1719
1720 static UINT msi_dialog_edit_control( msi_dialog *dialog, MSIRECORD *rec )
1721 {
1722 msi_control *control;
1723 LPCWSTR prop, text;
1724 LPWSTR val, begin, end;
1725 WCHAR num[MAX_NUM_DIGITS];
1726 DWORD limit;
1727
1728 control = msi_dialog_add_control( dialog, rec, szEdit,
1729 WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL );
1730 control->handler = msi_dialog_edit_handler;
1731
1732 text = MSI_RecordGetString( rec, 10 );
1733 if ( text )
1734 {
1735 begin = strchrW( text, '{' );
1736 end = strchrW( text, '}' );
1737
1738 if ( begin && end && end > begin &&
1739 begin[0] >= '0' && begin[0] <= '9' &&
1740 end - begin < MAX_NUM_DIGITS)
1741 {
1742 lstrcpynW( num, begin + 1, end - begin );
1743 limit = atolW( num );
1744
1745 SendMessageW( control->hwnd, EM_SETLIMITTEXT, limit, 0 );
1746 }
1747 }
1748
1749 prop = MSI_RecordGetString( rec, 9 );
1750 if( prop )
1751 control->property = strdupW( prop );
1752
1753 val = msi_dup_property( dialog->package->db, control->property );
1754 SetWindowTextW( control->hwnd, val );
1755 msi_free( val );
1756 return ERROR_SUCCESS;
1757 }
1758
1759 /******************** Masked Edit ********************************************/
1760
1761 #define MASK_MAX_GROUPS 20
1762
1763 struct msi_mask_group
1764 {
1765 UINT len;
1766 UINT ofs;
1767 WCHAR type;
1768 HWND hwnd;
1769 };
1770
1771 struct msi_maskedit_info
1772 {
1773 msi_dialog *dialog;
1774 WNDPROC oldproc;
1775 HWND hwnd;
1776 LPWSTR prop;
1777 UINT num_chars;
1778 UINT num_groups;
1779 struct msi_mask_group group[MASK_MAX_GROUPS];
1780 };
1781
1782 static BOOL msi_mask_editable( WCHAR type )
1783 {
1784 switch (type)
1785 {
1786 case '%':
1787 case '#':
1788 case '&':
1789 case '`':
1790 case '?':
1791 case '^':
1792 return TRUE;
1793 }
1794 return FALSE;
1795 }
1796
1797 static void msi_mask_control_change( struct msi_maskedit_info *info )
1798 {
1799 LPWSTR val;
1800 UINT i, n, r;
1801
1802 val = msi_alloc( (info->num_chars+1)*sizeof(WCHAR) );
1803 for( i=0, n=0; i<info->num_groups; i++ )
1804 {
1805 if( (info->group[i].len + n) > info->num_chars )
1806 {
1807 ERR("can't fit control %d text into template\n",i);
1808 break;
1809 }
1810 if (!msi_mask_editable(info->group[i].type))
1811 {
1812 for(r=0; r<info->group[i].len; r++)
1813 val[n+r] = info->group[i].type;
1814 val[n+r] = 0;
1815 }
1816 else
1817 {
1818 r = GetWindowTextW( info->group[i].hwnd, &val[n], info->group[i].len+1 );
1819 if( r != info->group[i].len )
1820 break;
1821 }
1822 n += r;
1823 }
1824
1825 TRACE("%d/%d controls were good\n", i, info->num_groups);
1826
1827 if( i == info->num_groups )
1828 {
1829 TRACE("Set property %s to %s\n", debugstr_w(info->prop), debugstr_w(val));
1830 msi_dialog_set_property( info->dialog->package, info->prop, val );
1831 msi_dialog_evaluate_control_conditions( info->dialog );
1832 }
1833 msi_free( val );
1834 }
1835
1836 /* now move to the next control if necessary */
1837 static VOID msi_mask_next_control( struct msi_maskedit_info *info, HWND hWnd )
1838 {
1839 HWND hWndNext;
1840 UINT len, i;
1841
1842 for( i=0; i<info->num_groups; i++ )
1843 if( info->group[i].hwnd == hWnd )
1844 break;
1845
1846 /* don't move from the last control */
1847 if( i >= (info->num_groups-1) )
1848 return;
1849
1850 len = SendMessageW( hWnd, WM_GETTEXTLENGTH, 0, 0 );
1851 if( len < info->group[i].len )
1852 return;
1853
1854 hWndNext = GetNextDlgTabItem( GetParent( hWnd ), hWnd, FALSE );
1855 SetFocus( hWndNext );
1856 }
1857
1858 static LRESULT WINAPI
1859 MSIMaskedEdit_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
1860 {
1861 struct msi_maskedit_info *info;
1862 HRESULT r;
1863
1864 TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam);
1865
1866 info = GetPropW(hWnd, szButtonData);
1867
1868 r = CallWindowProcW(info->oldproc, hWnd, msg, wParam, lParam);
1869
1870 switch( msg )
1871 {
1872 case WM_COMMAND:
1873 if (HIWORD(wParam) == EN_CHANGE)
1874 {
1875 msi_mask_control_change( info );
1876 msi_mask_next_control( info, (HWND) lParam );
1877 }
1878 break;
1879 case WM_NCDESTROY:
1880 msi_free( info->prop );
1881 msi_free( info );
1882 RemovePropW( hWnd, szButtonData );
1883 break;
1884 }
1885
1886 return r;
1887 }
1888
1889 /* fish the various bits of the property out and put them in the control */
1890 static void
1891 msi_maskedit_set_text( struct msi_maskedit_info *info, LPCWSTR text )
1892 {
1893 LPCWSTR p;
1894 UINT i;
1895
1896 p = text;
1897 for( i = 0; i < info->num_groups; i++ )
1898 {
1899 if( info->group[i].len < strlenW( p ) )
1900 {
1901 LPWSTR chunk = strdupW( p );
1902 chunk[ info->group[i].len ] = 0;
1903 SetWindowTextW( info->group[i].hwnd, chunk );
1904 msi_free( chunk );
1905 }
1906 else
1907 {
1908 SetWindowTextW( info->group[i].hwnd, p );
1909 break;
1910 }
1911 p += info->group[i].len;
1912 }
1913 }
1914
1915 static struct msi_maskedit_info * msi_dialog_parse_groups( LPCWSTR mask )
1916 {
1917 struct msi_maskedit_info * info = NULL;
1918 int i = 0, n = 0, total = 0;
1919 LPCWSTR p;
1920
1921 TRACE("masked control, template %s\n", debugstr_w(mask));
1922
1923 if( !mask )
1924 return info;
1925
1926 info = msi_alloc_zero( sizeof *info );
1927 if( !info )
1928 return info;
1929
1930 p = strchrW(mask, '<');
1931 if( p )
1932 p++;
1933 else
1934 p = mask;
1935
1936 for( i=0; i<MASK_MAX_GROUPS; i++ )
1937 {
1938 /* stop at the end of the string */
1939 if( p[0] == 0 || p[0] == '>' )
1940 break;
1941
1942 /* count the number of the same identifier */
1943 for( n=0; p[n] == p[0]; n++ )
1944 ;
1945 info->group[i].ofs = total;
1946 info->group[i].type = p[0];
1947 if( p[n] == '=' )
1948 {
1949 n++;
1950 total++; /* an extra not part of the group */
1951 }
1952 info->group[i].len = n;
1953 total += n;
1954 p += n;
1955 }
1956
1957 TRACE("%d characters in %d groups\n", total, i );
1958 if( i == MASK_MAX_GROUPS )
1959 ERR("too many groups in PIDTemplate %s\n", debugstr_w(mask));
1960
1961 info->num_chars = total;
1962 info->num_groups = i;
1963
1964 return info;
1965 }
1966
1967 static void
1968 msi_maskedit_create_children( struct msi_maskedit_info *info, LPCWSTR font )
1969 {
1970 DWORD width, height, style, wx, ww;
1971 RECT rect;
1972 HWND hwnd;
1973 UINT i;
1974
1975 style = WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL;
1976
1977 GetClientRect( info->hwnd, &rect );
1978
1979 width = rect.right - rect.left;
1980 height = rect.bottom - rect.top;
1981
1982 for( i = 0; i < info->num_groups; i++ )
1983 {
1984 if (!msi_mask_editable( info->group[i].type ))
1985 continue;
1986 wx = (info->group[i].ofs * width) / info->num_chars;
1987 ww = (info->group[i].len * width) / info->num_chars;
1988
1989 hwnd = CreateWindowW( szEdit, NULL, style, wx, 0, ww, height,
1990 info->hwnd, NULL, NULL, NULL );
1991 if( !hwnd )
1992 {
1993 ERR("failed to create mask edit sub window\n");
1994 break;
1995 }
1996
1997 SendMessageW( hwnd, EM_LIMITTEXT, info->group[i].len, 0 );
1998
1999 msi_dialog_set_font( info->dialog, hwnd,
2000 font?font:info->dialog->default_font );
2001 info->group[i].hwnd = hwnd;
2002 }
2003 }
2004
2005 /*
2006 * office 2003 uses "73931<````=````=````=````=`````>@@@@@"
2007 * delphi 7 uses "<????-??????-??????-????>" and "<???-???>"
2008 * filemaker pro 7 uses "<^^^^=^^^^=^^^^=^^^^=^^^^=^^^^=^^^^^>"
2009 */
2010 static UINT msi_dialog_maskedit_control( msi_dialog *dialog, MSIRECORD *rec )
2011 {
2012 LPWSTR font_mask, val = NULL, font;
2013 struct msi_maskedit_info *info = NULL;
2014 UINT ret = ERROR_SUCCESS;
2015 msi_control *control;
2016 LPCWSTR prop, mask;
2017
2018 TRACE("\n");
2019
2020 font_mask = msi_get_deformatted_field( dialog->package, rec, 10 );
2021 font = msi_dialog_get_style( font_mask, &mask );
2022 if( !mask )
2023 {
2024 WARN("mask template is empty\n");
2025 goto end;
2026 }
2027
2028 info = msi_dialog_parse_groups( mask );
2029 if( !info )
2030 {
2031 ERR("template %s is invalid\n", debugstr_w(mask));
2032 goto end;
2033 }
2034
2035 info->dialog = dialog;
2036
2037 control = msi_dialog_add_control( dialog, rec, szStatic,
2038 SS_OWNERDRAW | WS_GROUP | WS_VISIBLE );
2039 if( !control )
2040 {
2041 ERR("Failed to create maskedit container\n");
2042 ret = ERROR_FUNCTION_FAILED;
2043 goto end;
2044 }
2045 SetWindowLongPtrW( control->hwnd, GWL_EXSTYLE, WS_EX_CONTROLPARENT );
2046
2047 info->hwnd = control->hwnd;
2048
2049 /* subclass the static control */
2050 info->oldproc = (WNDPROC) SetWindowLongPtrW( info->hwnd, GWLP_WNDPROC,
2051 (LONG_PTR)MSIMaskedEdit_WndProc );
2052 SetPropW( control->hwnd, szButtonData, info );
2053
2054 prop = MSI_RecordGetString( rec, 9 );
2055 if( prop )
2056 info->prop = strdupW( prop );
2057
2058 msi_maskedit_create_children( info, font );
2059
2060 if( prop )
2061 {
2062 val = msi_dup_property( dialog->package->db, prop );
2063 if( val )
2064 {
2065 msi_maskedit_set_text( info, val );
2066 msi_free( val );
2067 }
2068 }
2069
2070 end:
2071 if( ret != ERROR_SUCCESS )
2072 msi_free( info );
2073 msi_free( font_mask );
2074 msi_free( font );
2075 return ret;
2076 }
2077
2078 /******************** Progress Bar *****************************************/
2079
2080 static UINT msi_dialog_progress_bar( msi_dialog *dialog, MSIRECORD *rec )
2081 {
2082 msi_control *control;
2083 DWORD attributes, style;
2084
2085 style = WS_VISIBLE;
2086 attributes = MSI_RecordGetInteger( rec, 8 );
2087 if( !(attributes & msidbControlAttributesProgress95) )
2088 style |= PBS_SMOOTH;
2089
2090 control = msi_dialog_add_control( dialog, rec, PROGRESS_CLASSW, style );
2091 if( !control )
2092 return ERROR_FUNCTION_FAILED;
2093
2094 event_subscribe( dialog, szSetProgress, control->name, szProgress );
2095 return ERROR_SUCCESS;
2096 }
2097
2098 /******************** Path Edit ********************************************/
2099
2100 struct msi_pathedit_info
2101 {
2102 msi_dialog *dialog;
2103 msi_control *control;
2104 WNDPROC oldproc;
2105 };
2106
2107 static void msi_dialog_update_pathedit( msi_dialog *dialog, msi_control *control )
2108 {
2109 LPWSTR prop, path;
2110 BOOL indirect;
2111
2112 if (!control && !(control = msi_dialog_find_control_by_type( dialog, szPathEdit )))
2113 return;
2114
2115 indirect = control->attributes & msidbControlAttributesIndirect;
2116 prop = msi_dialog_dup_property( dialog, control->property, indirect );
2117 path = msi_dialog_dup_property( dialog, prop, TRUE );
2118
2119 SetWindowTextW( control->hwnd, path );
2120 SendMessageW( control->hwnd, EM_SETSEL, 0, -1 );
2121
2122 msi_free( path );
2123 msi_free( prop );
2124 }
2125
2126 /* FIXME: test when this should fail */
2127 static BOOL msi_dialog_verify_path( LPWSTR path )
2128 {
2129 if ( !lstrlenW( path ) )
2130 return FALSE;
2131
2132 if ( PathIsRelativeW( path ) )
2133 return FALSE;
2134
2135 return TRUE;
2136 }
2137
2138 /* returns TRUE if the path is valid, FALSE otherwise */
2139 static BOOL msi_dialog_onkillfocus( msi_dialog *dialog, msi_control *control )
2140 {
2141 LPWSTR buf, prop;
2142 BOOL indirect;
2143 BOOL valid;
2144
2145 indirect = control->attributes & msidbControlAttributesIndirect;
2146 prop = msi_dialog_dup_property( dialog, control->property, indirect );
2147
2148 buf = msi_get_window_text( control->hwnd );
2149
2150 if ( !msi_dialog_verify_path( buf ) )
2151 {
2152 /* FIXME: display an error message box */
2153 ERR("Invalid path %s\n", debugstr_w( buf ));
2154 valid = FALSE;
2155 SetFocus( control->hwnd );
2156 }
2157 else
2158 {
2159 valid = TRUE;
2160 msi_dialog_set_property( dialog->package, prop, buf );
2161 }
2162
2163 msi_dialog_update_pathedit( dialog, control );
2164
2165 TRACE("edit %s contents changed, set %s\n", debugstr_w(control->name),
2166 debugstr_w(prop));
2167
2168 msi_free( buf );
2169 msi_free( prop );
2170
2171 return valid;
2172 }
2173
2174 static LRESULT WINAPI MSIPathEdit_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
2175 {
2176 struct msi_pathedit_info *info = GetPropW(hWnd, szButtonData);
2177 LRESULT r = 0;
2178
2179 TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam);
2180
2181 if ( msg == WM_KILLFOCUS )
2182 {
2183 /* if the path is invalid, don't handle this message */
2184 if ( !msi_dialog_onkillfocus( info->dialog, info->control ) )
2185 return 0;
2186 }
2187
2188 r = CallWindowProcW(info->oldproc, hWnd, msg, wParam, lParam);
2189
2190 if ( msg == WM_NCDESTROY )
2191 {
2192 msi_free( info );
2193 RemovePropW( hWnd, szButtonData );
2194 }
2195
2196 return r;
2197 }
2198
2199 static UINT msi_dialog_pathedit_control( msi_dialog *dialog, MSIRECORD *rec )
2200 {
2201 struct msi_pathedit_info *info;
2202 msi_control *control;
2203 LPCWSTR prop;
2204
2205 info = msi_alloc( sizeof *info );
2206 if (!info)
2207 return ERROR_FUNCTION_FAILED;
2208
2209 control = msi_dialog_add_control( dialog, rec, szEdit,
2210 WS_BORDER | WS_TABSTOP );
2211 control->attributes = MSI_RecordGetInteger( rec, 8 );
2212 prop = MSI_RecordGetString( rec, 9 );
2213 control->property = msi_dialog_dup_property( dialog, prop, FALSE );
2214
2215 info->dialog = dialog;
2216 info->control = control;
2217 info->oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
2218 (LONG_PTR)MSIPathEdit_WndProc );
2219 SetPropW( control->hwnd, szButtonData, info );
2220
2221 msi_dialog_update_pathedit( dialog, control );
2222
2223 return ERROR_SUCCESS;
2224 }
2225
2226 static UINT msi_dialog_radiogroup_handler( msi_dialog *dialog, msi_control *control, WPARAM param )
2227 {
2228 if (HIWORD(param) != BN_CLICKED)
2229 return ERROR_SUCCESS;
2230
2231 TRACE("clicked radio button %s, set %s\n", debugstr_w(control->name), debugstr_w(control->property));
2232
2233 msi_dialog_set_property( dialog->package, control->property, control->name );
2234
2235 return msi_dialog_button_handler( dialog, control, param );
2236 }
2237
2238 /* radio buttons are a bit different from normal controls */
2239 static UINT msi_dialog_create_radiobutton( MSIRECORD *rec, LPVOID param )
2240 {
2241 radio_button_group_descr *group = param;
2242 msi_dialog *dialog = group->dialog;
2243 msi_control *control;
2244 LPCWSTR prop, text, name;
2245 DWORD style, attributes = group->attributes;
2246
2247 style = WS_CHILD | BS_AUTORADIOBUTTON | BS_MULTILINE | WS_TABSTOP;
2248 name = MSI_RecordGetString( rec, 3 );
2249 text = MSI_RecordGetString( rec, 8 );
2250 if( attributes & msidbControlAttributesVisible )
2251 style |= WS_VISIBLE;
2252 if( ~attributes & msidbControlAttributesEnabled )
2253 style |= WS_DISABLED;
2254
2255 control = dialog_create_window( dialog, rec, 0, szButton, name, text, style,
2256 group->parent->hwnd );
2257 if (!control)
2258 return ERROR_FUNCTION_FAILED;
2259 control->handler = msi_dialog_radiogroup_handler;
2260
2261 if (group->propval && !strcmpW( control->name, group->propval ))
2262 SendMessageW(control->hwnd, BM_SETCHECK, BST_CHECKED, 0);
2263
2264 prop = MSI_RecordGetString( rec, 1 );
2265 if( prop )
2266 control->property = strdupW( prop );
2267
2268 return ERROR_SUCCESS;
2269 }
2270
2271 static BOOL CALLBACK msi_radioground_child_enum( HWND hWnd, LPARAM lParam )
2272 {
2273 EnableWindow( hWnd, lParam );
2274 return TRUE;
2275 }
2276
2277 static LRESULT WINAPI MSIRadioGroup_WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
2278 {
2279 WNDPROC oldproc = (WNDPROC)GetPropW( hWnd, szButtonData );
2280 LRESULT r;
2281
2282 TRACE("hWnd %p msg %04x wParam 0x%08lx lParam 0x%08lx\n", hWnd, msg, wParam, lParam);
2283
2284 if (msg == WM_COMMAND) /* Forward notifications to dialog */
2285 SendMessageW( GetParent( hWnd ), msg, wParam, lParam );
2286
2287 r = CallWindowProcW( oldproc, hWnd, msg, wParam, lParam );
2288
2289 /* make sure the radio buttons show as disabled if the parent is disabled */
2290 if (msg == WM_ENABLE)
2291 EnumChildWindows( hWnd, msi_radioground_child_enum, wParam );
2292
2293 return r;
2294 }
2295
2296 static UINT msi_dialog_radiogroup_control( msi_dialog *dialog, MSIRECORD *rec )
2297 {
2298 static const WCHAR query[] = {
2299 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
2300 'R','a','d','i','o','B','u','t','t','o','n',' ','W','H','E','R','E',' ',
2301 '`','P','r','o','p','e','r','t','y','`',' ','=',' ','\'','%','s','\'',0};
2302 UINT r;
2303 LPCWSTR prop;
2304 msi_control *control;
2305 MSIQUERY *view;
2306 radio_button_group_descr group;
2307 MSIPACKAGE *package = dialog->package;
2308 WNDPROC oldproc;
2309 DWORD attr, style = WS_GROUP;
2310
2311 prop = MSI_RecordGetString( rec, 9 );
2312
2313 TRACE("%p %p %s\n", dialog, rec, debugstr_w( prop ));
2314
2315 attr = MSI_RecordGetInteger( rec, 8 );
2316 if (attr & msidbControlAttributesHasBorder)
2317 style |= BS_GROUPBOX;
2318 else
2319 style |= BS_OWNERDRAW;
2320
2321 /* Create parent group box to hold radio buttons */
2322 control = msi_dialog_add_control( dialog, rec, szButton, style );
2323 if( !control )
2324 return ERROR_FUNCTION_FAILED;
2325
2326 oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
2327 (LONG_PTR)MSIRadioGroup_WndProc );
2328 SetPropW(control->hwnd, szButtonData, oldproc);
2329 SetWindowLongPtrW( control->hwnd, GWL_EXSTYLE, WS_EX_CONTROLPARENT );
2330
2331 if( prop )
2332 control->property = strdupW( prop );
2333
2334 /* query the Radio Button table for all control in this group */
2335 r = MSI_OpenQuery( package->db, &view, query, prop );
2336 if( r != ERROR_SUCCESS )
2337 {
2338 ERR("query failed for dialog %s radio group %s\n",
2339 debugstr_w(dialog->name), debugstr_w(prop));
2340 return ERROR_INVALID_PARAMETER;
2341 }
2342
2343 group.dialog = dialog;
2344 group.parent = control;
2345 group.attributes = MSI_RecordGetInteger( rec, 8 );
2346 group.propval = msi_dup_property( dialog->package->db, control->property );
2347
2348 r = MSI_IterateRecords( view, 0, msi_dialog_create_radiobutton, &group );
2349 msiobj_release( &view->hdr );
2350 msi_free( group.propval );
2351 return r;
2352 }
2353
2354 static void
2355 msi_seltree_sync_item_state( HWND hwnd, MSIFEATURE *feature, HTREEITEM hItem )
2356 {
2357 TVITEMW tvi;
2358 DWORD index = feature->ActionRequest;
2359
2360 TRACE("Feature %s -> %d %d %d\n", debugstr_w(feature->Title),
2361 feature->Installed, feature->Action, feature->ActionRequest);
2362
2363 if (index == INSTALLSTATE_UNKNOWN)
2364 index = INSTALLSTATE_ABSENT;
2365
2366 tvi.mask = TVIF_STATE;
2367 tvi.hItem = hItem;
2368 tvi.state = INDEXTOSTATEIMAGEMASK( index );
2369 tvi.stateMask = TVIS_STATEIMAGEMASK;
2370
2371 SendMessageW( hwnd, TVM_SETITEMW, 0, (LPARAM) &tvi );
2372 }
2373
2374 static UINT
2375 msi_seltree_popup_menu( HWND hwnd, INT x, INT y )
2376 {
2377 HMENU hMenu;
2378 INT r;
2379
2380 /* create a menu to display */
2381 hMenu = CreatePopupMenu();
2382
2383 /* FIXME: load strings from resources */
2384 AppendMenuA( hMenu, MF_ENABLED, INSTALLSTATE_LOCAL, "Install feature locally");
2385 AppendMenuA( hMenu, MF_ENABLED, USER_INSTALLSTATE_ALL, "Install entire feature");
2386 AppendMenuA( hMenu, MF_ENABLED, INSTALLSTATE_ADVERTISED, "Install on demand");
2387 AppendMenuA( hMenu, MF_ENABLED, INSTALLSTATE_ABSENT, "Don't install");
2388 r = TrackPopupMenu( hMenu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RETURNCMD,
2389 x, y, 0, hwnd, NULL );
2390 DestroyMenu( hMenu );
2391 return r;
2392 }
2393
2394 static void
2395 msi_seltree_update_feature_installstate( HWND hwnd, HTREEITEM hItem,
2396 MSIPACKAGE *package, MSIFEATURE *feature, INSTALLSTATE state )
2397 {
2398 feature->ActionRequest = state;
2399 msi_seltree_sync_item_state( hwnd, feature, hItem );
2400 ACTION_UpdateComponentStates( package, feature );
2401 }
2402
2403 static void
2404 msi_seltree_update_siblings_and_children_installstate( HWND hwnd, HTREEITEM curr,
2405 MSIPACKAGE *package, INSTALLSTATE state)
2406 {
2407 /* update all siblings */
2408 do
2409 {
2410 MSIFEATURE *feature;
2411 HTREEITEM child;
2412
2413 feature = msi_seltree_feature_from_item( hwnd, curr );
2414 msi_seltree_update_feature_installstate( hwnd, curr, package, feature, state );
2415
2416 /* update this sibling's children */
2417 child = (HTREEITEM)SendMessageW( hwnd, TVM_GETNEXTITEM, (WPARAM)TVGN_CHILD, (LPARAM)curr );
2418 if (child)
2419 msi_seltree_update_siblings_and_children_installstate( hwnd, child,
2420 package, state );
2421 }
2422 while ((curr = (HTREEITEM)SendMessageW( hwnd, TVM_GETNEXTITEM, (WPARAM)TVGN_NEXT, (LPARAM)curr )));
2423 }
2424
2425 static LRESULT
2426 msi_seltree_menu( HWND hwnd, HTREEITEM hItem )
2427 {
2428 struct msi_selection_tree_info *info;
2429 MSIFEATURE *feature;
2430 MSIPACKAGE *package;
2431 union {
2432 RECT rc;
2433 POINT pt[2];
2434 HTREEITEM hItem;
2435 } u;
2436 UINT r;
2437
2438 info = GetPropW(hwnd, szButtonData);
2439 package = info->dialog->package;
2440
2441 feature = msi_seltree_feature_from_item( hwnd, hItem );
2442 if (!feature)
2443 {
2444 ERR("item %p feature was NULL\n", hItem);
2445 return 0;
2446 }
2447
2448 /* get the item's rectangle to put the menu just below it */
2449 u.hItem = hItem;
2450 SendMessageW( hwnd, TVM_GETITEMRECT, 0, (LPARAM) &u.rc );
2451 MapWindowPoints( hwnd, NULL, u.pt, 2 );
2452
2453 r = msi_seltree_popup_menu( hwnd, u.rc.left, u.rc.top );
2454
2455 switch (r)
2456 {
2457 case USER_INSTALLSTATE_ALL:
2458 r = INSTALLSTATE_LOCAL;
2459 /* fall-through */
2460 case INSTALLSTATE_ADVERTISED:
2461 case INSTALLSTATE_ABSENT:
2462 {
2463 HTREEITEM child;
2464 child = (HTREEITEM)SendMessageW( hwnd, TVM_GETNEXTITEM, (WPARAM)TVGN_CHILD, (LPARAM)hItem );
2465 if (child)
2466 msi_seltree_update_siblings_and_children_installstate( hwnd, child, package, r );
2467 }
2468 /* fall-through */
2469 case INSTALLSTATE_LOCAL:
2470 msi_seltree_update_feature_installstate( hwnd, hItem, package, feature, r );
2471 break;
2472 }
2473
2474 return 0;
2475 }
2476
2477 static LRESULT WINAPI
2478 MSISelectionTree_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
2479 {
2480 struct msi_selection_tree_info *info;
2481 TVHITTESTINFO tvhti;
2482 HRESULT r;
2483
2484 TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam);
2485
2486 info = GetPropW(hWnd, szButtonData);
2487
2488 switch( msg )
2489 {
2490 case WM_LBUTTONDOWN:
2491 tvhti.pt.x = (short)LOWORD( lParam );
2492 tvhti.pt.y = (short)HIWORD( lParam );
2493 tvhti.flags = 0;
2494 tvhti.hItem = 0;
2495 CallWindowProcW(info->oldproc, hWnd, TVM_HITTEST, 0, (LPARAM) &tvhti );
2496 if (tvhti.flags & TVHT_ONITEMSTATEICON)
2497 return msi_seltree_menu( hWnd, tvhti.hItem );
2498 break;
2499 }
2500 r = CallWindowProcW(info->oldproc, hWnd, msg, wParam, lParam);
2501
2502 switch( msg )
2503 {
2504 case WM_NCDESTROY:
2505 msi_free( info );
2506 RemovePropW( hWnd, szButtonData );
2507 break;
2508 }
2509 return r;
2510 }
2511
2512 static void
2513 msi_seltree_add_child_features( MSIPACKAGE *package, HWND hwnd,
2514 LPCWSTR parent, HTREEITEM hParent )
2515 {
2516 struct msi_selection_tree_info *info = GetPropW( hwnd, szButtonData );
2517 MSIFEATURE *feature;
2518 TVINSERTSTRUCTW tvis;
2519 HTREEITEM hitem, hfirst = NULL;
2520
2521 LIST_FOR_EACH_ENTRY( feature, &package->features, MSIFEATURE, entry )
2522 {
2523 if ( parent && feature->Feature_Parent && strcmpW( parent, feature->Feature_Parent ))
2524 continue;
2525 else if ( parent && !feature->Feature_Parent )
2526 continue;
2527 else if ( !parent && feature->Feature_Parent )
2528 continue;
2529
2530 if ( !feature->Title )
2531 continue;
2532
2533 if ( !feature->Display )
2534 continue;
2535
2536 memset( &tvis, 0, sizeof tvis );
2537 tvis.hParent = hParent;
2538 tvis.hInsertAfter = TVI_LAST;
2539 tvis.u.item.mask = TVIF_TEXT | TVIF_PARAM;
2540 tvis.u.item.pszText = feature->Title;
2541 tvis.u.item.lParam = (LPARAM) feature;
2542
2543 hitem = (HTREEITEM) SendMessageW( hwnd, TVM_INSERTITEMW, 0, (LPARAM) &tvis );
2544 if (!hitem)
2545 continue;
2546
2547 if (!hfirst)
2548 hfirst = hitem;
2549
2550 msi_seltree_sync_item_state( hwnd, feature, hitem );
2551 msi_seltree_add_child_features( package, hwnd,
2552 feature->Feature, hitem );
2553
2554 /* the node is expanded if Display is odd */
2555 if ( feature->Display % 2 != 0 )
2556 SendMessageW( hwnd, TVM_EXPAND, TVE_EXPAND, (LPARAM) hitem );
2557 }
2558
2559 /* select the first item */
2560 SendMessageW( hwnd, TVM_SELECTITEM, TVGN_CARET | TVGN_DROPHILITE, (LPARAM) hfirst );
2561 info->selected = hfirst;
2562 }
2563
2564 static void msi_seltree_create_imagelist( HWND hwnd )
2565 {
2566 const int bm_width = 32, bm_height = 16, bm_count = 3;
2567 const int bm_resource = 0x1001;
2568 HIMAGELIST himl;
2569 int i;
2570 HBITMAP hbmp;
2571
2572 himl = ImageList_Create( bm_width, bm_height, FALSE, 4, 0 );
2573 if (!himl)
2574 {
2575 ERR("failed to create image list\n");
2576 return;
2577 }
2578
2579 for (i=0; i<bm_count; i++)
2580 {
2581 hbmp = LoadBitmapW( msi_hInstance, MAKEINTRESOURCEW(i+bm_resource) );
2582 if (!hbmp)
2583 {
2584 ERR("failed to load bitmap %d\n", i);
2585 break;
2586 }
2587
2588 /*
2589 * Add a dummy bitmap at offset zero because the treeview
2590 * can't use it as a state mask (zero means no user state).
2591 */
2592 if (!i)
2593 ImageList_Add( himl, hbmp, NULL );
2594
2595 ImageList_Add( himl, hbmp, NULL );
2596 }
2597
2598 SendMessageW( hwnd, TVM_SETIMAGELIST, TVSIL_STATE, (LPARAM)himl );
2599 }
2600
2601 static UINT msi_dialog_seltree_handler( msi_dialog *dialog,
2602 msi_control *control, WPARAM param )
2603 {
2604 struct msi_selection_tree_info *info = GetPropW( control->hwnd, szButtonData );
2605 LPNMTREEVIEWW tv = (LPNMTREEVIEWW)param;
2606 MSIRECORD *row, *rec;
2607 MSIFOLDER *folder;
2608 MSIFEATURE *feature;
2609 LPCWSTR dir, title = NULL;
2610 UINT r = ERROR_SUCCESS;
2611
2612 static const WCHAR select[] = {
2613 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
2614 '`','F','e','a','t','u','r','e','`',' ','W','H','E','R','E',' ',
2615 '`','T','i','t','l','e','`',' ','=',' ','\'','%','s','\'',0
2616 };
2617
2618 if (tv->hdr.code != TVN_SELCHANGINGW)
2619 return ERROR_SUCCESS;
2620
2621 info->selected = tv->itemNew.hItem;
2622
2623 if (!(tv->itemNew.mask & TVIF_TEXT))
2624 {
2625 feature = msi_seltree_feature_from_item( control->hwnd, tv->itemNew.hItem );
2626 if (feature)
2627 title = feature->Title;
2628 }
2629 else
2630 title = tv->itemNew.pszText;
2631
2632 row = MSI_QueryGetRecord( dialog->package->db, select, title );
2633 if (!row)
2634 return ERROR_FUNCTION_FAILED;
2635
2636 rec = MSI_CreateRecord( 1 );
2637
2638 MSI_RecordSetStringW( rec, 1, MSI_RecordGetString( row, 4 ) );
2639 msi_event_fire( dialog->package, szSelectionDescription, rec );
2640
2641 dir = MSI_RecordGetString( row, 7 );
2642 if (dir)
2643 {
2644 folder = msi_get_loaded_folder( dialog->package, dir );
2645 if (!folder)
2646 {
2647 r = ERROR_FUNCTION_FAILED;
2648 goto done;
2649 }
2650 MSI_RecordSetStringW( rec, 1, folder->ResolvedTarget );
2651 }
2652 else
2653 MSI_RecordSetStringW( rec, 1, NULL );
2654
2655 msi_event_fire( dialog->package, szSelectionPath, rec );
2656
2657 done:
2658 msiobj_release(&row->hdr);
2659 msiobj_release(&rec->hdr);
2660
2661 return r;
2662 }
2663
2664 static UINT msi_dialog_selection_tree( msi_dialog *dialog, MSIRECORD *rec )
2665 {
2666 msi_control *control;
2667 LPCWSTR prop, control_name;
2668 MSIPACKAGE *package = dialog->package;
2669 DWORD style;
2670 struct msi_selection_tree_info *info;
2671
2672 info = msi_alloc( sizeof *info );
2673 if (!info)
2674 return ERROR_FUNCTION_FAILED;
2675
2676 /* create the treeview control */
2677 style = TVS_HASLINES | TVS_HASBUTTONS | TVS_LINESATROOT;
2678 style |= WS_GROUP | WS_VSCROLL;
2679 control = msi_dialog_add_control( dialog, rec, WC_TREEVIEWW, style );
2680 if (!control)
2681 {
2682 msi_free(info);
2683 return ERROR_FUNCTION_FAILED;
2684 }
2685
2686 control->handler = msi_dialog_seltree_handler;
2687 control_name = MSI_RecordGetString( rec, 2 );
2688 control->attributes = MSI_RecordGetInteger( rec, 8 );
2689 prop = MSI_RecordGetString( rec, 9 );
2690 control->property = msi_dialog_dup_property( dialog, prop, FALSE );
2691
2692 /* subclass */
2693 info->dialog = dialog;
2694 info->hwnd = control->hwnd;
2695 info->oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
2696 (LONG_PTR)MSISelectionTree_WndProc );
2697 SetPropW( control->hwnd, szButtonData, info );
2698
2699 event_subscribe( dialog, szSelectionPath, control_name, szProperty );
2700
2701 /* initialize it */
2702 msi_seltree_create_imagelist( control->hwnd );
2703 msi_seltree_add_child_features( package, control->hwnd, NULL, NULL );
2704
2705 return ERROR_SUCCESS;
2706 }
2707
2708 /******************** Group Box ***************************************/
2709
2710 static UINT msi_dialog_group_box( msi_dialog *dialog, MSIRECORD *rec )
2711 {
2712 msi_control *control;
2713 DWORD style;
2714
2715 style = BS_GROUPBOX | WS_CHILD | WS_GROUP;
2716 control = msi_dialog_add_control( dialog, rec, WC_BUTTONW, style );
2717 if (!control)
2718 return ERROR_FUNCTION_FAILED;
2719
2720 return ERROR_SUCCESS;
2721 }
2722
2723 /******************** List Box ***************************************/
2724
2725 struct msi_listbox_info
2726 {
2727 msi_dialog *dialog;
2728 HWND hwnd;
2729 WNDPROC oldproc;
2730 DWORD num_items;
2731 DWORD addpos_items;
2732 LPWSTR *items;
2733 };
2734
2735 static LRESULT WINAPI MSIListBox_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
2736 {
2737 struct msi_listbox_info *info;
2738 LRESULT r;
2739 DWORD j;
2740
2741 TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam);
2742
2743 info = GetPropW( hWnd, szButtonData );
2744 if (!info)
2745 return 0;
2746
2747 r = CallWindowProcW( info->oldproc, hWnd, msg, wParam, lParam );
2748
2749 switch( msg )
2750 {
2751 case WM_NCDESTROY:
2752 for (j = 0; j < info->num_items; j++)
2753 msi_free( info->items[j] );
2754 msi_free( info->items );
2755 msi_free( info );
2756 RemovePropW( hWnd, szButtonData );
2757 break;
2758 }
2759
2760 return r;
2761 }
2762
2763 static UINT msi_listbox_add_item( MSIRECORD *rec, LPVOID param )
2764 {
2765 struct msi_listbox_info *info = param;
2766 LPCWSTR value, text;
2767 int pos;
2768
2769 value = MSI_RecordGetString( rec, 3 );
2770 text = MSI_RecordGetString( rec, 4 );
2771
2772 info->items[info->addpos_items] = strdupW( value );
2773
2774 pos = SendMessageW( info->hwnd, LB_ADDSTRING, 0, (LPARAM)text );
2775 SendMessageW( info->hwnd, LB_SETITEMDATA, pos, (LPARAM)info->items[info->addpos_items] );
2776 info->addpos_items++;
2777 return ERROR_SUCCESS;
2778 }
2779
2780 static UINT msi_listbox_add_items( struct msi_listbox_info *info, LPCWSTR property )
2781 {
2782 static const WCHAR query[] = {
2783 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
2784 '`','L','i','s','t','B','o','x','`',' ','W','H','E','R','E',' ',
2785 '`','P','r','o','p','e','r','t','y','`',' ','=',' ','\'','%','s','\'',' ',
2786 'O','R','D','E','R',' ','B','Y',' ','`','O','r','d','e','r','`',0};
2787 MSIQUERY *view;
2788 DWORD count;
2789 UINT r;
2790
2791 r = MSI_OpenQuery( info->dialog->package->db, &view, query, property );
2792 if ( r != ERROR_SUCCESS )
2793 return r;
2794
2795 /* just get the number of records */
2796 count = 0;
2797 r = MSI_IterateRecords( view, &count, NULL, NULL );
2798 if (r != ERROR_SUCCESS)
2799 {
2800 msiobj_release( &view->hdr );
2801 return r;
2802 }
2803 info->num_items = count;
2804 info->items = msi_alloc( sizeof(*info->items) * count );
2805
2806 r = MSI_IterateRecords( view, NULL, msi_listbox_add_item, info );
2807 msiobj_release( &view->hdr );
2808 return r;
2809 }
2810
2811 static UINT msi_dialog_listbox_handler( msi_dialog *dialog,
2812 msi_control *control, WPARAM param )
2813 {
2814 struct msi_listbox_info *info;
2815 int index;
2816 LPCWSTR value;
2817
2818 if( HIWORD(param) != LBN_SELCHANGE )
2819 return ERROR_SUCCESS;
2820
2821 info = GetPropW( control->hwnd, szButtonData );
2822 index = SendMessageW( control->hwnd, LB_GETCURSEL, 0, 0 );
2823 value = (LPCWSTR) SendMessageW( control->hwnd, LB_GETITEMDATA, index, 0 );
2824
2825 msi_dialog_set_property( info->dialog->package, control->property, value );
2826 msi_dialog_evaluate_control_conditions( info->dialog );
2827
2828 return ERROR_SUCCESS;
2829 }
2830
2831 static UINT msi_dialog_list_box( msi_dialog *dialog, MSIRECORD *rec )
2832 {
2833 struct msi_listbox_info *info;
2834 msi_control *control;
2835 DWORD attributes, style;
2836 LPCWSTR prop;
2837
2838 info = msi_alloc( sizeof *info );
2839 if (!info)
2840 return ERROR_FUNCTION_FAILED;
2841
2842 style = WS_TABSTOP | WS_GROUP | WS_CHILD | LBS_NOTIFY | WS_VSCROLL | WS_BORDER;
2843 attributes = MSI_RecordGetInteger( rec, 8 );
2844 if (~attributes & msidbControlAttributesSorted)
2845 style |= LBS_SORT;
2846
2847 control = msi_dialog_add_control( dialog, rec, WC_LISTBOXW, style );
2848 if (!control)
2849 {
2850 msi_free(info);
2851 return ERROR_FUNCTION_FAILED;
2852 }
2853
2854 control->handler = msi_dialog_listbox_handler;
2855
2856 prop = MSI_RecordGetString( rec, 9 );
2857 control->property = msi_dialog_dup_property( dialog, prop, FALSE );
2858
2859 /* subclass */
2860 info->dialog = dialog;
2861 info->hwnd = control->hwnd;
2862 info->items = NULL;
2863 info->addpos_items = 0;
2864 info->oldproc = (WNDPROC)SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC,
2865 (LONG_PTR)MSIListBox_WndProc );
2866 SetPropW( control->hwnd, szButtonData, info );
2867
2868 if ( control->property )
2869 msi_listbox_add_items( info, control->property );
2870
2871 return ERROR_SUCCESS;
2872 }
2873
2874 /******************** Directory Combo ***************************************/
2875
2876 static void msi_dialog_update_directory_combo( msi_dialog *dialog, msi_control *control )
2877 {
2878 LPWSTR prop, path;
2879 BOOL indirect;
2880
2881 if (!control && !(control = msi_dialog_find_control_by_type( dialog, szDirectoryCombo )))
2882 return;
2883
2884 indirect = control->attributes & msidbControlAttributesIndirect;
2885 prop = msi_dialog_dup_property( dialog, control->property, indirect );
2886 path = msi_dialog_dup_property( dialog, prop, TRUE );
2887
2888 PathStripPathW( path );
2889 PathRemoveBackslashW( path );
2890
2891 SendMessageW( control->hwnd, CB_INSERTSTRING, 0, (LPARAM)path );
2892 SendMessageW( control->hwnd, CB_SETCURSEL, 0, 0 );
2893
2894 msi_free( path );
2895 msi_free( prop );
2896 }
2897
2898 static UINT msi_dialog_directory_combo( msi_dialog *dialog, MSIRECORD *rec )
2899 {
2900 msi_control *control;
2901 LPCWSTR prop;
2902 DWORD style;
2903
2904 /* FIXME: use CBS_OWNERDRAWFIXED and add owner draw code */
2905 style = CBS_DROPDOWNLIST | CBS_HASSTRINGS | WS_CHILD |
2906 WS_GROUP | WS_TABSTOP | WS_VSCROLL;
2907 control = msi_dialog_add_control( dialog, rec, WC_COMBOBOXW, style );
2908 if (!control)
2909 return ERROR_FUNCTION_FAILED;
2910
2911 control->attributes = MSI_RecordGetInteger( rec, 8 );
2912 prop = MSI_RecordGetString( rec, 9 );
2913 control->property = msi_dialog_dup_property( dialog, prop, FALSE );
2914
2915 msi_dialog_update_directory_combo( dialog, control );
2916
2917 return ERROR_SUCCESS;
2918 }
2919
2920 /******************** Directory List ***************************************/
2921
2922 static void msi_dialog_update_directory_list( msi_dialog *dialog, msi_control *control )
2923 {
2924 WCHAR dir_spec[MAX_PATH];
2925 WIN32_FIND_DATAW wfd;
2926 LPWSTR prop, path;
2927 BOOL indirect;
2928 LVITEMW item;
2929 HANDLE file;
2930
2931 static const WCHAR asterisk[] = {'*',0};
2932
2933 if (!control && !(control = msi_dialog_find_control_by_type( dialog, szDirectoryList )))
2934 return;
2935
2936 /* clear the list-view */
2937 SendMessageW( control->hwnd, LVM_DELETEALLITEMS, 0, 0 );
2938
2939 indirect = control->attributes & msidbControlAttributesIndirect;
2940 prop = msi_dialog_dup_property( dialog, control->property, indirect );
2941 path = msi_dialog_dup_property( dialog, prop, TRUE );
2942
2943 lstrcpyW( dir_spec, path );
2944 lstrcatW( dir_spec, asterisk );
2945
2946 file = FindFirstFileW( dir_spec, &wfd );
2947 if ( file == INVALID_HANDLE_VALUE )
2948 return;
2949
2950 do
2951 {
2952 if ( wfd.dwFileAttributes != FILE_ATTRIBUTE_DIRECTORY )
2953 continue;
2954
2955 if ( !strcmpW( wfd.cFileName, szDot ) || !strcmpW( wfd.cFileName, szDotDot ) )
2956 continue;
2957
2958 item.mask = LVIF_TEXT;
2959 item.cchTextMax = MAX_PATH;
2960 item.iItem = 0;
2961 item.iSubItem = 0;
2962 item.pszText = wfd.cFileName;
2963
2964 SendMessageW( control->hwnd, LVM_INSERTITEMW, 0, (LPARAM)&item );
2965 } while ( FindNextFileW( file, &wfd ) );
2966
2967 msi_free( prop );
2968 msi_free( path );
2969 FindClose( file );
2970 }
2971
2972 UINT msi_dialog_directorylist_up( msi_dialog *dialog )
2973 {
2974 msi_control *control;
2975 LPWSTR prop, path, ptr;
2976 BOOL indirect;
2977
2978 control = msi_dialog_find_control_by_type( dialog, szDirectoryList );
2979 indirect = control->attributes & msidbControlAttributesIndirect;
2980 prop = msi_dialog_dup_property( dialog, control->property, indirect );
2981 path = msi_dialog_dup_property( dialog, prop, TRUE );
2982
2983 /* strip off the last directory */
2984 ptr = PathFindFileNameW( path );
2985 if (ptr != path) *(ptr - 1) = '\0';
2986 PathAddBackslashW( path );
2987
2988 msi_dialog_set_property( dialog->packa