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