Пример #1
0
        /// <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);
        }
Пример #2
0
        /// <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);
        }