/// <summary> /// コンストラクタ。 /// </summary> /// <param name="processes">VOICEROIDプロセスコレクション。</param> /// <param name="canUseConfig">各設定値を利用可能な状態であるか否か。</param> /// <param name="talkTextReplaceConfig">トークテキスト置換設定値。</param> /// <param name="exoConfig">AviUtl拡張編集ファイル用設定値。</param> /// <param name="appConfig">アプリ設定値。</param> /// <param name="uiConfig">UI設定値。</param> /// <param name="lastStatus">直近のアプリ状態値の設定先。</param> /// <param name="canModifyNotifier"> /// 設定変更可能な状態であるか否かの設定先。 /// </param> /// <param name="windowActivateService">ウィンドウアクティブ化サービス。</param> /// <param name="voiceroidActionService"> /// VOICEROIDプロセスアクションサービス。 /// </param> /// <param name="aviUtlFileDropService"> /// AviUtl拡張編集ファイルドロップサービス。 /// </param> public VoiceroidViewModel( IReadOnlyCollection <IProcess> processes, IReadOnlyReactiveProperty <bool> canUseConfig, IReadOnlyReactiveProperty <TalkTextReplaceConfig> talkTextReplaceConfig, IReadOnlyReactiveProperty <ExoConfig> exoConfig, IReadOnlyReactiveProperty <AppConfig> appConfig, IReadOnlyReactiveProperty <UIConfig> uiConfig, IReactiveProperty <IAppStatus> lastStatus, IReactiveProperty <bool> canModifyNotifier, IWindowActivateService windowActivateService, IVoiceroidActionService voiceroidActionService, IAviUtlFileDropService aviUtlFileDropService) { ValidateArgumentNull(processes, nameof(processes)); ValidateArgumentNull(canUseConfig, nameof(canUseConfig)); ValidateArgumentNull(talkTextReplaceConfig, nameof(talkTextReplaceConfig)); ValidateArgumentNull(exoConfig, nameof(exoConfig)); ValidateArgumentNull(appConfig, nameof(appConfig)); ValidateArgumentNull(uiConfig, nameof(uiConfig)); ValidateArgumentNull(lastStatus, nameof(lastStatus)); ValidateArgumentNull(canModifyNotifier, nameof(canModifyNotifier)); ValidateArgumentNull(windowActivateService, nameof(windowActivateService)); ValidateArgumentNull(voiceroidActionService, nameof(voiceroidActionService)); ValidateArgumentNull(aviUtlFileDropService, nameof(aviUtlFileDropService)); this.LastStatus = lastStatus; this.WindowActivateService = windowActivateService; this.VoiceroidActionService = voiceroidActionService; this.IsTextClearing = this.MakeInnerPropertyOf(appConfig, c => c.IsTextClearing); this.VoiceroidExecutablePathes = this.MakeInnerPropertyOf( uiConfig, c => c.VoiceroidExecutablePathes, notifyOnSameValue: true); // 表示状態のVOICEROIDプロセスコレクション this.VisibleProcesses = appConfig .ObserveInnerProperty(c => c.VoiceroidVisibilities) .Select(vv => vv.SelectVisibleOf(processes)) .ToReadOnlyReactiveProperty() .AddTo(this.CompositeDisposable); // VOICEROID選択コマンド実行可能状態 // 表示状態のVOICEROIDプロセスが2つ以上あれば選択可能 this.IsSelectVoiceroidCommandExecutable = this.VisibleProcesses .Select(vp => vp.Count >= 2) .ToReadOnlyReactiveProperty() .AddTo(this.CompositeDisposable); // VOICEROID選択コマンドのチップテキスト this.SelectVoiceroidCommandTip = this.VisibleProcesses .Select(_ => this.MakeSelectVoiceroidCommandTip()) .ToReadOnlyReactiveProperty() .AddTo(this.CompositeDisposable); // 最適表示列数 // 6プロセス単位で列数を増やす this.VisibleProcessesColumnCount = this.VisibleProcesses .Select(vp => Math.Min(Math.Max(1, (vp.Count + 5) / 6), 3)) .ToReadOnlyReactiveProperty() .AddTo(this.CompositeDisposable); // 選択中VOICEROIDプロセス this.SelectedProcess = new ReactiveProperty <IProcess>(this.VisibleProcesses.Value.First()) .AddTo(this.CompositeDisposable); // UI設定周りのセットアップ this.SetupUIConfig(uiConfig, processes); // 選択プロセス状態 this.IsProcessStartup = this .ObserveSelectedProcessProperty(p => p.IsStartup) .ToReadOnlyReactiveProperty() .AddTo(this.CompositeDisposable); this.IsProcessRunning = this .ObserveSelectedProcessProperty(p => p.IsRunning) .ToReadOnlyReactiveProperty() .AddTo(this.CompositeDisposable); this.IsProcessPlaying = this .ObserveSelectedProcessProperty(p => p.IsPlaying) .ToReadOnlyReactiveProperty() .AddTo(this.CompositeDisposable); this.IsProcessExecutable = Observable .CombineLatest( this.SelectedProcess, uiConfig.ObserveInnerProperty(c => c.VoiceroidExecutablePathes), (p, pathes) => { var path = (p == null) ? null : pathes[p.Id]?.Path; return(!string.IsNullOrEmpty(path) && File.Exists(path)); }) .ToReadOnlyReactiveProperty() .AddTo(this.CompositeDisposable); var processSaving = this .ObserveSelectedProcessProperty(p => p.IsSaving) .DistinctUntilChanged(); var processDialogShowing = this .ObserveSelectedProcessProperty(p => p.IsDialogShowing) .DistinctUntilChanged(); // トークテキスト this.TalkText = new ReactiveProperty <string>("").AddTo(this.CompositeDisposable); this.TalkTextLengthLimit = new ReactiveProperty <int>(TextComponent.TextLengthLimit) .AddTo(this.CompositeDisposable); this.IsTalkTextTabAccepted = this.MakeInnerPropertyOf(appConfig, c => c.IsTabAccepted); // アイドル状態設定先 var idle = new ReactiveProperty <bool>(true).AddTo(this.CompositeDisposable); this.IsIdle = idle; // 音声保存コマンド処理中フラグ設定先 var saveCommandBusy = new ReactiveProperty <bool>(false).AddTo(this.CompositeDisposable); // トークテキストファイルドロップコマンド処理中フラグ設定先 var dropTalkTextFileCommandBusy = new ReactiveProperty <bool>(false).AddTo(this.CompositeDisposable); // 本体側のテキストを使う設定 this.UseTargetText = this.MakeInnerPropertyOf( appConfig, c => c.UseTargetText, new[] { canUseConfig, this.IsIdle } .CombineLatestValuesAreAllTrue() .ToReadOnlyReactiveProperty() .AddTo(this.CompositeDisposable)); // - 本体側のテキストを使う設定が有効 // または // - 音声保存処理中かつ保存成功時クリア設定が有効 // ならばトークテキスト編集不可 this.IsTalkTextEditable = new[] { this.UseTargetText, new[] { saveCommandBusy, this.IsTextClearing } .CombineLatestValuesAreAllTrue() .DistinctUntilChanged(), } .CombineLatest(flags => flags.Any(f => f)) .Inverse() .ToReadOnlyReactiveProperty() .AddTo(this.CompositeDisposable); // VOICEROID選択コマンドコレクション(要素数 10 固定) this.SelectVoiceroidCommands = new ReadOnlyCollection <ICommand>( Enumerable.Range(0, 10) .Select( index => this.MakeCommand( () => this.ExecuteSelectVoiceroidCommand(index), this.IsSelectVoiceroidCommandExecutable, this.VisibleProcesses .Select(vp => index < vp.Count) .DistinctUntilChanged())) .ToArray()); // 前方/後方VOICEROID選択コマンド this.SelectPreviousVoiceroidCommand = this.MakeCommand( this.ExecuteSelectPreviousVoiceroidCommand, this.IsSelectVoiceroidCommandExecutable); this.SelectNextVoiceroidCommand = this.MakeCommand( this.ExecuteSelectNextVoiceroidCommand, this.IsSelectVoiceroidCommandExecutable); // 起動/終了コマンド this.RunExitCommand = this.MakeAsyncCommand( this.ExecuteRunExitCommand, canUseConfig, this.IsIdle, this.IsProcessStartup.Inverse(), processSaving.Inverse(), processDialogShowing.Inverse()); // 再生/停止コマンド var playStopCommandHolder = new AsyncPlayStopCommandHolder( new[] { canUseConfig, this.IsIdle, this.IsProcessRunning, processSaving.Inverse(), processDialogShowing.Inverse(), dropTalkTextFileCommandBusy.Inverse(), } .CombineLatestValuesAreAllTrue() .DistinctUntilChanged() .ObserveOnUIDispatcher(), () => this.SelectedProcess.Value, () => talkTextReplaceConfig.Value.VoiceReplaceItems, () => this.TalkText.Value, () => appConfig.Value.UseTargetText); this.PlayStopCommand = playStopCommandHolder.Command; playStopCommandHolder.Result .Where(r => r != null) .Subscribe(async r => await this.OnPlayStopCommandExecuted(r)) .AddTo(this.CompositeDisposable); // 保存コマンド var saveCommandHolder = new AsyncSaveCommandHolder( new[] { canUseConfig, this.IsIdle, this.IsProcessRunning, processSaving.Inverse(), processDialogShowing.Inverse(), new[] { this.TalkText .Select(t => !string.IsNullOrWhiteSpace(t)) .DistinctUntilChanged(), this.UseTargetText, // 敢えて空白文を保存したいことはまず無いと思われるので、 // 誤クリック抑止の意味も込めて空白文は送信不可とする。 // ただし本体側の文章を使う場合は空白文でも保存可能とする。 // ↓のコメントを外すと(可能ならば)空白文送信保存可能になる。 //this // .ObserveSelectedProcessProperty(p => p.CanSaveBlankText) // .DistinctUntilChanged(), } .CombineLatest(flags => flags.Any(f => f)) .DistinctUntilChanged(), dropTalkTextFileCommandBusy.Inverse(), } .CombineLatestValuesAreAllTrue() .DistinctUntilChanged() .ObserveOnUIDispatcher(), () => this.SelectedProcess.Value, () => talkTextReplaceConfig.Value, () => exoConfig.Value, () => appConfig.Value, () => this.TalkText.Value, aviUtlFileDropService); this.SaveCommand = saveCommandHolder.Command; saveCommandHolder.IsBusy .Subscribe(f => saveCommandBusy.Value = f) .AddTo(this.CompositeDisposable); saveCommandHolder.Result .Where(r => r != null) .Subscribe(async r => await this.OnSaveCommandExecuted(r)) .AddTo(this.CompositeDisposable); // 再生も音声保存もしていない時をアイドル状態とみなす new[] { playStopCommandHolder.IsBusy, saveCommandHolder.IsBusy, } .CombineLatestValuesAreAllFalse() .DistinctUntilChanged() .Subscribe( f => { idle.Value = f; // アイドル状態なら設定変更可能とする canModifyNotifier.Value = f; }) .AddTo(this.CompositeDisposable); // トークテキスト用ファイルドラッグオーバーコマンド this.DragOverTalkTextFileCommand = this.MakeCommand <DragEventArgs>( this.ExecuteDragOverTalkTextFileCommand, this.IsTalkTextEditable); // トークテキスト用ファイルドロップコマンド var dropTalkTextFileCommandHolder = new AsyncCommandHolder <DragEventArgs>( this.IsTalkTextEditable, this.ExecuteDropTalkTextFileCommand); this.DropTalkTextFileCommand = dropTalkTextFileCommandHolder.Command; dropTalkTextFileCommandHolder.IsBusy .Subscribe(f => dropTalkTextFileCommandBusy.Value = f) .AddTo(this.CompositeDisposable); }
/// <summary> /// コンストラクタ。 /// </summary> /// <param name="processes">VOICEROIDプロセスコレクション。</param> /// <param name="canUseConfig">各設定値を利用可能な状態であるか否か。</param> /// <param name="talkTextReplaceConfig">トークテキスト置換設定値。</param> /// <param name="exoConfig">AviUtl拡張編集ファイル用設定値。</param> /// <param name="appConfig">アプリ設定値。</param> /// <param name="uiConfig">UI設定値。</param> /// <param name="lastStatus">直近のアプリ状態値の設定先。</param> /// <param name="openFileDialogService">ファイル選択ダイアログサービス。</param> /// <param name="windowActivateService">ウィンドウアクティブ化サービス。</param> /// <param name="voiceroidActionService"> /// VOICEROIDプロセスアクションサービス。 /// </param> /// <param name="aviUtlFileDropService"> /// AviUtl拡張編集ファイルドロップサービス。 /// </param> public MainWindowViewModel( IReadOnlyCollection <IProcess> processes, IReadOnlyReactiveProperty <bool> canUseConfig, IReadOnlyReactiveProperty <TalkTextReplaceConfig> talkTextReplaceConfig, IReadOnlyReactiveProperty <ExoConfig> exoConfig, IReadOnlyReactiveProperty <AppConfig> appConfig, IReadOnlyReactiveProperty <UIConfig> uiConfig, IReactiveProperty <IAppStatus> lastStatus, IOpenFileDialogService openFileDialogService, IWindowActivateService windowActivateService, IVoiceroidActionService voiceroidActionService, IAviUtlFileDropService aviUtlFileDropService) { ValidateArgumentNull(processes, nameof(processes)); ValidateArgumentNull(canUseConfig, nameof(canUseConfig)); ValidateArgumentNull(talkTextReplaceConfig, nameof(talkTextReplaceConfig)); ValidateArgumentNull(exoConfig, nameof(exoConfig)); ValidateArgumentNull(appConfig, nameof(appConfig)); ValidateArgumentNull(uiConfig, nameof(uiConfig)); ValidateArgumentNull(lastStatus, nameof(lastStatus)); ValidateArgumentNull(openFileDialogService, nameof(openFileDialogService)); ValidateArgumentNull(windowActivateService, nameof(windowActivateService)); ValidateArgumentNull(voiceroidActionService, nameof(voiceroidActionService)); ValidateArgumentNull(aviUtlFileDropService, nameof(aviUtlFileDropService)); this.IsTopmost = this.MakeInnerPropertyOf(appConfig, c => c.IsTopmost); // VoiceroidViewModel 作成 var canModifyNotifier = new ReactiveProperty <bool>(false).AddTo(this.CompositeDisposable); this.Voiceroid = new VoiceroidViewModel( processes, canUseConfig, talkTextReplaceConfig, exoConfig, appConfig, uiConfig, lastStatus, canModifyNotifier, windowActivateService, voiceroidActionService, aviUtlFileDropService) .AddTo(this.CompositeDisposable); // 設定変更可能状態 var canModify = new[] { canUseConfig, canModifyNotifier } .CombineLatestValuesAreAllTrue() .ToReadOnlyReactiveProperty() .AddTo(this.CompositeDisposable); // その他 ViewModel 作成 this.TalkTextReplaceConfig = new TalkTextReplaceConfigViewModel( canModify, talkTextReplaceConfig, appConfig, uiConfig, lastStatus) .AddTo(this.CompositeDisposable); this.ExoConfig = new ExoConfigViewModel( canModify, exoConfig, appConfig, uiConfig, lastStatus, openFileDialogService) .AddTo(this.CompositeDisposable); this.AppConfig = new AppConfigViewModel( canModify, appConfig, uiConfig, lastStatus, openFileDialogService) .AddTo(this.CompositeDisposable); this.LastStatus = new AppStatusViewModel(lastStatus).AddTo(this.CompositeDisposable); }