ImageInfoView.axaml.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. using Avalonia;
  2. using Avalonia.Controls;
  3. using Avalonia.Input;
  4. using PicView.Avalonia.Converters;
  5. using PicView.Avalonia.FileSystem;
  6. using PicView.Avalonia.ImageHandling;
  7. using PicView.Avalonia.Navigation;
  8. using PicView.Avalonia.Resizing;
  9. using PicView.Avalonia.UI;
  10. using PicView.Avalonia.ViewModels;
  11. using PicView.Core.Extensions;
  12. using PicView.Core.Titles;
  13. using R3;
  14. namespace PicView.Avalonia.Views;
  15. public partial class ImageInfoView : UserControl
  16. {
  17. private readonly CompositeDisposable _disposables = new();
  18. public ImageInfoView()
  19. {
  20. InitializeComponent();
  21. Loaded += (_, _) =>
  22. {
  23. KeyDown += (_, e) =>
  24. {
  25. switch (e.Key)
  26. {
  27. case Key.Down:
  28. case Key.PageDown:
  29. ScrollViewer.LineDown();
  30. break;
  31. case Key.Up:
  32. case Key.PageUp:
  33. ScrollViewer.LineUp();
  34. break;
  35. case Key.Home:
  36. ScrollViewer.ScrollToHome();
  37. break;
  38. case Key.End:
  39. ScrollViewer.ScrollToEnd();
  40. break;
  41. }
  42. };
  43. PointerPressed += (_, e) =>
  44. {
  45. if (!e.GetCurrentPoint(this).Properties.IsRightButtonPressed)
  46. {
  47. return;
  48. }
  49. // Context menu doesn't want to be opened normally
  50. MainContextMenu.Open();
  51. };
  52. CloseItem.Click += (_, _) => (VisualRoot as Window)?.Close();
  53. PixelWidthTextBox.KeyDown += async (s, e) => await ResizeImageOnEnter(s, e);
  54. PixelHeightTextBox.KeyDown += async (s, e) => await ResizeImageOnEnter(s, e);
  55. PixelWidthTextBox.KeyUp += delegate { AdjustAspectRatio(PixelWidthTextBox); };
  56. PixelHeightTextBox.KeyUp += delegate { AdjustAspectRatio(PixelHeightTextBox); };
  57. if (DataContext is not MainViewModel vm)
  58. {
  59. return;
  60. }
  61. Observable.EveryValueChanged(vm.PicViewer.FileInfo, x => x.Value, UIHelper.GetFrameProvider)
  62. .Subscribe(UpdateValues).AddTo(_disposables);
  63. vm.Exif.RemoveImageDataCommand.Delay(TimeSpan.FromSeconds(2)).Subscribe(UpdateValues);
  64. ResetButton.Click += (_, _) =>
  65. {
  66. PixelWidthTextBox.Text = vm.PicViewer.PixelWidth.ToString();
  67. PixelHeightTextBox.Text = vm.PicViewer.PixelHeight.ToString();
  68. AdjustAspectRatio(PixelWidthTextBox);
  69. FullPathTextBox.Text = vm.PicViewer.FileInfo?.CurrentValue.FullName ?? "";
  70. DirectoryNameTextBox.Text = vm.PicViewer.FileInfo?.CurrentValue.DirectoryName ?? "";
  71. FileNameTextBox.Text = vm.PicViewer.FileInfo?.CurrentValue.Name ?? "";
  72. };
  73. SaveButton.Click += async (_, _) =>
  74. {
  75. var ext = GetExtension();
  76. var location = FullPathTextBox.Text; // TODO check if this is a valid path
  77. // and sync with file name/directory text boxes
  78. await SendToImageSaver(vm.PicViewer.FileInfo?.CurrentValue.FullName, location, PixelWidthTextBox.Text,
  79. PixelHeightTextBox.Text, ext).ConfigureAwait(false);
  80. };
  81. SaveAsButton.Click += async (_, _) =>
  82. {
  83. var fileInfoFullName = vm.PicViewer.FileInfo.CurrentValue.FullName;
  84. var ext = DetermineFileExtension(vm, ref fileInfoFullName);
  85. var file = await FilePicker.PickFileForSavingAsync(vm.PicViewer.FileInfo?.CurrentValue.FullName, ext);
  86. if (file is null)
  87. {
  88. return;
  89. }
  90. await SendToImageSaver(vm.PicViewer.FileInfo?.CurrentValue.FullName, file, PixelWidthTextBox.Text,
  91. PixelHeightTextBox.Text, ext).ConfigureAwait(false);
  92. };
  93. FileNameTextBox.KeyDown += async (_, e) =>
  94. {
  95. if (e.Key is not Key.Enter)
  96. {
  97. return;
  98. }
  99. var newPath = Path.Combine(vm.PicViewer.FileInfo.CurrentValue.DirectoryName, FileNameTextBox.Text);
  100. var oldPath = vm.PicViewer.FileInfo.CurrentValue.FullName;
  101. var renamed = await FileRenamer.AttemptRenameAsync(oldPath, newPath, vm).ConfigureAwait(false);
  102. if (renamed)
  103. {
  104. await NavigationManager.LoadPicFromFile(newPath, vm).ConfigureAwait(false);
  105. }
  106. };
  107. FullPathTextBox.KeyDown += async (_, e) =>
  108. {
  109. if (e.Key is not Key.Enter)
  110. {
  111. return;
  112. }
  113. var newPath = FullPathTextBox.Text;
  114. var oldPath = vm.PicViewer.FileInfo.CurrentValue.FullName;
  115. var renamed = await FileRenamer.AttemptRenameAsync(oldPath, newPath, vm).ConfigureAwait(false);
  116. if (renamed)
  117. {
  118. await NavigationManager.LoadPicFromFile(newPath, vm).ConfigureAwait(false);
  119. }
  120. };
  121. DirectoryNameTextBox.KeyDown += async (_, e) =>
  122. {
  123. if (e.Key is not Key.Enter)
  124. {
  125. return;
  126. }
  127. var oldDirectory = vm.PicViewer.FileInfo.CurrentValue.DirectoryName;
  128. var newDirectory = DirectoryNameTextBox.Text;
  129. var oldPath = vm.PicViewer.FileInfo.CurrentValue.FullName;
  130. var newPath = oldPath.Replace(oldDirectory, newDirectory);
  131. await FileRenamer.AttemptRenameAsync(oldPath, newPath, vm).ConfigureAwait(false);
  132. };
  133. };
  134. }
  135. private void UpdateValues(FileInfo? fileInfo)
  136. {
  137. if (DataContext is not MainViewModel vm)
  138. {
  139. return;
  140. }
  141. ExifHandling.UpdateExifValues(vm);
  142. if (DirectoryNameTextBox.Text != fileInfo.DirectoryName)
  143. {
  144. DirectoryNameTextBox.Text = fileInfo.DirectoryName;
  145. }
  146. FileSizeBox.Text = vm.PicViewer.FileInfo?.CurrentValue?.Length.GetReadableFileSize();
  147. ConversionHelper.DetermineIfOptimizeImageShouldBeEnabled(vm);
  148. GoogleLinkButton.IsEnabled = !string.IsNullOrWhiteSpace(vm.Exif.GoogleLink.CurrentValue);
  149. BingLinkButton.IsEnabled = !string.IsNullOrWhiteSpace(vm.Exif.BingLink.CurrentValue);
  150. }
  151. private async Task SendToImageSaver(string? location, string destination, string? width, string? height,
  152. string ext)
  153. {
  154. if (!uint.TryParse(width, out var widthValue) || !uint.TryParse(height, out var heightValue))
  155. {
  156. return;
  157. }
  158. var sameFile = destination.Equals(location, StringComparison.OrdinalIgnoreCase);
  159. await SaveImageHandler.SaveImageWithPossibleNavigation(DataContext as MainViewModel, location, destination,
  160. sameFile, ext, widthValue, heightValue,
  161. null, null, true);
  162. }
  163. private string GetExtension() => ConversionComboBox.SelectedIndex switch
  164. {
  165. 1 => ".png",
  166. 2 => ".jpg",
  167. 3 => ".webp",
  168. 4 => ".avif",
  169. 5 => ".heic",
  170. 6 => ".jxl",
  171. _ => ""
  172. };
  173. private void AdjustAspectRatio(TextBox sender)
  174. {
  175. if (DataContext is not MainViewModel vm)
  176. {
  177. return;
  178. }
  179. var aspectRatio = (double)vm.PicViewer.PixelWidth.CurrentValue / vm.PicViewer.PixelHeight.CurrentValue;
  180. AspectRatioHelper.SetAspectRatioForTextBox(PixelWidthTextBox, PixelHeightTextBox, sender == PixelWidthTextBox,
  181. aspectRatio, DataContext as MainViewModel);
  182. if (!int.TryParse(PixelWidthTextBox.Text, out var width) ||
  183. !int.TryParse(PixelHeightTextBox.Text, out var height))
  184. {
  185. return;
  186. }
  187. if (width <= 0 || height <= 0)
  188. {
  189. return;
  190. }
  191. var printSizes =
  192. AspectRatioHelper.GetPrintSizes(width, height, vm.Exif.DpiX.CurrentValue, vm.Exif.DpiY.CurrentValue);
  193. PrintSizeInchTextBox.Text = printSizes.PrintSizeInch;
  194. PrintSizeCmTextBox.Text = printSizes.PrintSizeCm;
  195. SizeMpTextBox.Text = printSizes.SizeMp;
  196. var gcd = ImageTitleFormatter.GCD(width, height);
  197. AspectRatioTextBox.Text =
  198. AspectRatioHelper.GetFormattedAspectRatio(gcd, vm.PicViewer.PixelWidth.CurrentValue,
  199. vm.PicViewer.PixelHeight.CurrentValue);
  200. }
  201. private static async Task DoResize(MainViewModel vm, bool isWidth, object width, object height)
  202. {
  203. if (isWidth)
  204. {
  205. if (!double.TryParse((string?)width, out var widthValue))
  206. {
  207. return;
  208. }
  209. if (widthValue > 0)
  210. {
  211. var success = await ConversionHelper.ResizeByWidth(vm.PicViewer.FileInfo.CurrentValue, widthValue)
  212. .ConfigureAwait(false);
  213. if (success)
  214. {
  215. await NavigationManager.QuickReload().ConfigureAwait(false);
  216. }
  217. }
  218. }
  219. else
  220. {
  221. if (!double.TryParse((string?)height, out var heightValue))
  222. {
  223. return;
  224. }
  225. if (heightValue > 0)
  226. {
  227. var success = await ConversionHelper.ResizeByHeight(vm.PicViewer.FileInfo.CurrentValue, heightValue)
  228. .ConfigureAwait(false);
  229. if (success)
  230. {
  231. await NavigationManager.QuickReload().ConfigureAwait(false);
  232. }
  233. }
  234. }
  235. }
  236. private async Task ResizeImageOnEnter(object? sender, KeyEventArgs e)
  237. {
  238. if (e.Key == Key.Enter)
  239. {
  240. if (DataContext is not MainViewModel vm)
  241. {
  242. return;
  243. }
  244. await DoResize(vm, Equals(sender, PixelWidthTextBox), PixelWidthTextBox.Text, PixelHeightTextBox.Text)
  245. .ConfigureAwait(false);
  246. }
  247. }
  248. private string DetermineFileExtension(MainViewModel vm, ref string destination)
  249. {
  250. var ext = vm.PicViewer.FileInfo.CurrentValue.Extension;
  251. if (NoConversion.IsSelected)
  252. {
  253. return ext;
  254. }
  255. if (PngItem.IsSelected)
  256. {
  257. ext = ".png";
  258. }
  259. else if (JpgItem.IsSelected)
  260. {
  261. ext = ".jpg";
  262. }
  263. else if (WebpItem.IsSelected)
  264. {
  265. ext = ".webp";
  266. }
  267. else if (AvifItem.IsSelected)
  268. {
  269. ext = ".avif";
  270. }
  271. else if (HeicItem.IsSelected)
  272. {
  273. ext = ".heic";
  274. }
  275. else if (JxlItem.IsSelected)
  276. {
  277. ext = ".jxl";
  278. }
  279. destination = Path.ChangeExtension(destination, ext);
  280. return ext;
  281. }
  282. protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
  283. {
  284. base.OnDetachedFromVisualTree(e);
  285. Disposable.Dispose(_disposables);
  286. }
  287. }