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