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