PageRenderTime 65ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/GitUI/UserControls/FileStatusList.cs

https://github.com/qgppl/gitextensions
C# | 1002 lines | 864 code | 119 blank | 19 comment | 181 complexity | c748a9cd87f7cd8c72bdb78c9c0691e0 MD5 | raw file
Possible License(s): GPL-3.0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.Specialized;
  4. using System.ComponentModel;
  5. using System.Drawing;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Reactive.Linq;
  9. using System.Text.RegularExpressions;
  10. using System.Threading;
  11. using System.Threading.Tasks;
  12. using System.Windows.Forms;
  13. using GitCommands;
  14. using GitUI.Properties;
  15. using GitUI.UserControls;
  16. using ResourceManager;
  17. namespace GitUI
  18. {
  19. using GitItemsWithParents = IDictionary<string, IList<GitItemStatus>>;
  20. public sealed partial class FileStatusList : GitModuleControl
  21. {
  22. private readonly TranslationString _UnsupportedMultiselectAction =
  23. new TranslationString("Operation not supported");
  24. private readonly TranslationString _DiffWithParent =
  25. new TranslationString("Diff with parent");
  26. public readonly TranslationString CombinedDiff =
  27. new TranslationString("Combined Diff");
  28. private IDisposable selectedIndexChangeSubscription;
  29. private static readonly TimeSpan SelectedIndexChangeThrottleDuration = TimeSpan.FromMilliseconds(50);
  30. private const int ImageSize = 16;
  31. private bool _filterVisible;
  32. public FileStatusList()
  33. {
  34. InitializeComponent(); Translate();
  35. FilterVisible = false;
  36. SelectFirstItemOnSetItems = true;
  37. _noDiffFilesChangesDefaultText = NoFiles.Text;
  38. #if !__MonoCS__ // TODO Drag'n'Drop doesn't work on Mono/Linux
  39. FileStatusListView.MouseMove += FileStatusListView_MouseMove;
  40. FileStatusListView.MouseDown += FileStatusListView_MouseDown;
  41. #endif
  42. if (_images == null)
  43. {
  44. _images = new ImageList();
  45. _images.Images.Add(Resources.Removed); // 0
  46. _images.Images.Add(Resources.Added); // 1
  47. _images.Images.Add(Resources.Modified); // 2
  48. _images.Images.Add(Resources.Renamed); // 3
  49. _images.Images.Add(Resources.Copied); // 4
  50. _images.Images.Add(Resources.IconSubmoduleDirty); // 5
  51. _images.Images.Add(Resources.IconSubmoduleRevisionUp); // 6
  52. _images.Images.Add(Resources.IconSubmoduleRevisionUpDirty); // 7
  53. _images.Images.Add(Resources.IconSubmoduleRevisionDown); // 8
  54. _images.Images.Add(Resources.IconSubmoduleRevisionDownDirty); // 9
  55. _images.Images.Add(Resources.IconSubmoduleRevisionSemiUp); // 10
  56. _images.Images.Add(Resources.IconSubmoduleRevisionSemiUpDirty); // 11
  57. _images.Images.Add(Resources.IconSubmoduleRevisionSemiDown); // 12
  58. _images.Images.Add(Resources.IconSubmoduleRevisionSemiDownDirty); // 13
  59. _images.Images.Add(Resources.IconFileStatusUnknown); // 14
  60. }
  61. FileStatusListView.SmallImageList = _images;
  62. FileStatusListView.LargeImageList = _images;
  63. HandleVisibility_NoFilesLabel_FilterComboBox(filesPresent: true);
  64. this.Controls.SetChildIndex(NoFiles, 0);
  65. NoFiles.Font = new Font(SystemFonts.MessageBoxFont, FontStyle.Italic);
  66. _filter = new Regex(".*");
  67. }
  68. protected override void DisposeCustomResources()
  69. {
  70. if (selectedIndexChangeSubscription != null)
  71. {
  72. selectedIndexChangeSubscription.Dispose();
  73. }
  74. }
  75. private void EnsureSelectedIndexChangeSubscription()
  76. {
  77. if (selectedIndexChangeSubscription == null)
  78. {
  79. selectedIndexChangeSubscription = Observable.FromEventPattern(
  80. h => FileStatusListView.SelectedIndexChanged += h,
  81. h => FileStatusListView.SelectedIndexChanged -= h)
  82. .Throttle(SelectedIndexChangeThrottleDuration)
  83. .ObserveOn(SynchronizationContext.Current)
  84. .Subscribe(_ => FileStatusListView_SelectedIndexChanged());
  85. }
  86. }
  87. private static ImageList _images;
  88. private readonly string _noDiffFilesChangesDefaultText;
  89. public void SetNoFilesText(string text)
  90. {
  91. NoFiles.Text = text;
  92. }
  93. public string GetNoFilesText()
  94. {
  95. return NoFiles.Text;
  96. }
  97. public bool FilterVisible
  98. {
  99. get
  100. {
  101. return _filterVisible;
  102. }
  103. set
  104. {
  105. _filterVisible = value;
  106. FilterComboBox.Visible = _filterVisible;
  107. FilterWatermarkLabel.Visible = _filterVisible;
  108. }
  109. }
  110. public override bool Focused
  111. {
  112. get
  113. {
  114. return FileStatusListView.Focused;
  115. }
  116. }
  117. public new void Focus()
  118. {
  119. if (FileStatusListView.Items.Count > 0)
  120. {
  121. if (SelectedItem == null)
  122. SelectedIndex = 0;
  123. FileStatusListView.Focus();
  124. }
  125. }
  126. public void BeginUpdate()
  127. {
  128. FileStatusListView.BeginUpdate();
  129. }
  130. public void EndUpdate()
  131. {
  132. FileStatusListView.EndUpdate();
  133. }
  134. private string GetItemText(Graphics graphics, GitItemStatus gitItemStatus)
  135. {
  136. var pathFormatter = new PathFormatter(graphics, FileStatusListView.Font);
  137. return pathFormatter.FormatTextForDrawing(FileStatusListView.ClientSize.Width - ImageSize,
  138. gitItemStatus.Name, gitItemStatus.OldName);
  139. }
  140. private void FileStatusListView_DrawItem(object sender, DrawListViewItemEventArgs e)
  141. {
  142. if (e.Bounds.Height <= 0 || e.Bounds.Width <= 0 || e.ItemIndex < 0)
  143. return;
  144. e.DrawBackground();
  145. Color color;
  146. if (e.Item.Selected)
  147. {
  148. e.Graphics.FillRectangle(SystemBrushes.Highlight, e.Bounds);
  149. color = SystemColors.HighlightText;
  150. }
  151. else
  152. color = SystemColors.WindowText;
  153. e.DrawFocusRectangle();
  154. e.Graphics.FillRectangle(Brushes.White, e.Bounds.Left, e.Bounds.Top, ImageSize, e.Bounds.Height);
  155. int centeredImageTop = e.Bounds.Top;
  156. if ((e.Bounds.Height - ImageSize) > 1)
  157. centeredImageTop = e.Bounds.Top + ((e.Bounds.Height - ImageSize) / 2);
  158. var image = e.Item.ImageList.Images[e.Item.ImageIndex];
  159. if (image != null)
  160. e.Graphics.DrawImage(image, e.Bounds.Left, centeredImageTop, ImageSize, ImageSize);
  161. GitItemStatus gitItemStatus = (GitItemStatus)e.Item.Tag;
  162. string text = GetItemText(e.Graphics, gitItemStatus);
  163. if (gitItemStatus.IsSubmodule && gitItemStatus.SubmoduleStatus != null && gitItemStatus.SubmoduleStatus.IsCompleted)
  164. text += gitItemStatus.SubmoduleStatus.Result.AddedAndRemovedString();
  165. e.Graphics.DrawString(text, e.Item.ListView.Font,
  166. new SolidBrush(color), e.Bounds.Left + ImageSize, e.Bounds.Top);
  167. }
  168. #if !__MonoCS__ // TODO Drag'n'Drop doesnt work on Mono/Linux
  169. void FileStatusListView_MouseDown(object sender, MouseEventArgs e)
  170. {
  171. //SELECT
  172. if (e.Button == MouseButtons.Right)
  173. {
  174. var hover = FileStatusListView.HitTest(e.Location);
  175. if (hover.Item != null && !hover.Item.Selected)
  176. {
  177. ClearSelected();
  178. hover.Item.Selected = true;
  179. }
  180. }
  181. //DRAG
  182. if (e.Button == MouseButtons.Left)
  183. {
  184. if (SelectedItems.Any())
  185. {
  186. // Remember the point where the mouse down occurred.
  187. // The DragSize indicates the size that the mouse can move
  188. // before a drag event should be started.
  189. Size dragSize = SystemInformation.DragSize;
  190. // Create a rectangle using the DragSize, with the mouse position being
  191. // at the center of the rectangle.
  192. dragBoxFromMouseDown = new Rectangle(new Point(e.X - (dragSize.Width / 2),
  193. e.Y - (dragSize.Height / 2)),
  194. dragSize);
  195. }
  196. else
  197. // Reset the rectangle if the mouse is not over an item in the ListView.
  198. dragBoxFromMouseDown = Rectangle.Empty;
  199. }
  200. }
  201. #endif
  202. public override ContextMenuStrip ContextMenuStrip
  203. {
  204. get
  205. {
  206. return FileStatusListView.ContextMenuStrip;
  207. }
  208. set
  209. {
  210. FileStatusListView.ContextMenuStrip = value;
  211. }
  212. }
  213. public override ContextMenu ContextMenu
  214. {
  215. get
  216. {
  217. return FileStatusListView.ContextMenu;
  218. }
  219. set
  220. {
  221. FileStatusListView.ContextMenu = value;
  222. }
  223. }
  224. #if !__MonoCS__ // TODO Drag'n'Drop doesnt work on Mono/Linux
  225. private Rectangle dragBoxFromMouseDown;
  226. void FileStatusListView_MouseMove(object sender, MouseEventArgs e)
  227. {
  228. ListView listView = sender as ListView;
  229. //DRAG
  230. // If the mouse moves outside the rectangle, start the drag.
  231. if (dragBoxFromMouseDown != Rectangle.Empty &&
  232. !dragBoxFromMouseDown.Contains(e.X, e.Y))
  233. {
  234. if (SelectedItems.Any())
  235. {
  236. StringCollection fileList = new StringCollection();
  237. foreach (GitItemStatus item in SelectedItems)
  238. {
  239. string fileName = Path.Combine(Module.WorkingDir, item.Name);
  240. fileList.Add(fileName.ToNativePath());
  241. }
  242. DataObject obj = new DataObject();
  243. obj.SetFileDropList(fileList);
  244. // Proceed with the drag and drop, passing in the list item.
  245. DoDragDrop(obj, DragDropEffects.Copy);
  246. dragBoxFromMouseDown = Rectangle.Empty;
  247. }
  248. }
  249. //TOOLTIP
  250. if (listView != null)
  251. {
  252. var point = new Point(e.X, e.Y);
  253. var hover = listView.HitTest(point);
  254. if (hover.Item != null)
  255. {
  256. var gitItemStatus = (GitItemStatus)hover.Item.Tag;
  257. string text;
  258. if (gitItemStatus.IsRenamed || gitItemStatus.IsCopied)
  259. text = string.Concat(gitItemStatus.Name, " (", gitItemStatus.OldName, ")");
  260. else
  261. text = gitItemStatus.Name;
  262. float fTextWidth = listView.CreateGraphics().MeasureString(text, listView.Font).Width + 17;
  263. //Use width-itemheight because the icon drawn in front of the text is the itemheight
  264. if (fTextWidth > (FileStatusListView.Width - FileStatusListView.GetItemRect(hover.Item.Index).Height))
  265. {
  266. if (!hover.Item.ToolTipText.Equals(gitItemStatus.ToString()))
  267. hover.Item.ToolTipText = gitItemStatus.ToString();
  268. }
  269. else
  270. hover.Item.ToolTipText = "";
  271. }
  272. }
  273. }
  274. #endif
  275. [Browsable(false)]
  276. public IEnumerable<GitItemStatus> AllItems
  277. {
  278. get
  279. {
  280. return (FileStatusListView.Items.Cast<ListViewItem>().
  281. Select(selectedItem => (GitItemStatus)selectedItem.Tag));
  282. }
  283. }
  284. [Browsable(false)]
  285. public IEnumerable<GitItemStatus> SelectedItems
  286. {
  287. get
  288. {
  289. return FileStatusListView.SelectedItems.Cast<ListViewItem>().
  290. Select(i => (GitItemStatus)i.Tag);
  291. }
  292. set
  293. {
  294. ClearSelected();
  295. if (value == null)
  296. return;
  297. foreach (var item in FileStatusListView.Items.Cast<ListViewItem>()
  298. .Where(i => value.Contains((GitItemStatus)i.Tag)))
  299. {
  300. item.Selected = true;
  301. }
  302. var first = FileStatusListView.SelectedItems.Cast<ListViewItem>().FirstOrDefault(x => x.Selected);
  303. if (first != null)
  304. first.EnsureVisible();
  305. StoreNextIndexToSelect();
  306. }
  307. }
  308. [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  309. [Browsable(false)]
  310. public GitItemStatus SelectedItem
  311. {
  312. get
  313. {
  314. if (FileStatusListView.SelectedItems.Count > 0)
  315. {
  316. ListViewItem item = FileStatusListView.SelectedItems[0];
  317. return (GitItemStatus)item.Tag;
  318. }
  319. return null;
  320. }
  321. set
  322. {
  323. ClearSelected();
  324. if (value == null)
  325. return;
  326. ListViewItem newSelected = null;
  327. foreach (ListViewItem item in FileStatusListView.Items)
  328. {
  329. if (value.CompareTo((GitItemStatus)item.Tag) == 0)
  330. {
  331. if (newSelected == null)
  332. {
  333. newSelected = item;
  334. }
  335. else if (item.Tag == value)
  336. {
  337. newSelected = item;
  338. break;
  339. }
  340. }
  341. }
  342. if (newSelected != null)
  343. {
  344. newSelected.Selected = true;
  345. newSelected.EnsureVisible();
  346. }
  347. }
  348. }
  349. [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  350. [Browsable(false)]
  351. public string SelectedItemParent
  352. {
  353. get
  354. {
  355. foreach (ListViewItem item in FileStatusListView.SelectedItems)
  356. return item.Group != null ? (string)item.Group.Tag : null;
  357. return null;
  358. }
  359. }
  360. public void ClearSelected()
  361. {
  362. foreach (ListViewItem item in FileStatusListView.SelectedItems)
  363. item.Selected = false;
  364. }
  365. [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  366. [Browsable(false)]
  367. public int SelectedIndex
  368. {
  369. get
  370. {
  371. foreach (int i in FileStatusListView.SelectedIndices)
  372. return i;
  373. return -1;
  374. }
  375. set
  376. {
  377. ClearSelected();
  378. if (value >= 0)
  379. {
  380. FileStatusListView.Items[value].Selected = true;
  381. FileStatusListView.Items[value].Focused = true;
  382. FileStatusListView.Items[value].EnsureVisible();
  383. }
  384. }
  385. }
  386. private int _nextIndexToSelect = -1;
  387. public void StoreNextIndexToSelect()
  388. {
  389. _nextIndexToSelect = -1;
  390. foreach (int idx in FileStatusListView.SelectedIndices)
  391. if (idx > _nextIndexToSelect)
  392. _nextIndexToSelect = idx;
  393. _nextIndexToSelect = _nextIndexToSelect - FileStatusListView.SelectedIndices.Count + 1;
  394. }
  395. public void SelectStoredNextIndex(int defaultIndex = -1)
  396. {
  397. _nextIndexToSelect = Math.Min(_nextIndexToSelect, FileStatusListView.Items.Count - 1);
  398. if (_nextIndexToSelect < 0 && defaultIndex > -1)
  399. _nextIndexToSelect = Math.Min(defaultIndex, FileStatusListView.Items.Count - 1);
  400. if (_nextIndexToSelect > -1)
  401. SelectedIndex = _nextIndexToSelect;
  402. _nextIndexToSelect = -1;
  403. }
  404. public event EventHandler SelectedIndexChanged;
  405. public event EventHandler DataSourceChanged;
  406. public new event EventHandler DoubleClick;
  407. public new event KeyEventHandler KeyDown;
  408. void FileStatusListView_DoubleClick(object sender, EventArgs e)
  409. {
  410. if (DoubleClick == null)
  411. UICommands.StartFileHistoryDialog(this, SelectedItem.Name, Revision);
  412. else
  413. DoubleClick(sender, e);
  414. }
  415. void FileStatusListView_SelectedIndexChanged()
  416. {
  417. if (SelectedIndexChanged != null)
  418. SelectedIndexChanged(this, EventArgs.Empty);
  419. }
  420. private static int GetItemImageIndex(GitItemStatus gitItemStatus)
  421. {
  422. if (gitItemStatus.IsDeleted)
  423. return 0;
  424. if (gitItemStatus.IsNew || !gitItemStatus.IsTracked)
  425. return 1;
  426. if (gitItemStatus.IsChanged || gitItemStatus.IsConflict)
  427. {
  428. if (!gitItemStatus.IsSubmodule || gitItemStatus.SubmoduleStatus == null ||
  429. !gitItemStatus.SubmoduleStatus.IsCompleted)
  430. return 2;
  431. var status = gitItemStatus.SubmoduleStatus.Result;
  432. if (status == null)
  433. return 2;
  434. if (status.Status == SubmoduleStatus.FastForward)
  435. return 6 + (status.IsDirty ? 1 : 0);
  436. if (status.Status == SubmoduleStatus.Rewind)
  437. return 8 + (status.IsDirty ? 1 : 0);
  438. if (status.Status == SubmoduleStatus.NewerTime)
  439. return 10 + (status.IsDirty ? 1 : 0);
  440. if (status.Status == SubmoduleStatus.OlderTime)
  441. return 12 + (status.IsDirty ? 1 : 0);
  442. return !status.IsDirty ? 2 : 5;
  443. }
  444. if (gitItemStatus.IsRenamed)
  445. return 3;
  446. if (gitItemStatus.IsCopied)
  447. return 4;
  448. return 14;//icon unknown
  449. }
  450. [Browsable(false)]
  451. [DefaultValue(true)]
  452. public bool IsEmpty
  453. {
  454. get { return GitItemStatuses == null || !GitItemStatuses.Any(); }
  455. }
  456. [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  457. [Browsable(false)]
  458. public IList<GitItemStatus> GitItemStatuses
  459. {
  460. get
  461. {
  462. var result = new List<GitItemStatus>();
  463. var data = GitItemStatusesWithParents;
  464. if (data != null)
  465. foreach (var plist in data.Values)
  466. result.AddAll(plist);
  467. return result;
  468. }
  469. set
  470. {
  471. if (value == null)
  472. GitItemStatusesWithParents = null;
  473. else
  474. SetGitItemStatuses(null, value);
  475. }
  476. }
  477. [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  478. [Browsable(false)]
  479. public IList<GitItemStatus> GitItemFilteredStatuses
  480. {
  481. get
  482. {
  483. var result = new List<GitItemStatus>();
  484. foreach(ListViewItem listViewItem in FileStatusListView.Items)
  485. {
  486. result.Add((GitItemStatus)listViewItem.Tag);
  487. }
  488. return result;
  489. }
  490. }
  491. [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  492. [Browsable(false)]
  493. public string GitFirstParent
  494. {
  495. get
  496. {
  497. var data = GitItemStatusesWithParents;
  498. if (data != null && data.Count > 0)
  499. return data.ElementAt(0).Key;
  500. return null;
  501. }
  502. }
  503. public void SetGitItemStatuses(string parentRev, IList<GitItemStatus> items)
  504. {
  505. var dictionary = new Dictionary<string, IList<GitItemStatus>> { { parentRev ?? "", items } };
  506. GitItemStatusesWithParents = dictionary;
  507. }
  508. private GitItemsWithParents _itemsDictionary = new Dictionary<string, IList<GitItemStatus>>();
  509. [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  510. [Browsable(false)]
  511. public GitItemsWithParents GitItemStatusesWithParents
  512. {
  513. get
  514. {
  515. return _itemsDictionary;
  516. }
  517. set
  518. {
  519. _itemsDictionary = value;
  520. UpdateFileStatusListView();
  521. }
  522. }
  523. private void UpdateFileStatusListView(bool updateCausedByFilter = false)
  524. {
  525. if (_itemsDictionary == null || !_itemsDictionary.Any())
  526. {
  527. HandleVisibility_NoFilesLabel_FilterComboBox(filesPresent: false);
  528. }
  529. else
  530. {
  531. EnsureSelectedIndexChangeSubscription();
  532. HandleVisibility_NoFilesLabel_FilterComboBox(filesPresent: true);
  533. }
  534. FileStatusListView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
  535. var previouslySelectedItems = new List<GitItemStatus>();
  536. if (updateCausedByFilter)
  537. {
  538. foreach (ListViewItem Item in FileStatusListView.SelectedItems)
  539. {
  540. previouslySelectedItems.Add((GitItemStatus)Item.Tag);
  541. }
  542. }
  543. FileStatusListView.BeginUpdate();
  544. FileStatusListView.ShowGroups = _itemsDictionary != null && _itemsDictionary.Count > 1;
  545. FileStatusListView.Groups.Clear();
  546. FileStatusListView.Items.Clear();
  547. if (_itemsDictionary != null)
  548. {
  549. var list = new List<ListViewItem>();
  550. foreach (var pair in _itemsDictionary)
  551. {
  552. ListViewGroup group = null;
  553. if (!String.IsNullOrEmpty(pair.Key))
  554. {
  555. var groupName = "";
  556. if (pair.Key == CombinedDiff.Text)
  557. {
  558. groupName = CombinedDiff.Text;
  559. }
  560. else
  561. {
  562. string shortHash = pair.Key.Length > 8 ? pair.Key.Substring(0, 8) : pair.Key;
  563. groupName = _DiffWithParent.Text + " " + shortHash;
  564. }
  565. group = new ListViewGroup(groupName);
  566. group.Tag = pair.Key;
  567. FileStatusListView.Groups.Add(group);
  568. }
  569. foreach (var item in pair.Value)
  570. {
  571. if (_filter.IsMatch(item.Name))
  572. {
  573. var listItem = new ListViewItem(item.Name, group);
  574. listItem.ImageIndex = GetItemImageIndex(item);
  575. if (item.SubmoduleStatus != null && !item.SubmoduleStatus.IsCompleted)
  576. {
  577. var capturedItem = item;
  578. item.SubmoduleStatus.ContinueWith((task) => listItem.ImageIndex = GetItemImageIndex(capturedItem),
  579. CancellationToken.None,
  580. TaskContinuationOptions.OnlyOnRanToCompletion,
  581. TaskScheduler.FromCurrentSynchronizationContext());
  582. }
  583. if (previouslySelectedItems.Contains(item))
  584. {
  585. listItem.Selected = true;
  586. }
  587. listItem.Tag = item;
  588. list.Add(listItem);
  589. }
  590. }
  591. }
  592. FileStatusListView.Items.AddRange(list.ToArray());
  593. }
  594. if (updateCausedByFilter == false)
  595. {
  596. FileStatusListView_SelectedIndexChanged();
  597. if (DataSourceChanged != null)
  598. DataSourceChanged(this, new EventArgs());
  599. if (SelectFirstItemOnSetItems)
  600. SelectFirstVisibleItem();
  601. }
  602. FileStatusListView_SizeChanged(null, null);
  603. FileStatusListView.SetGroupState(ListViewGroupState.Collapsible);
  604. FileStatusListView.EndUpdate();
  605. }
  606. [DefaultValue(true)]
  607. public bool SelectFirstItemOnSetItems { get; set; }
  608. public void SelectFirstVisibleItem()
  609. {
  610. if (FileStatusListView.Items.Count == 0)
  611. return;
  612. var group = FileStatusListView.Groups.Cast<ListViewGroup>().
  613. FirstOrDefault(gr => gr.Items.Count > 0);
  614. if (group != null)
  615. {
  616. ListViewItem sortedFirstGroupItem = FileStatusListView.Items.Cast<ListViewItem>().
  617. FirstOrDefault(item => item.Group == group);
  618. if (sortedFirstGroupItem != null)
  619. sortedFirstGroupItem.Selected = true;
  620. }
  621. else if (FileStatusListView.Items.Count > 0)
  622. FileStatusListView.Items[0].Selected = true;
  623. }
  624. /// <summary>
  625. /// Gets or sets the revision.
  626. /// </summary>
  627. /// <value>The revision.</value>
  628. [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  629. [Browsable(false)]
  630. public GitRevision Revision { get; set; }
  631. private void FileStatusListView_SizeChanged(object sender, EventArgs e)
  632. {
  633. NoFiles.Location = new Point(5, 5);
  634. NoFiles.Size = new Size(Size.Width - 10, Size.Height - 10);
  635. Refresh();
  636. FileStatusListView.BeginUpdate();
  637. FileStatusListView.AutoResizeColumn(0,
  638. ColumnHeaderAutoResizeStyle.HeaderSize);
  639. FileStatusListView.EndUpdate();
  640. }
  641. private void FileStatusListView_KeyDown(object sender, KeyEventArgs e)
  642. {
  643. switch (e.KeyCode)
  644. {
  645. case Keys.A:
  646. {
  647. if (!e.Control)
  648. break;
  649. FileStatusListView.BeginUpdate();
  650. try
  651. {
  652. for (var i = 0; i < FileStatusListView.Items.Count; i++)
  653. FileStatusListView.Items[i].Selected = true;
  654. e.Handled = true;
  655. }
  656. finally
  657. {
  658. FileStatusListView.EndUpdate();
  659. }
  660. break;
  661. }
  662. default:
  663. if (KeyDown != null)
  664. KeyDown(sender, e);
  665. break;
  666. }
  667. }
  668. public int SetSelectionFilter(string selectionFilter)
  669. {
  670. return SelectFiles(RegexForSelecting(selectionFilter));
  671. }
  672. private static Regex RegexForSelecting(string value)
  673. {
  674. return string.IsNullOrEmpty(value)
  675. ? new Regex("^$", RegexOptions.Compiled)
  676. : new Regex(value, RegexOptions.Compiled | RegexOptions.IgnoreCase);
  677. }
  678. private int SelectFiles(Regex selctionFilter)
  679. {
  680. try
  681. {
  682. SuspendLayout();
  683. var items = AllItems;
  684. int i = 0;
  685. foreach (var item in items)
  686. {
  687. FileStatusListView.Items[i].Selected = selctionFilter.IsMatch(item.Name);
  688. i++;
  689. }
  690. return FileStatusListView.SelectedIndices.Count;
  691. }
  692. finally
  693. {
  694. ResumeLayout(true);
  695. }
  696. }
  697. public void SetDiffs(List<GitRevision> revisions)
  698. {
  699. HandleVisibility_NoFilesLabel_FilterComboBox(filesPresent: true);
  700. switch (revisions.Count)
  701. {
  702. case 0:
  703. NoFiles.Text = _noDiffFilesChangesDefaultText;
  704. GitItemStatuses = null;
  705. break;
  706. case 1: // diff "parent" --> "selected revision"
  707. SetDiff(revisions[0]);
  708. break;
  709. case 2: // diff "first clicked revision" --> "second clicked revision"
  710. NoFiles.Text = _noDiffFilesChangesDefaultText;
  711. bool artificialRevSelected = revisions[0].IsArtificial() || revisions[1].IsArtificial();
  712. if (artificialRevSelected)
  713. {
  714. NoFiles.Text = _UnsupportedMultiselectAction.Text;
  715. GitItemStatuses = null;
  716. }
  717. else
  718. SetGitItemStatuses(revisions[1].Guid, Module.GetDiffFilesWithSubmodulesStatus(revisions[0].Guid, revisions[1].Guid));
  719. break;
  720. default: // more than 2 revisions selected => no diff
  721. NoFiles.Text = _UnsupportedMultiselectAction.Text;
  722. GitItemStatuses = null;
  723. break;
  724. }
  725. UpdateNoFilesLabelVisibility();
  726. }
  727. private void UpdateNoFilesLabelVisibility()
  728. {
  729. if (GitItemStatusesWithParents == null && GitItemStatuses == null)
  730. HandleVisibility_NoFilesLabel_FilterComboBox(filesPresent: false);
  731. else if (GitItemStatusesWithParents != null)
  732. {
  733. List<string> keys = GitItemStatusesWithParents.Keys.ToList();
  734. if (keys.Count == 0)
  735. HandleVisibility_NoFilesLabel_FilterComboBox(filesPresent: false);
  736. else if (keys.Count == 1 && (GitItemStatusesWithParents[keys[0]] == null || GitItemStatusesWithParents[keys[0]].Count == 0))
  737. HandleVisibility_NoFilesLabel_FilterComboBox(filesPresent: false);
  738. }
  739. else if (GitItemStatuses != null)
  740. {
  741. if (GitItemStatuses.Count == 0)
  742. HandleVisibility_NoFilesLabel_FilterComboBox(filesPresent: false);
  743. }
  744. }
  745. public void SetDiff(GitRevision revision)
  746. {
  747. NoFiles.Text = _noDiffFilesChangesDefaultText;
  748. Revision = revision;
  749. if (revision == null)
  750. GitItemStatuses = null;
  751. else if (revision.ParentGuids == null || revision.ParentGuids.Length == 0)
  752. GitItemStatuses = Module.GetTreeFiles(revision.TreeGuid, true);
  753. else
  754. {
  755. if (revision.Guid == GitRevision.UnstagedGuid) //working directory changes
  756. GitItemStatuses = Module.GetUnstagedFilesWithSubmodulesStatus();
  757. else if (revision.Guid == GitRevision.IndexGuid) //index
  758. GitItemStatuses = Module.GetStagedFilesWithSubmodulesStatus();
  759. else
  760. {
  761. GitItemsWithParents dictionary = new Dictionary<string, IList<GitItemStatus>>();
  762. foreach (var parentRev in revision.ParentGuids)
  763. {
  764. dictionary.Add(parentRev, Module.GetDiffFilesWithSubmodulesStatus(revision.Guid, parentRev));
  765. //Only add the first parent to the dictionary if the setting to show diffs
  766. //for app parents is disabled
  767. if (!AppSettings.ShowDiffForAllParents)
  768. break;
  769. }
  770. var isMergeCommit = revision.ParentGuids.Count() == 2;
  771. if (isMergeCommit)
  772. {
  773. var conflicts = Module.GetCombinedDiffFileList(revision.Guid);
  774. if (conflicts.Any())
  775. {
  776. dictionary.Add(CombinedDiff.Text, conflicts);
  777. }
  778. }
  779. GitItemStatusesWithParents = dictionary;
  780. }
  781. }
  782. }
  783. private void HandleVisibility_NoFilesLabel_FilterComboBox(bool filesPresent)
  784. {
  785. NoFiles.Visible = !filesPresent;
  786. if (_filterVisible)
  787. {
  788. FilterComboBox.Visible = filesPresent;
  789. }
  790. }
  791. #region Filtering
  792. private long _lastUserInputTime;
  793. private string _ToolTipText = "";
  794. private static Regex RegexForFiltering(string value)
  795. {
  796. return string.IsNullOrEmpty(value)
  797. ? new Regex(".", RegexOptions.Compiled)
  798. : new Regex(value, RegexOptions.Compiled | RegexOptions.IgnoreCase);
  799. }
  800. public void SetFilter(string value)
  801. {
  802. FilterComboBox.Text = value;
  803. FilterFiles(value);
  804. }
  805. private int FilterFiles(string value)
  806. {
  807. _filter = RegexForFiltering(value);
  808. UpdateFileStatusListView(true);
  809. return FileStatusListView.Items.Count;
  810. }
  811. private void FilterComboBox_TextUpdate(object sender, EventArgs e)
  812. {
  813. var currentTime = DateTime.Now.Ticks;
  814. if (_lastUserInputTime == 0)
  815. {
  816. long timerLastChanged = currentTime;
  817. var timer = new System.Windows.Forms.Timer { Interval = 250 };
  818. timer.Tick += (s, a) =>
  819. {
  820. if (NoUserInput(timerLastChanged))
  821. {
  822. _ToolTipText = "";
  823. var fileCount = 0;
  824. try
  825. {
  826. fileCount = FilterFiles(FilterComboBox.Text);
  827. }
  828. catch (ArgumentException ae)
  829. {
  830. _ToolTipText = ae.Message;
  831. }
  832. if (fileCount > 0)
  833. {
  834. AddToSelectionFilter(FilterComboBox.Text);
  835. }
  836. timer.Stop();
  837. _lastUserInputTime = 0;
  838. }
  839. timerLastChanged = _lastUserInputTime;
  840. };
  841. timer.Start();
  842. }
  843. _lastUserInputTime = currentTime;
  844. }
  845. private bool NoUserInput(long timerLastChanged)
  846. {
  847. return timerLastChanged == _lastUserInputTime;
  848. }
  849. private void AddToSelectionFilter(string filter)
  850. {
  851. if (!FilterComboBox.Items.Cast<string>().Any(candiate => candiate == filter))
  852. {
  853. const int SelectionFilterMaxLength = 10;
  854. if (FilterComboBox.Items.Count == SelectionFilterMaxLength)
  855. {
  856. FilterComboBox.Items.RemoveAt(SelectionFilterMaxLength - 1);
  857. }
  858. FilterComboBox.Items.Insert(0, filter);
  859. }
  860. }
  861. private void FilterComboBox_MouseEnter(object sender, EventArgs e)
  862. {
  863. FilterToolTip.SetToolTip(FilterComboBox, _ToolTipText);
  864. }
  865. private void FilterComboBox_SelectedIndexChanged(object sender, EventArgs e)
  866. {
  867. FilterFiles(FilterComboBox.Text);
  868. }
  869. private void FilterComboBox_GotFocus(object sender, EventArgs e)
  870. {
  871. FilterWatermarkLabel.Visible = false;
  872. }
  873. private void FilterComboBox_LostFocus(object sender, EventArgs e)
  874. {
  875. if (!FilterWatermarkLabel.Visible && string.IsNullOrEmpty(FilterComboBox.Text))
  876. {
  877. FilterWatermarkLabel.Visible = true;
  878. }
  879. }
  880. private Regex _filter;
  881. #endregion Filtering
  882. }
  883. }