OSDN Git Service

プロキシ設定を復旧しない条件を緩和する
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / ShipLabels.cs
1 // Copyright (C) 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.Drawing;\r
19 using System.Linq;\r
20 using System.Text.RegularExpressions;\r
21 using System.Windows.Forms;\r
22 using static System.Math;\r
23 \r
24 namespace KancolleSniffer\r
25 {\r
26     public enum ShipNameWidth\r
27     {\r
28         MainPanel = 93,\r
29         AkashiTimer = 53,\r
30         NDock = 69,\r
31         RepairList = NDock,\r
32         RepairListFull = 75,\r
33         ShipList = 82,\r
34         GroupConfig = 82,\r
35         Combined = 54,\r
36         BattleResult = 65,\r
37         CiShipName = 65,\r
38         Max = int.MaxValue\r
39     }\r
40 \r
41     public class ShipLabels\r
42     {\r
43         private readonly ShipLabel[][] _shiplabels = new ShipLabel[ShipInfo.MemberCount][];\r
44         private readonly ShipLabel[][] _shipLabels7 = new ShipLabel[7][];\r
45         private readonly ShipLabel[][] _combinedLabels = new ShipLabel[ShipInfo.MemberCount * 2][];\r
46         private readonly ShipLabel[] _akashiTimers = new ShipLabel[ShipInfo.MemberCount];\r
47         private readonly ShipLabel[] _akashiTimers7 = new ShipLabel[ShipInfo.MemberCount];\r
48         private readonly ShipLabel[][] _ndockLabels = new ShipLabel[DockInfo.DockCount][];\r
49         public static Color[] ColumnColors = {SystemColors.Control, Color.FromArgb(255, 250, 250, 250)};\r
50         private readonly List<ShipLabel> _hpLables = new List<ShipLabel>();\r
51         public bool ShowHpInPercent { get; private set; }\r
52 \r
53         public void CreateShipLabels(Control parent, EventHandler onClick)\r
54         {\r
55             CreateShipLabels(parent, onClick, _shiplabels, 16);\r
56         }\r
57 \r
58         public void CreateShipLabels7(Control parent, EventHandler onClick)\r
59         {\r
60             CreateShipLabels(parent, onClick, _shipLabels7, 14);\r
61         }\r
62 \r
63         public void CreateShipLabels(Control parent, EventHandler onClick, ShipLabel[][] shipLabels, int lineHeight)\r
64         {\r
65             parent.SuspendLayout();\r
66             const int top = 3, height = 12;\r
67             ShipLabel[] headings;\r
68             parent.Controls.AddRange(headings = new[]\r
69             {\r
70                 new ShipLabel {Location = new Point(109, top), Text = "HP", AutoSize = true},\r
71                 new ShipLabel {Location = new Point(128, top), Text = "cond", AutoSize = true},\r
72                 new ShipLabel {Location = new Point(162, top), Text = "Lv", AutoSize = true},\r
73                 new ShipLabel {Location = new Point(194, top), Text = "Exp", AutoSize = true},\r
74                 new ShipLabel {Location = new Point(0, 1), Size = new Size(parent.Width, lineHeight - 1)}\r
75             });\r
76             foreach (var label in headings)\r
77             {\r
78                 label.Scale();\r
79                 label.BackColor = ColumnColors[1];\r
80             }\r
81             for (var i = 0; i < shipLabels.Length; i++)\r
82             {\r
83                 var y = top + lineHeight * (i + 1);\r
84                 parent.Controls.AddRange(shipLabels[i] = new[]\r
85                 {\r
86                     new ShipLabel {Location = new Point(129, y), AutoSize = true, AnchorRight = true},\r
87                     new ShipLabel\r
88                     {\r
89                         Location = new Point(131, y),\r
90                         Size = new Size(24, height),\r
91                         TextAlign = ContentAlignment.MiddleRight\r
92                     },\r
93                     new ShipLabel\r
94                     {\r
95                         Location = new Point(155, y),\r
96                         Size = new Size(24, height),\r
97                         TextAlign = ContentAlignment.MiddleRight\r
98                     },\r
99                     new ShipLabel\r
100                     {\r
101                         Location = new Point(176, y),\r
102                         Size = new Size(42, height),\r
103                         TextAlign = ContentAlignment.MiddleRight\r
104                     },\r
105                     new ShipLabel {Location = new Point(2, y), AutoSize = true}, // 名前のZ-orderを下に\r
106                     new ShipLabel {Location = new Point(0, y - 2), Size = new Size(parent.Width, lineHeight - 1)}\r
107                 });\r
108                 foreach (var label in shipLabels[i])\r
109                 {\r
110                     label.Scale();\r
111                     label.PresetColor = label.BackColor = ColumnColors[i % 2];\r
112                     label.Tag = i;\r
113                     label.Click += onClick;\r
114                 }\r
115             }\r
116             _hpLables.AddRange(shipLabels.Select(labels => labels[0]));\r
117             headings[0].Cursor = Cursors.Hand;\r
118             headings[0].Click += (sender, ev) => ToggleHpPercent();\r
119             parent.ResumeLayout();\r
120         }\r
121 \r
122         public void ToggleHpPercent()\r
123         {\r
124             ShowHpInPercent = !ShowHpInPercent;\r
125             foreach (var label in _hpLables)\r
126                 label.ToggleHpPercent();\r
127         }\r
128 \r
129         public void SetShipLabels(ShipStatus[] statuses)\r
130         {\r
131             SetShipLabels(statuses, statuses.Length == 7 ? _shipLabels7 : _shiplabels);\r
132         }\r
133 \r
134         public void SetShipLabels(ShipStatus[] statuses, ShipLabel[][] shipLabels)\r
135         {\r
136             for (var i = 0; i < shipLabels.Length; i++)\r
137             {\r
138                 var labels = shipLabels[i];\r
139                 var s = i < statuses.Length ? statuses[i] : null;\r
140                 labels[0].SetHp(s);\r
141                 labels[1].SetCond(s);\r
142                 labels[2].SetLevel(s);\r
143                 labels[3].SetExpToNext(s);\r
144                 labels[4].SetName(s, ShipNameWidth.MainPanel);\r
145             }\r
146         }\r
147 \r
148         public void CreateCombinedShipLabels(Control parent, EventHandler onClick)\r
149         {\r
150             parent.SuspendLayout();\r
151             const int top = 3, height = 12, lh = 16;\r
152             const int parentWidth = 220; // parent.Widthを使うとDPIスケーリング時に計算がくるうので\r
153             ShipLabel[] headings;\r
154             parent.Controls.AddRange(headings = new[]\r
155             {\r
156                 new ShipLabel {Location = new Point(68, top), Text = "HP", AutoSize = true},\r
157                 new ShipLabel {Location = new Point(86, top), Text = "cnd", AutoSize = true},\r
158                 new ShipLabel {Location = new Point(177, top), Text = "HP", AutoSize = true},\r
159                 new ShipLabel {Location = new Point(195, top), Text = "cnd", AutoSize = true},\r
160                 new ShipLabel {Location = new Point(0, 1), Size = new Size(parentWidth, lh - 1)}\r
161             });\r
162             foreach (var label in headings)\r
163             {\r
164                 label.Scale();\r
165                 label.BackColor = ColumnColors[1];\r
166             }\r
167             for (var i = 0; i < _combinedLabels.Length; i++)\r
168             {\r
169                 var x = parentWidth / 2 * (i / ShipInfo.MemberCount);\r
170                 var y = top + lh * (i % ShipInfo.MemberCount + 1);\r
171                 parent.Controls.AddRange(_combinedLabels[i] = new[]\r
172                 {\r
173                     new ShipLabel {Location = new Point(x + 88, y), AutoSize = true, AnchorRight = true},\r
174                     new ShipLabel\r
175                     {\r
176                         Location = new Point(x + 85, y),\r
177                         Size = new Size(24, height),\r
178                         TextAlign = ContentAlignment.MiddleRight\r
179                     },\r
180                     new ShipLabel {Location = new Point(x + 2, y), AutoSize = true}, // 名前のZ-orderを下に\r
181                     new ShipLabel {Location = new Point(x, y - 2), Size = new Size(parentWidth / 2, lh - 1)}\r
182                 });\r
183                 foreach (var label in _combinedLabels[i])\r
184                 {\r
185                     label.Scale();\r
186                     label.PresetColor = label.BackColor = ColumnColors[i % 2];\r
187                     label.Tag = i;\r
188                     label.Click += onClick;\r
189                 }\r
190             }\r
191             _hpLables.AddRange(_combinedLabels.Select(record => record[0]).ToArray());\r
192             headings[0].Cursor = headings[2].Cursor = Cursors.Hand;\r
193             void HpToggle(object sender, EventArgs ev)\r
194             {\r
195                 foreach (var label in _hpLables)\r
196                     label.ToggleHpPercent();\r
197             }\r
198             headings[0].Click += HpToggle;\r
199             headings[2].Click += HpToggle;\r
200             parent.ResumeLayout();\r
201         }\r
202 \r
203         public void SetCombinedShipLabels(ShipStatus[] first, ShipStatus[] second)\r
204         {\r
205             for (var i = 0; i < _combinedLabels.Length; i++)\r
206             {\r
207                 var idx = i % ShipInfo.MemberCount;\r
208                 var statuses = i < ShipInfo.MemberCount ? first : second;\r
209                 var labels = _combinedLabels[i];\r
210                 var s = idx < statuses.Length ? statuses[idx] : null;\r
211                 labels[0].SetHp(s);\r
212                 labels[1].SetCond(s);\r
213                 labels[2].SetName(s, ShipNameWidth.Combined);\r
214             }\r
215         }\r
216 \r
217         public void CreateAkashiTimers(Control parent)\r
218         {\r
219             CreateAkashiTimers(parent, _akashiTimers, 16);\r
220         }\r
221 \r
222         public void CreateAkashiTimers7(Control parent)\r
223         {\r
224             CreateAkashiTimers(parent, _akashiTimers7, 14);\r
225         }\r
226 \r
227         public void CreateAkashiTimers(Control parent, ShipLabel[] timerLabels, int lineHeight)\r
228         {\r
229             parent.SuspendLayout();\r
230             for (var i = 0; i < timerLabels.Length; i++)\r
231             {\r
232                 const int x = 55;\r
233                 var y = 3 + lineHeight * (i + 1);\r
234                 ShipLabel label;\r
235                 parent.Controls.Add(\r
236                     label = timerLabels[i] =\r
237                         new ShipLabel\r
238                         {\r
239                             Location = new Point(x, y),\r
240                             Size = new Size(31, 12),\r
241                             TextAlign = ContentAlignment.TopRight\r
242                         });\r
243                 label.BackColor = ColumnColors[i % 2];\r
244             }\r
245             foreach (var label in timerLabels)\r
246                 label.Scale();\r
247             parent.ResumeLayout();\r
248         }\r
249 \r
250         public void AdjustAkashiTimers()\r
251         {\r
252             AdjustAkashiTimers(_akashiTimers, 16);\r
253             AdjustAkashiTimers(_akashiTimers7, 14);\r
254         }\r
255 \r
256         public void AdjustAkashiTimers(ShipLabel[] timers, int lineHeight)\r
257         {\r
258             var scale = ShipLabel.ScaleFactor;\r
259             if (scale.Height < 1.2)\r
260                 return;\r
261             for (var i = 0; i < timers.Length; i++)\r
262             {\r
263                 const int x = 55;\r
264                 var y = 3 + lineHeight * (i + 1);\r
265                 timers[i].Location = new Point((int)Round(x * scale.Width) - 3, (int)Round(y * scale.Height));\r
266                 timers[i].Size = new Size((int)Round(31 * scale.Width) + 1, (int)Round(12 * scale.Height));\r
267             }\r
268         }\r
269 \r
270         public void SetAkashiTimer(ShipStatus[] statuses, AkashiTimer.RepairSpan[] timers)\r
271         {\r
272             if (statuses.Length == 7)\r
273             {\r
274                 SetAkashiTimer(statuses, timers, _akashiTimers7, _shipLabels7);\r
275             }\r
276             else\r
277             {\r
278 \r
279                 SetAkashiTimer(statuses, timers, _akashiTimers, _shiplabels);\r
280             }\r
281         }\r
282 \r
283         public void SetAkashiTimer(ShipStatus[] statuses, AkashiTimer.RepairSpan[] timers, ShipLabel[] timerLabels, ShipLabel[][] shipLabels)\r
284         {\r
285             var shortest = -1;\r
286             for (var i = 0; i < timers.Length; i++)\r
287             {\r
288                 if (timers[i].Span <= TimeSpan.Zero)\r
289                     continue;\r
290                 if (shortest == -1 || timers[i].Span < timers[shortest].Span)\r
291                     shortest = i;\r
292             }\r
293             for (var i = 0; i < timerLabels.Length; i++)\r
294             {\r
295                 var label = timerLabels[i];\r
296                 var labelHp = shipLabels[i][0];\r
297                 var labelName = shipLabels[i][4];\r
298                 if (i >= timers.Length || timers[i].Span == TimeSpan.MinValue)\r
299                 {\r
300                     label.Visible = false;\r
301                     labelHp.ForeColor = Control.DefaultForeColor;\r
302                     continue;\r
303                 }\r
304                 var timer = timers[i];\r
305                 var stat = statuses[i];\r
306                 label.Visible = true;\r
307                 label.Text = timer.Span.ToString(@"mm\:ss");\r
308                 label.ForeColor = Control.DefaultForeColor;\r
309                 labelName.SetName(stat, ShipNameWidth.AkashiTimer);\r
310                 if (timer.Diff == 0)\r
311                 {\r
312                     labelHp.ForeColor = Control.DefaultForeColor;\r
313                     continue;\r
314                 }\r
315                 if (i == shortest)\r
316                     label.ForeColor = CUDColor.Red;\r
317                 labelHp.ForeColor = Color.DimGray;\r
318                 labelHp.SetHp(stat.NowHp + timer.Diff, stat.MaxHp);\r
319             }\r
320         }\r
321 \r
322         public void CreateNDockLabels(Control parent, EventHandler onClick)\r
323         {\r
324             for (var i = 0; i < _ndockLabels.Length; i++)\r
325             {\r
326                 var y = 3 + i * 15;\r
327                 parent.Controls.AddRange(\r
328                     _ndockLabels[i] = new[]\r
329                     {\r
330                         new ShipLabel\r
331                         {\r
332                             Location = new Point(138, y),\r
333                             AutoSize = true,\r
334                             AnchorRight = true\r
335                         },\r
336                         new ShipLabel {Location = new Point(29, y), AutoSize = true} // 名前のZ-orderを下に\r
337                     });\r
338                 foreach (var label in _ndockLabels[i])\r
339                 {\r
340                     label.Scale();\r
341                     label.Click += onClick;\r
342                 }\r
343             }\r
344         }\r
345 \r
346         public void SetNDockLabels(NameAndTimer[] ndock)\r
347         {\r
348             for (var i = 0; i < _ndockLabels.Length; i++)\r
349                 _ndockLabels[i][1].SetName(ndock[i].Name, ShipNameWidth.NDock);\r
350         }\r
351 \r
352         public void SetNDockTimer(int dock, AlarmTimer timer, DateTime now, bool finishTime)\r
353         {\r
354             var label = _ndockLabels[dock][0];\r
355             label.ForeColor = timer.IsFinished(now) ? CUDColor.Red : Color.Black;\r
356             label.Text = timer.ToString(now, finishTime);\r
357         }\r
358     }\r
359 \r
360     [DesignerCategory("Code")]\r
361     public class ShipLabel : Label\r
362     {\r
363         public static SizeF ScaleFactor { get; set; }\r
364         public static Font LatinFont { get; set; } = new Font("Tahoma", 8f);\r
365         public Color PresetColor { get; set; }\r
366         public bool AnchorRight { get; set; }\r
367         private int _right = int.MinValue;\r
368         private int _left;\r
369         private SlotStatus _slotStatus;\r
370         private ShipStatus _status;\r
371         private bool _hpPercent;\r
372 \r
373         public override Color BackColor\r
374         {\r
375             get => base.BackColor;\r
376             set => base.BackColor = value == DefaultBackColor ? PresetColor : value;\r
377         }\r
378 \r
379         [Flags]\r
380         private enum SlotStatus\r
381         {\r
382             Equipped = 0,\r
383             NormalEmpty = 1,\r
384             ExtraEmpty = 2\r
385         }\r
386 \r
387         public ShipLabel()\r
388         {\r
389             UseMnemonic = false;\r
390         }\r
391 \r
392         public void SetName(ShipStatus status, ShipNameWidth width = ShipNameWidth.Max)\r
393         {\r
394             if (status == null)\r
395             {\r
396                 SetName("");\r
397                 return;\r
398             }\r
399             var empty = SlotStatus.Equipped;\r
400             if (status.Id != -1)\r
401             {\r
402                 if (status.Slot.All(item => item.Id == -1))\r
403                     empty |= SlotStatus.NormalEmpty;\r
404                 if (status.SlotEx.Id == -1)\r
405                     empty |= SlotStatus.ExtraEmpty;\r
406             }\r
407             var dc = status.PreparedDamageControl;\r
408             var dcname = dc == 42 ? "[ダ]" : dc == 43 ? "[メ]" : "";\r
409             SetName(status.Escaped ? "[避]" : dcname, status.Name, empty, width);\r
410         }\r
411 \r
412         public void SetName(string name)\r
413         {\r
414             SetName("", name, SlotStatus.Equipped);\r
415         }\r
416 \r
417         public void SetName(string name, ShipNameWidth width)\r
418         {\r
419             SetName("", name, SlotStatus.Equipped, width);\r
420         }\r
421 \r
422         private void SetName(string prefix, string name, SlotStatus slotStatus, ShipNameWidth width = ShipNameWidth.Max)\r
423         {\r
424             if (name == null)\r
425                 name = "";\r
426             _slotStatus = slotStatus;\r
427             var lu = new Regex(@"^\p{Lu}").IsMatch(name);\r
428             var shift = (int)Round(ScaleFactor.Height);\r
429             if (lu && Font.Equals(Parent.Font))\r
430             {\r
431                 Location += new Size(0, -shift);\r
432                 Font = LatinFont;\r
433             }\r
434             else if (!lu && !Font.Equals(Parent.Font))\r
435             {\r
436                 Location += new Size(0, shift);\r
437                 Font = Parent.Font;\r
438             }\r
439             var result = prefix + name;\r
440             var measured = TextRenderer.MeasureText(result, Font).Width;\r
441             if (measured <= (int)width)\r
442             {\r
443                 Text = result;\r
444                 Invalidate(); // 必ずOnPaintを実行させるため\r
445                 return;\r
446             }\r
447             var truncated = "";\r
448             foreach (var ch in name)\r
449             {\r
450                 var tmp = truncated + ch;\r
451                 if (TextRenderer.MeasureText(tmp, Font).Width > (int)width * ScaleFactor.Width)\r
452                     break;\r
453                 truncated = tmp;\r
454             }\r
455             Text = prefix + truncated.TrimEnd(' ');\r
456             Invalidate();\r
457         }\r
458 \r
459         public void SetHp(ShipStatus status)\r
460         {\r
461             _status = status;\r
462             if (status == null)\r
463             {\r
464                 Text = "";\r
465                 BackColor = PresetColor;\r
466                 return;\r
467             }\r
468             Text = _hpPercent\r
469                 ? $"{(int)Ceiling(status.NowHp * 100.0 / status.MaxHp):D}%"\r
470                 : $"{status.NowHp:D}/{status.MaxHp:D}";\r
471             BackColor = DamageColor(status, PresetColor);\r
472         }\r
473 \r
474         public void ToggleHpPercent()\r
475         {\r
476             _hpPercent = !_hpPercent;\r
477             SetHp(_status);\r
478         }\r
479 \r
480         public void SetHp(int now, int max)\r
481         {\r
482             SetHp(new ShipStatus {NowHp = now, MaxHp = max});\r
483         }\r
484 \r
485         public static Color DamageColor(ShipStatus status, Color backcolor)\r
486         {\r
487             switch (status.DamageLevel)\r
488             {\r
489                 case ShipStatus.Damage.Sunk:\r
490                     return Color.CornflowerBlue;\r
491                 case ShipStatus.Damage.Badly:\r
492                     return CUDColor.Red;\r
493                 case ShipStatus.Damage.Half:\r
494                     return CUDColor.Orange;\r
495                 case ShipStatus.Damage.Small:\r
496                     return Color.FromArgb(240, 240, 0);\r
497                 default:\r
498                     return backcolor;\r
499             }\r
500         }\r
501 \r
502         public void SetCond(ShipStatus status)\r
503         {\r
504             if (status == null)\r
505             {\r
506                 Text = "";\r
507                 BackColor = PresetColor;\r
508                 return;\r
509             }\r
510             var cond = status.Cond;\r
511             Text = cond.ToString("D");\r
512             BackColor = cond >= 50\r
513                 ? CUDColor.Yellow\r
514                 : cond >= 30\r
515                     ? PresetColor\r
516                     : cond >= 20\r
517                         ? CUDColor.Orange\r
518                         : CUDColor.Red;\r
519         }\r
520 \r
521         public void SetLevel(ShipStatus status)\r
522         {\r
523             Text = status?.Level.ToString("D");\r
524         }\r
525 \r
526         public void SetExpToNext(ShipStatus status)\r
527         {\r
528             Text = status?.ExpToNext.ToString("D");\r
529         }\r
530 \r
531         public void SetRepairTime(ShipStatus status)\r
532         {\r
533             if (status == null)\r
534             {\r
535                 Text = "";\r
536                 return;\r
537             }\r
538             SetRepairTime(status.RepairTime);\r
539         }\r
540 \r
541         public void SetRepairTime(TimeSpan span)\r
542         {\r
543             Text = $@"{(int)span.TotalHours:d2}:{span:mm\:ss}";\r
544         }\r
545 \r
546         public void SetFleet(ShipStatus status)\r
547         {\r
548             Text = status == null ? "" : new[] {"", "1", "2", "3", "4"}[status.Fleet + 1];\r
549         }\r
550 \r
551         protected override void OnLayout(LayoutEventArgs levent)\r
552         {\r
553             base.OnLayout(levent);\r
554             if (!AnchorRight)\r
555                 return;\r
556             if (_right == int.MinValue || _left != Left)\r
557             {\r
558                 _right = Right;\r
559                 _left = Left;\r
560                 return;\r
561             }\r
562             if (_right == Right)\r
563                 return;\r
564             _left -= Right - _right;\r
565             Location = new Point(_left, Top);\r
566         }\r
567 \r
568         protected override void OnPaint(PaintEventArgs e)\r
569         {\r
570             base.OnPaint(e);\r
571             if ((_slotStatus & SlotStatus.NormalEmpty) != 0)\r
572             {\r
573                 e.Graphics.DrawRectangle(\r
574                     Pens.Black,\r
575                     ClientSize.Width - 3 * ScaleFactor.Width, 1 * ScaleFactor.Height,\r
576                     2 * ScaleFactor.Width, 4 * ScaleFactor.Height);\r
577             }\r
578             if ((_slotStatus & SlotStatus.ExtraEmpty) != 0)\r
579             {\r
580                 e.Graphics.DrawRectangle(\r
581                     Pens.Black,\r
582                     ClientSize.Width - 3 * ScaleFactor.Width, 7 * ScaleFactor.Height,\r
583                     2 * ScaleFactor.Width, 3 * ScaleFactor.Height);\r
584             }\r
585         }\r
586 \r
587         public void Scale()\r
588         {\r
589             Scale(ScaleFactor);\r
590         }\r
591     }\r
592 }