OSDN Git Service

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