OSDN Git Service

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