Reactos
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

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