OSDN Git Service

Model、View、Net、Utilの名前空間にクラスを分類する
[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, ListForm.SortOrder sortOrder, bool byShipType)\r
82         {\r
83             _mode = mode;\r
84             CreateShipList(sniffer, sortOrder, byShipType);\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, ListForm.SortOrder sortOrder, bool byShipType)\r
108         {\r
109             var ships = _mode == "修復" ? sniffer.RepairList : FilterByGroup(sniffer.ShipList, _mode).ToArray();\r
110             var order = _mode == "修復" ? ListForm.SortOrder.Repair : sortOrder;\r
111             if (!byShipType)\r
112             {\r
113                 _shipList = ships.OrderBy(s => s, new CompareShip(false, order)).ToArray();\r
114                 return;\r
115             }\r
116             var types = ships.Select(s => new {Id = s.Spec.ShipType, Name = s.Spec.ShipTypeName})\r
117                 .Distinct()\r
118                 .Select(stype =>\r
119                     new ShipStatus\r
120                     {\r
121                         Spec = new ShipSpec {Name = stype.Name, ShipType = stype.Id},\r
122                         Level = 1000,\r
123                         NowHp = -1000,\r
124                         Cond = -1000\r
125                     });\r
126             _shipList = ships.Concat(types).OrderBy(s => s, new CompareShip(true, order)).ToArray();\r
127         }\r
128 \r
129         private IEnumerable<ShipStatus> FilterByGroup(IEnumerable<ShipStatus> ships, string group)\r
130         {\r
131             var g = Array.FindIndex(new[] {"A", "B", "C", "D"}, x => x == group);\r
132             if (g == -1)\r
133                 return ships;\r
134             return from s in ships where GroupSettings[g].Contains(s.Id) select s;\r
135         }\r
136 \r
137         public IEnumerable<ShipStatus> CurrentShipList => _shipList.Where(ship => ship.Level != 1000);\r
138 \r
139         private class CompareShip : IComparer<ShipStatus>\r
140         {\r
141             private readonly bool _shipType;\r
142             private readonly ListForm.SortOrder _order;\r
143 \r
144             public CompareShip(bool type, ListForm.SortOrder order)\r
145             {\r
146                 _shipType = type;\r
147                 _order = order;\r
148             }\r
149 \r
150             public int Compare(ShipStatus a, ShipStatus b)\r
151             {\r
152                 if (a == null || b == null)\r
153                     throw new ArgumentNullException();\r
154                 if (_shipType)\r
155                 {\r
156                     if (a.Spec.ShipType != b.Spec.ShipType)\r
157                         return a.Spec.ShipType - b.Spec.ShipType;\r
158                     if (a.Level != b.Level)\r
159                     {\r
160                         if (a.Level == 1000)\r
161                             return -1;\r
162                         if (b.Level == 1000)\r
163                             return 1;\r
164                     }\r
165                 }\r
166                 if (_order == ListForm.SortOrder.Repair && a.RepairTime != b.RepairTime)\r
167                     return (int)(b.RepairTime - a.RepairTime).TotalSeconds;\r
168                 if (a.Cond != b.Cond)\r
169                 {\r
170                     if (_order == ListForm.SortOrder.CondAscend)\r
171                         return a.Cond - b.Cond;\r
172                     if (_order == ListForm.SortOrder.CondDescend)\r
173                         return b.Cond - a.Cond;\r
174                 }\r
175                 if (a.Level != b.Level)\r
176                 {\r
177                     if (_order == ListForm.SortOrder.ExpToNextAscend)\r
178                         return b.Level - a.Level;\r
179                     if (_order == ListForm.SortOrder.ExpToNextDescend)\r
180                         return a.Level - b.Level;\r
181                     if (!_shipType) // Condが同じかSortOrder.Noneで艦種なし\r
182                         return b.Level - a.Level;\r
183                 }\r
184                 if (a.ExpToNext != b.ExpToNext)\r
185                 {\r
186                     if (_order == ListForm.SortOrder.ExpToNextAscend)\r
187                         return a.ExpToNext - b.ExpToNext;\r
188                     if (_order == ListForm.SortOrder.ExpToNextDescend)\r
189                         return b.ExpToNext - a.ExpToNext;\r
190                 }\r
191                 if (a.Spec.SortNo != b.Spec.SortNo)\r
192                     return a.Spec.SortNo - b.Spec.SortNo;\r
193                 return a.Id - b.Id;\r
194             }\r
195         }\r
196 \r
197         private void SetupLabels()\r
198         {\r
199             for (var i = _labelList.Count; i * LineHeight < Height; i++)\r
200             {\r
201                 CreateGroupingComponents(i);\r
202                 CreateRepairLabels(i);\r
203                 CreateShipLabels(i);\r
204             }\r
205             for (var i = 0; i * LineHeight < Height; i++)\r
206             {\r
207                 _labelPanelList[i].Visible = InShipStatus(_mode);\r
208                 _groupingPanelList[i].Visible = _mode == "分類";\r
209                 _repairPanelList[i].Visible = _mode == "修復";\r
210             }\r
211             SetupScrollBar();\r
212         }\r
213 \r
214         private void SetupScrollBar()\r
215         {\r
216             var needBar = _shipList.Length * LineHeight * ShipLabel.ScaleFactor.Height > Height;\r
217             if (!needBar)\r
218             {\r
219                 ScrollBar.Visible = false;\r
220                 ScrollBar.Value = 0;\r
221                 return;\r
222             }\r
223             ScrollBar.Visible = true;\r
224             ScrollBar.Minimum = 0;\r
225             var lines = Max(1, Height / (int)Round(LineHeight * ShipLabel.ScaleFactor.Height));\r
226             var max = _shipList.Length - lines;\r
227             var largeChange = Min(lines, max);\r
228             ScrollBar.LargeChange = largeChange;\r
229             ScrollBar.Maximum = Max(0, max + largeChange - 1); // ScrollBarを最大まで動かしてもmaxには届かない\r
230             ScrollBar.Value = Min(ScrollBar.Value, max);\r
231         }\r
232 \r
233         private void CreateGroupingComponents(int i)\r
234         {\r
235             var y = LineHeight * i + 1;\r
236             var cfgp = new Panel\r
237             {\r
238                 Location = new Point(0, y),\r
239                 Size = new Size(ListForm.PanelWidth, LineHeight),\r
240                 BackColor = ShipLabel.ColumnColors[(i + 1) % 2]\r
241             };\r
242             cfgp.Scale(ShipLabel.ScaleFactor);\r
243             cfgp.Tag = cfgp.Location.Y;\r
244             var cfgl = new[]\r
245             {\r
246                 new ShipLabel\r
247                 {\r
248                     Location = new Point(90, 2),\r
249                     Size = new Size(24, LabelHeight),\r
250                     TextAlign = ContentAlignment.MiddleRight\r
251                 },\r
252                 new ShipLabel {Location = new Point(10, 2), AutoSize = true},\r
253                 new ShipLabel {Location = new Point(1, 2), AutoSize = true}\r
254             };\r
255 \r
256             var cb = new CheckBox[GroupCount];\r
257             for (var j = 0; j < cb.Length; j++)\r
258             {\r
259                 cb[j] = new CheckBox\r
260                 {\r
261                     Location = new Point(125 + j * 24, 2),\r
262                     FlatStyle = FlatStyle.Flat,\r
263                     Size = new Size(12, 11),\r
264                     Tag = i * 10 + j\r
265                 };\r
266                 cb[j].Scale(ShipLabel.ScaleFactor);\r
267                 cb[j].CheckedChanged += checkboxGroup_CheckedChanged;\r
268             }\r
269             _groupingLabelList.Add(cfgl);\r
270             _checkBoxesList.Add(cb);\r
271             _groupingPanelList.Add(cfgp);\r
272             // ReSharper disable CoVariantArrayConversion\r
273             cfgp.Controls.AddRange(cfgl);\r
274             cfgp.Controls.AddRange(cb);\r
275             // ReSharper restore CoVariantArrayConversion\r
276             Controls.Add(cfgp);\r
277             foreach (var label in cfgl)\r
278             {\r
279                 label.Scale();\r
280                 label.PresetColor =\r
281                     label.BackColor = ShipLabel.ColumnColors[(i + 1) % 2];\r
282             }\r
283         }\r
284 \r
285         private void checkboxGroup_CheckedChanged(object sender, EventArgs e)\r
286         {\r
287             var cb = (CheckBox)sender;\r
288             var group = (int)cb.Tag % 10;\r
289             var idx = (int)cb.Tag / 10;\r
290             if (cb.Checked)\r
291             {\r
292                 GroupSettings[group].Add(_shipList[idx + ScrollBar.Value].Id);\r
293             }\r
294             else\r
295             {\r
296                 GroupSettings[group].Remove(_shipList[idx + ScrollBar.Value].Id);\r
297             }\r
298             GroupUpdated = true;\r
299         }\r
300 \r
301         private void CreateRepairLabels(int i)\r
302         {\r
303             var y = LineHeight * i + 1;\r
304             const int height = LabelHeight;\r
305             var rpp = new Panel\r
306             {\r
307                 Location = new Point(0, y),\r
308                 Size = new Size(ListForm.PanelWidth, LineHeight),\r
309                 BackColor = ShipLabel.ColumnColors[(i + 1) % 2]\r
310             };\r
311             rpp.Scale(ShipLabel.ScaleFactor);\r
312             rpp.Tag = rpp.Location.Y;\r
313             var rpl = new[]\r
314             {\r
315                 new ShipLabel\r
316                 {\r
317                     Location = new Point(118, 0),\r
318                     AutoSize = true,\r
319                     AnchorRight = true,\r
320                     MinimumSize = new Size(0, LineHeight),\r
321                     TextAlign = ContentAlignment.MiddleLeft,\r
322                     Cursor = Cursors.Hand\r
323                 },\r
324                 new ShipLabel\r
325                 {\r
326                     Location = new Point(116, 2),\r
327                     Size = new Size(24, height),\r
328                     TextAlign = ContentAlignment.MiddleRight\r
329                 },\r
330                 new ShipLabel {Location = new Point(141, 2), AutoSize = true},\r
331                 new ShipLabel {Location = new Point(186, 2), AutoSize = true},\r
332                 new ShipLabel {Location = new Point(10, 2), AutoSize = true},\r
333                 new ShipLabel {Location = new Point(1, 2), AutoSize = true}\r
334             };\r
335             _repairLabelList.Add(rpl);\r
336             _repairPanelList.Add(rpp);\r
337             // ReSharper disable once CoVariantArrayConversion\r
338             rpp.Controls.AddRange(rpl);\r
339             Controls.Add(rpp);\r
340             foreach (var label in rpl)\r
341             {\r
342                 label.Scale();\r
343                 label.PresetColor =\r
344                     label.BackColor = ShipLabel.ColumnColors[(i + 1) % 2];\r
345             }\r
346             if (_hpPercent)\r
347                 rpl[0].ToggleHpPercent();\r
348             _hpLabels.Add(rpl[0]);\r
349             rpl[0].DoubleClick += HpLabelClickHandler;\r
350         }\r
351 \r
352         private void CreateShipLabels(int i)\r
353         {\r
354             var y = LineHeight * i + 1;\r
355             const int height = LabelHeight;\r
356             var lbp = new Panel\r
357             {\r
358                 Location = new Point(0, y),\r
359                 Size = new Size(ListForm.PanelWidth, LineHeight),\r
360                 BackColor = ShipLabel.ColumnColors[(i + 1) % 2]\r
361             };\r
362             lbp.Scale(ShipLabel.ScaleFactor);\r
363             var labels = new[]\r
364             {\r
365                 new ShipLabel\r
366                 {\r
367                     Location = new Point(126, 0),\r
368                     AutoSize = true,\r
369                     AnchorRight = true,\r
370                     MinimumSize = new Size(0, LineHeight),\r
371                     TextAlign = ContentAlignment.MiddleLeft,\r
372                     Cursor = Cursors.Hand\r
373                 },\r
374                 new ShipLabel\r
375                 {\r
376                     Location = new Point(128, 0),\r
377                     Size = new Size(24, LineHeight),\r
378                     TextAlign = ContentAlignment.MiddleRight\r
379                 },\r
380                 new ShipLabel\r
381                 {\r
382                     Location = new Point(154, 2),\r
383                     Size = new Size(24, height),\r
384                     TextAlign = ContentAlignment.MiddleRight\r
385                 },\r
386                 new ShipLabel\r
387                 {\r
388                     Location = new Point(175, 2),\r
389                     Size = new Size(42, height),\r
390                     TextAlign = ContentAlignment.MiddleRight\r
391                 },\r
392                 new ShipLabel {Location = new Point(10, 2), AutoSize = true},\r
393                 new ShipLabel {Location = new Point(1, 2), AutoSize = true}\r
394             };\r
395             _labelList.Add(labels);\r
396             _labelPanelList.Add(lbp);\r
397             // ReSharper disable once CoVariantArrayConversion\r
398             lbp.Controls.AddRange(labels);\r
399             Controls.Add(lbp);\r
400             foreach (var label in labels)\r
401             {\r
402                 label.Scale();\r
403                 label.PresetColor =\r
404                     label.BackColor = ShipLabel.ColumnColors[(i + 1) % 2];\r
405             }\r
406             if (_hpPercent)\r
407                 labels[0].ToggleHpPercent();\r
408             _hpLabels.Add(labels[0]);\r
409             labels[0].DoubleClick += HpLabelClickHandler;\r
410         }\r
411 \r
412         private void SetShipLabels()\r
413         {\r
414             for (var i = 0; i < (Height + LineHeight - 1) / LineHeight; i++)\r
415             {\r
416                 if (InShipStatus(_mode))\r
417                     SetShipStatus(i);\r
418                 if (_mode == "分類")\r
419                     SetGrouping(i);\r
420                 if (_mode == "修復")\r
421                     SetRepairList(i);\r
422             }\r
423         }\r
424 \r
425         private bool InShipStatus(string mode) => Array.Exists(new[] {"全艦", "A", "B", "C", "D"}, x => mode == x);\r
426 \r
427         private void SetShipStatus(int i)\r
428         {\r
429             var panel = _labelPanelList[i];\r
430             if (i + ScrollBar.Value >= _shipList.Length)\r
431             {\r
432                 panel.Visible = false;\r
433                 return;\r
434             }\r
435             var s = _shipList[i + ScrollBar.Value];\r
436             var labels = _labelList[i];\r
437             if (s.Level == 1000) // 艦種の表示\r
438             {\r
439                 SetShipType(i);\r
440                 return;\r
441             }\r
442             labels[0].SetHp(s);\r
443             labels[1].SetCond(s);\r
444             labels[2].SetLevel(s);\r
445             labels[3].SetExpToNext(s);\r
446             labels[4].SetName(s, ShipNameWidth.ShipList);\r
447             labels[5].SetFleet(s);\r
448             panel.Visible = true;\r
449         }\r
450 \r
451         private void SetShipType(int i)\r
452         {\r
453             var s = _shipList[i + ScrollBar.Value];\r
454             var labels = _labelList[i];\r
455             labels[0].SetHp(null);\r
456             labels[1].SetCond(null);\r
457             labels[2].SetLevel(null);\r
458             labels[3].SetExpToNext(null);\r
459             labels[4].SetName(null);\r
460             labels[5].SetFleet(null);\r
461             labels[5].Text = s.Name;\r
462             _labelPanelList[i].Visible = true;\r
463         }\r
464 \r
465         private void SetGrouping(int i)\r
466         {\r
467             var panel = _groupingPanelList[i];\r
468             if (i + ScrollBar.Value >= _shipList.Length)\r
469             {\r
470                 panel.Visible = false;\r
471                 _labelPanelList[i].Visible = false;\r
472                 return;\r
473             }\r
474             var s = _shipList[i + ScrollBar.Value];\r
475             var labels = _groupingLabelList[i];\r
476             if (s.Level == 1000)\r
477             {\r
478                 panel.Visible = false;\r
479                 SetShipType(i);\r
480                 return;\r
481             }\r
482             labels[0].SetLevel(s);\r
483             labels[1].SetName(s, ShipNameWidth.GroupConfig);\r
484             labels[2].SetFleet(s);\r
485             var cb = _checkBoxesList[i];\r
486             for (var j = 0; j < cb.Length; j++)\r
487                 cb[j].Checked = GroupSettings[j].Contains(s.Id);\r
488             panel.Visible = true;\r
489         }\r
490 \r
491         private void SetRepairList(int i)\r
492         {\r
493             var panel = _repairPanelList[i];\r
494             if (i + ScrollBar.Value >= _shipList.Length)\r
495             {\r
496                 panel.Visible = false;\r
497                 _labelPanelList[i].Visible = false;\r
498                 return;\r
499             }\r
500             var s = _shipList[i + ScrollBar.Value];\r
501             if (s.Level == 1000)\r
502             {\r
503                 panel.Visible = false;\r
504                 SetShipType(i);\r
505                 return;\r
506             }\r
507             var rpl = _repairLabelList[i];\r
508             rpl[0].SetHp(s);\r
509             rpl[1].SetLevel(s);\r
510             rpl[2].SetRepairTime(s);\r
511             rpl[3].Text = s.RepairTimePerHp.ToString(@"mm\:ss");\r
512             rpl[4].SetName(s, ShipNameWidth.RepairListFull);\r
513             rpl[5].SetFleet(s);\r
514             panel.Visible = true;\r
515         }\r
516 \r
517         public event Action HpLabelClick;\r
518 \r
519         private void HpLabelClickHandler(object sender, EventArgs ev)\r
520         {\r
521             HpLabelClick?.Invoke();\r
522         }\r
523 \r
524         public void ToggleHpPercent()\r
525         {\r
526             _hpPercent = !_hpPercent;\r
527             foreach (var label in _hpLabels)\r
528                 label.ToggleHpPercent();\r
529         }\r
530 \r
531         public void ShowShip(int id)\r
532         {\r
533             var i = Array.FindIndex(_shipList, s => s.Id == id);\r
534             if (i == -1)\r
535                 return;\r
536             ScrollBar.Value = Min(i, ScrollBar.Maximum + 1 - ScrollBar.LargeChange);\r
537             SetShipLabels();\r
538         }\r
539     }\r
540 }