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