OSDN Git Service

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