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