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