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