PageRenderTime 59ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/GitUI/UserControls/RevisionGridClasses/DvcsGraph.cs

https://github.com/qgppl/gitextensions
C# | 1400 lines | 1150 code | 170 blank | 80 comment | 224 complexity | 4e715eb15ca5f0463db2cc17d3fc41f7 MD5 | raw file
Possible License(s): GPL-3.0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Diagnostics;
  5. using System.Drawing;
  6. using System.Drawing.Drawing2D;
  7. using System.Drawing.Imaging;
  8. using System.Linq;
  9. using System.Threading;
  10. using System.Windows.Forms;
  11. using GitCommands;
  12. namespace GitUI.RevisionGridClasses
  13. {
  14. public sealed partial class DvcsGraph : DataGridView
  15. {
  16. #region Delegates
  17. public class LoadingEventArgs : EventArgs
  18. {
  19. public LoadingEventArgs(bool isLoading)
  20. {
  21. IsLoading = isLoading;
  22. }
  23. public bool IsLoading { get; private set; }
  24. }
  25. #endregion
  26. #region DataType enum
  27. [Flags]
  28. public enum DataType
  29. {
  30. Normal = 0,
  31. Active = 1,
  32. Special = 2,
  33. Filtered = 4,
  34. }
  35. #endregion
  36. #region FilterType enum
  37. public enum FilterType
  38. {
  39. None,
  40. Highlight,
  41. Hide,
  42. }
  43. #endregion
  44. private int _nodeDimension = 8;
  45. private int _laneWidth = 13;
  46. private int _laneLineWidth = 2;
  47. private const int MaxLanes = 40;
  48. private Brush _selectionBrush;
  49. private Pen _whiteBorderPen;
  50. private Pen _blackBorderPen;
  51. private readonly AutoResetEvent _backgroundEvent = new AutoResetEvent(false);
  52. private readonly Graph _graphData;
  53. private readonly Dictionary<Junction, int> _junctionColors = new Dictionary<Junction, int>();
  54. private readonly Color _nonRelativeColor = Color.LightGray;
  55. private readonly Color[] _possibleColors =
  56. {
  57. Color.Red,
  58. Color.MistyRose,
  59. Color.Magenta,
  60. Color.Violet,
  61. Color.Blue,
  62. Color.Azure,
  63. Color.Cyan,
  64. Color.SpringGreen,
  65. Color.Green,
  66. Color.Chartreuse,
  67. Color.Gold,
  68. Color.Orange
  69. };
  70. private int _backgroundScrollTo;
  71. private readonly Thread _backgroundThread;
  72. private volatile bool _shouldRun = true;
  73. private int _cacheCount; // Number of elements in the cache.
  74. private int _cacheCountMax; // Number of elements allowed in the cache. Is based on control height.
  75. private int _cacheHead = -1; // The 'slot' that is the head of the circular bitmap
  76. private int _cacheHeadRow; // The node row that is in the head slot
  77. private FilterType _filterMode = FilterType.None;
  78. private Bitmap _graphBitmap;
  79. private int _graphDataCount;
  80. private Graphics _graphWorkArea;
  81. private int _rowHeight; // Height of elements in the cache. Is equal to the control's row height.
  82. private int _visibleBottom;
  83. private int _visibleTop;
  84. public void SetDimensions(int nodeDimension, int laneWidth, int laneLineWidth, int rowHeight, Brush selectionBrush)
  85. {
  86. RowTemplate.Height = rowHeight;
  87. _nodeDimension = nodeDimension;
  88. _laneWidth = laneWidth;
  89. _laneLineWidth = laneLineWidth;
  90. this._selectionBrush = selectionBrush;
  91. dataGrid_Resize(null, null);
  92. }
  93. public DvcsGraph()
  94. {
  95. _graphData = new Graph();
  96. _backgroundThread = new Thread(BackgroundThreadEntry)
  97. {
  98. IsBackground = true,
  99. Priority = ThreadPriority.Normal,
  100. Name = "DvcsGraph.backgroundThread"
  101. };
  102. _backgroundThread.Start();
  103. InitializeComponent();
  104. ColumnHeadersDefaultCellStyle.Font = SystemFonts.DefaultFont;
  105. Font = SystemFonts.DefaultFont;
  106. DefaultCellStyle.Font = SystemFonts.DefaultFont;
  107. AlternatingRowsDefaultCellStyle.Font = SystemFonts.DefaultFont;
  108. RowsDefaultCellStyle.Font = SystemFonts.DefaultFont;
  109. RowHeadersDefaultCellStyle.Font = SystemFonts.DefaultFont;
  110. RowTemplate.DefaultCellStyle.Font = SystemFonts.DefaultFont;
  111. _whiteBorderPen = new Pen(Brushes.White, _laneLineWidth + 2);
  112. _blackBorderPen = new Pen(Brushes.Black, _laneLineWidth + 1);
  113. SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
  114. CellPainting += dataGrid_CellPainting;
  115. ColumnWidthChanged += dataGrid_ColumnWidthChanged;
  116. Scroll += dataGrid_Scroll;
  117. _graphData.Updated += graphData_Updated;
  118. VirtualMode = true;
  119. Clear();
  120. }
  121. protected override void OnCreateControl()
  122. {
  123. DataGridViewColumn dataGridColumnGraph;
  124. if (ColumnCount <= 0 || GraphColumn.HeaderText != "")
  125. dataGridColumnGraph = new DataGridViewTextBoxColumn();
  126. else
  127. dataGridColumnGraph = GraphColumn;
  128. dataGridColumnGraph.HeaderText = "";
  129. dataGridColumnGraph.Frozen = true;
  130. dataGridColumnGraph.Name = "dataGridColumnGraph";
  131. dataGridColumnGraph.ReadOnly = true;
  132. dataGridColumnGraph.SortMode = DataGridViewColumnSortMode.NotSortable;
  133. dataGridColumnGraph.Width = 70;
  134. dataGridColumnGraph.DefaultCellStyle.Font = SystemFonts.DefaultFont;
  135. if (ColumnCount == 0 || GraphColumn.HeaderText != "")
  136. Columns.Insert(0, dataGridColumnGraph);
  137. }
  138. /// <summary>
  139. /// 0
  140. /// </summary>
  141. internal DataGridViewColumn GraphColumn { get { return Columns[0]; } }
  142. /// <summary>
  143. /// 1
  144. /// </summary>
  145. internal DataGridViewColumn MessageColumn { get { return Columns[1]; } }
  146. /// <summary>
  147. /// 2
  148. /// </summary>
  149. internal DataGridViewColumn AuthorColumn { get { return Columns[2]; } }
  150. /// <summary>
  151. /// 3
  152. /// </summary>
  153. internal DataGridViewColumn DateColumn { get { return Columns[3]; } }
  154. internal DataGridViewColumn IdColumn { get { return Columns[4]; } }
  155. public void ShowAuthor(bool show)
  156. {
  157. this.AuthorColumn.Visible = show;
  158. this.DateColumn.Visible = show;
  159. }
  160. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2002:DoNotLockOnObjectsWithWeakIdentity", Justification = "It looks like such lock was made intentionally but it is better to rewrite this")]
  161. [DefaultValue(FilterType.None)]
  162. [Category("Behavior")]
  163. public FilterType FilterMode
  164. {
  165. get { return _filterMode; }
  166. set
  167. {
  168. // TODO: We only need to rebuild the graph if switching to or from hide
  169. if (_filterMode == value)
  170. {
  171. return;
  172. }
  173. this.InvokeSync(() =>
  174. {
  175. lock (_backgroundEvent) // Make sure the background thread isn't running
  176. {
  177. lock (_backgroundThread)
  178. {
  179. _backgroundScrollTo = 0;
  180. _graphDataCount = 0;
  181. }
  182. lock (_graphData)
  183. {
  184. _filterMode = value;
  185. _graphData.IsFilter = (_filterMode & FilterType.Hide) == FilterType.Hide;
  186. RebuildGraph();
  187. }
  188. }
  189. });
  190. }
  191. }
  192. [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  193. [Browsable(false)]
  194. public object[] SelectedData
  195. {
  196. get
  197. {
  198. if (SelectedRows.Count == 0)
  199. {
  200. return null;
  201. }
  202. var data = new object[SelectedRows.Count];
  203. for (int i = 0; i < SelectedRows.Count; i++)
  204. {
  205. data[i] = _graphData[i].Node.Data;
  206. }
  207. return data;
  208. }
  209. }
  210. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2002:DoNotLockOnObjectsWithWeakIdentity")]
  211. [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  212. [Browsable(false)]
  213. public string[] SelectedIds
  214. {
  215. get
  216. {
  217. if (SelectedRows.Count == 0)
  218. {
  219. return null;
  220. }
  221. var data = new string[SelectedRows.Count];
  222. for (int i = 0; i < SelectedRows.Count; i++)
  223. {
  224. if (_graphData[SelectedRows[i].Index] != null)
  225. data[SelectedRows.Count - 1 - i] = _graphData[SelectedRows[i].Index].Node.Id;
  226. }
  227. return data;
  228. }
  229. set
  230. {
  231. lock (_backgroundEvent)
  232. lock (_graphData)
  233. {
  234. ClearSelection();
  235. CurrentCell = null;
  236. if (value == null)
  237. return;
  238. foreach (string rowItem in value)
  239. {
  240. int? row = TryGetRevisionIndex(rowItem);
  241. if (row.HasValue && row.Value >= 0 && Rows.Count > row.Value)
  242. {
  243. Rows[row.Value].Selected = true;
  244. if (CurrentCell == null)
  245. {
  246. // Set the current cell to the first item. We use cell
  247. // 1 because cell 0 could be hidden if they've chosen to
  248. // not see the graph
  249. CurrentCell = Rows[row.Value].Cells[1];
  250. }
  251. }
  252. }
  253. }
  254. }
  255. }
  256. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2002:DoNotLockOnObjectsWithWeakIdentity", Justification = "It looks like such lock was made intentionally but it is better to rewrite this")]
  257. protected override void Dispose(bool disposing)
  258. {
  259. lock (_backgroundEvent)
  260. _shouldRun = false;
  261. if (disposing)
  262. {
  263. if (_whiteBorderPen != null)
  264. {
  265. _whiteBorderPen.Dispose();
  266. _whiteBorderPen = null;
  267. }
  268. if (_blackBorderPen != null)
  269. {
  270. _blackBorderPen.Dispose();
  271. _blackBorderPen = null;
  272. }
  273. if (_graphBitmap != null)
  274. {
  275. _graphBitmap.Dispose();
  276. _graphBitmap = null;
  277. }
  278. if (_backgroundEvent != null)
  279. {
  280. _backgroundEvent.Dispose();
  281. }
  282. }
  283. base.Dispose(disposing);
  284. }
  285. [Description("Loading Handler. NOTE: This will often happen on a background thread so UI operations may not be safe!")]
  286. [Category("Behavior")]
  287. public event EventHandler<LoadingEventArgs> Loading;
  288. public void ShowRevisionGraph()
  289. {
  290. GraphColumn.Visible = true;
  291. //updateData();
  292. _backgroundEvent.Set();
  293. }
  294. public void HideRevisionGraph()
  295. {
  296. GraphColumn.Visible = false;
  297. //updateData();
  298. _backgroundEvent.Set();
  299. }
  300. [DefaultValue(true)]
  301. [Browsable(false)]
  302. public bool RevisionGraphVisible
  303. {
  304. get
  305. {
  306. return GraphColumn.Visible;
  307. }
  308. }
  309. public void Add(string aId, string[] aParentIds, DataType aType, GitRevision aData)
  310. {
  311. lock (_graphData)
  312. {
  313. _graphData.Add(aId, aParentIds, aType, aData);
  314. }
  315. UpdateData();
  316. }
  317. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2002:DoNotLockOnObjectsWithWeakIdentity", Justification = "It looks like such lock was made intentionally but it is better to rewrite this")]
  318. public void Clear()
  319. {
  320. lock (_backgroundThread)
  321. {
  322. _backgroundScrollTo = 0;
  323. }
  324. lock (_graphData)
  325. {
  326. SetRowCount(0);
  327. _junctionColors.Clear();
  328. _graphData.Clear();
  329. _graphDataCount = 0;
  330. RebuildGraph();
  331. }
  332. _filterMode = FilterType.None;
  333. }
  334. public void FilterClear()
  335. {
  336. lock (_graphData)
  337. {
  338. foreach (Node n in _graphData.Nodes.Values)
  339. {
  340. n.IsFiltered = false;
  341. }
  342. _graphData.IsFilter = false;
  343. }
  344. }
  345. public void Filter(string aId)
  346. {
  347. lock (_graphData)
  348. {
  349. _graphData.Filter(aId);
  350. }
  351. }
  352. public bool RowIsRelative(int aRow)
  353. {
  354. lock (_graphData)
  355. {
  356. Graph.ILaneRow row = _graphData[aRow];
  357. if (row == null)
  358. {
  359. return false;
  360. }
  361. if (row.Node.Ancestors.Count > 0)
  362. return row.Node.Ancestors[0].IsRelative;
  363. return true;
  364. }
  365. }
  366. public GitRevision GetRowData(int aRow)
  367. {
  368. lock (_graphData)
  369. {
  370. Graph.ILaneRow row = _graphData[aRow];
  371. return row == null ? null : row.Node.Data;
  372. }
  373. }
  374. public string GetRowId(int aRow)
  375. {
  376. lock (_graphData)
  377. {
  378. Graph.ILaneRow row = _graphData[aRow];
  379. if (row == null)
  380. {
  381. return null;
  382. }
  383. return row.Node.Id;
  384. }
  385. }
  386. public int FindRow(string aId)
  387. {
  388. lock (_graphData)
  389. {
  390. int i;
  391. for (i = 0; i < _graphData.CachedCount; i++)
  392. {
  393. if (_graphData[i] != null && _graphData[i].Node.Id.CompareTo(aId) == 0)
  394. {
  395. break;
  396. }
  397. }
  398. return i == _graphData.Count ? -1 : i;
  399. }
  400. }
  401. public void Prune()
  402. {
  403. int count;
  404. lock (_graphData)
  405. {
  406. _graphData.Prune();
  407. count = _graphData.Count;
  408. SetRowCount(count);
  409. }
  410. }
  411. private void RebuildGraph()
  412. {
  413. // Redraw
  414. _cacheHead = -1;
  415. _cacheHeadRow = 0;
  416. ClearDrawCache();
  417. UpdateData();
  418. Invalidate(true);
  419. }
  420. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2002:DoNotLockOnObjectsWithWeakIdentity", Justification = "It looks like such lock was made intentionally but it is better to rewrite this")]
  421. private void SetRowCount(int count)
  422. {
  423. if (InvokeRequired)
  424. {
  425. // DO NOT INVOKE! The RowCount is fixed at other strategic points in time.
  426. // -Doing this in synch can lock up the application
  427. // -Doing this asynch causes the scrollbar to flicker and eats performance
  428. // -At first I was concerned that returning might lead to some cases where
  429. // we have more items in the list than we're showing, but I'm pretty sure
  430. // when we're done processing we'll update with the final count, so the
  431. // problem will only be temporary, and not able to distinguish it from
  432. // just git giving us data slowly.
  433. //Invoke(new MethodInvoker(delegate { setRowCount(count); }));
  434. return;
  435. }
  436. lock (_backgroundThread)
  437. {
  438. UpdatingVisibleRows = true;
  439. try
  440. {
  441. if (CurrentCell == null)
  442. {
  443. RowCount = count;
  444. CurrentCell = null;
  445. }
  446. else
  447. {
  448. RowCount = count;
  449. }
  450. }
  451. finally
  452. {
  453. UpdatingVisibleRows = false;
  454. }
  455. }
  456. }
  457. private void graphData_Updated(object graph)
  458. {
  459. // We have to post this since the thread owns a lock on GraphData that we'll
  460. // need in order to re-draw the graph.
  461. this.InvokeAsync(() =>
  462. {
  463. ClearDrawCache();
  464. Invalidate();
  465. });
  466. }
  467. private void dataGrid_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
  468. {
  469. if (e.RowIndex < 0)
  470. return;
  471. if (Rows[e.RowIndex].Height != RowTemplate.Height)
  472. {
  473. Rows[e.RowIndex].Height = RowTemplate.Height;
  474. dataGrid_Scroll(null, null);
  475. }
  476. if ((e.State & DataGridViewElementStates.Visible) == 0 || e.ColumnIndex != 0)
  477. return;
  478. var brush = (e.State & DataGridViewElementStates.Selected) == DataGridViewElementStates.Selected
  479. ? _selectionBrush : Brushes.White;
  480. e.Graphics.FillRectangle(brush, e.CellBounds);
  481. Rectangle srcRect = DrawGraph(e.RowIndex);
  482. if (!srcRect.IsEmpty)
  483. {
  484. e.Graphics.DrawImage
  485. (
  486. _graphBitmap,
  487. e.CellBounds,
  488. srcRect,
  489. GraphicsUnit.Pixel
  490. );
  491. }
  492. e.Handled = true;
  493. }
  494. private void dataGrid_ColumnWidthChanged(object sender, DataGridViewColumnEventArgs e)
  495. {
  496. ClearDrawCache();
  497. }
  498. private void dataGrid_Scroll(object sender, ScrollEventArgs e)
  499. {
  500. UpdateData();
  501. UpdateColumnWidth();
  502. }
  503. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2002:DoNotLockOnObjectsWithWeakIdentity", Justification = "It looks like such lock was made intentionally but it is better to rewrite this")]
  504. private void BackgroundThreadEntry()
  505. {
  506. while (_shouldRun)
  507. {
  508. if (_backgroundEvent.WaitOne(500))
  509. {
  510. lock (_backgroundEvent)
  511. {
  512. if (!_shouldRun)
  513. return;
  514. int scrollTo;
  515. lock (_backgroundThread)
  516. {
  517. scrollTo = _backgroundScrollTo;
  518. }
  519. int curCount;
  520. lock (_graphData)
  521. {
  522. curCount = _graphDataCount;
  523. _graphDataCount = _graphData.CachedCount;
  524. }
  525. if (RevisionGraphVisible)
  526. UpdateGraph(curCount, scrollTo);
  527. else
  528. {
  529. //do nothing... do not cache, the graph is invisible
  530. Thread.Sleep(10);
  531. }
  532. }
  533. }
  534. }
  535. }
  536. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2002:DoNotLockOnObjectsWithWeakIdentity", Justification = "It looks like such lock was made intentionally but it is better to rewrite this")]
  537. private void UpdateGraph(int curCount, int scrollTo)
  538. {
  539. while (curCount < scrollTo)
  540. {
  541. lock (_graphData)
  542. {
  543. // Cache the next item
  544. if (!_graphData.CacheTo(curCount))
  545. {
  546. Debug.WriteLine("Cached item FAILED {0}", curCount.ToString());
  547. lock (_backgroundThread)
  548. {
  549. _backgroundScrollTo = curCount;
  550. }
  551. break;
  552. }
  553. // Update the row (if needed)
  554. if (curCount == Math.Min(scrollTo, _visibleBottom) - 1)
  555. {
  556. this.InvokeAsync(o => UpdateRow((int)o), curCount);
  557. }
  558. int count = 0;
  559. if (FirstDisplayedCell != null)
  560. count = FirstDisplayedCell.RowIndex + DisplayedRowCount(true);
  561. if (curCount == count)
  562. this.InvokeAsync(UpdateColumnWidth);
  563. curCount = _graphData.CachedCount;
  564. _graphDataCount = curCount;
  565. }
  566. }
  567. }
  568. private void UpdateData()
  569. {
  570. _visibleTop = FirstDisplayedCell == null ? 0 : FirstDisplayedCell.RowIndex;
  571. _visibleBottom = _rowHeight > 0 ? _visibleTop + (Height / _rowHeight) : _visibleTop;
  572. //Add 5 for safe merge (1 for rounding and 1 for whitespace)....
  573. if (_visibleBottom + 2 > _graphData.Count)
  574. {
  575. //Currently we are doing some important work; we are recieving
  576. //rows that the user is viewing
  577. if (Loading != null && _graphData.Count > RowCount)// && graphData.Count != RowCount)
  578. {
  579. Loading(this, new LoadingEventArgs(true));
  580. }
  581. }
  582. else
  583. {
  584. //All rows that the user is viewing are loaded. We now can hide the loading
  585. //animation that is shown. (the event Loading(bool) triggers this!)
  586. if (Loading != null)
  587. {
  588. Loading(this, new LoadingEventArgs(false));
  589. }
  590. }
  591. if (_visibleBottom >= _graphData.Count)
  592. {
  593. _visibleBottom = _graphData.Count;
  594. }
  595. int targetBottom = _visibleBottom + 250;
  596. targetBottom = Math.Min(targetBottom, _graphData.Count);
  597. if (_backgroundScrollTo < targetBottom)
  598. {
  599. _backgroundScrollTo = targetBottom;
  600. _backgroundEvent.Set();
  601. }
  602. }
  603. private void UpdateRow(int row)
  604. {
  605. if (RowCount < _graphData.Count)
  606. {
  607. lock (_graphData)
  608. {
  609. SetRowCount(_graphData.Count);
  610. }
  611. }
  612. //We only need to invalidate if the row is visible
  613. if (_visibleBottom >= row &&
  614. _visibleTop <= row &&
  615. row < RowCount)
  616. {
  617. try
  618. {
  619. InvalidateRow(row);
  620. }
  621. catch (ArgumentOutOfRangeException)
  622. {
  623. // Ignore. It is possible that RowCount gets changed before
  624. // this is processed and the row is larger than RowCount.
  625. }
  626. }
  627. }
  628. public bool UpdatingVisibleRows { get; private set; }
  629. private void UpdateColumnWidth()
  630. {
  631. // Auto scale width on scroll
  632. if (GraphColumn.Visible)
  633. {
  634. int laneCount = 2;
  635. if (_graphData != null)
  636. {
  637. int width = 1;
  638. int start = VerticalScrollBar.Value/_rowHeight;
  639. int stop = start + DisplayedRowCount(true);
  640. lock (_graphData)
  641. {
  642. for (int i = start; i < stop && _graphData[i] != null; i++)
  643. {
  644. width = Math.Max(_graphData[i].Count, width);
  645. }
  646. }
  647. // When 'git log --first-parent' filtration is enabled and when only current
  648. // branch needed to be rendered (and this filter actually works),
  649. // it is much more readable to limit max lanes to 1.
  650. int maxLanes =
  651. (AppSettings.ShowFirstParent &&
  652. AppSettings.ShowCurrentBranchOnly &&
  653. AppSettings.BranchFilterEnabled) ? 1: MaxLanes;
  654. laneCount = Math.Min(Math.Max(laneCount, width), maxLanes);
  655. }
  656. if (GraphColumn.Width != _laneWidth*laneCount && _laneWidth*laneCount > GraphColumn.MinimumWidth)
  657. GraphColumn.Width = _laneWidth*laneCount;
  658. }
  659. }
  660. //Color of non-relative branches.
  661. private List<Color> GetJunctionColors(IEnumerable<Junction> aJunction)
  662. {
  663. List<Color> colors = new List<Color>();
  664. foreach (Junction j in aJunction)
  665. {
  666. colors.Add(GetJunctionColor(j));
  667. }
  668. if (colors.Count == 0)
  669. {
  670. colors.Add(Color.Black);
  671. }
  672. return colors;
  673. }
  674. private RevisionGraphDrawStyleEnum _revisionGraphDrawStyle;
  675. [DefaultValue(RevisionGraphDrawStyleEnum.DrawNonRelativesGray)]
  676. [Browsable(false)]
  677. public RevisionGraphDrawStyleEnum RevisionGraphDrawStyle
  678. {
  679. get
  680. {
  681. if (_revisionGraphDrawStyle == RevisionGraphDrawStyleEnum.HighlightSelected)
  682. return _revisionGraphDrawStyle;
  683. if (AppSettings.RevisionGraphDrawNonRelativesGray)
  684. return RevisionGraphDrawStyleEnum.DrawNonRelativesGray;
  685. return RevisionGraphDrawStyleEnum.Normal;
  686. }
  687. set
  688. {
  689. _revisionGraphDrawStyle = value;
  690. }
  691. }
  692. // http://en.wikipedia.org/wiki/File:RBG_color_wheel.svg
  693. private Color GetJunctionColor(Junction aJunction)
  694. {
  695. //Draw non-relative branches gray
  696. if (!aJunction.IsRelative && revisionGraphDrawStyleCache == RevisionGraphDrawStyleEnum.DrawNonRelativesGray)
  697. return _nonRelativeColor;
  698. //Draw non-highlighted branches gray
  699. if (!aJunction.HighLight && revisionGraphDrawStyleCache == RevisionGraphDrawStyleEnum.HighlightSelected)
  700. return _nonRelativeColor;
  701. if (!AppSettings.MulticolorBranches)
  702. return AppSettings.GraphColor;
  703. // This is the order to grab the colors in.
  704. int[] preferedColors = { 4, 8, 6, 10, 2, 5, 7, 3, 9, 1, 11 };
  705. int colorIndex;
  706. if (_junctionColors.TryGetValue(aJunction, out colorIndex))
  707. {
  708. return _possibleColors[colorIndex];
  709. }
  710. // Get adjacent junctions
  711. var adjacentJunctions = new List<Junction>();
  712. var adjacentColors = new List<int>();
  713. adjacentJunctions.AddRange(aJunction.Youngest.Ancestors);
  714. adjacentJunctions.AddRange(aJunction.Youngest.Descendants);
  715. adjacentJunctions.AddRange(aJunction.Oldest.Ancestors);
  716. adjacentJunctions.AddRange(aJunction.Oldest.Descendants);
  717. foreach (Junction peer in adjacentJunctions)
  718. {
  719. if (_junctionColors.TryGetValue(peer, out colorIndex))
  720. {
  721. adjacentColors.Add(colorIndex);
  722. }
  723. else
  724. {
  725. colorIndex = -1;
  726. }
  727. }
  728. if (adjacentColors.Count == 0) //This is an end-point. We need to 'pick' a new color
  729. {
  730. colorIndex = 0;
  731. }
  732. else //This is a parent branch, calculate new color based on parent branch
  733. {
  734. int start = adjacentColors[0];
  735. int i;
  736. for (i = 0; i < preferedColors.Length; i++)
  737. {
  738. colorIndex = (start + preferedColors[i]) % _possibleColors.Length;
  739. if (!adjacentColors.Contains(colorIndex))
  740. {
  741. break;
  742. }
  743. }
  744. if (i == preferedColors.Length)
  745. {
  746. var r = new Random();
  747. colorIndex = r.Next(preferedColors.Length);
  748. }
  749. }
  750. _junctionColors[aJunction] = colorIndex;
  751. return _possibleColors[colorIndex];
  752. }
  753. public override void Refresh()
  754. {
  755. ClearDrawCache();
  756. base.Refresh();
  757. }
  758. private void ClearDrawCache()
  759. {
  760. _cacheHead = 0;
  761. _cacheCount = 0;
  762. }
  763. private Rectangle DrawGraph(int aNeededRow)
  764. {
  765. if (aNeededRow < 0 || _graphData.Count == 0 || _graphData.Count <= aNeededRow)
  766. {
  767. return Rectangle.Empty;
  768. }
  769. #region Make sure the graph cache bitmap is setup
  770. int height = _cacheCountMax * _rowHeight;
  771. int width = GraphColumn.Width;
  772. if (_graphBitmap == null ||
  773. //Resize the bitmap when the with or height is changed. The height won't change very often.
  774. //The with changes more often, when branches become visible/invisible.
  775. //Try to be 'smart' and not resize the bitmap for each little change. Enlarge when needed
  776. //but never shrink the bitmap since the huge performance hit is worse than the little extra memory.
  777. _graphBitmap.Width < width || _graphBitmap.Height != height)
  778. {
  779. if (_graphBitmap != null)
  780. {
  781. _graphBitmap.Dispose();
  782. _graphBitmap = null;
  783. }
  784. if (width > 0 && height > 0)
  785. {
  786. _graphBitmap = new Bitmap(Math.Max(width, _laneWidth * 3), height, PixelFormat.Format32bppPArgb);
  787. _graphWorkArea = Graphics.FromImage(_graphBitmap);
  788. _graphWorkArea.SmoothingMode = SmoothingMode.AntiAlias;
  789. _cacheHead = 0;
  790. _cacheCount = 0;
  791. }
  792. else
  793. {
  794. return Rectangle.Empty;
  795. }
  796. }
  797. #endregion
  798. // Compute how much the head needs to move to show the requested item.
  799. int neededHeadAdjustment = aNeededRow - _cacheHead;
  800. if (neededHeadAdjustment > 0)
  801. {
  802. neededHeadAdjustment -= _cacheCountMax - 1;
  803. if (neededHeadAdjustment < 0)
  804. {
  805. neededHeadAdjustment = 0;
  806. }
  807. }
  808. int newRows = 0;
  809. if (_cacheCount < _cacheCountMax)
  810. {
  811. newRows = (aNeededRow - _cacheCount) + 1;
  812. }
  813. // Adjust the head of the cache
  814. _cacheHead = _cacheHead + neededHeadAdjustment;
  815. _cacheHeadRow = (_cacheHeadRow + neededHeadAdjustment) % _cacheCountMax;
  816. if (_cacheHeadRow < 0)
  817. {
  818. _cacheHeadRow = _cacheCountMax + _cacheHeadRow;
  819. }
  820. int start;
  821. int end;
  822. if (newRows > 0)
  823. {
  824. start = _cacheHead + _cacheCount;
  825. _cacheCount = Math.Min(_cacheCount + newRows, _cacheCountMax);
  826. end = _cacheHead + _cacheCount;
  827. }
  828. else if (neededHeadAdjustment > 0)
  829. {
  830. end = _cacheHead + _cacheCount;
  831. start = Math.Max(_cacheHead, end - neededHeadAdjustment);
  832. }
  833. else if (neededHeadAdjustment < 0)
  834. {
  835. start = _cacheHead;
  836. end = start + Math.Min(_cacheCountMax, -neededHeadAdjustment);
  837. }
  838. else
  839. {
  840. // Item already in the cache
  841. return CreateRectangle(aNeededRow, width);
  842. }
  843. if (RevisionGraphVisible)
  844. {
  845. if (!DrawVisibleGraph(start, end))
  846. return Rectangle.Empty;
  847. }
  848. return CreateRectangle(aNeededRow, width);
  849. }
  850. private bool DrawVisibleGraph(int start, int end)
  851. {
  852. for (int rowIndex = start; rowIndex < end; rowIndex++)
  853. {
  854. Graph.ILaneRow row = _graphData[rowIndex];
  855. if (row == null)
  856. {
  857. // This shouldn't be happening...If it does, clear the cache so we
  858. // eventually pick it up.
  859. Debug.WriteLine("Draw lane {0} NO DATA", rowIndex.ToString());
  860. ClearDrawCache();
  861. return false;
  862. }
  863. Region oldClip = _graphWorkArea.Clip;
  864. // Get the x,y value of the current item's upper left in the cache
  865. int curCacheRow = (_cacheHeadRow + rowIndex - _cacheHead) % _cacheCountMax;
  866. int x = 0;
  867. int y = curCacheRow * _rowHeight;
  868. var laneRect = new Rectangle(0, y, Width, _rowHeight);
  869. if (rowIndex == start || curCacheRow == 0)
  870. {
  871. // Draw previous row first. Clip top to row. We also need to clear the area
  872. // before we draw since nothing else would clear the top 1/2 of the item to draw.
  873. _graphWorkArea.RenderingOrigin = new Point(x, y - _rowHeight);
  874. var newClip = new Region(laneRect);
  875. _graphWorkArea.Clip = newClip;
  876. _graphWorkArea.Clear(Color.Transparent);
  877. DrawItem(_graphWorkArea, _graphData[rowIndex - 1]);
  878. _graphWorkArea.Clip = oldClip;
  879. }
  880. bool isLast = (rowIndex == end - 1);
  881. if (isLast)
  882. {
  883. var newClip = new Region(laneRect);
  884. _graphWorkArea.Clip = newClip;
  885. }
  886. _graphWorkArea.RenderingOrigin = new Point(x, y);
  887. bool success = DrawItem(_graphWorkArea, row);
  888. _graphWorkArea.Clip = oldClip;
  889. if (!success)
  890. {
  891. ClearDrawCache();
  892. return false;
  893. }
  894. }
  895. return true;
  896. }
  897. private Rectangle CreateRectangle(int aNeededRow, int width)
  898. {
  899. return new Rectangle
  900. (
  901. 0,
  902. (_cacheHeadRow + aNeededRow - _cacheHead) % _cacheCountMax * RowTemplate.Height,
  903. width,
  904. _rowHeight
  905. );
  906. }
  907. // end drawGraph
  908. private RevisionGraphDrawStyleEnum revisionGraphDrawStyleCache;
  909. private bool DrawItem(Graphics wa, Graph.ILaneRow row)
  910. {
  911. if (row == null || row.NodeLane == -1)
  912. {
  913. return false;
  914. }
  915. // Clip to the area we're drawing in, but draw 1 pixel past so
  916. // that the top/bottom of the line segment's anti-aliasing isn't
  917. // visible in the final rendering.
  918. int top = wa.RenderingOrigin.Y + _rowHeight / 2;
  919. var laneRect = new Rectangle(0, top, Width, _rowHeight);
  920. Region oldClip = wa.Clip;
  921. var newClip = new Region(laneRect);
  922. newClip.Intersect(oldClip);
  923. wa.Clip = newClip;
  924. wa.Clear(Color.Transparent);
  925. //Getting RevisionGraphDrawStyle results in call to AppSettings. This is not very cheap, cache.
  926. revisionGraphDrawStyleCache = RevisionGraphDrawStyle;
  927. //for (int r = 0; r < 2; r++)
  928. for (int lane = 0; lane < row.Count; lane++)
  929. {
  930. int mid = wa.RenderingOrigin.X + (int)((lane + 0.5) * _laneWidth);
  931. for (int item = 0; item < row.LaneInfoCount(lane); item++)
  932. {
  933. Graph.LaneInfo laneInfo = row[lane, item];
  934. bool highLight = (revisionGraphDrawStyleCache == RevisionGraphDrawStyleEnum.DrawNonRelativesGray && laneInfo.Junctions.Any(j => j.IsRelative)) ||
  935. (revisionGraphDrawStyleCache == RevisionGraphDrawStyleEnum.HighlightSelected && laneInfo.Junctions.Any(j => j.HighLight)) ||
  936. (revisionGraphDrawStyleCache == RevisionGraphDrawStyleEnum.Normal);
  937. List<Color> curColors = GetJunctionColors(laneInfo.Junctions);
  938. // Create the brush for drawing the line
  939. Brush brushLineColor = null;
  940. Pen brushLineColorPen = null;
  941. try
  942. {
  943. bool drawBorder = highLight && AppSettings.BranchBorders; //hide border for "non-relatives"
  944. if (curColors.Count == 1 || !AppSettings.StripedBranchChange)
  945. {
  946. if (curColors[0] != _nonRelativeColor)
  947. {
  948. brushLineColor = new SolidBrush(curColors[0]);
  949. }
  950. else if (curColors.Count > 1 && curColors[1] != _nonRelativeColor)
  951. {
  952. brushLineColor = new SolidBrush(curColors[1]);
  953. }
  954. else
  955. {
  956. drawBorder = false;
  957. brushLineColor = new SolidBrush(_nonRelativeColor);
  958. }
  959. }
  960. else
  961. {
  962. Color lastRealColor = curColors.LastOrDefault(c => c != _nonRelativeColor);
  963. if (lastRealColor.IsEmpty)
  964. {
  965. brushLineColor = new SolidBrush(_nonRelativeColor);
  966. drawBorder = false;
  967. }
  968. else
  969. {
  970. brushLineColor = new HatchBrush(HatchStyle.DarkDownwardDiagonal, curColors[0], lastRealColor);
  971. }
  972. }
  973. // Precalculate line endpoints
  974. bool singleLane = laneInfo.ConnectLane == lane;
  975. int x0 = mid;
  976. int y0 = top - 1;
  977. int x1 = singleLane ? x0 : mid + (laneInfo.ConnectLane - lane) * _laneWidth;
  978. int y1 = top + _rowHeight;
  979. Point p0 = new Point(x0, y0);
  980. Point p1 = new Point(x1, y1);
  981. // Precalculate curve control points when needed
  982. Point c0, c1;
  983. if (singleLane)
  984. {
  985. c0 = c1 = Point.Empty;
  986. }
  987. else
  988. {
  989. // Controls the curvature of cross-lane lines (0 = straight line, 1 = 90 degree turns)
  990. const float severity = 0.5f;
  991. c0 = new Point(x0, (int)(y0 * (1.0f - severity) + y1 * severity));
  992. c1 = new Point(x1, (int)(y1 * (1.0f - severity) + y0 * severity));
  993. }
  994. for (int i = drawBorder ? 0 : 2; i < 3; i++)
  995. {
  996. Pen penLine;
  997. switch (i)
  998. {
  999. case 0:
  1000. penLine = _whiteBorderPen;
  1001. break;
  1002. case 1:
  1003. penLine = _blackBorderPen;
  1004. break;
  1005. default:
  1006. if (brushLineColorPen == null)
  1007. brushLineColorPen = new Pen(brushLineColor, _laneLineWidth);
  1008. penLine = brushLineColorPen;
  1009. break;
  1010. }
  1011. if (singleLane)
  1012. {
  1013. wa.DrawLine(penLine, p0, p1);
  1014. }
  1015. else
  1016. {
  1017. wa.DrawBezier(penLine, p0, c0, c1, p1);
  1018. }
  1019. }
  1020. }
  1021. finally
  1022. {
  1023. if (brushLineColorPen != null)
  1024. ((IDisposable)brushLineColorPen).Dispose();
  1025. if (brushLineColor != null)
  1026. ((IDisposable)brushLineColor).Dispose();
  1027. }
  1028. }
  1029. }
  1030. // Reset the clip region
  1031. wa.Clip = oldClip;
  1032. {
  1033. // Draw node
  1034. var nodeRect = new Rectangle
  1035. (
  1036. wa.RenderingOrigin.X + (_laneWidth - _nodeDimension) / 2 + row.NodeLane * _laneWidth,
  1037. wa.RenderingOrigin.Y + (_rowHeight - _nodeDimension) / 2,
  1038. _nodeDimension,
  1039. _nodeDimension
  1040. );
  1041. Brush nodeBrush;
  1042. List<Color> nodeColors = GetJunctionColors(row.Node.Ancestors);
  1043. bool highlight = (revisionGraphDrawStyleCache == RevisionGraphDrawStyleEnum.DrawNonRelativesGray && row.Node.Ancestors.Any(j => j.IsRelative)) ||
  1044. (revisionGraphDrawStyleCache == RevisionGraphDrawStyleEnum.HighlightSelected && row.Node.Ancestors.Any(j => j.HighLight)) ||
  1045. (revisionGraphDrawStyleCache == RevisionGraphDrawStyleEnum.Normal);
  1046. bool drawBorder = AppSettings.BranchBorders && highlight;
  1047. if (nodeColors.Count == 1)
  1048. {
  1049. nodeBrush = new SolidBrush(highlight ? nodeColors[0] : _nonRelativeColor);
  1050. if (nodeColors[0] == _nonRelativeColor) drawBorder = false;
  1051. }
  1052. else
  1053. {
  1054. nodeBrush = new LinearGradientBrush(nodeRect, nodeColors[0], nodeColors[1],
  1055. LinearGradientMode.Horizontal);
  1056. if (nodeColors.All(c => c == _nonRelativeColor))
  1057. drawBorder = false;
  1058. }
  1059. if (_filterMode == FilterType.Highlight && row.Node.IsFiltered)
  1060. {
  1061. Rectangle highlightRect = nodeRect;
  1062. highlightRect.Inflate(2, 3);
  1063. wa.FillRectangle(Brushes.Yellow, highlightRect);
  1064. wa.DrawRectangle(Pens.Black, highlightRect);
  1065. }
  1066. if (row.Node.Data == null)
  1067. {
  1068. wa.FillEllipse(Brushes.White, nodeRect);
  1069. using (var pen = new Pen(Color.Red, 2))
  1070. {
  1071. wa.DrawEllipse(pen, nodeRect);
  1072. }
  1073. }
  1074. else if (row.Node.IsActive)
  1075. {
  1076. wa.FillRectangle(nodeBrush, nodeRect);
  1077. nodeRect.Inflate(1, 1);
  1078. using (var pen = new Pen(Color.Black, 3))
  1079. wa.DrawRectangle(pen, nodeRect);
  1080. }
  1081. else if (row.Node.IsSpecial)
  1082. {
  1083. wa.FillRectangle(nodeBrush, nodeRect);
  1084. if (drawBorder)
  1085. {
  1086. wa.DrawRectangle(Pens.Black, nodeRect);
  1087. }
  1088. }
  1089. else
  1090. {
  1091. wa.FillEllipse(nodeBrush, nodeRect);
  1092. if (drawBorder)
  1093. {
  1094. wa.DrawEllipse(Pens.Black, nodeRect);
  1095. }
  1096. }
  1097. }
  1098. return true;
  1099. }
  1100. public void HighlightBranch(string aId)
  1101. {
  1102. _graphData.HighlightBranch(aId);
  1103. Update();
  1104. }
  1105. public bool IsRevisionRelative(string aGuid)
  1106. {
  1107. return _graphData.IsRevisionRelative(aGuid);
  1108. }
  1109. public GitRevision GetRevision(string guid)
  1110. {
  1111. Node node;
  1112. if (_graphData.Nodes.TryGetValue(guid, out node))
  1113. return node.Data;
  1114. return null;
  1115. }
  1116. public int? TryGetRevisionIndex(string guid)
  1117. {
  1118. Node node;
  1119. if (guid != null)
  1120. {
  1121. if (_graphData.Nodes.TryGetValue(guid, out node))
  1122. {
  1123. return node.Index;
  1124. }
  1125. }
  1126. return null;
  1127. }
  1128. public List<string> GetRevisionChildren(string guid)
  1129. {
  1130. Node node;
  1131. List<string> childrenIds = new List<string>();
  1132. //We do not need a lock here since we load the data from the first commit and walkt through all
  1133. //parents. Children are always loaded, since we start at the newest commit.
  1134. //With lock, loading the commit info slows down terrible.
  1135. if (_graphData.Nodes.TryGetValue(guid, out node))
  1136. {
  1137. foreach (var descendant in node.Descendants)
  1138. {
  1139. childrenIds.Add(descendant.ChildOf(node).Id);
  1140. }
  1141. }
  1142. return childrenIds;
  1143. }
  1144. private void dataGrid_Resize(object sender, EventArgs e)
  1145. {
  1146. _rowHeight = RowTemplate.Height;
  1147. // Keep an extra page in the cache
  1148. _cacheCountMax = Height * 2 / _rowHeight + 1;
  1149. ClearDrawCache();
  1150. dataGrid_Scroll(null, null);
  1151. }
  1152. protected override void OnKeyDown(KeyEventArgs e)
  1153. {
  1154. if (e.KeyData == Keys.Home)
  1155. {
  1156. if (RowCount != 0)
  1157. {
  1158. ClearSelection();
  1159. Rows[0].Selected = true;
  1160. CurrentCell = Rows[0].Cells[1];
  1161. }
  1162. return;
  1163. }
  1164. else if (e.KeyData == Keys.End)
  1165. {
  1166. if (RowCount != 0)
  1167. {
  1168. ClearSelection();
  1169. Rows[RowCount - 1].Selected = true;
  1170. CurrentCell = Rows[RowCount - 1].Cells[1];
  1171. }
  1172. return;
  1173. }
  1174. base.OnKeyDown(e);
  1175. }
  1176. #region Nested type: Node
  1177. private sealed class Node
  1178. {
  1179. public readonly List<Junction> Ancestors = new List<Junction>();
  1180. public readonly List<Junction> Descendants = new List<Junction>();
  1181. public readonly string Id;
  1182. public GitRevision Data;
  1183. public DataType DataType;
  1184. public int InLane = int.MaxValue;
  1185. public int Index = int.MaxValue;
  1186. public Node(string aId)
  1187. {
  1188. Id = aId;
  1189. }
  1190. public bool IsActive
  1191. {
  1192. get { return (DataType & DataType.Active) == DataType.Active; }
  1193. }
  1194. public bool IsFiltered
  1195. {
  1196. get { return (DataType & DataType.Filtered) == DataType.Filtered; }
  1197. set
  1198. {
  1199. if (value)
  1200. {
  1201. DataType |= DataType.Filtered;
  1202. }
  1203. else
  1204. {
  1205. DataType &= ~DataType.Filtered;
  1206. }
  1207. }
  1208. }
  1209. public bool IsSpecial
  1210. {
  1211. get { return (DataType & DataType.Special) == DataType.Special; }
  1212. }
  1213. public override string ToString()
  1214. {
  1215. if (Data == null)
  1216. {
  1217. string name = Id.ToString();
  1218. if (name.Length > 8)
  1219. {
  1220. name = name.Substring(0, 4) + ".." + name.Substring(name.Length - 4, 4);
  1221. }
  1222. return string.Format("{0} ({1})", name, Index);
  1223. }
  1224. return Data.ToString();
  1225. }
  1226. }
  1227. #endregion
  1228. }
  1229. // end of class DvcsGraph
  1230. }