OSDN Git Service

ee358412a55f6d4151b7cf1457f352c45c712abb
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / View / ShipListPanel.cs
1 // Copyright (C) 2016 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.Drawing;\r
18 using System.Linq;\r
19 using System.Runtime.InteropServices;\r
20 using System.Windows.Forms;\r
21 using KancolleSniffer.Model;\r
22 using static System.Math;\r
23 \r
24 namespace KancolleSniffer.View\r
25 {\r
26     public class ShipListPanel : Panel\r
27     {\r
28         private const int LabelHeight = 12;\r
29         public const int LineHeight = 16;\r
30         private ShipStatus[] _shipList;\r
31         private readonly List<ShipLabel[]> _labelList = new List<ShipLabel[]>();\r
32         private readonly List<Panel> _labelPanelList = new List<Panel>();\r
33         private readonly List<CheckBox[]> _checkBoxesList = new List<CheckBox[]>();\r
34         private readonly List<ShipLabel[]> _groupingLabelList = new List<ShipLabel[]>();\r
35         private readonly List<Panel> _groupingPanelList = new List<Panel>();\r
36         private readonly List<ShipLabel[]> _repairLabelList = new List<ShipLabel[]>();\r
37         private readonly List<Panel> _repairPanelList = new List<Panel>();\r
38         private readonly List<ShipLabel> _hpLabels = new List<ShipLabel>();\r
39         private string _mode;\r
40         private bool _hpPercent;\r
41 \r
42         public const int GroupCount = 4;\r
43         public HashSet<int>[] GroupSettings { get; } = new HashSet<int>[GroupCount];\r
44         public bool GroupUpdated { get; set; }\r
45 \r
46         public ScrollBar ScrollBar { get; }\r
47 \r
48         public ShipListPanel()\r
49         {\r
50             ScrollBar = new VScrollBar {Dock = DockStyle.Right, Visible = false};\r
51             ScrollBar.ValueChanged += ScrollBarOnValueChanged;\r
52             Controls.Add(ScrollBar);\r
53         }\r
54 \r
55         private void ScrollBarOnValueChanged(object sender, EventArgs eventArgs)\r
56         {\r
57             SuspendDrawing();\r
58             SetShipLabels();\r
59             ResumeDrawing();\r
60         }\r
61 \r
62         protected override void OnResize(EventArgs ev)\r
63         {\r
64             base.OnResize(ev);\r
65             if (_shipList == null || _shipList.Length == 0 || !Visible)\r
66                 return;\r
67             SuspendDrawing();\r
68             SetupLabels();\r
69             SetShipLabels();\r
70             ResumeDrawing();\r
71         }\r
72 \r
73         protected override void OnMouseWheel(MouseEventArgs e)\r
74         {\r
75             if (!ScrollBar.Visible)\r
76                 return;\r
77             ScrollBar.Value = Max(ScrollBar.Minimum, Min(ScrollBar.Maximum - ScrollBar.LargeChange + 1,\r
78                 ScrollBar.Value - e.Delta * SystemInformation.MouseWheelScrollLines / 120));\r
79         }\r
80 \r
81         public void Update(Sniffer sniffer, string mode, ShipListConfig config)\r
82         {\r
83             _mode = mode;\r
84             CreateShipList(sniffer, config);\r
85             SuspendDrawing();\r
86             SetupLabels();\r
87             SetShipLabels();\r
88             ResumeDrawing();\r
89         }\r
90 \r
91         [DllImport("user32.dll")]\r
92         public static extern int SendMessage(IntPtr hWnd, int wMsg, bool wParam, IntPtr lParam);\r
93 \r
94         private void SuspendDrawing()\r
95         {\r
96             SendMessage(Handle, 11, false, IntPtr.Zero); // WM_SETREDRAW = 11\r
97             SuspendLayout();\r
98         }\r
99 \r
100         public void ResumeDrawing()\r
101         {\r
102             ResumeLayout();\r
103             SendMessage(Handle, 11, true, IntPtr.Zero);\r
104             Refresh();\r
105         }\r
106 \r
107         private void CreateShipList(Sniffer sniffer, ShipListConfig config)\r
108         {\r
109             var ships = FilterByShipTypes(\r
110                 _mode == "修復" ? sniffer.RepairList : FilterByGroup(sniffer.ShipList, _mode),\r
111                 config.ShipCategories).ToArray();\r
112             var order = _mode == "修復" ? ListForm.SortOrder.Repair : config.SortOrder;\r
113             if (!config.ShipType)\r
114             {\r
115                 _shipList = ships.OrderBy(s => s, new CompareShip(false, order)).ToArray();\r
116                 return;\r
117             }\r
118             _shipList = ships.Select(ship => new {Id = ship.Spec.ShipType, Name = ship.Spec.ShipTypeName})\r
119                 .Distinct().Select(type => new ShipStatus\r
120                 {\r
121                     Spec = new ShipSpec { Name = type.Name, ShipType = type.Id},\r
122                     Level = 1000,\r
123                 }).Concat(ships).OrderBy(ship => ship, new CompareShip(true, order)).ToArray();\r
124         }\r
125 \r
126         private IEnumerable<ShipStatus> FilterByGroup(IEnumerable<ShipStatus> ships, string group)\r
127         {\r
128             var g = Array.FindIndex(new[] {"A", "B", "C", "D"}, x => x == group);\r
129             if (g == -1)\r
130                 return ships;\r
131             return from s in ships where GroupSettings[g].Contains(s.Id) select s;\r
132         }\r
133 \r
134         private static readonly int[][] ShipTypeIds =\r
135         {\r
136             new[] // 戦艦\r
137             {\r
138                 8, // 巡洋戦艦\r
139                 9, // 戦艦\r
140                 10 // 航空戦艦\r
141             },\r
142             new[] // 空母\r
143             {\r
144                 18, // 装甲空母\r
145                 11, // 正規空母\r
146                 7 // 軽空母\r
147             },\r
148             new[] // 重巡\r
149             {\r
150                 5, // 重巡洋艦\r
151                 6 // 航空巡洋艦\r
152             },\r
153             new[] // 軽巡\r
154             {\r
155                 3, // 軽巡洋艦\r
156                 4, // 重雷装巡洋艦\r
157                 21 // 練習巡洋艦\r
158             },\r
159             new[] // 駆逐\r
160             {\r
161                 2 // 駆逐艦\r
162             },\r
163             new[] // 海防\r
164             {\r
165                 1 // 海防艦\r
166             },\r
167             new[] // 潜水\r
168             {\r
169                 13, // 潜水艦\r
170                 14 // 潜水空母\r
171             },\r
172             new[] // 補助\r
173             {\r
174                 16, // 水上機母艦\r
175                 17, // 揚陸艦\r
176                 19, // 工作艦\r
177                 20, // 潜水母艦\r
178                 22 // 補給艦\r
179             }\r
180         };\r
181 \r
182         private static readonly int[] ShipTypeSortIds = CreateShipTypeSortIds();\r
183 \r
184         private static int[] CreateShipTypeSortIds()\r
185         {\r
186             var ids = ShipTypeIds.SelectMany(x => x).ToArray();\r
187             var res = new int[ids.Max() + 1];\r
188             for (var i = 0; i < ids.Length; i++)\r
189                 res[ids[i]] = i;\r
190             return res;\r
191         }\r
192 \r
193         private IEnumerable<ShipStatus> FilterByShipTypes(IEnumerable<ShipStatus> ships, ShipCategory shipTypes)\r
194         {\r
195             var ids = Enumerable.Range(0, ShipTypeIds.Length)\r
196                 .Where(type => ((int)shipTypes & (1 << type)) != 0)\r
197                 .SelectMany(type => ShipTypeIds[type]).ToArray();\r
198             return ships.Where(ship => ids.Contains(ship.Spec.ShipType));\r
199         }\r
200 \r
201         public IEnumerable<ShipStatus> CurrentShipList => _shipList.Where(ship => ship.Level != 1000);\r
202 \r
203         private class CompareShip : IComparer<ShipStatus>\r
204         {\r
205             private readonly bool _shipType;\r
206             private readonly ListForm.SortOrder _order;\r
207 \r
208             public CompareShip(bool type, ListForm.SortOrder order)\r
209             {\r
210                 _shipType = type;\r
211                 _order = order;\r
212             }\r
213 \r
214             public int Compare(ShipStatus a, ShipStatus b)\r
215             {\r
216                 if (a == null || b == null)\r
217                     throw new ArgumentNullException();\r
218                 if (_shipType)\r
219                 {\r
220                     if (a.Spec.ShipType != b.Spec.ShipType)\r
221                         return ShipTypeSortIds[a.Spec.ShipType] - ShipTypeSortIds[b.Spec.ShipType];\r
222                     if (a.Level != b.Level)\r
223                     {\r
224                         if (a.Level == 1000)\r
225                             return -1;\r
226                         if (b.Level == 1000)\r
227                             return 1;\r
228                     }\r
229                 }\r
230                 if (_order == ListForm.SortOrder.Repair && a.RepairTime != b.RepairTime)\r
231                     return (int)(b.RepairTime - a.RepairTime).TotalSeconds;\r
232                 if (a.Cond != b.Cond)\r
233                 {\r
234                     if (_order == ListForm.SortOrder.CondAscend)\r
235                         return a.Cond - b.Cond;\r
236                     if (_order == ListForm.SortOrder.CondDescend)\r
237                         return b.Cond - a.Cond;\r
238                 }\r
239                 if (a.Level != b.Level)\r
240                 {\r
241                     if (_order == ListForm.SortOrder.ExpToNextAscend)\r
242                         return b.Level - a.Level;\r
243                     if (_order == ListForm.SortOrder.ExpToNextDescend)\r
244                         return a.Level - b.Level;\r
245                     if (!_shipType) // Condが同じかSortOrder.Noneで艦種なし\r
246                         return b.Level - a.Level;\r
247                 }\r
248                 if (a.ExpToNext != b.ExpToNext)\r
249                 {\r
250                     if (_order == ListForm.SortOrder.ExpToNextAscend)\r
251                         return a.ExpToNext - b.ExpToNext;\r
252                     if (_order == ListForm.SortOrder.ExpToNextDescend)\r
253                         return b.ExpToNext - a.ExpToNext;\r
254                 }\r
255                 if (a.Spec.SortId != b.Spec.SortId)\r
256                     return a.Spec.SortId - b.Spec.SortId;\r
257                 return a.Id - b.Id;\r
258             }\r
259         }\r
260 \r
261         private void SetupLabels()\r
262         {\r
263             for (var i = _labelList.Count; i * LineHeight < Height; i++)\r
264             {\r
265                 CreateGroupingComponents(i);\r
266                 CreateRepairLabels(i);\r
267                 CreateShipLabels(i);\r
268             }\r
269             for (var i = 0; i * LineHeight < Height; i++)\r
270             {\r
271                 _labelPanelList[i].Visible = InShipStatus(_mode);\r
272                 _groupingPanelList[i].Visible = _mode == "分類";\r
273                 _repairPanelList[i].Visible = _mode == "修復";\r
274             }\r
275             SetupScrollBar();\r
276         }\r
277 \r
278         private void SetupScrollBar()\r
279         {\r
280             var needBar = _shipList.Length * LineHeight * ShipLabel.ScaleFactor.Height > Height;\r
281             if (!needBar)\r
282             {\r
283                 ScrollBar.Visible = false;\r
284                 ScrollBar.Value = 0;\r
285                 return;\r
286             }\r
287             ScrollBar.Visible = true;\r
288             ScrollBar.Minimum = 0;\r
289             var lines = Max(1, Height / (int)Round(LineHeight * ShipLabel.ScaleFactor.Height));\r
290             var max = _shipList.Length - lines;\r
291             var largeChange = Min(lines, max);\r
292             ScrollBar.LargeChange = largeChange;\r
293             ScrollBar.Maximum = Max(0, max + largeChange - 1); // ScrollBarを最大まで動かしてもmaxには届かない\r
294             ScrollBar.Value = Min(ScrollBar.Value, max);\r
295         }\r
296 \r
297         private void CreateGroupingComponents(int i)\r
298         {\r
299             var y = LineHeight * i + 1;\r
300             var panel = new Panel\r
301             {\r
302                 Location = new Point(0, y),\r
303                 Size = new Size(ListForm.PanelWidth, LineHeight),\r
304                 BackColor = ShipLabel.ColumnColors[(i + 1) % 2]\r
305             };\r
306             panel.Scale(ShipLabel.ScaleFactor);\r
307             panel.Tag = panel.Location.Y;\r
308             var labels = new[]\r
309             {\r
310                 new ShipLabel\r
311                 {\r
312                     Location = new Point(90, 2),\r
313                     Size = new Size(24, LabelHeight),\r
314                     TextAlign = ContentAlignment.MiddleRight\r
315                 },\r
316                 new ShipLabel {Location = new Point(10, 2), AutoSize = true},\r
317                 new ShipLabel {Location = new Point(1, 2), AutoSize = true}\r
318             };\r
319 \r
320             var cb = new CheckBox[GroupCount];\r
321             for (var j = 0; j < cb.Length; j++)\r
322             {\r
323                 cb[j] = new CheckBox\r
324                 {\r
325                     Location = new Point(125 + j * 24, 2),\r
326                     FlatStyle = FlatStyle.Flat,\r
327                     Size = new Size(12, 11),\r
328                     Tag = i * 10 + j\r
329                 };\r
330                 cb[j].Scale(ShipLabel.ScaleFactor);\r
331                 cb[j].CheckedChanged += checkboxGroup_CheckedChanged;\r
332             }\r
333             _groupingLabelList.Add(labels);\r
334             _checkBoxesList.Add(cb);\r
335             _groupingPanelList.Add(panel);\r
336             // ReSharper disable CoVariantArrayConversion\r
337             panel.Controls.AddRange(labels);\r
338             panel.Controls.AddRange(cb);\r
339             // ReSharper restore CoVariantArrayConversion\r
340             Controls.Add(panel);\r
341             foreach (var label in labels)\r
342             {\r
343                 label.Scale();\r
344                 label.PresetColor =\r
345                     label.BackColor = ShipLabel.ColumnColors[(i + 1) % 2];\r
346             }\r
347         }\r
348 \r
349         private void checkboxGroup_CheckedChanged(object sender, EventArgs e)\r
350         {\r
351             var cb = (CheckBox)sender;\r
352             var group = (int)cb.Tag % 10;\r
353             var idx = (int)cb.Tag / 10;\r
354             if (cb.Checked)\r
355             {\r
356                 GroupSettings[group].Add(_shipList[idx + ScrollBar.Value].Id);\r
357             }\r
358             else\r
359             {\r
360                 GroupSettings[group].Remove(_shipList[idx + ScrollBar.Value].Id);\r
361             }\r
362             GroupUpdated = true;\r
363         }\r
364 \r
365         private void CreateRepairLabels(int i)\r
366         {\r
367             var y = LineHeight * i + 1;\r
368             const int height = LabelHeight;\r
369             var panel = new Panel\r
370             {\r
371                 Location = new Point(0, y),\r
372                 Size = new Size(ListForm.PanelWidth, LineHeight),\r
373                 BackColor = ShipLabel.ColumnColors[(i + 1) % 2]\r
374             };\r
375             panel.Scale(ShipLabel.ScaleFactor);\r
376             panel.Tag = panel.Location.Y;\r
377             var labels = new[]\r
378             {\r
379                 new ShipLabel\r
380                 {\r
381                     Location = new Point(118, 0),\r
382                     AutoSize = true,\r
383                     AnchorRight = true,\r
384                     MinimumSize = new Size(0, LineHeight),\r
385                     TextAlign = ContentAlignment.MiddleLeft,\r
386                     Cursor = Cursors.Hand\r
387                 },\r
388                 new ShipLabel\r
389                 {\r
390                     Location = new Point(116, 2),\r
391                     Size = new Size(24, height),\r
392                     TextAlign = ContentAlignment.MiddleRight\r
393                 },\r
394                 new ShipLabel {Location = new Point(141, 2), AutoSize = true},\r
395                 new ShipLabel {Location = new Point(186, 2), AutoSize = true},\r
396                 new ShipLabel {Location = new Point(10, 2), AutoSize = true},\r
397                 new ShipLabel {Location = new Point(1, 2), AutoSize = true}\r
398             };\r
399             _repairLabelList.Add(labels);\r
400             _repairPanelList.Add(panel);\r
401             // ReSharper disable once CoVariantArrayConversion\r
402             panel.Controls.AddRange(labels);\r
403             Controls.Add(panel);\r
404             foreach (var label in labels)\r
405             {\r
406                 label.Scale();\r
407                 label.PresetColor =\r
408                     label.BackColor = ShipLabel.ColumnColors[(i + 1) % 2];\r
409             }\r
410             if (_hpPercent)\r
411                 labels[0].ToggleHpPercent();\r
412             _hpLabels.Add(labels[0]);\r
413             labels[0].DoubleClick += HpLabelClickHandler;\r
414         }\r
415 \r
416         private void CreateShipLabels(int i)\r
417         {\r
418             var y = LineHeight * i + 1;\r
419             const int height = LabelHeight;\r
420             var panel = new Panel\r
421             {\r
422                 Location = new Point(0, y),\r
423                 Size = new Size(ListForm.PanelWidth, LineHeight),\r
424                 BackColor = ShipLabel.ColumnColors[(i + 1) % 2]\r
425             };\r
426             panel.Scale(ShipLabel.ScaleFactor);\r
427             var labels = new[]\r
428             {\r
429                 new ShipLabel\r
430                 {\r
431                     Location = new Point(126, 0),\r
432                     AutoSize = true,\r
433                     AnchorRight = true,\r
434                     MinimumSize = new Size(0, LineHeight),\r
435                     TextAlign = ContentAlignment.MiddleLeft,\r
436                     Cursor = Cursors.Hand\r
437                 },\r
438                 new ShipLabel\r
439                 {\r
440                     Location = new Point(128, 0),\r
441                     Size = new Size(24, LineHeight),\r
442                     TextAlign = ContentAlignment.MiddleRight\r
443                 },\r
444                 new ShipLabel\r
445                 {\r
446                     Location = new Point(154, 2),\r
447                     Size = new Size(24, height),\r
448                     TextAlign = ContentAlignment.MiddleRight\r
449                 },\r
450                 new ShipLabel\r
451                 {\r
452                     Location = new Point(175, 2),\r
453                     Size = new Size(42, height),\r
454                     TextAlign = ContentAlignment.MiddleRight\r
455                 },\r
456                 new ShipLabel {Location = new Point(10, 2), AutoSize = true},\r
457                 new ShipLabel {Location = new Point(1, 2), AutoSize = true}\r
458             };\r
459             _labelList.Add(labels);\r
460             _labelPanelList.Add(panel);\r
461             // ReSharper disable once CoVariantArrayConversion\r
462             panel.Controls.AddRange(labels);\r
463             Controls.Add(panel);\r
464             foreach (var label in labels)\r
465             {\r
466                 label.Scale();\r
467                 label.PresetColor =\r
468                     label.BackColor = ShipLabel.ColumnColors[(i + 1) % 2];\r
469             }\r
470             if (_hpPercent)\r
471                 labels[0].ToggleHpPercent();\r
472             _hpLabels.Add(labels[0]);\r
473             labels[0].DoubleClick += HpLabelClickHandler;\r
474         }\r
475 \r
476         private void SetShipLabels()\r
477         {\r
478             for (var i = 0; i < (Height + LineHeight - 1) / LineHeight; i++)\r
479             {\r
480                 if (InShipStatus(_mode))\r
481                     SetShipStatus(i);\r
482                 if (_mode == "分類")\r
483                     SetGrouping(i);\r
484                 if (_mode == "修復")\r
485                     SetRepairList(i);\r
486             }\r
487         }\r
488 \r
489         private bool InShipStatus(string mode) => Array.Exists(new[] {"全艦", "A", "B", "C", "D"}, x => mode == x);\r
490 \r
491         private void SetShipStatus(int i)\r
492         {\r
493             var panel = _labelPanelList[i];\r
494             if (i + ScrollBar.Value >= _shipList.Length)\r
495             {\r
496                 panel.Visible = false;\r
497                 return;\r
498             }\r
499             var s = _shipList[i + ScrollBar.Value];\r
500             var labels = _labelList[i];\r
501             if (s.Level == 1000) // 艦種の表示\r
502             {\r
503                 SetShipType(i);\r
504                 return;\r
505             }\r
506             labels[0].SetHp(s);\r
507             labels[1].SetCond(s);\r
508             labels[2].SetLevel(s);\r
509             labels[3].SetExpToNext(s);\r
510             labels[4].SetName(s, ShipNameWidth.ShipList);\r
511             labels[5].SetFleet(s);\r
512             panel.Visible = true;\r
513         }\r
514 \r
515         private void SetShipType(int i)\r
516         {\r
517             var s = _shipList[i + ScrollBar.Value];\r
518             var labels = _labelList[i];\r
519             labels[0].SetHp(null);\r
520             labels[1].SetCond(null);\r
521             labels[2].SetLevel(null);\r
522             labels[3].SetExpToNext(null);\r
523             labels[4].SetName(null);\r
524             labels[5].SetFleet(null);\r
525             labels[5].Text = s.Name;\r
526             _labelPanelList[i].Visible = true;\r
527         }\r
528 \r
529         private void SetGrouping(int i)\r
530         {\r
531             var panel = _groupingPanelList[i];\r
532             if (i + ScrollBar.Value >= _shipList.Length)\r
533             {\r
534                 panel.Visible = false;\r
535                 _labelPanelList[i].Visible = false;\r
536                 return;\r
537             }\r
538             var s = _shipList[i + ScrollBar.Value];\r
539             var labels = _groupingLabelList[i];\r
540             if (s.Level == 1000)\r
541             {\r
542                 panel.Visible = false;\r
543                 SetShipType(i);\r
544                 return;\r
545             }\r
546             labels[0].SetLevel(s);\r
547             labels[1].SetName(s, ShipNameWidth.GroupConfig);\r
548             labels[2].SetFleet(s);\r
549             var cb = _checkBoxesList[i];\r
550             for (var j = 0; j < cb.Length; j++)\r
551                 cb[j].Checked = GroupSettings[j].Contains(s.Id);\r
552             panel.Visible = true;\r
553         }\r
554 \r
555         private void SetRepairList(int i)\r
556         {\r
557             var panel = _repairPanelList[i];\r
558             if (i + ScrollBar.Value >= _shipList.Length)\r
559             {\r
560                 panel.Visible = false;\r
561                 _labelPanelList[i].Visible = false;\r
562                 return;\r
563             }\r
564             var s = _shipList[i + ScrollBar.Value];\r
565             if (s.Level == 1000)\r
566             {\r
567                 panel.Visible = false;\r
568                 SetShipType(i);\r
569                 return;\r
570             }\r
571             var labels = _repairLabelList[i];\r
572             labels[0].SetHp(s);\r
573             labels[1].SetLevel(s);\r
574             labels[2].SetRepairTime(s);\r
575             labels[3].Text = s.RepairTimePerHp.ToString(@"mm\:ss");\r
576             labels[4].SetName(s, ShipNameWidth.RepairListFull);\r
577             labels[5].SetFleet(s);\r
578             panel.Visible = true;\r
579         }\r
580 \r
581         public event Action HpLabelClick;\r
582 \r
583         private void HpLabelClickHandler(object sender, EventArgs ev)\r
584         {\r
585             HpLabelClick?.Invoke();\r
586         }\r
587 \r
588         public void ToggleHpPercent()\r
589         {\r
590             _hpPercent = !_hpPercent;\r
591             foreach (var label in _hpLabels)\r
592                 label.ToggleHpPercent();\r
593         }\r
594 \r
595         public void ShowShip(int id)\r
596         {\r
597             var i = Array.FindIndex(_shipList, s => s.Id == id);\r
598             if (i == -1)\r
599                 return;\r
600             ScrollBar.Value = Min(i, ScrollBar.Maximum + 1 - ScrollBar.LargeChange);\r
601             SetShipLabels();\r
602         }\r
603     }\r
604 }