OSDN Git Service

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