diff options
| author | Sean Hall <r.sean.hall@gmail.com> | 2019-02-15 18:49:04 -0600 |
|---|---|---|
| committer | Sean Hall <r.sean.hall@gmail.com> | 2021-05-11 19:11:19 -0500 |
| commit | 8d92f6d48aa428b1e92a26de53fbf511e780793c (patch) | |
| tree | 2cae54263f282fb1a3c611972db825a3a9c3f5ea /src | |
| parent | daf23d01f667ac66aac90cbc7c8a5a4f02e17a9f (diff) | |
| download | wix-8d92f6d48aa428b1e92a26de53fbf511e780793c.tar.gz wix-8d92f6d48aa428b1e92a26de53fbf511e780793c.tar.bz2 wix-8d92f6d48aa428b1e92a26de53fbf511e780793c.zip | |
Import WixBA from old v4 repo
Diffstat (limited to 'src')
20 files changed, 2636 insertions, 0 deletions
diff --git a/src/WixToolset.WixBA/BrowserProperties.cs b/src/WixToolset.WixBA/BrowserProperties.cs new file mode 100644 index 00000000..f40d4ed9 --- /dev/null +++ b/src/WixToolset.WixBA/BrowserProperties.cs | |||
| @@ -0,0 +1,40 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.UX | ||
| 4 | { | ||
| 5 | using System.Windows; | ||
| 6 | using System.Windows.Controls; | ||
| 7 | |||
| 8 | /// <summary> | ||
| 9 | /// Dependency Properties to support using a WebBrowser object. | ||
| 10 | /// </summary> | ||
| 11 | class BrowserProperties | ||
| 12 | { | ||
| 13 | /// <summary> | ||
| 14 | /// Dependency Propery used to pass an HTML string to the webBrowser object. | ||
| 15 | /// </summary> | ||
| 16 | public static readonly DependencyProperty HtmlDocProperty = | ||
| 17 | DependencyProperty.RegisterAttached("HtmlDoc", typeof(string), typeof(BrowserProperties), new PropertyMetadata(OnHtmlDocChanged)); | ||
| 18 | |||
| 19 | public static string GetHtmlDoc(DependencyObject dependencyObject) | ||
| 20 | { | ||
| 21 | return (string)dependencyObject.GetValue(HtmlDocProperty); | ||
| 22 | } | ||
| 23 | |||
| 24 | public static void SetHtmlDoc(DependencyObject dependencyObject, string htmldoc) | ||
| 25 | { | ||
| 26 | dependencyObject.SetValue(HtmlDocProperty, htmldoc); | ||
| 27 | } | ||
| 28 | |||
| 29 | /// <summary> | ||
| 30 | /// Event handler that passes the HtmlDoc Dependency Property to MavigateToString method. | ||
| 31 | /// </summary> | ||
| 32 | /// <param name="d"></param> | ||
| 33 | /// <param name="e"></param> | ||
| 34 | private static void OnHtmlDocChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) | ||
| 35 | { | ||
| 36 | var webBrowser = (WebBrowser)d; | ||
| 37 | webBrowser.NavigateToString((string)e.NewValue); | ||
| 38 | } | ||
| 39 | } | ||
| 40 | } | ||
diff --git a/src/WixToolset.WixBA/Hresult.cs b/src/WixToolset.WixBA/Hresult.cs new file mode 100644 index 00000000..68b4c5ea --- /dev/null +++ b/src/WixToolset.WixBA/Hresult.cs | |||
| @@ -0,0 +1,22 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.UX | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | |||
| 7 | /// <summary> | ||
| 8 | /// Utility class to work with HRESULTs | ||
| 9 | /// </summary> | ||
| 10 | internal class Hresult | ||
| 11 | { | ||
| 12 | /// <summary> | ||
| 13 | /// Determines if an HRESULT was a success code or not. | ||
| 14 | /// </summary> | ||
| 15 | /// <param name="status">HRESULT to verify.</param> | ||
| 16 | /// <returns>True if the status is a success code.</returns> | ||
| 17 | public static bool Succeeded(int status) | ||
| 18 | { | ||
| 19 | return status >= 0; | ||
| 20 | } | ||
| 21 | } | ||
| 22 | } | ||
diff --git a/src/WixToolset.WixBA/InstallationViewModel.cs b/src/WixToolset.WixBA/InstallationViewModel.cs new file mode 100644 index 00000000..6bec427a --- /dev/null +++ b/src/WixToolset.WixBA/InstallationViewModel.cs | |||
| @@ -0,0 +1,670 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.UX | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.ComponentModel; | ||
| 8 | using System.Linq; | ||
| 9 | using System.Reflection; | ||
| 10 | using System.Windows; | ||
| 11 | using System.Windows.Input; | ||
| 12 | using IO = System.IO; | ||
| 13 | using WixToolset.Bootstrapper; | ||
| 14 | |||
| 15 | /// <summary> | ||
| 16 | /// The states of detection. | ||
| 17 | /// </summary> | ||
| 18 | public enum DetectionState | ||
| 19 | { | ||
| 20 | Absent, | ||
| 21 | Present, | ||
| 22 | Newer, | ||
| 23 | } | ||
| 24 | |||
| 25 | /// <summary> | ||
| 26 | /// The states of installation. | ||
| 27 | /// </summary> | ||
| 28 | public enum InstallationState | ||
| 29 | { | ||
| 30 | Initializing, | ||
| 31 | Detecting, | ||
| 32 | Waiting, | ||
| 33 | Planning, | ||
| 34 | Applying, | ||
| 35 | Applied, | ||
| 36 | Failed, | ||
| 37 | } | ||
| 38 | |||
| 39 | /// <summary> | ||
| 40 | /// The model of the installation view in WixBA. | ||
| 41 | /// </summary> | ||
| 42 | public class InstallationViewModel : PropertyNotifyBase | ||
| 43 | { | ||
| 44 | private RootViewModel root; | ||
| 45 | |||
| 46 | private Dictionary<string, int> downloadRetries; | ||
| 47 | private bool downgrade; | ||
| 48 | private string downgradeMessage; | ||
| 49 | |||
| 50 | private ICommand licenseCommand; | ||
| 51 | private ICommand launchHomePageCommand; | ||
| 52 | private ICommand launchNewsCommand; | ||
| 53 | private ICommand launchVSExtensionPageCommand; | ||
| 54 | private ICommand installCommand; | ||
| 55 | private ICommand repairCommand; | ||
| 56 | private ICommand uninstallCommand; | ||
| 57 | private ICommand openLogCommand; | ||
| 58 | private ICommand openLogFolderCommand; | ||
| 59 | private ICommand tryAgainCommand; | ||
| 60 | |||
| 61 | private string message; | ||
| 62 | private DateTime cachePackageStart; | ||
| 63 | private DateTime executePackageStart; | ||
| 64 | |||
| 65 | /// <summary> | ||
| 66 | /// Creates a new model of the installation view. | ||
| 67 | /// </summary> | ||
| 68 | public InstallationViewModel(RootViewModel root) | ||
| 69 | { | ||
| 70 | this.root = root; | ||
| 71 | this.downloadRetries = new Dictionary<string, int>(); | ||
| 72 | |||
| 73 | this.root.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(this.RootPropertyChanged); | ||
| 74 | |||
| 75 | WixBA.Model.Bootstrapper.DetectBegin += this.DetectBegin; | ||
| 76 | WixBA.Model.Bootstrapper.DetectRelatedBundle += this.DetectedRelatedBundle; | ||
| 77 | WixBA.Model.Bootstrapper.DetectComplete += this.DetectComplete; | ||
| 78 | WixBA.Model.Bootstrapper.PlanPackageBegin += this.PlanPackageBegin; | ||
| 79 | WixBA.Model.Bootstrapper.PlanComplete += this.PlanComplete; | ||
| 80 | WixBA.Model.Bootstrapper.ApplyBegin += this.ApplyBegin; | ||
| 81 | WixBA.Model.Bootstrapper.CacheAcquireBegin += this.CacheAcquireBegin; | ||
| 82 | WixBA.Model.Bootstrapper.CacheAcquireComplete += this.CacheAcquireComplete; | ||
| 83 | WixBA.Model.Bootstrapper.ExecutePackageBegin += this.ExecutePackageBegin; | ||
| 84 | WixBA.Model.Bootstrapper.ExecutePackageComplete += this.ExecutePackageComplete; | ||
| 85 | WixBA.Model.Bootstrapper.Error += this.ExecuteError; | ||
| 86 | WixBA.Model.Bootstrapper.ResolveSource += this.ResolveSource; | ||
| 87 | WixBA.Model.Bootstrapper.ApplyComplete += this.ApplyComplete; | ||
| 88 | } | ||
| 89 | |||
| 90 | void RootPropertyChanged(object sender, PropertyChangedEventArgs e) | ||
| 91 | { | ||
| 92 | if (("DetectState" == e.PropertyName) || ("InstallState" == e.PropertyName)) | ||
| 93 | { | ||
| 94 | base.OnPropertyChanged("RepairEnabled"); | ||
| 95 | base.OnPropertyChanged("InstallEnabled"); | ||
| 96 | base.OnPropertyChanged("IsComplete"); | ||
| 97 | base.OnPropertyChanged("IsSuccessfulCompletion"); | ||
| 98 | base.OnPropertyChanged("IsFailedCompletion"); | ||
| 99 | base.OnPropertyChanged("StatusText"); | ||
| 100 | base.OnPropertyChanged("UninstallEnabled"); | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 104 | /// <summary> | ||
| 105 | /// Gets the version for the application. | ||
| 106 | /// </summary> | ||
| 107 | public string Version | ||
| 108 | { | ||
| 109 | get { return String.Concat("v", WixBA.Model.Version.ToString()); } | ||
| 110 | } | ||
| 111 | |||
| 112 | /// <summary> | ||
| 113 | /// The Publisher of this bundle. | ||
| 114 | /// </summary> | ||
| 115 | public string Publisher | ||
| 116 | { | ||
| 117 | get | ||
| 118 | { | ||
| 119 | string company = "[AssemblyCompany]"; | ||
| 120 | return WixDistribution.ReplacePlaceholders(company, typeof(WixBA).Assembly); | ||
| 121 | } | ||
| 122 | } | ||
| 123 | |||
| 124 | /// <summary> | ||
| 125 | /// The Publisher of this bundle. | ||
| 126 | /// </summary> | ||
| 127 | public string SupportUrl | ||
| 128 | { | ||
| 129 | get | ||
| 130 | { | ||
| 131 | return WixDistribution.SupportUrl; | ||
| 132 | } | ||
| 133 | } | ||
| 134 | public string VSExtensionUrl | ||
| 135 | { | ||
| 136 | get | ||
| 137 | { | ||
| 138 | return WixDistribution.VSExtensionsLandingUrl; | ||
| 139 | } | ||
| 140 | } | ||
| 141 | |||
| 142 | public string Message | ||
| 143 | { | ||
| 144 | get | ||
| 145 | { | ||
| 146 | return this.message; | ||
| 147 | } | ||
| 148 | |||
| 149 | set | ||
| 150 | { | ||
| 151 | if (this.message != value) | ||
| 152 | { | ||
| 153 | this.message = value; | ||
| 154 | base.OnPropertyChanged("Message"); | ||
| 155 | } | ||
| 156 | } | ||
| 157 | } | ||
| 158 | |||
| 159 | /// <summary> | ||
| 160 | /// Gets and sets whether the view model considers this install to be a downgrade. | ||
| 161 | /// </summary> | ||
| 162 | public bool Downgrade | ||
| 163 | { | ||
| 164 | get | ||
| 165 | { | ||
| 166 | return this.downgrade; | ||
| 167 | } | ||
| 168 | |||
| 169 | set | ||
| 170 | { | ||
| 171 | if (this.downgrade != value) | ||
| 172 | { | ||
| 173 | this.downgrade = value; | ||
| 174 | base.OnPropertyChanged("Downgrade"); | ||
| 175 | } | ||
| 176 | } | ||
| 177 | } | ||
| 178 | |||
| 179 | public string DowngradeMessage | ||
| 180 | { | ||
| 181 | get | ||
| 182 | { | ||
| 183 | return this.downgradeMessage; | ||
| 184 | } | ||
| 185 | set | ||
| 186 | { | ||
| 187 | if (this.downgradeMessage != value) | ||
| 188 | { | ||
| 189 | this.downgradeMessage = value; | ||
| 190 | base.OnPropertyChanged("DowngradeMessage"); | ||
| 191 | } | ||
| 192 | } | ||
| 193 | } | ||
| 194 | |||
| 195 | public ICommand LaunchHomePageCommand | ||
| 196 | { | ||
| 197 | get | ||
| 198 | { | ||
| 199 | if (this.launchHomePageCommand == null) | ||
| 200 | { | ||
| 201 | this.launchHomePageCommand = new RelayCommand(param => WixBA.LaunchUrl(this.SupportUrl), param => true); | ||
| 202 | } | ||
| 203 | |||
| 204 | return this.launchHomePageCommand; | ||
| 205 | } | ||
| 206 | } | ||
| 207 | |||
| 208 | public ICommand LaunchNewsCommand | ||
| 209 | { | ||
| 210 | get | ||
| 211 | { | ||
| 212 | if (this.launchNewsCommand == null) | ||
| 213 | { | ||
| 214 | this.launchNewsCommand = new RelayCommand(param => WixBA.LaunchUrl(WixDistribution.NewsUrl), param => true); | ||
| 215 | } | ||
| 216 | |||
| 217 | return this.launchNewsCommand; | ||
| 218 | } | ||
| 219 | } | ||
| 220 | |||
| 221 | public ICommand LaunchVSExtensionPageCommand | ||
| 222 | { | ||
| 223 | get | ||
| 224 | { | ||
| 225 | if (this.launchVSExtensionPageCommand == null) | ||
| 226 | { | ||
| 227 | this.launchVSExtensionPageCommand = new RelayCommand(param => WixBA.LaunchUrl(WixDistribution.VSExtensionsLandingUrl), param => true); | ||
| 228 | } | ||
| 229 | |||
| 230 | return this.launchVSExtensionPageCommand; | ||
| 231 | } | ||
| 232 | } | ||
| 233 | |||
| 234 | public ICommand LicenseCommand | ||
| 235 | { | ||
| 236 | get | ||
| 237 | { | ||
| 238 | if (this.licenseCommand == null) | ||
| 239 | { | ||
| 240 | this.licenseCommand = new RelayCommand(param => this.LaunchLicense(), param => true); | ||
| 241 | } | ||
| 242 | |||
| 243 | return this.licenseCommand; | ||
| 244 | } | ||
| 245 | } | ||
| 246 | |||
| 247 | public bool LicenseEnabled | ||
| 248 | { | ||
| 249 | get { return this.LicenseCommand.CanExecute(this); } | ||
| 250 | } | ||
| 251 | |||
| 252 | public ICommand CloseCommand | ||
| 253 | { | ||
| 254 | get { return this.root.CloseCommand; } | ||
| 255 | } | ||
| 256 | |||
| 257 | public bool IsComplete | ||
| 258 | { | ||
| 259 | get { return IsSuccessfulCompletion || IsFailedCompletion; } | ||
| 260 | } | ||
| 261 | |||
| 262 | public bool IsSuccessfulCompletion | ||
| 263 | { | ||
| 264 | get { return InstallationState.Applied == this.root.InstallState; } | ||
| 265 | } | ||
| 266 | |||
| 267 | public bool IsFailedCompletion | ||
| 268 | { | ||
| 269 | get { return InstallationState.Failed == this.root.InstallState; } | ||
| 270 | } | ||
| 271 | |||
| 272 | public ICommand InstallCommand | ||
| 273 | { | ||
| 274 | get | ||
| 275 | { | ||
| 276 | if (this.installCommand == null) | ||
| 277 | { | ||
| 278 | this.installCommand = new RelayCommand(param => WixBA.Plan(LaunchAction.Install), param => this.root.DetectState == DetectionState.Absent && this.root.InstallState == InstallationState.Waiting); | ||
| 279 | } | ||
| 280 | |||
| 281 | return this.installCommand; | ||
| 282 | } | ||
| 283 | } | ||
| 284 | |||
| 285 | public bool InstallEnabled | ||
| 286 | { | ||
| 287 | get { return this.InstallCommand.CanExecute(this); } | ||
| 288 | } | ||
| 289 | |||
| 290 | public ICommand RepairCommand | ||
| 291 | { | ||
| 292 | get | ||
| 293 | { | ||
| 294 | if (this.repairCommand == null) | ||
| 295 | { | ||
| 296 | this.repairCommand = new RelayCommand(param => WixBA.Plan(LaunchAction.Repair), param => this.root.DetectState == DetectionState.Present && this.root.InstallState == InstallationState.Waiting); | ||
| 297 | } | ||
| 298 | |||
| 299 | return this.repairCommand; | ||
| 300 | } | ||
| 301 | } | ||
| 302 | |||
| 303 | public bool RepairEnabled | ||
| 304 | { | ||
| 305 | get { return this.RepairCommand.CanExecute(this); } | ||
| 306 | } | ||
| 307 | |||
| 308 | public ICommand UninstallCommand | ||
| 309 | { | ||
| 310 | get | ||
| 311 | { | ||
| 312 | if (this.uninstallCommand == null) | ||
| 313 | { | ||
| 314 | this.uninstallCommand = new RelayCommand(param => WixBA.Plan(LaunchAction.Uninstall), param => this.root.DetectState == DetectionState.Present && this.root.InstallState == InstallationState.Waiting); | ||
| 315 | } | ||
| 316 | |||
| 317 | return this.uninstallCommand; | ||
| 318 | } | ||
| 319 | } | ||
| 320 | |||
| 321 | public bool UninstallEnabled | ||
| 322 | { | ||
| 323 | get { return this.UninstallCommand.CanExecute(this); } | ||
| 324 | } | ||
| 325 | |||
| 326 | public ICommand OpenLogCommand | ||
| 327 | { | ||
| 328 | get | ||
| 329 | { | ||
| 330 | if (this.openLogCommand == null) | ||
| 331 | { | ||
| 332 | this.openLogCommand = new RelayCommand(param => WixBA.OpenLog(new Uri(WixBA.Model.Engine.StringVariables["WixBundleLog"]))); | ||
| 333 | } | ||
| 334 | return this.openLogCommand; | ||
| 335 | } | ||
| 336 | } | ||
| 337 | |||
| 338 | public ICommand OpenLogFolderCommand | ||
| 339 | { | ||
| 340 | get | ||
| 341 | { | ||
| 342 | if (this.openLogFolderCommand == null) | ||
| 343 | { | ||
| 344 | string logFolder = IO.Path.GetDirectoryName(WixBA.Model.Engine.StringVariables["WixBundleLog"]); | ||
| 345 | this.openLogFolderCommand = new RelayCommand(param => WixBA.OpenLogFolder(logFolder)); | ||
| 346 | } | ||
| 347 | return this.openLogFolderCommand; | ||
| 348 | } | ||
| 349 | } | ||
| 350 | |||
| 351 | public ICommand TryAgainCommand | ||
| 352 | { | ||
| 353 | get | ||
| 354 | { | ||
| 355 | if (this.tryAgainCommand == null) | ||
| 356 | { | ||
| 357 | this.tryAgainCommand = new RelayCommand(param => | ||
| 358 | { | ||
| 359 | this.root.Canceled = false; | ||
| 360 | WixBA.Plan(WixBA.Model.PlannedAction); | ||
| 361 | }, param => IsFailedCompletion); | ||
| 362 | } | ||
| 363 | |||
| 364 | return this.tryAgainCommand; | ||
| 365 | } | ||
| 366 | } | ||
| 367 | |||
| 368 | public string StatusText | ||
| 369 | { | ||
| 370 | get | ||
| 371 | { | ||
| 372 | switch(this.root.InstallState) | ||
| 373 | { | ||
| 374 | case InstallationState.Applied: | ||
| 375 | return "Complete"; | ||
| 376 | case InstallationState.Failed: | ||
| 377 | return this.root.Canceled ? "Cancelled" : "Failed"; | ||
| 378 | default: | ||
| 379 | return "Unknown"; // this shouldn't be shown in the UI. | ||
| 380 | } | ||
| 381 | } | ||
| 382 | } | ||
| 383 | |||
| 384 | /// <summary> | ||
| 385 | /// Launches the license in the default viewer. | ||
| 386 | /// </summary> | ||
| 387 | private void LaunchLicense() | ||
| 388 | { | ||
| 389 | string folder = IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); | ||
| 390 | WixBA.LaunchUrl(IO.Path.Combine(folder, "License.txt")); | ||
| 391 | } | ||
| 392 | |||
| 393 | private void DetectBegin(object sender, DetectBeginEventArgs e) | ||
| 394 | { | ||
| 395 | this.root.DetectState = e.Installed ? DetectionState.Present : DetectionState.Absent; | ||
| 396 | WixBA.Model.PlannedAction = LaunchAction.Unknown; | ||
| 397 | } | ||
| 398 | |||
| 399 | private void DetectedRelatedBundle(object sender, DetectRelatedBundleEventArgs e) | ||
| 400 | { | ||
| 401 | if (e.Operation == RelatedOperation.Downgrade) | ||
| 402 | { | ||
| 403 | this.Downgrade = true; | ||
| 404 | } | ||
| 405 | |||
| 406 | if (!WixBA.Model.Bootstrapper.BAManifest.Bundle.Packages.ContainsKey(e.ProductCode)) | ||
| 407 | { | ||
| 408 | WixBA.Model.Bootstrapper.BAManifest.Bundle.AddRelatedBundleAsPackage(e); | ||
| 409 | } | ||
| 410 | } | ||
| 411 | |||
| 412 | private void DetectComplete(object sender, DetectCompleteEventArgs e) | ||
| 413 | { | ||
| 414 | // Parse the command line string before any planning. | ||
| 415 | this.ParseCommandLine(); | ||
| 416 | this.root.InstallState = InstallationState.Waiting; | ||
| 417 | |||
| 418 | if (LaunchAction.Uninstall == WixBA.Model.Command.Action && | ||
| 419 | ResumeType.Arp != WixBA.Model.Command.Resume) // MSI and WixStdBA require some kind of confirmation before proceeding so WixBA should, too. | ||
| 420 | { | ||
| 421 | WixBA.Model.Engine.Log(LogLevel.Verbose, "Invoking automatic plan for uninstall"); | ||
| 422 | WixBA.Plan(LaunchAction.Uninstall); | ||
| 423 | } | ||
| 424 | else if (Hresult.Succeeded(e.Status)) | ||
| 425 | { | ||
| 426 | if (this.Downgrade) | ||
| 427 | { | ||
| 428 | this.root.DetectState = DetectionState.Newer; | ||
| 429 | IEnumerable<PackageInfo> relatedPackages = WixBA.Model.Bootstrapper.BAManifest.Bundle.Packages.Values.Where(p => p.Type == PackageType.UpgradeBundle); | ||
| 430 | Version installedVersion = relatedPackages.Any() ? new Version(relatedPackages.Max(p => p.Version)) : null; | ||
| 431 | if (installedVersion != null && installedVersion < new Version(4, 1) && installedVersion.Build > 10) | ||
| 432 | { | ||
| 433 | this.DowngradeMessage = "You must uninstall WiX v" + installedVersion + " before you can install this."; | ||
| 434 | } | ||
| 435 | else | ||
| 436 | { | ||
| 437 | this.DowngradeMessage = "There is already a newer version of WiX installed on this machine."; | ||
| 438 | } | ||
| 439 | } | ||
| 440 | |||
| 441 | if (LaunchAction.Layout == WixBA.Model.Command.Action) | ||
| 442 | { | ||
| 443 | WixBA.PlanLayout(); | ||
| 444 | } | ||
| 445 | else if (WixBA.Model.Command.Display != Display.Full) | ||
| 446 | { | ||
| 447 | // If we're not waiting for the user to click install, dispatch plan with the default action. | ||
| 448 | WixBA.Model.Engine.Log(LogLevel.Verbose, "Invoking automatic plan for non-interactive mode."); | ||
| 449 | WixBA.Plan(WixBA.Model.Command.Action); | ||
| 450 | } | ||
| 451 | } | ||
| 452 | else | ||
| 453 | { | ||
| 454 | this.root.InstallState = InstallationState.Failed; | ||
| 455 | } | ||
| 456 | |||
| 457 | // Force all commands to reevaluate CanExecute. | ||
| 458 | // InvalidateRequerySuggested must be run on the UI thread. | ||
| 459 | root.Dispatcher.Invoke(new Action(CommandManager.InvalidateRequerySuggested)); | ||
| 460 | } | ||
| 461 | |||
| 462 | private void PlanPackageBegin(object sender, PlanPackageBeginEventArgs e) | ||
| 463 | { | ||
| 464 | // If we're able to run our BA, we don't want to install the .NET Framework since the framework on the machine is already good enough. | ||
| 465 | if ( e.PackageId.StartsWith("NetFx4", StringComparison.OrdinalIgnoreCase)) | ||
| 466 | { | ||
| 467 | e.State = RequestState.None; | ||
| 468 | } | ||
| 469 | } | ||
| 470 | |||
| 471 | private void PlanComplete(object sender, PlanCompleteEventArgs e) | ||
| 472 | { | ||
| 473 | if (Hresult.Succeeded(e.Status)) | ||
| 474 | { | ||
| 475 | this.root.PreApplyState = this.root.InstallState; | ||
| 476 | this.root.InstallState = InstallationState.Applying; | ||
| 477 | WixBA.Model.Engine.Apply(this.root.ViewWindowHandle); | ||
| 478 | } | ||
| 479 | else | ||
| 480 | { | ||
| 481 | this.root.InstallState = InstallationState.Failed; | ||
| 482 | } | ||
| 483 | } | ||
| 484 | |||
| 485 | private void ApplyBegin(object sender, ApplyBeginEventArgs e) | ||
| 486 | { | ||
| 487 | this.downloadRetries.Clear(); | ||
| 488 | } | ||
| 489 | |||
| 490 | private void CacheAcquireBegin(object sender, CacheAcquireBeginEventArgs e) | ||
| 491 | { | ||
| 492 | this.cachePackageStart = DateTime.Now; | ||
| 493 | } | ||
| 494 | |||
| 495 | private void CacheAcquireComplete(object sender, CacheAcquireCompleteEventArgs e) | ||
| 496 | { | ||
| 497 | this.AddPackageTelemetry("Cache", e.PackageOrContainerId ?? String.Empty, DateTime.Now.Subtract(this.cachePackageStart).TotalMilliseconds, e.Status); | ||
| 498 | } | ||
| 499 | |||
| 500 | private void ExecutePackageBegin(object sender, ExecutePackageBeginEventArgs e) | ||
| 501 | { | ||
| 502 | lock (this) | ||
| 503 | { | ||
| 504 | this.executePackageStart = e.ShouldExecute ? DateTime.Now : DateTime.MinValue; | ||
| 505 | } | ||
| 506 | } | ||
| 507 | |||
| 508 | private void ExecutePackageComplete(object sender, ExecutePackageCompleteEventArgs e) | ||
| 509 | { | ||
| 510 | lock (this) | ||
| 511 | { | ||
| 512 | if (DateTime.MinValue < this.executePackageStart) | ||
| 513 | { | ||
| 514 | this.AddPackageTelemetry("Execute", e.PackageId ?? String.Empty, DateTime.Now.Subtract(this.executePackageStart).TotalMilliseconds, e.Status); | ||
| 515 | this.executePackageStart = DateTime.MinValue; | ||
| 516 | } | ||
| 517 | } | ||
| 518 | } | ||
| 519 | |||
| 520 | private void ExecuteError(object sender, ErrorEventArgs e) | ||
| 521 | { | ||
| 522 | lock (this) | ||
| 523 | { | ||
| 524 | if (!this.root.Canceled) | ||
| 525 | { | ||
| 526 | // If the error is a cancel coming from the engine during apply we want to go back to the preapply state. | ||
| 527 | if (InstallationState.Applying == this.root.InstallState && (int)Error.UserCancelled == e.ErrorCode) | ||
| 528 | { | ||
| 529 | this.root.InstallState = this.root.PreApplyState; | ||
| 530 | } | ||
| 531 | else | ||
| 532 | { | ||
| 533 | this.Message = e.ErrorMessage; | ||
| 534 | |||
| 535 | if (Display.Full == WixBA.Model.Command.Display) | ||
| 536 | { | ||
| 537 | // On HTTP authentication errors, have the engine try to do authentication for us. | ||
| 538 | if (ErrorType.HttpServerAuthentication == e.ErrorType || ErrorType.HttpProxyAuthentication == e.ErrorType) | ||
| 539 | { | ||
| 540 | e.Result = Result.TryAgain; | ||
| 541 | } | ||
| 542 | else // show an error dialog. | ||
| 543 | { | ||
| 544 | MessageBoxButton msgbox = MessageBoxButton.OK; | ||
| 545 | switch (e.UIHint & 0xF) | ||
| 546 | { | ||
| 547 | case 0: | ||
| 548 | msgbox = MessageBoxButton.OK; | ||
| 549 | break; | ||
| 550 | case 1: | ||
| 551 | msgbox = MessageBoxButton.OKCancel; | ||
| 552 | break; | ||
| 553 | // There is no 2! That would have been MB_ABORTRETRYIGNORE. | ||
| 554 | case 3: | ||
| 555 | msgbox = MessageBoxButton.YesNoCancel; | ||
| 556 | break; | ||
| 557 | case 4: | ||
| 558 | msgbox = MessageBoxButton.YesNo; | ||
| 559 | break; | ||
| 560 | // default: stay with MBOK since an exact match is not available. | ||
| 561 | } | ||
| 562 | |||
| 563 | MessageBoxResult result = MessageBoxResult.None; | ||
| 564 | WixBA.View.Dispatcher.Invoke((Action)delegate() | ||
| 565 | { | ||
| 566 | result = MessageBox.Show(WixBA.View, e.ErrorMessage, "WiX Toolset", msgbox, MessageBoxImage.Error); | ||
| 567 | } | ||
| 568 | ); | ||
| 569 | |||
| 570 | // If there was a match from the UI hint to the msgbox value, use the result from the | ||
| 571 | // message box. Otherwise, we'll ignore it and return the default to Burn. | ||
| 572 | if ((e.UIHint & 0xF) == (int)msgbox) | ||
| 573 | { | ||
| 574 | e.Result = (Result)result; | ||
| 575 | } | ||
| 576 | } | ||
| 577 | } | ||
| 578 | } | ||
| 579 | } | ||
| 580 | else // canceled, so always return cancel. | ||
| 581 | { | ||
| 582 | e.Result = Result.Cancel; | ||
| 583 | } | ||
| 584 | } | ||
| 585 | } | ||
| 586 | |||
| 587 | private void ResolveSource(object sender, ResolveSourceEventArgs e) | ||
| 588 | { | ||
| 589 | int retries = 0; | ||
| 590 | |||
| 591 | this.downloadRetries.TryGetValue(e.PackageOrContainerId, out retries); | ||
| 592 | this.downloadRetries[e.PackageOrContainerId] = retries + 1; | ||
| 593 | |||
| 594 | e.Action = retries < 3 && !String.IsNullOrEmpty(e.DownloadSource) ? BOOTSTRAPPER_RESOLVESOURCE_ACTION.Download : BOOTSTRAPPER_RESOLVESOURCE_ACTION.None; | ||
| 595 | } | ||
| 596 | |||
| 597 | private void ApplyComplete(object sender, ApplyCompleteEventArgs e) | ||
| 598 | { | ||
| 599 | WixBA.Model.Result = e.Status; // remember the final result of the apply. | ||
| 600 | |||
| 601 | // Set the state to applied or failed unless the state has already been set back to the preapply state | ||
| 602 | // which means we need to show the UI as it was before the apply started. | ||
| 603 | if (this.root.InstallState != this.root.PreApplyState) | ||
| 604 | { | ||
| 605 | this.root.InstallState = Hresult.Succeeded(e.Status) ? InstallationState.Applied : InstallationState.Failed; | ||
| 606 | } | ||
| 607 | |||
| 608 | // If we're not in Full UI mode, we need to alert the dispatcher to stop and close the window for passive. | ||
| 609 | if (Bootstrapper.Display.Full != WixBA.Model.Command.Display) | ||
| 610 | { | ||
| 611 | // If its passive, send a message to the window to close. | ||
| 612 | if (Bootstrapper.Display.Passive == WixBA.Model.Command.Display) | ||
| 613 | { | ||
| 614 | WixBA.Model.Engine.Log(LogLevel.Verbose, "Automatically closing the window for non-interactive install"); | ||
| 615 | WixBA.Dispatcher.BeginInvoke(new Action(WixBA.View.Close)); | ||
| 616 | } | ||
| 617 | else | ||
| 618 | { | ||
| 619 | WixBA.Dispatcher.InvokeShutdown(); | ||
| 620 | } | ||
| 621 | return; | ||
| 622 | } | ||
| 623 | else if (Hresult.Succeeded(e.Status) && LaunchAction.UpdateReplace == WixBA.Model.PlannedAction) // if we successfully applied an update close the window since the new Bundle should be running now. | ||
| 624 | { | ||
| 625 | WixBA.Model.Engine.Log(LogLevel.Verbose, "Automatically closing the window since update successful."); | ||
| 626 | WixBA.Dispatcher.BeginInvoke(new Action(WixBA.View.Close)); | ||
| 627 | return; | ||
| 628 | } | ||
| 629 | else if (root.AutoClose) | ||
| 630 | { | ||
| 631 | // Automatically closing since the user clicked the X button. | ||
| 632 | WixBA.Dispatcher.BeginInvoke(new Action(WixBA.View.Close)); | ||
| 633 | return; | ||
| 634 | } | ||
| 635 | |||
| 636 | // Force all commands to reevaluate CanExecute. | ||
| 637 | // InvalidateRequerySuggested must be run on the UI thread. | ||
| 638 | root.Dispatcher.Invoke(new Action(CommandManager.InvalidateRequerySuggested)); | ||
| 639 | } | ||
| 640 | |||
| 641 | private void ParseCommandLine() | ||
| 642 | { | ||
| 643 | // Get array of arguments based on the system parsing algorithm. | ||
| 644 | string[] args = WixBA.Model.Command.GetCommandLineArgs(); | ||
| 645 | for (int i = 0; i < args.Length; ++i) | ||
| 646 | { | ||
| 647 | if (args[i].StartsWith("InstallFolder=", StringComparison.InvariantCultureIgnoreCase)) | ||
| 648 | { | ||
| 649 | // Allow relative directory paths. Also validates. | ||
| 650 | string[] param = args[i].Split(new char[] {'='}, 2); | ||
| 651 | this.root.InstallDirectory = IO.Path.Combine(Environment.CurrentDirectory, param[1]); | ||
| 652 | } | ||
| 653 | } | ||
| 654 | } | ||
| 655 | |||
| 656 | private void AddPackageTelemetry(string prefix, string id, double time, int result) | ||
| 657 | { | ||
| 658 | lock (this) | ||
| 659 | { | ||
| 660 | string key = String.Format("{0}Time_{1}", prefix, id); | ||
| 661 | string value = time.ToString(); | ||
| 662 | WixBA.Model.Telemetry.Add(new KeyValuePair<string, string>(key, value)); | ||
| 663 | |||
| 664 | key = String.Format("{0}Result_{1}", prefix, id); | ||
| 665 | value = String.Concat("0x", result.ToString("x")); | ||
| 666 | WixBA.Model.Telemetry.Add(new KeyValuePair<string, string>(key, value)); | ||
| 667 | } | ||
| 668 | } | ||
| 669 | } | ||
| 670 | } | ||
diff --git a/src/WixToolset.WixBA/Model.cs b/src/WixToolset.WixBA/Model.cs new file mode 100644 index 00000000..9f03e95b --- /dev/null +++ b/src/WixToolset.WixBA/Model.cs | |||
| @@ -0,0 +1,129 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.UX | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Net; | ||
| 8 | using WixToolset.Bootstrapper; | ||
| 9 | |||
| 10 | /// <summary> | ||
| 11 | /// The model. | ||
| 12 | /// </summary> | ||
| 13 | public class Model | ||
| 14 | { | ||
| 15 | private const string BurnBundleInstallDirectoryVariable = "InstallFolder"; | ||
| 16 | private const string BurnBundleLayoutDirectoryVariable = "WixBundleLayoutDirectory"; | ||
| 17 | private const string BurnBundleVersionVariable = "WixBundleVersion"; | ||
| 18 | |||
| 19 | /// <summary> | ||
| 20 | /// Creates a new model for the UX. | ||
| 21 | /// </summary> | ||
| 22 | /// <param name="bootstrapper">Bootstrapper hosting the UX.</param> | ||
| 23 | public Model(BootstrapperApplication bootstrapper) | ||
| 24 | { | ||
| 25 | this.Bootstrapper = bootstrapper; | ||
| 26 | this.Telemetry = new List<KeyValuePair<string, string>>(); | ||
| 27 | this.Version = this.Engine.VersionVariables[BurnBundleVersionVariable]; | ||
| 28 | } | ||
| 29 | |||
| 30 | /// <summary> | ||
| 31 | /// Gets the bootstrapper. | ||
| 32 | /// </summary> | ||
| 33 | public BootstrapperApplication Bootstrapper { get; private set; } | ||
| 34 | |||
| 35 | /// <summary> | ||
| 36 | /// Gets the bootstrapper command-line. | ||
| 37 | /// </summary> | ||
| 38 | public Command Command { get { return this.Bootstrapper.Command; } } | ||
| 39 | |||
| 40 | /// <summary> | ||
| 41 | /// Gets the bootstrapper engine. | ||
| 42 | /// </summary> | ||
| 43 | public Engine Engine { get { return this.Bootstrapper.Engine; } } | ||
| 44 | |||
| 45 | /// <summary> | ||
| 46 | /// Gets the key/value pairs used in telemetry. | ||
| 47 | /// </summary> | ||
| 48 | public List<KeyValuePair<string, string>> Telemetry { get; private set; } | ||
| 49 | |||
| 50 | /// <summary> | ||
| 51 | /// Get or set the final result of the installation. | ||
| 52 | /// </summary> | ||
| 53 | public int Result { get; set; } | ||
| 54 | |||
| 55 | /// <summary> | ||
| 56 | /// Get the version of the install. | ||
| 57 | /// </summary> | ||
| 58 | public Version Version { get; private set; } | ||
| 59 | |||
| 60 | /// <summary> | ||
| 61 | /// Get or set the path where the bundle is installed. | ||
| 62 | /// </summary> | ||
| 63 | public string InstallDirectory | ||
| 64 | { | ||
| 65 | get | ||
| 66 | { | ||
| 67 | if (!this.Engine.StringVariables.Contains(BurnBundleInstallDirectoryVariable)) | ||
| 68 | { | ||
| 69 | return null; | ||
| 70 | } | ||
| 71 | |||
| 72 | return this.Engine.StringVariables[BurnBundleInstallDirectoryVariable]; | ||
| 73 | } | ||
| 74 | |||
| 75 | set | ||
| 76 | { | ||
| 77 | this.Engine.StringVariables[BurnBundleInstallDirectoryVariable] = value; | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | /// <summary> | ||
| 82 | /// Get or set the path for the layout to be created. | ||
| 83 | /// </summary> | ||
| 84 | public string LayoutDirectory | ||
| 85 | { | ||
| 86 | get | ||
| 87 | { | ||
| 88 | if (!this.Engine.StringVariables.Contains(BurnBundleLayoutDirectoryVariable)) | ||
| 89 | { | ||
| 90 | return null; | ||
| 91 | } | ||
| 92 | |||
| 93 | return this.Engine.StringVariables[BurnBundleLayoutDirectoryVariable]; | ||
| 94 | } | ||
| 95 | |||
| 96 | set | ||
| 97 | { | ||
| 98 | this.Engine.StringVariables[BurnBundleLayoutDirectoryVariable] = value; | ||
| 99 | } | ||
| 100 | } | ||
| 101 | |||
| 102 | public LaunchAction PlannedAction { get; set; } | ||
| 103 | |||
| 104 | /// <summary> | ||
| 105 | /// Creates a correctly configured HTTP web request. | ||
| 106 | /// </summary> | ||
| 107 | /// <param name="uri">URI to connect to.</param> | ||
| 108 | /// <returns>Correctly configured HTTP web request.</returns> | ||
| 109 | public HttpWebRequest CreateWebRequest(string uri) | ||
| 110 | { | ||
| 111 | HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri); | ||
| 112 | request.UserAgent = String.Concat("WixInstall", this.Version.ToString()); | ||
| 113 | |||
| 114 | return request; | ||
| 115 | } | ||
| 116 | |||
| 117 | /// <summary> | ||
| 118 | /// Gets the display name for a package if possible. | ||
| 119 | /// </summary> | ||
| 120 | /// <param name="packageId">Identity of the package to find the display name.</param> | ||
| 121 | /// <returns>Display name of the package if found or the package id if not.</returns> | ||
| 122 | public string GetPackageName(string packageId) | ||
| 123 | { | ||
| 124 | PackageInfo package; | ||
| 125 | |||
| 126 | return this.Bootstrapper.BAManifest.Bundle.Packages.TryGetValue(packageId, out package) ? package.DisplayName : packageId; | ||
| 127 | } | ||
| 128 | } | ||
| 129 | } | ||
diff --git a/src/WixToolset.WixBA/NewsItem.cs b/src/WixToolset.WixBA/NewsItem.cs new file mode 100644 index 00000000..a8350104 --- /dev/null +++ b/src/WixToolset.WixBA/NewsItem.cs | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.UX | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | |||
| 7 | /// <summary> | ||
| 8 | /// The model for an individual news item. | ||
| 9 | /// </summary> | ||
| 10 | public class NewsItem | ||
| 11 | { | ||
| 12 | public string Author { get; set; } | ||
| 13 | public string Title { get; set; } | ||
| 14 | public string Url { get; set; } | ||
| 15 | public string Snippet { get; set; } | ||
| 16 | public DateTime Updated { get; set; } | ||
| 17 | } | ||
| 18 | } | ||
diff --git a/src/WixToolset.WixBA/ProgressViewModel.cs b/src/WixToolset.WixBA/ProgressViewModel.cs new file mode 100644 index 00000000..30aee5f1 --- /dev/null +++ b/src/WixToolset.WixBA/ProgressViewModel.cs | |||
| @@ -0,0 +1,194 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | using System; | ||
| 4 | using System.Collections.Generic; | ||
| 5 | using System.ComponentModel; | ||
| 6 | using System.Diagnostics; | ||
| 7 | using System.Text.RegularExpressions; | ||
| 8 | using WixToolset.Bootstrapper; | ||
| 9 | |||
| 10 | namespace WixToolset.UX | ||
| 11 | { | ||
| 12 | public class ProgressViewModel : PropertyNotifyBase | ||
| 13 | { | ||
| 14 | private static readonly Regex TrimActionTimeFromMessage = new Regex(@"^\w+\s+\d+:\d+:\d+:\s+", RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture | RegexOptions.Singleline); | ||
| 15 | |||
| 16 | private RootViewModel root; | ||
| 17 | private Dictionary<string, int> executingPackageOrderIndex; | ||
| 18 | |||
| 19 | private int progressPhases; | ||
| 20 | private int progress; | ||
| 21 | private int cacheProgress; | ||
| 22 | private int executeProgress; | ||
| 23 | private string package; | ||
| 24 | private string message; | ||
| 25 | |||
| 26 | public ProgressViewModel(RootViewModel root) | ||
| 27 | { | ||
| 28 | this.root = root; | ||
| 29 | this.executingPackageOrderIndex = new Dictionary<string, int>(); | ||
| 30 | |||
| 31 | this.root.PropertyChanged += this.RootPropertyChanged; | ||
| 32 | |||
| 33 | WixBA.Model.Bootstrapper.ExecutePackageBegin += this.ExecutePackageBegin; | ||
| 34 | WixBA.Model.Bootstrapper.ExecutePackageComplete += this.ExecutePackageComplete; | ||
| 35 | WixBA.Model.Bootstrapper.ExecuteProgress += this.ApplyExecuteProgress; | ||
| 36 | WixBA.Model.Bootstrapper.PlanBegin += this.PlanBegin; | ||
| 37 | WixBA.Model.Bootstrapper.PlanPackageComplete += this.PlanPackageComplete; | ||
| 38 | WixBA.Model.Bootstrapper.ApplyBegin += this.ApplyBegin; | ||
| 39 | WixBA.Model.Bootstrapper.Progress += this.ApplyProgress; | ||
| 40 | WixBA.Model.Bootstrapper.CacheAcquireProgress += this.CacheAcquireProgress; | ||
| 41 | WixBA.Model.Bootstrapper.CacheComplete += this.CacheComplete; | ||
| 42 | } | ||
| 43 | |||
| 44 | public bool ProgressEnabled | ||
| 45 | { | ||
| 46 | get { return this.root.InstallState == InstallationState.Applying; } | ||
| 47 | } | ||
| 48 | |||
| 49 | public int Progress | ||
| 50 | { | ||
| 51 | get | ||
| 52 | { | ||
| 53 | return this.progress; | ||
| 54 | } | ||
| 55 | |||
| 56 | set | ||
| 57 | { | ||
| 58 | if (this.progress != value) | ||
| 59 | { | ||
| 60 | this.progress = value; | ||
| 61 | base.OnPropertyChanged("Progress"); | ||
| 62 | } | ||
| 63 | } | ||
| 64 | } | ||
| 65 | |||
| 66 | public string Package | ||
| 67 | { | ||
| 68 | get | ||
| 69 | { | ||
| 70 | return this.package; | ||
| 71 | } | ||
| 72 | |||
| 73 | set | ||
| 74 | { | ||
| 75 | if (this.package != value) | ||
| 76 | { | ||
| 77 | this.package = value; | ||
| 78 | base.OnPropertyChanged("Package"); | ||
| 79 | } | ||
| 80 | } | ||
| 81 | } | ||
| 82 | |||
| 83 | public string Message | ||
| 84 | { | ||
| 85 | get | ||
| 86 | { | ||
| 87 | return this.message; | ||
| 88 | } | ||
| 89 | |||
| 90 | set | ||
| 91 | { | ||
| 92 | if (this.message != value) | ||
| 93 | { | ||
| 94 | this.message = value; | ||
| 95 | base.OnPropertyChanged("Message"); | ||
| 96 | } | ||
| 97 | } | ||
| 98 | } | ||
| 99 | |||
| 100 | void RootPropertyChanged(object sender, PropertyChangedEventArgs e) | ||
| 101 | { | ||
| 102 | if ("InstallState" == e.PropertyName) | ||
| 103 | { | ||
| 104 | base.OnPropertyChanged("ProgressEnabled"); | ||
| 105 | } | ||
| 106 | } | ||
| 107 | |||
| 108 | private void PlanBegin(object sender, PlanBeginEventArgs e) | ||
| 109 | { | ||
| 110 | lock (this) | ||
| 111 | { | ||
| 112 | this.executingPackageOrderIndex.Clear(); | ||
| 113 | } | ||
| 114 | } | ||
| 115 | |||
| 116 | private void PlanPackageComplete(object sender, PlanPackageCompleteEventArgs e) | ||
| 117 | { | ||
| 118 | if (ActionState.None != e.Execute) | ||
| 119 | { | ||
| 120 | lock (this) | ||
| 121 | { | ||
| 122 | Debug.Assert(!this.executingPackageOrderIndex.ContainsKey(e.PackageId)); | ||
| 123 | this.executingPackageOrderIndex.Add(e.PackageId, this.executingPackageOrderIndex.Count); | ||
| 124 | } | ||
| 125 | } | ||
| 126 | } | ||
| 127 | |||
| 128 | private void ExecutePackageBegin(object sender, ExecutePackageBeginEventArgs e) | ||
| 129 | { | ||
| 130 | lock (this) | ||
| 131 | { | ||
| 132 | this.Package = WixBA.Model.GetPackageName(e.PackageId); | ||
| 133 | this.Message = String.Format("Processing: {0}", this.Package); | ||
| 134 | e.Cancel = this.root.Canceled; | ||
| 135 | } | ||
| 136 | } | ||
| 137 | |||
| 138 | private void ExecutePackageComplete(object sender, ExecutePackageCompleteEventArgs e) | ||
| 139 | { | ||
| 140 | lock (this) | ||
| 141 | { // avoid a stale display | ||
| 142 | this.Message = String.Empty; | ||
| 143 | } | ||
| 144 | } | ||
| 145 | |||
| 146 | private void ApplyBegin(object sender, ApplyBeginEventArgs e) | ||
| 147 | { | ||
| 148 | this.progressPhases = e.PhaseCount; | ||
| 149 | } | ||
| 150 | |||
| 151 | private void ApplyProgress(object sender, ProgressEventArgs e) | ||
| 152 | { | ||
| 153 | lock (this) | ||
| 154 | { | ||
| 155 | e.Cancel = this.root.Canceled; | ||
| 156 | } | ||
| 157 | } | ||
| 158 | |||
| 159 | private void CacheAcquireProgress(object sender, CacheAcquireProgressEventArgs e) | ||
| 160 | { | ||
| 161 | lock (this) | ||
| 162 | { | ||
| 163 | this.cacheProgress = e.OverallPercentage; | ||
| 164 | this.Progress = (this.cacheProgress + this.executeProgress) / this.progressPhases; | ||
| 165 | e.Cancel = this.root.Canceled; | ||
| 166 | } | ||
| 167 | } | ||
| 168 | |||
| 169 | private void CacheComplete(object sender, CacheCompleteEventArgs e) | ||
| 170 | { | ||
| 171 | lock (this) | ||
| 172 | { | ||
| 173 | this.cacheProgress = 100; | ||
| 174 | this.Progress = (this.cacheProgress + this.executeProgress) / this.progressPhases; | ||
| 175 | } | ||
| 176 | } | ||
| 177 | |||
| 178 | private void ApplyExecuteProgress(object sender, ExecuteProgressEventArgs e) | ||
| 179 | { | ||
| 180 | lock (this) | ||
| 181 | { | ||
| 182 | this.executeProgress = e.OverallPercentage; | ||
| 183 | this.Progress = (this.cacheProgress + this.executeProgress) / this.progressPhases; | ||
| 184 | |||
| 185 | if (WixBA.Model.Command.Display == Display.Embedded) | ||
| 186 | { | ||
| 187 | WixBA.Model.Engine.SendEmbeddedProgress(e.ProgressPercentage, this.Progress); | ||
| 188 | } | ||
| 189 | |||
| 190 | e.Cancel = this.root.Canceled; | ||
| 191 | } | ||
| 192 | } | ||
| 193 | } | ||
| 194 | } | ||
diff --git a/src/WixToolset.WixBA/Properties/AssemblyInfo.cs b/src/WixToolset.WixBA/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..58d88046 --- /dev/null +++ b/src/WixToolset.WixBA/Properties/AssemblyInfo.cs | |||
| @@ -0,0 +1,22 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | using System; | ||
| 4 | using System.Reflection; | ||
| 5 | using System.Runtime.InteropServices; | ||
| 6 | using WixToolset.Bootstrapper; | ||
| 7 | using WixToolset.UX; | ||
| 8 | |||
| 9 | |||
| 10 | [assembly: AssemblyTitle("WixBA")] | ||
| 11 | [assembly: AssemblyDescription("WiX User Experience")] | ||
| 12 | |||
| 13 | // Setting ComVisible to false makes the types in this assembly not visible | ||
| 14 | // to COM components. If you need to access a type in this assembly from | ||
| 15 | // COM, set the ComVisible attribute to true on that type. | ||
| 16 | [assembly: ComVisible(false)] | ||
| 17 | [assembly: Guid("0ffc4944-9295-40b7-adac-3a6864b5219b")] | ||
| 18 | [assembly: CLSCompliantAttribute(true)] | ||
| 19 | |||
| 20 | // Identifies the class that derives from UserExperience and is the UX class that gets | ||
| 21 | // instantiated by the interop layer | ||
| 22 | [assembly: BootstrapperApplication(typeof(WixBA))] | ||
diff --git a/src/WixToolset.WixBA/PropertyNotifyBase.cs b/src/WixToolset.WixBA/PropertyNotifyBase.cs new file mode 100644 index 00000000..03174306 --- /dev/null +++ b/src/WixToolset.WixBA/PropertyNotifyBase.cs | |||
| @@ -0,0 +1,59 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.UX | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.ComponentModel; | ||
| 7 | using System.Diagnostics; | ||
| 8 | |||
| 9 | /// <summary> | ||
| 10 | /// It provides support for property change notifications. | ||
| 11 | /// </summary> | ||
| 12 | public abstract class PropertyNotifyBase : INotifyPropertyChanged | ||
| 13 | { | ||
| 14 | /// <summary> | ||
| 15 | /// Initializes a new instance of the <see cref="PropertyNotifyBase"/> class. | ||
| 16 | /// </summary> | ||
| 17 | protected PropertyNotifyBase() | ||
| 18 | { | ||
| 19 | } | ||
| 20 | |||
| 21 | /// <summary> | ||
| 22 | /// Raised when a property on this object has a new value. | ||
| 23 | /// </summary> | ||
| 24 | public event PropertyChangedEventHandler PropertyChanged; | ||
| 25 | |||
| 26 | /// <summary> | ||
| 27 | /// Warns the developer if this object does not have a public property with the | ||
| 28 | /// specified name. This method does not exist in a Release build. | ||
| 29 | /// </summary> | ||
| 30 | /// <param name="propertyName">Property name to verify.</param> | ||
| 31 | [Conditional("DEBUG")] | ||
| 32 | [DebuggerStepThrough] | ||
| 33 | public void VerifyPropertyName(string propertyName) | ||
| 34 | { | ||
| 35 | // Verify that the property name matches a real, public, instance property | ||
| 36 | // on this object. | ||
| 37 | if (null == TypeDescriptor.GetProperties(this)[propertyName]) | ||
| 38 | { | ||
| 39 | Debug.Fail(String.Concat("Invalid property name: ", propertyName)); | ||
| 40 | } | ||
| 41 | } | ||
| 42 | |||
| 43 | /// <summary> | ||
| 44 | /// Raises this object's PropertyChanged event. | ||
| 45 | /// </summary> | ||
| 46 | /// <param name="propertyName">The property that has a new value.</param> | ||
| 47 | protected virtual void OnPropertyChanged(string propertyName) | ||
| 48 | { | ||
| 49 | this.VerifyPropertyName(propertyName); | ||
| 50 | |||
| 51 | PropertyChangedEventHandler handler = this.PropertyChanged; | ||
| 52 | if (null != handler) | ||
| 53 | { | ||
| 54 | PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName); | ||
| 55 | handler(this, e); | ||
| 56 | } | ||
| 57 | } | ||
| 58 | } | ||
| 59 | } | ||
diff --git a/src/WixToolset.WixBA/RelayCommand.cs b/src/WixToolset.WixBA/RelayCommand.cs new file mode 100644 index 00000000..ecc482da --- /dev/null +++ b/src/WixToolset.WixBA/RelayCommand.cs | |||
| @@ -0,0 +1,45 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.UX | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Diagnostics; | ||
| 7 | using System.Windows.Input; | ||
| 8 | |||
| 9 | /// <summary> | ||
| 10 | /// Base class that implements ICommand interface via delegates. | ||
| 11 | /// </summary> | ||
| 12 | public class RelayCommand : ICommand | ||
| 13 | { | ||
| 14 | private readonly Action<object> execute; | ||
| 15 | private readonly Predicate<object> canExecute; | ||
| 16 | |||
| 17 | public RelayCommand(Action<object> execute) | ||
| 18 | : this(execute, null) | ||
| 19 | { | ||
| 20 | } | ||
| 21 | |||
| 22 | public RelayCommand(Action<object> execute, Predicate<object> canExecute) | ||
| 23 | { | ||
| 24 | this.execute = execute; | ||
| 25 | this.canExecute = canExecute; | ||
| 26 | } | ||
| 27 | |||
| 28 | public event EventHandler CanExecuteChanged | ||
| 29 | { | ||
| 30 | add { CommandManager.RequerySuggested += value; } | ||
| 31 | remove { CommandManager.RequerySuggested -= value; } | ||
| 32 | } | ||
| 33 | |||
| 34 | [DebuggerStepThrough] | ||
| 35 | public bool CanExecute(object parameter) | ||
| 36 | { | ||
| 37 | return this.canExecute == null ? true : this.canExecute(parameter); | ||
| 38 | } | ||
| 39 | |||
| 40 | public void Execute(object parameter) | ||
| 41 | { | ||
| 42 | this.execute(parameter); | ||
| 43 | } | ||
| 44 | } | ||
| 45 | } | ||
diff --git a/src/WixToolset.WixBA/Resources/logo-black-hollow.png b/src/WixToolset.WixBA/Resources/logo-black-hollow.png new file mode 100644 index 00000000..9d0290bd --- /dev/null +++ b/src/WixToolset.WixBA/Resources/logo-black-hollow.png | |||
| Binary files differ | |||
diff --git a/src/WixToolset.WixBA/Resources/logo-white-hollow.png b/src/WixToolset.WixBA/Resources/logo-white-hollow.png new file mode 100644 index 00000000..242c7350 --- /dev/null +++ b/src/WixToolset.WixBA/Resources/logo-white-hollow.png | |||
| Binary files differ | |||
diff --git a/src/WixToolset.WixBA/RootView.xaml b/src/WixToolset.WixBA/RootView.xaml new file mode 100644 index 00000000..bbf2f9f2 --- /dev/null +++ b/src/WixToolset.WixBA/RootView.xaml | |||
| @@ -0,0 +1,420 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8" ?> | ||
| 2 | <!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> | ||
| 3 | |||
| 4 | <Window x:Class="WixToolset.UX.RootView" | ||
| 5 | xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | ||
| 6 | xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | ||
| 7 | xmlns:ux="clr-namespace:WixToolset.UX" | ||
| 8 | MinWidth="550" | ||
| 9 | MinHeight="400" | ||
| 10 | Width="750" | ||
| 11 | Height="400" | ||
| 12 | AllowsTransparency="False" | ||
| 13 | Background="{x:Null}" | ||
| 14 | Closing="Window_Closing" | ||
| 15 | ResizeMode="CanResizeWithGrip" | ||
| 16 | WindowStartupLocation="CenterScreen" | ||
| 17 | WindowStyle="ThreeDBorderWindow"> | ||
| 18 | |||
| 19 | <Window.Resources> | ||
| 20 | <ResourceDictionary> | ||
| 21 | <ResourceDictionary.MergedDictionaries> | ||
| 22 | <ResourceDictionary Source="pack://application:,,,/WixBA;component/Styles.xaml" /> | ||
| 23 | </ResourceDictionary.MergedDictionaries> | ||
| 24 | </ResourceDictionary> | ||
| 25 | </Window.Resources> | ||
| 26 | |||
| 27 | <Border Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}" | ||
| 28 | BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}" | ||
| 29 | BorderThickness="0"> | ||
| 30 | |||
| 31 | <DockPanel x:Name="AppArtBoardArea" | ||
| 32 | Margin="20, 10"> | ||
| 33 | |||
| 34 | <DockPanel x:Name="ActionArea" | ||
| 35 | DockPanel.Dock="Bottom"> | ||
| 36 | |||
| 37 | <Grid x:Name="ActionGrid"> | ||
| 38 | <Grid.RowDefinitions> | ||
| 39 | <RowDefinition Height="Auto" /> | ||
| 40 | </Grid.RowDefinitions> | ||
| 41 | <Grid.ColumnDefinitions> | ||
| 42 | <ColumnDefinition Width="*" MinWidth="150" /> | ||
| 43 | <ColumnDefinition Width="Auto" /> | ||
| 44 | </Grid.ColumnDefinitions> | ||
| 45 | |||
| 46 | <StackPanel x:Name="LeftMarqueeDcIsland" | ||
| 47 | Grid.Column="0" | ||
| 48 | VerticalAlignment="Center" | ||
| 49 | DataContext="{Binding UpdateViewModel}"> | ||
| 50 | <ProgressBar x:Name="CheckingUpdatesProgress" | ||
| 51 | MinHeight="10" | ||
| 52 | IsIndeterminate="{Binding CheckingEnabled}" | ||
| 53 | Style="{DynamicResource UpdateMarqueeStyle}" | ||
| 54 | Visibility="{Binding CheckingEnabled, | ||
| 55 | Converter={StaticResource BooleanToVisibilityConverter}}" /> | ||
| 56 | </StackPanel> | ||
| 57 | |||
| 58 | <StackPanel x:Name="InitiateActionArea" | ||
| 59 | Grid.Column="1" | ||
| 60 | Margin="30, 0, 0, 0" | ||
| 61 | HorizontalAlignment="Right" | ||
| 62 | Orientation="Horizontal"> | ||
| 63 | <!-- Displayed from left to right, when ordered from top to bottom. Most preferred is at the top. --> | ||
| 64 | <StackPanel x:Name="UpdateBtnDcIsland" | ||
| 65 | DataContext="{Binding UpdateViewModel}" | ||
| 66 | Style="{DynamicResource ActionBtnStkPnlStyle}"> | ||
| 67 | <Button AutomationProperties.HelpText="An update is available. Click to get the update" | ||
| 68 | AutomationProperties.Name="Update" | ||
| 69 | Command="{Binding UpdateCommand}" | ||
| 70 | Content="Update" | ||
| 71 | Style="{DynamicResource ActionButtonStyle}" | ||
| 72 | Visibility="{Binding CanUpdate, | ||
| 73 | Converter={StaticResource BooleanToVisibilityConverter}}" /> | ||
| 74 | </StackPanel> | ||
| 75 | <StackPanel x:Name="InstallBtnDcIsland" | ||
| 76 | DataContext="{Binding InstallationViewModel}" | ||
| 77 | Style="{DynamicResource ActionBtnStkPnlStyle}"> | ||
| 78 | <Button AutomationProperties.HelpText="Click to accept license and install" | ||
| 79 | AutomationProperties.Name="Install" | ||
| 80 | Command="{Binding InstallCommand}" | ||
| 81 | Content="_Install" | ||
| 82 | IsDefault="True" | ||
| 83 | Style="{DynamicResource ActionButtonStyle}" | ||
| 84 | Visibility="{Binding InstallEnabled, | ||
| 85 | Converter={StaticResource BooleanToVisibilityConverter}}" /> | ||
| 86 | </StackPanel> | ||
| 87 | <StackPanel x:Name="RepairBtnDcIsland" | ||
| 88 | DataContext="{Binding InstallationViewModel}" | ||
| 89 | Style="{DynamicResource ActionBtnStkPnlStyle}"> | ||
| 90 | <Button x:Name="RepairButton" | ||
| 91 | AutomationProperties.HelpText="Click to Repair" | ||
| 92 | AutomationProperties.Name="Repair" | ||
| 93 | Command="{Binding RepairCommand}" | ||
| 94 | Content="Repair" | ||
| 95 | Style="{DynamicResource ActionButtonStyle}" | ||
| 96 | Visibility="{Binding RepairEnabled, | ||
| 97 | Converter={StaticResource BooleanToVisibilityConverter}}" /> | ||
| 98 | </StackPanel> | ||
| 99 | <StackPanel x:Name="UninstallBtnDcIsland" | ||
| 100 | DataContext="{Binding InstallationViewModel}" | ||
| 101 | Style="{DynamicResource ActionBtnStkPnlStyle}"> | ||
| 102 | <Button AutomationProperties.HelpText="Click to Uninstall" | ||
| 103 | AutomationProperties.Name="Uninstall" | ||
| 104 | Command="{Binding UninstallCommand}" | ||
| 105 | Content="Uninstall" | ||
| 106 | Style="{DynamicResource ActionButtonStyle}" | ||
| 107 | Visibility="{Binding UninstallEnabled, | ||
| 108 | Converter={StaticResource BooleanToVisibilityConverter}}" /> | ||
| 109 | </StackPanel> | ||
| 110 | <StackPanel x:Name="TryAgainBtnDcIsland" | ||
| 111 | DataContext="{Binding InstallationViewModel}" | ||
| 112 | Style="{DynamicResource ActionBtnStkPnlStyle}"> | ||
| 113 | <Button HorizontalAlignment="Center" | ||
| 114 | VerticalAlignment="Center" | ||
| 115 | AutomationProperties.HelpText="Click to try again" | ||
| 116 | AutomationProperties.Name="Try Again?" | ||
| 117 | Command="{Binding TryAgainCommand}" | ||
| 118 | Content="Try Again?" | ||
| 119 | Style="{DynamicResource ActionButtonStyle}" | ||
| 120 | Visibility="{Binding IsFailedCompletion, | ||
| 121 | Converter={StaticResource BooleanToVisibilityConverter}}" /> | ||
| 122 | </StackPanel> | ||
| 123 | |||
| 124 | <!-- Final Actions - only one of these is expected to be displayed, if any --> | ||
| 125 | <StackPanel x:Name="CloseBtnDcIsland" | ||
| 126 | HorizontalAlignment="Right" | ||
| 127 | DataContext="{Binding InstallationViewModel}" | ||
| 128 | Style="{DynamicResource ActionBtnStkPnlStyle}"> | ||
| 129 | <Button x:Name="CloseButton" | ||
| 130 | AutomationProperties.HelpText="Click to Close" | ||
| 131 | AutomationProperties.Name="Close" | ||
| 132 | Command="{Binding CloseCommand}" | ||
| 133 | Content="Close" | ||
| 134 | Style="{DynamicResource FinalActionButtonStyle}" | ||
| 135 | Visibility="{Binding IsSuccessfulCompletion, | ||
| 136 | Converter={StaticResource BooleanToVisibilityConverter}}" /> | ||
| 137 | </StackPanel> | ||
| 138 | <StackPanel x:Name="CancelBtnDcIsland" | ||
| 139 | HorizontalAlignment="Right" | ||
| 140 | DataContext="{Binding}" | ||
| 141 | Style="{DynamicResource ActionBtnStkPnlStyle}"> | ||
| 142 | <Button AutomationProperties.HelpText="Press to Cancel" | ||
| 143 | AutomationProperties.Name="Cancel" | ||
| 144 | Command="{Binding CancelCommand}" | ||
| 145 | Content="Cancel" | ||
| 146 | Style="{DynamicResource FinalActionButtonStyle}" | ||
| 147 | Visibility="{Binding CancelAvailable, | ||
| 148 | Converter={StaticResource BooleanToVisibilityConverter}}" /> | ||
| 149 | </StackPanel> | ||
| 150 | </StackPanel> | ||
| 151 | </Grid> | ||
| 152 | </DockPanel> | ||
| 153 | |||
| 154 | <StackPanel x:Name="FinalStatusArea" | ||
| 155 | DataContext="{Binding InstallationViewModel}" | ||
| 156 | HorizontalAlignment="Right" | ||
| 157 | Margin="0,10,0,20" | ||
| 158 | Orientation="Horizontal" | ||
| 159 | Visibility="{Binding IsComplete, | ||
| 160 | Converter={StaticResource BooleanToVisibilityConverter}}" | ||
| 161 | DockPanel.Dock="Bottom"> | ||
| 162 | <TextBlock x:Name="StatusTextBlk" | ||
| 163 | Style="{DynamicResource LabelTextBlkStyle}" | ||
| 164 | Text="{Binding StatusText, StringFormat={}{0}:}" | ||
| 165 | TextBlock.FontWeight="Bold" /> | ||
| 166 | <Button x:Name="ViewLogLink" | ||
| 167 | Margin="10,2,10,0" | ||
| 168 | Command="{Binding OpenLogCommand}" | ||
| 169 | Style="{StaticResource HyperlinkedButtonStyle}"> | ||
| 170 | <TextBlock> | ||
| 171 | <Hyperlink Command="{Binding OpenLogCommand}"><Run FontSize="{DynamicResource FontSizeButton}" Text="View Log" /></Hyperlink> | ||
| 172 | </TextBlock> | ||
| 173 | </Button> | ||
| 174 | <Button x:Name="ViewLogFolderLink" | ||
| 175 | Command="{Binding OpenLogFolderCommand}" | ||
| 176 | Style="{StaticResource HyperlinkedButtonStyle}"> | ||
| 177 | <TextBlock> | ||
| 178 | <Hyperlink Command="{Binding OpenLogFolderCommand}"><Run FontSize="{DynamicResource FontSizeButton}" Text="View Log Folder" /></Hyperlink> | ||
| 179 | </TextBlock> | ||
| 180 | </Button> | ||
| 181 | </StackPanel> | ||
| 182 | |||
| 183 | <StackPanel x:Name="StatusStkPnlDcIsland" | ||
| 184 | Margin="0,10" | ||
| 185 | DataContext="{Binding ProgressViewModel}" | ||
| 186 | DockPanel.Dock="Bottom"> | ||
| 187 | |||
| 188 | <StackPanel x:Name="ActionPackageNameTextStkPnl" | ||
| 189 | VerticalAlignment="Top"> | ||
| 190 | <TextBlock Style="{StaticResource StatusTextStyle}" | ||
| 191 | Text="{Binding Message}" | ||
| 192 | TextWrapping="WrapWithOverflow" | ||
| 193 | Visibility="{Binding ProgressEnabled, | ||
| 194 | Converter={StaticResource BooleanToVisibilityConverter}}" /> | ||
| 195 | </StackPanel> | ||
| 196 | |||
| 197 | <StackPanel x:Name="ActionProgressStkPnl" | ||
| 198 | Margin="0,5,0,0"> | ||
| 199 | <ProgressBar x:Name="ActionProgress" | ||
| 200 | Height="20" | ||
| 201 | VerticalAlignment="Center" | ||
| 202 | Style="{DynamicResource ActionProgressStyle}" | ||
| 203 | Visibility="{Binding ProgressEnabled, | ||
| 204 | Converter={StaticResource BooleanToVisibilityConverter}}" | ||
| 205 | Value="{Binding Progress}" /> | ||
| 206 | </StackPanel> | ||
| 207 | </StackPanel> | ||
| 208 | |||
| 209 | <StackPanel x:Name="TitleAndLogoStkPnl" | ||
| 210 | DockPanel.Dock="Top"> | ||
| 211 | <Grid x:Name="TitleGrid"> | ||
| 212 | <Grid.RowDefinitions> | ||
| 213 | <RowDefinition Height="Auto" /> | ||
| 214 | </Grid.RowDefinitions> | ||
| 215 | <Grid.ColumnDefinitions> | ||
| 216 | <ColumnDefinition Width="Auto" /> | ||
| 217 | <ColumnDefinition Width="*" /> | ||
| 218 | <ColumnDefinition Width="Auto" /> | ||
| 219 | </Grid.ColumnDefinitions> | ||
| 220 | |||
| 221 | <TextBlock x:Name="TitleTextBlk" | ||
| 222 | Grid.Column="0" | ||
| 223 | Style="{DynamicResource TitleTextBlkStyle}" | ||
| 224 | Text="{Binding Title, | ||
| 225 | Mode=OneTime}" /> | ||
| 226 | <Image x:Name="Logo" | ||
| 227 | Grid.Column="2" | ||
| 228 | Style="{DynamicResource LogoStyle}"> | ||
| 229 | </Image> | ||
| 230 | </Grid> | ||
| 231 | </StackPanel> | ||
| 232 | |||
| 233 | <Grid x:Name="MainStkPnl"> | ||
| 234 | <Grid.RowDefinitions> | ||
| 235 | <RowDefinition Height="*" /> | ||
| 236 | <RowDefinition Height="Auto" /> | ||
| 237 | </Grid.RowDefinitions> | ||
| 238 | <Grid.ColumnDefinitions> | ||
| 239 | <ColumnDefinition Width="Auto" /> | ||
| 240 | <ColumnDefinition Width="1*" /> | ||
| 241 | <ColumnDefinition Width="4*" MinWidth="200" /> | ||
| 242 | </Grid.ColumnDefinitions> | ||
| 243 | |||
| 244 | <Grid x:Name="SkuInfo" | ||
| 245 | Grid.Column="0" | ||
| 246 | Grid.Row="0" | ||
| 247 | DataContext="{Binding InstallationViewModel}"> | ||
| 248 | <Grid.RowDefinitions> | ||
| 249 | <RowDefinition Height="Auto" /> | ||
| 250 | <RowDefinition Height="Auto" /> | ||
| 251 | <RowDefinition Height="Auto" /> | ||
| 252 | <RowDefinition Height="Auto" /> | ||
| 253 | <RowDefinition Height="Auto" /> | ||
| 254 | <RowDefinition Height="Auto" /> | ||
| 255 | <RowDefinition Height="Auto" /> | ||
| 256 | </Grid.RowDefinitions> | ||
| 257 | <Grid.ColumnDefinitions> | ||
| 258 | <ColumnDefinition Width="Auto" /> | ||
| 259 | <ColumnDefinition Width="*" /> | ||
| 260 | </Grid.ColumnDefinitions> | ||
| 261 | |||
| 262 | <TextBlock x:Name="SkuPublisherLabel" | ||
| 263 | Grid.Row="0" | ||
| 264 | Grid.Column="0" | ||
| 265 | Style="{DynamicResource LabelTextBlkStyle}" | ||
| 266 | Text="Publisher:" /> | ||
| 267 | <TextBlock x:Name="SkuPublisherData" | ||
| 268 | Grid.Row="0" | ||
| 269 | Grid.Column="1" | ||
| 270 | Style="{DynamicResource DataTextBlkStyle}" | ||
| 271 | Text="{Binding Publisher, | ||
| 272 | Mode=OneTime}" /> | ||
| 273 | |||
| 274 | <TextBlock x:Name="SkuVersionLabel" | ||
| 275 | Grid.Row="1" | ||
| 276 | Grid.Column="0" | ||
| 277 | Style="{DynamicResource LabelTextBlkStyle}" | ||
| 278 | Text="Version:" /> | ||
| 279 | <TextBlock Grid.Row="1" | ||
| 280 | Grid.Column="1" | ||
| 281 | Style="{DynamicResource DataTextBlkStyle}" | ||
| 282 | Text="{Binding Version}" /> | ||
| 283 | |||
| 284 | <TextBlock x:Name="SkuLicenseLabel" | ||
| 285 | Grid.Row="2" | ||
| 286 | Grid.Column="0" | ||
| 287 | Style="{DynamicResource LabelTextBlkStyle}" | ||
| 288 | Text="License:" /> | ||
| 289 | <Button x:Name="SkuLicenseBtn" | ||
| 290 | Grid.Row="2" | ||
| 291 | Grid.Column="1" | ||
| 292 | HorizontalAlignment="Left" | ||
| 293 | AutomationProperties.HelpText="View the license" | ||
| 294 | AutomationProperties.Name="License" | ||
| 295 | Command="{Binding LicenseCommand}" | ||
| 296 | KeyboardNavigation.IsTabStop="False" | ||
| 297 | Style="{StaticResource HyperlinkedButtonStyle}"> | ||
| 298 | <TextBlock HorizontalAlignment="Left"> | ||
| 299 | <Hyperlink Command="{Binding LicenseCommand}" | ||
| 300 | IsEnabled="True" | ||
| 301 | KeyboardNavigation.IsTabStop="False"><Run FontSize="{DynamicResource FontSizeButton}" Text="View License" /></Hyperlink> | ||
| 302 | </TextBlock> | ||
| 303 | </Button> | ||
| 304 | |||
| 305 | <TextBlock x:Name="SkuNewsLabel" | ||
| 306 | Grid.Row="3" | ||
| 307 | Grid.Column="0" | ||
| 308 | Style="{DynamicResource LabelTextBlkStyle}" | ||
| 309 | Text="News:" /> | ||
| 310 | <Button x:Name="SkuNewsBtn" | ||
| 311 | Grid.Row="3" | ||
| 312 | Grid.Column="1" | ||
| 313 | HorizontalAlignment="Left" | ||
| 314 | AutomationProperties.HelpText="Latest News" | ||
| 315 | AutomationProperties.Name="News" | ||
| 316 | Command="{Binding LaunchNewsCommand}" | ||
| 317 | KeyboardNavigation.IsTabStop="False" | ||
| 318 | Style="{StaticResource HyperlinkedButtonStyle}"> | ||
| 319 | <TextBlock HorizontalAlignment="Left"> | ||
| 320 | <Hyperlink Command="{Binding LaunchNewsCommand}" | ||
| 321 | IsEnabled="True" | ||
| 322 | KeyboardNavigation.IsTabStop="False"><Run FontSize="{DynamicResource FontSizeButton}" Text="Latest News" /></Hyperlink> | ||
| 323 | </TextBlock> | ||
| 324 | </Button> | ||
| 325 | |||
| 326 | <TextBlock x:Name="SkuSupportLabel" | ||
| 327 | Grid.Row="4" | ||
| 328 | Grid.Column="0" | ||
| 329 | Style="{DynamicResource LabelTextBlkStyle}" | ||
| 330 | Text="Support:" /> | ||
| 331 | <Button x:Name="SkuSupportBtn" | ||
| 332 | Grid.Row="4" | ||
| 333 | Grid.Column="1" | ||
| 334 | HorizontalAlignment="Left" | ||
| 335 | AutomationProperties.HelpText="View Home Page for Support" | ||
| 336 | AutomationProperties.Name="Home Page" | ||
| 337 | Command="{Binding LaunchHomePageCommand}" | ||
| 338 | KeyboardNavigation.IsTabStop="False" | ||
| 339 | Style="{StaticResource HyperlinkedButtonStyle}"> | ||
| 340 | <TextBlock HorizontalAlignment="Left"> | ||
| 341 | <Hyperlink Command="{Binding LaunchHomePageCommand}" | ||
| 342 | IsEnabled="True" | ||
| 343 | KeyboardNavigation.IsTabStop="False"><Run FontSize="{DynamicResource FontSizeButton}" Text="{Binding SupportUrl, Mode=OneTime}" /></Hyperlink> | ||
| 344 | </TextBlock> | ||
| 345 | </Button> | ||
| 346 | |||
| 347 | <TextBlock x:Name="SkuVSExtensionLabel1" | ||
| 348 | Grid.Row="5" | ||
| 349 | Grid.Column="0" | ||
| 350 | Style="{DynamicResource LabelTextBlkStyle}" | ||
| 351 | Text="Visual Studio" /> | ||
| 352 | <TextBlock x:Name="SkuVSExtensionLabel2" | ||
| 353 | Grid.Row="6" | ||
| 354 | Grid.Column="0" | ||
| 355 | Style="{DynamicResource LabelTextBlkStyle}" | ||
| 356 | Text="Extension:" /> | ||
| 357 | <Button x:Name="SkuVSExtensionBtn" | ||
| 358 | Grid.Row="6" | ||
| 359 | Grid.Column="1" | ||
| 360 | HorizontalAlignment="Left" | ||
| 361 | AutomationProperties.HelpText="View Releases Page for VS Extension" | ||
| 362 | AutomationProperties.Name="Releases Page" | ||
| 363 | Command="{Binding LaunchVSExtensionPageCommand}" | ||
| 364 | KeyboardNavigation.IsTabStop="False" | ||
| 365 | Style="{StaticResource HyperlinkedButtonStyle}"> | ||
| 366 | <TextBlock HorizontalAlignment="Left"> | ||
| 367 | <Hyperlink Command="{Binding LaunchVSExtensionPageCommand}" | ||
| 368 | IsEnabled="True" | ||
| 369 | KeyboardNavigation.IsTabStop="False"><Run FontSize="{DynamicResource FontSizeButton}" Text="{Binding VSExtensionUrl, Mode=OneTime}" /></Hyperlink> | ||
| 370 | </TextBlock> | ||
| 371 | </Button> | ||
| 372 | </Grid> | ||
| 373 | |||
| 374 | <DockPanel x:Name="UpdateChangesStkPnlDcIsland" | ||
| 375 | Grid.Row="0" | ||
| 376 | Grid.Column="2" | ||
| 377 | DataContext="{Binding UpdateViewModel}" | ||
| 378 | Visibility="{Binding IsUpdateAvailable, | ||
| 379 | Converter={StaticResource BooleanToVisibilityConverter}}"> | ||
| 380 | |||
| 381 | <Grid x:Name="UpdateInfoGrid" | ||
| 382 | DockPanel.Dock="Top"> | ||
| 383 | <Grid.RowDefinitions> | ||
| 384 | <RowDefinition Height="Auto" /> | ||
| 385 | </Grid.RowDefinitions> | ||
| 386 | <Grid.ColumnDefinitions> | ||
| 387 | <ColumnDefinition Width="Auto" /> | ||
| 388 | <ColumnDefinition Width="*" /> | ||
| 389 | </Grid.ColumnDefinitions> | ||
| 390 | |||
| 391 | <TextBlock x:Name="UpdateTitleLabel" | ||
| 392 | Grid.Row="0" | ||
| 393 | Grid.Column="0" | ||
| 394 | Style="{DynamicResource LabelTextBlkStyle}" | ||
| 395 | Text="Available Update:" /> | ||
| 396 | |||
| 397 | <TextBlock x:Name="UpdateVersionLabel" | ||
| 398 | Grid.Row="0" | ||
| 399 | Grid.Column="1" | ||
| 400 | Style="{DynamicResource DataTextBlkStyle}" | ||
| 401 | Text="{Binding UpdateVersion}" /> | ||
| 402 | </Grid> | ||
| 403 | |||
| 404 | <WebBrowser DockPanel.Dock="Bottom" | ||
| 405 | ux:BrowserProperties.HtmlDoc="{Binding UpdateChanges}" /> | ||
| 406 | </DockPanel> | ||
| 407 | |||
| 408 | <TextBlock x:Name="DowngradeMessageTextBlk" | ||
| 409 | Grid.Row="1" | ||
| 410 | Grid.Column="0" | ||
| 411 | Grid.ColumnSpan="3" | ||
| 412 | DataContext="{Binding InstallationViewModel}" | ||
| 413 | Style="{DynamicResource LabelTextBlkStyle}" | ||
| 414 | Text="{Binding DowngradeMessage}" | ||
| 415 | Visibility="{Binding Downgrade, | ||
| 416 | Converter={StaticResource BooleanToVisibilityConverter}}" /> | ||
| 417 | </Grid> | ||
| 418 | </DockPanel> | ||
| 419 | </Border> | ||
| 420 | </Window> | ||
diff --git a/src/WixToolset.WixBA/RootView.xaml.cs b/src/WixToolset.WixBA/RootView.xaml.cs new file mode 100644 index 00000000..1d4301f2 --- /dev/null +++ b/src/WixToolset.WixBA/RootView.xaml.cs | |||
| @@ -0,0 +1,51 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.UX | ||
| 4 | { | ||
| 5 | using System.ComponentModel; | ||
| 6 | using System.Windows; | ||
| 7 | using System.Windows.Interop; | ||
| 8 | |||
| 9 | /// <summary> | ||
| 10 | /// Interaction logic for View.xaml | ||
| 11 | /// </summary> | ||
| 12 | public partial class RootView : Window | ||
| 13 | { | ||
| 14 | /// <summary> | ||
| 15 | /// Creates the view populated with it's model. | ||
| 16 | /// </summary> | ||
| 17 | /// <param name="viewModel">Model for the view.</param> | ||
| 18 | public RootView(RootViewModel viewModel) | ||
| 19 | { | ||
| 20 | this.DataContext = viewModel; | ||
| 21 | |||
| 22 | this.Loaded += (sender, e) => WixBA.Model.Engine.CloseSplashScreen(); | ||
| 23 | this.Closed += (sender, e) => this.Dispatcher.InvokeShutdown(); // shutdown dispatcher when the window is closed. | ||
| 24 | |||
| 25 | this.InitializeComponent(); | ||
| 26 | |||
| 27 | viewModel.Dispatcher = this.Dispatcher; | ||
| 28 | viewModel.ViewWindowHandle = new WindowInteropHelper(this).EnsureHandle(); | ||
| 29 | } | ||
| 30 | |||
| 31 | /// <summary> | ||
| 32 | /// Event is fired when the window is closing. | ||
| 33 | /// </summary> | ||
| 34 | /// <param name="sender"></param> | ||
| 35 | /// <param name="e"></param> | ||
| 36 | private void Window_Closing(object sender, CancelEventArgs e) | ||
| 37 | { | ||
| 38 | RootViewModel rvm = this.DataContext as RootViewModel; | ||
| 39 | if ((null != rvm) && (InstallationState.Applying == rvm.InstallState)) | ||
| 40 | { | ||
| 41 | rvm.CancelButton_Click(); | ||
| 42 | if (rvm.Canceled) | ||
| 43 | { | ||
| 44 | // Defer closing until the engine has canceled processing. | ||
| 45 | e.Cancel = true; | ||
| 46 | rvm.AutoClose = true; | ||
| 47 | } | ||
| 48 | } | ||
| 49 | } | ||
| 50 | } | ||
| 51 | } | ||
diff --git a/src/WixToolset.WixBA/RootViewModel.cs b/src/WixToolset.WixBA/RootViewModel.cs new file mode 100644 index 00000000..1de89adf --- /dev/null +++ b/src/WixToolset.WixBA/RootViewModel.cs | |||
| @@ -0,0 +1,204 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.UX | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Diagnostics; | ||
| 7 | using System.Reflection; | ||
| 8 | using System.Windows; | ||
| 9 | using System.Windows.Input; | ||
| 10 | using System.Windows.Threading; | ||
| 11 | using WixToolset.Bootstrapper; | ||
| 12 | |||
| 13 | /// <summary> | ||
| 14 | /// The errors returned from the engine | ||
| 15 | /// </summary> | ||
| 16 | public enum Error | ||
| 17 | { | ||
| 18 | UserCancelled = 1223, | ||
| 19 | } | ||
| 20 | |||
| 21 | /// <summary> | ||
| 22 | /// The model of the root view in WixBA. | ||
| 23 | /// </summary> | ||
| 24 | public class RootViewModel : PropertyNotifyBase | ||
| 25 | { | ||
| 26 | private ICommand cancelCommand; | ||
| 27 | private ICommand closeCommand; | ||
| 28 | |||
| 29 | private bool canceled; | ||
| 30 | private InstallationState installState; | ||
| 31 | private DetectionState detectState; | ||
| 32 | |||
| 33 | /// <summary> | ||
| 34 | /// Creates a new model of the root view. | ||
| 35 | /// </summary> | ||
| 36 | public RootViewModel() | ||
| 37 | { | ||
| 38 | this.InstallationViewModel = new InstallationViewModel(this); | ||
| 39 | this.ProgressViewModel = new ProgressViewModel(this); | ||
| 40 | this.UpdateViewModel = new UpdateViewModel(this); | ||
| 41 | } | ||
| 42 | |||
| 43 | public InstallationViewModel InstallationViewModel { get; private set; } | ||
| 44 | public ProgressViewModel ProgressViewModel { get; private set; } | ||
| 45 | public UpdateViewModel UpdateViewModel { get; private set; } | ||
| 46 | public Dispatcher Dispatcher { get; set; } | ||
| 47 | public IntPtr ViewWindowHandle { get; set; } | ||
| 48 | public bool AutoClose { get; set; } | ||
| 49 | |||
| 50 | public ICommand CloseCommand | ||
| 51 | { | ||
| 52 | get | ||
| 53 | { | ||
| 54 | if (this.closeCommand == null) | ||
| 55 | { | ||
| 56 | this.closeCommand = new RelayCommand(param => WixBA.View.Close()); | ||
| 57 | } | ||
| 58 | |||
| 59 | return this.closeCommand; | ||
| 60 | } | ||
| 61 | } | ||
| 62 | |||
| 63 | public ICommand CancelCommand | ||
| 64 | { | ||
| 65 | get | ||
| 66 | { | ||
| 67 | if (this.cancelCommand == null) | ||
| 68 | { | ||
| 69 | this.cancelCommand = new RelayCommand(param => | ||
| 70 | { | ||
| 71 | this.CancelButton_Click(); | ||
| 72 | }, | ||
| 73 | param => !this.Canceled); | ||
| 74 | } | ||
| 75 | |||
| 76 | return this.cancelCommand; | ||
| 77 | } | ||
| 78 | } | ||
| 79 | |||
| 80 | public bool CancelAvailable | ||
| 81 | { | ||
| 82 | get { return InstallationState.Applying == this.InstallState; } | ||
| 83 | } | ||
| 84 | |||
| 85 | public bool Canceled | ||
| 86 | { | ||
| 87 | get | ||
| 88 | { | ||
| 89 | return this.canceled; | ||
| 90 | } | ||
| 91 | |||
| 92 | set | ||
| 93 | { | ||
| 94 | if (this.canceled != value) | ||
| 95 | { | ||
| 96 | this.canceled = value; | ||
| 97 | base.OnPropertyChanged("Canceled"); | ||
| 98 | } | ||
| 99 | } | ||
| 100 | } | ||
| 101 | |||
| 102 | /// <summary> | ||
| 103 | /// Gets and sets the detect state of the view's model. | ||
| 104 | /// </summary> | ||
| 105 | public DetectionState DetectState | ||
| 106 | { | ||
| 107 | get | ||
| 108 | { | ||
| 109 | return this.detectState; | ||
| 110 | } | ||
| 111 | |||
| 112 | set | ||
| 113 | { | ||
| 114 | if (this.detectState != value) | ||
| 115 | { | ||
| 116 | this.detectState = value; | ||
| 117 | |||
| 118 | // Notify all the properties derived from the state that the state changed. | ||
| 119 | base.OnPropertyChanged("DetectState"); | ||
| 120 | } | ||
| 121 | } | ||
| 122 | } | ||
| 123 | |||
| 124 | /// <summary> | ||
| 125 | /// Gets and sets the installation state of the view's model. | ||
| 126 | /// </summary> | ||
| 127 | public InstallationState InstallState | ||
| 128 | { | ||
| 129 | get | ||
| 130 | { | ||
| 131 | return this.installState; | ||
| 132 | } | ||
| 133 | |||
| 134 | set | ||
| 135 | { | ||
| 136 | if (this.installState != value) | ||
| 137 | { | ||
| 138 | this.installState = value; | ||
| 139 | |||
| 140 | // Notify all the properties derived from the state that the state changed. | ||
| 141 | base.OnPropertyChanged("InstallState"); | ||
| 142 | base.OnPropertyChanged("CancelAvailable"); | ||
| 143 | } | ||
| 144 | } | ||
| 145 | } | ||
| 146 | |||
| 147 | /// <summary> | ||
| 148 | /// Gets and sets the state of the view's model before apply begins in order to return to that state if cancel or rollback occurs. | ||
| 149 | /// </summary> | ||
| 150 | public InstallationState PreApplyState { get; set; } | ||
| 151 | |||
| 152 | /// <summary> | ||
| 153 | /// Gets and sets the path where the bundle is currently installed or will be installed. | ||
| 154 | /// </summary> | ||
| 155 | public string InstallDirectory | ||
| 156 | { | ||
| 157 | get | ||
| 158 | { | ||
| 159 | return WixBA.Model.InstallDirectory; | ||
| 160 | } | ||
| 161 | |||
| 162 | set | ||
| 163 | { | ||
| 164 | if (WixBA.Model.InstallDirectory != value) | ||
| 165 | { | ||
| 166 | WixBA.Model.InstallDirectory = value; | ||
| 167 | base.OnPropertyChanged("InstallDirectory"); | ||
| 168 | } | ||
| 169 | } | ||
| 170 | } | ||
| 171 | |||
| 172 | /// <summary> | ||
| 173 | /// The Title of this bundle. | ||
| 174 | /// </summary> | ||
| 175 | public string Title | ||
| 176 | { | ||
| 177 | get | ||
| 178 | { | ||
| 179 | return WixDistribution.ShortProduct; | ||
| 180 | } | ||
| 181 | } | ||
| 182 | |||
| 183 | /// <summary> | ||
| 184 | /// Prompts the user to make sure they want to cancel. | ||
| 185 | /// This needs to run on the UI thread, use Dispatcher.Invoke to call this from a background thread. | ||
| 186 | /// </summary> | ||
| 187 | public void CancelButton_Click() | ||
| 188 | { | ||
| 189 | if (this.Canceled) | ||
| 190 | { | ||
| 191 | return; | ||
| 192 | } | ||
| 193 | |||
| 194 | if (Display.Full == WixBA.Model.Command.Display) | ||
| 195 | { | ||
| 196 | this.Canceled = (MessageBoxResult.Yes == MessageBox.Show(WixBA.View, "Are you sure you want to cancel?", "WiX Toolset", MessageBoxButton.YesNo, MessageBoxImage.Error)); | ||
| 197 | } | ||
| 198 | else | ||
| 199 | { | ||
| 200 | this.Canceled = true; | ||
| 201 | } | ||
| 202 | } | ||
| 203 | } | ||
| 204 | } | ||
diff --git a/src/WixToolset.WixBA/Styles.xaml b/src/WixToolset.WixBA/Styles.xaml new file mode 100644 index 00000000..89ccd2ea --- /dev/null +++ b/src/WixToolset.WixBA/Styles.xaml | |||
| @@ -0,0 +1,194 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8" ?> | ||
| 2 | <!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> | ||
| 3 | |||
| 4 | <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | ||
| 5 | xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | ||
| 6 | xmlns:System="clr-namespace:System;assembly=mscorlib" | ||
| 7 | xmlns:po="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" | ||
| 8 | xmlns:shell="clr-namespace:System.Windows.Shell;assembly=PresentationFramework" | ||
| 9 | xmlns:ux="clr-namespace:WixToolset.UX"> | ||
| 10 | |||
| 11 | <!-- Converters --> | ||
| 12 | <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" /> | ||
| 13 | |||
| 14 | <!-- Fonts --> | ||
| 15 | <FontFamily x:Key="FontFamily">Segoe UI, Arial</FontFamily> | ||
| 16 | <System:Double x:Key="FontSizeBranding">45</System:Double> | ||
| 17 | <System:Double x:Key="FontSizeMedium">12</System:Double> | ||
| 18 | <System:Double x:Key="FontSizeButton">14</System:Double> | ||
| 19 | |||
| 20 | <!-- Images --> | ||
| 21 | <BitmapImage x:Key="LogoOverLightBackground" UriSource="pack://application:,,,/WixBA;component/resources/logo-white-hollow.png" /> | ||
| 22 | <BitmapImage x:Key="LogoOverDarkBackground" UriSource="pack://application:,,,/WixBA;component/resources/logo-black-hollow.png" /> | ||
| 23 | |||
| 24 | <!-- Colors --> | ||
| 25 | <Color x:Key="ProgressIndicatorColor">#FF1EF1E8</Color> | ||
| 26 | |||
| 27 | <!-- Brushs --> | ||
| 28 | <SolidColorBrush x:Key="ProgressIndicatorBrush" Color="{DynamicResource ProgressIndicatorColor}" /> | ||
| 29 | |||
| 30 | <LinearGradientBrush x:Key="ProgressBarIndicatorAnimatedFill" StartPoint="0,0" EndPoint="1,0"> | ||
| 31 | <LinearGradientBrush.GradientStops> | ||
| 32 | <GradientStopCollection> | ||
| 33 | <GradientStop Offset="0" Color="#000000FF" /> | ||
| 34 | <GradientStop Offset="0.5" Color="#600000FF" /> | ||
| 35 | <GradientStop Offset="0.54" Color="{DynamicResource {x:Static SystemColors.ControlTextColorKey}}" /> | ||
| 36 | <GradientStop Offset="0.56" Color="{DynamicResource {x:Static SystemColors.ControlTextColorKey}}" /> | ||
| 37 | <GradientStop Offset="0.6" Color="#600000FF" /> | ||
| 38 | <GradientStop Offset="1" Color="#000000FF" /> | ||
| 39 | </GradientStopCollection> | ||
| 40 | </LinearGradientBrush.GradientStops> | ||
| 41 | </LinearGradientBrush> | ||
| 42 | |||
| 43 | <!-- Control Templates --> | ||
| 44 | <ControlTemplate x:Key="HyperlinkedButtonTemplateKey" TargetType="{x:Type Button}"> | ||
| 45 | <ContentPresenter Margin="{TemplateBinding Control.Padding}" | ||
| 46 | HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}" | ||
| 47 | VerticalAlignment="{TemplateBinding Control.VerticalContentAlignment}" | ||
| 48 | Content="{TemplateBinding ContentControl.Content}" | ||
| 49 | ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}" | ||
| 50 | SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" /> | ||
| 51 | </ControlTemplate> | ||
| 52 | |||
| 53 | <ControlTemplate x:Key="ProgressBarTemplateKey" TargetType="{x:Type ProgressBar}"> | ||
| 54 | <Border Name="TemplateRoot" | ||
| 55 | Margin="0,5" | ||
| 56 | BorderBrush="{TemplateBinding BorderBrush}" | ||
| 57 | BorderThickness="{TemplateBinding BorderThickness}" | ||
| 58 | CornerRadius="3"> | ||
| 59 | <Grid ClipToBounds="True" SnapsToDevicePixels="true"> | ||
| 60 | <Rectangle Fill="{TemplateBinding Background}" /> | ||
| 61 | <Rectangle Name="PART_Track" ClipToBounds="True" /> | ||
| 62 | <Decorator x:Name="PART_Indicator" HorizontalAlignment="Left"> | ||
| 63 | <Grid Name="Foreground"> | ||
| 64 | <Rectangle x:Name="Indicator" Fill="{TemplateBinding Foreground}" /> | ||
| 65 | <Grid x:Name="Animation"> | ||
| 66 | <Rectangle x:Name="PART_GlowRect" | ||
| 67 | Width="80" | ||
| 68 | Margin="-100,0,0,0" | ||
| 69 | HorizontalAlignment="Left" | ||
| 70 | Fill="{StaticResource ProgressBarIndicatorAnimatedFill}" /> | ||
| 71 | </Grid> | ||
| 72 | </Grid> | ||
| 73 | </Decorator> | ||
| 74 | </Grid> | ||
| 75 | </Border> | ||
| 76 | </ControlTemplate> | ||
| 77 | |||
| 78 | <!-- Styles --> | ||
| 79 | <Style x:Key="ActionBtnStkPnlStyle" TargetType="StackPanel"> | ||
| 80 | <Setter Property="Margin" Value="0,2,0,0" /> | ||
| 81 | <Setter Property="HorizontalAlignment" Value="Center" /> | ||
| 82 | </Style> | ||
| 83 | |||
| 84 | <Style x:Key="FinalActionsStkPnlStyle" TargetType="StackPanel"> | ||
| 85 | <Setter Property="Margin" Value="80,2,0,0" /> | ||
| 86 | </Style> | ||
| 87 | |||
| 88 | <Style x:Key="BrandStkPnlStyle" TargetType="StackPanel"> | ||
| 89 | <Setter Property="Margin" Value="0,0,20,0" /> | ||
| 90 | <Setter Property="VerticalAlignment" Value="Top" /> | ||
| 91 | <Setter Property="HorizontalAlignment" Value="Right" /> | ||
| 92 | <Setter Property="Width" Value="100" /> | ||
| 93 | <Setter Property="Width" Value="100" /> | ||
| 94 | </Style> | ||
| 95 | |||
| 96 | <Style x:Key="CommonTextBlkStyle" TargetType="TextBlock"> | ||
| 97 | <Setter Property="VerticalAlignment" Value="Center" /> | ||
| 98 | <Setter Property="HorizontalAlignment" Value="Center" /> | ||
| 99 | <Setter Property="FontWeight" Value="Bold" /> | ||
| 100 | <Setter Property="TextAlignment" Value="Center" /> | ||
| 101 | <Setter Property="TextWrapping" Value="WrapWithOverflow" /> | ||
| 102 | <Setter Property="FontFamily" Value="{DynamicResource FontFamily}" /> | ||
| 103 | </Style> | ||
| 104 | |||
| 105 | <Style x:Key="TitleTextBlkStyle" | ||
| 106 | BasedOn="{StaticResource CommonTextBlkStyle}" | ||
| 107 | TargetType="TextBlock"> | ||
| 108 | <Setter Property="VerticalAlignment" Value="Top" /> | ||
| 109 | <Setter Property="HorizontalAlignment" Value="Left" /> | ||
| 110 | <Setter Property="FontSize" Value="{DynamicResource ResourceKey=FontSizeBranding}" /> | ||
| 111 | <Setter Property="FontWeight" Value="ExtraBold" /> | ||
| 112 | <Setter Property="Margin" Value="0,5,0,0" /> | ||
| 113 | </Style> | ||
| 114 | |||
| 115 | |||
| 116 | <Style x:Key="LabelTextBlkStyle" | ||
| 117 | BasedOn="{StaticResource CommonTextBlkStyle}" | ||
| 118 | TargetType="TextBlock"> | ||
| 119 | <Setter Property="HorizontalAlignment" Value="Left" /> | ||
| 120 | <Setter Property="FontSize" Value="{DynamicResource FontSizeButton}" /> | ||
| 121 | <Setter Property="FontWeight" Value="Bold" /> | ||
| 122 | <Setter Property="TextAlignment" Value="Left" /> | ||
| 123 | <Setter Property="Margin" Value="0,2,3,0" /> | ||
| 124 | </Style> | ||
| 125 | |||
| 126 | <Style x:Key="DataTextBlkStyle" | ||
| 127 | BasedOn="{StaticResource CommonTextBlkStyle}" | ||
| 128 | TargetType="TextBlock"> | ||
| 129 | <Setter Property="HorizontalAlignment" Value="Left" /> | ||
| 130 | <Setter Property="FontSize" Value="{DynamicResource FontSizeMedium}" /> | ||
| 131 | <Setter Property="TextAlignment" Value="Left" /> | ||
| 132 | <Setter Property="Margin" Value="2,2,3,0" /> | ||
| 133 | </Style> | ||
| 134 | |||
| 135 | <Style x:Key="StatusTextStyle" | ||
| 136 | BasedOn="{StaticResource CommonTextBlkStyle}" | ||
| 137 | TargetType="TextBlock"> | ||
| 138 | <Setter Property="FontFamily" Value="{DynamicResource {x:Static SystemFonts.MessageFontFamilyKey}}" /> | ||
| 139 | <Setter Property="FontSize" Value="{DynamicResource FontSizeMedium}" /> | ||
| 140 | <Setter Property="Margin" Value="8" /> | ||
| 141 | <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}" /> | ||
| 142 | <Setter Property="VerticalAlignment" Value="Top" /> | ||
| 143 | <Setter Property="HorizontalAlignment" Value="Left" /> | ||
| 144 | </Style> | ||
| 145 | |||
| 146 | <Style x:Key="ActionButtonStyle" TargetType="Button"> | ||
| 147 | <Setter Property="MinHeight" Value="30" /> | ||
| 148 | <Setter Property="MinWidth" Value="100" /> | ||
| 149 | <Setter Property="Margin" Value="5,0,0,0" /> | ||
| 150 | <Setter Property="VerticalAlignment" Value="Center" /> | ||
| 151 | <Setter Property="VerticalContentAlignment" Value="Center" /> | ||
| 152 | <Setter Property="HorizontalAlignment" Value="Center" /> | ||
| 153 | <Setter Property="HorizontalContentAlignment" Value="Center" /> | ||
| 154 | <Setter Property="FontFamily" Value="{DynamicResource FontFamily}" /> | ||
| 155 | </Style> | ||
| 156 | |||
| 157 | <Style x:Key="FinalActionButtonStyle" | ||
| 158 | BasedOn="{StaticResource ActionButtonStyle}" | ||
| 159 | TargetType="Button"> | ||
| 160 | <Setter Property="Margin" Value="40,0,0,0" /> | ||
| 161 | </Style> | ||
| 162 | |||
| 163 | <Style x:Key="HyperlinkedButtonStyle" TargetType="Button"> | ||
| 164 | <Setter Property="Margin" Value="0,2,0,0" /> | ||
| 165 | <Setter Property="Template" Value="{StaticResource HyperlinkedButtonTemplateKey}" /> | ||
| 166 | <Setter Property="IsHitTestVisible" Value="True" /> | ||
| 167 | </Style> | ||
| 168 | |||
| 169 | <Style x:Key="LogoStyle" TargetType="Image"> | ||
| 170 | <Setter Property="Height" Value="65" /> | ||
| 171 | <Setter Property="Width" Value="102" /> | ||
| 172 | <Setter Property="VerticalAlignment" Value="Top" /> | ||
| 173 | <Setter Property="IsHitTestVisible" Value="False" /> | ||
| 174 | <Setter Property="Source" Value="{DynamicResource LogoOverLightBackground}" /> | ||
| 175 | <Style.Triggers> | ||
| 176 | <DataTrigger Binding="{Binding Path=IsLightBackground, Source={x:Static ux:WindowProperties.Instance}}" Value="false"> | ||
| 177 | <Setter Property="Source" Value="{DynamicResource LogoOverDarkBackground}" /> | ||
| 178 | </DataTrigger> | ||
| 179 | </Style.Triggers> | ||
| 180 | </Style> | ||
| 181 | |||
| 182 | <Style x:Key="UpdateMarqueeStyle" TargetType="ProgressBar"> | ||
| 183 | <Setter Property="Foreground" Value="{DynamicResource ProgressIndicatorBrush}" /> | ||
| 184 | <Setter Property="BorderBrush" Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}" /> | ||
| 185 | <Setter Property="BorderThickness" Value="1" /> | ||
| 186 | </Style> | ||
| 187 | |||
| 188 | <Style x:Key="ActionProgressStyle" TargetType="ProgressBar"> | ||
| 189 | <Setter Property="Foreground" Value="{DynamicResource ProgressIndicatorBrush}" /> | ||
| 190 | <Setter Property="BorderBrush" Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}" /> | ||
| 191 | <Setter Property="BorderThickness" Value="1" /> | ||
| 192 | <Setter Property="Template" Value="{StaticResource ProgressBarTemplateKey}" /> | ||
| 193 | </Style> | ||
| 194 | </ResourceDictionary> | ||
diff --git a/src/WixToolset.WixBA/UpdateViewModel.cs b/src/WixToolset.WixBA/UpdateViewModel.cs new file mode 100644 index 00000000..6b60112c --- /dev/null +++ b/src/WixToolset.WixBA/UpdateViewModel.cs | |||
| @@ -0,0 +1,207 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.UX | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.ComponentModel; | ||
| 7 | using System.Windows.Input; | ||
| 8 | using WixToolset.Bootstrapper; | ||
| 9 | |||
| 10 | /// <summary> | ||
| 11 | /// The states of the update view model. | ||
| 12 | /// </summary> | ||
| 13 | public enum UpdateState | ||
| 14 | { | ||
| 15 | Unknown, | ||
| 16 | Initializing, | ||
| 17 | Checking, | ||
| 18 | Current, | ||
| 19 | Available, | ||
| 20 | Failed, | ||
| 21 | } | ||
| 22 | |||
| 23 | /// <summary> | ||
| 24 | /// The model of the update view. | ||
| 25 | /// </summary> | ||
| 26 | public class UpdateViewModel : PropertyNotifyBase | ||
| 27 | { | ||
| 28 | private RootViewModel root; | ||
| 29 | private UpdateState state; | ||
| 30 | private ICommand updateCommand; | ||
| 31 | private string updateVersion; | ||
| 32 | private string updateChanges; | ||
| 33 | |||
| 34 | |||
| 35 | public UpdateViewModel(RootViewModel root) | ||
| 36 | { | ||
| 37 | this.root = root; | ||
| 38 | WixBA.Model.Bootstrapper.DetectUpdateBegin += this.DetectUpdateBegin; | ||
| 39 | WixBA.Model.Bootstrapper.DetectUpdate += this.DetectUpdate; | ||
| 40 | WixBA.Model.Bootstrapper.DetectUpdateComplete += this.DetectUpdateComplete; | ||
| 41 | |||
| 42 | this.root.PropertyChanged += new PropertyChangedEventHandler(this.RootPropertyChanged); | ||
| 43 | |||
| 44 | this.State = UpdateState.Initializing; | ||
| 45 | } | ||
| 46 | |||
| 47 | void RootPropertyChanged(object sender, PropertyChangedEventArgs e) | ||
| 48 | { | ||
| 49 | if ("InstallState" == e.PropertyName) | ||
| 50 | { | ||
| 51 | base.OnPropertyChanged("CanUpdate"); | ||
| 52 | } | ||
| 53 | } | ||
| 54 | |||
| 55 | public bool CheckingEnabled | ||
| 56 | { | ||
| 57 | get { return this.State == UpdateState.Initializing || this.State == UpdateState.Checking; } | ||
| 58 | } | ||
| 59 | |||
| 60 | public bool CanUpdate | ||
| 61 | { | ||
| 62 | get | ||
| 63 | { | ||
| 64 | switch(this.root.InstallState) | ||
| 65 | { | ||
| 66 | case InstallationState.Waiting: | ||
| 67 | case InstallationState.Applied: | ||
| 68 | case InstallationState.Failed: | ||
| 69 | return this.IsUpdateAvailable; | ||
| 70 | default: | ||
| 71 | return false; | ||
| 72 | } | ||
| 73 | } | ||
| 74 | } | ||
| 75 | |||
| 76 | public ICommand UpdateCommand | ||
| 77 | { | ||
| 78 | get | ||
| 79 | { | ||
| 80 | if (this.updateCommand == null) | ||
| 81 | { | ||
| 82 | this.updateCommand = new RelayCommand(param => WixBA.Plan(LaunchAction.UpdateReplace), param => this.CanUpdate); | ||
| 83 | } | ||
| 84 | |||
| 85 | return this.updateCommand; | ||
| 86 | } | ||
| 87 | } | ||
| 88 | |||
| 89 | public bool IsUpdateAvailable | ||
| 90 | { | ||
| 91 | get { return this.State == UpdateState.Available; } | ||
| 92 | } | ||
| 93 | |||
| 94 | /// <summary> | ||
| 95 | /// Gets and sets the state of the update view model. | ||
| 96 | /// </summary> | ||
| 97 | public UpdateState State | ||
| 98 | { | ||
| 99 | get | ||
| 100 | { | ||
| 101 | return this.state; | ||
| 102 | } | ||
| 103 | |||
| 104 | set | ||
| 105 | { | ||
| 106 | if (this.state != value) | ||
| 107 | { | ||
| 108 | this.state = value; | ||
| 109 | base.OnPropertyChanged("State"); | ||
| 110 | base.OnPropertyChanged("CanUpdate"); | ||
| 111 | base.OnPropertyChanged("CheckingEnabled"); | ||
| 112 | base.OnPropertyChanged("IsUpdateAvailable"); | ||
| 113 | } | ||
| 114 | } | ||
| 115 | } | ||
| 116 | /// <summary> | ||
| 117 | /// The version of an available update. | ||
| 118 | /// </summary> | ||
| 119 | public string UpdateVersion | ||
| 120 | { | ||
| 121 | get | ||
| 122 | { | ||
| 123 | return updateVersion; | ||
| 124 | } | ||
| 125 | set | ||
| 126 | { | ||
| 127 | if (this.updateVersion != value) | ||
| 128 | { | ||
| 129 | this.updateVersion = value; | ||
| 130 | base.OnPropertyChanged("UpdateVersion"); | ||
| 131 | } | ||
| 132 | } | ||
| 133 | } | ||
| 134 | |||
| 135 | /// <summary> | ||
| 136 | /// The changes in the available update. | ||
| 137 | /// </summary> | ||
| 138 | public string UpdateChanges | ||
| 139 | { | ||
| 140 | get | ||
| 141 | { | ||
| 142 | return updateChanges; | ||
| 143 | } | ||
| 144 | set | ||
| 145 | { | ||
| 146 | if (this.updateChanges != value) | ||
| 147 | { | ||
| 148 | this.updateChanges = value; | ||
| 149 | base.OnPropertyChanged("UpdateChanges"); | ||
| 150 | } | ||
| 151 | } | ||
| 152 | } | ||
| 153 | |||
| 154 | private void DetectUpdateBegin(object sender, Bootstrapper.DetectUpdateBeginEventArgs e) | ||
| 155 | { | ||
| 156 | // Don't check for updates if: | ||
| 157 | // the first check failed (no retry) | ||
| 158 | // if we are being run as an uninstall | ||
| 159 | // if we are not under a full UI. | ||
| 160 | if ((UpdateState.Failed != this.State) && (LaunchAction.Uninstall != WixBA.Model.Command.Action) && (Display.Full == WixBA.Model.Command.Display)) | ||
| 161 | { | ||
| 162 | this.State = UpdateState.Checking; | ||
| 163 | e.Skip = false; | ||
| 164 | } | ||
| 165 | } | ||
| 166 | |||
| 167 | private void DetectUpdate(object sender, Bootstrapper.DetectUpdateEventArgs e) | ||
| 168 | { | ||
| 169 | // The list of updates is sorted in descending version, so the first callback should be the largest update available. | ||
| 170 | // This update should be either larger than ours (so we are out of date), the same as ours (so we are current) | ||
| 171 | // or smaller than ours (we have a private build). If we really wanted to, we could leave the e.StopProcessingUpdates alone and | ||
| 172 | // enumerate all of the updates. | ||
| 173 | WixBA.Model.Engine.Log(LogLevel.Verbose, String.Format("Potential update v{0} from '{1}'; current version: v{2}", e.Version, e.UpdateLocation, WixBA.Model.Version)); | ||
| 174 | if (e.Version > WixBA.Model.Version) | ||
| 175 | { | ||
| 176 | WixBA.Model.Engine.SetUpdate(null, e.UpdateLocation, e.Size, UpdateHashType.None, null); | ||
| 177 | this.UpdateVersion = String.Concat("v", e.Version.ToString()); | ||
| 178 | string changesFormat = @"<body style='overflow: auto;'>{0}</body>"; | ||
| 179 | this.UpdateChanges = String.Format(changesFormat, e.Content); | ||
| 180 | this.State = UpdateState.Available; | ||
| 181 | } | ||
| 182 | else | ||
| 183 | { | ||
| 184 | this.State = UpdateState.Current; | ||
| 185 | } | ||
| 186 | e.StopProcessingUpdates = true; | ||
| 187 | } | ||
| 188 | |||
| 189 | private void DetectUpdateComplete(object sender, Bootstrapper.DetectUpdateCompleteEventArgs e) | ||
| 190 | { | ||
| 191 | // Failed to process an update, allow the existing bundle to still install. | ||
| 192 | if ((UpdateState.Failed != this.State) && !Hresult.Succeeded(e.Status)) | ||
| 193 | { | ||
| 194 | this.State = UpdateState.Failed; | ||
| 195 | WixBA.Model.Engine.Log(LogLevel.Verbose, String.Format("Failed to locate an update, status of 0x{0:X8}, updates disabled.", e.Status)); | ||
| 196 | e.IgnoreError = true; | ||
| 197 | } | ||
| 198 | // If we are uninstalling, we don't want to check or show an update | ||
| 199 | // If we are checking, then the feed didn't find any valid enclosures | ||
| 200 | // If we are initializing, we're either uninstalling or not a full UI | ||
| 201 | else if ((LaunchAction.Uninstall == WixBA.Model.Command.Action) || (UpdateState.Initializing == this.State) || (UpdateState.Checking == this.State)) | ||
| 202 | { | ||
| 203 | this.State = UpdateState.Unknown; | ||
| 204 | } | ||
| 205 | } | ||
| 206 | } | ||
| 207 | } | ||
diff --git a/src/WixToolset.WixBA/WindowProperties.cs b/src/WixToolset.WixBA/WindowProperties.cs new file mode 100644 index 00000000..bead5cc1 --- /dev/null +++ b/src/WixToolset.WixBA/WindowProperties.cs | |||
| @@ -0,0 +1,65 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.UX | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Windows; | ||
| 7 | using System.Windows.Media; | ||
| 8 | |||
| 9 | /// <summary> | ||
| 10 | /// Dependency Properties associated with the main Window object. | ||
| 11 | /// </summary> | ||
| 12 | public class WindowProperties : DependencyObject | ||
| 13 | { | ||
| 14 | /// <summary> | ||
| 15 | /// Dependency Property to hold the result of detecting the relative luminosity (or brightness) of a Windows background. | ||
| 16 | /// </summary> | ||
| 17 | public static readonly DependencyProperty IsLightBackgroundProperty = DependencyProperty.Register( | ||
| 18 | "IsLightBackground", typeof(bool), typeof(WindowProperties), new PropertyMetadata( false )); | ||
| 19 | |||
| 20 | private static Lazy<WindowProperties> _instance = new Lazy<WindowProperties>(() => | ||
| 21 | { | ||
| 22 | WindowProperties wp = new WindowProperties(); | ||
| 23 | wp.CheckBackgroundBrightness(); | ||
| 24 | return wp; | ||
| 25 | }); | ||
| 26 | |||
| 27 | public static WindowProperties Instance | ||
| 28 | { | ||
| 29 | get | ||
| 30 | { | ||
| 31 | return _instance.Value; | ||
| 32 | } | ||
| 33 | } | ||
| 34 | |||
| 35 | |||
| 36 | public bool IsLightBackground | ||
| 37 | { | ||
| 38 | get { return (bool)GetValue(IsLightBackgroundProperty); } | ||
| 39 | private set { SetValue(IsLightBackgroundProperty, value); } | ||
| 40 | } | ||
| 41 | |||
| 42 | /// <summary> | ||
| 43 | /// Use the Luminosity parameter of the background color to detect light vs dark theme settings. | ||
| 44 | /// </summary> | ||
| 45 | /// <remarks> | ||
| 46 | /// This approach detects both the common High Contrast themes (White vs Black) and custom themes which may have relatively lighter backgrounds. | ||
| 47 | /// </remarks> | ||
| 48 | public void CheckBackgroundBrightness() | ||
| 49 | { | ||
| 50 | SolidColorBrush windowbrush = System.Windows.SystemColors.WindowBrush; | ||
| 51 | System.Drawing.Color dcolor = System.Drawing.Color.FromArgb(windowbrush.Color.A, windowbrush.Color.R, windowbrush.Color.G, windowbrush.Color.B); | ||
| 52 | |||
| 53 | var brightness = dcolor.GetBrightness(); | ||
| 54 | // Test for 'Lightness' at an arbitrary point, approaching 1.0 (White). | ||
| 55 | if (0.7 < brightness) | ||
| 56 | { | ||
| 57 | this.IsLightBackground = true; | ||
| 58 | } | ||
| 59 | else | ||
| 60 | { | ||
| 61 | this.IsLightBackground = false; | ||
| 62 | } | ||
| 63 | } | ||
| 64 | } | ||
| 65 | } | ||
diff --git a/src/WixToolset.WixBA/WixBA.BootstrapperCore.config b/src/WixToolset.WixBA/WixBA.BootstrapperCore.config new file mode 100644 index 00000000..8e1d4729 --- /dev/null +++ b/src/WixToolset.WixBA/WixBA.BootstrapperCore.config | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8" ?> | ||
| 2 | <!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> | ||
| 3 | |||
| 4 | <configuration> | ||
| 5 | <configSections> | ||
| 6 | <sectionGroup name="wix.bootstrapper" type="WixToolset.Bootstrapper.BootstrapperSectionGroup, BootstrapperCore"> | ||
| 7 | <section name="host" type="WixToolset.Bootstrapper.HostSection, BootstrapperCore" /> | ||
| 8 | </sectionGroup> | ||
| 9 | </configSections> | ||
| 10 | <startup useLegacyV2RuntimeActivationPolicy="true"> | ||
| 11 | <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> | ||
| 12 | </startup> | ||
| 13 | <wix.bootstrapper> | ||
| 14 | <host assemblyName="WixBA" /> | ||
| 15 | </wix.bootstrapper> | ||
| 16 | </configuration> | ||
diff --git a/src/WixToolset.WixBA/WixBA.cs b/src/WixToolset.WixBA/WixBA.cs new file mode 100644 index 00000000..fb69a346 --- /dev/null +++ b/src/WixToolset.WixBA/WixBA.cs | |||
| @@ -0,0 +1,216 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.UX | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Diagnostics; | ||
| 8 | using System.IO; | ||
| 9 | using System.Net; | ||
| 10 | using System.Text; | ||
| 11 | using System.Windows.Input; | ||
| 12 | using Threading = System.Windows.Threading; | ||
| 13 | using WinForms = System.Windows.Forms; | ||
| 14 | |||
| 15 | using WixToolset.Bootstrapper; | ||
| 16 | |||
| 17 | /// <summary> | ||
| 18 | /// The WiX toolset user experience. | ||
| 19 | /// </summary> | ||
| 20 | public class WixBA : BootstrapperApplication | ||
| 21 | { | ||
| 22 | /// <summary> | ||
| 23 | /// Gets the global model. | ||
| 24 | /// </summary> | ||
| 25 | static public Model Model { get; private set; } | ||
| 26 | |||
| 27 | /// <summary> | ||
| 28 | /// Gets the global view. | ||
| 29 | /// </summary> | ||
| 30 | static public RootView View { get; private set; } | ||
| 31 | // TODO: We should refactor things so we dont have a global View. | ||
| 32 | |||
| 33 | /// <summary> | ||
| 34 | /// Gets the global dispatcher. | ||
| 35 | /// </summary> | ||
| 36 | static public Threading.Dispatcher Dispatcher { get; private set; } | ||
| 37 | |||
| 38 | /// <summary> | ||
| 39 | /// Launches the default web browser to the provided URI. | ||
| 40 | /// </summary> | ||
| 41 | /// <param name="uri">URI to open the web browser.</param> | ||
| 42 | public static void LaunchUrl(string uri) | ||
| 43 | { | ||
| 44 | WixBA.UseShellExecute(uri); | ||
| 45 | } | ||
| 46 | |||
| 47 | /// <summary> | ||
| 48 | /// Open a log file. | ||
| 49 | /// </summary> | ||
| 50 | /// <param name="uri">URI to a log file.</param> | ||
| 51 | internal static void OpenLog(Uri uri) | ||
| 52 | { | ||
| 53 | WixBA.UseShellExecute(uri.ToString()); | ||
| 54 | } | ||
| 55 | |||
| 56 | /// <summary> | ||
| 57 | /// Open a log folder. | ||
| 58 | /// </summary> | ||
| 59 | /// <param name="string">path to a log folder.</param> | ||
| 60 | internal static void OpenLogFolder(string logFolder) | ||
| 61 | { | ||
| 62 | WixBA.UseShellExecute(logFolder); | ||
| 63 | } | ||
| 64 | |||
| 65 | /// <summary> | ||
| 66 | /// Open a log folder. | ||
| 67 | /// </summary> | ||
| 68 | /// <param name="uri">path to a log folder.</param> | ||
| 69 | private static void UseShellExecute(string path) | ||
| 70 | { | ||
| 71 | // Switch the wait cursor since shellexec can take a second or so. | ||
| 72 | System.Windows.Input.Cursor cursor = WixBA.View.Cursor; | ||
| 73 | WixBA.View.Cursor = System.Windows.Input.Cursors.Wait; | ||
| 74 | Process process = null; | ||
| 75 | try | ||
| 76 | { | ||
| 77 | process = new Process(); | ||
| 78 | process.StartInfo.FileName = path; | ||
| 79 | process.StartInfo.UseShellExecute = true; | ||
| 80 | process.StartInfo.Verb = "open"; | ||
| 81 | |||
| 82 | process.Start(); | ||
| 83 | } | ||
| 84 | finally | ||
| 85 | { | ||
| 86 | if (null != process) | ||
| 87 | { | ||
| 88 | process.Dispose(); | ||
| 89 | } | ||
| 90 | // back to the original cursor. | ||
| 91 | WixBA.View.Cursor = cursor; | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | /// <summary> | ||
| 96 | /// Starts planning the appropriate action. | ||
| 97 | /// </summary> | ||
| 98 | /// <param name="action">Action to plan.</param> | ||
| 99 | public static void Plan(LaunchAction action) | ||
| 100 | { | ||
| 101 | WixBA.Model.PlannedAction = action; | ||
| 102 | WixBA.Model.Engine.Plan(WixBA.Model.PlannedAction); | ||
| 103 | } | ||
| 104 | |||
| 105 | public static void PlanLayout() | ||
| 106 | { | ||
| 107 | // Either default or set the layout directory | ||
| 108 | if (String.IsNullOrEmpty(WixBA.Model.Command.LayoutDirectory)) | ||
| 109 | { | ||
| 110 | WixBA.Model.LayoutDirectory = Directory.GetCurrentDirectory(); | ||
| 111 | |||
| 112 | // Ask the user for layout folder if one wasn't provided and we're in full UI mode | ||
| 113 | if (WixBA.Model.Command.Display == Display.Full) | ||
| 114 | { | ||
| 115 | WixBA.Dispatcher.Invoke((Action)delegate() | ||
| 116 | { | ||
| 117 | WinForms.FolderBrowserDialog browserDialog = new WinForms.FolderBrowserDialog(); | ||
| 118 | browserDialog.RootFolder = Environment.SpecialFolder.MyComputer; | ||
| 119 | |||
| 120 | // Default to the current directory. | ||
| 121 | browserDialog.SelectedPath = WixBA.Model.LayoutDirectory; | ||
| 122 | WinForms.DialogResult result = browserDialog.ShowDialog(); | ||
| 123 | |||
| 124 | if (WinForms.DialogResult.OK == result) | ||
| 125 | { | ||
| 126 | WixBA.Model.LayoutDirectory = browserDialog.SelectedPath; | ||
| 127 | WixBA.Plan(WixBA.Model.Command.Action); | ||
| 128 | } | ||
| 129 | else | ||
| 130 | { | ||
| 131 | WixBA.View.Close(); | ||
| 132 | } | ||
| 133 | } | ||
| 134 | ); | ||
| 135 | } | ||
| 136 | } | ||
| 137 | else | ||
| 138 | { | ||
| 139 | WixBA.Model.LayoutDirectory = WixBA.Model.Command.LayoutDirectory; | ||
| 140 | WixBA.Plan(WixBA.Model.Command.Action); | ||
| 141 | } | ||
| 142 | } | ||
| 143 | |||
| 144 | /// <summary> | ||
| 145 | /// Thread entry point for WiX Toolset UX. | ||
| 146 | /// </summary> | ||
| 147 | protected override void Run() | ||
| 148 | { | ||
| 149 | this.Engine.Log(LogLevel.Verbose, "Running the WiX BA."); | ||
| 150 | WixBA.Model = new Model(this); | ||
| 151 | WixBA.Dispatcher = Threading.Dispatcher.CurrentDispatcher; | ||
| 152 | RootViewModel viewModel = new RootViewModel(); | ||
| 153 | |||
| 154 | // Kick off detect which will populate the view models. | ||
| 155 | this.Engine.Detect(); | ||
| 156 | |||
| 157 | // Create a Window to show UI. | ||
| 158 | if (WixBA.Model.Command.Display == Display.Passive || | ||
| 159 | WixBA.Model.Command.Display == Display.Full) | ||
| 160 | { | ||
| 161 | this.Engine.Log(LogLevel.Verbose, "Creating a UI."); | ||
| 162 | WixBA.View = new RootView(viewModel); | ||
| 163 | WixBA.View.Show(); | ||
| 164 | } | ||
| 165 | |||
| 166 | Threading.Dispatcher.Run(); | ||
| 167 | |||
| 168 | this.PostTelemetry(); | ||
| 169 | this.Engine.Quit(WixBA.Model.Result); | ||
| 170 | } | ||
| 171 | |||
| 172 | private void PostTelemetry() | ||
| 173 | { | ||
| 174 | string result = String.Concat("0x", WixBA.Model.Result.ToString("x")); | ||
| 175 | |||
| 176 | StringBuilder telemetryData = new StringBuilder(); | ||
| 177 | foreach (KeyValuePair<string, string> kvp in WixBA.Model.Telemetry) | ||
| 178 | { | ||
| 179 | telemetryData.AppendFormat("{0}={1}+", kvp.Key, kvp.Value); | ||
| 180 | } | ||
| 181 | telemetryData.AppendFormat("Result={0}", result); | ||
| 182 | |||
| 183 | byte[] data = Encoding.UTF8.GetBytes(telemetryData.ToString()); | ||
| 184 | |||
| 185 | try | ||
| 186 | { | ||
| 187 | HttpWebRequest post = WixBA.Model.CreateWebRequest(String.Format(WixDistribution.TelemetryUrlFormat, WixBA.Model.Version.ToString(), result)); | ||
| 188 | post.Method = "POST"; | ||
| 189 | post.ContentType = "application/x-www-form-urlencoded"; | ||
| 190 | post.ContentLength = data.Length; | ||
| 191 | |||
| 192 | using (Stream postStream = post.GetRequestStream()) | ||
| 193 | { | ||
| 194 | postStream.Write(data, 0, data.Length); | ||
| 195 | } | ||
| 196 | |||
| 197 | HttpWebResponse response = (HttpWebResponse)post.GetResponse(); | ||
| 198 | } | ||
| 199 | catch (ArgumentException) | ||
| 200 | { | ||
| 201 | } | ||
| 202 | catch (FormatException) | ||
| 203 | { | ||
| 204 | } | ||
| 205 | catch (OverflowException) | ||
| 206 | { | ||
| 207 | } | ||
| 208 | catch (ProtocolViolationException) | ||
| 209 | { | ||
| 210 | } | ||
| 211 | catch (WebException) | ||
| 212 | { | ||
| 213 | } | ||
| 214 | } | ||
| 215 | } | ||
| 216 | } | ||
diff --git a/src/WixToolset.WixBA/WixBA.csproj b/src/WixToolset.WixBA/WixBA.csproj new file mode 100644 index 00000000..6858b172 --- /dev/null +++ b/src/WixToolset.WixBA/WixBA.csproj | |||
| @@ -0,0 +1,64 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> | ||
| 3 | <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="12.0"> | ||
| 4 | <PropertyGroup> | ||
| 5 | <ProjectGuid>{7C27518B-84AD-4679-8EF4-29DF552CF1AC}</ProjectGuid> | ||
| 6 | <AssemblyName>WixBA</AssemblyName> | ||
| 7 | <OutputType>Library</OutputType> | ||
| 8 | <RootNamespace>WixToolset.UX</RootNamespace> | ||
| 9 | </PropertyGroup> | ||
| 10 | <ItemGroup> | ||
| 11 | <Compile Include="Hresult.cs" /> | ||
| 12 | <Compile Include="Model.cs" /> | ||
| 13 | <Compile Include="BrowserProperties.cs" /> | ||
| 14 | <Compile Include="NewsItem.cs" /> | ||
| 15 | <Compile Include="ProgressViewModel.cs" /> | ||
| 16 | <Compile Include="RelayCommand.cs" /> | ||
| 17 | <Compile Include="InstallationViewModel.cs" /> | ||
| 18 | <Page Include="Styles.xaml"> | ||
| 19 | <Generator>MSBuild:Compile</Generator> | ||
| 20 | <SubType>Designer</SubType> | ||
| 21 | </Page> | ||
| 22 | <Page Include="RootView.xaml"> | ||
| 23 | <Generator>MSBuild:Compile</Generator> | ||
| 24 | <SubType>Designer</SubType> | ||
| 25 | </Page> | ||
| 26 | <Compile Include="PropertyNotifyBase.cs" /> | ||
| 27 | <Compile Include="RootView.xaml.cs"> | ||
| 28 | <DependentUpon>RootView.xaml</DependentUpon> | ||
| 29 | </Compile> | ||
| 30 | <Compile Include="RootViewModel.cs" /> | ||
| 31 | <Compile Include="UpdateViewModel.cs" /> | ||
| 32 | <Compile Include="WindowProperties.cs" /> | ||
| 33 | <Compile Include="WixBA.cs" /> | ||
| 34 | <Compile Include="Properties\AssemblyInfo.cs" /> | ||
| 35 | <None Include="WixBA.BootstrapperCore.config"> | ||
| 36 | <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | ||
| 37 | <SubType>Designer</SubType> | ||
| 38 | </None> | ||
| 39 | </ItemGroup> | ||
| 40 | <ItemGroup> | ||
| 41 | <Reference Include="PresentationCore" /> | ||
| 42 | <Reference Include="PresentationFramework" /> | ||
| 43 | <Reference Include="System" /> | ||
| 44 | <Reference Include="System.ComponentModel.DataAnnotations" /> | ||
| 45 | <Reference Include="System.Core" /> | ||
| 46 | <Reference Include="System.Drawing"> | ||
| 47 | <Private>False</Private> | ||
| 48 | </Reference> | ||
| 49 | <Reference Include="System.ServiceModel" /> | ||
| 50 | <Reference Include="System.Windows.Forms" /> | ||
| 51 | <Reference Include="System.Xml.Linq" /> | ||
| 52 | <Reference Include="System.Xml" /> | ||
| 53 | <Reference Include="System.Xaml" /> | ||
| 54 | <Reference Include="WindowsBase" /> | ||
| 55 | <ProjectReference Include="..\..\ext\BalExtension\mba\core\core.csproj" /> | ||
| 56 | </ItemGroup> | ||
| 57 | <ItemGroup> | ||
| 58 | <Resource Include="Resources\logo-white-hollow.png" /> | ||
| 59 | </ItemGroup> | ||
| 60 | <ItemGroup> | ||
| 61 | <Resource Include="Resources\logo-black-hollow.png" /> | ||
| 62 | </ItemGroup> | ||
| 63 | <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), wix.proj))\tools\WixBuild.targets" /> | ||
| 64 | </Project> \ No newline at end of file | ||
