using Comal.Classes;
using InABox.Clients;
using InABox.Core;
using InABox.Wpf;
using InABox.WPF;
using PRSDesktop.Panels.DataEntry;
using Syncfusion.Pdf;
using Syncfusion.Pdf.Parsing;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using Image = System.Windows.Controls.Image;
namespace PRSDesktop;
///
/// Control that allows to view a list of documents, within a zoom control, and providing methods to rotate/explode data.
///
///
/// This is originally from the Data entry panel, and this implementation is a little bit scuffed. Basically, because the
/// is not an , there is no good shared interface, so I made this abstract, with a type argument. Then to get the "EntityDocument"
/// ID or the Document ID, there are abstract functions.
///
/// Note one needs also to provide . This is a function used by the "Rotate Image" button, and its implementation needs
/// to update the THumbnail of the Entity Document, and save it, along with refreshing the view list.
///
///
public abstract class DocumentViewList : UserControl, INotifyPropertyChanged
{
public static readonly DependencyProperty CanRotateImageProperty = DependencyProperty.Register(nameof(CanRotateImage), typeof(bool), typeof(DocumentViewList), new PropertyMetadata(true, CanRotateImage_Changed));
private static void CanRotateImage_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is not DocumentViewList list) return;
list.DoPropertyChanged(e.Property.Name);
}
private IList _documents = [];
public IList Documents
{
get => _documents;
set
{
UpdateViewList(value);
}
}
private readonly object _viewListLock = new object();
private class ViewDocument
{
public ImageSource Image { get; set; }
public TDocument Document { get; set; }
public int PageNumber { get; set; }
public ViewDocument(ImageSource image, TDocument document, int page)
{
Image = image;
Document = document;
PageNumber = page;
}
}
private List ViewDocuments { get; } = new();
public ObservableCollection ViewList { get; init; } = new();
private ZoomPanel ZoomPanel;
private bool _canExplode;
public bool CanExplode
{
get => _canExplode;
set
{
_canExplode = value;
DoPropertyChanged();
}
}
public event Action? Explode;
public event Action? ExplodeAll;
public event Action? UpdateDocument;
public bool CanRotateImage
{
get => (bool)GetValue(CanRotateImageProperty);
set => SetValue(CanRotateImageProperty, value);
}
public DocumentViewList()
{
var border = new Border();
border.BorderBrush = Colors.Gray.ToBrush();
border.Background = Colors.DimGray.ToBrush();
ZoomPanel = new ZoomPanel();
var itemsControl = new ItemsControl();
itemsControl.Margin = new Thickness(10);
itemsControl.ItemsSource = ViewList;
var factory = new FrameworkElementFactory(typeof(StackPanel));
factory.SetValue(StackPanel.OrientationProperty, Orientation.Vertical);
itemsControl.ItemsPanel = new ItemsPanelTemplate(factory);
itemsControl.ContextMenu = new ContextMenu();
var explode = itemsControl.ContextMenu.AddItem("Regroup Pages", null, Explode_Click);
explode.Bind(VisibilityProperty, this, x => x.CanExplode, new InABox.WPF.BooleanToVisibilityConverter(Visibility.Visible, Visibility.Collapsed));
var explodeAll = itemsControl.ContextMenu.AddItem("Explode All Pages", null, ExplodeAll_Click);
explodeAll.Bind(VisibilityProperty, this, x => x.CanExplode, new InABox.WPF.BooleanToVisibilityConverter(Visibility.Visible, Visibility.Collapsed));
var viewImage = new MenuItem()
{
Header = "View Image"
};
viewImage.ToolTip = "Show this image in a separate window.";
viewImage.Bind(MenuItem.TagProperty, x => x);
viewImage.Click += ViewImage_Click;
itemsControl.ContextMenu.Items.Add(viewImage);
var rotateImage = new MenuItem()
{
Header = "Rotate Document"
};
rotateImage.ToolTip = "Rotate this document 90° clockwise";
rotateImage.Bind(MenuItem.TagProperty, x => x);
rotateImage.SetBinding(MenuItem.IsEnabledProperty, new Binding("CanRotateImage") { Source = this });
rotateImage.Click += RotateImage_Click;
itemsControl.ContextMenu.Items.Add(rotateImage);
itemsControl.ItemTemplate = TemplateGenerator.CreateDataTemplate(() =>
{
var img = new Image();
img.Bind(Image.SourceProperty, x => x);
img.Margin = new(0, 0, 0, 5);
img.ContextMenu = itemsControl.ContextMenu;
img.MouseLeftButtonDown += Img_MouseLeftButtonDown;
return img;
});
ZoomPanel.Content = itemsControl;
border.Child = ZoomPanel;
Content = border;
BindingOperations.EnableCollectionSynchronization(ViewList, _viewListLock);
}
protected abstract Guid GetID(TDocument document);
protected abstract Guid GetDocumentID(TDocument document);
protected abstract IEnumerable LoadDocuments(IEnumerable ids);
public void UpdateViewList(IList documents, bool force = false)
{
if (!force && documents.Count == _documents.Count && !documents.Any(x => _documents.All(y => GetID(x) != GetID(y))))
return;
_documents = documents;
ViewList.Clear();
ViewDocuments.Clear();
if(_documents.Count == 0)
{
return;
}
Task.Run(() =>
{
var docs = LoadDocuments(Documents.Select(GetDocumentID).Distinct());
LoadDocuments(docs);
}).ContinueWith((task) =>
{
if(task.Exception is not null)
{
MessageWindow.ShowError("An error occurred while loading the documents", task.Exception);
}
}, TaskScheduler.FromCurrentSynchronizationContext());
}
private void LoadDocuments(IEnumerable documents)
{
var bitmaps = new Dictionary>();
foreach (var document in documents.Where(x=>x.Data?.Any() == true))
{
List images;
var bitmapImages = new List();
var extension = Path.GetExtension(document.FileName).ToLower();
if (extension == ".pdf")
{
images = new List();
try
{
bitmapImages = ImageUtils.RenderPDFToImageSources(document.Data);
}
catch (Exception e)
{
MessageBox.Show($"Cannot load document '{document.FileName}': {e.Message}");
}
}
else if (extension == ".jpg" || extension == ".jpeg" || extension == ".png" || extension == ".bmp")
{
images = new List { document.Data };
}
else
{
images = ImageUtils.RenderTextFileToImages(Encoding.UTF8.GetString(document.Data));
}
bitmapImages.AddRange(images.Select(x =>
{
try
{
return ImageUtils.LoadImage(x);
}
catch (Exception e)
{
Dispatcher.BeginInvoke(() =>
{
MessageWindow.ShowError($"Cannot load document '{document.FileName}", e);
});
}
return null;
}).Where(x => x != null).Cast());
foreach (var image in bitmapImages)
{
if (!bitmaps.TryGetValue(document.ID, out var list))
{
list = new List();
bitmaps[document.ID] = list;
}
list.Add(image);
}
}
ViewDocuments.Clear();
var maxWidth = 0.0;
foreach (var scan in Documents)
{
if (bitmaps.TryGetValue(GetDocumentID(scan), out var list))
{
int page = 1;
foreach (var bitmap in list)
{
maxWidth = Math.Max(maxWidth, bitmap.Width);
ViewDocuments.Add(new(bitmap, scan, page));
page++;
}
}
}
lock (_viewListLock)
{
ViewList.Clear();
foreach(var doc in ViewDocuments)
{
ViewList.Add(doc.Image);
}
if(maxWidth != 0.0)
{
ZoomPanel.Scale = ZoomPanel.ActualWidth / (maxWidth * 1.1);
ZoomPanel.MinScale = ZoomPanel.Scale / 2;
}
}
}
private void RotateDocument(Document doc, int pageNumber)
{
var extension = Path.GetExtension(doc.FileName).ToLower();
if (extension == ".pdf")
{
var loadeddoc = new PdfLoadedDocument(doc.Data);
bool allPages = loadeddoc.PageCount() > 1;
if (allPages)
{
allPages = MessageWindow.New()
.Message("Do you want to rotate all pages in this PDF?")
.Title("Rotate all?")
.AddYesButton("All pages")
.AddNoButton("Just this page")
.Display().Result == MessageWindowResult.Yes;
}
if(allPages)
{
foreach (var page in loadeddoc.GetPages())
{
var rotation = (int)page.Rotation;
rotation = (rotation + 1) % 4;
page.Rotation = (PdfPageRotateAngle)rotation;
}
}
else if(pageNumber <= loadeddoc.PageCount())
{
var page = loadeddoc.GetPage(pageNumber - 1);
var rotation = (int)page.Rotation;
rotation = (rotation + 1) % 4;
page.Rotation = (PdfPageRotateAngle)rotation;
}
doc.Data = loadeddoc.SaveToBytes();
}
else if (extension == ".jpg" || extension == ".jpeg" || extension == ".png" || extension == ".bmp")
{
using var stream = new MemoryStream(doc.Data);
var bitmap = Bitmap.FromStream(stream);
bitmap.RotateFlip(RotateFlipType.Rotate90FlipNone);
using var outStream = new MemoryStream();
bitmap.Save(outStream, extension switch
{
".jpg" or ".jpeg" => ImageFormat.Jpeg,
".png" => ImageFormat.Png,
_ => ImageFormat.Bmp
});
doc.Data = outStream.ToArray();
}
else
{
using var stream = new MemoryStream(doc.Data);
var loadeddoc = DataEntryReGroupWindow.RenderToPDF(doc.FileName, stream);
foreach (var page in loadeddoc.GetPages())
{
var rotation = (int)page.Rotation;
rotation = (rotation + 1) % 4;
page.Rotation = (PdfPageRotateAngle)rotation;
}
doc.Data = loadeddoc.SaveToBytes();
}
}
private void RotateImage_Click(object sender, RoutedEventArgs e)
{
if (sender is not MenuItem item || item.Tag is not ImageSource image) return;
var document = ViewDocuments.FirstOrDefault(x => x.Image == image);
if (document is null)
{
MessageWindow.ShowError("An error occurred", "Document does not exist in ViewDocuments list");
return;
}
var doc = LoadDocuments(CoreUtils.One(GetDocumentID(document.Document))).First();
try
{
RotateDocument(doc, document.PageNumber);
}
catch(Exception err)
{
MessageWindow.ShowError("Something went wrong while trying to rotate this document.", err);
return;
}
Client.Save(doc, "Rotated by user.");
UpdateDocument?.Invoke(document.Document, doc);
}
private void ViewImage_Click(object sender, RoutedEventArgs e)
{
if (sender is not MenuItem item || item.Tag is not ImageSource image) return;
OpenImageWindow(image);
}
private void ExplodeAll_Click()
{
ExplodeAll?.Invoke();
}
private void Explode_Click()
{
Explode?.Invoke();
}
#region Image Window
private List OpenWindows = new();
public void CloseImageWindows()
{
while (OpenWindows.Count > 0)
{
var win = OpenWindows.Last();
OpenWindows.RemoveAt(OpenWindows.Count - 1);
win.Close();
}
}
private void OpenImageWindow(ImageSource image)
{
var window = OpenWindows.FirstOrDefault(x => x.Images.Contains(image));
if (window is not null)
{
window.Activate();
}
else
{
var docID = GetDocumentID(ViewDocuments.First(x => x.Image == image).Document);
var docs = ViewDocuments.Where(x => GetDocumentID(x.Document) == docID);
window = new DataEntryDocumentWindow();
window.Topmost = true;
foreach(var doc in docs)
{
window.Images.Add(doc.Image);
}
OpenWindows.Add(window);
window.Closed += OpenWindow_Closed;
window.Show();
}
}
private void OpenWindow_Closed(object? sender, EventArgs e)
{
if (sender is not DataEntryDocumentWindow window) return;
OpenWindows.Remove(window);
}
private void Img_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (sender is not Image image) return;
if(e.ClickCount >= 2)
{
OpenImageWindow(image.Source);
e.Handled = true;
}
}
#endregion
public event PropertyChangedEventHandler? PropertyChanged;
protected void DoPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class DataEntryViewList : DocumentViewList
{
protected override IEnumerable LoadDocuments(IEnumerable ids)
{
return DataEntryCache.Cache.LoadDocuments(ids, checkTimestamp: true);
}
protected override Guid GetID(DataEntryDocument document)
{
return document.ID;
}
protected override Guid GetDocumentID(DataEntryDocument document)
{
return document.Document.ID;
}
}