SelectionTool.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  1. using PaintDotNet.Measurement.HistoryFunctions;
  2. using PaintDotNet.Measurement.HistoryMementos;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.ComponentModel;
  6. using System.Drawing;
  7. using System.Drawing.Drawing2D;
  8. using System.Windows.Forms;
  9. namespace PaintDotNet.Measurement.Tools
  10. {
  11. public class SelectionTool : Tool
  12. {
  13. private bool tracking = false;
  14. private bool moveOriginMode = false;
  15. private Point lastXY;
  16. private SelectionHistoryMemento undoAction;
  17. private CombineMode combineMode;
  18. private List<Point> tracePoints = null;
  19. private DateTime startTime;
  20. private bool hasMoved = false;
  21. private bool append = false;
  22. private bool wasNotEmpty = false;
  23. private Selection newSelection;
  24. private SelectionRenderer newSelectionRenderer;
  25. private Cursor cursorMouseUp;
  26. private Cursor cursorMouseUpMinus;
  27. private Cursor cursorMouseUpPlus;
  28. private Cursor cursorMouseDown;
  29. protected CombineMode SelectionMode
  30. {
  31. get
  32. {
  33. return this.combineMode;
  34. }
  35. }
  36. protected void SetCursors(
  37. string cursorMouseUpResName,
  38. string cursorMouseUpMinusResName,
  39. string cursorMouseUpPlusResName,
  40. string cursorMouseDownResName)
  41. {
  42. if (this.cursorMouseUp != null)
  43. {
  44. this.cursorMouseUp.Dispose();
  45. this.cursorMouseUp = null;
  46. }
  47. if (cursorMouseUpResName != null)
  48. {
  49. this.cursorMouseUp = new Cursor(PdnResources.GetResourceStream(cursorMouseUpResName));
  50. }
  51. if (this.cursorMouseUpMinus != null)
  52. {
  53. this.cursorMouseUpMinus.Dispose();
  54. this.cursorMouseUpMinus = null;
  55. }
  56. if (cursorMouseUpMinusResName != null)
  57. {
  58. this.cursorMouseUpMinus = new Cursor(PdnResources.GetResourceStream(cursorMouseUpMinusResName));
  59. }
  60. if (this.cursorMouseUpPlus != null)
  61. {
  62. this.cursorMouseUpPlus.Dispose();
  63. this.cursorMouseUpPlus = null;
  64. }
  65. if (cursorMouseUpPlusResName != null)
  66. {
  67. this.cursorMouseUpPlus = new Cursor(PdnResources.GetResourceStream(cursorMouseUpPlusResName));
  68. }
  69. if (this.cursorMouseDown != null)
  70. {
  71. this.cursorMouseDown.Dispose();
  72. this.cursorMouseDown = null;
  73. }
  74. if (cursorMouseDownResName != null)
  75. {
  76. this.cursorMouseDown = new Cursor(PdnResources.GetResourceStream(cursorMouseDownResName));
  77. }
  78. }
  79. private Cursor GetCursor(bool mouseDown, bool ctrlDown, bool altDown)
  80. {
  81. Cursor cursor;
  82. if (mouseDown)
  83. {
  84. cursor = this.cursorMouseDown;
  85. }
  86. else if (ctrlDown)
  87. {
  88. cursor = this.cursorMouseUpPlus;
  89. }
  90. else if (altDown)
  91. {
  92. cursor = this.cursorMouseUpMinus;
  93. }
  94. else
  95. {
  96. cursor = this.cursorMouseUp;
  97. }
  98. return cursor;
  99. }
  100. private Cursor GetCursor()
  101. {
  102. return GetCursor(IsMouseDown, (ModifierKeys & Keys.Control) != 0, (ModifierKeys & Keys.Alt) != 0);
  103. }
  104. protected override void OnActivate()
  105. {
  106. // Assume that SetCursors() has been called by now
  107. this.Cursor = GetCursor();
  108. DocumentWorkspace.SetEnableSelectionTinting(true);
  109. this.newSelection = new Selection();
  110. this.newSelectionRenderer = new SelectionRenderer(this.RendererList, this.newSelection, this.DocumentWorkspace.GetUserControl());
  111. this.newSelectionRenderer.EnableSelectionTinting = false;
  112. this.newSelectionRenderer.EnableOutlineAnimation = false;
  113. this.newSelectionRenderer.Visible = false;
  114. this.RendererList.Add(this.newSelectionRenderer, true);
  115. base.OnActivate();
  116. }
  117. protected override void OnDeactivate()
  118. {
  119. DocumentWorkspace.SetEnableSelectionTinting(false);
  120. if (this.tracking)
  121. {
  122. Done();
  123. }
  124. base.OnDeactivate();
  125. SetCursors(null, null, null, null); // dispose 'em
  126. this.RendererList.Remove(this.newSelectionRenderer);
  127. this.newSelectionRenderer.Dispose();
  128. this.newSelectionRenderer = null;
  129. this.newSelection = null;
  130. }
  131. protected override void OnMouseDown(MouseEventArgs e)
  132. {
  133. base.OnMouseDown(e);
  134. this.Cursor = GetCursor();
  135. if (tracking)
  136. {
  137. moveOriginMode = true;
  138. lastXY = new Point(e.X, e.Y);
  139. OnMouseMove(e);
  140. }
  141. else if ((e.Button & MouseButtons.Left) == MouseButtons.Left ||
  142. (e.Button & MouseButtons.Right) == MouseButtons.Right)
  143. {
  144. tracking = true;
  145. hasMoved = false;
  146. startTime = DateTime.Now;
  147. tracePoints = new List<Point>();
  148. tracePoints.Add(new Point(e.X, e.Y));
  149. undoAction = new SelectionHistoryMemento("sentinel", this.Image, DocumentWorkspace);
  150. wasNotEmpty = !Selection.IsEmpty;
  151. // Determine this.combineMode
  152. if ((ModifierKeys & Keys.Control) != 0 && e.Button == MouseButtons.Left)
  153. {
  154. this.combineMode = CombineMode.Union;
  155. }
  156. else if ((ModifierKeys & Keys.Alt) != 0 && e.Button == MouseButtons.Left)
  157. {
  158. this.combineMode = CombineMode.Exclude;
  159. }
  160. else if ((ModifierKeys & Keys.Control) != 0 && e.Button == MouseButtons.Right)
  161. {
  162. this.combineMode = CombineMode.Xor;
  163. }
  164. else if ((ModifierKeys & Keys.Alt) != 0 && e.Button == MouseButtons.Right)
  165. {
  166. this.combineMode = CombineMode.Intersect;
  167. }
  168. else
  169. {
  170. this.combineMode = AppEnvironment.SelectionCombineMode();
  171. }
  172. DocumentWorkspace.SetEnableSelectionOutline(false);
  173. this.newSelection.Reset();
  174. PdnGraphicsPath basePath = Selection.CreatePath();
  175. this.newSelection.SetContinuation(basePath, CombineMode.Replace, true);
  176. this.newSelection.CommitContinuation();
  177. bool newSelectionRendererVisible = true;
  178. // Act on this.combineMode
  179. switch (this.combineMode)
  180. {
  181. case CombineMode.Xor:
  182. append = true;
  183. Selection.ResetContinuation();
  184. break;
  185. case CombineMode.Union:
  186. append = true;
  187. Selection.ResetContinuation();
  188. break;
  189. case CombineMode.Exclude:
  190. append = true;
  191. Selection.ResetContinuation();
  192. break;
  193. case CombineMode.Replace:
  194. append = false;
  195. Selection.Reset();
  196. break;
  197. case CombineMode.Intersect:
  198. append = true;
  199. Selection.ResetContinuation();
  200. break;
  201. default:
  202. throw new InvalidEnumArgumentException();
  203. }
  204. this.newSelectionRenderer.Visible = newSelectionRendererVisible;
  205. }
  206. }
  207. protected virtual List<Point> TrimShapePath(List<Point> trimTheseTracePoints)
  208. {
  209. return trimTheseTracePoints;
  210. }
  211. protected virtual List<PointF> CreateShape(List<Point> inputTracePoints)
  212. {
  213. List<PointF> points = Utility.PointListToPointFList(inputTracePoints);
  214. return points;
  215. }
  216. protected override void OnMouseMove(MouseEventArgs e)
  217. {
  218. base.OnMouseMove(e);
  219. if (moveOriginMode)
  220. {
  221. Size delta = new Size(e.X - lastXY.X, e.Y - lastXY.Y);
  222. for (int i = 0; i < tracePoints.Count; ++i)
  223. {
  224. Point pt = (Point)tracePoints[i];
  225. pt.X += delta.Width;
  226. pt.Y += delta.Height;
  227. tracePoints[i] = pt;
  228. }
  229. lastXY = new Point(e.X, e.Y);
  230. Render();
  231. }
  232. else if (tracking)
  233. {
  234. Point mouseXY = new Point(e.X, e.Y);
  235. if (mouseXY != (Point)tracePoints[tracePoints.Count - 1])
  236. {
  237. tracePoints.Add(mouseXY);
  238. }
  239. hasMoved = true;
  240. Render();
  241. }
  242. }
  243. private PointF[] CreateSelectionPolygon()
  244. {
  245. List<Point> trimmedTrace = this.TrimShapePath(tracePoints);
  246. List<PointF> shapePoints = CreateShape(trimmedTrace);
  247. List<PointF> polygon;
  248. switch (this.combineMode)
  249. {
  250. case CombineMode.Xor:
  251. case CombineMode.Exclude:
  252. polygon = shapePoints;
  253. break;
  254. default:
  255. case CombineMode.Complement:
  256. case CombineMode.Intersect:
  257. case CombineMode.Replace:
  258. case CombineMode.Union:
  259. polygon = Utility.SutherlandHodgman(DocumentWorkspace.GetDocument().Bounds, shapePoints);
  260. break;
  261. }
  262. return polygon.ToArray();
  263. }
  264. private void Render()
  265. {
  266. if (tracePoints != null && tracePoints.Count > 2)
  267. {
  268. PointF[] polygon = CreateSelectionPolygon();
  269. if (polygon.Length > 2)
  270. {
  271. DocumentWorkspace.ResetOutlineWhiteOpacity();
  272. this.newSelectionRenderer.ResetOutlineWhiteOpacity();
  273. Selection.SetContinuation(polygon, this.combineMode);
  274. CombineMode cm;
  275. if (SelectionMode == CombineMode.Replace)
  276. {
  277. cm = CombineMode.Replace;
  278. }
  279. else
  280. {
  281. cm = CombineMode.Xor;
  282. }
  283. this.newSelection.SetContinuation(polygon, cm);
  284. Update();
  285. }
  286. }
  287. }
  288. protected override void OnPulse()
  289. {
  290. if (this.tracking)
  291. {
  292. DocumentWorkspace.ResetOutlineWhiteOpacity();
  293. this.newSelectionRenderer.ResetOutlineWhiteOpacity();
  294. }
  295. base.OnPulse();
  296. }
  297. private enum WhatToDo
  298. {
  299. Clear,
  300. Emit,
  301. Reset,
  302. }
  303. private void Done()
  304. {
  305. if (tracking)
  306. {
  307. // Truth table for what we should do based on three flags:
  308. // append | moved | tooQuick | result | optimized expression to yield true
  309. // ---------+-------+----------+-----------------------------------------------------------------------
  310. // F | T | T | clear selection | !append && (!moved || tooQuick)
  311. // F | T | F | emit new selected area | !append && moved && !tooQuick
  312. // F | F | T | clear selection | !append && (!moved || tooQuick)
  313. // F | F | F | clear selection | !append && (!moved || tooQuick)
  314. // T | T | T | append to selection | append && moved
  315. // T | T | F | append to selection | append && moved
  316. // T | F | T | reset selection | append && !moved
  317. // T | F | F | reset selection | append && !moved
  318. //
  319. // append --> If the user was holding control, then true. Else false.
  320. // moved --> If they never moved the mouse, false. Else true.
  321. // tooQuick --> If they held the mouse button down for more than 50ms, false. Else true.
  322. //
  323. // "Clear selection" means to result in no selected area. If the selection area was previously empty,
  324. // then no HistoryMemento is emitted. Otherwise a Deselect HistoryMemento is emitted.
  325. //
  326. // "Reset selection" means to reset the selected area to how it was before interaction with the tool,
  327. // without a HistoryMemento.
  328. PointF[] polygon = CreateSelectionPolygon();
  329. this.hasMoved &= (polygon.Length > 1);
  330. // They were "too quick" if they weren't doing a selection for more than 50ms
  331. // This takes care of the case where someone wants to click to deselect, but accidentally moves
  332. // the mouse. This happens VERY frequently.
  333. bool tooQuick = Utility.TicksToMs((DateTime.Now - startTime).Ticks) <= 50;
  334. // If their selection was completedly out of bounds, it will be clipped
  335. bool clipped = (polygon.Length == 0);
  336. // What the user drew had no effect on the slection, e.g. subtraction where there was nothing in the first place
  337. bool noEffect = false;
  338. WhatToDo whatToDo;
  339. // If their selection gets completely clipped (i.e. outside the image canvas),
  340. // then result in a no-op
  341. if (append)
  342. {
  343. if (!hasMoved || clipped || noEffect)
  344. {
  345. whatToDo = WhatToDo.Reset;
  346. }
  347. else
  348. {
  349. whatToDo = WhatToDo.Emit;
  350. }
  351. }
  352. else
  353. {
  354. if (hasMoved && !tooQuick && !clipped && !noEffect)
  355. {
  356. whatToDo = WhatToDo.Emit;
  357. }
  358. else
  359. {
  360. whatToDo = WhatToDo.Clear;
  361. }
  362. }
  363. switch (whatToDo)
  364. {
  365. case WhatToDo.Clear:
  366. if (wasNotEmpty)
  367. {
  368. // emit a deselect history action
  369. undoAction.Name = DeselectFunction.StaticName;
  370. undoAction.Image = DeselectFunction.StaticImage;
  371. //HistoryStack.PushNewMemento(undoAction);
  372. }
  373. Selection.Reset();
  374. break;
  375. case WhatToDo.Emit:
  376. // emit newly selected area
  377. undoAction.Name = this.Name;
  378. //HistoryStack.PushNewMemento(undoAction);
  379. Selection.CommitContinuation();
  380. break;
  381. case WhatToDo.Reset:
  382. // reset selection, no HistoryMemento
  383. Selection.ResetContinuation();
  384. break;
  385. }
  386. DocumentWorkspace.ResetOutlineWhiteOpacity();
  387. this.newSelectionRenderer.ResetOutlineWhiteOpacity();
  388. this.newSelection.Reset();
  389. this.newSelectionRenderer.Visible = false;
  390. this.tracking = false;
  391. DocumentWorkspace.SetEnableSelectionOutline(true);
  392. DocumentWorkspace.InvalidateSurface(Utility.RoundRectangle(DocumentWorkspace.GetVisibleDocumentRectangleF()));
  393. }
  394. }
  395. protected override void OnMouseUp(MouseEventArgs e)
  396. {
  397. OnMouseMove(e);
  398. if (moveOriginMode)
  399. {
  400. moveOriginMode = false;
  401. }
  402. else
  403. {
  404. Done();
  405. }
  406. base.OnMouseUp(e);
  407. Cursor = GetCursor();
  408. }
  409. protected override void OnKeyDown(KeyEventArgs e)
  410. {
  411. base.OnKeyDown(e);
  412. if (tracking)
  413. {
  414. Render();
  415. }
  416. Cursor = GetCursor();
  417. }
  418. protected override void OnKeyUp(KeyEventArgs e)
  419. {
  420. base.OnKeyUp(e);
  421. if (tracking)
  422. {
  423. Render();
  424. }
  425. Cursor = GetCursor();
  426. }
  427. protected override void OnClick()
  428. {
  429. base.OnClick();
  430. if (!moveOriginMode)
  431. {
  432. Done();
  433. }
  434. }
  435. public SelectionTool(
  436. IDocumentWorkspace documentWorkspace,
  437. ImageResource toolBarImage,
  438. string name,
  439. string helpText,
  440. char hotKey,
  441. ToolBarConfigItems toolBarConfigItems)
  442. : base(documentWorkspace,
  443. toolBarImage,
  444. name,
  445. helpText,
  446. hotKey,
  447. false,
  448. toolBarConfigItems | ToolBarConfigItems.SelectionCombineMode)
  449. {
  450. this.tracking = false;
  451. }
  452. }
  453. }