OSDN Git Service

Viewの下をMainWindowとListWindowに分ける
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / MainForm.cs
index 109ba76..f68310c 100644 (file)
 // See the License for the specific language governing permissions and\r
 // limitations under the License.\r
 \r
-using System;\r
-using System.Collections.Generic;\r
-using System.ComponentModel;\r
-using System.Diagnostics;\r
-using System.Drawing;\r
-using System.Globalization;\r
-using System.IO;\r
-using System.Linq;\r
-using System.Net;\r
-using System.Runtime.InteropServices;\r
-using System.Text;\r
-using System.Text.RegularExpressions;\r
-using System.Threading;\r
-using System.Threading.Tasks;\r
 using System.Windows.Forms;\r
-using KancolleSniffer.Log;\r
-using KancolleSniffer.Model;\r
-using KancolleSniffer.Net;\r
 using KancolleSniffer.Util;\r
-using KancolleSniffer.View;\r
-using Microsoft.CSharp.RuntimeBinder;\r
-using static System.Math;\r
-using Timer = System.Windows.Forms.Timer;\r
 \r
 namespace KancolleSniffer\r
 {\r
     public partial class MainForm : Form\r
     {\r
-        private readonly ConfigDialog _configDialog;\r
-        private readonly ProxyManager _proxyManager;\r
-        private readonly ResizableToolTip _toolTip = new ResizableToolTip();\r
-        private readonly ResizableToolTip _tooltipCopy = new ResizableToolTip {ShowAlways = false, AutomaticDelay = 0};\r
-        private int _currentFleet;\r
-        private bool _combinedFleet;\r
-        private readonly MainShipLabels _mainLabels = new MainShipLabels();\r
-        private readonly MainNDockLabels _ndockLabels = new MainNDockLabels();\r
-        private NumberAndHistory _numberAndHistory;\r
-        private readonly ListForm _listForm;\r
-        private readonly NotificationManager _notificationManager;\r
-        private bool _started;\r
-        private bool _timerEnabled;\r
-        private string _debugLogFile;\r
-        private IEnumerator<string> _playLog;\r
-        private DateTime _prev, _now;\r
-\r
-        private readonly ErrorDialog _errorDialog = new ErrorDialog();\r
-        private readonly ErrorLog _errorLog;\r
-\r
-        public Sniffer Sniffer { get; } = new Sniffer();\r
-        public Config Config { get; } = new Config();\r
-\r
         public MainForm()\r
         {\r
             InitializeComponent();\r
-            HttpProxy.AfterSessionComplete += HttpProxy_AfterSessionComplete;\r
-            Config.Load();\r
-            _configDialog = new ConfigDialog(this);\r
-            _listForm = new ListForm(this);\r
-            _notificationManager = new NotificationManager(Alarm);\r
-            SetupView(_notificationManager);\r
-            _proxyManager = new ProxyManager(this);\r
-            _proxyManager.UpdatePacFile();\r
-            _errorLog = new ErrorLog(Sniffer);\r
-            LoadData();\r
-            Sniffer.RepeatingTimerController = new RepeatingTimerController(this);\r
-        }\r
-\r
-        private void SetupView(NotificationManager manager)\r
-        {\r
-            SetScaleFactorOfDpiScaling();\r
-            SetupFleetClick();\r
-            CreateMainLabels();\r
-            CreateNumberAndHistory(manager);\r
-            labelPresetAkashiTimer.BackColor = CustomColors.ColumnColors.Bright;\r
-            SetupQuestPanel();\r
-            panelRepairList.CreateLabels(panelRepairList_Click);\r
-            PerformZoom();\r
-        }\r
-\r
-        private void SetScaleFactorOfDpiScaling()\r
-        {\r
-            var autoScaleDimensions = new SizeF(6f, 12f); // AutoScaleDimensionの初期値\r
-            Scaler.Factor = new SizeF(CurrentAutoScaleDimensions.Width / autoScaleDimensions.Width,\r
-                CurrentAutoScaleDimensions.Height / autoScaleDimensions.Height);\r
-        }\r
-\r
-        private void SetupQuestPanel()\r
-        {\r
-            int prevHeight = questPanel.Height;\r
-            questPanel.CreateLabels(Config.QuestLines, labelQuest_DoubleClick);\r
-            Height += questPanel.Height - prevHeight;\r
-        }\r
-\r
-        private void CreateMainLabels()\r
-        {\r
-            _mainLabels.CreateAllShipLabels(new MainShipPanels\r
-            {\r
-                PanelShipInfo = panelShipInfo,\r
-                Panel7Ships = panel7Ships,\r
-                PanelCombinedFleet = panelCombinedFleet\r
-            }, ShowShipOnShipList);\r
-            _ndockLabels.Create(panelDock, labelNDock_Click);\r
-        }\r
-\r
-        private void CreateNumberAndHistory(NotificationManager manager)\r
-        {\r
-            _numberAndHistory = new NumberAndHistory(new NumberAndHistoryLabels\r
-            {\r
-                NumOfShips = labelNumOfShips,\r
-                NumOfEquips = labelNumOfEquips,\r
-                NumOfBuckets = labelNumOfBuckets,\r
-                BucketHistory = labelBucketHistory,\r
-                Achievement = labelAchievement,\r
-                FuelHistory = labelFuelHistory,\r
-                BulletHistory = labelBulletHistory,\r
-                SteelHistory = labelSteelHistory,\r
-                BauxiteHistory = labelBouxiteHistory,\r
-                ToolTip = _toolTip\r
-            }, Sniffer, new NotifySubmitter(manager));\r
-        }\r
-\r
-        private class NotifySubmitter : INotifySubmitter\r
-        {\r
-            private readonly NotificationManager _manager;\r
-\r
-            public NotifySubmitter(NotificationManager manager)\r
-            {\r
-                _manager = manager;\r
-            }\r
-\r
-            public void Flash()\r
-            {\r
-                _manager.Flash();\r
-            }\r
-\r
-            public void Enqueue(string key, string subject)\r
-            {\r
-                _manager.Enqueue(key, subject);\r
-            }\r
-        }\r
-\r
-        /// <summary>\r
-        /// パネルのz-orderがくるうのを避ける\r
-        /// https://stackoverflow.com/a/5777090/1429506\r
-        /// </summary>\r
-        private void MainForm_Shown(object sender, EventArgs e)\r
-        {\r
-            // ReSharper disable once NotAccessedVariable\r
-            IntPtr handle;\r
-            foreach (var panel in new[] {panelShipInfo, panel7Ships, panelCombinedFleet})\r
-                // ReSharper disable once RedundantAssignment\r
-                handle = panel.Handle;\r
-        }\r
-\r
-        private readonly FileSystemWatcher _watcher = new FileSystemWatcher\r
-        {\r
-            Path = AppDomain.CurrentDomain.BaseDirectory,\r
-            NotifyFilter = NotifyFilters.LastWrite\r
-        };\r
-\r
-        private readonly Timer _watcherTimer = new Timer {Interval = 1000};\r
-\r
-        private void LoadData()\r
-        {\r
-            var target = "";\r
-            Sniffer.LoadState();\r
-            _watcher.SynchronizingObject = this;\r
-            _watcherTimer.Tick += (sender, ev) =>\r
-            {\r
-                _watcherTimer.Stop();\r
-                switch (target)\r
-                {\r
-                    case "status.xml":\r
-                        Sniffer.LoadState();\r
-                        break;\r
-                    case "TP.csv":\r
-                        Sniffer.AdditionalData.LoadTpSpec();\r
-                        break;\r
-                }\r
-            };\r
-            _watcher.Changed += (sender, ev) =>\r
-            {\r
-                target = ev.Name;\r
-                _watcherTimer.Stop();\r
-                _watcherTimer.Start();\r
-            };\r
-            _watcher.EnableRaisingEvents = true;\r
-        }\r
-\r
-        private class RepeatingTimerController : Sniffer.IRepeatingTimerController\r
-        {\r
-            private readonly NotificationManager _manager;\r
-            private readonly Config _config;\r
-\r
-            public RepeatingTimerController(MainForm main)\r
-            {\r
-                _manager = main._notificationManager;\r
-                _config = main.Config;\r
-            }\r
-\r
-            public void Stop(string key)\r
-            {\r
-                _manager.StopRepeat(key,\r
-                    (key == "入渠終了" || key == "遠征終了") &&\r
-                    (_config.Notifications[key].Flags & NotificationType.Cont) != 0);\r
-            }\r
-\r
-            public void Stop(string key, int fleet) => _manager.StopRepeat(key, fleet);\r
-\r
-            public void Suspend(string exception = null) => _manager.SuspendRepeat(exception);\r
-\r
-            public void Resume() => _manager.ResumeRepeat();\r
-        }\r
-\r
-        private void HttpProxy_AfterSessionComplete(HttpProxy.Session session)\r
-        {\r
-            BeginInvoke(new Action<HttpProxy.Session>(ProcessRequest), session);\r
-        }\r
-\r
-        private void ProcessRequest(HttpProxy.Session session)\r
-        {\r
-            var url = session.Request.PathAndQuery;\r
-            if (!url.Contains("kcsapi/"))\r
-                return;\r
-            var request = session.Request.BodyAsString;\r
-            var response = session.Response.BodyAsString;\r
-            Privacy.Remove(ref url, ref request, ref response);\r
-            if (response == null || !response.StartsWith("svdata="))\r
-            {\r
-                WriteDebugLog(url, request, response);\r
-                return;\r
-            }\r
-            response = UnEscapeString(response.Remove(0, "svdata=".Length));\r
-            WriteDebugLog(url, request, response);\r
-            ProcessRequestMain(url, request, response);\r
-        }\r
-\r
-        private void ProcessRequestMain(string url, string request, string response)\r
-        {\r
-            try\r
-            {\r
-                UpdateInfo(Sniffer.Sniff(url, request, JsonParser.Parse(response)));\r
-                _errorLog.CheckBattleApi(url, request, response);\r
-            }\r
-\r
-            catch (RuntimeBinderException e)\r
-            {\r
-                if (_errorDialog.ShowDialog(this,\r
-                        "艦これに仕様変更があったか、受信内容が壊れています。",\r
-                        _errorLog.GenerateErrorLog(url, request, response, e.ToString())) == DialogResult.Abort)\r
-                    Exit();\r
-            }\r
-            catch (LogIOException e)\r
-            {\r
-                // ReSharper disable once PossibleNullReferenceException\r
-                if (_errorDialog.ShowDialog(this, e.Message, e.InnerException.ToString()) == DialogResult.Abort)\r
-                    Exit();\r
-            }\r
-            catch (BattleResultError)\r
-            {\r
-                if (_errorDialog.ShowDialog(this, "戦闘結果の計算に誤りがあります。",\r
-                        _errorLog.GenerateBattleErrorLog()) == DialogResult.Abort)\r
-                    Exit();\r
-            }\r
-            catch (Exception e)\r
-            {\r
-                if (_errorDialog.ShowDialog(this, "エラーが発生しました。",\r
-                        _errorLog.GenerateErrorLog(url, request, response, e.ToString())) == DialogResult.Abort)\r
-                    Exit();\r
-            }\r
-        }\r
-\r
-        private void Exit()\r
-        {\r
-            _proxyManager.Shutdown();\r
-            Environment.Exit(1);\r
-        }\r
-\r
-        private void WriteDebugLog(string url, string request, string response)\r
-        {\r
-            if (_debugLogFile != null)\r
-            {\r
-                File.AppendAllText(_debugLogFile,\r
-                    $"date: {DateTime.Now:g}\nurl: {url}\nrequest: {request}\nresponse: {response ?? "(null)"}\n");\r
-            }\r
-        }\r
-\r
-        private string UnEscapeString(string s)\r
-        {\r
-            try\r
-            {\r
-                var rx = new Regex(@"\\[uU]([0-9A-Fa-f]{4})");\r
-                return rx.Replace(s,\r
-                    match => ((char)int.Parse(match.Value.Substring(2), NumberStyles.HexNumber)).ToString());\r
-            }\r
-            catch (ArgumentException)\r
-            {\r
-                return s;\r
-            }\r
-        }\r
-\r
-        private void UpdateInfo(Sniffer.Update update)\r
-        {\r
-            if (update == Sniffer.Update.Start)\r
-            {\r
-                labelLogin.Visible = false;\r
-                linkLabelGuide.Visible = false;\r
-                _started = true;\r
-                _notificationManager.StopAllRepeat();\r
-                return;\r
-            }\r
-            if (!_started)\r
-                return;\r
-            if (_now == DateTime.MinValue)\r
-                _now = DateTime.Now;\r
-            if ((update & Sniffer.Update.Item) != 0)\r
-                UpdateItemInfo();\r
-            if ((update & Sniffer.Update.Timer) != 0)\r
-                UpdateTimers();\r
-            if ((update & Sniffer.Update.NDock) != 0)\r
-                UpdateNDocLabels();\r
-            if ((update & Sniffer.Update.Mission) != 0)\r
-                UpdateMissionLabels();\r
-            if ((update & Sniffer.Update.QuestList) != 0)\r
-                UpdateQuestList();\r
-            if ((update & Sniffer.Update.Ship) != 0)\r
-                UpdateShipInfo();\r
-            if ((update & Sniffer.Update.Battle) != 0)\r
-                UpdateBattleInfo();\r
-            if ((update & Sniffer.Update.Cell) != 0)\r
-                UpdateCellInfo();\r
-        }\r
-\r
-        private void MainForm_Load(object sender, EventArgs e)\r
-        {\r
-            SuppressActivate.Start();\r
-            RestoreLocation();\r
-            if (Config.HideOnMinimized && WindowState == FormWindowState.Minimized)\r
-                ShowInTaskbar = false;\r
-            if (Config.ShowHpInPercent)\r
-                _mainLabels.ToggleHpPercent();\r
-            if (Config.ShipList.Visible)\r
-                _listForm.Show();\r
-            ApplyConfig();\r
-            ApplyDebugLogSetting();\r
-            ApplyLogSetting();\r
-            ApplyProxySetting();\r
-            CheckVersionUp((current, latest) =>\r
-            {\r
-                if (latest == current)\r
-                    return;\r
-                linkLabelGuide.Text = $"バージョン{latest}があります。";\r
-                linkLabelGuide.LinkArea = new LinkArea(0, linkLabelGuide.Text.Length);\r
-                linkLabelGuide.Click += (obj, ev) =>\r
-                {\r
-                    Process.Start("https://ja.osdn.net/rel/kancollesniffer/" + latest);\r
-                };\r
-            });\r
-        }\r
-\r
-        public async void CheckVersionUp(Action<string, string> action)\r
-        {\r
-            var current = string.Join(".", Application.ProductVersion.Split('.').Take(2));\r
-            try\r
-            {\r
-                var latest = (await new WebClient().DownloadStringTaskAsync("http://kancollesniffer.osdn.jp/version"))\r
-                    .TrimEnd();\r
-                try\r
-                {\r
-                    action(current, latest);\r
-                }\r
-                catch (InvalidOperationException)\r
-                {\r
-                }\r
-            }\r
-            catch (WebException)\r
-            {\r
-            }\r
-        }\r
-\r
-        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)\r
-        {\r
-            if (!Config.ExitSilently)\r
-            {\r
-                using (var dialog = new ConfirmDialog())\r
-                {\r
-                    if (dialog.ShowDialog(this) != DialogResult.Yes)\r
-                    {\r
-                        e.Cancel = true;\r
-                        return;\r
-                    }\r
-                }\r
-            }\r
-            Config.ShipList.Visible = _listForm.Visible && _listForm.WindowState == FormWindowState.Normal;\r
-            _listForm.Close();\r
-            Sniffer.FlashLog();\r
-            Config.Location = (WindowState == FormWindowState.Normal ? Bounds : RestoreBounds).Location;\r
-            Config.ShowHpInPercent = _mainLabels.ShowHpInPercent;\r
-            Config.Save();\r
-            Sniffer.SaveState();\r
-            _proxyManager.Shutdown();\r
-        }\r
-\r
-        private void MainForm_Resize(object sender, EventArgs e)\r
-        {\r
-            if (_listForm == null) // DPIが100%でないときにInitializeComponentから呼ばれるので\r
-                return;\r
-            SuppressActivate.Start();\r
-            if (WindowState == FormWindowState.Minimized)\r
-            {\r
-                if (Config.HideOnMinimized)\r
-                    ShowInTaskbar = false;\r
-            }\r
-            _listForm.ChangeWindowState(WindowState);\r
-        }\r
-\r
-        public TimeOutChecker SuppressActivate = new TimeOutChecker();\r
-\r
-        private void MainForm_Activated(object sender, EventArgs e)\r
-        {\r
-            if (SuppressActivate.Check())\r
-                return;\r
-            if (NeedRaise)\r
-                RaiseBothWindows();\r
-        }\r
-\r
-        private bool NeedRaise => _listForm.Visible && WindowState != FormWindowState.Minimized;\r
-\r
-        private void RaiseBothWindows()\r
-        {\r
-            _listForm.Owner = null;\r
-            Owner = _listForm;\r
-            BringToFront();\r
-            Owner = null;\r
-        }\r
-\r
-        public class TimeOutChecker\r
-        {\r
-            private DateTime _lastCheck;\r
-            private readonly TimeSpan _timeout = TimeSpan.FromMilliseconds(500);\r
-\r
-            public void Start()\r
-            {\r
-                _lastCheck = DateTime.Now;\r
-            }\r
-\r
-            public bool Check()\r
-            {\r
-                var now = DateTime.Now;\r
-                var last = _lastCheck;\r
-                _lastCheck = now;\r
-                return now - last < _timeout;\r
-            }\r
-        }\r
-\r
-        private void notifyIconMain_MouseDoubleClick(object sender, MouseEventArgs e)\r
-        {\r
-            NotifyIconOpenToolStripMenuItem_Click(sender, e);\r
-        }\r
-\r
-        private void NotifyIconOpenToolStripMenuItem_Click(object sender, EventArgs e)\r
-        {\r
-            ShowInTaskbar = true;\r
-            WindowState = FormWindowState.Normal;\r
-            TopMost = _listForm.TopMost = Config.TopMost; // 最前面に表示されなくなることがあるのを回避する\r
-        }\r
-\r
-        private void ExitToolStripMenuItem_Click(object sender, EventArgs e)\r
-        {\r
-            Close();\r
-        }\r
-\r
-        private void ConfigToolStripMenuItem_Click(object sender, EventArgs e)\r
-        {\r
-            if (_configDialog.ShowDialog(this) == DialogResult.OK)\r
-            {\r
-                Config.Save();\r
-                ApplyConfig();\r
-                StopRepeatingTimer(_configDialog.RepeatSettingsChanged);\r
-            }\r
-        }\r
-\r
-        private void StopRepeatingTimer(IEnumerable<string> names)\r
-        {\r
-            foreach (var name in names)\r
-                _notificationManager.StopRepeat(name);\r
-        }\r
-\r
-        private void PerformZoom()\r
-        {\r
-            if (Config.Zoom == 100)\r
-            {\r
-                ShipLabel.Name.BaseFont = Font;\r
-                ShipLabel.Name.LatinFont = LatinFont();\r
-                return;\r
-            }\r
-            var prev = CurrentAutoScaleDimensions;\r
-            foreach (var control in new Control[]\r
-            {\r
-                this, _listForm, labelLogin, linkLabelGuide,\r
-                _configDialog, _configDialog.NotificationConfigDialog,\r
-                contextMenuStripMain, _errorDialog\r
-            })\r
-            {\r
-                control.Font = ZoomFont(control.Font);\r
-            }\r
-            foreach (var toolTip in new[] {_toolTip, _tooltipCopy})\r
-            {\r
-                toolTip.Font = ZoomFont(toolTip.Font);\r
-            }\r
-            ShipLabel.Name.BaseFont = Font;\r
-            ShipLabel.Name.LatinFont = LatinFont();\r
-            var cur = CurrentAutoScaleDimensions;\r
-            Scaler.Factor = Scaler.Scale(cur.Width / prev.Width, cur.Height / prev.Height);\r
-        }\r
-\r
-        private Font ZoomFont(Font font)\r
-        {\r
-            return new Font(font.FontFamily, font.Size * Config.Zoom / 100);\r
-        }\r
-\r
-        private Font LatinFont()\r
-        {\r
-            return new Font("Tahoma", 8f * Config.Zoom / 100);\r
-        }\r
-\r
-        private void RestoreLocation()\r
-        {\r
-            if (Config.Location.X == int.MinValue)\r
-                return;\r
-            if (IsTitleBarOnAnyScreen(Config.Location))\r
-                Location = Config.Location;\r
-        }\r
-\r
-        private void ApplyConfig()\r
-        {\r
-            if (TopMost != Config.TopMost)\r
-                TopMost = _listForm.TopMost = Config.TopMost;\r
-            Sniffer.ShipCounter.Margin = Config.MarginShips;\r
-            _numberAndHistory.UpdateNumOfShips();\r
-            Sniffer.ItemCounter.Margin = Config.MarginEquips;\r
-            _numberAndHistory.UpdateNumOfEquips();\r
-            Sniffer.Achievement.ResetHours = Config.ResetHours;\r
-            labelAkashiRepair.Visible = labelAkashiRepairTimer.Visible =\r
-                labelPresetAkashiTimer.Visible = Config.UsePresetAkashi;\r
-            Sniffer.WarnBadDamageWithDameCon = Config.WarnBadDamageWithDameCon;\r
-        }\r
-\r
-        public void ApplyDebugLogSetting()\r
-        {\r
-            _debugLogFile = Config.DebugLogging ? Config.DebugLogFile : null;\r
-        }\r
-\r
-        public bool ApplyProxySetting()\r
-        {\r
-            return _proxyManager.ApplyConfig();\r
-        }\r
-\r
-        public void ApplyLogSetting()\r
-        {\r
-            LogServer.OutputDir = Config.Log.OutputDir;\r
-            LogServer.LogProcessor = new LogProcessor(Sniffer.Material.MaterialHistory, Sniffer.MapDictionary);\r
-            Sniffer.EnableLog(Config.Log.On ? LogType.All : LogType.None);\r
-            Sniffer.MaterialLogInterval = Config.Log.MaterialLogInterval;\r
-            Sniffer.LogOutputDir = Config.Log.OutputDir;\r
-        }\r
-\r
-        public static bool IsTitleBarOnAnyScreen(Point location)\r
-        {\r
-            var rect = new Rectangle(\r
-                new Point(location.X + SystemInformation.IconSize.Width + SystemInformation.HorizontalFocusThickness,\r
-                    location.Y + SystemInformation.CaptionHeight), new Size(60, 1));\r
-            return Screen.AllScreens.Any(screen => screen.WorkingArea.Contains(rect));\r
-        }\r
-\r
-        private void timerMain_Tick(object sender, EventArgs e)\r
-        {\r
-            if (_timerEnabled)\r
-            {\r
-                try\r
-                {\r
-                    _now = DateTime.Now;\r
-                    UpdateTimers();\r
-                    NotifyTimers();\r
-                    _prev = _now;\r
-                }\r
-                catch (Exception ex)\r
-                {\r
-                    if (_errorDialog.ShowDialog(this, "エラーが発生しました。", ex.ToString()) == DialogResult.Abort)\r
-                        Exit();\r
-                }\r
-            }\r
-            if (_playLog == null || _configDialog.Visible)\r
-            {\r
-                labelPlayLog.Visible = false;\r
-                return;\r
-            }\r
-            PlayLog();\r
-        }\r
-\r
-        public void SetPlayLog(string file)\r
-        {\r
-            _playLog = File.ReadLines(file).GetEnumerator();\r
-        }\r
-\r
-        private void PlayLog()\r
-        {\r
-            var lines = new List<string>();\r
-            foreach (var s in new[] {"url: ", "request: ", "response: "})\r
-            {\r
-                do\r
-                {\r
-                    if (!_playLog.MoveNext() || _playLog.Current == null)\r
-                    {\r
-                        labelPlayLog.Visible = false;\r
-                        return;\r
-                    }\r
-                } while (!_playLog.Current.StartsWith(s));\r
-                lines.Add(_playLog.Current.Substring(s.Length));\r
-            }\r
-            labelPlayLog.Visible = !labelPlayLog.Visible;\r
-            ProcessRequestMain(lines[0], lines[1], lines[2]);\r
-        }\r
-\r
-        private void ShowShipOnShipList(object sender, EventArgs ev)\r
-        {\r
-            if (!_listForm.Visible)\r
-                return;\r
-            var idx = (int)((Control)sender).Tag;\r
-            var ship = (_combinedFleet\r
-                ? Sniffer.Fleets[0].Ships.Concat(Sniffer.Fleets[1].Ships).ToArray()\r
-                : Sniffer.Fleets[_currentFleet].Ships)[idx];\r
-            if (!ship.Empty)\r
-                _listForm.ShowShip(ship.Id);\r
-        }\r
-\r
-\r
-        private void UpdateItemInfo()\r
-        {\r
-            _numberAndHistory.Update();\r
-            if (_listForm.Visible)\r
-                _listForm.UpdateList();\r
-        }\r
-\r
-        private void UpdateShipInfo()\r
-        {\r
-            SetCurrentFleet();\r
-            SetCombined();\r
-            UpdatePanelShipInfo();\r
-            NotifyDamagedShip();\r
-            UpdateChargeInfo();\r
-            UpdateRepairList();\r
-            UpdateMissionLabels();\r
-            if (_listForm.Visible)\r
-                _listForm.UpdateList();\r
-        }\r
-\r
-        private bool _inSortie;\r
-\r
-        private void SetCurrentFleet()\r
-        {\r
-            var inSortie = Sniffer.InSortie;\r
-            if (_inSortie || inSortie == -1)\r
-            {\r
-                _inSortie = inSortie != -1;\r
-                return;\r
-            }\r
-            _inSortie = true;\r
-            if (inSortie == 10)\r
-            {\r
-                _combinedFleet = true;\r
-                _currentFleet = 0;\r
-            }\r
-            else\r
-            {\r
-                _combinedFleet = false;\r
-                _currentFleet = inSortie;\r
-            }\r
-        }\r
-\r
-        private bool _prevCombined;\r
-\r
-        private void SetCombined()\r
-        {\r
-            if (Sniffer.IsCombinedFleet && !_prevCombined)\r
-            {\r
-                _combinedFleet = true;\r
-                _currentFleet = 0;\r
-            }\r
-            _prevCombined = Sniffer.IsCombinedFleet;\r
-        }\r
-\r
-        private void UpdatePanelShipInfo()\r
-        {\r
-            var ships = Sniffer.Fleets[_currentFleet].ActualShips;\r
-            panel7Ships.Visible = ships.Count == 7;\r
-            _mainLabels.SetShipLabels(ships);\r
-            ShowCombinedFleet();\r
-            ShowCurrentFleetNumber();\r
-            UpdateAkashiTimer();\r
-            UpdateFighterPower(IsCombinedFighterPower);\r
-            UpdateLoS();\r
-            UpdateCondTimers();\r
-        }\r
-\r
-        private void ShowCombinedFleet()\r
-        {\r
-            if (!Sniffer.IsCombinedFleet)\r
-                _combinedFleet = false;\r
-            labelFleet1.Text = _combinedFleet ? CombinedName : "第一";\r
-            panelCombinedFleet.Visible = _combinedFleet;\r
-            if (_combinedFleet)\r
-                _mainLabels.SetCombinedShipLabels(Sniffer.Fleets[0].ActualShips, Sniffer.Fleets[1].ActualShips);\r
-        }\r
-\r
-        private void ShowCurrentFleetNumber()\r
-        {\r
-            var labels = new[] {labelCheckFleet1, labelCheckFleet2, labelCheckFleet3, labelCheckFleet4};\r
-            for (var i = 0; i < labels.Length; i++)\r
-                labels[i].Visible = _currentFleet == i;\r
-        }\r
-\r
-        private bool IsCombinedFighterPower => _combinedFleet &&\r
-                                               (Sniffer.Battle.BattleState == BattleState.None ||\r
-                                                Sniffer.Battle.EnemyIsCombined);\r
-\r
-        private string CombinedName\r
-        {\r
-            get\r
-            {\r
-                switch (Sniffer.Fleets[0].CombinedType)\r
-                {\r
-                    case CombinedType.Carrier:\r
-                        return "機動";\r
-                    case CombinedType.Surface:\r
-                        return "水上";\r
-                    case CombinedType.Transport:\r
-                        return "輸送";\r
-                    default:\r
-                        return "連合";\r
-                }\r
-            }\r
-        }\r
-\r
-        private void NotifyDamagedShip()\r
-        {\r
-            _notificationManager.StopRepeat("大破警告");\r
-            if (!Sniffer.BadlyDamagedShips.Any())\r
-                return;\r
-            SetNotification("大破警告", string.Join(" ", Sniffer.BadlyDamagedShips));\r
-            _notificationManager.Flash();\r
-        }\r
-\r
-        private void UpdateFighterPower(bool combined)\r
-        {\r
-            var fleets = Sniffer.Fleets;\r
-            var fp = combined\r
-                ? fleets[0].FighterPower + fleets[1].FighterPower\r
-                : fleets[_currentFleet].FighterPower;\r
-            labelFighterPower.Text = fp.Min.ToString("D");\r
-            var cr = combined\r
-                ? fleets[0].ContactTriggerRate + fleets[1].ContactTriggerRate\r
-                : fleets[_currentFleet].ContactTriggerRate;\r
-            var text = "制空: " + (fp.Diff ? fp.RangeString : fp.Min.ToString()) +\r
-                       $" 触接: {cr * 100:f1}";\r
-            _toolTip.SetToolTip(labelFighterPower, text);\r
-            _toolTip.SetToolTip(labelFighterPowerCaption, text);\r
-        }\r
-\r
-        private void UpdateLoS()\r
-        {\r
-            var fleet = Sniffer.Fleets[_currentFleet];\r
-            labelLoS.Text = RoundDown(fleet.GetLineOfSights(1)).ToString("F1");\r
-            var text = $"係数2: {RoundDown(fleet.GetLineOfSights(2)):F1}\r\n" +\r
-                       $"係数3: {RoundDown(fleet.GetLineOfSights(3)):F1}\r\n" +\r
-                       $"係数4: {RoundDown(fleet.GetLineOfSights(4)):F1}";\r
-            _toolTip.SetToolTip(labelLoS, text);\r
-            _toolTip.SetToolTip(labelLoSCaption, text);\r
-        }\r
-\r
-        private double RoundDown(double number)\r
-        {\r
-            return Floor(number * 10) / 10.0;\r
-        }\r
-\r
-        private void UpdateBattleInfo()\r
-        {\r
-            ResetBattleInfo();\r
-            _listForm.UpdateBattleResult();\r
-            _listForm.UpdateAirBattleResult();\r
-            if (Sniffer.Battle.BattleState == BattleState.None)\r
-                return;\r
-            panelBattleInfo.BringToFront();\r
-            var battle = Sniffer.Battle;\r
-            labelFormation.Text = new[] {"同航戦", "反航戦", "T字有利", "T字不利"}[battle.Formation[2] - 1];\r
-            UpdateBattleFighterPower();\r
-            if ((Config.Spoilers & Spoiler.ResultRank) != 0)\r
-                ShowResultRank();\r
-        }\r
-\r
-        private void UpdateCellInfo()\r
-        {\r
-            _listForm.UpdateCellInfo();\r
-        }\r
-\r
-        private void ResetBattleInfo()\r
-        {\r
-            labelFormation.Text = "";\r
-            labelEnemyFighterPower.Text = "";\r
-            labelFighterPower.ForeColor = DefaultForeColor;\r
-            labelFighterPowerCaption.Text = "制空";\r
-            labelResultRank.Text = "判定";\r
-            panelBattleInfo.Visible = Sniffer.Battle.BattleState != BattleState.None;\r
-        }\r
-\r
-        private void UpdateBattleFighterPower()\r
-        {\r
-            UpdateEnemyFighterPower();\r
-            var battle = Sniffer.Battle;\r
-            labelFighterPower.ForeColor = AirControlLevelColor(battle);\r
-            labelFighterPowerCaption.Text = AirControlLevelString(battle);\r
-            if (battle.BattleState == BattleState.AirRaid)\r
-            {\r
-                UpdateAirRaidFighterPower();\r
-            }\r
-            else\r
-            {\r
-                UpdateFighterPower(Sniffer.IsCombinedFleet && battle.EnemyIsCombined);\r
-            }\r
-        }\r
-\r
-        private void UpdateEnemyFighterPower()\r
-        {\r
-            var fp = Sniffer.Battle.EnemyFighterPower;\r
-            labelEnemyFighterPower.Text = fp.AirCombat + fp.UnknownMark;\r
-            var toolTip = fp.AirCombat == fp.Interception ? "" : "防空: " + fp.Interception + fp.UnknownMark;\r
-            _toolTip.SetToolTip(labelEnemyFighterPower, toolTip);\r
-            _toolTip.SetToolTip(labelEnemyFighterPowerCaption, toolTip);\r
-        }\r
-\r
-        private void UpdateAirRaidFighterPower()\r
-        {\r
-            var fp = Sniffer.Battle.FighterPower;\r
-            labelFighterPower.Text = fp.Min.ToString();\r
-            var toolTop = fp.Diff ? fp.RangeString : "";\r
-            _toolTip.SetToolTip(labelFighterPower, toolTop);\r
-            _toolTip.SetToolTip(labelFighterPowerCaption, toolTop);\r
-        }\r
-\r
-        private static Color AirControlLevelColor(BattleInfo battle)\r
-        {\r
-            return new[]\r
-                {DefaultForeColor, DefaultForeColor, CUDColors.Blue, CUDColors.Green, CUDColors.Orange, CUDColors.Red}[\r
-                battle.BattleState == BattleState.Night ? 0 : battle.AirControlLevel + 1];\r
-        }\r
-\r
-        private static string AirControlLevelString(BattleInfo battle)\r
-        {\r
-            return new[] {"制空", "拮抗", "確保", "優勢", "劣勢", "喪失"}[\r
-                battle.BattleState == BattleState.Night ? 0 : battle.AirControlLevel + 1];\r
-        }\r
-\r
-        private void ShowResultRank()\r
-        {\r
-            var result = new[] {"完全S", "勝利S", "勝利A", "勝利B", "敗北C", "敗北D", "敗北E"};\r
-            labelResultRank.Text = result[(int)Sniffer.Battle.ResultRank];\r
-        }\r
-\r
-        private void labelResultRank_Click(object sender, EventArgs e)\r
-        {\r
-            ShowResultRank();\r
-        }\r
-\r
-        private void UpdateChargeInfo()\r
-        {\r
-            var fuelSq = new[] {labelFuelSq1, labelFuelSq2, labelFuelSq3, labelFuelSq4};\r
-            var bullSq = new[] {labelBullSq1, labelBullSq2, labelBullSq3, labelBullSq4};\r
-\r
-            for (var i = 0; i < fuelSq.Length; i++)\r
-            {\r
-                var stat = Sniffer.Fleets[i].ChargeStatus;\r
-                fuelSq[i].ImageIndex = stat.Fuel;\r
-                bullSq[i].ImageIndex = stat.Bull;\r
-                var text = stat.Empty ? "" : $"燃{stat.FuelRatio * 100:f1}% 弾{stat.BullRatio * 100:f1}%";\r
-                _toolTip.SetToolTip(fuelSq[i], text);\r
-                _toolTip.SetToolTip(bullSq[i], text);\r
-            }\r
-        }\r
-\r
-        private void UpdateNDocLabels()\r
-        {\r
-            _ndockLabels.SetName(Sniffer.NDock);\r
-            SetNDockLabel();\r
-        }\r
-\r
-        private void SetNDockLabel()\r
-        {\r
-            labelNDock.Text = (Config.ShowEndTime & TimerKind.NDock) != 0 ? "入渠終了" : "入渠";\r
-        }\r
-\r
-        private void labelNDock_Click(object sender, EventArgs e)\r
-        {\r
-            Config.ShowEndTime ^= TimerKind.NDock;\r
-            SetNDockLabel();\r
-            UpdateTimers();\r
-        }\r
-\r
-        private void UpdateMissionLabels()\r
-        {\r
-            var nameLabels = new[] {labelMissionName1, labelMissionName2, labelMissionName3};\r
-            var paramsLabels = new[] {labelMissionParams1, labelMissionParams2, labelMissionParams3};\r
-            var names = Sniffer.Missions.Select(mission => mission.Name).ToArray();\r
-            for (var i = 0; i < ShipInfo.FleetCount - 1; i++)\r
-            {\r
-                var fleetParams = Sniffer.Fleets[i + 1].MissionParameter;\r
-                var inPort = string.IsNullOrEmpty(names[i]);\r
-                paramsLabels[i].Visible = inPort;\r
-                paramsLabels[i].Text = fleetParams;\r
-                nameLabels[i].Text = names[i];\r
-                _toolTip.SetToolTip(nameLabels[i], inPort ? "" : fleetParams);\r
-            }\r
-            SetMissionLabel();\r
-        }\r
-\r
-        private void SetMissionLabel()\r
-        {\r
-            labelMission.Text = (Config.ShowEndTime & TimerKind.Mission) != 0 ? "遠征終了" : "遠征";\r
-        }\r
-\r
-        private void labelMission_Click(object sender, EventArgs e)\r
-        {\r
-            Config.ShowEndTime ^= TimerKind.Mission;\r
-            SetMissionLabel();\r
-            UpdateTimers();\r
-        }\r
-\r
-        private void UpdateTimers()\r
-        {\r
-            var mission = new[] {labelMission1, labelMission2, labelMission3};\r
-            for (var i = 0; i < mission.Length; i++)\r
-            {\r
-                var entry = Sniffer.Missions[i];\r
-                SetTimerColor(mission[i], entry.Timer, _now);\r
-                mission[i].Text = entry.Timer.ToString(_now, (Config.ShowEndTime & TimerKind.Mission) != 0);\r
-            }\r
-            for (var i = 0; i < Sniffer.NDock.Length; i++)\r
-            {\r
-                var entry = Sniffer.NDock[i];\r
-                _ndockLabels.SetTimer(i, entry.Timer, _now, (Config.ShowEndTime & TimerKind.NDock) != 0);\r
-            }\r
-            var kdock = new[] {labelConstruct1, labelConstruct2, labelConstruct3, labelConstruct4};\r
-            for (var i = 0; i < kdock.Length; i++)\r
-            {\r
-                var timer = Sniffer.KDock[i];\r
-                SetTimerColor(kdock[i], timer, _now);\r
-                kdock[i].Text = timer.ToString(_now);\r
-            }\r
-            UpdateCondTimers();\r
-            UpdateAkashiTimer();\r
-            _timerEnabled = true;\r
-        }\r
-\r
-        private void NotifyTimers()\r
-        {\r
-            for (var i = 0; i < Sniffer.Missions.Length; i++)\r
-            {\r
-                var entry = Sniffer.Missions[i];\r
-                if (entry.Name == "前衛支援任務" || entry.Name == "艦隊決戦支援任務")\r
-                    continue;\r
-                CheckAlarm("遠征終了", entry.Timer, i + 1, entry.Name);\r
-            }\r
-            for (var i = 0; i < Sniffer.NDock.Length; i++)\r
-            {\r
-                var entry = Sniffer.NDock[i];\r
-                CheckAlarm("入渠終了", entry.Timer, i, entry.Name);\r
-            }\r
-            for (var i = 0; i < Sniffer.KDock.Length; i++)\r
-            {\r
-                var timer = Sniffer.KDock[i];\r
-                CheckAlarm("建造完了", timer, i, "");\r
-            }\r
-            NotifyCondTimers();\r
-            NotifyAkashiTimer();\r
-            _notificationManager.Flash();\r
-        }\r
-\r
-        private void CheckAlarm(string key, AlarmTimer timer, int fleet, string subject)\r
-        {\r
-            if (timer.CheckAlarm(_prev, _now))\r
-            {\r
-                SetNotification(key, fleet, subject);\r
-                return;\r
-            }\r
-            var pre = TimeSpan.FromSeconds(Config.Notifications[key].PreliminaryPeriod);\r
-            if (pre == TimeSpan.Zero)\r
-                return;\r
-            if (timer.CheckAlarm(_prev + pre, _now + pre))\r
-                SetPreNotification(key, fleet, subject);\r
-        }\r
-\r
-        private void SetTimerColor(Label label, AlarmTimer timer, DateTime now)\r
-        {\r
-            label.ForeColor = timer.IsFinished(now) ? CUDColors.Red : Color.Black;\r
-        }\r
-\r
-        private void UpdateCondTimers()\r
-        {\r
-            DateTime timer;\r
-            if (_combinedFleet)\r
-            {\r
-                var timer1 = Sniffer.GetConditionTimer(0);\r
-                var timer2 = Sniffer.GetConditionTimer(1);\r
-                timer = timer2 > timer1 ? timer2 : timer1;\r
-            }\r
-            else\r
-            {\r
-                timer = Sniffer.GetConditionTimer(_currentFleet);\r
-            }\r
-            if (timer == DateTime.MinValue)\r
-            {\r
-                labelCondTimerTitle.Text = "";\r
-                labelCondTimer.Text = "";\r
-                return;\r
-            }\r
-            var span = TimeSpan.FromSeconds(Ceiling((timer - _now).TotalSeconds));\r
-            if (span >= TimeSpan.FromMinutes(9) && Config.NotifyConditions.Contains(40))\r
-            {\r
-                labelCondTimerTitle.Text = "cond40まで";\r
-                labelCondTimer.Text = (span - TimeSpan.FromMinutes(9)).ToString(@"mm\:ss");\r
-                labelCondTimer.ForeColor = DefaultForeColor;\r
-            }\r
-            else\r
-            {\r
-                labelCondTimerTitle.Text = "cond49まで";\r
-                labelCondTimer.Text = (span >= TimeSpan.Zero ? span : TimeSpan.Zero).ToString(@"mm\:ss");\r
-                labelCondTimer.ForeColor = span <= TimeSpan.Zero ? CUDColors.Red : DefaultForeColor;\r
-            }\r
-        }\r
-\r
-        private void NotifyCondTimers()\r
-        {\r
-            var notice = Sniffer.GetConditionNotice(_prev, _now);\r
-            var pre = TimeSpan.FromSeconds(Config.Notifications["疲労回復"].PreliminaryPeriod);\r
-            var preNotice = pre == TimeSpan.Zero\r
-                ? new int[ShipInfo.FleetCount]\r
-                : Sniffer.GetConditionNotice(_prev + pre, _now + pre);\r
-            for (var i = 0; i < ShipInfo.FleetCount; i++)\r
-            {\r
-                if (Config.NotifyConditions.Contains(notice[i]))\r
-                {\r
-                    SetNotification("疲労回復" + notice[i], i, "cond" + notice[i]);\r
-                }\r
-                else if (Config.NotifyConditions.Contains(preNotice[i]))\r
-                {\r
-                    SetPreNotification("疲労回復" + preNotice[i], i, "cond" + notice[i]);\r
-                }\r
-            }\r
-        }\r
-\r
-        private void UpdateAkashiTimer()\r
-        {\r
-            if (Config.UsePresetAkashi)\r
-                UpdatePresetAkashiTimer();\r
-            _mainLabels.SetAkashiTimer(Sniffer.Fleets[_currentFleet].ActualShips,\r
-                Sniffer.AkashiTimer.GetTimers(_currentFleet, _now));\r
-        }\r
-\r
-        private void UpdatePresetAkashiTimer()\r
-        {\r
-            var akashi = Sniffer.AkashiTimer;\r
-            var span = akashi.GetPresetDeckTimer(_now);\r
-            var color = span == TimeSpan.Zero && akashi.CheckPresetRepairing() ? CUDColors.Red : DefaultForeColor;\r
-            var text = span == TimeSpan.MinValue ? "" : span.ToString(@"mm\:ss");\r
-            labelAkashiRepairTimer.ForeColor = color;\r
-            labelAkashiRepairTimer.Text = text;\r
-            if (akashi.CheckPresetRepairing() && !akashi.CheckRepairing(_currentFleet, _now))\r
-            {\r
-                labelPresetAkashiTimer.ForeColor = color;\r
-                labelPresetAkashiTimer.Text = text;\r
-            }\r
-            else\r
-            {\r
-                labelPresetAkashiTimer.ForeColor = DefaultForeColor;\r
-                labelPresetAkashiTimer.Text = "";\r
-            }\r
-        }\r
-\r
-        private void NotifyAkashiTimer()\r
-        {\r
-            var akashi = Sniffer.AkashiTimer;\r
-            var msgs = akashi.GetNotice(_prev, _now);\r
-            if (msgs.Length == 0)\r
-            {\r
-                _notificationManager.StopRepeat("泊地修理");\r
-                return;\r
-            }\r
-            if (!akashi.CheckRepairing(_now) && !(akashi.CheckPresetRepairing() && Config.UsePresetAkashi))\r
-            {\r
-                _notificationManager.StopRepeat("泊地修理");\r
-                return;\r
-            }\r
-            var skipPreliminary = false;\r
-            if (msgs[0].Proceeded == "20分経過しました。")\r
-            {\r
-                SetNotification("泊地修理20分経過", msgs[0].Proceeded);\r
-                msgs[0].Proceeded = "";\r
-                skipPreliminary = true;\r
-                // 修理完了がいるかもしれないので続ける\r
-            }\r
-            for (var i = 0; i < ShipInfo.FleetCount; i++)\r
-            {\r
-                if (msgs[i].Proceeded != "")\r
-                    SetNotification("泊地修理進行", i, msgs[i].Proceeded);\r
-                if (msgs[i].Completed != "")\r
-                    SetNotification("泊地修理完了", i, msgs[i].Completed);\r
-            }\r
-            var pre = TimeSpan.FromSeconds(Config.Notifications["泊地修理20分経過"].PreliminaryPeriod);\r
-            if (skipPreliminary || pre == TimeSpan.Zero)\r
-                return;\r
-            if ((msgs = akashi.GetNotice(_prev + pre, _now + pre))[0].Proceeded == "20分経過しました。")\r
-                SetPreNotification("泊地修理20分経過", 0, msgs[0].Proceeded);\r
-        }\r
-\r
-        private void SetNotification(string key, string subject)\r
-        {\r
-            SetNotification(key, 0, subject);\r
-        }\r
-\r
-        private void SetNotification(string key, int fleet, string subject)\r
-        {\r
-            var spec = Config.Notifications[_notificationManager.KeyToName(key)];\r
-            _notificationManager.Enqueue(key, fleet, subject,\r
-                (spec.Flags & Config.NotificationFlags & NotificationType.Repeat) == 0 ? 0 : spec.RepeatInterval);\r
-        }\r
-\r
-        private void SetPreNotification(string key, int fleet, string subject)\r
-        {\r
-            var spec = Config.Notifications[_notificationManager.KeyToName(key)];\r
-            if ((spec.Flags & NotificationType.Preliminary) != 0)\r
-                _notificationManager.Enqueue(key, fleet, subject, 0, true);\r
-        }\r
-\r
-        private void UpdateRepairList()\r
-        {\r
-            panelRepairList.SetRepairList(Sniffer.RepairList);\r
-            _toolTip.SetToolTip(label31, new RepairShipCount(Sniffer.RepairList).ToString());\r
-        }\r
-\r
-        private void UpdateQuestList()\r
-        {\r
-            questPanel.Update(Sniffer.Quests);\r
-            labelQuestCount.Text = Sniffer.Quests.Length.ToString();\r
-            SetQuestNotification();\r
-        }\r
-\r
-        private void SetQuestNotification()\r
-        {\r
-            Sniffer.GetQuestNotifications(out var notify, out var stop);\r
-            foreach (var questName in notify)\r
-                SetNotification("任務達成", 0, questName);\r
-            foreach (var questName in stop)\r
-                _notificationManager.StopRepeat("任務達成", questName);\r
-            _notificationManager.Flash();\r
-        }\r
-\r
-        private void Alarm(string balloonTitle, string balloonMessage, string name)\r
-        {\r
-            var flags = Config.Notifications[name].Flags;\r
-            var effective = Config.NotificationFlags & Config.Notifications[name].Flags;\r
-            if ((effective & NotificationType.FlashWindow) != 0)\r
-                Win32API.FlashWindow(Handle);\r
-            if ((effective & NotificationType.ShowBaloonTip) != 0)\r
-                notifyIconMain.ShowBalloonTip(20000, balloonTitle, balloonMessage, ToolTipIcon.Info);\r
-            if ((effective & NotificationType.PlaySound) != 0)\r
-                PlaySound(Config.Sounds[name], Config.Sounds.Volume);\r
-            if (Config.Pushbullet.On && (flags & NotificationType.Push) != 0)\r
-            {\r
-                Task.Run(() =>\r
-                {\r
-                    PushNotification.PushToPushbullet(Config.Pushbullet.Token, balloonTitle, balloonMessage);\r
-                });\r
-            }\r
-            if (Config.Pushover.On && (flags & NotificationType.Push) != 0)\r
-            {\r
-                Task.Run(() =>\r
-                {\r
-                    PushNotification.PushToPushover(Config.Pushover.ApiKey, Config.Pushover.UserKey,\r
-                        balloonTitle, balloonMessage);\r
-                });\r
-            }\r
-        }\r
-\r
-        [DllImport("winmm.dll")]\r
-        private static extern int mciSendString(String command,\r
-            StringBuilder buffer, int bufferSize, IntPtr hWndCallback);\r
-\r
-// ReSharper disable InconsistentNaming\r
-        // ReSharper disable once IdentifierTypo\r
-        private const int MM_MCINOTIFY = 0x3B9;\r
-\r
-        private const int MCI_NOTIFY_SUCCESSFUL = 1;\r
-// ReSharper restore InconsistentNaming\r
-\r
-        public void PlaySound(string file, int volume)\r
-        {\r
-            if (!File.Exists(file))\r
-                return;\r
-            mciSendString("close sound", null, 0, IntPtr.Zero);\r
-            if (mciSendString("open \"" + file + "\" type mpegvideo alias sound", null, 0, IntPtr.Zero) != 0)\r
-                return;\r
-            mciSendString("setaudio sound volume to " + volume * 10, null, 0, IntPtr.Zero);\r
-            mciSendString("play sound notify", null, 0, Handle);\r
         }\r
 \r
         protected override void WndProc(ref Message m)\r
         {\r
-            if (m.Msg == MM_MCINOTIFY && (int)m.WParam == MCI_NOTIFY_SUCCESSFUL)\r
-                mciSendString("close sound", null, 0, IntPtr.Zero);\r
+            SoundPlayer.CloseSound(m);\r
             base.WndProc(ref m);\r
         }\r
-\r
-        private void SetupFleetClick()\r
-        {\r
-            var labels = new[]\r
-            {\r
-                new[] {labelFleet1, labelFleet2, labelFleet3, labelFleet4},\r
-                new[] {labelFuelSq1, labelFuelSq2, labelFuelSq3, labelFuelSq4},\r
-                new[] {labelBullSq1, labelBullSq2, labelBullSq3, labelBullSq4}\r
-            };\r
-            foreach (var a in labels)\r
-            {\r
-                a[0].Tag = 0;\r
-                a[0].Click += labelFleet1_Click;\r
-                a[0].DoubleClick += labelFleet1_DoubleClick;\r
-                for (var fleet = 1; fleet < labels[0].Length; fleet++)\r
-                {\r
-                    a[fleet].Tag = fleet;\r
-                    a[fleet].Click += labelFleet_Click;\r
-                    a[fleet].DoubleClick += labelFleet_DoubleClick;\r
-                }\r
-            }\r
-        }\r
-\r
-        private void labelFleet_Click(object sender, EventArgs e)\r
-        {\r
-            if (!_started)\r
-                return;\r
-            var fleet = (int)((Label)sender).Tag;\r
-            if (_currentFleet == fleet)\r
-                return;\r
-            _combinedFleet = false;\r
-            _currentFleet = fleet;\r
-            UpdatePanelShipInfo();\r
-        }\r
-\r
-        private readonly SemaphoreSlim _clickSemaphore = new SemaphoreSlim(1);\r
-        private readonly SemaphoreSlim _doubleClickSemaphore = new SemaphoreSlim(0);\r
-\r
-        private async void labelFleet1_Click(object sender, EventArgs e)\r
-        {\r
-            if (!_started)\r
-                return;\r
-            if (_currentFleet != 0)\r
-            {\r
-                labelFleet_Click(sender, e);\r
-                return;\r
-            }\r
-            if (!_clickSemaphore.Wait(0))\r
-                return;\r
-            try\r
-            {\r
-                if (await _doubleClickSemaphore.WaitAsync(SystemInformation.DoubleClickTime))\r
-                    return;\r
-            }\r
-            finally\r
-            {\r
-                _clickSemaphore.Release();\r
-            }\r
-            _combinedFleet = Sniffer.IsCombinedFleet && !_combinedFleet;\r
-            UpdatePanelShipInfo();\r
-        }\r
-\r
-        private void labelFleet1_MouseHover(object sender, EventArgs e)\r
-        {\r
-            labelFleet1.Text = _currentFleet == 0 && Sniffer.IsCombinedFleet && !_combinedFleet ? "連合" : "第一";\r
-        }\r
-\r
-        private void labelFleet1_MouseLeave(object sender, EventArgs e)\r
-        {\r
-            labelFleet1.Text = _combinedFleet ? CombinedName : "第一";\r
-        }\r
-\r
-        private void labelFleet_DoubleClick(object sender, EventArgs e)\r
-        {\r
-            if (!_started)\r
-                return;\r
-            var fleet = (int)((Label)sender).Tag;\r
-            var text = TextGenerator.GenerateFleetData(Sniffer, fleet);\r
-            CopyFleetText(text, (Label)sender);\r
-        }\r
-\r
-        private void labelFleet1_DoubleClick(object sender, EventArgs e)\r
-        {\r
-            if (!_started)\r
-                return;\r
-            _doubleClickSemaphore.Release();\r
-            var text = TextGenerator.GenerateFleetData(Sniffer, 0);\r
-            if (_combinedFleet)\r
-                text += TextGenerator.GenerateFleetData(Sniffer, 1);\r
-            CopyFleetText(text, (Label)sender);\r
-        }\r
-\r
-        private void CopyFleetText(string text, Label fleetButton)\r
-        {\r
-            if (string.IsNullOrEmpty(text))\r
-                return;\r
-            Clipboard.SetText(text);\r
-            _tooltipCopy.Active = true;\r
-            _tooltipCopy.Show("コピーしました。", fleetButton);\r
-            Task.Run(async () =>\r
-            {\r
-                await Task.Delay(1000);\r
-                _tooltipCopy.Active = false;\r
-            });\r
-        }\r
-\r
-        private void labelBucketHistoryButton_Click(object sender, EventArgs e)\r
-        {\r
-            if (labelBucketHistory.Visible)\r
-            {\r
-                labelBucketHistory.Visible = false;\r
-                labelBucketHistoryButton.BackColor = DefaultBackColor;\r
-            }\r
-            else\r
-            {\r
-                labelBucketHistory.Visible = true;\r
-                labelBucketHistory.BringToFront();\r
-                labelBucketHistoryButton.BackColor = CustomColors.ActiveButtonColor;\r
-            }\r
-        }\r
-\r
-        private void labelBucketHistory_Click(object sender, EventArgs e)\r
-        {\r
-            labelBucketHistory.Visible = false;\r
-            labelBucketHistoryButton.BackColor = DefaultBackColor;\r
-        }\r
-\r
-        private void labelMaterialHistoryButton_Click(object sender, EventArgs e)\r
-        {\r
-            if (panelMaterialHistory.Visible)\r
-            {\r
-                panelMaterialHistory.Visible = false;\r
-                labelMaterialHistoryButton.BackColor = DefaultBackColor;\r
-            }\r
-            else\r
-            {\r
-                panelMaterialHistory.Visible = true;\r
-                panelMaterialHistory.BringToFront();\r
-                labelMaterialHistoryButton.BackColor = CustomColors.ActiveButtonColor;\r
-            }\r
-        }\r
-\r
-        private void panelMaterialHistory_Click(object sender, EventArgs e)\r
-        {\r
-            panelMaterialHistory.Visible = false;\r
-            labelMaterialHistoryButton.BackColor = DefaultBackColor;\r
-        }\r
-\r
-        public void ResetAchievement()\r
-        {\r
-            Sniffer.Achievement.Reset();\r
-            UpdateItemInfo();\r
-        }\r
-\r
-        private void labelRepairListButton_Click(object sender, EventArgs e)\r
-        {\r
-            if (panelRepairList.Visible)\r
-            {\r
-                panelRepairList.Visible = false;\r
-                labelRepairListButton.BackColor = DefaultBackColor;\r
-            }\r
-            else\r
-            {\r
-                panelRepairList.Visible = true;\r
-                panelRepairList.BringToFront();\r
-                labelRepairListButton.BackColor = CustomColors.ActiveButtonColor;\r
-            }\r
-        }\r
-\r
-        private void panelRepairList_Click(object sender, EventArgs e)\r
-        {\r
-            panelRepairList.Visible = false;\r
-            labelRepairListButton.BackColor = DefaultBackColor;\r
-        }\r
-\r
-        private void ShipListToolStripMenuItem_Click(object sender, EventArgs e)\r
-        {\r
-            _listForm.UpdateList();\r
-            _listForm.Show();\r
-            if (_listForm.WindowState == FormWindowState.Minimized)\r
-                _listForm.WindowState = FormWindowState.Normal;\r
-            _listForm.Activate();\r
-        }\r
-\r
-        private void LogToolStripMenuItem_Click(object sender, EventArgs e)\r
-        {\r
-            Process.Start("http://localhost:" + Config.Proxy.Listen + "/");\r
-        }\r
-\r
-        private void labelClearQuest_Click(object sender, EventArgs e)\r
-        {\r
-            Sniffer.ClearQuests();\r
-            UpdateQuestList();\r
-        }\r
-\r
-        private void labelClearQuest_MouseDown(object sender, MouseEventArgs e)\r
-        {\r
-            labelClearQuest.BackColor = CustomColors.ActiveButtonColor;\r
-        }\r
-\r
-        private void labelClearQuest_MouseUp(object sender, MouseEventArgs e)\r
-        {\r
-            labelClearQuest.BackColor = DefaultBackColor;\r
-        }\r
-\r
-        private void labelQuest_DoubleClick(object sender, EventArgs e)\r
-        {\r
-            var label = (Label)sender;\r
-            if (string.IsNullOrEmpty(label.Text))\r
-                return;\r
-            Clipboard.SetText(label.Text);\r
-            _tooltipCopy.Active = true;\r
-            _tooltipCopy.Show("コピーしました。", label);\r
-            Task.Run(async () =>\r
-            {\r
-                await Task.Delay(1000);\r
-                _tooltipCopy.Active = false;\r
-            });\r
-        }\r
-\r
-        private void CaptureToolStripMenuItem_Click(object sender, EventArgs e)\r
-        {\r
-            try\r
-            {\r
-                var proc = new ProcessStartInfo("BurageSnap.exe") {WorkingDirectory = "Capture"};\r
-                Process.Start(proc);\r
-            }\r
-            catch (FileNotFoundException)\r
-            {\r
-            }\r
-            catch (Win32Exception)\r
-            {\r
-            }\r
-        }\r
     }\r
 }
\ No newline at end of file