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