1 /* 2 * Implementation of the Microsoft Installer (msi.dll) 3 * 4 * Copyright 2005 Mike McCormack for CodeWeavers 5 * Copyright 2005 Aric Stewart for CodeWeavers 6 * 7 * This library is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU Lesser General Public 9 * License as published by the Free Software Foundation; either 10 * version 2.1 of the License, or (at your option) any later version. 11 * 12 * This library is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with this library; if not, write to the Free Software 19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 20 */ 21 22 #include "msipriv.h" 23 24 #include <olectl.h> 25 #include <richedit.h> 26 #include <shellapi.h> 27 28 WINE_DEFAULT_DEBUG_CHANNEL(msi); 29 30 extern HINSTANCE msi_hInstance; 31 32 struct msi_control_tag; 33 typedef struct msi_control_tag msi_control; 34 typedef UINT (*msi_handler)( msi_dialog *, msi_control *, WPARAM ); 35 typedef void (*msi_update)( msi_dialog *, msi_control * ); 36 typedef UINT (*control_event_handler)( msi_dialog *, const WCHAR *, const WCHAR * ); 37 typedef UINT (*event_handler)( msi_dialog *, const WCHAR * ); 38 39 struct msi_control_tag 40 { 41 struct list entry; 42 HWND hwnd; 43 msi_handler handler; 44 msi_update update; 45 LPWSTR property; 46 LPWSTR value; 47 HBITMAP hBitmap; 48 HICON hIcon; 49 LPWSTR tabnext; 50 LPWSTR type; 51 HMODULE hDll; 52 float progress_current; 53 float progress_max; 54 BOOL progress_backwards; 55 DWORD attributes; 56 WCHAR name[1]; 57 }; 58 59 typedef struct msi_font_tag 60 { 61 struct list entry; 62 HFONT hfont; 63 COLORREF color; 64 WCHAR name[1]; 65 } msi_font; 66 67 struct msi_dialog_tag 68 { 69 MSIPACKAGE *package; 70 msi_dialog *parent; 71 control_event_handler event_handler; 72 BOOL finished; 73 INT scale; 74 DWORD attributes; 75 SIZE size; 76 HWND hwnd; 77 LPWSTR default_font; 78 struct list fonts; 79 struct list controls; 80 HWND hWndFocus; 81 LPWSTR control_default; 82 LPWSTR control_cancel; 83 event_handler pending_event; 84 LPWSTR pending_argument; 85 INT retval; 86 WCHAR name[1]; 87 }; 88 89 struct subscriber 90 { 91 struct list entry; 92 msi_dialog *dialog; 93 WCHAR *event; 94 WCHAR *control; 95 WCHAR *attribute; 96 }; 97 98 typedef UINT (*msi_dialog_control_func)( msi_dialog *dialog, MSIRECORD *rec ); 99 struct control_handler 100 { 101 LPCWSTR control_type; 102 msi_dialog_control_func func; 103 }; 104 105 typedef struct 106 { 107 msi_dialog* dialog; 108 msi_control *parent; 109 LPWSTR propval; 110 } radio_button_group_descr; 111 112 static const WCHAR szMsiDialogClass[] = { 'M','s','i','D','i','a','l','o','g','C','l','o','s','e','C','l','a','s','s',0 }; 113 static const WCHAR szMsiHiddenWindow[] = { 'M','s','i','H','i','d','d','e','n','W','i','n','d','o','w',0 }; 114 static const WCHAR szStatic[] = { 'S','t','a','t','i','c',0 }; 115 static const WCHAR szButton[] = { 'B','U','T','T','O','N', 0 }; 116 static const WCHAR szButtonData[] = { 'M','S','I','D','A','T','A',0 }; 117 static const WCHAR szProgress[] = { 'P','r','o','g','r','e','s','s',0 }; 118 static const WCHAR szText[] = { 'T','e','x','t',0 }; 119 static const WCHAR szPushButton[] = { 'P','u','s','h','B','u','t','t','o','n',0 }; 120 static const WCHAR szLine[] = { 'L','i','n','e',0 }; 121 static const WCHAR szBitmap[] = { 'B','i','t','m','a','p',0 }; 122 static const WCHAR szCheckBox[] = { 'C','h','e','c','k','B','o','x',0 }; 123 static const WCHAR szScrollableText[] = { 'S','c','r','o','l','l','a','b','l','e','T','e','x','t',0 }; 124 static const WCHAR szComboBox[] = { 'C','o','m','b','o','B','o','x',0 }; 125 static const WCHAR szEdit[] = { 'E','d','i','t',0 }; 126 static const WCHAR szMaskedEdit[] = { 'M','a','s','k','e','d','E','d','i','t',0 }; 127 static const WCHAR szPathEdit[] = { 'P','a','t','h','E','d','i','t',0 }; 128 static const WCHAR szProgressBar[] = { 'P','r','o','g','r','e','s','s','B','a','r',0 }; 129 static const WCHAR szSetProgress[] = { 'S','e','t','P','r','o','g','r','e','s','s',0 }; 130 static const WCHAR szRadioButtonGroup[] = { 'R','a','d','i','o','B','u','t','t','o','n','G','r','o','u','p',0 }; 131 static const WCHAR szIcon[] = { 'I','c','o','n',0 }; 132 static const WCHAR szSelectionTree[] = { 'S','e','l','e','c','t','i','o','n','T','r','e','e',0 }; 133 static const WCHAR szGroupBox[] = { 'G','r','o','u','p','B','o','x',0 }; 134 static const WCHAR szListBox[] = { 'L','i','s','t','B','o','x',0 }; 135 static const WCHAR szDirectoryCombo[] = { 'D','i','r','e','c','t','o','r','y','C','o','m','b','o',0 }; 136 static const WCHAR szDirectoryList[] = { 'D','i','r','e','c','t','o','r','y','L','i','s','t',0 }; 137 static const WCHAR szVolumeCostList[] = { 'V','o','l','u','m','e','C','o','s','t','L','i','s','t',0 }; 138 static const WCHAR szVolumeSelectCombo[] = { 'V','o','l','u','m','e','S','e','l','e','c','t','C','o','m','b','o',0 }; 139 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}; 140 static const WCHAR szSelectionPath[] = {'S','e','l','e','c','t','i','o','n','P','a','t','h',0}; 141 static const WCHAR szHyperLink[] = {'H','y','p','e','r','L','i','n','k',0}; 142 143 /* dialog sequencing */ 144 145 #define WM_MSI_DIALOG_CREATE (WM_USER+0x100) 146 #define WM_MSI_DIALOG_DESTROY (WM_USER+0x101) 147 148 #define USER_INSTALLSTATE_ALL 0x1000 149 150 static DWORD uiThreadId; 151 static HWND hMsiHiddenWindow; 152 153 static LPWSTR msi_get_window_text( HWND hwnd ) 154 { 155 UINT sz, r; 156 LPWSTR buf; 157 158 sz = 0x20; 159 buf = msi_alloc( sz*sizeof(WCHAR) ); 160 while ( buf ) 161 { 162 r = GetWindowTextW( hwnd, buf, sz ); 163 if ( r < (sz - 1) ) 164 break; 165 sz *= 2; 166 buf = msi_realloc( buf, sz*sizeof(WCHAR) ); 167 } 168 169 return buf; 170 } 171 172 static INT msi_dialog_scale_unit( msi_dialog *dialog, INT val ) 173 { 174 return MulDiv( val, dialog->scale, 12 ); 175 } 176 177 static msi_control *msi_dialog_find_control( msi_dialog *dialog, LPCWSTR name ) 178 { 179 msi_control *control; 180 181 if( !name ) 182 return NULL; 183 if( !dialog->hwnd ) 184 return NULL; 185 LIST_FOR_EACH_ENTRY( control, &dialog->controls, msi_control, entry ) 186 if( !strcmpW( control->name, name ) ) /* FIXME: case sensitive? */ 187 return control; 188 return NULL; 189 } 190 191 static msi_control *msi_dialog_find_control_by_type( msi_dialog *dialog, LPCWSTR type ) 192 { 193 msi_control *control; 194 195 if( !type ) 196 return NULL; 197 if( !dialog->hwnd ) 198 return NULL; 199 LIST_FOR_EACH_ENTRY( control, &dialog->controls, msi_control, entry ) 200 if( !strcmpW( control->type, type ) ) /* FIXME: case sensitive? */ 201 return control; 202 return NULL; 203 } 204 205 static msi_control *msi_dialog_find_control_by_hwnd( msi_dialog *dialog, HWND hwnd ) 206 { 207 msi_control *control; 208 209 if( !dialog->hwnd ) 210 return NULL; 211 LIST_FOR_EACH_ENTRY( control, &dialog->controls, msi_control, entry ) 212 if( hwnd == control->hwnd ) 213 return control; 214 return NULL; 215 } 216 217 static LPWSTR msi_get_deformatted_field( MSIPACKAGE *package, MSIRECORD *rec, int field ) 218 { 219 LPCWSTR str = MSI_RecordGetString( rec, field ); 220 LPWSTR ret = NULL; 221 222 if (str) 223 deformat_string( package, str, &ret ); 224 return ret; 225 } 226 227 static LPWSTR msi_dialog_dup_property( msi_dialog *dialog, LPCWSTR property, BOOL indirect ) 228 { 229 LPWSTR prop = NULL; 230 231 if (!property) 232 return NULL; 233 234 if (indirect) 235 prop = msi_dup_property( dialog->package->db, property ); 236 237 if (!prop) 238 prop = strdupW( property ); 239 240 return prop; 241 } 242 243 /* 244 * msi_dialog_get_style 245 * 246 * Extract the {\style} string from the front of the text to display and 247 * update the pointer. Only the last style in a list is applied. 248 */ 249 static LPWSTR msi_dialog_get_style( LPCWSTR p, LPCWSTR *rest ) 250 { 251 LPWSTR ret; 252 LPCWSTR q, i, first; 253 DWORD len; 254 255 q = NULL; 256 *rest = p; 257 if( !p ) 258 return NULL; 259 260 while ((first = strchrW( p, '{' )) && (q = strchrW( first + 1, '}' ))) 261 { 262 p = first + 1; 263 if( *p != '\\' && *p != '&' ) 264 return NULL; 265 266 /* little bit of sanity checking to stop us getting confused with RTF */ 267 for( i=++p; i<q; i++ ) 268 if( *i == '}' || *i == '\\' ) 269 return NULL; 270 } 271 272 if (!q) 273 return NULL; 274 275 *rest = ++q; 276 len = q - p; 277 278 ret = msi_alloc( len*sizeof(WCHAR) ); 279 if( !ret ) 280 return ret; 281 memcpy( ret, p, len*sizeof(WCHAR) ); 282 ret[len-1] = 0; 283 return ret; 284 } 285 286 static UINT msi_dialog_add_font( MSIRECORD *rec, LPVOID param ) 287 { 288 msi_dialog *dialog = param; 289 msi_font *font; 290 LPCWSTR face, name; 291 LOGFONTW lf; 292 INT style; 293 HDC hdc; 294 295 /* create a font and add it to the list */ 296 name = MSI_RecordGetString( rec, 1 ); 297 font = msi_alloc( FIELD_OFFSET( msi_font, name[strlenW( name ) + 1] )); 298 strcpyW( font->name, name ); 299 list_add_head( &dialog->fonts, &font->entry ); 300 301 font->color = MSI_RecordGetInteger( rec, 4 ); 302 303 memset( &lf, 0, sizeof lf ); 304 face = MSI_RecordGetString( rec, 2 ); 305 lf.lfHeight = MSI_RecordGetInteger( rec, 3 ); 306 style = MSI_RecordGetInteger( rec, 5 ); 307 if( style & msidbTextStyleStyleBitsBold ) 308 lf.lfWeight = FW_BOLD; 309 if( style & msidbTextStyleStyleBitsItalic ) 310 lf.lfItalic = TRUE; 311 if( style & msidbTextStyleStyleBitsUnderline ) 312 lf.lfUnderline = TRUE; 313 if( style & msidbTextStyleStyleBitsStrike ) 314 lf.lfStrikeOut = TRUE; 315 lstrcpynW( lf.lfFaceName, face, LF_FACESIZE ); 316 317 /* adjust the height */ 318 hdc = GetDC( dialog->hwnd ); 319 if (hdc) 320 { 321 lf.lfHeight = -MulDiv(lf.lfHeight, GetDeviceCaps(hdc, LOGPIXELSY), 72); 322 ReleaseDC( dialog->hwnd, hdc ); 323 } 324 325 font->hfont = CreateFontIndirectW( &lf ); 326 327 TRACE("Adding font style %s\n", debugstr_w(font->name) ); 328 329 return ERROR_SUCCESS; 330 } 331 332 static msi_font *msi_dialog_find_font( msi_dialog *dialog, LPCWSTR name ) 333 { 334 msi_font *font = NULL; 335 336 LIST_FOR_EACH_ENTRY( font, &dialog->fonts, msi_font, entry ) 337 if( !strcmpW( font->name, name ) ) /* FIXME: case sensitive? */ 338 break; 339 340 return font; 341 } 342 343 static UINT msi_dialog_set_font( msi_dialog *dialog, HWND hwnd, LPCWSTR name ) 344 { 345 msi_font *font; 346 347 font = msi_dialog_find_font( dialog, name ); 348 if( font ) 349 SendMessageW( hwnd, WM_SETFONT, (WPARAM) font->hfont, TRUE ); 350 else 351 ERR("No font entry for %s\n", debugstr_w(name)); 352 return ERROR_SUCCESS; 353 } 354 355 static UINT msi_dialog_build_font_list( msi_dialog *dialog ) 356 { 357 static const WCHAR query[] = { 358 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ', 359 '`','T','e','x','t','S','t','y','l','e','`',0}; 360 MSIQUERY *view; 361 UINT r; 362 363 TRACE("dialog %p\n", dialog ); 364 365 r = MSI_OpenQuery( dialog->package->db, &view, query ); 366 if( r != ERROR_SUCCESS ) 367 return r; 368 369 r = MSI_IterateRecords( view, NULL, msi_dialog_add_font, dialog ); 370 msiobj_release( &view->hdr ); 371 return r; 372 } 373 374 static void msi_destroy_control( msi_control *t ) 375 { 376 list_remove( &t->entry ); 377 /* leave dialog->hwnd - destroying parent destroys child windows */ 378 msi_free( t->property ); 379 msi_free( t->value ); 380 if( t->hBitmap ) 381 DeleteObject( t->hBitmap ); 382 if( t->hIcon ) 383 DestroyIcon( t->hIcon ); 384 msi_free( t->tabnext ); 385 msi_free( t->type ); 386 if (t->hDll) 387 FreeLibrary( t->hDll ); 388 msi_free( t ); 389 } 390 391 static msi_control *dialog_create_window( msi_dialog *dialog, MSIRECORD *rec, DWORD exstyle, 392 const WCHAR *szCls, const WCHAR *name, const WCHAR *text, 393 DWORD style, HWND parent ) 394 { 395 DWORD x, y, width, height; 396 LPWSTR font = NULL, title_font = NULL; 397 LPCWSTR title = NULL; 398 msi_control *control; 399 400 style |= WS_CHILD; 401 402 control = msi_alloc( FIELD_OFFSET( msi_control, name[strlenW( name ) + 1] )); 403 if (!control) 404 return NULL; 405 406 strcpyW( control->name, name ); 407 list_add_tail( &dialog->controls, &control->entry ); 408 control->handler = NULL; 409 control->update = NULL; 410 control->property = NULL; 411 control->value = NULL; 412 control->hBitmap = NULL; 413 control->hIcon = NULL; 414 control->hDll = NULL; 415 control->tabnext = strdupW( MSI_RecordGetString( rec, 11) ); 416 control->type = strdupW( MSI_RecordGetString( rec, 3 ) ); 417 control->progress_current = 0; 418 control->progress_max = 100; 419 control->progress_backwards = FALSE; 420 421 x = MSI_RecordGetInteger( rec, 4 ); 422 y = MSI_RecordGetInteger( rec, 5 ); 423 width = MSI_RecordGetInteger( rec, 6 ); 424 height = MSI_RecordGetInteger( rec, 7 ); 425 426 x = msi_dialog_scale_unit( dialog, x ); 427 y = msi_dialog_scale_unit( dialog, y ); 428 width = msi_dialog_scale_unit( dialog, width ); 429 height = msi_dialog_scale_unit( dialog, height ); 430 431 if( text ) 432 { 433 deformat_string( dialog->package, text, &title_font ); 434 font = msi_dialog_get_style( title_font, &title ); 435 } 436 437 control->hwnd = CreateWindowExW( exstyle, szCls, title, style, 438 x, y, width, height, parent, NULL, NULL, NULL ); 439 440 TRACE("Dialog %s control %s hwnd %p\n", 441 debugstr_w(dialog->name), debugstr_w(text), control->hwnd ); 442 443 msi_dialog_set_font( dialog, control->hwnd, 444 font ? font : dialog->default_font ); 445 446 msi_free( title_font ); 447 msi_free( font ); 448 449 return control; 450 } 451 452 static LPWSTR msi_dialog_get_uitext( msi_dialog *dialog, LPCWSTR key ) 453 { 454 MSIRECORD *rec; 455 LPWSTR text; 456 457 static const WCHAR query[] = { 458 's','e','l','e','c','t',' ','*',' ', 459 'f','r','o','m',' ','`','U','I','T','e','x','t','`',' ', 460 'w','h','e','r','e',' ','`','K','e','y','`',' ','=',' ','\'','%','s','\'',0 461 }; 462 463 rec = MSI_QueryGetRecord( dialog->package->db, query, key ); 464 if (!rec) return NULL; 465 text = strdupW( MSI_RecordGetString( rec, 2 ) ); 466 msiobj_release( &rec->hdr ); 467 return text; 468 } 469 470 static MSIRECORD *msi_get_binary_record( MSIDATABASE *db, LPCWSTR name ) 471 { 472 static const WCHAR query[] = { 473 's','e','l','e','c','t',' ','*',' ', 474 'f','r','o','m',' ','B','i','n','a','r','y',' ', 475 'w','h','e','r','e',' ', 476 '`','N','a','m','e','`',' ','=',' ','\'','%','s','\'',0 477 }; 478 479 return MSI_QueryGetRecord( db, query, name ); 480 } 481 482 static HANDLE msi_load_image( MSIDATABASE *db, LPCWSTR name, UINT type, 483 UINT cx, UINT cy, UINT flags ) 484 { 485 MSIRECORD *rec; 486 HANDLE himage = NULL; 487 LPWSTR tmp; 488 UINT r; 489 490 TRACE("%p %s %u %u %08x\n", db, debugstr_w(name), cx, cy, flags); 491 492 if (!(tmp = msi_create_temp_file( db ))) return NULL; 493 494 rec = msi_get_binary_record( db, name ); 495 if( rec ) 496 { 497 r = MSI_RecordStreamToFile( rec, 2, tmp ); 498 if( r == ERROR_SUCCESS ) 499 { 500 himage = LoadImageW( 0, tmp, type, cx, cy, flags ); 501 } 502 msiobj_release( &rec->hdr ); 503 } 504 DeleteFileW( tmp ); 505 506 msi_free( tmp ); 507 return himage; 508 } 509 510 static HICON msi_load_icon( MSIDATABASE *db, LPCWSTR text, UINT attributes ) 511 { 512 DWORD cx = 0, cy = 0, flags; 513 514 flags = LR_LOADFROMFILE | LR_DEFAULTSIZE; 515 if( attributes & msidbControlAttributesFixedSize ) 516 { 517 flags &= ~LR_DEFAULTSIZE; 518 if( attributes & msidbControlAttributesIconSize16 ) 519 { 520 cx += 16; 521 cy += 16; 522 } 523 if( attributes & msidbControlAttributesIconSize32 ) 524 { 525 cx += 32; 526 cy += 32; 527 } 528 /* msidbControlAttributesIconSize48 handled by above logic */ 529 } 530 return msi_load_image( db, text, IMAGE_ICON, cx, cy, flags ); 531 } 532 533 static void msi_dialog_update_controls( msi_dialog *dialog, LPCWSTR property ) 534 { 535 msi_control *control; 536 537 LIST_FOR_EACH_ENTRY( control, &dialog->controls, msi_control, entry ) 538 { 539 if ( control->property && !strcmpW( control->property, property ) && control->update ) 540 control->update( dialog, control ); 541 } 542 } 543 544 static void msi_dialog_update_all_controls( msi_dialog *dialog ) 545 { 546 msi_control *control; 547 548 LIST_FOR_EACH_ENTRY( control, &dialog->controls, msi_control, entry ) 549 { 550 if ( control->property && control->update ) 551 control->update( dialog, control ); 552 } 553 } 554 555 static void msi_dialog_set_property( MSIPACKAGE *package, LPCWSTR property, LPCWSTR value ) 556 { 557 UINT r = msi_set_property( package->db, property, value, -1 ); 558 if (r == ERROR_SUCCESS && !strcmpW( property, szSourceDir )) 559 msi_reset_folders( package, TRUE ); 560 } 561 562 static MSIFEATURE *msi_seltree_feature_from_item( HWND hwnd, HTREEITEM hItem ) 563 { 564 TVITEMW tvi; 565 566 /* get the feature from the item */ 567 memset( &tvi, 0, sizeof tvi ); 568 tvi.hItem = hItem; 569 tvi.mask = TVIF_PARAM | TVIF_HANDLE; 570 SendMessageW( hwnd, TVM_GETITEMW, 0, (LPARAM)&tvi ); 571 return (MSIFEATURE *)tvi.lParam; 572 } 573 574 struct msi_selection_tree_info 575 { 576 msi_dialog *dialog; 577 HWND hwnd; 578 WNDPROC oldproc; 579 HTREEITEM selected; 580 }; 581 582 static MSIFEATURE *msi_seltree_get_selected_feature( msi_control *control ) 583 { 584 struct msi_selection_tree_info *info = GetPropW( control->hwnd, szButtonData ); 585 return msi_seltree_feature_from_item( control->hwnd, info->selected ); 586 } 587 588 static void dialog_handle_event( msi_dialog *dialog, const WCHAR *control, 589 const WCHAR *attribute, MSIRECORD *rec ) 590 { 591 msi_control* ctrl; 592 593 ctrl = msi_dialog_find_control( dialog, control ); 594 if (!ctrl) 595 return; 596 if( !strcmpW( attribute, szText ) ) 597 { 598 const WCHAR *font_text, *text = NULL; 599 WCHAR *font, *text_fmt = NULL; 600 601 font_text = MSI_RecordGetString( rec , 1 ); 602 font = msi_dialog_get_style( font_text, &text ); 603 deformat_string( dialog->package, text, &text_fmt ); 604 if (text_fmt) text = text_fmt; 605 else text = szEmpty; 606 607 SetWindowTextW( ctrl->hwnd, text ); 608 609 msi_free( font ); 610 msi_free( text_fmt ); 611 msi_dialog_check_messages( NULL ); 612 } 613 else if( !strcmpW( attribute, szProgress ) ) 614 { 615 DWORD func, val1, val2, units; 616 617 func = MSI_RecordGetInteger( rec, 1 ); 618 val1 = MSI_RecordGetInteger( rec, 2 ); 619 val2 = MSI_RecordGetInteger( rec, 3 ); 620 621 TRACE("progress: func %u val1 %u val2 %u\n", func, val1, val2); 622 623 units = val1 / 512; 624 switch (func) 625 { 626 case 0: /* init */ 627 SendMessageW( ctrl->hwnd, PBM_SETRANGE, 0, MAKELPARAM(0,100) ); 628 if (val2) 629 { 630 ctrl->progress_max = units ? units : 100; 631 ctrl->progress_current = units; 632 ctrl->progress_backwards = TRUE; 633 SendMessageW( ctrl->hwnd, PBM_SETPOS, 100, 0 ); 634 } 635 else 636 { 637 ctrl->progress_max = units ? units : 100; 638 ctrl->progress_current = 0; 639 ctrl->progress_backwards = FALSE; 640 SendMessageW( ctrl->hwnd, PBM_SETPOS, 0, 0 ); 641 } 642 break; 643 case 1: /* action data increment */ 644 if (val2) dialog->package->action_progress_increment = val1; 645 else dialog->package->action_progress_increment = 0; 646 break; 647 case 2: /* move */ 648 if (ctrl->progress_backwards) 649 { 650 if (units >= ctrl->progress_current) ctrl->progress_current -= units; 651 else ctrl->progress_current = 0; 652 } 653 else 654 { 655 if (ctrl->progress_current + units < ctrl->progress_max) ctrl->progress_current += units; 656 else ctrl->progress_current = ctrl->progress_max; 657 } 658 SendMessageW( ctrl->hwnd, PBM_SETPOS, MulDiv(100, ctrl->progress_current, ctrl->progress_max), 0 ); 659 break; 660 case 3: /* add */ 661 ctrl->progress_max += units; 662 break; 663 default: 664 FIXME("Unknown progress message %u\n", func); 665 break; 666 } 667 } 668 else if ( !strcmpW( attribute, szProperty ) ) 669 { 670 MSIFEATURE *feature = msi_seltree_get_selected_feature( ctrl ); 671 if (feature) msi_dialog_set_property( dialog->package, ctrl->property, feature->Directory ); 672 } 673 else if ( !strcmpW( attribute, szSelectionPath ) ) 674 { 675 BOOL indirect = ctrl->attributes & msidbControlAttributesIndirect; 676 LPWSTR path = msi_dialog_dup_property( dialog, ctrl->property, indirect ); 677 if (!path) return; 678 SetWindowTextW( ctrl->hwnd, path ); 679 msi_free(path); 680 } 681 else 682 { 683 FIXME("Attribute %s not being set\n", debugstr_w(attribute)); 684 return; 685 } 686 } 687 688 static void event_subscribe( msi_dialog *dialog, const WCHAR *event, const WCHAR *control, const WCHAR *attribute ) 689 { 690 struct subscriber *sub; 691 692 TRACE("dialog %s event %s control %s attribute %s\n", debugstr_w(dialog->name), debugstr_w(event), 693 debugstr_w(control), debugstr_w(attribute)); 694 695 LIST_FOR_EACH_ENTRY( sub, &dialog->package->subscriptions, struct subscriber, entry ) 696 { 697 if (sub->dialog == dialog && 698 !strcmpiW( sub->event, event ) && 699 !strcmpiW( sub->control, control ) && 700 !strcmpiW( sub->attribute, attribute )) 701 { 702 TRACE("already subscribed\n"); 703 return; 704 }; 705 } 706 if (!(sub = msi_alloc( sizeof(*sub) ))) return; 707 sub->dialog = dialog; 708 sub->event = strdupW( event ); 709 sub->control = strdupW( control ); 710 sub->attribute = strdupW( attribute ); 711 list_add_tail( &dialog->package->subscriptions, &sub->entry ); 712 } 713 714 struct dialog_control 715 { 716 msi_dialog *dialog; 717 const WCHAR *control; 718 }; 719 720 static UINT map_event( MSIRECORD *row, void *param ) 721 { 722 struct dialog_control *dc = param; 723 const WCHAR *event = MSI_RecordGetString( row, 3 ); 724 const WCHAR *attribute = MSI_RecordGetString( row, 4 ); 725 726 event_subscribe( dc->dialog, event, dc->control, attribute ); 727 return ERROR_SUCCESS; 728 } 729 730 static void dialog_map_events( msi_dialog *dialog, const WCHAR *control ) 731 { 732 static const WCHAR queryW[] = 733 {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ', 734 '`','E','v','e','n','t','M','a','p','p','i','n','g','`',' ', 735 'W','H','E','R','E',' ','`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',' ', 736 'A','N','D',' ','`','C','o','n','t','r','o','l','_','`',' ','=',' ','\'','%','s','\'',0}; 737 MSIQUERY *view; 738 struct dialog_control dialog_control = 739 { 740 dialog, 741 control 742 }; 743 744 if (!MSI_OpenQuery( dialog->package->db, &view, queryW, dialog->name, control )) 745 { 746 MSI_IterateRecords( view, NULL, map_event, &dialog_control ); 747 msiobj_release( &view->hdr ); 748 } 749 } 750 751 /* everything except radio buttons */ 752 static msi_control *msi_dialog_add_control( msi_dialog *dialog, 753 MSIRECORD *rec, LPCWSTR szCls, DWORD style ) 754 { 755 DWORD attributes; 756 const WCHAR *text = NULL, *name, *control_type; 757 DWORD exstyle = 0; 758 759 name = MSI_RecordGetString( rec, 2 ); 760 control_type = MSI_RecordGetString( rec, 3 ); 761 attributes = MSI_RecordGetInteger( rec, 8 ); 762 if (strcmpW( control_type, szScrollableText )) text = MSI_RecordGetString( rec, 10 ); 763 764 TRACE("%s, %s, %08x, %s, %08x\n", debugstr_w(szCls), debugstr_w(name), 765 attributes, debugstr_w(text), style); 766 767 if( attributes & msidbControlAttributesVisible ) 768 style |= WS_VISIBLE; 769 if( ~attributes & msidbControlAttributesEnabled ) 770 style |= WS_DISABLED; 771 if( attributes & msidbControlAttributesSunken ) 772 exstyle |= WS_EX_CLIENTEDGE; 773 774 dialog_map_events( dialog, name ); 775 776 return dialog_create_window( dialog, rec, exstyle, szCls, name, text, style, dialog->hwnd ); 777 } 778 779 struct msi_text_info 780 { 781 msi_font *font; 782 WNDPROC oldproc; 783 DWORD attributes; 784 }; 785 786 /* 787 * we don't erase our own background, 788 * so we have to make sure that the parent window redraws first 789 */ 790 static void msi_text_on_settext( HWND hWnd ) 791 { 792 HWND hParent; 793 RECT rc; 794 795 hParent = GetParent( hWnd ); 796 GetWindowRect( hWnd, &rc ); 797 MapWindowPoints( NULL, hParent, (LPPOINT) &rc, 2 ); 798 InvalidateRect( hParent, &rc, TRUE ); 799 } 800 801 static LRESULT WINAPI 802 MSIText_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) 803 { 804 struct msi_text_info *info; 805 LRESULT r = 0; 806 807 TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam); 808 809 info = GetPropW(hWnd, szButtonData); 810 811 if( msg == WM_CTLCOLORSTATIC && 812 ( info->attributes & msidbControlAttributesTransparent ) ) 813 { 814 SetBkMode( (HDC)wParam, TRANSPARENT ); 815 return (LRESULT) GetStockObject(NULL_BRUSH); 816 } 817 818 r = CallWindowProcW(info->oldproc, hWnd, msg, wParam, lParam); 819 if ( info->font ) 820 SetTextColor( (HDC)wParam, info->font->color ); 821 822 switch( msg ) 823 { 824 case WM_SETTEXT: 825 msi_text_on_settext( hWnd ); 826 break; 827 case WM_NCDESTROY: 828 msi_free( info ); 829 RemovePropW( hWnd, szButtonData ); 830 break; 831 } 832 833 return r; 834 } 835 836 static UINT msi_dialog_text_control( msi_dialog *dialog, MSIRECORD *rec ) 837 { 838 msi_control *control; 839 struct msi_text_info *info; 840 LPCWSTR text, ptr, prop, control_name; 841 LPWSTR font_name; 842 843 TRACE("%p %p\n", dialog, rec); 844 845 control = msi_dialog_add_control( dialog, rec, szStatic, SS_LEFT | WS_GROUP ); 846 if( !control ) 847 return ERROR_FUNCTION_FAILED; 848 849 info = msi_alloc( sizeof *info ); 850 if( !info ) 851 return ERROR_SUCCESS; 852 853 control_name = MSI_RecordGetString( rec, 2 ); 854 control->attributes = MSI_RecordGetInteger( rec, 8 ); 855 prop = MSI_RecordGetString( rec, 9 ); 856 control->property = msi_dialog_dup_property( dialog, prop, FALSE ); 857 858 text = MSI_RecordGetString( rec, 10 ); 859 font_name = msi_dialog_get_style( text, &ptr ); 860 info->font = ( font_name ) ? msi_dialog_find_font( dialog, font_name ) : NULL; 861 msi_free( font_name ); 862 863 info->attributes = MSI_RecordGetInteger( rec, 8 ); 864 if( info->attributes & msidbControlAttributesTransparent ) 865 SetWindowLongPtrW( control->hwnd, GWL_EXSTYLE, WS_EX_TRANSPARENT ); 866 867 info->oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC, 868 (LONG_PTR)MSIText_WndProc ); 869 SetPropW( control->hwnd, szButtonData, info ); 870 871 event_subscribe( dialog, szSelectionPath, control_name, szSelectionPath ); 872 return ERROR_SUCCESS; 873 } 874 875 /* strip any leading text style label from text field */ 876 static WCHAR *msi_get_binary_name( MSIPACKAGE *package, MSIRECORD *rec ) 877 { 878 WCHAR *p, *text; 879 880 text = msi_get_deformatted_field( package, rec, 10 ); 881 if (!text) 882 return NULL; 883 884 p = text; 885 while (*p && *p != '{') p++; 886 if (!*p++) return text; 887 888 while (*p && *p != '}') p++; 889 if (!*p++) return text; 890 891 p = strdupW( p ); 892 msi_free( text ); 893 return p; 894 } 895 896 static UINT msi_dialog_set_property_event( msi_dialog *dialog, LPCWSTR event, LPCWSTR arg ) 897 { 898 static const WCHAR szNullArg[] = {'{','}',0}; 899 LPWSTR p, prop, arg_fmt = NULL; 900 UINT len; 901 902 len = strlenW( event ); 903 prop = msi_alloc( len * sizeof(WCHAR) ); 904 strcpyW( prop, &event[1] ); 905 p = strchrW( prop, ']' ); 906 if (p && (p[1] == 0 || p[1] == ' ')) 907 { 908 *p = 0; 909 if (strcmpW( szNullArg, arg )) 910 deformat_string( dialog->package, arg, &arg_fmt ); 911 msi_dialog_set_property( dialog->package, prop, arg_fmt ); 912 msi_dialog_update_controls( dialog, prop ); 913 msi_free( arg_fmt ); 914 } 915 else ERR("Badly formatted property string - what happens?\n"); 916 msi_free( prop ); 917 return ERROR_SUCCESS; 918 } 919 920 static UINT msi_dialog_send_event( msi_dialog *dialog, LPCWSTR event, LPCWSTR arg ) 921 { 922 LPWSTR event_fmt = NULL, arg_fmt = NULL; 923 924 TRACE("Sending control event %s %s\n", debugstr_w(event), debugstr_w(arg)); 925 926 deformat_string( dialog->package, event, &event_fmt ); 927 deformat_string( dialog->package, arg, &arg_fmt ); 928 929 dialog->event_handler( dialog, event_fmt, arg_fmt ); 930 931 msi_free( event_fmt ); 932 msi_free( arg_fmt ); 933 934 return ERROR_SUCCESS; 935 } 936 937 static UINT msi_dialog_control_event( MSIRECORD *rec, LPVOID param ) 938 { 939 msi_dialog *dialog = param; 940 LPCWSTR condition, event, arg; 941 UINT r; 942 943 condition = MSI_RecordGetString( rec, 5 ); 944 r = MSI_EvaluateConditionW( dialog->package, condition ); 945 if (r == MSICONDITION_TRUE || r == MSICONDITION_NONE) 946 { 947 event = MSI_RecordGetString( rec, 3 ); 948 arg = MSI_RecordGetString( rec, 4 ); 949 if (event[0] == '[') 950 msi_dialog_set_property_event( dialog, event, arg ); 951 else 952 msi_dialog_send_event( dialog, event, arg ); 953 } 954 return ERROR_SUCCESS; 955 } 956 957 static UINT msi_dialog_button_handler( msi_dialog *dialog, msi_control *control, WPARAM param ) 958 { 959 static const WCHAR query[] = { 960 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ', 961 'C','o','n','t','r','o','l','E','v','e','n','t',' ','W','H','E','R','E',' ', 962 '`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',' ','A','N','D',' ', 963 '`','C','o','n','t','r','o','l','_','`',' ','=',' ','\'','%','s','\'',' ', 964 'O','R','D','E','R',' ','B','Y',' ','`','O','r','d','e','r','i','n','g','`',0}; 965 MSIQUERY *view; 966 UINT r; 967 968 if (HIWORD(param) != BN_CLICKED) 969 return ERROR_SUCCESS; 970 971 r = MSI_OpenQuery( dialog->package->db, &view, query, dialog->name, control->name ); 972 if (r != ERROR_SUCCESS) 973 { 974 ERR("query failed\n"); 975 return ERROR_SUCCESS; 976 } 977 r = MSI_IterateRecords( view, 0, msi_dialog_control_event, dialog ); 978 msiobj_release( &view->hdr ); 979 980 /* dialog control events must be processed last regardless of ordering */ 981 if (dialog->pending_event) 982 { 983 r = dialog->pending_event( dialog, dialog->pending_argument ); 984 985 msi_free( dialog->pending_argument ); 986 dialog->pending_event = NULL; 987 dialog->pending_argument = NULL; 988 } 989 return r; 990 } 991 992 static UINT msi_dialog_button_control( msi_dialog *dialog, MSIRECORD *rec ) 993 { 994 msi_control *control; 995 UINT attributes, style; 996 997 TRACE("%p %p\n", dialog, rec); 998 999 style = WS_TABSTOP; 1000 attributes = MSI_RecordGetInteger( rec, 8 ); 1001 if( attributes & msidbControlAttributesIcon ) 1002 style |= BS_ICON; 1003 1004 control = msi_dialog_add_control( dialog, rec, szButton, style ); 1005 if( !control ) 1006 return ERROR_FUNCTION_FAILED; 1007 1008 control->handler = msi_dialog_button_handler; 1009 1010 if (attributes & msidbControlAttributesIcon) 1011 { 1012 /* set the icon */ 1013 LPWSTR name = msi_get_binary_name( dialog->package, rec ); 1014 control->hIcon = msi_load_icon( dialog->package->db, name, attributes ); 1015 if (control->hIcon) 1016 { 1017 SendMessageW( control->hwnd, BM_SETIMAGE, IMAGE_ICON, (LPARAM) control->hIcon ); 1018 } 1019 else 1020 ERR("Failed to load icon %s\n", debugstr_w(name)); 1021 msi_free( name ); 1022 } 1023 1024 return ERROR_SUCCESS; 1025 } 1026 1027 static LPWSTR msi_get_checkbox_value( msi_dialog *dialog, LPCWSTR prop ) 1028 { 1029 static const WCHAR query[] = { 1030 'S','E','L','E','C','T',' ','*',' ', 1031 'F','R','O','M',' ','`','C','h','e','c','k','B','o','x','`',' ', 1032 'W','H','E','R','E',' ', 1033 '`','P','r','o','p','e','r','t','y','`',' ','=',' ', 1034 '\'','%','s','\'',0 1035 }; 1036 MSIRECORD *rec = NULL; 1037 LPWSTR ret = NULL; 1038 1039 /* find if there is a value associated with the checkbox */ 1040 rec = MSI_QueryGetRecord( dialog->package->db, query, prop ); 1041 if (!rec) 1042 return ret; 1043 1044 ret = msi_get_deformatted_field( dialog->package, rec, 2 ); 1045 if( ret && !ret[0] ) 1046 { 1047 msi_free( ret ); 1048 ret = NULL; 1049 } 1050 msiobj_release( &rec->hdr ); 1051 if (ret) 1052 return ret; 1053 1054 ret = msi_dup_property( dialog->package->db, prop ); 1055 if( ret && !ret[0] ) 1056 { 1057 msi_free( ret ); 1058 ret = NULL; 1059 } 1060 1061 return ret; 1062 } 1063 1064 static UINT msi_dialog_get_checkbox_state( msi_dialog *dialog, msi_control *control ) 1065 { 1066 WCHAR state[2] = {0}; 1067 DWORD sz = 2; 1068 1069 msi_get_property( dialog->package->db, control->property, state, &sz ); 1070 return state[0] ? 1 : 0; 1071 } 1072 1073 static void msi_dialog_set_checkbox_state( msi_dialog *dialog, msi_control *control, UINT state ) 1074 { 1075 static const WCHAR szState[] = {'1',0}; 1076 LPCWSTR val; 1077 1078 /* if uncheck then the property is set to NULL */ 1079 if (!state) 1080 { 1081 msi_dialog_set_property( dialog->package, control->property, NULL ); 1082 return; 1083 } 1084 1085 /* check for a custom state */ 1086 if (control->value && control->value[0]) 1087 val = control->value; 1088 else 1089 val = szState; 1090 1091 msi_dialog_set_property( dialog->package, control->property, val ); 1092 } 1093 1094 static void msi_dialog_checkbox_sync_state( msi_dialog *dialog, msi_control *control ) 1095 { 1096 UINT state = msi_dialog_get_checkbox_state( dialog, control ); 1097 SendMessageW( control->hwnd, BM_SETCHECK, state ? BST_CHECKED : BST_UNCHECKED, 0 ); 1098 } 1099 1100 static UINT msi_dialog_checkbox_handler( msi_dialog *dialog, msi_control *control, WPARAM param ) 1101 { 1102 UINT state; 1103 1104 if (HIWORD(param) != BN_CLICKED) 1105 return ERROR_SUCCESS; 1106 1107 TRACE("clicked checkbox %s, set %s\n", debugstr_w(control->name), debugstr_w(control->property)); 1108 1109 state = msi_dialog_get_checkbox_state( dialog, control ); 1110 state = state ? 0 : 1; 1111 msi_dialog_set_checkbox_state( dialog, control, state ); 1112 msi_dialog_checkbox_sync_state( dialog, control ); 1113 1114 return msi_dialog_button_handler( dialog, control, param ); 1115 } 1116 1117 static UINT msi_dialog_checkbox_control( msi_dialog *dialog, MSIRECORD *rec ) 1118 { 1119 msi_control *control; 1120 LPCWSTR prop; 1121 1122 TRACE("%p %p\n", dialog, rec); 1123 1124 control = msi_dialog_add_control( dialog, rec, szButton, BS_CHECKBOX | BS_MULTILINE | WS_TABSTOP ); 1125 control->handler = msi_dialog_checkbox_handler; 1126 control->update = msi_dialog_checkbox_sync_state; 1127 prop = MSI_RecordGetString( rec, 9 ); 1128 if (prop) 1129 { 1130 control->property = strdupW( prop ); 1131 control->value = msi_get_checkbox_value( dialog, prop ); 1132 TRACE("control %s value %s\n", debugstr_w(control->property), debugstr_w(control->value)); 1133 } 1134 msi_dialog_checkbox_sync_state( dialog, control ); 1135 return ERROR_SUCCESS; 1136 } 1137 1138 static UINT msi_dialog_line_control( msi_dialog *dialog, MSIRECORD *rec ) 1139 { 1140 DWORD attributes; 1141 LPCWSTR name; 1142 DWORD style, exstyle = 0; 1143 DWORD x, y, width, height; 1144 msi_control *control; 1145 1146 TRACE("%p %p\n", dialog, rec); 1147 1148 style = WS_CHILD | SS_ETCHEDHORZ | SS_SUNKEN; 1149 1150 name = MSI_RecordGetString( rec, 2 ); 1151 attributes = MSI_RecordGetInteger( rec, 8 ); 1152 1153 if( attributes & msidbControlAttributesVisible ) 1154 style |= WS_VISIBLE; 1155 if( ~attributes & msidbControlAttributesEnabled ) 1156 style |= WS_DISABLED; 1157 if( attributes & msidbControlAttributesSunken ) 1158 exstyle |= WS_EX_CLIENTEDGE; 1159 1160 dialog_map_events( dialog, name ); 1161 1162 control = msi_alloc( FIELD_OFFSET(msi_control, name[strlenW( name ) + 1] )); 1163 if (!control) 1164 return ERROR_OUTOFMEMORY; 1165 1166 strcpyW( control->name, name ); 1167 list_add_head( &dialog->controls, &control->entry ); 1168 control->handler = NULL; 1169 control->property = NULL; 1170 control->value = NULL; 1171 control->hBitmap = NULL; 1172 control->hIcon = NULL; 1173 control->hDll = NULL; 1174 control->tabnext = strdupW( MSI_RecordGetString( rec, 11) ); 1175 control->type = strdupW( MSI_RecordGetString( rec, 3 ) ); 1176 control->progress_current = 0; 1177 control->progress_max = 100; 1178 control->progress_backwards = FALSE; 1179 1180 x = MSI_RecordGetInteger( rec, 4 ); 1181 y = MSI_RecordGetInteger( rec, 5 ); 1182 width = MSI_RecordGetInteger( rec, 6 ); 1183 1184 x = msi_dialog_scale_unit( dialog, x ); 1185 y = msi_dialog_scale_unit( dialog, y ); 1186 width = msi_dialog_scale_unit( dialog, width ); 1187 height = 2; /* line is exactly 2 units in height */ 1188 1189 control->hwnd = CreateWindowExW( exstyle, szStatic, NULL, style, 1190 x, y, width, height, dialog->hwnd, NULL, NULL, NULL ); 1191 1192 TRACE("Dialog %s control %s hwnd %p\n", 1193 debugstr_w(dialog->name), debugstr_w(name), control->hwnd ); 1194 1195 return ERROR_SUCCESS; 1196 } 1197 1198 /******************** Scroll Text ********************************************/ 1199 1200 struct msi_scrolltext_info 1201 { 1202 msi_dialog *dialog; 1203 msi_control *control; 1204 WNDPROC oldproc; 1205 }; 1206 1207 static LRESULT WINAPI 1208 MSIScrollText_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) 1209 { 1210 struct msi_scrolltext_info *info; 1211 HRESULT r; 1212 1213 TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam); 1214 1215 info = GetPropW( hWnd, szButtonData ); 1216 1217 r = CallWindowProcW( info->oldproc, hWnd, msg, wParam, lParam ); 1218 1219 switch( msg ) 1220 { 1221 case WM_GETDLGCODE: 1222 return DLGC_WANTARROWS; 1223 case WM_NCDESTROY: 1224 msi_free( info ); 1225 RemovePropW( hWnd, szButtonData ); 1226 break; 1227 case WM_PAINT: 1228 /* native MSI sets a wait cursor here */ 1229 msi_dialog_button_handler( info->dialog, info->control, BN_CLICKED ); 1230 break; 1231 } 1232 return r; 1233 } 1234 1235 struct msi_streamin_info 1236 { 1237 LPSTR string; 1238 DWORD offset; 1239 DWORD length; 1240 }; 1241 1242 static DWORD CALLBACK 1243 msi_richedit_stream_in( DWORD_PTR arg, LPBYTE buffer, LONG count, LONG *pcb ) 1244 { 1245 struct msi_streamin_info *info = (struct msi_streamin_info*) arg; 1246 1247 if( (count + info->offset) > info->length ) 1248 count = info->length - info->offset; 1249 memcpy( buffer, &info->string[ info->offset ], count ); 1250 *pcb = count; 1251 info->offset += count; 1252 1253 TRACE("%d/%d\n", info->offset, info->length); 1254 1255 return 0; 1256 } 1257 1258 static void msi_scrolltext_add_text( msi_control *control, LPCWSTR text ) 1259 { 1260 struct msi_streamin_info info; 1261 EDITSTREAM es; 1262 1263 info.string = strdupWtoA( text ); 1264 info.offset = 0; 1265 info.length = lstrlenA( info.string ) + 1; 1266 1267 es.dwCookie = (DWORD_PTR) &info; 1268 es.dwError = 0; 1269 es.pfnCallback = msi_richedit_stream_in; 1270 1271 SendMessageW( control->hwnd, EM_STREAMIN, SF_RTF, (LPARAM) &es ); 1272 1273 msi_free( info.string ); 1274 } 1275 1276 static UINT msi_dialog_scrolltext_control( msi_dialog *dialog, MSIRECORD *rec ) 1277 { 1278 static const WCHAR szRichEdit20W[] = {'R','i','c','h','E','d','i','t','2','0','W',0}; 1279 struct msi_scrolltext_info *info; 1280 msi_control *control; 1281 HMODULE hRichedit; 1282 LPCWSTR text; 1283 DWORD style; 1284 1285 info = msi_alloc( sizeof *info ); 1286 if (!info) 1287 return ERROR_FUNCTION_FAILED; 1288 1289 hRichedit = LoadLibraryA("riched20"); 1290 1291 style = WS_BORDER | ES_MULTILINE | WS_VSCROLL | 1292 ES_READONLY | ES_AUTOVSCROLL | WS_TABSTOP; 1293 control = msi_dialog_add_control( dialog, rec, szRichEdit20W, style ); 1294 if (!control) 1295 { 1296 FreeLibrary( hRichedit ); 1297 msi_free( info ); 1298 return ERROR_FUNCTION_FAILED; 1299 } 1300 1301 control->hDll = hRichedit; 1302 1303 info->dialog = dialog; 1304 info->control = control; 1305 1306 /* subclass the static control */ 1307 info->oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC, 1308 (LONG_PTR)MSIScrollText_WndProc ); 1309 SetPropW( control->hwnd, szButtonData, info ); 1310 1311 /* add the text into the richedit */ 1312 text = MSI_RecordGetString( rec, 10 ); 1313 if (text) 1314 msi_scrolltext_add_text( control, text ); 1315 1316 return ERROR_SUCCESS; 1317 } 1318 1319 static HBITMAP msi_load_picture( MSIDATABASE *db, LPCWSTR name, 1320 INT cx, INT cy, DWORD flags ) 1321 { 1322 HBITMAP hOleBitmap = 0, hBitmap = 0, hOldSrcBitmap, hOldDestBitmap; 1323 MSIRECORD *rec = NULL; 1324 IStream *stm = NULL; 1325 IPicture *pic = NULL; 1326 HDC srcdc, destdc; 1327 BITMAP bm; 1328 UINT r; 1329 1330 rec = msi_get_binary_record( db, name ); 1331 if( !rec ) 1332 goto end; 1333 1334 r = MSI_RecordGetIStream( rec, 2, &stm ); 1335 msiobj_release( &rec->hdr ); 1336 if( r != ERROR_SUCCESS ) 1337 goto end; 1338 1339 r = OleLoadPicture( stm, 0, TRUE, &IID_IPicture, (LPVOID*) &pic ); 1340 IStream_Release( stm ); 1341 if( FAILED( r ) ) 1342 { 1343 ERR("failed to load picture\n"); 1344 goto end; 1345 } 1346 1347 r = IPicture_get_Handle( pic, (OLE_HANDLE*) &hOleBitmap ); 1348 if( FAILED( r ) ) 1349 { 1350 ERR("failed to get bitmap handle\n"); 1351 goto end; 1352 } 1353 1354 /* make the bitmap the desired size */ 1355 r = GetObjectW( hOleBitmap, sizeof bm, &bm ); 1356 if (r != sizeof bm ) 1357 { 1358 ERR("failed to get bitmap size\n"); 1359 goto end; 1360 } 1361 1362 if (flags & LR_DEFAULTSIZE) 1363 { 1364 cx = bm.bmWidth; 1365 cy = bm.bmHeight; 1366 } 1367 1368 srcdc = CreateCompatibleDC( NULL ); 1369 hOldSrcBitmap = SelectObject( srcdc, hOleBitmap ); 1370 destdc = CreateCompatibleDC( NULL ); 1371 hBitmap = CreateCompatibleBitmap( srcdc, cx, cy ); 1372 hOldDestBitmap = SelectObject( destdc, hBitmap ); 1373 StretchBlt( destdc, 0, 0, cx, cy, 1374 srcdc, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY); 1375 SelectObject( srcdc, hOldSrcBitmap ); 1376 SelectObject( destdc, hOldDestBitmap ); 1377 DeleteDC( srcdc ); 1378 DeleteDC( destdc ); 1379 1380 end: 1381 if ( pic ) 1382 IPicture_Release( pic ); 1383 return hBitmap; 1384 } 1385 1386 static UINT msi_dialog_bitmap_control( msi_dialog *dialog, MSIRECORD *rec ) 1387 { 1388 UINT cx, cy, flags, style, attributes; 1389 msi_control *control; 1390 LPWSTR name; 1391 1392 flags = LR_LOADFROMFILE; 1393 style = SS_BITMAP | SS_LEFT | WS_GROUP; 1394 1395 attributes = MSI_RecordGetInteger( rec, 8 ); 1396 if( attributes & msidbControlAttributesFixedSize ) 1397 { 1398 flags |= LR_DEFAULTSIZE; 1399 style |= SS_CENTERIMAGE; 1400 } 1401 1402 control = msi_dialog_add_control( dialog, rec, szStatic, style ); 1403 cx = MSI_RecordGetInteger( rec, 6 ); 1404 cy = MSI_RecordGetInteger( rec, 7 ); 1405 cx = msi_dialog_scale_unit( dialog, cx ); 1406 cy = msi_dialog_scale_unit( dialog, cy ); 1407 1408 name = msi_get_binary_name( dialog->package, rec ); 1409 control->hBitmap = msi_load_picture( dialog->package->db, name, cx, cy, flags ); 1410 if( control->hBitmap ) 1411 SendMessageW( control->hwnd, STM_SETIMAGE, 1412 IMAGE_BITMAP, (LPARAM) control->hBitmap ); 1413 else 1414 ERR("Failed to load bitmap %s\n", debugstr_w(name)); 1415 1416 msi_free( name ); 1417 1418 return ERROR_SUCCESS; 1419 } 1420 1421 static UINT msi_dialog_icon_control( msi_dialog *dialog, MSIRECORD *rec ) 1422 { 1423 msi_control *control; 1424 DWORD attributes; 1425 LPWSTR name; 1426 1427 TRACE("\n"); 1428 1429 control = msi_dialog_add_control( dialog, rec, szStatic, 1430 SS_ICON | SS_CENTERIMAGE | WS_GROUP ); 1431 1432 attributes = MSI_RecordGetInteger( rec, 8 ); 1433 name = msi_get_binary_name( dialog->package, rec ); 1434 control->hIcon = msi_load_icon( dialog->package->db, name, attributes ); 1435 if( control->hIcon ) 1436 SendMessageW( control->hwnd, STM_SETICON, (WPARAM) control->hIcon, 0 ); 1437 else 1438 ERR("Failed to load bitmap %s\n", debugstr_w(name)); 1439 msi_free( name ); 1440 return ERROR_SUCCESS; 1441 } 1442 1443 /******************** Combo Box ***************************************/ 1444 1445 struct msi_combobox_info 1446 { 1447 msi_dialog *dialog; 1448 HWND hwnd; 1449 WNDPROC oldproc; 1450 DWORD num_items; 1451 DWORD addpos_items; 1452 LPWSTR *items; 1453 }; 1454 1455 static LRESULT WINAPI MSIComboBox_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) 1456 { 1457 struct msi_combobox_info *info; 1458 LRESULT r; 1459 DWORD j; 1460 1461 TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam); 1462 1463 info = GetPropW( hWnd, szButtonData ); 1464 if (!info) 1465 return 0; 1466 1467 r = CallWindowProcW( info->oldproc, hWnd, msg, wParam, lParam ); 1468 1469 switch (msg) 1470 { 1471 case WM_NCDESTROY: 1472 for (j = 0; j < info->num_items; j++) 1473 msi_free( info->items[j] ); 1474 msi_free( info->items ); 1475 msi_free( info ); 1476 RemovePropW( hWnd, szButtonData ); 1477 break; 1478 } 1479 1480 return r; 1481 } 1482 1483 static UINT msi_combobox_add_item( MSIRECORD *rec, LPVOID param ) 1484 { 1485 struct msi_combobox_info *info = param; 1486 LPCWSTR value, text; 1487 int pos; 1488 1489 value = MSI_RecordGetString( rec, 3 ); 1490 text = MSI_RecordGetString( rec, 4 ); 1491 1492 info->items[info->addpos_items] = strdupW( value ); 1493 1494 pos = SendMessageW( info->hwnd, CB_ADDSTRING, 0, (LPARAM)text ); 1495 SendMessageW( info->hwnd, CB_SETITEMDATA, pos, (LPARAM)info->items[info->addpos_items] ); 1496 info->addpos_items++; 1497 1498 return ERROR_SUCCESS; 1499 } 1500 1501 static UINT msi_combobox_add_items( struct msi_combobox_info *info, LPCWSTR property ) 1502 { 1503 static const WCHAR query[] = { 1504 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ', 1505 '`','C','o','m','b','o','B','o','x','`',' ','W','H','E','R','E',' ', 1506 '`','P','r','o','p','e','r','t','y','`',' ','=',' ','\'','%','s','\'',' ', 1507 'O','R','D','E','R',' ','B','Y',' ','`','O','r','d','e','r','`',0}; 1508 MSIQUERY *view; 1509 DWORD count; 1510 UINT r; 1511 1512 r = MSI_OpenQuery( info->dialog->package->db, &view, query, property ); 1513 if (r != ERROR_SUCCESS) 1514 return r; 1515 1516 /* just get the number of records */ 1517 count = 0; 1518 r = MSI_IterateRecords( view, &count, NULL, NULL ); 1519 if (r != ERROR_SUCCESS) 1520 { 1521 msiobj_release( &view->hdr ); 1522 return r; 1523 } 1524 info->num_items = count; 1525 info->items = msi_alloc( sizeof(*info->items) * count ); 1526 1527 r = MSI_IterateRecords( view, NULL, msi_combobox_add_item, info ); 1528 msiobj_release( &view->hdr ); 1529 return r; 1530 } 1531 1532 static UINT msi_dialog_set_control_condition( MSIRECORD *rec, LPVOID param ) 1533 { 1534 static const WCHAR szHide[] = {'H','i','d','e',0}; 1535 static const WCHAR szShow[] = {'S','h','o','w',0}; 1536 static const WCHAR szDisable[] = {'D','i','s','a','b','l','e',0}; 1537 static const WCHAR szEnable[] = {'E','n','a','b','l','e',0}; 1538 static const WCHAR szDefault[] = {'D','e','f','a','u','l','t',0}; 1539 msi_dialog *dialog = param; 1540 msi_control *control; 1541 LPCWSTR name, action, condition; 1542 UINT r; 1543 1544 name = MSI_RecordGetString( rec, 2 ); 1545 action = MSI_RecordGetString( rec, 3 ); 1546 condition = MSI_RecordGetString( rec, 4 ); 1547 r = MSI_EvaluateConditionW( dialog->package, condition ); 1548 control = msi_dialog_find_control( dialog, name ); 1549 if (r == MSICONDITION_TRUE && control) 1550 { 1551 TRACE("%s control %s\n", debugstr_w(action), debugstr_w(name)); 1552 1553 /* FIXME: case sensitive? */ 1554 if (!strcmpW( action, szHide )) 1555 ShowWindow(control->hwnd, SW_HIDE); 1556 else if (!strcmpW( action, szShow )) 1557 ShowWindow(control->hwnd, SW_SHOW); 1558 else if (!strcmpW( action, szDisable )) 1559 EnableWindow(control->hwnd, FALSE); 1560 else if (!strcmpW( action, szEnable )) 1561 EnableWindow(control->hwnd, TRUE); 1562 else if (!strcmpW( action, szDefault )) 1563 SetFocus(control->hwnd); 1564 else 1565 FIXME("Unhandled action %s\n", debugstr_w(action)); 1566 } 1567 return ERROR_SUCCESS; 1568 } 1569 1570 static UINT msi_dialog_evaluate_control_conditions( msi_dialog *dialog ) 1571 { 1572 static const WCHAR query[] = { 1573 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ', 1574 'C','o','n','t','r','o','l','C','o','n','d','i','t','i','o','n',' ', 1575 'W','H','E','R','E',' ','`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',0}; 1576 UINT r; 1577 MSIQUERY *view; 1578 MSIPACKAGE *package = dialog->package; 1579 1580 TRACE("%p %s\n", dialog, debugstr_w(dialog->name)); 1581 1582 /* query the Control table for all the elements of the control */ 1583 r = MSI_OpenQuery( package->db, &view, query, dialog->name ); 1584 if (r != ERROR_SUCCESS) 1585 return ERROR_SUCCESS; 1586 1587 r = MSI_IterateRecords( view, 0, msi_dialog_set_control_condition, dialog ); 1588 msiobj_release( &view->hdr ); 1589 return r; 1590 } 1591 1592 static UINT msi_dialog_combobox_handler( msi_dialog *dialog, msi_control *control, WPARAM param ) 1593 { 1594 struct msi_combobox_info *info; 1595 int index; 1596 LPWSTR value; 1597 1598 if (HIWORD(param) != CBN_SELCHANGE && HIWORD(param) != CBN_EDITCHANGE) 1599 return ERROR_SUCCESS; 1600 1601 info = GetPropW( control->hwnd, szButtonData ); 1602 index = SendMessageW( control->hwnd, CB_GETCURSEL, 0, 0 ); 1603 if (index == CB_ERR) 1604 value = msi_get_window_text( control->hwnd ); 1605 else 1606 value = (LPWSTR) SendMessageW( control->hwnd, CB_GETITEMDATA, index, 0 ); 1607 1608 msi_dialog_set_property( info->dialog->package, control->property, value ); 1609 msi_dialog_evaluate_control_conditions( info->dialog ); 1610 1611 if (index == CB_ERR) 1612 msi_free( value ); 1613 1614 return ERROR_SUCCESS; 1615 } 1616 1617 static void msi_dialog_combobox_update( msi_dialog *dialog, msi_control *control ) 1618 { 1619 struct msi_combobox_info *info; 1620 LPWSTR value, tmp; 1621 DWORD j; 1622 1623 info = GetPropW( control->hwnd, szButtonData ); 1624 1625 value = msi_dup_property( dialog->package->db, control->property ); 1626 if (!value) 1627 { 1628 SendMessageW( control->hwnd, CB_SETCURSEL, -1, 0 ); 1629 return; 1630 } 1631 1632 for (j = 0; j < info->num_items; j++) 1633 { 1634 tmp = (LPWSTR) SendMessageW( control->hwnd, CB_GETITEMDATA, j, 0 ); 1635 if (!strcmpW( value, tmp )) 1636 break; 1637 } 1638 1639 if (j < info->num_items) 1640 { 1641 SendMessageW( control->hwnd, CB_SETCURSEL, j, 0 ); 1642 } 1643 else 1644 { 1645 SendMessageW( control->hwnd, CB_SETCURSEL, -1, 0 ); 1646 SetWindowTextW( control->hwnd, value ); 1647 } 1648 1649 msi_free(value); 1650 } 1651 1652 static UINT msi_dialog_combo_control( msi_dialog *dialog, MSIRECORD *rec ) 1653 { 1654 struct msi_combobox_info *info; 1655 msi_control *control; 1656 DWORD attributes, style; 1657 LPCWSTR prop; 1658 1659 info = msi_alloc( sizeof *info ); 1660 if (!info) 1661 return ERROR_FUNCTION_FAILED; 1662 1663 style = CBS_AUTOHSCROLL | WS_TABSTOP | WS_GROUP | WS_CHILD; 1664 attributes = MSI_RecordGetInteger( rec, 8 ); 1665 if ( ~attributes & msidbControlAttributesSorted) 1666 style |= CBS_SORT; 1667 if ( attributes & msidbControlAttributesComboList) 1668 style |= CBS_DROPDOWNLIST; 1669 else 1670 style |= CBS_DROPDOWN; 1671 1672 control = msi_dialog_add_control( dialog, rec, WC_COMBOBOXW, style ); 1673 if (!control) 1674 { 1675 msi_free( info ); 1676 return ERROR_FUNCTION_FAILED; 1677 } 1678 1679 control->handler = msi_dialog_combobox_handler; 1680 control->update = msi_dialog_combobox_update; 1681 1682 prop = MSI_RecordGetString( rec, 9 ); 1683 control->property = msi_dialog_dup_property( dialog, prop, FALSE ); 1684 1685 /* subclass */ 1686 info->dialog = dialog; 1687 info->hwnd = control->hwnd; 1688 info->items = NULL; 1689 info->addpos_items = 0; 1690 info->oldproc = (WNDPROC)SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC, 1691 (LONG_PTR)MSIComboBox_WndProc ); 1692 SetPropW( control->hwnd, szButtonData, info ); 1693 1694 if (control->property) 1695 msi_combobox_add_items( info, control->property ); 1696 1697 msi_dialog_combobox_update( dialog, control ); 1698 1699 return ERROR_SUCCESS; 1700 } 1701 1702 static UINT msi_dialog_edit_handler( msi_dialog *dialog, msi_control *control, WPARAM param ) 1703 { 1704 LPWSTR buf; 1705 1706 if (HIWORD(param) != EN_CHANGE) 1707 return ERROR_SUCCESS; 1708 1709 TRACE("edit %s contents changed, set %s\n", debugstr_w(control->name), debugstr_w(control->property)); 1710 1711 buf = msi_get_window_text( control->hwnd ); 1712 msi_dialog_set_property( dialog->package, control->property, buf ); 1713 msi_free( buf ); 1714 1715 return ERROR_SUCCESS; 1716 } 1717 1718 /* length of 2^32 + 1 */ 1719 #define MAX_NUM_DIGITS 11 1720 1721 static UINT msi_dialog_edit_control( msi_dialog *dialog, MSIRECORD *rec ) 1722 { 1723 msi_control *control; 1724 LPCWSTR prop, text; 1725 LPWSTR val, begin, end; 1726 WCHAR num[MAX_NUM_DIGITS]; 1727 DWORD limit; 1728 1729 control = msi_dialog_add_control( dialog, rec, szEdit, 1730 WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL ); 1731 control->handler = msi_dialog_edit_handler; 1732 1733 text = MSI_RecordGetString( rec, 10 ); 1734 if ( text ) 1735 { 1736 begin = strchrW( text, '{' ); 1737 end = strchrW( text, '}' ); 1738 1739 if ( begin && end && end > begin && 1740 begin[0] >= '0' && begin[0] <= '9' && 1741 end - begin < MAX_NUM_DIGITS) 1742 { 1743 lstrcpynW( num, begin + 1, end - begin ); 1744 limit = atolW( num ); 1745 1746 SendMessageW( control->hwnd, EM_SETLIMITTEXT, limit, 0 ); 1747 } 1748 } 1749 1750 prop = MSI_RecordGetString( rec, 9 ); 1751 if( prop ) 1752 control->property = strdupW( prop ); 1753 1754 val = msi_dup_property( dialog->package->db, control->property ); 1755 SetWindowTextW( control->hwnd, val ); 1756 msi_free( val ); 1757 return ERROR_SUCCESS; 1758 } 1759 1760 /******************** Masked Edit ********************************************/ 1761 1762 #define MASK_MAX_GROUPS 20 1763 1764 struct msi_mask_group 1765 { 1766 UINT len; 1767 UINT ofs; 1768 WCHAR type; 1769 HWND hwnd; 1770 }; 1771 1772 struct msi_maskedit_info 1773 { 1774 msi_dialog *dialog; 1775 WNDPROC oldproc; 1776 HWND hwnd; 1777 LPWSTR prop; 1778 UINT num_chars; 1779 UINT num_groups; 1780 struct msi_mask_group group[MASK_MAX_GROUPS]; 1781 }; 1782 1783 static BOOL msi_mask_editable( WCHAR type ) 1784 { 1785 switch (type) 1786 { 1787 case '%': 1788 case '#': 1789 case '&': 1790 case '`': 1791 case '?': 1792 case '^': 1793 return TRUE; 1794 } 1795 return FALSE; 1796 } 1797 1798 static void msi_mask_control_change( struct msi_maskedit_info *info ) 1799 { 1800 LPWSTR val; 1801 UINT i, n, r; 1802 1803 val = msi_alloc( (info->num_chars+1)*sizeof(WCHAR) ); 1804 for( i=0, n=0; i<info->num_groups; i++ ) 1805 { 1806 if (info->group[i].len == ~0u) 1807 { 1808 UINT len = SendMessageW( info->group[i].hwnd, WM_GETTEXTLENGTH, 0, 0 ); 1809 val = msi_realloc( val, (len + 1) * sizeof(WCHAR) ); 1810 GetWindowTextW( info->group[i].hwnd, val, len + 1 ); 1811 } 1812 else 1813 { 1814 if (info->group[i].len + n > info->num_chars) 1815 { 1816 ERR("can't fit control %d text into template\n",i); 1817 break; 1818 } 1819 if (!msi_mask_editable(info->group[i].type)) 1820 { 1821 for(r=0; r<info->group[i].len; r++) 1822 val[n+r] = info->group[i].type; 1823 val[n+r] = 0; 1824 } 1825 else 1826 { 1827 r = GetWindowTextW( info->group[i].hwnd, &val[n], info->group[i].len+1 ); 1828 if( r != info->group[i].len ) 1829 break; 1830 } 1831 n += r; 1832 } 1833 } 1834 1835 TRACE("%d/%d controls were good\n", i, info->num_groups); 1836 1837 if( i == info->num_groups ) 1838 { 1839 TRACE("Set property %s to %s\n", debugstr_w(info->prop), debugstr_w(val)); 1840 msi_dialog_set_property( info->dialog->package, info->prop, val ); 1841 msi_dialog_evaluate_control_conditions( info->dialog ); 1842 } 1843 msi_free( val ); 1844 } 1845 1846 /* now move to the next control if necessary */ 1847 static VOID msi_mask_next_control( struct msi_maskedit_info *info, HWND hWnd ) 1848 { 1849 HWND hWndNext; 1850 UINT len, i; 1851 1852 for( i=0; i<info->num_groups; i++ ) 1853 if( info->group[i].hwnd == hWnd ) 1854 break; 1855 1856 /* don't move from the last control */ 1857 if( i >= (info->num_groups-1) ) 1858 return; 1859 1860 len = SendMessageW( hWnd, WM_GETTEXTLENGTH, 0, 0 ); 1861 if( len < info->group[i].len ) 1862 return; 1863 1864 hWndNext = GetNextDlgTabItem( GetParent( hWnd ), hWnd, FALSE ); 1865 SetFocus( hWndNext ); 1866 } 1867 1868 static LRESULT WINAPI 1869 MSIMaskedEdit_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) 1870 { 1871 struct msi_maskedit_info *info; 1872 HRESULT r; 1873 1874 TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam); 1875 1876 info = GetPropW(hWnd, szButtonData); 1877 1878 r = CallWindowProcW(info->oldproc, hWnd, msg, wParam, lParam); 1879 1880 switch( msg ) 1881 { 1882 case WM_COMMAND: 1883 if (HIWORD(wParam) == EN_CHANGE) 1884 { 1885 msi_mask_control_change( info ); 1886 msi_mask_next_control( info, (HWND) lParam ); 1887 } 1888 break; 1889 case WM_NCDESTROY: 1890 msi_free( info->prop ); 1891 msi_free( info ); 1892 RemovePropW( hWnd, szButtonData ); 1893 break; 1894 } 1895 1896 return r; 1897 } 1898 1899 /* fish the various bits of the property out and put them in the control */ 1900 static void 1901 msi_maskedit_set_text( struct msi_maskedit_info *info, LPCWSTR text ) 1902 { 1903 LPCWSTR p; 1904 UINT i; 1905 1906 p = text; 1907 for( i = 0; i < info->num_groups; i++ ) 1908 { 1909 if( info->group[i].len < strlenW( p ) ) 1910 { 1911 LPWSTR chunk = strdupW( p ); 1912 chunk[ info->group[i].len ] = 0; 1913 SetWindowTextW( info->group[i].hwnd, chunk ); 1914 msi_free( chunk ); 1915 } 1916 else 1917 { 1918 SetWindowTextW( info->group[i].hwnd, p ); 1919 break; 1920 } 1921 p += info->group[i].len; 1922 } 1923 } 1924 1925 static struct msi_maskedit_info * msi_dialog_parse_groups( LPCWSTR mask ) 1926 { 1927 struct msi_maskedit_info *info; 1928 int i = 0, n = 0, total = 0; 1929 LPCWSTR p; 1930 1931 TRACE("masked control, template %s\n", debugstr_w(mask)); 1932 1933 if( !mask ) 1934 return NULL; 1935 1936 info = msi_alloc_zero( sizeof *info ); 1937 if( !info ) 1938 return info; 1939 1940 p = strchrW(mask, '<'); 1941 if( p ) 1942 p++; 1943 else 1944 p = mask; 1945 1946 for( i=0; i<MASK_MAX_GROUPS; i++ ) 1947 { 1948 /* stop at the end of the string */ 1949 if( p[0] == 0 || p[0] == '>' ) 1950 { 1951 if (!total) 1952 { 1953 /* create a group for the empty mask */ 1954 info->group[0].type = '&'; 1955 info->group[0].len = ~0u; 1956 i = 1; 1957 } 1958 break; 1959 } 1960 1961 /* count the number of the same identifier */ 1962 for( n=0; p[n] == p[0]; n++ ) 1963 ; 1964 info->group[i].ofs = total; 1965 info->group[i].type = p[0]; 1966 if( p[n] == '=' ) 1967 { 1968 n++; 1969 total++; /* an extra not part of the group */ 1970 } 1971 info->group[i].len = n; 1972 total += n; 1973 p += n; 1974 } 1975 1976 TRACE("%d characters in %d groups\n", total, i ); 1977 if( i == MASK_MAX_GROUPS ) 1978 ERR("too many groups in PIDTemplate %s\n", debugstr_w(mask)); 1979 1980 info->num_chars = total; 1981 info->num_groups = i; 1982 1983 return info; 1984 } 1985 1986 static void 1987 msi_maskedit_create_children( struct msi_maskedit_info *info, LPCWSTR font ) 1988 { 1989 DWORD width, height, style, wx, ww; 1990 RECT rect; 1991 HWND hwnd; 1992 UINT i; 1993 1994 style = WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL; 1995 1996 GetClientRect( info->hwnd, &rect ); 1997 1998 width = rect.right - rect.left; 1999 height = rect.bottom - rect.top; 2000 2001 for( i = 0; i < info->num_groups; i++ ) 2002 { 2003 if (!msi_mask_editable( info->group[i].type )) 2004 continue; 2005 if (info->num_chars) 2006 { 2007 wx = (info->group[i].ofs * width) / info->num_chars; 2008 ww = (info->group[i].len * width) / info->num_chars; 2009 } 2010 else 2011 { 2012 wx = 0; 2013 ww = width; 2014 } 2015 hwnd = CreateWindowW( szEdit, NULL, style, wx, 0, ww, height, 2016 info->hwnd, NULL, NULL, NULL ); 2017 if( !hwnd ) 2018 { 2019 ERR("failed to create mask edit sub window\n"); 2020 break; 2021 } 2022 2023 SendMessageW( hwnd, EM_LIMITTEXT, info->group[i].len, 0 ); 2024 2025 msi_dialog_set_font( info->dialog, hwnd, 2026 font?font:info->dialog->default_font ); 2027 info->group[i].hwnd = hwnd; 2028 } 2029 } 2030 2031 /* 2032 * office 2003 uses "73931<````=````=````=````=`````>@@@@@" 2033 * delphi 7 uses "<????-??????-??????-????>" and "<???-???>" 2034 * filemaker pro 7 uses "<^^^^=^^^^=^^^^=^^^^=^^^^=^^^^=^^^^^>" 2035 */ 2036 static UINT msi_dialog_maskedit_control( msi_dialog *dialog, MSIRECORD *rec ) 2037 { 2038 LPWSTR font_mask, val = NULL, font; 2039 struct msi_maskedit_info *info = NULL; 2040 UINT ret = ERROR_SUCCESS; 2041 msi_control *control; 2042 LPCWSTR prop, mask; 2043 2044 TRACE("\n"); 2045 2046 font_mask = msi_get_deformatted_field( dialog->package, rec, 10 ); 2047 font = msi_dialog_get_style( font_mask, &mask ); 2048 if( !mask ) 2049 { 2050 WARN("mask template is empty\n"); 2051 goto end; 2052 } 2053 2054 info = msi_dialog_parse_groups( mask ); 2055 if( !info ) 2056 { 2057 ERR("template %s is invalid\n", debugstr_w(mask)); 2058 goto end; 2059 } 2060 2061 info->dialog = dialog; 2062 2063 control = msi_dialog_add_control( dialog, rec, szStatic, 2064 SS_OWNERDRAW | WS_GROUP | WS_VISIBLE ); 2065 if( !control ) 2066 { 2067 ERR("Failed to create maskedit container\n"); 2068 ret = ERROR_FUNCTION_FAILED; 2069 goto end; 2070 } 2071 SetWindowLongPtrW( control->hwnd, GWL_EXSTYLE, WS_EX_CONTROLPARENT ); 2072 2073 info->hwnd = control->hwnd; 2074 2075 /* subclass the static control */ 2076 info->oldproc = (WNDPROC) SetWindowLongPtrW( info->hwnd, GWLP_WNDPROC, 2077 (LONG_PTR)MSIMaskedEdit_WndProc ); 2078 SetPropW( control->hwnd, szButtonData, info ); 2079 2080 prop = MSI_RecordGetString( rec, 9 ); 2081 if( prop ) 2082 info->prop = strdupW( prop ); 2083 2084 msi_maskedit_create_children( info, font ); 2085 2086 if( prop ) 2087 { 2088 val = msi_dup_property( dialog->package->db, prop ); 2089 if( val ) 2090 { 2091 msi_maskedit_set_text( info, val ); 2092 msi_free( val ); 2093 } 2094 } 2095 2096 end: 2097 if( ret != ERROR_SUCCESS ) 2098 msi_free( info ); 2099 msi_free( font_mask ); 2100 msi_free( font ); 2101 return ret; 2102 } 2103 2104 /******************** Progress Bar *****************************************/ 2105 2106 static UINT msi_dialog_progress_bar( msi_dialog *dialog, MSIRECORD *rec ) 2107 { 2108 msi_control *control; 2109 DWORD attributes, style; 2110 2111 style = WS_VISIBLE; 2112 attributes = MSI_RecordGetInteger( rec, 8 ); 2113 if( !(attributes & msidbControlAttributesProgress95) ) 2114 style |= PBS_SMOOTH; 2115 2116 control = msi_dialog_add_control( dialog, rec, PROGRESS_CLASSW, style ); 2117 if( !control ) 2118 return ERROR_FUNCTION_FAILED; 2119 2120 event_subscribe( dialog, szSetProgress, control->name, szProgress ); 2121 return ERROR_SUCCESS; 2122 } 2123 2124 /******************** Path Edit ********************************************/ 2125 2126 struct msi_pathedit_info 2127 { 2128 msi_dialog *dialog; 2129 msi_control *control; 2130 WNDPROC oldproc; 2131 }; 2132 2133 static void msi_dialog_update_pathedit( msi_dialog *dialog, msi_control *control ) 2134 { 2135 LPWSTR prop, path; 2136 BOOL indirect; 2137 2138 if (!control && !(control = msi_dialog_find_control_by_type( dialog, szPathEdit ))) 2139 return; 2140 2141 indirect = control->attributes & msidbControlAttributesIndirect; 2142 prop = msi_dialog_dup_property( dialog, control->property, indirect ); 2143 path = msi_dialog_dup_property( dialog, prop, TRUE ); 2144 2145 SetWindowTextW( control->hwnd, path ); 2146 SendMessageW( control->hwnd, EM_SETSEL, 0, -1 ); 2147 2148 msi_free( path ); 2149 msi_free( prop ); 2150 } 2151 2152 /* FIXME: test when this should fail */ 2153 static BOOL msi_dialog_verify_path( LPWSTR path ) 2154 { 2155 if ( !path[0] ) 2156 return FALSE; 2157 2158 if ( PathIsRelativeW( path ) ) 2159 return FALSE; 2160 2161 return TRUE; 2162 } 2163 2164 /* returns TRUE if the path is valid, FALSE otherwise */ 2165 static BOOL msi_dialog_onkillfocus( msi_dialog *dialog, msi_control *control ) 2166 { 2167 LPWSTR buf, prop; 2168 BOOL indirect; 2169 BOOL valid; 2170 2171 indirect = control->attributes & msidbControlAttributesIndirect; 2172 prop = msi_dialog_dup_property( dialog, control->property, indirect ); 2173 2174 buf = msi_get_window_text( control->hwnd ); 2175 2176 if ( !msi_dialog_verify_path( buf ) ) 2177 { 2178 /* FIXME: display an error message box */ 2179 ERR("Invalid path %s\n", debugstr_w( buf )); 2180 valid = FALSE; 2181 SetFocus( control->hwnd ); 2182 } 2183 else 2184 { 2185 valid = TRUE; 2186 msi_dialog_set_property( dialog->package, prop, buf ); 2187 } 2188 2189 msi_dialog_update_pathedit( dialog, control ); 2190 2191 TRACE("edit %s contents changed, set %s\n", debugstr_w(control->name), 2192 debugstr_w(prop)); 2193 2194 msi_free( buf ); 2195 msi_free( prop ); 2196 2197 return valid; 2198 } 2199 2200 static LRESULT WINAPI MSIPathEdit_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) 2201 { 2202 struct msi_pathedit_info *info = GetPropW(hWnd, szButtonData); 2203 LRESULT r = 0; 2204 2205 TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam); 2206 2207 if ( msg == WM_KILLFOCUS ) 2208 { 2209 /* if the path is invalid, don't handle this message */ 2210 if ( !msi_dialog_onkillfocus( info->dialog, info->control ) ) 2211 return 0; 2212 } 2213 2214 r = CallWindowProcW(info->oldproc, hWnd, msg, wParam, lParam); 2215 2216 if ( msg == WM_NCDESTROY ) 2217 { 2218 msi_free( info ); 2219 RemovePropW( hWnd, szButtonData ); 2220 } 2221 2222 return r; 2223 } 2224 2225 static UINT msi_dialog_pathedit_control( msi_dialog *dialog, MSIRECORD *rec ) 2226 { 2227 struct msi_pathedit_info *info; 2228 msi_control *control; 2229 LPCWSTR prop; 2230 2231 info = msi_alloc( sizeof *info ); 2232 if (!info) 2233 return ERROR_FUNCTION_FAILED; 2234 2235 control = msi_dialog_add_control( dialog, rec, szEdit, 2236 WS_BORDER | WS_TABSTOP ); 2237 control->attributes = MSI_RecordGetInteger( rec, 8 ); 2238 prop = MSI_RecordGetString( rec, 9 ); 2239 control->property = msi_dialog_dup_property( dialog, prop, FALSE ); 2240 control->update = msi_dialog_update_pathedit; 2241 2242 info->dialog = dialog; 2243 info->control = control; 2244 info->oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC, 2245 (LONG_PTR)MSIPathEdit_WndProc ); 2246 SetPropW( control->hwnd, szButtonData, info ); 2247 2248 msi_dialog_update_pathedit( dialog, control ); 2249 2250 return ERROR_SUCCESS; 2251 } 2252 2253 static UINT msi_dialog_radiogroup_handler( msi_dialog *dialog, msi_control *control, WPARAM param ) 2254 { 2255 if (HIWORD(param) != BN_CLICKED) 2256 return ERROR_SUCCESS; 2257 2258 TRACE("clicked radio button %s, set %s\n", debugstr_w(control->name), debugstr_w(control->property)); 2259 2260 msi_dialog_set_property( dialog->package, control->property, control->name ); 2261 2262 return msi_dialog_button_handler( dialog, control, param ); 2263 } 2264 2265 /* radio buttons are a bit different from normal controls */ 2266 static UINT msi_dialog_create_radiobutton( MSIRECORD *rec, LPVOID param ) 2267 { 2268 radio_button_group_descr *group = param; 2269 msi_dialog *dialog = group->dialog; 2270 msi_control *control; 2271 LPCWSTR prop, text, name; 2272 DWORD style = WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTORADIOBUTTON | BS_MULTILINE; 2273 2274 name = MSI_RecordGetString( rec, 3 ); 2275 text = MSI_RecordGetString( rec, 8 ); 2276 2277 control = dialog_create_window( dialog, rec, 0, szButton, name, text, style, 2278 group->parent->hwnd ); 2279 if (!control) 2280 return ERROR_FUNCTION_FAILED; 2281 control->handler = msi_dialog_radiogroup_handler; 2282 2283 if (group->propval && !strcmpW( control->name, group->propval )) 2284 SendMessageW(control->hwnd, BM_SETCHECK, BST_CHECKED, 0); 2285 2286 prop = MSI_RecordGetString( rec, 1 ); 2287 if( prop ) 2288 control->property = strdupW( prop ); 2289 2290 return ERROR_SUCCESS; 2291 } 2292 2293 static BOOL CALLBACK msi_radioground_child_enum( HWND hWnd, LPARAM lParam ) 2294 { 2295 EnableWindow( hWnd, lParam ); 2296 return TRUE; 2297 } 2298 2299 static LRESULT WINAPI MSIRadioGroup_WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam ) 2300 { 2301 WNDPROC oldproc = (WNDPROC)GetPropW( hWnd, szButtonData ); 2302 LRESULT r; 2303 2304 TRACE("hWnd %p msg %04x wParam 0x%08lx lParam 0x%08lx\n", hWnd, msg, wParam, lParam); 2305 2306 if (msg == WM_COMMAND) /* Forward notifications to dialog */ 2307 SendMessageW( GetParent( hWnd ), msg, wParam, lParam ); 2308 2309 r = CallWindowProcW( oldproc, hWnd, msg, wParam, lParam ); 2310 2311 /* make sure the radio buttons show as disabled if the parent is disabled */ 2312 if (msg == WM_ENABLE) 2313 EnumChildWindows( hWnd, msi_radioground_child_enum, wParam ); 2314 2315 return r; 2316 } 2317 2318 static UINT msi_dialog_radiogroup_control( msi_dialog *dialog, MSIRECORD *rec ) 2319 { 2320 static const WCHAR query[] = { 2321 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ', 2322 'R','a','d','i','o','B','u','t','t','o','n',' ','W','H','E','R','E',' ', 2323 '`','P','r','o','p','e','r','t','y','`',' ','=',' ','\'','%','s','\'',0}; 2324 UINT r; 2325 LPCWSTR prop; 2326 msi_control *control; 2327 MSIQUERY *view; 2328 radio_button_group_descr group; 2329 MSIPACKAGE *package = dialog->package; 2330 WNDPROC oldproc; 2331 DWORD attr, style = WS_GROUP; 2332 2333 prop = MSI_RecordGetString( rec, 9 ); 2334 2335 TRACE("%p %p %s\n", dialog, rec, debugstr_w( prop )); 2336 2337 attr = MSI_RecordGetInteger( rec, 8 ); 2338 if (attr & msidbControlAttributesVisible) 2339 style |= WS_VISIBLE; 2340 if (~attr & msidbControlAttributesEnabled) 2341 style |= WS_DISABLED; 2342 if (attr & msidbControlAttributesHasBorder) 2343 style |= BS_GROUPBOX; 2344 else 2345 style |= BS_OWNERDRAW; 2346 2347 /* Create parent group box to hold radio buttons */ 2348 control = msi_dialog_add_control( dialog, rec, szButton, style ); 2349 if( !control ) 2350 return ERROR_FUNCTION_FAILED; 2351 2352 oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC, 2353 (LONG_PTR)MSIRadioGroup_WndProc ); 2354 SetPropW(control->hwnd, szButtonData, oldproc); 2355 SetWindowLongPtrW( control->hwnd, GWL_EXSTYLE, WS_EX_CONTROLPARENT ); 2356 2357 if( prop ) 2358 control->property = strdupW( prop ); 2359 2360 /* query the Radio Button table for all control in this group */ 2361 r = MSI_OpenQuery( package->db, &view, query, prop ); 2362 if( r != ERROR_SUCCESS ) 2363 { 2364 ERR("query failed for dialog %s radio group %s\n", 2365 debugstr_w(dialog->name), debugstr_w(prop)); 2366 return ERROR_INVALID_PARAMETER; 2367 } 2368 2369 group.dialog = dialog; 2370 group.parent = control; 2371 group.propval = msi_dup_property( dialog->package->db, control->property ); 2372 2373 r = MSI_IterateRecords( view, 0, msi_dialog_create_radiobutton, &group ); 2374 msiobj_release( &view->hdr ); 2375 msi_free( group.propval ); 2376 return r; 2377 } 2378 2379 static void 2380 msi_seltree_sync_item_state( HWND hwnd, MSIFEATURE *feature, HTREEITEM hItem ) 2381 { 2382 TVITEMW tvi; 2383 DWORD index = feature->ActionRequest; 2384 2385 TRACE("Feature %s -> %d %d %d\n", debugstr_w(feature->Title), 2386 feature->Installed, feature->Action, feature->ActionRequest); 2387 2388 if (index == INSTALLSTATE_UNKNOWN) 2389 index = INSTALLSTATE_ABSENT; 2390 2391 tvi.mask = TVIF_STATE; 2392 tvi.hItem = hItem; 2393 tvi.state = INDEXTOSTATEIMAGEMASK( index ); 2394 tvi.stateMask = TVIS_STATEIMAGEMASK; 2395 2396 SendMessageW( hwnd, TVM_SETITEMW, 0, (LPARAM) &tvi ); 2397 } 2398 2399 static UINT 2400 msi_seltree_popup_menu( HWND hwnd, INT x, INT y ) 2401 { 2402 HMENU hMenu; 2403 INT r; 2404 2405 /* create a menu to display */ 2406 hMenu = CreatePopupMenu(); 2407 2408 /* FIXME: load strings from resources */ 2409 AppendMenuA( hMenu, MF_ENABLED, INSTALLSTATE_LOCAL, "Install feature locally"); 2410 AppendMenuA( hMenu, MF_ENABLED, USER_INSTALLSTATE_ALL, "Install entire feature"); 2411 AppendMenuA( hMenu, MF_ENABLED, INSTALLSTATE_ADVERTISED, "Install on demand"); 2412 AppendMenuA( hMenu, MF_ENABLED, INSTALLSTATE_ABSENT, "Don't install"); 2413 r = TrackPopupMenu( hMenu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RETURNCMD, 2414 x, y, 0, hwnd, NULL ); 2415 DestroyMenu( hMenu ); 2416 return r; 2417 } 2418 2419 static void 2420 msi_seltree_update_feature_installstate( HWND hwnd, HTREEITEM hItem, 2421 MSIPACKAGE *package, MSIFEATURE *feature, INSTALLSTATE state ) 2422 { 2423 feature->ActionRequest = state; 2424 msi_seltree_sync_item_state( hwnd, feature, hItem ); 2425 ACTION_UpdateComponentStates( package, feature ); 2426 } 2427 2428 static void 2429 msi_seltree_update_siblings_and_children_installstate( HWND hwnd, HTREEITEM curr, 2430 MSIPACKAGE *package, INSTALLSTATE state) 2431 { 2432 /* update all siblings */ 2433 do 2434 { 2435 MSIFEATURE *feature; 2436 HTREEITEM child; 2437 2438 feature = msi_seltree_feature_from_item( hwnd, curr ); 2439 msi_seltree_update_feature_installstate( hwnd, curr, package, feature, state ); 2440 2441 /* update this sibling's children */ 2442 child = (HTREEITEM)SendMessageW( hwnd, TVM_GETNEXTITEM, (WPARAM)TVGN_CHILD, (LPARAM)curr ); 2443 if (child) 2444 msi_seltree_update_siblings_and_children_installstate( hwnd, child, 2445 package, state ); 2446 } 2447 while ((curr = (HTREEITEM)SendMessageW( hwnd, TVM_GETNEXTITEM, (WPARAM)TVGN_NEXT, (LPARAM)curr ))); 2448 } 2449 2450 static LRESULT 2451 msi_seltree_menu( HWND hwnd, HTREEITEM hItem ) 2452 { 2453 struct msi_selection_tree_info *info; 2454 MSIFEATURE *feature; 2455 MSIPACKAGE *package; 2456 union { 2457 RECT rc; 2458 POINT pt[2]; 2459 HTREEITEM hItem; 2460 } u; 2461 UINT r; 2462 2463 info = GetPropW(hwnd, szButtonData); 2464 package = info->dialog->package; 2465 2466 feature = msi_seltree_feature_from_item( hwnd, hItem ); 2467 if (!feature) 2468 { 2469 ERR("item %p feature was NULL\n", hItem); 2470 return 0; 2471 } 2472 2473 /* get the item's rectangle to put the menu just below it */ 2474 u.hItem = hItem; 2475 SendMessageW( hwnd, TVM_GETITEMRECT, 0, (LPARAM) &u.rc ); 2476 MapWindowPoints( hwnd, NULL, u.pt, 2 ); 2477 2478 r = msi_seltree_popup_menu( hwnd, u.rc.left, u.rc.top ); 2479 2480 switch (r) 2481 { 2482 case USER_INSTALLSTATE_ALL: 2483 r = INSTALLSTATE_LOCAL; 2484 /* fall-through */ 2485 case INSTALLSTATE_ADVERTISED: 2486 case INSTALLSTATE_ABSENT: 2487 { 2488 HTREEITEM child; 2489 child = (HTREEITEM)SendMessageW( hwnd, TVM_GETNEXTITEM, (WPARAM)TVGN_CHILD, (LPARAM)hItem ); 2490 if (child) 2491 msi_seltree_update_siblings_and_children_installstate( hwnd, child, package, r ); 2492 } 2493 /* fall-through */ 2494 case INSTALLSTATE_LOCAL: 2495 msi_seltree_update_feature_installstate( hwnd, hItem, package, feature, r ); 2496 break; 2497 } 2498 2499 return 0; 2500 } 2501 2502 static LRESULT WINAPI 2503 MSISelectionTree_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) 2504 { 2505 struct msi_selection_tree_info *info; 2506 TVHITTESTINFO tvhti; 2507 HRESULT r; 2508 2509 TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam); 2510 2511 info = GetPropW(hWnd, szButtonData); 2512 2513 switch( msg ) 2514 { 2515 case WM_LBUTTONDOWN: 2516 tvhti.pt.x = (short)LOWORD( lParam ); 2517 tvhti.pt.y = (short)HIWORD( lParam ); 2518 tvhti.flags = 0; 2519 tvhti.hItem = 0; 2520 CallWindowProcW(info->oldproc, hWnd, TVM_HITTEST, 0, (LPARAM) &tvhti ); 2521 if (tvhti.flags & TVHT_ONITEMSTATEICON) 2522 return msi_seltree_menu( hWnd, tvhti.hItem ); 2523 break; 2524 } 2525 r = CallWindowProcW(info->oldproc, hWnd, msg, wParam, lParam); 2526 2527 switch( msg ) 2528 { 2529 case WM_NCDESTROY: 2530 msi_free( info ); 2531 RemovePropW( hWnd, szButtonData ); 2532 break; 2533 } 2534 return r; 2535 } 2536 2537 static void 2538 msi_seltree_add_child_features( MSIPACKAGE *package, HWND hwnd, 2539 LPCWSTR parent, HTREEITEM hParent ) 2540 { 2541 struct msi_selection_tree_info *info = GetPropW( hwnd, szButtonData ); 2542 MSIFEATURE *feature; 2543 TVINSERTSTRUCTW tvis; 2544 HTREEITEM hitem, hfirst = NULL; 2545 2546 LIST_FOR_EACH_ENTRY( feature, &package->features, MSIFEATURE, entry ) 2547 { 2548 if ( parent && feature->Feature_Parent && strcmpW( parent, feature->Feature_Parent )) 2549 continue; 2550 else if ( parent && !feature->Feature_Parent ) 2551 continue; 2552 else if ( !parent && feature->Feature_Parent ) 2553 continue; 2554 2555 if ( !feature->Title ) 2556 continue; 2557 2558 if ( !feature->Display ) 2559 continue; 2560 2561 memset( &tvis, 0, sizeof tvis ); 2562 tvis.hParent = hParent; 2563 tvis.hInsertAfter = TVI_LAST; 2564 tvis.u.item.mask = TVIF_TEXT | TVIF_PARAM; 2565 tvis.u.item.pszText = feature->Title; 2566 tvis.u.item.lParam = (LPARAM) feature; 2567 2568 hitem = (HTREEITEM) SendMessageW( hwnd, TVM_INSERTITEMW, 0, (LPARAM) &tvis ); 2569 if (!hitem) 2570 continue; 2571 2572 if (!hfirst) 2573 hfirst = hitem; 2574 2575 msi_seltree_sync_item_state( hwnd, feature, hitem ); 2576 msi_seltree_add_child_features( package, hwnd, 2577 feature->Feature, hitem ); 2578 2579 /* the node is expanded if Display is odd */ 2580 if ( feature->Display % 2 != 0 ) 2581 SendMessageW( hwnd, TVM_EXPAND, TVE_EXPAND, (LPARAM) hitem ); 2582 } 2583 2584 /* select the first item */ 2585 SendMessageW( hwnd, TVM_SELECTITEM, TVGN_CARET | TVGN_DROPHILITE, (LPARAM) hfirst ); 2586 info->selected = hfirst; 2587 } 2588 2589 static void msi_seltree_create_imagelist( HWND hwnd ) 2590 { 2591 const int bm_width = 32, bm_height = 16, bm_count = 3; 2592 const int bm_resource = 0x1001; 2593 HIMAGELIST himl; 2594 int i; 2595 HBITMAP hbmp; 2596 2597 himl = ImageList_Create( bm_width, bm_height, FALSE, 4, 0 ); 2598 if (!himl) 2599 { 2600 ERR("failed to create image list\n"); 2601 return; 2602 } 2603 2604 for (i=0; i<bm_count; i++) 2605 { 2606 hbmp = LoadBitmapW( msi_hInstance, MAKEINTRESOURCEW(i+bm_resource) ); 2607 if (!hbmp) 2608 { 2609 ERR("failed to load bitmap %d\n", i); 2610 break; 2611 } 2612 2613 /* 2614 * Add a dummy bitmap at offset zero because the treeview 2615 * can't use it as a state mask (zero means no user state). 2616 */ 2617 if (!i) 2618 ImageList_Add( himl, hbmp, NULL ); 2619 2620 ImageList_Add( himl, hbmp, NULL ); 2621 } 2622 2623 SendMessageW( hwnd, TVM_SETIMAGELIST, TVSIL_STATE, (LPARAM)himl ); 2624 } 2625 2626 static UINT msi_dialog_seltree_handler( msi_dialog *dialog, 2627 msi_control *control, WPARAM param ) 2628 { 2629 struct msi_selection_tree_info *info = GetPropW( control->hwnd, szButtonData ); 2630 LPNMTREEVIEWW tv = (LPNMTREEVIEWW)param; 2631 MSIRECORD *row, *rec; 2632 MSIFOLDER *folder; 2633 MSIFEATURE *feature; 2634 LPCWSTR dir, title = NULL; 2635 UINT r = ERROR_SUCCESS; 2636 2637 static const WCHAR select[] = { 2638 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ', 2639 '`','F','e','a','t','u','r','e','`',' ','W','H','E','R','E',' ', 2640 '`','T','i','t','l','e','`',' ','=',' ','\'','%','s','\'',0 2641 }; 2642 2643 if (tv->hdr.code != TVN_SELCHANGINGW) 2644 return ERROR_SUCCESS; 2645 2646 info->selected = tv->itemNew.hItem; 2647 2648 if (!(tv->itemNew.mask & TVIF_TEXT)) 2649 { 2650 feature = msi_seltree_feature_from_item( control->hwnd, tv->itemNew.hItem ); 2651 if (feature) 2652 title = feature->Title; 2653 } 2654 else 2655 title = tv->itemNew.pszText; 2656 2657 row = MSI_QueryGetRecord( dialog->package->db, select, title ); 2658 if (!row) 2659 return ERROR_FUNCTION_FAILED; 2660 2661 rec = MSI_CreateRecord( 1 ); 2662 2663 MSI_RecordSetStringW( rec, 1, MSI_RecordGetString( row, 4 ) ); 2664 msi_event_fire( dialog->package, szSelectionDescription, rec ); 2665 2666 dir = MSI_RecordGetString( row, 7 ); 2667 if (dir) 2668 { 2669 folder = msi_get_loaded_folder( dialog->package, dir ); 2670 if (!folder) 2671 { 2672 r = ERROR_FUNCTION_FAILED; 2673 goto done; 2674 } 2675 MSI_RecordSetStringW( rec, 1, folder->ResolvedTarget ); 2676 } 2677 else 2678 MSI_RecordSetStringW( rec, 1, NULL ); 2679 2680 msi_event_fire( dialog->package, szSelectionPath, rec ); 2681 2682 done: 2683 msiobj_release(&row->hdr); 2684 msiobj_release(&rec->hdr); 2685 2686 return r; 2687 } 2688 2689 static UINT msi_dialog_selection_tree( msi_dialog *dialog, MSIRECORD *rec ) 2690 { 2691 msi_control *control; 2692 LPCWSTR prop, control_name; 2693 MSIPACKAGE *package = dialog->package; 2694 DWORD style; 2695 struct msi_selection_tree_info *info; 2696 2697 info = msi_alloc( sizeof *info ); 2698 if (!info) 2699 return ERROR_FUNCTION_FAILED; 2700 2701 /* create the treeview control */ 2702 style = TVS_HASLINES | TVS_HASBUTTONS | TVS_LINESATROOT; 2703 style |= WS_GROUP | WS_VSCROLL | WS_TABSTOP; 2704 control = msi_dialog_add_control( dialog, rec, WC_TREEVIEWW, style ); 2705 if (!control) 2706 { 2707 msi_free(info); 2708 return ERROR_FUNCTION_FAILED; 2709 } 2710 2711 control->handler = msi_dialog_seltree_handler; 2712 control_name = MSI_RecordGetString( rec, 2 ); 2713 control->attributes = MSI_RecordGetInteger( rec, 8 ); 2714 prop = MSI_RecordGetString( rec, 9 ); 2715 control->property = msi_dialog_dup_property( dialog, prop, FALSE ); 2716 2717 /* subclass */ 2718 info->dialog = dialog; 2719 info->hwnd = control->hwnd; 2720 info->oldproc = (WNDPROC) SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC, 2721 (LONG_PTR)MSISelectionTree_WndProc ); 2722 SetPropW( control->hwnd, szButtonData, info ); 2723 2724 event_subscribe( dialog, szSelectionPath, control_name, szProperty ); 2725 2726 /* initialize it */ 2727 msi_seltree_create_imagelist( control->hwnd ); 2728 msi_seltree_add_child_features( package, control->hwnd, NULL, NULL ); 2729 2730 return ERROR_SUCCESS; 2731 } 2732 2733 /******************** Group Box ***************************************/ 2734 2735 static UINT msi_dialog_group_box( msi_dialog *dialog, MSIRECORD *rec ) 2736 { 2737 msi_control *control; 2738 DWORD style; 2739 2740 style = BS_GROUPBOX | WS_CHILD | WS_GROUP; 2741 control = msi_dialog_add_control( dialog, rec, WC_BUTTONW, style ); 2742 if (!control) 2743 return ERROR_FUNCTION_FAILED; 2744 2745 return ERROR_SUCCESS; 2746 } 2747 2748 /******************** List Box ***************************************/ 2749 2750 struct msi_listbox_info 2751 { 2752 msi_dialog *dialog; 2753 HWND hwnd; 2754 WNDPROC oldproc; 2755 DWORD num_items; 2756 DWORD addpos_items; 2757 LPWSTR *items; 2758 }; 2759 2760 static LRESULT WINAPI MSIListBox_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) 2761 { 2762 struct msi_listbox_info *info; 2763 LRESULT r; 2764 DWORD j; 2765 2766 TRACE("%p %04x %08lx %08lx\n", hWnd, msg, wParam, lParam); 2767 2768 info = GetPropW( hWnd, szButtonData ); 2769 if (!info) 2770 return 0; 2771 2772 r = CallWindowProcW( info->oldproc, hWnd, msg, wParam, lParam ); 2773 2774 switch( msg ) 2775 { 2776 case WM_NCDESTROY: 2777 for (j = 0; j < info->num_items; j++) 2778 msi_free( info->items[j] ); 2779 msi_free( info->items ); 2780 msi_free( info ); 2781 RemovePropW( hWnd, szButtonData ); 2782 break; 2783 } 2784 2785 return r; 2786 } 2787 2788 static UINT msi_listbox_add_item( MSIRECORD *rec, LPVOID param ) 2789 { 2790 struct msi_listbox_info *info = param; 2791 LPCWSTR value, text; 2792 int pos; 2793 2794 value = MSI_RecordGetString( rec, 3 ); 2795 text = MSI_RecordGetString( rec, 4 ); 2796 2797 info->items[info->addpos_items] = strdupW( value ); 2798 2799 pos = SendMessageW( info->hwnd, LB_ADDSTRING, 0, (LPARAM)text ); 2800 SendMessageW( info->hwnd, LB_SETITEMDATA, pos, (LPARAM)info->items[info->addpos_items] ); 2801 info->addpos_items++; 2802 return ERROR_SUCCESS; 2803 } 2804 2805 static UINT msi_listbox_add_items( struct msi_listbox_info *info, LPCWSTR property ) 2806 { 2807 static const WCHAR query[] = { 2808 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ', 2809 '`','L','i','s','t','B','o','x','`',' ','W','H','E','R','E',' ', 2810 '`','P','r','o','p','e','r','t','y','`',' ','=',' ','\'','%','s','\'',' ', 2811 'O','R','D','E','R',' ','B','Y',' ','`','O','r','d','e','r','`',0}; 2812 MSIQUERY *view; 2813 DWORD count; 2814 UINT r; 2815 2816 r = MSI_OpenQuery( info->dialog->package->db, &view, query, property ); 2817 if ( r != ERROR_SUCCESS ) 2818 return r; 2819 2820 /* just get the number of records */ 2821 count = 0; 2822 r = MSI_IterateRecords( view, &count, NULL, NULL ); 2823 if (r != ERROR_SUCCESS) 2824 { 2825 msiobj_release( &view->hdr ); 2826 return r; 2827 } 2828 info->num_items = count; 2829 info->items = msi_alloc( sizeof(*info->items) * count ); 2830 2831 r = MSI_IterateRecords( view, NULL, msi_listbox_add_item, info ); 2832 msiobj_release( &view->hdr ); 2833 return r; 2834 } 2835 2836 static UINT msi_dialog_listbox_handler( msi_dialog *dialog, 2837 msi_control *control, WPARAM param ) 2838 { 2839 struct msi_listbox_info *info; 2840 int index; 2841 LPCWSTR value; 2842 2843 if( HIWORD(param) != LBN_SELCHANGE ) 2844 return ERROR_SUCCESS; 2845 2846 info = GetPropW( control->hwnd, szButtonData ); 2847 index = SendMessageW( control->hwnd, LB_GETCURSEL, 0, 0 ); 2848 value = (LPCWSTR) SendMessageW( control->hwnd, LB_GETITEMDATA, index, 0 ); 2849 2850 msi_dialog_set_property( info->dialog->package, control->property, value ); 2851 msi_dialog_evaluate_control_conditions( info->dialog ); 2852 2853 return ERROR_SUCCESS; 2854 } 2855 2856 static UINT msi_dialog_list_box( msi_dialog *dialog, MSIRECORD *rec ) 2857 { 2858 struct msi_listbox_info *info; 2859 msi_control *control; 2860 DWORD attributes, style; 2861 LPCWSTR prop; 2862 2863 info = msi_alloc( sizeof *info ); 2864 if (!info) 2865 return ERROR_FUNCTION_FAILED; 2866 2867 style = WS_TABSTOP | WS_GROUP | WS_CHILD | LBS_NOTIFY | WS_VSCROLL | WS_BORDER; 2868 attributes = MSI_RecordGetInteger( rec, 8 ); 2869 if (~attributes & msidbControlAttributesSorted) 2870 style |= LBS_SORT; 2871 2872 control = msi_dialog_add_control( dialog, rec, WC_LISTBOXW, style ); 2873 if (!control) 2874 { 2875 msi_free(info); 2876 return ERROR_FUNCTION_FAILED; 2877 } 2878 2879 control->handler = msi_dialog_listbox_handler; 2880 2881 prop = MSI_RecordGetString( rec, 9 ); 2882 control->property = msi_dialog_dup_property( dialog, prop, FALSE ); 2883 2884 /* subclass */ 2885 info->dialog = dialog; 2886 info->hwnd = control->hwnd; 2887 info->items = NULL; 2888 info->addpos_items = 0; 2889 info->oldproc = (WNDPROC)SetWindowLongPtrW( control->hwnd, GWLP_WNDPROC, 2890 (LONG_PTR)MSIListBox_WndProc ); 2891 SetPropW( control->hwnd, szButtonData, info ); 2892 2893 if ( control->property ) 2894 msi_listbox_add_items( info, control->property ); 2895 2896 return ERROR_SUCCESS; 2897 } 2898 2899 /******************** Directory Combo ***************************************/ 2900 2901 static void msi_dialog_update_directory_combo( msi_dialog *dialog, msi_control *control ) 2902 { 2903 LPWSTR prop, path; 2904 BOOL indirect; 2905 2906 if (!control && !(control = msi_dialog_find_control_by_type( dialog, szDirectoryCombo ))) 2907 return; 2908 2909 indirect = control->attributes & msidbControlAttributesIndirect; 2910 prop = msi_dialog_dup_property( dialog, control->property, indirect ); 2911 path = msi_dialog_dup_property( dialog, prop, TRUE ); 2912 2913 PathStripPathW( path ); 2914 PathRemoveBackslashW( path ); 2915 2916 SendMessageW( control->hwnd, CB_INSERTSTRING, 0, (LPARAM)path ); 2917 SendMessageW( control->hwnd, CB_SETCURSEL, 0, 0 ); 2918 2919 msi_free( path ); 2920 msi_free( prop ); 2921 } 2922 2923 static UINT msi_dialog_directory_combo( msi_dialog *dialog, MSIRECORD *rec ) 2924 { 2925 msi_control *control; 2926 LPCWSTR prop; 2927 DWORD style; 2928 2929 /* FIXME: use CBS_OWNERDRAWFIXED and add owner draw code */ 2930 style = CBS_DROPDOWNLIST | CBS_HASSTRINGS | WS_CHILD | 2931 WS_GROUP | WS_TABSTOP | WS_VSCROLL; 2932 control = msi_dialog_add_control( dialog, rec, WC_COMBOBOXW, style ); 2933 if (!control) 2934 return ERROR_FUNCTION_FAILED; 2935 2936 control->attributes = MSI_RecordGetInteger( rec, 8 ); 2937 prop = MSI_RecordGetString( rec, 9 ); 2938 control->property = msi_dialog_dup_property( dialog, prop, FALSE ); 2939 2940 msi_dialog_update_directory_combo( dialog, control ); 2941 2942 return ERROR_SUCCESS; 2943 } 2944 2945 /******************** Directory List ***************************************/ 2946 2947 static void msi_dialog_update_directory_list( msi_dialog *dialog, msi_control *control ) 2948 { 2949 WCHAR dir_spec[MAX_PATH]; 2950 WIN32_FIND_DATAW wfd; 2951 LPWSTR prop, path; 2952 BOOL indirect; 2953 LVITEMW item; 2954 HANDLE file; 2955 2956 static const WCHAR asterisk[] = {'*',0}; 2957 2958 if (!control && !(control = msi_dialog_find_control_by_type( dialog, szDirectoryList ))) 2959 return; 2960 2961 /* clear the list-view */ 2962 SendMessageW( control->hwnd, LVM_DELETEALLITEMS, 0, 0 ); 2963 2964 indirect = control->attributes & msidbControlAttributesIndirect; 2965 prop = msi_dialog_dup_property( dialog, control->property, indirect ); 2966 path = msi_dialog_dup_property( dialog, prop, TRUE ); 2967 2968 lstrcpyW( dir_spec, path ); 2969 lstrcatW( dir_spec, asterisk ); 2970 2971 file = FindFirstFileW( dir_spec, &wfd ); 2972 if ( file == INVALID_HANDLE_VALUE ) 2973 return; 2974 2975 do 2976 { 2977 if ( wfd.dwFileAttributes != FILE_ATTRIBUTE_DIRECTORY ) 2978 continue; 2979 2980 if ( !strcmpW( wfd.cFileName, szDot ) || !strcmpW( wfd.cFileName, szDotDot ) ) 2981 continue; 2982 2983 item.mask = LVIF_TEXT; 2984 item.cchTextMax = MAX_PATH; 2985 item.iItem = 0; 2986 item.iSubItem = 0; 2987 item.pszText = wfd.cFileName; 2988 2989 SendMessageW( control->hwnd, LVM_INSERTITEMW, 0, (LPARAM)&item ); 2990 } while ( FindNextFileW( file, &wfd ) ); 2991 2992 msi_free( prop ); 2993 msi_free( path ); 2994 FindClose( file ); 2995 } 2996 2997 static UINT msi_dialog_directorylist_up( msi_dialog *dialog ) 2998 { 2999 msi_control *control; 3000 LPWSTR prop, path, ptr; 3001 BOOL indirect; 3002 3003 control = msi_dialog_find_control_by_type( dialog, szDirectoryList ); 3004 indirect = control->attributes & msidbControlAttributesIndirect; 3005 prop = msi_dialog_dup_property( dialog, control->property, indirect ); 3006 path = msi_dialog_dup_property( dialog, prop, TRUE ); 3007 3008 /* strip off the last directory */ 3009 ptr = PathFindFileNameW( path ); 3010 if (ptr != path) *(ptr - 1) = '\0'; 3011 PathAddBackslashW( path ); 3012 3013 msi_dialog_set_property( dialog->package, prop, path ); 3014 3015 msi_dialog_update_directory_list( dialog, NULL ); 3016 msi_dialog_update_directory_combo( dialog, NULL ); 3017 msi_dialog_update_pathedit( dialog, NULL ); 3018 3019 msi_free( path ); 3020 msi_free( prop ); 3021 3022 return ERROR_SUCCESS; 3023 } 3024 3025 static UINT msi_dialog_dirlist_handler( msi_dialog *dialog, 3026 msi_control *control, WPARAM param ) 3027 { 3028 LPNMHDR nmhdr = (LPNMHDR)param; 3029 WCHAR new_path[MAX_PATH]; 3030 WCHAR text[MAX_PATH]; 3031 LPWSTR path, prop; 3032 BOOL indirect; 3033 LVITEMW item; 3034 int index; 3035 3036 if (nmhdr->code != LVN_ITEMACTIVATE) 3037 return ERROR_SUCCESS; 3038 3039 index = SendMessageW( control->hwnd, LVM_GETNEXTITEM, -1, LVNI_SELECTED ); 3040 if ( index < 0 ) 3041 { 3042 ERR("No list-view item selected!\n"); 3043 return ERROR_FUNCTION_FAILED; 3044 } 3045 3046 item.iSubItem = 0; 3047 item.pszText = text; 3048 item.cchTextMax = MAX_PATH; 3049 SendMessageW( control->hwnd, LVM_GETITEMTEXTW, index, (LPARAM)&item ); 3050 3051 indirect = control->attributes & msidbControlAttributesIndirect; 3052 prop = msi_dialog_dup_property( dialog, control->property, indirect ); 3053 path = msi_dialog_dup_property( dialog, prop, TRUE ); 3054 3055 lstrcpyW( new_path, path ); 3056 lstrcatW( new_path, text ); 3057 lstrcatW( new_path, szBackSlash ); 3058 3059 msi_dialog_set_property( dialog->package, prop, new_path ); 3060 3061 msi_dialog_update_directory_list( dialog, NULL ); 3062 msi_dialog_update_directory_combo( dialog, NULL ); 3063 msi_dialog_update_pathedit( dialog, NULL ); 3064 3065 msi_free( prop ); 3066 msi_free( path ); 3067 return ERROR_SUCCESS; 3068 } 3069 3070 static UINT msi_dialog_directory_list( msi_dialog *dialog, MSIRECORD *rec ) 3071 { 3072 msi_control *control; 3073 LPCWSTR prop; 3074 DWORD style; 3075 3076 style = LVS_LIST | WS_VSCROLL | LVS_SHAREIMAGELISTS | 3077 LVS_AUTOARRANGE | LVS_SINGLESEL | WS_BORDER | 3078 LVS_SORTASCENDING | WS_CHILD | WS_GROUP | WS_TABSTOP; 3079 control = msi_dialog_add_control( dialog, rec, WC_LISTVIEWW, style ); 3080 if (!control) 3081 return ERROR_FUNCTION_FAILED; 3082 3083 control->attributes = MSI_RecordGetInteger( rec, 8 ); 3084 control->handler = msi_dialog_dirlist_handler; 3085 prop = MSI_RecordGetString( rec, 9 ); 3086 control->property = msi_dialog_dup_property( dialog, prop, FALSE ); 3087 3088 /* double click to activate an item in the list */ 3089 SendMessageW( control->hwnd, LVM_SETEXTENDEDLISTVIEWSTYLE, 3090 0, LVS_EX_TWOCLICKACTIVATE ); 3091 3092 msi_dialog_update_directory_list( dialog, control ); 3093 3094 return ERROR_SUCCESS; 3095 } 3096 3097 /******************** VolumeCost List ***************************************/ 3098 3099 static BOOL str_is_number( LPCWSTR str ) 3100 { 3101 int i; 3102 3103 for (i = 0; i < lstrlenW( str ); i++) 3104 if (!isdigitW(str[i])) 3105 return FALSE; 3106 3107 return TRUE; 3108 } 3109 3110 static const WCHAR column_keys[][80] = 3111 { 3112 {'V','o','l','u','m','e','C','o','s','t','V','o','l','u','m','e',0}, 3113 {'V','o','l','u','m','e','C','o','s','t','S','i','z','e',0}, 3114 {'V','o','l','u','m','e','C','o','s','t','A','v','a','i','l','a','b','l','e',0}, 3115 {'V','o','l','u','m','e','C','o','s','t','R','e','q','u','i','r','e','d',0}, 3116 {'V','o','l','u','m','e','C','o','s','t','D','i','f','f','e','r','e','n','c','e',0} 3117 }; 3118 3119 static void msi_dialog_vcl_add_columns( msi_dialog *dialog, msi_control *control, MSIRECORD *rec ) 3120 { 3121 LPCWSTR text = MSI_RecordGetString( rec, 10 ); 3122 LPCWSTR begin = text, end; 3123 WCHAR *num; 3124 LVCOLUMNW lvc; 3125 DWORD count = 0; 3126 3127 static const WCHAR negative[] = {'-',0}; 3128 3129 if (!text) return; 3130 3131 while ((begin = strchrW( begin, '{' )) && count < 5) 3132 { 3133 if (!(end = strchrW( begin, '}' ))) 3134 return; 3135 3136 num = msi_alloc( (end-begin+1)*sizeof(WCHAR) ); 3137 if (!num) 3138 return; 3139 3140 lstrcpynW( num, begin + 1, end - begin ); 3141 begin += end - begin + 1; 3142 3143 /* empty braces or '0' hides the column */ 3144 if ( !num[0] || !strcmpW( num, szZero ) ) 3145 { 3146 count++; 3147 msi_free( num ); 3148 continue; 3149 } 3150 3151 /* the width must be a positive number 3152 * if a width is invalid, all remaining columns are hidden 3153 */ 3154 if ( !strncmpW( num, negative, 1 ) || !str_is_number( num ) ) { 3155 msi_free( num ); 3156 return; 3157 } 3158 3159 ZeroMemory( &lvc, sizeof(lvc) ); 3160 lvc.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM; 3161 lvc.cx = atolW( num ); 3162 lvc.pszText = msi_dialog_get_uitext( dialog, column_keys[count] ); 3163 3164 SendMessageW( control->hwnd, LVM_INSERTCOLUMNW, count++, (LPARAM)&lvc ); 3165 msi_free( lvc.pszText ); 3166 msi_free( num ); 3167 } 3168 } 3169 3170 static LONGLONG msi_vcl_get_cost( msi_dialog *dialog ) 3171 { 3172 MSIFEATURE *feature; 3173 INT each_cost; 3174 LONGLONG total_cost = 0; 3175 3176 LIST_FOR_EACH_ENTRY( feature, &dialog->package->features, MSIFEATURE, entry ) 3177 { 3178 if (ERROR_SUCCESS == (MSI_GetFeatureCost(dialog->package, feature, 3179 MSICOSTTREE_SELFONLY, INSTALLSTATE_LOCAL, &each_cost))) 3180 { 3181 /* each_cost is in 512-byte units */ 3182 total_cost += ((LONGLONG)each_cost) * 512; 3183 } 3184 if (ERROR_SUCCESS == (MSI_GetFeatureCost(dialog->package, feature, 3185 MSICOSTTREE_SELFONLY, INSTALLSTATE_ABSENT, &each_cost))) 3186 { 3187 /* each_cost is in 512-byte units */ 3188 total_cost -= ((LONGLONG)each_cost) * 512; 3189 } 3190 } 3191 return total_cost; 3192 } 3193 3194 static void msi_dialog_vcl_add_drives( msi_dialog *dialog, msi_control *control ) 3195 { 3196 ULARGE_INTEGER total, free; 3197 LONGLONG difference, cost; 3198 WCHAR size_text[MAX_PATH]; 3199 WCHAR cost_text[MAX_PATH]; 3200 LPWSTR drives, ptr; 3201 LVITEMW lvitem; 3202 DWORD size; 3203 int i = 0; 3204 3205 cost = msi_vcl_get_cost(dialog); 3206 StrFormatByteSizeW(cost, cost_text, MAX_PATH); 3207 3208 size = GetLogicalDriveStringsW( 0, NULL ); 3209 if ( !size ) return; 3210 3211 drives = msi_alloc( (size + 1) * sizeof(WCHAR) ); 3212 if ( !drives ) return; 3213 3214 GetLogicalDriveStringsW( size, drives ); 3215 3216 ptr = drives; 3217 while (*ptr) 3218 { 3219 lvitem.mask = LVIF_TEXT; 3220 lvitem.iItem = i; 3221 lvitem.iSubItem = 0; 3222 lvitem.pszText = ptr; 3223 lvitem.cchTextMax = lstrlenW(ptr) + 1; 3224 SendMessageW( control->hwnd, LVM_INSERTITEMW, 0, (LPARAM)&lvitem ); 3225 3226 GetDiskFreeSpaceExW(ptr, &free, &total, NULL); 3227 difference = free.QuadPart - cost; 3228 3229 StrFormatByteSizeW(total.QuadPart, size_text, MAX_PATH); 3230 lvitem.iSubItem = 1; 3231 lvitem.pszText = size_text; 3232 lvitem.cchTextMax = lstrlenW(size_text) + 1; 3233 SendMessageW( control->hwnd, LVM_SETITEMW, 0, (LPARAM)&lvitem ); 3234 3235 StrFormatByteSizeW(free.QuadPart, size_text, MAX_PATH); 3236 lvitem.iSubItem = 2; 3237 lvitem.pszText = size_text; 3238 lvitem.cchTextMax = lstrlenW(size_text) + 1; 3239 SendMessageW( control->hwnd, LVM_SETITEMW, 0, (LPARAM)&lvitem ); 3240 3241 lvitem.iSubItem = 3; 3242 lvitem.pszText = cost_text; 3243 lvitem.cchTextMax = lstrlenW(cost_text) + 1; 3244 SendMessageW( control->hwnd, LVM_SETITEMW, 0, (LPARAM)&lvitem ); 3245 3246 StrFormatByteSizeW(difference, size_text, MAX_PATH); 3247 lvitem.iSubItem = 4; 3248 lvitem.pszText = size_text; 3249 lvitem.cchTextMax = lstrlenW(size_text) + 1; 3250 SendMessageW( control->hwnd, LVM_SETITEMW, 0, (LPARAM)&lvitem ); 3251 3252 ptr += lstrlenW(ptr) + 1; 3253 i++; 3254 } 3255 3256 msi_free( drives ); 3257 } 3258 3259 static UINT msi_dialog_volumecost_list( msi_dialog *dialog, MSIRECORD *rec ) 3260 { 3261 msi_control *control; 3262 DWORD style; 3263 3264 style = LVS_REPORT | WS_VSCROLL | WS_HSCROLL | LVS_SHAREIMAGELISTS | 3265 LVS_AUTOARRANGE | LVS_SINGLESEL | WS_BORDER | 3266 WS_CHILD | WS_TABSTOP | WS_GROUP; 3267 control = msi_dialog_add_control( dialog, rec, WC_LISTVIEWW, style ); 3268 if (!control) 3269 return ERROR_FUNCTION_FAILED; 3270 3271 msi_dialog_vcl_add_columns( dialog, control, rec ); 3272 msi_dialog_vcl_add_drives( dialog, control ); 3273 3274 return ERROR_SUCCESS; 3275 } 3276 3277 /******************** VolumeSelect Combo ***************************************/ 3278 3279 static UINT msi_dialog_volsel_handler( msi_dialog *dialog, 3280 msi_control *control, WPARAM param ) 3281 { 3282 WCHAR text[MAX_PATH]; 3283 LPWSTR prop; 3284 BOOL indirect; 3285 int index; 3286 3287 if (HIWORD(param) != CBN_SELCHANGE) 3288 return ERROR_SUCCESS; 3289 3290 index = SendMessageW( control->hwnd, CB_GETCURSEL, 0, 0 ); 3291 if ( index == CB_ERR ) 3292 { 3293 ERR("No ComboBox item selected!\n"); 3294 return ERROR_FUNCTION_FAILED; 3295 } 3296 3297 SendMessageW( control->hwnd, CB_GETLBTEXT, index, (LPARAM)text ); 3298 3299 indirect = control->attributes & msidbControlAttributesIndirect; 3300 prop = msi_dialog_dup_property( dialog, control->property, indirect ); 3301 3302 msi_dialog_set_property( dialog->package, prop, text ); 3303 3304 msi_free( prop ); 3305 return ERROR_SUCCESS; 3306 } 3307 3308 static void msi_dialog_vsc_add_drives( msi_dialog *dialog, msi_control *control ) 3309 { 3310 LPWSTR drives, ptr; 3311 DWORD size; 3312 3313 size = GetLogicalDriveStringsW( 0, NULL ); 3314 if ( !size ) return; 3315 3316 drives = msi_alloc( (size + 1) * sizeof(WCHAR) ); 3317 if ( !drives ) return; 3318 3319 GetLogicalDriveStringsW( size, drives ); 3320 3321 ptr = drives; 3322 while (*ptr) 3323 { 3324 SendMessageW( control->hwnd, CB_ADDSTRING, 0, (LPARAM)ptr ); 3325 ptr += lstrlenW(ptr) + 1; 3326 } 3327 3328 msi_free( drives ); 3329 } 3330 3331 static UINT msi_dialog_volumeselect_combo( msi_dialog *dialog, MSIRECORD *rec ) 3332 { 3333 msi_control *control; 3334 LPCWSTR prop; 3335 DWORD style; 3336 3337 /* FIXME: CBS_OWNERDRAWFIXED */ 3338 style = WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP | 3339 CBS_DROPDOWNLIST | CBS_SORT | CBS_HASSTRINGS | 3340 WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR; 3341 control = msi_dialog_add_control( dialog, rec, WC_COMBOBOXW, style ); 3342 if (!control) 3343 return ERROR_FUNCTION_FAILED; 3344 3345 control->attributes = MSI_RecordGetInteger( rec, 8 ); 3346 control->handler = msi_dialog_volsel_handler; 3347 prop = MSI_RecordGetString( rec, 9 ); 3348 control->property = msi_dialog_dup_property( dialog, prop, FALSE ); 3349 3350 msi_dialog_vsc_add_drives( dialog, control ); 3351 3352 return ERROR_SUCCESS; 3353 } 3354 3355 static UINT msi_dialog_hyperlink_handler( msi_dialog *dialog, msi_control *control, WPARAM param ) 3356 { 3357 static const WCHAR hrefW[] = {'h','r','e','f'}; 3358 static const WCHAR openW[] = {'o','p','e','n',0}; 3359 int len, len_href = sizeof(hrefW) / sizeof(hrefW[0]); 3360 const WCHAR *p, *q; 3361 WCHAR quote = 0; 3362 LITEM item; 3363 3364 item.mask = LIF_ITEMINDEX | LIF_URL; 3365 item.iLink = 0; 3366 item.szUrl[0] = 0; 3367 3368 SendMessageW( control->hwnd, LM_GETITEM, 0, (LPARAM)&item ); 3369 3370 p = item.szUrl; 3371 while (*p && *p != '<') p++; 3372 if (!*p++) return ERROR_SUCCESS; 3373 if (toupperW( *p++ ) != 'A' || !isspaceW( *p++ )) return ERROR_SUCCESS; 3374 while (*p && isspaceW( *p )) p++; 3375 3376 len = strlenW( p ); 3377 if (len > len_href && !memicmpW( p, hrefW, len_href )) 3378 { 3379 p += len_href; 3380 while (*p && isspaceW( *p )) p++; 3381 if (!*p || *p++ != '=') return ERROR_SUCCESS; 3382 while (*p && isspaceW( *p )) p++; 3383 3384 if (*p == '\"' || *p == '\'') quote = *p++; 3385 q = p; 3386 if (quote) 3387 { 3388 while (*q && *q != quote) q++; 3389 if (*q != quote) return ERROR_SUCCESS; 3390 } 3391 else 3392 { 3393 while (*q && *q != '>' && !isspaceW( *q )) q++; 3394 if (!*q) return ERROR_SUCCESS; 3395 } 3396 item.szUrl[q - item.szUrl] = 0; 3397 ShellExecuteW( NULL, openW, p, NULL, NULL, SW_SHOWNORMAL ); 3398 } 3399 return ERROR_SUCCESS; 3400 } 3401 3402 static UINT msi_dialog_hyperlink( msi_dialog *dialog, MSIRECORD *rec ) 3403 { 3404 msi_control *control; 3405 DWORD style = WS_CHILD | WS_TABSTOP | WS_GROUP; 3406 const WCHAR *text = MSI_RecordGetString( rec, 10 ); 3407 int len = strlenW( text ); 3408 LITEM item; 3409 3410 control = msi_dialog_add_control( dialog, rec, WC_LINK, style ); 3411 if (!control) 3412 return ERROR_FUNCTION_FAILED; 3413 3414 control->attributes = MSI_RecordGetInteger( rec, 8 ); 3415 control->handler = msi_dialog_hyperlink_handler; 3416 3417 item.mask = LIF_ITEMINDEX | LIF_STATE | LIF_URL; 3418 item.iLink = 0; 3419 item.state = LIS_ENABLED; 3420 item.stateMask = LIS_ENABLED; 3421 if (len < L_MAX_URL_LENGTH) strcpyW( item.szUrl, text ); 3422 else item.szUrl[0] = 0; 3423 3424 SendMessageW( control->hwnd, LM_SETITEM, 0, (LPARAM)&item ); 3425 3426 return ERROR_SUCCESS; 3427 } 3428 3429 static const struct control_handler msi_dialog_handler[] = 3430 { 3431 { szText, msi_dialog_text_control }, 3432 { szPushButton, msi_dialog_button_control }, 3433 { szLine, msi_dialog_line_control }, 3434 { szBitmap, msi_dialog_bitmap_control }, 3435 { szCheckBox, msi_dialog_checkbox_control }, 3436 { szScrollableText, msi_dialog_scrolltext_control }, 3437 { szComboBox, msi_dialog_combo_control }, 3438 { szEdit, msi_dialog_edit_control }, 3439 { szMaskedEdit, msi_dialog_maskedit_control }, 3440 { szPathEdit, msi_dialog_pathedit_control }, 3441 { szProgressBar, msi_dialog_progress_bar }, 3442 { szRadioButtonGroup, msi_dialog_radiogroup_control }, 3443 { szIcon, msi_dialog_icon_control }, 3444 { szSelectionTree, msi_dialog_selection_tree }, 3445 { szGroupBox, msi_dialog_group_box }, 3446 { szListBox, msi_dialog_list_box }, 3447 { szDirectoryCombo, msi_dialog_directory_combo }, 3448 { szDirectoryList, msi_dialog_directory_list }, 3449 { szVolumeCostList, msi_dialog_volumecost_list }, 3450 { szVolumeSelectCombo, msi_dialog_volumeselect_combo }, 3451 { szHyperLink, msi_dialog_hyperlink } 3452 }; 3453 3454 #define NUM_CONTROL_TYPES (sizeof msi_dialog_handler/sizeof msi_dialog_handler[0]) 3455 3456 static UINT msi_dialog_create_controls( MSIRECORD *rec, LPVOID param ) 3457 { 3458 msi_dialog *dialog = param; 3459 LPCWSTR control_type; 3460 UINT i; 3461 3462 /* find and call the function that can create this type of control */ 3463 control_type = MSI_RecordGetString( rec, 3 ); 3464 for( i=0; i<NUM_CONTROL_TYPES; i++ ) 3465 if (!strcmpiW( msi_dialog_handler[i].control_type, control_type )) 3466 break; 3467 if( i != NUM_CONTROL_TYPES ) 3468 msi_dialog_handler[i].func( dialog, rec ); 3469 else 3470 ERR("no handler for element type %s\n", debugstr_w(control_type)); 3471 3472 return ERROR_SUCCESS; 3473 } 3474 3475 static UINT msi_dialog_fill_controls( msi_dialog *dialog ) 3476 { 3477 static const WCHAR query[] = { 3478 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ', 3479 'C','o','n','t','r','o','l',' ','W','H','E','R','E',' ', 3480 '`','D','i','a','l','o','g','_','`',' ','=',' ','\'','%','s','\'',0}; 3481 UINT r; 3482 MSIQUERY *view; 3483 MSIPACKAGE *package = dialog->package; 3484 3485 TRACE("%p %s\n", dialog, debugstr_w(dialog->name) ); 3486 3487 /* query the Control table for all the elements of the control */ 3488 r = MSI_OpenQuery( package->db, &view, query, dialog->name ); 3489 if( r != ERROR_SUCCESS ) 3490 { 3491 ERR("query failed for dialog %s\n", debugstr_w(dialog->name)); 3492 return ERROR_INVALID_PARAMETER; 3493 } 3494 3495 r = MSI_IterateRecords( view, 0, msi_dialog_create_controls, dialog ); 3496 msiobj_release( &view->hdr ); 3497 return r; 3498 } 3499 3500 static UINT msi_dialog_reset( msi_dialog *dialog ) 3501 { 3502 /* FIXME: should restore the original values of any properties we changed */ 3503 return msi_dialog_evaluate_control_conditions( dialog ); 3504 } 3505 3506 /* figure out the height of 10 point MS Sans Serif */ 3507 static INT msi_dialog_get_sans_serif_height( HWND hwnd ) 3508 { 3509 static const WCHAR szSansSerif[] = { 3510 'M','S',' ','S','a','n','s',' ','S','e','r','i','f',0 }; 3511 LOGFONTW lf; 3512 TEXTMETRICW tm; 3513 BOOL r; 3514 LONG height = 0; 3515 HFONT hFont, hOldFont; 3516 HDC hdc; 3517 3518 hdc = GetDC( hwnd ); 3519 if (hdc) 3520 { 3521 memset( &lf, 0, sizeof lf ); 3522 lf.lfHeight = MulDiv(12, GetDeviceCaps(hdc, LOGPIXELSY), 72); 3523 strcpyW( lf.lfFaceName, szSansSerif ); 3524 hFont = CreateFontIndirectW(&lf); 3525 if (hFont) 3526 { 3527 hOldFont = SelectObject( hdc, hFont ); 3528 r = GetTextMetricsW( hdc, &tm ); 3529 if (r) 3530 height = tm.tmHeight; 3531 SelectObject( hdc, hOldFont ); 3532 DeleteObject( hFont ); 3533 } 3534 ReleaseDC( hwnd, hdc ); 3535 } 3536 return height; 3537 } 3538 3539 /* fetch the associated record from the Dialog table */ 3540 static MSIRECORD *msi_get_dialog_record( msi_dialog *dialog ) 3541 { 3542 static const WCHAR query[] = { 3543 'S','E','L','E','C','T',' ','*',' ', 3544 'F','R','O','M',' ','D','i','a','l','o','g',' ', 3545 'W','H','E','R','E',' ', 3546 '`','D','i','a','l','o','g','`',' ','=',' ','\'','%','s','\'',0}; 3547 MSIPACKAGE *package = dialog->package; 3548 MSIRECORD *rec = NULL; 3549 3550 TRACE("%p %s\n", dialog, debugstr_w(dialog->name) ); 3551 3552 rec = MSI_QueryGetRecord( package->db, query, dialog->name ); 3553 if( !rec ) 3554 WARN("query failed for dialog %s\n", debugstr_w(dialog->name)); 3555 3556 return rec; 3557 } 3558 3559 static void msi_dialog_adjust_dialog_pos( msi_dialog *dialog, MSIRECORD *rec, LPRECT pos ) 3560 { 3561 static const WCHAR szScreenX[] = {'S','c','r','e','e','n','X',0}; 3562 static const WCHAR szScreenY[] = {'S','c','r','e','e','n','Y',0}; 3563 3564 UINT xres, yres; 3565 POINT center; 3566 SIZE sz; 3567 LONG style; 3568 3569 center.x = MSI_RecordGetInteger( rec, 2 ); 3570 center.y = MSI_RecordGetInteger( rec, 3 ); 3571 3572 sz.cx = MSI_RecordGetInteger( rec, 4 ); 3573 sz.cy = MSI_RecordGetInteger( rec, 5 ); 3574 3575 sz.cx = msi_dialog_scale_unit( dialog, sz.cx ); 3576 sz.cy = msi_dialog_scale_unit( dialog, sz.cy ); 3577 3578 xres = msi_get_property_int( dialog->package->db, szScreenX, 0 ); 3579 yres = msi_get_property_int( dialog->package->db, szScreenY, 0 ); 3580 3581 center.x = MulDiv( center.x, xres, 100 ); 3582 center.y = MulDiv( center.y, yres, 100 ); 3583 3584 /* turn the client pos into the window rectangle */ 3585 if (dialog->package->center_x && dialog->package->center_y) 3586 { 3587 pos->left = dialog->package->center_x - sz.cx / 2.0; 3588 pos->right = pos->left + sz.cx; 3589 pos->top = dialog->package->center_y - sz.cy / 2.0; 3590 pos->bottom = pos->top + sz.cy; 3591 } 3592 else 3593 { 3594 pos->left = center.x - sz.cx/2; 3595 pos->right = pos->left + sz.cx; 3596 pos->top = center.y - sz.cy/2; 3597 pos->bottom = pos->top + sz.cy; 3598 3599 /* save the center */ 3600 dialog->package->center_x = center.x; 3601 dialog->package->center_y = center.y; 3602 } 3603 3604 dialog->size.cx = sz.cx; 3605 dialog->size.cy = sz.cy; 3606 3607 TRACE("%s\n", wine_dbgstr_rect(pos)); 3608 3609 style = GetWindowLongPtrW( dialog->hwnd, GWL_STYLE ); 3610 AdjustWindowRect( pos, style, FALSE ); 3611 } 3612 3613 static void msi_dialog_set_tab_order( msi_dialog *dialog, LPCWSTR first ) 3614 { 3615 struct list tab_chain; 3616 msi_control *control; 3617 HWND prev = HWND_TOP; 3618 3619 list_init( &tab_chain ); 3620 if (!(control = msi_dialog_find_control( dialog, first ))) return; 3621 3622 dialog->hWndFocus = control->hwnd; 3623 while (control) 3624 { 3625 list_remove( &control->entry ); 3626 list_add_tail( &tab_chain, &control->entry ); 3627 if (!control->tabnext) break; 3628 control = msi_dialog_find_control( dialog, control->tabnext ); 3629 } 3630 3631 LIST_FOR_EACH_ENTRY( control, &tab_chain, msi_control, entry ) 3632 { 3633 SetWindowPos( control->hwnd, prev, 0, 0, 0, 0, 3634 SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOREDRAW | 3635 SWP_NOREPOSITION | SWP_NOSENDCHANGING | SWP_NOSIZE ); 3636 prev = control->hwnd; 3637 } 3638 3639 /* put them back on the main list */ 3640 list_move_head( &dialog->controls, &tab_chain ); 3641 } 3642 3643 static LRESULT msi_dialog_oncreate( HWND hwnd, LPCREATESTRUCTW cs ) 3644 { 3645 static const WCHAR df[] = { 3646 'D','e','f','a','u','l','t','U','I','F','o','n','t',0 }; 3647 static const WCHAR dfv[] = { 3648 'M','S',' ','S','h','e','l','l',' ','D','l','g',0 }; 3649 msi_dialog *dialog = cs->lpCreateParams; 3650 MSIRECORD *rec = NULL; 3651 LPWSTR title = NULL; 3652 RECT pos; 3653 3654 TRACE("%p %p\n", dialog, dialog->package); 3655 3656 dialog->hwnd = hwnd; 3657 SetWindowLongPtrW( hwnd, GWLP_USERDATA, (LONG_PTR) dialog ); 3658 3659 rec = msi_get_dialog_record( dialog ); 3660 if( !rec ) 3661 { 3662 TRACE("No record found for dialog %s\n", debugstr_w(dialog->name)); 3663 return -1; 3664 } 3665 3666 dialog->scale = msi_dialog_get_sans_serif_height(dialog->hwnd); 3667 3668 msi_dialog_adjust_dialog_pos( dialog, rec, &pos ); 3669 3670 dialog->attributes = MSI_RecordGetInteger( rec, 6 ); 3671 3672 dialog->default_font = msi_dup_property( dialog->package->db, df ); 3673 if (!dialog->default_font) 3674 { 3675 dialog->default_font = strdupW(dfv); 3676 if (!dialog->default_font) 3677 { 3678 msiobj_release( &rec->hdr ); 3679 return -1; 3680 } 3681 } 3682 3683 title = msi_get_deformatted_field( dialog->package, rec, 7 ); 3684 SetWindowTextW( hwnd, title ); 3685 msi_free( title ); 3686 3687 SetWindowPos( hwnd, 0, pos.left, pos.top, 3688 pos.right - pos.left, pos.bottom - pos.top, 3689 SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOREDRAW ); 3690 3691 msi_dialog_build_font_list( dialog ); 3692 msi_dialog_fill_controls( dialog ); 3693 msi_dialog_evaluate_control_conditions( dialog ); 3694 msi_dialog_set_tab_order( dialog, MSI_RecordGetString( rec, 8 ) ); 3695 msiobj_release( &rec->hdr ); 3696 3697 return 0; 3698 } 3699 3700 static LRESULT msi_dialog_oncommand( msi_dialog *dialog, WPARAM param, HWND hwnd ) 3701 { 3702 msi_control *control = NULL; 3703 3704 TRACE("%p %p %08lx\n", dialog, hwnd, param); 3705 3706 switch (param) 3707 { 3708 case 1: /* enter */ 3709 control = msi_dialog_find_control( dialog, dialog->control_default ); 3710 break; 3711 case 2: /* escape */ 3712 control = msi_dialog_find_control( dialog, dialog->control_cancel ); 3713 break; 3714 default: 3715 control = msi_dialog_find_control_by_hwnd( dialog, hwnd ); 3716 } 3717 3718 if( control ) 3719 { 3720 if( control->handler ) 3721 { 3722 control->handler( dialog, control, param ); 3723 msi_dialog_evaluate_control_conditions( dialog ); 3724 } 3725 } 3726 3727 return 0; 3728 } 3729 3730 static LRESULT msi_dialog_onnotify( msi_dialog *dialog, LPARAM param ) 3731 { 3732 LPNMHDR nmhdr = (LPNMHDR) param; 3733 msi_control *control = msi_dialog_find_control_by_hwnd( dialog, nmhdr->hwndFrom ); 3734 3735 TRACE("%p %p\n", dialog, nmhdr->hwndFrom); 3736 3737 if ( control && control->handler ) 3738 control->handler( dialog, control, param ); 3739 3740 return 0; 3741 } 3742 3743 static void dialog_setfocus( msi_dialog *dialog ) 3744 { 3745 HWND hwnd = dialog->hWndFocus; 3746 3747 hwnd = GetNextDlgTabItem( dialog->hwnd, hwnd, TRUE); 3748 hwnd = GetNextDlgTabItem( dialog->hwnd, hwnd, FALSE); 3749 SetFocus( hwnd ); 3750 dialog->hWndFocus = hwnd; 3751 } 3752 3753 static LRESULT WINAPI MSIDialog_WndProc( HWND hwnd, UINT msg, 3754 WPARAM wParam, LPARAM lParam ) 3755 { 3756 msi_dialog *dialog = (LPVOID) GetWindowLongPtrW( hwnd, GWLP_USERDATA ); 3757 3758 TRACE("0x%04x\n", msg); 3759 3760 switch (msg) 3761 { 3762 case WM_MOVE: 3763 dialog->package->center_x = LOWORD(lParam) + dialog->size.cx / 2.0; 3764 dialog->package->center_y = HIWORD(lParam) + dialog->size.cy / 2.0; 3765 break; 3766 3767 case WM_CREATE: 3768 return msi_dialog_oncreate( hwnd, (LPCREATESTRUCTW)lParam ); 3769 3770 case WM_COMMAND: 3771 return msi_dialog_oncommand( dialog, wParam, (HWND)lParam ); 3772 3773 case WM_ACTIVATE: 3774 if( LOWORD(wParam) == WA_INACTIVE ) 3775 dialog->hWndFocus = GetFocus(); 3776 else 3777 dialog_setfocus( dialog ); 3778 return 0; 3779 3780 case WM_SETFOCUS: 3781 dialog_setfocus( dialog ); 3782 return 0; 3783 3784 /* bounce back to our subclassed static control */ 3785 case WM_CTLCOLORSTATIC: 3786 return SendMessageW( (HWND) lParam, WM_CTLCOLORSTATIC, wParam, lParam ); 3787 3788 case WM_DESTROY: 3789 dialog->hwnd = NULL; 3790 return 0; 3791 case WM_NOTIFY: 3792 return msi_dialog_onnotify( dialog, lParam ); 3793 } 3794 return DefWindowProcW(hwnd, msg, wParam, lParam); 3795 } 3796 3797 static void process_pending_messages( HWND hdlg ) 3798 { 3799 MSG msg; 3800 3801 while (PeekMessageW( &msg, 0, 0, 0, PM_REMOVE )) 3802 { 3803 if (hdlg && IsDialogMessageW( hdlg, &msg )) continue; 3804 TranslateMessage( &msg ); 3805 DispatchMessageW( &msg ); 3806 } 3807 } 3808 3809 static UINT dialog_run_message_loop( msi_dialog *dialog ) 3810 { 3811 DWORD style; 3812 HWND hwnd; 3813 3814 if( uiThreadId != GetCurrentThreadId() ) 3815 return SendMessageW( hMsiHiddenWindow, WM_MSI_DIALOG_CREATE, 0, (LPARAM) dialog ); 3816 3817 /* create the dialog window, don't show it yet */ 3818 style = WS_OVERLAPPED; 3819 if( dialog->attributes & msidbDialogAttributesVisible ) 3820 style |= WS_VISIBLE; 3821 3822 hwnd = CreateWindowW( szMsiDialogClass, dialog->name, style, 3823 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 3824 NULL, NULL, NULL, dialog ); 3825 if( !hwnd ) 3826 { 3827 ERR("Failed to create dialog %s\n", debugstr_w( dialog->name )); 3828 return ERROR_FUNCTION_FAILED; 3829 } 3830 3831 ShowWindow( hwnd, SW_SHOW ); 3832 /* UpdateWindow( hwnd ); - and causes the transparent static controls not to paint */ 3833 3834 if( dialog->attributes & msidbDialogAttributesModal ) 3835 { 3836 while( !dialog->finished ) 3837 { 3838 MsgWaitForMultipleObjects( 0, NULL, 0, INFINITE, QS_ALLINPUT ); 3839 process_pending_messages( dialog->hwnd ); 3840 } 3841 } 3842 else 3843 return ERROR_IO_PENDING; 3844 3845 return ERROR_SUCCESS; 3846 } 3847 3848 static LRESULT WINAPI MSIHiddenWindowProc( HWND hwnd, UINT msg, 3849 WPARAM wParam, LPARAM lParam ) 3850 { 3851 msi_dialog *dialog = (msi_dialog*) lParam; 3852 3853 TRACE("%d %p\n", msg, dialog); 3854 3855 switch (msg) 3856 { 3857 case WM_MSI_DIALOG_CREATE: 3858 return dialog_run_message_loop( dialog ); 3859 case WM_MSI_DIALOG_DESTROY: 3860 msi_dialog_destroy( dialog ); 3861 return 0; 3862 } 3863 return DefWindowProcW( hwnd, msg, wParam, lParam ); 3864 } 3865 3866 static BOOL dialog_register_class( void ) 3867 { 3868 WNDCLASSW cls; 3869 3870 ZeroMemory( &cls, sizeof cls ); 3871 cls.lpfnWndProc = MSIDialog_WndProc; 3872 cls.hInstance = NULL; 3873 cls.hIcon = LoadIconW(0, (LPWSTR)IDI_APPLICATION); 3874 cls.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW); 3875 cls.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1); 3876 cls.lpszMenuName = NULL; 3877 cls.lpszClassName = szMsiDialogClass; 3878 3879 if( !RegisterClassW( &cls ) ) 3880 return FALSE; 3881 3882 cls.lpfnWndProc = MSIHiddenWindowProc; 3883 cls.lpszClassName = szMsiHiddenWindow; 3884 3885 if( !RegisterClassW( &cls ) ) 3886 return FALSE; 3887 3888 uiThreadId = GetCurrentThreadId(); 3889 3890 hMsiHiddenWindow = CreateWindowW( szMsiHiddenWindow, NULL, WS_OVERLAPPED, 3891 0, 0, 100, 100, NULL, NULL, NULL, NULL ); 3892 if( !hMsiHiddenWindow ) 3893 return FALSE; 3894 3895 return TRUE; 3896 } 3897 3898 static msi_dialog *dialog_create( MSIPACKAGE *package, const WCHAR *name, msi_dialog *parent, 3899 control_event_handler event_handler ) 3900 { 3901 static const WCHAR szDialogCreated[] = 3902 {'D','i','a','l','o','g',' ','c','r','e','a','t','e','d',0}; 3903 MSIRECORD *rec = NULL; 3904 msi_dialog *dialog; 3905 3906 TRACE("%s\n", debugstr_w(name)); 3907 3908 if (!hMsiHiddenWindow) dialog_register_class(); 3909 3910 /* allocate the structure for the dialog to use */ 3911 dialog = msi_alloc_zero( FIELD_OFFSET( msi_dialog, name[strlenW( name ) + 1] )); 3912 if( !dialog ) 3913 return NULL; 3914 strcpyW( dialog->name, name ); 3915 dialog->parent = parent; 3916 dialog->package = package; 3917 dialog->event_handler = event_handler; 3918 dialog->finished = 0; 3919 list_init( &dialog->controls ); 3920 list_init( &dialog->fonts ); 3921 3922 /* verify that the dialog exists */ 3923 rec = msi_get_dialog_record( dialog ); 3924 if( !rec ) 3925 { 3926 msi_free( dialog ); 3927 return NULL; 3928 } 3929 dialog->attributes = MSI_RecordGetInteger( rec, 6 ); 3930 dialog->control_default = strdupW( MSI_RecordGetString( rec, 9 ) ); 3931 dialog->control_cancel = strdupW( MSI_RecordGetString( rec, 10 ) ); 3932 msiobj_release( &rec->hdr ); 3933 3934 rec = MSI_CreateRecord(2); 3935 if (!rec) return NULL; 3936 MSI_RecordSetStringW(rec, 1, name); 3937 MSI_RecordSetStringW(rec, 2, szDialogCreated); 3938 MSI_ProcessMessage(package, INSTALLMESSAGE_ACTIONSTART, rec); 3939 msiobj_release(&rec->hdr); 3940 3941 return dialog; 3942 } 3943 3944 static void msi_dialog_end_dialog( msi_dialog *dialog ) 3945 { 3946 TRACE("%p\n", dialog); 3947 dialog->finished = 1; 3948 PostMessageW(dialog->hwnd, WM_NULL, 0, 0); 3949 } 3950 3951 void msi_dialog_check_messages( HANDLE handle ) 3952 { 3953 DWORD r; 3954 3955 /* in threads other than the UI thread, block */ 3956 if( uiThreadId != GetCurrentThreadId() ) 3957 { 3958 if (!handle) return; 3959 while (MsgWaitForMultipleObjectsEx( 1, &handle, INFINITE, QS_ALLINPUT, 0 ) == WAIT_OBJECT_0 + 1) 3960 { 3961 MSG msg; 3962 while (PeekMessageW( &msg, NULL, 0, 0, PM_REMOVE )) 3963 { 3964 TranslateMessage( &msg ); 3965 DispatchMessageW( &msg ); 3966 } 3967 } 3968 return; 3969 } 3970 3971 /* there are two choices for the UI thread */ 3972 while (1) 3973 { 3974 process_pending_messages( NULL ); 3975 3976 if( !handle ) 3977 break; 3978 3979 /* 3980 * block here until somebody creates a new dialog or 3981 * the handle we're waiting on becomes ready 3982 */ 3983 r = MsgWaitForMultipleObjects( 1, &handle, 0, INFINITE, QS_ALLINPUT ); 3984 if( r == WAIT_OBJECT_0 ) 3985 break; 3986 } 3987 } 3988 3989 static void dialog_do_preview( msi_dialog *dialog ) 3990 { 3991 TRACE("\n"); 3992 dialog->attributes |= msidbDialogAttributesVisible; 3993 dialog->attributes &= ~msidbDialogAttributesModal; 3994 dialog_run_message_loop( dialog ); 3995 } 3996 3997 static void free_subscriber( struct subscriber *sub ) 3998 { 3999 msi_free( sub->event ); 4000 msi_free( sub->control ); 4001 msi_free( sub->attribute ); 4002 msi_free( sub ); 4003 } 4004 4005 static void event_cleanup_subscriptions( MSIPACKAGE *package, const WCHAR *dialog ) 4006 { 4007 struct list *item, *next; 4008 4009 LIST_FOR_EACH_SAFE( item, next, &package->subscriptions ) 4010 { 4011 struct subscriber *sub = LIST_ENTRY( item, struct subscriber, entry ); 4012 4013 if (strcmpW( sub->dialog->name, dialog )) continue; 4014 list_remove( &sub->entry ); 4015 free_subscriber( sub ); 4016 } 4017 } 4018 4019 void msi_dialog_destroy( msi_dialog *dialog ) 4020 { 4021 msi_font *font, *next; 4022 4023 if( uiThreadId != GetCurrentThreadId() ) 4024 { 4025 SendMessageW( hMsiHiddenWindow, WM_MSI_DIALOG_DESTROY, 0, (LPARAM) dialog ); 4026 return; 4027 } 4028 4029 if( dialog->hwnd ) 4030 ShowWindow( dialog->hwnd, SW_HIDE ); 4031 4032 if( dialog->hwnd ) 4033 DestroyWindow( dialog->hwnd ); 4034 4035 /* unsubscribe events */ 4036 event_cleanup_subscriptions( dialog->package, dialog->name ); 4037 4038 /* destroy the list of controls */ 4039 while( !list_empty( &dialog->controls ) ) 4040 { 4041 msi_control *t; 4042 4043 t = LIST_ENTRY( list_head( &dialog->controls ), 4044 msi_control, entry ); 4045 msi_destroy_control( t ); 4046 } 4047 4048 /* destroy the list of fonts */ 4049 LIST_FOR_EACH_ENTRY_SAFE( font, next, &dialog->fonts, msi_font, entry ) 4050 { 4051 list_remove( &font->entry ); 4052 DeleteObject( font->hfont ); 4053 msi_free( font ); 4054 } 4055 msi_free( dialog->default_font ); 4056 4057 msi_free( dialog->control_default ); 4058 msi_free( dialog->control_cancel ); 4059 dialog->package = NULL; 4060 msi_free( dialog ); 4061 } 4062 4063 void msi_dialog_unregister_class( void ) 4064 { 4065 DestroyWindow( hMsiHiddenWindow ); 4066 hMsiHiddenWindow = NULL; 4067 UnregisterClassW( szMsiDialogClass, NULL ); 4068 UnregisterClassW( szMsiHiddenWindow, NULL ); 4069 uiThreadId = 0; 4070 } 4071 4072 void msi_event_cleanup_all_subscriptions( MSIPACKAGE *package ) 4073 { 4074 struct list *item, *next; 4075 4076 LIST_FOR_EACH_SAFE( item, next, &package->subscriptions ) 4077 { 4078 struct subscriber *sub = LIST_ENTRY( item, struct subscriber, entry ); 4079 list_remove( &sub->entry ); 4080 free_subscriber( sub ); 4081 } 4082 } 4083 4084 static void MSI_ClosePreview( MSIOBJECTHDR *arg ) 4085 { 4086 MSIPREVIEW *preview = (MSIPREVIEW *)arg; 4087 msiobj_release( &preview->package->hdr ); 4088 } 4089 4090 static MSIPREVIEW *MSI_EnableUIPreview( MSIDATABASE *db ) 4091 { 4092 MSIPREVIEW *preview = NULL; 4093 MSIPACKAGE *package; 4094 4095 package = MSI_CreatePackage( db, NULL ); 4096 if (package) 4097 { 4098 preview = alloc_msiobject( MSIHANDLETYPE_PREVIEW, sizeof(MSIPREVIEW), MSI_ClosePreview ); 4099 if (preview) 4100 { 4101 preview->package = package; 4102 msiobj_addref( &package->hdr ); 4103 } 4104 msiobj_release( &package->hdr ); 4105 } 4106 return preview; 4107 } 4108 4109 UINT WINAPI MsiEnableUIPreview( MSIHANDLE hdb, MSIHANDLE *phPreview ) 4110 { 4111 MSIDATABASE *db; 4112 MSIPREVIEW *preview; 4113 UINT r = ERROR_FUNCTION_FAILED; 4114 4115 TRACE("%d %p\n", hdb, phPreview); 4116 4117 db = msihandle2msiinfo( hdb, MSIHANDLETYPE_DATABASE ); 4118 if (!db) 4119 { 4120 IWineMsiRemoteDatabase *remote_database; 4121 4122 remote_database = (IWineMsiRemoteDatabase *)msi_get_remote( hdb ); 4123 if (!remote_database) 4124 return ERROR_INVALID_HANDLE; 4125 4126 *phPreview = 0; 4127 4128 IWineMsiRemoteDatabase_Release( remote_database ); 4129 WARN("MsiEnableUIPreview not allowed during a custom action!\n"); 4130 4131 return ERROR_FUNCTION_FAILED; 4132 } 4133 preview = MSI_EnableUIPreview( db ); 4134 if (preview) 4135 { 4136 *phPreview = alloc_msihandle( &preview->hdr ); 4137 msiobj_release( &preview->hdr ); 4138 r = ERROR_SUCCESS; 4139 if (!*phPreview) 4140 r = ERROR_NOT_ENOUGH_MEMORY; 4141 } 4142 msiobj_release( &db->hdr ); 4143 return r; 4144 } 4145 4146 static UINT preview_event_handler( msi_dialog *dialog, const WCHAR *event, const WCHAR *argument ) 4147 { 4148 MESSAGE("Preview dialog event '%s' (arg='%s')\n", debugstr_w(event), debugstr_w(argument)); 4149 return ERROR_SUCCESS; 4150 } 4151 4152 static UINT MSI_PreviewDialogW( MSIPREVIEW *preview, LPCWSTR szDialogName ) 4153 { 4154 msi_dialog *dialog = NULL; 4155 UINT r = ERROR_SUCCESS; 4156 4157 if (preview->dialog) 4158 msi_dialog_destroy( preview->dialog ); 4159 4160 /* an empty name means we should just destroy the current preview dialog */ 4161 if (szDialogName) 4162 { 4163 dialog = dialog_create( preview->package, szDialogName, NULL, preview_event_handler ); 4164 if (dialog) 4165 dialog_do_preview( dialog ); 4166 else 4167 r = ERROR_FUNCTION_FAILED; 4168 } 4169 preview->dialog = dialog; 4170 return r; 4171 } 4172 4173 UINT WINAPI MsiPreviewDialogW( MSIHANDLE hPreview, LPCWSTR szDialogName ) 4174 { 4175 MSIPREVIEW *preview; 4176 UINT r; 4177 4178 TRACE("%d %s\n", hPreview, debugstr_w(szDialogName)); 4179 4180 preview = msihandle2msiinfo( hPreview, MSIHANDLETYPE_PREVIEW ); 4181 if (!preview) 4182 return ERROR_INVALID_HANDLE; 4183 4184 r = MSI_PreviewDialogW( preview, szDialogName ); 4185 msiobj_release( &preview->hdr ); 4186 return r; 4187 } 4188 4189 UINT WINAPI MsiPreviewDialogA( MSIHANDLE hPreview, LPCSTR szDialogName ) 4190 { 4191 UINT r; 4192 LPWSTR strW = NULL; 4193 4194 TRACE("%d %s\n", hPreview, debugstr_a(szDialogName)); 4195 4196 if (szDialogName) 4197 { 4198 strW = strdupAtoW( szDialogName ); 4199 if (!strW) 4200 return ERROR_OUTOFMEMORY; 4201 } 4202 r = MsiPreviewDialogW( hPreview, strW ); 4203 msi_free( strW ); 4204 return r; 4205 } 4206 4207 UINT WINAPI MsiPreviewBillboardW( MSIHANDLE hPreview, LPCWSTR szControlName, LPCWSTR szBillboard ) 4208 { 4209 FIXME("%d %s %s\n", hPreview, debugstr_w(szControlName), debugstr_w(szBillboard)); 4210 return ERROR_CALL_NOT_IMPLEMENTED; 4211 } 4212 4213 UINT WINAPI MsiPreviewBillboardA( MSIHANDLE hPreview, LPCSTR szControlName, LPCSTR szBillboard ) 4214 { 4215 FIXME("%d %s %s\n", hPreview, debugstr_a(szControlName), debugstr_a(szBillboard)); 4216 return ERROR_CALL_NOT_IMPLEMENTED; 4217 } 4218 4219 struct control_event 4220 { 4221 const WCHAR *event; 4222 event_handler handler; 4223 }; 4224 4225 static UINT dialog_event_handler( msi_dialog *, const WCHAR *, const WCHAR * ); 4226 4227 /* create a dialog box and run it if it's modal */ 4228 static INT event_do_dialog( MSIPACKAGE *package, const WCHAR *name, msi_dialog *parent, BOOL destroy_modeless ) 4229 { 4230 msi_dialog *dialog; 4231 UINT r; 4232 INT retval; 4233 4234 /* create a new dialog */ 4235 dialog = dialog_create( package, name, parent, dialog_event_handler ); 4236 if (dialog) 4237 { 4238 /* kill the current modeless dialog */ 4239 if (destroy_modeless && package->dialog) 4240 { 4241 msi_dialog_destroy( package->dialog ); 4242 package->dialog = NULL; 4243 } 4244 4245 /* modeless dialogs return an error message */ 4246 r = dialog_run_message_loop( dialog ); 4247 if (r == ERROR_SUCCESS) 4248 { 4249 retval = dialog->retval; 4250 msi_dialog_destroy( dialog ); 4251 return retval; 4252 } 4253 else 4254 { 4255 package->dialog = dialog; 4256 return IDOK; 4257 } 4258 } 4259 else return 0; 4260 } 4261 4262 /* end a modal dialog box */ 4263 static UINT event_end_dialog( msi_dialog *dialog, const WCHAR *argument ) 4264 { 4265 static const WCHAR exitW[] = {'E','x','i','t',0}; 4266 static const WCHAR retryW[] = {'R','e','t','r','y',0}; 4267 static const WCHAR ignoreW[] = {'I','g','n','o','r','e',0}; 4268 static const WCHAR returnW[] = {'R','e','t','u','r','n',0}; 4269 4270 if (!strcmpW( argument, exitW )) 4271 dialog->retval = IDCANCEL; 4272 else if (!strcmpW( argument, retryW )) 4273 dialog->retval = IDRETRY; 4274 else if (!strcmpW( argument, ignoreW )) 4275 dialog->retval = IDOK; 4276 else if (!strcmpW( argument, returnW )) 4277 dialog->retval = 0; 4278 else 4279 { 4280 ERR("Unknown argument string %s\n", debugstr_w(argument)); 4281 dialog->retval = IDABORT; 4282 } 4283 event_cleanup_subscriptions( dialog->package, dialog->name ); 4284 msi_dialog_end_dialog( dialog ); 4285 return ERROR_SUCCESS; 4286 } 4287 4288 static UINT pending_event_end_dialog( msi_dialog *dialog, const WCHAR *argument ) 4289 { 4290 dialog->pending_event = event_end_dialog; 4291 if (dialog->pending_argument) msi_free( dialog->pending_argument ); 4292 dialog->pending_argument = strdupW( argument ); 4293 return ERROR_SUCCESS; 4294 } 4295 4296 /* transition from one modal dialog to another modal dialog */ 4297 static UINT event_new_dialog( msi_dialog *dialog, const WCHAR *argument ) 4298 { 4299 /* store the name of the next dialog, and signal this one to end */ 4300 dialog->package->next_dialog = strdupW( argument ); 4301 msi_event_cleanup_all_subscriptions( dialog->package ); 4302 msi_dialog_end_dialog( dialog ); 4303 return ERROR_SUCCESS; 4304 } 4305 4306 static UINT pending_event_new_dialog( msi_dialog *dialog, const WCHAR *argument ) 4307 { 4308 dialog->pending_event = event_new_dialog; 4309 if (dialog->pending_argument) msi_free( dialog->pending_argument ); 4310 dialog->pending_argument = strdupW( argument ); 4311 return ERROR_SUCCESS; 4312 } 4313 4314 /* create a new child dialog of an existing modal dialog */ 4315 static UINT event_spawn_dialog( msi_dialog *dialog, const WCHAR *argument ) 4316 { 4317 INT r; 4318 /* don't destroy a modeless dialogs that might be our parent */ 4319 r = event_do_dialog( dialog->package, argument, dialog, FALSE ); 4320 if (r != 0) 4321 { 4322 dialog->retval = r; 4323 msi_dialog_end_dialog( dialog ); 4324 } 4325 else 4326 msi_dialog_update_all_controls(dialog); 4327 4328 return ERROR_SUCCESS; 4329 } 4330 4331 static UINT pending_event_spawn_dialog( msi_dialog *dialog, const WCHAR *argument ) 4332 { 4333 dialog->pending_event = event_spawn_dialog; 4334 if (dialog->pending_argument) msi_free( dialog->pending_argument ); 4335 dialog->pending_argument = strdupW( argument ); 4336 return ERROR_SUCCESS; 4337 } 4338 4339 /* creates a dialog that remains up for a period of time based on a condition */ 4340 static UINT event_spawn_wait_dialog( msi_dialog *dialog, const WCHAR *argument ) 4341 { 4342 FIXME("doing nothing\n"); 4343 return ERROR_SUCCESS; 4344 } 4345 4346 static UINT event_do_action( msi_dialog *dialog, const WCHAR *argument ) 4347 { 4348 ACTION_PerformAction( dialog->package, argument, SCRIPT_NONE ); 4349 return ERROR_SUCCESS; 4350 } 4351 4352 static UINT event_add_local( msi_dialog *dialog, const WCHAR *argument ) 4353 { 4354 MSIFEATURE *feature; 4355 4356 LIST_FOR_EACH_ENTRY( feature, &dialog->package->features, MSIFEATURE, entry ) 4357 { 4358 if (!strcmpW( argument, feature->Feature ) || !strcmpW( argument, szAll )) 4359 { 4360 if (feature->ActionRequest != INSTALLSTATE_LOCAL) 4361 msi_set_property( dialog->package->db, szPreselected, szOne, -1 ); 4362 MSI_SetFeatureStateW( dialog->package, feature->Feature, INSTALLSTATE_LOCAL ); 4363 } 4364 } 4365 return ERROR_SUCCESS; 4366 } 4367 4368 static UINT event_remove( msi_dialog *dialog, const WCHAR *argument ) 4369 { 4370 MSIFEATURE *feature; 4371 4372 LIST_FOR_EACH_ENTRY( feature, &dialog->package->features, MSIFEATURE, entry ) 4373 { 4374 if (!strcmpW( argument, feature->Feature ) || !strcmpW( argument, szAll )) 4375 { 4376 if (feature->ActionRequest != INSTALLSTATE_ABSENT) 4377 msi_set_property( dialog->package->db, szPreselected, szOne, -1 ); 4378 MSI_SetFeatureStateW( dialog->package, feature->Feature, INSTALLSTATE_ABSENT ); 4379 } 4380 } 4381 return ERROR_SUCCESS; 4382 } 4383 4384 static UINT event_add_source( msi_dialog *dialog, const WCHAR *argument ) 4385 { 4386 MSIFEATURE *feature; 4387 4388 LIST_FOR_EACH_ENTRY( feature, &dialog->package->features, MSIFEATURE, entry ) 4389 { 4390 if (!strcmpW( argument, feature->Feature ) || !strcmpW( argument, szAll )) 4391 { 4392 if (feature->ActionRequest != INSTALLSTATE_SOURCE) 4393 msi_set_property( dialog->package->db, szPreselected, szOne, -1 ); 4394 MSI_SetFeatureStateW( dialog->package, feature->Feature, INSTALLSTATE_SOURCE ); 4395 } 4396 } 4397 return ERROR_SUCCESS; 4398 } 4399 4400 void msi_event_fire( MSIPACKAGE *package, const WCHAR *event, MSIRECORD *rec ) 4401 { 4402 struct subscriber *sub; 4403 4404 TRACE("firing event %s\n", debugstr_w(event)); 4405 4406 LIST_FOR_EACH_ENTRY( sub, &package->subscriptions, struct subscriber, entry ) 4407 { 4408 if (strcmpiW( sub->event, event )) continue; 4409 dialog_handle_event( sub->dialog, sub->control, sub->attribute, rec ); 4410 } 4411 } 4412 4413 static UINT event_set_target_path( msi_dialog *dialog, const WCHAR *argument ) 4414 { 4415 WCHAR *path = msi_dup_property( dialog->package->db, argument ); 4416 MSIRECORD *rec = MSI_CreateRecord( 1 ); 4417 UINT r = ERROR_SUCCESS; 4418 4419 MSI_RecordSetStringW( rec, 1, path ); 4420 msi_event_fire( dialog->package, szSelectionPath, rec ); 4421 if (path) 4422 { 4423 /* failure to set the path halts the executing of control events */ 4424 r = MSI_SetTargetPathW( dialog->package, argument, path ); 4425 msi_free( path ); 4426 } 4427 msi_free( &rec->hdr ); 4428 return r; 4429 } 4430 4431 static UINT event_reset( msi_dialog *dialog, const WCHAR *argument ) 4432 { 4433 msi_dialog_reset( dialog ); 4434 return ERROR_SUCCESS; 4435 } 4436 4437 INT ACTION_ShowDialog( MSIPACKAGE *package, const WCHAR *dialog ) 4438 { 4439 static const WCHAR szDialog[] = {'D','i','a','l','o','g',0}; 4440 MSIRECORD *row; 4441 INT rc; 4442 4443 if (!TABLE_Exists(package->db, szDialog)) return 0; 4444 4445 row = MSI_CreateRecord(0); 4446 if (!row) return -1; 4447 MSI_RecordSetStringW(row, 0, dialog); 4448 rc = MSI_ProcessMessage(package, INSTALLMESSAGE_SHOWDIALOG, row); 4449 msiobj_release(&row->hdr); 4450 4451 if (rc == -2) rc = 0; 4452 4453 if (!rc) 4454 { 4455 MSIRECORD *row = MSI_CreateRecord(2); 4456 if (!row) return -1; 4457 MSI_RecordSetInteger(row, 1, 2726); 4458 MSI_RecordSetStringW(row, 2, dialog); 4459 MSI_ProcessMessage(package, INSTALLMESSAGE_INFO, row); 4460 4461 msiobj_release(&row->hdr); 4462 } 4463 return rc; 4464 } 4465 4466 INT ACTION_DialogBox( MSIPACKAGE *package, const WCHAR *dialog ) 4467 { 4468 INT r; 4469 4470 if (package->next_dialog) ERR("Already got next dialog... ignoring it\n"); 4471 package->next_dialog = NULL; 4472 4473 /* Dialogs are chained through NewDialog, which sets the next_dialog member. 4474 * We fall out of the loop if we reach a modeless dialog, which immediately 4475 * returns IDOK, or an EndDialog event, which returns the value corresponding 4476 * to its argument. 4477 */ 4478 r = event_do_dialog( package, dialog, NULL, TRUE ); 4479 while (package->next_dialog) 4480 { 4481 WCHAR *name = package->next_dialog; 4482 4483 package->next_dialog = NULL; 4484 r = event_do_dialog( package, name, NULL, TRUE ); 4485 msi_free( name ); 4486 } 4487 return r; 4488 } 4489 4490 static UINT event_set_install_level( msi_dialog *dialog, const WCHAR *argument ) 4491 { 4492 int level = atolW( argument ); 4493 4494 TRACE("setting install level to %d\n", level); 4495 return MSI_SetInstallLevel( dialog->package, level ); 4496 } 4497 4498 static UINT event_directory_list_up( msi_dialog *dialog, const WCHAR *argument ) 4499 { 4500 return msi_dialog_directorylist_up( dialog ); 4501 } 4502 4503 static UINT event_reinstall_mode( msi_dialog *dialog, const WCHAR *argument ) 4504 { 4505 return msi_set_property( dialog->package->db, szReinstallMode, argument, -1 ); 4506 } 4507 4508 static UINT event_reinstall( msi_dialog *dialog, const WCHAR *argument ) 4509 { 4510 return msi_set_property( dialog->package->db, szReinstall, argument, -1 ); 4511 } 4512 4513 static UINT event_validate_product_id( msi_dialog *dialog, const WCHAR *argument ) 4514 { 4515 return msi_validate_product_id( dialog->package ); 4516 } 4517 4518 static const WCHAR end_dialogW[] = {'E','n','d','D','i','a','l','o','g',0}; 4519 static const WCHAR new_dialogW[] = {'N','e','w','D','i','a','l','o','g',0}; 4520 static const WCHAR spawn_dialogW[] = {'S','p','a','w','n','D','i','a','l','o','g',0}; 4521 static const WCHAR spawn_wait_dialogW[] = {'S','p','a','w','n','W','a','i','t','D','i','a','l','o','g',0}; 4522 static const WCHAR do_actionW[] = {'D','o','A','c','t','i','o','n',0}; 4523 static const WCHAR add_localW[] = {'A','d','d','L','o','c','a','l',0}; 4524 static const WCHAR removeW[] = {'R','e','m','o','v','e',0}; 4525 static const WCHAR add_sourceW[] = {'A','d','d','S','o','u','r','c','e',0}; 4526 static const WCHAR set_target_pathW[] = {'S','e','t','T','a','r','g','e','t','P','a','t','h',0}; 4527 static const WCHAR resetW[] = {'R','e','s','e','t',0}; 4528 static const WCHAR set_install_levelW[] = {'S','e','t','I','n','s','t','a','l','l','L','e','v','e','l',0}; 4529 static const WCHAR directory_list_upW[] = {'D','i','r','e','c','t','o','r','y','L','i','s','t','U','p',0}; 4530 static const WCHAR selection_browseW[] = {'S','e','l','e','c','t','i','o','n','B','r','o','w','s','e',0}; 4531 static const WCHAR reinstall_modeW[] = {'R','e','i','n','s','t','a','l','l','M','o','d','e',0}; 4532 static const WCHAR reinstallW[] = {'R','e','i','n','s','t','a','l','l',0}; 4533 static const WCHAR validate_product_idW[] = {'V','a','l','i','d','a','t','e','P','r','o','d','u','c','t','I','D',0}; 4534 4535 static const struct control_event control_events[] = 4536 { 4537 { end_dialogW, pending_event_end_dialog }, 4538 { new_dialogW, pending_event_new_dialog }, 4539 { spawn_dialogW, pending_event_spawn_dialog }, 4540 { spawn_wait_dialogW, event_spawn_wait_dialog }, 4541 { do_actionW, event_do_action }, 4542 { add_localW, event_add_local }, 4543 { removeW, event_remove }, 4544 { add_sourceW, event_add_source }, 4545 { set_target_pathW, event_set_target_path }, 4546 { resetW, event_reset }, 4547 { set_install_levelW, event_set_install_level }, 4548 { directory_list_upW, event_directory_list_up }, 4549 { selection_browseW, event_spawn_dialog }, 4550 { reinstall_modeW, event_reinstall_mode }, 4551 { reinstallW, event_reinstall }, 4552 { validate_product_idW, event_validate_product_id }, 4553 { NULL, NULL } 4554 }; 4555 4556 static UINT dialog_event_handler( msi_dialog *dialog, const WCHAR *event, const WCHAR *argument ) 4557 { 4558 unsigned int i; 4559 4560 TRACE("handling event %s\n", debugstr_w(event)); 4561 4562 if (!event) return ERROR_SUCCESS; 4563 4564 for (i = 0; control_events[i].event; i++) 4565 { 4566 if (!strcmpW( control_events[i].event, event )) 4567 return control_events[i].handler( dialog, argument ); 4568 } 4569 FIXME("unhandled event %s arg(%s)\n", debugstr_w(event), debugstr_w(argument)); 4570 return ERROR_SUCCESS; 4571 } 4572