Example #1
0
        public static EngineOptionSettingDialog Build(

            // 共通設定のために必要なもの
            EngineOptionsForSetting commonSettings,
            EngineConfig config,

            // 個別設定のために必要なもの
            string engineDefineFolderPath
            )
        {
            var engineDefineEx = TheApp.app.EngineDefines.Find(x => x.FolderPath == engineDefineFolderPath);

            // -- エンジン共通設定

            commonSettings.IndivisualSetting = false;      // エンジン共通設定
            commonSettings.BuildOptionsFromDescriptions(); // OptionsをDescriptionsから構築する。

            // エンジン共通設定の、ユーザーの選択をシリアライズしたものがあるなら、そのValueを上書きする。
            var commonOptions = config.CommonOptions;

            if (commonOptions != null)
            {
                commonSettings.OverwriteEngineOptions(commonOptions);
            }


            // -- エンジン個別設定

            // -- エンジン個別設定

            // 思考エンジンの個別ダイアログのための項目を、実際に思考エンジンを起動して取得。
            // 一瞬で起動~終了するはずなので、UIスレッドでやっちゃっていいや…。
            var engineDefine = engineDefineEx.EngineDefine;
            var exefilename  = engineDefine.EngineExeFileName();

            var engine = new UsiEnginePlayer();

            try
            {
                engine.Engine.EngineSetting = true;
                engine.Start(exefilename);

                while (engine.Initializing)
                {
                    engine.OnIdle();
                    Thread.Sleep(100);
                    var ex = engine.Engine.Exception;
                    if (ex != null)
                    {
                        // タイムアウトでも例外が飛んでくるはず…。
                        TheApp.app.MessageShow(ex.Pretty(), MessageShowType.Error);
                        return(null);
                    }
                }
            }
            finally
            {
                engine.Dispose();
            }

            // エンジンからこれが取得出来ているはずなのだが。
            Debug.Assert(engine.Engine.OptionList != null);

            // エンジンからUsiOption文字列を取得

            var ind_options = new List <EngineOptionForSetting>();


            // "USI_Hash","Hash"の見つけたほうのコマンドを記録しておく。両方なければ"USI_Hash"にする。

            UsiOption USI_Hash_Option = null;
            UsiOption Hash_Option     = null;

            foreach (var option in engine.Engine.OptionList)
            {
                //Console.WriteLine(option.CreateOptionCommandString());

                // "USI_Ponder"は無視する。
                if (option.Name == "USI_Ponder")
                {
                    continue;
                }

                // "USI_Hash","Hash"は統合する。このループが抜けた直後に追加するので、ここでは無視で。
                else if (option.Name == "USI_Hash")
                {
                    USI_Hash_Option = option;
                    continue;
                }
                else if (option.Name == "Hash")
                {
                    Hash_Option = option;
                    continue;
                }

                var opt = new EngineOptionForSetting(option.Name, option.CreateOptionCommandString());
                opt.Value = option.GetDefault();
                ind_options.Add(opt);
            }

            // "USI_Hash","Hash"の両方が見つかるケース → "USI_Hash"を採用
            // 両方が見つからないケース → 勝手に"USI_Hash"を生成しとく。
            // 片側だけ見つかるケース → それを採用。
            // ※ このオプションのデフォルト値を取得してこないとダイアログに表示できないため、
            // このような慎重な処理になっている。

            if (USI_Hash_Option != null)
            {
                var opt = new EngineOptionForSetting("Hash_", USI_Hash_Option.CreateOptionCommandString());
                opt.Value = USI_Hash_Option.GetDefault();
                ind_options.Add(opt);
            }
            else if (Hash_Option != null)
            {
                var opt = new EngineOptionForSetting("Hash_", Hash_Option.CreateOptionCommandString());
                opt.Value = Hash_Option.GetDefault();
                ind_options.Add(opt);
            }
            else
            {
                var option = UsiOption.Parse("option name Hash_ type spin default 1024 min 1 max 1048576");
                var opt    = new EngineOptionForSetting(option.Name, option.CreateOptionCommandString());
                opt.Value = option.GetDefault();
                ind_options.Add(opt);
            }


            var ind_descriptions = engineDefine.EngineOptionDescriptions; // nullありうる

            if (ind_descriptions == null)
            {
                // この時は仕方ないので、Optionsの内容そのまま出しておかないと仕方ないのでは…。

                // headerとして、ハッシュ設定、スレッド設定が必要。
                ind_descriptions = EngineCommonOptionsSample.CreateEngineMinimumOptions().Descriptions;

                //ind_descriptions = new List<EngineOptionDescription>();

                // headerに存在しないoptionをheaderに追加。
                foreach (var option in ind_options)
                {
                    if (!ind_descriptions.Exists(x => x.Name == option.Name))
                    {
                        ind_descriptions.Add(new EngineOptionDescription(option.Name, option.Name, null, null, option.UsiBuildString));
                    }
                }

                // headerにあり、ind_optionsにない要素を追加。
                foreach (var desc in ind_descriptions)
                {
                    if (!ind_options.Exists(x => x.Name == desc.Name))
                    {
                        var option = new EngineOptionForSetting(desc.Name, desc.Name)
                        {
                            UsiBuildString = desc.UsiBuildString
                        };
                        ind_options.Add(option);
                    }
                }
            }
            else
            {
                // Descriptionsに欠落している項目を追加する。(Optionsにだけある項目を追加する。)
                foreach (var option in ind_options)
                {
                    if (!ind_descriptions.Exists(x => x.Name == option.Name))
                    {
                        ind_descriptions.Add(new EngineOptionDescription(option.Name, option.Name, null, null, option.UsiBuildString));
                    }
                }

                // DescriptionにあってOptionsにない項目をOptionsに追加する。
                foreach (var desc in ind_descriptions)
                {
                    if (!ind_options.Exists(x => x.Name == desc.Name))
                    {
                        ind_options.Add(new EngineOptionForSetting(desc.Name, desc.UsiBuildString));
                    }
                }
            }

            // -- エンジン個別設定でシリアライズしていた値で上書きしてやる。
            var indivisualConfig = config.IndivisualEnginesOptions.Find(x => x.FolderPath == engineDefineEx.FolderPath);

            if (indivisualConfig == null)
            {
                indivisualConfig = new IndivisualEngineOptions(engineDefineEx.FolderPath);
                config.IndivisualEnginesOptions.Add(indivisualConfig);
            }
            if (indivisualConfig.Options == null)
            {
                indivisualConfig.Options = new List <EngineOptionForIndivisual>();
            }

            var ind_setting = new EngineOptionsForSetting()
            {
                Options           = ind_options,      // エンジンから取得したオプション一式
                Descriptions      = ind_descriptions, // 個別設定のDescription
                IndivisualSetting = true,             // エンジン個別設定モードに
            };

            if (ind_setting.Options != null)
            {
                ind_setting.OverwriteEngineOptions(indivisualConfig.Options, commonSettings);
            }

            // -- ダイアログの構築

            var dialog = new EngineOptionSettingDialog();

            dialog.SettingControls(0).ViewModel.Setting = commonSettings;
            dialog.SettingControls(0).ViewModel.AddPropertyChangedHandler("ValueChanged", (args) =>
            {
                config.CommonOptions = commonSettings.ToEngineOptions();
                // 値が変わるごとに保存しておく。
            });

            dialog.SettingControls(1).ViewModel.Setting = ind_setting;
            dialog.SettingControls(1).ViewModel.AddPropertyChangedHandler("ValueChanged", (args) =>
            {
                indivisualConfig.Options = ind_setting.ToEngineOptionsForIndivisual();
            });
            dialog.ViewModel.EngineDisplayName = engineDefine.DisplayName;
            dialog.ViewModel.EngineConfigType  = config.EngineConfigType;

            return(dialog);
        }
        /// <summary>
        /// ViewModel.Settingが更新された時に、ViewModelのOptionsを更新する。
        /// </summary>
        /// <param name="setting"></param>
        private void UpdateUsiOptions(EngineOptionsForSetting setting)
        {
            Debug.Assert(setting != null && setting.Options != null && setting.Descriptions != null);

            var followCommonSettingDescription =
                "左側のチェックボックスの説明\r\n" +
                "このチェックボックスをオンにすると、この項目は共通設定に従います。\r\n" +
                "このチェックボックスをオフにすると、このエンジン用にこの項目の値を個別に設定できます。";

            // -- 順番にControlを生成して表示する。
            SuspendLayout();

            ClearPages();

            var page = new List <Control>();

            pages.Add(page);

            // これを基準に高さを調整していく。
            var hh = label1.Height;

            int y = hh / 4; // 最初の行のY座標。

            // エンジン個別設定なら「共通設定に従う」チェックボックスを左端に追加するので全体的に少し右にずらして表示する。
            var indivisual = setting.IndivisualSetting;
            var x_offset   = indivisual ? hh * 2 : 0;

            // bindしている値が変化した時にイベントを生起する
            var valueChanged = new Action(() => ViewModel.RaisePropertyChanged("ValueChanged", null));

            for (var k = 0; k < setting.Descriptions.Count; ++k)
            {
                var desc = setting.Descriptions[k];

                if (desc.Hide)
                {
                    continue;
                }

                var name = desc.Name;
                if (name == null)
                {
                    // 見出し項目のようであるな…。

                    // 先頭要素でなければ、水平線必要だわ。
                    if (pages[pages.Count - 1].Count != 0)
                    {
                        var label = new Label();
                        // 水平線なのでフォントは問題ではない
                        label.Font        = new Font("MS ゴシック", 9);
                        label.Location    = new Point(label_x[0] + x_offset, y);
                        label.Size        = new Size(ClientSize.Width, 1);
                        label.BorderStyle = BorderStyle.FixedSingle;
                        y += hh / 2;
                        Controls.Add(label);
                        page.Add(label);
                    }

                    var displayName = desc.DisplayName.TrimStart();

                    var description =
                        displayName + "の説明\r\n" +
                        (desc.Description == null ? "説明文がありません。" : desc.Description);

                    var label1 = new Label();
                    label1.Location = new Point(label_x[0] + x_offset, y);
                    label1.AutoSize = true;
                    label1.Text     = desc.DisplayName;
                    label1.Font     = new Font("MS ゴシック", 14);
                    // Font()の第三引数に " GraphicsUnit.Pixel "をつけるとdpi設定の影響を受けない。
                    // ここでは、dpi設定の影響を受けないといけないので、指定しない。

                    toolTip1.SetToolTip(label1, description);

                    Controls.Add(label1);
                    page.Add(label1);

                    y += label1.Height + hh / 2;
                    continue;
                }

                var e = setting.Options.Find(x => x.Name == name);
                if (e == null)
                {
                    continue;
                }

                UsiOption usiOption;
                try
                {
                    // parse出来なければ無視しておく。
                    usiOption = UsiOption.Parse(e.UsiBuildString);

                    // bindする予定の値がないなら、UsiOptionの生成文字列中の"default"の値を
                    // 持ってくる。初回起動時などはこの動作になる。
                    if (e.Value == null)
                    {
                        e.Value = usiOption.GetDefault();
                    }
                }
                catch
                {
                    // デバッグ用に、Parse()に失敗した文字列を出力してみる。
                    //Console.WriteLine(e.UsiBuildString);

                    continue;
                }

                Control control     = null;
                var     defaultText = usiOption.GetDefault();

                switch (usiOption.OptionType)
                {
                case UsiOptionType.CheckBox:
                    var checkbox = new CheckBox();

                    binder.BindString(e, "Value", checkbox, valueChanged);

                    control     = checkbox;
                    defaultText = usiOption.DefaultBool ? "オン(true)" : "オフ(false)";
                    break;

                case UsiOptionType.SpinBox:
                    var num = new NumericUpDown();
                    num.Minimum   = usiOption.MinValue;
                    num.Maximum   = usiOption.MaxValue;
                    num.Value     = usiOption.DefaultValue;
                    num.TextAlign = HorizontalAlignment.Center;

                    binder.BindString(e, "Value", num, valueChanged);

                    control = num;
                    break;

                case UsiOptionType.ComboBox:
                    var combobox = new ComboBox();
                    combobox.DropDownStyle = ComboBoxStyle.DropDownList;

                    // comboboxは、readonlyにすると勝手に背景色が変わってしまう..
                    // combobox.BackColor = System.Drawing.Color.White;
                    // → これでは変更できない…。

                    //combobox.FlatStyle = FlatStyle.Flat;
                    // → これ、ComboBoxを配置しているところが白いと同化して見にくい。
                    // まあいいや…。

                    if (desc.ComboboxDisplayName == null)
                    {
                        foreach (var s in usiOption.ComboList)
                        {
                            combobox.Items.Add(s);
                        }

                        binder.BindString(e, "Value", combobox, valueChanged);
                    }
                    else
                    {
                        // ComboBoxの日本語化
                        var split = desc.ComboboxDisplayName.Split(',');
                        var dic   = new Dictionary <string, string>();
                        for (int i = 0; i < split.Length / 2; ++i)
                        {
                            dic.Add(split[i * 2 + 0], split[i * 2 + 1]);
                        }

                        foreach (var s in usiOption.ComboList)
                        {
                            if (dic.ContainsKey(s))
                            {
                                combobox.Items.Add(dic[s]);
                            }

                            // デフォルト値もdicを経由して表示名に変換しておいてやる。
                            if (dic.ContainsKey(defaultText))
                            {
                                defaultText = dic[defaultText];
                            }
                        }

                        binder.BindStringWithDic(e, "Value", combobox, valueChanged, dic);
                    }

                    control = combobox;
                    break;

                case UsiOptionType.TextBox:
                    var textbox = new TextBox();

                    binder.BindString(e, "Value", textbox, valueChanged);

                    control = textbox;
                    break;
                }
                if (control != null)
                {
                    var displayName = desc.DisplayName == null ? desc.Name : desc.DisplayName;
                    var description =
                        displayName + "の説明\r\n" +
                        (desc.Description == null ? "説明文がありません。" : desc.Description) +
                        $"\r\nデフォルト値 = {defaultText}";

                    var label1 = new Label();
                    label1.Font     = new Font("MS ゴシック", 10);
                    label1.Location = new Point(label_x[0] + x_offset, y);
                    label1.AutoSize = true;
                    label1.Text     = displayName.LeftUnicode(18); // GPS将棋とか長すぎるオプション名がある。
                    toolTip1.SetToolTip(label1, description);

                    Controls.Add(label1);
                    page.Add(label1);

                    var label2 = new Label();
                    label2.Font     = new Font("MS ゴシック", 10);
                    label2.Location = new Point(label_x[2] + x_offset + hh /* 配置したcontrolから少し右に配置 */, y);
                    label2.AutoSize = true;
                    label2.Text     = desc.DescriptionSimple;
                    toolTip1.SetToolTip(label2, description);

                    Controls.Add(label2);
                    page.Add(label2);

                    control.Location = new Point(label_x[1] + x_offset, y);
                    toolTip1.SetToolTip(control, description);

                    control.Font = new Font("MS ゴシック", 9);

                    control.Size = new Size(label_x[2] - label_x[1], control.Height);
                    Controls.Add(control);
                    page.Add(control);

                    if (indivisual && e.EnableFollowCommonSetting)
                    {
                        // エンジン個別設定なので左端に「共通設定に従う」のチェックボックスを配置する。

                        var checkbox = new CheckBox();
                        checkbox.Location = new Point(label_x[0], y);
                        checkbox.Size     = new Size(x_offset, control.Height);
                        //checkbox.MouseHover += followCheckboxHover;
                        toolTip1.SetToolTip(checkbox, followCommonSettingDescription);

                        Controls.Add(checkbox);
                        page.Add(checkbox);

                        binder.Bind(e, "FollowCommonSetting", checkbox, v => {
                            valueChanged();
                            // 連動してlabel1,2,controlを無効化
                            label1.Enabled  = !v;
                            label2.Enabled  = !v;
                            control.Enabled = !v;
                        });

                        // ラベルをクリックしても左端のチェックボックスのオンオフが出来る。
                        //label1.Click += (sender,args)=> { checkbox.Checked ^= true; };
                        // …ようにしようと思ったら、label1がEnable == falseになってしまうので
                        // このクリックイベントが発生しなくなるのか…。うーむ、、そうか…。
                    }

                    y += control.Height + hh / 3;
                }

                // 次の項目が見出し項目であるか
                var nextIsHeader = k + 1 < setting.Descriptions.Count && !setting.Descriptions[k + 1].Hide && setting.Descriptions[k + 1].Name == null;
                if ((y >= button1.Location.Y - button1.Height * 2.5) ||
                    (nextIsHeader && y >= button1.Location.Y - button1.Height * 4.5)    /* 次が見出し項目なら早めに次ページに改ページする */
                    )
                {
                    // 次ページに

                    page = new List <Control>();
                    pages.Add(page);
                    y = hh / 4;
                }
            }

            // 最後のページから空のページでありうるので、その場合、最後のページを削除する。
            if (pages.Count != 0 && pages[pages.Count - 1].Count == 0)
            {
                pages.RemoveAt(pages.Count - 1);
            }

            ResumeLayout();

            UpdatePage(0);
        }
Example #3
0
        public static EngineOptionSettingDialog Build(

            // 共通設定のために必要なもの
            EngineOptionsForSetting commonSettings,
            EngineConfig config,

            // 個別設定のために必要なもの
            string engineDefineFolderPath
            )
        {
            var engineDefineEx = TheApp.app.EngineDefines.Find(x => x.FolderPath == engineDefineFolderPath);

            // -- エンジン共通設定

            commonSettings.IndivisualSetting = false;      // エンジン共通設定
            commonSettings.BuildOptionsFromDescriptions(); // OptionsをDescriptionsから構築する。

            // エンジン共通設定の、ユーザーの選択をシリアライズしたものがあるなら、そのValueを上書きする。
            var commonOptions = config.CommonOptions;

            if (commonOptions != null)
            {
                commonSettings.OverwriteEngineOptions(commonOptions);
            }


            // -- エンジン個別設定

            // -- エンジン個別設定

            // 思考エンジンの個別ダイアログのための項目を、実際に思考エンジンを起動して取得。
            // 一瞬で起動~終了するはずなので、UIスレッドでやっちゃっていいや…。
            var engineDefine = engineDefineEx.EngineDefine;
            var exefilename  = engineDefine.EngineExeFileName();

            var engine = new UsiEnginePlayer();

            try
            {
                engine.Engine.EngineSetting = true;
                engine.Start(exefilename);

                while (engine.Initializing)
                {
                    engine.OnIdle();
                    Thread.Sleep(100);
                    var ex = engine.Engine.Exception;
                    if (ex != null)
                    {
                        // time outも例外が飛んでくる…ようにすべき…。
                        // 現状の思考エンジンでここでタイムアウトにならないから、まあいいや…。
                        TheApp.app.MessageShow(ex.ToString(), MessageShowType.Error);
                        return(null);
                    }
                }
            }
            finally
            {
                engine.Dispose();
            }

            // エンジンからこれが取得出来ているはずなのだが。
            Debug.Assert(engine.Engine.OptionList != null);

            // エンジンからUsiOption文字列を取得

            var useHashCommand = engineDefine.IsSupported(ExtendedProtocol.UseHashCommandExtension);

            var ind_options = new List <EngineOptionForSetting>();

            // "Hash"は一つでいいので、2つ目を追加しないようにするために1度目のカウントであるかをフラグを持っておく。
            bool first_hash = true;

            foreach (var option in engine.Engine.OptionList)
            {
                //Console.WriteLine(option.CreateOptionCommandString());

                // "USI_Ponder"は無視する。
                if (option.Name == "USI_Ponder")
                {
                    continue;
                }

                // "USI_Hash","Hash"は統合する。
                else if (option.Name == "USI_Hash")
                {
                    // USI_Hash使わないエンジンなので無視する。
                    if (useHashCommand)
                    {
                        continue;
                    }

                    if (!first_hash)
                    {
                        continue;
                    }

                    option.SetName("Hash_"); // これにしておけばあとで置換される。
                    first_hash = false;
                }
                else if (option.Name == "Hash")
                {
                    //Debug.Assert(useHashCommand);

                    if (!first_hash)
                    {
                        continue;
                    }

                    option.SetName("Hash_"); // これにしておけばあとで置換される。
                    first_hash = false;
                }

                var opt = new EngineOptionForSetting(option.Name, option.CreateOptionCommandString());
                opt.Value = option.GetDefault();
                ind_options.Add(opt);
            }

            var ind_descriptions = engineDefine.EngineOptionDescriptions; // nullありうる

            if (ind_descriptions == null)
            {
                // この時は仕方ないので、Optionsの内容そのまま出しておかないと仕方ないのでは…。

                // headerとして、ハッシュ設定、スレッド設定が必要。
                ind_descriptions = EngineCommonOptionsSample.CreateEngineMinimumOptions().Descriptions;

                //ind_descriptions = new List<EngineOptionDescription>();

                // headerに存在しないoptionをheaderに追加。
                foreach (var option in ind_options)
                {
                    if (!ind_descriptions.Exists(x => x.Name == option.Name))
                    {
                        ind_descriptions.Add(new EngineOptionDescription(option.Name, option.Name, null, null, option.UsiBuildString));
                    }
                }

                // headerにあり、ind_optionsにない要素を追加。
                foreach (var desc in ind_descriptions)
                {
                    if (!ind_options.Exists(x => x.Name == desc.Name))
                    {
                        var option = new EngineOptionForSetting(desc.Name, desc.Name)
                        {
                            UsiBuildString = desc.UsiBuildString
                        };
                        ind_options.Add(option);
                    }
                }
            }
            else
            {
                // Descriptionsに欠落している項目を追加する。(Optionsにだけある項目を追加する。)
                foreach (var option in ind_options)
                {
                    if (!ind_descriptions.Exists(x => x.Name == option.Name))
                    {
                        ind_descriptions.Add(new EngineOptionDescription(option.Name, option.Name, null, null, option.UsiBuildString));
                    }
                }

                // DescriptionにあってOptionsにない項目をOptionsに追加する。
                foreach (var desc in ind_descriptions)
                {
                    if (!ind_options.Exists(x => x.Name == desc.Name))
                    {
                        ind_options.Add(new EngineOptionForSetting(desc.Name, desc.UsiBuildString));
                    }
                }
            }

            // -- エンジン個別設定でシリアライズしていた値で上書きしてやる。
            var indivisualConfig = config.IndivisualEnginesOptions.Find(x => x.FolderPath == engineDefineEx.FolderPath);

            if (indivisualConfig == null)
            {
                indivisualConfig = new IndivisualEngineOptions(engineDefineEx.FolderPath);
                config.IndivisualEnginesOptions.Add(indivisualConfig);
            }
            if (indivisualConfig.Options == null)
            {
                indivisualConfig.Options = new List <EngineOptionForIndivisual>();
            }

            var ind_setting = new EngineOptionsForSetting()
            {
                Options           = ind_options,      // エンジンから取得したオプション一式
                Descriptions      = ind_descriptions, // 個別設定のDescription
                IndivisualSetting = true,             // エンジン個別設定モードに
            };

            if (ind_setting.Options != null)
            {
                ind_setting.OverwriteEngineOptions(indivisualConfig.Options, commonSettings);
            }

            // -- ダイアログの構築

            var dialog = new EngineOptionSettingDialog();

            dialog.SettingControls(0).ViewModel.Setting = commonSettings;
            dialog.SettingControls(0).ViewModel.AddPropertyChangedHandler("ValueChanged", (args) =>
            {
                config.CommonOptions = commonSettings.ToEngineOptions();
                // 値が変わるごとに保存しておく。
            });

            dialog.SettingControls(1).ViewModel.Setting = ind_setting;
            dialog.SettingControls(1).ViewModel.AddPropertyChangedHandler("ValueChanged", (args) =>
            {
                indivisualConfig.Options = ind_setting.ToEngineOptionsForIndivisual();
            });
            dialog.ViewModel.EngineDisplayName = engineDefine.DisplayName;
            dialog.ViewModel.EngineConfigType  = config.EngineConfigType;

            return(dialog);
        }