diff --git a/Source/WPF/MyMoney/Dialogs/AttachmentDialog.xaml b/Source/WPF/MyMoney/Dialogs/AttachmentDialog.xaml index 2309b69e..8da7d049 100644 --- a/Source/WPF/MyMoney/Dialogs/AttachmentDialog.xaml +++ b/Source/WPF/MyMoney/Dialogs/AttachmentDialog.xaml @@ -9,6 +9,7 @@ ui:ThemeManager.IsThemeAware="True" Title="Attachments" Height="425" Width="800" xmlns:help="clr-namespace:Walkabout.Help" + AllowDrop="True" help:HelpService.HelpKeyword="Basics/Attachments/"> @@ -17,7 +18,22 @@ - + + + + + + + + + + + + + + + + diff --git a/Source/WPF/MyMoney/Dialogs/AttachmentDialog.xaml.cs b/Source/WPF/MyMoney/Dialogs/AttachmentDialog.xaml.cs index 80991aae..9b0fc453 100644 --- a/Source/WPF/MyMoney/Dialogs/AttachmentDialog.xaml.cs +++ b/Source/WPF/MyMoney/Dialogs/AttachmentDialog.xaml.cs @@ -1,4 +1,6 @@ -using System; +using ModernWpf.Controls; +using System; +using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using System.Text; @@ -6,8 +8,10 @@ using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; +using System.Windows.Interop; using System.Windows.Media; using System.Windows.Media.Imaging; +using System.Windows.Navigation; using Walkabout.Attachments; using Walkabout.Configuration; using Walkabout.Controls; @@ -55,6 +59,59 @@ public AttachmentDialog() this.resizerBrush = (Brush)this.Resources["ResizerThumbBrush"]; } + protected override void OnDragEnter(DragEventArgs e) + { + if (this.transaction == null) + { + return; + } + if (e.Data.GetDataPresent(DataFormats.FileDrop)) + { + e.Effects = DragDropEffects.Copy; + e.Handled = true; + } + } + + protected override void OnDrop(DragEventArgs e) + { + if (this.transaction == null) + { + return; + } + if (e.Data.GetDataPresent(DataFormats.FileDrop)) + { + var files = (string[])e.Data.GetData(DataFormats.FileDrop); + foreach (var file in files) + { + var extension = Path.GetExtension(file); + StringBuilder sb = new StringBuilder(); + try + { + string attachmentFullPath = this.Manager.GetUniqueFileName(transaction, extension); + File.Copy(files[0], attachmentFullPath, true); + transaction.HasAttachment = true; + } + catch (Exception ex) + { + sb.AppendLine(ex.Message); + } + if (sb.Length > 0) + { + MessageBoxEx.Show(sb.ToString(), "Add Attachments Failed", MessageBoxButton.OK, MessageBoxImage.Error); + } + } + this.LoadAttachments(); + } + } + + private void OnDoubleClickItem(AttachmentDialogItem item) + { + if (!string.IsNullOrEmpty(item.FileName)) + { + InternetExplorer.OpenUrl(0, item.FileName); + } + } + protected override void OnPreviewKeyDown(KeyEventArgs e) { if (e.Key == Key.Escape) @@ -69,13 +126,17 @@ private void CanvasGrid_MouseDown(object sender, MouseButtonEventArgs e) Point pos = e.GetPosition(this.CanvasGrid); HitTestResult result = VisualTreeHelper.HitTest(this.CanvasGrid, pos); - DependencyObject hit = result.VisualHit; + DependencyObject hit = result.VisualHit; if (hit != null) { AttachmentDialogItem item = WpfHelper.FindAncestor(hit); if (item != null) { this.SelectItem(item); + if (e.ClickCount > 1) + { + this.OnDoubleClickItem(item); + } return; } if (hit == this.resizer) @@ -140,6 +201,10 @@ private void LoadAttachments() // load an xamlpackage document. this.AddItem(new AttachmentDialogDocumentItem(filePath)); } + else + { + this.AddItem(new AttachmentDialogFileItem(filePath)); + } } catch { @@ -369,6 +434,7 @@ private void MoveResizer(AttachmentDialogItem item, Rect resizerBounds) { this.resizer.LimitBounds = this.GetScaledBounds(item.ResizeLimit); this.resizer.Bounds = this.GetScaledBounds(resizerBounds); + this.resizer.IsEnabled = item.LiveResizable; this.resizer.InvalidateArrange(); } } @@ -1078,6 +1144,92 @@ internal void RotateImage(double degrees) } + internal class AttachmentDialogFileItem : AttachmentDialogItem + { + private ImageSource imageSource; + private Image image; + + public AttachmentDialogFileItem(string fileName) + { + this.image = new Image(); + this.FileName = fileName; + image.Source = FileIcons.Extract(fileName); ; + this.AddVisualChild(this.image); + this.Loaded += this.OnItemLoaded; + } + + private void OnItemLoaded(object sender, RoutedEventArgs e) + { + if (image.Source == null) + { + var drawing = (DrawingImage)this.FindResource("DefaultFileImage"); + image.Source = drawing; + } + if (image.Source != null) + { + image.Width = this.image.Source.Width; + image.Height = this.image.Source.Height; + } + } + + public override FrameworkElement Content => this.image; + + public override string FileExtension => System.IO.Path.GetExtension(this.FileName); + + public override bool LiveResizable => false; + + public override Rect ResizeLimit + { + get { return new Rect(0, 0, this.image.Source.Width, this.image.Source.Height); } + } + + public override FrameworkElement CloneContent() + { + return new Image() { Source = imageSource }; + } + + public override void Copy() + { + } + + public override void Resize(Rect newBounds) + { + } + + public override void Save(string filePath) + { + } + + protected override Visual GetVisualChild(int index) + { + if (index == 0) + { + return this.image; + } + return null; + } + + protected override int VisualChildrenCount + { + get + { + return 1; + } + } + + protected override Size MeasureOverride(Size availableSize) + { + this.image.Measure(availableSize); + return new Size(this.image.Source.Width, this.image.Source.Height); + } + + protected override Size ArrangeOverride(Size finalSize) + { + this.image.Arrange(new Rect(0, 0, finalSize.Width, finalSize.Height)); + return finalSize; + } + } + /// /// This class implements the abstract AttachmentDialogItem by /// wrapping a RichTextBox as the content diff --git a/Source/WPF/MyMoney/Setup/changes.xml b/Source/WPF/MyMoney/Setup/changes.xml index b7ad1d1e..b01bcc33 100644 --- a/Source/WPF/MyMoney/Setup/changes.xml +++ b/Source/WPF/MyMoney/Setup/changes.xml @@ -2,6 +2,7 @@ - Fix issue #112: add hyperlinks on future bills report. + - Fix issue #115: Attach pdf files to register entries. - Fix issue #119: Add "Accept All" Transaction view context menu. diff --git a/Source/WPF/MyMoney/Utilities/FileIcons.cs b/Source/WPF/MyMoney/Utilities/FileIcons.cs new file mode 100644 index 00000000..c4280502 --- /dev/null +++ b/Source/WPF/MyMoney/Utilities/FileIcons.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Forms; +using System.Windows.Interop; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using Walkabout.Utilities; + +namespace Walkabout.Utilities +{ + internal class FileIcons + { + private const uint SHGFI_ICON = 0x100; + private const uint SHGFI_LARGEICON = 0x0; + private const uint SHGFI_SMALLICON = 0x1; + private const int FILE_ATTRIBUTE_NORMAL = 0x80; + private const uint SHGFI_USEFILEATTRIBUTES = 0x000000010; + [StructLayout(LayoutKind.Sequential)] + private struct SHFILEINFO + { + public IntPtr hIcon; + public IntPtr iIcon; + public uint dwAttributes; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] + public string szDisplayName; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)] + public string szTypeName; + }; + + [DllImport("shell32.dll")] + private static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags); + + [DllImport("User32.dll")] + public static extern int DestroyIcon(IntPtr hIcon); + + + public static ImageSource Extract(string fileName) + { + var shinfo = new SHFILEINFO(); + + SHGetFileInfo(fileName, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), SHGFI_ICON | SHGFI_LARGEICON); + //The icon is returned in the hIcon member of the shinfo struct + var imageSource = Imaging.CreateBitmapSourceFromHIcon( + shinfo.hIcon, + Int32Rect.Empty, + BitmapSizeOptions.FromEmptyOptions()); + + DestroyIcon(shinfo.hIcon); + return imageSource; + } + } + +} diff --git a/Source/WPF/MyMoney/Utilities/NativeMethods.cs b/Source/WPF/MyMoney/Utilities/NativeMethods.cs index b4fa7ee4..95be1b15 100644 --- a/Source/WPF/MyMoney/Utilities/NativeMethods.cs +++ b/Source/WPF/MyMoney/Utilities/NativeMethods.cs @@ -50,6 +50,16 @@ public static class NativeMethods public const int QS_ALLINPUT = QS_INPUT | QS_POSTMESSAGE | QS_TIMER | QS_PAINT | QS_HOTKEY; + + [StructLayout(LayoutKind.Sequential)] + public struct RECT + { + public int Left; + public int Top; + public int Right; + public int Bottom; + } + [DllImport("user32.dll", EntryPoint = "SetParent", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)] diff --git a/Source/WPF/MyMoney/Views/TransactionsView.xaml.cs b/Source/WPF/MyMoney/Views/TransactionsView.xaml.cs index 00309999..690b54b3 100644 --- a/Source/WPF/MyMoney/Views/TransactionsView.xaml.cs +++ b/Source/WPF/MyMoney/Views/TransactionsView.xaml.cs @@ -1058,7 +1058,7 @@ private void OnDataGridRowDragDrop(object sender, DragEventArgs e) { var transaction = row.Item as Transaction; AttachmentManager mgr = this.ServiceProvider.GetService(typeof(AttachmentManager)) as AttachmentManager; - if (transaction != null && transaction.HasAttachment == false && mgr != null) + if (transaction != null && mgr != null) { StringBuilder sb = new StringBuilder(); var files = (string[])e.Data.GetData(DataFormats.FileDrop); @@ -1243,31 +1243,14 @@ private void OnDataGridRowDragEnter(object sender, DragEventArgs e) else if (e.Data.GetDataPresent(DataFormats.FileDrop)) { var transaction = row.Item as Transaction; - if (transaction != null && transaction.HasAttachment == false) + if (transaction != null) { - var files = (string[])e.Data.GetData(DataFormats.FileDrop); - - if (files.Length == 1 && this.IsValidAttachmentExtension(files[0])) - { - this.SetDragDropStyles(row, DropType.File); - e.Effects = DragDropEffects.Copy; - } + e.Effects = DragDropEffects.Copy; } } } } - private bool IsValidAttachmentExtension(string filePath) - { - var extension = Path.GetExtension(filePath).ToLower(); - if (extension == ".jpg" || extension == ".png" || extension == ".gif" || extension == ".bmp") - { - return true; - } - - return false; - } - private void OnDataGridRowDragLeave(object sender, DragEventArgs e) { DataGridRow row = (DataGridRow)sender; diff --git a/Source/WPF/Version/VersionMaster.txt b/Source/WPF/Version/VersionMaster.txt index 80cbfbc9..cece9bf1 100644 --- a/Source/WPF/Version/VersionMaster.txt +++ b/Source/WPF/Version/VersionMaster.txt @@ -1 +1 @@ -2.1.0.21 \ No newline at end of file +2.1.0.22 \ No newline at end of file diff --git a/docs/Basics/Attachments.md b/docs/Basics/Attachments.md index 58260c1c..f09b2b6e 100644 --- a/docs/Basics/Attachments.md +++ b/docs/Basics/Attachments.md @@ -1,6 +1,10 @@ # Attachments -The first column of a transaction view is labeled "A" and stands for "Attachments" +The first column of a transaction view is labeled "A" and stands for "Attachments". +An attachment is a file of any type that you want to associate with your transaction. + +You can drag/drop any file from your desktop onto a transaction and make an Attachment that way. +You can also paste an image onto the selected transaction and it will add the image to the attachments. ![](../Images/Attachments.png) @@ -12,9 +16,16 @@ If you already have an attachment then the dialog loads it up so you can see it: ![](../Images/Attachments1.png) +If the attachment is a non-image format like .pdf you will instead see a file icon: + +![](../Images/Attachments7.png) + +You can double click the icon to open the attachment. You can also delete it, or drag drop more files +or past more images. One transaction can have as many attachments as you like. + If you are keeping receipts for sales tax deduction, check with your accountant to see if you also need to keep the paper copy someplace. -If you want to add a new attachment you can paste an image or any rich text (which is handy if the receipt was in an email) or you can click the **Scan** button : +You can paste an image or any rich text (which is handy if the receipt was in an email) or any file on disk (like a .pdf file) or you can click the **Scan** button : ![](../Images/Attachments2.png) @@ -33,12 +44,9 @@ When you paste rich text content, it can also contain images. ![](../Images/Attachments5.png) -**Drag/Drop** -You can also drag/drop a .png, .jpg or .gif from your desktop onto a transaction and make an Attachment that way. -You can also paste an image onto the selected transaction and it will add the image to the attachments. - **Printing** -Sometimes you need to return an item and you need a print out of the receipt, just select the image and click the print button: +Sometimes you need to return an item and you need a print out of the receipt, just select the image and click the print button, this only works for selected images or rich text. For other file types you will need to use the +appropriate file viewer to print them. ![](../Images/Attachments6.png) diff --git a/docs/Images/Attachments7.png b/docs/Images/Attachments7.png new file mode 100644 index 00000000..99d49602 Binary files /dev/null and b/docs/Images/Attachments7.png differ