From bfd79c7ca947ec205a238a334bfe7f37fd40bb21 Mon Sep 17 00:00:00 2001 From: sr55 Date: Tue, 27 Aug 2013 19:11:43 +0000 Subject: WinGui: Some more work on the Instant HandBrake prototype. git-svn-id: svn://svn.handbrake.fr/HandBrake/trunk@5756 b64f7644-9d1e-0410-96f1-a4d463321fa5 --- win/CS/HandBrakeWPF/Commands/CancelScanCommand.cs | 16 +- win/CS/HandBrakeWPF/HandBrakeWPF.csproj | 8 +- win/CS/HandBrakeWPF/Startup/CastleBootstrapper.cs | 2 + win/CS/HandBrakeWPF/ViewModels/InstantViewModel.cs | 1014 ++++++++++++++++++++ .../ViewModels/Interfaces/IInstantViewModel.cs | 18 + win/CS/HandBrakeWPF/ViewModels/ShellViewModel.cs | 8 +- win/CS/HandBrakeWPF/Views/InstantMainView.xaml | 182 ---- win/CS/HandBrakeWPF/Views/InstantMainView.xaml.cs | 15 - win/CS/HandBrakeWPF/Views/InstantView.xaml | 202 ++++ win/CS/HandBrakeWPF/Views/InstantView.xaml.cs | 27 + win/CS/HandBrakeWPF/Views/ShellView.xaml | 2 +- 11 files changed, 1288 insertions(+), 206 deletions(-) create mode 100644 win/CS/HandBrakeWPF/ViewModels/InstantViewModel.cs create mode 100644 win/CS/HandBrakeWPF/ViewModels/Interfaces/IInstantViewModel.cs delete mode 100644 win/CS/HandBrakeWPF/Views/InstantMainView.xaml delete mode 100644 win/CS/HandBrakeWPF/Views/InstantMainView.xaml.cs create mode 100644 win/CS/HandBrakeWPF/Views/InstantView.xaml create mode 100644 win/CS/HandBrakeWPF/Views/InstantView.xaml.cs (limited to 'win') diff --git a/win/CS/HandBrakeWPF/Commands/CancelScanCommand.cs b/win/CS/HandBrakeWPF/Commands/CancelScanCommand.cs index 617778b08..e9ec9966a 100644 --- a/win/CS/HandBrakeWPF/Commands/CancelScanCommand.cs +++ b/win/CS/HandBrakeWPF/Commands/CancelScanCommand.cs @@ -49,7 +49,7 @@ namespace HandBrakeWPF.Commands /// private void ScanServiceWrapperScanCompleted(object sender, HandBrake.ApplicationServices.EventArgs.ScanCompletedEventArgs e) { - Caliburn.Micro.Execute.OnUIThread(() => this.CanExecuteChanged(sender, EventArgs.Empty)); + Caliburn.Micro.Execute.OnUIThread(this.OnCanExecuteChanged); } /// @@ -64,7 +64,7 @@ namespace HandBrakeWPF.Commands /// private void ScanServiceWrapperScanStared(object sender, EventArgs e) { - Caliburn.Micro.Execute.OnUIThread(() => this.CanExecuteChanged(sender, EventArgs.Empty)); + Caliburn.Micro.Execute.OnUIThread(this.OnCanExecuteChanged); } #region Implementation of ICommand @@ -100,6 +100,18 @@ namespace HandBrakeWPF.Commands /// public event EventHandler CanExecuteChanged; + /// + /// The on can execute changed. + /// + protected virtual void OnCanExecuteChanged() + { + EventHandler handler = this.CanExecuteChanged; + if (handler != null) + { + handler(this, EventArgs.Empty); + } + } + #endregion } } diff --git a/win/CS/HandBrakeWPF/HandBrakeWPF.csproj b/win/CS/HandBrakeWPF/HandBrakeWPF.csproj index 204774d82..b0094ce23 100644 --- a/win/CS/HandBrakeWPF/HandBrakeWPF.csproj +++ b/win/CS/HandBrakeWPF/HandBrakeWPF.csproj @@ -184,7 +184,9 @@ + + @@ -194,8 +196,8 @@ EncoderOptionsView.xaml - - InstantMainView.xaml + + InstantView.xaml QueueSelectionView.xaml @@ -374,7 +376,7 @@ MSBuild:Compile Designer - + Designer MSBuild:Compile diff --git a/win/CS/HandBrakeWPF/Startup/CastleBootstrapper.cs b/win/CS/HandBrakeWPF/Startup/CastleBootstrapper.cs index aee06213c..0debbbcce 100644 --- a/win/CS/HandBrakeWPF/Startup/CastleBootstrapper.cs +++ b/win/CS/HandBrakeWPF/Startup/CastleBootstrapper.cs @@ -80,6 +80,8 @@ namespace HandBrakeWPF.Startup this.windsorContainer.Register(Component.For().ImplementedBy().LifeStyle.Is(LifestyleType.Singleton)); this.windsorContainer.Register(Component.For().ImplementedBy().LifeStyle.Is(LifestyleType.Singleton)); + this.windsorContainer.Register(Component.For().ImplementedBy().LifeStyle.Is(LifestyleType.Singleton)); + // Tab Components this.windsorContainer.Register(Component.For().ImplementedBy().LifeStyle.Is(LifestyleType.Singleton)); this.windsorContainer.Register(Component.For().ImplementedBy().LifeStyle.Is(LifestyleType.Singleton)); diff --git a/win/CS/HandBrakeWPF/ViewModels/InstantViewModel.cs b/win/CS/HandBrakeWPF/ViewModels/InstantViewModel.cs new file mode 100644 index 000000000..8edfb3a87 --- /dev/null +++ b/win/CS/HandBrakeWPF/ViewModels/InstantViewModel.cs @@ -0,0 +1,1014 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License. +// +// +// The instant view model. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace HandBrakeWPF.ViewModels +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Threading; + using System.Windows; + + using Caliburn.Micro; + + using HandBrake.ApplicationServices; + using HandBrake.ApplicationServices.EventArgs; + using HandBrake.ApplicationServices.Model; + using HandBrake.ApplicationServices.Parsing; + using HandBrake.ApplicationServices.Services.Interfaces; + using HandBrake.ApplicationServices.Utilities; + + using HandBrakeWPF.Commands; + using HandBrakeWPF.Helpers; + using HandBrakeWPF.Model; + using HandBrakeWPF.Services.Interfaces; + using HandBrakeWPF.ViewModels.Interfaces; + using HandBrakeWPF.Views; + + using Microsoft.Win32; + + using Ookii.Dialogs.Wpf; + + /// + /// The instant view model. + /// + public class InstantViewModel : ViewModelBase, IInstantViewModel + { + #region Constants and Fields + + /// + /// The encode service. + /// + private readonly IEncodeServiceWrapper encodeService; + + /// + /// The error service. + /// + private readonly IErrorService errorService; + + /// + /// The preset service. + /// + private readonly IPresetService presetService; + + /// + /// The queue processor. + /// + private readonly IQueueProcessor queueProcessor; + + /// + /// The scan service. + /// + private readonly IScanServiceWrapper scanService; + + /// + /// The shell view model. + /// + private readonly IShellViewModel shellViewModel; + + /// + /// The update service. + /// + private readonly IUpdateService updateService; + + /// + /// The user setting service. + /// + private readonly IUserSettingService userSettingService; + + /// + /// Windows 7 API Pack wrapper + /// + private readonly Win7 windowsSeven = new Win7(); + + /// + /// The is encoding. + /// + private bool isEncoding; + + /// + /// The last percentage complete value. + /// + private int lastEncodePercentage; + + /// + /// The ordered by duration. + /// + private bool orderedByDuration; + + /// + /// The ordered by title. + /// + private bool orderedByTitle; + + /// + /// The output directory. + /// + private string outputDirectory; + + /// + /// The program status label. + /// + private string programStatusLabel; + + /// + /// The scanned source. + /// + private Source scannedSource; + + /// + /// The selected preset. + /// + private Preset selectedPreset; + + /// + /// The show status window. + /// + private bool showStatusWindow; + + /// + /// The source label. + /// + private string sourceLabel; + + /// + /// The status label. + /// + private string statusLabel; + + #endregion + + #region Constructors and Destructors + + /// + /// Initializes a new instance of the class. + /// + /// + /// The user setting service. + /// + /// + /// The scan service. + /// + /// + /// The encode service. + /// + /// + /// The preset service. + /// + /// + /// The error service. + /// + /// + /// The shell view model. + /// + /// + /// The update service. + /// + /// + /// The notification service. + /// + /// + /// The when done service. + /// + public InstantViewModel( + IUserSettingService userSettingService, + IScanServiceWrapper scanService, + IEncodeServiceWrapper encodeService, + IPresetService presetService, + IErrorService errorService, + IShellViewModel shellViewModel, + IUpdateService updateService, + INotificationService notificationService, + IPrePostActionService whenDoneService) + { + this.userSettingService = userSettingService; + this.scanService = scanService; + this.encodeService = encodeService; + this.presetService = presetService; + this.errorService = errorService; + this.shellViewModel = shellViewModel; + this.updateService = updateService; + + this.queueProcessor = IoC.Get(); + + // Setup Properties + this.TitleList = new BindingList(); + this.ScannedSource = new Source(); + + // Setup Events + this.scanService.ScanStared += this.ScanStared; + this.scanService.ScanCompleted += this.ScanCompleted; + this.scanService.ScanStatusChanged += this.ScanStatusChanged; + this.queueProcessor.JobProcessingStarted += this.QueueProcessorJobProcessingStarted; + this.queueProcessor.QueueCompleted += this.QueueCompleted; + this.queueProcessor.QueueChanged += this.QueueChanged; + this.queueProcessor.EncodeService.EncodeStatusChanged += this.EncodeStatusChanged; + + this.Presets = this.presetService.Presets; + this.CancelScanCommand = new CancelScanCommand(this.scanService); + } + + #endregion + + #region Properties + + /// + /// Gets or sets the cancel scan command. + /// + public CancelScanCommand CancelScanCommand { get; set; } + + /// + /// Gets or sets a value indicating whether IsEncoding. + /// + public bool IsEncoding + { + get + { + return this.isEncoding; + } + + set + { + this.isEncoding = value; + this.NotifyOfPropertyChange(() => this.IsEncoding); + } + } + + /// + /// Gets or sets a value indicating whether ordered by duration. + /// + public bool OrderedByDuration + { + get + { + return this.orderedByDuration; + } + + set + { + this.orderedByDuration = value; + this.NotifyOfPropertyChange(() => this.OrderedByDuration); + } + } + + /// + /// Gets or sets a value indicating whether ordered by title. + /// + public bool OrderedByTitle + { + get + { + return this.orderedByTitle; + } + + set + { + this.orderedByTitle = value; + this.NotifyOfPropertyChange(() => this.OrderedByTitle); + } + } + + /// + /// Gets or sets the output directory. + /// + public string OutputDirectory + { + get + { + return this.outputDirectory; + } + set + { + this.outputDirectory = value; + this.NotifyOfPropertyChange(() => this.OutputDirectory); + } + } + + /// + /// Gets or sets Presets. + /// + public IEnumerable Presets { get; set; } + + /// + /// Gets or sets the Program Status Toolbar Label + /// This indicates the status of HandBrake + /// + public string ProgramStatusLabel + { + get + { + return string.IsNullOrEmpty(this.programStatusLabel) ? "Ready" : this.programStatusLabel; + } + + set + { + if (!Equals(this.programStatusLabel, value)) + { + this.programStatusLabel = value; + this.NotifyOfPropertyChange(() => this.ProgramStatusLabel); + } + } + } + + /// + /// Gets or sets a value indicating progress percentage. + /// + public int ProgressPercentage { get; set; } + + /// + /// Gets or sets the Last Scanned Source + /// This object contains information about the scanned source. + /// + public Source ScannedSource + { + get + { + return this.scannedSource; + } + + set + { + this.scannedSource = value; + + this.NotifyOfPropertyChange("ScannedSource"); + } + } + + /// + /// Gets or sets SelectedPreset. + /// + public Preset SelectedPreset + { + get + { + return this.selectedPreset; + } + set + { + this.selectedPreset = value; + this.NotifyOfPropertyChange(() => this.SelectedPreset); + } + } + + /// + /// Gets or sets a value indicating whether ShowStatusWindow. + /// + public bool ShowStatusWindow + { + get + { + return this.showStatusWindow; + } + + set + { + this.showStatusWindow = value; + this.NotifyOfPropertyChange(() => this.ShowStatusWindow); + } + } + + /// + /// Gets or sets the Source Label + /// This indicates the status of scans. + /// + public string SourceLabel + { + get + { + return string.IsNullOrEmpty(this.sourceLabel) ? "Select 'Source' to continue" : this.sourceLabel; + } + + set + { + if (!Equals(this.sourceLabel, value)) + { + this.sourceLabel = value; + this.NotifyOfPropertyChange("SourceLabel"); + } + } + } + + /// + /// Gets SourceName. + /// + public string SourceName + { + get + { + // Sanity Check + if (this.ScannedSource == null || this.ScannedSource.ScanPath == null) + { + return string.Empty; + } + + // The title that is selected has a source name. This means it's part of a batch scan. + // if (selectedTitle != null && !string.IsNullOrEmpty(selectedTitle.SourceName)) + // { + // return Path.GetFileNameWithoutExtension(selectedTitle.SourceName); + // } + + // Check if we have a Folder, if so, check if it's a DVD / Bluray drive and get the label. + if (this.ScannedSource.ScanPath.EndsWith("\\")) + { + foreach (DriveInformation item in GeneralUtilities.GetDrives()) + { + if (item.RootDirectory.Contains(this.ScannedSource.ScanPath)) + { + return item.VolumeLabel; + } + } + } + + if (Path.GetFileNameWithoutExtension(this.ScannedSource.ScanPath) != "VIDEO_TS") + { + return Path.GetFileNameWithoutExtension(this.ScannedSource.ScanPath); + } + + return Path.GetFileNameWithoutExtension(Path.GetDirectoryName(this.ScannedSource.ScanPath)); + } + } + + /// + /// Gets or sets the Program Status Toolbar Label + /// This indicates the status of HandBrake + /// + public string StatusLabel + { + get + { + return string.IsNullOrEmpty(this.statusLabel) ? "Ready" : this.statusLabel; + } + + set + { + if (!Equals(this.statusLabel, value)) + { + this.statusLabel = value; + this.NotifyOfPropertyChange(() => this.StatusLabel); + } + } + } + + /// + /// Gets or sets the selected titles. + /// + public BindingList TitleList { get; set; } + + #endregion + + #region Public Methods + + /// + /// The Destination Path + /// + public void BrowseDestination() + { + var saveFileDialog = new SaveFileDialog + { + Filter = "mp4|*.mp4;*.m4v|mkv|*.mkv", + CheckPathExists = true, + AddExtension = true, + DefaultExt = ".mp4", + OverwritePrompt = true, + }; + + saveFileDialog.ShowDialog(); + this.OutputDirectory = Path.GetDirectoryName(saveFileDialog.FileName); + } + + /// + /// Cancel a Scan + /// + public void CancelScan() + { + this.scanService.Stop(); + } + + /// + /// File Scan + /// + public void FileScan() + { + var dialog = new VistaOpenFileDialog { Filter = "All files (*.*)|*.*" }; + dialog.ShowDialog(); + this.StartScan(dialog.FileName, 0); + } + + /// + /// Support dropping a file onto the main window to scan. + /// + /// + /// The DragEventArgs. + /// + public void FilesDroppedOnWindow(DragEventArgs e) + { + if (e.Data.GetDataPresent(DataFormats.FileDrop)) + { + var fileNames = e.Data.GetData(DataFormats.FileDrop, true) as string[]; + if (fileNames != null && fileNames.Any() && + (File.Exists(fileNames[0]) || Directory.Exists(fileNames[0]))) + { + this.StartScan(fileNames[0], 0); + } + } + + e.Handled = true; + } + + /// + /// Folder Scan + /// + public void FolderScan() + { + var dialog = new VistaFolderBrowserDialog + { + Description = "Please select a folder.", + UseDescriptionForTitle = true + }; + dialog.ShowDialog(); + this.StartScan(dialog.SelectedPath, 0); + } + + /// + /// Launch the Help pages. + /// + public void LaunchHelp() + { + Process.Start("https://trac.handbrake.fr/wiki/HandBrakeGuide"); + } + + /// + /// The on load. + /// + public override void OnLoad() + { + // Check the CLI Executable. + CliCheckHelper.CheckCLIVersion(); + + // Perform an update check if required + // this.updateService.PerformStartupUpdateCheck(this.HandleUpdateCheckResults); + + // Setup the presets. + this.presetService.Load(); + if (this.presetService.CheckIfPresetsAreOutOfDate()) + { + if (!this.userSettingService.GetUserSetting(UserSettingConstants.PresetNotification)) + { + this.errorService.ShowMessageBox( + "HandBrake has determined your built-in presets are out of date... These presets will now be updated." + + Environment.NewLine + + "Your custom presets have not been updated so you may have to re-create these by deleting and re-adding them.", + "Preset Update", + MessageBoxButton.OK, + MessageBoxImage.Information); + } + } + + this.SelectedPreset = this.presetService.DefaultPreset; + + // Log Cleaning + if (this.userSettingService.GetUserSetting(UserSettingConstants.ClearOldLogs)) + { + var clearLog = new Thread(() => GeneralUtilities.ClearLogFiles(30)); + clearLog.Start(); + } + base.OnLoad(); + } + + /// + /// Open the About Window + /// + public void OpenAboutApplication() + { + var command = new OpenOptionsScreenCommand(); + command.Execute(OptionsTab.About); + } + + /// + /// Open the Log Window + /// + public void OpenLogWindow() + { + Window window = + Application.Current.Windows.Cast().FirstOrDefault(x => x.GetType() == typeof(LogView)); + + if (window != null) + { + var logvm = (ILogViewModel)window.DataContext; + logvm.SelectedTab = this.IsEncoding ? 0 : 1; + window.Activate(); + } + else + { + var logvm = IoC.Get(); + logvm.SelectedTab = this.IsEncoding ? 0 : 1; + this.WindowManager.ShowWindow(logvm); + } + } + + /// + /// Open the Options Window + /// + public void OpenOptionsWindow() + { + this.shellViewModel.DisplayWindow(ShellWindow.OptionsWindow); + } + + /// + /// The order by duration. + /// + public void OrderByDuration() + { + this.TitleList = + new BindingList(this.TitleList.OrderByDescending(o => o.Title.Duration).ToList()); + this.NotifyOfPropertyChange(() => this.TitleList); + this.OrderedByTitle = false; + this.OrderedByDuration = true; + } + + /// + /// The order by title. + /// + public void OrderByTitle() + { + this.TitleList = new BindingList(this.TitleList.OrderBy(o => o.Title.TitleNumber).ToList()); + this.NotifyOfPropertyChange(() => this.TitleList); + this.OrderedByTitle = true; + this.OrderedByDuration = false; + } + + /// + /// Pause an Encode + /// + public void PauseEncode() + { + this.queueProcessor.Pause(); + } + + /// + /// The select all. + /// + public void SelectAll() + { + foreach (SelectionTitle item in this.TitleList) + { + item.IsSelected = true; + } + } + + /// + /// The setup. + /// + /// + /// The scanned source. + /// + public void Setup(Source scannedSource) + { + this.TitleList.Clear(); + + if (scannedSource != null) + { + IEnumerable titles = this.orderedByTitle + ? scannedSource.Titles + : scannedSource.Titles.OrderByDescending(o => o.Duration).ToList(); + + foreach (Title item in titles) + { + var title = new SelectionTitle(item, item.SourceName) { IsSelected = true }; + this.TitleList.Add(title); + } + } + } + + /// <summary> + /// Shutdown this View + /// </summary> + public void Shutdown() + { + // Shutdown Service + this.encodeService.Shutdown(); + + // Unsubscribe from Events. + this.scanService.ScanStared -= this.ScanStared; + this.scanService.ScanCompleted -= this.ScanCompleted; + this.scanService.ScanStatusChanged -= this.ScanStatusChanged; + + this.queueProcessor.QueueCompleted -= this.QueueCompleted; + this.queueProcessor.QueueChanged -= this.QueueChanged; + this.queueProcessor.JobProcessingStarted -= this.QueueProcessorJobProcessingStarted; + this.queueProcessor.EncodeService.EncodeStatusChanged -= this.EncodeStatusChanged; + } + + /// <summary> + /// Start an Encode + /// </summary> + public void StartEncode() + { + // if (this.queueProcessor.IsProcessing) + // { + // this.errorService.ShowMessageBox("HandBrake is already encoding.", "Error", MessageBoxButton.OK, MessageBoxImage.Error); + // return; + // } + + //// Check if we already have jobs, and if we do, just start the queue. + // if (this.queueProcessor.Count != 0) + // { + // this.queueProcessor.Start(); + // return; + // } + + //// Otherwise, perform Santiy Checking then add to the queue and start if everything is ok. + // if (this.SelectedTitle == null) + // { + // this.errorService.ShowMessageBox("You must first scan a source.", "Error", MessageBoxButton.OK, MessageBoxImage.Error); + // return; + // } + + // if (string.IsNullOrEmpty(this.Destination)) + // { + // this.errorService.ShowMessageBox("The Destination field was empty.", "Error", MessageBoxButton.OK, MessageBoxImage.Error); + // return; + // } + + // if (File.Exists(this.Destination)) + // { + // MessageBoxResult result = this.errorService.ShowMessageBox("The current file already exists, do you wish to overwrite it?", "Question", MessageBoxButton.YesNo, MessageBoxImage.Question); + // if (result == MessageBoxResult.No) + // { + // return; + // } + // } + + //// Create the Queue Task and Start Processing + // QueueTask task = new QueueTask + // { + // Task = new EncodeTask(this.CurrentTask), + // CustomQuery = false + // }; + // this.queueProcessor.Add(task); + // this.queueProcessor.Start(); + // this.IsEncoding = true; + } + + /// <summary> + /// Start a Scan + /// </summary> + /// <param name="filename"> + /// The filename. + /// </param> + /// <param name="title"> + /// The title. + /// </param> + public void StartScan(string filename, int title) + { + if (!string.IsNullOrEmpty(filename)) + { + this.scanService.Scan( + filename, + title, + this.UserSettingService.GetUserSetting<int>(ASUserSettingConstants.PreviewScanCount), + null); + } + } + + /// <summary> + /// Stop an Encode. + /// </summary> + public void StopEncode() + { + this.queueProcessor.Pause(); + this.encodeService.Stop(); + } + + /// <summary> + /// The select all. + /// </summary> + public void UnSelectAll() + { + foreach (SelectionTitle item in this.TitleList) + { + item.IsSelected = false; + } + } + + #endregion + + #region Methods + + /// <summary> + /// The Encode Status has changed Handler + /// </summary> + /// <param name="sender"> + /// The Sender + /// </param> + /// <param name="e"> + /// The Encode Progress Event Args + /// </param> + private void EncodeStatusChanged(object sender, EncodeProgressEventArgs e) + { + int percent; + int.TryParse(Math.Round(e.PercentComplete).ToString(CultureInfo.InvariantCulture), out percent); + + Execute.OnUIThread( + () => + { + if (this.queueProcessor.EncodeService.IsEncoding) + { + string josPending = string.Empty; + if (!AppArguments.IsInstantHandBrake) + { + josPending = ", Pending Jobs {5}"; + } + + this.ProgramStatusLabel = + string.Format( + "{0:00.00}%, FPS: {1:000.0}, Avg FPS: {2:000.0}, Time Remaining: {3}, Elapsed: {4:hh\\:mm\\:ss}" + + josPending, + e.PercentComplete, + e.CurrentFrameRate, + e.AverageFrameRate, + e.EstimatedTimeLeft, + e.ElapsedTime, + this.queueProcessor.Count); + + if (this.lastEncodePercentage != percent && this.windowsSeven.IsWindowsSeven) + { + this.windowsSeven.SetTaskBarProgress(percent); + } + + this.lastEncodePercentage = percent; + this.ProgressPercentage = percent; + this.NotifyOfPropertyChange(() => this.ProgressPercentage); + } + else + { + this.ProgramStatusLabel = "Queue Finished"; + this.IsEncoding = false; + + if (this.windowsSeven.IsWindowsSeven) + { + this.windowsSeven.SetTaskBarProgressToNoProgress(); + } + } + }); + } + + /// <summary> + /// The queue changed. + /// </summary> + /// <param name="sender"> + /// The sender. + /// </param> + /// <param name="e"> + /// The EventArgs. + /// </param> + private void QueueChanged(object sender, EventArgs e) + { + Execute.OnUIThread( + () => { this.ProgramStatusLabel = string.Format("{0} Encodes Pending", this.queueProcessor.Count); }); + } + + /// <summary> + /// The Queue has completed handler + /// </summary> + /// <param name="sender"> + /// The Sender + /// </param> + /// <param name="e"> + /// The EventArgs + /// </param> + private void QueueCompleted(object sender, EventArgs e) + { + this.IsEncoding = false; + + Execute.OnUIThread( + () => + { + this.ProgramStatusLabel = "Queue Finished"; + this.IsEncoding = false; + + if (this.windowsSeven.IsWindowsSeven) + { + this.windowsSeven.SetTaskBarProgressToNoProgress(); + } + }); + } + + /// <summary> + /// Handle the Queue Starting Event + /// </summary> + /// <param name="sender"> + /// The sender. + /// </param> + /// <param name="e"> + /// The e. + /// </param> + private void QueueProcessorJobProcessingStarted(object sender, QueueProgressEventArgs e) + { + Execute.OnUIThread( + () => + { + this.ProgramStatusLabel = "Preparing to encode ..."; + this.IsEncoding = true; + }); + } + + /// <summary> + /// Handle the Scan Completed Event + /// </summary> + /// <param name="sender"> + /// The Sender + /// </param> + /// <param name="e"> + /// The EventArgs + /// </param> + private void ScanCompleted(object sender, ScanCompletedEventArgs e) + { + this.scanService.SouceData.CopyTo(this.ScannedSource); + this.NotifyOfPropertyChange(() => this.ScannedSource); + + Execute.OnUIThread( + () => + { + if (this.scannedSource != null) + { + this.Setup(this.scannedSource); + } + + if (e.Successful) + { + this.NotifyOfPropertyChange(() => this.ScannedSource); + this.NotifyOfPropertyChange(() => this.ScannedSource.Titles); + } + + this.ShowStatusWindow = false; + if (e.Successful) + { + this.SourceLabel = this.SourceName; + this.StatusLabel = "Scan Completed"; + } + else if (e.Cancelled) + { + this.SourceLabel = "Scan Cancelled."; + this.StatusLabel = "Scan Cancelled."; + } + else if (e.Exception == null && e.ErrorInformation != null) + { + this.SourceLabel = "Scan failed: " + e.ErrorInformation; + this.StatusLabel = "Scan failed: " + e.ErrorInformation; + } + else + { + this.SourceLabel = "Scan Failed... See Activity Log for details."; + this.StatusLabel = "Scan Failed... See Activity Log for details."; + } + }); + } + + /// <summary> + /// Handle the Scan Started Event + /// </summary> + /// <param name="sender"> + /// The Sender + /// </param> + /// <param name="e"> + /// The EventArgs + /// </param> + private void ScanStared(object sender, EventArgs e) + { + Execute.OnUIThread( + () => + { + this.StatusLabel = "Scanning source, please wait..."; + this.ShowStatusWindow = true; + }); + } + + /// <summary> + /// Handle the Scan Status Changed Event. + /// </summary> + /// <param name="sender"> + /// The Sender + /// </param> + /// <param name="e"> + /// The EventArgs + /// </param> + private void ScanStatusChanged(object sender, ScanProgressEventArgs e) + { + this.SourceLabel = string.Format("Scanning Title {0} of {1} ({2}%)", e.CurrentTitle, e.Titles, e.Percentage); + this.StatusLabel = string.Format("Scanning Title {0} of {1} ({2}%)", e.CurrentTitle, e.Titles, e.Percentage); + } + + #endregion + } +} \ No newline at end of file diff --git a/win/CS/HandBrakeWPF/ViewModels/Interfaces/IInstantViewModel.cs b/win/CS/HandBrakeWPF/ViewModels/Interfaces/IInstantViewModel.cs new file mode 100644 index 000000000..5481865b8 --- /dev/null +++ b/win/CS/HandBrakeWPF/ViewModels/Interfaces/IInstantViewModel.cs @@ -0,0 +1,18 @@ +// -------------------------------------------------------------------------------------------------------------------- +// <copyright file="IInstantViewModel.cs" company="HandBrake Project (http://handbrake.fr)"> +// This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License. +// </copyright> +// <summary> +// Defines the IInstantViewModel type. +// </summary> +// -------------------------------------------------------------------------------------------------------------------- + +namespace HandBrakeWPF.ViewModels.Interfaces +{ + /// <summary> + /// The InstantViewModel interface. + /// </summary> + public interface IInstantViewModel + { + } +} \ No newline at end of file diff --git a/win/CS/HandBrakeWPF/ViewModels/ShellViewModel.cs b/win/CS/HandBrakeWPF/ViewModels/ShellViewModel.cs index 565dfda76..162898f68 100644 --- a/win/CS/HandBrakeWPF/ViewModels/ShellViewModel.cs +++ b/win/CS/HandBrakeWPF/ViewModels/ShellViewModel.cs @@ -13,11 +13,8 @@ namespace HandBrakeWPF.ViewModels using Caliburn.Micro; - using Castle.Facilities.FactorySupport; - using HandBrake.ApplicationServices.Services.Interfaces; - using HandBrakeWPF.Helpers; using HandBrakeWPF.Model; using HandBrakeWPF.Services.Interfaces; using HandBrakeWPF.ViewModels.Interfaces; @@ -121,6 +118,11 @@ namespace HandBrakeWPF.ViewModels /// </summary> public IOptionsViewModel OptionsViewModel { get; set; } + /// <summary> + /// Gets or sets the instant view model. + /// </summary> + public IInstantViewModel InstantViewModel { get; set; } + /// <summary> /// Gets or sets a value indicating whether ShowMainWindow. /// </summary> diff --git a/win/CS/HandBrakeWPF/Views/InstantMainView.xaml b/win/CS/HandBrakeWPF/Views/InstantMainView.xaml deleted file mode 100644 index 29d8f1b4e..000000000 --- a/win/CS/HandBrakeWPF/Views/InstantMainView.xaml +++ /dev/null @@ -1,182 +0,0 @@ -<UserControl x:Class="HandBrakeWPF.Views.InstantMainView" - xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:cal="http://www.caliburnproject.org" - xmlns:controls="clr-namespace:HandBrakeWPF.Controls" - xmlns:converters="clr-namespace:HandBrakeWPF.Converters" - xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" - mc:Ignorable="d" - cal:Message.Attach="[Event Loaded] = [Action Load]" - > - - <i:Interaction.Triggers> - <i:EventTrigger EventName="Drop"> - <cal:ActionMessage MethodName="FilesDroppedOnWindow"> - <cal:Parameter Value="$eventArgs" /> - </cal:ActionMessage> - </i:EventTrigger> - </i:Interaction.Triggers> - - <UserControl.Resources> - <converters:BooleanToVisibilityConverter x:Key="boolToVisConverter" /> - </UserControl.Resources> - - <!-- Window Body --> - <Grid> - <Grid.RowDefinitions> - <RowDefinition Height="Auto" /> - <RowDefinition Height="Auto" /> - <RowDefinition Height="Auto" /> - <RowDefinition Height="*" /> - <RowDefinition Height="Auto" /> - </Grid.RowDefinitions> - - - <!-- Source --> - <StackPanel Orientation="Vertical" Grid.Row="0"> - <!-- Source --> - <StackPanel Margin="10,5,5,5" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - > - <StackPanel Orientation="Horizontal"> - <Label Content="Source" FontWeight="Bold" /> - <Label Content="{Binding Path=SourceLabel}" /> - </StackPanel> - - <StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="8,10,0,10"> - - <TextBlock Text="Drag and Drop a file - OR - Open a " /> - <Button cal:Message.Attach="[Event Click] = [Action FileScan]" Padding="8,2"> - File - </Button> - <TextBlock Text="- OR -" FontWeight="Bold" Margin="10,0,10,0" /> - <Button cal:Message.Attach="[Event Click] = [Action FolderScan]" Padding="8,2"> - Folder - </Button> - - </StackPanel> - - - <StackPanel Orientation="Horizontal"> - <Label Margin="8,0,0,0" Content="Title" /> - <ComboBox Name="Titles" - MinWidth="100" - Margin="8,0,0,0" - ItemsSource="{Binding ScannedSource.Titles}" - SelectedItem="{Binding Path=SelectedTitle}" - /> - <!--<Label Margin="8,0,0,0" Content="Angle" /> - <ComboBox Name="Angles" - MinWidth="60" - Margin="8,0,0,0" - ItemsSource="{Binding Angles}" - SelectedItem="{Binding SelectedAngle}"/>--> - - <Label Margin="8,0,0,0" Content="Chapters" /> - <ComboBox Name="StartPoint" - MinWidth="60" - Margin="8,0,0,0" - ItemsSource="{Binding StartEndRangeItems}" - SelectedItem="{Binding SelectedStartPoint}"/> - - <Label Margin="8,0,0,0" Content="through" /> - <ComboBox Name="EndPoint" - MinWidth="60" - Margin="8,0,0,0" - ItemsSource="{Binding StartEndRangeItems}" - SelectedItem="{Binding SelectedEndPoint}"/> - - <Label Margin="8,0,0,0" Content="Duration" /> - <Label Margin="8,0,0,0" Content="{Binding Duration}" /> - </StackPanel> - </StackPanel> - </StackPanel> - - - <!-- Destination --> - <StackPanel Grid.Row="1" - Margin="10,5,5,5" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - > - <Label Content="Destination" FontWeight="Bold" /> - <Grid> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="Auto" /> - <ColumnDefinition Width="*" /> - <ColumnDefinition Width="Auto" /> - </Grid.ColumnDefinitions> - <Label Margin="8,0,0,0" Content="File" /> - <TextBox Name="Destination" - Grid.Column="1" - Margin="8,0,0,0" - Text="{Binding Destination, - UpdateSourceTrigger=PropertyChanged}" - /> - <Button Name="DestinationBrowser" - Grid.Column="2" - Margin="8,0,5,0" - Padding="8,2" - Content="Browse" - cal:Message.Attach="[Event Click] = [Action BrowseDestination]" - /> - </Grid> - </StackPanel> - - <!-- Opitons --> - <StackPanel Grid.Row="2" - Margin="10,5,5,5" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - > - <Label Content="Options" FontWeight="Bold" /> - - <StackPanel Orientation="Horizontal" Margin="8,0,0,0"> - <TextBlock Text="Preset:" Margin="0,0,5,0" /> - <ComboBox ItemsSource="{Binding Presets}" SelectedItem="{Binding SelectedPreset}" MinWidth="150" /> - </StackPanel> - - </StackPanel> - - - <!-- Buttons and Queueing --> - <Grid Grid.Row="3" Margin="10,5,5,5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" > - <Grid.RowDefinitions> - <RowDefinition Height="Auto" /> - <RowDefinition Height="Auto" /> - </Grid.RowDefinitions> - <Label Content="Start" FontWeight="Bold" Grid.Row="0" /> - <Button Content="Start Encoding" cal:Message.Attach="[Event Click] = [Action StartEncode]" FontWeight="Bold" Grid.Row="1" Padding="8,2" HorizontalAlignment="Center" - Visibility="{Binding IsEncoding, Converter={StaticResource boolToVisConverter}, ConverterParameter=true}" /> - <Button Content="Stop Encoding" cal:Message.Attach="[Event Click] = [Action StopEncode]" FontWeight="Bold" Grid.Row="1" Padding="8,2" HorizontalAlignment="Center" - Visibility="{Binding IsEncoding, Converter={StaticResource boolToVisConverter}, ConverterParameter=false}" /> - </Grid> - - - <!-- StatusPanel --> - <controls:StatusPanel x:Name="loadingPanel" - Grid.Row="3" - Grid.RowSpan="2" - Height="70" - VerticalAlignment="Bottom" - Panel.ZIndex="10" - IsLoading="{Binding ShowStatusWindow}" - Message="{Binding StatusLabel}" - SubMessage="Please Wait ..." - /> - - <!-- Status Bar --> - <StatusBar Grid.Row="4" Grid.ColumnSpan="2" MinHeight="32" > - - <ProgressBar Value="{Binding ProgressPercentage}" Visibility="{Binding IsEncoding, Converter={StaticResource boolToVisConverter}}" - Width="100" Height="18" VerticalAlignment="Center"/> - <Label VerticalAlignment="Center" - Content="{Binding Path=ProgramStatusLabel}" /> - - </StatusBar> - - </Grid> -</UserControl> diff --git a/win/CS/HandBrakeWPF/Views/InstantMainView.xaml.cs b/win/CS/HandBrakeWPF/Views/InstantMainView.xaml.cs deleted file mode 100644 index 4a74deba4..000000000 --- a/win/CS/HandBrakeWPF/Views/InstantMainView.xaml.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace HandBrakeWPF.Views -{ - using System.Windows.Controls; - - /// <summary> - /// Interaction logic for InstantMainView.xaml - /// </summary> - public partial class InstantMainView : UserControl - { - public InstantMainView() - { - InitializeComponent(); - } - } -} diff --git a/win/CS/HandBrakeWPF/Views/InstantView.xaml b/win/CS/HandBrakeWPF/Views/InstantView.xaml new file mode 100644 index 000000000..bb9bc1971 --- /dev/null +++ b/win/CS/HandBrakeWPF/Views/InstantView.xaml @@ -0,0 +1,202 @@ +<UserControl x:Class="HandBrakeWPF.Views.InstantView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:cal="http://www.caliburnproject.org" + xmlns:controls="clr-namespace:HandBrakeWPF.Controls" + xmlns:converters="clr-namespace:HandBrakeWPF.Converters" + xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" + mc:Ignorable="d" + cal:Message.Attach="[Event Loaded] = [Action Load]" + > + + <i:Interaction.Triggers> + <i:EventTrigger EventName="Drop"> + <cal:ActionMessage MethodName="FilesDroppedOnWindow"> + <cal:Parameter Value="$eventArgs" /> + </cal:ActionMessage> + </i:EventTrigger> + </i:Interaction.Triggers> + + <UserControl.Resources> + <converters:BooleanToVisibilityConverter x:Key="boolToVisConverter" /> + </UserControl.Resources> + + <!-- Window Body --> + <Grid> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="*" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + + + <!-- Source --> + <StackPanel Orientation="Vertical" Grid.Row="0"> + <!-- Source --> + <StackPanel Margin="10,5,5,5" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + > + <StackPanel Orientation="Horizontal"> + <Label Content="Source" FontWeight="Bold" /> + </StackPanel> + + <StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="8,10,0,10"> + + <TextBlock Text="Drag and Drop a file - OR - Open a " /> + <Button cal:Message.Attach="[Event Click] = [Action FileScan]" Padding="8,2"> + File + </Button> + <TextBlock Text="- OR -" FontWeight="Bold" Margin="10,0,10,0" /> + <Button cal:Message.Attach="[Event Click] = [Action FolderScan]" Padding="8,2"> + Folder + </Button> + + </StackPanel> + + <ListBox Grid.Row="2" + Margin="10,10,10,10" + MinHeight="150" MaxHeight="150" + VerticalAlignment="Stretch" + Background="LightGray" + ItemsSource="{Binding TitleList}" + SelectionMode="Single"> + <ListBox.ItemContainerStyle> + <Style TargetType="ListBoxItem"> + <Setter Property="HorizontalContentAlignment" Value="Stretch" /> + <Setter Property="Background" Value="WhiteSmoke" /> + <Setter Property="Margin" Value="0,0,0,1" /> + </Style> + </ListBox.ItemContainerStyle> + + <ListBox.ContextMenu> + <ContextMenu> + <MenuItem Header="Select All" cal:Message.Attach="[Event Click] = [Action SelectAll]" /> + <MenuItem Header="Deselect All" cal:Message.Attach="[Event Click] = [Action UnSelectAll]" /> + <Separator /> + <MenuItem Header="Order by Title" IsChecked="{Binding OrderedByTitle}" cal:Message.Attach="[Event Click] = [Action OrderByTitle]" /> + <MenuItem Header="Order by Duration" IsChecked="{Binding OrderedByDuration}" cal:Message.Attach="[Event Click] = [Action OrderByDuration]" /> + </ContextMenu> + </ListBox.ContextMenu> + + <ListBox.ItemTemplate> + <DataTemplate> + <Grid HorizontalAlignment="Stretch" MinHeight="28"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="*" /> + <ColumnDefinition Width="Auto" /> + </Grid.ColumnDefinitions> + + <CheckBox IsChecked="{Binding IsSelected}" Grid.RowSpan="2" Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" /> + + <StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal" Margin="10,0,0,0" > + <TextBlock Text="Title:" Margin="0,0,5,0" FontWeight="Bold" /> + <TextBlock Text="{Binding Title}" Margin="5,0,5,0" /> + </StackPanel> + + <StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal" Margin="10,0,0,0" > + <TextBlock Text="Source:" Margin="0,0,5,0" FontWeight="Bold" /> + <TextBlock Text="{Binding SourceName}" Margin="5,0,5,0" /> + </StackPanel> + </Grid> + + </DataTemplate> + </ListBox.ItemTemplate> + </ListBox> + </StackPanel> + </StackPanel> + + + <!-- Destination --> + <StackPanel Grid.Row="1" + Margin="10,5,5,5" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + > + <Label Content="Destination" FontWeight="Bold" /> + <Grid> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="*" /> + <ColumnDefinition Width="Auto" /> + </Grid.ColumnDefinitions> + <Label Margin="8,0,0,0" Content="File" /> + <TextBox Name="Destination" + Grid.Column="1" + Margin="8,0,0,0" + Text="{Binding OutputDirectory, + UpdateSourceTrigger=PropertyChanged}" + /> + <Button Name="DestinationBrowser" + Grid.Column="2" + Margin="8,0,5,0" + Padding="8,2" + Content="Browse" + cal:Message.Attach="[Event Click] = [Action BrowseDestination]" + /> + </Grid> + </StackPanel> + + <!-- Opitons --> + <StackPanel Grid.Row="2" + Margin="10,5,5,5" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + > + <Label Content="Options" FontWeight="Bold" /> + + <StackPanel Orientation="Horizontal" Margin="8,0,0,0"> + <TextBlock Text="Preset:" Margin="0,0,5,0" /> + <ComboBox ItemsSource="{Binding Presets}" SelectedItem="{Binding SelectedPreset}" MinWidth="150" /> + </StackPanel> + + </StackPanel> + + + <!-- Buttons and Queueing --> + <Grid Grid.Row="3" Margin="10,5,5,5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" > + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <Label Content="Start" FontWeight="Bold" Grid.Row="0" /> + <Button Content="Start Encoding" cal:Message.Attach="[Event Click] = [Action StartEncode]" FontWeight="Bold" Grid.Row="1" Padding="8,2" HorizontalAlignment="Center" + Visibility="{Binding IsEncoding, Converter={StaticResource boolToVisConverter}, ConverterParameter=true}" /> + <Button Content="Stop Encoding" cal:Message.Attach="[Event Click] = [Action StopEncode]" FontWeight="Bold" Grid.Row="1" Padding="8,2" HorizontalAlignment="Center" + Visibility="{Binding IsEncoding, Converter={StaticResource boolToVisConverter}, ConverterParameter=false}" /> + </Grid> + + + <!-- StatusPanel --> + <controls:StatusPanel x:Name="loadingPanel" + Grid.Row="3" + Grid.RowSpan="2" + Height="70" + VerticalAlignment="Bottom" + Panel.ZIndex="10" + IsLoading="{Binding ShowStatusWindow}" + Message="{Binding StatusLabel}" + SubMessage="Please Wait ..." + /> + + <!-- Status Bar --> + <StatusBar Grid.Row="4" Grid.ColumnSpan="2" MinHeight="32" > + + <ProgressBar Value="{Binding ProgressPercentage}" Visibility="{Binding IsEncoding, Converter={StaticResource boolToVisConverter}}" + Width="100" Height="18" VerticalAlignment="Center"/> + <Label VerticalAlignment="Center" + Content="{Binding Path=ProgramStatusLabel}" /> + + </StatusBar> + + </Grid> +</UserControl> diff --git a/win/CS/HandBrakeWPF/Views/InstantView.xaml.cs b/win/CS/HandBrakeWPF/Views/InstantView.xaml.cs new file mode 100644 index 000000000..8ca18aa87 --- /dev/null +++ b/win/CS/HandBrakeWPF/Views/InstantView.xaml.cs @@ -0,0 +1,27 @@ +// -------------------------------------------------------------------------------------------------------------------- +// <copyright file="InstantView.xaml.cs" company="HandBrake Project (http://handbrake.fr)"> +// This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License. +// </copyright> +// <summary> +// Interaction logic for InstantMainView.xaml +// </summary> +// -------------------------------------------------------------------------------------------------------------------- + +namespace HandBrakeWPF.Views +{ + using System.Windows.Controls; + + /// <summary> + /// Interaction logic for InstantMainView.xaml + /// </summary> + public partial class InstantView : UserControl + { + /// <summary> + /// Initializes a new instance of the <see cref="InstantView"/> class. + /// </summary> + public InstantView() + { + InitializeComponent(); + } + } +} diff --git a/win/CS/HandBrakeWPF/Views/ShellView.xaml b/win/CS/HandBrakeWPF/Views/ShellView.xaml index 49b7c94dc..84411cdca 100644 --- a/win/CS/HandBrakeWPF/Views/ShellView.xaml +++ b/win/CS/HandBrakeWPF/Views/ShellView.xaml @@ -25,7 +25,7 @@ <ContentControl x:Name="MainViewModel" Visibility="{Binding ShowMainWindow, Converter={StaticResource boolToVisConverter}, ConverterParameter=false}" /> <ContentControl x:Name="OptionsViewModel" Visibility="{Binding ShowOptions, Converter={StaticResource boolToVisConverter}, ConverterParameter=false}" /> - <views:InstantMainView DataContext="{Binding MainViewModel}" + <views:InstantView DataContext="{Binding InstantViewModel}" Visibility="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.ShowInstant, Converter={StaticResource boolToVisConverter}, ConverterParameter=false}" /> </Grid> </Window> -- cgit v1.2.3