OSDN Git Service

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