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