OSDN Git Service

シフトキーを押しながらホイールで戦況を横スクロールさせる
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / MainForm.cs
1 // Copyright (C) 2013, 2014, 2015 Kazuhiro Fujieda <fujieda@users.osdn.me>\r
2 // \r
3 // Licensed under the Apache License, Version 2.0 (the "License");\r
4 // you may not use this file except in compliance with the License.\r
5 // You may obtain a copy of the License at\r
6 //\r
7 //    http://www.apache.org/licenses/LICENSE-2.0\r
8 //\r
9 // Unless required by applicable law or agreed to in writing, software\r
10 // distributed under the License is distributed on an "AS IS" BASIS,\r
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
12 // See the License for the specific language governing permissions and\r
13 // limitations under the License.\r
14 \r
15 using System;\r
16 using System.Collections.Generic;\r
17 using System.Diagnostics;\r
18 using System.Drawing;\r
19 using System.Globalization;\r
20 using System.IO;\r
21 using System.Linq;\r
22 using System.Net;\r
23 using System.Runtime.InteropServices;\r
24 using System.Text;\r
25 using System.Text.RegularExpressions;\r
26 using System.Threading.Tasks;\r
27 using System.Windows.Forms;\r
28 using Microsoft.CSharp.RuntimeBinder;\r
29 using static System.Math;\r
30 \r
31 namespace KancolleSniffer\r
32 {\r
33     public partial class MainForm : Form\r
34     {\r
35         private readonly Sniffer _sniffer = new Sniffer();\r
36         private readonly Config _config = new Config();\r
37         private readonly ConfigDialog _configDialog;\r
38         private readonly ProxyManager _proxyManager;\r
39         private readonly ResizableToolTip _toolTip = new ResizableToolTip {ShowAlways = true};\r
40         private readonly ResizableToolTip _toolTipQuest = new ResizableToolTip {ShowAlways = true, AutoPopDelay = 10000};\r
41         private readonly ResizableToolTip _tooltipCopy = new ResizableToolTip {AutomaticDelay = 0};\r
42         private int _currentFleet;\r
43         private bool _combinedFleet;\r
44         private readonly Label[] _labelCheckFleets;\r
45         private readonly MainFormLabels _mainLabels;\r
46         private readonly ListForm _listForm;\r
47         private readonly NotificationManager _notificationManager;\r
48         private bool _started;\r
49         private bool _timerEnabled;\r
50         private string _debugLogFile;\r
51         private IEnumerator<string> _playLog;\r
52         private DateTime _prev, _now;\r
53         private bool _inSortie;\r
54 \r
55         private readonly ErrorDialog _errorDialog = new ErrorDialog();\r
56         private readonly ErrorLog _errorLog;\r
57 \r
58         public MainForm()\r
59         {\r
60             InitializeComponent();\r
61             HttpProxy.AfterSessionComplete += HttpProxy_AfterSessionComplete;\r
62             _configDialog = new ConfigDialog(_config, this);\r
63             _labelCheckFleets = new[] {labelCheckFleet1, labelCheckFleet2, labelCheckFleet3, labelCheckFleet4};\r
64 \r
65             // この時点でAutoScaleDimensions == CurrentAutoScaleDimensionsなので、\r
66             // MainForm.Designer.csのAutoScaleDimensionsの6f,12fを使う。\r
67             ShipLabel.ScaleFactor = new SizeF(CurrentAutoScaleDimensions.Width / 6f,\r
68                 CurrentAutoScaleDimensions.Height / 12f);\r
69 \r
70             SetupFleetClick();\r
71             _mainLabels = new MainFormLabels();\r
72             _mainLabels.CreateAkashiTimers(panelShipInfo);\r
73             _mainLabels.CreateShipLabels(panelShipInfo, ShowShipOnShipList);\r
74             _mainLabels.CreateAkashiTimers7(panel7Ships);\r
75             _mainLabels.CreateShipLabels7(panel7Ships, ShowShipOnShipList);\r
76             _mainLabels.CreateCombinedShipLabels(panelCombinedFleet, ShowShipOnShipList);\r
77             _mainLabels.CreateNDockLabels(panelDock, labelNDock_Click);\r
78             panelRepairList.CreateLabels(panelRepairList_Click);\r
79             labelPresetAkashiTimer.BackColor = ShipLabel.ColumnColors[1];\r
80             _listForm = new ListForm(_sniffer, _config) {Owner = this};\r
81             _notificationManager = new NotificationManager(Alarm);\r
82             try\r
83             {\r
84                 _config.Load();\r
85             }\r
86             catch (Exception ex)\r
87             {\r
88                 throw new ConfigFileException("設定ファイルが壊れています。", ex);\r
89             }\r
90             _proxyManager = new ProxyManager(_config, this);\r
91             _errorLog = new ErrorLog(_sniffer);\r
92             _proxyManager.UpdatePacFile();\r
93             PerformZoom();\r
94             _mainLabels.AdjustAkashiTimers();\r
95             LoadData();\r
96             _sniffer.RepeatingTimerController = new RepeatingTimerController(_notificationManager, _config);\r
97         }\r
98 \r
99         /// <summary>\r
100         /// パネルのz-orderがくるうのを避ける\r
101         /// https://stackoverflow.com/a/5777090/1429506\r
102         /// </summary>\r
103         private void MainForm_Shown(object sender, EventArgs e)\r
104         {\r
105             // ReSharper disable once NotAccessedVariable\r
106             IntPtr handle;\r
107             foreach (var panel in new[] {panelShipInfo, panel7Ships, panelCombinedFleet})\r
108                 // ReSharper disable once RedundantAssignment\r
109                 handle = panel.Handle;\r
110         }\r
111 \r
112         private readonly FileSystemWatcher _watcher = new FileSystemWatcher\r
113         {\r
114             Path = AppDomain.CurrentDomain.BaseDirectory,\r
115             NotifyFilter = NotifyFilters.LastWrite\r
116         };\r
117 \r
118         private readonly Timer _watcherTimer = new Timer {Interval = 1000};\r
119 \r
120         private void LoadData()\r
121         {\r
122             var target = "";\r
123             _sniffer.LoadState();\r
124             DataLoader.LoadTpSpec();\r
125             _watcher.SynchronizingObject = this;\r
126             _watcherTimer.Tick += (sender, ev) =>\r
127             {\r
128                 _watcherTimer.Stop();\r
129                 switch (target)\r
130                 {\r
131                     case "status.xml":\r
132                         _sniffer.LoadState();\r
133                         break;\r
134                     case "TP.csv":\r
135                         DataLoader.LoadTpSpec();\r
136                         break;\r
137                 }\r
138             };\r
139             _watcher.Changed += (sender, ev) =>\r
140             {\r
141                 target = ev.Name;\r
142                 _watcherTimer.Stop();\r
143                 _watcherTimer.Start();\r
144             };\r
145             _watcher.EnableRaisingEvents = true;\r
146         }\r
147 \r
148         private class RepeatingTimerController : Sniffer.IRepeatingTimerController\r
149         {\r
150             private readonly NotificationManager _manager;\r
151             private readonly Config _config;\r
152 \r
153             public RepeatingTimerController(NotificationManager manager, Config config)\r
154             {\r
155                 _manager = manager;\r
156                 _config = config;\r
157             }\r
158 \r
159             public void Stop(string key)\r
160             {\r
161                 _manager.StopRepeat(key,\r
162                     (key == "入渠終了" || key == "遠征終了") &&\r
163                     (_config.Notifications[key].Flags & NotificationType.Cont) != 0);\r
164             }\r
165 \r
166             public void Stop(string key, int fleet) => _manager.StopRepeat(key, fleet);\r
167 \r
168             public void Suspend() => _manager.SuspendRepeat();\r
169 \r
170             public void Resume() => _manager.ResumeRepeat();\r
171         }\r
172 \r
173         public class ConfigFileException : Exception\r
174         {\r
175             public ConfigFileException(string message, Exception innerException) : base(message, innerException)\r
176             {\r
177             }\r
178         }\r
179 \r
180         private void HttpProxy_AfterSessionComplete(HttpProxy.Session session)\r
181         {\r
182             BeginInvoke(new Action<HttpProxy.Session>(ProcessRequest), session);\r
183         }\r
184 \r
185         private void ProcessRequest(HttpProxy.Session session)\r
186         {\r
187             var url = session.Request.PathAndQuery;\r
188             if (!url.Contains("kcsapi/"))\r
189                 return;\r
190             var request = session.Request.BodyAsString;\r
191             var response = session.Response.BodyAsString;\r
192             if (response == null || !response.StartsWith("svdata="))\r
193             {\r
194                 WriteDebugLog(url, request, response);\r
195                 return;\r
196             }\r
197             response = UnescapeString(response.Remove(0, "svdata=".Length));\r
198             WriteDebugLog(url, request, response);\r
199             ProcessRequestMain(url, request, response);\r
200         }\r
201 \r
202         private void ProcessRequestMain(string url, string request, string response)\r
203         {\r
204             try\r
205             {\r
206                 UpdateInfo(_sniffer.Sniff(url, request, JsonParser.Parse(response)));\r
207                 _errorLog.CheckBattleApi(url, request, response);\r
208             }\r
209 \r
210             catch (RuntimeBinderException e)\r
211             {\r
212                 if (_errorDialog.ShowDialog(this,\r
213                         "艦これに仕様変更があったか、受信内容が壊れています。",\r
214                         _errorLog.GenerateErrorLog(url, request, response, e.ToString())) == DialogResult.Abort)\r
215                     Exit();\r
216             }\r
217             catch (LogIOException e)\r
218             {\r
219                 // ReSharper disable once PossibleNullReferenceException\r
220                 if (_errorDialog.ShowDialog(this, e.Message, e.InnerException.ToString()) == DialogResult.Abort)\r
221                     Exit();\r
222             }\r
223             catch (BattleResultError)\r
224             {\r
225                 if (_errorDialog.ShowDialog(this, "戦闘結果の計算に誤りがあります。",\r
226                         _errorLog.GenerateBattleErrorLog()) == DialogResult.Abort)\r
227                     Exit();\r
228             }\r
229             catch (Exception e)\r
230             {\r
231                 if (_errorDialog.ShowDialog(this, "エラーが発生しました。",\r
232                         _errorLog.GenerateErrorLog(url, request, response, e.ToString())) == DialogResult.Abort)\r
233                     Exit();\r
234             }\r
235         }\r
236 \r
237         private void Exit()\r
238         {\r
239             _proxyManager.Shutdown();\r
240             Environment.Exit(1);\r
241         }\r
242 \r
243         private void WriteDebugLog(string url, string request, string response)\r
244         {\r
245             if (_debugLogFile != null)\r
246             {\r
247                 File.AppendAllText(_debugLogFile,\r
248                     $"date: {DateTime.Now:g}\nurl: {url}\nrequest: {request}\nresponse: {response ?? "(null)"}\n");\r
249             }\r
250         }\r
251 \r
252         private string UnescapeString(string s)\r
253         {\r
254             try\r
255             {\r
256                 var rx = new Regex(@"\\[uU]([0-9A-Fa-f]{4})");\r
257                 return rx.Replace(s,\r
258                     match => ((char)int.Parse(match.Value.Substring(2), NumberStyles.HexNumber)).ToString());\r
259             }\r
260             catch (ArgumentException)\r
261             {\r
262                 return s;\r
263             }\r
264         }\r
265 \r
266         private void UpdateInfo(Sniffer.Update update)\r
267         {\r
268             if (update == Sniffer.Update.Start)\r
269             {\r
270                 labelLogin.Visible = false;\r
271                 linkLabelGuide.Visible = false;\r
272                 _started = true;\r
273                 return;\r
274             }\r
275             if (!_started)\r
276                 return;\r
277             if (_now == DateTime.MinValue)\r
278                 _now = DateTime.Now;\r
279             if ((update & Sniffer.Update.Item) != 0)\r
280                 UpdateItemInfo();\r
281             if ((update & Sniffer.Update.Timer) != 0)\r
282                 UpdateTimers();\r
283             if ((update & Sniffer.Update.NDock) != 0)\r
284                 UpdateNDocLabels();\r
285             if ((update & Sniffer.Update.Mission) != 0)\r
286                 UpdateMissionLabels();\r
287             if ((update & Sniffer.Update.QuestList) != 0)\r
288                 UpdateQuestList();\r
289             if ((update & Sniffer.Update.Ship) != 0)\r
290                 UpdateShipInfo();\r
291             if ((update & Sniffer.Update.Battle) != 0)\r
292                 UpdateBattleInfo();\r
293         }\r
294 \r
295         private void MainForm_Load(object sender, EventArgs e)\r
296         {\r
297             RestoreLocation();\r
298             if (_config.HideOnMinimized && WindowState == FormWindowState.Minimized)\r
299                 ShowInTaskbar = false;\r
300             if (_config.ShowHpInPercent)\r
301                 _mainLabels.ToggleHpPercent();\r
302             if (_config.ShipList.Visible)\r
303                 _listForm.Show();\r
304             ApplyConfig();\r
305             ApplyDebugLogSetting();\r
306             ApplyLogSetting();\r
307             ApplyProxySetting();\r
308             CheckVersionUp((current, latest) =>\r
309             {\r
310                 if (double.Parse(latest) <= double.Parse(current))\r
311                     return;\r
312                 linkLabelGuide.Text = $"バージョン{latest}があります。";\r
313                 linkLabelGuide.LinkArea = new LinkArea(0, linkLabelGuide.Text.Length);\r
314                 linkLabelGuide.Click += (obj, ev) =>\r
315                 {\r
316                     Process.Start("https://ja.osdn.net/rel/kancollesniffer/" + latest);\r
317                 };\r
318             });\r
319         }\r
320 \r
321         public async void CheckVersionUp(Action<string, string> action)\r
322         {\r
323             var current = string.Join(".", Application.ProductVersion.Split('.').Take(2));\r
324             try\r
325             {\r
326                 var latest = (await new WebClient().DownloadStringTaskAsync("http://kancollesniffer.osdn.jp/version"))\r
327                     .TrimEnd();\r
328                 try\r
329                 {\r
330                     action(current, latest);\r
331                 }\r
332                 catch (InvalidOperationException)\r
333                 {\r
334                 }\r
335             }\r
336             catch (WebException)\r
337             {\r
338             }\r
339         }\r
340 \r
341         private void MainForm_FormClosing(object sender, FormClosingEventArgs e)\r
342         {\r
343             if (!_config.ExitSilently)\r
344             {\r
345                 using (var dialog = new ConfirmDialog())\r
346                 {\r
347                     if (dialog.ShowDialog(this) != DialogResult.Yes)\r
348                     {\r
349                         e.Cancel = true;\r
350                         return;\r
351                     }\r
352                 }\r
353             }\r
354             e.Cancel = false;\r
355             _sniffer.FlashLog();\r
356             _config.Location = (WindowState == FormWindowState.Normal ? Bounds : RestoreBounds).Location;\r
357             _config.ShowHpInPercent = _mainLabels.ShowHpInPercent;\r
358             _config.ShipList.Visible = _listForm.Visible && _listForm.WindowState == FormWindowState.Normal;\r
359             _config.Save();\r
360             _sniffer.SaveState();\r
361             _proxyManager.Shutdown();\r
362         }\r
363 \r
364         private void MainForm_Resize(object sender, EventArgs e)\r
365         {\r
366             ShowInTaskbar = !(_config.HideOnMinimized && WindowState == FormWindowState.Minimized);\r
367         }\r
368 \r
369         private void notifyIconMain_MouseDoubleClick(object sender, MouseEventArgs e)\r
370         {\r
371             NotifyIconOpenToolStripMenuItem_Click(sender, e);\r
372         }\r
373 \r
374         private void NotifyIconOpenToolStripMenuItem_Click(object sender, EventArgs e)\r
375         {\r
376             ShowInTaskbar = true;\r
377             WindowState = FormWindowState.Normal;\r
378             TopMost = _config.TopMost; // 最前面に表示されなくなることがあるのを回避する\r
379             Activate();\r
380         }\r
381 \r
382         private void ExitToolStripMenuItem_Click(object sender, EventArgs e)\r
383         {\r
384             Close();\r
385         }\r
386 \r
387         private void ConfigToolStripMenuItem_Click(object sender, EventArgs e)\r
388         {\r
389             if (_configDialog.ShowDialog(this) == DialogResult.OK)\r
390             {\r
391                 _config.Save();\r
392                 ApplyConfig();\r
393                 StopRepeatingTimer(_configDialog.RepeatSettingsChanged);\r
394             }\r
395         }\r
396 \r
397         private void StopRepeatingTimer(IEnumerable<string> names)\r
398         {\r
399             foreach (var name in names)\r
400                 _notificationManager.StopRepeat(name);\r
401         }\r
402 \r
403         private void PerformZoom()\r
404         {\r
405             if (_config.Zoom == 100)\r
406                 return;\r
407             var prev = CurrentAutoScaleDimensions;\r
408             foreach (var control in new Control[]\r
409             {\r
410                 this, _listForm, labelLogin, linkLabelGuide,\r
411                 _configDialog, _configDialog.NotificationConfigDialog,\r
412                 contextMenuStripMain, _errorDialog\r
413             })\r
414             {\r
415                 control.Font = new Font(control.Font.FontFamily, control.Font.Size * _config.Zoom / 100);\r
416             }\r
417             foreach (var toolTip in new[]{_toolTip, _toolTipQuest, _tooltipCopy})\r
418             {\r
419                 toolTip.Font = new Font(toolTip.Font.FontFamily, toolTip.Font.Size * _config.Zoom / 100);\r
420             }\r
421             ShipLabel.LatinFont = new Font("Tahoma", 8f * _config.Zoom / 100);\r
422             var cur = CurrentAutoScaleDimensions;\r
423             ShipLabel.ScaleFactor = new SizeF(ShipLabel.ScaleFactor.Width * cur.Width / prev.Width,\r
424                 ShipLabel.ScaleFactor.Height * cur.Height / prev.Height);\r
425         }\r
426 \r
427         private void RestoreLocation()\r
428         {\r
429             if (_config.Location.X == int.MinValue)\r
430                 return;\r
431             if (IsTitleBarOnAnyScreen(_config.Location))\r
432                 Location = _config.Location;\r
433         }\r
434 \r
435         private void ApplyConfig()\r
436         {\r
437             _listForm.TopMost = TopMost = _config.TopMost;\r
438             _sniffer.Item.MarginShips = _config.MarginShips;\r
439             UpdateNumOfShips();\r
440             _sniffer.Item.MarginEquips = _config.MarginEquips;\r
441             UpdateNumOfEquips();\r
442             _sniffer.Achievement.ResetHours = _config.ResetHours;\r
443             labelAkashiRepair.Visible = labelAkashiRepairTimer.Visible =\r
444                 labelPresetAkashiTimer.Visible = _config.UsePresetAkashi;\r
445         }\r
446 \r
447         public void ApplyDebugLogSetting()\r
448         {\r
449             _debugLogFile = _config.DebugLogging ? _config.DebugLogFile : null;\r
450         }\r
451 \r
452         public bool ApplyProxySetting()\r
453         {\r
454             return _proxyManager.ApplyConfig();\r
455         }\r
456 \r
457         public void ApplyLogSetting()\r
458         {\r
459             LogServer.OutputDir = _config.Log.OutputDir;\r
460             LogServer.MaterialHistory = _sniffer.Material.MaterialHistory;\r
461             _sniffer.EnableLog(_config.Log.On ? LogType.All : LogType.None);\r
462             _sniffer.MaterialLogInterval = _config.Log.MaterialLogInterval;\r
463             _sniffer.LogOutputDir = _config.Log.OutputDir;\r
464         }\r
465 \r
466         public static bool IsTitleBarOnAnyScreen(Point location)\r
467         {\r
468             var rect = new Rectangle(\r
469                 new Point(location.X + SystemInformation.IconSize.Width + SystemInformation.HorizontalFocusThickness,\r
470                     location.Y + SystemInformation.CaptionHeight), new Size(60, 1));\r
471             return Screen.AllScreens.Any(screen => screen.WorkingArea.Contains(rect));\r
472         }\r
473 \r
474         private void timerMain_Tick(object sender, EventArgs e)\r
475         {\r
476             if (_timerEnabled)\r
477             {\r
478                 try\r
479                 {\r
480                     _now = DateTime.Now;\r
481                     UpdateTimers();\r
482                     NotifyTimers();\r
483                     _prev = _now;\r
484                 }\r
485                 catch (Exception ex)\r
486                 {\r
487                     if (_errorDialog.ShowDialog(this, "エラーが発生しました。", ex.ToString()) == DialogResult.Abort)\r
488                         Exit();\r
489                 }\r
490             }\r
491             if (_playLog == null || _configDialog.Visible)\r
492             {\r
493                 labelPlayLog.Visible = false;\r
494                 return;\r
495             }\r
496             PlayLog();\r
497         }\r
498 \r
499         public void SetPlayLog(string file)\r
500         {\r
501             _playLog = File.ReadLines(file).GetEnumerator();\r
502         }\r
503 \r
504         private void PlayLog()\r
505         {\r
506             var lines = new List<string>();\r
507             foreach (var s in new[] {"url: ", "request: ", "response: "})\r
508             {\r
509                 do\r
510                 {\r
511                     if (!_playLog.MoveNext() || _playLog.Current == null)\r
512                     {\r
513                         labelPlayLog.Visible = false;\r
514                         return;\r
515                     }\r
516                 } while (!_playLog.Current.StartsWith(s));\r
517                 lines.Add(_playLog.Current.Substring(s.Length));\r
518             }\r
519             labelPlayLog.Visible = !labelPlayLog.Visible;\r
520             ProcessRequestMain(lines[0], lines[1], lines[2]);\r
521         }\r
522 \r
523         private void ShowShipOnShipList(object sender, EventArgs ev)\r
524         {\r
525             if (!_listForm.Visible)\r
526                 return;\r
527             var idx = (int)((Control)sender).Tag;\r
528             var statuses = _sniffer.GetShipStatuses(_currentFleet);\r
529             if (statuses.Length <= idx)\r
530                 return;\r
531             _listForm.ShowShip(statuses[idx].Id);\r
532         }\r
533 \r
534         private void UpdateItemInfo()\r
535         {\r
536             UpdateNumOfShips();\r
537             UpdateNumOfEquips();\r
538             _notificationManager.Flash();\r
539             labelNumOfBuckets.Text = _sniffer.Material.MaterialHistory[(int)Material.Bucket].Now.ToString("D");\r
540             UpdateBucketHistory();\r
541             var ac = _sniffer.Achievement.Value;\r
542             if (ac >= 10000)\r
543                 ac = 9999;\r
544             labelAchievement.Text = ac >= 1000 ? ((int)ac).ToString("D") : ac.ToString("F1");\r
545             _toolTip.SetToolTip(labelAchievement,\r
546                 "今月 " + _sniffer.Achievement.ValueOfMonth.ToString("F1") + "\n" +\r
547                 "EO " + _sniffer.ExMap.Achievement);\r
548             UpdateMaterialHistry();\r
549             if (_listForm.Visible)\r
550                 _listForm.UpdateList();\r
551         }\r
552 \r
553         private void UpdateNumOfShips()\r
554         {\r
555             var item = _sniffer.Item;\r
556             labelNumOfShips.Text = $"{item.NowShips:D}/{item.MaxShips:D}";\r
557             labelNumOfShips.ForeColor = item.TooManyShips ? CUDColor.Red : Color.Black;\r
558             if (item.AlarmShips)\r
559             {\r
560                 var message = $"残り{_sniffer.Item.MaxShips - _sniffer.Item.NowShips:D}隻";\r
561                 _notificationManager.Enqueue("艦娘数超過", message);\r
562                 item.AlarmShips = false;\r
563             }\r
564         }\r
565 \r
566         private void UpdateNumOfEquips()\r
567         {\r
568             var item = _sniffer.Item;\r
569             labelNumOfEquips.Text = $"{item.NowEquips:D}/{item.MaxEquips:D}";\r
570             labelNumOfEquips.ForeColor = item.TooManyEquips ? CUDColor.Red : Color.Black;\r
571             if (item.AlarmEquips)\r
572             {\r
573                 var message = $"残り{_sniffer.Item.MaxEquips - _sniffer.Item.NowEquips:D}個";\r
574                 _notificationManager.Enqueue("装備数超過", message);\r
575                 item.AlarmEquips = false;\r
576             }\r
577         }\r
578 \r
579         private void UpdateBucketHistory()\r
580         {\r
581             var count = _sniffer.Material.MaterialHistory[(int)Material.Bucket];\r
582             var day = CutOverflow(count.Now - count.BegOfDay, 999);\r
583             var week = CutOverflow(count.Now - count.BegOfWeek, 999);\r
584             labelBucketHistory.Text = $"{day:+#;-#;±0} 今日\n{week:+#;-#;±0} 今週";\r
585         }\r
586 \r
587         private void UpdateMaterialHistry()\r
588         {\r
589             var labels = new[] {labelFuelHistory, labelBulletHistory, labelSteelHistory, labelBouxiteHistory};\r
590             var text = new[] {"燃料", "弾薬", "鋼材", "ボーキ"};\r
591             for (var i = 0; i < labels.Length; i++)\r
592             {\r
593                 var count = _sniffer.Material.MaterialHistory[i];\r
594                 var port = CutOverflow(count.Now - _sniffer.Material.PrevPort[i], 99999);\r
595                 var day = CutOverflow(count.Now - count.BegOfDay, 99999);\r
596                 var week = CutOverflow(count.Now - count.BegOfWeek, 99999);\r
597                 labels[i].Text = $"{text[i]}\n{port:+#;-#;±0}\n{day:+#;-#;±0}\n{week:+#;-#;±0}";\r
598             }\r
599         }\r
600 \r
601         private int CutOverflow(int value, int limit)\r
602         {\r
603             if (value > limit)\r
604                 return limit;\r
605             if (value < -limit)\r
606                 return -limit;\r
607             return value;\r
608         }\r
609 \r
610         private void UpdateShipInfo()\r
611         {\r
612             SetCurrentFleet();\r
613             UpdatePanelShipInfo();\r
614             NotifyDamagedShip();\r
615             UpdateChargeInfo();\r
616             UpdateRepairList();\r
617             if (_listForm.Visible)\r
618                 _listForm.UpdateList();\r
619         }\r
620 \r
621         private void SetCurrentFleet()\r
622         {\r
623             var inSortie = _sniffer.InSortie;\r
624             if (_inSortie || !inSortie.Any(x => x))\r
625             {\r
626                 _inSortie = inSortie.Any(x => x);\r
627                 return;\r
628             }\r
629             _inSortie = true;\r
630             if (inSortie[0] && inSortie[1])\r
631             {\r
632                 _combinedFleet = true;\r
633                 _currentFleet = 0;\r
634             }\r
635             else\r
636             {\r
637                 _combinedFleet = false;\r
638                 _currentFleet = Array.FindIndex(inSortie, x => x);\r
639             }\r
640         }\r
641 \r
642         private void UpdatePanelShipInfo()\r
643         {\r
644             var statuses = _sniffer.GetShipStatuses(_currentFleet);\r
645             panel7Ships.Visible = statuses.Length == 7;\r
646             _mainLabels.SetShipLabels(statuses);\r
647             if (_sniffer.CombinedFleetType == 0)\r
648                 _combinedFleet = false;\r
649             labelFleet1.Text = _combinedFleet ? "連合" : "第一";\r
650             panelCombinedFleet.Visible = _combinedFleet;\r
651             if (_combinedFleet)\r
652                 _mainLabels.SetCombinedShipLabels(_sniffer.GetShipStatuses(0), _sniffer.GetShipStatuses(1));\r
653             for (var i = 0; i < _labelCheckFleets.Length; i++)\r
654                 _labelCheckFleets[i].Visible = _currentFleet == i;\r
655             UpdateAkashiTimer();\r
656             var battle = _sniffer.Battle;\r
657             UpdateFighterPower(_combinedFleet && (battle.BattleState == BattleState.None || battle.EnemyIsCombined));\r
658             UpdateLoS();\r
659             UpdateCondTimers();\r
660         }\r
661 \r
662         private void NotifyDamagedShip()\r
663         {\r
664             if (!_sniffer.BadlyDamagedShips.Any())\r
665                 return;\r
666             _notificationManager.Enqueue("大破警告", string.Join(" ", _sniffer.BadlyDamagedShips));\r
667             _notificationManager.Flash();\r
668         }\r
669 \r
670         public void UpdateFighterPower(bool combined)\r
671         {\r
672             var fp = combined\r
673                 ? _sniffer.GetFighterPower(0).Zip(_sniffer.GetFighterPower(1), (a, b) => a + b).ToArray()\r
674                 : _sniffer.GetFighterPower(_currentFleet);\r
675             labelFighterPower.Text = fp[0].ToString("D");\r
676             var cr = combined\r
677                 ? _sniffer.GetContactTriggerRate(0) + _sniffer.GetContactTriggerRate(1)\r
678                 : _sniffer.GetContactTriggerRate(_currentFleet);\r
679             var text = "制空: " + (fp[0] == fp[1] ? $"{fp[0]}" : $"{fp[0]}~{fp[1]}") +\r
680                        $" 触接: {cr * 100:f1}";\r
681             _toolTip.SetToolTip(labelFighterPower, text);\r
682             _toolTip.SetToolTip(labelFighterPowerCaption, text);\r
683         }\r
684 \r
685         private void UpdateLoS()\r
686         {\r
687             labelLoS.Text = RoundDown(_sniffer.GetFleetLineOfSights(_currentFleet, 1)).ToString("F1");\r
688             var text = $"係数3: {RoundDown(_sniffer.GetFleetLineOfSights(_currentFleet, 3)):F1}\r\n" +\r
689                        $"係数4: {RoundDown(_sniffer.GetFleetLineOfSights(_currentFleet, 4)):F1}";\r
690             _toolTip.SetToolTip(labelLoS, text);\r
691             _toolTip.SetToolTip(labelLoSCaption, text);\r
692         }\r
693 \r
694         private double RoundDown(double number)\r
695         {\r
696             return Floor(number * 10) / 10.0;\r
697         }\r
698 \r
699         private void UpdateBattleInfo()\r
700         {\r
701             ResetBattleInfo();\r
702             _listForm.UpdateBattleResult();\r
703             if (_sniffer.Battle.BattleState == BattleState.None)\r
704                 return;\r
705             panelBattleInfo.BringToFront();\r
706             var battle = _sniffer.Battle;\r
707             labelFormation.Text = new[] {"同航戦", "反航戦", "T字有利", "T字不利"}[battle.Formation[2] - 1];\r
708             UpdateBattleFighterPower();\r
709             if ((_config.Spoilers & Spoiler.ResultRank) != 0)\r
710                 ShowResultRank();\r
711             if (_sniffer.Battle.BattleState == BattleState.Day)\r
712                 _listForm.UpdateAirBattleResult();\r
713         }\r
714 \r
715         private void ResetBattleInfo()\r
716         {\r
717             labelFormation.Text = "";\r
718             labelEnemyFighterPower.Text = "";\r
719             labelFighterPower.ForeColor = DefaultForeColor;\r
720             labelResultRank.Text = "判定";\r
721             panelBattleInfo.Visible = _sniffer.Battle.BattleState != BattleState.None;\r
722         }\r
723 \r
724         private void UpdateBattleFighterPower()\r
725         {\r
726             var battle = _sniffer.Battle;\r
727             var power = battle.EnemyFighterPower;\r
728             labelEnemyFighterPower.Text = power.AirCombat + power.UnknownMark;\r
729             if (power.AirCombat != power.Interception)\r
730             {\r
731                 var text = "防空: " + power.Interception + power.UnknownMark;\r
732                 _toolTip.SetToolTip(labelEnemyFighterPower, text);\r
733                 _toolTip.SetToolTip(labelEnemyFighterPowerCaption, text);\r
734             }\r
735             UpdateFighterPower(_sniffer.CombinedFleetType > 0 && battle.EnemyIsCombined);\r
736             labelFighterPower.ForeColor = new[]\r
737                 {DefaultForeColor, DefaultForeColor, CUDColor.Blue, CUDColor.Green, CUDColor.Orange, CUDColor.Red}[\r
738                 battle.AirControlLevel + 1];\r
739         }\r
740 \r
741         private void ShowResultRank()\r
742         {\r
743             var result = new[] {"完全S", "勝利S", "勝利A", "勝利B", "敗北C", "敗北D", "敗北E"};\r
744             labelResultRank.Text = result[(int)_sniffer.Battle.ResultRank];\r
745         }\r
746 \r
747         private void labelResultRank_Click(object sender, EventArgs e)\r
748         {\r
749             ShowResultRank();\r
750         }\r
751 \r
752         private void UpdateChargeInfo()\r
753         {\r
754             var fuelSq = new[] {labelFuelSq1, labelFuelSq2, labelFuelSq3, labelFuelSq4};\r
755             var bullSq = new[] {labelBullSq1, labelBullSq2, labelBullSq3, labelBullSq4};\r
756 \r
757             for (var i = 0; i < fuelSq.Length; i++)\r
758             {\r
759                 var stat = _sniffer.ChargeStatuses[i];\r
760                 fuelSq[i].ImageIndex = stat.Fuel;\r
761                 bullSq[i].ImageIndex = stat.Bull;\r
762             }\r
763         }\r
764 \r
765         private void UpdateNDocLabels()\r
766         {\r
767             _mainLabels.SetNDockLabels(_sniffer.NDock);\r
768         }\r
769 \r
770 \r
771         private void labelNDock_Click(object sender, EventArgs e)\r
772         {\r
773             _config.ShowEndTime ^= TimerKind.NDock;\r
774             UpdateTimers();\r
775         }\r
776 \r
777         private void UpdateMissionLabels()\r
778         {\r
779             foreach (var entry in\r
780                 new[] {labelMissionName1, labelMissionName2, labelMissionName3}.Zip(_sniffer.Missions,\r
781                     (label, mission) => new {label, mission.Name}))\r
782                 entry.label.Text = entry.Name;\r
783         }\r
784 \r
785         private void labelMission_Click(object sender, EventArgs e)\r
786         {\r
787             _config.ShowEndTime ^= TimerKind.Mission;\r
788             UpdateTimers();\r
789         }\r
790 \r
791         private void UpdateTimers()\r
792         {\r
793             var mission = new[] {labelMission1, labelMission2, labelMission3};\r
794             for (var i = 0; i < mission.Length; i++)\r
795             {\r
796                 var entry = _sniffer.Missions[i];\r
797                 SetTimerColor(mission[i], entry.Timer, _now);\r
798                 mission[i].Text = entry.Timer.ToString(_now, (_config.ShowEndTime & TimerKind.Mission) != 0);\r
799             }\r
800             for (var i = 0; i < _sniffer.NDock.Length; i++)\r
801             {\r
802                 var entry = _sniffer.NDock[i];\r
803                 _mainLabels.SetNDockTimer(i, entry.Timer, _now, (_config.ShowEndTime & TimerKind.NDock) != 0);\r
804             }\r
805             var kdock = new[] {labelConstruct1, labelConstruct2, labelConstruct3, labelConstruct4};\r
806             for (var i = 0; i < kdock.Length; i++)\r
807             {\r
808                 var timer = _sniffer.KDock[i];\r
809                 SetTimerColor(kdock[i], timer, _now);\r
810                 kdock[i].Text = timer.ToString(_now);\r
811             }\r
812             UpdateCondTimers();\r
813             UpdateAkashiTimer();\r
814             _timerEnabled = true;\r
815         }\r
816 \r
817         private void NotifyTimers()\r
818         {\r
819             for (var i = 0; i < _sniffer.Missions.Length; i++)\r
820             {\r
821                 var entry = _sniffer.Missions[i];\r
822                 CheckAlarm("遠征終了", entry.Timer, i + 1, entry.Name);\r
823             }\r
824             for (var i = 0; i < _sniffer.NDock.Length; i++)\r
825             {\r
826                 var entry = _sniffer.NDock[i];\r
827                 CheckAlarm("入渠終了", entry.Timer, i, entry.Name);\r
828             }\r
829             for (var i = 0; i < _sniffer.KDock.Length; i++)\r
830             {\r
831                 var timer = _sniffer.KDock[i];\r
832                 CheckAlarm("建造完了", timer, 0, $"第{i + 1:D}ドック");\r
833             }\r
834             NotifyCondTimers();\r
835             NotifyAkashiTimer();\r
836             _notificationManager.Flash();\r
837         }\r
838 \r
839         private void CheckAlarm(string key, AlarmTimer timer, int fleet, string subject)\r
840         {\r
841             if (timer.CheckAlarm(_prev, _now))\r
842             {\r
843                 SetNotification(key, fleet, subject);\r
844                 return;\r
845             }\r
846             var pre = TimeSpan.FromSeconds(_config.Notifications[key].PreliminaryPeriod);\r
847             if (pre == TimeSpan.Zero)\r
848                 return;\r
849             if (timer.CheckAlarm(_prev + pre, _now + pre))\r
850                 SetPreNotification(key, fleet, subject);\r
851         }\r
852 \r
853         private void SetTimerColor(Label label, AlarmTimer timer, DateTime now)\r
854         {\r
855             label.ForeColor = timer.IsFinished(now) ? CUDColor.Red : Color.Black;\r
856         }\r
857 \r
858         private void UpdateCondTimers()\r
859         {\r
860             DateTime timer;\r
861             if (_combinedFleet)\r
862             {\r
863                 var timer1 = _sniffer.GetConditionTimer(0);\r
864                 var timer2 = _sniffer.GetConditionTimer(1);\r
865                 timer = timer2 > timer1 ? timer2 : timer1;\r
866             }\r
867             else\r
868             {\r
869                 timer = _sniffer.GetConditionTimer(_currentFleet);\r
870             }\r
871             if (timer == DateTime.MinValue)\r
872             {\r
873                 labelCondTimerTitle.Text = "";\r
874                 labelCondTimer.Text = "";\r
875                 return;\r
876             }\r
877             var span = TimeSpan.FromSeconds(Ceiling((timer - _now).TotalSeconds));\r
878             if (span >= TimeSpan.FromMinutes(9))\r
879             {\r
880                 labelCondTimerTitle.Text = "cond40まで";\r
881                 labelCondTimer.Text = (span - TimeSpan.FromMinutes(9)).ToString(@"mm\:ss");\r
882                 labelCondTimer.ForeColor = DefaultForeColor;\r
883             }\r
884             else\r
885             {\r
886                 labelCondTimerTitle.Text = "cond49まで";\r
887                 labelCondTimer.Text = (span >= TimeSpan.Zero ? span : TimeSpan.Zero).ToString(@"mm\:ss");\r
888                 labelCondTimer.ForeColor = span <= TimeSpan.Zero ? CUDColor.Red : DefaultForeColor;\r
889             }\r
890         }\r
891 \r
892         private void NotifyCondTimers()\r
893         {\r
894             var notice = _sniffer.GetConditionNotice(_prev, _now);\r
895             var pre = TimeSpan.FromSeconds(_config.Notifications["疲労回復"].PreliminaryPeriod);\r
896             var preNotice = pre == TimeSpan.Zero\r
897                 ? new int[ShipInfo.FleetCount]\r
898                 : _sniffer.GetConditionNotice(_prev + pre, _now + pre);\r
899             for (var i = 0; i < ShipInfo.FleetCount; i++)\r
900             {\r
901                 if (_config.NotifyConditions.Contains(notice[i]))\r
902                 {\r
903                     SetNotification("疲労回復" + notice[i], i, "cond" + notice[i]);\r
904                 }\r
905                 else if (_config.NotifyConditions.Contains(preNotice[i]))\r
906                 {\r
907                     SetPreNotification("疲労回復" + preNotice[i], i, "cond" + notice[i]);\r
908                 }\r
909             }\r
910         }\r
911 \r
912         private void UpdateAkashiTimer()\r
913         {\r
914             if (_config.UsePresetAkashi)\r
915                 UpdatePresetAkashiTimer();\r
916             var statuses = _sniffer.GetShipStatuses(_currentFleet);\r
917             _mainLabels.SetAkashiTimer(statuses,\r
918                 _sniffer.AkashiTimer.GetTimers(_currentFleet));\r
919         }\r
920 \r
921         private void UpdatePresetAkashiTimer()\r
922         {\r
923             var akashi = _sniffer.AkashiTimer;\r
924             var span = akashi.PresetDeckTimer;\r
925             var color = span == TimeSpan.Zero && akashi.CheckPresetRepairing() ? CUDColor.Red : DefaultForeColor;\r
926             var text = span == TimeSpan.MinValue ? "" : span.ToString(@"mm\:ss");\r
927             labelAkashiRepairTimer.ForeColor = color;\r
928             labelAkashiRepairTimer.Text = text;\r
929             if (akashi.CheckPresetRepairing() && !akashi.CheckRepairing(_currentFleet))\r
930             {\r
931                 labelPresetAkashiTimer.ForeColor = color;\r
932                 labelPresetAkashiTimer.Text = text;\r
933             }\r
934             else\r
935             {\r
936                 labelPresetAkashiTimer.ForeColor = DefaultForeColor;\r
937                 labelPresetAkashiTimer.Text = "";\r
938             }\r
939         }\r
940 \r
941         private void NotifyAkashiTimer()\r
942         {\r
943             var akashi = _sniffer.AkashiTimer;\r
944             var msgs = akashi.GetNotice(_prev, _now);\r
945             if (msgs.Length == 0)\r
946             {\r
947                 _notificationManager.StopRepeat("泊地修理");\r
948                 return;\r
949             }\r
950             if (!akashi.CheckRepairing() && !(akashi.CheckPresetRepairing() && _config.UsePresetAkashi))\r
951             {\r
952                 _notificationManager.StopRepeat("泊地修理");\r
953                 return;\r
954             }\r
955             var skipPreliminary = false;\r
956             if (msgs[0].Proceeded == "20分経過しました。")\r
957             {\r
958                 SetNotification("泊地修理20分経過", msgs[0].Proceeded);\r
959                 msgs[0].Proceeded = "";\r
960                 skipPreliminary = true;\r
961                 // 修理完了がいるかもしれないので続ける\r
962             }\r
963             for (var i = 0; i < ShipInfo.FleetCount; i++)\r
964             {\r
965                 if (msgs[i].Proceeded != "")\r
966                     SetNotification("泊地修理進行", i, msgs[i].Proceeded);\r
967                 if (msgs[i].Completed != "")\r
968                     SetNotification("泊地修理完了", i, msgs[i].Completed);\r
969             }\r
970             var pre = TimeSpan.FromSeconds(_config.Notifications["泊地修理20分経過"].PreliminaryPeriod);\r
971             if (skipPreliminary || pre == TimeSpan.Zero)\r
972                 return;\r
973             if ((msgs = akashi.GetNotice(_prev + pre, _now + pre))[0].Proceeded == "20分経過しました。")\r
974                 SetPreNotification("泊地修理20分経過", 0, msgs[0].Proceeded);\r
975         }\r
976 \r
977         private void SetNotification(string key, string subject)\r
978         {\r
979             SetNotification(key, 0, subject);\r
980         }\r
981 \r
982         private void SetNotification(string key, int fleet, string subject)\r
983         {\r
984             var spec = _config.Notifications[_notificationManager.KeyToName(key)];\r
985             _notificationManager.Enqueue(key, fleet, subject,\r
986                 (spec.Flags & _config.NotificationFlags & NotificationType.Repeat) == 0 ? 0 : spec.RepeatInterval);\r
987         }\r
988 \r
989         private void SetPreNotification(string key, int fleet, string subject)\r
990         {\r
991             var spec = _config.Notifications[_notificationManager.KeyToName(key)];\r
992             if ((spec.Flags & NotificationType.Preliminary) != 0)\r
993                 _notificationManager.Enqueue(key, fleet, subject, 0, true);\r
994         }\r
995 \r
996         private void UpdateRepairList()\r
997         {\r
998             panelRepairList.SetRepairList(_sniffer.RepairList);\r
999         }\r
1000 \r
1001         private void UpdateQuestList()\r
1002         {\r
1003             var category = new[]\r
1004             {\r
1005                 labelQuestColor1, labelQuestColor2, labelQuestColor3, labelQuestColor4, labelQuestColor5,\r
1006                 labelQuestColor6\r
1007             };\r
1008             var name = new[] {labelQuest1, labelQuest2, labelQuest3, labelQuest4, labelQuest5, labelQuest6};\r
1009             var count = new[]\r
1010             {\r
1011                 labelQuestCount1, labelQuestCount2, labelQuestCount3, labelQuestCount4, labelQuestCount5,\r
1012                 labelQuestCount6\r
1013             };\r
1014             var progress = new[]\r
1015                 {labelProgress1, labelProgress2, labelProgress3, labelProgress4, labelProgress5, labelProgress6};\r
1016             var quests = _sniffer.Quests;\r
1017             for (var i = 0; i < name.Length; i++)\r
1018             {\r
1019                 if (i < quests.Length)\r
1020                 {\r
1021                     category[i].BackColor = quests[i].Color;\r
1022                     name[i].Text = quests[i].Name;\r
1023                     progress[i].Text = $"{quests[i].Progress:D}%";\r
1024                     _toolTipQuest.SetToolTip(name[i], quests[i].ToToolTip());\r
1025                     var c = quests[i].Count;\r
1026                     if (c.Id == 0)\r
1027                     {\r
1028                         count[i].Text = "";\r
1029                         count[i].ForeColor = Color.Black;\r
1030                         _toolTip.SetToolTip(count[i], "");\r
1031                         continue;\r
1032                     }\r
1033                     count[i].Text = " " + c;\r
1034                     count[i].ForeColor = c.Cleared ? CUDColor.Green : Color.Black;\r
1035                     _toolTip.SetToolTip(count[i], c.ToToolTip());\r
1036                 }\r
1037                 else\r
1038                 {\r
1039                     category[i].BackColor = DefaultBackColor;\r
1040                     name[i].Text = count[i].Text = progress[i].Text = "";\r
1041                     _toolTipQuest.SetToolTip(name[i], "");\r
1042                     _toolTip.SetToolTip(count[i], "");\r
1043                 }\r
1044             }\r
1045         }\r
1046 \r
1047         private void Alarm(string balloonTitle, string balloonMessage, string name)\r
1048         {\r
1049             var flags = _config.Notifications[name].Flags;\r
1050             var effective = _config.NotificationFlags & _config.Notifications[name].Flags;\r
1051             if ((effective & NotificationType.FlashWindow) != 0)\r
1052                 Win32API.FlashWindow(Handle);\r
1053             if ((effective & NotificationType.ShowBaloonTip) != 0)\r
1054                 notifyIconMain.ShowBalloonTip(20000, balloonTitle, balloonMessage, ToolTipIcon.Info);\r
1055             if ((effective & NotificationType.PlaySound) != 0)\r
1056                 PlaySound(_config.Sounds[name], _config.Sounds.Volume);\r
1057             if (_config.Pushbullet.On && (flags & NotificationType.Push) != 0)\r
1058             {\r
1059                 Task.Run(() =>\r
1060                 {\r
1061                     PushNotification.PushToPushbullet(_config.Pushbullet.Token, balloonTitle, balloonMessage);\r
1062                 });\r
1063             }\r
1064             if (_config.Pushover.On && (flags & NotificationType.Push) != 0)\r
1065             {\r
1066                 Task.Run(() =>\r
1067                 {\r
1068                     PushNotification.PushToPushover(_config.Pushover.ApiKey, _config.Pushover.UserKey,\r
1069                         balloonTitle, balloonMessage);\r
1070                 });\r
1071             }\r
1072         }\r
1073 \r
1074         [DllImport("winmm.dll")]\r
1075         private static extern int mciSendString(String command,\r
1076             StringBuilder buffer, int bufferSize, IntPtr hwndCallback);\r
1077 \r
1078 // ReSharper disable InconsistentNaming\r
1079         private const int MM_MCINOTIFY = 0x3B9;\r
1080 \r
1081         private const int MCI_NOTIFY_SUCCESSFUL = 1;\r
1082 // ReSharper restore InconsistentNaming\r
1083 \r
1084         public void PlaySound(string file, int volume)\r
1085         {\r
1086             if (!File.Exists(file))\r
1087                 return;\r
1088             mciSendString("close sound", null, 0, IntPtr.Zero);\r
1089             if (mciSendString("open \"" + file + "\" type mpegvideo alias sound", null, 0, IntPtr.Zero) != 0)\r
1090                 return;\r
1091             mciSendString("setaudio sound volume to " + volume * 10, null, 0, IntPtr.Zero);\r
1092             mciSendString("play sound notify", null, 0, Handle);\r
1093         }\r
1094 \r
1095         protected override void WndProc(ref Message m)\r
1096         {\r
1097             if (m.Msg == MM_MCINOTIFY && (int)m.WParam == MCI_NOTIFY_SUCCESSFUL)\r
1098                 mciSendString("close sound", null, 0, IntPtr.Zero);\r
1099             base.WndProc(ref m);\r
1100         }\r
1101 \r
1102         private void SetupFleetClick()\r
1103         {\r
1104             var labels = new[]\r
1105             {\r
1106                 new[] {labelFleet1, labelFleet2, labelFleet3, labelFleet4},\r
1107                 new[] {labelFuelSq1, labelFuelSq2, labelFuelSq3, labelFuelSq4},\r
1108                 new[] {labelBullSq1, labelBullSq2, labelBullSq3, labelBullSq4}\r
1109             };\r
1110             foreach (var a in labels)\r
1111             {\r
1112                 for (var fleet = 0; fleet < labels[0].Length; fleet++)\r
1113                 {\r
1114                     a[fleet].Tag = fleet;\r
1115                     a[fleet].Click += labelFleet_Click;\r
1116                 }\r
1117             }\r
1118         }\r
1119 \r
1120         private void labelFleet_Click(object sender, EventArgs e)\r
1121         {\r
1122             if (!_started)\r
1123                 return;\r
1124             var fleet = (int)((Label)sender).Tag;\r
1125             if (_currentFleet == fleet)\r
1126             {\r
1127                 if (fleet > 0)\r
1128                     return;\r
1129                 _combinedFleet = _sniffer.CombinedFleetType > 0 && !_combinedFleet;\r
1130                 UpdatePanelShipInfo();\r
1131                 return;\r
1132             }\r
1133             _combinedFleet = false;\r
1134             _currentFleet = fleet;\r
1135             UpdatePanelShipInfo();\r
1136         }\r
1137 \r
1138         private void labelFleet1_MouseHover(object sender, EventArgs e)\r
1139         {\r
1140             labelFleet1.Text = _currentFleet == 0 && _sniffer.CombinedFleetType > 0 && !_combinedFleet ? "連合" : "第一";\r
1141         }\r
1142 \r
1143         private void labelFleet1_MouseLeave(object sender, EventArgs e)\r
1144         {\r
1145             labelFleet1.Text = _combinedFleet ? "連合" : "第一";\r
1146         }\r
1147 \r
1148         private readonly Color _activeButtonColor = Color.FromArgb(152, 179, 208);\r
1149 \r
1150         private void labelBucketHistoryButton_Click(object sender, EventArgs e)\r
1151         {\r
1152             if (labelBucketHistory.Visible)\r
1153             {\r
1154                 labelBucketHistory.Visible = false;\r
1155                 labelBucketHistoryButton.BackColor = DefaultBackColor;\r
1156             }\r
1157             else\r
1158             {\r
1159                 labelBucketHistory.Visible = true;\r
1160                 labelBucketHistory.BringToFront();\r
1161                 labelBucketHistoryButton.BackColor = _activeButtonColor;\r
1162             }\r
1163         }\r
1164 \r
1165         private void labelBucketHistory_Click(object sender, EventArgs e)\r
1166         {\r
1167             labelBucketHistory.Visible = false;\r
1168             labelBucketHistoryButton.BackColor = DefaultBackColor;\r
1169         }\r
1170 \r
1171         private void labelMaterialHistoryButton_Click(object sender, EventArgs e)\r
1172         {\r
1173             if (panelMaterialHistory.Visible)\r
1174             {\r
1175                 panelMaterialHistory.Visible = false;\r
1176                 labelMaterialHistoryButton.BackColor = DefaultBackColor;\r
1177             }\r
1178             else\r
1179             {\r
1180                 panelMaterialHistory.Visible = true;\r
1181                 panelMaterialHistory.BringToFront();\r
1182                 labelMaterialHistoryButton.BackColor = _activeButtonColor;\r
1183             }\r
1184         }\r
1185 \r
1186         private void panelMaterialHistory_Click(object sender, EventArgs e)\r
1187         {\r
1188             panelMaterialHistory.Visible = false;\r
1189             labelMaterialHistoryButton.BackColor = DefaultBackColor;\r
1190         }\r
1191 \r
1192         public void ResetAchievemnt()\r
1193         {\r
1194             _sniffer.Achievement.Reset();\r
1195             UpdateItemInfo();\r
1196         }\r
1197 \r
1198         private void labelRepairListButton_Click(object sender, EventArgs e)\r
1199         {\r
1200             if (panelRepairList.Visible)\r
1201             {\r
1202                 panelRepairList.Visible = false;\r
1203                 labelRepairListButton.BackColor = DefaultBackColor;\r
1204             }\r
1205             else\r
1206             {\r
1207                 panelRepairList.Visible = true;\r
1208                 panelRepairList.BringToFront();\r
1209                 labelRepairListButton.BackColor = _activeButtonColor;\r
1210             }\r
1211         }\r
1212 \r
1213         private void panelRepairList_Click(object sender, EventArgs e)\r
1214         {\r
1215             panelRepairList.Visible = false;\r
1216             labelRepairListButton.BackColor = DefaultBackColor;\r
1217         }\r
1218 \r
1219         private void ShipListToolStripMenuItem_Click(object sender, EventArgs e)\r
1220         {\r
1221             _listForm.UpdateList();\r
1222             _listForm.Show();\r
1223             if (_listForm.WindowState == FormWindowState.Minimized)\r
1224                 _listForm.WindowState = FormWindowState.Normal;\r
1225             _listForm.Activate();\r
1226         }\r
1227 \r
1228         private void LogToolStripMenuItem_Click(object sender, EventArgs e)\r
1229         {\r
1230             Process.Start("http://localhost:" + _config.Proxy.Listen + "/");\r
1231         }\r
1232 \r
1233         private void labelClearQuest_Click(object sender, EventArgs e)\r
1234         {\r
1235             _sniffer.ClearQuests();\r
1236             UpdateQuestList();\r
1237         }\r
1238 \r
1239         private void labelClearQuest_MouseDown(object sender, MouseEventArgs e)\r
1240         {\r
1241             labelClearQuest.BackColor = _activeButtonColor;\r
1242         }\r
1243 \r
1244         private void labelClearQuest_MouseUp(object sender, MouseEventArgs e)\r
1245         {\r
1246             labelClearQuest.BackColor = DefaultBackColor;\r
1247         }\r
1248 \r
1249         private void labelQuest_DoubleClick(object sender, EventArgs e)\r
1250         {\r
1251             var label = (Label)sender;\r
1252             Clipboard.SetText(label.Text);\r
1253             _tooltipCopy.Active = true;\r
1254             _tooltipCopy.Show("コピーしました。", label);\r
1255             Task.Run(async () =>\r
1256             {\r
1257                 await Task.Delay(1000);\r
1258                 _tooltipCopy.Active = false;\r
1259             });\r
1260         }\r
1261 \r
1262         private void CaptureToolStripMenuItem_Click(object sender, EventArgs e)\r
1263         {\r
1264             try\r
1265             {\r
1266                 var proc = new ProcessStartInfo("BurageSnap.exe") {WorkingDirectory = "Capture"};\r
1267                 Process.Start(proc);\r
1268             }\r
1269             catch (FileNotFoundException)\r
1270             {\r
1271             }\r
1272         }\r
1273     }\r
1274 }