1 /* 2 KeePass Password Safe - The Open-Source Password Manager 3 Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de> 4 5 This program is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published by 7 the Free Software Foundation; either version 2 of the License, or 8 (at your option) any later version. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with this program; if not, write to the Free Software 17 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18 */ 19 20 using System; 21 using System.Collections.Generic; 22 using System.ComponentModel; 23 using System.Diagnostics; 24 using System.Reflection; 25 using System.Text; 26 using System.Windows.Forms; 27 28 using KeePassLib.Native; 29 30 namespace KeePass.UI 31 { 32 public sealed class CustomSplitContainerEx : SplitContainer 33 { 34 private ControlCollection m_ccControls = null; 35 private Control m_cDefault = null; 36 37 private Control m_cFocused = null; 38 private Control m_cLastKnown = null; 39 40 [Browsable(false)] 41 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] 42 public double SplitterDistanceFrac 43 { 44 get 45 { 46 bool bVert = (this.Orientation == Orientation.Vertical); 47 48 int m = (bVert ? this.Width : this.Height); 49 if(m <= 0) { Debug.Assert(false); return 0.0; } 50 51 int d = this.SplitterDistance; 52 if(d < 0) { Debug.Assert(false); return 0.0; } 53 if(d == 0) return 0.0; // Avoid fExact infinity 54 55 double f = (double)d / (double)m; 56 57 try 58 { 59 FieldInfo fi = GetRatioField(bVert); 60 if(fi != null) 61 { 62 double fExact = (double)fi.GetValue(this); 63 if(fExact > double.Epsilon) 64 { 65 fExact = 1.0 / fExact; 66 67 // Test whether fExact makes sense and if so, 68 // use it instead of f; 1/m as boundary is 69 // slightly too strict 70 if(Math.Abs(fExact - f) <= (1.5 / (double)m)) 71 return fExact; 72 else { Debug.Assert(false); } 73 } 74 else { Debug.Assert(false); } 75 } 76 else { Debug.Assert(false); } 77 } 78 catch(Exception) { Debug.Assert(false); } 79 80 return f; 81 } 82 83 set 84 { 85 if((value < 0.0) || (value > 1.0)) { Debug.Assert(false); return; } 86 87 bool bVert = (this.Orientation == Orientation.Vertical); 88 89 int m = (bVert ? this.Width : this.Height); 90 if(m <= 0) { Debug.Assert(false); return; } 91 92 int d = (int)Math.Round(value * (double)m); 93 if(d < 0) { Debug.Assert(false); d = 0; } 94 if(d > m) { Debug.Assert(false); d = m; } 95 96 this.SplitterDistance = d; 97 if(d == 0) return; // Avoid infinity / division by zero 98 99 // If the position was auto-adjusted (e.g. due to 100 // minimum size constraints), skip the rest 101 if(this.SplitterDistance != d) return; 102 103 try 104 { 105 FieldInfo fi = GetRatioField(bVert); 106 if(fi != null) 107 { 108 double fEst = (double)fi.GetValue(this); 109 if(fEst <= double.Epsilon) { Debug.Assert(false); return; } 110 fEst = 1.0 / fEst; // m/d -> d/m 111 112 // Test whether fEst makes sense and if so, 113 // overwrite it with the exact value; 114 // we must test for 1.5/m, not 1/m, because .NET 115 // uses Math.Floor and we use Math.Round 116 if(Math.Abs(fEst - value) <= (1.5 / (double)m)) 117 fi.SetValue(this, 1.0 / value); // d/m -> m/d 118 else { Debug.Assert(false); } 119 } 120 else { Debug.Assert(false); } 121 } 122 catch(Exception) { Debug.Assert(false); } 123 } 124 } ShouldSerializeSplitterDistanceFrac()125 public bool ShouldSerializeSplitterDistanceFrac() { return false; } 126 CustomSplitContainerEx()127 public CustomSplitContainerEx() : base() 128 { 129 // if(Program.DesignMode) return; 130 } 131 InitEx(ControlCollection cc, Control cDefault)132 public void InitEx(ControlCollection cc, Control cDefault) 133 { 134 m_ccControls = cc; 135 m_cDefault = m_cLastKnown = cDefault; 136 } 137 FindInputFocus(ControlCollection cc)138 private static Control FindInputFocus(ControlCollection cc) 139 { 140 if(cc == null) { Debug.Assert(false); return null; } 141 142 foreach(Control c in cc) 143 { 144 if(c.Focused) 145 return c; 146 else if(c.ContainsFocus) 147 return FindInputFocus(c.Controls); 148 } 149 150 return null; 151 } 152 OnMouseDown(MouseEventArgs e)153 protected override void OnMouseDown(MouseEventArgs e) 154 { 155 m_cFocused = FindInputFocus(m_ccControls); 156 if(m_cFocused == null) m_cFocused = m_cDefault; 157 158 if(m_cFocused != null) m_cLastKnown = m_cFocused; 159 160 base.OnMouseDown(e); 161 } 162 OnMouseUp(MouseEventArgs e)163 protected override void OnMouseUp(MouseEventArgs e) 164 { 165 base.OnMouseUp(e); 166 167 if(m_cFocused != null) 168 { 169 UIUtil.SetFocus(m_cFocused, null); 170 m_cFocused = null; 171 } 172 else { Debug.Assert(false); } 173 } 174 OnEnter(EventArgs e)175 protected override void OnEnter(EventArgs e) 176 { 177 base.OnEnter(e); 178 179 if(this.Focused && (m_cFocused == null)) 180 { 181 if(m_cLastKnown != null) UIUtil.SetFocus(m_cLastKnown, null); 182 else if(m_cDefault != null) UIUtil.SetFocus(m_cDefault, null); 183 } 184 } 185 GetRatioField(bool bVert)186 private static FieldInfo GetRatioField(bool bVert) 187 { 188 // Both .NET and Mono store 'max/pos', not 'pos/max' 189 return typeof(SplitContainer).GetField( 190 (NativeLib.IsUnix() ? "fixed_none_ratio" : 191 (bVert ? "ratioWidth" : "ratioHeight")), 192 (BindingFlags.Instance | BindingFlags.NonPublic)); 193 } 194 } 195 } 196