using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Linq; using System.Security.Permissions; using System.Windows.Forms; using BrightIdeasSoftware; using PDFPatcher.Common; using PDFPatcher.Model; namespace PDFPatcher.Functions; [ToolboxItem(false)] public partial class MergerControl : FunctionControl { FileListHelper _listHelper; readonly SourceItem _itemsContainer = new SourceItem.Empty(); readonly string[] _bookmarkStyleButtonNames; public override string FunctionName => "合并文档"; public override Bitmap IconImage => Properties.Resources.Merger; public MergerControl() { InitializeComponent(); _bookmarkStyleButtonNames = ["_BoldStyleButton", "_BookmarkColorButton", "_ItalicStyleButton"]; this.OnFirstLoad(OnLoad); } void OnLoad() { var s = this.GetDpiScale(); _MainToolbar.ScaleElements(s); _FileMenu.ScaleElements(s); _ItemListMenu.ScaleElements(s); _RecentFileMenu.ScaleElements(s); _RecentFolderMenu.ScaleElements(s); _ItemList.ScaleColumnWidths(); _BookmarkColorButton.SelectedColorChanged += (s, e) => RefreshBookmarkColor(); AppContext.MainForm.SetTooltip(_BookmarkControl.FileList, "为目标 PDF 文件添加书签的信息文件(可选)"); AppContext.MainForm.SetTooltip(_ItemList, "在此添加需要合并的 PDF 文件、图片文件或包含上述类型文件的文件夹"); AppContext.MainForm.SetTooltip(_ImportButton, "点击此按钮将列表的文件合并为一个 PDF 文件"); AppContext.MainForm.SetTooltip(_TargetPdfFile.FileList, "生成的目标 PDF 文件路径"); _ItemList.EmptyListMsg = "请使用“添加文件”按钮添加需要合并的文件,或从资源管理器拖放文件到本列表框"; var fi = _FileTypeList.Images; fi.AddRange([ Properties.Resources.EmptyPage, Properties.Resources.OriginalPdfFile, Properties.Resources.Image, Properties.Resources.ImageFolder ]); _BookmarkControl.FileDialog.CheckFileExists = false; _BookmarkControl.BrowseForFile += FileControl_BrowseForFile; _TargetPdfFile.BrowseForFile += FileControl_BrowseForFile; _TargetPdfFile.FileDialog.OverwritePrompt = true; _IndividualMergerModeBox.CheckedChanged += (s, e) => _BookmarkControl.Enabled = !_IndividualMergerModeBox.Checked; _listHelper = new FileListHelper(_ItemList); _ItemList.FixEditControlWidth(); _ItemList.BeforeSorting += (s, e) => e.Canceled = true; _ItemList.CanExpandGetter = (x) => ((SourceItem)x).HasSubItems; _ItemList.ChildrenGetter = (x) => ((SourceItem)x).Items; _ItemList.SelectedIndexChanged += (s, e) => { var i = _ItemList.GetFirstSelectedIndex(); var en = false; if (i != -1) { var selection = _ItemList.SelectedObjects; AppContext.MainForm.StatusText = $"选中 {selection.Count.ToText()} 项,共 {selection.Cast().Sum(s => s.PageCount).ToText()} 页"; var b = (_ItemList.GetModelObject(i) as SourceItem).Bookmark; if (b != null && !String.IsNullOrEmpty(b.Title)) { en = true; _BoldStyleButton.Checked = b.IsBold; _ItalicStyleButton.Checked = b.IsItalic; } } else { UpdateFileStatistics(); } _MainToolbar.ToggleEnabled(en, _bookmarkStyleButtonNames); }; _ItemList.CellEditStarting += (s, e) => { _MainToolbar.Enabled = false; if (e.Column == _BookmarkColumn) { e.Control = new AutoResizingTextBox(e.CellBounds, ((SourceItem)e.RowObject).Bookmark?.Title, (Control)s); } else if (e.Column == _FolderColumn) { if (e.RowObject is SourceItem i && i.FolderName != null) { e.Control = new AutoResizingTextBox(e.CellBounds, i.FolderName, (Control)s) { ReadOnly = true }; } else { e.Cancel = true; } } }; _ItemList.CellEditFinishing += (s, e) => _MainToolbar.Enabled = true; _ItemList.CanDrop += ItemList_CanDropFile; _ItemList.Dropped += ItemList_FileDropped; _ItemList.ModelCanDrop += ItemList_CanDropModel; _ItemList.ModelDropped += ItemList_Dropped; _ItemListMenu.Opening += (s, e) => { foreach (ToolStripItem item in _ItemListMenu.Items) { SetupCommand(item); } }; _ItemList.AsTyped() .ConfigColumn(_NameColumn, c => { c.AspectGetter = o => o.FileName ?? "<空白页>"; c.ImageGetter = o => (int)o.Type; }) .ConfigColumn(_BookmarkColumn, c => { c.AspectGetter = (o) => o.Bookmark?.Title; c.AspectPutter = (o, v) => { var s = v as string; if (String.IsNullOrEmpty(s)) { o.Bookmark = null; } else if (o.Bookmark == null) { o.Bookmark = new BookmarkSettings(s); } else { o.Bookmark.Title = s; } }; }) .ConfigColumn(_PageRangeColumn, c => { c.AspectGetter = (o) => (o as SourceItem.Pdf)?.PageRanges; c.AspectPutter = (o, v) => { if (o is SourceItem.Pdf p) { p.PageRanges = v as string; if (String.IsNullOrEmpty(p.PageRanges)) { p.PageRanges = "1-" + p.PageCount.ToText(); } } UpdateFileStatistics(); }; }) .ConfigColumn(_FolderColumn, c => c.AspectGetter = (o) => o.FolderName); FileListHelper.SetupCommonPdfColumns(_FileTimeColumn); _AddFilesButton.DropDownOpening += FileListHelper.OpenPdfButtonDropDownOpeningHandler; _AddFilesButton.DropDownItemClicked += (s, e) => { _RecentFileMenu.Hide(); FilePath file = e.ClickedItem.ToolTipText; if (file.ExistsFile) { ExecuteCommand(Commands.OpenFile, file); AppContext.RecentItems.AddHistoryItem(AppContext.Recent.SourcePdfFiles, file); } else { FormHelper.ErrorBox("找不到文件:" + file); } }; } void UpdateFileStatistics() { var r = _ItemList.Roots; int c = r is System.Collections.IList l ? l.Count : r.Cast().Count(); AppContext.MainForm.StatusText = c == 0 ? Messages.Welcome : $"共 {c.ToText()} 项,{_ItemList.Objects.Cast().Sum(s => s.RecursivePageCount).ToText()} 页"; } [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)] protected override bool ProcessCmdKey(ref Message msg, Keys keyData) { if (_ItemList.IsCellEditing || !_ItemList.Focused) { return base.ProcessCmdKey(ref msg, keyData); } switch (keyData) { case Keys.Delete: ExecuteCommand(Commands.Delete); return true; case Keys.Control | Keys.B: _BoldStyleButton.PerformClick(); return true; case Keys.Control | Keys.I: _ItalicStyleButton.PerformClick(); return true; default: return base.ProcessCmdKey(ref msg, keyData); } } #region 拖放操作 void ItemList_CanDropFile(object sender, OlvDropEventArgs e) { if (e.DataObject is not DataObject o) { return; } var f = o.GetFileDropList(); var d = e.DropTargetItem; var child = d != null && e.MouseLocation.X > d.Position.X + d.GetBounds(ItemBoundsPortion.ItemOnly).Width / 2; var after = d == null || e.MouseLocation.Y > d.Position.Y + d.Bounds.Height / 2; foreach (var item in f) { if (System.IO.Directory.Exists(item)) { e.Handled = true; e.Effect = DragDropEffects.Copy; e.InfoMessage = $"添加目录{item}到{(child ? "所有子项" : String.Empty)}{(after ? "后面" : "前面")}"; return; } var ext = System.IO.Path.GetExtension(item).ToLowerInvariant(); if (ext == Constants.FileExtensions.Pdf || Constants.FileExtensions.AllSupportedImageExtension.Contains(ext)) { e.Handled = true; e.Effect = DragDropEffects.Copy; e.InfoMessage = $"添加文件{item}到{(child ? "所有子项" : String.Empty)}{(after ? "后面" : "前面")}"; return; } } } void ItemList_FileDropped(object sender, OlvDropEventArgs e) { if (e.DataObject is not DataObject o) { return; } var f = o.GetFileDropList(); var fl = new string[f.Count]; f.CopyTo(fl, 0); SourceItem.SortFileList(fl); var sl = new List(fl.Length); foreach (var item in fl) { var si = SourceItem.Create(item); if (si == null) { continue; } sl.Add(si); } var ti = e.ListView.GetModelObject(e.DropTargetIndex) as SourceItem; var d = e.DropTargetItem; var child = d != null && e.MouseLocation.X > d.Position.X + d.GetBounds(ItemBoundsPortion.ItemOnly).Width / 2; var after = d != null && e.MouseLocation.Y > d.Position.Y + d.Bounds.Height / 2; CopyOrMoveElement(sl, ti, child, after, false); } void ItemList_CanDropModel(object sender, ModelDropEventArgs e) { var si = e.SourceModels; var ti = e.TargetModel as SourceItem; if (si == null || si.Count == 0 || e.TargetModel == null) { e.Effect = DragDropEffects.None; return; } var copy = (ModifierKeys & Keys.Control) != Keys.None; if (!copy) { if (e.DropTargetItem.Selected) { e.Effect = DragDropEffects.None; return; } var al = _ItemList.GetAncestorsOrSelf(ti); foreach (SourceItem item in si) { if (al.IndexOf(item) != -1) { e.Effect = DragDropEffects.None; e.InfoMessage = "目标项不能是源项目的子项。"; return; } } } var d = e.DropTargetItem; var ml = e.MouseLocation; var child = ml.X > d.Position.X + d.GetBounds(ItemBoundsPortion.ItemOnly).Width / 2; var append = ml.Y > d.Position.Y + d.Bounds.Height / 2; if (!child && !copy) { var xi = e.DropTargetIndex + (append ? 1 : -1); if (xi > -1 && xi < e.ListView.GetItemCount() && e.ListView.Items[xi].Selected && GetParentSourceItem(ti) == GetParentSourceItem(_ItemList.GetModelObject(xi) as SourceItem)) { e.Effect = DragDropEffects.None; return; } } e.Effect = copy ? DragDropEffects.Copy : DragDropEffects.Move; e.InfoMessage = $"{(copy ? "复制" : "移动")}到{(child ? "所有子项" : String.Empty)}{(append ? "后面" : "前面")}"; } void ItemList_Dropped(object sender, ModelDropEventArgs e) { var t = e.TargetModel as SourceItem; var si = (e.SourceListView as TreeListView).GetSelectedModels(); if (si == null) { return; } var ti = e.TargetModel as SourceItem; var d = e.DropTargetItem; var child = e.MouseLocation.X > d.Position.X + d.GetBounds(ItemBoundsPortion.ItemOnly).Width / 2; var after = e.MouseLocation.Y > d.Position.Y + d.Bounds.Height / 2; var copy = (ModifierKeys & Keys.Control) != Keys.None; var deepCopy = copy && ((ModifierKeys & Keys.Shift) != Keys.None); var tii = _ItemList.TopItemIndex; CopyOrMoveElement(si, ti, child, after, copy); e.RefreshObjects(); _ItemList.TopItemIndex = tii; } #endregion public override void SetupCommand(ToolStripItem item) { var n = item.Name; if (item.OwnerItem != null && item.OwnerItem.Name == Commands.Selection) { EnableCommand(item, _ItemList.GetItemCount() > 0 && _ItemList.Focused, true); } else if (n.HasPrefix(Commands.Copy) || n.HasPrefix(Commands.Paste) || n == Commands.Delete) { EnableCommand(item, _ItemList.GetItemCount() > 0 && _ItemList.GetFirstSelectedIndex() > -1, true); } else if (n == Commands.Options) { item.Text = "合并文档设置(&S)..."; item.ToolTipText = "设置合并后的 PDF 文档"; EnableCommand(item, true, true); item.Tag = nameof(Function.MergerOptions); } base.SetupCommand(item); } void FileControl_BrowseForFile(object sender, EventArgs e) { _listHelper.PrepareSourceFiles(); } /// /// 复制或移动书签。 /// /// 需要复制或移动的源书签。 /// 目标书签。 /// 是否复制为子节点。 /// 是否复制到后面。 /// 是否复制书签。 internal void CopyOrMoveElement(List source, SourceItem target, bool child, bool after, bool copy) { if (copy) { var clones = new List(source.Count); foreach (SourceItem item in source) { clones.Add(item.Clone()); } source = clones; } else { foreach (var item in source) { GetParentSourceItem(item).Items.Remove(item); } _ItemList.RemoveObjects(source); } if (child && target != null) { if (after) { target.Items.AddRange(source); } else { var a = target.Items.ToArray(); target.Items.Clear(); target.Items.AddRange(source); target.Items.AddRange(a); } if (target == _itemsContainer) { _ItemList.SetObjects(target.Items); } else { _ItemList.Expand(target); } } else { var p = GetParentSourceItem(target); if (after) { p.Items.InsertRange(target != null ? p.Items.IndexOf(target) + 1 : p.Items.Count, source); } else { p.Items.InsertRange(target != null ? p.Items.IndexOf(target) : p.Items.Count, source); } if (p == _itemsContainer) { _ItemList.SetObjects(_itemsContainer.Items); } } _ItemList.SelectedObjects = source; } SourceItem GetParentSourceItem(SourceItem item) { var p = _ItemList.GetParentModel(item); p ??= _itemsContainer; return p; } void AddFiles(string[] files) { if (files == null || files.Length == 0) { return; } if ((ModifierKeys & Keys.Control) != Keys.None) { _ItemList.ClearObjects(); } if (files.Length > 3) { AppContext.MainForm.Enabled = false; } SourceItem.SortFileList(files); _AddDocumentWorker.RunWorkerAsync(files); } void _AddFolder_DropDownOpening(object sender, EventArgs e) { var l = _AddFolderButton.DropDown.Items; l.ClearDropDownItems(); foreach (var item in AppContext.Recent.Folders) { if (FileHelper.IsPathValid(item) && !String.IsNullOrEmpty(System.IO.Path.GetFileName(item))) { l.Add(FileHelper.GetEllipticPath(item, 50)).ToolTipText = item; } } } void _AddFolderButton_DropDownItemClicked(object sender, ToolStripItemClickedEventArgs e) { _RecentFolderMenu.Hide(); var f = e.ClickedItem.ToolTipText; if (!System.IO.Directory.Exists(f)) { FormHelper.ErrorBox("找不到文件夹:" + f); return; } ExecuteCommand(Commands.OpenFile, f); } void _ImportButton_Click(object sender, EventArgs e) { var infoFile = _BookmarkControl.Text.Trim(); var targetPdfFile = _TargetPdfFile.Text.Trim(); if (String.IsNullOrEmpty(targetPdfFile) && String.IsNullOrEmpty(targetPdfFile = _TargetPdfFile.BrowseTargetFile())) { FormHelper.ErrorBox(Messages.TargetFileNotSpecified); return; } if (!FileHelper.IsPathValid(targetPdfFile)) { FormHelper.ErrorBox($"输出文件名无效。{(FileHelper.HasFileNameMacro(targetPdfFile) ? "\n合并 PDF 文件功能不支持替代符。" : String.Empty)}"); return; } var l = _ItemList.GetItemCount(); if (l == 0) { FormHelper.InfoBox("请添加用于生成 PDF 文件的图片或 PDF 源文件。"); return; } _BookmarkControl.FileList.AddHistoryItem(); _TargetPdfFile.FileList.AddHistoryItem(); var fm = _IndividualMergerModeBox.Checked; var fl = fm ? new List(_itemsContainer.Items.Count) : _itemsContainer.Items; if (fm) { foreach (var item in _itemsContainer.Items) { if (item.HasContent) { fl.Add(item); } } if (fl.Count == 0) { FormHelper.ErrorBox("合并文件列表没有包含文件的首层项目。"); return; } } AppContext.MainForm.ResetWorker(); var worker = AppContext.MainForm.GetWorker(); worker.DoWork += (dummy, arg) => { var args = arg.Argument as object[]; var items = args[0] as ICollection; var target = args[1] as string; if (!(bool)args[3]) { // 合并为单一文件 Processor.Worker.MergeDocuments(items, target, args[2] as string); return; } // 顶层项合并为独立文件 Tracker.SetTotalProgressGoal(items.Count); foreach (var item in items) { var tn = FileHelper.CombinePath(System.IO.Path.GetDirectoryName(target), item.FileName + Constants.FileExtensions.Pdf); switch (item.Type) { case SourceItem.ItemType.Empty: Tracker.TraceMessage(Tracker.Category.Error, "首层项目不能为空白页。"); break; case SourceItem.ItemType.Pdf: case SourceItem.ItemType.Image: Processor.Worker.MergeDocuments([item], tn, null); break; case SourceItem.ItemType.Folder: Processor.Worker.MergeDocuments(item.Items, tn, null); break; } Tracker.IncrementTotalProgress(); } }; worker.RunWorkerAsync(new object[] { fl, targetPdfFile, infoFile, fm }); } void _SortMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e) { SourceItem.SortType sortOptions; bool recursive = ModifierKeys != Keys.Control; switch (e.ClickedItem.Name) { case "_SortByAlphaItem": sortOptions = SourceItem.SortType.Literal; break; case "_SortByNaturalNumberItem": sortOptions = SourceItem.SortType.NumericAwareSort; break; case "_SortByCaj": sortOptions = SourceItem.SortType.CajSort; break; case "_SortByFileTime": sortOptions = SourceItem.SortType.FileTime; break; case "_ReverseSort": sortOptions = SourceItem.SortType.Reverse; break; default: return; } _itemsContainer.SortItems(sortOptions, recursive); _ItemList.Objects = _itemsContainer.Items; } void _MainToolbar_ButtonClick(object sender, EventArgs e) { if (sender == _AddFilesButton) { ExecuteCommand(Commands.Open); } else if (sender == _ConfigButton) { AppContext.MainForm.SelectFunctionList(Function.MergerOptions); } else if (sender == _AddFolderButton) { using (var f = new OpenFileDialog() { FileName = "【选择目录】", Filter = _OpenImageBox.Filter, CheckFileExists = false, Title = "选择包含图片或 PDF 的文件夹,点击“打开”按钮" }) { if (f.ShowDialog() == DialogResult.OK) { var p = System.IO.Path.GetDirectoryName(f.FileName); if (String.IsNullOrEmpty(System.IO.Path.GetFileName(p))) { FormHelper.ErrorBox("选择的文件夹无效,不允许选择根目录。"); return; } ExecuteCommand(Commands.OpenFile, p); AppContext.RecentItems.AddHistoryItem(AppContext.Recent.Folders, p); } } } } void _MainToolbar_ItemClicked(object sender, ToolStripItemClickedEventArgs e) { ExecuteCommand(e.ClickedItem); } public override void ExecuteCommand(string commandName, params string[] parameters) { switch (commandName) { case Commands.Open: if (_OpenImageBox.ShowDialog() == DialogResult.OK) { ExecuteCommand(Commands.OpenFile, _OpenImageBox.FileNames); foreach (FilePath item in _OpenImageBox.FileNames) { if (item.HasExtension(Constants.FileExtensions.Pdf)) { AppContext.RecentItems.AddHistoryItem(AppContext.Recent.SourcePdfFiles, item); } } } break; case Commands.OpenFile: AddFiles(parameters); break; case Commands.LoadList: using (var f = new OpenFileDialog() { Title = "请选择需要打开的文件列表", Filter = Constants.FileExtensions.XmlFilter, DefaultExt = Constants.FileExtensions.Xml }) { if (f.ShowDialog() == DialogResult.OK) { _ItemList.DeselectAll(); _ItemList.ClearObjects(); _itemsContainer.Items.Clear(); _itemsContainer.Items.AddRange(Processor.SourceItemSerializer.Deserialize(f.FileName)); _ItemList.Objects = _itemsContainer.Items; UpdateFileStatistics(); } } break; case Commands.SaveList: using (var f = new SaveFileDialog() { Title = "请输入需要保存文件列表的文件名", Filter = Constants.FileExtensions.XmlFilter, DefaultExt = Constants.FileExtensions.Xml }) { if (f.ShowDialog() == DialogResult.OK) { Processor.SourceItemSerializer.Serialize(_itemsContainer.Items, f.FileName); } } break; case Commands.Delete: if (_ItemList.GetItemCount() == 0) { return; } var l = _ItemList.SelectedObjects; if (l.Count == 0) { if (FormHelper.YesNoBox("是否清空文件列表?") == DialogResult.Yes) { _ItemList.ClearObjects(); _itemsContainer.Items.Clear(); UpdateFileStatistics(); } } else { foreach (SourceItem item in _ItemList.SelectedObjects) { GetParentSourceItem(item).Items.Remove(item); _ItemList.RemoveObject(item); } UpdateFileStatistics(); } break; case Commands.Copy: var sb = StringBuilderCache.Acquire(200); var sl = GetSourceItems(true); if (!sl.HasContent()) { sl = GetSourceItems(false); if (!sl.HasContent()) { return; } } foreach (var item in sl) { if (item.Type == SourceItem.ItemType.Empty) { if (!String.IsNullOrEmpty(item.Bookmark?.Title)) { sb.Append('\t').Append(item.Bookmark.Title).Append('\t').Append('-').AppendLine(); } } else if (item.Type == SourceItem.ItemType.Pdf) { var pi = item as SourceItem.Pdf; sb.AppendLine(String.Join("\t", [ pi.FilePath.ToString(), item.Bookmark != null ? item.Bookmark.Title : String.Empty, pi.PageRanges, pi.PageCount.ToText () ])); } else if (item.Type == SourceItem.ItemType.Image) { var im = item as SourceItem.Image; sb.AppendLine(String.Join("\t", [ im.FilePath.ToString(), item.Bookmark != null ? item.Bookmark.Title : String.Empty, "-"/*im.PageRanges*/, im.PageCount.ToText () ])); } else if (item.Type == SourceItem.ItemType.Folder) { sb.AppendLine(item.FilePath.ToString()); } } if (sb.Length > 0) { Clipboard.SetText(sb.ToString()); } StringBuilderCache.Release(sb); break; case "_InsertEmptyPage": AddItem(new SourceItem.Empty()); break; case Commands.SelectAllItems: _ItemList.SelectAll(); break; case Commands.SelectNone: _ItemList.SelectObjects(null); break; case Commands.InvertSelection: _ItemList.InvertSelect(); break; case Commands.SelectAllImages: SelectItemsByType(SourceItem.ItemType.Image); break; case Commands.SelectAllPdf: SelectItemsByType(SourceItem.ItemType.Pdf); break; case Commands.SelectAllFolders: SelectItemsByType(SourceItem.ItemType.Folder); break; case "_EditItemProperty": case "_SetPdfOptions": _ItemList_ItemActivate(null, null); break; case "_SetCroppingOptions": SetImageCropping(); break; case "_RefreshFolder": foreach (SourceItem.Folder item in _ItemList.SelectedObjects) { item?.Reload(); } _ItemList.RefreshObjects(_ItemList.SelectedObjects); break; case "_PdfOptions": AppContext.MainForm.SelectFunctionList(Function.PatcherOptions); break; case "_BoldStyleButton": var cb = !_BoldStyleButton.Checked; foreach (SourceItem item in _ItemList.SelectedObjects) { if (item?.Bookmark != null) { item.Bookmark.IsBold = cb; } } goto case "__Refresh"; case "_ItalicStyleButton": var ci = !_ItalicStyleButton.Checked; foreach (SourceItem item in _ItemList.SelectedObjects) { if (item?.Bookmark != null) { item.Bookmark.IsItalic = ci; } } goto case "__Refresh"; case "_BookmarkColorButton": RefreshBookmarkColor(); break; case "_CopyFileName": CopySelectedItems(item => item.FilePath.FileNameWithoutExtension); break; case "_CopyBookmarkText": CopySelectedItems(item => item != null && item.Bookmark != null ? item.Bookmark.Title : String.Empty); break; case "_PasteBookmarkText": var ct = Clipboard.GetText(TextDataFormat.UnicodeText); if (String.IsNullOrEmpty(ct) || _ItemList.GetItemCount() == 0) { break; } var li = _ItemList.GetLastItemInDisplayOrder().Index; using (var sr = new System.IO.StringReader(ct)) { var i = _ItemList.GetFirstSelectedIndex(); if (i == -1) { i = 0; } while (i <= li && sr.Peek() != -1) { if (_ItemList.GetModelObject(i) is SourceItem b) { if (b.Bookmark == null) { b.Bookmark = new BookmarkSettings(sr.ReadLine()); } else { b.Bookmark.Title = sr.ReadLine(); } } var di = _ItemList.GetDisplayOrderOfItemIndex(i); ++di; var ni = _ItemList.GetNthItemInDisplayOrder(di); if (ni == null) { break; } i = ni.Index; } } break; case "_ClearBookmarkTitle": foreach (SourceItem item in _ItemList.SelectedObjects) { if (item?.Bookmark != null) { item.Bookmark = null; } } goto case "__Refresh"; case "_SetBookmarkTitle": foreach (SourceItem item in _ItemList.SelectedObjects) { if (item != null) { BookmarkSettings b; var t = System.IO.Path.GetFileNameWithoutExtension(item.FileName); if ((b = item.Bookmark) == null) { b = item.Bookmark = new BookmarkSettings(t); } else { item.Bookmark.Title = t; } } } goto case "__Refresh"; case "__Refresh": _ItemList.RefreshObjects(_ItemList.SelectedObjects); break; } } void CopySelectedItems(Converter converter) { var bt = StringBuilderCache.Acquire(200); foreach (SourceItem item in _ItemList.SelectedObjects) { bt.AppendLine(converter(item)); } if (bt.Length > 0) { try { Clipboard.SetDataObject(bt.ToString(), true, 10, 50); } catch (System.Runtime.InteropServices.ExternalException) { // ignore } } StringBuilderCache.Release(bt); } void RefreshBookmarkColor() { var sc = _BookmarkColorButton.Color == Color.White ? Color.Transparent : _BookmarkColorButton.Color; foreach (SourceItem item in _ItemList.SelectedObjects) { if (item?.Bookmark != null) { item.Bookmark.ForeColor = sc; } } _ItemList.RefreshObjects(_ItemList.SelectedObjects); } void SelectItemsByType(SourceItem.ItemType type) { var r = new List(); SelectItemsByType(type, r, _itemsContainer); _ItemList.SelectedObjects = r.Count > 0 ? r : null; } void SelectItemsByType(SourceItem.ItemType type, List result, SourceItem container) { foreach (var item in container.Items) { if (item.Type == type) { _ItemList.Reveal(item, false); result.Add(item); } if (item.HasSubItems) { SelectItemsByType(type, result, item); } } } void _ItemList_ItemActivate(object sender, EventArgs e) { var vi = GetFocusedPdfItem(); if (vi != null) { var pdfItem = _ItemList.GetModelObject(vi.Index) as SourceItem.Pdf; using (SourcePdfOptionForm f = new SourcePdfOptionForm(pdfItem)) { if (f.ShowDialog() == DialogResult.OK) { _ItemList.RefreshObject(pdfItem); } } return; } SetImageCropping(); } void SetImageCropping() { var items = _ItemList.SelectedObjects; if (items.Count == 0) { return; } var s = items[0] as SourceItem.Image; int c = 1; for (int i = 1; i < items.Count; i++) { if (items[i] is not SourceItem.Image image || image.Type == SourceItem.ItemType.Pdf) { continue; } if (s == null) { s = image; continue; } c++; if (!s.Cropping.Equals(image.Cropping)) { if (FormHelper.YesNoBox("选择的图片具有不同的设置,是否重置为统一的值?") == DialogResult.No) { return; } break; } } if (s == null) { return; } var o = new SourceItem.Image(c > 1 ? (FilePath)(c + " 个文件") : s.FilePath) { Cropping = s.Cropping.Clone() }; using (var f = new SourceImageOptionForm(o)) { if (f.ShowDialog() == DialogResult.OK) { foreach (SourceItem.Image image in items) { if (image == null || image.Type == SourceItem.ItemType.Pdf) { continue; } image.Cropping = o.Cropping.Clone(); } } } } ListViewItem GetFocusedPdfItem() { var vi = _ItemList.FocusedItem; if (vi == null || !vi.Text.EndsWith(Constants.FileExtensions.Pdf, StringComparison.OrdinalIgnoreCase)) { return null; } return vi; } void _ItemListMenu_Opening(object sender, CancelEventArgs e) { var vi = _ItemList.FocusedItem; if (vi == null) { _ItemListMenu.ToggleEnabled(false, "_SetPdfOptions", "_RefreshFolder", "_SetCroppingOptions"); return; } var s = _ItemList.GetModelObject(vi.Index) as SourceItem; _ItemListMenu.Items["_SetPdfOptions"].Enabled = s.Type == SourceItem.ItemType.Pdf; _ItemListMenu.Items["_SetCroppingOptions"].Enabled = s.Type == SourceItem.ItemType.Image; _ItemListMenu.Items["_RefreshFolder"].Enabled = s.Type == SourceItem.ItemType.Folder; } List GetSourceItems(bool selectedOnly) where T : SourceItem { if (_ItemList.GetItemCount() == 0) { return null; } var l = selectedOnly ? _ItemList.SelectedObjects : _ItemList.Objects; var items = new List(selectedOnly ? 10 : _ItemList.GetItemCount()); SelectItems(l, items); return items; } static void SelectItems(System.Collections.IEnumerable list, List results) where T : SourceItem { foreach (T item in list) { if (item == null) { continue; } results.Add(item); if (item.HasSubItems) { SelectItems(item.Items, results); } } } #region AddDocumentWorker void _AddDocumentWorker_DoWork(object sender, DoWorkEventArgs e) { var files = e.Argument as string[]; Array.ForEach(files, f => ((BackgroundWorker)sender).ReportProgress(0, f)); } void _AddDocumentWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { AppContext.MainForm.Enabled = true; } void _AddDocumentWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) { AddItem(SourceItem.Create(e.UserState as string)); } void AddItem(SourceItem item) { if (item == null) { return; } _ItemList.Focus(); AddItems([item]); } void AddItems(SourceItem[] items) { int i = _ItemList.GetLastSelectedIndex(); if (i == -1) { i = _ItemList.FocusedItem?.Index ?? -1; } if (i == -1) { _itemsContainer.Items.AddRange(items); _ItemList.Objects = _itemsContainer.Items; goto SELECT; } var m = _ItemList.GetModelObject(i) as SourceItem; var p = _ItemList.GetParentModel(m); if (p == null) { i = _itemsContainer.Items.IndexOf(m); _itemsContainer.Items.InsertRange(++i, items); _ItemList.Objects = _itemsContainer.Items; _ItemList.RebuildAll(true); goto SELECT; } i = p.Items.IndexOf(m); p.Items.InsertRange(++i, items); _ItemList.RefreshObject(p); SELECT: _ItemList.SelectedObjects = items; _ItemList.FocusedObject = items[0]; } #endregion #region IDefaultButtonControl 成员 public override Button DefaultButton => _ImportButton; #endregion void _ItemList_FormatRow(object sender, FormatRowEventArgs e) { var si = e.Model as SourceItem; var bs = si.Bookmark; if (bs == null) { return; } e.Item.UseItemStyleForSubItems = false; e.UseCellFormatEvents = false; var c = e.Item.SubItems[1]; c.ForeColor = bs.ForeColor.IsEmptyOrTransparent() ? Color.Black : bs.ForeColor; if (bs.IsBold || bs.IsItalic) { c.Font = new Font(c.Font, (bs.IsBold ? FontStyle.Bold : FontStyle.Regular) | (bs.IsItalic ? FontStyle.Italic : FontStyle.Regular)); } } }