OSDN Git Service

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