PageRenderTime 30ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/src/NUnit/UiException/Controls/SplitterBox.cs

#
C# | 517 lines | 334 code | 96 blank | 87 comment | 35 complexity | 6b8b5d50ce51f3ff3895722f25470181 MD5 | raw file
Possible License(s): GPL-2.0
  1. // ****************************************************************
  2. // This is free software licensed under the NUnit license. You may
  3. // obtain a copy of the license at http://nunit.org
  4. // ****************************************************************
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Text;
  8. using System.Windows.Forms;
  9. using System.Drawing;
  10. using NUnit.UiException.Properties;
  11. using System.Diagnostics;
  12. //
  13. // This re-implements SplitContainer. Why re-inventing the wheel?
  14. // Well... I faced some strange behaviors in SplitContainer in particular
  15. // when I started to provide a custom paint method. It seems to me
  16. // that there is a kind of defect that affects how the Invalidate or
  17. // paint event is called. In some situations I faced a SplitContainer
  18. // that didn't redraw itself while having some parts of its window
  19. // dirty. I didn't found out the cause of the problem.
  20. //
  21. // Another feature that is quite annoying is the unability to change
  22. // the mouse cursor while hovering some special areas of the splitter
  23. // bar. Maybe there is a trick or something but the normal way doesn't
  24. // look like to work.
  25. //
  26. namespace NUnit.UiException.Controls
  27. {
  28. /// <summary>
  29. /// Implements a place holder that can be splitted either horizontally or vertically.
  30. /// The SplitterBox is layouted with two place holders, respectively named Control1
  31. /// and Control2 where clients can put their controls.
  32. ///
  33. /// Unlike SplitContainer, the place holders in SplitterBox are the client controls
  34. /// itself. The direct consequence is the layout policy will be to dock the client
  35. /// controls in filling the maximum possible space.
  36. ///
  37. /// SplitterBox also add three buttons on the splitter bar that to change the split
  38. /// orientation and collapse either Control1 or Control2. The example below shows
  39. /// how to intialize and set up SplitterBox with two controls.
  40. /// <code>
  41. /// // creates a new SplitterBox, with a vertical split
  42. /// // and position splitter to appear in the middle of the window
  43. /// SplitterBox splitter = new SplitterBox();
  44. /// splitter.Orientation = Orientation.Vertical;
  45. /// splitter.SplitterDistance = 0.5f;
  46. /// splitter.Control1 = oneControl;
  47. /// splitter.Control2 = anotherControl;
  48. /// </code>
  49. /// </summary>
  50. public class SplitterBox : Control
  51. {
  52. public static readonly int SPLITTER_SIZE = 9;
  53. public static readonly int SPLITTER_HALFSIZE = SPLITTER_SIZE / 2;
  54. public static readonly int BUTTON_SIZE = 13;
  55. private Control _emptyControl1;
  56. private Control _emptyControl2;
  57. private Control _control1;
  58. private Control _control2;
  59. private Orientation _orientation;
  60. private float _x;
  61. private float _y;
  62. private Rectangle _splitterRectangle;
  63. private Rectangle _collapse1Rectangle;
  64. private Rectangle _collapse2Rectangle;
  65. private Rectangle _directionRectangle;
  66. private bool _movingSplitter;
  67. private Brush _brush;
  68. private Pen _pen;
  69. private Rectangle _rVerticalCollapse1;
  70. private Rectangle _rVerticalDirection;
  71. private Rectangle _rVerticalCollapse2;
  72. private Rectangle _rHorizontalCollapse1;
  73. private Rectangle _rHorizontalDirection;
  74. private Rectangle _rHorizontalCollapse2;
  75. public event EventHandler OrientationChanged;
  76. public event EventHandler SplitterDistanceChanged;
  77. /// <summary>
  78. /// Creates a new SplitterBox.
  79. /// </summary>
  80. public SplitterBox()
  81. {
  82. _brush = new SolidBrush(Color.FromArgb(146, 180, 224));
  83. _pen = new Pen(Color.FromArgb(103, 136, 190));
  84. _rVerticalCollapse1 = new Rectangle(0, 0, 9, 13);
  85. _rVerticalDirection = new Rectangle(10, 0, 9, 13);
  86. _rVerticalCollapse2 = new Rectangle(20, 0, 9, 13);
  87. _rHorizontalCollapse1 = new Rectangle(0, 24, 13, 9);
  88. _rHorizontalDirection = new Rectangle(14, 14, 13, 9);
  89. _rHorizontalCollapse2 = new Rectangle(0, 14, 13, 9);
  90. _emptyControl1 = new Control();
  91. _emptyControl2 = new Control();
  92. Width = 150;
  93. Height = 150;
  94. _control1 = _emptyControl1;
  95. _control2 = _emptyControl2;
  96. Controls.Add(_control1);
  97. Controls.Add(_control2);
  98. _x = _y = 0.5f;
  99. Orientation = Orientation.Vertical;
  100. DoLayout();
  101. return;
  102. }
  103. /// <summary>
  104. /// Gets or sets the orientation of the splitter in the SplitterBox.
  105. /// </summary>
  106. public Orientation Orientation
  107. {
  108. get { return (_orientation); }
  109. set {
  110. _orientation = value;
  111. DoLayout();
  112. }
  113. }
  114. /// <summary>
  115. /// Gets or sets the splitter distance expressed as a float number in the
  116. /// range [0 - 1]. A value of 0 collapses Control1 and makes Control2 take
  117. /// the whole space in the window. A value of 1 collapses Control2 and makes
  118. /// Control1 take the whole space in the window. A value of 0.5 makes the
  119. /// splitter appear in the middle of the window.
  120. ///
  121. /// Values that don't fall in [0 - 1] are automatically clipped to this range.
  122. /// </summary>
  123. public float SplitterDistance
  124. {
  125. get { return (_orientation == Orientation.Vertical ? _x : _y); }
  126. set {
  127. value = Math.Max(0, Math.Min(1, value));
  128. if (_orientation == Orientation.Vertical)
  129. _x = value;
  130. else
  131. _y = value;
  132. DoLayout();
  133. }
  134. }
  135. /// <summary>
  136. /// Gets or sets the "first" control to be shown. This control will appear
  137. /// either at the top or on the left when the orientation is respectively
  138. /// vertical or horizontal.
  139. /// If the value is not null, the control will automatically be added
  140. /// to the SplitterBox's hierarchy of controls.
  141. /// If the value is null, the former control is removed and replaced
  142. /// by a default and empty area.
  143. /// </summary>
  144. public Control Control1
  145. {
  146. get { return (_control1); }
  147. set
  148. {
  149. if (_control1 == value)
  150. return;
  151. Controls.Remove(_control1);
  152. if (value == null)
  153. value = _emptyControl1;
  154. _control1 = value;
  155. Controls.Add(value);
  156. DoLayout();
  157. return;
  158. }
  159. }
  160. /// <summary>
  161. /// Gets or sets the "second" control to be shown. This control will appear
  162. /// either at the bottom or on the right when the orientation is respectively
  163. /// vertical or horizontal.
  164. /// If the value is not null, the control will automatically be added
  165. /// to the SplitterBox's hierarchy of controls.
  166. /// If the value is null, the former control is removed and replaced
  167. /// by a default and empty area.
  168. /// </summary>
  169. public Control Control2
  170. {
  171. get { return (_control2); }
  172. set
  173. {
  174. if (_control2 == value)
  175. return;
  176. if (value == null)
  177. value = _emptyControl2;
  178. Controls.Remove(_control2);
  179. _control2 = value;
  180. Controls.Add(value);
  181. DoLayout();
  182. return;
  183. }
  184. }
  185. /// <summary>
  186. /// Gets the rectangle occupied with the splitter.
  187. /// </summary>
  188. public Rectangle SplitterRectangle
  189. {
  190. get { return (_splitterRectangle); }
  191. }
  192. /// <summary>
  193. /// Sets a new location for the splitter expressed as client coordinate.
  194. /// </summary>
  195. /// <param name="x">The new location in pixels when orientation is set to Vertical.</param>
  196. /// <param name="y">The new location in pixels when orientation is set to Horizontal.</param>
  197. public void PointToSplit(int x, int y)
  198. {
  199. if (_orientation == Orientation.Vertical)
  200. {
  201. x = Math.Max(0, Math.Min(Width, x));
  202. _x = (float)x / (float)Width;
  203. }
  204. else
  205. {
  206. y = Math.Max(0, Math.Min(Height, y));
  207. _y = (float)y / (float)Height;
  208. }
  209. DoLayout();
  210. return;
  211. }
  212. /// <summary>
  213. /// Collapses Control1.
  214. /// </summary>
  215. public void CollapseControl1()
  216. {
  217. PointToSplit(0, 0);
  218. }
  219. /// <summary>
  220. /// Collapses Control2.
  221. /// </summary>
  222. public void CollapseControl2()
  223. {
  224. PointToSplit(Width, Height);
  225. }
  226. protected Rectangle Collapse1Rectangle
  227. {
  228. get { return (_collapse1Rectangle); }
  229. }
  230. protected Rectangle Collapse2Rectangle
  231. {
  232. get { return (_collapse2Rectangle); }
  233. }
  234. protected Rectangle DirectionRectangle
  235. {
  236. get { return (_directionRectangle); }
  237. }
  238. private void HorizontalLayout()
  239. {
  240. int x = (Width - 41) / 2;
  241. int y;
  242. int top;
  243. top = (int)Math.Max(0, _y * Height - SPLITTER_HALFSIZE);
  244. top = Math.Min(top, Height - SPLITTER_SIZE);
  245. _splitterRectangle = new Rectangle(0, top, Width, SPLITTER_SIZE);
  246. y = _splitterRectangle.Top;
  247. _collapse1Rectangle = new Rectangle(x, y, BUTTON_SIZE, SPLITTER_SIZE);
  248. _directionRectangle = new Rectangle(_collapse1Rectangle.Right + 2, y, BUTTON_SIZE, SPLITTER_SIZE);
  249. _collapse2Rectangle = new Rectangle(_directionRectangle.Right + 2, y, BUTTON_SIZE, SPLITTER_SIZE);
  250. _control1.SetBounds(0, 0, Width, _splitterRectangle.Top);
  251. _control2.SetBounds(0, _splitterRectangle.Bottom, Width, Height - _splitterRectangle.Bottom);
  252. return;
  253. }
  254. private void VerticalLayout()
  255. {
  256. int y = (Height - 41) / 2;
  257. int left;
  258. int x;
  259. left = (int)Math.Max(0, _x * Width - SPLITTER_HALFSIZE);
  260. left = Math.Min(left, Width - SPLITTER_SIZE);
  261. _splitterRectangle = new Rectangle(left, 0, SPLITTER_SIZE, Height);
  262. x = _splitterRectangle.Left;
  263. _collapse1Rectangle = new Rectangle(x, y, SPLITTER_SIZE, BUTTON_SIZE);
  264. _directionRectangle = new Rectangle(x, _collapse1Rectangle.Bottom + 2, SPLITTER_SIZE, BUTTON_SIZE);
  265. _collapse2Rectangle = new Rectangle(x, _directionRectangle.Bottom + 2, SPLITTER_SIZE, BUTTON_SIZE);
  266. _control1.SetBounds(0, 0, _splitterRectangle.Left, Height);
  267. _control2.SetBounds(_splitterRectangle.Right, 0, Width - _splitterRectangle.Right, Height);
  268. return;
  269. }
  270. private void DoLayout()
  271. {
  272. if (_orientation == Orientation.Vertical)
  273. VerticalLayout();
  274. else
  275. HorizontalLayout();
  276. Invalidate();
  277. return;
  278. }
  279. protected override void OnSizeChanged(EventArgs e)
  280. {
  281. if (_control1 != null)
  282. DoLayout();
  283. base.OnSizeChanged(e);
  284. }
  285. protected override void OnMouseLeave(EventArgs e)
  286. {
  287. Cursor = Cursors.Default;
  288. base.OnMouseLeave(e);
  289. }
  290. protected override void OnMouseDown(MouseEventArgs e)
  291. {
  292. UpdateCursor(e.X, e.Y);
  293. if (_splitterRectangle.Contains(e.X, e.Y))
  294. {
  295. if (HoveringButtons(e.X, e.Y))
  296. return;
  297. _movingSplitter = true;
  298. }
  299. base.OnMouseDown(e);
  300. return;
  301. }
  302. protected override void OnMouseMove(MouseEventArgs e)
  303. {
  304. UpdateCursor(e.X, e.Y);
  305. if (_movingSplitter == true)
  306. {
  307. PointToSplit(e.X, e.Y);
  308. Invalidate();
  309. }
  310. base.OnMouseMove(e);
  311. return;
  312. }
  313. protected override void OnMouseUp(MouseEventArgs e)
  314. {
  315. bool wasMovingSplitter;
  316. UpdateCursor(e.X, e.Y);
  317. wasMovingSplitter = _movingSplitter;
  318. _movingSplitter = false;
  319. if (wasMovingSplitter)
  320. FireSplitterDistanceChanged();
  321. else
  322. {
  323. if (_collapse1Rectangle.Contains(e.X, e.Y))
  324. {
  325. CollapseControl1();
  326. FireSplitterDistanceChanged();
  327. return;
  328. }
  329. if (_collapse2Rectangle.Contains(e.X, e.Y))
  330. {
  331. CollapseControl2();
  332. FireSplitterDistanceChanged();
  333. return;
  334. }
  335. if (_directionRectangle.Contains(e.X, e.Y))
  336. {
  337. Orientation = (_orientation == Orientation.Vertical) ?
  338. Orientation.Horizontal :
  339. Orientation.Vertical;
  340. FireOrientationChanged();
  341. return;
  342. }
  343. }
  344. base.OnMouseUp(e);
  345. return;
  346. }
  347. private void FireOrientationChanged()
  348. {
  349. if (OrientationChanged != null)
  350. OrientationChanged(this, EventArgs.Empty);
  351. }
  352. private void FireSplitterDistanceChanged()
  353. {
  354. if (SplitterDistanceChanged != null)
  355. SplitterDistanceChanged(this, EventArgs.Empty);
  356. }
  357. protected override void OnPaint(PaintEventArgs e)
  358. {
  359. e.Graphics.FillRectangle(_brush, _splitterRectangle);
  360. if (Orientation == Orientation.Vertical)
  361. {
  362. e.Graphics.DrawLine(_pen, _splitterRectangle.Left, 0,
  363. SplitterRectangle.Left, _splitterRectangle.Height);
  364. e.Graphics.DrawLine(_pen, _splitterRectangle.Right - 1, 0,
  365. SplitterRectangle.Right - 1, _splitterRectangle.Height);
  366. e.Graphics.DrawImage(Resources.ImageSplitterBox,
  367. _collapse1Rectangle,
  368. _rVerticalCollapse1,
  369. GraphicsUnit.Pixel);
  370. e.Graphics.DrawImage(Resources.ImageSplitterBox,
  371. _directionRectangle,
  372. _rVerticalDirection,
  373. GraphicsUnit.Pixel);
  374. e.Graphics.DrawImage(Resources.ImageSplitterBox,
  375. _collapse2Rectangle,
  376. _rVerticalCollapse2,
  377. GraphicsUnit.Pixel);
  378. }
  379. else
  380. {
  381. e.Graphics.DrawLine(_pen, 0, _splitterRectangle.Top,
  382. Width, _splitterRectangle.Top);
  383. e.Graphics.DrawLine(_pen, 0, _splitterRectangle.Bottom - 1,
  384. Width, _splitterRectangle.Bottom - 1);
  385. e.Graphics.DrawImage(Resources.ImageSplitterBox,
  386. _collapse1Rectangle,
  387. _rHorizontalCollapse1,
  388. GraphicsUnit.Pixel);
  389. e.Graphics.DrawImage(Resources.ImageSplitterBox,
  390. _directionRectangle,
  391. _rHorizontalDirection,
  392. GraphicsUnit.Pixel);
  393. e.Graphics.DrawImage(Resources.ImageSplitterBox,
  394. _collapse2Rectangle,
  395. _rHorizontalCollapse2,
  396. GraphicsUnit.Pixel);
  397. }
  398. base.OnPaint(e);
  399. return;
  400. }
  401. private bool HoveringButtons(int x, int y)
  402. {
  403. if (!SplitterRectangle.Contains(x, y))
  404. return (false);
  405. return (_collapse1Rectangle.Contains(x, y) ||
  406. _collapse2Rectangle.Contains(x, y) ||
  407. _directionRectangle.Contains(x, y));
  408. }
  409. private void UpdateCursor(int x, int y)
  410. {
  411. if (!SplitterRectangle.Contains(x, y) ||
  412. HoveringButtons(x, y))
  413. {
  414. Cursor = Cursors.Default;
  415. return;
  416. }
  417. Cursor = (Orientation == Orientation.Vertical ? Cursors.VSplit : Cursors.HSplit);
  418. return;
  419. }
  420. }
  421. }