PageRenderTime 52ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/src/MarkPad/ShellViewModel.cs

https://github.com/bcott/DownmarkerWPF
C# | 440 lines | 376 code | 61 blank | 3 comment | 55 complexity | ed1fdfa132d0e77b018ab98a5facc6cf MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Reflection;
  6. using System.Threading.Tasks;
  7. using System.Windows;
  8. using System.Windows.Automation;
  9. using System.Windows.Input;
  10. using Caliburn.Micro;
  11. using MarkPad.Document;
  12. using MarkPad.Document.Events;
  13. using MarkPad.Document.Search;
  14. using MarkPad.DocumentSources;
  15. using MarkPad.DocumentSources.FileSystem;
  16. using MarkPad.DocumentSources.MetaWeblog;
  17. using MarkPad.Events;
  18. using MarkPad.Infrastructure;
  19. using MarkPad.Infrastructure.DialogService;
  20. using MarkPad.Plugins;
  21. using MarkPad.Settings.UI;
  22. using MarkPad.Updater;
  23. namespace MarkPad
  24. {
  25. public class ShellViewModel : Conductor<IScreen>, IShell, IHandle<FileOpenEvent>, IHandle<SettingsCloseEvent>, IHandle<OpenFromWebEvent>, IDoWorkAsyncronously
  26. {
  27. const string ShowSettingsState = "ShowSettings";
  28. const string NewDocumentDefaultName = "New Document";
  29. readonly IEventAggregator eventAggregator;
  30. readonly IDialogService dialogService;
  31. readonly IOpenDocumentFromWeb openDocumentFromWeb;
  32. readonly Func<DocumentViewModel> documentViewModelFactory;
  33. readonly List<string> loadingMessages = new List<string>();
  34. readonly IFileSystem fileSystem;
  35. readonly IDocumentFactory documentFactory;
  36. readonly object workLock = new object();
  37. private int numberOfNewDocuments;
  38. public ShellViewModel(
  39. IDialogService dialogService,
  40. IEventAggregator eventAggregator,
  41. MdiViewModel mdi,
  42. SettingsViewModel settingsViewModel,
  43. UpdaterViewModel updaterViewModel,
  44. Func<DocumentViewModel> documentViewModelFactory,
  45. IDocumentFactory documentFactory,
  46. IFileSystem fileSystem,
  47. SearchSettings searchSettings, IOpenDocumentFromWeb openDocumentFromWeb)
  48. {
  49. this.eventAggregator = eventAggregator;
  50. this.dialogService = dialogService;
  51. MDI = mdi;
  52. Updater = updaterViewModel;
  53. Updater.Initialise(this);
  54. this.documentViewModelFactory = documentViewModelFactory;
  55. this.documentFactory = documentFactory;
  56. this.fileSystem = fileSystem;
  57. SearchSettings = searchSettings;
  58. this.openDocumentFromWeb = openDocumentFromWeb;
  59. Settings = settingsViewModel;
  60. Settings.Initialize();
  61. NewDocumentCommand = new DelegateCommand(() => NewDocument());
  62. NewJekyllDocumentCommand = new DelegateCommand(() => NewDocument(CreateJekyllHeader()));
  63. SaveDocumentCommand = new AwaitableDelegateCommand(SaveDocument, () => !IsWorking);
  64. SaveDocumentAsCommand = new AwaitableDelegateCommand(SaveDocumentAs, () => !IsWorking);
  65. SaveAllDocumentsCommand = new AwaitableDelegateCommand(SaveAllDocuments, () => !IsWorking);
  66. PublishDocumentCommand = new AwaitableDelegateCommand(PublishDocument, () => !IsWorking);
  67. OpenDocumentCommand = new DelegateCommand(OpenDocument, () => !IsWorking);
  68. OpenFromWebCommand = new AwaitableDelegateCommand(OpenFromWeb, () => !IsWorking);
  69. CloseDocumentCommand = new DelegateCommand(CloseDocument, () => ActiveDocumentViewModel != null);
  70. ActivateItem(mdi);
  71. }
  72. public override string DisplayName
  73. {
  74. get { return "MarkPad"; }
  75. set { }
  76. }
  77. public ICommand NewDocumentCommand { get; private set; }
  78. public ICommand NewJekyllDocumentCommand { get; private set; }
  79. public IAsyncCommand SaveDocumentCommand { get; private set; }
  80. public IAsyncCommand SaveDocumentAsCommand { get; private set; }
  81. public IAsyncCommand SaveAllDocumentsCommand { get; private set; }
  82. public IAsyncCommand PublishDocumentCommand { get; private set; }
  83. public ICommand OpenDocumentCommand { get; private set; }
  84. public ICommand OpenFromWebCommand { get; private set; }
  85. public ICommand CloseDocumentCommand { get; private set; }
  86. private string currentState;
  87. public string CurrentState
  88. {
  89. get { return currentState; }
  90. set
  91. {
  92. currentState = value;
  93. if (ActiveDocumentViewModel == null) return;
  94. MDI.HtmlPreview.Visibility = (currentState == ShowSettingsState) ? Visibility.Hidden : Visibility.Visible;
  95. }
  96. }
  97. public MdiViewModel MDI { get; private set; }
  98. public SettingsViewModel Settings { get; private set; }
  99. public UpdaterViewModel Updater { get; set; }
  100. public DocumentViewModel ActiveDocumentViewModel { get { return MDI.ActiveItem as DocumentViewModel; } }
  101. public string WorkingText { get; private set; }
  102. public bool IsWorking { get; private set; }
  103. public IDisposable DoingWork(string work)
  104. {
  105. lock (workLock)
  106. {
  107. IsWorking = true;
  108. loadingMessages.Add(work);
  109. WorkingText = work;
  110. var view = (DependencyObject)GetView();
  111. AutomationProperties.SetHelpText(view, "Busy");
  112. return new DelegateDisposable(() =>
  113. {
  114. lock (workLock)
  115. {
  116. loadingMessages.Remove(work);
  117. if (loadingMessages.Count > 0)
  118. {
  119. IsWorking = true;
  120. WorkingText = loadingMessages.Last();
  121. }
  122. else
  123. {
  124. IsWorking = false;
  125. WorkingText = null;
  126. AutomationProperties.SetHelpText(view, string.Empty);
  127. }
  128. }
  129. });
  130. }
  131. }
  132. public IDisposable UpdateMessage(string newMessage, IDisposable workDisposible)
  133. {
  134. var newDisposable = DoingWork(newMessage);
  135. workDisposible.Dispose();
  136. return newDisposable;
  137. }
  138. public void Exit()
  139. {
  140. TryClose();
  141. }
  142. public void NewDocument(string text = "")
  143. {
  144. // C.M passes in "text"...?
  145. if (text == "text") text = "";
  146. var documentViewModel = documentViewModelFactory();
  147. var newDocumentName = BuildNewDocumentName();
  148. documentViewModel.Open(documentFactory.NewDocument(text, newDocumentName), isNew: true);
  149. MDI.Open(documentViewModel);
  150. documentViewModel.Update();
  151. }
  152. private string BuildNewDocumentName()
  153. {
  154. var newDocumentName = NewDocumentDefaultName;
  155. var newItemNo = ++numberOfNewDocuments;
  156. newDocumentName += newItemNo != 1 ? " (" + newItemNo + ")" : "";
  157. return newDocumentName;
  158. }
  159. private static string CreateJekyllHeader()
  160. {
  161. const string permalink = "new-page.html";
  162. const string title = "New Post";
  163. const string description = "Some Description";
  164. var date = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss zzz");
  165. return string.Format("---\r\nlayout: post\r\ntitle: {0}\r\npermalink: {1}\r\ndescription: {2}\r\ndate: {3}\r\ntags: \"some tags here\"\r\n---\r\n\r\n", title, permalink, description, date);
  166. }
  167. public void OpenDocument()
  168. {
  169. if (IsWorking) return;
  170. var path = dialogService.GetFileOpenPath("Open a markdown document.", Constants.ExtensionFilter + "|Any File (*.*)|*.*");
  171. if (path == null)
  172. return;
  173. OpenDocument(path);
  174. }
  175. public void OpenDocument(IEnumerable<string> filenames)
  176. {
  177. if (IsWorking) return;
  178. if (filenames == null) return;
  179. foreach (var fn in filenames)
  180. {
  181. eventAggregator.Publish(new FileOpenEvent(fn));
  182. }
  183. }
  184. private async Task SaveDocument()
  185. {
  186. if (IsWorking) return;
  187. var doc = MDI.ActiveItem as DocumentViewModel;
  188. if (doc != null)
  189. {
  190. using (DoingWork(string.Format("Saving {0}", doc.MarkpadDocument.Title)))
  191. {
  192. await doc.Save();
  193. }
  194. }
  195. }
  196. private async Task SaveDocumentAs()
  197. {
  198. if (IsWorking) return;
  199. var doc = MDI.ActiveItem as DocumentViewModel;
  200. if (doc != null)
  201. {
  202. using (DoingWork(string.Format("Saving {0}", doc.MarkpadDocument.Title)))
  203. {
  204. await doc.SaveAs();
  205. }
  206. }
  207. }
  208. public async Task SaveAllDocuments()
  209. {
  210. if (IsWorking) return;
  211. using (DoingWork(string.Format("Saving all documents")))
  212. {
  213. foreach (DocumentViewModel doc in MDI.Items)
  214. {
  215. await doc.Save();
  216. }
  217. }
  218. }
  219. public void CloseDocument()
  220. {
  221. if (IsWorking) return;
  222. if (CurrentState == ShowSettingsState)
  223. {
  224. Handle(new SettingsCloseEvent());
  225. return;
  226. }
  227. var doc = MDI.ActiveItem as DocumentViewModel;
  228. if (doc != null)
  229. {
  230. MDI.CloseItem(doc);
  231. return;
  232. }
  233. TryClose();
  234. }
  235. public void ShowSettings()
  236. {
  237. CurrentState = ShowSettingsState;
  238. }
  239. public void ToggleWebView()
  240. {
  241. var doc = MDI.ActiveItem as DocumentViewModel;
  242. if (doc != null)
  243. {
  244. doc.DistractionFree = !doc.DistractionFree;
  245. }
  246. }
  247. public void PrintDocument()
  248. {
  249. MDI.HtmlPreview.Print();
  250. }
  251. public void ShowHelp()
  252. {
  253. OpenHelpDocument("Markdown Help", "MarkdownHelp");
  254. OpenHelpDocument("MarkPad Help", "MarkPadHelp");
  255. }
  256. private void OpenHelpDocument(string title, string content)
  257. {
  258. if (!IsDocumentAlreadyOpen(title))
  259. {
  260. var documentViewModel = documentViewModelFactory();
  261. documentViewModel.Open(documentFactory.CreateHelpDocument(title, GetHelpText(content)));
  262. MDI.Open(documentViewModel);
  263. }
  264. }
  265. private bool IsDocumentAlreadyOpen(string screenName)
  266. {
  267. return MDI.Items.Any(item => item.DisplayName == screenName);
  268. }
  269. private static string GetHelpText(string file)
  270. {
  271. var helpResourceFile = "MarkPad." + file + ".md";
  272. using (var resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(helpResourceFile))
  273. using (var streamReader = new StreamReader(resourceStream))
  274. {
  275. return streamReader.ReadToEnd();
  276. }
  277. }
  278. private async Task PublishDocument()
  279. {
  280. var doc = MDI.ActiveItem as DocumentViewModel;
  281. if (doc == null) return;
  282. using (DoingWork(string.Format("Publishing {0}", doc.MarkpadDocument.Title)))
  283. {
  284. await doc.Publish();
  285. }
  286. }
  287. private async Task OpenFromWeb()
  288. {
  289. var result = await openDocumentFromWeb.Open();
  290. if (result.Success != true)
  291. return;
  292. var postId = result.SelectedPost.postid != null ? result.SelectedPost.postid.ToString() : null;
  293. var title = result.SelectedPost.title;
  294. var metaWebLogItem = new WebDocumentItem(null, eventAggregator, postId, title, result.SelectedBlog);
  295. await OpenDocument(metaWebLogItem, title, () => documentFactory.OpenBlogPost(result.SelectedBlog, postId, title));
  296. }
  297. public void Handle(SettingsCloseEvent message)
  298. {
  299. CurrentState = "HideSettings";
  300. }
  301. public async void Handle(FileOpenEvent message)
  302. {
  303. if (!fileSystem.File.Exists(message.Path)) return;
  304. var fileSystemSiteItem = new FileSystemSiteItem(eventAggregator, fileSystem, message.Path);
  305. await OpenDocument(fileSystemSiteItem, message.Path, () => documentFactory.OpenDocument(message.Path));
  306. }
  307. public async void Handle(OpenFromWebEvent message)
  308. {
  309. var metaWebLogItem = new WebDocumentItem(null, eventAggregator, message.Id, message.Name, message.Blog);
  310. await OpenDocument(metaWebLogItem, message.Name, () => documentFactory.OpenBlogPost(message.Blog, message.Id, message.Name));
  311. }
  312. async Task OpenDocument(ISiteItem siteItem, string documentName, Func<Task<IMarkpadDocument>> openDocument)
  313. {
  314. try
  315. {
  316. var openedDocs = MDI.Items.Cast<DocumentViewModel>();
  317. var openedDoc = openedDocs.SingleOrDefault(d => d.MarkpadDocument.IsSameItem(siteItem));
  318. if (openedDoc != null)
  319. MDI.ActivateItem(openedDoc);
  320. else
  321. {
  322. using (DoingWork(string.Format("Opening {0}", documentName)))
  323. {
  324. var document = await openDocument();
  325. var doc = documentViewModelFactory();
  326. doc.Open(document);
  327. MDI.Open(doc);
  328. }
  329. }
  330. }
  331. catch (Exception ex)
  332. {
  333. DoDefaultErrorHandling(ex, string.Format("Open {0}", documentName));
  334. }
  335. }
  336. private void DoDefaultErrorHandling(Exception e, string operation)
  337. {
  338. // We don't care about cancelled exceptions
  339. if (e is TaskCanceledException)
  340. return;
  341. dialogService.ShowError((string.IsNullOrEmpty(operation) ? "Error occured" : string.Format("Failed to {0}", operation)), e.Message, null);
  342. }
  343. public SearchSettings SearchSettings { get; private set; }
  344. public bool SearchingWithBar
  345. {
  346. get { return SearchSettings.SearchingWithBar; }
  347. set
  348. {
  349. if (SearchSettings.SearchingWithBar == value) return;
  350. SearchSettings.SearchingWithBar = value;
  351. if (!SearchSettings.SearchingWithBar)
  352. {
  353. if (ActiveDocumentViewModel != null)
  354. {
  355. ActiveDocumentViewModel.View.Editor.Focus();
  356. }
  357. SearchSettings.SelectSearch = true;
  358. }
  359. if (SearchSettings.SearchingWithBar)
  360. {
  361. SearchSettings.SelectSearch = false;
  362. Search(SearchType.Normal);
  363. }
  364. }
  365. }
  366. public void Search(SearchType searchType)
  367. {
  368. if (ActiveDocumentViewModel == null) return;
  369. var selectSearch = SearchSettings.SelectSearch || (searchType == SearchType.Next || searchType == SearchType.Prev || searchType == SearchType.Replace);
  370. ActiveDocumentViewModel.SearchProvider.DoSearch(searchType, selectSearch);
  371. SearchSettings.SelectSearch = true;
  372. if (searchType == SearchType.Normal)
  373. {
  374. // update the search highlighting
  375. ActiveDocumentViewModel.View.Editor.TextArea.TextView.Redraw();
  376. }
  377. }
  378. }
  379. }