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