using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Xml; namespace LunarSF.SHomeWorkshop.LunarMarkdownEditor { /// <summary> /// 工作区管理器中的条目。 /// </summary> public class WorkspaceTreeViewItem : TreeViewItem { static WorkspaceTreeViewItem() { fileBitmapImage = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/DocumentHS.png")); directoryBitmapImage = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/FolderHS.png")); pictureImage = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/PictureHS.png")); folderDocumentImage = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/FolderDocument.png")); imageFolderImage = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/ImageFolder.png")); soundImage = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/sound.png")); soundFolderImage = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/soundFolderImage.png")); vedioImage = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/vedio.png")); vedioFolderImage = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/vedioFolderImage.png")); } public enum Type { File, //Markdown文件节点 Folder, //文件夹节点 MetaFile, //目录元文件节点 Image, //图像文件节点 ImageFolder, //Images~目录节点 Sound, //音频文件节点 SoundFolder, //Sounds~目录节点 Vedio, //视频文件节点 VedioFolder, //Vedios~目录节点 } /// <summary> /// 表示此文件或目录是否被标记为“已废弃”。 /// </summary> public bool IsAborted { get { string filePath = null; if (this.IsMarkdownFilePath) filePath = this.fullPath; else if (this.IsDirectoryExists) { if (this.FullPath.EndsWith("~") == false && this.FullPath.EndsWith("~\\") == false) { filePath = Globals.MainWindow.GetMetaFilePathOfDirectory(this.FullPath); } } if (filePath == null) return false; var isAbortedFile = CustomMarkdownSupport.IsFileAborted(filePath); return isAbortedFile; } } /// <summary> /// 该文件是否被加密。 /// </summary> public bool IsEncrypted { get { string filePath = null; if (this.IsMarkdownFilePath) filePath = this.fullPath; else if (this.IsDirectoryExists) { if (this.FullPath.EndsWith("~") == false && this.FullPath.EndsWith("~\\") == false) { filePath = Globals.MainWindow.GetMetaFilePathOfDirectory(this.FullPath); } } if (filePath == null) return false; string password, passwordTip; return CustomMarkdownSupport.IsFileEncrypted(filePath, out password, out passwordTip); } } /// <summary> /// 节点类型。 /// </summary> public Type ItemType { get; set; } /// <summary> /// 节点Title左侧表示文件完成进度的文本。 /// </summary> public string StatuHeader { get { return this.statusHeader; } set { this.statusHeader = value; } } /// <summary> /// 表示文件完成状态的注释文本。 /// </summary> public string StatuTail { get { return this.statusTail; } set { this.statusTail = value; } } /// <summary> /// 图像文件图标。 /// </summary> static BitmapImage pictureImage; /// <summary> /// Markdown 文件图标。 /// </summary> static BitmapImage fileBitmapImage; /// <summary> /// Markdown 文件图标。 /// </summary> public static BitmapImage FileBitmapImage { get { return fileBitmapImage; } } /// <summary> /// 目录(文件夹)图标。 /// </summary> static BitmapImage directoryBitmapImage; /// <summary> /// 目录文件组合图标。 /// </summary> static BitmapImage folderDocumentImage; /// <summary> /// 目录文件组合图标。 /// </summary> public static BitmapImage FolderDocumentImage { get { return folderDocumentImage; } } /// <summary> /// 图像资源文件夹图标。 /// </summary> static BitmapImage imageFolderImage; /// <summary> /// 声音资源文件夹图标。 /// </summary> static BitmapImage soundFolderImage; /// <summary> /// 声音资源图标。 /// </summary> static BitmapImage soundImage; /// <summary> /// 视频资源文件夹图标。 /// </summary> static BitmapImage vedioFolderImage; /// <summary> /// 视频资源图标。 /// </summary> static BitmapImage vedioImage; private Brush foregroundOfText = Brushes.Black; /// <summary> /// 文本前景色。 /// </summary> public Brush ForegroundOfText { get { return foregroundOfText; } } private bool isExcluded = false; /// <summary> /// 提供这个属性是为了允许用户阻止某些文件被编译到CHM中。默认为false。 /// </summary> public bool IsExcluded { get { return this.isExcluded; } set { this.isExcluded = value; } } /// <summary> /// 用于树型列表框中各条目在选中时的边框色。 /// </summary> //public static Brush TreeViewItemBorderBrush = new SolidColorBrush(Color.FromArgb(255, 250, 160, 0)); /// <summary> /// [构造方法]工作区管理器条目。 /// </summary> /// <param name="fullPath">指向的对象文件的完整磁盘路径。</param> /// <param name="masterWindow">主窗口。</param> public WorkspaceTreeViewItem(string fullPath, MainWindow masterWindow) { this.fullPath = fullPath; if (Directory.Exists(this.fullPath)) { if (this.fullPath.EndsWith("\\") == false) { this.fullPath += "\\"; } } this.masterWindow = masterWindow; this.BorderThickness = new Thickness(1); this.Padding = new Thickness(2); this.SnapsToDevicePixels = true; this.showTitle = Globals.MainWindow.ShowTitleInWorkspaceManager; this.headerPanel.Children.Add(this.icon); this.headerPanel.Children.Add(this.headerTextBlock); this.Header = this.headerPanel; if (IsDirectoryExists) { headerTextBlock.FontWeight = FontWeights.Bold; if (IsImageResourceDirectory) { headerTextBlock.Foreground = foregroundOfText = Brushes.DimGray; this.icon.Source = imageFolderImage; this.ItemType = Type.ImageFolder; } else if (IsSoundResourceDirectory) { headerTextBlock.Foreground = foregroundOfText = Brushes.DimGray; this.icon.Source = soundFolderImage; this.ItemType = Type.SoundFolder; } else if (IsVedioResourceDirectory) { headerTextBlock.Foreground = foregroundOfText = Brushes.DimGray; this.icon.Source = vedioFolderImage; this.ItemType = Type.VedioFolder; } else { headerTextBlock.Foreground = foregroundOfText = Brushes.Black; this.icon.Source = directoryBitmapImage; this.ItemType = Type.Folder; DirectoryInfo di = new DirectoryInfo(FullPath); var metaFilePath = (di.FullName.EndsWith("\\") ? di.FullName : (di.FullName + "\\")) + "_" + di.Name + ".md"; } if (fullPath == Globals.PathOfWorkspace) { this.ToolTip = "工作区目录"; } else { this.ToolTip = this.FullPath; } } else if (IsImageFileExist) { this.icon.Source = pictureImage; this.ItemType = Type.Image; this.ToolTip = "单击预览,双击在文档中添加引用"; } else if (IsSoundFileExist) { this.icon.Source = soundImage; this.ItemType = Type.Sound; this.ToolTip = "单击预览,双击在文档中添加引用"; } else if (IsVedioFileExist) { this.icon.Source = vedioImage; this.ItemType = Type.Vedio; this.ToolTip = "单击预览,双击在文档中添加引用"; } else if (IsValidateFilePath) //if(IsMarkdownFilePath) { if (IsFolderDocument) { this.icon.Source = folderDocumentImage; this.ItemType = Type.MetaFile; } else { this.icon.Source = fileBitmapImage; this.ItemType = Type.File; } this.ToolTip = this.FullPath; } this.headerTextBlock.Inlines.Add(fileStatusSpan); this.headerTextBlock.Inlines.Add(splitterSpan); this.headerTextBlock.Inlines.Add(titleOrShortNameSpan); //this.headerTextBlock.Inlines.Add(new Span(new Run(this.ShortName))); //改成从文件中读取标题文本。 RefreshFileState(); //含调用ShowHeaderText(); //this.headerTextBlock.Foreground = this.Foreground; //Binding foregroundBinding = new Binding() { Source = this, Path = new PropertyPath(TreeViewItem.ForegroundProperty), }; //this.SetBinding(TextBlock.ForegroundProperty, foregroundBinding); this.PreviewMouseLeftButtonDown += WorkspaceTreeViewItem_PreviewMouseLeftButtonDown; this.headerPanel.PreviewMouseDown += headerPanel_PreviewMouseDown; //更改默认样式 var style = FindResource("MetroTreeViewItem") as Style; if (style != null) this.Style = style; this.Background = Brushes.Transparent; } /// <summary> /// /// 这个构造用于已经知道各参数的情况下直接构造——避免再读取硬盘。 /// </summary> /// <param name="title"></param> /// <param name="path"></param> /// <param name="statusHeader"></param> /// <param name="statusTail"></param> /// <param name="tooltip"></param> /// <param name="itemType"></param> /// <param name="masterWindow"></param> public WorkspaceTreeViewItem(string title, string path, string statusHeader, string statusTail, string tooltip, Type itemType, MainWindow masterWindow) { this.title = title; this.fullPath = path; this.statusHeader = statusHeader; this.statusTail = statusTail; this.ItemType = itemType; this.masterWindow = masterWindow; if (Directory.Exists(this.fullPath)) { if (this.fullPath.EndsWith("\\") == false) { this.fullPath += "\\"; } } this.BorderThickness = new Thickness(1); this.Padding = new Thickness(2); this.SnapsToDevicePixels = true; this.showTitle = Globals.MainWindow.ShowTitleInWorkspaceManager; this.headerPanel.Children.Add(this.icon); this.titleOrShortNameSpan.Inlines.Add(new Run(title));//这里的title也可能是短文件名。 this.headerPanel.Children.Add(this.headerTextBlock); this.Header = this.headerPanel; if (string.IsNullOrWhiteSpace(tooltip) == false) { this.ToolTip = tooltip; } switch (this.ItemType) { case Type.File: this.icon.Source = fileBitmapImage; break; case Type.Folder: headerTextBlock.FontWeight = FontWeights.Bold; headerTextBlock.Foreground = foregroundOfText = Brushes.Black; this.icon.Source = directoryBitmapImage; break; case Type.MetaFile: this.icon.Source = folderDocumentImage; break; case Type.Image: this.icon.Source = pictureImage; this.ToolTip = "单击预览,双击在文档中添加引用"; break; case Type.ImageFolder: headerTextBlock.FontWeight = FontWeights.Bold; headerTextBlock.Foreground = foregroundOfText = Brushes.DimGray; this.icon.Source = imageFolderImage; break; case Type.Sound: this.icon.Source = soundImage; this.ToolTip = "单击预览,双击在文档中添加引用"; break; case Type.SoundFolder: headerTextBlock.FontWeight = FontWeights.Bold; headerTextBlock.Foreground = foregroundOfText = Brushes.DimGray; this.icon.Source = soundFolderImage; break; case Type.Vedio: this.icon.Source = vedioImage; this.ToolTip = "单击预览,双击在文档中添加引用"; break; case Type.VedioFolder: headerTextBlock.FontWeight = FontWeights.Bold; headerTextBlock.Foreground = foregroundOfText = Brushes.DimGray; this.icon.Source = vedioFolderImage; break; } //if (IsDirectoryExists) //{ // if (fullPath == Globals.PathOfWorkspace) // { // this.ToolTip = "工作区目录"; // } // else // { // this.ToolTip = this.FullPath; // } // DirectoryInfo di = new DirectoryInfo(FullPath); // var metaFilePath = (di.FullName.EndsWith("\\") ? di.FullName : (di.FullName + "\\")) + "_" + di.Name + ".md"; //} this.headerTextBlock.Inlines.Add(fileStatusSpan); this.headerTextBlock.Inlines.Add(splitterSpan); this.headerTextBlock.Inlines.Add(titleOrShortNameSpan); //改成从文件中读取标题文本。 this.RefreshFileStateByHeader(statusHeader, statusTail); //此构造方法中不需要重复设置下面这两行。 //this.StatuHeader = header; //this.StatuTail = tail; this.PreviewMouseLeftButtonDown += WorkspaceTreeViewItem_PreviewMouseLeftButtonDown; this.headerPanel.PreviewMouseDown += headerPanel_PreviewMouseDown; //更改默认样式 var style = FindResource("MetroTreeViewItem") as Style; if (style != null) this.Style = style; this.Background = Brushes.Transparent; } /// <summary> /// 双击当前项触发不同操作: /// 如果是 Markdown 文件,打开; /// 如果是图像文件,预览并作为链接插入到当前活动编辑器中; /// 如果是目录,打开或创建目录元文件。 /// </summary> private void WorkspaceTreeViewItem_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e) { if (e.ClickCount == 2) { if (IsMarkdownFilePath) { OpenFile(); } else if (IsValidateImageFilePath(this.fullPath)) { PreviewImage(); InsertImageTagToDocument(this.fullPath, true); } else if (IsValidateSoundFilePath(this.fullPath)) { PreviewSound(); InsertSoundTagToDocument(this.fullPath, true); } else if (IsDirectoryExists) { OpenOrCreateDirectoryMetaFile(); } } else if (e.ClickCount == 1) { if (IsValidateImageFilePath(this.fullPath)) { PreviewImage(); } else if (IsValidateSoundFilePath(this.fullPath)) { PreviewSound(); } } } /// <summary> /// 取文件的注释短名。 /// 当图像文件为中文文件名时,编译为 CHM 易出现问题,而英文又不够直观。 /// 所以,考虑直接将图像文件命名为拼音(纯英文字母即可), /// 然后在一个单独的纯文本文件中记录这些英文字母对应的中文短名。 /// /// 注意:此备注文件中同一文件短名可能存在多行备注,以最后一个为准。例如: /// /// tp1.png|图片1.png /// tp2.png|图片2.png /// tp1.png|图片1新名称.png /// /// 此时返回的会是“图片1新名称.png”。也就是说“后一个会覆盖前一个”。 /// </summary> /// <param name="filePath">通常应传入图像文件的完整路径。</param> public static string GetCommentShortName(string filePath) { if (File.Exists(filePath)) { FileInfo fi = new FileInfo(filePath); var commentFilePath = fi.DirectoryName + "\\" + "~.txt"; if (File.Exists(commentFilePath) == false) return fi.Name; var lines = File.ReadAllLines(commentFilePath, Encoding.UTF8); var splitter = new char[] { '|' }; for (int i = lines.Length - 1; i >= 0; i--) { var line = lines[i]; if (line.StartsWith(";") || line.StartsWith(";") || line.StartsWith("/")) continue; var spans = line.Split(splitter, StringSplitOptions.RemoveEmptyEntries); if (spans.Length != 2) continue; if (spans[0] == fi.Name) return spans[1]; } return fi.Name; } return ""; } /// <summary> /// 根据 “ShowTitle”刷新。如果“ShowTitle”为真,尝试显示文档中设置的文档标题;否则直接显示文件短名。 /// </summary> private void ShowHeaderText() { this.titleOrShortNameSpan.Inlines.Clear(); if (showTitle) { if (this.IsMarkdownFilePath) { title = MainWindow.GetTitleOfMdFile(this.fullPath); if (string.IsNullOrWhiteSpace(title) == false) { this.titleOrShortNameSpan.Inlines.Add(new Run(title)); } else { this.titleOrShortNameSpan.Inlines.Add(new Run(this.ShortName)); } } else if (this.IsDirectoryExists) { DirectoryInfo di = new DirectoryInfo(this.fullPath); if (di.Name.ToLower() == "images~") { this.titleOrShortNameSpan.Inlines.Add(new Run(title = "图像~")); } else if (di.Name.ToLower() == "sounds~") { this.titleOrShortNameSpan.Inlines.Add(new Run(title = "声音~")); } else if (di.Name.ToLower() == "vedios~") { this.titleOrShortNameSpan.Inlines.Add(new Run(title = "视频~")); } else { var metaFilePath = (di.FullName.EndsWith("\\") ? di.FullName : (di.FullName + "\\")) + "_" + di.Name + ".md"; if (File.Exists(metaFilePath)) { var metaFileTitle = MainWindow.GetTitleOfMdFile(metaFilePath); if (string.IsNullOrWhiteSpace(metaFilePath) == false) { this.titleOrShortNameSpan.Inlines.Add(new Run(title = metaFileTitle)); } else { this.titleOrShortNameSpan.Inlines.Add(new Run(title = this.ShortName)); } } else { this.titleOrShortNameSpan.Inlines.Add(new Run(title = this.ShortName)); } } } else if (this.IsImageFileExist || this.IsSoundFileExist || this.IsVedioFileExist) { this.titleOrShortNameSpan.Inlines.Add(new Run(title = GetCommentShortName(this.fullPath))); } else { this.titleOrShortNameSpan.Inlines.Add(new Run(title = this.ShortName)); } } else { this.titleOrShortNameSpan.Inlines.Add(new Run(title = this.ShortName)); } } private bool showTitle = false; /// <summary> /// 为真时尝试显示在文件中设置的文档标题。否则直接显示文件短名。 /// </summary> public bool ShowTitle { get { return showTitle; } set { showTitle = value; ShowHeaderText(); } } /// <summary> /// 更改选取状态。 /// </summary> void headerPanel_PreviewMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e) { this.IsSelected = true; } /// <summary> /// 打开或创建目录元文件。 /// </summary> private void OpenOrCreateDirectoryMetaFile() { if (this.masterWindow != null) { string[] files = new string[1]; var di = new DirectoryInfo(this.FullPath); if (di.Name.EndsWith("~")) { LMessageBox.Show(" 以波型符结尾的是资源文件夹,不允许创建对应 Markdown 文件。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning); return; } files[0] = di.FullName + "_" + di.Name + ".md"; try { if (System.IO.File.Exists(files[0]) == false) { File.WriteAllText(files[0], $"\r\n%{MainWindow.FormatDocumentTitle(di.Name)}\r\n\r\n;{DateTime.Now.ToString()}"); } this.masterWindow.OpenDocuments(files); var workdpaceEntryItem = this.masterWindow.FindWorkspaceTreeViewItem(di.FullName); if (workdpaceEntryItem != null) workdpaceEntryItem.RefreshFileState(); } catch (Exception ex) { LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning); return; } } } /// <summary> /// 向当前活动编辑器中添加对图像文件的引用。 /// 根据图像文件是否在工作区决定是否将图像文件复制到工作区目录下。 /// </summary> /// <param name="imageFilePath">图像文件路径。</param> /// <param name="noRefreshWorkspaceManager">是否不需要刷新主界面工作区管理器。</param> public void InsertImageTagToDocument(string imageFilePath, bool noRefreshWorkspaceManager = false) { //在当前文档中添加对当前图片的引用。 if (File.Exists(imageFilePath) == false) { return; } //如果该图片不在当前文件资源文件夹下,先复制,然后再添加。 var selEditor = Globals.MainWindow.ActivedEditor; if (selEditor == null) return; if (File.Exists(selEditor.FullFilePath) == false) { LMessageBox.Show(" 需要先保存正在编辑的文件才能使用这个功能。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning); return; } var curDocumentImageResourceFolder = selEditor.ImageResourceDirectoryPath; if (Directory.Exists(curDocumentImageResourceFolder) == false) { Directory.CreateDirectory(curDocumentImageResourceFolder); if (noRefreshWorkspaceManager == false) { //Globals.MainWindow.WorkspaceManager.Refresh(null); WorkspaceTreeViewItem wtvi = Globals.MainWindow.FindWorkspaceTreeViewItem(this.fullPath); if (wtvi != null) { WorkspaceTreeViewItem wtviImg = Globals.MainWindow.FindWorkspaceTreeViewItem(imageFilePath); if (wtviImg == null) { wtviImg = new WorkspaceTreeViewItem(imageFilePath, Globals.MainWindow); } if (wtvi.Items.Contains(wtviImg) == false) { Globals.MainWindow.InsertSubWorkspaceTreeViewItem(wtvi, wtviImg); } } } } //这里要注意: //在工作区内的直接添加引用,不在工作区的才复制文件。 selEditor.TryToDropResourcesFiles(new System.Collections.Specialized.StringCollection() { FullPath, }, false); } /// <summary> /// 向当前活动编辑器中添加对声音文件的引用。 /// 根据声音文件是否在工作区决定是否将声音文件复制到工作区目录下。 /// </summary> /// <param name="soundFilePath">声音文件路径。</param> /// <param name="noRefreshWorkspaceManager">是否不需要刷新主界面工作区管理器。</param> public void InsertSoundTagToDocument(string soundFilePath, bool noRefreshWorkspaceManager = false) { //在当前文档中添加对当前声音的引用。 if (File.Exists(soundFilePath) == false) { return; } //如果该声音文件不在当前文件资源文件夹下,先复制,然后再添加。 var selEditor = Globals.MainWindow.ActivedEditor; if (selEditor == null) return; if (File.Exists(selEditor.FullFilePath) == false) { LMessageBox.Show(" 需要先保存正在编辑的文件才能使用这个功能。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning); return; } var curDocumentSoundResourceFolder = selEditor.SoundResourceDirectoryPath; if (Directory.Exists(curDocumentSoundResourceFolder) == false) { Directory.CreateDirectory(curDocumentSoundResourceFolder); if (noRefreshWorkspaceManager == false) { //Globals.MainWindow.WorkspaceManager.Refresh(null); WorkspaceTreeViewItem wtvi = Globals.MainWindow.FindWorkspaceTreeViewItem(this.fullPath); if (wtvi != null) { WorkspaceTreeViewItem wtviImg = Globals.MainWindow.FindWorkspaceTreeViewItem(soundFilePath); if (wtviImg == null) { wtviImg = new WorkspaceTreeViewItem(soundFilePath, Globals.MainWindow); } if (wtvi.Items.Contains(wtviImg) == false) { Globals.MainWindow.InsertSubWorkspaceTreeViewItem(wtvi, wtviImg); } } } } //这里要注意: //在工作区内的直接添加引用,不在工作区的才复制文件。 selEditor.TryToDropResourcesFiles(new System.Collections.Specialized.StringCollection() { FullPath, }, false); } /// <summary> /// 向当前活动编辑器中添加对视频文件的引用。 /// 根据视频文件是否在工作区决定是否将视频文件复制到工作区目录下。 /// </summary> /// <param name="vedioFilePath">视频文件路径。</param> /// <param name="noRefreshWorkspaceManager">是否不需要刷新主界面工作区管理器。</param> public void InsertVedioTagToDocument(string vedioFilePath, bool noRefreshWorkspaceManager = false) { //在当前文档中添加对当前视频的引用。 if (File.Exists(vedioFilePath) == false) { return; } //如果该视频文件不在当前文件资源文件夹下,先复制,然后再添加。 var selEditor = Globals.MainWindow.ActivedEditor; if (selEditor == null) return; if (File.Exists(selEditor.FullFilePath) == false) { LMessageBox.Show(" 需要先保存正在编辑的文件才能使用这个功能。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning); return; } var curDocumentVedioResourceFolder = selEditor.VedioResourceDirectoryPath; if (Directory.Exists(curDocumentVedioResourceFolder) == false) { Directory.CreateDirectory(curDocumentVedioResourceFolder); if (noRefreshWorkspaceManager == false) { //Globals.MainWindow.WorkspaceManager.Refresh(null); WorkspaceTreeViewItem wtvi = Globals.MainWindow.FindWorkspaceTreeViewItem(this.fullPath); if (wtvi != null) { WorkspaceTreeViewItem wtviImg = Globals.MainWindow.FindWorkspaceTreeViewItem(vedioFilePath); if (wtviImg == null) { wtviImg = new WorkspaceTreeViewItem(vedioFilePath, Globals.MainWindow); } if (wtvi.Items.Contains(wtviImg) == false) { Globals.MainWindow.InsertSubWorkspaceTreeViewItem(wtvi, wtviImg); } } } } //这里要注意: //在工作区内的直接添加引用,不在工作区的才复制文件。 selEditor.TryToDropResourcesFiles(new System.Collections.Specialized.StringCollection() { FullPath, }, false); } /// <summary> /// 取当前项对应的资源文件夹的完整路径。 /// </summary> public string ResourceDirectoryFullPath { get { if (IsMarkdownFilePath == false) return string.Empty; var parentDirectoryPath = this.fullPath.Substring(0, this.fullPath.LastIndexOf("\\") + 1); return parentDirectoryPath + ShortName.Substring(0, ShortName.Length - 3) + "~"; } } /// <summary> /// 打开当前项对应的 Markdown 文件。 /// </summary> public void OpenFile() { if (this.IsDirectoryExists) return;//双击目录不进行其它操作,默认会展开,不必写代码。 if (this.masterWindow != null) { string[] files = new string[1]; files[0] = this.fullPath; if (System.IO.File.Exists(this.fullPath) == false) { LMessageBox.Show(" 未找到此文档。可能通过其它途径(例如在Windows资源管理器中)删除了对应的磁盘文件。\r\n 下次启动此程序时将不会再显示这个条目。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning); return; } if (this.IsMarkdownFilePath) { this.masterWindow.OpenDocuments(files); } } } /// <summary> /// 预览当前项指向的图像文件。 /// </summary> public void PreviewImage() { if (this.IsDirectoryExists) return;//双击目录不进行其它操作,默认会展开,不必写代码。 if (this.masterWindow != null) { string[] files = new string[1]; files[0] = this.fullPath; if (System.IO.File.Exists(this.fullPath) == false) { LMessageBox.Show(" 未找到此文档。可能通过其它途径(例如在Windows资源管理器中)删除了对应的磁盘文件。\r\n 下次启动此程序时将不会再显示这个条目。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning); return; } if (this.IsImageFileExist) { try { //this.masterWindow.ImagePreview.Source = new BitmapImage(new Uri(this.fullPath));//不能加此前缀"file:///" + //用上面这个办法会造成图片文件无法删除,因为会被当前进程占用。 using (BinaryReader reader = new BinaryReader(File.Open(this.fullPath, FileMode.Open))) { FileInfo fi = new FileInfo(this.fullPath); byte[] bytes = reader.ReadBytes((int)fi.Length); reader.Close(); var bitmapImage = new BitmapImage(); bitmapImage.BeginInit(); bitmapImage.StreamSource = new MemoryStream(bytes); bitmapImage.EndInit(); Globals.MainWindow.ImagePreview.Source = bitmapImage; Globals.MainWindow.tbImageTitle.Text = this.ImageTitle; if (string.IsNullOrWhiteSpace(Globals.MainWindow.tbImageTitle.Text)) { Globals.MainWindow.tbImageTitle.Visibility = Visibility.Collapsed; } else { Globals.MainWindow.tbImageTitle.Visibility = Visibility.Visible; } bitmapImage.CacheOption = BitmapCacheOption.OnLoad; } this.masterWindow.imagePreviewOutBorder.Tag = this; Globals.MainWindow.tcResourcePreviewArea.SelectedIndex = 0; //自动弹出图像预览区域并不好,容易导致用户找不到当前选中的项目。 //if (Globals.MainWindow.rdLeftTools.ActualHeight < 40) //{ // Globals.MainWindow.rdLeftTools.Height = new GridLength(300); //} } catch (Exception ex) { LMessageBox.Show(ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning); } } } } /// <summary> /// 听当前项所指向的音频文件。 /// </summary> private void PreviewSound() { if (this.IsDirectoryExists) return;//双击目录不进行其它操作,默认会展开,不必写代码。 if (this.masterWindow != null) { string[] files = new string[1]; files[0] = this.fullPath; if (System.IO.File.Exists(this.fullPath) == false) { LMessageBox.Show(" 未找到此文档。可能通过其它途径(例如在Windows资源管理器中)删除了对应的磁盘文件。\r\n 下次启动此程序时将不会再显示这个条目。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning); return; } if (this.IsSoundFileExist) { try { this.masterWindow.mediaElement.Source = new Uri(this.fullPath);//不能加此前缀"file:///" + this.masterWindow.imagePreviewOutBorder.Tag = this; Globals.MainWindow.tcResourcePreviewArea.SelectedIndex = 1; } catch (Exception ex) { LMessageBox.Show(ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning); } } } } /// <summary> /// 承载当前项名称文本、图标的 StackPanel。 /// </summary> private StackPanel headerPanel = new StackPanel() { Orientation = Orientation.Horizontal, Margin = new Thickness(4, 0, 4, 0), }; /// <summary> /// 用于显示文件完成状态标记。 /// </summary> private Span fileStatusSpan = new Span(); /// <summary> /// 用来分隔“文件完成状态标记”与“文件标题(或短名)标记”。 /// </summary> private Span splitterSpan = new Span(); /// <summary> /// 用以显示文档标题(或文件短名)。 /// </summary> private Span titleOrShortNameSpan = new Span(); private TextBlock headerTextBlock = new TextBlock() { Foreground = Brushes.Black, }; /// <summary> /// 用于显示文件短名。 /// </summary> public TextBlock HeaderTextBlock { get { return headerTextBlock; } } /// <summary> /// 用于显示条目类别图标。 /// </summary> private Image icon = new Image() { Width = 16, Height = 16, Margin = new Thickness(0, 0, 2, 0), }; /// <summary> /// IsMarkdownFilePath的别名。 /// </summary> public bool IsValidateFilePath { get { return IsMarkdownFilePath; } } /// <summary> /// 是否合法的文本文件的路径。 /// </summary> public bool IsMarkdownFilePath { get { if (string.IsNullOrEmpty(this.fullPath)) return false; if (File.Exists(this.fullPath) == false) return false; if (this.fullPath.ToLower().EndsWith(".md") == false) return false; if (this.fullPath.Length <= 4) return false; return true; } } /// <summary> /// 是否带元文件、并可以添加下级目录或文件的普通目录。 /// 如果是资源目录则返回 false。 /// </summary> public bool IsMetaDirectoryPath { get { if (IsDirectoryExists == false) return false; if (this.FullPath.EndsWith("~\\") || this.FullPath.EndsWith("~")) return false; return true; } } /// <summary> /// 如果不是普通目录(而是资源目录或文件),返回null。 /// 如果是普通目录(带元文件),返回目录元文件的完整路径。 /// </summary> public string MetaFilePath { get { if (IsMetaDirectoryPath == false) return null; DirectoryInfo di = new DirectoryInfo(this.FullPath); var path = (di.FullName.EndsWith("\\") ? di.FullName : (di.FullName + "\\")) + "_" + di.Name + ".md"; return path; } } /// <summary> /// 如果不是普通目录(而是资源目录或文件),返回null。 /// 如果是普通目录(带元文件),返回目录元文件编译成的 Html 的完整路径。 /// </summary> public string MetaHtmlFilePath { get { if (IsMetaDirectoryPath == false) return null; DirectoryInfo di = new DirectoryInfo(this.FullPath); var path = (di.FullName.EndsWith("\\") ? di.FullName : (di.FullName + "\\")) + "_" + di.Name + ".html"; return path; } } /// <summary> /// 是不是所在目录指向的特定文件,规则是:“_目录名.md”(“_目录名.html”) /// </summary> public bool IsFolderDocument { get { if (IsMarkdownFilePath == false) return false; if (File.Exists(this.fullPath) == false) return false; FileInfo fileInfo = new FileInfo(this.fullPath); if (fileInfo.Name.ToLower() == ("_" + fileInfo.Directory.Name + ".md").ToLower()) { return true; } return false; } } private string fullPath; /// <summary> /// 当前项指向的 Markdown 文件、图像文件、目录的完全路径。 /// </summary> public string FullPath { get { if (IsDirectoryExists && this.fullPath.EndsWith("\\") == false) { this.fullPath += "\\"; } return this.fullPath; } set { this.fullPath = value; if (Directory.Exists(this.fullPath)) { if (this.fullPath.EndsWith("\\") == false) this.fullPath += "\\"; } this.RefreshFileState(); this.ToolTip = this.fullPath; } } /// <summary> /// 当前项指向的 Markdown 文件、图像文件、目录的短名。 /// </summary> public string ShortName { get { if (string.IsNullOrEmpty(this.fullPath)) { return string.Empty; } if (this.fullPath.EndsWith("\\"))//目录 { if (this.fullPath.Length < 2) return string.Empty; int index = this.fullPath.LastIndexOf('\\', this.fullPath.Length - 2);//不取最后一个 if (index < 0) return this.fullPath.Substring(0, this.fullPath.Length - 1); return this.fullPath.Substring(index + 1, this.fullPath.Length - index - 2);//不含反斜杠。 } else { int lastIndex = this.fullPath.LastIndexOf('\\'); if (lastIndex < 0) return this.fullPath; return this.fullPath.Substring(lastIndex + 1); } } } private string title = ""; /// <summary> /// 显示出来的标题文本。 /// </summary> public string Title { get { return title; } } public string ImageTitle { get { if (string.IsNullOrWhiteSpace(title)) return ""; if (title.ToLower().EndsWith(".png")) return title.Substring(0, title.Length - 4); if (title.ToLower().EndsWith(".jpg")) return title.Substring(0, title.Length - 4); if (title.ToLower().EndsWith(".jpeg")) return title.Substring(0, title.Length - 5); if (title.ToLower().EndsWith(".gif")) return title.Substring(0, title.Length - 4); if (title.ToLower().EndsWith(".ico")) return title.Substring(0, title.Length - 4); if (title.ToLower().EndsWith(".tiff")) return title.Substring(0, title.Length - 5); if (title.ToLower().EndsWith(".bmp")) return title.Substring(0, title.Length - 4); return title; } } /// <summary> /// 当前项指向的对象是否有效目录。 /// </summary> public bool IsDirectoryExists { get { return Directory.Exists(this.fullPath); } } /// <summary> /// 当前项指向的对象是否有效的图像资源文件夹。 /// </summary> public bool IsImageResourceDirectory { get { if (IsDirectoryExists == false) return false; DirectoryInfo directoryInfo = new DirectoryInfo(this.fullPath); if (directoryInfo.Name.ToLower() == "images~") return true; return false; } } /// <summary> /// 当前项指向的对象是否有效的声音资源文件夹。 /// </summary> public bool IsSoundResourceDirectory { get { if (IsDirectoryExists == false) return false; DirectoryInfo directoryInfo = new DirectoryInfo(this.fullPath); if (directoryInfo.Name.ToLower() == "sounds~") return true; return false; } } /// <summary> /// 当前项指向的对象是否有效的视频资源文件夹。 /// </summary> public bool IsVedioResourceDirectory { get { if (IsDirectoryExists == false) return false; DirectoryInfo directoryInfo = new DirectoryInfo(this.fullPath); if (directoryInfo.Name.ToLower() == "vedios~") return true; return false; } } /// <summary> /// 当前项指向的对象是否有效的图像资源文件。 /// </summary> public bool IsImageFileExist { get { var b = IsValidateImageFilePath(this.fullPath); return b; } } /// <summary> /// 指定路径是否有效的图像文件路径。 /// </summary> /// <param name="fullPath">要检查的图像文件路径。</param> public static bool IsValidateImageFilePath(string fullPath) { if (File.Exists(fullPath) == false) return false; var path = fullPath.ToLower(); if (path.EndsWith(".bmp")) return true; else if (path.EndsWith(".ico")) return true;//可以支持 else if (path.EndsWith(".png")) return true; else if (path.EndsWith(".gif")) return true; else if (path.EndsWith(".jpg")) return true; else if (path.EndsWith(".jpeg")) return true; else if (path.EndsWith(".tiff")) return true; return false; } /// <summary> /// 当前项指向的对象是否有效的声音资源文件。 /// </summary> public bool IsSoundFileExist { get { var b = IsValidateSoundFilePath(this.fullPath); return b; } } /// <summary> /// 指定路径是否有效的声音文件路径。 /// </summary> /// <param name="fullPath">要检查的声音文件路径。</param> /// <returns></returns> public static bool IsValidateSoundFilePath(string fullPath) { if (File.Exists(fullPath) == false) return false; var path = fullPath.ToLower(); if (path.EndsWith(".mp3")) return true; else if (path.EndsWith(".wav")) return true;//可以支持 else if (path.EndsWith(".wma")) return true; return false; } /// <summary> /// 当前项指向的对象是否有效的视频资源文件。 /// </summary> public bool IsVedioFileExist { get { var b = IsValidateVedioFilePath(this.fullPath); return b; } } /// <summary> /// 指定路径是否有效的视频文件路径。 /// </summary> /// <param name="fullPath">要检查的视频文件路径。</param> /// <returns></returns> public static bool IsValidateVedioFilePath(string fullPath) { if (File.Exists(fullPath) == false) return false; var path = fullPath.ToLower(); if (path.EndsWith(".mp4")) return true; else if (path.EndsWith(".flv")) return true;//可以支持 else if (path.EndsWith(".swf")) return true; return false; } /// <summary> /// 当前项指向的路径是否存在有效的文件。 /// </summary> public bool IsFileExists { get { return File.Exists(this.fullPath); } } /// <summary> /// 当前项指向的路径是否有效的资源文件。 /// </summary> public bool IsResourceFile { get { if (File.Exists(this.fullPath) == false) return false; int lastIndex = this.fullPath.LastIndexOf("\\"); if (lastIndex < 0) return false; var s = this.fullPath.Substring(0, lastIndex); if (s.EndsWith("~") == false) return false; return true; } } /// <summary> /// 当前项指向的目录是否有效的资源目录。 /// </summary> public bool IsResourceDirectory { get { if (Directory.Exists(this.fullPath) == false) return false; if (this.fullPath.EndsWith("~") || this.fullPath.EndsWith("~\\")) return true; return false; } } private MainWindow masterWindow; private string statusHeader; private string statusTail; /// <summary> /// 要用来打开文档,故需要保持此信息。 /// </summary> public MainWindow MasterWindow { get { return this.masterWindow; } } /// <summary> /// 当前项指向的对象所在的上级目录。 /// </summary> public string ParentDirectory { get { if (IsMarkdownFilePath) { return this.fullPath.Substring(0, this.fullPath.LastIndexOf("\\") + 1); } else if (IsDirectoryExists) { var path = this.fullPath; if (path.EndsWith("\\")) path = path.Substring(0, path.Length - 1); int lastIndex = path.LastIndexOf("\\"); if (lastIndex < 0) return null; return path.Substring(0, lastIndex + 1); } return null; } } /// <summary> /// 当前项的父项。 /// </summary> public WorkspaceTreeViewItem ParentWorkspaceTreeViewItem { get { if (this.Parent == null) return null; var parent = this.Parent as WorkspaceTreeViewItem; return parent; } } /// <summary> /// 这是相对工作区的路径。 /// 例如:工作区目录是 C:\秦朝政制制度\ /// 当前项的完全路径是 C:\秦朝政制制度\中央政制\皇帝制度\ /// 则此属性的返回值是 中央政制\皇帝制度\ /// </summary> public string RelativePath { get { var fullPathText = this.FullPath; if (string.IsNullOrWhiteSpace(fullPathText)) return ""; var workspacePathText = Globals.PathOfWorkspace.ToLower(); if (fullPathText.StartsWith(workspacePathText, StringComparison.CurrentCultureIgnoreCase)) { return fullPathText.Substring(workspacePathText.Length); } return ""; } } /// <summary> /// 当项目不是个 Markdown 文件时,返回 RelativePath。 /// 当项目是个 Markdown 文件时,返回的路径后缀名改为“.html”而不是“.md”,这是它和 RelativePath 唯一的区别。 /// </summary> public string RelativeHtmlPath { get { var rp = this.RelativePath; if (string.IsNullOrWhiteSpace(rp)) return rp; if (rp.EndsWith(".md", StringComparison.CurrentCultureIgnoreCase)) { return rp.Substring(0, rp.Length - 3) + ".html"; } if (rp.EndsWith("\\")) { rp = Globals.MainWindow.GetMetaFilePathOfDirectory(this.FullPath); if (rp.EndsWith(".md", StringComparison.CurrentCultureIgnoreCase)) { return rp.Substring(0, rp.Length - 3) + ".html"; } } return rp; } } /// <summary> /// 刷新当前 Markdown 或目录元文件的完成状态标志和标题(如果 Globals.MainWindow.ShowTitleInWorkspaceManager为true)。 /// </summary> internal void RefreshFileState() { this.ShowHeaderText(); if (Directory.Exists(fullPath)) { var dimmm = new DirectoryInfo(fullPath); if (dimmm.Name.ToLower().EndsWith("~")) { this.fileStatusSpan.Inlines.Clear(); return; } } var destFilePath = fullPath; if (File.Exists(fullPath) == false) { if (Directory.Exists(fullPath) == false) { this.headerTextBlock.Inlines.Add(new Span(new Run(this.ShortName))); return; } else { DirectoryInfo di = new DirectoryInfo(fullPath); var metaFilePath = (di.FullName.EndsWith("\\") ? di.FullName : (di.FullName + "\\")) + "_" + di.Name + ".md"; if (File.Exists(metaFilePath) == false) { this.fileStatusSpan.Inlines.Clear(); //实际上后一个是根本不会显示在工作区的。因为路径是这样的:ResourceDirectory~/Images~。 if (this.IsImageResourceDirectory || this.IsResourceDirectory) { this.fileStatusSpan.Inlines.Add(new Run("")); this.StatuHeader = ""; } else { this.fileStatusSpan.Inlines.Add(new Run("[-]")); this.StatuHeader = "[-]"; } this.fileStatusSpan.Background = Brushes.Red; this.fileStatusSpan.Foreground = Brushes.White; this.fileStatusSpan.FontWeight = FontWeights.Bold; this.fileStatusSpan.ToolTip = "未开始"; this.splitterSpan.Inlines.Clear(); this.splitterSpan.Inlines.Add(new Run(" ")); return; } destFilePath = metaFilePath; } } if (this.IsImageFileExist || this.IsSoundFileExist || this.IsVedioFileExist) { this.fileStatusSpan.Inlines.Clear(); return; } using (StreamReader sr = new StreamReader(destFilePath)) { var fstLine = sr.ReadLine(); if (fstLine != null && fstLine.Length >= 3) { string header = ""; Regex regex = new Regex(@"\[[ \t]*[-\-\++\##\%%][ \t]*\]"); var tail = ""; var match = regex.Match(fstLine); if (match.Success) { header = match.Value.Replace("-", "-").Replace("+", "+").Replace("%", "%").Replace("#", "#").Replace(" ", "").Replace(" ", "").Replace("\t", ""); tail = " ◆ " + fstLine.Substring(match.Length).Trim(new char[] { ' ', ' ', '\t' }); } RefreshFileStateByHeader(header, tail); this.StatuHeader = header; this.StatuTail = tail; } else { this.fileStatusSpan.Inlines.Clear(); this.fileStatusSpan.Inlines.Add(new Run("[-]")); this.fileStatusSpan.Background = Brushes.Red; this.fileStatusSpan.Foreground = Brushes.White; this.fileStatusSpan.FontWeight = FontWeights.Bold; this.fileStatusSpan.ToolTip = "未开始"; this.splitterSpan.Inlines.Clear(); this.splitterSpan.Inlines.Add(new Run(" ")); } } } private void RefreshFileStateByHeader(string header, string tail) { switch (ItemType) { case Type.Image: case Type.ImageFolder: case Type.Sound: case Type.SoundFolder: case Type.Vedio: case Type.VedioFolder: { this.fileStatusSpan.Inlines.Clear(); return; } } switch (header) { case "[%]": { this.fileStatusSpan.Inlines.Clear(); this.fileStatusSpan.Inlines.Add(new Run(header)); this.fileStatusSpan.Background = Brushes.Green; this.fileStatusSpan.Foreground = Brushes.White; this.fileStatusSpan.FontWeight = FontWeights.Bold; this.fileStatusSpan.ToolTip = "进行中" + tail; this.splitterSpan.Inlines.Clear(); this.splitterSpan.Inlines.Add(new Run(" ")); break; } case "[+]": { this.fileStatusSpan.Inlines.Clear(); this.fileStatusSpan.Inlines.Add(new Run(header)); this.fileStatusSpan.Background = Brushes.Blue; this.fileStatusSpan.Foreground = Brushes.White; this.fileStatusSpan.FontWeight = FontWeights.Bold; this.fileStatusSpan.ToolTip = "定稿" + tail; this.splitterSpan.Inlines.Clear(); this.splitterSpan.Inlines.Add(new Run(" ")); break; } case "[#]": { this.fileStatusSpan.Inlines.Clear(); this.fileStatusSpan.Inlines.Add(new Run(header)); this.fileStatusSpan.Background = Brushes.Brown; this.fileStatusSpan.Foreground = Brushes.White; this.fileStatusSpan.FontWeight = FontWeights.Bold; this.fileStatusSpan.ToolTip = "废弃" + tail; this.splitterSpan.Inlines.Clear(); this.splitterSpan.Inlines.Add(new Run(" ")); break; } //case "[-]": default: { this.fileStatusSpan.Inlines.Clear(); this.fileStatusSpan.Inlines.Add(new Run("[-]")); this.fileStatusSpan.Background = Brushes.Red; this.fileStatusSpan.Foreground = Brushes.White; this.fileStatusSpan.FontWeight = FontWeights.Bold; this.fileStatusSpan.ToolTip = "未开始" + tail; this.splitterSpan.Inlines.Clear(); this.splitterSpan.Inlines.Add(new Run(" ")); break; } } } /// <summary> /// 当前工作区树型列表项中是否某个子项的路径与指定路径相同。 /// </summary> internal bool IsSubWorkspaceTreeViewItemExists(string subPath) { if (string.IsNullOrWhiteSpace(subPath)) return false; if (this.HasItems == false) return false; foreach (var item in this.Items) { var wi = item as WorkspaceTreeViewItem; if (wi == null) continue; if (wi.IsDirectoryExists) { var a = wi.FullPath.ToLower(); if (a.EndsWith("\\") == false) a += "\\"; if (subPath.EndsWith("\\") == false) subPath += "\\"; if (subPath == a) return true; } else { var a = wi.FullPath.ToLower(); subPath = subPath.ToLower(); if (a == subPath) return true; } } return false; } } /// <summary> /// 按名称对工作区管理器中的同级条目进行排序。 /// </summary> public class WorkspaceTreeViewItemCompare : IComparer<WorkspaceTreeViewItem> { public int Compare(WorkspaceTreeViewItem x, WorkspaceTreeViewItem y) { return x.ShortName.CompareTo(y.ShortName); } } }