using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Media; using System.Windows.Media.Imaging; using InABox.Clients; using InABox.Core; using InABox.WPF; using Microsoft.Win32; using Microsoft.Xaml.Behaviors.Core; using Image = System.Windows.Controls.Image; using InABox.Wpf; using System.Threading; namespace InABox.DynamicGrid; public class DocumentConverter : AbstractConverter { public override object Convert(object value) { return value; } } public class TimeStampToBrushConverter : AbstractConverter { public System.Windows.Media.Brush? Empty { get; init; } public System.Windows.Media.Brush? Set { get; init; } public override System.Windows.Media.Brush? Convert(DateTime value) { return value.IsEmpty() ? Empty : Set; } } public class DynamicDocumentGrid : DynamicManyToManyGrid where TEntity : Entity, IPersistent, IRemotable, new() where TDocument : Entity, IEntityDocument, IPersistent, IRemotable, new() // Entity, IPersistent, IRemotable, IManyToMany, new() where TEntityLink : EntityLink, new() { public bool ShowSupercededColumn { get; set; } private bool _simpleTemplate; public bool SimpleTemplate { get => _simpleTemplate; set { _simpleTemplate = value; RowHeight = value ? 150 : 100; } } private DynamicTemplateColumn _template; public DynamicDocumentGrid() { MultiSelect = false; HiddenColumns.Add(x => x.DocumentLink.ID); HiddenColumns.Add(x => x.Superceded); HiddenColumns.Add(x => x.DocumentLink.FileName); HiddenColumns.Add(x => x.Thumbnail); HiddenColumns.Add(x => x.Notes); //ActionColumns.Add(new DynamicImageColumn(DocumentImage, ViewDocument) { Position = DynamicActionColumnPosition.Start }); //ActionColumns.Add(new DynamicImageColumn(DiskImage, SaveDocument) { Position = DynamicActionColumnPosition.Start }); _template = new DynamicTemplateColumn(DocumentTemplate) { Position = DynamicActionColumnPosition.Start, Width = 0, HeaderText = "Attached Documents" }; ActionColumns.Add(_template); //supercedecolumn = new DynamicImageColumn(SupercededImage, SupercedeDocument); //ActionColumns.Add(supercedecolumn); RowHeight = 100; } protected override void DoDoubleClick(object sender, DynamicGridCellClickEventArgs args) { var doc = SelectedRows.FirstOrDefault()?.ToObject(); if (doc != null) { var editor = new DocumentEditor(new IEntityDocument[] { doc }); //editor.PrintAllowed = Security.IsAllowed(); editor.SaveAllowed = false; editor.ShowDialog(); } } private FrameworkElement DocumentTemplate(CoreRow row) { return SimpleTemplate ? CreateSimpleTemplate() : CreateDetailedTemplate(); } private FrameworkElement CreateDetailedTemplate() { Grid grid = new Grid() { Height = 100, ContextMenu = CreateContextMenu(), RowDefinitions = { new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) }, new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }, }, ColumnDefinitions = { new ColumnDefinition() { Width = new GridLength(100) }, new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) }, new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Auto) }, } }; // grid.SetBinding( // Grid.BackgroundProperty, // new Binding("Superceded") // { // Converter = new TimeStampToBrushConverter() // { // Empty = new SolidColorBrush(Colors.LightYellow), // Set = new SolidColorBrush(Colors.Silver) // } // } // ); Image thumbnail = new Image() { Stretch = Stretch.Uniform, Margin = new Thickness(5, 2, 5, 2), }; var ttImage = new Image(); ttImage.SetBinding(Image.SourceProperty, new Binding("Thumbnail") { Converter = new BytesToBitmapImageConverter() }); thumbnail.ToolTip = new ToolTip() { Content = ttImage }; thumbnail.SetBinding(Image.SourceProperty, new Binding("Thumbnail") { Converter = new BytesToBitmapImageConverter() }); thumbnail.SetValue(Grid.RowProperty, 0); thumbnail.SetValue(Grid.RowSpanProperty, 2); thumbnail.SetValue(Grid.ColumnProperty, 0); grid.Children.Add(thumbnail); var dock = new DockPanel(); dock.SetValue(Grid.RowProperty, 0); dock.SetValue(Grid.ColumnProperty, 1); grid.Children.Add(dock); var superceded = new Label() { FontWeight = FontWeights.Bold, Content = "*** SUPERCEDED ***", Margin = new Thickness(0, 0, 5, 0) }; superceded.SetBinding(Label.VisibilityProperty, new Binding("Superceded") { Converter = new DateTimeToVisibilityConverter() }); superceded.SetValue(DockPanel.DockProperty, Dock.Left); dock.Children.Add(superceded); var filename = new Label() { FontWeight = FontWeights.Bold }; filename.SetBinding(Label.ContentProperty, new Binding("DocumentLink_FileName")); filename.SetValue(DockPanel.DockProperty, Dock.Left); dock.Children.Add(filename); var buttons = new StackPanel() { Orientation = Orientation.Horizontal }; buttons.SetValue(Grid.RowProperty, 0); buttons.SetValue(Grid.ColumnProperty, 2); grid.Children.Add(buttons); var view = new Button() { Content = new Image() { Source = Wpf.Resources.multi_image.AsBitmapImage() }, BorderBrush = new SolidColorBrush(Colors.Transparent), Background = new SolidColorBrush(Colors.Transparent), Height = 32, Width = 32, ToolTip = "View Documents", Command = new ActionCommand(ViewDocuments) }; buttons.Children.Add(view); var copy = new Button() { Content = new Image() { Source = Wpf.Resources.copy.AsBitmapImage() }, BorderBrush = new SolidColorBrush(Colors.Transparent), Background = new SolidColorBrush(Colors.Transparent), Height = 32, Width = 32, ToolTip = "Copy to Clipboard", Command = new ActionCommand(CopyDocuments) }; buttons.Children.Add(copy); var save = new Button() { Content = new Image() { Source = Wpf.Resources.download.AsBitmapImage() }, BorderBrush = new SolidColorBrush(Colors.Transparent), Background = new SolidColorBrush(Colors.Transparent), Height = 32, Width = 32, ToolTip = "Save Documents", Command = new ActionCommand(SaveDocuments) }; buttons.Children.Add(save); var print = new Button() { Content = new Image() { Source = Wpf.Resources.print.AsBitmapImage(), Margin = new Thickness(2) }, BorderBrush = new SolidColorBrush(Colors.Transparent), Background = new SolidColorBrush(Colors.Transparent), Height = 32, Width = 32, ToolTip = "Print Documents", Command = new ActionCommand(PrintDocuments) }; buttons.Children.Add(print); var notes = new Label() { }; notes.SetBinding(Label.ContentProperty, new Binding("Notes")); notes.SetValue(Grid.RowProperty, 1); notes.SetValue(Grid.ColumnProperty, 1); notes.SetValue(Grid.ColumnSpanProperty, 2); grid.Children.Add(notes); return grid; } private ContextMenu CreateContextMenu() { var menu = new ContextMenu(); menu.Items.Add(new MenuItem() { Header = "View Documents", Command = new ActionCommand(ViewDocuments) }); menu.Items.Add(new MenuItem() { Header = "Copy To Clipboard", Command = new ActionCommand(CopyDocuments) }); menu.Items.Add(new MenuItem() { Header = "Save Documents", Command = new ActionCommand(SaveDocuments) }); return menu; } private FrameworkElement CreateSimpleTemplate() { Grid grid = new Grid() { //Height = 150, ContextMenu = CreateContextMenu(), RowDefinitions = { new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }, new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) }, }, ColumnDefinitions = { new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) }, } }; Image thumbnail = new Image() { Stretch = Stretch.Uniform, Margin = new Thickness(5), //HorizontalAlignment = HorizontalAlignment.Stretch, //VerticalAlignment = VerticalAlignment.Stretch }; thumbnail.SetBinding(Image.SourceProperty, new Binding("Thumbnail") { Converter = new BytesToBitmapImageConverter() }); thumbnail.SetValue(Grid.RowProperty,0); grid.Children.Add(thumbnail); var filename = new Label() { HorizontalContentAlignment = HorizontalAlignment.Center, FontSize = 10 }; filename.SetBinding(Label.ContentProperty, new Binding("DocumentLink_FileName")); filename.SetValue(Grid.RowProperty,1); grid.Children.Add(filename); return grid; } private void GetDocuments(Action> action) { var ids = SelectedRows.Select(r => r.Get(c => c.DocumentLink.ID)).ToArray(); var files = Client.Query( new Filter(x => x.ID).InList(ids), Columns.None().Add(x => x.FileName).Add(x => x.Data) ).ToDictionary(x => x.FileName, x => x.Data); action?.Invoke(files); } private static string SanitiseFileName(string filename) { var basefilename = Path.GetFileNameWithoutExtension(filename); var extension = Path.GetExtension(filename); return Path.ChangeExtension(string.Join("_", basefilename.Split(Path.GetInvalidFileNameChars())), extension); } private void ViewDocuments() { GetDocuments((files) => { foreach (var file in files) { Task.Run(() => { var tempfile = Path.Combine(System.IO.Path.GetTempPath(), SanitiseFileName(file.Key)); try { File.WriteAllBytes(tempfile, file.Value); } catch { // Outlook likes to keep files open apparently, which breaks this code. } var info = new System.Diagnostics.ProcessStartInfo(tempfile); info.UseShellExecute = true; info.Verb = "Open"; Process.Start(info); }); } }); } private void CopyDocuments() { if (SelectedRows?.Any() != true) return; GetDocuments((files) => { System.Collections.Specialized.StringCollection FileCollection = new System.Collections.Specialized.StringCollection(); foreach(var file in files) { var tempfile = Path.Combine(System.IO.Path.GetTempPath(), SanitiseFileName(file.Key)); File.WriteAllBytes(tempfile, file.Value); FileCollection.Add(tempfile); } Clipboard.SetFileDropList(FileCollection); }); } private void SaveDocuments() { if (SelectedRows?.Any() != true) return; using(var fbd = new System.Windows.Forms.FolderBrowserDialog()) { var result = fbd.ShowDialog(); if (result == System.Windows.Forms.DialogResult.OK && !string.IsNullOrWhiteSpace(fbd.SelectedPath)) { var path = fbd.SelectedPath; GetDocuments(files => { foreach (var file in files) File.WriteAllBytes(Path.Combine(path, SanitiseFileName(file.Key)), file.Value); }); } } } private void PrintDocuments() { if (SelectedRows?.Any() != true) return; GetDocuments(files => { Task.Run(() => { foreach (var file in files) { var tempfile = Path.Combine(System.IO.Path.GetTempPath(), SanitiseFileName(file.Key)); File.WriteAllBytes(tempfile, file.Value); var info = new System.Diagnostics.ProcessStartInfo(tempfile); info.CreateNoWindow = true; info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden; info.UseShellExecute = true; info.Verb = "print"; Process.Start(info); } }); }); } protected override DynamicGridColumns LoadColumns() { return new DynamicGridColumns(); } protected override void DoReconfigure(DynamicGridOptions options) { base.DoReconfigure(options); options.SelectColumns = false; options.DragTarget = true; } public override int Order() { return int.MaxValue; } protected override void HandleDragOver(object sender, DragEventArgs e) { if (e.Data.GetDataPresent(DataFormats.FileDrop) || e.Data.GetDataPresent("FileGroupDescriptor")) { e.Effects = DragDropEffects.Copy; } else { e.Effects = DragDropEffects.None; } e.Handled = true; } protected override void HandleDragDrop(object sender, DragEventArgs e) { var result = DocumentUtils.HandleFileDrop(e); if(result is not null) { var docs = new List(); foreach (var (filename, stream) in result) { var doc = new Document(); doc.FileName = Path.GetFileName(filename).ToLower(); if (stream is null) { doc.Data = File.ReadAllBytes(filename); doc.TimeStamp = new FileInfo(filename).LastWriteTime; } else { using var memoryStream = new MemoryStream(); stream.CopyTo(memoryStream); doc.Data = memoryStream.ToArray(); doc.TimeStamp = DateTime.Now; } doc.CRC = CoreUtils.CalculateCRC(doc.Data); docs.Add(doc); } AddDocuments(docs); } } protected override void OnDragEnd(Type entity, CoreTable table, DragEventArgs e) { if (entity == typeof(Document)) { var refresh = false; var docIDS = table.Rows.Select(x => x.Get(x => x.ID)).ToArray(); var columns = Columns.None().Add(x => x.ID); foreach (var column in VisibleColumns) { if (column.ColumnName.StartsWith("DocumentLink.")) { columns.Add(string.Join('.', column.ColumnName.Split('.').Skip(1))); } } var docs = new Client() .Query( new Filter(x => x.ID).InList(docIDS), columns); foreach (var doc in docs.ToObjects()) { var entityDocument = new TDocument(); entityDocument.EntityLink.ID = Item.ID; entityDocument.DocumentLink.ID = doc.ID; entityDocument.DocumentLink.Synchronise(doc); SaveItem(entityDocument); refresh = true; } if (refresh) { DoChanged(); Refresh(false, true); } } else { base.OnDragEnd(entity, table, e); } } private void AddDocuments(IList documents) { if (documents.Any()) { new Client().Save(documents, "Initial Upload"); foreach (var doc in documents) { var newitem = CreateItem(); var prop = GetOtherLink(newitem); prop.ID = doc.ID; prop.Synchronise(doc); SaveItem(newitem); } DoChanged(); Refresh(false, true); } } protected override void DoAdd(bool OpenEditorOnDirectEdit = false) { var dlg = new OpenFileDialog(); dlg.Multiselect = true; if (dlg.ShowDialog() == true) { using (new WaitCursor()) { var docs = new List(); foreach (var filename in dlg.FileNames) { // Create a Document var doc = new Document(); doc.FileName = Path.GetFileName(filename).ToLower(); doc.TimeStamp = new FileInfo(dlg.FileName).LastWriteTime; doc.Data = File.ReadAllBytes(filename); doc.CRC = CoreUtils.CalculateCRC(doc.Data); docs.Add(doc); } AddDocuments(docs); } } } protected override void Reload( Filters criteria, Columns columns, ref SortOrder? sort, CancellationToken token, Action action) { base.Reload(criteria, columns, ref sort, token, (t,e) => { if (token.IsCancellationRequested) return; action(t,e); // Download Hi Res images in the background and replace them when available if (t != null && SimpleTemplate) { var ids = t.ExtractValues(x => x.DocumentLink.ID).Distinct().ToArray(); Client.Query( new Filter(x => x.ID).InList(ids), Columns.None().Add(x => x.ID).Add(x => x.Data), null, null, (d, _) => { if (token.IsCancellationRequested) return; if (d == null) return; var docs = d.ToDictionary(x => x.ID, x => x.Data); foreach (var row in t.Rows) { if (docs.TryGetValue(row.Get(x => x.DocumentLink.ID), out byte[]? data) && (data?.Any() == true)) { if (ImageUtils.IsPdf(data)) data = ImageUtils.PDFToBitmap(data, 0); row.Set(x => x.Thumbnail!, data); } } Dispatcher.BeginInvoke(() => Refresh(false,false)); } ); } }); } }