PageRenderTime 34ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/src/DocumentStrip.cs

https://bitbucket.org/tcz001/openpdn
C# | 626 lines | 508 code | 105 blank | 13 comment | 67 complexity | 23a068297e5a4abc0432ef5564fa963a MD5 | raw file
Possible License(s): Unlicense
  1. /////////////////////////////////////////////////////////////////////////////////
  2. // Paint.NET //
  3. // Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. //
  4. // Portions Copyright (C) Microsoft Corporation. All Rights Reserved. //
  5. // See src/Resources/Files/License.txt for full licensing and attribution //
  6. // details. //
  7. // . //
  8. /////////////////////////////////////////////////////////////////////////////////
  9. using PaintDotNet.SystemLayer;
  10. using System;
  11. using System.Collections.Generic;
  12. using System.ComponentModel;
  13. using System.Drawing;
  14. using System.Drawing.Drawing2D;
  15. using System.Drawing.Imaging;
  16. using System.IO;
  17. using System.Windows.Forms;
  18. namespace PaintDotNet
  19. {
  20. internal class DocumentStrip
  21. : ImageStrip,
  22. IDocumentList
  23. {
  24. private object thumbsLock = new object();
  25. private List<DocumentWorkspace> documents = new List<DocumentWorkspace>();
  26. private List<ImageStrip.Item> documentButtons = new List<ImageStrip.Item>();
  27. private Dictionary<DocumentWorkspace, ImageStrip.Item> dw2button = new Dictionary<DocumentWorkspace, ImageStrip.Item>();
  28. private Dictionary<DocumentWorkspace, RenderArgs> thumbs = new Dictionary<DocumentWorkspace, RenderArgs>();
  29. private ThumbnailManager thumbnailManager;
  30. private DocumentWorkspace selectedDocument = null;
  31. private bool ensureSelectedIsVisible = true;
  32. private int suspendThumbnailUpdates = 0;
  33. public void SuspendThumbnailUpdates()
  34. {
  35. ++this.suspendThumbnailUpdates;
  36. }
  37. public void ResumeThumbnailUpdates()
  38. {
  39. --this.suspendThumbnailUpdates;
  40. }
  41. public void SyncThumbnails()
  42. {
  43. if (!this.thumbnailManager.IsDisposed)
  44. {
  45. using (new WaitCursorChanger(this))
  46. {
  47. this.thumbnailManager.DrainQueue();
  48. }
  49. Refresh();
  50. }
  51. }
  52. public int ThumbnailUpdateLatency
  53. {
  54. get
  55. {
  56. return this.thumbnailManager.UpdateLatency;
  57. }
  58. set
  59. {
  60. this.thumbnailManager.UpdateLatency = value;
  61. }
  62. }
  63. public bool EnsureSelectedIsVisible
  64. {
  65. get
  66. {
  67. return this.ensureSelectedIsVisible;
  68. }
  69. set
  70. {
  71. if (this.ensureSelectedIsVisible != value)
  72. {
  73. this.ensureSelectedIsVisible = value;
  74. PerformLayout();
  75. }
  76. }
  77. }
  78. public DocumentWorkspace[] DocumentList
  79. {
  80. get
  81. {
  82. return this.documents.ToArray();
  83. }
  84. }
  85. public Image[] DocumentThumbnails
  86. {
  87. get
  88. {
  89. SyncThumbnails();
  90. Image[] thumbnails = new Image[this.documents.Count];
  91. for (int i = 0; i < thumbnails.Length; ++i)
  92. {
  93. DocumentWorkspace dw = this.documents[i];
  94. RenderArgs ra;
  95. if (!this.thumbs.TryGetValue(dw, out ra))
  96. {
  97. thumbnails[i] = null;
  98. }
  99. else
  100. {
  101. thumbnails[i] = ra.Bitmap;
  102. }
  103. }
  104. return thumbnails;
  105. }
  106. }
  107. public int DocumentCount
  108. {
  109. get
  110. {
  111. return this.documents.Count;
  112. }
  113. }
  114. public event EventHandler DocumentListChanged;
  115. protected virtual void OnDocumentListChanged()
  116. {
  117. if (DocumentListChanged != null)
  118. {
  119. DocumentListChanged(this, EventArgs.Empty);
  120. }
  121. }
  122. public DocumentWorkspace SelectedDocument
  123. {
  124. get
  125. {
  126. return this.selectedDocument;
  127. }
  128. set
  129. {
  130. if (!this.documents.Contains(value))
  131. {
  132. throw new ArgumentException("DocumentWorkspace isn't being tracked by this instance of DocumentStrip");
  133. }
  134. if (this.selectedDocument != value)
  135. {
  136. SelectDocumentWorkspace(value);
  137. OnDocumentClicked(value, DocumentClickAction.Select);
  138. Refresh();
  139. }
  140. }
  141. }
  142. public int SelectedDocumentIndex
  143. {
  144. get
  145. {
  146. return this.documents.IndexOf(this.selectedDocument);
  147. }
  148. }
  149. public override Size GetPreferredSize(Size proposedSize)
  150. {
  151. Size itemSize = ItemSize;
  152. int preferredWidth;
  153. if (this.ItemCount == 0)
  154. {
  155. preferredWidth = 0;
  156. }
  157. else
  158. {
  159. preferredWidth = itemSize.Width * DocumentCount;
  160. }
  161. Size preferredSize = new Size(preferredWidth, itemSize.Height);
  162. return preferredSize;
  163. }
  164. private bool OnDigitHotKeyPressed(Keys keys)
  165. {
  166. // strip off the Alt and Control stuff
  167. keys &= ~Keys.Alt;
  168. keys &= ~Keys.Control;
  169. if (keys < Keys.D0 || keys > Keys.D9)
  170. {
  171. return false;
  172. }
  173. int digit = (int)keys - (int)Keys.D0;
  174. int index;
  175. if (digit == 0)
  176. {
  177. index = 9;
  178. }
  179. else
  180. {
  181. index = digit - 1;
  182. }
  183. if (index < this.documents.Count)
  184. {
  185. PerformItemClick(index, ItemPart.Image, MouseButtons.Left);
  186. return true;
  187. }
  188. else
  189. {
  190. return false;
  191. }
  192. }
  193. public DocumentStrip()
  194. {
  195. PdnBaseForm.RegisterFormHotKey(Keys.Control | Keys.Tab, OnNextTabHotKeyPressed);
  196. PdnBaseForm.RegisterFormHotKey(Keys.Control | Keys.PageDown, OnNextTabHotKeyPressed);
  197. PdnBaseForm.RegisterFormHotKey(Keys.Control | Keys.Shift | Keys.Tab, OnPreviousTabHotKeyPressed);
  198. PdnBaseForm.RegisterFormHotKey(Keys.Control | Keys.PageUp, OnPreviousTabHotKeyPressed);
  199. thumbnailManager = new ThumbnailManager(this); // allow for a 1px black border
  200. InitializeComponent();
  201. for (int i = 0; i <= 9; ++i)
  202. {
  203. Keys digit = Utility.LetterOrDigitCharToKeys((char)(i + '0'));
  204. PdnBaseForm.RegisterFormHotKey(Keys.Control | digit, OnDigitHotKeyPressed);
  205. PdnBaseForm.RegisterFormHotKey(Keys.Alt | digit, OnDigitHotKeyPressed);
  206. }
  207. this.ShowCloseButtons = true;
  208. }
  209. private bool OnNextTabHotKeyPressed(Keys keys)
  210. {
  211. bool processed = NextTab();
  212. return processed;
  213. }
  214. private bool OnPreviousTabHotKeyPressed(Keys keys)
  215. {
  216. bool processed = PreviousTab();
  217. return processed;
  218. }
  219. public bool NextTab()
  220. {
  221. bool changed = false;
  222. if (this.selectedDocument != null)
  223. {
  224. int currentIndex = this.documents.IndexOf(this.selectedDocument);
  225. int newIndex = (currentIndex + 1) % this.documents.Count;
  226. SelectedDocument = this.documents[newIndex];
  227. changed = true;
  228. }
  229. return changed;
  230. }
  231. public bool PreviousTab()
  232. {
  233. bool changed = false;
  234. if (this.selectedDocument != null)
  235. {
  236. int currentIndex = this.documents.IndexOf(this.selectedDocument);
  237. int newIndex = (currentIndex + (this.documents.Count - 1)) % this.documents.Count;
  238. SelectedDocument = this.documents[newIndex];
  239. changed = true;
  240. }
  241. return changed;
  242. }
  243. private void InitializeComponent()
  244. {
  245. this.Name = "DocumentStrip";
  246. }
  247. protected override void Dispose(bool disposing)
  248. {
  249. if (disposing)
  250. {
  251. while (this.documents.Count > 0)
  252. {
  253. RemoveDocumentWorkspace(this.documents[this.documents.Count - 1]);
  254. }
  255. if (this.thumbnailManager != null)
  256. {
  257. this.thumbnailManager.Dispose();
  258. this.thumbnailManager = null;
  259. }
  260. foreach (DocumentWorkspace dw in this.thumbs.Keys)
  261. {
  262. RenderArgs ra = this.thumbs[dw];
  263. ra.Dispose();
  264. }
  265. this.thumbs.Clear();
  266. this.thumbs = null;
  267. }
  268. base.Dispose(disposing);
  269. }
  270. protected override void OnScrollArrowClicked(ArrowDirection arrowDirection)
  271. {
  272. int sign = 0;
  273. switch (arrowDirection)
  274. {
  275. case ArrowDirection.Left:
  276. sign = -1;
  277. break;
  278. case ArrowDirection.Right:
  279. sign = +1;
  280. break;
  281. }
  282. int delta = ItemSize.Width;
  283. ScrollOffset += sign * delta;
  284. base.OnScrollArrowClicked(arrowDirection);
  285. }
  286. protected override void OnItemClicked(Item item, ItemPart itemPart, MouseButtons mouseButtons)
  287. {
  288. DocumentWorkspace dw = item.Tag as DocumentWorkspace;
  289. if (dw != null)
  290. {
  291. switch (itemPart)
  292. {
  293. case ItemPart.None:
  294. // do nothing
  295. break;
  296. case ItemPart.CloseButton:
  297. if (mouseButtons == MouseButtons.Left)
  298. {
  299. OnDocumentClicked(dw, DocumentClickAction.Close);
  300. }
  301. break;
  302. case ItemPart.Image:
  303. if (mouseButtons == MouseButtons.Left)
  304. {
  305. SelectedDocument = dw;
  306. }
  307. else if (mouseButtons == MouseButtons.Right)
  308. {
  309. // TODO: right click menu
  310. }
  311. break;
  312. default:
  313. throw new InvalidEnumArgumentException();
  314. }
  315. }
  316. base.OnItemClicked(item, itemPart, mouseButtons);
  317. }
  318. public event EventHandler<EventArgs<Pair<DocumentWorkspace, DocumentClickAction>>> DocumentClicked;
  319. protected virtual void OnDocumentClicked(DocumentWorkspace dw, DocumentClickAction action)
  320. {
  321. if (DocumentClicked != null)
  322. {
  323. DocumentClicked(this, new EventArgs<Pair<DocumentWorkspace, DocumentClickAction>>(
  324. Pair.Create(dw, action)));
  325. }
  326. }
  327. public void UnlockDocumentWorkspaceDirtyValue(DocumentWorkspace unlockMe)
  328. {
  329. Item docItem = this.dw2button[unlockMe];
  330. docItem.UnlockDirtyValue();
  331. }
  332. public void LockDocumentWorkspaceDirtyValue(DocumentWorkspace lockMe, bool forceDirtyValue)
  333. {
  334. Item docItem = this.dw2button[lockMe];
  335. docItem.LockDirtyValue(forceDirtyValue);
  336. }
  337. public void AddDocumentWorkspace(DocumentWorkspace addMe)
  338. {
  339. this.documents.Add(addMe);
  340. ImageStrip.Item docButton = new ImageStrip.Item();
  341. docButton.Image = null;
  342. docButton.Tag = addMe;
  343. AddItem(docButton);
  344. this.documentButtons.Add(docButton);
  345. addMe.CompositionUpdated += Workspace_CompositionUpdated;
  346. this.dw2button.Add(addMe, docButton);
  347. if (addMe.Document != null)
  348. {
  349. QueueThumbnailUpdate(addMe);
  350. docButton.Dirty = addMe.Document.Dirty;
  351. addMe.Document.DirtyChanged += Document_DirtyChanged;
  352. }
  353. addMe.DocumentChanging += Workspace_DocumentChanging;
  354. addMe.DocumentChanged += Workspace_DocumentChanged;
  355. OnDocumentListChanged();
  356. }
  357. private void Workspace_DocumentChanging(object sender, EventArgs<Document> e)
  358. {
  359. if (e.Data != null)
  360. {
  361. e.Data.DirtyChanged -= Document_DirtyChanged;
  362. }
  363. }
  364. private void Workspace_DocumentChanged(object sender, EventArgs e)
  365. {
  366. DocumentWorkspace dw = (DocumentWorkspace)sender;
  367. ImageStrip.Item docButton = this.dw2button[dw];
  368. if (dw.Document != null)
  369. {
  370. docButton.Dirty = dw.Document.Dirty;
  371. dw.Document.DirtyChanged += Document_DirtyChanged;
  372. }
  373. else
  374. {
  375. docButton.Dirty = false;
  376. }
  377. }
  378. private void Document_DirtyChanged(object sender, EventArgs e)
  379. {
  380. for (int i = 0; i < this.documents.Count; ++i)
  381. {
  382. if (object.ReferenceEquals(sender, this.documents[i].Document))
  383. {
  384. ImageStrip.Item docButton = this.dw2button[this.documents[i]];
  385. docButton.Dirty = ((Document)sender).Dirty;
  386. }
  387. }
  388. }
  389. private void Workspace_CompositionUpdated(object sender, EventArgs e)
  390. {
  391. DocumentWorkspace dw = (DocumentWorkspace)sender;
  392. QueueThumbnailUpdate(dw);
  393. }
  394. public void RemoveDocumentWorkspace(DocumentWorkspace removeMe)
  395. {
  396. removeMe.CompositionUpdated -= Workspace_CompositionUpdated;
  397. if (this.selectedDocument == removeMe)
  398. {
  399. this.selectedDocument = null;
  400. }
  401. removeMe.DocumentChanging -= Workspace_DocumentChanging;
  402. removeMe.DocumentChanged -= Workspace_DocumentChanged;
  403. if (removeMe.Document != null)
  404. {
  405. removeMe.Document.DirtyChanged -= Document_DirtyChanged;
  406. }
  407. this.documents.Remove(removeMe);
  408. this.thumbnailManager.RemoveFromQueue(removeMe);
  409. ImageStrip.Item docButton = this.dw2button[removeMe];
  410. this.RemoveItem(docButton);
  411. this.dw2button.Remove(removeMe);
  412. this.documentButtons.Remove(docButton);
  413. if (this.thumbs.ContainsKey(removeMe))
  414. {
  415. RenderArgs thumbRA = this.thumbs[removeMe];
  416. Surface surface = thumbRA.Surface;
  417. thumbRA.Dispose();
  418. this.thumbs.Remove(removeMe);
  419. surface.Dispose();
  420. }
  421. OnDocumentListChanged();
  422. }
  423. protected override void OnLayout(LayoutEventArgs levent)
  424. {
  425. base.OnLayout(levent);
  426. if (this.ensureSelectedIsVisible &&
  427. (!Focused && !LeftScrollButton.Focused && !RightScrollButton.Focused))
  428. {
  429. int index = this.documents.IndexOf(this.selectedDocument);
  430. EnsureItemFullyVisible(index);
  431. }
  432. }
  433. public void SelectDocumentWorkspace(DocumentWorkspace selectMe)
  434. {
  435. UI.SuspendControlPainting(this);
  436. this.selectedDocument = selectMe;
  437. if (this.thumbs.ContainsKey(selectMe))
  438. {
  439. RenderArgs thumb = this.thumbs[selectMe];
  440. Bitmap bitmap = thumb.Bitmap;
  441. }
  442. else
  443. {
  444. QueueThumbnailUpdate(selectMe);
  445. }
  446. foreach (ImageStrip.Item docItem in this.documentButtons)
  447. {
  448. if ((docItem.Tag as DocumentWorkspace) == selectMe)
  449. {
  450. EnsureItemFullyVisible(docItem);
  451. docItem.Checked = true;
  452. }
  453. else
  454. {
  455. docItem.Checked = false;
  456. }
  457. }
  458. UI.ResumeControlPainting(this);
  459. Invalidate(true);
  460. }
  461. public void RefreshThumbnail(DocumentWorkspace dw)
  462. {
  463. if (this.documents.Contains(dw))
  464. {
  465. QueueThumbnailUpdate(dw);
  466. }
  467. }
  468. public void RefreshAllThumbnails()
  469. {
  470. foreach (DocumentWorkspace dw in this.documents)
  471. {
  472. QueueThumbnailUpdate(dw);
  473. }
  474. }
  475. private void OnThumbnailUpdated(DocumentWorkspace dw)
  476. {
  477. // We must double check that the DW is still around, because there's a chance
  478. // that the DW was been removed while the thumbnail was being rendered.
  479. if (this.dw2button.ContainsKey(dw))
  480. {
  481. ImageStrip.Item docButton = this.dw2button[dw];
  482. RenderArgs docRA = this.thumbs[dw];
  483. docButton.Image = docRA.Bitmap;
  484. docButton.Update();
  485. }
  486. }
  487. public void QueueThumbnailUpdate(DocumentWorkspace dw)
  488. {
  489. if (this.suspendThumbnailUpdates <= 0)
  490. {
  491. this.thumbnailManager.QueueThumbnailUpdate(dw, PreferredImageSize.Width - 2, OnThumbnailRendered);
  492. }
  493. }
  494. private void OnThumbnailRendered(object sender, EventArgs<Pair<IThumbnailProvider, Surface>> e)
  495. {
  496. RenderArgs ra = null;
  497. DocumentWorkspace dw = (DocumentWorkspace)e.Data.First;
  498. Size desiredSize = new Size(e.Data.Second.Width + 2, e.Data.Second.Height + 2);
  499. if (this.thumbs.ContainsKey(dw))
  500. {
  501. ra = this.thumbs[dw];
  502. if (ra.Size != desiredSize)
  503. {
  504. ra.Dispose();
  505. ra = null;
  506. this.thumbs.Remove(dw);
  507. }
  508. }
  509. if (ra == null)
  510. {
  511. Surface surface = new Surface(desiredSize);
  512. ra = new RenderArgs(surface);
  513. this.thumbs.Add(dw, ra);
  514. }
  515. ra.Surface.Clear(ColorBgra.Black);
  516. ra.Surface.CopySurface(e.Data.Second, new Point(1, 1));
  517. e.Data.Second.Dispose();
  518. OnThumbnailUpdated(dw);
  519. }
  520. }
  521. }