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) 2947 { 2948 *(ptr - 1) = '\0'; 2949 PathAddBackslashW( path ); 2950 } 2951 2952 msi_dialog_set_property( dialog->package, prop, path ); 2953 2954 msi_dialog_update_directory_list( dialog, NULL ); 2955 msi_dialog_update_directory_combo( dialog, NULL ); 2956 msi_dialog_update_pathedit( dialog, NULL ); 2957 2958 msi_free( path ); 2959 msi_free( prop ); 2960 2961 return ERROR_SUCCESS; 2962 } 2963 2964 static WCHAR *get_unique_folder_name( const WCHAR *root, int *ret_len ) 2965 { 2966 WCHAR newfolder[MAX_PATH], *path, *ptr; 2967 int len, count = 2; 2968 2969 len = LoadStringW( msi_hInstance, IDS_NEWFOLDER, newfolder, ARRAY_SIZE(newfolder) ); 2970 len += lstrlenW(root) + 1; 2971 if (!(path = msi_alloc( (len + 4) * sizeof(WCHAR) ))) return NULL; 2972 lstrcpyW( path, root ); 2973 lstrcatW( path, newfolder ); 2974 2975 for (;;) 2976 { 2977 if (GetFileAttributesW( path ) == INVALID_FILE_ATTRIBUTES) break; 2978 if (count > 99) 2979 { 2980 msi_free( path ); 2981 return NULL; 2982 } 2983 swprintf( path, len + 4, L"%s%s %u", root, newfolder, count++ ); 2984 } 2985 2986 ptr = wcsrchr( path, '\\' ) + 1; 2987 *ret_len = lstrlenW(ptr); 2988 memmove( path, ptr, *ret_len * sizeof(WCHAR) ); 2989 return path; 2990 } 2991 2992 static UINT msi_dialog_directorylist_new( msi_dialog *dialog ) 2993 { 2994 msi_control *control; 2995 WCHAR *path; 2996 LVITEMW item; 2997 int index; 2998 2999 control = msi_dialog_find_control_by_type( dialog, L"DirectoryList" ); 3000 3001 if (!(path = get_path_property( dialog, control ))) return ERROR_OUTOFMEMORY; 3002 3003 item.mask = LVIF_TEXT; 3004 item.iItem = 0; 3005 item.iSubItem = 0; 3006 item.pszText = get_unique_folder_name( path, &item.cchTextMax ); 3007 3008 index = SendMessageW( control->hwnd, LVM_INSERTITEMW, 0, (LPARAM)&item ); 3009 SendMessageW( control->hwnd, LVM_ENSUREVISIBLE, index, 0 ); 3010 SendMessageW( control->hwnd, LVM_EDITLABELW, index, -1 ); 3011 3012 msi_free( path ); 3013 msi_free( item.pszText ); 3014 return ERROR_SUCCESS; 3015 } 3016 3017 static UINT msi_dialog_dirlist_handler( msi_dialog *dialog, msi_control *control, WPARAM param ) 3018 { 3019 NMHDR *nmhdr = (NMHDR *)param; 3020 WCHAR text[MAX_PATH], *new_path, *path, *prop; 3021 BOOL indirect; 3022 3023 switch (nmhdr->code) 3024 { 3025 case LVN_ENDLABELEDITW: 3026 { 3027 NMLVDISPINFOW *info = (NMLVDISPINFOW *)param; 3028 if (!info->item.pszText) return ERROR_SUCCESS; 3029 lstrcpynW( text, info->item.pszText, ARRAY_SIZE(text) ); 3030 text[ARRAY_SIZE(text) - 1] = 0; 3031 break; 3032 } 3033 case LVN_ITEMACTIVATE: 3034 { 3035 LVITEMW item; 3036 int index = SendMessageW( control->hwnd, LVM_GETNEXTITEM, -1, LVNI_SELECTED ); 3037 if (index < 0) 3038 { 3039 ERR("no list-view item selected\n"); 3040 return ERROR_FUNCTION_FAILED; 3041 } 3042 3043 item.iSubItem = 0; 3044 item.pszText = text; 3045 item.cchTextMax = MAX_PATH; 3046 SendMessageW( control->hwnd, LVM_GETITEMTEXTW, index, (LPARAM)&item ); 3047 text[ARRAY_SIZE(text) - 1] = 0; 3048 break; 3049 } 3050 default: 3051 return ERROR_SUCCESS; 3052 } 3053 3054 indirect = control->attributes & msidbControlAttributesIndirect; 3055 prop = msi_dialog_dup_property( dialog, control->property, indirect ); 3056 path = msi_dialog_dup_property( dialog, prop, TRUE ); 3057 3058 if (!(new_path = msi_alloc( (lstrlenW(path) + lstrlenW(text) + 2) * sizeof(WCHAR) ))) 3059 { 3060 msi_free( prop ); 3061 msi_free( path ); 3062 return ERROR_OUTOFMEMORY; 3063 } 3064 lstrcpyW( new_path, path ); 3065 lstrcatW( new_path, text ); 3066 if (nmhdr->code == LVN_ENDLABELEDITW) CreateDirectoryW( new_path, NULL ); 3067 lstrcatW( new_path, L"\\" ); 3068 3069 msi_dialog_set_property( dialog->package, prop, new_path ); 3070 3071 msi_dialog_update_directory_list( dialog, NULL ); 3072 msi_dialog_update_directory_combo( dialog, NULL ); 3073 msi_dialog_update_pathedit( dialog, NULL ); 3074 3075 msi_free( prop ); 3076 msi_free( path ); 3077 msi_free( new_path ); 3078 3079 return ERROR_SUCCESS; 3080 } 3081 3082 static UINT msi_dialog_directory_list( msi_dialog *dialog, MSIRECORD *rec ) 3083 { 3084 msi_control *control; 3085 LPCWSTR prop; 3086 DWORD style; 3087 3088 style = LVS_LIST | WS_VSCROLL | LVS_SHAREIMAGELISTS | LVS_EDITLABELS | 3089 LVS_AUTOARRANGE | LVS_SINGLESEL | WS_BORDER | 3090 LVS_SORTASCENDING | WS_CHILD | WS_GROUP | WS_TABSTOP; 3091 control = msi_dialog_add_control( dialog, rec, WC_LISTVIEWW, style ); 3092 if (!control) 3093 return ERROR_FUNCTION_FAILED; 3094 3095 control->attributes = MSI_RecordGetInteger( rec, 8 ); 3096 control->handler = msi_dialog_dirlist_handler; 3097 prop = MSI_RecordGetString( rec, 9 ); 3098 control->property = msi_dialog_dup_property( dialog, prop, FALSE ); 3099 3100 /* double click to activate an item in the list */ 3101 SendMessageW( control->hwnd, LVM_SETEXTENDEDLISTVIEWSTYLE, 3102 0, LVS_EX_TWOCLICKACTIVATE ); 3103 3104 msi_dialog_update_directory_list( dialog, control ); 3105 3106 return ERROR_SUCCESS; 3107 } 3108 3109 /******************** VolumeCost List ***************************************/ 3110 3111 static BOOL str_is_number( LPCWSTR str ) 3112 { 3113 int i; 3114 3115 for (i = 0; i < lstrlenW( str ); i++) 3116 if (!iswdigit(str[i])) 3117 return FALSE; 3118 3119 return TRUE; 3120 } 3121 3122 static const WCHAR column_keys[][80] = 3123 { 3124 L"VolumeCostVolume", 3125 L"VolumeCostSize", 3126 L"VolumeCostAvailable", 3127 L"VolumeCostRequired", 3128 L"VolumeCostDifference", 3129 }; 3130 3131 static void msi_dialog_vcl_add_columns( msi_dialog *dialog, msi_control *control, MSIRECORD *rec ) 3132 { 3133 LPCWSTR text = MSI_RecordGetString( rec, 10 ); 3134 LPCWSTR begin = text, end; 3135 WCHAR *num; 3136 LVCOLUMNW lvc; 3137 DWORD count = 0; 3138 3139 if (!text) return; 3140 3141 while ((begin = wcschr( begin, '{' )) && count < 5) 3142 { 3143 if (!(end = wcschr( begin, '}' ))) 3144 return; 3145 3146 num = msi_alloc( (end-begin+1)*sizeof(WCHAR) ); 3147 if (!num) 3148 return; 3149 3150 lstrcpynW( num, begin + 1, end - begin ); 3151 begin += end - begin + 1; 3152 3153 /* empty braces or '0' hides the column */ 3154 if ( !num[0] || !wcscmp( num, L"0" ) ) 3155 { 3156 count++; 3157 msi_free( num ); 3158 continue; 3159 } 3160 3161 /* the width must be a positive number 3162 * if a width is invalid, all remaining columns are hidden 3163 */ 3164 if ( !wcsncmp( num, L"-", 1 ) || !str_is_number( num ) ) { 3165 #ifdef __REACTOS__ 3166 // Skip in case of prefix the string of displayed characters with {\style} or {&style}. 3167 if (count == 0 && (!wcsncmp(num, L"\\", 1) || !wcsncmp(num, L"&", 1))) 3168 { 3169 FIXME("Style prefix not supported\n"); 3170 msi_free(num); 3171 continue; 3172 } 3173 #endif 3174 msi_free( num ); 3175 return; 3176 } 3177 3178 ZeroMemory( &lvc, sizeof(lvc) ); 3179 lvc.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM; 3180 lvc.cx = wcstol( num, NULL, 10 ); 3181 lvc.pszText = msi_dialog_get_uitext( dialog, column_keys[count] ); 3182 3183 SendMessageW( control->hwnd, LVM_INSERTCOLUMNW, count++, (LPARAM)&lvc ); 3184 msi_free( lvc.pszText ); 3185 msi_free( num ); 3186 } 3187 } 3188 3189 static LONGLONG msi_vcl_get_cost( msi_dialog *dialog ) 3190 { 3191 MSIFEATURE *feature; 3192 INT each_cost; 3193 LONGLONG total_cost = 0; 3194 3195 LIST_FOR_EACH_ENTRY( feature, &dialog->package->features, MSIFEATURE, entry ) 3196 { 3197 if (ERROR_SUCCESS == (MSI_GetFeatureCost(dialog->package, feature, 3198 MSICOSTTREE_SELFONLY, INSTALLSTATE_LOCAL, &each_cost))) 3199 { 3200 /* each_cost is in 512-byte units */ 3201 total_cost += ((LONGLONG)each_cost) * 512; 3202 } 3203 if (ERROR_SUCCESS == (MSI_GetFeatureCost(dialog->package, feature, 3204 MSICOSTTREE_SELFONLY, INSTALLSTATE_ABSENT, &each_cost))) 3205 { 3206 /* each_cost is in 512-byte units */ 3207 total_cost -= ((LONGLONG)each_cost) * 512; 3208 } 3209 } 3210 return total_cost; 3211 } 3212 3213 static void msi_dialog_vcl_add_drives( msi_dialog *dialog, msi_control *control ) 3214 { 3215 ULARGE_INTEGER total, free; 3216 LONGLONG difference, cost; 3217 WCHAR size_text[MAX_PATH]; 3218 WCHAR cost_text[MAX_PATH]; 3219 LPWSTR drives, ptr; 3220 LVITEMW lvitem; 3221 #ifdef __REACTOS__ 3222 DWORD size; 3223 #else 3224 DWORD size, flags; 3225 #endif 3226 int i = 0; 3227 3228 cost = msi_vcl_get_cost(dialog); 3229 StrFormatByteSizeW(cost, cost_text, MAX_PATH); 3230 3231 size = GetLogicalDriveStringsW( 0, NULL ); 3232 if ( !size ) return; 3233 3234 drives = msi_alloc( (size + 1) * sizeof(WCHAR) ); 3235 if ( !drives ) return; 3236 3237 GetLogicalDriveStringsW( size, drives ); 3238 3239 ptr = drives; 3240 while (*ptr) 3241 { 3242 #ifdef __REACTOS__ 3243 if (GetDriveTypeW(ptr) != DRIVE_FIXED) 3244 #else 3245 if (GetVolumeInformationW(ptr, NULL, 0, NULL, 0, &flags, NULL, 0) && 3246 flags & FILE_READ_ONLY_VOLUME) 3247 #endif 3248 { 3249 ptr += lstrlenW(ptr) + 1; 3250 continue; 3251 } 3252 3253 lvitem.mask = LVIF_TEXT; 3254 lvitem.iItem = i; 3255 lvitem.iSubItem = 0; 3256 lvitem.pszText = ptr; 3257 lvitem.cchTextMax = lstrlenW(ptr) + 1; 3258 SendMessageW( control->hwnd, LVM_INSERTITEMW, 0, (LPARAM)&lvitem ); 3259 3260 GetDiskFreeSpaceExW(ptr, &free, &total, NULL); 3261 difference = free.QuadPart - cost; 3262 3263 StrFormatByteSizeW(total.QuadPart, size_text, MAX_PATH); 3264 lvitem.iSubItem = 1; 3265 lvitem.pszText = size_text; 3266 lvitem.cchTextMax = lstrlenW(size_text) + 1; 3267 SendMessageW( control->hwnd, LVM_SETITEMW, 0, (LPARAM)&lvitem ); 3268 3269 StrFormatByteSizeW(free.QuadPart, size_text, MAX_PATH); 3270 lvitem.iSubItem = 2; 3271 lvitem.pszText = size_text; 3272 lvitem.cchTextMax = lstrlenW(size_text) + 1; 3273 SendMessageW( control->hwnd, LVM_SETITEMW, 0, (LPARAM)&lvitem ); 3274 3275 lvitem.iSubItem = 3; 3276 lvitem.pszText = cost_text; 3277 lvitem.cchTextMax = lstrlenW(cost_text) + 1; 3278 SendMessageW( control->hwnd, LVM_SETITEMW, 0, (LPARAM)&lvitem ); 3279 3280 StrFormatByteSizeW(difference, size_text, MAX_PATH); 3281 lvitem.iSubItem = 4; 3282 lvitem.pszText = size_text; 3283 lvitem.cchTextMax = lstrlenW(size_text) + 1; 3284 SendMessageW( control->hwnd, LVM_SETITEMW, 0, (LPARAM)&lvitem ); 3285 3286 ptr += lstrlenW(ptr) + 1; 3287 i++; 3288 } 3289 3290 msi_free( drives ); 3291 } 3292 3293 static UINT msi_dialog_volumecost_list( msi_dialog *dialog, MSIRECORD *rec ) 3294 { 3295 msi_control *control; 3296 DWORD style; 3297 3298 style = LVS_REPORT | WS_VSCROLL | WS_HSCROLL | LVS_SHAREIMAGELISTS | 3299 LVS_AUTOARRANGE | LVS_SINGLESEL | WS_BORDER | 3300 WS_CHILD | WS_TABSTOP | WS_GROUP; 3301 control = msi_dialog_add_control( dialog, rec, WC_LISTVIEWW, style ); 3302 if (!control) 3303 return ERROR_FUNCTION_FAILED; 3304 3305 msi_dialog_vcl_add_columns( dialog, control, rec ); 3306 msi_dialog_vcl_add_drives( dialog, control ); 3307 3308 return ERROR_SUCCESS; 3309 } 3310 3311 /******************** VolumeSelect Combo ***************************************/ 3312 3313 static UINT msi_dialog_volsel_handler( msi_dialog *dialog, 3314 msi_control *control, WPARAM param ) 3315 { 3316 WCHAR text[MAX_PATH]; 3317 LPWSTR prop; 3318 BOOL indirect; 3319 int index; 3320 3321 if (HIWORD(param) != CBN_SELCHANGE) 3322 return ERROR_SUCCESS; 3323 3324 index = SendMessageW( control->hwnd, CB_GETCURSEL, 0, 0 ); 3325 if ( index == CB_ERR ) 3326 { 3327 ERR("No ComboBox item selected!\n"); 3328 return ERROR_FUNCTION_FAILED; 3329 } 3330 3331 SendMessageW( control->hwnd, CB_GETLBTEXT, index, (LPARAM)text ); 3332 3333 indirect = control->attributes & msidbControlAttributesIndirect; 3334 prop = msi_dialog_dup_property( dialog, control->property, indirect ); 3335 3336 msi_dialog_set_property( dialog->package, prop, text ); 3337 3338 msi_free( prop ); 3339 return ERROR_SUCCESS; 3340 } 3341 3342 static void msi_dialog_vsc_add_drives( msi_dialog *dialog, msi_control *control ) 3343 { 3344 LPWSTR drives, ptr; 3345 DWORD size; 3346 3347 size = GetLogicalDriveStringsW( 0, NULL ); 3348 if ( !size ) return; 3349 3350 drives = msi_alloc( (size + 1) * sizeof(WCHAR) ); 3351 if ( !drives ) return; 3352 3353 GetLogicalDriveStringsW( size, drives ); 3354 3355 ptr = drives; 3356 while (*ptr) 3357 { 3358 SendMessageW( control->hwnd, CB_ADDSTRING, 0, (LPARAM)ptr ); 3359 ptr += lstrlenW(ptr) + 1; 3360 } 3361 3362 msi_free( drives ); 3363 } 3364 3365 static UINT msi_dialog_volumeselect_combo( msi_dialog *dialog, MSIRECORD *rec ) 3366 { 3367 msi_control *control; 3368 LPCWSTR prop; 3369 DWORD style; 3370 3371 /* FIXME: CBS_OWNERDRAWFIXED */ 3372 style = WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP | 3373 CBS_DROPDOWNLIST | CBS_SORT | CBS_HASSTRINGS | 3374 WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR; 3375 control = msi_dialog_add_control( dialog, rec, WC_COMBOBOXW, style ); 3376 if (!control) 3377 return ERROR_FUNCTION_FAILED; 3378 3379 control->attributes = MSI_RecordGetInteger( rec, 8 ); 3380 control->handler = msi_dialog_volsel_handler; 3381 prop = MSI_RecordGetString( rec, 9 ); 3382 control->property = msi_dialog_dup_property( dialog, prop, FALSE ); 3383 3384 msi_dialog_vsc_add_drives( dialog, control ); 3385 3386 return ERROR_SUCCESS; 3387 } 3388 3389 static UINT msi_dialog_hyperlink_handler( msi_dialog *dialog, msi_control *control, WPARAM param ) 3390 { 3391 int len, len_href = ARRAY_SIZE( L"href" ) - 1; 3392 const WCHAR *p, *q; 3393 WCHAR quote = 0; 3394 LITEM item; 3395 3396 item.mask = LIF_ITEMINDEX | LIF_URL; 3397 item.iLink = 0; 3398 item.szUrl[0] = 0; 3399 3400 SendMessageW( control->hwnd, LM_GETITEM, 0, (LPARAM)&item ); 3401 3402 p = item.szUrl; 3403 while (*p && *p != '<') p++; 3404 if (!*p++) return ERROR_SUCCESS; 3405 if (towupper( *p++ ) != 'A' || !iswspace( *p++ )) return ERROR_SUCCESS; 3406 while (*p && iswspace( *p )) p++; 3407 3408 len = lstrlenW( p ); 3409 if (len > len_href && !wcsnicmp( p, L"href", len_href )) 3410 { 3411 p += len_href; 3412 while (*p && iswspace( *p )) p++; 3413 if (!*p || *p++ != '=') return ERROR_SUCCESS; 3414 while (*p && iswspace( *p )) p++; 3415 3416 if (*p == '\"' || *p == '\'') quote = *p++; 3417 q = p; 3418 if (quote) 3419 { 3420 while (*q && *q != quote) q++; 3421 if (*q != quote) return ERROR_SUCCESS; 3422 } 3423 else 3424 { 3425 while (*q && *q != '>' && !iswspace( *q )) q++; 3426 if (!*q) return ERROR_SUCCESS; 3427 } 3428 item.szUrl[q - item.szUrl] = 0; 3429 ShellExecuteW( NULL, L"open", p, NULL, NULL, SW_SHOWNORMAL ); 3430 } 3431 return ERROR_SUCCESS; 3432 } 3433 3434 static UINT msi_dialog_hyperlink( msi_dialog *dialog, MSIRECORD *rec ) 3435 { 3436 msi_control *control; 3437 DWORD style = WS_CHILD | WS_TABSTOP | WS_GROUP; 3438 const WCHAR *text = MSI_RecordGetString( rec, 10 ); 3439 int len = lstrlenW( text ); 3440 LITEM item; 3441 3442 control = msi_dialog_add_control( dialog, rec, WC_LINK, style ); 3443 if (!control) 3444 return ERROR_FUNCTION_FAILED; 3445 3446 control->attributes = MSI_RecordGetInteger( rec, 8 ); 3447 control->handler = msi_dialog_hyperlink_handler; 3448 3449 item.mask = LIF_ITEMINDEX | LIF_STATE | LIF_URL; 3450 item.iLink = 0; 3451 item.state = LIS_ENABLED; 3452 item.stateMask = LIS_ENABLED; 3453 if (len < L_MAX_URL_LENGTH) lstrcpyW( item.szUrl, text ); 3454 else item.szUrl[0] = 0; 3455 3456 SendMessageW( control->hwnd, LM_SETITEM, 0, (LPARAM)&item ); 3457 3458 return ERROR_SUCCESS; 3459 } 3460 3461 /******************** ListView *****************************************/ 3462 3463 struct listview_param 3464 { 3465 msi_dialog *dialog; 3466 msi_control *control; 3467 }; 3468 3469 static UINT msi_dialog_listview_handler( msi_dialog *dialog, msi_control *control, WPARAM param ) 3470 { 3471 NMHDR *nmhdr = (NMHDR *)param; 3472 3473 FIXME("code %#x (%d)\n", nmhdr->code, nmhdr->code); 3474 3475 return ERROR_SUCCESS; 3476 } 3477 3478 static UINT msi_listview_add_item( MSIRECORD *rec, LPVOID param ) 3479 { 3480 struct listview_param *lv_param = (struct listview_param *)param; 3481 LPCWSTR text, binary; 3482 LVITEMW item; 3483 HICON hIcon; 3484 3485 text = MSI_RecordGetString( rec, 4 ); 3486 binary = MSI_RecordGetString( rec, 5 ); 3487 hIcon = msi_load_icon( lv_param->dialog->package->db, binary, 0 ); 3488 3489 TRACE("Adding: text %s, binary %s, icon %p\n", debugstr_w(text), debugstr_w(binary), hIcon); 3490 3491 memset( &item, 0, sizeof(item) ); 3492 item.mask = LVIF_TEXT | LVIF_IMAGE; 3493 deformat_string( lv_param->dialog->package, text, &item.pszText ); 3494 item.iImage = ImageList_AddIcon( lv_param->control->hImageList, hIcon ); 3495 item.iItem = item.iImage; 3496 SendMessageW( lv_param->control->hwnd, LVM_INSERTITEMW, 0, (LPARAM)&item ); 3497 3498 DestroyIcon( hIcon ); 3499 3500 return ERROR_SUCCESS; 3501 } 3502 3503 static UINT msi_listview_add_items( msi_dialog *dialog, msi_control *control ) 3504 { 3505 MSIQUERY *view; 3506 struct listview_param lv_param = { dialog, control }; 3507 3508 if (MSI_OpenQuery( dialog->package->db, &view, L"SELECT * FROM `ListView` WHERE `Property` = '%s' ORDER BY `Order`", 3509 control->property ) == ERROR_SUCCESS) 3510 { 3511 MSI_IterateRecords( view, NULL, msi_listview_add_item, &lv_param ); 3512 msiobj_release( &view->hdr ); 3513 } 3514 3515 return ERROR_SUCCESS; 3516 } 3517 3518 static UINT msi_dialog_listview( msi_dialog *dialog, MSIRECORD *rec ) 3519 { 3520 msi_control *control; 3521 LPCWSTR prop; 3522 DWORD style, attributes; 3523 LVCOLUMNW col; 3524 RECT rc; 3525 3526 style = LVS_REPORT | LVS_NOCOLUMNHEADER | LVS_SHAREIMAGELISTS | LVS_SINGLESEL | 3527 LVS_SHOWSELALWAYS | WS_VSCROLL | WS_HSCROLL | WS_BORDER | WS_TABSTOP | WS_CHILD; 3528 attributes = MSI_RecordGetInteger( rec, 8 ); 3529 if ( ~attributes & msidbControlAttributesSorted ) 3530 style |= LVS_SORTASCENDING; 3531 control = msi_dialog_add_control( dialog, rec, WC_LISTVIEWW, style ); 3532 if (!control) 3533 return ERROR_FUNCTION_FAILED; 3534 3535 prop = MSI_RecordGetString( rec, 9 ); 3536 control->property = msi_dialog_dup_property( dialog, prop, FALSE ); 3537 3538 control->hImageList = ImageList_Create( 16, 16, ILC_COLOR32, 0, 1); 3539 SendMessageW( control->hwnd, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM)control->hImageList ); 3540 3541 col.mask = LVCF_FMT | LVCF_WIDTH; 3542 col.fmt = LVCFMT_LEFT; 3543 col.cx = 16; 3544 SendMessageW( control->hwnd, LVM_INSERTCOLUMNW, 0, (LPARAM)&col ); 3545 3546 GetClientRect( control->hwnd, &rc ); 3547 col.cx = rc.right - 16; 3548 SendMessageW( control->hwnd, LVM_INSERTCOLUMNW, 0, (LPARAM)&col ); 3549 3550 if (control->property) 3551 msi_listview_add_items( dialog, control ); 3552 3553 control->handler = msi_dialog_listview_handler; 3554 3555 return ERROR_SUCCESS; 3556 } 3557 3558 static const struct control_handler msi_dialog_handler[] = 3559 { 3560 { L"Text", msi_dialog_text_control }, 3561 { L"PushButton", msi_dialog_button_control }, 3562 { L"Line", msi_dialog_line_control }, 3563 { L"Bitmap", msi_dialog_bitmap_control }, 3564 { L"CheckBox", msi_dialog_checkbox_control }, 3565 { L"ScrollableText", msi_dialog_scrolltext_control }, 3566 { L"ComboBox", msi_dialog_combo_control }, 3567 { L"Edit", msi_dialog_edit_control }, 3568 { L"MaskedEdit", msi_dialog_maskedit_control }, 3569 { L"PathEdit", msi_dialog_pathedit_control }, 3570 { L"ProgressBar", msi_dialog_progress_bar }, 3571 { L"RadioButtonGroup", msi_dialog_radiogroup_control }, 3572 { L"Icon", msi_dialog_icon_control }, 3573 { L"SelectionTree", msi_dialog_selection_tree }, 3574 { L"GroupBox", msi_dialog_group_box }, 3575 { L"ListBox", msi_dialog_list_box }, 3576 { L"DirectoryCombo", msi_dialog_directory_combo }, 3577 { L"DirectoryList", msi_dialog_directory_list }, 3578 { L"VolumeCostList", msi_dialog_volumecost_list }, 3579 { L"VolumeSelectCombo", msi_dialog_volumeselect_combo }, 3580 { L"HyperLink", msi_dialog_hyperlink }, 3581 { L"ListView", msi_dialog_listview } 3582 }; 3583 3584 static UINT msi_dialog_create_controls( MSIRECORD *rec, LPVOID param ) 3585 { 3586 msi_dialog *dialog = param; 3587 LPCWSTR control_type; 3588 UINT i; 3589 3590 /* find and call the function that can create this type of control */ 3591 control_type = MSI_RecordGetString( rec, 3 ); 3592 for( i = 0; i < ARRAY_SIZE( msi_dialog_handler ); i++ ) 3593 if (!wcsicmp( msi_dialog_handler[i].control_type, control_type )) 3594 break; 3595 if( i != ARRAY_SIZE( msi_dialog_handler )) 3596 msi_dialog_handler[i].func( dialog, rec ); 3597 else 3598 ERR("no handler for element type %s\n", debugstr_w(control_type)); 3599 3600 return ERROR_SUCCESS; 3601 } 3602 3603 static UINT msi_dialog_fill_controls( msi_dialog *dialog ) 3604 { 3605 UINT r; 3606 MSIQUERY *view; 3607 MSIPACKAGE *package = dialog->package; 3608 3609 TRACE("%p %s\n", dialog, debugstr_w(dialog->name) ); 3610 3611 /* query the Control table for all the elements of the control */ 3612 r = MSI_OpenQuery( package->db, &view, L"SELECT * FROM `Control` WHERE `Dialog_` = '%s'", dialog->name ); 3613 if( r != ERROR_SUCCESS ) 3614 { 3615 ERR("query failed for dialog %s\n", debugstr_w(dialog->name)); 3616 return ERROR_INVALID_PARAMETER; 3617 } 3618 3619 r = MSI_IterateRecords( view, 0, msi_dialog_create_controls, dialog ); 3620 msiobj_release( &view->hdr ); 3621 return r; 3622 } 3623 3624 static UINT msi_dialog_reset( msi_dialog *dialog ) 3625 { 3626 /* FIXME: should restore the original values of any properties we changed */ 3627 return msi_dialog_evaluate_control_conditions( dialog ); 3628 } 3629 3630 /* figure out the height of 10 point MS Sans Serif */ 3631 static INT msi_dialog_get_sans_serif_height( HWND hwnd ) 3632 { 3633 LOGFONTW lf; 3634 TEXTMETRICW tm; 3635 BOOL r; 3636 LONG height = 0; 3637 HFONT hFont, hOldFont; 3638 HDC hdc; 3639 3640 hdc = GetDC( hwnd ); 3641 if (hdc) 3642 { 3643 memset( &lf, 0, sizeof lf ); 3644 lf.lfHeight = MulDiv(12, GetDeviceCaps(hdc, LOGPIXELSY), 72); 3645 lstrcpyW( lf.lfFaceName, L"MS Sans Serif" ); 3646 hFont = CreateFontIndirectW(&lf); 3647 if (hFont) 3648 { 3649 hOldFont = SelectObject( hdc, hFont ); 3650 r = GetTextMetricsW( hdc, &tm ); 3651 if (r) 3652 height = tm.tmHeight; 3653 SelectObject( hdc, hOldFont ); 3654 DeleteObject( hFont ); 3655 } 3656 ReleaseDC( hwnd, hdc ); 3657 } 3658 return height; 3659 } 3660 3661 /* fetch the associated record from the Dialog table */ 3662 static MSIRECORD *msi_get_dialog_record( msi_dialog *dialog ) 3663 { 3664 MSIPACKAGE *package = dialog->package; 3665 MSIRECORD *rec = NULL; 3666 3667 TRACE("%p %s\n", dialog, debugstr_w(dialog->name) ); 3668 3669 rec = MSI_QueryGetRecord( package->db, L"SELECT * FROM `Dialog` WHERE `Dialog` = '%s'", dialog->name ); 3670 if( !rec ) 3671 WARN("query failed for dialog %s\n", debugstr_w(dialog->name)); 3672 3673 return rec; 3674 } 3675 3676 static void msi_dialog_adjust_dialog_pos( msi_dialog *dialog, MSIRECORD *rec, LPRECT pos ) 3677 { 3678 UINT xres, yres; 3679 POINT center; 3680 SIZE sz; 3681 LONG style; 3682 3683 center.x = MSI_RecordGetInteger( rec, 2 ); 3684 center.y = MSI_RecordGetInteger( rec, 3 ); 3685 3686 sz.cx = MSI_RecordGetInteger( rec, 4 ); 3687 sz.cy = MSI_RecordGetInteger( rec, 5 ); 3688 3689 sz.cx = msi_dialog_scale_unit( dialog, sz.cx ); 3690 sz.cy = msi_dialog_scale_unit( dialog, sz.cy ); 3691 3692 xres = msi_get_property_int( dialog->package->db, L"ScreenX", 0 ); 3693 yres = msi_get_property_int( dialog->package->db, L"ScreenY", 0 ); 3694 3695 center.x = MulDiv( center.x, xres, 100 ); 3696 center.y = MulDiv( center.y, yres, 100 ); 3697 3698 /* turn the client pos into the window rectangle */ 3699 if (dialog->package->center_x && dialog->package->center_y) 3700 { 3701 pos->left = dialog->package->center_x - sz.cx / 2.0; 3702 pos->right = pos->left + sz.cx; 3703 pos->top = dialog->package->center_y - sz.cy / 2.0; 3704 pos->bottom = pos->top + sz.cy; 3705 } 3706 else 3707 { 3708 pos->left = center.x - sz.cx/2; 3709 pos->right = pos->left + sz.cx; 3710 pos->top = center.y - sz.cy/2; 3711 pos->bottom = pos->top + sz.cy; 3712 3713 /* save the center */ 3714 dialog->package->center_x = center.x; 3715 dialog->package->center_y = center.y; 3716 } 3717 3718 dialog->size.cx = sz.cx; 3719 dialog->size.cy = sz.cy; 3720 3721 TRACE("%s\n", wine_dbgstr_rect(pos)); 3722 3723 style = GetWindowLongPtrW( dialog->hwnd, GWL_STYLE ); 3724 AdjustWindowRect( pos, style, FALSE ); 3725 } 3726 3727 static void msi_dialog_set_tab_order( msi_dialog *dialog, LPCWSTR first ) 3728 { 3729 struct list tab_chain; 3730 msi_control *control; 3731 HWND prev = HWND_TOP; 3732 3733 list_init( &tab_chain ); 3734 if (!(control = msi_dialog_find_control( dialog, first ))) return; 3735 3736 dialog->hWndFocus = control->hwnd; 3737 while (control) 3738 { 3739 list_remove( &control->entry ); 3740 list_add_tail( &tab_chain, &control->entry ); 3741 if (!control->tabnext) break; 3742 control = msi_dialog_find_control( dialog, control->tabnext ); 3743 } 3744 3745 LIST_FOR_EACH_ENTRY( control, &tab_chain, msi_control, entry ) 3746 { 3747 SetWindowPos( control->hwnd, prev, 0, 0, 0, 0, 3748 SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOREDRAW | 3749 SWP_NOREPOSITION | SWP_NOSENDCHANGING | SWP_NOSIZE ); 3750 prev = control->hwnd; 3751 } 3752 3753 /* put them back on the main list */ 3754 list_move_head( &dialog->controls, &tab_chain ); 3755 } 3756 3757 static LRESULT msi_dialog_oncreate( HWND hwnd, LPCREATESTRUCTW cs ) 3758 { 3759 msi_dialog *dialog = cs->lpCreateParams; 3760 MSIRECORD *rec = NULL; 3761 LPWSTR title = NULL; 3762 RECT pos; 3763 3764 TRACE("%p %p\n", dialog, dialog->package); 3765 3766 dialog->hwnd = hwnd; 3767 SetWindowLongPtrW( hwnd, GWLP_USERDATA, (LONG_PTR) dialog ); 3768 3769 rec = msi_get_dialog_record( dialog ); 3770 if( !rec ) 3771 { 3772 TRACE("No record found for dialog %s\n", debugstr_w(dialog->name)); 3773 return -1; 3774 } 3775 3776 dialog->scale = msi_dialog_get_sans_serif_height(dialog->hwnd); 3777 3778 msi_dialog_adjust_dialog_pos( dialog, rec, &pos ); 3779 3780 dialog->attributes = MSI_RecordGetInteger( rec, 6 ); 3781 3782 dialog->default_font = msi_dup_property( dialog->package->db, L"DefaultUIFont" ); 3783 if (!dialog->default_font) 3784 { 3785 dialog->default_font = strdupW( L"MS Shell Dlg" ); 3786 if (!dialog->default_font) 3787 { 3788 msiobj_release( &rec->hdr ); 3789 return -1; 3790 } 3791 } 3792 3793 title = msi_get_deformatted_field( dialog->package, rec, 7 ); 3794 SetWindowTextW( hwnd, title ); 3795 msi_free( title ); 3796 3797 SetWindowPos( hwnd, 0, pos.left, pos.top, 3798 pos.right - pos.left, pos.bottom - pos.top, 3799 SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOREDRAW ); 3800 3801 msi_dialog_build_font_list( dialog ); 3802 msi_dialog_fill_controls( dialog ); 3803 msi_dialog_evaluate_control_conditions( dialog ); 3804 msi_dialog_set_tab_order( dialog, MSI_RecordGetString( rec, 8 ) ); 3805 msiobj_release( &rec->hdr ); 3806 3807 return 0; 3808 } 3809 3810 static LRESULT msi_dialog_oncommand( msi_dialog *dialog, WPARAM param, HWND hwnd ) 3811 { 3812 msi_control *control = NULL; 3813 3814 TRACE( "%p, %#Ix, %p\n", dialog, param, hwnd ); 3815 3816 switch (param) 3817 { 3818 case 1: /* enter */ 3819 control = msi_dialog_find_control( dialog, dialog->control_default ); 3820 break; 3821 case 2: /* escape */ 3822 control = msi_dialog_find_control( dialog, dialog->control_cancel ); 3823 break; 3824 default: 3825 control = msi_dialog_find_control_by_hwnd( dialog, hwnd ); 3826 } 3827 3828 if( control ) 3829 { 3830 if( control->handler ) 3831 { 3832 control->handler( dialog, control, param ); 3833 msi_dialog_evaluate_control_conditions( dialog ); 3834 } 3835 } 3836 3837 return 0; 3838 } 3839 3840 static LRESULT msi_dialog_onnotify( msi_dialog *dialog, LPARAM param ) 3841 { 3842 LPNMHDR nmhdr = (LPNMHDR) param; 3843 msi_control *control = msi_dialog_find_control_by_hwnd( dialog, nmhdr->hwndFrom ); 3844 3845 TRACE("%p %p\n", dialog, nmhdr->hwndFrom); 3846 3847 if ( control && control->handler ) 3848 control->handler( dialog, control, param ); 3849 3850 return 0; 3851 } 3852 3853 static void dialog_setfocus( msi_dialog *dialog ) 3854 { 3855 HWND hwnd = dialog->hWndFocus; 3856 3857 hwnd = GetNextDlgTabItem( dialog->hwnd, hwnd, TRUE); 3858 hwnd = GetNextDlgTabItem( dialog->hwnd, hwnd, FALSE); 3859 SetFocus( hwnd ); 3860 dialog->hWndFocus = hwnd; 3861 } 3862 3863 static LRESULT WINAPI MSIDialog_WndProc( HWND hwnd, UINT msg, 3864 WPARAM wParam, LPARAM lParam ) 3865 { 3866 msi_dialog *dialog = (LPVOID) GetWindowLongPtrW( hwnd, GWLP_USERDATA ); 3867 3868 TRACE("0x%04x\n", msg); 3869 3870 switch (msg) 3871 { 3872 case WM_MOVE: 3873 dialog->package->center_x = LOWORD(lParam) + dialog->size.cx / 2.0; 3874 dialog->package->center_y = HIWORD(lParam) + dialog->size.cy / 2.0; 3875 break; 3876 3877 case WM_CREATE: 3878 return msi_dialog_oncreate( hwnd, (LPCREATESTRUCTW)lParam ); 3879 3880 case WM_COMMAND: 3881 return msi_dialog_oncommand( dialog, wParam, (HWND)lParam ); 3882 3883 case WM_CLOSE: 3884 /* Simulate escape press */ 3885 return msi_dialog_oncommand(dialog, 2, NULL); 3886 3887 case WM_ACTIVATE: 3888 if( LOWORD(wParam) == WA_INACTIVE ) 3889 dialog->hWndFocus = GetFocus(); 3890 else 3891 dialog_setfocus( dialog ); 3892 return 0; 3893 3894 case WM_SETFOCUS: 3895 dialog_setfocus( dialog ); 3896 return 0; 3897 3898 /* bounce back to our subclassed static control */ 3899 case WM_CTLCOLORSTATIC: 3900 return SendMessageW( (HWND) lParam, WM_CTLCOLORSTATIC, wParam, lParam ); 3901 3902 case WM_DESTROY: 3903 dialog->hwnd = NULL; 3904 return 0; 3905 case WM_NOTIFY: 3906 return msi_dialog_onnotify( dialog, lParam ); 3907 } 3908 return DefWindowProcW(hwnd, msg, wParam, lParam); 3909 } 3910 3911 static void process_pending_messages( HWND hdlg ) 3912 { 3913 MSG msg; 3914 3915 while (PeekMessageW( &msg, 0, 0, 0, PM_REMOVE )) 3916 { 3917 if (hdlg && IsDialogMessageW( hdlg, &msg )) continue; 3918 TranslateMessage( &msg ); 3919 DispatchMessageW( &msg ); 3920 } 3921 } 3922 3923 static UINT dialog_run_message_loop( msi_dialog *dialog ) 3924 { 3925 DWORD style; 3926 HWND hwnd, parent; 3927 3928 if( uiThreadId != GetCurrentThreadId() ) 3929 return SendMessageW( hMsiHiddenWindow, WM_MSI_DIALOG_CREATE, 0, (LPARAM) dialog ); 3930 3931 /* create the dialog window, don't show it yet */ 3932 style = WS_OVERLAPPED | WS_SYSMENU; 3933 if( dialog->attributes & msidbDialogAttributesVisible ) 3934 style |= WS_VISIBLE; 3935 3936 if (dialog->parent == NULL && (dialog->attributes & msidbDialogAttributesMinimize)) 3937 style |= WS_MINIMIZEBOX; 3938 3939 parent = dialog->parent ? dialog->parent->hwnd : 0; 3940 3941 hwnd = CreateWindowW( L"MsiDialogCloseClass", dialog->name, style, 3942 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 3943 parent, NULL, NULL, dialog ); 3944 if( !hwnd ) 3945 { 3946 ERR("Failed to create dialog %s\n", debugstr_w( dialog->name )); 3947 return ERROR_FUNCTION_FAILED; 3948 } 3949 3950 ShowWindow( hwnd, SW_SHOW ); 3951 /* UpdateWindow( hwnd ); - and causes the transparent static controls not to paint */ 3952 3953 if( dialog->attributes & msidbDialogAttributesModal ) 3954 { 3955 while( !dialog->finished ) 3956 { 3957 MsgWaitForMultipleObjects( 0, NULL, 0, INFINITE, QS_ALLINPUT ); 3958 process_pending_messages( dialog->hwnd ); 3959 } 3960 } 3961 else 3962 return ERROR_IO_PENDING; 3963 3964 return ERROR_SUCCESS; 3965 } 3966 3967 static LRESULT WINAPI MSIHiddenWindowProc( HWND hwnd, UINT msg, 3968 WPARAM wParam, LPARAM lParam ) 3969 { 3970 msi_dialog *dialog = (msi_dialog*) lParam; 3971 3972 TRACE("%d %p\n", msg, dialog); 3973 3974 switch (msg) 3975 { 3976 case WM_MSI_DIALOG_CREATE: 3977 return dialog_run_message_loop( dialog ); 3978 case WM_MSI_DIALOG_DESTROY: 3979 msi_dialog_destroy( dialog ); 3980 return 0; 3981 } 3982 return DefWindowProcW( hwnd, msg, wParam, lParam ); 3983 } 3984 3985 static BOOL dialog_register_class( void ) 3986 { 3987 WNDCLASSW cls; 3988 3989 ZeroMemory( &cls, sizeof cls ); 3990 cls.lpfnWndProc = MSIDialog_WndProc; 3991 cls.hInstance = NULL; 3992 cls.hIcon = LoadIconW(0, (LPWSTR)IDI_APPLICATION); 3993 cls.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW); 3994 cls.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1); 3995 cls.lpszMenuName = NULL; 3996 cls.lpszClassName = L"MsiDialogCloseClass"; 3997 3998 if( !RegisterClassW( &cls ) ) 3999 return FALSE; 4000 4001 cls.lpfnWndProc = MSIHiddenWindowProc; 4002 cls.lpszClassName = L"MsiHiddenWindow"; 4003 4004 if( !RegisterClassW( &cls ) ) 4005 return FALSE; 4006 4007 uiThreadId = GetCurrentThreadId(); 4008 4009 hMsiHiddenWindow = CreateWindowW( L"MsiHiddenWindow", NULL, WS_OVERLAPPED, 4010 0, 0, 100, 100, NULL, NULL, NULL, NULL ); 4011 if( !hMsiHiddenWindow ) 4012 return FALSE; 4013 4014 return TRUE; 4015 } 4016 4017 static msi_dialog *dialog_create( MSIPACKAGE *package, const WCHAR *name, msi_dialog *parent, 4018 control_event_handler event_handler ) 4019 { 4020 MSIRECORD *rec = NULL; 4021 msi_dialog *dialog; 4022 4023 TRACE("%s\n", debugstr_w(name)); 4024 4025 if (!hMsiHiddenWindow) dialog_register_class(); 4026 4027 /* allocate the structure for the dialog to use */ 4028 dialog = msi_alloc_zero( FIELD_OFFSET( msi_dialog, name[lstrlenW( name ) + 1] )); 4029 if( !dialog ) 4030 return NULL; 4031 lstrcpyW( dialog->name, name ); 4032 dialog->parent = parent; 4033 dialog->package = package; 4034 dialog->event_handler = event_handler; 4035 dialog->finished = 0; 4036 list_init( &dialog->controls ); 4037 list_init( &dialog->fonts ); 4038 4039 /* verify that the dialog exists */ 4040 rec = msi_get_dialog_record( dialog ); 4041 if( !rec ) 4042 { 4043 msi_free( dialog ); 4044 return NULL; 4045 } 4046 dialog->attributes = MSI_RecordGetInteger( rec, 6 ); 4047 dialog->control_default = strdupW( MSI_RecordGetString( rec, 9 ) ); 4048 dialog->control_cancel = strdupW( MSI_RecordGetString( rec, 10 ) ); 4049 msiobj_release( &rec->hdr ); 4050 4051 rec = MSI_CreateRecord(2); 4052 if (!rec) 4053 { 4054 msi_dialog_destroy(dialog); 4055 return NULL; 4056 } 4057 MSI_RecordSetStringW(rec, 1, name); 4058 MSI_RecordSetStringW(rec, 2, L"Dialog created"); 4059 MSI_ProcessMessage(package, INSTALLMESSAGE_ACTIONSTART, rec); 4060 msiobj_release(&rec->hdr); 4061 4062 return dialog; 4063 } 4064 4065 static void msi_dialog_end_dialog( msi_dialog *dialog ) 4066 { 4067 TRACE("%p\n", dialog); 4068 dialog->finished = 1; 4069 PostMessageW(dialog->hwnd, WM_NULL, 0, 0); 4070 } 4071 4072 void msi_dialog_check_messages( HANDLE handle ) 4073 { 4074 DWORD r; 4075 4076 /* in threads other than the UI thread, block */ 4077 if( uiThreadId != GetCurrentThreadId() ) 4078 { 4079 if (!handle) return; 4080 while (MsgWaitForMultipleObjectsEx( 1, &handle, INFINITE, QS_ALLINPUT, 0 ) == WAIT_OBJECT_0 + 1) 4081 { 4082 MSG msg; 4083 while (PeekMessageW( &msg, NULL, 0, 0, PM_REMOVE )) 4084 { 4085 TranslateMessage( &msg ); 4086 DispatchMessageW( &msg ); 4087 } 4088 } 4089 return; 4090 } 4091 4092 /* there are two choices for the UI thread */ 4093 while (1) 4094 { 4095 process_pending_messages( NULL ); 4096 4097 if( !handle ) 4098 break; 4099 4100 /* 4101 * block here until somebody creates a new dialog or 4102 * the handle we're waiting on becomes ready 4103 */ 4104 r = MsgWaitForMultipleObjects( 1, &handle, 0, INFINITE, QS_ALLINPUT ); 4105 if( r == WAIT_OBJECT_0 ) 4106 break; 4107 } 4108 } 4109 4110 static void dialog_do_preview( msi_dialog *dialog ) 4111 { 4112 TRACE("\n"); 4113 dialog->attributes |= msidbDialogAttributesVisible; 4114 dialog->attributes &= ~msidbDialogAttributesModal; 4115 dialog_run_message_loop( dialog ); 4116 } 4117 4118 static void free_subscriber( struct subscriber *sub ) 4119 { 4120 msi_free( sub->event ); 4121 msi_free( sub->control ); 4122 msi_free( sub->attribute ); 4123 msi_free( sub ); 4124 } 4125 4126 static void event_cleanup_subscriptions( MSIPACKAGE *package, const WCHAR *dialog ) 4127 { 4128 struct list *item, *next; 4129 4130 LIST_FOR_EACH_SAFE( item, next, &package->subscriptions ) 4131 { 4132 struct subscriber *sub = LIST_ENTRY( item, struct subscriber, entry ); 4133 4134 if (wcscmp( sub->dialog->name, dialog )) continue; 4135 list_remove( &sub->entry ); 4136 free_subscriber( sub ); 4137 } 4138 } 4139 4140 void msi_dialog_destroy( msi_dialog *dialog ) 4141 { 4142 msi_font *font, *next; 4143 4144 if( uiThreadId != GetCurrentThreadId() ) 4145 { 4146 SendMessageW( hMsiHiddenWindow, WM_MSI_DIALOG_DESTROY, 0, (LPARAM) dialog ); 4147 return; 4148 } 4149 4150 if( dialog->hwnd ) 4151 { 4152 ShowWindow( dialog->hwnd, SW_HIDE ); 4153 DestroyWindow( dialog->hwnd ); 4154 } 4155 4156 /* unsubscribe events */ 4157 event_cleanup_subscriptions( dialog->package, dialog->name ); 4158 4159 /* destroy the list of controls */ 4160 while( !list_empty( &dialog->controls ) ) 4161 { 4162 msi_control *t; 4163 4164 t = LIST_ENTRY( list_head( &dialog->controls ), 4165 msi_control, entry ); 4166 msi_destroy_control( t ); 4167 } 4168 4169 /* destroy the list of fonts */ 4170 LIST_FOR_EACH_ENTRY_SAFE( font, next, &dialog->fonts, msi_font, entry ) 4171 { 4172 list_remove( &font->entry ); 4173 DeleteObject( font->hfont ); 4174 msi_free( font ); 4175 } 4176 msi_free( dialog->default_font ); 4177 4178 msi_free( dialog->control_default ); 4179 msi_free( dialog->control_cancel ); 4180 dialog->package = NULL; 4181 msi_free( dialog ); 4182 } 4183 4184 void msi_dialog_unregister_class( void ) 4185 { 4186 DestroyWindow( hMsiHiddenWindow ); 4187 hMsiHiddenWindow = NULL; 4188 UnregisterClassW( L"MsiDialogCloseClass", NULL ); 4189 UnregisterClassW( L"MsiHiddenWindow", NULL ); 4190 uiThreadId = 0; 4191 } 4192 4193 void msi_event_cleanup_all_subscriptions( MSIPACKAGE *package ) 4194 { 4195 struct list *item, *next; 4196 4197 LIST_FOR_EACH_SAFE( item, next, &package->subscriptions ) 4198 { 4199 struct subscriber *sub = LIST_ENTRY( item, struct subscriber, entry ); 4200 list_remove( &sub->entry ); 4201 free_subscriber( sub ); 4202 } 4203 } 4204 4205 static void MSI_ClosePreview( MSIOBJECTHDR *arg ) 4206 { 4207 MSIPREVIEW *preview = (MSIPREVIEW *)arg; 4208 msiobj_release( &preview->package->hdr ); 4209 } 4210 4211 static MSIPREVIEW *MSI_EnableUIPreview( MSIDATABASE *db ) 4212 { 4213 MSIPREVIEW *preview = NULL; 4214 MSIPACKAGE *package; 4215 4216 package = MSI_CreatePackage( db ); 4217 if (package) 4218 { 4219 preview = alloc_msiobject( MSIHANDLETYPE_PREVIEW, sizeof(MSIPREVIEW), MSI_ClosePreview ); 4220 if (preview) 4221 { 4222 preview->package = package; 4223 msiobj_addref( &package->hdr ); 4224 } 4225 msiobj_release( &package->hdr ); 4226 } 4227 return preview; 4228 } 4229 4230 UINT WINAPI MsiEnableUIPreview( MSIHANDLE hdb, MSIHANDLE *phPreview ) 4231 { 4232 MSIDATABASE *db; 4233 MSIPREVIEW *preview; 4234 UINT r = ERROR_FUNCTION_FAILED; 4235 4236 TRACE( "%lu %p\n", hdb, phPreview ); 4237 4238 if (!(db = msihandle2msiinfo(hdb, MSIHANDLETYPE_DATABASE))) 4239 return ERROR_INVALID_HANDLE; 4240 4241 preview = MSI_EnableUIPreview( db ); 4242 if (preview) 4243 { 4244 *phPreview = alloc_msihandle( &preview->hdr ); 4245 msiobj_release( &preview->hdr ); 4246 r = ERROR_SUCCESS; 4247 if (!*phPreview) 4248 r = ERROR_NOT_ENOUGH_MEMORY; 4249 } 4250 msiobj_release( &db->hdr ); 4251 return r; 4252 } 4253 4254 static UINT preview_event_handler( msi_dialog *dialog, const WCHAR *event, const WCHAR *argument ) 4255 { 4256 MESSAGE("Preview dialog event '%s' (arg='%s')\n", debugstr_w(event), debugstr_w(argument)); 4257 return ERROR_SUCCESS; 4258 } 4259 4260 static UINT MSI_PreviewDialogW( MSIPREVIEW *preview, LPCWSTR szDialogName ) 4261 { 4262 msi_dialog *dialog = NULL; 4263 UINT r = ERROR_SUCCESS; 4264 4265 if (preview->dialog) 4266 msi_dialog_destroy( preview->dialog ); 4267 4268 /* an empty name means we should just destroy the current preview dialog */ 4269 if (szDialogName) 4270 { 4271 dialog = dialog_create( preview->package, szDialogName, NULL, preview_event_handler ); 4272 if (dialog) 4273 dialog_do_preview( dialog ); 4274 else 4275 r = ERROR_FUNCTION_FAILED; 4276 } 4277 preview->dialog = dialog; 4278 return r; 4279 } 4280 4281 UINT WINAPI MsiPreviewDialogW( MSIHANDLE hPreview, LPCWSTR szDialogName ) 4282 { 4283 MSIPREVIEW *preview; 4284 UINT r; 4285 4286 TRACE( "%lu %s\n", hPreview, debugstr_w(szDialogName) ); 4287 4288 preview = msihandle2msiinfo( hPreview, MSIHANDLETYPE_PREVIEW ); 4289 if (!preview) 4290 return ERROR_INVALID_HANDLE; 4291 4292 r = MSI_PreviewDialogW( preview, szDialogName ); 4293 msiobj_release( &preview->hdr ); 4294 return r; 4295 } 4296 4297 UINT WINAPI MsiPreviewDialogA( MSIHANDLE hPreview, LPCSTR szDialogName ) 4298 { 4299 UINT r; 4300 LPWSTR strW = NULL; 4301 4302 TRACE( "%lu %s\n", hPreview, debugstr_a(szDialogName) ); 4303 4304 if (szDialogName) 4305 { 4306 strW = strdupAtoW( szDialogName ); 4307 if (!strW) 4308 return ERROR_OUTOFMEMORY; 4309 } 4310 r = MsiPreviewDialogW( hPreview, strW ); 4311 msi_free( strW ); 4312 return r; 4313 } 4314 4315 UINT WINAPI MsiPreviewBillboardW( MSIHANDLE hPreview, const WCHAR *szControlName, const WCHAR *szBillboard ) 4316 { 4317 FIXME( "%lu %s %s\n", hPreview, debugstr_w(szControlName), debugstr_w(szBillboard) ); 4318 return ERROR_CALL_NOT_IMPLEMENTED; 4319 } 4320 4321 UINT WINAPI MsiPreviewBillboardA( MSIHANDLE hPreview, const char *szControlName, const char *szBillboard ) 4322 { 4323 FIXME( "%lu %s %s\n", hPreview, debugstr_a(szControlName), debugstr_a(szBillboard) ); 4324 return ERROR_CALL_NOT_IMPLEMENTED; 4325 } 4326 4327 struct control_event 4328 { 4329 const WCHAR *event; 4330 event_handler handler; 4331 }; 4332 4333 static UINT dialog_event_handler( msi_dialog *, const WCHAR *, const WCHAR * ); 4334 4335 /* create a dialog box and run it if it's modal */ 4336 static INT event_do_dialog( MSIPACKAGE *package, const WCHAR *name, msi_dialog *parent, BOOL destroy_modeless ) 4337 { 4338 msi_dialog *dialog; 4339 UINT r; 4340 INT retval; 4341 4342 /* create a new dialog */ 4343 dialog = dialog_create( package, name, parent, dialog_event_handler ); 4344 if (dialog) 4345 { 4346 /* kill the current modeless dialog */ 4347 if (destroy_modeless && package->dialog) 4348 { 4349 msi_dialog_destroy( package->dialog ); 4350 package->dialog = NULL; 4351 } 4352 4353 /* modeless dialogs return an error message */ 4354 r = dialog_run_message_loop( dialog ); 4355 if (r == ERROR_SUCCESS) 4356 { 4357 retval = dialog->retval; 4358 msi_dialog_destroy( dialog ); 4359 return retval; 4360 } 4361 else 4362 { 4363 package->dialog = dialog; 4364 return IDOK; 4365 } 4366 } 4367 else return 0; 4368 } 4369 4370 /* end a modal dialog box */ 4371 static UINT event_end_dialog( msi_dialog *dialog, const WCHAR *argument ) 4372 { 4373 if (!wcscmp( argument, L"Exit" )) 4374 dialog->retval = IDCANCEL; 4375 else if (!wcscmp( argument, L"Retry" )) 4376 dialog->retval = IDRETRY; 4377 else if (!wcscmp( argument, L"Ignore" )) 4378 dialog->retval = IDOK; 4379 else if (!wcscmp( argument, L"Return" )) 4380 dialog->retval = 0; 4381 else 4382 { 4383 ERR("Unknown argument string %s\n", debugstr_w(argument)); 4384 dialog->retval = IDABORT; 4385 } 4386 event_cleanup_subscriptions( dialog->package, dialog->name ); 4387 msi_dialog_end_dialog( dialog ); 4388 return ERROR_SUCCESS; 4389 } 4390 4391 static UINT pending_event_end_dialog( msi_dialog *dialog, const WCHAR *argument ) 4392 { 4393 dialog->pending_event = event_end_dialog; 4394 msi_free( dialog->pending_argument ); 4395 dialog->pending_argument = strdupW( argument ); 4396 return ERROR_SUCCESS; 4397 } 4398 4399 /* transition from one modal dialog to another modal dialog */ 4400 static UINT event_new_dialog( msi_dialog *dialog, const WCHAR *argument ) 4401 { 4402 /* store the name of the next dialog, and signal this one to end */ 4403 dialog->package->next_dialog = strdupW( argument ); 4404 msi_event_cleanup_all_subscriptions( dialog->package ); 4405 msi_dialog_end_dialog( dialog ); 4406 return ERROR_SUCCESS; 4407 } 4408 4409 static UINT pending_event_new_dialog( msi_dialog *dialog, const WCHAR *argument ) 4410 { 4411 dialog->pending_event = event_new_dialog; 4412 msi_free( dialog->pending_argument ); 4413 dialog->pending_argument = strdupW( argument ); 4414 return ERROR_SUCCESS; 4415 } 4416 4417 /* create a new child dialog of an existing modal dialog */ 4418 static UINT event_spawn_dialog( msi_dialog *dialog, const WCHAR *argument ) 4419 { 4420 INT r; 4421 /* don't destroy a modeless dialogs that might be our parent */ 4422 r = event_do_dialog( dialog->package, argument, dialog, FALSE ); 4423 if (r != 0) 4424 { 4425 dialog->retval = r; 4426 msi_dialog_end_dialog( dialog ); 4427 } 4428 else 4429 msi_dialog_update_all_controls(dialog); 4430 4431 return ERROR_SUCCESS; 4432 } 4433 4434 static UINT pending_event_spawn_dialog( msi_dialog *dialog, const WCHAR *argument ) 4435 { 4436 dialog->pending_event = event_spawn_dialog; 4437 msi_free( dialog->pending_argument ); 4438 dialog->pending_argument = strdupW( argument ); 4439 return ERROR_SUCCESS; 4440 } 4441 4442 /* creates a dialog that remains up for a period of time based on a condition */ 4443 static UINT event_spawn_wait_dialog( msi_dialog *dialog, const WCHAR *argument ) 4444 { 4445 FIXME("doing nothing\n"); 4446 return ERROR_SUCCESS; 4447 } 4448 4449 static UINT event_do_action( msi_dialog *dialog, const WCHAR *argument ) 4450 { 4451 ACTION_PerformAction(dialog->package, argument); 4452 return ERROR_SUCCESS; 4453 } 4454 4455 static UINT event_add_local( msi_dialog *dialog, const WCHAR *argument ) 4456 { 4457 MSIFEATURE *feature; 4458 4459 LIST_FOR_EACH_ENTRY( feature, &dialog->package->features, MSIFEATURE, entry ) 4460 { 4461 if (!wcscmp( argument, feature->Feature ) || !wcscmp( argument, L"ALL" )) 4462 { 4463 if (feature->ActionRequest != INSTALLSTATE_LOCAL) 4464 msi_set_property( dialog->package->db, L"Preselected", L"1", -1 ); 4465 MSI_SetFeatureStateW( dialog->package, feature->Feature, INSTALLSTATE_LOCAL ); 4466 } 4467 } 4468 return ERROR_SUCCESS; 4469 } 4470 4471 static UINT event_remove( msi_dialog *dialog, const WCHAR *argument ) 4472 { 4473 MSIFEATURE *feature; 4474 4475 LIST_FOR_EACH_ENTRY( feature, &dialog->package->features, MSIFEATURE, entry ) 4476 { 4477 if (!wcscmp( argument, feature->Feature ) || !wcscmp( argument, L"ALL" )) 4478 { 4479 if (feature->ActionRequest != INSTALLSTATE_ABSENT) 4480 msi_set_property( dialog->package->db, L"Preselected", L"1", -1 ); 4481 MSI_SetFeatureStateW( dialog->package, feature->Feature, INSTALLSTATE_ABSENT ); 4482 } 4483 } 4484 return ERROR_SUCCESS; 4485 } 4486 4487 static UINT event_add_source( msi_dialog *dialog, const WCHAR *argument ) 4488 { 4489 MSIFEATURE *feature; 4490 4491 LIST_FOR_EACH_ENTRY( feature, &dialog->package->features, MSIFEATURE, entry ) 4492 { 4493 if (!wcscmp( argument, feature->Feature ) || !wcscmp( argument, L"ALL" )) 4494 { 4495 if (feature->ActionRequest != INSTALLSTATE_SOURCE) 4496 msi_set_property( dialog->package->db, L"Preselected", L"1", -1 ); 4497 MSI_SetFeatureStateW( dialog->package, feature->Feature, INSTALLSTATE_SOURCE ); 4498 } 4499 } 4500 return ERROR_SUCCESS; 4501 } 4502 4503 void msi_event_fire( MSIPACKAGE *package, const WCHAR *event, MSIRECORD *rec ) 4504 { 4505 struct subscriber *sub; 4506 4507 TRACE("firing event %s\n", debugstr_w(event)); 4508 4509 LIST_FOR_EACH_ENTRY( sub, &package->subscriptions, struct subscriber, entry ) 4510 { 4511 if (wcsicmp( sub->event, event )) continue; 4512 dialog_handle_event( sub->dialog, sub->control, sub->attribute, rec ); 4513 } 4514 } 4515 4516 static UINT event_set_target_path( msi_dialog *dialog, const WCHAR *argument ) 4517 { 4518 WCHAR *path = msi_dup_property( dialog->package->db, argument ); 4519 MSIRECORD *rec = MSI_CreateRecord( 1 ); 4520 UINT r = ERROR_SUCCESS; 4521 4522 MSI_RecordSetStringW( rec, 1, path ); 4523 msi_event_fire( dialog->package, L"SelectionPath", rec ); 4524 if (path) 4525 { 4526 /* failure to set the path halts the executing of control events */ 4527 r = MSI_SetTargetPathW( dialog->package, argument, path ); 4528 msi_free( path ); 4529 } 4530 msi_free( &rec->hdr ); 4531 return r; 4532 } 4533 4534 static UINT event_reset( msi_dialog *dialog, const WCHAR *argument ) 4535 { 4536 msi_dialog_reset( dialog ); 4537 return ERROR_SUCCESS; 4538 } 4539 4540 INT ACTION_ShowDialog( MSIPACKAGE *package, const WCHAR *dialog ) 4541 { 4542 MSIRECORD *row; 4543 INT rc; 4544 4545 if (!TABLE_Exists(package->db, L"Dialog")) return 0; 4546 4547 row = MSI_CreateRecord(0); 4548 if (!row) return -1; 4549 MSI_RecordSetStringW(row, 0, dialog); 4550 rc = MSI_ProcessMessage(package, INSTALLMESSAGE_SHOWDIALOG, row); 4551 msiobj_release(&row->hdr); 4552 4553 if (rc == -2) rc = 0; 4554 4555 if (!rc) 4556 { 4557 MSIRECORD *row = MSI_CreateRecord(2); 4558 if (!row) return -1; 4559 MSI_RecordSetInteger(row, 1, 2726); 4560 MSI_RecordSetStringW(row, 2, dialog); 4561 MSI_ProcessMessage(package, INSTALLMESSAGE_INFO, row); 4562 4563 msiobj_release(&row->hdr); 4564 } 4565 return rc; 4566 } 4567 4568 INT ACTION_DialogBox( MSIPACKAGE *package, const WCHAR *dialog ) 4569 { 4570 INT r; 4571 4572 if (package->next_dialog) ERR("Already got next dialog... ignoring it\n"); 4573 package->next_dialog = NULL; 4574 4575 /* Dialogs are chained through NewDialog, which sets the next_dialog member. 4576 * We fall out of the loop if we reach a modeless dialog, which immediately 4577 * returns IDOK, or an EndDialog event, which returns the value corresponding 4578 * to its argument. 4579 */ 4580 r = event_do_dialog( package, dialog, NULL, TRUE ); 4581 while (package->next_dialog) 4582 { 4583 WCHAR *name = package->next_dialog; 4584 4585 package->next_dialog = NULL; 4586 r = event_do_dialog( package, name, NULL, TRUE ); 4587 msi_free( name ); 4588 } 4589 return r; 4590 } 4591 4592 static UINT event_set_install_level( msi_dialog *dialog, const WCHAR *argument ) 4593 { 4594 int level = wcstol( argument, NULL, 10 ); 4595 4596 TRACE("setting install level to %d\n", level); 4597 return MSI_SetInstallLevel( dialog->package, level ); 4598 } 4599 4600 static UINT event_directory_list_up( msi_dialog *dialog, const WCHAR *argument ) 4601 { 4602 return msi_dialog_directorylist_up( dialog ); 4603 } 4604 4605 static UINT event_directory_list_new( msi_dialog *dialog, const WCHAR *argument ) 4606 { 4607 return msi_dialog_directorylist_new( dialog ); 4608 } 4609 4610 static UINT event_reinstall_mode( msi_dialog *dialog, const WCHAR *argument ) 4611 { 4612 return msi_set_property( dialog->package->db, L"REINSTALLMODE", argument, -1 ); 4613 } 4614 4615 static UINT event_reinstall( msi_dialog *dialog, const WCHAR *argument ) 4616 { 4617 return msi_set_property( dialog->package->db, L"REINSTALL", argument, -1 ); 4618 } 4619 4620 static UINT event_validate_product_id( msi_dialog *dialog, const WCHAR *argument ) 4621 { 4622 return msi_validate_product_id( dialog->package ); 4623 } 4624 4625 static const struct control_event control_events[] = 4626 { 4627 { L"EndDialog", pending_event_end_dialog }, 4628 { L"NewDialog", pending_event_new_dialog }, 4629 { L"SpawnDialog", pending_event_spawn_dialog }, 4630 { L"SpawnWaitDialog", event_spawn_wait_dialog }, 4631 { L"DoAction", event_do_action }, 4632 { L"AddLocal", event_add_local }, 4633 { L"Remove", event_remove }, 4634 { L"AddSource", event_add_source }, 4635 { L"SetTargetPath", event_set_target_path }, 4636 { L"Reset", event_reset }, 4637 { L"SetInstallLevel", event_set_install_level }, 4638 { L"DirectoryListUp", event_directory_list_up }, 4639 { L"DirectoryListNew", event_directory_list_new }, 4640 { L"SelectionBrowse", event_spawn_dialog }, 4641 { L"ReinstallMode", event_reinstall_mode }, 4642 { L"Reinstall", event_reinstall }, 4643 { L"ValidateProductID", event_validate_product_id }, 4644 { NULL, NULL } 4645 }; 4646 4647 static UINT dialog_event_handler( msi_dialog *dialog, const WCHAR *event, const WCHAR *argument ) 4648 { 4649 unsigned int i; 4650 4651 TRACE("handling event %s\n", debugstr_w(event)); 4652 4653 if (!event) return ERROR_SUCCESS; 4654 4655 for (i = 0; control_events[i].event; i++) 4656 { 4657 if (!wcscmp( control_events[i].event, event )) 4658 return control_events[i].handler( dialog, argument ); 4659 } 4660 FIXME("unhandled event %s arg(%s)\n", debugstr_w(event), debugstr_w(argument)); 4661 return ERROR_SUCCESS; 4662 } 4663